aicodeswitch 1.10.1 → 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/bin/ui.js CHANGED
@@ -1,128 +1,16 @@
1
1
  const { spawn } = require('child_process');
2
- const path = require('path');
3
- const fs = require('fs');
4
2
  const os = require('os');
5
3
  const chalk = require('chalk');
6
4
  const ora = require('ora');
7
-
8
- const PID_FILE = path.join(os.homedir(), '.aicodeswitch', 'server.pid');
9
- const LOG_FILE = path.join(os.homedir(), '.aicodeswitch', 'server.log');
10
-
11
- // 确保目录存在
12
- const ensureDir = (filePath) => {
13
- const dir = path.dirname(filePath);
14
- if (!fs.existsSync(dir)) {
15
- fs.mkdirSync(dir, { recursive: true });
16
- }
17
- };
18
-
19
- const isServerRunning = () => {
20
- if (!fs.existsSync(PID_FILE)) {
21
- return false;
22
- }
23
-
24
- try {
25
- const pid = parseInt(fs.readFileSync(PID_FILE, 'utf-8'), 10);
26
- // 检查进程是否存在
27
- process.kill(pid, 0);
28
- return true;
29
- } catch (err) {
30
- // 进程不存在,删除过期的 PID 文件
31
- fs.unlinkSync(PID_FILE);
32
- return false;
33
- }
34
- };
35
-
36
- const getServerInfo = () => {
37
- // 尝试多个可能的配置文件位置
38
- const possiblePaths = [
39
- path.join(os.homedir(), '.aicodeswitch', '.env'),
40
- path.join(os.homedir(), '.aicodeswitch', 'aicodeswitch.conf')
41
- ];
42
-
43
- let host = '127.0.0.1';
44
- let port = 4567;
45
-
46
- for (const dotenvPath of possiblePaths) {
47
- if (fs.existsSync(dotenvPath)) {
48
- const content = fs.readFileSync(dotenvPath, 'utf-8');
49
- const hostMatch = content.match(/HOST=(.+)/);
50
- const portMatch = content.match(/PORT=(.+)/);
51
-
52
- if (hostMatch) host = hostMatch[1].trim();
53
- if (portMatch) port = parseInt(portMatch[1].trim(), 10);
54
- break;
55
- }
56
- }
57
-
58
- return { host, port };
59
- };
60
-
61
- const startServer = async () => {
62
- const spinner = ora({
63
- text: chalk.cyan('Starting AI Code Switch server...'),
64
- color: 'cyan'
65
- }).start();
66
-
67
- ensureDir(PID_FILE);
68
- ensureDir(LOG_FILE);
69
-
70
- // 找到 main.js 的路径
71
- const serverPath = path.join(__dirname, '..', 'dist', 'server', 'main.js');
72
-
73
- if (!fs.existsSync(serverPath)) {
74
- spinner.fail(chalk.red('Server file not found!'));
75
- console.log(chalk.yellow(`\nPlease run ${chalk.cyan('npm run build')} first.\n`));
76
- process.exit(1);
77
- }
78
-
79
- // 启动服务器进程 - 完全分离
80
- // 打开日志文件用于输出
81
- const logFd = fs.openSync(LOG_FILE, 'a');
82
-
83
- const serverProcess = spawn('node', [serverPath], {
84
- detached: true,
85
- stdio: ['ignore', logFd, logFd] // 使用文件描述符
86
- });
87
-
88
- // 关闭文件描述符(子进程会保持打开)
89
- fs.closeSync(logFd);
90
-
91
- // 保存 PID
92
- fs.writeFileSync(PID_FILE, serverProcess.pid.toString());
93
-
94
- // 分离进程,让父进程可以退出
95
- serverProcess.unref();
96
-
97
- // 等待服务器启动
98
- await new Promise(resolve => setTimeout(resolve, 2000));
99
-
100
- // 检查服务器是否成功启动
101
- if (fs.existsSync(PID_FILE)) {
102
- try {
103
- const pid = parseInt(fs.readFileSync(PID_FILE, 'utf-8'), 10);
104
- process.kill(pid, 0);
105
- spinner.succeed(chalk.green('Server started successfully!'));
106
- return true;
107
- } catch (err) {
108
- spinner.fail(chalk.red('Failed to start server!'));
109
- console.log(chalk.yellow(`\nCheck logs: ${chalk.cyan(LOG_FILE)}\n`));
110
- return false;
111
- }
112
- } else {
113
- spinner.fail(chalk.red('Failed to start server!'));
114
- console.log(chalk.yellow(`\nCheck logs: ${chalk.cyan(LOG_FILE)}\n`));
115
- return false;
116
- }
117
- };
5
+ const start = require('./start');
6
+ const { getServerInfo } = require('./utils/get-server');
118
7
 
