@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.
- package/CHANGELOG.md +24 -0
- package/README.md +147 -69
- package/dist/.buildstamp +1 -1
- package/dist/agents/error-classifier.js +26 -77
- package/dist/agents/skills/security.js +1 -7
- package/dist/build-info.json +3 -3
- package/dist/cli/cron-cli/register.cron-dashboard.js +339 -0
- package/dist/cli/cron-cli/register.js +2 -0
- package/dist/cli/errors.js +187 -0
- package/dist/cli/program/command-registry.js +13 -0
- package/dist/cli/program/register.maintenance.js +21 -0
- package/dist/cli/program/register.subclis.js +9 -0
- package/dist/cli/swarm-cli/register.js +8 -0
- package/dist/cli/swarm-cli/register.swarm-status.js +488 -0
- package/dist/cli/telemetry-cli/register.js +10 -0
- package/dist/cli/telemetry-cli/register.telemetry-alerts.js +176 -0
- package/dist/cli/telemetry-cli/register.telemetry-metrics.js +323 -0
- package/dist/cli/telemetry-cli/register.telemetry-status.js +179 -0
- package/dist/commands/doctor-checks.js +498 -0
- package/dist/context-engine/index.js +1 -1
- package/dist/context-engine/legacy.js +1 -3
- package/dist/context-engine/summarizing.js +5 -8
- package/dist/cron/service/timer.js +18 -0
- package/dist/gateway/protocol/index.js +5 -2
- package/dist/gateway/protocol/schema/error-codes.js +1 -0
- package/dist/gateway/protocol/schema/swarm.js +80 -0
- package/dist/gateway/protocol/schema.js +1 -0
- package/dist/gateway/server-close.js +4 -0
- package/dist/gateway/server-constants.js +1 -0
- package/dist/gateway/server-cron.js +29 -0
- package/dist/gateway/server-maintenance.js +35 -2
- package/dist/gateway/server-methods/swarm.js +58 -0
- package/dist/gateway/server-methods/telemetry.js +71 -0
- package/dist/gateway/server-methods-list.js +8 -0
- package/dist/gateway/server-methods.js +9 -2
- package/dist/gateway/server.impl.js +33 -16
- package/dist/infra/abort-pattern.js +4 -4
- package/dist/infra/retry.js +3 -1
- package/dist/skills/commands.js +7 -25
- package/dist/skills/index.js +14 -17
- package/dist/skills/parser.js +12 -27
- package/dist/skills/registry.js +3 -6
- package/dist/skills/security.js +2 -8
- package/dist/swarm/service.js +247 -0
- package/dist/telemetry/alert-engine.js +258 -0
- package/dist/telemetry/cron-instrumentation.js +49 -0
- package/dist/telemetry/gateway-instrumentation.js +80 -0
- package/dist/telemetry/instrumentation.js +66 -0
- package/dist/telemetry/service.js +345 -0
- package/dist/tui/components/assistant-message.js +6 -2
- package/dist/tui/components/hyperlink-markdown.js +32 -0
- package/dist/tui/components/searchable-select-list.js +12 -1
- package/dist/tui/components/user-message.js +6 -2
- package/dist/tui/index.js +22 -6
- package/dist/tui/theme/theme-detection.js +226 -0
- package/dist/tui/tui-command-handlers.js +20 -0
- package/dist/tui/tui-formatters.js +4 -3
- package/dist/tui/utils/ctrl-c-handler.js +67 -0
- package/dist/tui/utils/osc8-hyperlinks.js +208 -0
- package/dist/tui/utils/safe-stop.js +180 -0
- package/dist/tui/utils/session-key-utils.js +81 -0
- package/dist/tui/utils/text-sanitization.js +284 -0
- package/dist/utils/lru-cache.js +116 -0
- package/dist/utils/performance.js +199 -0
- package/dist/utils/retry.js +240 -0
- package/docs/MELHORIAS_IMPLEMENTADAS.md +228 -0
- package/docs/MELHORIAS_PROFISSIONAIS.md +282 -0
- package/docs/PLANO_ACAO_TUI.md +357 -0
- package/docs/PROGRESSO_TUI.md +66 -0
- package/docs/RELATORIO_FINAL.md +217 -0
- package/docs/diagnostico-shell-completion.md +265 -0
- package/docs/features/advanced-memory.md +585 -0
- package/docs/features/discord-components-v2.md +277 -0
- package/docs/features/swarm.md +100 -0
- package/docs/features/telemetry.md +284 -0
- package/docs/integrations/INTEGRATION_PLAN.md +665 -345
- package/docs/models/provider-infrastructure.md +400 -0
- package/docs/security/exec-approvals.md +294 -0
- package/extensions/bluebubbles/package.json +1 -1
- package/extensions/copilot-proxy/package.json +1 -1
- package/extensions/diagnostics-otel/package.json +1 -1
- package/extensions/discord/package.json +1 -1
- package/extensions/feishu/package.json +1 -1
- package/extensions/google-antigravity-auth/package.json +1 -1
- package/extensions/google-gemini-cli-auth/package.json +1 -1
- package/extensions/googlechat/package.json +1 -1
- package/extensions/hexstrike-bridge/README.md +119 -0
- package/extensions/hexstrike-bridge/index.test.ts +247 -0
- package/extensions/hexstrike-bridge/index.ts +487 -0
- package/extensions/hexstrike-bridge/package.json +17 -0
- package/extensions/imessage/package.json +1 -1
- package/extensions/irc/package.json +1 -1
- package/extensions/line/package.json +1 -1
- package/extensions/llm-task/package.json +1 -1
- package/extensions/lobster/package.json +1 -1
- package/extensions/matrix/CHANGELOG.md +5 -0
- package/extensions/matrix/package.json +1 -1
- package/extensions/mattermost/package.json +1 -1
- package/extensions/mcp-server/index.ts +14 -0
- package/extensions/mcp-server/package.json +11 -0
- package/extensions/mcp-server/src/service.ts +540 -0
- package/extensions/memory-core/package.json +1 -1
- package/extensions/memory-lancedb/package.json +1 -1
- package/extensions/minimax-portal-auth/package.json +1 -1
- package/extensions/msteams/CHANGELOG.md +5 -0
- package/extensions/msteams/package.json +1 -1
- package/extensions/nextcloud-talk/package.json +1 -1
- package/extensions/nostr/CHANGELOG.md +5 -0
- package/extensions/nostr/package.json +1 -1
- package/extensions/open-prose/package.json +1 -1
- package/extensions/openai-codex-auth/package.json +1 -1
- package/extensions/signal/package.json +1 -1
- package/extensions/slack/package.json +1 -1
- package/extensions/telegram/package.json +1 -1
- package/extensions/tlon/package.json +1 -1
- package/extensions/twitch/CHANGELOG.md +5 -0
- package/extensions/twitch/package.json +1 -1
- package/extensions/voice-call/CHANGELOG.md +5 -0
- package/extensions/voice-call/package.json +1 -1
- package/extensions/whatsapp/package.json +1 -1
- package/extensions/zalo/CHANGELOG.md +5 -0
- package/extensions/zalo/package.json +1 -1
- package/extensions/zalouser/CHANGELOG.md +5 -0
- package/extensions/zalouser/package.json +1 -1
- 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
|
+
}
|