panrouter 1.4.0 → 1.4.2

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 +1 -5
  2. package/daemon.mjs +54 -74
  3. package/package.json +1 -1
package/cli.mjs CHANGED
@@ -160,11 +160,7 @@ function startTray() {
160
160
 
161
161
  log("..", "正在以托盘模式启动 Pan Router...", "yellow");
162
162
 
163
- const child = spawn(process.execPath, [
164
- daemonPath,
165
- `--serverPath="${path.join(__dirname, "server.mjs")}"`,
166
- `--trayPsPath="${path.join(__dirname, "tray-daemon.ps1")}"`,
167
- ], {
163
+ const child = spawn(process.execPath, [daemonPath], {
168
164
  stdio: "ignore",
169
165
  windowsHide: true,
170
166
  detached: true,
package/daemon.mjs CHANGED
@@ -3,20 +3,17 @@
3
3
  /**
4
4
  * Pan Router Daemon — 后台守护进程
5
5
  *
6
- * 用法 (由 cli.mjs --tray 调用):
7
- * node daemon.mjs --serverPath="..." --trayPsPath="..."
6
+ * server.mjs + tray-daemon.ps1 在无窗口下运行。
7
+ * server.mjs detached + windowsHide 启动。
8
+ * tray-daemon.ps1 用 pipe 连接 (不 detached — 否则失去 Window Station)。
8
9
  *
9
- * 架构 (参考 9Router):
10
- * daemon.mjs (无窗口, 常驻)
11
- * ├─ spawn server.mjs (hidden, UseShellExecute=true)
12
- * └─ spawn powershell tray-daemon.ps1 (pipe, NOT detached)
13
- * └─ NotifyIcon (右下角)
10
+ * 用法 ( cli.mjs --tray 调用):
11
+ * node daemon.mjs
14
12
  *
15
- * 关键: PS 子进程不 detached, 保持与 daemon 的 session 连接,
16
- * 否则失去 Window Station 无法创建 UI 通知区图标。
13
+ * 所有路径自动从 __dirname 解析 (同包目录)。
17
14
  */
18
15
 
19
- import { spawn, execSync } from "node:child_process";
16
+ import { spawn } from "node:child_process";
20
17
  import { createInterface } from "node:readline";
21
18
  import { fileURLToPath } from "node:url";
22
19
  import path from "node:path";
@@ -24,74 +21,60 @@ import fs from "node:fs";
24
21
  import http from "node:http";
25
22
 
26
23
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
24
+ const appDir = __dirname;
25
+ const serverPath = path.join(appDir, "server.mjs");
26
+ const trayPsPath = path.join(appDir, "tray-daemon.ps1");
27
27
 
28
- // ─── 解析参数 ────────────────────────────────────
29
- const serverPath = process.argv.find(a => a.startsWith("--serverPath="))?.split("=")[1];
30
- const trayPsPath = process.argv.find(a => a.startsWith("--trayPsPath="))?.split("=")[1];
31
- if (!serverPath || !trayPsPath) { process.exit(1); }
32
-
33
- const appDir = path.dirname(serverPath);
34
28
  const LOG = path.join(process.env.TEMP || "/tmp", "panrouter-daemon.log");
35
-
36
29
  function log(msg) {
37
30
  try { fs.appendFileSync(LOG, `${new Date().toISOString().slice(11,19)} ${msg}\n`); } catch {}
38
31
  }
39
32
 
40
- log("=== PanRouter Daemon (v2) ===");
33
+ log("=== PanRouter Daemon (v3) ===");
41
34
  log(`serverPath=${serverPath}`);
35
+ log(`trayPsPath=${trayPsPath}`);
42
36
 
43
37
  // ─── 健康检查 ─────────────────────────────────────
44
38
  function isOnline() {
45
39
  return new Promise(rs => {
46
- const req = http.get("http://127.0.0.1:50816/health", () => {});
47
- req.on("response", () => { req.destroy(); rs(true); });
48
- req.on("error", () => rs(false));
49
- req.setTimeout(1500, () => { req.destroy(); rs(false); });
40
+ try {
41
+ const req = http.get("http://127.0.0.1:50816/health", () => {});
42
+ req.on("response", () => { req.destroy(); rs(true); });
43
+ req.on("error", () => rs(false));
44
+ req.setTimeout(1500, () => { req.destroy(); rs(false); });
45
+ } catch { rs(false); }
50
46
  });
51
47
  }
52
48
 
53
49
  // ─── 启动隐藏的 server.mjs ────────────────────────
54
- const serverProcess = (() => {
55
- // 先杀旧的
56
- try {
57
- const wmic = spawn("wmic", [
58
- "process", "where", "name='node.exe'", "get", "ProcessId,CommandLine", "/format:csv"
59
- ], { stdio: ["ignore", "pipe", "ignore"] });
60
- let out = "";
61
- wmic.stdout.on("data", d => out += d.toString());
62
- wmic.on("close", () => {
63
- for (const line of out.split("\n")) {
64
- if (line.includes("server.mjs")) {
65
- const m = line.match(/(\d+),.*?server\.mjs/);
66
- if (m) { try { process.kill(parseInt(m[1]), "SIGKILL"); } catch {} }
67
- }
68
- }
69
- });
70
- } catch {}
71
-
72
- log("Starting server...");
73
- const child = spawn("cmd.exe", ["/c", "start", "/B", "node", serverPath], {
74
- cwd: appDir,
75
- stdio: "ignore",
76
- windowsHide: true,
77
- shell: false,
78
- });
79
- child.unref();
80
- log(`Server started via cmd /c start /B`);
81
- return child;
82
- })();
83
-
84
- // ─── 等服务器就绪 ─────────────────────────────────
50
+ log("Starting server...");
51
+ const serverProcess = spawn(process.execPath, [serverPath], {
52
+ cwd: appDir,
53
+ stdio: "ignore",
54
+ windowsHide: true,
55
+ detached: true,
56
+ shell: false,
57
+ });
58
+ serverProcess.on("error", (err) => log(`Server spawn ERROR: ${err.message}`));
59
+ serverProcess.unref();
60
+ log(`Server spawned PID=${serverProcess.pid || "(no pid)"}`);
61
+
62
+ // ─── 等服务器就绪后启动 PS 托盘 ──────────────────
85
63
  (async () => {
86
64
  let ready = false;
87
65
  for (let i = 0; i < 20; i++) {
88
66
  if (await isOnline()) { ready = true; break; }
89
67
  await new Promise(r => setTimeout(r, 500));
90
68
  }
91
- log(`Server ready=${ready}`);
69
+ log(`Server online=${ready}`);
70
+
71
+ if (!fs.existsSync(trayPsPath)) {
72
+ log(`ERROR: tray-daemon.ps1 not found at ${trayPsPath}`);
73
+ return;
74
+ }
92
75
 
93
76
  // ─── 启动 PS 托盘 (pipe, 不 detached!) ──────────
94
- // 关键: 9Router 的做法 - PS 必须与 daemon 同 session
77
+ // 9Router 的做法: PS 必须与 daemon 同 session 才能创建通知图标
95
78
  log("Starting PS tray...");
96
79
  const psProcess = spawn("powershell.exe", [
97
80
  "-NoProfile",
@@ -102,38 +85,35 @@ const serverProcess = (() => {
102
85
  stdio: ["pipe", "pipe", "pipe"],
103
86
  windowsHide: true,
104
87
  shell: false,
105
- // ⚠ 没有 detached: true — PS 进程保持子进程身份
88
+ // ⚠ 没有 detached — PS 保持子进程身份才能访问 Window Station
106
89
  });
107
90
 
108
- // IPC 读取 PS 事件
91
+ psProcess.on("error", (err) => log(`PS spawn ERROR: ${err.message}`));
92
+
93
+ // 读取 PS 事件 (stdout)
109
94
  createInterface({ input: psProcess.stdout }).on("line", (line) => {
110
95
  try {
111
96
  const evt = JSON.parse(line);
112
- log(`PS event: ${evt.type} idx=${evt.index}`);
113
- if (evt.type === "click") {
114
- if (evt.index === 2) { // 退出
115
- log("Exit from PS menu");
116
- try { if (serverProcess && !serverProcess.killed) serverProcess.kill(); } catch {}
117
- psSend({ action: "kill" });
118
- setTimeout(() => process.exit(0), 500);
119
- }
97
+ log(`PS: ${evt.type} idx=${evt.index}`);
98
+ if (evt.type === "click" && evt.index === 2) {
99
+ log("Exit from PS menu");
100
+ try { psProcess.stdin.write(JSON.stringify({ action: "kill" }) + "\n"); } catch {}
101
+ setTimeout(() => process.exit(0), 300);
120
102
  }
121
- } catch (e) { log(`PS parse err: ${e.message}`); }
103
+ } catch {}
122
104
  });
123
105
 
124
- psProcess.on("error", err => log(`PS error: ${err.message}`));
125
- psProcess.stderr.on("data", d => log(`[ps] ${d.toString().trim()}`));
126
- psProcess.on("exit", code => {
106
+ psProcess.stderr.on("data", (d) => log(`[ps-err] ${d.toString().trim()}`));
107
+ psProcess.on("exit", (code) => {
127
108
  log(`PS exited code=${code}`);
128
109
  process.exit(0);
129
110
  });
130
111
 
131
- // ─── 发送菜单配置到 PS ──────────────────────────
112
+ // 发送菜单配置
132
113
  function psSend(cmd) {
133
- if (psProcess?.stdin?.writable) {
134
- psProcess.stdin.write(JSON.stringify(cmd) + "\n");
135
- }
114
+ try { psProcess.stdin.write(JSON.stringify(cmd) + "\n"); } catch {}
136
115
  }
116
+ await new Promise(r => setTimeout(r, 500)); // 等 PS 就绪
137
117
  psSend({ action: "add-item", index: 0, title: "Pan Router - :50816", enabled: false });
138
118
  psSend({ action: "add-item", index: 1, title: "─".repeat(19), enabled: false });
139
119
  psSend({ action: "add-item", index: 2, title: "退出", enabled: true });
@@ -144,4 +124,4 @@ const serverProcess = (() => {
144
124
  })();
145
125
 
146
126
  process.on("SIGTERM", () => { log("SIGTERM"); process.exit(0); });
147
- process.on("SIGINT", () => { log("SIGINT"); process.exit(0); });
127
+ process.on("SIGINT", () => { log("SIGINT"); process.exit(0); });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "panrouter",
3
- "version": "1.4.0",
3
+ "version": "1.4.2",
4
4
  "description": "让 Claude Code 免费使用 DeepSeek 等模型,无需 API Key",
5
5
  "type": "module",
6
6
  "bin": {