@tinycloud/cli 1.2.3-beta.0 → 1.2.3-beta.2
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/README.md +3 -2
- package/bin/tinycloud +3 -0
- package/dist/courier-entry.js +7 -2
- package/dist/courier-entry.js.map +1 -1
- package/dist/index.js +145 -46
- package/dist/index.js.map +1 -1
- package/dist/legacy-entry.js +168 -135
- package/dist/legacy-entry.js.map +1 -1
- package/package.json +8 -7
- package/skills/tc-cli/REFERENCE.md +3 -3
- package/skills/tc-cli/SKILL.md +2 -2
package/dist/index.js
CHANGED
|
@@ -225,10 +225,11 @@ var init_warnings = __esm({
|
|
|
225
225
|
|
|
226
226
|
// src/output/formatter.ts
|
|
227
227
|
import ora from "ora";
|
|
228
|
+
import { CLI_NAME as CLI_NAME2 } from "@tinycloud/operations/cli-name";
|
|
228
229
|
function warningLine(warnings) {
|
|
229
230
|
const which = warnings.map((warning) => `${warning.grantCid ?? "unidentified"}: ${warning.reason}`).join(", ");
|
|
230
231
|
const subject = warnings.length === 1 ? "A stored grant was" : `${warnings.length} stored grants were`;
|
|
231
|
-
return `${subject} not used (${which}). Approve access again when a command needs it; remove the old grant with
|
|
232
|
+
return `${subject} not used (${which}). Approve access again when a command needs it; remove the old grant with ${CLI_NAME2} auth grants remove <cid>.`;
|
|
232
233
|
}
|
|
233
234
|
function outputError(code3, message, hint, details = {}) {
|
|
234
235
|
const { meta, warnings = [], setup, recovery, progress } = details;
|
|
@@ -383,6 +384,7 @@ import { readFileSync, readdirSync } from "fs";
|
|
|
383
384
|
import { join } from "path";
|
|
384
385
|
import { ProfileDeletedError, ProfileLockTimeoutError } from "@tinycloud/operations/state";
|
|
385
386
|
import { isSafeRecovery } from "@tinycloud/operations/recovery";
|
|
387
|
+
import { CLI_NAME as CLI_NAME3 } from "@tinycloud/operations/cli-name";
|
|
386
388
|
import {
|
|
387
389
|
buildSecretRequestUrl,
|
|
388
390
|
isSafeRequestAccessRecovery,
|
|
@@ -439,7 +441,7 @@ function missingPrivateKeyError() {
|
|
|
439
441
|
"AUTH_REQUIRED",
|
|
440
442
|
`Profile "${profileName}" cannot restore its session because its private key material is missing.`,
|
|
441
443
|
ExitCode.AUTH_REQUIRED,
|
|
442
|
-
{ hint: `Sign in again with:
|
|
444
|
+
{ hint: `Sign in again with: ${CLI_NAME3} --profile ${profileName} auth login --method openkey` }
|
|
443
445
|
);
|
|
444
446
|
}
|
|
445
447
|
function storageFullError(error, progress) {
|
|
@@ -530,7 +532,7 @@ function buildAuthHint(error) {
|
|
|
530
532
|
if (!spec) return void 0;
|
|
531
533
|
return [
|
|
532
534
|
"The active session is missing a TinyCloud capability.",
|
|
533
|
-
`Request it with:
|
|
535
|
+
`Request it with: ${CLI_NAME3} auth request --cap '${spec.replaceAll("'", "'\\''")}'`,
|
|
534
536
|
"Then retry the original command."
|
|
535
537
|
].join("\n");
|
|
536
538
|
}
|
|
@@ -573,12 +575,12 @@ function buildNetworkHint() {
|
|
|
573
575
|
const lines = [];
|
|
574
576
|
lines.push(activeHost ? `Active profile "${activeName}" \u2192 ${activeHost}` : `Active profile "${activeName}"`);
|
|
575
577
|
if (others.length === 0) {
|
|
576
|
-
lines.push(`No other profiles configured. Run
|
|
578
|
+
lines.push(`No other profiles configured. Run \`${CLI_NAME3} profile create <name>\` or \`${CLI_NAME3} init\`.`);
|
|
577
579
|
} else {
|
|
578
580
|
lines.push(`Switch to a reachable profile:`);
|
|
579
581
|
const longest = Math.max(...others.map((p) => p.name.length));
|
|
580
582
|
for (const { name, host } of others) {
|
|
581
|
-
lines.push(`
|
|
583
|
+
lines.push(` ${CLI_NAME3} profile switch ${name.padEnd(longest)} # ${host}`);
|
|
582
584
|
}
|
|
583
585
|
}
|
|
584
586
|
lines.push(`Or override per-command with --host or TC_HOST.`);
|
|
@@ -691,6 +693,7 @@ var init_types = __esm({
|
|
|
691
693
|
});
|
|
692
694
|
|
|
693
695
|
// src/lib/space.ts
|
|
696
|
+
import { CLI_NAME as CLI_NAME5 } from "@tinycloud/operations/cli-name";
|
|
694
697
|
function canonicalizeAddress(address) {
|
|
695
698
|
const trimmed = address.trim();
|
|
696
699
|
return trimmed.startsWith("0x") ? `0x${trimmed.slice(2).toLowerCase()}` : trimmed.toLowerCase();
|
|
@@ -732,7 +735,7 @@ function resolveAddress(profile, session) {
|
|
|
732
735
|
}
|
|
733
736
|
throw new CLIError(
|
|
734
737
|
"ADDRESS_UNKNOWN",
|
|
735
|
-
`Cannot determine Ethereum address for profile "${profile.name}". Run
|
|
738
|
+
`Cannot determine Ethereum address for profile "${profile.name}". Run \`${CLI_NAME5} auth login\` to refresh the session.`,
|
|
736
739
|
ExitCode.AUTH_REQUIRED
|
|
737
740
|
);
|
|
738
741
|
}
|
|
@@ -5681,6 +5684,7 @@ var init_permissions = __esm({
|
|
|
5681
5684
|
|
|
5682
5685
|
// src/auth/session-expired.ts
|
|
5683
5686
|
import { signInRecovery } from "@tinycloud/operations/recovery";
|
|
5687
|
+
import { CLI_NAME as CLI_NAME6 } from "@tinycloud/operations/cli-name";
|
|
5684
5688
|
function signInAgainHint(profileName, posture) {
|
|
5685
5689
|
const delegate = posture === "delegate-session";
|
|
5686
5690
|
const { command } = signInRecovery(
|
|
@@ -5688,7 +5692,7 @@ function signInAgainHint(profileName, posture) {
|
|
|
5688
5692
|
delegate ? "delegate-session" : posture === "local-owner-key" ? "local-owner-key" : "owner-openkey"
|
|
5689
5693
|
);
|
|
5690
5694
|
if (command === void 0) {
|
|
5691
|
-
return delegate ? `Have the owner approve a new scoped login for profile ${JSON.stringify(profileName)} with
|
|
5695
|
+
return delegate ? `Have the owner approve a new scoped login for profile ${JSON.stringify(profileName)} with ${CLI_NAME6} auth login --method openkey --paste --manifest <manifest.json>.` : `Sign in again to profile ${JSON.stringify(profileName)} with ${CLI_NAME6} auth login.`;
|
|
5692
5696
|
}
|
|
5693
5697
|
return delegate ? `Have the owner approve a new scoped login: ${command}. Pass the owner's code on stdin, newline-terminated.` : `Sign in again with: ${command}`;
|
|
5694
5698
|
}
|
|
@@ -5726,6 +5730,7 @@ var init_delegation_expiry = __esm({
|
|
|
5726
5730
|
});
|
|
5727
5731
|
|
|
5728
5732
|
// src/lib/replication-log.ts
|
|
5733
|
+
import { CLI_NAME as CLI_NAME7 } from "@tinycloud/operations/cli-name";
|
|
5729
5734
|
import { appendFileSync, chmodSync, mkdirSync, renameSync, statSync, writeFileSync } from "fs";
|
|
5730
5735
|
import { dirname as dirname2, join as join4 } from "path";
|
|
5731
5736
|
function appendReplicationEvent(root, event) {
|
|
@@ -5788,7 +5793,7 @@ function createReplicationEventSink(profileRoot, options) {
|
|
|
5788
5793
|
}
|
|
5789
5794
|
if (event.type === "replication.read" && (event.pendingState === "in_flight" || event.pendingState === "ambiguous") && !options.quiet && !pinnedNotices.has(event.key)) {
|
|
5790
5795
|
pinnedNotices.add(event.key);
|
|
5791
|
-
process.stderr.write(`[replication] ${event.key} reads from the network: an earlier write's outcome is unknown (${event.pendingState}${event.code ? `, ${event.code}` : ""}). Review:
|
|
5796
|
+
process.stderr.write(`[replication] ${event.key} reads from the network: an earlier write's outcome is unknown (${event.pendingState}${event.code ? `, ${event.code}` : ""}). Review: ${CLI_NAME7} replica report
|
|
5792
5797
|
`);
|
|
5793
5798
|
}
|
|
5794
5799
|
};
|
|
@@ -7783,6 +7788,7 @@ var init_dist = __esm({
|
|
|
7783
7788
|
|
|
7784
7789
|
// src/lib/replication-config.ts
|
|
7785
7790
|
import { join as join5 } from "path";
|
|
7791
|
+
import { CLI_NAME as CLI_NAME8 } from "@tinycloud/operations/cli-name";
|
|
7786
7792
|
import { profilePath, withProfileLock as withProfileLock2 } from "@tinycloud/operations/state";
|
|
7787
7793
|
import { sqliteReplicaStorage } from "@tinycloud/node-sdk";
|
|
7788
7794
|
function createCliReplicationConfig(profile, settings, output, enabled) {
|
|
@@ -7797,7 +7803,7 @@ function createCliReplicationConfig(profile, settings, output, enabled) {
|
|
|
7797
7803
|
"INVALID_REPLICATION_PREFIX",
|
|
7798
7804
|
error instanceof Error ? error.message : String(error),
|
|
7799
7805
|
ExitCode.USAGE_ERROR,
|
|
7800
|
-
{ hint: `Replace the saved replication prefix with
|
|
7806
|
+
{ hint: `Replace the saved replication prefix with \`${CLI_NAME8} auth login --replication-prefix ${replacement}\`; purging a replica alone does not change this profile setting.` }
|
|
7801
7807
|
);
|
|
7802
7808
|
}
|
|
7803
7809
|
}
|
|
@@ -7843,6 +7849,7 @@ __export(sdk_exports, {
|
|
|
7843
7849
|
storedSessionVerifiesLocally: () => storedSessionVerifiesLocally
|
|
7844
7850
|
});
|
|
7845
7851
|
import { TinyCloudNode, signedCompactUcanExpiry } from "@tinycloud/node-sdk";
|
|
7852
|
+
import { CLI_NAME as CLI_NAME9 } from "@tinycloud/operations/cli-name";
|
|
7846
7853
|
import { ownerSessionLacksProof } from "@tinycloud/operations/profile";
|
|
7847
7854
|
function jwkHasPrivateParameter(jwk) {
|
|
7848
7855
|
if (!jwk || typeof jwk !== "object") return false;
|
|
@@ -7865,7 +7872,7 @@ function signerJwkForProfile(profileName, sessionJwk, key) {
|
|
|
7865
7872
|
`Profile "${profileName}" cannot restore its session because its private key material is missing.`,
|
|
7866
7873
|
ExitCode.AUTH_REQUIRED,
|
|
7867
7874
|
{
|
|
7868
|
-
hint: `Sign in again with:
|
|
7875
|
+
hint: `Sign in again with: ${CLI_NAME9} --profile ${profileName} auth login --method openkey`
|
|
7869
7876
|
}
|
|
7870
7877
|
);
|
|
7871
7878
|
}
|
|
@@ -7952,7 +7959,7 @@ async function createSDKInstance(ctx, options) {
|
|
|
7952
7959
|
if (!key && !effectivePrivateKey && !(profile?.authMethod === "openkey" && session !== null)) {
|
|
7953
7960
|
throw new CLIError(
|
|
7954
7961
|
"AUTH_REQUIRED",
|
|
7955
|
-
`No key found for profile "${ctx.profile}". Run
|
|
7962
|
+
`No key found for profile "${ctx.profile}". Run \`${CLI_NAME9} --profile ${ctx.profile} auth rotate\` to create a new key and sign in.`,
|
|
7956
7963
|
ExitCode.AUTH_REQUIRED
|
|
7957
7964
|
);
|
|
7958
7965
|
}
|
|
@@ -8071,7 +8078,7 @@ async function bootstrapDelegatedSession(ctx, delegation) {
|
|
|
8071
8078
|
return restoreBeforeBootstrap(ctx.profile, written.previousProfile);
|
|
8072
8079
|
}, { timeoutMs: PROFILE_COMMIT_LOCK_TIMEOUT_MS }).catch((error) => (
|
|
8073
8080
|
// The lock or a read failed: the import's error stays the one reported.
|
|
8074
|
-
`Rolling back the provisional session of profile "${ctx.profile}" could not run (${failureName(error)}); check
|
|
8081
|
+
`Rolling back the provisional session of profile "${ctx.profile}" could not run (${failureName(error)}); check \`${CLI_NAME9} --profile ${ctx.profile} context\`.`
|
|
8075
8082
|
));
|
|
8076
8083
|
throw annotate(reported, note);
|
|
8077
8084
|
};
|
|
@@ -8101,7 +8108,7 @@ async function restoreBeforeBootstrap(profileName, previousProfile) {
|
|
|
8101
8108
|
});
|
|
8102
8109
|
}
|
|
8103
8110
|
if (failures.length === 0) return void 0;
|
|
8104
|
-
return `Rolling back the provisional session of profile "${profileName}" failed too (${failures.join("; ")}); check
|
|
8111
|
+
return `Rolling back the provisional session of profile "${profileName}" failed too (${failures.join("; ")}); check \`${CLI_NAME9} --profile ${profileName} context\`.`;
|
|
8105
8112
|
}
|
|
8106
8113
|
function failureName(error) {
|
|
8107
8114
|
if (error instanceof SyntaxError) return "a profile file is not valid JSON";
|
|
@@ -8125,7 +8132,7 @@ async function ensureAuthenticated(ctx, options) {
|
|
|
8125
8132
|
if (!session) {
|
|
8126
8133
|
throw new CLIError(
|
|
8127
8134
|
"AUTH_REQUIRED",
|
|
8128
|
-
`Not authenticated. Run
|
|
8135
|
+
`Not authenticated. Run \`${CLI_NAME9} auth login\` or \`${CLI_NAME9} init\` first.`,
|
|
8129
8136
|
ExitCode.AUTH_REQUIRED
|
|
8130
8137
|
);
|
|
8131
8138
|
}
|
|
@@ -8162,6 +8169,7 @@ __export(host_exports, {
|
|
|
8162
8169
|
spaceNameFromUri: () => spaceNameFromUri,
|
|
8163
8170
|
unhostedSpaceError: () => unhostedSpaceError
|
|
8164
8171
|
});
|
|
8172
|
+
import { CLI_NAME as CLI_NAME10 } from "@tinycloud/operations/cli-name";
|
|
8165
8173
|
import {
|
|
8166
8174
|
discoverLocalTinyCloudNode
|
|
8167
8175
|
} from "@tinycloud/sdk-core";
|
|
@@ -8255,13 +8263,13 @@ async function unhostedSpaceError(error, spaceUri, profileName) {
|
|
|
8255
8263
|
const owner = await isRootAuthority(spaceUri, profileName);
|
|
8256
8264
|
const hint = owner ? [
|
|
8257
8265
|
"You are the owner. Host it once:",
|
|
8258
|
-
`
|
|
8266
|
+
` ${CLI_NAME10} space host ${spaceName}`,
|
|
8259
8267
|
"Then retry."
|
|
8260
8268
|
].join("\n") : [
|
|
8261
8269
|
"You are a delegate and CANNOT host this space \u2014 only its owner can.",
|
|
8262
8270
|
"Emit a host request:",
|
|
8263
|
-
`
|
|
8264
|
-
|
|
8271
|
+
` ${CLI_NAME10} space host-request ${spaceName} --emit ./host-request.json`,
|
|
8272
|
+
`Send it to the owner; they run \`${CLI_NAME10} space host\` and confirm. Then retry.`
|
|
8265
8273
|
].join("\n");
|
|
8266
8274
|
const message = owner ? `Space '${spaceName}' (${spaceUri}) is not hosted.` : `Space '${spaceName}' (owner ${ownerDidFromSpaceUri(spaceUri) ?? spaceUri}) is not hosted.`;
|
|
8267
8275
|
return new CLIError("SPACE_NOT_HOSTED", message, ExitCode.ERROR, { hint });
|
|
@@ -8286,6 +8294,7 @@ var init_host = __esm({
|
|
|
8286
8294
|
|
|
8287
8295
|
// src/config/profiles.ts
|
|
8288
8296
|
import { chmod as chmod2, lstat, readdir as readdir2, rm as rm2, rmdir } from "fs/promises";
|
|
8297
|
+
import { CLI_NAME as CLI_NAME11 } from "@tinycloud/operations/cli-name";
|
|
8289
8298
|
import { join as join6 } from "path";
|
|
8290
8299
|
import {
|
|
8291
8300
|
profilePath as profilePath2,
|
|
@@ -8371,7 +8380,7 @@ var init_profiles = __esm({
|
|
|
8371
8380
|
if (!profile) {
|
|
8372
8381
|
throw new CLIError(
|
|
8373
8382
|
"PROFILE_NOT_FOUND",
|
|
8374
|
-
`Profile "${name}" does not exist. Create it with
|
|
8383
|
+
`Profile "${name}" does not exist. Create it with \`${CLI_NAME11} init --name ${name}\`.`
|
|
8375
8384
|
);
|
|
8376
8385
|
}
|
|
8377
8386
|
return profile;
|
|
@@ -8429,7 +8438,7 @@ var init_profiles = __esm({
|
|
|
8429
8438
|
if (config.defaultProfile === name) {
|
|
8430
8439
|
throw new CLIError(
|
|
8431
8440
|
"PROFILE_DELETE_DEFAULT",
|
|
8432
|
-
`Cannot delete the default profile "${name}". Change the default first with
|
|
8441
|
+
`Cannot delete the default profile "${name}". Change the default first with \`${CLI_NAME11} profile default <other>\`.`
|
|
8433
8442
|
);
|
|
8434
8443
|
}
|
|
8435
8444
|
const kind = await lstat(profileDir).then((stats) => stats.isSymbolicLink() ? "link" : "present", (error) => {
|
|
@@ -8566,15 +8575,18 @@ var init_profiles = __esm({
|
|
|
8566
8575
|
});
|
|
8567
8576
|
|
|
8568
8577
|
// src/commands/courier.ts
|
|
8569
|
-
|
|
8578
|
+
import { CLI_NAME } from "@tinycloud/operations/cli-name";
|
|
8579
|
+
var TC_OPTIONS_BEFORE_COURIER = `${CLI_NAME} courier takes no ${CLI_NAME} options and does not use ${CLI_NAME} profiles; Courier keeps its own profile in $COURIER_HOME (default ~/.courier). Put flags after "courier", e.g. ${CLI_NAME} courier init --host <url>.`;
|
|
8570
8580
|
|
|
8571
8581
|
// src/index.ts
|
|
8572
8582
|
init_errors();
|
|
8573
8583
|
import { readFileSync as readFileSync2 } from "fs";
|
|
8584
|
+
import { CLI_NAME as CLI_NAME13 } from "@tinycloud/operations/cli-name";
|
|
8574
8585
|
import { Command } from "commander";
|
|
8575
8586
|
|
|
8576
8587
|
// src/output/banner.ts
|
|
8577
8588
|
init_formatter();
|
|
8589
|
+
import { CLI_NAME as CLI_NAME4 } from "@tinycloud/operations/cli-name";
|
|
8578
8590
|
|
|
8579
8591
|
// src/output/taglines.ts
|
|
8580
8592
|
var HOLIDAY_TAGLINES = [
|
|
@@ -8645,14 +8657,14 @@ function resolveCommitHash() {
|
|
|
8645
8657
|
function formatBannerLine(version2) {
|
|
8646
8658
|
const commit = resolveCommitHash();
|
|
8647
8659
|
const tagline = pickTagline();
|
|
8648
|
-
const versionPart =
|
|
8660
|
+
const versionPart = `${CLI_NAME4} v${version2}`;
|
|
8649
8661
|
const commitPart = commit ? ` (${commit})` : "";
|
|
8650
8662
|
const separator = " \u2014 ";
|
|
8651
8663
|
if (!isInteractive()) {
|
|
8652
8664
|
return `${versionPart}${commitPart}${separator}${tagline}`;
|
|
8653
8665
|
}
|
|
8654
8666
|
return [
|
|
8655
|
-
theme.brand(
|
|
8667
|
+
theme.brand(`\u2601\uFE0F ${CLI_NAME4}`),
|
|
8656
8668
|
" ",
|
|
8657
8669
|
theme.muted(`v${version2}`),
|
|
8658
8670
|
commit ? theme.dim(` (${commit})`) : "",
|
|
@@ -8674,6 +8686,9 @@ init_theme();
|
|
|
8674
8686
|
init_formatter();
|
|
8675
8687
|
init_profiles();
|
|
8676
8688
|
|
|
8689
|
+
// src/commands/share.ts
|
|
8690
|
+
import { CLI_NAME as CLI_NAME12 } from "@tinycloud/operations/cli-name";
|
|
8691
|
+
|
|
8677
8692
|
// ../share-sdk/dist/index.js
|
|
8678
8693
|
init_sha2();
|
|
8679
8694
|
init_ed25519();
|
|
@@ -15973,7 +15988,7 @@ async function revokeShare(input) {
|
|
|
15973
15988
|
if (delegationCid === void 0) return { state: "unsupported", target, reason: "share has no node-enforced delegation receipt", code: "unsupported-target" };
|
|
15974
15989
|
if (target === "bearer") {
|
|
15975
15990
|
if (input.adapter.revokeDelegation === void 0) return { state: "unsupported", target, reason: "native delegation revocation authority is required", code: "unsupported-target" };
|
|
15976
|
-
await input.adapter.revokeDelegation({ delegationCid, scope });
|
|
15991
|
+
await input.adapter.revokeDelegation({ delegationCid, scope, record: input.record });
|
|
15977
15992
|
} else {
|
|
15978
15993
|
if (input.record.ownerDid === void 0) return { state: "unsupported", target, reason: "share has no Policy/v3 owner receipt", code: "unsupported-target" };
|
|
15979
15994
|
if (input.adapter.revokePolicyRoot === void 0) return { state: "unsupported", target, reason: "Policy/v3 root revocation authority is required", code: "unsupported-target" };
|
|
@@ -15985,11 +16000,14 @@ async function revokeShare(input) {
|
|
|
15985
16000
|
nodeAudience: policyNodeAudience(input.record)
|
|
15986
16001
|
});
|
|
15987
16002
|
}
|
|
15988
|
-
|
|
16003
|
+
let revokedAt = input.record.revokedAt ?? (input.now?.() ?? /* @__PURE__ */ new Date()).toISOString();
|
|
15989
16004
|
if (input.records?.update !== void 0) {
|
|
15990
|
-
await input.records.update(input.record.shareId, (record) => ({ ...record, revokedAt }));
|
|
16005
|
+
const updated = await input.records.update(input.record.shareId, (record) => ({ ...record, revokedAt: record.revokedAt ?? revokedAt }));
|
|
16006
|
+
revokedAt = updated?.revokedAt ?? revokedAt;
|
|
15991
16007
|
} else if (input.records !== void 0) {
|
|
15992
|
-
await input.records.
|
|
16008
|
+
const latest = await input.records.get(input.record.shareId);
|
|
16009
|
+
revokedAt = latest?.revokedAt ?? revokedAt;
|
|
16010
|
+
await input.records.put({ ...latest ?? input.record, revokedAt });
|
|
15993
16011
|
}
|
|
15994
16012
|
return { state: "revoked", target, delegationCid, revokedAt };
|
|
15995
16013
|
}
|
|
@@ -17680,12 +17698,12 @@ function shareCliError(error, operation = "publish") {
|
|
|
17680
17698
|
const profileName = "profileName" in failure ? failure.profileName : void 0;
|
|
17681
17699
|
const localKey = "localKey" in failure && failure.localKey === true;
|
|
17682
17700
|
const profileHint = profileName === void 0 ? "" : `--profile ${profileName} `;
|
|
17683
|
-
const loginHint = localKey ?
|
|
17701
|
+
const loginHint = localKey ? `\`${CLI_NAME12} ${profileHint}auth login --method local\`` : `\`${CLI_NAME12} ${profileHint}auth login --device --manifest builtin:share-publishing\` (or \`${CLI_NAME12} ${profileHint}enable share\`)`;
|
|
17684
17702
|
if (failure.kind === "caveated-session") {
|
|
17685
17703
|
const holder = profileName === void 0 ? "this session" : `profile ${profileName}'s session`;
|
|
17686
17704
|
return new CLIError(
|
|
17687
17705
|
"PERMISSION_DENIED",
|
|
17688
|
-
`${holder} carries signed restrictions (caveats) on the authority an anyone-with-link share needs, so it cannot create the share link; nothing was shared. Approve Share publishing without restrictions on a new, dedicated profile (any unused name):
|
|
17706
|
+
`${holder} carries signed restrictions (caveats) on the authority an anyone-with-link share needs, so it cannot create the share link; nothing was shared. Approve Share publishing without restrictions on a new, dedicated profile (any unused name): \`${CLI_NAME12} init --name publisher --key-only && ${CLI_NAME12} --profile publisher enable share\``,
|
|
17689
17707
|
5
|
|
17690
17708
|
);
|
|
17691
17709
|
}
|
|
@@ -18019,7 +18037,7 @@ function registerShareCommand(program2) {
|
|
|
18019
18037
|
return handleError(shareCliError(error, "notify"));
|
|
18020
18038
|
}
|
|
18021
18039
|
});
|
|
18022
|
-
share.command("revoke <id>").description("Revoke addressed
|
|
18040
|
+
share.command("revoke <id>").description("Revoke a bearer or addressed share this profile created").option("--ancestor", "Revoke the owner delegation ancestry").option("--json", "Print versioned JSON").action(async (id, options, command) => {
|
|
18023
18041
|
try {
|
|
18024
18042
|
const json = jsonOutput(options, command);
|
|
18025
18043
|
if (shareServices.records === void 0) throw new CLIError("AUTH_REQUIRED", "sender history storage is not configured", 3);
|
|
@@ -18042,12 +18060,21 @@ function registerShareCommand(program2) {
|
|
|
18042
18060
|
init_storage();
|
|
18043
18061
|
init_profiles();
|
|
18044
18062
|
init_constants();
|
|
18063
|
+
init_errors();
|
|
18045
18064
|
init_session_expired();
|
|
18046
18065
|
import { readFile as readFile4 } from "fs/promises";
|
|
18047
18066
|
import { join as join8 } from "path";
|
|
18048
18067
|
import { createHash } from "crypto";
|
|
18049
18068
|
import { writeJsonAtomic as writeJsonAtomic2 } from "@tinycloud/operations/state";
|
|
18050
|
-
import {
|
|
18069
|
+
import { withoutBearer } from "@tinycloud/operations/delegation-binding";
|
|
18070
|
+
import {
|
|
18071
|
+
LocationRecordValidationError,
|
|
18072
|
+
LocationRegistryHttpError,
|
|
18073
|
+
parseSignedCompactUcanAttenuation,
|
|
18074
|
+
parseSignedSiweCacao,
|
|
18075
|
+
revokePolicyRootV3,
|
|
18076
|
+
signCompactUcanAuthorization
|
|
18077
|
+
} from "@tinycloud/sdk-core";
|
|
18051
18078
|
import { extractSiweExpiration, InvalidRestoredSessionError } from "@tinycloud/node-sdk";
|
|
18052
18079
|
function requiredKvAction(meta) {
|
|
18053
18080
|
if (meta === null || typeof meta !== "object" || !("requiredAction" in meta)) return void 0;
|
|
@@ -18641,9 +18668,80 @@ function createShareAuthorityAdapters(input = {}) {
|
|
|
18641
18668
|
return "delivered";
|
|
18642
18669
|
}) };
|
|
18643
18670
|
const revocation = {
|
|
18644
|
-
|
|
18645
|
-
|
|
18646
|
-
|
|
18671
|
+
// The node authorizes a revocation signed by the delegation's delegator,
|
|
18672
|
+
// or by the owner of a space it targets. A bearer link is minted by the
|
|
18673
|
+
// session key, or by the owner wallet when it outlives the session, so a
|
|
18674
|
+
// proofless session-key revocation covers the first, and a local owner
|
|
18675
|
+
// key covers both. Without either key (a session-only profile that signed
|
|
18676
|
+
// in again since, for example) the link cannot be revoked here.
|
|
18677
|
+
revokeDelegation: input.revokeDelegation ?? (async ({ delegationCid, record }) => {
|
|
18678
|
+
const node = await authenticatedNode();
|
|
18679
|
+
const activeNode = await node.activeNodeIdentity();
|
|
18680
|
+
if (record.target.origin !== activeNode.origin) throw new Error("share delegation revocation is not bound to the active owner node");
|
|
18681
|
+
const linkUnavailable = new CLIError("SHARE_LINK_UNAVAILABLE", "this share's stored link could not be verified; nothing was revoked", ExitCode.NOT_FOUND);
|
|
18682
|
+
const resource = `${record.target.spaceId}/kv/${record.resource.path}`;
|
|
18683
|
+
let sessionIssued;
|
|
18684
|
+
try {
|
|
18685
|
+
const decoded = node.sharing.decodeLink(parseNativeShareUrl(record.link ?? ""));
|
|
18686
|
+
const linkKey = decoded.keyDid.split("#", 1)[0];
|
|
18687
|
+
const authorization2 = withoutBearer(decoded.delegation.authHeader ?? "");
|
|
18688
|
+
if (authorization2.split(".").length === 3) {
|
|
18689
|
+
const signed = parseSignedCompactUcanAttenuation(authorization2, delegationCid);
|
|
18690
|
+
if (signed.payload.aud.split("#", 1)[0] !== linkKey || signed.payload.att[resource] === void 0) throw linkUnavailable;
|
|
18691
|
+
sessionIssued = signed.payload.iss.split("#", 1)[0] === node.credentialHolderDid;
|
|
18692
|
+
} else {
|
|
18693
|
+
const signed = await parseSignedSiweCacao(authorization2, delegationCid);
|
|
18694
|
+
const owner = /^tinycloud:pkh:(eip155:[1-9]\d*:0x[0-9a-fA-F]{40}):/.exec(record.target.spaceId)?.[1];
|
|
18695
|
+
if (owner === void 0 || signed.issuer.toLowerCase() !== `did:pkh:${owner}`.toLowerCase() || signed.audience !== linkKey || signed.attenuation[resource] === void 0) throw linkUnavailable;
|
|
18696
|
+
sessionIssued = false;
|
|
18697
|
+
}
|
|
18698
|
+
} catch {
|
|
18699
|
+
throw linkUnavailable;
|
|
18700
|
+
}
|
|
18701
|
+
if (delegationCid === node.session?.delegationCid) throw linkUnavailable;
|
|
18702
|
+
let authorization;
|
|
18703
|
+
if (sessionIssued) {
|
|
18704
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
18705
|
+
authorization = (await signCompactUcanAuthorization({
|
|
18706
|
+
issuerDid: node.sessionDid,
|
|
18707
|
+
audienceDid: activeNode.nodeDid,
|
|
18708
|
+
attenuation: { [`urn:cid:${delegationCid}`]: { "tinycloud.delegation/revoke": [{}] } },
|
|
18709
|
+
facts: [{}],
|
|
18710
|
+
proofs: [],
|
|
18711
|
+
notBefore: now,
|
|
18712
|
+
expiresAt: now + 60,
|
|
18713
|
+
nonce: `urn:uuid:${crypto.randomUUID()}`,
|
|
18714
|
+
sign: (bytes) => node.signSessionBytes(bytes)
|
|
18715
|
+
})).authorization;
|
|
18716
|
+
} else if (!node.isSessionOnly) {
|
|
18717
|
+
authorization = await node.signOwnerRevocation(delegationCid);
|
|
18718
|
+
} else {
|
|
18719
|
+
throw new CLIError("SHARE_ISSUER_KEY_UNAVAILABLE", "the key that created this share link is not available to this profile; nothing was revoked", ExitCode.PERMISSION_DENIED);
|
|
18720
|
+
}
|
|
18721
|
+
const response = await fetchFn(`${activeNode.origin}/revoke`, { method: "POST", headers: { Authorization: authorization } });
|
|
18722
|
+
const body = await response.text();
|
|
18723
|
+
if (response.ok) {
|
|
18724
|
+
let receipt;
|
|
18725
|
+
try {
|
|
18726
|
+
receipt = JSON.parse(body);
|
|
18727
|
+
} catch {
|
|
18728
|
+
receipt = void 0;
|
|
18729
|
+
}
|
|
18730
|
+
if (typeof receipt === "object" && receipt !== null && "revoked" in receipt && receipt.revoked === true && "cid" in receipt && receipt.cid === delegationCid) return;
|
|
18731
|
+
throw new CLIError(
|
|
18732
|
+
"SHARE_REVOKE_UNCONFIRMED",
|
|
18733
|
+
`the node answered the share revocation with HTTP ${response.status} but did not confirm it; the link may already be revoked. Check whether it still opens before retrying`,
|
|
18734
|
+
ExitCode.ERROR,
|
|
18735
|
+
{ status: response.status }
|
|
18736
|
+
);
|
|
18737
|
+
}
|
|
18738
|
+
const nodeMessage = body.replace(/[\u0000-\u001f\u007f]+/g, " ").trim().slice(0, 200);
|
|
18739
|
+
throw new CLIError(
|
|
18740
|
+
"SHARE_REVOKE_REJECTED",
|
|
18741
|
+
`the node rejected the share revocation (HTTP ${response.status}${nodeMessage === "" ? "" : `: ${nodeMessage}`}); nothing was revoked`,
|
|
18742
|
+
ExitCode.NOT_FOUND,
|
|
18743
|
+
{ status: response.status }
|
|
18744
|
+
);
|
|
18647
18745
|
}),
|
|
18648
18746
|
revokePolicyRoot: input.revokePolicyRoot ?? (async (request) => {
|
|
18649
18747
|
const node = await authenticatedNode();
|
|
@@ -18720,7 +18818,7 @@ function selectedShareProfile() {
|
|
|
18720
18818
|
}
|
|
18721
18819
|
return process.env.TC_PROFILE;
|
|
18722
18820
|
}
|
|
18723
|
-
program.name(
|
|
18821
|
+
program.name(CLI_NAME13).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").option("--replication", "Enable read-through replication for this invocation").option("--no-replication", "Disable read-through replication for this invocation").option("--replication-debug", "Write replication diagnostics to stderr");
|
|
18724
18822
|
program.hook("preAction", async (thisCommand) => {
|
|
18725
18823
|
const opts = thisCommand.optsWithGlobals();
|
|
18726
18824
|
setJsonOutputRequested(opts.json === true);
|
|
@@ -18730,19 +18828,20 @@ program.hook("preAction", async (thisCommand) => {
|
|
|
18730
18828
|
emitBanner(version);
|
|
18731
18829
|
}
|
|
18732
18830
|
const commandName = thisCommand.name();
|
|
18733
|
-
const
|
|
18734
|
-
const
|
|
18831
|
+
const isRootCommand = thisCommand === program;
|
|
18832
|
+
const fullCommand = parentName && thisCommand.parent !== program ? `${parentName} ${commandName}` : commandName;
|
|
18833
|
+
const skipGuard = isRootCommand || ["init", "doctor", "completion", "help", "upgrade", "status"].includes(commandName) || fullCommand === "profile create";
|
|
18735
18834
|
if (!skipGuard && !opts.quiet && !shouldOutputJson()) {
|
|
18736
18835
|
try {
|
|
18737
18836
|
const config = await ProfileManager.getConfig();
|
|
18738
18837
|
const profileName = opts.profile || config.defaultProfile;
|
|
18739
18838
|
const hasProfile = await ProfileManager.profileExists(profileName);
|
|
18740
18839
|
if (!hasProfile) {
|
|
18741
|
-
process.stderr.write(theme.warn("\u26A0 No profile configured.") + " " + theme.muted(
|
|
18840
|
+
process.stderr.write(theme.warn("\u26A0 No profile configured.") + " " + theme.muted(`Run: ${CLI_NAME13} init`) + "\n\n");
|
|
18742
18841
|
} else {
|
|
18743
18842
|
const key = await ProfileManager.getKey(profileName);
|
|
18744
18843
|
if (!key) {
|
|
18745
|
-
process.stderr.write(theme.warn("\u26A0 No key found.") + " " + theme.muted(
|
|
18844
|
+
process.stderr.write(theme.warn("\u26A0 No key found.") + " " + theme.muted(`Run: ${CLI_NAME13} init`) + "\n\n");
|
|
18746
18845
|
}
|
|
18747
18846
|
}
|
|
18748
18847
|
} catch {
|
|
@@ -18789,14 +18888,14 @@ program.addHelpText("afterAll", () => {
|
|
|
18789
18888
|
if (!process.stdout.isTTY) return "";
|
|
18790
18889
|
return `
|
|
18791
18890
|
${theme.heading("Examples:")}
|
|
18792
|
-
${theme.command(
|
|
18793
|
-
${theme.command(
|
|
18794
|
-
${theme.command(
|
|
18795
|
-
${theme.command(
|
|
18796
|
-
${theme.command(
|
|
18797
|
-
${theme.command(
|
|
18798
|
-
${theme.command(
|
|
18799
|
-
${theme.command(
|
|
18891
|
+
${theme.command(`${CLI_NAME13} init`)} ${theme.muted("Set up a profile and generate keys")}
|
|
18892
|
+
${theme.command(`${CLI_NAME13} auth login`)} ${theme.muted("Authenticate via browser")}
|
|
18893
|
+
${theme.command(`${CLI_NAME13} kv put greeting "Hello"`)} ${theme.muted("Store a value")}
|
|
18894
|
+
${theme.command(`${CLI_NAME13} kv list`)} ${theme.muted("List all keys")}
|
|
18895
|
+
${theme.command(`${CLI_NAME13} secrets network init`)} ${theme.muted("Create the default secrets network")}
|
|
18896
|
+
${theme.command(`${CLI_NAME13} account apps list`)} ${theme.muted("List registered account apps")}
|
|
18897
|
+
${theme.command(`${CLI_NAME13} delegation create --to did:pkh:...`)} ${theme.muted("Grant access to another user")}
|
|
18898
|
+
${theme.command(`${CLI_NAME13} space list`)} ${theme.muted("Show your spaces")}
|
|
18800
18899
|
|
|
18801
18900
|
${theme.muted("Docs:")} ${theme.accent("https://docs.tinycloud.xyz/cli")}
|
|
18802
18901
|
${theme.muted("Repo:")} ${theme.accent("https://github.com/tinycloudlabs/web-sdk")}
|