Skip to main content

CLI Commands

nanoterm --help prints the full command tree at any time. Every subcommand also accepts --help and ships with at least one example. For an agent-readable JSON catalog, run nanoterm commands.

Global flags

FlagDescription
--org <id>Override the active org for this command (otherwise uses ~/.nanoterm/config.json / NANOTERM_ORG_ID)
--jsonOn error, emit a single JSONL line on stderr with kind, retryable, request_sent, attempt, status, code — designed for agents to make retry decisions

The --json field shape is stable. request_sent: false means the server never saw the request (safe to retry non-idempotent operations like ws create); request_sent: true with retryable: true means the server saw it and may have side effects — agents should check state before retrying.

nanoterm --json ws create --image ghcr.io/momenntal/nanoterm-claude-code 2>err.jsonl
echo $? # exit code
cat err.jsonl
# {"level":"error","kind":"network","retryable":true,"request_sent":false,"upstream":"ECONNREFUSED"}

auth

Manage authentication and API keys.

nanoterm auth login

Open the browser for sign-in. When you authorize the CLI on the /cli-auth page, a machine-scoped API key is minted and written to ~/.nanoterm/config.json (mode 0600). The CLI listens on a one-shot localhost port for the callback and exits after the first hit or a 5-minute timeout.

nanoterm auth login

nanoterm auth set-key <key>

Save an API key for CLI usage.

nanoterm auth set-key nt_a1b2c3d4...

nanoterm auth status

Show current authentication status.

nanoterm auth status
# Authenticated
# API: https://api.nanoterm.dev
# Key: nt_a1b2c3d4e5f6...
# Config: /Users/you/.nanoterm/config.json

nanoterm auth logout

Clear stored credentials.

nanoterm auth logout

ws

Manage workspaces.

nanoterm ws create

Create a new workspace.

FlagDescriptionDefault
-i, --image <image>Base imageghcr.io/momenntal/nanoterm-claude-code
-s, --size <size>small, medium, largesmall
-n, --name <name>Workspace nameauto-generated
-p, --port <mapping...>host:container to pin, or just container to auto-pick
--policy <id>Attach a policy
-r, --repo <url>Git repo to clone into /workspace
-b, --branch <branch>Branch to clonedefault branch
# Default Claude Code workspace
nanoterm ws create

# Specific image and size
nanoterm ws create --image ghcr.io/momenntal/nanoterm-codex --size medium

# Clone a repo at create time (auth via GITHUB_TOKEN secret)
nanoterm ws create --image claude-code --repo https://github.com/foo/bar.git

# Pin host ports for forwarded apps
nanoterm ws create -p 13000:3000 -p 18080:8080

When the image is nanoterm-claude-code or nanoterm-codex, the output reminds you which secrets auth-* command to run before the agent can talk to its provider — see secrets below.

nanoterm ws list

List all workspaces. Alias: nanoterm ws ls.

nanoterm ws list
# ID NAME STATUS IMAGE
# ws_a1b2c3d4 my-agent running ghcr.io/momenntal/nanoterm-claude-code
# ws_e5f6g7h8 test-runner stopped ubuntu:22.04

nanoterm ws ports <id>

Show the host-port mapping plus any auto-detected listeners inside the workspace.

nanoterm ws ports ws_a1b2c3d4
# Mapped (host → container URL):
# CONTAINER HOST URL
# 3000 http://localhost:13000
#
# Active listeners:
# PORT PROCESS DETECTED
# 3000 next-dev 08:01:23
#
# Forward an unmapped port: nanoterm proxy ws_a1b2c3d4 <port>

nanoterm ws stop <id> / start <id>

Stop preserves the filesystem; start resumes from it. Compute billing pauses while stopped.

nanoterm ws stop ws_a1b2c3d4
nanoterm ws start ws_a1b2c3d4

nanoterm ws rm <id>

Terminate and remove permanently.

nanoterm ws rm ws_a1b2c3d4
# ✓ Workspace removed

exec

Run a one-shot command in a workspace. Stdout/stderr stream to your terminal; the CLI exits with the command's exit code.

