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"
)
type Flags struct {
Verbose bool `flag:"verbose" short:"v" help:"Enable verbose output"`
Output string `flag:"output" short:"o" help:"Output file"`
}
func main() {
result, err := yargs.ParseFlags[Flags](os.Args[1:])
if err != 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"
)
type GlobalFlags struct {
Verbose bool `flag:"verbose" short:"v" help:"Enable verbose output"`
}
type StatusFlags struct {
Short bool `flag:"short" short:"s" help:"Show short status"`
}
type CommitFlags struct {
Message string `flag:"message" short:"m" help:"Commit message"`
Amend bool `flag:"amend" help:"Amend the last commit"`
}
type CloneFlags struct {
Depth int `flag:"depth" help:"Create a shallow clone with history truncated"`
Branch string `flag:"branch" short:"b" help:"Checkout a specific branch"`
}
type CloneArgs struct {
Repo string `pos:"0" help:"Repository URL"`
Dir string `pos:"1?" help:"Target directory"`
}
var helpConfig = 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"},
},
}
func handleStatus(args []string) error {
return runWithParse[StatusFlags, struct{}](args, func(result *yargs.TypedParseResult[GlobalFlags, StatusFlags, struct{}]) error {
fmt.Printf("short=%v verbose=%v\n", result.SubCommandFlags.Short, result.GlobalFlags.Verbose)
return nil
})
}
func handleCommit(args []string) error {
return runWithParse[CommitFlags, struct{}](args, func(result *yargs.TypedParseResult[GlobalFlags, CommitFlags, struct{}]) error {
fmt.Printf("message=%q amend=%v\n", result.SubCommandFlags.Message, result.SubCommandFlags.Amend)
return nil
})
}
func handleClone(args []string) error {
return runWithParse[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,
)
return nil
})
}
func runWithParse[S any, A any](args []string, fn func(*yargs.TypedParseResult[GlobalFlags, S, A]) error) error {
result, err := yargs.ParseAndHandleHelp[GlobalFlags, S, A](args, helpConfig)
if errors.Is(err, yargs.ErrShown) {
return nil
}
if err != nil {
return err
}
return fn(result)
}
func main() {
handlers := map[string]yargs.SubcommandHandler{
"status": func(_ context.Context, args []string) error { return handleStatus(args) },
"commit": func(_ context.Context, args []string) error { return handleCommit(args) },
"clone": func(_ context.Context, args []string) error { return handleClone(args) },
}
if err := 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.
type Flags struct {
Verbose bool `flag:"verbose" short:"v" help:"Enable verbose output"`
Output string `flag:"output" default:"out.txt" help:"Output path"`
Rate int `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)
type Flags struct {
Token *string `flag:"token" help:"Optional auth token"`
}
// Token is nil if not provided.type Flags struct {
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.
type Args struct {
Service string `pos:"0" help:"Service name"`
Image string `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:
type Flags struct {
Host string `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.
type RunFlags struct {
Detach bool `flag:"detach" help:"Run in background"`
}
type RunArgs struct {
Service string `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)
if ok {
if spec, 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.