1---
2name: charm-fang
3description: "Wrap Cobra with styled help, error output, auto versioning, and manpage generation via fang. Use when building Go CLIs with fang, styled Cobra help, or adding lipgloss-rendered help pages to a Go CLI."
4---
5
6# charm-fang
7
8Fang is NOT a Cobra replacement - it wraps Cobra. You still write `*cobra.Command` structs exactly as you would with plain Cobra. Fang intercepts execution to add:
9
10- styled help/usage output (lipgloss-rendered)
11- styled error output with an `ERROR` header block
12- auto `--version` from build info or a string you provide
13- hidden `man` subcommand (manpage via mango/roff)
14- `completion` subcommand for shell completions
15- `SilenceUsage = true` by default (no help dump on error)
16- signal handling via `WithNotifySignal`
17
18## Install
19
20```bash
21go get charm.land/fang/v2
22```
23
24Import path (v2, note the vanity domain):
25
26```go
27import "charm.land/fang/v2"
28```
29
30## Minimal App
31
32```go
33package main
34
35import (
36 "context"
37 "os"
38
39 "charm.land/fang/v2"
40 "github.com/spf13/cobra"
41)
42
43func main() {
44 root := &cobra.Command{
45 Use: "myapp",
46 Short: "Does something useful",
47 }
48 if err := fang.Execute(context.Background(), root); err != nil {
49 os.Exit(1)
50 }
51}
52```
53
54That's the full swap from `root.Execute()` to `fang.Execute()`.
55
56## Complete CLI Skeleton
57
58```go
59package main
60
61import (
62 "context"
63 "os"
64
65 "charm.land/fang/v2"
66 "github.com/spf13/cobra"
67)
68
69func main() {
70 var name string
71 var verbose bool
72
73 root := &cobra.Command{
74 Use: "myapp [flags]",
75 Short: "One-line description",
76 Long: "Longer description shown in full help.",
77 Version: "1.0.0", // overridden by fang if you use WithVersion
78 Example: `
79 # basic usage
80 myapp --name alice
81
82 # with subcommand
83 myapp greet --name alice`,
84 RunE: func(cmd *cobra.Command, args []string) error {
85 cmd.Printf("Hello, %s\n", name)
86 return nil
87 },
88 }
89
90 // persistent flags available to all subcommands
91 root.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose output")
92 // local flags for root only
93 root.Flags().StringVar(&name, "name", "world", "name to greet")
94
95 // subcommand
96 greet := &cobra.Command{
97 Use: "greet",
98 Short: "Greet someone",
99 RunE: func(cmd *cobra.Command, args []string) error {
100 cmd.Println("greet subcommand")
101 return nil
102 },
103 }
104 root.AddCommand(greet)
105
106 if err := fang.Execute(
107 context.Background(),
108 root,
109 fang.WithVersion("1.2.3"),
110 fang.WithCommit("abc1234"),
111 fang.WithNotifySignal(os.Interrupt),
112 ); err != nil {
113 os.Exit(1)
114 }
115}
116```
117
118## Options Reference
119
120| Option | Effect |
121|--------|--------|
122| `WithVersion(v string)` | Sets version string shown by `--version` |
123| `WithCommit(sha string)` | Appends short commit SHA to version |
124| `WithoutVersion()` | Disables `-v`/`--version` entirely |
125| `WithoutCompletions()` | Removes the `completion` subcommand |
126| `WithoutManpage()` | Removes the hidden `man` subcommand |
127| `WithNotifySignal(signals...)` | Cancels context on given OS signals |
128| `WithColorSchemeFunc(fn)` | Custom theme, light/dark-adaptive |
129| `WithErrorHandler(fn)` | Custom error rendering |
130
131If no `WithVersion` is passed, fang reads `debug.ReadBuildInfo()` automatically (works when installed via `go install`).
132
133## Custom Theme
134
135```go
136import "charm.land/lipgloss/v2"
137
138fang.Execute(ctx, root, fang.WithColorSchemeFunc(func(ld lipgloss.LightDarkFunc) fang.ColorScheme {
139 return fang.ColorScheme{
140 Title: ld(lipgloss.Color("#FF6B6B"), lipgloss.Color("#4ECDC4")),
141 Flag: lipgloss.Color("#0CB37F"),
142 Command: lipgloss.Color("#A550DF"),
143 // ... other fields
144 }
145}))
146```
147
148`LightDarkFunc` lets you return different colors based on terminal background. Use `fang.DefaultColorScheme` or `fang.AnsiColorScheme` as a reference.
149
150## Differences from Plain Cobra
151
152| Cobra | Fang |
153|-------|------|
154| `root.Execute()` | `fang.Execute(ctx, root, opts...)` |
155| plain text help | lipgloss-styled help |
156| errors printed raw | styled error block |
157| manual signal handling | `WithNotifySignal` |
158| manual manpage setup | built-in hidden `man` cmd |
159| `SilenceUsage` off by default | on by default |
160| no version auto-detect | reads build info automatically |
161
162## Flag Patterns (Cobra, unchanged)
163
164```go
165// string flag with short form
166cmd.Flags().StringVarP(&val, "name", "n", "default", "description")
167
168// persistent (inherited by subcommands)
169cmd.PersistentFlags().BoolVar(&debug, "debug", false, "enable debug")
170
171// hidden flag
172cmd.Flags().String("internal", "", "internal use")
173_ = cmd.Flags().MarkHidden("internal")
174
175// required flag
176cmd.Flags().String("config", "", "config file")
177_ = cmd.MarkFlagRequired("config")
178```
179
180## Command Groups
181
182```go
183root.AddGroup(&cobra.Group{
184 ID: "core",
185 Title: "Core Commands",
186})
187sub.GroupID = "core"
188```
189
190Groups show as sections in fang's styled help output.
191
192## Context Usage
193
194Subcommands get the context fang passes (with signal cancellation if configured):
195
196```go
197RunE: func(cmd *cobra.Command, args []string) error {
198 select {
199 case <-time.After(5 * time.Second):
200 return nil
201 case <-cmd.Context().Done():
202 return cmd.Context().Err()
203 }
204},
205```
206
207## go.mod
208
209```
210require (
211 charm.land/fang/v2 v2.x.x
212 github.com/spf13/cobra v1.x.x
213)
214```