Files
treg/tests/test_agents.py
Jason ZhouandClaude Opus 5.5 ce916f288d feat: /leads-signals launch page, lead-signals skill, one-command skill install (#710)
* feat(web): /intent-signals landing page for the buyer-signals launch

"Claude monitor buyer signals": built from /people-search, keeping its skill
grid (now eight signal tiles from the launch film's real run) and the People
Search Bench. New: a morning-run demo, a three-step 'your agent is the
monitor' section (Claude Code /schedule, Codex from cron, diff against the
last list), the $0.98 four-source receipt, and pay-per-check pricing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(web): /intent-signals scenes from the launch film

Replace the monitor steps, the receipt table and the signal tile grid with
the film's own scenes, rebuilt as HTML: a radar and signal feed that judges
each post, a thread → scored companies → decision makers flow with vendor
waterfall, 'treg monitors…' with evidence cards per signal, and the daily
hot-leads list a scheduled run grows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(web): /intent-signals hero plays the whole film in one take

The hero now runs monitor the topic → read the thread → score companies →
find decision makers as one continuous, clickable demo, replacing the old
hero table and the two separate scene sections. The 'treg monitors…'
section goes back to the skill tile grid, each tile now a small visual
scene (post, new position, news, job postings, funding, headcount chart,
tech adopters, Reddit thread) instead of text rows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(web): /intent-signals headline 'Claude for Signal Monitor' and the real Reddit mark

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* style(web): /intent-signals nav drops the Signals and Daily list links

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(web): /intent-signals works at every width

Tablet keeps every column and a smaller radar beside the feed; phone puts
the radar above the feed and the post above its thread, folds secondary
columns, wraps the prompt bar and truncates names instead of overlapping.
The prompt cursor now honours [hidden], and a new prompt clears the last
reply before it types.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* style(web): /intent-signals headline 'Claude for Monitor Leads Signal' in Geist Pixel

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(web): /intent-signals prices from the billed run, not the film's $0.98

The launch run's LinkedIn, Reddit and X sweep billed $0.52 for 2,945 signals
(4,710 with the free GitHub stargazers): $0.0002 per signal checked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(skills): lead-signals, the /intent-signals workflow as a skill

Detect, qualify, contact, hand back a ranked list; optionally keep watching
on the agent's own schedule with a diff against the last list. It names
signal families and the words to search the catalog with, never endpoint
ids, so it stays right as the catalog grows. Served at
/skills/lead-signals/SKILL.md and as the third entry in the well-known
skills index, like make-ugc.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(skills): lead-signals description says only when to use it; example vendors per signal

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(skills): install our skills with npx skills add superdesigndev/treg

The skills CLI (skills.sh) reads skills/ and skips symlinks, so build_plugin.py
now also writes real copies of the workflow skills (make-ugc, lead-signals)
there, and --check keeps them in step with src/treg/web/skills. The six skills
that are repo tooling carry metadata.internal so the CLI hides them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(skills): vendor-listing is repo tooling, hidden from npx skills add

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(web): /intent-signals shows the one-line skill install

npx skills add superdesigndev/treg --skill lead-signals, beside the treg set-up
command; both copy buttons share one handler and wrap on a phone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(cli): treg skill bootstrap installs every public skill the registry advertises

install.sh already runs it; it now also installs each skill in
/.well-known/skills/index.json (make-ugc, lead-signals) into the same global
agent folders as the treg skill. Best-effort, and a name from the index must
be a plain slug before it becomes a directory.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(skills): lead-signals sends an agent without treg to llms.txt to set up

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(web): the buyer-signals page lives at /leads-signals

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* style(web): /leads-signals tools grid shows Aviato people search instead of Apollo

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* style(web): /leads-signals nav reads Leads signals

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(web): /leads-signals served by _static_page like the other launch pages

The catalog's size comes from {ENDPOINTS}/{PROVIDERS} instead of typed numbers,
and the page carries the shared share image.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(context): the installer also installs the advertised workflow skills

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 19:30:04 +10:00

89 lines
3.9 KiB
Python

"""The per-agent skills-path registry + target resolution (`treg agents`, skill fan-out).
Pure logic over the registry - no DB, no network. Covers: the two-dir default fan-out, all-agents
targeting, global-scope resolution, install detection, and that env overrides (CLAUDE_CONFIG_DIR,
CODEX_HOME, XDG_CONFIG_HOME) are honored at call time.
"""
from __future__ import annotations
from pathlib import Path
from treg import agents as ag
def test_default_fanout_is_agents_plus_claude():
bases = ag.resolve_targets()
assert bases == [Path(".agents/skills"), Path(".claude/skills")]
def test_all_agents_dedupes_shared_bucket():
dirs = ag.resolve_targets(all_agents=True)
# every .agents/skills agent collapses to a single entry
assert dirs.count(Path(".agents/skills")) == 1
assert Path(".claude/skills") in dirs
assert Path(".windsurf/skills") in dirs
# far fewer distinct dirs than agents
assert len(dirs) < len(ag.AGENTS)
def test_single_agent_global_scope(monkeypatch, tmp_path):
monkeypatch.setenv("CODEX_HOME", str(tmp_path / "cx"))
assert ag.resolve_targets(agent="codex", scope_global=True) == [tmp_path / "cx" / "skills"]
def test_global_scope_uses_detected_or_falls_back(monkeypatch, tmp_path):
# point every marker into an empty home so nothing is "installed"
monkeypatch.setattr(ag, "HOME", tmp_path)
monkeypatch.setenv("CLAUDE_CONFIG_DIR", str(tmp_path / ".claude"))
monkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_path / ".config"))
monkeypatch.delenv("CODEX_HOME", raising=False)
assert ag.detect_installed() == []
bases = ag.resolve_targets(scope_global=True)
# fallback: claude global + the standard ~/.agents/skills
assert (tmp_path / ".claude" / "skills") in bases
assert (tmp_path / ag.STANDARD_PROJECT_DIR) in bases
def test_detect_installed_reads_markers(monkeypatch, tmp_path):
monkeypatch.setattr(ag, "HOME", tmp_path)
monkeypatch.setenv("CLAUDE_CONFIG_DIR", str(tmp_path / ".claude"))
monkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_path / ".config"))
monkeypatch.delenv("CODEX_HOME", raising=False)
(tmp_path / ".cursor").mkdir()
(tmp_path / ".claude").mkdir()
detected = ag.detect_installed()
assert "cursor" in detected and "claude-code" in detected
assert "codex" not in detected # ~/.codex absent
def test_claude_config_dir_override(monkeypatch, tmp_path):
monkeypatch.setenv("CLAUDE_CONFIG_DIR", str(tmp_path / "cc"))
assert ag.global_dir("claude-code") == tmp_path / "cc" / "skills"
def test_skill_bootstrap_installs_every_advertised_skill(monkeypatch, tmp_path, capsys):
"""`treg skill bootstrap` (what install.sh runs) drops the treg skill AND every other skill the
registry's well-known index advertises, and never turns a hostile name into a path."""
from types import SimpleNamespace
from treg import cli
pages = {
"/skill.md": "---\nname: treg\n---\n",
"/.well-known/skills/index.json": {"skills": [{"name": "treg"}, {"name": "lead-signals"}, {"name": "../evil"}]},
"/.well-known/skills/lead-signals/SKILL.md": "---\nname: lead-signals\n---\n",
}
def fake_get(url, **_):
body = pages.get(url.removeprefix("https://reg.example"), "")
return SimpleNamespace(status_code=200 if body else 404, text=body if isinstance(body, str) else "",
json=lambda: body, raise_for_status=lambda: None)
monkeypatch.setattr(cli.httpx, "get", fake_get)
monkeypatch.setattr(ag, "resolve_targets", lambda **_: [tmp_path / "skills"])
monkeypatch.delenv("TREG_TOKEN", raising=False)
cli.cmd_skill_bootstrap(SimpleNamespace(project=False, all_agents=False), {"base_url": "https://reg.example"})
installed = sorted(p.parent.name for p in (tmp_path / "skills").glob("*/SKILL.md"))
assert installed == ["lead-signals", "treg"]
assert not (tmp_path / "evil").exists()