@tinycloud/cli 1.3.0-beta.42 → 1.3.0-beta.44
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 +79 -43
- package/dist/index.js.map +1 -1
- package/dist/legacy-entry.js +129 -23
- package/dist/legacy-entry.js.map +1 -1
- package/package.json +2 -2
- package/skills/tc-cli/REFERENCE.md +4 -0
package/dist/index.js
CHANGED
|
@@ -416,6 +416,7 @@ import { join } from "path";
|
|
|
416
416
|
import { ProfileDeletedError, ProfileLockTimeoutError, ProfileSymlinkError } from "@tinycloud/operations/state";
|
|
417
417
|
import { isSafeRecovery } from "@tinycloud/operations/recovery";
|
|
418
418
|
import { CLI_NAME as CLI_NAME3 } from "@tinycloud/operations/cli-name";
|
|
419
|
+
import { isRevokedGrantRefusal, REVOKED_GRANT_MESSAGE } from "@tinycloud/operations/revoked-grant";
|
|
419
420
|
import {
|
|
420
421
|
buildSecretRequestUrl,
|
|
421
422
|
isSafeRequestAccessRecovery,
|
|
@@ -644,7 +645,7 @@ function buildNetworkHint() {
|
|
|
644
645
|
lines.push(`Or override per-command with --host or TC_HOST.`);
|
|
645
646
|
return lines.join("\n");
|
|
646
647
|
}
|
|
647
|
-
var activeProfileName, CLIError, SETUP_KEYS;
|
|
648
|
+
var activeProfileName, GRANT_REVOKED_EXIT_CODE, CLIError, SETUP_KEYS;
|
|
648
649
|
var init_errors = __esm({
|
|
649
650
|
"src/output/errors.ts"() {
|
|
650
651
|
"use strict";
|
|
@@ -653,6 +654,7 @@ var init_errors = __esm({
|
|
|
653
654
|
init_formatter();
|
|
654
655
|
init_storage();
|
|
655
656
|
init_warnings();
|
|
657
|
+
GRANT_REVOKED_EXIT_CODE = ExitCode.PERMISSION_DENIED;
|
|
656
658
|
CLIError = class extends Error {
|
|
657
659
|
constructor(code4, message, exitCode = ExitCode.ERROR, metadata) {
|
|
658
660
|
super(message);
|
|
@@ -26576,6 +26578,25 @@ var ShareHistoryRetryError = class extends Error {
|
|
|
26576
26578
|
}
|
|
26577
26579
|
code = "SHARE_HISTORY_RETRY";
|
|
26578
26580
|
};
|
|
26581
|
+
var ShareHistoryUnreadableError = class extends Error {
|
|
26582
|
+
constructor() {
|
|
26583
|
+
super("share history is unavailable");
|
|
26584
|
+
this.name = "ShareHistoryUnreadableError";
|
|
26585
|
+
}
|
|
26586
|
+
};
|
|
26587
|
+
var ShareHistoryReadError = class extends Error {
|
|
26588
|
+
constructor() {
|
|
26589
|
+
super("share history could not be read");
|
|
26590
|
+
this.name = "ShareHistoryReadError";
|
|
26591
|
+
}
|
|
26592
|
+
};
|
|
26593
|
+
var HISTORY_WRITE_ERROR_CODES = { EACCES: true, EPERM: true, EROFS: true, ENOSPC: true, EDQUOT: true, EIO: true };
|
|
26594
|
+
function shareHistoryReason(error) {
|
|
26595
|
+
if (error instanceof ShareHistoryUnreadableError) return "HISTORY_UNREADABLE";
|
|
26596
|
+
if (error instanceof ShareHistoryReadError) return "HISTORY_READ_FAILED";
|
|
26597
|
+
const code4 = typeof error === "object" && error !== null && "code" in error ? error.code : void 0;
|
|
26598
|
+
return typeof code4 === "string" && HISTORY_WRITE_ERROR_CODES[code4] === true ? "HISTORY_WRITE_FAILED" : "HISTORY_UNAVAILABLE";
|
|
26599
|
+
}
|
|
26579
26600
|
|
|
26580
26601
|
// src/commands/share.ts
|
|
26581
26602
|
var SHARE_ORIGIN = "https://share.tinycloud.xyz";
|
|
@@ -26757,16 +26778,18 @@ function expires(value) {
|
|
|
26757
26778
|
}
|
|
26758
26779
|
async function rememberPublishedShare(result) {
|
|
26759
26780
|
const record2 = historyRecordForPublishedShare(result);
|
|
26760
|
-
if (shareServices.records === void 0) return { record: record2, recorded: false };
|
|
26761
26781
|
try {
|
|
26782
|
+
if (shareServices.records === void 0) throw new Error("sender history is not configured");
|
|
26762
26783
|
await shareServices.records.put(record2);
|
|
26763
|
-
return { record: record2, recorded: true };
|
|
26764
|
-
} catch {
|
|
26765
|
-
|
|
26766
|
-
|
|
26784
|
+
return { record: record2, history: { recorded: true } };
|
|
26785
|
+
} catch (error) {
|
|
26786
|
+
const reason = shareHistoryReason(error);
|
|
26787
|
+
process.stderr.write(`Warning: share was published but not recorded in sender history (${reason}); future share list, share notify, and share revoke by ID will not find it. Keep share ID ${result.metadata.shareId} and CID ${result.link.cid}.
|
|
26788
|
+
`);
|
|
26789
|
+
return { record: record2, history: { recorded: false, reason } };
|
|
26767
26790
|
}
|
|
26768
26791
|
}
|
|
26769
|
-
async function notifyRecordedShare(record2, recipient, adapter, storage) {
|
|
26792
|
+
async function notifyRecordedShare(record2, recipient, adapter, storage, published) {
|
|
26770
26793
|
const result = await notifyShare({ shareId: record2.shareId, recipient, record: record2, adapter, checkDeliveryWindow: true });
|
|
26771
26794
|
if (result.state !== "partial-failure" && storage !== void 0) {
|
|
26772
26795
|
const mailbox = canonicalMailbox2(recipient).email;
|
|
@@ -26778,8 +26801,11 @@ async function notifyRecordedShare(record2, recipient, adapter, storage) {
|
|
|
26778
26801
|
deliveredRecipients: current.deliveredRecipients?.includes(mailbox) ? current.deliveredRecipients : [...current.deliveredRecipients ?? [], mailbox]
|
|
26779
26802
|
}));
|
|
26780
26803
|
if (updated === void 0) throw new Error("sender history record was removed");
|
|
26781
|
-
} catch {
|
|
26782
|
-
|
|
26804
|
+
} catch (error) {
|
|
26805
|
+
const reason = shareHistoryReason(error);
|
|
26806
|
+
const identifiers = published === void 0 ? "" : ` Keep share ID ${published.shareId} and CID ${published.cid}.`;
|
|
26807
|
+
process.stderr.write(`Warning: delivery succeeded, but could not record its confirmation in sender history (${reason}).${identifiers}
|
|
26808
|
+
`);
|
|
26783
26809
|
}
|
|
26784
26810
|
}
|
|
26785
26811
|
}
|
|
@@ -26868,17 +26894,17 @@ function registerShareCommand(program2) {
|
|
|
26868
26894
|
}
|
|
26869
26895
|
throw new CLIError(result.method === "openkey-device" ? "DEVICE_AUTH_REQUIRED" : "CLAIM_REQUIRED", "recipient authorization is required; continue through the configured authority adapter", 6);
|
|
26870
26896
|
}
|
|
26871
|
-
const { record: record2,
|
|
26897
|
+
const { record: record2, history } = await rememberPublishedShare(result);
|
|
26872
26898
|
let notification;
|
|
26873
26899
|
if (notifyRecipient !== void 0) {
|
|
26874
26900
|
if (shareServices.delivery === void 0) throw new CLIError("AUTH_REQUIRED", "delivery authority is not configured", 3);
|
|
26875
|
-
notification = await notifyRecordedShare(record2, notifyRecipient, shareServices.delivery, recorded ? shareServices.records : void 0);
|
|
26901
|
+
notification = await notifyRecordedShare(record2, notifyRecipient, shareServices.delivery, history.recorded ? shareServices.records : void 0, { shareId: result.metadata.shareId, cid: result.link.cid });
|
|
26876
26902
|
if (notification.state === "partial-failure") process.exitCode = 9;
|
|
26877
26903
|
if (notification.reason === "delivery-window-expired") process.stderr.write(NOTIFY_WINDOW_MESSAGE);
|
|
26878
26904
|
}
|
|
26879
26905
|
if (result.metadata.expiryClamped === true) process.stderr.write(`Share expiry clamped to session expiry (${result.metadata.expiresAt}).
|
|
26880
26906
|
`);
|
|
26881
|
-
if (json) writeJson2({ ...redactPublishedShare(result), expiryClamped: result.metadata.expiryClamped === true, ...artifact === void 0 || result.metadata.artifact === void 0 ? {} : { artifactId: result.metadata.artifact.id, version: result.metadata.artifact.version, mode: result.metadata.artifact.mode }, ...notification === void 0 ? {} : { notification } });
|
|
26907
|
+
if (json) writeJson2({ ...redactPublishedShare(result), history, expiryClamped: result.metadata.expiryClamped === true, ...artifact === void 0 || result.metadata.artifact === void 0 ? {} : { artifactId: result.metadata.artifact.id, version: result.metadata.artifact.version, mode: result.metadata.artifact.mode }, ...notification === void 0 ? {} : { notification } });
|
|
26882
26908
|
else {
|
|
26883
26909
|
if (result.metadata.artifact !== void 0) process.stderr.write(`Artifact ${result.metadata.artifact.id} v${result.metadata.artifact.version} (${result.metadata.artifact.mode})
|
|
26884
26910
|
`);
|
|
@@ -27252,9 +27278,9 @@ function createEncryptedProfileHistory(profileName, sessionSigner) {
|
|
|
27252
27278
|
const legacyPath = async (profile) => join12(await ProfileManager.getCacheDir(profile), "share-history-v1.bin");
|
|
27253
27279
|
const b64 = (value) => Buffer.from(value).toString("base64url");
|
|
27254
27280
|
const unb64 = (value) => {
|
|
27255
|
-
if (typeof value !== "string" || !/^[A-Za-z0-9_-]+$/.test(value)) throw new
|
|
27281
|
+
if (typeof value !== "string" || !/^[A-Za-z0-9_-]+$/.test(value)) throw new ShareHistoryUnreadableError();
|
|
27256
27282
|
const bytes = new Uint8Array(Buffer.from(value, "base64url"));
|
|
27257
|
-
if (b64(bytes) !== value) throw new
|
|
27283
|
+
if (b64(bytes) !== value) throw new ShareHistoryUnreadableError();
|
|
27258
27284
|
return bytes;
|
|
27259
27285
|
};
|
|
27260
27286
|
const isStoredRecord = (value) => typeof value === "object" && value !== null && "shareId" in value && typeof value.shareId === "string";
|
|
@@ -27283,44 +27309,54 @@ function createEncryptedProfileHistory(profileName, sessionSigner) {
|
|
|
27283
27309
|
const legacyKey = await crypto.subtle.importKey("raw", digest, { name: "AES-GCM" }, false, ["decrypt"]);
|
|
27284
27310
|
return preparedKeys = { profile, identity: snapshot.fingerprint, material, legacyKey };
|
|
27285
27311
|
};
|
|
27286
|
-
const
|
|
27312
|
+
const readIfPresent = async (file) => {
|
|
27287
27313
|
try {
|
|
27288
|
-
|
|
27289
|
-
if (envelope.version !== HISTORY_VERSION) throw new Error("share history is unavailable");
|
|
27290
|
-
const salt = unb64(envelope.kdfSalt);
|
|
27291
|
-
if (salt.length < 16) throw new Error("share history is unavailable");
|
|
27292
|
-
return salt;
|
|
27314
|
+
return await readFile7(file);
|
|
27293
27315
|
} catch (error) {
|
|
27294
|
-
if (error.code === "ENOENT") return
|
|
27295
|
-
throw new
|
|
27316
|
+
if (error.code === "ENOENT") return void 0;
|
|
27317
|
+
throw new ShareHistoryReadError();
|
|
27296
27318
|
}
|
|
27297
27319
|
};
|
|
27298
|
-
const
|
|
27320
|
+
const parseEnvelope = (text) => {
|
|
27321
|
+
let envelope;
|
|
27299
27322
|
try {
|
|
27300
|
-
|
|
27301
|
-
|
|
27302
|
-
|
|
27303
|
-
|
|
27304
|
-
|
|
27305
|
-
|
|
27306
|
-
|
|
27323
|
+
envelope = JSON.parse(text);
|
|
27324
|
+
} catch {
|
|
27325
|
+
throw new ShareHistoryUnreadableError();
|
|
27326
|
+
}
|
|
27327
|
+
if (typeof envelope !== "object" || envelope === null || envelope.version !== HISTORY_VERSION) throw new ShareHistoryUnreadableError();
|
|
27328
|
+
return envelope;
|
|
27329
|
+
};
|
|
27330
|
+
const decryptRecords = async (key, iv, ciphertext) => {
|
|
27331
|
+
try {
|
|
27332
|
+
const bytes = await crypto.subtle.decrypt({ name: "AES-GCM", iv }, key, ciphertext);
|
|
27307
27333
|
const values = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
27308
27334
|
return Array.isArray(values) ? values.filter(isStoredRecord) : [];
|
|
27309
|
-
} catch
|
|
27310
|
-
|
|
27311
|
-
if (error.code !== "ENOENT") throw new Error("share history is unavailable");
|
|
27312
|
-
try {
|
|
27313
|
-
const legacy = new Uint8Array(await readFile7(await legacyPath(profile)));
|
|
27314
|
-
if (legacy.length <= 12) return [];
|
|
27315
|
-
const bytes = await crypto.subtle.decrypt({ name: "AES-GCM", iv: legacy.slice(0, 12) }, ready.legacyKey, legacy.slice(12));
|
|
27316
|
-
const values = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
27317
|
-
return Array.isArray(values) ? values.filter(isStoredRecord) : [];
|
|
27318
|
-
} catch (legacyError) {
|
|
27319
|
-
if (legacyError.code === "ENOENT") return [];
|
|
27320
|
-
throw new Error("share history is unavailable");
|
|
27321
|
-
}
|
|
27335
|
+
} catch {
|
|
27336
|
+
throw new ShareHistoryUnreadableError();
|
|
27322
27337
|
}
|
|
27323
27338
|
};
|
|
27339
|
+
const preparedSalt = async (profile) => {
|
|
27340
|
+
const file = await readIfPresent(await path(profile));
|
|
27341
|
+
if (file === void 0) return crypto.getRandomValues(new Uint8Array(16));
|
|
27342
|
+
const salt = unb64(parseEnvelope(file.toString("utf8")).kdfSalt);
|
|
27343
|
+
if (salt.length < 16) throw new ShareHistoryUnreadableError();
|
|
27344
|
+
return salt;
|
|
27345
|
+
};
|
|
27346
|
+
const read4 = async (profile, ready) => {
|
|
27347
|
+
const file = await readIfPresent(await path(profile));
|
|
27348
|
+
if (file === void 0) {
|
|
27349
|
+
const legacy = await readIfPresent(await legacyPath(profile));
|
|
27350
|
+
if (legacy === void 0 || legacy.length <= 12) return [];
|
|
27351
|
+
return decryptRecords(ready.legacyKey, new Uint8Array(legacy.subarray(0, 12)), new Uint8Array(legacy.subarray(12)));
|
|
27352
|
+
}
|
|
27353
|
+
const envelope = parseEnvelope(file.toString("utf8"));
|
|
27354
|
+
if (envelope.kdfSalt !== b64(ready.salt)) throw saltChanged;
|
|
27355
|
+
const iv = unb64(envelope.iv);
|
|
27356
|
+
const ciphertext = unb64(envelope.ciphertext);
|
|
27357
|
+
if (iv.length !== 12 || ciphertext.length <= 16) throw new ShareHistoryUnreadableError();
|
|
27358
|
+
return decryptRecords(ready.key, iv, ciphertext);
|
|
27359
|
+
};
|
|
27324
27360
|
const write = async (profile, values, ready) => {
|
|
27325
27361
|
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
27326
27362
|
const bytes = new TextEncoder().encode(JSON.stringify(values));
|