@poolzin/pool-bot 2026.3.9 → 2026.3.10

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 (125) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +147 -69
  3. package/dist/.buildstamp +1 -1
  4. package/dist/agents/error-classifier.js +26 -77
  5. package/dist/agents/skills/security.js +1 -7
  6. package/dist/build-info.json +3 -3
  7. package/dist/cli/cron-cli/register.cron-dashboard.js +339 -0
  8. package/dist/cli/cron-cli/register.js +2 -0
  9. package/dist/cli/errors.js +187 -0
  10. package/dist/cli/program/command-registry.js +13 -0
  11. package/dist/cli/program/register.maintenance.js +21 -0
  12. package/dist/cli/program/register.subclis.js +9 -0
  13. package/dist/cli/swarm-cli/register.js +8 -0
  14. package/dist/cli/swarm-cli/register.swarm-status.js +488 -0
  15. package/dist/cli/telemetry-cli/register.js +10 -0
  16. package/dist/cli/telemetry-cli/register.telemetry-alerts.js +176 -0
  17. package/dist/cli/telemetry-cli/register.telemetry-metrics.js +323 -0
  18. package/dist/cli/telemetry-cli/register.telemetry-status.js +179 -0
  19. package/dist/commands/doctor-checks.js +498 -0
  20. package/dist/context-engine/index.js +1 -1
  21. package/dist/context-engine/legacy.js +1 -3
  22. package/dist/context-engine/summarizing.js +5 -8
  23. package/dist/cron/service/timer.js +18 -0
  24. package/dist/gateway/protocol/index.js +5 -2
  25. package/dist/gateway/protocol/schema/error-codes.js +1 -0
  26. package/dist/gateway/protocol/schema/swarm.js +80 -0
  27. package/dist/gateway/protocol/schema.js +1 -0
  28. package/dist/gateway/server-close.js +4 -0
  29. package/dist/gateway/server-constants.js +1 -0
  30. package/dist/gateway/server-cron.js +29 -0
  31. package/dist/gateway/server-maintenance.js +35 -2
  32. package/dist/gateway/server-methods/swarm.js +58 -0
  33. package/dist/gateway/server-methods/telemetry.js +71 -0
  34. package/dist/gateway/server-methods-list.js +8 -0
  35. package/dist/gateway/server-methods.js +9 -2
  36. package/dist/gateway/server.impl.js +33 -16
  37. package/dist/infra/abort-pattern.js +4 -4
  38. package/dist/infra/retry.js +3 -1
  39. package/dist/skills/commands.js +7 -25
  40. package/dist/skills/index.js +14 -17
  41. package/dist/skills/parser.js +12 -27
  42. package/dist/skills/registry.js +3 -6
  43. package/dist/skills/security.js +2 -8
  44. package/dist/swarm/service.js +247 -0
  45. package/dist/telemetry/alert-engine.js +258 -0
  46. package/dist/telemetry/cron-instrumentation.js +49 -0
  47. package/dist/telemetry/gateway-instrumentation.js +80 -0
  48. package/dist/telemetry/instrumentation.js +66 -0
  49. package/dist/telemetry/service.js +345 -0
  50. package/dist/tui/components/assistant-message.js +6 -2
  51. package/dist/tui/components/hyperlink-markdown.js +32 -0
  52. package/dist/tui/components/searchable-select-list.js +12 -1
  53. package/dist/tui/components/user-message.js +6 -2
  54. package/dist/tui/index.js +22 -6
  55. package/dist/tui/theme/theme-detection.js +226 -0
  56. package/dist/tui/tui-command-handlers.js +20 -0
  57. package/dist/tui/tui-formatters.js +4 -3
  58. package/dist/tui/utils/ctrl-c-handler.js +67 -0
  59. package/dist/tui/utils/osc8-hyperlinks.js +208 -0
  60. package/dist/tui/utils/safe-stop.js +180 -0
  61. package/dist/tui/utils/session-key-utils.js +81 -0
  62. package/dist/tui/utils/text-sanitization.js +284 -0
  63. package/dist/utils/lru-cache.js +116 -0
  64. package/dist/utils/performance.js +199 -0
  65. package/dist/utils/retry.js +240 -0
  66. package/docs/MELHORIAS_IMPLEMENTADAS.md +228 -0
  67. package/docs/MELHORIAS_PROFISSIONAIS.md +282 -0
  68. package/docs/PLANO_ACAO_TUI.md +357 -0
  69. package/docs/PROGRESSO_TUI.md +66 -0
  70. package/docs/RELATORIO_FINAL.md +217 -0
  71. package/docs/diagnostico-shell-completion.md +265 -0
  72. package/docs/features/advanced-memory.md +585 -0
  73. package/docs/features/discord-components-v2.md +277 -0
  74. package/docs/features/swarm.md +100 -0
  75. package/docs/features/telemetry.md +284 -0
  76. package/docs/integrations/INTEGRATION_PLAN.md +665 -345
  77. package/docs/models/provider-infrastructure.md +400 -0
  78. package/docs/security/exec-approvals.md +294 -0
  79. package/extensions/bluebubbles/package.json +1 -1
  80. package/extensions/copilot-proxy/package.json +1 -1
  81. package/extensions/diagnostics-otel/package.json +1 -1
  82. package/extensions/discord/package.json +1 -1
  83. package/extensions/feishu/package.json +1 -1
  84. package/extensions/google-antigravity-auth/package.json +1 -1
  85. package/extensions/google-gemini-cli-auth/package.json +1 -1
  86. package/extensions/googlechat/package.json +1 -1
  87. package/extensions/hexstrike-bridge/README.md +119 -0
  88. package/extensions/hexstrike-bridge/index.test.ts +247 -0
  89. package/extensions/hexstrike-bridge/index.ts +487 -0
  90. package/extensions/hexstrike-bridge/package.json +17 -0
  91. package/extensions/imessage/package.json +1 -1
  92. package/extensions/irc/package.json +1 -1
  93. package/extensions/line/package.json +1 -1
  94. package/extensions/llm-task/package.json +1 -1
  95. package/extensions/lobster/package.json +1 -1
  96. package/extensions/matrix/CHANGELOG.md +5 -0
  97. package/extensions/matrix/package.json +1 -1
  98. package/extensions/mattermost/package.json +1 -1
  99. package/extensions/mcp-server/index.ts +14 -0
  100. package/extensions/mcp-server/package.json +11 -0
  101. package/extensions/mcp-server/src/service.ts +540 -0
  102. package/extensions/memory-core/package.json +1 -1
  103. package/extensions/memory-lancedb/package.json +1 -1
  104. package/extensions/minimax-portal-auth/package.json +1 -1
  105. package/extensions/msteams/CHANGELOG.md +5 -0
  106. package/extensions/msteams/package.json +1 -1
  107. package/extensions/nextcloud-talk/package.json +1 -1
  108. package/extensions/nostr/CHANGELOG.md +5 -0
  109. package/extensions/nostr/package.json +1 -1
  110. package/extensions/open-prose/package.json +1 -1
  111. package/extensions/openai-codex-auth/package.json +1 -1
  112. package/extensions/signal/package.json +1 -1
  113. package/extensions/slack/package.json +1 -1
  114. package/extensions/telegram/package.json +1 -1
  115. package/extensions/tlon/package.json +1 -1
  116. package/extensions/twitch/CHANGELOG.md +5 -0
  117. package/extensions/twitch/package.json +1 -1
  118. package/extensions/voice-call/CHANGELOG.md +5 -0
  119. package/extensions/voice-call/package.json +1 -1
  120. package/extensions/whatsapp/package.json +1 -1
  121. package/extensions/zalo/CHANGELOG.md +5 -0
  122. package/extensions/zalo/package.json +1 -1
  123. package/extensions/zalouser/CHANGELOG.md +5 -0
  124. package/extensions/zalouser/package.json +1 -1
  125. package/package.json +8 -1
