panrouter 1.5.2 → 1.6.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/cli.mjs CHANGED
@@ -141,29 +141,28 @@ async function startServer() {
141
141
  // ─── 4. 以托盘模式启动 ──────────────────────────────────────────────────
142
142
 
143
143
  /**
144
- * VBS 隐藏启动 server + PS tray (Windows 原生, 最可靠)
144
+ * 启动 daemon (Node 常驻进程, 管理 server + PS tray)
145
145
  *
146
- * panrouter --tray
147
- * └─ wscript.exe panrouter-tray.vbs (WScript.Shell.Run, 隐藏)
148
- * ├─ node server.mjs (run 0 = 完全隐藏)
149
- * └─ powershell tray-daemon.ps1 (run 0 = 完全隐藏)
150
- * └─ NotifyIcon ✓
146
+ * cli.mjs ─→ node daemon.mjs (detached, hidden)
147
+ * ├─ 启动 server.mjs (子进程, detached, hidden)
148
+ * └─ 启动 powershell tray-daemon.ps1 (子进程, hidden)
149
+ * └─ 保持存活, 30s 健康检查
151
150
  *
152
- * WScript.Shell.Run 0 Windows 最可靠的隐藏启动方式,
153
- * 不依赖 spawndetached/Window Station 行为。
151
+ * key: process.execPath (绝对路径) 启动
152
+ * 避免 VBS/WScript/CMDPATH 查找问题
154
153
  */
155
154
  function startTray() {
156
- const vbsPath = path.join(__dirname, "panrouter-tray.vbs");
155
+ const daemonPath = path.join(__dirname, "daemon.mjs");
157
156
 
158
- if (!fs.existsSync(vbsPath)) {
159
- log("!!", "未找到 panrouter-tray.vbs", "red");
157
+ if (!fs.existsSync(daemonPath)) {
158
+ log("!!", "未找到 daemon.mjs", "red");
160
159
  process.exit(1);
161
160
  }
162
161
 
163
162
  log("..", "正在以托盘模式启动 Pan Router...", "yellow");
164
163
 
165
- // wscript //B = 批处理模式 (无窗口, 无交互)
166
- const child = spawn("wscript.exe", ["//B", "//NoLogo", vbsPath], {
164
+ const child = spawn(process.execPath, [daemonPath], {
165
+ cwd: __dirname,
167
166
  stdio: "ignore",
168
167
  windowsHide: true,
169
168
  detached: true,
@@ -172,7 +171,7 @@ function startTray() {
172
171
  child.unref();
173
172
 
174
173
  log("OK", "Pan Router 托盘已启动", "green");
175
- console.log(" 图标在任务栏右下角, 右键菜单可退出");
174
+ console.log(" 图标出现在右下角后, 即可运行 claude");
176
175
  }
177
176
 
178
177
  // ─── 主流程 ──────────────────────────────────────────────────────────────
package/daemon.mjs ADDED
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Pan Router Daemon (v4)
5
+ *
6
+ * 一个 Node 进程同时:
7
+ * 1. 隐藏启动 server.mjs(子进程)
8
+ * 2. 隐藏启动 tray-daemon.ps1(子进程)
9
+ * 3. 保持存活,守护两者
10
+ *
11
+ * 由 cli.mjs --tray 启动 (detached, 无窗口)。
12
+ */
13
+
14
+ import { spawn, execSync } from "node:child_process";
15
+ import { fileURLToPath } from "node:url";
16
+ import path from "node:path";
17
+ import fs from "node:fs";
18
+ import http from "node:http";
19
+
20
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
21
+ const serverPath = path.join(__dirname, "server.mjs");
22
+ const trayPsPath = path.join(__dirname, "tray-daemon.ps1");
23
+ const nodeExe = process.execPath; // ← 绝对路径!不依赖 PATH
24
+ const logPath = path.join(process.env.TEMP || "/tmp", "panrouter-daemon.log");
25
+
26
+ function log(msg) {
27
+ try { fs.appendFileSync(logPath, `${new Date().toISOString().slice(11,19)} ${msg}\n`); } catch {}
28
+ }
29
+
30
+ log("=== Daemon v4 ===");
31
+ log(`nodeExe=${nodeExe}`);
32
+ log(`serverPath=${serverPath}`);
33
+
34
+ // ─── 1. 杀旧 server ──────────────────────────────
35
+ try {
36
+ const out = execSync(
37
+ 'wmic process where "name=\'node.exe\'" get ProcessId,CommandLine /format:csv 2>nul',
38
+ { encoding: "utf8", windowsHide: true, timeout: 5000 }
39
+ );
40
+ for (const line of out.split("\n")) {
41
+ if (line.includes("server.mjs")) {
42
+ const m = line.match(/(\d+),.*?server\.mjs/);
43
+ if (m) { try { process.kill(parseInt(m[1]), "SIGKILL"); log(`Killed old server PID=${m[1]}`); } catch {} }
44
+ }
45
+ }
46
+ } catch {}
47
+
48
+ // ─── 2. 启动 server.mjs ──────────────────────────
49
+ // 关键: 用 process.execPath (绝对路径), 不用 "node" (可能找不到 PATH)
50
+ const server = spawn(nodeExe, [serverPath], {
51
+ cwd: __dirname,
52
+ stdio: "ignore",
53
+ windowsHide: true,
54
+ detached: true,
55
+ shell: false,
56
+ });
57
+ server.unref();
58
+ log(`Server spawned PID=${server.pid || "??"}`);
59
+
60
+ // ─── 3. 等就绪 ───────────────────────────────────
61
+ function isOnline() {
62
+ return new Promise(rs => {
63
+ try {
64
+ const req = http.get("http://127.0.0.1:50816/health", () => {});
65
+ req.on("response", () => { req.destroy(); rs(true); });
66
+ req.on("error", () => rs(false));
67
+ req.setTimeout(1500, () => { req.destroy(); rs(false); });
68
+ } catch { rs(false); }
69
+ });
70
+ }
71
+
72
+ (async () => {
73
+ let ready = false;
74
+ for (let i = 0; i < 20; i++) {
75
+ if (await isOnline()) { ready = true; break; }
76
+ await new Promise(r => setTimeout(r, 500));
77
+ }
78
+ log(`Server online=${ready}`);
79
+
80
+ // ─── 4. 启动 PS 托盘 ────────────────────────────
81
+ if (fs.existsSync(trayPsPath)) {
82
+ log("Starting PS tray...");
83
+ const ps = spawn("powershell.exe", [
84
+ "-NoProfile", "-ExecutionPolicy", "Bypass",
85
+ "-WindowStyle", "Hidden",
86
+ "-File", trayPsPath,
87
+ ], {
88
+ stdio: "ignore",
89
+ windowsHide: true,
90
+ shell: false,
91
+ });
92
+ ps.unref();
93
+ log(`PS spawned PID=${ps.pid || "??"}`);
94
+ } else {
95
+ log(`WARN: tray-daemon.ps1 not found at ${trayPsPath}`);
96
+ }
97
+
98
+ // ─── 5. 保持存活 ────────────────────────────────
99
+ log("Daemon running, keeping session alive");
100
+ process.stdin.resume();
101
+
102
+ // 定时检查 server, 挂了就重启
103
+ setInterval(() => {
104
+ isOnline().then(ok => {
105
+ if (!ok) {
106
+ log("Server offline, restarting...");
107
+ const s2 = spawn(nodeExe, [serverPath], {
108
+ cwd: __dirname, stdio: "ignore", windowsHide: true, detached: true, shell: false,
109
+ });
110
+ s2.unref();
111
+ log(`Server restarted PID=${s2.pid || "??"}`);
112
+ }
113
+ });
114
+ }, 30000);
115
+ })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "panrouter",
3
- "version": "1.5.2",
3
+ "version": "1.6.1",
4
4
  "description": "让 Claude Code 免费使用 DeepSeek 等模型,无需 API Key",
5
5
  "type": "module",
6
6
  "bin": {
@@ -8,9 +8,9 @@
8
8
  },
9
9
  "files": [
10
10
  "cli.mjs",
11
+ "daemon.mjs",
11
12
  "server.mjs",
12
- "tray-daemon.ps1",
13
- "panrouter-tray.vbs"
13
+ "tray-daemon.ps1"
14
14
  ],
15
15
  "license": "MIT"
16
16
  }
package/tray-daemon.ps1 CHANGED
@@ -42,10 +42,46 @@ $bmp.Dispose()
42
42
  $notifyIcon = New-Object System.Windows.Forms.NotifyIcon
43
43
  $notifyIcon.Icon = $icon
44
44
  $notifyIcon.Text = "Pan Router | 端口 50816"
45
- $notifyIcon.Visible = $true
46
- Write-Log "NotifyIcon visible"
47
45
 
48
- # ─── 健康检查 ──────────────────────────────────────
46
+ # ─── 右键菜单 (PS5.1 用 .GetNewClosure()) ─────────
47
+ $menu = New-Object System.Windows.Forms.ContextMenuStrip
48
+
49
+ $titleItem = New-Object System.Windows.Forms.ToolStripMenuItem
50
+ $titleItem.Text = "Pan Router - :50816"
51
+ $titleItem.Enabled = $false
52
+ $titleItem.Font = New-Object System.Drawing.Font("Segoe UI", 9, [System.Drawing.FontStyle]::Bold)
53
+ [void]$menu.Items.Add($titleItem)
54
+ [void]$menu.Items.Add((New-Object System.Windows.Forms.ToolStripSeparator))
55
+
56
+ $autoItem = New-Object System.Windows.Forms.ToolStripMenuItem
57
+ $autoItem.Text = "开机自启动"
58
+ $autoItem.Checked = Get-Autostart
59
+ $autoItem.Add_Click({
60
+ $ni = $notifyIcon
61
+ $ai = $autoItem
62
+ if ($ai.Checked) {
63
+ Set-Autostart $false; $ai.Checked = $false
64
+ $ni.ShowBalloonTip(2000, "Pan Router", "开机自启动已关闭", [System.Windows.Forms.ToolTipIcon]::Info)
65
+ } else {
66
+ Set-Autostart $true; $ai.Checked = $true
67
+ $ni.ShowBalloonTip(2000, "Pan Router", "开机自启动已开启 ✓", [System.Windows.Forms.ToolTipIcon]::Info)
68
+ }
69
+ }.GetNewClosure())
70
+ [void]$menu.Items.Add($autoItem)
71
+ [void]$menu.Items.Add((New-Object System.Windows.Forms.ToolStripSeparator))
72
+
73
+ $exitItem = New-Object System.Windows.Forms.ToolStripMenuItem
74
+ $exitItem.Text = "退出"
75
+ $exitItem.Add_Click({
76
+ Write-Log "Exit clicked"
77
+ $notifyIcon.Visible = $false
78
+ [System.Windows.Forms.Application]::Exit()
79
+ }.GetNewClosure())
80
+ [void]$menu.Items.Add($exitItem)
81
+
82
+ $notifyIcon.ContextMenuStrip = $menu
83
+ $notifyIcon.Visible = $true
84
+ Write-Log "NotifyIcon visible with menu"
49
85
  function Test-Online {
50
86
  try {
51
87
  $req = [System.Net.WebRequest]::Create("http://127.0.0.1:50816/health")
@@ -115,28 +151,6 @@ if ($ready) {
115
151
  }
116
152
  }
117
153
 
118
- # 右键菜单
119
- $menu = New-Object System.Windows.Forms.ContextMenuStrip
120
- $titleItem = New-Object System.Windows.Forms.ToolStripMenuItem("Pan Router - :50816")
121
- $titleItem.Enabled = $false
122
- $titleItem.Font = New-Object System.Drawing.Font("Segoe UI", 9, [System.Drawing.FontStyle]::Bold)
123
- $menu.Items.Add($titleItem)
124
- $menu.Items.Add("-")
125
-
126
- $autoItem = New-Object System.Windows.Forms.ToolStripMenuItem("开机自启动")
127
- $autoItem.Checked = Get-Autostart
128
- $autoItem.Add_Click({
129
- if ($autoItem.Checked) { Set-Autostart $false; $autoItem.Checked = $false; $notifyIcon.ShowBalloonTip(2000, "Pan Router", "开机自启动已关闭", [System.Windows.Forms.ToolTipIcon]::Info) }
130
- else { Set-Autostart $true; $autoItem.Checked = $true; $notifyIcon.ShowBalloonTip(2000, "Pan Router", "开机自启动已开启 ✓", [System.Windows.Forms.ToolTipIcon]::Info) }
131
- })
132
- $menu.Items.Add($autoItem)
133
- $menu.Items.Add("-")
134
-
135
- $exitItem = New-Object System.Windows.Forms.ToolStripMenuItem("退出")
136
- $exitItem.Add_Click({ Write-Log "Exit"; $notifyIcon.Visible = $false; [System.Windows.Forms.Application]::Exit() })
137
- $menu.Items.Add($exitItem)
138
- $notifyIcon.ContextMenuStrip = $menu
139
-
140
154
  # 左键: 状态
141
155
  $notifyIcon.Add_MouseClick({
142
156
  if ($_.Button -eq [System.Windows.Forms.MouseButtons]::Left) {
@@ -1,18 +0,0 @@
1
- ' Pan Router 隐藏启动器
2
- ' 用 WScript.Shell.Run 的 0 (隐藏) 启动后台进程
3
- ' Windows 原生, 最可靠的方式
4
-
5
- Dim WshShell, FSO, ScriptDir
6
- Set WshShell = CreateObject("WScript.Shell")
7
- Set FSO = CreateObject("Scripting.FileSystemObject")
8
-
9
- ScriptDir = FSO.GetParentFolderName(WScript.ScriptFullName)
10
-
11
- ' 0 = 隐藏窗口, False = 不等待返回
12
- WshShell.Run "node """ & ScriptDir & "\server.mjs""", 0, False
13
-
14
- ' 等 3 秒让服务器启动
15
- WScript.Sleep 3000
16
-
17
- ' 启动 PS 托盘 (无窗口)
18
- WshShell.Run "powershell -ExecutionPolicy Bypass -WindowStyle Hidden -STA -File """ & ScriptDir & "\tray-daemon.ps1""", 0, False