@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.
Files changed (125) hide show
  1. package/CHANGELOG.md +333 -0
  2. package/LICENSE +21 -0
  3. package/README.md +404 -0
  4. package/bin/ctx.js +8 -0
  5. package/dist/web/assets/index-D1AYlFLZ.js +3220 -0
  6. package/dist/web/assets/index-aL3cKxSK.css +41 -0
  7. package/dist/web/favicon.ico +0 -0
  8. package/dist/web/index.html +14 -0
  9. package/dist/web/logo.png +0 -0
  10. package/docs/CHANGELOG.md +582 -0
  11. package/docs/DIRECTORY_MIGRATION.md +112 -0
  12. package/docs/PROJECT_STRUCTURE.md +396 -0
  13. package/docs/bannel.png +0 -0
  14. package/docs/home.png +0 -0
  15. package/docs/logo.png +0 -0
  16. package/docs/multi-channel-load-balancing.md +249 -0
  17. package/package.json +73 -0
  18. package/src/commands/channels.js +504 -0
  19. package/src/commands/cli-type.js +99 -0
  20. package/src/commands/daemon.js +286 -0
  21. package/src/commands/doctor.js +332 -0
  22. package/src/commands/list.js +222 -0
  23. package/src/commands/logs.js +259 -0
  24. package/src/commands/port-config.js +115 -0
  25. package/src/commands/proxy-control.js +258 -0
  26. package/src/commands/proxy.js +152 -0
  27. package/src/commands/resume.js +137 -0
  28. package/src/commands/search.js +190 -0
  29. package/src/commands/stats.js +224 -0
  30. package/src/commands/switch.js +48 -0
  31. package/src/commands/toggle-proxy.js +222 -0
  32. package/src/commands/ui.js +92 -0
  33. package/src/commands/workspace.js +454 -0
  34. package/src/config/default.js +40 -0
  35. package/src/config/loader.js +75 -0
  36. package/src/config/paths.js +121 -0
  37. package/src/index.js +373 -0
  38. package/src/reset-config.js +92 -0
  39. package/src/server/api/agents.js +248 -0
  40. package/src/server/api/aliases.js +36 -0
  41. package/src/server/api/channels.js +258 -0
  42. package/src/server/api/claude-hooks.js +480 -0
  43. package/src/server/api/codex-channels.js +312 -0
  44. package/src/server/api/codex-projects.js +91 -0
  45. package/src/server/api/codex-proxy.js +182 -0
  46. package/src/server/api/codex-sessions.js +491 -0
  47. package/src/server/api/codex-statistics.js +57 -0
  48. package/src/server/api/commands.js +245 -0
  49. package/src/server/api/config-templates.js +182 -0
  50. package/src/server/api/config.js +147 -0
  51. package/src/server/api/convert.js +127 -0
  52. package/src/server/api/dashboard.js +125 -0
  53. package/src/server/api/env.js +144 -0
  54. package/src/server/api/favorites.js +77 -0
  55. package/src/server/api/gemini-channels.js +261 -0
  56. package/src/server/api/gemini-projects.js +91 -0
  57. package/src/server/api/gemini-proxy.js +160 -0
  58. package/src/server/api/gemini-sessions.js +397 -0
  59. package/src/server/api/gemini-statistics.js +57 -0
  60. package/src/server/api/health-check.js +118 -0
  61. package/src/server/api/mcp.js +336 -0
  62. package/src/server/api/pm2-autostart.js +269 -0
  63. package/src/server/api/projects.js +124 -0
  64. package/src/server/api/prompts.js +279 -0
  65. package/src/server/api/proxy.js +235 -0
  66. package/src/server/api/rules.js +271 -0
  67. package/src/server/api/sessions.js +595 -0
  68. package/src/server/api/settings.js +61 -0
  69. package/src/server/api/skills.js +305 -0
  70. package/src/server/api/statistics.js +91 -0
  71. package/src/server/api/terminal.js +202 -0
  72. package/src/server/api/ui-config.js +64 -0
  73. package/src/server/api/workspaces.js +407 -0
  74. package/src/server/codex-proxy-server.js +538 -0
  75. package/src/server/dev-server.js +26 -0
  76. package/src/server/gemini-proxy-server.js +518 -0
  77. package/src/server/index.js +305 -0
  78. package/src/server/proxy-server.js +469 -0
  79. package/src/server/services/agents-service.js +354 -0
  80. package/src/server/services/alias.js +71 -0
  81. package/src/server/services/channel-health.js +234 -0
  82. package/src/server/services/channel-scheduler.js +234 -0
  83. package/src/server/services/channels.js +347 -0
  84. package/src/server/services/codex-channels.js +625 -0
  85. package/src/server/services/codex-config.js +90 -0
  86. package/src/server/services/codex-parser.js +322 -0
  87. package/src/server/services/codex-sessions.js +665 -0
  88. package/src/server/services/codex-settings-manager.js +397 -0
  89. package/src/server/services/codex-speed-test-template.json +24 -0
  90. package/src/server/services/codex-statistics-service.js +255 -0
  91. package/src/server/services/commands-service.js +360 -0
  92. package/src/server/services/config-templates-service.js +732 -0
  93. package/src/server/services/env-checker.js +307 -0
  94. package/src/server/services/env-manager.js +300 -0
  95. package/src/server/services/favorites.js +163 -0
  96. package/src/server/services/gemini-channels.js +333 -0
  97. package/src/server/services/gemini-config.js +73 -0
  98. package/src/server/services/gemini-sessions.js +689 -0
  99. package/src/server/services/gemini-settings-manager.js +263 -0
  100. package/src/server/services/gemini-statistics-service.js +253 -0
  101. package/src/server/services/health-check.js +399 -0
  102. package/src/server/services/mcp-service.js +1188 -0
  103. package/src/server/services/prompts-service.js +492 -0
  104. package/src/server/services/proxy-runtime.js +79 -0
  105. package/src/server/services/pty-manager.js +435 -0
  106. package/src/server/services/rules-service.js +401 -0
  107. package/src/server/services/session-cache.js +127 -0
  108. package/src/server/services/session-converter.js +577 -0
  109. package/src/server/services/sessions.js +757 -0
  110. package/src/server/services/settings-manager.js +163 -0
  111. package/src/server/services/skill-service.js +965 -0
  112. package/src/server/services/speed-test.js +545 -0
  113. package/src/server/services/statistics-service.js +386 -0
  114. package/src/server/services/terminal-commands.js +155 -0
  115. package/src/server/services/terminal-config.js +140 -0
  116. package/src/server/services/terminal-detector.js +306 -0
  117. package/src/server/services/ui-config.js +130 -0
  118. package/src/server/services/workspace-service.js +662 -0
  119. package/src/server/utils/pricing.js +41 -0
  120. package/src/server/websocket-server.js +557 -0
  121. package/src/ui/menu.js +129 -0
  122. package/src/ui/prompts.js +100 -0
  123. package/src/utils/format.js +43 -0
  124. package/src/utils/port-helper.js +94 -0
  125. 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
+ };