coding-tool-x 3.5.5 → 3.5.7
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 +25 -4
- package/bin/ctx.js +6 -1
- package/dist/web/assets/{Analytics-gvYu5sCM.js → Analytics-C6DEmD3D.js} +1 -1
- package/dist/web/assets/{ConfigTemplates-CPlH8Ehd.js → ConfigTemplates-Cf_iTpC4.js} +1 -1
- package/dist/web/assets/{Home-B-qbu3uk.js → Home-BtBmYLJ1.js} +1 -1
- package/dist/web/assets/{PluginManager-B2tQ_YUq.js → PluginManager-DEk8vSw5.js} +1 -1
- package/dist/web/assets/{ProjectList-kDadoXXs.js → ProjectList-BMVhA_Kh.js} +1 -1
- package/dist/web/assets/{SessionList-eLgITwTV.js → SessionList-B5ioAXxg.js} +1 -1
- package/dist/web/assets/{SkillManager-B7zEB5Op.js → SkillManager-DcZOiiSf.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-C-RzB3ud.js → WorkspaceManager-BHqI8aGV.js} +1 -1
- package/dist/web/assets/{icons-DlxD2wZJ.js → icons-CQuif85v.js} +1 -1
- package/dist/web/assets/index-CtByKdkA.js +2 -0
- package/dist/web/assets/{index-BHeh2z0i.css → index-VGAxnLqi.css} +1 -1
- package/dist/web/index.html +3 -3
- package/docs/Caddyfile.example +19 -0
- package/docs/reverse-proxy-https.md +57 -0
- package/package.json +2 -1
- package/src/commands/daemon.js +33 -5
- package/src/commands/stats.js +41 -4
- package/src/commands/ui.js +12 -3
- package/src/config/paths.js +6 -0
- package/src/index.js +125 -34
- package/src/server/api/codex-sessions.js +6 -3
- package/src/server/api/dashboard.js +25 -1
- package/src/server/api/gemini-sessions.js +6 -3
- package/src/server/api/hooks.js +17 -1
- package/src/server/api/opencode-sessions.js +6 -3
- package/src/server/api/plugins.js +24 -33
- package/src/server/api/sessions.js +6 -3
- package/src/server/index.js +31 -9
- package/src/server/services/codex-sessions.js +107 -9
- package/src/server/services/https-cert.js +171 -0
- package/src/server/services/network-access.js +61 -2
- package/src/server/services/notification-hooks.js +181 -16
- package/src/server/services/plugins-service.js +502 -44
- package/src/server/services/session-launch-command.js +81 -0
- package/src/server/services/sessions.js +103 -33
- package/src/server/services/web-ui-runtime.js +54 -0
- package/src/server/websocket-server.js +35 -4
- package/dist/web/assets/index-DG00t-zy.js +0 -2
|
@@ -16,6 +16,21 @@ function isLoopbackAddress(address) {
|
|
|
16
16
|
return false;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
function hasTrustedProxySocket(req) {
|
|
20
|
+
if (!req) return false;
|
|
21
|
+
return isLoopbackAddress(req.socket && req.socket.remoteAddress);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getForwardedHeaderValue(req, headerName) {
|
|
25
|
+
if (!hasTrustedProxySocket(req) || !req.headers) {
|
|
26
|
+
return '';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const rawValue = req.headers[headerName];
|
|
30
|
+
const value = Array.isArray(rawValue) ? rawValue[0] : rawValue;
|
|
31
|
+
return String(value || '').split(',')[0].trim();
|
|
32
|
+
}
|
|
33
|
+
|
|
19
34
|
function isLoopbackRequest(req) {
|
|
20
35
|
if (!req) return false;
|
|
21
36
|
const socketAddress = req.socket && req.socket.remoteAddress;
|
|
@@ -30,6 +45,34 @@ function isLoopbackRequest(req) {
|
|
|
30
45
|
return true;
|
|
31
46
|
}
|
|
32
47
|
|
|
48
|
+
function getRequestProtocol(req) {
|
|
49
|
+
const forwardedProto = getForwardedHeaderValue(req, 'x-forwarded-proto').toLowerCase();
|
|
50
|
+
if (forwardedProto === 'https' || forwardedProto === 'https:') {
|
|
51
|
+
return 'https:';
|
|
52
|
+
}
|
|
53
|
+
if (forwardedProto === 'http' || forwardedProto === 'http:') {
|
|
54
|
+
return 'http:';
|
|
55
|
+
}
|
|
56
|
+
return req && req.socket && req.socket.encrypted ? 'https:' : 'http:';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function getRequestHost(req) {
|
|
60
|
+
const forwardedHost = getForwardedHeaderValue(req, 'x-forwarded-host');
|
|
61
|
+
if (forwardedHost) {
|
|
62
|
+
return forwardedHost;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!req || !req.headers) {
|
|
66
|
+
return '';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const host = req.headers.host;
|
|
70
|
+
if (Array.isArray(host)) {
|
|
71
|
+
return String(host[0] || '').trim();
|
|
72
|
+
}
|
|
73
|
+
return String(host || '').trim();
|
|
74
|
+
}
|
|
75
|
+
|
|
33
76
|
function isSameOriginRequest(req) {
|
|
34
77
|
if (!req) return false;
|
|
35
78
|
const origin = req.headers && req.headers.origin;
|
|
@@ -37,19 +80,32 @@ function isSameOriginRequest(req) {
|
|
|
37
80
|
return true;
|
|
38
81
|
}
|
|
39
82
|
|
|
40
|
-
const host = req
|
|
83
|
+
const host = getRequestHost(req);
|
|
41
84
|
if (!host) {
|
|
42
85
|
return false;
|
|
43
86
|
}
|
|
44
87
|
|
|
45
88
|
try {
|
|
46
89
|
const originUrl = new URL(origin);
|
|
47
|
-
return originUrl.host === host;
|
|
90
|
+
return originUrl.host.toLowerCase() === host.toLowerCase();
|
|
48
91
|
} catch (error) {
|
|
49
92
|
return false;
|
|
50
93
|
}
|
|
51
94
|
}
|
|
52
95
|
|
|
96
|
+
function isRemoteMutationAllowed(envValue) {
|
|
97
|
+
if (envValue === undefined || envValue === null) {
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const normalized = String(envValue).trim().toLowerCase();
|
|
102
|
+
if (!normalized) {
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return !['0', 'false', 'no', 'off'].includes(normalized);
|
|
107
|
+
}
|
|
108
|
+
|
|
53
109
|
function createRemoteMutationGuard(options = {}) {
|
|
54
110
|
const enabled = options.enabled === true;
|
|
55
111
|
const allowRemoteMutation = options.allowRemoteMutation === true;
|
|
@@ -111,7 +167,10 @@ module.exports = {
|
|
|
111
167
|
normalizeAddress,
|
|
112
168
|
isLoopbackAddress,
|
|
113
169
|
isLoopbackRequest,
|
|
170
|
+
getRequestProtocol,
|
|
171
|
+
getRequestHost,
|
|
114
172
|
isSameOriginRequest,
|
|
173
|
+
isRemoteMutationAllowed,
|
|
115
174
|
createRemoteMutationGuard,
|
|
116
175
|
createRemoteRouteGuard,
|
|
117
176
|
createSameOriginGuard
|
|
@@ -5,9 +5,11 @@ const http = require('http');
|
|
|
5
5
|
const https = require('https');
|
|
6
6
|
const { execSync, execFileSync } = require('child_process');
|
|
7
7
|
const { PATHS, NATIVE_PATHS } = require('../../config/paths');
|
|
8
|
+
const { loadConfig } = require('../../config/loader');
|
|
8
9
|
const { loadUIConfig, saveUIConfig } = require('./ui-config');
|
|
9
10
|
const codexSettingsManager = require('./codex-settings-manager');
|
|
10
11
|
const geminiSettingsManager = require('./gemini-settings-manager');
|
|
12
|
+
const { getWebUiProtocol } = require('./web-ui-runtime');
|
|
11
13
|
|
|
12
14
|
const MANAGED_HOOK_NAME = 'coding-tool-notify';
|
|
13
15
|
const MANAGED_OPENCODE_PLUGIN_FILE = 'coding-tool-notify.js';
|
|
@@ -57,6 +59,13 @@ function getFeishuConfig() {
|
|
|
57
59
|
};
|
|
58
60
|
}
|
|
59
61
|
|
|
62
|
+
function getBrowserNotificationConfig() {
|
|
63
|
+
const uiConfig = loadUIConfig();
|
|
64
|
+
return {
|
|
65
|
+
enabled: uiConfig.browserNotification?.enabled === true
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
60
69
|
function applyClaudeDisablePreference(uiConfig = {}, claudeEnabled) {
|
|
61
70
|
const nextConfig = (uiConfig && typeof uiConfig === 'object') ? { ...uiConfig } : {};
|
|
62
71
|
if (claudeEnabled) {
|
|
@@ -67,7 +76,7 @@ function applyClaudeDisablePreference(uiConfig = {}, claudeEnabled) {
|
|
|
67
76
|
return nextConfig;
|
|
68
77
|
}
|
|
69
78
|
|
|
70
|
-
function saveNotificationUiConfig(feishu = {}, claudeEnabled) {
|
|
79
|
+
function saveNotificationUiConfig(feishu = {}, browser = {}, claudeEnabled) {
|
|
71
80
|
let uiConfig = loadUIConfig();
|
|
72
81
|
if (typeof claudeEnabled === 'boolean') {
|
|
73
82
|
uiConfig = applyClaudeDisablePreference(uiConfig, claudeEnabled);
|
|
@@ -76,6 +85,9 @@ function saveNotificationUiConfig(feishu = {}, claudeEnabled) {
|
|
|
76
85
|
enabled: feishu.enabled === true,
|
|
77
86
|
webhookUrl: feishu.webhookUrl || ''
|
|
78
87
|
};
|
|
88
|
+
uiConfig.browserNotification = {
|
|
89
|
+
enabled: browser.enabled === true
|
|
90
|
+
};
|
|
79
91
|
saveUIConfig(uiConfig);
|
|
80
92
|
}
|
|
81
93
|
|
|
@@ -115,6 +127,85 @@ function removeNotifyScript() {
|
|
|
115
127
|
}
|
|
116
128
|
}
|
|
117
129
|
|
|
130
|
+
function getNotificationSourceLabel(source = 'claude') {
|
|
131
|
+
switch (String(source || '').trim().toLowerCase()) {
|
|
132
|
+
case 'codex':
|
|
133
|
+
return 'Codex CLI';
|
|
134
|
+
case 'gemini':
|
|
135
|
+
return 'Gemini CLI';
|
|
136
|
+
case 'opencode':
|
|
137
|
+
return 'OpenCode';
|
|
138
|
+
default:
|
|
139
|
+
return 'Claude Code';
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function resolveFallbackNotificationMessage(source = 'claude', eventType = '') {
|
|
144
|
+
const normalizedSource = String(source || '').trim().toLowerCase();
|
|
145
|
+
const normalizedEventType = String(eventType || '').trim();
|
|
146
|
+
|
|
147
|
+
if (normalizedSource === 'codex') {
|
|
148
|
+
if (normalizedEventType === 'agent-turn-complete') {
|
|
149
|
+
return 'Codex CLI 回合已完成 | 等待交互';
|
|
150
|
+
}
|
|
151
|
+
return 'Codex CLI 已返回结果 | 等待交互';
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (normalizedSource === 'gemini') {
|
|
155
|
+
return 'Gemini CLI 回合已完成 | 等待交互';
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (normalizedSource === 'opencode') {
|
|
159
|
+
if (normalizedEventType === 'session.error') {
|
|
160
|
+
return 'OpenCode 会话异常,请检查日志';
|
|
161
|
+
}
|
|
162
|
+
return 'OpenCode 响应已完成 | 等待交互';
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return 'Claude Code 任务已完成 | 等待交互';
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function buildBrowserNotificationEndpoint() {
|
|
169
|
+
try {
|
|
170
|
+
const config = loadConfig();
|
|
171
|
+
const port = Number(config?.ports?.webUI);
|
|
172
|
+
const resolvedPort = Number.isFinite(port) && port > 0 ? port : 19999;
|
|
173
|
+
const protocol = getWebUiProtocol();
|
|
174
|
+
return `${protocol}://127.0.0.1:${resolvedPort}/api/hooks/browser-event`;
|
|
175
|
+
} catch (error) {
|
|
176
|
+
return `${getWebUiProtocol()}://127.0.0.1:19999/api/hooks/browser-event`;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function buildBrowserNotificationPayload(input = {}) {
|
|
181
|
+
const rawSource = String(input?.source || '').trim().toLowerCase();
|
|
182
|
+
const source = ['claude', 'codex', 'gemini', 'opencode'].includes(rawSource) ? rawSource : 'claude';
|
|
183
|
+
const eventType = String(input?.eventType || input?.event_type || '').trim();
|
|
184
|
+
const rawMessage = String(input?.message || '').trim();
|
|
185
|
+
const timestamp = Number(input?.timestamp);
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
title: String(input?.title || '').trim() || 'Coding Tool',
|
|
189
|
+
source,
|
|
190
|
+
sourceLabel: getNotificationSourceLabel(source),
|
|
191
|
+
mode: normalizeType(input?.mode),
|
|
192
|
+
eventType,
|
|
193
|
+
message: rawMessage || resolveFallbackNotificationMessage(source, eventType),
|
|
194
|
+
timestamp: Number.isFinite(timestamp) ? timestamp : Date.now()
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function emitBrowserNotificationEvent(input = {}) {
|
|
199
|
+
const browser = getBrowserNotificationConfig();
|
|
200
|
+
if (!browser.enabled) {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const { broadcastBrowserNotification } = require('../websocket-server');
|
|
205
|
+
broadcastBrowserNotification(buildBrowserNotificationPayload(input));
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
|
|
118
209
|
function parseManagedType(input) {
|
|
119
210
|
const value = String(input || '');
|
|
120
211
|
const matches = [
|
|
@@ -433,8 +524,10 @@ function runWindowsPowerShellCommand(command) {
|
|
|
433
524
|
});
|
|
434
525
|
}
|
|
435
526
|
|
|
436
|
-
function generateNotifyScript(feishu = {}) {
|
|
527
|
+
function generateNotifyScript(feishu = {}, browser = {}) {
|
|
437
528
|
const feishuEnabled = feishu.enabled === true && !!feishu.webhookUrl;
|
|
529
|
+
const browserEnabled = browser.enabled === true;
|
|
530
|
+
const browserEndpoint = browserEnabled ? buildBrowserNotificationEndpoint() : '';
|
|
438
531
|
|
|
439
532
|
return `#!/usr/bin/env node
|
|
440
533
|
// Coding Tool 通知脚本 - 自动生成,请勿手动修改
|
|
@@ -446,6 +539,8 @@ const { execSync, execFileSync } = require('child_process')
|
|
|
446
539
|
|
|
447
540
|
const FEISHU_ENABLED = ${feishuEnabled ? 'true' : 'false'}
|
|
448
541
|
const FEISHU_WEBHOOK_URL = ${JSON.stringify(feishuEnabled ? feishu.webhookUrl : '')}
|
|
542
|
+
const BROWSER_ENABLED = ${browserEnabled ? 'true' : 'false'}
|
|
543
|
+
const BROWSER_ENDPOINT = ${JSON.stringify(browserEndpoint)}
|
|
449
544
|
|
|
450
545
|
function readArg(name) {
|
|
451
546
|
const prefix = \`\${name}=\`
|
|
@@ -483,21 +578,25 @@ function readOptionalPayload() {
|
|
|
483
578
|
return null
|
|
484
579
|
}
|
|
485
580
|
|
|
486
|
-
function
|
|
487
|
-
|
|
581
|
+
function resolveEventType(eventType, payload) {
|
|
582
|
+
return eventType || payload?.type || payload?.hook_event?.event_type || ''
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function resolveMessage(source, effectiveEventType) {
|
|
586
|
+
const normalizedSource = String(source || '').trim().toLowerCase()
|
|
488
587
|
|
|
489
|
-
if (
|
|
588
|
+
if (normalizedSource === 'codex') {
|
|
490
589
|
if (effectiveEventType === 'agent-turn-complete') {
|
|
491
590
|
return 'Codex CLI 回合已完成 | 等待交互'
|
|
492
591
|
}
|
|
493
592
|
return 'Codex CLI 已返回结果 | 等待交互'
|
|
494
593
|
}
|
|
495
594
|
|
|
496
|
-
if (
|
|
595
|
+
if (normalizedSource === 'gemini') {
|
|
497
596
|
return 'Gemini CLI 回合已完成 | 等待交互'
|
|
498
597
|
}
|
|
499
598
|
|
|
500
|
-
if (
|
|
599
|
+
if (normalizedSource === 'opencode') {
|
|
501
600
|
if (effectiveEventType === 'session.error') {
|
|
502
601
|
return 'OpenCode 会话异常,请检查日志'
|
|
503
602
|
}
|
|
@@ -628,15 +727,67 @@ function sendFeishu(message, source) {
|
|
|
628
727
|
})
|
|
629
728
|
}
|
|
630
729
|
|
|
730
|
+
function sendBrowserNotification(payload) {
|
|
731
|
+
if (!BROWSER_ENABLED || !BROWSER_ENDPOINT) {
|
|
732
|
+
return Promise.resolve()
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
return new Promise((resolve) => {
|
|
736
|
+
try {
|
|
737
|
+
const urlObj = new URL(BROWSER_ENDPOINT)
|
|
738
|
+
const data = JSON.stringify(payload)
|
|
739
|
+
const options = {
|
|
740
|
+
hostname: urlObj.hostname,
|
|
741
|
+
port: urlObj.port || (urlObj.protocol === 'https:' ? 443 : 80),
|
|
742
|
+
path: urlObj.pathname + urlObj.search,
|
|
743
|
+
method: 'POST',
|
|
744
|
+
headers: {
|
|
745
|
+
'Content-Type': 'application/json',
|
|
746
|
+
'Content-Length': Buffer.byteLength(data)
|
|
747
|
+
},
|
|
748
|
+
timeout: 5000
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
if (urlObj.protocol === 'https:' && ['127.0.0.1', 'localhost', '::1'].includes(urlObj.hostname)) {
|
|
752
|
+
options.rejectUnauthorized = false
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
const requestModule = urlObj.protocol === 'https:' ? https : http
|
|
756
|
+
const request = requestModule.request(options, () => resolve())
|
|
757
|
+
request.on('error', () => resolve())
|
|
758
|
+
request.on('timeout', () => {
|
|
759
|
+
request.destroy()
|
|
760
|
+
resolve()
|
|
761
|
+
})
|
|
762
|
+
request.write(data)
|
|
763
|
+
request.end()
|
|
764
|
+
} catch (error) {
|
|
765
|
+
resolve()
|
|
766
|
+
}
|
|
767
|
+
})
|
|
768
|
+
}
|
|
769
|
+
|
|
631
770
|
(async () => {
|
|
632
771
|
const source = readArg('--source') || 'claude'
|
|
633
772
|
const mode = readArg('--mode') || readArg('--cc-notify-type') || 'notification'
|
|
634
773
|
const eventType = readArg('--event-type') || ''
|
|
635
774
|
const payload = readOptionalPayload()
|
|
636
|
-
const
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
775
|
+
const effectiveEventType = resolveEventType(eventType, payload)
|
|
776
|
+
const normalizedMode = mode === 'dialog' ? 'dialog' : 'notification'
|
|
777
|
+
const message = resolveMessage(source, effectiveEventType)
|
|
778
|
+
const timestamp = Date.now()
|
|
779
|
+
|
|
780
|
+
notify(normalizedMode, message)
|
|
781
|
+
await Promise.all([
|
|
782
|
+
sendFeishu(message, source),
|
|
783
|
+
sendBrowserNotification({
|
|
784
|
+
source,
|
|
785
|
+
mode: normalizedMode,
|
|
786
|
+
eventType: effectiveEventType,
|
|
787
|
+
message,
|
|
788
|
+
timestamp
|
|
789
|
+
})
|
|
790
|
+
])
|
|
640
791
|
})().catch(() => {
|
|
641
792
|
process.exit(0)
|
|
642
793
|
})
|
|
@@ -856,9 +1007,9 @@ function runWindowsPowerShellCommand(command) {
|
|
|
856
1007
|
`;
|
|
857
1008
|
}
|
|
858
1009
|
|
|
859
|
-
function writeNotifyScript(feishu = {}) {
|
|
1010
|
+
function writeNotifyScript(feishu = {}, browser = getBrowserNotificationConfig()) {
|
|
860
1011
|
ensureParentDir(PATHS.notifyHook);
|
|
861
|
-
fs.writeFileSync(PATHS.notifyHook, generateNotifyScript(feishu), { mode: 0o755 });
|
|
1012
|
+
fs.writeFileSync(PATHS.notifyHook, generateNotifyScript(feishu, browser), { mode: 0o755 });
|
|
862
1013
|
}
|
|
863
1014
|
|
|
864
1015
|
function getClaudeHookStatus() {
|
|
@@ -1167,6 +1318,7 @@ function getNotificationSettings() {
|
|
|
1167
1318
|
success: true,
|
|
1168
1319
|
platform: os.platform(),
|
|
1169
1320
|
feishu: getFeishuConfig(),
|
|
1321
|
+
browser: getBrowserNotificationConfig(),
|
|
1170
1322
|
platforms: {
|
|
1171
1323
|
claude: getClaudeHookStatus(),
|
|
1172
1324
|
codex: getCodexHookStatus(),
|
|
@@ -1185,6 +1337,7 @@ function normalizePlatformInput(platform = {}) {
|
|
|
1185
1337
|
|
|
1186
1338
|
function saveNotificationSettings(input = {}) {
|
|
1187
1339
|
const existingFeishu = getFeishuConfig();
|
|
1340
|
+
const existingBrowser = getBrowserNotificationConfig();
|
|
1188
1341
|
const requestedWebhookUrl = String(input?.feishu?.webhookUrl || '').trim();
|
|
1189
1342
|
const feishu = {
|
|
1190
1343
|
enabled: input?.feishu?.enabled === true,
|
|
@@ -1193,6 +1346,11 @@ function saveNotificationSettings(input = {}) {
|
|
|
1193
1346
|
if (feishu.enabled && feishu.webhookUrl) {
|
|
1194
1347
|
validateFeishuWebhookUrl(feishu.webhookUrl);
|
|
1195
1348
|
}
|
|
1349
|
+
const browser = {
|
|
1350
|
+
enabled: input?.browser !== undefined
|
|
1351
|
+
? input?.browser?.enabled === true
|
|
1352
|
+
: existingBrowser.enabled === true
|
|
1353
|
+
};
|
|
1196
1354
|
const platforms = {
|
|
1197
1355
|
claude: normalizePlatformInput(input?.platforms?.claude),
|
|
1198
1356
|
codex: normalizePlatformInput(input?.platforms?.codex),
|
|
@@ -1200,11 +1358,11 @@ function saveNotificationSettings(input = {}) {
|
|
|
1200
1358
|
opencode: normalizePlatformInput(input?.platforms?.opencode)
|
|
1201
1359
|
};
|
|
1202
1360
|
|
|
1203
|
-
saveNotificationUiConfig(feishu, platforms.claude.enabled);
|
|
1361
|
+
saveNotificationUiConfig(feishu, browser, platforms.claude.enabled);
|
|
1204
1362
|
|
|
1205
1363
|
const hasManagedPlatform = Object.values(platforms).some((platform) => platform.enabled);
|
|
1206
1364
|
if (hasManagedPlatform) {
|
|
1207
|
-
writeNotifyScript(feishu);
|
|
1365
|
+
writeNotifyScript(feishu, browser);
|
|
1208
1366
|
}
|
|
1209
1367
|
|
|
1210
1368
|
saveClaudeHook(platforms.claude.enabled, platforms.claude.type);
|
|
@@ -1347,6 +1505,9 @@ function buildLegacyClaudeSaveInput(input = {}, currentSettings = getNotificatio
|
|
|
1347
1505
|
: {
|
|
1348
1506
|
enabled: currentSettings?.feishu?.enabled === true,
|
|
1349
1507
|
webhookUrl: currentSettings?.feishu?.webhookUrl || ''
|
|
1508
|
+
},
|
|
1509
|
+
browser: {
|
|
1510
|
+
enabled: currentSettings?.browser?.enabled === true
|
|
1350
1511
|
}
|
|
1351
1512
|
};
|
|
1352
1513
|
}
|
|
@@ -1356,6 +1517,7 @@ function getLegacyClaudeHookSettings() {
|
|
|
1356
1517
|
success: true,
|
|
1357
1518
|
stopHook: parseStopHookStatus(readClaudeSettings()),
|
|
1358
1519
|
feishu: getFeishuConfig(),
|
|
1520
|
+
browser: getBrowserNotificationConfig(),
|
|
1359
1521
|
platform: os.platform()
|
|
1360
1522
|
};
|
|
1361
1523
|
}
|
|
@@ -1553,7 +1715,7 @@ function syncManagedNotificationAssets() {
|
|
|
1553
1715
|
const hasManagedPlatform = Object.values(settings?.platforms || {}).some((platform) => platform?.enabled === true);
|
|
1554
1716
|
|
|
1555
1717
|
if (hasManagedPlatform) {
|
|
1556
|
-
writeNotifyScript(settings.feishu || {});
|
|
1718
|
+
writeNotifyScript(settings.feishu || {}, settings.browser || {});
|
|
1557
1719
|
} else {
|
|
1558
1720
|
removeNotifyScript();
|
|
1559
1721
|
}
|
|
@@ -1575,6 +1737,7 @@ module.exports = {
|
|
|
1575
1737
|
getLegacyClaudeHookSettings,
|
|
1576
1738
|
saveNotificationSettings,
|
|
1577
1739
|
saveLegacyClaudeHookSettings,
|
|
1740
|
+
emitBrowserNotificationEvent,
|
|
1578
1741
|
testNotification,
|
|
1579
1742
|
initDefaultHooks,
|
|
1580
1743
|
syncManagedNotificationAssets,
|
|
@@ -1600,6 +1763,8 @@ module.exports = {
|
|
|
1600
1763
|
buildGeminiCommand,
|
|
1601
1764
|
buildStopHookCommand,
|
|
1602
1765
|
buildClaudeCommand,
|
|
1766
|
+
buildBrowserNotificationEndpoint,
|
|
1767
|
+
buildBrowserNotificationPayload,
|
|
1603
1768
|
buildOpenCodePluginContent,
|
|
1604
1769
|
getOpenCodeManagedPluginPath,
|
|
1605
1770
|
generateNotifyScript,
|