@candledottv/cli 0.8.0 → 0.8.2

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 (2) hide show
  1. package/dist/index.js +207 -68
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -18761,6 +18761,24 @@ function isLoopbackHost2(hostname) {
18761
18761
  return true;
18762
18762
  return /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(host);
18763
18763
  }
18764
+ function isPrivateHost2(hostname) {
18765
+ const host = hostname.toLowerCase().replace(/^\[|\]$/g, "");
18766
+ if (!host.includes(".") && !host.includes(":"))
18767
+ return true;
18768
+ if (/\.(local|internal|home\.arpa)$/.test(host))
18769
+ return true;
18770
+ if (/^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(host))
18771
+ return true;
18772
+ if (/^192\.168\.\d{1,3}\.\d{1,3}$/.test(host))
18773
+ return true;
18774
+ if (/^172\.(1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3}$/.test(host))
18775
+ return true;
18776
+ if (/^169\.254\.\d{1,3}\.\d{1,3}$/.test(host))
18777
+ return true;
18778
+ if (/^f[cd][0-9a-f]{2}:/.test(host))
18779
+ return true;
18780
+ return /^fe[89ab][0-9a-f]:/.test(host);
18781
+ }
18764
18782
  function assertTransportSecurity(apiUrl, env) {
18765
18783
  let parsed;
18766
18784
  try {
@@ -18775,9 +18793,9 @@ function assertTransportSecurity(apiUrl, env) {
18775
18793
  }
18776
18794
  if (isLoopbackHost2(parsed.hostname))
18777
18795
  return;
18778
- if (env.CANDLE_ALLOW_INSECURE_HTTP?.trim())
18796
+ if (env.CANDLE_ALLOW_INSECURE_HTTP?.trim() && isPrivateHost2(parsed.hostname))
18779
18797
  return;
18780
- throw new Error(`Refusing to send credentials in the clear to ${parsed.origin}. Set CANDLE_API_URL to an https:// ` + "URL, or set CANDLE_ALLOW_INSECURE_HTTP=1 if this really is a trusted local endpoint.");
18798
+ throw new Error(`Refusing to send credentials in the clear to ${parsed.origin}. Set CANDLE_API_URL to an https:// ` + (isPrivateHost2(parsed.hostname) ? "URL, or set CANDLE_ALLOW_INSECURE_HTTP=1 if this really is a trusted local endpoint." : "URL. CANDLE_ALLOW_INSECURE_HTTP does not apply here: it covers private networks only, " + "and this is a public address."));
18781
18799
  }
18782
18800
  function resolveConfig(env = process.env) {
18783
18801
  const apiUrl = env.CANDLE_API_URL?.trim() || DEFAULT_API_URL2;
@@ -19527,7 +19545,7 @@ var init_tools = __esm(() => {
19527
19545
  });
19528
19546
 
19529
19547
  // ../mcp/src/version.ts
19530
- var SERVER_VERSION = "0.5.0";
19548
+ var SERVER_VERSION = "0.6.2";
19531
19549
 
19532
19550
  // ../mcp/src/server.ts
19533
19551
  var exports_server = {};
@@ -19578,6 +19596,24 @@ function isLoopbackHost(hostname) {
19578
19596
  return true;
19579
19597
  return /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(host);
19580
19598
  }
19599
+ function isPrivateHost(hostname) {
19600
+ const host = hostname.toLowerCase().replace(/^\[|\]$/g, "");
19601
+ if (!host.includes(".") && !host.includes(":"))
19602
+ return true;
19603
+ if (/\.(local|internal|home\.arpa)$/.test(host))
19604
+ return true;
19605
+ if (/^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(host))
19606
+ return true;
19607
+ if (/^192\.168\.\d{1,3}\.\d{1,3}$/.test(host))
19608
+ return true;
19609
+ if (/^172\.(1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3}$/.test(host))
19610
+ return true;
19611
+ if (/^169\.254\.\d{1,3}\.\d{1,3}$/.test(host))
19612
+ return true;
19613
+ if (/^f[cd][0-9a-f]{2}:/.test(host))
19614
+ return true;
19615
+ return /^fe[89ab][0-9a-f]:/.test(host);
19616
+ }
19581
19617
  function insecureApiUrlFault(url, env = process.env) {
19582
19618
  let parsed;
19583
19619
  try {
@@ -19589,9 +19625,9 @@ function insecureApiUrlFault(url, env = process.env) {
19589
19625
  return;
19590
19626
  if (isLoopbackHost(parsed.hostname))
19591
19627
  return;
19592
- if (env[ALLOW_INSECURE_HTTP_ENV]?.trim())
19628
+ if (env[ALLOW_INSECURE_HTTP_ENV]?.trim() && isPrivateHost(parsed.hostname))
19593
19629
  return;
19594
- return `Refusing to send credentials in the clear to ${parsed.origin}. Use https://, or set ` + `${ALLOW_INSECURE_HTTP_ENV}=1 if this really is a trusted local endpoint.`;
19630
+ return `Refusing to send credentials in the clear to ${parsed.origin}. Use https://` + (isPrivateHost(parsed.hostname) ? `, or set ${ALLOW_INSECURE_HTTP_ENV}=1 if this really is a trusted local endpoint.` : `. ${ALLOW_INSECURE_HTTP_ENV} does not apply here: it covers private networks only, and this is a public address.`);
19595
19631
  }
19596
19632
  function resolveApiUrl(configuredApiUrl, env = process.env) {
19597
19633
  const fromEnv = env.CANDLE_API_URL?.trim();
@@ -19889,7 +19925,7 @@ function resolveProfileName(config, opts) {
19889
19925
  if (requested !== undefined) {
19890
19926
  if (!isValidProfileName(requested))
19891
19927
  return { ok: false, message: `Invalid profile name: ${requested}` };
19892
- if (!(requested in profiles)) {
19928
+ if (!Object.hasOwn(profiles, requested)) {
19893
19929
  return {
19894
19930
  ok: false,
19895
19931
  message: `No profile named "${requested}".${names.length ? `
@@ -19899,7 +19935,7 @@ ${listForHumans(profiles, config.activeProfile)}` : " Run: candle auth login --p
19899
19935
  }
19900
19936
  return { ok: true, name: requested };
19901
19937
  }
19902
- if (config.activeProfile && config.activeProfile in profiles)
19938
+ if (config.activeProfile && Object.hasOwn(profiles, config.activeProfile))
19903
19939
  return { ok: true, name: config.activeProfile };
19904
19940
  if (names.length === 0)
19905
19941
  return { ok: true, name: undefined };
@@ -19919,7 +19955,7 @@ function resolveProfileNameForLogin(config, opts) {
19919
19955
  return { ok: false, message: `Invalid profile name: ${requested}` };
19920
19956
  return { ok: true, name: requested };
19921
19957
  }
19922
- if (config.activeProfile && config.activeProfile in profiles)
19958
+ if (config.activeProfile && Object.hasOwn(profiles, config.activeProfile))
19923
19959
  return { ok: true, name: config.activeProfile };
19924
19960
  const names = Object.keys(profiles);
19925
19961
  return { ok: true, name: names.length === 1 ? names[0] : undefined };
@@ -20016,6 +20052,41 @@ function profileTable(config, now) {
20016
20052
  import { chmod, mkdir, readFile, rename, writeFile } from "node:fs/promises";
20017
20053
  import { homedir } from "node:os";
20018
20054
  import { dirname, join } from "node:path";
20055
+
20056
+ // src/file-lock.ts
20057
+ import { open, rm, stat } from "node:fs/promises";
20058
+ var STALE_MS = 30000;
20059
+ var RETRY_MS = 25;
20060
+ var TIMEOUT_MS = 1e4;
20061
+ async function withFileLock(target, fn) {
20062
+ const lockPath = `${target}.lock`;
20063
+ const deadline = Date.now() + TIMEOUT_MS;
20064
+ for (;; ) {
20065
+ try {
20066
+ await (await open(lockPath, "wx")).close();
20067
+ break;
20068
+ } catch (error) {
20069
+ if (error.code !== "EEXIST")
20070
+ throw error;
20071
+ const age = await stat(lockPath).then((s) => Date.now() - s.mtimeMs).catch(() => 0);
20072
+ if (age > STALE_MS) {
20073
+ await rm(lockPath, { force: true });
20074
+ continue;
20075
+ }
20076
+ if (Date.now() > deadline) {
20077
+ throw new Error(`Timed out waiting for ${lockPath}. Another candle process is writing; if none is running, delete that file.`);
20078
+ }
20079
+ await new Promise((resolve) => setTimeout(resolve, RETRY_MS));
20080
+ }
20081
+ }
20082
+ try {
20083
+ return await fn();
20084
+ } finally {
20085
+ await rm(lockPath, { force: true });
20086
+ }
20087
+ }
20088
+
20089
+ // src/secret-store.ts
20019
20090
  var SECRET_REFS = {
20020
20091
  deviceToken: "device_token",
20021
20092
  apiKey: "api_key"
@@ -20073,26 +20144,30 @@ class EncryptedFileSecretStore {
20073
20144
  }
20074
20145
  async set(ref, value) {
20075
20146
  const passphrase = await this.resolvePassphrase();
20076
- const contents = await this.readContents();
20077
- const salt = crypto.getRandomValues(new Uint8Array(SALT_LENGTH_BYTES));
20078
- const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH_BYTES));
20079
- const key = await deriveKey(passphrase, salt, this.iterations);
20080
- const ciphertext = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, key, new TextEncoder().encode(value));
20081
- contents[ref] = {
20082
- salt: toBase64(salt),
20083
- iv: toBase64(iv),
20084
- ciphertext: toBase64(new Uint8Array(ciphertext)),
20085
- iterations: this.iterations
20086
- };
20087
- await this.writeContents(contents);
20147
+ return withFileLock(this.path, async () => {
20148
+ const contents = await this.readContents();
20149
+ const salt = crypto.getRandomValues(new Uint8Array(SALT_LENGTH_BYTES));
20150
+ const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH_BYTES));
20151
+ const key = await deriveKey(passphrase, salt, this.iterations);
20152
+ const ciphertext = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, key, new TextEncoder().encode(value));
20153
+ contents[ref] = {
20154
+ salt: toBase64(salt),
20155
+ iv: toBase64(iv),
20156
+ ciphertext: toBase64(new Uint8Array(ciphertext)),
20157
+ iterations: this.iterations
20158
+ };
20159
+ await this.writeContents(contents);
20160
+ });
20088
20161
  }
20089
20162
  async delete(ref) {
20090
20163
  await this.resolvePassphrase();
20091
- const contents = await this.readContents();
20092
- if (!(ref in contents))
20093
- return;
20094
- delete contents[ref];
20095
- await this.writeContents(contents);
20164
+ return withFileLock(this.path, async () => {
20165
+ const contents = await this.readContents();
20166
+ if (!Object.hasOwn(contents, ref))
20167
+ return;
20168
+ delete contents[ref];
20169
+ await this.writeContents(contents);
20170
+ });
20096
20171
  }
20097
20172
  async resolvePassphrase() {
20098
20173
  if (this.cachedPassphrase !== undefined)
@@ -20128,7 +20203,7 @@ class EncryptedFileSecretStore {
20128
20203
  const dir = dirname(this.path);
20129
20204
  await mkdir(dir, { recursive: true });
20130
20205
  await chmod(dir, 448);
20131
- const tmpPath = `${this.path}.tmp`;
20206
+ const tmpPath = `${this.path}.${crypto.randomUUID()}.tmp`;
20132
20207
  await writeFile(tmpPath, JSON.stringify(contents, null, 2), { encoding: "utf8", mode: 384 });
20133
20208
  await chmod(tmpPath, 384);
20134
20209
  await rename(tmpPath, this.path);
@@ -20183,7 +20258,7 @@ async function resolveApiKey(deps, profile) {
20183
20258
  }
20184
20259
 
20185
20260
  // src/version.ts
20186
- var CLI_VERSION = "0.8.0";
20261
+ var CLI_VERSION = "0.8.2";
20187
20262
 
20188
20263
  // src/commands/auth.ts
20189
20264
  var DEVICE_CODE_PATH = "/api/v1/agent/device/code";
@@ -20865,21 +20940,25 @@ async function keysCreate(args, ctx) {
20865
20940
  }
20866
20941
  const body = result.body;
20867
20942
  const apiKeyRef = ctx.profile ? profileSecretRef(ctx.profile, "apiKey") : SECRET_REFS.apiKey;
20868
- const existingKey = await deps.store.get(apiKeyRef);
20869
20943
  let stored = false;
20870
- if (!existingKey) {
20871
- await deps.store.set(apiKeyRef, body.key);
20872
- if (ctx.profile) {
20873
- await deps.updateProfile(ctx.profile, { keyPrefix: body.keyPrefix, scopes: body.scopes });
20874
- } else {
20875
- await deps.writeConfig({ keyPrefix: body.keyPrefix, scopes: body.scopes });
20944
+ let storeError;
20945
+ try {
20946
+ if (!await deps.store.get(apiKeyRef)) {
20947
+ await deps.store.set(apiKeyRef, body.key);
20948
+ if (ctx.profile) {
20949
+ await deps.updateProfile(ctx.profile, { keyPrefix: body.keyPrefix, scopes: body.scopes });
20950
+ } else {
20951
+ await deps.writeConfig({ keyPrefix: body.keyPrefix, scopes: body.scopes });
20952
+ }
20953
+ stored = true;
20876
20954
  }
20877
- stored = true;
20955
+ } catch (error) {
20956
+ storeError = error instanceof Error ? error.message : String(error);
20878
20957
  }
20879
20958
  if (json) {
20880
- deps.stdout.write(`${JSON.stringify({ ...body, stored })}
20959
+ deps.stdout.write(`${JSON.stringify({ ...body, stored, ...storeError ? { storeError } : {} })}
20881
20960
  `);
20882
- return 0;
20961
+ return storeError ? 1 : 0;
20883
20962
  }
20884
20963
  deps.stdout.write(`API key: ${body.key}
20885
20964
  `);
@@ -20889,14 +20968,22 @@ async function keysCreate(args, ctx) {
20889
20968
  `);
20890
20969
  deps.stdout.write(`Scopes: ${formatScopesForSummary(body.scopes)}
20891
20970
  `);
20971
+ if (storeError !== undefined) {
20972
+ deps.stderr.write(`
20973
+ WARNING: the key above was NOT stored in the ${deps.backend} store: ${storeError}
20974
+ ` + "It is live on your account. Save it now, or revoke it with: candle keys revoke " + `${body.keyPrefix}
20975
+ `);
20976
+ }
20892
20977
  if (!requestedScopes) {
20893
20978
  deps.stdout.write(`No --scopes given: the server granted the default scopes (swap:write excluded).
20894
20979
  `);
20895
20980
  }
20896
- deps.stdout.write(stored ? `Stored in the ${deps.backend} backend as the CLI's working key.
20981
+ if (storeError === undefined) {
20982
+ deps.stdout.write(stored ? `Stored in the ${deps.backend} backend as the CLI's working key.
20897
20983
  ` : `Not stored: the CLI already manages a different working key. This key belongs to whichever agent it was minted for.
20898
20984
  `);
20899
- return 0;
20985
+ }
20986
+ return storeError === undefined ? 0 : 1;
20900
20987
  }
20901
20988
  async function keysRevoke(args, ctx) {
20902
20989
  const { deps, apiUrl, json } = ctx;
@@ -21149,7 +21236,7 @@ async function profileAdd(args, ctx) {
21149
21236
  return 2;
21150
21237
  }
21151
21238
  const config = await deps.readConfig();
21152
- if (config.profiles?.[name]) {
21239
+ if (config.profiles !== undefined && Object.hasOwn(config.profiles, name)) {
21153
21240
  writeLocalFailure(deps, {
21154
21241
  code: "PROFILE_EXISTS",
21155
21242
  message: `Profile "${name}" already exists.`,
@@ -21181,7 +21268,7 @@ async function profileUse(args, ctx) {
21181
21268
  return 2;
21182
21269
  }
21183
21270
  const config = await deps.readConfig();
21184
- const profile = config.profiles?.[name];
21271
+ const profile = config.profiles !== undefined && Object.hasOwn(config.profiles, name) ? config.profiles[name] : undefined;
21185
21272
  if (!profile) {
21186
21273
  const names = Object.keys(config.profiles ?? {}).join(", ") || "(none)";
21187
21274
  writeLocalFailure(deps, {
@@ -24768,11 +24855,16 @@ function buildCipherSuite() {
24768
24855
  function parseSolanaSecret(input) {
24769
24856
  const trimmed = input.trim();
24770
24857
  if (!trimmed.startsWith("[")) {
24858
+ let decoded;
24771
24859
  try {
24772
- return base58.decode(trimmed);
24860
+ decoded = base58.decode(trimmed);
24773
24861
  } catch {
24774
24862
  throw new Error("Invalid Solana private key: expected a base58 string or an id.json byte array.");
24775
24863
  }
24864
+ if (decoded.length !== 64) {
24865
+ throw new Error(`Invalid Solana private key: expected 64 bytes, got ${decoded.length}. A 32-byte value is the ` + "SEED, not the keypair; export the full secret key, which is what solana-keygen writes to id.json.");
24866
+ }
24867
+ return decoded;
24776
24868
  }
24777
24869
  const parsed = (() => {
24778
24870
  try {
@@ -24790,8 +24882,8 @@ function parseSolanaSecret(input) {
24790
24882
  function decodeWalletPrivateKey(chain2, privateKey) {
24791
24883
  if (chain2 === "evm") {
24792
24884
  const hex2 = privateKey.startsWith("0x") ? privateKey.slice(2) : privateKey;
24793
- if (hex2.length === 0 || hex2.length % 2 !== 0 || !/^[0-9a-fA-F]+$/.test(hex2)) {
24794
- throw new Error('Invalid EVM private key: expected a hex string (optionally "0x"-prefixed)');
24885
+ if (hex2.length !== 64 || !/^[0-9a-fA-F]+$/.test(hex2)) {
24886
+ throw new Error("Invalid EVM private key: expected 32 bytes as 64 hex characters " + `(optionally "0x"-prefixed), got ${hex2.length} characters`);
24795
24887
  }
24796
24888
  return Uint8Array.from(Buffer.from(hex2, "hex"));
24797
24889
  }
@@ -25187,22 +25279,32 @@ async function walletsImport(args, ctx) {
25187
25279
  }
25188
25280
  async function verifyImportLanded(args) {
25189
25281
  const { deps } = args.ctx;
25190
- const listed = await apiRequest("/api/v1/agent/wallets", {
25191
- method: "GET",
25192
- auth: "key",
25193
- credentials: { apiKey: args.apiKey },
25194
- apiUrl: args.apiUrl,
25195
- fetch: deps.fetch,
25196
- env: deps.env
25197
- });
25198
- if (!listed.ok)
25199
- return { status: "unchecked" };
25200
- const page = listed.body.page;
25201
- if (!Array.isArray(page))
25202
- return { status: "unchecked" };
25203
- const account = page.find((row) => typeof row.userAddress === "string")?.userAddress;
25204
- const found = page.some((row) => row._id === args.id);
25205
- return { status: found ? "verified" : "missing", ...account !== undefined ? { account } : {} };
25282
+ let cursor = null;
25283
+ let account;
25284
+ for (let pageCount = 0;pageCount < 25; pageCount++) {
25285
+ const query = cursor === null ? "?limit=100" : `?limit=100&cursor=${encodeURIComponent(cursor)}`;
25286
+ const listed = await apiRequest(`/api/v1/agent/wallets${query}`, {
25287
+ method: "GET",
25288
+ auth: "key",
25289
+ credentials: { apiKey: args.apiKey },
25290
+ apiUrl: args.apiUrl,
25291
+ fetch: deps.fetch,
25292
+ env: deps.env
25293
+ });
25294
+ if (!listed.ok)
25295
+ return { status: "unchecked", ...account !== undefined ? { account } : {} };
25296
+ const body = listed.body;
25297
+ if (!Array.isArray(body.page))
25298
+ return { status: "unchecked", ...account !== undefined ? { account } : {} };
25299
+ account ??= body.page.find((row) => typeof row.userAddress === "string")?.userAddress;
25300
+ if (body.page.some((row) => row._id === args.id)) {
25301
+ return { status: "verified", ...account !== undefined ? { account } : {} };
25302
+ }
25303
+ if (body.isDone !== false || typeof body.continueCursor !== "string")
25304
+ break;
25305
+ cursor = body.continueCursor;
25306
+ }
25307
+ return { status: "missing", ...account !== undefined ? { account } : {} };
25206
25308
  }
25207
25309
  async function walletsRevoke(args, ctx) {
25208
25310
  const { deps, apiUrl, json } = ctx;
@@ -25315,7 +25417,7 @@ async function writeKeystoreFile(path, contents) {
25315
25417
  const dir = dirname3(path);
25316
25418
  await mkdir2(dir, { recursive: true });
25317
25419
  await chmod2(dir, 448);
25318
- const tmpPath = `${path}.tmp`;
25420
+ const tmpPath = `${path}.${crypto.randomUUID()}.tmp`;
25319
25421
  await writeFile2(tmpPath, contents, { encoding: "utf8", mode: 384 });
25320
25422
  await chmod2(tmpPath, 384);
25321
25423
  await rename2(tmpPath, path);
@@ -27919,7 +28021,16 @@ async function walletsGenerate(args, ctx) {
27919
28021
  let existingRaw = null;
27920
28022
  try {
27921
28023
  existingRaw = await deps.readFile(keystorePath);
27922
- } catch {
28024
+ } catch (error) {
28025
+ const code = error?.code;
28026
+ if (code !== undefined && code !== "ENOENT") {
28027
+ writeLocalFailure(deps, {
28028
+ code: "KEYSTORE_UNREADABLE",
28029
+ message: `Could not read ${keystorePath}: ${error instanceof Error ? error.message : error}`,
28030
+ suggestion: "Refusing to continue: a keystore may exist at that path, and overwriting it would destroy the only copy of its keys."
28031
+ }, json);
28032
+ return 1;
28033
+ }
27923
28034
  existingRaw = null;
27924
28035
  }
27925
28036
  if (existingRaw !== null && !resuming) {
@@ -28045,14 +28156,25 @@ Sealed to ${keystorePath}
28045
28156
  if (alreadyExists) {
28046
28157
  const found = await lookupByAddress(entry.address, apiKey, ctx);
28047
28158
  if (found) {
28159
+ const promoted = await promoteStagedSigner(deps, entry, found._id);
28048
28160
  entry.imported = true;
28049
- entry.privyWalletId = found._id;
28161
+ entry.linkedWalletId = found._id;
28050
28162
  entry.importedAt = new Date().toISOString();
28051
28163
  await persist(store, keystorePath);
28052
- if (!json)
28053
- deps.stdout.write(` [${entry.index}] ${entry.address} already imported, reconciled
28164
+ if (promoted.ok) {
28165
+ if (!json) {
28166
+ deps.stdout.write(` [${entry.index}] ${entry.address} already imported, reconciled as ${found._id}
28054
28167
  `);
28055
- continue;
28168
+ }
28169
+ continue;
28170
+ }
28171
+ failures++;
28172
+ writeLocalFailure(deps, {
28173
+ code: "SIGNER_MISSING",
28174
+ message: `Wallet ${entry.address} exists as ${found._id}, but ${promoted.message}.`,
28175
+ suggestion: `Revoke it (candle wallets revoke ${found._id}) and run: candle wallets generate --resume`
28176
+ }, json);
28177
+ break;
28056
28178
  }
28057
28179
  }
28058
28180
  failures++;
@@ -28068,6 +28190,7 @@ Sealed to ${keystorePath}
28068
28190
  break;
28069
28191
  }
28070
28192
  entry.imported = true;
28193
+ entry.linkedWalletId = flow.submitted.id;
28071
28194
  entry.privyWalletId = flow.submitted.privyWalletId;
28072
28195
  entry.importedAt = new Date().toISOString();
28073
28196
  await persist(store, keystorePath);
@@ -28106,6 +28229,22 @@ ${imported}/${store.entries.length} imported.
28106
28229
  `);
28107
28230
  return 0;
28108
28231
  }
28232
+ async function promoteStagedSigner(deps, entry, linkedWalletId) {
28233
+ const committedRef = walletSignerRef(linkedWalletId);
28234
+ if (await deps.store.get(committedRef) !== null)
28235
+ return { ok: true };
28236
+ const pendingRef = importPendingSignerRef(entry.chain, entry.address);
28237
+ const staged = await deps.store.get(pendingRef);
28238
+ if (staged === null) {
28239
+ return {
28240
+ ok: false,
28241
+ message: `its signer is on neither "${committedRef}" nor "${pendingRef}" in the ${deps.backend} store`
28242
+ };
28243
+ }
28244
+ await deps.store.set(committedRef, staged);
28245
+ await deps.store.delete(pendingRef).catch(() => {});
28246
+ return { ok: true };
28247
+ }
28109
28248
  async function persist(store, path) {
28110
28249
  await writeKeystoreFile(path, await serializeKeystore(store.entries, store.key, store.salt, store.iterations));
28111
28250
  }
@@ -28124,7 +28263,7 @@ async function lookupByAddress(address, apiKey, ctx) {
28124
28263
  }
28125
28264
 
28126
28265
  // src/config.ts
28127
- import { chmod as chmod3, mkdir as mkdir3, readFile as readFile2, rm, writeFile as writeFile3 } from "node:fs/promises";
28266
+ import { chmod as chmod3, mkdir as mkdir3, readFile as readFile2, rm as rm2, writeFile as writeFile3 } from "node:fs/promises";
28128
28267
  import { homedir as homedir4 } from "node:os";
28129
28268
  import { join as join6 } from "node:path";
28130
28269
  function configDir2() {
@@ -28159,7 +28298,7 @@ async function updateProfile(name, patch) {
28159
28298
  }
28160
28299
  async function clearConfig() {
28161
28300
  try {
28162
- await rm(configFilePath());
28301
+ await rm2(configFilePath());
28163
28302
  } catch (err) {
28164
28303
  if (err.code !== "ENOENT")
28165
28304
  throw err;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@candledottv/cli",
3
- "version": "0.8.0",
3
+ "version": "0.8.2",
4
4
  "description": "The Candle CLI: authorize a device from your browser, then manage API keys, wallets, and setup health from the terminal",
5
5
  "type": "module",
6
6
  "bin": {