How the Aqua CLI Command Structure Is Organized

The aqua CLI command structure relies on the urfave/cli v3 framework, with a central Runner in pkg/cli/runner.go that assembles individual commands from dedicated packages under pkg/cli/, each exposing a New constructor returning a *cli.Command and receiving shared runtime parameters via util.Param and global arguments via cliargs.GlobalArgs.

The aquaproj/aqua repository implements a highly modular aqua CLI command structure designed for scalability and testing. By delegating each subcommand to its own package and standardizing the constructor pattern, the codebase enables developers to add or modify CLI functionality without affecting unrelated components.

Central Command Assembly in pkg/cli/runner.go

The entry point for all CLI operations resides in pkg/cli/runner.go, specifically within the Run function. This function acts as the composition root, instantiating shared dependencies before delegating to individual command packages.

First, Run creates a util.Param struct containing runtime essentials:

// pkg/cli/runner.go
param := &util.Param{
    Stdin:   env.Stdin,
    Stdout:  env.Stdout,
    Stderr:  env.Stderr,
    Logger:  logger,
    Runtime: runtime.New(),
    Version: env.Version,
}

It then initializes cliargs.GlobalArgs to capture flags available across all commands. These dependencies are passed to a commands() helper that invokes each package's New constructor:

return urfave.Command(env, &cli.Command{
    Name:  "aqua",
    Usage: "Version Manager of CLI. https://aquaproj.github.io/",
    Flags: cliargs.GlobalFlags(globalArgs),
    Commands: commands(
        param,
        globalArgs,
        initcmd.New,
        install.New,
        generate.New,
        updateaqua.New,
        upc.New,
        update.New,
        which.New,
        info.New,
        remove.New,
        vacuum.New,
        token.New,
        cp.New,
        cpolicy.New,
        cpolicy.NewInitPolicy,
        exec.New,
        list.New,
        genr.New,
        root.New,
    ),
}).Run(ctx, env.Args)

This centralized registration pattern ensures the aqua CLI command structure remains explicit and discoverable, with all top-level commands enumerated in a single location.

Modular Command Packages Under pkg/cli/

Each subcommand lives in its own package beneath pkg/cli/, following a strict naming convention. The commands() function in runner.go accepts constructor functions that return *cli.Command instances. The current modular structure includes:

  • init (pkg/cli/initcmd): Initializes a new aqua.yaml configuration
  • install (pkg/cli/install): Downloads tools and creates symlinks
  • generate (pkg/cli/generate): Generates shell completion scripts
  • updateaqua (pkg/cli/updateaqua): Self-updates the aqua binary
  • upc (pkg/cli/upc): Updates aqua configuration file(s)
  • update (pkg/cli/update): Updates package versions and registries
  • which (pkg/cli/which): Displays the installed path of a command
  • info (pkg/cli/info): Prints detailed package information
  • remove (pkg/cli/remove): Removes installed tools
  • vacuum (pkg/cli/vacuum): Cleans up unused files
  • token (pkg/cli/token): Manages GitHub token storage
  • cp (pkg/cli/cp): Copies files from a tool's install directory
  • policy (pkg/cli/cpolicy): Manages security policies with constructors New and NewInitPolicy
  • exec (pkg/cli/exec): Executes commands within the tool-specific environment
  • list (pkg/cli/list): Lists installed tools
  • genr (pkg/cli/genr): Generates a JSON schema for aqua.yaml
  • root-dir (pkg/cli/root): Prints the Aqua root directory (AQUA_ROOT_DIR)

This package-per-command approach isolates business logic and allows independent testing of each CLI surface.

Standard Command Implementation Pattern

Every command package follows an identical four-step implementation pattern to maintain consistency across the aqua CLI command structure:

  1. Define an Args struct embedding *cliargs.GlobalArgs to inherit global flags
  2. Define a command struct holding *util.Param for runtime access
  3. Implement New(r *util.Param, globalArgs *cliargs.GlobalArgs) *cli.Command registering flags, usage text, and the action function
  4. Implement an action method handling profiling setup, parameter conversion via util.SetParam, and controller invocation

