4.5 KiB
| title | description |
|---|---|
| Command Builders | Creating commands, flag helpers, args validation, and the config struct pattern. |
Command Builders
The framework provides three command constructors and a full set of flag helpers. All wrap cobra but remove the need to import it directly.
Command Types
NewCommand -- Standard command (returns error)
The most common form. The handler returns an error which Main() handles:
cmd := cli.NewCommand("build", "Build the project", "", func(cmd *cli.Command, args []string) error {
if err := compile(); err != nil {
return cli.WrapVerb(err, "compile", "project")
}
cli.Success("Build complete")
return nil
})
The third parameter is the long description (shown in --help). Pass "" to omit it.
NewGroup -- Parent command (subcommands only)
Creates a command with no handler, used to group subcommands:
scoreCmd := cli.NewGroup("score", "Scoring commands", "")
scoreCmd.AddCommand(grammarCmd, attentionCmd, tierCmd)
root.AddCommand(scoreCmd)
NewRun -- Simple command (no error return)
For commands that cannot fail:
cmd := cli.NewRun("version", "Show version", "", func(cmd *cli.Command, args []string) {
cli.Println("v1.0.0")
})
Re-exports
The framework re-exports cobra types so you never need to import cobra directly:
cli.Command // = cobra.Command
cli.PositionalArgs // = cobra.PositionalArgs
Flag Helpers
All flag helpers follow the same signature: (cmd, ptr, name, short, default, usage). Pass "" for the short name to omit the short flag.
var cfg struct {
Model string
Verbose bool
Count int
Score float64
Seed int64
Timeout time.Duration
Tags []string
}
cli.StringFlag(cmd, &cfg.Model, "model", "m", "", "Model path")
cli.BoolFlag(cmd, &cfg.Verbose, "verbose", "v", false, "Verbose output")
cli.IntFlag(cmd, &cfg.Count, "count", "n", 10, "Item count")
cli.Float64Flag(cmd, &cfg.Score, "score", "s", 0.0, "Min score")
cli.Int64Flag(cmd, &cfg.Seed, "seed", "", 0, "Random seed")
cli.DurationFlag(cmd, &cfg.Timeout, "timeout", "t", 30*time.Second, "Timeout")
cli.StringSliceFlag(cmd, &cfg.Tags, "tag", "", nil, "Tags")
Persistent Flags
Persistent flags are inherited by all subcommands:
cli.PersistentStringFlag(parentCmd, &dbPath, "db", "d", "", "Database path")
cli.PersistentBoolFlag(parentCmd, &debug, "debug", "", false, "Debug mode")
Args Validation
Constrain the number of positional arguments:
cmd := cli.NewCommand("deploy", "Deploy to env", "", deployFn)
cli.WithArgs(cmd, cli.ExactArgs(1)) // Exactly 1 arg
cli.WithArgs(cmd, cli.MinimumNArgs(1)) // At least 1
cli.WithArgs(cmd, cli.MaximumNArgs(3)) // At most 3
cli.WithArgs(cmd, cli.RangeArgs(1, 3)) // Between 1 and 3
cli.WithArgs(cmd, cli.NoArgs()) // No args allowed
cli.WithArgs(cmd, cli.ArbitraryArgs()) // Any number of args
Command Configuration
Add examples to help text:
cli.WithExample(cmd, ` core build --targets linux/amd64
core build --ci`)
Pattern: Config Struct + Flags
The idiomatic pattern for commands with many flags is to define a config struct, bind flags to its fields, then pass the struct to the business logic:
type DistillOpts struct {
Model string
Probes string
Runs int
DryRun bool
}
func addDistillCommand(parent *cli.Command) {
var cfg DistillOpts
cmd := cli.NewCommand("distill", "Run distillation", "", func(cmd *cli.Command, args []string) error {
return RunDistill(cfg)
})
cli.StringFlag(cmd, &cfg.Model, "model", "m", "", "Model config path")
cli.StringFlag(cmd, &cfg.Probes, "probes", "p", "", "Probe set name")
cli.IntFlag(cmd, &cfg.Runs, "runs", "r", 3, "Runs per probe")
cli.BoolFlag(cmd, &cfg.DryRun, "dry-run", "", false, "Preview without executing")
parent.AddCommand(cmd)
}
Registration Function Pattern
Commands are organised in packages under cmd/. Each package exports an Add*Commands function:
// cmd/score/commands.go
package score
import "forge.lthn.ai/core/cli/pkg/cli"
func AddScoreCommands(root *cli.Command) {
scoreCmd := cli.NewGroup("score", "Scoring commands", "")
grammarCmd := cli.NewCommand("grammar", "Grammar analysis", "", runGrammar)
cli.StringFlag(grammarCmd, &inputPath, "input", "i", "", "Input file")
scoreCmd.AddCommand(grammarCmd)
root.AddCommand(scoreCmd)
}
Then in main.go:
cli.Main(
cli.WithCommands("score", score.AddScoreCommands),
)