@candledottv/cli 0.13.24 → 0.13.25

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 +4 -4
  2. package/dist/index.js +237 -91
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -109,8 +109,8 @@ chosen words from your saved copy. Clear your terminal's scrollback too: the CLI
109
109
  | `candle keys limits <prefix> <asset>=<raw> [<asset>=<raw> ...]` | Sets a key's per-asset caps (`sol`, `usdc`, `cndl`, `eth`, `usdg`, in base units per transaction) with the profile's own API key. It can only tighten; raising or removing a cap is `LOOSEN_REQUIRES_SESSION`, and the owner does that in the web key manager. A device token cannot set these caps. |
110
110
  | `candle keys update <prefix\|label> --embedded-wallet allow\|deny [--yes]`, `candle keys self embedded-wallet deny` | Allow or deny a key the account's embedded wallet, with the device token (allowing needs the prefix typed back at a terminal). `self embedded-wallet deny` lets the profile's own key give it up with no prompt; a key can never allow itself. |
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
- | `candle keys stop <prefix\|label>` | 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
- | `candle keys resume <prefix\|label>` | 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. |
112
+ | `candle keys stop <prefix\|label>` | Pause one key using the device token. Its requests are refused with `PROFILE_PAUSED` until the owner resumes it in the signed-in console; the key and its local credential are kept. Repeating the command succeeds. |
113
+ | `candle keys resume <prefix\|label>` | A device token cannot resume a stopped key: the server answers `LOOSEN_REQUIRES_SESSION` and the key stays stopped. The owner resumes it in the signed-in console. An API key cannot stop or resume itself; a revoked key cannot be resumed. |
114
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\|label>` | Revokes an API key by prefix or label. A label resolves to its prefix the way `keys access` does; two live keys with the same label refuse and list their prefixes. 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. |
@@ -120,7 +120,7 @@ chosen words from your saved copy. Clear your terminal's scrollback too: the CLI
120
120
  | `candle wallets authorize` | Gives agents access to the account's embedded wallets. The grant is yours to make in a browser (each wallet's owner is you, so neither the CLI nor the API can attach Candle's signer): this opens the Agents page's grant-only step, creates no key, and with an API key waits until the wallets show as delegated. On a new account, run it before `candle auth login`. |
121
121
  | `candle wallets trust\|untrust <label\|address\|id\|prefix*>...` | Mark linked wallets as yours so an agent's Withdraw key can move funds into them, or clear the mark. Owner only, over the device token, with a typed confirm; a trailing `*`, as in `'tr-*'`, selects by prefix. |
122
122
  | `candle wallets allow-launch\|disallow-launch <label\|address\|id\|prefix*>...` | Deprecated. Where the server has `TEE_LAUNCH_BY_SCOPE_ENABLED` on, a key's `launch:write` decides TEE launches: `allow-launch` saves the value and exits 0, and `disallow-launch` saves it, prints the server's `ALLOW_LAUNCH_NOT_ENFORCED` message and exits 1, because no launch is stopped. A server with the switch off still lets TEE wallets pay for a launch (`candle launch`) only after `allow-launch`. Owner only, over the device token; the screen shows each wallet's label, address and bound key before a typed confirm. `disallow-launch --yes` skips the confirm. |
123
- | `candle wallets close-empty [--wallet embedded\|<tee address\|label>] [--keep <mint>]... [--client-trade-id <id>] [--preview] [--yes]` | Closes a wallet's empty token accounts and returns their rent to it, after a preview: the embedded wallet (`transfer:write`), or a Solana TEE wallet bound to the key (`swap:write`), whose close transactions Candle builds, this machine checks and the key signer approves. `--preview` prints what would close — the whole preview as one JSON document on stdout under `--json` — and closes, signs and prompts nothing; headless without `--yes`, the `CONFIRMATION_REQUIRED` refusal carries that same preview under `details`. |
123
+ | `candle wallets close-empty [--wallet embedded\|<tee address\|label>] [--keep <mint>]... [--client-trade-id <id>] [--preview] [--yes]` | Closes a wallet's empty token accounts and returns their rent to it, after a preview: the embedded wallet (`swap:write` or `transfer:write`), or a Solana TEE wallet bound to the key (`swap:write`), whose close transactions Candle builds, this machine checks and the key signer approves. `--preview` prints what would close — the whole preview as one JSON document on stdout under `--json` — and closes, signs and prompts nothing; headless without `--yes`, the `CONFIRMATION_REQUIRED` refusal carries that same preview under `details`. |
124
124
  | `candle vault init\|status\|list\|new-key\|rename\|phrase show\|restore\|reconcile-exposure` | The encrypted local vault, `vault.enc` (Tier 1). `init` creates it with a passphrase factor and a 24-word recovery phrase (and no key), labels its read-back derivation as verification, and leaves security-key enrollment to the separate optional `candle vault enroll security-key` command; `new-key` derives a key inside it (`--count`, `--labels-from <file>` for many under one unlock), `list [filter] [--balances]` shows one line per key, `rename` changes a label only, `phrase show` displays the phrase on a terminal (never in `--json`, a log or a pipe), and `restore --phrase` rebuilds the derived keys on another machine with a new passphrase (`--count`, `--tee-count`, `--external-count`, `--evm-count` and `--evm-tee-count` for EVM keys and Hood TEE wallets, or a Solana gap scan with `--rpc-url`); a restored vault never allocates new keys. `reconcile-exposure` re-reads the account and adds exposure records, clearing nothing. No key ever leaves the vault to reach Candle. See [CLI custody](https://docs.candle.tv/developers/cli-custody). |
125
125
  | `candle vault factor list\|add passphrase\|add security-key\|add touch-id\|add passkey\|remove` (or `vault enroll <kind>`), `vault backup --to <path>\|icloud`, `vault verify-backup <path>` | What can open the vault, and proving a copy of it works: `backup` verifies the copy in full before reporting (a copy is sealed, keeping only the passphrase and security-key envelopes, unless it goes to a recognised local disk or removable drive, so a cloud folder and any path the CLI cannot place are both sealed unless `--accept-shared-domain` is passed), and `verify-backup` re-checks an existing one against this vault's key and address set. `add security-key` enrolls a FIDO2 security key (CTAP2 `hmac-secret`, user-verified with the key's PIN or biometric) through the bundled `candle-fido2` helper (`--install-helper` fetches and verifies this release's helper first); one key is not a recoverable factor, two keys are a pair, an enrolled key can authorize adding another, and the passphrase stays the recovery floor. `add touch-id` is available in 0.13.23 on supported Macs with the signed `candle-enclave.app`; npm ships no native helper. `add passkey` remains unavailable (`VAULT_FACTOR_UNSUPPORTED_ON_PLATFORM`): the shipped helper lacks the associated-domain entitlement. Every vault command then takes `--factor <id\|passphrase\|security-key\|touch-id>` to say which envelope opens it and `--device <id>` to name the key when several are attached. |
126
126
  | `candle vault transfer\|promote\|promote-batch\|fund\|demote\|export-key` | Moving value and authority. `transfer` and `fund` sign locally after showing the decoded transaction (`transfer` from a vault key or a promoted TEE wallet, which warns when an agent may be trading it and reports the finalized transfer to Candle's history; `fund` reaches a TEE wallet from its pinned vault key, or an external wallet, with no `--yes`; both verify once, at the unlock, which names the send, then show the destination grouped in fours and take a typed `confirm`); `promote --from <cold label>` derives a fresh TEE wallet pinned to that key (an EVM vault key becomes a Hood TEE wallet), shows the pinned vault address and asks you to type `confirm` to promote the new wallet (promotion moves no funds), `promote --in-place <label> --sweep-to <cold label>` promotes a key at its own address after a typed warning, `promote-batch --pairs-from <file>` does many in place under one unlock, `--to-key` binds the result to another of your keys, and `demote` (disable then sweep; `--emergency` sweeps even when the stop request fails) sends it back to its pinned vault; `export-key` is the one ceremony that writes a single private key to a file you name. `demote` takes several addresses, or `--all --empty` for every Solana TEE wallet with nothing left to sweep, under one unlock and one typed `confirm` (`--yes` skips it), with a result line per wallet and one `--json` document. |
@@ -240,7 +240,7 @@ transparency log: [Verify a Candle release](https://docs.candle.tv/developers/ve
240
240
 
241
241
  Where the API has `OWNER_CONFIRM_ENABLED` on, an owner step made with the device token that widens what the account's keys can do also needs one approval in your browser, signed in as the account owner. The API answers `403 OWNER_CONFIRM_REQUIRED` and changes nothing; the CLI then:
242
242
 
243
- 1. Prints what you are approving (a summary the server built), `Open <url>` and `Code: XXXX-XXXX` on stderr, with "Only approve if you just ran this command." It opens the browser only with `--open`. Under `--json` it writes one line to stderr instead, `{"ok":false,"pending":true,"code":"OWNER_CONFIRM_REQUIRED","ownerConfirm":{"userCode","verificationUriComplete","expiresIn","step","summary"}}`, and keeps stdout for the final result.
243
+ 1. Prints what you are approving (a summary the server built), `Open <url>` and `Code: XXXX-XXXX` on stderr, with "Only approve if you just ran this command." When the API names the account the request was made as (0.13.24+), the first line reads `This needs your approval in the browser as <display name> (<short wallet>): <summary>` and a following line says the browser must be signed in as that account — so a wrong-account browser is obvious before it answers "Request not found". It opens the browser only with `--open`. Under `--json` it writes one line to stderr instead, `{"ok":false,"pending":true,"code":"OWNER_CONFIRM_REQUIRED","ownerConfirm":{"userCode","verificationUriComplete","expiresIn","step","summary","account":{"wallet","displayName"}}}`, and keeps stdout for the final result.
244
244
  2. Waits, polling every few seconds. The code expires 5 minutes after it was issued.
245
245
  3. When you approve at `/dev/agent/confirm`, sends the identical request again and prints its result. Deny it and the command exits 1 with `Denied in the browser; nothing changed.`; let it expire and it exits 1 with `The confirmation expired; nothing changed. Run the command again.`
246
246
  4. With `--no-wait` it prints the code and exits 7 without waiting. The command does not resume later: run it again without `--no-wait`, or make the change in the console.
package/dist/index.js CHANGED
@@ -734,6 +734,23 @@ var init_agent_key_access = __esm(() => {
734
734
  ];
735
735
  });
736
736
 
737
+ // src/terminal-text.ts
738
+ function escapeTerminalText(value) {
739
+ return value.replace(/[\u0000-\u0008\u000a-\u001f\u007f-\u009f]/g, (char) => {
740
+ if (char === `
741
+ `)
742
+ return "\\n";
743
+ if (char === "\r")
744
+ return "\\r";
745
+ return `\\x${char.charCodeAt(0).toString(16).padStart(2, "0")}`;
746
+ });
747
+ }
748
+ function terminalLines(value) {
749
+ return value.split(`
750
+ `).map(escapeTerminalText).join(`
751
+ `);
752
+ }
753
+
737
754
  // src/terminal-theme.ts
738
755
  var TERMINAL_TOKENS;
739
756
  var init_terminal_theme = __esm(() => {
@@ -761,7 +778,7 @@ var init_terminal_theme = __esm(() => {
761
778
  });
762
779
 
763
780
  // src/version.ts
764
- var CLI_VERSION = "0.13.24";
781
+ var CLI_VERSION = "0.13.25";
765
782
 
766
783
  // src/presentation.ts
767
784
  function terminalStyle(deps, stream, machine = false) {
@@ -781,6 +798,7 @@ function terminalStyle(deps, stream, machine = false) {
781
798
  };
782
799
  }
783
800
  function accent(text, style, role = "brand") {
801
+ text = terminalLines(text);
784
802
  if (!style.color)
785
803
  return text;
786
804
  const token = TERMINAL_TOKENS[style.theme][role];
@@ -944,6 +962,7 @@ function symbols(style) {
944
962
  return style.styled ? UNICODE_SYMBOLS : ASCII_SYMBOLS;
945
963
  }
946
964
  function sgr(text, style, code) {
965
+ text = terminalLines(text);
947
966
  return style.color && text ? `\x1B[${code}m${text}\x1B[0m` : text;
948
967
  }
949
968
  function bold(text, style) {
@@ -953,30 +972,32 @@ function dim(text, style) {
953
972
  return sgr(text, style, "2");
954
973
  }
955
974
  function heading(text, style) {
956
- return wrapText(text, proseWidth(style)).map((line) => bold(line, style)).join(`
975
+ return wrapText(terminalLines(text), proseWidth(style)).map((line) => bold(line, style)).join(`
957
976
  `);
958
977
  }
959
978
  function fields(rows, style, indent = 2) {
979
+ rows = rows.map(([label, value]) => [escapeTerminalText(label), value]);
960
980
  const budget = proseWidth(style);
961
981
  const pad = " ".repeat(Math.min(indent, Math.max(0, budget)));
962
982
  const labelWidth = Math.max(0, ...rows.map(([label]) => displayWidth(label)));
963
983
  const sideWidth = budget - pad.length - labelWidth - 2;
964
984
  const out = [];
965
985
  for (const [label, raw] of rows) {
966
- const value = raw === undefined || raw === null || raw === "" ? symbols(style).missing : String(raw);
986
+ const paint = (text) => typeof raw === "object" && raw !== null ? accent(text, style, raw.role) : text;
987
+ const value = raw === undefined || raw === null || raw === "" ? symbols(style).missing : escapeTerminalText(typeof raw === "object" ? raw.text : String(raw));
967
988
  if (sideWidth >= 1 && fitsWidth(value, sideWidth)) {
968
989
  const [first = "", ...rest] = value.split(`
969
990
  `).flatMap((line) => wrapText(line, sideWidth));
970
- out.push(`${pad}${dim(padDisplay(label, labelWidth), style)} ${first}`);
991
+ out.push(`${pad}${dim(padDisplay(label, labelWidth), style)} ${paint(first)}`);
971
992
  for (const line of rest)
972
- out.push(`${pad}${" ".repeat(labelWidth + 2)}${line}`);
993
+ out.push(`${pad}${" ".repeat(labelWidth + 2)}${paint(line)}`);
973
994
  } else {
974
- out.push(...stackedField(label, value, style, pad, budget));
995
+ out.push(...stackedField(label, value, style, pad, budget, paint));
975
996
  }
976
997
  }
977
998
  return out;
978
999
  }
979
- function stackedField(label, value, style, pad, budget) {
1000
+ function stackedField(label, value, style, pad, budget, paint) {
980
1001
  const labelBudget = Math.max(1, budget - pad.length);
981
1002
  const out = wrapText(label, labelBudget).map((line) => `${pad}${dim(line, style)}`);
982
1003
  const nested = budget - pad.length - 2;
@@ -993,11 +1014,12 @@ function stackedField(label, value, style, pad, budget) {
993
1014
  for (const part of value.split(`
994
1015
  `)) {
995
1016
  for (const line of wrapText(part, valueWidth))
996
- out.push(line ? `${valuePad}${line}` : "");
1017
+ out.push(line ? `${valuePad}${paint(line)}` : "");
997
1018
  }
998
1019
  return out;
999
1020
  }
1000
1021
  function renderNotice(level, text, style, indent = 2) {
1022
+ text = terminalLines(text);
1001
1023
  const sym = symbols(style);
1002
1024
  const mark = level === "note" ? sym.note : level === "warning" ? sym.warning : sym.danger;
1003
1025
  const budget = proseWidth(style);
@@ -1027,6 +1049,7 @@ ${renderNotice(level, text, style).join(`
1027
1049
  `);
1028
1050
  }
1029
1051
  function success(text, style) {
1052
+ text = terminalLines(text);
1030
1053
  const mark = symbols(style).ok;
1031
1054
  const hang = " ".repeat(displayWidth(mark) + 1);
1032
1055
  const budget = Math.max(1, proseWidth(style) - displayWidth(mark) - 1);
@@ -1048,17 +1071,19 @@ var init_presentation = __esm(() => {
1048
1071
  });
1049
1072
 
1050
1073
  // src/render.ts
1051
- function formatScopesForSummary(scopes) {
1052
- return sortAgentKeyScopes(scopes).map((scope) => scope === "swap:write" ? `${scope} (${SWAP_WRITE_NOTE})` : scope === "transfer:write" ? `${scope} (${TRANSFER_WRITE_NOTE})` : scope === "transfer:bound" ? `${scope} (${TRANSFER_BOUND_NOTE})` : scope === "perps:write" ? `${scope} (${PERPS_WRITE_NOTE})` : scope).join(", ");
1053
- }
1054
1074
  function terminalText(value) {
1055
1075
  return value.replace(/[\u0000-\u001f\u007f-\u009f]/g, "");
1056
1076
  }
1077
+ function formatScopesForSummary(scopes) {
1078
+ return sortAgentKeyScopes(scopes).map((scope) => scope === "swap:write" ? `${scope} (${SWAP_WRITE_NOTE})` : scope === "transfer:write" ? `${scope} (${TRANSFER_WRITE_NOTE})` : scope === "transfer:bound" ? `${scope} (${TRANSFER_BOUND_NOTE})` : scope === "perps:write" ? `${scope} (${PERPS_WRITE_NOTE})` : scope).join(", ");
1079
+ }
1057
1080
  function numericColumn(rows, col) {
1058
1081
  const known = rows.map((row) => row[col] ?? "").filter((cell) => cell !== "" && cell !== "–" && cell !== "-");
1059
1082
  return known.length > 0 && known.every((cell) => NUMERIC_CELL.test(cell));
1060
1083
  }
1061
1084
  function renderTable(headers, rows, options) {
1085
+ headers = headers.map(escapeTerminalText);
1086
+ rows = rows.map((row) => row.map((cell) => escapeTerminalText(cell ?? "")));
1062
1087
  if (!options)
1063
1088
  return renderPlainTable(headers, rows);
1064
1089
  const specs = headers.map((_, col) => options.columns?.[col] ?? {});
@@ -1219,6 +1244,14 @@ function errorParts(result, ctx) {
1219
1244
  return { message: result.message, fixes: result.uiHint ? [{ label: result.uiHint }] : [] };
1220
1245
  }
1221
1246
  function renderErrorBlock(parts, style) {
1247
+ parts = {
1248
+ message: terminalLines(parts.message),
1249
+ fixes: parts.fixes.map((fix) => ({
1250
+ ...fix,
1251
+ label: terminalLines(fix.label),
1252
+ ...fix.command ? { command: terminalLines(fix.command) } : {}
1253
+ }))
1254
+ };
1222
1255
  const budget = proseWidth(style);
1223
1256
  const out = [...errorMessageLines(parts.message, style, budget)];
1224
1257
  if (parts.fixes.length === 0)
@@ -1286,7 +1319,7 @@ function writeFailure(deps, result, ctx, json) {
1286
1319
  deps.stdout.write(`${JSON.stringify(errorEnvelope(result, ctx))}
1287
1320
  `);
1288
1321
  else
1289
- deps.stderr.write(`${renderError(result, ctx)}
1322
+ deps.stderr.write(`${terminalLines(renderError(result, ctx))}
1290
1323
  `);
1291
1324
  }
1292
1325
  function writeLocalFailure(deps, failure2, json) {
@@ -1298,7 +1331,7 @@ function writeLocalFailure(deps, failure2, json) {
1298
1331
  const separator = failure2.suggestion?.includes(`
1299
1332
  `) ? `
1300
1333
  ` : " ";
1301
- deps.stderr.write(`${failure2.suggestion ? `${failure2.message}${separator}${failure2.suggestion}` : failure2.message}
1334
+ deps.stderr.write(`${terminalLines(failure2.suggestion ? `${failure2.message}${separator}${failure2.suggestion}` : failure2.message)}
1302
1335
  `);
1303
1336
  }
1304
1337
  function writeUsageFailure(deps, message, json) {
@@ -1306,7 +1339,7 @@ function writeUsageFailure(deps, message, json) {
1306
1339
  deps.stdout.write(`${JSON.stringify({ ok: false, code: "USAGE", message })}
1307
1340
  `);
1308
1341
  else
1309
- deps.stderr.write(`${message}
1342
+ deps.stderr.write(`${terminalLines(message)}
1310
1343
  `);
1311
1344
  }
1312
1345
  function portalDevicesUrl(apiUrl, portalOrigin) {
@@ -6858,7 +6891,7 @@ var init_launch_definition = __esm(() => {
6858
6891
  {
6859
6892
  key: "curve.virtualQuote",
6860
6893
  type: "decimal",
6861
- help: "exp-custom's virtual quote, in whole quote units: 1 to 100 SOL or 66000 to 6600000 CNDL; requires --curve-g and EXP_CUSTOM_CURVES.",
6894
+ help: "exp-custom's virtual quote, in whole quote units: 1 to 100 SOL or 66000 to 6600000 CNDL; requires --curve-g.",
6862
6895
  modes: [
6863
6896
  "exp-custom"
6864
6897
  ],
@@ -6897,7 +6930,7 @@ var init_launch_definition = __esm(() => {
6897
6930
  {
6898
6931
  key: "curve.threshold",
6899
6932
  type: "decimal",
6900
- help: "exp-custom's graduation threshold, in whole quote units: 1 to 250 SOL or 66000 to 16500000 CNDL; requires --curve-v and EXP_CUSTOM_CURVES.",
6933
+ help: "exp-custom's graduation threshold, in whole quote units: 1 to 250 SOL or 66000 to 16500000 CNDL; requires --curve-v.",
6901
6934
  modes: [
6902
6935
  "exp-custom"
6903
6936
  ],
@@ -20215,10 +20248,13 @@ function credentialEnvOverrides(env) {
20215
20248
  return ["CANDLE_API_KEY_FILE", "CANDLE_API_KEY", "CANDLE_DEVICE_TOKEN"].filter((name) => env[name]?.trim());
20216
20249
  }
20217
20250
  function identityLine(profile, account, apiUrl, overrides, username, options = {}) {
20218
- const address = account === undefined ? undefined : shortId(account, options.full);
20251
+ profile = profile === undefined ? undefined : escapeTerminalText(profile);
20252
+ username = username === undefined ? undefined : escapeTerminalText(username);
20253
+ overrides = overrides?.map(escapeTerminalText);
20254
+ const address = account === undefined ? undefined : escapeTerminalText(shortId(account, options.full));
20219
20255
  const shown = overrides?.length ? address ? `${address} (${overrides.join(", ")} override)` : `unknown (${overrides.join(", ")} override)` : username && address ? `${username} (${address})` : address ?? "unknown";
20220
20256
  const name = profile ?? "none";
20221
- const host = apiUrl.replace(/^https:\/\//i, "").replace(/\/+$/, "");
20257
+ const host = escapeTerminalText(apiUrl).replace(/^https:\/\//i, "").replace(/\/+$/, "");
20222
20258
  const sep2 = options.style ? symbols(options.style).note : "·";
20223
20259
  return `Profile ${options.style ? bold(name, options.style) : name} ${sep2} Account ${shown} ${sep2} ${host}`;
20224
20260
  }
@@ -25688,6 +25724,76 @@ var init_hood_legs = __esm(() => {
25688
25724
  V4_SWAP_PARAMS = { tuple: [V4_POOL_KEY, "bool", "uint", "uint", "uint", "bytes"] };
25689
25725
  });
25690
25726
 
25727
+ // src/hood-operation-wait.ts
25728
+ async function waitForHoodOperation(ctx, key, id, evidence) {
25729
+ const deadline = ctx.deps.now() + WAIT_MS;
25730
+ const hint = `candle swap status --operation ${safeText(id)}`;
25731
+ const readCtx = {
25732
+ ...ctx,
25733
+ deps: {
25734
+ ...ctx.deps,
25735
+ fetch: (input, init) => ctx.deps.fetch(input, {
25736
+ ...init,
25737
+ signal: AbortSignal.timeout(Math.max(1, deadline - ctx.deps.now()))
25738
+ })
25739
+ }
25740
+ };
25741
+ for (;; ) {
25742
+ let result2;
25743
+ try {
25744
+ result2 = responseSchema.parse(await request(readCtx, key, `/api/v1/agent/hood-tee/operations/${encodeURIComponent(id)}`));
25745
+ if (result2.operation.operationId !== id)
25746
+ throw new Error("The server returned a different operation");
25747
+ } catch (error) {
25748
+ throw new TradingError("HOOD_WAIT_UNAVAILABLE", `Could not follow Hood settlement. Do not repeat the swap. Read ${hint}.`, {
25749
+ exitCode: 3,
25750
+ details: {
25751
+ ...evidence,
25752
+ operationId: id,
25753
+ readCode: error instanceof TradingError ? error.code : "INVALID_RESPONSE"
25754
+ }
25755
+ });
25756
+ }
25757
+ const op = result2.operation;
25758
+ const held = op.guard?.recoveryRequired === true || op.recovery?.recoveryRequired === true;
25759
+ const completed = op.status === "completed" && op.legs.length === op.plannedLegCount && op.legs.every((leg) => leg.status === "landed");
25760
+ const failed = op.status === "failed" || op.status === "expired";
25761
+ const final = !held && (completed || failed);
25762
+ if (final || ctx.deps.now() >= deadline) {
25763
+ ctx.deps.stdout.write(`${JSON.stringify(result2, null, ctx.json ? undefined : 2)}
25764
+ `);
25765
+ if (!final)
25766
+ ctx.deps.stderr.write(`Still not final after ten minutes. Do not repeat the swap. Read ${hint}.
25767
+ `);
25768
+ else if (failed)
25769
+ ctx.deps.stderr.write(`Hood operation ${safeText(op.status)}. Do not repeat the swap. Read ${hint}.
25770
+ `);
25771
+ if (!final)
25772
+ return 3;
25773
+ if (completed)
25774
+ return 0;
25775
+ return op.legs.some((leg) => leg.kind === "trade" && leg.status === "landed") ? 3 : 1;
25776
+ }
25777
+ await ctx.deps.sleep(Math.min(POLL_MS, deadline - ctx.deps.now()));
25778
+ }
25779
+ }
25780
+ var WAIT_MS, POLL_MS = 5000, responseSchema;
25781
+ var init_hood_operation_wait = __esm(() => {
25782
+ init_zod();
25783
+ init_trading();
25784
+ WAIT_MS = 10 * 60000;
25785
+ responseSchema = exports_external.object({
25786
+ operation: exports_external.object({
25787
+ operationId: exports_external.string(),
25788
+ status: exports_external.enum(["active", "completed", "failed", "expired"]),
25789
+ plannedLegCount: exports_external.number().int().positive(),
25790
+ legs: exports_external.array(exports_external.object({ kind: exports_external.string(), status: exports_external.string() }).passthrough()),
25791
+ guard: exports_external.object({ recoveryRequired: exports_external.boolean() }).passthrough().optional(),
25792
+ recovery: exports_external.object({ recoveryRequired: exports_external.boolean() }).passthrough().optional()
25793
+ }).passthrough()
25794
+ }).passthrough();
25795
+ });
25796
+
25691
25797
  // src/human-output.ts
25692
25798
  function writeFields(deps, stream, rows, title) {
25693
25799
  const style = terminalStyle(deps, stream);
@@ -26011,7 +26117,7 @@ function walletRecordCheck(row, trading, records) {
26011
26117
  differences
26012
26118
  } : pass("record");
26013
26119
  }
26014
- async function inspectReport(ctx, key, row, listing, records, payable, name) {
26120
+ async function inspectReport(ctx, key, row, listing, records, payable, name, chain2) {
26015
26121
  const checks = [pass("key"), payable ? failure2("payable", payable) : pass("payable")];
26016
26122
  checks.push(listing.scopes.includes("swap:write") ? pass("scope") : failure2("scope", new TradingError("SCOPE_MISSING", "The bound key needs swap:write.")));
26017
26123
  const report = {
@@ -26024,7 +26130,7 @@ async function inspectReport(ctx, key, row, listing, records, payable, name) {
26024
26130
  return { report };
26025
26131
  }
26026
26132
  checks.push(walletRecordCheck(row, listing.rows, records));
26027
- const inspected = await inspectTradingWallet(ctx, row, listing.appId, "swap:write", "solana", {
26133
+ const inspected = await inspectTradingWallet(ctx, row, listing.appId, "swap:write", chain2 ?? rowChain(row) ?? "solana", {
26028
26134
  apiKey: key,
26029
26135
  keyPrefix: listing.keyPrefix
26030
26136
  });
@@ -26217,14 +26323,18 @@ async function buildCheck(ctx, key, wallet3, report, from, to, amount, slippage,
26217
26323
  }
26218
26324
  }
26219
26325
  async function teeCheck(args, ctx) {
26220
- const parsed = parseArgs(args, { valueFlags: ["--label-prefix", "--max", "--rpc-url"], booleanFlags: ["--sign"] });
26326
+ const parsed = parseArgs(args, {
26327
+ valueFlags: ["--label-prefix", "--max", "--rpc-url", "--chain"],
26328
+ booleanFlags: ["--sign"]
26329
+ });
26221
26330
  if ("error" in parsed) {
26222
26331
  writeUsageFailure(ctx.deps, parsed.error, ctx.json);
26223
26332
  return 2;
26224
26333
  }
26225
26334
  const max = Number(parsed.values["--max"] ?? "10");
26226
- if (!parsed.positionals.length && parsed.values["--label-prefix"] === undefined || !Number.isSafeInteger(max) || max < 1) {
26227
- writeUsageFailure(ctx.deps, "Usage: candle tee check <address|id|label>... [--label-prefix <p>] [--sign] [--max <n>] [--rpc-url <url>]", ctx.json);
26335
+ const chainFlag = parsed.values["--chain"];
26336
+ if (!parsed.positionals.length && parsed.values["--label-prefix"] === undefined || !Number.isSafeInteger(max) || max < 1 || chainFlag !== undefined && chainFlag !== "solana" && chainFlag !== "hood") {
26337
+ writeUsageFailure(ctx.deps, "Usage: candle tee check <address|id|label>... [--label-prefix <p>] [--chain solana|hood] [--sign] [--max <n>] [--rpc-url <url>]", ctx.json);
26228
26338
  return 2;
26229
26339
  }
26230
26340
  try {
@@ -26256,8 +26366,13 @@ async function teeCheck(args, ctx) {
26256
26366
  for (const name of targets) {
26257
26367
  const matches = listing.rows.filter((row) => matchesName(row, name));
26258
26368
  const payable = matches.length === 1 ? undefined : new TradingError("TEE_WALLET_REQUIRED", `"${safeText(name)}" matches ${matches.length} TEE wallets. Bound to this key: ${listing.rows.map(describeWallet).join("; ") || "no TEE wallets bound to this key"}.`);
26259
- const { report, wallet: wallet3 } = await inspectReport(ctx, key, matches.length === 1 ? matches[0] : undefined, listing, records, payable, name);
26260
- if (sign4 && wallet3)
26369
+ const { report, wallet: wallet3 } = await inspectReport(ctx, key, matches.length === 1 ? matches[0] : undefined, listing, records, payable, name, chainFlag);
26370
+ if (sign4 && wallet3?.chain === "evm")
26371
+ report.checks.push({
26372
+ ...skipped("sign", "hood"),
26373
+ message: "Signing is checked on Solana wallets only; a Hood test build would hold the wallet."
26374
+ });
26375
+ else if (sign4 && wallet3)
26261
26376
  await buildCheck(ctx, key, wallet3, report, "SOL", "USDC", "0.000001", 50, parsed.values["--rpc-url"], true);
26262
26377
  else
26263
26378
  report.checks.push(skipped("sign", sign4 ? report.checks.find((check) => check.status === "fail")?.id ?? "signer" : "--sign not set"));
@@ -26294,7 +26409,7 @@ async function swapCheck(args, ctx) {
26294
26409
  if (from.chain !== "solana" || to.chain !== "solana")
26295
26410
  throw new TradingError("VALIDATION_FAILED", "Dry run checks Solana TEE pairs; Hood and bridge pairs are unsupported.");
26296
26411
  if (parsed.values["--to"] !== undefined || parsed.booleans.has("--wait")) {
26297
- writeUsageFailure(ctx.deps, "--to and --wait are for a bridge.", ctx.json);
26412
+ writeUsageFailure(ctx.deps, "--to selects a bridge destination; --wait follows a bridge or a Hood TEE swap without --dry-run. Follow a Hood swap with candle swap status --operation <id>, or a job with candle swap status <client-trade-id>.", ctx.json);
26298
26413
  return 2;
26299
26414
  }
26300
26415
  if (from.asset === to.asset || !from.base && !to.base)
@@ -26317,7 +26432,7 @@ async function swapCheck(args, ctx) {
26317
26432
  payable = error;
26318
26433
  }
26319
26434
  const records = await readWalletRecords(ctx, key);
26320
- const { report, wallet: wallet3 } = await inspectReport(ctx, key, row, listing, records, payable, parsed.values["--wallet"]);
26435
+ const { report, wallet: wallet3 } = await inspectReport(ctx, key, row, listing, records, payable, parsed.values["--wallet"], from.chain);
26321
26436
  if (wallet3)
26322
26437
  await buildCheck(ctx, key, wallet3, report, from.asset, to.asset, parsed.values["--amount"], slippage, parsed.values["--rpc-url"], false);
26323
26438
  else
@@ -26452,7 +26567,7 @@ function printTradingResult(ctx, result2) {
26452
26567
  }
26453
26568
  const printed = ctx.json && (result2.kind === "swap" || result2.kind === "trade") ? swapJsonResult(result2) : result2;
26454
26569
  ctx.deps.stdout.write(ctx.json ? `${JSON.stringify(printed)}
26455
- ` : `${JSON.stringify(result2, null, 2)}
26570
+ ` : `${terminalLines(JSON.stringify(result2, null, 2))}
26456
26571
  `);
26457
26572
  const job = result2.job;
26458
26573
  const operation = job?.operation;
@@ -26501,11 +26616,11 @@ async function lookupOperation(ctx, key, id, kind) {
26501
26616
  async function swapStatus(args, ctx) {
26502
26617
  const parsed = parseArgs(args, { valueFlags: ["--kind", "--operation"], booleanFlags: ["--wait"] });
26503
26618
  if (!("error" in parsed) && parsed.values["--operation"] !== undefined) {
26504
- if (parsed.positionals.length || parsed.values["--kind"] || parsed.booleans.has("--wait")) {
26505
- writeUsageFailure(ctx.deps, "Usage: candle swap status --operation <operationId>", ctx.json);
26619
+ if (parsed.positionals.length || parsed.values["--kind"]) {
26620
+ writeUsageFailure(ctx.deps, "Usage: candle swap status --operation <operationId> [--wait]", ctx.json);
26506
26621
  return 2;
26507
26622
  }
26508
- return hoodOperationRead(ctx, parsed.values["--operation"]);
26623
+ return hoodOperationRead(ctx, parsed.values["--operation"], undefined, false, parsed.booleans.has("--wait"));
26509
26624
  }
26510
26625
  if ("error" in parsed || parsed.positionals.length !== 1 || !validClientId(parsed.positionals[0] ?? "") || parsed.values["--kind"] !== undefined && !["trade", "swap", "launch"].includes(parsed.values["--kind"])) {
26511
26626
  writeUsageFailure(ctx.deps, "Usage: candle swap status <id> [--kind trade|swap|launch] [--wait]", ctx.json);
@@ -26578,7 +26693,7 @@ async function swapReceipt(args, ctx) {
26578
26693
  }
26579
26694
  describeReceipt(ctx, body, answer.job);
26580
26695
  ctx.deps.stdout.write(ctx.json ? `${JSON.stringify(body)}
26581
- ` : `${JSON.stringify(body, null, 2)}
26696
+ ` : `${terminalLines(JSON.stringify(body, null, 2))}
26582
26697
  `);
26583
26698
  return 0;
26584
26699
  } catch (error) {
@@ -26589,7 +26704,7 @@ function describeReceipt(ctx, body, job) {
26589
26704
  const settlement = body.settlement;
26590
26705
  if (settlement === null || typeof settlement !== "object") {
26591
26706
  if (typeof body.next === "string")
26592
- ctx.deps.stderr.write(`${body.next}
26707
+ ctx.deps.stderr.write(`${terminalLines(body.next)}
26593
26708
  `);
26594
26709
  return;
26595
26710
  }
@@ -26712,8 +26827,9 @@ async function swap2(args, ctx) {
26712
26827
  slippage,
26713
26828
  ...bridge
26714
26829
  });
26715
- if (flags["--to"] !== undefined || parsed.booleans.has("--wait"))
26716
- throw new TradingUsage("--to and --wait are for a bridge: SOL or USDC to ETH or USDG, or back.");
26830
+ const unsupportedWait = "--to is for a bridge: SOL or USDC to ETH or USDG, or back. --wait follows a bridge or a Hood TEE swap. Follow a Hood swap with candle swap status --operation <id>; use candle swap status <client-trade-id> for a job.";
26831
+ if (flags["--to"] !== undefined)
26832
+ throw new TradingUsage(unsupportedWait);
26717
26833
  const chain2 = pairChain(fromAsset, toAsset);
26718
26834
  const walletFlag = flags["--wallet"];
26719
26835
  const named = walletFlag === undefined ? undefined : walletNameChain(walletFlag);
@@ -26721,6 +26837,7 @@ async function swap2(args, ctx) {
26721
26837
  throw chainMismatch(`--wallet ${safeText(walletFlag)}`, named, chain2);
26722
26838
  if (chain2 === "hood")
26723
26839
  return await hoodSwap(ctx, {
26840
+ wait: parsed.booleans.has("--wait"),
26724
26841
  flags,
26725
26842
  yes: parsed.booleans.has("--yes"),
26726
26843
  id,
@@ -26728,6 +26845,8 @@ async function swap2(args, ctx) {
26728
26845
  from: fromAsset,
26729
26846
  to: toAsset
26730
26847
  });
26848
+ if (parsed.booleans.has("--wait"))
26849
+ throw new TradingUsage(unsupportedWait);
26731
26850
  const from = fromAsset.asset;
26732
26851
  const to = toAsset.asset;
26733
26852
  if (from === to)
@@ -26953,9 +27072,19 @@ async function hoodSwap(ctx, args) {
26953
27072
  const kind = from.base && to.base ? "swap" : "trade";
26954
27073
  const key = await tradingKey(ctx);
26955
27074
  const prior = await lookupOperation(ctx, key, id, kind);
27075
+ const replay = async (result2) => {
27076
+ if (!args.wait)
27077
+ return printTradingResult(ctx, result2);
27078
+ const operation = result2.job?.operation;
27079
+ if (typeof operation?.operationId !== "string")
27080
+ throw new TradingUsage(`No Hood TEE operationId is available. Follow with candle swap status ${id}; a Hood TEE swap uses candle swap status --operation <id>.`);
27081
+ return waitForHoodOperation(ctx, key, operation.operationId);
27082
+ };
26956
27083
  if (prior)
26957
- return printTradingResult(ctx, prior);
27084
+ return replay(prior);
26958
27085
  const payer = await tradingPayer(ctx, key, flags["--wallet"], "swap:write", "hood");
27086
+ if (args.wait && payer.kind !== "tee")
27087
+ throw new TradingUsage(`--wait follows a bridge or a Hood TEE swap. Follow this swap with candle swap status ${id}; a Hood TEE swap uses candle swap status --operation <id>.`);
26959
27088
  const payerAddress = payer.kind === "tee" ? payer.wallet.address : payer.address;
26960
27089
  const rpc = lazyEvmRpc(ctx, flags["--rpc-url"]);
26961
27090
  const decimals = await hoodDecimals(from, rpc);
@@ -26995,7 +27124,7 @@ async function hoodSwap(ctx, args) {
26995
27124
  ...payer.kind === "embedded" ? { deferExecution: true } : {}
26996
27125
  });
26997
27126
  if (built.job || built.status === "executed")
26998
- return printTradingResult(ctx, { ...built, clientTradeId: id, kind });
27127
+ return replay({ ...built, clientTradeId: id, kind });
26999
27128
  const body = kind === "swap" ? built.payload : built;
27000
27129
  const data = swapBuildSchema.parse(body);
27001
27130
  const echoed = kind === "swap" ? body?.recipient : body?.walletAddress;
@@ -27079,7 +27208,7 @@ async function hoodSwap(ctx, args) {
27079
27208
  treasury: () => hoodFeeTreasury(rpc()),
27080
27209
  curve: () => registeredCurveFor(rpc(), token2)
27081
27210
  });
27082
- const run = await runSequencedLegs(ctx, key, {
27211
+ const execute = () => runSequencedLegs(ctx, key, {
27083
27212
  wallet: wallet3,
27084
27213
  first: sequenced,
27085
27214
  submitPath: kind === "swap" ? "/api/v1/agent/swap/submit" : "/api/v1/trade/agent/submit",
@@ -27095,6 +27224,16 @@ async function hoodSwap(ctx, args) {
27095
27224
  notices.push(notice2);
27096
27225
  }
27097
27226
  });
27227
+ let run;
27228
+ try {
27229
+ run = await execute();
27230
+ } catch (error) {
27231
+ if (args.wait && error instanceof TradingError && (error.exitCode === 3 || error.details?.stage === "reverted"))
27232
+ return waitForHoodOperation(ctx, key, sequenced.operationId, error.details);
27233
+ throw error;
27234
+ }
27235
+ if (args.wait)
27236
+ return waitForHoodOperation(ctx, key, sequenced.operationId);
27098
27237
  const settled = kind === "swap" ? reportSettlement(ctx, run.final.settlement, { asset: to.asset, decimals: outDecimals }, id) : {};
27099
27238
  return printTradingResult(ctx, {
27100
27239
  ...run.final,
@@ -27353,18 +27492,21 @@ async function bridgeSwap(ctx, args) {
27353
27492
  return printTradingResult(ctx, { ...receipt, waited: { final: false, error: reason } });
27354
27493
  }
27355
27494
  }
27356
- async function hoodOperationRead(ctx, id, candidateHash, recover = false) {
27495
+ async function hoodOperationRead(ctx, id, candidateHash, recover = false, wait = false) {
27357
27496
  if (!validClientId(id)) {
27358
27497
  writeUsageFailure(ctx.deps, "An operationId is required", ctx.json);
27359
27498
  return 2;
27360
27499
  }
27361
27500
  try {
27362
27501
  const key = await tradingKey(ctx);
27502
+ if (wait)
27503
+ return await waitForHoodOperation(ctx, key, id);
27363
27504
  const path = `/api/v1/agent/hood-tee/operations/${encodeURIComponent(id)}${recover ? "/recover" : ""}`;
27364
27505
  const result2 = await request(ctx, key, path, recover ? candidateHash ? { candidateHash } : {} : undefined);
27365
27506
  const operation = result2.operation;
27366
27507
  const guard = operation?.guard;
27367
- ctx.deps.stdout.write(`${JSON.stringify(result2, null, ctx.json ? undefined : 2)}
27508
+ const output = JSON.stringify(result2, null, ctx.json ? undefined : 2);
27509
+ ctx.deps.stdout.write(`${ctx.json ? output : terminalLines(output)}
27368
27510
  `);
27369
27511
  if (guard)
27370
27512
  ctx.deps.stderr.write(`${safeText(guard.reason)}: ${safeText(guard.clearingCondition)} Read: candle swap status --operation ${id}.
@@ -27396,6 +27538,7 @@ var init_swap = __esm(() => {
27396
27538
  init_client();
27397
27539
  init_evm_lite();
27398
27540
  init_hood_legs();
27541
+ init_hood_operation_wait();
27399
27542
  init_human_output();
27400
27543
  init_presentation();
27401
27544
  init_profiles();
@@ -47613,11 +47756,10 @@ function mintedByLabel(mintedBy, ownDeviceTokenPrefix) {
47613
47756
  function labelCell(label) {
47614
47757
  if (!label)
47615
47758
  return "";
47616
- const cleaned = Array.from(label).map((ch) => {
47759
+ const cleaned = Array.from(escapeTerminalText(label)).map((ch) => {
47617
47760
  const code = ch.codePointAt(0) ?? 0;
47618
- const isControl = code < 32 || code >= 127 && code <= 159;
47619
47761
  const isBidiOrInvisible = code >= 8203 && code <= 8207 || code >= 8234 && code <= 8238 || code >= 8294 && code <= 8297 || code === 65279;
47620
- return isControl || isBidiOrInvisible ? " " : ch;
47762
+ return isBidiOrInvisible ? " " : ch;
47621
47763
  }).join("");
47622
47764
  return cleaned.replace(/\s+/g, " ").trim();
47623
47765
  }
@@ -77051,7 +77193,7 @@ var LAUNCH_DEFINITION2 = {
77051
77193
  {
77052
77194
  key: "curve.virtualQuote",
77053
77195
  type: "decimal",
77054
- help: "exp-custom's virtual quote, in whole quote units: 1 to 100 SOL or 66000 to 6600000 CNDL; requires --curve-g and EXP_CUSTOM_CURVES.",
77196
+ help: "exp-custom's virtual quote, in whole quote units: 1 to 100 SOL or 66000 to 6600000 CNDL; requires --curve-g.",
77055
77197
  modes: [
77056
77198
  "exp-custom"
77057
77199
  ],
@@ -77090,7 +77232,7 @@ var LAUNCH_DEFINITION2 = {
77090
77232
  {
77091
77233
  key: "curve.threshold",
77092
77234
  type: "decimal",
77093
- help: "exp-custom's graduation threshold, in whole quote units: 1 to 250 SOL or 66000 to 16500000 CNDL; requires --curve-v and EXP_CUSTOM_CURVES.",
77235
+ help: "exp-custom's graduation threshold, in whole quote units: 1 to 250 SOL or 66000 to 16500000 CNDL; requires --curve-v.",
77094
77236
  modes: [
77095
77237
  "exp-custom"
77096
77238
  ],
@@ -78082,7 +78224,7 @@ var HELP = {
78082
78224
  group: "Trade",
78083
78225
  intro: "Quote, swap and bridge on Solana or Hood; read an operation",
78084
78226
  summary: "Quote, confirm and swap on Solana or Hood, bridge between them; read an operation by id",
78085
- description: "Swaps run through a TEE wallet's bound key: the quote is shown and confirmed before anything is sent. The first buy after a launch is this command, unless the launch carried it with candle launch --buy. The assets decide the chain (ETH, USDG or a 0x token is Hood; SOL, USDC, CNDL or a mint is Solana) and a named wallet must be on it. A Hood TEE wallet signs one leg at a time: approve, Permit2, trade, then the fee, each only after the one before it landed. SOL or USDC to ETH or USDG, or back, is a bridge through Relay from a TEE wallet into this key's own TEE wallet on the other chain (--to names it when there are several); Candle charges no fee, only configured raw caps and USD limits apply (unset means unlimited, zero still binds), and this machine checks Relay's deposit before it is signed. The bridge prints its deposit and returns while Relay fills; --wait follows it for up to ten minutes. While it is open, a sweep of either wallet refuses BRIDGE_IN_FLIGHT for two hours. Vault to vault: fund the Solana TEE wallet from the vault, bridge, then disable and sweep the Hood TEE wallet into its vault. Every plan can trade Candle-launched tokens and base assets, and buy or sell a token that was not launched on Candle. Each trade pays the plan's agent fee on top; a base-pair swap or a bridge between this key's own wallets pays none. candle plans shows the fees in force. With --dry-run, stdout reports per-check results and the quote; nothing is signed or sent. Otherwise with --json, stdout is one JSON value for every swap, trade, replay and status read: 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.",
78227
+ description: "Swaps run through a TEE wallet's bound key: the quote is shown and confirmed before anything is sent. The first buy after a launch is this command, unless the launch carried it with candle launch --buy. The assets decide the chain (ETH, USDG or a 0x token is Hood; SOL, USDC, CNDL or a mint is Solana) and a named wallet must be on it. A Hood TEE wallet signs one leg at a time: approve, Permit2, trade, then the fee, each only after the one before it landed. SOL or USDC to ETH or USDG, or back, is a bridge through Relay from a TEE wallet into this key's own TEE wallet on the other chain (--to names it when there are several); Candle charges no fee, only configured raw caps and USD limits apply (unset means unlimited, zero still binds), and this machine checks Relay's deposit before it is signed. The bridge prints its deposit and returns while Relay fills; --wait follows it for up to ten minutes. While it is open, a sweep of either wallet refuses BRIDGE_IN_FLIGHT for two hours. On a Hood TEE swap, --wait follows the trade and fee legs for up to ten minutes and prints the same operation response as candle swap status --operation <id>; exit 0 means completed, 1 failed, and 3 partial execution, unresolved settlement or timeout. Embedded and Solana same-chain swaps use candle swap status <client-trade-id>. Vault to vault: fund the Solana TEE wallet from the vault, bridge, then disable and sweep the Hood TEE wallet into its vault. Every plan can trade Candle-launched tokens and base assets, and buy or sell a token that was not launched on Candle. Each trade pays the plan's agent fee on top; a base-pair swap or a bridge between this key's own wallets pays none. candle plans shows the fees in force. With --dry-run, stdout reports per-check results and the quote; nothing is signed or sent. Otherwise with --json, stdout is one JSON value: swaps, trades, replays and job status reads carry kind, status, clientTradeId, signature (the primary transaction, or null), signatures (a list) and raw (the original response); Hood --wait and explicit --operation reads return the operation response. Every human line is on stderr. The keys older versions printed stay beside them.",
78086
78228
  usage: [
78087
78229
  "candle swap <from> <to> [flags]",
78088
78230
  "candle swap status <id>",
@@ -78097,8 +78239,8 @@ var HELP = {
78097
78239
  description: "Quote, confirm and swap on Solana or Hood, or bridge between them"
78098
78240
  },
78099
78241
  {
78100
- invocation: "status <id> [--kind trade|swap|launch] [--wait] | status --operation <operationId>",
78101
- description: "Read without resending; --wait follows a bridge. --operation reads a Hood TEE guard with no signing or sending, and exits 3 while held"
78242
+ invocation: "status <id> [--kind trade|swap|launch] [--wait] | status --operation <operationId> [--wait]",
78243
+ description: "Read without resending; --wait follows a bridge. --operation reads a Hood TEE guard; add --wait to follow all legs for up to ten minutes (0 completed, 1 failed, 3 partial or unresolved)"
78102
78244
  },
78103
78245
  {
78104
78246
  invocation: "recover --operation <operationId> [--hash <0x…>]",
@@ -78947,9 +79089,12 @@ var HELP = {
78947
79089
  },
78948
79090
  {
78949
79091
  invocation: "stop <prefix|label>",
78950
- description: "Pause one key with the device token; reversible with keys resume"
79092
+ description: "Pause one key with the device token (starting it again is done in the signed-in console)"
79093
+ },
79094
+ {
79095
+ invocation: "resume <prefix|label>",
79096
+ description: "Resume a stopped key in the signed-in console, because the server refuses a device token here (LOOSEN_REQUIRES_SESSION)"
78951
79097
  },
78952
- { invocation: "resume <prefix|label>", description: "Resume a stopped key with the device token" },
78953
79098
  {
78954
79099
  invocation: "rotate <prefix|label> ([--grace <duration> | --now] [--force] [--yes] | --finish)",
78955
79100
  description: "Give a key a new secret, keeping its prefix, label, access, limits, wallets and history. The old secret works through --grace (default 1h, at most 24h) or stops at once with --now; --finish ends an open window early. The profile's own key is replaced in the keychain; update CANDLE_API_KEY or CANDLE_API_KEY_FILE yourself and restart MCP. --force rotates again while a window is open"
@@ -79038,7 +79183,7 @@ var HELP = {
79038
79183
  },
79039
79184
  {
79040
79185
  invocation: "close-empty [--wallet embedded|<tee address|label>] [--keep <mint>]... [--client-trade-id <id>] [--preview] [--yes]",
79041
- description: "Close a wallet's empty token accounts and return their rent to it, after a preview: the embedded wallet (transfer:write), or a TEE wallet bound to this key, signed by its key signer (swap:write). --preview prints what would close (one JSON document on stdout under --json) and closes nothing"
79186
+ description: "Close a wallet's empty token accounts and return their rent to it, after a preview: the embedded wallet (swap:write or transfer:write), or a TEE wallet bound to this key, signed by its key signer (swap:write). --preview prints what would close (one JSON document on stdout under --json) and closes nothing"
79042
79187
  }
79043
79188
  ],
79044
79189
  flags: [
@@ -79252,8 +79397,8 @@ var HELP = {
79252
79397
  usage: ["candle tee <subcommand> [flags]"],
79253
79398
  rows: [
79254
79399
  {
79255
- invocation: "check <address|id|label>... [--label-prefix <p>] [--sign] [--max <n>] [--rpc-url <url>]",
79256
- description: "Check payable membership, record parity and this machine's signer without spending. --sign builds a tiny SOL/USDC quote, checks spend, signs and simulates; never broadcasts (default cap 10). Needs swap:write"
79400
+ invocation: "check <address|id|label>... [--label-prefix <p>] [--chain solana|hood] [--sign] [--max <n>] [--rpc-url <url>]",
79401
+ description: "Check payable membership, record parity and this machine's signer without spending. Each wallet is checked on its own chain; --chain checks every wallet against one chain (CHAIN_MISMATCH otherwise). --sign builds a tiny SOL/USDC quote on a Solana wallet, checks spend, signs and simulates; never broadcasts (default cap 10). A Hood wallet skips sign: a Hood build holds the wallet. Needs swap:write"
79257
79402
  },
79258
79403
  {
79259
79404
  invocation: "new [--label <name>]",
@@ -79319,6 +79464,7 @@ var HELP = {
79319
79464
  examples: [
79320
79465
  "candle tee check --label-prefix pool- --json",
79321
79466
  "candle tee check AgentOne --sign --rpc-url <url>",
79467
+ "candle tee check hood-1 --chain hood --json",
79322
79468
  "candle tee new --label AgentOne",
79323
79469
  "candle tee status AgentOneAddress",
79324
79470
  "candle tee sweep AgentOneAddress",
@@ -86292,7 +86438,7 @@ ${USAGE6}`, json);
86292
86438
  } catch (error) {
86293
86439
  if (!(error instanceof CoreClientError))
86294
86440
  throw error;
86295
- deps.stderr.write(`${error.code}: ${error.message}
86441
+ deps.stderr.write(`${terminalLines(`${error.code}: ${error.message}`)}
86296
86442
  `);
86297
86443
  return 1;
86298
86444
  }
@@ -92005,7 +92151,7 @@ async function help(args, ctx) {
92005
92151
  }
92006
92152
  const topic = renderTopic(word3, terminalStyle(ctx.deps, "stdout", ctx.json), args.includes("--more"));
92007
92153
  if (topic === undefined) {
92008
- ctx.deps.stderr.write(`Unknown command: ${word3}
92154
+ ctx.deps.stderr.write(`Unknown command: ${escapeTerminalText(word3)}
92009
92155
  `);
92010
92156
  ctx.deps.stderr.write(renderTopLevel());
92011
92157
  return 1;
@@ -92335,7 +92481,7 @@ async function keysAccessHistory(target, ctx) {
92335
92481
  `);
92336
92482
  return 0;
92337
92483
  }
92338
- const rows = result2.body.changes ?? [];
92484
+ const rows = (result2.body.changes ?? []).filter((row2) => row2.eventKind !== "profile_edit" && row2.change !== "profile_edit");
92339
92485
  if (rows.length === 0) {
92340
92486
  notice(deps, "stdout", "note", `No access changes on key ${resolved.keyPrefix}.`);
92341
92487
  return 0;
@@ -92645,7 +92791,7 @@ function writeRefusal(ctx, refusal3, suggestion) {
92645
92791
  `);
92646
92792
  return;
92647
92793
  }
92648
- ctx.deps.stderr.write(`${suggestion ? `${message} ${suggestion}` : message}
92794
+ ctx.deps.stderr.write(`${escapeTerminalText(suggestion ? `${message} ${suggestion}` : message)}
92649
92795
  `);
92650
92796
  }
92651
92797
  function writeApiFailure(ctx, result2, authType) {
@@ -92740,8 +92886,8 @@ function identity2(who) {
92740
92886
  if (who.kind === "candle")
92741
92887
  return "Candle";
92742
92888
  if (who.kind === "agent")
92743
- return terminalText(who.name ?? "Agent");
92744
- return terminalText(who.name ?? who.account);
92889
+ return escapeTerminalText(who.name ?? "Agent");
92890
+ return escapeTerminalText(who.name ?? who.account);
92745
92891
  }
92746
92892
  function sectionText(bytes, section2) {
92747
92893
  if (bytes.length > TOKEN_PAGE_BODY_CAP_BYTES)
@@ -92780,7 +92926,7 @@ function factValue(fact) {
92780
92926
  if (fact.raw === undefined)
92781
92927
  return fact.status;
92782
92928
  const value = fact.decimals ? rawUnits2(fact.raw, fact.decimals) : fact.raw;
92783
- return `${value}${fact.unit === "token" ? "" : ` ${fact.unit}`} (${fact.status}, ${fact.coverage}, ${terminalText(fact.source)}${fact.measuredAt ? `, ${fact.measuredAt}` : ""})`;
92929
+ return `${value}${fact.unit === "token" ? "" : ` ${fact.unit}`} (${fact.status}, ${fact.coverage}, ${escapeTerminalText(fact.source)}${fact.measuredAt ? `, ${fact.measuredAt}` : ""})`;
92784
92930
  }
92785
92931
  function rawUnits2(raw, decimals) {
92786
92932
  if (!/^\d+$/.test(raw))
@@ -92822,7 +92968,7 @@ function writeAbout(ctx, about) {
92822
92968
  if (v.linksWithheld)
92823
92969
  notice(ctx.deps, "stdout", "note", "Links are withheld pending review.");
92824
92970
  ctx.deps.stdout.write(`
92825
- ${terminalTextKeepingLines(v.text)}
92971
+ ${escapeTerminalText(v.text)}
92826
92972
  `);
92827
92973
  }
92828
92974
  writeHeading(ctx.deps, "stdout", "Supply, measured by Candle");
@@ -92831,12 +92977,7 @@ ${terminalTextKeepingLines(v.text)}
92831
92977
  ctx.deps.stdout.write(` Measurements unavailable
92832
92978
  `);
92833
92979
  else
92834
- writeFields(ctx.deps, "stdout", facts.map((f) => [terminalText(f.metric), factValue(f)]));
92835
- }
92836
- function terminalTextKeepingLines(text3) {
92837
- return text3.split(`
92838
- `).map((line) => terminalText(line.replace(/\t/g, " "))).join(`
92839
- `);
92980
+ writeFields(ctx.deps, "stdout", facts.map((f) => [escapeTerminalText(f.metric), factValue(f)]));
92840
92981
  }
92841
92982
  async function tokenAbout(args, ctx) {
92842
92983
  const usage4 = usageFor(ctx, USAGE9.about);
@@ -93150,12 +93291,12 @@ async function tokenHistory(args, ctx) {
93150
93291
  ]);
93151
93292
  if (item.text !== null)
93152
93293
  ctx.deps.stdout.write(`
93153
- ${terminalTextKeepingLines(item.text)}
93294
+ ${escapeTerminalText(item.text)}
93154
93295
  `);
93155
93296
  }
93156
93297
  if (data.nextCursor)
93157
93298
  ctx.deps.stdout.write(`
93158
- More: --cursor ${data.nextCursor}
93299
+ More: --cursor ${escapeTerminalText(data.nextCursor)}
93159
93300
  `);
93160
93301
  return 0;
93161
93302
  }
@@ -93341,12 +93482,12 @@ async function tokenComments(args, ctx) {
93341
93482
  ]);
93342
93483
  if (item.text !== null)
93343
93484
  ctx.deps.stdout.write(`
93344
- ${terminalTextKeepingLines(item.text)}
93485
+ ${escapeTerminalText(item.text)}
93345
93486
  `);
93346
93487
  }
93347
93488
  if (data.nextCursor)
93348
93489
  ctx.deps.stdout.write(`
93349
- More: --cursor ${data.nextCursor}
93490
+ More: --cursor ${escapeTerminalText(data.nextCursor)}
93350
93491
  `);
93351
93492
  return 0;
93352
93493
  }
@@ -101467,7 +101608,7 @@ async function read2(ctx, argv, command) {
101467
101608
  for (const warning of result2.warnings)
101468
101609
  notice(ctx.deps, "stderr", "note", `${safeText(warning)}
101469
101610
  `.trim());
101470
- const out = (line) => ctx.deps.stdout.write(`${line}
101611
+ const out = (line) => ctx.deps.stdout.write(`${terminalLines(line)}
101471
101612
  `);
101472
101613
  out(`Hyperliquid ${result2.network}; OI floor $${result2.gate.minOpenInterestUsd} (${result2.gate.source}). Tradeability is Candle's build gate at read time, not a promise. OI caps may still refuse an order.`);
101473
101614
  if ("market" in result2) {
@@ -102885,7 +103026,7 @@ async function runPaidRead(ctx, input) {
102885
103026
  ctx.deps.stdout.write(`${JSON.stringify({ ok: true, data, ...paid.payment ? { payment: paid.payment } : {}, ...paid.servedFromCredit ? { servedFromCredit: true } : {} })}
102886
103027
  `);
102887
103028
  } else {
102888
- ctx.deps.stdout.write(`${JSON.stringify(data, null, 2)}
103029
+ ctx.deps.stdout.write(`${terminalLines(JSON.stringify(data, null, 2))}
102889
103030
  `);
102890
103031
  }
102891
103032
  return 0;
@@ -104299,6 +104440,9 @@ function financialHeading(text4, style) {
104299
104440
  function signed(text4, style) {
104300
104441
  return /^\+/.test(text4) ? accent(text4, style, "positive") : /^[-−]/.test(text4) ? accent(text4, style, "negative") : text4;
104301
104442
  }
104443
+ function signedField(text4) {
104444
+ return /^\+/.test(text4) ? { text: text4, role: "positive" } : /^[-−]/.test(text4) ? { text: text4, role: "negative" } : text4;
104445
+ }
104302
104446
  function financialTable(ctx, headers2, rows, columns) {
104303
104447
  const style = terminalStyle(ctx.deps, "stdout");
104304
104448
  const clean3 = rows.map((row2) => row2.map(terminalText));
@@ -104315,7 +104459,7 @@ function financialTable(ctx, headers2, rows, columns) {
104315
104459
  if (style.width >= 60 && table.split(`
104316
104460
  `).every((line) => displayWidth(line) <= style.width))
104317
104461
  return table;
104318
- return clean3.map((row2) => fields(headers2.map((label, col) => [label, specs[col]?.signed ? signed(row2[col] ?? "", style) : row2[col]]), style).join(`
104462
+ return clean3.map((row2) => fields(headers2.map((label, col) => [label, specs[col]?.signed ? signedField(row2[col] ?? "") : row2[col]]), style).join(`
104319
104463
  `)).join(`
104320
104464
 
104321
104465
  `);
@@ -104329,7 +104473,7 @@ function writeFinancialLines(ctx, rows) {
104329
104473
  for (const [label = "", value = "", note = ""] of rows) {
104330
104474
  const head = ` ${dim(padDisplay(label, labelWidth), style)} ${" ".repeat(valueWidth - displayWidth(value))}${signed(value, style)}`;
104331
104475
  if (displayWidth(head) > style.width) {
104332
- ctx.deps.stdout.write(`${fields([[label, signed(value, style)]], style).join(`
104476
+ ctx.deps.stdout.write(`${fields([[label, signedField(value)]], style).join(`
104333
104477
  `)}
104334
104478
  `);
104335
104479
  if (note)
@@ -106319,10 +106463,12 @@ async function quantAsk(args, ctx) {
106319
106463
  })}
106320
106464
  `);
106321
106465
  } else {
106322
- ctx.deps.stdout.write(`${answer2}
106466
+ ctx.deps.stdout.write(`${result2.paragraphs.map(escapeTerminalText).join(`
106467
+
106468
+ `)}
106323
106469
  `);
106324
106470
  if (remaining !== undefined) {
106325
- ctx.deps.stderr.write(`${remaining} Quant answer${remaining === 1 ? "" : "s"} left today${resetsAt !== undefined ? `, resets at ${resetsAt}` : ""}.
106471
+ ctx.deps.stderr.write(`${remaining} Quant answer${remaining === 1 ? "" : "s"} left today${resetsAt !== undefined ? `, resets at ${escapeTerminalText(String(resetsAt))}` : ""}.
106326
106472
  `);
106327
106473
  }
106328
106474
  }
@@ -108476,10 +108622,10 @@ function eventLine(event, widths) {
108476
108622
  const { doing, detail } = action(event);
108477
108623
  return [
108478
108624
  formatTime(event.at).padEnd(widths.time),
108479
- event.kind.padEnd(widths.kind),
108480
- who(event).padEnd(widths.who),
108481
- doing,
108482
- detail
108625
+ escapeTerminalText(event.kind).padEnd(widths.kind),
108626
+ escapeTerminalText(who(event)).padEnd(widths.who),
108627
+ escapeTerminalText(doing),
108628
+ escapeTerminalText(detail)
108483
108629
  ].filter((cell) => cell.trim().length > 0).join(" ").trimEnd();
108484
108630
  }
108485
108631
  async function tokenEvents(args, ctx) {
@@ -108560,7 +108706,7 @@ async function tokenEvents(args, ctx) {
108560
108706
  const widths = {
108561
108707
  time: Math.max(...events.map((event) => formatTime(event.at).length)),
108562
108708
  kind: Math.max(...events.map((event) => event.kind.length)),
108563
- who: Math.max(...events.map((event) => who(event).length))
108709
+ who: Math.max(...events.map((event) => escapeTerminalText(who(event)).length))
108564
108710
  };
108565
108711
  for (const event of events)
108566
108712
  out.write(`${eventLine(event, widths)}
@@ -108758,7 +108904,7 @@ ${retry}
108758
108904
  ["Result", payload.changed ? "edited" : "nothing changed"],
108759
108905
  ["Version", payload.version === null ? "-" : String(payload.version)],
108760
108906
  ...payload.changed ? [["Changed", changedOf(body)]] : [],
108761
- ["Description", payload.description ? terminalText(payload.description) : "-"],
108907
+ ["Description", payload.description ? escapeTerminalText(payload.description) : "-"],
108762
108908
  ...linkRows(payload.socials)
108763
108909
  ]);
108764
108910
  return 0;
@@ -108812,18 +108958,18 @@ async function tokenHistory2(args, ctx) {
108812
108958
  writeHeading(ctx.deps, "stdout", `version ${item.version}`);
108813
108959
  writeFields(ctx.deps, "stdout", [
108814
108960
  ["At", item.createdAt],
108815
- ["By", item.author?.name ? terminalText(item.author.name) : "Agent"],
108961
+ ["By", item.author?.name ? escapeTerminalText(item.author.name) : "Agent"],
108816
108962
  ["Changed", item.changedFields.join(", ")],
108817
108963
  ...item.changedFields.map((field) => {
108818
108964
  const now = field === "description" ? item.description : item.socials[field.slice("socials.".length)];
108819
108965
  const was = field === "description" ? item.prevDescription : item.prevSocials[field.slice("socials.".length)];
108820
- return [field, `${was ? terminalText(was) : "-"} -> ${now ? terminalText(now) : "-"}`];
108966
+ return [field, `${was ? escapeTerminalText(was) : "-"} -> ${now ? escapeTerminalText(now) : "-"}`];
108821
108967
  })
108822
108968
  ]);
108823
108969
  }
108824
108970
  if (data.nextCursor)
108825
108971
  ctx.deps.stdout.write(`
108826
- More: --cursor ${data.nextCursor}
108972
+ More: --cursor ${escapeTerminalText(data.nextCursor)}
108827
108973
  `);
108828
108974
  return 0;
108829
108975
  }
@@ -118722,7 +118868,7 @@ async function runWithConfirm(argv, deps) {
118722
118868
  async function runCommand(argv, deps) {
118723
118869
  const extracted = extractGlobalFlags(argv);
118724
118870
  if ("error" in extracted) {
118725
- deps.stderr.write(`${extracted.error}
118871
+ deps.stderr.write(`${escapeTerminalText(extracted.error)}
118726
118872
  `);
118727
118873
  return 2;
118728
118874
  }
@@ -118843,7 +118989,7 @@ async function runCommand(argv, deps) {
118843
118989
  return 1;
118844
118990
  }
118845
118991
  if (verdict.warning)
118846
- deps.stderr.write(`${verdict.warning}
118992
+ deps.stderr.write(`${terminalLines(verdict.warning)}
118847
118993
  `);
118848
118994
  }
118849
118995
  if (ROUTED_COMMANDS.has(word3) && routesToCommand(cmd, sub))
@@ -118874,7 +119020,7 @@ function splitFix(message) {
118874
119020
  }
118875
119021
  function unknownCommand(deps, token2, word3) {
118876
119022
  if (token2 !== undefined)
118877
- deps.stderr.write(`Unknown command: ${token2}
119023
+ deps.stderr.write(`Unknown command: ${escapeTerminalText(token2)}
118878
119024
  `);
118879
119025
  deps.stderr.write((word3 === undefined ? undefined : renderTopic(word3)) ?? renderTopLevel());
118880
119026
  return 1;
@@ -119090,12 +119236,12 @@ if (isMainModule) {
119090
119236
  process.exitCode = 1;
119091
119237
  if (stream === process.stderr)
119092
119238
  return;
119093
- process.stderr.write(`Unexpected error: ${err?.message ?? String(err)}
119239
+ process.stderr.write(`Unexpected error: ${escapeTerminalText(err?.message ?? String(err))}
119094
119240
  `);
119095
119241
  });
119096
119242
  }
119097
119243
  await main().catch((err) => {
119098
- process.stderr.write(`Unexpected error: ${err instanceof Error ? err.message : String(err)}
119244
+ process.stderr.write(`Unexpected error: ${escapeTerminalText(err instanceof Error ? err.message : String(err))}
119099
119245
  `);
119100
119246
  process.exitCode = 1;
119101
119247
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@candledottv/cli",
3
- "version": "0.13.24",
3
+ "version": "0.13.25",
4
4
  "description": "The Candle CLI: authorize a device from your browser, then manage API keys, wallets, and setup health from the terminal",
5
5
  "type": "module",
6
6
  "bin": {