panrouter 1.4.1 → 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 -75
  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,76 +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
- // 用 node 直接 spawn 子进程, 避免 cmd /c 的额外 shell
74
- const child = spawn(process.execPath, [serverPath], {
75
- cwd: appDir,
76
- stdio: "ignore",
77
- windowsHide: true,
78
- detached: true,
79
- shell: false,
80
- });
81
- child.unref();
82
- log(`Server started PID=${child.pid}`);
83
- return child;
84
- })();
85
-
86
- // ─── 等服务器就绪 ─────────────────────────────────
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 托盘 ──────────────────
87
63
  (async () => {
88
64
  let ready = false;
89
65
  for (let i = 0; i < 20; i++) {
90
66
  if (await isOnline()) { ready = true; break; }
91
67
  await new Promise(r => setTimeout(r, 500));
92
68
  }
93
- 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
+ }
94
75
 
95
76
  // ─── 启动 PS 托盘 (pipe, 不 detached!) ──────────
96
- // 关键: 9Router 的做法 - PS 必须与 daemon 同 session
77
+ // 9Router 的做法: PS 必须与 daemon 同 session 才能创建通知图标
97
78
  log("Starting PS tray...");
98
79
  const psProcess = spawn("powershell.exe", [
99
80
  "-NoProfile",
@@ -104,37 +85,35 @@ const serverProcess = (() => {
104
85
  stdio: ["pipe", "pipe", "pipe"],
105
86
  windowsHide: true,
106
87
  shell: false,
107
- // ⚠ 没有 detached: true — PS 进程保持子进程身份
88
+ // ⚠ 没有 detached — PS 保持子进程身份才能访问 Window Station
108
89
  });
109
90
 
110
- // IPC 读取 PS 事件
91
+ psProcess.on("error", (err) => log(`PS spawn ERROR: ${err.message}`));
92
+
93
+ // 读取 PS 事件 (stdout)
111
94
  createInterface({ input: psProcess.stdout }).on("line", (line) => {
112
95
  try {
113
96
  const evt = JSON.parse(line);
114
- log(`PS event: ${evt.type} idx=${evt.index}`);
115
- if (evt.type === "click") {
116
- if (evt.index === 2) { // 退出
117
- log("Exit from PS menu");
118
- psSend({ action: "kill" });
119
- setTimeout(() => process.exit(0), 500);
120
- }
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);
121
102
  }
122
- } catch (e) { log(`PS parse err: ${e.message}`); }
103
+ } catch {}
123
104
  });
124
105
 
125
- psProcess.on("error", err => log(`PS error: ${err.message}`));
126
- psProcess.stderr.on("data", d => log(`[ps] ${d.toString().trim()}`));
127
- psProcess.on("exit", code => {
106
+ psProcess.stderr.on("data", (d) => log(`[ps-err] ${d.toString().trim()}`));
107
+ psProcess.on("exit", (code) => {
128
108
  log(`PS exited code=${code}`);
129
109
  process.exit(0);
130
110
  });
131
111
 
132
- // ─── 发送菜单配置到 PS ──────────────────────────
112
+ // 发送菜单配置
133
113
  function psSend(cmd) {
134
- if (psProcess?.stdin?.writable) {
135
- psProcess.stdin.write(JSON.stringify(cmd) + "\n");
136
- }
114
+ try { psProcess.stdin.write(JSON.stringify(cmd) + "\n"); } catch {}
137
115
  }
116
+ await new Promise(r => setTimeout(r, 500)); // 等 PS 就绪
138
117
  psSend({ action: "add-item", index: 0, title: "Pan Router - :50816", enabled: false });
139
118
  psSend({ action: "add-item", index: 1, title: "─".repeat(19), enabled: false });
140
119
  psSend({ action: "add-item", index: 2, title: "退出", enabled: true });
@@ -145,4 +124,4 @@ const serverProcess = (() => {
145
124
  })();
146
125
 
147
126
  process.on("SIGTERM", () => { log("SIGTERM"); process.exit(0); });
148
- 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.1",
3
+ "version": "1.4.2",
4
4
  "description": "让 Claude Code 免费使用 DeepSeek 等模型,无需 API Key",
5
5
  "type": "module",
6
6
  "bin": {