@tinycloud/cli 1.5.0-beta.0 → 1.5.0-beta.10

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.
@@ -1857,7 +1857,7 @@ var init_sha256 = __esm({
1857
1857
  });
1858
1858
 
1859
1859
  // ../sdk-core/src/manifest.ts
1860
- import { resolveSecretPath, SECRET_NAME_RE } from "@tinycloud/sdk-services";
1860
+ import { resolveSecretPath, SECRET_NAME_RE, parseNetworkId } from "@tinycloud/sdk-services";
1861
1861
  function parseExpiry(duration) {
1862
1862
  if (typeof duration !== "string" || duration.length === 0) {
1863
1863
  throw new ManifestValidationError(
@@ -1900,6 +1900,14 @@ function expandEncryptionPermissionEntry(entry) {
1900
1900
  `tinycloud.encryption entries require path to be a networkId URN (got ${JSON.stringify(entry.path)})`
1901
1901
  );
1902
1902
  }
1903
+ if (entry.path.includes("/")) {
1904
+ throw new ManifestValidationError("named keys go in manifest.keys");
1905
+ }
1906
+ try {
1907
+ parseNetworkId(entry.path);
1908
+ } catch {
1909
+ throw new ManifestValidationError("tinycloud.encryption entries require a valid networkId URN");
1910
+ }
1903
1911
  const normalizedActions = [];
1904
1912
  for (const action of entry.actions) {
1905
1913
  if (action === "decrypt" || action === "tinycloud.encryption/decrypt") {
@@ -2004,8 +2012,52 @@ function validateManifest(input) {
2004
2012
  if (m.secrets !== void 0) {
2005
2013
  validateManifestSecrets(m.secrets);
2006
2014
  }
2015
+ if (Object.prototype.hasOwnProperty.call(m, "keys_app_id") && (typeof m.keys_app_id !== "string" || !KEY_APP_ID.test(m.keys_app_id))) {
2016
+ throw new ManifestValidationError("manifest.keys_app_id is invalid");
2017
+ }
2018
+ validateManifestKeys(m.keys_app_id ?? m.app_id, m.keys);
2007
2019
  return m;
2008
2020
  }
2021
+ function validateManifestKeys(appId, keys) {
2022
+ if (keys === void 0) return;
2023
+ if (!Array.isArray(keys) || keys.length > 32) {
2024
+ throw new ManifestValidationError("manifest.keys must contain at most 32 entries");
2025
+ }
2026
+ if (keys.length === 0) return;
2027
+ if (!KEY_APP_ID.test(appId)) {
2028
+ throw new ManifestValidationError("manifest.keys requires a valid app_id");
2029
+ }
2030
+ const paths = /* @__PURE__ */ new Set();
2031
+ let prefixSeen = false;
2032
+ for (const [index, value] of keys.entries()) {
2033
+ const label = `manifest.keys[${index}]`;
2034
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
2035
+ throw new ManifestValidationError(`${label} must be an object`);
2036
+ }
2037
+ const entry = value;
2038
+ const fields = Object.keys(entry);
2039
+ const hasPath = Object.prototype.hasOwnProperty.call(entry, "path");
2040
+ const hasPrefix = Object.prototype.hasOwnProperty.call(entry, "prefix");
2041
+ if (hasPath === hasPrefix || fields.some((field) => !["path", "prefix", "abilities"].includes(field))) {
2042
+ throw new ManifestValidationError(`${label} must have one path or prefix and abilities`);
2043
+ }
2044
+ if (!Array.isArray(entry.abilities) || entry.abilities.length === 0 || new Set(entry.abilities).size !== entry.abilities.length || entry.abilities.some((ability) => typeof ability !== "string" || !KEY_ABILITIES.includes(ability))) {
2045
+ throw new ManifestValidationError(`${label}.abilities is invalid`);
2046
+ }
2047
+ if (hasPath) {
2048
+ const path = entry.path;
2049
+ if (typeof path !== "string" || path.length > 255 || !KEY_PATH.test(path) || !path.startsWith(`apps/${appId}/`) || paths.has(path)) {
2050
+ throw new ManifestValidationError(`${label}.path is invalid`);
2051
+ }
2052
+ paths.add(path);
2053
+ } else {
2054
+ if (entry.prefix !== `apps/${appId}/` || prefixSeen) {
2055
+ throw new ManifestValidationError(`${label}.prefix is invalid`);
2056
+ }
2057
+ prefixSeen = true;
2058
+ }
2059
+ }
2060
+ }
2009
2061
  function resolveManifestKnowledgeRoot(knowledge) {
2010
2062
  if (knowledge === void 0) {
2011
2063
  return void 0;
@@ -2405,7 +2457,7 @@ function withCapabilitiesReadForSpaces(resources) {
2405
2457
  ...[...spaces].map(capabilitiesReadPermission)
2406
2458
  ]);
2407
2459
  }
2408
- var import_ms, DEFAULT_KNOWLEDGE_ROOT, ManifestValidationError, DEFAULT_EXPIRY, DEFAULT_DEFAULTS, DEFAULT_MANIFEST_VERSION, DEFAULT_MANIFEST_SPACE, SECRETS_SPACE, VAULT_PERMISSION_SERVICE, SERVICE_SHORT_TO_LONG, ENCRYPTION_PERMISSION_SERVICE, ENCRYPTION_MANIFEST_SPACE, SERVICE_LONG_TO_SHORT2, DEFAULT_STANDARD_ENTRIES, DEFAULT_ADMIN_ENTRIES, DEFAULT_ALL_ENTRIES;
2460
+ var import_ms, DEFAULT_KNOWLEDGE_ROOT, ManifestValidationError, DEFAULT_EXPIRY, DEFAULT_DEFAULTS, DEFAULT_MANIFEST_VERSION, DEFAULT_MANIFEST_SPACE, SECRETS_SPACE, VAULT_PERMISSION_SERVICE, SERVICE_SHORT_TO_LONG, ENCRYPTION_PERMISSION_SERVICE, ENCRYPTION_MANIFEST_SPACE, SERVICE_LONG_TO_SHORT2, DEFAULT_STANDARD_ENTRIES, DEFAULT_ADMIN_ENTRIES, DEFAULT_ALL_ENTRIES, KEY_SEGMENT, KEY_APP_ID, KEY_PATH, KEY_ABILITIES;
2409
2461
  var init_manifest = __esm({
2410
2462
  "../sdk-core/src/manifest.ts"() {
2411
2463
  "use strict";
@@ -2488,6 +2540,10 @@ var init_manifest = __esm({
2488
2540
  actions: ["read", "write"]
2489
2541
  }
2490
2542
  ];
2543
+ KEY_SEGMENT = "[a-z0-9][a-z0-9._-]{0,63}";
2544
+ KEY_APP_ID = new RegExp(`^${KEY_SEGMENT}$`);
2545
+ KEY_PATH = new RegExp(`^${KEY_SEGMENT}(?:/${KEY_SEGMENT}){2,7}$`);
2546
+ KEY_ABILITIES = ["key.create", "decrypt", "key.block", "key.unblock"];
2491
2547
  }
2492
2548
  });
2493
2549
 
@@ -15794,7 +15850,7 @@ async function refusal(response) {
15794
15850
  async function prepareLinkRecord(input) {
15795
15851
  const apiOrigin2 = checkApiOrigin(input.apiOrigin);
15796
15852
  const nowMs = toEpochMs(input.now);
15797
- const expiresMs = input.expiresAt.getTime();
15853
+ const expiresMs = Math.floor(input.expiresAt.getTime() / 1e3) * 1e3;
15798
15854
  if (!Number.isFinite(nowMs) || !Number.isFinite(expiresMs)) throw new LinkError("invalid_argument", "now and expiresAt must be valid dates");
15799
15855
  let blob;
15800
15856
  let key;
@@ -16456,7 +16512,7 @@ function formatSpace(space) {
16456
16512
  init_profile_gate();
16457
16513
  init_dist();
16458
16514
  init_profiles();
16459
- import { CLI_NAME as CLI_NAME17 } from "@tinycloud/operations/cli-name";
16515
+ import { CLI_NAME as CLI_NAME18 } from "@tinycloud/operations/cli-name";
16460
16516
  import { get as httpGet } from "http";
16461
16517
  import { get as httpsGet } from "https";
16462
16518
  import { spawn } from "child_process";
@@ -31318,6 +31374,7 @@ init_errors();
31318
31374
  init_constants();
31319
31375
  import { NodeWasmBindings, signedCompactUcanExpiry as signedCompactUcanExpiry2 } from "@tinycloud/node-sdk";
31320
31376
  import { isLocalOwnerProfile as isLocalOwnerProfile2, ownerSessionLacksProof as ownerSessionLacksProof2 } from "@tinycloud/operations/profile";
31377
+ import { CLI_NAME as CLI_NAME12 } from "@tinycloud/operations/cli-name";
31321
31378
 
31322
31379
  // src/lib/duration.ts
31323
31380
  function parseDuration(input) {
@@ -31353,6 +31410,87 @@ init_manifest();
31353
31410
  init_raw_encryption();
31354
31411
  init_owner_did();
31355
31412
  init_caveat_refusal();
31413
+
31414
+ // src/lib/named-keys.ts
31415
+ init_constants();
31416
+ init_errors();
31417
+ import { extractRecapAttenuations } from "@tinycloud/sdk-core";
31418
+ import {
31419
+ EncryptionResourceError,
31420
+ KEY_CREATE_ABILITY,
31421
+ formatPrefixResource,
31422
+ nodeSupportsNamedKeys
31423
+ } from "@tinycloud/sdk-services/encryption/resource";
31424
+ import { DECRYPT_ACTION } from "@tinycloud/sdk-services";
31425
+ var NAMED_KEYS_NETWORK = "default";
31426
+ function namedKeysNetworkId(ownerDid) {
31427
+ return `urn:tinycloud:encryption:${ownerDid}:${NAMED_KEYS_NETWORK}`;
31428
+ }
31429
+ function parseKeysPrefix(input, ownerDid) {
31430
+ const prefix = input.endsWith("/") ? input.slice(0, -1) : input;
31431
+ const invalid3 = (reason) => new CLIError("INVALID_KEY_PREFIX", `Invalid \`--keys\` folder "${input}": ${reason}`, ExitCode.USAGE_ERROR);
31432
+ if (prefix.length === 0) throw invalid3("the folder can't be empty.");
31433
+ if (prefix === "apps" || prefix.startsWith("apps/")) {
31434
+ throw invalid3("folders under `apps/` belong to apps and can't be used from the CLI.");
31435
+ }
31436
+ try {
31437
+ return { prefix, resource: formatPrefixResource(namedKeysNetworkId(ownerDid), prefix) };
31438
+ } catch (error) {
31439
+ if (error instanceof EncryptionResourceError) throw invalid3(error.message);
31440
+ throw error;
31441
+ }
31442
+ }
31443
+ function keyAbilitiesFor(keys) {
31444
+ return { [keys.resource]: [KEY_CREATE_ABILITY, DECRYPT_ACTION] };
31445
+ }
31446
+ function keyPathInPrefix(prefix, path) {
31447
+ return path.startsWith(`${prefix}/`) && path.length > prefix.length + 1;
31448
+ }
31449
+ async function assertNodeSupportsNamedKeys(host, fetchImpl = fetch) {
31450
+ let info = null;
31451
+ try {
31452
+ const response = await fetchImpl(`${host}/info`);
31453
+ if (response.ok) info = await response.json();
31454
+ } catch {
31455
+ info = null;
31456
+ }
31457
+ if (!nodeSupportsNamedKeys(info)) {
31458
+ throw new CLIError(
31459
+ "NAMED_KEYS_UNAVAILABLE",
31460
+ `${host} doesn't support named keys, so \`--keys\` can't be used there. Nothing was signed.`,
31461
+ ExitCode.NODE_ERROR
31462
+ );
31463
+ }
31464
+ }
31465
+ function partialKeysGrantError() {
31466
+ return new CLIError(
31467
+ "KEYS_PARTIAL_GRANT",
31468
+ "Named keys need both permissions (create and use), but only one was approved, so this sign-in wasn't saved. Sign in again and approve both.",
31469
+ ExitCode.PERMISSION_DENIED
31470
+ );
31471
+ }
31472
+ function namedKeysGrantIn(siwe, keys) {
31473
+ let recap = {};
31474
+ try {
31475
+ recap = siwe === void 0 ? {} : extractRecapAttenuations(siwe);
31476
+ } catch {
31477
+ return "none";
31478
+ }
31479
+ const row = recap[keys.resource] ?? {};
31480
+ const wanted = keyAbilitiesFor(keys)[keys.resource];
31481
+ const granted = wanted.filter((ability) => ability in row);
31482
+ if (granted.length === 0) return "none";
31483
+ return granted.length === wanted.length && Object.keys(row).length === wanted.length ? "both" : "partial";
31484
+ }
31485
+ function withVerifiedNamedKeys(profile, session) {
31486
+ if (profile.namedKeys === void 0) return profile;
31487
+ const siwe = typeof session === "object" && session !== null ? session.siwe : void 0;
31488
+ if (typeof siwe === "string" && namedKeysGrantIn(siwe, profile.namedKeys) === "both") return profile;
31489
+ const { namedKeys: _dropped, ...rest } = profile;
31490
+ return rest;
31491
+ }
31492
+
31493
+ // src/auth/scoped-login.ts
31356
31494
  var CAPABILITIES_READ = "tinycloud.capabilities/read";
31357
31495
  var CLOCK_SKEW_MS = 3e4;
31358
31496
  var SIGNED_RECAP = "signed-recap";
@@ -31605,6 +31743,35 @@ function verifySignedSession(data, key, sessionDid, expected = {}) {
31605
31743
  }
31606
31744
  return { ownerDid, permissions, expiresAt };
31607
31745
  }
31746
+ function withoutRequestedKeyRow(signed, keys, signerDid) {
31747
+ const mismatch = () => new CLIError(
31748
+ "OPENKEY_SCOPE_MISMATCH",
31749
+ `The key permission in the approval didn't match what was asked for "<folder>/", so this sign-in wasn't saved. Sign in again with \`${CLI_NAME12} auth login --keys <folder>\`.`,
31750
+ ExitCode.PERMISSION_DENIED
31751
+ );
31752
+ let expectedResource;
31753
+ try {
31754
+ expectedResource = parseKeysPrefix(keys.prefix, canonicalOwnerDid(signerDid)).resource;
31755
+ } catch {
31756
+ throw mismatch();
31757
+ }
31758
+ if (keys.resource !== expectedResource) throw mismatch();
31759
+ const rest = [];
31760
+ const actions = /* @__PURE__ */ new Set();
31761
+ for (const permission of signed) {
31762
+ if (!isVerifiedRawEncryptionPermission(permission) || permission.path !== expectedResource) {
31763
+ rest.push(permission);
31764
+ continue;
31765
+ }
31766
+ if (permission.caveats?.some((caveat) => Object.keys(caveat).length > 0)) throw mismatch();
31767
+ for (const action of permission.actions) actions.add(action);
31768
+ }
31769
+ if (actions.size === 0) return rest;
31770
+ const wanted = keyAbilitiesFor(keys)[expectedResource];
31771
+ if ([...actions].some((action) => !wanted.includes(action))) throw mismatch();
31772
+ if (actions.size !== wanted.length) throw partialKeysGrantError();
31773
+ return rest;
31774
+ }
31608
31775
  function verifyScopedLogin(data, key, sessionDid, requested, expected = {}) {
31609
31776
  const notStored = expected.purpose === "grant" ? "No grant was stored." : "No scoped session was saved.";
31610
31777
  let signed;
@@ -31630,10 +31797,11 @@ function verifyScopedLogin(data, key, sessionDid, requested, expected = {}) {
31630
31797
  }
31631
31798
  const legacyNested = [];
31632
31799
  const approved = [];
31633
- for (const permission of signed.permissions) {
31800
+ const keyRowRest = expected.namedKeyRow === void 0 ? signed.permissions : withoutRequestedKeyRow(signed.permissions, expected.namedKeyRow, signed.ownerDid);
31801
+ for (const permission of keyRowRest) {
31634
31802
  (isLegacyNestedDecrypt(permission, requested, signed.ownerDid, spaceId) ? legacyNested : approved).push(permission);
31635
31803
  }
31636
- for (const permission of signed.permissions) {
31804
+ for (const permission of keyRowRest) {
31637
31805
  if (isVerifiedRawEncryptionPermission(permission) && !rawEncryptionOwnerMatches(permission.path, signed.ownerDid)) {
31638
31806
  throw new CLIError("OPENKEY_SCOPE_MISMATCH", `OpenKey signed decrypt for a network not owned by the approving identity. ${notStored}`, ExitCode.PERMISSION_DENIED);
31639
31807
  }
@@ -31650,80 +31818,6 @@ function verifyScopedLogin(data, key, sessionDid, requested, expected = {}) {
31650
31818
 
31651
31819
  // src/auth/login-commit.ts
31652
31820
  import { lstat as lstat6 } from "fs/promises";
31653
-
31654
- // src/lib/named-keys.ts
31655
- init_constants();
31656
- init_errors();
31657
- import { extractRecapAttenuations } from "@tinycloud/sdk-core";
31658
- import {
31659
- EncryptionResourceError,
31660
- KEY_CREATE_ABILITY,
31661
- formatPrefixResource,
31662
- nodeSupportsNamedKeys
31663
- } from "@tinycloud/sdk-services/encryption/resource";
31664
- import { DECRYPT_ACTION } from "@tinycloud/sdk-services";
31665
- var NAMED_KEYS_NETWORK = "default";
31666
- function namedKeysNetworkId(ownerDid) {
31667
- return `urn:tinycloud:encryption:${ownerDid}:${NAMED_KEYS_NETWORK}`;
31668
- }
31669
- function parseKeysPrefix(input, ownerDid) {
31670
- const prefix = input.endsWith("/") ? input.slice(0, -1) : input;
31671
- const invalid3 = (reason) => new CLIError("INVALID_KEY_PREFIX", `Invalid \`--keys\` folder "${input}": ${reason}`, ExitCode.USAGE_ERROR);
31672
- if (prefix.length === 0) throw invalid3("the folder can't be empty.");
31673
- if (prefix === "apps" || prefix.startsWith("apps/")) {
31674
- throw invalid3("folders under `apps/` belong to apps and can't be used from the CLI.");
31675
- }
31676
- try {
31677
- return { prefix, resource: formatPrefixResource(namedKeysNetworkId(ownerDid), prefix) };
31678
- } catch (error) {
31679
- if (error instanceof EncryptionResourceError) throw invalid3(error.message);
31680
- throw error;
31681
- }
31682
- }
31683
- function keyAbilitiesFor(keys) {
31684
- return { [keys.resource]: [KEY_CREATE_ABILITY, DECRYPT_ACTION] };
31685
- }
31686
- function keyPathInPrefix(prefix, path) {
31687
- return path.startsWith(`${prefix}/`) && path.length > prefix.length + 1;
31688
- }
31689
- async function assertNodeSupportsNamedKeys(host, fetchImpl = fetch) {
31690
- let info = null;
31691
- try {
31692
- const response = await fetchImpl(`${host}/info`);
31693
- if (response.ok) info = await response.json();
31694
- } catch {
31695
- info = null;
31696
- }
31697
- if (!nodeSupportsNamedKeys(info)) {
31698
- throw new CLIError(
31699
- "NAMED_KEYS_UNAVAILABLE",
31700
- `${host} doesn't support named keys, so \`--keys\` can't be used there. Nothing was signed.`,
31701
- ExitCode.NODE_ERROR
31702
- );
31703
- }
31704
- }
31705
- function namedKeysGrantIn(siwe, keys) {
31706
- let recap = {};
31707
- try {
31708
- recap = siwe === void 0 ? {} : extractRecapAttenuations(siwe);
31709
- } catch {
31710
- return "none";
31711
- }
31712
- const row = recap[keys.resource] ?? {};
31713
- const wanted = keyAbilitiesFor(keys)[keys.resource];
31714
- const granted = wanted.filter((ability) => ability in row);
31715
- if (granted.length === 0) return "none";
31716
- return granted.length === wanted.length && Object.keys(row).length === wanted.length ? "both" : "partial";
31717
- }
31718
- function withVerifiedNamedKeys(profile, session) {
31719
- if (profile.namedKeys === void 0) return profile;
31720
- const siwe = typeof session === "object" && session !== null ? session.siwe : void 0;
31721
- if (typeof siwe === "string" && namedKeysGrantIn(siwe, profile.namedKeys) === "both") return profile;
31722
- const { namedKeys: _dropped, ...rest } = profile;
31723
- return rest;
31724
- }
31725
-
31726
- // src/auth/login-commit.ts
31727
31821
  init_constants();
31728
31822
  init_profiles();
31729
31823
  init_storage();
@@ -31731,14 +31825,14 @@ init_errors();
31731
31825
  init_space();
31732
31826
  import { join as join13 } from "path";
31733
31827
  import { REPLICA_LOGOUT_PENDING_FILE as REPLICA_LOGOUT_PENDING_FILE3 } from "@tinycloud/node-sdk";
31734
- import { CLI_NAME as CLI_NAME12 } from "@tinycloud/operations/cli-name";
31828
+ import { CLI_NAME as CLI_NAME13 } from "@tinycloud/operations/cli-name";
31735
31829
  import { isLocalOwnerProfile as isLocalOwnerProfile3, ownerSessionLacksProof as ownerSessionLacksProof3 } from "@tinycloud/operations/profile";
31736
31830
  import { ProfileLockTimeoutError as ProfileLockTimeoutError2, profilePath as profilePath7 } from "@tinycloud/operations/state";
31737
31831
  function assertNotLocalOwner(profileName2, profile, flow) {
31738
31832
  if (profile === null || !isLocalOwnerProfile3(profile)) return;
31739
31833
  throw new CLIError(
31740
31834
  "LOCAL_OWNER_PROFILE",
31741
- `Profile "${profileName2}" holds a local owner key. ${flow} would turn it into an OpenKey profile while keeping that key. Use a separate profile: \`${CLI_NAME12} init --name publisher --key-only\`, then \`${CLI_NAME12} --profile publisher auth login --device --manifest ...\`.`,
31835
+ `Profile "${profileName2}" holds a local owner key. ${flow} would turn it into an OpenKey profile while keeping that key. Use a separate profile: \`${CLI_NAME13} init --name publisher --key-only\`, then \`${CLI_NAME13} --profile publisher auth login --device --manifest ...\`.`,
31742
31836
  ExitCode.USAGE_ERROR
31743
31837
  );
31744
31838
  }
@@ -31773,7 +31867,7 @@ function assertSessionReplaceable(profileName2, snapshot, ownerDid, scope, newEx
31773
31867
  if (problem !== void 0) {
31774
31868
  throw new CLIError(
31775
31869
  "PROFILE_STATE_INCONSISTENT",
31776
- `Profile "${profileName2}" is inconsistent (${problem}), possibly from an interrupted write. Nothing was saved. Check \`${CLI_NAME12} --profile ${profileName2} context\`, then pass --replace-session to replace this state, or use a new profile.`,
31870
+ `Profile "${profileName2}" is inconsistent (${problem}), possibly from an interrupted write. Nothing was saved. Check \`${CLI_NAME13} --profile ${profileName2} context\`, then pass --replace-session to replace this state, or use a new profile.`,
31777
31871
  ExitCode.ERROR
31778
31872
  );
31779
31873
  }
@@ -31789,7 +31883,7 @@ function assertSessionReplaceable(profileName2, snapshot, ownerDid, scope, newEx
31789
31883
  const space = typeof session.spaceId === "string" ? session.spaceId : "an unknown space";
31790
31884
  throw new CLIError(
31791
31885
  "SESSION_IN_USE",
31792
- `Profile "${profileName2}" has a live session for ${space}${expiresAt ? ` until ${expiresAt}` : ""} that this login would ${keepsScope ? "shorten" : "narrow or replace"}, dropping that authority. Nothing was saved. Keep the user's existing profiles: use a new profile name (\`${CLI_NAME12} init --name publisher --key-only\`, then \`${CLI_NAME12} --profile publisher auth login --device --manifest ...\`), or pass --replace-session to replace this session.`,
31886
+ `Profile "${profileName2}" has a live session for ${space}${expiresAt ? ` until ${expiresAt}` : ""} that this login would ${keepsScope ? "shorten" : "narrow or replace"}, dropping that authority. Nothing was saved. Keep the user's existing profiles: use a new profile name (\`${CLI_NAME13} init --name publisher --key-only\`, then \`${CLI_NAME13} --profile publisher auth login --device --manifest ...\`), or pass --replace-session to replace this session.`,
31793
31887
  ExitCode.USAGE_ERROR
31794
31888
  );
31795
31889
  }
@@ -31823,7 +31917,7 @@ async function commitLogin(profileName2, snapshot, commit) {
31823
31917
  if (canonicalJson(current.profile) !== canonicalJson(snapshot.profile) || canonicalJson(current.key) !== canonicalJson(snapshot.key) || canonicalJson(current.session) !== canonicalJson(snapshot.session)) {
31824
31918
  throw new CLIError(
31825
31919
  "PROFILE_CHANGED_DURING_LOGIN",
31826
- `Profile "${profileName2}" changed while this login was in progress (another login, key rotation or logout). Nothing was saved; check \`${CLI_NAME12} --profile ${profileName2} context\` and run the login again if it is still needed.`,
31920
+ `Profile "${profileName2}" changed while this login was in progress (another login, key rotation or logout). Nothing was saved; check \`${CLI_NAME13} --profile ${profileName2} context\` and run the login again if it is still needed.`,
31827
31921
  ExitCode.ERROR
31828
31922
  );
31829
31923
  }
@@ -31856,7 +31950,7 @@ async function commitLogin(profileName2, snapshot, commit) {
31856
31950
  if (failures.length === 0) throw error;
31857
31951
  throw new CLIError(
31858
31952
  "PROFILE_STATE_INCONSISTENT",
31859
- `Saving the login for profile "${profileName2}" failed (${errorMessage(error)}), and restoring its previous state failed too (${failures.map(errorMessage).join("; ")}). The profile's key, session and settings may not match. Check \`${CLI_NAME12} --profile ${profileName2} context\`, then run the login again with --replace-session, or use a new profile.`,
31953
+ `Saving the login for profile "${profileName2}" failed (${errorMessage(error)}), and restoring its previous state failed too (${failures.map(errorMessage).join("; ")}). The profile's key, session and settings may not match. Check \`${CLI_NAME13} --profile ${profileName2} context\`, then run the login again with --replace-session, or use a new profile.`,
31860
31954
  ExitCode.ERROR
31861
31955
  );
31862
31956
  }
@@ -31872,7 +31966,7 @@ async function commitLogin(profileName2, snapshot, commit) {
31872
31966
 
31873
31967
  // src/auth/owner-key.ts
31874
31968
  init_theme();
31875
- import { CLI_NAME as CLI_NAME13 } from "@tinycloud/operations/cli-name";
31969
+ import { CLI_NAME as CLI_NAME14 } from "@tinycloud/operations/cli-name";
31876
31970
  function openKeyPrimaryFlag(delegation) {
31877
31971
  return typeof delegation.primary === "boolean" ? delegation.primary : void 0;
31878
31972
  }
@@ -31883,7 +31977,7 @@ function withOwnerKeyPrimary(profile, primary) {
31883
31977
  function nonPrimaryKeyWarning(ownerDid) {
31884
31978
  const address = ownerDid.split(":")[4] ?? ownerDid;
31885
31979
  return `${theme.warn("Warning:")} you signed in with OpenKey key ${address} (${ownerDid}), which is not your account's primary OpenKey key. This key is a separate owner with its own spaces and data, so this profile does not see the data stored under your primary key.
31886
- To use your primary key, log in again and choose the primary key in OpenKey, for example into a new profile: ${CLI_NAME13} init --name <profile>
31980
+ To use your primary key, log in again and choose the primary key in OpenKey, for example into a new profile: ${CLI_NAME14} init --name <profile>
31887
31981
  `;
31888
31982
  }
31889
31983
  function warnIfNotPrimaryKey(ownerDid, primary) {
@@ -31892,7 +31986,7 @@ function warnIfNotPrimaryKey(ownerDid, primary) {
31892
31986
  }
31893
31987
  function announceFirstOwner(profileName2, expectedOwner, ownerDid) {
31894
31988
  if (expectedOwner !== void 0 || ownerDid === void 0) return;
31895
- process.stderr.write(`Profile "${profileName2}" now belongs to ${ownerDid}. If that isn't your OpenKey account, stop using this profile and create a new one with ${CLI_NAME13} init.
31989
+ process.stderr.write(`Profile "${profileName2}" now belongs to ${ownerDid}. If that isn't your OpenKey account, stop using this profile and create a new one with ${CLI_NAME14} init.
31896
31990
  `);
31897
31991
  }
31898
31992
  function ownerLoginPermissions(owner) {
@@ -32723,7 +32817,7 @@ init_types();
32723
32817
  init_constants();
32724
32818
  init_permissions();
32725
32819
  init_sdk();
32726
- import { CLI_NAME as CLI_NAME15 } from "@tinycloud/operations/cli-name";
32820
+ import { CLI_NAME as CLI_NAME16 } from "@tinycloud/operations/cli-name";
32727
32821
  import { TinyCloudNode as TinyCloudNode2 } from "@tinycloud/node-sdk";
32728
32822
  import { DelegationRequestBindingSchema, PermissionEntrySchema } from "@tinycloud/operations/artifacts";
32729
32823
  import {
@@ -32738,7 +32832,7 @@ import {
32738
32832
  // src/lib/revoke-delegation.ts
32739
32833
  init_constants();
32740
32834
  init_errors();
32741
- import { CLI_NAME as CLI_NAME14 } from "@tinycloud/operations/cli-name";
32835
+ import { CLI_NAME as CLI_NAME15 } from "@tinycloud/operations/cli-name";
32742
32836
  import { parseSignedCompactUcanAttenuation as parseSignedCompactUcanAttenuation3 } from "@tinycloud/sdk-core";
32743
32837
  import { inspectStoredDelegation, withoutBearer as withoutBearer3 } from "@tinycloud/operations/delegation-binding";
32744
32838
  init_permissions();
@@ -32964,7 +33058,8 @@ async function revokeDelegationOnNode(params) {
32964
33058
  throw new CLIError(
32965
33059
  "RAW_RECAP_RESOURCE_UNSUPPORTED",
32966
33060
  `Cannot revoke "${cid}": this CLI profile has no record of this grant and the node's record does not match the signed-in owner.`,
32967
- ExitCode.PERMISSION_DENIED
33061
+ ExitCode.PERMISSION_DENIED,
33062
+ profile.authMethod === "openkey" ? { hint: "This permission can't be revoked from the CLI yet. Revoke it at https://account.tinycloud.xyz/delegations." } : void 0
32968
33063
  );
32969
33064
  }
32970
33065
  fallbackSpaceId = fallback.space;
@@ -33002,7 +33097,7 @@ async function revokeDelegationOnNode(params) {
33002
33097
  "REVOKE_UNAUTHORIZED",
33003
33098
  "The node rejected this revocation: Unauthorized Revoker.",
33004
33099
  ExitCode.PERMISSION_DENIED,
33005
- { status: 403, hint: `Use a different profile belonging to the delegation's grantor, its recipient, or the owner of a space it covers; the rejected profile was "${ctx.profile}" (DID ${profileDid}). Retry with \`${CLI_NAME14} --profile <profile-name> delegation revoke ${cid}\`.` }
33100
+ { status: 403, hint: `Use a different profile belonging to the delegation's grantor, its recipient, or the owner of a space it covers; the rejected profile was "${ctx.profile}" (DID ${profileDid}). Retry with \`${CLI_NAME15} --profile <profile-name> delegation revoke ${cid}\`.` }
33006
33101
  );
33007
33102
  }
33008
33103
  throw cliErrorFromService(result.error);
@@ -33085,7 +33180,7 @@ function renderList(result) {
33085
33180
  }
33086
33181
  process.stdout.write(`Profile: ${result.profile}
33087
33182
  `);
33088
- process.stdout.write(`Session CID: ${result.session?.cid ?? "unavailable"}` + (result.session?.cid ? ` (current session; revoke with ${CLI_NAME15} delegation revoke ${result.session.cid})
33183
+ process.stdout.write(`Session CID: ${result.session?.cid ?? "unavailable"}` + (result.session?.cid ? ` (current session; revoke with ${CLI_NAME16} delegation revoke ${result.session.cid})
33089
33184
  ` : "\n"));
33090
33185
  process.stdout.write(`Local verification: ${result.localVerification}; node validation and revocation are not checked.
33091
33186
  `);
@@ -33183,7 +33278,7 @@ function registerAuthGrantsCommand(auth, options = {}) {
33183
33278
  ExitCode.ERROR,
33184
33279
  {
33185
33280
  causeCode: cause.code,
33186
- hint: `Do not use --revoke again. After fixing local profile storage, run: ${CLI_NAME15} --profile ${ctx.profile} auth grants remove ${cid}`
33281
+ hint: `Do not use --revoke again. After fixing local profile storage, run: ${CLI_NAME16} --profile ${ctx.profile} auth grants remove ${cid}`
33187
33282
  }
33188
33283
  );
33189
33284
  }
@@ -33248,7 +33343,7 @@ init_safe_text();
33248
33343
  init_raw_encryption();
33249
33344
  init_space();
33250
33345
  import { DEFAULT_EXPIRY as DEFAULT_EXPIRY2, parseExpiry as parseExpiry3, SERVICE_LONG_TO_SHORT as SERVICE_LONG_TO_SHORT3, VAULT_PERMISSION_SERVICE as VAULT_PERMISSION_SERVICE2 } from "@tinycloud/sdk-core";
33251
- import { CLI_NAME as CLI_NAME16 } from "@tinycloud/operations/cli-name";
33346
+ import { CLI_NAME as CLI_NAME17 } from "@tinycloud/operations/cli-name";
33252
33347
  var APPS_AGENT_PRESET = "apps-agent";
33253
33348
  var APPS_AGENT_SPACE = "applications";
33254
33349
  var APPS_AGENT_EXPIRY_DAYS = 7;
@@ -33330,7 +33425,7 @@ function lifetimePhrase(ms2) {
33330
33425
  return `${hours} hour${hours === 1 ? "" : "s"}`;
33331
33426
  }
33332
33427
  function revokeSentence(lifetimeMs2) {
33333
- return `It lasts up to ${lifetimePhrase(lifetimeMs2)}; revoke it any time with \`${CLI_NAME16} delegation revoke <id>\`.`;
33428
+ return `It lasts up to ${lifetimePhrase(lifetimeMs2)}; revoke it any time with \`${CLI_NAME17} delegation revoke <id>\`.`;
33334
33429
  }
33335
33430
  function appsAgentScopeNotice(mode, appIds = []) {
33336
33431
  const variant = APPS_AGENT_VARIANTS[mode];
@@ -33525,7 +33620,7 @@ function describeGrantScope(requested, requestedExpiry, applicationsSpace, maxLi
33525
33620
  const durations = [lifetimeMs(requestedExpiry), ...entryLifetimes].filter((ms2) => ms2 !== void 0).map((ms2) => Math.min(ms2, maxLifetimeMs ?? Number.POSITIVE_INFINITY));
33526
33621
  const longest = Math.max(...durations);
33527
33622
  lines.push(
33528
- durations.length === 0 || !Number.isFinite(longest) ? "Its lifetime could not be read from the request." : `It lasts up to ${lifetimePhrase(longest)}${requestedExpiry === void 0 ? " (no lifetime was requested; this is the default)" : ""}; revoke it any time with \`${CLI_NAME16} delegation revoke <id>\`.`
33623
+ durations.length === 0 || !Number.isFinite(longest) ? "Its lifetime could not be read from the request." : `It lasts up to ${lifetimePhrase(longest)}${requestedExpiry === void 0 ? " (no lifetime was requested; this is the default)" : ""}; revoke it any time with \`${CLI_NAME17} delegation revoke <id>\`.`
33529
33624
  );
33530
33625
  return { text: lines.join("\n"), entries: requested, extras };
33531
33626
  }
@@ -33789,7 +33884,7 @@ async function prepareProfileRemoteLogout(ctx, expectedSessionCid, discardLocalC
33789
33884
  "REVOCATION_AUTHORITY_UNAVAILABLE",
33790
33885
  "OpenKey owner CID revocation requires a verified signed revoke-control grant or wallet proof. No CID was revoked; session, key and replica files remain for retry, while local replica reads stay denied.",
33791
33886
  ExitCode.AUTH_REQUIRED,
33792
- { hint: "Obtain verified owner revoke authority before retrying `auth logout --revoke`, or use plain `auth logout` to end this local session without node revocation." }
33887
+ { hint: `This sign-in can't be revoked from the CLI yet. Revoke it at https://account.tinycloud.xyz/delegations. To sign out on this computer only, run \`${CLI_NAME18} auth logout\`.` }
33793
33888
  );
33794
33889
  }
33795
33890
  const signerDid = addressToDID(
@@ -33833,7 +33928,7 @@ async function revokeForLogout(ctx, discardLocalChanges) {
33833
33928
  "REVOCATION_FAILED",
33834
33929
  `Revocation failed for ${cid}: ${result.error.message}. Session, key, and replica files are retained for retry; local reads stay denied.`,
33835
33930
  ExitCode.ERROR,
33836
- { cid, hint: `Retry \`${CLI_NAME17} auth logout --revoke\` when the node is available.` }
33931
+ { cid, hint: `Retry \`${CLI_NAME18} auth logout --revoke\` when the node is available.` }
33837
33932
  );
33838
33933
  }
33839
33934
  if (result.data.cid !== cid || result.data.revoked !== true) {
@@ -33852,7 +33947,7 @@ async function revokeForLogout(ctx, discardLocalChanges) {
33852
33947
  "REVOCATION_FAILED",
33853
33948
  `Revocation failed before local logout: ${error instanceof Error ? error.message : String(error)}. Session, key, and replica files are retained for retry; local reads stay denied.`,
33854
33949
  ExitCode.ERROR,
33855
- { hint: `Retry \`${CLI_NAME17} auth logout --revoke\` when online authority is available.` }
33950
+ { hint: `Retry \`${CLI_NAME18} auth logout --revoke\` when online authority is available.` }
33856
33951
  );
33857
33952
  }
33858
33953
  }
@@ -34051,7 +34146,7 @@ async function finishProfileLogout(ctx, options = {}) {
34051
34146
  function registerAuthCommand(program, options) {
34052
34147
  const auth = program.command("auth").description("Authentication management");
34053
34148
  registerAuthGrantsCommand(auth, { openKeyAcquisition: options?.openKeyAcquisition });
34054
- auth.command("login").description("Authenticate with TinyCloud").option("--device", "Approve on another device (e.g. a phone) through OpenKey device authorization; requires a KV-scoped --manifest (SQL and secret decrypt need browser or --paste login)").option("--paste", "Wait up to 5 minutes for a signed code from OpenKey on stdin instead of a browser callback (Safari may require paste)").option("--no-popup", "Print the OpenKey URL without opening a browser").option("--method <method>", "Authentication method: local or openkey").option("--manifest <fileOrBase64>", `Request only this manifest's permissions (one space, plus raw encryption network entries such as a secrets decrypt grant); ${SHARE_PUBLISHING_MANIFEST_REF} covers \`${CLI_NAME17} share publish\``).option("--expiry <duration>", "OpenKey session lifetime, e.g. 1h or 7d (device login: at most 30d, default 30d)").option("--owner <did>", "Sign in as this owner (did:pkh:eip155:CHAIN:ADDRESS): OpenKey preselects that key, e.g. one that is not the account's primary key, and an approval by any other identity is refused. With --manifest, also names the secrets owner for a manifest's `secrets` on a profile with no recorded owner").option("--replace-session", "Scoped or device login: replace this profile's live session even though the new scope would narrow, change or shorten it (prefer a new profile)").option("--force", "Replica-enabled OpenKey login: replace the stored session key even when recorded shares could not be checked or hold unexpired shares that need the current key").option("--keys <folder>", "Also let this sign-in create and use encryption keys in this folder (for example `qa`). The node must support named keys. With OpenKey, the first time on a profile also needs `--owner`.").option("--replication-prefix <prefix>", "Add replication get+sync authority for a folder prefix ending in /, or root (repeatable)", (prefix, values = []) => [...values, prefix]).option("--replication-allow-secrets", "Allow replication of secret material (network-wide decrypt warning)").action(async (options2, cmd) => {
34149
+ auth.command("login").description("Authenticate with TinyCloud").option("--device", "Approve on another device (e.g. a phone) through OpenKey device authorization; requires a KV-scoped --manifest (SQL and secret decrypt need browser or --paste login)").option("--paste", "Wait up to 5 minutes for a signed code from OpenKey on stdin instead of a browser callback (Safari may require paste)").option("--no-popup", "Print the OpenKey URL without opening a browser").option("--method <method>", "Authentication method: local or openkey").option("--manifest <fileOrBase64>", `Request only this manifest's permissions (one space, plus raw encryption network entries such as a secrets decrypt grant); ${SHARE_PUBLISHING_MANIFEST_REF} covers \`${CLI_NAME18} share publish\``).option("--expiry <duration>", "OpenKey session lifetime, e.g. 1h or 7d (device login: at most 30d, default 30d)").option("--owner <did>", "Sign in as this owner (did:pkh:eip155:CHAIN:ADDRESS): OpenKey preselects that key, e.g. one that is not the account's primary key, and an approval by any other identity is refused. With --manifest, also names the secrets owner for a manifest's `secrets` on a profile with no recorded owner").option("--replace-session", "Scoped or device login: replace this profile's live session even though the new scope would narrow, change or shorten it (prefer a new profile)").option("--force", "Replica-enabled OpenKey login: replace the stored session key even when recorded shares could not be checked or hold unexpired shares that need the current key").option("--keys <folder>", "Also let this sign-in create and use encryption keys in this folder (for example `qa`). The node must support named keys. With OpenKey, the first time on a profile also needs `--owner`.").option("--replication-prefix <prefix>", "Add replication get+sync authority for a folder prefix ending in /, or root (repeatable)", (prefix, values = []) => [...values, prefix]).option("--replication-allow-secrets", "Allow replication of secret material (network-wide decrypt warning)").action(async (options2, cmd) => {
34055
34150
  try {
34056
34151
  if (options2.device && options2.paste) {
34057
34152
  throw new CLIError("INVALID_ARGUMENT", "--device and --paste are mutually exclusive.", ExitCode.USAGE_ERROR);
@@ -34209,7 +34304,7 @@ function registerAuthCommand(program, options) {
34209
34304
  authenticated: false,
34210
34305
  replicasRemoved,
34211
34306
  replicasKept: options2.keepReplicas === true,
34212
- warning: online ? "The node confirmed revocation of the listed CIDs before local logout." : options2.keepReplicas ? "Legacy replica files remain on disk; this local logout clears the session but does not revoke node grants. Purge the files before a future sign-in if they must not be reused." : `Logout is local; delegations and grants on the node stay valid until they expire. Use \`${CLI_NAME17} delegation revoke <cid>\` to end access now.`,
34307
+ warning: online ? "The node confirmed revocation of the listed CIDs before local logout." : options2.keepReplicas ? "Legacy replica files remain on disk; this local logout clears the session but does not revoke node grants. Purge the files before a future sign-in if they must not be reused." : `Logout is local; delegations and grants on the node stay valid until they expire. Use \`${CLI_NAME18} delegation revoke <cid>\` to end access now.`,
34213
34308
  ...online === void 0 ? {} : {
34214
34309
  revokedCids: online.plan.cids,
34215
34310
  revocationExcluded: online.plan.excluded
@@ -34354,7 +34449,7 @@ ${appsAgentScopeNotice(preset.mode, preset.appIds)}
34354
34449
  if (profile.authMethod === "openkey" || options2.device) {
34355
34450
  const key = await ProfileManager.getKey(ctx.profile);
34356
34451
  if (!key) {
34357
- throw new CLIError("NO_KEY", `No key found for profile "${ctx.profile}". Run \`${CLI_NAME17} --profile ${ctx.profile} auth rotate\` to create a new key and sign in.`, ExitCode.AUTH_REQUIRED);
34452
+ throw new CLIError("NO_KEY", `No key found for profile "${ctx.profile}". Run \`${CLI_NAME18} --profile ${ctx.profile} auth rotate\` to create a new key and sign in.`, ExitCode.AUTH_REQUIRED);
34358
34453
  }
34359
34454
  const openkeyHost = resolveOpenKeyHost(profile);
34360
34455
  const grants = [];
@@ -34374,7 +34469,7 @@ ${appsAgentScopeNotice(preset.mode, preset.appIds)}
34374
34469
  if (notices) process.stderr.write(`${notices.prompts[index]}
34375
34470
  `);
34376
34471
  const reason = permissionGrantReason(
34377
- `Grant requested TinyCloud permissions from \`${CLI_NAME17} auth request --grant\`.`,
34472
+ `Grant requested TinyCloud permissions from \`${CLI_NAME18} auth request --grant\`.`,
34378
34473
  group
34379
34474
  );
34380
34475
  const request = grantRequestPermissions(group, profile.spaceId ?? profile.spaceName);
@@ -34484,7 +34579,7 @@ ${appsAgentScopeNotice(preset.mode, preset.appIds)}
34484
34579
  kind: parsed.kind,
34485
34580
  requestId: parsed.requestId,
34486
34581
  requested: parsed.requested,
34487
- next: `${CLI_NAME17} auth retry ${parsed.requestId}`
34582
+ next: `${CLI_NAME18} auth retry ${parsed.requestId}`
34488
34583
  });
34489
34584
  return;
34490
34585
  }
@@ -35093,7 +35188,7 @@ async function acquireOpenKeyApproval(params) {
35093
35188
  if (!key) {
35094
35189
  throw new CLIError(
35095
35190
  "NO_KEY",
35096
- `No key found for profile "${params.ctx.profile}". Run \`${CLI_NAME17} --profile ${params.ctx.profile} auth rotate\` to create a new key and sign in.`,
35191
+ `No key found for profile "${params.ctx.profile}". Run \`${CLI_NAME18} --profile ${params.ctx.profile} auth rotate\` to create a new key and sign in.`,
35097
35192
  ExitCode.AUTH_REQUIRED
35098
35193
  );
35099
35194
  }
@@ -35166,7 +35261,7 @@ async function ensureDelegationAuthority(params) {
35166
35261
  if (!key) {
35167
35262
  throw new CLIError(
35168
35263
  "NO_KEY",
35169
- `No key found for profile "${params.ctx.profile}". Run \`${CLI_NAME17} --profile ${params.ctx.profile} auth rotate\` to create a new key and sign in.`,
35264
+ `No key found for profile "${params.ctx.profile}". Run \`${CLI_NAME18} --profile ${params.ctx.profile} auth rotate\` to create a new key and sign in.`,
35170
35265
  ExitCode.AUTH_REQUIRED
35171
35266
  );
35172
35267
  }
@@ -35420,7 +35515,7 @@ async function rotateAuthKey(profileName2, host, options = {}) {
35420
35515
  if (!profile.privateKey) {
35421
35516
  throw new CLIError(
35422
35517
  "LOCAL_OWNER_KEY_REQUIRED",
35423
- `Profile "${profileName2}" does not have a local owner private key. Run \`${CLI_NAME17} auth login --method local\` first.`,
35518
+ `Profile "${profileName2}" does not have a local owner private key. Run \`${CLI_NAME18} auth login --method local\` first.`,
35424
35519
  ExitCode.AUTH_REQUIRED
35425
35520
  );
35426
35521
  }
@@ -35678,6 +35773,8 @@ async function refreshPreparedOpenKeySession(params) {
35678
35773
  if (expectedOwner && normalizePkhIdentifier(owner) !== normalizePkhIdentifier(expectedOwner)) {
35679
35774
  throw new CLIError("OPENKEY_OWNER_MISMATCH", "The verified owner differs from this profile. No approval was started.", ExitCode.PERMISSION_DENIED);
35680
35775
  }
35776
+ const namedKeys = options.keys === void 0 ? void 0 : parseKeysPrefix(options.keys, canonicalOwnerDid(owner));
35777
+ if (namedKeys !== void 0) await assertNodeSupportsNamedKeys(host);
35681
35778
  if (options.force !== true) await assertShareHistoryAllowsKeyDrop(profileName2, "login");
35682
35779
  const replicationConfig = createCliReplicationConfig(profileName2, replication, { debug: false, quiet: true }, true);
35683
35780
  if (!replicationConfig) throw new CLIError("V2_REQUIRED", "Replica configuration is unavailable; no approval was started.", ExitCode.AUTH_REQUIRED);
@@ -35698,12 +35795,15 @@ async function refreshPreparedOpenKeySession(params) {
35698
35795
  capabilityRequest,
35699
35796
  includeAccountRegistryPermissions: false,
35700
35797
  autoBootstrapAccount: false,
35701
- replication: replicationConfig
35798
+ replication: replicationConfig,
35799
+ ...namedKeys === void 0 ? {} : { additionalRawAbilities: keyAbilitiesFor(namedKeys) }
35702
35800
  });
35703
35801
  let handoff;
35704
35802
  let preparedDid;
35803
+ let handedBackSiwe;
35705
35804
  const gateWasActive = await readGateSnapshot(profilePath8(profileName2)).then((gate) => gate !== void 0 && gate.principalHash !== null, () => true);
35706
35805
  try {
35806
+ if (namedKeys !== void 0) process.stderr.write(KEYS_APPROVAL_NOTE(namedKeys.prefix));
35707
35807
  await node.signInWithOpenKey(async (prepared) => {
35708
35808
  preparedDid = prepared.verificationMethod;
35709
35809
  handoff = await startPreparedOpenKeyHandoff({
@@ -35712,14 +35812,23 @@ async function refreshPreparedOpenKeySession(params) {
35712
35812
  noPopup: options.noPopup,
35713
35813
  paste: options.paste
35714
35814
  });
35715
- return handoff.result;
35815
+ return handoff.result.then((result) => {
35816
+ handedBackSiwe = result.signedMessage;
35817
+ return result;
35818
+ });
35716
35819
  });
35717
35820
  const active = node.restorableSession;
35718
35821
  if (!active || !preparedDid || node.sessionDid !== preparedDid || active.verificationMethod !== preparedDid) {
35719
35822
  throw new CLIError("OPENKEY_SESSION_MISMATCH", "OpenKey signed a different SDK session key; no profile or session was saved.", ExitCode.PERMISSION_DENIED);
35720
35823
  }
35721
35824
  const key = active.jwk;
35722
- const verified = verifyScopedLogin({ ...active }, key, preparedDid, permissions, { expectedOwner: owner, expiry });
35825
+ const verified = verifyScopedLogin({ ...active }, key, preparedDid, permissions, {
35826
+ expectedOwner: owner,
35827
+ expiry,
35828
+ ...namedKeys === void 0 ? {} : { namedKeyRow: namedKeys }
35829
+ });
35830
+ const grant = namedKeys === void 0 ? "none" : namedKeysGrantIn(active.siwe, namedKeys);
35831
+ if (grant === "partial") throw partialKeysGrantError();
35723
35832
  const session = {
35724
35833
  ...verified.session,
35725
35834
  ...node.replicaStamp ? { replicaStamp: node.replicaStamp } : {},
@@ -35740,6 +35849,10 @@ async function refreshPreparedOpenKeySession(params) {
35740
35849
  ...replication.allowSecrets === true ? { allowSecrets: true } : {}
35741
35850
  }
35742
35851
  };
35852
+ if (namedKeys !== void 0) {
35853
+ delete updatedProfile.namedKeys;
35854
+ if (grant === "both") updatedProfile.namedKeys = namedKeys;
35855
+ }
35743
35856
  await commitLogin(profileName2, snapshot, {
35744
35857
  key,
35745
35858
  session,
@@ -35749,6 +35862,10 @@ async function refreshPreparedOpenKeySession(params) {
35749
35862
  });
35750
35863
  await handoff?.complete(true);
35751
35864
  announceFirstOwner(profileName2, expectedOwner, owner);
35865
+ if (namedKeys !== void 0) {
35866
+ if (grant === "both") await readyNamedKeysNetwork(node);
35867
+ else process.stderr.write(keysNotGrantedAfterLogin(profileName2));
35868
+ }
35752
35869
  return {
35753
35870
  profile: updatedProfile,
35754
35871
  delegationData: session,
@@ -35758,14 +35875,14 @@ async function refreshPreparedOpenKeySession(params) {
35758
35875
  } catch (error) {
35759
35876
  await handoff?.complete(false);
35760
35877
  await undoPreparedAttempt(profileName2, node, owner, gateWasActive).catch(() => void 0);
35761
- throw error;
35878
+ throw namedKeys !== void 0 && isNarrowedRecapMismatch(error) && namedKeysGrantIn(handedBackSiwe, namedKeys) === "partial" ? partialKeysGrantError() : error;
35762
35879
  }
35763
35880
  }
35764
35881
  var KEYS_APPROVAL_NOTE = (folder) => `On the OpenKey page, the row that mentions tinycloud.encryption lets this profile create and use keys in the folder "${folder}/". Keep every row ticked. To sign in without keys, untick that whole row and nothing else; any other change makes OpenKey refuse the sign-in.
35765
35882
  `;
35766
- var keysNotGrantedAfterLogin = (profileName2) => `You're signed in, but without named keys: the tinycloud.encryption permission wasn't approved. To add them, sign in again with \`${CLI_NAME17} --profile ${profileName2} auth login --keys <folder>\` and leave the row that mentions tinycloud.encryption ticked.
35883
+ var keysNotGrantedAfterLogin = (profileName2) => `You're signed in, but without named keys: the tinycloud.encryption permission wasn't approved. To add them, sign in again with \`${CLI_NAME18} --profile ${profileName2} auth login --keys <folder>\` and leave the row that mentions tinycloud.encryption ticked.
35767
35884
  `;
35768
- var KEYS_FOLDER_NOT_READY = `You're signed in, but your encryption keys aren't set up yet. Try \`${CLI_NAME17} keys ensure <folder>/<name>\` again in a minute.
35885
+ var KEYS_FOLDER_NOT_READY = `You're signed in, but your encryption keys aren't set up yet. Try \`${CLI_NAME18} keys ensure <folder>/<name>\` again in a minute.
35769
35886
  `;
35770
35887
  async function readyNamedKeysNetwork(node) {
35771
35888
  try {
@@ -35792,7 +35909,7 @@ async function refreshPreparedKeysSession(params) {
35792
35909
  if (expectedOwner && normalizePkhIdentifier(owner) !== normalizePkhIdentifier(expectedOwner)) {
35793
35910
  throw new CLIError("OPENKEY_OWNER_MISMATCH", "The verified owner differs from this profile. No approval was started.", ExitCode.PERMISSION_DENIED);
35794
35911
  }
35795
- const namedKeys = parseKeysPrefix(options.keys, owner);
35912
+ const namedKeys = parseKeysPrefix(options.keys, canonicalOwnerDid(owner));
35796
35913
  await assertNodeSupportsNamedKeys(host);
35797
35914
  if (options.force !== true) await assertShareHistoryAllowsKeyDrop(profileName2, "login");
35798
35915
  const node = new TinyCloudNode3({
@@ -35805,6 +35922,7 @@ async function refreshPreparedKeysSession(params) {
35805
35922
  });
35806
35923
  let handoff;
35807
35924
  let preparedDid;
35925
+ let handedBackSiwe;
35808
35926
  try {
35809
35927
  process.stderr.write(KEYS_APPROVAL_NOTE(namedKeys.prefix));
35810
35928
  await node.signInWithOpenKey(async (prepared) => {
@@ -35815,7 +35933,10 @@ async function refreshPreparedKeysSession(params) {
35815
35933
  noPopup: options.noPopup,
35816
35934
  paste: options.paste
35817
35935
  });
35818
- return handoff.result;
35936
+ return handoff.result.then((result) => {
35937
+ handedBackSiwe = result.signedMessage;
35938
+ return result;
35939
+ });
35819
35940
  });
35820
35941
  const active = node.restorableSession;
35821
35942
  if (!active || !preparedDid || node.sessionDid !== preparedDid || active.verificationMethod !== preparedDid) {
@@ -35849,16 +35970,9 @@ async function refreshPreparedKeysSession(params) {
35849
35970
  } catch (error) {
35850
35971
  await handoff?.complete(false);
35851
35972
  await node.replication?.close();
35852
- throw isNarrowedRecapMismatch(error) ? partialKeysGrantError() : error;
35973
+ throw isNarrowedRecapMismatch(error) && namedKeysGrantIn(handedBackSiwe, namedKeys) === "partial" ? partialKeysGrantError() : error;
35853
35974
  }
35854
35975
  }
35855
- function partialKeysGrantError() {
35856
- return new CLIError(
35857
- "KEYS_PARTIAL_GRANT",
35858
- "Named keys need both permissions (create and use), but only one was approved, so this sign-in wasn't saved. Sign in again and approve both.",
35859
- ExitCode.PERMISSION_DENIED
35860
- );
35861
- }
35862
35976
  async function undoPreparedAttempt(profileName2, node, owner, gateWasActive) {
35863
35977
  if (!gateWasActive) {
35864
35978
  const undone = await ProfileManager.withLock(profileName2, async () => {
@@ -35897,14 +36011,7 @@ async function refreshOpenKeySession(profileName2, host, options = {}) {
35897
36011
  const estimatedExpiry = expiry === void 0 ? void 0 : new Date(Date.now() + expiry.durationMs).toISOString();
35898
36012
  assertSessionReplaceable(profileName2, snapshot, expectedOwner, permissions, estimatedExpiry);
35899
36013
  }
35900
- if (options.keys !== void 0) {
35901
- if (replication.prefixes.length > 0) {
35902
- throw new CLIError(
35903
- "INVALID_ARGUMENT",
35904
- `With OpenKey sign-in, \`--keys\` can't be used on a profile that has replication prefixes (given with \`--replication-prefix\` or saved from an earlier sign-in). Nothing was started. Use a separate profile for named keys: \`${CLI_NAME17} --profile <name> auth login --owner did:pkh:eip155:<chain>:<address> --keys <folder>\`.`,
35905
- ExitCode.USAGE_ERROR
35906
- );
35907
- }
36014
+ if (options.keys !== void 0 && replication.prefixes.length === 0) {
35908
36015
  return refreshPreparedKeysSession({ profileName: profileName2, host, profile, snapshot, options, expectedOwner, expiry });
35909
36016
  }
35910
36017
  if (replication.prefixes.length > 0) {
@@ -35982,7 +36089,7 @@ async function refreshOpenKeySession(profileName2, host, options = {}) {
35982
36089
  }
35983
36090
 
35984
36091
  // src/commands/completion.ts
35985
- import { CLI_NAME as CLI_NAME18 } from "@tinycloud/operations/cli-name";
36092
+ import { CLI_NAME as CLI_NAME19 } from "@tinycloud/operations/cli-name";
35986
36093
  function registerCompletionCommand(program) {
35987
36094
  const completion = program.command("completion").description("Generate shell completions");
35988
36095
  completion.command("bash").description("Output bash completions").action(() => {
@@ -35999,7 +36106,7 @@ function registerCompletionCommand(program) {
35999
36106
  });
36000
36107
  }
36001
36108
  function generateBashCompletion() {
36002
- return `# ${CLI_NAME18} bash completion
36109
+ return `# ${CLI_NAME19} bash completion
36003
36110
  _tc_completions() {
36004
36111
  local cur prev commands subcommands
36005
36112
  COMPREPLY=()
@@ -36024,11 +36131,11 @@ _tc_completions() {
36024
36131
  COMPREPLY=( $(compgen -W "\${subcommands}" -- "\${cur}") )
36025
36132
  fi
36026
36133
  }
36027
- complete -F _tc_completions ${CLI_NAME18}
36134
+ complete -F _tc_completions ${CLI_NAME19}
36028
36135
  `;
36029
36136
  }
36030
36137
  function generateZshCompletion() {
36031
- return `#compdef ${CLI_NAME18}
36138
+ return `#compdef ${CLI_NAME19}
36032
36139
 
36033
36140
  _tc() {
36034
36141
  local -a commands
@@ -36079,50 +36186,50 @@ _tc
36079
36186
  `;
36080
36187
  }
36081
36188
  function generateFishCompletion() {
36082
- return `# ${CLI_NAME18} fish completion
36189
+ return `# ${CLI_NAME19} fish completion
36083
36190
  set -l commands init auth kv space delegation share node profile completion
36084
36191
 
36085
36192
  # Disable file completion by default
36086
- complete -c ${CLI_NAME18} -f
36193
+ complete -c ${CLI_NAME19} -f
36087
36194
 
36088
36195
  # Top-level commands
36089
- complete -c ${CLI_NAME18} -n "not __fish_seen_subcommand_from $commands" -a init -d "Initialize a new TinyCloud profile"
36090
- complete -c ${CLI_NAME18} -n "not __fish_seen_subcommand_from $commands" -a auth -d "Authentication management"
36091
- complete -c ${CLI_NAME18} -n "not __fish_seen_subcommand_from $commands" -a kv -d "Key-value store operations"
36092
- complete -c ${CLI_NAME18} -n "not __fish_seen_subcommand_from $commands" -a space -d "Space management"
36093
- complete -c ${CLI_NAME18} -n "not __fish_seen_subcommand_from $commands" -a delegation -d "Manage delegations"
36094
- complete -c ${CLI_NAME18} -n "not __fish_seen_subcommand_from $commands" -a share -d "Share data with others"
36095
- complete -c ${CLI_NAME18} -n "not __fish_seen_subcommand_from $commands" -a node -d "Node health and info"
36096
- complete -c ${CLI_NAME18} -n "not __fish_seen_subcommand_from $commands" -a profile -d "Profile management"
36097
- complete -c ${CLI_NAME18} -n "not __fish_seen_subcommand_from $commands" -a completion -d "Generate shell completions"
36196
+ complete -c ${CLI_NAME19} -n "not __fish_seen_subcommand_from $commands" -a init -d "Initialize a new TinyCloud profile"
36197
+ complete -c ${CLI_NAME19} -n "not __fish_seen_subcommand_from $commands" -a auth -d "Authentication management"
36198
+ complete -c ${CLI_NAME19} -n "not __fish_seen_subcommand_from $commands" -a kv -d "Key-value store operations"
36199
+ complete -c ${CLI_NAME19} -n "not __fish_seen_subcommand_from $commands" -a space -d "Space management"
36200
+ complete -c ${CLI_NAME19} -n "not __fish_seen_subcommand_from $commands" -a delegation -d "Manage delegations"
36201
+ complete -c ${CLI_NAME19} -n "not __fish_seen_subcommand_from $commands" -a share -d "Share data with others"
36202
+ complete -c ${CLI_NAME19} -n "not __fish_seen_subcommand_from $commands" -a node -d "Node health and info"
36203
+ complete -c ${CLI_NAME19} -n "not __fish_seen_subcommand_from $commands" -a profile -d "Profile management"
36204
+ complete -c ${CLI_NAME19} -n "not __fish_seen_subcommand_from $commands" -a completion -d "Generate shell completions"
36098
36205
 
36099
36206
  # Subcommands
36100
- complete -c ${CLI_NAME18} -n "__fish_seen_subcommand_from auth" -a "login logout rotate status whoami"
36101
- complete -c ${CLI_NAME18} -n "__fish_seen_subcommand_from kv" -a "get put delete list head"
36102
- complete -c ${CLI_NAME18} -n "__fish_seen_subcommand_from space" -a "list create info switch"
36103
- complete -c ${CLI_NAME18} -n "__fish_seen_subcommand_from delegation" -a "create list info revoke"
36104
- complete -c ${CLI_NAME18} -n "__fish_seen_subcommand_from share" -a "create receive list revoke"
36105
- complete -c ${CLI_NAME18} -n "__fish_seen_subcommand_from node" -a "health version status"
36106
- complete -c ${CLI_NAME18} -n "__fish_seen_subcommand_from profile" -a "list create show switch delete"
36107
- complete -c ${CLI_NAME18} -n "__fish_seen_subcommand_from completion" -a "bash zsh fish"
36207
+ complete -c ${CLI_NAME19} -n "__fish_seen_subcommand_from auth" -a "login logout rotate status whoami"
36208
+ complete -c ${CLI_NAME19} -n "__fish_seen_subcommand_from kv" -a "get put delete list head"
36209
+ complete -c ${CLI_NAME19} -n "__fish_seen_subcommand_from space" -a "list create info switch"
36210
+ complete -c ${CLI_NAME19} -n "__fish_seen_subcommand_from delegation" -a "create list info revoke"
36211
+ complete -c ${CLI_NAME19} -n "__fish_seen_subcommand_from share" -a "create receive list revoke"
36212
+ complete -c ${CLI_NAME19} -n "__fish_seen_subcommand_from node" -a "health version status"
36213
+ complete -c ${CLI_NAME19} -n "__fish_seen_subcommand_from profile" -a "list create show switch delete"
36214
+ complete -c ${CLI_NAME19} -n "__fish_seen_subcommand_from completion" -a "bash zsh fish"
36108
36215
 
36109
36216
  # Global options
36110
- complete -c ${CLI_NAME18} -l profile -s p -d "Profile to use"
36111
- complete -c ${CLI_NAME18} -l host -s H -d "TinyCloud node URL"
36112
- complete -c ${CLI_NAME18} -l verbose -s v -d "Enable verbose output"
36113
- complete -c ${CLI_NAME18} -l no-cache -d "Disable caching"
36114
- complete -c ${CLI_NAME18} -l quiet -s q -d "Suppress non-essential output"
36115
- complete -c ${CLI_NAME18} -l replication -d "Enable read-through replication"
36116
- complete -c ${CLI_NAME18} -l no-replication -d "Disable replication"
36117
- complete -c ${CLI_NAME18} -l replication-debug -d "Write replication diagnostics to stderr"
36217
+ complete -c ${CLI_NAME19} -l profile -s p -d "Profile to use"
36218
+ complete -c ${CLI_NAME19} -l host -s H -d "TinyCloud node URL"
36219
+ complete -c ${CLI_NAME19} -l verbose -s v -d "Enable verbose output"
36220
+ complete -c ${CLI_NAME19} -l no-cache -d "Disable caching"
36221
+ complete -c ${CLI_NAME19} -l quiet -s q -d "Suppress non-essential output"
36222
+ complete -c ${CLI_NAME19} -l replication -d "Enable read-through replication"
36223
+ complete -c ${CLI_NAME19} -l no-replication -d "Disable replication"
36224
+ complete -c ${CLI_NAME19} -l replication-debug -d "Write replication diagnostics to stderr"
36118
36225
  `;
36119
36226
  }
36120
36227
 
36121
36228
  // src/commands/courier.ts
36122
- import { CLI_NAME as CLI_NAME19 } from "@tinycloud/operations/cli-name";
36123
- var TC_OPTIONS_BEFORE_COURIER = `${CLI_NAME19} courier takes no ${CLI_NAME19} options and does not use ${CLI_NAME19} profiles; Courier keeps its own profile in $COURIER_HOME (default ~/.courier). Put flags after "courier", e.g. ${CLI_NAME19} courier init --host <url>.`;
36229
+ import { CLI_NAME as CLI_NAME20 } from "@tinycloud/operations/cli-name";
36230
+ var TC_OPTIONS_BEFORE_COURIER = `${CLI_NAME20} courier takes no ${CLI_NAME20} options and does not use ${CLI_NAME20} profiles; Courier keeps its own profile in $COURIER_HOME (default ~/.courier). Put flags after "courier", e.g. ${CLI_NAME20} courier init --host <url>.`;
36124
36231
  function registerCourierCommand(program) {
36125
- const courier = program.command("courier").description(`Agent-to-agent threads over TinyCloud; run \`${CLI_NAME19} courier --help\` (beta)`).argument("[args...]").allowUnknownOption().helpOption(false);
36232
+ const courier = program.command("courier").description(`Agent-to-agent threads over TinyCloud; run \`${CLI_NAME20} courier --help\` (beta)`).argument("[args...]").allowUnknownOption().helpOption(false);
36126
36233
  courier.action(() => courier.error(TC_OPTIONS_BEFORE_COURIER, { exitCode: 2, code: "tc.courier.options" }));
36127
36234
  }
36128
36235
 
@@ -36167,7 +36274,7 @@ init_formatter();
36167
36274
  init_errors();
36168
36275
  init_constants();
36169
36276
  init_sdk();
36170
- import { CLI_NAME as CLI_NAME20 } from "@tinycloud/operations/cli-name";
36277
+ import { CLI_NAME as CLI_NAME21 } from "@tinycloud/operations/cli-name";
36171
36278
  import { PermissionNotInManifestError } from "@tinycloud/node-sdk";
36172
36279
  async function queryDelegations(node, options, until) {
36173
36280
  const found = [];
@@ -36178,7 +36285,7 @@ async function queryDelegations(node, options, until) {
36178
36285
  if (page.error.code === "PERMISSION_DENIED") {
36179
36286
  throw new CLIError(
36180
36287
  "PERMISSION_DENIED",
36181
- `This session cannot read delegations: it lacks tinycloud.delegation/list. Run \`${CLI_NAME20} auth login\` again to grant it.`,
36288
+ `This session cannot read delegations: it lacks tinycloud.delegation/list. Run \`${CLI_NAME21} auth login\` again to grant it.`,
36182
36289
  ExitCode.PERMISSION_DENIED
36183
36290
  );
36184
36291
  }
@@ -36353,7 +36460,7 @@ init_constants();
36353
36460
  init_formatter();
36354
36461
  init_theme();
36355
36462
  init_errors();
36356
- import { CLI_NAME as CLI_NAME21 } from "@tinycloud/operations/cli-name";
36463
+ import { CLI_NAME as CLI_NAME22 } from "@tinycloud/operations/cli-name";
36357
36464
  init_session_expired();
36358
36465
  init_types();
36359
36466
  function registerDoctorCommand(program) {
@@ -36387,7 +36494,7 @@ function registerDoctorCommand(program) {
36387
36494
  const profile = await ProfileManager.getProfile(profileName2);
36388
36495
  keyDetail = profile.did ? `${profile.did.slice(0, 20)}...` : "key found";
36389
36496
  } else {
36390
- keyDetail = `no key \u2014 run ${CLI_NAME21} init`;
36497
+ keyDetail = `no key \u2014 run ${CLI_NAME22} init`;
36391
36498
  }
36392
36499
  } catch {
36393
36500
  keyDetail = "error reading key";
@@ -36409,7 +36516,7 @@ function registerDoctorCommand(program) {
36409
36516
  } else if (state2 === "unverified") {
36410
36517
  sessionDetail = `not usable (no signed OpenKey proof) \u2014 ${signInAgainHint(profileName2, resolveProfilePosture(profile))}`;
36411
36518
  } else {
36412
- sessionDetail = sessionOk ? "active" : `no session \u2014 run ${CLI_NAME21} auth login`;
36519
+ sessionDetail = sessionOk ? "active" : `no session \u2014 run ${CLI_NAME22} auth login`;
36413
36520
  }
36414
36521
  } catch {
36415
36522
  sessionDetail = "error reading session";
@@ -36437,7 +36544,7 @@ function registerDoctorCommand(program) {
36437
36544
  try {
36438
36545
  const profile = await ProfileManager.getProfile(profileName2);
36439
36546
  spaceOk = Boolean(profile.spaceId);
36440
- spaceDetail = spaceOk ? `${profile.spaceId.slice(0, 16)}...` : `no space \u2014 run ${CLI_NAME21} space create`;
36547
+ spaceDetail = spaceOk ? `${profile.spaceId.slice(0, 16)}...` : `no space \u2014 run ${CLI_NAME22} space create`;
36441
36548
  } catch {
36442
36549
  spaceDetail = "error checking space";
36443
36550
  }
@@ -36627,7 +36734,7 @@ ${rowCount} row${rowCount === 1 ? "" : "s"} returned`) + "\n");
36627
36734
  }
36628
36735
 
36629
36736
  // src/commands/enable.ts
36630
- import { CLI_NAME as CLI_NAME22 } from "@tinycloud/operations/cli-name";
36737
+ import { CLI_NAME as CLI_NAME23 } from "@tinycloud/operations/cli-name";
36631
36738
  init_constants();
36632
36739
  init_profiles();
36633
36740
  init_errors();
@@ -36635,7 +36742,7 @@ init_formatter();
36635
36742
  init_publishing_manifest();
36636
36743
  function registerEnableCommand(program) {
36637
36744
  const enable = program.command("enable").description("Enable a narrowly scoped TinyCloud service");
36638
- enable.command("share").description(`Approve Share publishing scope through OpenKey device authorization (same as \`${CLI_NAME22} auth login --device --manifest builtin:share-publishing\`). Use a dedicated profile, e.g. \`${CLI_NAME22} init --name publisher --key-only && ${CLI_NAME22} --profile publisher enable share\`; keep existing profiles for their apps.`).option("--replace-session", "Replace this profile's live session even though the new scope would narrow, change or shorten it (prefer a new profile)").action(async (options, command) => {
36745
+ enable.command("share").description(`Approve Share publishing scope through OpenKey device authorization (same as \`${CLI_NAME23} auth login --device --manifest builtin:share-publishing\`). Use a dedicated profile, e.g. \`${CLI_NAME23} init --name publisher --key-only && ${CLI_NAME23} --profile publisher enable share\`; keep existing profiles for their apps.`).option("--replace-session", "Replace this profile's live session even though the new scope would narrow, change or shorten it (prefer a new profile)").action(async (options, command) => {
36639
36746
  try {
36640
36747
  const globalOptions = command.optsWithGlobals();
36641
36748
  const context = await ProfileManager.resolveContext(globalOptions, { storedSession: "unused" });
@@ -36670,7 +36777,7 @@ function registerEnableCommand(program) {
36670
36777
  init_profiles();
36671
36778
  init_constants();
36672
36779
  init_sdk();
36673
- import { CLI_NAME as CLI_NAME23 } from "@tinycloud/operations/cli-name";
36780
+ import { CLI_NAME as CLI_NAME24 } from "@tinycloud/operations/cli-name";
36674
36781
  import { operationSpaceResolver as operationSpaceResolver2 } from "@tinycloud/operations/delegation-binding";
36675
36782
 
36676
36783
  // src/lib/knowledge-input.ts
@@ -36936,7 +37043,7 @@ function registerAppsCommand(program) {
36936
37043
  "after",
36937
37044
  `
36938
37045
  Examples:
36939
- $ ${CLI_NAME23} apps install xyz.tinycloud.sample.healthlog
37046
+ $ ${CLI_NAME24} apps install xyz.tinycloud.sample.healthlog
36940
37047
  `
36941
37048
  ).action(async (appIdInput, _options, cmd) => {
36942
37049
  try {
@@ -37028,7 +37135,7 @@ init_profiles();
37028
37135
  init_formatter();
37029
37136
  init_errors();
37030
37137
  init_constants();
37031
- import { CLI_NAME as CLI_NAME24 } from "@tinycloud/operations/cli-name";
37138
+ import { CLI_NAME as CLI_NAME25 } from "@tinycloud/operations/cli-name";
37032
37139
  function registerInitCommand(program) {
37033
37140
  program.command("init").description("Initialize a new TinyCloud profile").option("--name <profile>", "Profile name", "default").option("--key-only", "Only generate key, skip authentication").option("--host <url>", "TinyCloud node URL").option("--paste", "Use manual paste mode for authentication").option("--no-popup", "Print the OpenKey URL without opening a browser").option("--default-space <name>", "Default space used when --space is omitted (e.g. applications)").action(async (options, cmd) => {
37034
37141
  try {
@@ -37046,7 +37153,7 @@ function registerInitCommand(program) {
37046
37153
  if (await ProfileManager.profileExists(profileName2)) {
37047
37154
  throw new CLIError(
37048
37155
  "PROFILE_EXISTS",
37049
- `Profile "${profileName2}" already exists. Use \`${CLI_NAME24} profile delete ${profileName2}\` first or choose a different name.`,
37156
+ `Profile "${profileName2}" already exists. Use \`${CLI_NAME25} profile delete ${profileName2}\` first or choose a different name.`,
37050
37157
  ExitCode.ERROR
37051
37158
  );
37052
37159
  }
@@ -37102,7 +37209,7 @@ init_session_expired();
37102
37209
  init_constants();
37103
37210
  init_profiles();
37104
37211
  import { invokeOperation as invokeOperation2 } from "@tinycloud/operations";
37105
- import { CLI_NAME as CLI_NAME25 } from "@tinycloud/operations/cli-name";
37212
+ import { CLI_NAME as CLI_NAME26 } from "@tinycloud/operations/cli-name";
37106
37213
  init_permissions();
37107
37214
  init_errors();
37108
37215
  init_formatter();
@@ -37205,7 +37312,7 @@ function knowledgeFailure(result, profile, appId) {
37205
37312
  case "error": {
37206
37313
  if (result.error.code === "SESSION_NOT_FOUND" || result.error.code === "PROFILE_POSTURE_NOT_ALLOWED" && result.context.posture === "unauthenticated") {
37207
37314
  return new CLIError("AUTH_REQUIRED", "Not signed in to TinyCloud.", ExitCode.AUTH_REQUIRED, {
37208
- hint: `Sign in with: ${CLI_NAME25} --profile ${result.context.profile} auth login`
37315
+ hint: `Sign in with: ${CLI_NAME26} --profile ${result.context.profile} auth login`
37209
37316
  });
37210
37317
  }
37211
37318
  if (result.error.code === "NODE_UNREACHABLE") {
@@ -37235,7 +37342,7 @@ function knowledgeFailure(result, profile, appId) {
37235
37342
  }
37236
37343
  }
37237
37344
  function missingGrantHint(profile, appId) {
37238
- const request = `${CLI_NAME25} --profile ${profile} auth request`;
37345
+ const request = `${CLI_NAME26} --profile ${profile} auth request`;
37239
37346
  const lines = appId === void 0 ? [
37240
37347
  ` ${request} --preset ${APPS_AGENT_PRESET} --emit req.json (read-only, all installed apps)`,
37241
37348
  ` ${request} --cap tinycloud.kv:${APPS_AGENT_SPACE}::get,list --emit req.json (all files, no table access)`,
@@ -37248,7 +37355,7 @@ function missingGrantHint(profile, appId) {
37248
37355
  return [
37249
37356
  "Ask the owner for a grant, narrowest first. Create the request:",
37250
37357
  ...lines,
37251
- `The owner runs \`${CLI_NAME25} auth grant req.json > grant.json\`; then import the result with \`${CLI_NAME25} --profile ${profile} auth import grant.json\`.`
37358
+ `The owner runs \`${CLI_NAME26} auth grant req.json > grant.json\`; then import the result with \`${CLI_NAME26} --profile ${profile} auth import grant.json\`.`
37252
37359
  ].join("\n");
37253
37360
  }
37254
37361
 
@@ -37397,9 +37504,12 @@ function registerKvCommand(program) {
37397
37504
  return handleError(error);
37398
37505
  }
37399
37506
  });
37400
- kv.command("put <key> [value]").description("Set a value").option("--file <path>", "Read value from file").option("--stdin", "Read value from stdin").option("--space <name|uri>", "Target a non-primary space (short name or full URI)").action(async (key, value, options, cmd) => {
37507
+ kv.command("put <key> [value]").description("Set a value").option("--file <path>", "Read value from file").option("--stdin", "Read value from stdin").option("--if-none-match <value>", "`--if-none-match '*'`: Write only if the key doesn't exist yet; never overwrite").option("--space <name|uri>", "Target a non-primary space (short name or full URI)").action(async (key, value, options, cmd) => {
37401
37508
  try {
37402
37509
  assertAddressableKey(key);
37510
+ if (options.ifNoneMatch !== void 0 && options.ifNoneMatch !== "*") {
37511
+ throw new CLIError("USAGE_ERROR", "`--if-none-match` only accepts `'*'`.", ExitCode.USAGE_ERROR);
37512
+ }
37403
37513
  const globalOpts = cmd.optsWithGlobals();
37404
37514
  const ctx = await ProfileManager.resolveContext(globalOpts);
37405
37515
  const node = await ensureAuthenticated(ctx);
@@ -37423,8 +37533,15 @@ function registerKvCommand(program) {
37423
37533
  }
37424
37534
  }
37425
37535
  const { kv: kv2, spaceUri } = await kvHandle(node, options.space, ctx.profile);
37426
- const result = await withSpinner(`Writing ${key}...`, () => kv2.put(key, putValue));
37536
+ const result = await withSpinner(`Writing ${key}...`, () => kv2.put(
37537
+ key,
37538
+ putValue,
37539
+ options.ifNoneMatch === "*" ? { ifNoneMatch: "*" } : void 0
37540
+ ));
37427
37541
  if (!result.ok) {
37542
+ if (options.ifNoneMatch === "*" && result.error.code === "KV_PRECONDITION_FAILED") {
37543
+ throw new CLIError("KV_ALREADY_EXISTS", `Key "${key}" already exists, so nothing was written.`, ExitCode.PERMISSION_DENIED);
37544
+ }
37428
37545
  await throwKvError(result.error, spaceUri, ctx.profile);
37429
37546
  }
37430
37547
  outputJson({ key, written: true, ...retryFields(result.data?.retry) });
@@ -37555,7 +37672,7 @@ init_errors();
37555
37672
  init_sdk();
37556
37673
  import { readFile as readFile12 } from "fs/promises";
37557
37674
  import { NamedKeyError, createNamedKeyClient } from "@tinycloud/node-sdk/encryption-keys";
37558
- import { CLI_NAME as CLI_NAME26 } from "@tinycloud/operations/cli-name";
37675
+ import { CLI_NAME as CLI_NAME27 } from "@tinycloud/operations/cli-name";
37559
37676
  async function readStdin3() {
37560
37677
  const chunks = [];
37561
37678
  for await (const chunk of process.stdin) {
@@ -37579,7 +37696,7 @@ function cliErrorFromNamedKey(error, command, path) {
37579
37696
  case "KEY_NOT_FOUND":
37580
37697
  return new CLIError(
37581
37698
  "NOT_FOUND",
37582
- command === "decrypt" ? "The key this envelope needs doesn't exist on this node, so it can't be decrypted here." : `There's no key at ${path} yet. Create it with \`${CLI_NAME26} keys ensure ${path}\`.`,
37699
+ command === "decrypt" ? "The key this envelope needs doesn't exist on this node, so it can't be decrypted here." : `There's no key at ${path} yet. Create it with \`${CLI_NAME27} keys ensure ${path}\`.`,
37583
37700
  ExitCode.NOT_FOUND
37584
37701
  );
37585
37702
  case "KEY_BLOCKED":
@@ -37589,7 +37706,7 @@ function cliErrorFromNamedKey(error, command, path) {
37589
37706
  }
37590
37707
  if (error.status === 401 || error.status === 403) {
37591
37708
  return new CLIError("PERMISSION_DENIED", error.message, ExitCode.PERMISSION_DENIED, {
37592
- hint: `Sign in with \`${CLI_NAME26} auth login --keys <folder>\` for this key's folder.`
37709
+ hint: `Sign in with \`${CLI_NAME27} auth login --keys <folder>\` for this key's folder.`
37593
37710
  });
37594
37711
  }
37595
37712
  return new CLIError(error.code, error.message, error.status === void 0 ? ExitCode.ERROR : ExitCode.NODE_ERROR);
@@ -37609,7 +37726,7 @@ async function grantedPrefix(deps, profileName2) {
37609
37726
  if (keys === void 0) {
37610
37727
  throw new CLIError(
37611
37728
  "KEYS_NOT_GRANTED",
37612
- `Profile "${profileName2}" can't use named keys yet. Sign in with \`${CLI_NAME26} --profile ${profileName2} auth login --keys <folder>\`.`,
37729
+ `Profile "${profileName2}" can't use named keys yet. Sign in with \`${CLI_NAME27} --profile ${profileName2} auth login --keys <folder>\`.`,
37613
37730
  ExitCode.PERMISSION_DENIED
37614
37731
  );
37615
37732
  }
@@ -37619,7 +37736,7 @@ function assertInGrantedPrefix(prefix, path, profileName2) {
37619
37736
  if (!keyPathInPrefix(prefix, path)) {
37620
37737
  throw new CLIError(
37621
37738
  "KEY_OUTSIDE_GRANTED_PREFIX",
37622
- `Key "${path}" is outside the folder "${prefix}/" this sign-in covers. To use it, sign in with \`${CLI_NAME26} --profile ${profileName2} auth login --method local --keys <folder>\`.`,
37739
+ `Key "${path}" is outside the folder "${prefix}/" this sign-in covers. To use it, sign in with \`${CLI_NAME27} --profile ${profileName2} auth login --method local --keys <folder>\`.`,
37623
37740
  ExitCode.PERMISSION_DENIED
37624
37741
  );
37625
37742
  }
@@ -37647,7 +37764,7 @@ var defaultDeps = {
37647
37764
  };
37648
37765
  function registerKeysCommand(program, deps = defaultDeps) {
37649
37766
  const { outputJson: outputJson2, handleError: handleError2 } = deps;
37650
- const keys = program.command("keys").description(`Named encryption keys (sign in with \`${CLI_NAME26} auth login --keys <folder>\` first)`);
37767
+ const keys = program.command("keys").description(`Named encryption keys (sign in with \`${CLI_NAME27} auth login --keys <folder>\` first)`);
37651
37768
  keys.command("ensure <path>").description("Create the named key if it doesn't exist yet, then show it").action(async (path, _options, cmd) => {
37652
37769
  try {
37653
37770
  const ctx = await deps.resolveContext(cmd.optsWithGlobals());
@@ -37725,7 +37842,7 @@ init_errors();
37725
37842
  init_constants();
37726
37843
  init_theme();
37727
37844
  import { resolveManifestKnowledgeRoot as resolveManifestKnowledgeRoot2 } from "@tinycloud/sdk-core";
37728
- import { CLI_NAME as CLI_NAME27 } from "@tinycloud/operations/cli-name";
37845
+ import { CLI_NAME as CLI_NAME28 } from "@tinycloud/operations/cli-name";
37729
37846
  import { readFile as readFile13 } from "fs/promises";
37730
37847
  var DEFAULT_APP_SPACE = "applications";
37731
37848
  function registerManifestCommand(program) {
@@ -37733,8 +37850,8 @@ function registerManifestCommand(program) {
37733
37850
  manifest.command("resolve <source>").description("Resolve a manifest file or URL to its effective space, paths, and DB basenames").addHelpText("after", `
37734
37851
 
37735
37852
  Examples:
37736
- $ ${CLI_NAME27} manifest resolve ./manifest.json
37737
- $ ${CLI_NAME27} manifest resolve https://app.example.com/manifest.json --json
37853
+ $ ${CLI_NAME28} manifest resolve ./manifest.json
37854
+ $ ${CLI_NAME28} manifest resolve https://app.example.com/manifest.json --json
37738
37855
 
37739
37856
  What it shows:
37740
37857
  - app_id, name, manifest_version
@@ -37744,7 +37861,7 @@ What it shows:
37744
37861
 
37745
37862
  This command is read-only and does NOT contact the node \u2014 it just resolves
37746
37863
  the manifest against the active profile's address/chain so you know which
37747
- \`--space\` and \`--db\` values to pass to other ${CLI_NAME27} commands.
37864
+ \`--space\` and \`--db\` values to pass to other ${CLI_NAME28} commands.
37748
37865
  `).action(async (source, _options, cmd) => {
37749
37866
  try {
37750
37867
  const globalOpts = cmd.optsWithGlobals();
@@ -37813,7 +37930,7 @@ ${theme.heading("SQL databases")}
37813
37930
  `);
37814
37931
  }
37815
37932
  process.stdout.write(theme.muted(`
37816
- Use with: ${CLI_NAME27} sql query --space ${spaceName} --db <db> "..."
37933
+ Use with: ${CLI_NAME28} sql query --space ${spaceName} --db <db> "..."
37817
37934
  `));
37818
37935
  }
37819
37936
  if (permissions.length > 0) {
@@ -37933,7 +38050,7 @@ init_profiles();
37933
38050
  init_formatter();
37934
38051
  init_errors();
37935
38052
  init_constants();
37936
- import { CLI_NAME as CLI_NAME28 } from "@tinycloud/operations/cli-name";
38053
+ import { CLI_NAME as CLI_NAME29 } from "@tinycloud/operations/cli-name";
37937
38054
  import { createInterface as createInterface5 } from "readline";
37938
38055
  init_theme();
37939
38056
  init_types();
@@ -38079,9 +38196,9 @@ profile at command time. Precedence for every kv/sql command:
38079
38196
  explicit --space flag > profile defaultSpace > primary space.
38080
38197
 
38081
38198
  Examples:
38082
- $ ${CLI_NAME28} profile set-default-space applications
38083
- $ ${CLI_NAME28} profile set-default-space applications --profile cli-test
38084
- $ ${CLI_NAME28} profile set-default-space --unset
38199
+ $ ${CLI_NAME29} profile set-default-space applications
38200
+ $ ${CLI_NAME29} profile set-default-space applications --profile cli-test
38201
+ $ ${CLI_NAME29} profile set-default-space --unset
38085
38202
  `).action(async (name, options, cmd) => {
38086
38203
  try {
38087
38204
  const globalOpts = cmd.optsWithGlobals();
@@ -38093,7 +38210,7 @@ Examples:
38093
38210
  if (!options.unset && (name === void 0 || name === "")) {
38094
38211
  throw new CLIError(
38095
38212
  "USAGE_ERROR",
38096
- `Provide a space name (e.g. \`${CLI_NAME28} profile set-default-space applications\`) or pass --unset.`,
38213
+ `Provide a space name (e.g. \`${CLI_NAME29} profile set-default-space applications\`) or pass --unset.`,
38097
38214
  ExitCode.USAGE_ERROR
38098
38215
  );
38099
38216
  }
@@ -38152,7 +38269,7 @@ function parseOperatorType(raw) {
38152
38269
 
38153
38270
  // src/commands/replica.ts
38154
38271
  init_profile_replicas();
38155
- import { CLI_NAME as CLI_NAME29 } from "@tinycloud/operations/cli-name";
38272
+ import { CLI_NAME as CLI_NAME30 } from "@tinycloud/operations/cli-name";
38156
38273
  import { randomBytes as randomBytes7 } from "crypto";
38157
38274
  import { constants as fsConstants5 } from "fs";
38158
38275
  import { mkdir as mkdir5, open as open11, readdir as readdir6, rename as rename4, unlink as unlink5, lstat as lstat9 } from "fs/promises";
@@ -38293,11 +38410,11 @@ var EXIT_BY_CODE = {
38293
38410
  var READ_FAILURE = {
38294
38411
  absent: { code: "KEY_ABSENT", exit: ExitCode.NOT_FOUND, what: "is not in the replica (the replica covers its whole prefix)" },
38295
38412
  deleted: { code: "KEY_DELETED", exit: ExitCode.NOT_FOUND, what: "was deleted at the source" },
38296
- content_missing: { code: "CONTENT_MISSING", exit: ExitCode.NOT_FOUND, what: `is known but its content has not been fetched yet; run ${CLI_NAME29} replica sync` },
38413
+ content_missing: { code: "CONTENT_MISSING", exit: ExitCode.NOT_FOUND, what: `is known but its content has not been fetched yet; run ${CLI_NAME30} replica sync` },
38297
38414
  coverage_incomplete: {
38298
38415
  code: "COVERAGE_INCOMPLETE",
38299
38416
  exit: ExitCode.NOT_FOUND,
38300
- what: `is not in the replica, which has not finished its first sync; run ${CLI_NAME29} replica sync`
38417
+ what: `is not in the replica, which has not finished its first sync; run ${CLI_NAME30} replica sync`
38301
38418
  },
38302
38419
  not_covered: { code: "NOT_COVERED", exit: ExitCode.USAGE_ERROR, what: "is outside the replica's prefix" }
38303
38420
  };
@@ -38415,7 +38532,7 @@ async function existingReplicaName(profile, option) {
38415
38532
  const names = await replicaNames(profile);
38416
38533
  if (names.length === 1) return names[0];
38417
38534
  if (names.length === 0) {
38418
- throw new ReplicaError(ReplicaErrorCode.NOT_FOUND, `Profile "${profile}" has no replicas. Create one with: ${CLI_NAME29} replica sync --prefix <prefix>`);
38535
+ throw new ReplicaError(ReplicaErrorCode.NOT_FOUND, `Profile "${profile}" has no replicas. Create one with: ${CLI_NAME30} replica sync --prefix <prefix>`);
38419
38536
  }
38420
38537
  throw new CLIError("USAGE_ERROR", `Profile "${profile}" has several replicas (${names.join(", ")}); pass --replica <name>.`, ExitCode.USAGE_ERROR);
38421
38538
  }
@@ -38523,9 +38640,9 @@ async function storedGrants(profile) {
38523
38640
  function grantHint(space, prefix) {
38524
38641
  return [
38525
38642
  "A replica needs a device grant with get and sync on its prefix:",
38526
- ` device: ${CLI_NAME29} auth request --cap tinycloud.kv:${space}:${prefix}:get,list,metadata,sync --expiry 30d --emit req.json`,
38527
- ` owner: ${CLI_NAME29} auth grant req.json > grant.json`,
38528
- ` device: ${CLI_NAME29} auth import grant.json`
38643
+ ` device: ${CLI_NAME30} auth request --cap tinycloud.kv:${space}:${prefix}:get,list,metadata,sync --expiry 30d --emit req.json`,
38644
+ ` owner: ${CLI_NAME30} auth grant req.json > grant.json`,
38645
+ ` device: ${CLI_NAME30} auth import grant.json`
38529
38646
  ].join("\n");
38530
38647
  }
38531
38648
  function resolveSpace(input, prefix, grants) {
@@ -38616,7 +38733,7 @@ function describeStatus(status) {
38616
38733
  formatField("Size", formatBytes(status.bytes)),
38617
38734
  formatField("Grant", status.device.delegationCid),
38618
38735
  formatField("Last error", status.lastError ? `${status.lastError.code}: ${status.lastError.message}` : null),
38619
- formatField("Purge", status.purgePending ? `incomplete: content files remain; the next ${CLI_NAME29} replica command retries` : null)
38736
+ formatField("Purge", status.purgePending ? `incomplete: content files remain; the next ${CLI_NAME30} replica command retries` : null)
38620
38737
  ].join("\n");
38621
38738
  }
38622
38739
  async function writeFileAtomic(path, bytes) {
@@ -38780,7 +38897,7 @@ or coverage incomplete; 5 grant expired, revoked or not yet valid; 6 network;
38780
38897
  (options, cmd) => run(async () => {
38781
38898
  const profile = await profileName(cmd);
38782
38899
  if (options.purge) {
38783
- throw new CLIError("REPORT_READ_ONLY", `Replica report is read-only. To remove a named local replica, run ${CLI_NAME29} replica reset --replica <name> --purge; flag-owned purge has no equivalent yet (TC-1309).`, ExitCode.USAGE_ERROR);
38900
+ throw new CLIError("REPORT_READ_ONLY", `Replica report is read-only. To remove a named local replica, run ${CLI_NAME30} replica reset --replica <name> --purge; flag-owned purge has no equivalent yet (TC-1309).`, ExitCode.USAGE_ERROR);
38784
38901
  }
38785
38902
  if (options.clearPending) {
38786
38903
  throw new CLIError("REPORT_READ_ONLY", "Clearing pending replica writes has no equivalent yet; see TC-1309 for a safe replacement command.", ExitCode.USAGE_ERROR);
@@ -38882,7 +38999,7 @@ or coverage incomplete; 5 grant expired, revoked or not yet valid; 6 network;
38882
38999
  if (differs !== void 0) {
38883
39000
  throw new ReplicaError(
38884
39001
  ReplicaErrorCode.CONFIG_MISMATCH,
38885
- `Replica ${name} already follows ${differs}. Use another --replica name, or ${CLI_NAME29} replica reset --replica ${name} --purge first.`
39002
+ `Replica ${name} already follows ${differs}. Use another --replica name, or ${CLI_NAME30} replica reset --replica ${name} --purge first.`
38886
39003
  );
38887
39004
  }
38888
39005
  }
@@ -39047,7 +39164,7 @@ init_theme();
39047
39164
  init_errors();
39048
39165
  init_warnings();
39049
39166
  init_constants();
39050
- import { CLI_NAME as CLI_NAME31 } from "@tinycloud/operations/cli-name";
39167
+ import { CLI_NAME as CLI_NAME32 } from "@tinycloud/operations/cli-name";
39051
39168
  import { readFile as readFile14 } from "fs/promises";
39052
39169
  import { join as join17 } from "path";
39053
39170
  import { homedir, hostname, userInfo } from "os";
@@ -39258,7 +39375,7 @@ function parseStatusEnvelope(line) {
39258
39375
  // src/lib/secrets-wait.ts
39259
39376
  init_constants();
39260
39377
  init_errors();
39261
- import { CLI_NAME as CLI_NAME30 } from "@tinycloud/operations/cli-name";
39378
+ import { CLI_NAME as CLI_NAME31 } from "@tinycloud/operations/cli-name";
39262
39379
  var WAIT_BACKOFF_MS = [3e3, 4500, 6750, 10125];
39263
39380
  var WAIT_STEADY_INTERVAL_MS = 15e3;
39264
39381
  var DEFAULT_WAIT_TIMEOUT = "5m";
@@ -39266,7 +39383,7 @@ var MAX_WAIT_TIMEOUT_MS = 9 * 6e4;
39266
39383
  var MAX_CONSECUTIVE_FAILURES = 5;
39267
39384
  var TIMEOUT_RE = /^(0|\d+s|\d+m)$/;
39268
39385
  var RETRYABLE_STATUS_ERRORS = { NODE_UNREACHABLE: true, NODE_ERROR: true };
39269
- var EXPLICIT_KEY_HINT = `${CLI_NAME30} secrets wait never signs in, and an explicit private key has no stored session. Use a local profile signed in once with ${CLI_NAME30} --profile <profile> auth login --method local, without TC_PRIVATE_KEY.`;
39386
+ var EXPLICIT_KEY_HINT = `${CLI_NAME31} secrets wait never signs in, and an explicit private key has no stored session. Use a local profile signed in once with ${CLI_NAME31} --profile <profile> auth login --method local, without TC_PRIVATE_KEY.`;
39270
39387
  function abortableSleep(ms2, signal) {
39271
39388
  return new Promise((resolve9) => {
39272
39389
  if (signal.aborted) {
@@ -39302,7 +39419,7 @@ async function runSecretsWait(request, deps) {
39302
39419
  let step = 0;
39303
39420
  const cancelled = () => new CLIError(
39304
39421
  "CANCELLED",
39305
- `Interrupted: ${CLI_NAME30} secrets wait stopped before the secrets were ready.`,
39422
+ `Interrupted: ${CLI_NAME31} secrets wait stopped before the secrets were ready.`,
39306
39423
  ExitCode.CANCELLED,
39307
39424
  progress === void 0 ? {} : { progress }
39308
39425
  );
@@ -39446,13 +39563,13 @@ function statusError(error, profile, warnings) {
39446
39563
  if (error.code === "AUTH_REQUIRED" || error.code === "SESSION_NOT_FOUND") {
39447
39564
  const recovery = recoveryOf(error, "sign_in");
39448
39565
  if (recovery !== void 0) {
39449
- const hint = recovery.command !== void 0 ? `Sign in once with: ${recovery.command}, then retry.` : `Sign in to profile "${profile}" once with ${CLI_NAME30} auth login, then retry.`;
39566
+ const hint = recovery.command !== void 0 ? `Sign in once with: ${recovery.command}, then retry.` : `Sign in to profile "${profile}" once with ${CLI_NAME31} auth login, then retry.`;
39450
39567
  return new CLIError("AUTH_REQUIRED", error.message, ExitCode.AUTH_REQUIRED, { hint, recovery, ...extra });
39451
39568
  }
39452
39569
  }
39453
39570
  if (error.code === "PROFILE_MIGRATION_REQUIRED") {
39454
39571
  const recovery = recoveryOf(error, "complete_migration");
39455
- const hint = recovery?.command !== void 0 ? `Run ${recovery.command} once to finish this profile's one-time upgrade, then retry wait.` : `Run ${CLI_NAME30} auth caps once with profile "${profile}" to finish its one-time upgrade, then retry wait.`;
39572
+ const hint = recovery?.command !== void 0 ? `Run ${recovery.command} once to finish this profile's one-time upgrade, then retry wait.` : `Run ${CLI_NAME31} auth caps once with profile "${profile}" to finish its one-time upgrade, then retry wait.`;
39456
39573
  return new CLIError("PROFILE_MIGRATION_REQUIRED", error.message, ExitCode.ERROR, {
39457
39574
  hint,
39458
39575
  ...recovery === void 0 ? {} : { recovery },
@@ -39644,11 +39761,11 @@ async function runSecretOperation(params) {
39644
39761
  }
39645
39762
  function secretPermissionReason(action, name) {
39646
39763
  const target = name ? ` secret "${name}"` : " secrets";
39647
- return `Allow \`${CLI_NAME31} secrets ${action}${name ? ` ${name}` : ""}\` to access${target} with the required TinyCloud permissions.`;
39764
+ return `Allow \`${CLI_NAME32} secrets ${action}${name ? ` ${name}` : ""}\` to access${target} with the required TinyCloud permissions.`;
39648
39765
  }
39649
39766
  function assertOwnerApprovalPossible(profileName2, profile, action, name, missing = []) {
39650
39767
  if (profile.authMethod !== "openkey" || process.stdin.isTTY || process.stderr.isTTY) return;
39651
- const command = `${CLI_NAME31} secrets ${action === "del" ? "delete" : action}${name ? ` ${name}` : ""}`;
39768
+ const command = `${CLI_NAME32} secrets ${action === "del" ? "delete" : action}${name ? ` ${name}` : ""}`;
39652
39769
  throw new CLIError(
39653
39770
  "PERMISSION_DENIED",
39654
39771
  action === "get" ? missing.length > 0 && missing.every((permission) => permission.service === "tinycloud.encryption" && Array.isArray(permission.actions) && permission.actions.length > 0 && permission.actions.every((capability) => capability === SECRET_DECRYPT_CAPABILITY)) ? `Profile "${profileName2}" lacks the scoped decrypt authority (${SECRET_DECRYPT_CAPABILITY}) needed to read secret "${name}", and requesting it needs an interactive browser approval.` : `Profile "${profileName2}" lacks a scoped read or decrypt grant needed to read secret "${name}", and requesting it needs an interactive browser approval.` : `Profile "${profileName2}" holds no grant for \`${command}\`, and requesting one needs an interactive browser approval.`,
@@ -39659,7 +39776,7 @@ function assertOwnerApprovalPossible(profileName2, profile, action, name, missin
39659
39776
  );
39660
39777
  }
39661
39778
  function scopedSecretLoginHint(profileName2) {
39662
- return `Have the owner approve a scoped login whose manifest names the secret: ${CLI_NAME31} --profile ${profileName2} auth login --method openkey --paste --manifest <manifest.json>. Pass the owner's code on stdin, newline-terminated.`;
39779
+ return `Have the owner approve a scoped login whose manifest names the secret: ${CLI_NAME32} --profile ${profileName2} auth login --method openkey --paste --manifest <manifest.json>. Pass the owner's code on stdin, newline-terminated.`;
39663
39780
  }
39664
39781
  async function runSecretOperationAttempt(label, operation) {
39665
39782
  try {
@@ -39793,7 +39910,7 @@ ${message}`,
39793
39910
  "AUTH_REQUIRED",
39794
39911
  "Not signed in to TinyCloud.",
39795
39912
  ExitCode.AUTH_REQUIRED,
39796
- { hint: `Sign in with: ${CLI_NAME31} --profile ${result.context.profile} auth login` }
39913
+ { hint: `Sign in with: ${CLI_NAME32} --profile ${result.context.profile} auth login` }
39797
39914
  );
39798
39915
  }
39799
39916
  if (result.error.code === "NODE_UNREACHABLE") {
@@ -40270,7 +40387,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
40270
40387
  name: "Encryption network",
40271
40388
  ok: false,
40272
40389
  detail: `${networkName} not found`,
40273
- hint: `${CLI_NAME31} secrets network init ${networkName}`
40390
+ hint: `${CLI_NAME32} secrets network init ${networkName}`
40274
40391
  }
40275
40392
  ];
40276
40393
  let secret;
@@ -40316,7 +40433,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
40316
40433
  name: "Secret access",
40317
40434
  ok: false,
40318
40435
  detail: notFound ? `${resolved.permissionPaths.vault} not found` : result.error.message,
40319
- hint: notFound ? local ? `${CLI_NAME31} secrets put ${resolved.name}${formatSecretScopeFlag(scopeOptions)} --stdin` : `${CLI_NAME31} secrets request ${resolved.name}${formatSecretScopeFlag(scopeOptions)}` : `Ask the owner profile to grant ${secretCapabilityAction("get")} and ${SECRET_DECRYPT_CAPABILITY}.`
40436
+ hint: notFound ? local ? `${CLI_NAME32} secrets put ${resolved.name}${formatSecretScopeFlag(scopeOptions)} --stdin` : `${CLI_NAME32} secrets request ${resolved.name}${formatSecretScopeFlag(scopeOptions)}` : `Ask the owner profile to grant ${secretCapabilityAction("get")} and ${SECRET_DECRYPT_CAPABILITY}.`
40320
40437
  });
40321
40438
  }
40322
40439
  } else {
@@ -40560,7 +40677,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
40560
40677
  if (effectiveLocal(profile, options)) {
40561
40678
  throw new CLIError(
40562
40679
  "LOCAL_OWNER_PROFILE",
40563
- `Profile "${ctx.profile}" uses a local key, which owns its secrets: store them with ${CLI_NAME31} secrets put NAME --stdin instead of requesting them.`,
40680
+ `Profile "${ctx.profile}" uses a local key, which owns its secrets: store them with ${CLI_NAME32} secrets put NAME --stdin instead of requesting them.`,
40564
40681
  ExitCode.USAGE_ERROR
40565
40682
  );
40566
40683
  }
@@ -40683,7 +40800,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
40683
40800
 
40684
40801
  // src/commands/share.ts
40685
40802
  import { Option } from "commander";
40686
- import { CLI_NAME as CLI_NAME32 } from "@tinycloud/operations/cli-name";
40803
+ import { CLI_NAME as CLI_NAME33 } from "@tinycloud/operations/cli-name";
40687
40804
  import { ProfileLockTimeoutError as ProfileLockTimeoutError3 } from "@tinycloud/operations/state";
40688
40805
  init_errors();
40689
40806
 
@@ -42144,7 +42261,7 @@ function shareCliError(error, operation = "publish") {
42144
42261
  "SHARE_HISTORY_UNREADABLE",
42145
42262
  "sender history cannot be read with the current session key",
42146
42263
  1,
42147
- { hint: `Run \`${CLI_NAME32} share history recover\` to move the unreadable file aside. The old file is kept, not deleted.` }
42264
+ { hint: `Run \`${CLI_NAME33} share history recover\` to move the unreadable file aside. The old file is kept, not deleted.` }
42148
42265
  );
42149
42266
  }
42150
42267
  if (error instanceof SharePublishAuthorityError) {
@@ -42152,12 +42269,12 @@ function shareCliError(error, operation = "publish") {
42152
42269
  const profileName2 = "profileName" in failure ? failure.profileName : void 0;
42153
42270
  const localKey = "localKey" in failure && failure.localKey === true;
42154
42271
  const profileHint = profileName2 === void 0 ? "" : `--profile ${profileName2} `;
42155
- const loginHint = localKey ? `\`${CLI_NAME32} ${profileHint}auth login --method local\`` : `\`${CLI_NAME32} ${profileHint}auth login --device --manifest builtin:share-publishing\` (or \`${CLI_NAME32} ${profileHint}enable share\`)`;
42272
+ const loginHint = localKey ? `\`${CLI_NAME33} ${profileHint}auth login --method local\`` : `\`${CLI_NAME33} ${profileHint}auth login --device --manifest builtin:share-publishing\` (or \`${CLI_NAME33} ${profileHint}enable share\`)`;
42156
42273
  if (failure.kind === "caveated-session") {
42157
42274
  const holder = profileName2 === void 0 ? "this session" : `profile ${profileName2}'s session`;
42158
42275
  return new CLIError(
42159
42276
  "PERMISSION_DENIED",
42160
- `${holder} carries signed restrictions (caveats) on the authority an anyone-with-link share needs, so it cannot create the share link; nothing was shared. Approve Share publishing without restrictions on a new, dedicated profile (any unused name): \`${CLI_NAME32} init --name publisher --key-only && ${CLI_NAME32} --profile publisher enable share\``,
42277
+ `${holder} carries signed restrictions (caveats) on the authority an anyone-with-link share needs, so it cannot create the share link; nothing was shared. Approve Share publishing without restrictions on a new, dedicated profile (any unused name): \`${CLI_NAME33} init --name publisher --key-only && ${CLI_NAME33} --profile publisher enable share\``,
42161
42278
  5
42162
42279
  );
42163
42280
  }
@@ -42258,7 +42375,7 @@ function shareCliError(error, operation = "publish") {
42258
42375
  if (nodeCode === "ENOENT") return new CLIError("INVALID_ARGUMENT", "share input was not found", 2);
42259
42376
  if (nodeCode === "EISDIR") return new CLIError("INVALID_ARGUMENT", "share input must be a Markdown file", 2);
42260
42377
  const mapped = typeof nodeCode === "string" && known[nodeCode] !== void 0 ? known[nodeCode] : known[message];
42261
- const classified = mapped === void 0 ? classifyShareFailure(error, `\`${CLI_NAME32} auth login\``) : void 0;
42378
+ const classified = mapped === void 0 ? classifyShareFailure(error, `\`${CLI_NAME33} auth login\``) : void 0;
42262
42379
  if (classified !== void 0) return Object.assign(new CLIError(classified.code, classified.reason, classified.exitCode), { cause: error });
42263
42380
  if (error instanceof TypeError) return new CLIError("INVALID_ARGUMENT", "share input is invalid", 2);
42264
42381
  return new CLIError(mapped?.code ?? "INVALID_ARGUMENT", mapped ? mapped.code : "share operation failed", mapped?.exit ?? 2);
@@ -42668,7 +42785,7 @@ init_constants();
42668
42785
  init_sdk();
42669
42786
  init_host();
42670
42787
  init_theme();
42671
- import { CLI_NAME as CLI_NAME33 } from "@tinycloud/operations/cli-name";
42788
+ import { CLI_NAME as CLI_NAME34 } from "@tinycloud/operations/cli-name";
42672
42789
  import { ACCOUNT_REGISTRY_SPACE } from "@tinycloud/node-sdk";
42673
42790
  import { randomBytes as randomBytes9 } from "crypto";
42674
42791
  import { mkdir as mkdir7, writeFile as writeFile4 } from "fs/promises";
@@ -42715,7 +42832,7 @@ function registerSpaceCommand(program) {
42715
42832
  } else {
42716
42833
  const profile = await ProfileManager.getProfile(ctx.profile);
42717
42834
  const cap = "tinycloud.kv:account:spaces/:get,list";
42718
- const hint = resolveProfilePosture(profile) === "delegate-session" ? `Ask the space owner to grant it. Create the request with: ${CLI_NAME33} --profile ${ctx.profile} auth request --cap ${cap} --emit` : `Grant it to this session with: ${CLI_NAME33} --profile ${ctx.profile} auth request --grant --cap ${cap}. The default OpenKey owner sign-in covers only the default space, so it includes neither.`;
42835
+ const hint = resolveProfilePosture(profile) === "delegate-session" ? `Ask the space owner to grant it. Create the request with: ${CLI_NAME34} --profile ${ctx.profile} auth request --cap ${cap} --emit` : `Grant it to this session with: ${CLI_NAME34} --profile ${ctx.profile} auth request --grant --cap ${cap}. The default OpenKey owner sign-in covers only the default space, so it includes neither.`;
42719
42836
  throw new CLIError(
42720
42837
  "PERMISSION_DENIED",
42721
42838
  `Listing spaces needs tinycloud.kv/get and tinycloud.kv/list on the account space's spaces/ registry (${accountSpaceId ?? ACCOUNT_REGISTRY_SPACE}), or tinycloud.space/list. This session holds neither.`,
@@ -42752,10 +42869,10 @@ function registerSpaceCommand(program) {
42752
42869
 
42753
42870
  A delegate cannot host a space \u2014 only its owner (root authority) can. This
42754
42871
  emits a tinycloud.host.request artifact naming the space and its owner so you
42755
- can hand it to the owner; they run \`${CLI_NAME33} space host <name>\` and confirm.
42872
+ can hand it to the owner; they run \`${CLI_NAME34} space host <name>\` and confirm.
42756
42873
 
42757
42874
  If you ARE the owner of the resolved space, this refuses and tells you to host
42758
- it directly with \`${CLI_NAME33} space host <name>\` (no request needed).
42875
+ it directly with \`${CLI_NAME34} space host <name>\` (no request needed).
42759
42876
  `).action(async (name, options, cmd) => {
42760
42877
  try {
42761
42878
  const globalOpts = cmd.optsWithGlobals();
@@ -42766,7 +42883,7 @@ it directly with \`${CLI_NAME33} space host <name>\` (no request needed).
42766
42883
  if (await isRootAuthority(spaceId, ctx.profile)) {
42767
42884
  throw new CLIError(
42768
42885
  "ALREADY_ROOT_AUTHORITY",
42769
- `You are the owner of ${spaceId}. Host it directly: ${CLI_NAME33} space host ${spaceName}`,
42886
+ `You are the owner of ${spaceId}. Host it directly: ${CLI_NAME34} space host ${spaceName}`,
42770
42887
  ExitCode.USAGE_ERROR
42771
42888
  );
42772
42889
  }
@@ -42852,7 +42969,7 @@ init_sdk();
42852
42969
  init_space();
42853
42970
  init_host();
42854
42971
  init_theme();
42855
- import { CLI_NAME as CLI_NAME34 } from "@tinycloud/operations/cli-name";
42972
+ import { CLI_NAME as CLI_NAME35 } from "@tinycloud/operations/cli-name";
42856
42973
  import { writeFile as writeFile5 } from "fs/promises";
42857
42974
  import { resolve as resolve8 } from "path";
42858
42975
  async function dbHandle(node, dbName, spaceInput, profileName2) {
@@ -42876,12 +42993,12 @@ database for simple apps, or pass --db to target a named database. Pass
42876
42993
  --space to target a non-primary space (e.g. the manifest "applications" space).
42877
42994
 
42878
42995
  Common workflows:
42879
- $ ${CLI_NAME34} sql execute "CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT)"
42880
- $ ${CLI_NAME34} sql execute "INSERT INTO notes (body) VALUES (?)" --params '["ship docs"]'
42881
- $ ${CLI_NAME34} sql query "SELECT id, body FROM notes ORDER BY id"
42882
- $ ${CLI_NAME34} sql query "SELECT * FROM events WHERE type = ?" --db analytics --params '["signup"]'
42883
- $ ${CLI_NAME34} sql query "SELECT count(*) FROM conversation" --space applications --db xyz.tinycloud.listen/conversations
42884
- $ ${CLI_NAME34} sql export --db analytics --output analytics.db
42996
+ $ ${CLI_NAME35} sql execute "CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT)"
42997
+ $ ${CLI_NAME35} sql execute "INSERT INTO notes (body) VALUES (?)" --params '["ship docs"]'
42998
+ $ ${CLI_NAME35} sql query "SELECT id, body FROM notes ORDER BY id"
42999
+ $ ${CLI_NAME35} sql query "SELECT * FROM events WHERE type = ?" --db analytics --params '["signup"]'
43000
+ $ ${CLI_NAME35} sql query "SELECT count(*) FROM conversation" --space applications --db xyz.tinycloud.listen/conversations
43001
+ $ ${CLI_NAME35} sql export --db analytics --output analytics.db
42885
43002
 
42886
43003
  Commands:
42887
43004
  query Read rows with SELECT statements
@@ -42898,10 +43015,10 @@ Tips:
42898
43015
  sql.command("query <sql>").description("Run a read-only SELECT query").option("--db <name>", "SQLite database name within the current space", "default").option("--space <name|uri>", "Target a non-primary space (short name or full URI)").option("--params <json>", "Bind parameters as a JSON array for ? placeholders").addHelpText("after", `
42899
43016
 
42900
43017
  Examples:
42901
- $ ${CLI_NAME34} sql query "SELECT * FROM notes ORDER BY id"
42902
- $ ${CLI_NAME34} sql query "SELECT * FROM notes WHERE id = ?" --params '[42]'
42903
- $ ${CLI_NAME34} sql query "SELECT count(*) AS total FROM events" --db analytics --json
42904
- $ ${CLI_NAME34} sql query "SELECT count(*) FROM conversation" --space applications --db xyz.tinycloud.listen/conversations
43018
+ $ ${CLI_NAME35} sql query "SELECT * FROM notes ORDER BY id"
43019
+ $ ${CLI_NAME35} sql query "SELECT * FROM notes WHERE id = ?" --params '[42]'
43020
+ $ ${CLI_NAME35} sql query "SELECT count(*) AS total FROM events" --db analytics --json
43021
+ $ ${CLI_NAME35} sql query "SELECT count(*) FROM conversation" --space applications --db xyz.tinycloud.listen/conversations
42905
43022
 
42906
43023
  Output:
42907
43024
  Human output is formatted as a table. Piped output or --json returns
@@ -42942,11 +43059,11 @@ ${rowCount} row${rowCount === 1 ? "" : "s"} returned`) + "\n");
42942
43059
  sql.command("execute <sql>").description("Run a write or schema statement").option("--db <name>", "SQLite database name within the current space", "default").option("--space <name|uri>", "Target a non-primary space (short name or full URI)").option("--params <json>", "Bind parameters as a JSON array for ? placeholders").addHelpText("after", `
42943
43060
 
42944
43061
  Examples:
42945
- $ ${CLI_NAME34} sql execute "CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT)"
42946
- $ ${CLI_NAME34} sql execute "INSERT INTO notes (body) VALUES (?)" --params '["first note"]'
42947
- $ ${CLI_NAME34} sql execute "UPDATE notes SET body = ? WHERE id = ?" --params '["edited", 1]'
42948
- $ ${CLI_NAME34} sql execute "DROP TABLE old_notes" --db archive
42949
- $ ${CLI_NAME34} sql execute "DELETE FROM conversation WHERE id = ?" --space applications --db xyz.tinycloud.listen/conversations --params '["abc"]'
43062
+ $ ${CLI_NAME35} sql execute "CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT)"
43063
+ $ ${CLI_NAME35} sql execute "INSERT INTO notes (body) VALUES (?)" --params '["first note"]'
43064
+ $ ${CLI_NAME35} sql execute "UPDATE notes SET body = ? WHERE id = ?" --params '["edited", 1]'
43065
+ $ ${CLI_NAME35} sql execute "DROP TABLE old_notes" --db archive
43066
+ $ ${CLI_NAME35} sql execute "DELETE FROM conversation WHERE id = ?" --space applications --db xyz.tinycloud.listen/conversations --params '["abc"]'
42950
43067
 
42951
43068
  Output:
42952
43069
  Returns JSON with the changed row count and last inserted row id when available.
@@ -42975,9 +43092,9 @@ Output:
42975
43092
  sql.command("export").description("Export a SQLite database as a binary .db file").option("--db <name>", "SQLite database name within the current space", "default").option("--space <name|uri>", "Target a non-primary space (short name or full URI)").option("-o, --output <file>", "Output file path", "export.db").addHelpText("after", `
42976
43093
 
42977
43094
  Examples:
42978
- $ ${CLI_NAME34} sql export
42979
- $ ${CLI_NAME34} sql export --db analytics --output analytics.db
42980
- $ ${CLI_NAME34} sql export --space applications --db xyz.tinycloud.listen/conversations --output listen.db
43095
+ $ ${CLI_NAME35} sql export
43096
+ $ ${CLI_NAME35} sql export --db analytics --output analytics.db
43097
+ $ ${CLI_NAME35} sql export --space applications --db xyz.tinycloud.listen/conversations --output listen.db
42981
43098
 
42982
43099
  Output:
42983
43100
  Writes the database file locally and returns JSON with the path and size.
@@ -43010,19 +43127,19 @@ Output:
43010
43127
  sql.command("copy").description("Copy rows between SQL databases (optionally across spaces)").requiredOption("--from-db <name>", "Source database name").requiredOption("--to-db <name>", "Destination database name").option("--from-space <name|uri>", "Source space (defaults to primary)").option("--to-space <name|uri>", "Destination space (defaults to primary)").option("--table <name...>", "Restrict copy to specific tables (repeat or comma-separated)").option("--dry-run", "Print the plan without writing", false).addHelpText("after", `
43011
43128
 
43012
43129
  Examples:
43013
- $ ${CLI_NAME34} sql copy --from-db com.tinycloud.conversation-sync/conversations \\
43130
+ $ ${CLI_NAME35} sql copy --from-db com.tinycloud.conversation-sync/conversations \\
43014
43131
  --to-db xyz.tinycloud.listen/conversations \\
43015
43132
  --space applications --dry-run
43016
- $ ${CLI_NAME34} sql copy --from-space applications --from-db com.foo/data \\
43133
+ $ ${CLI_NAME35} sql copy --from-space applications --from-db com.foo/data \\
43017
43134
  --to-space applications --to-db com.bar/data \\
43018
43135
  --table conversation --table participant
43019
43136
 
43020
43137
  Notes:
43021
43138
  - Refuses to run when (resolved space, db) is identical for source and destination.
43022
- - Does NOT create destination tables. Run the target app once (or use \`${CLI_NAME34} sql execute\`)
43139
+ - Does NOT create destination tables. Run the target app once (or use \`${CLI_NAME35} sql execute\`)
43023
43140
  to materialize the schema before copying.
43024
43141
  - One row at a time; suitable for small/medium datasets. Large copies should
43025
- use \`${CLI_NAME34} sql export\` + bulk import.
43142
+ use \`${CLI_NAME35} sql export\` + bulk import.
43026
43143
  - Authorization: the active session/delegation must cover sql/read on source
43027
43144
  AND sql/write on destination. Otherwise the relevant operation will fail.
43028
43145
  `).action(async (options, cmd) => {
@@ -43122,7 +43239,7 @@ init_errors();
43122
43239
  init_formatter();
43123
43240
  init_theme();
43124
43241
  init_permissions();
43125
- import { CLI_NAME as CLI_NAME35 } from "@tinycloud/operations/cli-name";
43242
+ import { CLI_NAME as CLI_NAME36 } from "@tinycloud/operations/cli-name";
43126
43243
  import {
43127
43244
  NodeWasmBindings as NodeWasmBindings2
43128
43245
  } from "@tinycloud/node-sdk";
@@ -43396,7 +43513,7 @@ function formatStatus(summary) {
43396
43513
  lines.push(`Default profile: ${theme.value(summary.defaultProfile)}`);
43397
43514
  lines.push("");
43398
43515
  if (summary.profiles.length === 0) {
43399
- lines.push(theme.muted(`No profiles configured. Run: ${CLI_NAME35} init`));
43516
+ lines.push(theme.muted(`No profiles configured. Run: ${CLI_NAME36} init`));
43400
43517
  return `${lines.join("\n")}
43401
43518
  `;
43402
43519
  }