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.
Global options
Section titled “Global options”| Option | Environment | Default | Meaning |
|---|---|---|---|
--home DIR | ZUNDER_GUARD_HOME | ~/.zunder-guard | Guard’s home: the config, the journals, the key file |
--config FILE | guard.toml in the home | another config file | |
--version, --help | the version; the help text |
Commands
Section titled “Commands”| Command | What it does | Who 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|rules | prints one configured value; network and mode both print the mode (paper, testnet or mainnet), rules prints your rules as a zr1_ code | anyone on the machine |
zunder-guard key check --key-stdin | reads 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 none | a person, or the installer |
zunder-guard pair | a new client key for a bot, shown once, and a new pairing code; a running Guard accepts it after a restart | a person |
zunder-guard client add --out FILE | a 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 restart | a 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 200 | a health check |
zunder-guard journal-init --mode paper|testnet|mainnet --note "…" | starts a risk journal at the account’s equity now. Never replaces one | a 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 first | a person |
zunder-guard journal-show --mode paper|testnet|mainnet | prints the risk journal’s records, one JSON line each. Reads only | anyone on the machine |
zunder-guard kill --reason "…" | pulls the kill switch: writes the kill file; a running Guard opens nothing and flattens | anyone on the machine |
zunder-guard check-config | validates the config and prints its rules; reads no key, connects to nothing | anyone |
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 machine | an 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.
run, in detail
Section titled “run, in detail”- The network. Without
--network(orZUNDER_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 withzunder-guard run --network testnet, or withZUNDER_GUARD_NETWORK=testnetset. - The key.
--key-stdin, or (testnet only)--key-file FILE(ZUNDER_GUARD_KEY_FILE), or else the fileapi-wallet-keyin 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_CONFIRMset 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, asinit --non-interactivewould. Testnet and mainnet needinit. - The listen address comes from the config (
127.0.0.1:8547by 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(1by default);--ip-share(ZUNDER_GUARD_IP_SHARE) overrides it at start. Several Guards on one machine:1/Neach. A share too small to keep Guard safe is refused.
Environment variables
Section titled “Environment variables”| Variable | Read by | Same as |
|---|---|---|
ZUNDER_GUARD_HOME | every command | --home |
ZUNDER_GUARD_RULES | init, run | --rules |
ZUNDER_GUARD_ACCOUNT | init, run | --account |
ZUNDER_GUARD_NETWORK | init, run | --network (the mode) |
ZUNDER_GUARD_LISTEN | run, health | --listen |
ZUNDER_GUARD_IP_SHARE | init, run | --ip-share |
ZUNDER_GUARD_KEY_FILE | run | --key-file |
ZUNDER_GUARD_CONTAINER | run | --container |
ZUNDER_MAINNET_CONFIRM | run, journal-init, journal-resume on mainnet | no 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.
Guard’s status
Section titled “Guard’s status”There is no status command. A running Guard answers its status on its own address, to anyone on the machine:
curl -s http://127.0.0.1:8547/guard/statusThe 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.
Commands only a person runs
Section titled “Commands only a person runs”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.
Example
Section titled “Example”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.
Not in Guard
Section titled “Not in Guard”These names appeared in earlier drafts of these docs. They do not exist; use the command on the right.
| Not a command | Use instead |
|---|---|
zunder-guard status | curl -s http://127.0.0.1:8547/guard/status |
zunder-guard resume | zunder-guard journal-resume --mode M --note "…" |
zunder-guard journal show | zunder-guard journal-show --mode M |
zunder-guard rules export | zunder-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