@candledottv/cli 0.13.20 → 0.13.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +94 -4
  2. package/dist/index.js +43185 -42562
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -111,7 +111,7 @@ chosen words from your saved copy. Clear your terminal's scrollback too: the CLI
111
111
  | `candle keys signer approve <code> --key <prefix\|label> [--reject]`, `candle keys signer move <prefix\|label> [--to-key <prefix\|label>]` | Key signers for bots on a different machine from the vault. `approve` accepts (or rejects) a key's signer request with the device token, after you type the full fingerprint the trading machine printed. `move`, on the machine holding a wallet's current signer, moves every wallet on the key onto its active signer; `--to-key` moves a revoked key's wallets to a live key. |
112
112
  | `candle keys stop <prefix>` | Pause one key using the device token. Its requests are refused with `PROFILE_PAUSED` until resumed; the key and its local credential are kept. Repeating the command succeeds. |
113
113
  | `candle keys resume <prefix>` | Resume a stopped key using the device token. Repeating the command succeeds. An API key cannot stop or resume itself; a revoked key cannot be resumed. |
114
- | `candle keys rotate <prefix\|label> ([--grace <duration> \| --now] [--force] [--yes] [--idempotency-key <key>] \| --finish)` | Gives a key a new secret and keeps its prefix, label, access, limits, wallets and history. The old secret keeps working through `--grace` (default `1h`, at most `24h`) or stops at once with `--now`; `--finish` ends an open window early. When the key is the CLI's own, the new secret replaces it in the keychain. A copy in `CANDLE_API_KEY` or anywhere else must be updated by hand before the window ends; `candle doctor` warns while the old secret is in use. Each run sends an `Idempotency-Key` and retries a dropped connection once; if there is still no answer, rerun with the printed `--idempotency-key` to recover the first secret. |
114
+ | `candle keys rotate <prefix\|label> ([--grace <duration> \| --now] [--force] [--yes] [--idempotency-key <key>] \| --finish)` | Gives a key a new secret and keeps its prefix, label, access, limits, wallets and history. The old secret keeps working through `--grace` (default `1h`, at most `24h`) or stops at once with `--now`; `--finish` ends an open window early. When the key is the CLI's own, the new secret replaces it in the keychain. A copy in `CANDLE_API_KEY_FILE`, `CANDLE_API_KEY` or anywhere else must be updated by hand before the window ends; `candle doctor` warns while the old secret is in use. Each run sends an `Idempotency-Key` and retries a dropped connection once; if there is still no answer, rerun with the printed `--idempotency-key` to recover the first secret. |
115
115
  | `candle keys revoke <prefix>` | Revokes an API key by prefix. Revoking the CLI's own stored key also clears it locally. A prefix can start with `-`; the key commands accept it as typed, and `--` (`candle keys revoke -- -QVeHyie`) ends the options on any command. |
116
116
  | `candle keys wallets <prefix>`, `keys wallets set <prefix> --wallets <id,id>`, `keys wallets scope <prefix> --scope all\|selected` | Each key is an agent profile with its own wallet set: list it, replace it, or limit the profile to its assigned wallets. |
117
117
  | `candle wallets` (alias `candle wallet`) | Shows the account's embedded (launch) wallets and any linked wallets, using the API key, with a `Signer` column saying whether this machine holds each linked wallet's signing key and a `Trusted` column saying whether it is yours (linked while signed in, or marked with `trust`). It links and revokes; it never generates or prints a key, which the vault now derives. |
@@ -139,6 +139,7 @@ chosen words from your saved copy. Clear your terminal's scrollback too: the CLI
139
139
  | `candle tee new\|enable` | `new` adds a key to an existing `tee-wallets.enc` and refuses when that store is absent (`vault new-key --chain solana`, then `vault promote --from <label> --to-key <prefix>`). `--factor` does not apply. `enable <address> --vault <address>` delegates an existing key with a pinned sweep vault. `vault import-legacy --tee` migrates an existing store. |
