[object Object]

← back to Cli Printing Press

feat(cli): add --json flag to generate, print, and vision commands

7f5c35202eed5a6f48cd8243462be953e853b886 · 2026-03-27 13:35:10 -0700 · Trevin Chow

Enables agents to programmatically extract results from the three
mutating commands. JSON goes to stdout via json.NewEncoder, matching
the existing pattern in dogfood and scorecard. All stderr output is
unchanged.

The generate command's "polished" field reflects actual outcome (not
just the flag), and print's "phases_completed" derives from pipeline
state so --resume reports correct progress.

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

Files touched

Diff

commit 7f5c35202eed5a6f48cd8243462be953e853b886
Author: Trevin Chow <trevin@trevinchow.com>
Date:   Fri Mar 27 13:35:10 2026 -0700

    feat(cli): add --json flag to generate, print, and vision commands
    
    Enables agents to programmatically extract results from the three
    mutating commands. JSON goes to stdout via json.NewEncoder, matching
    the existing pattern in dogfood and scorecard. All stderr output is
    unchanged.
    
    The generate command's "polished" field reflects actual outcome (not
    just the flag), and print's "phases_completed" derives from pipeline
    state so --resume reports correct progress.
    
    Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---
 internal/cli/root.go   | 45 +++++++++++++++++++++++++++++++++++++++++++++
 internal/cli/vision.go |  9 +++++++++
 2 files changed, 54 insertions(+)