The install command exemplifies this pattern in pkg/cli/install/command.go:

func New(r *util.Param, globalArgs *cliargs.GlobalArgs) *cli.Command {
    args := &Args{GlobalArgs: globalArgs}
    i := &command{r: r}
    return &cli.Command{
        Name:    "install",
        Aliases: []string{"i"},
        Usage:   "Install tools",
        Action: func(ctx context.Context, _ *cli.Command) error {
            return i.action(ctx, args)
        },
        Flags: []cli.Flag{ /* bool/string flags */ },
    }
}

The action method typically initializes a controller from the pkg/controller layer and executes core logic, keeping the CLI adapter thin and focused on argument parsing and I/O handling.

Shared Infrastructure Across Commands

Two critical abstractions enable dependency injection throughout the aqua CLI command structure:

  • util.Param (defined in pkg/cli/util/param.go): A shared runtime context containing Stdin, Stdout, Stderr, Logger, Runtime, and Version. Every command receives this via its New constructor, ensuring consistent I/O and logging without global state.
  • cliargs.GlobalArgs (defined in pkg/cli/cliargs/global.go): A struct capturing flags applicable to all commands, such as configuration file paths or log levels. The Args struct in each command embeds this to automatically inherit global flag parsing.

This design eliminates tight coupling between commands and the environment, facilitating unit testing through mock util.Param injection.

Key Files Defining the Command Structure

Path Responsibility
pkg/cli/runner.go Central assembly point and entry point for the CLI; contains the Run function and command registration
pkg/cli/<command>/command.go Individual command implementations (e.g., pkg/cli/install/command.go)
pkg/cli/cliargs/global.go Definition of global flags and GlobalArgs struct
pkg/cli/util/param.go Shared runtime parameters passed to every command constructor
pkg/controller/* Business logic layer invoked by command action methods
cmd/aqua/main.go Minimal wrapper that calls cli.Run with the environment

Summary

  • The aqua CLI command structure uses urfave/cli v3 as its foundational framework.
  • Centralized registration occurs in pkg/cli/runner.go, where the Run function assembles all commands via a commands() helper.
  • Modular packages under pkg/cli/ isolate each command, with directories like install/, update/, and info/ each exporting a New constructor.
  • Standardized patterns include an Args struct embedding GlobalArgs, a command struct holding util.Param, and an action method delegating to controllers.
  • Shared infrastructure (util.Param and cliargs.GlobalArgs) provides consistent runtime context and global flag handling across all subcommands.

Frequently Asked Questions

What framework does the aqua CLI use for its command structure?

The aqua CLI is built on urfave/cli v3, a popular Go framework for building command-line interfaces. This framework provides the cli.Command struct and flag-parsing primitives that the aqua CLI command structure extends through its modular package organization.

How can I add a new command to the aqua CLI?

To add a new command, create a package under pkg/cli/<name>/ containing a command.go file. Implement the standard pattern: define an Args struct embedding *cliargs.GlobalArgs, a command struct with *util.Param, and a New function returning *cli.Command. Finally, add your package's New function to the commands() slice in pkg/cli/runner.go and rebuild the binary.

What is the purpose of util.Param in the command structure?

util.Param (located in pkg/cli/util/param.go) serves as a dependency injection container for runtime resources including standard I/O streams, the structured logger, runtime information, and version metadata. Every command receives this parameter through its constructor, enabling isolated testing and consistent resource access without global variables.

How does the aqua CLI handle global flags across all subcommands?

Global flags are defined in cliargs.GlobalArgs (within pkg/cli/cliargs/global.go) and exposed via cliargs.GlobalFlags(). Each command's local Args struct embeds *cliargs.GlobalArgs, automatically inheriting these flags. The New constructor in each package binds these arguments to the urfave command definition, ensuring uniform flag availability throughout the aqua CLI command structure.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →