@delt/claude-alarm 0.1.0
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 +166 -0
- package/dist/channel/server.js +333 -0
- package/dist/channel/server.js.map +1 -0
- package/dist/cli.js +921 -0
- package/dist/cli.js.map +1 -0
- package/dist/dashboard/index.html +580 -0
- package/dist/hub/server.js +512 -0
- package/dist/hub/server.js.map +1 -0
- package/dist/index.d.ts +196 -0
- package/dist/index.js +696 -0
- package/dist/index.js.map +1 -0
- package/package.json +54 -0
- package/src/dashboard/index.html +580 -0
package/README.md
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# claude-alarm
|
|
2
|
+
|
|
3
|
+
Monitor and interact with multiple Claude Code sessions from a web dashboard. Get desktop notifications when tasks complete, send messages to Claude, and track session status — all through MCP Channels.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
[Dashboard] ──message──> [Hub Server] ──WebSocket──> [Channel Server] ──> Claude Code
|
|
7
|
+
<──
|
|
8
|
+
Claude Code ──reply/notify──> [Channel Server] ──> [Hub Server] ──> [Dashboard]
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **Web Dashboard** — Monitor all Claude Code sessions in one place
|
|
14
|
+
- **Two-way Messaging** — Send messages to Claude and receive replies
|
|
15
|
+
- **Desktop Notifications** — Get Windows/macOS/Linux toast notifications
|
|
16
|
+
- **Session Status** — See which sessions are idle, working, or waiting for input
|
|
17
|
+
- **Token Auth** — Secure hub access with auto-generated tokens
|
|
18
|
+
- **Multi-session** — Connect multiple Claude Code instances simultaneously
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
### 1. Install
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install -g claude-alarm
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### 2. Start the Hub
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
claude-alarm hub start -d
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
This starts the hub server in the background and prints your auth token.
|
|
35
|
+
|
|
36
|
+
### 3. Setup a Project
|
|
37
|
+
|
|
38
|
+
In your project directory:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
claude-alarm setup
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
This creates `.mcp.json` with the claude-alarm channel server config.
|
|
45
|
+
|
|
46
|
+
### 4. Run Claude Code
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
claude --dangerously-load-development-channels server:claude-alarm
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### 5. Open Dashboard
|
|
53
|
+
|
|
54
|
+
Open `http://127.0.0.1:7890` in your browser.
|
|
55
|
+
|
|
56
|
+
## CLI Commands
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
claude-alarm hub start [-d] Start the hub server (-d for daemon mode)
|
|
60
|
+
claude-alarm hub stop Stop the hub daemon
|
|
61
|
+
claude-alarm hub status Show hub status
|
|
62
|
+
claude-alarm setup [dir] Add claude-alarm to .mcp.json
|
|
63
|
+
claude-alarm test Send a test notification
|
|
64
|
+
claude-alarm token Show current auth token
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## How It Works
|
|
68
|
+
|
|
69
|
+
claude-alarm uses [MCP Channels](https://modelcontextprotocol.io) to create a communication bridge between Claude Code and a web dashboard.
|
|
70
|
+
|
|
71
|
+
- **Hub Server** — Central server that manages sessions, serves the dashboard, and routes messages
|
|
72
|
+
- **Channel Server** — MCP server that runs inside Claude Code, providing tools and forwarding messages
|
|
73
|
+
- **Dashboard** — Web UI for monitoring sessions and sending messages
|
|
74
|
+
|
|
75
|
+
### Tools Available to Claude
|
|
76
|
+
|
|
77
|
+
| Tool | Description |
|
|
78
|
+
|------|-------------|
|
|
79
|
+
| `notify` | Send a desktop notification (title, message, level) |
|
|
80
|
+
| `reply` | Send a message to the dashboard |
|
|
81
|
+
| `status` | Update session status (idle, working, waiting_input) |
|
|
82
|
+
|
|
83
|
+
## Configuration
|
|
84
|
+
|
|
85
|
+
Config is stored at `~/.claude-alarm/config.json`:
|
|
86
|
+
|
|
87
|
+
```json
|
|
88
|
+
{
|
|
89
|
+
"hub": {
|
|
90
|
+
"host": "127.0.0.1",
|
|
91
|
+
"port": 7890,
|
|
92
|
+
"token": "auto-generated-uuid"
|
|
93
|
+
},
|
|
94
|
+
"notifications": {
|
|
95
|
+
"desktop": true,
|
|
96
|
+
"sound": true
|
|
97
|
+
},
|
|
98
|
+
"webhooks": []
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Custom Session Names
|
|
103
|
+
|
|
104
|
+
The `.mcp.json` created by `claude-alarm setup` automatically uses the project directory name as the session name. You can customize it:
|
|
105
|
+
|
|
106
|
+
```json
|
|
107
|
+
{
|
|
108
|
+
"mcpServers": {
|
|
109
|
+
"claude-alarm": {
|
|
110
|
+
"command": "npx",
|
|
111
|
+
"args": ["-y", "claude-alarm"],
|
|
112
|
+
"env": {
|
|
113
|
+
"CLAUDE_ALARM_SESSION_NAME": "my-project"
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Webhooks
|
|
121
|
+
|
|
122
|
+
Send notifications to external services:
|
|
123
|
+
|
|
124
|
+
```json
|
|
125
|
+
{
|
|
126
|
+
"webhooks": [
|
|
127
|
+
{
|
|
128
|
+
"url": "https://hooks.slack.com/services/...",
|
|
129
|
+
"headers": { "Content-Type": "application/json" }
|
|
130
|
+
}
|
|
131
|
+
]
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Remote Access
|
|
136
|
+
|
|
137
|
+
To access the hub from another machine:
|
|
138
|
+
|
|
139
|
+
1. Set host to `0.0.0.0` in `~/.claude-alarm/config.json`
|
|
140
|
+
2. Open port 7890 in your firewall
|
|
141
|
+
3. On the remote machine, set the hub address in `.mcp.json`:
|
|
142
|
+
|
|
143
|
+
```json
|
|
144
|
+
{
|
|
145
|
+
"mcpServers": {
|
|
146
|
+
"claude-alarm": {
|
|
147
|
+
"command": "npx",
|
|
148
|
+
"args": ["-y", "claude-alarm"],
|
|
149
|
+
"env": {
|
|
150
|
+
"CLAUDE_ALARM_HUB_HOST": "your-server-ip",
|
|
151
|
+
"CLAUDE_ALARM_HUB_PORT": "7890",
|
|
152
|
+
"CLAUDE_ALARM_HUB_TOKEN": "your-token"
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Requirements
|
|
160
|
+
|
|
161
|
+
- Node.js >= 18
|
|
162
|
+
- Claude Code with MCP Channels support
|
|
163
|
+
|
|
164
|
+
## License
|
|
165
|
+
|
|
166
|
+
MIT
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/channel/server.ts
|
|
4
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
5
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
+
import {
|
|
7
|
+
CallToolRequestSchema,
|
|
8
|
+
ListToolsRequestSchema
|
|
9
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
10
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
11
|
+
|
|
12
|
+
// src/shared/logger.ts
|
|
13
|
+
var logger = {
|
|
14
|
+
info(msg, ...args) {
|
|
15
|
+
console.error(`[claude-alarm] ${msg}`, ...args);
|
|
16
|
+
},
|
|
17
|
+
warn(msg, ...args) {
|
|
18
|
+
console.error(`[claude-alarm WARN] ${msg}`, ...args);
|
|
19
|
+
},
|
|
20
|
+
error(msg, ...args) {
|
|
21
|
+
console.error(`[claude-alarm ERROR] ${msg}`, ...args);
|
|
22
|
+
},
|
|
23
|
+
debug(msg, ...args) {
|
|
24
|
+
if (process.env.CLAUDE_ALARM_DEBUG) {
|
|
25
|
+
console.error(`[claude-alarm DEBUG] ${msg}`, ...args);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// src/shared/constants.ts
|
|
31
|
+
import path from "path";
|
|
32
|
+
import os from "os";
|
|
33
|
+
var DEFAULT_HUB_HOST = "127.0.0.1";
|
|
34
|
+
var DEFAULT_HUB_PORT = 7890;
|
|
35
|
+
var CONFIG_DIR = path.join(os.homedir(), ".claude-alarm");
|
|
36
|
+
var CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
|
|
37
|
+
var PID_FILE = path.join(CONFIG_DIR, "hub.pid");
|
|
38
|
+
var LOG_FILE = path.join(CONFIG_DIR, "hub.log");
|
|
39
|
+
var WS_PATH_CHANNEL = "/ws/channel";
|
|
40
|
+
var CHANNEL_SERVER_NAME = "claude-alarm";
|
|
41
|
+
var CHANNEL_SERVER_VERSION = "0.1.0";
|
|
42
|
+
|
|
43
|
+
// src/shared/config.ts
|
|
44
|
+
import fs from "fs";
|
|
45
|
+
import path2 from "path";
|
|
46
|
+
import { randomUUID } from "crypto";
|
|
47
|
+
var DEFAULT_CONFIG = {
|
|
48
|
+
hub: {
|
|
49
|
+
host: DEFAULT_HUB_HOST,
|
|
50
|
+
port: DEFAULT_HUB_PORT
|
|
51
|
+
},
|
|
52
|
+
notifications: {
|
|
53
|
+
desktop: true,
|
|
54
|
+
sound: true
|
|
55
|
+
},
|
|
56
|
+
webhooks: []
|
|
57
|
+
};
|
|
58
|
+
function ensureConfigDir() {
|
|
59
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
60
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function loadConfig() {
|
|
64
|
+
ensureConfigDir();
|
|
65
|
+
let config2;
|
|
66
|
+
if (!fs.existsSync(CONFIG_FILE)) {
|
|
67
|
+
config2 = { ...DEFAULT_CONFIG, hub: { ...DEFAULT_CONFIG.hub } };
|
|
68
|
+
} else {
|
|
69
|
+
try {
|
|
70
|
+
const raw = fs.readFileSync(CONFIG_FILE, "utf-8");
|
|
71
|
+
const parsed = JSON.parse(raw);
|
|
72
|
+
config2 = { ...DEFAULT_CONFIG, ...parsed, hub: { ...DEFAULT_CONFIG.hub, ...parsed.hub } };
|
|
73
|
+
} catch {
|
|
74
|
+
config2 = { ...DEFAULT_CONFIG, hub: { ...DEFAULT_CONFIG.hub } };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (!config2.hub.token) {
|
|
78
|
+
config2.hub.token = randomUUID();
|
|
79
|
+
saveConfig(config2);
|
|
80
|
+
}
|
|
81
|
+
return config2;
|
|
82
|
+
}
|
|
83
|
+
function saveConfig(config2) {
|
|
84
|
+
ensureConfigDir();
|
|
85
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config2, null, 2), { encoding: "utf-8", mode: 384 });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// src/channel/hub-client.ts
|
|
89
|
+
import WebSocket from "ws";
|
|
90
|
+
var HubClient = class {
|
|
91
|
+
constructor(sessionId2, sessionName2, hubHost2 = DEFAULT_HUB_HOST, hubPort2 = DEFAULT_HUB_PORT, token) {
|
|
92
|
+
this.sessionId = sessionId2;
|
|
93
|
+
this.sessionName = sessionName2;
|
|
94
|
+
this.hubHost = hubHost2;
|
|
95
|
+
this.hubPort = hubPort2;
|
|
96
|
+
this.token = token;
|
|
97
|
+
}
|
|
98
|
+
ws = null;
|
|
99
|
+
reconnectTimer = null;
|
|
100
|
+
messageHandlers = [];
|
|
101
|
+
queue = [];
|
|
102
|
+
connected = false;
|
|
103
|
+
connect() {
|
|
104
|
+
const tokenQuery = this.token ? `?token=${encodeURIComponent(this.token)}` : "";
|
|
105
|
+
const url = `ws://${this.hubHost}:${this.hubPort}${WS_PATH_CHANNEL}${tokenQuery}`;
|
|
106
|
+
logger.debug(`Connecting to hub at ${url}`);
|
|
107
|
+
try {
|
|
108
|
+
this.ws = new WebSocket(url);
|
|
109
|
+
this.ws.on("open", () => {
|
|
110
|
+
logger.info("Connected to hub");
|
|
111
|
+
this.connected = true;
|
|
112
|
+
const registration = {
|
|
113
|
+
type: "register",
|
|
114
|
+
session: {
|
|
115
|
+
id: this.sessionId,
|
|
116
|
+
name: this.sessionName,
|
|
117
|
+
status: "idle",
|
|
118
|
+
connectedAt: Date.now(),
|
|
119
|
+
lastActivity: Date.now(),
|
|
120
|
+
cwd: process.cwd()
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
this.ws.send(JSON.stringify(registration));
|
|
124
|
+
for (const msg of this.queue) {
|
|
125
|
+
this.ws.send(JSON.stringify(msg));
|
|
126
|
+
}
|
|
127
|
+
this.queue = [];
|
|
128
|
+
});
|
|
129
|
+
this.ws.on("message", (data) => {
|
|
130
|
+
try {
|
|
131
|
+
const msg = JSON.parse(data.toString());
|
|
132
|
+
for (const handler of this.messageHandlers) {
|
|
133
|
+
handler(msg);
|
|
134
|
+
}
|
|
135
|
+
} catch (err) {
|
|
136
|
+
logger.warn("Failed to parse hub message:", err);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
this.ws.on("close", () => {
|
|
140
|
+
logger.info("Disconnected from hub");
|
|
141
|
+
this.connected = false;
|
|
142
|
+
this.scheduleReconnect();
|
|
143
|
+
});
|
|
144
|
+
this.ws.on("error", (err) => {
|
|
145
|
+
logger.debug(`Hub connection error: ${err.message}`);
|
|
146
|
+
this.connected = false;
|
|
147
|
+
});
|
|
148
|
+
} catch {
|
|
149
|
+
logger.debug("Failed to connect to hub, will retry");
|
|
150
|
+
this.scheduleReconnect();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
send(msg) {
|
|
154
|
+
if (this.connected && this.ws?.readyState === WebSocket.OPEN) {
|
|
155
|
+
this.ws.send(JSON.stringify(msg));
|
|
156
|
+
} else {
|
|
157
|
+
if (this.queue.length < 100) {
|
|
158
|
+
this.queue.push(msg);
|
|
159
|
+
}
|
|
160
|
+
logger.debug("Hub not connected, message queued");
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
onMessage(handler) {
|
|
164
|
+
this.messageHandlers.push(handler);
|
|
165
|
+
}
|
|
166
|
+
disconnect() {
|
|
167
|
+
if (this.reconnectTimer) {
|
|
168
|
+
clearTimeout(this.reconnectTimer);
|
|
169
|
+
this.reconnectTimer = null;
|
|
170
|
+
}
|
|
171
|
+
if (this.ws) {
|
|
172
|
+
this.ws.close();
|
|
173
|
+
this.ws = null;
|
|
174
|
+
}
|
|
175
|
+
this.connected = false;
|
|
176
|
+
}
|
|
177
|
+
scheduleReconnect() {
|
|
178
|
+
if (this.reconnectTimer) return;
|
|
179
|
+
this.reconnectTimer = setTimeout(() => {
|
|
180
|
+
this.reconnectTimer = null;
|
|
181
|
+
this.connect();
|
|
182
|
+
}, 5e3);
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
// src/channel/server.ts
|
|
187
|
+
var sessionId = randomUUID2();
|
|
188
|
+
var sessionName = process.env.CLAUDE_ALARM_SESSION_NAME ?? `session-${sessionId.slice(0, 8)}`;
|
|
189
|
+
var server = new Server(
|
|
190
|
+
{
|
|
191
|
+
name: CHANNEL_SERVER_NAME,
|
|
192
|
+
version: CHANNEL_SERVER_VERSION
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
capabilities: {
|
|
196
|
+
experimental: { "claude/channel": {} },
|
|
197
|
+
tools: {}
|
|
198
|
+
},
|
|
199
|
+
instructions: 'Messages from the claude-alarm dashboard arrive as <channel source="claude-alarm" sender="...">. Read the message and act on it. To reply, call the reply tool with the message content. Use the notify tool to send desktop notifications. Use the status tool to update your session status.'
|
|
200
|
+
}
|
|
201
|
+
);
|
|
202
|
+
var config = loadConfig();
|
|
203
|
+
var hubHost = process.env.CLAUDE_ALARM_HUB_HOST ?? config.hub.host;
|
|
204
|
+
var hubPort = process.env.CLAUDE_ALARM_HUB_PORT ? parseInt(process.env.CLAUDE_ALARM_HUB_PORT, 10) : config.hub.port;
|
|
205
|
+
var hubToken = process.env.CLAUDE_ALARM_HUB_TOKEN ?? config.hub.token;
|
|
206
|
+
var hubClient = new HubClient(
|
|
207
|
+
sessionId,
|
|
208
|
+
sessionName,
|
|
209
|
+
hubHost,
|
|
210
|
+
hubPort,
|
|
211
|
+
hubToken
|
|
212
|
+
);
|
|
213
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
214
|
+
tools: [
|
|
215
|
+
{
|
|
216
|
+
name: "notify",
|
|
217
|
+
description: "Send a desktop notification to the user. Use this when you complete a task, encounter an error, or need user attention. The notification will appear as a system toast/popup.",
|
|
218
|
+
inputSchema: {
|
|
219
|
+
type: "object",
|
|
220
|
+
properties: {
|
|
221
|
+
title: { type: "string", description: "Notification title (short)" },
|
|
222
|
+
message: { type: "string", description: "Notification body text" },
|
|
223
|
+
level: {
|
|
224
|
+
type: "string",
|
|
225
|
+
enum: ["info", "warning", "error", "success"],
|
|
226
|
+
description: "Notification level (default: info)"
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
required: ["title", "message"]
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
name: "reply",
|
|
234
|
+
description: "Send a message to the web dashboard. Use this to communicate status updates, results, or any information the user should see in the monitoring dashboard.",
|
|
235
|
+
inputSchema: {
|
|
236
|
+
type: "object",
|
|
237
|
+
properties: {
|
|
238
|
+
content: { type: "string", description: "Message content to display on the dashboard" }
|
|
239
|
+
},
|
|
240
|
+
required: ["content"]
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
name: "status",
|
|
245
|
+
description: 'Update your session status displayed on the dashboard. Set to "working" when actively processing, "waiting_input" when you need user input, or "idle" when done.',
|
|
246
|
+
inputSchema: {
|
|
247
|
+
type: "object",
|
|
248
|
+
properties: {
|
|
249
|
+
status: {
|
|
250
|
+
type: "string",
|
|
251
|
+
enum: ["idle", "working", "waiting_input"],
|
|
252
|
+
description: "Current session status"
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
required: ["status"]
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
]
|
|
259
|
+
}));
|
|
260
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
261
|
+
const { name, arguments: args } = request.params;
|
|
262
|
+
switch (name) {
|
|
263
|
+
case "notify": {
|
|
264
|
+
const title = args?.title;
|
|
265
|
+
const message = args?.message;
|
|
266
|
+
const level = args?.level ?? "info";
|
|
267
|
+
logger.info(`Notify [${level}]: ${title} - ${message}`);
|
|
268
|
+
hubClient.send({
|
|
269
|
+
type: "notify",
|
|
270
|
+
sessionId,
|
|
271
|
+
title,
|
|
272
|
+
message,
|
|
273
|
+
level
|
|
274
|
+
});
|
|
275
|
+
return {
|
|
276
|
+
content: [{ type: "text", text: `Notification sent: "${title}"` }]
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
case "reply": {
|
|
280
|
+
const content = args?.content;
|
|
281
|
+
logger.info(`Reply: ${content.slice(0, 100)}...`);
|
|
282
|
+
hubClient.send({
|
|
283
|
+
type: "reply",
|
|
284
|
+
sessionId,
|
|
285
|
+
content
|
|
286
|
+
});
|
|
287
|
+
return {
|
|
288
|
+
content: [{ type: "text", text: "Message sent to dashboard." }]
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
case "status": {
|
|
292
|
+
const status = args?.status;
|
|
293
|
+
logger.info(`Status update: ${status}`);
|
|
294
|
+
hubClient.send({
|
|
295
|
+
type: "status",
|
|
296
|
+
sessionId,
|
|
297
|
+
status
|
|
298
|
+
});
|
|
299
|
+
return {
|
|
300
|
+
content: [{ type: "text", text: `Status updated to "${status}".` }]
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
default:
|
|
304
|
+
return {
|
|
305
|
+
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
|
306
|
+
isError: true
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
async function main() {
|
|
311
|
+
logger.info(`Starting MCP channel server (session: ${sessionId})`);
|
|
312
|
+
hubClient.connect();
|
|
313
|
+
hubClient.onMessage(async (msg) => {
|
|
314
|
+
if (msg.type === "message_to_session" && msg.sessionId === sessionId) {
|
|
315
|
+
logger.info(`Message from dashboard: ${msg.content}`);
|
|
316
|
+
await server.notification({
|
|
317
|
+
method: "notifications/claude/channel",
|
|
318
|
+
params: {
|
|
319
|
+
content: msg.content,
|
|
320
|
+
meta: { sender: "dashboard", timestamp: String(Date.now()) }
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
const transport = new StdioServerTransport();
|
|
326
|
+
await server.connect(transport);
|
|
327
|
+
logger.info("MCP channel server running on stdio");
|
|
328
|
+
}
|
|
329
|
+
main().catch((err) => {
|
|
330
|
+
logger.error("Fatal error:", err);
|
|
331
|
+
process.exit(1);
|
|
332
|
+
});
|
|
333
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/channel/server.ts","../../src/shared/logger.ts","../../src/shared/constants.ts","../../src/shared/config.ts","../../src/channel/hub-client.ts"],"sourcesContent":["import { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport {\n CallToolRequestSchema,\n ListToolsRequestSchema,\n} from '@modelcontextprotocol/sdk/types.js';\nimport { randomUUID } from 'node:crypto';\nimport { logger } from '../shared/logger.js';\nimport { CHANNEL_SERVER_NAME, CHANNEL_SERVER_VERSION } from '../shared/constants.js';\nimport { loadConfig } from '../shared/config.js';\nimport { HubClient } from './hub-client.js';\nimport type { SessionStatus, NotifyLevel } from '../shared/types.js';\n\nconst sessionId = randomUUID();\nconst sessionName = process.env.CLAUDE_ALARM_SESSION_NAME ?? `session-${sessionId.slice(0, 8)}`;\n\nconst server = new Server(\n {\n name: CHANNEL_SERVER_NAME,\n version: CHANNEL_SERVER_VERSION,\n },\n {\n capabilities: {\n experimental: { 'claude/channel': {} },\n tools: {},\n },\n instructions:\n 'Messages from the claude-alarm dashboard arrive as <channel source=\"claude-alarm\" sender=\"...\">. ' +\n 'Read the message and act on it. To reply, call the reply tool with the message content. ' +\n 'Use the notify tool to send desktop notifications. Use the status tool to update your session status.',\n },\n);\n\n// Load config for hub connection (env vars take priority)\nconst config = loadConfig();\nconst hubHost = process.env.CLAUDE_ALARM_HUB_HOST ?? config.hub.host;\nconst hubPort = process.env.CLAUDE_ALARM_HUB_PORT ? parseInt(process.env.CLAUDE_ALARM_HUB_PORT, 10) : config.hub.port;\nconst hubToken = process.env.CLAUDE_ALARM_HUB_TOKEN ?? config.hub.token;\n\n// Hub client for forwarding to central hub\nconst hubClient = new HubClient(\n sessionId,\n sessionName,\n hubHost,\n hubPort,\n hubToken,\n);\n\n// --- Tools ---\n\nserver.setRequestHandler(ListToolsRequestSchema, async () => ({\n tools: [\n {\n name: 'notify',\n description:\n 'Send a desktop notification to the user. Use this when you complete a task, encounter an error, or need user attention. The notification will appear as a system toast/popup.',\n inputSchema: {\n type: 'object' as const,\n properties: {\n title: { type: 'string', description: 'Notification title (short)' },\n message: { type: 'string', description: 'Notification body text' },\n level: {\n type: 'string',\n enum: ['info', 'warning', 'error', 'success'],\n description: 'Notification level (default: info)',\n },\n },\n required: ['title', 'message'],\n },\n },\n {\n name: 'reply',\n description:\n 'Send a message to the web dashboard. Use this to communicate status updates, results, or any information the user should see in the monitoring dashboard.',\n inputSchema: {\n type: 'object' as const,\n properties: {\n content: { type: 'string', description: 'Message content to display on the dashboard' },\n },\n required: ['content'],\n },\n },\n {\n name: 'status',\n description:\n 'Update your session status displayed on the dashboard. Set to \"working\" when actively processing, \"waiting_input\" when you need user input, or \"idle\" when done.',\n inputSchema: {\n type: 'object' as const,\n properties: {\n status: {\n type: 'string',\n enum: ['idle', 'working', 'waiting_input'],\n description: 'Current session status',\n },\n },\n required: ['status'],\n },\n },\n ],\n}));\n\nserver.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { name, arguments: args } = request.params;\n\n switch (name) {\n case 'notify': {\n const title = args?.title as string;\n const message = args?.message as string;\n const level = (args?.level as NotifyLevel) ?? 'info';\n logger.info(`Notify [${level}]: ${title} - ${message}`);\n hubClient.send({\n type: 'notify',\n sessionId,\n title,\n message,\n level,\n });\n return {\n content: [{ type: 'text', text: `Notification sent: \"${title}\"` }],\n };\n }\n\n case 'reply': {\n const content = args?.content as string;\n logger.info(`Reply: ${content.slice(0, 100)}...`);\n hubClient.send({\n type: 'reply',\n sessionId,\n content,\n });\n return {\n content: [{ type: 'text', text: 'Message sent to dashboard.' }],\n };\n }\n\n case 'status': {\n const status = args?.status as SessionStatus;\n logger.info(`Status update: ${status}`);\n hubClient.send({\n type: 'status',\n sessionId,\n status,\n });\n return {\n content: [{ type: 'text', text: `Status updated to \"${status}\".` }],\n };\n }\n\n default:\n return {\n content: [{ type: 'text', text: `Unknown tool: ${name}` }],\n isError: true,\n };\n }\n});\n\n// --- Startup ---\n\nasync function main() {\n logger.info(`Starting MCP channel server (session: ${sessionId})`);\n\n // Connect to hub (non-blocking, will retry)\n hubClient.connect();\n\n // Listen for messages from hub and forward to Claude via channel notification\n hubClient.onMessage(async (msg) => {\n if (msg.type === 'message_to_session' && msg.sessionId === sessionId) {\n logger.info(`Message from dashboard: ${msg.content}`);\n await server.notification({\n method: 'notifications/claude/channel',\n params: {\n content: msg.content,\n meta: { sender: 'dashboard', timestamp: String(Date.now()) },\n },\n });\n }\n });\n\n const transport = new StdioServerTransport();\n await server.connect(transport);\n logger.info('MCP channel server running on stdio');\n}\n\nmain().catch((err) => {\n logger.error('Fatal error:', err);\n process.exit(1);\n});\n","/**\n * Logger that writes to stderr only.\n * CRITICAL: In MCP channel servers, stdout is used for the stdio protocol.\n * Any console.log() would corrupt the protocol. Always use this logger.\n */\nexport const logger = {\n info(msg: string, ...args: unknown[]) {\n console.error(`[claude-alarm] ${msg}`, ...args);\n },\n warn(msg: string, ...args: unknown[]) {\n console.error(`[claude-alarm WARN] ${msg}`, ...args);\n },\n error(msg: string, ...args: unknown[]) {\n console.error(`[claude-alarm ERROR] ${msg}`, ...args);\n },\n debug(msg: string, ...args: unknown[]) {\n if (process.env.CLAUDE_ALARM_DEBUG) {\n console.error(`[claude-alarm DEBUG] ${msg}`, ...args);\n }\n },\n};\n","import path from 'node:path';\nimport os from 'node:os';\n\nexport const DEFAULT_HUB_HOST = '127.0.0.1';\nexport const DEFAULT_HUB_PORT = 7890;\n\nexport const CONFIG_DIR = path.join(os.homedir(), '.claude-alarm');\nexport const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');\nexport const PID_FILE = path.join(CONFIG_DIR, 'hub.pid');\nexport const LOG_FILE = path.join(CONFIG_DIR, 'hub.log');\n\nexport const WS_PATH_CHANNEL = '/ws/channel';\nexport const WS_PATH_DASHBOARD = '/ws/dashboard';\n\nexport const CHANNEL_SERVER_NAME = 'claude-alarm';\nexport const CHANNEL_SERVER_VERSION = '0.1.0';\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport { randomUUID } from 'node:crypto';\nimport { CONFIG_DIR, CONFIG_FILE, DEFAULT_HUB_HOST, DEFAULT_HUB_PORT } from './constants.js';\nimport type { AppConfig } from './types.js';\n\nconst DEFAULT_CONFIG: AppConfig = {\n hub: {\n host: DEFAULT_HUB_HOST,\n port: DEFAULT_HUB_PORT,\n },\n notifications: {\n desktop: true,\n sound: true,\n },\n webhooks: [],\n};\n\nexport function ensureConfigDir(): void {\n if (!fs.existsSync(CONFIG_DIR)) {\n fs.mkdirSync(CONFIG_DIR, { recursive: true });\n }\n}\n\nexport function loadConfig(): AppConfig {\n ensureConfigDir();\n let config: AppConfig;\n if (!fs.existsSync(CONFIG_FILE)) {\n config = { ...DEFAULT_CONFIG, hub: { ...DEFAULT_CONFIG.hub } };\n } else {\n try {\n const raw = fs.readFileSync(CONFIG_FILE, 'utf-8');\n const parsed = JSON.parse(raw);\n config = { ...DEFAULT_CONFIG, ...parsed, hub: { ...DEFAULT_CONFIG.hub, ...parsed.hub } };\n } catch {\n config = { ...DEFAULT_CONFIG, hub: { ...DEFAULT_CONFIG.hub } };\n }\n }\n\n // Auto-generate token if missing\n if (!config.hub.token) {\n config.hub.token = randomUUID();\n saveConfig(config);\n }\n\n return config;\n}\n\n/** Get the current token, generating one if needed */\nexport function getOrCreateToken(): string {\n const config = loadConfig();\n return config.hub.token!;\n}\n\nexport function saveConfig(config: AppConfig): void {\n ensureConfigDir();\n fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), { encoding: 'utf-8', mode: 0o600 });\n}\n\n/**\n * Add claude-alarm as an MCP channel server to .mcp.json\n */\nexport function setupMcpConfig(targetDir?: string): string {\n const dir = targetDir ?? process.cwd();\n const mcpPath = path.join(dir, '.mcp.json');\n\n let mcpConfig: Record<string, any> = {};\n if (fs.existsSync(mcpPath)) {\n try {\n mcpConfig = JSON.parse(fs.readFileSync(mcpPath, 'utf-8'));\n } catch {\n mcpConfig = {};\n }\n }\n\n if (!mcpConfig.mcpServers) {\n mcpConfig.mcpServers = {};\n }\n\n mcpConfig.mcpServers['claude-alarm'] = {\n command: 'npx',\n args: ['-y', '@delt/claude-alarm'],\n env: {\n CLAUDE_ALARM_SESSION_NAME: path.basename(dir),\n },\n };\n\n fs.writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2), 'utf-8');\n return mcpPath;\n}\n","import WebSocket from 'ws';\nimport { logger } from '../shared/logger.js';\nimport { DEFAULT_HUB_HOST, DEFAULT_HUB_PORT, WS_PATH_CHANNEL } from '../shared/constants.js';\nimport type { ChannelMessage, SessionInfo } from '../shared/types.js';\n\nexport class HubClient {\n private ws: WebSocket | null = null;\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n private messageHandlers: Array<(msg: ChannelMessage) => void> = [];\n private queue: ChannelMessage[] = [];\n private connected = false;\n\n constructor(\n private sessionId: string,\n private sessionName: string,\n private hubHost = DEFAULT_HUB_HOST,\n private hubPort = DEFAULT_HUB_PORT,\n private token?: string,\n ) {}\n\n connect(): void {\n const tokenQuery = this.token ? `?token=${encodeURIComponent(this.token)}` : '';\n const url = `ws://${this.hubHost}:${this.hubPort}${WS_PATH_CHANNEL}${tokenQuery}`;\n logger.debug(`Connecting to hub at ${url}`);\n\n try {\n this.ws = new WebSocket(url);\n\n this.ws.on('open', () => {\n logger.info('Connected to hub');\n this.connected = true;\n\n // Register this session\n const registration: ChannelMessage = {\n type: 'register',\n session: {\n id: this.sessionId,\n name: this.sessionName,\n status: 'idle',\n connectedAt: Date.now(),\n lastActivity: Date.now(),\n cwd: process.cwd(),\n },\n };\n this.ws!.send(JSON.stringify(registration));\n\n // Flush queued messages\n for (const msg of this.queue) {\n this.ws!.send(JSON.stringify(msg));\n }\n this.queue = [];\n });\n\n this.ws.on('message', (data) => {\n try {\n const msg = JSON.parse(data.toString()) as ChannelMessage;\n for (const handler of this.messageHandlers) {\n handler(msg);\n }\n } catch (err) {\n logger.warn('Failed to parse hub message:', err);\n }\n });\n\n this.ws.on('close', () => {\n logger.info('Disconnected from hub');\n this.connected = false;\n this.scheduleReconnect();\n });\n\n this.ws.on('error', (err) => {\n logger.debug(`Hub connection error: ${err.message}`);\n this.connected = false;\n });\n } catch {\n logger.debug('Failed to connect to hub, will retry');\n this.scheduleReconnect();\n }\n }\n\n send(msg: ChannelMessage): void {\n if (this.connected && this.ws?.readyState === WebSocket.OPEN) {\n this.ws.send(JSON.stringify(msg));\n } else {\n if (this.queue.length < 100) {\n this.queue.push(msg);\n }\n logger.debug('Hub not connected, message queued');\n }\n }\n\n onMessage(handler: (msg: ChannelMessage) => void): void {\n this.messageHandlers.push(handler);\n }\n\n disconnect(): void {\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n if (this.ws) {\n this.ws.close();\n this.ws = null;\n }\n this.connected = false;\n }\n\n private scheduleReconnect(): void {\n if (this.reconnectTimer) return;\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = null;\n this.connect();\n }, 5000);\n }\n}\n"],"mappings":"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","names":["randomUUID","path","config","config","sessionId","sessionName","hubHost","hubPort","randomUUID"]}
|