aicodeswitch 3.9.4 → 4.0.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/README.md CHANGED
@@ -14,11 +14,11 @@ AI Code Switch 是帮助你在本地管理 AI 编程工具接入大模型的工
14
14
 
15
15
  * 可登记任意的大模型API接口服务,让你可以在一个面板管理你的大模型厂商
16
16
  * 供应商API一键切换:可以注册多个供应商的多个API接口,当想要切换供应商时,只需要点击一个按钮就可以立即切换
17
- * 自动配置:无需你手动去修改claude code或codex的系统配置文件,aicodeswitch自动帮你修改,你只要启动它,似乎claude code或codex就尽在掌握
17
+ * 自动配置:无需你手动去修改claude code或codex的系统配置文件,aicodeswitch自动帮你修改,你只要启动它,无需配置claude code或codex就可以让它们正常工作
18
18
  * 一键配置供应商,省去复杂的供应商配置流程
19
19
  * API转流:将兼容openai接口标准的模型,轻松接入到claude code中,支持将符合openai、anthropic、gemini的数据格式转发给claude code或codex
20
20
  * 按需代理:根据请求类型,让不同的模型来处理不同的任务,节省成本,基于该特性,可以让glm等原生非多模态模型支持图像识别
21
- * 智能故障切换:当API服务发生故障时,智能切换到其他API服务进行尝试,特别适用于中转服务商
21
+ * 智能故障切换:当API服务发生故障时,智能切换到其他API服务进行尝试,无需手动切换,特别适用于中转服务商
22
22
  * 代理:针对无法直接访问的模型服务,例如地区被ban,支持设置代理来解决
23
23
  * tokens超量限制:避免其中一个服务商的用量超过限制,浪费了钱,适合有免费额度的服务商
24
24
  * 次数超量限制:次数到达一定量后,切换其他服务商,适合coding plan的服务商
@@ -29,7 +29,8 @@ AI Code Switch 是帮助你在本地管理 AI 编程工具接入大模型的工
29
29
  * 导入和导出:一键备份数据,在多太电脑间共享aicodeswitch配置
30
30
  * 自定义API Key,支持B/S架构,让aicodeswitch成为在线服务,提供给团队使用
31
31
  * 数据完全本地,自主可控
32
- * 特殊语法:在发送的提示词最前面添加!!来直接切换为高智商模型服务,简单快捷
32
+ * 特殊语法:在发送的提示词最前面添加`[!]`来直接切换为高智商模型服务,简单快捷
33
+ * 服务端部署,可随时随地使用
33
34
 
34
35
  ## 桌面客户端
35
36
 
@@ -79,15 +80,15 @@ http://127.0.0.1:4567
79
80
 
80
81
  **配置供应商**
81
82
 
82
- * 什么是供应商?
83
- * 供应商配置有什么用?
83
+ * 什么是供应商?
84
+ * 供应商配置有什么用?
84
85
 
85
86
  具体请看下方文档。
86
87
 
87
88
  **路由配置**
88
89
 
89
- * 什么是路由?
90
- * 什么是路由规则?
90
+ * 什么是路由?
91
+ * 什么是路由规则?
91
92
 
92
93
  具体请看下方文档。
93
94
 
@@ -115,22 +116,22 @@ Codex的配置覆盖逻辑一模一样。
115
116
 
116
117
  通过将你所有的AI服务商统一起来管理,可以帮你:
117
118
 
