← back to Cli Printing Press
docs: add plan files to repo
875269222582c98de76876fe8cbac3fc0828fcf4 · 2026-03-23 09:56:41 -0700 · Matt Van Horn
Files touched
A docs/plans/2026-03-23-feat-cli-printing-press-phase1-template-engine-plan.mdA docs/plans/2026-03-23-feat-cli-printing-press-phase2-openapi-parser-plan.mdA docs/plans/2026-03-23-feat-cli-printing-press-plan.md
Diff
commit 875269222582c98de76876fe8cbac3fc0828fcf4
Author: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Date: Mon Mar 23 09:56:41 2026 -0700
docs: add plan files to repo
---
...i-printing-press-phase1-template-engine-plan.md | 406 +++++++++++++++
...li-printing-press-phase2-openapi-parser-plan.md | 196 +++++++
.../2026-03-23-feat-cli-printing-press-plan.md | 572 +++++++++++++++++++++
3 files changed, 1174 insertions(+)
diff --git a/docs/plans/2026-03-23-feat-cli-printing-press-phase1-template-engine-plan.md b/docs/plans/2026-03-23-feat-cli-printing-press-phase1-template-engine-plan.md
new file mode 100644
index 00000000..abf517de
--- /dev/null
+++ b/docs/plans/2026-03-23-feat-cli-printing-press-phase1-template-engine-plan.md
@@ -0,0 +1,406 @@
+---
+title: "CLI Printing Press Phase 1: Go Template Engine"
+type: feat
+status: active
+date: 2026-03-23
+origin: docs/plans/2026-03-23-feat-cli-printing-press-plan.md
+---
+
+# CLI Printing Press Phase 1: Go Template Engine
+
+## Goal
+
+Build the core template engine that generates production Go + Cobra CLI projects from a structured API description. Not from OpenAPI yet - from a simpler internal YAML format that we control. OpenAPI parsing comes in Phase 2.
+
+By the end of Phase 1: `printing-press generate --spec stytch.yaml` produces a complete, compilable, runnable Go CLI tool with Steinberger-quality patterns.
+
+## What Gets Built
+
+A Go CLI tool called `printing-press` that:
+1. Reads a YAML API description file
+2. Generates a complete Go + Cobra project directory
+3. The generated project compiles with `go build`
+4. The generated project runs: `<tool> --help`, `<tool> <command> --json`, `<tool> doctor`
+5. The generated project follows every Steinberger pattern extracted from wacli/discrawl/sag
+
+## The Internal API Description Format
+
+This is the input format. Simpler than OpenAPI, designed to be easy for agents to generate.
+
+```yaml
+# stytch.yaml - API definition for CLI Printing Press
+name: stytch
+description: "Stytch authentication API CLI"
+version: "0.1.0"
+base_url: "https://api.stytch.com/v1"
+
+auth:
+ type: api_key # api_key | oauth2 | bearer_token | none
+ header: "Authorization"
+ format: "Basic {project_id}:{secret}"
+ env_vars:
+ - STYTCH_PROJECT_ID
+ - STYTCH_SECRET
+
+config:
+ format: toml # toml | yaml
+ path: "~/.config/stytch-cli/config.toml"
+
+resources:
+ users:
+ description: "Manage Stytch users"
+ endpoints:
+ list:
+ method: GET
+ path: "/users"
+ description: "List all users"
+ params:
+ - name: limit
+ type: int
+ default: 100
+ description: "Max users to return"
+ - name: cursor
+ type: string
+ description: "Pagination cursor"
+ response:
+ type: array
+ item: User
+ pagination:
+ type: cursor
+ cursor_field: "cursor"
+ has_more_field: "results.has_more"
+
+ get:
+ method: GET
+ path: "/users/{user_id}"
+ description: "Get a user by ID"
+ params:
+ - name: user_id
+ type: string
+ required: true
+ positional: true
+ description: "User ID"
+ response:
+ type: object
+ item: User
+
+ create:
+ method: POST
+ path: "/users"
+ description: "Create a new user"
+ body:
+ - name: email
+ type: string
+ description: "User email"
+ - name: phone_number
+ type: string
+ description: "User phone"
+ - name: name
+ type: object
+ fields:
+ - name: first_name
+ type: string
+ - name: last_name
+ type: string
+ response:
+ type: object
+ item: User
+
+ delete:
+ method: DELETE
+ path: "/users/{user_id}"
+ description: "Delete a user"
+ params:
+ - name: user_id
+ type: string
+ required: true
+ positional: true
+
+ sessions:
+ description: "Manage user sessions"
+ endpoints:
+ list:
+ method: GET
+ path: "/sessions"
+ params:
+ - name: user_id
+ type: string
+ required: true
+ response:
+ type: array
+ item: Session
+
+ revoke:
+ method: POST
+ path: "/sessions/revoke"
+ body:
+ - name: session_id
+ type: string
+ required: true
+
+types:
+ User:
+ fields:
+ - name: user_id
+ type: string
+ - name: email
+ type: string
+ - name: phone_number
+ type: string
+ - name: status
+ type: string
+ - name: created_at
+ type: string
+ Session:
+ fields:
+ - name: session_id
+ type: string
+ - name: user_id
+ type: string
+ - name: started_at
+ type: string
+ - name: expires_at
+ type: string
+```
+
+## What Gets Generated
+
+From the YAML above, `printing-press generate --spec stytch.yaml` produces:
+
+```
+stytch-cli/
+ cmd/stytch-cli/main.go # Thin main (~15 lines, discrawl pattern)
+ internal/cli/
+ root.go # Cobra root, persistent flags (--json, --timeout, --config)
+ output.go # Tri-mode: human/json/plain via type-switch
+ helpers.go # parseTime, truncate, csvList, typed exit codes
+ version.go # var version = "0.1.0"
+ doctor.go # Health check: config, auth, API connectivity
+ users.go # users list, users get, users create, users delete
+ sessions.go # sessions list, sessions revoke
+ internal/config/
+ config.go # TOML config, Load(), Write(), auth resolution
+ internal/client/
+ client.go # HTTP client: base URL, auth, timeout, retry
+ pagination.go # Cursor-based pagination helper
+ internal/types/
+ types.go # User, Session structs with JSON tags
+ go.mod # cobra, go-toml, testify
+ go.sum
+ .goreleaser.yaml # 6 targets, ldflags, checksums
+ .golangci.yml # Standard linter config
+ Makefile # build, test, lint, install
+ README.md # Generated from API description
+ SPEC.md # Agent build contract (discrawl pattern)
+```
+
+### Key Generated Patterns (from Steinberger source code analysis)
+
+**main.go** (discrawl pattern):
+```go
+package main
+
+import (
+ "context"
+ "fmt"
+ "os"
+
+ "github.com/USER/stytch-cli/internal/cli"
+)
+
+func main() {
+ if err := cli.Execute(context.Background(), os.Args[1:]); err != nil {
+ fmt.Fprintln(os.Stderr, err.Error())
+ os.Exit(cli.ExitCode(err))
+ }
+}
+```
+
+**root.go** (wacli pattern):
+```go
+func Execute(ctx context.Context, args []string) error {
+ var flags rootFlags
+ rootCmd := &cobra.Command{
+ Use: "stytch-cli",
+ Short: "Stytch authentication API CLI",
+ SilenceUsage: true,
+ SilenceErrors: true,
+ Version: version,
+ }
+ rootCmd.PersistentFlags().BoolVar(&flags.asJSON, "json", false, "Output as JSON")
+ rootCmd.PersistentFlags().BoolVar(&flags.plain, "plain", false, "Output as plain text")
+ rootCmd.PersistentFlags().BoolVar(&flags.quiet, "quiet", false, "Bare output")
+ rootCmd.PersistentFlags().StringVar(&flags.configPath, "config", "", "Config file path")
+ rootCmd.PersistentFlags().DurationVar(&flags.timeout, "timeout", 30*time.Second, "Request timeout")
+ // ... add subcommands ...
+ rootCmd.SetArgs(args)
+ return rootCmd.Execute()
+}
+```
+
+**output.go** (discrawl tri-mode pattern):
+```go
+func (r *rootFlags) print(cmd *cobra.Command, value any) error {
+ if r.asJSON {
+ enc := json.NewEncoder(cmd.OutOrStdout())
+ enc.SetIndent("", " ")
+ return enc.Encode(value)
+ }
+ if r.plain {
+ return printPlain(cmd.OutOrStdout(), value)
+ }
+ return printHuman(cmd.OutOrStdout(), value)
+}
+```
+
+**Typed exit codes** (discrawl pattern):
+```go
+type cliError struct {
+ code int
+ err error
+}
+
+func usageErr(err error) error { return &cliError{code: 2, err: err} }
+func configErr(err error) error { return &cliError{code: 3, err: err} }
+func authErr(err error) error { return &cliError{code: 4, err: err} }
+func apiErr(err error) error { return &cliError{code: 5, err: err} }
+```
+
+**doctor.go** (wacli/discrawl pattern):
+```go
+func newDoctorCmd(flags *rootFlags) *cobra.Command {
+ return &cobra.Command{
+ Use: "doctor",
+ Short: "Check CLI health",
+ RunE: func(cmd *cobra.Command, args []string) error {
+ report := map[string]any{}
+ // Check config
+ // Check auth credentials
+ // Check API connectivity (GET base_url with timeout)
+ return flags.print(cmd, report)
+ },
+ }
+}
+```
+
+## Implementation Steps
+
+### Step 1: Project scaffold
+
+Create the `cli-printing-press` Go project:
+```
+cli-printing-press/
+ cmd/printing-press/main.go
+ internal/
+ spec/spec.go # Parse the YAML spec format
+ generator/generator.go # Orchestrate generation
+ templates/ # Go template files
+ main.go.tmpl
+ root.go.tmpl
+ output.go.tmpl
+ helpers.go.tmpl
+ version.go.tmpl
+ doctor.go.tmpl
+ command.go.tmpl # Per-resource command file
+ config.go.tmpl
+ client.go.tmpl
+ pagination.go.tmpl
+ types.go.tmpl
+ go.mod.tmpl
+ goreleaser.yaml.tmpl
+ golangci.yml.tmpl
+ makefile.tmpl
+ readme.md.tmpl
+ spec.md.tmpl
+ testdata/
+ stytch.yaml # Test spec (from above)
+ clerk.yaml # Second test spec
+ loops.yaml # Third test spec (simplest)
+ go.mod
+ go.sum
+```
+
+### Step 2: Spec parser
+
+Parse the YAML spec into Go structs. Validate: required fields present, types valid, no duplicate resource names, auth type recognized.
+
+### Step 3: Templates
+
+Write Go templates for each generated file. Use `text/template` with custom functions:
+- `toLower`, `toTitle`, `toCamel`, `toSnake`, `toKebab`
+- `pluralize`, `singularize`
+- `cobraFlagType` (maps spec types to Cobra flag types)
+- `goType` (maps spec types to Go types)
+- `jsonTag` (generates `json:"name"` tags)
+
+### Step 4: Generator
+
+Wire spec parser -> templates -> file writer. For each resource in the spec, generate a command file. Wire all commands into root.go.
+
+### Step 5: Validation
+
+After generation, run the quality gates:
+1. `go mod tidy` on generated project
+2. `go vet ./...`
+3. `go build ./...`
+4. Run `<tool> --help` and verify output
+5. Run `<tool> --version` and verify
+6. Run `<tool> doctor` and verify it reports config/auth status
+
+### Step 6: Test with 3 real APIs
+
+Write spec files for Stytch, Clerk, and Loops. Generate all 3. Verify all compile and run.
+
+## Acceptance Criteria
+
+- [ ] `printing-press generate --spec stytch.yaml` produces a directory
+- [ ] Generated project compiles with `go build ./...`
+- [ ] Generated project has `--help` on every command
+- [ ] Generated project has `--json` on every command
+- [ ] Generated project has `--version`
+- [ ] Generated project has `doctor` command
+- [ ] Generated project has tri-mode output (human/json/plain)
+- [ ] Generated project has typed exit codes (0, 2, 3, 4, 5)
+- [ ] Generated project has TOML config support
+- [ ] Generated project has auth resolution (env var -> config -> flag)
+- [ ] Generated project has `.goreleaser.yaml` for 6 targets
+- [ ] Generated project has `.golangci.yml`
+- [ ] Generated project has `Makefile`
+- [ ] Generated project has `README.md` from API description
+- [ ] Generated project has `SPEC.md` agent build contract
+- [ ] 3 test specs (Stytch, Clerk, Loops) all generate and compile
+- [ ] `go vet` passes on all generated projects
+- [ ] Quality gate tracker verifies all gates pass
+
+## What Phase 1 Does NOT Include
+
+- OpenAPI spec parsing (Phase 2)
+- Natural language API description (Phase 3)
+- Claude Code skill / plugin packaging (Phase 4)
+- Community catalog / PR submission flow (Phase 4)
+- Security validation for catalog entries (Phase 4)
+- Homebrew formula (Phase 4)
+- OAuth2 auth template (Phase 2 - only api_key and bearer_token in Phase 1)
+
+## Technical Decisions
+
+- **Language**: Go (we're generating Go CLIs, the generator should also be Go)
+- **Template engine**: `text/template` (stdlib, no dependencies)
+- **Spec format**: YAML via `gopkg.in/yaml.v3`
+- **CLI framework for printing-press itself**: Cobra (dogfooding our own patterns)
+- **Testing**: testify + golden file tests (compare generated output against expected)
+
+## Learnings to Capture for Phase 2
+
+After Phase 1 ships, document:
+1. Which Steinberger patterns were hardest to template?
+2. Which parts of the YAML spec format were awkward? What should change?
+3. How well do the generated CLIs actually work against real APIs?
+4. What's the delta between generated and hand-written for the 3 test APIs?
+5. What did the quality gates catch?
+
+These feed directly into the Phase 2 plan (OpenAPI parser).
+
+## Origin
+
+Phase 1 of: docs/plans/2026-03-23-feat-cli-printing-press-plan.md
diff --git a/docs/plans/2026-03-23-feat-cli-printing-press-phase2-openapi-parser-plan.md b/docs/plans/2026-03-23-feat-cli-printing-press-phase2-openapi-parser-plan.md
new file mode 100644
index 00000000..0a659533
--- /dev/null
+++ b/docs/plans/2026-03-23-feat-cli-printing-press-phase2-openapi-parser-plan.md
@@ -0,0 +1,196 @@
+---
+title: "CLI Printing Press Phase 2: OpenAPI Parser"
+type: feat
+status: active
+date: 2026-03-23
+origin: docs/plans/2026-03-23-feat-cli-printing-press-plan.md
+---
+
+# CLI Printing Press Phase 2: OpenAPI Parser
+
+## Goal
+
+Add `printing-press generate --spec openapi.yaml` that accepts OpenAPI 3.0+ specs and maps them to our internal YAML format, then runs the existing generator. The user should be able to take any published OpenAPI spec (Stytch, Clerk, ClickUp, Discord) and get a production CLI.
+
+By the end of Phase 2: `printing-press generate --spec https://raw.githubusercontent.com/stytchauth/stytch-openapi/main/openapi.yaml` produces a working CLI directly from the official spec.
+
+## Learnings from Phase 1 (Applied Here)
+
+Building Phase 1 revealed several things that directly shape Phase 2:
+
+### 1. Template bugs surface late
+
+The config template had a field/method name collision (`AuthHeader` as both a struct field and method) that only showed up when the generated code compiled. The command template had a `replacePathParam` function duplicated across per-resource files. These were invisible until `go build` ran on the output.
+
+**Applied to Phase 2**: The OpenAPI-to-internal-spec mapping must be validated BEFORE passing to the generator. Don't just map and hope - parse the internal spec back through `spec.Validate()` to catch issues early.
+
+### 2. The internal YAML format is good but has gaps
+
+Writing the Stytch/Clerk/Loops specs by hand revealed:
+- No way to express **nested body objects** cleanly (Param.Fields exists but the generator doesn't handle it well)
+- No way to express **response pagination patterns** that differ from query pagination
+- No way to express **enum values** for parameters (the `enum` type in Cobra flags was hand-coded in the template)
+- No way to express **header-based auth** vs **query-based auth** (Stytch uses Basic auth in headers, some APIs use API keys in query params)
+- The `auth.format` field is a string template (`"Basic {project_id}:{secret}"`) which is fragile
+
+**Applied to Phase 2**: The OpenAPI parser should handle these natively and produce an internal spec that includes: nested body schemas, response envelope paths, enum constraints, and auth scheme details. Extend the internal spec format where needed.
+
+### 3. Template functions are the critical integration point
+
+The generator has 12 custom template functions (`goType`, `cobraFlagFunc`, `defaultVal`, `envVarField`, etc.). Every spec field that needs to become Go code flows through these functions. When the OpenAPI parser adds new types (e.g., `array of strings`, `object with nested fields`, `number with enum constraints`), these functions must handle them.
+
+**Applied to Phase 2**: Add comprehensive type mapping tests. Every OpenAPI type combination (string, integer, boolean, array, object, string+enum, string+format:date-time, etc.) must have a test showing what Go type and Cobra flag type it maps to.
+
+### 4. Quality gates catch real problems
+
+The validate.go gates (go mod tidy, go vet, go build, --help, version, doctor) caught issues that would have been silent in Phase 1. The integration test running all 3 specs took 25 seconds - fast enough to run on every change.
+
+**Applied to Phase 2**: Every OpenAPI spec we test against must pass all quality gates. If a generated CLI doesn't compile, the spec mapping is wrong. Add the 5 real-world specs (Stytch, Clerk, ClickUp, Discord, Asana) as test fixtures.
+
+### 5. Codex delegation worked well for mechanical tasks
+
+Codex built validate.go, the test specs, and the integration tests in one shot. The prompt worked best when it was: "here's what exists, here's exactly what to build, here are the rules." The structured prompt format (context -> task -> rules) produced clean results.
+
+**Applied to Phase 2**: The OpenAPI mapping logic is mechanical (schema -> type, path -> endpoint, security -> auth). Good candidate for Codex delegation. The edge-case handling (weird OpenAPI patterns) is better for Claude.
+
+## What Gets Built
+
+### New file: `internal/openapi/parser.go`
+
+Parses OpenAPI 3.0+ YAML/JSON specs into our `spec.APISpec` internal format.
+
+### New file: `internal/openapi/parser_test.go`
+
+Tests with real-world OpenAPI specs from GitHub.
+
+### Modified: `internal/cli/root.go`
+
+The `generate` command auto-detects whether `--spec` points to an OpenAPI spec or our internal format. Detection: check for `openapi:` key at the top level of the YAML.
+
+### Modified: `internal/spec/spec.go`
+
+Extend the internal format with:
+- `Param.Enum []string` - enum constraints
+- `Param.Format string` - OpenAPI format hints (date-time, email, uri, etc.)
+- `Endpoint.ResponsePath string` - path to extract the data array from response (e.g., `"data"`, `"results.items"`)
+- `AuthConfig.Scheme string` - OpenAPI security scheme name
+- `AuthConfig.In string` - header, query, cookie
+
+### New: `testdata/openapi/` directory
+
+Real OpenAPI specs downloaded from:
+- `stytch-openapi.yaml` (github.com/stytchauth/stytch-openapi)
+- `clerk-openapi.yaml` (clerk.com/docs/reference)
+- `clickup-openapi.json` (developer.clickup.com)
+
+## Implementation Steps
+
+### Step 1: Extend internal spec format
+
+Add the new fields to `spec.go`. Update validation. Update existing tests.
+
+Files: `internal/spec/spec.go`, `internal/spec/spec_test.go`
+
+### Step 2: Build OpenAPI parser
+
+Use `github.com/getkin/kin-openapi/openapi3` (the most popular Go OpenAPI 3.0 parser, 2.1K stars).
+
+Map OpenAPI concepts to internal spec:
+- `info.title` -> `name` (kebab-cased)
+- `info.description` -> `description`
+- `info.version` -> `version`
+- `servers[0].url` -> `base_url`
+- `security` + `components.securitySchemes` -> `auth`
+- `paths` -> `resources` (group by first path segment, e.g., `/users/{id}` -> resource `users`)
+- `paths.{path}.{method}` -> `endpoints` (name from operationId or path+method)
+- `parameters` -> `params` (path params are positional, query params are flags)
+- `requestBody` -> `body`
+- `responses.200.content.application/json.schema` -> `response`
+- `components.schemas` -> `types`
+
+Files: `internal/openapi/parser.go`
+
+### Step 3: Auto-detection in CLI
+
+When `--spec` is provided, peek at the first few bytes. If it contains `openapi:` or `"openapi"`, parse as OpenAPI. Otherwise, parse as internal format.
+
+Files: `internal/cli/root.go`
+
+### Step 4: Download real OpenAPI specs for testing
+
+```bash
+# Stytch
+curl -o testdata/openapi/stytch.yaml https://raw.githubusercontent.com/stytchauth/stytch-openapi/main/openapi/stytch_api.yaml
+
+# Clerk
+curl -o testdata/openapi/clerk.json https://clerk.com/docs/reference/backend-api/openapi.json
+
+# Discord (large, for stress testing)
+curl -o testdata/openapi/discord.json https://raw.githubusercontent.com/discord/discord-api-spec/main/specs/openapi.json
+```
+
+Files: `testdata/openapi/`
+
+### Step 5: Integration tests with real specs
+
+Generate CLIs from all 3 real OpenAPI specs. Run quality gates. Verify compilation.
+
+Files: `internal/openapi/parser_test.go`
+
+### Step 6: Handle OpenAPI edge cases
+
+Real OpenAPI specs have:
+- `$ref` references (must resolve)
+- `allOf`/`oneOf`/`anyOf` (common in request bodies)
+- Deeply nested schemas
+- Parameters defined at path level (inherited by all operations)
+- Multiple security schemes (pick the simplest)
+- Response schemas with wrappers (data is nested inside `{"data": [...]}`)
+- Pagination via headers (Link header) or response fields
+
+The kin-openapi library handles `$ref` resolution. For `allOf`, merge properties. For `oneOf`/`anyOf`, use `any` type. For nested schemas, flatten to max 2 levels.
+
+## Acceptance Criteria
+
+- [ ] `printing-press generate --spec stytch-openapi.yaml` produces a compilable CLI
+- [ ] `printing-press generate --spec clerk-openapi.json` produces a compilable CLI
+- [ ] Auto-detection: OpenAPI specs and internal specs both work with `--spec`
+- [ ] `$ref` resolution works (schemas reference other schemas)
+- [ ] `allOf` merging works (common in request bodies)
+- [ ] Path parameters become positional args
+- [ ] Query parameters become flags
+- [ ] Request body fields become flags
+- [ ] Security schemes map to auth config
+- [ ] Response schemas map to types
+- [ ] Enum constraints are preserved
+- [ ] All quality gates pass on generated CLIs
+- [ ] Existing internal-format specs still work (no regressions)
+
+## What Phase 2 Does NOT Include
+
+- URL-based spec fetching (`--spec https://...`) - nice-to-have, not required
+- OpenAPI 2.0 (Swagger) support - only OpenAPI 3.0+
+- OAuth2 flow generation (just maps to bearer_token for now)
+- Webhook handling
+- Server-sent events / streaming endpoints
+
+## Technical Decisions
+
+- **OpenAPI library**: `github.com/getkin/kin-openapi/openapi3` - handles parsing, validation, and `$ref` resolution. Used by Kubernetes, CoreDNS, and many production tools.
+- **Resource grouping**: Group endpoints by first path segment. `/users/{id}` and `/users` both go under `users` resource. `/users/{id}/sessions` goes under `users` with nested path.
+- **Operation naming**: Use `operationId` if present, otherwise `method + last path segment` (e.g., `GET /users/{id}` -> `get`, `POST /users` -> `create`, `DELETE /users/{id}` -> `delete`).
+- **Type mapping**: OpenAPI `string` -> Go `string`, `integer` -> `int`, `boolean` -> `bool`, `number` -> `float64`, `array` -> use item type, `object` -> generate struct in types.
+
+## Risks
+
+| Risk | Impact | Mitigation |
+|------|--------|------------|
+| Real OpenAPI specs are messy/incomplete | Generated CLI is missing endpoints | Log warnings for skipped endpoints. Generate what we can, skip what we can't. |
+| kin-openapi library has quirks | Parser bugs | Pin version, write integration tests against real specs |
+| Type mapping gets complex (nested objects, arrays of objects) | Generated Go code won't compile | Start with flat types, add nesting incrementally. Flatten to `json.RawMessage` for anything too complex. |
+| allOf/oneOf/anyOf handling | Complex schemas produce bad Go types | allOf: merge properties. oneOf/anyOf: use `any` type. Good enough for v1. |
+
+## Origin
+
+Phase 2 of: docs/plans/2026-03-23-feat-cli-printing-press-plan.md
+Builds on: docs/plans/2026-03-23-feat-cli-printing-press-phase1-template-engine-plan.md (completed)
diff --git a/docs/plans/2026-03-23-feat-cli-printing-press-plan.md b/docs/plans/2026-03-23-feat-cli-printing-press-plan.md
new file mode 100644
index 00000000..2a3882e6
--- /dev/null
+++ b/docs/plans/2026-03-23-feat-cli-printing-press-plan.md
@@ -0,0 +1,572 @@
+---
+title: "CLI Printing Press: Describe Your API, Get a Production CLI"
+type: feat
+status: active
+date: 2026-03-23
+---
+
+# CLI Printing Press
+
+## Describe your API. Get a production CLI.
+
+Steinberger writes the same fetch-parse-output pattern across four languages. gogcli (Go, 6.5K stars), wacli (Go, 677 stars), oracle (TypeScript, 1.7K stars), imsg (Swift, 914 stars) - each one requires import statements, an argument parsing library, an HTTP client, a JSON parser, output formatting, error handling boilerplate, and build configuration. Measured in wacli's actual code: 79% boilerplate, 21% business logic.
+
+Every developer building agent-accessible tools hits this. Every company wrapping an API in a CLI rewrites the same scaffolding. Stainless proved the model works at $30K/year generating CLIs from OpenAPI specs - but they sell to API providers (Stripe, OpenAI), not to the developers who want to USE those APIs from the terminal.
+
+CLI Printing Press fills that gap. Describe what API you want to wrap - in English, or with an OpenAPI spec - and get a production Go CLI with Steinberger-quality patterns. Single binary. `--json` on every command. Auth management. Structured output. Proper exit codes. Ready to `brew install`.
+
+## The Market Gap
+
+| Layer | Who it serves | Examples | Gap |
+|-------|--------------|---------|-----|
+| Enterprise SDK generators | API providers ($30K/yr) | Stainless ($25M raised), Speakeasy, Fern (acquired by Postman) | Too expensive for individual developers |
+| Abandoned OSS generators | Nobody (unmaintained) | danielgtaylor/openapi-cli-generator (195 stars) | Dead projects |
+| CLI frameworks | Developers who write CLIs by hand | Cobra (43.5K), Typer (19K), Clap (16.3K) | Still requires writing all the business logic |
+| **API consumer CLI generator** | **Developers who want to USE APIs** | **Nobody** | **This is the gap** |
+
+## What It Produces
+
+Given: "I want a CLI for the GitHub API that can list repos, create issues, and manage PRs"
+
+CLI Printing Press generates a complete Go project:
+
+```
+github-cli/
+ main.go # Cobra root command, version, help
+ cmd/
+ repos_list.go # gh-cli repos list --user steipete --limit 10
+ repos_get.go # gh-cli repos get --owner steipete --name wacli
+ issues_create.go # gh-cli issues create --repo steipete/wacli --title "Bug"
+ issues_list.go # gh-cli issues list --repo steipete/wacli --state open
+ prs_list.go # gh-cli prs list --repo steipete/wacli
+ prs_merge.go # gh-cli prs merge --repo steipete/wacli --number 42
+ internal/
+ client.go # HTTP client with auth, retries, rate limiting
+ output.go # --json, --table, --csv output formatting
+ auth.go # Bearer token, OAuth, cookie auth management
+ config.go # Config file (~/.github-cli.yaml)
+ go.mod
+ go.sum
+ Makefile # build, test, install, brew-formula
+ README.md # Generated from API docs
+```
+
+Every generated command follows the Steinberger pattern:
+- `--json` flag on every command (machine-readable for agents)
+- Human-readable table output by default (for humans)
+- Proper exit codes (0 success, 1 user error, 2 API error)
+- `--help` with examples generated from API docs
+- Auth from env var, config file, or keychain
+- Timeout, retry, and rate limiting built in
+
+## The Steinberger Patterns (Extracted from 7 Go Repos)
+
+Analyzed every Go CLI Steinberger has built: wacli, gogcli, discrawl, sag, brabble, sonoscli, camsnap. These patterns repeat across all of them.
+
+### Framework Choice
+- **5/7 use Cobra** (wacli, brabble, sonoscli, camsnap, sag)
+- **1 uses Kong** (gogcli - the most complex one, ~80 commands)
+- **1 uses stdlib `flag`** (discrawl - the simplest approach)
+- **Template default: Cobra** (covers 90% of use cases)
+
+### Project Structure (universal across all repos)
+```
+cmd/<binary>/main.go # Thin: ~15 lines, calls Execute() + os.Exit()
+internal/
+ cli/
+ root.go # Root command, persistent flags (--json, --timeout, --config)
+ <feature>.go # One file per command group
+ output.go # Tri-mode formatting (human/json/tsv)
+ doctor.go # Health checks (present in 5/7 repos)
+ version.go # Version command
+ config/config.go # Config loading with defaults
+ <domain>/ # Business logic packages
+.goreleaser.yaml # GoReleaser v2 for builds
+.golangci.yml # Linter config
+.github/workflows/
+ ci.yml # CI pipeline
+ release.yml # Auto-release
+```
+
+### Tri-Mode Output (every CLI)
+| Mode | Flag | Format | Use case |
+|------|------|--------|----------|
+| Human | default | Colored tables via `text/tabwriter` | Interactive terminal |
+| JSON | `--json` (persistent/global) | Pretty-printed, sometimes wrapped in `{success, data, error}` envelope | Agents, piping |
+| Plain/TSV | `--plain` or `--format tsv` | Tab-separated, no headers | awk/cut/grep piping |
+
+sonoscli evolved this to `--format plain|json|tsv` with `--json` deprecated. gogcli adds `--results-only` to strip the envelope and `--select` for field selection.
+
+### Auth Hierarchy (4 levels, template generates the right one)
+| Level | Method | Example repo | When to use |
+|-------|--------|-------------|-------------|
+| 0 | None | brabble (local daemon) | Local-only tools |
+| 1 | Env var + flag | sag (`ELEVENLABS_API_KEY`) | Simple API key auth |
+| 2 | Config file token | discrawl (TOML), camsnap (YAML) | Token from multiple sources |
+| 3 | QR/device auth | wacli (WhatsApp linked devices) | Messaging platforms |
+| 4 | OAuth2 + keyring | gogcli (`99designs/keyring`, multi-account) | Google/Microsoft/enterprise APIs |
+
+### Structured Exit Codes (gogcli is the gold standard)
+```
+0 success
+1 generic error
+2 usage/parse error
+3 empty results
+4 auth required
+5 not found
+6 permission denied
+7 rate limited
+8 retryable (timeout, server error)
+10 config error
+130 cancelled (SIGINT)
+```
+
+### Standard Subcommands (appear across multiple repos)
+- `auth` - authentication flow (gogcli, sonoscli)
+- `doctor` - health checks (5/7 repos)
+- `version` - version info (all repos)
+- `status` - current state (discrawl, brabble, sonoscli)
+- `sync` - data synchronization (wacli, discrawl)
+- `watch`/`tail` - live monitoring (sonoscli, camsnap, discrawl)
+
+### Config Pattern
+- XDG-style: `~/.config/<tool>/config.{toml,yaml}`
+- Precedence: `--config` flag > env var (`<TOOL>_CONFIG`) > default path
+- Formats: TOML (discrawl, brabble) or YAML (camsnap, sonoscli)
+- Template default: TOML
+
+### Build/Release
+- GoReleaser v2 for all repos
+- `ldflags: -s -w -X main.version={{.Version}}`
+- Homebrew taps via GoReleaser auto-generation
+- Universal macOS binaries (amd64+arm64)
+- golangci-lint in 6/7 repos
+
+## CLI Best Practices Checklist (from clig.dev, Atlassian, 12 Factor CLI, agent research)
+
+The template generates CLIs that follow these standards by default. Users don't need to know the rules - the template encodes them.
+
+### Non-Negotiable (every generated CLI has these)
+- stdout for data, stderr for messages/progress/errors
+- `--help`/`-h` with examples and required/optional markers
+- `--version`/`-V` and `version` subcommand
+- Exit codes: 0 success, 1 error, 2 usage, 3 not found, 4 auth, 5 conflict
+- `--json` persistent flag on every command
+- `--quiet`/`-q` for bare output (one value per line)
+- `--no-color` + respect `NO_COLOR` env + `TERM=dumb` + TTY detection
+- `--force`/`--yes` to skip interactive prompts
+- Noun-verb subcommand hierarchy
+- XDG config paths (`~/.config/<tool>/`)
+- Config precedence: flags > env vars > project config > user config
+
+### Agent-Ready (generated when `--agent-friendly` flag is used)
+- JSON to stdout only, never mixed with human text
+- Flat JSON objects (avoid deep nesting)
+- Consistent types (ISO 8601 dates, seconds for durations)
+- NDJSON (one JSON per line) for streaming output
+- Structured error objects: `{"error": "code", "message": "...", "suggestion": "..."}`
+- `--dry-run` for all mutating operations
+- Idempotent verbs: `ensure`/`apply`/`sync` over `create`/`delete`
+- Auto-detect non-TTY and output JSON by default in headless mode
+- Shell completions (bash, zsh, fish)
+
+### Delightful (generated for human-facing CLIs)
+- Spinners for tasks with unknown duration (charmbracelet/huh)
+- Progress bars for measurable multi-step tasks
+- Suggest next command after each action
+- Prompt for missing required options (when TTY)
+- Sub-500ms startup time
+- `doctor` subcommand for health checks
+
+## Three Input Modes
+
+### Mode 1: Natural Language
+```
+$ printing-press generate "Stripe API - manage customers, charges, and subscriptions"
+```
+Agent reads Stripe's API docs, generates OpenAPI spec, produces Go CLI.
+
+### Mode 2: OpenAPI Spec
+```
+$ printing-press generate --spec stripe-openapi.yaml --name stripe-cli
+```
+Direct from spec. Most precise. Supports OpenAPI 3.0+.
+
+### Mode 3: Example Requests
+```
+$ printing-press generate --from-curl examples.sh --name myapi-cli
+```
+Feed it a collection of curl commands. It infers the API structure and generates a CLI.
+
+## Architecture: A Claude Code Plugin
+
+CLI Printing Press is a Claude Code plugin - the same model as Compound Engineering (EveryInc/compound-engineering-plugin, the most popular CE plugin). The plugin contains:
+
+```
+cli-printing-press/
+ .claude-plugin/
+ plugin.json # Plugin manifest (name, version, repo)
+ CLAUDE.md # Plugin conventions
+ skills/
+ printing-press/
+ SKILL.md # /printing-press <description> - generates a CLI
+ printing-press-catalog/
+ SKILL.md # /printing-press-catalog - browse pre-built CLIs
+ templates/
+ go-cobra/ # Go + Cobra template (Steinberger patterns)
+ cmd.go.tmpl
+ client.go.tmpl
+ output.go.tmpl
+ auth.go.tmpl
+ config.go.tmpl
+ Makefile.tmpl
+ go.mod.tmpl
+ README.md
+ catalog/ # Pre-built CLI definitions (the "library")
+ github.yaml # GitHub API CLI definition
+ stripe.yaml # Stripe API CLI definition
+ slack.yaml # Slack API CLI definition
+ ... (community-contributed)
+ scripts/
+ generate.sh # Template engine
+ validate.sh # Security validation for submissions
+```
+
+**Installation:**
+```
+/plugin marketplace add mvanhorn/cli-printing-press
+/plugin install printing-press@cli-printing-press
+```
+
+**Usage:**
+```
+/printing-press "Stripe API - manage customers, charges, and subscriptions"
+/printing-press --spec stripe-openapi.yaml --name stripe-cli
+/printing-press-catalog # browse pre-built CLIs
+/printing-press-catalog install github # install pre-built GitHub CLI
+```
+
+## The Community Catalog (and the Security Question)
+
+The catalog is where this gets interesting - and dangerous.
+
+### The Vision
+
+Anyone can contribute a CLI definition to the catalog. You run `/printing-press "Twilio API"`, it generates a great CLI, you submit a PR to add `catalog/twilio.yaml` to the repo. Next person who wants a Twilio CLI just runs `/printing-press-catalog install twilio` instead of generating from scratch.
+
+Like Homebrew formulae. Like ClawHub skills. But for CLI tool definitions.
+
+### The Security Model: Curated, Not Open
+
+The key decision: **catalog entries are curated by maintainers, not auto-published.** Here's why:
+
+**The injection risk is real.** A CLI definition includes:
+- API endpoints (could point to a malicious proxy instead of the real API)
+- Auth configuration (could exfiltrate tokens to an attacker's server)
+- Shell commands in Makefiles (could run arbitrary code during build)
+- Generated Go code patterns (could include backdoors in the templates)
+
+A community member submitting `catalog/aws.yaml` that points auth to `auth.totally-not-evil.com` would compromise every user who installs it.
+
+**Why NOT ClawHub/open marketplace:**
+- ClawHub has 13.7K skills with VirusTotal scanning, but scanning generated Go source for subtle backdoors (a token exfiltration hidden in error handling) is much harder than scanning for malware binaries
+- The attack surface is the API definition, not the binary - a malicious definition produces a "clean" binary that does bad things by design
+- Trust needs to be at the definition level, not the binary level
+
+**Why NOT fully open PRs:**
+- Even with review, subtle endpoint substitution (`api.stripe.com` vs `api.str1pe.com`) is easy to miss in a YAML file
+- The reviewing burden grows linearly with submissions
+
+**The model: three tiers.**
+
+| Tier | Who | What | Trust |
+|------|-----|------|-------|
+| **Official** | Printing Press maintainers | Top 20 APIs (GitHub, Stripe, Slack, etc.) | Verified against official API docs. Signed. |
+| **Verified** | Community PRs, maintainer-reviewed | Any API where the submitter demonstrates it works against the real API | Reviewed for endpoint authenticity. Signed after merge. |
+| **Unverified** | Community PRs, auto-merged if CI passes | Any API definition that passes validation | NOT reviewed for authenticity. Flagged with warning. User accepts risk. |
+
+**Security validation (automated, runs in CI on every PR):**
+1. All API endpoints must use HTTPS
+2. All endpoints must resolve to IPs that match the API provider's known ranges (where available)
+3. No shell commands in the definition (Makefile is generated from template, not from definition)
+4. Auth endpoints must match the API provider's documented auth URLs
+5. Generated Go code is `go vet` clean
+6. No `replace` directives in go.mod (prevents dependency hijacking)
+7. Binary is built and smoke-tested against the real API (with a read-only test account where possible)
+
+**What the user sees:**
+```
+$ /printing-press-catalog install stripe
+Installing: stripe (Official - verified by CLI Printing Press maintainers)
+Source: catalog/stripe.yaml (signed: abc123)
+
+$ /printing-press-catalog install obscure-api
+Installing: obscure-api (Unverified - community-contributed, not reviewed)
+WARNING: This CLI definition has not been verified against the real API.
+ Endpoints and auth configuration have not been audited.
+ Proceed? [y/N]
+```
+
+### The PR Submission Flow
+
+```
+1. User runs: /printing-press "Twilio API"
+2. Agent generates the CLI project + catalog/twilio.yaml definition
+3. User tests it, confirms it works
+4. User runs: /printing-press submit twilio
+5. Agent:
+ - Forks mvanhorn/cli-printing-press
+ - Adds catalog/twilio.yaml
+ - Runs validation suite locally
+ - Opens PR with:
+ - The YAML definition
+ - Test evidence (screenshots of CLI output against real API)
+ - Attestation: "I tested this against the real Twilio API"
+6. CI runs security validation
+7. Maintainer reviews (for Verified tier) or auto-merges (for Unverified tier)
+```
+
+## What APIs Look Like (Patterns from Steinberger's 10 CLIs)
+
+Analysis of the actual APIs behind all 10 Steinberger CLIs reveals three tiers:
+
+**Tier 1: Cloud API wrappers (CLI Printing Press sweet spot)**
+- sag (ElevenLabs), oracle (multi-LLM), summarize (content+LLM), gogcli (Google Suite)
+- REST/JSON, API keys or OAuth2, cursor pagination
+- This is what the template generator optimizes for
+
+**Tier 2: Platform API wrappers (possible with client libraries)**
+- discrawl (Discord REST + WebSocket Gateway), wacli (WhatsApp via whatsmeow)
+- The CLI wraps a Go client library, not raw HTTP
+- Template can scaffold the Cobra structure; domain logic is manual
+
+**Tier 3: Local network/device tools (not templateable)**
+- sonoscli (UPnP/SSDP), camsnap (RTSP), brabble (local whisper.cpp)
+- No cloud API. Discovery-based, protocol-specific. Template adds no value.
+
+### What the Template MUST Support
+
+| Pattern | Appears in | Implementation |
+|---------|-----------|---------------|
+| API key auth (env var + flag + file) | 6/10 CLIs | Triple priority chain with provenance tracking |
+| REST/JSON | 7/10 CLIs | net/http client with configurable base URL |
+| `--json` output | 10/10 CLIs | Tri-mode: human/json/plain via type-switch dispatch |
+| Context-aware timeouts | 10/10 Go CLIs | `context.WithTimeout` + signal handling |
+| Config file | 8/10 CLIs | TOML (default) at `~/.config/<tool>/config.toml` |
+| Cursor pagination | 4/10 CLIs | Generic `nextPageToken` / `before`/`after` patterns |
+
+### What the Template SHOULD Support (optional modules)
+
+| Pattern | When needed | Implementation |
+|---------|------------|---------------|
+| OAuth2 flow | Google, Microsoft, enterprise APIs | `golang.org/x/oauth2` + keyring storage |
+| Streaming (SSE/chunked) | LLM APIs, real-time data | `bufio.Scanner` on response body |
+| Retry with backoff | Rate-limited APIs | Exponential backoff with jitter |
+| Local SQLite cache | Tools that sync data locally | `modernc.org/sqlite` (pure Go, no CGO) |
+| WebSocket tailing | Real-time event APIs | `gorilla/websocket` + worker pool |
+
+## First Test Cases (Verified: Have APIs, No Official CLIs)
+
+### Tier 1: First 3 to build (simplest, cleanest specs)
+
+| Product | Category | OpenAPI Spec | ~Commands | Why first |
+|---------|----------|-------------|----------|-----------|
+| **Stytch** | Auth | github.com/stytchauth/stytch-openapi | ~15 | Smallest surface. Clean spec. Perfect validation. |
+| **Clerk** | Auth | clerk.com/docs/reference | ~15-20 | Popular. On their CLI roadmap. Clean CRUD. |
+| **Loops** | Email | loops.so/docs | ~8-10 | Tiny API. Contacts + events + emails. Fastest to validate. |
+
+### Tier 2: Showcase candidates (prove it scales)
+
+| Product | Category | OpenAPI Spec | ~Commands | Why |
+|---------|----------|-------------|----------|-----|
+| **ClickUp** | PM | developer.clickup.com/docs/open-api-spec | ~25-30 | 5+ community CLIs prove demand. No official. Showcase. |
+| **Intercom** | Support | github.com/intercom/Intercom-OpenAPI | ~20-25 | Major platform. Zero CLI presence. |
+| **Front** | Messaging | github.com/frontapp/front-api-specs | ~20 | Clean REST. Official specs. |
+| **Shortcut** | PM | developer.shortcut.com/api/rest/v3 | ~15-20 | Simpler than ClickUp. Good mid-range test. |
+
+### Tier 3: Large-scale validation
+
+| Product | Category | OpenAPI Spec | ~Commands | Why |
+|---------|----------|-------------|----------|-----|
+| **Asana** | PM | github.com/Asana/openapi | ~30-35 | Major tool, official spec, many community CLIs |
+| **Square** | Payments | github.com/square/connect-api-specification | ~40+ | Tests complex API with many resources |
+| **Discord** | Community | github.com/discord/discord-api-spec | ~50+ | Massive. Tests generator at scale. |
+
+### Eliminated (already have official CLIs)
+Stripe, Vercel, Netlify, Railway, Render, Fly.io, Supabase, PlanetScale, Neon, Turso, Cloudflare, Fastly, Sentry, HubSpot, Auth0, Slack, Resend, Replicate, Modal, Datadog, W&B, PostHog, Grafana (34 products verified).
+
+## Architectural Learnings from Production Agent Fleet Management
+
+Patterns extracted from a mature 14-skill autonomous agent system (15,000+ lines, 179 calibrated predictions, overnight mode submitting 21 PRs while sleeping). Applied to CLI Printing Press:
+
+### Quality Gates (adapt for CLI generation)
+
+The agent fleet uses 6 mandatory gates before any external action. For CLI Printing Press:
+
+1. **Spec validation** - parse OpenAPI spec, flag incomplete endpoints, missing auth docs
+2. **Generation validation** - `go vet` + `go build` on generated code
+3. **Wiring verification** - grep for call sites of every generated Cobra command (no orphan commands)
+4. **Test validation** - `go test` passes on generated test stubs
+5. **Output verification** - each generated command produces valid JSON with `--json`
+6. **Doctor validation** - `<tool> doctor` runs successfully
+
+Gate tracker file pattern: accumulate passes in `/tmp/printing-press-gates-$$.txt`, block if any missing.
+
+### Confidence Scoring (adapt for generation quality)
+
+Track per-API-spec generation success:
+- Did it compile on first try? (binary: yes/no)
+- How many manual fixes needed? (0 = perfect, 1-3 = good, 4+ = bad template)
+- Which template modules failed? (auth? pagination? output formatting?)
+- Calibrate: adjust template weights based on outcomes
+
+### Registry Pattern
+
+Central JSON registry at `~/.printing-press/registry.json`:
+```json
+{
+ "generated_tools": [
+ {
+ "name": "stytch-cli",
+ "api": "stytch",
+ "spec_source": "github.com/stytchauth/stytch-openapi",
+ "generated_at": "2026-03-23T...",
+ "status": "compiled|tested|published",
+ "commands": 15,
+ "quality_score": 0.95,
+ "last_regenerated": "2026-03-23T..."
+ }
+ ]
+}
+```
+
+With `.backup` always alongside.
+
+### Skip Cache
+
+`~/.printing-press/skip-specs.json` for known-bad API specs:
+```json
+{
+ "broken_spec": ["some-api/v1 - missing auth schema"],
+ "rate_limited": ["api-that-blocks-generators"],
+ "deprecated": ["old-api/v2 - sunset 2026-01"]
+}
+```
+
+Checked BEFORE any generation attempt.
+
+### Anti-Slop Patterns for Generated Docs
+
+Generated READMEs must not contain: "leverages", "utilizes", "comprehensive", "elegant solution", "not just X, it's Y", em dashes for dramatic effect. Every claim must reference a specific command or flag.
+
+## Implementation
+
+### Phase 1: Template Engine (Weeks 1-2)
+
+Build the Go project generator from a structured API description (not OpenAPI yet - a simpler internal format).
+
+**Deliverables:**
+- Go template system that produces Cobra CLI projects
+- `client.go` template with auth, retries, rate limiting
+- `output.go` template with --json/--table/--csv
+- Command template that generates one command file per endpoint
+- `Makefile` with build, test, install, brew-formula targets
+- 3 example CLIs generated: GitHub API, Stripe API, Slack API
+
+**Gate:** Generated CLIs compile, run, and produce correct output against real APIs.
+
+### Phase 2: OpenAPI Parser (Weeks 3-4)
+
+Parse OpenAPI 3.0+ specs and map them to the internal format.
+
+**Deliverables:**
+- OpenAPI 3.0 parser (use kin-openapi or libopenapi in Go)
+- Mapping: OpenAPI paths -> Cobra subcommands, parameters -> flags, schemas -> Go structs
+- Auth scheme detection (bearer, OAuth2, API key) -> appropriate auth template
+- 5 real-world OpenAPI specs tested: GitHub, Stripe, Twilio, Slack, OpenAI
+
+**Gate:** `printing-press generate --spec github-openapi.yaml` produces a working CLI.
+
+### Phase 3: Natural Language Mode (Weeks 5-6)
+
+Agent reads API docs and generates the internal API description.
+
+**Deliverables:**
+- Claude Code skill: `/printing-press <description>`
+- Agent fetches API documentation, extracts endpoints, generates internal spec
+- Fallback: if docs are unclear, generate a skeleton and ask the user to fill in details
+- OpenClaw skill published on ClawHub
+
+**Gate:** `/printing-press "Stripe API - customers and charges"` produces a working CLI from just that sentence.
+
+### Phase 4: Catalog and Community (Weeks 7-10)
+
+**Deliverables:**
+- Claude Code plugin published: `/plugin marketplace add mvanhorn/cli-printing-press`
+- 10 Official catalog entries: GitHub, Stripe, Slack, Twilio, OpenAI, Linear, Vercel, Cloudflare, Supabase, Discord
+- `/printing-press-catalog` browser and installer skill
+- `/printing-press submit` for community contributions
+- Security validation CI pipeline (endpoint verification, HTTPS enforcement, dependency scanning)
+- Three-tier trust model (Official / Verified / Unverified) with clear user-facing warnings
+- GitHub Action for downstream projects (regenerate CLI when OpenAPI spec changes)
+- README with benchmarks and contribution guide
+
+## Benchmark Targets
+
+| Metric | Hand-written Go + Cobra | CLI Printing Press |
+|--------|------------------------|-------------------|
+| Time to working CLI | Hours to days | Minutes |
+| Lines of code written | 200-2000 (depending on API) | 0 (generated) |
+| Boilerplate ratio | 79% (measured in wacli) | 0% (all generated) |
+| Agent token cost | 500-2000 tokens to write | ~50 tokens to invoke |
+| Binary quality | Production (if you're good) | Production (patterns are codified) |
+| Auth management | Manual | Built-in |
+| Output formatting | Manual | Built-in |
+
+## Why Not Just Ask Claude to Write the Go Code?
+
+You can. And agents do. But:
+
+1. **Inconsistency.** Every generation produces slightly different patterns. CLI Printing Press generates from templates - same patterns every time.
+2. **The 79% problem.** Even when agents write correct Go, 79% is boilerplate. CLI Printing Press eliminates it at the template level, not the generation level.
+3. **Maintenance.** When the Cobra best practices change, you update the template once. Every generated CLI gets the improvement.
+4. **Auth is hard.** OAuth2 flows, token refresh, keychain integration, multi-account support - these are complex and error-prone. Codified once in the template, correct forever.
+5. **Discoverability.** Pre-generated CLIs for popular APIs are immediately useful. No agent invocation needed - just `brew install stripe-printing-press`.
+
+## Risks
+
+| Risk | Impact | Mitigation |
+|------|--------|------------|
+| OpenAPI specs are often incomplete or wrong | Generated CLIs miss endpoints or have wrong types | Validate against real API calls. Flag gaps for manual review. |
+| api2cli already exists (Node.js, new) | Direct competitor | CLI Printing Press generates Go (single binary), not Node.js. Different target audience. |
+| LLMs get good enough that templates aren't needed | Product becomes redundant | Templates provide consistency and maintenance benefits that raw generation can't. If LLMs do make templates unnecessary, the pre-generated CLI catalog still has value. |
+| Generated CLIs feel generic | Users don't adopt over hand-written | Make templates customizable. Allow post-generation customization. The goal is 90% done, not 100%. |
+| Malicious catalog submissions | Token exfiltration, endpoint hijacking, supply chain attacks | Three-tier trust model. Automated endpoint verification. No shell in definitions. Signed Official entries. User warnings on Unverified entries. |
+| Catalog grows faster than review capacity | Unreviewed entries accumulate, trust erodes | Auto-merge Unverified tier with clear warnings. Focus review effort on Verified tier. Community can flag suspicious entries. |
+
+## Relationship to Nullhuman
+
+CLI Printing Press is the practical product. It ships in weeks, generates Go that agents already write well, and fills a market gap between abandoned OSS and $30K/year enterprise.
+
+If Nullhuman (the language) ever ships, CLI Printing Press could generate Nullhuman code instead of Go. But CLI Printing Press doesn't depend on Nullhuman. It's independently valuable.
+
+The two projects share research:
+- Steinberger's CLI patterns (codified from wacli/gogcli source code)
+- The "CLI > MCP" thesis (32x fewer tokens, 100% vs 72% reliability)
+- The four-layer agent stack (Markdown for instructions, typed tools for execution)
+- Capability-based security (could be added as a runtime wrapper around generated Go binaries)
+
+## Sources
+
+- steipete/wacli (Go, 677 stars) - WhatsApp CLI, 79% boilerplate measured
+- steipete/gogcli (Go, 6.5K stars) - Google Suite CLI
+- Stainless ($25M raised, ~$1M ARR) - Enterprise SDK/CLI generation
+- Speakeasy - Freemium SDK/CLI generation
+- danielgtaylor/openapi-cli-generator (195 stars) - Abandoned OSS
+- api2cli (new, Node.js) - "Turn any REST API into an agent-ready CLI"
+- ClawHub (13.7K skills) - Distribution channel
+- "CLI > MCP" benchmarks: 32x fewer tokens, 100% vs 72% reliability (rentierdigital.xyz, scalekit.com)
+
+### Origin
+
+Split from: docs/plans/2026-03-23-feat-nullhuman-agent-programming-language-plan.md
← 4b510ed7 feat(validate): quality gates + Clerk/Loops test specs + int
·
back to Cli Printing Press
·
feat(spec): extend internal format for OpenAPI + add real Op 4e3888ab →