@@ -0,0 +1,339 @@
1
+ import { danger } from "../../globals.js";
2
+ import { defaultRuntime } from "../../runtime.js";
3
+ import { colorize, isRich, theme } from "../../terminal/theme.js";
4
+ import { renderTable } from "../../terminal/table.js";
5
+ import { addGatewayClientOptions, callGatewayFromCli } from "../gateway-rpc.js";
6
+ import { formatDurationHuman } from "../../infra/format-time/format-duration.js";
7
+ import { resolveCronStaggerMs } from "../../cron/stagger.js";
8
+ // Sparkline characters for visual history
9
+ const SPARK_CHARS = ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"];
10
+ // Status indicators
11
+ const STATUS_ICONS = {
12
+ ok: "●",
13
+ error: "✖",
14
+ running: "▶",
15
+ skipped: "○",
16
+ idle: "◌",
17
+ disabled: "⊘",
18
+ };
19
+ // Format relative time with color coding
20
+ function formatRelativeTime(ms, nowMs) {
21
+ if (!ms)
22
+ return "-";
23
+ const delta = ms - nowMs;
24
+ const absMs = Math.abs(delta);
25
+ if (absMs < 60_000)
26
+ return delta >= 0 ? "<1m" : "<1m ago";
27
+ if (absMs < 3_600_000) {
28
+ const mins = Math.round(absMs / 60_000);
29
+ return delta >= 0 ? `${mins}m` : `${mins}m ago`;
30
+ }
31
+ if (absMs < 86_400_000) {
32
+ const hours = Math.round(absMs / 3_600_000);
33
+ return delta >= 0 ? `${hours}h` : `${hours}h ago`;
34
+ }
35
+ const days = Math.round(absMs / 86_400_000);
36
+ return delta >= 0 ? `${days}d` : `${days}d ago`;
37
+ }
38
+ // Format schedule compactly
39
+ function formatScheduleCompact(job) {
40
+ const { schedule } = job;
41
+ if (schedule.kind === "at") {
42
+ const date = new Date(schedule.at);
43
+ return `once ${date.toLocaleDateString()}`;
44
+ }
45
+ if (schedule.kind === "every") {
46
+ return `every ${formatDurationHuman(schedule.everyMs)}`;
47
+ }
48
+ // cron
49
+ const staggerMs = resolveCronStaggerMs(schedule);
50
+ const stagger = staggerMs > 0 ? `~${formatDurationHuman(staggerMs)}` : "exact";
51
+ return `${schedule.expr} ${stagger}`;
52
+ }
53
+ // Get status with icon
54
+ function getStatusDisplay(job, rich) {
55
+ if (!job.enabled) {
56
+ return rich ? `${colorize(rich, theme.muted, STATUS_ICONS.disabled)} disabled` : "disabled";
57
+ }
58
+ if (job.state.runningAtMs) {
59
+ return rich ? `${colorize(rich, theme.warn, STATUS_ICONS.running)} running` : "running";
60
+ }
61
+ const status = job.state.lastStatus ?? "idle";
62
+ const icon = STATUS_ICONS[status] ?? STATUS_ICONS.idle;
63
+ if (!rich)
64
+ return status;
65
+ switch (status) {
66
+ case "ok":
67
+ return `${colorize(rich, theme.success, icon)} ${status}`;
68
+ case "error":
69
+ return `${colorize(rich, theme.error, icon)} ${status}`;
70
+ case "skipped":
71
+ return `${colorize(rich, theme.muted, icon)} ${status}`;
72
+ default:
73
+ return `${colorize(rich, theme.muted, icon)} ${status}`;
74
+ }
75
+ }
76
+ // Generate sparkline from execution history (simulated from state)
77
+ function generateSparkline(job, rich) {
78
+ // In a real implementation, this would use actual history data
79
+ // For now, we create a visual indicator based on recent state
80
+ const { consecutiveErrors = 0, lastStatus } = job.state;
81
+ if (!rich) {
82
+ return consecutiveErrors > 0 ? `!${consecutiveErrors}` : "-";
83
+ }
84
+ if (consecutiveErrors > 0) {
85
+ const level = Math.min(consecutiveErrors, SPARK_CHARS.length - 1);
86
+ return colorize(rich, theme.error, SPARK_CHARS[level].repeat(3));
87
+ }
88
+ if (lastStatus === "ok") {
89
+ return colorize(rich, theme.success, "▁▃▆");
90
+ }
91
+ return colorize(rich, theme.muted, "···");
92
+ }
93
+ // Format job target
94
+ function formatTarget(job, rich) {
95
+ const { sessionTarget, agentId } = job;
96
+ if (!rich)
97
+ return agentId ?? sessionTarget ?? "-";
98
+ const target = sessionTarget === "isolated"
99
+ ? colorize(rich, theme.accentBright, "iso")
100
+ : colorize(rich, theme.accent, "main");
101
+ if (agentId) {
102
+ return `${target}:${colorize(rich, theme.info, agentId.slice(0, 8))}`;
103
+ }
104
+ return target;
105
+ }
106
+ // Calculate health score (0-100)
107
+ function calculateHealthScore(job) {
108
+ if (!job.enabled)
109
+ return 100; // Disabled jobs are "healthy" by not running
110
+ if (job.state.runningAtMs)
111
+ return 100; // Currently running is fine
112
+ const { consecutiveErrors = 0, lastStatus, scheduleErrorCount = 0 } = job.state;
113
+ if (consecutiveErrors >= 5 || scheduleErrorCount >= 10)
114
+ return 0;
115
+ if (consecutiveErrors > 0)
116
+ return Math.max(0, 100 - consecutiveErrors * 20);
117
+ if (lastStatus === "error")
118
+ return 50;
119
+ if (lastStatus === "skipped")
120
+ return 75;
121
+ return 100;
122
+ }
123
+ // Get health indicator
124
+ function getHealthIndicator(score, rich) {
125
+ if (!rich)
126
+ return `${score}%`;
127
+ if (score >= 90)
128
+ return colorize(rich, theme.success, "●");
129
+ if (score >= 70)
130
+ return colorize(rich, theme.warn, "●");
131
+ return colorize(rich, theme.error, "●");
132
+ }
133
+ // Print dashboard header
134
+ function printDashboardHeader(runtime = defaultRuntime) {
135
+ const rich = isRich();
136
+ const now = new Date();
137
+ const timestamp = now.toLocaleString();
138
+ runtime.log("");
139
+ if (rich) {
140
+ runtime.log(colorize(rich, theme.heading, "╔═══════════════════════════════════════════════════════════╗"));
141
+ runtime.log(colorize(rich, theme.heading, `║ 🕐 CRON DASHBOARD ${timestamp.padStart(26)} ║`));
142
+ runtime.log(colorize(rich, theme.heading, "╚═══════════════════════════════════════════════════════════╝"));
143
+ }
144
+ else {
145
+ runtime.log("═══════════════════════════════════════════════════════════");
146
+ runtime.log(`CRON DASHBOARD - ${timestamp}`);
147
+ runtime.log("═══════════════════════════════════════════════════════════");
148
+ }
149
+ runtime.log("");
150
+ }
151
+ // Print summary stats
152
+ function printSummaryStats(jobs, runtime = defaultRuntime) {
153
+ const rich = isRich();
154
+ const now = Date.now();
155
+ const enabled = jobs.filter((j) => j.enabled).length;
156
+ const running = jobs.filter((j) => j.state.runningAtMs).length;
157
+ const errors = jobs.filter((j) => j.enabled && j.state.lastStatus === "error" && !j.state.runningAtMs).length;
158
+ const upcoming = jobs.filter((j) => j.enabled && j.state.nextRunAtMs && j.state.nextRunAtMs > now).length;
159
+ if (rich) {
160
+ const stats = [
161
+ `${colorize(rich, theme.info, "Total:")} ${jobs.length}`,
162
+ `${colorize(rich, theme.success, "Enabled:")} ${enabled}`,
163
+ `${colorize(rich, theme.warn, "Running:")} ${running}`,
164
+ `${colorize(rich, theme.error, "Errors:")} ${errors}`,
165
+ `${colorize(rich, theme.accent, "Upcoming:")} ${upcoming}`,
166
+ ].join(" │ ");
167
+ runtime.log(` ${stats}`);
168
+ }
169
+ else {
170
+ runtime.log(` Total: ${jobs.length} | Enabled: ${enabled} | Running: ${running} | Errors: ${errors} | Upcoming: ${upcoming}`);
171
+ }
172
+ runtime.log("");
173
+ }
174
+ // Print jobs table
175
+ function printJobsTable(jobs, runtime = defaultRuntime) {
176
+ const rich = isRich();
177
+ const now = Date.now();
178
+ if (jobs.length === 0) {
179
+ runtime.log(rich
180
+ ? colorize(rich, theme.muted, " No cron jobs configured.")
181
+ : " No cron jobs configured.");
182
+ return;
183
+ }
184
+ // Sort: running first, then by next run time
185
+ const sortedJobs = [...jobs].sort((a, b) => {
186
+ if (a.state.runningAtMs && !b.state.runningAtMs)
187
+ return -1;
188
+ if (!a.state.runningAtMs && b.state.runningAtMs)
189
+ return 1;
190
+ if (!a.enabled && b.enabled)
191
+ return 1;
192
+ if (a.enabled && !b.enabled)
193
+ return -1;
194
+ const aNext = a.state.nextRunAtMs ?? Infinity;
195
+ const bNext = b.state.nextRunAtMs ?? Infinity;
196
+ return aNext - bNext;
197
+ });
198
+ const columns = [
199
+ { key: "health", header: "", align: "center", minWidth: 2, maxWidth: 3 },
200
+ {
201
+ key: "status",
202
+ header: rich ? colorize(rich, theme.heading, "Status") : "Status",
203
+ align: "left",
204
+ minWidth: 10,
205
+ maxWidth: 12,
206
+ },
207
+ {
208
+ key: "name",
209
+ header: rich ? colorize(rich, theme.heading, "Name") : "Name",
210
+ align: "left",
211
+ minWidth: 20,
212
+ flex: true,
213
+ },
214
+ {
215
+ key: "schedule",
216
+ header: rich ? colorize(rich, theme.heading, "Schedule") : "Schedule",
217
+ align: "left",
218
+ minWidth: 18,
219
+ maxWidth: 25,
220
+ },
221
+ {
222
+ key: "next",
223
+ header: rich ? colorize(rich, theme.heading, "Next") : "Next",
224
+ align: "right",
225
+ minWidth: 10,
226
+ maxWidth: 12,
227
+ },
228
+ {
229
+ key: "last",
230
+ header: rich ? colorize(rich, theme.heading, "Last") : "Last",
231
+ align: "right",
232
+ minWidth: 10,
233
+ maxWidth: 12,
234
+ },
235
+ {
236
+ key: "target",
237
+ header: rich ? colorize(rich, theme.heading, "Target") : "Target",
238
+ align: "left",
239
+ minWidth: 12,
240
+ maxWidth: 15,
241
+ },
242
+ {
243
+ key: "trend",
244
+ header: rich ? colorize(rich, theme.heading, "Trend") : "Trend",
245
+ align: "center",
246
+ minWidth: 4,
247
+ maxWidth: 6,
248
+ },
249
+ ];
250
+ const rows = sortedJobs.map((job) => {
251
+ const healthScore = calculateHealthScore(job);
252
+ const nextRun = formatRelativeTime(job.state.nextRunAtMs, now);
253
+ const lastRun = formatRelativeTime(job.state.lastRunAtMs, now);
254
+ // Color-code the times
255
+ const coloredNext = rich && job.enabled && job.state.nextRunAtMs
256
+ ? job.state.nextRunAtMs - now < 300_000
257
+ ? colorize(rich, theme.warn, nextRun) // < 5 min
258
+ : colorize(rich, theme.muted, nextRun)
259
+ : nextRun;
260
+ const coloredLast = rich && job.state.lastRunAtMs
261
+ ? now - job.state.lastRunAtMs < 300_000
262
+ ? colorize(rich, theme.accentBright, lastRun) // < 5 min ago
263
+ : colorize(rich, theme.muted, lastRun)
264
+ : lastRun;
265
+ return {
266
+ health: getHealthIndicator(healthScore, rich),
267
+ status: getStatusDisplay(job, rich),
268
+ name: rich ? colorize(rich, theme.info, job.name) : job.name,
269
+ schedule: formatScheduleCompact(job),
270
+ next: coloredNext,
271
+ last: coloredLast,
272
+ target: formatTarget(job, rich),
273
+ trend: generateSparkline(job, rich),
274
+ };
275
+ });
276
+ const table = renderTable({
277
+ columns,
278
+ rows,
279
+ border: rich ? "unicode" : "ascii",
280
+ padding: 1,
281
+ });
282
+ runtime.log(table);
283
+ }
284
+ // Print legend
285
+ function printLegend(runtime = defaultRuntime) {
286
+ const rich = isRich();
287
+ runtime.log("");
288
+ if (rich) {
289
+ runtime.log(colorize(rich, theme.muted, " Legend:"));
290
+ runtime.log(` ${colorize(rich, theme.success, STATUS_ICONS.ok)} ok ${colorize(rich, theme.error, STATUS_ICONS.error)} error ${colorize(rich, theme.warn, STATUS_ICONS.running)} running ${colorize(rich, theme.muted, STATUS_ICONS.skipped)} skipped ${colorize(rich, theme.muted, STATUS_ICONS.disabled)} disabled`);
291
+ runtime.log(` ${colorize(rich, theme.success, "●")} healthy ${colorize(rich, theme.warn, "●")} warning ${colorize(rich, theme.error, "●")} critical`);
292
+ }
293
+ else {
294
+ runtime.log(" Legend:");
295
+ runtime.log(" ● ok ✖ error ▶ running ○ skipped ⊘ disabled");
296
+ runtime.log(" ● healthy ● warning ● critical");
297
+ }
298
+ }
299
+ // Print footer with tips
300
+ function printFooter(runtime = defaultRuntime) {
301
+ const rich = isRich();
302
+ runtime.log("");
303
+ if (rich) {
304
+ runtime.log(colorize(rich, theme.muted, " Commands: poolbot cron list | poolbot cron add --help | poolbot cron status"));
305
+ }
306
+ else {
307
+ runtime.log(" Commands: poolbot cron list | poolbot cron add --help | poolbot cron status");
308
+ }
309
+ runtime.log("");
310
+ }
311
+ export function registerCronDashboardCommand(cron) {
312
+ addGatewayClientOptions(cron
313
+ .command("dashboard")
314
+ .alias("dash")
315
+ .description("Visual dashboard for cron jobs")
316
+ .option("--all", "Include disabled jobs", false)
317
+ .option("--json", "Output JSON (disables visual dashboard)", false)
318
+ .action(async (opts) => {
319
+ try {
320
+ const res = await callGatewayFromCli("cron.list", opts, {
321
+ includeDisabled: Boolean(opts.all),
322
+ });
323
+ if (opts.json) {
324
+ defaultRuntime.log(JSON.stringify(res, null, 2));
325
+ return;
326
+ }
327
+ const jobs = res?.jobs ?? [];
328
+ printDashboardHeader();
329
+ printSummaryStats(jobs);
330
+ printJobsTable(jobs);
331
+ printLegend();
332
+ printFooter();
333
+ }
334
+ catch (err) {
335
+ defaultRuntime.error(danger(String(err)));
336
+ defaultRuntime.exit(1);
337
+ }
338
+ }));
339
+ }
@@ -1,6 +1,7 @@
1
1
  import { formatDocsLink } from "../../terminal/links.js";