diff --git a/internal/cli/root.go b/internal/cli/root.go
index 68b169d8..1a207e58 100644
--- a/internal/cli/root.go
+++ b/internal/cli/root.go
@@ -3,6 +3,7 @@ package cli
 import (
 	"crypto/sha256"
 	"encoding/hex"
+	"encoding/json"
 	"fmt"
 	"io"
 	"net/http"
@@ -55,6 +56,7 @@ func newGenerateCmd() *cobra.Command {
 	var lenient bool
 	var docsURL string
 	var polish bool
+	var asJSON bool
 
 	cmd := &cobra.Command{
 		Use:   "generate",
@@ -115,6 +117,7 @@ func newGenerateCmd() *cobra.Command {
 					}
 				}
 
+				polished := false
 				if polish {
 					fmt.Fprintln(os.Stderr, "Running LLM polish pass...")
 					polishResult, polishErr := llmpolish.Polish(llmpolish.PolishRequest{
@@ -126,12 +129,22 @@ func newGenerateCmd() *cobra.Command {
 					} else if polishResult.Skipped {
 						fmt.Fprintf(os.Stderr, "polish skipped: %s\n", polishResult.SkipReason)
 					} else {
+						polished = true
 						fmt.Fprintf(os.Stderr, "Polish: %d help texts improved, %d examples added, README %v\n",
 							polishResult.HelpTextsImproved, polishResult.ExamplesAdded, polishResult.READMERewritten)
 					}
 				}
 
 				fmt.Fprintf(os.Stderr, "Generated %s-cli at %s (from docs)\n", parsed.Name, absOut)
+				if asJSON {
+					json.NewEncoder(os.Stdout).Encode(map[string]interface{}{
+						"name":       parsed.Name,
+						"output_dir": absOut,
+						"spec_files": specFiles,
+						"validated":  validate,
+						"polished":   polished,
+					})
+				}
 				return nil
 			}
 
@@ -199,6 +212,7 @@ func newGenerateCmd() *cobra.Command {
 				}
 			}
 
+			polished := false
 			if polish {
 				fmt.Fprintln(os.Stderr, "Running LLM polish pass...")
 				polishResult, polishErr := llmpolish.Polish(llmpolish.PolishRequest{
@@ -210,12 +224,22 @@ func newGenerateCmd() *cobra.Command {
 				} else if polishResult.Skipped {
 					fmt.Fprintf(os.Stderr, "polish skipped: %s\n", polishResult.SkipReason)
 				} else {
+					polished = true
 					fmt.Fprintf(os.Stderr, "Polish: %d help texts improved, %d examples added, README %v\n",
 						polishResult.HelpTextsImproved, polishResult.ExamplesAdded, polishResult.READMERewritten)
 				}
 			}
 
 			fmt.Fprintf(os.Stderr, "Generated %s-cli at %s\n", apiSpec.Name, absOut)
+			if asJSON {
+				json.NewEncoder(os.Stdout).Encode(map[string]interface{}{
+					"name":       apiSpec.Name,
+					"output_dir": absOut,
+					"spec_files": specFiles,
+					"validated":  validate,
+					"polished":   polished,
+				})
+			}
 			return nil
 		},
 	}
@@ -229,6 +253,7 @@ func newGenerateCmd() *cobra.Command {
 	cmd.Flags().BoolVar(&lenient, "lenient", false, "Skip validation errors from broken $refs in OpenAPI specs")
 	cmd.Flags().StringVar(&docsURL, "docs", "", "API documentation URL to generate spec from")
 	cmd.Flags().BoolVar(&polish, "polish", false, "Run LLM polish pass on generated CLI (requires claude or codex CLI)")
+	cmd.Flags().BoolVar(&asJSON, "json", false, "Output as JSON")
 
 	return cmd
 }
@@ -353,6 +378,7 @@ func newPrintCmd() *cobra.Command {
 	var outputDir string
 	var force bool
 	var resume bool
+	var asJSON bool
 
 	cmd := &cobra.Command{
 		Use:   "print <api-name>",
@@ -380,6 +406,14 @@ func newPrintCmd() *cobra.Command {
 			}
 			fmt.Fprintf(os.Stderr, "\nStart with: /ce:work %s\n", state.PlanPath(pipeline.PhasePreflight))
 
+			if asJSON {
+				json.NewEncoder(os.Stdout).Encode(map[string]interface{}{
+					"api_name":         apiName,
+					"pipeline_dir":     state.OutputDir,
+					"phases_completed": countCompletedPhases(state),
+					"state_file":       pipeline.StatePath(apiName),
+				})
+			}
 			return nil
 		},
 	}
@@ -387,6 +421,17 @@ func newPrintCmd() *cobra.Command {
 	cmd.Flags().StringVar(&outputDir, "output", "", "Output directory (default: ./<api-name>-cli)")
 	cmd.Flags().BoolVar(&force, "force", false, "Overwrite existing pipeline")
 	cmd.Flags().BoolVar(&resume, "resume", false, "Resume from existing checkpoint")
+	cmd.Flags().BoolVar(&asJSON, "json", false, "Output as JSON")
 
 	return cmd
 }
+
+func countCompletedPhases(state *pipeline.PipelineState) int {
+	n := 0
+	for _, p := range state.Phases {
+		if p.Status == pipeline.StatusCompleted {
+			n++
+		}
+	}
+	return n
+}
diff --git a/internal/cli/vision.go b/internal/cli/vision.go
index 3a309466..2c032d6c 100644
--- a/internal/cli/vision.go
+++ b/internal/cli/vision.go
@@ -1,6 +1,7 @@
 package cli
 
 import (
+	"encoding/json"
 	"fmt"
 	"os"
 	"path/filepath"
@@ -12,6 +13,7 @@ import (
 func newVisionCmd() *cobra.Command {
 	var apiName string
 	var outputDir string
+	var asJSON bool
 
 	cmd := &cobra.Command{
 		Use:   "vision",
@@ -75,12 +77,19 @@ The vision command produces the structure; Phase 0 fills it with intelligence.`,
 
 			fmt.Fprintf(os.Stderr, "Visionary research template written to %s/visionary-research.md\n", absOut)
 			fmt.Fprintf(os.Stderr, "Run Phase 0 (SKILL.md) to fill it with real research.\n")
+			if asJSON {
+				json.NewEncoder(os.Stdout).Encode(map[string]interface{}{
+					"api_name":    apiName,
+					"output_file": filepath.Join(absOut, "visionary-research.md"),
+				})
+			}
 			return nil
 		},
 	}
 
 	cmd.Flags().StringVar(&apiName, "api", "", "API name to research")
 	cmd.Flags().StringVar(&outputDir, "output", "", "Output directory (default: <api>-cli)")
+	cmd.Flags().BoolVar(&asJSON, "json", false, "Output as JSON")
 
 	return cmd
 }

← 862dfa70 fix(generate): reject non-empty output directory without --f  ·  back to Cli Printing Press  ·  feat(cli): add --dry-run flag to generate command b494c3b8 →