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.
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Canonical CLI command definitions.
3
+ * This is the single source of truth for all CLI commands.
4
+ *
5
+ * Consumed by:
6
+ * - catalog-generators.ts → --help, --llm-help
7
+ * - drift detection tests → cross-reference validation
8
+ */
9
+
10
+ export interface CatalogCommand {
11
+ name: string
12
+ usage: string
13
+ description: string
14
+ args?: { position: number; name: string; required: boolean; description?: string }[]
15
+ flags?: { name: string; type: string; default?: unknown; description: string }[]
16
+ examples?: string[]
17
+ }
18
+
19
+ export const CLI_COMMANDS: CatalogCommand[] = [
20
+ // --- Setup ---
21
+ {
22
+ name: 'setup',
23
+ usage: 'livetap setup',
24
+ description: 'Configure .mcp.json, start the daemon, and print restart instructions. Run this after npm install.',
25
+ },
26
+ // --- Daemon ---
27
+ {
28
+ name: 'start',
29
+ usage: 'livetap start',
30
+ description: 'Start the livetap daemon (HTTP API)',
31
+ flags: [
32
+ { name: '--port', type: 'number', default: 8788, description: 'Daemon port (env: LIVETAP_PORT)' },
33
+ { name: '--foreground', type: 'boolean', description: 'Run in foreground (don\'t detach)' },
34
+ ],
35
+ },
36
+ {
37
+ name: 'stop',
38
+ usage: 'livetap stop',
39
+ description: 'Stop the daemon',
40
+ },
41
+ {
42
+ name: 'status',
43
+ usage: 'livetap status',
44
+ description: 'Show daemon, taps, and watchers',
45
+ flags: [
46
+ { name: '--json', type: 'boolean', description: 'Output as JSON' },
47
+ ],
48
+ },
49
+ // --- Connections ---
50
+ {
51
+ name: 'tap',
52
+ usage: 'livetap tap <uri|file.json>',
53
+ description: 'Tap into a data source (MQTT, WebSocket, file, or webhook)',
54
+ args: [
55
+ { position: 0, name: 'source', required: true, description: 'URI (mqtt://..., wss://..., file:///path), "webhook", or a .json config file' },
56
+ ],
57
+ flags: [
58
+ { name: '--name', type: 'string', description: 'Display name for the connection' },
59
+ ],
60
+ examples: [
61
+ 'livetap tap mqtt://broker.emqx.io:1883/sensors/#',
62
+ 'livetap tap wss://stream.example.com/prices',
63
+ 'livetap tap file:///var/log/nginx/error.log',
64
+ 'livetap tap webhook',
65
+ 'livetap tap connection.json',
66
+ ],
67
+ },
68
+ {
69
+ name: 'untap',
70
+ usage: 'livetap untap <connectionId>',
71
+ description: 'Remove a tap',
72
+ args: [
73
+ { position: 0, name: 'connectionId', required: true },
74
+ ],
75
+ },
76
+ {
77
+ name: 'taps',
78
+ usage: 'livetap taps',
79
+ description: 'List active taps',
80
+ flags: [
81
+ { name: '--json', type: 'boolean', description: 'Output as JSON' },
82
+ ],
83
+ },
84
+ // --- Sampling ---
85
+ {
86
+ name: 'sip',
87
+ usage: 'livetap sip <connectionId>',
88
+ description: 'Sip from a stream (sample recent entries as pretty JSON)',
89
+ args: [
90
+ { position: 0, name: 'connectionId', required: true },
91
+ ],
92
+ flags: [
93
+ { name: '--max', type: 'number', default: 10, description: 'Max entries to return' },
94
+ { name: '--back', type: 'number', default: 60, description: 'Backfill seconds' },
95
+ { name: '--raw', type: 'boolean', description: 'Output raw JSON' },
96
+ ],
97
+ },
98
+ // --- Watchers ---
99
+ {
100
+ name: 'watch',
101
+ usage: 'livetap watch <connectionId> "expression"',
102
+ description: 'Create an expression-based watcher',
103
+ args: [
104
+ { position: 0, name: 'connectionId', required: true },
105
+ { position: 1, name: 'expression', required: true, description: '"field > value", supports AND/OR' },
106
+ ],
107
+ flags: [
108
+ { name: '--cooldown', type: 'number', default: 60, description: 'Seconds between repeated alerts (0 = every match)' },
109
+ { name: '--action', type: 'string', description: '"channel_alert" (default), "webhook:URL", or "shell:command"' },
110
+ ],
111
+ examples: [
112
+ 'livetap watch conn_abc "temperature > 50"',
113
+ 'livetap watch conn_abc "temp > 50 AND humidity > 90"',
114
+ 'livetap watch conn_abc "temp > 50 OR smoke > 0.05"',
115
+ 'livetap watch conn_abc "price > 70000" --cooldown 300',
116
+ ],
117
+ },
118
+ {
119
+ name: 'unwatch',
120
+ usage: 'livetap unwatch <watcherId>',
121
+ description: 'Remove a watcher',
122
+ args: [
123
+ { position: 0, name: 'watcherId', required: true },
124
+ ],
125
+ },
126
+ {
127
+ name: 'watchers',
128
+ usage: 'livetap watchers [connectionId|watcherId]',
129
+ description: 'List watchers, show watcher details, or view watcher logs',
130
+ args: [
131
+ { position: 0, name: 'connectionId or watcherId', required: false, description: 'Filter by connection (conn_xxx) or show details for a watcher (w_xxx)' },
132
+ ],
133
+ flags: [
134
+ { name: '--json', type: 'boolean', description: 'Output as JSON' },
135
+ { name: '--logs', type: 'string', description: 'Show evaluation logs for a watcher ID' },
136
+ ],
137
+ examples: [
138
+ 'livetap watchers',
139
+ 'livetap watchers conn_abc',
140
+ 'livetap watchers w_abc',
141
+ 'livetap watchers --logs w_abc',
142
+ ],
143
+ },
144
+ ]
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Canonical data barrel — THE single import point for all canonical data.
3
+ *
4
+ * Usage:
5
+ * import { TOOLS, CLI_COMMANDS, META } from '../canonical/index.js'
6
+ */
7
+
8
+ export { TOOLS } from './tools.js'
9
+ export { CLI_COMMANDS, type CatalogCommand } from './cli.js'
10
+ export { META } from './meta.js'
@@ -0,0 +1,222 @@
1
+ /**
2
+ * Canonical metadata — everything that isn't tool schemas or CLI commands.
3
+ * This is the single source of truth for descriptions, operators, source types,
4
+ * examples, do-not rules, tips, data shapes, and generation specs.
5
+ *
6
+ * Consumed by:
7
+ * - catalog-generators.ts → --help, --llm-help, MCP instructions
8
+ * - /lt-push skill → README + CONTRIBUTING generation
9
+ * - drift detection tests → cross-reference validation
10
+ * - package.json → description field (synced by /lt-push)
11
+ */
12
+
13
+ export const META = {
14
+ name: 'LiveTap',
15
+ npmName: 'livetap',
16
+ description: 'Push live data streams into your AI coding agent',
17
+ npmDescription: 'Push live data streams into your AI coding agent. LLM/AI agent? Run: npx livetap --llm-help',
18
+ repo: 'https://github.com/livetap/livetap',
19
+ license: 'MIT',
20
+
21
+ channelsExplainer: 'Claude Code 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.',
22
+
23
+ demoImages: [
24
+ {
25
+ path: 'docs/images/demo-setup.png',
26
+ alt: '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.',
27
+ caption: 'Real solar inverter data — from natural language request to live alert in under 30 seconds',
28
+ },
29
+ {
30
+ path: 'docs/images/demo-alert.png',
31
+ alt: '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.',
32
+ caption: 'Escalating alerts — the agent compares values across alerts and analyzes trends autonomously',
33
+ },
34
+ ],
35
+
36
+ sourceTypes: [
37
+ {
38
+ type: 'mqtt',
39
+ params: '{ type: "mqtt", broker, port, tls, topics, username?, password? }',
40
+ cli: 'mqtt://host:port/topic/#',
41
+ useCase: 'IoT sensors, home automation',
42
+ },
43
+ {
44
+ type: 'websocket',
45
+ params: '{ type: "websocket", url, headers?, handshake? }',
46
+ cli: 'wss://...',
47
+ useCase: 'Finance, real-time APIs',
48
+ },
49
+ {
50
+ type: 'file',
51
+ params: '{ type: "file", path }',
52
+ cli: 'file:///path/to/log',
53
+ useCase: 'Log monitoring, DevOps',
54
+ },
55
+ {
56
+ type: 'webhook',
57
+ params: '{ type: "webhook" }',
58
+ cli: 'webhook',
59
+ useCase: 'CI/CD, external services',
60
+ status: 'built-deferred' as const,
61
+ },
62
+ ],
63
+
64
+ operators: ['>', '<', '>=', '<=', '==', '!=', 'contains', 'matches'] as const,
65
+
66
+ doNotRules: [
67
+ 'Do NOT add livetap to ~/.claude/mcp.json — it goes in .mcp.json in the project root',
68
+ 'Do NOT configure livetap as type:http — it is a stdio MCP server (command + args)',
69
+ 'Do NOT worry about the daemon — setup starts it, and the MCP proxy auto-starts it if needed',
70
+ 'Do NOT guess field names — always read_stream first to see actual payload structure',
71
+ 'Do NOT use npm init — use the existing project directory',
72
+ ],
73
+
74
+ dataShapes: [
75
+ {
76
+ source: 'MQTT/WebSocket',
77
+ format: 'JSON',
78
+ description: 'entries have { payload: "{...json...}", topic: "..." }. The payload is parsed as JSON.',
79
+ usage: 'Use dot-paths into the parsed JSON: "sensors.temperature.value", "metadata.device_name"',
80
+ },
81
+ {
82
+ source: 'File',
83
+ format: 'plain text',
84
+ description: 'entries have { payload: "the raw line", format: "text" }.',
85
+ usage: 'Use field "payload" with contains/matches: { field: "payload", op: "contains", value: "ERROR" }',
86
+ },
87
+ {
88
+ source: 'File',
89
+ format: 'JSON lines',
90
+ description: 'entries have { payload: "{...json...}", format: "json" }. Parsed as JSON.',
91
+ usage: 'Use dot-paths like MQTT: "level", "msg", "status"',
92
+ },
93
+ ],
94
+
95
+ tips: [
96
+ 'The daemon auto-starts when needed. If a tool returns "daemon was restarted", just retry your request.',
97
+ 'Watcher IDs (w_xxx) are globally unique. You don\'t need the connectionId to get, update, or delete a watcher.',
98
+ 'Common MQTT brokers: broker.emqx.io (public demo), test.mosquitto.org (public test).',
99
+ 'For regex watchers, use the "matches" operator: { field: "payload", op: "matches", value: "ERROR|FATAL" }',
100
+ 'If a field path doesn\'t exist in the payload, the condition evaluates to false (no crash, no error).',
101
+ 'Fields with dots in the key name (like OBIS codes "2.8.0") are looked up as literal keys first, then as dot-paths.',
102
+ ],
103
+
104
+ setupSteps: [
105
+ 'Step 1: npm install livetap (skip if already installed)',
106
+ 'Step 2: npx livetap setup (creates .mcp.json and starts the daemon — do NOT create .mcp.json manually)',
107
+ 'Step 3: Tell user to restart Claude Code: claude --dangerously-load-development-channels server:livetap --continue',
108
+ 'Step 4: After restart, use create_connection to connect, read_stream to sample, create_watcher to alert',
109
+ ],
110
+
111
+ examples: [
112
+ {
113
+ title: 'IoT sensor monitoring',
114
+ sourceType: 'mqtt' as const,
115
+ url: 'mqtt://broker.emqx.io:1883/justinx/demo/#',
116
+ publicBroker: true,
117
+ fieldPaths: ['sensors.environmental.temperature.value'],
118
+ condition: 'temperature > 25',
119
+ narrative: 'Agent connects to the free public broker, samples payload structure, sets watcher on sensors.environmental.temperature.value > 25. Fires: "sensor-zone-c hit 25.4C at 10:05:08Z"',
120
+ },
121
+ {
122
+ title: 'WebSocket trade stream',
123
+ sourceType: 'websocket' as const,
124
+ url: 'wss://stream.binance.com:9443/ws/btcusdt@trade',
125
+ fieldPaths: ['p', 'q', 'T'],
126
+ condition: 'payload contains "btcusdt"',
127
+ narrative: 'Agent connects, samples to discover trade fields (p=price, q=quantity, T=timestamp), sets up a watcher to log each trade. Can filter by quantity or use regex on the symbol field.',
128
+ },
129
+ {
130
+ title: 'Log file monitoring',
131
+ sourceType: 'file' as const,
132
+ url: 'file:///var/log/nginx/error.log',
133
+ fieldPaths: ['payload'],
134
+ condition: 'payload matches "5[0-9]{2}"',
135
+ narrative: 'Agent tails error log, creates regex watcher for 5xx errors, summarizes each match: "503 Service Unavailable on /api/data — upstream auth-service not responding"',
136
+ },
137
+ {
138
+ title: 'WiFi disconnect detection',
139
+ sourceType: 'file' as const,
140
+ url: 'file:///var/log/wifi.log',
141
+ fieldPaths: ['payload'],
142
+ condition: 'payload matches "power state changed"',
143
+ narrative: 'Agent monitors WiFi log for power state changes, reports outage duration: "Wi-Fi powered OFF at 17:51:45, back ON at 17:51:47 (2s outage)"',
144
+ },
145
+ ],
146
+
147
+ alertGuidance: [
148
+ 'When a watcher alert fires, you receive the FULL stream entry — all fields, not just the matched condition.',
149
+ 'Analyze the data: format it clearly (tables work well), explain what the values mean, identify what triggered the alert.',
150
+ 'Compare across alerts: if you have seen previous alerts from the same watcher, compare values and note trends (e.g. "consumption increased by 174W since last alert").',
151
+ 'Be proactive: if values are escalating, say so. If something looks anomalous compared to earlier samples, flag it.',
152
+ 'Include context: device IDs, timestamps, related fields — not just the field that matched the condition.',
153
+ ],
154
+
155
+ troubleshooting: [
156
+ {
157
+ problem: 'Daemon won\'t start / "Unable to connect"',
158
+ solution: 'Run `livetap start --foreground` to see error output. Check if port 8788 is in use: `lsof -i :8788`. Use `--port` or `LIVETAP_PORT` to change.',
159
+ },
160
+ {
161
+ problem: 'MQTT connection refused',
162
+ solution: '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`.',
163
+ },
164
+ {
165
+ problem: 'Watcher not firing',
166
+ solution: '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.',
167
+ },
168
+ {
169
+ problem: 'MCP tools not showing after restart',
170
+ solution: 'Verify `.mcp.json` exists in the project root (not `~/.claude/mcp.json`). Restart Claude Code with the `--dangerously-load-development-channels server:livetap` flag.',
171
+ },
172
+ ],
173
+
174
+ limitations: [
175
+ 'In-memory stream buffer — data does not persist across daemon restarts.',
176
+ 'No authentication on the daemon HTTP API (localhost only, port 8788).',
177
+ 'Tested with streams up to ~50 msg/s. High-throughput streams (1000+ msg/s) may need higher cooldowns on watchers.',
178
+ 'MQTT credentials are passed as tool parameters, not stored on disk.',
179
+ 'Single daemon instance per port. Multiple projects can share a daemon or use different ports.',
180
+ ],
181
+
182
+ architectureDiagram: `Source (MQTT/WS/File) ──> Subscriber ──> StreamStore ──> Watcher Engine
183
+ | |
184
+ v v (on match)
185
+ read_stream Channel Alert
186
+ (agent samples) ──> Claude Code
187
+ ──> agent acts`,
188
+
189
+ readmeSpec: {
190
+ audience: 'Developers and AI coding agents (Claude Code, Codex, etc.)',
191
+ tone: 'Direct, practical, no fluff. Show don\'t tell.',
192
+ sections: [
193
+ { id: 'tagline', notes: 'One-liner + 1 sentence expansion. No badges yet.' },
194
+ { id: 'hero-demo', notes: 'Both demo screenshots immediately after tagline. Use demoImages from canonical. Each with alt text and caption. This is the wow moment — show before explaining.' },
195
+ { id: 'quick-start', notes: 'Use npm (not bun) for quickstart — universal. npx livetap setup, then restart Claude. Explain what --dangerously-load-development-channels does using channelsExplainer. Show one example prompt AND expected outcome.' },
196
+ { id: 'agent-setup', notes: 'For AI agents reading this. npx livetap --llm-help pointer. Quick steps. do-not rules. After-restart workflow. Mention MCP tools auto-register on restart — no discovery step needed.' },
197
+ { id: 'what-it-does', notes: 'Architecture paragraph + ASCII flow diagram. Mention daemon, in-memory StreamStore, Channels API.' },
198
+ { id: 'source-types-and-data', notes: 'MERGED section: source types table with data shape info inline. Each row shows type, params, CLI, and how the payload looks. End with IMPORTANT: always read_stream first.' },
199
+ { id: 'examples', notes: 'One subsection per example from canonical data. Show user prompt + agent behavior. Include one CLI-only example (livetap tap, sip, watch sequence) for people who want to verify manually before handing to the agent.' },
200
+ { id: 'expression-watchers', notes: 'JSON example, ALL operators listed here (single location, not repeated), cooldown guidance, how alerts arrive as channel tags.' },
201
+ { id: 'cli', notes: 'Full CLI reference from canonical CLI commands. Group by function. Include all flags.' },
202
+ { id: 'mcp-tools', notes: 'Table of all MCP tools from canonical data.' },
203
+ { id: 'troubleshooting', notes: 'Top problems and solutions from canonical troubleshooting array.' },
204
+ { id: 'limitations', notes: 'Upfront about what this tool does NOT do. From canonical limitations array.' },
205
+ { id: 'config', notes: 'Daemon port, state directory, MCP config (.mcp.json), --llm-help.' },
206
+ { id: 'development', notes: 'Clone, install, test.' },
207
+ { id: 'contributing', notes: 'Brief or link to CONTRIBUTING.md.' },
208
+ { id: 'license', notes: 'MIT.' },
209
+ ],
210
+ },
211
+
212
+ contributingSpec: {
213
+ audience: 'Contributors',
214
+ tone: 'Concise, welcoming.',
215
+ sections: [
216
+ { id: 'setup', notes: 'Clone, bun install, bun test.' },
217
+ { id: 'architecture', notes: 'Brief overview, link to docs/PLAN.md for details.' },
218
+ { id: 'testing', notes: 'How to run tests, port ranges (28xxx), SKIP_LIVE_MQTT.' },
219
+ { id: 'pr-process', notes: 'Fork, branch, test, PR to v0.' },
220
+ ],
221
+ },
222
+ }
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Canonical MCP tool definitions.
3
+ * This is the single source of truth for all tool schemas.
4
+ *
5
+ * Consumed by:
6
+ * - src/mcp/tools.ts → tool registration + handler dispatch
7
+ * - catalog-generators.ts → --llm-help, MCP instructions
8
+ * - drift detection tests → cross-reference validation
9
+ */
10
+
11
+ export const TOOLS = [
12
+ {
13
+ name: 'create_connection',
14
+ 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.',
15
+ inputSchema: {
16
+ type: 'object' as const,
17
+ properties: {
18
+ type: { type: 'string', enum: ['mqtt', 'webhook', 'websocket', 'file'], description: 'Source type', default: 'mqtt' },
19
+ name: { type: 'string', description: 'Display name for the connection' },
20
+ broker: { type: 'string', description: 'MQTT broker hostname (required for mqtt)' },
21
+ port: { type: 'number', description: 'Broker port (default 1883 for mqtt)', default: 1883 },
22
+ tls: { type: 'boolean', description: 'Use TLS (default false)', default: false },
23
+ username: { type: 'string', description: 'MQTT username', default: '' },
24
+ password: { type: 'string', description: 'MQTT password', default: '' },
25
+ topics: { type: 'array', items: { type: 'string' }, description: 'MQTT topic filters (required for mqtt)' },
26
+ url: { type: 'string', description: 'WebSocket URL (required for websocket)' },
27
+ headers: { type: 'object', description: 'WebSocket auth headers' },
28
+ handshake: { type: 'string', description: 'Message to send after WS connect (e.g. subscription JSON)' },
29
+ path: { type: 'string', description: 'Absolute file path to tail (required for file type, e.g. "/var/log/app.log")' },
30
+ },
31
+ },
32
+ },
33
+ {
34
+ name: 'list_connections',
35
+ description: 'List all active connections with their status, message rate, and buffered count.',
36
+ inputSchema: { type: 'object' as const, properties: {} },
37
+ },
38
+ {
39
+ name: 'get_connection',
40
+ description: 'Get detailed status of a specific connection.',
41
+ inputSchema: {
42
+ type: 'object' as const,
43
+ properties: {
44
+ connectionId: { type: 'string', description: 'The connection ID (e.g. "conn_a1b2c3d4")' },
45
+ },
46
+ required: ['connectionId'],
47
+ },
48
+ },
49
+ {
50
+ name: 'destroy_connection',
51
+ description: 'Destroy a connection — stops the source subscriber, cleans up the stream buffer.',
52
+ inputSchema: {
53
+ type: 'object' as const,
54
+ properties: {
55
+ connectionId: { type: 'string', description: 'The connection ID to destroy' },
56
+ },
57
+ required: ['connectionId'],
58
+ },
59
+ },
60
+ {
61
+ name: 'read_stream',
62
+ 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.",
63
+ inputSchema: {
64
+ type: 'object' as const,
65
+ properties: {
66
+ connectionId: { type: 'string', description: 'The connection ID to read from' },
67
+ backfillSeconds: { type: 'number', description: 'Include entries from the last N seconds (default 60)', default: 60 },
68
+ maxEntries: { type: 'number', description: 'Max entries to return (default 10)', default: 10 },
69
+ },
70
+ required: ['connectionId'],
71
+ },
72
+ },
73
+ {
74
+ name: 'create_watcher',
75
+ 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.',
76
+ inputSchema: {
77
+ type: 'object' as const,
78
+ properties: {
79
+ connectionId: { type: 'string', description: 'The connection ID to watch' },
80
+ conditions: {
81
+ type: 'array',
82
+ description: 'Array of conditions: [{field: "dot.path", op: ">", value: 50}]. Supported ops: >, <, >=, <=, ==, !=, contains, matches (regex)',
83
+ items: {
84
+ type: 'object',
85
+ properties: {
86
+ field: { type: 'string', description: 'Dot-path into the JSON payload (e.g. "sensors.temperature.value")' },
87
+ op: { type: 'string', enum: ['>', '<', '>=', '<=', '==', '!=', 'contains', 'matches'], description: 'Comparison operator. "matches" takes a regex pattern string.' },
88
+ value: { description: 'Value to compare against (number, string, or boolean)' },
89
+ },
90
+ required: ['field', 'op', 'value'],
91
+ },
92
+ },
93
+ match: { type: 'string', enum: ['all', 'any'], description: 'How to combine conditions: "all" (AND) or "any" (OR). Default: "all"', default: 'all' },
94
+ action: { description: '"channel_alert" (default), or {webhook: "url"}, or {shell: "command"}', default: 'channel_alert' },
95
+ 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 },
96
+ },
97
+ required: ['connectionId', 'conditions'],
98
+ },
99
+ },
100
+ {
101
+ name: 'list_watchers',
102
+ description: 'List watchers. Optionally filter by connectionId. If omitted, lists all watchers.',
103
+ inputSchema: {
104
+ type: 'object' as const,
105
+ properties: {
106
+ connectionId: { type: 'string', description: 'Optional: filter by connection ID' },
107
+ },
108
+ },
109
+ },
110
+ {
111
+ name: 'get_watcher',
112
+ description: 'Get details of a specific watcher including conditions, status, match count, and config.',
113
+ inputSchema: {
114
+ type: 'object' as const,
115
+ properties: {
116
+ watcherId: { type: 'string', description: 'The watcher ID' },
117
+ },
118
+ required: ['watcherId'],
119
+ },
120
+ },
121
+ {
122
+ name: 'get_watcher_logs',
123
+ description: 'Get evaluation logs from a watcher — shows MATCH, SUPPRESSED, FIELD_NOT_FOUND, and CHECKPOINT events.',
124
+ inputSchema: {
125
+ type: 'object' as const,
126
+ properties: {
127
+ watcherId: { type: 'string', description: 'The watcher ID' },
128
+ lines: { type: 'number', description: 'Number of log lines to return (default 50)', default: 50 },
129
+ },
130
+ required: ['watcherId'],
131
+ },
132
+ },
133
+ {
134
+ name: 'update_watcher',
135
+ description: "Update a watcher's conditions, match mode, action, or cooldown. The watcher restarts with the new config.",
136
+ inputSchema: {
137
+ type: 'object' as const,
138
+ properties: {
139
+ watcherId: { type: 'string', description: 'The watcher ID' },
140
+ conditions: { type: 'array', description: 'New conditions array', items: { type: 'object' } },
141
+ match: { type: 'string', enum: ['all', 'any'] },
142
+ action: { description: 'New action' },
143
+ cooldown: { type: 'number', description: 'New cooldown in seconds' },
144
+ },
145
+ required: ['watcherId'],
146
+ },
147
+ },
148
+ {
149
+ name: 'delete_watcher',
150
+ description: 'Stop and remove a watcher.',
151
+ inputSchema: {
152
+ type: 'object' as const,
153
+ properties: {
154
+ watcherId: { type: 'string', description: 'The watcher ID to delete' },
155
+ },
156
+ required: ['watcherId'],
157
+ },
158
+ },
159
+ {
160
+ name: 'restart_watcher',
161
+ description: 'Restart a stopped watcher.',
162
+ inputSchema: {
163
+ type: 'object' as const,
164
+ properties: {
165
+ watcherId: { type: 'string', description: 'The watcher ID to restart' },
166
+ },
167
+ required: ['watcherId'],
168
+ },
169
+ },
170
+ {
171
+ name: 'status',
172
+ description: 'Get daemon status: uptime, port, active connections and watchers count.',
173
+ inputSchema: {
174
+ type: 'object' as const,
175
+ properties: {},
176
+ },
177
+ },
178
+ ]