panrouter 1.9.0 → 2.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.
Files changed (3) hide show
  1. package/cli.mjs +52 -85
  2. package/package.json +1 -1
  3. package/tray-daemon.ps1 +71 -51
package/cli.mjs CHANGED
@@ -2,14 +2,6 @@
2
2
 
3
3
  /**
4
4
  * Pan Router CLI — 一键安装 + 启动
5
- *
6
- * 用法:
7
- * panrouter # 一键安装 + 前台启动
8
- * panrouter --install # 只安装配置
9
- * panrouter --server # 只启动代理(前台)
10
- * panrouter --tray # 以托盘模式启动(隐藏窗口)
11
- * panrouter --tray-install # 安装 + 配置 + 托盘启动
12
- * panrouter --help # 帮助
13
5
  */
14
6
 
15
7
  import { execSync, spawn } from "node:child_process";
@@ -48,7 +40,7 @@ function runSilent(cmd) {
48
40
  }
49
41
  }
50
42
 
51
- // ─── 1. 检查 / 安装 Claude Code ──────────────────────────────────────────
43
+ // ─── 1. 安装与配置 ──────────────────────────────────────────
52
44
 
53
45
  function installClaudeCode() {
54
46
  log("..", "正在检查 Claude Code...", "yellow");
@@ -56,7 +48,6 @@ function installClaudeCode() {
56
48
  log("OK", "Claude Code 已就绪", "green");
57
49
  return true;
58
50
  }
59
-
60
51
  log("..", "正在安装 Claude Code...", "yellow");
61
52
  if (!run("npm install -g @anthropic-ai/claude-code")) {
62
53
  log("!!", "Claude Code 安装失败", "red");
@@ -66,20 +57,10 @@ function installClaudeCode() {
66
57
  return true;
67
58
  }
68
59
 
69
- // ─── 2. 写入配置文件 ─────────────────────────────────────────────────────
70
-
71
60
  function writeConfig() {
72
61
  log("..", "正在写入配置...", "yellow");
73
-
74
- if (!fs.existsSync(CLAUDE_DIR)) {
75
- fs.mkdirSync(CLAUDE_DIR, { recursive: true });
76
- }
77
-
78
- // 备份
79
- if (fs.existsSync(SETTINGS_PATH)) {
80
- fs.copyFileSync(SETTINGS_PATH, BACKUP_PATH);
81
- log("OK", "原配置已备份", "green");
82
- }
62
+ if (!fs.existsSync(CLAUDE_DIR)) fs.mkdirSync(CLAUDE_DIR, { recursive: true });
63
+ if (fs.existsSync(SETTINGS_PATH)) fs.copyFileSync(SETTINGS_PATH, BACKUP_PATH);
83
64
 
84
65
  const config = {
85
66
  env: {
@@ -91,13 +72,10 @@ function writeConfig() {
91
72
  },
92
73
  hasCompletedOnboarding: true,
93
74
  };
94
-
95
75
  fs.writeFileSync(SETTINGS_PATH, JSON.stringify(config, null, 2), "utf-8");
96
- log("OK", "配置文件已写入 ~/.claude/settings.json", "green");
76
+ log("OK", "配置文件已写入", "green");
97
77
  }
98
78
 
99
- // ─── 3. 启动代理服务器(后台运行) ───────────────────────────────────────
100
-
101
79
  async function isPortOpen() {
102
80
  return new Promise(rs => {
103
81
  const req = http.get("http://127.0.0.1:50816/health", () => {});
@@ -107,10 +85,10 @@ async function isPortOpen() {
107
85
  });
108
86
  }
109
87
 
88
+ // ─── 2. 启动服务(前台黑框) ────────────────────────────────
89
+
110
90
  async function startServer() {
111
91
  const serverPath = path.join(__dirname, "server.mjs");
112
-
113
- // 关掉旧的 Pan Router
114
92
  try {
115
93
  if (process.platform === "win32") {
116
94
  execSync('taskkill /f /fi "WINDOWTITLE eq Pan Router*" >nul 2>&1', { stdio: "pipe" });
@@ -120,7 +98,6 @@ async function startServer() {
120
98
  } catch {}
121
99
 
122
100
  log("..", "正在启动 Pan Router(端口 50816)...", "yellow");
123
-
124
101
  if (process.platform === "win32") {
125
102
  execSync(`start "Pan Router" cmd /c "node ${serverPath} & pause"`, { stdio: "pipe" });
126
103
  } else {
@@ -128,79 +105,71 @@ async function startServer() {
128
105
  child.unref();
129
106
  }
130
107
 
131
- // 等待服务启动
132
108
  for (let i = 0; i < 15; i++) {
133
- if (await isPortOpen()) { break; }
109
+ if (await isPortOpen()) break;
134
110
  await new Promise(rs => setTimeout(rs, 1000));
135
111
  }
136
-
137
- log("OK", "Pan Router 已启动(端口 50816)", "green");
138
- console.log("\n 现在可以运行: \x1b[33mclaude \"你好\"\x1b[0m\n");
112
+ log("OK", "Pan Router 已启动!现在可以运行 claude 了", "green");
139
113
  }
140
114
 
141
- // ─── 4. 以托盘模式启动 ──────────────────────────────────────────────────
115
+ // ─── 3. 托盘模式(后台静默) ────────────────────────────────
142
116
 
143
- function startTray() {
117
+ async function startTray() {
118
+ const serverPath = path.join(__dirname, "server.mjs");
144
119
  const vbsPath = path.join(__dirname, "panrouter-tray.vbs");
145
120
 
146
- if (!fs.existsSync(vbsPath)) {
147
- log("!!", "未找到 panrouter-tray.vbs", "red");
148
- process.exit(1);
149
- }
150
-
151
- log("..", "正在以托盘模式启动 Pan Router...", "yellow");
121
+ log("..", "正在启动代理并加载托盘...", "yellow");
152
122
 
153
- // 直接调用 wscript,去掉多余的 cmd.exe 嵌套层级,更稳定
154
- const child = spawn("wscript.exe", ["//B", "//NoLogo", vbsPath], {
155
- cwd: __dirname,
156
- stdio: "ignore",
157
- windowsHide: true,
158
- detached: true,
159
- shell: false,
123
+ // 【核心修复1】:由 Node 主进程直接启动 Server,确保 API 百分百可用
124
+ const srv = spawn(process.execPath, [serverPath], {
125
+ cwd: __dirname,
126
+ stdio: "ignore",
127
+ detached: true,
128
+ windowsHide: true
160
129
  });
161
- child.unref();
130
+ srv.unref();
131
+
132
+ // 【核心修复2】:将 Node 的绝对路径通过环境变量塞给 VBS/PowerShell
133
+ process.env.PANROUTER_NODE_PATH = process.execPath;
134
+
135
+ // 启动托盘 UI
136
+ if (fs.existsSync(vbsPath)) {
137
+ const tray = spawn("wscript.exe", ["//B", "//NoLogo", vbsPath], {
138
+ cwd: __dirname,
139
+ stdio: "ignore",
140
+ windowsHide: true,
141
+ detached: true,
142
+ env: process.env // 传递环境变量
143
+ });
144
+ tray.unref();
145
+ }
162
146
 
163
- log("OK", "Pan Router 托盘已启动", "green");
164
- console.log(" 图标在右下角, 右键可进行管理");
147
+ // 验证端口
148
+ let ok = false;
149
+ for (let i = 0; i < 15; i++) {
150
+ if (await isPortOpen()) { ok = true; break; }
151
+ await new Promise(rs => setTimeout(rs, 1000));
152
+ }
153
+
154
+ if (ok) {
155
+ log("OK", "Pan Router 代理已在后台运行 (端口: 50816)!", "green");
156
+ console.log(" 托盘图标应该已出现。如果系统原因导致图标没出来,代理依然可用。");
157
+ } else {
158
+ log("!!", "代理启动超时,请检查端口 50816 是否被占用", "red");
159
+ }
165
160
  }
166
161
 
167
- // ─── 主流程 ──────────────────────────────────────────────────────────────
162
+ // ─── 主流程 ──────────────────────────────────────────────────
168
163
 
169
164
  function printBanner() {
170
- console.log(`
171
- \x1b[36m============================================\x1b[0m
172
- \x1b[36m Pan Router - Claude Code 一键安装\x1b[0m
173
- \x1b[36m 无需 API Key,开箱即用\x1b[0m
174
- \x1b[36m============================================\x1b[0m
175
- `);
165
+ console.log(`\n\x1b[36m=== Pan Router - Claude Code 代理 ===\x1b[0m\n`);
176
166
  }
177
167
 
178
168
  async function main() {
179
169
  const args = process.argv.slice(2);
180
170
 
181
171
  if (args.includes("--help") || args.includes("-h")) {
182
- console.log(`
183
- pan-router — Claude Code 免费 AI 路由代理
184
-
185
- \x1b[33m用法:\x1b[0m
186
- panrouter 一键安装 + 前台启动
187
- panrouter --install 只安装配置
188
- panrouter --server 只启动代理(前台窗口)
189
- panrouter --tray 以托盘模式启动(右下角隐藏图标)
190
- panrouter --tray-install 安装配置 + 托盘启动
191
- panrouter --help 显示帮助
192
-
193
- \x1b[33m托盘模式:\x1b[0m
194
- 在系统通知区(右下角)显示图标,右键菜单:
195
- - 启动/重启服务
196
- - 开关开机自启动
197
- - 退出关闭服务器和托盘
198
- 左键点击图标显示菜单
199
-
200
- \x1b[33m配置:\x1b[0m
201
- 代理运行在 http://127.0.0.1:50816
202
- Claude Code 自动使用,无需额外设置
203
- `);
172
+ console.log("用法:\n panrouter --server (带命令行窗口运行)\n panrouter --tray (后台隐藏运行 + 托盘)\n panrouter --tray-install (安装配置 + 托盘)");
204
173
  return;
205
174
  }
206
175
 
@@ -208,7 +177,6 @@ async function main() {
208
177
  printBanner();
209
178
  if (!installClaudeCode()) process.exit(1);
210
179
  writeConfig();
211
- console.log("\n ✓ 安装完成,运行 \x1b[33mpanrouter --server\x1b[0m 启动代理\n");
212
180
  return;
213
181
  }
214
182
 
@@ -218,7 +186,7 @@ async function main() {
218
186
  }
219
187
 
220
188
  if (args.includes("--tray") || args.includes("-t")) {
221
- startTray();
189
+ await startTray();
222
190
  return;
223
191
  }
224
192
 
@@ -226,11 +194,10 @@ async function main() {
226
194
  printBanner();
227
195
  if (!installClaudeCode()) process.exit(1);
228
196
  writeConfig();
229
- startTray();
197
+ await startTray();
230
198
  return;
231
199
  }
232
200
 
233
- // 默认:全流程
234
201
  printBanner();
235
202
  if (!installClaudeCode()) process.exit(1);
236
203
  writeConfig();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "panrouter",
3
- "version": "1.9.0",
3
+ "version": "2.0.0",
4
4
  "description": "让 Claude Code 免费使用 DeepSeek 等模型,无需 API Key",
5
5
  "type": "module",
6
6
  "bin": {
package/tray-daemon.ps1 CHANGED
@@ -1,72 +1,100 @@
1
1
  <#
2
2
  .SYNOPSIS
3
- Pan Router 托盘守护脚本
4
- 去除了导致 UI 假死的同步网络请求,增加了稳定的重试和容错机制。
3
+ Pan Router 托盘守护脚本 (终极稳固版)
5
4
  #>
6
5
 
7
- Add-Type -AssemblyName System.Windows.Forms
8
- Add-Type -AssemblyName System.Drawing
9
-
10
- # 抑制全局报错弹窗,防止后台服务因为意外报错直接死掉
11
6
  $ErrorActionPreference = "SilentlyContinue"
12
7
 
8
+ # 增加调试日志功能(记录在系统临时目录,遇到问题能查原因)
9
+ $logFile = "$env:TEMP\panrouter_tray_debug.log"
10
+ "--- $(Get-Date -Format 'HH:mm:ss') Tray Script Started ---" | Out-File $logFile -Append
11
+
12
+ try {
13
+ Add-Type -AssemblyName System.Windows.Forms
14
+ Add-Type -AssemblyName System.Drawing
15
+ } catch {
16
+ "Failed to load assemblies: $($_.Exception.Message)" | Out-File $logFile -Append
17
+ Exit
18
+ }
19
+
13
20
  $scriptPath = $MyInvocation.MyCommand.Path
14
21
  $scriptDir = Split-Path $scriptPath -Parent
15
22
  $serverPath = Join-Path $scriptDir "server.mjs"
16
- $nodePath = (Get-Command node -ErrorAction SilentlyContinue).Source
17
23
 
18
- # 如果系统环境没装 Node,直接静默退出
19
- if (-not $nodePath) { Exit }
24
+ # 接收从 Node CLI 传来的绝对路径,如果没有则降级使用 "node"
25
+ $nodePath = $env:PANROUTER_NODE_PATH
26
+ if ([string]::IsNullOrWhiteSpace($nodePath)) {
27
+ $nodePath = "node"
28
+ }
29
+ "Node Path identified as: $nodePath" | Out-File $logFile -Append
20
30
 
21
- # ─── 核心功能:杀旧启新 ──────────────────────────────
31
+ # ─── 定义重启服务的逻辑 ────────────────────────────────────
22
32
  function Restart-Server {
23
- # 强制清理旧的代理进程
33
+ "Executing Restart-Server..." | Out-File $logFile -Append
24
34
  try {
25
- wmic process where "name='node.exe'" get ProcessId,CommandLine /format:csv 2>$null | ForEach-Object {
26
- if ($_ -match '(\d+),.*?server\.mjs') {
27
- Stop-Process -Id $Matches[1] -Force -ErrorAction SilentlyContinue
28
- }
29
- }
35
+ # 使用更为稳健的 WMI 查询关闭进程
36
+ Get-WmiObject Win32_Process -Filter "Name = 'node.exe'" |
37
+ Where-Object { $_.CommandLine -match "server\.mjs" } |
38
+ ForEach-Object { Stop-Process -Id $_.ProcessId -Force }
30
39
  } catch {}
31
40
 
32
- # 无黑框启动新进程
33
41
  try {
34
- Start-Process -FilePath $nodePath -ArgumentList "`"$serverPath`"" -WorkingDirectory $scriptDir -WindowStyle Hidden -PassThru -NoNewWindow | Out-Null
35
- } catch {}
42
+ Start-Process -FilePath $nodePath -ArgumentList "`"$serverPath`"" -WorkingDirectory $scriptDir -WindowStyle Hidden
43
+ "Server process spawned successfully." | Out-File $logFile -Append
44
+ } catch {
45
+ "Server start error: $($_.Exception.Message)" | Out-File $logFile -Append
46
+ }
36
47
  }
37
48
 
38
- # 启动时立刻执行一次
39
- Restart-Server
49
+ # ─── 保证服务在运行(用于应对开机自启动的情况) ───────────
50
+ $isRunning = $false
51
+ try {
52
+ $procs = Get-WmiObject Win32_Process -Filter "Name = 'node.exe'"
53
+ foreach ($p in $procs) {
54
+ if ($p.CommandLine -match "server\.mjs") {
55
+ $isRunning = $true
56
+ break
57
+ }
58
+ }
59
+ } catch {}
40
60
 
41
- # ─── 绘制托盘图标 ────────────────────────────────────────
42
- $bmp = New-Object System.Drawing.Bitmap(16, 16)
43
- $g = [System.Drawing.Graphics]::FromImage($bmp)
44
- $g.SmoothingMode = 'HighQuality'
45
- $g.Clear([System.Drawing.Color]::Transparent)
46
- $brush = New-Object System.Drawing.SolidBrush([System.Drawing.Color]::FromArgb(0, 120, 215))
47
- $g.FillEllipse($brush, 0, 0, 15, 15)
48
- $font = New-Object System.Drawing.Font("Segoe UI", 8.5, [System.Drawing.FontStyle]::Bold)
49
- $fg = New-Object System.Drawing.SolidBrush([System.Drawing.Color]::White)
50
- $g.DrawString("P", $font, $fg, 3, 1.5)
61
+ if (-not $isRunning) {
62
+ "Server not running on load, starting..." | Out-File $logFile -Append
63
+ Restart-Server
64
+ }
51
65
 
52
- $hIcon = $bmp.GetHicon()
53
- $icon = [System.Drawing.Icon]::FromHandle($hIcon)
66
+ # ─── 绘制托盘图标 ──────────────────────────────────────────
67
+ try {
68
+ $bmp = New-Object System.Drawing.Bitmap(16, 16)
69
+ $g = [System.Drawing.Graphics]::FromImage($bmp)
70
+ $g.SmoothingMode = 'HighQuality'
71
+ $g.Clear([System.Drawing.Color]::Transparent)
72
+ $brush = New-Object System.Drawing.SolidBrush([System.Drawing.Color]::FromArgb(0, 120, 215))
73
+ $g.FillEllipse($brush, 0, 0, 15, 15)
74
+ $font = New-Object System.Drawing.Font("Segoe UI", 8, [System.Drawing.FontStyle]::Bold)
75
+ $fg = New-Object System.Drawing.SolidBrush([System.Drawing.Color]::White)
76
+ $g.DrawString("P", $font, $fg, 3, 2)
77
+
78
+ $hIcon = $bmp.GetHicon()
79
+ $icon = [System.Drawing.Icon]::FromHandle($hIcon)
80
+ } catch {
81
+ "Icon drawing error: $($_.Exception.Message)" | Out-File $logFile -Append
82
+ Exit
83
+ }
54
84
 
55
- # ─── 初始化托盘对象 ──────────────────────────────────
85
+ # ─── 初始化托盘菜单 ────────────────────────────────────────
56
86
  $notifyIcon = New-Object System.Windows.Forms.NotifyIcon
57
87
  $notifyIcon.Icon = $icon
58
88
  $notifyIcon.Text = "Pan Router (端口: 50816)"
59
89
 
60
90
  $menu = New-Object System.Windows.Forms.ContextMenuStrip
61
91
 
62
- # 标题 (不可点)
63
92
  $titleItem = New-Object System.Windows.Forms.ToolStripMenuItem("Pan Router | :50816")
64
93
  $titleItem.Enabled = $false
65
94
  $titleItem.Font = New-Object System.Drawing.Font("Segoe UI", 9, [System.Drawing.FontStyle]::Bold)
66
95
  $menu.Items.Add($titleItem) | Out-Null
67
96
  $menu.Items.Add((New-Object System.Windows.Forms.ToolStripSeparator)) | Out-Null
68
97
 
69
- # 重启服务选项
70
98
  $restartItem = New-Object System.Windows.Forms.ToolStripMenuItem("重启后台服务")
71
99
  $restartItem.Add_Click({
72
100
  Restart-Server
@@ -74,10 +102,9 @@ $restartItem.Add_Click({
74
102
  })
75
103
  $menu.Items.Add($restartItem) | Out-Null
76
104
 
77
- # 开机自启动选项
78
105
  $autoItem = New-Object System.Windows.Forms.ToolStripMenuItem("开机自启动")
79
106
  try {
80
- $regKey = Get-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run" -Name PanRouter -ErrorAction SilentlyContinue
107
+ $regKey = Get-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run" -Name PanRouter
81
108
  $autoItem.Checked = ($regKey -ne $null)
82
109
  } catch {}
83
110
 
@@ -95,39 +122,32 @@ $autoItem.Add_Click({
95
122
  $menu.Items.Add($autoItem) | Out-Null
96
123
  $menu.Items.Add((New-Object System.Windows.Forms.ToolStripSeparator)) | Out-Null
97
124
 
98
- # 退出选项
99
125
  $exitItem = New-Object System.Windows.Forms.ToolStripMenuItem("退出")
100
126
  $exitItem.Add_Click({
101
127
  $notifyIcon.Visible = $false
102
- try { wmic process where "name='node.exe'" get ProcessId,CommandLine /format:csv 2>$null | ForEach-Object { if ($_ -match '(\d+),.*?server\.mjs') { Stop-Process -Id $Matches[1] -Force -ErrorAction SilentlyContinue } } } catch {}
128
+ try {
129
+ Get-WmiObject Win32_Process -Filter "Name = 'node.exe'" | Where-Object { $_.CommandLine -match "server\.mjs" } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force }
130
+ } catch {}
103
131
  [System.Windows.Forms.Application]::Exit()
104
132
  })
105
133
  $menu.Items.Add($exitItem) | Out-Null
106
134
 
107
135
  $notifyIcon.ContextMenuStrip = $menu
108
136
 
109
- # ─── 优化左键操作 ────────────────────────────────────
110
137
  $notifyIcon.Add_MouseClick({
111
138
  if ($_.Button -eq [System.Windows.Forms.MouseButtons]::Left) {
112
- # 利用反射直接呼出右键菜单,取消容易卡死的网络检查
113
139
  $mi = [System.Windows.Forms.NotifyIcon].GetMethod("ShowContextMenu", [System.Reflection.BindingFlags]::NonPublic -bor [System.Reflection.BindingFlags]::Instance)
114
140
  $mi.Invoke($notifyIcon, $null)
115
141
  }
116
142
  })
117
143
 
118
- # ─── 启动与保持运行 ──────────────────────────────────
119
144
  $notifyIcon.Visible = $true
120
145
 
121
- # 使用 ApplicationContext 保持消息循环,防止脚本在执行完后意外释放托盘图标
146
+ "UI setup completed, entering ApplicationContext loop." | Out-File $logFile -Append
147
+
122
148
  $appContext = New-Object System.Windows.Forms.ApplicationContext
123
149
  [System.Windows.Forms.Application]::Run($appContext)
124
150
 
125
- # 清理资源
151
+ "Exiting..." | Out-File $logFile -Append
126
152
  $notifyIcon.Visible = $false
127
153
  $notifyIcon.Dispose()
128
- $font.Dispose()
129
- $fg.Dispose()
130
- $brush.Dispose()
131
- $g.Dispose()
132
- [System.Runtime.InteropServices.Marshal]::DestroyIcon($hIcon)
133
- $icon.Dispose()