Output Formatting
Customize CLI output with default formats and custom formatters
Default Output Formats
The SDK provides three built-in output formats controlled by the --output flag:
| Format | Description | Use Case |
|---|---|---|
json | Pretty-printed JSON | Default, machine-readable |
yaml | YAML format | Configuration output |
text | Plain text | Simple string results |
JSON Output (Default)
./binary greet --set name=Alice{
"greeting": "Hello, Alice!",
"timestamp": 1700000000
}YAML Output
./binary greet --set name=Alice --output yamlgreeting: Hello, Alice!
timestamp: 1700000000Text Output
Best for simple string results:
./binary greet --set name=Alice --output textHello, Alice!Custom Formatters
Use WithCLIFormatter() to customize output for specific reasoners.
Basic Formatter
a.RegisterReasoner("greet", greetHandler,
agent.WithCLI(),
agent.WithCLIFormatter(func(ctx context.Context, result any, err error) {
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
return
}
fmt.Println(result)
}),
)Formatter with Type Assertion
a.RegisterReasoner("analyze", analyzeHandler,
agent.WithCLI(),
agent.WithCLIFormatter(func(ctx context.Context, result any, err error) {
if err != nil {
fmt.Fprintf(os.Stderr, "❌ Analysis failed: %v\n", err)
os.Exit(1)
return
}
data, ok := result.(map[string]any)
if !ok {
fmt.Fprintf(os.Stderr, "Unexpected result type\n")
os.Exit(1)
return
}
fmt.Println("📊 Analysis Results")
fmt.Println("──────────────────")
fmt.Printf("Sentiment: %s\n", data["sentiment"])
fmt.Printf("Confidence: %.2f%%\n", data["confidence"].(float64)*100)
fmt.Printf("Key topics: %v\n", data["topics"])
}),
)Output:
📊 Analysis Results
──────────────────
Sentiment: positive
Confidence: 87.50%
Key topics: [technology innovation growth]Color Output
Use color for better readability (respects NO_COLOR environment variable):
import "github.com/fatih/color" // Popular color library
a.RegisterReasoner("status", statusHandler,
agent.WithCLI(),
agent.WithCLIFormatter(func(ctx context.Context, result any, err error) {
if err != nil {
color.Red("✗ Error: %v", err)
os.Exit(1)
return
}
data := result.(map[string]any)
status := data["status"].(string)
switch status {
case "success":
color.Green("✓ %s", data["message"])
case "warning":
color.Yellow("⚠ %s", data["message"])
case "error":
color.Red("✗ %s", data["message"])
}
}),
)Respecting CLI Config
Access CLI config to respect color settings:
a.RegisterReasoner("greet", greetHandler,
agent.WithCLI(),
agent.WithCLIFormatter(func(ctx context.Context, result any, err error) {
// Colors automatically disabled if DisableColors: true
// or if NO_COLOR env var is set
if err != nil {
color.Red("Error: %v", err)
os.Exit(1)
return
}
color.Green("Success: %v", result)
}),
)Structured Table Output
Format results as tables for better readability:
import "github.com/olekukonko/tablewriter"
a.RegisterReasoner("list", listHandler,
agent.WithCLI(),
agent.WithCLIFormatter(func(ctx context.Context, result any, err error) {
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
return
}
items := result.([]map[string]any)
table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"ID", "Name", "Status"})
for _, item := range items {
table.Append([]string{
fmt.Sprintf("%v", item["id"]),
fmt.Sprintf("%v", item["name"]),
fmt.Sprintf("%v", item["status"]),
})
}
table.Render()
}),
)Output:
+----+----------+---------+
| ID | NAME | STATUS |
+----+----------+---------+
| 1 | Alice | active |
| 2 | Bob | pending |
| 3 | Charlie | active |
+----+----------+---------+Progress Indicators
Show progress for long-running operations:
import "github.com/schollz/progressbar/v3"
a.RegisterReasoner("process", func(ctx context.Context, input map[string]any) (any, error) {
items := input["items"].([]any)
if agent.IsCLIMode(ctx) {
bar := progressbar.Default(int64(len(items)))
for _, item := range items {
// Process item
processItem(item)
bar.Add(1)
}
} else {
// Server mode: no progress bar
for _, item := range items {
processItem(item)
}
}
return map[string]any{"processed": len(items)}, nil
}, agent.WithCLI())Error Formatting
Simple Error Messages
agent.WithCLIFormatter(func(ctx context.Context, result any, err error) {
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
return
}
fmt.Println(result)
})Detailed Error Messages
agent.WithCLIFormatter(func(ctx context.Context, result any, err error) {
if err != nil {
fmt.Fprintf(os.Stderr, "❌ Operation failed\n\n")
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
// Additional context
if strings.Contains(err.Error(), "API key") {
fmt.Fprintf(os.Stderr, "\nHint: Set OPENAI_API_KEY environment variable\n")
}
os.Exit(1)
return
}
// Success output
fmt.Printf("✓ %v\n", result)
})Exit Codes
Use meaningful exit codes:
agent.WithCLIFormatter(func(ctx context.Context, result any, err error) {
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
// Different exit codes for different errors
if errors.Is(err, ErrNotFound) {
os.Exit(2) // Not found
} else if errors.Is(err, ErrPermission) {
os.Exit(3) // Permission denied
} else {
os.Exit(1) // General error
}
return
}
// Success
fmt.Println(result)
os.Exit(0)
})Common exit codes:
0- Success1- General error2- Misuse / invalid input3- Permission error4- Not found
Streaming Output
For streaming results, write directly to stdout:
a.RegisterReasoner("stream", func(ctx context.Context, input map[string]any) (any, error) {
if agent.IsCLIMode(ctx) {
// Stream output directly
for i := 0; i < 10; i++ {
fmt.Printf("Processing chunk %d...\n", i)
time.Sleep(100 * time.Millisecond)
}
return "Streaming complete", nil
}
// Server mode: return structured result
return map[string]any{"chunks": 10}, nil
}, agent.WithCLI())JSON Lines Output
For processing pipelines:
agent.WithCLIFormatter(func(ctx context.Context, result any, err error) {
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
return
}
items := result.([]map[string]any)
// Output as JSON Lines (one JSON object per line)
for _, item := range items {
jsonBytes, _ := json.Marshal(item)
fmt.Println(string(jsonBytes))
}
})Usage in pipeline:
./binary list | jq '.name' | sortConditional Formatting
Different formats for different contexts:
agent.WithCLIFormatter(func(ctx context.Context, result any, err error) {
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
return
}
// Check if output is a TTY (interactive terminal)
if isatty.IsTerminal(os.Stdout.Fd()) {
// Interactive: use colors and formatting
color.Green("✓ Success")
fmt.Printf("Result: %v\n", result)
} else {
// Piped: machine-readable output
jsonBytes, _ := json.Marshal(result)
fmt.Println(string(jsonBytes))
}
})Best Practices
Human vs Machine Output
// Good: Detect context
if agent.IsCLIMode(ctx) {
// Human-friendly
return "Operation successful!", nil
}
// Machine-readable
return map[string]any{"status": "success"}, nilConsistent Error Format
// Centralized error formatter
func formatError(err error) {
fmt.Fprintf(os.Stderr, "❌ Error: %v\n", err)
// Add hints for common errors
if strings.Contains(err.Error(), "connection") {
fmt.Fprintf(os.Stderr, "Hint: Check network connectivity\n")
}
os.Exit(1)
}
// Use in all formatters
agent.WithCLIFormatter(func(ctx context.Context, result any, err error) {
if err != nil {
formatError(err)
return
}
// ...
})Respect Output Flag
When --output flag is used, respect it even with custom formatters:
// SDK handles this automatically
// Default formatters respect --output flag
// Custom formatters bypass --output flag