2
2
  import { theme } from "../../terminal/theme.js";
3
3
  import { registerCronAddCommand, registerCronListCommand, registerCronStatusCommand, } from "./register.cron-add.js";
4
+ import { registerCronDashboardCommand } from "./register.cron-dashboard.js";
4
5
  import { registerCronEditCommand } from "./register.cron-edit.js";
5
6
  import { registerCronSimpleCommands } from "./register.cron-simple.js";
6
7
  export function registerCronCli(program) {
@@ -10,6 +11,7 @@ export function registerCronCli(program) {
10
11
  .addHelpText("after", () => `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/cron", "docs.molt.bot/cli/cron")}\n`);
11
12
  registerCronStatusCommand(cron);
12
13
  registerCronListCommand(cron);
14
+ registerCronDashboardCommand(cron);
13
15
  registerCronAddCommand(cron);
14
16
  registerCronSimpleCommands(cron);
15
17
  registerCronEditCommand(cron);
@@ -0,0 +1,187 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Structured error class for PoolBot CLI
4
+ * Provides rich error information with recovery suggestions
5
+ */
6
+ export class PoolBotError extends Error {
7
+ code;
8
+ category;
9
+ recoverable;
10
+ severity;
11
+ suggestions;
12
+ context;
13
+ exitCode;
14
+ constructor(options) {
15
+ super(options.message);
16
+ this.name = "PoolBotError";
17
+ this.code = options.code;
18
+ this.category = options.category;
19
+ this.recoverable = options.recoverable;
20
+ this.severity = options.severity ?? "error";
21
+ this.suggestions = options.suggestions ?? [];
22
+ this.context = options.context;
23
+ this.exitCode = options.exitCode ?? 1;
24
+ if (options.cause) {
25
+ this.cause = options.cause;
26
+ }
27
+ // Maintain proper stack trace
28
+ Error.captureStackTrace?.(this, PoolBotError);
29
+ }
30
+ /**
31
+ * Format error for user display with suggestions
32
+ */
33
+ formatForDisplay() {
34
+ const lines = [`❌ ${this.message}`, ` Code: ${this.code}`];
35
+ if (this.suggestions.length > 0) {
36
+ lines.push("", "💡 Suggestions:");
37
+ for (const suggestion of this.suggestions) {
38
+ lines.push(` • ${suggestion}`);
39
+ }
40
+ }
41
+ if (this.recoverable) {
42
+ lines.push("", "ℹ️ This error can be recovered from.");
43
+ }
44
+ return lines.join("\n");
45
+ }
46
+ /**
47
+ * Format error for JSON output
48
+ */
49
+ toJSON() {
50
+ const errorObj = {
51
+ message: this.message,
52
+ code: this.code,
53
+ category: this.category,
54
+ recoverable: this.recoverable,
55
+ severity: this.severity,
56
+ suggestions: this.suggestions,
57
+ context: this.context,
58
+ exitCode: this.exitCode,
59
+ };
60
+ if (this.cause) {
61
+ errorObj.cause = String(this.cause);
62
+ }
63
+ return { error: errorObj };
64
+ }
65
+ }
66
+ /**
67
+ * Error code registry with predefined common errors
68
+ */
69
+ export const ErrorCodes = {
70
+ // Config errors (E1xxx)
71
+ CONFIG_INVALID: "E1001",
72
+ CONFIG_MISSING: "E1002",
73
+ CONFIG_DEPRECATED: "E1003",
74
+ CONFIG_VERSION_MISMATCH: "E1004",
75
+ // Gateway errors (E2xxx)
76
+ GATEWAY_NOT_RUNNING: "E2001",
77
+ GATEWAY_CONNECTION_FAILED: "E2002",
78
+ GATEWAY_TIMEOUT: "E2003",
79
+ GATEWAY_AUTH_FAILED: "E2004",
80
+ // Auth errors (E3xxx)
81
+ AUTH_TOKEN_MISSING: "E3001",
82
+ AUTH_TOKEN_INVALID: "E3002",
83
+ AUTH_OAUTH_EXPIRED: "E3003",
84
+ // Network errors (E4xxx)
85
+ NETWORK_UNREACHABLE: "E4001",
86
+ NETWORK_TIMEOUT: "E4002",
87
+ DNS_RESOLUTION_FAILED: "E4003",
88
+ // Validation errors (E5xxx)
89
+ VALIDATION_INVALID_INPUT: "E5001",
90
+ VALIDATION_MISSING_REQUIRED: "E5002",
91
+ VALIDATION_TYPE_MISMATCH: "E5003",
92
+ // Filesystem errors (E6xxx)
93
+ FILE_NOT_FOUND: "E6001",
94
+ FILE_PERMISSION_DENIED: "E6002",
95
+ FILE_ALREADY_EXISTS: "E6003",
96
+ // Plugin errors (E7xxx)
97
+ PLUGIN_LOAD_FAILED: "E7001",
98
+ PLUGIN_NOT_FOUND: "E7002",
99
+ PLUGIN_INCOMPATIBLE: "E7003",
100
+ // Completion errors (E8xxx)
101
+ COMPLETION_CACHE_MISSING: "E8001",
102
+ COMPLETION_SHELL_UNSUPPORTED: "E8002",
103
+ COMPLETION_PROFILE_WRITE_FAILED: "E8003",
104
+ };
105
+ /**
106
+ * Helper functions for common error scenarios
107
+ */
108
+ export function createConfigError(message, suggestions, context) {
109
+ return new PoolBotError({
110
+ message,
111
+ code: ErrorCodes.CONFIG_INVALID,
112
+ category: "config",
113
+ recoverable: true,
114
+ suggestions: suggestions ?? ["Run `poolbot doctor` to check configuration"],
115
+ context,
116
+ });
117
+ }
118
+ export function createGatewayError(message, code, suggestions, context) {
119
+ return new PoolBotError({
120
+ message,
121
+ code,
122
+ category: "gateway",
123
+ recoverable: true,
124
+ suggestions: suggestions ?? [
125
+ "Run `poolbot gateway status` to check gateway health",
126
+ "Run `poolbot doctor` for diagnostics",
127
+ ],
128
+ context,
129
+ });
130
+ }
131
+ export function createValidationError(message, context) {
132
+ return new PoolBotError({
133
+ message,
134
+ code: ErrorCodes.VALIDATION_INVALID_INPUT,
135
+ category: "validation",
136
+ recoverable: true,
137
+ suggestions: ["Check command help with `--help` for usage information"],
138
+ context,
139
+ });
140
+ }
141
+ export function createCompletionError(message, code, suggestions) {
142
+ return new PoolBotError({
143
+ message,
144
+ code,
145
+ category: "config",
146
+ recoverable: true,
147
+ suggestions: suggestions ?? [
148
+ "Run `poolbot completion --write-state` to generate cache",
149
+ "Run `poolbot completion --install` to install to shell profile",
150
+ ],
151
+ });
152
+ }
153
+ /**
154
+ * Async wrapper that converts exceptions to PoolBotError
155
+ */
156
+ export async function withErrorHandling(operation, errorMapper) {
157
+ try {
158
+ return await operation();
159
+ }
160
+ catch (err) {
161
+ if (err instanceof PoolBotError) {
162
+ throw err;
163
+ }
164
+ throw errorMapper(err);
165
+ }
166
+ }
167
+ /**
168
+ * Zod schema for validating error options
169
+ */
170
+ export const PoolBotErrorSchema = z.object({
171
+ message: z.string(),
172
+ code: z.string(),
173
+ category: z.enum([
174
+ "config",
175
+ "gateway",
176
+ "auth",
177
+ "network",
178
+ "validation",
179
+ "filesystem",
180
+ "runtime",
181
+ "plugin",
182
+ ]),
183
+ recoverable: z.boolean(),
184
+ severity: z.enum(["error", "warning", "info"]).optional(),
185
+ suggestions: z.array(z.string()).optional(),
186
+ exitCode: z.number().optional(),
187
+ });
@@ -187,6 +187,19 @@ const coreEntries = [
187
187
  mod.registerSkillsCli(program);
188
188
  },
189
189
  },
190
+ {
191
+ commands: [
192
+ {
193
+ name: "swarm",
194
+ description: "Agent swarm coordination and management",
195
+ hasSubcommands: true,
196
+ },
197
+ ],
198
+ register: async ({ program }) => {
199
+ const mod = await import("../swarm-cli/register.js");
200
+ mod.registerSwarmCommand(program);
201
+ },
202
+ },
190
203
  ];
