Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

CLI Design

Overview

nexusctl is the command-line interface for managing Firecracker microVMs through the Nexus daemon. It is a thin HTTP client – all state lives in nexusd, and nexusctl is stateless aside from configuration.

The recommended alias is nxc:

nexusctl vm list
nxc vm list          # identical

Command Grammar

Every command follows noun-verb ordering:

nexusctl <resource> <action> [name] [flags]

Resources map directly to Nexus API entities. Actions are consistent across all resources.

Resources

ResourceShortnameDescription
vmvmFirecracker microVM instances
workspacewsbtrfs subvolume snapshots attached to VMs
agentaAgent runtime sessions (portal + work VM pairs)
imageimgMaster images (read-only base subvolumes)
networknetBridge and tap device configuration
routertvsock routes between VMs
servicesvcvsock services registered by guest agents

Shortnames work anywhere the full resource name works:

nexusctl ws list
nexusctl workspace list   # identical

Standard Actions

Every resource supports a consistent set of verbs. Not every verb applies to every resource – attempting an unsupported action returns a clear error.

ActionDescriptionApplies to
listList resources in a tableall
createCreate a new resourcevm, workspace, agent, route
inspectShow detailed resource stateall
deleteRemove a resourceall
startStart a stopped resourcevm, agent
stopStop a running resourcevm, agent
logsStream or tail logsvm, agent

Special Commands

Some commands live outside the resource-action pattern:

CommandDescription
nexusctl attach <vm>Open a terminal session to a VM (WebSocket/ttyd)
nexusctl initInteractive project setup wizard
nexusctl applyApply declarative configuration from nexus.yaml
nexusctl config <subcommand>Manage CLI and daemon configuration
nexusctl completion <shell>Generate shell completions
nexusctl versionPrint version, daemon version, and API version
nexusctl statusQuick system health check (daemon, VMs, network)

Command Map

graph TD
    nexusctl["nexusctl"]

    nexusctl --> vm["vm"]
    nexusctl --> ws["workspace (ws)"]
    nexusctl --> agent["agent (a)"]
    nexusctl --> image["image (img)"]
    nexusctl --> network["network (net)"]
    nexusctl --> route["route (rt)"]
    nexusctl --> service["service (svc)"]
    nexusctl --> config["config"]
    nexusctl --> special["attach / init / apply / status"]

    vm --> vm_actions["list | create | inspect | delete | start | stop | logs"]
    ws --> ws_actions["list | create | inspect | delete | snapshot | restore"]
    agent --> agent_actions["list | create | inspect | delete | start | stop | logs"]
    image --> image_actions["list | inspect | delete | import"]
    network --> network_actions["list | inspect"]
    route --> route_actions["list | create | inspect | delete"]
    service --> service_actions["list | inspect"]
    config --> config_actions["list | edit | set | get"]

Output Formatting

Default: Columnar Tables

All list commands produce clean, aligned tables with no decoration:

$ nexusctl vm list
NAME            ROLE      STATE     VCPU  MEM    IP             AGE
agent-code-1    work      running   2     512M   172.16.0.11    3h
portal-oc-1     portal    running   1     256M   172.16.0.10    3h
git-server      service   stopped   1     128M   172.16.0.12    2d

The --output / -o flag controls format:

FormatFlagDescription
Table-o tableDefault. Human-readable columns.
Wide-o wideTable with additional columns (IDs, paths, timestamps).
JSON-o jsonFull JSON array. Machine-readable.
YAML-o yamlFull YAML. Matches config file format.
Name-o nameOne resource name per line. For piping.
$ nexusctl vm list -o name
agent-code-1
portal-oc-1
git-server

$ nexusctl vm list -o json
[
  {
    "name": "agent-code-1",
    "role": "work",
    "state": "running",
    "vcpu_count": 2,
    "mem_size_mib": 512,
    "ip_address": "172.16.0.11",
    "created_at": "2026-02-18T10:30:00Z"
  }
]

JSON Field Selection

The --json flag selects specific fields and implies JSON output. Combine with --jq for inline filtering.

$ nexusctl vm list --json name,state,ip_address
[
  {"name": "agent-code-1", "state": "running", "ip_address": "172.16.0.11"},
  {"name": "portal-oc-1", "state": "running", "ip_address": "172.16.0.10"}
]

