@tinycloud/cli 0.5.0 → 0.6.0-beta.1
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 +16 -2
- package/dist/index.js +596 -49
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -548,7 +548,7 @@ var ProfileManager = class _ProfileManager {
|
|
|
548
548
|
};
|
|
549
549
|
|
|
550
550
|
// src/auth/local-key.ts
|
|
551
|
-
import { TCWSessionManager, initPanicHook } from "@tinycloud/node-sdk-wasm";
|
|
551
|
+
import { TCWSessionManager, importKey, initPanicHook } from "@tinycloud/node-sdk-wasm";
|
|
552
552
|
import { PrivateKeySigner } from "@tinycloud/node-sdk";
|
|
553
553
|
import { randomBytes } from "crypto";
|
|
554
554
|
var wasmInitialized = false;
|
|
@@ -568,6 +568,12 @@ function generateKey() {
|
|
|
568
568
|
const did = mgr.getDID(keyId);
|
|
569
569
|
return { jwk, did };
|
|
570
570
|
}
|
|
571
|
+
function keyToDID(jwk) {
|
|
572
|
+
ensureWasm();
|
|
573
|
+
const mgr = new TCWSessionManager();
|
|
574
|
+
const keyId = importKey(mgr, JSON.stringify(jwk), "imported");
|
|
575
|
+
return mgr.getDID(keyId);
|
|
576
|
+
}
|
|
571
577
|
function generateEthereumPrivateKey() {
|
|
572
578
|
const keyBytes = randomBytes(32);
|
|
573
579
|
return "0x" + keyBytes.toString("hex");
|
|
@@ -594,10 +600,20 @@ async function localKeySignIn(options) {
|
|
|
594
600
|
});
|
|
595
601
|
await node.signIn();
|
|
596
602
|
const address = await new PrivateKeySigner(options.privateKey).getAddress();
|
|
603
|
+
const session = node.session;
|
|
604
|
+
if (!session) {
|
|
605
|
+
throw new Error("Local key sign-in did not produce a TinyCloud session");
|
|
606
|
+
}
|
|
597
607
|
return {
|
|
598
|
-
spaceId:
|
|
608
|
+
spaceId: session.spaceId,
|
|
599
609
|
address,
|
|
600
|
-
chainId: 1
|
|
610
|
+
chainId: 1,
|
|
611
|
+
delegationHeader: session.delegationHeader,
|
|
612
|
+
delegationCid: session.delegationCid,
|
|
613
|
+
jwk: session.jwk,
|
|
614
|
+
verificationMethod: session.verificationMethod,
|
|
615
|
+
siwe: session.siwe,
|
|
616
|
+
signature: session.signature
|
|
601
617
|
};
|
|
602
618
|
}
|
|
603
619
|
|
|
@@ -843,12 +859,41 @@ function registerInitCommand(program2) {
|
|
|
843
859
|
}
|
|
844
860
|
|
|
845
861
|
// src/commands/auth.ts
|
|
862
|
+
import { get as httpGet } from "http";
|
|
863
|
+
import { get as httpsGet } from "https";
|
|
864
|
+
import { spawn } from "child_process";
|
|
865
|
+
import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
|
|
866
|
+
import { dirname as dirname2 } from "path";
|
|
846
867
|
import { createInterface as createInterface2 } from "readline";
|
|
847
868
|
|
|
869
|
+
// src/config/types.ts
|
|
870
|
+
var CLI_PROFILE_POSTURES = [
|
|
871
|
+
"owner-openkey",
|
|
872
|
+
"delegate-session",
|
|
873
|
+
"local-owner-key"
|
|
874
|
+
];
|
|
875
|
+
var CLI_OPERATOR_TYPES = ["human", "agent"];
|
|
876
|
+
function isCLIProfilePosture(value) {
|
|
877
|
+
return typeof value === "string" && CLI_PROFILE_POSTURES.includes(value);
|
|
878
|
+
}
|
|
879
|
+
function isCLIOperatorType(value) {
|
|
880
|
+
return typeof value === "string" && CLI_OPERATOR_TYPES.includes(value);
|
|
881
|
+
}
|
|
882
|
+
function resolveProfilePosture(profile) {
|
|
883
|
+
if (isCLIProfilePosture(profile.posture)) return profile.posture;
|
|
884
|
+
if (profile.authMethod === "local") return "local-owner-key";
|
|
885
|
+
return "owner-openkey";
|
|
886
|
+
}
|
|
887
|
+
function resolveProfileOperatorType(profile) {
|
|
888
|
+
if (isCLIOperatorType(profile.operatorType)) return profile.operatorType;
|
|
889
|
+
return "human";
|
|
890
|
+
}
|
|
891
|
+
|
|
848
892
|
// src/lib/sdk.ts
|
|
849
893
|
import { TinyCloudNode } from "@tinycloud/node-sdk";
|
|
850
894
|
|
|
851
895
|
// src/lib/permissions.ts
|
|
896
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
852
897
|
import { appendFile, readFile as readFile2 } from "fs/promises";
|
|
853
898
|
import { join as join4 } from "path";
|
|
854
899
|
import {
|
|
@@ -898,9 +943,37 @@ async function resolveSpaceUri(input, profileName) {
|
|
|
898
943
|
function additionalDelegationsPath(profile) {
|
|
899
944
|
return join4(PROFILES_DIR, profile, "additional-delegations.json");
|
|
900
945
|
}
|
|
946
|
+
function permissionRequestsPath(profile) {
|
|
947
|
+
return join4(PROFILES_DIR, profile, "auth-requests.json");
|
|
948
|
+
}
|
|
901
949
|
function grantHistoryPath(profile) {
|
|
902
950
|
return join4(PROFILES_DIR, profile, "auth-grants.jsonl");
|
|
903
951
|
}
|
|
952
|
+
function createPermissionRequestArtifact(params) {
|
|
953
|
+
return {
|
|
954
|
+
kind: "tinycloud.auth.request",
|
|
955
|
+
version: 1,
|
|
956
|
+
requestId: `req_${Date.now().toString(36)}_${randomBytes2(4).toString("hex")}`,
|
|
957
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
958
|
+
profile: params.profileName,
|
|
959
|
+
posture: resolveProfilePosture(params.profile),
|
|
960
|
+
operatorType: resolveProfileOperatorType(params.profile),
|
|
961
|
+
host: params.host,
|
|
962
|
+
did: didWithoutFragment(params.profile.sessionDid ?? params.profile.did),
|
|
963
|
+
primaryDid: params.profile.primaryDid,
|
|
964
|
+
spaceId: params.profile.spaceId,
|
|
965
|
+
requestedExpiry: params.requestedExpiry,
|
|
966
|
+
requested: params.requested,
|
|
967
|
+
command: {
|
|
968
|
+
argv: params.argv ?? process.argv.slice(2),
|
|
969
|
+
cwd: params.cwd ?? process.cwd()
|
|
970
|
+
}
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
function didWithoutFragment(did) {
|
|
974
|
+
const fragment = did.indexOf("#");
|
|
975
|
+
return fragment === -1 ? did : did.slice(0, fragment);
|
|
976
|
+
}
|
|
904
977
|
async function loadAdditionalDelegations(profile) {
|
|
905
978
|
const raw = await readJson(
|
|
906
979
|
additionalDelegationsPath(profile)
|
|
@@ -918,6 +991,41 @@ async function appendAdditionalDelegation(profile, entry) {
|
|
|
918
991
|
next.push(entry);
|
|
919
992
|
await saveAdditionalDelegations(profile, next);
|
|
920
993
|
}
|
|
994
|
+
async function loadPermissionRequestArtifacts(profile) {
|
|
995
|
+
const raw = await readJson(
|
|
996
|
+
permissionRequestsPath(profile)
|
|
997
|
+
);
|
|
998
|
+
return Array.isArray(raw) ? raw.filter(isPermissionRequestArtifact) : [];
|
|
999
|
+
}
|
|
1000
|
+
async function savePermissionRequestArtifacts(profile, entries) {
|
|
1001
|
+
const profileDir = join4(PROFILES_DIR, profile);
|
|
1002
|
+
await ensureDir(profileDir);
|
|
1003
|
+
await writeJson(permissionRequestsPath(profile), entries);
|
|
1004
|
+
}
|
|
1005
|
+
async function appendPermissionRequestArtifact(profile, artifact) {
|
|
1006
|
+
const existing = await loadPermissionRequestArtifacts(profile);
|
|
1007
|
+
const next = existing.filter((item) => item.requestId !== artifact.requestId);
|
|
1008
|
+
next.push(artifact);
|
|
1009
|
+
await savePermissionRequestArtifacts(profile, next);
|
|
1010
|
+
}
|
|
1011
|
+
async function getPermissionRequestArtifact(profile, requestId) {
|
|
1012
|
+
const existing = await loadPermissionRequestArtifacts(profile);
|
|
1013
|
+
return existing.find((item) => item.requestId === requestId) ?? null;
|
|
1014
|
+
}
|
|
1015
|
+
async function getLastPermissionRequestArtifact(profile) {
|
|
1016
|
+
const existing = await loadPermissionRequestArtifacts(profile);
|
|
1017
|
+
return existing.at(-1) ?? null;
|
|
1018
|
+
}
|
|
1019
|
+
function isPermissionRequestArtifact(value) {
|
|
1020
|
+
if (value === null || typeof value !== "object") return false;
|
|
1021
|
+
const candidate = value;
|
|
1022
|
+
return candidate.kind === "tinycloud.auth.request" && candidate.version === 1 && typeof candidate.requestId === "string" && Array.isArray(candidate.requested);
|
|
1023
|
+
}
|
|
1024
|
+
function isDelegationImportArtifact(value) {
|
|
1025
|
+
if (value === null || typeof value !== "object") return false;
|
|
1026
|
+
const candidate = value;
|
|
1027
|
+
return candidate.kind === "tinycloud.auth.delegation" && candidate.version === 1 && candidate.delegation !== void 0 && typeof candidate.delegation === "object";
|
|
1028
|
+
}
|
|
921
1029
|
async function replayAdditionalDelegations(node, profile) {
|
|
922
1030
|
const entries = await loadAdditionalDelegations(profile);
|
|
923
1031
|
for (const entry of entries) {
|
|
@@ -1130,7 +1238,21 @@ async function createSDKInstance(ctx, options) {
|
|
|
1130
1238
|
host: ctx.host,
|
|
1131
1239
|
privateKey: effectivePrivateKey
|
|
1132
1240
|
});
|
|
1133
|
-
|
|
1241
|
+
if (session && session.delegationHeader && session.delegationCid && session.spaceId) {
|
|
1242
|
+
await node2.restoreSession({
|
|
1243
|
+
delegationHeader: session.delegationHeader,
|
|
1244
|
+
delegationCid: session.delegationCid,
|
|
1245
|
+
spaceId: session.spaceId,
|
|
1246
|
+
jwk: session.jwk ?? key,
|
|
1247
|
+
verificationMethod: session.verificationMethod ?? profile.sessionDid ?? profile.did,
|
|
1248
|
+
address: session.address,
|
|
1249
|
+
chainId: session.chainId,
|
|
1250
|
+
siwe: session.siwe,
|
|
1251
|
+
signature: session.signature
|
|
1252
|
+
});
|
|
1253
|
+
} else {
|
|
1254
|
+
await node2.signIn();
|
|
1255
|
+
}
|
|
1134
1256
|
await replayAdditionalDelegations(node2, ctx.profile);
|
|
1135
1257
|
return node2;
|
|
1136
1258
|
}
|
|
@@ -1252,17 +1374,22 @@ function registerAuthCommand(program2) {
|
|
|
1252
1374
|
} catch {
|
|
1253
1375
|
profile = null;
|
|
1254
1376
|
}
|
|
1377
|
+
const posture = profile ? resolveProfilePosture(profile) : null;
|
|
1378
|
+
const operatorType = profile ? resolveProfileOperatorType(profile) : null;
|
|
1255
1379
|
const authenticated = session !== null;
|
|
1256
1380
|
if (shouldOutputJson()) {
|
|
1257
1381
|
outputJson({
|
|
1258
1382
|
authenticated,
|
|
1259
1383
|
did: profile?.did ?? null,
|
|
1384
|
+
sessionDid: profile?.sessionDid ?? null,
|
|
1260
1385
|
primaryDid: profile?.primaryDid ?? null,
|
|
1261
1386
|
spaceId: profile?.spaceId ?? null,
|
|
1262
1387
|
host: ctx.host,
|
|
1263
1388
|
profile: ctx.profile,
|
|
1264
1389
|
hasKey: hasKey !== null,
|
|
1265
1390
|
authMethod: profile?.authMethod ?? null,
|
|
1391
|
+
posture,
|
|
1392
|
+
operatorType,
|
|
1266
1393
|
address: profile?.address ?? null
|
|
1267
1394
|
});
|
|
1268
1395
|
} else {
|
|
@@ -1270,8 +1397,11 @@ function registerAuthCommand(program2) {
|
|
|
1270
1397
|
process.stdout.write(formatField("Profile", ctx.profile) + "\n");
|
|
1271
1398
|
process.stdout.write(formatField("Authenticated", authenticated) + "\n");
|
|
1272
1399
|
process.stdout.write(formatField("Auth Method", profile?.authMethod ?? null) + "\n");
|
|
1400
|
+
process.stdout.write(formatField("Posture", posture) + "\n");
|
|
1401
|
+
process.stdout.write(formatField("Operator", operatorType) + "\n");
|
|
1273
1402
|
process.stdout.write(formatField("Host", ctx.host) + "\n");
|
|
1274
1403
|
process.stdout.write(formatField("DID", profile?.did ?? null) + "\n");
|
|
1404
|
+
process.stdout.write(formatField("Session DID", profile?.sessionDid ?? null) + "\n");
|
|
1275
1405
|
process.stdout.write(formatField("Primary DID", profile?.primaryDid ?? null) + "\n");
|
|
1276
1406
|
process.stdout.write(formatField("Address", profile?.address ?? null) + "\n");
|
|
1277
1407
|
process.stdout.write(formatField("Space ID", profile?.spaceId ?? null) + "\n");
|
|
@@ -1281,7 +1411,7 @@ function registerAuthCommand(program2) {
|
|
|
1281
1411
|
handleError(error);
|
|
1282
1412
|
}
|
|
1283
1413
|
});
|
|
1284
|
-
auth.command("request").description("
|
|
1414
|
+
auth.command("request").description("Create a TinyCloud permission request artifact").option(
|
|
1285
1415
|
"--cap <spec>",
|
|
1286
1416
|
"Capability spec: tinycloud.<service>:<space>:<path>:<actions-csv> (repeatable)",
|
|
1287
1417
|
(value, previous) => [...previous, value],
|
|
@@ -1289,13 +1419,13 @@ function registerAuthCommand(program2) {
|
|
|
1289
1419
|
).option("--permission <file>", 'JSON permission request: { "permissions": PermissionEntry[] }').option("--manifest <fileOrBase64>", "Manifest file, base64:<json>, or raw base64 JSON").option(
|
|
1290
1420
|
"--expiry <duration>",
|
|
1291
1421
|
`Lifetime of the granted delegation. ms-format string (e.g. "7d", "30m") or raw milliseconds. Defaults to 7d, capped by the active session's expiry.`
|
|
1292
|
-
).option("--yes", "Skip local-key TTY confirmation", false).action(async (options, cmd) => {
|
|
1422
|
+
).option("--emit [file]", "Emit the request artifact to stdout, or write it to file when provided").option("--grant", "Grant the requested permissions immediately with this owner profile").option("--yes", "Skip local-key TTY confirmation", false).action(async (options, cmd) => {
|
|
1293
1423
|
try {
|
|
1294
1424
|
const globalOpts = cmd.optsWithGlobals();
|
|
1295
1425
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
1296
1426
|
const profile = await ProfileManager.getProfile(ctx.profile);
|
|
1297
|
-
const session = await ProfileManager.getSession(ctx.profile);
|
|
1298
1427
|
const requested = await collectRequestedPermissions(options, ctx.profile);
|
|
1428
|
+
const expiryOption = parseExpiryOption(options.expiry);
|
|
1299
1429
|
if (requested.length === 0) {
|
|
1300
1430
|
throw new CLIError(
|
|
1301
1431
|
"NO_CAPS_REQUESTED",
|
|
@@ -1303,6 +1433,18 @@ function registerAuthCommand(program2) {
|
|
|
1303
1433
|
ExitCode.USAGE_ERROR
|
|
1304
1434
|
);
|
|
1305
1435
|
}
|
|
1436
|
+
if (!options.grant) {
|
|
1437
|
+
const artifact = createPermissionRequestArtifact({
|
|
1438
|
+
profileName: ctx.profile,
|
|
1439
|
+
profile,
|
|
1440
|
+
host: ctx.host,
|
|
1441
|
+
requested,
|
|
1442
|
+
requestedExpiry: expiryOption
|
|
1443
|
+
});
|
|
1444
|
+
await appendPermissionRequestArtifact(ctx.profile, artifact);
|
|
1445
|
+
await emitPermissionRequestArtifact(artifact, options.emit);
|
|
1446
|
+
return;
|
|
1447
|
+
}
|
|
1306
1448
|
const node = await ensureAuthenticated(ctx);
|
|
1307
1449
|
if (node.hasRuntimePermissions(requested)) {
|
|
1308
1450
|
outputJson({ changed: false, missing: [], added: [] });
|
|
@@ -1316,14 +1458,13 @@ function registerAuthCommand(program2) {
|
|
|
1316
1458
|
const delegationCids2 = [];
|
|
1317
1459
|
let expiry2;
|
|
1318
1460
|
const openkeyHost = resolveOpenKeyHost(profile);
|
|
1319
|
-
const expiryOption2 = parseExpiryOption(options.expiry);
|
|
1320
1461
|
for (const group of groupPermissionsBySpace(requested)) {
|
|
1321
1462
|
const delegationData = await startAuthFlow(profile.did, {
|
|
1322
1463
|
jwk: key,
|
|
1323
1464
|
host: ctx.host,
|
|
1324
1465
|
permissions: group,
|
|
1325
1466
|
openkeyHost,
|
|
1326
|
-
expiry:
|
|
1467
|
+
expiry: expiryOption
|
|
1327
1468
|
});
|
|
1328
1469
|
const delegation = portableFromOpenKeyDelegation(delegationData, group, ctx.host);
|
|
1329
1470
|
const stored = storedAdditionalDelegation(delegation, group);
|
|
@@ -1358,8 +1499,6 @@ function registerAuthCommand(program2) {
|
|
|
1358
1499
|
ExitCode.USAGE_ERROR
|
|
1359
1500
|
);
|
|
1360
1501
|
}
|
|
1361
|
-
void session;
|
|
1362
|
-
const expiryOption = parseExpiryOption(options.expiry);
|
|
1363
1502
|
const delegations = await node.grantRuntimePermissions(
|
|
1364
1503
|
requested,
|
|
1365
1504
|
expiryOption !== void 0 ? { expiry: expiryOption } : void 0
|
|
@@ -1394,6 +1533,136 @@ function registerAuthCommand(program2) {
|
|
|
1394
1533
|
handleError(error);
|
|
1395
1534
|
}
|
|
1396
1535
|
});
|
|
1536
|
+
auth.command("import [source]").description("Import a TinyCloud delegation or permission request artifact").option("--stdin", "Read the JSON artifact from stdin").option("--paste", "Read the JSON artifact from stdin").action(async (source, options, cmd) => {
|
|
1537
|
+
try {
|
|
1538
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
1539
|
+
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
1540
|
+
const raw = await readAuthArtifactSource(source, {
|
|
1541
|
+
stdin: options.stdin === true || options.paste === true
|
|
1542
|
+
});
|
|
1543
|
+
const parsed = JSON.parse(raw);
|
|
1544
|
+
if (isPermissionRequestArtifact(parsed)) {
|
|
1545
|
+
await appendPermissionRequestArtifact(ctx.profile, parsed);
|
|
1546
|
+
outputJson({
|
|
1547
|
+
imported: true,
|
|
1548
|
+
kind: parsed.kind,
|
|
1549
|
+
requestId: parsed.requestId,
|
|
1550
|
+
requested: parsed.requested,
|
|
1551
|
+
next: `tc auth retry ${parsed.requestId}`
|
|
1552
|
+
});
|
|
1553
|
+
return;
|
|
1554
|
+
}
|
|
1555
|
+
const imported = normalizeDelegationImport(parsed);
|
|
1556
|
+
const node = await ensureAuthenticated(ctx);
|
|
1557
|
+
await appendAdditionalDelegation(ctx.profile, storedAdditionalDelegation(
|
|
1558
|
+
imported.delegation,
|
|
1559
|
+
imported.permissions
|
|
1560
|
+
));
|
|
1561
|
+
await node.useRuntimeDelegation(imported.delegation);
|
|
1562
|
+
await appendGrantHistory(ctx.profile, {
|
|
1563
|
+
addedCaps: imported.permissions,
|
|
1564
|
+
source: "cli",
|
|
1565
|
+
delegationCid: imported.delegation.cid,
|
|
1566
|
+
expiry: imported.delegation.expiry.toISOString()
|
|
1567
|
+
});
|
|
1568
|
+
outputJson({
|
|
1569
|
+
imported: true,
|
|
1570
|
+
kind: "tinycloud.auth.delegation",
|
|
1571
|
+
requestId: imported.requestId ?? null,
|
|
1572
|
+
delegationCid: imported.delegation.cid,
|
|
1573
|
+
permissions: imported.permissions,
|
|
1574
|
+
expiry: imported.delegation.expiry.toISOString()
|
|
1575
|
+
});
|
|
1576
|
+
} catch (error) {
|
|
1577
|
+
handleError(error);
|
|
1578
|
+
}
|
|
1579
|
+
});
|
|
1580
|
+
auth.command("grant [request]").description("Grant a TinyCloud permission request artifact to its requester").option("--stdin", "Read the JSON request artifact from stdin").option("--paste", "Read the JSON request artifact from stdin").option("--yes", "Skip local-key TTY confirmation", false).action(async (source, options, cmd) => {
|
|
1581
|
+
try {
|
|
1582
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
1583
|
+
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
1584
|
+
const profile = await ProfileManager.getProfile(ctx.profile);
|
|
1585
|
+
const raw = await readAuthArtifactSource(source, {
|
|
1586
|
+
stdin: options.stdin === true || options.paste === true
|
|
1587
|
+
});
|
|
1588
|
+
const parsed = JSON.parse(raw);
|
|
1589
|
+
if (!isPermissionRequestArtifact(parsed)) {
|
|
1590
|
+
throw new CLIError(
|
|
1591
|
+
"INVALID_AUTH_REQUEST",
|
|
1592
|
+
"Auth grant requires a tinycloud.auth.request artifact.",
|
|
1593
|
+
ExitCode.USAGE_ERROR
|
|
1594
|
+
);
|
|
1595
|
+
}
|
|
1596
|
+
const node = await ensureAuthenticated(ctx);
|
|
1597
|
+
await ensureDelegationAuthority({
|
|
1598
|
+
ctx,
|
|
1599
|
+
profile,
|
|
1600
|
+
node,
|
|
1601
|
+
requested: parsed.requested,
|
|
1602
|
+
expiryOption: parsed.requestedExpiry,
|
|
1603
|
+
yes: options.yes === true
|
|
1604
|
+
});
|
|
1605
|
+
const result = await node.delegateTo(
|
|
1606
|
+
parsed.did,
|
|
1607
|
+
parsed.requested,
|
|
1608
|
+
parsed.requestedExpiry !== void 0 ? { expiry: parsed.requestedExpiry } : void 0
|
|
1609
|
+
);
|
|
1610
|
+
outputJson({
|
|
1611
|
+
kind: "tinycloud.auth.delegation",
|
|
1612
|
+
version: 1,
|
|
1613
|
+
requestId: parsed.requestId,
|
|
1614
|
+
delegationCid: result.delegation.cid,
|
|
1615
|
+
delegation: result.delegation,
|
|
1616
|
+
permissions: parsed.requested,
|
|
1617
|
+
expiry: result.delegation.expiry.toISOString(),
|
|
1618
|
+
prompted: result.prompted
|
|
1619
|
+
});
|
|
1620
|
+
} catch (error) {
|
|
1621
|
+
handleError(error);
|
|
1622
|
+
}
|
|
1623
|
+
});
|
|
1624
|
+
auth.command("retry [requestId]").description("Check whether a stored permission request is now satisfied").option("--last", "Use the latest stored permission request for this profile").option("--exec", "Run the captured command when the request is covered").action(async (requestId, options, cmd) => {
|
|
1625
|
+
try {
|
|
1626
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
1627
|
+
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
1628
|
+
const artifact = options.last ? await getLastPermissionRequestArtifact(ctx.profile) : requestId ? await getPermissionRequestArtifact(ctx.profile, requestId) : null;
|
|
1629
|
+
if (!artifact) {
|
|
1630
|
+
throw new CLIError(
|
|
1631
|
+
"REQUEST_NOT_FOUND",
|
|
1632
|
+
options.last ? `No stored permission requests exist for profile "${ctx.profile}".` : "Provide a requestId or use --last.",
|
|
1633
|
+
ExitCode.NOT_FOUND
|
|
1634
|
+
);
|
|
1635
|
+
}
|
|
1636
|
+
const node = await ensureAuthenticated(ctx);
|
|
1637
|
+
const covered = node.hasRuntimePermissions(artifact.requested);
|
|
1638
|
+
if (options.exec) {
|
|
1639
|
+
if (!covered) {
|
|
1640
|
+
throw new CLIError(
|
|
1641
|
+
"PERMISSIONS_MISSING",
|
|
1642
|
+
`Request ${artifact.requestId} is not covered yet. Import a delegation, then retry with --exec.`,
|
|
1643
|
+
ExitCode.PERMISSION_DENIED
|
|
1644
|
+
);
|
|
1645
|
+
}
|
|
1646
|
+
if (!artifact.command?.argv?.length) {
|
|
1647
|
+
throw new CLIError(
|
|
1648
|
+
"COMMAND_NOT_CAPTURED",
|
|
1649
|
+
`Request ${artifact.requestId} does not include a captured command.`,
|
|
1650
|
+
ExitCode.USAGE_ERROR
|
|
1651
|
+
);
|
|
1652
|
+
}
|
|
1653
|
+
await execCapturedCommand(artifact.command);
|
|
1654
|
+
return;
|
|
1655
|
+
}
|
|
1656
|
+
outputJson({
|
|
1657
|
+
requestId: artifact.requestId,
|
|
1658
|
+
covered,
|
|
1659
|
+
missing: covered ? [] : artifact.requested,
|
|
1660
|
+
command: artifact.command ?? null
|
|
1661
|
+
});
|
|
1662
|
+
} catch (error) {
|
|
1663
|
+
handleError(error);
|
|
1664
|
+
}
|
|
1665
|
+
});
|
|
1397
1666
|
auth.command("caps").description("Show granted capabilities for the active session").option("--diff <spec>", "Show missing capabilities for a spec").option("--history", "Show recent permission grants").action(async (options, cmd) => {
|
|
1398
1667
|
try {
|
|
1399
1668
|
const globalOpts = cmd.optsWithGlobals();
|
|
@@ -1459,23 +1728,31 @@ function registerAuthCommand(program2) {
|
|
|
1459
1728
|
const profile = await ProfileManager.getProfile(ctx.profile);
|
|
1460
1729
|
const session = await ProfileManager.getSession(ctx.profile);
|
|
1461
1730
|
const authenticated = session !== null;
|
|
1731
|
+
const posture = resolveProfilePosture(profile);
|
|
1732
|
+
const operatorType = resolveProfileOperatorType(profile);
|
|
1462
1733
|
if (shouldOutputJson()) {
|
|
1463
1734
|
outputJson({
|
|
1464
1735
|
profile: ctx.profile,
|
|
1465
1736
|
did: profile.did,
|
|
1737
|
+
sessionDid: profile.sessionDid ?? null,
|
|
1466
1738
|
primaryDid: profile.primaryDid ?? null,
|
|
1467
1739
|
spaceId: profile.spaceId ?? null,
|
|
1468
1740
|
host: profile.host,
|
|
1469
1741
|
authenticated,
|
|
1470
1742
|
authMethod: profile.authMethod ?? null,
|
|
1743
|
+
posture,
|
|
1744
|
+
operatorType,
|
|
1471
1745
|
address: profile.address ?? null
|
|
1472
1746
|
});
|
|
1473
1747
|
} else {
|
|
1474
1748
|
process.stdout.write(theme.heading("Identity") + "\n");
|
|
1475
1749
|
process.stdout.write(formatField("Profile", ctx.profile) + "\n");
|
|
1476
1750
|
process.stdout.write(formatField("DID", profile.did) + "\n");
|
|
1751
|
+
process.stdout.write(formatField("Session DID", profile.sessionDid ?? null) + "\n");
|
|
1477
1752
|
process.stdout.write(formatField("Primary DID", profile.primaryDid ?? null) + "\n");
|
|
1478
1753
|
process.stdout.write(formatField("Auth Method", profile.authMethod ?? null) + "\n");
|
|
1754
|
+
process.stdout.write(formatField("Posture", posture) + "\n");
|
|
1755
|
+
process.stdout.write(formatField("Operator", operatorType) + "\n");
|
|
1479
1756
|
process.stdout.write(formatField("Address", profile.address ?? null) + "\n");
|
|
1480
1757
|
process.stdout.write(formatField("Space ID", profile.spaceId ?? null) + "\n");
|
|
1481
1758
|
process.stdout.write(formatField("Host", profile.host) + "\n");
|
|
@@ -1486,6 +1763,210 @@ function registerAuthCommand(program2) {
|
|
|
1486
1763
|
}
|
|
1487
1764
|
});
|
|
1488
1765
|
}
|
|
1766
|
+
async function emitPermissionRequestArtifact(artifact, emitOption) {
|
|
1767
|
+
if (typeof emitOption === "string" && emitOption.length > 0) {
|
|
1768
|
+
await mkdir2(dirname2(emitOption), { recursive: true });
|
|
1769
|
+
await writeFile2(emitOption, JSON.stringify(artifact, null, 2) + "\n", "utf8");
|
|
1770
|
+
outputJson({
|
|
1771
|
+
emitted: true,
|
|
1772
|
+
path: emitOption,
|
|
1773
|
+
requestId: artifact.requestId,
|
|
1774
|
+
requested: artifact.requested
|
|
1775
|
+
});
|
|
1776
|
+
return;
|
|
1777
|
+
}
|
|
1778
|
+
outputJson(artifact);
|
|
1779
|
+
}
|
|
1780
|
+
async function readAuthArtifactSource(source, options) {
|
|
1781
|
+
if (options.stdin || source === "-" || !source && !isInteractive()) {
|
|
1782
|
+
return readStdin();
|
|
1783
|
+
}
|
|
1784
|
+
if (!source) {
|
|
1785
|
+
throw new CLIError(
|
|
1786
|
+
"IMPORT_SOURCE_REQUIRED",
|
|
1787
|
+
"Provide an artifact file, URL, or use --stdin.",
|
|
1788
|
+
ExitCode.USAGE_ERROR
|
|
1789
|
+
);
|
|
1790
|
+
}
|
|
1791
|
+
if (source.startsWith("http://") || source.startsWith("https://")) {
|
|
1792
|
+
return readUrl(source);
|
|
1793
|
+
}
|
|
1794
|
+
return readFile3(source, "utf8");
|
|
1795
|
+
}
|
|
1796
|
+
async function readStdin() {
|
|
1797
|
+
const chunks = [];
|
|
1798
|
+
for await (const chunk of process.stdin) {
|
|
1799
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
|
|
1800
|
+
}
|
|
1801
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
1802
|
+
}
|
|
1803
|
+
function readUrl(source) {
|
|
1804
|
+
return new Promise((resolve3, reject) => {
|
|
1805
|
+
const getter = source.startsWith("https://") ? httpsGet : httpGet;
|
|
1806
|
+
const request = getter(source, (response) => {
|
|
1807
|
+
const status = response.statusCode ?? 0;
|
|
1808
|
+
if (status >= 300 && status < 400 && response.headers.location) {
|
|
1809
|
+
response.resume();
|
|
1810
|
+
readUrl(new URL(response.headers.location, source).toString()).then(resolve3, reject);
|
|
1811
|
+
return;
|
|
1812
|
+
}
|
|
1813
|
+
if (status < 200 || status >= 300) {
|
|
1814
|
+
response.resume();
|
|
1815
|
+
reject(new CLIError(
|
|
1816
|
+
"IMPORT_FETCH_FAILED",
|
|
1817
|
+
`Failed to fetch ${source}: HTTP ${status}.`,
|
|
1818
|
+
ExitCode.ERROR
|
|
1819
|
+
));
|
|
1820
|
+
return;
|
|
1821
|
+
}
|
|
1822
|
+
const chunks = [];
|
|
1823
|
+
response.on("data", (chunk) => {
|
|
1824
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
1825
|
+
});
|
|
1826
|
+
response.on("end", () => resolve3(Buffer.concat(chunks).toString("utf8")));
|
|
1827
|
+
});
|
|
1828
|
+
request.on("error", reject);
|
|
1829
|
+
});
|
|
1830
|
+
}
|
|
1831
|
+
function normalizeDelegationImport(value) {
|
|
1832
|
+
if (isDelegationImportArtifact(value)) {
|
|
1833
|
+
const delegation = normalizePortableDelegation(value.delegation);
|
|
1834
|
+
return {
|
|
1835
|
+
requestId: value.requestId,
|
|
1836
|
+
delegation,
|
|
1837
|
+
permissions: Array.isArray(value.permissions) && value.permissions.length > 0 ? value.permissions : permissionsFromDelegation(delegation)
|
|
1838
|
+
};
|
|
1839
|
+
}
|
|
1840
|
+
if (isStoredDelegationLike(value)) {
|
|
1841
|
+
const delegation = normalizePortableDelegation(value.delegation);
|
|
1842
|
+
return {
|
|
1843
|
+
delegation,
|
|
1844
|
+
permissions: Array.isArray(value.permissions) && value.permissions.length > 0 ? value.permissions : permissionsFromDelegation(delegation)
|
|
1845
|
+
};
|
|
1846
|
+
}
|
|
1847
|
+
if (isPortableDelegationLike(value)) {
|
|
1848
|
+
const delegation = normalizePortableDelegation(value);
|
|
1849
|
+
return {
|
|
1850
|
+
delegation,
|
|
1851
|
+
permissions: permissionsFromDelegation(delegation)
|
|
1852
|
+
};
|
|
1853
|
+
}
|
|
1854
|
+
throw new CLIError(
|
|
1855
|
+
"INVALID_AUTH_IMPORT",
|
|
1856
|
+
"Auth import must be a tinycloud.auth.delegation artifact, a portable delegation, or a tinycloud.auth.request artifact.",
|
|
1857
|
+
ExitCode.USAGE_ERROR
|
|
1858
|
+
);
|
|
1859
|
+
}
|
|
1860
|
+
function isStoredDelegationLike(value) {
|
|
1861
|
+
if (value === null || typeof value !== "object") return false;
|
|
1862
|
+
const candidate = value;
|
|
1863
|
+
return isPortableDelegationLike(candidate.delegation);
|
|
1864
|
+
}
|
|
1865
|
+
function isPortableDelegationLike(value) {
|
|
1866
|
+
if (value === null || typeof value !== "object") return false;
|
|
1867
|
+
const candidate = value;
|
|
1868
|
+
return typeof candidate.cid === "string" && typeof candidate.spaceId === "string" && typeof candidate.path === "string" && Array.isArray(candidate.actions) && candidate.delegationHeader !== void 0 && typeof candidate.delegationHeader === "object";
|
|
1869
|
+
}
|
|
1870
|
+
function normalizePortableDelegation(delegation) {
|
|
1871
|
+
const rawExpiry = delegation.expiry;
|
|
1872
|
+
const expiry = rawExpiry instanceof Date ? rawExpiry : new Date(String(rawExpiry));
|
|
1873
|
+
if (Number.isNaN(expiry.getTime())) {
|
|
1874
|
+
throw new CLIError(
|
|
1875
|
+
"INVALID_AUTH_IMPORT",
|
|
1876
|
+
"Imported delegation must include a valid expiry.",
|
|
1877
|
+
ExitCode.USAGE_ERROR
|
|
1878
|
+
);
|
|
1879
|
+
}
|
|
1880
|
+
return { ...delegation, expiry };
|
|
1881
|
+
}
|
|
1882
|
+
async function ensureDelegationAuthority(params) {
|
|
1883
|
+
if (params.node.hasRuntimePermissions(params.requested)) return;
|
|
1884
|
+
if (params.profile.authMethod === "openkey") {
|
|
1885
|
+
const key = await ProfileManager.getKey(params.ctx.profile);
|
|
1886
|
+
if (!key) {
|
|
1887
|
+
throw new CLIError(
|
|
1888
|
+
"NO_KEY",
|
|
1889
|
+
`No key found for profile "${params.ctx.profile}". Run \`tc init\` first.`,
|
|
1890
|
+
ExitCode.AUTH_REQUIRED
|
|
1891
|
+
);
|
|
1892
|
+
}
|
|
1893
|
+
const openkeyHost = resolveOpenKeyHost(params.profile);
|
|
1894
|
+
for (const group of groupPermissionsBySpace(params.requested)) {
|
|
1895
|
+
const delegationData = await startAuthFlow(params.profile.did, {
|
|
1896
|
+
jwk: key,
|
|
1897
|
+
host: params.ctx.host,
|
|
1898
|
+
permissions: group,
|
|
1899
|
+
openkeyHost,
|
|
1900
|
+
expiry: params.expiryOption
|
|
1901
|
+
});
|
|
1902
|
+
const delegation = portableFromOpenKeyDelegation(delegationData, group, params.ctx.host);
|
|
1903
|
+
await appendAdditionalDelegation(
|
|
1904
|
+
params.ctx.profile,
|
|
1905
|
+
storedAdditionalDelegation(delegation, group)
|
|
1906
|
+
);
|
|
1907
|
+
await params.node.useRuntimeDelegation(delegation);
|
|
1908
|
+
await appendGrantHistory(params.ctx.profile, {
|
|
1909
|
+
addedCaps: group,
|
|
1910
|
+
source: "cli",
|
|
1911
|
+
delegationCid: delegation.cid,
|
|
1912
|
+
expiry: delegation.expiry.toISOString()
|
|
1913
|
+
});
|
|
1914
|
+
}
|
|
1915
|
+
return;
|
|
1916
|
+
}
|
|
1917
|
+
if (isInteractive()) {
|
|
1918
|
+
if (!params.yes) {
|
|
1919
|
+
await confirmPermissionRequest(params.requested);
|
|
1920
|
+
}
|
|
1921
|
+
} else if (!params.yes) {
|
|
1922
|
+
throw new CLIError(
|
|
1923
|
+
"CONFIRMATION_REQUIRED",
|
|
1924
|
+
"Local-key auth grants in non-interactive mode require --yes.",
|
|
1925
|
+
ExitCode.USAGE_ERROR
|
|
1926
|
+
);
|
|
1927
|
+
}
|
|
1928
|
+
const delegations = await params.node.grantRuntimePermissions(
|
|
1929
|
+
params.requested,
|
|
1930
|
+
params.expiryOption !== void 0 ? { expiry: params.expiryOption } : void 0
|
|
1931
|
+
);
|
|
1932
|
+
for (const delegation of delegations) {
|
|
1933
|
+
const covering = permissionsFromDelegation(delegation);
|
|
1934
|
+
await appendAdditionalDelegation(
|
|
1935
|
+
params.ctx.profile,
|
|
1936
|
+
storedAdditionalDelegation(delegation, covering)
|
|
1937
|
+
);
|
|
1938
|
+
await appendGrantHistory(params.ctx.profile, {
|
|
1939
|
+
addedCaps: covering,
|
|
1940
|
+
source: "cli",
|
|
1941
|
+
delegationCid: delegation.cid,
|
|
1942
|
+
expiry: delegation.expiry.toISOString()
|
|
1943
|
+
});
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1946
|
+
function execCapturedCommand(command) {
|
|
1947
|
+
return new Promise((resolve3, reject) => {
|
|
1948
|
+
const child = spawn(process.execPath, [process.argv[1], ...command.argv], {
|
|
1949
|
+
cwd: command.cwd,
|
|
1950
|
+
env: process.env,
|
|
1951
|
+
stdio: "inherit"
|
|
1952
|
+
});
|
|
1953
|
+
child.on("error", reject);
|
|
1954
|
+
child.on("exit", (code, signal) => {
|
|
1955
|
+
if (signal) {
|
|
1956
|
+
reject(new CLIError(
|
|
1957
|
+
"COMMAND_SIGNAL",
|
|
1958
|
+
`Captured command exited from signal ${signal}.`,
|
|
1959
|
+
ExitCode.ERROR
|
|
1960
|
+
));
|
|
1961
|
+
return;
|
|
1962
|
+
}
|
|
1963
|
+
if (code && code !== 0) {
|
|
1964
|
+
process.exitCode = code;
|
|
1965
|
+
}
|
|
1966
|
+
resolve3();
|
|
1967
|
+
});
|
|
1968
|
+
});
|
|
1969
|
+
}
|
|
1489
1970
|
async function collectRequestedPermissions(options, profile) {
|
|
1490
1971
|
const permissions = [];
|
|
1491
1972
|
for (const spec of options.cap ?? []) {
|
|
@@ -1596,6 +2077,7 @@ async function handleLocalAuth(profileName, host) {
|
|
|
1596
2077
|
let privateKey;
|
|
1597
2078
|
let address;
|
|
1598
2079
|
let did;
|
|
2080
|
+
let sessionDid = profile?.sessionDid;
|
|
1599
2081
|
if (profile?.authMethod === "local" && profile.privateKey && profile.address) {
|
|
1600
2082
|
privateKey = profile.privateKey;
|
|
1601
2083
|
address = profile.address;
|
|
@@ -1619,10 +2101,13 @@ async function handleLocalAuth(profileName, host) {
|
|
|
1619
2101
|
}
|
|
1620
2102
|
const hasKey = await ProfileManager.getKey(profileName);
|
|
1621
2103
|
if (!hasKey) {
|
|
1622
|
-
const { jwk } = await withSpinner("Generating session key...", async () => {
|
|
2104
|
+
const { jwk, did: generatedSessionDid } = await withSpinner("Generating session key...", async () => {
|
|
1623
2105
|
return generateKey();
|
|
1624
2106
|
});
|
|
1625
2107
|
await ProfileManager.setKey(profileName, jwk);
|
|
2108
|
+
sessionDid = generatedSessionDid;
|
|
2109
|
+
} else if (!sessionDid) {
|
|
2110
|
+
sessionDid = keyToDID(hasKey);
|
|
1626
2111
|
}
|
|
1627
2112
|
const sessionResult = await withSpinner("Signing in...", async () => {
|
|
1628
2113
|
return localKeySignIn({ privateKey, host });
|
|
@@ -1631,17 +2116,28 @@ async function handleLocalAuth(profileName, host) {
|
|
|
1631
2116
|
authMethod: "local",
|
|
1632
2117
|
address,
|
|
1633
2118
|
chainId: DEFAULT_CHAIN_ID,
|
|
1634
|
-
spaceId: sessionResult.spaceId
|
|
2119
|
+
spaceId: sessionResult.spaceId,
|
|
2120
|
+
delegationHeader: sessionResult.delegationHeader,
|
|
2121
|
+
delegationCid: sessionResult.delegationCid,
|
|
2122
|
+
jwk: sessionResult.jwk,
|
|
2123
|
+
verificationMethod: sessionResult.verificationMethod,
|
|
2124
|
+
siwe: sessionResult.siwe,
|
|
2125
|
+
signature: sessionResult.signature
|
|
1635
2126
|
});
|
|
2127
|
+
sessionDid = sessionResult.verificationMethod;
|
|
1636
2128
|
await ProfileManager.setProfile(profileName, {
|
|
2129
|
+
...profile,
|
|
1637
2130
|
name: profileName,
|
|
1638
2131
|
host,
|
|
1639
2132
|
chainId: DEFAULT_CHAIN_ID,
|
|
1640
2133
|
spaceName: "default",
|
|
1641
2134
|
did,
|
|
2135
|
+
sessionDid,
|
|
1642
2136
|
primaryDid: did,
|
|
1643
2137
|
spaceId: sessionResult.spaceId,
|
|
1644
2138
|
createdAt: profile?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
2139
|
+
posture: profile?.posture ?? "local-owner-key",
|
|
2140
|
+
operatorType: profile?.operatorType ?? "human",
|
|
1645
2141
|
authMethod: "local",
|
|
1646
2142
|
privateKey,
|
|
1647
2143
|
address
|
|
@@ -1650,6 +2146,7 @@ async function handleLocalAuth(profileName, host) {
|
|
|
1650
2146
|
authenticated: true,
|
|
1651
2147
|
profile: profileName,
|
|
1652
2148
|
did,
|
|
2149
|
+
sessionDid,
|
|
1653
2150
|
address,
|
|
1654
2151
|
spaceId: sessionResult.spaceId,
|
|
1655
2152
|
authMethod: "local"
|
|
@@ -1674,6 +2171,9 @@ async function handleOpenKeyAuth(profileName, host, paste) {
|
|
|
1674
2171
|
await ProfileManager.setSession(profileName, delegationData);
|
|
1675
2172
|
const updatedProfile = {
|
|
1676
2173
|
...profile,
|
|
2174
|
+
sessionDid: profile.sessionDid ?? profile.did,
|
|
2175
|
+
posture: profile.posture ?? "owner-openkey",
|
|
2176
|
+
operatorType: profile.operatorType ?? "human",
|
|
1677
2177
|
authMethod: "openkey"
|
|
1678
2178
|
};
|
|
1679
2179
|
if (delegationData.spaceId) {
|
|
@@ -1691,9 +2191,9 @@ async function handleOpenKeyAuth(profileName, host, paste) {
|
|
|
1691
2191
|
}
|
|
1692
2192
|
|
|
1693
2193
|
// src/commands/kv.ts
|
|
1694
|
-
import { readFile as
|
|
1695
|
-
import { writeFile as
|
|
1696
|
-
async function
|
|
2194
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
2195
|
+
import { writeFile as writeFile3 } from "fs/promises";
|
|
2196
|
+
async function readStdin2() {
|
|
1697
2197
|
const chunks = [];
|
|
1698
2198
|
for await (const chunk of process.stdin) {
|
|
1699
2199
|
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
@@ -1718,7 +2218,7 @@ function registerKvCommand(program2) {
|
|
|
1718
2218
|
const metadata = result.data.headers ?? {};
|
|
1719
2219
|
if (options.output) {
|
|
1720
2220
|
const content = typeof data === "string" ? data : JSON.stringify(data);
|
|
1721
|
-
await
|
|
2221
|
+
await writeFile3(options.output, content);
|
|
1722
2222
|
outputJson({ key, written: options.output });
|
|
1723
2223
|
return;
|
|
1724
2224
|
}
|
|
@@ -1755,9 +2255,9 @@ function registerKvCommand(program2) {
|
|
|
1755
2255
|
throw new CLIError("USAGE_ERROR", "Provide only one of: value argument, --file, or --stdin", ExitCode.USAGE_ERROR);
|
|
1756
2256
|
}
|
|
1757
2257
|
if (options.file) {
|
|
1758
|
-
putValue = await
|
|
2258
|
+
putValue = await readFile4(options.file);
|
|
1759
2259
|
} else if (options.stdin) {
|
|
1760
|
-
putValue = await
|
|
2260
|
+
putValue = await readStdin2();
|
|
1761
2261
|
} else {
|
|
1762
2262
|
try {
|
|
1763
2263
|
putValue = JSON.parse(value);
|
|
@@ -2225,10 +2725,19 @@ function registerProfileCommand(program2) {
|
|
|
2225
2725
|
name: p.name,
|
|
2226
2726
|
host: p.host,
|
|
2227
2727
|
did: p.did,
|
|
2728
|
+
posture: resolveProfilePosture(p),
|
|
2729
|
+
operatorType: resolveProfileOperatorType(p),
|
|
2228
2730
|
active: name === config.defaultProfile
|
|
2229
2731
|
};
|
|
2230
2732
|
} catch {
|
|
2231
|
-
return {
|
|
2733
|
+
return {
|
|
2734
|
+
name,
|
|
2735
|
+
host: null,
|
|
2736
|
+
did: null,
|
|
2737
|
+
posture: null,
|
|
2738
|
+
operatorType: null,
|
|
2739
|
+
active: name === config.defaultProfile
|
|
2740
|
+
};
|
|
2232
2741
|
}
|
|
2233
2742
|
})
|
|
2234
2743
|
);
|
|
@@ -2242,7 +2751,8 @@ function registerProfileCommand(program2) {
|
|
|
2242
2751
|
const marker = p.active ? theme.success("\u25CF ") : " ";
|
|
2243
2752
|
const name = p.active ? theme.brand(p.name) : p.name;
|
|
2244
2753
|
const host = theme.muted(p.host || "no host");
|
|
2245
|
-
|
|
2754
|
+
const posture = p.posture ? theme.muted(String(p.posture)) : theme.muted("no posture");
|
|
2755
|
+
process.stdout.write(`${marker}${name} ${host} ${posture}
|
|
2246
2756
|
`);
|
|
2247
2757
|
}
|
|
2248
2758
|
}
|
|
@@ -2250,10 +2760,18 @@ function registerProfileCommand(program2) {
|
|
|
2250
2760
|
handleError(error);
|
|
2251
2761
|
}
|
|
2252
2762
|
});
|
|
2253
|
-
profile.command("create <name>").description("Create a new profile").option("--host <url>", "TinyCloud node URL").
|
|
2763
|
+
profile.command("create <name>").description("Create a new profile").option("--host <url>", "TinyCloud node URL").option(
|
|
2764
|
+
"--posture <posture>",
|
|
2765
|
+
`Profile posture: ${CLI_PROFILE_POSTURES.join(", ")}. Defaults to owner-openkey.`
|
|
2766
|
+
).option(
|
|
2767
|
+
"--operator <type>",
|
|
2768
|
+
`Operator type: ${CLI_OPERATOR_TYPES.join(", ")}. Defaults to human.`
|
|
2769
|
+
).action(async (name, options, cmd) => {
|
|
2254
2770
|
try {
|
|
2255
2771
|
const globalOpts = cmd.optsWithGlobals();
|
|
2256
2772
|
const host = options.host ?? globalOpts.host ?? "https://node.tinycloud.xyz";
|
|
2773
|
+
const posture = parseProfilePosture(options.posture);
|
|
2774
|
+
const operatorType = parseOperatorType(options.operator);
|
|
2257
2775
|
if (await ProfileManager.profileExists(name)) {
|
|
2258
2776
|
throw new CLIError("PROFILE_EXISTS", `Profile "${name}" already exists`, ExitCode.ERROR);
|
|
2259
2777
|
}
|
|
@@ -2266,9 +2784,12 @@ function registerProfileCommand(program2) {
|
|
|
2266
2784
|
chainId: 1,
|
|
2267
2785
|
spaceName: "default",
|
|
2268
2786
|
did,
|
|
2269
|
-
|
|
2787
|
+
sessionDid: did,
|
|
2788
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2789
|
+
posture,
|
|
2790
|
+
operatorType
|
|
2270
2791
|
});
|
|
2271
|
-
outputJson({ profile: name, did, host, created: true });
|
|
2792
|
+
outputJson({ profile: name, did, host, posture, operatorType, created: true });
|
|
2272
2793
|
} catch (error) {
|
|
2273
2794
|
handleError(error);
|
|
2274
2795
|
}
|
|
@@ -2283,9 +2804,13 @@ function registerProfileCommand(program2) {
|
|
|
2283
2804
|
const hasSession = await ProfileManager.getSession(profileName) !== null;
|
|
2284
2805
|
const config = await ProfileManager.getConfig();
|
|
2285
2806
|
const isDefault = profileName === config.defaultProfile;
|
|
2807
|
+
const posture = resolveProfilePosture(p);
|
|
2808
|
+
const operatorType = resolveProfileOperatorType(p);
|
|
2286
2809
|
if (shouldOutputJson()) {
|
|
2287
2810
|
outputJson({
|
|
2288
2811
|
...p,
|
|
2812
|
+
posture,
|
|
2813
|
+
operatorType,
|
|
2289
2814
|
hasKey,
|
|
2290
2815
|
hasSession,
|
|
2291
2816
|
isDefault
|
|
@@ -2295,6 +2820,9 @@ function registerProfileCommand(program2) {
|
|
|
2295
2820
|
`);
|
|
2296
2821
|
process.stdout.write(formatField("Host", p.host) + "\n");
|
|
2297
2822
|
process.stdout.write(formatField("DID", p.did) + "\n");
|
|
2823
|
+
process.stdout.write(formatField("Session DID", p.sessionDid ?? null) + "\n");
|
|
2824
|
+
process.stdout.write(formatField("Posture", posture) + "\n");
|
|
2825
|
+
process.stdout.write(formatField("Operator", operatorType) + "\n");
|
|
2298
2826
|
process.stdout.write(formatField("Space", p.spaceId || null) + "\n");
|
|
2299
2827
|
process.stdout.write(formatField("Key", hasKey) + "\n");
|
|
2300
2828
|
process.stdout.write(formatField("Session", hasSession) + "\n");
|
|
@@ -2336,6 +2864,24 @@ function registerProfileCommand(program2) {
|
|
|
2336
2864
|
}
|
|
2337
2865
|
});
|
|
2338
2866
|
}
|
|
2867
|
+
function parseProfilePosture(raw) {
|
|
2868
|
+
if (raw === void 0 || raw === null || raw === "") return "owner-openkey";
|
|
2869
|
+
if (isCLIProfilePosture(raw)) return raw;
|
|
2870
|
+
throw new CLIError(
|
|
2871
|
+
"INVALID_POSTURE",
|
|
2872
|
+
`Invalid posture "${String(raw)}". Use one of: ${CLI_PROFILE_POSTURES.join(", ")}.`,
|
|
2873
|
+
ExitCode.USAGE_ERROR
|
|
2874
|
+
);
|
|
2875
|
+
}
|
|
2876
|
+
function parseOperatorType(raw) {
|
|
2877
|
+
if (raw === void 0 || raw === null || raw === "") return "human";
|
|
2878
|
+
if (isCLIOperatorType(raw)) return raw;
|
|
2879
|
+
throw new CLIError(
|
|
2880
|
+
"INVALID_OPERATOR",
|
|
2881
|
+
`Invalid operator "${String(raw)}". Use one of: ${CLI_OPERATOR_TYPES.join(", ")}.`,
|
|
2882
|
+
ExitCode.USAGE_ERROR
|
|
2883
|
+
);
|
|
2884
|
+
}
|
|
2339
2885
|
|
|
2340
2886
|
// src/commands/completion.ts
|
|
2341
2887
|
function registerCompletionCommand(program2) {
|
|
@@ -2468,10 +3014,10 @@ complete -c tc -l quiet -s q -d "Suppress non-essential output"
|
|
|
2468
3014
|
}
|
|
2469
3015
|
|
|
2470
3016
|
// src/commands/vault.ts
|
|
2471
|
-
import { readFile as
|
|
2472
|
-
import { writeFile as
|
|
3017
|
+
import { readFile as readFile5 } from "fs/promises";
|
|
3018
|
+
import { writeFile as writeFile4 } from "fs/promises";
|
|
2473
3019
|
import { PrivateKeySigner as PrivateKeySigner2 } from "@tinycloud/node-sdk";
|
|
2474
|
-
async function
|
|
3020
|
+
async function readStdin3() {
|
|
2475
3021
|
const chunks = [];
|
|
2476
3022
|
for await (const chunk of process.stdin) {
|
|
2477
3023
|
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
@@ -2526,9 +3072,9 @@ function registerVaultCommand(program2) {
|
|
|
2526
3072
|
throw new CLIError("USAGE_ERROR", "Provide only one of: value argument, --file, or --stdin", ExitCode.USAGE_ERROR);
|
|
2527
3073
|
}
|
|
2528
3074
|
if (options.file) {
|
|
2529
|
-
putValue = new Uint8Array(await
|
|
3075
|
+
putValue = new Uint8Array(await readFile5(options.file));
|
|
2530
3076
|
} else if (options.stdin) {
|
|
2531
|
-
putValue = new Uint8Array(await
|
|
3077
|
+
putValue = new Uint8Array(await readStdin3());
|
|
2532
3078
|
} else {
|
|
2533
3079
|
putValue = value;
|
|
2534
3080
|
}
|
|
@@ -2558,7 +3104,7 @@ function registerVaultCommand(program2) {
|
|
|
2558
3104
|
const data = result.data.data ?? result.data;
|
|
2559
3105
|
if (options.output) {
|
|
2560
3106
|
const content = data instanceof Uint8Array ? Buffer.from(data) : typeof data === "string" ? data : JSON.stringify(data);
|
|
2561
|
-
await
|
|
3107
|
+
await writeFile4(options.output, content);
|
|
2562
3108
|
outputJson({ key, written: options.output });
|
|
2563
3109
|
return;
|
|
2564
3110
|
}
|
|
@@ -2641,9 +3187,9 @@ function registerVaultCommand(program2) {
|
|
|
2641
3187
|
}
|
|
2642
3188
|
|
|
2643
3189
|
// src/commands/secrets.ts
|
|
2644
|
-
import { readFile as
|
|
2645
|
-
import { writeFile as
|
|
2646
|
-
async function
|
|
3190
|
+
import { readFile as readFile6 } from "fs/promises";
|
|
3191
|
+
import { writeFile as writeFile5 } from "fs/promises";
|
|
3192
|
+
async function readStdin4() {
|
|
2647
3193
|
const chunks = [];
|
|
2648
3194
|
for await (const chunk of process.stdin) {
|
|
2649
3195
|
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
@@ -2741,7 +3287,7 @@ function registerSecretsCommand(program2) {
|
|
|
2741
3287
|
}
|
|
2742
3288
|
const value = String(result.data);
|
|
2743
3289
|
if (options.output) {
|
|
2744
|
-
await
|
|
3290
|
+
await writeFile5(options.output, value);
|
|
2745
3291
|
outputJson({ name, written: options.output });
|
|
2746
3292
|
return;
|
|
2747
3293
|
}
|
|
@@ -2768,9 +3314,9 @@ function registerSecretsCommand(program2) {
|
|
|
2768
3314
|
throw new CLIError("USAGE_ERROR", "Provide only one of: value argument, --file, or --stdin", ExitCode.USAGE_ERROR);
|
|
2769
3315
|
}
|
|
2770
3316
|
if (options.file) {
|
|
2771
|
-
secretValue = await
|
|
3317
|
+
secretValue = await readFile6(options.file, "utf-8");
|
|
2772
3318
|
} else if (options.stdin) {
|
|
2773
|
-
secretValue = (await
|
|
3319
|
+
secretValue = (await readStdin4()).toString("utf-8");
|
|
2774
3320
|
} else {
|
|
2775
3321
|
secretValue = value;
|
|
2776
3322
|
}
|
|
@@ -2848,10 +3394,10 @@ function registerSecretsCommand(program2) {
|
|
|
2848
3394
|
}
|
|
2849
3395
|
|
|
2850
3396
|
// src/commands/vars.ts
|
|
2851
|
-
import { readFile as
|
|
2852
|
-
import { writeFile as
|
|
3397
|
+
import { readFile as readFile7 } from "fs/promises";
|
|
3398
|
+
import { writeFile as writeFile6 } from "fs/promises";
|
|
2853
3399
|
var VARIABLES_PREFIX = "variables/";
|
|
2854
|
-
async function
|
|
3400
|
+
async function readStdin5() {
|
|
2855
3401
|
const chunks = [];
|
|
2856
3402
|
for await (const chunk of process.stdin) {
|
|
2857
3403
|
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
@@ -2921,7 +3467,7 @@ function registerVarsCommand(program2) {
|
|
|
2921
3467
|
value = typeof data === "string" ? data : JSON.stringify(data);
|
|
2922
3468
|
}
|
|
2923
3469
|
if (options.output) {
|
|
2924
|
-
await
|
|
3470
|
+
await writeFile6(options.output, value);
|
|
2925
3471
|
outputJson({ name, written: options.output });
|
|
2926
3472
|
return;
|
|
2927
3473
|
}
|
|
@@ -2949,9 +3495,9 @@ function registerVarsCommand(program2) {
|
|
|
2949
3495
|
throw new CLIError("USAGE_ERROR", "Provide only one of: value argument, --file, or --stdin", ExitCode.USAGE_ERROR);
|
|
2950
3496
|
}
|
|
2951
3497
|
if (options.file) {
|
|
2952
|
-
varValue = await
|
|
3498
|
+
varValue = await readFile7(options.file, "utf-8");
|
|
2953
3499
|
} else if (options.stdin) {
|
|
2954
|
-
varValue = (await
|
|
3500
|
+
varValue = (await readStdin5()).toString("utf-8");
|
|
2955
3501
|
} else {
|
|
2956
3502
|
varValue = value;
|
|
2957
3503
|
}
|
|
@@ -3095,7 +3641,7 @@ function registerDoctorCommand(program2) {
|
|
|
3095
3641
|
}
|
|
3096
3642
|
|
|
3097
3643
|
// src/commands/sql.ts
|
|
3098
|
-
import { writeFile as
|
|
3644
|
+
import { writeFile as writeFile7 } from "fs/promises";
|
|
3099
3645
|
import { resolve } from "path";
|
|
3100
3646
|
async function dbHandle(node, dbName, spaceInput, profileName) {
|
|
3101
3647
|
const spaceUri = await resolveSpaceUri(spaceInput, profileName);
|
|
@@ -3231,7 +3777,7 @@ Output:
|
|
|
3231
3777
|
const blob = result.data;
|
|
3232
3778
|
const buffer = Buffer.from(await blob.arrayBuffer());
|
|
3233
3779
|
const outputPath = resolve(options.output);
|
|
3234
|
-
await
|
|
3780
|
+
await writeFile7(outputPath, buffer);
|
|
3235
3781
|
outputJson({
|
|
3236
3782
|
file: outputPath,
|
|
3237
3783
|
size: blob.size,
|
|
@@ -3360,7 +3906,7 @@ function quoteIdent(name) {
|
|
|
3360
3906
|
}
|
|
3361
3907
|
|
|
3362
3908
|
// src/commands/duckdb.ts
|
|
3363
|
-
import { readFile as
|
|
3909
|
+
import { readFile as readFile8, writeFile as writeFile8 } from "fs/promises";
|
|
3364
3910
|
import { resolve as resolve2 } from "path";
|
|
3365
3911
|
function registerDuckdbCommand(program2) {
|
|
3366
3912
|
const duckdb = program2.command("duckdb").description("DuckDB database operations");
|
|
@@ -3474,7 +4020,7 @@ ${rowCount} row${rowCount === 1 ? "" : "s"} returned`) + "\n");
|
|
|
3474
4020
|
const blob = result.data;
|
|
3475
4021
|
const buffer = Buffer.from(await blob.arrayBuffer());
|
|
3476
4022
|
const outputPath = resolve2(options.output);
|
|
3477
|
-
await
|
|
4023
|
+
await writeFile8(outputPath, buffer);
|
|
3478
4024
|
outputJson({
|
|
3479
4025
|
file: outputPath,
|
|
3480
4026
|
size: blob.size,
|
|
@@ -3490,7 +4036,7 @@ ${rowCount} row${rowCount === 1 ? "" : "s"} returned`) + "\n");
|
|
|
3490
4036
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
3491
4037
|
const node = await ensureAuthenticated(ctx);
|
|
3492
4038
|
const filePath = resolve2(file);
|
|
3493
|
-
const bytes = new Uint8Array(await
|
|
4039
|
+
const bytes = new Uint8Array(await readFile8(filePath));
|
|
3494
4040
|
const result = await withSpinner(
|
|
3495
4041
|
"Importing database...",
|
|
3496
4042
|
() => node.duckdb.db(options.db).import(bytes)
|
|
@@ -3511,7 +4057,7 @@ ${rowCount} row${rowCount === 1 ? "" : "s"} returned`) + "\n");
|
|
|
3511
4057
|
}
|
|
3512
4058
|
|
|
3513
4059
|
// src/commands/manifest.ts
|
|
3514
|
-
import { readFile as
|
|
4060
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
3515
4061
|
var DEFAULT_APP_SPACE = "applications";
|
|
3516
4062
|
function registerManifestCommand(program2) {
|
|
3517
4063
|
const manifest = program2.command("manifest").description("Inspect TinyCloud app manifests");
|
|
@@ -3619,7 +4165,7 @@ async function loadManifestSource(source) {
|
|
|
3619
4165
|
}
|
|
3620
4166
|
return response.text();
|
|
3621
4167
|
}
|
|
3622
|
-
return
|
|
4168
|
+
return readFile9(source, "utf8");
|
|
3623
4169
|
}
|
|
3624
4170
|
function prefixWithAppId(path, appId) {
|
|
3625
4171
|
const slash = path.indexOf("/");
|
|
@@ -3764,6 +4310,7 @@ ${theme.heading("Examples:")}
|
|
|
3764
4310
|
${theme.command("tc auth login")} ${theme.muted("Authenticate via browser")}
|
|
3765
4311
|
${theme.command('tc kv put greeting "Hello"')} ${theme.muted("Store a value")}
|
|
3766
4312
|
${theme.command("tc kv list")} ${theme.muted("List all keys")}
|
|
4313
|
+
${theme.command("tc secrets network init")} ${theme.muted("Create the default secrets network")}
|
|
3767
4314
|
${theme.command("tc delegation create --to did:pkh:...")} ${theme.muted("Grant access to another user")}
|
|
3768
4315
|
${theme.command("tc space list")} ${theme.muted("Show your spaces")}
|
|
3769
4316
|
|