@tinycloud/cli 1.1.0-beta.13 → 1.1.0-beta.14

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.
@@ -165,8 +165,14 @@ import ora from "ora";
165
165
  function outputJson(data) {
166
166
  process.stdout.write(JSON.stringify(data, null, 2) + "\n");
167
167
  }
168
- function outputError(code3, message, hint, meta) {
169
- if (isInteractive()) {
168
+ function warningLine(warnings) {
169
+ const which = warnings.map((warning) => `${warning.grantCid ?? "unidentified"}: ${warning.reason}`).join(", ");
170
+ const subject = warnings.length === 1 ? "A stored grant was" : `${warnings.length} stored grants were`;
171
+ return `${subject} not used (${which}). Approve access again when a command needs it; the old grant expires on its own.`;
172
+ }
173
+ function outputError(code3, message, hint, details = {}) {
174
+ const { meta, warnings = [] } = details;
175
+ if (!shouldOutputJson()) {
170
176
  process.stderr.write(
171
177
  `${theme.error("\u2717")} ${theme.label(code3)}: ${message}
172
178
  `
@@ -177,20 +183,40 @@ function outputError(code3, message, hint, meta) {
177
183
  `);
178
184
  }
179
185
  }
186
+ if (warnings.length > 0) process.stderr.write(` ${theme.warn(warningLine(warnings))}
187
+ `);
180
188
  } else {
181
189
  const payload = {
182
190
  error: { code: code3, message }
183
191
  };
184
192
  if (hint) payload.error.hint = hint;
185
193
  if (meta) payload.error.meta = meta;
194
+ if (warnings.length > 0) payload.error.warnings = warnings;
186
195
  process.stderr.write(JSON.stringify(payload, null, 2) + "\n");
187
196
  }
188
197
  }
198
+ function outputWarnings(warnings) {
199
+ if (warnings.length === 0) return;
200
+ process.stderr.write(shouldOutputJson() ? `${JSON.stringify({ warnings })}
201
+ ` : `${theme.warn("!")} ${warningLine(warnings)}
202
+ `);
203
+ }
204
+ function operationWarnings(value) {
205
+ if (!Array.isArray(value)) return [];
206
+ return value.flatMap((warning) => {
207
+ if (warning === null || typeof warning !== "object") return [];
208
+ const { code: code3, reason, grantCid } = warning;
209
+ if (typeof code3 !== "string" || !WARNING_CODE.test(code3) || typeof reason !== "string" || !WARNING_REASON.test(reason)) {
210
+ return [];
211
+ }
212
+ return [{ code: code3, reason, ...typeof grantCid === "string" && DELEGATION_CID.test(grantCid) ? { grantCid } : {} }];
213
+ });
214
+ }
189
215
  function isInteractive() {
190
216
  return Boolean(process.stdout.isTTY);
191
217
  }
192
218
  async function withSpinner(label, fn) {
193
- if (!isInteractive()) {
219
+ if (shouldOutputJson()) {
194
220
  return fn();
195
221
  }
196
222
  const spinner = ora(label).start();
@@ -204,7 +230,7 @@ async function withSpinner(label, fn) {
204
230
  }
205
231
  }
206
232
  function shouldOutputJson() {
207
- return !isInteractive() || process.argv.includes("--json");
233
+ return !isInteractive() || (globalThis[JSON_REQUESTED] ?? process.argv.includes("--json"));
208
234
  }
209
235
  function formatField(label, value) {
210
236
  if (value === null || value === void 0) return ` ${theme.label(label + ":")} ${theme.muted("\u2014")}`;
@@ -253,10 +279,15 @@ function formatTimeAgo(date) {
253
279
  if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`;
254
280
  return `${Math.floor(seconds / 86400)}d ago`;
255
281
  }
282
+ var WARNING_CODE, WARNING_REASON, DELEGATION_CID, JSON_REQUESTED;
256
283
  var init_formatter = __esm({
257
284
  "src/output/formatter.ts"() {
258
285
  "use strict";
259
286
  init_theme();
287
+ WARNING_CODE = /^[A-Z][A-Z_]{0,63}$/;
288
+ WARNING_REASON = /^[a-z][a-z_]{0,63}$/;
289
+ DELEGATION_CID = /^bafkr4i[a-z2-7]{52}$/;
290
+ JSON_REQUESTED = /* @__PURE__ */ Symbol.for("tinycloud.cli.jsonOutputRequested");
260
291
  }
261
292
  });
262
293
 
@@ -361,11 +392,12 @@ function cliErrorFromService(error, message = error.message) {
361
392
  return missingPrivateKeyError();
362
393
  }
363
394
  const missingCapability = validatedCapabilityOf({ ...error, meta }) !== void 0;
364
- const denied = verdict === "forbidden" || verdict === "unauthenticated" && missingCapability;
395
+ const denied = verdict === "forbidden" || verdict === "unauthenticated" && missingCapability || verdict === void 0 && error.code === "PERMISSION_DENIED";
396
+ const unauthenticated = !denied && (verdict === "unauthenticated" || verdict === void 0 && error.code === "AUTH_REQUIRED");
365
397
  return new CLIError(
366
- denied ? "PERMISSION_DENIED" : verdict === "unauthenticated" ? "AUTH_REQUIRED" : error.code,
398
+ denied ? "PERMISSION_DENIED" : unauthenticated ? "AUTH_REQUIRED" : error.code,
367
399
  message,
368
- denied ? ExitCode.PERMISSION_DENIED : verdict === "unauthenticated" ? ExitCode.AUTH_REQUIRED : ExitCode.ERROR,
400
+ denied ? ExitCode.PERMISSION_DENIED : unauthenticated ? ExitCode.AUTH_REQUIRED : ExitCode.ERROR,
369
401
  meta
370
402
  );
371
403
  }
@@ -437,7 +469,10 @@ function handleError(error) {
437
469
  meta.resource = capability.resource;
438
470
  meta.requiredAction = capability.requiredAction;
439
471
  }
440
- outputError(cliError.code, cliError.message, hint, Object.keys(meta).length ? meta : void 0);
472
+ outputError(cliError.code, cliError.message, hint, {
473
+ ...Object.keys(meta).length ? { meta } : {},
474
+ warnings: operationWarnings(cliError.metadata?.warnings)
475
+ });
441
476
  process.exit(cliError.exitCode);
442
477
  }
443
478
  function buildAuthHint(error) {
@@ -13471,6 +13506,10 @@ function canonicalOwnerDid(did, label = "--owner") {
13471
13506
  }
13472
13507
  return `did:pkh:eip155:${match[1]}:${ensureEip55(match[2])}`;
13473
13508
  }
13509
+ function canonicalNetworkUrn(path) {
13510
+ const match = /^urn:tinycloud:encryption:(did:pkh:eip155:[1-9]\d*:0x[0-9a-fA-F]{40}):([^:]*)$/.exec(path);
13511
+ return match ? `urn:tinycloud:encryption:${canonicalOwnerDid(match[1])}:${match[2]}` : path;
13512
+ }
13474
13513
  function rawEncryptionOwnerMatches(path, ownerDid) {
13475
13514
  const match = /^urn:tinycloud:encryption:(did:pkh:eip155:.+):([a-z0-9][a-z0-9-]*)$/.exec(path);
13476
13515
  if (!match) return false;
@@ -13613,13 +13652,14 @@ async function replayAdditionalDelegations(node, profile, options) {
13613
13652
  for (const stored of entries) {
13614
13653
  const kind = storedDelegationKind(stored);
13615
13654
  if (kind === "compact" || kind === "signed-login") {
13616
- const installed = await replayStoredDelegation(activator, stored, {
13655
+ const replay = await replayStoredDelegation(activator, stored, {
13617
13656
  host: options.host,
13618
13657
  migrated,
13619
13658
  resolveSpace
13620
13659
  });
13621
- if (installed === void 0 && process.env.TC_DEBUG_REPLAY === "1") {
13622
- process.stderr.write("[replay] skipping a stored delegation refused by validation or its request binding\n");
13660
+ if (replay.status === "skipped" && process.env.TC_DEBUG_REPLAY === "1") {
13661
+ process.stderr.write(`[replay] skipping a stored delegation: ${replay.reason}
13662
+ `);
13623
13663
  }
13624
13664
  continue;
13625
13665
  }
@@ -13649,6 +13689,14 @@ async function replayAdditionalDelegations(node, profile, options) {
13649
13689
  function storedAdditionalDelegation(delegation, permissions) {
13650
13690
  return { delegation, permissions };
13651
13691
  }
13692
+ async function cliGrantRecord(node, delegation, permissions, host) {
13693
+ const { bindCliGrant } = await import("@tinycloud/operations/delegation-binding");
13694
+ return bindCliGrant(
13695
+ node,
13696
+ storedAdditionalDelegation(delegation, permissions),
13697
+ host
13698
+ );
13699
+ }
13652
13700
  async function appendGrantHistory(profile, entry) {
13653
13701
  await ProfileManager.ensureProfileDir(profile);
13654
13702
  const line = JSON.stringify({
@@ -13803,7 +13851,8 @@ function permissionsFromDelegation(delegation) {
13803
13851
  }
13804
13852
  function compactPermission(permission) {
13805
13853
  const service = permission.service;
13806
- const space = permission.space.startsWith("tinycloud:") ? permission.space.slice(permission.space.lastIndexOf(":") + 1) : permission.space;
13854
+ const fullSpace = permission.space ?? (isRawEncryptionPermission(permission) ? ENCRYPTION_MANIFEST_SPACE2 : "");
13855
+ const space = fullSpace.startsWith("tinycloud:") ? fullSpace.slice(fullSpace.lastIndexOf(":") + 1) : fullSpace;
13807
13856
  const actions = permission.actions.map((action) => action.startsWith(`${service}/`) ? action.slice(service.length + 1) : action).join(",");
13808
13857
  return `${service}:${space}:${permission.path}:${actions}`;
13809
13858
  }
@@ -13899,6 +13948,35 @@ function serviceFromActions(actions) {
13899
13948
  return first.includes("/") ? first.slice(0, first.indexOf("/")) : "tinycloud.unknown";
13900
13949
  }
13901
13950
 
13951
+ // src/auth/session-expired.ts
13952
+ init_constants();
13953
+ init_errors();
13954
+ var SESSION_EXPIRED_REASON = "session_expired";
13955
+ function signInAgainHint(profileName, posture) {
13956
+ if (posture === "local-owner-key") {
13957
+ return `Sign in again with: tc --profile ${profileName} auth login --method local`;
13958
+ }
13959
+ if (posture === "delegate-session") {
13960
+ return `Have the owner approve a new scoped login: tc --profile ${profileName} auth login --method openkey --paste --manifest <manifest.json>. Pass the owner's code on stdin, newline-terminated.`;
13961
+ }
13962
+ return `Sign in again with: tc --profile ${profileName} auth login --method openkey`;
13963
+ }
13964
+ function sessionExpiredError(profileName, posture) {
13965
+ return new CLIError(
13966
+ "AUTH_REQUIRED",
13967
+ `The session for profile "${profileName}" has expired or is no longer valid.`,
13968
+ ExitCode.AUTH_REQUIRED,
13969
+ { hint: signInAgainHint(profileName, posture), reason: SESSION_EXPIRED_REASON }
13970
+ );
13971
+ }
13972
+ function withSignInHint(error, profileName, posture) {
13973
+ if (error.code !== "AUTH_REQUIRED") return error;
13974
+ return new CLIError(error.code, error.message, error.exitCode, {
13975
+ ...error.metadata,
13976
+ hint: signInAgainHint(profileName, posture)
13977
+ });
13978
+ }
13979
+
13902
13980
  // src/lib/sdk.ts
13903
13981
  function jwkHasPrivateParameter(jwk) {
13904
13982
  if (!jwk || typeof jwk !== "object") return false;
@@ -13925,6 +14003,16 @@ function signerJwkForProfile(profileName, sessionJwk, key) {
13925
14003
  }
13926
14004
  );
13927
14005
  }
14006
+ async function restoreProfileSession(node, profileName, profile, sessionData) {
14007
+ try {
14008
+ await node.restoreSession(sessionData);
14009
+ } catch (error) {
14010
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "AUTH_EXPIRED") {
14011
+ throw sessionExpiredError(profileName, profile === null ? void 0 : resolveProfilePosture(profile));
14012
+ }
14013
+ throw error;
14014
+ }
14015
+ }
13928
14016
  async function createSDKInstance(ctx, options) {
13929
14017
  const profile = options?.privateKey ? await ProfileManager.getProfile(ctx.profile).catch(() => null) : await ProfileManager.getProfile(ctx.profile);
13930
14018
  const session = await ProfileManager.getSession(ctx.profile);
@@ -13944,7 +14032,7 @@ async function createSDKInstance(ctx, options) {
13944
14032
  });
13945
14033
  let restoredOwnSession2 = false;
13946
14034
  if (session && session.delegationHeader && session.delegationCid && session.spaceId) {
13947
- await node2.restoreSession({
14035
+ await restoreProfileSession(node2, ctx.profile, profile, {
13948
14036
  delegationHeader: session.delegationHeader,
13949
14037
  delegationCid: session.delegationCid,
13950
14038
  spaceId: session.spaceId,
@@ -13974,7 +14062,7 @@ async function createSDKInstance(ctx, options) {
13974
14062
  if (options?.privateKey) {
13975
14063
  await node.signIn();
13976
14064
  } else if (session && session.delegationHeader && session.delegationCid && session.spaceId) {
13977
- await node.restoreSession({
14065
+ await restoreProfileSession(node, ctx.profile, profile, {
13978
14066
  delegationHeader: session.delegationHeader,
13979
14067
  delegationCid: session.delegationCid,
13980
14068
  spaceId: session.spaceId,
@@ -14556,6 +14644,41 @@ function validateDelegationCallbackPayload(value) {
14556
14644
  }
14557
14645
  return null;
14558
14646
  }
14647
+ async function delegationFromInput(input, did, options) {
14648
+ const trimmed = input.trim();
14649
+ let parsed;
14650
+ if (/^[a-z2-7]{4}-[a-z2-7]{4}$/i.test(trimmed)) {
14651
+ if (!options.jwk) throw new Error("A CLI session key is required to retrieve a delegation.");
14652
+ const apiHost = process.env.TC_OPENKEY_API_HOST ?? options.openkeyApiHost ?? (options.openkeyHost && options.openkeyHost !== DEFAULT_OPENKEY_HOST ? options.openkeyHost : DEFAULT_OPENKEY_DEVICE_API_HOST);
14653
+ const response = await fetch(`${apiHost.replace(/\/$/, "")}/api/delegation-codes/${trimmed.toLowerCase()}`, {
14654
+ redirect: "error"
14655
+ });
14656
+ if (!response.ok) {
14657
+ throw new Error(response.status === 404 ? "Delegation code not found or expired. Use the full code shown in OpenKey instead." : `Could not retrieve delegation code (HTTP ${response.status}).`);
14658
+ }
14659
+ const result = await response.json();
14660
+ parsed = result.delegation;
14661
+ const delegation = parsed;
14662
+ const jwk = delegation?.jwk;
14663
+ const localJwk = publicJwkForDelegation(options.jwk);
14664
+ if (typeof delegation?.verificationMethod !== "string" || delegation.verificationMethod.split("#")[0] !== did.split("#")[0] || !jwk || typeof jwk !== "object" || Array.isArray(jwk) || jwk.kty !== "OKP" || jwk.crv !== "Ed25519" || typeof jwk.x !== "string" || jwk.x !== localJwk.x || [...PRIVATE_JWK_FIELDS].some((field) => field in jwk)) {
14665
+ throw new Error("Delegation is bound to a different CLI key.");
14666
+ }
14667
+ } else {
14668
+ try {
14669
+ parsed = JSON.parse(trimmed);
14670
+ } catch {
14671
+ try {
14672
+ parsed = JSON.parse(Buffer.from(trimmed, "base64").toString("utf-8"));
14673
+ } catch {
14674
+ throw new Error("Invalid delegation code. Expected JSON, base64-encoded JSON, or XXXX-XXXX.");
14675
+ }
14676
+ }
14677
+ }
14678
+ const invalid = validateDelegationCallbackPayload(parsed);
14679
+ if (invalid) throw new Error(`Invalid delegation code: ${invalid}`);
14680
+ return parsed;
14681
+ }
14559
14682
  function shouldOpenBrowser(options) {
14560
14683
  if (options.noPopup) return false;
14561
14684
  const env = process.env.TC_AUTH_NO_POPUP ?? process.env.TC_NO_POPUP;
@@ -14580,21 +14703,6 @@ async function callbackFlow(did, options = {}) {
14580
14703
  reject(result.error);
14581
14704
  }
14582
14705
  }
14583
- function parsePasteInput(input) {
14584
- const trimmed = input.trim();
14585
- let parsed;
14586
- try {
14587
- parsed = JSON.parse(trimmed);
14588
- } catch {
14589
- const decoded = Buffer.from(trimmed, "base64").toString("utf-8");
14590
- parsed = JSON.parse(decoded);
14591
- }
14592
- const invalid = validateDelegationCallbackPayload(parsed);
14593
- if (invalid) {
14594
- throw new Error(`Invalid delegation code: ${invalid}`);
14595
- }
14596
- return parsed;
14597
- }
14598
14706
  const server = createServer((req, res) => {
14599
14707
  if (req.method === "POST" && req.url === "/callback") {
14600
14708
  let body = "";
@@ -14663,18 +14771,21 @@ async function callbackFlow(did, options = {}) {
14663
14771
  }
14664
14772
  if (process.stdin.isTTY) {
14665
14773
  console.error(`
14666
- If the browser can't connect back, paste the delegation code here:`);
14774
+ If the browser can't connect back, enter the short code or paste the full delegation code here:`);
14667
14775
  rl = createInterface({
14668
14776
  input: process.stdin,
14669
14777
  output: process.stderr
14670
14778
  });
14671
- rl.on("line", (input) => {
14672
- if (settled) return;
14779
+ let resolving = false;
14780
+ rl.on("line", async (input) => {
14781
+ if (settled || resolving) return;
14782
+ resolving = true;
14673
14783
  try {
14674
- const data = parsePasteInput(input);
14675
- settle({ data });
14676
- } catch {
14677
- console.error("Invalid delegation code. Expected JSON or base64-encoded JSON. Try again:");
14784
+ settle({ data: await delegationFromInput(input, did, options) });
14785
+ } catch (error) {
14786
+ console.error(error instanceof Error ? error.message : "Invalid delegation code. Try again:");
14787
+ } finally {
14788
+ resolving = false;
14678
14789
  }
14679
14790
  });
14680
14791
  }
@@ -14701,24 +14812,7 @@ Open this URL in a browser to authenticate:
14701
14812
  if (answered || input.trim() === "") return;
14702
14813
  answered = true;
14703
14814
  rl.close();
14704
- let parsed;
14705
- try {
14706
- parsed = JSON.parse(input.trim());
14707
- } catch {
14708
- try {
14709
- const decoded = Buffer.from(input.trim(), "base64").toString("utf-8");
14710
- parsed = JSON.parse(decoded);
14711
- } catch {
14712
- reject(new Error("Invalid delegation code. Expected JSON or base64-encoded JSON."));
14713
- return;
14714
- }
14715
- }
14716
- const invalid = validateDelegationCallbackPayload(parsed);
14717
- if (invalid) {
14718
- reject(new Error(`Invalid delegation code: ${invalid}`));
14719
- return;
14720
- }
14721
- resolve4(parsed);
14815
+ void delegationFromInput(input, did, options).then(resolve4, reject);
14722
14816
  });
14723
14817
  rl.on("close", () => {
14724
14818
  if (answered) return;
@@ -14733,7 +14827,7 @@ Open this URL in a browser to authenticate:
14733
14827
  }
14734
14828
  ));
14735
14829
  });
14736
- rl.setPrompt("Paste delegation code: ");
14830
+ rl.setPrompt("Enter short code or paste full delegation code: ");
14737
14831
  rl.prompt();
14738
14832
  });
14739
14833
  }
@@ -15017,7 +15111,13 @@ function validateLoginPermissions(permissions) {
15017
15111
  }
15018
15112
  }
15019
15113
  function scopedLoginPermissions(permissions) {
15020
- const space = permissions.find((p) => !isRawEncryptionPermission(p)).space ?? "";
15114
+ return withCapabilitiesRead(permissions, permissions.find((p) => !isRawEncryptionPermission(p)).space ?? "");
15115
+ }
15116
+ function grantRequestPermissions(group, anchorSpace) {
15117
+ const request = group.map((p) => isRawEncryptionPermission(p) ? { ...p, space: ENCRYPTION_MANIFEST_SPACE2, path: canonicalNetworkUrn(p.path) } : p);
15118
+ return withCapabilitiesRead(request, request.find((p) => !isRawEncryptionPermission(p))?.space ?? anchorSpace);
15119
+ }
15120
+ function withCapabilitiesRead(permissions, space) {
15021
15121
  const hasRead = permissions.some((p) => p.service === "tinycloud.capabilities" && p.path === "" && p.actions.includes(CAPABILITIES_READ) && normalizePkhIdentifier(p.space ?? "") === normalizePkhIdentifier(space));
15022
15122
  return hasRead ? permissions : [{ service: "tinycloud.capabilities", space, path: "", actions: [CAPABILITIES_READ] }, ...permissions];
15023
15123
  }
@@ -15904,6 +16004,7 @@ function registerAuthCommand(program) {
15904
16004
  "Grant requested TinyCloud permissions from `tc auth request --grant`.",
15905
16005
  group
15906
16006
  );
16007
+ const request = grantRequestPermissions(group, profile.spaceId ?? profile.spaceName);
15907
16008
  let delegationData;
15908
16009
  if (options.device) {
15909
16010
  const approval = await acquireDeviceDelegation({
@@ -15911,7 +16012,7 @@ function registerAuthCommand(program) {
15911
16012
  jwk: key,
15912
16013
  nodeOrigin: ctx.host,
15913
16014
  shareOrigin: DEFAULT_SHARE_ORIGIN,
15914
- permissions: group,
16015
+ permissions: request,
15915
16016
  expiry: parseRequestedExpiry(expiryOption ?? "7d"),
15916
16017
  reason,
15917
16018
  expectedOwner: pinnedOwner(profile),
@@ -15923,17 +16024,17 @@ function registerAuthCommand(program) {
15923
16024
  delegationData = await startAuthFlow(profile.did, {
15924
16025
  jwk: key,
15925
16026
  host: ctx.host,
15926
- permissions: group,
16027
+ permissions: request,
15927
16028
  reason,
15928
16029
  openkeyHost,
15929
16030
  expiry: expiryCap === void 0 ? void 0 : openKeyExpiryParam(expiryCap),
15930
16031
  noPopup: options.popup === false
15931
16032
  });
15932
16033
  }
15933
- const delegation = portableFromOpenKeyDelegation(delegationData, group, ctx.host, proof);
16034
+ const delegation = portableFromOpenKeyDelegation(delegationData, request, ctx.host, proof);
15934
16035
  grants.push({ delegation, effective: permissionsFromDelegation(delegation) });
15935
16036
  }
15936
- await activateAndStoreOpenKeyGrants(ctx.profile, node, grants, options.manifest ? "manifest" : "cli");
16037
+ await activateAndStoreOpenKeyGrants(ctx.profile, ctx.host, node, grants, options.manifest ? "manifest" : "cli");
15937
16038
  const delegationCids2 = grants.map(({ delegation }) => delegation.cid);
15938
16039
  const expiry2 = grants.at(-1)?.delegation.expiry.toISOString();
15939
16040
  reportDeclined(declined);
@@ -15969,8 +16070,7 @@ function registerAuthCommand(program) {
15969
16070
  for (const delegation of delegations) {
15970
16071
  const covering = permissionsFromDelegation(delegation);
15971
16072
  localEffective.push(...covering);
15972
- const stored = storedAdditionalDelegation(delegation, covering);
15973
- await appendAdditionalDelegation(ctx.profile, stored);
16073
+ await appendAdditionalDelegation(ctx.profile, await cliGrantRecord(node, delegation, covering, ctx.host));
15974
16074
  delegationCids.push(delegation.cid);
15975
16075
  expiry = delegation.expiry.toISOString();
15976
16076
  await appendGrantHistory(ctx.profile, {
@@ -16392,6 +16492,8 @@ async function importRequestBoundDelegation(ctx, artifact) {
16392
16492
  { profile: ctx.profile, host: ctx.host, allowOwnerProfile: true },
16393
16493
  artifact
16394
16494
  );
16495
+ const warnings = operationWarnings(result.warnings);
16496
+ const metadata = warnings.length === 0 ? void 0 : { warnings };
16395
16497
  switch (result.status) {
16396
16498
  case "ok": {
16397
16499
  const output2 = result.output;
@@ -16404,22 +16506,25 @@ async function importRequestBoundDelegation(ctx, artifact) {
16404
16506
  permissions: output2.effectivePermissions,
16405
16507
  expiry: output2.expiry
16406
16508
  });
16509
+ outputWarnings(warnings);
16407
16510
  return;
16408
16511
  }
16409
16512
  case "authority_required":
16410
16513
  throw new CLIError(
16411
16514
  "AUTHORITY_REQUIRED",
16412
16515
  "The active session requires additional authority before importing this delegation.",
16413
- ExitCode.PERMISSION_DENIED
16516
+ ExitCode.PERMISSION_DENIED,
16517
+ metadata
16414
16518
  );
16415
16519
  case "setup_required":
16416
16520
  throw new CLIError(
16417
16521
  "SETUP_REQUIRED",
16418
16522
  "The active profile requires setup before importing this delegation.",
16419
- ExitCode.ERROR
16523
+ ExitCode.ERROR,
16524
+ metadata
16420
16525
  );
16421
16526
  case "error":
16422
- throw cliErrorFromService(result.error);
16527
+ throw withSignInHint(cliErrorFromService({ ...result.error, meta: metadata }), ctx.profile, result.context.posture);
16423
16528
  }
16424
16529
  }
16425
16530
  function isStoredDelegationLike(value) {
@@ -16444,9 +16549,10 @@ function normalizePortableDelegation(delegation) {
16444
16549
  }
16445
16550
  return { ...delegation, expiry };
16446
16551
  }
16447
- async function activateAndStoreOpenKeyGrants(profileName, node, grants, source) {
16448
- for (const { delegation } of grants) await node.useRuntimeDelegation(delegation);
16552
+ async function activateAndStoreOpenKeyGrants(profileName, host, node, grants, source) {
16449
16553
  if (grants.length === 0) return;
16554
+ const records = await Promise.all(grants.map(({ delegation, effective }) => cliGrantRecord(node, delegation, effective, host)));
16555
+ for (const { delegation } of grants) await node.useRuntimeDelegation(delegation);
16450
16556
  await ProfileManager.withLock(profileName, async () => {
16451
16557
  for (const { delegation, effective } of grants) {
16452
16558
  await appendGrantHistory(profileName, {
@@ -16456,10 +16562,7 @@ async function activateAndStoreOpenKeyGrants(profileName, node, grants, source)
16456
16562
  expiry: delegation.expiry.toISOString()
16457
16563
  });
16458
16564
  }
16459
- await appendAdditionalDelegations(
16460
- profileName,
16461
- grants.map(({ delegation, effective }) => storedAdditionalDelegation(delegation, effective))
16462
- );
16565
+ await appendAdditionalDelegations(profileName, records);
16463
16566
  });
16464
16567
  }
16465
16568
  async function ensureDelegationAuthority(params) {
@@ -16482,20 +16585,22 @@ async function ensureDelegationAuthority(params) {
16482
16585
  expectedOwner: pinnedOwner(params.profile),
16483
16586
  expiry: expiryCap
16484
16587
  };
16588
+ const anchorSpace = params.anchorSpace ?? params.profile.spaceId ?? params.profile.spaceName;
16485
16589
  const grants = [];
16486
16590
  for (const group of groupPermissionsBySpace(params.requested)) {
16591
+ const request = grantRequestPermissions(group, anchorSpace);
16487
16592
  const delegationData = await acquireOpenKey(params.profile.did, {
16488
16593
  jwk: key,
16489
16594
  host: params.ctx.host,
16490
- permissions: group,
16595
+ permissions: request,
16491
16596
  reason: permissionGrantReason(params.reason, group),
16492
16597
  openkeyHost,
16493
16598
  expiry: expiryCap === void 0 ? void 0 : openKeyExpiryParam(expiryCap)
16494
16599
  });
16495
- const delegation = portableFromOpenKeyDelegation(delegationData, group, params.ctx.host, proof);
16600
+ const delegation = portableFromOpenKeyDelegation(delegationData, request, params.ctx.host, proof);
16496
16601
  grants.push({ delegation, effective: permissionsFromDelegation(delegation) });
16497
16602
  }
16498
- await activateAndStoreOpenKeyGrants(params.ctx.profile, params.node, grants, "cli");
16603
+ await activateAndStoreOpenKeyGrants(params.ctx.profile, params.ctx.host, params.node, grants, "cli");
16499
16604
  return;
16500
16605
  }
16501
16606
  if (isInteractive()) {
@@ -16517,7 +16622,7 @@ async function ensureDelegationAuthority(params) {
16517
16622
  const covering = permissionsFromDelegation(delegation);
16518
16623
  await appendAdditionalDelegation(
16519
16624
  params.ctx.profile,
16520
- storedAdditionalDelegation(delegation, covering)
16625
+ await cliGrantRecord(params.node, delegation, covering, params.ctx.host)
16521
16626
  );
16522
16627
  await appendGrantHistory(params.ctx.profile, {
16523
16628
  addedCaps: covering,
@@ -16649,7 +16754,8 @@ function portableFromOpenKeyDelegation(data, requested, host, proof) {
16649
16754
  }
16650
16755
  const returnedSpace = data.spaceId;
16651
16756
  const effective = session.permissions;
16652
- const primary = effective.find((permission) => !isRawEncryptionPermission(permission)) ?? effective[0];
16757
+ const spaced = effective.filter((permission) => !isRawEncryptionPermission(permission));
16758
+ const primary = spaced.find((permission) => permission.service !== "tinycloud.capabilities") ?? spaced[0] ?? effective[0];
16653
16759
  const resources = effective.map((permission) => ({
16654
16760
  service: permission.service.slice("tinycloud.".length),
16655
16761
  space: isVerifiedRawEncryptionPermission(permission) ? "encryption" : returnedSpace,
@@ -16669,7 +16775,9 @@ function portableFromOpenKeyDelegation(data, requested, host, proof) {
16669
16775
  delegateDID: data.verificationMethod,
16670
16776
  ownerAddress: ownerParts[4],
16671
16777
  chainId: Number(ownerParts[3]),
16672
- host
16778
+ host,
16779
+ // Verified above; replay rebuilds the CACAO from it before trusting the grant.
16780
+ siweProof: { siwe: data.siwe, signature: data.signature }
16673
16781
  };
16674
16782
  }
16675
16783
  async function rotateAuthKey(profileName, host, options = {}) {
@@ -18394,6 +18502,7 @@ async function runSecretOperation(params) {
18394
18502
  reason: secretPermissionReason(params.action, params.name),
18395
18503
  yes: true,
18396
18504
  force: true,
18505
+ anchorSpace: params.space ?? SECRETS_SPACE3,
18397
18506
  openKeyAcquisition: params.openKeyAcquisition
18398
18507
  })
18399
18508
  );
@@ -18478,7 +18587,26 @@ async function runSecretOperationAttempt(label, operation) {
18478
18587
  throw error;
18479
18588
  }
18480
18589
  }
18590
+ function withOperationWarnings(error, warnings) {
18591
+ if (warnings === void 0 || warnings.length === 0) return error;
18592
+ const cliError = wrapError(error);
18593
+ cliError.metadata = { ...cliError.metadata, warnings };
18594
+ return cliError;
18595
+ }
18481
18596
  async function invokeCanonicalSecretGet(params) {
18597
+ const warnings = /* @__PURE__ */ new Map();
18598
+ const collect = (result) => {
18599
+ for (const warning of result.warnings ?? []) warnings.set(JSON.stringify(warning), warning);
18600
+ return result;
18601
+ };
18602
+ try {
18603
+ const result = await canonicalSecretGet(params, collect);
18604
+ return warnings.size === 0 ? result : { ...result, warnings: [...warnings.values()] };
18605
+ } catch (error) {
18606
+ throw withOperationWarnings(error, [...warnings.values()]);
18607
+ }
18608
+ }
18609
+ async function canonicalSecretGet(params, collect) {
18482
18610
  const auth = authOptions(params.options);
18483
18611
  let ownerNode;
18484
18612
  if (!auth?.privateKey) {
@@ -18501,10 +18629,10 @@ async function invokeCanonicalSecretGet(params) {
18501
18629
  ...params.scope === void 0 ? {} : { scope: params.scope },
18502
18630
  ...params.space === void 0 ? {} : { space: params.space }
18503
18631
  };
18504
- const invoke = () => withSpinner(
18632
+ const invoke = async () => collect(await withSpinner(
18505
18633
  params.label,
18506
18634
  () => auth?.privateKey ? invokeSecretsGetWithLocalAuthorityRetry(target, input) : invokeOperation2("tinycloud.secrets.get", 1, target, input)
18507
- );
18635
+ ));
18508
18636
  let first = await invoke();
18509
18637
  if (first.status === "error" && first.error.code === "SESSION_NOT_FOUND" && auth?.privateKey === void 0) {
18510
18638
  const profile2 = await ProfileManager.getProfile(params.ctx.profile);
@@ -18542,6 +18670,7 @@ async function invokeCanonicalSecretGet(params) {
18542
18670
  reason: secretPermissionReason("get", params.name),
18543
18671
  yes: true,
18544
18672
  force: true,
18673
+ anchorSpace: params.space ?? SECRETS_SPACE3,
18545
18674
  openKeyAcquisition: params.openKeyAcquisition
18546
18675
  })
18547
18676
  );
@@ -18553,7 +18682,7 @@ function throwCanonicalSecretGetError(result, name) {
18553
18682
  throw new CLIError(
18554
18683
  "PERMISSION_DENIED",
18555
18684
  "Permission denied while reading secret",
18556
- ExitCode.ERROR
18685
+ ExitCode.PERMISSION_DENIED
18557
18686
  );
18558
18687
  case "setup_required":
18559
18688
  throw new CLIError(
@@ -18575,11 +18704,10 @@ ${result.setup.message}`, setup: result.setup }
18575
18704
  if (result.error.code === "NODE_UNREACHABLE") {
18576
18705
  throw new CLIError("NETWORK_ERROR", result.error.message, ExitCode.NETWORK_ERROR);
18577
18706
  }
18578
- throw new CLIError(
18579
- result.error.code,
18580
- result.error.message,
18581
- result.error.code === "PERMISSION_HINT_INVALID" ? ExitCode.PERMISSION_DENIED : ExitCode.ERROR
18582
- );
18707
+ if (result.error.code === "PERMISSION_HINT_INVALID") {
18708
+ throw new CLIError(result.error.code, result.error.message, ExitCode.PERMISSION_DENIED);
18709
+ }
18710
+ throw withSignInHint(cliErrorFromService(result.error), result.context.profile, result.context.posture);
18583
18711
  case "ok":
18584
18712
  throw new Error("Expected a failed canonical secret result.");
18585
18713
  }
@@ -19195,19 +19323,26 @@ function registerSecretsCommand(program, openKeyAcquisition) {
19195
19323
  openKeyAcquisition
19196
19324
  });
19197
19325
  if (result.status !== "ok") {
19198
- throwCanonicalSecretGetError(result, name);
19326
+ try {
19327
+ throwCanonicalSecretGetError(result, name);
19328
+ } catch (error) {
19329
+ throw withOperationWarnings(error, result.warnings);
19330
+ }
19199
19331
  }
19200
19332
  const value = result.output.value;
19201
- if (options.output) {
19202
- await writeSecretFile(options.output, value);
19203
- outputJson({ name, written: options.output });
19204
- return;
19205
- }
19206
- if (options.raw || options.valueOnly) {
19207
- process.stdout.write(value);
19208
- return;
19333
+ try {
19334
+ if (options.output) {
19335
+ await writeSecretFile(options.output, value);
19336
+ outputJson({ name, written: options.output });
19337
+ } else if (options.raw || options.valueOnly) {
19338
+ process.stdout.write(value);
19339
+ } else {
19340
+ outputJson({ name, value });
19341
+ }
19342
+ } catch (error) {
19343
+ throw withOperationWarnings(error, result.warnings);
19209
19344
  }
19210
- outputJson({ name, value });
19345
+ outputWarnings(operationWarnings(result.warnings));
19211
19346
  } catch (error) {
19212
19347
  handleError(error);
19213
19348
  }