140
140
  | `candle swap <from> <to> --amount <n>\|--percent <n> --wallet <tee> [--to <tee>] [--wait] [--dry-run]` | Quote, confirm and swap through the TEE wallet's bound key; the first buy after a launch is this command, unless `candle launch --buy` made it. The assets decide the chain (ETH, USDG or a 0x token is Hood; SOL, USDC, CNDL or a mint is Solana). SOL or USDC to ETH or USDG, or back, is a bridge through Relay into this key's own TEE wallet on the other chain (`--to` names it when there are several); `--wait` follows it for up to ten minutes. With `--json`, stdout is exactly one JSON value for swaps, token trades, replays and `swap status`: `kind`, `status`, `clientTradeId`, `signature` (the primary transaction, or `null`), `signatures` (a list) and `raw` (the original response). Every human line is on stderr. The keys older versions printed stay beside them. `--dry-run` (Solana TEE payer only) checks the wallet, builds the quote and checks its spend, then stops: it never signs, sends, prompts or saves an operation, refuses `--yes`, `--percent` and `--client-trade-id`, and refuses embedded, Hood and bridge payers. With `--json` stdout is the per-check results and the quote. |
141
141
  | `candle swap status <id> [--kind trade\|swap\|launch] [--wait]` | Read an operation without resending it; `--wait` follows a bridge until it ends. |
142
+ | `candle swap receipt <hash>` / `candle swap receipt --client-swap-id <id>` | Re-read a one-shot swap's settlement from chain (a swap this key made through MCP or the SDK, not a `candle swap`); reads only, never resends. The `--client-swap-id` form reads the swap's row first and its receipt only when the row is `confirmed`. With `--json`, stdout is the hash form's `{ success, settlement }` or the id form's object with `receipt` (`read`, `no_hash`, `not_written`, `not_found`) and `next`. A human line on stderr says `settled <amount> <asset>` (the hash form prints raw units), or `Settlement: <state>` with `(refunded)`, `(refunded, Relay fallback)` or `(Relay failure)` when Relay failed the bridge. Exit 0 on any read answer, 1 on an API error (a hash this key did not broadcast is `RECEIPT_NOT_FOUND`), 2 on usage. Poll no faster than every 15 seconds. |
142
143
  | `candle transfer --to <address\|wallet name\|allowlist label\|vault> --asset <SOL\|USDC\|CNDL\|ETH\|USDG>\|--mint <mint>\|--token <0x...> --amount <decimal\|max> [--wallet <tee>] [--client-id <id>] [--rpc-url <url>] [--yes]` | Move funds out of a TEE wallet through its bound Withdraw key, to the wallet's own pinned vault (`--to vault`, any token, `max` allowed, one SOL transfer at most the server's backstop ceiling, 50 SOL by default) or to another of the account's wallets you linked while signed in or marked trusted (a base asset, counted against the key's caps, which must include a per-asset cap and a USD `--tx-limit`). Where the server has `TEE_WITHDRAWALS_ENABLED` on (it ships off), also to an address on the account's withdrawal allowlist, named by its label, id or address, with the same base-asset rule. The asset decides the chain: ETH, USDG or `--token` moves from a Hood TEE wallet. The destination and its kind are shown before you confirm; Candle builds, the relay signs, Candle broadcasts. `--client-id <id>` (1 to 128 of letters, digits, `.` `_` `:` `-`) names the transfer yourself: reusing an id for a transfer already built or sent is `IDEMPOTENCY_CONFLICT` naming that transfer, so a retry never sends twice. |
143
144
  | `candle transfer status <transfer id\|client id> [--json]` | Where a transfer stands, by the id the build returned or the `--client-id` it was sent under: `built` (signable until `expiresAt`), `expired`, `submitted` with its `signature`, `unconfirmed` (may still land; check the chain or `candle activity` before using a new client id), or `failed`. Candle keeps the answer for 24 hours; an older transfer is in `candle activity`. An unknown id is `JOB_NOT_FOUND`. |
