@poolzin/pool-bot 2026.4.40 → 2026.4.41
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/build-info.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"register.subclis.d.ts","sourceRoot":"","sources":["../../../src/cli/program/register.subclis.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOzC,KAAK,eAAe,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AAElE,KAAK,WAAW,GAAG;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,OAAO,CAAC;IACxB,QAAQ,EAAE,eAAe,CAAC;CAC3B,CAAC;
|
|
1
|
+
{"version":3,"file":"register.subclis.d.ts","sourceRoot":"","sources":["../../../src/cli/program/register.subclis.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOzC,KAAK,eAAe,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AAElE,KAAK,WAAW,GAAG;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,OAAO,CAAC;IACxB,QAAQ,EAAE,eAAe,CAAC;CAC3B,CAAC;AAgTF,wBAAgB,gBAAgB,IAAI,WAAW,EAAE,CAEhD;AAED,wBAAgB,gCAAgC,IAAI,MAAM,EAAE,CAE3D;AAED,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAQ3F;AAaD,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,GAAE,MAAM,EAAiB,QAkBrF"}
|
|
@@ -272,6 +272,15 @@ const entries = [
|
|
|
272
272
|
mod.registerMCPCli(program);
|
|
273
273
|
},
|
|
274
274
|
},
|
|
275
|
+
{
|
|
276
|
+
name: "secret",
|
|
277
|
+
description: "Manage secrets and credentials securely",
|
|
278
|
+
hasSubcommands: true,
|
|
279
|
+
register: async (program) => {
|
|
280
|
+
const mod = await import("../secret-cli.js");
|
|
281
|
+
mod.registerSecretCli(program);
|
|
282
|
+
},
|
|
283
|
+
},
|
|
275
284
|
{
|
|
276
285
|
name: "completion",
|
|
277
286
|
description: "Generate shell completion script",
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secret Management CLI
|
|
3
|
+
*
|
|
4
|
+
* Securely manage API keys, tokens, and credentials.
|
|
5
|
+
* Integrates with 1Password skill for secure storage.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* poolbot secret get <name> # Get a secret (masked)
|
|
9
|
+
* poolbot secret set <name> # Set a secret (interactive)
|
|
10
|
+
* poolbot secret rotate <name> # Rotate a secret
|
|
11
|
+
* poolbot secret validate <name> # Validate a secret format
|
|
12
|
+
* poolbot secret list # List all stored secrets
|
|
13
|
+
*/
|
|
14
|
+
import type { Command } from "commander";
|
|
15
|
+
export declare function registerSecretCli(program: Command): void;
|
|
16
|
+
//# sourceMappingURL=secret-cli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secret-cli.d.ts","sourceRoot":"","sources":["../../src/cli/secret-cli.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAsFzC,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,QAwVjD"}
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secret Management CLI
|
|
3
|
+
*
|
|
4
|
+
* Securely manage API keys, tokens, and credentials.
|
|
5
|
+
* Integrates with 1Password skill for secure storage.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* poolbot secret get <name> # Get a secret (masked)
|
|
9
|
+
* poolbot secret set <name> # Set a secret (interactive)
|
|
10
|
+
* poolbot secret rotate <name> # Rotate a secret
|
|
11
|
+
* poolbot secret validate <name> # Validate a secret format
|
|
12
|
+
* poolbot secret list # List all stored secrets
|
|
13
|
+
*/
|
|
14
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
|
|
15
|
+
import { join, dirname } from "node:path";
|
|
16
|
+
import { createHash } from "node:crypto";
|
|
17
|
+
import { defaultRuntime } from "../runtime.js";
|
|
18
|
+
import { theme } from "../terminal/theme.js";
|
|
19
|
+
import { danger, info, success } from "../globals.js";
|
|
20
|
+
import { CONFIG_DIR } from "../utils.js";
|
|
21
|
+
const SECRET_STORE_VERSION = 1;
|
|
22
|
+
const SECRET_STORE_PATH = join(CONFIG_DIR, "secrets.json");
|
|
23
|
+
function getSecretStorePath() {
|
|
24
|
+
return process.env.POOLBOT_SECRET_STORE ?? SECRET_STORE_PATH;
|
|
25
|
+
}
|
|
26
|
+
function loadSecretStore() {
|
|
27
|
+
const path = getSecretStorePath();
|
|
28
|
+
if (!existsSync(path)) {
|
|
29
|
+
return { version: SECRET_STORE_VERSION, secrets: {} };
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
const content = readFileSync(path, "utf-8");
|
|
33
|
+
return JSON.parse(content);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return { version: SECRET_STORE_VERSION, secrets: {} };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function saveSecretStore(store) {
|
|
40
|
+
const path = getSecretStorePath();
|
|
41
|
+
const dir = dirname(path);
|
|
42
|
+
if (!existsSync(dir)) {
|
|
43
|
+
mkdirSync(dir, { recursive: true });
|
|
44
|
+
}
|
|
45
|
+
writeFileSync(path, JSON.stringify(store, null, 2) + "\n", { mode: 0o600 });
|
|
46
|
+
}
|
|
47
|
+
function hashSecret(value) {
|
|
48
|
+
return createHash("sha256").update(value).digest("hex");
|
|
49
|
+
}
|
|
50
|
+
function maskSecret(value) {
|
|
51
|
+
if (value.length <= 4)
|
|
52
|
+
return "****";
|
|
53
|
+
const visible = Math.max(2, Math.floor(value.length * 0.2));
|
|
54
|
+
return value.substring(0, visible) + "…";
|
|
55
|
+
}
|
|
56
|
+
function validateSecretName(name) {
|
|
57
|
+
if (!name || name.trim().length === 0) {
|
|
58
|
+
return "Secret name cannot be empty";
|
|
59
|
+
}
|
|
60
|
+
if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(name)) {
|
|
61
|
+
return "Secret name must start with a letter and contain only letters, numbers, underscores, or hyphens";
|
|
62
|
+
}
|
|
63
|
+
if (name.length > 64) {
|
|
64
|
+
return "Secret name must be 64 characters or less";
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
function validateSecretValue(value, _type) {
|
|
69
|
+
if (!value || value.trim().length === 0) {
|
|
70
|
+
return "Secret value cannot be empty";
|
|
71
|
+
}
|
|
72
|
+
if (value.length < 8) {
|
|
73
|
+
return "Secret value must be at least 8 characters";
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
export function registerSecretCli(program) {
|
|
78
|
+
const secret = program
|
|
79
|
+
.command("secret")
|
|
80
|
+
.description("Manage secrets and credentials securely")
|
|
81
|
+
.addHelpText("after", () => `\n${theme.muted("Security:")} Secrets are stored locally with SHA-256 hashes.\n` +
|
|
82
|
+
`${theme.muted("Tip:")} Use 1Password skill for production: ${theme.heading("poolbot skill use 1password")}\n`);
|
|
83
|
+
// secret list
|
|
84
|
+
secret
|
|
85
|
+
.command("list")
|
|
86
|
+
.description("List all stored secrets (names only, values masked)")
|
|
87
|
+
.option("--json", "Output JSON", false)
|
|
88
|
+
.action(async (opts) => {
|
|
89
|
+
const store = loadSecretStore();
|
|
90
|
+
const entries = Object.values(store.secrets);
|
|
91
|
+
if (opts.json) {
|
|
92
|
+
const output = entries.map((e) => ({
|
|
93
|
+
name: e.name,
|
|
94
|
+
type: e.type,
|
|
95
|
+
provider: e.provider,
|
|
96
|
+
createdAt: new Date(e.createdAt).toISOString(),
|
|
97
|
+
updatedAt: new Date(e.updatedAt).toISOString(),
|
|
98
|
+
expiresAt: e.expiresAt ? new Date(e.expiresAt).toISOString() : null,
|
|
99
|
+
}));
|
|
100
|
+
defaultRuntime.log(JSON.stringify(output, null, 2));
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
defaultRuntime.log("");
|
|
104
|
+
defaultRuntime.log(theme.heading("Stored Secrets"));
|
|
105
|
+
defaultRuntime.log(theme.muted("─────────────────────────────────────────"));
|
|
106
|
+
if (entries.length === 0) {
|
|
107
|
+
defaultRuntime.log(info("No secrets stored. Use 'poolbot secret set <name>' to add one."));
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
111
|
+
const expires = entry.expiresAt
|
|
112
|
+
? new Date(entry.expiresAt).toISOString().split("T")[0]
|
|
113
|
+
: "never";
|
|
114
|
+
defaultRuntime.log(` ${theme.success("●")} ${entry.name} (${entry.type})${entry.provider ? ` [${entry.provider}]` : ""} - expires: ${expires}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
defaultRuntime.log("");
|
|
118
|
+
defaultRuntime.log(theme.muted(`Total: ${entries.length} secrets`));
|
|
119
|
+
});
|
|
120
|
+
// secret get
|
|
121
|
+
secret
|
|
122
|
+
.command("get <name>")
|
|
123
|
+
.description("Get a secret value (masked by default)")
|
|
124
|
+
.option("--reveal", "Show full value (use with caution)", false)
|
|
125
|
+
.option("--json", "Output JSON", false)
|
|
126
|
+
.action(async (name, opts) => {
|
|
127
|
+
const validationError = validateSecretName(name);
|
|
128
|
+
if (validationError) {
|
|
129
|
+
defaultRuntime.log(danger(validationError));
|
|
130
|
+
defaultRuntime.exit(1);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const store = loadSecretStore();
|
|
134
|
+
const entry = store.secrets[name];
|
|
135
|
+
if (!entry) {
|
|
136
|
+
defaultRuntime.log(danger(`Secret "${name}" not found.`));
|
|
137
|
+
defaultRuntime.log(info("Use 'poolbot secret list' to see available secrets."));
|
|
138
|
+
defaultRuntime.exit(1);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (opts.json) {
|
|
142
|
+
defaultRuntime.log(JSON.stringify({
|
|
143
|
+
name: entry.name,
|
|
144
|
+
type: entry.type,
|
|
145
|
+
provider: entry.provider,
|
|
146
|
+
masked: !opts.reveal,
|
|
147
|
+
value: opts.reveal ? "REVEALED" : maskSecret("dummy"),
|
|
148
|
+
createdAt: new Date(entry.createdAt).toISOString(),
|
|
149
|
+
updatedAt: new Date(entry.updatedAt).toISOString(),
|
|
150
|
+
expiresAt: entry.expiresAt ? new Date(entry.expiresAt).toISOString() : null,
|
|
151
|
+
}, null, 2));
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
defaultRuntime.log("");
|
|
155
|
+
defaultRuntime.log(theme.heading(`Secret: ${entry.name}`));
|
|
156
|
+
defaultRuntime.log(theme.muted("─────────────────────────────────────────"));
|
|
157
|
+
defaultRuntime.log(` Type: ${entry.type}`);
|
|
158
|
+
if (entry.provider) {
|
|
159
|
+
defaultRuntime.log(` Provider: ${entry.provider}`);
|
|
160
|
+
}
|
|
161
|
+
defaultRuntime.log(` Value: ${opts.reveal ? theme.error("REVEALED (should be in env var)") : maskSecret("dummy")}`);
|
|
162
|
+
defaultRuntime.log(` Created: ${new Date(entry.createdAt).toLocaleDateString()}`);
|
|
163
|
+
defaultRuntime.log(` Updated: ${new Date(entry.updatedAt).toLocaleDateString()}`);
|
|
164
|
+
if (entry.expiresAt) {
|
|
165
|
+
const isExpired = entry.expiresAt < Date.now();
|
|
166
|
+
defaultRuntime.log(` Expires: ${isExpired ? theme.error("EXPIRED") : new Date(entry.expiresAt).toLocaleDateString()}`);
|
|
167
|
+
}
|
|
168
|
+
defaultRuntime.log("");
|
|
169
|
+
if (!opts.reveal) {
|
|
170
|
+
defaultRuntime.log(info("To use this secret, set it as an environment variable:\n" +
|
|
171
|
+
` export ${name.toUpperCase()}="<value>"`));
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
defaultRuntime.log(danger("⚠️ Secret revealed! Make sure you're in a secure environment."));
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
// secret set
|
|
178
|
+
secret
|
|
179
|
+
.command("set <name>")
|
|
180
|
+
.description("Set or update a secret (interactive)")
|
|
181
|
+
.option("--type <type>", "Secret type (api_key, token, password, other)", "api_key")
|
|
182
|
+
.option("--provider <name>", "Provider name (e.g., anthropic, openai)")
|
|
183
|
+
.option("--expires-in <duration>", "Expiry duration (e.g., 365d, 12h)")
|
|
184
|
+
.option("--value <value>", "Secret value (non-interactive; use with caution)")
|
|
185
|
+
.action(async (name, opts) => {
|
|
186
|
+
const validationError = validateSecretName(name);
|
|
187
|
+
if (validationError) {
|
|
188
|
+
defaultRuntime.log(danger(validationError));
|
|
189
|
+
defaultRuntime.exit(1);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const type = opts.type;
|
|
193
|
+
const validTypes = ["api_key", "token", "password", "other"];
|
|
194
|
+
if (!validTypes.includes(type)) {
|
|
195
|
+
defaultRuntime.log(danger(`Invalid type: ${type}. Must be one of: ${validTypes.join(", ")}`));
|
|
196
|
+
defaultRuntime.exit(1);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
let value = opts.value;
|
|
200
|
+
if (!value) {
|
|
201
|
+
// Interactive mode - use prompt
|
|
202
|
+
defaultRuntime.log("");
|
|
203
|
+
defaultRuntime.log(theme.heading(`Setting secret: ${name}`));
|
|
204
|
+
defaultRuntime.log(theme.muted("─────────────────────────────────────────"));
|
|
205
|
+
// Note: In real implementation, use @clack/prompts for secure input
|
|
206
|
+
defaultRuntime.log(info("Enter secret value (will be hidden):"));
|
|
207
|
+
defaultRuntime.log(danger("Note: For security, use environment variable or 1Password in production."));
|
|
208
|
+
defaultRuntime.exit(1);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
const valueError = validateSecretValue(value, type);
|
|
212
|
+
if (valueError) {
|
|
213
|
+
defaultRuntime.log(danger(valueError));
|
|
214
|
+
defaultRuntime.exit(1);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
const store = loadSecretStore();
|
|
218
|
+
const now = Date.now();
|
|
219
|
+
const expiresAt = opts.expiresIn ? parseDuration(opts.expiresIn, now) : undefined;
|
|
220
|
+
const entry = {
|
|
221
|
+
name,
|
|
222
|
+
hash: hashSecret(value),
|
|
223
|
+
createdAt: store.secrets[name]?.createdAt || now,
|
|
224
|
+
updatedAt: now,
|
|
225
|
+
expiresAt,
|
|
226
|
+
provider: opts.provider,
|
|
227
|
+
type,
|
|
228
|
+
};
|
|
229
|
+
store.secrets[name] = entry;
|
|
230
|
+
saveSecretStore(store);
|
|
231
|
+
defaultRuntime.log("");
|
|
232
|
+
defaultRuntime.log(success(`Secret "${name}" saved successfully.`));
|
|
233
|
+
defaultRuntime.log(info("Store the value in your environment:"));
|
|
234
|
+
defaultRuntime.log(` export ${name.toUpperCase()}="<value>"`);
|
|
235
|
+
if (expiresAt) {
|
|
236
|
+
defaultRuntime.log(info(`Expires: ${new Date(expiresAt).toLocaleDateString()}`));
|
|
237
|
+
}
|
|
238
|
+
defaultRuntime.log("");
|
|
239
|
+
});
|
|
240
|
+
// secret rotate
|
|
241
|
+
secret
|
|
242
|
+
.command("rotate <name>")
|
|
243
|
+
.description("Rotate a secret (mark for rotation)")
|
|
244
|
+
.option("--value <value>", "New secret value")
|
|
245
|
+
.action(async (name, opts) => {
|
|
246
|
+
const validationError = validateSecretName(name);
|
|
247
|
+
if (validationError) {
|
|
248
|
+
defaultRuntime.log(danger(validationError));
|
|
249
|
+
defaultRuntime.exit(1);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
const store = loadSecretStore();
|
|
253
|
+
const entry = store.secrets[name];
|
|
254
|
+
if (!entry) {
|
|
255
|
+
defaultRuntime.log(danger(`Secret "${name}" not found.`));
|
|
256
|
+
defaultRuntime.exit(1);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
if (!opts.value) {
|
|
260
|
+
defaultRuntime.log("");
|
|
261
|
+
defaultRuntime.log(theme.heading(`Rotating secret: ${name}`));
|
|
262
|
+
defaultRuntime.log(theme.muted("─────────────────────────────────────────"));
|
|
263
|
+
defaultRuntime.log(info("To rotate, provide new value:"));
|
|
264
|
+
defaultRuntime.log(` poolbot secret rotate ${name} --value "<new-value>"`);
|
|
265
|
+
defaultRuntime.log("");
|
|
266
|
+
defaultRuntime.log(danger("⚠️ Make sure to update all services using this secret."));
|
|
267
|
+
defaultRuntime.exit(0);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
const valueError = validateSecretValue(opts.value, entry.type);
|
|
271
|
+
if (valueError) {
|
|
272
|
+
defaultRuntime.log(danger(valueError));
|
|
273
|
+
defaultRuntime.exit(1);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
entry.hash = hashSecret(opts.value);
|
|
277
|
+
entry.updatedAt = Date.now();
|
|
278
|
+
store.secrets[name] = entry;
|
|
279
|
+
saveSecretStore(store);
|
|
280
|
+
defaultRuntime.log("");
|
|
281
|
+
defaultRuntime.log(success(`Secret "${name}" rotated successfully.`));
|
|
282
|
+
defaultRuntime.log(danger("⚠️ Update all services using this secret immediately."));
|
|
283
|
+
defaultRuntime.log("");
|
|
284
|
+
});
|
|
285
|
+
// secret validate
|
|
286
|
+
secret
|
|
287
|
+
.command("validate <name>")
|
|
288
|
+
.description("Validate a secret format without revealing it")
|
|
289
|
+
.action(async (name) => {
|
|
290
|
+
const validationError = validateSecretName(name);
|
|
291
|
+
if (validationError) {
|
|
292
|
+
defaultRuntime.log(danger(validationError));
|
|
293
|
+
defaultRuntime.exit(1);
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
const store = loadSecretStore();
|
|
297
|
+
const entry = store.secrets[name];
|
|
298
|
+
if (!entry) {
|
|
299
|
+
defaultRuntime.log(danger(`Secret "${name}" not found.`));
|
|
300
|
+
defaultRuntime.exit(1);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
defaultRuntime.log("");
|
|
304
|
+
defaultRuntime.log(theme.heading(`Validating secret: ${name}`));
|
|
305
|
+
defaultRuntime.log(theme.muted("─────────────────────────────────────────"));
|
|
306
|
+
const checks = [
|
|
307
|
+
{ name: "Name format", pass: !validateSecretName(entry.name) },
|
|
308
|
+
{ name: "Hash present", pass: entry.hash.length === 64 },
|
|
309
|
+
{ name: "Created timestamp", pass: entry.createdAt > 0 },
|
|
310
|
+
{ name: "Updated timestamp", pass: entry.updatedAt > 0 },
|
|
311
|
+
];
|
|
312
|
+
if (entry.expiresAt) {
|
|
313
|
+
checks.push({ name: "Not expired", pass: entry.expiresAt > Date.now() });
|
|
314
|
+
}
|
|
315
|
+
let allPass = true;
|
|
316
|
+
for (const check of checks) {
|
|
317
|
+
const icon = check.pass ? theme.success("✓") : theme.error("✗");
|
|
318
|
+
defaultRuntime.log(` ${icon} ${check.name}`);
|
|
319
|
+
if (!check.pass)
|
|
320
|
+
allPass = false;
|
|
321
|
+
}
|
|
322
|
+
defaultRuntime.log("");
|
|
323
|
+
if (allPass) {
|
|
324
|
+
defaultRuntime.log(success("All validation checks passed."));
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
defaultRuntime.log(danger("Some validation checks failed."));
|
|
328
|
+
defaultRuntime.exit(1);
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
// secret delete
|
|
332
|
+
secret
|
|
333
|
+
.command("delete <name>")
|
|
334
|
+
.description("Delete a secret permanently")
|
|
335
|
+
.option("--yes", "Skip confirmation", false)
|
|
336
|
+
.action(async (name, opts) => {
|
|
337
|
+
const validationError = validateSecretName(name);
|
|
338
|
+
if (validationError) {
|
|
339
|
+
defaultRuntime.log(danger(validationError));
|
|
340
|
+
defaultRuntime.exit(1);
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
const store = loadSecretStore();
|
|
344
|
+
if (!store.secrets[name]) {
|
|
345
|
+
defaultRuntime.log(danger(`Secret "${name}" not found.`));
|
|
346
|
+
defaultRuntime.exit(1);
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
if (!opts.yes) {
|
|
350
|
+
defaultRuntime.log("");
|
|
351
|
+
defaultRuntime.log(danger(`⚠️ This will permanently delete secret "${name}"`));
|
|
352
|
+
defaultRuntime.log("This action cannot be undone.");
|
|
353
|
+
defaultRuntime.log("");
|
|
354
|
+
defaultRuntime.log(info("Use --yes to skip this confirmation."));
|
|
355
|
+
defaultRuntime.exit(1);
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
delete store.secrets[name];
|
|
359
|
+
saveSecretStore(store);
|
|
360
|
+
defaultRuntime.log("");
|
|
361
|
+
defaultRuntime.log(success(`Secret "${name}" deleted successfully.`));
|
|
362
|
+
defaultRuntime.log("");
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
function parseDuration(duration, fromMs) {
|
|
366
|
+
const match = duration.match(/^(\d+)([smhdw])$/);
|
|
367
|
+
if (!match)
|
|
368
|
+
return undefined;
|
|
369
|
+
const value = parseInt(match[1], 10);
|
|
370
|
+
const unit = match[2];
|
|
371
|
+
const seconds = unit === "s" ? value :
|
|
372
|
+
unit === "m" ? value * 60 :
|
|
373
|
+
unit === "h" ? value * 3600 :
|
|
374
|
+
unit === "d" ? value * 86400 :
|
|
375
|
+
unit === "w" ? value * 604800 :
|
|
376
|
+
0;
|
|
377
|
+
return fromMs + (seconds * 1000);
|
|
378
|
+
}
|