Files
Itamar Zand 59ca9f67ac feat: publish the CLI fleet to PyPI (per-CLI + umbrella)
Wire every cli-web-<app> CLI and a new cli-anything-web umbrella package
into release-please + PyPI Trusted Publishing, so users can install each
CLI individually (pip install cli-web-<app> / uvx cli-web-<app>) or the
whole fleet at once (pip install cli-anything-web).

- release-please-config.json / manifest: add the 20 CLIs + meta umbrella
- meta/: new cli-anything-web metapackage (unpinned deps on all CLIs)
- publish.yml: single generic resolver (tag -> dir) covers every package
- docs.py + README + per-CLI docs + skills: show the published install
- docs/PUBLISHING.md: one-time Trusted Publisher setup for all 22 projects
2026-06-14 12:29:19 +03:00

95 lines
3.6 KiB
Python

"""Generate fleet documentation from registry.json.
README.md's CLI table and install command are generated artifacts: the
content between ``<!-- fleet-table:start -->`` / ``<!-- fleet-table:end -->``
and ``<!-- fleet-install:start -->`` / ``<!-- fleet-install:end -->`` markers
is rewritten from the registry, so the docs can never drift from the fleet
again. ``--check`` mode (used in CI) fails if the README is stale.
"""
from __future__ import annotations
import re
from pathlib import Path
from .registry import Registry
_AUTH_DISPLAY = {
"none": "None",
"cookie": "Cookie",
"google_sso": "Google SSO",
"waf_cookies": "WAF cookies",
"browser (OpenAI SSO)": "Browser (OpenAI SSO)",
"optional (OAuth via token_v2)": "Optional (OAuth)",
"cookie (optional — required for write ops)": "Cookie (optional)",
}
TABLE_START = "<!-- fleet-table:start -->"
TABLE_END = "<!-- fleet-table:end -->"
INSTALL_START = "<!-- fleet-install:start -->"
INSTALL_END = "<!-- fleet-install:end -->"
def render_table(registry: Registry) -> str:
lines = [
f"{len(registry.clis)} real CLIs generated by the plugin — shipped as reference implementations:",
"",
"| CLI | Website | Protocol | Auth | Skill | Description |",
"|-----|---------|----------|------|-------|-------------|",
]
for e in registry.clis:
extra = e.extra
website = extra.get("display_website", e.website)
skill = extra.get("skill", "")
description = extra.get("description", "")
skill_cell = f"[📖 Skill]({skill})" if skill else "—"
auth = _AUTH_DISPLAY.get(e.auth, e.auth[0].upper() + e.auth[1:] if e.auth else e.auth)
lines.append(
f"| [`{e.name}`]({e.app_dir}/) | {website} | {e.protocol} | {auth} "
f"| {skill_cell} | {description} |"
)
return "\n".join(lines)
def render_install(registry: Registry) -> str:
# A no-auth CLI makes the best copy-paste example (runs with zero setup).
sample = next(
(e.name for e in registry.clis if e.auth == "none"),
registry.clis[0].name if registry.clis else "cli-web-gh-trending",
)
return (
"```bash\n"
"# Install the entire fleet (every CLI) in one command:\n"
"pip install cli-anything-web # or: uv tool install cli-anything-web\n"
"\n"
"# ...or install only the CLIs you want, by name (see the table above):\n"
f"pip install {sample}\n"
f"uvx {sample} --help # or run it once, no install, via uvx\n"
"```\n"
"\n"
"> Working from a clone instead? Install a CLI editable with "
"`pip install -e <app>/agent-harness` (see [QUICKSTART](QUICKSTART.md))."
)
def _replace_region(text: str, start: str, end: str, content: str) -> str:
pattern = re.compile(re.escape(start) + r".*?" + re.escape(end), re.S)
if not pattern.search(text):
raise ValueError(f"README markers not found: {start} ... {end}")
return pattern.sub(f"{start}\n{content}\n{end}", text)
def generate(root: Path, check: bool = False) -> bool:
"""Regenerate README regions. Returns True if the README was up to date."""
registry = Registry.load(root / "registry.json")
readme = root / "README.md"
original = readme.read_text(encoding="utf-8")
updated = _replace_region(original, TABLE_START, TABLE_END, render_table(registry))
updated = _replace_region(updated, INSTALL_START, INSTALL_END, render_install(registry))
fresh = updated == original
if not check and not fresh:
readme.write_text(updated, encoding="utf-8")
return fresh