SKILL.md

  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```