panrouter 1.3.0 → 1.4.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
@@ -138,41 +138,42 @@ async function startServer() {
138
138
  console.log("\n 现在可以运行: \x1b[33mclaude \"你好\"\x1b[0m\n");
139
139
  }
140
140
 
141
- // ─── 4. 以托盘模式启动(VBS 启动器 + 自包含 PS 托盘) ─────────────────────
141
+ // ─── 4. 以托盘模式启动 ──────────────────────────────────────────────────
142
142
 
143
143
  /**
144
- * 启动策略 (参考 9Router):
145
- * 1. tray-launcher.vbs 启动 tray-daemon.ps1 (WshShell.Run, 完全隐藏)
146
- * 2. tray-daemon.ps1 自包含:
147
- * - 隐藏启动 server.mjs
148
- * - NotifyIcon (右下角)
149
- * - 右键菜单 (开机自启动 / 退出)
150
- * - 30秒健康检查自动重启
151
- * 3. cli.mjs 进程立刻退出
144
+ * 启动策略 (参考 9Router Windows 托盘):
145
+ *
146
+ * cli.mjs ─spawn(detached)──→ node daemon.mjs (无窗口, 常驻)
147
+ * ├─ cmd /c start /B → node server.mjs (隐藏)
148
+ * └─ spawn(pipe) ───→ powershell tray-daemon.ps1
149
+ * └─ NotifyIcon
150
+ *
151
+ * 关键: PS 必须用 pipe 连接(不 detached), 保持与 daemon 的 Window Station
152
+ * 连接, 否则无法创建通知区图标。
152
153
  */
