Skip to content
Join the waitlistWaitlist

CLI

Every zunder-guard command and flag, with its environment variable, as the code defines them.

Every refusal exits with status 2 and its reason on standard error.

OptionEnvironmentDefaultMeaning
--home DIRZUNDER_GUARD_HOME~/.zunder-guardGuard’s home: the config, the journals, the key file
--config FILEguard.toml in the homeanother config file
--version, --helpthe version; the help text
CommandWhat it doesWho runs it
zunder-guard init [--interactive | --non-interactive] [--rules zr1_…] [--account 0x…] [--network paper|testnet|mainnet] [--account-network testnet|mainnet] [--confirm-mainnet 0x…] [--equity-cap USDC] [--key-stdin | --no-key] [--listen L] [--ip-share S] [--key-store auto|file|systemd-creds] [--force] [--client-key-out FILE] [--refuse-mainnet REASON]the setup: the rules, the account, the mode, the API wallet key (checked with Hyperliquid and stored), a client key for your bot (shown once), a pairing code, the risk journal for paper or testnet. Interactive on a terminal (the default); --non-interactive takes every answer from the flags and the key from standard input (--key-stdin)a person, or an installer
zunder-guard config get network|mode|account|listen|rulesprints one configured value; network and mode both print the mode (paper, testnet or mainnet), rules prints your rules as a zr1_ codeanyone on the machine
zunder-guard key check --key-stdinreads the key from standard input, checks with Hyperliquid that it is an API wallet of the configured account, prints its address (never the key), and records it as api_wallet when the config names nonea person, or the installer
zunder-guard paira new client key for a bot, shown once, and a new pairing code; a running Guard accepts it after a restarta person
zunder-guard client add --out FILEa new client key written to a new file only its owner can read (mode 0600), never shown; for a bot or an MCP agent that reads its key from a file. A running Guard accepts it after a restarta person
zunder-guard run [--network paper|testnet|mainnet] [--listen L] [--ip-share S] [--key-stdin | --key-file FILE] [--container] [--rules zr1_…] [--account 0x…]runs Guard until stopped (SIGTERM or Ctrl-C)a person or a service
zunder-guard health [--listen L | --url http://host:port]exits 0 when Guard’s GET /healthz answers 200a health check
zunder-guard journal-init --mode paper|testnet|mainnet --note "…"starts a risk journal at the account’s equity now. Never replaces onea person
zunder-guard journal-resume --mode paper|testnet|mainnet --note "…"clears a drawdown halt after a person’s review; the note goes into the journal. Stop Guard firsta person
zunder-guard journal-show --mode paper|testnet|mainnetprints the risk journal’s records, one JSON line each. Reads onlyanyone on the machine
zunder-guard kill --reason "…"pulls the kill switch: writes the kill file; a running Guard opens nothing and flattensanyone on the machine
zunder-guard check-configvalidates the config and prints its rules; reads no key, connects to nothinganyone
zunder-guard mcp [--network paper|testnet|mainnet] [--key-file FILE | --key-stdin] [--guard-url URL] [--confirm-account 0x…] [--kill-file FILE]the MCP server for AI agents over standard input and output, trading only through a Guard on this machinean MCP client

journal-init, journal-resume and journal-show need --mode; journal-init and journal-resume also need --note. A mainnet journal-init or journal-resume needs ZUNDER_MAINNET_CONFIRM too.

  • The network. Without --network (or ZUNDER_GUARD_NETWORK), Guard runs paper mode, and only a paper config starts that way: a testnet or mainnet config refuses rather than quietly running paper. With it, the config’s mode must be the same. So a testnet config starts with zunder-guard run --network testnet, or with ZUNDER_GUARD_NETWORK=testnet set.
  • The key. --key-stdin, or (testnet only) --key-file FILE (ZUNDER_GUARD_KEY_FILE), or else the file api-wallet-key in the home. A key file must be readable by its owner only (mode 0600); in a container (--container, ZUNDER_GUARD_CONTAINER, or Docker’s /.dockerenv) a readable file is accepted with a warning, as long as nobody else can write it. Paper mode reads no key.
  • Mainnet needs all of this at once: a mainnet config with every guard (allow_mainnet = true, the equity cap, the account, the API wallet, no rule looser than the defaults), ZUNDER_MAINNET_CONFIRM set to the account’s address at this start, a risk journal started for mainnet and this account, and the key on standard input only (--key-stdin). Everything but Hyperliquid’s own checks is checked before the key is read. See Paper, testnet and mainnet.
  • An empty home with --rules (ZUNDER_GUARD_RULES) and --account (ZUNDER_GUARD_ACCOUNT) is set up for paper mode first, as init --non-interactive would. Testnet and mainnet need init.
  • The listen address comes from the config (127.0.0.1:8547 by default); --listen (ZUNDER_GUARD_LISTEN) overrides it at start, with a warning for anything but loopback.
  • The IP share comes from the config’s ip_share (1 by default); --ip-share (ZUNDER_GUARD_IP_SHARE) overrides it at start. Several Guards on one machine: 1/N each. A share too small to keep Guard safe is refused.
VariableRead bySame as
ZUNDER_GUARD_HOMEevery command--home
ZUNDER_GUARD_RULESinit, run--rules
ZUNDER_GUARD_ACCOUNTinit, run--account
ZUNDER_GUARD_NETWORKinit, run--network (the mode)
ZUNDER_GUARD_LISTENrun, health--listen
ZUNDER_GUARD_IP_SHAREinit, run--ip-share
ZUNDER_GUARD_KEY_FILErun--key-file
ZUNDER_GUARD_CONTAINERrun--container
ZUNDER_MAINNET_CONFIRMrun, journal-init, journal-resume on mainnetno flag: the person’s confirmation, the account’s address, at every mainnet start (the same variable as Zunder’s own runner)

The other flags have no environment variable. The mcp command reads no environment variable at all: its client key comes from --key-file or --key-stdin only.

There is no status command. A running Guard answers its status on its own address, to anyone on the machine:

Terminal window
curl -s http://127.0.0.1:8547/guard/status

The answer is JSON: the mode, the account, killed (the reason, or null), the risk state (active, halted_for_day or stopped), equity, positions, open orders, alerts, the kill file’s path, your rules as a zr1_ code and the last event. Guard’s decisions, newest last: curl -s 'http://127.0.0.1:8547/guard/events?since=0'. Both are listed in Events.

init, pair, client add, journal-init and journal-resume change what Guard may do. Guard never runs them itself, and neither the MCP server nor any web page can. journal-resume also needs Guard stopped: it opens the risk journal, which a running Guard holds locked.

Terminal window
zunder-guard kill --reason "bot looping on SOL"
curl -s http://127.0.0.1:8547/guard/status
# {"schema":1, … "killed":"bot looping on SOL", … "kill_file":"/home/you/.zunder-guard/kill", …}

The kill switch stays pulled until a person removes the kill file (its path is kill_file in the status) and restarts Guard.

These names appeared in earlier drafts of these docs. They do not exist; use the command on the right.

Not a commandUse instead
zunder-guard statuscurl -s http://127.0.0.1:8547/guard/status
zunder-guard resumezunder-guard journal-resume --mode M --note "…"
zunder-guard journal showzunder-guard journal-show --mode M
zunder-guard rules exportzunder-guard config get rules
zunder-guard-mcp (a separate program)zunder-guard mcp; releases ship one program

This page as plain Markdown, for people and LLMs: /docs/reference/cli.md