118
- 1. 避免频繁修改配置文件,通过aicodeswitch,可以一键切换到不同的供应商的AI服务API
119
- 2. 通过aicodeswitch,将不同供应商的接口数据,转换为工具可以正确使用的接口数据格式,也就是说,你可以将Claude Code接入遵循openai的接口数据协议的其他接口
120
- 3. 避免你忘记曾经注册过那些供应商
121
- 4. 充分榨干不怎么用的供应商的服务,避免充值后不怎么用浪费了
119
+ 1. 避免频繁修改配置文件,通过aicodeswitch,可以一键切换到不同的供应商的AI服务API
120
+ 2. 通过aicodeswitch,将不同供应商的接口数据,转换为工具可以正确使用的接口数据格式,也就是说,你可以将Claude Code接入遵循openai的接口数据协议的其他接口
121
+ 3. 避免你忘记曾经注册过那些供应商
122
+ 4. 充分榨干不怎么用的供应商的服务,避免充值后不怎么用浪费了
122
123
 
123
124
  ### 什么事API服务的“源类型”
124
125
 
125
126
  供应商接口返回的数据格式标准类型,目前支持以下几种:
126
127
 
127
- * OpenAI Chat
128
- * OpenAI Code
129
- * OpenAI Responses
130
- * Claude Chat
131
- * Claude Code
132
- * DeepSeek Chat
133
- * Gemini
128
+ * OpenAI(Responses API标准)
129
+ * OpenAI Chat (Chat Completions API标准)
130
+ * DeepSeek Chat(reasoning_content标准)
131
+ * Claude
132
+ * Claude Chat
133
+ * Gemini
134
+ * Gemini Chat
134
135
 
135
136
  **有什么用?**
136
137
 
@@ -140,7 +141,7 @@ aicodeswitch内部,会根据“源类型”来转换数据。例如,你的
140
141
 
141
142
  ### 什么是路由?
142
143
 
143
- 路由是aicodeswitch的核心功能,它负责将不同的对象(目前指Claude Code和Codex)的请求,路由到不同的供应商API服务上。
144
+ 路由是aicodeswitch的核心功能,它负责将不同的对象工具(目前指Claude Code和Codex)的请求,路由到不同的供应商API服务上。
144
145
 
145
146
  ### 什么是“客户端工具”?
146
147
 
@@ -189,16 +190,16 @@ aicodeswitch内部,会根据“源类型”来转换数据。例如,你的
189
190
 
190
191
  **请求日志**:所有 API 请求的详细记录
191
192
 
192
- * 请求来源和目标
193
- * 请求内容和响应
194
- * 耗时和状态码
195
- * 错误信息(如有)
193
+ * 请求来源和目标
194
+ * 请求内容和响应
195
+ * 耗时和状态码
196
+ * 错误信息(如有)
196
197
 
197
198
  **错误日志**:错误和异常记录
198
199
 
199
- * 错误类型
200
- * 错误详情
201
- * 发生时间
200
+ * 错误类型
201
+ * 错误详情
202
+ * 发生时间
202
203
 
203
204
  **会话日志**:按照会话session来汇集日志
204
205
 
@@ -242,17 +243,17 @@ PORT=4567
242
243
 
243
244
  ## 我的开源
244
245
 