153
154
  function startTray() {
154
- const launcherPath = path.join(__dirname, "tray-launcher.vbs");
155
-
156
- if (!fs.existsSync(launcherPath)) {
157
- log("!!", "未找到 tray-launcher.vbs", "red");
155
+ const daemonPath = path.join(__dirname, "daemon.mjs");
156
+ if (!fs.existsSync(daemonPath)) {
157
+ log("!!", "未找到 daemon.mjs", "red");
158
158
  process.exit(1);
159
159
  }
160
160
 
161
161
  log("..", "正在以托盘模式启动 Pan Router...", "yellow");
162
162
 
163
- // wscript.exe //B = 无窗口, 自动退出
164
- const child = spawn("wscript.exe", [
165
- "//B", "//NoLogo",
166
- launcherPath,
163
+ const child = spawn(process.execPath, [
164
+ daemonPath,
165
+ `--serverPath="${path.join(__dirname, "server.mjs")}"`,
166
+ `--trayPsPath="${path.join(__dirname, "tray-daemon.ps1")}"`,
167
167
  ], {
168
168
  stdio: "ignore",
169
- detached: true,
170
169
  windowsHide: true,
170
+ detached: true,
171
171
  shell: false,
172
172
  });
173
173
  child.unref();
174
174
 
175
- log("OK", "Pan Router 托盘已启动(图标在任务栏右下角)", "green");
175
+ log("OK", "Pan Router 托盘已启动", "green");
176
+ console.log(" 图标在任务栏右下角, 右键菜单可退出");
176
177
  }
177
178
 
178
179
  // ─── 主流程 ──────────────────────────────────────────────────────────────
package/daemon.mjs ADDED
@@ -0,0 +1,148 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Pan Router Daemon — 后台守护进程
5
+ *
6
+ * 用法 (由 cli.mjs --tray 调用):
7
+ * node daemon.mjs --serverPath="..." --trayPsPath="..."
8
+ *
9
+ * 架构 (参考 9Router):
10
+ * daemon.mjs (无窗口, 常驻)
11
+ * ├─ spawn server.mjs (hidden, UseShellExecute=true)
12
+ * └─ spawn powershell tray-daemon.ps1 (pipe, NOT detached)
13
+ * └─ NotifyIcon (右下角)
14
+ *
15
+ * 关键: PS 子进程不 detached, 保持与 daemon 的 session 连接,
16
+ * 否则失去 Window Station 无法创建 UI 通知区图标。
17
+ */
18
+
19
+ import { spawn, execSync } from "node:child_process";
20
+ import { createInterface } from "node:readline";
21
+ import { fileURLToPath } from "node:url";
22
+ import path from "node:path";
23
+ import fs from "node:fs";
24
+ import http from "node:http";
25
+
26
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
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
+ const LOG = path.join(process.env.TEMP || "/tmp", "panrouter-daemon.log");
35
+
36
+ function log(msg) {
37
+ try { fs.appendFileSync(LOG, `${new Date().toISOString().slice(11,19)} ${msg}\n`); } catch {}
38
+ }
39
+
40
+ log("=== PanRouter Daemon (v2) ===");
41
+ log(`serverPath=${serverPath}`);
42
+
43
+ // ─── 健康检查 ─────────────────────────────────────
44
+ function isOnline() {
45
+ 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); });
50
+ });
51
+ }
52
+
53
+ // ─── 启动隐藏的 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
+ // ─── 等服务器就绪 ─────────────────────────────────
87
+ (async () => {
88
+ let ready = false;
89
+ for (let i = 0; i < 20; i++) {
90
+ if (await isOnline()) { ready = true; break; }
91
+ await new Promise(r => setTimeout(r, 500));
92
+ }
93
+ log(`Server ready=${ready}`);
94
+
95
+ // ─── 启动 PS 托盘 (pipe, 不 detached!) ──────────
96
+ // 关键: 9Router 的做法 - PS 必须与 daemon 同 session
97
+ log("Starting PS tray...");
98
+ const psProcess = spawn("powershell.exe", [
99
+ "-NoProfile",
100
+ "-ExecutionPolicy", "Bypass",
101
+ "-WindowStyle", "Hidden",
102
+ "-File", trayPsPath,
103
+ ], {
104
+ stdio: ["pipe", "pipe", "pipe"],
105
+ windowsHide: true,
106
+ shell: false,
107
+ // ⚠ 没有 detached: true — PS 进程保持子进程身份
108
+ });
109
+
110
+ // IPC 读取 PS 事件
111
+ createInterface({ input: psProcess.stdout }).on("line", (line) => {
112
+ try {
113
+ 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
+ }
121
+ }
122
+ } catch (e) { log(`PS parse err: ${e.message}`); }
123
+ });
124
+
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 => {
128
+ log(`PS exited code=${code}`);
129
+ process.exit(0);
130
+ });
131
+
132
+ // ─── 发送菜单配置到 PS ──────────────────────────
133
+ function psSend(cmd) {
134
+ if (psProcess?.stdin?.writable) {
135
+ psProcess.stdin.write(JSON.stringify(cmd) + "\n");
136
+ }
137
+ }
138
+ psSend({ action: "add-item", index: 0, title: "Pan Router - :50816", enabled: false });
139
+ psSend({ action: "add-item", index: 1, title: "─".repeat(19), enabled: false });
140
+ psSend({ action: "add-item", index: 2, title: "退出", enabled: true });
141
+ psSend({ action: "set-tooltip", text: "Pan Router | 端口 50816" });
142
+
143
+ log("Daemon running — keeping session alive");
144
+ process.stdin.resume(); // keep alive
145
+ })();
146
+
147
+ process.on("SIGTERM", () => { log("SIGTERM"); process.exit(0); });
148
+ 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.3.0",
3
+ "version": "1.4.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
- "tray-launcher.vbs"
13
+ "tray-daemon.ps1"
14
14
  ],
15
15
  "license": "MIT"
16
16
  }
package/tray-daemon.ps1 CHANGED
@@ -1,38 +1,28 @@
1
1
  <#
2
2
  .SYNOPSIS
3
- Pan Router Tray Daemon 完全自包含的托盘进程
3
+ Pan Router 托盘进程纯 NotifyIcon IPC 包装器
4
4
 
