← back to Cli Printing Press
feat(generator): add color and TTY detection to generated CLIs
3cb6e869a1a2b80c0bb0295e891e8ba43f8fe1e0 · 2026-03-23 23:49:48 -0700 · Matt Van Horn
Add go-isatty dependency, colorEnabled() with NO_COLOR/TERM=dumb support,
bold/green/red/yellow ANSI helpers, bold table headers, colored doctor
status output, and --no-color persistent flag.
Files touched
M internal/generator/templates/doctor.go.tmplM internal/generator/templates/go.mod.tmplM internal/generator/templates/helpers.go.tmplM internal/generator/templates/root.go.tmpl
Diff
commit 3cb6e869a1a2b80c0bb0295e891e8ba43f8fe1e0
Author: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Date: Mon Mar 23 23:49:48 2026 -0700
feat(generator): add color and TTY detection to generated CLIs
Add go-isatty dependency, colorEnabled() with NO_COLOR/TERM=dumb support,
bold/green/red/yellow ANSI helpers, bold table headers, colored doctor
status output, and --no-color persistent flag.
---
internal/generator/templates/doctor.go.tmpl | 27 ++++++++++++++--
internal/generator/templates/go.mod.tmpl | 1 +
internal/generator/templates/helpers.go.tmpl | 48 +++++++++++++++++++++++++++-
internal/generator/templates/root.go.tmpl | 1 +
4 files changed, 73 insertions(+), 4 deletions(-)
diff --git a/internal/generator/templates/doctor.go.tmpl b/internal/generator/templates/doctor.go.tmpl
index 8f1742b4..a41e7922 100644
--- a/internal/generator/templates/doctor.go.tmpl
+++ b/internal/generator/templates/doctor.go.tmpl
@@ -3,6 +3,7 @@ package cli
import (
"fmt"
"net/http"
+ "strings"
"time"
"github.com/USER/{{.Name}}-cli/internal/config"
@@ -57,11 +58,31 @@ func newDoctorCmd(flags *rootFlags) *cobra.Command {
return flags.printJSON(cmd, report)
}
- // Human-readable output
+ // Human-readable output with color
w := cmd.OutOrStdout()
- for _, key := range []string{"config", "config_path", "base_url", "auth", "auth_source", "api", "version"} {
+ checkKeys := []struct{ key, label string }{
+ {"config", "Config"},
+ {"auth", "Auth"},
+ {"api", "API"},
+ }
+ for _, ck := range checkKeys {
+ v, ok := report[ck.key]
+ if !ok {
+ continue
+ }
+ s := fmt.Sprintf("%v", v)
+ indicator := green("OK")
+ if strings.Contains(s, "error") || strings.Contains(s, "not configured") || strings.Contains(s, "unreachable") {
+ indicator = red("FAIL")
+ } else if strings.Contains(s, "not ") {
+ indicator = yellow("WARN")
+ }
+ fmt.Fprintf(w, " %s %s: %s\n", indicator, ck.label, s)
+ }
+ // Print info keys without status indicator
+ for _, key := range []string{"config_path", "base_url", "auth_source", "version"} {
if v, ok := report[key]; ok {
- fmt.Fprintf(w, "%s=%v\n", key, v)
+ fmt.Fprintf(w, " %s: %v\n", key, v)
}
}
return nil
diff --git a/internal/generator/templates/go.mod.tmpl b/internal/generator/templates/go.mod.tmpl
index 16f02592..b78cba70 100644
--- a/internal/generator/templates/go.mod.tmpl
+++ b/internal/generator/templates/go.mod.tmpl
@@ -4,6 +4,7 @@ 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 afb1803d..66d826d1 100644
--- a/internal/generator/templates/helpers.go.tmpl
+++ b/internal/generator/templates/helpers.go.tmpl
@@ -9,10 +9,56 @@ import (
"sort"
"strings"
"text/tabwriter"
+
+ "github.com/mattn/go-isatty"
)
var As = errors.As
+// noColor is set by the --no-color flag
+var noColor bool
+
+func colorEnabled() bool {
+ if noColor {
+ return false
+ }
+ if os.Getenv("NO_COLOR") != "" {
+ return false
+ }
+ if os.Getenv("TERM") == "dumb" {
+ return false
+ }
+ return isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd())
+}
+
+func bold(s string) string {
+ if !colorEnabled() {
+ return s
+ }
+ return "\033[1m" + s + "\033[0m"
+}
+
+func green(s string) string {
+ if !colorEnabled() {
+ return s
+ }
+ return "\033[32m" + s + "\033[0m"
+}
+
+func red(s string) string {
+ if !colorEnabled() {
+ return s
+ }
+ return "\033[31m" + s + "\033[0m"
+}
+
+func yellow(s string) string {
+ if !colorEnabled() {
+ return s
+ }
+ return "\033[33m" + s + "\033[0m"
+}
+
type cliError struct {
code int
err error
@@ -251,7 +297,7 @@ func printAutoTable(w io.Writer, items []map[string]any) error {
tw := newTabWriter(w)
upperHeaders := make([]string, len(headers))
for i, h := range headers {
- upperHeaders[i] = strings.ToUpper(h)
+ upperHeaders[i] = bold(strings.ToUpper(h))
}
fmt.Fprintln(tw, strings.Join(upperHeaders, "\t"))
diff --git a/internal/generator/templates/root.go.tmpl b/internal/generator/templates/root.go.tmpl
index 55089ee7..95cecc4d 100644
--- a/internal/generator/templates/root.go.tmpl
+++ b/internal/generator/templates/root.go.tmpl
@@ -40,6 +40,7 @@ func Execute() error {
rootCmd.PersistentFlags().StringVar(&flags.configPath, "config", "", "Config file path")
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(&noColor, "no-color", false, "Disable colored output")
{{- range $name, $resource := .Resources}}
rootCmd.AddCommand(new{{camel $name}}Cmd(&flags)) {{/* FuncPrefix matches resource name for top-level */}}
← babd0c66 fix(openapi): filter global query params that appear on >80%
·
back to Cli Printing Press
·
feat(cli): accept URLs for --spec with local caching 2e70bc46 →