@tinycloud/cli 1.1.0 → 1.2.0-beta.0

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.
@@ -160,6 +160,51 @@ var init_theme = __esm({
160
160
  }
161
161
  });
162
162
 
163
+ // src/output/warnings.ts
164
+ function warningKey(warning) {
165
+ return JSON.stringify([warning.code, warning.reason, warning.grantCid ?? null]);
166
+ }
167
+ function mergeWarnings(first, second) {
168
+ const merged = [...first];
169
+ const counts = /* @__PURE__ */ new Map();
170
+ for (const warning of first) {
171
+ const key = warningKey(warning);
172
+ counts.set(key, (counts.get(key) ?? 0) + 1);
173
+ }
174
+ const seen = /* @__PURE__ */ new Map();
175
+ for (const warning of second) {
176
+ const key = warningKey(warning);
177
+ const occurrence = (seen.get(key) ?? 0) + 1;
178
+ seen.set(key, occurrence);
179
+ if (occurrence > (counts.get(key) ?? 0)) merged.push(warning);
180
+ }
181
+ return merged;
182
+ }
183
+ function recordWarnings(warnings) {
184
+ if (warnings.length === 0) return;
185
+ const holder = globalThis;
186
+ holder[PENDING_WARNINGS] = mergeWarnings(holder[PENDING_WARNINGS] ?? [], warnings);
187
+ }
188
+ function drainWarnings() {
189
+ const holder = globalThis;
190
+ const warnings = holder[PENDING_WARNINGS] ?? [];
191
+ delete holder[PENDING_WARNINGS];
192
+ return warnings;
193
+ }
194
+ function discardWarnings(cid) {
195
+ const holder = globalThis;
196
+ if (holder[PENDING_WARNINGS]) {
197
+ holder[PENDING_WARNINGS] = holder[PENDING_WARNINGS].filter((warning) => warning.grantCid !== cid);
198
+ }
199
+ }
200
+ var PENDING_WARNINGS;
201
+ var init_warnings = __esm({
202
+ "src/output/warnings.ts"() {
203
+ "use strict";
204
+ PENDING_WARNINGS = /* @__PURE__ */ Symbol.for("tinycloud.cli.pendingWarnings.v1");
205
+ }
206
+ });
207
+
163
208
  // src/output/formatter.ts
164
209
  import ora from "ora";
165
210
  function outputJson(data) {
@@ -168,7 +213,7 @@ function outputJson(data) {
168
213
  function warningLine(warnings) {
169
214
  const which = warnings.map((warning) => `${warning.grantCid ?? "unidentified"}: ${warning.reason}`).join(", ");
170
215
  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.`;
216
+ return `${subject} not used (${which}). Approve access again when a command needs it; remove the old grant with tc auth grants remove <cid>.`;
172
217
  }
173
218
  function outputError(code3, message, hint, details = {}) {
174
219
  const { meta, warnings = [] } = details;
@@ -183,7 +228,7 @@ function outputError(code3, message, hint, details = {}) {
183
228
  `);
184
229
  }
185
230
  }
186
- if (warnings.length > 0) process.stderr.write(` ${theme.warn(warningLine(warnings))}
231
+ if (warnings.length > 0) process.stderr.write(` ${theme.warn("!")} ${warningLine(warnings)}
187
232
  `);
188
233
  } else {
189
234
  const payload = {
@@ -196,9 +241,10 @@ function outputError(code3, message, hint, details = {}) {
196
241
  }
197
242
  }
198
243
  function outputWarnings(warnings) {
199
- if (warnings.length === 0) return;
200
- process.stderr.write(shouldOutputJson() ? `${JSON.stringify({ warnings })}
201
- ` : `${theme.warn("!")} ${warningLine(warnings)}
244
+ const combined = mergeWarnings(drainWarnings(), operationWarnings(warnings));
245
+ if (combined.length === 0) return;
246
+ process.stderr.write(shouldOutputJson() ? `${JSON.stringify({ warnings: combined })}
247
+ ` : `${theme.warn("!")} ${warningLine(combined)}
202
248
  `);
203
249
  }