5
- 功能:
6
- - 隐藏启动 server.mjs (node)
7
- - 右下角 NotifyIcon
8
- - 右键菜单: 开机自启动 / 退出
9
- - 30秒健康检查, 自动重启
10
- - 日志到 %TEMP%\panrouter-tray.log
5
+ IPC: stdin JSON 命令, stdout JSON 事件
11
6
 
12
- 用法 (由 tray-launcher.vbs 调用):
13
- powershell -ExecutionPolicy Bypass -WindowStyle Hidden -STA -File tray-daemon.ps1
7
+ 命令 (stdin):
8
+ {"action":"add-item","index":0,"title":"...","enabled":true}
9
+ {"action":"update-item","index":0,"title":"...","enabled":true}
10
+ {"action":"set-tooltip","text":"..."}
11
+ {"action":"kill"}
14
12
 
15
- 也可直接测试:
16
- powershell -ExecutionPolicy Bypass -STA -File tray-daemon.ps1
13
+ 事件 (stdout):
14
+ {"type":"started"}
15
+ {"type":"click","index":0}
16
+ {"type":"error","message":"..."}
17
17
  #>
18
18
 
19
- $logFile = "$env:TEMP\panrouter-tray.log"
20
- function Write-Log($m) { "$(Get-Date -Format 'HH:mm:ss') $m" | Out-File -Append -Encoding utf8 $logFile }
21
-
22
- Write-Log "=== PanRouter Tray Daemon ==="
23
-
24
- # ─── 路径 (与脚本同目录) ────────────────────────
25
- $scriptDir = Split-Path $MyInvocation.MyCommand.Path -Parent
26
- $serverPath = Join-Path $scriptDir "server.mjs"
27
-
28
- # ─── 加载 WinForms ─────────────────────────────
29
19
  Add-Type -AssemblyName System.Windows.Forms
30
20
  Add-Type -AssemblyName System.Drawing
31
- Write-Log "Assemblies loaded"
32
21
 