$ nexusctl vm list --json name,state --jq '.[] | select(.state == "running") | .name'
"agent-code-1"
"portal-oc-1"

Layered Detail

Information density increases through progressive commands:

graph LR
    A["list"] -->|more columns| B["-o wide"]
    B -->|single resource| C["inspect"]
    C -->|machine parse| D["inspect -o json"]
$ nexusctl vm list                       # summary table
$ nexusctl vm list -o wide               # adds ID, CID, PID, socket path
$ nexusctl vm inspect agent-code-1       # full detail, formatted
$ nexusctl vm inspect agent-code-1 -o json   # full detail, structured

Color

Semantic colors convey state at a glance:

ColorMeaning
GreenRunning, success, healthy
YellowPending, warning, created
RedError, stopped, crashed, failed
DimMetadata, secondary info, timestamps

Color behavior:

  • TTY detected: Colors enabled by default.
  • Pipe / redirect: Colors disabled automatically.
  • NO_COLOR env set: Colors disabled (per no-color.org).
  • FORCE_COLOR env set: Colors forced on regardless of TTY.

Progress Indicators

  • Discrete steps: Creating workspace... (2 of 4) with step descriptions.
  • Indeterminate waits: Spinner with elapsed time: Waiting for VM to boot... (3.2s).
  • Non-TTY: Progress messages printed as plain lines, no ANSI escape sequences.

Interactive Behavior

TTY Gating

Every interactive prompt has a non-interactive equivalent. If stdin is not a TTY and a required value is missing, the command fails with an explicit error:

$ echo | nexusctl vm delete agent-code-1
Error: refusing to delete VM without confirmation
  Run with --yes to skip confirmation: nexusctl vm delete agent-code-1 --yes

Override Flags

FlagEffect
--yes / -ySkip all confirmation prompts
--interactiveForce interactive mode even without TTY
--no-interactiveForce non-interactive mode even with TTY

Destructive Operations

Commands that destroy data or stop running processes require confirmation:

$ nexusctl vm delete agent-code-1
VM "agent-code-1" is currently running with 1 attached workspace.
Delete this VM? This will stop it and detach all workspaces. [y/N] y
Deleted VM "agent-code-1"

Bypass with -y:

$ nexusctl vm delete agent-code-1 -y
Deleted VM "agent-code-1"

Error Messages

Every error has three parts: what failed, why it failed, and how to fix it.

$ nexusctl vm start agent-code-1
Error: cannot start VM "agent-code-1"
  VM is already running (state: running, PID: 4821)
  To restart, stop it first: nexusctl vm stop agent-code-1

$ nexusctl vm create --image nonexistent/image
Error: cannot create VM
  Image "nonexistent/image" not found
  Available images: nexusctl image list

$ nexusctl vm lis
Error: unknown action "lis" for resource "vm"
  Did you mean: list
  Available actions: list, create, inspect, delete, start, stop, logs

Error Output

  • All errors go to stderr.
  • All normal output goes to stdout.
  • Non-zero exit codes on failure.

Exit Codes

CodeMeaning
0Success
1General error
2Usage error (bad flags, missing args)
3Daemon unreachable
4Resource not found
5Conflict (e.g., VM already running)

Onboarding

First Run Detection

When nexusctl cannot reach nexusd, it detects whether the daemon is installed and guides the user:

$ nexusctl vm list
Error: cannot connect to Nexus daemon at 127.0.0.1:9600
  The daemon does not appear to be running.

  Start it:     systemctl --user start nexus.service
  Enable it:    systemctl --user enable nexus.service
  Check status: systemctl --user status nexus.service

If the daemon is not installed at all:

$ nexusctl vm list
Error: cannot connect to Nexus daemon at 127.0.0.1:9600
  The nexus package does not appear to be installed.

  Install it:   sudo pacman -S nexus

Init Wizard

nexusctl init creates a project-level nexus.yaml through guided prompts:

$ nexusctl init
Nexus project setup

? Base image for work VMs:  workfort/code-agent (default)
? vCPUs per work VM:        2
? Memory per work VM (MiB): 512
? Agent runtime:            openclaw

Created nexus.yaml

Next steps:
  nexusctl apply              Apply this configuration
  nexusctl agent create dev   Create an agent from this config

Next-Command Hints

After successful operations, suggest the logical next step:

