@tinycloud/cli 1.3.0-beta.41 → 1.3.0-beta.43
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/courier-entry.js +5519 -1073
- package/dist/courier-entry.js.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/legacy-entry.js +94 -12
- package/dist/legacy-entry.js.map +1 -1
- package/package.json +3 -3
- package/skills/courier/SKILL.md +94 -8
- package/skills/tc-cli/REFERENCE.md +2 -0
package/dist/legacy-entry.js
CHANGED
|
@@ -610,6 +610,7 @@ import { join as join2 } from "path";
|
|
|
610
610
|
import { ProfileDeletedError, ProfileLockTimeoutError, ProfileSymlinkError } from "@tinycloud/operations/state";
|
|
611
611
|
import { isSafeRecovery } from "@tinycloud/operations/recovery";
|
|
612
612
|
import { CLI_NAME as CLI_NAME2 } from "@tinycloud/operations/cli-name";
|
|
613
|
+
import { isRevokedGrantRefusal, REVOKED_GRANT_MESSAGE } from "@tinycloud/operations/revoked-grant";
|
|
613
614
|
import {
|
|
614
615
|
buildSecretRequestUrl,
|
|
615
616
|
isSafeRequestAccessRecovery,
|
|
@@ -637,6 +638,9 @@ function cliErrorFromService(error, message = error.message) {
|
|
|
637
638
|
const missingCapability = validatedCapabilityOf({ ...error, meta }) !== void 0;
|
|
638
639
|
const denied = verdict === "forbidden" || verdict === "unauthenticated" && missingCapability || verdict === void 0 && error.code === "PERMISSION_DENIED";
|
|
639
640
|
const unauthenticated = !denied && (verdict === "unauthenticated" || verdict === void 0 && error.code === "AUTH_REQUIRED");
|
|
641
|
+
if (unauthenticated && isRevokedGrantRefusal({ ...error, meta, message })) {
|
|
642
|
+
return new CLIError("GRANT_REVOKED", REVOKED_GRANT_MESSAGE, GRANT_REVOKED_EXIT_CODE, { status: 401 });
|
|
643
|
+
}
|
|
640
644
|
return new CLIError(
|
|
641
645
|
denied ? "PERMISSION_DENIED" : unauthenticated ? "AUTH_REQUIRED" : error.code,
|
|
642
646
|
message,
|
|
@@ -862,7 +866,7 @@ function buildNetworkHint() {
|
|
|
862
866
|
lines.push(`Or override per-command with --host or TC_HOST.`);
|
|
863
867
|
return lines.join("\n");
|
|
864
868
|
}
|
|
865
|
-
var activeProfileName, CLIError, SETUP_KEYS;
|
|
869
|
+
var activeProfileName, GRANT_REVOKED_EXIT_CODE, CLIError, SETUP_KEYS;
|
|
866
870
|
var init_errors = __esm({
|
|
867
871
|
"src/output/errors.ts"() {
|
|
868
872
|
"use strict";
|
|
@@ -871,6 +875,7 @@ var init_errors = __esm({
|
|
|
871
875
|
init_formatter();
|
|
872
876
|
init_storage2();
|
|
873
877
|
init_warnings();
|
|
878
|
+
GRANT_REVOKED_EXIT_CODE = ExitCode.PERMISSION_DENIED;
|
|
874
879
|
CLIError = class extends Error {
|
|
875
880
|
constructor(code4, message, exitCode = ExitCode.ERROR, metadata) {
|
|
876
881
|
super(message);
|
|
@@ -12360,7 +12365,75 @@ import { CLI_NAME as CLI_NAME13 } from "@tinycloud/operations/cli-name";
|
|
|
12360
12365
|
import { parseSignedCompactUcanAttenuation as parseSignedCompactUcanAttenuation2 } from "@tinycloud/sdk-core";
|
|
12361
12366
|
import { inspectStoredDelegation, withoutBearer as withoutBearer2 } from "@tinycloud/operations/delegation-binding";
|
|
12362
12367
|
init_permissions();
|
|
12368
|
+
|
|
12369
|
+
// src/lib/revoke-node-record.ts
|
|
12370
|
+
var PAGE_LIMIT = 100;
|
|
12371
|
+
var MAX_PAGES = 50;
|
|
12372
|
+
var MAX_RECORDS = PAGE_LIMIT * MAX_PAGES;
|
|
12373
|
+
var MAX_CHAIN_DEPTH = 8;
|
|
12363
12374
|
function normalizeDid(input) {
|
|
12375
|
+
const fragment = input.indexOf("#");
|
|
12376
|
+
return (fragment < 0 ? input : input.slice(0, fragment)).trim().toLowerCase();
|
|
12377
|
+
}
|
|
12378
|
+
var SPACE = /^tinycloud:pkh:eip155:([1-9]\d{0,17}):(0x[0-9a-fA-F]{40}):[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
12379
|
+
function ownerOfSpace(space) {
|
|
12380
|
+
const match = SPACE.exec(space);
|
|
12381
|
+
return match === null ? void 0 : `did:pkh:eip155:${match[1]}:${match[2].toLowerCase()}`;
|
|
12382
|
+
}
|
|
12383
|
+
function ownerSpaceFromNodeRecords(records, cid, ownerDid, sessionDid) {
|
|
12384
|
+
if (records.length > MAX_RECORDS) return void 0;
|
|
12385
|
+
const matches = records.filter((record3) => record3.cid === cid);
|
|
12386
|
+
const target = matches[0];
|
|
12387
|
+
if (!target || new Set(matches.map(({ delegatorDid, delegateDid, resources }) => JSON.stringify([delegatorDid, delegateDid, resources]))).size !== 1) return void 0;
|
|
12388
|
+
const owner = normalizeDid(ownerDid);
|
|
12389
|
+
if (target.resources.length === 0) return void 0;
|
|
12390
|
+
const spaces = /* @__PURE__ */ new Set();
|
|
12391
|
+
for (const { resource } of target.resources) {
|
|
12392
|
+
const space = /^(tinycloud:[^/]+)\//.exec(resource)?.[1];
|
|
12393
|
+
if (!space || ownerOfSpace(space) !== owner) return void 0;
|
|
12394
|
+
spaces.add(space);
|
|
12395
|
+
}
|
|
12396
|
+
if (spaces.size !== 1) return void 0;
|
|
12397
|
+
const session = sessionDid === void 0 ? void 0 : normalizeDid(sessionDid);
|
|
12398
|
+
const byCid = /* @__PURE__ */ new Map();
|
|
12399
|
+
for (const record3 of records) {
|
|
12400
|
+
const sameCid = byCid.get(record3.cid);
|
|
12401
|
+
if (sameCid === void 0) byCid.set(record3.cid, [record3]);
|
|
12402
|
+
else sameCid.push(record3);
|
|
12403
|
+
}
|
|
12404
|
+
const verdicts = /* @__PURE__ */ new Map();
|
|
12405
|
+
const rooted = (record3, depth) => {
|
|
12406
|
+
const issuer = normalizeDid(record3.delegatorDid);
|
|
12407
|
+
if (issuer === owner || issuer === session) return true;
|
|
12408
|
+
const known = verdicts.get(record3.cid);
|
|
12409
|
+
if (known !== void 0) return known;
|
|
12410
|
+
verdicts.set(record3.cid, false);
|
|
12411
|
+
const verdict = depth < MAX_CHAIN_DEPTH && record3.parents.some((parentCid) => (byCid.get(parentCid) ?? []).some((parent) => normalizeDid(parent.delegateDid) === issuer && rooted(parent, depth + 1)));
|
|
12412
|
+
verdicts.set(record3.cid, verdict);
|
|
12413
|
+
return verdict;
|
|
12414
|
+
};
|
|
12415
|
+
return rooted(target, 0) ? [...spaces][0] : void 0;
|
|
12416
|
+
}
|
|
12417
|
+
async function lookupOwnerSpaceOnNode(node, cid, ownerDid) {
|
|
12418
|
+
const records = [];
|
|
12419
|
+
let cursor;
|
|
12420
|
+
for (let page = 0; page < MAX_PAGES; page += 1) {
|
|
12421
|
+
const query = await node.delegationManager.query({
|
|
12422
|
+
direction: "all",
|
|
12423
|
+
limit: PAGE_LIMIT,
|
|
12424
|
+
...cursor === void 0 ? {} : { cursor }
|
|
12425
|
+
});
|
|
12426
|
+
if (!query.ok) return void 0;
|
|
12427
|
+
if (query.data.items.length > PAGE_LIMIT || records.length + query.data.items.length > MAX_RECORDS) return void 0;
|
|
12428
|
+
for (const item of query.data.items) records.push(item);
|
|
12429
|
+
cursor = query.data.nextCursor;
|
|
12430
|
+
if (cursor === void 0) return ownerSpaceFromNodeRecords(records, cid, ownerDid, node.sessionDid);
|
|
12431
|
+
}
|
|
12432
|
+
return void 0;
|
|
12433
|
+
}
|
|
12434
|
+
|
|
12435
|
+
// src/lib/revoke-delegation.ts
|
|
12436
|
+
function normalizeDid2(input) {
|
|
12364
12437
|
const trimmed = input.trim();
|
|
12365
12438
|
const fragment = trimmed.indexOf("#");
|
|
12366
12439
|
return (fragment < 0 ? trimmed : trimmed.slice(0, fragment)).toLowerCase();
|
|
@@ -12379,7 +12452,7 @@ async function cidBoundOwnerSpace(record3, ownerDid, node, host) {
|
|
|
12379
12452
|
const signed = parseSignedCompactUcanAttenuation2(withoutBearer2(authorization), delegation.cid);
|
|
12380
12453
|
for (const resource of Object.keys(signed.payload.att)) {
|
|
12381
12454
|
const space = /^(tinycloud:[^/]+)\/[^/]+(?:\/.*)?$/.exec(resource)?.[1];
|
|
12382
|
-
if (space && ownerDidFromSpace(space) &&
|
|
12455
|
+
if (space && ownerDidFromSpace(space) && normalizeDid2(ownerDidFromSpace(space)) === normalizeDid2(ownerDid)) {
|
|
12383
12456
|
return space;
|
|
12384
12457
|
}
|
|
12385
12458
|
}
|
|
@@ -12393,7 +12466,7 @@ async function cidBoundOwnerSpace(record3, ownerDid, node, host) {
|
|
|
12393
12466
|
if (inspected.local !== "verified" || inspected.cid !== delegation.cid) return void 0;
|
|
12394
12467
|
for (const permission of inspected.effectivePermissions) {
|
|
12395
12468
|
const space = permission.space;
|
|
12396
|
-
if (space && ownerDidFromSpace(space) &&
|
|
12469
|
+
if (space && ownerDidFromSpace(space) && normalizeDid2(ownerDidFromSpace(space)) === normalizeDid2(ownerDid)) {
|
|
12397
12470
|
return space;
|
|
12398
12471
|
}
|
|
12399
12472
|
}
|
|
@@ -12461,7 +12534,11 @@ async function revokeDelegationOnNode(params) {
|
|
|
12461
12534
|
host: ctx.host
|
|
12462
12535
|
});
|
|
12463
12536
|
if (!boundSpace) {
|
|
12464
|
-
throw new CLIError(
|
|
12537
|
+
throw new CLIError(
|
|
12538
|
+
"TARGET_NOT_FOUND",
|
|
12539
|
+
`This profile cannot list the owner's delegations on the node, so it cannot look up "${cid}", and it has no local record of that grant. Use the profile that created the grant, or revoke it from the Delegations screen of the account site (https://account.tinycloud.xyz).`,
|
|
12540
|
+
ExitCode.NOT_FOUND
|
|
12541
|
+
);
|
|
12465
12542
|
}
|
|
12466
12543
|
targetSpaceSource = boundSpace.source;
|
|
12467
12544
|
} else {
|
|
@@ -12492,24 +12569,29 @@ async function revokeDelegationOnNode(params) {
|
|
|
12492
12569
|
if (error === null || typeof error !== "object" || !("code" in error) || error.code !== "RAW_RECAP_RESOURCE_UNSUPPORTED") {
|
|
12493
12570
|
throw error;
|
|
12494
12571
|
}
|
|
12495
|
-
const
|
|
12572
|
+
const ownerDid = profile.ownerDid ?? ownerDidFromSpace(node.accountSpaceId) ?? ownerDidFromSpace(profile.spaceId);
|
|
12573
|
+
let fallback = boundSpace ?? await resolveCidBoundTargetSpace({
|
|
12496
12574
|
profileName: ctx.profile,
|
|
12497
|
-
ownerDid
|
|
12575
|
+
ownerDid,
|
|
12498
12576
|
cid,
|
|
12499
12577
|
node,
|
|
12500
12578
|
host: ctx.host,
|
|
12501
12579
|
localTarget: params.localTarget
|
|
12502
12580
|
});
|
|
12581
|
+
if (!fallback && ownerDid) {
|
|
12582
|
+
const space = await lookupOwnerSpaceOnNode(node, cid, ownerDid);
|
|
12583
|
+
if (space) fallback = { space, source: "node-delegation-record" };
|
|
12584
|
+
}
|
|
12503
12585
|
if (!fallback) {
|
|
12504
12586
|
throw new CLIError(
|
|
12505
12587
|
"RAW_RECAP_RESOURCE_UNSUPPORTED",
|
|
12506
|
-
`Cannot
|
|
12588
|
+
`Cannot revoke "${cid}": this CLI profile has no record of this grant and the node's record does not match the signed-in owner.`,
|
|
12507
12589
|
ExitCode.PERMISSION_DENIED
|
|
12508
12590
|
);
|
|
12509
12591
|
}
|
|
12510
12592
|
fallbackSpaceId = fallback.space;
|
|
12511
12593
|
authorityScopeSource = fallback.source;
|
|
12512
|
-
authorityScopeReason = fallback.source === "local-signed-grant-artifact" ? "Raw-CID revoke authority could not be issued; the fallback space came from an owner-signed local grant whose Authorization recomputes to this CID." : "Raw-CID revoke authority could not be issued; the fallback space came from a locally verified owner-signed stored grant bound to this CID.";
|
|
12594
|
+
authorityScopeReason = fallback.source === "local-signed-grant-artifact" ? "Raw-CID revoke authority could not be issued; the fallback space came from an owner-signed local grant whose Authorization recomputes to this CID." : fallback.source === "node-delegation-record" ? "Raw-CID revoke authority could not be issued; the fallback space came from the node's delegation record, whose issuer chains to the signed-in owner and whose space the owner holds." : "Raw-CID revoke authority could not be issued; the fallback space came from a locally verified owner-signed stored grant bound to this CID.";
|
|
12513
12595
|
targetSpaceSource = fallback.source;
|
|
12514
12596
|
const acquired = await ensureDelegationAuthority({
|
|
12515
12597
|
ctx,
|
|
@@ -12522,7 +12604,7 @@ async function revokeDelegationOnNode(params) {
|
|
|
12522
12604
|
actions: ["tinycloud.delegation/revoke"]
|
|
12523
12605
|
}],
|
|
12524
12606
|
expiryOption: void 0,
|
|
12525
|
-
reason: `Revoke delegation ${cid} using its CID-bound owner-signed grant ${fallback.source === "local-stored-grant" ? "record" : "artifact"}`,
|
|
12607
|
+
reason: fallback.source === "node-delegation-record" ? `Revoke delegation ${cid} using the node's record of the owner's grant` : `Revoke delegation ${cid} using its CID-bound owner-signed grant ${fallback.source === "local-stored-grant" ? "record" : "artifact"}`,
|
|
12526
12608
|
yes: params.yes,
|
|
12527
12609
|
consentAlreadyGiven: true,
|
|
12528
12610
|
persist: false,
|
|
@@ -17059,7 +17141,7 @@ var EXIT_BY_CODE = {
|
|
|
17059
17141
|
[ReplicaErrorCode.GRANT_NOT_COVERING]: ExitCode.AUTH_REQUIRED,
|
|
17060
17142
|
[ReplicaErrorCode.GRANT_NOT_YET_VALID]: ExitCode.PERMISSION_DENIED,
|
|
17061
17143
|
[ReplicaErrorCode.GRANT_EXPIRED]: ExitCode.PERMISSION_DENIED,
|
|
17062
|
-
[ReplicaErrorCode.GRANT_REVOKED]:
|
|
17144
|
+
[ReplicaErrorCode.GRANT_REVOKED]: GRANT_REVOKED_EXIT_CODE,
|
|
17063
17145
|
[ReplicaErrorCode.CONSENT_EXPIRED]: ExitCode.PERMISSION_DENIED,
|
|
17064
17146
|
[ReplicaErrorCode.GATED]: ExitCode.PERMISSION_DENIED,
|
|
17065
17147
|
[ReplicaErrorCode.GRANT_UNAUTHORIZED]: ExitCode.PERMISSION_DENIED,
|
|
@@ -17844,7 +17926,7 @@ import {
|
|
|
17844
17926
|
canonicalSecretRequestOwner,
|
|
17845
17927
|
isValidSecretRequestScope,
|
|
17846
17928
|
missingSecretRecovery,
|
|
17847
|
-
ownerOfSpace,
|
|
17929
|
+
ownerOfSpace as ownerOfSpace2,
|
|
17848
17930
|
resolveSecretsOwner as resolveSecretsOwner2,
|
|
17849
17931
|
SecretRequestInputError,
|
|
17850
17932
|
validSecretNames
|
|
@@ -18858,7 +18940,7 @@ async function readDelegatedSecretValue(params) {
|
|
|
18858
18940
|
if (!envelopeResult.ok) {
|
|
18859
18941
|
if (envelopeResult.error.code === "NOT_FOUND" || envelopeResult.error.code === "KEY_NOT_FOUND" || envelopeResult.error.code === "KV_NOT_FOUND") {
|
|
18860
18942
|
const recovery = missingSecretRecovery({
|
|
18861
|
-
owner:
|
|
18943
|
+
owner: ownerOfSpace2(params.space?.startsWith("tinycloud:") ? params.space : params.delegation.spaceId),
|
|
18862
18944
|
local: params.local,
|
|
18863
18945
|
names: [params.name],
|
|
18864
18946
|
...params.scope === void 0 ? {} : { scope: params.scope }
|