@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 +16 -2
- package/dist/index.js +115 -83
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
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
|
|
2656
|
-
const
|
|
2657
|
-
|
|
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
|
-
|
|
2667
|
-
const
|
|
2668
|
-
|
|
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("
|
|
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
|
|
2680
|
-
const
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
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
|
|
2694
|
-
const
|
|
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
|
-
...
|
|
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
|
|
2712
|
-
const
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
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
|
|
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
|
|
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
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
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
|
|
2796
|
-
const
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|