@tinycloud/cli 0.7.0-beta.7 → 0.7.0-beta.9

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
@@ -816,12 +816,19 @@ function buildAuthUrl(did, options = {}) {
816
816
  if (options.host) {
817
817
  params.set("host", options.host);
818
818
  }
819
+ const reason = typeof options.reason === "string" ? options.reason.trim() : "";
819
820
  if (options.permissions?.length) {
820
821
  params.set(
821
822
  "permissions",
822
- Buffer.from(JSON.stringify({ permissions: options.permissions })).toString("base64url")
823
+ Buffer.from(JSON.stringify({
824
+ permissions: options.permissions,
825
+ ...reason ? { reason } : {}
826
+ })).toString("base64url")
823
827
  );
824
828
  }
829
+ if (reason) {
830
+ params.set("reason", reason);
831
+ }
825
832
  if (options.expiry !== void 0) {
826
833
  params.set("expiry", String(options.expiry));
827
834
  }
@@ -912,8 +919,8 @@ async function callbackFlow(did, options = {}) {
912
919
  }
913
920
  if (openBrowser) {
914
921
  try {
915
- const open = (await import("open")).default;
916
- await open(authUrl);
922
+ const open2 = (await import("open")).default;
923
+ await open2(authUrl);
917
924
  } catch {
918
925
  server.close();
919
926
  throw new Error("Failed to open browser");
@@ -11391,6 +11398,10 @@ function registerAuthCommand(program2) {
11391
11398
  jwk: key,
11392
11399
  host: ctx.host,
11393
11400
  permissions: group,
11401
+ reason: permissionGrantReason(
11402
+ "Grant requested TinyCloud permissions from `tc auth request --grant`.",
11403
+ group
11404
+ ),
11394
11405
  openkeyHost,
11395
11406
  expiry: expiryOption,
11396
11407
  noPopup: options.popup === false
@@ -11535,6 +11546,7 @@ function registerAuthCommand(program2) {
11535
11546
  node,
11536
11547
  requested: parsed.requested,
11537
11548
  expiryOption: parsed.requestedExpiry,
11549
+ reason: "Grant permissions requested by a TinyCloud auth request artifact.",
11538
11550
  yes: options.yes === true
11539
11551
  });
11540
11552
  const grant = await grantAuthRequest(node, parsed);
@@ -11818,6 +11830,7 @@ async function ensureDelegationAuthority(params) {
11818
11830
  jwk: key,
11819
11831
  host: params.ctx.host,
11820
11832
  permissions: group,
11833
+ reason: permissionGrantReason(params.reason, group),
11821
11834
  openkeyHost,
11822
11835
  expiry: params.expiryOption
11823
11836
  });
@@ -11865,6 +11878,12 @@ async function ensureDelegationAuthority(params) {
11865
11878
  });
11866
11879
  }
11867
11880
  }