nanoterm exec <workspace-id> [flags] -- <command...>
FlagDescription
--cwd <path>Working directory inside the workspace (default /workspace)
--detachDon't wait — return immediately. Pair with --log and nanoterm exec-wait to follow up.
--log <path>Log file inside the workspace for stdout+stderr (with --detach)

Flag position is flexible — --cwd, --detach, --log work either before OR after the workspace ID. The -- separator marks the start of the workspace command.

# All four lines are equivalent
nanoterm exec ws_a1b2c3d4 --cwd /workspace/api -- pnpm build
nanoterm exec --cwd /workspace/api ws_a1b2c3d4 -- pnpm build
nanoterm exec ws_a1b2c3d4 -- pnpm build
nanoterm exec --cwd=/workspace/api ws_a1b2c3d4 -- pnpm build

# Multi-token shell pipeline (use bash -lc)
nanoterm exec ws_a1b2c3d4 -- bash -lc "ls -la | head"

# Detached background job — see exec-wait below
nanoterm exec ws_a1b2c3d4 --detach --log /tmp/build.log -- pnpm build

If a command exits 127 and looks like a shell one-liner (pipes, redirects, &&, etc.), the CLI hints at nanoterm shell — most likely you wanted the command run through a shell rather than as its own argv.


exec-wait

Wait for a detached exec --detach (or shell --detach) job to finish, then exit with the job's exit code. Polls a sidecar <log>.exit file written by the detach wrapper.

nanoterm exec-wait <workspace-id> --log <path> [--interval <sec>] [--timeout <sec>]
nanoterm exec ws_a1b2c3d4 --detach --log /tmp/install.log -- npm install
nanoterm exec-wait ws_a1b2c3d4 --log /tmp/install.log
echo $? # the npm install exit code

Why a sidecar file, not ps -p <pid>: detached children may be reparented to a non-reaping PID 1 inside the container and linger as <defunct>, which would cause naive ps polling to never terminate. The sidecar file is written exactly once and is unaffected by zombie state — exec-wait is the canonical way to await a detached job.


shell

Open an interactive bash session in the workspace. Same wire as exec but with a pre-configured bash -l PTY — convenient for ad-hoc poking around.

nanoterm shell ws_a1b2c3d4
# agent@ws-a1b2c3d4:/workspace$ ls

Ctrl+D exits.


attach

Attach a full PTY to a long-running agent process inside the workspace. Use this when the workspace already has a foreground agent running and you want to take over the terminal.

nanoterm attach ws_a1b2c3d4
# Connected. Press Ctrl+D to detach.

Supports color, window resize, and interactive programs (vim, top, etc.). For a fresh shell instead, prefer nanoterm shell.


files

Read, write, copy, and tail files inside a workspace.

nanoterm files cp <src> <dst>

Copy a file in either direction. The argument with a <id>: prefix is the workspace side.

# Local → workspace
nanoterm files cp ./script.sh ws_a1b2c3d4:/workspace/script.sh

# Workspace → local
nanoterm files cp ws_a1b2c3d4:/workspace/out.txt ./out.txt

nanoterm cp (top-level) is kept as a hidden alias.

All files cp / files write operations are byte-verified server-side: the API stats the file after writing and returns {bytes, mtime}, which the CLI compares against what was sent. A mismatch (silent truncation through a misconfigured proxy, e.g.) fails loudly with FILE_WRITE_BYTE_MISMATCH instead of pretending the upload succeeded.

nanoterm files write <id> <path>

Write stdin to a file inside the workspace. Useful for piping generated content without a local temp file.

echo "hello" | nanoterm files write ws_a1b2c3d4 /workspace/hello.txt

cat config.yaml | nanoterm files write ws_a1b2c3d4 /etc/myapp/config.yaml

nanoterm files tail <id> <path>

Poll-stream the tail of a file as it grows. Polling, not inotify — works the same on every runtime.

nanoterm files tail ws_a1b2c3d4 /workspace/build.log

nanoterm tail (top-level) is kept as a hidden alias.


secrets

Manage org-scoped secrets. They're encrypted at rest and injected into every workspace as environment variables.

