panrouter 3.6.0 → 3.7.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/cli.mjs +171 -84
- package/package.json +1 -1
package/cli.mjs
CHANGED
|
@@ -10,6 +10,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
10
10
|
const HOME = process.env.USERPROFILE || process.env.HOME;
|
|
11
11
|
const CLAUDE_DIR = path.join(HOME, ".claude");
|
|
12
12
|
const SETTINGS_PATH = path.join(CLAUDE_DIR, "settings.json");
|
|
13
|
+
const BACKUP_PATH = path.join(CLAUDE_DIR, "settings.json.panrouter.backup");
|
|
13
14
|
|
|
14
15
|
function log(label, msg, color = "") {
|
|
15
16
|
const colors = { green: "\x1b[32m", yellow: "\x1b[33m", red: "\x1b[31m", cyan: "\x1b[36m", reset: "\x1b[0m" };
|
|
@@ -17,28 +18,13 @@ function log(label, msg, color = "") {
|
|
|
17
18
|
console.log(`${c}[${label}]${colors.reset} ${msg}`);
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
log("..", "正在检查 Claude Code...", "yellow");
|
|
22
|
-
try {
|
|
23
|
-
execSync("claude --version", { stdio: "pipe" });
|
|
24
|
-
log("OK", "Claude Code 已就绪", "green");
|
|
25
|
-
return true;
|
|
26
|
-
} catch {
|
|
27
|
-
log("..", "正在安装 Claude Code...", "yellow");
|
|
28
|
-
try {
|
|
29
|
-
execSync("npm install -g @anthropic-ai/claude-code", { stdio: "inherit" });
|
|
30
|
-
log("OK", "Claude Code 安装成功", "green");
|
|
31
|
-
return true;
|
|
32
|
-
} catch {
|
|
33
|
-
log("!!", "Claude Code 安装失败", "red");
|
|
34
|
-
return false;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
21
|
+
// ─── 配置 ────────────────────────────────────────────────
|
|
38
22
|
|
|
39
23
|
function writeConfig() {
|
|
40
24
|
log("..", "正在配置 Claude Code 路由...", "yellow");
|
|
41
25
|
if (!fs.existsSync(CLAUDE_DIR)) fs.mkdirSync(CLAUDE_DIR, { recursive: true });
|
|
26
|
+
if (fs.existsSync(SETTINGS_PATH)) fs.copyFileSync(SETTINGS_PATH, BACKUP_PATH);
|
|
27
|
+
|
|
42
28
|
const config = {
|
|
43
29
|
env: {
|
|
44
30
|
ANTHROPIC_BASE_URL: "http://127.0.0.1:50816",
|
|
@@ -53,6 +39,27 @@ function writeConfig() {
|
|
|
53
39
|
log("OK", "配置完成", "green");
|
|
54
40
|
}
|
|
55
41
|
|
|
42
|
+
// ─── 安装 Claude Code ────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
function ensureClaudeCode() {
|
|
45
|
+
try {
|
|
46
|
+
execSync("claude --version", { stdio: "pipe" });
|
|
47
|
+
return true; // 已装
|
|
48
|
+
} catch {
|
|
49
|
+
log("..", "正在安装 Claude Code...", "yellow");
|
|
50
|
+
try {
|
|
51
|
+
execSync("npm install -g @anthropic-ai/claude-code", { stdio: "inherit", timeout: 120000 });
|
|
52
|
+
log("OK", "Claude Code 安装成功", "green");
|
|
53
|
+
return true;
|
|
54
|
+
} catch {
|
|
55
|
+
log("!!", "Claude Code 安装失败", "red");
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ─── 端口检测 ────────────────────────────────────────────
|
|
62
|
+
|
|
56
63
|
async function isPortOpen() {
|
|
57
64
|
return new Promise(rs => {
|
|
58
65
|
const req = http.get("http://127.0.0.1:50816/health", () => {});
|
|
@@ -62,125 +69,205 @@ async function isPortOpen() {
|
|
|
62
69
|
});
|
|
63
70
|
}
|
|
64
71
|
|
|
72
|
+
// ─── 启动前台服务 ────────────────────────────────────────
|
|
73
|
+
|
|
65
74
|
async function startServer() {
|
|
66
75
|
const serverPath = path.join(__dirname, "server.mjs");
|
|
67
76
|
try {
|
|
68
|
-
|
|
69
|
-
execSync('taskkill /f /fi "WINDOWTITLE eq Pan Router*" >nul 2>&1', { stdio: "pipe" });
|
|
70
|
-
} else {
|
|
71
|
-
execSync("pkill -f 'node.*server.mjs' 2>/dev/null", { stdio: "pipe" });
|
|
72
|
-
}
|
|
77
|
+
execSync('taskkill /f /fi "WINDOWTITLE eq Pan Router*" >nul 2>&1', { stdio: "pipe" });
|
|
73
78
|
} catch {}
|
|
74
79
|
|
|
75
80
|
log("..", "正在启动代理...", "yellow");
|
|
76
|
-
|
|
77
|
-
execSync(`start "Pan Router" cmd /c "node ${serverPath} & pause"`, { stdio: "pipe" });
|
|
78
|
-
} else {
|
|
79
|
-
spawn("node", [serverPath], { cwd: __dirname, stdio: "ignore", detached: true }).unref();
|
|
80
|
-
}
|
|
81
|
+
execSync(`start "Pan Router" cmd /c "node ${serverPath} & pause"`, { stdio: "pipe" });
|
|
81
82
|
|
|
82
83
|
for (let i = 0; i < 15; i++) {
|
|
83
84
|
if (await isPortOpen()) break;
|
|
84
85
|
await new Promise(rs => setTimeout(rs, 1000));
|
|
85
86
|
}
|
|
86
|
-
log("OK", "Pan Router
|
|
87
|
+
log("OK", "Pan Router 运行中", "green");
|
|
87
88
|
}
|
|
88
89
|
|
|
90
|
+
// ─── 启动托盘(后台) ────────────────────────────────────
|
|
91
|
+
|
|
89
92
|
async function startTray() {
|
|
90
93
|
const serverPath = path.join(__dirname, "server.mjs");
|
|
91
94
|
const psPath = path.join(__dirname, "tray-daemon.ps1");
|
|
92
|
-
log("..", "正在后台启动代理...", "yellow");
|
|
93
95
|
|
|
94
|
-
//
|
|
96
|
+
// 给 PS 脚本加 BOM(中文系统兼容)
|
|
95
97
|
try {
|
|
96
|
-
const
|
|
97
|
-
if (
|
|
98
|
+
const content = fs.readFileSync(psPath, "utf8");
|
|
99
|
+
if (content.charCodeAt(0) !== 0xFEFF) {
|
|
98
100
|
const bom = Buffer.from([0xEF, 0xBB, 0xBF]);
|
|
99
|
-
fs.writeFileSync(psPath, Buffer.concat([bom, Buffer.from(
|
|
100
|
-
}
|
|
101
|
-
} catch (e) {
|
|
102
|
-
// 忽略权限问题
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
try {
|
|
106
|
-
if (process.platform === "win32") {
|
|
107
|
-
execSync('taskkill /f /fi "WINDOWTITLE eq Pan Router*" >nul 2>&1', { stdio: "pipe" });
|
|
101
|
+
fs.writeFileSync(psPath, Buffer.concat([bom, Buffer.from(content, "utf8")]));
|
|
108
102
|
}
|
|
109
103
|
} catch {}
|
|
110
104
|
|
|
105
|
+
// 清理旧进程
|
|
106
|
+
try { execSync('taskkill /f /fi "WINDOWTITLE eq Pan Router*" >nul 2>&1', { stdio: "pipe" }); } catch {}
|
|
107
|
+
|
|
108
|
+
// 启动 server(隐藏)
|
|
111
109
|
const srv = spawn(process.execPath, [serverPath], {
|
|
112
|
-
cwd: __dirname,
|
|
113
|
-
stdio: "ignore",
|
|
114
|
-
windowsHide: true,
|
|
115
|
-
detached: true
|
|
110
|
+
cwd: __dirname, stdio: "ignore", windowsHide: true, detached: true,
|
|
116
111
|
});
|
|
117
112
|
srv.unref();
|
|
118
113
|
|
|
114
|
+
// 启动托盘(隐藏)
|
|
115
|
+
const tray = spawn("powershell.exe", [
|
|
116
|
+
"-NoProfile", "-STA", "-ExecutionPolicy", "Bypass",
|
|
117
|
+
"-WindowStyle", "Hidden", "-File", `"${psPath}"`,
|
|
118
|
+
], {
|
|
119
|
+
cwd: __dirname, stdio: "ignore", windowsHide: true, shell: true,
|
|
120
|
+
env: { ...process.env, PANROUTER_NODE: process.execPath },
|
|
121
|
+
});
|
|
122
|
+
tray.unref();
|
|
123
|
+
|
|
124
|
+
// 验证端口
|
|
119
125
|
let ok = false;
|
|
120
126
|
for (let i = 0; i < 15; i++) {
|
|
121
|
-
if (await isPortOpen()) {
|
|
122
|
-
ok = true;
|
|
123
|
-
break;
|
|
124
|
-
}
|
|
127
|
+
if (await isPortOpen()) { ok = true; break; }
|
|
125
128
|
await new Promise(rs => setTimeout(rs, 1000));
|
|
126
129
|
}
|
|
127
130
|
|
|
128
131
|
if (ok) {
|
|
129
|
-
log("OK", "
|
|
132
|
+
log("OK", "Pan Router 已在后台运行(托盘在右下角)", "green");
|
|
130
133
|
} else {
|
|
131
|
-
log("!!", "
|
|
134
|
+
log("!!", "代理启动超时,请检查端口 50816", "red");
|
|
132
135
|
}
|
|
136
|
+
}
|
|
133
137
|
|
|
134
|
-
|
|
138
|
+
// ─── 停止 ────────────────────────────────────────────────
|
|
135
139
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
140
|
+
function stopAll() {
|
|
141
|
+
log("..", "正在停止...", "yellow");
|
|
142
|
+
try { execSync('taskkill /f /fi "WINDOWTITLE eq Pan Router*" >nul 2>&1', { stdio: "pipe" }); } catch {}
|
|
143
|
+
try { execSync("taskkill /f /im powershell.exe >nul 2>&1", { stdio: "pipe" }); } catch {}
|
|
144
|
+
// 杀 server.mjs 相关 node 进程
|
|
145
|
+
try {
|
|
146
|
+
const out = execSync(
|
|
147
|
+
'wmic process where "name=\'node.exe\'" get ProcessId,CommandLine /format:csv 2>nul',
|
|
148
|
+
{ encoding: "utf8", windowsHide: true, timeout: 3000 }
|
|
149
|
+
);
|
|
150
|
+
for (const line of out.split("\n")) {
|
|
151
|
+
if (line.includes("server.mjs")) {
|
|
152
|
+
const m = line.match(/(\d+),.*?server\.mjs/);
|
|
153
|
+
if (m) try { process.kill(parseInt(m[1]), "SIGKILL"); } catch {}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
} catch {}
|
|
157
|
+
log("OK", "已停止", "green");
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ─── 状态 ────────────────────────────────────────────────
|
|
149
161
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
162
|
+
async function showStatus() {
|
|
163
|
+
const online = await isPortOpen();
|
|
164
|
+
const version = "3.6.0";
|
|
165
|
+
console.log(`\n Pan Router v${version}`);
|
|
166
|
+
console.log(` 端口 50816: ${online ? "✓ 运行中" : "✗ 未启动"}`);
|
|
167
|
+
if (online) {
|
|
168
|
+
try {
|
|
169
|
+
const out = execSync(
|
|
170
|
+
'wmic process where "name=\'node.exe\'" get ProcessId,CommandLine /format:csv 2>nul',
|
|
171
|
+
{ encoding: "utf8", windowsHide: true, timeout: 3000 }
|
|
172
|
+
);
|
|
173
|
+
for (const line of out.split("\n")) {
|
|
174
|
+
if (line.includes("server.mjs")) {
|
|
175
|
+
const m = line.match(/(\d+),.*?server\.mjs/);
|
|
176
|
+
if (m) console.log(` PID: ${m[1]}`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
} catch {}
|
|
180
|
+
}
|
|
181
|
+
console.log("");
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ─── 版本 ────────────────────────────────────────────────
|
|
185
|
+
|
|
186
|
+
function showVersion() {
|
|
187
|
+
console.log("3.6.0");
|
|
188
|
+
}
|
|
153
189
|
|
|
154
|
-
|
|
190
|
+
// ─── 日志 ────────────────────────────────────────────────
|
|
155
191
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
192
|
+
function openLogs() {
|
|
193
|
+
const logPath = path.join(process.env.TEMP || "/tmp", "panrouter_tray.log");
|
|
194
|
+
if (fs.existsSync(logPath)) {
|
|
195
|
+
execSync(`notepad "${logPath}"`, { stdio: "inherit" });
|
|
159
196
|
} else {
|
|
160
|
-
|
|
161
|
-
console.log(" 托盘图标应该已在右下角显示。");
|
|
197
|
+
log("!!", "日志文件不存在", "red");
|
|
162
198
|
}
|
|
163
199
|
}
|
|
164
200
|
|
|
201
|
+
// ─── 主流程 ──────────────────────────────────────────────
|
|
202
|
+
|
|
165
203
|
async function main() {
|
|
166
204
|
const args = process.argv.slice(2);
|
|
205
|
+
|
|
206
|
+
// --help
|
|
167
207
|
if (args.includes("--help") || args.includes("-h")) {
|
|
168
|
-
console.log(
|
|
208
|
+
console.log(`
|
|
209
|
+
Pan Router — Claude Code 免费路由代理
|
|
210
|
+
|
|
211
|
+
用法:
|
|
212
|
+
panrouter 安装/配置 → 启动托盘(最常用)
|
|
213
|
+
panrouter --setup 只配置 Claude Code 路由
|
|
214
|
+
panrouter --server 前台窗口模式启动
|
|
215
|
+
panrouter --stop 停止所有
|
|
216
|
+
panrouter --status 查看运行状态
|
|
217
|
+
panrouter --logs 查看日志
|
|
218
|
+
panrouter --version 版本号
|
|
219
|
+
panrouter --help 帮助
|
|
220
|
+
`);
|
|
169
221
|
return;
|
|
170
222
|
}
|
|
171
223
|
|
|
172
|
-
|
|
173
|
-
if (args.includes("--
|
|
224
|
+
// --version
|
|
225
|
+
if (args.includes("--version") || args.includes("-v")) {
|
|
226
|
+
showVersion();
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
174
229
|
|
|
175
|
-
|
|
176
|
-
if (
|
|
177
|
-
|
|
230
|
+
// --status
|
|
231
|
+
if (args.includes("--status")) {
|
|
232
|
+
await showStatus();
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
178
235
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
236
|
+
// --logs
|
|
237
|
+
if (args.includes("--logs")) {
|
|
238
|
+
openLogs();
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// --stop
|
|
243
|
+
if (args.includes("--stop")) {
|
|
244
|
+
stopAll();
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// --setup(只配置)
|
|
249
|
+
if (args.includes("--setup")) {
|
|
250
|
+
writeConfig();
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// --server(前台窗口)
|
|
255
|
+
if (args.includes("--server") || args.includes("-s")) {
|
|
182
256
|
await startServer();
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// --tray(显式指定,兼容旧指令)
|
|
261
|
+
if (args.includes("--tray") || args.includes("-t")) {
|
|
262
|
+
writeConfig();
|
|
263
|
+
await startTray();
|
|
264
|
+
return;
|
|
183
265
|
}
|
|
266
|
+
|
|
267
|
+
// 默认:安装 + 配置 + 托盘
|
|
268
|
+
if (!ensureClaudeCode()) process.exit(1);
|
|
269
|
+
writeConfig();
|
|
270
|
+
await startTray();
|
|
184
271
|
}
|
|
185
272
|
|
|
186
273
|
main();
|