119
8
  const openBrowser = (url) => {
120
- const platform = os.platform();
121
9
  let command;
122
10
 
123
- if (platform === 'darwin') {
11
+ if (os.platform() === 'darwin') {
124
12
  command = 'open';
125
- } else if (platform === 'win32') {
13
+ } else if (os.platform() === 'win32') {
126
14
  command = 'start';
127
15
  } else {
128
16
  // Linux and others
@@ -138,36 +26,33 @@ const openBrowser = (url) => {
138
26
  };
139
27
 
140
28
  const openUI = async () => {
141
- console.log('\n');
142
-
143
- const running = isServerRunning();
29
+ // 使用 start 命令启动服务器(如果需要)
30
+ // 使用 silent 和 noExit 选项来控制行为
31
+ const started = await start({ silent: true, noExit: true });
144
32
 
145
- if (!running) {
146
- console.log(chalk.yellow(' Server is not running, starting server first...\n'));
147
- const started = await startServer();
148
- if (!started) {
149
- console.log(chalk.red('\n✗ Failed to start server, cannot open UI\n'));
150
- process.exit(1);
151
- }
152
- } else {
153
- console.log(chalk.green('✓ Server is already running\n'));
33
+ if (!started) {
34
+ console.log(chalk.red('\n✗ Failed to start server, cannot open UI\n'));
35
+ process.exit(1);
154
36
  }
155
37
 
156
38
  const { host, port } = getServerInfo();
157
39
  const url = `http://${host}:${port}`;
158
40
 
159
- console.log(chalk.cyan('🌐 Opening browser...'));
160
- console.log(chalk.white(' URL: ') + chalk.cyan.bold(url) + '\n');
41
+ const spinner = ora({
42
+ text: chalk.cyan('Opening browser...'),
43
+ color: 'cyan'
44
+ }).start();
161
45
 
162
46
  try {
163
47
  openBrowser(url);
164
- console.log(chalk.green('Browser opened successfully!\n'));
48
+ spinner.succeed(chalk.green('Browser opened successfully!'));
49
+ console.log(chalk.white(' URL: ') + chalk.cyan.bold(url) + '\n');
165
50
  } catch (err) {
166
- console.log(chalk.yellow('Failed to open browser automatically'));
167
- console.log(chalk.white(' Please open this URL manually: ') + chalk.cyan.bold(url) + '\n');
51
+ spinner.fail(chalk.red('Failed to open browser automatically'));
52
+ console.log(chalk.yellow('Please open this URL manually: ') + chalk.cyan.bold(url) + '\n');
168
53
  }
169
54
 
170
55
  process.exit(0);
171
56
  };
172
57
 
173
- module.exports = openUI();
58
+ module.exports = openUI;
package/bin/update.js CHANGED
@@ -280,4 +280,4 @@ const update = async () => {
280
280
  console.log('\n');
281
281
  };
282
282
 
283
- module.exports = update();
283
+ module.exports = update;
@@ -0,0 +1,58 @@
1
+ const path = require('path');
2
+ const fs = require('fs');
3
+ const os = require('os');
4
+
5
+ const PID_FILE = path.join(os.homedir(), '.aicodeswitch', 'server.pid');
6
+
7
+ /**
8
+ * 判断服务器是否正在运行
9
+ * @returns
10
+ */
11
+ const isServerRunning = () => {
12
+ if (!fs.existsSync(PID_FILE)) {
13
+ return false;
14
+ }
15
+
16
+ try {
17
+ const pid = parseInt(fs.readFileSync(PID_FILE, 'utf-8'), 10);
18
+ // 检查进程是否存在
19
+ process.kill(pid, 0);
20
+ return true;
21
+ } catch (err) {
22
+ // 进程不存在,删除过期的 PID 文件
23
+ fs.unlinkSync(PID_FILE);
24
+ return false;
25
+ }
26
+ };
27
+
28
+ /**
29
+ * 获取服务器信息
30
+ * @returns
31
+ */
32
+ const getServerInfo = () => {
33
+ // 尝试多个可能的配置文件位置
34
+ const possiblePaths = [
35
+ path.join(os.homedir(), '.aicodeswitch', '.env'),
36
+ path.join(os.homedir(), '.aicodeswitch', 'aicodeswitch.conf')
37
+ ];
38
+
39
+ let host = '127.0.0.1';
40
+ let port = 4567;
41
+
42
+ for (const dotenvPath of possiblePaths) {
43
+ if (fs.existsSync(dotenvPath)) {
44
+ const content = fs.readFileSync(dotenvPath, 'utf-8');
45
+ const hostMatch = content.match(/HOST=(.+)/);
46
+ const portMatch = content.match(/PORT=(.+)/);
47
+
48
+ if (hostMatch) host = hostMatch[1].trim();
49
+ if (portMatch) port = parseInt(portMatch[1].trim(), 10);
50
+ break;
51
+ }
52
+ }
53
+
54
+ return { host, port };
55
+ };
56
+
57
+ module.exports.isServerRunning = isServerRunning;
58
+ module.exports.getServerInfo = getServerInfo;
@@ -0,0 +1,118 @@
1
+ const { exec } = require('child_process');
2
+ const util = require('util');
3
+ const execPromise = util.promisify(exec);
4
+
5
+ /**
6
+ * 获取占用指定端口的进程 PID
7
+ * @param {number} port - 端口号
8
+ * @returns {Promise<number|null>} 返回 PID 或 null
9
+ */
10
+ const findPidByPort = async (port) => {
11
+ const platform = process.platform;
12
+
13
+ try {
14
+ if (platform === 'win32') {
15
+ // Windows: 使用 netstat
16
+ const { stdout } = await execPromise(`netstat -ano | findstr :${port}`);
17
+ const lines = stdout.split('\n').filter(line => line.trim());
18
+
19
+ for (const line of lines) {
20
+ const parts = line.trim().split(/\s+/);
21
+ // 格式: 协议 本地地址 外部地址 状态 PID
22
+ if (parts.length >= 5) {
23
+ const localAddress = parts[1];
24
+ if (localAddress.includes(`:${port}`)) {
25
+ const pid = parseInt(parts[parts.length - 1], 10);
26
+ if (!isNaN(pid)) {
27
+ return pid;
28
+ }
29
+ }
30
+ }
31
+ }
32
+ } else {
33
+ // macOS 和 Linux: 使用 lsof
34
+ const { stdout } = await execPromise(`lsof -ti :${port}`);
35
+ const pidStr = stdout.trim();
36
+ if (pidStr) {
37
+ // lsof -ti 可能返回多个 PID,取第一个
38
+ const pids = pidStr.split('\n').filter(p => p);
39
+ if (pids.length > 0) {
40
+ return parseInt(pids[0], 10);
41
+ }
42
+ }
43
+ }
44
+ } catch (err) {
45
+ // 命令执行失败,说明端口未被占用
46
+ return null;
47
+ }
48
+
49
+ return null;
50
+ };
51
+
52
+ /**
53
+ * 终止指定 PID 的进程
54
+ * @param {number} pid - 进程 PID
55
+ * @returns {Promise<boolean>} 是否成功终止
56
+ */
57
+ const killProcess = async (pid) => {
58
+ const platform = process.platform;
59
+
60
+ try {
61
+ if (platform === 'win32') {
62
+ // Windows: 使用 taskkill
63
+ await execPromise(`taskkill /F /PID ${pid}`);
64
+ } else {
65
+ // macOS 和 Linux: 使用 kill
66
+ process.kill(pid, 'SIGTERM');
67
+
68
+ // 等待进程停止
69
+ let attempts = 0;
70
+ const maxAttempts = 10;
71
+
72
+ while (attempts < maxAttempts) {
73
+ try {
74
+ process.kill(pid, 0);
75
+ await new Promise(resolve => setTimeout(resolve, 200));
76
+ attempts++;
77
+ } catch (err) {
78
+ // 进程已停止
79
+ return true;
80
+ }
81
+ }
82
+
83
+ // 强制终止
84
+ process.kill(pid, 'SIGKILL');
85
+ }
86
+ return true;
87
+ } catch (err) {
88
+ // 进程可能已经不存在
89
+ return false;
90
+ }
91
+ };
92
+
93
+ /**
94
+ * 获取进程信息(用于显示)
95
+ * @param {number} pid - 进程 PID
96
+ * @returns {Promise<string>} 进程信息
97
+ */
98
+ const getProcessInfo = async (pid) => {
99
+ const platform = process.platform;
100
+
101
+ try {
102
+ if (platform === 'win32') {
103
+ const { stdout } = await execPromise(`tasklist /FI "PID eq ${pid}" /FO CSV /NH`);
104
+ return stdout.trim();
105
+ } else {
106
+ const { stdout } = await execPromise(`ps -p ${pid} -o comm=`);
107
+ return stdout.trim();
108
+ }
109
+ } catch (err) {
110
+ return 'Unknown';
111
+ }
112
+ };
113
+
114
+ module.exports = {
115
+ findPidByPort,
116
+ killProcess,
117
+ getProcessInfo
118
+ };
package/bin/version.js CHANGED
@@ -44,4 +44,4 @@ const version = () => {
44
44
  process.exit(0);
45
45
  };
46
46
 
47
- module.exports = version();
47
+ module.exports = version;