191
204
  function collectCoreCliCommandNames(predicate) {
192
205
  const seen = new Set();
@@ -6,6 +6,18 @@ import { defaultRuntime } from "../../runtime.js";
6
6
  import { formatDocsLink } from "../../terminal/links.js";
7
7
  import { theme } from "../../terminal/theme.js";
8
8
  import { runCommandWithRuntime } from "../cli-utils.js";
9
+ const DOCTOR_CHECKS = [
10
+ "config",
11
+ "auth",
12
+ "gateway",
13
+ "completion",
14
+ "security",
15
+ "plugins",
16
+ "memory",
17
+ "workspace",
18
+ "state",
19
+ "all",
20
+ ];
9
21
  export function registerMaintenanceCommands(program) {
10
22
  program
11
23
  .command("doctor")
@@ -19,6 +31,13 @@ export function registerMaintenanceCommands(program) {
19
31
  .option("--non-interactive", "Run without prompts (safe migrations only)", false)
20
32
  .option("--generate-gateway-token", "Generate and configure a gateway token", false)
21
33
  .option("--deep", "Scan system services for extra gateway installs", false)
34
+ .option("--check <check>", `Run specific check: ${DOCTOR_CHECKS.join(", ")}`, (value) => {
35
+ if (!DOCTOR_CHECKS.includes(value)) {
36
+ throw new Error(`Invalid check: ${value}. Valid: ${DOCTOR_CHECKS.join(", ")}`);
37
+ }
38
+ return value;
39
+ })
40
+ .option("--json", "Output results in JSON format", false)
22
41
  .action(async (opts) => {
23
42
  await runCommandWithRuntime(defaultRuntime, async () => {
24
43
  await doctorCommand(defaultRuntime, {
@@ -29,6 +48,8 @@ export function registerMaintenanceCommands(program) {
29
48
  nonInteractive: Boolean(opts.nonInteractive),
30
49
  generateGatewayToken: Boolean(opts.generateGatewayToken),
31
50
  deep: Boolean(opts.deep),
51
+ check: opts.check,
52
+ json: Boolean(opts.json),
32
53
  });
33
54
  });
34
55
  });
@@ -272,6 +272,15 @@ const entries = [
272
272
  mod.registerCompletionCli(program);
273
273
  },
274
274
  },
275
+ {
276
+ name: "telemetry",
277
+ description: "OpenTelemetry observability and metrics",
278
+ hasSubcommands: true,
279
+ register: async (program) => {
280
+ const mod = await import("../telemetry-cli/register.js");
281
+ mod.registerTelemetryCli(program);
282
+ },
283
+ },
275
284
  ];
276
285
  export function getSubCliEntries() {
277
286
  return entries;
@@ -0,0 +1,8 @@
1
+ import { registerSwarmCreateCommand, registerSwarmListCommand, registerSwarmStatusCommand, } from "./register.swarm-status.js";
2
+ export function registerSwarmCommand(program) {
3
+ const swarm = program.command("swarm").description("Agent swarm coordination and management");
4
+ registerSwarmStatusCommand(swarm);
5
+ registerSwarmListCommand(swarm);
6
+ registerSwarmCreateCommand(swarm);
7
+ return swarm;
8
+ }