@tinycloud/cli 0.6.0-beta.5 → 0.6.0-beta.7

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 CHANGED
@@ -2045,18 +2045,33 @@ function parseExpiryOption(raw) {
2045
2045
  }
2046
2046
  function groupPermissionsBySpace(permissions) {
2047
2047
  const groups = /* @__PURE__ */ new Map();
2048
+ const rawEntries = [];
2048
2049
  for (const permission of permissions) {
2050
+ if (isRawPermission(permission)) {
2051
+ rawEntries.push(permission);
2052
+ continue;
2053
+ }
2049
2054
  const group = groups.get(permission.space) ?? [];
2050
2055
  group.push(permission);
2051
2056
  groups.set(permission.space, group);
2052
2057
  }
2053
- return Array.from(groups.values());
2058
+ const grouped = Array.from(groups.values());
2059
+ if (grouped.length === 0) {
2060
+ return rawEntries.length > 0 ? [rawEntries] : [];
2061
+ }
2062
+ grouped[0].push(...rawEntries);
2063
+ return grouped;
2064
+ }
2065
+ function isRawPermission(permission) {
2066
+ return permission.service === "tinycloud.encryption" && permission.path.startsWith("urn:tinycloud:encryption:");
2054
2067
  }
2055
2068
  function portableFromOpenKeyDelegation(data, permissions, host) {
2056
- const primary = permissions[0];
2057
- const returnedSpace = String(data.spaceId ?? primary.space);
2058
- const expectedSpaces = new Set(permissions.map((permission) => permission.space));
2059
- if (expectedSpaces.size !== 1 || !expectedSpaces.has(returnedSpace)) {
2069
+ const primary = permissions.find((permission) => !isRawPermission(permission)) ?? permissions[0];
2070
+ const returnedSpace = String(data.spaceId ?? primary.space ?? "encryption");
2071
+ const expectedSpaces = new Set(
2072
+ permissions.filter((permission) => !isRawPermission(permission)).map((permission) => permission.space)
2073
+ );
2074
+ if (expectedSpaces.size > 0 && (expectedSpaces.size !== 1 || !expectedSpaces.has(returnedSpace))) {
2060
2075
  throw new CLIError(
2061
2076
  "OPENKEY_SCOPE_MISMATCH",
2062
2077
  `OpenKey returned delegation for ${returnedSpace}, expected ${Array.from(expectedSpaces).join(", ")}.`,
@@ -2172,6 +2187,16 @@ async function handleLocalAuth(profileName, host) {
2172
2187
  });
2173
2188
  }
2174
2189
  async function handleOpenKeyAuth(profileName, host, paste) {
2190
+ const { profile, delegationData } = await refreshOpenKeySession(profileName, host, { paste });
2191
+ outputJson({
2192
+ authenticated: true,
2193
+ profile: profileName,
2194
+ did: profile.did,
2195
+ spaceId: delegationData.spaceId,
2196
+ authMethod: "openkey"
2197
+ });
2198
+ }
2199
+ async function refreshOpenKeySession(profileName, host, options = {}) {
2175
2200
  const key = await ProfileManager.getKey(profileName);
2176
2201
  if (!key) {
2177
2202
  throw new CLIError(
@@ -2182,7 +2207,7 @@ async function handleOpenKeyAuth(profileName, host, paste) {
2182
2207
  }
2183
2208
  const profile = await ProfileManager.getProfile(profileName);
2184
2209
  const delegationData = await startAuthFlow(profile.did, {
2185
- paste,
2210
+ paste: options.paste,
2186
2211
  jwk: key,
2187
2212
  host,
2188
2213
  openkeyHost: resolveOpenKeyHost(profile)
@@ -2200,13 +2225,7 @@ async function handleOpenKeyAuth(profileName, host, paste) {
2200
2225
  updatedProfile.ownerDid = delegationData.ownerDid;
2201
2226
  }
2202
2227
  await ProfileManager.setProfile(profileName, updatedProfile);
2203
- outputJson({
2204
- authenticated: true,
2205
- profile: profileName,
2206
- did: profile.did,
2207
- spaceId: delegationData.spaceId,
2208
- authMethod: "openkey"
2209
- });
2228
+ return { profile: updatedProfile, delegationData };
2210
2229
  }
2211
2230
 
2212
2231
  // src/commands/kv.ts
@@ -3217,6 +3236,11 @@ function registerVaultCommand(program2) {
3217
3236
  // src/commands/secrets.ts
3218
3237
  import { readFile as readFile6 } from "fs/promises";
3219
3238
  import { writeFile as writeFile5 } from "fs/promises";
3239
+ import {
3240
+ resolveSecretListPrefix,
3241
+ resolveSecretPath
3242
+ } from "@tinycloud/node-sdk";
3243
+ var SECRETS_SPACE = "secrets";
3220
3244
  async function readStdin4() {
3221
3245
  const chunks = [];
3222
3246
  for await (const chunk of process.stdin) {
@@ -3232,6 +3256,95 @@ function resolveSecretScope(options) {
3232
3256
  const scope = options.scope ?? options.space;
3233
3257
  return scope ? { scope } : void 0;
3234
3258
  }
3259
+ async function ensureSecretsNode(ctx, options) {
3260
+ const auth = authOptions(options);
3261
+ if (auth?.privateKey) {
3262
+ return ensureAuthenticated(ctx, auth);
3263
+ }
3264
+ const profile = await ProfileManager.getProfile(ctx.profile).catch(() => null);
3265
+ if (profile?.authMethod === "openkey" && canRequestOwnerPermissions(profile)) {
3266
+ const session = await ProfileManager.getSession(ctx.profile);
3267
+ if (!session || isStoredSessionExpired(session)) {
3268
+ await withSpinner(
3269
+ session ? "Refreshing TinyCloud session..." : "Creating TinyCloud session...",
3270
+ () => refreshOpenKeySession(ctx.profile, ctx.host)
3271
+ );
3272
+ }
3273
+ }
3274
+ return ensureAuthenticated(ctx, auth);
3275
+ }
3276
+ async function runSecretOperation(params) {
3277
+ const first = await withSpinner(params.label, params.operation);
3278
+ if (first.ok || !shouldRequestSecretPermissions(first.error)) {
3279
+ return first;
3280
+ }
3281
+ const profile = await ProfileManager.getProfile(params.ctx.profile);
3282
+ if (!canRequestOwnerPermissions(profile)) {
3283
+ return first;
3284
+ }
3285
+ const requested = secretPermissionEntries({
3286
+ action: params.action,
3287
+ name: params.name,
3288
+ options: params.scopeOptions,
3289
+ node: params.node
3290
+ });
3291
+ await withSpinner(
3292
+ "Requesting secret permissions...",
3293
+ () => ensureDelegationAuthority({
3294
+ ctx: params.ctx,
3295
+ profile,
3296
+ node: params.node,
3297
+ requested,
3298
+ expiryOption: void 0,
3299
+ yes: true
3300
+ })
3301
+ );
3302
+ return withSpinner(params.label, params.operation);
3303
+ }
3304
+ function canRequestOwnerPermissions(profile) {
3305
+ const posture = resolveProfilePosture(profile);
3306
+ return posture === "owner-openkey" || posture === "local-owner-key";
3307
+ }
3308
+ function shouldRequestSecretPermissions(error) {
3309
+ if (error.code !== "PERMISSION_DENIED") return false;
3310
+ return /permission|session expired|autosign|capabilit/i.test(error.message);
3311
+ }
3312
+ function isStoredSessionExpired(session) {
3313
+ const record = session;
3314
+ const direct = parseDate(record.expiresAt ?? record.expiry ?? record.expirationTime);
3315
+ if (direct) return direct.getTime() <= Date.now();
3316
+ if (typeof record.siwe !== "string") return false;
3317
+ const match = record.siwe.match(/^Expiration Time:\s*(.+)$/im);
3318
+ const expiry = match ? parseDate(match[1].trim()) : null;
3319
+ return expiry !== null && expiry.getTime() <= Date.now();
3320
+ }
3321
+ function parseDate(value) {
3322
+ if (value instanceof Date) {
3323
+ return Number.isNaN(value.getTime()) ? null : value;
3324
+ }
3325
+ if (typeof value !== "string" || value.trim() === "") return null;
3326
+ const date = new Date(value);
3327
+ return Number.isNaN(date.getTime()) ? null : date;
3328
+ }
3329
+ function secretPermissionEntries(params) {
3330
+ const path = params.action === "list" ? resolveSecretListPrefix(params.options) : resolveSecretPath(params.name ?? "", params.options).permissionPaths.vault;
3331
+ const permissions = [{
3332
+ service: "tinycloud.kv",
3333
+ space: SECRETS_SPACE,
3334
+ path,
3335
+ actions: [params.action],
3336
+ skipPrefix: true
3337
+ }];
3338
+ if (params.action === "get") {
3339
+ permissions.push({
3340
+ service: "tinycloud.encryption",
3341
+ path: params.node.getDefaultEncryptionNetworkId(),
3342
+ actions: ["decrypt"],
3343
+ skipPrefix: true
3344
+ });
3345
+ }
3346
+ return permissions;
3347
+ }
3235
3348
  function registerSecretsCommand(program2) {
3236
3349
  const secrets = program2.command("secrets").description("Encrypted secrets management");
3237
3350
  const network = secrets.command("network").description("Manage the default secrets encryption network");
@@ -3277,12 +3390,16 @@ function registerSecretsCommand(program2) {
3277
3390
  try {
3278
3391
  const globalOpts = cmd.optsWithGlobals();
3279
3392
  const ctx = await ProfileManager.resolveContext(globalOpts);
3280
- const node = await ensureAuthenticated(ctx, authOptions(options));
3393
+ const node = await ensureSecretsNode(ctx, options);
3281
3394
  const scopeOptions = resolveSecretScope(options);
3282
- const result = await withSpinner(
3283
- "Listing secrets...",
3284
- () => node.secrets.list(scopeOptions)
3285
- );
3395
+ const result = await runSecretOperation({
3396
+ ctx,
3397
+ node,
3398
+ action: "list",
3399
+ scopeOptions,
3400
+ label: "Listing secrets...",
3401
+ operation: () => node.secrets.list(scopeOptions)
3402
+ });
3286
3403
  if (!result.ok) {
3287
3404
  throw new CLIError(result.error.code, result.error.message, ExitCode.ERROR);
3288
3405
  }
@@ -3301,12 +3418,17 @@ function registerSecretsCommand(program2) {
3301
3418
  try {
3302
3419
  const globalOpts = cmd.optsWithGlobals();
3303
3420
  const ctx = await ProfileManager.resolveContext(globalOpts);
3304
- const node = await ensureAuthenticated(ctx, authOptions(options));
3421
+ const node = await ensureSecretsNode(ctx, options);
3305
3422
  const scopeOptions = resolveSecretScope(options);
3306
- const result = await withSpinner(
3307
- `Getting secret ${name}...`,
3308
- () => node.secrets.get(name, scopeOptions)
3309
- );
3423
+ const result = await runSecretOperation({
3424
+ ctx,
3425
+ node,
3426
+ action: "get",
3427
+ name,
3428
+ scopeOptions,
3429
+ label: `Getting secret ${name}...`,
3430
+ operation: () => node.secrets.get(name, scopeOptions)
3431
+ });
3310
3432
  if (!result.ok) {
3311
3433
  if (result.error.code === "NOT_FOUND" || result.error.code === "KEY_NOT_FOUND") {
3312
3434
  throw new CLIError("NOT_FOUND", `Secret "${name}" not found`, ExitCode.NOT_FOUND);
@@ -3332,7 +3454,7 @@ function registerSecretsCommand(program2) {
3332
3454
  try {
3333
3455
  const globalOpts = cmd.optsWithGlobals();
3334
3456
  const ctx = await ProfileManager.resolveContext(globalOpts);
3335
- const node = await ensureAuthenticated(ctx, authOptions(options));
3457
+ const node = await ensureSecretsNode(ctx, options);
3336
3458
  let secretValue;
3337
3459
  const sources = [value !== void 0, !!options.file, !!options.stdin].filter(Boolean);
3338
3460
  if (sources.length === 0) {
@@ -3349,10 +3471,15 @@ function registerSecretsCommand(program2) {
3349
3471
  secretValue = value;
3350
3472
  }
3351
3473
  const scopeOptions = resolveSecretScope(options);
3352
- const result = await withSpinner(
3353
- `Storing secret ${name}...`,
3354
- () => node.secrets.put(name, secretValue, scopeOptions)
3355
- );
3474
+ const result = await runSecretOperation({
3475
+ ctx,
3476
+ node,
3477
+ action: "put",
3478
+ name,
3479
+ scopeOptions,
3480
+ label: `Storing secret ${name}...`,
3481
+ operation: () => node.secrets.put(name, secretValue, scopeOptions)
3482
+ });
3356
3483
  if (!result.ok) {
3357
3484
  throw new CLIError(result.error.code, result.error.message, ExitCode.ERROR);
3358
3485
  }
@@ -3365,12 +3492,17 @@ function registerSecretsCommand(program2) {
3365
3492
  try {
3366
3493
  const globalOpts = cmd.optsWithGlobals();
3367
3494
  const ctx = await ProfileManager.resolveContext(globalOpts);
3368
- const node = await ensureAuthenticated(ctx, authOptions(options));
3495
+ const node = await ensureSecretsNode(ctx, options);
3369
3496
  const scopeOptions = resolveSecretScope(options);
3370
- const result = await withSpinner(
3371
- `Deleting secret ${name}...`,
3372
- () => node.secrets.delete(name, scopeOptions)
3373
- );
3497
+ const result = await runSecretOperation({
3498
+ ctx,
3499
+ node,
3500
+ action: "del",
3501
+ name,
3502
+ scopeOptions,
3503
+ label: `Deleting secret ${name}...`,
3504
+ operation: () => node.secrets.delete(name, scopeOptions)
3505
+ });
3374
3506
  if (!result.ok) {
3375
3507
  throw new CLIError(result.error.code, result.error.message, ExitCode.ERROR);
3376
3508
  }
@@ -4281,6 +4413,358 @@ function registerUpgradeCommand(program2) {
4281
4413
  });
4282
4414
  }
4283
4415
 
4416
+ // src/commands/status.ts
4417
+ import {
4418
+ NodeWasmBindings
4419
+ } from "@tinycloud/node-sdk";
4420
+ var wasmBindings = null;
4421
+ function registerStatusCommand(program2) {
4422
+ program2.command("status").description("Show local TinyCloud profile, session, delegation, and permission state").action(async (_options, cmd) => {
4423
+ try {
4424
+ const globalOpts = cmd.optsWithGlobals();
4425
+ const ctx = await ProfileManager.resolveContext(globalOpts);
4426
+ const config = await ProfileManager.getConfig();
4427
+ const names = (await ProfileManager.listProfiles()).sort(
4428
+ (a, b) => a.localeCompare(b)
4429
+ );
4430
+ const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
4431
+ const profiles = await Promise.all(
4432
+ names.map(
4433
+ (name) => inspectProfile({
4434
+ name,
4435
+ activeProfile: ctx.profile,
4436
+ defaultProfile: config.defaultProfile
4437
+ })
4438
+ )
4439
+ );
4440
+ const summary = {
4441
+ generatedAt,
4442
+ activeProfile: ctx.profile,
4443
+ defaultProfile: config.defaultProfile,
4444
+ profileCount: profiles.length,
4445
+ authenticatedProfileCount: profiles.filter((p) => p.authenticated).length,
4446
+ activeDelegationCount: profiles.reduce(
4447
+ (sum, profile) => sum + profile.activeDelegationCount,
4448
+ 0
4449
+ ),
4450
+ profiles
4451
+ };
4452
+ if (shouldOutputJson()) {
4453
+ outputJson(summary);
4454
+ return;
4455
+ }
4456
+ process.stdout.write(formatStatus(summary));
4457
+ } catch (error) {
4458
+ handleError(error);
4459
+ }
4460
+ });
4461
+ }
4462
+ async function inspectProfile(params) {
4463
+ const issues = [];
4464
+ const profile = await readProfile(params.name, issues);
4465
+ const session = await readSession(params.name, issues);
4466
+ const hasKey = await readHasKey(params.name, issues);
4467
+ const storedDelegations = await readDelegations(params.name, issues);
4468
+ const sessionPermissions = session ? sessionPermissionsFromRecap(session) : [];
4469
+ const sessionExpiry = session ? extractSessionExpiry(session) : null;
4470
+ const sessionExpired = sessionExpiry === null ? null : sessionExpiry.getTime() <= Date.now();
4471
+ const statusSession = {
4472
+ present: session !== null,
4473
+ expired: session === null ? null : sessionExpired,
4474
+ expiresAt: sessionExpiry?.toISOString() ?? null,
4475
+ permissions: sessionPermissions,
4476
+ permissionsCompact: compactPermissions(sessionPermissions)
4477
+ };
4478
+ const delegations = storedDelegations.map(inspectDelegation);
4479
+ const activeDelegationPermissions = delegations.filter((delegation) => delegation.active).flatMap((delegation) => delegation.permissions);
4480
+ const permissions = uniquePermissions([
4481
+ ...sessionPermissions,
4482
+ ...activeDelegationPermissions
4483
+ ]);
4484
+ const hasPrivateKey = typeof profile?.privateKey === "string" && profile.privateKey.length > 0;
4485
+ const localKeyAuthenticated = profile?.authMethod === "local" && hasPrivateKey;
4486
+ const sessionAuthenticated = session !== null && sessionExpired !== true;
4487
+ const authenticated = localKeyAuthenticated || sessionAuthenticated;
4488
+ const status = resolveStatus({
4489
+ exists: profile !== null,
4490
+ authenticated,
4491
+ localKeyAuthenticated,
4492
+ sessionExpired
4493
+ });
4494
+ return {
4495
+ name: params.name,
4496
+ active: params.name === params.activeProfile,
4497
+ default: params.name === params.defaultProfile,
4498
+ exists: profile !== null,
4499
+ status,
4500
+ host: profile?.host ?? null,
4501
+ did: profile?.did ?? null,
4502
+ sessionDid: profile?.sessionDid ?? null,
4503
+ ownerDid: profile?.ownerDid ?? null,
4504
+ address: profile?.address ?? null,
4505
+ spaceId: profile?.spaceId ?? null,
4506
+ authMethod: profile?.authMethod ?? null,
4507
+ posture: profile ? resolveProfilePosture(profile) : null,
4508
+ operatorType: profile ? resolveProfileOperatorType(profile) : null,
4509
+ hasKey,
4510
+ hasPrivateKey,
4511
+ authenticated,
4512
+ session: statusSession,
4513
+ delegations,
4514
+ permissions,
4515
+ permissionsCompact: compactPermissions(permissions),
4516
+ permissionCount: permissions.length,
4517
+ activeDelegationCount: delegations.filter((delegation) => delegation.active).length,
4518
+ delegationCount: delegations.length,
4519
+ issues
4520
+ };
4521
+ }
4522
+ async function readProfile(name, issues) {
4523
+ try {
4524
+ return await ProfileManager.getProfile(name);
4525
+ } catch (error) {
4526
+ issues.push(`profile: ${messageFromError(error)}`);
4527
+ return null;
4528
+ }
4529
+ }
4530
+ async function readSession(name, issues) {
4531
+ try {
4532
+ return asRecord(await ProfileManager.getSession(name));
4533
+ } catch (error) {
4534
+ issues.push(`session: ${messageFromError(error)}`);
4535
+ return null;
4536
+ }
4537
+ }
4538
+ async function readHasKey(name, issues) {
4539
+ try {
4540
+ return await ProfileManager.getKey(name) !== null;
4541
+ } catch (error) {
4542
+ issues.push(`key: ${messageFromError(error)}`);
4543
+ return false;
4544
+ }
4545
+ }
4546
+ async function readDelegations(name, issues) {
4547
+ try {
4548
+ return await loadAdditionalDelegations(name);
4549
+ } catch (error) {
4550
+ issues.push(`delegations: ${messageFromError(error)}`);
4551
+ return [];
4552
+ }
4553
+ }
4554
+ function inspectDelegation(entry) {
4555
+ const expiry = parseDate2(entry.delegation.expiry);
4556
+ const expired = expiry === null ? null : expiry.getTime() <= Date.now();
4557
+ const permissions = normalizePermissions(
4558
+ Array.isArray(entry.permissions) && entry.permissions.length > 0 ? entry.permissions : permissionsFromDelegation(entry.delegation)
4559
+ );
4560
+ return {
4561
+ cid: entry.delegation.cid,
4562
+ active: expired !== true,
4563
+ expired,
4564
+ expiresAt: expiry?.toISOString() ?? null,
4565
+ permissions,
4566
+ permissionsCompact: compactPermissions(permissions)
4567
+ };
4568
+ }
4569
+ function resolveStatus(params) {
4570
+ if (!params.exists) return "missing";
4571
+ if (params.localKeyAuthenticated) return "local-key";
4572
+ if (params.authenticated) return "logged-in";
4573
+ if (params.sessionExpired === true) return "expired";
4574
+ return "signed-out";
4575
+ }
4576
+ function sessionPermissionsFromRecap(session) {
4577
+ if (typeof session.siwe !== "string" || session.siwe.length === 0) return [];
4578
+ try {
4579
+ const rawEntries = getWasmBindings().parseRecapFromSiwe(session.siwe);
4580
+ if (!Array.isArray(rawEntries)) return [];
4581
+ return normalizePermissions(rawEntries.map(permissionFromRawRecap));
4582
+ } catch {
4583
+ return [];
4584
+ }
4585
+ }
4586
+ function permissionFromRawRecap(value) {
4587
+ const record = asRecord(value);
4588
+ if (!record) return null;
4589
+ const service = stringValue(record.service);
4590
+ const space = stringValue(record.space);
4591
+ const path = stringValue(record.path);
4592
+ const actions = Array.isArray(record.actions) ? record.actions.map(String).filter(Boolean) : [];
4593
+ if (!service || !space || path === null || actions.length === 0) return null;
4594
+ return {
4595
+ service: normalizeService2(service),
4596
+ space,
4597
+ path,
4598
+ actions
4599
+ };
4600
+ }
4601
+ function normalizePermissions(entries) {
4602
+ const permissions = [];
4603
+ for (const entry of entries) {
4604
+ const permission = permissionFromUnknown(entry);
4605
+ if (permission) permissions.push(permission);
4606
+ }
4607
+ return uniquePermissions(permissions);
4608
+ }
4609
+ function permissionFromUnknown(value) {
4610
+ const record = asRecord(value);
4611
+ if (!record) return null;
4612
+ const service = stringValue(record.service);
4613
+ const space = stringValue(record.space);
4614
+ const path = stringValue(record.path);
4615
+ const actions = Array.isArray(record.actions) ? record.actions.map(String).filter(Boolean) : [];
4616
+ if (!service || !space || path === null || actions.length === 0) return null;
4617
+ return {
4618
+ service: normalizeService2(service),
4619
+ space,
4620
+ path,
4621
+ actions
4622
+ };
4623
+ }
4624
+ function uniquePermissions(entries) {
4625
+ const seen = /* @__PURE__ */ new Set();
4626
+ const unique2 = [];
4627
+ for (const entry of entries) {
4628
+ const key = compactPermission(entry);
4629
+ if (seen.has(key)) continue;
4630
+ seen.add(key);
4631
+ unique2.push(entry);
4632
+ }
4633
+ return unique2;
4634
+ }
4635
+ function compactPermissions(entries) {
4636
+ return entries.map(compactPermission);
4637
+ }
4638
+ function extractSessionExpiry(session) {
4639
+ for (const key of ["expiresAt", "expiry", "expirationTime"]) {
4640
+ const parsed = parseDate2(session[key]);
4641
+ if (parsed) return parsed;
4642
+ }
4643
+ if (typeof session.siwe !== "string") return null;
4644
+ const match = session.siwe.match(/^Expiration Time:\s*(.+)$/im);
4645
+ return match ? parseDate2(match[1].trim()) : null;
4646
+ }
4647
+ function parseDate2(value) {
4648
+ if (value instanceof Date) {
4649
+ return Number.isNaN(value.getTime()) ? null : value;
4650
+ }
4651
+ if (typeof value === "number" && Number.isFinite(value)) {
4652
+ const millis = value > 0 && value < 1e12 ? value * 1e3 : value;
4653
+ const date2 = new Date(millis);
4654
+ return Number.isNaN(date2.getTime()) ? null : date2;
4655
+ }
4656
+ if (typeof value !== "string" || value.trim() === "") return null;
4657
+ const date = new Date(value);
4658
+ return Number.isNaN(date.getTime()) ? null : date;
4659
+ }
4660
+ function getWasmBindings() {
4661
+ wasmBindings ??= new NodeWasmBindings();
4662
+ return wasmBindings;
4663
+ }
4664
+ function normalizeService2(service) {
4665
+ return service.startsWith("tinycloud.") ? service : `tinycloud.${service}`;
4666
+ }
4667
+ function asRecord(value) {
4668
+ return value !== null && typeof value === "object" ? value : null;
4669
+ }
4670
+ function stringValue(value) {
4671
+ return typeof value === "string" ? value : null;
4672
+ }
4673
+ function messageFromError(error) {
4674
+ return error instanceof Error ? error.message : String(error);
4675
+ }
4676
+ function formatStatus(summary) {
4677
+ const lines = [];
4678
+ lines.push(theme.heading("TinyCloud Status"));
4679
+ lines.push(`Active profile: ${theme.value(summary.activeProfile)}`);
4680
+ lines.push(`Default profile: ${theme.value(summary.defaultProfile)}`);
4681
+ lines.push("");
4682
+ if (summary.profiles.length === 0) {
4683
+ lines.push(theme.muted("No profiles configured. Run: tc init"));
4684
+ return `${lines.join("\n")}
4685
+ `;
4686
+ }
4687
+ lines.push(theme.label("Profiles"));
4688
+ for (const profile of summary.profiles) {
4689
+ lines.push(formatProfile(profile));
4690
+ }
4691
+ return `${lines.join("\n")}
4692
+ `;
4693
+ }
4694
+ function formatProfile(profile) {
4695
+ const marker = profile.active ? theme.success("*") : " ";
4696
+ const name = profile.default ? `${profile.name} (default)` : profile.name;
4697
+ const host = profile.host ? theme.muted(profile.host) : theme.muted("no host");
4698
+ const summary = [
4699
+ `${marker} ${profile.active ? theme.brand(name) : name}`,
4700
+ formatProfileStatus(profile.status),
4701
+ profile.posture ?? "no posture",
4702
+ plural(profile.permissionCount, "permission"),
4703
+ `${profile.activeDelegationCount}/${profile.delegationCount} delegations`,
4704
+ host
4705
+ ].join(" ");
4706
+ const lines = [summary];
4707
+ lines.push(` session: ${formatSession(profile.session)}`);
4708
+ if (profile.permissionsCompact.length > 0) {
4709
+ lines.push(" permissions:");
4710
+ for (const permission of profile.permissionsCompact) {
4711
+ lines.push(` ${permission}`);
4712
+ }
4713
+ }
4714
+ if (profile.delegations.length > 0) {
4715
+ lines.push(" delegations:");
4716
+ for (const delegation of profile.delegations) {
4717
+ lines.push(` ${formatDelegation(delegation)}`);
4718
+ }
4719
+ }
4720
+ if (profile.issues.length > 0) {
4721
+ lines.push(" issues:");
4722
+ for (const issue of profile.issues) {
4723
+ lines.push(` ${theme.warn(issue)}`);
4724
+ }
4725
+ }
4726
+ return lines.join("\n");
4727
+ }
4728
+ function formatProfileStatus(status) {
4729
+ switch (status) {
4730
+ case "logged-in":
4731
+ return theme.success("logged in");
4732
+ case "local-key":
4733
+ return theme.success("local key");
4734
+ case "expired":
4735
+ return theme.warn("expired");
4736
+ case "missing":
4737
+ return theme.warn("missing");
4738
+ case "signed-out":
4739
+ return theme.muted("signed out");
4740
+ }
4741
+ }
4742
+ function formatSession(session) {
4743
+ if (!session.present) return theme.muted("none");
4744
+ if (session.expired === true) {
4745
+ return `${theme.warn("expired")}${formatExpiresAt(session.expiresAt)}`;
4746
+ }
4747
+ if (session.expired === false) {
4748
+ return `${theme.success("active")}${formatExpiresAt(session.expiresAt)}`;
4749
+ }
4750
+ return `${theme.success("present")}${formatExpiresAt(session.expiresAt)}`;
4751
+ }
4752
+ function formatDelegation(delegation) {
4753
+ const state = delegation.expired === true ? theme.warn("expired") : theme.success("active");
4754
+ return [
4755
+ delegation.cid,
4756
+ state,
4757
+ formatExpiresAt(delegation.expiresAt).trim(),
4758
+ plural(delegation.permissions.length, "permission")
4759
+ ].filter(Boolean).join(" ");
4760
+ }
4761
+ function formatExpiresAt(expiresAt) {
4762
+ return expiresAt ? ` until ${expiresAt}` : "";
4763
+ }
4764
+ function plural(count, label) {
4765
+ return `${count} ${label}${count === 1 ? "" : "s"}`;
4766
+ }
4767
+
4284
4768
  // src/index.ts
4285
4769
  var { version } = JSON.parse(
4286
4770
  readFileSync3(new URL("../package.json", import.meta.url), "utf-8")
@@ -4295,7 +4779,7 @@ program.hook("preAction", async (thisCommand) => {
4295
4779
  const commandName = thisCommand.name();
4296
4780
  const parentName = thisCommand.parent?.name();
4297
4781
  const fullCommand = parentName && parentName !== "tc" ? `${parentName} ${commandName}` : commandName;
4298
- const skipGuard = ["tc", "init", "doctor", "completion", "help", "upgrade"].includes(commandName) || fullCommand === "profile create";
4782
+ const skipGuard = ["tc", "init", "doctor", "completion", "help", "upgrade", "status"].includes(commandName) || fullCommand === "profile create";
4299
4783
  if (!skipGuard && !opts.quiet && isInteractive()) {
4300
4784
  try {
4301
4785
  const config = await ProfileManager.getConfig();
@@ -4330,6 +4814,9 @@ registerSqlCommand(program);
4330
4814
  registerDuckdbCommand(program);
4331
4815
  registerManifestCommand(program);
4332
4816
  registerUpgradeCommand(program);
4817
+ registerStatusCommand(program);
4818
+ program.addHelpText("before", () => `${theme.label("Version:")} ${theme.value(version)}
4819
+ `);
4333
4820
  program.addHelpText("afterAll", () => {
4334
4821
  if (!process.stdout.isTTY) return "";
4335
4822
  return `