@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,306 @@
|
|
|
1
|
+
const { execSync } = require('child_process');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 检测系统中可用的终端工具
|
|
8
|
+
*/
|
|
9
|
+
function detectAvailableTerminals() {
|
|
10
|
+
const platform = process.platform;
|
|
11
|
+
|
|
12
|
+
if (platform === 'win32') {
|
|
13
|
+
return detectWindowsTerminals();
|
|
14
|
+
} else if (platform === 'darwin') {
|
|
15
|
+
return detectMacTerminals();
|
|
16
|
+
} else {
|
|
17
|
+
return detectLinuxTerminals();
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Windows 终端检测
|
|
23
|
+
* 只保留经过验证、确定能自动执行命令的终端
|
|
24
|
+
*/
|
|
25
|
+
function detectWindowsTerminals() {
|
|
26
|
+
const terminals = [];
|
|
27
|
+
|
|
28
|
+
// CMD - 系统自带,始终可用
|
|
29
|
+
terminals.push({
|
|
30
|
+
id: 'cmd',
|
|
31
|
+
name: 'CMD',
|
|
32
|
+
available: true,
|
|
33
|
+
isDefault: true,
|
|
34
|
+
command: 'start "Claude Session" cmd /k "cd /d "{cwd}" && claude -r {sessionId}"'
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// PowerShell
|
|
38
|
+
try {
|
|
39
|
+
execSync('where powershell', { encoding: 'utf8', stdio: 'pipe' });
|
|
40
|
+
terminals.push({
|
|
41
|
+
id: 'powershell',
|
|
42
|
+
name: 'PowerShell',
|
|
43
|
+
available: true,
|
|
44
|
+
isDefault: false,
|
|
45
|
+
command: 'start powershell -NoExit -Command "cd \'{cwd}\'; claude -r {sessionId}"'
|
|
46
|
+
});
|
|
47
|
+
} catch (e) {
|
|
48
|
+
// PowerShell 不可用
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Windows Terminal
|
|
52
|
+
try {
|
|
53
|
+
execSync('where wt', { encoding: 'utf8', stdio: 'pipe' });
|
|
54
|
+
terminals.push({
|
|
55
|
+
id: 'windows-terminal',
|
|
56
|
+
name: 'Windows Terminal',
|
|
57
|
+
available: true,
|
|
58
|
+
isDefault: false,
|
|
59
|
+
command: 'wt.exe -d "{cwd}" cmd /k "claude -r {sessionId}"'
|
|
60
|
+
});
|
|
61
|
+
} catch (e) {
|
|
62
|
+
// Windows Terminal 不可用
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Git Bash
|
|
66
|
+
const gitBashPaths = [
|
|
67
|
+
'C:\\Program Files\\Git\\bin\\bash.exe',
|
|
68
|
+
'C:\\Program Files (x86)\\Git\\bin\\bash.exe'
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
for (const bashPath of gitBashPaths) {
|
|
72
|
+
if (fs.existsSync(bashPath)) {
|
|
73
|
+
terminals.push({
|
|
74
|
+
id: 'git-bash',
|
|
75
|
+
name: 'Git Bash',
|
|
76
|
+
available: true,
|
|
77
|
+
isDefault: false,
|
|
78
|
+
command: `start "" "${bashPath}" -c "cd '{cwd}' && claude -r {sessionId}; exec bash"`,
|
|
79
|
+
executablePath: bashPath
|
|
80
|
+
});
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return terminals;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* macOS 终端检测
|
|
90
|
+
*/
|
|
91
|
+
function detectMacTerminals() {
|
|
92
|
+
const terminals = [];
|
|
93
|
+
|
|
94
|
+
// Terminal.app - 系统自带,始终可用
|
|
95
|
+
terminals.push({
|
|
96
|
+
id: 'terminal',
|
|
97
|
+
name: 'Terminal.app',
|
|
98
|
+
available: true,
|
|
99
|
+
isDefault: true,
|
|
100
|
+
command: 'osascript -e \'tell application "Terminal" to activate\' -e \'tell application "Terminal" to do script "cd \'{cwd}\' && claude -r {sessionId}"\''
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// iTerm2
|
|
104
|
+
if (fs.existsSync('/Applications/iTerm.app')) {
|
|
105
|
+
terminals.push({
|
|
106
|
+
id: 'iterm2',
|
|
107
|
+
name: 'iTerm2',
|
|
108
|
+
available: true,
|
|
109
|
+
isDefault: false,
|
|
110
|
+
command: 'osascript -e \'tell application "iTerm" to create window with default profile command "cd {cwd} && claude -r {sessionId}"\''
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Ghostty
|
|
115
|
+
if (fs.existsSync('/Applications/Ghostty.app')) {
|
|
116
|
+
terminals.push({
|
|
117
|
+
id: 'ghostty',
|
|
118
|
+
name: 'Ghostty',
|
|
119
|
+
available: true,
|
|
120
|
+
isDefault: false,
|
|
121
|
+
command: 'open -a Ghostty --args -e "cd \'{cwd}\' && claude -r {sessionId}; exec $SHELL"'
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
// 也检查 homebrew 安装的 ghostty
|
|
125
|
+
try {
|
|
126
|
+
execSync('which ghostty', { encoding: 'utf8', stdio: 'pipe' });
|
|
127
|
+
if (!terminals.find(t => t.id === 'ghostty')) {
|
|
128
|
+
terminals.push({
|
|
129
|
+
id: 'ghostty',
|
|
130
|
+
name: 'Ghostty',
|
|
131
|
+
available: true,
|
|
132
|
+
isDefault: false,
|
|
133
|
+
command: 'ghostty -e "cd \'{cwd}\' && claude -r {sessionId}; exec $SHELL"'
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
} catch (e) {
|
|
137
|
+
// Ghostty CLI 不可用
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Alacritty
|
|
141
|
+
if (fs.existsSync('/Applications/Alacritty.app')) {
|
|
142
|
+
terminals.push({
|
|
143
|
+
id: 'alacritty',
|
|
144
|
+
name: 'Alacritty',
|
|
145
|
+
available: true,
|
|
146
|
+
isDefault: false,
|
|
147
|
+
command: 'alacritty --working-directory "{cwd}" -e bash -c "claude -r {sessionId}; exec bash"'
|
|
148
|
+
});
|
|
149
|
+
} else {
|
|
150
|
+
try {
|
|
151
|
+
execSync('which alacritty', { encoding: 'utf8', stdio: 'pipe' });
|
|
152
|
+
terminals.push({
|
|
153
|
+
id: 'alacritty',
|
|
154
|
+
name: 'Alacritty',
|
|
155
|
+
available: true,
|
|
156
|
+
isDefault: false,
|
|
157
|
+
command: 'alacritty --working-directory "{cwd}" -e bash -c "claude -r {sessionId}; exec bash"'
|
|
158
|
+
});
|
|
159
|
+
} catch (e) {
|
|
160
|
+
// Alacritty 不可用
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Kitty
|
|
165
|
+
if (fs.existsSync('/Applications/kitty.app')) {
|
|
166
|
+
terminals.push({
|
|
167
|
+
id: 'kitty',
|
|
168
|
+
name: 'Kitty',
|
|
169
|
+
available: true,
|
|
170
|
+
isDefault: false,
|
|
171
|
+
command: 'kitty --directory "{cwd}" bash -c "claude -r {sessionId}; exec bash"'
|
|
172
|
+
});
|
|
173
|
+
} else {
|
|
174
|
+
try {
|
|
175
|
+
execSync('which kitty', { encoding: 'utf8', stdio: 'pipe' });
|
|
176
|
+
terminals.push({
|
|
177
|
+
id: 'kitty',
|
|
178
|
+
name: 'Kitty',
|
|
179
|
+
available: true,
|
|
180
|
+
isDefault: false,
|
|
181
|
+
command: 'kitty --directory "{cwd}" bash -c "claude -r {sessionId}; exec bash"'
|
|
182
|
+
});
|
|
183
|
+
} catch (e) {
|
|
184
|
+
// Kitty 不可用
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Warp
|
|
189
|
+
if (fs.existsSync('/Applications/Warp.app')) {
|
|
190
|
+
terminals.push({
|
|
191
|
+
id: 'warp',
|
|
192
|
+
name: 'Warp',
|
|
193
|
+
available: true,
|
|
194
|
+
isDefault: false,
|
|
195
|
+
command: 'open -a Warp "{cwd}" --args -e "claude -r {sessionId}"'
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Hyper
|
|
200
|
+
if (fs.existsSync('/Applications/Hyper.app')) {
|
|
201
|
+
terminals.push({
|
|
202
|
+
id: 'hyper',
|
|
203
|
+
name: 'Hyper',
|
|
204
|
+
available: true,
|
|
205
|
+
isDefault: false,
|
|
206
|
+
command: 'open -a Hyper --args "{cwd}"'
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// WezTerm
|
|
211
|
+
if (fs.existsSync('/Applications/WezTerm.app')) {
|
|
212
|
+
terminals.push({
|
|
213
|
+
id: 'wezterm',
|
|
214
|
+
name: 'WezTerm',
|
|
215
|
+
available: true,
|
|
216
|
+
isDefault: false,
|
|
217
|
+
command: 'wezterm start --cwd "{cwd}" -- bash -c "claude -r {sessionId}; exec bash"'
|
|
218
|
+
});
|
|
219
|
+
} else {
|
|
220
|
+
try {
|
|
221
|
+
execSync('which wezterm', { encoding: 'utf8', stdio: 'pipe' });
|
|
222
|
+
terminals.push({
|
|
223
|
+
id: 'wezterm',
|
|
224
|
+
name: 'WezTerm',
|
|
225
|
+
available: true,
|
|
226
|
+
isDefault: false,
|
|
227
|
+
command: 'wezterm start --cwd "{cwd}" -- bash -c "claude -r {sessionId}; exec bash"'
|
|
228
|
+
});
|
|
229
|
+
} catch (e) {
|
|
230
|
+
// WezTerm 不可用
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Rio
|
|
235
|
+
try {
|
|
236
|
+
execSync('which rio', { encoding: 'utf8', stdio: 'pipe' });
|
|
237
|
+
terminals.push({
|
|
238
|
+
id: 'rio',
|
|
239
|
+
name: 'Rio',
|
|
240
|
+
available: true,
|
|
241
|
+
isDefault: false,
|
|
242
|
+
command: 'rio -e bash -c "cd \'{cwd}\' && claude -r {sessionId}; exec bash"'
|
|
243
|
+
});
|
|
244
|
+
} catch (e) {
|
|
245
|
+
// Rio 不可用
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return terminals;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Linux 终端检测
|
|
253
|
+
*/
|
|
254
|
+
function detectLinuxTerminals() {
|
|
255
|
+
const terminals = [];
|
|
256
|
+
|
|
257
|
+
const terminalConfigs = [
|
|
258
|
+
{ id: 'gnome-terminal', name: 'GNOME Terminal', cmd: 'gnome-terminal', args: '-- bash -c "cd \'{cwd}\' && claude -r {sessionId}; exec bash"' },
|
|
259
|
+
{ id: 'konsole', name: 'Konsole', cmd: 'konsole', args: '-e bash -c "cd \'{cwd}\' && claude -r {sessionId}; exec bash"' },
|
|
260
|
+
{ id: 'xfce4-terminal', name: 'XFCE Terminal', cmd: 'xfce4-terminal', args: '-e "bash -c \\"cd \'{cwd}\' && claude -r {sessionId}; exec bash\\""' },
|
|
261
|
+
{ id: 'xterm', name: 'XTerm', cmd: 'xterm', args: '-e "cd \'{cwd}\' && claude -r {sessionId}; exec bash"' },
|
|
262
|
+
{ id: 'alacritty', name: 'Alacritty', cmd: 'alacritty', args: '--working-directory "{cwd}" -e bash -c "claude -r {sessionId}; exec bash"' },
|
|
263
|
+
{ id: 'kitty', name: 'Kitty', cmd: 'kitty', args: '--directory "{cwd}" bash -c "claude -r {sessionId}; exec bash"' },
|
|
264
|
+
{ id: 'tilix', name: 'Tilix', cmd: 'tilix', args: '-e "bash -c \\"cd \'{cwd}\' && claude -r {sessionId}; exec bash\\""' },
|
|
265
|
+
{ id: 'ghostty', name: 'Ghostty', cmd: 'ghostty', args: '-e "cd \'{cwd}\' && claude -r {sessionId}; exec $SHELL"' },
|
|
266
|
+
{ id: 'wezterm', name: 'WezTerm', cmd: 'wezterm', args: 'start --cwd "{cwd}" -- bash -c "claude -r {sessionId}; exec bash"' },
|
|
267
|
+
{ id: 'rio', name: 'Rio', cmd: 'rio', args: '-e bash -c "cd \'{cwd}\' && claude -r {sessionId}; exec bash"' },
|
|
268
|
+
{ id: 'foot', name: 'Foot', cmd: 'foot', args: 'bash -c "cd \'{cwd}\' && claude -r {sessionId}; exec bash"' },
|
|
269
|
+
{ id: 'terminator', name: 'Terminator', cmd: 'terminator', args: '-e "bash -c \\"cd \'{cwd}\' && claude -r {sessionId}; exec bash\\""' },
|
|
270
|
+
{ id: 'urxvt', name: 'URxvt', cmd: 'urxvt', args: '-e bash -c "cd \'{cwd}\' && claude -r {sessionId}; exec bash"' },
|
|
271
|
+
{ id: 'st', name: 'st (suckless)', cmd: 'st', args: '-e bash -c "cd \'{cwd}\' && claude -r {sessionId}; exec bash"' }
|
|
272
|
+
];
|
|
273
|
+
|
|
274
|
+
let foundDefault = false;
|
|
275
|
+
terminalConfigs.forEach((config) => {
|
|
276
|
+
try {
|
|
277
|
+
execSync(`which ${config.cmd}`, { encoding: 'utf8', stdio: 'pipe' });
|
|
278
|
+
terminals.push({
|
|
279
|
+
id: config.id,
|
|
280
|
+
name: config.name,
|
|
281
|
+
available: true,
|
|
282
|
+
isDefault: !foundDefault, // 第一个可用的设为默认
|
|
283
|
+
command: `${config.cmd} ${config.args}`
|
|
284
|
+
});
|
|
285
|
+
foundDefault = true;
|
|
286
|
+
} catch (e) {
|
|
287
|
+
// 此终端不可用
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
return terminals;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* 获取默认终端
|
|
296
|
+
*/
|
|
297
|
+
function getDefaultTerminal() {
|
|
298
|
+
const terminals = detectAvailableTerminals();
|
|
299
|
+
const defaultTerminal = terminals.find(t => t.isDefault);
|
|
300
|
+
return defaultTerminal || terminals[0];
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
module.exports = {
|
|
304
|
+
detectAvailableTerminals,
|
|
305
|
+
getDefaultTerminal
|
|
306
|
+
};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
|
|
5
|
+
const UI_CONFIG_DIR = path.join(os.homedir(), '.claude', 'cc-tool');
|
|
6
|
+
const UI_CONFIG_FILE = path.join(UI_CONFIG_DIR, 'ui-config.json');
|
|
7
|
+
|
|
8
|
+
// Default UI config
|
|
9
|
+
const DEFAULT_UI_CONFIG = {
|
|
10
|
+
theme: 'light',
|
|
11
|
+
panelVisibility: {
|
|
12
|
+
showChannels: true,
|
|
13
|
+
showLogs: true
|
|
14
|
+
},
|
|
15
|
+
channelLocks: {
|
|
16
|
+
claude: false,
|
|
17
|
+
codex: false,
|
|
18
|
+
gemini: false
|
|
19
|
+
},
|
|
20
|
+
channelCollapse: {
|
|
21
|
+
claude: [],
|
|
22
|
+
codex: [],
|
|
23
|
+
gemini: []
|
|
24
|
+
},
|
|
25
|
+
channelOrder: {
|
|
26
|
+
claude: [],
|
|
27
|
+
codex: [],
|
|
28
|
+
gemini: []
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// 内存缓存
|
|
33
|
+
let uiConfigCache = null;
|
|
34
|
+
let cacheInitialized = false;
|
|
35
|
+
|
|
36
|
+
// Ensure UI config directory exists
|
|
37
|
+
function ensureConfigDir() {
|
|
38
|
+
if (!fs.existsSync(UI_CONFIG_DIR)) {
|
|
39
|
+
fs.mkdirSync(UI_CONFIG_DIR, { recursive: true });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 从文件读取并缓存
|
|
44
|
+
function readUIConfigFromFile() {
|
|
45
|
+
ensureConfigDir();
|
|
46
|
+
|
|
47
|
+
if (!fs.existsSync(UI_CONFIG_FILE)) {
|
|
48
|
+
return { ...DEFAULT_UI_CONFIG };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const content = fs.readFileSync(UI_CONFIG_FILE, 'utf8');
|
|
53
|
+
const data = JSON.parse(content);
|
|
54
|
+
// Merge with defaults to ensure all keys exist
|
|
55
|
+
return {
|
|
56
|
+
theme: data.theme || DEFAULT_UI_CONFIG.theme,
|
|
57
|
+
panelVisibility: { ...DEFAULT_UI_CONFIG.panelVisibility, ...data.panelVisibility },
|
|
58
|
+
channelLocks: { ...DEFAULT_UI_CONFIG.channelLocks, ...data.channelLocks },
|
|
59
|
+
channelCollapse: { ...DEFAULT_UI_CONFIG.channelCollapse, ...data.channelCollapse },
|
|
60
|
+
channelOrder: { ...DEFAULT_UI_CONFIG.channelOrder, ...data.channelOrder }
|
|
61
|
+
};
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.error('Error loading UI config:', error);
|
|
64
|
+
return { ...DEFAULT_UI_CONFIG };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 初始化缓存(延迟初始化)
|
|
69
|
+
function initializeCache() {
|
|
70
|
+
if (cacheInitialized) return;
|
|
71
|
+
uiConfigCache = readUIConfigFromFile();
|
|
72
|
+
cacheInitialized = true;
|
|
73
|
+
|
|
74
|
+
// 监听文件变化,更新缓存
|
|
75
|
+
try {
|
|
76
|
+
fs.watchFile(UI_CONFIG_FILE, { persistent: false }, () => {
|
|
77
|
+
uiConfigCache = readUIConfigFromFile();
|
|
78
|
+
});
|
|
79
|
+
} catch (err) {
|
|
80
|
+
console.error('Failed to watch UI config file:', err);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Load UI config(使用缓存)
|
|
85
|
+
function loadUIConfig() {
|
|
86
|
+
if (!cacheInitialized) {
|
|
87
|
+
initializeCache();
|
|
88
|
+
}
|
|
89
|
+
return JSON.parse(JSON.stringify(uiConfigCache)); // 深拷贝返回
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Save UI config(同时更新缓存)
|
|
93
|
+
function saveUIConfig(config) {
|
|
94
|
+
ensureConfigDir();
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
fs.writeFileSync(UI_CONFIG_FILE, JSON.stringify(config, null, 2), 'utf8');
|
|
98
|
+
// 同时更新缓存
|
|
99
|
+
uiConfigCache = JSON.parse(JSON.stringify(config));
|
|
100
|
+
} catch (error) {
|
|
101
|
+
console.error('Error saving UI config:', error);
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Update specific config key
|
|
107
|
+
function updateUIConfig(key, value) {
|
|
108
|
+
const config = loadUIConfig();
|
|
109
|
+
config[key] = value;
|
|
110
|
+
saveUIConfig(config);
|
|
111
|
+
return config;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Update nested config
|
|
115
|
+
function updateNestedUIConfig(parentKey, childKey, value) {
|
|
116
|
+
const config = loadUIConfig();
|
|
117
|
+
if (!config[parentKey]) {
|
|
118
|
+
config[parentKey] = {};
|
|
119
|
+
}
|
|
120
|
+
config[parentKey][childKey] = value;
|
|
121
|
+
saveUIConfig(config);
|
|
122
|
+
return config;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
module.exports = {
|
|
126
|
+
loadUIConfig,
|
|
127
|
+
saveUIConfig,
|
|
128
|
+
updateUIConfig,
|
|
129
|
+
updateNestedUIConfig
|
|
130
|
+
};
|