@delt/claude-alarm 0.1.0 → 0.1.2
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/dist/channel/server.js.map +1 -1
- package/dist/cli.js +414 -125
- package/dist/cli.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -12,7 +12,7 @@ var __export = (target, all) => {
|
|
|
12
12
|
// src/shared/constants.ts
|
|
13
13
|
import path from "path";
|
|
14
14
|
import os from "os";
|
|
15
|
-
var DEFAULT_HUB_HOST, DEFAULT_HUB_PORT, CONFIG_DIR, CONFIG_FILE, PID_FILE, LOG_FILE, WS_PATH_CHANNEL, WS_PATH_DASHBOARD;
|
|
15
|
+
var DEFAULT_HUB_HOST, DEFAULT_HUB_PORT, CONFIG_DIR, CONFIG_FILE, PID_FILE, LOG_FILE, WS_PATH_CHANNEL, WS_PATH_DASHBOARD, CHANNEL_SERVER_NAME, CHANNEL_SERVER_VERSION;
|
|
16
16
|
var init_constants = __esm({
|
|
17
17
|
"src/shared/constants.ts"() {
|
|
18
18
|
"use strict";
|
|
@@ -24,6 +24,88 @@ var init_constants = __esm({
|
|
|
24
24
|
LOG_FILE = path.join(CONFIG_DIR, "hub.log");
|
|
25
25
|
WS_PATH_CHANNEL = "/ws/channel";
|
|
26
26
|
WS_PATH_DASHBOARD = "/ws/dashboard";
|
|
27
|
+
CHANNEL_SERVER_NAME = "claude-alarm";
|
|
28
|
+
CHANNEL_SERVER_VERSION = "0.1.0";
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// src/shared/config.ts
|
|
33
|
+
import fs from "fs";
|
|
34
|
+
import path2 from "path";
|
|
35
|
+
import { randomUUID } from "crypto";
|
|
36
|
+
function ensureConfigDir() {
|
|
37
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
38
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function loadConfig() {
|
|
42
|
+
ensureConfigDir();
|
|
43
|
+
let config2;
|
|
44
|
+
if (!fs.existsSync(CONFIG_FILE)) {
|
|
45
|
+
config2 = { ...DEFAULT_CONFIG, hub: { ...DEFAULT_CONFIG.hub } };
|
|
46
|
+
} else {
|
|
47
|
+
try {
|
|
48
|
+
const raw = fs.readFileSync(CONFIG_FILE, "utf-8");
|
|
49
|
+
const parsed = JSON.parse(raw);
|
|
50
|
+
config2 = { ...DEFAULT_CONFIG, ...parsed, hub: { ...DEFAULT_CONFIG.hub, ...parsed.hub } };
|
|
51
|
+
} catch {
|
|
52
|
+
config2 = { ...DEFAULT_CONFIG, hub: { ...DEFAULT_CONFIG.hub } };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (!config2.hub.token) {
|
|
56
|
+
config2.hub.token = randomUUID();
|
|
57
|
+
saveConfig(config2);
|
|
58
|
+
}
|
|
59
|
+
return config2;
|
|
60
|
+
}
|
|
61
|
+
function getOrCreateToken() {
|
|
62
|
+
const config2 = loadConfig();
|
|
63
|
+
return config2.hub.token;
|
|
64
|
+
}
|
|
65
|
+
function saveConfig(config2) {
|
|
66
|
+
ensureConfigDir();
|
|
67
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config2, null, 2), { encoding: "utf-8", mode: 384 });
|
|
68
|
+
}
|
|
69
|
+
function setupMcpConfig(targetDir) {
|
|
70
|
+
const dir = targetDir ?? process.cwd();
|
|
71
|
+
const mcpPath = path2.join(dir, ".mcp.json");
|
|
72
|
+
let mcpConfig = {};
|
|
73
|
+
if (fs.existsSync(mcpPath)) {
|
|
74
|
+
try {
|
|
75
|
+
mcpConfig = JSON.parse(fs.readFileSync(mcpPath, "utf-8"));
|
|
76
|
+
} catch {
|
|
77
|
+
mcpConfig = {};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (!mcpConfig.mcpServers) {
|
|
81
|
+
mcpConfig.mcpServers = {};
|
|
82
|
+
}
|
|
83
|
+
mcpConfig.mcpServers["claude-alarm"] = {
|
|
84
|
+
command: "npx",
|
|
85
|
+
args: ["-y", "@delt/claude-alarm", "serve"],
|
|
86
|
+
env: {
|
|
87
|
+
CLAUDE_ALARM_SESSION_NAME: path2.basename(dir)
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
fs.writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2), "utf-8");
|
|
91
|
+
return mcpPath;
|
|
92
|
+
}
|
|
93
|
+
var DEFAULT_CONFIG;
|
|
94
|
+
var init_config = __esm({
|
|
95
|
+
"src/shared/config.ts"() {
|
|
96
|
+
"use strict";
|
|
97
|
+
init_constants();
|
|
98
|
+
DEFAULT_CONFIG = {
|
|
99
|
+
hub: {
|
|
100
|
+
host: DEFAULT_HUB_HOST,
|
|
101
|
+
port: DEFAULT_HUB_PORT
|
|
102
|
+
},
|
|
103
|
+
notifications: {
|
|
104
|
+
desktop: true,
|
|
105
|
+
sound: true
|
|
106
|
+
},
|
|
107
|
+
webhooks: []
|
|
108
|
+
};
|
|
27
109
|
}
|
|
28
110
|
});
|
|
29
111
|
|
|
@@ -61,27 +143,27 @@ var init_session_manager = __esm({
|
|
|
61
143
|
register(session) {
|
|
62
144
|
this.sessions.set(session.id, { ...session });
|
|
63
145
|
}
|
|
64
|
-
unregister(
|
|
65
|
-
const session = this.sessions.get(
|
|
66
|
-
this.sessions.delete(
|
|
146
|
+
unregister(sessionId2) {
|
|
147
|
+
const session = this.sessions.get(sessionId2);
|
|
148
|
+
this.sessions.delete(sessionId2);
|
|
67
149
|
return session;
|
|
68
150
|
}
|
|
69
|
-
updateStatus(
|
|
70
|
-
const session = this.sessions.get(
|
|
151
|
+
updateStatus(sessionId2, status) {
|
|
152
|
+
const session = this.sessions.get(sessionId2);
|
|
71
153
|
if (session) {
|
|
72
154
|
session.status = status;
|
|
73
155
|
session.lastActivity = Date.now();
|
|
74
156
|
}
|
|
75
157
|
return session;
|
|
76
158
|
}
|
|
77
|
-
updateActivity(
|
|
78
|
-
const session = this.sessions.get(
|
|
159
|
+
updateActivity(sessionId2) {
|
|
160
|
+
const session = this.sessions.get(sessionId2);
|
|
79
161
|
if (session) {
|
|
80
162
|
session.lastActivity = Date.now();
|
|
81
163
|
}
|
|
82
164
|
}
|
|
83
|
-
get(
|
|
84
|
-
return this.sessions.get(
|
|
165
|
+
get(sessionId2) {
|
|
166
|
+
return this.sessions.get(sessionId2);
|
|
85
167
|
}
|
|
86
168
|
getAll() {
|
|
87
169
|
return Array.from(this.sessions.values());
|
|
@@ -240,17 +322,17 @@ var init_server = __esm({
|
|
|
240
322
|
host;
|
|
241
323
|
port;
|
|
242
324
|
token;
|
|
243
|
-
constructor(
|
|
244
|
-
this.host =
|
|
245
|
-
this.port =
|
|
246
|
-
this.token =
|
|
247
|
-
if (
|
|
325
|
+
constructor(config2) {
|
|
326
|
+
this.host = config2?.hub?.host ?? DEFAULT_HUB_HOST;
|
|
327
|
+
this.port = config2?.hub?.port ?? DEFAULT_HUB_PORT;
|
|
328
|
+
this.token = config2?.hub?.token;
|
|
329
|
+
if (config2?.notifications) {
|
|
248
330
|
this.notifier.configure({
|
|
249
|
-
desktop:
|
|
331
|
+
desktop: config2.notifications.desktop
|
|
250
332
|
});
|
|
251
333
|
}
|
|
252
|
-
if (
|
|
253
|
-
this.notifier.configure({ webhooks:
|
|
334
|
+
if (config2?.webhooks) {
|
|
335
|
+
this.notifier.configure({ webhooks: config2.webhooks });
|
|
254
336
|
}
|
|
255
337
|
this.notifier.configure({ dashboardUrl: `http://${this.host}:${this.port}` });
|
|
256
338
|
this.httpServer = http.createServer((req, res) => this.handleHttp(req, res));
|
|
@@ -375,17 +457,17 @@ var init_server = __esm({
|
|
|
375
457
|
this.jsonResponse(res, 400, { error: "Invalid JSON" });
|
|
376
458
|
return;
|
|
377
459
|
}
|
|
378
|
-
const { sessionId, content } = body;
|
|
379
|
-
if (!
|
|
460
|
+
const { sessionId: sessionId2, content } = body;
|
|
461
|
+
if (!sessionId2 || !content) {
|
|
380
462
|
this.jsonResponse(res, 400, { error: "sessionId and content are required" });
|
|
381
463
|
return;
|
|
382
464
|
}
|
|
383
|
-
const ws = this.channelSockets.get(
|
|
465
|
+
const ws = this.channelSockets.get(sessionId2);
|
|
384
466
|
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
|
385
467
|
this.jsonResponse(res, 404, { error: "Session not connected" });
|
|
386
468
|
return;
|
|
387
469
|
}
|
|
388
|
-
const msg = { type: "message_to_session", sessionId, content };
|
|
470
|
+
const msg = { type: "message_to_session", sessionId: sessionId2, content };
|
|
389
471
|
ws.send(JSON.stringify(msg));
|
|
390
472
|
this.jsonResponse(res, 200, { ok: true });
|
|
391
473
|
}
|
|
@@ -415,14 +497,14 @@ var init_server = __esm({
|
|
|
415
497
|
}
|
|
416
498
|
});
|
|
417
499
|
ws.on("close", () => {
|
|
418
|
-
for (const [
|
|
500
|
+
for (const [sessionId2, sock] of this.channelSockets) {
|
|
419
501
|
if (sock === ws) {
|
|
420
|
-
const session = this.sessions.unregister(
|
|
421
|
-
this.channelSockets.delete(
|
|
422
|
-
logger.info(`Channel disconnected: ${
|
|
502
|
+
const session = this.sessions.unregister(sessionId2);
|
|
503
|
+
this.channelSockets.delete(sessionId2);
|
|
504
|
+
logger.info(`Channel disconnected: ${sessionId2}`);
|
|
423
505
|
this.broadcastToDashboards({
|
|
424
506
|
type: "session_disconnected",
|
|
425
|
-
sessionId
|
|
507
|
+
sessionId: sessionId2
|
|
426
508
|
});
|
|
427
509
|
break;
|
|
428
510
|
}
|
|
@@ -553,90 +635,287 @@ var init_server = __esm({
|
|
|
553
635
|
}
|
|
554
636
|
});
|
|
555
637
|
|
|
556
|
-
// src/
|
|
557
|
-
import
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
};
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
638
|
+
// src/channel/hub-client.ts
|
|
639
|
+
import WebSocket2 from "ws";
|
|
640
|
+
var HubClient;
|
|
641
|
+
var init_hub_client = __esm({
|
|
642
|
+
"src/channel/hub-client.ts"() {
|
|
643
|
+
"use strict";
|
|
644
|
+
init_logger();
|
|
645
|
+
init_constants();
|
|
646
|
+
HubClient = class {
|
|
647
|
+
constructor(sessionId2, sessionName2, hubHost2 = DEFAULT_HUB_HOST, hubPort2 = DEFAULT_HUB_PORT, token) {
|
|
648
|
+
this.sessionId = sessionId2;
|
|
649
|
+
this.sessionName = sessionName2;
|
|
650
|
+
this.hubHost = hubHost2;
|
|
651
|
+
this.hubPort = hubPort2;
|
|
652
|
+
this.token = token;
|
|
653
|
+
}
|
|
654
|
+
ws = null;
|
|
655
|
+
reconnectTimer = null;
|
|
656
|
+
messageHandlers = [];
|
|
657
|
+
queue = [];
|
|
658
|
+
connected = false;
|
|
659
|
+
connect() {
|
|
660
|
+
const tokenQuery = this.token ? `?token=${encodeURIComponent(this.token)}` : "";
|
|
661
|
+
const url = `ws://${this.hubHost}:${this.hubPort}${WS_PATH_CHANNEL}${tokenQuery}`;
|
|
662
|
+
logger.debug(`Connecting to hub at ${url}`);
|
|
663
|
+
try {
|
|
664
|
+
this.ws = new WebSocket2(url);
|
|
665
|
+
this.ws.on("open", () => {
|
|
666
|
+
logger.info("Connected to hub");
|
|
667
|
+
this.connected = true;
|
|
668
|
+
const registration = {
|
|
669
|
+
type: "register",
|
|
670
|
+
session: {
|
|
671
|
+
id: this.sessionId,
|
|
672
|
+
name: this.sessionName,
|
|
673
|
+
status: "idle",
|
|
674
|
+
connectedAt: Date.now(),
|
|
675
|
+
lastActivity: Date.now(),
|
|
676
|
+
cwd: process.cwd()
|
|
677
|
+
}
|
|
678
|
+
};
|
|
679
|
+
this.ws.send(JSON.stringify(registration));
|
|
680
|
+
for (const msg of this.queue) {
|
|
681
|
+
this.ws.send(JSON.stringify(msg));
|
|
682
|
+
}
|
|
683
|
+
this.queue = [];
|
|
684
|
+
});
|
|
685
|
+
this.ws.on("message", (data) => {
|
|
686
|
+
try {
|
|
687
|
+
const msg = JSON.parse(data.toString());
|
|
688
|
+
for (const handler of this.messageHandlers) {
|
|
689
|
+
handler(msg);
|
|
690
|
+
}
|
|
691
|
+
} catch (err) {
|
|
692
|
+
logger.warn("Failed to parse hub message:", err);
|
|
693
|
+
}
|
|
694
|
+
});
|
|
695
|
+
this.ws.on("close", () => {
|
|
696
|
+
logger.info("Disconnected from hub");
|
|
697
|
+
this.connected = false;
|
|
698
|
+
this.scheduleReconnect();
|
|
699
|
+
});
|
|
700
|
+
this.ws.on("error", (err) => {
|
|
701
|
+
logger.debug(`Hub connection error: ${err.message}`);
|
|
702
|
+
this.connected = false;
|
|
703
|
+
});
|
|
704
|
+
} catch {
|
|
705
|
+
logger.debug("Failed to connect to hub, will retry");
|
|
706
|
+
this.scheduleReconnect();
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
send(msg) {
|
|
710
|
+
if (this.connected && this.ws?.readyState === WebSocket2.OPEN) {
|
|
711
|
+
this.ws.send(JSON.stringify(msg));
|
|
712
|
+
} else {
|
|
713
|
+
if (this.queue.length < 100) {
|
|
714
|
+
this.queue.push(msg);
|
|
715
|
+
}
|
|
716
|
+
logger.debug("Hub not connected, message queued");
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
onMessage(handler) {
|
|
720
|
+
this.messageHandlers.push(handler);
|
|
721
|
+
}
|
|
722
|
+
disconnect() {
|
|
723
|
+
if (this.reconnectTimer) {
|
|
724
|
+
clearTimeout(this.reconnectTimer);
|
|
725
|
+
this.reconnectTimer = null;
|
|
726
|
+
}
|
|
727
|
+
if (this.ws) {
|
|
728
|
+
this.ws.close();
|
|
729
|
+
this.ws = null;
|
|
730
|
+
}
|
|
731
|
+
this.connected = false;
|
|
732
|
+
}
|
|
733
|
+
scheduleReconnect() {
|
|
734
|
+
if (this.reconnectTimer) return;
|
|
735
|
+
this.reconnectTimer = setTimeout(() => {
|
|
736
|
+
this.reconnectTimer = null;
|
|
737
|
+
this.connect();
|
|
738
|
+
}, 5e3);
|
|
739
|
+
}
|
|
740
|
+
};
|
|
582
741
|
}
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
742
|
+
});
|
|
743
|
+
|
|
744
|
+
// src/channel/server.ts
|
|
745
|
+
var server_exports2 = {};
|
|
746
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
747
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
748
|
+
import {
|
|
749
|
+
CallToolRequestSchema,
|
|
750
|
+
ListToolsRequestSchema
|
|
751
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
752
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
753
|
+
async function main() {
|
|
754
|
+
logger.info(`Starting MCP channel server (session: ${sessionId})`);
|
|
755
|
+
hubClient.connect();
|
|
756
|
+
hubClient.onMessage(async (msg) => {
|
|
757
|
+
if (msg.type === "message_to_session" && msg.sessionId === sessionId) {
|
|
758
|
+
logger.info(`Message from dashboard: ${msg.content}`);
|
|
759
|
+
await server.notification({
|
|
760
|
+
method: "notifications/claude/channel",
|
|
761
|
+
params: {
|
|
762
|
+
content: msg.content,
|
|
763
|
+
meta: { sender: "dashboard", timestamp: String(Date.now()) }
|
|
764
|
+
}
|
|
765
|
+
});
|
|
596
766
|
}
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
}
|
|
602
|
-
return config;
|
|
603
|
-
}
|
|
604
|
-
function getOrCreateToken() {
|
|
605
|
-
const config = loadConfig();
|
|
606
|
-
return config.hub.token;
|
|
607
|
-
}
|
|
608
|
-
function saveConfig(config) {
|
|
609
|
-
ensureConfigDir();
|
|
610
|
-
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), { encoding: "utf-8", mode: 384 });
|
|
767
|
+
});
|
|
768
|
+
const transport = new StdioServerTransport();
|
|
769
|
+
await server.connect(transport);
|
|
770
|
+
logger.info("MCP channel server running on stdio");
|
|
611
771
|
}
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
772
|
+
var sessionId, sessionName, server, config, hubHost, hubPort, hubToken, hubClient;
|
|
773
|
+
var init_server2 = __esm({
|
|
774
|
+
"src/channel/server.ts"() {
|
|
775
|
+
"use strict";
|
|
776
|
+
init_logger();
|
|
777
|
+
init_constants();
|
|
778
|
+
init_config();
|
|
779
|
+
init_hub_client();
|
|
780
|
+
sessionId = randomUUID2();
|
|
781
|
+
sessionName = process.env.CLAUDE_ALARM_SESSION_NAME ?? `session-${sessionId.slice(0, 8)}`;
|
|
782
|
+
server = new Server(
|
|
783
|
+
{
|
|
784
|
+
name: CHANNEL_SERVER_NAME,
|
|
785
|
+
version: CHANNEL_SERVER_VERSION
|
|
786
|
+
},
|
|
787
|
+
{
|
|
788
|
+
capabilities: {
|
|
789
|
+
experimental: { "claude/channel": {} },
|
|
790
|
+
tools: {}
|
|
791
|
+
},
|
|
792
|
+
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.'
|
|
793
|
+
}
|
|
794
|
+
);
|
|
795
|
+
config = loadConfig();
|
|
796
|
+
hubHost = process.env.CLAUDE_ALARM_HUB_HOST ?? config.hub.host;
|
|
797
|
+
hubPort = process.env.CLAUDE_ALARM_HUB_PORT ? parseInt(process.env.CLAUDE_ALARM_HUB_PORT, 10) : config.hub.port;
|
|
798
|
+
hubToken = process.env.CLAUDE_ALARM_HUB_TOKEN ?? config.hub.token;
|
|
799
|
+
hubClient = new HubClient(
|
|
800
|
+
sessionId,
|
|
801
|
+
sessionName,
|
|
802
|
+
hubHost,
|
|
803
|
+
hubPort,
|
|
804
|
+
hubToken
|
|
805
|
+
);
|
|
806
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
807
|
+
tools: [
|
|
808
|
+
{
|
|
809
|
+
name: "notify",
|
|
810
|
+
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.",
|
|
811
|
+
inputSchema: {
|
|
812
|
+
type: "object",
|
|
813
|
+
properties: {
|
|
814
|
+
title: { type: "string", description: "Notification title (short)" },
|
|
815
|
+
message: { type: "string", description: "Notification body text" },
|
|
816
|
+
level: {
|
|
817
|
+
type: "string",
|
|
818
|
+
enum: ["info", "warning", "error", "success"],
|
|
819
|
+
description: "Notification level (default: info)"
|
|
820
|
+
}
|
|
821
|
+
},
|
|
822
|
+
required: ["title", "message"]
|
|
823
|
+
}
|
|
824
|
+
},
|
|
825
|
+
{
|
|
826
|
+
name: "reply",
|
|
827
|
+
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.",
|
|
828
|
+
inputSchema: {
|
|
829
|
+
type: "object",
|
|
830
|
+
properties: {
|
|
831
|
+
content: { type: "string", description: "Message content to display on the dashboard" }
|
|
832
|
+
},
|
|
833
|
+
required: ["content"]
|
|
834
|
+
}
|
|
835
|
+
},
|
|
836
|
+
{
|
|
837
|
+
name: "status",
|
|
838
|
+
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.',
|
|
839
|
+
inputSchema: {
|
|
840
|
+
type: "object",
|
|
841
|
+
properties: {
|
|
842
|
+
status: {
|
|
843
|
+
type: "string",
|
|
844
|
+
enum: ["idle", "working", "waiting_input"],
|
|
845
|
+
description: "Current session status"
|
|
846
|
+
}
|
|
847
|
+
},
|
|
848
|
+
required: ["status"]
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
]
|
|
852
|
+
}));
|
|
853
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
854
|
+
const { name, arguments: args } = request.params;
|
|
855
|
+
switch (name) {
|
|
856
|
+
case "notify": {
|
|
857
|
+
const title = args?.title;
|
|
858
|
+
const message = args?.message;
|
|
859
|
+
const level = args?.level ?? "info";
|
|
860
|
+
logger.info(`Notify [${level}]: ${title} - ${message}`);
|
|
861
|
+
hubClient.send({
|
|
862
|
+
type: "notify",
|
|
863
|
+
sessionId,
|
|
864
|
+
title,
|
|
865
|
+
message,
|
|
866
|
+
level
|
|
867
|
+
});
|
|
868
|
+
return {
|
|
869
|
+
content: [{ type: "text", text: `Notification sent: "${title}"` }]
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
case "reply": {
|
|
873
|
+
const content = args?.content;
|
|
874
|
+
logger.info(`Reply: ${content.slice(0, 100)}...`);
|
|
875
|
+
hubClient.send({
|
|
876
|
+
type: "reply",
|
|
877
|
+
sessionId,
|
|
878
|
+
content
|
|
879
|
+
});
|
|
880
|
+
return {
|
|
881
|
+
content: [{ type: "text", text: "Message sent to dashboard." }]
|
|
882
|
+
};
|
|
883
|
+
}
|
|
884
|
+
case "status": {
|
|
885
|
+
const status = args?.status;
|
|
886
|
+
logger.info(`Status update: ${status}`);
|
|
887
|
+
hubClient.send({
|
|
888
|
+
type: "status",
|
|
889
|
+
sessionId,
|
|
890
|
+
status
|
|
891
|
+
});
|
|
892
|
+
return {
|
|
893
|
+
content: [{ type: "text", text: `Status updated to "${status}".` }]
|
|
894
|
+
};
|
|
895
|
+
}
|
|
896
|
+
default:
|
|
897
|
+
return {
|
|
898
|
+
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
|
899
|
+
isError: true
|
|
900
|
+
};
|
|
901
|
+
}
|
|
902
|
+
});
|
|
903
|
+
main().catch((err) => {
|
|
904
|
+
logger.error("Fatal error:", err);
|
|
905
|
+
process.exit(1);
|
|
906
|
+
});
|
|
625
907
|
}
|
|
626
|
-
|
|
627
|
-
command: "npx",
|
|
628
|
-
args: ["-y", "@delt/claude-alarm"],
|
|
629
|
-
env: {
|
|
630
|
-
CLAUDE_ALARM_SESSION_NAME: path2.basename(dir)
|
|
631
|
-
}
|
|
632
|
-
};
|
|
633
|
-
fs.writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2), "utf-8");
|
|
634
|
-
return mcpPath;
|
|
635
|
-
}
|
|
908
|
+
});
|
|
636
909
|
|
|
637
910
|
// src/cli.ts
|
|
911
|
+
init_config();
|
|
638
912
|
init_constants();
|
|
639
913
|
init_logger();
|
|
914
|
+
import { spawn } from "child_process";
|
|
915
|
+
import fs3 from "fs";
|
|
916
|
+
import path4 from "path";
|
|
917
|
+
import readline from "readline";
|
|
918
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
640
919
|
var __dirname2 = path4.dirname(fileURLToPath2(import.meta.url));
|
|
641
920
|
function printUsage() {
|
|
642
921
|
console.log(`
|
|
@@ -657,9 +936,9 @@ Quick start:
|
|
|
657
936
|
`);
|
|
658
937
|
}
|
|
659
938
|
async function hubStart(daemon) {
|
|
660
|
-
const
|
|
661
|
-
const host =
|
|
662
|
-
const port =
|
|
939
|
+
const config2 = loadConfig();
|
|
940
|
+
const host = config2.hub.host ?? DEFAULT_HUB_HOST;
|
|
941
|
+
const port = config2.hub.port ?? DEFAULT_HUB_PORT;
|
|
663
942
|
if (fs3.existsSync(PID_FILE)) {
|
|
664
943
|
const pid = parseInt(fs3.readFileSync(PID_FILE, "utf-8").trim(), 10);
|
|
665
944
|
if (isProcessRunning(pid)) {
|
|
@@ -682,7 +961,7 @@ async function hubStart(daemon) {
|
|
|
682
961
|
child.unref();
|
|
683
962
|
console.log(`Hub started as daemon (PID: ${child.pid})`);
|
|
684
963
|
console.log(`Dashboard: http://${host}:${port}`);
|
|
685
|
-
console.log(`Token: ${
|
|
964
|
+
console.log(`Token: ${config2.hub.token}`);
|
|
686
965
|
console.log(`Logs: ${LOG_FILE}`);
|
|
687
966
|
} else {
|
|
688
967
|
console.error("Failed to start hub daemon");
|
|
@@ -690,9 +969,9 @@ async function hubStart(daemon) {
|
|
|
690
969
|
}
|
|
691
970
|
} else {
|
|
692
971
|
console.log(`Starting hub on http://${host}:${port} (press Ctrl+C to stop)`);
|
|
693
|
-
console.log(`Token: ${
|
|
972
|
+
console.log(`Token: ${config2.hub.token}`);
|
|
694
973
|
const { HubServer: HubServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
|
|
695
|
-
const hub = new HubServer2(
|
|
974
|
+
const hub = new HubServer2(config2);
|
|
696
975
|
await hub.start();
|
|
697
976
|
ensureConfigDir();
|
|
698
977
|
fs3.writeFileSync(PID_FILE, String(process.pid), "utf-8");
|
|
@@ -721,9 +1000,9 @@ function hubStop() {
|
|
|
721
1000
|
fs3.unlinkSync(PID_FILE);
|
|
722
1001
|
}
|
|
723
1002
|
async function hubStatus() {
|
|
724
|
-
const
|
|
725
|
-
const host =
|
|
726
|
-
const port =
|
|
1003
|
+
const config2 = loadConfig();
|
|
1004
|
+
const host = config2.hub.host ?? DEFAULT_HUB_HOST;
|
|
1005
|
+
const port = config2.hub.port ?? DEFAULT_HUB_PORT;
|
|
727
1006
|
let pidInfo = "not running";
|
|
728
1007
|
if (fs3.existsSync(PID_FILE)) {
|
|
729
1008
|
const pid = parseInt(fs3.readFileSync(PID_FILE, "utf-8").trim(), 10);
|
|
@@ -742,7 +1021,7 @@ async function hubStatus() {
|
|
|
742
1021
|
console.log(`Sessions: ${data.sessions}`);
|
|
743
1022
|
console.log(`Uptime: ${Math.round(data.uptime / 1e3)}s`);
|
|
744
1023
|
console.log(`Dashboard: http://${host}:${port}`);
|
|
745
|
-
const token =
|
|
1024
|
+
const token = config2.hub.token;
|
|
746
1025
|
if (token) {
|
|
747
1026
|
console.log(`Token: ${token.slice(0, 8)}...(masked)`);
|
|
748
1027
|
}
|
|
@@ -761,13 +1040,13 @@ function setup(targetDir) {
|
|
|
761
1040
|
console.log(" 2. Run Claude Code: claude --dangerously-load-development-channels server:claude-alarm");
|
|
762
1041
|
}
|
|
763
1042
|
async function test() {
|
|
764
|
-
const
|
|
765
|
-
const host =
|
|
766
|
-
const port =
|
|
1043
|
+
const config2 = loadConfig();
|
|
1044
|
+
const host = config2.hub.host ?? DEFAULT_HUB_HOST;
|
|
1045
|
+
const port = config2.hub.port ?? DEFAULT_HUB_PORT;
|
|
767
1046
|
try {
|
|
768
1047
|
const headers = { "Content-Type": "application/json" };
|
|
769
|
-
if (
|
|
770
|
-
headers["Authorization"] = `Bearer ${
|
|
1048
|
+
if (config2.hub.token) {
|
|
1049
|
+
headers["Authorization"] = `Bearer ${config2.hub.token}`;
|
|
771
1050
|
}
|
|
772
1051
|
const res = await fetch(`http://${host}:${port}/api/notify`, {
|
|
773
1052
|
method: "POST",
|
|
@@ -830,16 +1109,16 @@ claude-alarm init for "${projectName}"
|
|
|
830
1109
|
if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
|
|
831
1110
|
mcpConfig.mcpServers["claude-alarm"] = {
|
|
832
1111
|
command: "npx",
|
|
833
|
-
args: ["-y", "@delt/claude-alarm"],
|
|
1112
|
+
args: ["-y", "@delt/claude-alarm", "serve"],
|
|
834
1113
|
env
|
|
835
1114
|
};
|
|
836
1115
|
fs3.writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2), "utf-8");
|
|
837
1116
|
console.log(`
|
|
838
1117
|
\u2713 Created ${mcpPath}`);
|
|
839
1118
|
if (remote.toLowerCase() !== "y") {
|
|
840
|
-
const
|
|
841
|
-
const host =
|
|
842
|
-
const port =
|
|
1119
|
+
const config2 = loadConfig();
|
|
1120
|
+
const host = config2.hub.host ?? DEFAULT_HUB_HOST;
|
|
1121
|
+
const port = config2.hub.port ?? DEFAULT_HUB_PORT;
|
|
843
1122
|
let hubRunning = false;
|
|
844
1123
|
try {
|
|
845
1124
|
const res = await fetch(`http://${host}:${port}/api/status`);
|
|
@@ -856,7 +1135,13 @@ claude-alarm init for "${projectName}"
|
|
|
856
1135
|
}
|
|
857
1136
|
console.log(`
|
|
858
1137
|
Next step:`);
|
|
859
|
-
console.log(` claude --dangerously-load-development-channels server:claude-alarm
|
|
1138
|
+
console.log(` claude --dangerously-load-development-channels server:claude-alarm`);
|
|
1139
|
+
console.log(`
|
|
1140
|
+
To skip permission prompts (allows remote control without approval):`);
|
|
1141
|
+
console.log(` claude --dangerously-load-development-channels server:claude-alarm --dangerously-skip-permissions`);
|
|
1142
|
+
console.log(`
|
|
1143
|
+
WARNING: --dangerously-skip-permissions allows Claude to execute any action`);
|
|
1144
|
+
console.log(` without your approval. Only use in trusted, isolated environments.
|
|
860
1145
|
`);
|
|
861
1146
|
}
|
|
862
1147
|
function showToken() {
|
|
@@ -871,7 +1156,7 @@ function isProcessRunning(pid) {
|
|
|
871
1156
|
return false;
|
|
872
1157
|
}
|
|
873
1158
|
}
|
|
874
|
-
async function
|
|
1159
|
+
async function main2() {
|
|
875
1160
|
const args = process.argv.slice(2);
|
|
876
1161
|
const cmd = args[0];
|
|
877
1162
|
const sub = args[1];
|
|
@@ -879,6 +1164,10 @@ async function main() {
|
|
|
879
1164
|
printUsage();
|
|
880
1165
|
return;
|
|
881
1166
|
}
|
|
1167
|
+
if (cmd === "serve") {
|
|
1168
|
+
await Promise.resolve().then(() => (init_server2(), server_exports2));
|
|
1169
|
+
return;
|
|
1170
|
+
}
|
|
882
1171
|
if (cmd === "init") {
|
|
883
1172
|
await init();
|
|
884
1173
|
return;
|
|
@@ -914,7 +1203,7 @@ async function main() {
|
|
|
914
1203
|
printUsage();
|
|
915
1204
|
process.exit(1);
|
|
916
1205
|
}
|
|
917
|
-
|
|
1206
|
+
main2().catch((err) => {
|
|
918
1207
|
logger.error("CLI error:", err);
|
|
919
1208
|
process.exit(1);
|
|
920
1209
|
});
|