← back to Cli Printing Press
feat(cli): emit AGENTS.md for printed CLIs (fast-track of #681) (#728)
a9b9aa659a84a877f42e21bb91167707bb5e46b9 · 2026-05-08 07:31:32 -0700 · Trevin Chow
* feat(cli): emit AGENTS.md for printed CLIs
* feat(cli): link upstream and add Local Customizations to AGENTS.md
Apply review feedback from #681:
- Link "CLI Printing Press" to the upstream repo so the reference is
resolvable for agents working without prior context.
- Add a "Local Customizations" section that defines the //PATCH: comment
convention and the .printing-press-patches.json index, so divergence
from the generator output is recorded inline and cataloged at the CLI
root rather than discovered by diff archaeology.
Bump TestGenerateProjectsCompile expected file counts by one across
stytch/clerk/loops to reflect the AGENTS.md emission landing on top of
post-PR-#681 main.
Co-Authored-By: Mat Silverstein <matsilverstein@gmail.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: silverstein <matsilverstein@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Files touched
M ONBOARDING.mdM README.mdM docs/PIPELINE.mdA internal/generator/agents_test.goM internal/generator/generator.goM internal/generator/generator_test.goA internal/generator/templates/agents.md.tmplM skills/printing-press/SKILL.mdM testdata/golden/cases/generate-golden-api-rich-auth/artifacts.txtM testdata/golden/cases/generate-golden-api/artifacts.txtA testdata/golden/expected/generate-golden-api-rich-auth/printing-press-rich-auth/AGENTS.mdA testdata/golden/expected/generate-golden-api/printing-press-golden/AGENTS.md
Diff
commit a9b9aa659a84a877f42e21bb91167707bb5e46b9
Author: Trevin Chow <trevin@trevinchow.com>
Date: Fri May 8 07:31:32 2026 -0700
feat(cli): emit AGENTS.md for printed CLIs (fast-track of #681) (#728)
* feat(cli): emit AGENTS.md for printed CLIs
* feat(cli): link upstream and add Local Customizations to AGENTS.md
Apply review feedback from #681:
- Link "CLI Printing Press" to the upstream repo so the reference is
resolvable for agents working without prior context.
- Add a "Local Customizations" section that defines the //PATCH: comment
convention and the .printing-press-patches.json index, so divergence
from the generator output is recorded inline and cataloged at the CLI
root rather than discovered by diff archaeology.
Bump TestGenerateProjectsCompile expected file counts by one across
stytch/clerk/loops to reflect the AGENTS.md emission landing on top of
post-PR-#681 main.
Co-Authored-By: Mat Silverstein <matsilverstein@gmail.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: silverstein <matsilverstein@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---
ONBOARDING.md | 2 +-
README.md | 2 +-
docs/PIPELINE.md | 1 +
internal/generator/agents_test.go | 48 +++++++++++++++
internal/generator/generator.go | 3 +-
internal/generator/generator_test.go | 7 ++-
internal/generator/templates/agents.md.tmpl | 71 ++++++++++++++++++++++
skills/printing-press/SKILL.md | 6 +-
.../generate-golden-api-rich-auth/artifacts.txt | 1 +
.../golden/cases/generate-golden-api/artifacts.txt | 1 +
.../printing-press-rich-auth/AGENTS.md | 71 ++++++++++++++++++++++
.../printing-press-golden/AGENTS.md | 71 ++++++++++++++++++++++
12 files changed, 275 insertions(+), 9 deletions(-)
diff --git a/ONBOARDING.md b/ONBOARDING.md
index 3dae201c..3b3fdbdf 100644
--- a/ONBOARDING.md
+++ b/ONBOARDING.md
@@ -1,6 +1,6 @@
# CLI Printing Press Onboarding Guide
-You give the printing press an API spec. It gives you back a Go CLI, an MCP server, and the few artifacts needed to keep the next step grounded. It handles REST (OpenAPI) and GraphQL. One command, a lean fast path, two binaries.
+You give the printing press an API spec. It gives you back a Go CLI, an MCP server, a root `AGENTS.md` operating guide, and the few artifacts needed to keep the next step grounded. It handles REST (OpenAPI) and GraphQL. One command, a lean fast path, two binaries.
The key idea: most API CLI generators stop at wrapping endpoints. The printing press goes further -- it profiles each API, detects its domain archetype (communication, project management, payments, etc.), and generates domain-specific "power user" commands like `sync`, `search`, `stale`, `health`, and `similar` on top of the standard CRUD wrappers.
diff --git a/README.md b/README.md
index 19fda7b1..6197e67f 100644
--- a/README.md
+++ b/README.md
@@ -459,7 +459,7 @@ A small sample, see the [full catalog](https://github.com/mvanhorn/printing-pres
| `kalshi-pp-cli` | Payments | Trade prediction markets from the terminal. |
| `recipe-goat-pp-cli` | Food and Dining | Trust-aware ranking across 37 recipe sites. |
-Each published CLI ships a research manuscript, verification proofs, and a `.printing-press.json` provenance manifest.
+Each newly published CLI ships a root `AGENTS.md` operating guide, a research manuscript, verification proofs, and a `.printing-press.json` provenance manifest.
## Troubleshooting
diff --git a/docs/PIPELINE.md b/docs/PIPELINE.md
index 55835f3b..b478ae3f 100644
--- a/docs/PIPELINE.md
+++ b/docs/PIPELINE.md
@@ -99,6 +99,7 @@ Inputs:
Outputs:
- Generated CLI source tree under the API's output directory
+- Root `AGENTS.md` operating guide for shell agents working inside the generated CLI tree
- Working CLI binary for the target API
Freshness ownership:
diff --git a/internal/generator/agents_test.go b/internal/generator/agents_test.go
new file mode 100644
index 00000000..f7699ac2
--- /dev/null
+++ b/internal/generator/agents_test.go
@@ -0,0 +1,48 @@
+package generator
+
+import (
+ "os"
+ "path/filepath"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestGeneratedAgentsGuideRendersPortableAgentContract(t *testing.T) {
+ t.Parallel()
+
+ apiSpec := minimalSpec("finance")
+ outputDir := filepath.Join(t.TempDir(), "finance-pp-cli")
+ gen := New(apiSpec, outputDir)
+ require.NoError(t, gen.Generate())
+
+ agents, err := os.ReadFile(filepath.Join(outputDir, "AGENTS.md"))
+ require.NoError(t, err)
+ content := string(agents)
+
+ assert.Contains(t, content, "# Finance Printed CLI Agent Guide")
+ assert.Contains(t, content, "generated `finance-pp-cli` printed CLI")
+ assert.Contains(t, content, "finance-pp-cli doctor --json")
+ assert.Contains(t, content, "finance-pp-cli agent-context --pretty")
+ assert.Contains(t, content, `finance-pp-cli which "<capability>" --json`)
+ assert.Contains(t, content, "finance-pp-cli <command> --help")
+ assert.Contains(t, content, "finance-pp-cli <command> --agent")
+ assert.Contains(t, content, "finance-pp-cli <command> --dry-run --agent")
+ assert.Contains(t, content, "README.md")
+ assert.Contains(t, content, "SKILL.md")
+
+ assert.NotContains(t, content, "## Command Reference")
+ assert.NotContains(t, content, "npx -y @mvanhorn/printing-press install")
+ assert.NotContains(t, content, "export ")
+ assert.NotContains(t, content, "<cli>")
+ assert.NotContains(t, content, "Claude Code")
+ assertASCII(t, content)
+}
+
+func assertASCII(t *testing.T, content string) {
+ t.Helper()
+ for _, r := range content {
+ require.LessOrEqualf(t, r, rune(127), "generated AGENTS.md should stay ASCII-only; found %q", r)
+ }
+}
diff --git a/internal/generator/generator.go b/internal/generator/generator.go
index ad883cfa..4be33ea8 100644
--- a/internal/generator/generator.go
+++ b/internal/generator/generator.go
@@ -1360,6 +1360,7 @@ func (g *Generator) renderSingleFiles() error {
"types.go.tmpl": filepath.Join("internal", "types", "types.go"),
"golangci.yml.tmpl": ".golangci.yml",
"readme.md.tmpl": "README.md",
+ "agents.md.tmpl": "AGENTS.md",
"skill.md.tmpl": "SKILL.md",
"LICENSE.tmpl": "LICENSE",
"NOTICE.tmpl": "NOTICE",
@@ -1368,7 +1369,7 @@ func (g *Generator) renderSingleFiles() error {
for tmplName, outPath := range singleFiles {
var data any
switch tmplName {
- case "readme.md.tmpl", "skill.md.tmpl", "which.go.tmpl", "which_test.go.tmpl":
+ case "readme.md.tmpl", "agents.md.tmpl", "skill.md.tmpl", "which.go.tmpl", "which_test.go.tmpl":
data = g.readmeData()
case "helpers.go.tmpl":
hFlags := computeHelperFlags(g.Spec)
diff --git a/internal/generator/generator_test.go b/internal/generator/generator_test.go
index 83be19b7..904ac7eb 100644
--- a/internal/generator/generator_test.go
+++ b/internal/generator/generator_test.go
@@ -43,6 +43,7 @@ func TestGenerateProjectsCompile(t *testing.T) {
mustInclude := []string{
"go.mod",
"Makefile",
+ "AGENTS.md",
"README.md",
"SKILL.md",
"internal/cli/root.go",
@@ -75,9 +76,9 @@ func TestGenerateProjectsCompile(t *testing.T) {
// Bump it AND add to mustInclude above when adding always-emitted
// templates. Per-spec dynamic files (per-resource command files,
// generated tests) account for the difference between fixtures.
- {name: "stytch", specPath: filepath.Join("..", "..", "testdata", "stytch.yaml"), expectedFiles: 54},
- {name: "clerk", specPath: filepath.Join("..", "..", "testdata", "clerk.yaml"), expectedFiles: 59},
- {name: "loops", specPath: filepath.Join("..", "..", "testdata", "loops.yaml"), expectedFiles: 56},
+ {name: "stytch", specPath: filepath.Join("..", "..", "testdata", "stytch.yaml"), expectedFiles: 55},
+ {name: "clerk", specPath: filepath.Join("..", "..", "testdata", "clerk.yaml"), expectedFiles: 60},
+ {name: "loops", specPath: filepath.Join("..", "..", "testdata", "loops.yaml"), expectedFiles: 57},
}
for _, tt := range tests {
diff --git a/internal/generator/templates/agents.md.tmpl b/internal/generator/templates/agents.md.tmpl
new file mode 100644
index 00000000..91d8851e
--- /dev/null
+++ b/internal/generator/templates/agents.md.tmpl
@@ -0,0 +1,71 @@
+# {{.ProseName}} Printed CLI Agent Guide
+
+This directory is a generated `{{.Name}}-pp-cli` printed CLI. It was produced by [CLI Printing Press](https://github.com/mvanhorn/cli-printing-press), so treat systemic fixes as upstream Printing Press fixes first. Keep local edits narrow and document why a generated-tree patch belongs here.
+
+## Local Operating Contract
+
+Start by asking the generated CLI for current runtime truth:
+
+```bash
+{{.Name}}-pp-cli doctor --json
+{{.Name}}-pp-cli agent-context --pretty
+```
+
+Use runtime discovery instead of relying on a copied command list:
+
+```bash
+{{.Name}}-pp-cli which "<capability>" --json
+{{.Name}}-pp-cli <command> --help
+```
+
+Add `--agent` to command invocations for JSON, compact output, non-interactive defaults, no color, and confirmation-safe scripting:
+
+```bash
+{{.Name}}-pp-cli <command> --agent
+```
+
+Before running an unfamiliar command that may mutate remote state, inspect its help and prefer a dry run:
+
+```bash
+{{.Name}}-pp-cli <command> --help
+{{.Name}}-pp-cli <command> --dry-run --agent
+```
+
+Use `--yes --no-input` only after the target, arguments, and side effects are clear.
+
+For install, auth, examples, and longer product guidance, read `README.md` and `SKILL.md`. This file intentionally stays small so repo-local agents get invariant local guidance without duplicating the generated docs.
+
+## Local Customizations
+
+If you modify this CLI beyond what the generator produced, record each customization so it isn't lost on the next regen and is visible to the next reader.
+
+1. **Mark every changed site** in source with a comment summarizing the deviation:
+
+ ```
+ // PATCH: <one-line summary>
+ ```
+
+ Include an upstream reference inline when there is one (e.g. `// PATCH(upstream cli-printing-press#<issue>): ...`). `grep -rn 'PATCH' .` from this directory then surfaces every customization.
+
+2. **Catalog the change** in a `.printing-press-patches.json` at this CLI's root (parallel to `.printing-press.json`). Minimum shape:
+
+ ```json
+ {
+ "schema_version": 1,
+ "applied_at": "YYYY-MM-DD",
+ "base_run_id": "<copy from .printing-press.json>",
+ "base_printing_press_version": "<copy from .printing-press.json>",
+ "patches": [
+ {
+ "id": "short-identifier",
+ "summary": "What changed (one sentence).",
+ "reason": "Why this customization was needed (one or two sentences).",
+ "files": ["internal/cli/foo.go"],
+ "validated_outcome": "Optional: non-obvious test result that confirms the fix.",
+ "upstream_issue": "Optional: https://github.com/mvanhorn/cli-printing-press/issues/<n>"
+ }
+ ]
+ }
+ ```
+
+This file is an **index of customizations**, not a second copy of the diff. Diffs live in `git`; code lives in the source files; the inline `// PATCH:` comment carries the local semantics. Keep `summary` and `reason` short -- if you find yourself writing tables of field renames or code transformations, that detail belongs in the source comment or commit message, not here.
diff --git a/skills/printing-press/SKILL.md b/skills/printing-press/SKILL.md
index e95d1f0f..1f0dc3d1 100644
--- a/skills/printing-press/SKILL.md
+++ b/skills/printing-press/SKILL.md
@@ -2444,13 +2444,13 @@ A template-level check would require every possible semantic mismatch to be patt
The agent can't verify runtime behavior without running commands; stick to help-text and source-based claims. For runtime-behavior claims (e.g., "returns 5 matching recipes"), Phase 5 dogfood is the right gate.
-## Phase 4.9: README/SKILL Correctness Audit
+## Phase 4.9: README/SKILL/AGENTS Correctness Audit
-**Runs after Phase 4.8, before Phase 5.** Phase 4.8 reviews whether the SKILL's trigger phrases and major claims match shipped behavior. Phase 4.9 reviews the two user-facing artifacts as documents: README.md and SKILL.md must not contain boilerplate that does not apply to this CLI.
+**Runs after Phase 4.8, before Phase 5.** Phase 4.8 reviews whether the SKILL's trigger phrases and major claims match shipped behavior. Phase 4.9 reviews the user-facing artifacts as documents: README.md, SKILL.md, and AGENTS.md must not contain boilerplate that does not apply to this CLI.
Use the Agent tool or review directly with this prompt contract:
-> Audit `$CLI_WORK_DIR/README.md` and `$CLI_WORK_DIR/SKILL.md` for factual correctness against the shipped CLI. Ground truth is `<cli> --help` recursively, `$CLI_WORK_DIR/internal/cli/*.go`, `$RESEARCH_DIR/research.json`, and the absorb manifest.
+> Audit `$CLI_WORK_DIR/README.md`, `$CLI_WORK_DIR/SKILL.md`, and `$CLI_WORK_DIR/AGENTS.md` for factual correctness against the shipped CLI. Ground truth is `<cli> --help` recursively, `$CLI_WORK_DIR/internal/cli/*.go`, `$RESEARCH_DIR/research.json`, and the absorb manifest.
>
> Check:
> - Every command, subcommand, flag, exit code, config path, and example resolves to the printed CLI.
diff --git a/testdata/golden/cases/generate-golden-api-rich-auth/artifacts.txt b/testdata/golden/cases/generate-golden-api-rich-auth/artifacts.txt
index 222d9b35..117fb898 100644
--- a/testdata/golden/cases/generate-golden-api-rich-auth/artifacts.txt
+++ b/testdata/golden/cases/generate-golden-api-rich-auth/artifacts.txt
@@ -1,6 +1,7 @@
# Locks rich AuthEnvVar template consumption across machine and prose surfaces.
printing-press-rich-auth/SKILL.md
printing-press-rich-auth/README.md
+printing-press-rich-auth/AGENTS.md
printing-press-rich-auth/internal/cli/agent_context.go
printing-press-rich-auth/internal/cli/auth.go
printing-press-rich-auth/internal/cli/doctor.go
diff --git a/testdata/golden/cases/generate-golden-api/artifacts.txt b/testdata/golden/cases/generate-golden-api/artifacts.txt
index 8f334d66..83dc1f18 100644
--- a/testdata/golden/cases/generate-golden-api/artifacts.txt
+++ b/testdata/golden/cases/generate-golden-api/artifacts.txt
@@ -26,6 +26,7 @@ printing-press-golden/internal/types/types.go
dogfood.json
scorecard.json
printing-press-golden/README.md
+printing-press-golden/AGENTS.md
printing-press-golden/SKILL.md
printing-press-golden/internal/cli/doctor.go
printing-press-golden/internal/cliutil/verifyenv.go
diff --git a/testdata/golden/expected/generate-golden-api-rich-auth/printing-press-rich-auth/AGENTS.md b/testdata/golden/expected/generate-golden-api-rich-auth/printing-press-rich-auth/AGENTS.md
new file mode 100644
index 00000000..1e2f28c0
--- /dev/null
+++ b/testdata/golden/expected/generate-golden-api-rich-auth/printing-press-rich-auth/AGENTS.md
@@ -0,0 +1,71 @@
+# Printing Press Rich Printed CLI Agent Guide
+
+This directory is a generated `printing-press-rich-pp-cli` printed CLI. It was produced by [CLI Printing Press](https://github.com/mvanhorn/cli-printing-press), so treat systemic fixes as upstream Printing Press fixes first. Keep local edits narrow and document why a generated-tree patch belongs here.
+
+## Local Operating Contract
+
+Start by asking the generated CLI for current runtime truth:
+
+```bash
+printing-press-rich-pp-cli doctor --json
+printing-press-rich-pp-cli agent-context --pretty
+```
+
+Use runtime discovery instead of relying on a copied command list:
+
+```bash
+printing-press-rich-pp-cli which "<capability>" --json
+printing-press-rich-pp-cli <command> --help
+```
+
+Add `--agent` to command invocations for JSON, compact output, non-interactive defaults, no color, and confirmation-safe scripting:
+
+```bash
+printing-press-rich-pp-cli <command> --agent
+```
+
+Before running an unfamiliar command that may mutate remote state, inspect its help and prefer a dry run:
+
+```bash
+printing-press-rich-pp-cli <command> --help
+printing-press-rich-pp-cli <command> --dry-run --agent
+```
+
+Use `--yes --no-input` only after the target, arguments, and side effects are clear.
+
+For install, auth, examples, and longer product guidance, read `README.md` and `SKILL.md`. This file intentionally stays small so repo-local agents get invariant local guidance without duplicating the generated docs.
+
+## Local Customizations
+
+If you modify this CLI beyond what the generator produced, record each customization so it isn't lost on the next regen and is visible to the next reader.
+
+1. **Mark every changed site** in source with a comment summarizing the deviation:
+
+ ```
+ // PATCH: <one-line summary>
+ ```
+
+ Include an upstream reference inline when there is one (e.g. `// PATCH(upstream cli-printing-press#<issue>): ...`). `grep -rn 'PATCH' .` from this directory then surfaces every customization.
+
+2. **Catalog the change** in a `.printing-press-patches.json` at this CLI's root (parallel to `.printing-press.json`). Minimum shape:
+
+ ```json
+ {
+ "schema_version": 1,
+ "applied_at": "YYYY-MM-DD",
+ "base_run_id": "<copy from .printing-press.json>",
+ "base_printing_press_version": "<copy from .printing-press.json>",
+ "patches": [
+ {
+ "id": "short-identifier",
+ "summary": "What changed (one sentence).",
+ "reason": "Why this customization was needed (one or two sentences).",
+ "files": ["internal/cli/foo.go"],
+ "validated_outcome": "Optional: non-obvious test result that confirms the fix.",
+ "upstream_issue": "Optional: https://github.com/mvanhorn/cli-printing-press/issues/<n>"
+ }
+ ]
+ }
+ ```
+
+This file is an **index of customizations**, not a second copy of the diff. Diffs live in `git`; code lives in the source files; the inline `// PATCH:` comment carries the local semantics. Keep `summary` and `reason` short -- if you find yourself writing tables of field renames or code transformations, that detail belongs in the source comment or commit message, not here.
diff --git a/testdata/golden/expected/generate-golden-api/printing-press-golden/AGENTS.md b/testdata/golden/expected/generate-golden-api/printing-press-golden/AGENTS.md
new file mode 100644
index 00000000..98c40b0f
--- /dev/null
+++ b/testdata/golden/expected/generate-golden-api/printing-press-golden/AGENTS.md
@@ -0,0 +1,71 @@
+# Printing Press Golden Printed CLI Agent Guide
+
+This directory is a generated `printing-press-golden-pp-cli` printed CLI. It was produced by [CLI Printing Press](https://github.com/mvanhorn/cli-printing-press), so treat systemic fixes as upstream Printing Press fixes first. Keep local edits narrow and document why a generated-tree patch belongs here.
+
+## Local Operating Contract
+
+Start by asking the generated CLI for current runtime truth:
+
+```bash
+printing-press-golden-pp-cli doctor --json
+printing-press-golden-pp-cli agent-context --pretty
+```
+
+Use runtime discovery instead of relying on a copied command list:
+
+```bash
+printing-press-golden-pp-cli which "<capability>" --json
+printing-press-golden-pp-cli <command> --help
+```
+
+Add `--agent` to command invocations for JSON, compact output, non-interactive defaults, no color, and confirmation-safe scripting:
+
+```bash
+printing-press-golden-pp-cli <command> --agent
+```
+
+Before running an unfamiliar command that may mutate remote state, inspect its help and prefer a dry run:
+
+```bash
+printing-press-golden-pp-cli <command> --help
+printing-press-golden-pp-cli <command> --dry-run --agent
+```
+
+Use `--yes --no-input` only after the target, arguments, and side effects are clear.
+
+For install, auth, examples, and longer product guidance, read `README.md` and `SKILL.md`. This file intentionally stays small so repo-local agents get invariant local guidance without duplicating the generated docs.
+
+## Local Customizations
+
+If you modify this CLI beyond what the generator produced, record each customization so it isn't lost on the next regen and is visible to the next reader.
+
+1. **Mark every changed site** in source with a comment summarizing the deviation:
+
+ ```
+ // PATCH: <one-line summary>
+ ```
+
+ Include an upstream reference inline when there is one (e.g. `// PATCH(upstream cli-printing-press#<issue>): ...`). `grep -rn 'PATCH' .` from this directory then surfaces every customization.
+
+2. **Catalog the change** in a `.printing-press-patches.json` at this CLI's root (parallel to `.printing-press.json`). Minimum shape:
+
+ ```json
+ {
+ "schema_version": 1,
+ "applied_at": "YYYY-MM-DD",
+ "base_run_id": "<copy from .printing-press.json>",
+ "base_printing_press_version": "<copy from .printing-press.json>",
+ "patches": [
+ {
+ "id": "short-identifier",
+ "summary": "What changed (one sentence).",
+ "reason": "Why this customization was needed (one or two sentences).",
+ "files": ["internal/cli/foo.go"],
+ "validated_outcome": "Optional: non-obvious test result that confirms the fix.",
+ "upstream_issue": "Optional: https://github.com/mvanhorn/cli-printing-press/issues/<n>"
+ }
+ ]
+ }
+ ```
+
+This file is an **index of customizations**, not a second copy of the diff. Diffs live in `git`; code lives in the source files; the inline `// PATCH:` comment carries the local semantics. Keep `summary` and `reason` short -- if you find yourself writing tables of field renames or code transformations, that detail belongs in the source comment or commit message, not here.
← c9ebffaa ci: add Main CI workflow for post-merge build+test+golden ga
·
back to Cli Printing Press
·
fix(cli): anchor openapi loader normalization (#730) a4b0eb30 →