@candledottv/cli 0.13.21 → 0.13.23

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 +43 -4
  2. package/dist/index.js +464 -223
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -84,6 +84,127 @@ var __callDispose = (stack, error, hasError) => {
84
84
  return next();
85
85
  };
86
86
 
87
+ // ../mcp/src/api-key-file.ts
88
+ import { closeSync, constants, fstatSync, openSync, readSync } from "node:fs";
89
+ import { resolve } from "node:path";
90
+ function hasApiKeyFile(env) {
91
+ return Boolean(env.CANDLE_API_KEY_FILE?.trim());
92
+ }
93
+ function assertApiKeySources(env) {
94
+ if (hasApiKeyFile(env) && (env.CANDLE_API_KEY?.trim() || env.CANDLE_AGENT_API_KEY?.trim())) {
95
+ throw new ApiKeyFileError("API_KEY_SOURCE_CONFLICT", "CANDLE_API_KEY_FILE conflicts with CANDLE_API_KEY or CANDLE_AGENT_API_KEY. Unset the raw sources or unset CANDLE_API_KEY_FILE.");
96
+ }
97
+ }
98
+ function requireDescriptorMetadata(stat) {
99
+ if ([stat.uid, stat.mode, stat.size, stat.mtimeNs, stat.ctimeNs].some((value) => typeof value !== "bigint")) {
100
+ throw new ApiKeyFileError("API_KEY_FILE_UNSUPPORTED", "CANDLE_API_KEY_FILE requires POSIX descriptor metadata. Use an existing credential source or a supported host.");
101
+ }
102
+ }
103
+ function invalid(rule) {
104
+ throw new ApiKeyFileError("API_KEY_FILE_INVALID", `CANDLE_API_KEY_FILE ${rule}. Fix the file locally.`);
105
+ }
106
+ function readApiKeyFile(path, io = apiKeyFileIo) {
107
+ let fd;
108
+ let key = "";
109
+ let failure;
110
+ try {
111
+ const uid = io.effectiveUid();
112
+ if (!["linux", "darwin"].includes(io.platform) || uid === undefined || !io.constants.O_NOFOLLOW || !io.constants.O_NONBLOCK || !io.open || !io.stat || !io.read || !io.close) {
113
+ throw new ApiKeyFileError("API_KEY_FILE_UNSUPPORTED", "CANDLE_API_KEY_FILE requires Linux or macOS with POSIX descriptor checks. Use an existing credential source or a supported host.");
114
+ }
115
+ fd = io.open(path, io.constants.O_RDONLY | io.constants.O_NOFOLLOW | io.constants.O_NONBLOCK);
116
+ const before = io.stat(fd);
117
+ requireDescriptorMetadata(before);
118
+ const mode = Number(before.mode & 0o7777n);
119
+ if (!before.isFile() || before.uid !== BigInt(uid) || mode !== 256 && mode !== 384) {
120
+ invalid("must be a regular file owned by the effective user with mode 0400 or 0600");
121
+ }
122
+ if (before.size > 4096n)
123
+ invalid("must be at most 4096 bytes");
124
+ const bytes = Buffer.alloc(4097);
125
+ let count = 0;
126
+ while (count < bytes.length) {
127
+ const read = io.read(fd, bytes, count, bytes.length - count);
128
+ if (read === 0)
129
+ break;
130
+ count += read;
131
+ }
132
+ const after = io.stat(fd);
133
+ requireDescriptorMetadata(after);
134
+ if (!after.isFile() || count > 4096 || BigInt(count) !== before.size || before.uid !== after.uid || before.mode !== after.mode || before.size !== after.size || before.mtimeNs !== after.mtimeNs || before.ctimeNs !== after.ctimeNs)
135
+ invalid("must remain unchanged while being read");
136
+ let end = count;
137
+ if (bytes[end - 1] === 10) {
138
+ end--;
139
+ if (bytes[end - 1] === 13)
140
+ end--;
141
+ }
142
+ if (end === 0)
143
+ invalid("must contain one nonempty printable ASCII token, optionally followed by LF or CRLF");
144
+ for (let i = 0;i < end; i++) {
145
+ const byte = bytes[i] ?? 0;
146
+ if (byte < 33 || byte > 126)
147
+ invalid("must contain one nonempty printable ASCII token, optionally followed by LF or CRLF");
148
+ }
149
+ key = bytes.subarray(0, end).toString("ascii");
150
+ } catch (error) {
151
+ const code = error?.code;
152
+ failure = error instanceof ApiKeyFileError ? error : code === "ELOOP" ? new ApiKeyFileError("API_KEY_FILE_INVALID", "CANDLE_API_KEY_FILE must not be a final-component symlink. Fix the file locally.") : ["ENOSYS", "ENOTSUP"].includes(String(code)) ? new ApiKeyFileError("API_KEY_FILE_UNSUPPORTED", "CANDLE_API_KEY_FILE requires POSIX descriptor checks. Use an existing credential source or a supported host.") : new ApiKeyFileError("API_KEY_FILE_UNREADABLE", "Cannot read CANDLE_API_KEY_FILE. Check the configured path and access.");
153
+ } finally {
154
+ if (fd !== undefined) {
155
+ try {
156
+ io.close(fd);
157
+ } catch {
158
+ failure ??= new ApiKeyFileError("API_KEY_FILE_UNREADABLE", "Cannot close CANDLE_API_KEY_FILE. Check local file access.");
159
+ }
160
+ }
161
+ }
162
+ if (failure)
163
+ throw failure;
164
+ return key;
165
+ }
166
+ function apiKeyFileSnapshot(env, load = readApiKeyFile, cwd = process.cwd()) {
167
+ env = { ...env };
168
+ const selected = hasApiKeyFile(env);
169
+ const path = selected ? resolve(cwd, env.CANDLE_API_KEY_FILE ?? "") : undefined;
170
+ let result;
171
+ return () => {
172
+ result ??= Promise.resolve().then(async () => {
173
+ assertApiKeySources(env);
174
+ if (path === undefined)
175
+ return;
176
+ try {
177
+ return await load(path);
178
+ } catch (error) {
179
+ if (error instanceof ApiKeyFileError)
180
+ throw error;
181
+ throw new ApiKeyFileError("API_KEY_FILE_UNREADABLE", "Cannot read CANDLE_API_KEY_FILE. Check the configured path and access.");
182
+ }
183
+ });
184
+ return result;
185
+ };
186
+ }
187
+ var ApiKeyFileError, apiKeyFileIo;
188
+ var init_api_key_file = __esm(() => {
189
+ ApiKeyFileError = class ApiKeyFileError extends Error {
190
+ code;
191
+ constructor(code, message) {
192
+ super(message);
193
+ this.code = code;
194
+ this.name = "ApiKeyFileError";
195
+ }
196
+ };
197
+ apiKeyFileIo = {
198
+ platform: process.platform,
199
+ effectiveUid: () => process.geteuid?.(),
200
+ constants,
201
+ open: openSync,
202
+ stat: (fd) => fstatSync(fd, { bigint: true }),
203
+ read: (fd, buffer, offset, length) => readSync(fd, buffer, offset, length, null),
204
+ close: closeSync
205
+ };
206
+ });
207
+
87
208
  // src/client.ts