144
145
  | `candle launch --name <name> --symbol <symbol> --image <path>\|--image-url <url> --wallet <tee>\|embedded [--banner <file\|URL>] [--description <text>] [--website <url>] [--x <url>] [--telegram <url>] [--discord <url>] [--buy <amount>] [--quote-asset <asset>] [--dex-version v3\|v4] [--mode <mode>] [--curve-v <V> --curve-g <G>] [--creator-fee-bps 0\|100\|200] [--staker-allocation-bps <bps>] [--visibility production\|test\|local\|hidden] [--pool-fee 25\|50\|100] [--pool-fee-from <bps> --pool-fee-minutes <min>] [--pool-open legacy\|curve_price] [--exp-pool-shape floor\|full_range] [--fee-wallet <address>\|--fee-wallet-multisig <address> [--fee-wallet-multisig-vault-index <n>]] [--require-fee-wallet] [--open-in <duration>] [--max-buy-per-wallet <amount> --cap-window <duration>] [--allowlist <path> --allowlist-window <duration>] [--dev-buy-max <amount>] [--dry-run] [--check] [--client-trade-id <id>] [--yes]` | Create a token from the named wallet, with an optional first buy. `--wallet embedded` (or `main`) uses `/launch/headless` on Solana or Hood, with or without `--buy`; every plan can use it with `launch:write` and the key's embedded-wallet allowance, without `swap:write`. It previews through `/launch/headless/dry-run`, shows the quote for confirmation, then sends one headless request. A Solana buy is a creator bundle signed by Candle, preserving `bundleId` in the result; a Hood buy is best effort after creation. The server's slippage floor applies; there is no separate CLI build-floor comparison. TEE wallets use `/launch/self` and need Pro or Max, `launch:write`, the bound key and verified signing authority; a server with `TEE_LAUNCH_BY_SCOPE_ENABLED` off also asks for the owner's `allowLaunch` (deprecated). A TEE first buy needs `TEE_LAUNCH_BUYS_ENABLED` (off by default), and with `TEE_LAUNCH_BY_SCOPE_ENABLED` on it also needs `swap:write` (a key without it gets `SCOPE_MISSING`); Solana creator buys also need `AGENT_TRADING_ENABLED` and a configured Jito engine. No gate or plan default changes. `--buy` is a decimal in whole quote units (SOL/USDC/CNDL on Solana, ETH on Hood; USDG has no first buy); zero or empty means no buy. A Hood launch needs `--dex-version v3\|v4`. `--description` and the four absolute http(s) socials flags travel with every preview/build: `--x` maps to `socials.twitter`. `--staker-allocation-bps` accepts 50–1000; `--visibility` is production, test, local or hidden (test keys need test or hidden). `--mode exp-custom` needs `--curve-v` and `--curve-g` in whole quote units; ranges and common help come from the shared launch definition and appear in `candle launch --help`. `--pool-fee` sets the Solana graduated pool fee (default 50 bps); `--pool-fee-from` and `--pool-fee-minutes` add a fall to that rate. Preset `--pool-open curve_price` opens at the curve's last price and burns the rest; omitted it applies when allocations fit `graduation.maxStakerCarveBps`, otherwise falls back to legacy. Exp `--exp-pool-shape` is floor or full_range. `--image-url` is https; `--image` uploads a local PNG/JPEG/GIF/WebP up to 10MB. `--banner` takes a wide local file or https URL. Uploads are public and permanent before confirmation, even on a dry run or decline. `--dry-run` stops at the preview without claiming the launch id. Reuse `--client-trade-id` for recovery, and always pass `--json` in scripts. `--check` answers which launch path works before any of it, in one call (`GET /api/v1/launch/preflight`): the key's tier and embedded-wallet allowance, each bound TEE wallet's allowLaunch (served `true` where the server has `TEE_LAUNCH_BY_SCOPE_ENABLED` on) and verified-active state, and the server's `TEE_LAUNCH_BY_SCOPE_ENABLED`, `TEE_LAUNCH_BUYS_ENABLED` and `AGENT_TRADING_ENABLED` switches. No name, symbol or image is needed, nothing is uploaded or sent, `--wallet <name>` narrows the answer to one wallet, and it exits 1 when no launch path works. On Solana, where the server has `CURVE_LAUNCH_RULES_ENABLED` on, launch rules are fixed at launch and enforced by the curve program: `--open-in` delays the open (until then only the launching wallet may buy, up to `--dev-buy-max`, which defaults to `--buy`), `--max-buy-per-wallet` with `--cap-window` caps what each wallet spends on the curve after the open, and `--allowlist <path>` (one address per line, or comma separated, at most 4,096) with `--allowlist-window` lets only the listed wallets buy. Durations are seconds or `90m`, `6h`, `2d` (the open at most 7 days away, each window at most 24 hours); amounts are whole quote units. The CLI checks each value before any request with the API's bounds, sends the request's nested `launchRules` in raw units, and the preview prints a Launch rules block from the API's answer. Nobody can change the rules after the launch. |
