@tinycloud/cli 1.1.0-beta.9 → 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.
package/dist/index.js CHANGED
@@ -32,7 +32,9 @@ var init_constants = __esm({
32
32
  NOT_FOUND: 4,
33
33
  PERMISSION_DENIED: 5,
34
34
  NETWORK_ERROR: 6,
35
- NODE_ERROR: 7
35
+ NODE_ERROR: 7,
36
+ /** A write was refused because the owner's TinyCloud storage is full. */
37
+ STORAGE_FULL: 10
36
38
  };
37
39
  }
38
40
  });
@@ -70,10 +72,55 @@ var init_theme = __esm({
70
72
  }
71
73
  });
72
74
 
75
+ // src/output/warnings.ts
76
+ function warningKey(warning) {
77
+ return JSON.stringify([warning.code, warning.reason, warning.grantCid ?? null]);
78
+ }
79
+ function mergeWarnings(first, second) {
80
+ const merged = [...first];
81
+ const counts = /* @__PURE__ */ new Map();
82
+ for (const warning of first) {
83
+ const key = warningKey(warning);
84
+ counts.set(key, (counts.get(key) ?? 0) + 1);
85
+ }
86
+ const seen = /* @__PURE__ */ new Map();
87
+ for (const warning of second) {
88
+ const key = warningKey(warning);
89
+ const occurrence = (seen.get(key) ?? 0) + 1;
90
+ seen.set(key, occurrence);
91
+ if (occurrence > (counts.get(key) ?? 0)) merged.push(warning);
92
+ }
93
+ return merged;
94
+ }
95
+ function recordWarnings(warnings) {
96
+ if (warnings.length === 0) return;
97
+ const holder = globalThis;
98
+ holder[PENDING_WARNINGS] = mergeWarnings(holder[PENDING_WARNINGS] ?? [], warnings);
99
+ }
100
+ function drainWarnings() {
101
+ const holder = globalThis;
102
+ const warnings = holder[PENDING_WARNINGS] ?? [];
103
+ delete holder[PENDING_WARNINGS];
104
+ return warnings;
105
+ }
106
+ var PENDING_WARNINGS;
107
+ var init_warnings = __esm({
108
+ "src/output/warnings.ts"() {
109
+ "use strict";
110
+ PENDING_WARNINGS = /* @__PURE__ */ Symbol.for("tinycloud.cli.pendingWarnings.v1");
111
+ }
112
+ });
113
+
73
114
  // src/output/formatter.ts
74
115
  import ora from "ora";
