@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/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(sessionId) {
65
- const session = this.sessions.get(sessionId);
66
- this.sessions.delete(sessionId);
146
+ unregister(sessionId2) {
147
+ const session = this.sessions.get(sessionId2);
148
+ this.sessions.delete(sessionId2);
67
149
  return session;
68
150
  }
69
- updateStatus(sessionId, status) {
70
- const session = this.sessions.get(sessionId);
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(sessionId) {
78
- const session = this.sessions.get(sessionId);
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(sessionId) {
84
- return this.sessions.get(sessionId);
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(config) {
244
- this.host = config?.hub?.host ?? DEFAULT_HUB_HOST;
245
- this.port = config?.hub?.port ?? DEFAULT_HUB_PORT;
246
- this.token = config?.hub?.token;
247
- if (config?.notifications) {
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: config.notifications.desktop
331
+ desktop: config2.notifications.desktop
250
332
  });
251
333
  }
252
- if (config?.webhooks) {
253
- this.notifier.configure({ webhooks: config.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 (!sessionId || !content) {
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(sessionId);
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 [sessionId, sock] of this.channelSockets) {
500
+ for (const [sessionId2, sock] of this.channelSockets) {
419
501
  if (sock === ws) {
420
- const session = this.sessions.unregister(sessionId);
421
- this.channelSockets.delete(sessionId);
422
- logger.info(`Channel disconnected: ${sessionId}`);
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/cli.ts
557
- import { spawn } from "child_process";
558
- import fs3 from "fs";
559
- import path4 from "path";
560
- import readline from "readline";
561
- import { fileURLToPath as fileURLToPath2 } from "url";
562
-
563
- // src/shared/config.ts
564
- init_constants();
565
- import fs from "fs";
566
- import path2 from "path";
567
- import { randomUUID } from "crypto";
568
- var DEFAULT_CONFIG = {
569
- hub: {
570
- host: DEFAULT_HUB_HOST,
571
- port: DEFAULT_HUB_PORT
572
- },
573
- notifications: {
574
- desktop: true,
575
- sound: true
576
- },
577
- webhooks: []
578
- };
579
- function ensureConfigDir() {
580
- if (!fs.existsSync(CONFIG_DIR)) {
581
- fs.mkdirSync(CONFIG_DIR, { recursive: true });
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
- function loadConfig() {
585
- ensureConfigDir();
586
- let config;
587
- if (!fs.existsSync(CONFIG_FILE)) {
588
- config = { ...DEFAULT_CONFIG, hub: { ...DEFAULT_CONFIG.hub } };
589
- } else {
590
- try {
591
- const raw = fs.readFileSync(CONFIG_FILE, "utf-8");
592
- const parsed = JSON.parse(raw);
593
- config = { ...DEFAULT_CONFIG, ...parsed, hub: { ...DEFAULT_CONFIG.hub, ...parsed.hub } };
594
- } catch {
595
- config = { ...DEFAULT_CONFIG, hub: { ...DEFAULT_CONFIG.hub } };
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
- if (!config.hub.token) {
599
- config.hub.token = randomUUID();
600
- saveConfig(config);
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
- function setupMcpConfig(targetDir) {
613
- const dir = targetDir ?? process.cwd();
614
- const mcpPath = path2.join(dir, ".mcp.json");
615
- let mcpConfig = {};
616
- if (fs.existsSync(mcpPath)) {
617
- try {
618
- mcpConfig = JSON.parse(fs.readFileSync(mcpPath, "utf-8"));
619
- } catch {
620
- mcpConfig = {};
621
- }
622
- }
623
- if (!mcpConfig.mcpServers) {
624
- mcpConfig.mcpServers = {};
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
- mcpConfig.mcpServers["claude-alarm"] = {
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 config = loadConfig();
661
- const host = config.hub.host ?? DEFAULT_HUB_HOST;
662
- const port = config.hub.port ?? DEFAULT_HUB_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: ${config.hub.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: ${config.hub.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(config);
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 config = loadConfig();
725
- const host = config.hub.host ?? DEFAULT_HUB_HOST;
726
- const port = config.hub.port ?? DEFAULT_HUB_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 = config.hub.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 config = loadConfig();
765
- const host = config.hub.host ?? DEFAULT_HUB_HOST;
766
- const port = config.hub.port ?? DEFAULT_HUB_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 (config.hub.token) {
770
- headers["Authorization"] = `Bearer ${config.hub.token}`;
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 config = loadConfig();
841
- const host = config.hub.host ?? DEFAULT_HUB_HOST;
842
- const port = config.hub.port ?? DEFAULT_HUB_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 main() {
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
- main().catch((err) => {
1206
+ main2().catch((err) => {
918
1207
  logger.error("CLI error:", err);
919
1208
  process.exit(1);
920
1209
  });