@tinycloud/cli 0.6.0-beta.6 → 0.6.0-beta.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +541 -35
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -620,6 +620,25 @@ async function localKeySignIn(options) {
|
|
|
620
620
|
// src/auth/browser-auth.ts
|
|
621
621
|
import { createServer } from "http";
|
|
622
622
|
import { createInterface } from "readline";
|
|
623
|
+
var PRIVATE_JWK_FIELDS = /* @__PURE__ */ new Set([
|
|
624
|
+
"d",
|
|
625
|
+
"p",
|
|
626
|
+
"q",
|
|
627
|
+
"dp",
|
|
628
|
+
"dq",
|
|
629
|
+
"qi",
|
|
630
|
+
"oth",
|
|
631
|
+
"k"
|
|
632
|
+
]);
|
|
633
|
+
function publicJwkForDelegation(jwk) {
|
|
634
|
+
const publicJwk = {};
|
|
635
|
+
for (const [key, value] of Object.entries(jwk)) {
|
|
636
|
+
if (!PRIVATE_JWK_FIELDS.has(key)) {
|
|
637
|
+
publicJwk[key] = value;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
return publicJwk;
|
|
641
|
+
}
|
|
623
642
|
async function startAuthFlow(did, options = {}) {
|
|
624
643
|
if (options.paste) {
|
|
625
644
|
return pasteFlow(did, options);
|
|
@@ -641,7 +660,9 @@ function buildAuthUrl(did, options = {}) {
|
|
|
641
660
|
params.set("callback", options.callback);
|
|
642
661
|
}
|
|
643
662
|
if (options.jwk) {
|
|
644
|
-
const jwkB64 = Buffer.from(
|
|
663
|
+
const jwkB64 = Buffer.from(
|
|
664
|
+
JSON.stringify(publicJwkForDelegation(options.jwk))
|
|
665
|
+
).toString("base64url");
|
|
645
666
|
params.set("jwk", jwkB64);
|
|
646
667
|
}
|
|
647
668
|
if (options.host) {
|
|
@@ -2045,18 +2066,33 @@ function parseExpiryOption(raw) {
|
|
|
2045
2066
|
}
|
|
2046
2067
|
function groupPermissionsBySpace(permissions) {
|
|
2047
2068
|
const groups = /* @__PURE__ */ new Map();
|
|
2069
|
+
const rawEntries = [];
|
|
2048
2070
|
for (const permission of permissions) {
|
|
2071
|
+
if (isRawPermission(permission)) {
|
|
2072
|
+
rawEntries.push(permission);
|
|
2073
|
+
continue;
|
|
2074
|
+
}
|
|
2049
2075
|
const group = groups.get(permission.space) ?? [];
|
|
2050
2076
|
group.push(permission);
|
|
2051
2077
|
groups.set(permission.space, group);
|
|
2052
2078
|
}
|
|
2053
|
-
|
|
2079
|
+
const grouped = Array.from(groups.values());
|
|
2080
|
+
if (grouped.length === 0) {
|
|
2081
|
+
return rawEntries.length > 0 ? [rawEntries] : [];
|
|
2082
|
+
}
|
|
2083
|
+
grouped[0].push(...rawEntries);
|
|
2084
|
+
return grouped;
|
|
2085
|
+
}
|
|
2086
|
+
function isRawPermission(permission) {
|
|
2087
|
+
return permission.service === "tinycloud.encryption" && permission.path.startsWith("urn:tinycloud:encryption:");
|
|
2054
2088
|
}
|
|
2055
2089
|
function portableFromOpenKeyDelegation(data, permissions, host) {
|
|
2056
|
-
const primary = permissions[0];
|
|
2057
|
-
const returnedSpace = String(data.spaceId ?? primary.space);
|
|
2058
|
-
const expectedSpaces = new Set(
|
|
2059
|
-
|
|
2090
|
+
const primary = permissions.find((permission) => !isRawPermission(permission)) ?? permissions[0];
|
|
2091
|
+
const returnedSpace = String(data.spaceId ?? primary.space ?? "encryption");
|
|
2092
|
+
const expectedSpaces = new Set(
|
|
2093
|
+
permissions.filter((permission) => !isRawPermission(permission)).map((permission) => permission.space)
|
|
2094
|
+
);
|
|
2095
|
+
if (expectedSpaces.size > 0 && (expectedSpaces.size !== 1 || !expectedSpaces.has(returnedSpace))) {
|
|
2060
2096
|
throw new CLIError(
|
|
2061
2097
|
"OPENKEY_SCOPE_MISMATCH",
|
|
2062
2098
|
`OpenKey returned delegation for ${returnedSpace}, expected ${Array.from(expectedSpaces).join(", ")}.`,
|
|
@@ -2172,6 +2208,16 @@ async function handleLocalAuth(profileName, host) {
|
|
|
2172
2208
|
});
|
|
2173
2209
|
}
|
|
2174
2210
|
async function handleOpenKeyAuth(profileName, host, paste) {
|
|
2211
|
+
const { profile, delegationData } = await refreshOpenKeySession(profileName, host, { paste });
|
|
2212
|
+
outputJson({
|
|
2213
|
+
authenticated: true,
|
|
2214
|
+
profile: profileName,
|
|
2215
|
+
did: profile.did,
|
|
2216
|
+
spaceId: delegationData.spaceId,
|
|
2217
|
+
authMethod: "openkey"
|
|
2218
|
+
});
|
|
2219
|
+
}
|
|
2220
|
+
async function refreshOpenKeySession(profileName, host, options = {}) {
|
|
2175
2221
|
const key = await ProfileManager.getKey(profileName);
|
|
2176
2222
|
if (!key) {
|
|
2177
2223
|
throw new CLIError(
|
|
@@ -2182,7 +2228,7 @@ async function handleOpenKeyAuth(profileName, host, paste) {
|
|
|
2182
2228
|
}
|
|
2183
2229
|
const profile = await ProfileManager.getProfile(profileName);
|
|
2184
2230
|
const delegationData = await startAuthFlow(profile.did, {
|
|
2185
|
-
paste,
|
|
2231
|
+
paste: options.paste,
|
|
2186
2232
|
jwk: key,
|
|
2187
2233
|
host,
|
|
2188
2234
|
openkeyHost: resolveOpenKeyHost(profile)
|
|
@@ -2200,13 +2246,7 @@ async function handleOpenKeyAuth(profileName, host, paste) {
|
|
|
2200
2246
|
updatedProfile.ownerDid = delegationData.ownerDid;
|
|
2201
2247
|
}
|
|
2202
2248
|
await ProfileManager.setProfile(profileName, updatedProfile);
|
|
2203
|
-
|
|
2204
|
-
authenticated: true,
|
|
2205
|
-
profile: profileName,
|
|
2206
|
-
did: profile.did,
|
|
2207
|
-
spaceId: delegationData.spaceId,
|
|
2208
|
-
authMethod: "openkey"
|
|
2209
|
-
});
|
|
2249
|
+
return { profile: updatedProfile, delegationData };
|
|
2210
2250
|
}
|
|
2211
2251
|
|
|
2212
2252
|
// src/commands/kv.ts
|
|
@@ -3217,6 +3257,11 @@ function registerVaultCommand(program2) {
|
|
|
3217
3257
|
// src/commands/secrets.ts
|
|
3218
3258
|
import { readFile as readFile6 } from "fs/promises";
|
|
3219
3259
|
import { writeFile as writeFile5 } from "fs/promises";
|
|
3260
|
+
import {
|
|
3261
|
+
resolveSecretListPrefix,
|
|
3262
|
+
resolveSecretPath
|
|
3263
|
+
} from "@tinycloud/node-sdk";
|
|
3264
|
+
var SECRETS_SPACE = "secrets";
|
|
3220
3265
|
async function readStdin4() {
|
|
3221
3266
|
const chunks = [];
|
|
3222
3267
|
for await (const chunk of process.stdin) {
|
|
@@ -3232,6 +3277,95 @@ function resolveSecretScope(options) {
|
|
|
3232
3277
|
const scope = options.scope ?? options.space;
|
|
3233
3278
|
return scope ? { scope } : void 0;
|
|
3234
3279
|
}
|
|
3280
|
+
async function ensureSecretsNode(ctx, options) {
|
|
3281
|
+
const auth = authOptions(options);
|
|
3282
|
+
if (auth?.privateKey) {
|
|
3283
|
+
return ensureAuthenticated(ctx, auth);
|
|
3284
|
+
}
|
|
3285
|
+
const profile = await ProfileManager.getProfile(ctx.profile).catch(() => null);
|
|
3286
|
+
if (profile?.authMethod === "openkey" && canRequestOwnerPermissions(profile)) {
|
|
3287
|
+
const session = await ProfileManager.getSession(ctx.profile);
|
|
3288
|
+
if (!session || isStoredSessionExpired(session)) {
|
|
3289
|
+
await withSpinner(
|
|
3290
|
+
session ? "Refreshing TinyCloud session..." : "Creating TinyCloud session...",
|
|
3291
|
+
() => refreshOpenKeySession(ctx.profile, ctx.host)
|
|
3292
|
+
);
|
|
3293
|
+
}
|
|
3294
|
+
}
|
|
3295
|
+
return ensureAuthenticated(ctx, auth);
|
|
3296
|
+
}
|
|
3297
|
+
async function runSecretOperation(params) {
|
|
3298
|
+
const first = await withSpinner(params.label, params.operation);
|
|
3299
|
+
if (first.ok || !shouldRequestSecretPermissions(first.error)) {
|
|
3300
|
+
return first;
|
|
3301
|
+
}
|
|
3302
|
+
const profile = await ProfileManager.getProfile(params.ctx.profile);
|
|
3303
|
+
if (!canRequestOwnerPermissions(profile)) {
|
|
3304
|
+
return first;
|
|
3305
|
+
}
|
|
3306
|
+
const requested = secretPermissionEntries({
|
|
3307
|
+
action: params.action,
|
|
3308
|
+
name: params.name,
|
|
3309
|
+
options: params.scopeOptions,
|
|
3310
|
+
node: params.node
|
|
3311
|
+
});
|
|
3312
|
+
await withSpinner(
|
|
3313
|
+
"Requesting secret permissions...",
|
|
3314
|
+
() => ensureDelegationAuthority({
|
|
3315
|
+
ctx: params.ctx,
|
|
3316
|
+
profile,
|
|
3317
|
+
node: params.node,
|
|
3318
|
+
requested,
|
|
3319
|
+
expiryOption: void 0,
|
|
3320
|
+
yes: true
|
|
3321
|
+
})
|
|
3322
|
+
);
|
|
3323
|
+
return withSpinner(params.label, params.operation);
|
|
3324
|
+
}
|
|
3325
|
+
function canRequestOwnerPermissions(profile) {
|
|
3326
|
+
const posture = resolveProfilePosture(profile);
|
|
3327
|
+
return posture === "owner-openkey" || posture === "local-owner-key";
|
|
3328
|
+
}
|
|
3329
|
+
function shouldRequestSecretPermissions(error) {
|
|
3330
|
+
if (error.code !== "PERMISSION_DENIED") return false;
|
|
3331
|
+
return /permission|session expired|autosign|capabilit/i.test(error.message);
|
|
3332
|
+
}
|
|
3333
|
+
function isStoredSessionExpired(session) {
|
|
3334
|
+
const record = session;
|
|
3335
|
+
const direct = parseDate(record.expiresAt ?? record.expiry ?? record.expirationTime);
|
|
3336
|
+
if (direct) return direct.getTime() <= Date.now();
|
|
3337
|
+
if (typeof record.siwe !== "string") return false;
|
|
3338
|
+
const match = record.siwe.match(/^Expiration Time:\s*(.+)$/im);
|
|
3339
|
+
const expiry = match ? parseDate(match[1].trim()) : null;
|
|
3340
|
+
return expiry !== null && expiry.getTime() <= Date.now();
|
|
3341
|
+
}
|
|
3342
|
+
function parseDate(value) {
|
|
3343
|
+
if (value instanceof Date) {
|
|
3344
|
+
return Number.isNaN(value.getTime()) ? null : value;
|
|
3345
|
+
}
|
|
3346
|
+
if (typeof value !== "string" || value.trim() === "") return null;
|
|
3347
|
+
const date = new Date(value);
|
|
3348
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
3349
|
+
}
|
|
3350
|
+
function secretPermissionEntries(params) {
|
|
3351
|
+
const path = params.action === "list" ? resolveSecretListPrefix(params.options) : resolveSecretPath(params.name ?? "", params.options).permissionPaths.vault;
|
|
3352
|
+
const permissions = [{
|
|
3353
|
+
service: "tinycloud.kv",
|
|
3354
|
+
space: SECRETS_SPACE,
|
|
3355
|
+
path,
|
|
3356
|
+
actions: [params.action],
|
|
3357
|
+
skipPrefix: true
|
|
3358
|
+
}];
|
|
3359
|
+
if (params.action === "get") {
|
|
3360
|
+
permissions.push({
|
|
3361
|
+
service: "tinycloud.encryption",
|
|
3362
|
+
path: params.node.getDefaultEncryptionNetworkId(),
|
|
3363
|
+
actions: ["decrypt"],
|
|
3364
|
+
skipPrefix: true
|
|
3365
|
+
});
|
|
3366
|
+
}
|
|
3367
|
+
return permissions;
|
|
3368
|
+
}
|
|
3235
3369
|
function registerSecretsCommand(program2) {
|
|
3236
3370
|
const secrets = program2.command("secrets").description("Encrypted secrets management");
|
|
3237
3371
|
const network = secrets.command("network").description("Manage the default secrets encryption network");
|
|
@@ -3277,12 +3411,16 @@ function registerSecretsCommand(program2) {
|
|
|
3277
3411
|
try {
|
|
3278
3412
|
const globalOpts = cmd.optsWithGlobals();
|
|
3279
3413
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
3280
|
-
const node = await
|
|
3414
|
+
const node = await ensureSecretsNode(ctx, options);
|
|
3281
3415
|
const scopeOptions = resolveSecretScope(options);
|
|
3282
|
-
const result = await
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3416
|
+
const result = await runSecretOperation({
|
|
3417
|
+
ctx,
|
|
3418
|
+
node,
|
|
3419
|
+
action: "list",
|
|
3420
|
+
scopeOptions,
|
|
3421
|
+
label: "Listing secrets...",
|
|
3422
|
+
operation: () => node.secrets.list(scopeOptions)
|
|
3423
|
+
});
|
|
3286
3424
|
if (!result.ok) {
|
|
3287
3425
|
throw new CLIError(result.error.code, result.error.message, ExitCode.ERROR);
|
|
3288
3426
|
}
|
|
@@ -3301,12 +3439,17 @@ function registerSecretsCommand(program2) {
|
|
|
3301
3439
|
try {
|
|
3302
3440
|
const globalOpts = cmd.optsWithGlobals();
|
|
3303
3441
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
3304
|
-
const node = await
|
|
3442
|
+
const node = await ensureSecretsNode(ctx, options);
|
|
3305
3443
|
const scopeOptions = resolveSecretScope(options);
|
|
3306
|
-
const result = await
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3444
|
+
const result = await runSecretOperation({
|
|
3445
|
+
ctx,
|
|
3446
|
+
node,
|
|
3447
|
+
action: "get",
|
|
3448
|
+
name,
|
|
3449
|
+
scopeOptions,
|
|
3450
|
+
label: `Getting secret ${name}...`,
|
|
3451
|
+
operation: () => node.secrets.get(name, scopeOptions)
|
|
3452
|
+
});
|
|
3310
3453
|
if (!result.ok) {
|
|
3311
3454
|
if (result.error.code === "NOT_FOUND" || result.error.code === "KEY_NOT_FOUND") {
|
|
3312
3455
|
throw new CLIError("NOT_FOUND", `Secret "${name}" not found`, ExitCode.NOT_FOUND);
|
|
@@ -3332,7 +3475,7 @@ function registerSecretsCommand(program2) {
|
|
|
3332
3475
|
try {
|
|
3333
3476
|
const globalOpts = cmd.optsWithGlobals();
|
|
3334
3477
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
3335
|
-
const node = await
|
|
3478
|
+
const node = await ensureSecretsNode(ctx, options);
|
|
3336
3479
|
let secretValue;
|
|
3337
3480
|
const sources = [value !== void 0, !!options.file, !!options.stdin].filter(Boolean);
|
|
3338
3481
|
if (sources.length === 0) {
|
|
@@ -3349,10 +3492,15 @@ function registerSecretsCommand(program2) {
|
|
|
3349
3492
|
secretValue = value;
|
|
3350
3493
|
}
|
|
3351
3494
|
const scopeOptions = resolveSecretScope(options);
|
|
3352
|
-
const result = await
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3495
|
+
const result = await runSecretOperation({
|
|
3496
|
+
ctx,
|
|
3497
|
+
node,
|
|
3498
|
+
action: "put",
|
|
3499
|
+
name,
|
|
3500
|
+
scopeOptions,
|
|
3501
|
+
label: `Storing secret ${name}...`,
|
|
3502
|
+
operation: () => node.secrets.put(name, secretValue, scopeOptions)
|
|
3503
|
+
});
|
|
3356
3504
|
if (!result.ok) {
|
|
3357
3505
|
throw new CLIError(result.error.code, result.error.message, ExitCode.ERROR);
|
|
3358
3506
|
}
|
|
@@ -3365,12 +3513,17 @@ function registerSecretsCommand(program2) {
|
|
|
3365
3513
|
try {
|
|
3366
3514
|
const globalOpts = cmd.optsWithGlobals();
|
|
3367
3515
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
3368
|
-
const node = await
|
|
3516
|
+
const node = await ensureSecretsNode(ctx, options);
|
|
3369
3517
|
const scopeOptions = resolveSecretScope(options);
|
|
3370
|
-
const result = await
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3518
|
+
const result = await runSecretOperation({
|
|
3519
|
+
ctx,
|
|
3520
|
+
node,
|
|
3521
|
+
action: "del",
|
|
3522
|
+
name,
|
|
3523
|
+
scopeOptions,
|
|
3524
|
+
label: `Deleting secret ${name}...`,
|
|
3525
|
+
operation: () => node.secrets.delete(name, scopeOptions)
|
|
3526
|
+
});
|
|
3374
3527
|
if (!result.ok) {
|
|
3375
3528
|
throw new CLIError(result.error.code, result.error.message, ExitCode.ERROR);
|
|
3376
3529
|
}
|
|
@@ -4281,6 +4434,358 @@ function registerUpgradeCommand(program2) {
|
|
|
4281
4434
|
});
|
|
4282
4435
|
}
|
|
4283
4436
|
|
|
4437
|
+
// src/commands/status.ts
|
|
4438
|
+
import {
|
|
4439
|
+
NodeWasmBindings
|
|
4440
|
+
} from "@tinycloud/node-sdk";
|
|
4441
|
+
var wasmBindings = null;
|
|
4442
|
+
function registerStatusCommand(program2) {
|
|
4443
|
+
program2.command("status").description("Show local TinyCloud profile, session, delegation, and permission state").action(async (_options, cmd) => {
|
|
4444
|
+
try {
|
|
4445
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
4446
|
+
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
4447
|
+
const config = await ProfileManager.getConfig();
|
|
4448
|
+
const names = (await ProfileManager.listProfiles()).sort(
|
|
4449
|
+
(a, b) => a.localeCompare(b)
|
|
4450
|
+
);
|
|
4451
|
+
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4452
|
+
const profiles = await Promise.all(
|
|
4453
|
+
names.map(
|
|
4454
|
+
(name) => inspectProfile({
|
|
4455
|
+
name,
|
|
4456
|
+
activeProfile: ctx.profile,
|
|
4457
|
+
defaultProfile: config.defaultProfile
|
|
4458
|
+
})
|
|
4459
|
+
)
|
|
4460
|
+
);
|
|
4461
|
+
const summary = {
|
|
4462
|
+
generatedAt,
|
|
4463
|
+
activeProfile: ctx.profile,
|
|
4464
|
+
defaultProfile: config.defaultProfile,
|
|
4465
|
+
profileCount: profiles.length,
|
|
4466
|
+
authenticatedProfileCount: profiles.filter((p) => p.authenticated).length,
|
|
4467
|
+
activeDelegationCount: profiles.reduce(
|
|
4468
|
+
(sum, profile) => sum + profile.activeDelegationCount,
|
|
4469
|
+
0
|
|
4470
|
+
),
|
|
4471
|
+
profiles
|
|
4472
|
+
};
|
|
4473
|
+
if (shouldOutputJson()) {
|
|
4474
|
+
outputJson(summary);
|
|
4475
|
+
return;
|
|
4476
|
+
}
|
|
4477
|
+
process.stdout.write(formatStatus(summary));
|
|
4478
|
+
} catch (error) {
|
|
4479
|
+
handleError(error);
|
|
4480
|
+
}
|
|
4481
|
+
});
|
|
4482
|
+
}
|
|
4483
|
+
async function inspectProfile(params) {
|
|
4484
|
+
const issues = [];
|
|
4485
|
+
const profile = await readProfile(params.name, issues);
|
|
4486
|
+
const session = await readSession(params.name, issues);
|
|
4487
|
+
const hasKey = await readHasKey(params.name, issues);
|
|
4488
|
+
const storedDelegations = await readDelegations(params.name, issues);
|
|
4489
|
+
const sessionPermissions = session ? sessionPermissionsFromRecap(session) : [];
|
|
4490
|
+
const sessionExpiry = session ? extractSessionExpiry(session) : null;
|
|
4491
|
+
const sessionExpired = sessionExpiry === null ? null : sessionExpiry.getTime() <= Date.now();
|
|
4492
|
+
const statusSession = {
|
|
4493
|
+
present: session !== null,
|
|
4494
|
+
expired: session === null ? null : sessionExpired,
|
|
4495
|
+
expiresAt: sessionExpiry?.toISOString() ?? null,
|
|
4496
|
+
permissions: sessionPermissions,
|
|
4497
|
+
permissionsCompact: compactPermissions(sessionPermissions)
|
|
4498
|
+
};
|
|
4499
|
+
const delegations = storedDelegations.map(inspectDelegation);
|
|
4500
|
+
const activeDelegationPermissions = delegations.filter((delegation) => delegation.active).flatMap((delegation) => delegation.permissions);
|
|
4501
|
+
const permissions = uniquePermissions([
|
|
4502
|
+
...sessionPermissions,
|
|
4503
|
+
...activeDelegationPermissions
|
|
4504
|
+
]);
|
|
4505
|
+
const hasPrivateKey = typeof profile?.privateKey === "string" && profile.privateKey.length > 0;
|
|
4506
|
+
const localKeyAuthenticated = profile?.authMethod === "local" && hasPrivateKey;
|
|
4507
|
+
const sessionAuthenticated = session !== null && sessionExpired !== true;
|
|
4508
|
+
const authenticated = localKeyAuthenticated || sessionAuthenticated;
|
|
4509
|
+
const status = resolveStatus({
|
|
4510
|
+
exists: profile !== null,
|
|
4511
|
+
authenticated,
|
|
4512
|
+
localKeyAuthenticated,
|
|
4513
|
+
sessionExpired
|
|
4514
|
+
});
|
|
4515
|
+
return {
|
|
4516
|
+
name: params.name,
|
|
4517
|
+
active: params.name === params.activeProfile,
|
|
4518
|
+
default: params.name === params.defaultProfile,
|
|
4519
|
+
exists: profile !== null,
|
|
4520
|
+
status,
|
|
4521
|
+
host: profile?.host ?? null,
|
|
4522
|
+
did: profile?.did ?? null,
|
|
4523
|
+
sessionDid: profile?.sessionDid ?? null,
|
|
4524
|
+
ownerDid: profile?.ownerDid ?? null,
|
|
4525
|
+
address: profile?.address ?? null,
|
|
4526
|
+
spaceId: profile?.spaceId ?? null,
|
|
4527
|
+
authMethod: profile?.authMethod ?? null,
|
|
4528
|
+
posture: profile ? resolveProfilePosture(profile) : null,
|
|
4529
|
+
operatorType: profile ? resolveProfileOperatorType(profile) : null,
|
|
4530
|
+
hasKey,
|
|
4531
|
+
hasPrivateKey,
|
|
4532
|
+
authenticated,
|
|
4533
|
+
session: statusSession,
|
|
4534
|
+
delegations,
|
|
4535
|
+
permissions,
|
|
4536
|
+
permissionsCompact: compactPermissions(permissions),
|
|
4537
|
+
permissionCount: permissions.length,
|
|
4538
|
+
activeDelegationCount: delegations.filter((delegation) => delegation.active).length,
|
|
4539
|
+
delegationCount: delegations.length,
|
|
4540
|
+
issues
|
|
4541
|
+
};
|
|
4542
|
+
}
|
|
4543
|
+
async function readProfile(name, issues) {
|
|
4544
|
+
try {
|
|
4545
|
+
return await ProfileManager.getProfile(name);
|
|
4546
|
+
} catch (error) {
|
|
4547
|
+
issues.push(`profile: ${messageFromError(error)}`);
|
|
4548
|
+
return null;
|
|
4549
|
+
}
|
|
4550
|
+
}
|
|
4551
|
+
async function readSession(name, issues) {
|
|
4552
|
+
try {
|
|
4553
|
+
return asRecord(await ProfileManager.getSession(name));
|
|
4554
|
+
} catch (error) {
|
|
4555
|
+
issues.push(`session: ${messageFromError(error)}`);
|
|
4556
|
+
return null;
|
|
4557
|
+
}
|
|
4558
|
+
}
|
|
4559
|
+
async function readHasKey(name, issues) {
|
|
4560
|
+
try {
|
|
4561
|
+
return await ProfileManager.getKey(name) !== null;
|
|
4562
|
+
} catch (error) {
|
|
4563
|
+
issues.push(`key: ${messageFromError(error)}`);
|
|
4564
|
+
return false;
|
|
4565
|
+
}
|
|
4566
|
+
}
|
|
4567
|
+
async function readDelegations(name, issues) {
|
|
4568
|
+
try {
|
|
4569
|
+
return await loadAdditionalDelegations(name);
|
|
4570
|
+
} catch (error) {
|
|
4571
|
+
issues.push(`delegations: ${messageFromError(error)}`);
|
|
4572
|
+
return [];
|
|
4573
|
+
}
|
|
4574
|
+
}
|
|
4575
|
+
function inspectDelegation(entry) {
|
|
4576
|
+
const expiry = parseDate2(entry.delegation.expiry);
|
|
4577
|
+
const expired = expiry === null ? null : expiry.getTime() <= Date.now();
|
|
4578
|
+
const permissions = normalizePermissions(
|
|
4579
|
+
Array.isArray(entry.permissions) && entry.permissions.length > 0 ? entry.permissions : permissionsFromDelegation(entry.delegation)
|
|
4580
|
+
);
|
|
4581
|
+
return {
|
|
4582
|
+
cid: entry.delegation.cid,
|
|
4583
|
+
active: expired !== true,
|
|
4584
|
+
expired,
|
|
4585
|
+
expiresAt: expiry?.toISOString() ?? null,
|
|
4586
|
+
permissions,
|
|
4587
|
+
permissionsCompact: compactPermissions(permissions)
|
|
4588
|
+
};
|
|
4589
|
+
}
|
|
4590
|
+
function resolveStatus(params) {
|
|
4591
|
+
if (!params.exists) return "missing";
|
|
4592
|
+
if (params.localKeyAuthenticated) return "local-key";
|
|
4593
|
+
if (params.authenticated) return "logged-in";
|
|
4594
|
+
if (params.sessionExpired === true) return "expired";
|
|
4595
|
+
return "signed-out";
|
|
4596
|
+
}
|
|
4597
|
+
function sessionPermissionsFromRecap(session) {
|
|
4598
|
+
if (typeof session.siwe !== "string" || session.siwe.length === 0) return [];
|
|
4599
|
+
try {
|
|
4600
|
+
const rawEntries = getWasmBindings().parseRecapFromSiwe(session.siwe);
|
|
4601
|
+
if (!Array.isArray(rawEntries)) return [];
|
|
4602
|
+
return normalizePermissions(rawEntries.map(permissionFromRawRecap));
|
|
4603
|
+
} catch {
|
|
4604
|
+
return [];
|
|
4605
|
+
}
|
|
4606
|
+
}
|
|
4607
|
+
function permissionFromRawRecap(value) {
|
|
4608
|
+
const record = asRecord(value);
|
|
4609
|
+
if (!record) return null;
|
|
4610
|
+
const service = stringValue(record.service);
|
|
4611
|
+
const space = stringValue(record.space);
|
|
4612
|
+
const path = stringValue(record.path);
|
|
4613
|
+
const actions = Array.isArray(record.actions) ? record.actions.map(String).filter(Boolean) : [];
|
|
4614
|
+
if (!service || !space || path === null || actions.length === 0) return null;
|
|
4615
|
+
return {
|
|
4616
|
+
service: normalizeService2(service),
|
|
4617
|
+
space,
|
|
4618
|
+
path,
|
|
4619
|
+
actions
|
|
4620
|
+
};
|
|
4621
|
+
}
|
|
4622
|
+
function normalizePermissions(entries) {
|
|
4623
|
+
const permissions = [];
|
|
4624
|
+
for (const entry of entries) {
|
|
4625
|
+
const permission = permissionFromUnknown(entry);
|
|
4626
|
+
if (permission) permissions.push(permission);
|
|
4627
|
+
}
|
|
4628
|
+
return uniquePermissions(permissions);
|
|
4629
|
+
}
|
|
4630
|
+
function permissionFromUnknown(value) {
|
|
4631
|
+
const record = asRecord(value);
|
|
4632
|
+
if (!record) return null;
|
|
4633
|
+
const service = stringValue(record.service);
|
|
4634
|
+
const space = stringValue(record.space);
|
|
4635
|
+
const path = stringValue(record.path);
|
|
4636
|
+
const actions = Array.isArray(record.actions) ? record.actions.map(String).filter(Boolean) : [];
|
|
4637
|
+
if (!service || !space || path === null || actions.length === 0) return null;
|
|
4638
|
+
return {
|
|
4639
|
+
service: normalizeService2(service),
|
|
4640
|
+
space,
|
|
4641
|
+
path,
|
|
4642
|
+
actions
|
|
4643
|
+
};
|
|
4644
|
+
}
|
|
4645
|
+
function uniquePermissions(entries) {
|
|
4646
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4647
|
+
const unique2 = [];
|
|
4648
|
+
for (const entry of entries) {
|
|
4649
|
+
const key = compactPermission(entry);
|
|
4650
|
+
if (seen.has(key)) continue;
|
|
4651
|
+
seen.add(key);
|
|
4652
|
+
unique2.push(entry);
|
|
4653
|
+
}
|
|
4654
|
+
return unique2;
|
|
4655
|
+
}
|
|
4656
|
+
function compactPermissions(entries) {
|
|
4657
|
+
return entries.map(compactPermission);
|
|
4658
|
+
}
|
|
4659
|
+
function extractSessionExpiry(session) {
|
|
4660
|
+
for (const key of ["expiresAt", "expiry", "expirationTime"]) {
|
|
4661
|
+
const parsed = parseDate2(session[key]);
|
|
4662
|
+
if (parsed) return parsed;
|
|
4663
|
+
}
|
|
4664
|
+
if (typeof session.siwe !== "string") return null;
|
|
4665
|
+
const match = session.siwe.match(/^Expiration Time:\s*(.+)$/im);
|
|
4666
|
+
return match ? parseDate2(match[1].trim()) : null;
|
|
4667
|
+
}
|
|
4668
|
+
function parseDate2(value) {
|
|
4669
|
+
if (value instanceof Date) {
|
|
4670
|
+
return Number.isNaN(value.getTime()) ? null : value;
|
|
4671
|
+
}
|
|
4672
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
4673
|
+
const millis = value > 0 && value < 1e12 ? value * 1e3 : value;
|
|
4674
|
+
const date2 = new Date(millis);
|
|
4675
|
+
return Number.isNaN(date2.getTime()) ? null : date2;
|
|
4676
|
+
}
|
|
4677
|
+
if (typeof value !== "string" || value.trim() === "") return null;
|
|
4678
|
+
const date = new Date(value);
|
|
4679
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
4680
|
+
}
|
|
4681
|
+
function getWasmBindings() {
|
|
4682
|
+
wasmBindings ??= new NodeWasmBindings();
|
|
4683
|
+
return wasmBindings;
|
|
4684
|
+
}
|
|
4685
|
+
function normalizeService2(service) {
|
|
4686
|
+
return service.startsWith("tinycloud.") ? service : `tinycloud.${service}`;
|
|
4687
|
+
}
|
|
4688
|
+
function asRecord(value) {
|
|
4689
|
+
return value !== null && typeof value === "object" ? value : null;
|
|
4690
|
+
}
|
|
4691
|
+
function stringValue(value) {
|
|
4692
|
+
return typeof value === "string" ? value : null;
|
|
4693
|
+
}
|
|
4694
|
+
function messageFromError(error) {
|
|
4695
|
+
return error instanceof Error ? error.message : String(error);
|
|
4696
|
+
}
|
|
4697
|
+
function formatStatus(summary) {
|
|
4698
|
+
const lines = [];
|
|
4699
|
+
lines.push(theme.heading("TinyCloud Status"));
|
|
4700
|
+
lines.push(`Active profile: ${theme.value(summary.activeProfile)}`);
|
|
4701
|
+
lines.push(`Default profile: ${theme.value(summary.defaultProfile)}`);
|
|
4702
|
+
lines.push("");
|
|
4703
|
+
if (summary.profiles.length === 0) {
|
|
4704
|
+
lines.push(theme.muted("No profiles configured. Run: tc init"));
|
|
4705
|
+
return `${lines.join("\n")}
|
|
4706
|
+
`;
|
|
4707
|
+
}
|
|
4708
|
+
lines.push(theme.label("Profiles"));
|
|
4709
|
+
for (const profile of summary.profiles) {
|
|
4710
|
+
lines.push(formatProfile(profile));
|
|
4711
|
+
}
|
|
4712
|
+
return `${lines.join("\n")}
|
|
4713
|
+
`;
|
|
4714
|
+
}
|
|
4715
|
+
function formatProfile(profile) {
|
|
4716
|
+
const marker = profile.active ? theme.success("*") : " ";
|
|
4717
|
+
const name = profile.default ? `${profile.name} (default)` : profile.name;
|
|
4718
|
+
const host = profile.host ? theme.muted(profile.host) : theme.muted("no host");
|
|
4719
|
+
const summary = [
|
|
4720
|
+
`${marker} ${profile.active ? theme.brand(name) : name}`,
|
|
4721
|
+
formatProfileStatus(profile.status),
|
|
4722
|
+
profile.posture ?? "no posture",
|
|
4723
|
+
plural(profile.permissionCount, "permission"),
|
|
4724
|
+
`${profile.activeDelegationCount}/${profile.delegationCount} delegations`,
|
|
4725
|
+
host
|
|
4726
|
+
].join(" ");
|
|
4727
|
+
const lines = [summary];
|
|
4728
|
+
lines.push(` session: ${formatSession(profile.session)}`);
|
|
4729
|
+
if (profile.permissionsCompact.length > 0) {
|
|
4730
|
+
lines.push(" permissions:");
|
|
4731
|
+
for (const permission of profile.permissionsCompact) {
|
|
4732
|
+
lines.push(` ${permission}`);
|
|
4733
|
+
}
|
|
4734
|
+
}
|
|
4735
|
+
if (profile.delegations.length > 0) {
|
|
4736
|
+
lines.push(" delegations:");
|
|
4737
|
+
for (const delegation of profile.delegations) {
|
|
4738
|
+
lines.push(` ${formatDelegation(delegation)}`);
|
|
4739
|
+
}
|
|
4740
|
+
}
|
|
4741
|
+
if (profile.issues.length > 0) {
|
|
4742
|
+
lines.push(" issues:");
|
|
4743
|
+
for (const issue of profile.issues) {
|
|
4744
|
+
lines.push(` ${theme.warn(issue)}`);
|
|
4745
|
+
}
|
|
4746
|
+
}
|
|
4747
|
+
return lines.join("\n");
|
|
4748
|
+
}
|
|
4749
|
+
function formatProfileStatus(status) {
|
|
4750
|
+
switch (status) {
|
|
4751
|
+
case "logged-in":
|
|
4752
|
+
return theme.success("logged in");
|
|
4753
|
+
case "local-key":
|
|
4754
|
+
return theme.success("local key");
|
|
4755
|
+
case "expired":
|
|
4756
|
+
return theme.warn("expired");
|
|
4757
|
+
case "missing":
|
|
4758
|
+
return theme.warn("missing");
|
|
4759
|
+
case "signed-out":
|
|
4760
|
+
return theme.muted("signed out");
|
|
4761
|
+
}
|
|
4762
|
+
}
|
|
4763
|
+
function formatSession(session) {
|
|
4764
|
+
if (!session.present) return theme.muted("none");
|
|
4765
|
+
if (session.expired === true) {
|
|
4766
|
+
return `${theme.warn("expired")}${formatExpiresAt(session.expiresAt)}`;
|
|
4767
|
+
}
|
|
4768
|
+
if (session.expired === false) {
|
|
4769
|
+
return `${theme.success("active")}${formatExpiresAt(session.expiresAt)}`;
|
|
4770
|
+
}
|
|
4771
|
+
return `${theme.success("present")}${formatExpiresAt(session.expiresAt)}`;
|
|
4772
|
+
}
|
|
4773
|
+
function formatDelegation(delegation) {
|
|
4774
|
+
const state = delegation.expired === true ? theme.warn("expired") : theme.success("active");
|
|
4775
|
+
return [
|
|
4776
|
+
delegation.cid,
|
|
4777
|
+
state,
|
|
4778
|
+
formatExpiresAt(delegation.expiresAt).trim(),
|
|
4779
|
+
plural(delegation.permissions.length, "permission")
|
|
4780
|
+
].filter(Boolean).join(" ");
|
|
4781
|
+
}
|
|
4782
|
+
function formatExpiresAt(expiresAt) {
|
|
4783
|
+
return expiresAt ? ` until ${expiresAt}` : "";
|
|
4784
|
+
}
|
|
4785
|
+
function plural(count, label) {
|
|
4786
|
+
return `${count} ${label}${count === 1 ? "" : "s"}`;
|
|
4787
|
+
}
|
|
4788
|
+
|
|
4284
4789
|
// src/index.ts
|
|
4285
4790
|
var { version } = JSON.parse(
|
|
4286
4791
|
readFileSync3(new URL("../package.json", import.meta.url), "utf-8")
|
|
@@ -4295,7 +4800,7 @@ program.hook("preAction", async (thisCommand) => {
|
|
|
4295
4800
|
const commandName = thisCommand.name();
|
|
4296
4801
|
const parentName = thisCommand.parent?.name();
|
|
4297
4802
|
const fullCommand = parentName && parentName !== "tc" ? `${parentName} ${commandName}` : commandName;
|
|
4298
|
-
const skipGuard = ["tc", "init", "doctor", "completion", "help", "upgrade"].includes(commandName) || fullCommand === "profile create";
|
|
4803
|
+
const skipGuard = ["tc", "init", "doctor", "completion", "help", "upgrade", "status"].includes(commandName) || fullCommand === "profile create";
|
|
4299
4804
|
if (!skipGuard && !opts.quiet && isInteractive()) {
|
|
4300
4805
|
try {
|
|
4301
4806
|
const config = await ProfileManager.getConfig();
|
|
@@ -4330,6 +4835,7 @@ registerSqlCommand(program);
|
|
|
4330
4835
|
registerDuckdbCommand(program);
|
|
4331
4836
|
registerManifestCommand(program);
|
|
4332
4837
|
registerUpgradeCommand(program);
|
|
4838
|
+
registerStatusCommand(program);
|
|
4333
4839
|
program.addHelpText("before", () => `${theme.label("Version:")} ${theme.value(version)}
|
|
4334
4840
|
`);
|
|
4335
4841
|
program.addHelpText("afterAll", () => {
|