A reflection-based, generic, type-safe CLI parser for Go that reads struct tags and generates help text, with subcommands, grouped commands, and agent-readable CLI context.
- Features
- Install
- Quick Start (Single Command)
- Subcommands (Global + Command Flags)
- Command Groups ("gh repo" style)
- Help Generation (Human + Agent)
- Help metadata fields
- Flag Tags
- Supported Flag Types
- Optional flags via pointers
- Port validation
- Positional Argument Schemas
- Aliases
- Partial Parsing (Known Flags)
- Registry & Introspection
- Remaining Args (
--) - Errors
- API Reference
- Development
- License
- Type-safe flag parsing with Go generics.
- Global + subcommand flag separation (including mixed ordering).
- Positional argument schemas with validation and auto-population.
- Automatic human help plus agent-readable CLI context.
- Command groups ("docker run"-style) and aliases.
- Flags can appear anywhere; supports
--passthrough. - Optional flags via pointer types; defaults via struct tags.
- Built-in
Porttype with optional range validation. - Partial parsing for "known flags" and consume-only workflows.
- Registry-based command introspection for advanced dispatching.
go get github.com/shayne/yargs@latestUse ParseFlags when you have no subcommands.
package main
import (
"fmt""log""os""github.com/shayne/yargs"
)
typeFlagsstruct {
Verbosebool`flag:"verbose" short:"v" help:"Enable verbose output"`Outputstring`flag:"output" short:"o" help:"Output file"`
}
funcmain() {
result, err:= yargs.ParseFlags[Flags](os.Args[1:])
iferr!=nil {
log.Fatal(err)
}
fmt.Printf("verbose=%v output=%s args=%v\n", result.Flags.Verbose, result.Flags.Output, result.Args)
}Use ParseWithCommand or ParseAndHandleHelp when you have subcommands.
package main
import (
"context""errors""fmt""os""github.com/shayne/yargs"
)
typeGlobalFlagsstruct {
Verbosebool`flag:"verbose" short:"v" help:"Enable verbose output"`
}
typeStatusFlagsstruct {
Shortbool`flag:"short" short:"s" help:"Show short status"`
}
typeCommitFlagsstruct {
Messagestring`flag:"message" short:"m" help:"Commit message"`Amendbool`flag:"amend" help:"Amend the last commit"`
}
typeCloneFlagsstruct {
Depthint`flag:"depth" help:"Create a shallow clone with history truncated"`Branchstring`flag:"branch" short:"b" help:"Checkout a specific branch"`
}
typeCloneArgsstruct {
Repostring`pos:"0" help:"Repository URL"`Dirstring`pos:"1?" help:"Target directory"`
}
varhelpConfig= yargs.HelpConfig{
Command: yargs.CommandInfo{
Name: "git",
Description: "The stupid content tracker",
},
SubCommands: map[string]yargs.SubCommandInfo{
"status": {Name: "status", Description: "Show working tree status"},
"commit": {Name: "commit", Description: "Record changes to the repository"},
"clone": {Name: "clone", Description: "Clone a repository into a new directory"},
},
}
funchandleStatus(args []string) error {
returnrunWithParse[StatusFlags, struct{}](args, func(result*yargs.TypedParseResult[GlobalFlags, StatusFlags, struct{}]) error {
fmt.Printf("short=%v verbose=%v\n", result.SubCommandFlags.Short, result.GlobalFlags.Verbose)
returnnil
})
}
funchandleCommit(args []string) error {
returnrunWithParse[CommitFlags, struct{}](args, func(result*yargs.TypedParseResult[GlobalFlags, CommitFlags, struct{}]) error {
fmt.Printf("message=%q amend=%v\n", result.SubCommandFlags.Message, result.SubCommandFlags.Amend)
returnnil
})
}
funchandleClone(args []string) error {
returnrunWithParse[CloneFlags, CloneArgs](args, func(result*yargs.TypedParseResult[GlobalFlags, CloneFlags, CloneArgs]) error {
fmt.Printf("repo=%s dir=%s depth=%d branch=%s\n",
result.Args.Repo,
result.Args.Dir,
result.SubCommandFlags.Depth,
result.SubCommandFlags.Branch,
)
returnnil
})
}
funcrunWithParse[Sany, Aany](args []string, fnfunc(*yargs.TypedParseResult[GlobalFlags, S, A]) error) error {
result, err:=yargs.ParseAndHandleHelp[GlobalFlags, S, A](args, helpConfig)
iferrors.Is(err, yargs.ErrShown) {
returnnil
}
iferr!=nil {
returnerr
}
returnfn(result)
}
funcmain() {
handlers:=map[string]yargs.SubcommandHandler{
"status": func(_ context.Context, args []string) error { returnhandleStatus(args) },
"commit": func(_ context.Context, args []string) error { returnhandleCommit(args) },
"clone": func(_ context.Context, args []string) error { returnhandleClone(args) },
}
iferr:=yargs.RunSubcommands(context.Background(), os.Args[1:], helpConfig, GlobalFlags{}, handlers); err!=nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}Use RunSubcommandsWithGroups and Group/GroupInfo for grouped commands.
config:= yargs.HelpConfig{
Command: yargs.CommandInfo{Name: "gh", Description: "GitHub CLI"},
Groups: map[string]yargs.GroupInfo{
"repo": {
Name: "repo",
Description: "Manage repositories",
Commands: map[string]yargs.SubCommandInfo{
"create": {Name: "create", Description: "Create a repository"},
"view": {Name: "view", Description: "View a repository"},
},
},
"issue": {
Name: "issue",
Description: "Manage issues",
Commands: map[string]yargs.SubCommandInfo{
"list": {Name: "list", Description: "List issues"},
"create": {Name: "create", Description: "Create an issue"},
},
},
},
}
groups:=map[string]yargs.Group{
"repo": {
Description: "Manage repositories",
Commands: map[string]yargs.SubcommandHandler{
"create": handleRepoCreate,
"view": handleRepoView,
},
},
"issue": {
Description: "Manage issues",
Commands: map[string]yargs.SubcommandHandler{
"list": handleIssueList,
"create": handleIssueCreate,
},
},
}
_=yargs.RunSubcommandsWithGroups(context.Background(), os.Args[1:], config, GlobalFlags{}, nil, groups)Yargs can emit human help for people and agent-readable context for automation agents from the same metadata.
- Global:
GenerateGlobalHelp - Group:
GenerateGroupHelp - Group command:
GenerateGroupCommandHelp - Subcommand:
GenerateSubCommandHelp - Dispatcher:
RunSubcommandsandRunSubcommandsWithGroups
- Global:
GenerateAgentHelp - Command, group, or group command:
GenerateAgentHelpForCommand - Registry-aware command help:
GenerateAgentHelpFromRegistry - Flag:
--help-agent
Agent help is structured Markdown intended to be loaded into an automation agent's context after it discovers the CLI. It includes purpose, operating rules, discovery commands, usage, flags, positional arguments, examples, and safety notes. Hidden commands and hidden groups are omitted.
config:= yargs.HelpConfig{
Command: yargs.CommandInfo{
Name: "app",
Description: "Manage app services",
Agent: yargs.AgentInfo{
Summary: "Use app to inspect and operate services.",
Rules: []string{
"Prefer --json when parsing command output.",
},
Safety: []string{
"Do not delete services unless the user explicitly asks.",
},
},
},
}
fmt.Print(yargs.GenerateAgentHelp(config, struct{}{}))ParseWithCommandAndHelp and ParseAndHandleHelp will detect help, -h,
--help, and --help-agent and return the right error sentinel.
ParseAndHandleHelp prints help automatically and returns ErrShown.
The help subcommand is supported as app help or app help <command>.
You control help output with these fields:
CommandInfo:Name,Description,Examples,AgentSubCommandInfo:Name,Description,Usage,Examples,Aliases,Hidden,AgentGroupInfo:Name,Description,Commands,Hidden,Agent
AgentInfo can provide Summary, Rules, Discovery, and Safety. Rules and
safety notes inherit from the CLI to groups and commands.
typeFlagsstruct {
Verbosebool`flag:"verbose" short:"v" help:"Enable verbose output"`Outputstring`flag:"output" default:"out.txt" help:"Output path"`Rateint`flag:"rate" help:"Requests per second"`
}Supported struct tags:
flag:"name"- long flag name. Defaults to lowercased field name.short:"x"- single-character alias.help:"text"- help text for auto-generation.default:"value"- default if flag not provided.port:"min-max"- range validation forPortfields.pos:"N"- positional argument schema (see below).
string,boolint,int8,int16,int32,int64uint,uint8,uint16,uint32,uint64float32,float64time.Durationurl.URL,*url.URLyargs.Port(uint16 alias with optional range validation)- Pointers to any of the above (for optional flags)
[]string(comma-separated or repeated flags)
typeFlagsstruct {
Token*string`flag:"token" help:"Optional auth token"`
}
// Token is nil if not provided.typeFlagsstruct {
HTTPPort yargs.Port`flag:"http" port:"1-65535" help:"HTTP port"`AdminPort*yargs.Port`flag:"admin" port:"8000-9000" help:"Admin port"`
}Define positional arguments using pos:"N" tags. Yargs will validate counts
and populate the struct.
typeArgsstruct {
Servicestring`pos:"0" help:"Service name"`Imagestring`pos:"1" help:"Image or payload"`Tags []string`pos:"2*" help:"Optional tags"`
}Positional tag variants:
pos:"0"required argument at index 0pos:"0?"optional argument at index 0pos:"0*"variadic (0 or more) at index 0pos:"0+"variadic (1 or more) at index 0
Use Aliases on SubCommandInfo to register alternative command names.
ApplyAliases will rewrite the args to canonical names and is used by the
built-in dispatchers.
config:= yargs.HelpConfig{
SubCommands: map[string]yargs.SubCommandInfo{
"status": {Name: "status", Aliases: []string{"st", "stat"}},
},
}
args:=yargs.ApplyAliases(os.Args[1:], config)Aliases also work for grouped commands (group-local aliases).
When you only want a subset of flags and want to preserve unknown args:
typeFlagsstruct {
Hoststring`flag:"host"`Tags []string`flag:"tags" short:"t"`
}
res, err:=yargs.ParseKnownFlags[Flags](os.Args[1:], yargs.KnownFlagsOptions{
SplitCommaSlices: true,
})
// res.Flags contains only known flags; res.RemainingArgs preserves the rest.Or use the lower-level ConsumeFlagsBySpec:
specs:=map[string]yargs.ConsumeSpec{
"host": {Kind: reflect.String},
"tags": {Kind: reflect.Slice, SplitComma: true},
}
remaining, values:=yargs.ConsumeFlagsBySpec(os.Args[1:], specs)Use Registry for schema-aware command resolution, positional metadata, and
registry-backed agent help.
typeRunFlagsstruct {
Detachbool`flag:"detach" help:"Run in background"`
}
typeRunArgsstruct {
Servicestring`pos:"0" help:"Service name"`
}
reg:= yargs.Registry{
Command: yargs.CommandInfo{Name: "app"},
SubCommands: map[string]yargs.CommandSpec{
"run": {
Info: yargs.SubCommandInfo{
Name: "run",
Description: "Run a service",
Agent: yargs.AgentInfo{
Summary: "Start a named service.",
},
},
FlagsSchema: RunFlags{},
ArgsSchema: RunArgs{},
},
},
}
resolved, ok, err:=yargs.ResolveCommandWithRegistry(os.Args[1:], reg)
ifok {
ifspec, ok:=resolved.PArg(0); ok {
fmt.Printf("arg0 name=%s required=%v\n", spec.Name, spec.Required)
}
}
fmt.Print(yargs.GenerateAgentHelpFromRegistry(reg, []string{"run"}, struct{}{}))Everything after -- is preserved in RemainingArgs.
result, _:= yargs.ParseFlags[Flags]([]string{"-v", "arg1", "--", "--raw", "x"})
// result.Args == []string{"arg1"}// result.RemainingArgs == []string{"--raw", "x"}Yargs exposes structured errors for control flow and diagnostics:
ErrHelp,ErrSubCommandHelp,ErrHelpAgentfor help requests.ErrShownfromParseAndHandleHelpwhen it already printed output.InvalidFlagErrorfor unknown flags.InvalidArgsErrorfor bad positional args.FlagValueErrorfor type conversion or validation issues.
ParseAndHandleHelp will print a clean message for users, and if the global
flags struct has a Verbose bool field set to true, it will also print the full
error chain for FlagValueError.
For full, generated API docs, see:
https://pkg.go.dev/github.com/shayne/yargs
This repo uses mise for tool and task management.
mise install
mise run testmise run fmtmise run tidymise run testmise run check
MIT. See LICENSE.