# How the Aqua CLI Runner File Registers Commands: A Deep Dive into the Constructor Pattern

> Explore how the aqua CLI runner registers commands. Learn about the constructor pattern, shared parameters, and command instantiation in this deep dive into its codebase.

- Repository: [aquaproj/aqua](https://github.com/aquaproj/aqua)
- Tags: deep-dive
- Published: 2026-02-25

---

**The aqua CLI runner registers commands by passing a variadic list of constructor functions to a `commands` helper that instantiates each sub-command with shared `util.Param` and `cliargs.GlobalArgs` parameters, wiring them into the top-level `urfave.Command` structure defined in [`pkg/cli/runner.go`](https://github.com/aquaproj/aqua/blob/main/pkg/cli/runner.go).**

The aqua CLI from the `aquaproj/aqua` repository is a declarative CLI version manager written in Go. Understanding how the aqua CLI runner file registers commands reveals the modular, constructor-based architecture that makes the tool extensible and maintains clean separation between command definitions and execution logic.

## The Entry Point: pkg/cli/runner.go

The command registration flow begins in [`pkg/cli/runner.go`](https://github.com/aquaproj/aqua/blob/main/pkg/cli/runner.go) with the `Run` function, which serves as the primary entry point for the executable. This function initializes the runtime environment by creating a `util.Param` object that encapsulates I/O streams, structured logging via `slogutil.Logger`, runtime detection, and version metadata.

```go
func Run(ctx context.Context, logger *slogutil.Logger, env *urfave.Env) error { … }

```

After parameter initialization, the function constructs a `cliargs.GlobalArgs` structure to hold configuration for flags that apply across all sub-commands. These parameters are then passed to the `commands` helper function, which returns a slice of `*cli.Command` instances ready for execution.

## The Constructor Pattern for Command Registration

The registration mechanism relies on a **constructor function pattern** (type `newC`) rather than static command definitions. In [`pkg/cli/runner.go`](https://github.com/aquaproj/aqua/blob/main/pkg/cli/runner.go), the `commands` function accepts a variadic list of these constructors and returns the assembled command slice:

```go
// commands creates a slice of CLI commands by applying the given command constructors
func commands(param *util.Param, globalArgs *cliargs.GlobalArgs, newCs ...newC) []*cli.Command { … }

```

Each constructor follows the signature `New(param *util.Param, globalArgs *cliargs.GlobalArgs) *cli.Command` and resides in its own feature package. The `commands` helper iterates over the provided constructors, invoking each with the shared parameters to build the complete command tree.

### Registered Command Constructors

The `Run` function explicitly declares all available sub-commands by passing their constructors to the `commands` helper:

```go
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,
),

```

Each function—such as `install.New` from [`pkg/cli/install/command.go`](https://github.com/aquaproj/aqua/blob/main/pkg/cli/install/command.go) or `exec.New` from [`pkg/cli/exec/command.go`](https://github.com/aquaproj/aqua/blob/main/pkg/cli/exec/command.go)—returns a fully configured `*cli.Command` instance with its specific flags, arguments, and action handlers.

## Global Parameter Propagation and Flag Registration

The registration system ensures consistent behavior across all commands through **shared parameter injection**. The single `util.Param` instance created in `Run` propagates to every sub-command constructor, guaranteeing uniform I/O handling, logging configuration, and runtime environment detection.

Global flags—such as `--log-level`—are registered via `cliargs.GlobalFlags(globalArgs)` and apply to every sub-command automatically, eliminating redundant flag definitions in individual command packages.

## Sub-Command Implementation Structure

Individual commands are modularized under `pkg/cli/*/` directories, with each package containing a [`command.go`](https://github.com/aquaproj/aqua/blob/main/command.go) file that exports its constructor. For example:

- **Install command**: Defined in [`pkg/cli/install/command.go`](https://github.com/aquaproj/aqua/blob/main/pkg/cli/install/command.go) via `install.New`
- **Update command**: Defined in [`pkg/cli/update/command.go`](https://github.com/aquaproj/aqua/blob/main/pkg/cli/update/command.go) via `update.New`
- **Exec command**: Defined in [`pkg/cli/exec/command.go`](https://github.com/aquaproj/aqua/blob/main/pkg/cli/exec/command.go) via `exec.New`

This structure enforces the **Single Responsibility Principle** by isolating command logic, flag definitions, and help text within dedicated packages. When the `commands` helper executes a constructor, it injects the shared parameters, allowing each command to access global resources without direct dependencies on the runner.

## Execution Flow and Error Handling

After assembling the command tree, the `Run` function initializes the top-level `urfave.Command` with the returned slice and invokes the runtime:

```go
urfave.Command(...).Run(ctx, env.Args)

```

The framework parses command-line arguments, dispatches to the appropriate sub-command based on the first positional argument, and executes the corresponding action. Error handling is centralized through `exitErrHandlerFunc` (defined at `runner.go#L90-L98`), which ensures consistent exit codes and error formatting across all command failures, including the critical `exec` command.

## Practical Command Registration Examples

To observe the registration in action, invoke the aqua CLI help flag to view all wired sub-commands:

```bash

# Display all registered commands and global flags

$ aqua --help
Version Manager of CLI. https://aquaproj.github.io/
...
Commands:
  init      Initialize a new Aqua configuration
  install   Install tools defined in aqua.yaml
  generate  Generate a lock file from aqua.yaml
  update    Update installed tools
  which     Show the path of an installed tool
  exec      Execute a command with tools added to PATH
  list      List installed tools
  remove    Remove installed tools

```

You can also verify global flag propagation across commands:

```bash

# Apply a global flag to any sub-command

$ aqua --log-level=debug install
$ aqua --log-level=debug list

```

## Summary

- **The aqua CLI runner file** ([`pkg/cli/runner.go`](https://github.com/aquaproj/aqua/blob/main/pkg/cli/runner.go)) serves as the central registry for all sub-commands via the `Run` function and `commands` helper.
- **Constructor functions** (type `newC`) define a standard interface for command creation, accepting shared `util.Param` and `cliargs.GlobalArgs` parameters.
- **Seventeen distinct commands**—including `initcmd.New`, `install.New`, `exec.New`, and `root.New`—are registered declaratively in the `commands` call within `Run`.
- **Modular packaging** places each command's implementation in `pkg/cli/<name>/command.go`, maintaining clean separation of concerns.
- **Global parameters and flags** propagate automatically to all sub-commands through dependency injection, ensuring consistent logging and I/O behavior.

## Frequently Asked Questions

### How does the aqua CLI runner file register sub-commands?

The runner file registers sub-commands by invoking a `commands` helper function with a variadic list of constructor functions (e.g., `install.New`, `exec.New`). Each constructor returns a `*cli.Command`, and the helper aggregates these into a slice that the top-level `urfave.Command` uses to build the CLI tree.

### What is the role of the `commands` function in runner.go?

The `commands` function acts as a factory aggregator. It accepts shared parameters (`util.Param`, `cliargs.GlobalArgs`) and a variadic list of constructor functions (type `newC`), then iterates over each constructor to instantiate and return the complete slice of CLI commands. This pattern decouples command registration from command implementation.

### Where are individual aqua CLI commands defined?

Each command is defined in its own package under `pkg/cli/*/command.go`. For example, the install command logic resides in [`pkg/cli/install/command.go`](https://github.com/aquaproj/aqua/blob/main/pkg/cli/install/command.go) and exports an `install.New` constructor that returns a configured `*cli.Command` with specific flags and action handlers.

### How are global flags shared across all aqua commands?

Global flags are defined in `cliargs.GlobalArgs` and registered via `cliargs.GlobalFlags(globalArgs)`. These are passed to every command constructor in the `commands` helper, ensuring all sub-commands recognize universal flags like `--log-level` without individual flag declarations in each package.