@tinycloud/cli 1.1.0-beta.8 → 1.1.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 +251 -67
- package/dist/index.js.map +1 -1
- package/dist/legacy-entry.js +10589 -6656
- package/dist/legacy-entry.js.map +1 -1
- package/package.json +8 -7
- package/skills/tc-cli/AUTH.md +7 -4
- package/skills/tc-cli/REFERENCE.md +41 -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
|
});
|
|
@@ -72,8 +74,14 @@ var init_theme = __esm({
|
|
|
72
74
|
|
|
73
75
|
// src/output/formatter.ts
|
|
74
76
|
import ora from "ora";
|
|
75
|
-
function
|
|
76
|
-
|
|
77
|
+
function warningLine(warnings) {
|
|
78
|
+
const which = warnings.map((warning) => `${warning.grantCid ?? "unidentified"}: ${warning.reason}`).join(", ");
|
|
79
|
+
const subject = warnings.length === 1 ? "A stored grant was" : `${warnings.length} stored grants were`;
|
|
80
|
+
return `${subject} not used (${which}). Approve access again when a command needs it; the old grant expires on its own.`;
|
|
81
|
+
}
|
|
82
|
+
function outputError(code3, message, hint, details = {}) {
|
|
83
|
+
const { meta, warnings = [] } = details;
|
|
84
|
+
if (!shouldOutputJson()) {
|
|
77
85
|
process.stderr.write(
|
|
78
86
|
`${theme.error("\u2717")} ${theme.label(code3)}: ${message}
|
|
79
87
|
`
|
|
@@ -84,26 +92,127 @@ function outputError(code3, message, hint) {
|
|
|
84
92
|
`);
|
|
85
93
|
}
|
|
86
94
|
}
|
|
95
|
+
if (warnings.length > 0) process.stderr.write(` ${theme.warn(warningLine(warnings))}
|
|
96
|
+
`);
|
|
87
97
|
} else {
|
|
88
98
|
const payload = {
|
|
89
99
|
error: { code: code3, message }
|
|
90
100
|
};
|
|
91
101
|
if (hint) payload.error.hint = hint;
|
|
102
|
+
if (meta) payload.error.meta = meta;
|
|
103
|
+
if (warnings.length > 0) payload.error.warnings = warnings;
|
|
92
104
|
process.stderr.write(JSON.stringify(payload, null, 2) + "\n");
|
|
93
105
|
}
|
|
94
106
|
}
|
|
107
|
+
function operationWarnings(value) {
|
|
108
|
+
if (!Array.isArray(value)) return [];
|
|
109
|
+
return value.flatMap((warning) => {
|
|
110
|
+
if (warning === null || typeof warning !== "object") return [];
|
|
111
|
+
const { code: code3, reason, grantCid } = warning;
|
|
112
|
+
if (typeof code3 !== "string" || !WARNING_CODE.test(code3) || typeof reason !== "string" || !WARNING_REASON.test(reason)) {
|
|
113
|
+
return [];
|
|
114
|
+
}
|
|
115
|
+
return [{ code: code3, reason, ...typeof grantCid === "string" && DELEGATION_CID.test(grantCid) ? { grantCid } : {} }];
|
|
116
|
+
});
|
|
117
|
+
}
|
|
95
118
|
function isInteractive() {
|
|
96
119
|
return Boolean(process.stdout.isTTY);
|
|
97
120
|
}
|
|
98
|
-
function
|
|
99
|
-
|
|
100
|
-
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
101
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
121
|
+
function setJsonOutputRequested(requested) {
|
|
122
|
+
globalThis[JSON_REQUESTED] = requested;
|
|
102
123
|
}
|
|
124
|
+
function shouldOutputJson() {
|
|
125
|
+
return !isInteractive() || (globalThis[JSON_REQUESTED] ?? process.argv.includes("--json"));
|
|
126
|
+
}
|
|
127
|
+
var WARNING_CODE, WARNING_REASON, DELEGATION_CID, JSON_REQUESTED;
|
|
103
128
|
var init_formatter = __esm({
|
|
104
129
|
"src/output/formatter.ts"() {
|
|
105
130
|
"use strict";
|
|
106
131
|
init_theme();
|
|
132
|
+
WARNING_CODE = /^[A-Z][A-Z_]{0,63}$/;
|
|
133
|
+
WARNING_REASON = /^[a-z][a-z_]{0,63}$/;
|
|
134
|
+
DELEGATION_CID = /^bafkr4i[a-z2-7]{52}$/;
|
|
135
|
+
JSON_REQUESTED = /* @__PURE__ */ Symbol.for("tinycloud.cli.jsonOutputRequested");
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// src/output/storage.ts
|
|
140
|
+
function storageRejection(error) {
|
|
141
|
+
let node = error;
|
|
142
|
+
for (let depth = 0; depth < MAX_CAUSE_DEPTH && typeof node === "object" && node !== null; depth += 1) {
|
|
143
|
+
const record = node;
|
|
144
|
+
let code3;
|
|
145
|
+
if (record.code === "STORAGE_QUOTA_EXCEEDED" || record.code === "STORAGE_LIMIT_REACHED") {
|
|
146
|
+
code3 = record.code;
|
|
147
|
+
} else if (record.code !== void 0) {
|
|
148
|
+
return void 0;
|
|
149
|
+
} else if (isStorageStatus(record)) {
|
|
150
|
+
code3 = storageCodeFromText(record.message);
|
|
151
|
+
}
|
|
152
|
+
if (code3) {
|
|
153
|
+
const account = accountTotals(record) ?? accountTotals(record.meta) ?? accountTotals(record.metadata);
|
|
154
|
+
return account ? { code: code3, account } : { code: code3 };
|
|
155
|
+
}
|
|
156
|
+
node = record.cause;
|
|
157
|
+
}
|
|
158
|
+
return void 0;
|
|
159
|
+
}
|
|
160
|
+
function describeStorageRejection(rejection, progress) {
|
|
161
|
+
const reason = rejection.code === "STORAGE_LIMIT_REACHED" ? "This write is larger than the TinyCloud storage you have left" : "TinyCloud storage is full";
|
|
162
|
+
const message = progress ? `${progress}${reason}.` : `${reason}; nothing was written.`;
|
|
163
|
+
const { account } = rejection;
|
|
164
|
+
const totals = account ? `${formatStorageBytes(account.usedBytes)} used of ${formatStorageBytes(account.limitBytes)}${account.plan ? ` (${account.plan} plan)` : ""}. ` : "";
|
|
165
|
+
return {
|
|
166
|
+
message,
|
|
167
|
+
hint: `${totals}Reading still works.
|
|
168
|
+
Free up space or upgrade: ${MANAGE_STORAGE_URL}`
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
function formatStorageBytes(bytes) {
|
|
172
|
+
const units = ["KiB", "MiB", "GiB", "TiB"];
|
|
173
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
174
|
+
let value = bytes / 1024;
|
|
175
|
+
let unit = 0;
|
|
176
|
+
while (value >= 1024 && unit < units.length - 1) {
|
|
177
|
+
value /= 1024;
|
|
178
|
+
unit += 1;
|
|
179
|
+
}
|
|
180
|
+
return `${value.toFixed(1).replace(/\.0$/, "")} ${units[unit]}`;
|
|
181
|
+
}
|
|
182
|
+
function storageCodeFromText(message) {
|
|
183
|
+
if (typeof message !== "string") return void 0;
|
|
184
|
+
if (message.includes("larger than the TinyCloud storage you have left")) return "STORAGE_LIMIT_REACHED";
|
|
185
|
+
if (message.includes("TinyCloud storage is full") || /storage quota exceeded/i.test(message)) {
|
|
186
|
+
return "STORAGE_QUOTA_EXCEEDED";
|
|
187
|
+
}
|
|
188
|
+
return void 0;
|
|
189
|
+
}
|
|
190
|
+
function isStorageStatus(record) {
|
|
191
|
+
const meta = "meta" in record && typeof record.meta === "object" && record.meta !== null ? record.meta : {};
|
|
192
|
+
const statuses = [
|
|
193
|
+
"status" in record ? record.status : void 0,
|
|
194
|
+
"statusCode" in record ? record.statusCode : void 0,
|
|
195
|
+
"status" in meta ? meta.status : void 0
|
|
196
|
+
];
|
|
197
|
+
return statuses.some((value) => value === 402 || value === 413);
|
|
198
|
+
}
|
|
199
|
+
function accountTotals(meta) {
|
|
200
|
+
if (typeof meta !== "object" || meta === null) return void 0;
|
|
201
|
+
const account = meta.account;
|
|
202
|
+
if (typeof account !== "object" || account === null) return void 0;
|
|
203
|
+
const { usedBytes, limitBytes, plan } = account;
|
|
204
|
+
if (!isByteCount(usedBytes) || !isByteCount(limitBytes) || limitBytes === 0) return void 0;
|
|
205
|
+
return typeof plan === "string" && /^[A-Za-z0-9][A-Za-z0-9 _.-]{0,31}$/.test(plan) ? { usedBytes, limitBytes, plan } : { usedBytes, limitBytes };
|
|
206
|
+
}
|
|
207
|
+
function isByteCount(value) {
|
|
208
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
209
|
+
}
|
|
210
|
+
var MANAGE_STORAGE_URL, MAX_CAUSE_DEPTH;
|
|
211
|
+
var init_storage = __esm({
|
|
212
|
+
"src/output/storage.ts"() {
|
|
213
|
+
"use strict";
|
|
214
|
+
MANAGE_STORAGE_URL = "https://account.tinycloud.xyz/billing";
|
|
215
|
+
MAX_CAUSE_DEPTH = 4;
|
|
107
216
|
}
|
|
108
217
|
});
|
|
109
218
|
|
|
@@ -111,23 +220,25 @@ var init_formatter = __esm({
|
|
|
111
220
|
import { readFileSync, readdirSync } from "fs";
|
|
112
221
|
import { join } from "path";
|
|
113
222
|
import { ProfileDeletedError, ProfileLockTimeoutError } from "@tinycloud/operations/state";
|
|
223
|
+
import { authorizationVerdictOf, parseCapabilityResource, SERVICE_LONG_TO_SHORT, validatedCapabilityOf } from "@tinycloud/sdk-core";
|
|
114
224
|
function setActiveProfileName(name) {
|
|
115
225
|
activeProfileName = name;
|
|
116
226
|
}
|
|
117
227
|
function wrapError(error) {
|
|
228
|
+
const storageFull = storageFullError(error);
|
|
229
|
+
if (storageFull) return storageFull;
|
|
230
|
+
if (error instanceof CLIError) return error;
|
|
118
231
|
const message = error instanceof Error ? error.message : String(error);
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
return new CLIError(
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
);
|
|
232
|
+
const verdict = authorizationVerdictOf(error);
|
|
233
|
+
if (verdict === "unauthenticated") {
|
|
234
|
+
return new CLIError("AUTH_REQUIRED", message, ExitCode.AUTH_REQUIRED);
|
|
235
|
+
}
|
|
236
|
+
if (verdict === "forbidden") {
|
|
237
|
+
return new CLIError("PERMISSION_DENIED", message, ExitCode.PERMISSION_DENIED);
|
|
238
|
+
}
|
|
239
|
+
if (verdict === void 0 && message.includes("Missing private key parameter in JWK")) {
|
|
240
|
+
return missingPrivateKeyError();
|
|
129
241
|
}
|
|
130
|
-
if (error instanceof CLIError) return error;
|
|
131
242
|
if (error instanceof ProfileLockTimeoutError) {
|
|
132
243
|
return new CLIError(
|
|
133
244
|
"PROFILE_LOCK_TIMEOUT",
|
|
@@ -139,13 +250,13 @@ function wrapError(error) {
|
|
|
139
250
|
if (error instanceof ProfileDeletedError) {
|
|
140
251
|
return new CLIError("PROFILE_NOT_FOUND", message);
|
|
141
252
|
}
|
|
142
|
-
if (message.includes("Not signed in") || message.includes("AUTH_EXPIRED") || message.includes("Session expired")) {
|
|
253
|
+
if (verdict === void 0 && (message.includes("Not signed in") || message.includes("AUTH_EXPIRED") || message.includes("Session expired"))) {
|
|
143
254
|
return new CLIError("AUTH_REQUIRED", message, ExitCode.AUTH_REQUIRED);
|
|
144
255
|
}
|
|
145
256
|
if (message.includes("NOT_FOUND") || message.includes("KV_NOT_FOUND")) {
|
|
146
257
|
return new CLIError("NOT_FOUND", message, ExitCode.NOT_FOUND);
|
|
147
258
|
}
|
|
148
|
-
if (message.includes("PERMISSION_DENIED")) {
|
|
259
|
+
if (verdict === void 0 && message.includes("PERMISSION_DENIED")) {
|
|
149
260
|
return new CLIError("PERMISSION_DENIED", message, ExitCode.PERMISSION_DENIED);
|
|
150
261
|
}
|
|
151
262
|
if (message.includes("ECONNREFUSED") || message.includes("ETIMEDOUT") || message.includes("fetch failed")) {
|
|
@@ -153,39 +264,61 @@ function wrapError(error) {
|
|
|
153
264
|
}
|
|
154
265
|
return new CLIError("ERROR", message, ExitCode.ERROR);
|
|
155
266
|
}
|
|
267
|
+
function missingPrivateKeyError() {
|
|
268
|
+
const profileName = activeProfileName ?? process.env.TC_PROFILE ?? DEFAULT_PROFILE;
|
|
269
|
+
return new CLIError(
|
|
270
|
+
"AUTH_REQUIRED",
|
|
271
|
+
`Profile "${profileName}" cannot restore its session because its private key material is missing.`,
|
|
272
|
+
ExitCode.AUTH_REQUIRED,
|
|
273
|
+
{ hint: `Sign in again with: tc --profile ${profileName} auth login --method openkey` }
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
function storageFullError(error, progress) {
|
|
277
|
+
if (error instanceof CLIError && error.exitCode === ExitCode.STORAGE_FULL) return error;
|
|
278
|
+
const rejection = storageRejection(error);
|
|
279
|
+
if (!rejection) return void 0;
|
|
280
|
+
const { message, hint } = describeStorageRejection(rejection, progress);
|
|
281
|
+
return new CLIError(rejection.code, message, ExitCode.STORAGE_FULL, { hint });
|
|
282
|
+
}
|
|
156
283
|
function handleError(error) {
|
|
157
284
|
const cliError = wrapError(error);
|
|
158
285
|
const prebuilt = typeof cliError.metadata?.hint === "string" ? cliError.metadata.hint : void 0;
|
|
159
286
|
const hint = prebuilt ?? buildAuthHint(cliError) ?? (cliError.code === "NETWORK_ERROR" ? buildNetworkHint() : void 0);
|
|
160
|
-
|
|
287
|
+
const authMeta = cliError.code === "AUTH_REQUIRED" || cliError.code === "PERMISSION_DENIED" ? cliError.metadata : void 0;
|
|
288
|
+
const meta = {};
|
|
289
|
+
if (cliError.status !== void 0 && authMeta !== void 0) meta.status = cliError.status;
|
|
290
|
+
const capability = validatedCapabilityOf({ meta: authMeta });
|
|
291
|
+
if (capability) {
|
|
292
|
+
meta.resource = capability.resource;
|
|
293
|
+
meta.requiredAction = capability.requiredAction;
|
|
294
|
+
}
|
|
295
|
+
outputError(cliError.code, cliError.message, hint, {
|
|
296
|
+
...Object.keys(meta).length ? { meta } : {},
|
|
297
|
+
warnings: operationWarnings(cliError.metadata?.warnings)
|
|
298
|
+
});
|
|
161
299
|
process.exit(cliError.exitCode);
|
|
162
300
|
}
|
|
163
301
|
function buildAuthHint(error) {
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
return void 0;
|
|
168
|
-
}
|
|
169
|
-
const spec = capSpecFromAuthMeta(resource, requiredAction);
|
|
302
|
+
const capability = validatedCapabilityOf({ meta: error.metadata });
|
|
303
|
+
if (!capability) return void 0;
|
|
304
|
+
const spec = capSpecFromAuthMeta(capability.resource, capability.requiredAction);
|
|
170
305
|
if (!spec) return void 0;
|
|
171
306
|
return [
|
|
172
307
|
"The active session is missing a TinyCloud capability.",
|
|
173
|
-
`Request it with: tc auth request --cap
|
|
308
|
+
`Request it with: tc auth request --cap '${spec.replaceAll("'", "'\\''")}'`,
|
|
174
309
|
"Then retry the original command."
|
|
175
310
|
].join("\n");
|
|
176
311
|
}
|
|
177
312
|
function capSpecFromAuthMeta(resource, action) {
|
|
178
|
-
const slash =
|
|
179
|
-
if (slash
|
|
180
|
-
const
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
const spaceName = spaceUri.startsWith("tinycloud:") ? spaceUri.slice(spaceUri.lastIndexOf(":") + 1) : spaceUri;
|
|
188
|
-
return `tinycloud.${serviceShort}:${spaceName}:${path}:${actionName}`;
|
|
313
|
+
const slash = action.indexOf("/");
|
|
314
|
+
if (slash < 0) return void 0;
|
|
315
|
+
const longService = action.slice(0, slash);
|
|
316
|
+
const serviceShort = SERVICE_LONG_TO_SHORT[longService];
|
|
317
|
+
if (!serviceShort) return void 0;
|
|
318
|
+
const parsed = parseCapabilityResource(resource, serviceShort);
|
|
319
|
+
if (!parsed) return void 0;
|
|
320
|
+
const spaceName = parsed.space.slice(parsed.space.lastIndexOf(":") + 1);
|
|
321
|
+
return `${longService}:${spaceName}:${parsed.path}:${action.slice(slash + 1)}`;
|
|
189
322
|
}
|
|
190
323
|
function buildNetworkHint() {
|
|
191
324
|
const readHost = (name) => {
|
|
@@ -232,6 +365,7 @@ var init_errors = __esm({
|
|
|
232
365
|
"use strict";
|
|
233
366
|
init_constants();
|
|
234
367
|
init_formatter();
|
|
368
|
+
init_storage();
|
|
235
369
|
CLIError = class extends Error {
|
|
236
370
|
constructor(code3, message, exitCode = ExitCode.ERROR, metadata) {
|
|
237
371
|
super(message);
|
|
@@ -239,7 +373,9 @@ var init_errors = __esm({
|
|
|
239
373
|
this.exitCode = exitCode;
|
|
240
374
|
this.metadata = metadata;
|
|
241
375
|
this.name = "CLIError";
|
|
376
|
+
if (typeof metadata?.status === "number" && Number.isInteger(metadata.status) && metadata.status >= 400 && metadata.status <= 599) this.status = metadata.status;
|
|
242
377
|
}
|
|
378
|
+
status;
|
|
243
379
|
};
|
|
244
380
|
}
|
|
245
381
|
});
|
|
@@ -297,7 +433,7 @@ async function listDirs(dirPath) {
|
|
|
297
433
|
}
|
|
298
434
|
}
|
|
299
435
|
var PRIVATE_FILE_MODE, PRIVATE_DIR_MODE;
|
|
300
|
-
var
|
|
436
|
+
var init_storage2 = __esm({
|
|
301
437
|
"src/config/storage.ts"() {
|
|
302
438
|
"use strict";
|
|
303
439
|
PRIVATE_FILE_MODE = 384;
|
|
@@ -555,7 +691,7 @@ var init_profiles = __esm({
|
|
|
555
691
|
"src/config/profiles.ts"() {
|
|
556
692
|
"use strict";
|
|
557
693
|
init_constants();
|
|
558
|
-
|
|
694
|
+
init_storage2();
|
|
559
695
|
init_errors();
|
|
560
696
|
ProfileManager = class _ProfileManager {
|
|
561
697
|
// ── Initialization ──────────────────────────────────────────────────
|
|
@@ -625,7 +761,7 @@ var init_profiles = __esm({
|
|
|
625
761
|
if (!profile) {
|
|
626
762
|
throw new CLIError(
|
|
627
763
|
"PROFILE_NOT_FOUND",
|
|
628
|
-
`Profile "${name}" does not exist.
|
|
764
|
+
`Profile "${name}" does not exist. Create it with \`tc init --name ${name}\`.`
|
|
629
765
|
);
|
|
630
766
|
}
|
|
631
767
|
return profile;
|
|
@@ -5636,6 +5772,37 @@ var init_zod = __esm({
|
|
|
5636
5772
|
}
|
|
5637
5773
|
});
|
|
5638
5774
|
|
|
5775
|
+
// src/auth/session-expired.ts
|
|
5776
|
+
function signInAgainHint(profileName, posture) {
|
|
5777
|
+
if (posture === "local-owner-key") {
|
|
5778
|
+
return `Sign in again with: tc --profile ${profileName} auth login --method local`;
|
|
5779
|
+
}
|
|
5780
|
+
if (posture === "delegate-session") {
|
|
5781
|
+
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.`;
|
|
5782
|
+
}
|
|
5783
|
+
return `Sign in again with: tc --profile ${profileName} auth login --method openkey`;
|
|
5784
|
+
}
|
|
5785
|
+
function sessionExpiredError(profileName, posture) {
|
|
5786
|
+
return new CLIError(
|
|
5787
|
+
"AUTH_REQUIRED",
|
|
5788
|
+
`The session for profile "${profileName}" has expired or is no longer valid.`,
|
|
5789
|
+
ExitCode.AUTH_REQUIRED,
|
|
5790
|
+
{ hint: signInAgainHint(profileName, posture), reason: SESSION_EXPIRED_REASON }
|
|
5791
|
+
);
|
|
5792
|
+
}
|
|
5793
|
+
function isSessionExpiredError(error) {
|
|
5794
|
+
return error instanceof CLIError && error.metadata?.reason === SESSION_EXPIRED_REASON;
|
|
5795
|
+
}
|
|
5796
|
+
var SESSION_EXPIRED_REASON;
|
|
5797
|
+
var init_session_expired = __esm({
|
|
5798
|
+
"src/auth/session-expired.ts"() {
|
|
5799
|
+
"use strict";
|
|
5800
|
+
init_constants();
|
|
5801
|
+
init_errors();
|
|
5802
|
+
SESSION_EXPIRED_REASON = "session_expired";
|
|
5803
|
+
}
|
|
5804
|
+
});
|
|
5805
|
+
|
|
5639
5806
|
// src/lib/raw-encryption.ts
|
|
5640
5807
|
var init_raw_encryption = __esm({
|
|
5641
5808
|
"src/lib/raw-encryption.ts"() {
|
|
@@ -5698,13 +5865,14 @@ async function replayAdditionalDelegations(node, profile, options) {
|
|
|
5698
5865
|
for (const stored of entries) {
|
|
5699
5866
|
const kind = storedDelegationKind(stored);
|
|
5700
5867
|
if (kind === "compact" || kind === "signed-login") {
|
|
5701
|
-
const
|
|
5868
|
+
const replay = await replayStoredDelegation(activator, stored, {
|
|
5702
5869
|
host: options.host,
|
|
5703
5870
|
migrated,
|
|
5704
5871
|
resolveSpace
|
|
5705
5872
|
});
|
|
5706
|
-
if (
|
|
5707
|
-
process.stderr.write(
|
|
5873
|
+
if (replay.status === "skipped" && process.env.TC_DEBUG_REPLAY === "1") {
|
|
5874
|
+
process.stderr.write(`[replay] skipping a stored delegation: ${replay.reason}
|
|
5875
|
+
`);
|
|
5708
5876
|
}
|
|
5709
5877
|
continue;
|
|
5710
5878
|
}
|
|
@@ -5735,7 +5903,7 @@ var init_permissions = __esm({
|
|
|
5735
5903
|
"src/lib/permissions.ts"() {
|
|
5736
5904
|
"use strict";
|
|
5737
5905
|
init_constants();
|
|
5738
|
-
|
|
5906
|
+
init_storage2();
|
|
5739
5907
|
init_profiles();
|
|
5740
5908
|
init_errors();
|
|
5741
5909
|
init_constants();
|
|
@@ -5782,6 +5950,16 @@ function signerJwkForProfile(profileName, sessionJwk, key) {
|
|
|
5782
5950
|
}
|
|
5783
5951
|
);
|
|
5784
5952
|
}
|
|
5953
|
+
async function restoreProfileSession(node, profileName, profile, sessionData) {
|
|
5954
|
+
try {
|
|
5955
|
+
await node.restoreSession(sessionData);
|
|
5956
|
+
} catch (error) {
|
|
5957
|
+
if (typeof error === "object" && error !== null && "code" in error && error.code === "AUTH_EXPIRED") {
|
|
5958
|
+
throw sessionExpiredError(profileName, profile === null ? void 0 : resolveProfilePosture(profile));
|
|
5959
|
+
}
|
|
5960
|
+
throw error;
|
|
5961
|
+
}
|
|
5962
|
+
}
|
|
5785
5963
|
async function createSDKInstance(ctx, options) {
|
|
5786
5964
|
const profile = options?.privateKey ? await ProfileManager.getProfile(ctx.profile).catch(() => null) : await ProfileManager.getProfile(ctx.profile);
|
|
5787
5965
|
const session = await ProfileManager.getSession(ctx.profile);
|
|
@@ -5790,7 +5968,7 @@ async function createSDKInstance(ctx, options) {
|
|
|
5790
5968
|
if (!key && !effectivePrivateKey && !(profile?.authMethod === "openkey" && session !== null)) {
|
|
5791
5969
|
throw new CLIError(
|
|
5792
5970
|
"AUTH_REQUIRED",
|
|
5793
|
-
`No key found for profile "${ctx.profile}". Run \`tc
|
|
5971
|
+
`No key found for profile "${ctx.profile}". Run \`tc --profile ${ctx.profile} auth rotate\` to create a new key and sign in.`,
|
|
5794
5972
|
ExitCode.AUTH_REQUIRED
|
|
5795
5973
|
);
|
|
5796
5974
|
}
|
|
@@ -5801,7 +5979,7 @@ async function createSDKInstance(ctx, options) {
|
|
|
5801
5979
|
});
|
|
5802
5980
|
let restoredOwnSession2 = false;
|
|
5803
5981
|
if (session && session.delegationHeader && session.delegationCid && session.spaceId) {
|
|
5804
|
-
await node2.
|
|
5982
|
+
await restoreProfileSession(node2, ctx.profile, profile, {
|
|
5805
5983
|
delegationHeader: session.delegationHeader,
|
|
5806
5984
|
delegationCid: session.delegationCid,
|
|
5807
5985
|
spaceId: session.spaceId,
|
|
@@ -5831,7 +6009,7 @@ async function createSDKInstance(ctx, options) {
|
|
|
5831
6009
|
if (options?.privateKey) {
|
|
5832
6010
|
await node.signIn();
|
|
5833
6011
|
} else if (session && session.delegationHeader && session.delegationCid && session.spaceId) {
|
|
5834
|
-
await node.
|
|
6012
|
+
await restoreProfileSession(node, ctx.profile, profile, {
|
|
5835
6013
|
delegationHeader: session.delegationHeader,
|
|
5836
6014
|
delegationCid: session.delegationCid,
|
|
5837
6015
|
spaceId: session.spaceId,
|
|
@@ -5975,6 +6153,7 @@ var init_sdk = __esm({
|
|
|
5975
6153
|
init_errors();
|
|
5976
6154
|
init_constants();
|
|
5977
6155
|
init_permissions();
|
|
6156
|
+
init_session_expired();
|
|
5978
6157
|
BOOTSTRAP_PROFILE_FIELDS = ["sessionDid", "spaceId"];
|
|
5979
6158
|
}
|
|
5980
6159
|
});
|
|
@@ -6073,7 +6252,7 @@ function formatBannerLine(version2) {
|
|
|
6073
6252
|
}
|
|
6074
6253
|
function emitBanner(version2) {
|
|
6075
6254
|
if (bannerEmitted) return;
|
|
6076
|
-
if (
|
|
6255
|
+
if (shouldOutputJson()) return;
|
|
6077
6256
|
if (process.env.TC_HIDE_BANNER === "1") return;
|
|
6078
6257
|
bannerEmitted = true;
|
|
6079
6258
|
process.stderr.write(formatBannerLine(version2) + "\n\n");
|
|
@@ -15019,7 +15198,6 @@ async function notifyShare(input) {
|
|
|
15019
15198
|
};
|
|
15020
15199
|
}
|
|
15021
15200
|
let attempts = 0;
|
|
15022
|
-
let lastError;
|
|
15023
15201
|
while (attempts < attemptsLimit) {
|
|
15024
15202
|
if (input.signal?.aborted) throw new ShareNotifyError("share delivery was cancelled");
|
|
15025
15203
|
attempts += 1;
|
|
@@ -15046,10 +15224,19 @@ async function notifyShare(input) {
|
|
|
15046
15224
|
reason: "delivery-window-expired"
|
|
15047
15225
|
};
|
|
15048
15226
|
}
|
|
15049
|
-
|
|
15227
|
+
if (error !== null && typeof error === "object" && "status" in error && (error.status === 401 || error.status === 403)) {
|
|
15228
|
+
return {
|
|
15229
|
+
protocol: "tinycloud-share",
|
|
15230
|
+
version: 1,
|
|
15231
|
+
shareId: input.shareId,
|
|
15232
|
+
state: "partial-failure",
|
|
15233
|
+
idempotencyKey,
|
|
15234
|
+
attempts,
|
|
15235
|
+
retryable: false
|
|
15236
|
+
};
|
|
15237
|
+
}
|
|
15050
15238
|
}
|
|
15051
15239
|
}
|
|
15052
|
-
void lastError;
|
|
15053
15240
|
return { protocol: "tinycloud-share", version: 1, shareId: input.shareId, state: "partial-failure", idempotencyKey, attempts, retryable: true };
|
|
15054
15241
|
}
|
|
15055
15242
|
async function defaultIdempotencyKey(shareId, recipient) {
|
|
@@ -16676,7 +16863,6 @@ function parseDuration(input) {
|
|
|
16676
16863
|
}
|
|
16677
16864
|
|
|
16678
16865
|
// src/commands/share.ts
|
|
16679
|
-
init_formatter();
|
|
16680
16866
|
init_errors();
|
|
16681
16867
|
|
|
16682
16868
|
// src/share/output.ts
|
|
@@ -16942,9 +17128,8 @@ function shareCliError(error, operation = "publish") {
|
|
|
16942
17128
|
if (failure.kind === "registry-rejected") {
|
|
16943
17129
|
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);
|
|
16944
17130
|
}
|
|
16945
|
-
if (failure.kind === "storage-
|
|
16946
|
-
|
|
16947
|
-
return new CLIError("STORAGE_QUOTA_EXCEEDED", `storage quota exceeded${sizes}; nothing was shared`, 4);
|
|
17131
|
+
if (failure.kind === "storage-full") {
|
|
17132
|
+
return storageFullError(failure);
|
|
16948
17133
|
}
|
|
16949
17134
|
if (failure.kind === "upload-failed") {
|
|
16950
17135
|
return new CLIError("UPLOAD_FAILED", "share source upload failed; nothing was shared", 4);
|
|
@@ -16964,6 +17149,8 @@ function shareCliError(error, operation = "publish") {
|
|
|
16964
17149
|
const code3 = error.code === "fetch-failed" ? "NOT_FOUND" : error.code.replaceAll("-", "_").toUpperCase();
|
|
16965
17150
|
return new CLIError(code3, error.message, exit);
|
|
16966
17151
|
}
|
|
17152
|
+
const storageFull = storageFullError(error);
|
|
17153
|
+
if (storageFull) return storageFull;
|
|
16967
17154
|
const message = error instanceof Error ? error.message : String(error);
|
|
16968
17155
|
const nodeCode = typeof error === "object" && error !== null && "code" in error ? error.code : void 0;
|
|
16969
17156
|
const known = {
|
|
@@ -17257,8 +17444,10 @@ function registerShareCommand(program2) {
|
|
|
17257
17444
|
}
|
|
17258
17445
|
|
|
17259
17446
|
// src/share/adapters.ts
|
|
17447
|
+
init_storage();
|
|
17260
17448
|
init_profiles();
|
|
17261
17449
|
init_constants();
|
|
17450
|
+
init_session_expired();
|
|
17262
17451
|
import { readFile as readFile4 } from "fs/promises";
|
|
17263
17452
|
import { join as join6 } from "path";
|
|
17264
17453
|
import { createHash } from "crypto";
|
|
@@ -17270,15 +17459,9 @@ function requiredKvAction(meta) {
|
|
|
17270
17459
|
const action = meta.requiredAction;
|
|
17271
17460
|
return action === "tinycloud.kv/put" || action === "tinycloud.kv/get" ? action : void 0;
|
|
17272
17461
|
}
|
|
17273
|
-
function isByteCount(value) {
|
|
17274
|
-
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
17275
|
-
}
|
|
17276
17462
|
function throwKvUploadFailure(error) {
|
|
17277
|
-
|
|
17278
|
-
|
|
17279
|
-
const { usedBytes, limitBytes } = meta;
|
|
17280
|
-
throw new SharePublishAuthorityError(isByteCount(usedBytes) && isByteCount(limitBytes) ? { kind: "storage-quota-exceeded", usedBytes, limitBytes } : { kind: "storage-quota-exceeded" });
|
|
17281
|
-
}
|
|
17463
|
+
const rejection = storageRejection(error);
|
|
17464
|
+
if (rejection) throw new SharePublishAuthorityError({ kind: "storage-full", ...rejection });
|
|
17282
17465
|
throw new SharePublishAuthorityError({ kind: "upload-failed" });
|
|
17283
17466
|
}
|
|
17284
17467
|
var DEFAULT_SHARE_ORIGIN = "https://share.tinycloud.xyz";
|
|
@@ -17577,7 +17760,7 @@ function createShareAuthorityAdapters(input = {}) {
|
|
|
17577
17760
|
} catch (error) {
|
|
17578
17761
|
const profileConfig = await ProfileManager.getProfile(profile).catch(() => void 0);
|
|
17579
17762
|
const code3 = typeof error === "object" && error !== null && "code" in error ? error.code : void 0;
|
|
17580
|
-
if (error instanceof InvalidRestoredSessionError || code3 === "AUTH_EXPIRED") {
|
|
17763
|
+
if (error instanceof InvalidRestoredSessionError || code3 === "AUTH_EXPIRED" || isSessionExpiredError(error)) {
|
|
17581
17764
|
throw new SharePublishAuthorityError({
|
|
17582
17765
|
kind: "owner-space-unresolved",
|
|
17583
17766
|
localKey: profileConfig?.authMethod === "local",
|
|
@@ -17939,6 +18122,7 @@ function selectedShareProfile() {
|
|
|
17939
18122
|
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");
|
|
17940
18123
|
program.hook("preAction", async (thisCommand) => {
|
|
17941
18124
|
const opts = thisCommand.optsWithGlobals();
|
|
18125
|
+
setJsonOutputRequested(opts.json === true);
|
|
17942
18126
|
const parentName = thisCommand.parent?.name();
|
|
17943
18127
|
const isShareCommand = parentName === "share" || thisCommand.name() === "share";
|
|
17944
18128
|
if (!opts.quiet && !isShareCommand) {
|
|
@@ -17947,7 +18131,7 @@ program.hook("preAction", async (thisCommand) => {
|
|
|
17947
18131
|
const commandName = thisCommand.name();
|
|
17948
18132
|
const fullCommand = parentName && parentName !== "tc" ? `${parentName} ${commandName}` : commandName;
|
|
17949
18133
|
const skipGuard = ["tc", "init", "doctor", "completion", "help", "upgrade", "status"].includes(commandName) || fullCommand === "profile create";
|
|
17950
|
-
if (!skipGuard && !opts.quiet &&
|
|
18134
|
+
if (!skipGuard && !opts.quiet && !shouldOutputJson()) {
|
|
17951
18135
|
try {
|
|
17952
18136
|
const config = await ProfileManager.getConfig();
|
|
17953
18137
|
const profileName = opts.profile || config.defaultProfile;
|