← back to Cli Printing Press
fix(cli): generator template improvements — ID typing, dead imports, README cookbook (#102)
82a3614844064fb71ba9da7be67e2cbcbd2f7ef1 · 2026-04-01 00:59:18 -0700 · Trevin Chow
* fix(cli): generator template improvements — ID typing, dead imports, README cookbook
Three template improvements from the Steam retro:
1. ID params override int→string: Parameters named *id/*ids now generate
StringVar instead of IntVar. SteamID64 (17 digits) overflows int64,
and zero-value int confusion makes IntVar unsuitable for identifiers.
Affects goType, cobraFlagFunc, defaultVal, and zeroVal — all have
param-aware variants that check isIDParam().
2. Remove dead import markers: The `var _ = strings.ReplaceAll` and
similar lines are no longer emitted. Removed unused `strings` import.
Made `io` import conditional on POST/PUT/PATCH methods.
3. README Cookbook section: readme.md.tmpl now emits a Cookbook section
with 5 common workflow examples (JSON output, field filtering,
dry-run, sync+search, export). Scorecard README scorer gives +2 for
this section.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(cli): scope ID-to-string override to query/path params only, deterministic cookbook
Codex review fixes:
1. ID-to-string override now only applies to query/path flag params, not
request body fields. Body fields keep their original int type to
preserve the JSON wire format — sending "123" instead of 123 would
break strict APIs. Added defaultValForParam() alongside defaultVal().
2. firstResource() sorts map keys before selecting, making the README
cookbook examples deterministic across runs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Files touched
M internal/generator/generator.goM internal/generator/templates/command_endpoint.go.tmplM internal/generator/templates/readme.md.tmpl
Diff
commit 82a3614844064fb71ba9da7be67e2cbcbd2f7ef1
Author: Trevin Chow <trevin@trevinchow.com>
Date: Wed Apr 1 00:59:18 2026 -0700
fix(cli): generator template improvements — ID typing, dead imports, README cookbook (#102)
* fix(cli): generator template improvements — ID typing, dead imports, README cookbook
Three template improvements from the Steam retro:
1. ID params override int→string: Parameters named *id/*ids now generate
StringVar instead of IntVar. SteamID64 (17 digits) overflows int64,
and zero-value int confusion makes IntVar unsuitable for identifiers.
Affects goType, cobraFlagFunc, defaultVal, and zeroVal — all have
param-aware variants that check isIDParam().
2. Remove dead import markers: The `var _ = strings.ReplaceAll` and
similar lines are no longer emitted. Removed unused `strings` import.
Made `io` import conditional on POST/PUT/PATCH methods.
3. README Cookbook section: readme.md.tmpl now emits a Cookbook section
with 5 common workflow examples (JSON output, field filtering,
dry-run, sync+search, export). Scorecard README scorer gives +2 for
this section.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(cli): scope ID-to-string override to query/path params only, deterministic cookbook
Codex review fixes:
1. ID-to-string override now only applies to query/path flag params, not
request body fields. Body fields keep their original int type to
preserve the JSON wire format — sending "123" instead of 123 would
break strict APIs. Added defaultValForParam() alongside defaultVal().
2. firstResource() sorts map keys before selecting, making the README
cookbook examples deterministic across runs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---
internal/generator/generator.go | 86 +++++++++++++++++++---
.../generator/templates/command_endpoint.go.tmpl | 15 ++--
internal/generator/templates/readme.md.tmpl | 22 ++++++
3 files changed, 101 insertions(+), 22 deletions(-)
diff --git a/internal/generator/generator.go b/internal/generator/generator.go
index 2b5389d7..11182ce9 100644
--- a/internal/generator/generator.go
+++ b/internal/generator/generator.go
@@ -6,6 +6,7 @@ import (
"os"
"os/exec"
"path/filepath"
+ "sort"
"strconv"
"strings"
"text/template"
@@ -68,18 +69,27 @@ func New(s *spec.APISpec, outputDir string) *Generator {
templates: make(map[string]*template.Template),
}
g.funcs = template.FuncMap{
- "title": cases.Title(language.English).String,
- "lower": strings.ToLower,
- "upper": strings.ToUpper,
- "join": strings.Join,
- "camel": toCamel,
- "snake": toSnake,
- "pascal": toPascal,
- "goType": goType,
- "goStoreType": goStoreType,
- "cobraFlagFunc": cobraFlagFunc,
- "defaultVal": defaultVal,
- "zeroVal": zeroVal,
+ "title": cases.Title(language.English).String,
+ "lower": strings.ToLower,
+ "upper": strings.ToUpper,
+ "join": strings.Join,
+ "camel": toCamel,
+ "snake": toSnake,
+ "pascal": toPascal,
+ "goType": goType,
+ "goTypeForParam": goTypeForParam,
+ "goStoreType": goStoreType,
+ "cobraFlagFunc": cobraFlagFunc,
+ "cobraFlagFuncForParam": cobraFlagFuncForParam,
+ "defaultVal": defaultVal,
+ "defaultValForParam": defaultValForParam,
+ "zeroVal": zeroVal,
+ "zeroValForParam": func(name, t string) string {
+ if isIDParam(name) && t == "int" {
+ return `""`
+ }
+ return zeroVal(t)
+ },
"positionalArgs": positionalArgs,
"configTag": configTag,
"camelToJSON": camelToJSON,
@@ -97,6 +107,17 @@ func New(s *spec.APISpec, outputDir string) *Generator {
"modulePath": func() string { return naming.CLI(s.Name) },
"kebab": toKebab,
"envName": func(s string) string { return strings.ToUpper(strings.ReplaceAll(s, "-", "_")) },
+ "firstResource": func(resources map[string]spec.Resource) string {
+ var names []string
+ for name := range resources {
+ names = append(names, name)
+ }
+ sort.Strings(names)
+ if len(names) > 0 {
+ return names[0]
+ }
+ return "resource"
+ },
}
return g
}
@@ -618,6 +639,17 @@ func toPascal(s string) string {
return strings.Join(parts, "")
}
+// isIDParam returns true if the parameter name suggests it's an identifier
+// that should be typed as string regardless of the spec's declared type.
+// IDs like steamid (17-digit number) overflow int64, and zero-value confusion
+// makes IntVar unsuitable for identifiers.
+func isIDParam(name string) bool {
+ lower := strings.ToLower(name)
+ return strings.HasSuffix(lower, "id") || strings.HasSuffix(lower, "ids") ||
+ strings.HasSuffix(lower, "_id") || strings.HasSuffix(lower, "_ids") ||
+ lower == "steamid" || lower == "steamids"
+}
+
func goType(t string) string {
switch t {
case "string":
@@ -708,6 +740,36 @@ func cobraFlagFunc(t string) string {
}
}
+// goTypeForParam returns the Go type for a parameter, overriding int→string
+// for ID-like parameters to avoid overflow and zero-value confusion.
+func goTypeForParam(name, t string) string {
+ if isIDParam(name) && t == "int" {
+ return "string"
+ }
+ return goType(t)
+}
+
+// cobraFlagFuncForParam returns the cobra flag function, overriding IntVar→StringVar
+// for ID-like parameters.
+func cobraFlagFuncForParam(name, t string) string {
+ if isIDParam(name) && t == "int" {
+ return "StringVar"
+ }
+ return cobraFlagFunc(t)
+}
+
+// defaultValForParam returns the default value for a flag parameter,
+// overriding int→string for ID-like parameters.
+func defaultValForParam(p spec.Param) string {
+ if isIDParam(p.Name) && p.Type == "int" {
+ if p.Default != nil {
+ return fmt.Sprintf("%q", fmt.Sprintf("%v", p.Default))
+ }
+ return `""`
+ }
+ return defaultVal(p)
+}
+
func defaultVal(p spec.Param) string {
if p.Default != nil {
// Coerce the default value to match the declared param type
diff --git a/internal/generator/templates/command_endpoint.go.tmpl b/internal/generator/templates/command_endpoint.go.tmpl
index a3f83537..b27c210b 100644
--- a/internal/generator/templates/command_endpoint.go.tmpl
+++ b/internal/generator/templates/command_endpoint.go.tmpl
@@ -6,23 +6,18 @@ package cli
import (
"encoding/json"
"fmt"
+{{- if or (eq .Endpoint.Method "POST") (eq .Endpoint.Method "PUT") (eq .Endpoint.Method "PATCH")}}
"io"
+{{- end}}
"os"
- "strings"
"github.com/spf13/cobra"
)
-var _ = strings.ReplaceAll // ensure import
-var _ = fmt.Sprintf // ensure import
-var _ = io.ReadAll // ensure import
-var _ = os.Stdin // ensure import
-var _ json.RawMessage // ensure import
-
func new{{camel .FuncPrefix}}{{camel .EndpointName}}Cmd(flags *rootFlags) *cobra.Command {
{{- range .Endpoint.Params}}
{{- if not .Positional}}
- var flag{{camel .Name}} {{goType .Type}}
+ var flag{{camel .Name}} {{goTypeForParam .Name .Type}}
{{- end}}
{{- end}}
{{- range .Endpoint.Body}}
@@ -78,7 +73,7 @@ func new{{camel .FuncPrefix}}{{camel .EndpointName}}Cmd(flags *rootFlags) *cobra
params := map[string]string{}
{{- range .Endpoint.Params}}
{{- if not .Positional}}
- if flag{{camel .Name}} != {{zeroVal .Type}} {
+ if flag{{camel .Name}} != {{zeroValForParam .Name .Type}} {
params["{{.Name}}"] = fmt.Sprintf("%v", flag{{camel .Name}})
}
{{- end}}
@@ -239,7 +234,7 @@ func new{{camel .FuncPrefix}}{{camel .EndpointName}}Cmd(flags *rootFlags) *cobra
{{- range .Endpoint.Params}}
{{- if not .Positional}}
- cmd.Flags().{{cobraFlagFunc .Type}}(&flag{{camel .Name}}, "{{flagName .Name}}", {{defaultVal .}}, "{{oneline .Description}}")
+ cmd.Flags().{{cobraFlagFuncForParam .Name .Type}}(&flag{{camel .Name}}, "{{flagName .Name}}", {{defaultValForParam .}}, "{{oneline .Description}}")
{{- if .Required}}
_ = cmd.MarkFlagRequired("{{flagName .Name}}")
{{- end}}
diff --git a/internal/generator/templates/readme.md.tmpl b/internal/generator/templates/readme.md.tmpl
index f2248df2..0dc1df26 100644
--- a/internal/generator/templates/readme.md.tmpl
+++ b/internal/generator/templates/readme.md.tmpl
@@ -135,6 +135,28 @@ This CLI is designed for AI agent consumption:
Exit codes: `0` success, `2` usage error, `3` not found, `4` auth error, `5` API error, `7` rate limited, `10` config error.
+## Cookbook
+
+Common workflows and recipes:
+
+```bash
+# List resources as JSON for scripting
+{{.Name}}-pp-cli {{firstResource .Resources}} list --json
+
+# Filter to specific fields
+{{.Name}}-pp-cli {{firstResource .Resources}} list --json --select id,name,status
+
+# Dry run to preview the request
+{{.Name}}-pp-cli {{firstResource .Resources}} list --dry-run
+
+# Sync data locally for offline search
+{{.Name}}-pp-cli sync
+{{.Name}}-pp-cli search "query"
+
+# Export for backup
+{{.Name}}-pp-cli export --format jsonl > backup.jsonl
+```
+
## Health Check
```bash
← 8a5d01cc fix(cli): scorer behavioral detection — path validity, insig
·
back to Cli Printing Press
·
fix(ci): add post-merge hook to rebuild binary after git pul 3af5d2d7 →