panrouter 1.8.0 → 1.9.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 +7 -6
  2. package/package.json +1 -1
  3. package/tray-daemon.ps1 +76 -67
package/cli.mjs CHANGED
@@ -150,8 +150,8 @@ function startTray() {
150
150
 
151
151
  log("..", "正在以托盘模式启动 Pan Router...", "yellow");
152
152
 
153
- // cmd /c start /B → VBS 在新进程树中运行, 有 Window Station
154
- const child = spawn("cmd.exe", ["/c", "start", "/B", "wscript.exe", "//B", "//NoLogo", vbsPath], {
153
+ // 直接调用 wscript,去掉多余的 cmd.exe 嵌套层级,更稳定
154
+ const child = spawn("wscript.exe", ["//B", "//NoLogo", vbsPath], {
155
155
  cwd: __dirname,
156
156
  stdio: "ignore",
157
157
  windowsHide: true,
@@ -161,7 +161,7 @@ function startTray() {
161
161
  child.unref();
162
162
 
163
163
  log("OK", "Pan Router 托盘已启动", "green");
164
- console.log(" 图标在右下角, 右键可退出");
164
+ console.log(" 图标在右下角, 右键可进行管理");
165
165
  }
166
166
 
167
167
  // ─── 主流程 ──────────────────────────────────────────────────────────────
@@ -192,9 +192,10 @@ async function main() {
192
192
 
193
193
  \x1b[33m托盘模式:\x1b[0m
194
194
  在系统通知区(右下角)显示图标,右键菜单:
195
- - 开关 开机自启动
196
- - 退出 关闭服务器和托盘
197
- 左键点击图标查看运行状态
195
+ - 启动/重启服务
196
+ - 开关开机自启动
197
+ - 退出关闭服务器和托盘
198
+ 左键点击图标显示菜单
198
199
 
199
200
  \x1b[33m配置:\x1b[0m
200
201
  代理运行在 http://127.0.0.1:50816
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "panrouter",
3
- "version": "1.8.0",
3
+ "version": "1.9.0",
4
4
  "description": "让 Claude Code 免费使用 DeepSeek 等模型,无需 API Key",
5
5
  "type": "module",
6
6
  "bin": {
package/tray-daemon.ps1 CHANGED
@@ -1,45 +1,44 @@
1
1
  <#
2
2
  .SYNOPSIS
3
- Pan Router 托盘 (自包含: 启动服务器 + 托盘图标 + 菜单)
4
- 必须从 WScript.Shell.Run (VBS) 或正常控制台启动, 不能从 detached Node 进程启动
3
+ Pan Router 托盘守护脚本
4
+ 去除了导致 UI 假死的同步网络请求,增加了稳定的重试和容错机制。
5
5
  #>
6
6
 
7
+ Add-Type -AssemblyName System.Windows.Forms
8
+ Add-Type -AssemblyName System.Drawing
9
+
10
+ # 抑制全局报错弹窗,防止后台服务因为意外报错直接死掉
11
+ $ErrorActionPreference = "SilentlyContinue"
12
+
7
13
  $scriptPath = $MyInvocation.MyCommand.Path
8
14
  $scriptDir = Split-Path $scriptPath -Parent
9
15
  $serverPath = Join-Path $scriptDir "server.mjs"
10
- $logFile = "$env:TEMP\panrouter-tray.log"
11
- $now = Get-Date -Format "HH:mm:ss"
16
+ $nodePath = (Get-Command node -ErrorAction SilentlyContinue).Source
12
17
 
13
- "$now === PanRouter Tray START ===" | Out-File $logFile
14
- "$now PID=$pid" | Out-File -Append $logFile
15
- "$now serverPath=$serverPath" | Out-File -Append $logFile
18
+ # 如果系统环境没装 Node,直接静默退出
19
+ if (-not $nodePath) { Exit }
16
20
 
17
- # ─── 杀掉旧 server ──────────────────────────────
18
- try {
19
- $old = wmic process where "name='node.exe'" get ProcessId,CommandLine /format:csv 2>$null
20
- foreach ($line in $old) {
21
- if ($line -match '(\d+),.*?server\.mjs') {
22
- Stop-Process -Id $Matches[1] -Force -ErrorAction SilentlyContinue
23
- "$now Killed old server PID=$($Matches[1])" | Out-File -Append $logFile
21
+ # ─── 核心功能:杀旧启新 ──────────────────────────────
22
+ function Restart-Server {
23
+ # 强制清理旧的代理进程
24
+ 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
+ }
24
29
  }
25
- }
26
- } catch {}
30
+ } catch {}
27
31
 
28
- # ─── 启动 server.mjs (用 node 完整路径) ─────────
29
- try {
30
- $nodePath = (Get-Command node).Source
31
- "$now nodePath=$nodePath" | Out-File -Append $logFile
32
- $p = Start-Process -FilePath $nodePath -ArgumentList "`"$serverPath`"" -WorkingDirectory $scriptDir -WindowStyle Hidden -PassThru -NoNewWindow
33
- if ($p -and $p.Id) { "$now Server started PID=$($p.Id)" | Out-File -Append $logFile }
34
- } catch {
35
- "$now Server start FAILED: $_" | Out-File -Append $logFile
32
+ # 无黑框启动新进程
33
+ try {
34
+ Start-Process -FilePath $nodePath -ArgumentList "`"$serverPath`"" -WorkingDirectory $scriptDir -WindowStyle Hidden -PassThru -NoNewWindow | Out-Null
35
+ } catch {}
36
36
  }
37
37
 
38
- # ─── WinForms ────────────────────────────────────
39
- Add-Type -AssemblyName System.Windows.Forms
40
- Add-Type -AssemblyName System.Drawing
38
+ # 启动时立刻执行一次
39
+ Restart-Server
41
40
 
42
- # ─── 图标 ────────────────────────────────────────
41
+ # ─── 绘制托盘图标 ────────────────────────────────────────
43
42
  $bmp = New-Object System.Drawing.Bitmap(16, 16)
44
43
  $g = [System.Drawing.Graphics]::FromImage($bmp)
45
44
  $g.SmoothingMode = 'HighQuality'
@@ -49,76 +48,86 @@ $g.FillEllipse($brush, 0, 0, 15, 15)
49
48
  $font = New-Object System.Drawing.Font("Segoe UI", 8.5, [System.Drawing.FontStyle]::Bold)
50
49
  $fg = New-Object System.Drawing.SolidBrush([System.Drawing.Color]::White)
51
50
  $g.DrawString("P", $font, $fg, 3, 1.5)
52
- $font.Dispose(); $fg.Dispose(); $brush.Dispose(); $g.Dispose()
51
+
53
52
  $hIcon = $bmp.GetHicon()
54
53
  $icon = [System.Drawing.Icon]::FromHandle($hIcon)
55
- $bmp.Dispose()
56
54
 
57
- # ─── NotifyIcon ──────────────────────────────────
55
+ # ─── 初始化托盘对象 ──────────────────────────────────
58
56
  $notifyIcon = New-Object System.Windows.Forms.NotifyIcon
59
57
  $notifyIcon.Icon = $icon
60
- $notifyIcon.Text = "Pan Router | 端口 50816"
58
+ $notifyIcon.Text = "Pan Router (端口: 50816)"
61
59
 
62
- # ─── 菜单 ────────────────────────────────────────
63
60
  $menu = New-Object System.Windows.Forms.ContextMenuStrip
64
- [void]$menu.Items.Add((New-Object System.Windows.Forms.ToolStripMenuItem("Pan Router - :50816") { Enabled = $false; Font = New-Object System.Drawing.Font("Segoe UI", 9, [System.Drawing.FontStyle]::Bold) }))
65
- [void]$menu.Items.Add((New-Object System.Windows.Forms.ToolStripSeparator))
66
61
 
62
+ # 标题 (不可点)
63
+ $titleItem = New-Object System.Windows.Forms.ToolStripMenuItem("Pan Router | :50816")
64
+ $titleItem.Enabled = $false
65
+ $titleItem.Font = New-Object System.Drawing.Font("Segoe UI", 9, [System.Drawing.FontStyle]::Bold)
66
+ $menu.Items.Add($titleItem) | Out-Null
67
+ $menu.Items.Add((New-Object System.Windows.Forms.ToolStripSeparator)) | Out-Null
68
+
69
+ # 重启服务选项
70
+ $restartItem = New-Object System.Windows.Forms.ToolStripMenuItem("重启后台服务")
71
+ $restartItem.Add_Click({
72
+ Restart-Server
73
+ $notifyIcon.ShowBalloonTip(2000, "Pan Router", "后台代理服务已重新启动 ✓", [System.Windows.Forms.ToolTipIcon]::Info)
74
+ })
75
+ $menu.Items.Add($restartItem) | Out-Null
76
+
77
+ # 开机自启动选项
67
78
  $autoItem = New-Object System.Windows.Forms.ToolStripMenuItem("开机自启动")
68
- try { $autoItem.Checked = (Get-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run" -Name PanRouter -ErrorAction Stop).PanRouter -ne $null } catch {}
79
+ try {
80
+ $regKey = Get-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run" -Name PanRouter -ErrorAction SilentlyContinue
81
+ $autoItem.Checked = ($regKey -ne $null)
82
+ } catch {}
83
+
69
84
  $autoItem.Add_Click({
70
85
  if ($autoItem.Checked) {
71
86
  reg delete "HKCU\Software\Microsoft\Windows\CurrentVersion\Run" /v PanRouter /f 2>&1 | Out-Null
72
87
  $autoItem.Checked = $false
73
- $notifyIcon.ShowBalloonTip(2000, "Pan Router", "开机自启动已关闭", [System.Windows.Forms.ToolTipIcon]::Info)
74
88
  } else {
75
- reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Run" /v PanRouter /t REG_SZ /d "powershell -ExecutionPolicy Bypass -WindowStyle Hidden -STA -File `"$scriptPath`"" /f 2>&1 | Out-Null
89
+ $vbsPath = Join-Path $scriptDir "panrouter-tray.vbs"
90
+ $cmd = "wscript.exe //B //NoLogo `"$vbsPath`""
91
+ reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Run" /v PanRouter /t REG_SZ /d $cmd /f 2>&1 | Out-Null
76
92
  $autoItem.Checked = $true
77
- $notifyIcon.ShowBalloonTip(2000, "Pan Router", "开机自启动已开启 ✓", [System.Windows.Forms.ToolTipIcon]::Info)
78
93
  }
79
- }.GetNewClosure())
80
- [void]$menu.Items.Add($autoItem)
81
- [void]$menu.Items.Add((New-Object System.Windows.Forms.ToolStripSeparator))
94
+ })
95
+ $menu.Items.Add($autoItem) | Out-Null
96
+ $menu.Items.Add((New-Object System.Windows.Forms.ToolStripSeparator)) | Out-Null
82
97
 
98
+ # 退出选项
83
99
  $exitItem = New-Object System.Windows.Forms.ToolStripMenuItem("退出")
84
100
  $exitItem.Add_Click({
85
101
  $notifyIcon.Visible = $false
86
- try { $old = wmic process where "name='node.exe'" get ProcessId,CommandLine /format:csv 2>$null; foreach ($line in $old) { if ($line -match '(\d+),.*?server\.mjs') { Stop-Process -Id $Matches[1] -Force -ErrorAction SilentlyContinue } } } catch {}
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 {}
87
103
  [System.Windows.Forms.Application]::Exit()
88
- }.GetNewClosure())
89
- [void]$menu.Items.Add($exitItem)
104
+ })
105
+ $menu.Items.Add($exitItem) | Out-Null
90
106
 
91
107
  $notifyIcon.ContextMenuStrip = $menu
92
108
 
93
- # ─── 左键 ────────────────────────────────────────
109
+ # ─── 优化左键操作 ────────────────────────────────────
94
110
  $notifyIcon.Add_MouseClick({
95
111
  if ($_.Button -eq [System.Windows.Forms.MouseButtons]::Left) {
96
- try { $req = [System.Net.WebRequest]::Create("http://127.0.0.1:50816/health"); $req.Timeout = 1500; $resp = $req.GetResponse(); $resp.Close(); $notifyIcon.ShowBalloonTip(2000, "Pan Router", "运行正常 ✓ (端口 50816)", [System.Windows.Forms.ToolTipIcon]::Info) }
97
- catch { $notifyIcon.ShowBalloonTip(2000, "Pan Router", "服务器未响应 ⚠", [System.Windows.Forms.ToolTipIcon]::Error) }
112
+ # 利用反射直接呼出右键菜单,取消容易卡死的网络检查
113
+ $mi = [System.Windows.Forms.NotifyIcon].GetMethod("ShowContextMenu", [System.Reflection.BindingFlags]::NonPublic -bor [System.Reflection.BindingFlags]::Instance)
114
+ $mi.Invoke($notifyIcon, $null)
98
115
  }
99
116
  })
100
117
 
101
- # ─── 健康检查 ────────────────────────────────────
102
- $healthTimer = New-Object System.Windows.Forms.Timer
103
- $healthTimer.Interval = 30000
104
- $healthTimer.Add_Tick({
105
- try { $req = [System.Net.WebRequest]::Create("http://127.0.0.1:50816/health"); $req.Timeout = 1500; $resp = $req.GetResponse(); $resp.Close() }
106
- catch {
107
- try { $old = wmic process where "name='node.exe'" get ProcessId,CommandLine /format:csv 2>$null; foreach ($line in $old) { if ($line -match '(\d+),.*?server\.mjs') { Stop-Process -Id $Matches[1] -Force -ErrorAction SilentlyContinue } } } catch {}
108
- try { Start-Process -FilePath (Get-Command node).Source -ArgumentList "`"$serverPath`"" -WorkingDirectory $scriptDir -WindowStyle Hidden -NoNewWindow } catch {}
109
- }
110
- })
111
- $healthTimer.Start()
112
- "$now Health timer started" | Out-File -Append $logFile
113
-
114
- # ═══ 进入消息循环 ═══
118
+ # ─── 启动与保持运行 ──────────────────────────────────
115
119
  $notifyIcon.Visible = $true
116
- "$now Icon visible, entering message loop" | Out-File -Append $logFile
117
- [System.Windows.Forms.Application]::Run()
118
- "$now Exited message loop" | Out-File -Append $logFile
119
120
 
120
- # 清理
121
- $healthTimer.Stop()
121
+ # 使用 ApplicationContext 保持消息循环,防止脚本在执行完后意外释放托盘图标
122
+ $appContext = New-Object System.Windows.Forms.ApplicationContext
123
+ [System.Windows.Forms.Application]::Run($appContext)
124
+
125
+ # 清理资源
126
+ $notifyIcon.Visible = $false
122
127
  $notifyIcon.Dispose()
128
+ $font.Dispose()
129
+ $fg.Dispose()
130
+ $brush.Dispose()
131
+ $g.Dispose()
123
132
  [System.Runtime.InteropServices.Marshal]::DestroyIcon($hIcon)
124
133
  $icon.Dispose()