claude-skills/agent-launcher/skills/wrap-up/scripts/overview_page.py
Claude d1f2396c6f
feat(agent-launcher): new domain plugin for building Claude Managed Agents
Adds the agent-launcher/ top-level domain — a plugin re-implementation of
Anthropic's launch-your-agent reference skill (Apache-2.0; independent, not a
fork) for building Claude Managed Agents (CMA) in the user's own account.

Every session starts with a goal (./my-agent/goal.json, surfaced by an opt-in
AGENT_LAUNCHER_SESSION=1 SessionStart hook + /cs:goal); loop_compiler.py
compiles that goal into a bounded grade->iterate loop (CMA user.define_outcome
self-grading, max_iterations 1..20), a recurring POSIX-cron scheduled-deployment
loop, or a single-pass interview->stage->launch workflow.

- 6 skills: agent-launcher-orchestrator (context: fork goal router) + interview
  + stage-launch + grade-iterate + run-without-you + wrap-up
- 18 stdlib-only deterministic scaffolder tools (NO network/API calls; live
  launches emitted as BYOK curl that never prints the key); all pass --help/--sample
- 4 agents (orchestrator + interviewer + grader + deployer), 8 /cs:* commands
- opt-in SessionStart/SessionEnd hooks (exit 0 on any error), 5 shared
  references, 4 assets (build-sheet schema + overview/next-directions templates
  + example)
- validators enforce CMA limits (<=20 skills/session, <=8 memory stores,
  depth-1 multiagent, max_iterations <=20, <=1000 deployments/org)
- registered in marketplace.json; headline counters trued up via
  derive_counters.py --check (skills 362->368, domains 18->19, tools 644->664,
  refs 741->746, agents 102->106, commands 116->124, plugins 88->89)

Distinct from engineering/agent-harness (generic bounded loop over any domain)
and engineering/write-a-skill (authors Claude Code skills, not CMAs).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012FwXG6TqCXKZQvF4iD69cv
2026-08-17 02:39:34 +00:00

118 lines
4.5 KiB
Python

#!/usr/bin/env python3
"""overview_page.py — regenerate a single-file ./my-agent/agent-overview.html.
Fills assets/agent-overview.template.html with the build sheet's primitives, the
latest verdict, and the next moves. Self-contained (inline CSS, no external
assets, theme-aware). Stdlib-only; no network calls.
Examples:
overview_page.py --sheet ./my-agent/build-sheet.json --out-dir ./my-agent \
--status live --loop-shape cron-loop --last-verdict satisfied
overview_page.py --sample
"""
import argparse
import datetime as dt
import html
import json
import sys
from pathlib import Path
def _now():
return dt.datetime.now(dt.timezone.utc).replace(microsecond=0).isoformat()
def _esc(x):
return html.escape(str(x), quote=True)
def render(sheet, status, loop_shape, last_verdict, console_link):
prim = sheet.get("primitives", {})
agent = prim.get("agent", {})
env = prim.get("environment", {})
session = prim.get("session", {})
rows = []
def row(k, v):
rows.append(f"<tr><td>{_esc(k)}</td><td>{_esc(v)}</td></tr>")
row("agent model", agent.get("model", "?"))
row("tools", ", ".join(t.get("type", t.get("name", "?")) for t in agent.get("tools", [])) or "—")
if agent.get("skills"):
row("skills", ", ".join(agent["skills"]))
row("environment", f"{env.get('type', 'cloud')} / {env.get('networking', 'unrestricted')}")
if session.get("resources"):
row("resources", ", ".join(r.get("type", "?") for r in session["resources"]))
if session.get("memory_stores"):
row("memory stores", str(len(session["memory_stores"])))
row("outcome", "self-grading rubric" if prim.get("outcome") else "none")
schedule = "none (on-demand)"
if prim.get("deployment"):
s = prim["deployment"].get("schedule", {})
schedule = f"{s.get('expression', '?')} [{s.get('timezone', 'UTC')}]"
iterations = str(prim.get("outcome", {}).get("max_iterations", "-")) if prim.get("outcome") else "-"
moves = []
for d in (sheet.get("deferrals", []) or [])[:2]:
moves.append(f"<li>{_esc(d.get('version', 'v?'))}: {_esc(d.get('item', ''))} — {_esc(d.get('mechanism', ''))}</li>")
if not moves:
moves.append("<li>Monitor the first scheduled runs; tighten the rubric if verdicts drift.</li>")
tmpl = (Path(__file__).resolve().parents[3] / "assets" / "agent-overview.template.html").read_text()
out = tmpl
subs = {
"{{agent_name}}": _esc(sheet.get("agent_name", "agent")),
"{{status}}": _esc(status),
"{{loop_shape}}": _esc(loop_shape or sheet.get("loop_hint", "single-pass")),
"{{goal}}": _esc(sheet.get("goal", "")),
"{{primitives_rows}}": "\n ".join(rows),
"{{last_verdict}}": _esc(last_verdict or "(not graded yet)"),
"{{iterations}}": _esc(iterations),
"{{schedule}}": _esc(schedule),
"{{next_moves}}": "".join(moves),
"{{updated_at}}": _esc(_now()),
"{{console_link}}": _esc(console_link or "https://platform.claude.com/"),
}
for k, v in subs.items():
out = out.replace(k, v)
return out
def main() -> int:
ap = argparse.ArgumentParser(description="Regenerate a single-file agent-overview.html.")
ap.add_argument("--sheet", help="build-sheet.json.")
ap.add_argument("--out-dir", default="./my-agent")
ap.add_argument("--status", default="live")
ap.add_argument("--loop-shape", default=None)
ap.add_argument("--last-verdict", default=None)
ap.add_argument("--console-link", default=None)
ap.add_argument("--stdout", action="store_true")
ap.add_argument("--sample", action="store_true")
args = ap.parse_args()
if args.sample:
sheet = json.loads((Path(__file__).resolve().parents[3] / "assets" / "example-build-sheet.json").read_text())
html_out = render(sheet, "live", "cron-loop", "satisfied", None)
print(html_out[:800] + "\n... [truncated in --sample] ...")
return 0
if not args.sheet:
print("Provide --sheet path.", file=sys.stderr)
return 2
sheet = json.loads(Path(args.sheet).read_text())
html_out = render(sheet, args.status, args.loop_shape, args.last_verdict, args.console_link)
if args.stdout:
print(html_out)
return 0
out_dir = Path(args.out_dir)
out_dir.mkdir(parents=True, exist_ok=True)
dest = out_dir / "agent-overview.html"
dest.write_text(html_out)
print(f"Wrote {dest} ({len(html_out)} bytes, self-contained)")
return 0
if __name__ == "__main__":
raise SystemExit(main())