openalerts 0.2.6

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 (102) hide show
  1. package/README.md +253 -0
  2. package/dist/channels/console.d.ts +6 -0
  3. package/dist/channels/console.d.ts.map +1 -0
  4. package/dist/channels/console.js +10 -0
  5. package/dist/channels/console.js.map +1 -0
  6. package/dist/channels/telegram.d.ts +12 -0
  7. package/dist/channels/telegram.d.ts.map +1 -0
  8. package/dist/channels/telegram.js +28 -0
  9. package/dist/channels/telegram.js.map +1 -0
  10. package/dist/channels/webhook.d.ts +8 -0
  11. package/dist/channels/webhook.d.ts.map +1 -0
  12. package/dist/channels/webhook.js +15 -0
  13. package/dist/channels/webhook.js.map +1 -0
  14. package/dist/cli.d.ts +3 -0
  15. package/dist/cli.d.ts.map +1 -0
  16. package/dist/cli.js +234 -0
  17. package/dist/cli.js.map +1 -0
  18. package/dist/config.d.ts +51 -0
  19. package/dist/config.d.ts.map +1 -0
  20. package/dist/config.js +86 -0
  21. package/dist/config.js.map +1 -0
  22. package/dist/core/alert-channel.d.ts +16 -0
  23. package/dist/core/alert-channel.d.ts.map +1 -0
  24. package/dist/core/alert-channel.js +23 -0
  25. package/dist/core/alert-channel.js.map +1 -0
  26. package/dist/core/bounded-map.d.ts +52 -0
  27. package/dist/core/bounded-map.d.ts.map +1 -0
  28. package/dist/core/bounded-map.js +129 -0
  29. package/dist/core/bounded-map.js.map +1 -0
  30. package/dist/core/engine.d.ts +36 -0
  31. package/dist/core/engine.d.ts.map +1 -0
  32. package/dist/core/engine.js +162 -0
  33. package/dist/core/engine.js.map +1 -0
  34. package/dist/core/evaluator.d.ts +19 -0
  35. package/dist/core/evaluator.d.ts.map +1 -0
  36. package/dist/core/evaluator.js +168 -0
  37. package/dist/core/evaluator.js.map +1 -0
  38. package/dist/core/event-bus.d.ts +18 -0
  39. package/dist/core/event-bus.d.ts.map +1 -0
  40. package/dist/core/event-bus.js +32 -0
  41. package/dist/core/event-bus.js.map +1 -0
  42. package/dist/core/formatter.d.ts +15 -0
  43. package/dist/core/formatter.d.ts.map +1 -0
  44. package/dist/core/formatter.js +125 -0
  45. package/dist/core/formatter.js.map +1 -0
  46. package/dist/core/rules.d.ts +3 -0
  47. package/dist/core/rules.d.ts.map +1 -0
  48. package/dist/core/rules.js +410 -0
  49. package/dist/core/rules.js.map +1 -0
  50. package/dist/core/store.d.ts +9 -0
  51. package/dist/core/store.d.ts.map +1 -0
  52. package/dist/core/store.js +72 -0
  53. package/dist/core/store.js.map +1 -0
  54. package/dist/core/types.d.ts +158 -0
  55. package/dist/core/types.d.ts.map +1 -0
  56. package/dist/core/types.js +18 -0
  57. package/dist/core/types.js.map +1 -0
  58. package/dist/db/index.d.ts +6 -0
  59. package/dist/db/index.d.ts.map +1 -0
  60. package/dist/db/index.js +31 -0
  61. package/dist/db/index.js.map +1 -0
  62. package/dist/db/queries.d.ts +157 -0
  63. package/dist/db/queries.d.ts.map +1 -0
  64. package/dist/db/queries.js +221 -0
  65. package/dist/db/queries.js.map +1 -0
  66. package/dist/db/schema.d.ts +5 -0
  67. package/dist/db/schema.d.ts.map +1 -0
  68. package/dist/db/schema.js +177 -0
  69. package/dist/db/schema.js.map +1 -0
  70. package/dist/readers/openclaw.d.ts +11 -0
  71. package/dist/readers/openclaw.d.ts.map +1 -0
  72. package/dist/readers/openclaw.js +267 -0
  73. package/dist/readers/openclaw.js.map +1 -0
  74. package/dist/server/dashboard.d.ts +2 -0
  75. package/dist/server/dashboard.d.ts.map +1 -0
  76. package/dist/server/dashboard.js +765 -0
  77. package/dist/server/dashboard.js.map +1 -0
  78. package/dist/server/index.d.ts +10 -0
  79. package/dist/server/index.d.ts.map +1 -0
  80. package/dist/server/index.js +28 -0
  81. package/dist/server/index.js.map +1 -0
  82. package/dist/server/routes.d.ts +6 -0
  83. package/dist/server/routes.d.ts.map +1 -0
  84. package/dist/server/routes.js +146 -0
  85. package/dist/server/routes.js.map +1 -0
  86. package/dist/server/sse.d.ts +21 -0
  87. package/dist/server/sse.d.ts.map +1 -0
  88. package/dist/server/sse.js +53 -0
  89. package/dist/server/sse.js.map +1 -0
  90. package/dist/watchers/files.d.ts +19 -0
  91. package/dist/watchers/files.d.ts.map +1 -0
  92. package/dist/watchers/files.js +105 -0
  93. package/dist/watchers/files.js.map +1 -0
  94. package/dist/watchers/gateway-adapter.d.ts +18 -0
  95. package/dist/watchers/gateway-adapter.d.ts.map +1 -0
  96. package/dist/watchers/gateway-adapter.js +273 -0
  97. package/dist/watchers/gateway-adapter.js.map +1 -0
  98. package/dist/watchers/gateway.d.ts +27 -0
  99. package/dist/watchers/gateway.d.ts.map +1 -0
  100. package/dist/watchers/gateway.js +131 -0
  101. package/dist/watchers/gateway.js.map +1 -0
  102. package/package.json +48 -0