$ nexusctl vm create --image workfort/code-agent --name agent-code-1
Created VM "agent-code-1" (state: created)

  Start it: nexusctl vm start agent-code-1

$ nexusctl vm start agent-code-1
Started VM "agent-code-1" (state: running, IP: 172.16.0.11)

  Attach terminal: nexusctl attach agent-code-1
  View logs:       nexusctl vm logs agent-code-1

Hints are suppressed in non-TTY environments and when using -o json or -o name.

Configuration

Format

All configuration is YAML. No exceptions.

Precedence

Configuration resolves in this order (highest wins):

graph TD
    A["CLI flags"] --> B["Environment variables"]
    B --> C["Project config ./nexus.yaml"]
    C --> D["User config ~/.config/nexusctl/config.yaml"]
    D --> E["System config /etc/nexus/nexus.yaml"]
    E --> F["Built-in defaults"]

    style A fill:#2d6,stroke:#333,color:#000
    style F fill:#999,stroke:#333,color:#000
  1. CLI flags--api-url, --output, etc.
  2. Environment variablesNEXUS_API_URL, NEXUS_OUTPUT, etc.
  3. Project config./nexus.yaml in the current directory (or parent search).
  4. User config~/.config/nexusctl/config.yaml (respects XDG_CONFIG_HOME).
  5. System config/etc/nexus/nexus.yaml.
  6. Built-in defaults.

Environment Variables

All env vars use the NEXUS_ prefix. Flag names map to env vars by uppercasing and replacing hyphens with underscores:

FlagEnv Var
--api-urlNEXUS_API_URL
--outputNEXUS_OUTPUT
--no-colorNEXUS_NO_COLOR

User Config

# ~/.config/nexusctl/config.yaml
api_url: "http://127.0.0.1:9600"
output: table
defaults:
  vm:
    vcpu_count: 2
    mem_size_mib: 512
  workspace:
    base_image: workfort/code-agent

Config Commands

$ nexusctl config list
KEY                SOURCE              VALUE
api_url            /etc/nexus/nexus.yaml   http://127.0.0.1:9600
output             default             table
defaults.vm.vcpu   ~/.config/nexusctl/config.yaml  2
defaults.vm.mem    ~/.config/nexusctl/config.yaml  512

$ nexusctl config get api_url
http://127.0.0.1:9600

$ nexusctl config set defaults.vm.vcpu_count 4
Set defaults.vm.vcpu_count = 4 in ~/.config/nexusctl/config.yaml

$ nexusctl config edit
# opens ~/.config/nexusctl/config.yaml in $EDITOR

config list shows every effective value and which source it came from. This eliminates the “where is this setting coming from?” problem.

Declarative Configuration

Project File

A nexus.yaml in the project root declares the desired infrastructure:

# nexus.yaml
version: 1

agents:
  dev:
    image: workfort/code-agent
    vcpu: 2
    mem: 512
    runtime: openclaw
    workspaces:
      - name: code
        base: workfort/code-agent
      - name: data
        base: empty
        size: 2G

network:
  bridge: nexbr0
  cidr: 172.16.0.0/24

Apply with Diff and Dry Run

$ nexusctl apply --dry-run
Comparing nexus.yaml against current state...

  + create agent "dev"
    + create VM "dev-portal" (portal, 1 vCPU, 256M)
    + create VM "dev-work" (work, 2 vCPU, 512M)
    + create workspace "dev-code" from workfort/code-agent
    + create workspace "dev-data" (empty, 2G)
    + create route dev-portal:9000 → dev-work:9000

No changes applied (dry run)

$ nexusctl apply
Comparing nexus.yaml against current state...

  + create agent "dev"
    + create VM "dev-portal" (portal, 1 vCPU, 256M)
    + create VM "dev-work" (work, 2 vCPU, 512M)
    + create workspace "dev-code" from workfort/code-agent
    + create workspace "dev-data" (empty, 2G)
    + create route dev-portal:9000 → dev-work:9000

Apply these changes? [y/N] y
Creating agent "dev"... done (2.1s)

Agent "dev" is running.
  Attach: nexusctl attach dev

Shell Completion

Generation

$ nexusctl completion bash >> ~/.bashrc
$ nexusctl completion zsh > ~/.zfunc/_nexusctl
$ nexusctl completion fish > ~/.config/fish/completions/nexusctl.fish

Dynamic Completions

