@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,258 @@
1
+ const chalk = require('chalk');
2
+ const http = require('http');
3
+ const { loadConfig } = require('../config/loader');
4
+
5
+ const CHANNEL_CONFIG = {
6
+ claude: {
7
+ name: 'Claude',
8
+ icon: '🟢',
9
+ apiPath: '/api/proxy'
10
+ },
11
+ codex: {
12
+ name: 'Codex',
13
+ icon: '🔵',
14
+ apiPath: '/api/codex-proxy'
15
+ },
16
+ gemini: {
17
+ name: 'Gemini',
18
+ icon: '🟣',
19
+ apiPath: '/api/gemini-proxy'
20
+ }
21
+ };
22
+
23
+ /**
24
+ * HTTP 请求辅助函数
25
+ */
26
+ function httpRequest(method, path, data = null) {
27
+ const config = loadConfig();
28
+ const port = config.ports?.webUI || 10099;
29
+
30
+ return new Promise((resolve, reject) => {
31
+ const postData = data ? JSON.stringify(data) : null;
32
+ const options = {
33
+ hostname: 'localhost',
34
+ port: port,
35
+ path: path,
36
+ method: method,
37
+ headers: {
38
+ 'Content-Type': 'application/json',
39
+ ...(postData && { 'Content-Length': Buffer.byteLength(postData) })
40
+ },
41
+ timeout: 5000
42
+ };
43
+
44
+ const req = http.request(options, (res) => {
45
+ let responseData = '';
46
+
47
+ res.on('data', (chunk) => {
48
+ responseData += chunk;
49
+ });
50
+
51
+ res.on('end', () => {
52
+ try {
53
+ const json = JSON.parse(responseData);
54
+ resolve({ data: json, status: res.statusCode });
55
+ } catch (err) {
56
+ reject(new Error('Invalid JSON response'));
57
+ }
58
+ });
59
+ });
60
+
61
+ req.on('error', (err) => {
62
+ reject(err);
63
+ });
64
+
65
+ req.on('timeout', () => {
66
+ req.destroy();
67
+ reject(new Error('Request timeout'));
68
+ });
69
+
70
+ if (postData) {
71
+ req.write(postData);
72
+ }
73
+
74
+ req.end();
75
+ });
76
+ }
77
+
78
+ /**
79
+ * 检查 UI 服务是否运行
80
+ */
81
+ async function checkUIService() {
82
+ try {
83
+ await httpRequest('GET', '/api/ping');
84
+ return true;
85
+ } catch (err) {
86
+ return false;
87
+ }
88
+ }
89
+
90
+ /**
91
+ * 启动代理
92
+ */
93
+ async function handleProxyStart(channel) {
94
+ const channelInfo = CHANNEL_CONFIG[channel];
95
+ if (!channelInfo) {
96
+ console.error(chalk.red(`\n❌ 无效的渠道类型: ${channel}\n`));
97
+ console.log(chalk.gray('支持的渠道: claude, codex, gemini\n'));
98
+ process.exit(1);
99
+ }
100
+
101
+ console.log(chalk.cyan(`\n🚀 启动 ${channelInfo.name} 代理服务...\n`));
102
+
103
+ // 检查 UI 服务
104
+ const uiRunning = await checkUIService();
105
+ if (!uiRunning) {
106
+ console.error(chalk.red('❌ UI 服务未运行\n'));
107
+ console.log(chalk.yellow('💡 请先启动 UI 服务:'));
108
+ console.log(chalk.gray(' ') + chalk.cyan('ctx start') + chalk.gray(' 或 ') + chalk.cyan('ctx ui\n'));
109
+ process.exit(1);
110
+ }
111
+
112
+ try {
113
+ const response = await httpRequest('POST', `${channelInfo.apiPath}/start`);
114
+
115
+ if (response.data.success) {
116
+ console.log(chalk.green(`✅ ${channelInfo.name} 代理已启动\n`));
117
+ console.log(chalk.gray(`${channelInfo.icon} 代理端口: ${response.data.port}`));
118
+ console.log(chalk.gray(`🌐 代理地址: http://localhost:${response.data.port}\n`));
119
+ } else {
120
+ console.error(chalk.red(`❌ 启动失败: ${response.data.message}\n`));
121
+ process.exit(1);
122
+ }
123
+ } catch (error) {
124
+ if (error.code === 'ECONNREFUSED') {
125
+ console.error(chalk.red('❌ 无法连接到 UI 服务\n'));
126
+ console.log(chalk.yellow('💡 请确保 UI 服务正在运行: ') + chalk.cyan('ctx start\n'));
127
+ } else {
128
+ console.error(chalk.red(`❌ 启动失败: ${error.message}\n`));
129
+ }
130
+ process.exit(1);
131
+ }
132
+ }
133
+
134
+ /**
135
+ * 停止代理
136
+ */
137
+ async function handleProxyStop(channel) {
138
+ const channelInfo = CHANNEL_CONFIG[channel];
139
+ if (!channelInfo) {
140
+ console.error(chalk.red(`\n❌ 无效的渠道类型: ${channel}\n`));
141
+ process.exit(1);
142
+ }
143
+
144
+ console.log(chalk.cyan(`\n⏹️ 停止 ${channelInfo.name} 代理服务...\n`));
145
+
146
+ const uiRunning = await checkUIService();
147
+ if (!uiRunning) {
148
+ console.error(chalk.red('❌ UI 服务未运行,无法停止代理\n'));
149
+ process.exit(1);
150
+ }
151
+
152
+ try {
153
+ const response = await httpRequest('POST', `${channelInfo.apiPath}/stop`);
154
+
155
+ if (response.data.success) {
156
+ console.log(chalk.green(`✅ ${channelInfo.name} 代理已停止\n`));
157
+ } else {
158
+ console.error(chalk.red(`❌ 停止失败: ${response.data.message}\n`));
159
+ process.exit(1);
160
+ }
161
+ } catch (error) {
162
+ console.error(chalk.red(`❌ 停止失败: ${error.message}\n`));
163
+ process.exit(1);
164
+ }
165
+ }
166
+
167
+ /**
168
+ * 重启代理
169
+ */
170
+ async function handleProxyRestart(channel) {
171
+ const channelInfo = CHANNEL_CONFIG[channel];
172
+ if (!channelInfo) {
173
+ console.error(chalk.red(`\n❌ 无效的渠道类型: ${channel}\n`));
174
+ process.exit(1);
175
+ }
176
+
177
+ console.log(chalk.cyan(`\n🔄 重启 ${channelInfo.name} 代理服务...\n`));
178
+
179
+ await handleProxyStop(channel);
180
+ await new Promise(resolve => setTimeout(resolve, 1000));
181
+ await handleProxyStart(channel);
182
+ }
183
+
184
+ /**
185
+ * 查看代理状态
186
+ */
187
+ async function handleProxyStatus(channel) {
188
+ const channelInfo = CHANNEL_CONFIG[channel];
189
+ if (!channelInfo) {
190
+ console.error(chalk.red(`\n❌ 无效的渠道类型: ${channel}\n`));
191
+ process.exit(1);
192
+ }
193
+
194
+ const uiRunning = await checkUIService();
195
+ if (!uiRunning) {
196
+ console.log(chalk.bold.cyan(`\n╔══════════════════════════════════════╗`));
197
+ console.log(chalk.bold.cyan(`║ ${channelInfo.name} 代理服务状态 ║`));
198
+ console.log(chalk.bold.cyan(`╚══════════════════════════════════════╝\n`));
199
+ console.log(chalk.gray(' ❌ UI 服务未运行\n'));
200
+ console.log(chalk.yellow('💡 请先启动 UI 服务: ') + chalk.cyan('ctx start\n'));
201
+ return;
202
+ }
203
+
204
+ try {
205
+ const response = await httpRequest('GET', `${channelInfo.apiPath}/status`);
206
+ const status = response.data;
207
+
208
+ console.log(chalk.bold.cyan(`\n╔══════════════════════════════════════╗`));
209
+ console.log(chalk.bold.cyan(`║ ${channelInfo.name} 代理服务状态 ║`));
210
+ console.log(chalk.bold.cyan(`╚══════════════════════════════════════╝\n`));
211
+
212
+ if (status.running) {
213
+ console.log(chalk.green(' ✅ 状态: 运行中'));
214
+ console.log(chalk.gray(` ${channelInfo.icon} 端口: ${status.port}`));
215
+ console.log(chalk.gray(` 🌐 地址: http://localhost:${status.port}`));
216
+ if (status.runtime) {
217
+ console.log(chalk.gray(` ⏱️ 运行时长: ${formatRuntime(status.runtime)}`));
218
+ }
219
+ } else {
220
+ console.log(chalk.gray(' ❌ 状态: 未运行'));
221
+ }
222
+
223
+ console.log(chalk.bold('\n💡 提示:'));
224
+ console.log(chalk.gray(` • 使用 `) + chalk.cyan(`ctx ${channel} start`) + chalk.gray(` 启动代理`));
225
+ console.log(chalk.gray(` • 使用 `) + chalk.cyan(`ctx logs ${channel}`) + chalk.gray(` 查看日志`));
226
+ console.log(chalk.gray(` • 使用 `) + chalk.cyan(`ctx stats ${channel}`) + chalk.gray(` 查看统计\n`));
227
+ } catch (error) {
228
+ console.error(chalk.red(`❌ 查询状态失败: ${error.message}\n`));
229
+ process.exit(1);
230
+ }
231
+ }
232
+
233
+ /**
234
+ * 格式化运行时长
235
+ */
236
+ function formatRuntime(ms) {
237
+ const seconds = Math.floor(ms / 1000);
238
+ const minutes = Math.floor(seconds / 60);
239
+ const hours = Math.floor(minutes / 60);
240
+ const days = Math.floor(hours / 24);
241
+
242
+ if (days > 0) {
243
+ return `${days}天 ${hours % 24}小时`;
244
+ } else if (hours > 0) {
245
+ return `${hours}小时 ${minutes % 60}分钟`;
246
+ } else if (minutes > 0) {
247
+ return `${minutes}分钟`;
248
+ } else {
249
+ return `${seconds}秒`;
250
+ }
251
+ }
252
+
253
+ module.exports = {
254
+ handleProxyStart,
255
+ handleProxyStop,
256
+ handleProxyRestart,
257
+ handleProxyStatus
258
+ };
@@ -0,0 +1,152 @@
1
+ const { startProxyServer, stopProxyServer, getProxyStatus } = require('../server/proxy-server');
2
+ const {
3
+ setProxyConfig,
4
+ restoreSettings,
5
+ isProxyConfig,
6
+ settingsExists,
7
+ hasBackup
8
+ } = require('../server/services/settings-manager');
9
+
10
+ /**
11
+ * 启动代理
12
+ */
13
+ async function handleProxyStart() {
14
+ try {
15
+ console.log('\n🚀 启动代理服务...\n');
16
+
17
+ // 1. 检查配置文件
18
+ if (!settingsExists()) {
19
+ console.error('❌ 未找到 Claude Code 配置文件');
20
+ console.log('请至少运行一次 Claude Code 以生成配置文件');
21
+ console.log('配置文件路径: ~/.claude/settings.json\n');
22
+ process.exit(1);
23
+ }
24
+
25
+ // 2. 检查是否已经是代理配置
26
+ if (isProxyConfig()) {
27
+ console.log('⚠️ 已经配置为代理模式');
28
+ }
29
+
30
+ // 3. 启动代理服务器
31
+ const proxyResult = await startProxyServer();
32
+
33
+ if (!proxyResult.success) {
34
+ console.error('❌ 代理服务器启动失败\n');
35
+ process.exit(1);
36
+ }
37
+
38
+ console.log(`✅ 代理服务器已启动: http://127.0.0.1:${proxyResult.port}`);
39
+
40
+ // 4. 修改配置文件
41
+ const configResult = setProxyConfig(proxyResult.port);
42
+ console.log('✅ 配置文件已更新');
43
+
44
+ if (hasBackup()) {
45
+ console.log('✅ 原配置已备份');
46
+ }
47
+
48
+ console.log('\n代理服务运行中...');
49
+ console.log('现在可以使用 Claude Code,它将通过代理访问 API');
50
+ console.log('使用 Ctrl+C 停止服务(配置不会恢复)');
51
+ console.log('或使用 "ctx proxy stop" 停止并恢复配置\n');
52
+
53
+ // 保持进程运行
54
+ return new Promise(() => {
55
+ // 处理退出信号
56
+ process.on('SIGINT', async () => {
57
+ console.log('\n\n⚠️ 收到退出信号');
58
+ console.log('提示: 配置文件未恢复,使用 "ctx proxy stop" 恢复配置\n');
59
+ process.exit(0);
60
+ });
61
+ });
62
+ } catch (error) {
63
+ console.error('❌ 启动代理失败:', error.message);
64
+ process.exit(1);
65
+ }
66
+ }
67
+
68
+ /**
69
+ * 停止代理
70
+ */
71
+ async function handleProxyStop() {
72
+ try {
73
+ console.log('\n⏹️ 停止代理服务...\n');
74
+
75
+ const status = getProxyStatus();
76
+
77
+ // 1. 停止代理服务器
78
+ if (status.running) {
79
+ await stopProxyServer();
80
+ console.log('✅ 代理服务器已停止');
81
+ } else {
82
+ console.log('⚠️ 代理服务器未运行');
83
+ }
84
+
85
+ // 2. 恢复配置文件
86
+ if (hasBackup()) {
87
+ restoreSettings();
88
+ console.log('✅ 配置文件已恢复');
89
+ } else {
90
+ console.log('⚠️ 未找到备份文件,配置未恢复');
91
+ }
92
+
93
+ console.log('\n✅ 代理已完全停止并清理\n');
94
+ } catch (error) {
95
+ console.error('❌ 停止代理失败:', error.message);
96
+ process.exit(1);
97
+ }
98
+ }
99
+
100
+ /**
101
+ * 显示代理状态
102
+ */
103
+ function handleProxyStatus() {
104
+ const chalk = require('chalk');
105
+ console.log(chalk.bold.cyan('\n╔══════════════════════════════════════╗'));
106
+ console.log(chalk.bold.cyan('║ 代理服务状态 ║'));
107
+ console.log(chalk.bold.cyan('╚══════════════════════════════════════╝\n'));
108
+
109
+ const proxyStatus = getProxyStatus();
110
+ const configIsProxy = isProxyConfig();
111
+ const backup = hasBackup();
112
+
113
+ console.log(chalk.bold('代理服务器:'));
114
+ if (proxyStatus.running) {
115
+ console.log(chalk.green(` ✅ 状态: 运行中`));
116
+ console.log(chalk.gray(` 📍 端口: ${proxyStatus.port}`));
117
+ console.log(chalk.gray(` 🌐 地址: http://127.0.0.1:${proxyStatus.port}`));
118
+ } else {
119
+ console.log(chalk.gray(` ❌ 状态: 未运行`));
120
+ }
121
+
122
+ console.log(chalk.bold('\n配置文件:'));
123
+ console.log(` 代理模式: ${configIsProxy ? chalk.green('✅ 已启用') : chalk.gray('❌ 未启用')}`);
124
+ console.log(` 配置备份: ${backup ? chalk.green('✅ 存在') : chalk.gray('❌ 不存在')}`);
125
+
126
+ // 根据状态给出建议
127
+ console.log(chalk.bold('\n💡 建议操作:'));
128
+
129
+ if (proxyStatus.running && configIsProxy) {
130
+ console.log(chalk.green(' ✓ 代理正常运行,Claude Code 将通过代理访问 API'));
131
+ console.log(chalk.gray(' • 使用 Web UI (ctx ui) 可以动态管理渠道'));
132
+ console.log(chalk.gray(' • 使用 ctx proxy stop 停止代理并恢复配置'));
133
+ } else if (proxyStatus.running && !configIsProxy) {
134
+ console.log(chalk.yellow(' ⚠️ 代理服务在运行,但配置未启用代理模式'));
135
+ console.log(chalk.gray(' • 配置可能被手动修改,建议运行: ctx proxy stop'));
136
+ } else if (!proxyStatus.running && configIsProxy) {
137
+ console.log(chalk.yellow(' ⚠️ 配置为代理模式,但代理服务未运行'));
138
+ console.log(chalk.gray(' • Claude Code 可能无法正常工作'));
139
+ console.log(chalk.gray(' • 建议运行: ctx reset (恢复配置)'));
140
+ } else {
141
+ console.log(chalk.gray(' • 代理未运行,Claude Code 使用常规配置'));
142
+ console.log(chalk.gray(' • 如需启用动态切换,运行: ctx ui'));
143
+ }
144
+
145
+ console.log('\n');
146
+ }
147
+
148
+ module.exports = {
149
+ handleProxyStart,
150
+ handleProxyStop,
151
+ handleProxyStatus
152
+ };
@@ -0,0 +1,137 @@
1
+ // 恢复会话命令
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const { execSync } = require('child_process');
5
+ const chalk = require('chalk');
6
+ const ora = require('ora');
7
+ const { getSessionsDir } = require('../utils/session');
8
+
9
+ /**
10
+ * 恢复会话
11
+ */
12
+ async function resumeSession(config, sessionId, fork = false) {
13
+ // 构建命令参数
14
+ const args = ['-r', sessionId];
15
+ if (fork) {
16
+ args.push('--fork-session');
17
+ }
18
+
19
+ // 从会话文件中提取原始工作目录
20
+ const sessionFile = path.join(getSessionsDir(config), `${sessionId}.jsonl`);
21
+ let cwd = process.cwd();
22
+
23
+ try {
24
+ const fd = fs.openSync(sessionFile, 'r');
25
+ const buffer = Buffer.alloc(2048);
26
+ fs.readSync(fd, buffer, 0, 2048, 0);
27
+ fs.closeSync(fd);
28
+
29
+ const content = buffer.toString('utf8');
30
+ const lines = content.split('\n').slice(0, 5);
31
+
32
+ for (const line of lines) {
33
+ try {
34
+ const json = JSON.parse(line);
35
+ if (json.cwd) {
36
+ cwd = json.cwd;
37
+ break;
38
+ }
39
+ } catch (e) {
40
+ // 继续
41
+ }
42
+ }
43
+ } catch (e) {
44
+ // 使用当前目录
45
+ }
46
+
47
+ // 显示加载动画
48
+ const spinner = ora({
49
+ text: chalk.cyan('正在准备启动 Claude...'),
50
+ spinner: 'dots',
51
+ }).start();
52
+
53
+ // 等待 500ms,让用户看到加载状态
54
+ await new Promise(resolve => setTimeout(resolve, 500));
55
+
56
+ spinner.succeed(chalk.green('准备完成!\n'));
57
+
58
+ console.log(chalk.gray('━'.repeat(50)));
59
+ console.log(chalk.green.bold(`✨ 会话: ${sessionId.substring(0, 8)}`));
60
+ console.log(chalk.gray(`📁 目录: ${cwd}`));
61
+ console.log(chalk.gray('━'.repeat(50)) + '\n');
62
+
63
+ // 完全清理终端状态,避免输入冲突
64
+ if (process.stdin.isTTY && process.stdin.setRawMode) {
65
+ try {
66
+ process.stdin.setRawMode(false);
67
+ } catch (e) {
68
+ // 忽略
69
+ }
70
+ }
71
+
72
+ // 移除所有 stdin 监听器,防止残留事件
73
+ process.stdin.removeAllListeners();
74
+
75
+ // 添加错误处理器,防止 EIO 错误导致崩溃
76
+ process.stdin.on('error', (err) => {
77
+ // 忽略 EIO 错误(通常发生在 Ctrl+C 时)
78
+ if (err.code === 'EIO' || err.code === 'ENOTTY') {
79
+ // 安静退出
80
+ process.exit(0);
81
+ }
82
+ });
83
+
84
+ // 暂停输入流
85
+ process.stdin.pause();
86
+
87
+ // 再等待 100ms 确保终端状态稳定
88
+ await new Promise(resolve => setTimeout(resolve, 100));
89
+
90
+ // 使用 execSync 完全替代当前进程
91
+ // ct 会阻塞等待 claude 完成,然后一起退出
92
+ // Claude 独占终端,不会有输入冲突
93
+ try {
94
+ const command = `claude ${args.join(' ')}`;
95
+
96
+ // 切换到目标目录
97
+ const originalCwd = process.cwd();
98
+ try {
99
+ process.chdir(cwd);
100
+ } catch (e) {
101
+ console.log(chalk.yellow(`\n⚠️ 无法切换到目录: ${cwd}`));
102
+ console.log(chalk.gray(`将在当前目录启动\n`));
103
+ }
104
+
105
+ // execSync 会阻塞并完全接管终端
106
+ // 此时 ct 进程只是等待,不处理任何输入
107
+ execSync(command, {
108
+ stdio: 'inherit', // 完全继承 stdio,让 claude 控制终端
109
+ });
110
+
111
+ // 恢复目录
112
+ try {
113
+ process.chdir(originalCwd);
114
+ } catch (e) {
115
+ // 忽略
116
+ }
117
+
118
+ // Claude 正常退出 - 立即退出避免清理错误
119
+ process.exit(0);
120
+ } catch (error) {
121
+ // Claude 执行出错或被用户中断(Ctrl+C)
122
+ // 立即退出,不等待清理
123
+ if (error.status !== undefined) {
124
+ process.exit(error.status);
125
+ } else if (error.signal === 'SIGINT') {
126
+ // 用户按了 Ctrl+C,正常退出
127
+ process.exit(0);
128
+ } else {
129
+ console.log(chalk.red(`\n❌ 启动失败: ${error.message}`));
130
+ process.exit(1);
131
+ }
132
+ }
133
+ }
134
+
135
+ module.exports = {
136
+ resumeSession,
137
+ };