livetap 0.2.0 → 0.2.1

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/README.md CHANGED
@@ -4,29 +4,36 @@
4
4
 
5
5
  Connect MQTT brokers, WebSocket feeds, or tail log files. Your agent samples, watches, and acts on real-time data through natural language.
6
6
 
7
- <!-- TODO: Add demo GIF here after recording -->
8
- <!-- ![livetap demo](demo.gif) -->
7
+ ![Claude Code connects to a live MQTT stream of solar inverter data, samples payloads with OBIS codes, and creates a watcher for high power consumption (16.7.0 > 3000W). When the alert fires, it autonomously formats the data and analyzes the reading.](docs/images/demo-setup.png)
8
+ *Real solar inverter data — from natural language request to live alert in under 30 seconds*
9
+
10
+ ![A second alert fires and Claude Code autonomously compares previous and current values, noting consumption increased by 174W and is still climbing. It formats a comparison table and recommends investigation.](docs/images/demo-alert.png)
11
+ *Escalating alerts — the agent compares values across alerts and analyzes trends autonomously*
9
12
 
10
13
  ## Quick start
11
14
 
12
- **Requirements:** [Bun](https://bun.sh), Claude Code v2.1.80+
15
+ **Requirements:** [Bun](https://bun.sh) or Node.js 18+, Claude Code v2.1.80+
13
16
 
14
17
  ```bash
15
- bun add livetap
18
+ npm install livetap
19
+ npx livetap setup
16
20
  ```
17
21
 
18
- Set up and start Claude Code:
22
+ Restart Claude Code with Channels enabled:
19
23
 
20
24
  ```bash
21
- livetap setup
22
- claude --dangerously-load-development-channels server:livetap
25
+ claude --dangerously-load-development-channels server:livetap --continue
23
26
  ```
24
27
 
28
+ > **What's that flag?** Claude Code [Channels](https://code.claude.com/docs/en/channels) let MCP servers push real-time events (like watcher alerts) into your conversation. The `--dangerously-load-development-channels` flag enables this for locally-installed servers. It only loads servers listed in your project's `.mcp.json` — nothing is sent externally.
29
+
25
30
  Then ask your agent:
26
31
 
27
32
  > "Connect to the IoT demo at mqtt://broker.emqx.io on topic justinx/demo/# and watch for temperature above 23 degrees"
28
33
 
29
- ## Setup with Claude Code (for AI agents)
34
+ The agent connects to the free public MQTT broker, samples the data to learn the payload structure, creates a watcher, and alerts you when temperature exceeds 23C.
35
+
36
+ ## Setup for AI agents
30
37
 
31
38
  If you are Claude Code or another AI agent, run `npx livetap --llm-help` for structured setup instructions.
32
39
 
@@ -39,49 +46,25 @@ npx livetap setup # Step 2: creates .mcp.json and starts daemon
39
46
  # claude --dangerously-load-development-channels server:livetap --continue
40
47
  ```
41
48
 
49
+ After restart, your 13 MCP tools are automatically available — no discovery step needed.
50
+
42
51
  **IMPORTANT — do NOT:**
43
52
  - Add livetap to `~/.claude/mcp.json` — it goes in `.mcp.json` in the project root
44
53
  - Configure livetap as `type: "http"` — it is a stdio MCP server (command + args)
45
54
  - Worry about the daemon — setup starts it, and the MCP proxy auto-starts it if needed
46
55
  - Guess field names — always `read_stream` first to see actual payload structure
56
+ - Use `npm init` — use the existing project directory
47
57
 
48
- ### After restart — use MCP tools
49
-
50
- Once restarted with the channel flag, you have 13 MCP tools available. Follow this workflow:
58
+ ### After restart — workflow
51
59
 
52
60
  1. **CONNECT** — `create_connection({ type: "mqtt", broker: "broker.emqx.io", port: 1883, tls: false, topics: ["justinx/demo/#"] })`
53
61
  2. **SAMPLE** — `read_stream({ connectionId: "conn_xxx", backfillSeconds: 60, maxEntries: 10 })` — ALWAYS sample first to see field paths
54
62
  3. **WATCH** — `create_watcher({ connectionId: "conn_xxx", conditions: [{ field: "sensors.temperature.value", op: ">", value: 50 }], match: "all", cooldown: 60 })`
55
-
56
- ### Supported source types
57
-
58
- | Type | create_connection params | CLI |
59
- |------|------------------------|-----|
60
- | MQTT | `{ type: "mqtt", broker: "host", port: 1883, tls: false, topics: ["topic/#"] }` | `livetap tap mqtt://host:1883/topic/#` |
61
- | WebSocket | `{ type: "websocket", url: "wss://..." }` | `livetap tap wss://...` |
62
- | File | `{ type: "file", path: "/var/log/app.log" }` | `livetap tap file:///var/log/app.log` |
63
-
64
- ### Data shape by source
65
-
66
- - **MQTT/WebSocket (JSON):** payload is parsed. Use dot-paths: `sensors.temperature.value`
67
- - **File (plain text):** field is `payload`. Use: `{ field: "payload", op: "contains", value: "ERROR" }` or `{ field: "payload", op: "matches", value: "5[0-9]{2}" }`
68
- - **File (JSON lines):** parsed. Use dot-paths: `level`, `msg`
69
- - **IMPORTANT:** always `read_stream` first. The field is `payload`, NOT `line` or `message`.
70
-
71
- ### Watcher operators
72
-
73
- `>`, `<`, `>=`, `<=`, `==`, `!=`, `contains`, `matches` (regex)
74
-
75
- ### If the daemon is not running
76
-
77
- The daemon auto-starts on setup and on MCP proxy init, and retries on failed requests. If it still fails, run:
78
- ```bash
79
- livetap start
80
- ```
63
+ 4. **ACT** — when `<channel>` alerts arrive, do what the user asked
81
64
 
82
65
  ## What it does
83
66
 
84
- livetap runs a background daemon that connects to live data sources, buffers messages in memory, and pushes alerts into your Claude Code session via the [Channels API](https://code.claude.com/docs/en/channels). Your agent sees the data in real-time and can create expression-based watchers that fire when conditions match.
67
+ LiveTap runs a background daemon that connects to live data sources, buffers messages in an in-memory StreamStore, and pushes alerts into your Claude Code session via the [Channels API](https://code.claude.com/docs/en/channels). Your agent sees the data in real-time and can create expression-based watchers that fire when conditions match.
85
68
 
86
69
  ```
87
70
  Source (MQTT/WS/File) ──> Subscriber ──> StreamStore ──> Watcher Engine
@@ -92,26 +75,37 @@ Source (MQTT/WS/File) ──> Subscriber ──> StreamStore ──> Watcher Eng
92
75
  ──> agent acts
93
76
  ```
94
77
 
78
+ ## Supported sources and data shapes
79
+
80
+ | Type | create_connection params | CLI | Payload format |
81
+ |------|------------------------|-----|----------------|
82
+ | **MQTT** | `{ type: "mqtt", broker, port, tls, topics, username?, password? }` | `livetap tap mqtt://host:port/topic/#` | JSON parsed — use dot-paths: `sensors.temperature.value` |
83
+ | **WebSocket** | `{ type: "websocket", url, headers?, handshake? }` | `livetap tap wss://...` | JSON parsed — use dot-paths: `p`, `data.value` |
84
+ | **File** | `{ type: "file", path }` | `livetap tap file:///path/to/log` | Plain text: `{ payload: "the raw line" }`. JSON lines: parsed into dot-paths |
85
+
86
+ **IMPORTANT:** always `read_stream` first to see actual field names. The field is `payload`, NOT `line` or `message`.
87
+
95
88
  ## Examples
96
89
 
97
90
  ### IoT sensor monitoring
98
91
 
99
92
  ```
100
- You: "Connect to mqtt://broker.emqx.io:1883/justinx/demo/# and watch for temperature above 25°C"
93
+ You: "Connect to mqtt://broker.emqx.io:1883/justinx/demo/# and watch for temperature above 25C"
101
94
 
102
- Agent: Creates connection, samples the data to learn the payload structure,
95
+ Agent: Connects to the free public broker, samples the data to learn the payload structure,
103
96
  sets up watcher on sensors.environmental.temperature.value > 25.
104
- When it fires, summarizes: "sensor-zone-c hit 25.4°C at 10:05:08Z"
97
+ When it fires: "sensor-zone-c hit 25.4C at 10:05:08Z"
105
98
  ```
106
99
 
107
- ### Crypto price alerts
100
+ ### WebSocket trade stream
108
101
 
109
102
  ```
110
- You: "Tap the Binance BTC/USDT trade stream and alert me if price drops below 60000"
103
+ You: "Tap the Binance BTC/USDT trade stream and log each trade"
111
104
 
112
105
  Agent: Connects to wss://stream.binance.com:9443/ws/btcusdt@trade,
113
- samples to see the price field is "p" (string),
114
- sets up watcher with regex: p matches "^[1-5]" (prices starting with 1-5 = below 60k)
106
+ samples to discover trade fields (p=price, q=quantity, T=timestamp),
107
+ sets up a watcher to log each trade. Can filter by quantity or
108
+ use regex on the symbol field.
115
109
  ```
116
110
 
117
111
  ### Log file monitoring
@@ -120,8 +114,8 @@ Agent: Connects to wss://stream.binance.com:9443/ws/btcusdt@trade,
120
114
  You: "Watch my nginx error log for 5xx errors and summarize each one"
121
115
 
122
116
  Agent: Taps file:///var/log/nginx/error.log,
123
- creates watcher: payload matches "5[0-9]{2}",
124
- when an error appears, analyzes it:
117
+ creates regex watcher: payload matches "5[0-9]{2}",
118
+ summarizes each match:
125
119
  "503 Service Unavailable on /api/data — upstream auth-service not responding"
126
120
  ```
127
121
 
@@ -131,103 +125,137 @@ Agent: Taps file:///var/log/nginx/error.log,
131
125
  You: "Monitor /var/log/wifi.log and alert me when WiFi drops"
132
126
 
133
127
  Agent: Taps the file, samples to see log format, creates regex watcher
134
- for power state changes. When you toggle WiFi:
128
+ for power state changes. Reports outage duration:
135
129
  "Wi-Fi powered OFF at 17:51:45, back ON at 17:51:47 (2s outage)"
136
130
  ```
137
131
 
132
+ ### CLI walkthrough (no agent)
133
+
134
+ You can also use LiveTap directly from the terminal:
135
+
136
+ ```bash
137
+ # 1. Tap a source
138
+ livetap tap mqtt://broker.emqx.io:1883/justinx/demo/#
139
+
140
+ # 2. Sample the data to see what's flowing
141
+ livetap sip conn_xxxx
142
+
143
+ # 3. Set up a watcher
144
+ livetap watch conn_xxxx "sensors.environmental.temperature.value > 25"
145
+
146
+ # 4. Check status
147
+ livetap status
148
+ livetap watchers --logs w_xxxx
149
+ ```
150
+
151
+ ## Expression watchers
152
+
153
+ Watchers use structured conditions:
154
+
155
+ ```json
156
+ {
157
+ "conditions": [
158
+ { "field": "sensors.temperature.value", "op": ">", "value": 50 },
159
+ { "field": "sensors.humidity.value", "op": ">", "value": 90 }
160
+ ],
161
+ "match": "all",
162
+ "cooldown": 60
163
+ }
164
+ ```
165
+
166
+ **Operators:** `>`, `<`, `>=`, `<=`, `==`, `!=`, `contains`, `matches` (regex)
167
+
168
+ **Match modes:** `"all"` = AND (all conditions must be true), `"any"` = OR (at least one)
169
+
170
+ **Cooldown:** Seconds between repeated alerts. `0` for every match, `60` default. Use 0 for rare events, 30-60 for sensors, 300+ for high-frequency streams.
171
+
172
+ When a watcher fires, the alert arrives as a `<channel>` tag in your Claude Code session. The agent reads it and acts — writing to a file, calling an API, or whatever you asked.
173
+
138
174
  ## CLI
139
175
 
140
176
  ```bash
177
+ # Setup
178
+ livetap setup # Configure .mcp.json, start daemon, print restart instructions
179
+
141
180
  # Daemon
142
181
  livetap start # Start daemon (auto-started by setup)
143
- livetap stop # Stop
144
- livetap status # Dashboard
182
+ livetap start --port 9000 # Custom port (default 8788, env: LIVETAP_PORT)
183
+ livetap start --foreground # Run in foreground (don't detach)
184
+ livetap stop # Stop daemon
185
+ livetap status # Show daemon, taps, and watchers
186
+ livetap status --json # JSON output
145
187
 
146
188
  # Tap into data sources
147
189
  livetap tap mqtt://broker.emqx.io:1883/sensors/# # MQTT broker
148
190
  livetap tap wss://stream.binance.com:9443/ws/btcusdt@trade # WebSocket
149
191
  livetap tap file:///var/log/nginx/error.log # Log file
192
+ livetap tap connection.json # Config from file
193
+ livetap tap <uri> --name "my-source" # With display name
150
194
  livetap taps # List active taps
151
- livetap untap <connectionId> # Remove
195
+ livetap untap <connectionId> # Remove a tap
152
196
 
153
197
  # Sample data
154
198
  livetap sip <connectionId> # Pretty JSON output
155
199
  livetap sip <connectionId> --raw # Raw JSON
200
+ livetap sip <connectionId> --max 20 --back 120 # 20 entries, last 120 seconds
156
201
 
157
202
  # Watchers
158
203
  livetap watch <connId> "temperature > 50" # Numeric
159
204
  livetap watch <connId> "payload matches 'ERROR|FATAL'" # Regex
160
205
  livetap watch <connId> "temp > 50 AND humidity > 90" # AND
206
+ livetap watch <connId> "temp > 50 OR smoke > 0.05" # OR
161
207
  livetap watch <connId> "price > 70000" --cooldown 300 # Custom cooldown
208
+ livetap watch <connId> "status == 'error'" --action webhook:https://... # Webhook action
162
209
  livetap watchers # List all
163
- livetap watchers --logs <watcherId> # View logs
210
+ livetap watchers <connectionId> # Filter by connection
211
+ livetap watchers <watcherId> # Show details
212
+ livetap watchers --logs <watcherId> # View evaluation logs
164
213
  livetap unwatch <watcherId> # Remove
165
214
  ```
166
215
 
167
- ## Supported sources
168
-
169
- | Protocol | Example | Use case |
170
- |----------|---------|----------|
171
- | **MQTT** | `livetap tap mqtt://broker.emqx.io:1883/sensors/#` | IoT sensors, home automation |
172
- | **WebSocket** | `livetap tap wss://stream.binance.com:9443/ws/btcusdt@trade` | Finance, real-time APIs |
173
- | **File tailing** | `livetap tap file:///var/log/nginx/error.log` | Log monitoring, DevOps |
174
- | **Webhook** | `livetap tap webhook` | CI/CD, external services |
175
- | Kafka | Planned | Event sourcing, analytics |
216
+ `livetap --help` for the full reference. `livetap --llm-help` for machine-readable JSON.
176
217
 
177
218
  ## MCP tools
178
219
 
179
- livetap exposes 13 MCP tools that your agent uses automatically:
220
+ LiveTap exposes 13 MCP tools that your agent uses automatically:
180
221
 
181
222
  | Tool | What it does |
182
223
  |------|-------------|
183
- | `create_connection` | Connect to MQTT, WebSocket, or file |
184
- | `list_connections` | List active connections |
185
- | `get_connection` | Connection details and stats |
186
- | `destroy_connection` | Remove a connection |
224
+ | `create_connection` | Connect to MQTT, WebSocket, file, or webhook |
225
+ | `list_connections` | List active connections with status and message rate |
226
+ | `get_connection` | Get detailed connection status |
227
+ | `destroy_connection` | Stop and remove a connection |
187
228
  | `read_stream` | Sample recent entries from a stream |
188
229
  | `create_watcher` | Set up expression-based alerts |
189
- | `list_watchers` | List watchers |
190
- | `get_watcher` | Watcher details |
191
- | `get_watcher_logs` | View MATCH/SUPPRESSED logs |
192
- | `update_watcher` | Change conditions or cooldown |
193
- | `delete_watcher` | Remove a watcher |
230
+ | `list_watchers` | List watchers, optionally filter by connection |
231
+ | `get_watcher` | Watcher details: conditions, status, match count |
232
+ | `get_watcher_logs` | View MATCH, SUPPRESSED, FIELD_NOT_FOUND logs |
233
+ | `update_watcher` | Change conditions, match mode, action, or cooldown |
234
+ | `delete_watcher` | Stop and remove a watcher |
194
235
  | `restart_watcher` | Restart a stopped watcher |
195
236
  | `status` | Daemon health, uptime, connections, and watchers summary |
196
237
 
197
- ## Expression watchers
198
-
199
- Watchers use structured conditions — not arbitrary code:
200
-
201
- ```json
202
- {
203
- "conditions": [
204
- { "field": "sensors.temperature.value", "op": ">", "value": 50 },
205
- { "field": "sensors.humidity.value", "op": ">", "value": 90 }
206
- ],
207
- "match": "all",
208
- "cooldown": 60
209
- }
210
- ```
211
-
212
- **Operators:** `>`, `<`, `>=`, `<=`, `==`, `!=`, `contains`, `matches` (regex)
238
+ ## Troubleshooting
213
239
 
214
- **Cooldown:** Seconds between repeated alerts. `0` for every match, `60` default.
240
+ **Daemon won't start / "Unable to connect"**
241
+ Run `livetap start --foreground` to see error output. Check if port 8788 is in use: `lsof -i :8788`. Use `--port` or `LIVETAP_PORT` env var to change.
215
242
 
216
- When a watcher fires, the alert arrives as a `<channel>` tag in your Claude Code session. The agent reads it and acts — writing to a file, calling an API, or whatever you asked.
243
+ **MQTT connection refused**
244
+ Verify the broker is reachable: `nc -zv broker.emqx.io 1883`. Check that `tls: false` and `port: 1883` are set for unencrypted brokers. Brokers on port 8883 typically require `tls: true`.
217
245
 
218
- ## How the agent uses livetap
246
+ **Watcher not firing**
247
+ Run `read_stream` (or `livetap sip`) to verify data is flowing. Check field paths match the actual payload structure. View watcher logs: `livetap watchers --logs <watcherId>` — look for FIELD_NOT_FOUND or SUPPRESSED events.
219
248
 
220
- The MCP instructions teach the agent this workflow:
249
+ **MCP tools not showing after restart**
250
+ Verify `.mcp.json` exists in the project root (not `~/.claude/mcp.json`). Restart Claude Code with the `--dangerously-load-development-channels server:livetap` flag.
221
251
 
222
- 1. **CONNECT** — `create_connection` to tap a source
223
- 2. **SAMPLE** — `read_stream` to see the data shape (always before creating watchers)
224
- 3. **WATCH** — `create_watcher` with the correct field paths
225
- 4. **ACT** — when `<channel>` alerts arrive, do what the user asked
252
+ ## Limitations
226
253
 
227
- The agent knows field paths differ by source:
228
- - **MQTT/WebSocket:** JSON payload parseduse dot-paths like `sensors.temperature.value`
229
- - **File (text):** raw line in `payload` field use `payload contains "ERROR"` or `payload matches "5[0-9]{2}"`
230
- - **File (JSON lines):** parsed use dot-paths like `level`, `msg`
254
+ - **In-memory stream buffer** data does not persist across daemon restarts.
255
+ - **No daemon API auth**the HTTP API listens on localhost only (port 8788).
256
+ - **Throughput** tested with streams up to ~50 msg/s. High-throughput streams (1000+ msg/s) may need higher cooldowns on watchers.
257
+ - **Credentials** MQTT credentials are passed as tool parameters, not stored on disk.
258
+ - **Single instance** — one daemon per port. Multiple projects can share a daemon or use different ports.
231
259
 
232
260
  ## Configuration
233
261
 
@@ -235,19 +263,9 @@ The agent knows field paths differ by source:
235
263
 
236
264
  **State directory:** `~/.livetap/` stores `daemon.pid`, daemon logs, and watcher evaluation logs.
237
265
 
238
- **MCP config:** `.mcp.json` in your project root:
239
- ```json
240
- {
241
- "mcpServers": {
242
- "livetap": {
243
- "command": "bun",
244
- "args": ["path/to/src/mcp/channel.ts"]
245
- }
246
- }
247
- }
248
- ```
266
+ **MCP config:** `npx livetap setup` generates `.mcp.json` in your project root with the correct absolute path.
249
267
 
250
- **Machine-readable help:** `livetap --llm-help` outputs structured JSON for AI agents.
268
+ **Machine-readable help:** `npx livetap --llm-help` outputs structured JSON with setup steps, CLI commands, and MCP tool schemas.
251
269
 
252
270
  ## Development
253
271
 
@@ -255,22 +273,14 @@ The agent knows field paths differ by source:
255
273
  git clone https://github.com/livetap/livetap.git
256
274
  cd livetap
257
275
  bun install
258
- bun test # 121 tests
259
- bun test tests/phase1/ # Specific phase
260
- SKIP_LIVE_MQTT=1 bun test # Skip tests needing broker.emqx.io
276
+ bun test # Run all tests
277
+ bun test tests/phase0/ # Canonical drift detection
278
+ SKIP_LIVE_MQTT=1 bun test # Skip tests needing external brokers
261
279
  ```
262
280
 
263
- See [docs/PLAN.md](docs/PLAN.md) for the full build plan with phased architecture.
264
-
265
281
  ## Contributing
266
282
 
267
- 1. Fork and clone
268
- 2. `bun install && bun test`
269
- 3. Make changes, add tests
270
- 4. `bun test` must pass
271
- 5. PR to `main` branch
272
-
273
- See [docs/PLAN.md](docs/PLAN.md) for architecture and module layout.
283
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for setup, architecture, testing, and PR guidelines.
274
284
 
275
285
  ## License
276
286
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "livetap",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Push live data streams into your AI coding agent. LLM/AI agent? Run: npx livetap --llm-help",
5
5
  "type": "module",
6
6
  "bin": {
@@ -10,9 +10,20 @@
10
10
  "test": "bun test"
11
11
  },
12
12
  "keywords": [
13
- "mqtt", "kafka", "websocket", "webhook", "streaming",
14
- "real-time", "monitoring", "alerts", "mcp", "claude-code",
15
- "ai-agent", "iot", "observability", "data-pipeline"
13
+ "mqtt",
14
+ "kafka",
15
+ "websocket",
16
+ "webhook",
17
+ "streaming",
18
+ "real-time",
19
+ "monitoring",
20
+ "alerts",
21
+ "mcp",
22
+ "claude-code",
23
+ "ai-agent",
24
+ "iot",
25
+ "observability",
26
+ "data-pipeline"
16
27
  ],
17
28
  "license": "MIT",
18
29
  "repository": {
package/src/mcp/tools.ts CHANGED
@@ -3,181 +3,14 @@
3
3
  * Registers tools on an MCP Server that proxy to the daemon HTTP API.
4
4
  */
5
5
 
6
- import { z } from 'zod'
7
6
  import type { Server } from '@modelcontextprotocol/sdk/server/index.js'
8
7
  import {
9
8
  ListToolsRequestSchema,
10
9
  CallToolRequestSchema,
11
10
  } from '@modelcontextprotocol/sdk/types.js'
12
11
 
13
- export const TOOLS = [
14
- {
15
- name: 'create_connection',
16
- description: 'Create a data connection. MQTT: connects to a broker and subscribes to topics. WebSocket: connects to a remote WS URL. Webhook: creates an HTTP ingest endpoint.',
17
- inputSchema: {
18
- type: 'object' as const,
19
- properties: {
20
- type: { type: 'string', enum: ['mqtt', 'webhook', 'websocket', 'file'], description: 'Source type', default: 'mqtt' },
21
- name: { type: 'string', description: 'Display name for the connection' },
22
- broker: { type: 'string', description: 'MQTT broker hostname (required for mqtt)' },
23
- port: { type: 'number', description: 'Broker port (default 1883 for mqtt)', default: 1883 },
24
- tls: { type: 'boolean', description: 'Use TLS (default false)', default: false },
25
- username: { type: 'string', description: 'MQTT username', default: '' },
26
- password: { type: 'string', description: 'MQTT password', default: '' },
27
- topics: { type: 'array', items: { type: 'string' }, description: 'MQTT topic filters (required for mqtt)' },
28
- url: { type: 'string', description: 'WebSocket URL (required for websocket)' },
29
- headers: { type: 'object', description: 'WebSocket auth headers' },
30
- handshake: { type: 'string', description: 'Message to send after WS connect (e.g. subscription JSON)' },
31
- path: { type: 'string', description: 'Absolute file path to tail (required for file type, e.g. "/var/log/app.log")' },
32
- },
33
- },
34
- },
35
- {
36
- name: 'list_connections',
37
- description: 'List all active connections with their status, message rate, and buffered count.',
38
- inputSchema: { type: 'object' as const, properties: {} },
39
- },
40
- {
41
- name: 'get_connection',
42
- description: 'Get detailed status of a specific connection.',
43
- inputSchema: {
44
- type: 'object' as const,
45
- properties: {
46
- connectionId: { type: 'string', description: 'The connection ID (e.g. "conn_a1b2c3d4")' },
47
- },
48
- required: ['connectionId'],
49
- },
50
- },
51
- {
52
- name: 'destroy_connection',
53
- description: 'Destroy a connection — stops the source subscriber, cleans up the stream buffer.',
54
- inputSchema: {
55
- type: 'object' as const,
56
- properties: {
57
- connectionId: { type: 'string', description: 'The connection ID to destroy' },
58
- },
59
- required: ['connectionId'],
60
- },
61
- },
62
- {
63
- name: 'read_stream',
64
- description: "Read recent entries from a connection's live stream. Use this to inspect what data is flowing through a connection and understand the payload structure before creating watchers.",
65
- inputSchema: {
66
- type: 'object' as const,
67
- properties: {
68
- connectionId: { type: 'string', description: 'The connection ID to read from' },
69
- backfillSeconds: { type: 'number', description: 'Include entries from the last N seconds (default 60)', default: 60 },
70
- maxEntries: { type: 'number', description: 'Max entries to return (default 10)', default: 10 },
71
- },
72
- required: ['connectionId'],
73
- },
74
- },
75
- {
76
- name: 'create_watcher',
77
- description: 'Create an expression-based watcher on a connection. The watcher evaluates structured conditions against each stream entry and fires an alert when conditions match. ALWAYS use read_stream first to understand the data shape and field paths.',
78
- inputSchema: {
79
- type: 'object' as const,
80
- properties: {
81
- connectionId: { type: 'string', description: 'The connection ID to watch' },
82
- conditions: {
83
- type: 'array',
84
- description: 'Array of conditions: [{field: "dot.path", op: ">", value: 50}]. Supported ops: >, <, >=, <=, ==, !=, contains, matches (regex)',
85
- items: {
86
- type: 'object',
87
- properties: {
88
- field: { type: 'string', description: 'Dot-path into the JSON payload (e.g. "sensors.temperature.value")' },
89
- op: { type: 'string', enum: ['>', '<', '>=', '<=', '==', '!=', 'contains', 'matches'], description: 'Comparison operator. "matches" takes a regex pattern string.' },
90
- value: { description: 'Value to compare against (number, string, or boolean)' },
91
- },
92
- required: ['field', 'op', 'value'],
93
- },
94
- },
95
- match: { type: 'string', enum: ['all', 'any'], description: 'How to combine conditions: "all" (AND) or "any" (OR). Default: "all"', default: 'all' },
96
- action: { description: '"channel_alert" (default), or {webhook: "url"}, or {shell: "command"}', default: 'channel_alert' },
97
- cooldown: { type: 'number', description: 'Seconds between repeated alerts. 0 for every match, 60 default. Use 0 for rare events (webhooks), 30-60 for sensors, 300+ for high-frequency streams.', default: 60 },
98
- },
99
- required: ['connectionId', 'conditions'],
100
- },
101
- },
102
- {
103
- name: 'list_watchers',
104
- description: 'List watchers. Optionally filter by connectionId. If omitted, lists all watchers.',
105
- inputSchema: {
106
- type: 'object' as const,
107
- properties: {
108
- connectionId: { type: 'string', description: 'Optional: filter by connection ID' },
109
- },
110
- },
111
- },
112
- {
113
- name: 'get_watcher',
114
- description: 'Get details of a specific watcher including conditions, status, match count, and config.',
115
- inputSchema: {
116
- type: 'object' as const,
117
- properties: {
118
- watcherId: { type: 'string', description: 'The watcher ID' },
119
- },
120
- required: ['watcherId'],
121
- },
122
- },
123
- {
124
- name: 'get_watcher_logs',
125
- description: 'Get evaluation logs from a watcher — shows MATCH, SUPPRESSED, FIELD_NOT_FOUND, and CHECKPOINT events.',
126
- inputSchema: {
127
- type: 'object' as const,
128
- properties: {
129
- watcherId: { type: 'string', description: 'The watcher ID' },
130
- lines: { type: 'number', description: 'Number of log lines to return (default 50)', default: 50 },
131
- },
132
- required: ['watcherId'],
133
- },
134
- },
135
- {
136
- name: 'update_watcher',
137
- description: "Update a watcher's conditions, match mode, action, or cooldown. The watcher restarts with the new config.",
138
- inputSchema: {
139
- type: 'object' as const,
140
- properties: {
141
- watcherId: { type: 'string', description: 'The watcher ID' },
142
- conditions: { type: 'array', description: 'New conditions array', items: { type: 'object' } },
143
- match: { type: 'string', enum: ['all', 'any'] },
144
- action: { description: 'New action' },
145
- cooldown: { type: 'number', description: 'New cooldown in seconds' },
146
- },
147
- required: ['watcherId'],
148
- },
149
- },
150
- {
151
- name: 'delete_watcher',
152
- description: 'Stop and remove a watcher.',
153
- inputSchema: {
154
- type: 'object' as const,
155
- properties: {
156
- watcherId: { type: 'string', description: 'The watcher ID to delete' },
157
- },
158
- required: ['watcherId'],
159
- },
160
- },
161
- {
162
- name: 'restart_watcher',
163
- description: 'Restart a stopped watcher.',
164
- inputSchema: {
165
- type: 'object' as const,
166
- properties: {
167
- watcherId: { type: 'string', description: 'The watcher ID to restart' },
168
- },
169
- required: ['watcherId'],
170
- },
171
- },
172
- {
173
- name: 'status',
174
- description: 'Get daemon status: uptime, port, active connections and watchers count.',
175
- inputSchema: {
176
- type: 'object' as const,
177
- properties: {},
178
- },
179
- },
180
- ]
12
+ export { TOOLS } from '../shared/canonical/tools.js'
13
+ import { TOOLS } from '../shared/canonical/tools.js'
181
14
 
182
15
  function text(content: string) {
183
16
  return { content: [{ type: 'text' as const, text: content }] }