11881
+ function permissionGrantReason(context, permissions) {
11882
+ const first = permissions[0];
11883
+ const summary = first ? compactPermission(first) : "no permissions";
11884
+ const more = permissions.length > 1 ? ` and ${permissions.length - 1} more permission${permissions.length === 2 ? "" : "s"}` : "";
11885
+ return `${context} Requested: ${summary}${more}.`;
11886
+ }
11868
11887
  function execCapturedCommand(command) {
11869
11888
  return new Promise((resolve3, reject) => {
11870
11889
  const child = spawn(process.execPath, [process.argv[1], ...command.argv], {
@@ -13541,12 +13560,17 @@ async function runSecretOperation(params) {
13541
13560
  node: params.node,
13542
13561
  requested,
13543
13562
  expiryOption: void 0,
13563
+ reason: secretPermissionReason(params.action, params.name),
13544
13564
  yes: true,
13545
13565
  force: true
13546
13566
  })
13547
13567
  );
13548
13568
  return runSecretOperationAttempt(params.label, params.operation);
13549
13569
  }
13570
+ function secretPermissionReason(action, name) {
13571
+ const target = name ? ` secret "${name}"` : " secrets";
13572
+ return `Allow \`tc secrets ${action}${name ? ` ${name}` : ""}\` to access${target} with the required TinyCloud permissions.`;
13573
+ }
13550
13574
  async function runSecretOperationAttempt(label, operation) {
13551
13575
  try {
13552
13576
  return await withSpinner(label, operation);
@@ -14257,8 +14281,8 @@ function registerSecretsCommand(program2) {
14257
14281
  });
14258
14282
  secrets.command("manage").description("Open the TinyCloud Secrets Manager in your browser").action(async () => {
14259
14283
  try {
14260
- const open = (await import("open")).default;
14261
- await open("https://secrets.tinycloud.xyz");
14284
+ const open2 = (await import("open")).default;
14285
+ await open2("https://secrets.tinycloud.xyz");
14262
14286
  outputJson({ opened: "https://secrets.tinycloud.xyz" });
14263
14287
  } catch (error) {
14264
14288
  handleError(error);
@@ -15474,6 +15498,310 @@ function plural(count, label) {
15474
15498
  return `${count} ${label}${count === 1 ? "" : "s"}`;
15475
15499
  }
15476
15500
 
15501
+ // src/commands/account.ts
15502
+ import open from "open";
15503
+ import { readFile as readFile10 } from "fs/promises";
15504
+ var ACCOUNT_BILLING_URL = "https://account.tinycloud.xyz/billing";
15505
+ function registerAccountCommand(program2) {
15506
+ const account = program2.command("account").description("Account applications, spaces, delegations, and billing");
15507
+ account.command("status").description("Show account status").action(async (_options, cmd) => {
15508
+ try {
15509
+ const node = await authenticatedNode(cmd);
15510
+ const status = await node.account.status();
15511
+ assertOk(status);
15512
+ outputJson(status.data);
15513
+ } catch (error) {
15514
+ handleError(error);
15515
+ }
15516
+ });
15517
+ const apps = account.command("apps").description("Manage account application registry");
15518
+ apps.command("list").description("List applications registered under account/applications").option("--live", "Read canonical account KV records instead of the SQLite index").action(async (_options, cmd) => {
15519
+ try {
15520
+ const options = _options;
15521
+ const node = await authenticatedNode(cmd);
15522
+ const result = options.live ? await node.account.applications.list() : await node.account.index.applications.list();
15523
+ assertOk(result);
15524
+ const payload = { applications: result.data, count: result.data.length };
15525
+ if (shouldOutputJson()) {
15526
+ outputJson(payload);
15527
+ return;
15528
+ }
15529
+ if (result.data.length === 0) {
15530
+ process.stdout.write(theme.muted("No account applications registered.") + "\n");
15531
+ return;
15532
+ }
15533
+ process.stdout.write(
15534
+ formatTable(
15535
+ ["App ID", "Name", "Manifests", "Updated"],
15536
+ result.data.map((app) => [
15537
+ app.appId,
15538
+ app.name ?? "\u2014",
15539
+ String(app.manifests.length),
15540
+ app.updatedAt ?? "\u2014"
15541
+ ])
15542
+ ) + "\n"
15543
+ );
15544
+ } catch (error) {
15545
+ handleError(error);
15546
+ }
15547
+ });
15548
+ const spaces = account.command("spaces").description("Manage account space registry");
15549
+ spaces.command("list").description("List spaces registered under account/spaces").option("--live", "Read canonical account KV records instead of the SQLite index").action(async (options, cmd) => {
15550
+ try {
15551
+ const node = await authenticatedNode(cmd);
15552
+ const result = options.live ? await node.account.spaces.list() : await node.account.index.spaces.list();
15553
+ assertOk(result);
15554
+ const payload = { spaces: result.data.map(formatSpace), count: result.data.length };
15555
+ if (shouldOutputJson()) {
15556
+ outputJson(payload);
15557
+ return;
15558
+ }
15559
+ if (result.data.length === 0) {
15560
+ process.stdout.write(theme.muted("No account spaces registered.") + "\n");
15561
+ return;
15562
+ }
15563
+ process.stdout.write(
15564
+ formatTable(
15565
+ ["Space", "Type", "Owner", "Status", "Updated"],
15566
+ result.data.map((space) => [
15567
+ space.name,
15568
+ space.type,
15569
+ space.ownerDid,
15570
+ space.status,
15571
+ space.updatedAt ?? "\u2014"
15572
+ ])
15573
+ ) + "\n"
15574
+ );
15575
+ } catch (error) {
15576
+ handleError(error);
15577
+ }
15578
+ });
15579
+ spaces.command("info <space-id>").description("Show a registered account space").action(async (spaceId, _options, cmd) => {
15580
+ try {
15581
+ const node = await authenticatedNode(cmd);
15582
+ const result = await node.account.spaces.get(spaceId);
15583
+ assertOk(result);
15584
+ outputJson(formatSpace(result.data));
15585
+ } catch (error) {
15586
+ handleError(error);
15587
+ }
15588
+ });
15589
+ spaces.command("register <space-id>").description("Register a space in account/spaces").requiredOption("--name <name>", "Display name for the space").requiredOption("--owner <did>", "Owner DID for the space").option("--type <type>", "Space type: owned, delegated, or discovered", "discovered").option("--permission <permission...>", "Permission strings for the space").action(async (spaceId, options, cmd) => {
15590
+ try {
15591
+ const node = await authenticatedNode(cmd);
15592
+ const result = await node.account.spaces.register({
15593
+ spaceId,
15594
+ name: options.name,
15595
+ ownerDid: options.owner,
15596
+ type: options.type,
15597
+ permissions: options.permission ?? [],
15598
+ status: "active"
15599
+ });
15600
+ assertOk(result);
15601
+ outputJson({ space: formatSpace(result.data), registered: true });
15602
+ } catch (error) {
15603
+ handleError(error);
15604
+ }
15605
+ });
15606
+ spaces.command("sync").description("Register currently accessible spaces into account/spaces").action(async (_options, cmd) => {
15607
+ try {
15608
+ const node = await authenticatedNode(cmd);
15609
+ const result = await node.account.spaces.syncAccessible();
15610
+ assertOk(result);
15611
+ outputJson({ spaces: result.data.map(formatSpace), count: result.data.length, synced: true });
15612
+ } catch (error) {
15613
+ handleError(error);
15614
+ }
15615
+ });
15616
+ spaces.command("remove <space-id>").alias("delete").description("Remove a space registry entry").action(async (spaceId, _options, cmd) => {
15617
+ try {
15618
+ const node = await authenticatedNode(cmd);
15619
+ const result = await node.account.spaces.remove(spaceId);
15620
+ assertOk(result);
15621
+ outputJson({ spaceId, removed: true });
15622
+ } catch (error) {
15623
+ handleError(error);
15624
+ }
15625
+ });
15626
+ apps.command("info <app-id>").description("Show a registered account application").action(async (appId, _options, cmd) => {
15627
+ try {
15628
+ const node = await authenticatedNode(cmd);
15629
+ const result = await node.account.applications.get(appId);
15630
+ assertOk(result);
15631
+ outputJson(result.data);
15632
+ } catch (error) {
15633
+ handleError(error);
15634
+ }
15635
+ });
15636
+ apps.command("register <manifest>").description("Register an app manifest in account/applications").action(async (manifestSource, _options, cmd) => {
15637
+ try {
15638
+ const node = await authenticatedNode(cmd);
15639
+ const manifest = await loadManifestSource2(manifestSource);
15640
+ const result = await node.account.applications.register(manifest);
15641
+ assertOk(result);
15642
+ outputJson({ application: result.data, registered: true });
15643
+ } catch (error) {
15644
+ handleError(error);
15645
+ }
15646
+ });
15647
+ apps.command("remove <app-id>").alias("delete").description("Remove an application registry entry").action(async (appId, _options, cmd) => {
15648
+ try {
15649
+ const node = await authenticatedNode(cmd);
15650
+ const result = await node.account.applications.remove(appId);
15651
+ assertOk(result);
15652
+ outputJson({ appId, removed: true });
15653
+ } catch (error) {
15654
+ handleError(error);
15655
+ }
15656
+ });
15657
+ const delegations = account.command("delegations").description("View and revoke account delegations");
15658
+ delegations.command("list").description("List delegations granted by or to this account").option("--granted", "Show only delegations granted by this account").option("--received", "Show only delegations granted to this account").option("--space <space>", "Filter by space name or ID").option("--live", "Read live delegation services instead of the SQLite index").action(async (options, cmd) => {
15659
+ try {
15660
+ if (options.granted && options.received) {
15661
+ throw new CLIError("USAGE_ERROR", "Use only one of --granted or --received.", ExitCode.USAGE_ERROR);
15662
+ }
15663
+ const node = await authenticatedNode(cmd);
15664
+ const direction = options.granted ? "granted" : options.received ? "received" : "all";
15665
+ const result = options.live ? await node.account.delegations.list({ direction, space: options.space }) : await node.account.index.delegations.list({ direction, space: options.space });
15666
+ assertOk(result);
15667
+ const payload = { delegations: result.data.map(formatDelegation2), count: result.data.length };
15668
+ if (shouldOutputJson()) {
15669
+ outputJson(payload);
15670
+ return;
15671
+ }
15672
+ if (result.data.length === 0) {
15673
+ process.stdout.write(theme.muted("No delegations found.") + "\n");
15674
+ return;
15675
+ }
15676
+ process.stdout.write(
15677
+ formatTable(
15678
+ ["CID", "Direction", "Space", "Counterparty", "Status", "Expiry"],
15679
+ result.data.map((delegation) => [
15680
+ delegation.cid,
15681
+ delegation.direction,
15682
+ delegation.spaceName ?? delegation.spaceId,
15683
+ delegation.counterpartyDid,
15684
+ delegation.status,
15685
+ delegation.expiry.toISOString()
15686
+ ])
15687
+ ) + "\n"
15688
+ );
15689
+ } catch (error) {
15690
+ handleError(error);
15691
+ }
15692
+ });
15693
+ delegations.command("revoke <cid>").description("Revoke an active delegation granted by this account").requiredOption("--space <space>", "Space name or ID containing the delegation").action(async (cid, options, cmd) => {
15694
+ try {
15695
+ const node = await authenticatedNode(cmd);
15696
+ const result = await node.account.delegations.revoke({ cid, space: options.space });
15697
+ assertOk(result);
15698
+ outputJson({ cid, space: options.space, revoked: true });
15699
+ } catch (error) {
15700
+ handleError(error);
15701
+ }
15702
+ });
15703
+ const index = account.command("index").description("Manage the materialized account SQLite index");
15704
+ index.command("status").description("Show account SQLite index sync status").action(async (_options, cmd) => {
15705
+ try {
15706
+ const node = await authenticatedNode(cmd);
15707
+ const result = await node.account.index.status();
15708
+ assertOk(result);
15709
+ outputJson(result.data);
15710
+ } catch (error) {
15711
+ handleError(error);
15712
+ }
15713
+ });
15714
+ index.command("rebuild").description("Rebuild account SQLite index from canonical account data").action(async (_options, cmd) => {
15715
+ try {
15716
+ const node = await authenticatedNode(cmd);
15717
+ const result = await node.account.index.rebuild();
15718
+ assertOk(result);
15719
+ outputJson(result.data);
15720
+ } catch (error) {
15721
+ handleError(error);
15722
+ }
15723
+ });
15724
+ index.command("query <sql>").description("Query the materialized account SQLite index").option("--params <json>", "Bind parameters as a JSON array for ? placeholders").action(async (sql, options, cmd) => {
15725
+ try {
15726
+ const node = await authenticatedNode(cmd);
15727
+ const params = parseParams(options.params);
15728
+ const result = await node.account.index.query(sql, params);
15729
+ assertOk(result);
15730
+ outputJson(result.data);
15731
+ } catch (error) {
15732
+ handleError(error);
15733
+ }
15734
+ });
15735
+ const billing = account.command("billing").description("Open account billing");
15736
+ for (const name of ["status", "checkout", "portal"]) {
15737
+ billing.command(name).description(`${name === "status" ? "Show" : "Open"} account billing page`).option("--open", "Open account.tinycloud.xyz in your browser").action(async (options) => {
15738
+ try {
15739
+ if (options.open) {
15740
+ await open(ACCOUNT_BILLING_URL);
15741
+ }
15742
+ outputJson({ url: ACCOUNT_BILLING_URL, opened: Boolean(options.open) });
15743
+ } catch (error) {
15744
+ handleError(error);
15745
+ }
15746
+ });
15747
+ }
15748
+ }
15749
+ async function authenticatedNode(cmd) {
15750
+ const globalOpts = cmd.optsWithGlobals();
15751
+ const ctx = await ProfileManager.resolveContext(globalOpts);
15752
+ return ensureAuthenticated(ctx);
15753
+ }
15754
+ function assertOk(result) {
15755
+ if (!result.ok) {
15756
+ throw new CLIError(result.error.code, result.error.message, ExitCode.ERROR);
15757
+ }
15758
+ }
15759
+ async function loadManifestSource2(source) {
15760
+ const raw = /^https?:\/\//i.test(source) ? await fetchManifest(source) : await readFile10(source, "utf8");
15761
+ return JSON.parse(raw);
15762
+ }
15763
+ async function fetchManifest(source) {
15764
+ const response = await fetch(source);
15765
+ if (!response.ok) {
15766
+ throw new CLIError(
15767
+ "MANIFEST_FETCH_FAILED",
15768
+ `Failed to fetch manifest from ${source}: ${response.status} ${response.statusText}`,
15769
+ ExitCode.NETWORK_ERROR
15770
+ );
15771
+ }
15772
+ return response.text();
15773
+ }
15774
+ function parseParams(input) {
15775
+ if (!input) return void 0;
15776
+ const parsed = JSON.parse(input);
15777
+ if (!Array.isArray(parsed)) {
15778
+ throw new CLIError("INVALID_PARAMS", "--params must be a JSON array.", ExitCode.USAGE_ERROR);
15779
+ }
15780
+ return parsed;
15781
+ }
15782
+ function formatDelegation2(delegation) {
15783
+ return {
15784
+ cid: delegation.cid,
15785
+ direction: delegation.direction,
15786
+ spaceId: delegation.spaceId,
15787
+ spaceName: delegation.spaceName,
15788
+ counterpartyDid: delegation.counterpartyDid,
15789
+ delegateDid: delegation.delegateDid,
15790
+ delegatorDid: delegation.delegatorDid,
15791
+ path: delegation.path,
15792
+ actions: delegation.actions,
15793
+ expiry: delegation.expiry.toISOString(),
15794
+ status: delegation.status,
15795
+ createdAt: delegation.createdAt?.toISOString()
15796
+ };
15797
+ }
15798
+ function formatSpace(space) {
15799
+ return {
15800
+ ...space,
15801
+ expiresAt: space.expiresAt?.toISOString()
15802
+ };
15803
+ }
15804
+
15477
15805
  // src/index.ts
15478
15806
  var { version } = JSON.parse(
15479
15807
  readFileSync3(new URL("../package.json", import.meta.url), "utf-8")
@@ -15524,6 +15852,7 @@ registerDuckdbCommand(program);
15524
15852
  registerManifestCommand(program);
15525
15853
  registerUpgradeCommand(program);
15526
15854
  registerStatusCommand(program);
15855
+ registerAccountCommand(program);
15527
15856
  program.addHelpText("before", () => `${theme.label("Version:")} ${theme.value(version)}
15528
15857
  `);
15529
15858
  program.addHelpText("afterAll", () => {
@@ -15535,6 +15864,7 @@ ${theme.heading("Examples:")}
15535
15864
  ${theme.command('tc kv put greeting "Hello"')} ${theme.muted("Store a value")}
15536
15865
  ${theme.command("tc kv list")} ${theme.muted("List all keys")}
15537
15866
  ${theme.command("tc secrets network init")} ${theme.muted("Create the default secrets network")}
15867
+ ${theme.command("tc account apps list")} ${theme.muted("List registered account apps")}
15538
15868
  ${theme.command("tc delegation create --to did:pkh:...")} ${theme.muted("Grant access to another user")}
15539
15869
  ${theme.command("tc space list")} ${theme.muted("Show your spaces")}
15540
15870