# golang-cli A small command-line interface library: you declare a **command tree** once, and dispatch, `?`-help, and TAB-completion are all derived from it. Output can be colorized text or JSON from the same command code. Built on [`github.com/chzyer/readline`](https://github.com/chzyer/readline). It is generic over a *client* type `C` (typically a gRPC client) that is threaded unchanged into every command and completion function — so your command code receives the concrete client with no type assertions. ```go import cli "git.ipng.ch/ipng/golang-cli" ``` ## The building blocks | Concept | API | |---|---| | **The parse tree** | `cli.Node[C]` + `cli.Walk` / `cli.ExpandPaths` | | **Dynamic nodes** (live completion candidates) | `Node.Dynamic func(ctx, C, args) []string` | | **Command functions** | `Node.Run func(ctx, C, args) error`, run by `cli.Dispatch` / `cli.Shell` | | **Interactive shell** | `cli.Shell[C]` (TAB-completion, `?`-help, prefix abbreviation) | | **Output** | `cli.Emit(text, value)` → text or JSON; `cli.KV` / `cli.Paint` / `cli.Label` | A *slot* node (`Dynamic != nil`, with a placeholder `Word` like ``) accepts any single token as an argument. `Dynamic` receives the args captured by slot nodes *earlier on the path*, so a `` slot can list only the services of the `` already typed. ## Usage ```go type inventory struct { /* your client */ } func dynServers(_ context.Context, inv inventory, _ []string) []string { /* ... */ } func runShow(_ context.Context, inv inventory, args []string) error { // Hand Emit a human string and a machine value; the framework prints one or // the other based on the output format (see -json below). return cli.Emit(cli.KV("name", args[0]), map[string]any{"name": args[0]}) } func buildTree() *cli.Node[inventory] { return &cli.Node[inventory]{Children: []*cli.Node[inventory]{ {Word: "show", Help: "show state", Children: []*cli.Node[inventory]{ {Word: "server", Help: "list servers", Run: runShowServers, Children: []*cli.Node[inventory]{ {Word: "", Help: "show one", Dynamic: dynServers, Run: runShow}, }}, }}, {Word: "quit", Help: "exit", Run: func(context.Context, inventory, []string) error { return cli.ErrQuit }}, }} } func main() { root, inv := buildTree(), newInventory() if args := os.Args[1:]; len(args) > 0 { // one-shot _ = cli.Dispatch(context.Background(), root, inv, cli.SplitTokens(strings.Join(args, " "))) return } // interactive REPL: TAB completion, '?' help, prefix abbreviation _ = (&cli.Shell[inventory]{Root: root, Client: inv, Prompt: "inv> "}).Run(context.Background()) } ``` Return `cli.ErrQuit` from a command's `Run` to stop the REPL. `Shell.FormatError` lets you render command errors however you like (e.g. unwrap a gRPC status to its message and color it); it defaults to `err.Error()`. ## Output: color and JSON A command describes its result **once** and the framework renders it: ```go cli.Emit( cli.KV("name", name), // text form: blue "name=" + value map[string]any{"name": name}, // machine form, used under -json ) ``` Toggle the format and color once at startup: ```go if *jsonFlag { cli.SetFormat(cli.FormatJSON) } cli.SetColor(*colorFlag && !*jsonFlag) ``` - `cli.KV(key, value)` — `"key=value"` with the key painted blue. - `cli.Paint(s, cli.Red|Green|Blue|Yellow|Cyan)` — color a status word. - `cli.Label(s)` — blue (what `KV` uses for the key). With color off (`-color=false`) or in JSON mode, no ANSI escapes are emitted, so output stays script-safe. ## Runnable example [`example/main.go`](example/main.go) is a complete, dependency-free demo — an in-memory "server inventory" CLI: ```sh go run ./example # interactive shell go run ./example show server web1 # name=web1 count=3 services=http, https, ssh go run ./example -json show server web1 # {"name":"web1","count":3,"services":[...]} go run ./example -color=false show server web1 # no ANSI escapes go run ./example colors # the ANSI palette go run ./example ping db1 # pong from db1 ``` In the interactive shell, TAB completes and `?` lists what can follow: ``` inv> show server web1 web2 db1 inv> show server web1 service ? show one service : http https ssh ``` ## Versioning Released as semver Go module tags. Pin a version with: ```sh go get git.ipng.ch/ipng/golang-cli@v1.0.0 ``` The import path stays `git.ipng.ch/ipng/golang-cli` for all `v0`/`v1` releases; a future `v2` would import as `git.ipng.ch/ipng/golang-cli/v2`. ## Notes - **OpenBSD**: readline's native termios path is broken there; the library installs an `x/sys/unix`-based override automatically (`term_openbsd.go`), a no-op on every other platform. Verified building on Linux (amd64/arm64) and OpenBSD. - Requires Go 1.25+ (generics). Dependencies: `chzyer/readline`, `golang.org/x/sys`. ## License Apache-2.0. See [LICENSE](LICENSE).