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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -59,7 +59,9 @@ var init_constants = __esm({
59
59
  NOT_FOUND: 4,
60
60
  PERMISSION_DENIED: 5,
61
61
  NETWORK_ERROR: 6,
62
- NODE_ERROR: 7
62
+ NODE_ERROR: 7,
63
+ /** A write was refused because the owner's TinyCloud storage is full. */
64
+ STORAGE_FULL: 10
63
65
  };
64
66
  }
65
67
  });
@@ -163,8 +165,14 @@ import ora from "ora";
163
165
  function outputJson(data) {
164
166
  process.stdout.write(JSON.stringify(data, null, 2) + "\n");
165
167
  }
166
- function outputError(code3, message, hint, meta) {
167
- if (isInteractive()) {
168
+ function warningLine(warnings) {
169
+ const which = warnings.map((warning) => `${warning.grantCid ?? "unidentified"}: ${warning.reason}`).join(", ");
170
+ const subject = warnings.length === 1 ? "A stored grant was" : `${warnings.length} stored grants were`;
171
+ return `${subject} not used (${which}). Approve access again when a command needs it; the old grant expires on its own.`;
172
+ }
173
+ function outputError(code3, message, hint, details = {}) {
174
+ const { meta, warnings = [] } = details;
175
+ if (!shouldOutputJson()) {
168
176
  process.stderr.write(
169
177
  `${theme.error("\u2717")} ${theme.label(code3)}: ${message}
170
178
  `
@@ -175,20 +183,40 @@ function outputError(code3, message, hint, meta) {
175
183
  `);
176
184
  }
177
185
  }
186
+ if (warnings.length > 0) process.stderr.write(` ${theme.warn(warningLine(warnings))}
187
+ `);
178
188
  } else {
179
189
  const payload = {
180
190
  error: { code: code3, message }
181
191
  };
182
192
  if (hint) payload.error.hint = hint;
183
193
  if (meta) payload.error.meta = meta;
194
+ if (warnings.length > 0) payload.error.warnings = warnings;
184
195
  process.stderr.write(JSON.stringify(payload, null, 2) + "\n");
185
196
  }
186
197
  }
198
+ function outputWarnings(warnings) {
199
+ if (warnings.length === 0) return;
200
+ process.stderr.write(shouldOutputJson() ? `${JSON.stringify({ warnings })}
201
+ ` : `${theme.warn("!")} ${warningLine(warnings)}
202
+ `);
203
+ }
204
+ function operationWarnings(value) {
205
+ if (!Array.isArray(value)) return [];
206
+ return value.flatMap((warning) => {
207
+ if (warning === null || typeof warning !== "object") return [];
208
+ const { code: code3, reason, grantCid } = warning;
209
+ if (typeof code3 !== "string" || !WARNING_CODE.test(code3) || typeof reason !== "string" || !WARNING_REASON.test(reason)) {
210
+ return [];
211
+ }
212
+ return [{ code: code3, reason, ...typeof grantCid === "string" && DELEGATION_CID.test(grantCid) ? { grantCid } : {} }];
213
+ });
214
+ }
187
215
  function isInteractive() {
188
216
  return Boolean(process.stdout.isTTY);
189
217
  }
190
218
  async function withSpinner(label, fn) {
191
- if (!isInteractive()) {
219
+ if (shouldOutputJson()) {
192
220
  return fn();
193
221
  }
194
222
  const spinner = ora(label).start();
@@ -202,7 +230,7 @@ async function withSpinner(label, fn) {
202
230
  }
203
231
  }
204
232
  function shouldOutputJson() {
205
- return !isInteractive() || process.argv.includes("--json");
233
+ return !isInteractive() || (globalThis[JSON_REQUESTED] ?? process.argv.includes("--json"));
206
234
  }
207
235
  function formatField(label, value) {
208
236
  if (value === null || value === void 0) return ` ${theme.label(label + ":")} ${theme.muted("\u2014")}`;
@@ -251,10 +279,95 @@ function formatTimeAgo(date) {
251
279
  if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`;
252
280
  return `${Math.floor(seconds / 86400)}d ago`;
253
281
  }
282
+ var WARNING_CODE, WARNING_REASON, DELEGATION_CID, JSON_REQUESTED;
254
283
  var init_formatter = __esm({
255
284
  "src/output/formatter.ts"() {
256
285
  "use strict";
257
286
  init_theme();
287
+ WARNING_CODE = /^[A-Z][A-Z_]{0,63}$/;
288
+ WARNING_REASON = /^[a-z][a-z_]{0,63}$/;
289
+ DELEGATION_CID = /^bafkr4i[a-z2-7]{52}$/;
290
+ JSON_REQUESTED = /* @__PURE__ */ Symbol.for("tinycloud.cli.jsonOutputRequested");
291
+ }
292
+ });
293
+
294
+ // src/output/storage.ts
295
+ function storageRejection(error) {
296
+ let node = error;
297
+ for (let depth = 0; depth < MAX_CAUSE_DEPTH && typeof node === "object" && node !== null; depth += 1) {
298
+ const record = node;
299
+ 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) {
303
+ return void 0;
304
+ } else if (isStorageStatus(record)) {
305
+ code3 = storageCodeFromText(record.message);
306
+ }
307
+ if (code3) {
308
+ const account = accountTotals(record) ?? accountTotals(record.meta) ?? accountTotals(record.metadata);
309
+ return account ? { code: code3, account } : { code: code3 };
310
+ }
311
+ node = record.cause;
312
+ }
313
+ return void 0;
314
+ }
315
+ function describeStorageRejection(rejection, progress) {
316
+ const reason = rejection.code === "STORAGE_LIMIT_REACHED" ? "This write is larger than the TinyCloud storage you have left" : "TinyCloud storage is full";
317
+ const message = progress ? `${progress}${reason}.` : `${reason}; nothing was written.`;
318
+ const { account } = rejection;
319
+ const totals = account ? `${formatStorageBytes(account.usedBytes)} used of ${formatStorageBytes(account.limitBytes)}${account.plan ? ` (${account.plan} plan)` : ""}. ` : "";
320
+ return {
321
+ message,
322
+ hint: `${totals}Reading still works.
323
+ Free up space or upgrade: ${MANAGE_STORAGE_URL}`
324
+ };
325
+ }
326
+ function formatStorageBytes(bytes) {
327
+ const units = ["KiB", "MiB", "GiB", "TiB"];
328
+ if (bytes < 1024) return `${bytes} B`;
329
+ let value = bytes / 1024;
330
+ let unit = 0;
331
+ while (value >= 1024 && unit < units.length - 1) {
332
+ value /= 1024;
333
+ unit += 1;
334
+ }
335
+ return `${value.toFixed(1).replace(/\.0$/, "")} ${units[unit]}`;
336
+ }
337
+ function storageCodeFromText(message) {
338
+ if (typeof message !== "string") return void 0;
339
+ if (message.includes("larger than the TinyCloud storage you have left")) return "STORAGE_LIMIT_REACHED";
340
+ if (message.includes("TinyCloud storage is full") || /storage quota exceeded/i.test(message)) {
341
+ return "STORAGE_QUOTA_EXCEEDED";
342
+ }
343
+ return void 0;
344
+ }
345
+ function isStorageStatus(record) {
346
+ const meta = "meta" in record && typeof record.meta === "object" && record.meta !== null ? record.meta : {};
347
+ const statuses = [
348
+ "status" in record ? record.status : void 0,
349
+ "statusCode" in record ? record.statusCode : void 0,
350
+ "status" in meta ? meta.status : void 0
351
+ ];
352
+ return statuses.some((value) => value === 402 || value === 413);
353
+ }
354
+ function accountTotals(meta) {
355
+ if (typeof meta !== "object" || meta === null) return void 0;
356
+ const account = meta.account;
357
+ if (typeof account !== "object" || account === null) return void 0;
358
+ const { usedBytes, limitBytes, plan } = account;
359
+ if (!isByteCount(usedBytes) || !isByteCount(limitBytes) || limitBytes === 0) return void 0;
360
+ return typeof plan === "string" && /^[A-Za-z0-9][A-Za-z0-9 _.-]{0,31}$/.test(plan) ? { usedBytes, limitBytes, plan } : { usedBytes, limitBytes };
361
+ }
362
+ function isByteCount(value) {
363
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
364
+ }
365
+ var MANAGE_STORAGE_URL, MAX_CAUSE_DEPTH;
366
+ var init_storage2 = __esm({
367
+ "src/output/storage.ts"() {
368
+ "use strict";
369
+ MANAGE_STORAGE_URL = "https://account.tinycloud.xyz/billing";
370
+ MAX_CAUSE_DEPTH = 4;
258
371
  }
259
372
  });
260
373
 
@@ -268,6 +381,8 @@ function setActiveProfileName(name) {
268
381
  }
269
382
  function cliErrorFromService(error, message = error.message) {
270
383
  if (error instanceof CLIError) return error;
384
+ const storageFull = storageFullError(error);
385
+ if (storageFull) return storageFull;
271
386
  const meta = { ...error.meta };
272
387
  delete meta.hint;
273
388
  const status = error.status ?? error.statusCode;
@@ -277,15 +392,18 @@ function cliErrorFromService(error, message = error.message) {
277
392
  return missingPrivateKeyError();
278
393
  }
279
394
  const missingCapability = validatedCapabilityOf({ ...error, meta }) !== void 0;
280
- const denied = verdict === "forbidden" || verdict === "unauthenticated" && missingCapability;
395
+ const denied = verdict === "forbidden" || verdict === "unauthenticated" && missingCapability || verdict === void 0 && error.code === "PERMISSION_DENIED";
396
+ const unauthenticated = !denied && (verdict === "unauthenticated" || verdict === void 0 && error.code === "AUTH_REQUIRED");
281
397
  return new CLIError(
282
- denied ? "PERMISSION_DENIED" : verdict === "unauthenticated" ? "AUTH_REQUIRED" : error.code,
398
+ denied ? "PERMISSION_DENIED" : unauthenticated ? "AUTH_REQUIRED" : error.code,
283
399
  message,
284
- denied ? ExitCode.PERMISSION_DENIED : verdict === "unauthenticated" ? ExitCode.AUTH_REQUIRED : ExitCode.ERROR,
400
+ denied ? ExitCode.PERMISSION_DENIED : unauthenticated ? ExitCode.AUTH_REQUIRED : ExitCode.ERROR,
285
401
  meta
286
402
  );
287
403
  }
288
404
  function wrapError(error) {
405
+ const storageFull = storageFullError(error);
406
+ if (storageFull) return storageFull;
289
407
  if (error instanceof CLIError) return error;
290
408
  const message = error instanceof Error ? error.message : String(error);
291
409
  const verdict = authorizationVerdictOf(error);
@@ -332,6 +450,13 @@ function missingPrivateKeyError() {
332
450
  { hint: `Sign in again with: tc --profile ${profileName} auth login --method openkey` }
333
451
  );
334
452
  }
453
+ function storageFullError(error, progress) {
454
+ if (error instanceof CLIError && error.exitCode === ExitCode.STORAGE_FULL) return error;
455
+ const rejection = storageRejection(error);
456
+ if (!rejection) return void 0;
457
+ const { message, hint } = describeStorageRejection(rejection, progress);
458
+ return new CLIError(rejection.code, message, ExitCode.STORAGE_FULL, { hint });
459
+ }
335
460
  function handleError(error) {
336
461
  const cliError = wrapError(error);
337
462
  const prebuilt = typeof cliError.metadata?.hint === "string" ? cliError.metadata.hint : void 0;
@@ -344,7 +469,10 @@ function handleError(error) {
344
469
  meta.resource = capability.resource;
345
470
  meta.requiredAction = capability.requiredAction;
346
471
  }
347
- outputError(cliError.code, cliError.message, hint, Object.keys(meta).length ? meta : void 0);
472
+ outputError(cliError.code, cliError.message, hint, {
473
+ ...Object.keys(meta).length ? { meta } : {},
474
+ warnings: operationWarnings(cliError.metadata?.warnings)
475
+ });
348
476
  process.exit(cliError.exitCode);
349
477
  }
350
478
  function buildAuthHint(error) {
@@ -414,6 +542,7 @@ var init_errors = __esm({
414
542
  "use strict";
415
543
  init_constants();
416
544
  init_formatter();
545
+ init_storage2();
417
546
  CLIError = class extends Error {
418
547
  constructor(code3, message, exitCode = ExitCode.ERROR, metadata) {
419
548
  super(message);
@@ -7055,7 +7184,7 @@ function validatedCapabilityOf2(error) {
7055
7184
  if (verdict !== "unauthenticated" && verdict !== "forbidden") return void 0;
7056
7185
  const seen = /* @__PURE__ */ new Set();
7057
7186
  let node = error;
7058
- for (let depth = 0; depth < MAX_CAUSE_DEPTH && typeof node === "object" && node !== null && !seen.has(node); depth += 1) {
7187
+ for (let depth = 0; depth < MAX_CAUSE_DEPTH2 && typeof node === "object" && node !== null && !seen.has(node); depth += 1) {
7059
7188
  seen.add(node);
7060
7189
  const record = node;
7061
7190
  const status = errorStatusOf(record);
@@ -7075,7 +7204,7 @@ function validatedCapabilityOf2(error) {
7075
7204
  }
7076
7205
  return void 0;
7077
7206
  }
7078
- var MAX_CAUSE_DEPTH = 8;
7207
+ var MAX_CAUSE_DEPTH2 = 8;
7079
7208
  function errorStatusOf(node) {
7080
7209
  const meta = node.meta;
7081
7210
  const candidates = [
@@ -7094,7 +7223,7 @@ function authorizationVerdictOf2(error) {
7094
7223
  const seen = /* @__PURE__ */ new Set();
7095
7224
  let sawUnauthorizedCode = false;
7096
7225
  let node = error;
7097
- for (let depth = 0; depth < MAX_CAUSE_DEPTH && typeof node === "object" && node !== null && !seen.has(node); depth += 1) {
7226
+ for (let depth = 0; depth < MAX_CAUSE_DEPTH2 && typeof node === "object" && node !== null && !seen.has(node); depth += 1) {
7098
7227
  seen.add(node);
7099
7228
  const record = node;
7100
7229
  const status = errorStatusOf(record);
@@ -13377,6 +13506,10 @@ function canonicalOwnerDid(did, label = "--owner") {
13377
13506
  }
13378
13507
  return `did:pkh:eip155:${match[1]}:${ensureEip55(match[2])}`;
13379
13508
  }
13509
+ function canonicalNetworkUrn(path) {
13510
+ const match = /^urn:tinycloud:encryption:(did:pkh:eip155:[1-9]\d*:0x[0-9a-fA-F]{40}):([^:]*)$/.exec(path);
13511
+ return match ? `urn:tinycloud:encryption:${canonicalOwnerDid(match[1])}:${match[2]}` : path;
13512
+ }
13380
13513
  function rawEncryptionOwnerMatches(path, ownerDid) {
13381
13514
  const match = /^urn:tinycloud:encryption:(did:pkh:eip155:.+):([a-z0-9][a-z0-9-]*)$/.exec(path);
13382
13515
  if (!match) return false;
@@ -13519,13 +13652,14 @@ async function replayAdditionalDelegations(node, profile, options) {
13519
13652
  for (const stored of entries) {
13520
13653
  const kind = storedDelegationKind(stored);
13521
13654
  if (kind === "compact" || kind === "signed-login") {
13522
- const installed = await replayStoredDelegation(activator, stored, {
13655
+ const replay = await replayStoredDelegation(activator, stored, {
13523
13656
  host: options.host,
13524
13657
  migrated,
13525
13658
  resolveSpace
13526
13659
  });
13527
- if (installed === void 0 && process.env.TC_DEBUG_REPLAY === "1") {
13528
- process.stderr.write("[replay] skipping a stored delegation refused by validation or its request binding\n");
13660
+ if (replay.status === "skipped" && process.env.TC_DEBUG_REPLAY === "1") {
13661
+ process.stderr.write(`[replay] skipping a stored delegation: ${replay.reason}
13662
+ `);
13529
13663
  }
13530
13664
  continue;
13531
13665
  }
@@ -13555,6 +13689,14 @@ async function replayAdditionalDelegations(node, profile, options) {
13555
13689
  function storedAdditionalDelegation(delegation, permissions) {
13556
13690
  return { delegation, permissions };
13557
13691
  }
13692
+ async function cliGrantRecord(node, delegation, permissions, host) {
13693
+ const { bindCliGrant } = await import("@tinycloud/operations/delegation-binding");
13694
+ return bindCliGrant(
13695
+ node,
13696
+ storedAdditionalDelegation(delegation, permissions),
13697
+ host
13698
+ );
13699
+ }
13558
13700
  async function appendGrantHistory(profile, entry) {
13559
13701
  await ProfileManager.ensureProfileDir(profile);
13560
13702
  const line = JSON.stringify({
@@ -13709,7 +13851,8 @@ function permissionsFromDelegation(delegation) {
13709
13851
  }
13710
13852
  function compactPermission(permission) {
13711
13853
  const service = permission.service;
13712
- const space = permission.space.startsWith("tinycloud:") ? permission.space.slice(permission.space.lastIndexOf(":") + 1) : permission.space;
13854
+ const fullSpace = permission.space ?? (isRawEncryptionPermission(permission) ? ENCRYPTION_MANIFEST_SPACE2 : "");
13855
+ const space = fullSpace.startsWith("tinycloud:") ? fullSpace.slice(fullSpace.lastIndexOf(":") + 1) : fullSpace;
13713
13856
  const actions = permission.actions.map((action) => action.startsWith(`${service}/`) ? action.slice(service.length + 1) : action).join(",");
13714
13857
  return `${service}:${space}:${permission.path}:${actions}`;
13715
13858
  }
@@ -13805,6 +13948,35 @@ function serviceFromActions(actions) {
13805
13948
  return first.includes("/") ? first.slice(0, first.indexOf("/")) : "tinycloud.unknown";
13806
13949
  }
13807
13950
 
13951
+ // src/auth/session-expired.ts
13952
+ init_constants();
13953
+ init_errors();
13954
+ var SESSION_EXPIRED_REASON = "session_expired";
13955
+ function signInAgainHint(profileName, posture) {
13956
+ if (posture === "local-owner-key") {
13957
+ return `Sign in again with: tc --profile ${profileName} auth login --method local`;
13958
+ }
13959
+ if (posture === "delegate-session") {
13960
+ return `Have the owner approve a new scoped login: tc --profile ${profileName} auth login --method openkey --paste --manifest <manifest.json>. Pass the owner's code on stdin, newline-terminated.`;
13961
+ }
13962
+ return `Sign in again with: tc --profile ${profileName} auth login --method openkey`;
13963
+ }
13964
+ function sessionExpiredError(profileName, posture) {
13965
+ return new CLIError(
13966
+ "AUTH_REQUIRED",
13967
+ `The session for profile "${profileName}" has expired or is no longer valid.`,
13968
+ ExitCode.AUTH_REQUIRED,
13969
+ { hint: signInAgainHint(profileName, posture), reason: SESSION_EXPIRED_REASON }
13970
+ );
13971
+ }
13972
+ function withSignInHint(error, profileName, posture) {
13973
+ if (error.code !== "AUTH_REQUIRED") return error;
13974
+ return new CLIError(error.code, error.message, error.exitCode, {
13975
+ ...error.metadata,
13976
+ hint: signInAgainHint(profileName, posture)
13977
+ });
13978
+ }
13979
+
13808
13980
  // src/lib/sdk.ts
13809
13981
  function jwkHasPrivateParameter(jwk) {
13810
13982
  if (!jwk || typeof jwk !== "object") return false;
@@ -13831,6 +14003,16 @@ function signerJwkForProfile(profileName, sessionJwk, key) {
13831
14003
  }
13832
14004
  );
13833
14005
  }
14006
+ async function restoreProfileSession(node, profileName, profile, sessionData) {
14007
+ try {
14008
+ await node.restoreSession(sessionData);
14009
+ } catch (error) {
14010
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "AUTH_EXPIRED") {
14011
+ throw sessionExpiredError(profileName, profile === null ? void 0 : resolveProfilePosture(profile));
14012
+ }
14013
+ throw error;
14014
+ }
14015
+ }
13834
14016
  async function createSDKInstance(ctx, options) {
13835
14017
  const profile = options?.privateKey ? await ProfileManager.getProfile(ctx.profile).catch(() => null) : await ProfileManager.getProfile(ctx.profile);
13836
14018
  const session = await ProfileManager.getSession(ctx.profile);
@@ -13850,7 +14032,7 @@ async function createSDKInstance(ctx, options) {
13850
14032
  });
13851
14033
  let restoredOwnSession2 = false;
13852
14034
  if (session && session.delegationHeader && session.delegationCid && session.spaceId) {
13853
- await node2.restoreSession({
14035
+ await restoreProfileSession(node2, ctx.profile, profile, {
13854
14036
  delegationHeader: session.delegationHeader,
13855
14037
  delegationCid: session.delegationCid,
13856
14038
  spaceId: session.spaceId,
@@ -13880,7 +14062,7 @@ async function createSDKInstance(ctx, options) {
13880
14062
  if (options?.privateKey) {
13881
14063
  await node.signIn();
13882
14064
  } else if (session && session.delegationHeader && session.delegationCid && session.spaceId) {
13883
- await node.restoreSession({
14065
+ await restoreProfileSession(node, ctx.profile, profile, {
13884
14066
  delegationHeader: session.delegationHeader,
13885
14067
  delegationCid: session.delegationCid,
13886
14068
  spaceId: session.spaceId,
@@ -14462,6 +14644,41 @@ function validateDelegationCallbackPayload(value) {
14462
14644
  }
14463
14645
  return null;
14464
14646
  }
14647
+ async function delegationFromInput(input, did, options) {
14648
+ const trimmed = input.trim();
14649
+ let parsed;
14650
+ if (/^[a-z2-7]{4}-[a-z2-7]{4}$/i.test(trimmed)) {
14651
+ if (!options.jwk) throw new Error("A CLI session key is required to retrieve a delegation.");
14652
+ const apiHost = process.env.TC_OPENKEY_API_HOST ?? options.openkeyApiHost ?? (options.openkeyHost && options.openkeyHost !== DEFAULT_OPENKEY_HOST ? options.openkeyHost : DEFAULT_OPENKEY_DEVICE_API_HOST);
14653
+ const response = await fetch(`${apiHost.replace(/\/$/, "")}/api/delegation-codes/${trimmed.toLowerCase()}`, {
14654
+ redirect: "error"
14655
+ });
14656
+ if (!response.ok) {
14657
+ throw new Error(response.status === 404 ? "Delegation code not found or expired. Use the full code shown in OpenKey instead." : `Could not retrieve delegation code (HTTP ${response.status}).`);
14658
+ }
14659
+ const result = await response.json();
14660
+ parsed = result.delegation;
14661
+ const delegation = parsed;
14662
+ const jwk = delegation?.jwk;
14663
+ const localJwk = publicJwkForDelegation(options.jwk);
14664
+ if (typeof delegation?.verificationMethod !== "string" || delegation.verificationMethod.split("#")[0] !== did.split("#")[0] || !jwk || typeof jwk !== "object" || Array.isArray(jwk) || jwk.kty !== "OKP" || jwk.crv !== "Ed25519" || typeof jwk.x !== "string" || jwk.x !== localJwk.x || [...PRIVATE_JWK_FIELDS].some((field) => field in jwk)) {
14665
+ throw new Error("Delegation is bound to a different CLI key.");
14666
+ }
14667
+ } else {
14668
+ try {
14669
+ parsed = JSON.parse(trimmed);
14670
+ } catch {
14671
+ try {
14672
+ parsed = JSON.parse(Buffer.from(trimmed, "base64").toString("utf-8"));
14673
+ } catch {
14674
+ throw new Error("Invalid delegation code. Expected JSON, base64-encoded JSON, or XXXX-XXXX.");
14675
+ }
14676
+ }
14677
+ }
14678
+ const invalid = validateDelegationCallbackPayload(parsed);
14679
+ if (invalid) throw new Error(`Invalid delegation code: ${invalid}`);
14680
+ return parsed;
14681
+ }
14465
14682
  function shouldOpenBrowser(options) {
14466
14683
  if (options.noPopup) return false;
14467
14684
  const env = process.env.TC_AUTH_NO_POPUP ?? process.env.TC_NO_POPUP;
@@ -14486,21 +14703,6 @@ async function callbackFlow(did, options = {}) {
14486
14703
  reject(result.error);
14487
14704
  }
14488
14705
  }
14489
- function parsePasteInput(input) {
14490
- const trimmed = input.trim();
14491
- let parsed;
14492
- try {
14493
- parsed = JSON.parse(trimmed);
14494
- } catch {
14495
- const decoded = Buffer.from(trimmed, "base64").toString("utf-8");
14496
- parsed = JSON.parse(decoded);
14497
- }
14498
- const invalid = validateDelegationCallbackPayload(parsed);
14499
- if (invalid) {
14500
- throw new Error(`Invalid delegation code: ${invalid}`);
14501
- }
14502
- return parsed;
14503
- }
14504
14706
  const server = createServer((req, res) => {
14505
14707
  if (req.method === "POST" && req.url === "/callback") {
14506
14708
  let body = "";
@@ -14569,18 +14771,21 @@ async function callbackFlow(did, options = {}) {
14569
14771
  }
14570
14772
  if (process.stdin.isTTY) {
14571
14773
  console.error(`
14572
- If the browser can't connect back, paste the delegation code here:`);
14774
+ If the browser can't connect back, enter the short code or paste the full delegation code here:`);
14573
14775
  rl = createInterface({
14574
14776
  input: process.stdin,
14575
14777
  output: process.stderr
14576
14778
  });
14577
- rl.on("line", (input) => {
14578
- if (settled) return;
14779
+ let resolving = false;
14780
+ rl.on("line", async (input) => {
14781
+ if (settled || resolving) return;
14782
+ resolving = true;
14579
14783
  try {
14580
- const data = parsePasteInput(input);
14581
- settle({ data });
14582
- } catch {
14583
- console.error("Invalid delegation code. Expected JSON or base64-encoded JSON. Try again:");
14784
+ settle({ data: await delegationFromInput(input, did, options) });
14785
+ } catch (error) {
14786
+ console.error(error instanceof Error ? error.message : "Invalid delegation code. Try again:");
14787
+ } finally {
14788
+ resolving = false;
14584
14789
  }
14585
14790
  });
14586
14791
  }
@@ -14607,24 +14812,7 @@ Open this URL in a browser to authenticate:
14607
14812
  if (answered || input.trim() === "") return;
14608
14813
  answered = true;
14609
14814
  rl.close();
14610
- let parsed;
14611
- try {
14612
- parsed = JSON.parse(input.trim());
14613
- } catch {
14614
- try {
14615
- const decoded = Buffer.from(input.trim(), "base64").toString("utf-8");
14616
- parsed = JSON.parse(decoded);
14617
- } catch {
14618
- reject(new Error("Invalid delegation code. Expected JSON or base64-encoded JSON."));
14619
- return;
14620
- }
14621
- }
14622
- const invalid = validateDelegationCallbackPayload(parsed);
14623
- if (invalid) {
14624
- reject(new Error(`Invalid delegation code: ${invalid}`));
14625
- return;
14626
- }
14627
- resolve4(parsed);
14815
+ void delegationFromInput(input, did, options).then(resolve4, reject);
14628
14816
  });
14629
14817
  rl.on("close", () => {
14630
14818
  if (answered) return;
@@ -14639,7 +14827,7 @@ Open this URL in a browser to authenticate:
14639
14827
  }
14640
14828
  ));
14641
14829
  });
14642
- rl.setPrompt("Paste delegation code: ");
14830
+ rl.setPrompt("Enter short code or paste full delegation code: ");
14643
14831
  rl.prompt();
14644
14832
  });
14645
14833
  }
@@ -14923,7 +15111,13 @@ function validateLoginPermissions(permissions) {
14923
15111
  }
14924
15112
  }
14925
15113
  function scopedLoginPermissions(permissions) {
14926
- const space = permissions.find((p) => !isRawEncryptionPermission(p)).space ?? "";
15114
+ return withCapabilitiesRead(permissions, permissions.find((p) => !isRawEncryptionPermission(p)).space ?? "");
15115
+ }
15116
+ function grantRequestPermissions(group, anchorSpace) {
15117
+ const request = group.map((p) => isRawEncryptionPermission(p) ? { ...p, space: ENCRYPTION_MANIFEST_SPACE2, path: canonicalNetworkUrn(p.path) } : p);
15118
+ return withCapabilitiesRead(request, request.find((p) => !isRawEncryptionPermission(p))?.space ?? anchorSpace);
15119
+ }
15120
+ function withCapabilitiesRead(permissions, space) {
14927
15121
  const hasRead = permissions.some((p) => p.service === "tinycloud.capabilities" && p.path === "" && p.actions.includes(CAPABILITIES_READ) && normalizePkhIdentifier(p.space ?? "") === normalizePkhIdentifier(space));
14928
15122
  return hasRead ? permissions : [{ service: "tinycloud.capabilities", space, path: "", actions: [CAPABILITIES_READ] }, ...permissions];
14929
15123
  }
@@ -15810,6 +16004,7 @@ function registerAuthCommand(program) {
15810
16004
  "Grant requested TinyCloud permissions from `tc auth request --grant`.",
15811
16005
  group
15812
16006
  );
16007
+ const request = grantRequestPermissions(group, profile.spaceId ?? profile.spaceName);
15813
16008
  let delegationData;
15814
16009
  if (options.device) {
15815
16010
  const approval = await acquireDeviceDelegation({
@@ -15817,7 +16012,7 @@ function registerAuthCommand(program) {
15817
16012
  jwk: key,
15818
16013
  nodeOrigin: ctx.host,
15819
16014
  shareOrigin: DEFAULT_SHARE_ORIGIN,
15820
- permissions: group,
16015
+ permissions: request,
15821
16016
  expiry: parseRequestedExpiry(expiryOption ?? "7d"),
15822
16017
  reason,
15823
16018
  expectedOwner: pinnedOwner(profile),
@@ -15829,17 +16024,17 @@ function registerAuthCommand(program) {
15829
16024
  delegationData = await startAuthFlow(profile.did, {
15830
16025
  jwk: key,
15831
16026
  host: ctx.host,
15832
- permissions: group,
16027
+ permissions: request,
15833
16028
  reason,
15834
16029
  openkeyHost,
15835
16030
  expiry: expiryCap === void 0 ? void 0 : openKeyExpiryParam(expiryCap),
15836
16031
  noPopup: options.popup === false
15837
16032
  });
15838
16033
  }
15839
- const delegation = portableFromOpenKeyDelegation(delegationData, group, ctx.host, proof);
16034
+ const delegation = portableFromOpenKeyDelegation(delegationData, request, ctx.host, proof);
15840
16035
  grants.push({ delegation, effective: permissionsFromDelegation(delegation) });
15841
16036
  }
15842
- await activateAndStoreOpenKeyGrants(ctx.profile, node, grants, options.manifest ? "manifest" : "cli");
16037
+ await activateAndStoreOpenKeyGrants(ctx.profile, ctx.host, node, grants, options.manifest ? "manifest" : "cli");
15843
16038
  const delegationCids2 = grants.map(({ delegation }) => delegation.cid);
15844
16039
  const expiry2 = grants.at(-1)?.delegation.expiry.toISOString();
15845
16040
  reportDeclined(declined);
@@ -15875,8 +16070,7 @@ function registerAuthCommand(program) {
15875
16070
  for (const delegation of delegations) {
15876
16071
  const covering = permissionsFromDelegation(delegation);
15877
16072
  localEffective.push(...covering);
15878
- const stored = storedAdditionalDelegation(delegation, covering);
15879
- await appendAdditionalDelegation(ctx.profile, stored);
16073
+ await appendAdditionalDelegation(ctx.profile, await cliGrantRecord(node, delegation, covering, ctx.host));
15880
16074
  delegationCids.push(delegation.cid);
15881
16075
  expiry = delegation.expiry.toISOString();
15882
16076
  await appendGrantHistory(ctx.profile, {
@@ -16298,6 +16492,8 @@ async function importRequestBoundDelegation(ctx, artifact) {
16298
16492
  { profile: ctx.profile, host: ctx.host, allowOwnerProfile: true },
16299
16493
  artifact
16300
16494
  );
16495
+ const warnings = operationWarnings(result.warnings);
16496
+ const metadata = warnings.length === 0 ? void 0 : { warnings };
16301
16497
  switch (result.status) {
16302
16498
  case "ok": {
16303
16499
  const output2 = result.output;
@@ -16310,22 +16506,25 @@ async function importRequestBoundDelegation(ctx, artifact) {
16310
16506
  permissions: output2.effectivePermissions,
16311
16507
  expiry: output2.expiry
16312
16508
  });
16509
+ outputWarnings(warnings);
16313
16510
  return;
16314
16511
  }
16315
16512
  case "authority_required":
16316
16513
  throw new CLIError(
16317
16514
  "AUTHORITY_REQUIRED",
16318
16515
  "The active session requires additional authority before importing this delegation.",
16319
- ExitCode.PERMISSION_DENIED
16516
+ ExitCode.PERMISSION_DENIED,
16517
+ metadata
16320
16518
  );
16321
16519
  case "setup_required":
16322
16520
  throw new CLIError(
16323
16521
  "SETUP_REQUIRED",
16324
16522
  "The active profile requires setup before importing this delegation.",
16325
- ExitCode.ERROR
16523
+ ExitCode.ERROR,
16524
+ metadata
16326
16525
  );
16327
16526
  case "error":
16328
- throw cliErrorFromService(result.error);
16527
+ throw withSignInHint(cliErrorFromService({ ...result.error, meta: metadata }), ctx.profile, result.context.posture);
16329
16528
  }
16330
16529
  }
16331
16530
  function isStoredDelegationLike(value) {
@@ -16350,9 +16549,10 @@ function normalizePortableDelegation(delegation) {
16350
16549
  }
16351
16550
  return { ...delegation, expiry };
16352
16551
  }
16353
- async function activateAndStoreOpenKeyGrants(profileName, node, grants, source) {
16354
- for (const { delegation } of grants) await node.useRuntimeDelegation(delegation);
16552
+ async function activateAndStoreOpenKeyGrants(profileName, host, node, grants, source) {
16355
16553
  if (grants.length === 0) return;
16554
+ const records = await Promise.all(grants.map(({ delegation, effective }) => cliGrantRecord(node, delegation, effective, host)));
16555
+ for (const { delegation } of grants) await node.useRuntimeDelegation(delegation);
16356
16556
  await ProfileManager.withLock(profileName, async () => {
16357
16557
  for (const { delegation, effective } of grants) {
16358
16558
  await appendGrantHistory(profileName, {
@@ -16362,10 +16562,7 @@ async function activateAndStoreOpenKeyGrants(profileName, node, grants, source)
16362
16562
  expiry: delegation.expiry.toISOString()
16363
16563
  });
16364
16564
  }
16365
- await appendAdditionalDelegations(
16366
- profileName,
16367
- grants.map(({ delegation, effective }) => storedAdditionalDelegation(delegation, effective))
16368
- );
16565
+ await appendAdditionalDelegations(profileName, records);
16369
16566
  });
16370
16567
  }
16371
16568
  async function ensureDelegationAuthority(params) {
@@ -16388,20 +16585,22 @@ async function ensureDelegationAuthority(params) {
16388
16585
  expectedOwner: pinnedOwner(params.profile),
16389
16586
  expiry: expiryCap
16390
16587
  };
16588
+ const anchorSpace = params.anchorSpace ?? params.profile.spaceId ?? params.profile.spaceName;
16391
16589
  const grants = [];
16392
16590
  for (const group of groupPermissionsBySpace(params.requested)) {
16591
+ const request = grantRequestPermissions(group, anchorSpace);
16393
16592
  const delegationData = await acquireOpenKey(params.profile.did, {
16394
16593
  jwk: key,
16395
16594
  host: params.ctx.host,
16396
- permissions: group,
16595
+ permissions: request,
16397
16596
  reason: permissionGrantReason(params.reason, group),
16398
16597
  openkeyHost,
16399
16598
  expiry: expiryCap === void 0 ? void 0 : openKeyExpiryParam(expiryCap)
16400
16599
  });
16401
- const delegation = portableFromOpenKeyDelegation(delegationData, group, params.ctx.host, proof);
16600
+ const delegation = portableFromOpenKeyDelegation(delegationData, request, params.ctx.host, proof);
16402
16601
  grants.push({ delegation, effective: permissionsFromDelegation(delegation) });
16403
16602
  }
16404
- await activateAndStoreOpenKeyGrants(params.ctx.profile, params.node, grants, "cli");
16603
+ await activateAndStoreOpenKeyGrants(params.ctx.profile, params.ctx.host, params.node, grants, "cli");
16405
16604
  return;
16406
16605
  }
16407
16606
  if (isInteractive()) {
@@ -16423,7 +16622,7 @@ async function ensureDelegationAuthority(params) {
16423
16622
  const covering = permissionsFromDelegation(delegation);
16424
16623
  await appendAdditionalDelegation(
16425
16624
  params.ctx.profile,
16426
- storedAdditionalDelegation(delegation, covering)
16625
+ await cliGrantRecord(params.node, delegation, covering, params.ctx.host)
16427
16626
  );
16428
16627
  await appendGrantHistory(params.ctx.profile, {
16429
16628
  addedCaps: covering,
@@ -16555,7 +16754,8 @@ function portableFromOpenKeyDelegation(data, requested, host, proof) {
16555
16754
  }
16556
16755
  const returnedSpace = data.spaceId;
16557
16756
  const effective = session.permissions;
16558
- const primary = effective.find((permission) => !isRawEncryptionPermission(permission)) ?? effective[0];
16757
+ const spaced = effective.filter((permission) => !isRawEncryptionPermission(permission));
16758
+ const primary = spaced.find((permission) => permission.service !== "tinycloud.capabilities") ?? spaced[0] ?? effective[0];
16559
16759
  const resources = effective.map((permission) => ({
16560
16760
  service: permission.service.slice("tinycloud.".length),
16561
16761
  space: isVerifiedRawEncryptionPermission(permission) ? "encryption" : returnedSpace,
@@ -16575,7 +16775,9 @@ function portableFromOpenKeyDelegation(data, requested, host, proof) {
16575
16775
  delegateDID: data.verificationMethod,
16576
16776
  ownerAddress: ownerParts[4],
16577
16777
  chainId: Number(ownerParts[3]),
16578
- host
16778
+ host,
16779
+ // Verified above; replay rebuilds the CACAO from it before trusting the grant.
16780
+ siweProof: { siwe: data.siwe, signature: data.signature }
16579
16781
  };
16580
16782
  }
16581
16783
  async function rotateAuthKey(profileName, host, options = {}) {
@@ -17531,15 +17733,6 @@ async function throwKvError(error, spaceUri, profileName) {
17531
17733
  if (hosted) throw hosted;
17532
17734
  throw cliErrorFromService(error);
17533
17735
  }
17534
- function isByteCount(value) {
17535
- return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
17536
- }
17537
- function storageQuotaError(error) {
17538
- if (error.code !== "STORAGE_QUOTA_EXCEEDED") return void 0;
17539
- const { usedBytes, limitBytes } = typeof error.meta === "object" && error.meta !== null ? error.meta : {};
17540
- const sizes = isByteCount(usedBytes) && isByteCount(limitBytes) ? ` (${formatBytes(usedBytes)} used of ${formatBytes(limitBytes)} limit)` : "";
17541
- return new CLIError("STORAGE_QUOTA_EXCEEDED", `storage quota exceeded${sizes}; nothing was written`, ExitCode.ERROR);
17542
- }
17543
17736
  function assertAddressableKey(key) {
17544
17737
  for (let i = 0; i < key.length; i++) {
17545
17738
  const code3 = key.charCodeAt(i);
@@ -17635,8 +17828,6 @@ function registerKvCommand(program) {
17635
17828
  const { kv: kv2, spaceUri } = await kvHandle(node, options.space, ctx.profile);
17636
17829
  const result = await withSpinner(`Writing ${key}...`, () => kv2.put(key, putValue));
17637
17830
  if (!result.ok) {
17638
- const quota = storageQuotaError(result.error);
17639
- if (quota) throw quota;
17640
17831
  await throwKvError(result.error, spaceUri, ctx.profile);
17641
17832
  }
17642
17833
  outputJson({ key, written: true });
@@ -18311,6 +18502,7 @@ async function runSecretOperation(params) {
18311
18502
  reason: secretPermissionReason(params.action, params.name),
18312
18503
  yes: true,
18313
18504
  force: true,
18505
+ anchorSpace: params.space ?? SECRETS_SPACE3,
18314
18506
  openKeyAcquisition: params.openKeyAcquisition
18315
18507
  })
18316
18508
  );
@@ -18395,7 +18587,26 @@ async function runSecretOperationAttempt(label, operation) {
18395
18587
  throw error;
18396
18588
  }
18397
18589
  }
18590
+ function withOperationWarnings(error, warnings) {
18591
+ if (warnings === void 0 || warnings.length === 0) return error;
18592
+ const cliError = wrapError(error);
18593
+ cliError.metadata = { ...cliError.metadata, warnings };
18594
+ return cliError;
18595
+ }
18398
18596
  async function invokeCanonicalSecretGet(params) {
18597
+ const warnings = /* @__PURE__ */ new Map();
18598
+ const collect = (result) => {
18599
+ for (const warning of result.warnings ?? []) warnings.set(JSON.stringify(warning), warning);
18600
+ return result;
18601
+ };
18602
+ try {
18603
+ const result = await canonicalSecretGet(params, collect);
18604
+ return warnings.size === 0 ? result : { ...result, warnings: [...warnings.values()] };
18605
+ } catch (error) {
18606
+ throw withOperationWarnings(error, [...warnings.values()]);
18607
+ }
18608
+ }
18609
+ async function canonicalSecretGet(params, collect) {
18399
18610
  const auth = authOptions(params.options);
18400
18611
  let ownerNode;
18401
18612
  if (!auth?.privateKey) {
@@ -18418,10 +18629,10 @@ async function invokeCanonicalSecretGet(params) {
18418
18629
  ...params.scope === void 0 ? {} : { scope: params.scope },
18419
18630
  ...params.space === void 0 ? {} : { space: params.space }
18420
18631
  };
18421
- const invoke = () => withSpinner(
18632
+ const invoke = async () => collect(await withSpinner(
18422
18633
  params.label,
18423
18634
  () => auth?.privateKey ? invokeSecretsGetWithLocalAuthorityRetry(target, input) : invokeOperation2("tinycloud.secrets.get", 1, target, input)
18424
- );
18635
+ ));
18425
18636
  let first = await invoke();
18426
18637
  if (first.status === "error" && first.error.code === "SESSION_NOT_FOUND" && auth?.privateKey === void 0) {
18427
18638
  const profile2 = await ProfileManager.getProfile(params.ctx.profile);
@@ -18459,6 +18670,7 @@ async function invokeCanonicalSecretGet(params) {
18459
18670
  reason: secretPermissionReason("get", params.name),
18460
18671
  yes: true,
18461
18672
  force: true,
18673
+ anchorSpace: params.space ?? SECRETS_SPACE3,
18462
18674
  openKeyAcquisition: params.openKeyAcquisition
18463
18675
  })
18464
18676
  );
@@ -18470,7 +18682,7 @@ function throwCanonicalSecretGetError(result, name) {
18470
18682
  throw new CLIError(
18471
18683
  "PERMISSION_DENIED",
18472
18684
  "Permission denied while reading secret",
18473
- ExitCode.ERROR
18685
+ ExitCode.PERMISSION_DENIED
18474
18686
  );
18475
18687
  case "setup_required":
18476
18688
  throw new CLIError(
@@ -18492,11 +18704,10 @@ ${result.setup.message}`, setup: result.setup }
18492
18704
  if (result.error.code === "NODE_UNREACHABLE") {
18493
18705
  throw new CLIError("NETWORK_ERROR", result.error.message, ExitCode.NETWORK_ERROR);
18494
18706
  }
18495
- throw new CLIError(
18496
- result.error.code,
18497
- result.error.message,
18498
- result.error.code === "PERMISSION_HINT_INVALID" ? ExitCode.PERMISSION_DENIED : ExitCode.ERROR
18499
- );
18707
+ if (result.error.code === "PERMISSION_HINT_INVALID") {
18708
+ throw new CLIError(result.error.code, result.error.message, ExitCode.PERMISSION_DENIED);
18709
+ }
18710
+ throw withSignInHint(cliErrorFromService(result.error), result.context.profile, result.context.posture);
18500
18711
  case "ok":
18501
18712
  throw new Error("Expected a failed canonical secret result.");
18502
18713
  }
@@ -19112,19 +19323,26 @@ function registerSecretsCommand(program, openKeyAcquisition) {
19112
19323
  openKeyAcquisition
19113
19324
  });
19114
19325
  if (result.status !== "ok") {
19115
- throwCanonicalSecretGetError(result, name);
19326
+ try {
19327
+ throwCanonicalSecretGetError(result, name);
19328
+ } catch (error) {
19329
+ throw withOperationWarnings(error, result.warnings);
19330
+ }
19116
19331
  }
19117
19332
  const value = result.output.value;
19118
- if (options.output) {
19119
- await writeSecretFile(options.output, value);
19120
- outputJson({ name, written: options.output });
19121
- return;
19122
- }
19123
- if (options.raw || options.valueOnly) {
19124
- process.stdout.write(value);
19125
- return;
19333
+ try {
19334
+ if (options.output) {
19335
+ await writeSecretFile(options.output, value);
19336
+ outputJson({ name, written: options.output });
19337
+ } else if (options.raw || options.valueOnly) {
19338
+ process.stdout.write(value);
19339
+ } else {
19340
+ outputJson({ name, value });
19341
+ }
19342
+ } catch (error) {
19343
+ throw withOperationWarnings(error, result.warnings);
19126
19344
  }
19127
- outputJson({ name, value });
19345
+ outputWarnings(operationWarnings(result.warnings));
19128
19346
  } catch (error) {
19129
19347
  handleError(error);
19130
19348
  }
@@ -29312,7 +29530,6 @@ var MAX_INLINE_BYTES2 = 256 * 1024;
29312
29530
 
29313
29531
  // src/commands/share.ts
29314
29532
  import { ProfileLockTimeoutError as ProfileLockTimeoutError3 } from "@tinycloud/operations/state";
29315
- init_formatter();
29316
29533
  init_errors();
29317
29534
 
29318
29535
  // src/share/output.ts
@@ -29575,9 +29792,8 @@ function shareCliError(error, operation = "publish") {
29575
29792
  if (failure.kind === "registry-rejected") {
29576
29793
  return new CLIError("REGISTRY_REJECTED", "the TinyCloud location registry rejected this session's location record, so nothing was shared; retrying will not help. Log in again, and report the problem if it persists", 6);
29577
29794
  }
29578
- if (failure.kind === "storage-quota-exceeded") {
29579
- const sizes = failure.usedBytes === void 0 || failure.limitBytes === void 0 ? "" : ` (${formatBytes(failure.usedBytes)} used of ${formatBytes(failure.limitBytes)} limit)`;
29580
- return new CLIError("STORAGE_QUOTA_EXCEEDED", `storage quota exceeded${sizes}; nothing was shared`, 4);
29795
+ if (failure.kind === "storage-full") {
29796
+ return storageFullError(failure);
29581
29797
  }
29582
29798
  if (failure.kind === "upload-failed") {
29583
29799
  return new CLIError("UPLOAD_FAILED", "share source upload failed; nothing was shared", 4);
@@ -29597,6 +29813,8 @@ function shareCliError(error, operation = "publish") {
29597
29813
  const code3 = error.code === "fetch-failed" ? "NOT_FOUND" : error.code.replaceAll("-", "_").toUpperCase();
29598
29814
  return new CLIError(code3, error.message, exit);
29599
29815
  }
29816
+ const storageFull = storageFullError(error);
29817
+ if (storageFull) return storageFull;
29600
29818
  const message = error instanceof Error ? error.message : String(error);
29601
29819
  const nodeCode = typeof error === "object" && error !== null && "code" in error ? error.code : void 0;
29602
29820
  const known = {
@@ -30054,6 +30272,8 @@ async function dbHandle(node, dbName, spaceInput, profileName) {
30054
30272
  return { handle: sql.db(dbName), spaceUri };
30055
30273
  }
30056
30274
  async function throwSqlError(error, spaceUri, profileName, prefix) {
30275
+ const storageFull = storageFullError(error, prefix);
30276
+ if (storageFull) throw storageFull;
30057
30277
  const hosted = await unhostedSpaceError(error, spaceUri, profileName);
30058
30278
  if (hosted) throw hosted;
30059
30279
  const message = prefix ? `${prefix}${error.message}` : error.message;