204
250
  function operationWarnings(value) {
@@ -284,6 +330,7 @@ var init_formatter = __esm({
284
330
  "src/output/formatter.ts"() {
285
331
  "use strict";
286
332
  init_theme();
333
+ init_warnings();
287
334
  WARNING_CODE = /^[A-Z][A-Z_]{0,63}$/;
288
335
  WARNING_REASON = /^[a-z][a-z_]{0,63}$/;
289
336
  DELEGATION_CID = /^bafkr4i[a-z2-7]{52}$/;
@@ -295,20 +342,20 @@ var init_formatter = __esm({
295
342
  function storageRejection(error) {
296
343
  let node = error;
297
344
  for (let depth = 0; depth < MAX_CAUSE_DEPTH && typeof node === "object" && node !== null; depth += 1) {
298
- const record = node;
345
+ const record2 = node;
299
346
  let code3;
300
- if (record.code === "STORAGE_QUOTA_EXCEEDED" || record.code === "STORAGE_LIMIT_REACHED") {
301
- code3 = record.code;
302
- } else if (record.code !== void 0) {
347
+ if (record2.code === "STORAGE_QUOTA_EXCEEDED" || record2.code === "STORAGE_LIMIT_REACHED") {
348
+ code3 = record2.code;
349
+ } else if (record2.code !== void 0) {
303
350
  return void 0;
304
- } else if (isStorageStatus(record)) {
305
- code3 = storageCodeFromText(record.message);
351
+ } else if (isStorageStatus(record2)) {
352
+ code3 = storageCodeFromText(record2.message);
306
353
  }
307
354
  if (code3) {
308
- const account = accountTotals(record) ?? accountTotals(record.meta) ?? accountTotals(record.metadata);
355
+ const account = accountTotals(record2) ?? accountTotals(record2.meta) ?? accountTotals(record2.metadata);
309
356
  return account ? { code: code3, account } : { code: code3 };
310
357
  }
311
- node = record.cause;
358
+ node = record2.cause;
312
359
  }
313
360
  return void 0;
314
361
  }
@@ -342,11 +389,11 @@ function storageCodeFromText(message) {
342
389
  }
343
390
  return void 0;
344
391
  }
345
- function isStorageStatus(record) {
346
- const meta = "meta" in record && typeof record.meta === "object" && record.meta !== null ? record.meta : {};
392
+ function isStorageStatus(record2) {
393
+ const meta = "meta" in record2 && typeof record2.meta === "object" && record2.meta !== null ? record2.meta : {};
347
394
  const statuses = [
348
- "status" in record ? record.status : void 0,
349
- "statusCode" in record ? record.statusCode : void 0,
395
+ "status" in record2 ? record2.status : void 0,
396
+ "statusCode" in record2 ? record2.statusCode : void 0,
350
397
  "status" in meta ? meta.status : void 0
351
398
  ];
352
399
  return statuses.some((value) => value === 402 || value === 413);
@@ -463,6 +510,9 @@ function handleError(error) {
463
510
  const hint = prebuilt ?? buildAuthHint(cliError) ?? (cliError.code === "NETWORK_ERROR" ? buildNetworkHint() : void 0);
464
511
  const authMeta = cliError.code === "AUTH_REQUIRED" || cliError.code === "PERMISSION_DENIED" ? cliError.metadata : void 0;
465
512
  const meta = {};
513
+ if (cliError.code === "GRANT_REVOKED_LOCAL_REMOVE_FAILED" && typeof cliError.metadata?.causeCode === "string" && /^[A-Z][A-Z0-9_]{0,63}$/.test(cliError.metadata.causeCode)) {
514
+ meta.causeCode = cliError.metadata.causeCode;
515
+ }
466
516
  if (cliError.status !== void 0 && authMeta !== void 0) meta.status = cliError.status;
467
517
  const capability = validatedCapabilityOf({ meta: authMeta });
468
518
  if (capability) {
@@ -471,7 +521,7 @@ function handleError(error) {
471
521
  }
472
522
  outputError(cliError.code, cliError.message, hint, {
473
523
  ...Object.keys(meta).length ? { meta } : {},
474
- warnings: operationWarnings(cliError.metadata?.warnings)
524
+ warnings: mergeWarnings(drainWarnings(), operationWarnings(cliError.metadata?.warnings))
475
525
  });
476
526
  process.exit(cliError.exitCode);
477
527
  }
@@ -543,6 +593,7 @@ var init_errors = __esm({
543
593
  init_constants();
544
594
  init_formatter();
545
595
  init_storage2();
596
+ init_warnings();
546
597
  CLIError = class extends Error {
547
598
  constructor(code3, message, exitCode = ExitCode.ERROR, metadata) {
548
599
  super(message);
@@ -7215,21 +7266,21 @@ function validatedCapabilityOf2(error) {
7215
7266
  let node = error;
7216
7267
  for (let depth = 0; depth < MAX_CAUSE_DEPTH2 && typeof node === "object" && node !== null && !seen.has(node); depth += 1) {
7217
7268
  seen.add(node);
7218
- const record = node;
7219
- const status = errorStatusOf(record);
7220
- const meta = record.meta;
7221
- if ((status === 401 || status === 403 || status === void 0 && record.code === ErrorCodes.AUTH_UNAUTHORIZED) && typeof meta === "object" && meta !== null && !Array.isArray(meta)) {
7269
+ const record2 = node;
7270
+ const status = errorStatusOf(record2);
7271
+ const meta = record2.meta;
7272
+ if ((status === 401 || status === 403 || status === void 0 && record2.code === ErrorCodes.AUTH_UNAUTHORIZED) && typeof meta === "object" && meta !== null && !Array.isArray(meta)) {
7222
7273
  const metadata = meta;
7223
7274
  const { resource, requiredAction } = metadata;
7224
7275
  if (typeof requiredAction === "string" && grantableActions.has(requiredAction)) {
7225
7276
  const actionService = requiredAction.slice("tinycloud.".length, requiredAction.indexOf("/"));
7226
- const explicitService = record.service === void 0 ? metadata.service : record.service;
7277
+ const explicitService = record2.service === void 0 ? metadata.service : record2.service;
7227
7278
  if (safeCapabilityResource(resource, actionService, requiredAction) && (explicitService === void 0 || explicitService === actionService || explicitService === `tinycloud.${actionService}`)) {
7228
7279
  return { resource, requiredAction };
7229
7280
  }
7230
7281
  }
7231
7282
  }
7232
- node = record.cause;
7283
+ node = record2.cause;
7233
7284
  }
7234
7285
  return void 0;
7235
7286
  }
@@ -7254,13 +7305,13 @@ function authorizationVerdictOf2(error) {
7254
7305
  let node = error;
7255
7306
  for (let depth = 0; depth < MAX_CAUSE_DEPTH2 && typeof node === "object" && node !== null && !seen.has(node); depth += 1) {
7256
7307
  seen.add(node);
7257
- const record = node;
7258
- const status = errorStatusOf(record);
7308
+ const record2 = node;
7309
+ const status = errorStatusOf(record2);
7259
7310
  if (status === 401) return "unauthenticated";
7260
7311
  if (status === 403) return "forbidden";
7261
7312
  if (status !== void 0) return "other";
7262
- if (record.code === ErrorCodes.AUTH_UNAUTHORIZED) sawUnauthorizedCode = true;
7263
- node = record.cause;
7313
+ if (record2.code === ErrorCodes.AUTH_UNAUTHORIZED) sawUnauthorizedCode = true;
7314
+ node = record2.cause;
7264
7315
  }
7265
7316
  return sawUnauthorizedCode ? "forbidden" : void 0;
7266
7317
  }
@@ -10947,8 +10998,8 @@ function normalizeWebhookRecord(data) {
10947
10998
  if (!data || typeof data !== "object") {
10948
10999
  return null;
10949
11000
  }
10950
- const record = isRecordContainer(data);
10951
- const candidate = pickWebhookRecord(record) ?? normalizeWebhookRecord(record.webhook) ?? normalizeWebhookRecord(record.hook) ?? normalizeWebhookRecord(record.subscription) ?? normalizeWebhookRecord(record.data);
11001
+ const record2 = isRecordContainer(data);
11002
+ const candidate = pickWebhookRecord(record2) ?? normalizeWebhookRecord(record2.webhook) ?? normalizeWebhookRecord(record2.hook) ?? normalizeWebhookRecord(record2.subscription) ?? normalizeWebhookRecord(record2.data);
10952
11003
  return candidate ?? null;
10953
11004
  }
10954
11005
  function normalizeWebhookRecordList(data) {
@@ -10959,12 +11010,12 @@ function normalizeWebhookRecordList(data) {
10959
11010
  if (!data || typeof data !== "object") {
10960
11011
  return null;
10961
11012
  }
10962
- const record = isRecordContainer(data);
10963
- const nested = maybeRecordArray(record.webhooks) ?? maybeRecordArray(record.subscriptions) ?? maybeRecordArray(record.hooks) ?? maybeRecordArray(record.data);
11013
+ const record2 = isRecordContainer(data);
11014
+ const nested = maybeRecordArray(record2.webhooks) ?? maybeRecordArray(record2.subscriptions) ?? maybeRecordArray(record2.hooks) ?? maybeRecordArray(record2.data);
10964
11015
  if (nested) {
10965
11016
  return nested;
10966
11017
  }
10967
- const single = pickWebhookRecord(record);
11018
+ const single = pickWebhookRecord(record2);
10968
11019
  return single ? [single] : null;
10969
11020
  }
10970
11021
  function maybeRecordArray(value) {
@@ -14222,7 +14273,7 @@ async function appendLocalGrantArtifact(profile, delegation) {
14222
14273
  await withProfileLock2(profile, async () => {
14223
14274
  await refuseWriteToDeletedProfile2(profile);
14224
14275
  const records = await loadLocalGrantArtifacts(profile);
14225
- if (records.some((record) => record.delegationCid === delegation.cid)) return;
14276
+ if (records.some((record2) => record2.delegationCid === delegation.cid)) return;
14226
14277
  await writeJsonAtomic(localGrantArtifactsPath(profile), [
14227
14278
  ...records,
14228
14279
  { delegationCid: delegation.cid, delegation }
@@ -14239,6 +14290,20 @@ async function appendAdditionalDelegations(profile, entries) {
14239
14290
  async function appendAdditionalDelegation(profile, entry) {
14240
14291
  await appendAdditionalDelegations(profile, [entry]);
14241
14292
  }
14293
+ async function removeStoredDelegations(profile, cid) {
14294
+ return updateProfileStore(profile, "additional-delegations", (records) => {
14295
+ const remaining = records.filter((entry) => {
14296
+ if (entry === null || typeof entry !== "object" || !("delegation" in entry)) return true;
14297
+ const delegation = entry.delegation;
14298
+ return delegation === null || typeof delegation !== "object" || !("cid" in delegation) || delegation.cid !== cid;
14299
+ });
14300
+ const removed = records.length - remaining.length;
14301
+ if (removed === 0) {
14302
+ throw new CLIError("GRANT_NOT_FOUND", `No stored grant with CID ${cid} exists for this profile.`, ExitCode.NOT_FOUND);
14303
+ }
14304
+ return { records: remaining, result: removed };
14305
+ });
14306
+ }
14242
14307
  async function loadPermissionRequestArtifacts(profile) {
14243
14308
  const raw = await readAuthRequests(profile);
14244
14309
  return raw.filter(isCompatiblePermissionRequestArtifact);
@@ -14266,55 +14331,13 @@ async function getLastPermissionRequestArtifact(profile) {
14266
14331
  return existing.at(-1) ?? null;
14267
14332
  }
14268
14333
  async function replayAdditionalDelegations(node, profile, options) {
14269
- const {
14270
- operationSpaceResolver,
14271
- prepareStoredDelegationReplay,
14272
- replayStoredDelegation,
14273
- storedDelegationKind
14274
- } = await import("@tinycloud/operations/delegation-binding");
14275
- const activator = node;
14276
- const migrated = await prepareStoredDelegationReplay(profile, activator, {
14334
+ const { operationSpaceResolver: operationSpaceResolver2, replayProfileDelegations } = await import("@tinycloud/operations/delegation-binding");
14335
+ const { warnings } = await replayProfileDelegations(profile, node, {
14277
14336
  host: options.host,
14278
- migrate: options.migrate
14337
+ migrate: options.migrate,
14338
+ resolveSpace: operationSpaceResolver2(node, options.ownerSpace)
14279
14339
  });
14280
- const resolveSpace2 = operationSpaceResolver(node, options.ownerSpace);
14281
- const entries = await loadAdditionalDelegations(profile);
14282
- for (const stored of entries) {
14283
- const kind = storedDelegationKind(stored);
14284
- if (kind === "compact" || kind === "signed-login") {
14285
- const replay = await replayStoredDelegation(activator, stored, {
14286
- host: options.host,
14287
- migrated,
14288
- resolveSpace: resolveSpace2
14289
- });
14290
- if (replay.status === "skipped" && process.env.TC_DEBUG_REPLAY === "1") {
14291
- process.stderr.write(`[replay] skipping a stored delegation: ${replay.reason}
14292
- `);
14293
- }
14294
- continue;
14295
- }
14296
- if (kind === "refused") {
14297
- if (process.env.TC_DEBUG_REPLAY === "1") {
14298
- process.stderr.write("[replay] skipping a malformed stored record or one bound to a request it cannot be held to\n");
14299
- }
14300
- continue;
14301
- }
14302
- const entry = stored;
14303
- const expiry = entry.delegation.expiry instanceof Date ? entry.delegation.expiry : new Date(entry.delegation.expiry);
14304
- if (expiry.getTime() <= Date.now()) continue;
14305
- try {
14306
- await node.useRuntimeDelegation({
14307
- ...entry.delegation,
14308
- delegationHeader: { Authorization: entry.delegation.delegationHeader.Authorization },
14309
- expiry
14310
- });
14311
- } catch (err2) {
14312
- if (process.env.TC_DEBUG_REPLAY === "1") {
14313
- process.stderr.write(`[replay] skipping ${entry.delegation.cid}: ${err2.message}
14314
- `);
14315
- }
14316
- }
14317
- }
14340
+ return warnings;
14318
14341
  }
14319
14342
  function storedAdditionalDelegation(delegation, permissions) {
14320
14343
  return { delegation, permissions };
@@ -14608,6 +14631,7 @@ function withSignInHint(error, profileName2, posture) {
14608
14631
  }
14609
14632
 
14610
14633
  // src/lib/sdk.ts
14634
+ init_warnings();
14611
14635
  function jwkHasPrivateParameter(jwk) {
14612
14636
  if (!jwk || typeof jwk !== "object") return false;
14613
14637
  const d = jwk.d;
@@ -14643,6 +14667,44 @@ async function restoreProfileSession(node, profileName2, profile, sessionData) {
14643
14667
  throw error;
14644
14668
  }
14645
14669
  }
14670
+ function persistedSessionData(profileName2, profile, session, key) {
14671
+ return {
14672
+ delegationHeader: session.delegationHeader,
14673
+ delegationCid: session.delegationCid,
14674
+ spaceId: session.spaceId,
14675
+ jwk: signerJwkForProfile(profileName2, session.jwk, key),
14676
+ verificationMethod: session.verificationMethod ?? profile.sessionDid ?? profile.did,
14677
+ address: session.address,
14678
+ chainId: session.chainId,
14679
+ siwe: session.siwe,
14680
+ signature: session.signature
14681
+ };
14682
+ }
14683
+ async function restoreProfileSessionNode(ctx) {
14684
+ const profile = await ProfileManager.getProfile(ctx.profile);
14685
+ const session = await ProfileManager.getSession(ctx.profile);
14686
+ if (!session) return { unavailable: "session_missing" };
14687
+ if (typeof session.delegationCid !== "string" || typeof session.spaceId !== "string" || session.delegationHeader === null || typeof session.delegationHeader !== "object" || !("Authorization" in session.delegationHeader) || typeof session.delegationHeader.Authorization !== "string") {
14688
+ return { unavailable: "session_invalid" };
14689
+ }
14690
+ if (typeof session.expiresAt === "string" && Date.parse(session.expiresAt) <= Date.now()) {
14691
+ return { unavailable: "session_expired" };
14692
+ }
14693
+ const key = await ProfileManager.getKey(ctx.profile);
14694
+ try {
14695
+ const node = new TinyCloudNode({
14696
+ host: ctx.host,
14697
+ ...profile.authMethod === "local" && profile.privateKey ? { privateKey: profile.privateKey } : {}
14698
+ });
14699
+ await restoreProfileSession(node, ctx.profile, profile, persistedSessionData(ctx.profile, profile, session, key));
14700
+ return { node };
14701
+ } catch (error) {
14702
+ if (error instanceof CLIError && error.metadata?.reason === "session_expired") {
14703
+ return { unavailable: "session_expired" };
14704
+ }
14705
+ return { unavailable: "session_invalid" };
14706
+ }
14707
+ }
14646
14708
  async function createSDKInstance(ctx, options) {
14647
14709
  const profile = options?.privateKey ? await ProfileManager.getProfile(ctx.profile).catch(() => null) : await ProfileManager.getProfile(ctx.profile);
14648
14710
  const session = await ProfileManager.getSession(ctx.profile);
@@ -14660,56 +14722,35 @@ async function createSDKInstance(ctx, options) {
14660
14722
  host: ctx.host,
14661
14723
  privateKey: effectivePrivateKey
14662
14724
  });
14663
- let restoredOwnSession2 = false;
14664
- if (session && session.delegationHeader && session.delegationCid && session.spaceId) {
14665
- await restoreProfileSession(node2, ctx.profile, profile, {
14666
- delegationHeader: session.delegationHeader,
14667
- delegationCid: session.delegationCid,
14668
- spaceId: session.spaceId,
14669
- jwk: signerJwkForProfile(ctx.profile, session.jwk, key),
14670
- verificationMethod: session.verificationMethod ?? profile?.sessionDid ?? profile?.did,
14671
- address: session.address,
14672
- chainId: session.chainId,
14673
- siwe: session.siwe,
14674
- signature: session.signature
14675
- });
14676
- restoredOwnSession2 = true;
14725
+ const restoredOwnSession = Boolean(session && session.delegationHeader && session.delegationCid && session.spaceId);
14726
+ if (restoredOwnSession) {
14727
+ await restoreProfileSession(node2, ctx.profile, profile, persistedSessionData(ctx.profile, profile, session, key));
14677
14728
  } else {
14678
14729
  await node2.signIn();
14679
14730
  }
14680
- await replayAdditionalDelegations(node2, ctx.profile, {
14681
- host: ctx.host,
14682
- ownerSpace: profile.spaceId,
14683
- migrate: restoredOwnSession2 && options?.privateKey === void 0
14684
- });
14731
+ if (restoredOwnSession) {
14732
+ recordWarnings(await replayAdditionalDelegations(node2, ctx.profile, {
14733
+ host: ctx.host,
14734
+ ownerSpace: profile.spaceId,
14735
+ migrate: options?.privateKey === void 0
14736
+ }));
14737
+ }
14685
14738
  return node2;
14686
14739
  }
14687
14740
  const node = new TinyCloudNode({
14688
14741
  host: ctx.host,
14689
14742
  privateKey: options?.privateKey
14690
14743
  });
14691
- let restoredOwnSession = false;
14692
14744
  if (options?.privateKey) {
14693
14745
  await node.signIn();
14694
14746
  } else if (session && session.delegationHeader && session.delegationCid && session.spaceId) {
14695
- await restoreProfileSession(node, ctx.profile, profile, {
14696
- delegationHeader: session.delegationHeader,
14697
- delegationCid: session.delegationCid,
14698
- spaceId: session.spaceId,
14699
- jwk: signerJwkForProfile(ctx.profile, session.jwk, key),
14700
- verificationMethod: session.verificationMethod ?? profile?.did,
14701
- address: session.address,
14702
- chainId: session.chainId,
14703
- siwe: session.siwe,
14704
- signature: session.signature
14705
- });
14706
- restoredOwnSession = true;
14747
+ await restoreProfileSession(node, ctx.profile, profile, persistedSessionData(ctx.profile, profile, session, key));
14748
+ recordWarnings(await replayAdditionalDelegations(node, ctx.profile, {
14749
+ host: ctx.host,
14750
+ ownerSpace: profile?.spaceId,
14751
+ migrate: true
14752
+ }));
14707
14753
  }
14708
- await replayAdditionalDelegations(node, ctx.profile, {
14709
- host: ctx.host,
14710
- ownerSpace: profile?.spaceId,
14711
- migrate: restoredOwnSession
14712
- });
14713
14754
  return node;
14714
14755
  }
14715
14756
  var BOOTSTRAP_PROFILE_FIELDS = ["sessionDid", "spaceId"];
@@ -19312,8 +19353,8 @@ async function generateLocalIdentity(chainId = 1) {
19312
19353
  return { privateKey, address, did };
19313
19354
  }
19314
19355
  async function localKeySignIn(options) {
19315
- const { TinyCloudNode: TinyCloudNode2 } = await import("@tinycloud/node-sdk");
19316
- const node = new TinyCloudNode2({
19356
+ const { TinyCloudNode: TinyCloudNode3 } = await import("@tinycloud/node-sdk");
19357
+ const node = new TinyCloudNode3({
19317
19358
  privateKey: options.privateKey,
19318
19359
  host: options.host,
19319
19360
  autoCreateSpace: true
@@ -19875,8 +19916,8 @@ function deviceAuthRateLimit(response) {
19875
19916
  }
19876
19917
  function publicSessionJwk(value) {
19877
19918
  const publicJwk = publicJwkForDelegation(value);
19878
- const record = publicJwk;
19879
- if (record.kty !== "OKP" || record.crv !== "Ed25519" || typeof record.x !== "string") {
19919
+ const record2 = publicJwk;
19920
+ if (record2.kty !== "OKP" || record2.crv !== "Ed25519" || typeof record2.x !== "string") {
19880
19921
  throw new Error("CLI session key is not a public Ed25519 JWK");
19881
19922
  }
19882
19923
  return publicJwk;
@@ -20196,6 +20237,427 @@ async function loginWithDeviceAuthorization(input) {
20196
20237
 
20197
20238
  // src/commands/auth.ts
20198
20239
  init_theme();
20240
+
20241
+ // src/commands/auth-grants.ts
20242
+ init_profiles();
20243
+ init_types();
20244
+ init_constants();
20245
+ import { TinyCloudNode as TinyCloudNode2 } from "@tinycloud/node-sdk";
20246
+ import { DelegationRequestBindingSchema, PermissionEntrySchema } from "@tinycloud/operations/artifacts";
20247
+ import {
20248
+ inspectStoredDelegation as inspectStoredDelegation2,
20249
+ operationSpaceResolver,
20250
+ storedDelegationKind,
20251
+ storedDelegationForAnotherKey,
20252
+ storedDelegationSource,
20253
+ withoutBearer as withoutBearer2
20254
+ } from "@tinycloud/operations/delegation-binding";
20255
+
20256
+ // src/lib/revoke-delegation.ts
20257
+ init_constants();
20258
+ init_errors();
20259
+ import { parseSignedCompactUcanAttenuation } from "@tinycloud/sdk-core";
20260
+ import { inspectStoredDelegation, withoutBearer } from "@tinycloud/operations/delegation-binding";
20261
+ function normalizeDid(input) {
20262
+ const trimmed = input.trim();
20263
+ const fragment = trimmed.indexOf("#");
20264
+ return (fragment < 0 ? trimmed : trimmed.slice(0, fragment)).toLowerCase();
20265
+ }
20266
+ function ownerDidFromSpace(spaceId) {
20267
+ const owner = /^tinycloud:((?:did:)?pkh:eip155:[1-9]\d*:0x[0-9a-fA-F]{40})(?::|\/|$)/.exec(spaceId ?? "")?.[1];
20268
+ if (!owner) return void 0;
20269
+ return owner.startsWith("did:") ? owner : `did:${owner}`;
20270
+ }
20271
+ async function cidBoundOwnerSpace(record2, ownerDid, node, host) {
20272
+ const { delegation } = record2;
20273
+ const authorization = delegation.delegationHeader?.Authorization;
20274
+ if (typeof authorization !== "string" || typeof delegation.cid !== "string") return void 0;
20275
+ if (!delegation.siweProof && withoutBearer(authorization).split(".").length === 3) {
20276
+ try {
20277
+ const signed = parseSignedCompactUcanAttenuation(withoutBearer(authorization), delegation.cid);
20278
+ for (const resource of Object.keys(signed.payload.att)) {
20279
+ const space = /^(tinycloud:[^/]+)\/[^/]+\/.*$/.exec(resource)?.[1];
20280
+ if (space && ownerDidFromSpace(space) && normalizeDid(ownerDidFromSpace(space)) === normalizeDid(ownerDid)) {
20281
+ return space;
20282
+ }
20283
+ }
20284
+ } catch {
20285
+ return void 0;
20286
+ }
20287
+ return void 0;
20288
+ }
20289
+ if (!node || !delegation.siweProof) return void 0;
20290
+ const inspected = await inspectStoredDelegation(record2, { node, host: host ?? delegation.host });
20291
+ if (inspected.local !== "verified" || inspected.cid !== delegation.cid) return void 0;
20292
+ for (const permission of inspected.effectivePermissions) {
20293
+ const space = permission.space;
20294
+ if (space && ownerDidFromSpace(space) && normalizeDid(ownerDidFromSpace(space)) === normalizeDid(ownerDid)) {
20295
+ return space;
20296
+ }
20297
+ }
20298
+ return void 0;
20299
+ }
20300
+ async function resolveCidBoundTargetSpace(params) {
20301
+ if (!params.ownerDid) return void 0;
20302
+ if (params.localTarget?.delegation?.cid === params.cid) {
20303
+ const space = await cidBoundOwnerSpace(params.localTarget, params.ownerDid, params.node, params.host);
20304
+ if (space) return { space, source: "local-stored-grant" };
20305
+ }
20306
+ for (const artifact of await loadLocalGrantArtifacts(params.profileName)) {
20307
+ if (artifact.delegationCid !== params.cid || artifact.delegation.cid !== params.cid) continue;
20308
+ const space = await cidBoundOwnerSpace(artifact, params.ownerDid, params.node, params.host);
20309
+ if (space) return { space, source: "local-signed-grant-artifact" };
20310
+ }
20311
+ return void 0;
20312
+ }
20313
+ async function revokeDelegationOnNode(params) {
20314
+ const { ctx, node, profile, cid } = params;
20315
+ const revokePermission = {
20316
+ service: "tinycloud.delegation",
20317
+ space: `urn:cid:${cid}`,
20318
+ path: "",
20319
+ actions: ["tinycloud.delegation/revoke"]
20320
+ };
20321
+ const rawAuthorityCovered = node.hasRuntimePermissions([revokePermission]);
20322
+ if (!rawAuthorityCovered) await requirePermissionConsent([revokePermission], params.yes);
20323
+ let targetFound = params.localTarget?.delegation?.cid === cid;
20324
+ let targetSpaceSource = targetFound ? "local-stored-grant" : "node";
20325
+ let boundSpace;
20326
+ if (!targetFound) {
20327
+ const accountSpaceId = node.accountSpaceId;
20328
+ if (!accountSpaceId) {
20329
+ throw new CLIError("DELEGATION_QUERY_AUTHORITY_UNAVAILABLE", "Cannot determine the account space needed to query delegation records.", ExitCode.AUTH_REQUIRED);
20330
+ }
20331
+ let queryDenied = false;
20332
+ let cursor;
20333
+ do {
20334
+ const query = await node.delegationManager.query({
20335
+ direction: "all",
20336
+ limit: 100,
20337
+ ...cursor === void 0 ? {} : { cursor }
20338
+ });
20339
+ if (!query.ok) {
20340
+ if (query.error.meta?.status === 403) {
20341
+ queryDenied = true;
20342
+ break;
20343
+ }
20344
+ throw cliErrorFromService(query.error);
20345
+ }
20346
+ targetFound = query.data.items.some((delegation) => delegation.cid === cid);
20347
+ cursor = targetFound ? void 0 : query.data.nextCursor;
20348
+ } while (cursor !== void 0);
20349
+ if (!targetFound) {
20350
+ const recordedGrant = [...await readGrantHistory(ctx.profile)].reverse().find((entry) => entry.delegationCid === cid);
20351
+ if (recordedGrant) {
20352
+ targetSpaceSource = "local-grant-history";
20353
+ } else if (queryDenied) {
20354
+ boundSpace = await resolveCidBoundTargetSpace({
20355
+ profileName: ctx.profile,
20356
+ ownerDid: profile.ownerDid ?? ownerDidFromSpace(node.accountSpaceId) ?? ownerDidFromSpace(profile.spaceId),
20357
+ cid,
20358
+ node,
20359
+ host: ctx.host
20360
+ });
20361
+ if (!boundSpace) {
20362
+ throw new CLIError("TARGET_NOT_FOUND", `Delegation "${cid}" was not found by the node or local grant sources.`, ExitCode.NOT_FOUND);
20363
+ }
20364
+ targetSpaceSource = boundSpace.source;
20365
+ } else {
20366
+ throw new CLIError("TARGET_NOT_FOUND", `Delegation "${cid}" was not found by the node or local grant history.`, ExitCode.NOT_FOUND);
20367
+ }
20368
+ }
20369
+ }
20370
+ let authorityScopeSource = "cid-resource";
20371
+ let authorityScopeReason = "The revoke authority is scoped to the exact delegation CID.";
20372
+ let fallbackSpaceId;
20373
+ let commandAuthorityCid;
20374
+ if (!rawAuthorityCovered) {
20375
+ try {
20376
+ const acquired = await ensureDelegationAuthority({
20377
+ ctx,
20378
+ profile,
20379
+ node,
20380
+ requested: [revokePermission],
20381
+ expiryOption: void 0,
20382
+ reason: `Revoke delegation ${cid}`,
20383
+ yes: params.yes,
20384
+ consentAlreadyGiven: true,
20385
+ persist: false,
20386
+ openKeyAcquisition: params.openKeyAcquisition
20387
+ });
20388
+ commandAuthorityCid = acquired?.cid;
20389
+ } catch (error) {
20390
+ if (error === null || typeof error !== "object" || !("code" in error) || error.code !== "RAW_RECAP_RESOURCE_UNSUPPORTED") {
20391
+ throw error;
20392
+ }
20393
+ const fallback = boundSpace ?? await resolveCidBoundTargetSpace({
20394
+ profileName: ctx.profile,
20395
+ ownerDid: profile.ownerDid ?? ownerDidFromSpace(node.accountSpaceId) ?? ownerDidFromSpace(profile.spaceId),
20396
+ cid,
20397
+ node,
20398
+ host: ctx.host,
20399
+ localTarget: params.localTarget
20400
+ });
20401
+ if (!fallback) {
20402
+ throw new CLIError(
20403
+ "RAW_RECAP_RESOURCE_UNSUPPORTED",
20404
+ `Cannot safely revoke "${cid}": raw CID ReCap resources are unavailable and no matching owner-signed local grant binds its space to the CID.`,
20405
+ ExitCode.PERMISSION_DENIED
20406
+ );
20407
+ }
20408
+ fallbackSpaceId = fallback.space;
20409
+ authorityScopeSource = fallback.source;
20410
+ authorityScopeReason = fallback.source === "local-signed-grant-artifact" ? "Raw-CID revoke authority could not be issued; the fallback space came from an owner-signed local grant whose Authorization recomputes to this CID." : "Raw-CID revoke authority could not be issued; the fallback space came from a locally verified owner-signed stored grant bound to this CID.";
20411
+ targetSpaceSource = fallback.source;
20412
+ const acquired = await ensureDelegationAuthority({
20413
+ ctx,
20414
+ profile,
20415
+ node,
20416
+ requested: [{
20417
+ service: "tinycloud.delegation",
20418
+ space: profile.authMethod !== "openkey" && fallback.space === node.accountSpaceId ? "default" : fallback.space,
20419
+ path: "",
20420
+ actions: ["tinycloud.delegation/revoke"]
20421
+ }],
20422
+ expiryOption: void 0,
20423
+ reason: `Revoke delegation ${cid} using its CID-bound owner-signed grant ${fallback.source === "local-stored-grant" ? "record" : "artifact"}`,
20424
+ yes: params.yes,
20425
+ consentAlreadyGiven: true,
20426
+ persist: false,
20427
+ openKeyAcquisition: params.openKeyAcquisition
20428
+ });
20429
+ commandAuthorityCid = acquired?.cid;
20430
+ }
20431
+ }
20432
+ const result = fallbackSpaceId ? await node.delegationManager.revoke(cid, {
20433
+ targetSpaceId: fallbackSpaceId,
20434
+ ...commandAuthorityCid === void 0 ? {} : { authorityCid: commandAuthorityCid }
20435
+ }) : commandAuthorityCid === void 0 ? await node.revokeDelegation(cid) : await node.delegationManager.revoke(cid, { authorityCid: commandAuthorityCid });
20436
+ if (!result.ok) {
20437
+ if (result.error.meta?.status === 403 && result.error.message === "Failed to revoke delegation: 403 - Unauthorized Revoker") {
20438
+ const profileDid = profile.ownerDid ?? ownerDidFromSpace(node.accountSpaceId) ?? ownerDidFromSpace(profile.spaceId) ?? "unknown";
20439
+ throw new CLIError(
20440
+ "REVOKE_UNAUTHORIZED",
20441
+ "The node rejected this revocation: Unauthorized Revoker.",
20442
+ ExitCode.PERMISSION_DENIED,
20443
+ { 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 \`tc --profile <profile-name> delegation revoke ${cid}\`.` }
20444
+ );
20445
+ }
20446
+ throw cliErrorFromService(result.error);
20447
+ }
20448
+ return { cid, revoked: true, targetSpaceSource, authorityScopeSource, authorityScopeReason };
20449
+ }
20450
+
20451
+ // src/commands/auth-grants.ts
20452
+ init_warnings();
20453
+ init_errors();
20454
+ init_formatter();
20455
+ var CID = /^bafkr4i[a-z2-7]{52}$/;
20456
+ function record(value) {
20457
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
20458
+ }
20459
+ function isoDate(value) {
20460
+ if (typeof value !== "string" && !(value instanceof Date)) return null;
20461
+ const date = value instanceof Date ? value : new Date(value);
20462
+ return Number.isFinite(date.getTime()) ? date.toISOString() : null;
20463
+ }
20464
+ async function localContext(options) {
20465
+ const profileName2 = options.profile ?? process.env.TC_PROFILE ?? (await ProfileManager.getConfig()).defaultProfile;
20466
+ const profile = await ProfileManager.getProfile(profileName2);
20467
+ const ctx = await ProfileManager.resolveContext({
20468
+ ...options,
20469
+ profile: profileName2,
20470
+ host: options.host ?? process.env.TC_HOST ?? profile.host
20471
+ });
20472
+ return { ctx, profile };
20473
+ }
20474
+ function storedScope(value) {
20475
+ const parsed = PermissionEntrySchema.array().safeParse(record(value)?.permissions);
20476
+ return parsed.success ? parsed.data : [];
20477
+ }
20478
+ function storedProof(value) {
20479
+ const kind = storedDelegationKind(value);
20480
+ if (kind === "signed-login") return "siwe";
20481
+ if (kind === "compact") return "compact";
20482
+ if (kind === "other") return "missing";
20483
+ const header = record(record(record(value)?.delegation)?.delegationHeader)?.Authorization;
20484
+ if (typeof header !== "string") return null;
20485
+ return withoutBearer2(header).includes(".") ? "compact" : "missing";
20486
+ }
20487
+ async function listedGrant(value, index, options) {
20488
+ const delegation = record(record(value)?.delegation);
20489
+ const rawCid = delegation?.cid;
20490
+ const expiry = isoDate(delegation?.expiry);
20491
+ const forAnotherKey = options.node !== void 0 && storedDelegationForAnotherKey(value, options.node);
20492
+ const inspection = forAnotherKey ? void 0 : await inspectStoredDelegation2(value, options);
20493
+ const source = storedDelegationSource(value);
20494
+ return {
20495
+ index,
20496
+ cid: typeof rawCid === "string" && CID.test(rawCid) ? rawCid : null,
20497
+ expiry,
20498
+ expired: expiry !== null && Date.parse(expiry) <= Date.now(),
20499
+ local: inspection?.local ?? "for_another_key",
20500
+ reason: inspection?.local === "refused" ? inspection.reason : null,
20501
+ node: "not_checked",
20502
+ bound: DelegationRequestBindingSchema.safeParse(record(value)?.authorityRequest).success,
20503
+ proof: storedProof(value),
20504
+ source,
20505
+ scope: inspection?.local === "verified" ? inspection.effectivePermissions : storedScope(value),
20506
+ scopeSource: inspection?.local === "verified" ? "signed" : "stored"
20507
+ };
20508
+ }
20509
+ function localSessionCid(session, host) {
20510
+ const header = record(session.delegationHeader)?.Authorization;
20511
+ const cid = session.delegationCid;
20512
+ if (typeof header !== "string" || typeof cid !== "string" || !CID.test(cid)) return null;
20513
+ try {
20514
+ return new TinyCloudNode2({ host }).computeDelegationCid(header) === cid ? cid : null;
20515
+ } catch {
20516
+ return null;
20517
+ }
20518
+ }
20519
+ function renderList(result) {
20520
+ if (shouldOutputJson()) {
20521
+ outputJson(result);
20522
+ return;
20523
+ }
20524
+ process.stdout.write(`Profile: ${result.profile}
20525
+ `);
20526
+ process.stdout.write(`Session CID: ${result.session?.cid ?? "unavailable"}` + (result.session?.cid ? ` (revoke with tc delegation revoke ${result.session.cid})
20527
+ ` : "\n"));
20528
+ process.stdout.write(`Local verification: ${result.localVerification}; node validation and revocation are not checked.
20529
+ `);
20530
+ process.stdout.write(formatTable(
20531
+ ["CID", "LOCAL", "EXPIRES", "SOURCE", "BOUND", "SCOPE"],
20532
+ result.grants.map((grant) => [
20533
+ grant.cid ?? "\u2014",
20534
+ grant.local === "refused" ? `refused (${grant.reason})` : grant.local === "for_another_key" ? "for another key (not installable here)" : grant.local,
20535
+ grant.expiry ?? "\u2014",
20536
+ grant.source,
20537
+ grant.bound ? "yes" : "no",
20538
+ grant.scope.map((permission) => `${permission.service}:${permission.space ?? "default"}:${permission.path}:${permission.actions.join(",")}`).join("; ") || "\u2014"
20539
+ ])
20540
+ ) + "\n");
20541
+ }
20542
+ function registerAuthGrantsCommand(auth, options = {}) {
20543
+ const grants = auth.command("grants").description("Inspect and remove locally stored grants");
20544
+ grants.command("list").description("Inspect locally stored grants without asking the node (node chain and revocation remain unchecked)").action(async (_options, cmd) => {
20545
+ try {
20546
+ const { ctx, profile } = await localContext(cmd.optsWithGlobals());
20547
+ const session = record(await ProfileManager.getSession(ctx.profile));
20548
+ const restored = await restoreProfileSessionNode(ctx);
20549
+ const node = "node" in restored ? restored.node : void 0;
20550
+ const entries = await loadAdditionalDelegations(ctx.profile);
20551
+ const grants2 = await Promise.all(entries.map((entry, index) => listedGrant(entry, index, {
20552
+ node,
20553
+ host: ctx.host,
20554
+ ...node ? { resolveSpace: operationSpaceResolver(node, profile.spaceId) } : {}
20555
+ })));
20556
+ renderList({
20557
+ profile: ctx.profile,
20558
+ session: session === void 0 ? null : {
20559
+ cid: localSessionCid(session, ctx.host),
20560
+ expiresAt: isoDate(session.expiresAt ?? session.expiry)
20561
+ },
20562
+ localVerification: "node" in restored ? "available" : restored.unavailable,
20563
+ grants: grants2
20564
+ });
20565
+ } catch (error) {
20566
+ handleError(error);
20567
+ }
20568
+ });
20569
+ grants.command("remove <cid>").description("Remove locally stored grants for exactly this CID; optionally revoke as the owner").option("--revoke", "Revoke on the node before removing local grants").option("--yes", "Skip local-key TTY confirmation", false).action(async (cid, flags, cmd) => {
20570
+ try {
20571
+ const { ctx, profile } = await localContext(cmd.optsWithGlobals());
20572
+ if (flags.revoke && resolveProfilePosture(profile) === "delegate-session") {
20573
+ throw new CLIError(
20574
+ "REVOKE_UNAUTHORIZED",
20575
+ "A delegate-session profile cannot revoke a stored grant. Remove it locally or ask its owner to revoke it.",
20576
+ ExitCode.PERMISSION_DENIED
20577
+ );
20578
+ }
20579
+ if (!CID.test(cid)) {
20580
+ throw new CLIError("INVALID_ARGUMENT", "Expected a delegation CID.", ExitCode.USAGE_ERROR);
20581
+ }
20582
+ const entries = await loadAdditionalDelegations(ctx.profile);
20583
+ const matched = entries.filter((entry) => record(record(entry)?.delegation)?.cid === cid);
20584
+ if (matched.length === 0) {
20585
+ throw new CLIError("GRANT_NOT_FOUND", `No locally stored grant has CID ${cid}.`, ExitCode.NOT_FOUND);
20586
+ }
20587
+ if (flags.revoke) {
20588
+ const revokeCtx = await ProfileManager.resolveContext({ ...cmd.optsWithGlobals(), profile: ctx.profile });
20589
+ const node = await ensureAuthenticated(revokeCtx);
20590
+ const resolveSpace2 = operationSpaceResolver(node, profile.spaceId);
20591
+ let localTarget = matched[0];
20592
+ for (const candidate of matched) {
20593
+ const inspection = await inspectStoredDelegation2(candidate, { node, host: revokeCtx.host, resolveSpace: resolveSpace2 });
20594
+ if (inspection.local === "verified") {
20595
+ localTarget = candidate;
20596
+ break;
20597
+ }
20598
+ }
20599
+ const revoked = await revokeDelegationOnNode({
20600
+ ctx: revokeCtx,
20601
+ profile,
20602
+ node,
20603
+ cid,
20604
+ yes: flags.yes ?? false,
20605
+ localTarget,
20606
+ openKeyAcquisition: options.openKeyAcquisition
20607
+ });
20608
+ let removed;
20609
+ try {
20610
+ removed = await removeStoredDelegations(ctx.profile, cid);
20611
+ } catch (error) {
20612
+ const cause = wrapError(error);
20613
+ if (cause.code === "GRANT_NOT_FOUND") {
20614
+ removed = 0;
20615
+ } else {
20616
+ throw new CLIError(
20617
+ "GRANT_REVOKED_LOCAL_REMOVE_FAILED",
20618
+ `Delegation ${cid} was revoked on the node, but its local grant could not be removed (${cause.code}).`,
20619
+ ExitCode.ERROR,
20620
+ {
20621
+ causeCode: cause.code,
20622
+ hint: `Do not use --revoke again. After fixing local profile storage, run: tc --profile ${ctx.profile} auth grants remove ${cid}`
20623
+ }
20624
+ );
20625
+ }
20626
+ }
20627
+ discardWarnings(cid);
20628
+ const result = {
20629
+ profile: ctx.profile,
20630
+ cid,
20631
+ removed,
20632
+ revoked: true,
20633
+ targetSpaceSource: revoked.targetSpaceSource,
20634
+ authorityScopeSource: revoked.authorityScopeSource,
20635
+ authorityScopeReason: revoked.authorityScopeReason
20636
+ };
20637
+ if (shouldOutputJson()) outputJson(result);
20638
+ else process.stdout.write(`Revoked ${cid} and removed ${removed} local grant${removed === 1 ? "" : "s"}.
20639
+ `);
20640
+ } else {
20641
+ const removed = await removeStoredDelegations(ctx.profile, cid);
20642
+ discardWarnings(cid);
20643
+ const result = {
20644
+ profile: ctx.profile,
20645
+ cid,
20646
+ removed,
20647
+ revoked: false,
20648
+ warning: "Only local copies were removed; the node delegation was not revoked."
20649
+ };
20650
+ if (shouldOutputJson()) outputJson(result);
20651
+ else process.stdout.write(`Removed ${removed} local grant${removed === 1 ? "" : "s"} for ${cid}. The node delegation was not revoked.
20652
+ `);
20653
+ }
20654
+ } catch (error) {
20655
+ handleError(error);
20656
+ }
20657
+ });
20658
+ }
20659
+
20660
+ // src/commands/auth.ts
20199
20661
  init_space();
20200
20662
  function resolveOpenKeyHost(profile) {
20201
20663
  return process.env.TC_OPENKEY_HOST ?? profile.openkeyHost ?? DEFAULT_OPENKEY_HOST;
@@ -20226,18 +20688,19 @@ async function promptAuthMethod() {
20226
20688
  });
20227
20689
  });
20228
20690
  }
20229
- function registerAuthCommand(program) {
20691
+ function registerAuthCommand(program, options) {
20230
20692
  const auth = program.command("auth").description("Authentication management");
20231
- 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", "Use manual paste mode instead of browser callback").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 \`tc 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)").action(async (options, cmd) => {
20693
+ registerAuthGrantsCommand(auth, { openKeyAcquisition: options?.openKeyAcquisition });
20694
+ 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", "Use manual paste mode instead of browser callback").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 \`tc 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)").action(async (options2, cmd) => {
20232
20695
  try {
20233
- if (options.device && options.paste) {
20696
+ if (options2.device && options2.paste) {
20234
20697
  throw new CLIError("INVALID_ARGUMENT", "--device and --paste are mutually exclusive.", ExitCode.USAGE_ERROR);
20235
20698
  }
20236
- const scoped = options.device || options.manifest || options.expiry || options.owner;
20237
- if (scoped && options.method === "local") {
20699
+ const scoped = options2.device || options2.manifest || options2.expiry || options2.owner;
20700
+ if (scoped && options2.method === "local") {
20238
20701
  throw new CLIError("INVALID_ARGUMENT", "--device, --manifest, --expiry and --owner require OpenKey login.", ExitCode.USAGE_ERROR);
20239
20702
  }
20240
- if (options.device && !options.manifest) {
20703
+ if (options2.device && !options2.manifest) {
20241
20704
  throw new CLIError(
20242
20705
  "MANIFEST_REQUIRED",
20243
20706
  `Device login requests an explicit scope. Pass --manifest FILE, or --manifest ${SHARE_PUBLISHING_MANIFEST_REF} for Share publishing.`,
@@ -20246,19 +20709,19 @@ function registerAuthCommand(program) {
20246
20709
  }
20247
20710
  const globalOpts = cmd.optsWithGlobals();
20248
20711
  const ctx = await ProfileManager.resolveContext(globalOpts);
20249
- const owner = options.owner === void 0 ? void 0 : canonicalOwnerDid(options.owner);
20250
- const permissions = options.manifest ? await loadManifestPermissions(options.manifest, ctx.profile, { allowLogicalSpaces: true, ownerDid: owner, device: options.device === true }) : void 0;
20712
+ const owner = options2.owner === void 0 ? void 0 : canonicalOwnerDid(options2.owner);
20713
+ const permissions = options2.manifest ? await loadManifestPermissions(options2.manifest, ctx.profile, { allowLogicalSpaces: true, ownerDid: owner, device: options2.device === true }) : void 0;
20251
20714
  const persistHost = globalOpts.host !== void 0;
20252
- if (options.device) {
20715
+ if (options2.device) {
20253
20716
  const { profile, result } = await loginWithDeviceAuthorization({
20254
20717
  profileName: ctx.profile,
20255
20718
  nodeOrigin: ctx.host,
20256
20719
  shareOrigin: DEFAULT_SHARE_ORIGIN,
20257
20720
  permissions,
20258
- ...options.expiry === void 0 ? {} : { expiry: parseRequestedExpiry(parseExpiryOption(options.expiry)) },
20721
+ ...options2.expiry === void 0 ? {} : { expiry: parseRequestedExpiry(parseExpiryOption(options2.expiry)) },
20259
20722
  reason: "Allow this TinyCloud CLI profile to use the permissions in the requested manifest.",
20260
20723
  expectedOwner: owner,
20261
- replaceSession: options.replaceSession === true,
20724
+ replaceSession: options2.replaceSession === true,
20262
20725
  persistHost
20263
20726
  });
20264
20727
  reportDeclined(result.declined);
@@ -20279,19 +20742,19 @@ function registerAuthCommand(program) {
20279
20742
  return;
20280
20743
  }
20281
20744
  let method;
20282
- if (options.method) {
20283
- if (options.method !== "local" && options.method !== "openkey") {
20745
+ if (options2.method) {
20746
+ if (options2.method !== "local" && options2.method !== "openkey") {
20284
20747
  throw new CLIError(
20285
20748
  "INVALID_METHOD",
20286
- `Invalid auth method "${options.method}". Use "local" or "openkey".`,
20749
+ `Invalid auth method "${options2.method}". Use "local" or "openkey".`,
20287
20750
  ExitCode.USAGE_ERROR
20288
20751
  );
20289
20752
  }
20290
- method = options.method;
20753
+ method = options2.method;
20291
20754
  } else {
20292
20755
  method = scoped ? "openkey" : await promptAuthMethod();
20293
20756
  }
20294
- if (method === "openkey" && !options.paste && options.popup !== false && !process.stdin.isTTY && !process.stderr.isTTY) {
20757
+ if (method === "openkey" && !options2.paste && options2.popup !== false && !process.stdin.isTTY && !process.stderr.isTTY) {
20295
20758
  throw new CLIError(
20296
20759
  "INTERACTIVE_LOGIN_REQUIRED",
20297
20760
  "Browser login needs a visible approval URL and would wait silently here. Use `--paste` to print the URL and provide the owner's code on stdin.",
@@ -20302,12 +20765,12 @@ function registerAuthCommand(program) {
20302
20765
  await handleLocalAuth(ctx.profile, ctx.host);
20303
20766
  } else {
20304
20767
  await handleOpenKeyAuth(ctx.profile, ctx.host, {
20305
- paste: options.paste,
20306
- noPopup: options.popup === false,
20768
+ paste: options2.paste,
20769
+ noPopup: options2.popup === false,
20307
20770
  permissions,
20308
- expiry: parseExpiryOption(options.expiry),
20771
+ expiry: parseExpiryOption(options2.expiry),
20309
20772
  expectedOwner: owner,
20310
- replaceSession: options.replaceSession === true,
20773
+ replaceSession: options2.replaceSession === true,
20311
20774
  persistHost
20312
20775
  });
20313
20776
  }
@@ -20315,31 +20778,31 @@ function registerAuthCommand(program) {
20315
20778
  handleError(error);
20316
20779
  }
20317
20780
  });
20318
- auth.command("logout").description("Clear the local session and remove local replicas. Logout is local; delegations and grants on the node stay valid until they expire. Use `tc delegation revoke <cid>` to end access now.").option("--keep-replicas", "Keep this profile's local replicas").action(async (options, cmd) => {
20781
+ auth.command("logout").description("Clear the local session and remove local replicas. Logout is local; delegations and grants on the node stay valid until they expire. Use `tc delegation revoke <cid>` to end access now.").option("--keep-replicas", "Keep this profile's local replicas").action(async (options2, cmd) => {
20319
20782
  try {
20320
20783
  const globalOpts = cmd.optsWithGlobals();
20321
20784
  const ctx = await ProfileManager.resolveContext(globalOpts);
20322
20785
  await ProfileManager.getProfile(ctx.profile);
20323
20786
  await ProfileManager.clearSession(ctx.profile);
20324
- const replicasRemoved = options.keepReplicas ? [] : await removeProfileReplicas(ctx.profile);
20787
+ const replicasRemoved = options2.keepReplicas ? [] : await removeProfileReplicas(ctx.profile);
20325
20788
  outputJson({
20326
20789
  profile: ctx.profile,
20327
20790
  authenticated: false,
20328
20791
  replicasRemoved,
20329
- replicasKept: options.keepReplicas === true,
20792
+ replicasKept: options2.keepReplicas === true,
20330
20793
  warning: "Logout is local; delegations and grants on the node stay valid until they expire. Use `tc delegation revoke <cid>` to end access now."
20331
20794
  });
20332
20795
  } catch (error) {
20333
20796
  handleError(error);
20334
20797
  }
20335
20798
  });
20336
- auth.command("rotate").description("Rotate the active profile session key").option("--paste", "Use manual paste mode instead of browser callback").option("--no-popup", "Print the OpenKey URL without opening a browser").action(async (options, cmd) => {
20799
+ auth.command("rotate").description("Rotate the active profile session key").option("--paste", "Use manual paste mode instead of browser callback").option("--no-popup", "Print the OpenKey URL without opening a browser").action(async (options2, cmd) => {
20337
20800
  try {
20338
20801
  const globalOpts = cmd.optsWithGlobals();
20339
20802
  const ctx = await ProfileManager.resolveContext(globalOpts);
20340
20803
  await rotateAuthKey(ctx.profile, ctx.host, {
20341
- paste: options.paste,
20342
- noPopup: options.popup === false
20804
+ paste: options2.paste,
20805
+ noPopup: options2.popup === false
20343
20806
  });
20344
20807
  } catch (error) {
20345
20808
  handleError(error);
@@ -20404,16 +20867,16 @@ function registerAuthCommand(program) {
20404
20867
  ).option("--permission <file>", 'JSON permission request: { "permissions": PermissionEntry[] }').option("--manifest <fileOrBase64>", "Manifest file, base64:<json>, or raw base64 JSON").option(
20405
20868
  "--expiry <duration>",
20406
20869
  `Lifetime of the granted delegation. ms-format string (e.g. "7d", "30m") or raw milliseconds. Defaults to 7d, capped by the active session's expiry.`
20407
- ).option("--emit [file]", "Emit the request artifact to stdout, or write it to file when provided").option("--grant", "Grant the requested permissions immediately with this owner profile").option("--yes", "Skip local-key TTY confirmation", false).option("--no-popup", "Print the OpenKey URL without opening a browser when granting with OpenKey").option("--device", "With --grant: approve on another device (e.g. a phone) through OpenKey device authorization (KV-scoped; SQL needs browser login)").action(async (options, cmd) => {
20870
+ ).option("--emit [file]", "Emit the request artifact to stdout, or write it to file when provided").option("--grant", "Grant the requested permissions immediately with this owner profile").option("--yes", "Skip local-key TTY confirmation", false).option("--no-popup", "Print the OpenKey URL without opening a browser when granting with OpenKey").option("--device", "With --grant: approve on another device (e.g. a phone) through OpenKey device authorization (KV-scoped; SQL needs browser login)").action(async (options2, cmd) => {
20408
20871
  try {
20409
20872
  const globalOpts = cmd.optsWithGlobals();
20410
20873
  const ctx = await ProfileManager.resolveContext(globalOpts);
20411
20874
  const profile = await ProfileManager.getProfile(ctx.profile);
20412
- if (options.device && (!options.grant || resolveProfilePosture(profile) !== "owner-openkey")) {
20875
+ if (options2.device && (!options2.grant || resolveProfilePosture(profile) !== "owner-openkey")) {
20413
20876
  throw new CLIError("INVALID_ARGUMENT", "--device requires --grant on an OpenKey owner profile.", ExitCode.USAGE_ERROR);
20414
20877
  }
20415
- const requested = await collectRequestedPermissions(options, ctx.profile);
20416
- const expiryOption = parseExpiryOption(options.expiry);
20878
+ const requested = await collectRequestedPermissions(options2, ctx.profile);
20879
+ const expiryOption = parseExpiryOption(options2.expiry);
20417
20880
  if (requested.length === 0) {
20418
20881
  throw new CLIError(
20419
20882
  "NO_CAPS_REQUESTED",
@@ -20421,7 +20884,7 @@ function registerAuthCommand(program) {
20421
20884
  ExitCode.USAGE_ERROR
20422
20885
  );
20423
20886
  }
20424
- if (!options.grant) {
20887
+ if (!options2.grant) {
20425
20888
  const artifact = createPermissionRequestArtifact({
20426
20889
  profileName: ctx.profile,
20427
20890
  profile,
@@ -20430,7 +20893,7 @@ function registerAuthCommand(program) {
20430
20893
  requestedExpiry: expiryOption
20431
20894
  });
20432
20895
  await appendPermissionRequestArtifact(ctx.profile, artifact);
20433
- await emitPermissionRequestArtifact(artifact, options.emit);
20896
+ await emitPermissionRequestArtifact(artifact, options2.emit);
20434
20897
  return;
20435
20898
  }
20436
20899
  const node = await ensureAuthenticated(ctx);
@@ -20438,7 +20901,7 @@ function registerAuthCommand(program) {
20438
20901
  outputJson({ changed: false, missing: [], added: [] });
20439
20902
  return;
20440
20903
  }
20441
- if (profile.authMethod === "openkey" || options.device) {
20904
+ if (profile.authMethod === "openkey" || options2.device) {
20442
20905
  const key = await ProfileManager.getKey(ctx.profile);
20443
20906
  if (!key) {
20444
20907
  throw new CLIError("NO_KEY", `No key found for profile "${ctx.profile}". Run \`tc --profile ${ctx.profile} auth rotate\` to create a new key and sign in.`, ExitCode.AUTH_REQUIRED);
@@ -20460,7 +20923,7 @@ function registerAuthCommand(program) {
20460
20923
  );
20461
20924
  const request = grantRequestPermissions(group, profile.spaceId ?? profile.spaceName);
20462
20925
  let delegationData;
20463
- if (options.device) {
20926
+ if (options2.device) {
20464
20927
  const approval = await acquireDeviceDelegation({
20465
20928
  sessionDid: keyToDID(key),
20466
20929
  jwk: key,
@@ -20482,13 +20945,13 @@ function registerAuthCommand(program) {
20482
20945
  reason,
20483
20946
  openkeyHost,
20484
20947
  expiry: expiryCap === void 0 ? void 0 : openKeyExpiryParam(expiryCap),
20485
- noPopup: options.popup === false
20948
+ noPopup: options2.popup === false
20486
20949
  });
20487
20950
  }
20488
20951
  const delegation = portableFromOpenKeyDelegation(delegationData, request, ctx.host, proof);
20489
20952
  grants.push({ delegation, effective: permissionsFromDelegation(delegation) });
20490
20953
  }
20491
- await activateAndStoreOpenKeyGrants(ctx.profile, ctx.host, node, grants, options.manifest ? "manifest" : "cli");
20954
+ await activateAndStoreOpenKeyGrants(ctx.profile, ctx.host, node, grants, options2.manifest ? "manifest" : "cli");
20492
20955
  const delegationCids2 = grants.map(({ delegation }) => delegation.cid);
20493
20956
  const expiry2 = grants.at(-1)?.delegation.expiry.toISOString();
20494
20957
  reportDeclined(declined);
@@ -20498,15 +20961,15 @@ function registerAuthCommand(program) {
20498
20961
  delegationCid: delegationCids2[0],
20499
20962
  delegationCids: delegationCids2,
20500
20963
  expiry: expiry2,
20501
- ...options.device ? { declined } : {}
20964
+ ...options2.device ? { declined } : {}
20502
20965
  });
20503
20966
  return;
20504
20967
  }
20505
20968
  if (isInteractive()) {
20506
- if (!options.yes) {
20969
+ if (!options2.yes) {
20507
20970
  await confirmPermissionRequest(requested);
20508
20971
  }
20509
- } else if (!options.yes) {
20972
+ } else if (!options2.yes) {
20510
20973
  throw new CLIError(
20511
20974
  "CONFIRMATION_REQUIRED",
20512
20975
  "Local-key permission requests in non-interactive mode require --yes.",
@@ -20529,7 +20992,7 @@ function registerAuthCommand(program) {
20529
20992
  expiry = delegation.expiry.toISOString();
20530
20993
  await appendGrantHistory(ctx.profile, {
20531
20994
  addedCaps: covering,
20532
- source: options.manifest ? "manifest" : "cli",
20995
+ source: options2.manifest ? "manifest" : "cli",
20533
20996
  delegationCid: delegation.cid,
20534
20997
  expiry
20535
20998
  });
@@ -20549,12 +21012,12 @@ function registerAuthCommand(program) {
20549
21012
  handleError(error);
20550
21013
  }
20551
21014
  });
20552
- auth.command("import [source]").description("Import a TinyCloud delegation or permission request artifact").option("--stdin", "Read the JSON artifact from stdin").option("--paste", "Read the JSON artifact from stdin").action(async (source, options, cmd) => {
21015
+ auth.command("import [source]").description("Import a TinyCloud delegation or permission request artifact").option("--stdin", "Read the JSON artifact from stdin").option("--paste", "Read the JSON artifact from stdin").action(async (source, options2, cmd) => {
20553
21016
  try {
20554
21017
  const globalOpts = cmd.optsWithGlobals();
20555
21018
  const ctx = await ProfileManager.resolveContext(globalOpts);
20556
21019
  const raw = await readAuthArtifactSource(source, {
20557
- stdin: options.stdin === true || options.paste === true
21020
+ stdin: options2.stdin === true || options2.paste === true
20558
21021
  });
20559
21022
  const parsed = JSON.parse(raw);
20560
21023
  if (isCompatiblePermissionRequestArtifact(parsed)) {
@@ -20573,8 +21036,8 @@ function registerAuthCommand(program) {
20573
21036
  return;
20574
21037
  }
20575
21038
  const imported = normalizeDelegationImport(parsed);
20576
- const { activateUnboundCompactImport, storedDelegationKind } = await import("@tinycloud/operations/delegation-binding");
20577
- const kind = storedDelegationKind({ delegation: imported.delegation });
21039
+ const { activateUnboundImport, storedDelegationKind: storedDelegationKind2, verifiedCompactAudience, STORED_GRANT_SKIP_REASONS } = await import("@tinycloud/operations/delegation-binding");
21040
+ const kind = storedDelegationKind2({ delegation: imported.delegation });
20578
21041
  if (kind === "refused") {
20579
21042
  throw new CLIError(
20580
21043
  "INVALID_AUTH_IMPORT",
@@ -20582,39 +21045,72 @@ function registerAuthCommand(program) {
20582
21045
  ExitCode.USAGE_ERROR
20583
21046
  );
20584
21047
  }
21048
+ if (kind === "other") {
21049
+ throw new CLIError(
21050
+ "INVALID_AUTH_IMPORT",
21051
+ "Imported delegation must carry verifiable signed proof.",
21052
+ ExitCode.USAGE_ERROR
21053
+ );
21054
+ }
21055
+ const profile = await ProfileManager.getProfile(ctx.profile);
21056
+ let bootstrap;
20585
21057
  let node;
20586
21058
  try {
20587
21059
  node = await ensureAuthenticated(ctx);
20588
21060
  } catch (error) {
20589
- const profile = await ProfileManager.getProfile(ctx.profile);
20590
21061
  const session = await ProfileManager.getSession(ctx.profile);
20591
21062
  if (session || resolveProfilePosture(profile) !== "delegate-session") throw error;
20592
- node = (await bootstrapDelegatedSession(ctx, imported.delegation)).node;
21063
+ bootstrap = await bootstrapDelegatedSession(ctx, imported.delegation);
21064
+ node = bootstrap.node;
20593
21065
  }
20594
- const targetsSessionKey = typeof imported.delegation.delegateDID === "string" && principalDidEquals(imported.delegation.delegateDID, node.sessionDid);
21066
+ const signedAudience = kind === "compact" ? verifiedCompactAudience(node, imported.delegation) : void 0;
21067
+ let transportMatchesAudience = false;
21068
+ if (signedAudience !== void 0) {
21069
+ try {
21070
+ transportMatchesAudience = principalDidEquals(imported.delegation.delegateDID, signedAudience);
21071
+ } catch {
21072
+ }
21073
+ }
21074
+ if (kind === "compact" && (signedAudience === void 0 || !transportMatchesAudience)) {
21075
+ const reason = signedAudience === void 0 ? "proof_invalid" : "audience_mismatch";
21076
+ const refusal = new CLIError(
21077
+ "DELEGATION_REJECTED",
21078
+ `Imported delegation was rejected (${reason}). No grant was stored.`,
21079
+ ExitCode.ERROR
21080
+ );
21081
+ if (bootstrap) await bootstrap.abandon(refusal);
21082
+ throw refusal;
21083
+ }
21084
+ const targetsSessionKey = kind === "signed-login" || signedAudience !== void 0 && principalDidEquals(signedAudience, node.sessionDid);
20595
21085
  let activated = false;
20596
21086
  let permissions = imported.permissions;
20597
- if (targetsSessionKey && kind === "compact") {
20598
- const record = await activateUnboundCompactImport(
20599
- node,
20600
- imported.delegation,
20601
- ctx.host
20602
- );
20603
- await appendAdditionalDelegation(ctx.profile, record);
20604
- permissions = record.permissions;
21087
+ if (targetsSessionKey) {
21088
+ let record2;
21089
+ try {
21090
+ record2 = await activateUnboundImport(
21091
+ node,
21092
+ imported.delegation,
21093
+ ctx.host
21094
+ );
21095
+ } catch (error) {
21096
+ const candidate = error !== null && typeof error === "object" && "reason" in error ? error.reason : void 0;
21097
+ const reason = typeof candidate === "string" && STORED_GRANT_SKIP_REASONS.includes(candidate) ? candidate : "invalid";
21098
+ const refusal = new CLIError(
21099
+ "DELEGATION_REJECTED",
21100
+ `Imported delegation was rejected (${reason}). No grant was stored.`,
21101
+ ExitCode.ERROR
21102
+ );
21103
+ if (bootstrap) await bootstrap.abandon(refusal);
21104
+ throw refusal;
21105
+ }
21106
+ await appendAdditionalDelegation(ctx.profile, record2);
21107
+ permissions = record2.permissions;
20605
21108
  activated = true;
20606
21109
  } else {
20607
21110
  await appendAdditionalDelegation(ctx.profile, storedAdditionalDelegation(
20608
21111
  imported.delegation,
20609
21112
  imported.permissions
20610
21113
  ));
20611
- if (targetsSessionKey) {
20612
- await node.useRuntimeDelegation({
20613
- ...imported.delegation,
20614
- delegationHeader: { Authorization: imported.delegation.delegationHeader.Authorization }
20615
- });
20616
- activated = true;
20617
- }
20618
21114
  }
20619
21115
  await appendGrantHistory(ctx.profile, {
20620
21116
  addedCaps: permissions,
@@ -20635,13 +21131,13 @@ function registerAuthCommand(program) {
20635
21131
  handleError(error);
20636
21132
  }
20637
21133
  });
20638
- auth.command("grant [request]").description("Grant a TinyCloud permission request artifact to its requester").option("--stdin", "Read the JSON request artifact from stdin").option("--paste", "Read the JSON request artifact from stdin").option("--yes", "Skip local-key TTY confirmation", false).action(async (source, options, cmd) => {
21134
+ auth.command("grant [request]").description("Grant a TinyCloud permission request artifact to its requester").option("--stdin", "Read the JSON request artifact from stdin").option("--paste", "Read the JSON request artifact from stdin").option("--yes", "Skip local-key TTY confirmation", false).action(async (source, options2, cmd) => {
20639
21135
  try {
20640
21136
  const globalOpts = cmd.optsWithGlobals();
20641
21137
  const ctx = await ProfileManager.resolveContext(globalOpts);
20642
21138
  const profile = await ProfileManager.getProfile(ctx.profile);
20643
21139
  const raw = await readAuthArtifactSource(source, {
20644
- stdin: options.stdin === true || options.paste === true
21140
+ stdin: options2.stdin === true || options2.paste === true
20645
21141
  });
20646
21142
  const parsed = JSON.parse(raw);
20647
21143
  if (!isCompatiblePermissionRequestArtifact(parsed)) {
@@ -20661,7 +21157,7 @@ function registerAuthCommand(program) {
20661
21157
  requested,
20662
21158
  expiryOption: parsed.requestedExpiry,
20663
21159
  reason: "Grant permissions requested by a TinyCloud auth request artifact.",
20664
- yes: options.yes === true
21160
+ yes: options2.yes === true
20665
21161
  });
20666
21162
  const grant = await grantAuthRequest(node, resolvedRequest);
20667
21163
  await appendLocalGrantArtifact(ctx.profile, grant.delegation);
@@ -20676,21 +21172,21 @@ function registerAuthCommand(program) {
20676
21172
  handleError(error);
20677
21173
  }
20678
21174
  });
20679
- auth.command("retry [requestId]").description("Check whether a stored permission request is now satisfied").option("--last", "Use the latest stored permission request for this profile").option("--exec", "Run the captured command when the request is covered").action(async (requestId, options, cmd) => {
21175
+ auth.command("retry [requestId]").description("Check whether a stored permission request is now satisfied").option("--last", "Use the latest stored permission request for this profile").option("--exec", "Run the captured command when the request is covered").action(async (requestId, options2, cmd) => {
20680
21176
  try {
20681
21177
  const globalOpts = cmd.optsWithGlobals();
20682
21178
  const ctx = await ProfileManager.resolveContext(globalOpts);
20683
- const artifact = options.last ? await getLastPermissionRequestArtifact(ctx.profile) : requestId ? await getPermissionRequestArtifact(ctx.profile, requestId) : null;
21179
+ const artifact = options2.last ? await getLastPermissionRequestArtifact(ctx.profile) : requestId ? await getPermissionRequestArtifact(ctx.profile, requestId) : null;
20684
21180
  if (!artifact) {
20685
21181
  throw new CLIError(
20686
21182
  "REQUEST_NOT_FOUND",
20687
- options.last ? `No stored permission requests exist for profile "${ctx.profile}".` : "Provide a requestId or use --last.",
21183
+ options2.last ? `No stored permission requests exist for profile "${ctx.profile}".` : "Provide a requestId or use --last.",
20688
21184
  ExitCode.NOT_FOUND
20689
21185
  );
20690
21186
  }
20691
21187
  const node = await ensureAuthenticated(ctx);
20692
21188
  const covered = node.hasRuntimePermissions(artifact.requested);
20693
- if (options.exec) {
21189
+ if (options2.exec) {
20694
21190
  if (!covered) {
20695
21191
  throw new CLIError(
20696
21192
  "PERMISSIONS_MISSING",
@@ -20719,11 +21215,11 @@ function registerAuthCommand(program) {
20719
21215
  handleError(error);
20720
21216
  }
20721
21217
  });
20722
- auth.command("caps").description("Show granted capabilities for the active session").option("--diff <spec>", "Show missing capabilities for a spec").option("--history", "Show recent permission grants").action(async (options, cmd) => {
21218
+ auth.command("caps").description("Show granted capabilities for the active session").option("--diff <spec>", "Show missing capabilities for a spec").option("--history", "Show recent permission grants").action(async (options2, cmd) => {
20723
21219
  try {
20724
21220
  const globalOpts = cmd.optsWithGlobals();
20725
21221
  const ctx = await ProfileManager.resolveContext(globalOpts);
20726
- if (options.history) {
21222
+ if (options2.history) {
20727
21223
  const history = (await readGrantHistory(ctx.profile)).slice(-20);
20728
21224
  if (shouldOutputJson()) {
20729
21225
  outputJson({ grants: history });
@@ -20745,8 +21241,8 @@ function registerAuthCommand(program) {
20745
21241
  const node = await ensureAuthenticated(ctx);
20746
21242
  const runtimeDelegations = node.getRuntimePermissionDelegations();
20747
21243
  const granted = runtimeDelegations.flatMap(permissionsFromDelegation);
20748
- if (options.diff) {
20749
- const requested = [await parseCapSpec(options.diff, ctx.profile)];
21244
+ if (options2.diff) {
21245
+ const requested = [await parseCapSpec(options2.diff, ctx.profile)];
20750
21246
  const covered = node.hasRuntimePermissions(requested);
20751
21247
  outputJson({
20752
21248
  requested,
@@ -21032,6 +21528,13 @@ async function ensureDelegationAuthority(params) {
21032
21528
  await requirePermissionConsent(params.requested, params.yes);
21033
21529
  }
21034
21530
  if (params.profile.authMethod === "openkey") {
21531
+ if (params.requested.some(isRawCidRevocationPermission)) {
21532
+ throw new CLIError(
21533
+ "RAW_RECAP_RESOURCE_UNSUPPORTED",
21534
+ "OpenKey cannot sign raw urn:cid revocation authority.",
21535
+ ExitCode.PERMISSION_DENIED
21536
+ );
21537
+ }
21035
21538
  const key = await ProfileManager.getKey(params.ctx.profile);
21036
21539
  if (!key) {
21037
21540
  throw new CLIError(
@@ -21052,26 +21555,14 @@ async function ensureDelegationAuthority(params) {
21052
21555
  const anchorSpace = params.anchorSpace ?? params.profile.spaceId ?? params.profile.spaceName;
21053
21556
  if (params.persist === false) {
21054
21557
  const request = grantRequestPermissions(params.requested, anchorSpace);
21055
- let delegationData;
21056
- try {
21057
- delegationData = await acquireOpenKey(params.profile.did, {
21058
- jwk: key,
21059
- host: params.ctx.host,
21060
- permissions: request,
21061
- reason: permissionGrantReason(params.reason, request),
21062
- openkeyHost,
21063
- expiry: expiryCap === void 0 ? void 0 : openKeyExpiryParam(expiryCap)
21064
- });
21065
- } catch (error) {
21066
- if (params.requested.some(isRawCidRevocationPermission) && /invalid ReCap resource URI/i.test(error instanceof Error ? error.message : String(error))) {
21067
- throw new CLIError(
21068
- "RAW_RECAP_RESOURCE_UNSUPPORTED",
21069
- "The OpenKey/WASM ReCap encoder does not support raw urn:cid revocation resources.",
21070
- ExitCode.PERMISSION_DENIED
21071
- );
21072
- }
21073
- throw error;
21074
- }
21558
+ const delegationData = await acquireOpenKey(params.profile.did, {
21559
+ jwk: key,
21560
+ host: params.ctx.host,
21561
+ permissions: request,
21562
+ reason: permissionGrantReason(params.reason, request),
21563
+ openkeyHost,
21564
+ expiry: expiryCap === void 0 ? void 0 : openKeyExpiryParam(expiryCap)
21565
+ });
21075
21566
  const delegation = portableFromOpenKeyDelegation(
21076
21567
  delegationData,
21077
21568
  request,
@@ -21732,45 +22223,16 @@ init_profiles();
21732
22223
  init_formatter();
21733
22224
  init_errors();
21734
22225
  init_constants();
21735
- import { parseSignedCompactUcanAttenuation } from "@tinycloud/sdk-core";
21736
- function normalizeDid(input) {
22226
+ function normalizeDid2(input) {
21737
22227
  const normalized = input.trim();
21738
22228
  const fragmentIndex = normalized.indexOf("#");
21739
22229
  return (fragmentIndex === -1 ? normalized : normalized.slice(0, fragmentIndex)).toLowerCase();
21740
22230
  }
21741
22231
  function didMatches(actual, expected) {
21742
22232
  if (!actual) return false;
21743
- return normalizeDid(actual) === normalizeDid(expected);
22233
+ return normalizeDid2(actual) === normalizeDid2(expected);
21744
22234
  }
21745
- function ownerDidFromSpace(spaceId) {
21746
- const owner = /^tinycloud:((?:did:)?pkh:eip155:[1-9]\d*:0x[0-9a-fA-F]{40})(?::|\/|$)/.exec(spaceId ?? "")?.[1];
21747
- if (!owner) return void 0;
21748
- return owner.startsWith("did:") ? owner : `did:${owner}`;
21749
- }
21750
- async function resolveCidBoundTargetSpace(params) {
21751
- if (!params.ownerDid) return void 0;
21752
- const grants = await loadLocalGrantArtifacts(params.profileName);
21753
- for (const grant of grants) {
21754
- const delegation = grant.delegation;
21755
- if (grant.delegationCid !== params.cid || delegation.cid !== params.cid) continue;
21756
- try {
21757
- const authorization = delegation.delegationHeader.Authorization.replace(/^Bearer /i, "");
21758
- const signed = parseSignedCompactUcanAttenuation(authorization, params.cid);
21759
- for (const resource of Object.keys(signed.payload.att)) {
21760
- const space = /^(tinycloud:[^/]+)\/[^/]+\/.*$/.exec(resource)?.[1];
21761
- if (!space) continue;
21762
- const ownerMatch = /^tinycloud:((?:did:)?pkh:eip155:[1-9]\d*:0x[0-9a-fA-F]{40}):/.exec(space);
21763
- const owner = ownerMatch?.[1];
21764
- const ownerDid = owner?.startsWith("did:") ? owner : owner ? `did:${owner}` : void 0;
21765
- if (ownerDid && normalizeDid(ownerDid) === normalizeDid(params.ownerDid)) return space;
21766
- }
21767
- } catch {
21768
- continue;
21769
- }
21770
- }
21771
- return void 0;
21772
- }
21773
- function registerDelegationCommand(program) {
22235
+ function registerDelegationCommand(program, configuration = {}) {
21774
22236
  const delegation = program.command("delegation").description("Manage delegations");
21775
22237
  delegation.command("create").description("Create a delegation").requiredOption("--to <did>", "Recipient DID").requiredOption("--path <path>", "KV path scope").requiredOption("--actions <actions>", "Comma-separated actions (e.g., kv/get,kv/list)").option("--expiry <duration>", "Expiry duration (e.g., 1h, 7d, ISO date)", "1h").action(async (options, cmd) => {
21776
22238
  try {
@@ -21854,152 +22316,14 @@ function registerDelegationCommand(program) {
21854
22316
  const ctx = await ProfileManager.resolveContext(globalOpts);
21855
22317
  const node = await ensureAuthenticated(ctx);
21856
22318
  const profile = await ProfileManager.getProfile(ctx.profile);
21857
- const revokePermission = {
21858
- service: "tinycloud.delegation",
21859
- space: `urn:cid:${cid}`,
21860
- path: "",
21861
- actions: ["tinycloud.delegation/revoke"]
21862
- };
21863
- const rawAuthorityCovered = node.hasRuntimePermissions([revokePermission]);
21864
- if (!rawAuthorityCovered) await requirePermissionConsent([revokePermission], options.yes);
21865
- let targetFound = false;
21866
- let targetSpaceSource;
21867
- let queryDenied = false;
21868
- let boundSpace;
21869
- const accountSpaceId = node.accountSpaceId;
21870
- if (!accountSpaceId) {
21871
- throw new CLIError(
21872
- "DELEGATION_QUERY_AUTHORITY_UNAVAILABLE",
21873
- "Cannot determine the account space needed to query delegation records.",
21874
- ExitCode.AUTH_REQUIRED
21875
- );
21876
- }
21877
- let cursor;
21878
- do {
21879
- const query = await node.delegationManager.query({
21880
- direction: "all",
21881
- limit: 100,
21882
- ...cursor === void 0 ? {} : { cursor }
21883
- });
21884
- if (!query.ok) {
21885
- if (query.error.meta?.status === 403) {
21886
- queryDenied = true;
21887
- break;
21888
- }
21889
- throw cliErrorFromService(query.error);
21890
- }
21891
- targetFound = query.data.items.some((delegation2) => delegation2.cid === cid);
21892
- cursor = targetFound ? void 0 : query.data.nextCursor;
21893
- } while (cursor !== void 0);
21894
- targetSpaceSource = "node";
21895
- if (!targetFound) {
21896
- const grantHistory = await readGrantHistory(ctx.profile);
21897
- const recordedGrant = [...grantHistory].reverse().find((entry) => entry.delegationCid === cid);
21898
- if (recordedGrant) {
21899
- targetSpaceSource = "local-grant-history";
21900
- } else if (queryDenied) {
21901
- boundSpace = await resolveCidBoundTargetSpace({
21902
- profileName: ctx.profile,
21903
- ownerDid: profile.ownerDid ?? ownerDidFromSpace(node.accountSpaceId) ?? ownerDidFromSpace(profile.spaceId),
21904
- cid
21905
- });
21906
- if (boundSpace) {
21907
- targetSpaceSource = "local-signed-grant-artifact";
21908
- } else {
21909
- throw new CLIError(
21910
- "TARGET_NOT_FOUND",
21911
- `Delegation "${cid}" was not found by the node or local grant sources.`,
21912
- ExitCode.NOT_FOUND
21913
- );
21914
- }
21915
- } else {
21916
- throw new CLIError(
21917
- "TARGET_NOT_FOUND",
21918
- `Delegation "${cid}" was not found by the node or local grant history.`,
21919
- ExitCode.NOT_FOUND
21920
- );
21921
- }
21922
- }
21923
- let authorityScopeSource = "cid-resource";
21924
- let authorityScopeReason = "The revoke authority is scoped to the exact delegation CID.";
21925
- let fallbackSpaceId;
21926
- let commandAuthorityCid;
21927
- const authorizeFallbackSpace = async (spaceId) => {
21928
- fallbackSpaceId = spaceId;
21929
- authorityScopeSource = "local-signed-grant-artifact";
21930
- authorityScopeReason = "Raw-CID revoke authority could not be issued; the fallback space came from an owner-signed local grant whose Authorization recomputes to this CID.";
21931
- targetSpaceSource = "local-signed-grant-artifact";
21932
- const acquired = await ensureDelegationAuthority({
21933
- ctx,
21934
- profile,
21935
- node,
21936
- requested: [{
21937
- service: "tinycloud.delegation",
21938
- space: spaceId === node.accountSpaceId ? "default" : spaceId,
21939
- path: "",
21940
- actions: ["tinycloud.delegation/revoke"]
21941
- }],
21942
- expiryOption: void 0,
21943
- reason: `Revoke delegation ${cid} using its CID-bound owner-signed grant artifact`,
21944
- yes: options.yes,
21945
- consentAlreadyGiven: true,
21946
- persist: false
21947
- });
21948
- commandAuthorityCid = acquired?.cid;
21949
- };
21950
- if (!rawAuthorityCovered) {
21951
- try {
21952
- const acquired = await ensureDelegationAuthority({
21953
- ctx,
21954
- profile,
21955
- node,
21956
- requested: [revokePermission],
21957
- expiryOption: void 0,
21958
- reason: `Revoke delegation ${cid}`,
21959
- yes: options.yes,
21960
- consentAlreadyGiven: true,
21961
- persist: false
21962
- });
21963
- commandAuthorityCid = acquired?.cid;
21964
- } catch (error) {
21965
- if (error === null || typeof error !== "object" || !("code" in error) || error.code !== "RAW_RECAP_RESOURCE_UNSUPPORTED") {
21966
- throw error;
21967
- }
21968
- const fallbackSpace = boundSpace ?? await resolveCidBoundTargetSpace({
21969
- profileName: ctx.profile,
21970
- ownerDid: profile.ownerDid ?? ownerDidFromSpace(node.accountSpaceId) ?? ownerDidFromSpace(profile.spaceId),
21971
- cid
21972
- });
21973
- if (!fallbackSpace) {
21974
- throw new CLIError(
21975
- "RAW_RECAP_RESOURCE_UNSUPPORTED",
21976
- `Cannot safely revoke "${cid}": raw CID ReCap resources are unavailable and no matching owner-signed local grant artifact binds its space to the CID.`,
21977
- ExitCode.PERMISSION_DENIED
21978
- );
21979
- }
21980
- await authorizeFallbackSpace(fallbackSpace);
21981
- }
21982
- }
21983
- const result = fallbackSpaceId ? await node.delegationManager.revoke(cid, {
21984
- targetSpaceId: fallbackSpaceId,
21985
- ...commandAuthorityCid === void 0 ? {} : { authorityCid: commandAuthorityCid }
21986
- }) : commandAuthorityCid === void 0 ? await node.revokeDelegation(cid) : await node.delegationManager.revoke(cid, { authorityCid: commandAuthorityCid });
21987
- if (!result.ok) {
21988
- if (result.error.meta?.status === 403 && result.error.message === "Failed to revoke delegation: 403 - Unauthorized Revoker") {
21989
- const profileDid = profile.ownerDid ?? ownerDidFromSpace(node.accountSpaceId) ?? ownerDidFromSpace(profile.spaceId) ?? "unknown";
21990
- throw new CLIError(
21991
- "REVOKE_UNAUTHORIZED",
21992
- "The node rejected this revocation: Unauthorized Revoker.",
21993
- ExitCode.PERMISSION_DENIED,
21994
- {
21995
- status: 403,
21996
- 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 \`tc --profile <profile-name> delegation revoke ${cid}\`.`
21997
- }
21998
- );
21999
- }
22000
- throw cliErrorFromService(result.error);
22001
- }
22002
- outputJson({ cid, revoked: true, targetSpaceSource, authorityScopeSource, authorityScopeReason });
22319
+ outputJson(await revokeDelegationOnNode({
22320
+ ctx,
22321
+ node,
22322
+ profile,
22323
+ cid,
22324
+ yes: options.yes,
22325
+ openKeyAcquisition: configuration.openKeyAcquisition
22326
+ }));
22003
22327
  } catch (error) {
22004
22328
  handleError(error);
22005
22329
  }
@@ -23206,8 +23530,8 @@ async function storedGrants(profile) {
23206
23530
  const records = await readAdditionalDelegations2(profile).catch(
23207
23531
  () => []
23208
23532
  );
23209
- for (const record of records) {
23210
- const authorization = record.delegation?.delegationHeader?.Authorization;
23533
+ for (const record2 of records) {
23534
+ const authorization = record2.delegation?.delegationHeader?.Authorization;
23211
23535
  if (typeof authorization === "string") authorizations.push(authorization);
23212
23536
  }
23213
23537
  const grants = [];
@@ -23572,6 +23896,7 @@ init_profiles();
23572
23896
  init_formatter();
23573
23897
  init_theme();
23574
23898
  init_errors();
23899
+ init_warnings();
23575
23900
  init_constants();
23576
23901
  import { readFile as readFile9 } from "fs/promises";
23577
23902
  import { join as join9 } from "path";
@@ -23762,16 +24087,16 @@ function withOperationWarnings(error, warnings) {
23762
24087
  return cliError;
23763
24088
  }
23764
24089
  async function invokeCanonicalSecretGet(params) {
23765
- const warnings = /* @__PURE__ */ new Map();
24090
+ let warnings = [];
23766
24091
  const collect = (result) => {
23767
- for (const warning of result.warnings ?? []) warnings.set(JSON.stringify(warning), warning);
24092
+ warnings = mergeWarnings(warnings, result.warnings ?? []);
23768
24093
  return result;
23769
24094
  };
23770
24095
  try {
23771
24096
  const result = await canonicalSecretGet(params, collect);
23772
- return warnings.size === 0 ? result : { ...result, warnings: [...warnings.values()] };
24097
+ return warnings.length === 0 ? result : { ...result, warnings };
23773
24098
  } catch (error) {
23774
- throw withOperationWarnings(error, [...warnings.values()]);
24099
+ throw withOperationWarnings(error, warnings);
23775
24100
  }
23776
24101
  }
23777
24102
  async function canonicalSecretGet(params, collect) {
@@ -23889,9 +24214,9 @@ function shouldRequestSecretPermissions(error) {
23889
24214
  return /permission|session expired|autosign|capabilit/i.test(error.message);
23890
24215
  }
23891
24216
  function thrownPermissionError(error) {
23892
- const record = error;
23893
- const message = typeof record?.message === "string" ? record.message : String(error);
23894
- const code3 = typeof record?.code === "string" ? record.code : "PERMISSION_DENIED";
24217
+ const record2 = error;
24218
+ const message = typeof record2?.message === "string" ? record2.message : String(error);
24219
+ const code3 = typeof record2?.code === "string" ? record2.code : "PERMISSION_DENIED";
23895
24220
  if (code3 !== "PERMISSION_DENIED" && !/permission|session expired|autosign|capabilit/i.test(message)) {
23896
24221
  return null;
23897
24222
  }
@@ -24205,11 +24530,11 @@ async function readDelegatedSecretValue(params) {
24205
24530
  return parseDecryptedSecretPayload(decrypted.data, params.secretPath);
24206
24531
  }
24207
24532
  function isStoredSessionExpired(session) {
24208
- const record = session;
24209
- const direct = parseDate(record.expiresAt ?? record.expiry ?? record.expirationTime);
24533
+ const record2 = session;
24534
+ const direct = parseDate(record2.expiresAt ?? record2.expiry ?? record2.expirationTime);
24210
24535
  if (direct) return direct.getTime() <= Date.now();
24211
- if (typeof record.siwe !== "string") return false;
24212
- const match = record.siwe.match(/^Expiration Time:\s*(.+)$/im);
24536
+ if (typeof record2.siwe !== "string") return false;
24537
+ const match = record2.siwe.match(/^Expiration Time:\s*(.+)$/im);
24213
24538
  const expiry = match ? parseDate(match[1].trim()) : null;
24214
24539
  return expiry !== null && expiry.getTime() <= Date.now();
24215
24540
  }
@@ -29202,7 +29527,7 @@ function baseCache(cid2) {
29202
29527
  }
29203
29528
  return baseCache22;
29204
29529
  }
29205
- var CID = class _CID {
29530
+ var CID2 = class _CID {
29206
29531
  code;
29207
29532
  version;
29208
29533
  multihash;
@@ -29521,12 +29846,12 @@ var code = 85;
29521
29846
  var SHA256_CODE = 18;
29522
29847
  async function computeCid(bytes3) {
29523
29848
  const digest3 = create(SHA256_CODE, sha256(new Uint8Array(bytes3)));
29524
- return CID.create(1, code, digest3).toString();
29849
+ return CID2.create(1, code, digest3).toString();
29525
29850
  }
29526
29851
  function isCanonicalRawCid(cidString) {
29527
29852
  let cid2;
29528
29853
  try {
29529
- cid2 = CID.parse(cidString);
29854
+ cid2 = CID2.parse(cidString);
29530
29855
  } catch {
29531
29856
  return false;
29532
29857
  }
@@ -30111,7 +30436,7 @@ function verifyCompactUcanAuthorization(authorization, expectedCid) {
30111
30436
  const publicKey = ed25519PublicKeyFromDidKey(principal);
30112
30437
  if (!equal(publicKey, fromBase64Url(jwk.x))) throw new TypeError("compact Authorization JWK does not bind issuer");
30113
30438
  if (!ed25519.verify(signature, new TextEncoder().encode(`${headerSegment}.${payloadSegment}`), publicKey, { zip215: false })) throw new TypeError("compact Authorization signature is invalid");
30114
- const cid2 = CID.createV1(85, create(30, blake3(new TextEncoder().encode(authorization)))).toString();
30439
+ const cid2 = CID2.createV1(85, create(30, blake3(new TextEncoder().encode(authorization)))).toString();
30115
30440
  if (expectedCid !== void 0 && cid2 !== expectedCid) throw new TypeError("compact Authorization CID mismatch");
30116
30441
  return { authorization, cid: cid2, header, payload };
30117
30442
  }
@@ -30230,7 +30555,7 @@ function base32Lower(bytes3) {
30230
30555
  var KEY_LENGTH2 = 32;
30231
30556
  var MAX_INLINE_BYTES = 256 * 1024;
30232
30557
  function assertCanonicalCid(cidString) {
30233
- const cid2 = CID.parse(cidString);
30558
+ const cid2 = CID2.parse(cidString);
30234
30559
  if (cid2.version !== 1 || cid2.code !== code || cid2.multihash.code !== 18 || // sha2-256 (0x12) only, at the link layer too
30235
30560
  cid2.toString() !== cidString) {
30236
30561
  throw new TypeError(`not a canonical CIDv1 raw sha2-256 base32 CID: ${cidString}`);
@@ -30261,21 +30586,21 @@ async function parseSealedInlineShareUrl(url, options = {}) {
30261
30586
  throw new TypeError("sealed inline payload is not valid JSON");
30262
30587
  }
30263
30588
  if (typeof value !== "object" || value === null || Array.isArray(value) || canonicalize2(value) !== payloadText) throw new TypeError("sealed inline payload is not canonical JSON");
30264
- const record = value;
30265
- if (Object.keys(record).length !== 4 || record.v !== 2 || typeof record.c !== "string" || typeof record.cid !== "string" || typeof record.k !== "string") throw new TypeError("sealed inline payload has invalid fields");
30589
+ const record2 = value;
30590
+ if (Object.keys(record2).length !== 4 || record2.v !== 2 || typeof record2.c !== "string" || typeof record2.cid !== "string" || typeof record2.k !== "string") throw new TypeError("sealed inline payload has invalid fields");
30266
30591
  let ciphertext;
30267
30592
  let key32;
30268
30593
  try {
30269
- ciphertext = fromBase64Url(record.c);
30270
- key32 = fromBase64Url(record.k);
30594
+ ciphertext = fromBase64Url(record2.c);
30595
+ key32 = fromBase64Url(record2.k);
30271
30596
  } catch {
30272
30597
  throw new TypeError("sealed inline payload has invalid base64url material");
30273
30598
  }
30274
30599
  if (ciphertext.byteLength === 0 || ciphertext.byteLength > MAX_INLINE_BYTES) throw new TypeError("sealed inline ciphertext is outside the allowed size");
30275
30600
  if (key32.byteLength !== KEY_LENGTH2) throw new TypeError("sealed inline key must be 32 bytes");
30276
- assertCanonicalCid(record.cid);
30277
- if (await computeCid(ciphertext) !== record.cid) throw new TypeError("sealed inline ciphertext does not match its CID");
30278
- return { kind: "inline", ciphertextCid: record.cid, ciphertext, key32 };
30601
+ assertCanonicalCid(record2.cid);
30602
+ if (await computeCid(ciphertext) !== record2.cid) throw new TypeError("sealed inline ciphertext does not match its CID");
30603
+ return { kind: "inline", ciphertextCid: record2.cid, ciphertext, key32 };
30279
30604
  }
30280
30605
  var SHARE_RESULT_VERSION = 1;
30281
30606
  var DEFAULT_MAX_SEALED_BLOB_BYTES = 100 * 1024 * 1024 + 29;
@@ -31040,7 +31365,7 @@ function baseCache2(cid2) {
31040
31365
  }
31041
31366
  return baseCache32;
31042
31367
  }
31043
- var CID2 = class _CID2 {
31368
+ var CID22 = class _CID2 {
31044
31369
  code;
31045
31370
  version;
31046
31371
  multihash;
@@ -31398,16 +31723,16 @@ var ShareNotifyError = class extends Error {
31398
31723
  }
31399
31724
  code = "delivery-failed";
31400
31725
  };
31401
- function recipientMatchesShareRecord(record, recipient) {
31402
- const matcher = record.recipientMatcher;
31726
+ function recipientMatchesShareRecord(record2, recipient) {
31727
+ const matcher = record2.recipientMatcher;
31403
31728
  const mailbox = canonicalMailbox(recipient);
31404
31729
  if (mailbox === void 0) return false;
31405
31730
  if (matcher.kind === "exactEmail") return canonicalMailbox(matcher.value)?.email === mailbox.email;
31406
31731
  if (matcher.kind === "emailDomain") return mailbox.domain === matcher.value;
31407
31732
  return false;
31408
31733
  }
31409
- function shareDeliveryWindowExpiresAt(record) {
31410
- const expiresAt = Math.min(Date.parse(record.expiresAt), Date.parse(record.registeredAt) + 5 * 60 * 1e3);
31734
+ function shareDeliveryWindowExpiresAt(record2) {
31735
+ const expiresAt = Math.min(Date.parse(record2.expiresAt), Date.parse(record2.registeredAt) + 5 * 60 * 1e3);
31411
31736
  if (!Number.isFinite(expiresAt)) throw new ShareNotifyError("share delivery history has invalid timestamps");
31412
31737
  return expiresAt;
31413
31738
  }
@@ -31484,17 +31809,17 @@ async function defaultIdempotencyKey(shareId, recipient) {
31484
31809
  ));
31485
31810
  return `tinycloud-share:${shareId}:${toBase64Url(digest3)}`;
31486
31811
  }
31487
- function targetKind2(record) {
31488
- if (record.targetKind !== void 0) return record.targetKind;
31489
- return record.recipientMatcher.kind === "exactEmail" ? "email" : record.recipientMatcher.kind === "emailDomain" ? "emailDomain" : record.recipientMatcher.kind === "recipientDid" ? "recipientDid" : "bearer";
31812
+ function targetKind2(record2) {
31813
+ if (record2.targetKind !== void 0) return record2.targetKind;
31814
+ return record2.recipientMatcher.kind === "exactEmail" ? "email" : record2.recipientMatcher.kind === "emailDomain" ? "emailDomain" : record2.recipientMatcher.kind === "recipientDid" ? "recipientDid" : "bearer";
31490
31815
  }
31491
- function policyNodeAudience(record) {
31492
- const envelope = record.deliveryMaterial?.envelope;
31493
- if (typeof envelope !== "object" || envelope === null || Array.isArray(envelope)) return record.target.nodeAudience;
31816
+ function policyNodeAudience(record2) {
31817
+ const envelope = record2.deliveryMaterial?.envelope;
31818
+ if (typeof envelope !== "object" || envelope === null || Array.isArray(envelope)) return record2.target.nodeAudience;
31494
31819
  const binding = envelope.attestedEnforcerBinding;
31495
- if (typeof binding !== "object" || binding === null || Array.isArray(binding)) return record.target.nodeAudience;
31820
+ if (typeof binding !== "object" || binding === null || Array.isArray(binding)) return record2.target.nodeAudience;
31496
31821
  const nodeAudience = binding.nodeAudience;
31497
- return typeof nodeAudience === "string" ? nodeAudience : record.target.nodeAudience;
31822
+ return typeof nodeAudience === "string" ? nodeAudience : record2.target.nodeAudience;
31498
31823
  }
31499
31824
  async function revokeShare(input) {
31500
31825
  const target = targetKind2(input.record);
@@ -31518,32 +31843,32 @@ async function revokeShare(input) {
31518
31843
  }
31519
31844
  const revokedAt = (input.now?.() ?? /* @__PURE__ */ new Date()).toISOString();
31520
31845
  if (input.records?.update !== void 0) {
31521
- await input.records.update(input.record.shareId, (record) => ({ ...record, revokedAt }));
31846
+ await input.records.update(input.record.shareId, (record2) => ({ ...record2, revokedAt }));
31522
31847
  } else if (input.records !== void 0) {
31523
31848
  await input.records.put({ ...input.record, revokedAt });
31524
31849
  }
31525
31850
  return { state: "revoked", target, delegationCid, revokedAt };
31526
31851
  }
31527
- function redactRecord(record, revealLink, link3) {
31528
- const matcher = record.recipientMatcher;
31529
- const revealedLink = revealLink ? link3 ?? record.link : void 0;
31852
+ function redactRecord(record2, revealLink, link3) {
31853
+ const matcher = record2.recipientMatcher;
31854
+ const revealedLink = revealLink ? link3 ?? record2.link : void 0;
31530
31855
  return {
31531
- shareId: record.shareId,
31856
+ shareId: record2.shareId,
31532
31857
  target: matcher.kind === "exactEmail" ? "email" : matcher.kind === "emailDomain" ? "email-domain" : matcher.kind === "recipientDid" ? "recipient-did" : "bearer",
31533
31858
  ...matcher.kind === "exactEmail" ? { recipient: matcher.value } : matcher.kind === "emailDomain" ? { recipient: `*@${matcher.value}` } : matcher.kind === "recipientDid" ? { recipient: matcher.value } : {},
31534
- expiresAt: record.expiresAt,
31535
- revoked: record.revokedAt !== void 0,
31859
+ expiresAt: record2.expiresAt,
31860
+ revoked: record2.revokedAt !== void 0,
31536
31861
  ...revealedLink === void 0 ? {} : { link: revealedLink }
31537
31862
  };
31538
31863
  }
31539
31864
  async function listShares(storage) {
31540
31865
  const records = await storage.list();
31541
- return records.map((record) => redactRecord(record, false));
31866
+ return records.map((record2) => redactRecord(record2, false));
31542
31867
  }
31543
31868
  async function showShare(input) {
31544
- const record = await input.storage.get(input.shareId);
31545
- if (record === void 0) throw new Error("share not found");
31546
- return redactRecord(record, input.revealLink === true, input.link);
31869
+ const record2 = await input.storage.get(input.shareId);
31870
+ if (record2 === void 0) throw new Error("share not found");
31871
+ return redactRecord(record2, input.revealLink === true, input.link);
31547
31872
  }
31548
31873
  var SHARE_V2_PROTOCOL = Object.freeze({
31549
31874
  challengeDomain: "xyz.tinycloud.share/policy-challenge/v2\0",
@@ -33202,24 +33527,24 @@ function expires(value) {
33202
33527
  }
33203
33528
  }
33204
33529
  async function rememberPublishedShare(result) {
33205
- const record = historyRecordForPublishedShare(result);
33206
- if (shareServices.records === void 0) return { record, recorded: false };
33530
+ const record2 = historyRecordForPublishedShare(result);
33531
+ if (shareServices.records === void 0) return { record: record2, recorded: false };
33207
33532
  try {
33208
- await shareServices.records.put(record);
33209
- return { record, recorded: true };
33533
+ await shareServices.records.put(record2);
33534
+ return { record: record2, recorded: true };
33210
33535
  } catch {
33211
33536
  process.stderr.write("Warning: share was published but not recorded in sender history; future share list, share notify, and share revoke by ID will not find it.\n");
33212
- return { record, recorded: false };
33537
+ return { record: record2, recorded: false };
33213
33538
  }
33214
33539
  }
33215
- async function notifyRecordedShare(record, recipient, adapter, storage) {
33216
- const result = await notifyShare({ shareId: record.shareId, recipient, record, adapter, checkDeliveryWindow: true });
33540
+ async function notifyRecordedShare(record2, recipient, adapter, storage) {
33541
+ const result = await notifyShare({ shareId: record2.shareId, recipient, record: record2, adapter, checkDeliveryWindow: true });
33217
33542
  if (result.state !== "partial-failure" && storage !== void 0) {
33218
33543
  const mailbox = canonicalMailbox2(recipient).email;
33219
- if (!record.deliveredRecipients?.includes(mailbox)) {
33544
+ if (!record2.deliveredRecipients?.includes(mailbox)) {
33220
33545
  try {
33221
33546
  if (storage.update === void 0) throw new Error("atomic sender history update is unavailable");
33222
- const updated = await storage.update(record.shareId, (current) => ({
33547
+ const updated = await storage.update(record2.shareId, (current) => ({
33223
33548
  ...current,
33224
33549
  deliveredRecipients: current.deliveredRecipients?.includes(mailbox) ? current.deliveredRecipients : [...current.deliveredRecipients ?? [], mailbox]
33225
33550
  }));
@@ -33309,11 +33634,11 @@ function registerShareCommand(program) {
33309
33634
  }
33310
33635
  throw new CLIError(result.method === "openkey-device" ? "DEVICE_AUTH_REQUIRED" : "CLAIM_REQUIRED", "recipient authorization is required; continue through the configured authority adapter", 6);
33311
33636
  }
33312
- const { record, recorded } = await rememberPublishedShare(result);
33637
+ const { record: record2, recorded } = await rememberPublishedShare(result);
33313
33638
  let notification;
33314
33639
  if (notifyRecipient !== void 0) {
33315
33640
  if (shareServices.delivery === void 0) throw new CLIError("AUTH_REQUIRED", "delivery authority is not configured", 3);
33316
- notification = await notifyRecordedShare(record, notifyRecipient, shareServices.delivery, recorded ? shareServices.records : void 0);
33641
+ notification = await notifyRecordedShare(record2, notifyRecipient, shareServices.delivery, recorded ? shareServices.records : void 0);
33317
33642
  if (notification.state === "partial-failure") process.exitCode = 9;
33318
33643
  if (notification.reason === "delivery-window-expired") process.stderr.write(NOTIFY_WINDOW_MESSAGE);
33319
33644
  }
@@ -33411,19 +33736,19 @@ function registerShareCommand(program) {
33411
33736
  const json = jsonOutput(options, command);
33412
33737
  if (shareServices.delivery === void 0) throw new CLIError("AUTH_REQUIRED", "delivery authority is not configured", 3);
33413
33738
  if (shareServices.records === void 0) throw new CLIError("AUTH_REQUIRED", "sender history storage is not configured", 3);
33414
- const record = await shareServices.records.get(id);
33415
- if (record === void 0) throw new CLIError("NOT_FOUND", "share not found", 4);
33416
- if (!record.actions.includes("tinycloud.kv/get")) {
33739
+ const record2 = await shareServices.records.get(id);
33740
+ if (record2 === void 0) throw new CLIError("NOT_FOUND", "share not found", 4);
33741
+ if (!record2.actions.includes("tinycloud.kv/get")) {
33417
33742
  throw new CLIError("INVALID_ARGUMENT", "share notify requires a stored share with the read action; no invitation was sent", 2);
33418
33743
  }
33419
33744
  const mailbox = canonicalMailbox2(options.to);
33420
33745
  if (mailbox === void 0) throw new ShareNotifyError("recipient is invalid");
33421
- if (!recipientMatchesShareRecord(record, mailbox.email)) throw new ShareNotifyError("recipient does not match the stored share target");
33422
- if (record.recipientMatcher.kind === "emailDomain" && shareDeliveryWindowExpiresAt(record) > Date.now()) {
33746
+ if (!recipientMatchesShareRecord(record2, mailbox.email)) throw new ShareNotifyError("recipient does not match the stored share target");
33747
+ if (record2.recipientMatcher.kind === "emailDomain" && shareDeliveryWindowExpiresAt(record2) > Date.now()) {
33423
33748
  if (shareServices.assertDomainDelivery === void 0) throw new CLIError("UNAVAILABLE", "could not verify TinyCloud node 1.17.3 domain delivery support; no invitation was sent", 4);
33424
33749
  await shareServices.assertDomainDelivery();
33425
33750
  }
33426
- const result = await notifyRecordedShare(record, options.to, shareServices.delivery, shareServices.records);
33751
+ const result = await notifyRecordedShare(record2, options.to, shareServices.delivery, shareServices.records);
33427
33752
  if (json) writeJson2(result);
33428
33753
  else process.stdout.write(`${result.state}
33429
33754
  `);
@@ -33437,9 +33762,9 @@ function registerShareCommand(program) {
33437
33762
  try {
33438
33763
  const json = jsonOutput(options, command);
33439
33764
  if (shareServices.records === void 0) throw new CLIError("AUTH_REQUIRED", "sender history storage is not configured", 3);
33440
- const record = shareServices.getRecord ? await shareServices.getRecord(id) : await shareServices.records.get(id);
33441
- if (record === void 0) throw new CLIError("NOT_FOUND", "share not found", 4);
33442
- const result = await revokeShare({ record, records: shareServices.records, adapter: shareServices.revocation, scope: options.ancestor ? "ancestor" : "direct" });
33765
+ const record2 = shareServices.getRecord ? await shareServices.getRecord(id) : await shareServices.records.get(id);
33766
+ if (record2 === void 0) throw new CLIError("NOT_FOUND", "share not found", 4);
33767
+ const result = await revokeShare({ record: record2, records: shareServices.records, adapter: shareServices.revocation, scope: options.ancestor ? "ancestor" : "direct" });
33443
33768
  if (result.state === "unsupported") {
33444
33769
  throw new CLIError("UNSUPPORTED_TARGET", result.reason, 2);
33445
33770
  }
@@ -34009,8 +34334,8 @@ async function readHasKey(name, issues) {
34009
34334
  async function readDelegations(name, issues) {
34010
34335
  try {
34011
34336
  const entries = await loadAdditionalDelegations(name);
34012
- const { storedDelegationKind } = await import("@tinycloud/operations/delegation-binding");
34013
- const readable = entries.filter((entry) => storedDelegationKind(entry) !== "refused");
34337
+ const { storedDelegationKind: storedDelegationKind2 } = await import("@tinycloud/operations/delegation-binding");
34338
+ const readable = entries.filter((entry) => storedDelegationKind2(entry) !== "refused");
34014
34339
  if (readable.length < entries.length) {
34015
34340
  issues.push(`delegations: ${entries.length - readable.length} unreadable stored record(s) skipped`);
34016
34341
  }
@@ -34053,12 +34378,12 @@ function sessionPermissionsFromRecap(session) {
34053
34378
  }
34054
34379
  }
34055
34380
  function permissionFromRawRecap(value) {
34056
- const record = asRecord(value);
34057
- if (!record) return null;
34058
- const service = stringValue(record.service);
34059
- const space = stringValue(record.space);
34060
- const path = stringValue(record.path);
34061
- const actions = Array.isArray(record.actions) ? record.actions.map(String).filter(Boolean) : [];
34381
+ const record2 = asRecord(value);
34382
+ if (!record2) return null;
34383
+ const service = stringValue(record2.service);
34384
+ const space = stringValue(record2.space);
34385
+ const path = stringValue(record2.path);
34386
+ const actions = Array.isArray(record2.actions) ? record2.actions.map(String).filter(Boolean) : [];
34062
34387
  if (!service || !space || path === null || actions.length === 0) return null;
34063
34388
  return {
34064
34389
  service: normalizeService2(service),
@@ -34076,12 +34401,12 @@ function normalizePermissions(entries) {
34076
34401
  return uniquePermissions(permissions);
34077
34402
  }
34078
34403
  function permissionFromUnknown(value) {
34079
- const record = asRecord(value);
34080
- if (!record) return null;
34081
- const service = stringValue(record.service);
34082
- const space = stringValue(record.space);
34083
- const path = stringValue(record.path);
34084
- const actions = Array.isArray(record.actions) ? record.actions.map(String).filter(Boolean) : [];
34404
+ const record2 = asRecord(value);
34405
+ if (!record2) return null;
34406
+ const service = stringValue(record2.service);
34407
+ const space = stringValue(record2.space);
34408
+ const path = stringValue(record2.path);
34409
+ const actions = Array.isArray(record2.actions) ? record2.actions.map(String).filter(Boolean) : [];
34085
34410
  if (!service || !space || path === null || actions.length === 0) return null;
34086
34411
  return {
34087
34412
  service: normalizeService2(service),