nanoterm secrets list

nanoterm secrets list
# NAME TYPE UPDATED
# ANTHROPIC_API_KEY env 2026-04-15
# GITHUB_TOKEN env 2026-04-12

nanoterm secrets set <name>

Set an env-type secret. Pass the value via --from-env (recommended) or stdin; --value is allowed but logs to shell history.

# Pull from your own shell env
nanoterm secrets set ANTHROPIC_API_KEY --from-env ANTHROPIC_API_KEY

# Or via stdin
echo "$ANTHROPIC_API_KEY" | nanoterm secrets set ANTHROPIC_API_KEY --stdin

nanoterm secrets rm <name>

Delete a secret.

nanoterm secrets auth-claude

Run the Claude Code subscription OAuth flow on a sandboxed workspace, capture the resulting ~/.claude.json, and save it as an org secret. Every future workspace gets it pre-injected.

nanoterm secrets auth-claude
# Opens the browser; on completion the credential is stored.

For Claude API-key usage instead, set the env directly:

nanoterm secrets set ANTHROPIC_API_KEY --from-env ANTHROPIC_API_KEY

nanoterm secrets auth-codex

OpenAI Codex CLI auth. Only --api-key is supported today (no ChatGPT subscription flow).

nanoterm secrets auth-codex --api-key sk-...

nanoterm secrets auth-github

Save a GitHub PAT as the GITHUB_TOKEN secret, used by ws create --repo and any workspace git operation.

nanoterm secrets run -- <cmd>

Run a local command with org secrets pre-loaded into its environment (sister of infisical run). The command runs on your machine, not in a workspace — useful for scripts that need the same secrets your agents already use.

nanoterm secrets run -- env | grep API
nanoterm secrets run -- pnpm dev

nanoterm secrets status

Sanity-check: shows which agent providers (Claude, Codex) have working credentials in the org.


snapshot

Local Podman runtime only. Commits the running container to a local image. Restoring creates a new workspace from that snapshot; the source workspace is untouched.

# Commit
nanoterm snapshot create ws_a1b2c3d4 --name before-migration

# List
nanoterm snapshot list ws_a1b2c3d4

# Restore into a new workspace
nanoterm snapshot restore ws_a1b2c3d4 sn_x1y2z3

# Diff against the live workspace
nanoterm snapshot diff ws_a1b2c3d4 sn_x1y2z3

# Delete (also removes the local image)
nanoterm snapshot rm ws_a1b2c3d4 sn_x1y2z3

proxy

Forward an unmapped container port to your local machine — useful when an app starts listening after ws create ran, so port pinning wasn't possible upfront.

nanoterm proxy ws_a1b2c3d4 3000
# Forwarding http://localhost:3000 → ws_a1b2c3d4:3000 (Ctrl+C to stop)

nanoterm ws ports <id> lists detected listeners that are good candidates for proxying.

On startup, proxy performs a one-shot WebSocket pre-flight to verify auth + workspace existence + container reachability before opening the local listener. If the upstream returns 401/403, or the workspace doesn't exist, you get a clear error and the listener never opens — no more "listening on :15173 but every connection 401s".


Other groups

These exist and have full --help text — kept terse here:

GroupWhat it does
nanoterm keys ...Create / list / revoke API keys (mirror of the dashboard's API Keys page)
nanoterm policy ...Create / list / update governance policies
nanoterm members ...List org members, invite by email, revoke invitations
nanoterm org current / switch <id>Show or change the active org for the CLI
nanoterm billing plans / current / upgradeInspect plan and quota usage
nanoterm audit listRecent audit-log entries for the org
nanoterm commandsJSON catalog of every command + flags — for agent consumption

Auto-update notice

The CLI checks npm at most once per day and prints a one-line notice when a newer version is available. The check is best-effort (silent on network failure) and runs in the background — it never blocks the command you ran.

If the API ever raises the minimum required CLI version, you'll get a 426 CLI_VERSION_TOO_OLD response from the server with the exact upgrade command. Run npm install -g @nanoterm/cli@latest and retry.