@tinycloud/cli 1.4.0 → 1.5.0-beta.1

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.
@@ -9049,7 +9049,8 @@ async function assertPendingRemoteLogoutSession(name, expected) {
9049
9049
  }
9050
9050
  async function clearProfileCredentials(name, syncDirectory = false, legacyCleanup = false) {
9051
9051
  const profile = await ProfileManager.getProfile(name);
9052
- await ProfileManager.setProfileDurable(name, { ...profile, authSignedOut: true, ...legacyCleanup ? { legacyReplicaCleanup: true } : {} });
9052
+ const { namedKeys: _namedKeys, ...signedOutProfile } = profile;
9053
+ await ProfileManager.setProfileDurable(name, { ...signedOutProfile, authSignedOut: true, ...legacyCleanup ? { legacyReplicaCleanup: true } : {} });
9053
9054
  let failed = false;
9054
9055
  let error;
9055
9056
  try {
@@ -9481,7 +9482,8 @@ async function bootstrapDelegatedSession(ctx, delegation) {
9481
9482
  jwk,
9482
9483
  verificationMethod: sessionDid
9483
9484
  };
9484
- const profile = { ...previousProfile, sessionDid, spaceId: delegation.spaceId };
9485
+ const { namedKeys: _namedKeys, ...previousFields } = previousProfile;
9486
+ const profile = { ...previousFields, sessionDid, spaceId: delegation.spaceId };
9485
9487
  try {
9486
9488
  await ProfileManager.setSession(ctx.profile, session);
9487
9489
  await ProfileManager.setProfile(ctx.profile, profile);
@@ -16454,7 +16456,7 @@ function formatSpace(space) {
16454
16456
  init_profile_gate();
16455
16457
  init_dist();
16456
16458
  init_profiles();
16457
- import { CLI_NAME as CLI_NAME17 } from "@tinycloud/operations/cli-name";
16459
+ import { CLI_NAME as CLI_NAME18 } from "@tinycloud/operations/cli-name";
16458
16460
  import { get as httpGet } from "http";
16459
16461
  import { get as httpsGet } from "https";
16460
16462
  import { spawn } from "child_process";
@@ -30611,7 +30613,7 @@ function createEncryptedProfileHistory(profileName2, sessionSigner) {
30611
30613
  throw new ShareHistoryReadError();
30612
30614
  }
30613
30615
  };
30614
- const parseEnvelope = (text) => {
30616
+ const parseEnvelope2 = (text) => {
30615
30617
  let envelope;
30616
30618
  try {
30617
30619
  envelope = JSON.parse(text);
@@ -30633,7 +30635,7 @@ function createEncryptedProfileHistory(profileName2, sessionSigner) {
30633
30635
  const preparedSalt = async (profile) => {
30634
30636
  const file = await readIfPresent(await path(profile));
30635
30637
  if (file === void 0) return crypto.getRandomValues(new Uint8Array(16));
30636
- const salt = unb64(parseEnvelope(file.toString("utf8")).kdfSalt);
30638
+ const salt = unb64(parseEnvelope2(file.toString("utf8")).kdfSalt);
30637
30639
  if (salt.length < 16) throw new ShareHistoryUnreadableError();
30638
30640
  return salt;
30639
30641
  };
@@ -30644,7 +30646,7 @@ function createEncryptedProfileHistory(profileName2, sessionSigner) {
30644
30646
  if (legacy === void 0 || legacy.length <= 12) return [];
30645
30647
  return decryptRecords(ready.legacyKey, new Uint8Array(legacy.subarray(0, 12)), new Uint8Array(legacy.subarray(12)));
30646
30648
  }
30647
- const envelope = parseEnvelope(file.toString("utf8"));
30649
+ const envelope = parseEnvelope2(file.toString("utf8"));
30648
30650
  if (envelope.kdfSalt !== b64(ready.salt)) throw saltChanged;
30649
30651
  const iv = unb64(envelope.iv);
30650
30652
  const ciphertext = unb64(envelope.ciphertext);
@@ -31281,9 +31283,16 @@ async function localKeySignIn(options) {
31281
31283
  privateKey: options.privateKey,
31282
31284
  host: options.host,
31283
31285
  autoCreateSpace: true,
31286
+ ...options.keyAbilities ? { additionalRawAbilities: options.keyAbilities } : {},
31284
31287
  ...replication ? { replication } : {}
31285
31288
  });
31286
31289
  await node.signIn();
31290
+ let networkReady;
31291
+ if (options.keyAbilities && !node.freshEncryptionNetworkCreated) {
31292
+ networkReady = await node.ensureEncryptionNetwork().then(() => true, () => false);
31293
+ } else if (options.keyAbilities) {
31294
+ networkReady = true;
31295
+ }
31287
31296
  const address = await new PrivateKeySigner(options.privateKey).getAddress();
31288
31297
  const session = node.session;
31289
31298
  if (!session) {
@@ -31299,7 +31308,8 @@ async function localKeySignIn(options) {
31299
31308
  verificationMethod: session.verificationMethod,
31300
31309
  siwe: session.siwe,
31301
31310
  signature: session.signature,
31302
- ...node.replicaStamp === void 0 ? {} : { replicaStamp: node.replicaStamp }
31311
+ ...node.replicaStamp === void 0 ? {} : { replicaStamp: node.replicaStamp },
31312
+ ...networkReady === void 0 ? {} : { networkReady }
31303
31313
  };
31304
31314
  }
31305
31315
 
@@ -31308,6 +31318,7 @@ init_errors();
31308
31318
  init_constants();
31309
31319
  import { NodeWasmBindings, signedCompactUcanExpiry as signedCompactUcanExpiry2 } from "@tinycloud/node-sdk";
31310
31320
  import { isLocalOwnerProfile as isLocalOwnerProfile2, ownerSessionLacksProof as ownerSessionLacksProof2 } from "@tinycloud/operations/profile";
31321
+ import { CLI_NAME as CLI_NAME12 } from "@tinycloud/operations/cli-name";
31311
31322
 
31312
31323
  // src/lib/duration.ts
31313
31324
  function parseDuration(input) {
@@ -31343,6 +31354,87 @@ init_manifest();
31343
31354
  init_raw_encryption();
31344
31355
  init_owner_did();
31345
31356
  init_caveat_refusal();
31357
+
31358
+ // src/lib/named-keys.ts
31359
+ init_constants();
31360
+ init_errors();
31361
+ import { extractRecapAttenuations } from "@tinycloud/sdk-core";
31362
+ import {
31363
+ EncryptionResourceError,
31364
+ KEY_CREATE_ABILITY,
31365
+ formatPrefixResource,
31366
+ nodeSupportsNamedKeys
31367
+ } from "@tinycloud/sdk-services/encryption/resource";
31368
+ import { DECRYPT_ACTION } from "@tinycloud/sdk-services";
31369
+ var NAMED_KEYS_NETWORK = "default";
31370
+ function namedKeysNetworkId(ownerDid) {
31371
+ return `urn:tinycloud:encryption:${ownerDid}:${NAMED_KEYS_NETWORK}`;
31372
+ }
31373
+ function parseKeysPrefix(input, ownerDid) {
31374
+ const prefix = input.endsWith("/") ? input.slice(0, -1) : input;
31375
+ const invalid3 = (reason) => new CLIError("INVALID_KEY_PREFIX", `Invalid \`--keys\` folder "${input}": ${reason}`, ExitCode.USAGE_ERROR);
31376
+ if (prefix.length === 0) throw invalid3("the folder can't be empty.");
31377
+ if (prefix === "apps" || prefix.startsWith("apps/")) {
31378
+ throw invalid3("folders under `apps/` belong to apps and can't be used from the CLI.");
31379
+ }
31380
+ try {
31381
+ return { prefix, resource: formatPrefixResource(namedKeysNetworkId(ownerDid), prefix) };
31382
+ } catch (error) {
31383
+ if (error instanceof EncryptionResourceError) throw invalid3(error.message);
31384
+ throw error;
31385
+ }
31386
+ }
31387
+ function keyAbilitiesFor(keys) {
31388
+ return { [keys.resource]: [KEY_CREATE_ABILITY, DECRYPT_ACTION] };
31389
+ }
31390
+ function keyPathInPrefix(prefix, path) {
31391
+ return path.startsWith(`${prefix}/`) && path.length > prefix.length + 1;
31392
+ }
31393
+ async function assertNodeSupportsNamedKeys(host, fetchImpl = fetch) {
31394
+ let info = null;
31395
+ try {
31396
+ const response = await fetchImpl(`${host}/info`);
31397
+ if (response.ok) info = await response.json();
31398
+ } catch {
31399
+ info = null;
31400
+ }
31401
+ if (!nodeSupportsNamedKeys(info)) {
31402
+ throw new CLIError(
31403
+ "NAMED_KEYS_UNAVAILABLE",
31404
+ `${host} doesn't support named keys, so \`--keys\` can't be used there. Nothing was signed.`,
31405
+ ExitCode.NODE_ERROR
31406
+ );
31407
+ }
31408
+ }
31409
+ function partialKeysGrantError() {
31410
+ return new CLIError(
31411
+ "KEYS_PARTIAL_GRANT",
31412
+ "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.",
31413
+ ExitCode.PERMISSION_DENIED
31414
+ );
31415
+ }
31416
+ function namedKeysGrantIn(siwe, keys) {
31417
+ let recap = {};
31418
+ try {
31419
+ recap = siwe === void 0 ? {} : extractRecapAttenuations(siwe);
31420
+ } catch {
31421
+ return "none";
31422
+ }
31423
+ const row = recap[keys.resource] ?? {};
31424
+ const wanted = keyAbilitiesFor(keys)[keys.resource];
31425
+ const granted = wanted.filter((ability) => ability in row);
31426
+ if (granted.length === 0) return "none";
31427
+ return granted.length === wanted.length && Object.keys(row).length === wanted.length ? "both" : "partial";
31428
+ }
31429
+ function withVerifiedNamedKeys(profile, session) {
31430
+ if (profile.namedKeys === void 0) return profile;
31431
+ const siwe = typeof session === "object" && session !== null ? session.siwe : void 0;
31432
+ if (typeof siwe === "string" && namedKeysGrantIn(siwe, profile.namedKeys) === "both") return profile;
31433
+ const { namedKeys: _dropped, ...rest } = profile;
31434
+ return rest;
31435
+ }
31436
+
31437
+ // src/auth/scoped-login.ts
31346
31438
  var CAPABILITIES_READ = "tinycloud.capabilities/read";
31347
31439
  var CLOCK_SKEW_MS = 3e4;
31348
31440
  var SIGNED_RECAP = "signed-recap";
@@ -31595,6 +31687,35 @@ function verifySignedSession(data, key, sessionDid, expected = {}) {
31595
31687
  }
31596
31688
  return { ownerDid, permissions, expiresAt };
31597
31689
  }
31690
+ function withoutRequestedKeyRow(signed, keys, signerDid) {
31691
+ const mismatch = () => new CLIError(
31692
+ "OPENKEY_SCOPE_MISMATCH",
31693
+ `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>\`.`,
31694
+ ExitCode.PERMISSION_DENIED
31695
+ );
31696
+ let expectedResource;
31697
+ try {
31698
+ expectedResource = parseKeysPrefix(keys.prefix, canonicalOwnerDid(signerDid)).resource;
31699
+ } catch {
31700
+ throw mismatch();
31701
+ }
31702
+ if (keys.resource !== expectedResource) throw mismatch();
31703
+ const rest = [];
31704
+ const actions = /* @__PURE__ */ new Set();
31705
+ for (const permission of signed) {
31706
+ if (!isVerifiedRawEncryptionPermission(permission) || permission.path !== expectedResource) {
31707
+ rest.push(permission);
31708
+ continue;
31709
+ }
31710
+ if (permission.caveats?.some((caveat) => Object.keys(caveat).length > 0)) throw mismatch();
31711
+ for (const action of permission.actions) actions.add(action);
31712
+ }
31713
+ if (actions.size === 0) return rest;
31714
+ const wanted = keyAbilitiesFor(keys)[expectedResource];
31715
+ if ([...actions].some((action) => !wanted.includes(action))) throw mismatch();
31716
+ if (actions.size !== wanted.length) throw partialKeysGrantError();
31717
+ return rest;
31718
+ }
31598
31719
  function verifyScopedLogin(data, key, sessionDid, requested, expected = {}) {
31599
31720
  const notStored = expected.purpose === "grant" ? "No grant was stored." : "No scoped session was saved.";
31600
31721
  let signed;
@@ -31620,10 +31741,11 @@ function verifyScopedLogin(data, key, sessionDid, requested, expected = {}) {
31620
31741
  }
31621
31742
  const legacyNested = [];
31622
31743
  const approved = [];
31623
- for (const permission of signed.permissions) {
31744
+ const keyRowRest = expected.namedKeyRow === void 0 ? signed.permissions : withoutRequestedKeyRow(signed.permissions, expected.namedKeyRow, signed.ownerDid);
31745
+ for (const permission of keyRowRest) {
31624
31746
  (isLegacyNestedDecrypt(permission, requested, signed.ownerDid, spaceId) ? legacyNested : approved).push(permission);
31625
31747
  }
31626
- for (const permission of signed.permissions) {
31748
+ for (const permission of keyRowRest) {
31627
31749
  if (isVerifiedRawEncryptionPermission(permission) && !rawEncryptionOwnerMatches(permission.path, signed.ownerDid)) {
31628
31750
  throw new CLIError("OPENKEY_SCOPE_MISMATCH", `OpenKey signed decrypt for a network not owned by the approving identity. ${notStored}`, ExitCode.PERMISSION_DENIED);
31629
31751
  }
@@ -31639,22 +31761,22 @@ function verifyScopedLogin(data, key, sessionDid, requested, expected = {}) {
31639
31761
  }
31640
31762
 
31641
31763
  // src/auth/login-commit.ts
31764
+ import { lstat as lstat6 } from "fs/promises";
31642
31765
  init_constants();
31643
31766
  init_profiles();
31644
31767
  init_storage();
31645
31768
  init_errors();
31646
31769
  init_space();
31647
- import { lstat as lstat6 } from "fs/promises";
31648
31770
  import { join as join13 } from "path";
31649
31771
  import { REPLICA_LOGOUT_PENDING_FILE as REPLICA_LOGOUT_PENDING_FILE3 } from "@tinycloud/node-sdk";
31650
- import { CLI_NAME as CLI_NAME12 } from "@tinycloud/operations/cli-name";
31772
+ import { CLI_NAME as CLI_NAME13 } from "@tinycloud/operations/cli-name";
31651
31773
  import { isLocalOwnerProfile as isLocalOwnerProfile3, ownerSessionLacksProof as ownerSessionLacksProof3 } from "@tinycloud/operations/profile";
31652
31774
  import { ProfileLockTimeoutError as ProfileLockTimeoutError2, profilePath as profilePath7 } from "@tinycloud/operations/state";
31653
31775
  function assertNotLocalOwner(profileName2, profile, flow) {
31654
31776
  if (profile === null || !isLocalOwnerProfile3(profile)) return;
31655
31777
  throw new CLIError(
31656
31778
  "LOCAL_OWNER_PROFILE",
31657
- `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 ...\`.`,
31779
+ `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 ...\`.`,
31658
31780
  ExitCode.USAGE_ERROR
31659
31781
  );
31660
31782
  }
@@ -31689,7 +31811,7 @@ function assertSessionReplaceable(profileName2, snapshot, ownerDid, scope, newEx
31689
31811
  if (problem !== void 0) {
31690
31812
  throw new CLIError(
31691
31813
  "PROFILE_STATE_INCONSISTENT",
31692
- `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.`,
31814
+ `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.`,
31693
31815
  ExitCode.ERROR
31694
31816
  );
31695
31817
  }
@@ -31705,7 +31827,7 @@ function assertSessionReplaceable(profileName2, snapshot, ownerDid, scope, newEx
31705
31827
  const space = typeof session.spaceId === "string" ? session.spaceId : "an unknown space";
31706
31828
  throw new CLIError(
31707
31829
  "SESSION_IN_USE",
31708
- `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.`,
31830
+ `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.`,
31709
31831
  ExitCode.USAGE_ERROR
31710
31832
  );
31711
31833
  }
@@ -31739,7 +31861,7 @@ async function commitLogin(profileName2, snapshot, commit) {
31739
31861
  if (canonicalJson(current.profile) !== canonicalJson(snapshot.profile) || canonicalJson(current.key) !== canonicalJson(snapshot.key) || canonicalJson(current.session) !== canonicalJson(snapshot.session)) {
31740
31862
  throw new CLIError(
31741
31863
  "PROFILE_CHANGED_DURING_LOGIN",
31742
- `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.`,
31864
+ `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.`,
31743
31865
  ExitCode.ERROR
31744
31866
  );
31745
31867
  }
@@ -31758,20 +31880,21 @@ async function commitLogin(profileName2, snapshot, commit) {
31758
31880
  assertSessionReplaceable(profileName2, current, commit.approved.ownerDid, commit.approved.scope, newExpiresAt);
31759
31881
  }
31760
31882
  try {
31883
+ const profile = withVerifiedNamedKeys(commit.profile, commit.session);
31761
31884
  await ProfileManager.setKey(profileName2, commit.key);
31762
31885
  await ProfileManager.setSession(profileName2, commit.session);
31763
31886
  if (current.profile?.authSignedOut === true || commit.durable === true) {
31764
31887
  await syncAuthCredentialFiles(profilePath7(profileName2));
31765
- await ProfileManager.setProfileDurable(profileName2, { ...commit.profile, authSignedOut: false });
31888
+ await ProfileManager.setProfileDurable(profileName2, { ...profile, authSignedOut: false });
31766
31889
  } else {
31767
- await ProfileManager.setProfile(profileName2, commit.profile);
31890
+ await ProfileManager.setProfile(profileName2, profile);
31768
31891
  }
31769
31892
  } catch (error) {
31770
31893
  const failures = await restore(profileName2, current);
31771
31894
  if (failures.length === 0) throw error;
31772
31895
  throw new CLIError(
31773
31896
  "PROFILE_STATE_INCONSISTENT",
31774
- `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.`,
31897
+ `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.`,
31775
31898
  ExitCode.ERROR
31776
31899
  );
31777
31900
  }
@@ -31787,7 +31910,7 @@ async function commitLogin(profileName2, snapshot, commit) {
31787
31910
 
31788
31911
  // src/auth/owner-key.ts
31789
31912
  init_theme();
31790
- import { CLI_NAME as CLI_NAME13 } from "@tinycloud/operations/cli-name";
31913
+ import { CLI_NAME as CLI_NAME14 } from "@tinycloud/operations/cli-name";
31791
31914
  function openKeyPrimaryFlag(delegation) {
31792
31915
  return typeof delegation.primary === "boolean" ? delegation.primary : void 0;
31793
31916
  }
@@ -31798,7 +31921,7 @@ function withOwnerKeyPrimary(profile, primary) {
31798
31921
  function nonPrimaryKeyWarning(ownerDid) {
31799
31922
  const address = ownerDid.split(":")[4] ?? ownerDid;
31800
31923
  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.
31801
- 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>
31924
+ 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>
31802
31925
  `;
31803
31926
  }
31804
31927
  function warnIfNotPrimaryKey(ownerDid, primary) {
@@ -31807,7 +31930,7 @@ function warnIfNotPrimaryKey(ownerDid, primary) {
31807
31930
  }
31808
31931
  function announceFirstOwner(profileName2, expectedOwner, ownerDid) {
31809
31932
  if (expectedOwner !== void 0 || ownerDid === void 0) return;
31810
- 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.
31933
+ 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.
31811
31934
  `);
31812
31935
  }
31813
31936
  function ownerLoginPermissions(owner) {
@@ -32209,6 +32332,7 @@ async function loginWithDeviceAuthorization(input) {
32209
32332
  operatorType: existing?.operatorType ?? "human",
32210
32333
  authMethod: "openkey"
32211
32334
  }, result.primary);
32335
+ delete profile.namedKeys;
32212
32336
  await commitLogin(input.profileName, snapshot, {
32213
32337
  key,
32214
32338
  session: result.session,
@@ -32637,7 +32761,7 @@ init_types();
32637
32761
  init_constants();
32638
32762
  init_permissions();
32639
32763
  init_sdk();
32640
- import { CLI_NAME as CLI_NAME15 } from "@tinycloud/operations/cli-name";
32764
+ import { CLI_NAME as CLI_NAME16 } from "@tinycloud/operations/cli-name";
32641
32765
  import { TinyCloudNode as TinyCloudNode2 } from "@tinycloud/node-sdk";
32642
32766
  import { DelegationRequestBindingSchema, PermissionEntrySchema } from "@tinycloud/operations/artifacts";
32643
32767
  import {
@@ -32652,7 +32776,7 @@ import {
32652
32776
  // src/lib/revoke-delegation.ts
32653
32777
  init_constants();
32654
32778
  init_errors();
32655
- import { CLI_NAME as CLI_NAME14 } from "@tinycloud/operations/cli-name";
32779
+ import { CLI_NAME as CLI_NAME15 } from "@tinycloud/operations/cli-name";
32656
32780
  import { parseSignedCompactUcanAttenuation as parseSignedCompactUcanAttenuation3 } from "@tinycloud/sdk-core";
32657
32781
  import { inspectStoredDelegation, withoutBearer as withoutBearer3 } from "@tinycloud/operations/delegation-binding";
32658
32782
  init_permissions();
@@ -32916,7 +33040,7 @@ async function revokeDelegationOnNode(params) {
32916
33040
  "REVOKE_UNAUTHORIZED",
32917
33041
  "The node rejected this revocation: Unauthorized Revoker.",
32918
33042
  ExitCode.PERMISSION_DENIED,
32919
- { 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}\`.` }
33043
+ { 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}\`.` }
32920
33044
  );
32921
33045
  }
32922
33046
  throw cliErrorFromService(result.error);
@@ -32999,7 +33123,7 @@ function renderList(result) {
32999
33123
  }
33000
33124
  process.stdout.write(`Profile: ${result.profile}
33001
33125
  `);
33002
- process.stdout.write(`Session CID: ${result.session?.cid ?? "unavailable"}` + (result.session?.cid ? ` (current session; revoke with ${CLI_NAME15} delegation revoke ${result.session.cid})
33126
+ process.stdout.write(`Session CID: ${result.session?.cid ?? "unavailable"}` + (result.session?.cid ? ` (current session; revoke with ${CLI_NAME16} delegation revoke ${result.session.cid})
33003
33127
  ` : "\n"));
33004
33128
  process.stdout.write(`Local verification: ${result.localVerification}; node validation and revocation are not checked.
33005
33129
  `);
@@ -33097,7 +33221,7 @@ function registerAuthGrantsCommand(auth, options = {}) {
33097
33221
  ExitCode.ERROR,
33098
33222
  {
33099
33223
  causeCode: cause.code,
33100
- hint: `Do not use --revoke again. After fixing local profile storage, run: ${CLI_NAME15} --profile ${ctx.profile} auth grants remove ${cid}`
33224
+ hint: `Do not use --revoke again. After fixing local profile storage, run: ${CLI_NAME16} --profile ${ctx.profile} auth grants remove ${cid}`
33101
33225
  }
33102
33226
  );
33103
33227
  }
@@ -33162,7 +33286,7 @@ init_safe_text();
33162
33286
  init_raw_encryption();
33163
33287
  init_space();
33164
33288
  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";
33165
- import { CLI_NAME as CLI_NAME16 } from "@tinycloud/operations/cli-name";
33289
+ import { CLI_NAME as CLI_NAME17 } from "@tinycloud/operations/cli-name";
33166
33290
  var APPS_AGENT_PRESET = "apps-agent";
33167
33291
  var APPS_AGENT_SPACE = "applications";
33168
33292
  var APPS_AGENT_EXPIRY_DAYS = 7;
@@ -33244,7 +33368,7 @@ function lifetimePhrase(ms2) {
33244
33368
  return `${hours} hour${hours === 1 ? "" : "s"}`;
33245
33369
  }
33246
33370
  function revokeSentence(lifetimeMs2) {
33247
- return `It lasts up to ${lifetimePhrase(lifetimeMs2)}; revoke it any time with \`${CLI_NAME16} delegation revoke <id>\`.`;
33371
+ return `It lasts up to ${lifetimePhrase(lifetimeMs2)}; revoke it any time with \`${CLI_NAME17} delegation revoke <id>\`.`;
33248
33372
  }
33249
33373
  function appsAgentScopeNotice(mode, appIds = []) {
33250
33374
  const variant = APPS_AGENT_VARIANTS[mode];
@@ -33439,7 +33563,7 @@ function describeGrantScope(requested, requestedExpiry, applicationsSpace, maxLi
33439
33563
  const durations = [lifetimeMs(requestedExpiry), ...entryLifetimes].filter((ms2) => ms2 !== void 0).map((ms2) => Math.min(ms2, maxLifetimeMs ?? Number.POSITIVE_INFINITY));
33440
33564
  const longest = Math.max(...durations);
33441
33565
  lines.push(
33442
- 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>\`.`
33566
+ 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>\`.`
33443
33567
  );
33444
33568
  return { text: lines.join("\n"), entries: requested, extras };
33445
33569
  }
@@ -33747,7 +33871,7 @@ async function revokeForLogout(ctx, discardLocalChanges) {
33747
33871
  "REVOCATION_FAILED",
33748
33872
  `Revocation failed for ${cid}: ${result.error.message}. Session, key, and replica files are retained for retry; local reads stay denied.`,
33749
33873
  ExitCode.ERROR,
33750
- { cid, hint: `Retry \`${CLI_NAME17} auth logout --revoke\` when the node is available.` }
33874
+ { cid, hint: `Retry \`${CLI_NAME18} auth logout --revoke\` when the node is available.` }
33751
33875
  );
33752
33876
  }
33753
33877
  if (result.data.cid !== cid || result.data.revoked !== true) {
@@ -33766,7 +33890,7 @@ async function revokeForLogout(ctx, discardLocalChanges) {
33766
33890
  "REVOCATION_FAILED",
33767
33891
  `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.`,
33768
33892
  ExitCode.ERROR,
33769
- { hint: `Retry \`${CLI_NAME17} auth logout --revoke\` when online authority is available.` }
33893
+ { hint: `Retry \`${CLI_NAME18} auth logout --revoke\` when online authority is available.` }
33770
33894
  );
33771
33895
  }
33772
33896
  }
@@ -33965,7 +34089,7 @@ async function finishProfileLogout(ctx, options = {}) {
33965
34089
  function registerAuthCommand(program, options) {
33966
34090
  const auth = program.command("auth").description("Authentication management");
33967
34091
  registerAuthGrantsCommand(auth, { openKeyAcquisition: options?.openKeyAcquisition });
33968
- 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("--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) => {
34092
+ 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) => {
33969
34093
  try {
33970
34094
  if (options2.device && options2.paste) {
33971
34095
  throw new CLIError("INVALID_ARGUMENT", "--device and --paste are mutually exclusive.", ExitCode.USAGE_ERROR);
@@ -33981,6 +34105,9 @@ function registerAuthCommand(program, options) {
33981
34105
  ExitCode.USAGE_ERROR
33982
34106
  );
33983
34107
  }
34108
+ if (options2.keys !== void 0 && (options2.device || options2.manifest)) {
34109
+ throw new CLIError("INVALID_ARGUMENT", "`--keys` can't be used with `--device` or `--manifest`.", ExitCode.USAGE_ERROR);
34110
+ }
33984
34111
  const globalOpts = cmd.optsWithGlobals();
33985
34112
  const ctx = await ProfileManager.resolveContext(globalOpts, { storedSession: "unused" });
33986
34113
  await recoverPendingLogout(ctx.profile);
@@ -34042,6 +34169,8 @@ function registerAuthCommand(program, options) {
34042
34169
  );
34043
34170
  }
34044
34171
  method = options2.method;
34172
+ } else if (options2.keys !== void 0) {
34173
+ method = profileConfig?.authMethod === "openkey" || options2.paste || options2.owner ? "openkey" : "local";
34045
34174
  } else {
34046
34175
  method = scoped ? "openkey" : await promptAuthMethod();
34047
34176
  }
@@ -34054,7 +34183,7 @@ function registerAuthCommand(program, options) {
34054
34183
  }
34055
34184
  if (method === "local") {
34056
34185
  buildReplicationLoginRequest(void 0, replicationOptions);
34057
- await handleLocalAuth(ctx.profile, ctx.host, { replication: replicationOptions });
34186
+ await handleLocalAuth(ctx.profile, ctx.host, { replication: replicationOptions, keys: options2.keys });
34058
34187
  await persistReplicationLogin(ctx.profile, replicationOptions);
34059
34188
  if (replicationOptions.allowSecrets) writeReplicationSecretsWarning();
34060
34189
  } else {
@@ -34067,7 +34196,8 @@ function registerAuthCommand(program, options) {
34067
34196
  expectedOwner: owner,
34068
34197
  replaceSession: options2.replaceSession === true,
34069
34198
  persistHost,
34070
- force: options2.force === true
34199
+ force: options2.force === true,
34200
+ keys: options2.keys
34071
34201
  });
34072
34202
  if (!preparedCommitted) await persistReplicationLogin(ctx.profile, replicationOptions);
34073
34203
  if (replicationOptions.allowSecrets) writeReplicationSecretsWarning();
@@ -34117,7 +34247,7 @@ function registerAuthCommand(program, options) {
34117
34247
  authenticated: false,
34118
34248
  replicasRemoved,
34119
34249
  replicasKept: options2.keepReplicas === true,
34120
- 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.`,
34250
+ 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.`,
34121
34251
  ...online === void 0 ? {} : {
34122
34252
  revokedCids: online.plan.cids,
34123
34253
  revocationExcluded: online.plan.excluded
@@ -34262,7 +34392,7 @@ ${appsAgentScopeNotice(preset.mode, preset.appIds)}
34262
34392
  if (profile.authMethod === "openkey" || options2.device) {
34263
34393
  const key = await ProfileManager.getKey(ctx.profile);
34264
34394
  if (!key) {
34265
- 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);
34395
+ 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);
34266
34396
  }
34267
34397
  const openkeyHost = resolveOpenKeyHost(profile);
34268
34398
  const grants = [];
@@ -34282,7 +34412,7 @@ ${appsAgentScopeNotice(preset.mode, preset.appIds)}
34282
34412
  if (notices) process.stderr.write(`${notices.prompts[index]}
34283
34413
  `);
34284
34414
  const reason = permissionGrantReason(
34285
- `Grant requested TinyCloud permissions from \`${CLI_NAME17} auth request --grant\`.`,
34415
+ `Grant requested TinyCloud permissions from \`${CLI_NAME18} auth request --grant\`.`,
34286
34416
  group
34287
34417
  );
34288
34418
  const request = grantRequestPermissions(group, profile.spaceId ?? profile.spaceName);
@@ -34392,7 +34522,7 @@ ${appsAgentScopeNotice(preset.mode, preset.appIds)}
34392
34522
  kind: parsed.kind,
34393
34523
  requestId: parsed.requestId,
34394
34524
  requested: parsed.requested,
34395
- next: `${CLI_NAME17} auth retry ${parsed.requestId}`
34525
+ next: `${CLI_NAME18} auth retry ${parsed.requestId}`
34396
34526
  });
34397
34527
  return;
34398
34528
  }
@@ -35001,7 +35131,7 @@ async function acquireOpenKeyApproval(params) {
35001
35131
  if (!key) {
35002
35132
  throw new CLIError(
35003
35133
  "NO_KEY",
35004
- `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.`,
35134
+ `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.`,
35005
35135
  ExitCode.AUTH_REQUIRED
35006
35136
  );
35007
35137
  }
@@ -35074,7 +35204,7 @@ async function ensureDelegationAuthority(params) {
35074
35204
  if (!key) {
35075
35205
  throw new CLIError(
35076
35206
  "NO_KEY",
35077
- `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.`,
35207
+ `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.`,
35078
35208
  ExitCode.AUTH_REQUIRED
35079
35209
  );
35080
35210
  }
@@ -35328,12 +35458,13 @@ async function rotateAuthKey(profileName2, host, options = {}) {
35328
35458
  if (!profile.privateKey) {
35329
35459
  throw new CLIError(
35330
35460
  "LOCAL_OWNER_KEY_REQUIRED",
35331
- `Profile "${profileName2}" does not have a local owner private key. Run \`${CLI_NAME17} auth login --method local\` first.`,
35461
+ `Profile "${profileName2}" does not have a local owner private key. Run \`${CLI_NAME18} auth login --method local\` first.`,
35332
35462
  ExitCode.AUTH_REQUIRED
35333
35463
  );
35334
35464
  }
35335
35465
  const result2 = await handleLocalAuth(profileName2, host, {
35336
- emitOutput: false
35466
+ emitOutput: false,
35467
+ keys: profile.namedKeys?.prefix
35337
35468
  });
35338
35469
  outputRotationResult(result2.profile, profileName2, oldDid, "local");
35339
35470
  return;
@@ -35358,8 +35489,9 @@ async function rotateAuthKey(profileName2, host, options = {}) {
35358
35489
  }
35359
35490
  await ProfileManager.setKey(profileName2, jwk);
35360
35491
  await ProfileManager.clearSession(profileName2);
35492
+ const { namedKeys: _rotated, ...currentFields } = current;
35361
35493
  await ProfileManager.setProfile(profileName2, {
35362
- ...current,
35494
+ ...currentFields,
35363
35495
  host,
35364
35496
  did,
35365
35497
  sessionDid: did,
@@ -35371,7 +35503,8 @@ async function rotateAuthKey(profileName2, host, options = {}) {
35371
35503
  const result = await refreshOpenKeySession(profileName2, host, {
35372
35504
  paste: options.paste,
35373
35505
  noPopup: options.noPopup,
35374
- permissions
35506
+ permissions,
35507
+ ...profile.namedKeys === void 0 ? {} : { keys: profile.namedKeys.prefix, expectedOwner: profile.ownerDid }
35375
35508
  });
35376
35509
  outputRotationResult(result.profile, profileName2, oldDid, "openkey");
35377
35510
  }
@@ -35420,12 +35553,12 @@ async function persistCurrentLocalSession(profileName2, profile, session, replic
35420
35553
  signature: session.signature,
35421
35554
  ...replicaStamp === void 0 ? {} : { replicaStamp }
35422
35555
  });
35423
- if (profile.sessionDid !== session.verificationMethod || profile.spaceId !== session.spaceId) {
35424
- await ProfileManager.updateProfile(profileName2, (current) => ({
35556
+ if (profile.sessionDid !== session.verificationMethod || profile.spaceId !== session.spaceId || profile.namedKeys !== void 0) {
35557
+ await ProfileManager.updateProfile(profileName2, (current) => withVerifiedNamedKeys({
35425
35558
  ...current,
35426
35559
  sessionDid: session.verificationMethod,
35427
35560
  spaceId: session.spaceId
35428
- }));
35561
+ }, session));
35429
35562
  }
35430
35563
  });
35431
35564
  }
@@ -35457,11 +35590,14 @@ async function handleLocalAuth(profileName2, host, options = {}) {
35457
35590
  process.stderr.write(formatField("DID", did) + "\n\n");
35458
35591
  }
35459
35592
  }
35593
+ const namedKeys = options.keys === void 0 ? void 0 : parseKeysPrefix(options.keys, did);
35594
+ if (namedKeys !== void 0) await assertNodeSupportsNamedKeys(host);
35460
35595
  const sessionResult = await withSpinner("Signing in...", async () => {
35461
35596
  const replication = options.replication ?? profile?.replication;
35462
35597
  return localKeySignIn({
35463
35598
  privateKey,
35464
35599
  host,
35600
+ ...namedKeys === void 0 ? {} : { keyAbilities: keyAbilitiesFor(namedKeys) },
35465
35601
  ...replication?.prefixes.length ? { profile: profileName2, replication } : {}
35466
35602
  });
35467
35603
  });
@@ -35496,9 +35632,12 @@ async function handleLocalAuth(profileName2, host, options = {}) {
35496
35632
  operatorType: profile?.operatorType ?? "human",
35497
35633
  authMethod: "local",
35498
35634
  privateKey,
35499
- address
35635
+ address,
35636
+ ...namedKeys === void 0 ? {} : { namedKeys }
35500
35637
  };
35638
+ if (namedKeys === void 0) delete updatedProfile.namedKeys;
35501
35639
  await commitLogin(profileName2, snapshot, { key, session, profile: updatedProfile });
35640
+ if (namedKeys !== void 0 && sessionResult.networkReady === false) process.stderr.write(KEYS_FOLDER_NOT_READY);
35502
35641
  if (options.emitOutput ?? true) {
35503
35642
  outputJson({
35504
35643
  authenticated: true,
@@ -35577,6 +35716,8 @@ async function refreshPreparedOpenKeySession(params) {
35577
35716
  if (expectedOwner && normalizePkhIdentifier(owner) !== normalizePkhIdentifier(expectedOwner)) {
35578
35717
  throw new CLIError("OPENKEY_OWNER_MISMATCH", "The verified owner differs from this profile. No approval was started.", ExitCode.PERMISSION_DENIED);
35579
35718
  }
35719
+ const namedKeys = options.keys === void 0 ? void 0 : parseKeysPrefix(options.keys, canonicalOwnerDid(owner));
35720
+ if (namedKeys !== void 0) await assertNodeSupportsNamedKeys(host);
35580
35721
  if (options.force !== true) await assertShareHistoryAllowsKeyDrop(profileName2, "login");
35581
35722
  const replicationConfig = createCliReplicationConfig(profileName2, replication, { debug: false, quiet: true }, true);
35582
35723
  if (!replicationConfig) throw new CLIError("V2_REQUIRED", "Replica configuration is unavailable; no approval was started.", ExitCode.AUTH_REQUIRED);
@@ -35597,12 +35738,15 @@ async function refreshPreparedOpenKeySession(params) {
35597
35738
  capabilityRequest,
35598
35739
  includeAccountRegistryPermissions: false,
35599
35740
  autoBootstrapAccount: false,
35600
- replication: replicationConfig
35741
+ replication: replicationConfig,
35742
+ ...namedKeys === void 0 ? {} : { additionalRawAbilities: keyAbilitiesFor(namedKeys) }
35601
35743
  });
35602
35744
  let handoff;
35603
35745
  let preparedDid;
35746
+ let handedBackSiwe;
35604
35747
  const gateWasActive = await readGateSnapshot(profilePath8(profileName2)).then((gate) => gate !== void 0 && gate.principalHash !== null, () => true);
35605
35748
  try {
35749
+ if (namedKeys !== void 0) process.stderr.write(KEYS_APPROVAL_NOTE(namedKeys.prefix));
35606
35750
  await node.signInWithOpenKey(async (prepared) => {
35607
35751
  preparedDid = prepared.verificationMethod;
35608
35752
  handoff = await startPreparedOpenKeyHandoff({
@@ -35611,14 +35755,23 @@ async function refreshPreparedOpenKeySession(params) {
35611
35755
  noPopup: options.noPopup,
35612
35756
  paste: options.paste
35613
35757
  });
35614
- return handoff.result;
35758
+ return handoff.result.then((result) => {
35759
+ handedBackSiwe = result.signedMessage;
35760
+ return result;
35761
+ });
35615
35762
  });
35616
35763
  const active = node.restorableSession;
35617
35764
  if (!active || !preparedDid || node.sessionDid !== preparedDid || active.verificationMethod !== preparedDid) {
35618
35765
  throw new CLIError("OPENKEY_SESSION_MISMATCH", "OpenKey signed a different SDK session key; no profile or session was saved.", ExitCode.PERMISSION_DENIED);
35619
35766
  }
35620
35767
  const key = active.jwk;
35621
- const verified = verifyScopedLogin({ ...active }, key, preparedDid, permissions, { expectedOwner: owner, expiry });
35768
+ const verified = verifyScopedLogin({ ...active }, key, preparedDid, permissions, {
35769
+ expectedOwner: owner,
35770
+ expiry,
35771
+ ...namedKeys === void 0 ? {} : { namedKeyRow: namedKeys }
35772
+ });
35773
+ const grant = namedKeys === void 0 ? "none" : namedKeysGrantIn(active.siwe, namedKeys);
35774
+ if (grant === "partial") throw partialKeysGrantError();
35622
35775
  const session = {
35623
35776
  ...verified.session,
35624
35777
  ...node.replicaStamp ? { replicaStamp: node.replicaStamp } : {},
@@ -35639,6 +35792,10 @@ async function refreshPreparedOpenKeySession(params) {
35639
35792
  ...replication.allowSecrets === true ? { allowSecrets: true } : {}
35640
35793
  }
35641
35794
  };
35795
+ if (namedKeys !== void 0) {
35796
+ delete updatedProfile.namedKeys;
35797
+ if (grant === "both") updatedProfile.namedKeys = namedKeys;
35798
+ }
35642
35799
  await commitLogin(profileName2, snapshot, {
35643
35800
  key,
35644
35801
  session,
@@ -35648,6 +35805,10 @@ async function refreshPreparedOpenKeySession(params) {
35648
35805
  });
35649
35806
  await handoff?.complete(true);
35650
35807
  announceFirstOwner(profileName2, expectedOwner, owner);
35808
+ if (namedKeys !== void 0) {
35809
+ if (grant === "both") await readyNamedKeysNetwork(node);
35810
+ else process.stderr.write(keysNotGrantedAfterLogin(profileName2));
35811
+ }
35651
35812
  return {
35652
35813
  profile: updatedProfile,
35653
35814
  delegationData: session,
@@ -35657,7 +35818,102 @@ async function refreshPreparedOpenKeySession(params) {
35657
35818
  } catch (error) {
35658
35819
  await handoff?.complete(false);
35659
35820
  await undoPreparedAttempt(profileName2, node, owner, gateWasActive).catch(() => void 0);
35660
- throw error;
35821
+ throw namedKeys !== void 0 && isNarrowedRecapMismatch(error) && namedKeysGrantIn(handedBackSiwe, namedKeys) === "partial" ? partialKeysGrantError() : error;
35822
+ }
35823
+ }
35824
+ 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.
35825
+ `;
35826
+ 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.
35827
+ `;
35828
+ 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.
35829
+ `;
35830
+ async function readyNamedKeysNetwork(node) {
35831
+ try {
35832
+ await node.ensureEncryptionNetwork();
35833
+ } catch {
35834
+ process.stderr.write(KEYS_FOLDER_NOT_READY);
35835
+ }
35836
+ }
35837
+ function isNarrowedRecapMismatch(error) {
35838
+ const text = error instanceof Error ? `${error.code ?? ""} ${error.message}` : String(error);
35839
+ return text.includes("narrowed_recap_mismatch");
35840
+ }
35841
+ async function refreshPreparedKeysSession(params) {
35842
+ const { profileName: profileName2, host, profile, snapshot, options, expectedOwner, expiry } = params;
35843
+ const storedOwner = snapshot.session && snapshot.key && profile.sessionDid ? verifySignedSession(snapshot.session, snapshot.key, profile.sessionDid).ownerDid : void 0;
35844
+ const owner = options.expectedOwner ? canonicalOwnerDid(options.expectedOwner) : storedOwner ?? (profile.ownerDid ? canonicalOwnerDid(profile.ownerDid) : void 0);
35845
+ if (!owner) {
35846
+ throw new CLIError(
35847
+ "OWNER_REQUIRED",
35848
+ "The first OpenKey sign-in with `--keys` needs your account: add `--owner did:pkh:eip155:<chain>:<address>`. Nothing was started.",
35849
+ ExitCode.AUTH_REQUIRED
35850
+ );
35851
+ }
35852
+ if (expectedOwner && normalizePkhIdentifier(owner) !== normalizePkhIdentifier(expectedOwner)) {
35853
+ throw new CLIError("OPENKEY_OWNER_MISMATCH", "The verified owner differs from this profile. No approval was started.", ExitCode.PERMISSION_DENIED);
35854
+ }
35855
+ const namedKeys = parseKeysPrefix(options.keys, canonicalOwnerDid(owner));
35856
+ await assertNodeSupportsNamedKeys(host);
35857
+ if (options.force !== true) await assertShareHistoryAllowsKeyDrop(profileName2, "login");
35858
+ const node = new TinyCloudNode3({
35859
+ host,
35860
+ signer: ownerIdentitySigner(owner),
35861
+ additionalRawAbilities: keyAbilitiesFor(namedKeys),
35862
+ includeAccountRegistryPermissions: false,
35863
+ autoBootstrapAccount: false,
35864
+ ...expiry ? { sessionExpirationMs: expiry.durationMs } : {}
35865
+ });
35866
+ let handoff;
35867
+ let preparedDid;
35868
+ let handedBackSiwe;
35869
+ try {
35870
+ process.stderr.write(KEYS_APPROVAL_NOTE(namedKeys.prefix));
35871
+ await node.signInWithOpenKey(async (prepared) => {
35872
+ preparedDid = prepared.verificationMethod;
35873
+ handoff = await startPreparedOpenKeyHandoff({
35874
+ prepared,
35875
+ openKeyHost: resolveOpenKeyHost(profile),
35876
+ noPopup: options.noPopup,
35877
+ paste: options.paste
35878
+ });
35879
+ return handoff.result.then((result) => {
35880
+ handedBackSiwe = result.signedMessage;
35881
+ return result;
35882
+ });
35883
+ });
35884
+ const active = node.restorableSession;
35885
+ if (!active || !preparedDid || node.sessionDid !== preparedDid || active.verificationMethod !== preparedDid) {
35886
+ throw new CLIError("OPENKEY_SESSION_MISMATCH", "OpenKey signed a different SDK session key; no profile or session was saved.", ExitCode.PERMISSION_DENIED);
35887
+ }
35888
+ const key = active.jwk;
35889
+ const signed = verifySignedSession({ ...active }, key, preparedDid, { expectedOwner: owner, expiry });
35890
+ const grant = namedKeysGrantIn(active.siwe, namedKeys);
35891
+ if (grant === "partial") throw partialKeysGrantError();
35892
+ const session = { ...withVerifiedAuthority({ ...active, jwk: key }, signed), authMethod: "openkey" };
35893
+ const updatedProfile = {
35894
+ ...profile,
35895
+ host: options.persistHost === true || !profile.host ? host : profile.host,
35896
+ did: preparedDid,
35897
+ sessionDid: preparedDid,
35898
+ posture: profile.posture ?? "owner-openkey",
35899
+ operatorType: profile.operatorType ?? "human",
35900
+ authMethod: "openkey",
35901
+ ownerDid: signed.ownerDid,
35902
+ spaceId: active.spaceId
35903
+ };
35904
+ delete updatedProfile.namedKeys;
35905
+ if (grant === "both") updatedProfile.namedKeys = namedKeys;
35906
+ await commitLogin(profileName2, snapshot, { key, session, profile: updatedProfile });
35907
+ await handoff?.complete(true);
35908
+ if (grant === "both") await readyNamedKeysNetwork(node);
35909
+ if (grant === "none") {
35910
+ process.stderr.write(keysNotGrantedAfterLogin(profileName2));
35911
+ }
35912
+ return { profile: updatedProfile, delegationData: session, declined: [], legacyNested: [] };
35913
+ } catch (error) {
35914
+ await handoff?.complete(false);
35915
+ await node.replication?.close();
35916
+ throw isNarrowedRecapMismatch(error) && namedKeysGrantIn(handedBackSiwe, namedKeys) === "partial" ? partialKeysGrantError() : error;
35661
35917
  }
35662
35918
  }
35663
35919
  async function undoPreparedAttempt(profileName2, node, owner, gateWasActive) {
@@ -35698,6 +35954,9 @@ async function refreshOpenKeySession(profileName2, host, options = {}) {
35698
35954
  const estimatedExpiry = expiry === void 0 ? void 0 : new Date(Date.now() + expiry.durationMs).toISOString();
35699
35955
  assertSessionReplaceable(profileName2, snapshot, expectedOwner, permissions, estimatedExpiry);
35700
35956
  }
35957
+ if (options.keys !== void 0 && replication.prefixes.length === 0) {
35958
+ return refreshPreparedKeysSession({ profileName: profileName2, host, profile, snapshot, options, expectedOwner, expiry });
35959
+ }
35701
35960
  if (replication.prefixes.length > 0) {
35702
35961
  return refreshPreparedOpenKeySession({
35703
35962
  profileName: profileName2,
@@ -35760,6 +36019,7 @@ async function refreshOpenKeySession(profileName2, host, options = {}) {
35760
36019
  ...typeof sanitizedSession.spaceId === "string" ? { spaceId: sanitizedSession.spaceId } : {},
35761
36020
  ...verifiedOwner === void 0 ? {} : { ownerDid: verifiedOwner }
35762
36021
  }, ownerKeyPrimary);
36022
+ delete updatedProfile.namedKeys;
35763
36023
  await commitLogin(profileName2, snapshot, {
35764
36024
  key,
35765
36025
  session: sanitizedSession,
@@ -35772,7 +36032,7 @@ async function refreshOpenKeySession(profileName2, host, options = {}) {
35772
36032
  }
35773
36033
 
35774
36034
  // src/commands/completion.ts
35775
- import { CLI_NAME as CLI_NAME18 } from "@tinycloud/operations/cli-name";
36035
+ import { CLI_NAME as CLI_NAME19 } from "@tinycloud/operations/cli-name";
35776
36036
  function registerCompletionCommand(program) {
35777
36037
  const completion = program.command("completion").description("Generate shell completions");
35778
36038
  completion.command("bash").description("Output bash completions").action(() => {
@@ -35789,7 +36049,7 @@ function registerCompletionCommand(program) {
35789
36049
  });
35790
36050
  }
35791
36051
  function generateBashCompletion() {
35792
- return `# ${CLI_NAME18} bash completion
36052
+ return `# ${CLI_NAME19} bash completion
35793
36053
  _tc_completions() {
35794
36054
  local cur prev commands subcommands
35795
36055
  COMPREPLY=()
@@ -35814,11 +36074,11 @@ _tc_completions() {
35814
36074
  COMPREPLY=( $(compgen -W "\${subcommands}" -- "\${cur}") )
35815
36075
  fi
35816
36076
  }
35817
- complete -F _tc_completions ${CLI_NAME18}
36077
+ complete -F _tc_completions ${CLI_NAME19}
35818
36078
  `;
35819
36079
  }
35820
36080
  function generateZshCompletion() {
35821
- return `#compdef ${CLI_NAME18}
36081
+ return `#compdef ${CLI_NAME19}
35822
36082
 
35823
36083
  _tc() {
35824
36084
  local -a commands
@@ -35869,50 +36129,50 @@ _tc
35869
36129
  `;
35870
36130
  }
35871
36131
  function generateFishCompletion() {
35872
- return `# ${CLI_NAME18} fish completion
36132
+ return `# ${CLI_NAME19} fish completion
35873
36133
  set -l commands init auth kv space delegation share node profile completion
35874
36134
 
35875
36135
  # Disable file completion by default
35876
- complete -c ${CLI_NAME18} -f
36136
+ complete -c ${CLI_NAME19} -f
35877
36137
 
35878
36138
  # Top-level commands
35879
- complete -c ${CLI_NAME18} -n "not __fish_seen_subcommand_from $commands" -a init -d "Initialize a new TinyCloud profile"
35880
- complete -c ${CLI_NAME18} -n "not __fish_seen_subcommand_from $commands" -a auth -d "Authentication management"
35881
- complete -c ${CLI_NAME18} -n "not __fish_seen_subcommand_from $commands" -a kv -d "Key-value store operations"
35882
- complete -c ${CLI_NAME18} -n "not __fish_seen_subcommand_from $commands" -a space -d "Space management"
35883
- complete -c ${CLI_NAME18} -n "not __fish_seen_subcommand_from $commands" -a delegation -d "Manage delegations"
35884
- complete -c ${CLI_NAME18} -n "not __fish_seen_subcommand_from $commands" -a share -d "Share data with others"
35885
- complete -c ${CLI_NAME18} -n "not __fish_seen_subcommand_from $commands" -a node -d "Node health and info"
35886
- complete -c ${CLI_NAME18} -n "not __fish_seen_subcommand_from $commands" -a profile -d "Profile management"
35887
- complete -c ${CLI_NAME18} -n "not __fish_seen_subcommand_from $commands" -a completion -d "Generate shell completions"
36139
+ complete -c ${CLI_NAME19} -n "not __fish_seen_subcommand_from $commands" -a init -d "Initialize a new TinyCloud profile"
36140
+ complete -c ${CLI_NAME19} -n "not __fish_seen_subcommand_from $commands" -a auth -d "Authentication management"
36141
+ complete -c ${CLI_NAME19} -n "not __fish_seen_subcommand_from $commands" -a kv -d "Key-value store operations"
36142
+ complete -c ${CLI_NAME19} -n "not __fish_seen_subcommand_from $commands" -a space -d "Space management"
36143
+ complete -c ${CLI_NAME19} -n "not __fish_seen_subcommand_from $commands" -a delegation -d "Manage delegations"
36144
+ complete -c ${CLI_NAME19} -n "not __fish_seen_subcommand_from $commands" -a share -d "Share data with others"
36145
+ complete -c ${CLI_NAME19} -n "not __fish_seen_subcommand_from $commands" -a node -d "Node health and info"
36146
+ complete -c ${CLI_NAME19} -n "not __fish_seen_subcommand_from $commands" -a profile -d "Profile management"
36147
+ complete -c ${CLI_NAME19} -n "not __fish_seen_subcommand_from $commands" -a completion -d "Generate shell completions"
35888
36148
 
35889
36149
  # Subcommands
35890
- complete -c ${CLI_NAME18} -n "__fish_seen_subcommand_from auth" -a "login logout rotate status whoami"
35891
- complete -c ${CLI_NAME18} -n "__fish_seen_subcommand_from kv" -a "get put delete list head"
35892
- complete -c ${CLI_NAME18} -n "__fish_seen_subcommand_from space" -a "list create info switch"
35893
- complete -c ${CLI_NAME18} -n "__fish_seen_subcommand_from delegation" -a "create list info revoke"
35894
- complete -c ${CLI_NAME18} -n "__fish_seen_subcommand_from share" -a "create receive list revoke"
35895
- complete -c ${CLI_NAME18} -n "__fish_seen_subcommand_from node" -a "health version status"
35896
- complete -c ${CLI_NAME18} -n "__fish_seen_subcommand_from profile" -a "list create show switch delete"
35897
- complete -c ${CLI_NAME18} -n "__fish_seen_subcommand_from completion" -a "bash zsh fish"
36150
+ complete -c ${CLI_NAME19} -n "__fish_seen_subcommand_from auth" -a "login logout rotate status whoami"
36151
+ complete -c ${CLI_NAME19} -n "__fish_seen_subcommand_from kv" -a "get put delete list head"
36152
+ complete -c ${CLI_NAME19} -n "__fish_seen_subcommand_from space" -a "list create info switch"
36153
+ complete -c ${CLI_NAME19} -n "__fish_seen_subcommand_from delegation" -a "create list info revoke"
36154
+ complete -c ${CLI_NAME19} -n "__fish_seen_subcommand_from share" -a "create receive list revoke"
36155
+ complete -c ${CLI_NAME19} -n "__fish_seen_subcommand_from node" -a "health version status"
36156
+ complete -c ${CLI_NAME19} -n "__fish_seen_subcommand_from profile" -a "list create show switch delete"
36157
+ complete -c ${CLI_NAME19} -n "__fish_seen_subcommand_from completion" -a "bash zsh fish"
35898
36158
 
35899
36159
  # Global options
35900
- complete -c ${CLI_NAME18} -l profile -s p -d "Profile to use"
35901
- complete -c ${CLI_NAME18} -l host -s H -d "TinyCloud node URL"
35902
- complete -c ${CLI_NAME18} -l verbose -s v -d "Enable verbose output"
35903
- complete -c ${CLI_NAME18} -l no-cache -d "Disable caching"
35904
- complete -c ${CLI_NAME18} -l quiet -s q -d "Suppress non-essential output"
35905
- complete -c ${CLI_NAME18} -l replication -d "Enable read-through replication"
35906
- complete -c ${CLI_NAME18} -l no-replication -d "Disable replication"
35907
- complete -c ${CLI_NAME18} -l replication-debug -d "Write replication diagnostics to stderr"
36160
+ complete -c ${CLI_NAME19} -l profile -s p -d "Profile to use"
36161
+ complete -c ${CLI_NAME19} -l host -s H -d "TinyCloud node URL"
36162
+ complete -c ${CLI_NAME19} -l verbose -s v -d "Enable verbose output"
36163
+ complete -c ${CLI_NAME19} -l no-cache -d "Disable caching"
36164
+ complete -c ${CLI_NAME19} -l quiet -s q -d "Suppress non-essential output"
36165
+ complete -c ${CLI_NAME19} -l replication -d "Enable read-through replication"
36166
+ complete -c ${CLI_NAME19} -l no-replication -d "Disable replication"
36167
+ complete -c ${CLI_NAME19} -l replication-debug -d "Write replication diagnostics to stderr"
35908
36168
  `;
35909
36169
  }
35910
36170
 
35911
36171
  // src/commands/courier.ts
35912
- import { CLI_NAME as CLI_NAME19 } from "@tinycloud/operations/cli-name";
35913
- 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>.`;
36172
+ import { CLI_NAME as CLI_NAME20 } from "@tinycloud/operations/cli-name";
36173
+ 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>.`;
35914
36174
  function registerCourierCommand(program) {
35915
- const courier = program.command("courier").description(`Agent-to-agent threads over TinyCloud; run \`${CLI_NAME19} courier --help\` (beta)`).argument("[args...]").allowUnknownOption().helpOption(false);
36175
+ const courier = program.command("courier").description(`Agent-to-agent threads over TinyCloud; run \`${CLI_NAME20} courier --help\` (beta)`).argument("[args...]").allowUnknownOption().helpOption(false);
35916
36176
  courier.action(() => courier.error(TC_OPTIONS_BEFORE_COURIER, { exitCode: 2, code: "tc.courier.options" }));
35917
36177
  }
35918
36178
 
@@ -35957,7 +36217,7 @@ init_formatter();
35957
36217
  init_errors();
35958
36218
  init_constants();
35959
36219
  init_sdk();
35960
- import { CLI_NAME as CLI_NAME20 } from "@tinycloud/operations/cli-name";
36220
+ import { CLI_NAME as CLI_NAME21 } from "@tinycloud/operations/cli-name";
35961
36221
  import { PermissionNotInManifestError } from "@tinycloud/node-sdk";
35962
36222
  async function queryDelegations(node, options, until) {
35963
36223
  const found = [];
@@ -35968,7 +36228,7 @@ async function queryDelegations(node, options, until) {
35968
36228
  if (page.error.code === "PERMISSION_DENIED") {
35969
36229
  throw new CLIError(
35970
36230
  "PERMISSION_DENIED",
35971
- `This session cannot read delegations: it lacks tinycloud.delegation/list. Run \`${CLI_NAME20} auth login\` again to grant it.`,
36231
+ `This session cannot read delegations: it lacks tinycloud.delegation/list. Run \`${CLI_NAME21} auth login\` again to grant it.`,
35972
36232
  ExitCode.PERMISSION_DENIED
35973
36233
  );
35974
36234
  }
@@ -36143,7 +36403,7 @@ init_constants();
36143
36403
  init_formatter();
36144
36404
  init_theme();
36145
36405
  init_errors();
36146
- import { CLI_NAME as CLI_NAME21 } from "@tinycloud/operations/cli-name";
36406
+ import { CLI_NAME as CLI_NAME22 } from "@tinycloud/operations/cli-name";
36147
36407
  init_session_expired();
36148
36408
  init_types();
36149
36409
  function registerDoctorCommand(program) {
@@ -36177,7 +36437,7 @@ function registerDoctorCommand(program) {
36177
36437
  const profile = await ProfileManager.getProfile(profileName2);
36178
36438
  keyDetail = profile.did ? `${profile.did.slice(0, 20)}...` : "key found";
36179
36439
  } else {
36180
- keyDetail = `no key \u2014 run ${CLI_NAME21} init`;
36440
+ keyDetail = `no key \u2014 run ${CLI_NAME22} init`;
36181
36441
  }
36182
36442
  } catch {
36183
36443
  keyDetail = "error reading key";
@@ -36199,7 +36459,7 @@ function registerDoctorCommand(program) {
36199
36459
  } else if (state2 === "unverified") {
36200
36460
  sessionDetail = `not usable (no signed OpenKey proof) \u2014 ${signInAgainHint(profileName2, resolveProfilePosture(profile))}`;
36201
36461
  } else {
36202
- sessionDetail = sessionOk ? "active" : `no session \u2014 run ${CLI_NAME21} auth login`;
36462
+ sessionDetail = sessionOk ? "active" : `no session \u2014 run ${CLI_NAME22} auth login`;
36203
36463
  }
36204
36464
  } catch {
36205
36465
  sessionDetail = "error reading session";
@@ -36227,7 +36487,7 @@ function registerDoctorCommand(program) {
36227
36487
  try {
36228
36488
  const profile = await ProfileManager.getProfile(profileName2);
36229
36489
  spaceOk = Boolean(profile.spaceId);
36230
- spaceDetail = spaceOk ? `${profile.spaceId.slice(0, 16)}...` : `no space \u2014 run ${CLI_NAME21} space create`;
36490
+ spaceDetail = spaceOk ? `${profile.spaceId.slice(0, 16)}...` : `no space \u2014 run ${CLI_NAME22} space create`;
36231
36491
  } catch {
36232
36492
  spaceDetail = "error checking space";
36233
36493
  }
@@ -36417,7 +36677,7 @@ ${rowCount} row${rowCount === 1 ? "" : "s"} returned`) + "\n");
36417
36677
  }
36418
36678
 
36419
36679
  // src/commands/enable.ts
36420
- import { CLI_NAME as CLI_NAME22 } from "@tinycloud/operations/cli-name";
36680
+ import { CLI_NAME as CLI_NAME23 } from "@tinycloud/operations/cli-name";
36421
36681
  init_constants();
36422
36682
  init_profiles();
36423
36683
  init_errors();
@@ -36425,7 +36685,7 @@ init_formatter();
36425
36685
  init_publishing_manifest();
36426
36686
  function registerEnableCommand(program) {
36427
36687
  const enable = program.command("enable").description("Enable a narrowly scoped TinyCloud service");
36428
- 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) => {
36688
+ 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) => {
36429
36689
  try {
36430
36690
  const globalOptions = command.optsWithGlobals();
36431
36691
  const context = await ProfileManager.resolveContext(globalOptions, { storedSession: "unused" });
@@ -36460,7 +36720,7 @@ function registerEnableCommand(program) {
36460
36720
  init_profiles();
36461
36721
  init_constants();
36462
36722
  init_sdk();
36463
- import { CLI_NAME as CLI_NAME23 } from "@tinycloud/operations/cli-name";
36723
+ import { CLI_NAME as CLI_NAME24 } from "@tinycloud/operations/cli-name";
36464
36724
  import { operationSpaceResolver as operationSpaceResolver2 } from "@tinycloud/operations/delegation-binding";
36465
36725
 
36466
36726
  // src/lib/knowledge-input.ts
@@ -36726,7 +36986,7 @@ function registerAppsCommand(program) {
36726
36986
  "after",
36727
36987
  `
36728
36988
  Examples:
36729
- $ ${CLI_NAME23} apps install xyz.tinycloud.sample.healthlog
36989
+ $ ${CLI_NAME24} apps install xyz.tinycloud.sample.healthlog
36730
36990
  `
36731
36991
  ).action(async (appIdInput, _options, cmd) => {
36732
36992
  try {
@@ -36818,7 +37078,7 @@ init_profiles();
36818
37078
  init_formatter();
36819
37079
  init_errors();
36820
37080
  init_constants();
36821
- import { CLI_NAME as CLI_NAME24 } from "@tinycloud/operations/cli-name";
37081
+ import { CLI_NAME as CLI_NAME25 } from "@tinycloud/operations/cli-name";
36822
37082
  function registerInitCommand(program) {
36823
37083
  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) => {
36824
37084
  try {
@@ -36836,7 +37096,7 @@ function registerInitCommand(program) {
36836
37096
  if (await ProfileManager.profileExists(profileName2)) {
36837
37097
  throw new CLIError(
36838
37098
  "PROFILE_EXISTS",
36839
- `Profile "${profileName2}" already exists. Use \`${CLI_NAME24} profile delete ${profileName2}\` first or choose a different name.`,
37099
+ `Profile "${profileName2}" already exists. Use \`${CLI_NAME25} profile delete ${profileName2}\` first or choose a different name.`,
36840
37100
  ExitCode.ERROR
36841
37101
  );
36842
37102
  }
@@ -36892,7 +37152,7 @@ init_session_expired();
36892
37152
  init_constants();
36893
37153
  init_profiles();
36894
37154
  import { invokeOperation as invokeOperation2 } from "@tinycloud/operations";
36895
- import { CLI_NAME as CLI_NAME25 } from "@tinycloud/operations/cli-name";
37155
+ import { CLI_NAME as CLI_NAME26 } from "@tinycloud/operations/cli-name";
36896
37156
  init_permissions();
36897
37157
  init_errors();
36898
37158
  init_formatter();
@@ -36995,7 +37255,7 @@ function knowledgeFailure(result, profile, appId) {
36995
37255
  case "error": {
36996
37256
  if (result.error.code === "SESSION_NOT_FOUND" || result.error.code === "PROFILE_POSTURE_NOT_ALLOWED" && result.context.posture === "unauthenticated") {
36997
37257
  return new CLIError("AUTH_REQUIRED", "Not signed in to TinyCloud.", ExitCode.AUTH_REQUIRED, {
36998
- hint: `Sign in with: ${CLI_NAME25} --profile ${result.context.profile} auth login`
37258
+ hint: `Sign in with: ${CLI_NAME26} --profile ${result.context.profile} auth login`
36999
37259
  });
37000
37260
  }
37001
37261
  if (result.error.code === "NODE_UNREACHABLE") {
@@ -37025,7 +37285,7 @@ function knowledgeFailure(result, profile, appId) {
37025
37285
  }
37026
37286
  }
37027
37287
  function missingGrantHint(profile, appId) {
37028
- const request = `${CLI_NAME25} --profile ${profile} auth request`;
37288
+ const request = `${CLI_NAME26} --profile ${profile} auth request`;
37029
37289
  const lines = appId === void 0 ? [
37030
37290
  ` ${request} --preset ${APPS_AGENT_PRESET} --emit req.json (read-only, all installed apps)`,
37031
37291
  ` ${request} --cap tinycloud.kv:${APPS_AGENT_SPACE}::get,list --emit req.json (all files, no table access)`,
@@ -37038,7 +37298,7 @@ function missingGrantHint(profile, appId) {
37038
37298
  return [
37039
37299
  "Ask the owner for a grant, narrowest first. Create the request:",
37040
37300
  ...lines,
37041
- `The owner runs \`${CLI_NAME25} auth grant req.json > grant.json\`; then import the result with \`${CLI_NAME25} --profile ${profile} auth import grant.json\`.`
37301
+ `The owner runs \`${CLI_NAME26} auth grant req.json > grant.json\`; then import the result with \`${CLI_NAME26} --profile ${profile} auth import grant.json\`.`
37042
37302
  ].join("\n");
37043
37303
  }
37044
37304
 
@@ -37337,6 +37597,175 @@ function registerKvCommand(program) {
37337
37597
  });
37338
37598
  }
37339
37599
 
37600
+ // src/commands/keys.ts
37601
+ init_profiles();
37602
+ init_constants();
37603
+ init_formatter();
37604
+ init_errors();
37605
+ init_sdk();
37606
+ import { readFile as readFile12 } from "fs/promises";
37607
+ import { NamedKeyError, createNamedKeyClient } from "@tinycloud/node-sdk/encryption-keys";
37608
+ import { CLI_NAME as CLI_NAME27 } from "@tinycloud/operations/cli-name";
37609
+ async function readStdin3() {
37610
+ const chunks = [];
37611
+ for await (const chunk of process.stdin) {
37612
+ chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
37613
+ }
37614
+ return Buffer.concat(chunks);
37615
+ }
37616
+ async function readInput(file) {
37617
+ return file === void 0 ? readStdin3() : readFile12(file);
37618
+ }
37619
+ function cliErrorFromNamedKey(error, command, path) {
37620
+ if (!(error instanceof NamedKeyError)) return error;
37621
+ switch (error.code) {
37622
+ case "NAMED_KEYS_UNSUPPORTED":
37623
+ return new CLIError("NAMED_KEYS_UNAVAILABLE", "This node doesn't support named keys.", ExitCode.NODE_ERROR);
37624
+ case "INVALID_KEY_TARGET":
37625
+ case "KEY_PATH_INVALID":
37626
+ return new CLIError("INVALID_KEY_PATH", "That key name isn't valid.", ExitCode.USAGE_ERROR);
37627
+ case "INVALID_ENVELOPE":
37628
+ return new CLIError("INVALID_ENVELOPE", "The envelope is damaged or incomplete.", ExitCode.USAGE_ERROR);
37629
+ case "KEY_NOT_FOUND":
37630
+ return new CLIError(
37631
+ "NOT_FOUND",
37632
+ 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}\`.`,
37633
+ ExitCode.NOT_FOUND
37634
+ );
37635
+ case "KEY_BLOCKED":
37636
+ return new CLIError("KEY_BLOCKED", `Key ${path} is blocked, so it can't be used.`, ExitCode.PERMISSION_DENIED);
37637
+ case "DECRYPT_FAILED":
37638
+ return new CLIError("DECRYPT_FAILED", `Couldn't decrypt this envelope with key ${path}.`, ExitCode.ERROR);
37639
+ }
37640
+ if (error.status === 401 || error.status === 403) {
37641
+ return new CLIError("PERMISSION_DENIED", error.message, ExitCode.PERMISSION_DENIED, {
37642
+ hint: `Sign in with \`${CLI_NAME27} auth login --keys <folder>\` for this key's folder.`
37643
+ });
37644
+ }
37645
+ return new CLIError(error.code, error.message, error.status === void 0 ? ExitCode.ERROR : ExitCode.NODE_ERROR);
37646
+ }
37647
+ async function withNetworkSetup(node, call) {
37648
+ try {
37649
+ return await call();
37650
+ } catch (error) {
37651
+ if (!(error instanceof NamedKeyError) || error.code !== "NETWORK_NOT_FOUND") throw error;
37652
+ await node.ensureEncryptionNetwork();
37653
+ return call();
37654
+ }
37655
+ }
37656
+ async function grantedPrefix(deps, profileName2) {
37657
+ const profile = await deps.getProfile(profileName2);
37658
+ const keys = profile.namedKeys;
37659
+ if (keys === void 0) {
37660
+ throw new CLIError(
37661
+ "KEYS_NOT_GRANTED",
37662
+ `Profile "${profileName2}" can't use named keys yet. Sign in with \`${CLI_NAME27} --profile ${profileName2} auth login --keys <folder>\`.`,
37663
+ ExitCode.PERMISSION_DENIED
37664
+ );
37665
+ }
37666
+ return keys.prefix;
37667
+ }
37668
+ function assertInGrantedPrefix(prefix, path, profileName2) {
37669
+ if (!keyPathInPrefix(prefix, path)) {
37670
+ throw new CLIError(
37671
+ "KEY_OUTSIDE_GRANTED_PREFIX",
37672
+ `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>\`.`,
37673
+ ExitCode.PERMISSION_DENIED
37674
+ );
37675
+ }
37676
+ }
37677
+ function parseEnvelope(bytes) {
37678
+ let parsed;
37679
+ try {
37680
+ parsed = JSON.parse(bytes.toString("utf8"));
37681
+ } catch {
37682
+ throw new CLIError("INVALID_ENVELOPE", "The input isn't an envelope (expected JSON).", ExitCode.USAGE_ERROR);
37683
+ }
37684
+ const envelope = parsed;
37685
+ if (typeof envelope !== "object" || envelope === null || envelope.v !== 2 || typeof envelope.keyPath !== "string") {
37686
+ throw new CLIError("INVALID_ENVELOPE", 'The input isn\'t a named-key envelope (it needs `"v": 2` and a `keyPath`).', ExitCode.USAGE_ERROR);
37687
+ }
37688
+ return envelope;
37689
+ }
37690
+ var defaultDeps = {
37691
+ resolveContext: (options) => ProfileManager.resolveContext(options),
37692
+ getProfile: (name) => ProfileManager.getProfile(name),
37693
+ authenticate: (ctx) => ensureAuthenticated(ctx),
37694
+ createClient: createNamedKeyClient,
37695
+ outputJson,
37696
+ handleError
37697
+ };
37698
+ function registerKeysCommand(program, deps = defaultDeps) {
37699
+ const { outputJson: outputJson2, handleError: handleError2 } = deps;
37700
+ const keys = program.command("keys").description(`Named encryption keys (sign in with \`${CLI_NAME27} auth login --keys <folder>\` first)`);
37701
+ keys.command("ensure <path>").description("Create the named key if it doesn't exist yet, then show it").action(async (path, _options, cmd) => {
37702
+ try {
37703
+ const ctx = await deps.resolveContext(cmd.optsWithGlobals());
37704
+ assertInGrantedPrefix(await grantedPrefix(deps, ctx.profile), path, ctx.profile);
37705
+ const node = await deps.authenticate(ctx);
37706
+ const client = deps.createClient(node);
37707
+ const descriptor = await withSpinner(`Getting or creating key ${path}\u2026`, () => withNetworkSetup(node, () => client.ensureKey(path))).catch((error) => {
37708
+ throw cliErrorFromNamedKey(error, "ensure", path);
37709
+ });
37710
+ outputJson2({ ...descriptor });
37711
+ } catch (error) {
37712
+ await handleError2(error);
37713
+ }
37714
+ });
37715
+ keys.command("encrypt <path>").description("Encrypt data with a named key and output the encrypted envelope as JSON").option("--in <file>", "Read the data to encrypt from a file (default: standard input)").option("--out <file>", "Write the envelope to a file (default: standard output)").action(async (path, options, cmd) => {
37716
+ try {
37717
+ const ctx = await deps.resolveContext(cmd.optsWithGlobals());
37718
+ assertInGrantedPrefix(await grantedPrefix(deps, ctx.profile), path, ctx.profile);
37719
+ if (options.out) await validatePrivateOutput(options.out, "--out");
37720
+ const plaintext = await readInput(options.in);
37721
+ const node = await deps.authenticate(ctx);
37722
+ const client = deps.createClient(node);
37723
+ const envelope = await withSpinner(`Encrypting with ${path}\u2026`, () => withNetworkSetup(node, () => client.encrypt(path, plaintext))).catch((error) => {
37724
+ throw cliErrorFromNamedKey(error, "encrypt", path);
37725
+ });
37726
+ const json = JSON.stringify(envelope);
37727
+ if (options.out) {
37728
+ await writePrivateOutput(options.out, new TextEncoder().encode(json));
37729
+ outputJson2({ keyPath: envelope.keyPath, networkId: envelope.networkId, written: options.out });
37730
+ return;
37731
+ }
37732
+ process.stdout.write(json + "\n");
37733
+ } catch (error) {
37734
+ await handleError2(error);
37735
+ }
37736
+ });
37737
+ keys.command("decrypt").description("Decrypt an envelope made with a named key and output the original data").option("--in <file>", "Read the envelope from a file (default: standard input)").option("--out <file>", "Write the decrypted data to a file (default: standard output)").action(async (options, cmd) => {
37738
+ try {
37739
+ const ctx = await deps.resolveContext(cmd.optsWithGlobals());
37740
+ const prefix = await grantedPrefix(deps, ctx.profile);
37741
+ if (options.out) await validatePrivateOutput(options.out, "--out");
37742
+ const envelope = parseEnvelope(await readInput(options.in));
37743
+ assertInGrantedPrefix(prefix, envelope.keyPath, ctx.profile);
37744
+ const profile = await deps.getProfile(ctx.profile);
37745
+ if (profile.ownerDid !== void 0 && envelope.networkId !== namedKeysNetworkId(profile.ownerDid)) {
37746
+ throw new CLIError(
37747
+ "KEY_OUTSIDE_GRANTED_PREFIX",
37748
+ "This envelope was made with a different encryption network than this profile's, so this profile can't decrypt it.",
37749
+ ExitCode.PERMISSION_DENIED
37750
+ );
37751
+ }
37752
+ const node = await deps.authenticate(ctx);
37753
+ const client = deps.createClient(node);
37754
+ const plaintext = await withSpinner("Decrypting\u2026", () => client.decrypt(envelope)).catch((error) => {
37755
+ throw cliErrorFromNamedKey(error, "decrypt", envelope.keyPath);
37756
+ });
37757
+ if (options.out) {
37758
+ await writePrivateOutput(options.out, plaintext);
37759
+ outputJson2({ keyPath: envelope.keyPath, written: options.out });
37760
+ return;
37761
+ }
37762
+ process.stdout.write(plaintext);
37763
+ } catch (error) {
37764
+ await handleError2(error);
37765
+ }
37766
+ });
37767
+ }
37768
+
37340
37769
  // src/commands/manifest.ts
37341
37770
  init_profiles();
37342
37771
  init_sdk();
@@ -37346,16 +37775,16 @@ init_errors();
37346
37775
  init_constants();
37347
37776
  init_theme();
37348
37777
  import { resolveManifestKnowledgeRoot as resolveManifestKnowledgeRoot2 } from "@tinycloud/sdk-core";
37349
- import { CLI_NAME as CLI_NAME26 } from "@tinycloud/operations/cli-name";
37350
- import { readFile as readFile12 } from "fs/promises";
37778
+ import { CLI_NAME as CLI_NAME28 } from "@tinycloud/operations/cli-name";
37779
+ import { readFile as readFile13 } from "fs/promises";
37351
37780
  var DEFAULT_APP_SPACE = "applications";
37352
37781
  function registerManifestCommand(program) {
37353
37782
  const manifest = program.command("manifest").description("Inspect TinyCloud app manifests");
37354
37783
  manifest.command("resolve <source>").description("Resolve a manifest file or URL to its effective space, paths, and DB basenames").addHelpText("after", `
37355
37784
 
37356
37785
  Examples:
37357
- $ ${CLI_NAME26} manifest resolve ./manifest.json
37358
- $ ${CLI_NAME26} manifest resolve https://app.example.com/manifest.json --json
37786
+ $ ${CLI_NAME28} manifest resolve ./manifest.json
37787
+ $ ${CLI_NAME28} manifest resolve https://app.example.com/manifest.json --json
37359
37788
 
37360
37789
  What it shows:
37361
37790
  - app_id, name, manifest_version
@@ -37365,7 +37794,7 @@ What it shows:
37365
37794
 
37366
37795
  This command is read-only and does NOT contact the node \u2014 it just resolves
37367
37796
  the manifest against the active profile's address/chain so you know which
37368
- \`--space\` and \`--db\` values to pass to other ${CLI_NAME26} commands.
37797
+ \`--space\` and \`--db\` values to pass to other ${CLI_NAME28} commands.
37369
37798
  `).action(async (source, _options, cmd) => {
37370
37799
  try {
37371
37800
  const globalOpts = cmd.optsWithGlobals();
@@ -37434,7 +37863,7 @@ ${theme.heading("SQL databases")}
37434
37863
  `);
37435
37864
  }
37436
37865
  process.stdout.write(theme.muted(`
37437
- Use with: ${CLI_NAME26} sql query --space ${spaceName} --db <db> "..."
37866
+ Use with: ${CLI_NAME28} sql query --space ${spaceName} --db <db> "..."
37438
37867
  `));
37439
37868
  }
37440
37869
  if (permissions.length > 0) {
@@ -37461,7 +37890,7 @@ async function loadManifestSource2(source) {
37461
37890
  }
37462
37891
  return response.text();
37463
37892
  }
37464
- return readFile12(source, "utf8");
37893
+ return readFile13(source, "utf8");
37465
37894
  }
37466
37895
  function prefixWithAppId(path, appId) {
37467
37896
  const slash = path.indexOf("/");
@@ -37554,7 +37983,7 @@ init_profiles();
37554
37983
  init_formatter();
37555
37984
  init_errors();
37556
37985
  init_constants();
37557
- import { CLI_NAME as CLI_NAME27 } from "@tinycloud/operations/cli-name";
37986
+ import { CLI_NAME as CLI_NAME29 } from "@tinycloud/operations/cli-name";
37558
37987
  import { createInterface as createInterface5 } from "readline";
37559
37988
  init_theme();
37560
37989
  init_types();
@@ -37700,9 +38129,9 @@ profile at command time. Precedence for every kv/sql command:
37700
38129
  explicit --space flag > profile defaultSpace > primary space.
37701
38130
 
37702
38131
  Examples:
37703
- $ ${CLI_NAME27} profile set-default-space applications
37704
- $ ${CLI_NAME27} profile set-default-space applications --profile cli-test
37705
- $ ${CLI_NAME27} profile set-default-space --unset
38132
+ $ ${CLI_NAME29} profile set-default-space applications
38133
+ $ ${CLI_NAME29} profile set-default-space applications --profile cli-test
38134
+ $ ${CLI_NAME29} profile set-default-space --unset
37706
38135
  `).action(async (name, options, cmd) => {
37707
38136
  try {
37708
38137
  const globalOpts = cmd.optsWithGlobals();
@@ -37714,7 +38143,7 @@ Examples:
37714
38143
  if (!options.unset && (name === void 0 || name === "")) {
37715
38144
  throw new CLIError(
37716
38145
  "USAGE_ERROR",
37717
- `Provide a space name (e.g. \`${CLI_NAME27} profile set-default-space applications\`) or pass --unset.`,
38146
+ `Provide a space name (e.g. \`${CLI_NAME29} profile set-default-space applications\`) or pass --unset.`,
37718
38147
  ExitCode.USAGE_ERROR
37719
38148
  );
37720
38149
  }
@@ -37773,7 +38202,7 @@ function parseOperatorType(raw) {
37773
38202
 
37774
38203
  // src/commands/replica.ts
37775
38204
  init_profile_replicas();
37776
- import { CLI_NAME as CLI_NAME28 } from "@tinycloud/operations/cli-name";
38205
+ import { CLI_NAME as CLI_NAME30 } from "@tinycloud/operations/cli-name";
37777
38206
  import { randomBytes as randomBytes7 } from "crypto";
37778
38207
  import { constants as fsConstants5 } from "fs";
37779
38208
  import { mkdir as mkdir5, open as open11, readdir as readdir6, rename as rename4, unlink as unlink5, lstat as lstat9 } from "fs/promises";
@@ -37914,11 +38343,11 @@ var EXIT_BY_CODE = {
37914
38343
  var READ_FAILURE = {
37915
38344
  absent: { code: "KEY_ABSENT", exit: ExitCode.NOT_FOUND, what: "is not in the replica (the replica covers its whole prefix)" },
37916
38345
  deleted: { code: "KEY_DELETED", exit: ExitCode.NOT_FOUND, what: "was deleted at the source" },
37917
- content_missing: { code: "CONTENT_MISSING", exit: ExitCode.NOT_FOUND, what: `is known but its content has not been fetched yet; run ${CLI_NAME28} replica sync` },
38346
+ 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` },
37918
38347
  coverage_incomplete: {
37919
38348
  code: "COVERAGE_INCOMPLETE",
37920
38349
  exit: ExitCode.NOT_FOUND,
37921
- what: `is not in the replica, which has not finished its first sync; run ${CLI_NAME28} replica sync`
38350
+ what: `is not in the replica, which has not finished its first sync; run ${CLI_NAME30} replica sync`
37922
38351
  },
37923
38352
  not_covered: { code: "NOT_COVERED", exit: ExitCode.USAGE_ERROR, what: "is outside the replica's prefix" }
37924
38353
  };
@@ -38036,7 +38465,7 @@ async function existingReplicaName(profile, option) {
38036
38465
  const names = await replicaNames(profile);
38037
38466
  if (names.length === 1) return names[0];
38038
38467
  if (names.length === 0) {
38039
- throw new ReplicaError(ReplicaErrorCode.NOT_FOUND, `Profile "${profile}" has no replicas. Create one with: ${CLI_NAME28} replica sync --prefix <prefix>`);
38468
+ throw new ReplicaError(ReplicaErrorCode.NOT_FOUND, `Profile "${profile}" has no replicas. Create one with: ${CLI_NAME30} replica sync --prefix <prefix>`);
38040
38469
  }
38041
38470
  throw new CLIError("USAGE_ERROR", `Profile "${profile}" has several replicas (${names.join(", ")}); pass --replica <name>.`, ExitCode.USAGE_ERROR);
38042
38471
  }
@@ -38144,9 +38573,9 @@ async function storedGrants(profile) {
38144
38573
  function grantHint(space, prefix) {
38145
38574
  return [
38146
38575
  "A replica needs a device grant with get and sync on its prefix:",
38147
- ` device: ${CLI_NAME28} auth request --cap tinycloud.kv:${space}:${prefix}:get,list,metadata,sync --expiry 30d --emit req.json`,
38148
- ` owner: ${CLI_NAME28} auth grant req.json > grant.json`,
38149
- ` device: ${CLI_NAME28} auth import grant.json`
38576
+ ` device: ${CLI_NAME30} auth request --cap tinycloud.kv:${space}:${prefix}:get,list,metadata,sync --expiry 30d --emit req.json`,
38577
+ ` owner: ${CLI_NAME30} auth grant req.json > grant.json`,
38578
+ ` device: ${CLI_NAME30} auth import grant.json`
38150
38579
  ].join("\n");
38151
38580
  }
38152
38581
  function resolveSpace(input, prefix, grants) {
@@ -38237,7 +38666,7 @@ function describeStatus(status) {
38237
38666
  formatField("Size", formatBytes(status.bytes)),
38238
38667
  formatField("Grant", status.device.delegationCid),
38239
38668
  formatField("Last error", status.lastError ? `${status.lastError.code}: ${status.lastError.message}` : null),
38240
- formatField("Purge", status.purgePending ? `incomplete: content files remain; the next ${CLI_NAME28} replica command retries` : null)
38669
+ formatField("Purge", status.purgePending ? `incomplete: content files remain; the next ${CLI_NAME30} replica command retries` : null)
38241
38670
  ].join("\n");
38242
38671
  }
38243
38672
  async function writeFileAtomic(path, bytes) {
@@ -38401,7 +38830,7 @@ or coverage incomplete; 5 grant expired, revoked or not yet valid; 6 network;
38401
38830
  (options, cmd) => run(async () => {
38402
38831
  const profile = await profileName(cmd);
38403
38832
  if (options.purge) {
38404
- throw new CLIError("REPORT_READ_ONLY", `Replica report is read-only. To remove a named local replica, run ${CLI_NAME28} replica reset --replica <name> --purge; flag-owned purge has no equivalent yet (TC-1309).`, ExitCode.USAGE_ERROR);
38833
+ 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);
38405
38834
  }
38406
38835
  if (options.clearPending) {
38407
38836
  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);
@@ -38503,7 +38932,7 @@ or coverage incomplete; 5 grant expired, revoked or not yet valid; 6 network;
38503
38932
  if (differs !== void 0) {
38504
38933
  throw new ReplicaError(
38505
38934
  ReplicaErrorCode.CONFIG_MISMATCH,
38506
- `Replica ${name} already follows ${differs}. Use another --replica name, or ${CLI_NAME28} replica reset --replica ${name} --purge first.`
38935
+ `Replica ${name} already follows ${differs}. Use another --replica name, or ${CLI_NAME30} replica reset --replica ${name} --purge first.`
38507
38936
  );
38508
38937
  }
38509
38938
  }
@@ -38668,8 +39097,8 @@ init_theme();
38668
39097
  init_errors();
38669
39098
  init_warnings();
38670
39099
  init_constants();
38671
- import { CLI_NAME as CLI_NAME30 } from "@tinycloud/operations/cli-name";
38672
- import { readFile as readFile13 } from "fs/promises";
39100
+ import { CLI_NAME as CLI_NAME32 } from "@tinycloud/operations/cli-name";
39101
+ import { readFile as readFile14 } from "fs/promises";
38673
39102
  import { join as join17 } from "path";
38674
39103
  import { homedir, hostname, userInfo } from "os";
38675
39104
  import { appSecretScope } from "@tinycloud/sdk-core";
@@ -38879,7 +39308,7 @@ function parseStatusEnvelope(line) {
38879
39308
  // src/lib/secrets-wait.ts
38880
39309
  init_constants();
38881
39310
  init_errors();
38882
- import { CLI_NAME as CLI_NAME29 } from "@tinycloud/operations/cli-name";
39311
+ import { CLI_NAME as CLI_NAME31 } from "@tinycloud/operations/cli-name";
38883
39312
  var WAIT_BACKOFF_MS = [3e3, 4500, 6750, 10125];
38884
39313
  var WAIT_STEADY_INTERVAL_MS = 15e3;
38885
39314
  var DEFAULT_WAIT_TIMEOUT = "5m";
@@ -38887,7 +39316,7 @@ var MAX_WAIT_TIMEOUT_MS = 9 * 6e4;
38887
39316
  var MAX_CONSECUTIVE_FAILURES = 5;
38888
39317
  var TIMEOUT_RE = /^(0|\d+s|\d+m)$/;
38889
39318
  var RETRYABLE_STATUS_ERRORS = { NODE_UNREACHABLE: true, NODE_ERROR: true };
38890
- var EXPLICIT_KEY_HINT = `${CLI_NAME29} secrets wait never signs in, and an explicit private key has no stored session. Use a local profile signed in once with ${CLI_NAME29} --profile <profile> auth login --method local, without TC_PRIVATE_KEY.`;
39319
+ 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.`;
38891
39320
  function abortableSleep(ms2, signal) {
38892
39321
  return new Promise((resolve9) => {
38893
39322
  if (signal.aborted) {
@@ -38923,7 +39352,7 @@ async function runSecretsWait(request, deps) {
38923
39352
  let step = 0;
38924
39353
  const cancelled = () => new CLIError(
38925
39354
  "CANCELLED",
38926
- `Interrupted: ${CLI_NAME29} secrets wait stopped before the secrets were ready.`,
39355
+ `Interrupted: ${CLI_NAME31} secrets wait stopped before the secrets were ready.`,
38927
39356
  ExitCode.CANCELLED,
38928
39357
  progress === void 0 ? {} : { progress }
38929
39358
  );
@@ -39067,13 +39496,13 @@ function statusError(error, profile, warnings) {
39067
39496
  if (error.code === "AUTH_REQUIRED" || error.code === "SESSION_NOT_FOUND") {
39068
39497
  const recovery = recoveryOf(error, "sign_in");
39069
39498
  if (recovery !== void 0) {
39070
- const hint = recovery.command !== void 0 ? `Sign in once with: ${recovery.command}, then retry.` : `Sign in to profile "${profile}" once with ${CLI_NAME29} auth login, then retry.`;
39499
+ 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.`;
39071
39500
  return new CLIError("AUTH_REQUIRED", error.message, ExitCode.AUTH_REQUIRED, { hint, recovery, ...extra });
39072
39501
  }
39073
39502
  }
39074
39503
  if (error.code === "PROFILE_MIGRATION_REQUIRED") {
39075
39504
  const recovery = recoveryOf(error, "complete_migration");
39076
- const hint = recovery?.command !== void 0 ? `Run ${recovery.command} once to finish this profile's one-time upgrade, then retry wait.` : `Run ${CLI_NAME29} auth caps once with profile "${profile}" to finish its one-time upgrade, then retry wait.`;
39505
+ 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.`;
39077
39506
  return new CLIError("PROFILE_MIGRATION_REQUIRED", error.message, ExitCode.ERROR, {
39078
39507
  hint,
39079
39508
  ...recovery === void 0 ? {} : { recovery },
@@ -39099,7 +39528,7 @@ var SECRET_KV_ABILITIES = {
39099
39528
  del: secretCapabilityAction("del"),
39100
39529
  list: secretCapabilityAction("list")
39101
39530
  };
39102
- async function readStdin3() {
39531
+ async function readStdin4() {
39103
39532
  const chunks = [];
39104
39533
  for await (const chunk of process.stdin) {
39105
39534
  chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
@@ -39265,11 +39694,11 @@ async function runSecretOperation(params) {
39265
39694
  }
39266
39695
  function secretPermissionReason(action, name) {
39267
39696
  const target = name ? ` secret "${name}"` : " secrets";
39268
- return `Allow \`${CLI_NAME30} secrets ${action}${name ? ` ${name}` : ""}\` to access${target} with the required TinyCloud permissions.`;
39697
+ return `Allow \`${CLI_NAME32} secrets ${action}${name ? ` ${name}` : ""}\` to access${target} with the required TinyCloud permissions.`;
39269
39698
  }
39270
39699
  function assertOwnerApprovalPossible(profileName2, profile, action, name, missing = []) {
39271
39700
  if (profile.authMethod !== "openkey" || process.stdin.isTTY || process.stderr.isTTY) return;
39272
- const command = `${CLI_NAME30} secrets ${action === "del" ? "delete" : action}${name ? ` ${name}` : ""}`;
39701
+ const command = `${CLI_NAME32} secrets ${action === "del" ? "delete" : action}${name ? ` ${name}` : ""}`;
39273
39702
  throw new CLIError(
39274
39703
  "PERMISSION_DENIED",
39275
39704
  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.`,
@@ -39280,7 +39709,7 @@ function assertOwnerApprovalPossible(profileName2, profile, action, name, missin
39280
39709
  );
39281
39710
  }
39282
39711
  function scopedSecretLoginHint(profileName2) {
39283
- return `Have the owner approve a scoped login whose manifest names the secret: ${CLI_NAME30} --profile ${profileName2} auth login --method openkey --paste --manifest <manifest.json>. Pass the owner's code on stdin, newline-terminated.`;
39712
+ 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.`;
39284
39713
  }
39285
39714
  async function runSecretOperationAttempt(label, operation) {
39286
39715
  try {
@@ -39414,7 +39843,7 @@ ${message}`,
39414
39843
  "AUTH_REQUIRED",
39415
39844
  "Not signed in to TinyCloud.",
39416
39845
  ExitCode.AUTH_REQUIRED,
39417
- { hint: `Sign in with: ${CLI_NAME30} --profile ${result.context.profile} auth login` }
39846
+ { hint: `Sign in with: ${CLI_NAME32} --profile ${result.context.profile} auth login` }
39418
39847
  );
39419
39848
  }
39420
39849
  if (result.error.code === "NODE_UNREACHABLE") {
@@ -39563,7 +39992,7 @@ function permissionsFromDelegation2(delegation) {
39563
39992
  }
39564
39993
  async function loadDelegationCandidates(source) {
39565
39994
  try {
39566
- const raw = JSON.parse(await readFile13(source, "utf8"));
39995
+ const raw = JSON.parse(await readFile14(source, "utf8"));
39567
39996
  return normalizeDelegationCandidates(raw, source);
39568
39997
  } catch (error) {
39569
39998
  if (error?.code !== "ENOENT") {
@@ -39583,7 +40012,7 @@ async function loadDelegationCandidates(source) {
39583
40012
  }
39584
40013
  try {
39585
40014
  const importedPath = join17(resolveProfilesDir(), source, "additional-delegations.json");
39586
- const raw = JSON.parse(await readFile13(importedPath, "utf8"));
40015
+ const raw = JSON.parse(await readFile14(importedPath, "utf8"));
39587
40016
  return normalizeDelegationCandidates(raw, source);
39588
40017
  } catch (error) {
39589
40018
  if (error?.code === "ENOENT") {
@@ -39891,7 +40320,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
39891
40320
  name: "Encryption network",
39892
40321
  ok: false,
39893
40322
  detail: `${networkName} not found`,
39894
- hint: `${CLI_NAME30} secrets network init ${networkName}`
40323
+ hint: `${CLI_NAME32} secrets network init ${networkName}`
39895
40324
  }
39896
40325
  ];
39897
40326
  let secret;
@@ -39937,7 +40366,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
39937
40366
  name: "Secret access",
39938
40367
  ok: false,
39939
40368
  detail: notFound ? `${resolved.permissionPaths.vault} not found` : result.error.message,
39940
- hint: notFound ? local ? `${CLI_NAME30} secrets put ${resolved.name}${formatSecretScopeFlag(scopeOptions)} --stdin` : `${CLI_NAME30} secrets request ${resolved.name}${formatSecretScopeFlag(scopeOptions)}` : `Ask the owner profile to grant ${secretCapabilityAction("get")} and ${SECRET_DECRYPT_CAPABILITY}.`
40369
+ 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}.`
39941
40370
  });
39942
40371
  }
39943
40372
  } else {
@@ -40090,9 +40519,9 @@ function registerSecretsCommand(program, openKeyAcquisition) {
40090
40519
  throw new CLIError("USAGE_ERROR", "Provide only one of: value argument, --file, or --stdin", ExitCode.USAGE_ERROR);
40091
40520
  }
40092
40521
  if (options.file) {
40093
- secretValue = await readFile13(options.file, "utf-8");
40522
+ secretValue = await readFile14(options.file, "utf-8");
40094
40523
  } else if (options.stdin) {
40095
- secretValue = (await readStdin3()).toString("utf-8");
40524
+ secretValue = (await readStdin4()).toString("utf-8");
40096
40525
  } else {
40097
40526
  secretValue = value;
40098
40527
  }
@@ -40181,7 +40610,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
40181
40610
  if (effectiveLocal(profile, options)) {
40182
40611
  throw new CLIError(
40183
40612
  "LOCAL_OWNER_PROFILE",
40184
- `Profile "${ctx.profile}" uses a local key, which owns its secrets: store them with ${CLI_NAME30} secrets put NAME --stdin instead of requesting them.`,
40613
+ `Profile "${ctx.profile}" uses a local key, which owns its secrets: store them with ${CLI_NAME32} secrets put NAME --stdin instead of requesting them.`,
40185
40614
  ExitCode.USAGE_ERROR
40186
40615
  );
40187
40616
  }
@@ -40304,7 +40733,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
40304
40733
 
40305
40734
  // src/commands/share.ts
40306
40735
  import { Option } from "commander";
40307
- import { CLI_NAME as CLI_NAME31 } from "@tinycloud/operations/cli-name";
40736
+ import { CLI_NAME as CLI_NAME33 } from "@tinycloud/operations/cli-name";
40308
40737
  import { ProfileLockTimeoutError as ProfileLockTimeoutError3 } from "@tinycloud/operations/state";
40309
40738
  init_errors();
40310
40739
 
@@ -40399,7 +40828,7 @@ function receiveJson(result, path) {
40399
40828
 
40400
40829
  // src/share/io.ts
40401
40830
  import { constants } from "fs";
40402
- import { lstat as lstat10, mkdir as mkdir6, mkdtemp, open as open13, readFile as readFile14, realpath as realpath2, stat as stat6, link as link3, rename as rename5, rm as rm6, unlink as unlink6 } from "fs/promises";
40831
+ import { lstat as lstat10, mkdir as mkdir6, mkdtemp, open as open13, readFile as readFile15, realpath as realpath2, stat as stat6, link as link3, rename as rename5, rm as rm6, unlink as unlink6 } from "fs/promises";
40403
40832
  import { randomBytes as randomBytes8 } from "crypto";
40404
40833
  import { basename as basename4, join as join18, resolve as resolve6, sep as sep3 } from "path";
40405
40834
  init_errors();
@@ -40446,7 +40875,7 @@ async function readShareInput(input, name, limit = MAX_SHARE_STDIN_BYTES) {
40446
40875
  const path = resolve6(input);
40447
40876
  const info = await stat6(path);
40448
40877
  if (!info.isFile() || info.size > limit) throw new Error("MAX_BYTES_EXCEEDED");
40449
- const bytes = new Uint8Array(await readFile14(path));
40878
+ const bytes = new Uint8Array(await readFile15(path));
40450
40879
  if (bytes.byteLength > limit) throw new Error("MAX_BYTES_EXCEEDED");
40451
40880
  return { bytes, filename };
40452
40881
  }
@@ -41765,7 +42194,7 @@ function shareCliError(error, operation = "publish") {
41765
42194
  "SHARE_HISTORY_UNREADABLE",
41766
42195
  "sender history cannot be read with the current session key",
41767
42196
  1,
41768
- { hint: `Run \`${CLI_NAME31} share history recover\` to move the unreadable file aside. The old file is kept, not deleted.` }
42197
+ { hint: `Run \`${CLI_NAME33} share history recover\` to move the unreadable file aside. The old file is kept, not deleted.` }
41769
42198
  );
41770
42199
  }
41771
42200
  if (error instanceof SharePublishAuthorityError) {
@@ -41773,12 +42202,12 @@ function shareCliError(error, operation = "publish") {
41773
42202
  const profileName2 = "profileName" in failure ? failure.profileName : void 0;
41774
42203
  const localKey = "localKey" in failure && failure.localKey === true;
41775
42204
  const profileHint = profileName2 === void 0 ? "" : `--profile ${profileName2} `;
41776
- const loginHint = localKey ? `\`${CLI_NAME31} ${profileHint}auth login --method local\`` : `\`${CLI_NAME31} ${profileHint}auth login --device --manifest builtin:share-publishing\` (or \`${CLI_NAME31} ${profileHint}enable share\`)`;
42205
+ 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\`)`;
41777
42206
  if (failure.kind === "caveated-session") {
41778
42207
  const holder = profileName2 === void 0 ? "this session" : `profile ${profileName2}'s session`;
41779
42208
  return new CLIError(
41780
42209
  "PERMISSION_DENIED",
41781
- `${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_NAME31} init --name publisher --key-only && ${CLI_NAME31} --profile publisher enable share\``,
42210
+ `${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\``,
41782
42211
  5
41783
42212
  );
41784
42213
  }
@@ -41879,7 +42308,7 @@ function shareCliError(error, operation = "publish") {
41879
42308
  if (nodeCode === "ENOENT") return new CLIError("INVALID_ARGUMENT", "share input was not found", 2);
41880
42309
  if (nodeCode === "EISDIR") return new CLIError("INVALID_ARGUMENT", "share input must be a Markdown file", 2);
41881
42310
  const mapped = typeof nodeCode === "string" && known[nodeCode] !== void 0 ? known[nodeCode] : known[message];
41882
- const classified = mapped === void 0 ? classifyShareFailure(error, `\`${CLI_NAME31} auth login\``) : void 0;
42311
+ const classified = mapped === void 0 ? classifyShareFailure(error, `\`${CLI_NAME33} auth login\``) : void 0;
41883
42312
  if (classified !== void 0) return Object.assign(new CLIError(classified.code, classified.reason, classified.exitCode), { cause: error });
41884
42313
  if (error instanceof TypeError) return new CLIError("INVALID_ARGUMENT", "share input is invalid", 2);
41885
42314
  return new CLIError(mapped?.code ?? "INVALID_ARGUMENT", mapped ? mapped.code : "share operation failed", mapped?.exit ?? 2);
@@ -42289,7 +42718,7 @@ init_constants();
42289
42718
  init_sdk();
42290
42719
  init_host();
42291
42720
  init_theme();
42292
- import { CLI_NAME as CLI_NAME32 } from "@tinycloud/operations/cli-name";
42721
+ import { CLI_NAME as CLI_NAME34 } from "@tinycloud/operations/cli-name";
42293
42722
  import { ACCOUNT_REGISTRY_SPACE } from "@tinycloud/node-sdk";
42294
42723
  import { randomBytes as randomBytes9 } from "crypto";
42295
42724
  import { mkdir as mkdir7, writeFile as writeFile4 } from "fs/promises";
@@ -42336,7 +42765,7 @@ function registerSpaceCommand(program) {
42336
42765
  } else {
42337
42766
  const profile = await ProfileManager.getProfile(ctx.profile);
42338
42767
  const cap = "tinycloud.kv:account:spaces/:get,list";
42339
- const hint = resolveProfilePosture(profile) === "delegate-session" ? `Ask the space owner to grant it. Create the request with: ${CLI_NAME32} --profile ${ctx.profile} auth request --cap ${cap} --emit` : `Grant it to this session with: ${CLI_NAME32} --profile ${ctx.profile} auth request --grant --cap ${cap}. The default OpenKey owner sign-in covers only the default space, so it includes neither.`;
42768
+ 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.`;
42340
42769
  throw new CLIError(
42341
42770
  "PERMISSION_DENIED",
42342
42771
  `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.`,
@@ -42373,10 +42802,10 @@ function registerSpaceCommand(program) {
42373
42802
 
42374
42803
  A delegate cannot host a space \u2014 only its owner (root authority) can. This
42375
42804
  emits a tinycloud.host.request artifact naming the space and its owner so you
42376
- can hand it to the owner; they run \`${CLI_NAME32} space host <name>\` and confirm.
42805
+ can hand it to the owner; they run \`${CLI_NAME34} space host <name>\` and confirm.
42377
42806
 
42378
42807
  If you ARE the owner of the resolved space, this refuses and tells you to host
42379
- it directly with \`${CLI_NAME32} space host <name>\` (no request needed).
42808
+ it directly with \`${CLI_NAME34} space host <name>\` (no request needed).
42380
42809
  `).action(async (name, options, cmd) => {
42381
42810
  try {
42382
42811
  const globalOpts = cmd.optsWithGlobals();
@@ -42387,7 +42816,7 @@ it directly with \`${CLI_NAME32} space host <name>\` (no request needed).
42387
42816
  if (await isRootAuthority(spaceId, ctx.profile)) {
42388
42817
  throw new CLIError(
42389
42818
  "ALREADY_ROOT_AUTHORITY",
42390
- `You are the owner of ${spaceId}. Host it directly: ${CLI_NAME32} space host ${spaceName}`,
42819
+ `You are the owner of ${spaceId}. Host it directly: ${CLI_NAME34} space host ${spaceName}`,
42391
42820
  ExitCode.USAGE_ERROR
42392
42821
  );
42393
42822
  }
@@ -42473,7 +42902,7 @@ init_sdk();
42473
42902
  init_space();
42474
42903
  init_host();
42475
42904
  init_theme();
42476
- import { CLI_NAME as CLI_NAME33 } from "@tinycloud/operations/cli-name";
42905
+ import { CLI_NAME as CLI_NAME35 } from "@tinycloud/operations/cli-name";
42477
42906
  import { writeFile as writeFile5 } from "fs/promises";
42478
42907
  import { resolve as resolve8 } from "path";
42479
42908
  async function dbHandle(node, dbName, spaceInput, profileName2) {
@@ -42497,12 +42926,12 @@ database for simple apps, or pass --db to target a named database. Pass
42497
42926
  --space to target a non-primary space (e.g. the manifest "applications" space).
42498
42927
 
42499
42928
  Common workflows:
42500
- $ ${CLI_NAME33} sql execute "CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT)"
42501
- $ ${CLI_NAME33} sql execute "INSERT INTO notes (body) VALUES (?)" --params '["ship docs"]'
42502
- $ ${CLI_NAME33} sql query "SELECT id, body FROM notes ORDER BY id"
42503
- $ ${CLI_NAME33} sql query "SELECT * FROM events WHERE type = ?" --db analytics --params '["signup"]'
42504
- $ ${CLI_NAME33} sql query "SELECT count(*) FROM conversation" --space applications --db xyz.tinycloud.listen/conversations
42505
- $ ${CLI_NAME33} sql export --db analytics --output analytics.db
42929
+ $ ${CLI_NAME35} sql execute "CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT)"
42930
+ $ ${CLI_NAME35} sql execute "INSERT INTO notes (body) VALUES (?)" --params '["ship docs"]'
42931
+ $ ${CLI_NAME35} sql query "SELECT id, body FROM notes ORDER BY id"
42932
+ $ ${CLI_NAME35} sql query "SELECT * FROM events WHERE type = ?" --db analytics --params '["signup"]'
42933
+ $ ${CLI_NAME35} sql query "SELECT count(*) FROM conversation" --space applications --db xyz.tinycloud.listen/conversations
42934
+ $ ${CLI_NAME35} sql export --db analytics --output analytics.db
42506
42935
 
42507
42936
  Commands:
42508
42937
  query Read rows with SELECT statements
@@ -42519,10 +42948,10 @@ Tips:
42519
42948
  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", `
42520
42949
 
42521
42950
  Examples:
42522
- $ ${CLI_NAME33} sql query "SELECT * FROM notes ORDER BY id"
42523
- $ ${CLI_NAME33} sql query "SELECT * FROM notes WHERE id = ?" --params '[42]'
42524
- $ ${CLI_NAME33} sql query "SELECT count(*) AS total FROM events" --db analytics --json
42525
- $ ${CLI_NAME33} sql query "SELECT count(*) FROM conversation" --space applications --db xyz.tinycloud.listen/conversations
42951
+ $ ${CLI_NAME35} sql query "SELECT * FROM notes ORDER BY id"
42952
+ $ ${CLI_NAME35} sql query "SELECT * FROM notes WHERE id = ?" --params '[42]'
42953
+ $ ${CLI_NAME35} sql query "SELECT count(*) AS total FROM events" --db analytics --json
42954
+ $ ${CLI_NAME35} sql query "SELECT count(*) FROM conversation" --space applications --db xyz.tinycloud.listen/conversations
42526
42955
 
42527
42956
  Output:
42528
42957
  Human output is formatted as a table. Piped output or --json returns
@@ -42563,11 +42992,11 @@ ${rowCount} row${rowCount === 1 ? "" : "s"} returned`) + "\n");
42563
42992
  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", `
42564
42993
 
42565
42994
  Examples:
42566
- $ ${CLI_NAME33} sql execute "CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT)"
42567
- $ ${CLI_NAME33} sql execute "INSERT INTO notes (body) VALUES (?)" --params '["first note"]'
42568
- $ ${CLI_NAME33} sql execute "UPDATE notes SET body = ? WHERE id = ?" --params '["edited", 1]'
42569
- $ ${CLI_NAME33} sql execute "DROP TABLE old_notes" --db archive
42570
- $ ${CLI_NAME33} sql execute "DELETE FROM conversation WHERE id = ?" --space applications --db xyz.tinycloud.listen/conversations --params '["abc"]'
42995
+ $ ${CLI_NAME35} sql execute "CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT)"
42996
+ $ ${CLI_NAME35} sql execute "INSERT INTO notes (body) VALUES (?)" --params '["first note"]'
42997
+ $ ${CLI_NAME35} sql execute "UPDATE notes SET body = ? WHERE id = ?" --params '["edited", 1]'
42998
+ $ ${CLI_NAME35} sql execute "DROP TABLE old_notes" --db archive
42999
+ $ ${CLI_NAME35} sql execute "DELETE FROM conversation WHERE id = ?" --space applications --db xyz.tinycloud.listen/conversations --params '["abc"]'
42571
43000
 
42572
43001
  Output:
42573
43002
  Returns JSON with the changed row count and last inserted row id when available.
@@ -42596,9 +43025,9 @@ Output:
42596
43025
  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", `
42597
43026
 
42598
43027
  Examples:
42599
- $ ${CLI_NAME33} sql export
42600
- $ ${CLI_NAME33} sql export --db analytics --output analytics.db
42601
- $ ${CLI_NAME33} sql export --space applications --db xyz.tinycloud.listen/conversations --output listen.db
43028
+ $ ${CLI_NAME35} sql export
43029
+ $ ${CLI_NAME35} sql export --db analytics --output analytics.db
43030
+ $ ${CLI_NAME35} sql export --space applications --db xyz.tinycloud.listen/conversations --output listen.db
42602
43031
 
42603
43032
  Output:
42604
43033
  Writes the database file locally and returns JSON with the path and size.
@@ -42631,19 +43060,19 @@ Output:
42631
43060
  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", `
42632
43061
 
42633
43062
  Examples:
42634
- $ ${CLI_NAME33} sql copy --from-db com.tinycloud.conversation-sync/conversations \\
43063
+ $ ${CLI_NAME35} sql copy --from-db com.tinycloud.conversation-sync/conversations \\
42635
43064
  --to-db xyz.tinycloud.listen/conversations \\
42636
43065
  --space applications --dry-run
42637
- $ ${CLI_NAME33} sql copy --from-space applications --from-db com.foo/data \\
43066
+ $ ${CLI_NAME35} sql copy --from-space applications --from-db com.foo/data \\
42638
43067
  --to-space applications --to-db com.bar/data \\
42639
43068
  --table conversation --table participant
42640
43069
 
42641
43070
  Notes:
42642
43071
  - Refuses to run when (resolved space, db) is identical for source and destination.
42643
- - Does NOT create destination tables. Run the target app once (or use \`${CLI_NAME33} sql execute\`)
43072
+ - Does NOT create destination tables. Run the target app once (or use \`${CLI_NAME35} sql execute\`)
42644
43073
  to materialize the schema before copying.
42645
43074
  - One row at a time; suitable for small/medium datasets. Large copies should
42646
- use \`${CLI_NAME33} sql export\` + bulk import.
43075
+ use \`${CLI_NAME35} sql export\` + bulk import.
42647
43076
  - Authorization: the active session/delegation must cover sql/read on source
42648
43077
  AND sql/write on destination. Otherwise the relevant operation will fail.
42649
43078
  `).action(async (options, cmd) => {
@@ -42743,7 +43172,7 @@ init_errors();
42743
43172
  init_formatter();
42744
43173
  init_theme();
42745
43174
  init_permissions();
42746
- import { CLI_NAME as CLI_NAME34 } from "@tinycloud/operations/cli-name";
43175
+ import { CLI_NAME as CLI_NAME36 } from "@tinycloud/operations/cli-name";
42747
43176
  import {
42748
43177
  NodeWasmBindings as NodeWasmBindings2
42749
43178
  } from "@tinycloud/node-sdk";
@@ -43017,7 +43446,7 @@ function formatStatus(summary) {
43017
43446
  lines.push(`Default profile: ${theme.value(summary.defaultProfile)}`);
43018
43447
  lines.push("");
43019
43448
  if (summary.profiles.length === 0) {
43020
- lines.push(theme.muted(`No profiles configured. Run: ${CLI_NAME34} init`));
43449
+ lines.push(theme.muted(`No profiles configured. Run: ${CLI_NAME36} init`));
43021
43450
  return `${lines.join("\n")}
43022
43451
  `;
43023
43452
  }
@@ -43173,14 +43602,14 @@ function registerUpgradeCommand(program) {
43173
43602
  }
43174
43603
 
43175
43604
  // src/commands/vault.ts
43176
- import { readFile as readFile15 } from "fs/promises";
43605
+ import { readFile as readFile16 } from "fs/promises";
43177
43606
  init_profiles();
43178
43607
  init_formatter();
43179
43608
  init_errors();
43180
43609
  init_constants();
43181
43610
  init_sdk();
43182
43611
  import { PrivateKeySigner as PrivateKeySigner2 } from "@tinycloud/node-sdk";
43183
- async function readStdin4() {
43612
+ async function readStdin5() {
43184
43613
  const chunks = [];
43185
43614
  for await (const chunk of process.stdin) {
43186
43615
  chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
@@ -43235,9 +43664,9 @@ function registerVaultCommand(program) {
43235
43664
  throw new CLIError("USAGE_ERROR", "Provide only one of: value argument, --file, or --stdin", ExitCode.USAGE_ERROR);
43236
43665
  }
43237
43666
  if (options.file) {
43238
- putValue = new Uint8Array(await readFile15(options.file));
43667
+ putValue = new Uint8Array(await readFile16(options.file));
43239
43668
  } else if (options.stdin) {
43240
- putValue = new Uint8Array(await readStdin4());
43669
+ putValue = new Uint8Array(await readStdin5());
43241
43670
  } else {
43242
43671
  putValue = value;
43243
43672
  }
@@ -43350,14 +43779,14 @@ function registerVaultCommand(program) {
43350
43779
  }
43351
43780
 
43352
43781
  // src/commands/vars.ts
43353
- import { readFile as readFile16 } from "fs/promises";
43782
+ import { readFile as readFile17 } from "fs/promises";
43354
43783
  init_profiles();
43355
43784
  init_formatter();
43356
43785
  init_errors();
43357
43786
  init_constants();
43358
43787
  init_sdk();
43359
43788
  var VARIABLES_PREFIX = "variables/";
43360
- async function readStdin5() {
43789
+ async function readStdin6() {
43361
43790
  const chunks = [];
43362
43791
  for await (const chunk of process.stdin) {
43363
43792
  chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
@@ -43455,9 +43884,9 @@ function registerVarsCommand(program) {
43455
43884
  throw new CLIError("USAGE_ERROR", "Provide only one of: value argument, --file, or --stdin", ExitCode.USAGE_ERROR);
43456
43885
  }
43457
43886
  if (options.file) {
43458
- varValue = await readFile16(options.file, "utf-8");
43887
+ varValue = await readFile17(options.file, "utf-8");
43459
43888
  } else if (options.stdin) {
43460
- varValue = (await readStdin5()).toString("utf-8");
43889
+ varValue = (await readStdin6()).toString("utf-8");
43461
43890
  } else {
43462
43891
  varValue = value;
43463
43892
  }
@@ -43500,6 +43929,7 @@ function registerTinyCloudCommands(program) {
43500
43929
  registerAuthCommand(program);
43501
43930
  registerEnableCommand(program);
43502
43931
  registerKvCommand(program);
43932
+ registerKeysCommand(program);
43503
43933
  registerReplicaCommand(program);
43504
43934
  registerCourierCommand(program);
43505
43935
  registerSpaceCommand(program);