@@ -147,6 +148,7 @@ chosen words from your saved copy. Clear your terminal's scrollback too: the CLI
147
148
  | `candle pnl [--profile <name>]` | P&L: realized net, fees, unrealized and total, then open positions, the same figures the console shows. Without `--profile`, the account's books (every profile, the web app and the CLI, one ledger; needs the Read scope), with a By agent table (each key, then Manual, then the account total). With `--profile <name>`, that profile's key's share of the same P&L: realized on its sales and the open positions that belong to it, with a row per wallet that sums to the total. Each wallet is its own average-cost pool, and a recorded move between the account's wallets carries its cost. Both show a Solana block, a Hood block, then the combined figure, the closed positions as money made, minus money lost, plus partial sells, and a `CHAIN` column (and `WALLET`, where the API names it) on positions. Unpriced positions are never valued at zero; dust (worth under one cent) is marked and still counted. A bridge between Solana and Hood is not a trade, and bridge and Relay costs are not netted. DAMM v2 LP positions are included: realized (withdrawals against the cost basis at the add, plus claimed fees), unrealized (open positions at their share of the pool plus unclaimed fees, against that basis) and vs holding (what the deposited tokens would be worth held); `Total` stays the console's token figure and `Total with LP` covers tokens and LP. |
148
149
  | `candle portfolio [<filter>] [--chain solana\|hood] [--rpc-url <url>] [--evm-rpc-url <url>] [--no-perps] [--no-vault]` | Vault, TEE and embedded wallets on Solana and Hood in one table with chain, amount, price and value, subtotals per group and per chain, and a total. Solana vault and external balances are read over your own Solana RPC; Candle is sent only the mints, for prices. EVM vault keys are read over your own Hood RPC (`--evm-rpc-url`, else `CANDLE_EVM_RPC_URL`, else the built-in Hood RPC) for ETH and USDG only, priced by asset, never by address; an RPC on another chain is refused. An unpriced Hood token shows why (`no-market-row`, `stale-mark`, ...). `--chain` shows one chain. Each TEE wallet's DAMM v2 LP positions follow the tokens, valued by Candle at the same marks, and count in the total; one whose pool could not be read is shown as not read and the total says it is partial. A Hyperliquid section follows (each EVM TEE wallet's account, read from Hyperliquid directly, never in the total); `--no-perps` skips it. It needs no terminal: without one, `--factor touch-id` or `passkey` still unlocks the vault, and otherwise the vault row says not read and the rest is shown, but the total is partial (exit 3); `--no-vault` leaves the vault out on purpose and may exit 0. |
