coding-tool-x 3.4.11 → 3.4.12

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.
@@ -5,14 +5,14 @@
5
5
  <link rel="icon" href="/favicon.ico">
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
7
  <title>CC-TOOL - ClaudeCode增强工作助手</title>
8
- <script type="module" crossorigin src="/assets/index-D547X48u.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-EMrm1wk-.js"></script>
9
9
  <link rel="modulepreload" crossorigin href="/assets/markdown-DyTJGI4N.js">
10
10
  <link rel="modulepreload" crossorigin href="/assets/vue-vendor-3bf-fPGP.js">
11
11
  <link rel="modulepreload" crossorigin href="/assets/vendors-CKPV1OAU.js">
12
12
  <link rel="modulepreload" crossorigin href="/assets/naive-ui-Bdxp09n2.js">
13
13
  <link rel="modulepreload" crossorigin href="/assets/icons-B5Pl4lrD.js">
14
14
  <link rel="stylesheet" crossorigin href="/assets/markdown-BfC0goYb.css">
15
- <link rel="stylesheet" crossorigin href="/assets/index-NC-fbfg8.css">
15
+ <link rel="stylesheet" crossorigin href="/assets/index-B02wDWNC.css">
16
16
  </head>
17
17
  <body>
18
18
  <div id="app"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coding-tool-x",
3
- "version": "3.4.11",
3
+ "version": "3.4.12",
4
4
  "description": "Vibe Coding 增强工作助手 - 智能会话管理、动态渠道切换、全局搜索、实时监控",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -1,603 +1,56 @@
1
1
  const express = require('express');
2
2
  const router = express.Router();
3
- const fs = require('fs');
4
- const path = require('path');
5
- const os = require('os');
6
- const https = require('https');
7
- const http = require('http');
8
- const { PATHS, NATIVE_PATHS } = require('../../config/paths');
9
- const { resolvePreferredHomeDir, normalizeWindowsHomePath } = require('../../utils/home-dir');
3
+ const notificationHooks = require('../services/notification-hooks');
10
4
  const { createSameOriginGuard } = require('../services/network-access');
5
+ const { resolvePreferredHomeDir, normalizeWindowsHomePath } = require('../../utils/home-dir');
11
6
 
12
- // 检测操作系统
13
- const platform = os.platform(); // 'darwin' | 'win32' | 'linux'
14
7
  router.use(createSameOriginGuard({
15
8
  message: '禁止跨站访问 Claude Hooks 配置接口'
16
9
  }));
17
10
 