75
- function outputError(code3, message, hint, meta) {
76
- if (isInteractive()) {
116
+ function warningLine(warnings) {
117
+ const which = warnings.map((warning) => `${warning.grantCid ?? "unidentified"}: ${warning.reason}`).join(", ");
118
+ const subject = warnings.length === 1 ? "A stored grant was" : `${warnings.length} stored grants were`;
119
+ return `${subject} not used (${which}). Approve access again when a command needs it; remove the old grant with tc auth grants remove <cid>.`;
120
+ }
121
+ function outputError(code3, message, hint, details = {}) {
122
+ const { meta, warnings = [] } = details;
123
+ if (!shouldOutputJson()) {
77
124
  process.stderr.write(
78
125
  `${theme.error("\u2717")} ${theme.label(code3)}: ${message}
79
126
  `
@@ -84,27 +131,135 @@ function outputError(code3, message, hint, meta) {
84
131
  `);
85
132
  }
86
133
  }
134
+ if (warnings.length > 0) process.stderr.write(` ${theme.warn("!")} ${warningLine(warnings)}
135
+ `);
87
136
  } else {
88
137
  const payload = {
89
138
  error: { code: code3, message }
90
139
  };
91
140
  if (hint) payload.error.hint = hint;
92
141
  if (meta) payload.error.meta = meta;
142
+ if (warnings.length > 0) payload.error.warnings = warnings;
93
143
  process.stderr.write(JSON.stringify(payload, null, 2) + "\n");
94
144
  }
95
145
  }
146
+ function outputWarnings(warnings) {
147
+ const combined = mergeWarnings(drainWarnings(), operationWarnings(warnings));
148
+ if (combined.length === 0) return;
149
+ process.stderr.write(shouldOutputJson() ? `${JSON.stringify({ warnings: combined })}
150
+ ` : `${theme.warn("!")} ${warningLine(combined)}
151
+ `);
152
+ }
153
+ function operationWarnings(value) {
154
+ if (!Array.isArray(value)) return [];
155
+ return value.flatMap((warning) => {
156
+ if (warning === null || typeof warning !== "object") return [];
157
+ const { code: code3, reason, grantCid } = warning;
158
+ if (typeof code3 !== "string" || !WARNING_CODE.test(code3) || typeof reason !== "string" || !WARNING_REASON.test(reason)) {
159
+ return [];
160
+ }
161
+ return [{ code: code3, reason, ...typeof grantCid === "string" && DELEGATION_CID.test(grantCid) ? { grantCid } : {} }];
162
+ });
163
+ }
96
164
  function isInteractive() {
97
165
  return Boolean(process.stdout.isTTY);
98
166
  }
99
- function formatBytes(bytes) {
100
- if (bytes < 1024) return `${bytes} B`;
101
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
102
- return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
167
+ function setJsonOutputRequested(requested) {
168
+ globalThis[JSON_REQUESTED] = requested;
169
+ }
170
+ function shouldOutputJson() {
171
+ return !isInteractive() || (globalThis[JSON_REQUESTED] ?? process.argv.includes("--json"));
103
172
  }
173
+ var WARNING_CODE, WARNING_REASON, DELEGATION_CID, JSON_REQUESTED;
104
174
  var init_formatter = __esm({
105
175
  "src/output/formatter.ts"() {
106
176
  "use strict";
107
177
  init_theme();
178
+ init_warnings();
179
+ WARNING_CODE = /^[A-Z][A-Z_]{0,63}$/;
180
+ WARNING_REASON = /^[a-z][a-z_]{0,63}$/;
181
+ DELEGATION_CID = /^bafkr4i[a-z2-7]{52}$/;
182
+ JSON_REQUESTED = /* @__PURE__ */ Symbol.for("tinycloud.cli.jsonOutputRequested");
183
+ }
184
+ });
185
+
186
+ // src/output/storage.ts
187
+ function storageRejection(error) {
188
+ let node = error;
189
+ for (let depth = 0; depth < MAX_CAUSE_DEPTH && typeof node === "object" && node !== null; depth += 1) {
190
+ const record = node;
191
+ let code3;
192
+ if (record.code === "STORAGE_QUOTA_EXCEEDED" || record.code === "STORAGE_LIMIT_REACHED") {
193
+ code3 = record.code;
194
+ } else if (record.code !== void 0) {
195
+ return void 0;
196
+ } else if (isStorageStatus(record)) {
197
+ code3 = storageCodeFromText(record.message);
198
+ }
199
+ if (code3) {
200
+ const account = accountTotals(record) ?? accountTotals(record.meta) ?? accountTotals(record.metadata);
201
+ return account ? { code: code3, account } : { code: code3 };
202
+ }
203
+ node = record.cause;
204
+ }
205
+ return void 0;
206
+ }
207
+ function describeStorageRejection(rejection, progress) {
208
+ const reason = rejection.code === "STORAGE_LIMIT_REACHED" ? "This write is larger than the TinyCloud storage you have left" : "TinyCloud storage is full";
209
+ const message = progress ? `${progress}${reason}.` : `${reason}; nothing was written.`;
210
+ const { account } = rejection;
211
+ const totals = account ? `${formatStorageBytes(account.usedBytes)} used of ${formatStorageBytes(account.limitBytes)}${account.plan ? ` (${account.plan} plan)` : ""}. ` : "";
212
+ return {
213
+ message,
214
+ hint: `${totals}Reading still works.
215
+ Free up space or upgrade: ${MANAGE_STORAGE_URL}`
216
+ };
217
+ }
218
+ function formatStorageBytes(bytes) {
219
+ const units = ["KiB", "MiB", "GiB", "TiB"];
220
+ if (bytes < 1024) return `${bytes} B`;
221
+ let value = bytes / 1024;
222
+ let unit = 0;
223
+ while (value >= 1024 && unit < units.length - 1) {
224
+ value /= 1024;
225
+ unit += 1;
226
+ }
227
+ return `${value.toFixed(1).replace(/\.0$/, "")} ${units[unit]}`;
228
+ }
229
+ function storageCodeFromText(message) {
230
+ if (typeof message !== "string") return void 0;
231
+ if (message.includes("larger than the TinyCloud storage you have left")) return "STORAGE_LIMIT_REACHED";
232
+ if (message.includes("TinyCloud storage is full") || /storage quota exceeded/i.test(message)) {
233
+ return "STORAGE_QUOTA_EXCEEDED";
234
+ }
235
+ return void 0;
236
+ }
237
+ function isStorageStatus(record) {
238
+ const meta = "meta" in record && typeof record.meta === "object" && record.meta !== null ? record.meta : {};
239
+ const statuses = [
240
+ "status" in record ? record.status : void 0,
241
+ "statusCode" in record ? record.statusCode : void 0,
242
+ "status" in meta ? meta.status : void 0
243
+ ];
244
+ return statuses.some((value) => value === 402 || value === 413);
245
+ }
246
+ function accountTotals(meta) {
247
+ if (typeof meta !== "object" || meta === null) return void 0;
248
+ const account = meta.account;
249
+ if (typeof account !== "object" || account === null) return void 0;
250
+ const { usedBytes, limitBytes, plan } = account;
251
+ if (!isByteCount(usedBytes) || !isByteCount(limitBytes) || limitBytes === 0) return void 0;
252
+ return typeof plan === "string" && /^[A-Za-z0-9][A-Za-z0-9 _.-]{0,31}$/.test(plan) ? { usedBytes, limitBytes, plan } : { usedBytes, limitBytes };
253
+ }
254
+ function isByteCount(value) {
255
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
256
+ }
257
+ var MANAGE_STORAGE_URL, MAX_CAUSE_DEPTH;
258
+ var init_storage = __esm({
259
+ "src/output/storage.ts"() {
260
+ "use strict";
261
+ MANAGE_STORAGE_URL = "https://account.tinycloud.xyz/billing";
262
+ MAX_CAUSE_DEPTH = 4;
108
263
  }
109
264
  });
110
265
 
@@ -117,6 +272,8 @@ function setActiveProfileName(name) {
117
272
  activeProfileName = name;
118
273
  }
119
274
  function wrapError(error) {
275
+ const storageFull = storageFullError(error);
276
+ if (storageFull) return storageFull;
120
277
  if (error instanceof CLIError) return error;
121
278
  const message = error instanceof Error ? error.message : String(error);
122
279
  const verdict = authorizationVerdictOf(error);
@@ -163,19 +320,32 @@ function missingPrivateKeyError() {
163
320
  { hint: `Sign in again with: tc --profile ${profileName} auth login --method openkey` }
164
321
  );
165
322
  }
323
+ function storageFullError(error, progress) {
324
+ if (error instanceof CLIError && error.exitCode === ExitCode.STORAGE_FULL) return error;
325
+ const rejection = storageRejection(error);
326
+ if (!rejection) return void 0;
327
+ const { message, hint } = describeStorageRejection(rejection, progress);
328
+ return new CLIError(rejection.code, message, ExitCode.STORAGE_FULL, { hint });
329
+ }
166
330
  function handleError(error) {
167
331
  const cliError = wrapError(error);
168
332
  const prebuilt = typeof cliError.metadata?.hint === "string" ? cliError.metadata.hint : void 0;
169
333
  const hint = prebuilt ?? buildAuthHint(cliError) ?? (cliError.code === "NETWORK_ERROR" ? buildNetworkHint() : void 0);
170
334
  const authMeta = cliError.code === "AUTH_REQUIRED" || cliError.code === "PERMISSION_DENIED" ? cliError.metadata : void 0;
171
335
  const meta = {};
336
+ if (cliError.code === "GRANT_REVOKED_LOCAL_REMOVE_FAILED" && typeof cliError.metadata?.causeCode === "string" && /^[A-Z][A-Z0-9_]{0,63}$/.test(cliError.metadata.causeCode)) {
337
+ meta.causeCode = cliError.metadata.causeCode;
338
+ }
172
339
  if (cliError.status !== void 0 && authMeta !== void 0) meta.status = cliError.status;
173
340
  const capability = validatedCapabilityOf({ meta: authMeta });
174
341
  if (capability) {
175
342
  meta.resource = capability.resource;
176
343
  meta.requiredAction = capability.requiredAction;
177
344
  }
178
- outputError(cliError.code, cliError.message, hint, Object.keys(meta).length ? meta : void 0);
345
+ outputError(cliError.code, cliError.message, hint, {
346
+ ...Object.keys(meta).length ? { meta } : {},
347
+ warnings: mergeWarnings(drainWarnings(), operationWarnings(cliError.metadata?.warnings))
348
+ });
179
349
  process.exit(cliError.exitCode);
180
350
  }
181
351
  function buildAuthHint(error) {
@@ -245,6 +415,8 @@ var init_errors = __esm({
245
415
  "use strict";
246
416
  init_constants();
247
417
  init_formatter();
418
+ init_storage();
419
+ init_warnings();
248
420
  CLIError = class extends Error {
249
421
  constructor(code3, message, exitCode = ExitCode.ERROR, metadata) {
250
422
  super(message);
@@ -312,7 +484,7 @@ async function listDirs(dirPath) {
312
484
  }
313
485
  }
314
486
  var PRIVATE_FILE_MODE, PRIVATE_DIR_MODE;
315
- var init_storage = __esm({
487
+ var init_storage2 = __esm({
316
488
  "src/config/storage.ts"() {
317
489
  "use strict";
318
490
  PRIVATE_FILE_MODE = 384;
@@ -570,7 +742,7 @@ var init_profiles = __esm({
570
742
  "src/config/profiles.ts"() {
571
743
  "use strict";
572
744
  init_constants();
573
- init_storage();
745
+ init_storage2();
574
746
  init_errors();
575
747
  ProfileManager = class _ProfileManager {
576
748
  // ── Initialization ──────────────────────────────────────────────────
@@ -640,7 +812,7 @@ var init_profiles = __esm({
640
812
  if (!profile) {
641
813
  throw new CLIError(
642
814
  "PROFILE_NOT_FOUND",
643
- `Profile "${name}" does not exist. Run \`tc init\` or \`tc profile create ${name}\` first.`
815
+ `Profile "${name}" does not exist. Create it with \`tc init --name ${name}\`.`
644
816
  );
645
817
  }
646
818
  return profile;
@@ -5651,6 +5823,37 @@ var init_zod = __esm({
5651
5823
  }
5652
5824
  });
5653
5825
 
5826
+ // src/auth/session-expired.ts
5827
+ function signInAgainHint(profileName, posture) {
5828
+ if (posture === "local-owner-key") {
5829
+ return `Sign in again with: tc --profile ${profileName} auth login --method local`;
5830
+ }
5831
+ if (posture === "delegate-session") {
5832
+ 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.`;
5833
+ }
5834
+ return `Sign in again with: tc --profile ${profileName} auth login --method openkey`;
5835
+ }
5836
+ function sessionExpiredError(profileName, posture) {
5837
+ return new CLIError(
5838
+ "AUTH_REQUIRED",
5839
+ `The session for profile "${profileName}" has expired or is no longer valid.`,
5840
+ ExitCode.AUTH_REQUIRED,
5841
+ { hint: signInAgainHint(profileName, posture), reason: SESSION_EXPIRED_REASON }
5842
+ );
5843
+ }
5844
+ function isSessionExpiredError(error) {
5845
+ return error instanceof CLIError && error.metadata?.reason === SESSION_EXPIRED_REASON;
5846
+ }
5847
+ var SESSION_EXPIRED_REASON;
5848
+ var init_session_expired = __esm({
5849
+ "src/auth/session-expired.ts"() {
5850
+ "use strict";
5851
+ init_constants();
5852
+ init_errors();
5853
+ SESSION_EXPIRED_REASON = "session_expired";
5854
+ }
5855
+ });
5856
+
5654
5857
  // src/lib/raw-encryption.ts
5655
5858
  var init_raw_encryption = __esm({
5656
5859
  "src/lib/raw-encryption.ts"() {
@@ -5693,64 +5896,20 @@ import {
5693
5896
  withProfileLock as withProfileLock2,
5694
5897
  writeJsonAtomic
5695
5898
  } from "@tinycloud/operations/state";
5696
- async function loadAdditionalDelegations(profile) {
5697
- return readAdditionalDelegations(profile);
5698
- }
5699
5899
  async function replayAdditionalDelegations(node, profile, options) {
5700
- const {
5701
- operationSpaceResolver,
5702
- prepareStoredDelegationReplay,
5703
- replayStoredDelegation,
5704
- storedDelegationKind
5705
- } = await import("@tinycloud/operations/delegation-binding");
5706
- const activator = node;
5707
- const migrated = await prepareStoredDelegationReplay(profile, activator, {
5900
+ const { operationSpaceResolver, replayProfileDelegations } = await import("@tinycloud/operations/delegation-binding");
5901
+ const { warnings } = await replayProfileDelegations(profile, node, {
5708
5902
  host: options.host,
5709
- migrate: options.migrate
5903
+ migrate: options.migrate,
5904
+ resolveSpace: operationSpaceResolver(node, options.ownerSpace)
5710
5905
  });
5711
- const resolveSpace = operationSpaceResolver(node, options.ownerSpace);
5712
- const entries = await loadAdditionalDelegations(profile);
5713
- for (const stored of entries) {
5714
- const kind = storedDelegationKind(stored);
5715
- if (kind === "compact" || kind === "signed-login") {
5716
- const installed = await replayStoredDelegation(activator, stored, {
5717
- host: options.host,
5718
- migrated,
5719
- resolveSpace
5720
- });
5721
- if (installed === void 0 && process.env.TC_DEBUG_REPLAY === "1") {
5722
- process.stderr.write("[replay] skipping a stored delegation refused by validation or its request binding\n");
5723
- }
5724
- continue;
5725
- }
5726
- if (kind === "refused") {
5727
- if (process.env.TC_DEBUG_REPLAY === "1") {
5728
- process.stderr.write("[replay] skipping a malformed stored record or one bound to a request it cannot be held to\n");
5729
- }
5730
- continue;
5731
- }
5732
- const entry = stored;
5733
- const expiry = entry.delegation.expiry instanceof Date ? entry.delegation.expiry : new Date(entry.delegation.expiry);
5734
- if (expiry.getTime() <= Date.now()) continue;
5735
- try {
5736
- await node.useRuntimeDelegation({
5737
- ...entry.delegation,
5738
- delegationHeader: { Authorization: entry.delegation.delegationHeader.Authorization },
5739
- expiry
5740
- });
5741
- } catch (err) {
5742
- if (process.env.TC_DEBUG_REPLAY === "1") {
5743
- process.stderr.write(`[replay] skipping ${entry.delegation.cid}: ${err.message}
5744
- `);
5745
- }
5746
- }
5747
- }
5906
+ return warnings;
5748
5907
  }
5749
5908
  var init_permissions = __esm({
5750
5909
  "src/lib/permissions.ts"() {
5751
5910
  "use strict";
5752
5911
  init_constants();
5753
- init_storage();
5912
+ init_storage2();
5754
5913
  init_profiles();
5755
5914
  init_errors();
5756
5915
  init_constants();
@@ -5769,6 +5928,7 @@ __export(sdk_exports, {
5769
5928
  createSDKInstance: () => createSDKInstance,
5770
5929
  ensureAuthenticated: () => ensureAuthenticated,
5771
5930
  jwkHasPrivateParameter: () => jwkHasPrivateParameter,
5931
+ restoreProfileSessionNode: () => restoreProfileSessionNode,
5772
5932
  selectSignerJwk: () => selectSignerJwk
5773
5933
  });
5774
5934
  import { TinyCloudNode } from "@tinycloud/node-sdk";
@@ -5797,6 +5957,54 @@ function signerJwkForProfile(profileName, sessionJwk, key) {
5797
5957
  }
5798
5958
  );
5799
5959
  }
5960
+ async function restoreProfileSession(node, profileName, profile, sessionData) {
5961
+ try {
5962
+ await node.restoreSession(sessionData);
5963
+ } catch (error) {
5964
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "AUTH_EXPIRED") {
5965
+ throw sessionExpiredError(profileName, profile === null ? void 0 : resolveProfilePosture(profile));
5966
+ }
5967
+ throw error;
5968
+ }
5969
+ }
5970
+ function persistedSessionData(profileName, profile, session, key) {
5971
+ return {
5972
+ delegationHeader: session.delegationHeader,
5973
+ delegationCid: session.delegationCid,
5974
+ spaceId: session.spaceId,
5975
+ jwk: signerJwkForProfile(profileName, session.jwk, key),
5976
+ verificationMethod: session.verificationMethod ?? profile.sessionDid ?? profile.did,
5977
+ address: session.address,
5978
+ chainId: session.chainId,
5979
+ siwe: session.siwe,
5980
+ signature: session.signature
5981
+ };
5982
+ }
5983
+ async function restoreProfileSessionNode(ctx) {
5984
+ const profile = await ProfileManager.getProfile(ctx.profile);
5985
+ const session = await ProfileManager.getSession(ctx.profile);
5986
+ if (!session) return { unavailable: "session_missing" };
5987
+ 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") {
5988
+ return { unavailable: "session_invalid" };
5989
+ }
5990
+ if (typeof session.expiresAt === "string" && Date.parse(session.expiresAt) <= Date.now()) {
5991
+ return { unavailable: "session_expired" };
5992
+ }
5993
+ const key = await ProfileManager.getKey(ctx.profile);
5994
+ try {
5995
+ const node = new TinyCloudNode({
5996
+ host: ctx.host,
5997
+ ...profile.authMethod === "local" && profile.privateKey ? { privateKey: profile.privateKey } : {}
5998
+ });
5999
+ await restoreProfileSession(node, ctx.profile, profile, persistedSessionData(ctx.profile, profile, session, key));
6000
+ return { node };
6001
+ } catch (error) {
6002
+ if (error instanceof CLIError && error.metadata?.reason === "session_expired") {
6003
+ return { unavailable: "session_expired" };
6004
+ }
6005
+ return { unavailable: "session_invalid" };
6006
+ }
6007
+ }
5800
6008
  async function createSDKInstance(ctx, options) {
5801
6009
  const profile = options?.privateKey ? await ProfileManager.getProfile(ctx.profile).catch(() => null) : await ProfileManager.getProfile(ctx.profile);
5802
6010
  const session = await ProfileManager.getSession(ctx.profile);
@@ -5805,7 +6013,7 @@ async function createSDKInstance(ctx, options) {
5805
6013
  if (!key && !effectivePrivateKey && !(profile?.authMethod === "openkey" && session !== null)) {
5806
6014
  throw new CLIError(
5807
6015
  "AUTH_REQUIRED",
5808
- `No key found for profile "${ctx.profile}". Run \`tc init\` first.`,
6016
+ `No key found for profile "${ctx.profile}". Run \`tc --profile ${ctx.profile} auth rotate\` to create a new key and sign in.`,
5809
6017
  ExitCode.AUTH_REQUIRED
5810
6018
  );
5811
6019
  }
@@ -5814,56 +6022,35 @@ async function createSDKInstance(ctx, options) {
5814
6022
  host: ctx.host,
5815
6023
  privateKey: effectivePrivateKey
5816
6024
  });
5817
- let restoredOwnSession2 = false;
5818
- if (session && session.delegationHeader && session.delegationCid && session.spaceId) {
5819
- await node2.restoreSession({
5820
- delegationHeader: session.delegationHeader,
5821
- delegationCid: session.delegationCid,
5822
- spaceId: session.spaceId,
5823
- jwk: signerJwkForProfile(ctx.profile, session.jwk, key),
5824
- verificationMethod: session.verificationMethod ?? profile?.sessionDid ?? profile?.did,
5825
- address: session.address,
5826
- chainId: session.chainId,
5827
- siwe: session.siwe,
5828
- signature: session.signature
5829
- });
5830
- restoredOwnSession2 = true;
6025
+ const restoredOwnSession = Boolean(session && session.delegationHeader && session.delegationCid && session.spaceId);
6026
+ if (restoredOwnSession) {
6027
+ await restoreProfileSession(node2, ctx.profile, profile, persistedSessionData(ctx.profile, profile, session, key));
5831
6028
  } else {
5832
6029
  await node2.signIn();
5833
6030
  }
5834
- await replayAdditionalDelegations(node2, ctx.profile, {
5835
- host: ctx.host,
5836
- ownerSpace: profile.spaceId,
5837
- migrate: restoredOwnSession2 && options?.privateKey === void 0
5838
- });
6031
+ if (restoredOwnSession) {
6032
+ recordWarnings(await replayAdditionalDelegations(node2, ctx.profile, {
6033
+ host: ctx.host,
6034
+ ownerSpace: profile.spaceId,
6035
+ migrate: options?.privateKey === void 0
6036
+ }));
6037
+ }
5839
6038
  return node2;
5840
6039
  }
5841
6040
  const node = new TinyCloudNode({
5842
6041
  host: ctx.host,
5843
6042
  privateKey: options?.privateKey
5844
6043
  });
5845
- let restoredOwnSession = false;
5846
6044
  if (options?.privateKey) {
5847
6045
  await node.signIn();
5848
6046
  } else if (session && session.delegationHeader && session.delegationCid && session.spaceId) {
5849
- await node.restoreSession({
5850
- delegationHeader: session.delegationHeader,
5851
- delegationCid: session.delegationCid,
5852
- spaceId: session.spaceId,
5853
- jwk: signerJwkForProfile(ctx.profile, session.jwk, key),
5854
- verificationMethod: session.verificationMethod ?? profile?.did,
5855
- address: session.address,
5856
- chainId: session.chainId,
5857
- siwe: session.siwe,
5858
- signature: session.signature
5859
- });
5860
- restoredOwnSession = true;
6047
+ await restoreProfileSession(node, ctx.profile, profile, persistedSessionData(ctx.profile, profile, session, key));
6048
+ recordWarnings(await replayAdditionalDelegations(node, ctx.profile, {
6049
+ host: ctx.host,
6050
+ ownerSpace: profile?.spaceId,
6051
+ migrate: true
6052
+ }));
5861
6053
  }
5862
- await replayAdditionalDelegations(node, ctx.profile, {
5863
- host: ctx.host,
5864
- ownerSpace: profile?.spaceId,
5865
- migrate: restoredOwnSession
5866
- });
5867
6054
  return node;
5868
6055
  }
5869
6056
  async function bootstrapDelegatedSession(ctx, delegation) {
@@ -5990,6 +6177,8 @@ var init_sdk = __esm({
5990
6177
  init_errors();
5991
6178
  init_constants();
5992
6179
  init_permissions();
6180
+ init_session_expired();
6181
+ init_warnings();
5993
6182
  BOOTSTRAP_PROFILE_FIELDS = ["sessionDid", "spaceId"];
5994
6183
  }
5995
6184
  });
@@ -6088,7 +6277,7 @@ function formatBannerLine(version2) {
6088
6277
  }
6089
6278
  function emitBanner(version2) {
6090
6279
  if (bannerEmitted) return;
6091
- if (!isInteractive()) return;
6280
+ if (shouldOutputJson()) return;
6092
6281
  if (process.env.TC_HIDE_BANNER === "1") return;
6093
6282
  bannerEmitted = true;
6094
6283
  process.stderr.write(formatBannerLine(version2) + "\n\n");
@@ -16699,7 +16888,6 @@ function parseDuration(input) {
16699
16888
  }
16700
16889
 
16701
16890
  // src/commands/share.ts
16702
- init_formatter();
16703
16891
  init_errors();
16704
16892
 
16705
16893
  // src/share/output.ts
@@ -16965,9 +17153,8 @@ function shareCliError(error, operation = "publish") {
16965
17153
  if (failure.kind === "registry-rejected") {
16966
17154
  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);
16967
17155
  }
16968
- if (failure.kind === "storage-quota-exceeded") {
16969
- const sizes = failure.usedBytes === void 0 || failure.limitBytes === void 0 ? "" : ` (${formatBytes(failure.usedBytes)} used of ${formatBytes(failure.limitBytes)} limit)`;
16970
- return new CLIError("STORAGE_QUOTA_EXCEEDED", `storage quota exceeded${sizes}; nothing was shared`, 4);
17156
+ if (failure.kind === "storage-full") {
17157
+ return storageFullError(failure);
16971
17158
  }
16972
17159
  if (failure.kind === "upload-failed") {
16973
17160
  return new CLIError("UPLOAD_FAILED", "share source upload failed; nothing was shared", 4);
@@ -16987,6 +17174,8 @@ function shareCliError(error, operation = "publish") {
16987
17174
  const code3 = error.code === "fetch-failed" ? "NOT_FOUND" : error.code.replaceAll("-", "_").toUpperCase();
16988
17175
  return new CLIError(code3, error.message, exit);
16989
17176
  }
17177
+ const storageFull = storageFullError(error);
17178
+ if (storageFull) return storageFull;
16990
17179
  const message = error instanceof Error ? error.message : String(error);
16991
17180
  const nodeCode = typeof error === "object" && error !== null && "code" in error ? error.code : void 0;
16992
17181
  const known = {
@@ -17280,8 +17469,10 @@ function registerShareCommand(program2) {
17280
17469
  }
17281
17470
 
17282
17471
  // src/share/adapters.ts
17472
+ init_storage();
17283
17473
  init_profiles();
17284
17474
  init_constants();
17475
+ init_session_expired();
17285
17476
  import { readFile as readFile4 } from "fs/promises";
17286
17477
  import { join as join6 } from "path";
17287
17478
  import { createHash } from "crypto";
@@ -17293,15 +17484,9 @@ function requiredKvAction(meta) {
17293
17484
  const action = meta.requiredAction;
17294
17485
  return action === "tinycloud.kv/put" || action === "tinycloud.kv/get" ? action : void 0;
17295
17486
  }
17296
- function isByteCount(value) {
17297
- return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
17298
- }
17299
17487
  function throwKvUploadFailure(error) {
17300
- if (typeof error === "object" && error !== null && "code" in error && error.code === "STORAGE_QUOTA_EXCEEDED") {
17301
- const meta = "meta" in error && typeof error.meta === "object" && error.meta !== null ? error.meta : {};
17302
- const { usedBytes, limitBytes } = meta;
17303
- throw new SharePublishAuthorityError(isByteCount(usedBytes) && isByteCount(limitBytes) ? { kind: "storage-quota-exceeded", usedBytes, limitBytes } : { kind: "storage-quota-exceeded" });
17304
- }
17488
+ const rejection = storageRejection(error);
17489
+ if (rejection) throw new SharePublishAuthorityError({ kind: "storage-full", ...rejection });
17305
17490
  throw new SharePublishAuthorityError({ kind: "upload-failed" });
17306
17491
  }
17307
17492
  var DEFAULT_SHARE_ORIGIN = "https://share.tinycloud.xyz";
@@ -17600,7 +17785,7 @@ function createShareAuthorityAdapters(input = {}) {
17600
17785
  } catch (error) {
17601
17786
  const profileConfig = await ProfileManager.getProfile(profile).catch(() => void 0);
17602
17787
  const code3 = typeof error === "object" && error !== null && "code" in error ? error.code : void 0;
17603
- if (error instanceof InvalidRestoredSessionError || code3 === "AUTH_EXPIRED") {
17788
+ if (error instanceof InvalidRestoredSessionError || code3 === "AUTH_EXPIRED" || isSessionExpiredError(error)) {
17604
17789
  throw new SharePublishAuthorityError({
17605
17790
  kind: "owner-space-unresolved",
17606
17791
  localKey: profileConfig?.authMethod === "local",
@@ -17962,6 +18147,7 @@ function selectedShareProfile() {
17962
18147
  program.name("tc").description("TinyCloud CLI \u2014 self-sovereign storage from the terminal").version(version).option("-p, --profile <name>", "Profile to use").option("-H, --host <url>", "TinyCloud node URL").option("-v, --verbose", "Enable verbose output").option("--no-cache", "Disable caching").option("-q, --quiet", "Suppress non-essential output").option("--json", "Force JSON output");
17963
18148
  program.hook("preAction", async (thisCommand) => {
17964
18149
  const opts = thisCommand.optsWithGlobals();
18150
+ setJsonOutputRequested(opts.json === true);
17965
18151
  const parentName = thisCommand.parent?.name();
17966
18152
  const isShareCommand = parentName === "share" || thisCommand.name() === "share";
17967
18153
  if (!opts.quiet && !isShareCommand) {
@@ -17970,7 +18156,7 @@ program.hook("preAction", async (thisCommand) => {
17970
18156
  const commandName = thisCommand.name();
17971
18157
  const fullCommand = parentName && parentName !== "tc" ? `${parentName} ${commandName}` : commandName;
17972
18158
  const skipGuard = ["tc", "init", "doctor", "completion", "help", "upgrade", "status"].includes(commandName) || fullCommand === "profile create";
17973
- if (!skipGuard && !opts.quiet && isInteractive()) {
18159
+ if (!skipGuard && !opts.quiet && !shouldOutputJson()) {
17974
18160
  try {
17975
18161
  const config = await ProfileManager.getConfig();
17976
18162
  const profileName = opts.profile || config.defaultProfile;
@@ -17987,6 +18173,9 @@ program.hook("preAction", async (thisCommand) => {
17987
18173
  }
17988
18174
  }
17989
18175
  });
18176
+ program.hook("postAction", () => {
18177
+ outputWarnings([]);
18178
+ });
17990
18179
  configureShareCommandServices({
17991
18180
  targetAdapter: shareAuthority.targetAdapter,
17992
18181
  records: shareAuthority.records,