@adversity/coding-tool-x 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +333 -0
- package/LICENSE +21 -0
- package/README.md +404 -0
- package/bin/ctx.js +8 -0
- package/dist/web/assets/index-D1AYlFLZ.js +3220 -0
- package/dist/web/assets/index-aL3cKxSK.css +41 -0
- package/dist/web/favicon.ico +0 -0
- package/dist/web/index.html +14 -0
- package/dist/web/logo.png +0 -0
- package/docs/CHANGELOG.md +582 -0
- package/docs/DIRECTORY_MIGRATION.md +112 -0
- package/docs/PROJECT_STRUCTURE.md +396 -0
- package/docs/bannel.png +0 -0
- package/docs/home.png +0 -0
- package/docs/logo.png +0 -0
- package/docs/multi-channel-load-balancing.md +249 -0
- package/package.json +73 -0
- package/src/commands/channels.js +504 -0
- package/src/commands/cli-type.js +99 -0
- package/src/commands/daemon.js +286 -0
- package/src/commands/doctor.js +332 -0
- package/src/commands/list.js +222 -0
- package/src/commands/logs.js +259 -0
- package/src/commands/port-config.js +115 -0
- package/src/commands/proxy-control.js +258 -0
- package/src/commands/proxy.js +152 -0
- package/src/commands/resume.js +137 -0
- package/src/commands/search.js +190 -0
- package/src/commands/stats.js +224 -0
- package/src/commands/switch.js +48 -0
- package/src/commands/toggle-proxy.js +222 -0
- package/src/commands/ui.js +92 -0
- package/src/commands/workspace.js +454 -0
- package/src/config/default.js +40 -0
- package/src/config/loader.js +75 -0
- package/src/config/paths.js +121 -0
- package/src/index.js +373 -0
- package/src/reset-config.js +92 -0
- package/src/server/api/agents.js +248 -0
- package/src/server/api/aliases.js +36 -0
- package/src/server/api/channels.js +258 -0
- package/src/server/api/claude-hooks.js +480 -0
- package/src/server/api/codex-channels.js +312 -0
- package/src/server/api/codex-projects.js +91 -0
- package/src/server/api/codex-proxy.js +182 -0
- package/src/server/api/codex-sessions.js +491 -0
- package/src/server/api/codex-statistics.js +57 -0
- package/src/server/api/commands.js +245 -0
- package/src/server/api/config-templates.js +182 -0
- package/src/server/api/config.js +147 -0
- package/src/server/api/convert.js +127 -0
- package/src/server/api/dashboard.js +125 -0
- package/src/server/api/env.js +144 -0
- package/src/server/api/favorites.js +77 -0
- package/src/server/api/gemini-channels.js +261 -0
- package/src/server/api/gemini-projects.js +91 -0
- package/src/server/api/gemini-proxy.js +160 -0
- package/src/server/api/gemini-sessions.js +397 -0
- package/src/server/api/gemini-statistics.js +57 -0
- package/src/server/api/health-check.js +118 -0
- package/src/server/api/mcp.js +336 -0
- package/src/server/api/pm2-autostart.js +269 -0
- package/src/server/api/projects.js +124 -0
- package/src/server/api/prompts.js +279 -0
- package/src/server/api/proxy.js +235 -0
- package/src/server/api/rules.js +271 -0
- package/src/server/api/sessions.js +595 -0
- package/src/server/api/settings.js +61 -0
- package/src/server/api/skills.js +305 -0
- package/src/server/api/statistics.js +91 -0
- package/src/server/api/terminal.js +202 -0
- package/src/server/api/ui-config.js +64 -0
- package/src/server/api/workspaces.js +407 -0
- package/src/server/codex-proxy-server.js +538 -0
- package/src/server/dev-server.js +26 -0
- package/src/server/gemini-proxy-server.js +518 -0
- package/src/server/index.js +305 -0
- package/src/server/proxy-server.js +469 -0
- package/src/server/services/agents-service.js +354 -0
- package/src/server/services/alias.js +71 -0
- package/src/server/services/channel-health.js +234 -0
- package/src/server/services/channel-scheduler.js +234 -0
- package/src/server/services/channels.js +347 -0
- package/src/server/services/codex-channels.js +625 -0
- package/src/server/services/codex-config.js +90 -0
- package/src/server/services/codex-parser.js +322 -0
- package/src/server/services/codex-sessions.js +665 -0
- package/src/server/services/codex-settings-manager.js +397 -0
- package/src/server/services/codex-speed-test-template.json +24 -0
- package/src/server/services/codex-statistics-service.js +255 -0
- package/src/server/services/commands-service.js +360 -0
- package/src/server/services/config-templates-service.js +732 -0
- package/src/server/services/env-checker.js +307 -0
- package/src/server/services/env-manager.js +300 -0
- package/src/server/services/favorites.js +163 -0
- package/src/server/services/gemini-channels.js +333 -0
- package/src/server/services/gemini-config.js +73 -0
- package/src/server/services/gemini-sessions.js +689 -0
- package/src/server/services/gemini-settings-manager.js +263 -0
- package/src/server/services/gemini-statistics-service.js +253 -0
- package/src/server/services/health-check.js +399 -0
- package/src/server/services/mcp-service.js +1188 -0
- package/src/server/services/prompts-service.js +492 -0
- package/src/server/services/proxy-runtime.js +79 -0
- package/src/server/services/pty-manager.js +435 -0
- package/src/server/services/rules-service.js +401 -0
- package/src/server/services/session-cache.js +127 -0
- package/src/server/services/session-converter.js +577 -0
- package/src/server/services/sessions.js +757 -0
- package/src/server/services/settings-manager.js +163 -0
- package/src/server/services/skill-service.js +965 -0
- package/src/server/services/speed-test.js +545 -0
- package/src/server/services/statistics-service.js +386 -0
- package/src/server/services/terminal-commands.js +155 -0
- package/src/server/services/terminal-config.js +140 -0
- package/src/server/services/terminal-detector.js +306 -0
- package/src/server/services/ui-config.js +130 -0
- package/src/server/services/workspace-service.js +662 -0
- package/src/server/utils/pricing.js +41 -0
- package/src/server/websocket-server.js +557 -0
- package/src/ui/menu.js +129 -0
- package/src/ui/prompts.js +100 -0
- package/src/utils/format.js +43 -0
- package/src/utils/port-helper.js +94 -0
- package/src/utils/session.js +239 -0
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
const express = require('express');
|
|
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
|
+
|
|
9
|
+
// Claude settings.json 路径
|
|
10
|
+
const CLAUDE_SETTINGS_PATH = path.join(os.homedir(), '.claude', 'settings.json');
|
|
11
|
+
|
|
12
|
+
// UI 配置路径(记录用户是否主动关闭过、飞书配置等)
|
|
13
|
+
const UI_CONFIG_PATH = path.join(os.homedir(), '.cc-tool', 'ui-config.json');
|
|
14
|
+
|
|
15
|
+
// 通知脚本路径(用于飞书通知)
|
|
16
|
+
const NOTIFY_SCRIPT_PATH = path.join(os.homedir(), '.cc-tool', 'notify-hook.js');
|
|
17
|
+
|
|
18
|
+
// 检测操作系统
|
|
19
|
+
const platform = os.platform(); // 'darwin' | 'win32' | 'linux'
|
|
20
|
+
|
|
21
|
+
// 读取 Claude settings.json
|
|
22
|
+
function readClaudeSettings() {
|
|
23
|
+
try {
|
|
24
|
+
if (fs.existsSync(CLAUDE_SETTINGS_PATH)) {
|
|
25
|
+
const content = fs.readFileSync(CLAUDE_SETTINGS_PATH, 'utf8');
|
|
26
|
+
return JSON.parse(content);
|
|
27
|
+
}
|
|
28
|
+
return {};
|
|
29
|
+
} catch (error) {
|
|
30
|
+
console.error('Failed to read Claude settings:', error);
|
|
31
|
+
return {};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 写入 Claude settings.json
|
|
36
|
+
function writeClaudeSettings(settings) {
|
|
37
|
+
try {
|
|
38
|
+
const dir = path.dirname(CLAUDE_SETTINGS_PATH);
|
|
39
|
+
if (!fs.existsSync(dir)) {
|
|
40
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
41
|
+
}
|
|
42
|
+
fs.writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2), 'utf8');
|
|
43
|
+
return true;
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.error('Failed to write Claude settings:', error);
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 读取 UI 配置
|
|
51
|
+
function readUIConfig() {
|
|
52
|
+
try {
|
|
53
|
+
if (fs.existsSync(UI_CONFIG_PATH)) {
|
|
54
|
+
const content = fs.readFileSync(UI_CONFIG_PATH, 'utf8');
|
|
55
|
+
return JSON.parse(content);
|
|
56
|
+
}
|
|
57
|
+
return {};
|
|
58
|
+
} catch (error) {
|
|
59
|
+
return {};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 写入 UI 配置
|
|
64
|
+
function writeUIConfig(config) {
|
|
65
|
+
try {
|
|
66
|
+
const dir = path.dirname(UI_CONFIG_PATH);
|
|
67
|
+
if (!fs.existsSync(dir)) {
|
|
68
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
69
|
+
}
|
|
70
|
+
fs.writeFileSync(UI_CONFIG_PATH, JSON.stringify(config, null, 2), 'utf8');
|
|
71
|
+
return true;
|
|
72
|
+
} catch (error) {
|
|
73
|
+
console.error('Failed to write UI config:', error);
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// 生成系统通知命令(跨平台)
|
|
79
|
+
function generateSystemNotificationCommand(type) {
|
|
80
|
+
if (platform === 'darwin') {
|
|
81
|
+
// macOS
|
|
82
|
+
if (type === 'dialog') {
|
|
83
|
+
return `osascript -e 'display dialog "Claude Code 任务已完成 | 等待交互" with title "Coding Tool" buttons {"好的"} default button 1 with icon note'`;
|
|
84
|
+
} else {
|
|
85
|
+
// 优先使用 terminal-notifier(点击可打开终端),否则使用 osascript
|
|
86
|
+
// terminal-notifier 需要 brew install terminal-notifier
|
|
87
|
+
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`;
|
|
88
|
+
}
|
|
89
|
+
} else if (platform === 'win32') {
|
|
90
|
+
// Windows
|
|
91
|
+
if (type === 'dialog') {
|
|
92
|
+
return `powershell -Command "Add-Type -AssemblyName PresentationFramework; [System.Windows.MessageBox]::Show('Claude Code 任务已完成 | 等待交互', 'Coding Tool', 'OK', 'Information')"`;
|
|
93
|
+
} else {
|
|
94
|
+
return `powershell -Command "$wshell = New-Object -ComObject Wscript.Shell; $wshell.Popup('任务已完成 | 等待交互', 5, 'Coding Tool', 0x40)"`;
|
|
95
|
+
}
|
|
96
|
+
} else {
|
|
97
|
+
// Linux
|
|
98
|
+
if (type === 'dialog') {
|
|
99
|
+
return `zenity --info --title="Coding Tool" --text="Claude Code 任务已完成 | 等待交互" 2>/dev/null || notify-send "Coding Tool" "任务已完成 | 等待交互"`;
|
|
100
|
+
} else {
|
|
101
|
+
return `notify-send "Coding Tool" "任务已完成 | 等待交互"`;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// 生成通知脚本内容(支持系统通知 + 飞书通知)
|
|
107
|
+
function generateNotifyScript(config) {
|
|
108
|
+
const { systemNotification, feishu } = config;
|
|
109
|
+
|
|
110
|
+
let script = `#!/usr/bin/env node
|
|
111
|
+
// CC-Tool 通知脚本 - 自动生成,请勿手动修改
|
|
112
|
+
const https = require('https');
|
|
113
|
+
const http = require('http');
|
|
114
|
+
const { execSync } = require('child_process');
|
|
115
|
+
const os = require('os');
|
|
116
|
+
|
|
117
|
+
const platform = os.platform();
|
|
118
|
+
const timestamp = new Date().toLocaleString('zh-CN');
|
|
119
|
+
|
|
120
|
+
`;
|
|
121
|
+
|
|
122
|
+
// 系统通知部分
|
|
123
|
+
if (systemNotification && systemNotification.enabled) {
|
|
124
|
+
const cmd = generateSystemNotificationCommand(systemNotification.type);
|
|
125
|
+
script += `// 系统通知
|
|
126
|
+
try {
|
|
127
|
+
execSync(${JSON.stringify(cmd)}, { stdio: 'ignore' });
|
|
128
|
+
} catch (e) {
|
|
129
|
+
console.error('系统通知失败:', e.message);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// 飞书通知部分
|
|
136
|
+
if (feishu && feishu.enabled && feishu.webhookUrl) {
|
|
137
|
+
script += `// 飞书通知
|
|
138
|
+
const feishuUrl = ${JSON.stringify(feishu.webhookUrl)};
|
|
139
|
+
const feishuData = JSON.stringify({
|
|
140
|
+
msg_type: 'interactive',
|
|
141
|
+
card: {
|
|
142
|
+
header: {
|
|
143
|
+
title: { tag: 'plain_text', content: '🎉 Coding Tool - 任务完成' },
|
|
144
|
+
template: 'green'
|
|
145
|
+
},
|
|
146
|
+
elements: [
|
|
147
|
+
{
|
|
148
|
+
tag: 'div',
|
|
149
|
+
text: { tag: 'lark_md', content: '**状态**: Claude Code 任务已完成 | 等待交互' }
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
tag: 'div',
|
|
153
|
+
text: { tag: 'lark_md', content: '**时间**: ' + timestamp }
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
tag: 'div',
|
|
157
|
+
text: { tag: 'lark_md', content: '**设备**: ' + os.hostname() }
|
|
158
|
+
}
|
|
159
|
+
]
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
try {
|
|
164
|
+
const urlObj = new URL(feishuUrl);
|
|
165
|
+
const options = {
|
|
166
|
+
hostname: urlObj.hostname,
|
|
167
|
+
port: urlObj.port || (urlObj.protocol === 'https:' ? 443 : 80),
|
|
168
|
+
path: urlObj.pathname + urlObj.search,
|
|
169
|
+
method: 'POST',
|
|
170
|
+
headers: {
|
|
171
|
+
'Content-Type': 'application/json',
|
|
172
|
+
'Content-Length': Buffer.byteLength(feishuData)
|
|
173
|
+
},
|
|
174
|
+
timeout: 10000
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const reqModule = urlObj.protocol === 'https:' ? https : http;
|
|
178
|
+
const req = reqModule.request(options, (res) => {
|
|
179
|
+
// 忽略响应
|
|
180
|
+
});
|
|
181
|
+
req.on('error', (e) => {
|
|
182
|
+
console.error('飞书通知失败:', e.message);
|
|
183
|
+
});
|
|
184
|
+
req.write(feishuData);
|
|
185
|
+
req.end();
|
|
186
|
+
} catch (e) {
|
|
187
|
+
console.error('飞书通知失败:', e.message);
|
|
188
|
+
}
|
|
189
|
+
`;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return script;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// 写入通知脚本
|
|
196
|
+
function writeNotifyScript(config) {
|
|
197
|
+
try {
|
|
198
|
+
const dir = path.dirname(NOTIFY_SCRIPT_PATH);
|
|
199
|
+
if (!fs.existsSync(dir)) {
|
|
200
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const script = generateNotifyScript(config);
|
|
204
|
+
fs.writeFileSync(NOTIFY_SCRIPT_PATH, script, { mode: 0o755 });
|
|
205
|
+
return true;
|
|
206
|
+
} catch (error) {
|
|
207
|
+
console.error('Failed to write notify script:', error);
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// 从现有 hooks 配置中解析 Stop hook 状态
|
|
213
|
+
function parseStopHookStatus(settings) {
|
|
214
|
+
const hooks = settings.hooks;
|
|
215
|
+
if (!hooks || !hooks.Stop || !Array.isArray(hooks.Stop) || hooks.Stop.length === 0) {
|
|
216
|
+
return { enabled: false, type: 'notification' };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const stopHook = hooks.Stop[0];
|
|
220
|
+
if (!stopHook.hooks || !Array.isArray(stopHook.hooks) || stopHook.hooks.length === 0) {
|
|
221
|
+
return { enabled: false, type: 'notification' };
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const command = stopHook.hooks[0].command || '';
|
|
225
|
+
|
|
226
|
+
// 判断通知类型(跨平台检测)
|
|
227
|
+
const isDialog = command.includes('display dialog') ||
|
|
228
|
+
command.includes('MessageBox') ||
|
|
229
|
+
command.includes('zenity --info');
|
|
230
|
+
const isNotification = command.includes('display notification') ||
|
|
231
|
+
command.includes('Popup') ||
|
|
232
|
+
command.includes('notify-send');
|
|
233
|
+
|
|
234
|
+
// 检查是否是我们的通知脚本
|
|
235
|
+
const isOurScript = command.includes('notify-hook.js');
|
|
236
|
+
|
|
237
|
+
if (isDialog || isNotification || isOurScript) {
|
|
238
|
+
return {
|
|
239
|
+
enabled: true,
|
|
240
|
+
type: isDialog ? 'dialog' : 'notification'
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return { enabled: false, type: 'notification' };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// 获取飞书配置
|
|
248
|
+
function getFeishuConfig() {
|
|
249
|
+
const uiConfig = readUIConfig();
|
|
250
|
+
return {
|
|
251
|
+
enabled: uiConfig.feishuNotification?.enabled || false,
|
|
252
|
+
webhookUrl: uiConfig.feishuNotification?.webhookUrl || ''
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// 保存飞书配置
|
|
257
|
+
function saveFeishuConfig(feishu) {
|
|
258
|
+
const uiConfig = readUIConfig();
|
|
259
|
+
uiConfig.feishuNotification = {
|
|
260
|
+
enabled: feishu.enabled || false,
|
|
261
|
+
webhookUrl: feishu.webhookUrl || ''
|
|
262
|
+
};
|
|
263
|
+
return writeUIConfig(uiConfig);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// 更新 Stop hook 配置
|
|
267
|
+
function updateStopHook(systemNotification, feishu) {
|
|
268
|
+
const settings = readClaudeSettings();
|
|
269
|
+
|
|
270
|
+
// 检查是否有任何通知需要启用
|
|
271
|
+
const hasSystemNotification = systemNotification && systemNotification.enabled;
|
|
272
|
+
const hasFeishu = feishu && feishu.enabled && feishu.webhookUrl;
|
|
273
|
+
|
|
274
|
+
if (!hasSystemNotification && !hasFeishu) {
|
|
275
|
+
// 都关闭了,移除 Stop hook
|
|
276
|
+
if (settings.hooks && settings.hooks.Stop) {
|
|
277
|
+
delete settings.hooks.Stop;
|
|
278
|
+
if (Object.keys(settings.hooks).length === 0) {
|
|
279
|
+
delete settings.hooks;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
// 删除通知脚本
|
|
283
|
+
if (fs.existsSync(NOTIFY_SCRIPT_PATH)) {
|
|
284
|
+
fs.unlinkSync(NOTIFY_SCRIPT_PATH);
|
|
285
|
+
}
|
|
286
|
+
} else {
|
|
287
|
+
// 生成并写入通知脚本
|
|
288
|
+
writeNotifyScript({ systemNotification, feishu });
|
|
289
|
+
|
|
290
|
+
// 更新 Stop hook 指向通知脚本
|
|
291
|
+
settings.hooks = settings.hooks || {};
|
|
292
|
+
settings.hooks.Stop = [
|
|
293
|
+
{
|
|
294
|
+
hooks: [
|
|
295
|
+
{
|
|
296
|
+
type: 'command',
|
|
297
|
+
command: `node "${NOTIFY_SCRIPT_PATH}"`
|
|
298
|
+
}
|
|
299
|
+
]
|
|
300
|
+
}
|
|
301
|
+
];
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return writeClaudeSettings(settings);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// 初始化默认 hooks 配置(服务启动时调用)
|
|
308
|
+
function initDefaultHooks() {
|
|
309
|
+
try {
|
|
310
|
+
const uiConfig = readUIConfig();
|
|
311
|
+
|
|
312
|
+
// 如果用户主动关闭过通知,不自动开启
|
|
313
|
+
if (uiConfig.claudeNotificationDisabledByUser === true) {
|
|
314
|
+
console.log('[Claude Hooks] 用户已主动关闭通知,跳过自动初始化');
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const settings = readClaudeSettings();
|
|
319
|
+
const currentStatus = parseStopHookStatus(settings);
|
|
320
|
+
|
|
321
|
+
// 如果已经有 Stop hook 配置,不覆盖
|
|
322
|
+
if (currentStatus.enabled) {
|
|
323
|
+
console.log('[Claude Hooks] 已存在 Stop hook 配置,跳过初始化');
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// 写入默认配置(右上角卡片通知)
|
|
328
|
+
const systemNotification = { enabled: true, type: 'notification' };
|
|
329
|
+
const feishu = getFeishuConfig();
|
|
330
|
+
|
|
331
|
+
if (updateStopHook(systemNotification, feishu)) {
|
|
332
|
+
console.log('[Claude Hooks] 已自动开启任务完成通知(右上角卡片)');
|
|
333
|
+
}
|
|
334
|
+
} catch (error) {
|
|
335
|
+
console.error('[Claude Hooks] 初始化默认配置失败:', error);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// GET /api/claude/hooks - 获取 hooks 配置状态
|
|
340
|
+
router.get('/', (req, res) => {
|
|
341
|
+
try {
|
|
342
|
+
const settings = readClaudeSettings();
|
|
343
|
+
const stopHook = parseStopHookStatus(settings);
|
|
344
|
+
const feishu = getFeishuConfig();
|
|
345
|
+
|
|
346
|
+
res.json({
|
|
347
|
+
success: true,
|
|
348
|
+
stopHook,
|
|
349
|
+
feishu,
|
|
350
|
+
platform
|
|
351
|
+
});
|
|
352
|
+
} catch (error) {
|
|
353
|
+
console.error('Error getting Claude hooks:', error);
|
|
354
|
+
res.status(500).json({ error: error.message });
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
// POST /api/claude/hooks - 保存 hooks 配置
|
|
359
|
+
router.post('/', (req, res) => {
|
|
360
|
+
try {
|
|
361
|
+
const { stopHook, feishu } = req.body;
|
|
362
|
+
|
|
363
|
+
// 保存飞书配置到 UI 配置文件
|
|
364
|
+
if (feishu !== undefined) {
|
|
365
|
+
saveFeishuConfig(feishu);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// 更新 Stop hook
|
|
369
|
+
const systemNotification = stopHook ? {
|
|
370
|
+
enabled: stopHook.enabled,
|
|
371
|
+
type: stopHook.type || 'notification'
|
|
372
|
+
} : { enabled: false, type: 'notification' };
|
|
373
|
+
|
|
374
|
+
const feishuConfig = feishu || getFeishuConfig();
|
|
375
|
+
|
|
376
|
+
// 更新用户关闭标记
|
|
377
|
+
const uiConfig = readUIConfig();
|
|
378
|
+
if (systemNotification.enabled || feishuConfig.enabled) {
|
|
379
|
+
// 用户开启了通知,清除关闭标记
|
|
380
|
+
if (uiConfig.claudeNotificationDisabledByUser) {
|
|
381
|
+
delete uiConfig.claudeNotificationDisabledByUser;
|
|
382
|
+
writeUIConfig(uiConfig);
|
|
383
|
+
}
|
|
384
|
+
} else {
|
|
385
|
+
// 用户关闭了所有通知
|
|
386
|
+
uiConfig.claudeNotificationDisabledByUser = true;
|
|
387
|
+
writeUIConfig(uiConfig);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (updateStopHook(systemNotification, feishuConfig)) {
|
|
391
|
+
res.json({
|
|
392
|
+
success: true,
|
|
393
|
+
message: '配置已保存',
|
|
394
|
+
stopHook: systemNotification,
|
|
395
|
+
feishu: feishuConfig
|
|
396
|
+
});
|
|
397
|
+
} else {
|
|
398
|
+
res.status(500).json({ error: '保存配置失败' });
|
|
399
|
+
}
|
|
400
|
+
} catch (error) {
|
|
401
|
+
console.error('Error saving Claude hooks:', error);
|
|
402
|
+
res.status(500).json({ error: error.message });
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
// POST /api/claude/hooks/test - 测试通知
|
|
407
|
+
router.post('/test', (req, res) => {
|
|
408
|
+
try {
|
|
409
|
+
const { type, testFeishu, webhookUrl } = req.body;
|
|
410
|
+
|
|
411
|
+
if (testFeishu && webhookUrl) {
|
|
412
|
+
// 测试飞书通知
|
|
413
|
+
const urlObj = new URL(webhookUrl);
|
|
414
|
+
const data = JSON.stringify({
|
|
415
|
+
msg_type: 'interactive',
|
|
416
|
+
card: {
|
|
417
|
+
header: {
|
|
418
|
+
title: { tag: 'plain_text', content: '🧪 Coding Tool - 测试通知' },
|
|
419
|
+
template: 'blue'
|
|
420
|
+
},
|
|
421
|
+
elements: [
|
|
422
|
+
{
|
|
423
|
+
tag: 'div',
|
|
424
|
+
text: { tag: 'lark_md', content: '**状态**: 这是一条测试通知' }
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
tag: 'div',
|
|
428
|
+
text: { tag: 'lark_md', content: '**时间**: ' + new Date().toLocaleString('zh-CN') }
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
tag: 'div',
|
|
432
|
+
text: { tag: 'lark_md', content: '**设备**: ' + os.hostname() }
|
|
433
|
+
}
|
|
434
|
+
]
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
const options = {
|
|
439
|
+
hostname: urlObj.hostname,
|
|
440
|
+
port: urlObj.port || (urlObj.protocol === 'https:' ? 443 : 80),
|
|
441
|
+
path: urlObj.pathname + urlObj.search,
|
|
442
|
+
method: 'POST',
|
|
443
|
+
headers: {
|
|
444
|
+
'Content-Type': 'application/json',
|
|
445
|
+
'Content-Length': Buffer.byteLength(data)
|
|
446
|
+
},
|
|
447
|
+
timeout: 10000
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
const reqModule = urlObj.protocol === 'https:' ? https : http;
|
|
451
|
+
const request = reqModule.request(options, (response) => {
|
|
452
|
+
let body = '';
|
|
453
|
+
response.on('data', chunk => body += chunk);
|
|
454
|
+
response.on('end', () => {
|
|
455
|
+
res.json({ success: true, message: '飞书测试通知已发送' });
|
|
456
|
+
});
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
request.on('error', (e) => {
|
|
460
|
+
res.status(500).json({ error: '飞书通知发送失败: ' + e.message });
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
request.write(data);
|
|
464
|
+
request.end();
|
|
465
|
+
} else {
|
|
466
|
+
// 测试系统通知
|
|
467
|
+
const command = generateSystemNotificationCommand(type || 'notification');
|
|
468
|
+
const { execSync } = require('child_process');
|
|
469
|
+
execSync(command, { stdio: 'ignore' });
|
|
470
|
+
res.json({ success: true, message: '系统测试通知已发送' });
|
|
471
|
+
}
|
|
472
|
+
} catch (error) {
|
|
473
|
+
console.error('Error testing notification:', error);
|
|
474
|
+
res.status(500).json({ error: error.message });
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
// 导出初始化函数供服务启动时调用
|
|
479
|
+
module.exports = router;
|
|
480
|
+
module.exports.initDefaultHooks = initDefaultHooks;
|