[object Object]

← back to Cli Printing Press

feat(cli): add smart-default output format to generator templates (#60)

283ab9bfeaadaa44a20b8affe68453b870682de7 · 2026-03-29 18:45:35 -0700 · Trevin Chow

Generated CLIs now auto-detect terminal vs pipe to choose output format:
- Terminal (human typing): renders tables via printAutoTable
- Pipe (agent/script): outputs JSON (existing behavior)
- --json/--agent/--csv/--compact: always machine format

This works because Claude Code, Codex, and scripts capture stdout as a
pipe, so isTerminal() returns false automatically. Humans in interactive
terminals get readable tables without needing --human-friendly.

Adds wantsHumanTable() helper and formatCompact() for large numbers.
Only applies to GET/HEAD endpoints with array responses.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit 283ab9bfeaadaa44a20b8affe68453b870682de7
Author: Trevin Chow <trevin@trevinchow.com>
Date:   Sun Mar 29 18:45:35 2026 -0700

    feat(cli): add smart-default output format to generator templates (#60)
    
    Generated CLIs now auto-detect terminal vs pipe to choose output format:
    - Terminal (human typing): renders tables via printAutoTable
    - Pipe (agent/script): outputs JSON (existing behavior)
    - --json/--agent/--csv/--compact: always machine format
    
    This works because Claude Code, Codex, and scripts capture stdout as a
    pipe, so isTerminal() returns false automatically. Humans in interactive
    terminals get readable tables without needing --human-friendly.
    
    Adds wantsHumanTable() helper and formatCompact() for large numbers.
    Only applies to GET/HEAD endpoints with array responses.
    
    Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---
 .../generator/templates/command_endpoint.go.tmpl   | 14 +++++++++++
 internal/generator/templates/helpers.go.tmpl       | 29 ++++++++++++++++++++++
 2 files changed, 43 insertions(+)

diff --git a/internal/generator/templates/command_endpoint.go.tmpl b/internal/generator/templates/command_endpoint.go.tmpl
index 59f198c1..c51fb786 100644
--- a/internal/generator/templates/command_endpoint.go.tmpl
+++ b/internal/generator/templates/command_endpoint.go.tmpl
@@ -191,6 +191,20 @@ func new{{camel .FuncPrefix}}{{camel .EndpointName}}Cmd(flags *rootFlags) *cobra
 				}
 				return printOutput(cmd.OutOrStdout(), json.RawMessage(envelopeJSON), true)
 			}
+{{- end}}
+{{- if or (eq .Endpoint.Method "GET") (eq .Endpoint.Method "HEAD")}}
+			if wantsHumanTable(cmd.OutOrStdout(), flags) {
+				var items []map[string]any
+				if json.Unmarshal(data, &items) == nil && len(items) > 0 {
+					if err := printAutoTable(cmd.OutOrStdout(), items); err != nil {
+						return err
+					}
+					if len(items) >= 25 {
+						fmt.Fprintf(os.Stderr, "\nShowing %d results. To narrow: add --limit, --json --select, or filter flags.\n", len(items))
+					}
+					return nil
+				}
+			}
 {{- end}}
 			return printOutputWithFlags(cmd.OutOrStdout(), data, flags)
 		},
diff --git a/internal/generator/templates/helpers.go.tmpl b/internal/generator/templates/helpers.go.tmpl
index 87720dfc..0413e589 100644
--- a/internal/generator/templates/helpers.go.tmpl
+++ b/internal/generator/templates/helpers.go.tmpl
@@ -519,6 +519,35 @@ func suggestFlag(unknown string, cmd *cobra.Command) string {
 	return best
 }
 
+// wantsHumanTable returns true when output should be a human-friendly table.
+// Smart default: terminal=table, pipe=JSON.
+// - Human in terminal: isTerminal()=true → table
+// - Claude Code/Codex bash tool: stdout piped → JSON
+// - --json/--csv/--compact/--agent: machine format → JSON
+func wantsHumanTable(w io.Writer, flags *rootFlags) bool {
+	if flags.asJSON || flags.csv || flags.compact || flags.quiet || flags.plain {
+		return false
+	}
+	if flags.selectFields != "" {
+		return false
+	}
+	return isTerminal(w)
+}
+
+// formatCompact formats large numbers compactly (e.g., 1.2M, 728K).
+func formatCompact(n int64) string {
+	switch {
+	case n >= 1_000_000:
+		return fmt.Sprintf("%.1fM", float64(n)/1_000_000)
+	case n >= 10_000:
+		return fmt.Sprintf("%.0fK", float64(n)/1_000)
+	case n >= 1_000:
+		return fmt.Sprintf("%.1fK", float64(n)/1_000)
+	default:
+		return fmt.Sprintf("%d", n)
+	}
+}
+
 func printAutoTable(w io.Writer, items []map[string]any) error {
 	if len(items) == 0 {
 		return nil

← f27c735e docs(cli): add "Why These CLIs Win" section to README (#59)  ·  back to Cli Printing Press  ·  feat(cli): add spec_source, auth_required, client_pattern to f5716d90 →