33
- # ═══════════════════════════════════════════════════════════
34
- # 1. 生成托盘图标 (蓝色底 P)
35
- # ═══════════════════════════════════════════════════════════
22
+ $ErrorActionPreference = "Stop"
23
+ [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
24
+
25
+ # ─── 生成图标 (蓝色 P, 纯内存) ────────────────────
36
26
  $bmp = New-Object System.Drawing.Bitmap(16, 16)
37
27
  $g = [System.Drawing.Graphics]::FromImage($bmp)
38
28
  $g.SmoothingMode = 'HighQuality'
@@ -46,206 +36,64 @@ $font.Dispose(); $fg.Dispose(); $brush.Dispose(); $g.Dispose()
46
36
  $hIcon = $bmp.GetHicon()
47
37
  $icon = [System.Drawing.Icon]::FromHandle($hIcon)
48
38
  $bmp.Dispose()
49
- Write-Log "Icon created"
50
39
 
51
- # ═══════════════════════════════════════════════════════════
52
- # 2. NotifyIcon
53
- # ═══════════════════════════════════════════════════════════
40
+ # ─── NotifyIcon ──────────────────────────────────
54
41
  $notifyIcon = New-Object System.Windows.Forms.NotifyIcon
55
42
  $notifyIcon.Icon = $icon
56
43
  $notifyIcon.Text = "Pan Router | 端口 50816"
57
44
  $notifyIcon.Visible = $true
58
- Write-Log "NotifyIcon visible"
59
-
60
- # ═══════════════════════════════════════════════════════════
61
- # 3. 隐藏启动 server.mjs
62
- # ═══════════════════════════════════════════════════════════
63
- function Start-Server {
64
- # 杀掉旧 server
65
- try {
66
- $old = wmic process where "name='node.exe'" get ProcessId,CommandLine /format:csv 2>$null
67
- foreach ($line in $old) {
68
- if ($line -match '(\d+),.*?server\.mjs') {
69
- $pid = $Matches[1]
70
- Write-Log "Kill old server PID=$pid"
71
- Stop-Process -Id $pid -Force -ErrorAction SilentlyContinue
72
- }
73
- }
74
- } catch {}
75
-
76
- # UseShellExecute=$true 避免输出缓冲死锁
77
- $psi = New-Object System.Diagnostics.ProcessStartInfo
78
- $psi.FileName = "node"
79
- $psi.Arguments = "`"$serverPath`""
80
- $psi.WorkingDirectory = $scriptDir
81
- $psi.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden
82
- $psi.CreateNoWindow = $true
83
- $psi.UseShellExecute = $true
84
- try {
85
- $p = [System.Diagnostics.Process]::Start($psi)
86
- Write-Log "Server started PID=$($p.Id)"
87
- return $p
88
- } catch {
89
- Write-Log "Server start FAILED: $_"
90
- return $null
91
- }
92
- }
93
-
94
- # ═══════════════════════════════════════════════════════════
95
- # 4. 健康检查
96
- # ═══════════════════════════════════════════════════════════
97
- function Test-Online {
98
- try {
99
- $req = [System.Net.WebRequest]::Create("http://127.0.0.1:50816/health")
100
- $req.Timeout = 1500
101
- $resp = $req.GetResponse()
102
- $resp.Close()
103
- return $true
104
- } catch { return $false }
105
- }
106
45
 
107
- # ═══════════════════════════════════════════════════════════
108
- # 5. 开机自启动 (Registry)
109
- # ═══════════════════════════════════════════════════════════
110
- $autostartName = "PanRouter"
111
- $runKey = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run"
46
+ $menu = New-Object System.Windows.Forms.ContextMenuStrip
47
+ $notifyIcon.ContextMenuStrip = $menu
48
+ $items = @()
112
49
 
113
- function Get-Autostart {
50
+ function Write-Event($obj) {
114
51
  try {
115
- $val = Get-ItemProperty -Path $runKey -Name $autostartName -ErrorAction Stop
116
- return $val.$autostartName -ne $null
117
- } catch { return $false }
52
+ [Console]::Out.WriteLine(($obj | ConvertTo-Json -Compress))
53
+ [Console]::Out.Flush()
54
+ } catch {}
118
55
  }
119
56
 
120
- function Set-Autostart($enable) {
121
- if ($enable) {
122
- $vbsPath = Join-Path $scriptDir "tray-launcher.vbs"
123
- reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Run" /v $autostartName /t REG_SZ /d "wscript.exe //B `"$vbsPath`"" /f 2>&1 | Out-Null
124
- Write-Log "Autostart ON: $vbsPath"
125
- } else {
126
- reg delete "HKCU\Software\Microsoft\Windows\CurrentVersion\Run" /v $autostartName /f 2>&1 | Out-Null
127
- Write-Log "Autostart OFF"
128
- }
57
+ function Add-MenuItem($index, $title, $enabled) {
58
+ $item = New-Object System.Windows.Forms.ToolStripMenuItem
59
+ $item.Text = $title
60
+ $item.Enabled = $enabled
61
+ $idx = $index
62
+ $item.Add_Click({ Write-Event @{type="click"; index=$idx} }.GetNewClosure())
63
+ $menu.Items.Add($item) | Out-Null
64
+ $items += $item
129
65
  }
130
66
 
131
- # ═══════════════════════════════════════════════════════════
132
- # ══ 启动流程 ══
133
- # ═══════════════════════════════════════════════════════════
134
-
135
- # 启动服务器
136
- $serverProcess = Start-Server
137
-
138
- # 等待就绪 (10秒)
139
- $ready = $false
140
- for ($i = 0; $i -lt 20; $i++) {
141
- Start-Sleep -Milliseconds 500
142
- if (Test-Online) { $ready = $true; break }
143
- }
144
- Write-Log "Server ready=$ready"
145
- if ($ready) {
146
- $notifyIcon.ShowBalloonTip(3000, "Pan Router", "服务器已就绪 (端口 50816)", [System.Windows.Forms.ToolTipIcon]::Info)
147
- } else {
148
- $notifyIcon.ShowBalloonTip(3000, "Pan Router", "服务器启动失败, 查看日志", [System.Windows.Forms.ToolTipIcon]::Error)
67
+ function Set-Tooltip($text) {
68
+ if ($text.Length -gt 63) { $text = $text.Substring(0, 63) }
69
+ $notifyIcon.Text = $text
149
70
  }
150
71
 
151
- # ═══════════════════════════════════════════════════════════
152
- # ══ 右键菜单 ══
153
- # ═══════════════════════════════════════════════════════════
154
- $menu = New-Object System.Windows.Forms.ContextMenuStrip
155
-
156
- # 标题
157
- $titleItem = New-Object System.Windows.Forms.ToolStripMenuItem("Pan Router - :50816")
158
- $titleItem.Enabled = $false
159
- $titleItem.Font = New-Object System.Drawing.Font("Segoe UI", 9, [System.Drawing.FontStyle]::Bold)
160
- $menu.Items.Add($titleItem)
161
- $menu.Items.Add("-")
162
-
163
- # 开机自启动
164
- $autoItem = New-Object System.Windows.Forms.ToolStripMenuItem("开机自启动")
165
- $autoItem.Checked = Get-Autostart
166
- $autoItem.Add_Click({
167
- if ($autoItem.Checked) {
168
- Set-Autostart $false
169
- $autoItem.Checked = $false
170
- $notifyIcon.ShowBalloonTip(2000, "Pan Router", "开机自启动已关闭", [System.Windows.Forms.ToolTipIcon]::Info)
171
- } else {
172
- Set-Autostart $true
173
- $autoItem.Checked = $true
174
- $notifyIcon.ShowBalloonTip(2000, "Pan Router", "开机自启动已开启 ✓", [System.Windows.Forms.ToolTipIcon]::Info)
175
- }
176
- })
177
- $menu.Items.Add($autoItem)
178
- $menu.Items.Add("-")
179
-
180
- # 退出
181
- $exitItem = New-Object System.Windows.Forms.ToolStripMenuItem("退出")
182
- $exitItem.Add_Click({
183
- Write-Log "Exit clicked"
184
- $notifyIcon.Visible = $false
185
- [System.Windows.Forms.Application]::Exit()
186
- })
187
- $menu.Items.Add($exitItem)
188
-
189
- $notifyIcon.ContextMenuStrip = $menu
190
-
191
- # ═══════════════════════════════════════════════════════════
192
- # ══ 左键: 状态气泡 ══
193
- # ═══════════════════════════════════════════════════════════
194
- $notifyIcon.Add_MouseClick({
195
- if ($_.Button -eq [System.Windows.Forms.MouseButtons]::Left) {
196
- if (Test-Online) {
197
- $notifyIcon.ShowBalloonTip(2000, "Pan Router", "运行正常 ✓ (端口 50816)", [System.Windows.Forms.ToolTipIcon]::Info)
198
- } else {
199
- $notifyIcon.ShowBalloonTip(2000, "Pan Router", "服务器未响应 ⚠", [System.Windows.Forms.ToolTipIcon]::Error)
200
- }
201
- }
202
- })
203
-
204
- # ═══════════════════════════════════════════════════════════
205
- # ══ 定时健康检查 (30秒) ══
206
- # ═══════════════════════════════════════════════════════════
207
- $healthTimer = New-Object System.Windows.Forms.Timer
208
- $healthTimer.Interval = 30000
209
- $healthTimer.Add_Tick({
210
- if (-not (Test-Online)) {
211
- Write-Log "Health check FAILED, restarting server..."
212
- Start-Server
213
- Start-Sleep -Seconds 3
214
- if (Test-Online) {
215
- $notifyIcon.ShowBalloonTip(3000, "Pan Router", "服务器已自动重启", [System.Windows.Forms.ToolTipIcon]::Info)
216
- }
217
- }
218
- })
219
- $healthTimer.Start()
220
-
221
- # ═══════════════════════════════════════════════════════════
222
- # ══ 退出清理 ══
223
- # ═══════════════════════════════════════════════════════════
224
- [System.Windows.Forms.Application]::ApplicationExit += {
225
- Write-Log "AppExit cleanup"
226
- $healthTimer.Stop()
227
- try {
228
- if ($serverProcess -and !$serverProcess.HasExited) {
229
- $serverProcess.Kill()
230
- $serverProcess.WaitForExit(3000)
231
- }
232
- } catch {}
233
- # 补刀: 杀残留
72
+ # ─── stdin 轮询 (9Router 做法: 100ms Timer) ──────
73
+ $timer = New-Object System.Windows.Forms.Timer
74
+ $timer.Interval = 100
75
+ $timer.Add_Tick({
234
76
  try {
235
- $old = wmic process where "name='node.exe'" get ProcessId,CommandLine /format:csv 2>$null
236
- foreach ($line in $old) {
237
- if ($line -match '(\d+),.*?server\.mjs') { Stop-Process -Id $Matches[1] -Force -ErrorAction SilentlyContinue }
77
+ while ([Console]::In.Peek() -ne -1) {
78
+ $line = [Console]::In.ReadLine()
79
+ if ([string]::IsNullOrWhiteSpace($line)) { continue }
80
+ $cmd = $line | ConvertFrom-Json
81
+ switch ($cmd.action) {
82
+ "add-item" { Add-MenuItem $cmd.index $cmd.title $cmd.enabled }
83
+ "update-item" { }
84
+ "set-tooltip" { Set-Tooltip $cmd.text }
85
+ "kill" {
86
+ $notifyIcon.Visible = $false
87
+ $notifyIcon.Dispose()
88
+ $icon.Dispose()
89
+ [System.Runtime.InteropServices.Marshal]::DestroyIcon($hIcon)
90
+ [System.Windows.Forms.Application]::Exit()
91
+ }
92
+ }
238
93
  }
239
- } catch {}
240
- $notifyIcon.Dispose()
241
- [System.Runtime.InteropServices.Marshal]::DestroyIcon($hIcon)
242
- $icon.Dispose()
243
- Write-Log "Cleanup done"
244
- }
94
+ } catch { Write-Event @{type="error"; message=$_.Exception.Message} }
95
+ })
96
+ $timer.Start()
245
97
 
246
- # ═══════════════════════════════════════════════════════════
247
- # ══ 消息循环 ══
248
- # ═══════════════════════════════════════════════════════════
249
- Write-Log "Entering message loop"
98
+ Write-Event @{type="started"}
250
99
  [System.Windows.Forms.Application]::Run()
251
- Write-Log "Message loop exited"
package/tray-launcher.vbs DELETED
@@ -1,14 +0,0 @@
1
- ' Pan Router Tray Launcher
2
- ' Windows 原生无窗口启动器(VBScript 内置, 无需任何运行时)
3
- ' 用法: wscript.exe tray-launcher.vbs
4
- ' 或 cscript.exe //nologo tray-launcher.vbs
5
-
6
- Dim WshShell, FSO, ScriptDir
7
- Set WshShell = CreateObject("WScript.Shell")
8
- Set FSO = CreateObject("Scripting.FileSystemObject")
9
-
10
- ScriptDir = FSO.GetParentFolderName(WScript.ScriptFullName)
11
- PS1Path = ScriptDir & "\tray-daemon.ps1"
12
-
13
- ' 用 PowerShell 启动托盘守护进程 (0 = 隐藏窗口, False = 不等待返回)
14
- WshShell.Run "powershell -ExecutionPolicy Bypass -WindowStyle Hidden -STA -File """ & PS1Path & """", 0, False