@tinycloud/cli 0.5.0-beta.13 → 0.5.1-beta.0

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 CHANGED
@@ -24,6 +24,11 @@ tc kv put greeting "Hello, world"
24
24
  tc kv get greeting
25
25
  tc kv list
26
26
 
27
+ # Manage network-encrypted secrets on the default network
28
+ tc secrets network init
29
+ tc secrets put ANTHROPIC_API_KEY "sk-..."
30
+ tc secrets get ANTHROPIC_API_KEY
31
+
27
32
  # Manage spaces
28
33
  tc space list
29
34
  tc space create
@@ -55,12 +60,21 @@ tc delegation create --to did:pkh:eip155:1:0x...
55
60
  | `tc profile list` | List profiles |
56
61
  | `tc profile show` | Show profile details |
57
62
  | `tc profile create` | Create a new profile |
58
- | `tc vault` | Manage encrypted vaults |
59
- | `tc secrets` | Manage secrets |
63
+ | `tc vault` | Manage encrypted KV vaults |
64
+ | `tc secrets` | Manage network-encrypted secrets |
65
+ | `tc secrets network show` | Show a secrets decryption network |
66
+ | `tc secrets network init` | Create or fetch the default secrets network |
67
+ | `tc secrets network grant` | Grant `tinycloud.encryption/decrypt` on a secrets network |
60
68
  | `tc vars` | Manage environment variables |
61
69
  | `tc doctor` | Run diagnostic checks |
62
70
  | `tc completion` | Generate shell completions |
63
71
 
72
+ Secret names are env-style uppercase identifiers such as `FIREFLIES_API_KEY`.
73
+ `tc secrets network show` accepts either a short network name or a full
74
+ `urn:tinycloud:encryption:<principal>:<network>` identifier. `tc secrets
75
+ network grant` takes the short name, resolves the network, and grants
76
+ `tinycloud.encryption/decrypt`.
77
+
64
78
  ## Global Options
65
79
 