88
209
  function setOwnerConfirmHooks(hooks) {
89
210
  const previous = installedOwnerConfirmHooks;
@@ -634,7 +755,7 @@ var init_terminal_theme = __esm(() => {
634
755
  });
635
756
 
636
757
  // src/version.ts
637
- var CLI_VERSION = "0.13.21";
758
+ var CLI_VERSION = "0.13.23";
638
759
 
639
760
  // src/presentation.ts
640
761
  function terminalStyle(deps, stream, machine = false) {
@@ -5697,13 +5818,16 @@ function keyedRedirectGuard(doFetch) {
5697
5818
  };
5698
5819
  }
5699
5820
  function resolveConfig(env = process.env) {
5821
+ assertApiKeySources(env);
5822
+ const fileKey = hasApiKeyFile(env) ? readApiKeyFile(env.CANDLE_API_KEY_FILE ?? "") : undefined;
5700
5823
  const apiUrl = env.CANDLE_API_URL?.trim() || DEFAULT_API_URL2;
5701
5824
  assertTransportSecurity(apiUrl, env);
5702
- const apiKey = env.CANDLE_AGENT_API_KEY?.trim() || env.CANDLE_API_KEY?.trim();
5825
+ const apiKey = fileKey ?? (env.CANDLE_AGENT_API_KEY?.trim() || env.CANDLE_API_KEY?.trim());
5703
5826
  return apiKey ? { apiUrl, apiKey } : { apiUrl };
5704
5827
  }
5705
5828
  var DEFAULT_API_URL2 = "https://api.alpha.candle.tv", REDIRECT_STATUSES2, MAX_REDIRECTS2 = 5, CrossOriginRedirectError2;
5706
5829
  var init_client2 = __esm(() => {
5830
+ init_api_key_file();
5707
5831
  REDIRECT_STATUSES2 = new Set([301, 302, 303, 307, 308]);
5708
5832
  CrossOriginRedirectError2 = class CrossOriginRedirectError2 extends Error {
5709
5833
  from;
@@ -5909,7 +6033,7 @@ var init_convert = __esm(() => {
5909
6033
  });
5910
6034
 
5911
6035
  // ../mcp/src/image.ts
5912
- import { constants } from "node:fs";
6036
+ import { constants as constants2 } from "node:fs";
5913
6037
  import { lstat, open, realpath } from "node:fs/promises";
5914
6038
  import { dirname, isAbsolute, relative, sep } from "node:path";
5915
6039
  function configuredImageDir(env) {
@@ -5935,16 +6059,16 @@ function errno(error) {
5935
6059
  return error?.code;
5936
6060
  }
5937
6061
  async function realImageDir(imageDir, fs) {
5938
- const invalid = () => new ImageRefusal("IMAGE_DIR_INVALID", `CANDLE_IMAGE_DIR must be an absolute path to an existing directory, and not the filesystem root. ${NOTHING_UPLOADED}`);
6062
+ const invalid2 = () => new ImageRefusal("IMAGE_DIR_INVALID", `CANDLE_IMAGE_DIR must be an absolute path to an existing directory, and not the filesystem root. ${NOTHING_UPLOADED}`);
5939
6063
  if (!isAbsolute(imageDir))
5940
- throw invalid();
6064
+ throw invalid2();
5941
6065
  try {
5942
6066
  const real = await fs.realpath(imageDir);
5943
6067
  if (dirname(real) === real || !(await fs.lstat(real, { bigint: true })).isDirectory())
5944
- throw invalid();
6068
+ throw invalid2();
5945
6069
  return real;
5946
6070
  } catch {
5947
- throw invalid();
6071
+ throw invalid2();
5948
6072
  }
5949
6073
  }
5950
6074
  async function missingInside(dirReal, path, fs) {
@@ -5987,7 +6111,7 @@ async function readImageInDir(imageDir, imagePath, fs = nodeFs, field = "imagePa
5987
6111
  throw outside();
5988
6112
  let handle;
5989
6113
  try {
5990
- handle = await fs.open(fileReal, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0) | (constants.O_NONBLOCK ?? 0));
6114
+ handle = await fs.open(fileReal, constants2.O_RDONLY | (constants2.O_NOFOLLOW ?? 0) | (constants2.O_NONBLOCK ?? 0));
5991
6115
  } catch (error) {
5992
6116
  throw errno(error) === "ELOOP" ? outside() : unreadable("it could not be opened");
5993
6117
  }
@@ -6078,24 +6202,24 @@ async function resolveLaunchImage(args, imageDir, cfg, doFetch, fs = nodeFs) {
6078
6202
  const hasUrl = typeof rest.imageUrl === "string" && rest.imageUrl.length > 0;
6079
6203
  const hasBannerPath = typeof bannerPath === "string" && bannerPath.length > 0;
6080
6204
  const hasBannerUrl = typeof rest.bannerUrl === "string" && rest.bannerUrl.length > 0;
6081
- const invalid = (message) => ({ refused: { code: "MCP_VALIDATION", message } });
6205
+ const invalid2 = (message) => ({ refused: { code: "MCP_VALIDATION", message } });
6082
6206
  if (imageDir === undefined) {
6083
6207
  if (imagePath !== undefined)
6084
- return invalid(`imagePath is not available on this server: no CANDLE_IMAGE_DIR is configured. Pass imageUrl, an https URL. ${NOTHING_UPLOADED}`);
6208
+ return invalid2(`imagePath is not available on this server: no CANDLE_IMAGE_DIR is configured. Pass imageUrl, an https URL. ${NOTHING_UPLOADED}`);
6085
6209
  if (bannerPath !== undefined)
6086
- return invalid(`bannerPath is not available on this server: no CANDLE_IMAGE_DIR is configured. Pass bannerUrl, an https URL. ${NOTHING_UPLOADED}`);
6210
+ return invalid2(`bannerPath is not available on this server: no CANDLE_IMAGE_DIR is configured. Pass bannerUrl, an https URL. ${NOTHING_UPLOADED}`);
6087
6211
  return { args: rest };
6088
6212
  }
6089
6213
  if (hasPath && hasUrl)
6090
- return invalid(`Pass exactly one of imageUrl or imagePath, not both. ${NOTHING_UPLOADED}`);
6214
+ return invalid2(`Pass exactly one of imageUrl or imagePath, not both. ${NOTHING_UPLOADED}`);
6091
6215
  if (hasBannerPath && hasBannerUrl)
6092
- return invalid(`Pass at most one of bannerUrl or bannerPath, not both. ${NOTHING_UPLOADED}`);
6216
+ return invalid2(`Pass at most one of bannerUrl or bannerPath, not both. ${NOTHING_UPLOADED}`);
6093
6217
  if (!hasPath && !hasUrl)
6094
- return invalid("Pass imageUrl (an https URL) or imagePath (an absolute path to an image file inside CANDLE_IMAGE_DIR).");
6218
+ return invalid2("Pass imageUrl (an https URL) or imagePath (an absolute path to an image file inside CANDLE_IMAGE_DIR).");
6095
6219
  if (!hasPath && !hasBannerPath)
6096
6220
  return { args: rest };
6097
6221
  if (!cfg.apiKey)
6098
- return invalid(`CANDLE_AGENT_API_KEY is required to upload an image; the key needs the launch:write scope. ${NOTHING_UPLOADED}`);
6222
+ return invalid2(`CANDLE_AGENT_API_KEY is required to upload an image; the key needs the launch:write scope. ${NOTHING_UPLOADED}`);
6099
6223
  const read = async (path, field) => {
6100
6224
  try {
6101
6225
  return await readImageInDir(imageDir, path, fs, field);
@@ -6135,7 +6259,7 @@ async function resolveLaunchImage(args, imageDir, cfg, doFetch, fs = nodeFs) {
6135
6259
  if (uploaded === undefined)
6136
6260
  return result2;
6137
6261
  const why = "refused" in result2 ? result2.refused.message : result2.uploadFailed;
6138
- return invalid(`The banner upload failed: ${why} The image was already uploaded to ${uploaded}; pass that as imageUrl on the retry. No launch was attempted.`);
6262
+ return invalid2(`The banner upload failed: ${why} The image was already uploaded to ${uploaded}; pass that as imageUrl on the retry. No launch was attempted.`);
6139
6263
  }
6140
6264
  uploadedBanner = result2.url;
6141
6265
  launchArgs.bannerUrl = uploadedBanner;
@@ -6189,7 +6313,7 @@ function createLabClient(options) {
6189
6313
  const doFetch = options.fetch ?? globalThis.fetch;
6190
6314
  const callMs = options.callTimeoutMs ?? LAB_CALL_TIMEOUT_MS;
6191
6315
  const now = options.now ?? Date.now;
6192
- const sleep = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
6316
+ const sleep = options.sleep ?? ((ms) => new Promise((resolve2) => setTimeout(resolve2, ms)));
6193
6317
  async function call(method, path, valid = () => true, body, timeoutMs = callMs) {
6194
6318
  const controller = new AbortController;
6195
6319
  let timedOut = false;
@@ -10077,7 +10201,7 @@ function perpsMarketGate(meta, ctx, minOpenInterestUsd) {
10077
10201
  return { tradeable: false, reason: "minOpenInterest" };
10078
10202
  return { tradeable: true, reason: null };
10079
10203
  }
10080
- function resolve(snapshot, coin) {
10204
+ function resolve2(snapshot, coin) {
10081
10205
  if (/^[A-Za-z0-9]{1,20}$/.test(coin)) {
10082
10206
  const exact = snapshot.universe.findIndex((row) => row.name === coin);
10083
10207
  if (exact !== -1)
@@ -10321,7 +10445,7 @@ class PerpsMarketReader {
10321
10445
  async market(coin) {
10322
10446
  this.key();
10323
10447
  const [config, core, extras] = await Promise.all([this.config(), this.core(), this.extras()]);
10324
- const asset = resolve(core, coin);
10448
+ const asset = resolve2(core, coin);
10325
10449
  const meta = at(core.universe, asset);
10326
10450
  const ctx = at(core.ctxs, asset);
10327
10451
  return {
@@ -10347,7 +10471,7 @@ class PerpsMarketReader {
10347
10471
  if (opts.sigFigs !== undefined)
10348
10472
  bounded(opts.sigFigs, 5, 2, 5, "sigFigs");
10349
10473
  const [config, core] = await Promise.all([this.config(), this.core()]);
10350
- const canonical2 = at(core.universe, resolve(core, coin)).name;
10474
+ const canonical2 = at(core.universe, resolve2(core, coin)).name;
10351
10475
  const value = await info(this.opts.fetcher, this.opts.network, {
10352
10476
  type: "l2Book",
10353
10477
  coin: canonical2,
@@ -10388,7 +10512,7 @@ class PerpsMarketReader {
10388
10512
  this.key();
10389
10513
  const window = perpsCandleWindow(opts, this.now());
10390
10514
  const [config, core] = await Promise.all([this.config(), this.core()]);
10391
- const canonical2 = at(core.universe, resolve(core, coin)).name;
10515
+ const canonical2 = at(core.universe, resolve2(core, coin)).name;
10392
10516
  const value = await info(this.opts.fetcher, this.opts.network, {
10393
10517
  type: "candleSnapshot",
10394
10518
  req: { coin: canonical2, interval: window.interval, startTime: window.startTime, endTime: window.endTime }
@@ -10423,7 +10547,7 @@ class PerpsMarketReader {
10423
10547
  this.key();
10424
10548
  const window = perpsFundingWindow(opts, this.now());
10425
10549
  const [config, core] = await Promise.all([this.config(), this.core()]);
10426
- const canonical2 = at(core.universe, resolve(core, coin)).name;
10550
+ const canonical2 = at(core.universe, resolve2(core, coin)).name;
10427
10551
  const value = await info(this.opts.fetcher, this.opts.network, {
10428
10552
  type: "fundingHistory",
10429
10553
  coin: canonical2,
@@ -10515,7 +10639,7 @@ var init_hyperliquid_markets = __esm(() => {
10515
10639
  function isTime(value) {
10516
10640
  return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
10517
10641
  }
10518
- function invalid(field, message) {
10642
+ function invalid2(field, message) {
10519
10643
  return new PerpsHistoryError("VALIDATION_FAILED", message, { field });
10520
10644
  }
10521
10645
  function unavailable2(message, retryAfterMs) {
@@ -10546,16 +10670,16 @@ function encodeCursor(state) {
10546
10670
  }
10547
10671
  function decodeCursor(cursor) {
10548
10672
  if (cursor.length > PERPS_HISTORY_MAX_CURSOR_LENGTH || !cursor.startsWith("ph1."))
10549
- throw invalid("cursor", "cursor is not one this client issued");
10673
+ throw invalid2("cursor", "cursor is not one this client issued");
10550
10674
  let state;
10551
10675
  try {
10552
10676
  state = JSON.parse(base64UrlDecode(cursor.slice(4)));
10553
10677
  } catch {
10554
- throw invalid("cursor", "cursor is not one this client issued");
10678
+ throw invalid2("cursor", "cursor is not one this client issued");
10555
10679
  }
10556
10680
  const ok = state !== null && typeof state === "object" && state.v === 1 && (state.k === "fills" || state.k === "funding") && (state.n === "mainnet" || state.n === "testnet") && typeof state.w === "string" && EVM_ADDRESS_RE2.test(state.w) && isTime(state.s) && isTime(state.e) && state.s <= state.e && Number.isSafeInteger(state.l) && state.l >= 1 && state.l <= PERPS_HISTORY_MAX_LIMIT[state.k] && isTime(state.t) && state.t >= state.s && state.t <= state.e && Array.isArray(state.b) && state.b.every((id) => typeof id === "string" && id.length <= 512) && Number.isSafeInteger(state.c) && state.c >= 0;
10557
10681
  if (!ok)
10558
- throw invalid("cursor", "cursor is not one this client issued");
10682
+ throw invalid2("cursor", "cursor is not one this client issued");
10559
10683
  return state;
10560
10684
  }
10561
10685
  function canonical2(value) {
@@ -10641,35 +10765,35 @@ function resolveWindow(req) {
10641
10765
  const kind = req.kind;
10642
10766
  const address = req.address.toLowerCase();
10643
10767
  if (!EVM_ADDRESS_RE2.test(address))
10644
- throw invalid("wallet", "The wallet's Hyperliquid account is not an EVM address");
10768
+ throw invalid2("wallet", "The wallet's Hyperliquid account is not an EVM address");
10645
10769
  for (const field of ["startTime", "endTime"]) {
10646
10770
  const value = options[field];
10647
10771
  if (value !== undefined && !isTime(value))
10648
- throw invalid(field, `${field} must be a whole, non-negative number of epoch milliseconds`);
10772
+ throw invalid2(field, `${field} must be a whole, non-negative number of epoch milliseconds`);
10649
10773
  }
10650
10774
  const max = PERPS_HISTORY_MAX_LIMIT[kind];
10651
10775
  if (options.limit !== undefined && (!Number.isSafeInteger(options.limit) || options.limit < 1 || options.limit > max))
10652
- throw invalid("limit", `limit must be a whole number from 1 to ${max} for ${kind}`);
10776
+ throw invalid2("limit", `limit must be a whole number from 1 to ${max} for ${kind}`);
10653
10777
  if (options.cursor !== undefined) {
10654
10778
  if (typeof options.cursor !== "string" || options.cursor.length === 0)
10655
- throw invalid("cursor", "cursor is not one this client issued");
10779
+ throw invalid2("cursor", "cursor is not one this client issued");
10656
10780
  const state = decodeCursor(options.cursor);
10657
10781
  if (state.k !== kind)
10658
- throw invalid("cursor", `This cursor pages ${state.k}, not ${kind}`);
10782
+ throw invalid2("cursor", `This cursor pages ${state.k}, not ${kind}`);
10659
10783
  if (state.n !== req.network)
10660
- throw invalid("cursor", `This cursor pages ${state.n}, not ${req.network}`);
10784
+ throw invalid2("cursor", `This cursor pages ${state.n}, not ${req.network}`);
10661
10785
  if (state.w !== address)
10662
- throw invalid("cursor", "This cursor pages another wallet");
10786
+ throw invalid2("cursor", "This cursor pages another wallet");
10663
10787
  if (options.startTime !== undefined && options.startTime !== state.s || options.endTime !== undefined && options.endTime !== state.e || options.limit !== undefined && options.limit !== state.l)
10664
- throw invalid("cursor", "The window or page size changed; start again without a cursor");
10788
+ throw invalid2("cursor", "The window or page size changed; start again without a cursor");
10665
10789
  return { state };
10666
10790
  }
10667
10791
  const endTime = options.endTime ?? req.now();
10668
10792
  const startTime = options.startTime ?? Math.max(0, endTime - PERPS_HISTORY_DEFAULT_WINDOW_MS);
10669
10793
  if (!isTime(endTime))
10670
- throw invalid("endTime", "endTime must be epoch milliseconds");
10794
+ throw invalid2("endTime", "endTime must be epoch milliseconds");
10671
10795
  if (startTime > endTime)
10672
- throw invalid("startTime", "startTime must not be after endTime");
10796
+ throw invalid2("startTime", "startTime must not be after endTime");
10673
10797
  return {
10674
10798
  state: {
10675
10799
  v: 1,
@@ -10840,7 +10964,7 @@ function pickSettings(raw) {
10840
10964
  return out;
10841
10965
  }
10842
10966
  async function hyperliquidInfo(fetcher, network, body, opts = {}) {
10843
- const sleep = opts.sleep ?? ((ms) => new Promise((resolve2) => setTimeout(resolve2, ms)));
10967
+ const sleep = opts.sleep ?? ((ms) => new Promise((resolve3) => setTimeout(resolve3, ms)));
10844
10968
  for (let attempt = 0;; attempt++) {
10845
10969
  const controller = new AbortController;
10846
10970
  const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? INFO_TIMEOUT_MS);
@@ -10876,7 +11000,7 @@ async function hyperliquidInfo(fetcher, network, body, opts = {}) {
10876
11000
  }
10877
11001
  }
10878
11002
  async function readHyperliquidMarketSnapshot(fetcher, network, opts = {}) {
10879
- const sleep = opts.sleep ?? ((ms) => new Promise((resolve2) => setTimeout(resolve2, ms)));
11003
+ const sleep = opts.sleep ?? ((ms) => new Promise((resolve3) => setTimeout(resolve3, ms)));
10880
11004
  let limited = false;
10881
11005
  const watched = async (url, init) => {
10882
11006
  const res = await fetcher(url, init);
@@ -13202,8 +13326,13 @@ async function swap(args, cfg, doFetch) {
13202
13326
  ...ok ? {} : { isError: true }
13203
13327
  };
13204
13328
  }
13205
- function registerTools(server, env = process.env) {
13206
- const cfg = resolveConfig(env);
13329
+ function serverConfig(server, env, resolved) {
13330
+ const cfg = localServerConfigs.get(server) ?? resolved ?? resolveConfig(env);
13331
+ localServerConfigs.set(server, cfg);
13332
+ return cfg;
13333
+ }
13334
+ function registerTools(server, env = process.env, resolved) {
13335
+ const cfg = serverConfig(server, env, resolved);
13207
13336
  const allowed = resolveToolAllowlist(env);
13208
13337
  registerToolSubset(server, {
13209
13338
  tools: [...allowed].filter((name) => !isProfileEditTool(name)),
@@ -13353,7 +13482,7 @@ function registerToolSubset(server, options) {
13353
13482
  });
13354
13483
  register("candle_get_market", {
13355
13484
  title: "Get market state",
13356
- description: "Read Candle and indexed external markets, including indexed-but-not-routable tokens. No key needed. " + EXTERNAL_MARKET_DISCLOSURE + " " + "Read candleLaunched, launchpad, venue and trade.routable; jupiterOk and discovery flags are distinct. " + "Routability is stored eligibility, not a quote or permission. General quotes use POST /api/v1/trade/agent/quote. " + "Curve quotes and lifecycle describe Candle launches. MARKET_NOT_FOUND is a legacy code: read " + "error.routing.reason, error.discovery and sibling error.retryable. A curve-only 404 does not mean untradeable.",
13485
+ description: "Read Candle and indexed external markets, including indexed-but-not-routable tokens. No key needed. quoteAsset names the pool pair; quoteAssets lists accepted settlement ids, with quoteAssets[0] the default (not a liquidity promise). " + EXTERNAL_MARKET_DISCLOSURE + " " + "Read candleLaunched, launchpad, venue and trade.routable; jupiterOk and discovery flags are distinct. " + "Routability is stored eligibility, not a quote or permission. General quotes use POST /api/v1/trade/agent/quote. " + "Curve quotes and lifecycle describe Candle launches. MARKET_NOT_FOUND is a legacy code: read " + "error.routing.reason, error.discovery and sibling error.retryable. A curve-only 404 does not mean untradeable.",
13357
13486
  inputSchema: getMarketShape
13358
13487
  }, async (args) => callAndRelay("candle_get_market", args, getConfig(), doFetch));
13359
13488
  register("candle_quote", {
@@ -13877,8 +14006,8 @@ function registerToolSubset(server, options) {
13877
14006
  annotations: labRead
13878
14007
  }, labTool("candle_lab_edges"));
13879
14008
  }
13880
- async function registerEligibleProfileTool(server, env = process.env, doFetch = fetch) {
13881
- const cfg = resolveConfig(env);
14009
+ async function registerEligibleProfileTool(server, env = process.env, doFetch = fetch, resolved) {
14010
+ const cfg = serverConfig(server, env, resolved);
13882
14011
  const allowlist = resolveToolAllowlist(env);
13883
14012
  const wanted = PROFILE_EDIT_TOOL_NAMES.filter((name) => allowlist.has(name));
13884
14013
  let eligible = false;
@@ -13906,7 +14035,7 @@ async function registerEligibleProfileTool(server, env = process.env, doFetch =
13906
14035
  }
13907
14036
  return eligible;
13908
14037
  }
13909
- 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, SWAP_RECEIPT_DESCRIPTION, SWAP_SETTLEMENT_REREAD, getMarketShape, quoteShape, 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, getSwapReceiptShape, vampCandidatesShape, getFeesShape, atomicPayerShape, atomicLaunchDryRunShape, claimFeesShape, resolveTokenShape, tradingWalletsShape, spendableBalancesShape, profileWalletsShape, profilePnlShape, profileTradesShape, noArgsShape, setProfileWalletsShape, swapShape, tradeShape, tradeExecuteShape, tokenImageUrl, tokenBannerUrl, localImageShape, localLaunchTokenShape, localLaunchAndSeedShape, IMAGE_PATH_DISCLOSURE, transferShape, sweepShape, PROFILE_PNL_HYPERLIQUID;
14038
+ 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, SWAP_RECEIPT_DESCRIPTION, SWAP_SETTLEMENT_REREAD, getMarketShape, quoteShape, 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, getSwapReceiptShape, vampCandidatesShape, getFeesShape, atomicPayerShape, atomicLaunchDryRunShape, claimFeesShape, resolveTokenShape, tradingWalletsShape, spendableBalancesShape, profileWalletsShape, profilePnlShape, profileTradesShape, noArgsShape, setProfileWalletsShape, swapShape, tradeShape, tradeExecuteShape, tokenImageUrl, tokenBannerUrl, localImageShape, localLaunchTokenShape, localLaunchAndSeedShape, IMAGE_PATH_DISCLOSURE, transferShape, sweepShape, PROFILE_PNL_HYPERLIQUID, localServerConfigs;
13910
14039
  var init_tools = __esm(() => {
13911
14040
  init_zod();
13912
14041
  init_chain_registry();
@@ -14254,6 +14383,7 @@ var init_tools = __esm(() => {
14254
14383
  mints: exports_external.array(exports_external.string()).optional().describe("Extra token mints/contracts to sweep besides the chain's base assets (own-wallet destinations only).")
14255
14384
  };
14256
14385
  PROFILE_PNL_HYPERLIQUID = "While Hyperliquid is enabled, `hyperliquid` reports the profile's main perps over every EVM TEE wallet bound " + "to it, per binding period (`coverage: 'profile'`): realized gross, funding (positive received, negative paid), " + "fees (builder fees included), realizedNetUsd = gross + funding - fees, `unrealizedUsd` at each wallet's " + "cutoff, and `netUsd` = realized net + unrealized, with `markedAt` (the oldest wallet cutoff) and open " + "`positions` (each `fundingNetUsd` positive when earned). Facts and unrealized stop at the same cutoff; each " + "`byWallet` period is `live` (reconciled on this read, not yet settled) or `settled` (a stored snapshot). " + "When `read` is true, `pnl.totalUsd` includes `netUsd` and `perpsInTotal` is true. When `read` is false, " + "`pnl.totalUsd` is spot only, `perpsInTotal` is false and `reason` says why; that is never a zero. Check " + "`historyPartial` and `partialReasons`.";
14386
+ localServerConfigs = new WeakMap;
14257
14387
  });
14258
14388
 
14259
14389
  // ../../node_modules/@noble/hashes/esm/_md.js
@@ -20076,7 +20206,7 @@ function apiKeyPrefix(key) {
20076
20206
  return;
20077
20207
  }
20078
20208
  function credentialEnvOverrides(env) {
20079
- return ["CANDLE_API_KEY", "CANDLE_DEVICE_TOKEN"].filter((name) => env[name]?.trim());
20209
+ return ["CANDLE_API_KEY_FILE", "CANDLE_API_KEY", "CANDLE_DEVICE_TOKEN"].filter((name) => env[name]?.trim());
20080
20210
  }
20081
20211
  function identityLine(profile, account, apiUrl, overrides, username, options = {}) {
20082
20212
  const address = account === undefined ? undefined : shortId(account, options.full);
@@ -20097,7 +20227,7 @@ async function printIdentity(ctx) {
20097
20227
  if (overrides.length > 0) {
20098
20228
  account = undefined;
20099
20229
  username = undefined;
20100
- const key = ctx.deps.env.CANDLE_API_KEY?.trim();
20230
+ const key = ctx.deps.env.CANDLE_API_KEY_FILE?.trim() || ctx.deps.env.CANDLE_API_KEY?.trim() ? (await resolveApiCredential(ctx.deps, ctx.profile)).key : undefined;
20101
20231
  if (key) {
20102
20232
  const lookup = await fetchAccount(ctx.deps, ctx.apiUrl, key);
20103
20233
  if (lookup.account)
@@ -20149,6 +20279,7 @@ function profileTable(config, now) {
20149
20279
  var ProfileFleetOnlyError, PRE_PROFILE_FIELDS, API_KEY_PREFIXES, API_KEY_RANDOM_LENGTH = 43, INVALID_RPC_HOST = "invalid (fix with profile set)";
20150
20280
  var init_profiles = __esm(() => {
20151
20281
  init_account();
20282
+ init_deps();
20152
20283
  init_presentation();
20153
20284
  init_render();
20154
20285
  ProfileFleetOnlyError = class ProfileFleetOnlyError extends Error {
@@ -22706,7 +22837,7 @@ __export(exports_store, {
22706
22837
  candleConfigDir: () => candleConfigDir,
22707
22838
  CONFIG_DIR_ENV: () => CONFIG_DIR_ENV
22708
22839
  });
22709
- import { access, chmod as chmod3, constants as constants2, mkdir as mkdir3, readFile as readFile5, stat as stat2 } from "node:fs/promises";
22840
+ import { access, chmod as chmod3, constants as constants3, mkdir as mkdir3, readFile as readFile5, stat as stat2 } from "node:fs/promises";
22710
22841
  import { dirname as dirname4, join as join3 } from "node:path";
22711
22842
  function candleConfigDir(env, home) {
22712
22843
  const configured = env.CANDLE_CONFIG_DIR?.trim();
@@ -22746,7 +22877,7 @@ function errnoOf(error) {
22746
22877
  }
22747
22878
  async function writableDir(path) {
22748
22879
  try {
22749
- await access(path, constants2.W_OK | constants2.X_OK);
22880
+ await access(path, constants3.W_OK | constants3.X_OK);
22750
22881
  return;
22751
22882
  } catch (error) {
22752
22883
  return errnoOf(error);
@@ -23215,7 +23346,7 @@ async function withFileLock(target, fn) {
23215
23346
  if (Date.now() > deadline) {
23216
23347
  throw new Error(`Timed out waiting for ${lockPath}. Another candle process is writing; if none is running, delete that file.`);
23217
23348
  }
23218
- await new Promise((resolve2) => setTimeout(resolve2, RETRY_MS));
23349
+ await new Promise((resolve3) => setTimeout(resolve3, RETRY_MS));
23219
23350
  }
23220
23351
  }
23221
23352
  try {
@@ -23388,18 +23519,18 @@ async function promptVisibleLine(promptText) {
23388
23519
  }
23389
23520
  async function readVisibleLine(promptText, io) {
23390
23521
  const readline = await import("node:readline");
23391
- return new Promise((resolve2, reject) => {
23522
+ return new Promise((resolve3, reject) => {
23392
23523
  const rl = readline.createInterface({ input: io.input, output: io.output, terminal: true });
23393
23524
  rl.question(promptText, (answer) => {
23394
23525
  rl.close();
23395
- resolve2(answer);
23526
+ resolve3(answer);
23396
23527
  });
23397
23528
  onInterrupted(rl, io, reject);
23398
23529
  });
23399
23530
  }
23400
23531
  async function readHiddenLine(promptText, io) {
23401
23532
  const readline = await import("node:readline");
23402
- return new Promise((resolve2, reject) => {
23533
+ return new Promise((resolve3, reject) => {
23403
23534
  const rl = readline.createInterface({ input: io.input, output: io.output, terminal: true });
23404
23535
  const rlInternals = rl;
23405
23536
  rlInternals._writeToOutput = (text) => {
@@ -23410,7 +23541,7 @@ async function readHiddenLine(promptText, io) {
23410
23541
  rl.close();
23411
23542
  io.output.write(`
23412
23543
  `);
23413
- resolve2(answer);
23544
+ resolve3(answer);
23414
23545
  });
23415
23546
  onInterrupted(rl, io, reject);
23416
23547
  });
@@ -23440,6 +23571,8 @@ var init_secret_store = __esm(() => {
23440
23571
 
23441
23572
  // src/deps.ts
23442
23573
  async function resolveDeviceToken(deps, profile) {
23574
+ if (hasApiKeyFile(deps.env))
23575
+ await resolveApiCredential(deps, profile);
23443
23576
  if (profile !== undefined)
23444
23577
  assertOrdinaryProfile(await deps.readConfig(), profile);
23445
23578
  const fromEnv = deps.env.CANDLE_DEVICE_TOKEN?.trim();
@@ -23452,20 +23585,53 @@ async function resolveDeviceToken(deps, profile) {
23452
23585
  const stored = await deps.store.get(ref);
23453
23586
  return stored ?? undefined;
23454
23587
  }
23455
- async function resolveApiKey(deps, profile) {
23588
+ function beginCredentialInvocation(deps) {
23589
+ const scoped = { ...deps, env: { ...deps.env } };
23590
+ return { ...scoped, resolvedApiKeyFile: apiKeyFileSnapshot(scoped.env, scoped.loadApiKeyFile) };
23591
+ }
23592
+ async function resolveApiCredential(deps, profile) {
23593
+ if (hasApiKeyFile(deps.env)) {
23594
+ let load = deps.resolvedApiKeyFile ?? fileSnapshots.get(deps);
23595
+ if (!load) {
23596
+ load = apiKeyFileSnapshot(deps.env, deps.loadApiKeyFile);
23597
+ fileSnapshots.set(deps, load);
23598
+ }
23599
+ const key2 = await load();
23600
+ if (profile !== undefined)
23601
+ assertOrdinaryProfile(await deps.readConfig(), profile);
23602
+ return { key: key2, source: "file" };
23603
+ }
23456
23604
  if (profile !== undefined)
23457
23605
  assertOrdinaryProfile(await deps.readConfig(), profile);
23458
23606
  const fromEnv = deps.env.CANDLE_API_KEY?.trim();
23459
23607
  if (fromEnv)
23460
- return fromEnv;
23608
+ return { key: fromEnv, source: "env" };
23461
23609
  const ref = profile ? profileSecretRef(profile, "apiKey") : SECRET_REFS.apiKey;
23462
- const stored = await deps.store.get(ref);
23463
- return stored ?? undefined;
23610
+ const key = await deps.store.get(ref) ?? undefined;
23611
+ return { key, source: key ? profile ? "profile" : "legacy" : "none" };
23464
23612
  }
23613
+ async function resolveExternalApiCredential(deps) {
23614
+ if (hasApiKeyFile(deps.env)) {
23615
+ let load = deps.resolvedApiKeyFile ?? fileSnapshots.get(deps);
23616
+ if (!load) {
23617
+ load = apiKeyFileSnapshot(deps.env, deps.loadApiKeyFile);
23618
+ fileSnapshots.set(deps, load);
23619
+ }
23620
+ return { key: await load(), source: "file" };
23621
+ }
23622
+ const fromEnv = deps.env.CANDLE_API_KEY?.trim();
23623
+ return fromEnv ? { key: fromEnv, source: "env" } : undefined;
23624
+ }
23625
+ async function resolveApiKey(deps, profile) {
23626
+ return (await resolveApiCredential(deps, profile)).key;
23627
+ }
23628
+ var fileSnapshots;
23465
23629
  var init_deps = __esm(() => {
23630
+ init_api_key_file();
23466
23631
  init_ownership();
23467
23632
  init_profiles();
23468
23633
  init_secret_store();
23634
+ fileSnapshots = new WeakMap;
23469
23635
  });
23470
23636
 
23471
23637
  // src/key-signers.ts
@@ -26181,6 +26347,10 @@ var init_trading_checks = __esm(() => {
26181
26347
  // src/commands/swap.ts
26182
26348
  import { randomUUID as randomUUID5 } from "node:crypto";
26183
26349
  function tradingFailure(ctx, error, id) {
26350
+ if (error instanceof ApiKeyFileError) {
26351
+ writeLocalFailure(ctx.deps, { code: error.code, message: error.message }, ctx.json);
26352
+ return 1;
26353
+ }
26184
26354
  if (error instanceof TradingUsage) {
26185
26355
  writeUsageFailure(ctx.deps, error.message, ctx.json);
26186
26356
  return 2;
@@ -27212,6 +27382,7 @@ async function swapRecover(args, ctx) {
27212
27382
  var isText = (value) => typeof value === "string" && value.length > 0, textsOf = (value) => Array.isArray(value) ? value.filter(isText) : [], AGENT_MAX_SLIPPAGE_BPS = 5000, SLIPPAGE_RANGE_MESSAGE, settlementSchema, SWAP_RECEIPT_USAGE = "Usage: candle swap receipt <hash> | candle swap receipt --client-swap-id <id>", BRIDGE_STATUS_WORDS, bridgeFactsSchema, HOOD_TRADE_LEGS, bridgeBuildSchema;
27213
27383
  var init_swap = __esm(() => {
27214
27384
  init_zod();
27385
+ init_api_key_file();
27215
27386
  init_swap_receipt();
27216
27387
  init_tools();
27217
27388
  init_args();
@@ -27540,7 +27711,7 @@ async function readSignerRoles(rpc, addresses, opts) {
27540
27711
  } catch {}
27541
27712
  return { ...base2, target: programData, unresolvedProgram: true };
27542
27713
  };
27543
- await new Promise((resolve2) => {
27714
+ await new Promise((resolve3) => {
27544
27715
  const pump = () => {
27545
27716
  let skippedAny = false;
27546
27717
  while (active < cap && next < tasks.length) {
@@ -27575,7 +27746,7 @@ async function readSignerRoles(rpc, addresses, opts) {
27575
27746
  if (skippedAny)
27576
27747
  progress();
27577
27748
  if (active === 0 && next >= tasks.length)
27578
- resolve2();
27749
+ resolve3();
27579
27750
  };
27580
27751
  pump();
27581
27752
  });
@@ -28050,7 +28221,8 @@ function accountUnresolved(reason) {
28050
28221
  }
28051
28222
  async function readControlledBy(ctx) {
28052
28223
  const { deps } = ctx;
28053
- const apiKey = await resolveApiKey(deps, ctx.profile);
28224
+ const credential = await resolveApiCredential(deps, ctx.profile);
28225
+ const apiKey = credential.key;
28054
28226
  if (!apiKey)
28055
28227
  throw accountUnresolved("no API key is available");
28056
28228
  const keyPrefix = apiKeyPrefix(apiKey);
@@ -28071,7 +28243,7 @@ async function readControlledBy(ctx) {
28071
28243
  throw accountUnresolved("the response carried no account");
28072
28244
  }
28073
28245
  const username = typeof body.username === "string" && body.username.length > 0 ? body.username : null;
28074
- const keySource = deps.env.CANDLE_API_KEY?.trim() ? "env" : ctx.profile !== undefined ? "profile" : "default";
28246
+ const keySource = credential.source === "legacy" || credential.source === "none" ? "default" : credential.source;
28075
28247
  const apiUrlFrom = ctx.apiUrlFlag !== undefined ? "--api-url" : deps.env.CANDLE_API_URL?.trim() ? "CANDLE_API_URL" : undefined;
28076
28248
  return {
28077
28249
  account: body.account,
@@ -28111,7 +28283,7 @@ async function readKeyLabel(ctx, keyPrefix) {
28111
28283
  }
28112
28284
  function renderControlledBy(controlledBy, n) {
28113
28285
  const subject = n === 1 ? "This key" : `These ${n} keys`;
28114
- const source = controlledBy.keySource === "env" ? "CANDLE_API_KEY" : controlledBy.keySource === "profile" ? `profile ${controlledBy.profileName ?? ""}`.trimEnd() : "default credentials";
28286
+ const source = controlledBy.keySource === "file" ? "CANDLE_API_KEY_FILE" : controlledBy.keySource === "env" ? "CANDLE_API_KEY" : controlledBy.keySource === "profile" ? `profile ${controlledBy.profileName ?? ""}`.trimEnd() : "default credentials";
28115
28287
  const cleaned = controlledBy.keyLabel !== null ? labelCell(controlledBy.keyLabel) : "";
28116
28288
  const label = cleaned.length > 0 ? `(${cleaned}) ` : "";
28117
28289
  const environment = controlledBy.environment ?? "not a Candle host";
@@ -29250,7 +29422,7 @@ var init_ecies = __esm(() => {
29250
29422
  });
29251
29423
 
29252
29424
  // src/vault/enclave.ts
29253
- import { access as access2, constants as constants3 } from "node:fs/promises";
29425
+ import { access as access2, constants as constants4 } from "node:fs/promises";
29254
29426
  import { dirname as dirname7, join as join7 } from "node:path";
29255
29427
  function parseReleasePolicy(value) {
29256
29428
  const bad = (detail) => {
@@ -29278,7 +29450,7 @@ function parseReleasePolicy(value) {
29278
29450
  }
29279
29451
  async function isExecutable(path) {
29280
29452
  try {
29281
- await access2(path, constants3.X_OK);
29453
+ await access2(path, constants4.X_OK);
29282
29454
  return true;
29283
29455
  } catch {
29284
29456
  return false;
@@ -29835,11 +30007,11 @@ var init_platform = __esm(() => {
29835
30007
  });
29836
30008
 
29837
30009
  // src/vault/fido2.ts
29838
- import { access as access3, constants as constants4 } from "node:fs/promises";
30010
+ import { access as access3, constants as constants5 } from "node:fs/promises";
29839
30011
  import { dirname as dirname8, join as join8 } from "node:path";
29840
30012
  async function isExecutable2(path) {
29841
30013
  try {
29842
- await access3(path, constants4.X_OK);
30014
+ await access3(path, constants5.X_OK);
29843
30015
  return true;
29844
30016
  } catch {
29845
30017
  return false;
@@ -38977,6 +39149,10 @@ async function typedConfirm(io, label, address, action) {
38977
39149
  return typed.trim().toLowerCase() === "confirm";
38978
39150
  }
38979
39151
  function writeVaultFailure(ctx, error) {
39152
+ if (error instanceof ApiKeyFileError) {
39153
+ writeLocalFailure(ctx.deps, { code: error.code, message: error.message }, ctx.json);
39154
+ return 1;
39155
+ }
38980
39156
  if (isUsageError(error))
38981
39157
  return usage2(ctx, error.message);
38982
39158
  if (isVaultError(error)) {
@@ -39071,6 +39247,7 @@ function takeRepeatedFlag(args, flag) {
39071
39247
  }
39072
39248
  var noTerminalRefusals, PASSPHRASE_ONLY_PREFIX = "Passphrase only: ";
39073
39249
  var init_vault_support = __esm(() => {
39250
+ init_api_key_file();
39074
39251
  init_args();
39075
39252
  init_presentation();
39076
39253
  init_render();
@@ -41564,15 +41741,15 @@ class Chacha20Poly1305Context {
41564
41741
  return await this._open(iv, data, aad);
41565
41742
  }
41566
41743
  _seal(iv, data, aad) {
41567
- return new Promise((resolve2) => {
41744
+ return new Promise((resolve3) => {
41568
41745
  const ret = chacha20poly1305(this._key, new Uint8Array(iv), new Uint8Array(aad)).encrypt(new Uint8Array(data));
41569
- resolve2(ret.buffer);
41746
+ resolve3(ret.buffer);
41570
41747
  });
41571
41748
  }
41572
41749
  _open(iv, data, aad) {
41573
- return new Promise((resolve2) => {
41750
+ return new Promise((resolve3) => {
41574
41751
  const ret = chacha20poly1305(this._key, new Uint8Array(iv), new Uint8Array(aad)).decrypt(new Uint8Array(data));
41575
- resolve2(ret.buffer);
41752
+ resolve3(ret.buffer);
41576
41753
  });
41577
41754
  }
41578
41755
  }
@@ -41765,8 +41942,8 @@ class Mutex {
41765
41942
  }
41766
41943
  async lock() {
41767
41944
  let releaseLock;
41768
- const nextLock = new Promise((resolve2) => {
41769
- releaseLock = resolve2;
41945
+ const nextLock = new Promise((resolve3) => {
41946
+ releaseLock = resolve3;
41770
41947
  });
41771
41948
  const previousLock = __classPrivateFieldGet(this, _Mutex_locked, "f");
41772
41949
  __classPrivateFieldSet(this, _Mutex_locked, nextLock, "f");
@@ -48595,7 +48772,7 @@ class Protocol {
48595
48772
  }
48596
48773
  request(request2, resultSchema, options) {
48597
48774
  const { relatedRequestId, resumptionToken, onresumptiontoken } = options !== null && options !== undefined ? options : {};
48598
- return new Promise((resolve2, reject) => {
48775
+ return new Promise((resolve3, reject) => {
48599
48776
  var _a, _b, _c, _d, _e, _f;
48600
48777
  if (!this._transport) {
48601
48778
  reject(new Error("Not connected"));
@@ -48646,7 +48823,7 @@ class Protocol {
48646
48823
  }
48647
48824
  try {
48648
48825
  const result2 = resultSchema.parse(response.result);
48649
- resolve2(result2);
48826
+ resolve3(result2);
48650
48827
  } catch (error) {
48651
48828
  reject(error);
48652
48829
  }
@@ -49121,11 +49298,11 @@ var require_codegen = __commonJS((exports) => {
49121
49298
  const rhs = this.rhs === undefined ? "" : ` = ${this.rhs}`;
49122
49299
  return `${varKind} ${this.name}${rhs};` + _n;
49123
49300
  }
49124
- optimizeNames(names, constants5) {
49301
+ optimizeNames(names, constants6) {
49125
49302
  if (!names[this.name.str])
49126
49303
  return;
49127
49304
  if (this.rhs)
49128
- this.rhs = optimizeExpr(this.rhs, names, constants5);
49305
+ this.rhs = optimizeExpr(this.rhs, names, constants6);
49129
49306
  return this;
49130
49307
  }
49131
49308
  get names() {
@@ -49143,10 +49320,10 @@ var require_codegen = __commonJS((exports) => {
49143
49320
  render({ _n }) {
49144
49321
  return `${this.lhs} = ${this.rhs};` + _n;
49145
49322
  }
49146
- optimizeNames(names, constants5) {
49323
+ optimizeNames(names, constants6) {
49147
49324
  if (this.lhs instanceof code_1.Name && !names[this.lhs.str] && !this.sideEffects)
49148
49325
  return;
49149
- this.rhs = optimizeExpr(this.rhs, names, constants5);
49326
+ this.rhs = optimizeExpr(this.rhs, names, constants6);
49150
49327
  return this;
49151
49328
  }
49152
49329
  get names() {
@@ -49212,8 +49389,8 @@ var require_codegen = __commonJS((exports) => {
49212
49389
  optimizeNodes() {
49213
49390
  return `${this.code}` ? this : undefined;
49214
49391
  }
49215
- optimizeNames(names, constants5) {
49216
- this.code = optimizeExpr(this.code, names, constants5);
49392
+ optimizeNames(names, constants6) {
49393
+ this.code = optimizeExpr(this.code, names, constants6);
49217
49394
  return this;
49218
49395
  }
49219
49396
  get names() {
@@ -49243,12 +49420,12 @@ var require_codegen = __commonJS((exports) => {
49243
49420
  }
49244
49421
  return nodes.length > 0 ? this : undefined;
49245
49422
  }
49246
- optimizeNames(names, constants5) {
49423
+ optimizeNames(names, constants6) {
49247
49424
  const { nodes } = this;
49248
49425
  let i = nodes.length;
49249
49426
  while (i--) {
49250
49427
  const n = nodes[i];
49251
- if (n.optimizeNames(names, constants5))
49428
+ if (n.optimizeNames(names, constants6))
49252
49429
  continue;
49253
49430
  subtractNames(names, n.names);
49254
49431
  nodes.splice(i, 1);
@@ -49305,12 +49482,12 @@ var require_codegen = __commonJS((exports) => {
49305
49482
  return;
49306
49483
  return this;
49307
49484
  }
49308
- optimizeNames(names, constants5) {
49485
+ optimizeNames(names, constants6) {
49309
49486
  var _a;
49310
- this.else = (_a = this.else) === null || _a === undefined ? undefined : _a.optimizeNames(names, constants5);
49311
- if (!(super.optimizeNames(names, constants5) || this.else))
49487
+ this.else = (_a = this.else) === null || _a === undefined ? undefined : _a.optimizeNames(names, constants6);
49488
+ if (!(super.optimizeNames(names, constants6) || this.else))
49312
49489
  return;
49313
- this.condition = optimizeExpr(this.condition, names, constants5);
49490
+ this.condition = optimizeExpr(this.condition, names, constants6);
49314
49491
  return this;
49315
49492
  }
49316
49493
  get names() {
@@ -49335,10 +49512,10 @@ var require_codegen = __commonJS((exports) => {
49335
49512
  render(opts) {
49336
49513
  return `for(${this.iteration})` + super.render(opts);
49337
49514
  }
49338
- optimizeNames(names, constants5) {
49339
- if (!super.optimizeNames(names, constants5))
49515
+ optimizeNames(names, constants6) {
49516
+ if (!super.optimizeNames(names, constants6))
49340
49517
  return;
49341
- this.iteration = optimizeExpr(this.iteration, names, constants5);
49518
+ this.iteration = optimizeExpr(this.iteration, names, constants6);
49342
49519
  return this;
49343
49520
  }
49344
49521
  get names() {
@@ -49376,10 +49553,10 @@ var require_codegen = __commonJS((exports) => {
49376
49553
  render(opts) {
49377
49554
  return `for(${this.varKind} ${this.name} ${this.loop} ${this.iterable})` + super.render(opts);
49378
49555
  }
49379
- optimizeNames(names, constants5) {
49380
- if (!super.optimizeNames(names, constants5))
49556
+ optimizeNames(names, constants6) {
49557
+ if (!super.optimizeNames(names, constants6))
49381
49558
  return;
49382
- this.iterable = optimizeExpr(this.iterable, names, constants5);
49559
+ this.iterable = optimizeExpr(this.iterable, names, constants6);
49383
49560
  return this;
49384
49561
  }
49385
49562
  get names() {
@@ -49424,11 +49601,11 @@ var require_codegen = __commonJS((exports) => {
49424
49601
  (_b = this.finally) === null || _b === undefined || _b.optimizeNodes();
49425
49602
  return this;
49426
49603
  }
49427
- optimizeNames(names, constants5) {
49604
+ optimizeNames(names, constants6) {
49428
49605
  var _a, _b;
49429
- super.optimizeNames(names, constants5);
49430
- (_a = this.catch) === null || _a === undefined || _a.optimizeNames(names, constants5);
49431
- (_b = this.finally) === null || _b === undefined || _b.optimizeNames(names, constants5);
49606
+ super.optimizeNames(names, constants6);
49607
+ (_a = this.catch) === null || _a === undefined || _a.optimizeNames(names, constants6);
49608
+ (_b = this.finally) === null || _b === undefined || _b.optimizeNames(names, constants6);
49432
49609
  return this;
49433
49610
  }
49434
49611
  get names() {
@@ -49702,7 +49879,7 @@ var require_codegen = __commonJS((exports) => {
49702
49879
  function addExprNames(names, from) {
49703
49880
  return from instanceof code_1._CodeOrName ? addNames(names, from.names) : names;
49704
49881
  }
49705
- function optimizeExpr(expr, names, constants5) {
49882
+ function optimizeExpr(expr, names, constants6) {
49706
49883
  if (expr instanceof code_1.Name)
49707
49884
  return replaceName(expr);
49708
49885
  if (!canOptimize(expr))
@@ -49717,14 +49894,14 @@ var require_codegen = __commonJS((exports) => {
49717
49894
  return items;
49718
49895
  }, []));
49719
49896
  function replaceName(n) {
49720
- const c = constants5[n.str];
49897
+ const c = constants6[n.str];
49721
49898
  if (c === undefined || names[n.str] !== 1)
49722
49899
  return n;
49723
49900
  delete names[n.str];
49724
49901
  return c;
49725
49902
  }
49726
49903
  function canOptimize(e) {
49727
- return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants5[c.str] !== undefined);
49904
+ return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants6[c.str] !== undefined);
49728
49905
  }
49729
49906
  }
49730
49907
  function subtractNames(names, from) {
@@ -51629,7 +51806,7 @@ var require_compile = __commonJS((exports) => {
51629
51806
  const schOrFunc = root.refs[ref];
51630
51807
  if (schOrFunc)
51631
51808
  return schOrFunc;
51632
- let _sch = resolve2.call(this, root, ref);
51809
+ let _sch = resolve3.call(this, root, ref);
51633
51810
  if (_sch === undefined) {
51634
51811
  const schema = (_a = root.localRefs) === null || _a === undefined ? undefined : _a[ref];
51635
51812
  const { schemaId } = this.opts;
@@ -51656,7 +51833,7 @@ var require_compile = __commonJS((exports) => {
51656
51833
  function sameSchemaEnv(s1, s2) {
51657
51834
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
51658
51835
  }
51659
- function resolve2(root, ref) {
51836
+ function resolve3(root, ref) {
51660
51837
  let sch;
51661
51838
  while (typeof (sch = this.refs[ref]) == "string")
51662
51839
  ref = sch;
@@ -52242,7 +52419,7 @@ var require_fast_uri = __commonJS((exports, module) => {
52242
52419
  }
52243
52420
  return uri;
52244
52421
  }
52245
- function resolve2(baseURI, relativeURI, options) {
52422
+ function resolve3(baseURI, relativeURI, options) {
52246
52423
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
52247
52424
  const { parsed: baseParsed, malformedAuthorityOrPort: baseMalformed } = parseWithStatus(baseURI, schemelessOptions);
52248
52425
  const { parsed: relativeParsed, malformedAuthorityOrPort: relativeMalformed } = parseWithStatus(relativeURI, schemelessOptions);
@@ -52527,7 +52704,7 @@ var require_fast_uri = __commonJS((exports, module) => {
52527
52704
  var fastUri = {
52528
52705
  SCHEMES,
52529
52706
  normalize: normalize3,
52530
- resolve: resolve2,
52707
+ resolve: resolve3,
52531
52708
  resolveComponent,
52532
52709
  equal,
52533
52710
  serialize,
@@ -53335,8 +53512,8 @@ var require_multipleOf = __commonJS((exports) => {
53335
53512
  const { gen: gen2, data, schemaCode, it } = cxt;
53336
53513
  const prec = it.opts.multipleOfPrecision;
53337
53514
  const res = gen2.let("res");
53338
- const invalid2 = prec ? (0, codegen_1._)`Math.abs(Math.round(${res}) - ${res}) > 1e-${prec}` : (0, codegen_1._)`${res} !== parseInt(${res})`;
53339
- cxt.fail$data((0, codegen_1._)`(${schemaCode} === 0 || (${res} = ${data}/${schemaCode}, ${invalid2}))`);
53515
+ const invalid3 = prec ? (0, codegen_1._)`Math.abs(Math.round(${res}) - ${res}) > 1e-${prec}` : (0, codegen_1._)`${res} !== parseInt(${res})`;
53516
+ cxt.fail$data((0, codegen_1._)`(${schemaCode} === 0 || (${res} = ${data}/${schemaCode}, ${invalid3}))`);
53340
53517
  }
53341
53518
  };
53342
53519
  exports.default = def;
@@ -58007,12 +58184,12 @@ class StdioServerTransport {
58007
58184
  (_a = this.onclose) === null || _a === undefined || _a.call(this);
58008
58185
  }
58009
58186
  send(message) {
58010
- return new Promise((resolve2) => {
58187
+ return new Promise((resolve3) => {
58011
58188
  const json = serializeMessage(message);
58012
58189
  if (this._stdout.write(json)) {
58013
- resolve2();
58190
+ resolve3();
58014
58191
  } else {
58015
- this._stdout.once("drain", resolve2);
58192
+ this._stdout.once("drain", resolve3);
58016
58193
  }
58017
58194
  });
58018
58195
  }
@@ -73259,7 +73436,7 @@ class Protocol2 {
73259
73436
  return;
73260
73437
  }
73261
73438
  const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1000;
73262
- await new Promise((resolve6) => setTimeout(resolve6, pollInterval));
73439
+ await new Promise((resolve7) => setTimeout(resolve7, pollInterval));
73263
73440
  options?.signal?.throwIfAborted();
73264
73441
  }
73265
73442
  } catch (error2) {
@@ -73271,7 +73448,7 @@ class Protocol2 {
73271
73448
  }
73272
73449
  request(request2, resultSchema, options) {
73273
73450
  const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
73274
- return new Promise((resolve6, reject) => {
73451
+ return new Promise((resolve7, reject) => {
73275
73452
  const earlyReject = (error2) => {
73276
73453
  reject(error2);
73277
73454
  };
@@ -73349,7 +73526,7 @@ class Protocol2 {
73349
73526
  if (!parseResult.success) {
73350
73527
  reject(parseResult.error);
73351
73528
  } else {
73352
- resolve6(parseResult.data);
73529
+ resolve7(parseResult.data);
73353
73530
  }
73354
73531
  } catch (error2) {
73355
73532
  reject(error2);
@@ -73540,12 +73717,12 @@ class Protocol2 {
73540
73717
  interval = task.pollInterval;
73541
73718
  }
73542
73719
  } catch {}
73543
- return new Promise((resolve6, reject) => {
73720
+ return new Promise((resolve7, reject) => {
73544
73721
  if (signal.aborted) {
73545
73722
  reject(new McpError2(ErrorCode2.InvalidRequest, "Request cancelled"));
73546
73723
  return;
73547
73724
  }
73548
- const timeoutId = setTimeout(resolve6, interval);
73725
+ const timeoutId = setTimeout(resolve7, interval);
73549
73726
  signal.addEventListener("abort", () => {
73550
73727
  clearTimeout(timeoutId);
73551
73728
  reject(new McpError2(ErrorCode2.InvalidRequest, "Request cancelled"));
@@ -74368,7 +74545,7 @@ class McpServer2 {
74368
74545
  let task = createTaskResult.task;
74369
74546
  const pollInterval = task.pollInterval ?? 5000;
74370
74547
  while (task.status !== "completed" && task.status !== "failed" && task.status !== "cancelled") {
74371
- await new Promise((resolve6) => setTimeout(resolve6, pollInterval));
74548
+ await new Promise((resolve7) => setTimeout(resolve7, pollInterval));
74372
74549
  const updatedTask = await extra.taskStore.getTask(taskId);
74373
74550
  if (!updatedTask) {
74374
74551
  throw new McpError2(ErrorCode2.InternalError, `Task ${taskId} not found during polling`);
@@ -74992,12 +75169,12 @@ class StdioServerTransport2 {
74992
75169
  this.onclose?.();
74993
75170
  }
74994
75171
  send(message) {
74995
- return new Promise((resolve6) => {
75172
+ return new Promise((resolve7) => {
74996
75173
  const json = serializeMessage2(message);
74997
75174
  if (this._stdout.write(json)) {
74998
- resolve6();
75175
+ resolve7();
74999
75176
  } else {
75000
- this._stdout.once("drain", resolve6);
75177
+ this._stdout.once("drain", resolve7);
75001
75178
  }
75002
75179
  });
75003
75180
  }
@@ -75012,27 +75189,29 @@ __export(exports_server, {
75012
75189
  runStdioServer: () => runStdioServer,
75013
75190
  createCandleMcpServer: () => createCandleMcpServer
75014
75191
  });
75015
- function createCandleMcpServer(env = process.env) {
75192
+ function createCandleMcpServer(env = process.env, resolved) {
75016
75193
  const server = new McpServer2({ name: "candle-mcp", version: SERVER_VERSION }, { instructions: INSTRUCTIONS });
75017
- registerTools(server, env);
75194
+ registerTools(server, env, resolved ?? resolveConfig(env));
75018
75195
  return server;
75019
75196
  }
75020
75197
  async function runStdioServer(env = process.env, transport = new StdioServerTransport2) {
75021
- const server = createCandleMcpServer(env);
75022
- await registerEligibleProfileTool(server, env);
75198
+ env = { ...env };
75199
+ const cfg = resolveConfig(env);
75200
+ const server = createCandleMcpServer(env, cfg);
75201
+ await registerEligibleProfileTool(server, env, undefined, cfg);
75023
75202
  await server.connect(transport);
75024
75203
  const dispatch = transport.onmessage;
75025
75204
  transport.onmessage = (message, extra) => {
75026
75205
  if ("method" in message && message.method === "tools/list") {
75027
- registerEligibleProfileTool(server, env).then(() => dispatch?.(message, extra));
75206
+ registerEligibleProfileTool(server, env, undefined, cfg).then(() => dispatch?.(message, extra));
75028
75207
  } else
75029
75208
  dispatch?.(message, extra);
75030
75209
  };
75031
- await new Promise((resolve6) => {
75210
+ await new Promise((resolve7) => {
75032
75211
  const sdkOnClose = transport.onclose;
75033
75212
  transport.onclose = () => {
75034
75213
  sdkOnClose?.();
75035
- resolve6();
75214
+ resolve7();
75036
75215
  };
75037
75216
  });
75038
75217
  }
@@ -75040,6 +75219,7 @@ var INSTRUCTIONS;
75040
75219
  var init_server3 = __esm(() => {
75041
75220
  init_mcp2();
75042
75221
  init_stdio4();
75222
+ init_client2();
75043
75223
  init_market_disclosure();
75044
75224
  init_tools();
75045
75225
  INSTRUCTIONS = `Candle is a trading and token-launch rail for agents. You hold a scoped API key, never a private key; signing and funding stay with the key owner's wallet.
@@ -75114,6 +75294,7 @@ Full reference, error catalogue and end-to-end recipes: https://docs.candle.tv
75114
75294
  });
75115
75295
 
75116
75296
  // src/index.ts
75297
+ init_api_key_file();
75117
75298
  init_client();
75118
75299
  import { spawn as spawn3 } from "node:child_process";
75119
75300
  import { realpathSync } from "node:fs";
@@ -76396,6 +76577,8 @@ async function authStatus(args, ctx) {
76396
76577
  return 2;
76397
76578
  }
76398
76579
  const config = await deps.readConfig();
76580
+ const credential = await resolveApiCredential(deps, ctx.profile);
76581
+ const apiKey = credential.key;
76399
76582
  let coreOwned = false;
76400
76583
  const deviceToken = await resolveDeviceToken(deps, ctx.profile).catch((error) => {
76401
76584
  if (!(error instanceof ProfileCoreOwnedError))
@@ -76403,7 +76586,6 @@ async function authStatus(args, ctx) {
76403
76586
  coreOwned = true;
76404
76587
  return;
76405
76588
  });
76406
- const apiKey = await resolveApiKey(deps, ctx.profile);
76407
76589
  const rows = [];
76408
76590
  if (coreOwned) {
76409
76591
  rows.push({
@@ -76448,15 +76630,18 @@ async function authStatus(args, ctx) {
76448
76630
  const configPath = configFilePathForDisplay(deps.env);
76449
76631
  const profileFields = effectiveProfileFields(config, ctx.profile);
76450
76632
  const cachedAccount = ctx.profile !== undefined ? profileFields.account : undefined;
76633
+ const shownKeyPrefix = credential.source === "file" ? apiKey ? apiKeyPrefix(apiKey) : undefined : profileFields.keyPrefix;
76451
76634
  const mismatch = ctx.profile !== undefined && account !== undefined && cachedAccount !== undefined && account !== cachedAccount && credentialEnvOverrides(deps.env).length === 0;
76452
76635
  if (json) {
76453
76636
  deps.stdout.write(`${JSON.stringify({
76454
76637
  backend: deps.backend,
76455
76638
  profile: ctx.profile,
76456
76639
  deviceTokenPrefix: profileFields.deviceTokenPrefix,
76457
- keyPrefix: profileFields.keyPrefix,
76640
+ keyPrefix: shownKeyPrefix,
76641
+ apiKeySource: credential.source,
76458
76642
  account,
76459
76643
  cachedAccount,
76644
+ envOverrides: credentialEnvOverrides(deps.env),
76460
76645
  apiUrl,
76461
76646
  configPath,
76462
76647
  rows
@@ -76464,9 +76649,10 @@ async function authStatus(args, ctx) {
76464
76649
  `);
76465
76650
  return exitCode;
76466
76651
  }
76467
- const shownAccount = account ?? profileFields.account;
76468
- const shownUsername = account !== undefined ? username : profileFields.username;
76469
- deps.stdout.write(`${identityLine(ctx.profile, shownAccount, apiUrl, undefined, shownUsername, identityStyle(deps, "stdout"))}
76652
+ const overrides = deps.env.CANDLE_API_KEY_FILE?.trim() ? credentialEnvOverrides(deps.env) : [];
76653
+ const shownAccount = overrides.length ? account : account ?? profileFields.account;
76654
+ const shownUsername = overrides.length ? undefined : account !== undefined ? username : profileFields.username;
76655
+ deps.stdout.write(`${identityLine(ctx.profile, shownAccount, apiUrl, overrides.length ? overrides : undefined, shownUsername, identityStyle(deps, "stdout"))}
76470
76656
  `);
76471
76657
  const style = terminalStyle(deps, "stdout");
76472
76658
  if (mismatch) {
@@ -76479,7 +76665,7 @@ ${heading("Credentials", style)}
76479
76665
  ${fields([
76480
76666
  ["Backend", deps.backend],
76481
76667
  ["Device token prefix", profileFields.deviceTokenPrefix],
76482
- ["API key prefix", profileFields.keyPrefix],
76668
+ ["API key prefix", shownKeyPrefix],
76483
76669
  ["Config file", configPath]
76484
76670
  ], style).join(`
76485
76671
  `)}
@@ -77745,6 +77931,10 @@ var ENVIRONMENT = [
77745
77931
  name: "CANDLE_X402_PAY_TO",
77746
77932
  description: "Devnet tests only: the address devnet x402 payments go to (mainnet pays its pinned address)"
77747
77933
  },
77934
+ {
77935
+ name: "CANDLE_API_KEY_FILE",
77936
+ description: "Owner-only API-key file (Linux/macOS 0400/0600); exclusive, conflicts with raw key variables; restart MCP after replacement"
77937
+ },
77748
77938
  { name: "CANDLE_API_KEY", description: "An agent API key; beats the stored one" },
77749
77939
  { name: "CANDLE_DEVICE_TOKEN", description: "A device token, for key-management commands" },
77750
77940
  {
@@ -77790,6 +77980,7 @@ var ENV_API = [
77790
77980
  "CANDLE_PROFILE",
77791
77981
  "CANDLE_API_URL",
77792
77982
  "CANDLE_API_KEY",
77983
+ "CANDLE_API_KEY_FILE",
77793
77984
  "CANDLE_DEVICE_TOKEN",
77794
77985
  "CANDLE_KEYRING_PASSPHRASE"
77795
77986
  ];
@@ -78741,7 +78932,7 @@ var HELP = {
78741
78932
  { invocation: "resume <prefix>", description: "Resume a stopped key with the device token" },
78742
78933
  {
78743
78934
  invocation: "rotate <prefix|label> ([--grace <duration> | --now] [--force] [--yes] | --finish)",
78744
- description: "Give a key a new secret, keeping its prefix, label, access, limits, wallets and history. The old secret works through --grace (default 1h, at most 24h) or stops at once with --now; --finish ends an open window early. The profile's own key is replaced in the keychain; update CANDLE_API_KEY and other copies yourself. --force rotates again while a window is open"
78935
+ description: "Give a key a new secret, keeping its prefix, label, access, limits, wallets and history. The old secret works through --grace (default 1h, at most 24h) or stops at once with --now; --finish ends an open window early. The profile's own key is replaced in the keychain; update CANDLE_API_KEY or CANDLE_API_KEY_FILE yourself and restart MCP. --force rotates again while a window is open"
78745
78936
  },
78746
78937
  { invocation: "revoke <prefix>", description: "Revoke an API key permanently" },
78747
78938
  {
@@ -79579,7 +79770,7 @@ async function completion(args, ctx) {
79579
79770
  init_agent_key_access();
79580
79771
  init_args();
79581
79772
  init_client();
79582
- import { closeSync, writeSync } from "node:fs";
79773
+ import { closeSync as closeSync2, writeSync } from "node:fs";
79583
79774
  import { join as join11 } from "node:path";
79584
79775
 
79585
79776
  // src/core/agent-profiles.ts
@@ -81842,8 +82033,8 @@ class SignalService {
81842
82033
  this.armIdle();
81843
82034
  }
81844
82035
  if (!this.snapshotSeen) {
81845
- const ready = new Promise((resolve2) => this.waiters.push(resolve2));
81846
- const wait = this.deps.waitForSnapshot ?? ((ms, until) => Promise.race([until, new Promise((resolve2) => this.schedule(resolve2, ms))]));
82036
+ const ready = new Promise((resolve3) => this.waiters.push(resolve3));
82037
+ const wait = this.deps.waitForSnapshot ?? ((ms, until) => Promise.race([until, new Promise((resolve3) => this.schedule(resolve3, ms))]));
81847
82038
  await wait(SIGNAL_QUERY_WAIT_MS, ready);
81848
82039
  }
81849
82040
  const rows = this.rowsCache.filter((row2) => query2.chain === undefined || row2.chain === query2.chain).filter((row2) => query2.mint === undefined || sameWallet(row2.chain, row2.mint, query2.mint)).filter((row2) => query2.pattern === undefined || row2.pattern === query2.pattern).slice(0, query2.limit);
@@ -81941,8 +82132,8 @@ class SignalService {
81941
82132
  const parsed = frame.signals.map(parseStreamRow).filter((row2) => row2 !== undefined);
81942
82133
  this.rowsCache = replaceTape(parsed);
81943
82134
  this.snapshotSeen = true;
81944
- for (const resolve2 of this.waiters.splice(0))
81945
- resolve2();
82135
+ for (const resolve3 of this.waiters.splice(0))
82136
+ resolve3();
81946
82137
  this.deps.events.publish("signals", this.snapshotAll());
81947
82138
  this.deps.events.publish("signals:mine", this.snapshotMine());
81948
82139
  return;
@@ -82699,8 +82890,8 @@ var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
82699
82890
  var TURN_ID = /^[A-Za-z0-9_-]{1,64}$/;
82700
82891
  var MESSAGE_ID = /^[A-Za-z0-9_-]{1,64}$/;
82701
82892
  var MAX_SEND_BYTES = 64 * 1024;
82702
- var defaultSleep = (ms) => new Promise((resolve2) => {
82703
- const timer = setTimeout(resolve2, ms);
82893
+ var defaultSleep = (ms) => new Promise((resolve3) => {
82894
+ const timer = setTimeout(resolve3, ms);
82704
82895
  timer.unref?.();
82705
82896
  });
82706
82897
  function refusalError(refusal2) {
@@ -83376,11 +83567,11 @@ class CoreServer {
83376
83567
  }
83377
83568
  await rm4(path, { force: true });
83378
83569
  const server = createServer((socket) => this.accept(socket));
83379
- await new Promise((resolve2, reject) => {
83570
+ await new Promise((resolve3, reject) => {
83380
83571
  server.once("error", reject);
83381
83572
  server.listen(path, () => {
83382
83573
  server.off("error", reject);
83383
- resolve2();
83574
+ resolve3();
83384
83575
  });
83385
83576
  });
83386
83577
  await chmod9(path, 384);
@@ -83393,7 +83584,7 @@ class CoreServer {
83393
83584
  const server = this.server;
83394
83585
  this.server = undefined;
83395
83586
  if (server)
83396
- await new Promise((resolve2) => server.close(() => resolve2()));
83587
+ await new Promise((resolve3) => server.close(() => resolve3()));
83397
83588
  await rm4(this.options.socketPath, { force: true });
83398
83589
  }
83399
83590
  async disconnect(clientId2, reason) {
@@ -85623,7 +85814,7 @@ ${USAGE5}`, json);
85623
85814
  try {
85624
85815
  writeSync(viewTokenFd, `${tokens.viewToken}
85625
85816
  `);
85626
- closeSync(viewTokenFd);
85817
+ closeSync2(viewTokenFd);
85627
85818
  } catch {
85628
85819
  writeLocalFailure(deps, { code: "CORE_VIEW_TOKEN_FD", message: `Could not write the view token to descriptor ${viewTokenFd}.` }, json);
85629
85820
  return 1;
@@ -85648,7 +85839,7 @@ Socket: ${paths.socket}
85648
85839
  `);
85649
85840
  }
85650
85841
  const running = server;
85651
- await new Promise((resolve2) => {
85842
+ await new Promise((resolve3) => {
85652
85843
  let unsubscribe;
85653
85844
  let done = false;
85654
85845
  const finish = (reason) => {
@@ -85657,7 +85848,7 @@ Socket: ${paths.socket}
85657
85848
  done = true;
85658
85849
  stopReason = reason;
85659
85850
  unsubscribe?.();
85660
- resolve2();
85851
+ resolve3();
85661
85852
  };
85662
85853
  running.onViewCount((count2) => {
85663
85854
  if (count2 !== 0 || !running.hadView())
@@ -85895,9 +86086,9 @@ class CoreClientError extends Error {
85895
86086
  }
85896
86087
  }
85897
86088
  async function connectCore(input) {
85898
- const socket = await new Promise((resolve2, reject) => {
86089
+ const socket = await new Promise((resolve3, reject) => {
85899
86090
  const s = createConnection(input.socketPath);
85900
- s.once("connect", () => resolve2(s));
86091
+ s.once("connect", () => resolve3(s));
85901
86092
  s.once("error", (error) => {
85902
86093
  reject(error.code === "ENOENT" || error.code === "ECONNREFUSED" ? new CoreClientError("CORE_NOT_RUNNING", `No Candle core is listening at ${input.socketPath}. Start it with candle tui.`) : error);
85903
86094
  });
@@ -85946,8 +86137,8 @@ async function connectCore(input) {
85946
86137
  if (closed)
85947
86138
  return Promise.reject(new CoreClientError("CORE_DISCONNECTED", "The Candle core closed the connection."));
85948
86139
  const id = nextId++;
85949
- return new Promise((resolve2, reject) => {
85950
- pending.set(id, { resolve: resolve2, reject });
86140
+ return new Promise((resolve3, reject) => {
86141
+ pending.set(id, { resolve: resolve3, reject });
85951
86142
  socket.write(encodeLine({ jsonrpc: "2.0", id, method, params: params ?? {} }));
85952
86143
  });
85953
86144
  },
@@ -86083,6 +86274,7 @@ ${USAGE6}`, json);
86083
86274
  }
86084
86275
 
86085
86276
  // src/commands/doctor.ts
86277
+ init_api_key_file();
86086
86278
  init_agent_key_access();
86087
86279
  init_args();
86088
86280
  init_client();
@@ -86104,7 +86296,7 @@ function assertSafeRef(ref) {
86104
86296
  }
86105
86297
  var RUN_TIMEOUT_MS = 1e4;
86106
86298
  function run(bin, args, stdin) {
86107
- return new Promise((resolve2, reject) => {
86299
+ return new Promise((resolve3, reject) => {
86108
86300
  const child = spawn(bin, args, { stdio: ["pipe", "pipe", "pipe"], env: process.env });
86109
86301
  let stdout = "";
86110
86302
  let stderr = "";
@@ -86133,7 +86325,7 @@ function run(bin, args, stdin) {
86133
86325
  return;
86134
86326
  settled = true;
86135
86327
  clearTimeout(timeout);
86136
- resolve2({ status: code ?? -1, stdout, stderr });
86328
+ resolve3({ status: code ?? -1, stdout, stderr });
86137
86329
  });
86138
86330
  if (stdin !== undefined)
86139
86331
  child.stdin.write(stdin);
@@ -87874,7 +88066,7 @@ var DOCTOR_ROW_IDS = {
87874
88066
  };
87875
88067
  var API_KEY_CHECK = "API key valid";
87876
88068
  function keyRotationRow(until, source) {
87877
- const fix = source === "CANDLE_API_KEY" ? "update CANDLE_API_KEY where this key is deployed to the new secret" : "store the new secret with candle auth login, or rotate from this profile with candle keys rotate";
88069
+ const fix = source === "CANDLE_API_KEY_FILE" ? "replace the old secret in CANDLE_API_KEY_FILE and restart MCP; the CLI did not update the file" : source === "CANDLE_API_KEY" ? "update CANDLE_API_KEY where this key is deployed to the new secret" : "store the new secret with candle auth login, or rotate from this profile with candle keys rotate";
87878
88070
  return {
87879
88071
  check: "Key rotation",
87880
88072
  state: "WARN",
@@ -87991,6 +88183,20 @@ async function doctor(args, ctx) {
87991
88183
  writeUsageFailure(deps, "--role must be owner, bot, or auto", json);
87992
88184
  return 2;
87993
88185
  }
88186
+ if (hasApiKeyFile(deps.env)) {
88187
+ try {
88188
+ await resolveApiKey(deps, ctx.profile);
88189
+ } catch (error) {
88190
+ if (!(error instanceof ApiKeyFileError))
88191
+ throw error;
88192
+ writeLocalFailure(deps, {
88193
+ code: error.code,
88194
+ message: error.message,
88195
+ details: { rows: [{ check: "API key", state: "FAIL", code: error.code, detail: error.message }] }
88196
+ }, json);
88197
+ return 1;
88198
+ }
88199
+ }
87994
88200
  const rows = [];
87995
88201
  const config = await deps.readConfig();
87996
88202
  const fields2 = effectiveProfileFields(config, ctx.profile);
@@ -88041,7 +88247,11 @@ async function doctor(args, ctx) {
88041
88247
  coreOwned = true;
88042
88248
  return;
88043
88249
  }));
88044
- const apiKey = await readCredential(() => resolveApiKey(deps, ctx.profile));
88250
+ let apiCredential;
88251
+ const apiKey = await readCredential(async () => {
88252
+ apiCredential = await resolveApiCredential(deps, ctx.profile);
88253
+ return apiCredential.key;
88254
+ });
88045
88255
  const role = requestedRole === "auto" ? apiKey && !deviceToken ? "bot" : "owner" : requestedRole;
88046
88256
  rows.push(deviceToken || apiKey ? {
88047
88257
  check: "Credentials present",
@@ -88058,7 +88268,10 @@ async function doctor(args, ctx) {
88058
88268
  value: apiUrl,
88059
88269
  source: ctx.apiUrlFlag !== undefined ? "--api-url" : deps.env.CANDLE_API_URL?.trim() ? "CANDLE_API_URL" : ctx.profile && config.profiles?.[ctx.profile]?.apiUrl?.trim() ? `profile ${ctx.profile}` : config.apiUrl?.trim() ? "config" : "default"
88060
88270
  },
88061
- apiKey: { source: source(apiKey, "CANDLE_API_KEY"), prefix: apiKey ? apiKeyPrefix(apiKey) ?? null : null },
88271
+ apiKey: {
88272
+ source: apiCredential?.source === "file" ? "CANDLE_API_KEY_FILE" : source(apiKey, "CANDLE_API_KEY"),
88273
+ prefix: apiKey ? apiKeyPrefix(apiKey) ?? null : null
88274
+ },
88062
88275
  deviceToken: { source: source(deviceToken, "CANDLE_DEVICE_TOKEN") }
88063
88276
  };
88064
88277
  rows.push({ check: "API URL", state: "PASS", detail: `${apiUrl} (from ${provenance.apiUrl.source})` }, {
@@ -88329,7 +88542,7 @@ init_solana_endpoint();
88329
88542
  // src/vault/domains.ts
88330
88543
  init_errors2();
88331
88544
  import { homedir as homedir4 } from "node:os";
88332
- import { basename as basename2, dirname as dirname10, isAbsolute as isAbsolute2, join as join12, resolve as resolve2, sep as sep2 } from "node:path";
88545
+ import { basename as basename2, dirname as dirname10, isAbsolute as isAbsolute2, join as join12, resolve as resolve3, sep as sep2 } from "node:path";
88333
88546
  var ICLOUD_DRIVE_SEGMENTS = ["Library", "Mobile Documents", "com~apple~CloudDocs"];
88334
88547
  var ICLOUD_SHORTHAND = "icloud";
88335
88548
  var ICLOUD_BACKUP_FOLDER = "Candle";
@@ -88378,7 +88591,7 @@ function placeResolvedPath(absolute, home) {
88378
88591
  return "unknown";
88379
88592
  }
88380
88593
  async function classifyDestination(path, opts) {
88381
- const absolute = isAbsolute2(path) ? path : resolve2(path);
88594
+ const absolute = isAbsolute2(path) ? path : resolve3(path);
88382
88595
  const spelledHome = opts.home ?? homedir4();
88383
88596
  let home;
88384
88597
  try {
@@ -93308,15 +93521,23 @@ async function keysRotate(args, ctx) {
93308
93521
  storeError = error instanceof Error ? error.message : String(error);
93309
93522
  }
93310
93523
  }
93311
- const envKey = deps.env.CANDLE_API_KEY?.trim();
93312
- const envNamesKey = envKey !== undefined && envKey !== "" && apiKeyPrefix(envKey) === keyPrefix;
93524
+ let credential;
93525
+ try {
93526
+ credential = await resolveExternalApiCredential(deps);
93527
+ } catch {
93528
+ credential = undefined;
93529
+ }
93530
+ const namesKey = credential?.key !== undefined && apiKeyPrefix(credential.key) === keyPrefix;
93531
+ const envNamesKey = credential?.source === "env" && namesKey;
93532
+ const fileNamesKey = credential?.source === "file" && namesKey;
93313
93533
  const until = body.previousValidUntil;
93314
93534
  if (json) {
93315
93535
  deps.stdout.write(`${JSON.stringify({
93316
93536
  ...body,
93317
93537
  stored,
93318
93538
  ...storeError ? { storeError } : {},
93319
- ...envNamesKey ? { candleApiKeyStale: true } : {}
93539
+ ...envNamesKey ? { candleApiKeyStale: true } : {},
93540
+ ...fileNamesKey ? { candleApiKeyFileStale: true } : {}
93320
93541
  })}
93321
93542
  `);
93322
93543
  return storeError ? 1 : 0;
@@ -93338,6 +93559,9 @@ async function keysRotate(args, ctx) {
93338
93559
  `)}
93339
93560
  `);
93340
93561
  const deadline = until === null ? "now" : `before ${formatTime(until)}`;
93562
+ if (fileNamesKey) {
93563
+ notice(deps, "stderr", "warning", `CANDLE_API_KEY_FILE still holds the old secret. The file was not updated. Replace it ${deadline} and restart MCP.`);
93564
+ }
93341
93565
  if (envNamesKey) {
93342
93566
  notice(deps, "stderr", "warning", `CANDLE_API_KEY holds the old secret, and this CLI cannot change it. Update CANDLE_API_KEY where this key is deployed ${deadline}.`);
93343
93567
  }
@@ -93637,7 +93861,7 @@ function createLabClient2(options) {
93637
93861
  const doFetch = options.fetch ?? globalThis.fetch;
93638
93862
  const callMs = options.callTimeoutMs ?? LAB_CALL_TIMEOUT_MS2;
93639
93863
  const now = options.now ?? Date.now;
93640
- const sleep = options.sleep ?? ((ms) => new Promise((resolve3) => setTimeout(resolve3, ms)));
93864
+ const sleep = options.sleep ?? ((ms) => new Promise((resolve4) => setTimeout(resolve4, ms)));
93641
93865
  async function call2(method, path, valid = () => true, body, timeoutMs = callMs) {
93642
93866
  const controller = new AbortController;
93643
93867
  let timedOut = false;
@@ -96064,6 +96288,7 @@ var READ_ONLY_TOOL_NAMES = [
96064
96288
  "candle_get_plans"
96065
96289
  ];
96066
96290
  var CREDENTIAL_ENV_NAMES = [
96291
+ "CANDLE_API_KEY_FILE",
96067
96292
  "CANDLE_API_KEY",
96068
96293
  "CANDLE_AGENT_API_KEY",
96069
96294
  "CANDLE_DEVICE_TOKEN",
@@ -97274,7 +97499,7 @@ function perpsMarketGate2(meta, ctx, minOpenInterestUsd) {
97274
97499
  return { tradeable: false, reason: "minOpenInterest" };
97275
97500
  return { tradeable: true, reason: null };
97276
97501
  }
97277
- function resolve3(snapshot, coin) {
97502
+ function resolve4(snapshot, coin) {
97278
97503
  if (/^[A-Za-z0-9]{1,20}$/.test(coin)) {
97279
97504
  const exact = snapshot.universe.findIndex((row2) => row2.name === coin);
97280
97505
  if (exact !== -1)
@@ -97552,7 +97777,7 @@ class PerpsMarketReader2 {
97552
97777
  async market(coin) {
97553
97778
  this.key();
97554
97779
  const [config, core, extras] = await Promise.all([this.config(), this.core(), this.extras()]);
97555
- const asset = resolve3(core, coin);
97780
+ const asset = resolve4(core, coin);
97556
97781
  const meta = at2(core.universe, asset);
97557
97782
  const ctx = at2(core.ctxs, asset);
97558
97783
  return {
@@ -97578,7 +97803,7 @@ class PerpsMarketReader2 {
97578
97803
  if (opts.sigFigs !== undefined)
97579
97804
  bounded2(opts.sigFigs, 5, 2, 5, "sigFigs");
97580
97805
  const [config, core] = await Promise.all([this.config(), this.core()]);
97581
- const canonical4 = at2(core.universe, resolve3(core, coin)).name;
97806
+ const canonical4 = at2(core.universe, resolve4(core, coin)).name;
97582
97807
  const value = await info2(this.opts.fetcher, this.opts.network, {
97583
97808
  type: "l2Book",
97584
97809
  coin: canonical4,
@@ -97619,7 +97844,7 @@ class PerpsMarketReader2 {
97619
97844
  this.key();
97620
97845
  const window = perpsCandleWindow2(opts, this.now());
97621
97846
  const [config, core] = await Promise.all([this.config(), this.core()]);
97622
- const canonical4 = at2(core.universe, resolve3(core, coin)).name;
97847
+ const canonical4 = at2(core.universe, resolve4(core, coin)).name;
97623
97848
  const value = await info2(this.opts.fetcher, this.opts.network, {
97624
97849
  type: "candleSnapshot",
97625
97850
  req: { coin: canonical4, interval: window.interval, startTime: window.startTime, endTime: window.endTime }
@@ -97654,7 +97879,7 @@ class PerpsMarketReader2 {
97654
97879
  this.key();
97655
97880
  const window = perpsFundingWindow2(opts, this.now());
97656
97881
  const [config, core] = await Promise.all([this.config(), this.core()]);
97657
- const canonical4 = at2(core.universe, resolve3(core, coin)).name;
97882
+ const canonical4 = at2(core.universe, resolve4(core, coin)).name;
97658
97883
  const value = await info2(this.opts.fetcher, this.opts.network, {
97659
97884
  type: "fundingHistory",
97660
97885
  coin: canonical4,
@@ -97718,7 +97943,7 @@ var EVM_ADDRESS_RE4 = /^0x[0-9a-f]{40}$/;
97718
97943
  function isTime2(value) {
97719
97944
  return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
97720
97945
  }
97721
- function invalid2(field, message) {
97946
+ function invalid3(field, message) {
97722
97947
  return new PerpsHistoryError2("VALIDATION_FAILED", message, { field });
97723
97948
  }
97724
97949
  function unavailable5(message, retryAfterMs2) {
@@ -97749,16 +97974,16 @@ function encodeCursor2(state) {
97749
97974
  }
97750
97975
  function decodeCursor2(cursor) {
97751
97976
  if (cursor.length > PERPS_HISTORY_MAX_CURSOR_LENGTH2 || !cursor.startsWith("ph1."))
97752
- throw invalid2("cursor", "cursor is not one this client issued");
97977
+ throw invalid3("cursor", "cursor is not one this client issued");
97753
97978
  let state;
97754
97979
  try {
97755
97980
  state = JSON.parse(base64UrlDecode2(cursor.slice(4)));
97756
97981
  } catch {
97757
- throw invalid2("cursor", "cursor is not one this client issued");
97982
+ throw invalid3("cursor", "cursor is not one this client issued");
97758
97983
  }
97759
97984
  const ok = state !== null && typeof state === "object" && state.v === 1 && (state.k === "fills" || state.k === "funding") && (state.n === "mainnet" || state.n === "testnet") && typeof state.w === "string" && EVM_ADDRESS_RE4.test(state.w) && isTime2(state.s) && isTime2(state.e) && state.s <= state.e && Number.isSafeInteger(state.l) && state.l >= 1 && state.l <= PERPS_HISTORY_MAX_LIMIT2[state.k] && isTime2(state.t) && state.t >= state.s && state.t <= state.e && Array.isArray(state.b) && state.b.every((id) => typeof id === "string" && id.length <= 512) && Number.isSafeInteger(state.c) && state.c >= 0;
97760
97985
  if (!ok)
97761
- throw invalid2("cursor", "cursor is not one this client issued");
97986
+ throw invalid3("cursor", "cursor is not one this client issued");
97762
97987
  return state;
97763
97988
  }
97764
97989
  function canonical4(value) {
@@ -97844,35 +98069,35 @@ function resolveWindow2(req) {
97844
98069
  const kind = req.kind;
97845
98070
  const address = req.address.toLowerCase();
97846
98071
  if (!EVM_ADDRESS_RE4.test(address))
97847
- throw invalid2("wallet", "The wallet's Hyperliquid account is not an EVM address");
98072
+ throw invalid3("wallet", "The wallet's Hyperliquid account is not an EVM address");
97848
98073
  for (const field of ["startTime", "endTime"]) {
97849
98074
  const value = options[field];
97850
98075
  if (value !== undefined && !isTime2(value))
97851
- throw invalid2(field, `${field} must be a whole, non-negative number of epoch milliseconds`);
98076
+ throw invalid3(field, `${field} must be a whole, non-negative number of epoch milliseconds`);
97852
98077
  }
97853
98078
  const max = PERPS_HISTORY_MAX_LIMIT2[kind];
97854
98079
  if (options.limit !== undefined && (!Number.isSafeInteger(options.limit) || options.limit < 1 || options.limit > max))
97855
- throw invalid2("limit", `limit must be a whole number from 1 to ${max} for ${kind}`);
98080
+ throw invalid3("limit", `limit must be a whole number from 1 to ${max} for ${kind}`);
97856
98081
  if (options.cursor !== undefined) {
97857
98082
  if (typeof options.cursor !== "string" || options.cursor.length === 0)
97858
- throw invalid2("cursor", "cursor is not one this client issued");
98083
+ throw invalid3("cursor", "cursor is not one this client issued");
97859
98084
  const state = decodeCursor2(options.cursor);
97860
98085
  if (state.k !== kind)
97861
- throw invalid2("cursor", `This cursor pages ${state.k}, not ${kind}`);
98086
+ throw invalid3("cursor", `This cursor pages ${state.k}, not ${kind}`);
97862
98087
  if (state.n !== req.network)
97863
- throw invalid2("cursor", `This cursor pages ${state.n}, not ${req.network}`);
98088
+ throw invalid3("cursor", `This cursor pages ${state.n}, not ${req.network}`);
97864
98089
  if (state.w !== address)
97865
- throw invalid2("cursor", "This cursor pages another wallet");
98090
+ throw invalid3("cursor", "This cursor pages another wallet");
97866
98091
  if (options.startTime !== undefined && options.startTime !== state.s || options.endTime !== undefined && options.endTime !== state.e || options.limit !== undefined && options.limit !== state.l)
97867
- throw invalid2("cursor", "The window or page size changed; start again without a cursor");
98092
+ throw invalid3("cursor", "The window or page size changed; start again without a cursor");
97868
98093
  return { state };
97869
98094
  }
97870
98095
  const endTime = options.endTime ?? req.now();
97871
98096
  const startTime = options.startTime ?? Math.max(0, endTime - PERPS_HISTORY_DEFAULT_WINDOW_MS2);
97872
98097
  if (!isTime2(endTime))
97873
- throw invalid2("endTime", "endTime must be epoch milliseconds");
98098
+ throw invalid3("endTime", "endTime must be epoch milliseconds");
97874
98099
  if (startTime > endTime)
97875
- throw invalid2("startTime", "startTime must not be after endTime");
98100
+ throw invalid3("startTime", "startTime must not be after endTime");
97876
98101
  return {
97877
98102
  state: {
97878
98103
  v: 1,
@@ -98105,7 +98330,7 @@ class HyperliquidReadError2 extends Error {
98105
98330
  var INFO_TIMEOUT_MS2 = 5000;
98106
98331
  var RATE_LIMIT_RETRY_MS2 = 1000;
98107
98332
  async function hyperliquidInfo2(fetcher, network2, body, opts = {}) {
98108
- const sleep = opts.sleep ?? ((ms) => new Promise((resolve4) => setTimeout(resolve4, ms)));
98333
+ const sleep = opts.sleep ?? ((ms) => new Promise((resolve5) => setTimeout(resolve5, ms)));
98109
98334
  for (let attempt = 0;; attempt++) {
98110
98335
  const controller = new AbortController;
98111
98336
  const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? INFO_TIMEOUT_MS2);
@@ -98141,7 +98366,7 @@ async function hyperliquidInfo2(fetcher, network2, body, opts = {}) {
98141
98366
  }
98142
98367
  }
98143
98368
  async function readHyperliquidMarketSnapshot2(fetcher, network2, opts = {}) {
98144
- const sleep = opts.sleep ?? ((ms) => new Promise((resolve4) => setTimeout(resolve4, ms)));
98369
+ const sleep = opts.sleep ?? ((ms) => new Promise((resolve5) => setTimeout(resolve5, ms)));
98145
98370
  let limited = false;
98146
98371
  const watched = async (url, init) => {
98147
98372
  const res = await fetcher(url, init);
@@ -98655,10 +98880,10 @@ function abortableSleep(sleep, ms, signal) {
98655
98880
  return sleep(ms);
98656
98881
  if (signal.aborted)
98657
98882
  return Promise.resolve();
98658
- return new Promise((resolve4) => {
98883
+ return new Promise((resolve5) => {
98659
98884
  const done = () => {
98660
98885
  signal.removeEventListener("abort", done);
98661
- resolve4();
98886
+ resolve5();
98662
98887
  };
98663
98888
  signal.addEventListener("abort", done);
98664
98889
  sleep(ms).then(done, done);
@@ -101077,6 +101302,7 @@ async function perpsReduceOnly(args, ctx) {
101077
101302
  }
101078
101303
 
101079
101304
  // src/commands/perps-markets.ts
101305
+ init_api_key_file();
101080
101306
  init_args();
101081
101307
  init_deps();
101082
101308
  init_presentation();
@@ -101220,6 +101446,10 @@ async function read2(ctx, argv, command) {
101220
101446
  }
101221
101447
  return 0;
101222
101448
  } catch (error) {
101449
+ if (error instanceof ApiKeyFileError) {
101450
+ writeLocalFailure(ctx.deps, { code: error.code, message: error.message }, ctx.json);
101451
+ return 1;
101452
+ }
101223
101453
  if (error instanceof PerpsMarketError2) {
101224
101454
  if (error.code === "PERPS_USAGE") {
101225
101455
  writeUsageFailure(ctx.deps, error.message, ctx.json);
@@ -103623,7 +103853,7 @@ init_human_output();
103623
103853
 
103624
103854
  // src/plugins.ts
103625
103855
  import { spawn as spawn2 } from "node:child_process";
103626
- import { accessSync, constants as constants5, readdirSync, statSync } from "node:fs";
103856
+ import { accessSync, constants as constants6, readdirSync, statSync } from "node:fs";
103627
103857
  import { delimiter, join as join14 } from "node:path";
103628
103858
  var PLUGIN_PREFIX = "candle-";
103629
103859
  var RESERVED_HELPER_NAMES = ["fido2", "enclave"];
@@ -103634,7 +103864,7 @@ function executableAt(path) {
103634
103864
  try {
103635
103865
  if (!statSync(path).isFile())
103636
103866
  return false;
103637
- accessSync(path, constants5.X_OK);
103867
+ accessSync(path, constants6.X_OK);
103638
103868
  return true;
103639
103869
  } catch {
103640
103870
  return false;
@@ -103743,16 +103973,16 @@ function pluginEnvironment(input) {
103743
103973
  return env;
103744
103974
  }
103745
103975
  function realRunPlugin(path, args, env) {
103746
- return new Promise((resolve4) => {
103976
+ return new Promise((resolve5) => {
103747
103977
  let child;
103748
103978
  try {
103749
103979
  child = spawn2(path, args, { stdio: "inherit", env });
103750
103980
  } catch {
103751
- resolve4(1);
103981
+ resolve5(1);
103752
103982
  return;
103753
103983
  }
103754
- child.on("error", () => resolve4(1));
103755
- child.on("close", (code) => resolve4(code ?? 1));
103984
+ child.on("error", () => resolve5(1));
103985
+ child.on("close", (code) => resolve5(code ?? 1));
103756
103986
  });
103757
103987
  }
103758
103988
  function pluginInvocation(argv, env, isBuiltIn) {
@@ -107513,9 +107743,9 @@ function parseRenamePairs(contents) {
107513
107743
  continue;
107514
107744
  }
107515
107745
  rows.push({ line, from, to });
107516
- const invalid3 = validateLabel(to);
107517
- if (invalid3 !== undefined)
107518
- findings.push({ line, problem: invalid3 });
107746
+ const invalid4 = validateLabel(to);
107747
+ if (invalid4 !== undefined)
107748
+ findings.push({ line, problem: invalid4 });
107519
107749
  }
107520
107750
  for (const field of ["from", "to"]) {
107521
107751
  const seen = new Map;
@@ -107596,9 +107826,9 @@ async function vaultRename(args, ctx) {
107596
107826
  return usage2(ctx, RENAME_USAGE);
107597
107827
  }
107598
107828
  if (!batch && next !== undefined) {
107599
- const invalid3 = validateLabel(next);
107600
- if (invalid3 !== undefined)
107601
- return usage2(ctx, invalid3);
107829
+ const invalid4 = validateLabel(next);
107830
+ if (invalid4 !== undefined)
107831
+ return usage2(ctx, invalid4);
107602
107832
  if (old === next)
107603
107833
  return usage2(ctx, SAME_STRING_LINE);
107604
107834
  }
@@ -107856,9 +108086,9 @@ async function teeRename(args, ctx) {
107856
108086
  return refusal3(ctx, parsedFile.findings, 2);
107857
108087
  pairs = parsedFile.rows;
107858
108088
  } else {
107859
- const invalid3 = validateLabel(next);
107860
- if (invalid3 !== undefined)
107861
- return usage2(ctx, invalid3);
108089
+ const invalid4 = validateLabel(next);
108090
+ if (invalid4 !== undefined)
108091
+ return usage2(ctx, invalid4);
107862
108092
  if (old === next)
107863
108093
  return usage2(ctx, SAME_STRING_LINE);
107864
108094
  pairs = [{ line: 0, from: old, to: next }];
@@ -109675,7 +109905,7 @@ init_args();
109675
109905
  init_human_output();
109676
109906
  init_presentation();
109677
109907
  import { chmod as chmod13, copyFile, mkdir as mkdir11, stat as stat6 } from "node:fs/promises";
109678
- import nodePath, { dirname as dirname11, resolve as resolve4 } from "node:path";
109908
+ import nodePath, { dirname as dirname11, resolve as resolve5 } from "node:path";
109679
109909
  init_crypto();
109680
109910
  init_errors2();
109681
109911
  init_evm_record();
@@ -110008,7 +110238,7 @@ function fileModeOctal(mode) {
110008
110238
  }
110009
110239
  function resolveBackupDestination(to, deps) {
110010
110240
  if (to.trim().toLowerCase() !== ICLOUD_SHORTHAND)
110011
- return { path: resolve4(to) };
110241
+ return { path: resolve5(to) };
110012
110242
  const home = homeDirOf(deps.env);
110013
110243
  return { path: icloudBackupPath(home, deps.now()), requires: icloudDriveDir(home) };
110014
110244
  }
@@ -110087,7 +110317,7 @@ async function vaultVerifyBackup(args, ctx) {
110087
110317
  const path = resolvedVault.path;
110088
110318
  return runVaultCommand(ctx, async ({ hold }) => {
110089
110319
  const raw = await requireVaultRaw(ctx, resolvedVault);
110090
- const target = resolve4(copyPath);
110320
+ const target = resolve5(copyPath);
110091
110321
  const copyRaw = await readVaultRaw(target);
110092
110322
  if (copyRaw === null)
110093
110323
  throw new VaultError("VAULT_MISSING", `No file at ${target}.`, {
@@ -110788,8 +111018,8 @@ init_errors2();
110788
111018
  init_promote_support();
110789
111019
  init_store();
110790
111020
  init_vault_support();
110791
- import { access as access4, chmod as chmod14, constants as constants6, lstat as lstat2, writeFile as writeFile13 } from "node:fs/promises";
110792
- import { dirname as dirname12, resolve as resolve5 } from "node:path";
111021
+ import { access as access4, chmod as chmod14, constants as constants7, lstat as lstat2, writeFile as writeFile13 } from "node:fs/promises";
111022
+ import { dirname as dirname12, resolve as resolve6 } from "node:path";
110793
111023
  async function vaultExportKey(args, ctx) {
110794
111024
  const parsed = parseArgs(args, {
110795
111025
  valueFlags: ["--keystore", "--to"],
@@ -110809,7 +111039,7 @@ async function vaultExportKey(args, ctx) {
110809
111039
  return 1;
110810
111040
  if (!requireTty(ctx, "vault export-key"))
110811
111041
  return 1;
110812
- const destination = resolve5(to);
111042
+ const destination = resolve6(to);
110813
111043
  const resolvedVault = vaultPathFor(ctx, parsed);
110814
111044
  if ("error" in resolvedVault)
110815
111045
  return usage2(ctx, resolvedVault.error);
@@ -110923,7 +111153,7 @@ async function assertExportTargetWritable(destination) {
110923
111153
  throw error;
110924
111154
  }
110925
111155
  try {
110926
- await access4(parent, constants6.W_OK);
111156
+ await access4(parent, constants7.W_OK);
110927
111157
  } catch {
110928
111158
  throw new VaultError("VAULT_WRITE_FAILED", `${parent} is not writable.`, {
110929
111159
  suggestion: "Choose a directory you can write to, or fix its permissions, then retry."
@@ -111688,7 +111918,7 @@ async function createVault(request2, clock) {
111688
111918
  if (indexRequiresVersion4(seedIndex)) {
111689
111919
  orphaned = await moveAsideOrphanRecord(request2.path, {
111690
111920
  now: clock.now,
111691
- sleep: clock.sleep ?? ((ms) => new Promise((resolve6) => setTimeout(resolve6, ms)))
111921
+ sleep: clock.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)))
111692
111922
  });
111693
111923
  }
111694
111924
  const dek = freshDek();
@@ -115796,14 +116026,14 @@ function parseCounts(count3, teeCount, externalCount, rpcUrl, evmCount, evmTeeCo
115796
116026
  return evmTeeParsed;
115797
116027
  const allOmitted = vaultCount === undefined && teeParsed === undefined && externalParsed === undefined;
115798
116028
  const scan2 = rpcUrl !== undefined;
115799
- const resolve6 = (value) => {
116029
+ const resolve7 = (value) => {
115800
116030
  if (value !== undefined)
115801
116031
  return value;
115802
116032
  return scan2 ? undefined : 1;
115803
116033
  };
115804
- const solanaVault = allOmitted && !scan2 ? 1 : resolve6(vaultCount);
115805
- const solanaTee = allOmitted && !scan2 ? 1 : resolve6(teeParsed);
115806
- const solanaExternal = allOmitted && !scan2 ? 1 : resolve6(externalParsed);
116034
+ const solanaVault = allOmitted && !scan2 ? 1 : resolve7(vaultCount);
116035
+ const solanaTee = allOmitted && !scan2 ? 1 : resolve7(teeParsed);
116036
+ const solanaExternal = allOmitted && !scan2 ? 1 : resolve7(externalParsed);
115807
116037
  return {
115808
116038
  solanaVault,
115809
116039
  solanaTee,
@@ -117978,9 +118208,11 @@ async function clearConfig() {
117978
118208
 
117979
118209
  // src/index.ts
117980
118210
  init_ownership();
118211
+ init_deps();
117981
118212
  init_file_lock();
117982
118213
 
117983
118214
  // src/guard.ts
118215
+ init_api_key_file();
117984
118216
  init_account();
117985
118217
  init_profiles();
117986
118218
  async function verifyProfileAccount(ctx, config) {
@@ -117989,6 +118221,8 @@ async function verifyProfileAccount(ctx, config) {
117989
118221
  return { ok: true, skipped: "--no-verify-account" };
117990
118222
  if (!profile)
117991
118223
  return { ok: true, skipped: "no profile" };
118224
+ if (hasApiKeyFile(deps.env))
118225
+ return { ok: true, skipped: "CANDLE_API_KEY_FILE external override" };
117992
118226
  if (credentialEnvOverrides(deps.env).length > 0)
117993
118227
  return { ok: true, skipped: "env override" };
117994
118228
  const cached = effectiveProfileFields(config, profile).account;
@@ -118081,9 +118315,9 @@ init_evm_tee();
118081
118315
  // release-policy.json
118082
118316
  var release_policy_default = {
118083
118317
  macosHelper: {
118084
- release: "omit",
118318
+ release: "signed",
118085
118319
  bundleId: "tv.candle.cli.enclave",
118086
- teamId: ""
118320
+ teamId: "ZC2DLBZS4U"
118087
118321
  }
118088
118322
  };
118089
118323
 
@@ -118375,6 +118609,7 @@ function extractOwnerConfirmFlags(argv) {
118375
118609
  return { argv: [...kept, ...tail], open: open8, noWait };
118376
118610
  }
118377
118611
  async function run2(argv, deps) {
118612
+ deps = beginCredentialInvocation(deps);
118378
118613
  const confirmFlags = extractOwnerConfirmFlags(argv);
118379
118614
  argv = confirmFlags.argv;
118380
118615
  const confirmState = { pendingWithoutWait: false };
@@ -118406,6 +118641,11 @@ async function runWithConfirm(argv, deps) {
118406
118641
  try {
118407
118642
  code = await runCommand(argv, deps);
118408
118643
  } catch (error2) {
118644
+ if (error2 instanceof ApiKeyFileError) {
118645
+ const parsed2 = extractGlobalFlags(argv);
118646
+ writeLocalFailure(deps, { code: error2.code, message: error2.message }, !("error" in parsed2) && parsed2.flags.json);
118647
+ return 1;
118648
+ }
118409
118649
  if (error2 instanceof ProfileFleetOnlyError) {
118410
118650
  const parsed2 = extractGlobalFlags(argv);
118411
118651
  writeLocalFailure(deps, { code: error2.code, message: error2.message }, !("error" in parsed2) && parsed2.flags.json);
@@ -118506,7 +118746,8 @@ async function runCommand(argv, deps) {
118506
118746
  vaultDevice: flags2.vaultDevice
118507
118747
  });
118508
118748
  }
118509
- const config2 = cmd === "doctor" ? await deps.readConfig() : await migrateProfiles(deps);
118749
+ const keylessMcp = cmd === "mcp" && tokens.includes("--read-only");
118750
+ const config2 = cmd === "doctor" || keylessMcp ? await deps.readConfig() : await migrateProfiles(deps);
118510
118751
  const isAuthLogin = cmd === "auth" && sub === "login";
118511
118752
  const isProfileCommand = cmd === "profile";
118512
118753
  const resolution = isAuthLogin ? resolveProfileNameForLogin(config2, { flag: flags2.profile, env: deps.env }) : isProfileCommand ? { ok: true, name: undefined } : resolveProfileName(config2, { flag: flags2.profile, env: deps.env });
@@ -118599,7 +118840,7 @@ function unknownCommand(deps, token2, word3) {
118599
118840
  }
118600
118841
  async function migrateProfiles(deps) {
118601
118842
  const before = await deps.readConfig();
118602
- if (deps.env.CANDLE_API_KEY?.trim() || deps.env.CANDLE_DEVICE_TOKEN?.trim())
118843
+ if (hasApiKeyFile(deps.env) || deps.env.CANDLE_API_KEY?.trim() || deps.env.CANDLE_DEVICE_TOKEN?.trim())
118603
118844
  return before;
118604
118845
  const { config: config2, migrated } = migratedConfig(before);
118605
118846
  if (!migrated)
@@ -118628,12 +118869,12 @@ function realOpenBrowser(serverUrl) {
118628
118869
  }
118629
118870
  var HELPER_WORKING_DIRECTORY = "/";
118630
118871
  function realSpawnHelper(path, requestLine, opts) {
118631
- return new Promise((resolve6) => {
118872
+ return new Promise((resolve7) => {
118632
118873
  let child;
118633
118874
  try {
118634
118875
  child = spawn3(path, opts.args ?? [], { stdio: ["pipe", "pipe", "pipe"], cwd: HELPER_WORKING_DIRECTORY });
118635
118876
  } catch (error2) {
118636
- resolve6({ stdout: "", stderr: "", exitCode: null, signal: null, spawnError: messageOf4(error2) });
118877
+ resolve7({ stdout: "", stderr: "", exitCode: null, signal: null, spawnError: messageOf4(error2) });
118637
118878
  return;
118638
118879
  }
118639
118880
  const out = [];
@@ -118646,7 +118887,7 @@ function realSpawnHelper(path, requestLine, opts) {
118646
118887
  return;
118647
118888
  settled = true;
118648
118889
  clearTimeout(timer);
118649
- resolve6({ stdout: Buffer.concat(out).toString("utf8"), stderr: Buffer.concat(err).toString("utf8"), ...result2 });
118890
+ resolve7({ stdout: Buffer.concat(out).toString("utf8"), stderr: Buffer.concat(err).toString("utf8"), ...result2 });
118650
118891
  };
118651
118892
  let exited;
118652
118893
  const timer = setTimeout(() => {
@@ -118695,7 +118936,7 @@ async function buildRealDeps() {
118695
118936
  }
118696
118937
  },
118697
118938
  now: () => Date.now(),
118698
- sleep: (ms) => new Promise((resolve6) => setTimeout(resolve6, ms)),
118939
+ sleep: (ms) => new Promise((resolve7) => setTimeout(resolve7, ms)),
118699
118940
  openBrowser: realOpenBrowser,
118700
118941
  env: process.env,
118701
118942
  nodeVersion: process.versions.node,
@@ -118709,10 +118950,10 @@ async function buildRealDeps() {
118709
118950
  },
118710
118951
  readFile: (path) => readFile21(path, "utf8"),
118711
118952
  readBytes: (path) => readFile21(path),
118712
- readStdin: () => new Promise((resolve6, reject) => {
118953
+ readStdin: () => new Promise((resolve7, reject) => {
118713
118954
  const chunks = [];
118714
118955
  process.stdin.on("data", (chunk) => chunks.push(chunk));
118715
- process.stdin.on("end", () => resolve6(new Uint8Array(Buffer.concat(chunks))));
118956
+ process.stdin.on("end", () => resolve7(new Uint8Array(Buffer.concat(chunks))));
118716
118957
  process.stdin.on("error", reject);
118717
118958
  process.stdin.resume();
118718
118959
  }),
@@ -118752,13 +118993,13 @@ async function buildRealDeps() {
118752
118993
  };
118753
118994
  }
118754
118995
  function realRunHook(command, stdin, env, timeoutMs) {
118755
- return new Promise((resolve6) => {
118996
+ return new Promise((resolve7) => {
118756
118997
  let timedOut = false;
118757
118998
  let child;
118758
118999
  try {
118759
119000
  child = spawn3(command, { shell: true, env: { ...process.env, ...env }, stdio: ["pipe", "pipe", "pipe"] });
118760
119001
  } catch (err) {
118761
- resolve6({
119002
+ resolve7({
118762
119003
  exitCode: null,
118763
119004
  signal: null,
118764
119005
  timedOut: false,
@@ -118776,11 +119017,11 @@ function realRunHook(command, stdin, env, timeoutMs) {
118776
119017
  child.stdin?.end(stdin);
118777
119018
  child.on("error", (err) => {
118778
119019
  clearTimeout(timer);
118779
- resolve6({ exitCode: null, signal: null, timedOut, error: err.message });
119020
+ resolve7({ exitCode: null, signal: null, timedOut, error: err.message });
118780
119021
  });
118781
119022
  child.on("close", (exitCode, signal) => {
118782
119023
  clearTimeout(timer);
118783
- resolve6({ exitCode, signal, timedOut });
119024
+ resolve7({ exitCode, signal, timedOut });
118784
119025
  });
118785
119026
  });
118786
119027
  }