@candledottv/cli 0.13.16 → 0.13.18

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 +28 -13
  2. package/dist/index.js +543 -173
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -532,7 +532,7 @@ var init_terminal_theme = __esm(() => {
532
532
  });
533
533
 
534
534
  // src/version.ts
535
- var CLI_VERSION = "0.13.16";
535
+ var CLI_VERSION = "0.13.18";
536
536
 
537
537
  // src/presentation.ts
538
538
  function terminalStyle(deps, stream, machine = false) {
@@ -11163,6 +11163,25 @@ var init_solana_alt = __esm(() => {
11163
11163
  ];
11164
11164
  });
11165
11165
 
11166
+ // src/account.ts
11167
+ async function fetchAccount(deps, apiUrl, apiKey) {
11168
+ const identity = await apiRequest("/api/v1/agent/wallets/embedded", {
11169
+ auth: "key",
11170
+ credentials: { apiKey },
11171
+ apiUrl,
11172
+ fetch: deps.fetch,
11173
+ env: deps.env
11174
+ });
11175
+ const body = identity.ok ? identity.body : undefined;
11176
+ const account = body?.account;
11177
+ if (account)
11178
+ return { account, ...body?.username ? { username: body.username } : {} };
11179
+ return { failure: identity.ok ? "no account in the response" : identity.message };
11180
+ }
11181
+ var init_account = __esm(() => {
11182
+ init_client();
11183
+ });
11184
+
11166
11185
  // src/profiles.ts