66
80
  ```
package/dist/index.js CHANGED
@@ -2643,8 +2643,6 @@ function registerVaultCommand(program2) {
2643
2643
  // src/commands/secrets.ts
2644
2644
  import { readFile as readFile5 } from "fs/promises";
2645
2645
  import { writeFile as writeFile4 } from "fs/promises";
2646
- import { PrivateKeySigner as PrivateKeySigner3 } from "@tinycloud/node-sdk";
2647
- var SECRETS_PREFIX = "secrets/";
2648
2646
  async function readStdin3() {
2649
2647
  const chunks = [];
2650
2648
  for await (const chunk of process.stdin) {
@@ -2652,92 +2650,96 @@ async function readStdin3() {
2652
2650
  }
2653
2651
  return Buffer.concat(chunks);
2654
2652
  }
2655
- function resolvePrivateKey2(options) {
2656
- const key = options.privateKey || process.env.TC_PRIVATE_KEY;
2657
- if (!key) {
2658
- throw new CLIError(
2659
- "AUTH_REQUIRED",
2660
- "Private key required. Use --private-key <hex> or set TC_PRIVATE_KEY env var.",
2661
- ExitCode.AUTH_REQUIRED
2662
- );
2663
- }
2664
- return key;
2653
+ function authOptions(options) {
2654
+ const privateKey = options.privateKey || process.env.TC_PRIVATE_KEY;
2655
+ return privateKey ? { privateKey } : void 0;
2665
2656
  }
2666
- async function unlockVault2(node, privateKey) {
2667
- const signer = new PrivateKeySigner3(privateKey);
2668
- const result = await node.vault.unlock(signer);
2669
- if (result && !result.ok) {
2670
- throw new CLIError(result.error.code, result.error.message, ExitCode.ERROR);
2671
- }
2657
+ function resolveSecretScope(options) {
2658
+ const scope = options.scope ?? options.space;
2659
+ return scope ? { scope } : void 0;
2672
2660
  }
2673
2661
  function registerSecretsCommand(program2) {
2674
2662
  const secrets = program2.command("secrets").description("Encrypted secrets management");
2675
- secrets.command("list").description("List secrets").option("--space <spaceId>", "Space to list secrets from (for delegated access)").option("--private-key <hex>", "Ethereum private key (or set TC_PRIVATE_KEY)").action(async (options, cmd) => {
2663
+ const network = secrets.command("network").description("Manage the default secrets encryption network");
2664
+ network.command("show [nameOrNetworkId]").description("Show a secrets encryption network").option("--private-key <hex>", "Ethereum private key override (or set TC_PRIVATE_KEY)").action(async (nameOrNetworkId, options, cmd) => {
2676
2665
  try {
2677
2666
  const globalOpts = cmd.optsWithGlobals();
2678
2667
  const ctx = await ProfileManager.resolveContext(globalOpts);
2679
- const privateKey = resolvePrivateKey2(options);
2680
- const node = await ensureAuthenticated(ctx, { privateKey });
2681
- await withSpinner("Unlocking vault...", () => unlockVault2(node, privateKey));
2682
- if (options.space) {
2683
- throw new CLIError(
2684
- "NOT_IMPLEMENTED",
2685
- `Listing secrets from a delegated space (${options.space}) is not yet supported at the SDK level. The vault service currently operates on the space bound to the active session. SDK support for cross-space vault operations is planned.`,
2686
- ExitCode.ERROR
2687
- );
2688
- }
2689
- const result = await withSpinner("Listing secrets...", () => node.vault.list({ prefix: SECRETS_PREFIX }));
2668
+ const node = await ensureAuthenticated(ctx, authOptions(options));
2669
+ const requested = nameOrNetworkId ?? "default";
2670
+ const networkId = requested.startsWith("urn:tinycloud:encryption:") ? requested : node.getDefaultEncryptionNetworkId(requested);
2671
+ const descriptor = await withSpinner(
2672
+ "Fetching encryption network...",
2673
+ () => node.getEncryptionNetwork(requested)
2674
+ );
2675
+ outputJson({
2676
+ networkId,
2677
+ exists: descriptor !== null,
2678
+ ...descriptor ? { descriptor } : {}
2679
+ });
2680
+ } catch (error) {
2681
+ handleError(error);
2682
+ }
2683
+ });
2684
+ network.command("init [name]").description("Create a secrets encryption network if needed").option("--private-key <hex>", "Ethereum private key override (or set TC_PRIVATE_KEY)").action(async (name, options, cmd) => {
2685
+ try {
2686
+ const globalOpts = cmd.optsWithGlobals();
2687
+ const ctx = await ProfileManager.resolveContext(globalOpts);
2688
+ const node = await ensureAuthenticated(ctx, authOptions(options));
2689
+ const descriptor = await withSpinner(
2690
+ "Ensuring encryption network...",
2691
+ () => node.ensureEncryptionNetwork(name ?? "default")
2692
+ );
2693
+ outputJson({
2694
+ networkId: descriptor.networkId,
2695
+ state: descriptor.state,
2696
+ descriptor
2697
+ });
2698
+ } catch (error) {
2699
+ handleError(error);
2700
+ }
2701
+ });
2702
+ secrets.command("list").description("List secrets").option("--scope <scope>", "Logical secret scope").option("--space <scope>", "Deprecated alias for --scope").option("--private-key <hex>", "Ethereum private key (or set TC_PRIVATE_KEY)").action(async (options, cmd) => {
2703
+ try {
2704
+ const globalOpts = cmd.optsWithGlobals();
2705
+ const ctx = await ProfileManager.resolveContext(globalOpts);
2706
+ const node = await ensureAuthenticated(ctx, authOptions(options));
2707
+ const scopeOptions = resolveSecretScope(options);
2708
+ const result = await withSpinner(
2709
+ "Listing secrets...",
2710
+ () => node.secrets.list(scopeOptions)
2711
+ );
2690
2712
  if (!result.ok) {
2691
2713
  throw new CLIError(result.error.code, result.error.message, ExitCode.ERROR);
2692
2714
  }
2693
- const keys = result.data.data ?? result.data;
2694
- const keyList = Array.isArray(keys) ? keys : [];
2695
- const secretNames = keyList.map(
2696
- (k) => typeof k === "string" && k.startsWith(SECRETS_PREFIX) ? k.slice(SECRETS_PREFIX.length) : k
2697
- );
2715
+ const secretNames = Array.isArray(result.data) ? result.data : [];
2716
+ const scope = options.scope ?? options.space;
2698
2717
  outputJson({
2699
2718
  secrets: secretNames,
2700
2719
  count: secretNames.length,
2701
- ...options.space ? { space: options.space } : {}
2720
+ ...scope ? { scope } : {}
2702
2721
  });
2703
2722
  } catch (error) {
2704
2723
  handleError(error);
2705
2724
  }
2706
2725
  });
2707
- secrets.command("get <name>").description("Get a secret value").option("--raw", "Output raw value (no JSON wrapping)").option("-o, --output <file>", "Write value to file").option("--private-key <hex>", "Ethereum private key (or set TC_PRIVATE_KEY)").action(async (name, options, cmd) => {
2726
+ secrets.command("get <name>").description("Get a secret value").option("--scope <scope>", "Logical secret scope").option("--space <scope>", "Deprecated alias for --scope").option("--raw", "Output raw value (no JSON wrapping)").option("-o, --output <file>", "Write value to file").option("--private-key <hex>", "Ethereum private key (or set TC_PRIVATE_KEY)").action(async (name, options, cmd) => {
2708
2727
  try {
2709
2728
  const globalOpts = cmd.optsWithGlobals();
2710
2729
  const ctx = await ProfileManager.resolveContext(globalOpts);
2711
- const privateKey = resolvePrivateKey2(options);
2712
- const node = await ensureAuthenticated(ctx, { privateKey });
2713
- await withSpinner("Unlocking vault...", () => unlockVault2(node, privateKey));
2714
- const vaultKey = `${SECRETS_PREFIX}${name}`;
2715
- const result = await withSpinner(`Getting secret ${name}...`, () => node.vault.get(vaultKey));
2730
+ const node = await ensureAuthenticated(ctx, authOptions(options));
2731
+ const scopeOptions = resolveSecretScope(options);
2732
+ const result = await withSpinner(
2733
+ `Getting secret ${name}...`,
2734
+ () => node.secrets.get(name, scopeOptions)
2735
+ );
2716
2736
  if (!result.ok) {
2717
- if (result.error.code === "NOT_FOUND") {
2737
+ if (result.error.code === "NOT_FOUND" || result.error.code === "KEY_NOT_FOUND") {
2718
2738
  throw new CLIError("NOT_FOUND", `Secret "${name}" not found`, ExitCode.NOT_FOUND);
2719
2739
  }
2720
2740
  throw new CLIError(result.error.code, result.error.message, ExitCode.ERROR);
2721
2741
  }
2722
- const data = result.data.data ?? result.data;
2723
- let value;
2724
- if (typeof data === "string") {
2725
- try {
2726
- const parsed = JSON.parse(data);
2727
- value = parsed.value;
2728
- } catch {
2729
- value = data;
2730
- }
2731
- } else if (data instanceof Uint8Array) {
2732
- try {
2733
- const parsed = JSON.parse(Buffer.from(data).toString("utf-8"));
2734
- value = parsed.value;
2735
- } catch {
2736
- value = Buffer.from(data).toString("utf-8");
2737
- }
2738
- } else {
2739
- value = data.value ?? data;
2740
- }
2742
+ const value = String(result.data);
2741
2743
  if (options.output) {
2742
2744
  await writeFile4(options.output, value);
2743
2745
  outputJson({ name, written: options.output });
@@ -2752,13 +2754,11 @@ function registerSecretsCommand(program2) {
2752
2754
  handleError(error);
2753
2755
  }
2754
2756
  });
2755
- secrets.command("put <name> [value]").description("Store a secret").option("--file <path>", "Read value from file").option("--stdin", "Read value from stdin").option("--private-key <hex>", "Ethereum private key (or set TC_PRIVATE_KEY)").action(async (name, value, options, cmd) => {
2757
+ secrets.command("put <name> [value]").description("Store a secret").option("--scope <scope>", "Logical secret scope").option("--space <scope>", "Deprecated alias for --scope").option("--file <path>", "Read value from file").option("--stdin", "Read value from stdin").option("--private-key <hex>", "Ethereum private key (or set TC_PRIVATE_KEY)").action(async (name, value, options, cmd) => {
2756
2758
  try {
2757
2759
  const globalOpts = cmd.optsWithGlobals();
2758
2760
  const ctx = await ProfileManager.resolveContext(globalOpts);
2759
- const privateKey = resolvePrivateKey2(options);
2760
- const node = await ensureAuthenticated(ctx, { privateKey });
2761
- await withSpinner("Unlocking vault...", () => unlockVault2(node, privateKey));
2761
+ const node = await ensureAuthenticated(ctx, authOptions(options));
2762
2762
  let secretValue;
2763
2763
  const sources = [value !== void 0, !!options.file, !!options.stdin].filter(Boolean);
2764
2764
  if (sources.length === 0) {
@@ -2774,12 +2774,11 @@ function registerSecretsCommand(program2) {
2774
2774
  } else {
2775
2775
  secretValue = value;
2776
2776
  }
2777
- const payload = JSON.stringify({
2778
- value: secretValue,
2779
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
2780
- });
2781
- const vaultKey = `${SECRETS_PREFIX}${name}`;
2782
- const result = await withSpinner(`Storing secret ${name}...`, () => node.vault.put(vaultKey, payload));
2777
+ const scopeOptions = resolveSecretScope(options);
2778
+ const result = await withSpinner(
2779
+ `Storing secret ${name}...`,
2780
+ () => node.secrets.put(name, secretValue, scopeOptions)
2781
+ );
2783
2782
  if (!result.ok) {
2784
2783
  throw new CLIError(result.error.code, result.error.message, ExitCode.ERROR);
2785
2784
  }
@@ -2788,15 +2787,16 @@ function registerSecretsCommand(program2) {
2788
2787
  handleError(error);
2789
2788
  }
2790
2789
  });
2791
- secrets.command("delete <name>").description("Delete a secret").option("--private-key <hex>", "Ethereum private key (or set TC_PRIVATE_KEY)").action(async (name, options, cmd) => {
2790
+ secrets.command("delete <name>").description("Delete a secret").option("--scope <scope>", "Logical secret scope").option("--space <scope>", "Deprecated alias for --scope").option("--private-key <hex>", "Ethereum private key (or set TC_PRIVATE_KEY)").action(async (name, options, cmd) => {
2792
2791
  try {
2793
2792
  const globalOpts = cmd.optsWithGlobals();
2794
2793
  const ctx = await ProfileManager.resolveContext(globalOpts);
2795
- const privateKey = resolvePrivateKey2(options);
2796
- const node = await ensureAuthenticated(ctx, { privateKey });
2797
- await withSpinner("Unlocking vault...", () => unlockVault2(node, privateKey));
2798
- const vaultKey = `${SECRETS_PREFIX}${name}`;
2799
- const result = await withSpinner(`Deleting secret ${name}...`, () => node.vault.delete(vaultKey));
2794
+ const node = await ensureAuthenticated(ctx, authOptions(options));
2795
+ const scopeOptions = resolveSecretScope(options);
2796
+ const result = await withSpinner(
2797
+ `Deleting secret ${name}...`,
2798
+ () => node.secrets.delete(name, scopeOptions)
2799
+ );
2800
2800
  if (!result.ok) {
2801
2801
  throw new CLIError(result.error.code, result.error.message, ExitCode.ERROR);
2802
2802
  }
@@ -2805,6 +2805,37 @@ function registerSecretsCommand(program2) {
2805
2805
  handleError(error);
2806
2806
  }
2807
2807
  });
2808
+ network.command("grant <recipientDid> [name]").description("Grant decrypt permission for a secrets encryption network").option("--private-key <hex>", "Ethereum private key override (or set TC_PRIVATE_KEY)").action(async (recipientDid, name, options, cmd) => {
2809
+ try {
2810
+ const globalOpts = cmd.optsWithGlobals();
2811
+ const ctx = await ProfileManager.resolveContext(globalOpts);
2812
+ const node = await ensureAuthenticated(ctx, authOptions(options));
2813
+ const networkName = name ?? "default";
2814
+ const descriptor = await withSpinner(
2815
+ "Ensuring encryption network...",
2816
+ () => node.ensureEncryptionNetwork(networkName)
2817
+ );
2818
+ const permission = {
2819
+ service: "tinycloud.encryption",
2820
+ path: descriptor.networkId,
2821
+ actions: ["decrypt"]
2822
+ };
2823
+ const result = await withSpinner(
2824
+ `Granting decrypt permission to ${recipientDid}...`,
2825
+ () => node.delegateTo(recipientDid, [permission])
2826
+ );
2827
+ outputJson({
2828
+ networkId: descriptor.networkId,
2829
+ recipientDid,
2830
+ cid: result.delegation.cid,
2831
+ prompted: result.prompted,
2832
+ path: result.delegation.path,
2833
+ actions: result.delegation.actions
2834
+ });
2835
+ } catch (error) {
2836
+ handleError(error);
2837
+ }
2838
+ });
2808
2839
  secrets.command("manage").description("Open the TinyCloud Secrets Manager in your browser").action(async () => {
2809
2840
  try {
2810
2841
  const open = (await import("open")).default;
@@ -2827,7 +2858,7 @@ async function readStdin4() {
2827
2858
  }
2828
2859
  return Buffer.concat(chunks);
2829
2860
  }
2830
- function resolvePrivateKey3(options) {
2861
+ function resolvePrivateKey2(options) {
2831
2862
  const key = options.privateKey || process.env.TC_PRIVATE_KEY;
2832
2863
  if (!key) {
2833
2864
  throw new CLIError(
@@ -2844,7 +2875,7 @@ function registerVarsCommand(program2) {
2844
2875
  try {
2845
2876
  const globalOpts = cmd.optsWithGlobals();
2846
2877
  const ctx = await ProfileManager.resolveContext(globalOpts);
2847
- const privateKey = resolvePrivateKey3(options);
2878
+ const privateKey = resolvePrivateKey2(options);
2848
2879
  const node = await ensureAuthenticated(ctx, { privateKey });
2849
2880
  const prefixedKv = node.kv.withPrefix(VARIABLES_PREFIX);
2850
2881
  const result = await withSpinner("Listing variables...", () => prefixedKv.list());
@@ -2865,7 +2896,7 @@ function registerVarsCommand(program2) {
2865
2896
  try {
2866
2897
  const globalOpts = cmd.optsWithGlobals();
2867
2898
  const ctx = await ProfileManager.resolveContext(globalOpts);
2868
- const privateKey = resolvePrivateKey3(options);
2899
+ const privateKey = resolvePrivateKey2(options);
2869
2900
  const node = await ensureAuthenticated(ctx, { privateKey });
2870
2901
  const prefixedKv = node.kv.withPrefix(VARIABLES_PREFIX);
2871
2902
  const result = await withSpinner(`Getting variable ${name}...`, () => prefixedKv.get(name));
@@ -2907,7 +2938,7 @@ function registerVarsCommand(program2) {
2907
2938
  try {
2908
2939
  const globalOpts = cmd.optsWithGlobals();
2909
2940
  const ctx = await ProfileManager.resolveContext(globalOpts);
2910
- const privateKey = resolvePrivateKey3(options);
2941
+ const privateKey = resolvePrivateKey2(options);
2911
2942
  const node = await ensureAuthenticated(ctx, { privateKey });
2912
2943
  let varValue;
2913
2944
  const sources = [value !== void 0, !!options.file, !!options.stdin].filter(Boolean);
@@ -2942,7 +2973,7 @@ function registerVarsCommand(program2) {
2942
2973
  try {
2943
2974
  const globalOpts = cmd.optsWithGlobals();
2944
2975
  const ctx = await ProfileManager.resolveContext(globalOpts);
2945
- const privateKey = resolvePrivateKey3(options);
2976
+ const privateKey = resolvePrivateKey2(options);
2946
2977
  const node = await ensureAuthenticated(ctx, { privateKey });
2947
2978
  const prefixedKv = node.kv.withPrefix(VARIABLES_PREFIX);
2948
2979
  const result = await withSpinner(`Deleting variable ${name}...`, () => prefixedKv.delete(name));
@@ -3733,6 +3764,7 @@ ${theme.heading("Examples:")}
3733
3764
  ${theme.command("tc auth login")} ${theme.muted("Authenticate via browser")}
3734
3765
  ${theme.command('tc kv put greeting "Hello"')} ${theme.muted("Store a value")}
3735
3766
  ${theme.command("tc kv list")} ${theme.muted("List all keys")}
3767
+ ${theme.command("tc secrets network init")} ${theme.muted("Create the default secrets network")}
3736
3768
  ${theme.command("tc delegation create --to did:pkh:...")} ${theme.muted("Grant access to another user")}
3737
3769
  ${theme.command("tc space list")} ${theme.muted("Show your spaces")}
3738
3770