@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,435 @@
1
+ /**
2
+ * PTY Manager - 伪终端进程管理服务
3
+ * 管理所有 Web 终端的 PTY 进程生命周期
4
+ */
5
+
6
+ const os = require('os');
7
+ const path = require('path');
8
+
9
+ // 尝试加载 node-pty,如果失败则提示
10
+ let pty = null;
11
+ let ptyError = null;
12
+
13
+ try {
14
+ // 优先使用 @lydell/node-pty (支持更新的 Node.js 版本)
15
+ pty = require('@lydell/node-pty');
16
+ } catch (err) {
17
+ try {
18
+ // 回退到 node-pty
19
+ pty = require('node-pty');
20
+ } catch (err2) {
21
+ ptyError = err2.message;
22
+ console.error('Warning: node-pty failed to load:', err2.message);
23
+ }
24
+ }
25
+
26
+ class PtyManager {
27
+ constructor() {
28
+ // 终端进程池: terminalId -> { pty, ws, metadata }
29
+ this.terminals = new Map();
30
+ this.nextId = 1;
31
+
32
+ // 清理已退出的进程
33
+ this.cleanupInterval = setInterval(() => this.cleanupDeadTerminals(), 30000);
34
+ }
35
+
36
+ /**
37
+ * 获取默认 shell
38
+ */
39
+ getDefaultShell() {
40
+ if (process.platform === 'win32') {
41
+ return process.env.COMSPEC || 'cmd.exe';
42
+ }
43
+ return process.env.SHELL || '/bin/zsh';
44
+ }
45
+
46
+ /**
47
+ * 检查 PTY 是否可用
48
+ */
49
+ isPtyAvailable() {
50
+ return pty !== null;
51
+ }
52
+
53
+ /**
54
+ * 获取 PTY 不可用的原因
55
+ */
56
+ getPtyError() {
57
+ if (ptyError) {
58
+ return ptyError;
59
+ }
60
+ if (!pty) {
61
+ return 'node-pty not loaded';
62
+ }
63
+ // 检查 Node.js 版本兼容性
64
+ const nodeVersion = process.version;
65
+ const majorVersion = parseInt(nodeVersion.slice(1).split('.')[0], 10);
66
+ if (majorVersion >= 23) {
67
+ return `Node.js ${nodeVersion} may not be compatible with node-pty. Please use Node.js v20 or v22 LTS.`;
68
+ }
69
+ return null;
70
+ }
71
+
72
+ /**
73
+ * 创建新终端
74
+ * @param {Object} options - 终端配置
75
+ * @returns {Object} 终端信息
76
+ */
77
+ create(options = {}) {
78
+ // 检查 PTY 是否可用
79
+ if (!pty) {
80
+ const errMsg = this.getPtyError() || 'node-pty is not available';
81
+ throw new Error(`Cannot create terminal: ${errMsg}`);
82
+ }
83
+
84
+ const {
85
+ cwd = os.homedir(),
86
+ cols = 120,
87
+ rows = 30,
88
+ shell = this.getDefaultShell(),
89
+ args = [],
90
+ env = {},
91
+ channel = 'claude',
92
+ sessionId = null,
93
+ projectName = null,
94
+ startCommand = null
95
+ } = options;
96
+
97
+ const terminalId = `term_${this.nextId++}_${Date.now()}`;
98
+
99
+ // 合并环境变量
100
+ const termEnv = {
101
+ ...process.env,
102
+ ...env,
103
+ TERM: 'xterm-256color',
104
+ COLORTERM: 'truecolor',
105
+ LANG: process.env.LANG || 'en_US.UTF-8'
106
+ };
107
+
108
+ // 创建 PTY 进程
109
+ let ptyProcess;
110
+ try {
111
+ ptyProcess = pty.spawn(shell, args, {
112
+ name: 'xterm-256color',
113
+ cols: Math.max(cols, 80),
114
+ rows: Math.max(rows, 24),
115
+ cwd: cwd,
116
+ env: termEnv
117
+ });
118
+ } catch (err) {
119
+ // 提供更有用的错误信息
120
+ const nodeVersion = process.version;
121
+ const majorVersion = parseInt(nodeVersion.slice(1).split('.')[0], 10);
122
+ if (majorVersion >= 23) {
123
+ throw new Error(`PTY creation failed. Node.js ${nodeVersion} is not compatible with node-pty. Please use Node.js v20 or v22 LTS.`);
124
+ }
125
+ throw err;
126
+ }
127
+
128
+ const terminal = {
129
+ id: terminalId,
130
+ pty: ptyProcess,
131
+ ws: null,
132
+ buffer: [], // 缓存未发送的输出(用于 WebSocket 断开期间)
133
+ history: [], // 持久历史记录(用于重连时恢复)
134
+ historySize: 0, // 历史记录字节大小
135
+ maxHistorySize: 100 * 1024, // 最大历史记录大小 100KB
136
+ metadata: {
137
+ cwd,
138
+ shell,
139
+ cols,
140
+ rows,
141
+ channel,
142
+ sessionId,
143
+ projectName,
144
+ startCommand,
145
+ createdAt: Date.now(),
146
+ pid: ptyProcess.pid
147
+ }
148
+ };
149
+
150
+ // 监听 PTY 输出
151
+ ptyProcess.onData((data) => {
152
+ // 始终保存到历史记录(用于重连时恢复)
153
+ terminal.history.push(data);
154
+ terminal.historySize += data.length;
155
+
156
+ // 如果历史记录超出限制,裁剪前面的内容
157
+ while (terminal.historySize > terminal.maxHistorySize && terminal.history.length > 1) {
158
+ const removed = terminal.history.shift();
159
+ terminal.historySize -= removed.length;
160
+ }
161
+
162
+ if (terminal.ws && terminal.ws.readyState === 1) { // WebSocket.OPEN
163
+ terminal.ws.send(JSON.stringify({
164
+ type: 'terminal:output',
165
+ terminalId,
166
+ data
167
+ }));
168
+ } else {
169
+ // 缓存输出,等待 WebSocket 连接
170
+ terminal.buffer.push(data);
171
+ // 限制缓存大小
172
+ if (terminal.buffer.length > 1000) {
173
+ terminal.buffer = terminal.buffer.slice(-500);
174
+ }
175
+ }
176
+ });
177
+
178
+ // 监听 PTY 退出
179
+ ptyProcess.onExit(({ exitCode, signal }) => {
180
+ console.log(`Terminal ${terminalId} exited with code ${exitCode}, signal ${signal}`);
181
+
182
+ if (terminal.ws && terminal.ws.readyState === 1) {
183
+ terminal.ws.send(JSON.stringify({
184
+ type: 'terminal:exited',
185
+ terminalId,
186
+ exitCode,
187
+ signal
188
+ }));
189
+ }
190
+
191
+ // 标记为已退出,稍后清理
192
+ terminal.exited = true;
193
+ terminal.exitCode = exitCode;
194
+ });
195
+
196
+ this.terminals.set(terminalId, terminal);
197
+
198
+ console.log(`Created terminal ${terminalId} (pid: ${ptyProcess.pid}) in ${cwd}`);
199
+
200
+ // 如果有启动命令,延迟执行
201
+ if (startCommand) {
202
+ setTimeout(() => {
203
+ if (!terminal.exited) {
204
+ ptyProcess.write(startCommand + '\r');
205
+ }
206
+ }, 500);
207
+ }
208
+
209
+ return {
210
+ id: terminalId,
211
+ pid: ptyProcess.pid,
212
+ metadata: terminal.metadata
213
+ };
214
+ }
215
+
216
+ /**
217
+ * 绑定 WebSocket 到终端
218
+ * @param {string} terminalId - 终端 ID
219
+ * @param {WebSocket} ws - WebSocket 连接
220
+ * @returns {boolean} 是否成功
221
+ */
222
+ attachWebSocket(terminalId, ws) {
223
+ const terminal = this.terminals.get(terminalId);
224
+ if (!terminal) {
225
+ console.warn(`Terminal ${terminalId} not found`);
226
+ return false;
227
+ }
228
+
229
+ terminal.ws = ws;
230
+
231
+ // 发送历史记录(用于重连时恢复之前的输出)
232
+ if (terminal.history.length > 0) {
233
+ const historyData = terminal.history.join('');
234
+ ws.send(JSON.stringify({
235
+ type: 'terminal:output',
236
+ terminalId,
237
+ data: historyData
238
+ }));
239
+ console.log(`Sent ${terminal.history.length} history chunks (${terminal.historySize} bytes) to terminal ${terminalId}`);
240
+ }
241
+
242
+ // 清空临时缓冲区(已经包含在历史记录中了)
243
+ terminal.buffer = [];
244
+
245
+ // 如果终端已退出,通知客户端
246
+ if (terminal.exited) {
247
+ ws.send(JSON.stringify({
248
+ type: 'terminal:exited',
249
+ terminalId,
250
+ exitCode: terminal.exitCode
251
+ }));
252
+ }
253
+
254
+ console.log(`WebSocket attached to terminal ${terminalId}`);
255
+ return true;
256
+ }
257
+
258
+ /**
259
+ * 解绑 WebSocket
260
+ * @param {string} terminalId - 终端 ID
261
+ */
262
+ detachWebSocket(terminalId) {
263
+ const terminal = this.terminals.get(terminalId);
264
+ if (terminal) {
265
+ terminal.ws = null;
266
+ }
267
+ }
268
+
269
+ /**
270
+ * 向终端写入数据
271
+ * @param {string} terminalId - 终端 ID
272
+ * @param {string} data - 输入数据
273
+ * @returns {boolean} 是否成功
274
+ */
275
+ write(terminalId, data) {
276
+ const terminal = this.terminals.get(terminalId);
277
+ if (!terminal || terminal.exited) {
278
+ return false;
279
+ }
280
+
281
+ terminal.pty.write(data);
282
+ return true;
283
+ }
284
+
285
+ /**
286
+ * 调整终端大小
287
+ * @param {string} terminalId - 终端 ID
288
+ * @param {number} cols - 列数
289
+ * @param {number} rows - 行数
290
+ * @returns {boolean} 是否成功
291
+ */
292
+ resize(terminalId, cols, rows) {
293
+ const terminal = this.terminals.get(terminalId);
294
+ if (!terminal || terminal.exited) {
295
+ return false;
296
+ }
297
+
298
+ const newCols = Math.max(cols, 80);
299
+ const newRows = Math.max(rows, 24);
300
+
301
+ terminal.pty.resize(newCols, newRows);
302
+ terminal.metadata.cols = newCols;
303
+ terminal.metadata.rows = newRows;
304
+
305
+ return true;
306
+ }
307
+
308
+ /**
309
+ * 销毁终端
310
+ * @param {string} terminalId - 终端 ID
311
+ * @returns {boolean} 是否成功
312
+ */
313
+ destroy(terminalId) {
314
+ const terminal = this.terminals.get(terminalId);
315
+ if (!terminal) {
316
+ return false;
317
+ }
318
+
319
+ // 关闭 WebSocket
320
+ if (terminal.ws) {
321
+ terminal.ws.send(JSON.stringify({
322
+ type: 'terminal:exited',
323
+ terminalId,
324
+ exitCode: -1,
325
+ reason: 'destroyed'
326
+ }));
327
+ }
328
+
329
+ // 杀死 PTY 进程
330
+ if (!terminal.exited) {
331
+ try {
332
+ terminal.pty.kill();
333
+ } catch (e) {
334
+ console.warn(`Failed to kill terminal ${terminalId}:`, e.message);
335
+ }
336
+ }
337
+
338
+ this.terminals.delete(terminalId);
339
+ console.log(`Destroyed terminal ${terminalId}`);
340
+
341
+ return true;
342
+ }
343
+
344
+ /**
345
+ * 获取终端信息
346
+ * @param {string} terminalId - 终端 ID
347
+ * @returns {Object|null} 终端信息
348
+ */
349
+ get(terminalId) {
350
+ const terminal = this.terminals.get(terminalId);
351
+ if (!terminal) {
352
+ return null;
353
+ }
354
+
355
+ return {
356
+ id: terminal.id,
357
+ pid: terminal.metadata.pid,
358
+ metadata: terminal.metadata,
359
+ connected: terminal.ws && terminal.ws.readyState === 1,
360
+ exited: terminal.exited || false
361
+ };
362
+ }
363
+
364
+ /**
365
+ * 列出所有终端
366
+ * @returns {Array} 终端列表
367
+ */
368
+ list() {
369
+ const result = [];
370
+ for (const [id, terminal] of this.terminals) {
371
+ result.push({
372
+ id,
373
+ pid: terminal.metadata.pid,
374
+ metadata: terminal.metadata,
375
+ connected: terminal.ws && terminal.ws.readyState === 1,
376
+ exited: terminal.exited || false
377
+ });
378
+ }
379
+ return result;
380
+ }
381
+
382
+ /**
383
+ * 清理已退出的终端
384
+ */
385
+ cleanupDeadTerminals() {
386
+ const now = Date.now();
387
+ for (const [id, terminal] of this.terminals) {
388
+ // 清理已退出超过 5 分钟且无 WebSocket 连接的终端
389
+ if (terminal.exited && !terminal.ws) {
390
+ const exitedTime = terminal.exitedAt || now;
391
+ if (now - exitedTime > 5 * 60 * 1000) {
392
+ this.terminals.delete(id);
393
+ console.log(`Cleaned up dead terminal ${id}`);
394
+ }
395
+ }
396
+ }
397
+ }
398
+
399
+ /**
400
+ * 销毁所有终端
401
+ */
402
+ destroyAll() {
403
+ for (const [id] of this.terminals) {
404
+ this.destroy(id);
405
+ }
406
+
407
+ if (this.cleanupInterval) {
408
+ clearInterval(this.cleanupInterval);
409
+ this.cleanupInterval = null;
410
+ }
411
+ }
412
+ }
413
+
414
+ // 单例
415
+ const ptyManager = new PtyManager();
416
+
417
+ // 进程退出时清理
418
+ process.on('exit', () => {
419
+ ptyManager.destroyAll();
420
+ });
421
+
422
+ process.on('SIGINT', () => {
423
+ ptyManager.destroyAll();
424
+ process.exit(0);
425
+ });
426
+
427
+ process.on('SIGTERM', () => {
428
+ ptyManager.destroyAll();
429
+ process.exit(0);
430
+ });
431
+
432
+ module.exports = {
433
+ ptyManager,
434
+ PtyManager
435
+ };