@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 +303 -114
- package/dist/index.js.map +1 -1
- package/dist/legacy-entry.js +10738 -6668
- package/dist/legacy-entry.js.map +1 -1
- package/package.json +8 -7
- package/skills/tc-cli/AUTH.md +22 -7
- package/skills/tc-cli/REFERENCE.md +55 -7
- package/skills/tc-cli/SKILL.md +2 -0
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
|
|
76
|
-
|
|
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
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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,
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
5818
|
-
if (
|
|
5819
|
-
await node2.
|
|
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
|
-
|
|
5835
|
-
|
|
5836
|
-
|
|
5837
|
-
|
|
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.
|
|
5850
|
-
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
|
|
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 (
|
|
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-
|
|
16969
|
-
|
|
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
|
-
|
|
17301
|
-
|
|
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 &&
|
|
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,
|