149
150
  | `candle scan <contract address> [--chain solana\|hood]` | The token card for one contract, as a Quant group gets it: market, all-time high since launch, bundles, snipers, insiders, fresh wallets, wallet age, tracked wallets in the last hour, the same-name leader, forensics coverage and the Candle page, with the source of each figure. Facts only; a field with no source prints a dash. The chain is read from the address unless `--chain` names it. No key needed and none sent. Served only where `TOKEN_CARD_ENABLED` is on, and `TOKEN_CARD_DISABLED` elsewhere. |
151
+ | `candle token forensics <mint> [--chain solana\|hood] [--wallet <vault key>] [--yes]` | The pre-buy forensics report: deployer history, deploy-window buyers, `coverage` and `safety`. No key needed; a configured API key is sent. On Hood that key also lets the report carry hacc's EVM contract safety (`safety.evm`), read and charged on the key's own hacc research plan; with only a device token a Hood report reads `no_key`. Past the free rate with x402 per-use on, Candle's per-use price is drawn from the key's account credit or paid from the vault, separately from hacc's research credits. |
150
152
  | `candle token clusters <mint> [--chain solana\|hood]` | Optional third-party Solana cluster estimates, with fetched time and a linked-wallet caveat. Unavailable analysis prints a reason and exits 0; `--json` emits the availability union. No key. |
151
153
  | `candle token edit <mint> [--chain solana\|hood] [--description <text>] [--website <url>] [--x <url>] [--telegram <url>] [--discord <url>] [--clear <field>]...` | As the token's creator, set Candle's copy of its description and links; `--clear` removes one. Name, symbol and image cannot be changed, and nothing is written on chain. Needs a `launch:write` key from the launching account. Served only where `TOKEN_METADATA_EDIT_ENABLED` is on, and `METADATA_EDIT_DISABLED` elsewhere. |
152
154
  | `candle token history <mint> [--chain solana\|hood]` | The token's metadata edits, newest first. Public; no key sent. |
@@ -161,7 +163,7 @@ chosen words from your saved copy. Clear your terminal's scrollback too: the CLI
161
163
  | `candle profile rename <old> <new>` | Renames a profile. |
162
164
  | `candle profile remove <name> --yes` | Deletes a profile and its stored credentials. |
163
165
  | `candle profile set <name> --rpc-url <url>\|--clear-rpc-url` | Sets or clears this profile's Solana RPC, used when `--rpc-url` and `CANDLE_SOLANA_RPC_URL` are not given. Only its host is ever shown. |
