| name | gocli-builder |
| description | Build agent-native Go CLI tools from a battle-tested Cobra template. Use when the user asks you to build a CLI tool in Go, create a command-line interface, or scaffold a new Go CLI project. |
Go CLI Builder
Use this skill to scaffold a production-quality, agent-native Go CLI tool from
the gocli-builder template.
Template location
The template lives at {{TEMPLATE_PATH}}/template/.
When to use
- User asks to "build a CLI", "create a command-line tool", or "scaffold a Go project"
- User needs a tool that other agents or scripts will consume
- User wants a Go CLI with structured output, proper error handling, and logging
How to scaffold
1. Collect required information
Ask the user (or infer):
- Binary name (e.g.,
gh, taskctl, deployer)
- Go module path (e.g.,
github.com/org/repo)
- Purpose — what the CLI does (used to write descriptions)
2. Copy and customize the template
cp -r {{TEMPLATE_PATH}}/template/ <target-dir>
Then replace placeholders:
APPNAME → the binary name (case-sensitive, everywhere)
github.com/user/APPNAME → the real Go module path
3. Verify it builds
cd <target-dir>
go mod tidy
go build ./...
go test ./...
4. Customize the root command
Edit cmd/<APPNAME>/root.go:
- Update
Use, Short, Long with real descriptions
- The root
RunE is a placeholder — replace with real logic
- Add subcommands via
cmd.AddCommand(newSubCmd()) in newRootCmd()
5. Add real subcommands
For each subcommand, follow this pattern:
func newMyCmd() *cobra.Command {
var someFlag string
cmd := &cobra.Command{
Use: "mycmd",
Short: "Does something useful",
RunE: func(cmd *cobra.Command, args []string) error {
out := cli.Stdout(cmd, outputFormat)
log := cli.Stderr(verbose, quiet)
log.Debug("starting mycmd with flag=%s", someFlag)
result := map[string]interface{}{
"status": "done",
}
return out.Print(result)
},
}
cmd.Flags().StringVar(&someFlag, "flag", "", "Some flag")
return cmd
}
Conventions (MUST follow)
Flags
--output / -o → plain (default) or json. Already registered on root.
--verbose / -v → enables debug logging to stderr. Already registered on root.
--quiet / -q → suppresses all but error output. Already registered on root.
Output
- stdout = data (the result). Agents parse this. Use
cli.Stdout(cmd, outputFormat).Print(...).
- stderr = diagnostics (logs, progress, warnings). Use
cli.Stderr(verbose, quiet).Info(...).
- In JSON mode,
Print marshals with indentation for readability while staying parseable.
Errors
- Return
cli.Exitf(cli.CodeError, "bad input: %s", val) for user errors (exit 1)
- Return
cli.Exitf(cli.CodeFatal, "cannot connect: %s", err) for system errors (exit 2)
- Return
cli.Wrap(err, "failed to process file %s", path) to wrap an underlying error
main.go already handles cli.Error — exit code is extracted automatically
Testing
- Unit test commands with
cli.ExecuteCommand(rootCmd, args...) — runs in-process
- Snapshot test output with
cli.AssertGolden(t, "testname", []byte(stdout))
- Integration test with
cli.RunTest(t, args...) — runs the built binary
Project structure
cmd/<APPNAME>/ # entry points (one package)
internal/cli/ # cli utilities (output, errors, logging, testing)
internal/<domain>/ # domain logic packages
pkg/<lib>/ # shared libraries (if needed)
Example: adding a "list" subcommand
func newListCmd() *cobra.Command {
var filter string
cmd := &cobra.Command{
Use: "list",
Short: "List items",
RunE: func(cmd *cobra.Command, args []string) error {
out := cli.Stdout(cmd, outputFormat)
log := cli.Stderr(verbose, quiet)
items, err := fetchItems(filter)
if err != nil {
return cli.WrapFatal(err, "failed to fetch items")
}
log.Info("found %d items", len(items))
return out.PrintTable(
[]string{"ID", "NAME", "STATUS"},
toRows(items),
)
},
}
cmd.Flags().StringVar(&filter, "filter", "", "Filter items by name")
return cmd
}
Reference
Full package API:
cli.RegisterOutputFlags(cmd, &format) — adds --output to a command
cli.RegisterLogFlags(cmd, &verbose, &quiet) — adds --verbose, --quiet
cli.Stdout(cmd, format) *Output — creates a format-aware stdout writer (pass cmd for test-redirect support)
out.Print(v) — print any value (auto JSON/plain)
out.PrintTable(headers, rows) — print a table
out.IsJSON() — check if output is JSON mode
cli.Stderr(verbose, quiet) *Logger — creates a stderr logger
log.Debug/Info/Warn/Error/Logf — leveled logging
cli.Exitf(code, msg, args...) — create an exit error
cli.Wrap/WrapFatal(err, msg, args...) — wrap an error with exit code
cli.ExecuteCommand(root, args...) (stdout, stderr, err) — run a command in tests
cli.AssertGolden(t, name, data) — snapshot test against golden file
cli.RunTest(t, args...) CmdResult — run the built binary in tests
cli.Progress(w, msg, args...) — write a progress line