Commands and local state
Applies to: v0.4.0. This is a map to the real command help, not a substitute for the parser in your installed version.
Find the right help
vox --version
vox --help
vox node --help
vox room --help
vox room join --help
vox trust --help
vox service --help
vox agent --help
vox man
vox man prints a man page generated from the same command definitions as the CLI. Use a
subcommand's --help when checking a particular argument; an example from a different release
is not an alias for your parser.
| Intent | Entry point | Prerequisite |
|---|---|---|
| Make, attach, list or detach a node | vox node create, attach, list, detach |
Node passphrase, if it has one |
| Interactive client | vox or vox tui |
Terminal; a client of the daemon |
| Run the daemon in a terminal | vox daemon |
Optional: attaching a node starts one |
| Run an anchor | vox node with no subcommand |
A headless node; reachable infrastructure |
| Ask a node about rooms | vox room list, read, roster |
The node attached |
| Inspect runtime | vox status, vox status --json |
The node attached |
| Change peer trust | vox trust add, rename, remove |
Compared fingerprint; passphrase after 30 minutes (vox status shows the minutes left) |
| List peer trust | vox trust list |
The node attached |
| Room lifecycle | vox room retention, admin, leave, end |
Creator or admin for the room-wide ones |
| Share a port in a new room | vox serve NAME=PORT |
Existing local service |
| Join a service room | vox connect ROOM_LINK |
Room link and passphrase |
| Reach services | the daemon's proxy (vox up says where), vox forward SERVICE.NODE.ROOM.vox |
Host's trust; a node attached, or the forward running |
| Close live tunnels | vox tunnel close |
vox status lists them |
| Share and pull files | vox share, vox share list, vox share stop, vox room get |
Attached node; the sharer's daemon serving it |
| A room's family LAN | vox lan up |
sudo vox lan helper running |
| Wire a harness | vox agent plugin, skill, trust, doctor |
--node for plugin and hook |
Select the node
A node is chosen per command, in this order: --node NAME, then VOX_NODE, then the only
attached node, then the only node on disk. When none of these settles it, the command refuses
and lists the nodes; that is not a corrupt node. Name the node explicitly in scripts and agent
integrations, so nothing acts as your person's node by accident.
Session-holding commands (serve, connect, up, forward, lan up) and vox node attach
start the daemon in the background when none runs and attach their node. One-shot commands
(room, status, trust, share, service) only ask an attached node, and say so when it is
not: vox node attach NAME first.
A vox daemon started while the daemon holding the data root is stopping says so, waits until it
has stopped, and then serves its node itself. vox tui exits when its terminal goes away, and
SIGHUP or SIGTERM stop it cleanly.
Data/config selection follows explicit flags, then VOX_DATA_DIR / VOX_CONFIG_DIR, then
XDG/platform defaults. Each data root has its own daemon and nodes, so two shells with different
roots see different nodes even when both say --node robertgpt. A command may create
directories while resolving paths; do not treat a guessed node name as a harmless diagnostic
probe.
Local state
On Linux the usual data root is ~/.local/share/vox/, unless XDG or Vox overrides select
another. On macOS the default root is ~/Library/Application Support/vox/. Config uses the
corresponding XDG/platform config location; it is not necessarily the same root as data on Linux.
Inside the data root:
nodes/NAME/holds one node: identity material such asvault.cbor, the room storestore.redb, and that node's ownconfig/, cursors and agent sessions.files/ROOM_ID/holds the shares it pulled (see Send and receive files), anddecisions/its decision record (below)..daemon/holds the daemon's lock, its control socketvox.sock, the port it reuses, the list of nodes kept attached (attach), itsconfig, andlog, where a daemon started in the background writes its output.
The decision record
vox status lists the most recent refusals near its top, newest first:
recent refusals
4s ago refused 34rtzgeq333h to join a room: answering 34rtzgeq333h: join proof-of-possession failed
In the TUI, d on the room list, or :decisions, shows the whole record, newest first, titled
Decisions (newest first · kept 14 days · Esc: back). (vox status also begins with this node's
own fingerprint, in groups beside its art.)
Each node writes down every refusal and every change of access it decides: a join or a tunnel it
refused, a session it cut, a node added to or removed from its keyring, a share it stopped. Each
decision says what was decided, what was asked, who it was about (your name for them, or the
start of their fingerprint), when, and why, in the node's own words, for example join proof-of-possession failed for a wrong room passphrase. It never holds message text, a file's
name or contents, a passphrase or a key. A refusal that can repeat many times a minute, such as a
refused stream, is written the first time; its repeats in the next hour are counted and written
as one.
The record is sealed at rest under the node's identity, in
nodes/NAME/decisions/YYYY-MM-DD.sealed, one file per UTC day, readable by your account only, and
it is read only through the node: in the TUI with d, in the app's Decision record view, and its
latest refusals in vox status. Files older than 14 days are deleted, and the record is never
sent anywhere. A record an earlier build wrote in plain text (.jsonl) is sealed into its day's
file, and the plain file removed, the next time the node is attached.
These are not caches to remove when a join is refused. The source creates private directories/files on supported Unix systems; still protect the account and machine that can use them. Never attach a state directory, vault, passphrase file or unreviewed config to a bug report.
Passphrase input
The identity passphrase protects a node's key material. Every node has one: node create refuses
an empty one and creates nothing. A room passphrase is a separate join factor, and it may be
empty. At a terminal, use the masked prompt. For an unattended
command, use its supported passphrase-file option and restrict the file to the intended OS user.
node create and node attach read the identity passphrase from --passphrase-file; room create and room join read the room passphrase from --passphrase-file, where - selects
stdin. Without a terminal or that explicit input, they fail rather than wait on an unattended
prompt. A keyring change (vox trust add, remove, rename, drive, read) takes the identity
passphrase only typed at a terminal, never from a file or the environment; a room's retention or
name asks for none (see when the passphrase is asked for).
To keep a node attached across daemon restarts:
vox node attach robertgpt --keep --passphrase-file PATH_TO_PRIVATE_FILE
The daemon then reads that file at each start. A foreground daemon also takes a passphrase file with the identity passphrase on its first line. For explicit room selection, each additional room line can be the room ID, one space, and that room's passphrase. The named-room form splits at the first space; later spaces belong to the room passphrase. The parser also tries a whole line as a passphrase for closed rooms before the named form. A schematic file, not literal secrets:
IDENTITY_PASSPHRASE
ROOM_ID ROOM_PASSPHRASE
ANOTHER_ROOM_ID ANOTHER_ROOM_PASSPHRASE
Use vox daemon --node robertgpt --passphrase-file PATH_TO_PRIVATE_FILE with the intended file,
owned by your user and readable only by that user. Store it outside shared repositories; do not
create it by typing secrets into a shell command that remains in history. This format is
source-reviewed in the daemon parser, not exercised by the manual's command check.
--identity-passphrase and room --passphrase are intentionally refused: process arguments
and shell history expose secrets. VOX_ROOM_PASSPHRASE is also refused.
VOX_IDENTITY_PASSPHRASE is supported for attaching a node, never for a keyring change, but an
environment can be read by same-user processes and inherited by children. A supported mechanism is not a promise that it is equally private.
Do not write a real passphrase into a documentation example, paste it to a model, or capture it in a screenshot. An empty room passphrase leaves the room's link as its only join factor; an example should not silently opt you into that choice.
Output, cursors and status
Room/member selectors can accept unambiguous prefixes where help says so. A --since cursor
uses the full entry hash returned by a successful read; do not shorten it. --json selects
structured output for commands that advertise it; it is not a universal top-level switch.
A nonzero exit status means inspect the command's explanation. Coordination commands have the specific claim status meanings. A hook's zero exit is deliberately not a delivery assertion.
vox status shows the node's rooms with each member's trust, connection and last sync, its
peers with their path (direct or relayed) and round-trip time, its tunnels, and anything that
needs attention.
Its gateway section says, for IPv4 and IPv6, which router the machine's default route names,
which routers the daemon asked for a port mapping, and which method answered: PCP, NAT-PMP,
UPnP-IGD or an IPv6 pinhole. For example:
gateway
ipv4 next hop 192.168.1.1 via en0
asked 192.168.1.1:5351, 192.0.0.9:5351: PCP answered at 192.168.1.1:5351
ipv6 no default route
no gateway asked
asked …: none answered means no router granted a mapping; no gateway asked means there was
none to ask. vox status --json carries the same under gateway.ipv4 and gateway.ipv6
(next_hop, asked, and answered with the method as rung, the outside address as
external and the mapping's lifetime in seconds), and the machine's last network change under
network_changed. The daemon renews a mapping before it lapses and deletes every mapping it holds
when it stops; its log names each one, for example vox daemon: deleted the port mapping UDP 53277 at 192.168.1.1:5351 (PCP). When gathering support evidence, use the smallest relevant status excerpt.
Paths, aliases, peer addresses, session IDs and even public fingerprints can expose private
relationships.
Source: CLI parser, node selection, path resolution and the daemon and its files. The daemon parser defines the passphrase-file lines.