{"id":47890410,"url":"https://github.com/GiGurra/boa","last_synced_at":"2026-04-12T06:01:12.047Z","repository":{"id":200063000,"uuid":"705048394","full_name":"GiGurra/boa","owner":"GiGurra","description":"Opinionated declarative CLI args, ENV vars and config files","archived":false,"fork":false,"pushed_at":"2026-04-11T19:24:03.000Z","size":589,"stargazers_count":27,"open_issues_count":1,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-04-11T19:28:04.146Z","etag":null,"topics":["cli","command","command-line","go","golang"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/GiGurra.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2023-10-14T22:12:44.000Z","updated_at":"2026-04-11T19:23:18.000Z","dependencies_parsed_at":"2026-01-18T04:01:39.950Z","dependency_job_id":null,"html_url":"https://github.com/GiGurra/boa","commit_stats":null,"previous_names":["gigurra/boa"],"tags_count":196,"template":false,"template_full_name":null,"purl":"pkg:github/GiGurra/boa","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GiGurra%2Fboa","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GiGurra%2Fboa/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GiGurra%2Fboa/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GiGurra%2Fboa/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/GiGurra","download_url":"https://codeload.github.com/GiGurra/boa/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GiGurra%2Fboa/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31705574,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-12T05:11:36.334Z","status":"ssl_error","status_checked_at":"2026-04-12T05:11:27.332Z","response_time":58,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cli","command","command-line","go","golang"],"created_at":"2026-04-04T03:00:37.431Z","updated_at":"2026-04-12T06:01:12.042Z","avatar_url":"https://github.com/GiGurra.png","language":"Go","readme":"# BOA\n\n[![CI Status](https://github.com/GiGurra/boa/actions/workflows/ci.yml/badge.svg)](https://github.com/GiGurra/boa/actions/workflows/ci.yml)\n[![Go Report Card](https://goreportcard.com/badge/github.com/GiGurra/boa)](https://goreportcard.com/report/github.com/GiGurra/boa)\n\nLike if [kong](https://github.com/alecthomas/kong) and [urfave/cli](https://github.com/urfave/cli) had a baby and made it [cobra](https://github.com/spf13/cobra) compatible.\n\nSelf-documenting CLIs from Go structs. Define your parameters once and get flags, env vars, validation, config file loading, and help text — all generated automatically. The result is a CLI that's easy to write, easy for humans to use, and easy for LLMs to invoke — because the full parameter schema is right there in `--help`.\n\nBuilt on top of [cobra](https://github.com/spf13/cobra), not replacing it. Full cobra interop when you need it.\n\n**[Full Documentation](https://gigurra.github.io/boa/)**\n\n## Quick Start\n\n```bash\ngo get github.com/GiGurra/boa@latest\n```\n\n```go\ntype Params struct {\n    Name string `descr:\"your name\"`\n    Port int    `descr:\"port number\" default:\"8080\" optional:\"true\"`\n}\n\nfunc main() {\n    boa.CmdT[Params]{\n        Use:   \"my-app\",\n        Short: \"a simple CLI tool\",\n        RunFunc: func(params *Params, cmd *cobra.Command, args []string) {\n            fmt.Printf(\"Hello %s on port %d\\n\", params.Name, params.Port)\n        },\n    }.Run()\n}\n```\n\nThis is what you get — flag names, short flags, defaults, required/optional, descriptions, and usage line all generated from the struct:\n\n```\n$ my-app --help\na simple CLI tool\n\nUsage:\n  my-app [flags]\n\nFlags:\n  -h, --help          help for my-app\n  -n, --name string   your name (required)\n  -p, --port int      port number (default 8080)\n```\n\nAnd this is how you interact with it:\n\n```\n$ my-app --name Alice\nHello Alice on port 8080\n\n$ my-app --name Bob --port 3000\nHello Bob on port 3000\n\n$ my-app\nUsage:\n  my-app [flags]\n\nFlags:\n  -h, --help          help for my-app\n  -n, --name string   your name (required)\n  -p, --port int      port number (default 8080)\n\nError: required flag \"name\" not set\n```\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eParameter Types\u003c/b\u003e\u003c/summary\u003e\n\nAll standard Go types work out of the box:\n\n```go\ntype Params struct {\n    Host    string            `descr:\"server host\"`                    // required by default\n    Port    int               `descr:\"port\" default:\"8080\"`            // with default\n    Name    *string           `descr:\"user name\"`                      // pointer = optional, nil = not set\n    Tags    []string          `descr:\"tags\" default:\"[a,b,c]\"`         // --tags a,b,c\n    Labels  map[string]string `descr:\"labels\"`                         // --labels env=prod,team=backend\n    Input   string            `positional:\"true\"`                      // positional arg\n    Timeout time.Duration     `descr:\"timeout\" default:\"30s\"`          // durations, IPs, URLs, etc.\n    Matrix  [][]int           `descr:\"matrix\" optional:\"true\"`         // complex types use JSON: '[[1,2],[3,4]]'\n}\n```\n\nPointer fields are optional by default — `nil` means \"not set\", so you can distinguish between \"user passed zero\" and \"user didn't pass anything\":\n\n```go\ntype Params struct {\n    Retries *int `descr:\"retry count\"` // nil if not provided, *0 if --retries 0\n}\n\nboa.CmdT[Params]{\n    Use: \"app\",\n    RunFunc: func(p *Params, cmd *cobra.Command, args []string) {\n        if p.Retries != nil {\n            fmt.Printf(\"Retrying %d times\\n\", *p.Retries)\n        } else {\n            fmt.Println(\"Using default retry strategy\")\n        }\n    },\n}.Run()\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eSubcommands\u003c/b\u003e\u003c/summary\u003e\n\n```go\ntype ServeParams struct {\n    Host string `descr:\"bind address\" default:\"0.0.0.0\"`\n    Port int    `descr:\"port\" default:\"8080\"`\n}\n\ntype DeployParams struct {\n    Target string `descr:\"deploy target\" alts:\"staging,production\" strict:\"true\"`\n    DryRun bool   `descr:\"dry run mode\" optional:\"true\"`\n}\n\nfunc main() {\n    boa.CmdT[boa.NoParams]{\n        Use:   \"my-app\",\n        Short: \"a multi-command CLI\",\n        SubCmds: boa.SubCmds(\n            boa.CmdT[ServeParams]{\n                Use: \"serve\", Short: \"start the server\",\n                RunFunc: func(p *ServeParams, cmd *cobra.Command, args []string) {\n                    fmt.Printf(\"Serving on %s:%d\\n\", p.Host, p.Port)\n                },\n            },\n            boa.CmdT[DeployParams]{\n                Use: \"deploy\", Short: \"deploy the app\",\n                RunFunc: func(p *DeployParams, cmd *cobra.Command, args []string) {\n                    fmt.Printf(\"Deploying to %s (dry-run: %v)\\n\", p.Target, p.DryRun)\n                },\n            },\n        ),\n    }.Run()\n}\n```\n\n```\n$ my-app --help\na multi-command CLI\n\nUsage:\n  my-app [command]\n\nAvailable Commands:\n  serve       start the server\n  deploy      deploy the app\n\n$ my-app deploy --target staging --dry-run\nDeploying to staging (dry-run: true)\n\n$ my-app bogus\nError: unknown command \"bogus\" for \"my-app\"\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eConfig Files\u003c/b\u003e\u003c/summary\u003e\n\nTag a field with `configfile` and boa loads it automatically. CLI and env vars always win:\n\n```go\ntype Params struct {\n    ConfigFile string     `configfile:\"true\" optional:\"true\" default:\"config.json\"`\n    Host       string     `descr:\"server host\"`\n    Port       int        `descr:\"port\" default:\"8080\"`\n    Internal   [][]string `boa:\"configonly\"` // loaded from config only, no CLI flag\n}\n```\n\n```bash\n$ cat config.json\n{\"Host\": \"prod.example.com\", \"Port\": 443, \"Internal\": [[\"a\",\"b\"],[\"c\",\"d\"]]}\n\n$ my-app                              # uses config.json values\n$ my-app --host override.local        # CLI wins over config file\n$ my-app --config-file staging.json   # different config file\n$ HOST=ci.local my-app                # env var wins over config file\n```\n\nNested structs can have their own config files. Priority: CLI \u003e env \u003e root config \u003e substruct config \u003e defaults:\n\n```go\ntype DBConfig struct {\n    ConfigFile string `configfile:\"true\" optional:\"true\"`\n    Host       string `default:\"localhost\"`\n    Port       int    `default:\"5432\"`\n}\n\ntype Params struct {\n    ConfigFile string   `configfile:\"true\" optional:\"true\" default:\"app.json\"`\n    DB         DBConfig\n}\n```\n\nJSON is built in. Register other formats with one line:\n\n```go\nboa.RegisterConfigFormat(\".yaml\", yaml.Unmarshal)\nboa.RegisterConfigFormat(\".toml\", toml.Unmarshal)\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eStruct Composition\u003c/b\u003e\u003c/summary\u003e\n\nNamed fields auto-prefix their children. Embedded fields stay flat:\n\n```go\ntype DBConfig struct {\n    Host string `default:\"localhost\"`\n    Port int    `default:\"5432\"`\n}\n\ntype CommonFlags struct {\n    Verbose bool `optional:\"true\"`\n}\n\ntype Params struct {\n    CommonFlags           // embedded: --verbose (no prefix)\n    Primary DBConfig      // named: --primary-host, --primary-port\n    Replica DBConfig      // named: --replica-host, --replica-port\n}\n```\n\n```\n$ my-app --help\nFlags:\n  --verbose              (default false)\n  --primary-host string  (default \"localhost\")\n  --primary-port int     (default 5432)\n  --replica-host string  (default \"localhost\")\n  --replica-port int     (default 5432)\n```\n\nDeep nesting chains prefixes: `Infra.Primary.Host` becomes `--infra-primary-host`. Env vars follow the same pattern: `INFRA_PRIMARY_HOST`.\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eValidation\u003c/b\u003e\u003c/summary\u003e\n\nStruct tag validation:\n\n```go\ntype Params struct {\n    Port     int    `descr:\"port\" min:\"1\" max:\"65535\"`\n    LogLevel string `descr:\"log level\" alts:\"debug,info,warn,error\" strict:\"true\"`\n    Name     string `descr:\"name\" pattern:\"^[a-z]+$\"`\n    Tags     []string `descr:\"tags\" min:\"1\" max:\"5\"` // min/max = slice length\n}\n```\n\nProgrammatic validation with `HookContext` — for cases where struct tags aren't enough:\n\n```go\ntype Params struct {\n    Host string `descr:\"Server hostname\"`\n    Port int    `descr:\"Server port\"`\n    CIDR string `descr:\"Allowed CIDR range\" optional:\"true\"`\n}\n\nboa.CmdT[Params]{\n    Use: \"server\",\n    InitFuncCtx: func(ctx *boa.HookContext, p *Params, cmd *cobra.Command) error {\n        // Type-safe custom validator\n        boa.GetParamT(ctx, \u0026p.Port).SetCustomValidatorT(func(port int) error {\n            if port \u003c 1024 \u0026\u0026 port != 80 \u0026\u0026 port != 443 {\n                return fmt.Errorf(\"non-standard privileged port %d\", port)\n            }\n            return nil\n        })\n\n        // Conditional required: CIDR only required when host is not localhost\n        ctx.GetParam(\u0026p.CIDR).SetRequiredFn(func() bool {\n            return p.Host != \"localhost\"\n        })\n\n        return nil\n    },\n    RunFunc: func(p *Params, cmd *cobra.Command, args []string) {\n        fmt.Printf(\"Listening on %s:%d\\n\", p.Host, p.Port)\n    },\n}.Run()\n```\n\nYou can also implement validation directly on your params struct:\n\n```go\ntype ServerConfig struct {\n    Host     string\n    Port     int\n    LogLevel string\n}\n\nfunc (c *ServerConfig) InitCtx(ctx *boa.HookContext) error {\n    ctx.GetParam(\u0026c.Port).SetDefault(boa.Default(8080))\n    ctx.GetParam(\u0026c.LogLevel).SetAlternatives([]string{\"debug\", \"info\", \"warn\", \"error\"})\n    ctx.GetParam(\u0026c.LogLevel).SetStrictAlts(true)\n    return nil\n}\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eError Handling\u003c/b\u003e\u003c/summary\u003e\n\n| Method | Behavior |\n|--------|----------|\n| `Run()` | Shows usage + error on bad input, exits 1. Other errors panic. |\n| `RunE()` | Returns all errors silently for programmatic use |\n| `ToCobra()` | Returns `*cobra.Command` for custom execution via `boa.Execute(cmd)` |\n\n`Run()` for simple CLIs:\n\n```go\nboa.CmdT[Params]{\n    Use: \"app\",\n    RunFunc: func(p *Params, cmd *cobra.Command, args []string) {\n        fmt.Println(\"Success!\")\n    },\n}.Run()\n// Bad input → prints usage + error, exits 1\n// Runtime panic → crashes with stack trace\n```\n\n`RunE()` when you need error handling:\n\n```go\nerr := boa.CmdT[Params]{\n    Use: \"app\",\n    RunFuncE: func(p *Params, cmd *cobra.Command, args []string) error {\n        if p.Port \u003c 1024 {\n            return fmt.Errorf(\"port must be \u003e= 1024\")\n        }\n        return nil\n    },\n}.RunE()\n\nif err != nil {\n    log.Fatalf(\"Command failed: %v\", err)\n}\n```\n\n`ToCobra()` when embedding boa in a larger cobra app:\n\n```go\ncmd := boa.CmdT[Params]{\n    Use: \"sub\",\n    RunFunc: func(p *Params, cmd *cobra.Command, args []string) { ... },\n}.ToCobra()\n\nrootCmd.AddCommand(cmd)\nboa.Execute(rootCmd) // prints usage + error on failure\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eShell Completions\u003c/b\u003e\u003c/summary\u003e\n\nEvery boa CLI gets shell completions for free via cobra. No extra code needed:\n\n```\n$ my-app completion bash   # bash completions\n$ my-app completion zsh    # zsh completions\n$ my-app completion fish   # fish completions\n$ my-app completion powershell  # powershell completions\n```\n\nInstall once and get tab completion for all flags, subcommands, and enum values:\n\n```bash\n# bash\nmy-app completion bash \u003e /etc/bash_completion.d/my-app\n\n# zsh\nmy-app completion zsh \u003e \"${fpath[1]}/_my-app\"\n\n# fish\nmy-app completion fish \u003e ~/.config/fish/completions/my-app.fish\n```\n\nFields with `alts` automatically complete to their allowed values.\n\nStatic completions from struct tags:\n\n```go\ntype Params struct {\n    LogLevel string `descr:\"log level\" alts:\"debug,info,warn,error\"` // tab-completes to these values\n}\n```\n\nDynamic completions — e.g. completing based on output from other CLIs:\n\n```go\nboa.CmdT[Params]{\n    Use: \"deploy\",\n    InitFunc: func(p *Params, cmd *cobra.Command) error {\n        cmd.RegisterFlagCompletionFunc(\"namespace\", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n            // Call kubectl to get real namespace list\n            out, err := exec.Command(\"kubectl\", \"get\", \"namespaces\", \"-o\", \"jsonpath={.items[*].metadata.name}\").Output()\n            if err != nil {\n                return nil, cobra.ShellCompDirectiveError\n            }\n            return strings.Fields(string(out)), cobra.ShellCompDirectiveNoFileComp\n        })\n        return nil\n    },\n    RunFunc: func(p *Params, cmd *cobra.Command, args []string) { ... },\n}.Run()\n// $ deploy --namespace \u003cTAB\u003e\n// default    kube-system    production    staging\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eStruct Tags Reference\u003c/b\u003e\u003c/summary\u003e\n\n| Tag | Description | Example |\n|-----|-------------|---------|\n| `descr` / `desc` | Description text | `descr:\"User name\"` |\n| `name` / `long` | Override flag name | `name:\"user-name\"` |\n| `default` | Default value | `default:\"8080\"` |\n| `env` | Environment variable name | `env:\"PORT\"` |\n| `short` | Short flag (single char) | `short:\"p\"` |\n| `positional` / `pos` | Marks positional argument | `positional:\"true\"` |\n| `required` / `req` | Marks as required | `required:\"true\"` |\n| `optional` / `opt` | Marks as optional | `optional:\"true\"` |\n| `alts` | Allowed values (enum) | `alts:\"debug,info,warn,error\"` |\n| `strict` | Validate against alts | `strict:\"true\"` |\n| `min` | Min value or min length | `min:\"1\"` |\n| `max` | Max value or max length | `max:\"65535\"` |\n| `pattern` | Regex pattern | `pattern:\"^[a-z]+$\"` |\n| `configfile` | Auto-load config from path | `configfile:\"true\"` |\n| `boa` | Special directives | `boa:\"ignore\"`, `boa:\"configonly\"` |\n\u003c/details\u003e\n\n## Further Reading\n\n- [Getting Started](https://gigurra.github.io/boa/getting-started/) — all parameter types, subcommands, config files\n- [Struct Tags](https://gigurra.github.io/boa/struct-tags/) — complete tag reference with auto-prefixing\n- [Validation](https://gigurra.github.io/boa/validation/) — required/optional, alternatives, conditional requirements\n- [Lifecycle Hooks](https://gigurra.github.io/boa/hooks/) — customize behavior at each stage\n- [Enrichers](https://gigurra.github.io/boa/enrichers/) — auto-derive flag names, env vars, short flags\n- [Error Handling](https://gigurra.github.io/boa/error-handling/) — Run() vs RunE() and error propagation\n- [Advanced](https://gigurra.github.io/boa/advanced/) — custom types, config format registry, viper-like discovery\n- [Cobra Interop](https://gigurra.github.io/boa/cobra-interop/) — access cobra primitives, migrate incrementally\n","funding_links":[],"categories":["Command Line","Build Automation"],"sub_categories":["Standard CLI"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FGiGurra%2Fboa","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FGiGurra%2Fboa","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FGiGurra%2Fboa/lists"}