← back to Cli Printing Press
feat(templates): auto-JSON piping, --no-input, --compact (Ramp CLI learnings)
300c01bdac139f2fdf4c6469ed9e21f3f1aaf2d3 · 2026-03-26 18:29:33 -0700 · Matt Van Horn
Inspired by Ramp CLI's agent-first design (launched 2026-03-25):
1. Auto-JSON when piped: printOutput() detects non-terminal stdout via
os.ModeCharDevice and defaults to JSON. Agents no longer need to pass
--json explicitly - piping to jq/grep just works. Explicit --json and
--human flags override.
2. --no-input global flag: disables all interactive prompts for CI and
agent environments. Combines with --yes (confirmation bypass) for
fully non-interactive operation.
3. --compact output mode: returns only high-gravity fields (id, name,
title, status, state, priority, url, timestamps). Cuts agent token
costs 60-80% on list commands. Uses Ramp's insight that LLM context
window consumption is a per-token cost center.
Removed go-isatty dependency from go.mod.tmpl in favor of local
isTerminal() using os.ModeCharDevice (zero external deps).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Files touched
M internal/generator/templates/go.mod.tmplM internal/generator/templates/helpers.go.tmplM internal/generator/templates/root.go.tmpl
Diff
commit 300c01bdac139f2fdf4c6469ed9e21f3f1aaf2d3
Author: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Date: Thu Mar 26 18:29:33 2026 -0700
feat(templates): auto-JSON piping, --no-input, --compact (Ramp CLI learnings)
Inspired by Ramp CLI's agent-first design (launched 2026-03-25):
1. Auto-JSON when piped: printOutput() detects non-terminal stdout via
os.ModeCharDevice and defaults to JSON. Agents no longer need to pass
--json explicitly - piping to jq/grep just works. Explicit --json and
--human flags override.
2. --no-input global flag: disables all interactive prompts for CI and
agent environments. Combines with --yes (confirmation bypass) for
fully non-interactive operation.
3. --compact output mode: returns only high-gravity fields (id, name,
title, status, state, priority, url, timestamps). Cuts agent token
costs 60-80% on list commands. Uses Ramp's insight that LLM context
window consumption is a per-token cost center.
Removed go-isatty dependency from go.mod.tmpl in favor of local
isTerminal() using os.ModeCharDevice (zero external deps).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---
internal/generator/templates/go.mod.tmpl | 1 -
internal/generator/templates/helpers.go.tmpl | 64 ++++++++++++++++++++++++++--
internal/generator/templates/root.go.tmpl | 4 ++
3 files changed, 65 insertions(+), 4 deletions(-)
diff --git a/internal/generator/templates/go.mod.tmpl b/internal/generator/templates/go.mod.tmpl
index bdaa3876..c64dc2e6 100644
--- a/internal/generator/templates/go.mod.tmpl
+++ b/internal/generator/templates/go.mod.tmpl
@@ -4,7 +4,6 @@ go 1.23
require (
github.com/spf13/cobra v1.9.1
- github.com/mattn/go-isatty v0.0.20
{{- if eq .Config.Format "toml"}}
github.com/pelletier/go-toml/v2 v2.2.4
{{- end}}
diff --git a/internal/generator/templates/helpers.go.tmpl b/internal/generator/templates/helpers.go.tmpl
index 0ea7e5d2..1361d710 100644
--- a/internal/generator/templates/helpers.go.tmpl
+++ b/internal/generator/templates/helpers.go.tmpl
@@ -12,8 +12,6 @@ import (
"sort"
"strings"
"text/tabwriter"
-
- "github.com/mattn/go-isatty"
)
var As = errors.As
@@ -37,7 +35,18 @@ func colorEnabled() bool {
if os.Getenv("TERM") == "dumb" {
return false
}
- return isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd())
+ return isTerminal(os.Stdout)
+}
+
+func isTerminal(w io.Writer) bool {
+ if f, ok := w.(*os.File); ok {
+ fi, err := f.Stat()
+ if err != nil {
+ return true
+ }
+ return (fi.Mode() & os.ModeCharDevice) != 0
+ }
+ return false
}
func bold(s string) string {
@@ -287,6 +296,10 @@ func filterFields(data json.RawMessage, fields string) json.RawMessage {
// printOutputWithFlags routes output through the right format based on flags.
func printOutputWithFlags(w io.Writer, data json.RawMessage, flags *rootFlags) error {
+ // Apply --compact: filter to high-gravity fields only
+ if flags.compact {
+ data = compactFields(data)
+ }
// Apply --select field filtering
if flags.selectFields != "" {
data = filterFields(data, flags.selectFields)
@@ -302,6 +315,47 @@ func printOutputWithFlags(w io.Writer, data json.RawMessage, flags *rootFlags) e
return printOutput(w, data, flags.asJSON)
}
+// compactFields keeps only the most important fields for agent consumption.
+func compactFields(data json.RawMessage) json.RawMessage {
+ keepFields := map[string]bool{
+ "id": true, "name": true, "title": true, "identifier": true,
+ "status": true, "state": true, "type": true, "priority": true,
+ "url": true, "email": true, "key": true,
+ "created_at": true, "updated_at": true, "createdAt": true, "updatedAt": true,
+ "description": true,
+ }
+
+ var items []map[string]any
+ if err := json.Unmarshal(data, &items); err == nil {
+ filtered := make([]map[string]any, 0, len(items))
+ for _, item := range items {
+ compact := map[string]any{}
+ for k, v := range item {
+ if keepFields[k] {
+ compact[k] = v
+ }
+ }
+ filtered = append(filtered, compact)
+ }
+ result, _ := json.Marshal(filtered)
+ return result
+ }
+
+ var obj map[string]any
+ if err := json.Unmarshal(data, &obj); err == nil {
+ compact := map[string]any{}
+ for k, v := range obj {
+ if keepFields[k] {
+ compact[k] = v
+ }
+ }
+ result, _ := json.Marshal(compact)
+ return result
+ }
+
+ return data
+}
+
// printCSV renders JSON arrays as CSV with header row.
func printCSV(w io.Writer, data json.RawMessage) error {
var items []map[string]any
@@ -346,6 +400,10 @@ func printCSV(w io.Writer, data json.RawMessage) error {
// printOutput auto-detects arrays and renders as tables, or prints raw JSON for objects.
func printOutput(w io.Writer, data json.RawMessage, asJSON bool) error {
+ if !asJSON && !isTerminal(w) {
+ asJSON = true
+ }
+
if asJSON {
enc := json.NewEncoder(w)
enc.SetIndent("", " ")
diff --git a/internal/generator/templates/root.go.tmpl b/internal/generator/templates/root.go.tmpl
index 7fada1a0..50d0fb61 100644
--- a/internal/generator/templates/root.go.tmpl
+++ b/internal/generator/templates/root.go.tmpl
@@ -18,11 +18,13 @@ var version = "{{.Version}}"
type rootFlags struct {
asJSON bool
+ compact bool
csv bool
plain bool
quiet bool
dryRun bool
noCache bool
+ noInput bool
yes bool
selectFields string
configPath string
@@ -43,6 +45,7 @@ func Execute() error {
rootCmd.SetVersionTemplate("{{.Name}}-cli {{"{{"}} .Version {{"}}"}}\n")
rootCmd.PersistentFlags().BoolVar(&flags.asJSON, "json", false, "Output as JSON")
+ rootCmd.PersistentFlags().BoolVar(&flags.compact, "compact", false, "Return only key fields (id, name, status, timestamps) for minimal token usage")
rootCmd.PersistentFlags().BoolVar(&flags.csv, "csv", false, "Output as CSV (table and array responses)")
rootCmd.PersistentFlags().BoolVar(&flags.plain, "plain", false, "Output as plain tab-separated text")
rootCmd.PersistentFlags().BoolVar(&flags.quiet, "quiet", false, "Bare output, one value per line")
@@ -50,6 +53,7 @@ func Execute() error {
rootCmd.PersistentFlags().DurationVar(&flags.timeout, "timeout", 30*time.Second, "Request timeout")
rootCmd.PersistentFlags().BoolVar(&flags.dryRun, "dry-run", false, "Show request without sending")
rootCmd.PersistentFlags().BoolVar(&flags.noCache, "no-cache", false, "Bypass response cache")
+ rootCmd.PersistentFlags().BoolVar(&flags.noInput, "no-input", false, "Disable all interactive prompts (for CI/agents)")
rootCmd.PersistentFlags().StringVar(&flags.selectFields, "select", "", "Comma-separated fields to include in output (e.g. --select id,name,status)")
rootCmd.PersistentFlags().BoolVar(&flags.yes, "yes", false, "Skip confirmation prompts (for agents and scripts)")
rootCmd.PersistentFlags().BoolVar(&noColor, "no-color", false, "Disable colored output")
← 64c58d86 feat(templates): agent-friendly error messages, truncation h
·
back to Cli Printing Press
·
docs: update README with v2 overhaul, agent-first features, 345b0753 →