@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.
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2026.4.40",
3
- "commit": "8f045b56b0a4c9c8bd20295a87b076d26f54b9d4",
4
- "builtAt": "2026-04-08T01:37:44.093Z"
2
+ "version": "2026.4.41",
3
+ "commit": "258285674de27bd59e66926b547cb27a311ff6a4",
4
+ "builtAt": "2026-04-08T01:55:26.000Z"
5
5
  }
@@ -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;AAuSF,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"}
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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poolzin/pool-bot",
3
- "version": "2026.4.40",
3
+ "version": "2026.4.41",
4
4
  "description": "🎱 Pool Bot - AI assistant with PLCODE integrations",
5
5
  "keywords": [],
6
6
  "license": "MIT",