aicodeswitch 1.10.2 → 2.0.1

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 CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ### 2.0.1 (2026-01-27)
6
+
5
7
  ### 1.10.2 (2026-01-26)
6
8
 
7
9
  ### 1.10.1 (2026-01-25)
package/bin/cli.js CHANGED
@@ -1,18 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const path = require('path');
4
-
5
3
  const args = process.argv.slice(2);
6
4
  const command = args[0];
7
5
 
8
6
  const commands = {
9
- start: () => require(path.join(__dirname, 'start')),
10
- stop: () => require(path.join(__dirname, 'stop')),
11
- restart: () => require(path.join(__dirname, 'restart')),
12
- update: () => require(path.join(__dirname, 'update')),
13
- restore: () => require(path.join(__dirname, 'restore')),
14
- version: () => require(path.join(__dirname, 'version')),
15
- ui: () => require(path.join(__dirname, 'ui')),
7
+ start: require('./start'),
8
+ stop: require('./stop'),
9
+ restart: require('./restart'),
10
+ update: require('./update'),
11
+ restore: require('./restore'),
12
+ version: require('./version'),
13
+ ui: require('./ui'),
16
14
  };
17
15
 
18
16
  if (!command || !commands[command]) {
package/bin/restart.js CHANGED
@@ -1,232 +1,10 @@
1
- const { spawn } = require('child_process');
2
- const path = require('path');
3
- const fs = require('fs');
4
- const os = require('os');
5
- const chalk = require('chalk');
6
- const boxen = require('boxen');
7
- const ora = require('ora');
1
+ // 重启服务器 - 依次执行 stop 和 start 命令
2
+ // 这样可以复用 stop 和 start 命令中的所有逻辑
3
+ const stop = require('./stop');
4
+ const start = require('./start');
8
5
 
9
- const PID_FILE = path.join(os.homedir(), '.aicodeswitch', 'server.pid');
10
- const LOG_FILE = path.join(os.homedir(), '.aicodeswitch', 'server.log');
11
-
12
- // 确保目录存在
13
- const ensureDir = (filePath) => {
14
- const dir = path.dirname(filePath);
15
- if (!fs.existsSync(dir)) {
16
- fs.mkdirSync(dir, { recursive: true });
17
- }
18
- };
19
-
20
- const isServerRunning = () => {
21
- if (!fs.existsSync(PID_FILE)) {
22
- return false;
23
- }
24
-
25
- try {
26
- const pid = parseInt(fs.readFileSync(PID_FILE, 'utf-8'), 10);
27
- // 检查进程是否存在
28
- process.kill(pid, 0);
29
- return true;
30
- } catch (err) {
31
- // 进程不存在,删除过期的 PID 文件
32
- fs.unlinkSync(PID_FILE);
33
- return false;
34
- }
35
- };
36
-
37
- const getServerInfo = () => {
38
- // 尝试多个可能的配置文件位置
39
- const possiblePaths = [
40
- path.join(os.homedir(), '.aicodeswitch', '.env'),
41
- path.join(os.homedir(), '.aicodeswitch', 'aicodeswitch.conf')
42
- ];
43
-
44
- let host = '127.0.0.1';
45
- let port = 4567;
46
-
47
- for (const dotenvPath of possiblePaths) {
48
- if (fs.existsSync(dotenvPath)) {
49
- const content = fs.readFileSync(dotenvPath, 'utf-8');
50
- const hostMatch = content.match(/HOST=(.+)/);
51
- const portMatch = content.match(/PORT=(.+)/);
52
-
53
- if (hostMatch) host = hostMatch[1].trim();
54
- if (portMatch) port = parseInt(portMatch[1].trim(), 10);
55
- break;
56
- }
57
- }
58
-
59
- return { host, port };
60
- };
61
-
62
- const stopServer = async () => {
63
- if (!fs.existsSync(PID_FILE)) {
64
- return true; // 服务未运行,视为停止成功
65
- }
66
-
67
- const spinner = ora({
68
- text: chalk.cyan('Stopping server...'),
69
- color: 'cyan'
70
- }).start();
71
-
72
- try {
73
- const pid = parseInt(fs.readFileSync(PID_FILE, 'utf-8'), 10);
74
-
75
- try {
76
- process.kill(pid, 'SIGTERM');
77
-
78
- // 等待进程停止
79
- let attempts = 0;
80
- const maxAttempts = 10;
81
-
82
- await new Promise((resolve) => {
83
- const checkStopped = setInterval(() => {
84
- attempts++;
85
- try {
86
- process.kill(pid, 0);
87
- if (attempts >= maxAttempts) {
88
- clearInterval(checkStopped);
89
- // 强制终止
90
- try {
91
- process.kill(pid, 'SIGKILL');
92
- } catch (e) {
93
- // 进程可能已经停止
94
- }
95
- resolve();
96
- }
97
- } catch (err) {
98
- // 进程已停止
99
- clearInterval(checkStopped);
100
- resolve();
101
- }
102
- }, 200);
103
- });
104
-
105
- spinner.succeed(chalk.green('Server stopped'));
106
- fs.unlinkSync(PID_FILE);
107
- return true;
108
- } catch (err) {
109
- // 进程不存在
110
- spinner.succeed(chalk.green('Server stopped'));
111
- fs.unlinkSync(PID_FILE);
112
- return true;
113
- }
114
- } catch (err) {
115
- spinner.fail(chalk.red('Failed to stop server'));
116
- return false;
117
- }
118
- };
119
-
120
- const startServer = async () => {
121
- const spinner = ora({
122
- text: chalk.cyan('Starting server...'),
123
- color: 'cyan'
124
- }).start();
125
-
126
- ensureDir(PID_FILE);
127
- ensureDir(LOG_FILE);
128
-
129
- // 找到 main.js 的路径
130
- const serverPath = path.join(__dirname, '..', 'dist', 'server', 'main.js');
131
-
132
- if (!fs.existsSync(serverPath)) {
133
- spinner.fail(chalk.red('Server file not found!'));
134
- console.log(chalk.yellow(`\nPlease run ${chalk.cyan('npm run build')} first.\n`));
135
- return false;
136
- }
137
-
138
- // 启动服务器进程 - 完全分离
139
- // 打开日志文件用于输出
140
- const logFd = fs.openSync(LOG_FILE, 'a');
141
-
142
- const serverProcess = spawn('node', [serverPath], {
143
- detached: true,
144
- stdio: ['ignore', logFd, logFd] // 使用文件描述符
145
- });
146
-
147
- // 关闭文件描述符(子进程会保持打开)
148
- fs.closeSync(logFd);
149
-
150
- // 保存 PID
151
- fs.writeFileSync(PID_FILE, serverProcess.pid.toString());
152
-
153
- // 分离进程,让父进程可以退出
154
- serverProcess.unref();
155
-
156
- // 等待服务器启动
157
- await new Promise(resolve => setTimeout(resolve, 2000));
158
-
159
- // 检查服务器是否成功启动
160
- if (fs.existsSync(PID_FILE)) {
161
- try {
162
- const pid = parseInt(fs.readFileSync(PID_FILE, 'utf-8'), 10);
163
- process.kill(pid, 0);
164
- spinner.succeed(chalk.green('Server started'));
165
- return true;
166
- } catch (err) {
167
- spinner.fail(chalk.red('Failed to start server'));
168
- return false;
169
- }
170
- } else {
171
- spinner.fail(chalk.red('Failed to start server'));
172
- return false;
173
- }
174
- };
175
-
176
- const restart = async () => {
177
- console.log('\n');
178
-
179
- const wasRunning = isServerRunning();
180
-
181
- if (wasRunning) {
182
- console.log(chalk.cyan('🔄 Restarting AI Code Switch server...\n'));
183
-
184
- // 停止服务器
185
- const stopped = await stopServer();
186
- if (!stopped) {
187
- console.log(chalk.red('\nFailed to stop server. Restart aborted.\n'));
188
- process.exit(1);
189
- }
190
-
191
- // 等待一下确保端口释放
192
- await new Promise(resolve => setTimeout(resolve, 500));
193
- } else {
194
- console.log(chalk.cyan('Starting AI Code Switch server...\n'));
195
- }
196
-
197
- // 启动服务器
198
- const started = await startServer();
199
-
200
- if (!started) {
201
- console.log(chalk.yellow(`\nCheck logs: ${chalk.cyan(LOG_FILE)}\n`));
202
- process.exit(1);
203
- }
204
-
205
- const { host, port } = getServerInfo();
206
- const url = `http://${host}:${port}`;
207
-
208
- // 显示漂亮的启动信息
209
- console.log(boxen(
210
- chalk.green.bold('🚀 AI Code Switch Server\n\n') +
211
- chalk.white('Status: ') + chalk.green.bold('● Running\n') +
212
- chalk.white('URL: ') + chalk.cyan.bold(url) + '\n' +
213
- chalk.white('Logs: ') + chalk.gray(LOG_FILE) + '\n\n' +
214
- chalk.gray('Server has been ' + (wasRunning ? 'restarted' : 'started') + ' successfully'),
215
- {
216
- padding: 1,
217
- margin: 1,
218
- borderStyle: 'double',
219
- borderColor: 'green'
220
- }
221
- ));
222
-
223
- console.log(chalk.cyan('💡 Tips:\n'));
224
- console.log(chalk.white(' • Open browser: ') + chalk.cyan(url));
225
- console.log(chalk.white(' • View logs: ') + chalk.gray(`tail -f ${LOG_FILE}`));
226
- console.log(chalk.white(' • Stop server: ') + chalk.yellow('aicos stop'));
227
- console.log('\n');
228
-
229
- process.exit(0);
6
+ const restart = () => {
7
+ stop({ silent: true, callback: () => start() });
230
8
  };
231
9
 
232
- module.exports = restart();
10
+ module.exports = restart;
package/bin/restore.js CHANGED
@@ -225,4 +225,4 @@ const restore = async () => {
225
225
  console.log('\n');
226
226
  };
227
227
 
228
- module.exports = restore();
228
+ module.exports = restore;
package/bin/start.js CHANGED
@@ -5,6 +5,7 @@ const os = require('os');
5
5
  const chalk = require('chalk');
6
6
  const boxen = require('boxen');
7
7
  const ora = require('ora');
8
+ const { isServerRunning, getServerInfo } = require('./utils/get-server');
8
9
 
9
10
  const PID_FILE = path.join(os.homedir(), '.aicodeswitch', 'server.pid');
10
11
  const LOG_FILE = path.join(os.homedir(), '.aicodeswitch', 'server.log');
@@ -17,71 +18,49 @@ const ensureDir = (filePath) => {
17
18
  }
18
19
  };
19
20
 
20
- const isServerRunning = () => {
21
- if (!fs.existsSync(PID_FILE)) {
22
- return false;
23
- }
24
-
25
- try {
26
- const pid = parseInt(fs.readFileSync(PID_FILE, 'utf-8'), 10);
27
- // 检查进程是否存在
28
- process.kill(pid, 0);
29
- return true;
30
- } catch (err) {
31
- // 进程不存在,删除过期的 PID 文件
32
- fs.unlinkSync(PID_FILE);
33
- return false;
34
- }
35
- };
36
-
37
- const getServerInfo = () => {
38
- // 尝试多个可能的配置文件位置
39
- const possiblePaths = [
40
- path.join(os.homedir(), '.aicodeswitch', '.env'),
41
- path.join(os.homedir(), '.aicodeswitch', 'aicodeswitch.conf')
42
- ];
43
-
44
- let host = '127.0.0.1';
45
- let port = 4567;
46
-
47
- for (const dotenvPath of possiblePaths) {
48
- if (fs.existsSync(dotenvPath)) {
49
- const content = fs.readFileSync(dotenvPath, 'utf-8');
50
- const hostMatch = content.match(/HOST=(.+)/);
51
- const portMatch = content.match(/PORT=(.+)/);
52
-
53
- if (hostMatch) host = hostMatch[1].trim();
54
- if (portMatch) port = parseInt(portMatch[1].trim(), 10);
55
- break;
56
- }
57
- }
58
-
59
- return { host, port };
60
- };
61
-
62
- const start = async () => {
21
+ const start = async (options = {}) => {
22
+ const { silent = false, noExit = false, callback } = options;
63
23
  console.log('\n');
64
24
 
65
- // 检查是否已经运行
25
+ // 已经运行
66
26
  if (isServerRunning()) {
67
27
  const { host, port } = getServerInfo();
68
- console.log(boxen(
69
- chalk.yellow.bold('⚠ Server is already running!\n\n') +
70
- chalk.white(`URL: `) + chalk.cyan.bold(`http://${host}:${port}`),
71
- {
72
- padding: 1,
73
- margin: 1,
74
- borderStyle: 'round',
75
- borderColor: 'yellow'
28
+ if (!silent) {
29
+ if (!silent) {
30
+ console.log(boxen(
31
+ chalk.yellow.bold('⚠ Server is already running!\n\n') +
32
+ chalk.white(`URL: `) + chalk.cyan.bold(`http://${host}:${port}`),
33
+ {
34
+ padding: 1,
35
+ margin: 1,
36
+ borderStyle: 'round',
37
+ borderColor: 'yellow'
38
+ }
39
+ ));
40
+ console.log('');
76
41
  }
77
- ));
78
- console.log('');
79
- process.exit(0);
42
+
43
+ if (callback) {
44
+ callback();
45
+ }
46
+
47
+ if (!noExit) {
48
+ process.exit(0);
49
+ }
50
+
51
+ return true;
52
+ }
53
+ if (callback) callback();
54
+ if (!noExit) process.exit(0);
55
+ return true;
80
56
  }
81
57
 
58
+
59
+ // 启动服务器
60
+
82
61
  const spinner = ora({
83
62
  text: chalk.cyan('Starting AI Code Switch server...'),
84
- color: 'cyan'
63
+ color: 'cyan',
85
64
  }).start();
86
65
 
87
66
  ensureDir(PID_FILE);
@@ -93,7 +72,8 @@ const start = async () => {
93
72
  if (!fs.existsSync(serverPath)) {
94
73
  spinner.fail(chalk.red('Server file not found!'));
95
74
  console.log(chalk.yellow(`\nPlease run ${chalk.cyan('npm run build')} first.\n`));
96
- process.exit(1);
75
+ if (!noExit) process.exit(1);
76
+ return false;
97
77
  }
98
78
 
99
79
  // 启动服务器进程 - 完全分离
@@ -122,45 +102,53 @@ const start = async () => {
122
102
  try {
123
103
  const pid = parseInt(fs.readFileSync(PID_FILE, 'utf-8'), 10);
124
104
  process.kill(pid, 0);
125
- spinner.succeed(chalk.green('Server started successfully!'));
126
-
127
- const { host, port } = getServerInfo();
128
- const url = `http://${host}:${port}`;
129
-
130
- // 显示漂亮的启动信息
131
- console.log(boxen(
132
- chalk.green.bold('🚀 AI Code Switch Server\n\n') +
133
- chalk.white('Status: ') + chalk.green.bold(' Running\n') +
134
- chalk.white('URL: ') + chalk.cyan.bold(url) + '\n' +
135
- chalk.white('PID: ') + chalk.yellow(pid) + '\n' +
136
- chalk.white('Logs: ') + chalk.gray(LOG_FILE) + '\n\n' +
137
- chalk.gray('Open the URL in your browser to access the dashboard'),
138
- {
139
- padding: 1,
140
- margin: 1,
141
- borderStyle: 'double',
142
- borderColor: 'green'
143
- }
144
- ));
145
-
146
- console.log(chalk.cyan('💡 Tips:\n'));
147
- console.log(chalk.white(' Open browser: ') + chalk.cyan(url));
148
- console.log(chalk.white(' • View logs: ') + chalk.gray(`tail -f ${LOG_FILE}`));
149
- console.log(chalk.white(' • Stop server: ') + chalk.yellow('aicos stop'));
150
- console.log('\n');
105
+ if (!silent) {
106
+ spinner.succeed(chalk.green('Server started successfully!'));
107
+
108
+ const { host, port } = getServerInfo();
109
+ const url = `http://${host}:${port}`;
110
+
111
+ // 显示漂亮的启动信息
112
+ console.log(boxen(
113
+ chalk.green.bold('🚀 AI Code Switch Server\n\n') +
114
+ chalk.white('Status: ') + chalk.green.bold('● Running\n') +
115
+ chalk.white('URL: ') + chalk.cyan.bold(url) + '\n' +
116
+ chalk.white('PID: ') + chalk.yellow(pid) + '\n' +
117
+ chalk.white('Logs: ') + chalk.gray(LOG_FILE) + '\n\n' +
118
+ chalk.gray('Open the URL in your browser to access the dashboard'),
119
+ {
120
+ padding: 1,
121
+ margin: 1,
122
+ borderStyle: 'double',
123
+ borderColor: 'green'
124
+ }
125
+ ));
126
+
127
+ console.log(chalk.cyan('💡 Tips:\n'));
128
+ console.log(chalk.white(' • Open browser: ') + chalk.cyan(url));
129
+ console.log(chalk.white(' • View logs: ') + chalk.gray(`tail -f ${LOG_FILE}`));
130
+ console.log(chalk.white(' • Stop server: ') + chalk.yellow('aicos stop'));
131
+ console.log('\n');
132
+ }
151
133
 
134
+ // (callback)
135
+ if (callback) callback();
152
136
  // 立即退出,返回控制台
153
- process.exit(0);
137
+ if (!noExit) process.exit(0);
138
+ return true;
154
139
  } catch (err) {
155
140
  spinner.fail(chalk.red('Failed to start server!'));
156
141
  console.log(chalk.yellow(`\nCheck logs: ${chalk.cyan(LOG_FILE)}\n`));
157
- process.exit(1);
142
+ if (!noExit) process.exit(1);
143
+ return false;
158
144
  }
159
145
  } else {
160
146
  spinner.fail(chalk.red('Failed to start server!'));
161
147
  console.log(chalk.yellow(`\nCheck logs: ${chalk.cyan(LOG_FILE)}\n`));
162
- process.exit(1);
148
+ if (!noExit) process.exit(1);
149
+ return false;
163
150
  }
164
151
  };
165
152
 
166
- module.exports = start();
153
+ // 导出辅助函数供其他模块使用
154
+ module.exports = start;
package/bin/stop.js CHANGED
@@ -4,23 +4,77 @@ const os = require('os');
4
4
  const chalk = require('chalk');
5
5
  const boxen = require('boxen');
6
6
  const ora = require('ora');
7
+ const { findPidByPort, killProcess, getProcessInfo } = require('./utils/port-utils');
8
+ const { getServerInfo } = require('./utils/get-server');
7
9
 
8
10
  const PID_FILE = path.join(os.homedir(), '.aicodeswitch', 'server.pid');
9
11
 
10
- const stop = () => {
12
+ const stop = async (options = {}) => {
13
+ const { callback, silent } = options;
14
+
11
15
  console.log('\n');
12
16
 
17
+ const { host, port } = getServerInfo();
18
+
13
19
  if (!fs.existsSync(PID_FILE)) {
14
- console.log(boxen(
15
- chalk.yellow.bold('⚠ Server is not running'),
16
- {
17
- padding: 1,
18
- margin: 1,
19
- borderStyle: 'round',
20
- borderColor: 'yellow'
20
+ if (!silent) {
21
+ // PID 文件不存在,尝试通过端口检测进程
22
+ console.log(boxen(
23
+ chalk.yellow('⚠ PID file not found, checking port...'),
24
+ {
25
+ padding: 1,
26
+ margin: 1,
27
+ borderStyle: 'round',
28
+ borderColor: 'yellow'
29
+ }
30
+ ));
31
+ }
32
+
33
+ const spinner = ora({
34
+ text: chalk.cyan(`Checking port ${port}...`),
35
+ color: 'cyan'
36
+ }).start();
37
+
38
+ const pid = await findPidByPort(port);
39
+
40
+ if (pid) {
41
+ spinner.text = chalk.cyan(`Found process on port ${port}, stopping...`);
42
+
43
+ const processInfo = await getProcessInfo(pid);
44
+ if (!silent) {
45
+ console.log('\n' + chalk.gray(`Process found: ${chalk.white(pid)} (${chalk.gray(processInfo)})`));
46
+ }
47
+
48
+ const killed = await killProcess(pid);
49
+
50
+ if (killed) {
51
+ spinner.succeed(chalk.green(`Process ${pid} terminated successfully`));
52
+ showStoppedMessage();
53
+ } else {
54
+ spinner.fail(chalk.red('Failed to terminate process'));
21
55
  }
22
- ));
23
- console.log('\n');
56
+ } else {
57
+ spinner.info(chalk.yellow(`No process found on port ${port}`));
58
+ if (!silent) {
59
+ console.log(boxen(
60
+ chalk.yellow.bold('⚠ Server is not running'),
61
+ {
62
+ padding: 1,
63
+ margin: 1,
64
+ borderStyle: 'round',
65
+ borderColor: 'yellow'
66
+ }
67
+ ));
68
+ }
69
+ }
70
+ if (!silent) {
71
+ console.log('\n');
72
+ }
73
+
74
+ if (callback) {
75
+ console.log(boxen(chalk.yellow.bold('⚠ Server is not running')));
76
+ callback();
77
+ }
24
78
  return;
25
79
  }
26
80
 
@@ -50,14 +104,20 @@ const stop = () => {
50
104
  process.kill(pid, 'SIGKILL');
51
105
  spinner.warn(chalk.yellow('Server forcefully stopped'));
52
106
  fs.unlinkSync(PID_FILE);
53
- showStoppedMessage();
107
+ if (!silent) {
108
+ showStoppedMessage();
109
+ }
110
+ callback && callback();
54
111
  }
55
112
  } catch (err) {
56
113
  // 进程已停止
57
114
  clearInterval(checkStopped);
58
115
  spinner.succeed(chalk.green('Server stopped successfully'));
59
116
  fs.unlinkSync(PID_FILE);
60
- showStoppedMessage();
117
+ if (!silent) {
118
+ showStoppedMessage();
119
+ }
120
+ callback && callback();
61
121
  }
62
122
  }, 200);
63
123
 
@@ -65,7 +125,10 @@ const stop = () => {
65
125
  // 进程不存在
66
126
  spinner.warn(chalk.yellow('Process not found'));
67
127
  fs.unlinkSync(PID_FILE);
68
- showStoppedMessage();
128
+ if (!silent) {
129
+ showStoppedMessage();
130
+ }
131
+ callback && callback();
69
132
  }
70
133
  } catch (err) {
71
134
  spinner.fail(chalk.red('Failed to stop server'));
@@ -87,4 +150,4 @@ const showStoppedMessage = () => {
87
150
  console.log(chalk.white('Use ') + chalk.cyan('aicos start') + chalk.white(' to start the server again.\n'));
88
151
  };
89
152
 
90
- module.exports = stop();
153
+ module.exports = stop;