Completions query the daemon for live state. Tab-completing a VM name fetches the current VM list:

$ nexusctl vm inspect <TAB>
agent-code-1    (work, running)
portal-oc-1     (portal, running)
git-server      (service, stopped)

Zsh completions include descriptions. Resource shortnames complete identically to full names.

Help System

Progressive Disclosure

Default help shows only core commands. Advanced usage is available but not shown upfront.

$ nexusctl --help
nexusctl - manage Firecracker microVMs via Nexus

Usage: nexusctl <resource> <action> [name] [flags]

Core Commands:
  vm          Manage virtual machines
  workspace   Manage btrfs workspaces
  agent       Manage agent sessions
  attach      Open terminal to a VM

Getting Started:
  init        Set up a new project
  apply       Apply declarative configuration
  status      System health check

Configuration:
  config      View and edit settings
  completion  Generate shell completions

Run 'nexusctl <resource> --help' for resource-specific actions.
Run 'nexusctl help --all' for the full command list.

Full help includes every resource, action, and global flag:

$ nexusctl help --all

Resource Help

Each resource shows its actions, 2-3 usage examples, and flag groups:

$ nexusctl vm --help
Manage Firecracker virtual machines

Usage: nexusctl vm <action> [name] [flags]

Actions:
  list      List all VMs
  create    Create a new VM
  inspect   Show VM details
  delete    Remove a VM
  start     Start a stopped VM
  stop      Stop a running VM
  logs      View VM console logs

Examples:
  nexusctl vm list
  nexusctl vm create --image workfort/code-agent --name my-vm
  nexusctl vm create my-vm                          # uses default image
  nexusctl vm inspect my-vm -o json

Basic Flags:
  -o, --output <format>   Output format: table, wide, json, yaml, name
      --json <fields>     Select JSON fields (implies -o json)
      --jq <expr>         Filter JSON output with jq expression

Resource Flags:
      --image <name>      Base image for the VM
      --role <role>       VM role: work, portal, service (default: work)
      --vcpu <n>          vCPU count (default: 1)
      --mem <mib>         Memory in MiB (default: 128)

Advanced Flags:
      --cid <n>           Override vsock context ID (auto-assigned by default)
      --no-start          Create without starting
      --dry-run           Show what would happen without executing

Flag Grouping

Flags in --help are organized into groups: basic, resource-specific, and advanced. Basic flags appear on every command. Advanced flags are things most users never touch.

Smart Defaults

The CLI minimizes required input. Sensible defaults make the common case trivial:

$ nexusctl vm create my-vm

This single command:

  • Uses the default image from config (or workfort/code-agent)
  • Assigns role work
  • Allocates 1 vCPU, 128 MiB memory (or user config defaults)
  • Auto-assigns a vsock CID
  • Auto-assigns an IP address
  • Creates and starts the VM

Explicit flags override any default:

$ nexusctl vm create my-vm --vcpu 4 --mem 1024 --role portal --no-start

Dry Run

All commands that create, modify, or destroy state support --dry-run:

$ nexusctl vm delete agent-code-1 --dry-run
Would delete VM "agent-code-1" (state: running)
  Would stop VM first
  Would detach workspace "agent-code-1-ws" (data preserved)

No changes applied (dry run)

$ nexusctl ws create --base workfort/code-agent --name new-ws --dry-run
Would create workspace "new-ws"
  Source: workfort/code-agent
  Type: btrfs snapshot

No changes applied (dry run)

--dry-run returns exit code 0 on success (the operation would succeed) and non-zero if it would fail.

Composability

Piping

-o name produces one name per line for piping:

$ nexusctl vm list -o name | xargs -I{} nexusctl vm stop {}

$ nexusctl vm list --json name,state --jq '.[] | select(.state == "stopped") | .name' \
  | xargs -I{} nexusctl vm delete {} -y

Scripting

Structured JSON output and consistent exit codes make nexusctl scriptable:

#!/bin/bash
set -e

# Create and wait for VM
nexusctl vm create --image workfort/code-agent --name build-vm -o json > /dev/null
nexusctl vm start build-vm

# Check state
state=$(nexusctl vm inspect build-vm --json state --jq '.state' -r)
if [ "$state" != "running" ]; then
  echo "VM failed to start" >&2
  exit 1
fi

# Do work...
nexusctl attach build-vm --exec "make build"