11167
11186
  function assertOrdinaryProfile(config, profile) {
11168
11187
  if (profile !== undefined && config.profiles?.[profile]?.apiKeyItem !== undefined) {
@@ -11297,7 +11316,7 @@ function credentialEnvOverrides(env) {
11297
11316
  }
11298
11317
  function identityLine(profile, account, apiUrl, overrides, username, options = {}) {
11299
11318
  const address = account === undefined ? undefined : shortId(account, options.full);
11300
- const shown = overrides?.length ? `unknown (${overrides.join(", ")} override)` : username && address ? `${username} (${address})` : address ?? "unknown";
11319
+ const shown = overrides?.length ? address ? `${address} (${overrides.join(", ")} override)` : `unknown (${overrides.join(", ")} override)` : username && address ? `${username} (${address})` : address ?? "unknown";
11301
11320
  const name = profile ?? "none";
11302
11321
  const host = apiUrl.replace(/^https:\/\//i, "").replace(/\/+$/, "");
11303
11322
  const sep = options.style ? symbols(options.style).note : "·";
@@ -11308,7 +11327,20 @@ async function printIdentity(ctx) {
11308
11327
  return;
11309
11328
  const config = await ctx.deps.readConfig();
11310
11329
  const fields2 = effectiveProfileFields(config, ctx.profile);
11311
- const line = identityLine(ctx.profile, fields2.account, ctx.apiUrl, credentialEnvOverrides(ctx.deps.env), fields2.username, identityStyle(ctx.deps, "stdout"));
11330
+ const overrides = credentialEnvOverrides(ctx.deps.env);
11331
+ let account = fields2.account;
11332
+ let username = fields2.username;
11333
+ if (overrides.length > 0) {
11334
+ account = undefined;
11335
+ username = undefined;
11336
+ const key = ctx.deps.env.CANDLE_API_KEY?.trim();
11337
+ if (key) {
11338
+ const lookup = await fetchAccount(ctx.deps, ctx.apiUrl, key);
11339
+ if (lookup.account)
11340
+ account = lookup.account;
11341
+ }
11342
+ }
11343
+ const line = identityLine(ctx.profile, account, ctx.apiUrl, overrides.length > 0 ? overrides : undefined, username, identityStyle(ctx.deps, "stdout"));
11312
11344
  ctx.deps.stdout.write(`${line}
11313
11345
 
11314
11346
  `);
@@ -11352,6 +11384,7 @@ function profileTable(config, now) {
11352
11384
  }
11353
11385
  var ProfileFleetOnlyError, PRE_PROFILE_FIELDS, API_KEY_PREFIXES, API_KEY_RANDOM_LENGTH = 43, INVALID_RPC_HOST = "invalid (fix with profile set)";
11354
11386
  var init_profiles = __esm(() => {
11387
+ init_account();
11355
11388
  init_presentation();
11356
11389
  init_render();
11357
11390
  ProfileFleetOnlyError = class ProfileFleetOnlyError extends Error {
@@ -12603,9 +12636,9 @@ function kekReuseKey(passphrase, kdf) {
12603
12636
  const inputs = JSON.stringify([passphrase, kdf.salt, kdf.m, kdf.t, kdf.p, kdf.version]);
12604
12637
  return b64u(sha2562(new TextEncoder().encode(inputs)));
12605
12638
  }
12606
- async function derivePassphraseKek(passphrase, kdf, notice2) {
12639
+ async function derivePassphraseKek(passphrase, kdf, notice2, derivationLabel = "Deriving the vault key") {
12607
12640
  assertKdfInBounds(kdf);
12608
- notice2?.(`Deriving the vault key (Argon2id, ${Math.round(kdf.m / 1024)} MiB)
12641
+ notice2?.(`${derivationLabel} (Argon2id, ${Math.round(kdf.m / 1024)} MiB)
12609
12642
  `);
12610
12643
  const reuseKey = testKekReuse ? kekReuseKey(passphrase, kdf) : null;
12611
12644
  const reused = reuseKey ? testKekReuse?.get(reuseKey) : undefined;
@@ -13990,7 +14023,7 @@ function closeVault(vault) {
13990
14023
  async function unlockVault(path, raw, request, opts = {}) {
13991
14024
  const file = parseVaultFile(raw);
13992
14025
  const envelope = pickEnvelope(file, request);
13993
- const dek = await unwrapDek(file, envelope, request, opts.notice);
14026
+ const dek = await unwrapDek(file, envelope, request, opts.notice, opts.derivationLabel);
13994
14027
  if (dek.length !== DEK_BYTES) {
13995
14028
  wipe(dek);
13996
14029
  throw new VaultError("VAULT_UNLOCK_FAILED", "The unwrapped key is the wrong length; this file is corrupt.");
@@ -14030,9 +14063,9 @@ async function openWithDek(path, raw, file, envelope, dek) {
14030
14063
  throw error;
14031
14064
  }
14032
14065
  }
14033
- async function unwrapDek(file, envelope, request, notice2) {
14066
+ async function unwrapDek(file, envelope, request, notice2, derivationLabel) {
14034
14067
  if (request.factor === "passphrase") {
14035
- const kek = await derivePassphraseKek(request.passphrase, passphraseKdf(envelope), notice2);
14068
+ const kek = await derivePassphraseKek(request.passphrase, passphraseKdf(envelope), notice2, derivationLabel);
14036
14069
  return withSecret(kek, async (kekBytes) => {
14037
14070
  const kekKey2 = await importAesKey(kekBytes);
14038
14071
  return open(kekKey2, envelope.wrap, envelopeAad(file, envelope), {
@@ -28989,13 +29022,13 @@ __export(exports_vault_support, {
28989
29022
  runVaultCommand: () => runVaultCommand,
28990
29023
  requireVaultRaw: () => requireVaultRaw,
28991
29024
  requireTty: () => requireTty,
28992
- requirePromptStreams: () => requirePromptStreams,
28993
29025
  refusePromptsWithoutTerminal: () => refusePromptsWithoutTerminal,
28994
29026
  refuseEnvPassphrase: () => refuseEnvPassphrase,
28995
29027
  openWithTypedPassphrase: () => openWithTypedPassphrase,
28996
29028
  nonDefaultVaultFooter: () => nonDefaultVaultFooter,
28997
29029
  missingVault: () => missingVault,
28998
29030
  menuSafeText: () => menuSafeText,
29031
+ isNoTerminalRefusal: () => isNoTerminalRefusal,
28999
29032
  groupAddress: () => groupAddress,
29000
29033
  findExternalEntry: () => findExternalEntry,
29001
29034
  factorMenu: () => factorMenu,
@@ -29039,22 +29072,23 @@ function requireTty(ctx, what) {
29039
29072
  }));
29040
29073
  return false;
29041
29074
  }
29042
- function requirePromptStreams(ctx, what) {
29043
- if (ctx.deps.isTTY.stdin && ctx.deps.isTTY.stderr)
29044
- return true;
29045
- writeVaultFailure(ctx, new VaultError("VAULT_UNLOCK_FAILED", `${what} needs a terminal for the passphrase prompt: standard input and standard error must both be a terminal. Standard output may be redirected; that is where the listing goes.`));
29046
- return false;
29047
- }
29048
29075
  function refusePromptsWithoutTerminal(ctx, what) {
29049
29076
  if (ctx.deps.isTTY.stdin && ctx.deps.isTTY.stderr)
29050
29077
  return ctx;
29051
29078
  const refuse2 = async () => {
29052
- throw new VaultError("VAULT_UNLOCK_FAILED", `${what} needs a terminal here: this unlock reads a vault passphrase, a security key PIN or a choice from a prompt, and standard input and standard error must both be a terminal for that. Standard output may be redirected.`, {
29079
+ throw tagNoTerminal(new VaultError("VAULT_UNLOCK_FAILED", `${what} needs a terminal here: this unlock reads a vault passphrase, a security key PIN or a choice from a prompt, and standard input and standard error must both be a terminal for that. Standard output may be redirected.`, {
29053
29080
  suggestion: "Run it from a terminal, or pass --factor touch-id or --factor passkey to unlock with a factor that reads no prompt. No environment variable or flag supplies a passphrase or PIN."
29054
- });
29081
+ }));
29055
29082
  };
29056
29083
  return { ...ctx, deps: { ...ctx.deps, promptSecret: refuse2, promptLine: refuse2 } };
29057
29084
  }
29085
+ function tagNoTerminal(error) {
29086
+ noTerminalRefusals.add(error);
29087
+ return error;
29088
+ }
29089
+ function isNoTerminalRefusal(error) {
29090
+ return error instanceof VaultError && noTerminalRefusals.has(error);
29091
+ }
29058
29092
  function vaultPathFor(ctx, parsed) {
29059
29093
  const flag = parsed.values["--keystore"];
29060
29094
  if (flag !== undefined)
@@ -29674,7 +29708,7 @@ function takeRepeatedFlag(args, flag) {
29674
29708
  }
29675
29709
  return { values, rest };
29676
29710
  }
29677
- var PASSPHRASE_ONLY_PREFIX = "Passphrase only: ";
29711
+ var noTerminalRefusals, PASSPHRASE_ONLY_PREFIX = "Passphrase only: ";
29678
29712
  var init_vault_support = __esm(() => {
29679
29713
  init_args();
29680
29714
  init_presentation();
@@ -29690,6 +29724,7 @@ var init_vault_support = __esm(() => {
29690
29724
  init_platform();
29691
29725
  init_sidecar();
29692
29726
  init_store();
29727
+ noTerminalRefusals = new WeakSet;
29693
29728
  });
29694
29729
 
29695
29730
  // src/vault/ed25519.ts
@@ -38135,6 +38170,7 @@ async function keysList(args, ctx) {
38135
38170
  async function keysCreate(args, ctx) {
38136
38171
  const { deps, apiUrl, json } = ctx;
38137
38172
  const parsed = parseArgs(args, {
38173
+ booleanFlags: ["--no-store"],
38138
38174
  valueFlags: [
38139
38175
  "--scopes",
38140
38176
  "--access",
@@ -38239,11 +38275,12 @@ async function keysCreate(args, ctx) {
38239
38275
  return 1;
38240
38276
  }
38241
38277
  const body = result.body;
38278
+ const noStore = parsed.booleans.has("--no-store");
38242
38279
  const apiKeyRef = ctx.profile ? profileSecretRef(ctx.profile, "apiKey") : SECRET_REFS.apiKey;
38243
38280
  let stored = false;
38244
38281
  let storeError;
38245
38282
  try {
38246
- if (!await deps.store.get(apiKeyRef)) {
38283
+ if (!noStore && !await deps.store.get(apiKeyRef)) {
38247
38284
  await deps.store.set(apiKeyRef, body.key);
38248
38285
  if (ctx.profile) {
38249
38286
  await deps.updateProfile(ctx.profile, { keyPrefix: body.keyPrefix, scopes: body.scopes });
@@ -38277,7 +38314,7 @@ Or mint it that way: candle keys create --access withdraw --embedded-wallet allo
38277
38314
  ...body.embeddedWallet === "allowed" || body.embeddedWallet === "denied" ? [["Embedded wallet", embeddedWalletSummary(body.embeddedWallet)]] : [],
38278
38315
  [
38279
38316
  "Stored",
38280
- storeError !== undefined ? "no (store failed)" : stored ? `${deps.backend}, as this CLI's working key` : "no (the CLI already manages a different working key)"
38317
+ storeError !== undefined ? "no (store failed)" : stored ? `${deps.backend}, as this CLI's working key` : noStore ? "no (--no-store)" : "no (the CLI already manages a different working key)"
38281
38318
  ]
38282
38319
  ], style).join(`
38283
38320
  `)}
@@ -38564,7 +38601,7 @@ var init_token_clusters = __esm(() => {
38564
38601
  });
38565
38602
 
38566
38603
  // ../mcp/src/version.ts
38567
- var SERVER_VERSION = "0.10.29";
38604
+ var SERVER_VERSION = "0.10.30";
38568
38605
 
38569
38606
  // ../mcp/src/update-notice.ts
38570
38607
  function newer(a, b) {
@@ -40422,6 +40459,12 @@ async function executeTrade(args, cfg, doFetch) {
40422
40459
  let amountRaw;
40423
40460
  let resolved = {};
40424
40461
  let paper = isPaperFlag(args.paper);
40462
+ const deferExecution = isPaperFlag(args.deferExecution);
40463
+ if (deferExecution && paper) {
40464
+ return errText("deferExecution cannot be combined with paper; a paper trade records a quote and never executes", {
40465
+ clientTradeId
40466
+ });
40467
+ }
40425
40468
  try {
40426
40469
  if (args.side === "buy") {
40427
40470
  const amount = args.amount;
@@ -40530,6 +40573,9 @@ async function executeTrade(args, cfg, doFetch) {
40530
40573
  } catch (err) {
40531
40574
  return errText(err instanceof Error ? err.message : String(err), { clientTradeId });
40532
40575
  }
40576
+ if (deferExecution && paper) {
40577
+ return errText("deferExecution needs a live holding, and this sell resolved to the paper book (the wallet holds none of the mint); a paper trade never executes", { clientTradeId });
40578
+ }
40533
40579
  const posted = await postJson(`${base(cfg)}/api/v1/trade/agent/build`, apiKey, {
40534
40580
  clientTradeId,
40535
40581
  mint: args.mint,
@@ -40539,7 +40585,8 @@ async function executeTrade(args, cfg, doFetch) {
40539
40585
  payer: { type: "main" },
40540
40586
  ...args.quoteAsset !== undefined ? { quoteAsset: args.quoteAsset } : {},
40541
40587
  ...args.maxSlippageBps !== undefined ? { maxSlippageBps: args.maxSlippageBps } : {},
40542
- ...paper ? { paper: true } : {}
40588
+ ...paper ? { paper: true } : {},
40589
+ ...deferExecution ? { deferExecution: true } : {}
40543
40590
  }, doFetch);
40544
40591
  if ("thrown" in posted)
40545
40592
  return transportError("clientTradeId", clientTradeId, posted.thrown);
@@ -40791,32 +40838,38 @@ async function executionStatus(cfg, doFetch) {
40791
40838
  }
40792
40839
  return { ok: res.ok, status: res.status, body };
40793
40840
  };
40794
- const [wallets2, tier, limits] = await Promise.all([
40841
+ const [wallets2, tier, limits, risk] = await Promise.all([
40795
40842
  get("/api/v1/agent/wallets/embedded"),
40796
40843
  get("/api/v1/agent/tier"),
40797
- get("/api/v1/agent/keys/self/limits")
40844
+ get("/api/v1/agent/keys/self/limits"),
40845
+ get("/api/v1/agent/keys/self/risk")
40798
40846
  ]);
40799
40847
  const unreadable2 = [
40800
40848
  ["wallets", wallets2],
40801
40849
  ["tier", tier],
40802
- ["limits", limits]
40850
+ ["limits", limits],
40851
+ ["risk", risk]
40803
40852
  ].filter(([, r]) => !r.ok);
40804
40853
  const tierBody = tier.ok ? tier.body : undefined;
40854
+ const riskBody = risk.ok ? risk.body : undefined;
40855
+ const stopped = riskBody?.stopped === true;
40805
40856
  return {
40806
40857
  text: JSON.stringify({
40807
40858
  success: true,
40808
- ready: unreadable2.length === 0 ? true : undefined,
40859
+ ready: stopped ? false : unreadable2.length === 0 ? true : undefined,
40860
+ stopped: stopped ? { tripped: riskBody?.tripped ?? null, clearedBy: STOP_CLEARED_BY } : undefined,
40809
40861
  notice: tierBody?.maxExpired ? tierBody.maxExpiredNotice : undefined,
40810
40862
  updateAvailable: updateAvailable() ?? undefined,
40811
40863
  unreadable: unreadable2.length > 0 ? unreadable2.map(([name]) => name) : undefined,
40812
40864
  wallets: wallets2.body,
40813
40865
  tier: tier.body,
40814
- limits: limits.body
40866
+ limits: limits.body,
40867
+ risk: risk.body
40815
40868
  }, null, 2),
40816
40869
  ...unreadable2.length > 0 ? { isError: true } : {}
40817
40870
  };
40818
40871
  }
40819
- var SOLANA_SEED_CEILINGS, MAIN_PAYER, SWEEP_BASE_ASSETS;
40872
+ var SOLANA_SEED_CEILINGS, MAIN_PAYER, SWEEP_BASE_ASSETS, STOP_CLEARED_BY = "An account owner reviews the profile and resumes it from the agent console; an API key cannot resume it.";
40820
40873
  var init_orchestrate = __esm(() => {
40821
40874
  init_convert();
40822
40875
  init_image();
@@ -45158,6 +45211,13 @@ function buildRequest(name, args, cfg) {
45158
45211
  const path = kind === "launch" ? `/api/v1/launch/headless/jobs/${id}` : kind === "swap" ? `/api/v1/agent/swap/by-client-id/${id}` : `/api/v1/trade/agent/jobs/${id}`;
45159
45212
  return { url: `${base2}${path}`, init: { method: "GET", headers: jsonHeaders(apiKey) } };
45160
45213
  }
45214
+ case "candle_trade_execute": {
45215
+ const { clientTradeId } = args;
45216
+ return {
45217
+ url: `${base2}/api/v1/trade/agent/execute`,
45218
+ init: { method: "POST", headers: jsonHeaders(requireApiKey2(cfg)), body: JSON.stringify({ clientTradeId }) }
45219
+ };
45220
+ }
45161
45221
  case "candle_get_staking": {
45162
45222
  return { url: `${base2}/api/v1/agent/staking`, init: { method: "GET", headers: jsonHeaders(requireApiKey2(cfg)) } };
45163
45223
  }
@@ -45246,13 +45306,40 @@ function buildRequest(name, args, cfg) {
45246
45306
  }
45247
45307
  case "candle_get_profile_trades": {
45248
45308
  const apiKey = requireApiKey2(cfg);
45249
- const { keyPrefix, limit } = args;
45250
- const query2 = limit === undefined ? "" : `?limit=${encodeURIComponent(String(limit))}`;
45309
+ const { keyPrefix, limit, since, until, cursor } = args;
45310
+ const params = new URLSearchParams;
45311
+ if (limit !== undefined)
45312
+ params.set("limit", String(limit));
45313
+ if (since !== undefined)
45314
+ params.set("since", since);
45315
+ if (until !== undefined)
45316
+ params.set("until", until);
45317
+ if (cursor !== undefined)
45318
+ params.set("cursor", cursor);
45251
45319
  return {
45252
- url: `${base2}/api/v1/agent/keys/${encodeURIComponent(keyPrefix)}/trades${query2}`,
45320
+ url: `${base2}/api/v1/agent/keys/${encodeURIComponent(keyPrefix)}/trades${params.size ? `?${params}` : ""}`,
45253
45321
  init: { method: "GET", headers: jsonHeaders(apiKey) }
45254
45322
  };
45255
45323
  }
45324
+ case "candle_get_paper_inventory": {
45325
+ return {
45326
+ url: `${base2}/api/v1/trade/agent/paper/inventory`,
45327
+ init: { method: "GET", headers: jsonHeaders(requireApiKey2(cfg)) }
45328
+ };
45329
+ }
45330
+ case "candle_get_profile_paper": {
45331
+ const { keyPrefix } = args;
45332
+ return {
45333
+ url: `${base2}/api/v1/agent/keys/${encodeURIComponent(keyPrefix)}/paper`,
45334
+ init: { method: "GET", headers: jsonHeaders(requireApiKey2(cfg)) }
45335
+ };
45336
+ }
45337
+ case "candle_get_account_pnl": {
45338
+ return {
45339
+ url: `${base2}/api/v1/agent/keys/pnl`,
45340
+ init: { method: "GET", headers: jsonHeaders(requireApiKey2(cfg)) }
45341
+ };
45342
+ }
45256
45343
  case "candle_transfer": {
45257
45344
  const apiKey = requireApiKey2(cfg);
45258
45345
  return {
@@ -45697,9 +45784,24 @@ function registerToolSubset(server, options) {
45697
45784
  }, async (args) => callAndRelay("candle_get_profile_pnl", args, getConfig(), doFetch));
45698
45785
  register("candle_get_profile_trades", {
45699
45786
  title: "Read an agent profile's trade history",
45700
- description: "Orders, actual fills, fees, timestamps and transaction hashes for this profile. Includes FAILED " + "trades, with an errorCode saying why each did not go through, so this answers 'what happened to " + "my order' as well as 'what did I trade'. Reads only; moves nothing.",
45787
+ description: "Orders, actual fills, fees, timestamps and transaction hashes for this profile. Page by time with " + "`since`, `until` and `cursor` (continue with the response's `nextCursor`). Includes FAILED " + "trades, with an errorCode saying why each did not go through, so this answers 'what happened to " + "my order' as well as 'what did I trade'. The profile's swaps are listed among the trades by " + "time, each with kind 'swap', rail ('swap' for candle_swap, 'build' for the CLI's build and " + "submit), fromAsset, toAsset and amountRaw; a candle_swap made without a clientSwapId is not " + "recorded, so it is not listed. Reads only; moves nothing.",
45701
45788
  inputSchema: profileTradesShape
45702
45789
  }, async (args) => callAndRelay("candle_get_profile_trades", args, getConfig(), doFetch));
45790
+ register("candle_get_paper_inventory", {
45791
+ title: "Read this key's paper inventory",
45792
+ description: "This key's PAPER book in raw units: the positions that paper buys have credited and a paper " + "percent sell sizes itself from. Derived from admitted quotes; no money moved and the live " + "wallet is untouched. Never mix it with live holdings (candle_get_portfolio). Fails retryably " + "when the book cannot be read completely. Needs swap:write on the key. Reads only.",
45793
+ inputSchema: noArgsShape
45794
+ }, async (args) => callAndRelay("candle_get_paper_inventory", args, getConfig(), doFetch));
45795
+ register("candle_get_profile_paper", {
45796
+ title: "Read an agent profile's paper P&L",
45797
+ description: "One profile's PAPER results: the same accounting as candle_get_profile_pnl, from trades that " + "never happened. `pnl` is the paper P&L and `trades` its 50 most recent paper trades. Carries " + '`basis: "quote"`: the figures come from quotes at admission and are optimistic by the ' + "slippage and fees real execution pays. Never add them to live P&L. Reads only.",
45798
+ inputSchema: profilePnlShape
45799
+ }, async (args) => callAndRelay("candle_get_profile_paper", args, getConfig(), doFetch));
45800
+ register("candle_get_account_pnl", {
45801
+ title: "Read P&L across all of an account's agent keys",
45802
+ description: "The account's P&L split by agent profile in one read, as `candle pnl` without `--profile` " + "does: one row per profile with its ledger figures and, where it has a board entry, `board` " + "(all-time public total with creator-fee claims credited to the launching agent, so a profile's " + "board total can differ from its per-key ledger P&L). A wallet shared by several keys books a " + "round to its bound key, else the key whose buy opened it. Use candle_get_profile_pnl for one " + "profile's positions. Reads only.",
45803
+ inputSchema: noArgsShape
45804
+ }, async (args) => callAndRelay("candle_get_account_pnl", args, getConfig(), doFetch));
45703
45805
  register("candle_get_portfolio", {
45704
45806
  title: "Read what the account holds, on Solana and Hood",
45705
45807
  description: "Balances and prices for the wallets Candle already knows on this account, on both chains: the " + "embedded wallet and every TEE wallet. Solana wallets are in `embedded` and `tee` (SOL as raw " + "`lamports`, tokens as raw `amountRaw` with `decimals`). Hood wallets are in `hood.embedded` and " + "`hood.tee` (ETH as raw `wei`, ERC-20s including USDG), never in the top-level arrays. Every " + "wallet and holding carries `chain`. Prices are in `prices`: a Solana mint under its own " + "address, Hood ETH under `hood:native`, and a Hood token under `hood:<contract lowercased>`, so " + "lowercase the address before looking it up. An unpriced entry is `priceUsd: null`, never zero; " + "a Hood one says why in `unpricedReason` (no-market-row, unusable-price, stale-mark, " + "source-unavailable), and `hood.unpriced` / `hood.unpricedByReason` count them. A Hood token " + "mark older than six hours is not used, so many Hood tokens read unpriced. A wallet whose read " + "failed has null balances, never zero, and is listed in `unavailable` (Solana) or " + "`hood.unavailable`; check `complete` before quoting a total. `walletsComplete` is false only " + "when the wallet list itself was cut off, and is absent on a server that predates it: then " + "`complete: false` may be a cut-off list or a failed read. Vault and external wallets are " + "not included: Candle does not know their addresses (the Candle CLI's `candle portfolio` reads " + "them over your own RPC). Needs a key with the account:read scope; without it the answer is " + "SCOPE_MISSING. A server that predates Hood in the portfolio omits `hood` and `chain`. Reads " + "only; moves nothing." + (options.localHyperliquid ? " This local server adds `hyperliquid`: each Hood TEE wallet's Hyperliquid account (account value, " + "unrealized P&L, positions, risk level at the default thresholds) read live from Hyperliquid, with a " + "subtotal. It is not in the ledger and is never part of any total. `read: false` with a reason, or " + "entries in `unread`, means those accounts are unknown, not empty." : ""),
@@ -45721,7 +45823,7 @@ function registerToolSubset(server, options) {
45721
45823
  });
45722
45824
  register("candle_execution_status", {
45723
45825
  title: "Can this key execute right now",
45724
- description: "One call before trading: the embedded wallets to spend from, the tier that decides what " + "may be traded, and this key's own spend limits. Reads only; moves nothing. Call it when " + "a run starts, or after an authorization error, rather than inferring readiness from a " + "failed trade. If a read could not be completed the tool says which one and does NOT " + "claim the account is unready: an unreachable endpoint and a missing tier are different " + "problems with different fixes.",
45826
+ description: "One call before trading: the embedded wallets to spend from, the tier that decides what " + "may be traded, this key's own spend limits, and its loss limits and stop state (risk: " + "lossLimits, state, stopped, tripped). A stopped key reports ready: false and a top-level " + "stopped naming the bound that broke; every trade answers RISK_LIMIT_REACHED until an " + "account owner resumes it, so stop trading and say why rather than retrying. Any scope " + "can read its own. Reads only; moves nothing. Call it when " + "a run starts, or after an authorization error, rather than inferring readiness from a " + "failed trade. If a read could not be completed the tool says which one and does NOT " + "claim the account is unready: an unreachable endpoint and a missing tier are different " + "problems with different fixes.",
45725
45827
  inputSchema: {}
45726
45828
  }, async () => {
45727
45829
  const result = await executionStatus(getConfig(), doFetch);
@@ -45780,6 +45882,13 @@ function registerToolSubset(server, options) {
45780
45882
  const result = await executeTrade(args, getConfig(), doFetch);
45781
45883
  return { content: [{ type: "text", text: result.text }], ...result.isError ? { isError: true } : {} };
45782
45884
  });
45885
+ register("candle_trade_execute", {
45886
+ title: "Execute a deferred trade",
45887
+ description: "Execute a trade you built with candle_trade and deferExecution: true. MOVES REAL FUNDS: Candle signs " + "the plan it kept back from the build with the account's own delegation and broadcasts it. Takes the " + "clientTradeId and nothing else, so nothing you send can change what gets signed; to change the trade, " + `build a new one.
45888
+
45889
+ ` + "Exactly one execution per build. Calling again never broadcasts a second time: it returns the stored " + "result once the first has confirmed, and a 409 IDEMPOTENCY_CONFLICT while the outcome is still " + "unknown (read candle_get_operation with the clientTradeId, kind trade, before doing anything else). " + "More than 30 minutes after the build it is 409 QUOTE_EXPIRED: build again for a fresh quote. A " + "trade built without deferExecution has already executed and is refused here.",
45890
+ inputSchema: tradeExecuteShape
45891
+ }, async (args) => callAndRelay("candle_trade_execute", args, getConfig(), doFetch));
45783
45892
  register("candle_atomic_launch_dry_run", {
45784
45893
  title: "Dry-run an atomic Solana launch",
45785
45894
  description: "Check a Solana launch bundled with first buys and price each buy, without building anything. Runs the " + "same checks as the atomic build, in the same order (payers, Believer check, fee and spend limits, the " + "key's transaction cap), then returns the launch preview (curveTerms) and one fill per first buy: the " + "tokens it would receive and the floor the build would sign it at. Reads only; moves nothing, reserves " + "nothing, and the clientLaunchId stays unused. It does not check balances, the daily cap, or the " + "transaction's size, so the real launch can still refuse. Needs launch:write, swap:write and Pro or Max.",
@@ -45988,7 +46097,7 @@ async function registerEligibleProfileTool(server, env = process.env, doFetch =
45988
46097
  }
45989
46098
  return eligible;
45990
46099
  }
45991
- var VAMP_CANDIDATES_DESCRIPTION, localProfileTools, STAKING_DISCLOSURE = "No instant unstake. Read pool.withdrawalTimelockSeconds for the wait. Solana rewards accrue to the holding wallet. sCNDL in any eligible account wallet counts toward Pro and earns the account's Hood staking reward, except a wallet another account also claims, wallets past the 200-wallet cap, and an account with no EVM wallet. CNDL in a pending withdrawal counts toward nothing for Pro.", stakingWriteShape, TOOL_NAMES, LAB_TOOL_NAMES, PROFILE_EDIT_TOOL_NAMES, isProfileEditTool = (name) => PROFILE_EDIT_TOOL_NAMES.includes(name), OPERATION_NOT_FOUND_RULE, getMarketShape, priceHistoryShape, TOKEN_EVENTS_LIMIT_MESSAGE = "limit must be a whole number from 1 to 1000", tokenEventsLimit, TOKEN_EVENTS_KIND_NOTE = "kept back from the request and applied to the returned page by this server, so a filtered page can be shorter than limit while older events of other kinds remain: page with nextCursor as before until it is null", tokenCardShape, tokenEventsShape, tokenForensicsShape, FEED_LIMIT_MESSAGE = "limit must be a whole number from 1 to 200", feedLimit, getFeedShape, reportActivityShape, getAgentProfileShape, listAgentsShape, createPostShape, deletePostShape, listAgentPostsShape, getAgentPositionsShape, getOperationShape, vampCandidatesShape, getFeesShape, atomicPayerShape, atomicLaunchDryRunShape, claimFeesShape, resolveTokenShape, profileWalletsShape, profilePnlShape, profileTradesShape, setProfileWalletsShape, swapShape, tradeShape, tokenImageUrl, tokenBannerUrl, localImageShape, localLaunchTokenShape, localLaunchAndSeedShape, IMAGE_PATH_DISCLOSURE, transferShape, sweepShape, PROFILE_PNL_HYPERLIQUID;
46100
+ var VAMP_CANDIDATES_DESCRIPTION, localProfileTools, STAKING_DISCLOSURE = "No instant unstake. Read pool.withdrawalTimelockSeconds for the wait. Solana rewards accrue to the holding wallet. sCNDL in any eligible account wallet counts toward Pro and earns the account's Hood staking reward, except a wallet another account also claims, wallets past the 200-wallet cap, and an account with no EVM wallet. CNDL in a pending withdrawal counts toward nothing for Pro.", stakingWriteShape, TOOL_NAMES, LAB_TOOL_NAMES, PROFILE_EDIT_TOOL_NAMES, isProfileEditTool = (name) => PROFILE_EDIT_TOOL_NAMES.includes(name), OPERATION_NOT_FOUND_RULE, getMarketShape, priceHistoryShape, TOKEN_EVENTS_LIMIT_MESSAGE = "limit must be a whole number from 1 to 1000", tokenEventsLimit, TOKEN_EVENTS_KIND_NOTE = "kept back from the request and applied to the returned page by this server, so a filtered page can be shorter than limit while older events of other kinds remain: page with nextCursor as before until it is null", tokenCardShape, tokenEventsShape, tokenForensicsShape, FEED_LIMIT_MESSAGE = "limit must be a whole number from 1 to 200", feedLimit, getFeedShape, reportActivityShape, getAgentProfileShape, listAgentsShape, createPostShape, deletePostShape, listAgentPostsShape, getAgentPositionsShape, getOperationShape, vampCandidatesShape, getFeesShape, atomicPayerShape, atomicLaunchDryRunShape, claimFeesShape, resolveTokenShape, profileWalletsShape, profilePnlShape, profileTradesShape, noArgsShape, setProfileWalletsShape, swapShape, tradeShape, tradeExecuteShape, tokenImageUrl, tokenBannerUrl, localImageShape, localLaunchTokenShape, localLaunchAndSeedShape, IMAGE_PATH_DISCLOSURE, transferShape, sweepShape, PROFILE_PNL_HYPERLIQUID;
45992
46101
  var init_tools = __esm(() => {
45993
46102
  init_zod();
45994
46103
  init_client2();
@@ -46053,6 +46162,7 @@ var init_tools = __esm(() => {
46053
46162
  "candle_get_agent_positions",
46054
46163
  "candle_get_plans",
46055
46164
  "candle_trade",
46165
+ "candle_trade_execute",
46056
46166
  "candle_launch_and_seed",
46057
46167
  "candle_atomic_launch_dry_run",
46058
46168
  "candle_swap",
@@ -46063,6 +46173,9 @@ var init_tools = __esm(() => {
46063
46173
  "candle_set_profile_wallets",
46064
46174
  "candle_get_profile_pnl",
46065
46175
  "candle_get_profile_trades",
46176
+ "candle_get_paper_inventory",
46177
+ "candle_get_profile_paper",
46178
+ "candle_get_account_pnl",
46066
46179
  "candle_get_portfolio",
46067
46180
  "candle_resolve_token",
46068
46181
  "candle_execution_status",
@@ -46245,8 +46358,12 @@ var init_tools = __esm(() => {
46245
46358
  };
46246
46359
  profileTradesShape = {
46247
46360
  keyPrefix: exports_external.string().describe("The profile's API key prefix"),
46248
- limit: exports_external.number().optional().describe("How many of the most recent trades to return. Default 200, max 1000.")
46361
+ limit: exports_external.number().optional().describe("How many of the most recent trades to return. Default 200, max 1000."),
46362
+ since: exports_external.string().optional().describe("Inclusive lower bound on the trade's createdAt: an ISO 8601 time or epoch milliseconds."),
46363
+ until: exports_external.string().optional().describe("Exclusive upper bound on createdAt, in the same forms as `since`."),
46364
+ cursor: exports_external.string().optional().describe("The `nextCursor` of the previous page, to continue the same window.")
46249
46365
  };
46366
+ noArgsShape = {};
46250
46367
  setProfileWalletsShape = {
46251
46368
  keyPrefix: exports_external.string().describe("The profile's API key prefix"),
46252
46369
  walletIds: exports_external.array(exports_external.string()).describe("The linked-wallet ids the profile may spend from -- ids, not addresses. This REPLACES the " + "whole set: any wallet omitted loses access. An empty array assigns none.")
@@ -46267,7 +46384,11 @@ var init_tools = __esm(() => {
46267
46384
  quoteAsset: exports_external.string().optional().describe('What the wallet spends on a buy or receives on a sell: "sol", "usdc" or "cndl" on Solana, ' + '"eth" or "usdg" on Hood. Echoing the `quoteAsset` a quote (POST /api/v1/trade/agent/quote) returned is safe. On Solana it applies only ' + "to an arbitrary mint Candle never launched (a buy or sell works on any plan) and is ignored for a Candle token, " + "whose quote comes from the token itself. On Hood it is the settlement asset of a DEX " + "trade; a USDG buy adds an approval transaction an ETH buy does not. It is not the route: " + "the cheapest path to the asset is chosen separately. Defaults to sol / ETH settlement."),
46268
46385
  maxSlippageBps: exports_external.number().int().min(0).max(5000).optional().describe("Slippage bound in bps, 0-5000 (50%). Server defaults to 100 (1%)"),
46269
46386
  clientTradeId: exports_external.string().optional().describe("Idempotency key. Auto-generated when omitted and echoed in the result. Retrying with the " + "SAME id is safe (idempotent replay); a new id is a SECOND trade."),
46270
- paper: exports_external.preprocess((value) => value === "true" || value === 1 || value === "1" ? true : value === "false" || value === 0 || value === "0" ? false : value, exports_external.boolean().optional().describe("Rehearse instead of trading. The request passes every admission rule a live trade passes " + "-- the same planner, spend gate, key cap and loss limits -- and records the quote, but " + "nothing is ever broadcast and no funds move. Use it to check that a strategy is admitted " + "before risking anything on it. A paper fill is optimistic by construction: it books the " + "quoted price, so the gap between a paper arm and a live one IS the execution cost. " + "A sell of a mint this key already paper-bought also closes that paper book when the " + "live wallet is empty, even if this flag is omitted."))
46387
+ paper: exports_external.preprocess((value) => value === "true" || value === 1 || value === "1" ? true : value === "false" || value === 0 || value === "0" ? false : value, exports_external.boolean().optional().describe("Rehearse instead of trading. The request passes every admission rule a live trade passes " + "-- the same planner, spend gate, key cap and loss limits -- and records the quote, but " + "nothing is ever broadcast and no funds move. Use it to check that a strategy is admitted " + "before risking anything on it. A paper fill is optimistic by construction: it books the " + "quoted price, so the gap between a paper arm and a live one IS the execution cost. " + "A sell of a mint this key already paper-bought also closes that paper book when the " + "live wallet is empty, even if this flag is omitted.")),
46388
+ deferExecution: exports_external.preprocess((value) => value === "true" || value === 1 || value === "1" ? true : value === "false" || value === 0 || value === "0" ? false : value, exports_external.boolean().optional().describe("Quote first, spend later. The trade is built and the result carries the quote with " + 'status "built"; NOTHING is signed or spent until candle_trade_execute is called with the same ' + "clientTradeId. The build holds for 30 minutes; executing after that is QUOTE_EXPIRED and you " + "build again for a fresh quote. Rebuilding with the same clientTradeId re-quotes it, until it has " + "been sent to candle_trade_execute. There is no cancel: let an unwanted build expire. Cannot be " + "combined with `paper`."))
46389
+ };
46390
+ tradeExecuteShape = {
46391
+ clientTradeId: exports_external.string().min(1).describe("The clientTradeId of a candle_trade built with deferExecution: true. Nothing else is accepted.")
46271
46392
  };
46272
46393
  tokenImageUrl = launchSchemaField(launchTokenShape, "imageUrl");
46273
46394
  tokenBannerUrl = launchSchemaField(launchTokenShape, "bannerUrl");
@@ -74092,29 +74213,11 @@ ${USAGE3}` : USAGE3, ctx.json);
74092
74213
  }
74093
74214
 
74094
74215
  // src/commands/auth.ts
74095
- import { homedir as homedir3 } from "node:os";
74096
- import { join as join9 } from "node:path";
74097
-
74098
- // src/account.ts
74099
- init_client();
74100
- async function fetchAccount(deps, apiUrl, apiKey) {
74101
- const identity = await apiRequest("/api/v1/agent/wallets/embedded", {
74102
- auth: "key",
74103
- credentials: { apiKey },
74104
- apiUrl,
74105
- fetch: deps.fetch,
74106
- env: deps.env
74107
- });
74108
- const body = identity.ok ? identity.body : undefined;
74109
- const account = body?.account;
74110
- if (account)
74111
- return { account, ...body?.username ? { username: body.username } : {} };
74112
- return { failure: identity.ok ? "no account in the response" : identity.message };
74113
- }
74114
-
74115
- // src/commands/auth.ts
74216
+ init_account();
74116
74217
  init_agent_key_access();
74117
74218
  init_args();
74219
+ import { homedir as homedir3 } from "node:os";
74220
+ import { join as join9 } from "node:path";
74118
74221
 
74119
74222
  // src/checks.ts
74120
74223
  init_client();
@@ -76510,9 +76613,9 @@ var HELP = {
76510
76613
  group: "Account",
76511
76614
  intro: "Holdings, prices and value of every wallet",
76512
76615
  summary: "Every wallet's holdings, prices and value on Solana and Hood (vault read over your own RPCs)",
76513
- description: "Vault, TEE and embedded wallets on Solana and Hood, each token with its chain, amount, price and value, then subtotals per group and per chain and one total. TEE and embedded balances, on both chains, come from Candle. Solana vault and external wallets are read over your own Solana RPC, and Candle is sent only the mints they hold, for prices. EVM vault keys are read over your own EVM RPC for ETH and USDG only (the CLI cannot list other tokens), which must be Hood's (chain id 4663); any other chain is refused. Their prices come from Candle by asset (ETH, USDG), never by address. Unpriced tokens are shown as unpriced with the reason (no-market-row, stale-mark, unusable-price, source-unavailable) and left out of the total. A wallet that could not be read in full is marked not read, the footer names its chain, and the exit is 3. Where Candle serves LP, each Solana TEE wallet's DAMM v2 positions follow the tokens (share of the pool plus unclaimed fees, valued by Candle at the same marks) and count in the total; a position whose pool could not be read is shown as not read and the total says it is partial. A bridge moves value between the chain subtotals; it is not a trade. With a filter, only the wallets whose label or address contains it are read and shown, the subtotals and total are for those wallets, and the first line says how many matched. When Candle listed only part of the account's wallets, the result stays partial even if every wallet shown was read, and no match says so. After the totals, a Hyperliquid section (live from Hyperliquid, not in the ledger, and never in the total) lists each EVM TEE wallet's Hyperliquid account value, unrealized P&L, positions and risk at the default thresholds, read from Hyperliquid's public API on this machine; a failed read there is not read and the exit is 3. --chain solana and --no-perps read nothing from Hyperliquid.",
76616
+ description: "Vault, TEE and embedded wallets on Solana and Hood, each token with its chain, amount, price and value, then subtotals per group and per chain and one total. TEE and embedded balances, on both chains, come from Candle. Solana vault and external wallets are read over your own Solana RPC, and Candle is sent only the mints they hold, for prices. EVM vault keys are read over your own EVM RPC for ETH and USDG only (the CLI cannot list other tokens), which must be Hood's (chain id 4663); any other chain is refused. Their prices come from Candle by asset (ETH, USDG), never by address. Unpriced tokens are shown as unpriced with the reason (no-market-row, stale-mark, unusable-price, source-unavailable) and left out of the total. A wallet that could not be read in full is marked not read, the footer names its chain, and the exit is 3. Where Candle serves LP, each Solana TEE wallet's DAMM v2 positions follow the tokens (share of the pool plus unclaimed fees, valued by Candle at the same marks) and count in the total; a position whose pool could not be read is shown as not read and the total says it is partial. A bridge moves value between the chain subtotals; it is not a trade. With a filter, only the wallets whose label or address contains it are read and shown, the subtotals and total are for those wallets, and the first line says how many matched. When Candle listed only part of the account's wallets, the result stays partial even if every wallet shown was read, and no match says so. After the totals, a Hyperliquid section (live from Hyperliquid, not in the ledger, and never in the total) lists each EVM TEE wallet's Hyperliquid account value, unrealized P&L, positions and risk at the default thresholds, read from Hyperliquid's public API on this machine; a failed read there is not read and the exit is 3. --chain solana and --no-perps read nothing from Hyperliquid. The vault is read only for its addresses, so the command needs no terminal: without one, a factor that asks nothing (--factor touch-id or passkey) still unlocks it, and otherwise the vault row says not read and the other groups are shown, but the result is partial: the total says so and the exit is 3. --no-vault leaves the vault out on purpose, and then the result can be complete (exit 0).",
76514
76617
  usage: [
76515
- "candle portfolio [<filter>] [--chain solana|hood] [--rpc-url <url>] [--evm-rpc-url <url>] [--no-perps] [--json]"
76618
+ "candle portfolio [<filter>] [--chain solana|hood] [--rpc-url <url>] [--evm-rpc-url <url>] [--no-perps] [--no-vault] [--json]"
76516
76619
  ],
76517
76620
  rows: [
76518
76621
  {
@@ -76534,6 +76637,10 @@ var HELP = {
76534
76637
  invocation: "--no-perps",
76535
76638
  description: "Skip the Hyperliquid section: send no EVM TEE address to Hyperliquid's public API"
76536
76639
  },
76640
+ {
76641
+ invocation: "--no-vault",
76642
+ description: "Leave the vault out: no unlock, and no vault address is sent to your RPC"
76643
+ },
76537
76644
  KEYSTORE_FLAG
76538
76645
  ],
76539
76646
  examples: [
@@ -76895,8 +77002,8 @@ var HELP = {
76895
77002
  description: "List live API keys: name and Read, Write or Withdraw access; --all includes revoked keys; --scopes adds the raw scopes"
76896
77003
  },
76897
77004
  {
76898
- invocation: "create [--access read|write|withdraw|trade-perps | --scopes <a,b,c>] [--label <name>] [--expires-in <days>] [--tx-limit <usd> [--reset daily|weekly|monthly|never]] [--embedded-wallet deny|allow]",
76899
- description: "Create an API key; --access mints one of the levels (write trades and launches; only withdraw can move funds; trade-perps is exactly account:read and perps:write, for Hyperliquid perps setup and orders, and it cannot fund its own Hyperliquid account: a Relay deposit needs swap:write and a withdrawal needs perps:withdraw, each a separate grant through --scopes). A trade-perps key trades from an EVM TEE wallet promoted onto it (candle vault new-key --chain evm, then candle vault promote --from <cold> --to-key <prefix>; candle tee new is Solana only) and a signer on the trading machine (candle tee signer new --key <prefix>). --embedded-wallet defaults to deny; allow lets the key spend the account's embedded wallet and is confirmed at a terminal"
77005
+ invocation: "create [--access read|write|withdraw|trade-perps | --scopes <a,b,c>] [--label <name>] [--expires-in <days>] [--tx-limit <usd> [--reset daily|weekly|monthly|never]] [--embedded-wallet deny|allow] [--no-store]",
77006
+ description: "Create an API key; --no-store prints it without saving it as the profile's working key (default: store only if no working key exists). --access mints one of the levels (write trades and launches; only withdraw can move funds; trade-perps is exactly account:read and perps:write, for Hyperliquid perps setup and orders, and it cannot fund its own Hyperliquid account: a Relay deposit needs swap:write and a withdrawal needs perps:withdraw, each a separate grant through --scopes). A trade-perps key trades from an EVM TEE wallet promoted onto it (candle vault new-key --chain evm, then candle vault promote --from <cold> --to-key <prefix>; candle tee new is Solana only) and a signer on the trading machine (candle tee signer new --key <prefix>). --embedded-wallet defaults to deny; allow lets the key spend the account's embedded wallet and is confirmed at a terminal"
76900
77007
  },
76901
77008
  {
76902
77009
  invocation: "access <prefix|label|self> (--access read|write|withdraw [--yes] | --history)",
@@ -76910,6 +77017,10 @@ var HELP = {
76910
77017
  invocation: "limit <prefix|label> (--tx-limit <usd> [--reset daily|weekly|monthly|never] | --clear)",
76911
77018
  description: "Set or clear the USD transaction cap on an existing key, with the device token. --reset defaults to daily"
76912
77019
  },
77020
+ {
77021
+ invocation: "limits <prefix> <asset>=<raw> [<asset>=<raw> ...]",
77022
+ description: "Set a key's per-asset caps (sol, usdc, cndl, eth, usdg; a whole number of base units per transaction) with the profile's own API key. It can only lower a cap or add one; raising or removing one is refused (LOOSEN_REQUIRES_SESSION) and is the owner's, in the web key manager. The list replaces the key's caps"
77023
+ },
76913
77024
  {
76914
77025
  invocation: "self embedded-wallet deny",
76915
77026
  description: "The profile's own key gives up the embedded wallet, with no prompt. A key can never allow itself: the owner does that with keys update"
@@ -76946,11 +77057,12 @@ var HELP = {
76946
77057
  "candle keys list",
76947
77058
  "candle keys create --access withdraw --label rebalancer",
76948
77059
  "candle keys create --access trade-perps --label perps-bot",
76949
- "candle keys create --scopes trade:write --label agent-one",
77060
+ "candle keys create --scopes trade:write --label agent-one --no-store",
76950
77061
  "candle keys access cndl --access withdraw",
76951
77062
  "candle keys access self --access read --yes",
76952
77063
  "candle keys update agent-one --embedded-wallet deny --yes",
76953
77064
  "candle keys self embedded-wallet deny",
77065
+ "candle keys limits Ab3dEf9h sol=1000000000 usdc=50000000",
76954
77066
  "candle keys wallets ck_live_ab12",
76955
77067
  "candle tee rebind --label-prefix dest- --to-key Ab3dEf9h",
76956
77068
  "candle keys signer approve ABCD-EFGH --key tr-2",
@@ -76981,6 +77093,10 @@ var HELP = {
76981
77093
  description: "Give agents access to the account's embedded wallets: opens the Agents page's grant step in your browser (no key is created) and, with an API key, waits until it lands"
76982
77094
  },
76983
77095
  { invocation: "revoke <wallet-id>", description: "Revoke a linked wallet" },
77096
+ {
77097
+ invocation: "allowlist [--all]",
77098
+ description: "The account's withdrawal allowlist (the destinations candle transfer --to <label> resolves by). Any key reads it; approving an address is the owner's, in the agent console's Wallets tab (an API key cannot approve); an acting key can revoke one, a read-only key cannot. --all adds removed rows"
77099
+ },
76984
77100
  {
76985
77101
  invocation: "trust <label|address|id|prefix*>...",
76986
77102
  description: "Mark linked wallets yours, so agents can move funds into them (owner only; typed confirm)"
@@ -77014,6 +77130,7 @@ var HELP = {
77014
77130
  "candle wallet authorize",
77015
77131
  "candle wallet import --chain solana --key-file ./signer.json",
77016
77132
  "candle wallet revoke wal_123",
77133
+ "candle wallet allowlist",
77017
77134
  "candle wallet trust 'tr-*' 'dest-*'",
77018
77135
  "candle wallet allow-launch launcher-1",
77019
77136
  "candle wallet close-empty --keep EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
@@ -77872,6 +77989,9 @@ var TOOL_CLASSES = {
77872
77989
  candle_get_profile_wallets: READ,
77873
77990
  candle_get_profile_pnl: READ,
77874
77991
  candle_get_profile_trades: READ,
77992
+ candle_get_paper_inventory: READ,
77993
+ candle_get_profile_paper: READ,
77994
+ candle_get_account_pnl: READ,
77875
77995
  candle_get_portfolio: READ,
77876
77996
  candle_resolve_token: READ,
77877
77997
  candle_execution_status: READ,
@@ -77891,6 +78011,7 @@ var TOOL_CLASSES = {
77891
78011
  candle_perps_risk: READ,
77892
78012
  candle_perps_thresholds: READ,
77893
78013
  candle_trade: card("trade", "clientTradeId"),
78014
+ candle_trade_execute: card("trade"),
77894
78015
  candle_swap: card("swap", "clientSwapId"),
77895
78016
  candle_launch_token: card("launch", "clientLaunchId"),
77896
78017
  candle_launch_and_seed: card("launch", "clientLaunchId"),
@@ -77980,7 +78101,8 @@ async function readOnce(executor, target, writeKey) {
77980
78101
  if (!readsById(target))
77981
78102
  return { readable: false };
77982
78103
  const kind = target.kind === "launch" ? "launch" : "trade";
77983
- const outcome = await executor.execute("candle_get_operation", { clientId: target.opId, kind }, writeKey);
78104
+ const clientId = target.tool === "candle_trade_execute" ? target.params.clientTradeId : target.opId;
78105
+ const outcome = await executor.execute("candle_get_operation", { clientId, kind }, writeKey);
77984
78106
  if (outcome.state === "succeeded" || outcome.state === "pending") {
77985
78107
  const job = outcome.result?.job ?? outcome.result;
77986
78108
  const status = typeof job?.status === "string" ? job.status : "";
@@ -89471,8 +89593,7 @@ async function externalList(args, ctx) {
89471
89593
  return usage(ctx, `Unexpected argument: ${parsed.positionals[0]}`);
89472
89594
  if (!refuseEnvPassphrase(ctx))
89473
89595
  return 1;
89474
- if (!requireTty(ctx, "external list"))
89475
- return 1;
89596
+ const promptCtx = refusePromptsWithoutTerminal(ctx, "external list");
89476
89597
  const { deps } = ctx;
89477
89598
  const resolvedVault = vaultPathFor(ctx, parsed);
89478
89599
  if ("error" in resolvedVault)
@@ -89480,7 +89601,7 @@ async function externalList(args, ctx) {
89480
89601
  const path = resolvedVault.path;
89481
89602
  return runVaultCommand(ctx, async ({ hold }) => {
89482
89603
  const raw = await requireVaultRaw(ctx, resolvedVault);
89483
- const vault = hold((await unlockInteractively(ctx, path, raw, { acceptOlderCopy: parsed.booleans.has("--accept-older-copy") })).vault);
89604
+ const vault = hold((await unlockInteractively(promptCtx, path, raw, { acceptOlderCopy: parsed.booleans.has("--accept-older-copy") })).vault);
89484
89605
  const externals = vault.index.entries.filter((entry) => entry.role === "external");
89485
89606
  const rows = externals.map((entry) => ({
89486
89607
  address: entry.address,
@@ -90263,6 +90384,100 @@ ${fields(rows, style).join(`
90263
90384
  return 0;
90264
90385
  }
90265
90386
 
90387
+ // src/commands/keys-limits.ts
90388
+ init_args();
90389
+ init_client();
90390
+ init_deps();
90391
+ init_presentation();
90392
+ init_profiles();
90393
+ init_key_output();
90394
+ var USAGE8 = "Usage: candle keys limits <prefix> <asset>=<raw-amount> [<asset>=<raw-amount> ...]";
90395
+ var KEY_LIMIT_ASSETS = { sol: 9, usdc: 6, cndl: 6, eth: 18, usdg: 6 };
90396
+ var RAW_AMOUNT = /^[1-9][0-9]*$/;
90397
+ var NO_API_KEY3 = {
90398
+ code: "API_KEY_REQUIRED",
90399
+ message: "candle keys limits changes caps with an API key, and this profile holds none.",
90400
+ suggestion: "Select the profile holding the key with --profile. A device token cannot set per-asset caps."
90401
+ };
90402
+ var LOOSEN_HINT = "This command can only lower a cap. Raising or removing one needs the owner's session: use the web key manager.";
90403
+ async function keysLimits(args, ctx) {
90404
+ const { deps, apiUrl, json } = ctx;
90405
+ const parsed = parseArgs(args, { keyPrefixPositional: true });
90406
+ if ("error" in parsed) {
90407
+ writeScreenUsageFailure(deps, parsed.error, json);
90408
+ return 2;
90409
+ }
90410
+ const target = parsed.positionals[0];
90411
+ const caps = parsed.positionals.slice(1);
90412
+ if (!target || caps.length === 0) {
90413
+ writeScreenUsageFailure(deps, USAGE8, json);
90414
+ return 2;
90415
+ }
90416
+ const limits = [];
90417
+ for (const cap of caps) {
90418
+ const eq = cap.indexOf("=");
90419
+ const asset = (eq < 0 ? cap : cap.slice(0, eq)).toLowerCase();
90420
+ const raw = eq < 0 ? "" : cap.slice(eq + 1);
90421
+ if (!(asset in KEY_LIMIT_ASSETS)) {
90422
+ writeScreenUsageFailure(deps, `The asset must be one of: ${Object.keys(KEY_LIMIT_ASSETS).join(", ")}.`, json);
90423
+ return 2;
90424
+ }
90425
+ if (!RAW_AMOUNT.test(raw)) {
90426
+ writeScreenUsageFailure(deps, `${asset}=<raw> takes a positive whole number of base units (${asset} has ${KEY_LIMIT_ASSETS[asset]} decimals), with no sign, decimal point or leading zero.`, json);
90427
+ return 2;
90428
+ }
90429
+ if (limits.some((limit) => limit.asset === asset)) {
90430
+ writeScreenUsageFailure(deps, `${asset} is named twice.`, json);
90431
+ return 2;
90432
+ }
90433
+ limits.push({ asset, maxPerTxRaw: raw });
90434
+ }
90435
+ await printIdentity(ctx);
90436
+ const apiKey = await resolveApiKey(deps, ctx.profile);
90437
+ if (!apiKey) {
90438
+ writeScreenLocalFailure(deps, NO_API_KEY3, json);
90439
+ return 1;
90440
+ }
90441
+ const result = await apiRequest(`/api/v1/agent/keys/${encodeURIComponent(target)}/limits`, {
90442
+ method: "PUT",
90443
+ auth: "key",
90444
+ credentials: { apiKey },
90445
+ apiUrl,
90446
+ fetch: deps.fetch,
90447
+ env: deps.env,
90448
+ body: { limits }
90449
+ });
90450
+ if (!result.ok) {
90451
+ if (!json && result.code === "LOOSEN_REQUIRES_SESSION")
90452
+ writeScreenLocalFailure(deps, {
90453
+ code: "LOOSEN_REQUIRES_SESSION",
90454
+ message: "That would raise or remove a cap; nothing changed.",
90455
+ suggestion: LOOSEN_HINT
90456
+ }, json);
90457
+ else
90458
+ writeScreenFailure(deps, result, { apiUrl, authType: "key" }, json);
90459
+ return 1;
90460
+ }
90461
+ const stored = result.body.limits ?? limits;
90462
+ if (json) {
90463
+ deps.stdout.write(`${JSON.stringify({ ok: true, keyPrefix: target, limits: stored })}
90464
+ `);
90465
+ return 0;
90466
+ }
90467
+ const style = terminalStyle(deps, "stdout");
90468
+ const rows = [
90469
+ ["Key", target],
90470
+ ...stored.map((limit) => [limit.asset, `${limit.maxPerTxRaw} base units per transaction`])
90471
+ ];
90472
+ deps.stdout.write(`${success("Per-asset caps updated", style)}
90473
+
90474
+ ${heading("Caps", style)}
90475
+ ${fields(rows, style).join(`
90476
+ `)}
90477
+ `);
90478
+ return 0;
90479
+ }
90480
+
90266
90481
  // src/commands/keys-rotate.ts
90267
90482
  init_args();
90268
90483
  init_client();
@@ -90292,7 +90507,7 @@ var TOKEN_PAGE_BODY_CAP_BYTES = 32 * 1024;
90292
90507
  var TOKEN_PAGE_SECTIONS = ["thesis", "tokenomics", "lore"];
90293
90508
  var FLAG_REASONS = ["spam", "scam", "abuse", "misleading", "unsafe_link", "other"];
90294
90509
  var FLAG_DETAIL_LIMIT = 500;
90295
- var USAGE8 = {
90510
+ var USAGE9 = {
90296
90511
  about: "Usage: candle token about <address> [--chain solana|hood] [--capabilities]",
90297
90512
  claim: "Usage: candle token claim <address> [--chain solana|hood] [--creator [--challenge [--message-out <path>] | --challenge-id <id> --signature <sig>]] [--idempotency-key <key>]",
90298
90513
  section: `Usage: candle token section set <address> <thesis|tokenomics|lore> --file <path> --base-revision <n> [--clear] [--chain solana|hood] [--idempotency-key <key>]
@@ -90552,7 +90767,7 @@ function terminalTextKeepingLines(text3) {
90552
90767
  `);
90553
90768
  }
90554
90769
  async function tokenAbout(args, ctx) {
90555
- const usage4 = usageFor(ctx, USAGE8.about);
90770
+ const usage4 = usageFor(ctx, USAGE9.about);
90556
90771
  const parsed = parseArgs(args, { valueFlags: ["--chain"], booleanFlags: ["--capabilities"] });
90557
90772
  if ("error" in parsed)
90558
90773
  return usage4(parsed.error);
@@ -90598,7 +90813,7 @@ async function tokenAbout(args, ctx) {
90598
90813
  return 0;
90599
90814
  }
90600
90815
  async function tokenClaim(args, ctx) {
90601
- const usage4 = usageFor(ctx, USAGE8.claim);
90816
+ const usage4 = usageFor(ctx, USAGE9.claim);
90602
90817
  const parsed = parseArgs(args, {
90603
90818
  valueFlags: ["--chain", "--challenge-id", "--signature", "--message-out", "--idempotency-key"],
90604
90819
  booleanFlags: ["--creator", "--challenge"],
@@ -90725,7 +90940,7 @@ var SECTION_FLAGS = {
90725
90940
  restore: { valueFlags: ["--chain", "--version-id", "--base-revision", "--idempotency-key"], booleanFlags: [] }
90726
90941
  };
90727
90942
  async function tokenSection(args, ctx) {
90728
- const usage4 = usageFor(ctx, USAGE8.section);
90943
+ const usage4 = usageFor(ctx, USAGE9.section);
90729
90944
  const action = args[0];
90730
90945
  if (action !== "set" && action !== "restore")
90731
90946
  return usage4();
@@ -90818,7 +91033,7 @@ async function tokenSection(args, ctx) {
90818
91033
  return 0;
90819
91034
  }
90820
91035
  async function tokenHistory(args, ctx) {
90821
- const usage4 = usageFor(ctx, USAGE8.history);
91036
+ const usage4 = usageFor(ctx, USAGE9.history);
90822
91037
  const parsed = parseArgs(args, { valueFlags: ["--chain", "--limit", "--cursor"] });
90823
91038
  if ("error" in parsed)
90824
91039
  return usage4(parsed.error);
@@ -90873,7 +91088,7 @@ More: --cursor ${data.nextCursor}
90873
91088
  return 0;
90874
91089
  }
90875
91090
  async function tokenFlag(args, ctx) {
90876
- const usage4 = usageFor(ctx, USAGE8.flag);
91091
+ const usage4 = usageFor(ctx, USAGE9.flag);
90877
91092
  const parsed = parseArgs(args, { valueFlags: ["--chain", "--content", "--reason", "--detail", "--idempotency-key"] });
90878
91093
  if ("error" in parsed)
90879
91094
  return usage4(parsed.error);
@@ -90968,7 +91183,7 @@ async function tokenUpdate(args, ctx) {
90968
91183
  return 0;
90969
91184
  }
90970
91185
  async function tokenComment(args, ctx) {
90971
- const usage4 = usageFor(ctx, USAGE8.comment);
91186
+ const usage4 = usageFor(ctx, USAGE9.comment);
90972
91187
  const parsed = parseArgs(args, { valueFlags: ["--chain", "--file", "--idempotency-key"], pathFlags: ["--file"] });
90973
91188
  if ("error" in parsed)
90974
91189
  return usage4(parsed.error);
@@ -91013,7 +91228,7 @@ async function tokenComment(args, ctx) {
91013
91228
  return 0;
91014
91229
  }
91015
91230
  async function tokenComments(args, ctx) {
91016
- const usage4 = usageFor(ctx, USAGE8.comments);
91231
+ const usage4 = usageFor(ctx, USAGE9.comments);
91017
91232
  const parsed = parseArgs(args, { valueFlags: ["--chain", "--limit", "--cursor"] });
91018
91233
  if ("error" in parsed)
91019
91234
  return usage4(parsed.error);
@@ -91100,7 +91315,7 @@ async function tokenPost(args, ctx) {
91100
91315
  }
91101
91316
 
91102
91317
  // src/commands/keys-rotate.ts
91103
- var USAGE9 = `Usage: candle keys rotate <prefix|label> [--grace <duration> | --now] [--force] [--yes] [--idempotency-key <key>]
91318
+ var USAGE10 = `Usage: candle keys rotate <prefix|label> [--grace <duration> | --now] [--force] [--yes] [--idempotency-key <key>]
91104
91319
  candle keys rotate <prefix|label> --finish`;
91105
91320
  var MAX_GRACE_SECONDS = 86400;
91106
91321
  var DEFAULT_GRACE_SECONDS = 3600;
@@ -91127,7 +91342,7 @@ async function keysRotate(args, ctx) {
91127
91342
  }
91128
91343
  const target = parsed.positionals[0];
91129
91344
  if (!target || parsed.positionals.length !== 1) {
91130
- writeScreenUsageFailure(deps, USAGE9, json);
91345
+ writeScreenUsageFailure(deps, USAGE10, json);
91131
91346
  return 2;
91132
91347
  }
91133
91348
  const finish = parsed.booleans.has("--finish");
@@ -91380,7 +91595,7 @@ init_presentation();
91380
91595
  init_profiles();
91381
91596
  init_render();
91382
91597
  init_key_output();
91383
- var NO_API_KEY3 = {
91598
+ var NO_API_KEY4 = {
91384
91599
  code: "NO_API_KEY",
91385
91600
  message: "No API key for this profile.",
91386
91601
  suggestion: "Set CANDLE_API_KEY, or run `candle keys create` and store one."
@@ -91404,7 +91619,7 @@ async function keysWalletsList(args, ctx) {
91404
91619
  await printIdentity(ctx);
91405
91620
  const apiKey = await resolveApiKey(deps, ctx.profile);
91406
91621
  if (!apiKey) {
91407
- writeScreenLocalFailure(deps, NO_API_KEY3, json);
91622
+ writeScreenLocalFailure(deps, NO_API_KEY4, json);
91408
91623
  return 1;
91409
91624
  }
91410
91625
  const result = await apiRequest(`/api/v1/agent/keys/${encodeURIComponent(prefix)}/wallets`, {
@@ -91470,7 +91685,7 @@ async function keysWalletsSet(args, ctx) {
91470
91685
  await printIdentity(ctx);
91471
91686
  const apiKey = await resolveApiKey(deps, ctx.profile);
91472
91687
  if (!apiKey) {
91473
- writeScreenLocalFailure(deps, NO_API_KEY3, json);
91688
+ writeScreenLocalFailure(deps, NO_API_KEY4, json);
91474
91689
  return 1;
91475
91690
  }
91476
91691
  const result = await apiRequest(`/api/v1/agent/keys/${encodeURIComponent(prefix)}/wallets`, {
@@ -91525,7 +91740,7 @@ async function keysWalletsScope(args, ctx) {
91525
91740
  await printIdentity(ctx);
91526
91741
  const apiKey = await resolveApiKey(deps, ctx.profile);
91527
91742
  if (!apiKey) {
91528
- writeScreenLocalFailure(deps, NO_API_KEY3, json);
91743
+ writeScreenLocalFailure(deps, NO_API_KEY4, json);
91529
91744
  return 1;
91530
91745
  }
91531
91746
  const result = await apiRequest(`/api/v1/agent/keys/${encodeURIComponent(prefix)}/wallet-scope`, {
@@ -92047,6 +92262,8 @@ init_solana_lite();
92047
92262
  var NATIVE_MINT2 = "So11111111111111111111111111111111111111112";
92048
92263
  var LAMPORTS_PER_SIGNATURE = 5000n;
92049
92264
  var TOKEN_ACCOUNT_RENT_LAMPORTS = 2039280n;
92265
+ var BUYER_TALLY_RENT_LAMPORTS = 1461600n;
92266
+ var REGISTER_BUYER = [144, 92, 137, 46, 209, 220, 27, 164];
92050
92267
  var DEFAULT_UNITS_PER_INSTRUCTION = 200000n;
92051
92268
  var MAX_COMPUTE_UNITS = 1400000n;
92052
92269
 
@@ -92130,7 +92347,7 @@ function readLeg(tx, index, wallet3) {
92130
92347
  if (program === MEMO_PROGRAM_ID)
92131
92348
  continue;
92132
92349
  if (program !== undefined)
92133
- others.push({ program, data: ix.data });
92350
+ others.push({ program, data: ix.data, accounts: accounts.map(key) });
92134
92351
  }
92135
92352
  if (unitPrice > 0n) {
92136
92353
  const units = unitLimit ?? (computed * DEFAULT_UNITS_PER_INSTRUCTION > MAX_COMPUTE_UNITS ? MAX_COMPUTE_UNITS : computed * DEFAULT_UNITS_PER_INSTRUCTION);
@@ -92138,12 +92355,36 @@ function readLeg(tx, index, wallet3) {
92138
92355
  }
92139
92356
  return { lamports, tokenOut, systemTransfers, others };
92140
92357
  }
92358
+ function isRegisterBuyer(data) {
92359
+ return data.length >= 8 && REGISTER_BUYER.every((byte, i) => data[i] === byte);
92360
+ }
92361
+ function readRegisterBuyer(register, swap3, wallet3) {
92362
+ if (register.program !== swap3.program)
92363
+ throw new BundleReadError("leg 1 registers the buyer with another program");
92364
+ if (register.data.length !== 12 || u323(register.data, 8) !== 0n)
92365
+ throw new BundleReadError("leg 1 registers the buyer with a proof");
92366
+ const [buyer, rentPayer, rules, tally] = register.accounts;
92367
+ if (buyer !== wallet3)
92368
+ throw new BundleReadError("leg 1 registers a buyer other than this wallet");
92369
+ if (rules === undefined || tally === undefined || !swap3.accounts.includes(rules) || !swap3.accounts.includes(tally))
92370
+ throw new BundleReadError("leg 1 registers the buyer for another pool than the one it buys");
92371
+ return rentPayer === wallet3 ? BUYER_TALLY_RENT_LAMPORTS : 0n;
92372
+ }
92141
92373
  function readLaunchBundle(args) {
92142
92374
  const launch = readLeg(decodeLeg(args.legs[0], 0), 0, args.wallet);
92143
92375
  const buy = readLeg(decodeLeg(args.legs[1], 1), 1, args.wallet);
92144
- if (buy.others.length !== 1)
92376
+ let registerLamports = 0n;
92377
+ const first = buy.others[0];
92378
+ if (first && isRegisterBuyer(first.data)) {
92379
+ if (!args.rules)
92380
+ throw new BundleReadError("leg 1 registers a buyer on a launch without rules");
92381
+ const swap4 = buy.others[1];
92382
+ if (buy.others.length !== 2 || !swap4 || isRegisterBuyer(swap4.data))
92383
+ throw new BundleReadError("leg 1 is not one curve swap");
92384
+ registerLamports = readRegisterBuyer(first, swap4, args.wallet);
92385
+ } else if (buy.others.length !== 1)
92145
92386
  throw new BundleReadError("leg 1 is not one curve swap");
92146
- const swap3 = buy.others[0];
92387
+ const swap3 = buy.others[buy.others.length - 1];
92147
92388
  if (u64(swap3.data, 8) !== args.buyRaw)
92148
92389
  throw new BundleReadError("leg 1 does not swap the confirmed --buy");
92149
92390
  if (args.minOutRaw !== undefined && u64(swap3.data, 16) !== args.minOutRaw)
@@ -92163,7 +92404,7 @@ function readLaunchBundle(args) {
92163
92404
  feeRaw = buy.tokenOut;
92164
92405
  quoteRaw = args.buyRaw + buy.tokenOut + launch.tokenOut;
92165
92406
  }
92166
- return { lamports: launch.lamports + buy.lamports, quoteRaw, feeRaw };
92407
+ return { lamports: launch.lamports + buy.lamports + registerLamports, quoteRaw, feeRaw };
92167
92408
  }
92168
92409
 
92169
92410
  // src/launch-check.ts
@@ -92380,7 +92621,7 @@ function poolRequestFields(flags) {
92380
92621
 
92381
92622
  // src/commands/launch.ts
92382
92623
  init_swap();
92383
- var USAGE10 = `Usage: ${LAUNCH_USAGE}`;
92624
+ var USAGE11 = `Usage: ${LAUNCH_USAGE}`;
92384
92625
  var EMBEDDED_WALLET_NAMES = new Set(LAUNCH_DEFINITION.wallet.embeddedNames);
92385
92626
  var LAUNCH_ARG_SPEC = {
92386
92627
  valueFlags: [
@@ -92491,7 +92732,7 @@ async function launch(args, ctx) {
92491
92732
  const dryRun = parsed.booleans.has("--dry-run");
92492
92733
  const check = parsed.booleans.has("--check");
92493
92734
  if (check && dryRun) {
92494
- writeUsageFailure(ctx.deps, `--check and --dry-run are two different stops; pass one. ${USAGE10}`, ctx.json);
92735
+ writeUsageFailure(ctx.deps, `--check and --dry-run are two different stops; pass one. ${USAGE11}`, ctx.json);
92495
92736
  return 2;
92496
92737
  }
92497
92738
  const fieldProblem = launchFieldProblem(flags);
@@ -92501,11 +92742,11 @@ async function launch(args, ctx) {
92501
92742
  }
92502
92743
  const curveProblem = curveFlagProblem(flags);
92503
92744
  if (curveProblem) {
92504
- writeUsageFailure(ctx.deps, `${curveProblem} ${USAGE10}`, ctx.json);
92745
+ writeUsageFailure(ctx.deps, `${curveProblem} ${USAGE11}`, ctx.json);
92505
92746
  return 2;
92506
92747
  }
92507
92748
  if (flags["--image"] !== undefined && flags["--image-url"] !== undefined) {
92508
- writeUsageFailure(ctx.deps, `Pass --image <path> or --image-url <https-url>, not both: a launch has one image. ${USAGE10}`, ctx.json);
92749
+ writeUsageFailure(ctx.deps, `Pass --image <path> or --image-url <https-url>, not both: a launch has one image. ${USAGE11}`, ctx.json);
92509
92750
  return 2;
92510
92751
  }
92511
92752
  if (flags["--image-url"] !== undefined && !flags["--image-url"].startsWith("https://")) {
@@ -92518,24 +92759,24 @@ async function launch(args, ctx) {
92518
92759
  }
92519
92760
  const missingRequired = !flags["--name"] || !flags["--symbol"] || flags["--image"] === undefined && flags["--image-url"] === undefined || !flags["--wallet"];
92520
92761
  if (parsed.positionals.length || !check && missingRequired || !validClientId(id)) {
92521
- writeUsageFailure(ctx.deps, USAGE10, ctx.json);
92762
+ writeUsageFailure(ctx.deps, USAGE11, ctx.json);
92522
92763
  return 2;
92523
92764
  }
92524
92765
  try {
92525
92766
  poolRequestFields(flags);
92526
92767
  } catch (error) {
92527
- writeUsageFailure(ctx.deps, `${error.message}. ${USAGE10}`, ctx.json);
92768
+ writeUsageFailure(ctx.deps, `${error.message}. ${USAGE11}`, ctx.json);
92528
92769
  return 2;
92529
92770
  }
92530
92771
  let feeWallet;
92531
92772
  try {
92532
92773
  feeWallet = feeWalletSelection(flags);
92533
92774
  } catch (error) {
92534
- writeUsageFailure(ctx.deps, `${error.message}. ${USAGE10}`, ctx.json);
92775
+ writeUsageFailure(ctx.deps, `${error.message}. ${USAGE11}`, ctx.json);
92535
92776
  return 2;
92536
92777
  }
92537
92778
  if (parsed.booleans.has("--require-fee-wallet") && !feeWallet) {
92538
- writeUsageFailure(ctx.deps, `--require-fee-wallet needs --fee-wallet <address>: pass the wallet this launch's fees earn to. ${USAGE10}`, ctx.json);
92779
+ writeUsageFailure(ctx.deps, `--require-fee-wallet needs --fee-wallet <address>: pass the wallet this launch's fees earn to. ${USAGE11}`, ctx.json);
92539
92780
  return 2;
92540
92781
  }
92541
92782
  try {
@@ -92543,11 +92784,11 @@ async function launch(args, ctx) {
92543
92784
  launchBuyRaw(flags["--buy"], 18);
92544
92785
  const hinted = flagChain(flags);
92545
92786
  if (hinted === "hood" && feeWalletGiven(flags)) {
92546
- writeUsageFailure(ctx.deps, `--fee-wallet is Solana only; a Hood launch cannot take it. ${USAGE10}`, ctx.json);
92787
+ writeUsageFailure(ctx.deps, `--fee-wallet is Solana only; a Hood launch cannot take it. ${USAGE11}`, ctx.json);
92547
92788
  return 2;
92548
92789
  }
92549
92790
  if (!check && hinted === "hood" && flags["--dex-version"] === undefined) {
92550
- writeUsageFailure(ctx.deps, `A Hood launch needs --dex-version v3|v4. ${USAGE10}`, ctx.json);
92791
+ writeUsageFailure(ctx.deps, `A Hood launch needs --dex-version v3|v4. ${USAGE11}`, ctx.json);
92551
92792
  return 2;
92552
92793
  }
92553
92794
  let image;
@@ -92565,7 +92806,7 @@ async function launch(args, ctx) {
92565
92806
  let rules;
92566
92807
  if (!check && launchRulesGiven(flags)) {
92567
92808
  if (hinted === "hood") {
92568
- writeUsageFailure(ctx.deps, `${LAUNCH_RULES_SOLANA_ONLY} ${USAGE10}`, ctx.json);
92809
+ writeUsageFailure(ctx.deps, `${LAUNCH_RULES_SOLANA_ONLY} ${USAGE11}`, ctx.json);
92569
92810
  return 2;
92570
92811
  }
92571
92812
  try {
@@ -92972,7 +93213,8 @@ async function atomicLaunch(ctx, key, args) {
92972
93213
  wallet: wallet3.address,
92973
93214
  quote: quoteAsset,
92974
93215
  buyRaw: BigInt(buy.raw),
92975
- ...minOut !== undefined ? { minOutRaw: BigInt(minOut) } : {}
93216
+ ...minOut !== undefined ? { minOutRaw: BigInt(minOut) } : {},
93217
+ rules: args.rules !== undefined
92976
93218
  });
92977
93219
  } catch (error) {
92978
93220
  if (!(error instanceof BundleReadError))
@@ -93385,7 +93627,7 @@ init_presentation();
93385
93627
  init_render();
93386
93628
  init_trading();
93387
93629
  init_swap();
93388
- var USAGE11 = "Usage: candle launch status <mint-or-client-launch-id> [--wait]";
93630
+ var USAGE12 = "Usage: candle launch status <mint-or-client-launch-id> [--wait]";
93389
93631
  var LAUNCH_WAIT_MS = 10 * 60 * 1000;
93390
93632
  var LAUNCH_WAIT_POLL_MS = 1e4;
93391
93633
  var LIVE = new Set(["trading", "bonded", "migrated"]);
@@ -93431,7 +93673,7 @@ async function readStatus(ctx, key, id) {
93431
93673
  async function launchStatus(args, ctx) {
93432
93674
  const parsed = parseArgs(args, { booleanFlags: ["--wait"] });
93433
93675
  if ("error" in parsed || parsed.positionals.length !== 1 || !validClientId(parsed.positionals[0] ?? "")) {
93434
- writeUsageFailure(ctx.deps, USAGE11, ctx.json);
93676
+ writeUsageFailure(ctx.deps, USAGE12, ctx.json);
93435
93677
  return 2;
93436
93678
  }
93437
93679
  const id = parsed.positionals[0];
@@ -93915,6 +94157,7 @@ var MCP_TOOL_NAMES = [
93915
94157
  "candle_get_plans",
93916
94158
  "candle_report_activity",
93917
94159
  "candle_trade",
94160
+ "candle_trade_execute",
93918
94161
  "candle_swap",
93919
94162
  "candle_transfer",
93920
94163
  "candle_sweep",
@@ -93923,6 +94166,9 @@ var MCP_TOOL_NAMES = [
93923
94166
  "candle_set_profile_wallets",
93924
94167
  "candle_get_profile_pnl",
93925
94168
  "candle_get_profile_trades",
94169
+ "candle_get_paper_inventory",
94170
+ "candle_get_profile_paper",
94171
+ "candle_get_account_pnl",
93926
94172
  "candle_get_portfolio",
93927
94173
  "candle_resolve_token",
93928
94174
  "candle_execution_status",
@@ -94044,7 +94290,8 @@ async function mcp(args, ctx) {
94044
94290
  }
94045
94291
  const identityConfig = await deps.readConfig();
94046
94292
  const identityFields = effectiveProfileFields(identityConfig, ctx.profile);
94047
- deps.stderr.write(`${identityLine(ctx.profile, identityFields.account, apiUrl, credentialEnvOverrides(deps.env), identityFields.username, identityStyle(deps, "stderr"))}
94293
+ const identityOverrides = credentialEnvOverrides(deps.env);
94294
+ deps.stderr.write(`${identityLine(ctx.profile, identityOverrides.length > 0 ? undefined : identityFields.account, apiUrl, identityOverrides.length > 0 ? identityOverrides : undefined, identityFields.username, identityStyle(deps, "stderr"))}
94048
94295
 
94049
94296
  `);
94050
94297
  if (parsed.booleans.has("--print-config")) {
@@ -99328,7 +99575,7 @@ init_swap();
99328
99575
  init_vault_support();
99329
99576
  var WITHDRAW_SCOPE = "perps:withdraw";
99330
99577
  var ID_RE = /^[A-Za-z0-9._:-]{1,128}$/;
99331
- var USAGE12 = "Usage: candle perps withdraw <amount> --to solana|hood [--wallet <evm tee>] [--id <id>] [--keystore <path>] | candle perps withdraw status <id>";
99578
+ var USAGE13 = "Usage: candle perps withdraw <amount> --to solana|hood [--wallet <evm tee>] [--id <id>] [--keystore <path>] | candle perps withdraw status <id>";
99332
99579
  var WITHDRAW_POLL_MS = 10 * 60 * 1000;
99333
99580
  var WITHDRAW_POLL_INTERVAL_MS = 5000;
99334
99581
  var TERMINAL = new Set(["settled", "refunded", "failed"]);
@@ -99379,7 +99626,7 @@ async function withdrawStatus(ctx, id) {
99379
99626
  async function perpsWithdraw(args, ctx) {
99380
99627
  if (args[0] === "status") {
99381
99628
  if (args.length !== 2 || !ID_RE.test(args[1]))
99382
- return usage10(ctx, USAGE12);
99629
+ return usage10(ctx, USAGE13);
99383
99630
  try {
99384
99631
  return await withdrawStatus(ctx, args[1]);
99385
99632
  } catch (error) {
@@ -99398,7 +99645,7 @@ async function perpsWithdraw(args, ctx) {
99398
99645
  const [amount] = parsed.positionals;
99399
99646
  const to = parsed.values["--to"];
99400
99647
  if (parsed.positionals.length !== 1 || !amount || to !== "solana" && to !== "hood")
99401
- return usage10(ctx, USAGE12);
99648
+ return usage10(ctx, USAGE13);
99402
99649
  const amountMicros = parseWithdrawAmount(amount);
99403
99650
  if (amountMicros === null)
99404
99651
  return usage10(ctx, "The amount must be a positive USDC decimal with at most 6 decimals.");
@@ -101480,12 +101727,12 @@ function planPromotionLine2(table) {
101480
101727
  init_presentation();
101481
101728
  init_render();
101482
101729
  init_key_output();
101483
- var USAGE13 = "Usage: candle plans";
101730
+ var USAGE14 = "Usage: candle plans";
101484
101731
  async function plans(args, ctx) {
101485
101732
  const parsed = parseArgs(args, {});
101486
101733
  if ("error" in parsed || parsed.positionals.length > 0) {
101487
101734
  writeScreenUsageFailure(ctx.deps, "error" in parsed ? `${parsed.error}
101488
- ${USAGE13}` : USAGE13, ctx.json);
101735
+ ${USAGE14}` : USAGE14, ctx.json);
101489
101736
  return 2;
101490
101737
  }
101491
101738
  const result = await apiRequest("/api/v1/agent/plans", {
@@ -101874,15 +102121,14 @@ async function runPlugin(name2, rawArgs, ctx) {
101874
102121
  if (split2.wallets.length > 0) {
101875
102122
  if (!refuseEnvPassphrase(ctx))
101876
102123
  return 1;
101877
- if (!requireTty(ctx, "candle <plugin> --wallet"))
101878
- return 1;
102124
+ const promptCtx = refusePromptsWithoutTerminal(ctx, "candle <plugin> --wallet");
101879
102125
  const resolvedVault = vaultPathFor(ctx, { values: {}, booleans: new Set, positionals: [] });
101880
102126
  if ("error" in resolvedVault)
101881
102127
  return usage(ctx, resolvedVault.error);
101882
102128
  const vaultPath = resolvedVault.path;
101883
102129
  const resolved = await runVaultCommand(ctx, async ({ hold }) => {
101884
102130
  const raw = await requireVaultRaw(ctx, resolvedVault);
101885
- const vault = hold((await unlockInteractively(ctx, vaultPath, raw)).vault);
102131
+ const vault = hold((await unlockInteractively(promptCtx, vaultPath, raw)).vault);
101886
102132
  for (const requested of split2.wallets) {
101887
102133
  const entry = findExternalEntry(vault.index, requested);
101888
102134
  if (entry === undefined) {
@@ -102100,7 +102346,7 @@ function writePosition(ctx, p) {
102100
102346
  // src/commands/pnl.ts
102101
102347
  var CHAINS = ["solana", "hood"];
102102
102348
  var CHAIN_TITLES = { solana: "Solana", hood: "Hood" };
102103
- var NO_API_KEY4 = {
102349
+ var NO_API_KEY5 = {
102104
102350
  code: "NO_API_KEY",
102105
102351
  message: "No API key for this profile.",
102106
102352
  suggestion: "Set CANDLE_API_KEY, or run `candle auth login` to store one."
@@ -102119,7 +102365,7 @@ async function pnl(args, ctx) {
102119
102365
  }
102120
102366
  const apiKey = await resolveApiKey(deps, ctx.profile);
102121
102367
  if (!apiKey) {
102122
- writeLocalFailure(deps, NO_API_KEY4, json);
102368
+ writeLocalFailure(deps, NO_API_KEY5, json);
102123
102369
  return 1;
102124
102370
  }
102125
102371
  const perProfile = ctx.profileFlag !== undefined;
@@ -102567,19 +102813,25 @@ var UNREAD_LABELS = {
102567
102813
  usdg: "USDG",
102568
102814
  "hood-tokens": "tokens"
102569
102815
  };
102570
- var NO_API_KEY5 = {
102816
+ var NO_API_KEY6 = {
102571
102817
  code: "NO_API_KEY",
102572
102818
  message: "No API key for this profile.",
102573
102819
  suggestion: "Set CANDLE_API_KEY, or run `candle auth login` to store one."
102574
102820
  };
102821
+ var NO_TERMINAL_REASON = "not read: no terminal for its unlock";
102822
+ var NO_TERMINAL_NOTE = "The vault was not read: its unlock needs a prompt, and standard input and standard error are not both a terminal. Run it at a terminal, pass --factor touch-id or --factor passkey to unlock without a prompt, or pass --no-vault to leave it out.";
102575
102823
  async function portfolio(args, ctx) {
102576
102824
  const parsed = parseArgs(args, {
102577
102825
  valueFlags: ["--rpc-url", "--evm-rpc-url", "--keystore", "--chain"],
102578
- booleanFlags: ["--no-perps"],
102826
+ booleanFlags: ["--no-perps", "--no-vault"],
102579
102827
  pathFlags: ["--keystore"]
102580
102828
  });
102581
102829
  if ("error" in parsed)
102582
102830
  return usage(ctx, parsed.error);
102831
+ const noVault = parsed.booleans.has("--no-vault");
102832
+ if (noVault && parsed.values["--keystore"] !== undefined) {
102833
+ return usage(ctx, "--keystore names the vault to read and has no effect with --no-vault.");
102834
+ }
102583
102835
  if (parsed.positionals.length > 1)
102584
102836
  return usage(ctx, `Unexpected argument: ${parsed.positionals[1]}`);
102585
102837
  const filter = parsed.positionals[0];
@@ -102602,7 +102854,7 @@ async function portfolio(args, ctx) {
102602
102854
  }
102603
102855
  const apiKey = await resolveApiKey(deps, ctx.profile);
102604
102856
  if (!apiKey) {
102605
- writeLocalFailure(deps, NO_API_KEY5, ctx.json);
102857
+ writeLocalFailure(deps, NO_API_KEY6, ctx.json);
102606
102858
  return 1;
102607
102859
  }
102608
102860
  let solana;
@@ -102626,16 +102878,30 @@ async function portfolio(args, ctx) {
102626
102878
  let vaultEntries;
102627
102879
  let evmEntries;
102628
102880
  let vaultReason;
102881
+ let vaultSkipped = false;
102629
102882
  let known = 0;
102630
- const raw = await readVaultRaw(resolvedVault.path);
102631
- if (raw === null) {
102632
- vaultReason = `no vault at ${resolvedVault.path}`;
102633
- } else {
102883
+ const raw = noVault ? null : await readVaultRaw(resolvedVault.path);
102884
+ let vault;
102885
+ if (raw !== null) {
102634
102886
  if (!refuseEnvPassphrase(ctx))
102635
102887
  return 1;
102636
- if (!requirePromptStreams(ctx, "portfolio"))
102637
- return 1;
102638
- const vault = hold((await unlockInteractively(ctx, resolvedVault.path, raw)).vault);
102888
+ try {
102889
+ const unlockCtx = refusePromptsWithoutTerminal(ctx, "portfolio");
102890
+ vault = hold((await unlockInteractively(unlockCtx, resolvedVault.path, raw)).vault);
102891
+ } catch (error) {
102892
+ if (!isNoTerminalRefusal(error))
102893
+ throw error;
102894
+ }
102895
+ }
102896
+ if (noVault) {
102897
+ vaultReason = "not read (--no-vault)";
102898
+ } else if (raw === null) {
102899
+ vaultReason = `no vault at ${resolvedVault.path}`;
102900
+ } else if (vault === undefined) {
102901
+ vaultReason = NO_TERMINAL_REASON;
102902
+ vaultSkipped = true;
102903
+ notice(deps, "stderr", "note", NO_TERMINAL_NOTE);
102904
+ } else {
102639
102905
  vaultEntries = showSolana ? vault.index.entries.filter((entry) => entry.chain === "solana" && (entry.role === "vault" || entry.role === "external")).map((entry) => ({ address: entry.address, label: entry.label, role: entry.role })) : [];
102640
102906
  evmEntries = showHood ? vault.index.entries.filter((entry) => entry.chain === "evm" && entry.role === "vault").map((entry) => ({ address: entry.address, label: entry.label })) : [];
102641
102907
  known += vaultEntries.length + evmEntries.length;
@@ -102850,7 +103116,7 @@ async function portfolio(args, ctx) {
102850
103116
  const listStatus = walletListStatus(answered);
102851
103117
  const shownUnread = unavailable6.length > 0 || lpUnread > 0;
102852
103118
  const listNote = listStatus === "cut-off" ? "cut-off" : listStatus === "unknown" && !shownUnread ? "unknown" : null;
102853
- const complete = listNote === null && !shownUnread && !perpsPartial;
103119
+ const complete = listNote === null && !shownUnread && !perpsPartial && !vaultSkipped;
102854
103120
  const totalUsd = groups.reduce((sum, g) => sum + g.valueUsd, 0);
102855
103121
  const unpriced = groups.reduce((sum, g) => sum + g.unpriced, 0);
102856
103122
  const lp = lpSection ? {
@@ -102927,6 +103193,7 @@ async function portfolio(args, ctx) {
102927
103193
  byChain,
102928
103194
  unavailableByChain,
102929
103195
  listNote,
103196
+ vaultSkipped,
102930
103197
  lp
102931
103198
  });
102932
103199
  if (perps)
@@ -103302,7 +103569,7 @@ ${tableBlock(ctx, "LP positions", ["GROUP", "WALLET", "POSITION", "POOL", "HOLDI
103302
103569
  notes.push([
103303
103570
  "total",
103304
103571
  formatUsd(totals.totalUsd),
103305
- totals.unpriced > 0 ? `${totals.unpriced} unpriced ${totals.unpriced === 1 ? "holding" : "holdings"} not counted` : "every holding priced"
103572
+ (totals.vaultSkipped ? "partial: the vault was not read (no terminal for its unlock). " : "") + (totals.unpriced > 0 ? `${totals.unpriced} unpriced ${totals.unpriced === 1 ? "holding" : "holdings"} not counted` : totals.vaultSkipped ? "other holdings priced" : "every holding priced")
103306
103573
  ]);
103307
103574
  const style = terminalStyle(deps, "stdout");
103308
103575
  const width = Math.max(...notes.map(([label]) => displayWidth(label)));
@@ -103324,6 +103591,8 @@ ${tableBlock(ctx, "LP positions", ["GROUP", "WALLET", "POSITION", "POOL", "HOLDI
103324
103591
  if (unavailable6 > 0) {
103325
103592
  warnings.push(`${unavailable6} ${unavailable6 === 1 ? "wallet" : "wallets"} could not be read in full (${unreadByChain.map(([chain2, n]) => `${n} on ${CHAIN_NAMES[chain2]}`).join(", ")}). What was not read is marked "not read" and is not in the total.`);
103326
103593
  }
103594
+ if (totals.vaultSkipped)
103595
+ warnings.push("The vault was not read: no terminal for its unlock. Its holdings are not in the total, so the total is partial (exit 3). Run it in a terminal, or pass --no-vault to leave the vault out on purpose.");
103327
103596
  writeNotes2(ctx, warnings);
103328
103597
  writeListNote(ctx, totals.listNote);
103329
103598
  const later = [];
@@ -103377,6 +103646,7 @@ ${financialHeading("Hyperliquid (live from Hyperliquid, not in the ledger; not i
103377
103646
  }
103378
103647
 
103379
103648
  // src/commands/profile.ts
103649
+ init_account();
103380
103650
  init_args();
103381
103651
  init_client();
103382
103652
  init_human_output();
@@ -103673,7 +103943,7 @@ candle profile use <name>`);
103673
103943
  init_args();
103674
103944
  init_deps();
103675
103945
  init_key_output();
103676
- var USAGE14 = 'Usage: candle quant ask "<text>" [--timeout <s>] [--json]';
103946
+ var USAGE15 = 'Usage: candle quant ask "<text>" [--timeout <s>] [--json]';
103677
103947
  var QUANT_ASK_DEFAULT_TIMEOUT_S = 300;
103678
103948
  var QUANT_ASK_MAX_TIMEOUT_S = 3600;
103679
103949
  var QUANT_ASK_SEND_TRIES = 3;
@@ -103842,7 +104112,7 @@ async function quantAsk(args, ctx) {
103842
104112
  const timeoutS = timeoutRaw === undefined ? QUANT_ASK_DEFAULT_TIMEOUT_S : Number(timeoutRaw);
103843
104113
  const text4 = "error" in parsed ? "" : parsed.positionals[0] ?? "";
103844
104114
  if ("error" in parsed || parsed.positionals.length !== 1 || !text4.trim() || !Number.isInteger(timeoutS) || timeoutS < 1 || timeoutS > QUANT_ASK_MAX_TIMEOUT_S) {
103845
- writeScreenUsageFailure(ctx.deps, USAGE14, ctx.json);
104115
+ writeScreenUsageFailure(ctx.deps, USAGE15, ctx.json);
103846
104116
  return 2;
103847
104117
  }
103848
104118
  const apiKey = await resolveApiKey(ctx.deps, ctx.profile);
@@ -104051,7 +104321,7 @@ init_client();
104051
104321
  init_human_output();
104052
104322
  init_presentation();
104053
104323
  init_render();
104054
- var USAGE15 = "Usage: candle scan <contract address> [--chain solana|hood] [--json]";
104324
+ var USAGE16 = "Usage: candle scan <contract address> [--chain solana|hood] [--json]";
104055
104325
  var OTHER_CHAIN = "Candle trades Solana and Hood today; that is not a Solana or Hood contract address.";
104056
104326
  var HOOD_ADDRESS = /^0x[0-9a-fA-F]{40}$/;
104057
104327
  var SOLANA_ADDRESS = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
@@ -104151,20 +104421,20 @@ async function scan(args, ctx) {
104151
104421
  }
104152
104422
  const named = parsed.values["--chain"];
104153
104423
  if (parsed.positionals.length !== 1 || named !== undefined && named !== "solana" && named !== "hood") {
104154
- writeUsageFailure(ctx.deps, USAGE15, ctx.json);
104424
+ writeUsageFailure(ctx.deps, USAGE16, ctx.json);
104155
104425
  return 2;
104156
104426
  }
104157
104427
  const address = parsed.positionals[0].trim();
104158
104428
  const found = chainOfAddress(address);
104159
104429
  if (found === null) {
104160
104430
  writeUsageFailure(ctx.deps, `${OTHER_CHAIN}
104161
- ${USAGE15}`, ctx.json);
104431
+ ${USAGE16}`, ctx.json);
104162
104432
  return 2;
104163
104433
  }
104164
104434
  if (named !== undefined && named !== found) {
104165
104435
  const label = (c) => c === "hood" ? "Hood" : "Solana";
104166
104436
  writeUsageFailure(ctx.deps, `That address is a ${label(found)} contract, not a ${label(named)} one. Candle trades Solana and Hood today.
104167
- ${USAGE15}`, ctx.json);
104437
+ ${USAGE16}`, ctx.json);
104168
104438
  return 2;
104169
104439
  }
104170
104440
  const chain2 = found;
@@ -104507,8 +104777,7 @@ async function sign5(args, ctx) {
104507
104777
  return usage(ctx, solana.error);
104508
104778
  if (!refuseEnvPassphrase(ctx))
104509
104779
  return 1;
104510
- if (!requireTty(ctx, "candle sign"))
104511
- return 1;
104780
+ const promptCtx = refusePromptsWithoutTerminal(ctx, "candle sign");
104512
104781
  const { deps } = ctx;
104513
104782
  const resolvedVault = vaultPathFor(ctx, parsed);
104514
104783
  if ("error" in resolvedVault)
@@ -104538,7 +104807,7 @@ async function sign5(args, ctx) {
104538
104807
  throw error;
104539
104808
  }
104540
104809
  const raw = await requireVaultRaw(ctx, resolvedVault);
104541
- const opened = await unlockInteractively(ctx, path, raw, {
104810
+ const opened = await unlockInteractively(promptCtx, path, raw, {
104542
104811
  acceptOlderCopy: parsed.booleans.has("--accept-older-copy")
104543
104812
  });
104544
104813
  const vault = hold(opened.vault);
@@ -104697,8 +104966,7 @@ async function signMessage2(args, ctx) {
104697
104966
  const requested = lifted.values[0];
104698
104967
  if (!refuseEnvPassphrase(ctx))
104699
104968
  return 1;
104700
- if (!requireTty(ctx, "candle sign message"))
104701
- return 1;
104969
+ const promptCtx = refusePromptsWithoutTerminal(ctx, "candle sign message");
104702
104970
  const { deps } = ctx;
104703
104971
  const resolvedVault = vaultPathFor(ctx, parsed);
104704
104972
  if ("error" in resolvedVault)
@@ -104712,7 +104980,7 @@ async function signMessage2(args, ctx) {
104712
104980
  throw new VaultError("VAULT_UNREADABLE", `The message could not be read: ${error instanceof Error ? error.message : error}.`);
104713
104981
  }
104714
104982
  const raw = await requireVaultRaw(ctx, resolvedVault);
104715
- const opened = await unlockInteractively(ctx, path, raw, {
104983
+ const opened = await unlockInteractively(promptCtx, path, raw, {
104716
104984
  acceptOlderCopy: parsed.booleans.has("--accept-older-copy")
104717
104985
  });
104718
104986
  const vault = hold(opened.vault);
@@ -105480,8 +105748,7 @@ async function vaultRename(args, ctx) {
105480
105748
  const id = parsed.values["--id"];
105481
105749
  if (!refuseEnvPassphrase(ctx))
105482
105750
  return 1;
105483
- if (!requireTty(ctx, "vault rename"))
105484
- return 1;
105751
+ const promptCtx = refusePromptsWithoutTerminal(ctx, "vault rename");
105485
105752
  const { deps } = ctx;
105486
105753
  const resolvedVault = vaultPathFor(ctx, parsed);
105487
105754
  if ("error" in resolvedVault)
@@ -105502,7 +105769,7 @@ async function vaultRename(args, ctx) {
105502
105769
  }
105503
105770
  return runVaultCommand(ctx, async ({ hold }) => {
105504
105771
  const raw = await requireVaultRaw(ctx, resolvedVault);
105505
- const opened = await unlockInteractively(ctx, path, raw, {
105772
+ const opened = await unlockInteractively(promptCtx, path, raw, {
105506
105773
  acceptOlderCopy: parsed.booleans.has("--accept-older-copy")
105507
105774
  });
105508
105775
  const vault = hold(opened.vault);
@@ -105773,10 +106040,9 @@ async function teeRename(args, ctx) {
105773
106040
  if ("error" in vaultPath)
105774
106041
  return usage(ctx, vaultPath.error);
105775
106042
  const raw = rows.some((row2) => row2.tee) ? await readVaultRaw(vaultPath.path) : null;
105776
- if (raw !== null && !requireTty(ctx, "tee rename"))
105777
- return 1;
106043
+ const promptCtx = refusePromptsWithoutTerminal(ctx, "tee rename");
105778
106044
  return runVaultCommand(ctx, async ({ hold }) => {
105779
- const vault = raw === null ? null : hold((await unlockInteractively(ctx, vaultPath.path, raw, {
106045
+ const vault = raw === null ? null : hold((await unlockInteractively(promptCtx, vaultPath.path, raw, {
105780
106046
  acceptOlderCopy: parsed.booleans.has("--accept-older-copy")
105781
106047
  })).vault);
105782
106048
  const local = [];
@@ -106042,7 +106308,7 @@ init_deps();
106042
106308
  init_human_output();
106043
106309
  init_render();
106044
106310
  var KINDS = ["trade", "signal", "callout"];
106045
- var USAGE16 = "Usage: candle token events <mint> [--chain solana|hood] [--kind trade|signal|callout] [--since <iso-8601>] [--limit <n>] [--before <cursor>] [--json]";
106311
+ var USAGE17 = "Usage: candle token events <mint> [--chain solana|hood] [--kind trade|signal|callout] [--since <iso-8601>] [--limit <n>] [--before <cursor>] [--json]";
106046
106312
  function readEventsBody(body) {
106047
106313
  if (body === null || typeof body !== "object" || !Array.isArray(body.events))
106048
106314
  return null;
@@ -106100,11 +106366,11 @@ async function tokenEvents(args, ctx) {
106100
106366
  const since = parsed.values["--since"];
106101
106367
  const before = parsed.values["--before"];
106102
106368
  if (parsed.positionals.length !== 1 || chain2 !== "solana" && chain2 !== "hood") {
106103
- writeUsageFailure(ctx.deps, USAGE16, ctx.json);
106369
+ writeUsageFailure(ctx.deps, USAGE17, ctx.json);
106104
106370
  return 2;
106105
106371
  }
106106
106372
  if (kind !== undefined && !KINDS.includes(kind)) {
106107
- writeUsageFailure(ctx.deps, USAGE16, ctx.json);
106373
+ writeUsageFailure(ctx.deps, USAGE17, ctx.json);
106108
106374
  return 2;
106109
106375
  }
106110
106376
  if (limit !== undefined && !/^[1-9][0-9]{0,3}$/.test(limit) || Number(limit) > 1000) {
@@ -106443,7 +106709,7 @@ init_render();
106443
106709
  init_solana_verify();
106444
106710
  init_trading();
106445
106711
  init_swap();
106446
- var USAGE17 = "Usage: candle transfer --to <address|wallet name|allowlist label|vault> --asset <SOL|USDC|CNDL|ETH|USDG>|--mint <mint>|--token <0x...> --amount <decimal|max> [--wallet <name>] [--rpc-url <url>] [--yes] [--json]";
106712
+ var USAGE18 = "Usage: candle transfer --to <address|wallet name|allowlist label|vault> --asset <SOL|USDC|CNDL|ETH|USDG>|--mint <mint>|--token <0x...> --amount <decimal|max> [--wallet <name>] [--rpc-url <url>] [--yes] [--json]";
106447
106713
  var TRANSFER_ASSETS = [...Object.keys(BASES), ...Object.keys(HOOD_BASES)];
106448
106714
  var BASE58_ADDRESS = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
106449
106715
  async function readLinkedWallets(ctx, key) {
@@ -106588,7 +106854,7 @@ async function transfer(args, ctx) {
106588
106854
  const flags2 = parsed.values;
106589
106855
  const asset = flags2["--asset"]?.toUpperCase();
106590
106856
  if (parsed.positionals.length !== 0 || !flags2["--to"] || !flags2["--amount"] || [flags2["--asset"], flags2["--mint"], flags2["--token"]].filter(Boolean).length !== 1 || asset !== undefined && !TRANSFER_ASSETS.includes(asset) || flags2["--mint"] !== undefined && !BASE58_ADDRESS.test(flags2["--mint"]) || flags2["--token"] !== undefined && !checkEvmAddress(flags2["--token"]).ok) {
106591
- writeUsageFailure(ctx.deps, USAGE17, ctx.json);
106857
+ writeUsageFailure(ctx.deps, USAGE18, ctx.json);
106592
106858
  return 2;
106593
106859
  }
106594
106860
  const to = flags2["--to"];
@@ -109585,7 +109851,10 @@ async function createVault(request2, clock) {
109585
109851
  if (raw === null) {
109586
109852
  throw new VaultError("VAULT_WRITE_FAILED", `The vault was written to ${request2.path} but could not be read back.`);
109587
109853
  }
109588
- return unlockWithPassphrase(request2.path, raw, request2.passphrase, { notice: request2.notice });
109854
+ return unlockWithPassphrase(request2.path, raw, request2.passphrase, {
109855
+ notice: request2.notice,
109856
+ derivationLabel: request2.verificationLabel
109857
+ });
109589
109858
  }
109590
109859
 
109591
109860
  // src/commands/vault-init.ts
@@ -109659,8 +109928,8 @@ async function runPhraseCeremony(ctx, vault, opts = {}) {
109659
109928
  const { closeVault: closeVault2 } = await Promise.resolve().then(() => (init_store(), exports_store));
109660
109929
  closeVault2(reopened);
109661
109930
  }
109662
- notice(deps, "stdout", "danger", `The 24 words below re-derive every key this vault derives, on any BIP-39 wallet, with no passphrase and no server.
109663
- They do NOT restore any key imported from the Phase 1 TEE wallet store; the vault file plus a factor does that.
109931
+ notice(deps, "stdout", "warning", `The 24 words below re-derive every key this vault derives, on any BIP-39 wallet, with no passphrase and no server.
109932
+ They do NOT restore any key you imported from an older wallet store; the vault file plus a factor does that.
109664
109933
  Anyone who reads them can move every derived key's funds.
109665
109934
 
109666
109935
  `);
@@ -109688,6 +109957,9 @@ Anyone who reads them can move every derived key's funds.
109688
109957
  }
109689
109958
  deps.stdout.write(`
109690
109959
  `);
109960
+ await deps.promptLine("Press Enter when you have saved the 24 words offline; the screen will clear for the check: ");
109961
+ deps.stdout.write("\x1B[2J\x1B[H");
109962
+ notice(deps, "stdout", "warning", "The phrase is no longer on screen. Clear your terminal's scrollback: the words were rendered there and this CLI cannot remove them.");
109691
109963
  const positions = randomPositions(3, words.length);
109692
109964
  let confirmed = true;
109693
109965
  for (const position of positions) {
@@ -109696,8 +109968,6 @@ Anyone who reads them can move every derived key's funds.
109696
109968
  confirmed = false;
109697
109969
  }
109698
109970
  deps.stdout.write("\x1B[2J\x1B[H");
109699
- notice(deps, "stdout", "warning", `The phrase is no longer on screen. Clear your terminal's scrollback: the words were rendered there and this CLI cannot remove them.
109700
- `.trim());
109701
109971
  notice(deps, "stdout", "warning", `This vault now records that the recovery phrase was exported; that record never resets.
109702
109972
  `.trim());
109703
109973
  words.fill("");
@@ -109768,7 +110038,8 @@ async function vaultInit(args, ctx) {
109768
110038
  strength: strengthFor(own),
109769
110039
  rootEntropy,
109770
110040
  label: parsed.values["--label"],
109771
- notice: (line) => deps.stderr.write(line)
110041
+ notice: (line) => deps.stderr.write(line),
110042
+ verificationLabel: "Verifying the vault key"
109772
110043
  }, deps));
109773
110044
  try {
109774
110045
  if (ctx.json) {
@@ -109798,12 +110069,13 @@ async function vaultInit(args, ctx) {
109798
110069
  ["Verified", "file re-read, opened with your passphrase, root blob decrypted"],
109799
110070
  ["Create a key", "candle vault new-key --chain solana"]
109800
110071
  ]);
109801
- notice(deps, "stdout", "note", `
110072
+ if (deps.platform === "darwin")
110073
+ notice(deps, "stdout", "note", `
109802
110074
  ${APPLE_ACCOUNT_NOTICE}
109803
110075
  `.trim());
109804
110076
  if (countRecoverableFactors(vault.file.envelopes) === 1) {
109805
110077
  notice(deps, "stdout", "warning", `
109806
- This vault has exactly one recoverable factor: the passphrase. Lose it and no copy of this file can be opened, and the recovery phrase becomes the only route back. Add a second when you can: candle vault enroll security-key
110078
+ This vault has exactly one recoverable factor: the passphrase. Lose it and no copy of this file can be opened, and the recovery phrase becomes the only route back. To add a security key later, run this separate optional command: candle vault enroll security-key
109807
110079
  `.trim());
109808
110080
  }
109809
110081
  notice(deps, "stdout", "note", `
@@ -110962,13 +111234,12 @@ async function vaultList(args, ctx) {
110962
111234
  if (balances && "error" in evmRpc)
110963
111235
  return usage(ctx, evmRpc.error);
110964
111236
  const evmRpcUrl = "url" in evmRpc ? evmRpc.url : DEFAULT_HOOD_RPC_URL;
110965
- if (!requirePromptStreams(ctx, "vault list"))
110966
- return 1;
111237
+ const promptCtx = refusePromptsWithoutTerminal(ctx, "vault list");
110967
111238
  return runVaultCommand(ctx, async ({ hold }) => {
110968
111239
  const raw = await readVaultRaw(path);
110969
111240
  if (raw === null)
110970
111241
  throw missingVault(ctx, resolvedVault);
110971
- const vault = hold((await unlockInteractively(ctx, path, raw, { acceptOlderCopy: parsed.booleans.has("--accept-older-copy") })).vault);
111242
+ const vault = hold((await unlockInteractively(promptCtx, path, raw, { acceptOlderCopy: parsed.booleans.has("--accept-older-copy") })).vault);
110972
111243
  const all = vault.index.entries;
110973
111244
  const filtered = filter === undefined ? all : all.filter((entry) => matches2(entry, filter));
110974
111245
  const showRetired = parsed.booleans.has("--all");
@@ -111358,7 +111629,7 @@ async function promoteEvmFresh(input, fromLabel) {
111358
111629
  addKeys: [blob]
111359
111630
  }, ctx.deps));
111360
111631
  scanStart = await appendScanStart(ctx, vault.path, address, height);
111361
- await confirmAddress(ctx, "vault", destination.address, `sweep this wallet home to ${destination.address}`);
111632
+ await confirmAddress(ctx, "vault", destination.address, `promote a new TEE wallet with pinned vault ${destination.address}`);
111362
111633
  if (toKey !== undefined) {
111363
111634
  notice(ctx.deps, "stderr", "note", `${[toKeyPlanLine(toKey), ...signerStaysLines(toKey), ...targetWarnings(toKey.target, "evm")].join(`
111364
111635
  `)}
@@ -111738,7 +112009,7 @@ async function promoteFresh(ctx, parsed, fromLabel, toKey) {
111738
112009
  },
111739
112010
  addKeys: [blob]
111740
112011
  }, ctx.deps));
111741
- await confirmAddress(ctx, "vault", destination.address, `sweep this wallet home to ${destination.address}`);
112012
+ await confirmAddress(ctx, "vault", destination.address, `promote a new TEE wallet with pinned vault ${destination.address}`);
111742
112013
  if (toKey !== undefined) {
111743
112014
  notice(ctx.deps, "stderr", "note", `${[toKeyPlanLine(toKey), ...signerStaysLines(toKey), ...targetWarnings(toKey.target, "solana")].join(`
111744
112015
  `)}
@@ -114249,8 +114520,8 @@ init_store();
114249
114520
  init_vault_support();
114250
114521
  var LIST_POINTER = "One line per key, with an optional SOL balance: candle vault list";
114251
114522
  var NO_VERIFIED_BACKUP_NOTE = `No backup of this vault has ever been verified from this machine. If this file is lost, only the 24-word recovery phrase can rebuild it, and it rebuilds derived keys only. Take one now:
114252
- candle vault backup --to <path>
114253
- on a Mac with iCloud Drive: candle vault backup --to icloud`;
114523
+ candle vault backup --to <path>`;
114524
+ var ICLOUD_BACKUP_LINE = "on a Mac with iCloud Drive: candle vault backup --to icloud";
114254
114525
  async function vaultStatus(args, ctx) {
114255
114526
  const parsed = parseArgs(args, {
114256
114527
  valueFlags: ["--keystore"],
@@ -114392,7 +114663,8 @@ async function vaultStatus(args, ctx) {
114392
114663
  notice(deps, "stdout", "note", "No vault.state.json beside this vault, so an older copy of it cannot be recognized on this machine.");
114393
114664
  }
114394
114665
  if (sidecar?.lastVerifiedBackupAt === undefined)
114395
- notice(deps, "stdout", "warning", NO_VERIFIED_BACKUP_NOTE);
114666
+ notice(deps, "stdout", "warning", deps.platform === "darwin" ? `${NO_VERIFIED_BACKUP_NOTE}
114667
+ ${ICLOUD_BACKUP_LINE}` : NO_VERIFIED_BACKUP_NOTE);
114396
114668
  if (legacyPresent) {
114397
114669
  notice(deps, "stdout", "warning", `A legacy wallets.enc exists at ${legacy}; this CLI does not read it.
114398
114670
  A key it holds that was imported is already a linked wallet, managed with: candle wallets
@@ -114698,7 +114970,7 @@ init_human_output();
114698
114970
  init_release();
114699
114971
  import { dirname as dirname13, join as join15 } from "node:path";
114700
114972
  init_key_output();
114701
- var USAGE18 = "Usage: candle verify <file> --bundle <path> [--identity <uri>] [--issuer <url>]";
114973
+ var USAGE19 = "Usage: candle verify <file> --bundle <path> [--identity <uri>] [--issuer <url>]";
114702
114974
  async function resolveIdentity(deps, bundlePath, flag) {
114703
114975
  if (flag)
114704
114976
  return { kind: "ok", uri: flag, provenance: "identity from --identity" };
@@ -114730,19 +115002,19 @@ async function verify(args, ctx) {
114730
115002
  });
114731
115003
  if ("error" in parsed) {
114732
115004
  writeScreenUsageFailure(deps, `${parsed.error}
114733
- ${USAGE18}`, json);
115005
+ ${USAGE19}`, json);
114734
115006
  return 2;
114735
115007
  }
114736
115008
  const file = parsed.positionals[0];
114737
115009
  if (parsed.positionals.length !== 1 || file === undefined) {
114738
115010
  writeScreenUsageFailure(deps, `verify takes exactly one file.
114739
- ${USAGE18}`, json);
115011
+ ${USAGE19}`, json);
114740
115012
  return 2;
114741
115013
  }
114742
115014
  const bundlePath = parsed.values["--bundle"];
114743
115015
  if (!bundlePath) {
114744
115016
  writeScreenUsageFailure(deps, `--bundle is required.
114745
- ${USAGE18}`, json);
115017
+ ${USAGE19}`, json);
114746
115018
  return 2;
114747
115019
  }
114748
115020
  const resolved = await resolveIdentity(deps, bundlePath, parsed.values["--identity"]);
@@ -114756,7 +115028,7 @@ ${USAGE18}`, json);
114756
115028
  }
114757
115029
  if (resolved.kind === "absent") {
114758
115030
  writeScreenUsageFailure(deps, `--identity is required: there is no latest.json beside ${bundlePath} to take the release version from.
114759
- ${USAGE18}`, json);
115031
+ ${USAGE19}`, json);
114760
115032
  return 2;
114761
115033
  }
114762
115034
  const identity4 = resolved.uri;
@@ -115006,6 +115278,101 @@ function walletsDisallowLaunch(args, ctx) {
115006
115278
  return setAllowLaunch(args, ctx, false);
115007
115279
  }
115008
115280
 
115281
+ // src/commands/wallets-allowlist.ts
115282
+ init_args();
115283
+ init_client();
115284
+ init_deps();
115285
+ init_human_output();
115286
+ init_presentation();
115287
+ init_profiles();
115288
+ init_render();
115289
+ init_key_output();
115290
+ var EMPTY_NOTE = "No approved withdrawal addresses. An agent can move funds only to the account's own wallets and its vault.";
115291
+ var MANAGED_HINT = "Approving an address needs the owner signed in to the agent console's Wallets tab; an API key cannot approve. An acting key can revoke one; a read-only key cannot.";
115292
+ async function walletsAllowlist(args, ctx) {
115293
+ const { deps, apiUrl, json } = ctx;
115294
+ const parsed = parseArgs(args, { booleanFlags: ["--all"] });
115295
+ if ("error" in parsed) {
115296
+ writeScreenUsageFailure(deps, parsed.error, json);
115297
+ return 2;
115298
+ }
115299
+ if (parsed.positionals.length > 0) {
115300
+ writeScreenUsageFailure(deps, `Unexpected argument: ${parsed.positionals[0]}`, json);
115301
+ return 2;
115302
+ }
115303
+ await printIdentity(ctx);
115304
+ const apiKey = await resolveApiKey(deps, ctx.profile);
115305
+ if (!apiKey) {
115306
+ writeScreenLocalFailure(deps, { code: "NO_API_KEY", message: "No API key available.", suggestion: "Run: candle keys create" }, json);
115307
+ return 1;
115308
+ }
115309
+ const query3 = parsed.booleans.has("--all") ? "?includeRevoked=true" : "";
115310
+ const result = await apiRequest(`/api/v1/agent/withdrawal-addresses${query3}`, {
115311
+ method: "GET",
115312
+ auth: "key",
115313
+ credentials: { apiKey },
115314
+ apiUrl,
115315
+ fetch: deps.fetch,
115316
+ env: deps.env
115317
+ });
115318
+ if (!result.ok) {
115319
+ writeScreenFailure(deps, result, { apiUrl, authType: "key" }, json);
115320
+ return 1;
115321
+ }
115322
+ const body = result.body;
115323
+ if (!Array.isArray(body.addresses)) {
115324
+ writeScreenLocalFailure(deps, { code: "INVALID_RESPONSE", message: "The withdrawal allowlist read did not return a list." }, json);
115325
+ return 1;
115326
+ }
115327
+ const rows = body.addresses;
115328
+ if (json) {
115329
+ deps.stdout.write(`${JSON.stringify({ success: true, addresses: rows, count: rows.length })}
115330
+ `);
115331
+ return 0;
115332
+ }
115333
+ const style = terminalStyle(deps, "stdout");
115334
+ deps.stdout.write(`${heading(`Withdrawal allowlist (${rows.length})`, style)}
115335
+
115336
+ `);
115337
+ if (rows.length === 0) {
115338
+ notice(deps, "stdout", "note", EMPTY_NOTE);
115339
+ notice(deps, "stdout", "note", MANAGED_HINT);
115340
+ return 0;
115341
+ }
115342
+ if (style.width < 100) {
115343
+ for (const row2 of rows) {
115344
+ writeFields(deps, "stdout", [
115345
+ ["Id", row2.id],
115346
+ ["Address", row2.address],
115347
+ ["Label", row2.label],
115348
+ ["Chain", row2.chain],
115349
+ ["Revoked", row2.revokedAt ? "yes" : "no"]
115350
+ ]);
115351
+ deps.stdout.write(`
115352
+ `);
115353
+ }
115354
+ } else {
115355
+ deps.stdout.write(`${renderTable(["Id", "Chain", "Address", "Label", "Revoked"], rows.map((row2) => [
115356
+ row2.id,
115357
+ row2.chain,
115358
+ row2.address,
115359
+ row2.label ?? symbols(style).missing,
115360
+ row2.revokedAt ? "yes" : "no"
115361
+ ]), {
115362
+ style,
115363
+ width: style.width,
115364
+ indent: 2,
115365
+ rule: false,
115366
+ columns: [{ id: true }, { priority: 3 }, { id: true }, { priority: 2 }, { priority: 1 }]
115367
+ })}
115368
+ `);
115369
+ }
115370
+ deps.stdout.write(`
115371
+ `);
115372
+ notice(deps, "stdout", "note", MANAGED_HINT);
115373
+ return 0;
115374
+ }
115375
+
115009
115376
  // src/commands/wallets-authorize.ts
115010
115377
  init_args();
115011
115378
  init_client();
@@ -115015,7 +115382,7 @@ init_presentation();
115015
115382
  init_profiles();
115016
115383
  init_render();
115017
115384
  init_key_output();
115018
- var USAGE19 = "Usage: candle wallets authorize [--no-browser] [--no-wait] [--timeout <seconds>] [--json]";
115385
+ var USAGE20 = "Usage: candle wallets authorize [--no-browser] [--no-wait] [--timeout <seconds>] [--json]";
115019
115386
  var AUTHORIZE_WAIT_DEFAULT_SECONDS = 600;
115020
115387
  var AUTHORIZE_POLL_MS = 3000;
115021
115388
  function authorizeUrl(apiUrl, portalOrigin) {
@@ -115038,12 +115405,12 @@ async function walletsAuthorize(args, ctx) {
115038
115405
  const parsed = parseArgs(args, { booleanFlags: ["--no-browser", "--no-wait"], valueFlags: ["--timeout"] });
115039
115406
  if ("error" in parsed) {
115040
115407
  writeScreenUsageFailure(deps, `${parsed.error}
115041
- ${USAGE19}`, json);
115408
+ ${USAGE20}`, json);
115042
115409
  return 2;
115043
115410
  }
115044
115411
  if (parsed.positionals.length > 0) {
115045
115412
  writeScreenUsageFailure(deps, `Unexpected argument: ${parsed.positionals[0]}
115046
- ${USAGE19}`, json);
115413
+ ${USAGE20}`, json);
115047
115414
  return 2;
115048
115415
  }
115049
115416
  let timeoutSeconds = AUTHORIZE_WAIT_DEFAULT_SECONDS;
@@ -115052,7 +115419,7 @@ ${USAGE19}`, json);
115052
115419
  const n = Number(rawTimeout);
115053
115420
  if (!Number.isInteger(n) || n <= 0) {
115054
115421
  writeScreenUsageFailure(deps, `--timeout must be a whole number of seconds above 0, got ${rawTimeout}
115055
- ${USAGE19}`, json);
115422
+ ${USAGE20}`, json);
115056
115423
  return 2;
115057
115424
  }
115058
115425
  timeoutSeconds = n;
@@ -115159,7 +115526,7 @@ init_solana_lite();
115159
115526
  init_trading();
115160
115527
  init_swap();
115161
115528
  import { randomUUID as randomUUID16 } from "node:crypto";
115162
- var USAGE20 = "Usage: candle wallet close-empty [--wallet embedded|<tee address|label>] [--keep <mint>]... [--client-trade-id <id>] [--preview] [--yes] [--json]";
115529
+ var USAGE21 = "Usage: candle wallet close-empty [--wallet embedded|<tee address|label>] [--keep <mint>]... [--client-trade-id <id>] [--preview] [--yes] [--json]";
115163
115530
  var BASE58_ADDRESS2 = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
115164
115531
  var LISTED_ROWS = 50;
115165
115532
  var TOKEN_IX_CLOSE_ACCOUNT3 = 9;
@@ -115434,7 +115801,7 @@ async function closeTee(ctx, key, wallet3, name2, run2) {
115434
115801
  async function walletsCloseEmpty(args, ctx) {
115435
115802
  const lifted = splitKeep(args);
115436
115803
  if ("error" in lifted) {
115437
- writeUsageFailure(ctx.deps, `${lifted.error}. ${USAGE20}`, ctx.json);
115804
+ writeUsageFailure(ctx.deps, `${lifted.error}. ${USAGE21}`, ctx.json);
115438
115805
  return 2;
115439
115806
  }
115440
115807
  const parsed = parseArgs(lifted.rest, {
@@ -115448,7 +115815,7 @@ async function walletsCloseEmpty(args, ctx) {
115448
115815
  const named = parsed.values["--client-trade-id"];
115449
115816
  const id = named ?? `close-${randomUUID16()}`;
115450
115817
  if (parsed.positionals.length !== 0 || !validClientId(id) || !lifted.keep.every((m) => BASE58_ADDRESS2.test(m))) {
115451
- writeUsageFailure(ctx.deps, USAGE20, ctx.json);
115818
+ writeUsageFailure(ctx.deps, USAGE21, ctx.json);
115452
115819
  return 2;
115453
115820
  }
115454
115821
  const run2 = {
@@ -115708,6 +116075,7 @@ init_ownership();
115708
116075
  init_file_lock();
115709
116076
 
115710
116077
  // src/guard.ts
116078
+ init_account();
115711
116079
  init_profiles();
115712
116080
  async function verifyProfileAccount(ctx, config) {
115713
116081
  const { deps, profile } = ctx;
@@ -115956,6 +116324,7 @@ var COMMANDS = {
115956
116324
  access: keysAccess,
115957
116325
  update: keysUpdate,
115958
116326
  limit: keysLimit,
116327
+ limits: keysLimits,
115959
116328
  self: keysSelf,
115960
116329
  rotate: keysRotate,
115961
116330
  revoke: keysRevoke,
@@ -115974,7 +116343,8 @@ var COMMANDS = {
115974
116343
  "allow-launch": walletsAllowLaunch,
115975
116344
  "disallow-launch": walletsDisallowLaunch,
115976
116345
  "close-empty": walletsCloseEmpty,
115977
- authorize: walletsAuthorize
116346
+ authorize: walletsAuthorize,
116347
+ allowlist: walletsAllowlist
115978
116348
  },
115979
116349
  bare: wallets
115980
116350
  },