Skip to content
Alpha: for testing only; it does not yet meet the Cyber Resilience Act’s requirements. Status and limits →

The vai command

vai is one Go binary for macOS, Linux and Windows, installed with a second name, vaicoa, for scripts and for machines where vai is taken. vai help lists the commands; vai help twin the commands that start with twin.

usage: vai [--hub NAME] [--agent INTEGRATION] [--output text|json] [--passphrase-file FILE] <command> [arguments]
commandwhat it does
vai login / logout / whoamisign in at the portal in the browser for your licence; the token goes to the OS credential store
vai up NAME [--location nbg1] [--server-type T] [--token-file F | --hcloud-context C] [--twins-…] [--plan] [--yes] [--abort]create a hub in your project, pair this device as its owner, activate the licence (resumable)
vai down NAME [--plan] [--confirm NAME]delete a hub and everything of it in the project (refused while it manages a server)
vai upgrade [NAME] --dist DIR [--plan]install another hub release on the hub’s server, data and pairings kept
vai status [NAME]the hub: its licence, servers, twins, open approvals and releases
vai open [PATH]the hub’s portal in the browser, through a local proxy while vai runs
vai hub list / hub current / hub use NAMEthe hubs on this machine, which one a command talks to and why, the default
vai licencethe licence as the hub and the portal see it
commandwhat it does
vai pair INVITEpair this machine with a hub, with an invite (or --listener URL --code CODE)
vai pair invite --role ROLE [--name NAME]an invite for another device (owner): one string, valid 10 minutes
vai devices / devices revoke ID|NAME [--confirm NAME]the hub’s devices; revoke one (owner)
vai agent register INTEGRATIONregister this device’s agent key for an integration (a person, once)
vai agent revoke INTEGRATION [--device ID]revoke an agent key
vai approvals [--all]what agents and capped devices asked a person to approve
vai approve ID [--confirm NAME] / reject IDapprove (a presence device only) or reject one
commandwhat it does
vai host create NAME [--type cpx22] [--env production|staging] [--location L] [--release V]a production or staging Podman host
vai host list / host delete NAME [--confirm NAME]the hosts; delete one and its server (a device below presence asks for an approval)
vai open host NAME / host access NAME [--from ADDR,…] / host access list / host access revokereach a staging host that is not public, as for twins
vai host public NAME [on|off]whether a staging host is public (80/443 from anywhere, its own TLS)
vai host reseed NAME [--copy-from PRODUCTION]reseed a staging host’s data, or fill it with a copy of production
vai env copy PRODUCTION / env copy allow PRODUCTION --reason R [--mask required|waived] / env copy forbid PRODUCTIONwhether twins may hold a copy of a production host’s data (allowing: owner, presence device)
vai release list / release promote VERSION --host NAME [--now]the releases the hub imported; promote one to a host
commandwhat it does
vai twin create --production HOST --task TASK [--branch B] [--ttl H] [--data seed|copy|empty]a twin of a production or staging host
vai twin list [--all] / twin show TWIN / twin evidence TWINthe twins; one twin’s state, data and web access; what the hub knows of it, with its digest
vai twin extend TWIN --hours Hextend a twin once
vai twin reset TWIN / twin reseed TWINback to its checkpoint; run its seed again
vai twin delete TWIN [--confirm TASK]delete a twin and its server
vai open twin TWIN [--ttl 1h]a twin’s page in the browser, through a grant from this device’s address
vai twin access TWIN [--from ADDR,… | --any] [--ttl 1h] / twin access list / twin access revokeweb access links for named addresses
vai twin settings [--type T --location L] [--token-file F | --hcloud-context C]the twins’ Hetzner project (owner)
vai dns status / dns setup --zone ZONE [--base NAME] / dns verifytwin names from a zone in Hetzner’s Cloud DNS (owner)
commandwhat it does
vai api METHOD PATH [JSON|-]one signed request to the hub’s admin API; a path without /api/ gets /api/v1 in front
vai versionthe version

A machine can know several hubs, like kubectl’s contexts. A command talks to one, chosen in this order: --hub NAME, then VAI_HUB, then the default (vai hub use NAME), then the only hub there is. Every command that changes something names the hub first, on stderr (hub: acme).

A coding agent runs vai --agent INTEGRATION …, or has VAI_AGENT=INTEGRATION in its environment. Its requests are signed with the device’s agent key for that integration and held to the agent’s ceiling; the first use registers the key, which asks a person once. With VAI_AGENT set, what only a person may sign is refused before anything is signed: vai open, vai approve, pairing, vai agent register and revoke. See agents and approvals.

Destructive commands take --confirm NAME and never --yes, so an agent can call them without a terminal; the hub still decides.

A device reaches its hub through a WireGuard tunnel inside the vai process. While a long-lived vai runs (vai open, vai open twin), it shares its tunnel with the other vai runs of the same user on a local socket that only that user can use; every request is still signed by its own key, end to end. There is no daemon: nothing listens unless a long-lived vai runs. On Windows each vai opens its own tunnel.

variableeffect
VAI_HUBthe hub a command talks to, after --hub
VAI_AGENTagent mode for that integration, as --agent; vai then never signs with the person’s key
VAI_AGENT_SESSIONthe agent’s session in the audit; default: one per vai run
VAI_HOMEone directory for everything of vai, instead of the system’s default places
VAI_NO_BROWSERset: vai login, vai open and vai open twin print the link instead of opening a browser
VAI_LINK_SHAREoff: this run neither uses nor shares another run’s tunnel
VAI_PASSPHRASE_FILE, VAI_PASSPHRASEthe passphrase of a key file, from a file or the environment (level passphrase-stored)

Before the rename these were PBX_*. The old names are not read; vai moves its old directories once.

Plain lines for people; --output json for scripts; --progress json on long commands.

codemeaning
0ok
1failed
2usage
3the hub answered with a status other than 2xx
4refused by policy: the waitlist, a ceiling, the protection level
5waiting for a person’s approval (202); the approval’s id is printed