A simple, lightweight Go CLI application framework built on the standard library flag package.
- 🚀 Simple to Use - Built on standard library
flagpackage, low learning curve - 📦 Lightweight - Zero third-party dependencies, clean codebase
- 🎯 Flexible Configuration - Support custom help and version commands
- 🧩 Subcommand Support - Built-in subcommand routing and management
- 🔧 Context Support - Native
context.Contextsupport for timeout control and cancellation - 📝 Auto Help Generation - Automatically generate formatted help information
go get github.com/h3go/clipackage main
import (
"context"
"fmt"
"os"
"github.com/h3go/cli"
)
func main() {
// Create CLI application
app := cli.NewProgram("myapp", "1.0.0")
app.Usage = "A simple CLI application"
// Create command
initCmd := cli.NewCommand("init", "Initialize a new project")
var path string
initCmd.Flags = func(fs *flag.FlagSet) {
fs.StringVar(&path, "path", ".", "Project path")
}
initCmd.Action = func(ctx context.Context, cmd *cli.Command) error {
fmt.Printf("Initializing project at %s\n", path)
return nil
}
// Register command
app.Commands = []*cli.Command{initCmd}
// Run application
if err := app.Run(os.Args); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}Run:
$ myapp init --path ./myproject
Initializing project at ./myprojectpackage main
import (
"context"
"fmt"
"os"
"github.com/h3go/cli"
)
func main() {
app := cli.NewProgram("myapp", "1.0.0")
app.Usage = "A feature-rich CLI application"
app.Banner = `
__ __
| \/ |_ _ __ _ _ __ _ __
| |\/| | | | |/ _' | '_ \| '_ \
| | | | |_| | (_| | |_) | |_) |
|_| |_|\__, |\__,_| .__/| .__/
|___/ |_| |_|
`
// init command
initCmd := cli.NewCommand("init", "Initialize a new project")
initCmd.Description = "Create a new project with the specified configuration"
var initPath string
var verbose bool
initCmd.Flags = func(fs *flag.FlagSet) {
fs.StringVar(&initPath, "path", ".", "Project path")
fs.BoolVar(&verbose, "verbose", false, "Verbose output")
}
initCmd.Action = func(ctx context.Context, cmd *cli.Command) error {
if verbose {
fmt.Printf("Initializing project at %s (verbose mode)\n", initPath)
} else {
fmt.Printf("Initializing project at %s\n", initPath)
}
return nil
}
// build command
buildCmd := cli.NewCommand("build", "Build the project")
buildCmd.Description = "Compile the project with specified options"
var buildOutput string
var optimize bool
buildCmd.Flags = func(fs *flag.FlagSet) {
fs.StringVar(&buildOutput, "output", "bin/app", "Output path")
fs.BoolVar(&optimize, "optimize", false, "Enable optimization")
}
buildCmd.Action = func(ctx context.Context, cmd *cli.Command) error {
fmt.Printf("Building project to %s (optimize=%v)\n", buildOutput, optimize)
return nil
}
// deploy command
deployCmd := cli.NewCommand("deploy", "Deploy the application")
var env string
deployCmd.Flags = func(fs *flag.FlagSet) {
fs.StringVar(&env, "env", "production", "Environment (development/staging/production)")
}
deployCmd.Action = func(ctx context.Context, cmd *cli.Command) error {
fmt.Printf("Deploying to %s environment\n", env)
return nil
}
app.Commands = []*cli.Command{initCmd, buildCmd, deployCmd}
if err := app.Run(os.Args); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}app := cli.NewProgram("myapp", "1.0.0")
app.Usage = "Application description"
app.Banner = "ASCII art banner (optional)"cmd := cli.NewCommand("commandname", "Short description")
cmd.Description = "Long description (optional)"Add flags using standard library flag package style:
var name string
var age int
var verbose bool
cmd.Flags = func(fs *flag.FlagSet) {
fs.StringVar(&name, "name", "default", "User name")
fs.IntVar(&age, "age", 0, "User age")
fs.BoolVar(&verbose, "verbose", false, "Enable verbose output")
}cmd.Action = func(ctx context.Context, cmd *cli.Command) error {
fmt.Printf("Name: %s, Age: %d\n", name, age)
return nil
}Besides using closures to capture variables, you can also use convenience methods to get flag values in Action:
cmd := cli.NewCommand("config", "Configure settings")
cmd.Flags = func(fs *flag.FlagSet) {
fs.String("host", "localhost", "Server host")
fs.Int("port", 8080, "Server port")
fs.Bool("debug", false, "Enable debug mode")
fs.Int64("max-size", 1024, "Maximum size in bytes")
fs.Uint("workers", 4, "Number of workers")
fs.Uint64("limit", 1000000, "Rate limit")
fs.Float64("ratio", 0.5, "Compression ratio")
fs.Duration("timeout", 30*time.Second, "Request timeout")
}
cmd.Action = func(ctx context.Context, cmd *cli.Command) error {
// Use convenience methods to get flag values
host := cmd.String("host") // Get string
port := cmd.Int("port") // Get int
debug := cmd.Bool("debug") // Get bool
maxSize := cmd.Int64("max-size") // Get int64
workers := cmd.Uint("workers") // Get uint
limit := cmd.Uint64("limit") // Get uint64
ratio := cmd.Float64("ratio") // Get float64
timeout := cmd.Duration("timeout") // Get duration
fmt.Printf("Server: %s:%d\n", host, port)
fmt.Printf("Debug: %v\n", debug)
fmt.Printf("Max Size: %d bytes\n", maxSize)
fmt.Printf("Workers: %d\n", workers)
fmt.Printf("Limit: %d\n", limit)
fmt.Printf("Ratio: %.2f\n", ratio)
fmt.Printf("Timeout: %v\n", timeout)
return nil
}Usage example:
$ myapp config --host example.com --port 3000 --debug --timeout 1m
Server: example.com:3000
Debug: true
Max Size: 1024 bytes
Workers: 4
Limit: 1000000
Ratio: 0.50
Timeout: 1m0sNote: These convenience methods search from merged flags (including global flags and command flags), returning zero values if not found.
app.Commands = []*cli.Command{cmd1, cmd2, cmd3}if err := app.Run(os.Args); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}Use RunContext for timeout and cancellation support:
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := app.RunContext(ctx, os.Args); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}Check context in commands:
cmd.Action = func(ctx context.Context, cmd *cli.Command) error {
select {
case <-ctx.Done():
return ctx.Err()
default:
// Execute command logic
}
return nil
}The framework automatically provides help functionality:
# Show application help
$ myapp -h
$ myapp --help
$ myapp help
# Show command help
$ myapp init -h
$ myapp help initThe framework automatically provides version information:
$ myapp -v
$ myapp --version
$ myapp version// Hide built-in help/version
app.HideHelpCommand = true
app.HideVersionCommand = true
app.HideHelpFlag = true
app.HideVersionFlag = true
// Custom help command
customHelp := cli.NewCommand("help", "Custom help")
customHelp.Action = func(ctx context.Context, cmd *cli.Command) error {
fmt.Println("My custom help message")
return nil
}
app.HelpCommand = customHelp
// Custom version command
customVersion := cli.NewCommand("version", "Custom version")
customVersion.Action = func(ctx context.Context, cmd *cli.Command) error {
fmt.Println("My custom version info")
return nil
}
app.VersionCommand = customVersionSet a default command to execute when users don't provide a command:
app := cli.NewProgram("myapp", "1.0.0")
app.DefaultCommand = "serve" // Set default command
serveCmd := cli.NewCommand("serve", "Start the server")
var port int
serveCmd.Flags = func(fs *flag.FlagSet) {
fs.IntVar(&port, "port", 8080, "Port to listen on")
}
serveCmd.Action = func(ctx context.Context, cmd *cli.Command) error {
fmt.Printf("Server listening on port %d\n", port)
return nil
}
app.Commands = []*cli.Command{serveCmd}Usage example:
# Execute default command serve when no command is provided
$ myapp
Server listening on port 8080
# Pass flags to default command
$ myapp --port 3000
Server listening on port 3000
# Explicitly specify other commands
$ myapp help
# Show help informationtype Program struct {
Commands []*Command // Command list
Name string // Application name
Usage string // Application description
Version string // Application version
Banner string // Application banner (ASCII art, etc.)
DefaultCommand string // Default command name (used when no command is specified)
HideHelpCommand bool // Hide help command
HideVersionCommand bool // Hide version command
HideHelpFlag bool // Hide -h/--help flag
HideVersionFlag bool // Hide -v/--version flag
HelpCommand *Command // help command (customizable)
VersionCommand *Command // version command (customizable)
}
func NewProgram(appName, version string) *Program
func (p *Program) Run(args []string) error
func (p *Program) RunContext(ctx context.Context, args []string) error
func (p *Program) Get(name string) *Command
func (p *Program) SetOutput(w io.Writer)
func (p *Program) Output() io.Writer
func (p *Program) PrintUsage() errortype Command struct {
Name string // Command name (e.g., "init", "migrate")
Usage string // Short usage description (one line)
Description string // Detailed description (multiple lines)
Flags func(*flag.FlagSet) // Command flag configuration function
Action ActionFunc // Command execution function
HideHelpFlag bool // Hide -h help flag
}
func NewCommand(name, usage string) *Command
func DefaultHelpCommand() *Command
func DefaultVersionCommand() *Command
func (c *Command) Run(args []string) error
func (c *Command) RunContext(ctx context.Context, args []string) error
func (c *Command) SetOutput(w io.Writer)
func (c *Command) Output() io.Writer
func (c *Command) SetProgram(p *Program)
func (c *Command) Program() *Program
func (c *Command) PrintUsage() error
func (c *Command) String(name string) string
func (c *Command) Bool(name string) bool
func (c *Command) Int(name string) int
func (c *Command) Int64(name string) int64
func (c *Command) Uint(name string) uint
func (c *Command) Uint64(name string) uint64
func (c *Command) Float64(name string) float64
func (c *Command) Duration(name string) time.Durationtype ActionFunc func(ctx context.Context, cmd *Command) errorCheck the examples directory for more examples:
| Feature | cli | cobra | urfave/cli |
|---|---|---|---|
| Dependencies | 0 | Multiple | 0 |
| Based on stdlib | ✅ | ❌ | ✅ |
| Learning curve | Low | Medium | Low |
| Feature richness | Medium | High | High |
| Code size | Minimal | Large | Medium |
MIT License - see LICENSE
Issues and Pull Requests are welcome!