← back to Cli Printing Press
fix(cli): align generated install and rename metadata (#749)
ca580efc6ad47f0f4678b9a54fb1773c7efb33b7 · 2026-05-08 14:49:15 -0700 · Trevin Chow
Files touched
M internal/cli/publish.goM internal/cli/publish_test.goM internal/generator/generator_test.goM internal/generator/install_section.goM internal/generator/install_section_test.goM internal/generator/skill_test.goM internal/generator/templates/readme.md.tmplM internal/generator/templates/skill.md.tmplM internal/pipeline/renamecli.goM internal/pipeline/renamecli_test.goM skills/printing-press-publish/SKILL.mdM testdata/golden/expected/generate-golden-api-rich-auth/printing-press-rich-auth/README.mdM testdata/golden/expected/generate-golden-api-rich-auth/printing-press-rich-auth/SKILL.mdM testdata/golden/expected/generate-golden-api/printing-press-golden/README.mdM testdata/golden/expected/generate-golden-api/printing-press-golden/SKILL.mdM testdata/golden/expected/generate-public-param-names/public-param-golden/README.mdM testdata/golden/expected/generate-public-param-names/public-param-golden/SKILL.md
Diff
commit ca580efc6ad47f0f4678b9a54fb1773c7efb33b7
Author: Trevin Chow <trevin@trevinchow.com>
Date: Fri May 8 14:49:15 2026 -0700
fix(cli): align generated install and rename metadata (#749)
---
internal/cli/publish.go | 17 ++--
internal/cli/publish_test.go | 11 ++-
internal/generator/generator_test.go | 3 +-
internal/generator/install_section.go | 37 +++++---
internal/generator/install_section_test.go | 23 +++++
internal/generator/skill_test.go | 13 +--
internal/generator/templates/readme.md.tmpl | 20 +++-
internal/generator/templates/skill.md.tmpl | 25 +++--
internal/pipeline/renamecli.go | 78 ++++++++++++++--
internal/pipeline/renamecli_test.go | 101 ++++++++++++++++++++-
skills/printing-press-publish/SKILL.md | 1 -
.../printing-press-rich-auth/README.md | 19 ++--
.../printing-press-rich-auth/SKILL.md | 26 ++----
.../printing-press-golden/README.md | 19 ++--
.../printing-press-golden/SKILL.md | 26 ++----
.../public-param-golden/README.md | 19 ++--
.../public-param-golden/SKILL.md | 26 ++----
17 files changed, 312 insertions(+), 152 deletions(-)
diff --git a/internal/cli/publish.go b/internal/cli/publish.go
index cfc40deb..fa92d50e 100644
--- a/internal/cli/publish.go
+++ b/internal/cli/publish.go
@@ -84,14 +84,13 @@ func newPublishRenameCmd() *cobra.Command {
var dir string
var oldName string
var newName string
- var apiName string
+ var legacyAPIName string
var asJSON bool
cmd := &cobra.Command{
- Use: "rename",
- Short: "Rename a staged CLI (for name collision resolution)",
- Example: ` printing-press publish rename --dir /tmp/staging/library/ai/notion --old-name notion-pp-cli --new-name notion-alt-pp-cli --json
- printing-press publish rename --dir /tmp/staging/library/ai/notion --old-name notion-pp-cli --new-name notion-2-pp-cli --api-name notion --json`,
+ Use: "rename",
+ Short: "Rename a staged CLI (for name collision resolution)",
+ Example: ` printing-press publish rename --dir /tmp/staging/library/ai/notion --old-name notion-pp-cli --new-name notion-alt-pp-cli --json`,
RunE: func(cmd *cobra.Command, args []string) error {
if dir == "" {
return &ExitError{Code: ExitInputError, Err: fmt.Errorf("--dir is required")}
@@ -102,11 +101,7 @@ func newPublishRenameCmd() *cobra.Command {
if newName == "" {
return &ExitError{Code: ExitInputError, Err: fmt.Errorf("--new-name is required")}
}
- if apiName == "" {
- apiName = naming.TrimCLISuffix(oldName)
- }
-
- filesModified, err := pipeline.RenameCLI(dir, oldName, newName, apiName)
+ filesModified, err := pipeline.RenameCLI(dir, oldName, newName, legacyAPIName)
if asJSON {
result := RenameResult{
@@ -144,7 +139,7 @@ func newPublishRenameCmd() *cobra.Command {
cmd.Flags().StringVar(&dir, "dir", "", "Staged CLI directory to rename (required)")
cmd.Flags().StringVar(&oldName, "old-name", "", "Current CLI name (required)")
cmd.Flags().StringVar(&newName, "new-name", "", "New CLI name (required)")
- cmd.Flags().StringVar(&apiName, "api-name", "", "Original API name (defaults to TrimCLISuffix of old-name)")
+ cmd.Flags().StringVar(&legacyAPIName, "api-name", "", "Deprecated no-op; api_name now follows --new-name")
cmd.Flags().BoolVar(&asJSON, "json", false, "Output as JSON")
return cmd
diff --git a/internal/cli/publish_test.go b/internal/cli/publish_test.go
index b97bdc08..4314b5b7 100644
--- a/internal/cli/publish_test.go
+++ b/internal/cli/publish_test.go
@@ -780,7 +780,7 @@ func main() {}
assert.Greater(t, result.FilesModified, 0)
}
-func TestPublishRenameAPINameFallback(t *testing.T) {
+func TestPublishRenameAPINameTracksNewName(t *testing.T) {
root := t.TempDir()
oldName := "test-pp-cli"
newName := "test-alt-pp-cli"
@@ -794,8 +794,9 @@ func TestPublishRenameAPINameFallback(t *testing.T) {
})
cmd := newPublishCmd()
- // No --api-name flag — should fall back to TrimCLISuffix("test-pp-cli") = "test"
- cmd.SetArgs([]string{"rename", "--dir", cliDir, "--old-name", oldName, "--new-name", newName, "--json"})
+ // The legacy flag is accepted for old callers but no longer controls
+ // metadata; the final public slug follows --new-name.
+ cmd.SetArgs([]string{"rename", "--dir", cliDir, "--old-name", oldName, "--new-name", newName, "--api-name", "test", "--json"})
output, err := runWithCapturedStdout(t, cmd.Execute)
require.NoError(t, err)
@@ -804,13 +805,13 @@ func TestPublishRenameAPINameFallback(t *testing.T) {
require.NoError(t, json.Unmarshal([]byte(output), &result))
assert.True(t, result.Success)
- // Verify manifest has correct api_name from fallback
+ // Verify manifest has the final public API slug after rename.
newDir := filepath.Join(root, naming.LibraryDirName(newName))
mData, err := os.ReadFile(filepath.Join(newDir, pipeline.CLIManifestFilename))
require.NoError(t, err)
var m pipeline.CLIManifest
require.NoError(t, json.Unmarshal(mData, &m))
- assert.Equal(t, "test", m.APIName, "api_name should come from TrimCLISuffix fallback")
+ assert.Equal(t, "test-alt", m.APIName, "api_name should track the final public slug")
assert.Equal(t, newName, m.CLIName)
}
diff --git a/internal/generator/generator_test.go b/internal/generator/generator_test.go
index 16d4ba34..a2723862 100644
--- a/internal/generator/generator_test.go
+++ b/internal/generator/generator_test.go
@@ -1888,7 +1888,8 @@ func TestGenerateWithOwnerField(t *testing.T) {
assert.Contains(t, string(mainGo), "testowner")
readme, err := os.ReadFile(filepath.Join(outputDir, "README.md"))
require.NoError(t, err)
- assert.Contains(t, string(readme), "go install github.com/mvanhorn/printing-press-library/library/other/owned/cmd/owned-pp-cli@latest")
+ assert.Contains(t, string(readme), "npx -y @mvanhorn/printing-press install owned")
+ assert.NotContains(t, string(readme), "library/other/owned")
}
func TestGenerateWithEmptyOwner(t *testing.T) {
diff --git a/internal/generator/install_section.go b/internal/generator/install_section.go
index b8c223aa..f3d67d99 100644
--- a/internal/generator/install_section.go
+++ b/internal/generator/install_section.go
@@ -16,15 +16,14 @@ const SkillInstallSectionStartHeading = "## Prerequisites: Install the CLI"
// the template and is not enforced by the canonical-sections check.
const SkillInstallSectionEndSubstr = "Do not proceed with skill commands until verification succeeds."
-// canonicalSkillInstallSectionFormat is the literal text the generator
-// emits into a printed CLI's SKILL.md install section. Indexed verbs:
+// canonicalSkillInstallSectionStartFormat is the literal text the generator
+// emits at the start of a printed CLI's SKILL.md install section. Indexed verb:
//
// %[1]s — CLI slug (e.g. "linear" — produces linear-pp-cli)
-// %[2]s — catalog category (or "other" when empty)
//
// Stays in lockstep with internal/generator/templates/skill.md.tmpl via
// TestCanonicalSkillInstallSectionMatchesTemplate.
-const canonicalSkillInstallSectionFormat = "## Prerequisites: Install the CLI\n" +
+const canonicalSkillInstallSectionStartFormat = "## Prerequisites: Install the CLI\n" +
"\n" +
"This skill drives the `%[1]s-pp-cli` binary. **You must verify the CLI is installed before invoking any command from this skill.** If it is missing, install it first:\n" +
"\n" +
@@ -34,28 +33,42 @@ const canonicalSkillInstallSectionFormat = "## Prerequisites: Install the CLI\n"
" ```\n" +
"2. Verify: `%[1]s-pp-cli --version`\n" +
"3. Ensure `$GOPATH/bin` (or `$HOME/go/bin`) is on `$PATH`.\n" +
- "\n" +
- "If the `npx` install fails (no Node, offline, etc.), fall back to a direct Go install (requires Go 1.26.3 or newer):\n" +
+ "\n"
+
+// canonicalSkillInstallSectionGoFallbackFormat is appended only once the
+// catalog category is known. Before publish, the category-agnostic installer is
+// the only canonical path; emitting library/other/<slug> creates drift.
+const canonicalSkillInstallSectionGoFallbackFormat = "If the `npx` install fails (no Node, offline, etc.), fall back to a direct Go install (requires Go 1.26.3 or newer):\n" +
"\n" +
"```bash\n" +
"go install github.com/mvanhorn/printing-press-library/library/%[2]s/%[1]s/cmd/%[1]s-pp-cli@latest\n" +
"```\n" +
- "\n" +
- "If `--version` reports \"command not found\" after install, the install step did not put the binary on `$PATH`. Do not proceed with skill commands until verification succeeds.\n"
+ "\n"
+
+const canonicalSkillInstallSectionPrepublishFallback = "If the `npx` install fails before this CLI has a public-library category, install Node or use the category-specific Go fallback after publish.\n" +
+ "\n"
+
+const canonicalSkillInstallSectionEnd = "If `--version` reports \"command not found\" after install, the install step did not put the binary on `$PATH`. Do not proceed with skill commands until verification succeeds.\n"
// CanonicalSkillInstallSection returns the exact text of the install/
// prerequisites section that the generator emits into a printed CLI's
-// SKILL.md, given the CLI slug and catalog category (empty -> "other").
+// SKILL.md, given the CLI slug and catalog category. A blank category emits
+// only the category-agnostic installer path so generate-time output does not
+// bake in the publish-time placeholder category.
//
// The verify-skill canonical-sections check uses this function to detect
// post-publish edits to the install instructions. The function is the
// authoritative source post-generation; the template stays in sync via
// TestCanonicalSkillInstallSectionMatchesTemplate.
func CanonicalSkillInstallSection(name, category string) string {
- if category == "" {
- category = "other"
+ section := fmt.Sprintf(canonicalSkillInstallSectionStartFormat, name)
+ if category != "" {
+ section += fmt.Sprintf(canonicalSkillInstallSectionGoFallbackFormat, name, category)
+ } else {
+ section += canonicalSkillInstallSectionPrepublishFallback
}
- return fmt.Sprintf(canonicalSkillInstallSectionFormat, name, category)
+ section += canonicalSkillInstallSectionEnd
+ return section
}
// ExtractSkillInstallSection slices the install/prerequisites block out of
diff --git a/internal/generator/install_section_test.go b/internal/generator/install_section_test.go
index e2b80c18..348a8538 100644
--- a/internal/generator/install_section_test.go
+++ b/internal/generator/install_section_test.go
@@ -64,6 +64,29 @@ func TestCanonicalSkillInstallSectionMatchesTemplate(t *testing.T) {
}
}
+func TestCategorylessInstallSectionsAvoidOtherLibraryPath(t *testing.T) {
+ t.Parallel()
+
+ apiSpec := minimalSpec("gohighlevel")
+ outputDir := filepath.Join(t.TempDir(), "gohighlevel-pp-cli")
+ require.NoError(t, New(apiSpec, outputDir).Generate())
+
+ for _, filename := range []string{"SKILL.md", "README.md"} {
+ t.Run(filename, func(t *testing.T) {
+ t.Parallel()
+
+ rendered, err := os.ReadFile(filepath.Join(outputDir, filename))
+ require.NoError(t, err)
+
+ content := string(rendered)
+ require.NotContains(t, content, "library/other/gohighlevel",
+ "category-less generation must not bake in a publish-time placeholder category")
+ require.Contains(t, content, "npx -y @mvanhorn/printing-press install gohighlevel",
+ "category-less generation should keep the category-agnostic installer path")
+ })
+ }
+}
+
// TestExtractSkillInstallSectionMissingStart confirms the extractor
// reports ok=false when the canonical heading is missing — the case
// where an agent has rewritten the section into something unrecognizable.
diff --git a/internal/generator/skill_test.go b/internal/generator/skill_test.go
index ce775ab5..f21b35a6 100644
--- a/internal/generator/skill_test.go
+++ b/internal/generator/skill_test.go
@@ -600,9 +600,9 @@ func TestSkillFrontmatterOmitsAllEnvVarDeclarations(t *testing.T) {
}
}
-// TestSkillFrontmatterMetadataDefaultsCategoryToOther asserts that when the
-// spec has no Category set, the install module path falls back to 'other'.
-func TestSkillFrontmatterMetadataDefaultsCategoryToOther(t *testing.T) {
+// TestSkillFrontmatterMetadataOmitsUnknownCategoryInstall asserts that when the
+// spec has no Category set, generated install metadata stays category-agnostic.
+func TestSkillFrontmatterMetadataOmitsUnknownCategoryInstall(t *testing.T) {
t.Parallel()
apiSpec := minimalSpec("uncategorized")
@@ -615,9 +615,10 @@ func TestSkillFrontmatterMetadataDefaultsCategoryToOther(t *testing.T) {
require.NoError(t, err)
content := string(skill)
- assert.Contains(t, content,
- "module: github.com/mvanhorn/printing-press-library/library/other/uncategorized/cmd/uncategorized-pp-cli",
- "empty Category should default to 'other' in install module path")
+ assert.NotContains(t, content, "library/other/uncategorized",
+ "empty Category should not bake a placeholder category into install metadata")
+ assert.Contains(t, content, "npx -y @mvanhorn/printing-press install uncategorized --cli-only",
+ "empty Category should keep the category-agnostic installer path")
}
// TestSkillNoExtraCommandsIsBackwardCompatible asserts the template emits
diff --git a/internal/generator/templates/readme.md.tmpl b/internal/generator/templates/readme.md.tmpl
index b246711f..ed0376ef 100644
--- a/internal/generator/templates/readme.md.tmpl
+++ b/internal/generator/templates/readme.md.tmpl
@@ -30,15 +30,21 @@ For CLI only (no skill):
npx -y @mvanhorn/printing-press install {{.Name}} --cli-only
```
+{{ if .Category}}
### Without Node (Go fallback)
If `npx` isn't available (no Node, offline), install the CLI directly via Go (requires Go 1.26.3 or newer):
```bash
-go install github.com/mvanhorn/printing-press-library/library/{{if .Category}}{{.Category}}{{else}}other{{end}}/{{.Name}}/cmd/{{.Name}}-pp-cli@latest
+go install github.com/mvanhorn/printing-press-library/library/{{.Category}}/{{.Name}}/cmd/{{.Name}}-pp-cli@latest
```
This installs the CLI only — no skill.
+{{- else}}
+### Without Node
+
+The generated install path is category-agnostic until this CLI is published. If `npx` is not available before publish, install Node or use the category-specific Go fallback from the public-library entry after publish.
+{{- end}}
### Pre-built binary
@@ -348,9 +354,13 @@ Then invoke `/pp-{{.Name}} <query>` in Claude Code. The skill is the most effici
If you'd rather register this CLI as an MCP server in Claude Code, install the MCP binary first:
+{{ if .Category}}
```bash
-go install github.com/mvanhorn/printing-press-library/library/{{if .Category}}{{.Category}}{{else}}other{{end}}/{{.Name}}/cmd/{{.Name}}-pp-mcp@latest
+go install github.com/mvanhorn/printing-press-library/library/{{.Category}}/{{.Name}}/cmd/{{.Name}}-pp-mcp@latest
```
+{{- else}}
+Install the MCP binary from this CLI's published public-library entry or pre-built release.
+{{- end}}
Then register it:
{{- if and (eq .Auth.Type "api_key") $canonicalEnvVar}}
@@ -437,9 +447,13 @@ Requires Claude Desktop 1.0.0 or later. Pre-built bundles ship for macOS Apple S
If you can't use the MCPB bundle (older Claude Desktop, unsupported platform), install the MCP binary and configure it manually.
+{{ if .Category}}
```bash
-go install github.com/mvanhorn/printing-press-library/library/{{if .Category}}{{.Category}}{{else}}other{{end}}/{{.Name}}/cmd/{{.Name}}-pp-mcp@latest
+go install github.com/mvanhorn/printing-press-library/library/{{.Category}}/{{.Name}}/cmd/{{.Name}}-pp-mcp@latest
```
+{{- else}}
+Install the MCP binary from this CLI's published public-library entry or pre-built release.
+{{- end}}
Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json`):
diff --git a/internal/generator/templates/skill.md.tmpl b/internal/generator/templates/skill.md.tmpl
index 92d52654..2bd2e9c2 100644
--- a/internal/generator/templates/skill.md.tmpl
+++ b/internal/generator/templates/skill.md.tmpl
@@ -10,10 +10,12 @@ metadata:
requires:
bins:
- {{.Name}}-pp-cli
+{{- if .Category}}
install:
- kind: go
bins: [{{.Name}}-pp-cli]
- module: github.com/mvanhorn/printing-press-library/library/{{if .Category}}{{.Category}}{{else}}other{{end}}/{{.Name}}/cmd/{{.Name}}-pp-cli
+ module: github.com/mvanhorn/printing-press-library/library/{{.Category}}/{{.Name}}/cmd/{{.Name}}-pp-cli
+{{- end}}
---
# {{.ProseName}} — Printing Press CLI
@@ -28,13 +30,15 @@ This skill drives the `{{.Name}}-pp-cli` binary. **You must verify the CLI is in
```
2. Verify: `{{.Name}}-pp-cli --version`
3. Ensure `$GOPATH/bin` (or `$HOME/go/bin`) is on `$PATH`.
-
+{{ if .Category}}
If the `npx` install fails (no Node, offline, etc.), fall back to a direct Go install (requires Go 1.26.3 or newer):
```bash
-go install github.com/mvanhorn/printing-press-library/library/{{if .Category}}{{.Category}}{{else}}other{{end}}/{{.Name}}/cmd/{{.Name}}-pp-cli@latest
+go install github.com/mvanhorn/printing-press-library/library/{{.Category}}/{{.Name}}/cmd/{{.Name}}-pp-cli@latest
```
-
+{{ else}}
+If the `npx` install fails before this CLI has a public-library category, install Node or use the category-specific Go fallback after publish.
+{{ end}}
If `--version` reports "command not found" after install, the install step did not put the binary on `$PATH`. Do not proceed with skill commands until verification succeeds.
{{- if and .Narrative .Narrative.ValueProp}}
@@ -369,16 +373,25 @@ Parse `$ARGUMENTS`:
3. **Anything else** → Direct Use (execute as CLI command with `--agent`)
## MCP Server Installation
-
+{{ if .Category}}
1. Install the MCP server:
```bash
- go install github.com/mvanhorn/printing-press-library/library/{{if .Category}}{{.Category}}{{else}}other{{end}}/{{.Name}}/cmd/{{.Name}}-pp-mcp@latest
+ go install github.com/mvanhorn/printing-press-library/library/{{.Category}}/{{.Name}}/cmd/{{.Name}}-pp-mcp@latest
```
2. Register with Claude Code:
```bash
claude mcp add {{.Name}}-pp-mcp -- {{.Name}}-pp-mcp
```
3. Verify: `claude mcp list`
+{{- else}}
+Install the MCP binary from this CLI's published public-library entry or pre-built release, then register it:
+
+```bash
+claude mcp add {{.Name}}-pp-mcp -- {{.Name}}-pp-mcp
+```
+
+Verify: `claude mcp list`
+{{- end}}
## Direct Use
diff --git a/internal/pipeline/renamecli.go b/internal/pipeline/renamecli.go
index ab6a670d..731aeb76 100644
--- a/internal/pipeline/renamecli.go
+++ b/internal/pipeline/renamecli.go
@@ -20,18 +20,20 @@ var renameExtensions = []string{".go", ".yaml", ".yml", ".md"}
// from newCLIName, and cmd/oldCLIName/ → cmd/newCLIName/
// - File content: replaces occurrences of oldCLIName with newCLIName in
// .go, .yaml, .yml, .md files and Makefiles (skips .manuscripts/)
-// - Manifest: updates cli_name to newCLIName, preserves api_name as
-// originalAPIName
+// - Metadata: updates .printing-press.json, manifest.json, and
+// tools-manifest.json to the final public slug/binary names
//
// This function does NOT call RewriteModulePath — that handles import
// paths and is run separately during packaging. RenameCLI handles exactly
// the user-visible references that RewriteModulePath intentionally skips.
-func RenameCLI(dir, oldCLIName, newCLIName, originalAPIName string) (int, error) {
+func RenameCLI(dir, oldCLIName, newCLIName, _ string) (int, error) {
if err := validateRenameInputs(oldCLIName, newCLIName); err != nil {
return 0, err
}
- oldMCPName := naming.MCP(naming.TrimCLISuffix(oldCLIName))
- newMCPName := naming.MCP(naming.TrimCLISuffix(newCLIName))
+ oldSlug := naming.TrimCLISuffix(oldCLIName)
+ newSlug := naming.TrimCLISuffix(newCLIName)
+ oldMCPName := naming.MCP(oldSlug)
+ newMCPName := naming.MCP(newSlug)
// Path traversal protection: verify the directory and new name resolve
// within the expected parent.
@@ -82,8 +84,10 @@ func RenameCLI(dir, oldCLIName, newCLIName, originalAPIName string) (int, error)
return fmt.Errorf("reading %s: %w", path, err)
}
- result := strings.ReplaceAll(string(content), oldCLIName, newCLIName)
- result = strings.ReplaceAll(result, oldMCPName, newMCPName)
+ result := renameCLIContent(string(content), oldCLIName, newCLIName, oldMCPName, newMCPName, oldSlug, newSlug)
+ if filepath.Base(path) == ".gitignore" {
+ result = anchorRenamedGitignorePatterns(result, newCLIName, newMCPName)
+ }
if result == string(content) {
return nil
}
@@ -98,22 +102,30 @@ func RenameCLI(dir, oldCLIName, newCLIName, originalAPIName string) (int, error)
return filesModified, fmt.Errorf("walking directory: %w", err)
}
- // 2. Update manifest: set cli_name, preserve api_name.
+ // 2. Update metadata files from the final public slug/binary names.
manifestPath := filepath.Join(absDir, CLIManifestFilename)
if manifestData, readErr := os.ReadFile(manifestPath); readErr == nil {
var m CLIManifest
if jsonErr := json.Unmarshal(manifestData, &m); jsonErr == nil {
m.CLIName = newCLIName
- m.APIName = originalAPIName
+ m.APIName = newSlug
if m.MCPBinary != "" {
m.MCPBinary = newMCPName
}
if writeErr := WriteCLIManifest(absDir, m); writeErr != nil {
return filesModified, fmt.Errorf("updating manifest: %w", writeErr)
}
+ if writeErr := WriteMCPBManifestFromStruct(absDir, m); writeErr != nil {
+ return filesModified, fmt.Errorf("updating MCPB manifest: %w", writeErr)
+ }
filesModified++
}
}
+ if modified, err := updateToolsManifestAPIName(absDir, newSlug); err != nil {
+ return filesModified, err
+ } else if modified {
+ filesModified++
+ }
// 3. Rename cmd/ subdirectory if it exists.
oldCmdDir := filepath.Join(absDir, "cmd", oldCLIName)
@@ -141,11 +153,57 @@ func RenameCLI(dir, oldCLIName, newCLIName, originalAPIName string) (int, error)
return filesModified, nil
}
+func renameCLIContent(content, oldCLIName, newCLIName, oldMCPName, newMCPName, oldSlug, newSlug string) string {
+ result := strings.ReplaceAll(content, oldCLIName, newCLIName)
+ result = strings.ReplaceAll(result, oldMCPName, newMCPName)
+ result = strings.ReplaceAll(result, "pp-"+oldSlug, "pp-"+newSlug)
+ result = strings.ReplaceAll(result, "/"+oldSlug+"/cmd/", "/"+newSlug+"/cmd/")
+ return result
+}
+
+func anchorRenamedGitignorePatterns(content, cliName, mcpName string) string {
+ lines := strings.Split(content, "\n")
+ for i, line := range lines {
+ if line == cliName || line == mcpName {
+ lines[i] = "/" + line
+ }
+ }
+ return strings.Join(lines, "\n")
+}
+
+func updateToolsManifestAPIName(dir, apiName string) (bool, error) {
+ path := filepath.Join(dir, ToolsManifestFilename)
+ data, err := os.ReadFile(path)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return false, nil
+ }
+ return false, fmt.Errorf("reading tools manifest: %w", err)
+ }
+ var m ToolsManifest
+ if err := json.Unmarshal(data, &m); err != nil {
+ return false, fmt.Errorf("parsing tools manifest: %w", err)
+ }
+ if m.APIName == apiName {
+ return false, nil
+ }
+ m.APIName = apiName
+ updated, err := json.MarshalIndent(m, "", " ")
+ if err != nil {
+ return false, fmt.Errorf("marshaling tools manifest: %w", err)
+ }
+ updated = append(updated, '\n')
+ if err := os.WriteFile(path, updated, 0o644); err != nil {
+ return false, fmt.Errorf("writing tools manifest: %w", err)
+ }
+ return true, nil
+}
+
// shouldRenameFile returns true if a file should be processed during rename.
// Checks extension (.go, .yaml, .yml, .md) and base name (Makefile).
func shouldRenameFile(path string) bool {
base := filepath.Base(path)
- if base == "Makefile" {
+ if base == "Makefile" || base == ".gitignore" {
return true
}
for _, ext := range renameExtensions {
diff --git a/internal/pipeline/renamecli_test.go b/internal/pipeline/renamecli_test.go
index ce8b786c..de05cb26 100644
--- a/internal/pipeline/renamecli_test.go
+++ b/internal/pipeline/renamecli_test.go
@@ -111,6 +111,29 @@ CLI for the `+apiName+` API.
`+"```"+`
claude mcp add `+apiName+` `+mcpName+`
`+"```"+`
+
+Install the skill from `+"`cli-skills/pp-"+apiName+"`"+` and install with:
+
+`+"```"+`
+go install github.com/mvanhorn/printing-press-library/library/other/`+apiName+`/cmd/`+cliName+`@latest
+`+"```"+`
+`), 0o644))
+
+ // SKILL.md
+ require.NoError(t, os.WriteFile(filepath.Join(dir, "SKILL.md"), []byte(`---
+name: pp-`+apiName+`
+metadata:
+ openclaw:
+ install:
+ - kind: go
+ module: github.com/mvanhorn/printing-press-library/library/other/`+apiName+`/cmd/`+cliName+`
+---
+
+# `+apiName+`
+
+`+"```bash"+`
+go install github.com/mvanhorn/printing-press-library/library/other/`+apiName+`/cmd/`+cliName+`@latest
+`+"```"+`
`), 0o644))
// go.mod (module path uses bare CLI name, as generated CLIs do)
@@ -134,6 +157,39 @@ go 1.24
}
data, _ := json.MarshalIndent(m, "", " ")
require.NoError(t, os.WriteFile(filepath.Join(dir, CLIManifestFilename), data, 0o644))
+
+ mcpb := MCPBManifest{
+ ManifestVersion: MCPBManifestVersion,
+ Name: mcpName,
+ Version: "1.0.0",
+ Description: apiName + " API surface as MCP tools.",
+ Author: MCPBAuthor{Name: "CLI Printing Press"},
+ Server: MCPBServer{
+ Type: "binary",
+ EntryPoint: "bin/" + mcpName,
+ MCPConfig: MCPBLaunchSpec{
+ Command: "${__dirname}/bin/" + mcpName,
+ Args: []string{},
+ },
+ },
+ }
+ mcpbData, err := json.MarshalIndent(mcpb, "", " ")
+ require.NoError(t, err)
+ require.NoError(t, os.WriteFile(filepath.Join(dir, MCPBManifestFilename), mcpbData, 0o644))
+
+ toolsManifest := ToolsManifest{
+ APIName: apiName,
+ BaseURL: "https://example.com",
+ Description: "tools for " + apiName,
+ MCPReady: "full",
+ Auth: ManifestAuth{Type: "none"},
+ Tools: []ManifestTool{},
+ }
+ toolsData, err := json.MarshalIndent(toolsManifest, "", " ")
+ require.NoError(t, err)
+ require.NoError(t, os.WriteFile(filepath.Join(dir, ToolsManifestFilename), toolsData, 0o644))
+
+ require.NoError(t, os.WriteFile(filepath.Join(dir, ".gitignore"), []byte(cliName+"\n"+mcpName+"\n"), 0o644))
}
func TestRenameCLI(t *testing.T) {
@@ -212,15 +268,52 @@ func TestRenameCLI(t *testing.T) {
assert.NotContains(t, string(readme), oldName)
assert.Contains(t, string(readme), newMCPName)
assert.NotContains(t, string(readme), oldMCPName)
+ assert.Contains(t, string(readme), "cli-skills/pp-"+naming.TrimCLISuffix(newName))
+ assert.NotContains(t, string(readme), "`cli-skills/pp-"+apiName+"`")
+ assert.Contains(t, string(readme), "library/other/"+naming.TrimCLISuffix(newName)+"/cmd/"+newName)
+ assert.NotContains(t, string(readme), "library/other/"+apiName+"/cmd/"+newName)
+
+ // SKILL should have new skill identity, install metadata, and Go fallback path.
+ skill, err := os.ReadFile(filepath.Join(newDir, "SKILL.md"))
+ require.NoError(t, err)
+ assert.Contains(t, string(skill), "name: pp-"+naming.TrimCLISuffix(newName))
+ assert.NotContains(t, string(skill), "name: pp-"+apiName+"\n")
+ assert.Contains(t, string(skill), "library/other/"+naming.TrimCLISuffix(newName)+"/cmd/"+newName)
+ assert.NotContains(t, string(skill), "library/other/"+apiName+"/cmd/"+newName)
- // Manifest should have new cli_name, original api_name, and new MCP binary
+ // Manifest should have the final public slug, CLI name, and MCP binary.
mData, err := os.ReadFile(filepath.Join(newDir, CLIManifestFilename))
require.NoError(t, err)
var m CLIManifest
require.NoError(t, json.Unmarshal(mData, &m))
assert.Equal(t, newName, m.CLIName)
- assert.Equal(t, apiName, m.APIName)
+ assert.Equal(t, naming.TrimCLISuffix(newName), m.APIName)
assert.Equal(t, newMCPName, m.MCPBinary)
+
+ // MCPB manifest should launch the renamed MCP binary.
+ mcpbData, err := os.ReadFile(filepath.Join(newDir, MCPBManifestFilename))
+ require.NoError(t, err)
+ var mcpb MCPBManifest
+ require.NoError(t, json.Unmarshal(mcpbData, &mcpb))
+ assert.Equal(t, newMCPName, mcpb.Name)
+ assert.Equal(t, "bin/"+newMCPName, mcpb.Server.EntryPoint)
+ assert.Equal(t, "${__dirname}/bin/"+newMCPName, mcpb.Server.MCPConfig.Command)
+ assert.NotContains(t, string(mcpbData), oldMCPName)
+
+ // Tools manifest should use the post-rename public API slug.
+ toolsData, err := os.ReadFile(filepath.Join(newDir, ToolsManifestFilename))
+ require.NoError(t, err)
+ var tools ToolsManifest
+ require.NoError(t, json.Unmarshal(toolsData, &tools))
+ assert.Equal(t, naming.TrimCLISuffix(newName), tools.APIName)
+
+ // Bare binary ignore patterns must be root-anchored so cmd/<binary> is tracked.
+ gitignore, err := os.ReadFile(filepath.Join(newDir, ".gitignore"))
+ require.NoError(t, err)
+ assert.Contains(t, string(gitignore), "/"+newName)
+ assert.Contains(t, string(gitignore), "/"+newMCPName)
+ assert.NotContains(t, string(gitignore), "\n"+newName+"\n")
+ assert.NotContains(t, string(gitignore), "\n"+newMCPName+"\n")
})
t.Run("numeric qualifier renames correctly", func(t *testing.T) {
@@ -386,13 +479,13 @@ func main() {}
assert.Contains(t, string(rootGo), `Use: "`+newName+`"`)
assert.NotContains(t, string(rootGo), oldName)
- // Manifest should have new cli_name, original api_name
+ // Manifest should have the final public slug and CLI name.
mData, err := os.ReadFile(filepath.Join(newDir, CLIManifestFilename))
require.NoError(t, err)
var m CLIManifest
require.NoError(t, json.Unmarshal(mData, &m))
assert.Equal(t, newName, m.CLIName)
- assert.Equal(t, apiName, m.APIName)
+ assert.Equal(t, naming.TrimCLISuffix(newName), m.APIName)
})
t.Run("skips non-target file extensions", func(t *testing.T) {
diff --git a/skills/printing-press-publish/SKILL.md b/skills/printing-press-publish/SKILL.md
index f7e15aa3..6589ec0d 100644
--- a/skills/printing-press-publish/SKILL.md
+++ b/skills/printing-press-publish/SKILL.md
@@ -581,7 +581,6 @@ printing-press publish rename \
--dir "$PUBLISH_REPO_DIR/library/<category>/<api-slug>" \
--old-name <old-cli-name> \
--new-name "$NEW_CLI_NAME" \
- --api-name "$ORIGINAL_API_SLUG" \
--json
```
diff --git a/testdata/golden/expected/generate-golden-api-rich-auth/printing-press-rich-auth/README.md b/testdata/golden/expected/generate-golden-api-rich-auth/printing-press-rich-auth/README.md
index 9d1e5277..c40c6b35 100644
--- a/testdata/golden/expected/generate-golden-api-rich-auth/printing-press-rich-auth/README.md
+++ b/testdata/golden/expected/generate-golden-api-rich-auth/printing-press-rich-auth/README.md
@@ -16,15 +16,10 @@ For CLI only (no skill):
npx -y @mvanhorn/printing-press install printing-press-rich --cli-only
```
-### Without Node (Go fallback)
-If `npx` isn't available (no Node, offline), install the CLI directly via Go (requires Go 1.26.3 or newer):
+### Without Node
-```bash
-go install github.com/mvanhorn/printing-press-library/library/other/printing-press-rich/cmd/printing-press-rich-pp-cli@latest
-```
-
-This installs the CLI only — no skill.
+The generated install path is category-agnostic until this CLI is published. If `npx` is not available before publish, install Node or use the category-specific Go fallback from the public-library entry after publish.
### Pre-built binary
@@ -144,9 +139,8 @@ Then invoke `/pp-printing-press-rich <query>` in Claude Code. The skill is the m
If you'd rather register this CLI as an MCP server in Claude Code, install the MCP binary first:
-```bash
-go install github.com/mvanhorn/printing-press-library/library/other/printing-press-rich/cmd/printing-press-rich-pp-mcp@latest
-```
+
+Install the MCP binary from this CLI's published public-library entry or pre-built release.
Then register it:
@@ -173,9 +167,8 @@ Requires Claude Desktop 1.0.0 or later. Pre-built bundles ship for macOS Apple S
If you can't use the MCPB bundle (older Claude Desktop, unsupported platform), install the MCP binary and configure it manually.
-```bash
-go install github.com/mvanhorn/printing-press-library/library/other/printing-press-rich/cmd/printing-press-rich-pp-mcp@latest
-```
+
+Install the MCP binary from this CLI's published public-library entry or pre-built release.
Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json`):
diff --git a/testdata/golden/expected/generate-golden-api-rich-auth/printing-press-rich-auth/SKILL.md b/testdata/golden/expected/generate-golden-api-rich-auth/printing-press-rich-auth/SKILL.md
index d7dececc..855f1005 100644
--- a/testdata/golden/expected/generate-golden-api-rich-auth/printing-press-rich-auth/SKILL.md
+++ b/testdata/golden/expected/generate-golden-api-rich-auth/printing-press-rich-auth/SKILL.md
@@ -10,10 +10,6 @@ metadata:
requires:
bins:
- printing-press-rich-pp-cli
- install:
- - kind: go
- bins: [printing-press-rich-pp-cli]
- module: github.com/mvanhorn/printing-press-library/library/other/printing-press-rich/cmd/printing-press-rich-pp-cli
---
# Printing Press Rich — Printing Press CLI
@@ -29,11 +25,7 @@ This skill drives the `printing-press-rich-pp-cli` binary. **You must verify the
2. Verify: `printing-press-rich-pp-cli --version`
3. Ensure `$GOPATH/bin` (or `$HOME/go/bin`) is on `$PATH`.
-If the `npx` install fails (no Node, offline, etc.), fall back to a direct Go install (requires Go 1.26.3 or newer):
-
-```bash
-go install github.com/mvanhorn/printing-press-library/library/other/printing-press-rich/cmd/printing-press-rich-pp-cli@latest
-```
+If the `npx` install fails before this CLI has a public-library category, install Node or use the category-specific Go fallback after publish.
If `--version` reports "command not found" after install, the install step did not put the binary on `$PATH`. Do not proceed with skill commands until verification succeeds.
@@ -161,15 +153,13 @@ Parse `$ARGUMENTS`:
## MCP Server Installation
-1. Install the MCP server:
- ```bash
- go install github.com/mvanhorn/printing-press-library/library/other/printing-press-rich/cmd/printing-press-rich-pp-mcp@latest
- ```
-2. Register with Claude Code:
- ```bash
- claude mcp add printing-press-rich-pp-mcp -- printing-press-rich-pp-mcp
- ```
-3. Verify: `claude mcp list`
+Install the MCP binary from this CLI's published public-library entry or pre-built release, then register it:
+
+```bash
+claude mcp add printing-press-rich-pp-mcp -- printing-press-rich-pp-mcp
+```
+
+Verify: `claude mcp list`
## Direct Use
diff --git a/testdata/golden/expected/generate-golden-api/printing-press-golden/README.md b/testdata/golden/expected/generate-golden-api/printing-press-golden/README.md
index fde3b361..9d5bdaf8 100644
--- a/testdata/golden/expected/generate-golden-api/printing-press-golden/README.md
+++ b/testdata/golden/expected/generate-golden-api/printing-press-golden/README.md
@@ -16,15 +16,10 @@ For CLI only (no skill):
npx -y @mvanhorn/printing-press install printing-press-golden --cli-only
```
-### Without Node (Go fallback)
-If `npx` isn't available (no Node, offline), install the CLI directly via Go (requires Go 1.26.3 or newer):
+### Without Node
-```bash
-go install github.com/mvanhorn/printing-press-library/library/other/printing-press-golden/cmd/printing-press-golden-pp-cli@latest
-```
-
-This installs the CLI only — no skill.
+The generated install path is category-agnostic until this CLI is published. If `npx` is not available before publish, install Node or use the category-specific Go fallback from the public-library entry after publish.
### Pre-built binary
@@ -165,9 +160,8 @@ Then invoke `/pp-printing-press-golden <query>` in Claude Code. The skill is the
If you'd rather register this CLI as an MCP server in Claude Code, install the MCP binary first:
-```bash
-go install github.com/mvanhorn/printing-press-library/library/other/printing-press-golden/cmd/printing-press-golden-pp-mcp@latest
-```
+
+Install the MCP binary from this CLI's published public-library entry or pre-built release.
Then register it:
@@ -194,9 +188,8 @@ Requires Claude Desktop 1.0.0 or later. Pre-built bundles ship for macOS Apple S
If you can't use the MCPB bundle (older Claude Desktop, unsupported platform), install the MCP binary and configure it manually.
-```bash
-go install github.com/mvanhorn/printing-press-library/library/other/printing-press-golden/cmd/printing-press-golden-pp-mcp@latest
-```
+
+Install the MCP binary from this CLI's published public-library entry or pre-built release.
Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json`):
diff --git a/testdata/golden/expected/generate-golden-api/printing-press-golden/SKILL.md b/testdata/golden/expected/generate-golden-api/printing-press-golden/SKILL.md
index bd0b2761..260a1e42 100644
--- a/testdata/golden/expected/generate-golden-api/printing-press-golden/SKILL.md
+++ b/testdata/golden/expected/generate-golden-api/printing-press-golden/SKILL.md
@@ -10,10 +10,6 @@ metadata:
requires:
bins:
- printing-press-golden-pp-cli
- install:
- - kind: go
- bins: [printing-press-golden-pp-cli]
- module: github.com/mvanhorn/printing-press-library/library/other/printing-press-golden/cmd/printing-press-golden-pp-cli
---
# Printing Press Golden — Printing Press CLI
@@ -29,11 +25,7 @@ This skill drives the `printing-press-golden-pp-cli` binary. **You must verify t
2. Verify: `printing-press-golden-pp-cli --version`
3. Ensure `$GOPATH/bin` (or `$HOME/go/bin`) is on `$PATH`.
-If the `npx` install fails (no Node, offline, etc.), fall back to a direct Go install (requires Go 1.26.3 or newer):
-
-```bash
-go install github.com/mvanhorn/printing-press-library/library/other/printing-press-golden/cmd/printing-press-golden-pp-cli@latest
-```
+If the `npx` install fails before this CLI has a public-library category, install Node or use the category-specific Go fallback after publish.
If `--version` reports "command not found" after install, the install step did not put the binary on `$PATH`. Do not proceed with skill commands until verification succeeds.
@@ -170,15 +162,13 @@ Parse `$ARGUMENTS`:
## MCP Server Installation
-1. Install the MCP server:
- ```bash
- go install github.com/mvanhorn/printing-press-library/library/other/printing-press-golden/cmd/printing-press-golden-pp-mcp@latest
- ```
-2. Register with Claude Code:
- ```bash
- claude mcp add printing-press-golden-pp-mcp -- printing-press-golden-pp-mcp
- ```
-3. Verify: `claude mcp list`
+Install the MCP binary from this CLI's published public-library entry or pre-built release, then register it:
+
+```bash
+claude mcp add printing-press-golden-pp-mcp -- printing-press-golden-pp-mcp
+```
+
+Verify: `claude mcp list`
## Direct Use
diff --git a/testdata/golden/expected/generate-public-param-names/public-param-golden/README.md b/testdata/golden/expected/generate-public-param-names/public-param-golden/README.md
index 60acc282..ec2b4076 100644
--- a/testdata/golden/expected/generate-public-param-names/public-param-golden/README.md
+++ b/testdata/golden/expected/generate-public-param-names/public-param-golden/README.md
@@ -16,15 +16,10 @@ For CLI only (no skill):
npx -y @mvanhorn/printing-press install public-param-golden --cli-only
```
-### Without Node (Go fallback)
-If `npx` isn't available (no Node, offline), install the CLI directly via Go (requires Go 1.26.3 or newer):
+### Without Node
-```bash
-go install github.com/mvanhorn/printing-press-library/library/other/public-param-golden/cmd/public-param-golden-pp-cli@latest
-```
-
-This installs the CLI only — no skill.
+The generated install path is category-agnostic until this CLI is published. If `npx` is not available before publish, install Node or use the category-specific Go fallback from the public-library entry after publish.
### Pre-built binary
@@ -136,9 +131,8 @@ Then invoke `/pp-public-param-golden <query>` in Claude Code. The skill is the m
If you'd rather register this CLI as an MCP server in Claude Code, install the MCP binary first:
-```bash
-go install github.com/mvanhorn/printing-press-library/library/other/public-param-golden/cmd/public-param-golden-pp-mcp@latest
-```
+
+Install the MCP binary from this CLI's published public-library entry or pre-built release.
Then register it:
@@ -164,9 +158,8 @@ Requires Claude Desktop 1.0.0 or later. Pre-built bundles ship for macOS Apple S
If you can't use the MCPB bundle (older Claude Desktop, unsupported platform), install the MCP binary and configure it manually.
-```bash
-go install github.com/mvanhorn/printing-press-library/library/other/public-param-golden/cmd/public-param-golden-pp-mcp@latest
-```
+
+Install the MCP binary from this CLI's published public-library entry or pre-built release.
Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json`):
diff --git a/testdata/golden/expected/generate-public-param-names/public-param-golden/SKILL.md b/testdata/golden/expected/generate-public-param-names/public-param-golden/SKILL.md
index cfa65c8c..9fd42252 100644
--- a/testdata/golden/expected/generate-public-param-names/public-param-golden/SKILL.md
+++ b/testdata/golden/expected/generate-public-param-names/public-param-golden/SKILL.md
@@ -10,10 +10,6 @@ metadata:
requires:
bins:
- public-param-golden-pp-cli
- install:
- - kind: go
- bins: [public-param-golden-pp-cli]
- module: github.com/mvanhorn/printing-press-library/library/other/public-param-golden/cmd/public-param-golden-pp-cli
---
# Public Param Golden — Printing Press CLI
@@ -29,11 +25,7 @@ This skill drives the `public-param-golden-pp-cli` binary. **You must verify the
2. Verify: `public-param-golden-pp-cli --version`
3. Ensure `$GOPATH/bin` (or `$HOME/go/bin`) is on `$PATH`.
-If the `npx` install fails (no Node, offline, etc.), fall back to a direct Go install (requires Go 1.26.3 or newer):
-
-```bash
-go install github.com/mvanhorn/printing-press-library/library/other/public-param-golden/cmd/public-param-golden-pp-cli@latest
-```
+If the `npx` install fails before this CLI has a public-library category, install Node or use the category-specific Go fallback after publish.
If `--version` reports "command not found" after install, the install step did not put the binary on `$PATH`. Do not proceed with skill commands until verification succeeds.
@@ -138,15 +130,13 @@ Parse `$ARGUMENTS`:
## MCP Server Installation
-1. Install the MCP server:
- ```bash
- go install github.com/mvanhorn/printing-press-library/library/other/public-param-golden/cmd/public-param-golden-pp-mcp@latest
- ```
-2. Register with Claude Code:
- ```bash
- claude mcp add public-param-golden-pp-mcp -- public-param-golden-pp-mcp
- ```
-3. Verify: `claude mcp list`
+Install the MCP binary from this CLI's published public-library entry or pre-built release, then register it:
+
+```bash
+claude mcp add public-param-golden-pp-mcp -- public-param-golden-pp-mcp
+```
+
+Verify: `claude mcp list`
## Direct Use
← 9675f8b2 fix(cli): preserve novel cli files on force generate (#747)
·
back to Cli Printing Press
·
feat(cli): credit original printer in per-CLI README (closes ee8f6ad9 →