18
- const HOME_DIR = resolvePreferredHomeDir(platform, process.env, os.homedir());
19
-
20
- // Claude settings.json 路径
21
- const CLAUDE_SETTINGS_PATH = NATIVE_PATHS.claude.settings;
22
-
23
- // UI 配置路径(记录用户是否主动关闭过、飞书配置等)
24
- const UI_CONFIG_PATH = PATHS.uiConfig;
25
-
26
- // 通知脚本路径(用于飞书通知)
27
- const NOTIFY_SCRIPT_PATH = PATHS.notifyHook;
28
-
29
- function buildWindowsPopupCommand() {
30
- const script = [
31
- 'Add-Type -AssemblyName System.Windows.Forms',
32
- 'Add-Type -AssemblyName System.Drawing',
33
- '$form = New-Object System.Windows.Forms.Form',
34
- "$form.Text = 'Coding Tool'",
35
- '$form.Width = 360',
36
- '$form.Height = 120',
37
- '$form.StartPosition = [System.Windows.Forms.FormStartPosition]::Manual',
38
- '$form.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::FixedToolWindow',
39
- '$form.ShowInTaskbar = $false',
40
- '$form.TopMost = $true',
41
- '$form.MaximizeBox = $false',
42
- '$form.MinimizeBox = $false',
43
- '$workingArea = [System.Windows.Forms.Screen]::PrimaryScreen.WorkingArea',
44
- '$form.Location = New-Object System.Drawing.Point(($workingArea.Right - $form.Width - 16), ($workingArea.Top + 16))',
45
- '$titleLabel = New-Object System.Windows.Forms.Label',
46
- "$titleLabel.Text = 'Coding Tool'",
47
- "$titleLabel.Font = New-Object System.Drawing.Font('Segoe UI', 10, [System.Drawing.FontStyle]::Bold)",
48
- '$titleLabel.AutoSize = $true',
49
- '$titleLabel.Location = New-Object System.Drawing.Point(14, 12)',
50
- '$messageLabel = New-Object System.Windows.Forms.Label',
51
- "$messageLabel.Text = '任务已完成 | 等待交互'",
52
- "$messageLabel.Font = New-Object System.Drawing.Font('Segoe UI', 9)",
53
- '$messageLabel.MaximumSize = New-Object System.Drawing.Size(332, 0)',
54
- '$messageLabel.AutoSize = $true',
55
- '$messageLabel.Location = New-Object System.Drawing.Point(14, 40)',
56
- '$form.Controls.Add($titleLabel)',
57
- '$form.Controls.Add($messageLabel)',
58
- '$timer = New-Object System.Windows.Forms.Timer',
59
- '$timer.Interval = 5000',
60
- '$timer.Add_Tick({ $timer.Stop(); $form.Close() })',
61
- '$timer.Start()',
62
- '[void]$form.ShowDialog()'
63
- ].join('; ');
64
- return `powershell -NoProfile -Command ${JSON.stringify(script)}`;
65
- }
66
-
67
- // 读取 Claude settings.json
68
- function readClaudeSettings() {
69
- try {
70
- if (fs.existsSync(CLAUDE_SETTINGS_PATH)) {
71
- const content = fs.readFileSync(CLAUDE_SETTINGS_PATH, 'utf8');
72
- return JSON.parse(content);
73
- }
74
- return {};
75
- } catch (error) {
76
- console.error('Failed to read Claude settings:', error);
77
- return {};
78
- }
79
- }
80
-
81
- // 写入 Claude settings.json
82
- function writeClaudeSettings(settings) {
83
- try {
84
- const dir = path.dirname(CLAUDE_SETTINGS_PATH);
85
- if (!fs.existsSync(dir)) {
86
- fs.mkdirSync(dir, { recursive: true });
87
- }
88
- fs.writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2), 'utf8');
89
- return true;
90
- } catch (error) {
91
- console.error('Failed to write Claude settings:', error);
92
- return false;
93
- }
94
- }
95
-
96
- // 读取 UI 配置
97
- function readUIConfig() {
98
- try {
99
- if (fs.existsSync(UI_CONFIG_PATH)) {
100
- const content = fs.readFileSync(UI_CONFIG_PATH, 'utf8');
101
- return JSON.parse(content);
102
- }
103
- return {};
104
- } catch (error) {
105
- return {};
106
- }
107
- }
108
-
109
- // 写入 UI 配置
110
- function writeUIConfig(config) {
111
- try {
112
- const dir = path.dirname(UI_CONFIG_PATH);
113
- if (!fs.existsSync(dir)) {
114
- fs.mkdirSync(dir, { recursive: true });
115
- }
116
- fs.writeFileSync(UI_CONFIG_PATH, JSON.stringify(config, null, 2), 'utf8');
117
- return true;
118
- } catch (error) {
119
- console.error('Failed to write UI config:', error);
120
- return false;
121
- }
122
- }
123
-
124
- // 生成系统通知命令(跨平台)
125
- function generateSystemNotificationCommand(type, platformOverride = platform) {
126
- if (platformOverride === 'darwin') {
127
- // macOS
128
- if (type === 'dialog') {
129
- return `osascript -e 'display dialog "Claude Code 任务已完成 | 等待交互" with title "Coding Tool" buttons {"好的"} default button 1 with icon note'`;
130
- } else {
131
- // 优先使用 terminal-notifier(点击可打开终端),否则使用 osascript
132
- // terminal-notifier 需要 brew install terminal-notifier
133
- return `if command -v terminal-notifier &>/dev/null; then terminal-notifier -title "Coding Tool" -message "任务已完成 | 等待交互" -sound Glass -activate com.apple.Terminal; else osascript -e 'display notification "任务已完成 | 等待交互" with title "Coding Tool" sound name "Glass"'; fi`;
134
- }
135
- } else if (platformOverride === 'win32') {
136
- // Windows
137
- if (type === 'dialog') {
138
- return `powershell -Command "Add-Type -AssemblyName PresentationFramework; [System.Windows.MessageBox]::Show('Claude Code 任务已完成 | 等待交互', 'Coding Tool', 'OK', 'Information')" || ${buildWindowsPopupCommand()}`;
139
- } else {
140
- return buildWindowsPopupCommand();
141
- }
142
- } else {
143
- // Linux
144
- if (type === 'dialog') {
145
- return `zenity --info --title="Coding Tool" --text="Claude Code 任务已完成 | 等待交互" 2>/dev/null || notify-send "Coding Tool" "任务已完成 | 等待交互"`;
146
- } else {
147
- return `notify-send "Coding Tool" "任务已完成 | 等待交互"`;
148
- }
149
- }
150
- }
151
-
152
- // 生成通知脚本内容(支持系统通知 + 飞书通知)
153
- function generateNotifyScript(config) {
154
- const { systemNotification, feishu } = config;
155
-
156
- let script = `#!/usr/bin/env node
157
- // CC-Tool 通知脚本 - 自动生成,请勿手动修改
158
- const https = require('https');
159
- const http = require('http');
160
- const { execSync } = require('child_process');
161
- const os = require('os');
162
-
163
- const platform = os.platform();
164
- const timestamp = new Date().toLocaleString('zh-CN');
165
-
166
- `;
167
-
168
- // 系统通知部分
169
- if (systemNotification && systemNotification.enabled) {
170
- const cmd = generateSystemNotificationCommand(systemNotification.type);
171
- script += `// 系统通知
172
- try {
173
- execSync(${JSON.stringify(cmd)}, { stdio: 'ignore', windowsHide: true });
174
- } catch (e) {
175
- console.error('系统通知失败:', e.message);
176
- }
177
-
178
- `;
179
- }
180
-
181
- // 飞书通知部分
182
- if (feishu && feishu.enabled && feishu.webhookUrl) {
183
- script += `// 飞书通知
184
- const feishuUrl = ${JSON.stringify(feishu.webhookUrl)};
185
- const feishuData = JSON.stringify({
186
- msg_type: 'interactive',
187
- card: {
188
- header: {
189
- title: { tag: 'plain_text', content: '[DONE] Coding Tool - 任务完成' },
190
- template: 'green'
191
- },
192
- elements: [
193
- {
194
- tag: 'div',
195
- text: { tag: 'lark_md', content: '**状态**: Claude Code 任务已完成 | 等待交互' }
196
- },
197
- {
198
- tag: 'div',
199
- text: { tag: 'lark_md', content: '**时间**: ' + timestamp }
200
- },
201
- {
202
- tag: 'div',
203
- text: { tag: 'lark_md', content: '**设备**: ' + os.hostname() }
204
- }
205
- ]
206
- }
207
- });
208
-
209
- try {
210
- const urlObj = new URL(feishuUrl);
211
- const options = {
212
- hostname: urlObj.hostname,
213
- port: urlObj.port || (urlObj.protocol === 'https:' ? 443 : 80),
214
- path: urlObj.pathname + urlObj.search,
215
- method: 'POST',
216
- headers: {
217
- 'Content-Type': 'application/json',
218
- 'Content-Length': Buffer.byteLength(feishuData)
219
- },
220
- timeout: 10000
221
- };
222
-
223
- const reqModule = urlObj.protocol === 'https:' ? https : http;
224
- const req = reqModule.request(options, (res) => {
225
- // 忽略响应
226
- });
227
- req.on('error', (e) => {
228
- console.error('飞书通知失败:', e.message);
229
- });
230
- req.write(feishuData);
231
- req.end();
232
- } catch (e) {
233
- console.error('飞书通知失败:', e.message);
234
- }
235
- `;
236
- }
237
-
238
- return script;
239
- }
240
-
241
- function parseNotifyTypeMarker(command) {
242
- const marker = command.match(/--cc-notify-type=(['"])?(dialog|notification)\1/i);
243
- return marker ? marker[2].toLowerCase() : null;
244
- }
245
-
246
- function getStopHookCommand(settings) {
247
- const hooks = settings?.hooks?.Stop;
248
- if (!Array.isArray(hooks) || hooks.length === 0) {
249
- return '';
250
- }
251
- const firstHook = hooks[0]?.hooks;
252
- if (!Array.isArray(firstHook) || firstHook.length === 0) {
253
- return '';
254
- }
255
- return firstHook[0]?.command || '';
256
- }
257
-
258
- function normalizePathForCompare(rawPath) {
259
- return String(rawPath || '').replace(/\\/g, '/');
260
- }
261
-
262
- function shouldRepairStopHook(settings, expectedScriptPath = NOTIFY_SCRIPT_PATH, fileExists = fs.existsSync) {
263
- const command = getStopHookCommand(settings);
264
- if (!command || !command.includes('notify-hook.js')) {
265
- return false;
266
- }
267
-
268
- const normalizedCommand = normalizePathForCompare(command);
269
- const normalizedExpected = normalizePathForCompare(expectedScriptPath);
270
- if (!normalizedCommand.includes(normalizedExpected)) {
271
- return true;
272
- }
273
-
274
- const markerType = parseNotifyTypeMarker(command);
275
- if (!markerType) {
276
- return true;
277
- }
278
-
279
- return !fileExists(expectedScriptPath);
280
- }
281
-
282
- function buildStopHookCommand(type) {
283
- const notifyType = type === 'dialog' ? 'dialog' : 'notification';
284
- return `node "${NOTIFY_SCRIPT_PATH}" --cc-notify-type=${notifyType}`;
285
- }
286
-
287
- // 写入通知脚本
288
- function writeNotifyScript(config) {
289
- try {
290
- const dir = path.dirname(NOTIFY_SCRIPT_PATH);
291
- if (!fs.existsSync(dir)) {
292
- fs.mkdirSync(dir, { recursive: true });
293
- }
294
-
295
- const script = generateNotifyScript(config);
296
- fs.writeFileSync(NOTIFY_SCRIPT_PATH, script, { mode: 0o755 });
297
- return true;
298
- } catch (error) {
299
- console.error('Failed to write notify script:', error);
300
- return false;
301
- }
302
- }
303
-
304
- // 从现有 hooks 配置中解析 Stop hook 状态
305
- function parseStopHookStatus(settings) {
306
- const hooks = settings.hooks;
307
- if (!hooks || !hooks.Stop || !Array.isArray(hooks.Stop) || hooks.Stop.length === 0) {
308
- return { enabled: false, type: 'notification' };
309
- }
310
-
311
- const stopHook = hooks.Stop[0];
312
- if (!stopHook.hooks || !Array.isArray(stopHook.hooks) || stopHook.hooks.length === 0) {
313
- return { enabled: false, type: 'notification' };
314
- }
315
-
316
- const command = stopHook.hooks[0].command || '';
317
- const markerType = parseNotifyTypeMarker(command);
318
-
319
- if (markerType) {
320
- return { enabled: true, type: markerType };
321
- }
322
-
323
- // 判断通知类型(跨平台检测)
324
- const isDialog = command.includes('display dialog') ||
325
- command.includes('MessageBox') ||
326
- command.includes('zenity --info');
327
- const isNotification = command.includes('display notification') ||
328
- command.includes('Popup') ||
329
- command.includes('notify-send') ||
330
- command.includes('ToastNotificationManager') ||
331
- command.includes('CreateToastNotifier');
332
-
333
- // 检查是否是我们的通知脚本
334
- const isOurScript = command.includes('notify-hook.js');
335
-
336
- if (isDialog || isNotification || isOurScript) {
337
- return {
338
- enabled: true,
339
- type: isDialog ? 'dialog' : 'notification'
340
- };
341
- }
342
-
343
- return { enabled: false, type: 'notification' };
344
- }
345
-
346
- // 获取飞书配置
347
- function getFeishuConfig() {
348
- const uiConfig = readUIConfig();
349
- return {
350
- enabled: uiConfig.feishuNotification?.enabled || false,
351
- webhookUrl: uiConfig.feishuNotification?.webhookUrl || ''
352
- };
353
- }
354
-
355
- // 保存飞书配置
356
- function saveFeishuConfig(feishu) {
357
- const uiConfig = readUIConfig();
358
- uiConfig.feishuNotification = {
359
- enabled: feishu.enabled || false,
360
- webhookUrl: feishu.webhookUrl || ''
361
- };
362
- return writeUIConfig(uiConfig);
363
- }
364
-
365
- // 更新 Stop hook 配置
366
- function updateStopHook(systemNotification, feishu) {
367
- const settings = readClaudeSettings();
368
-
369
- // 检查是否有任何通知需要启用
370
- const hasSystemNotification = systemNotification && systemNotification.enabled;
371
- const hasFeishu = feishu && feishu.enabled && feishu.webhookUrl;
372
-
373
- if (!hasSystemNotification && !hasFeishu) {
374
- // 都关闭了,移除 Stop hook
375
- if (settings.hooks && settings.hooks.Stop) {
376
- delete settings.hooks.Stop;
377
- if (Object.keys(settings.hooks).length === 0) {
378
- delete settings.hooks;
379
- }
380
- }
381
- // 删除通知脚本
382
- if (fs.existsSync(NOTIFY_SCRIPT_PATH)) {
383
- fs.unlinkSync(NOTIFY_SCRIPT_PATH);
384
- }
385
- } else {
386
- // 生成并写入通知脚本
387
- if (!writeNotifyScript({ systemNotification, feishu })) {
388
- return false;
389
- }
390
-
391
- // 更新 Stop hook 指向通知脚本
392
- settings.hooks = settings.hooks || {};
393
- settings.hooks.Stop = [
394
- {
395
- hooks: [
396
- {
397
- type: 'command',
398
- command: buildStopHookCommand(systemNotification?.type)
399
- }
400
- ]
401
- }
402
- ];
403
- }
404
-
405
- return writeClaudeSettings(settings);
406
- }
407
-
408
- // 初始化默认 hooks 配置(服务启动时调用)
409
- function initDefaultHooks() {
410
- try {
411
- const uiConfig = readUIConfig();
412
-
413
- // 如果用户主动关闭过通知,不自动开启
414
- if (uiConfig.claudeNotificationDisabledByUser === true) {
415
- console.log('[Claude Hooks] 用户已主动关闭通知,跳过自动初始化');
416
- return;
417
- }
418
-
419
- const settings = readClaudeSettings();
420
- const currentStatus = parseStopHookStatus(settings);
421
-
422
- // 如果已经有 Stop hook 配置,优先尝试自愈旧路径,再决定是否跳过
423
- if (currentStatus.enabled) {
424
- if (shouldRepairStopHook(settings)) {
425
- const systemNotification = {
426
- enabled: true,
427
- type: currentStatus.type || 'notification'
428
- };
429
- const feishu = getFeishuConfig();
430
- if (updateStopHook(systemNotification, feishu)) {
431
- console.log('[Claude Hooks] 检测到旧版 Stop hook 路径,已自动修复');
432
- } else {
433
- console.warn('[Claude Hooks] Stop hook 路径修复失败,保留原配置');
434
- }
435
- } else {
436
- console.log('[Claude Hooks] 已存在 Stop hook 配置,跳过初始化');
437
- }
438
- return;
439
- }
440
-
441
- // 写入默认配置(右上角卡片通知)
442
- const systemNotification = { enabled: true, type: 'notification' };
443
- const feishu = getFeishuConfig();
444
-
445
- if (updateStopHook(systemNotification, feishu)) {
446
- console.log('[Claude Hooks] 已自动开启任务完成通知(右上角卡片)');
447
- }
448
- } catch (error) {
449
- console.error('[Claude Hooks] 初始化默认配置失败:', error);
450
- }
451
- }
452
-
453
- // GET /api/claude/hooks - 获取 hooks 配置状态
454
11
  router.get('/', (req, res) => {
455
12
  try {
456
- const settings = readClaudeSettings();
457
- const stopHook = parseStopHookStatus(settings);
458
- const feishu = getFeishuConfig();
459
-
460
- res.json({
461
- success: true,
462
- stopHook,
463
- feishu,
464
- platform
465
- });
13
+ res.json(notificationHooks.getLegacyClaudeHookSettings());
466
14
  } catch (error) {
467
15
  console.error('Error getting Claude hooks:', error);
468
- res.status(500).json({ error: error.message });
16
+ res.status(error.statusCode || 500).json({ error: error.message });
469
17
  }
470
18
  });
471
19
 
472
- // POST /api/claude/hooks - 保存 hooks 配置
473
20
  router.post('/', (req, res) => {
474
21
  try {
475
- const { stopHook, feishu } = req.body;
476
-
477
- // 保存飞书配置到 UI 配置文件
478
- if (feishu !== undefined) {
479
- saveFeishuConfig(feishu);
480
- }
481
-
482
- // 更新 Stop hook
483
- const systemNotification = stopHook ? {
484
- enabled: stopHook.enabled,
485
- type: stopHook.type || 'notification'
486
- } : { enabled: false, type: 'notification' };
487
-
488
- const feishuConfig = feishu || getFeishuConfig();
489
-
490
- // 更新用户关闭标记
491
- const uiConfig = readUIConfig();
492
- if (systemNotification.enabled || feishuConfig.enabled) {
493
- // 用户开启了通知,清除关闭标记
494
- if (uiConfig.claudeNotificationDisabledByUser) {
495
- delete uiConfig.claudeNotificationDisabledByUser;
496
- writeUIConfig(uiConfig);
497
- }
498
- } else {
499
- // 用户关闭了所有通知
500
- uiConfig.claudeNotificationDisabledByUser = true;
501
- writeUIConfig(uiConfig);
502
- }
503
-
504
- if (updateStopHook(systemNotification, feishuConfig)) {
505
- res.json({
506
- success: true,
507
- message: '配置已保存',
508
- stopHook: systemNotification,
509
- feishu: feishuConfig
510
- });
511
- } else {
512
- res.status(500).json({ error: '保存配置失败' });
513
- }
22
+ const result = notificationHooks.saveLegacyClaudeHookSettings(req.body || {});
23
+ res.json({
24
+ ...result,
25
+ message: '配置已保存'
26
+ });
514
27
  } catch (error) {
515
28
  console.error('Error saving Claude hooks:', error);
516
- res.status(500).json({ error: error.message });
29
+ res.status(error.statusCode || 500).json({ error: error.message });
517
30
  }
518
31
  });
519
32
 
520
- // POST /api/claude/hooks/test - 测试通知
521
- router.post('/test', (req, res) => {
33
+ router.post('/test', async (req, res) => {
522
34
  try {
523
- const { type, testFeishu, webhookUrl } = req.body;
524
-
525
- if (testFeishu && webhookUrl) {
526
- // 测试飞书通知
527
- const urlObj = new URL(webhookUrl);
528
- const data = JSON.stringify({
529
- msg_type: 'interactive',
530
- card: {
531
- header: {
532
- title: { tag: 'plain_text', content: '[TEST] Coding Tool - 测试通知' },
533
- template: 'blue'
534
- },
535
- elements: [
536
- {
537
- tag: 'div',
538
- text: { tag: 'lark_md', content: '**状态**: 这是一条测试通知' }
539
- },
540
- {
541
- tag: 'div',
542
- text: { tag: 'lark_md', content: '**时间**: ' + new Date().toLocaleString('zh-CN') }
543
- },
544
- {
545
- tag: 'div',
546
- text: { tag: 'lark_md', content: '**设备**: ' + os.hostname() }
547
- }
548
- ]
549
- }
550
- });
551
-
552
- const options = {
553
- hostname: urlObj.hostname,
554
- port: urlObj.port || (urlObj.protocol === 'https:' ? 443 : 80),
555
- path: urlObj.pathname + urlObj.search,
556
- method: 'POST',
557
- headers: {
558
- 'Content-Type': 'application/json',
559
- 'Content-Length': Buffer.byteLength(data)
560
- },
561
- timeout: 10000
562
- };
563
-
564
- const reqModule = urlObj.protocol === 'https:' ? https : http;
565
- const request = reqModule.request(options, (response) => {
566
- let body = '';
567
- response.on('data', chunk => body += chunk);
568
- response.on('end', () => {
569
- res.json({ success: true, message: '飞书测试通知已发送' });
570
- });
571
- });
572
-
573
- request.on('error', (e) => {
574
- res.status(500).json({ error: '飞书通知发送失败: ' + e.message });
575
- });
576
-
577
- request.write(data);
578
- request.end();
579
- } else {
580
- // 测试系统通知
581
- const command = generateSystemNotificationCommand(type || 'notification');
582
- const { execSync } = require('child_process');
583
- execSync(command, { stdio: 'ignore', windowsHide: true });
584
- res.json({ success: true, message: '系统测试通知已发送' });
585
- }
35
+ await notificationHooks.testNotification(req.body || {});
36
+ res.json({
37
+ success: true,
38
+ message: req.body?.testFeishu ? '飞书测试通知已发送' : '系统测试通知已发送'
39
+ });
586
40
  } catch (error) {
587
41
  console.error('Error testing notification:', error);
588
- res.status(500).json({ error: error.message });
42
+ res.status(error.statusCode || 500).json({ error: error.message });
589
43
  }
590
44
  });
591
45
 
592
- // 导出初始化函数供服务启动时调用
593
46
  module.exports = router;
594
- module.exports.initDefaultHooks = initDefaultHooks;
47
+ module.exports.initDefaultHooks = notificationHooks.initDefaultHooks;
595
48
  module.exports._test = {
596
- generateSystemNotificationCommand,
597
- parseStopHookStatus,
598
- parseNotifyTypeMarker,
599
- buildStopHookCommand,
49
+ generateSystemNotificationCommand: notificationHooks._test.generateSystemNotificationCommand,
50
+ parseStopHookStatus: notificationHooks._test.parseStopHookStatus,
51
+ parseNotifyTypeMarker: notificationHooks._test.parseNotifyTypeMarker,
52
+ buildStopHookCommand: notificationHooks._test.buildStopHookCommand,
600
53
  normalizeWindowsHomePath,
601
54
  resolvePreferredHomeDir,
602
- shouldRepairStopHook
55
+ shouldRepairStopHook: notificationHooks._test.shouldRepairStopHook
603
56
  };
@@ -199,11 +199,12 @@ async function startServer(port, host = '127.0.0.1', options = {}) {
199
199
  app.use('/api/env', require('./api/env'));
200
200
  app.use('/api/skills', require('./api/skills'));
201
201
  const claudeHooks = require('./api/claude-hooks');
202
+ const notificationHooks = require('./services/notification-hooks');
202
203
  app.use('/api/claude/hooks', claudeHooks);
203
204
  app.use('/api/hooks', require('./api/hooks'));
204
205
 
205
206
  // 初始化 Claude hooks 默认配置(自动开启任务完成通知)
206
- claudeHooks.initDefaultHooks();
207
+ notificationHooks.initDefaultHooks();
207
208
 
208
209
  // Claude Code 专有功能 API
209
210
  app.use('/api/commands', require('./api/commands'));