# Cleanup
nexusctl vm delete build-vm -y

Exit Codes in Conditionals

if nexusctl vm inspect my-vm &>/dev/null; then
  echo "VM exists"
else
  echo "VM not found, creating..."
  nexusctl vm create my-vm
fi

Performance Targets

OperationTarget
CLI startup (parse args, load config)< 50ms
vm list (daemon query + render)< 200ms
vm inspect (daemon query + render)< 100ms
Shell completion (query + return)< 300ms
apply diff computation< 500ms

The CLI does no heavy computation. It parses arguments, reads config, makes one HTTP call, and formats the response. If it feels slow, the daemon is the bottleneck, not the CLI.

Architecture

graph LR
    subgraph CLI["nexusctl"]
        Args["Arg Parser"]
        Config["Config Loader"]
        Client["HTTP Client"]
        Formatter["Output Formatter"]
    end

    subgraph Daemon["nexusd"]
        API["HTTP API :9600"]
    end

    Args --> Client
    Config --> Client
    Client -->|"HTTP/JSON"| API
    API -->|"JSON response"| Formatter
    Formatter -->|"table / json / yaml"| Stdout["stdout"]
    Formatter -->|"errors"| Stderr["stderr"]

nexusctl is a single static binary. No runtime dependencies beyond libc. The binary contains:

  1. Argument parser – Clap with derive macros. Handles subcommands, aliases, shortnames, completions.
  2. Config loader – Reads YAML from all sources, merges by precedence, resolves env vars.
  3. HTTP client – reqwest (blocking). One request per command invocation. WebSocket for attach.
  4. Output formatter – Renders JSON responses into the requested format. Handles color, alignment, truncation.

Startup Sequence

1. Parse args                    (~5ms)
2. Load config (flags + env + files) (~10ms)
3. HTTP request to daemon        (network-bound)
4. Format + print response       (~5ms)

No daemon connection is made for --help, --version, completion, or config commands.

Full Command Reference

nexusctl vm list [--role <role>] [--state <state>]
nexusctl vm create [name] [--image <img>] [--role <role>] [--vcpu <n>] [--mem <mib>]
nexusctl vm inspect <name>
nexusctl vm delete <name> [-y]
nexusctl vm start <name>
nexusctl vm stop <name> [--force]
nexusctl vm logs <name> [--follow] [--tail <n>]

nexusctl workspace list [--vm <name>]
nexusctl workspace create [name] [--base <image>] [--size <size>]
nexusctl workspace inspect <name>
nexusctl workspace delete <name> [-y]
nexusctl workspace snapshot <name> [--tag <label>]
nexusctl workspace restore <name> --snapshot <tag>

nexusctl agent list
nexusctl agent create [name] [--image <img>] [--runtime <rt>]
nexusctl agent inspect <name>
nexusctl agent delete <name> [-y]
nexusctl agent start <name>
nexusctl agent stop <name>
nexusctl agent logs <name> [--follow] [--tail <n>]

nexusctl image list
nexusctl image inspect <name>
nexusctl image delete <name> [-y]
nexusctl image import <path> [--name <name>]

nexusctl network list
nexusctl network inspect <bridge>

nexusctl route list [--vm <name>]
nexusctl route create --from <vm:port> --to <vm:port>
nexusctl route inspect <id>
nexusctl route delete <id> [-y]

nexusctl service list [--vm <name>]
nexusctl service inspect <name>

nexusctl attach <vm> [--exec <command>]
nexusctl init
nexusctl apply [--dry-run] [-y] [-f <file>]
nexusctl status
nexusctl version

nexusctl config list
nexusctl config get <key>
nexusctl config set <key> <value>
nexusctl config edit

nexusctl completion bash|zsh|fish

Global Flags

These flags are available on every command:

-o, --output <format>     Output format: table, wide, json, yaml, name
    --json <fields>       Select JSON fields (comma-separated)
    --jq <expr>           jq filter expression (requires --json or -o json)
    --api-url <url>       Nexus daemon URL (default: http://127.0.0.1:9600)
    --no-color            Disable colored output
    --no-interactive      Disable interactive prompts
-y, --yes                 Skip confirmation prompts
    --dry-run             Preview changes without applying
-v, --verbose             Increase log verbosity (repeatable: -vvv)
-q, --quiet               Suppress non-error output
-h, --help                Show help
    --version             Show version