package/README.md ADDED
@@ -0,0 +1,253 @@
1
+ # openalerts
2
+
3
+ Standalone monitoring daemon for [OpenClaw](https://openclaw.dev). Connects to your local OpenClaw gateway in real time, fires alerts via Telegram or webhook when something goes wrong, and serves a live dashboard at `http://localhost:4242`.
4
+
5
+ No code changes to OpenClaw needed — runs as a separate process alongside it.
6
+
7
+ ---
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ npm install -g openalerts
13
+ ```
14
+
15
+ > Requires **Node.js ≥ 22.5.0** (uses the built-in `node:sqlite` module — no native builds).
16
+
17
+ ---
18
+
19
+ ## Quick start
20
+
21
+ ```bash
22
+ # 1. Create default config (auto-detects your OpenClaw gateway token)
23
+ openalerts init
24
+
25
+ # 2. Edit config to add your alert channel
26
+ # ~/.openalerts/config.json
27
+
28
+ # 3. Start monitoring
29
+ openalerts start
30
+ ```
31
+
32
+ Dashboard at **http://127.0.0.1:4242** — the gateway overlay dismisses automatically once connected.
33
+
34
+ ---
35
+
36
+ ## CLI
37
+
38
+ | Command | Description |
39
+ | -------- | --------------------------------------------------------- |
40
+ | `openalerts init` | Create default config at `~/.openalerts/config.json` |
41
+ | `openalerts start` | Start the monitoring daemon |
42
+ | `openalerts status` | Print live engine state (daemon must be running) |
43
+ | `openalerts test` | Fire a test alert through all configured channels |
44
+
45
+ **Options for `start`:**
46
+
47
+ | Flag | Default | Description |
48
+ | ---- | ------- | ----------- |
49
+ | `--port N` | `4242` | HTTP server port |
50
+ | `--config PATH` | `~/.openalerts/config.json` | Config file path |
51
+
52
+ ---
53
+
54
+ ## Configuration
55
+
56
+ `~/.openalerts/config.json` (created by `openalerts init`):
57
+
58
+ ```json
59
+ {
60
+ "gatewayUrl": "ws://127.0.0.1:18789",
61
+ "gatewayToken": "<auto-detected from ~/.openclaw/openclaw.json>",
62
+ "stateDir": "~/.openalerts",
63
+ "watch": {
64
+ "openclawDir": "~/.openclaw",
65
+ "workspaces": ["workspace", "workspace-study"]
66
+ },
67
+ "server": {
68
+ "port": 4242,
69
+ "host": "127.0.0.1"
70
+ },
71
+ "channels": [
72
+ { "type": "telegram", "token": "BOT_TOKEN", "chatId": "CHAT_ID" },
73
+ { "type": "webhook", "webhookUrl": "https://your-endpoint" },
74
+ { "type": "console" }
75
+ ],
76
+ "quiet": false
77
+ }
78
+ ```
79
+
80
+ **Gateway token** is auto-detected from `gateway.auth.token` in `~/.openclaw/openclaw.json` — no manual copy needed.
81
+
82
+ **Channels**: configure at least one. Falls back to `console` if none are set.
83
+
84
+ ---
85
+
86
+ ## What it monitors
87
+
88
+ Captures everything from the OpenClaw gateway WebSocket in real time:
89
+
90
+ | Event | What's captured |
91
+ | ----- | --------------- |
92
+ | Agent runs | Start, streaming, complete, error, aborted |
93
+ | Tool calls | Tool name, duration, result |
94
+ | LLM usage | Token counts, cost per call |
95
+ | Shell exec | Command, pid, output, exit code |
96
+ | Heartbeats | Gateway health, active sessions, queue depth |
97
+ | Cron jobs | Schedule, last run, next run, errors |
98
+ | Sessions | Active sessions, costs, message counts |
99
+ | Workspace docs | SOUL.md, HEARTBEAT.md, MEMORY.md per agent |
100
+
101
+ ---
102
+
103
+ ## Alert rules
104
+
105
+ Ten rules run against every event in real time. All thresholds and cooldowns are configurable via the `rules` key in config.
106
+
107
+ | Rule | Triggers when | Threshold | Cooldown |
108
+ | ---- | ------------- | --------- | -------- |
109
+ | `infra-errors` | `infra.error` events in 1 min | 1 | 15 min |
110
+ | `llm-errors` | `llm.error` or `agent.error` in 1 min | 1 | 15 min |
111
+ | `tool-errors` | `tool.error` in 1 min | 1 | 15 min |
112
+ | `heartbeat-fail` | Consecutive heartbeat failures | 3 | 30 min |
113
+ | `session-stuck` | Session idle too long | 120 s | 30 min |
114
+ | `high-error-rate` | Error % of last 20 calls | 50% | 30 min |
115
+ | `queue-depth` | Items in delivery queue | 10 | 15 min |
116
+ | `gateway-down` | No heartbeat from watchdog | 30 s | 60 min |
117
+ | `cost-hourly-spike` | LLM cost per hour | $5 | 30 min |
118
+ | `cost-daily-budget` | LLM cost per day | $20 | 6 h |
119
+
120
+ Override a rule threshold in config:
121
+
122
+ ```json
123
+ {
124
+ "rules": {
125
+ "llm-errors": { "threshold": 5 },
126
+ "gateway-down": { "enabled": false },
127
+ "heartbeat-fail": { "cooldownMinutes": 60 }
128
+ }
129
+ }
130
+ ```
131
+
132
+ ---
133
+
134
+ ## Dashboard
135
+
136
+ Open **http://127.0.0.1:4242** after starting.
137
+
138
+ | Tab | What's shown |
139
+ | --- | ------------ |
140
+ | **Overview** | Gateway health log, live activity feed, 24h stats, recent alerts |
141
+ | **Workspaces** | Per-agent SOUL.md, HEARTBEAT.md, MEMORY.md, USER.md previews |
142
+ | **Alerts** | Full alert history with severity, rule ID, fingerprint |
143
+ | **Sessions** | Active sessions with status, token counts, cost |
144
+ | **Live Monitor** | Real-time per-run timeline — steps, tool calls, LLM responses, exec |
145
+ | **Cron Jobs** | Scheduled job status, last/next run, consecutive errors |
146
+ | **Diagnostics** | Raw engine event log |
147
+ | **Delivery Queue** | Pending/failed alert delivery items |
148
+
149
+ Live Monitor sidebar shows sessions colour-coded by status:
150
+ - Green pulse = active (idle, waiting for input)
151
+ - Yellow pulse = thinking (LLM streaming in progress)
152
+ - Grey = no recent activity
153
+
154
+ ---
155
+
156
+ ## REST API
157
+
158
+ All endpoints return JSON. CORS enabled.
159
+
160
+ | Method | Path | Description |
161
+ | ------ | ---- | ----------- |
162
+ | `GET` | `/api/state` | Full dashboard snapshot |
163
+ | `GET` | `/api/activity` | Unified activity log (actions + diagnostics merged) |
164
+ | `GET` | `/api/logs` | OpenClaw-style log format (`ts`, `level`, `subsystem`, `message`) |
165
+ | `GET` | `/api/actions` | Raw agent/tool/chat action rows |
166
+ | `GET` | `/api/heartbeats` | Gateway heartbeat history |
167
+ | `GET` | `/api/alerts` | Fired alert history |
168
+ | `GET` | `/api/diagnostics` | Engine diagnostic events |
169
+ | `GET` | `/api/engine` | Engine stats, uptime, gateway connection status |
170
+ | `POST` | `/api/test` | Fire a test alert |
171
+ | `GET` | `/events` | SSE stream (live push to dashboard) |
172
+ | `GET` | `/health` | `{ok, ts, sseClients, gatewayConnected}` |
173
+
174
+ Most list endpoints accept `?limit=N` (default 100, max 500) and `/api/activity`, `/api/actions`, `/api/logs` also accept `?session=KEY`.
175
+
176
+ ### SSE events (`GET /events`)
177
+
178
+ | Event | Payload |
179
+ | ----- | ------- |
180
+ | `state` | Full dashboard snapshot — sent on connect and on file changes |
181
+ | `openalerts` | Alert fired: `{rule_id, severity, title, detail, ts, fingerprint}` |
182
+ | `action` | Agent step: `{id, runId, sessionKey, type, toolName, content, ts}` |
183
+ | `health` | Gateway heartbeat: `{queueDepth, activeSessions, sessions, ts}` |
184
+ | `diagnostic` | Engine event: `{event_type, ts, summary, session_key}` |
185
+ | `exec` | Shell command: `{type, runId, pid, command, output, exitCode, ts}` |
186
+
187
+ ---
188
+
189
+ ## Architecture
190
+
191
+ ```
192
+ openalerts
193
+ ├── cli.ts Entry point — wires everything together
194
+ ├── config.ts Config loading + gateway token auto-detection
195
+ ├── core/
196
+ │ ├── engine.ts Alert engine — ingests events, evaluates rules, fires alerts
197
+ │ ├── evaluator.ts Sliding-window + cooldown rule state
198
+ │ ├── rules.ts 10 alert rule definitions
199
+ │ └── types.ts Event and alert type definitions
200
+ ├── watchers/
201
+ │ ├── gateway.ts WebSocket client for OpenClaw gateway
202
+ │ ├── gateway-adapter.ts Translates gateway frames → engine events + SSE payloads
203
+ │ └── files.ts node:fs watcher for OpenClaw workspace files
204
+ ├── readers/
205
+ │ └── openclaw.ts Reads SOUL.md, cron jobs, sessions, delivery queue, config
206
+ ├── channels/
207
+ │ ├── telegram.ts Telegram Bot API (no SDK, direct HTTP)
208
+ │ ├── webhook.ts HTTP POST to any webhook URL
209
+ │ └── console.ts Console fallback
210
+ ├── server/
211
+ │ ├── index.ts HTTP server bootstrap
212
+ │ ├── routes.ts All REST + SSE route handlers
213
+ │ ├── sse.ts SSE manager (15s keepalive, broadcast)
214
+ │ └── dashboard.ts Embedded dashboard HTML (vanilla JS, zero framework)
215
+ └── db/
216
+ ├── index.ts SQLite open + periodic prune
217
+ ├── schema.ts 12 tables: sessions, actions, alerts, heartbeats, cron_jobs…
218
+ └── queries.ts Typed query functions including getActivityLog (UNION query)
219
+ ```
220
+
221
+ **Storage**: SQLite at `~/.openalerts/openalerts.db` using Node 22's built-in `node:sqlite` — no native build step, no binaries to compile.
222
+
223
+ **Zero heavy dependencies**: only `ws` (WebSocket client). No Express, no ORM, no native modules.
224
+
225
+ ---
226
+
227
+ ## Data flow
228
+
229
+ ```
230
+ OpenClaw gateway (ws://127.0.0.1:18789)
231
+ │ health, agent, chat, exec events
232
+
233
+ gateway-adapter.ts ──► engine.ingest() ──► rule evaluation ──► Telegram / webhook
234
+ │ │
235
+ │ SQLite DB JSONL event store
236
+ │ (actions, alerts, (warm-start rule
237
+ │ heartbeats, sessions) state on restart)
238
+
239
+ SSE broadcast ──► dashboard live updates
240
+
241
+ OpenClaw workspace files (~/.openclaw/)
242
+ │ SOUL.md, HEARTBEAT.md, cron/jobs.json, sessions.json…
243
+
244
+ openclaw.ts reader ──► SQLite (agent_info, cron_jobs, sessions)
245
+
246
+ node:fs watcher triggers re-reads on change
247
+ ```
248
+
249
+ ---
250
+
251
+ ## License
252
+
253
+ Apache-2.0
@@ -0,0 +1,6 @@
1
+ import type { AlertChannel, AlertEvent } from "../core/types.js";
2
+ export declare class ConsoleChannel implements AlertChannel {
3
+ readonly name = "console";
4
+ send(alert: AlertEvent, formatted: string): void;
5
+ }
6
+ //# sourceMappingURL=console.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"console.d.ts","sourceRoot":"","sources":["../../src/channels/console.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEjE,qBAAa,cAAe,YAAW,YAAY;IACjD,QAAQ,CAAC,IAAI,aAAa;IAC1B,IAAI,CAAC,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;CAMjD"}
@@ -0,0 +1,10 @@
1
+ export class ConsoleChannel {
2
+ name = "console";
3
+ send(alert, formatted) {
4
+ const prefix = alert.severity === "critical" ? "🚨" :
5
+ alert.severity === "error" ? "❌" :
6
+ alert.severity === "warn" ? "⚠️" : "ℹ️";
7
+ console.log(`\n${prefix} ALERT [${alert.ruleId}]\n${formatted}\n`);
8
+ }
9
+ }
10
+ //# sourceMappingURL=console.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"console.js","sourceRoot":"","sources":["../../src/channels/console.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,cAAc;IAChB,IAAI,GAAG,SAAS,CAAC;IAC1B,IAAI,CAAC,KAAiB,EAAE,SAAiB;QACvC,MAAM,MAAM,GAAG,KAAK,CAAC,QAAQ,KAAK,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YACtC,KAAK,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;gBAClC,KAAK,CAAC,QAAQ,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;QACvD,OAAO,CAAC,GAAG,CAAC,KAAK,MAAM,WAAW,KAAK,CAAC,MAAM,MAAM,SAAS,IAAI,CAAC,CAAC;IACrE,CAAC;CACF"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Telegram alert channel — uses Bot API directly (no OpenClaw dependency).
3
+ */
4
+ import type { AlertChannel, AlertEvent } from "../core/types.js";
5
+ export declare class TelegramChannel implements AlertChannel {
6
+ readonly name = "telegram";
7
+ private token;
8
+ private chatId;
9
+ constructor(token: string, chatId: string);
10
+ send(_alert: AlertEvent, formatted: string): Promise<void>;
11
+ }
12
+ //# sourceMappingURL=telegram.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telegram.d.ts","sourceRoot":"","sources":["../../src/channels/telegram.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEjE,qBAAa,eAAgB,YAAW,YAAY;IAClD,QAAQ,CAAC,IAAI,cAAc;IAC3B,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,MAAM,CAAS;gBAEX,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IAKnC,IAAI,CAAC,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAoBjE"}
@@ -0,0 +1,28 @@
1
+ export class TelegramChannel {
2
+ name = "telegram";
3
+ token;
4
+ chatId;
5
+ constructor(token, chatId) {
6
+ this.token = token;
7
+ this.chatId = chatId;
8
+ }
9
+ async send(_alert, formatted) {
10
+ const url = `https://api.telegram.org/bot${this.token}/sendMessage`;
11
+ const body = JSON.stringify({
12
+ chat_id: this.chatId,
13
+ text: formatted,
14
+ parse_mode: "HTML",
15
+ disable_web_page_preview: true,
16
+ });
17
+ const res = await fetch(url, {
18
+ method: "POST",
19
+ headers: { "Content-Type": "application/json" },
20
+ body,
21
+ });
22
+ if (!res.ok) {
23
+ const text = await res.text().catch(() => "");
24
+ throw new Error(`Telegram API ${res.status}: ${text.substring(0, 200)}`);
25
+ }
26
+ }
27
+ }
28
+ //# sourceMappingURL=telegram.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telegram.js","sourceRoot":"","sources":["../../src/channels/telegram.ts"],"names":[],"mappings":"AAKA,MAAM,OAAO,eAAe;IACjB,IAAI,GAAG,UAAU,CAAC;IACnB,KAAK,CAAS;IACd,MAAM,CAAS;IAEvB,YAAY,KAAa,EAAE,MAAc;QACvC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,MAAkB,EAAE,SAAiB;QAC9C,MAAM,GAAG,GAAG,+BAA+B,IAAI,CAAC,KAAK,cAAc,CAAC;QACpE,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC;YAC1B,OAAO,EAAE,IAAI,CAAC,MAAM;YACpB,IAAI,EAAE,SAAS;YACf,UAAU,EAAE,MAAM;YAClB,wBAAwB,EAAE,IAAI;SAC/B,CAAC,CAAC;QAEH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAC3B,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI;SACL,CAAC,CAAC;QAEH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;YAC9C,MAAM,IAAI,KAAK,CAAC,gBAAgB,GAAG,CAAC,MAAM,KAAK,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;QAC3E,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,8 @@
1
+ import type { AlertChannel, AlertEvent } from "../core/types.js";
2
+ export declare class WebhookChannel implements AlertChannel {
3
+ readonly name = "webhook";
4
+ private url;
5
+ constructor(url: string);
6
+ send(alert: AlertEvent, formatted: string): Promise<void>;
7
+ }
8
+ //# sourceMappingURL=webhook.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../../src/channels/webhook.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEjE,qBAAa,cAAe,YAAW,YAAY;IACjD,QAAQ,CAAC,IAAI,aAAa;IAC1B,OAAO,CAAC,GAAG,CAAS;gBAER,GAAG,EAAE,MAAM;IAEjB,IAAI,CAAC,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAQhE"}
@@ -0,0 +1,15 @@
1
+ export class WebhookChannel {
2
+ name = "webhook";
3
+ url;
4
+ constructor(url) { this.url = url; }
5
+ async send(alert, formatted) {
6
+ const res = await fetch(this.url, {
7
+ method: "POST",
8
+ headers: { "Content-Type": "application/json" },
9
+ body: JSON.stringify({ alert, formatted }),
10
+ });
11
+ if (!res.ok)
12
+ throw new Error(`Webhook ${res.status}`);
13
+ }
14
+ }
15
+ //# sourceMappingURL=webhook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook.js","sourceRoot":"","sources":["../../src/channels/webhook.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,cAAc;IAChB,IAAI,GAAG,SAAS,CAAC;IAClB,GAAG,CAAS;IAEpB,YAAY,GAAW,IAAI,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC;IAE5C,KAAK,CAAC,IAAI,CAAC,KAAiB,EAAE,SAAiB;QAC7C,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE;YAChC,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;SAC3C,CAAC,CAAC;QACH,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,WAAW,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;IACxD,CAAC;CACF"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,234 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * openalerts CLI
4
+ *
5
+ * Usage:
6
+ * openalerts start Start monitoring daemon
7
+ * openalerts start --port 4242 Custom port
8
+ * openalerts start --config ./openalerts.config.json
9
+ * openalerts status Print engine state (if running)
10
+ * openalerts init Create default config file
11
+ * openalerts test Send test alert (daemon must be running)
12
+ */
13
+ import path from "node:path";
14
+ import os from "node:os";
15
+ import { openDb, pruneDb } from "./db/index.js";
16
+ import { loadConfig, detectGatewayToken, writeDefaultConfig } from "./config.js";
17
+ import { loadAll } from "./readers/openclaw.js";
18
+ import { FileWatcher } from "./watchers/files.js";
19
+ import { GatewayClient } from "./watchers/gateway.js";
20
+ import { translateGatewayEvent } from "./watchers/gateway-adapter.js";
21
+ import { OpenAlertsEngine } from "./core/engine.js";
22
+ import { TelegramChannel } from "./channels/telegram.js";
23
+ import { WebhookChannel } from "./channels/webhook.js";
24
+ import { ConsoleChannel } from "./channels/console.js";
25
+ import { startHttpServer } from "./server/index.js";
26
+ // ── CLI arg parsing ───────────────────────────────────────────────────────────
27
+ const args = process.argv.slice(2);
28
+ const command = args[0] ?? "start";
29
+ function getFlag(name, defaultVal) {
30
+ const idx = args.indexOf(name);
31
+ if (idx !== -1 && args[idx + 1])
32
+ return args[idx + 1];
33
+ return defaultVal;
34
+ }
35
+ // ── Commands ──────────────────────────────────────────────────────────────────
36
+ if (command === "init") {
37
+ const configPath = getFlag("--config") ?? path.join(os.homedir(), ".openalerts", "config.json");
38
+ writeDefaultConfig(configPath);
39
+ console.log(`[init] Created config at ${configPath}`);
40
+ console.log(`[init] Edit it and run: openalerts start`);
41
+ process.exit(0);
42
+ }
43
+ if (command === "test") {
44
+ const port = parseInt(getFlag("--port") ?? "4242");
45
+ fetch(`http://127.0.0.1:${port}/api/test`, { method: "POST" })
46
+ .then(r => r.json())
47
+ .then(d => { console.log("[test]", d); process.exit(0); })
48
+ .catch(err => { console.error("[test] Failed (is daemon running?):", err.message); process.exit(1); });
49
+ // guard: exit after 5s if fetch hangs
50
+ setTimeout(() => process.exit(1), 5000);
51
+ process.exitCode = 0; // allow async
52
+ }
53
+ else if (command === "status") {
54
+ const port = parseInt(getFlag("--port") ?? "4242");
55
+ fetch(`http://127.0.0.1:${port}/api/engine`)
56
+ .then(r => r.json())
57
+ .then(d => { console.log(JSON.stringify(d, null, 2)); process.exit(0); })
58
+ .catch(err => { console.error("[status] Failed (is daemon running?):", err.message); process.exit(1); });
59
+ setTimeout(() => process.exit(1), 5000);
60
+ }
61
+ else if (command === "start") {
62
+ const configPath = getFlag("--config");
63
+ const portArg = getFlag("--port");
64
+ const config = loadConfig(configPath);
65
+ if (portArg)
66
+ config.server.port = parseInt(portArg);
67
+ // Auto-detect gateway token if not configured
68
+ if (!config.gatewayToken) {
69
+ config.gatewayToken = detectGatewayToken(config.watch.openclawDir);
70
+ if (config.gatewayToken) {
71
+ console.log("[config] Auto-detected gateway operator token");
72
+ }
73
+ }
74
+ startDaemon(config);
75
+ }
76
+ else {
77
+ console.error(`Unknown command: ${command}`);
78
+ console.error("Usage: openalerts [start|init|status|test] [--port N] [--config path]");
79
+ process.exit(1);
80
+ }
81
+ // ── Daemon startup ────────────────────────────────────────────────────────────
82
+ async function startDaemon(config) {
83
+ console.log("[openalerts] Starting…");
84
+ console.log(`[openalerts] State dir: ${config.stateDir}`);
85
+ console.log(`[openalerts] Watching: ${config.watch.openclawDir}`);
86
+ // ── 1. Open database ───────────────────────────────────────────────────────
87
+ const db = openDb(config.stateDir);
88
+ console.log("[db] SQLite opened");
89
+ // ── 2. Build alert channels ────────────────────────────────────────────────
90
+ const channels = [];
91
+ for (const ch of config.channels) {
92
+ if (ch.type === "telegram" && ch.token && ch.chatId) {
93
+ channels.push(new TelegramChannel(ch.token, ch.chatId));
94
+ console.log("[channels] Telegram channel added");
95
+ }
96
+ else if (ch.type === "webhook" && ch.webhookUrl) {
97
+ channels.push(new WebhookChannel(ch.webhookUrl));
98
+ console.log("[channels] Webhook channel added");
99
+ }
100
+ else if (ch.type === "console") {
101
+ channels.push(new ConsoleChannel());
102
+ console.log("[channels] Console channel added");
103
+ }
104
+ }
105
+ if (channels.length === 0) {
106
+ channels.push(new ConsoleChannel());
107
+ console.log("[channels] No channels configured — using console fallback");
108
+ }
109
+ // ── 3. Start alert engine ──────────────────────────────────────────────────
110
+ const engine = new OpenAlertsEngine({
111
+ stateDir: path.join(config.stateDir, "events"),
112
+ config: {
113
+ quiet: config.quiet,
114
+ rules: config.rules,
115
+ },
116
+ channels,
117
+ diagnosisHint: 'Run "openclaw doctor" in your terminal',
118
+ db,
119
+ });
120
+ engine.start();
121
+ // ── 4. Initial file load ───────────────────────────────────────────────────
122
+ console.log("[loader] Loading OpenClaw files…");
123
+ try {
124
+ loadAll(db, config.watch);
125
+ console.log("[loader] Initial load complete");
126
+ }
127
+ catch (err) {
128
+ console.warn(`[loader] Initial load warning: ${err}`);
129
+ }
130
+ // ── 5. Gateway connection state (declared early so closure captures it) ──────
131
+ let gatewayConnected = false;
132
+ let startupGwAlertFired = false;
133
+ // ── 6. Start HTTP server + SSE ─────────────────────────────────────────────
134
+ const httpServer = startHttpServer(config.server, db, engine, () => gatewayConnected);
135
+ // Wire engine events to SSE (push live alerts/diagnostics to dashboard)
136
+ engine.bus.on(event => {
137
+ httpServer.sse.emit("diagnostic", {
138
+ event_type: event.type, ts: event.ts,
139
+ summary: `${event.type}${event.outcome ? `:${event.outcome}` : ""}`,
140
+ channel: event.channel, session_key: event.sessionKey, agent_id: event.agentId,
141
+ });
142
+ });
143
+ // ── 7. File watcher ────────────────────────────────────────────────────────
144
+ const fileWatcher = new FileWatcher(db, config.watch, (changed) => {
145
+ console.log(`[watcher] Changed: ${path.basename(changed)}`);
146
+ // Push updated state to SSE clients
147
+ try {
148
+ import("./db/queries.js").then(({ getDashboardState }) => {
149
+ httpServer.sse.emit("state", getDashboardState(db));
150
+ }).catch(() => { });
151
+ }
152
+ catch { /* ignore */ }
153
+ });
154
+ fileWatcher.start();
155
+ // ── 8. Gateway WebSocket ───────────────────────────────────────────────────
156
+ const gwClient = new GatewayClient({
157
+ url: config.gatewayUrl,
158
+ token: config.gatewayToken,
159
+ });
160
+ gwClient.on("ready", () => {
161
+ gatewayConnected = true;
162
+ console.log("[gateway] Connected to OpenClaw gateway ✓");
163
+ // Push updated state (heartbeats, sessions) to dashboard
164
+ import("./db/queries.js").then(({ getDashboardState }) => {
165
+ httpServer.sse.emit("state", getDashboardState(db));
166
+ }).catch(() => { });
167
+ });
168
+ gwClient.on("disconnected", () => {
169
+ const wasConnected = gatewayConnected;
170
+ gatewayConnected = false;
171
+ if (wasConnected) {
172
+ console.log("[gateway] Lost connection — reconnecting…");
173
+ engine.ingest({ type: "infra.heartbeat", ts: Date.now(), outcome: "error" });
174
+ }
175
+ });
176
+ gwClient.on("error", (err) => {
177
+ // ECONNREFUSED = openclaw not running, suppress noisy repeat logs
178
+ if (!err.message.includes("ECONNREFUSED") && !err.message.includes("connect ECONNREFUSED")) {
179
+ console.warn(`[gateway] ${err.message}`);
180
+ }
181
+ });
182
+ // All real gateway event names from OpenClaw's WS protocol
183
+ const GW_EVENTS = ["health", "tick", "agent", "chat", "cron.run", "exec.started", "exec.output", "exec.completed"];
184
+ for (const evt of GW_EVENTS) {
185
+ gwClient.on(evt, (payload) => {
186
+ const result = translateGatewayEvent(evt, payload, db);
187
+ if (result.event)
188
+ engine.ingest(result.event);
189
+ if (result.sseType && result.ssePayload) {
190
+ httpServer.sse.emit(result.sseType, result.ssePayload);
191
+ }
192
+ });
193
+ }
194
+ gwClient.start();
195
+ // Fire gateway-down alert if not connected within 30s of startup
196
+ setTimeout(() => {
197
+ if (!gatewayConnected && !startupGwAlertFired) {
198
+ startupGwAlertFired = true;
199
+ console.warn("[gateway] Not connected after 30s — OpenClaw may be down");
200
+ engine.ingest({ type: "infra.heartbeat", ts: Date.now(), outcome: "error" });
201
+ // Manually fire a gateway-down style alert via 3 consecutive heartbeat failures
202
+ for (let i = 0; i < 3; i++) {
203
+ engine.ingest({ type: "infra.heartbeat", ts: Date.now() + i, outcome: "error" });
204
+ }
205
+ }
206
+ }, 30_000);
207
+ // ── 9. Periodic tasks ─────────────────────────────────────────────────────
208
+ // Re-sync files + DB prune every 5 minutes
209
+ const syncInterval = setInterval(() => {
210
+ try {
211
+ loadAll(db, config.watch);
212
+ }
213
+ catch { /* ignore */ }
214
+ try {
215
+ pruneDb(db);
216
+ }
217
+ catch { /* ignore */ }
218
+ }, 5 * 60 * 1000);
219
+ // ── 10. Graceful shutdown ──────────────────────────────────────────────────
220
+ function shutdown(sig) {
221
+ console.log(`\n[openalerts] ${sig} — shutting down…`);
222
+ clearInterval(syncInterval);
223
+ gwClient.stop();
224
+ fileWatcher.stop();
225
+ engine.stop();
226
+ httpServer.close();
227
+ db.close();
228
+ process.exit(0);
229
+ }
230
+ process.on("SIGINT", () => shutdown("SIGINT"));
231
+ process.on("SIGTERM", () => shutdown("SIGTERM"));
232
+ console.log("[openalerts] Running. Press Ctrl+C to stop.");
233
+ }
234
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}