A declarative CLI framework for GO
go get github.com/kingoftac/flagon/cliFlagon is a declarative CLI framework for Go that allows you to define commands, arguments, flags, and handlers in a structured way. It supports middleware, hooks, and Lua scripting for plugins.
package main
import (
"context"
"log"
"os"
"github.com/kingoftac/flagon/cli"
)
func main() {
c := cli.New(&cli.Command{
Name: "myapp",
Description: "My awesome CLI app",
Commands: []*cli.Command{
{
Name: "greet",
Description: "Greet someone",
Args: []cli.Arg{
{Name: "name", Description: "Name to greet"},
},
Handler: func(ctx context.Context) error {
name := cli.Args(ctx)[0]
log.Printf("Hello, %s!", name)
return nil
},
},
},
})
if err := c.Run(os.Args[1:]); err != nil {
log.Fatal(err)
}
}Run it:
go run main.go greet "World"
# Output: Hello, World!{
Name: "build",
Description: "Build the project",
Flags: func(fs *flag.FlagSet) {
fs.Bool("verbose", false, "enable verbose output")
fs.String("output", "build", "output directory")
},
Handler: func(ctx context.Context) error {
flags := cli.Flags(ctx)
verbose := flags["verbose"].(bool)
output := flags["output"].(string)
if verbose {
log.Println("Building with verbose output")
}
log.Printf("Output directory: %s", output)
return nil
},
}func LoggingMiddleware(logger *log.Logger) cli.Middleware {
return func(next cli.Handler) cli.Handler {
return func(ctx context.Context) error {
cmd := cli.CurrentCommand(ctx)
logger.Printf("Executing command: %s", cmd.Name)
err := next(ctx)
logger.Printf("Finished command: %s", cmd.Name)
return err
}
}
}
func main() {
c := cli.New(&cli.Command{
Name: "myapp",
Middleware: []cli.Middleware{
LoggingMiddleware(log.Default()),
},
Commands: []*cli.Command{
{
Name: "test",
Handler: func(ctx context.Context) error {
log.Println("Running tests...")
return nil
},
},
},
})
// ...
}func main() {
c := cli.New(&cli.Command{
Name: "myapp",
Commands: []*cli.Command{
{
Name: "deploy",
Before: []cli.Hook{
func(ctx context.Context) error {
log.Println("Pre-deployment checks...")
return nil
},
},
After: []cli.Hook{
func(ctx context.Context) error {
log.Println("Cleanup after deployment...")
return nil
},
},
Handler: func(ctx context.Context) error {
log.Println("Deploying application...")
return nil
},
},
},
})
// Global hooks
c.Hook(cli.BeforeRun, func(ctx context.Context) error {
log.Println("Application starting...")
return nil
})
c.Hook(cli.AfterRun, func(ctx context.Context) error {
log.Println("Application finished.")
return nil
})
// ...
}c := cli.New(&cli.Command{
Name: "myapp",
Commands: []*cli.Command{
{
Name: "db",
Description: "Database operations",
Commands: []*cli.Command{
{
Name: "migrate",
Description: "Run database migrations",
Handler: func(ctx context.Context) error {
log.Println("Running migrations...")
return nil
},
},
{
Name: "seed",
Description: "Seed database",
Handler: func(ctx context.Context) error {
log.Println("Seeding database...")
return nil
},
},
},
},
},
}){
Name: "sensitive",
Description: "Sensitive operation",
Middleware: []cli.Middleware{
func(next cli.Handler) cli.Handler {
return func(ctx context.Context) error {
log.Println("Checking permissions...")
// Permission check logic
return next(ctx)
}
},
},
Handler: func(ctx context.Context) error {
log.Println("Performing sensitive operation...")
return nil
},
}Defines a CLI command with its properties:
type Command struct {
Name string
Description string
Summary string
Hidden bool
Aliases []string
Args []Arg
Flags func(fs *flag.FlagSet)
Handler Handler
Commands []*Command
Before []Hook
After []Hook
Middleware []Middleware
}Defines a command argument:
type Arg struct {
Name string
Description string
Optional bool
Variadic bool
}Function signature for command handlers:
type Handler func(ctx context.Context) errorFunction for wrapping handlers:
type Middleware func(next Handler) HandlerFunction for lifecycle hooks:
type Hook func(ctx context.Context) errorCreates a new CLI instance:
func New(root *Command, opts ...Option) *CLIExecutes the CLI with given arguments:
func (c *CLI) Run(args []string) errorAppFromContext(ctx): Get the app instanceCurrentCommand(ctx): Get current commandArgs(ctx): Get positional argumentsFlags(ctx): Get flag values
WithLogger(log.Logger): Set custom loggerWithAppData(map[string]any): Set app dataWithWriters(out, err io.Writer): Set output writers
Flagon supports extending CLI functionality with Lua scripts for dynamic command definition.
go get github.com/kingoftac/flagon/luapackage main
import (
"log"
"os"
"github.com/kingoftac/flagon/cli"
"github.com/kingoftac/flagon/lua"
)
func main() {
c := cli.New(&cli.Command{
Name: "myapp",
})
// Load Lua plugins
engine := lua.NewEngine(c)
if err := engine.LoadFile("plugins/myplugin.lua"); err != nil {
log.Fatal(err)
}
if err := c.Run(os.Args[1:]); err != nil {
log.Fatal(err)
}
}Create a .lua file to define commands:
command {
name = "hello",
description = "Say hello",
args = {
{ name = "name", description = "Name to greet" }
},
flags = {
-- Lua doesn't handle flags directly, use Go for complex flags
},
middleware = {
function(ctx)
ctx.log("info", "Before hello")
ctx.next()
ctx.log("info", "After hello")
end
},
handler = function(ctx)
print("Hello, " .. ctx.args[1] .. "!")
end
}In Lua handlers and middleware, ctx provides:
ctx.args: Array of positional argumentsctx.log(level, message): Log messagesctx.next(): Call next middleware/handler (middleware only)
Plugins run in a sandboxed Lua environment with:
- Base libraries:
table,string,math - Safe functions only (no
dofile,loadfile, etc.) printredirected to CLI logger
We welcome contributions! Please:
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests if applicable
- Submit a pull request
# Clone the repo
git clone https://github.com/kingoftac/flagon.git
cd flagon
# Run unit tests (excludes fuzz tests)
make test
# Build WASM demo
make build-wasmFlagon includes comprehensive unit tests and fuzz tests for security-critical components.
# Run all unit tests
make test
# Run tests for a specific package
cd cli && go test -v
cd lua && go test -vFuzz tests help discover edge cases and potential security vulnerabilities in input parsing and the Lua sandbox.
# Run a specific fuzz test (default: FuzzCLIRun, 30s)
make fuzz
# Run a specific fuzz test with custom duration
make fuzz FUZZ_TEST=FuzzLuaScriptExecution FUZZ_TIME=1m
# Run all fuzz tests sequentially
make fuzz-all FUZZ_TIME=30sAvailable fuzz tests:
| Package | Test | Description |
|---|---|---|
cli |
FuzzCLIRun |
Full CLI execution with arbitrary arguments |
cli |
FuzzValidatePositionalArgs |
Argument validation |
cli |
FuzzFindSubcommand |
Subcommand lookup |
cli |
FuzzCollides |
Name collision detection |
cli |
FuzzContextFunctions |
Context value operations |
cli |
FuzzMiddlewareChain |
Middleware pipeline |
lua |
FuzzLuaScriptExecution |
Lua sandbox escape attempts |
lua |
FuzzDecodeCommand |
Lua table to Command struct |
lua |
FuzzLuaHandler |
Handler callback execution |
lua |
FuzzLuaMiddleware |
Middleware callback execution |
Please report bugs and request features via GitHub Issues.
This project is licensed under the MIT License - see the LICENSE file for details.