245
- * [PCM](https://github.com/tangshuang/pcm): 用户意图识别、精准上下文、多线对话的Agent系统
246
- * [Lan Transfer](https://github.com/tangshuang/lan-transfer): 免费高效的局域网文件互传工具
247
- * [MCP Bone](https://github.com/tangshuang/mcp-bone): 远程托管的MCP服管理工具
248
- * [Anys](https://github.com/tangshuang/anys): 免费前端监控kit
249
- * [WebCut](https://github.com/tangshuang/webcut): 免费开源的网页端视频剪辑UI框架
250
- * [indb](https://github.com/tangshuang/indb): 网页端轻量kv数据库操作库
251
- * [Formast](https://github.com/tangshuang/formast): 复杂业务场景下的企业级JSON驱动表单框架
246
+ * [PCM](https://github.com/tangshuang/pcm): 用户意图识别、精准上下文、多线对话的Agent系统
247
+ * [Lan Transfer](https://github.com/tangshuang/lan-transfer): 免费高效的局域网文件互传工具
248
+ * [MCP Bone](https://github.com/tangshuang/mcp-bone): 远程托管的MCP服管理工具
249
+ * [Anys](https://github.com/tangshuang/anys): 免费前端监控kit
250
+ * [WebCut](https://github.com/tangshuang/webcut): 免费开源的网页端视频剪辑UI框架
251
+ * [indb](https://github.com/tangshuang/indb): 网页端轻量kv数据库操作库
252
+ * [Formast](https://github.com/tangshuang/formast): 复杂业务场景下的企业级JSON驱动表单框架
252
253
 
253
254
  ## 关联资源
254
255
 
255
- * [Claude Code 深度教程](https://claudecode.tangshuang.net): 100%免费的Claude Code入门到精通教程
256
+ * [Claude Code 深度教程](https://claudecode.tangshuang.net): 100%免费的Claude Code入门到精通教程
256
257
 
257
258
  ## 支持我
258
259
 
@@ -264,8 +265,8 @@ PORT=4567
264
265
 
265
266
  此项目采用双许可证模式:
266
267
 
267
- * **开源使用**:项目默认采用 GPL 3.0 许可证,允许个人免费使用、修改和分发,但所有衍生品必须开源。
268
- * **商业使用**:如果您希望商业化使用而不遵守 GPL 条款(例如闭源销售),请联系我们购买单独的商业许可证。
268
+ * **开源使用**:项目默认采用 GPL 3.0 许可证,允许个人免费使用、修改和分发,但所有衍生品必须开源。
269
+ * **商业使用**:如果您希望商业化使用而不遵守 GPL 条款(例如闭源销售),请联系我们购买单独的商业许可证。
269
270
 
270
271
  ## 技术支持
271
272
 
package/UPGRADE.md CHANGED
@@ -1,5 +1,7 @@
1
1
  本次升级将会有破坏性影响,你需要注意:
2
2
 
3
- * 升级后,不再使用数据库来持久化数据,而是使用json文件,你可以通过直接阅读json文件来查看数据
4
- * 升级后,你需要重启服务,让新的数据持久化系统生效
5
- * 升级后,如果遇到问题,不要惊慌,你可以直接重新安装老版本,老版本仍然使用原来的数据库文件,你可以先把老版本的数据备份后,安装新版本再来恢复数据
3
+ * 对于API服务,你需要进行检查,OpenAI 类型的服务,请检查 `baseUrl` 是否以 `/v1` 结尾,如果是,请移除。
4
+ * 对codex/claude-code的配置覆盖逻辑,不再与“是否激活路由”绑定,而是在启动服务时写入覆盖并备份,停止服务时恢复备份。
5
+ * 升级后,一旦你启动 AICodeSwitch,就可以统计到所有请求,让你的AI编程统计数据更准确。
6
+ * 由于Codex升级后,数据结构有了较大的变化,导致我们的转流可能存在问题,请为Codex使用复合Responses API标准的接口(注:部分国内厂商实现的responses接口不够完善,部分工具不支持)。
7
+ * 升级后,数据版本会变更,如果重启后没有看到原来的数据,需退回上一个版本,导出数据后重新升级,并导入备份的数据。
package/bin/restore.js CHANGED
@@ -4,6 +4,9 @@ const os = require('os');
4
4
  const chalk = require('chalk');
5
5
  const boxen = require('boxen');
6
6
  const ora = require('ora');
7
+ const { parseToml, stringifyToml, mergeJsonSettings, mergeTomlSettings, atomicWriteFile } = require('./utils/config-helpers');
8
+ const { isServerRunning, getServerInfo } = require('./utils/get-server');
9
+ const { findPidByPort } = require('./utils/port-utils');
7
10
 
8
11
  // 停用所有激活的路由(直接操作数据库文件)
9
12
  const deactivateAllRoutes = () => {
@@ -55,7 +58,7 @@ const deactivateAllRoutes = () => {
55
58
  }
56
59
  };
57
60
 
58
- // 恢复 Claude Code 配置
61
+ // 恢复 Claude Code 配置(使用智能合并)
59
62
  const restoreClaudeConfig = () => {
60
63
  const results = {
61
64
  restored: [],
@@ -69,13 +72,33 @@ const restoreClaudeConfig = () => {
69
72
  const claudeSettingsPath = path.join(claudeDir, 'settings.json');
70
73
  const claudeSettingsBakPath = path.join(claudeDir, 'settings.json.aicodeswitch_backup');
71
74
 
72
- // Restore settings.json
75
+ // Restore settings.json(智能合并)
73
76
  if (fs.existsSync(claudeSettingsBakPath)) {
74
- if (fs.existsSync(claudeSettingsPath)) {
75
- fs.unlinkSync(claudeSettingsPath);
77
+ try {
78
+ const backupSettings = JSON.parse(fs.readFileSync(claudeSettingsBakPath, 'utf-8'));
79
+ let currentSettings = {};
80
+ if (fs.existsSync(claudeSettingsPath)) {
81
+ try {
82
+ currentSettings = JSON.parse(fs.readFileSync(claudeSettingsPath, 'utf-8'));
83
+ } catch (e) {
84
+ // 忽略解析错误
85
+ }
86
+ }
87
+
88
+ const mergedSettings = mergeJsonSettings(
89
+ backupSettings,
90
+ currentSettings,
91
+ ['env.ANTHROPIC_AUTH_TOKEN', 'env.ANTHROPIC_BASE_URL', 'env.API_TIMEOUT_MS',
92
+ 'env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC', 'env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS',
93
+ 'permissions', 'skipDangerousModePermissionPrompt']
94
+ );
95
+
96
+ atomicWriteFile(claudeSettingsPath, JSON.stringify(mergedSettings, null, 2));
97
+ fs.unlinkSync(claudeSettingsBakPath);
98
+ results.restored.push('settings.json');
99
+ } catch (error) {
100
+ results.errors.push({ file: 'settings.json', error: error.message });
76
101
  }
77
- fs.renameSync(claudeSettingsBakPath, claudeSettingsPath);
78
- results.restored.push('settings.json');
79
102
  } else {
80
103
  results.notFound.push('settings.json.aicodeswitch_backup');
81
104
  }
@@ -88,13 +111,31 @@ const restoreClaudeConfig = () => {
88
111
  const claudeJsonPath = path.join(homeDir, '.claude.json');
89
112
  const claudeJsonBakPath = path.join(homeDir, '.claude.json.aicodeswitch_backup');
90
113
 
91
- // Restore .claude.json
114
+ // Restore .claude.json(智能合并)
92
115
  if (fs.existsSync(claudeJsonBakPath)) {
93
- if (fs.existsSync(claudeJsonPath)) {
94
- fs.unlinkSync(claudeJsonPath);
116
+ try {
117
+ const backupJson = JSON.parse(fs.readFileSync(claudeJsonBakPath, 'utf-8'));
118
+ let currentJson = {};
119
+ if (fs.existsSync(claudeJsonPath)) {
120
+ try {
121
+ currentJson = JSON.parse(fs.readFileSync(claudeJsonPath, 'utf-8'));
122
+ } catch (e) {
123
+ // 忽略解析错误
124
+ }
125
+ }
126
+
127
+ const mergedJson = mergeJsonSettings(
128
+ backupJson,
129
+ currentJson,
130
+ ['hasCompletedOnboarding', 'mcpServers']
131
+ );
132
+
133
+ atomicWriteFile(claudeJsonPath, JSON.stringify(mergedJson, null, 2));
134
+ fs.unlinkSync(claudeJsonBakPath);
135
+ results.restored.push('.claude.json');
136
+ } catch (error) {
137
+ results.errors.push({ file: '.claude.json', error: error.message });
95
138
  }
96
- fs.renameSync(claudeJsonBakPath, claudeJsonPath);
97
- results.restored.push('.claude.json');
98
139
  } else {
99
140
  results.notFound.push('.claude.json.aicodeswitch_backup');
100
141
  }
@@ -105,7 +146,7 @@ const restoreClaudeConfig = () => {
105
146
  return results;
106
147
  };
107
148
 
108
- // 恢复 Codex 配置
149
+ // 恢复 Codex 配置(使用智能合并)
109
150
  const restoreCodexConfig = () => {
110
151
  const results = {
111
152
  restored: [],
@@ -119,13 +160,32 @@ const restoreCodexConfig = () => {
119
160
  const codexConfigPath = path.join(codexDir, 'config.toml');
120
161
  const codexConfigBakPath = path.join(codexDir, 'config.toml.aicodeswitch_backup');
121
162
 
122
- // Restore config.toml
163
+ // Restore config.toml(智能合并)
123
164
  if (fs.existsSync(codexConfigBakPath)) {
124
- if (fs.existsSync(codexConfigPath)) {
125
- fs.unlinkSync(codexConfigPath);
165
+ try {
166
+ const backupConfig = parseToml(fs.readFileSync(codexConfigBakPath, 'utf-8'));
167
+ let currentConfig = {};
168
+ if (fs.existsSync(codexConfigPath)) {
169
+ try {
170
+ currentConfig = parseToml(fs.readFileSync(codexConfigPath, 'utf-8'));
171
+ } catch (e) {
172
+ // 忽略解析错误
173
+ }
174
+ }
175
+
176
+ const mergedConfig = mergeTomlSettings(
177
+ backupConfig,
178
+ currentConfig,
179
+ ['model_provider', 'model', 'model_reasoning_effort', 'disable_response_storage',
180
+ 'preferred_auth_method', 'requires_openai_auth', 'enableRouteSelection', 'model_providers.aicodeswitch']
181
+ );
182
+
183
+ atomicWriteFile(codexConfigPath, stringifyToml(mergedConfig));
184
+ fs.unlinkSync(codexConfigBakPath);
185
+ results.restored.push('config.toml');
186
+ } catch (error) {
187
+ results.errors.push({ file: 'config.toml', error: error.message });
126
188
  }
127
- fs.renameSync(codexConfigBakPath, codexConfigPath);
128
- results.restored.push('config.toml');
129
189
  } else {
130
190
  results.notFound.push('config.toml.aicodeswitch_backup');
131
191
  }
@@ -138,13 +198,31 @@ const restoreCodexConfig = () => {
138
198
  const codexAuthPath = path.join(homeDir, '.codex', 'auth.json');
139
199
  const codexAuthBakPath = path.join(homeDir, '.codex', 'auth.json.aicodeswitch_backup');
140
200
 
141
- // Restore auth.json
201
+ // Restore auth.json(智能合并)
142
202
  if (fs.existsSync(codexAuthBakPath)) {
143
- if (fs.existsSync(codexAuthPath)) {
144
- fs.unlinkSync(codexAuthPath);
203
+ try {
204
+ const backupAuth = JSON.parse(fs.readFileSync(codexAuthBakPath, 'utf-8'));
205
+ let currentAuth = {};
206
+ if (fs.existsSync(codexAuthPath)) {
207
+ try {
208
+ currentAuth = JSON.parse(fs.readFileSync(codexAuthPath, 'utf-8'));
209
+ } catch (e) {
210
+ // 忽略解析错误
211
+ }
212
+ }
213
+
214
+ const mergedAuth = mergeJsonSettings(
215
+ backupAuth,
216
+ currentAuth,
217
+ ['OPENAI_API_KEY']
218
+ );
219
+
220
+ atomicWriteFile(codexAuthPath, JSON.stringify(mergedAuth, null, 2));
221
+ fs.unlinkSync(codexAuthBakPath);
222
+ results.restored.push('auth.json');
223
+ } catch (error) {
224
+ results.errors.push({ file: 'auth.json', error: error.message });
145
225
  }
146
- fs.renameSync(codexAuthBakPath, codexAuthPath);
147
- results.restored.push('auth.json');
148
226
  } else {
149
227
  results.notFound.push('auth.json.aicodeswitch_backup');
150
228
  }
@@ -243,6 +321,32 @@ const restore = async () => {
243
321
  process.exit(1);
244
322
  }
245
323
 
324
+ // 服务运行中时,禁止执行手动 restore,避免中断当前代理服务
325
+ const { host, port } = getServerInfo();
326
+ const runningPid = await findPidByPort(port);
327
+ const runningByPidFile = isServerRunning();
328
+
329
+ if (runningByPidFile || runningPid) {
330
+ const pidText = runningPid ? `${runningPid}` : 'unknown';
331
+ const message = chalk.yellow.bold('⚠ Restore skipped: server is running\n\n') +
332
+ chalk.white('Detected running server: ') +
333
+ chalk.cyan(`http://${host}:${port}`) +
334
+ chalk.white(` (PID: ${pidText})\n\n`) +
335
+ chalk.white('Please run ') + chalk.cyan.bold('aicos stop') +
336
+ chalk.white(' first.\n') +
337
+ chalk.white('The ') + chalk.cyan.bold('stop') +
338
+ chalk.white(' command will automatically restore configuration files.\n');
339
+
340
+ console.log(boxen(message, {
341
+ padding: 1,
342
+ margin: 1,
343
+ borderStyle: 'round',
344
+ borderColor: 'yellow'
345
+ }));
346
+ console.log('');
347
+ return;
348
+ }
349
+
246
350
  // 恢复配置
247
351
  if (target === 'claude-code' || !target) {
248
352
  const spinner = ora({
package/bin/start.js CHANGED
@@ -27,39 +27,31 @@ const start = async (options = {}) => {
27
27
  const { host, port } = getServerInfo();
28
28
  if (isServerRunning() || await findPidByPort(port)) {
29
29
  if (!silent) {
30
- if (!silent) {
31
- console.log(boxen(
32
- chalk.yellow.bold(' Server is already running!\n\n') +
33
- chalk.white(`URL: `) + chalk.cyan.bold(`http://${host}:${port}\n\n`) +
34
- chalk.white('Use ') + chalk.cyan('aicos restart') + chalk.white(' to restart the server.\n'),
35
- {
36
- padding: 1,
37
- margin: 1,
38
- borderStyle: 'round',
39
- borderColor: 'yellow'
40
- }
41
- ));
42
- console.log('');
43
- }
44
-
45
- if (callback) {
46
- callback();
47
- }
30
+ console.log(boxen(
31
+ chalk.yellow.bold('⚠ Server is already running!\n\n') +
32
+ chalk.white('URL: ') + chalk.cyan.bold(`http://${host}:${port}\n\n`) +
33
+ chalk.white('Use ') + chalk.cyan('aicos restart') + chalk.white(' to restart the server.\n'),
34
+ {
35
+ padding: 1,
36
+ margin: 1,
37
+ borderStyle: 'round',
38
+ borderColor: 'yellow'
39
+ }
40
+ ));
41
+ console.log('');
42
+ }
48
43
 
49
- if (!noExit) {
50
- process.exit(0);
51
- }
44
+ if (callback) {
45
+ callback();
46
+ }
52
47
 
53
- return true;
48
+ if (!noExit) {
49
+ process.exit(0);
54
50
  }
55
- if (callback) callback();
56
- if (!noExit) process.exit(0);
51
+
57
52
  return true;
58
53
  }
59
54
 
60
-
61
- // 启动服务器
62
-
63
55
  const spinner = ora({
64
56
  text: chalk.cyan('Starting AI Code Switch server...'),
65
57
  color: 'cyan',
@@ -79,12 +71,11 @@ const start = async (options = {}) => {
79
71
  }
80
72
 
81
73
  // 启动服务器进程 - 完全分离
82
- // 打开日志文件用于输出
83
74
  const logFd = fs.openSync(LOG_FILE, 'a');
84
75
 
85
76
  const serverProcess = spawn('node', [serverPath], {
86
77
  detached: true,
87
- stdio: ['ignore', logFd, logFd] // 使用文件描述符
78
+ stdio: ['ignore', logFd, logFd]
88
79
  });
89
80
 
90
81
  // 关闭文件描述符(子进程会保持打开)
@@ -104,13 +95,13 @@ const start = async (options = {}) => {
104
95
  try {
105
96
  const pid = parseInt(fs.readFileSync(PID_FILE, 'utf-8'), 10);
106
97
  process.kill(pid, 0);
98
+
107
99
  if (!silent) {
108
100
  spinner.succeed(chalk.green('Server started successfully!'));
109
101
 
110
- const { host, port } = getServerInfo();
111
- const url = `http://${host}:${port}`;
102
+ const { host: runningHost, port: runningPort } = getServerInfo();
103
+ const url = `http://${runningHost}:${runningPort}`;
112
104
 
113
- // 显示漂亮的启动信息
114
105
  console.log(boxen(
115
106
  chalk.green.bold('🚀 AI Code Switch Server\n\n') +
116
107
  chalk.white('Status: ') + chalk.green.bold('● Running\n') +
@@ -133,9 +124,7 @@ const start = async (options = {}) => {
133
124
  console.log('\n');
134
125
  }
135
126
 
136
- // (callback)
137
127
  if (callback) callback();
138
- // 立即退出,返回控制台
139
128
  if (!noExit) process.exit(0);
140
129
  return true;
141
130
  } catch (err) {
@@ -144,13 +133,12 @@ const start = async (options = {}) => {
144
133
  if (!noExit) process.exit(1);
145
134
  return false;
146
135
  }
147
- } else {
148
- spinner.fail(chalk.red('Failed to start server!'));
149
- console.log(chalk.yellow(`\nCheck logs: ${chalk.cyan(LOG_FILE)}\n`));
150
- if (!noExit) process.exit(1);
151
- return false;
152
136
  }
137
+
138
+ spinner.fail(chalk.red('Failed to start server!'));
139
+ console.log(chalk.yellow(`\nCheck logs: ${chalk.cyan(LOG_FILE)}\n`));
140
+ if (!noExit) process.exit(1);
141
+ return false;
153
142
  };
154
143
 
155
- // 导出辅助函数供其他模块使用
156
- module.exports = start;
144
+ module.exports = start;
package/bin/stop.js CHANGED
@@ -25,7 +25,7 @@ const stop = async (options = {}) => {
25
25
  console.log('\n' + chalk.gray(`Process found: ${chalk.white(pid)} (${chalk.gray(processInfo)})`));
26
26
  }
27
27
 
28
- // 尝试终止进程
28
+ // 尝试终止进程(服务端会在 SIGTERM/SIGINT 时执行配置恢复)
29
29
  process.kill(pid, 'SIGTERM');
30
30
 
31
31
  // 等待进程停止
@@ -58,7 +58,7 @@ const stop = async (options = {}) => {
58
58
  catch (err) {
59
59
  // 进程不存在
60
60
  if (err.code === 'ESRCH') {
61
- spinner.warn(chalk.yellow(`PID ${pid} not found!`));
61
+ spinner.warn(chalk.yellow(`PID ${pid} not found!`));
62
62
  }
63
63
  else {
64
64
  spinner.fail(chalk.red(`\nError: ${err.message}\n`));
@@ -115,4 +115,4 @@ const showStoppedMessage = () => {
115
115
  console.log(chalk.white('Use ') + chalk.cyan('aicos start') + chalk.white(' to start the server again.\n'));
116
116
  };
117
117
 
118
- module.exports = stop;
118
+ module.exports = stop;