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/CHANGELOG.md +4 -0
- package/bin/cli.js +7 -9
- package/bin/restart.js +7 -229
- package/bin/restore.js +1 -1
- package/bin/start.js +75 -87
- package/bin/stop.js +77 -14
- package/bin/ui.js +19 -134
- package/bin/update.js +1 -1
- package/bin/utils/get-server.js +58 -0
- package/bin/utils/port-utils.js +118 -0
- package/bin/version.js +1 -1
- package/dist/server/database.js +480 -129
- package/dist/server/main.js +164 -22
- package/dist/server/proxy-server.js +417 -120
- package/dist/server/transformers/claude-openai.js +86 -3
- package/dist/server/transformers/streaming.js +4 -1
- package/dist/server/utils.js +16 -0
- package/dist/ui/assets/index-BLqGemLn.js +423 -0
- package/dist/ui/assets/index-IVPeH7yC.css +1 -0
- package/dist/ui/index.html +2 -2
- package/dist/ui/migration.md +7 -0
- package/package.json +3 -2
- package/public/migration.md +7 -0
- package/dist/ui/assets/index-BdKga7KO.js +0 -391
- package/dist/ui/assets/index-Dat4drax.css +0 -1
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
|
|
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
|
-
|
|
142
|
-
|
|
143
|
-
const
|
|
29
|
+
// 使用 start 命令启动服务器(如果需要)
|
|
30
|
+
// 使用 silent 和 noExit 选项来控制行为
|
|
31
|
+
const started = await start({ silent: true, noExit: true });
|
|
144
32
|
|
|
145
|
-
if (!
|
|
146
|
-
console.log(chalk.
|
|
147
|
-
|
|
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
|
-
|
|
160
|
-
|
|
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
|
-
|
|
48
|
+
spinner.succeed(chalk.green('Browser opened successfully!'));
|
|
49
|
+
console.log(chalk.white(' URL: ') + chalk.cyan.bold(url) + '\n');
|
|
165
50
|
} catch (err) {
|
|
166
|
-
|
|
167
|
-
console.log(chalk.
|
|
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
|
@@ -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