Skip to content

bjaus/cli

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

109 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

cli

Go Reference Go Report Card CI codecov

Small interfaces. Big CLIs.

Commands are structs. Capabilities are interfaces. The framework discovers what your command can do through type assertion — no base types to embed, no configuration DSL. Just implement the interfaces you need.

Install

go get github.com/bjaus/cli

Quick Start

package main

import (
    "context"
    "fmt"
    "os"

    "github.com/bjaus/cli"
)

type GreetCmd struct {
    Name string `flag:"name" short:"n" default:"World" help:"Who to greet"`
}

func (g *GreetCmd) Run(_ context.Context) error {
    fmt.Printf("Hello, %s!\n", g.Name)
    return nil
}

func main() {
    cli.ExecuteAndExit(context.Background(), &GreetCmd{}, os.Args)
}
$ greet --name Alice
Hello, Alice!

$ greet --help
Usage:
  greet [flags]

Flags:
  -n, --name string   Who to greet (default: World)
  -h, --help          Show help

Design

Commands implement Commander:

type Commander interface {
    Run(ctx context.Context) error
}

Everything else is opt-in through interfaces:

// Naming
type Namer interface { Name() string }
type Descriptor interface { Description() string }
type Aliaser interface { Aliases() []string }

// Structure
type Subcommander interface { Subcommands() []Commander }
type Discoverer interface { Discover() ([]Commander, error) }

// Lifecycle
type Beforer interface { Before(context.Context) (context.Context, error) }
type Afterer interface { After(context.Context) error }
type Validator interface { Validate() error }

// ... and 20+ more

The framework discovers capabilities at runtime. A command that implements Namer gets a custom name. One that doesn't uses its struct type name. There's no magic — just Go interfaces.

Features

Feature Description
Struct-based flags flag:"port" tags with defaults, env vars, enums, and validation
Positional arguments Named args with arg:"target", variadic support, built-in validators
Subcommands Nested command trees via Subcommander interface
Lifecycle hooks Before, After, Validate, Init, Default
Dependency injection Type-safe injection via bind
Shell completion Bash, Zsh, Fish, PowerShell with dynamic completions
Plugin discovery External commands from directories or PATH
Configuration Load from JSON, YAML, env files, or remote sources
Help renderers Default, Compact, Tree, Man, JSON, Markdown formats

Example: Subcommands

type App struct {
    Verbose bool `flag:"verbose" short:"v" help:"Verbose output"`
}

func (a *App) Name() string        { return "myapp" }
func (a *App) Description() string { return "My application" }

func (a *App) Subcommands() []cli.Commander {
    return []cli.Commander{&ServeCmd{}, &DeployCmd{}}
}

func (a *App) Run(ctx context.Context) error {
    return cli.ErrShowHelp // show help when no subcommand given
}

type ServeCmd struct {
    Port int `flag:"port" short:"p" default:"8080" env:"PORT"`
}

func (s *ServeCmd) Name() string { return "serve" }
func (s *ServeCmd) Run(ctx context.Context) error {
    fmt.Printf("Listening on :%d\n", s.Port)
    return nil
}
$ myapp serve --port 9000
Listening on :9000

$ PORT=3000 myapp serve
Listening on :3000

Example: Dependency Injection

func main() {
    db, _ := sql.Open("postgres", os.Getenv("DATABASE_URL"))

    cli.ExecuteAndExit(ctx, &App{}, os.Args,
        cli.WithBindings(
            bind.Value(db),
        ),
    )
}

type ServeCmd struct {
    DB   *sql.DB // injected automatically
    Port int     `flag:"port" default:"8080"`
}

func (s *ServeCmd) Run(ctx context.Context) error {
    // s.DB is ready to use
    return nil
}

Documentation

Guide Description
Getting Started Installation and first command
Flags All 19+ struct tags, types, and patterns
Arguments Positional args and validators
Subcommands Command hierarchies and embedding
Lifecycle Init, Validate, Before, After hooks
Dependency Injection Type-safe resource sharing
Configuration Config files and remote sources
Completion Shell completion for all shells
Help Customizing help output
Plugins External command discovery
Errors Error handling and exit codes
Advanced Custom types, testing, patterns

Value Priority

Values are resolved in order (highest to lowest):

  1. CLI argument — --port 8080
  2. Environment variable — PORT=8080
  3. Config resolver — from config file
  4. Default tag — default:"8080"
  5. Zero value — 0

License

MIT

About

Small interfaces. Big CLIs.

Resources

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors