@poolzin/pool-bot 2026.4.43 → 2026.4.45

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.
Files changed (50) hide show
  1. package/dist/agents/model-catalog-gemma4.d.ts +6 -0
  2. package/dist/agents/model-catalog-gemma4.d.ts.map +1 -0
  3. package/dist/agents/model-catalog-gemma4.js +17 -0
  4. package/dist/agents/model-catalog.d.ts.map +1 -1
  5. package/dist/agents/model-catalog.js +7 -0
  6. package/dist/agents/tools/music-edit-tool.d.ts +9 -0
  7. package/dist/agents/tools/music-edit-tool.d.ts.map +1 -0
  8. package/dist/agents/tools/music-edit-tool.js +46 -0
  9. package/dist/agents/tools/video-edit-tool.d.ts +9 -0
  10. package/dist/agents/tools/video-edit-tool.d.ts.map +1 -0
  11. package/dist/agents/tools/video-edit-tool.js +46 -0
  12. package/dist/build-info.json +3 -3
  13. package/dist/cli/completion.d.ts +15 -0
  14. package/dist/cli/completion.d.ts.map +1 -0
  15. package/dist/cli/completion.js +183 -0
  16. package/dist/cli/onboard-cli.d.ts +20 -0
  17. package/dist/cli/onboard-cli.d.ts.map +1 -0
  18. package/dist/cli/onboard-cli.js +443 -0
  19. package/dist/cli/plugins-config-tui.d.ts.map +1 -1
  20. package/dist/cli/plugins-config-tui.js +1 -1
  21. package/dist/cli/program/register.subclis.d.ts.map +1 -1
  22. package/dist/cli/program/register.subclis.js +18 -0
  23. package/dist/cli/sessions-checkpoints-cli.d.ts +6 -0
  24. package/dist/cli/sessions-checkpoints-cli.d.ts.map +1 -0
  25. package/dist/cli/sessions-checkpoints-cli.js +117 -0
  26. package/dist/cli/webhooks-cli.d.ts +13 -0
  27. package/dist/cli/webhooks-cli.d.ts.map +1 -1
  28. package/dist/cli/webhooks-cli.js +323 -130
  29. package/dist/errors/user-friendly-errors.d.ts +23 -0
  30. package/dist/errors/user-friendly-errors.d.ts.map +1 -0
  31. package/dist/errors/user-friendly-errors.js +182 -0
  32. package/dist/infra/net/fetch-guard.d.ts.map +1 -1
  33. package/dist/memory/wiki/store.d.ts +53 -0
  34. package/dist/memory/wiki/store.d.ts.map +1 -0
  35. package/dist/memory/wiki/store.js +222 -0
  36. package/dist/memory/wiki/types.d.ts +57 -0
  37. package/dist/memory/wiki/types.d.ts.map +1 -0
  38. package/dist/memory/wiki/types.js +6 -0
  39. package/dist/plugins/webhook-ingress.d.ts +104 -0
  40. package/dist/plugins/webhook-ingress.d.ts.map +1 -0
  41. package/dist/plugins/webhook-ingress.js +287 -0
  42. package/dist/providers/ollama-vision.d.ts +30 -0
  43. package/dist/providers/ollama-vision.d.ts.map +1 -0
  44. package/dist/providers/ollama-vision.js +62 -0
  45. package/dist/sessions/checkpoints.d.ts +76 -0
  46. package/dist/sessions/checkpoints.d.ts.map +1 -0
  47. package/dist/sessions/checkpoints.js +162 -0
  48. package/dist/slack/channel.d.ts.map +1 -1
  49. package/dist/slack/channel.js +13 -2
  50. package/package.json +1 -1
@@ -1,140 +1,333 @@
1
- import { danger } from "../globals.js";
2
- import { DEFAULT_GMAIL_LABEL, DEFAULT_GMAIL_MAX_BYTES, DEFAULT_GMAIL_RENEW_MINUTES, DEFAULT_GMAIL_SERVE_BIND, DEFAULT_GMAIL_SERVE_PATH, DEFAULT_GMAIL_SERVE_PORT, DEFAULT_GMAIL_SUBSCRIPTION, DEFAULT_GMAIL_TOPIC, } from "../hooks/gmail.js";
3
- import { runGmailService, runGmailSetup, } from "../hooks/gmail-ops.js";
1
+ /**
2
+ * Webhook CLI - Manage webhook ingress routes
3
+ *
4
+ * Commands:
5
+ * poolbot webhooks list # List all webhook routes
6
+ * poolbot webhooks create <path> # Create webhook route
7
+ * poolbot webhooks show <id> # Show webhook details
8
+ * poolbot webhooks delete <id> # Delete webhook route
9
+ * poolbot webhooks enable <id> # Enable webhook route
10
+ * poolbot webhooks disable <id> # Disable webhook route
11
+ * poolbot webhooks stats # Show webhook statistics
12
+ * poolbot webhooks serve # Start webhook server
13
+ */
14
+ import crypto from "node:crypto";
15
+ import { loadConfig } from "../config/config.js";
16
+ import { resolveAgentWorkspaceDir } from "../agents/agent-scope.js";
4
17
  import { defaultRuntime } from "../runtime.js";
5
- import { formatDocsLink } from "../terminal/links.js";
6
18
  import { theme } from "../terminal/theme.js";
19
+ import { danger, info, success } from "../globals.js";
20
+ import { formatDocsLink } from "../terminal/links.js";
21
+ import { createWebhookRoute, getWebhookRoute, listWebhookRoutes, deleteWebhookRoute, updateWebhookRoute, getWebhookStats, startWebhookServer, } from "../plugins/webhook-ingress.js";
22
+ import { runCommandWithRuntime } from "./cli-utils.js";
23
+ import { callGatewayCli } from "../gateway/call.js";
24
+ function runWebhookCommand(action) {
25
+ return runCommandWithRuntime(defaultRuntime, action);
26
+ }
7
27
  export function registerWebhooksCli(program) {
8
28
  const webhooks = program
9
29
  .command("webhooks")
10
- .description("Webhook helpers and integrations")
11
- .addHelpText("after", () => `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/webhooks", "docs.molt.bot/cli/webhooks")}\n`);
12
- const gmail = webhooks.command("gmail").description("Gmail Pub/Sub hooks (via gogcli)");
13
- gmail
14
- .command("setup")
15
- .description("Configure Gmail watch + Pub/Sub + Poolbot hooks")
16
- .requiredOption("--account <email>", "Gmail account to watch")
17
- .option("--project <id>", "GCP project id (OAuth client owner)")
18
- .option("--topic <name>", "Pub/Sub topic name", DEFAULT_GMAIL_TOPIC)
19
- .option("--subscription <name>", "Pub/Sub subscription name", DEFAULT_GMAIL_SUBSCRIPTION)
20
- .option("--label <label>", "Gmail label to watch", DEFAULT_GMAIL_LABEL)
21
- .option("--hook-url <url>", "Poolbot hook URL")
22
- .option("--hook-token <token>", "Poolbot hook token")
23
- .option("--push-token <token>", "Push token for gog watch serve")
24
- .option("--bind <host>", "gog watch serve bind host", DEFAULT_GMAIL_SERVE_BIND)
25
- .option("--port <port>", "gog watch serve port", String(DEFAULT_GMAIL_SERVE_PORT))
26
- .option("--path <path>", "gog watch serve path", DEFAULT_GMAIL_SERVE_PATH)
27
- .option("--include-body", "Include email body snippets", true)
28
- .option("--max-bytes <n>", "Max bytes for body snippets", String(DEFAULT_GMAIL_MAX_BYTES))
29
- .option("--renew-minutes <n>", "Renew watch every N minutes", String(DEFAULT_GMAIL_RENEW_MINUTES))
30
- .option("--tailscale <mode>", "Expose push endpoint via tailscale (funnel|serve|off)", "funnel")
31
- .option("--tailscale-path <path>", "Path for tailscale serve/funnel")
32
- .option("--tailscale-target <target>", "Tailscale serve/funnel target (port, host:port, or URL)")
33
- .option("--push-endpoint <url>", "Explicit Pub/Sub push endpoint")
34
- .option("--json", "Output JSON summary", false)
30
+ .description("Manage webhook ingress routes (create/list/delete/serve)")
31
+ .addHelpText("after", () => `\n${theme.muted("Docs:")} ${formatDocsLink("/plugins/webhooks", "docs.molt.bot/plugins/webhooks")}\n` +
32
+ `${theme.muted("Tip:")} Use 'poolbot webhooks serve' to start the webhook server\n`);
33
+ // webhooks list
34
+ webhooks
35
+ .command("list")
36
+ .description("List webhook routes")
37
+ .option("--json", "Output JSON", false)
35
38
  .action(async (opts) => {
36
- try {
37
- const parsed = parseGmailSetupOptions(opts);
38
- await runGmailSetup(parsed);
39
- }
40
- catch (err) {
41
- defaultRuntime.error(danger(String(err)));
42
- defaultRuntime.exit(1);
43
- }
39
+ await runWebhookCommand(async () => {
40
+ const config = loadConfig();
41
+ const workspaceDir = resolveAgentWorkspaceDir(config, "main");
42
+ const routes = listWebhookRoutes(workspaceDir);
43
+ if (opts.json) {
44
+ defaultRuntime.log(JSON.stringify(routes, null, 2));
45
+ return;
46
+ }
47
+ defaultRuntime.log("");
48
+ defaultRuntime.log(theme.heading("Webhook Routes"));
49
+ defaultRuntime.log(theme.muted("─────────────────────────────────────────"));
50
+ if (routes.length === 0) {
51
+ defaultRuntime.log(info("No webhook routes configured."));
52
+ defaultRuntime.log("Create one with: poolbot webhooks create /my-webhook");
53
+ }
54
+ else {
55
+ for (const route of routes) {
56
+ const status = route.enabled ? theme.success("●") : theme.muted("○");
57
+ const methods = route.methods.join("/");
58
+ defaultRuntime.log(` ${status} ${route.path} [${methods}] ${route.enabled ? "" : theme.muted("(disabled)")}`);
59
+ defaultRuntime.log(theme.muted(` ID: ${route.id} | Triggers: ${route.triggerCount}`));
60
+ }
61
+ }
62
+ defaultRuntime.log("");
63
+ const stats = getWebhookStats(workspaceDir);
64
+ defaultRuntime.log(theme.muted(`Total: ${stats.total} routes | Enabled: ${stats.enabled} | Total triggers: ${stats.totalTriggers}`));
65
+ defaultRuntime.exit(0);
66
+ });
67
+ });
68
+ // webhooks create
69
+ webhooks
70
+ .command("create <path>")
71
+ .description("Create webhook route")
72
+ .requiredOption("--agent <id>", "Agent ID to run")
73
+ .requiredOption("--message <text>", "Message/prompt to send")
74
+ .option("--session <key>", "Session key (optional)")
75
+ .option("--skills <list>", "Comma-separated skills to enable")
76
+ .option("--secret <secret>", "Shared secret (auto-generated if omitted)")
77
+ .option("--methods <methods>", "HTTP methods (default: POST)")
78
+ .option("--rate-limit <n>", "Rate limit per minute", Number.parseInt)
79
+ .action(async (path, opts) => {
80
+ await runWebhookCommand(async () => {
81
+ const config = loadConfig();
82
+ const workspaceDir = resolveAgentWorkspaceDir(config, "main");
83
+ const secret = opts.secret ?? crypto.randomUUID();
84
+ const methods = opts.methods ? opts.methods.split(",").map((m) => m.trim()) : ["POST"];
85
+ const skills = opts.skills ? opts.skills.split(",").map((s) => s.trim()) : undefined;
86
+ try {
87
+ const routeId = createWebhookRoute(workspaceDir, {
88
+ path,
89
+ secret,
90
+ taskFlow: {
91
+ agentId: opts.agent ?? "main",
92
+ message: opts.message ?? "",
93
+ sessionKey: opts.session,
94
+ skills,
95
+ },
96
+ methods,
97
+ rateLimitPerMin: opts.rateLimit,
98
+ });
99
+ defaultRuntime.log("");
100
+ defaultRuntime.log(success(`Created webhook route: ${path}`));
101
+ defaultRuntime.log(` ID: ${routeId}`);
102
+ defaultRuntime.log(` Secret: ${theme.error(secret)} (keep this secret!)`);
103
+ defaultRuntime.log(` Methods: ${methods.join("/")}`);
104
+ defaultRuntime.log("");
105
+ defaultRuntime.log(info("Usage example:"));
106
+ defaultRuntime.log(` curl -X POST http://localhost:8888${path} \\`);
107
+ defaultRuntime.log(` -H "Content-Type: application/json" \\`);
108
+ defaultRuntime.log(` -H "X-Webhook-Signature: <hmac-sha256>" \\`);
109
+ defaultRuntime.log(` -d '{"key": "value"}'`);
110
+ defaultRuntime.log("");
111
+ defaultRuntime.log(info("Start the webhook server with: poolbot webhooks serve"));
112
+ defaultRuntime.exit(0);
113
+ }
114
+ catch (error) {
115
+ defaultRuntime.log(danger(`Failed to create webhook: ${error instanceof Error ? error.message : String(error)}`));
116
+ defaultRuntime.exit(1);
117
+ }
118
+ });
119
+ });
120
+ // webhooks show
121
+ webhooks
122
+ .command("show <id>")
123
+ .description("Show webhook details")
124
+ .option("--json", "Output JSON", false)
125
+ .action(async (id, opts) => {
126
+ await runWebhookCommand(async () => {
127
+ const config = loadConfig();
128
+ const workspaceDir = resolveAgentWorkspaceDir(config, "main");
129
+ const route = getWebhookRoute(workspaceDir, id);
130
+ if (!route) {
131
+ defaultRuntime.log(danger(`Webhook "${id}" not found.`));
132
+ defaultRuntime.exit(1);
133
+ return;
134
+ }
135
+ if (opts.json) {
136
+ defaultRuntime.log(JSON.stringify(route, null, 2));
137
+ return;
138
+ }
139
+ defaultRuntime.log("");
140
+ defaultRuntime.log(theme.heading(`Webhook: ${route.path}`));
141
+ defaultRuntime.log(theme.muted("─────────────────────────────────────────"));
142
+ defaultRuntime.log(` ID: ${route.id}`);
143
+ defaultRuntime.log(` Path: ${route.path}`);
144
+ defaultRuntime.log(` Status: ${route.enabled ? theme.success("enabled") : theme.warn("disabled")}`);
145
+ defaultRuntime.log(` Methods: ${route.methods.join("/")}`);
146
+ defaultRuntime.log(` Rate limit: ${route.rateLimitPerMin ?? "none"} /min`);
147
+ defaultRuntime.log(` Created: ${new Date(route.createdAt).toLocaleString()}`);
148
+ if (route.lastTriggeredAt) {
149
+ defaultRuntime.log(` Last triggered: ${new Date(route.lastTriggeredAt).toLocaleString()}`);
150
+ }
151
+ defaultRuntime.log(` Trigger count: ${route.triggerCount}`);
152
+ defaultRuntime.log("");
153
+ defaultRuntime.log(theme.heading("Task Flow"));
154
+ defaultRuntime.log(theme.muted("─────────────────────────────────────────"));
155
+ defaultRuntime.log(` Agent: ${route.taskFlow.agentId}`);
156
+ defaultRuntime.log(` Message: ${route.taskFlow.message.substring(0, 100)}...`);
157
+ if (route.taskFlow.sessionKey) {
158
+ defaultRuntime.log(` Session: ${route.taskFlow.sessionKey}`);
159
+ }
160
+ if (route.taskFlow.skills?.length) {
161
+ defaultRuntime.log(` Skills: ${route.taskFlow.skills.join(", ")}`);
162
+ }
163
+ defaultRuntime.exit(0);
164
+ });
165
+ });
166
+ // webhooks delete
167
+ webhooks
168
+ .command("delete <id>")
169
+ .description("Delete webhook route")
170
+ .option("--yes", "Skip confirmation", false)
171
+ .action(async (id, opts) => {
172
+ await runWebhookCommand(async () => {
173
+ const config = loadConfig();
174
+ const workspaceDir = resolveAgentWorkspaceDir(config, "main");
175
+ const route = getWebhookRoute(workspaceDir, id);
176
+ if (!route) {
177
+ defaultRuntime.log(danger(`Webhook "${id}" not found.`));
178
+ defaultRuntime.exit(1);
179
+ return;
180
+ }
181
+ if (!opts.yes) {
182
+ defaultRuntime.log("");
183
+ defaultRuntime.log(danger(`⚠️ This will permanently delete webhook "${route.path}"`));
184
+ defaultRuntime.log("This action cannot be undone.");
185
+ defaultRuntime.log("");
186
+ defaultRuntime.log(info("Use --yes to skip this confirmation."));
187
+ defaultRuntime.exit(1);
188
+ return;
189
+ }
190
+ const deleted = deleteWebhookRoute(workspaceDir, id);
191
+ if (deleted) {
192
+ defaultRuntime.log("");
193
+ defaultRuntime.log(success(`Webhook "${route.path}" deleted.`));
194
+ }
195
+ else {
196
+ defaultRuntime.log(danger(`Failed to delete webhook "${id}".`));
197
+ defaultRuntime.exit(1);
198
+ }
199
+ });
200
+ });
201
+ // webhooks enable/disable
202
+ webhooks
203
+ .command("enable <id>")
204
+ .description("Enable webhook route")
205
+ .action(async (id) => {
206
+ await runWebhookCommand(async () => {
207
+ const config = loadConfig();
208
+ const workspaceDir = resolveAgentWorkspaceDir(config, "main");
209
+ const route = updateWebhookRoute(workspaceDir, id, { enabled: true });
210
+ if (!route) {
211
+ defaultRuntime.log(danger(`Webhook "${id}" not found.`));
212
+ defaultRuntime.exit(1);
213
+ return;
214
+ }
215
+ defaultRuntime.log("");
216
+ defaultRuntime.log(success(`Webhook "${route.path}" enabled.`));
217
+ defaultRuntime.exit(0);
218
+ });
219
+ });
220
+ webhooks
221
+ .command("disable <id>")
222
+ .description("Disable webhook route")
223
+ .action(async (id) => {
224
+ await runWebhookCommand(async () => {
225
+ const config = loadConfig();
226
+ const workspaceDir = resolveAgentWorkspaceDir(config, "main");
227
+ const route = updateWebhookRoute(workspaceDir, id, { enabled: false });
228
+ if (!route) {
229
+ defaultRuntime.log(danger(`Webhook "${id}" not found.`));
230
+ defaultRuntime.exit(1);
231
+ return;
232
+ }
233
+ defaultRuntime.log("");
234
+ defaultRuntime.log(success(`Webhook "${route.path}" disabled.`));
235
+ defaultRuntime.exit(0);
236
+ });
44
237
  });
45
- gmail
46
- .command("run")
47
- .description("Run gog watch serve + auto-renew loop")
48
- .option("--account <email>", "Gmail account to watch")
49
- .option("--topic <topic>", "Pub/Sub topic path (projects/.../topics/..)")
50
- .option("--subscription <name>", "Pub/Sub subscription name")
51
- .option("--label <label>", "Gmail label to watch")
52
- .option("--hook-url <url>", "Poolbot hook URL")
53
- .option("--hook-token <token>", "Poolbot hook token")
54
- .option("--push-token <token>", "Push token for gog watch serve")
55
- .option("--bind <host>", "gog watch serve bind host")
56
- .option("--port <port>", "gog watch serve port")
57
- .option("--path <path>", "gog watch serve path")
58
- .option("--include-body", "Include email body snippets")
59
- .option("--max-bytes <n>", "Max bytes for body snippets")
60
- .option("--renew-minutes <n>", "Renew watch every N minutes")
61
- .option("--tailscale <mode>", "Expose push endpoint via tailscale (funnel|serve|off)")
62
- .option("--tailscale-path <path>", "Path for tailscale serve/funnel")
63
- .option("--tailscale-target <target>", "Tailscale serve/funnel target (port, host:port, or URL)")
238
+ // webhooks stats
239
+ webhooks
240
+ .command("stats")
241
+ .description("Show webhook statistics")
242
+ .option("--json", "Output JSON", false)
64
243
  .action(async (opts) => {
65
- try {
66
- const parsed = parseGmailRunOptions(opts);
67
- await runGmailService(parsed);
68
- }
69
- catch (err) {
70
- defaultRuntime.error(danger(String(err)));
71
- defaultRuntime.exit(1);
72
- }
244
+ await runWebhookCommand(async () => {
245
+ const config = loadConfig();
246
+ const workspaceDir = resolveAgentWorkspaceDir(config, "main");
247
+ const stats = getWebhookStats(workspaceDir);
248
+ if (opts.json) {
249
+ defaultRuntime.log(JSON.stringify(stats, null, 2));
250
+ return;
251
+ }
252
+ defaultRuntime.log("");
253
+ defaultRuntime.log(theme.heading("Webhook Statistics"));
254
+ defaultRuntime.log(theme.muted("─────────────────────────────────────────"));
255
+ defaultRuntime.log(` Total routes: ${stats.total}`);
256
+ defaultRuntime.log(` Enabled routes: ${stats.enabled}`);
257
+ defaultRuntime.log(` Total triggers: ${stats.totalTriggers}`);
258
+ defaultRuntime.log("");
259
+ if (stats.routes.length > 0) {
260
+ defaultRuntime.log(theme.heading("Top Routes"));
261
+ defaultRuntime.log(theme.muted("─────────────────────────────────────────"));
262
+ const topRoutes = stats.routes.sort((a, b) => b.triggers - a.triggers).slice(0, 5);
263
+ for (const route of topRoutes) {
264
+ defaultRuntime.log(` ${route.path}: ${route.triggers} triggers`);
265
+ }
266
+ }
267
+ defaultRuntime.exit(0);
268
+ });
269
+ });
270
+ // webhooks serve
271
+ webhooks
272
+ .command("serve")
273
+ .description("Start webhook server")
274
+ .option("--port <n>", "Port to listen on", Number.parseInt)
275
+ .action(async (opts) => {
276
+ await runWebhookCommand(async () => {
277
+ const config = loadConfig();
278
+ const workspaceDir = resolveAgentWorkspaceDir(config, "main");
279
+ const port = opts.port ?? 8888;
280
+ defaultRuntime.log("");
281
+ defaultRuntime.log(theme.heading("Starting Webhook Server"));
282
+ defaultRuntime.log(theme.muted("─────────────────────────────────────────"));
283
+ const routes = listWebhookRoutes(workspaceDir);
284
+ if (routes.length === 0) {
285
+ defaultRuntime.log(info("No webhook routes configured."));
286
+ defaultRuntime.log("Create one with: poolbot webhooks create /my-webhook");
287
+ defaultRuntime.exit(0);
288
+ return;
289
+ }
290
+ defaultRuntime.log(` Port: ${port}`);
291
+ defaultRuntime.log(` Routes: ${routes.length}`);
292
+ defaultRuntime.log("");
293
+ defaultRuntime.log("Registered routes:");
294
+ for (const route of routes) {
295
+ const status = route.enabled ? theme.success("✓") : theme.muted("○");
296
+ defaultRuntime.log(` ${status} ${route.path} [${route.methods.join("/")}]`);
297
+ }
298
+ defaultRuntime.log("");
299
+ // Start server
300
+ const { server } = await startWebhookServer(workspaceDir, port, async (route, payload) => {
301
+ // Execute agent with task flow configuration
302
+ defaultRuntime.log(`Triggered: ${route.path} -> ${route.taskFlow.agentId}`);
303
+ defaultRuntime.log(` Message: ${route.taskFlow.message}`);
304
+ try {
305
+ // Call gateway to run agent
306
+ await callGatewayCli({
307
+ method: "agent.run",
308
+ params: {
309
+ agentId: route.taskFlow.agentId,
310
+ message: route.taskFlow.message,
311
+ sessionKey: route.taskFlow.sessionKey,
312
+ skills: route.taskFlow.skills,
313
+ context: { webhookPayload: payload, webhookRoute: route.path },
314
+ },
315
+ });
316
+ defaultRuntime.log(success(`Agent executed successfully`));
317
+ }
318
+ catch (error) {
319
+ defaultRuntime.log(danger(`Agent execution failed: ${error instanceof Error ? error.message : String(error)}`));
320
+ throw error;
321
+ }
322
+ });
323
+ defaultRuntime.log(info("Press Ctrl+C to stop"));
324
+ // Handle shutdown
325
+ process.on("SIGINT", () => {
326
+ defaultRuntime.log("");
327
+ defaultRuntime.log(info("Shutting down webhook server..."));
328
+ server.close();
329
+ process.exit(0);
330
+ });
331
+ });
73
332
  });
74
- }
75
- function parseGmailSetupOptions(raw) {
76
- const accountRaw = raw.account;
77
- const account = typeof accountRaw === "string" ? accountRaw.trim() : "";
78
- if (!account)
79
- throw new Error("--account is required");
80
- return {
81
- account,
82
- project: stringOption(raw.project),
83
- topic: stringOption(raw.topic),
84
- subscription: stringOption(raw.subscription),
85
- label: stringOption(raw.label),
86
- hookUrl: stringOption(raw.hookUrl),
87
- hookToken: stringOption(raw.hookToken),
88
- pushToken: stringOption(raw.pushToken),
89
- bind: stringOption(raw.bind),
90
- port: numberOption(raw.port),
91
- path: stringOption(raw.path),
92
- includeBody: booleanOption(raw.includeBody),
93
- maxBytes: numberOption(raw.maxBytes),
94
- renewEveryMinutes: numberOption(raw.renewMinutes),
95
- tailscale: stringOption(raw.tailscale),
96
- tailscalePath: stringOption(raw.tailscalePath),
97
- tailscaleTarget: stringOption(raw.tailscaleTarget),
98
- pushEndpoint: stringOption(raw.pushEndpoint),
99
- json: Boolean(raw.json),
100
- };
101
- }
102
- function parseGmailRunOptions(raw) {
103
- return {
104
- account: stringOption(raw.account),
105
- topic: stringOption(raw.topic),
106
- subscription: stringOption(raw.subscription),
107
- label: stringOption(raw.label),
108
- hookUrl: stringOption(raw.hookUrl),
109
- hookToken: stringOption(raw.hookToken),
110
- pushToken: stringOption(raw.pushToken),
111
- bind: stringOption(raw.bind),
112
- port: numberOption(raw.port),
113
- path: stringOption(raw.path),
114
- includeBody: booleanOption(raw.includeBody),
115
- maxBytes: numberOption(raw.maxBytes),
116
- renewEveryMinutes: numberOption(raw.renewMinutes),
117
- tailscale: stringOption(raw.tailscale),
118
- tailscalePath: stringOption(raw.tailscalePath),
119
- tailscaleTarget: stringOption(raw.tailscaleTarget),
120
- };
121
- }
122
- function stringOption(value) {
123
- if (typeof value !== "string")
124
- return undefined;
125
- const trimmed = value.trim();
126
- return trimmed ? trimmed : undefined;
127
- }
128
- function numberOption(value) {
129
- if (value === undefined || value === null)
130
- return undefined;
131
- const n = typeof value === "number" ? value : Number(value);
132
- if (!Number.isFinite(n) || n <= 0)
133
- return undefined;
134
- return Math.floor(n);
135
- }
136
- function booleanOption(value) {
137
- if (value === undefined || value === null)
138
- return undefined;
139
- return Boolean(value);
140
333
  }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * User-Friendly Error Messages
3
+ *
4
+ * Transforms technical errors into clear, actionable messages.
5
+ */
6
+ export type ErrorCategory = "auth" | "network" | "config" | "model" | "rate_limit" | "permission" | "resource" | "validation" | "unknown";
7
+ export type UserFriendlyError = {
8
+ category: ErrorCategory;
9
+ message: string;
10
+ cause?: string;
11
+ solution: string[];
12
+ docsLink?: string;
13
+ retryable?: boolean;
14
+ };
15
+ /**
16
+ * Convert an error to a user-friendly error.
17
+ */
18
+ export declare function toUserFriendlyError(error: unknown): UserFriendlyError;
19
+ /**
20
+ * Format a user-friendly error for display.
21
+ */
22
+ export declare function formatUserFriendlyError(err: UserFriendlyError): string;
23
+ //# sourceMappingURL=user-friendly-errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"user-friendly-errors.d.ts","sourceRoot":"","sources":["../../src/errors/user-friendly-errors.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,MAAM,aAAa,GACrB,MAAM,GACN,SAAS,GACT,QAAQ,GACR,OAAO,GACP,YAAY,GACZ,YAAY,GACZ,UAAU,GACV,YAAY,GACZ,SAAS,CAAC;AAEd,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,EAAE,aAAa,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AA4IF;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,iBAAiB,CA2BrE;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,iBAAiB,GAAG,MAAM,CA0BtE"}
@@ -0,0 +1,182 @@
1
+ /**
2
+ * User-Friendly Error Messages
3
+ *
4
+ * Transforms technical errors into clear, actionable messages.
5
+ */
6
+ const ERROR_PATTERNS = [
7
+ // Auth errors
8
+ {
9
+ pattern: /unauthorized|401|authentication failed/i,
10
+ category: "auth",
11
+ message: "Authentication failed",
12
+ solution: [
13
+ "Check your API key or token is correct",
14
+ "Ensure the token hasn't expired",
15
+ "Run 'poolbot secret get <name>' to verify",
16
+ ],
17
+ docsLink: "/auth/troubleshooting",
18
+ retryable: false,
19
+ },
20
+ {
21
+ pattern: /forbidden|403|access denied/i,
22
+ category: "permission",
23
+ message: "Access denied",
24
+ solution: [
25
+ "Check you have the required permissions",
26
+ "Contact your administrator to grant access",
27
+ ],
28
+ docsLink: "/auth/permissions",
29
+ retryable: false,
30
+ },
31
+ // Network errors
32
+ {
33
+ pattern: /network|connection.*refused|ECONNREFUSED/i,
34
+ category: "network",
35
+ message: "Network connection failed",
36
+ solution: [
37
+ "Check your internet connection",
38
+ "Verify the service is running",
39
+ "Try again in a few moments",
40
+ ],
41
+ retryable: true,
42
+ },
43
+ {
44
+ pattern: /timeout|ETIMEDOUT/i,
45
+ category: "network",
46
+ message: "Request timed out",
47
+ solution: [
48
+ "The service is taking too long to respond",
49
+ "Check if the service is overloaded",
50
+ "Try again with a smaller request",
51
+ ],
52
+ retryable: true,
53
+ },
54
+ // Rate limit errors
55
+ {
56
+ pattern: /rate.*limit|429|too.*many.*request/i,
57
+ category: "rate_limit",
58
+ message: "Rate limit exceeded",
59
+ solution: [
60
+ "You've made too many requests",
61
+ "Wait before trying again",
62
+ "Consider upgrading your plan for higher limits",
63
+ ],
64
+ docsLink: "/limits",
65
+ retryable: true,
66
+ },
67
+ // Model errors
68
+ {
69
+ pattern: /model.*not.*found|invalid.*model/i,
70
+ category: "model",
71
+ message: "Model not found",
72
+ solution: [
73
+ "Check the model name is correct",
74
+ "Run 'poolbot models list' to see available models",
75
+ "Ensure the model provider is configured",
76
+ ],
77
+ docsLink: "/models",
78
+ retryable: false,
79
+ },
80
+ {
81
+ pattern: /context.*exceeded|context.*window|token.*limit/i,
82
+ category: "model",
83
+ message: "Context window exceeded",
84
+ solution: [
85
+ "Your message is too long for this model",
86
+ "Try splitting your request into smaller parts",
87
+ "Use a model with a larger context window",
88
+ ],
89
+ docsLink: "/models/context",
90
+ retryable: false,
91
+ },
92
+ // Config errors
93
+ {
94
+ pattern: /config.*not.*found|missing.*config/i,
95
+ category: "config",
96
+ message: "Configuration missing",
97
+ solution: [
98
+ "Run 'poolbot config show' to check your config",
99
+ "Set the required configuration values",
100
+ "Run 'poolbot onboard' for guided setup",
101
+ ],
102
+ docsLink: "/config",
103
+ retryable: false,
104
+ },
105
+ // Resource errors
106
+ {
107
+ pattern: /not.*found|404|resource.*not.*exist/i,
108
+ category: "resource",
109
+ message: "Resource not found",
110
+ solution: [
111
+ "Check the resource ID is correct",
112
+ "Ensure the resource exists",
113
+ "List available resources to verify",
114
+ ],
115
+ retryable: false,
116
+ },
117
+ // Validation errors
118
+ {
119
+ pattern: /invalid|validation.*failed|bad.*request|400/i,
120
+ category: "validation",
121
+ message: "Invalid input",
122
+ solution: [
123
+ "Check your input format",
124
+ "Review the required fields",
125
+ "See the documentation for examples",
126
+ ],
127
+ retryable: false,
128
+ },
129
+ ];
130
+ /**
131
+ * Convert an error to a user-friendly error.
132
+ */
133
+ export function toUserFriendlyError(error) {
134
+ const errorMessage = error instanceof Error ? error.message : String(error);
135
+ for (const pattern of ERROR_PATTERNS) {
136
+ if (pattern.pattern.test(errorMessage)) {
137
+ return {
138
+ category: pattern.category,
139
+ message: pattern.message,
140
+ cause: errorMessage,
141
+ solution: pattern.solution,
142
+ docsLink: pattern.docsLink,
143
+ retryable: pattern.retryable,
144
+ };
145
+ }
146
+ }
147
+ return {
148
+ category: "unknown",
149
+ message: "An unexpected error occurred",
150
+ cause: errorMessage,
151
+ solution: [
152
+ "Try again",
153
+ "Check the logs for more details",
154
+ "Contact support if the issue persists",
155
+ ],
156
+ retryable: true,
157
+ };
158
+ }
159
+ /**
160
+ * Format a user-friendly error for display.
161
+ */
162
+ export function formatUserFriendlyError(err) {
163
+ const lines = [];
164
+ lines.push(`❌ ${err.message}`);
165
+ if (err.cause && err.cause !== err.message) {
166
+ lines.push(` Cause: ${err.cause}`);
167
+ }
168
+ lines.push("");
169
+ lines.push("Solutions:");
170
+ for (const solution of err.solution) {
171
+ lines.push(` • ${solution}`);
172
+ }
173
+ if (err.docsLink) {
174
+ lines.push("");
175
+ lines.push(`📖 Documentation: ${err.docsLink}`);
176
+ }
177
+ if (err.retryable) {
178
+ lines.push("");
179
+ lines.push("💡 This error is temporary. You can retry.");
180
+ }
181
+ return lines.join("\n");
182
+ }