Boilerplate Generator (Template-Driven)
Primary method: run the v2 scaffold script — it renders everything below
deterministically and writes a .manifest.json provenance file:
python ${CLAUDE_PLUGIN_ROOT}/scripts/scaffold-cli.py <app>/agent-harness \
--app-name <app> \
--protocol <rest|graphql|html-scraping|batchexecute> \
--http-client <httpx|curl_cffi> \
--auth-type <none|cookie|api-key|google-sso> \
--resource <name> [--resource <name> ...] \
[--has-polling] [--has-context] [--has-partial-ids]
Requires pip install jinja2 (TEMPLATE_VERSION 2.1.0). Each repeatable
--resource flag scaffolds a commands/<resource>.py module.
If the script is unavailable, do NOT render templates by hand from
memory — follow skills/shared/RECOVERY.md §scaffold-cli.py Unavailable
(adapt from the newest generated CLI, e.g., capitoltrades/agent-harness/).
All templates live in ${CLAUDE_PLUGIN_ROOT}/templates/*.tpl (Jinja2 syntax).
Generated code must satisfy skills/shared/CONVENTIONS.md — the templates
already encode those rules; never "simplify" them away.
Step 1: Collect Template Variables
| Variable |
Type |
Source |
Example |
app_name |
str |
CLI name (cli-web-<app> → <app>) |
hackernews |
APP_NAME |
str |
UPPER_SNAKE (hyphens → underscores first) |
HACKERNEWS |
AppName |
str |
PascalCase |
HackerNews |
protocol |
enum |
traffic-analysis.json: rest, graphql, html-scraping, batchexecute |
rest |
http_client |
enum |
protection detection: httpx, curl_cffi |
httpx |
auth_type |
enum |
site profile: none, cookie, api-key, google-sso |
cookie |
resources |
list[str] |
<APP>.md endpoint groups (one --resource flag each) |
stories users |
has_polling |
bool |
async/long-running operations exist |
false |
has_context |
bool |
CLI needs use <id> / status context |
false |
has_partial_ids |
bool |
resource IDs support prefix matching |
false |
Casing is derived inside the templates via Jinja2 filters — you only supply
--app-name.
Step 2: Template → Output Map
Template (templates/) |
Output |
Rendered when |
exceptions.py.tpl |
core/exceptions.py |
always |
client_rest_httpx.py.tpl |
core/client.py |
http_client=httpx, non-batchexecute (GraphQL/HTML variants are {% if %} blocks inside) |
client_rest_curl.py.tpl |
core/client.py |
http_client=curl_cffi, non-batchexecute |
client_batchexecute.py.tpl |
core/client.py |
protocol=batchexecute |
auth.py.tpl (UNIFIED) |
core/auth.py |
auth_type != none — google-sso specifics (cookie domain priority, regional forcing) are a {% if auth_type == "google_sso" %} block; there is no separate Google template |
cli_entry.py.tpl |
<app>_cli.py (+ __main__.py) |
always — REPL default, UTF-8 fix, --json propagation |
command_group.py.tpl |
commands/<resource>.py |
once per --resource flag — Click group skeleton wired to the client |
helpers.py.tpl |
utils/helpers.py |
always (handle_errors, print_json) |
helpers_polling.py.tpl |
appended to helpers |
--has-polling |
helpers_context.py.tpl |
appended to helpers |
--has-context |
helpers_partial_ids.py.tpl |
appended to helpers |
--has-partial-ids |
output.py.tpl |
utils/output.py |
always (json_success/json_error) |
setup.py.tpl |
setup.py |
always — namespace packages, entry point, deps per http_client/protocol |
conftest.py.tpl |
tests/conftest.py |
always |
test_e2e.py.tpl |
tests/test_e2e.py |
always — _resolve_cli + TestCLISubprocess fixtures pre-wired |
rpc_types.py.tpl, rpc_encoder.py.tpl, rpc_decoder.py.tpl |
core/rpc/ |
protocol=batchexecute only |
README.md.tpl |
cli_web/<app>/README.md skeleton |
always |
SKILL.md.tpl |
per-CLI skill skeleton |
always (filled in during Phase 4) |
Also produced by the script (not templates): __init__.py files, the vendored
runtime adapters utils/repl_skin.py, utils/doctor.py, and
utils/mcp_server.py (canonical source cli-web-core/cli_web_core/, synced by
cli-web-devkit resync; the entry point registers doctor + mcp-serve from
them), utils/config.py, and .manifest.json
at the harness root recording template_version, profile
(protocol/http_client/auth), resources, and generation timestamp — fleet
tooling (cli-web-devkit drift) depends on it.
Step 3: Profile Recipes
| Site profile |
Flags |
| No-auth + read-only (HN, gh-trending) |
--auth-type none — NO auth.py, NO session.py, NO auth commands (dead code is a checklist FAIL) |
| No-auth + CRUD (Dev.to) |
--auth-type api-key — minimal auth module, auth set-key |
| Cookie auth (reddit, linkedin) |
--auth-type cookie + --has-polling if generation ops exist |
| Google SSO + RPC (notebooklm, stitch) |
--auth-type google-sso --protocol batchexecute — renders rpc/ subpackage + Google block in auth.py |
| Anti-bot protected (Cloudflare/WAF) |
--http-client curl_cffi — see capture/references/protection-detection.md escalation ladder |
| Stateful apps (notebooks/projects) |
add --has-context; UUID-keyed resources add --has-partial-ids |
Step 4: Post-Scaffold Checklist
Then continue with methodology/SKILL.md Step B: fill in endpoint methods in
client.py from <APP>.md, flesh out the scaffolded command modules, and
keep _print_repl_help() in sync (CONVENTIONS.md §REPL Rules).
1---2name: boilerplate3description: Documents the template inventory and variable contract behind scaffold-cli.py — which Jinja2 template renders with which variables for each site profile. Use during Phase 2 scaffolding when choosing scaffold flags or understanding what the generated boilerplate contains. The scaffold-cli.py script is the primary path.4---56# Boilerplate Generator (Template-Driven)78> **Primary method:** run the v2 scaffold script — it renders everything below9> deterministically and writes a `.manifest.json` provenance file:10> ```bash11> python ${CLAUDE_PLUGIN_ROOT}/scripts/scaffold-cli.py <app>/agent-harness \12> --app-name <app> \13> --protocol <rest|graphql|html-scraping|batchexecute> \14> --http-client <httpx|curl_cffi> \15> --auth-type <none|cookie|api-key|google-sso> \16> --resource <name> [--resource <name> ...] \17> [--has-polling] [--has-context] [--has-partial-ids]18> ```19> Requires `pip install jinja2` (TEMPLATE_VERSION 2.1.0). Each repeatable20> `--resource` flag scaffolds a `commands/<resource>.py` module.21>22> **If the script is unavailable**, do NOT render templates by hand from23> memory — follow `skills/shared/RECOVERY.md` §scaffold-cli.py Unavailable24> (adapt from the newest generated CLI, e.g., `capitoltrades/agent-harness/`).2526All templates live in `${CLAUDE_PLUGIN_ROOT}/templates/*.tpl` (Jinja2 syntax).27Generated code must satisfy `skills/shared/CONVENTIONS.md` — the templates28already encode those rules; never "simplify" them away.2930---3132## Step 1: Collect Template Variables3334| Variable | Type | Source | Example |35|----------|------|--------|---------|36| `app_name` | str | CLI name (`cli-web-<app>` → `<app>`) | `hackernews` |37| `APP_NAME` | str | UPPER_SNAKE (hyphens → underscores first) | `HACKERNEWS` |38| `AppName` | str | PascalCase | `HackerNews` |39| `protocol` | enum | traffic-analysis.json: `rest`, `graphql`, `html-scraping`, `batchexecute` | `rest` |40| `http_client` | enum | protection detection: `httpx`, `curl_cffi` | `httpx` |41| `auth_type` | enum | site profile: `none`, `cookie`, `api-key`, `google-sso` | `cookie` |42| `resources` | list[str] | `<APP>.md` endpoint groups (one `--resource` flag each) | `stories users` |43| `has_polling` | bool | async/long-running operations exist | `false` |44| `has_context` | bool | CLI needs `use <id>` / `status` context | `false` |45| `has_partial_ids` | bool | resource IDs support prefix matching | `false` |4647Casing is derived inside the templates via Jinja2 filters — you only supply48`--app-name`.4950## Step 2: Template → Output Map5152| Template (`templates/`) | Output | Rendered when |53|--------------------------|--------|---------------|54| `exceptions.py.tpl` | `core/exceptions.py` | always |55| `client_rest_httpx.py.tpl` | `core/client.py` | `http_client=httpx`, non-batchexecute (GraphQL/HTML variants are `{% if %}` blocks inside) |56| `client_rest_curl.py.tpl` | `core/client.py` | `http_client=curl_cffi`, non-batchexecute |57| `client_batchexecute.py.tpl` | `core/client.py` | `protocol=batchexecute` |58| `auth.py.tpl` (UNIFIED) | `core/auth.py` | `auth_type != none` — google-sso specifics (cookie domain priority, regional forcing) are a `{% if auth_type == "google_sso" %}` block; there is no separate Google template |59| `cli_entry.py.tpl` | `<app>_cli.py` (+ `__main__.py`) | always — REPL default, UTF-8 fix, `--json` propagation |60| `command_group.py.tpl` | `commands/<resource>.py` | once per `--resource` flag — Click group skeleton wired to the client |61| `helpers.py.tpl` | `utils/helpers.py` | always (`handle_errors`, `print_json`) |62| `helpers_polling.py.tpl` | appended to helpers | `--has-polling` |63| `helpers_context.py.tpl` | appended to helpers | `--has-context` |64| `helpers_partial_ids.py.tpl` | appended to helpers | `--has-partial-ids` |65| `output.py.tpl` | `utils/output.py` | always (`json_success`/`json_error`) |66| `setup.py.tpl` | `setup.py` | always — namespace packages, entry point, deps per `http_client`/`protocol` |67| `conftest.py.tpl` | `tests/conftest.py` | always |68| `test_e2e.py.tpl` | `tests/test_e2e.py` | always — `_resolve_cli` + `TestCLISubprocess` fixtures pre-wired |69| `rpc_types.py.tpl`, `rpc_encoder.py.tpl`, `rpc_decoder.py.tpl` | `core/rpc/` | `protocol=batchexecute` only |70| `README.md.tpl` | `cli_web/<app>/README.md` skeleton | always |71| `SKILL.md.tpl` | per-CLI skill skeleton | always (filled in during Phase 4) |7273Also produced by the script (not templates): `__init__.py` files, the vendored74runtime adapters `utils/repl_skin.py`, `utils/doctor.py`, and75`utils/mcp_server.py` (canonical source `cli-web-core/cli_web_core/`, synced by76`cli-web-devkit resync`; the entry point registers `doctor` + `mcp-serve` from77them), `utils/config.py`, and **`.manifest.json`**78at the harness root recording `template_version`, profile79(protocol/http_client/auth), resources, and generation timestamp — fleet80tooling (`cli-web-devkit drift`) depends on it.8182## Step 3: Profile Recipes8384| Site profile | Flags |85|--------------|-------|86| No-auth + read-only (HN, gh-trending) | `--auth-type none` — NO auth.py, NO session.py, NO auth commands (dead code is a checklist FAIL) |87| No-auth + CRUD (Dev.to) | `--auth-type api-key` — minimal auth module, `auth set-key` |88| Cookie auth (reddit, linkedin) | `--auth-type cookie` + `--has-polling` if generation ops exist |89| Google SSO + RPC (notebooklm, stitch) | `--auth-type google-sso --protocol batchexecute` — renders `rpc/` subpackage + Google block in auth.py |90| Anti-bot protected (Cloudflare/WAF) | `--http-client curl_cffi` — see `capture/references/protection-detection.md` escalation ladder |91| Stateful apps (notebooks/projects) | add `--has-context`; UUID-keyed resources add `--has-partial-ids` |9293## Step 4: Post-Scaffold Checklist9495- [ ] `.manifest.json` exists at the harness root with `template_version: 2.1.0`96- [ ] `cli_web/` has NO `__init__.py`; `cli_web/<app>/` HAS one97- [ ] No unresolved Jinja2 markers (`{{`, `{%`) remain in any output file98- [ ] `FILL_IN_BASE_URL` markers noted — replace during implementation99- [ ] `auth_type=none` → no `core/auth.py`, no auth command group anywhere100- [ ] `protocol=batchexecute` → `core/rpc/` has types/encoder/decoder; otherwise `core/rpc/` does NOT exist101- [ ] One `commands/<resource>.py` exists per `--resource` flag, all registered on the CLI group102- [ ] All files pass `python -m py_compile`103104Then continue with methodology/SKILL.md Step B: fill in endpoint methods in105`client.py` from `<APP>.md`, flesh out the scaffolded command modules, and106keep `_print_repl_help()` in sync (CONVENTIONS.md §REPL Rules).