wallaby-cli
v0.8.0
Published
Wallaby developer CLI — drive the Wallaby API and SDK from the terminal
Readme
wallaby-cli
The Wallaby developer CLI. Talk to any Wallaby environment from your terminal — no more hand-rolled curl scripts or Postman collections. Create wallets, send transfers, test the full custody recovery path, and point it at any environment with one flag.
It is also built to be driven by AI agents and CI: every command speaks JSON and uses meaningful exit codes (more under For agents & CI).
Install
npm install -g wallaby-cli # puts `wallaby` and `wallaby-mcp` on your PATHRequires Node 20+. macOS keychain is used for secrets out of the box.
Five-minute setup
First generate an API key in the Wallaby portal — the portal mints a signing keypair alongside it, so keep the private key it shows you handy. Then:
wallaby initThe wizard first asks which profile you're setting up — the active one, another existing one, a brand-new one, or a duplicate of an existing profile (the quick way to add a second test user). Then it walks through everything the profile needs:
- API key — paste the one from the portal.
- Your user id (
externalUserId) — accept the generated UUID if you're starting fresh. - Signing keypair — paste the portal's private key (the default), or generate one locally and register the printed public key back in the portal ("update public key"). Either way both halves are stored in your OS keychain, and client JWTs are minted from them on every request — you never handle tokens manually.
- Sign-up / sign-in — the wizard looks your user id up: a new one is created with the wallet password you choose; an existing one signs in with its current password.
Then prove the whole thing end to end:
wallaby flow smoke # read-only sanity checkGreen across the board? You're done. Add --save-password on wallaby auth login (or say
yes in the wizard) and no command will ever prompt you again.
Everyday commands
wallaby whoami # where am I pointed, as whom, and does it all work?
wallaby system chains # what chains does this env support?
wallaby system tokens --search usdc # find a token
wallaby system tokens --page 2 --limit 100 # list endpoints return 50 items/page — page through, or --all for everything
wallaby user wallets # your wallets
wallaby wallet create # new wallet (2-of-3 key shares, keychain-stored)
wallaby wallet balances [walletId] # current balance of every asset (live read)
wallaby wallet assets [walletId] # assets configured on the wallet (with configIds)
wallaby wallet add-token <assetConfigId> --wallet <walletId> # track a new token (see `system tokens`)
wallaby wallet add-token --chain SUI --sui-object-id 0x…::module::COIN --name Coin --symbol COIN --decimals 9 # custom token
wallaby wallet update-asset <configId> --hide --wallet <walletId> # show/hide or rename an asset
wallaby wallet shares # what recovery material does THIS machine hold?
wallaby wallet shares --verify # …and prove it actually decrypts (flags stale material)
wallaby wallet forget [walletId] --force # delete one wallet's material from this device
wallaby wallet export-seed [walletId] # print the decrypted seed phrase (confirms first — it controls the funds)
wallaby wallet import --wallet <walletId> # recover a wallet onto this device from its seed phrase
wallaby wallet change-password # rotate the wallet password (re-wraps this device's material)
wallaby wallet derive --chain AVALANCHE # derive addresses for a pooled custodial wallet (13 per account)
wallaby tx transfer --from 0x… --to 0x… --amount 0.01 --asset-id <id> # --wallet optional
wallaby tx status --hash 0x… --reference <ref> --chain ETHEREUM
wallaby discover # what can I do? every command, plus the API endpoint groups
wallaby discover send # task search: matching commands first, then raw endpoints
wallaby discover create-wallet --schema # …and show the request body shape for `api -d`
wallaby api GET /api/v1/chains # raw escape hatch — any endpoint, gh-api style
pbpaste | wallaby api POST /api/v1/wallets/create-with-shares -d @- # paste/pipe a JSON body
wallaby dd # Datadog logs link for the last command's requests
wallaby dd --last-error --web # …or the last failure, opened in the browserEvery command takes --profile <name> to target another environment and --json for
machine-readable output. --verbose turns on the SDK's internal logging for debugging
(warning: it prints tokens and request payloads — keep it off normally).
Mistype a command and you get a did-you-mean suggestion; wallaby autocomplete prints
setup instructions for shell tab-completion.
Short aliases are wired for the commands you type most: bal (wallet balances),
wb, wa (wallet assets), wallets/uw (user wallets), ua, send
(tx transfer), txs, txst, swap, tokens, chains, who (whoami), disc
(discover). They work anywhere the full command does — wallaby bal --json.
Interactive shell: wallaby shell
For an exploratory, k9s-style session over the whole command surface:
wallaby shell # or: wallaby interactive
wallaby shell --profile dev # pin the environment for the session
wallaby shell --no-fullscreen # render inline instead of taking over the screenBy default it runs fullscreen (the alternate screen buffer, like k9s/vim) — it takes
over the terminal and restores your prompt untouched on exit. Toggle it at runtime with
Ctrl+F, disable it with --no-fullscreen (or WALLABY_SHELL_FULLSCREEN=false).
Instead of one command per process, you get a persistent full-screen UI:
- Fuzzy "even" search — start typing to filter every command;
bal,send, and evenwbalall surfacewallet balances/tx transfer. Aliases show in the list. With an empty query, your recently-run commands come first (remembered across sessions). - Guided input — pick a command and it prompts field-by-field from that
operation's schema (enums become pick-lists, booleans a
y/n), so you never guess flag names. A side panel lists every input with its status (done ✓ / current ❯ / pending) as you go. Wallet-id fields prefill from the profile'sdefaultWalletId. A required field won't advance while empty — it errors in place.Shift+Tabsteps back to a prior field (values preserved); on an optional fieldTabskips all remaining optional fields at once. Inputs are validated against the schema before the call — a bad value shows inline, not after a round-trip. - Live completion — id/token/profile fields offer real values pulled live from
your account (wallet ids, supported tokens, config profiles, recent recipients),
cached for the session.
Ctrl+Rrefreshes them. - A confirmation gate before any state-changing call (transfers, wallet creation),
showing the exact inputs so nothing fund-moving runs by accident.
ebounces back to the form to fix a value. - Switch profiles in-session with
Ctrl+P(or theconfig usecommand /usealias) — type to filter (scales to many profiles),↑/↓to move,⏎to switch. It changes the live session and saves the choice as the default (likeconfig use), so it sticks. - Sign in / out without leaving —
login(masked password,Tabto save it),signup, andlogoutrun in-session.api-keysets the API key on the active profile — paste a key (Tabopens the portal to generate one) and it's saved to your config, session rebuilt so the next call uses it. The remaining setup wizards (init,user new) still point you to the plain command. - Explore the response — in a result,
f(fields) opens a field picker: a checkbox list of the response's attributes (Spaceto toggle,afor all,⏎to apply) with the selection status shown alongside a live preview — pick exactly the fields you want./filters to matching lines, andssearches (highlights matches,n/Nto jump). - A
:command bar (k9s-style) — press:to run any command by name or alias without going back to the palette first::bal,:wallet balances,:config use,:send— with live suggestions as you type.:qreturns to the palette. In a result it also takes the view verbs (:search <term>,:project wallets.*.id,:filter,:copy,:json,:rerun,:edit), so bare keys and:reach the same actions — use whichever you prefer. - Same plumbing as the CLI — results render as the same JSON (
jtoggles the full envelope,ccopies the current view,rre-runs,eedits and re-runs), and every call is recorded forwallaby dd.
Keys:
- Palette — type to filter ·
↑/↓move ·⏎select ·Ctrl+Rrefresh live values ·Ctrl+Pswitch profile ·Ctrl+Ftoggle fullscreen ·Esc/Ctrl+Cquit. - Form —
⏎next ·Shift+Tabprevious field ·Tabskip optional fields ·Esccancel. - Auth (login/signup) — type password (masked) ·
Tabtoggle "save password" ·⏎submit ·Esccancel. - API key — paste the key ·
Tabopen the portal ·⏎save ·Esccancel. - Confirm —
yrun ·eedit ·n/Esccancel. - Result —
↑/↓scroll ·jJSON/human ·ccopy ·fpick fields (palso) ·/filter ·ssearch (n/Njump) ·rre-run ·eedit & re-run ·:command bar ·⏎/Escback. - Field picker —
↑/↓move ·Spacetoggle ·aall/none ·⏎apply ·Esccancel. :command bar (from palette or a result) — type a command/alias or a result verb ·⏎run ·Esccancel ·:qback to palette.
The shell needs a real terminal — it refuses to start when piped or redirected (exit
2), so scripts and CI never hang on it. For automation, keep using the individual
commands with --json. Colors follow the usual conventions — set NO_COLOR (or run in
a non-color terminal) and the UI renders plain.
Some API areas (Merchants, Admin-Transactions, Query) intentionally have no dedicated
commands — reach them with wallaby api. discover marks results that already have a
first-class command, and warns about endpoints that can't be called raw because their
request bodies need material only the SDK's signing session can produce.
The wallet/tx commands above take a wallet id; omit it to use the profile's default
(wallaby config set defaultWalletId <id>, or the WALLABY_WALLET_ID env var).
Server-side debugging: wallaby dd
Every command sends one x-client-request-id (a uuid) on all its API calls and records
them to ~/.config/wallaby/history.jsonl — timestamps, status, duration, and the server's
requestId (no bodies, no credentials). When a command fails, the requestId is printed
right there; wallaby dd then works retroactively:
wallaby dd # request details + Log Explorer link for the last command
wallaby dd --web # …and open it in the browser (-w)
wallaby dd --last-error # the last command with a failed request
wallaby dd --list # recent commands, numbered
wallaby dd 3 # the 3rd most recent
wallaby dd --endpoint /transfers --since 2h # facet search instead of history
wallaby dd --user <externalUserId> --since 1dLinks query @clientRequestId / @requestId in Datadog Log Explorer. Point them at a
different Datadog site with wallaby config set ddSite <host> (or WALLABY_DD_SITE).
Pooled custodial wallets: batch address derivation
A pooled custodial wallet can derive addresses for many accounts off its own seed without persisting any of them — 13 addresses per account index, all inside the enclave:
wallaby wallet derive --chain AVALANCHE # account 1
wallaby wallet derive --chain SOLANA --accounts 1,2,5 # a list
wallaby wallet derive --chain AVALANCHE --accounts 1-76 # 76 accounts = 988 addresses, one request
wallaby wallet derive --chain AVALANCHE --starting-address-index 13 # the next page for the same accountAccount index 0 is the wallet itself, so indexes start at 1; a request carries at most 76 of them.
The endpoint is behind PooledCustodialOnlyGuard and nothing in the API marks a wallet pooled
custodial — that is a manual DB update giving the wallet's wallet_type the pooled_custodial
role. Both commands say so before they run, and a rejection from the guard is reported as that
instruction rather than a bare 403.
To find out how much derivation an environment can take:
wallaby wallet derive-bench --chain AVALANCHE # 10s at concurrency 10
wallaby wallet derive-bench --chain AVALANCHE --duration 60 --concurrency 25
wallaby wallet derive-bench --chain AVALANCHE --accounts-per-request 76 # peak addresses/s
wallaby wallet derive-bench --chain AVALANCHE --ramp # double concurrency until the first 429It signs in once, then loops flat out and reports req/s, accounts/s, addresses/s, p50/p90/p99,
the status histogram and the live x-ratelimit-* window. Two things to read it honestly:
- Every derive is two API calls. The signature message a derive is bound to is single-use, so each request mints a fresh one first. The report splits median latency into the signature-message leg and the derive leg — if the first leg dominates you are measuring this machine's ECIES rewrap and keychain reads, not the server.
- Request history recording is suspended for the run, so a bench does not flood
wallaby dd.
Profiles: environments and test users
A profile = one environment + one set of credentials. All seven deployed environments
are pre-seeded (local, dev-testnet, dev-mainnet, staging-testnet, staging-mainnet,
testnet, mainnet); dev-testnet starts active.
wallaby config # list profiles (active one starred)
wallaby config use staging-testnet # switch the default
wallaby config rename dev-testnet josh-dev
wallaby config remove old-profile --forceNeed a second test user? One command — it clones the active profile (the signing keypair is bound to your API key, not the user), generates a fresh identity, signs it up, and switches to it:
wallaby user new alice --save-password # clone + signup + switch, done
wallaby wallet create # you're alice now
wallaby whoami # …see?--from <profile> clones a different source, --no-use keeps your current profile active.
(The longhand still works: config clone src dest --generate-user then
auth login --signup --profile dest.)
Switching between users is just --profile alice on any command, or config use alice to
make it sticky. wallaby whoami always tells you where you stand: profile, environment,
user, credential and session state, and what this machine holds.
Config lives at ~/.config/wallaby/config.json (no secrets except the API key);
field resolution is always flag > environment variable > config file.
Flows: one-command end-to-end tests
wallaby flow # list scenarios
wallaby flow smoke # read-only sweep: reachability, chains, user, wallets
wallaby flow wallet-lifecycle # sign in → create wallet → verify listed → balances
wallaby flow recovery-roundtrip # the big one — see below
wallaby flow <name> --sandbox # same flow, throwaway user — your account stays cleanWallet-creating flows permanently add wallets to whatever account they run against
(there is no server-side wallet delete). Run them bare when you mean to test your own
account; otherwise add --sandbox: the CLI mints credentials for a fresh throwaway user
(same API key and signing key — both are key-bound, not user-bound), signs it up as an
extra first step in the report, and runs the flow as that user with a generated password
(no prompt, --password ignored). Everything the run stores — device identity, session,
key shares — lives in process memory only, so nothing lands in your keychain and nothing
outlives the run. The report names the throwaway externalUserId if you ever want to
inspect what it created.
recovery-roundtrip proves the custody story end to end: it creates a throwaway wallet,
exports the seed phrase (forcing share + MPC reconstruction, since no seed is stored),
fully wipes this device's state, signs back in as a new device, re-imports from the
seed, and verifies the recovered wallet has the identical id and addresses. Decrypted
material never leaves flow memory. If it fails between the wipe and the import, run
wallaby auth login followed by wallaby wallet import --wallet <id> to finish the
recovery by hand — the wallet stays recoverable server-side.
The same recovery path is available as standalone commands for real wallets:
wallet export-seed reconstructs and prints the seed phrase (share + MPC, confirms
before printing), and wallet import --wallet <id> re-attaches a wallet to this
device from its seed — after a device wipe, a stale share (wallet shares --verify
flags those), or on a brand-new machine.
Each step reports pass/fail, timing, and a structured error naming exactly what diverged.
A failed flow exits 1. New scenarios are a single file in src/flows/.
For agents & CI
Every command supports --json and emits one stable envelope:
{ "ok": true, "command": "api", "profile": "dev-testnet",
"baseUrl": "https://dev-testnet.wallaby.cash/api/v1", "durationMs": 312, "data": { } }On failure, ok is false and error: { status, errorCode, message } is present.
Exit codes: 0 success, 1 request/API failure, 2 usage or config error, 127 unknown
command (with a did-you-mean suggestion on a TTY). Warnings go to stderr; stdout is always
clean JSON under --json.
Envelopes also echo the API's rate-limit state (rateLimit: { limit, remaining, resetAt }
from the x-ratelimit-* response headers) so scripted consumers can self-throttle; a 429
fails with rate limited — resets in ~Ns, and flows wait out the reset and retry the
rate-limited step instead of dying mid-scenario.
Headless configuration (CI, containers):
| Setting | Env var |
|---|---|
| profile | WALLABY_PROFILE |
| base URL | WALLABY_BASE_URL |
| API key | WALLABY_API_KEY |
| user id | WALLABY_EXTERNAL_USER_ID |
| wallet password | WALLABY_PASSWORD |
| new password (wallet change-password) | WALLABY_NEW_PASSWORD |
| seed phrase (wallet import) | WALLABY_SEED_PHRASE |
| secret store | WALLABY_STORE=file + WALLABY_PASSPHRASE=… |
With WALLABY_STORE=file, secrets live in an encrypted file (scrypt + AES-256-GCM) instead
of the OS keychain, so no interactive keychain access is needed.
MCP server
The same operations are exposed to AI agents over the Model Context Protocol.
The package ships a second binary, wallaby-mcp, that serves every operation as an MCP tool
over stdio (system_chains, wallet_balances, tx_transfer, swap_quote, api_request,
flow_run, … — the CLI command paths in snake_case). CLI and MCP share one core logic
layer (src/core/), so a tool returns the identical JSON envelope its --json command does.
To find a capability instead of guessing, call the discover tool — it searches the
first-class tools and a catalog of every Wallaby API endpoint (method, path, summary)
for use with api_request, parsed from the installed SDK so it never drifts. Endpoint
results say when a dedicated tool covers them (coveredBy) or when a raw call can't work
because the request needs SDK-signed material (clientCrypto). The server also exposes read-only resources:
wallaby://docs/agents (the agent guide), wallaby://docs/readme, and
wallaby://api/endpoints (the full catalog as JSON).
Installing the package puts both wallaby and wallaby-mcp on your PATH —
no separate install needed.
Automated (recommended)
wallaby init asks at the end whether to register with Claude Code and/or the Desktop app.
Or run it standalone at any time:
wallaby mcp install # prompts for Claude Code and Claude Desktop in one goClaude Code (CLI) — manual
claude mcp add wallaby -- wallaby-mcpClaude Desktop app — manual
Open ~/Library/Application Support/Claude/claude_desktop_config.json (create it if it doesn't
exist) and add the mcpServers block:
{
"mcpServers": {
"wallaby": {
"command": "wallaby-mcp"
}
}
}Restart Claude Desktop after saving. To target a specific environment, add an env key:
{
"mcpServers": {
"wallaby": {
"command": "wallaby-mcp",
"env": { "WALLABY_PROFILE": "dev-testnet" }
}
}
}The server has no TTY, so it never prompts. It reads the active profile from
WALLABY_PROFILE / WALLABY_BASE_URL (falling back to the config file's active profile),
and resolves the wallet password — needed by wallet_create, tx_transfer,
wallet_export_seed, wallet_import, and wallet_shares --verify — from
WALLABY_PASSWORD or the keychain-saved password only (never as a tool argument;
likewise wallet_change_password reads the new password from WALLABY_NEW_PASSWORD
only). If it's missing, the tool returns an actionable error instead of hanging. Read-only tools (chains, balances, quotes, status, whoami, …) need only the
profile credentials. Tools are annotated with readOnlyHint / destructiveHint so clients
can gate the fund-moving ones.
⚠️ The full surface is exposed, including
tx_transfer,wallet_create, andwallet_export_seed(which returns decrypted seed phrases). Point the server at a non-production profile when wiring it to an agent.
How secrets are handled
Everything sensitive lives in your OS keychain (service wallaby-cli):
- the client-JWT signing keypair (
keys init/ theinitwizard) — short-lived RS256 tokens are minted from it per command; the private key never leaves the keychain - the SDK's device key and encrypted wallet shares (created by login / wallet create)
- your wallet password, only if you opt in with
auth login --save-password(convenient on a personal machine; skip it on shared ones —auth logoutdeletes it)
On macOS the keychain is driven through Apple's /usr/bin/security (the same approach
gh and git use), so entries stay silently readable across node upgrades. If macOS ever
starts password-prompting on wallaby's keychain entries — typical for entries created by
an older CLI version or a different node binary — run wallaby auth repair once and
click Allow on each prompt; it rewrites every entry so nothing prompts again.
The plaintext config file holds only the API key and non-secret settings. The SDK's
internal logging is silenced by default because its debug output can include secrets;
--verbose re-enables it deliberately.
Development
git clone [email protected]:WALLABY-WaaS/wallaby-cli.git
cd wallaby-cli && npm install
npm run dev -- <command> # run from src/ without rebuilding (ts-node)
npm run build # compile to dist/
npm run typecheck
npm test # unit tests (node:test) + a headless TUI flow testnpm run dev -- <command> runs straight from src/ — no npm link needed. If you do want
the built wallaby/wallaby-mcp binaries on your PATH from a local checkout, npm link
after a build.
The package has one core logic layer and two entry points:
src/core/— framework-agnostic operations (operations/<topic>.ts) plus the execution context. Each operation is a pure function(ctx, input) => datawith a zod input schema; the registry insrc/core/operations/index.tsis the source of truth the MCP server reads.src/cli/— the oclif CLI. Commands insrc/cli/commands/<topic>/are thin wrappers that map flags to a core operation and format its result. oclif discovers them from the build output.src/mcp/— the MCP server (server.ts), launched bybin/mcp.js.src/cli/tui/+src/core/shell/— the interactive shell (wallaby shell). The Ink UI (tui/app.ts) is a third consumer of the operations registry;core/shell/holds the framework-agnostic pieces (fuzzycatalog, zod-shape → promptinput, live-valueresources,defaults/validate,history,aliases,actions, jq-liteproject). Ink is ESM-only and loaded via dynamicimport()fromcommands/shell.ts.src/lib/— shared low-level plumbing (config, client, secret store, output envelope); e2e scenarios live insrc/flows/.
Add a capability by writing the operation in src/core/operations/, registering it in
index.ts (which exposes it over MCP), and dropping a thin CLI command in the matching
src/cli/commands/ topic folder.
