CLI command reference
anet manages the Hub, accounts, Networks, nodes, and external channels. This page keeps the current commands and behavior most likely to cause mistakes; follow the linked guides for full configuration.
Install and get help
# Stable
npm install -g @sleep2agi/agent-network@latest
# Preview
npm install -g @sleep2agi/agent-network@preview
anet --help
anet <command> --help
anet <command> <subcommand> --help # e.g. anet node delete --help
anet -vNode.js 22.13+ and Bun 1.2+ are required. --help (or -h) only prints help; it does not mint a token, start a service, or perform another business action. Every command and subcommand has its own usage text and exits 0.
Shortest startup path
# Terminal 1
anet hub start
# Terminal 2
anet login --hub http://127.0.0.1:9200 --username admin
# After login
anet node create my-agent
anet node start my-agentThe initial password depends on the release channel: stable (@latest) uses a fixed default documented under --password in anet hub start --help; preview (@preview) prints a one-time random password on first start. Run anet passwd immediately after logging in.
See Getting started for installation and first-time setup.
Hub
| Command | Purpose |
|---|---|
anet hub start | Start the Hub; listens on 127.0.0.1:9200 by default |
anet hub stop [--port <p>] | Stop the local Hub listening on a port |
anet hub status [--port <p>] | Show listener state, PID, and server version |
anet hub dashboard | Start the Dashboard on port 3000 by default |
anet hub config | Inspect or change local Hub launch settings |
anet hub admin reset-user --username <user> | Reset a user's password and user tokens on the Hub host |
Common start options:
| Option | Purpose |
|---|---|
--port <port> | Hub port; default 9200 |
--host <host> / --ip <host> | Bind address; defaults to loopback-only 127.0.0.1 |
--username <user> | Set the bootstrap administrator username |
--password <pass> | Explicitly set the bootstrap administrator password |
--dev-open | Disable authentication; isolated development only |
--version <v> | commhub-server version to launch (an exact version, or latest / preview) |
--channel <c> | latest or preview; overrides the channel picked from anet's own version |
Which Hub version starts: without --version, anet hub start first decides the channel by comparing the installed anet version with the @sleep2agi/agent-network dist-tags (equal to or behind latest means latest; a prerelease newer than latest means preview; the -preview.N suffix alone says nothing, since latest carries it too). It then takes that channel's @sleep2agi/commhub-server dist-tag and launches that exact version through bunx. If that tag is older than the minimum this anet supports, it uses the minimum instead. Start prints one line, Hub version: @sleep2agi/commhub-server@<version> [source: registry | cache | explicit | pinned minimum], naming the version and where it came from. When the npm registry can't be reached, it uses the newest version in the local bun cache that meets the minimum, with a warning. If the cache only has older versions, it does not start one silently: it prints a warning naming both versions and tells you to pass --version <cached version> if you want it anyway.
Do not use --dev-open or expose 0.0.0.0:9200 directly in production. See Production deployment.
Stopping the Hub (anet hub stop): it only stops a process that listens on that port and whose command line reads as commhub-server; it never matches processes by name. Finding the listener does not need lsof: on Linux it tries /proc/net/tcp(6) socket inodes against /proc/<pid>/fd, then ss -ltnp, lsof, netstat; on macOS lsof, then netstat -anv; on Windows netstat -ano. The first one that can run decides. When none can run, it uses ~/.anet/server/hub-<port>.pid.json, written by anet hub start (ignored if that PID has exited or now belongs to another program). The output lists every probe's result, the PID and command line it found, and which PID it actually stopped. It sends SIGTERM, and after 3 seconds SIGKILL to a survivor whose command line still reads as commhub-server.
| Case | Action | Exit code |
|---|---|---|
commhub-server found and stopped | stopped | 0 |
nothing listening on the port and /health does not answer | nothing | 0 |
| the port belongs to another program, or its command line / owner cannot be read | refused; prints the PID and command line | 1 |
/health answers but its PID cannot be found on this machine (no probe works and no pid file) | nothing; tells you to stop it from the process manager that started it (pm2, systemd, …) | 1 |
still running after SIGKILL, or /health still answers | — | 1 |
--port is not a valid port | — | 2 |
Accounts, Networks, and tokens
Accounts
| Command | Purpose |
|---|---|
anet register | Create an account |
anet login | Log in with username and password |
anet login --token <token> | Log in with an existing API token |
anet logout | Revoke this login session on the Hub, then delete the locally saved login token (see Logging out below) |
anet whoami | Show the current user and accessible Networks |
anet passwd | Change the password and rotate the current login token |
Logging out (anet logout): with the saved token, calls GET /api/auth/sessions to get current_token_id, then DELETE /api/auth/sessions/<token_id> to revoke that login session (the same pair of endpoints the app's Settings → Account → Signed-in devices → sign out uses; see Signed-in devices API), then removes token / user / network_id / network_name from ~/.anet/config.json (hub is kept). Output shows only the token id, never the token.
| Case | Local | Server | Exit code |
|---|---|---|---|
| Revoked, or the token was already invalid on the Hub (401) | Removed | Invalid | 0 |
Hub unreachable, or Hub older than v0.9.0-preview.70 (/api/auth/sessions 404) | Removed | Still valid; prints a ⚠ warning and how to revoke it | 0 |
The saved token is an explicit API token (anet login --token) or a node token | Removed | Not revoked (it may be in use elsewhere); prints a ⚠ warning — revoke with anet token revoke <token-id> | 0 |
| The local config file cannot be written | Not removed | — | 1 |
If the token is still valid on the server: sign that device out in the app under Settings → Account → Signed-in devices, or anet login again and run anet passwd (changing the password signs out every other login session). The COMMHUB_TOKEN that anet init project writes into each project's .anet/.env is a copy of the same token and stops working once it is revoked.
Networks
| Command | Purpose |
|---|---|
anet network ls | List Networks the current user has joined |
anet network create <name> | Create a Network |
anet network use <name> | Switch the current Network |
anet network info | Inspect the current Network |
anet network rename <old> <new> | Rename a Network |
anet network delete <name> --force | Delete a Network |
anet network invite [options] | Create an invite code |
anet network join <code> | Join with an invite code |
anet network members | List current Network members |
Invite options include --role admin|member|viewer, --uses <n>, and --expires <days>.
Tokens
| Command | Purpose |
|---|---|
anet token / anet token ls | List the current user's API tokens |
anet token create <name> | Create an API token; plaintext is shown once |
anet token revoke <token-id> | Revoke a token |
See Token model for token types, scopes, and compatibility behavior.
Nodes
Interactive menu: anet node
No commands to memorise: run anet node with no arguments in the node's directory. It lists every node in this directory — name, runtime (claude-agent-sdk / claude-code-cli / codex-* / grok-* / opencode-cli), state (running / stopped), model, plus a login column for codex nodes — then you pick a node and an action: start, stop, restart, attach (enter the TUI), show the recent log, change model, delete.
- Before anything runs it prints the exact equivalent
anet …command and runs it only aftery; delete asks you to type the node name. What runs is the printed command; the menu does no lifecycle work itself. - A codex node goes to the codex actions (log in, continue, copy, …), the same as
anet node codex— see the Codex node cheat sheet. - Not a terminal (piped, a script, an agent): it prints the node table, a short cheat sheet and the usual usage line, exit 0. No token appears in the output.
anet node --help/anet node helpprint help exactly as before.
Codex nodes without memorising commands: run anet node codex in the node's directory, pick a node and an action; it prints the equivalent command and asks before running it. Cheat sheet: Codex node cheat sheet.
| Command | Purpose |
|---|---|
anet node create <name> | Create a node; opens the runtime wizard when omitted |
anet node start <name> | Start a node in the current terminal |
anet node start <name> --tmux | Start or attach to a node in tmux |
anet node stop <name> | Stop the node and its same-name tmux session |
anet node restart <name> | Stop and start one node |
anet node resume <name> [--session <id>] | Request the saved or specified session; recovery depends on the runtime, including the V1 co-presence limitation below |
anet node delete <name> --force | Delete the local node configuration and the node's row on the Hub |
anet node rename <ref> <new> | Rename a node already registered with the Hub |
anet node clone <src> <new> | Copy a node's settings into a node with a new identity (see "Clone a node" below) |
anet node edit <ref> [--runtime <id>] [--model <id>] | Change an existing node's runtime / model; takes effect on restart |
anet node ls | List local nodes and network state |
anet node ls --all [--network <id|name>] [--json] | List every node the Hub shows you in the current Network, grouped by machine (hostname); read-only, see below |
anet node <start|stop|restart> <alias> --remote | Start / stop / restart a node on another machine: start / stop go through that machine's daemon, restart goes to the node itself (see "Managing nodes remotely" below) |
anet node edit <alias> --model <id> --remote | Switch the model of a node on another machine (no daemon needed) |
anet info <name> | Show node configuration, process, and recent tasks |
anet logs <name> [--follow] | Read or follow node logs |
anet node migrate-token-to-envref <name> | Replace plaintext secrets with envRef after writing a backup |
OpenCode V1 co-presence recovery requires this node's valid history checkpoint. Old CLI 2.3.0-preview.171 / runtime 2.5.0-preview.137 cannot restore it. The new candidate pair CLI 2.3.0-preview.172 / runtime 2.5.0-preview.138 imports a private checkpoint into fresh launch data and verifies the original session/history; failed import does not silently create a new session. A legacy node without a checkpoint still creates a new session: capture its history from the running original bridge during a coordinated task/TUI quiet window before the first upgrade. Crash recovery covers only the last successful checkpoint; external file references are not migrated. Candidates are not downloadable until exact-main publication gates pass. See the repository runbook's V1 restart and upgrade boundary.
anet node ls --all ignores the current directory. It reads the Hub as you (your anet login) via /api/status + /api/host-supervisors and lists the nodes of the current Network (or the one named by --network <id|name>, matched by id, name, or a unique id prefix) grouped by machine: alias, runtime, status with last heartbeat, model, and whether that machine has a live daemon (anet daemon; only a machine with an online daemon can be managed remotely later).
Network: team (net_0123456) — 3 node(s) on 2 machine(s)
host-alpha daemon: alpha-daemon online — remote-manageable
ALIAS RUNTIME STATUS LAST SEEN MODEL
a-coder claude-agent-sdk idle 12s ago claude-sonnet-4-5
a-writer codex-app-server working 1m ago gpt-5
host-beta daemon: none visible
ALIAS RUNTIME STATUS LAST SEEN MODEL
b-runner grok-build-cli offline 3d ago -- Permissions are exactly what the Hub returns: a member with restricted agent access sees only the nodes granted to them. The Hub returns no daemons to such members, so the line reads
daemon: none visible("not visible to you", not "there is none"). - Only your login token (
tokenin~/.anet/config.json) is used, neverCOMMHUB_TOKENor a node token. --jsonprints{ network, daemons_readable, machines: [{ hostname, daemon, daemons, nodes }] }.- Without
--all,anet node lsis unchanged.
Managing nodes remotely (--remote)
From any machine where you ran anet login, manage a node that lives on another machine (#562):
anet node stop coder-b --remote # shows where it lives and whether the daemon is online, then asks y/N
anet node start coder-b --remote --yes # --yes skips the question (scripts)
anet node restart coder-b --remote --wait 90 # wait up to 90 s for the result (default 60, --wait 0 = don't wait)
anet node edit coder-b --model m2 --remote # switch model (the node restarts once to apply it)Node coder-b — on machine-b, daemon daemon-b online; hub state: active / idle
Will run 将执行:
anet node stop coder-b --remote --network net_271eb4d9e48c --yes
(ask the hub to have daemon daemon-b on machine-b stop "coder-b" (its config is kept; start it again with --remote))
Run it? 执行? [y/N]: y
Dispatched (stop_node sr_c6dd58d6e565).
Waiting up to 60s for the hub to show the result …
✓ "coder-b" is stopped on machine-b- It uses the Hub's existing lifecycle tools (the same ones the app calls): start / stop are carried out by that machine's daemon (
anet daemon); restart / model change are sent by the Hub to the node itself, which finishes its current turn, exits (code 75) and is respawned by its ownanet node startwrapper — whether a daemon or a person started that wrapper. - start / stop require that the node's machine has an online daemon and that the node was created by that daemon (the app's "new node" on that machine). Otherwise it refuses before dispatching and says why, e.g.
this machine can't be managed remotely: no daemon online on machine-c; a node started by hand withanet node startis started / stopped on its own machine. - restart / model change (#570) need no daemon, so they work for hand-started nodes too. They require that the node is running and reports
config_update_capable(it runs under ananet node startwrapper, which respawns it on exit 75). Otherwise it refuses before dispatching:"<alias>" can't be restarted remotely: it doesn't support config updates (started without `anet node start`?); restart it on its machine— e.g. anagent-noderun directly by pm2 / systemd, which nothing would bring back. The confirmation says how it will be done, e.g.ask "hand-b" on machine-b itself, through the hub, to restart (it's not managed by a daemon). - A restart / model change counts as done only when the Hub shows the node back (config revision advanced, a fresh report, not offline). On timeout it says what it saw last (went offline and never came back → look on that machine; still working → it restarts after its current turn).
- Permissions are the Hub's: a viewer gets the Hub's
permission_denied; a member with restricted agent access sees no daemons and cannot read the node's config, so start / stop / restart / model change are all refused before dispatching, with the reason — the node is not touched. - Only your login token (
tokenin~/.anet/config.json) is used, neverCOMMHUB_TOKEN; a saved node token is refused. - Not in a terminal (script / pipe) and no
--yes: nothing is done, exit code1. On timeout it says honestly that the Hub accepted the request but has not shown the result yet; check withanet node ls --all. --network <id|name>picks another Network; local-only flags (--tmux,--copresence, …) are refused with--remote(exit code2).stop --forcestops a node that still has tasks in flight (the Hub audits it). Remote delete is out of scope for this step.
anet node delete <name> --force first runs the same stop as anet node stop (co-presence tmux sessions included: codex co-presence by marker + CODEX_HOME, others by exact session name, never a prefix), then deletes the local files (.anet/nodes/<id>/), then the node's row on the Hub, so it does not keep showing as "offline" in the app or dashboard. If the stop cannot be proven, it exits with an error and deletes nothing.
When the node is not in the current directory (a clone / codex fork made with
--workdir), anet finds it through the source directory's.anet/child-workdirs.jsonand the codex login index, does not delete it for you, and printscd <dir> && anet node delete <name>, exit code1.When several nodes match the name, it refuses and lists a delete-by-
node_idcommand for each.The Hub row is matched only by the
node_idin the local config, never by name. A node on another machine that reuses the same name is not deleted; anet printsLeft untouched: … belong to other node_id(s).If the Hub is unreachable or refuses, the local files are still deleted, anet prints a warning with the exact retry command, and exits
1:bashanet node delete <node_id> --hub-only # remove only the Hub row anet node delete <node_id> --hub-only --hub <url> # when the node used a different Hub than your loginWith no Hub configured, only the local files are removed and the exit code is
0. Very old node configs have nonode_id; their Hub row cannot be matched safely, so anet leaves it and tells you to remove it in the app or dashboard.
node delete does not automatically revoke the node's issued ntok_. To invalidate it completely, also run anet token revoke <token-id>.
anet node stop sends SIGTERM only and waits up to 8 seconds. It never escalates to SIGKILL — there is no --force on stop. If the node has not exited by then, anet prints pid <n> survived SIGTERM, keeps the pidfile, and exits 1: it reports the failure rather than claiming the node stopped, because a surviving process keeps heart-beating and would revert a rename. For the same reason node restart and node delete refuse to proceed. The only command that sends SIGKILL is anet node rename --force.
COMMHUB_TOKEN is not a CLI option, and there is no anet node start --token. Node authentication resolves in this order: node config, global config, then the legacy COMMHUB_TOKEN environment fallback. anet login --token logs in the CLI user; it does not inject a temporary node token into node start.
Common creation options:
| Option | Purpose |
|---|---|
--runtime <runtime> | Select a runtime; use the current channel's wizard and Runtime comparison as the source of truth |
--model <id> | Override the runtime's default model |
--resume <id> | claude-code-cli: bind a specific Claude Code session |
--resume-latest | claude-code-cli: bind the latest session in this project |
--tools <list> | Configure tools for runtimes that support this option |
anet session ls lists Claude Code sessions for the current directory. Session semantics differ by runtime; do not use a Claude session ID as a Codex thread ID.
Clone a node
Full guide (clone vs fork, what is copied, the codex login, deleting the copy): Copying a node (clone / fork) and cleaning up.
To get "another node just like this one":
anet node clone <source> <new-name>
# same thing
anet node create <new-name> --from <source>The new node is registered with the Hub as its own node (the same endpoint anet node create uses) and gets its own node_id and ntok_. It is not started unless you pass --start. A table at the end lists every item as copied / regenerated / skipped.
| Category | What |
|---|---|
| Copied | runtime, model, tools, permission flags, system prompt, non-secret env, the commhub channel; with --workdir also the rules file (CLAUDE.md / AGENTS.md), skills directories and .mcp.json (env / header values blanked); for codex nodes codex-home/config.toml, AGENTS.md, skills/ |
| Regenerated | node_id, alias, ntok_, the claude-code-cli session id, grok co-presence sockets, codexProjectDir |
| Not copied | the token, every session / thread id, logs, pid / lock files, goals, inbox, channel bot credentials, codex auth.json, co-presence identity files, and the values of secret env entries (the key names are kept as an envRef pointing at the new node; fill them in before starting) |
| Option | Purpose |
|---|---|
--workdir <dir> | Put the clone in another project directory (created if missing). The path must be ASCII (a node name may be Chinese, a directory may not). Without it the clone shares the source's project directory, rules file and skills |
--model <id> | Use a different model |
--start | Start right after cloning (skipped, with a hint, while secrets are still unset) |
Refused: an existing destination, a new name equal to the source's, a destination inside the source node's own directory, a non-ASCII --workdir, opencode-cli nodes (their runtime binding and login live outside the node config — create one with anet node create --runtime opencode-cli), and host daemons (role=host_supervisor).
A codex node's login is not copied. One login per node (refresh tokens are single-use; sharing knocks the others out): log the new node in before starting it, CODEX_HOME=<new-node-dir>/codex-home codex login --device-auth. Started without a login, anet stages this host's ~/.codex login — but refuses (exit 1) if another node on this host already uses it (--allow-shared-codex-login shares anyway, unsafely; see One login per node). For a registered account use anet node codex account install <new-name> --source codex-login:<profile-id>. To carry the codex conversation history too, use anet node codex fork (see Codex co-presence). A conversation you started with codex outside anet becomes a node with anet node codex adopt <new-name> --thread <id> (see Copying a node).
Never cp -r a node directory
.anet/nodes/<name>/config.json holds the node's node_id and ntok_. A verbatim copy is the same Hub identity as the source: both processes subscribe to the same inbox, every task runs twice and is answered twice, and the Dashboard cannot tell which one replied. Logs, sessions and the codex login are shared too. Always use anet node clone to copy a node.
Project-wide lifecycle
These commands scan .anet/nodes/ under the current directory:
| Command | Purpose |
|---|---|
anet project up | Start every node that is not already running |
anet project restart | Restart every node |
anet project down | Stop every node and report it offline |
Shared options:
--stagger <seconds>: delay between nodes; default 3 seconds,0disables it.--only a,b: operate only on listed aliases or node IDs.--exclude x,y: skip listed aliases or node IDs.
See Batch agents for batch creation and cleanup.
Channels
| Command | Purpose |
|---|---|
anet channel add telegram <node> --bot-token <token> --allow <uid> | Add Telegram |
anet channel add feishu <node> ... | Add Feishu; currently a preview feature |
anet channel allow feishu <node> ... | Change Feishu DM or group allowlists |
anet channel ls [node] | List channels |
anet channel status [node] | Show Telegram's effective config path and allowlist |
Channel settings are not hot-reloaded; restart the node after changing them. anet channel add wechat has not shipped. See Channel integration.
Goals
| Command | Purpose |
|---|---|
anet goal list [node] | List local goals |
anet goal show <node> <id> | Show details and progress records |
anet goal wake-log <node> <id> [--tail N] [--json] | Export the complete wake history |
anet goal edit <node> <id> ... | Change interval, text, or status |
anet goal cancel <node> <id> | Mark a goal cancelled |
anet node loop <node> "<task>" [--every 5m] | Create a recurring task on an online node and wait up to 15 seconds for confirmation |
node loop submits /aloop through the Hub. goal edit/cancel modify .anet/nodes/<node>/goals.json directly. A running node does not hot-reload external file changes; restart it after edit/cancel. See Goals and Loops for native Dashboard /goal and /loop, ANet /aloop and /agoal, statuses, and self-management tools.
Diagnostics and maintenance
| Command | Purpose |
|---|---|
anet status | Show nodes and task summary for the current Network |
anet tasks [status] [--limit <n>] | Query tasks |
anet doctor | Check configuration, Hub, dependencies, secrets, and channels |
anet doctor --fix | Apply compatibility migrations and repair recoverable token problems; modifies configuration |
| `anet upgrade [--channel latest | preview] [--dry-run]` |
anet config / anet config path / anet config json | Show global config summary, path, or JSON (tokens masked) |
anet init | Configure the Hub URL. When switching to a different Hub, the saved login session is first revoked on the old Hub (best effort — a failure only warns), then the old Hub's token and login info are removed from the config; the old token is never sent to the new Hub. Run anet login again afterwards. anet login --hub <another hub> does the same |
anet init project | Create CommHub MCP project files in the current directory |
anet setup | Install dependencies for selected runtimes |
See the Upgrade guide for upgrade details.
Reading the four numbers in anet status
CommHub: http://127.0.0.1:9200
Agents: 127 idle, 0 working, 1 needs attention, 143 offline
└─ 18 offline for 1-3 days, 27 offline for more than 3 days
SSE: 12 connected
Tasks: 10 recent| Number | Meaning |
|---|---|
idle | Free, waiting for work |
working | Actively progressing a turn (includes waiting_input — the turn is alive, just waiting on a human) |
needs attention | Someone should look: blocked / error, plus any status this version does not recognise |
offline | Heartbeat expired, or stopped cleanly |
The line under offline: how long have they been gone
When there are offline nodes, a breakdown line is printed. "Just stopped" and "gone for three days and nobody noticed" used to be the same number. Measured once over 84 nodes: of 45 offline, 27 had been gone more than 3 days, 18 for 1-3 days, and none within the last 6 hours — "there is no live outage right now" and "45 nodes are down" are two completely different conclusions, and only the second one was on screen.
Nodes with no usable timestamp go into their own "no timestamp (we do not know how long)" bucket and are not folded into the freshest one — "I do not know how long it has been gone" and "it just went down" are different things.
🔴 blocked has no matching duration, and should not have one. The roster has no "when did it become blocked" field; updated_at keeps being refreshed by the heartbeat, so using it as a blocked-duration prints "4 minutes ago" for a node stuck for hours — worse than showing nothing. See Is this node still alive.
The four add up to the node total. The needs attention slot is hidden when it is 0.
🔴 blocked / error are not counted as working. They mean "stuck", not "making progress" — folding them into working would let an operator read "N working" as "all fine". When needs attention is above 0, the nodes behind it are listed with their self-reported status and the task they were on.
⚠️ These statuses are self-reported and carry no liveness proof. A node whose process died but has not yet been swept can still show as idle here. To confirm it is still there, send it a task — anet status answers "what it last said about itself", not "whether it is still running".
Node permissions: anet node perm
In development, not yet released. Show or change a node's permissions on the Hub: the mode, the peer allowlist (who it may send to) and the inbox allowlist (who may send to it).
anet node perm <node> [--json]
anet node perm <node> [--mode normal|readonly|restricted]
[--peers <a,b,…>|none] [--peers-add <x>] [--peers-remove <x>]
[--inbox <a,user:bob,…>|none] [--inbox-add <x>] [--inbox-remove <x>]
[--hub <url>] [--json]<node>is a local node name, anode_idor a Hub alias. Without a change flag it only prints the current settings (plus a read-only view of thetool/restallowlists).--inboxentries:<alias>/<node_id>is an agent,user:<username>/user:<id>is a person,user:*is any person, and!strictrequires the node's owner and network owner/admin to be listed too. See inbox allowlist.noneclears that allowlist (no restriction). Removing the last entry with--*-removeis refused, because an empty list means fully open; writenoneexplicitly.- Changes need the user token from
anet login, as the node's owner, a network owner/admin or a hub admin. Against an old Hub that does not reportinbox_allowlist, the display says so and--inbox*is refused rather than pretending to succeed. --jsonprints{ok, state, changed}({ok: false, error, …}on failure). A usage error exits 2; a Hub refusal or unreachable Hub exits 1.
anet node perm reviewer --inbox planner,user:alice
anet node perm reviewer --inbox-add user:bob
anet node perm reviewer --peers none --mode normal --jsonNode and daemon environment secrets (preview)
Node env: anet node secret
anet node secret list <node> [--json]
anet node secret set <node> <KEY> [--from-env VAR]
anet node secret unset <node> <KEY>Writes nodes/<node>/secrets.env (0600, atomic replace, symlinks refused) and lists key names only. Values come from --from-env or stdin; a value in argv is a usage error.
Daemon-scoped: anet daemon secret
anet daemon secret list <daemon> [--node <alias>] [--json]
anet daemon secret set <daemon> <KEY> --from-env VAR (--node <alias> | --all)
anet daemon secret unset <daemon> <KEY> (--node <alias> | --all)Manages scoped-secrets.json in the daemon directory (0600, revision CAS, the target must already be bound). set/unset require --node or --all: an unscoped write becomes the default for every child, which is a permission decision, never an accident. Receipts and list show key names and their source (daemon/node) only.
Both commands are entries into the existing daemon_provider_snapshot secret_* actions — no second read/write path. Values load on the node's next full start; restarting the bridge alone does not update the native TUI/server. A name on the Hub token page is not injected into this process. Precedence: Environment precedence at startup.
Adopt manually started nodes (preview source, board #626)
On Linux, configure the daemon's adopt_roots with absolute allowed workdirs; the empty default refuses adoption. Load configuration through the existing operator-managed daemon startup procedure. A node workdir must not be HOME or a system directory. Use your human anet login account from the manual node's workdir:
anet daemon adopt demo --daemon <daemon-node-id>
anet daemon adopt demo --daemon <daemon-node-id> --yes
anet daemon adopt --all --daemon <daemon-node-id> --yes
anet daemon unadopt demo --yesWithout --yes, commands only show a plan. --all enumerates this workdir's .anet/nodes, not the whole machine. An accepted request is pending, not a successful adoption. The daemon checks UID, paths, configuration identity, /proc, HOME and reproducible environment before atomically recording and acknowledging it. Adoption and revocation never restart or signal the node. Only bare/default-socket tmux is supported; co-presence or unreproducible environments are refused (key names, never values). Run anet daemon adopted from the daemon workdir to list local records with active Hub authority.
adopt_path_writable_by_others: the workdir, .anet, .anet/nodes, the node directory or config.json is group/other-writable. Fix it with chmod go-w on the reported path. For a shared team workspace (e.g. 2775) that must stay group-writable, the workdir itself only may instead carry the sticky bit: a daemon-owned workdir with chmod +t <workdir> (e.g. drwxrwsr-t) is accepted, because other users then cannot rename or delete the daemon-owned .anet inside it. .anet and everything below it must still be go-w.
This increment binds nodes only; remote stop/start for adopted nodes requires board #627. Until then it fails explicitly instead of entering legacy child-process cleanup. Registry: <daemon-workdir>/.anet/child-workdirs.json. Original node configurations and secrets stay in place. Disaster recovery requires those private files and the Hub data backup; cloning source does not restore bindings, accounts or secrets. Revoking a binding does not delete the workdir.
Preview-only features
The following commands exist in the current preview and must not be presented as stable features:
| Command | Purpose |
|---|---|
anet daemon up [name] | Create and start a host_supervisor (version requirements: which versions have anet daemon) |
anet daemon init <name> / start <name> / restart <name> / list | Manage local daemons (same version requirements; for restart see the daemon page) |
anet node start <name> --copresence | Start Codex app-server, bridge, and shared TUI |
anet opencode ... | Manage the preview OpenCode integration |
🔴 There is no daemon-level stop / delete / status. A daemon is just an agent-node with role=host_supervisor, so use the node-level commands: anet node stop <name> to stop it, anet node delete <name> to remove it, anet node ls to see whether it is running (anet daemon list only lists locally configured daemons and carries no liveness). anet daemon restart calls that same stop internally.
Two counter-intuitive things about daemons
1. On an existing daemon, anet daemon init <name> changes nothing. It prints ✓ "<name>" already a host_supervisor daemon and returns — a green check mark with not one byte written. Changing the config requires --force (keeps node_id, but re-mints the token, and the daemon must be restarted for it to take effect).
2. The runtime list in the config is a write-time snapshot; it does not self-heal. Runtimes added later are not back-filled into existing daemon configs, which shows up as that machine offering fewer runtimes in the client's server picker. anet daemon list now prints which ones are missing and the command to back-fill.
--copresence only applies to runtime=codex-app-server. Its default sandbox is read-only. Full filesystem and network access requires --dangerously-allow-full-access; a TTY requires typing yes, and a non-TTY caller must also pass --yes-danger-full-access.
Resume a co-presence node with anet node start <name> --copresence; do not replace it with a normal node start.
opencode-cli is currently an agent-node-managed task runtime, not an attachable shared OpenCode TUI. The shared Grok TUI runtime grok-build-cli is also absent from the current preview packages; the available grok-build-acp runtime does not support attach.
Other commands
| Command | Purpose |
|---|---|
anet import [alias] | Import recoverable local node configuration from the Hub |
anet run --alias <name> | Start a minimal SSE echo agent that does not invoke an LLM |
anet demo [name] | Run experimental demos; not a production orchestration path |
anet batch <verb> | Manage groups created by anet create --batch |
anet license / anet activate <key> | Legacy license compatibility; Apache-2.0 users normally do not need these |
Legacy aliases such as anet create and anet start remain for compatibility. New documentation uses anet node ... consistently.
Exit codes
| Code | Meaning |
|---|---|
0 | Success, or only help was printed (for example anet node or anet network with no subcommand, or any command / subcommand with --help or -h) |
1 | Failure: not initialized / not logged in / session expired, Hub unreachable or returned an error, object not found, local write failed, refused to act |
2 | Usage error: missing required argument, unknown subcommand, invalid value |
Scripts and CI can rely on anet … || exit 1. Before #515 the following printed an error but exited 0; they now exit non-zero:
| Command | Case | Before → now |
|---|---|---|
anet whoami | not logged in / session expired / Hub unreachable | 0 → 1 |
anet network <ls|use|info|create|delete|rename|invite|join|members> | not initialized / not logged in, Hub error, Network not found, unreachable | 0 → 1 |
anet network create|use|delete|rename|join (missing argument), anet network <unknown> | usage error | 0 → 2 |
anet token ls|create|revoke | not logged in, Hub error, unreachable | 0 → 1 |
anet token revoke (no token id) | usage error | 0 → 2 |
anet passwd | not logged in, passwords differ, Hub refused, unreachable | 0 → 1 |
anet activate | not initialized, activation failed, unreachable / no license key | 0 → 1 / 0 → 2 |
anet status, anet tasks | no Hub configured; anet tasks unreachable | 0 → 1 |
anet hub start | Hub did not come up within 15 s (including Bun missing) | 0 → 1 |
anet hub stop | see Stopping the Hub above | 0 → 1 / 2 |
anet hub admin reset-user | no --username / DB not found, reset failed | 0 → 2 / 0 → 1 |
anet hub <unknown>, anet node <unknown>, anet session <unknown>, anet batch <unknown verb> | usage error | 0 → 2 |
anet node resume (no node), anet logs (no node), anet batch <verb> (no prefix) | usage error | 0 → 2 |
anet project down (a node failed to stop), anet node delete (node process refused to exit) | already set exit code 1, but the CLI's final process.exit(0) overrode it | 0 → 1 |
anet upgrade | a package failed to upgrade or its registry lookup failed | 0 → 1 |
anet node delete --force | local files deleted, but the Hub row was not removed (unreachable / refused); prints a --hub-only retry command | new: 1 |
anet create --batch, anet batch cleanup | no Hub, invalid preset/option, auto-login failed, no node created | 0 → 1 / 2 |
anet demo … | no Hub / token / key, creating the Network or nodes failed | 0 → 1 |
Some older usage-error paths still exit 1 (also non-zero, so scripts still catch them); they were not changed just to make them 2. anet hub status is a status query and still exits 0 when the Hub is not running.
Errors and secrets
- On an error anet prints one sentence and the next command to run, without a stack trace. To see the stack:
ANET_DEBUG=1 anet …. - A fatal error whose message is an error code still prints
[anet] FATAL: Error: <CODE>, so scripts can pull the code from logs. - anet never prints a full secret:
anet config,anet config json,anet node startandanet doctorshow tokens asutok_…ab12(prefix…last 4). Tokens,Bearerheaders andtoken=URL parameters inside error messages are masked the same way. anet node create --env KEY=…andanet node migrate-token-to-envrefwrite the secret to.anet/nodes/<node>/.env(mode 600, gitignored) and print only the masked value plus anexportcommand that reads it from that file.- Exceptions:
anet token createandanet hub admin reset-userexist to hand out a new credential and show it once.
Configuration locations and environment variables
| Path | Contents |
|---|---|
~/.anet/config.json | Current Hub, user token, and Network |
.anet/nodes/<node>/config.json | Node configuration |
~/.commhub/commhub.db | Default Hub SQLite database |
~/.anet/server/admin-utok.json | Local administrator recovery token on the Hub host |
Common environment variables:
| Variable | Purpose |
|---|---|
COMMHUB_URL | Hub URL |
COMMHUB_ALIAS | Node alias |
COMMHUB_TOKEN | Authentication token; a token in node configuration takes precedence |
COMMHUB_AUTH_TOKEN | Legacy Hub master-token compatibility path; new deployments use user and node tokens |
ANET_CODEX_RESUME_TIMEOUT_MS | Codex session-resume deadline for anet node start --copresence (whole milliseconds, max 900000); when unset it starts at 300 s and adds 120 s per started GiB of rollout, capped at 15 minutes; timeout errors report elapsed time and rollout size |
Codex co-presence startup creates a private recovery point after quiescing the old processes. A failed backup is fail-closed and its partial directory is removed. Only an operator who explicitly accepts losing safe rollback may pass --skip-recovery-backup; the command prints a prominent warning and does not present an older recovery point as the backup for this launch. | ANTHROPIC_BASE_URL | Anthropic-compatible model endpoint | | ANTHROPIC_AUTH_TOKEN | Credential for third-party Anthropic-compatible endpoints | | ANTHROPIC_API_KEY | Credential for Anthropic's official endpoint |
Use envRef for secrets; do not commit tokens or model keys in configuration. See Security model.