@delt/claude-alarm 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -11,7 +11,7 @@ Claude Code ──reply/notify──> [Channel Server] ──> [Hub Server]
11
11
  ## Features
12
12
 
13
13
  - **Web Dashboard** — Monitor all Claude Code sessions in one place
14
- - **Two-way Messaging** — Send messages to Claude and receive replies
14
+ - **Two-way Messaging** — Send messages to Claude and receive replies (with markdown rendering)
15
15
  - **Desktop Notifications** — Get Windows/macOS/Linux toast notifications
16
16
  - **Session Status** — See which sessions are idle, working, or waiting for input
17
17
  - **Token Auth** — Secure hub access with auto-generated tokens
@@ -19,43 +19,46 @@ Claude Code ──reply/notify──> [Channel Server] ──> [Hub Server]
19
19
 
20
20
  ## Quick Start
21
21
 
22
- ### 1. Install
22
+ ### 1. Install & Initialize
23
+
24
+ In your project directory:
23
25
 
24
26
  ```bash
25
- npm install -g claude-alarm
27
+ npx @delt/claude-alarm init
26
28
  ```
27
29
 
30
+ This creates `.mcp.json` with the claude-alarm channel server config.
31
+
28
32
  ### 2. Start the Hub
29
33
 
30
34
  ```bash
31
- claude-alarm hub start -d
35
+ npx @delt/claude-alarm hub start
32
36
  ```
33
37
 
34
- This starts the hub server in the background and prints your auth token.
38
+ This starts the hub server and prints your auth token.
35
39
 
36
- ### 3. Setup a Project
37
-
38
- In your project directory:
40
+ ### 3. Run Claude Code
39
41
 
40
42
  ```bash
41
- claude-alarm setup
43
+ claude --dangerously-load-development-channels server:claude-alarm
42
44
  ```
43
45
 
44
- This creates `.mcp.json` with the claude-alarm channel server config.
45
-
46
- ### 4. Run Claude Code
46
+ To allow remote control without approval prompts:
47
47
 
48
48
  ```bash
49
- claude --dangerously-load-development-channels server:claude-alarm
49
+ claude --dangerously-load-development-channels server:claude-alarm --dangerously-skip-permissions
50
50
  ```
51
51
 
52
- ### 5. Open Dashboard
52
+ > **WARNING:** `--dangerously-skip-permissions` allows Claude to execute any action without your approval. Only use in trusted, isolated environments.
53
+
54
+ ### 4. Open Dashboard
53
55
 
54
56
  Open `http://127.0.0.1:7890` in your browser.
55
57
 
56
58
  ## CLI Commands
57
59
 
58
60
  ```
61
+ claude-alarm init Setup everything and show next steps
59
62
  claude-alarm hub start [-d] Start the hub server (-d for daemon mode)
60
63
  claude-alarm hub stop Stop the hub daemon
61
64
  claude-alarm hub status Show hub status
@@ -101,14 +104,14 @@ Config is stored at `~/.claude-alarm/config.json`:
101
104
 
102
105
  ### Custom Session Names
103
106
 
104
- The `.mcp.json` created by `claude-alarm setup` automatically uses the project directory name as the session name. You can customize it:
107
+ The `.mcp.json` created by `claude-alarm init` automatically uses the project directory name as the session name. You can customize it:
105
108
 
106
109
  ```json