164
- | `candle mcp [--tools <a,b,c>] [--read-only] [--print-config]` | Runs the Candle MCP server (built into this binary) with this CLI's stored API key and API URL in its environment, so an MCP client config is just `{"mcpServers": {"candle": {"command": "/Users/you/.local/bin/candle", "args": ["mcp"]}}}` -- the absolute path, because GUI hosts launch servers with the app's environment and never see your PATH. Run `--print-config` to print that block filled in for this install. `--read-only` starts it with no key and only the seven keyless read tools; `--tools` pins an explicit allowlist. The server is bundled into the binary, so the host needs nothing else installed. With `CANDLE_IMAGE_DIR` set to an absolute directory path in its environment, the two launch tools accept `imagePath` for an image file inside that directory; the upload is public and permanent, on a dry run too. |
166
+ | `candle mcp [--tools <a,b,c>] [--read-only] [--print-config]` | Runs the Candle MCP server (built into this binary) with this CLI's resolved API key and API URL in a private in-process configuration, so an MCP client config is just `{"mcpServers": {"candle": {"command": "/Users/you/.local/bin/candle", "args": ["mcp"]}}}` -- the absolute path, because GUI hosts launch servers with the app's environment and never see your PATH. Run `--print-config` to print that block filled in for this install. `--read-only` starts it with no key and only the seven keyless read tools; `--tools` pins an explicit allowlist. The server is bundled into the binary, so the host needs nothing else installed. With `CANDLE_IMAGE_DIR` set to an absolute directory path in its environment, the two launch tools accept `imagePath` for an image file inside that directory; the upload is public and permanent, on a dry run too. |
165
167
  | `candle doctor` | Runs a full health check (runtime, backend, credentials, API reachability, credential validity, wallet delegation, the install method, the account's plan and its trade fee, and whether the `candle-fido2` security key helper is beside the binary) as a PASS/FAIL/SKIP table meant for pasting into a bug report. Exits nonzero on any FAIL. |
166
168
  | `candle verify <file> --bundle <path> [--identity <uri>] [--issuer <url>]` | Verifies a release asset's Sigstore bundle against the trusted root compiled into this binary. No network, no credentials, and nothing else installed: the bundle carries the certificate and the transparency-log entry. `--identity` defaults to the release identity for the version in a `latest.json` sitting beside the bundle; `--issuer` defaults to GitHub Actions'. Prints `Release signature verified` with the full file, identity, provenance and issuer fields and exits 0, or the reason on stderr and exits 1. |
167
169
  | `candle update [--check] [--to <tag>]` | Replaces this binary, and the `candle-fido2` helper beside it, with the latest signed release. Nothing is renamed until both downloads pass; the binary is renamed over the running binary only after its SHA-256 matches both SHA256SUMS and `latest.json` AND its Sigstore bundle verifies in process against that exact version's release workflow. `--check` reports what is available and installs nothing; `--to <tag>` pins a release (an older one installs, with a warning). A Homebrew or npm install is left alone, with the command that owns it printed instead. |
@@ -381,7 +383,7 @@ address is shortened (`FfU8…8pPD`; `--full` prints it whole) and an `https://`
381
383
  `auth login` and `doctor`, which carry `profile` and `account`; `auth status` and `doctor` also
382
384
  carry `cachedAccount`, the account the profile recorded, whenever a profile is resolved; scripts
383
385
  get identity from `auth status --json`. The account is cached
384
- at login from the API. Where the line is printed and `CANDLE_API_KEY` or `CANDLE_DEVICE_TOKEN` is
386
+ at login from the API. Where the line is printed and `CANDLE_API_KEY_FILE`, `CANDLE_API_KEY` or `CANDLE_DEVICE_TOKEN` is
385
387
  overriding the stored credential, it reads `Account <address> (CANDLE_API_KEY override)` when the
386
388
  overriding key could be asked which account it belongs to, and
387
389
  `Account unknown (CANDLE_API_KEY override)` when it could not (offline, revoked, or a
@@ -396,7 +398,7 @@ both and the repairs in order of cost. A key that was legitimately re-issued is
396
398
  re-authenticates instead; `--no-verify-account` skips the check for one invocation without
397
399
  repairing anything. An unreachable API turns the check into a warning, never a failure. The
398
400
  check is skipped when
399
- `CANDLE_API_KEY` or `CANDLE_DEVICE_TOKEN` is overriding the stored credential, when a profile has
401
+ `CANDLE_API_KEY_FILE`, `CANDLE_API_KEY` or `CANDLE_DEVICE_TOKEN` is overriding the stored credential, when a profile has
400
402
  no cached account or no stored key, and for the commands that only read the identity or repair it:
401
403
  `auth login`, `auth status`, `auth logout`, `doctor`, `verify` (which acts as no identity at all:
402
404
  two files and a signature), `plans` (a public read that sends no credential) and the `profile`
@@ -413,6 +415,7 @@ the profile they were migrated into.
413
415
  | Variable | Effect |
414
416
  | --- | --- |
415
417
  | `CANDLE_DEVICE_TOKEN` | Overrides the stored device token for this process. Every command that needs the device token checks this first, before the store. |
418
+ | `CANDLE_API_KEY_FILE` | Exclusive external API-key source; Linux/macOS regular file owned by the effective UID, mode exactly 0400 or 0600. Conflicts with either raw key variable; no stored-key fallback. |
416
419
  | `CANDLE_API_KEY` | Overrides the stored API key for this process, same precedence as above. |
417
420
  | `CANDLE_API_URL` | Overrides the API base URL, beating the stored config value (but not an explicit `--api-url` flag). |
418
421
  | `CANDLE_PROFILE` | Selects the profile when `--profile` is not given. |
@@ -431,6 +434,44 @@ the profile they were migrated into.
431
434
  `CANDLE_DEVICE_TOKEN` and `CANDLE_API_KEY` together mean CI needs no storage backend at all: set
432
435
  both and every command works without ever touching a keychain or the encrypted file.
433
436
 
437
+ ### External key files (unreleased source support)
438
+
439
+ A host may supply an absolute path with both raw key variables absent:
440
+
441
+ ```sh
442
+ CANDLE_API_KEY_FILE=/run/user/1000/candle/agent.key candle wallets
443
+ CANDLE_API_KEY_FILE=/run/user/1000/candle/agent.key candle mcp
444
+ ```
445
+
446
+ The file is read once per invocation, including identity, doctor and requests. It is never
447
+ imported into a profile or credential store. File identity replaces the cached profile account;
448
+ logout clears stored credentials but the external source remains active until unset. Rotation
449
+ never rewrites this file: replace the old secret before the grace deadline (immediately for
450
+ `--now`) and restart MCP. JSON rotation reports `candleApiKeyFileStale: true` when applicable.
451
+
452
+ A nonblank file setting conflicts with either nonblank `CANDLE_API_KEY` or
453
+ `CANDLE_AGENT_API_KEY`, even for equal values, before any file/store read. Without a file,
454
+ the CLI uses `CANDLE_API_KEY`, then the selected profile slot (or legacy slot without a profile);
455
+ the standalone MCP alias `CANDLE_AGENT_API_KEY` does not supply a CLI credential.
456
+ Blank inputs select no file. Relative paths use the invocation's initial working directory;
457
+ spaces in a nonblank path are preserved, with no shell or tilde expansion.
458
+
459
+ Use a trusted directory. Final symlinks, other owners, modes other than 0400/0600, nonregular
460
+ files, more than 4096 bytes, and changes during reading are refused. The file must contain one
461
+ nonempty printable ASCII token without spaces, optionally followed by one LF or CRLF. Windows
462
+ and hosts lacking the required POSIX primitives refuse file input. Errors use
463
+ `API_KEY_SOURCE_CONFLICT`, `API_KEY_FILE_UNSUPPORTED`, `API_KEY_FILE_UNREADABLE` or
464
+ `API_KEY_FILE_INVALID` and omit paths and key bytes; repair the file locally rather than relying
465
+ on a fallback. Help/version/completion stay local. `mcp --read-only` ignores every key source
466
+ and does not load the file, including with `--print-config`.
467
+
468
+ The key stays in private memory; it is never inserted into environment variables, argv, launch
469
+ config or saved files. Ordinary browser/helper/hook/keychain children retain the nonsecret path;
470
+ plugins keep their environment allowlist and receive neither the path nor key. This reduces
471
+ environment exposure, not access by root or another process with the same UID. Extended ACLs
472
+ are not audited. Source readiness does not imply an installed npm/binary release supports this;
473
+ publication is a separately authorized human release.
474
+
434
475
  ## What this CLI deliberately does not do
435
476
 
436
477
  **A built-in integration with any third-party exchange or privacy service.** Candle is never a
@@ -663,3 +704,52 @@ hash it computed from its own signed bytes, and tells you not to repeat the trad
663
704
  `No leg landed` is no longer printed on an empty list: it was never negative evidence; the CLI says
664
705
  `No leg is confirmed yet`. JSON output keeps `legEvidence`, `reconciliation`, `recoveryRequired`,
665
706
  `safeAction` and `pollAfterMs` from the server. Status makes no signing or send calls.
707
+
708
+ ## Legacy Hood leg recovery and readable guards
709
+
710
+ A `WALLET_BUSY` refusal names the guarded `operationId`, rail (`operationKind`), client `refId`,
711
+ leg, fixed `reason`, `clearingCondition`, `statusPath` and `statusCommand`. An operation id is
712
+ separate from a rail's client id:
713
+
714
+ ```sh
715
+ candle swap status --operation <operationId>
716
+ candle swap recover --operation <operationId> --hash <0x-transaction-hash>
717
+ ```
718
+
719
+ `GET /api/v1/agent/hood-tee/operations/{operationId}` and
720
+ `POST /api/v1/agent/hood-tee/operations/{operationId}/recover` return
721
+ `{ success: true, operation: { ..., guard?, recovery? } }`. The POST body accepts only
722
+ `{ candidateHash?: string }`. A locator names a transaction to verify; it never proves identity.
723
+ Both paths use the account's owning rail scope (`swap:write`, `launch:read`, or `transfer:write`);
724
+ swap operations also require the key that placed them. An unauthorized lookup returns the same
725
+ 404 `JOB_NOT_FOUND` as an unknown id. Recovery inside an active window returns 409
726
+ `OPERATION_ACTIVE`, retryable true. Reads and recovery work while the trading switch is off.
727
+
728
+ Legacy recovery reconstructs the exact signed type-2 envelope and verifies its hash, signer and
729
+ historical Candle stamp before recording an exact-hash receipt. It needs no client-held bytes.
730
+ It never signs, sends, rebroadcasts, restamps, returns a next leg, collects fees or revives the
731
+ operation. Failed/expired parents and unattempted planned legs keep their labels. Proving one
732
+ leg keeps every other unresolved leg held. A nonce count, elapsed time, missing transaction,
733
+ allowance or terminal job label cannot clear a hold. `not_sent` requires positive bound claim
734
+ proof that signing cannot have happened or happen later; pending/ambiguous claims remain held.
735
+
736
+ While `guard.recoveryRequired` is true, read status and follow `clearingCondition`. CLI status and
737
+ recover exit 3 while held. `legacy_identity_missing` can accept a locator; `nonce_conflict`,
738
+ `receipt_conflict`, `legacy_candidate_conflict` and `legacy_proof_incomplete` keep the wallet held
739
+ until the stated proof or a separately reviewed disposition exists. A landed leg stays landed.
740
+ No rebuild or repeat-trade instruction follows an uncertain result.
741
+
742
+ Every Hood rail job read carries its operation's `guard`, regardless of the job's terminal label.
743
+ `GET /api/v1/agent/wallets/{walletId}/hood-tee` is the read-only locator of last resort, even with
744
+ no job row: `guard: { held, incomplete, operations }` lists at most 10 held operations. A held
745
+ failed/expired operation also makes `activeOperation` non-null for installed custody clients.
746
+ Incomplete proof without a known holder omits `activeOperation`, so those clients fail closed.
747
+ Current custody commands refuse `WALLET_GUARDED`. Preserve that compatibility projection during
748
+ rollback. This endpoint performs no recovery writes.
749
+
750
+ SDK: `getHoodTeeOperation(operationId)` and
751
+ `recoverHoodTeeOperation(operationId, { candidateHash? })`; `GuardSummary` and `WalletBusyDetails`
752
+ are exported. MCP: `candle_get_operation` accepts `operationId` exclusively instead of
753
+ `clientId`/`kind`; `candle_recover_operation` accepts `operationId` and optional `candidateHash`.
754
+ An older API without explicit operation lookup requires reading the job by its client id.
755
+ These client source changes require a separate human package release; this repair bumps no version.