107
110
  {
108
111
  "mcpServers": {
109
112
  "claude-alarm": {
110
113
  "command": "npx",
111
- "args": ["-y", "claude-alarm"],
114
+ "args": ["-y", "@delt/claude-alarm", "serve"],
112
115
  "env": {
113
116
  "CLAUDE_ALARM_SESSION_NAME": "my-project"
114
117
  }
@@ -138,14 +141,14 @@ To access the hub from another machine:
138
141
 
139
142
  1. Set host to `0.0.0.0` in `~/.claude-alarm/config.json`
140
143
  2. Open port 7890 in your firewall
141
- 3. On the remote machine, set the hub address in `.mcp.json`:
144
+ 3. On the remote machine, run `claude-alarm init` and select remote hub (Y), or manually set `.mcp.json`:
142
145
 
143
146
  ```json
144
147
  {
145
148
  "mcpServers": {
146
149
  "claude-alarm": {
147
150
  "command": "npx",
148
- "args": ["-y", "claude-alarm"],
151
+ "args": ["-y", "@delt/claude-alarm", "serve"],
149
152
  "env": {
150
153
  "CLAUDE_ALARM_HUB_HOST": "your-server-ip",
151
154
  "CLAUDE_ALARM_HUB_PORT": "7890",
@@ -156,6 +159,18 @@ To access the hub from another machine:
156
159
  }
157
160
  ```
158
161
 
162
+ ## Platform Support
163
+
164
+ Desktop notifications work across all major platforms via [node-notifier](https://github.com/mikaelbr/node-notifier):
165
+
166
+ | Platform | Notification Engine | Click to Open |
167
+ |----------|-------------------|---------------|
168
+ | Windows | SnoreToast (built-in) | `Start-Process` |
169
+ | macOS | terminal-notifier | `open` |
170
+ | Linux | notify-send (libnotify) | `xdg-open` |
171
+
172
+ No additional setup needed — `node-notifier` automatically detects your OS and uses the appropriate tool.
173
+
159
174
  ## Requirements
160
175
 
161
176
  - Node.js >= 18
@@ -117,7 +117,8 @@ var HubClient = class {
117
117
  status: "idle",
118
118
  connectedAt: Date.now(),
119
119
  lastActivity: Date.now(),
120
- cwd: process.cwd()
120
+ cwd: process.cwd(),
121
+ channelEnabled: true
121
122
  }
122
123
  };
123
124
  this.ws.send(JSON.stringify(registration));
@@ -1 +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', 'serve'],\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"]}
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', 'serve'],\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 channelEnabled: true,\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"]}
package/dist/cli.js CHANGED
@@ -382,14 +382,20 @@ var init_server = __esm({
382
382
  }
383
383
  stop() {
384
384
  return new Promise((resolve) => {
385
- for (const ws of this.channelSockets.values()) ws.close();
386
- for (const ws of this.dashboardSockets) ws.close();
385
+ for (const ws of this.channelSockets.values()) ws.terminate();
386
+ for (const ws of this.dashboardSockets) ws.terminate();
387
+ this.channelSockets.clear();
388
+ this.dashboardSockets.clear();
387
389
  this.wssChannel.close();
388
390
  this.wssDashboard.close();
389
391
  this.httpServer.close(() => {
390
392
  logger.info("Hub server stopped");
391
393
  resolve();
392
394
  });
395
+ setTimeout(() => {
396
+ logger.warn("Force shutting down");
397
+ resolve();
398
+ }, 3e3);
393
399
  });
394
400
  }
395
401
  // --- HTTP Handler ---
@@ -523,10 +529,14 @@ var init_server = __esm({
523
529
  switch (msg.type) {
524
530
  case "register": {
525
531
  const session = msg.session;
532
+ const isReregister = !!this.sessions.get(session.id);
526
533
  this.sessions.register(session);
527
534
  this.channelSockets.set(session.id, ws);
528
- logger.info(`Session registered: ${session.id} (${session.name})`);
529
- this.broadcastToDashboards({ type: "session_connected", session });
535
+ logger.info(`Session registered: ${session.id} (${session.name}, channel: ${session.channelEnabled ?? false})`);
536
+ this.broadcastToDashboards({
537
+ type: isReregister ? "session_updated" : "session_connected",
538
+ session
539
+ });
530
540
  break;
531
541
  }
532
542
  case "status": {
@@ -684,7 +694,8 @@ var init_hub_client = __esm({
684
694
  status: "idle",
685
695
  connectedAt: Date.now(),
686
696
  lastActivity: Date.now(),
687
- cwd: process.cwd()
697
+ cwd: process.cwd(),
698
+ channelEnabled: true
688
699
  }
689
700
  };
690
701
  this.ws.send(JSON.stringify(registration));