panrouter 2.0.0 → 3.0.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.
- package/cli.mjs +45 -113
- package/package.json +2 -3
- package/tray-daemon.ps1 +48 -104
- package/panrouter-tray.vbs +0 -13
package/cli.mjs
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* Pan Router CLI — 一键安装 + 启动
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
3
|
import { execSync, spawn } from "node:child_process";
|
|
8
4
|
import http from "node:http";
|
|
9
5
|
import fs from "node:fs";
|
|
@@ -14,7 +10,6 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
14
10
|
const HOME = process.env.USERPROFILE || process.env.HOME;
|
|
15
11
|
const CLAUDE_DIR = path.join(HOME, ".claude");
|
|
16
12
|
const SETTINGS_PATH = path.join(CLAUDE_DIR, "settings.json");
|
|
17
|
-
const BACKUP_PATH = path.join(CLAUDE_DIR, "settings.json.panrouter.backup");
|
|
18
13
|
|
|
19
14
|
function log(label, msg, color = "") {
|
|
20
15
|
const colors = { green: "\x1b[32m", yellow: "\x1b[33m", red: "\x1b[31m", cyan: "\x1b[36m", reset: "\x1b[0m" };
|
|
@@ -22,46 +17,28 @@ function log(label, msg, color = "") {
|
|
|
22
17
|
console.log(`${c}[${label}]${colors.reset} ${msg}`);
|
|
23
18
|
}
|
|
24
19
|
|
|
25
|
-
function run(cmd) {
|
|
26
|
-
try {
|
|
27
|
-
execSync(cmd, { stdio: "inherit" });
|
|
28
|
-
return true;
|
|
29
|
-
} catch {
|
|
30
|
-
return false;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function runSilent(cmd) {
|
|
35
|
-
try {
|
|
36
|
-
execSync(cmd, { stdio: "pipe" });
|
|
37
|
-
return true;
|
|
38
|
-
} catch {
|
|
39
|
-
return false;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// ─── 1. 安装与配置 ──────────────────────────────────────────
|
|
44
|
-
|
|
45
20
|
function installClaudeCode() {
|
|
46
21
|
log("..", "正在检查 Claude Code...", "yellow");
|
|
47
|
-
|
|
22
|
+
try {
|
|
23
|
+
execSync("claude --version", { stdio: "pipe" });
|
|
48
24
|
log("OK", "Claude Code 已就绪", "green");
|
|
49
25
|
return true;
|
|
26
|
+
} catch {
|
|
27
|
+
log("..", "正在安装 Claude Code...", "yellow");
|
|
28
|
+
try {
|
|
29
|
+
execSync("npm install -g @anthropic-ai/claude-code", { stdio: "inherit" });
|
|
30
|
+
log("OK", "Claude Code 安装成功", "green");
|
|
31
|
+
return true;
|
|
32
|
+
} catch {
|
|
33
|
+
log("!!", "Claude Code 安装失败", "red");
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
50
36
|
}
|
|
51
|
-
log("..", "正在安装 Claude Code...", "yellow");
|
|
52
|
-
if (!run("npm install -g @anthropic-ai/claude-code")) {
|
|
53
|
-
log("!!", "Claude Code 安装失败", "red");
|
|
54
|
-
return false;
|
|
55
|
-
}
|
|
56
|
-
log("OK", "Claude Code 安装成功", "green");
|
|
57
|
-
return true;
|
|
58
37
|
}
|
|
59
38
|
|
|
60
39
|
function writeConfig() {
|
|
61
|
-
log("..", "
|
|
40
|
+
log("..", "正在配置 Claude Code 路由...", "yellow");
|
|
62
41
|
if (!fs.existsSync(CLAUDE_DIR)) fs.mkdirSync(CLAUDE_DIR, { recursive: true });
|
|
63
|
-
if (fs.existsSync(SETTINGS_PATH)) fs.copyFileSync(SETTINGS_PATH, BACKUP_PATH);
|
|
64
|
-
|
|
65
42
|
const config = {
|
|
66
43
|
env: {
|
|
67
44
|
ANTHROPIC_BASE_URL: "http://127.0.0.1:50816",
|
|
@@ -73,7 +50,7 @@ function writeConfig() {
|
|
|
73
50
|
hasCompletedOnboarding: true,
|
|
74
51
|
};
|
|
75
52
|
fs.writeFileSync(SETTINGS_PATH, JSON.stringify(config, null, 2), "utf-8");
|
|
76
|
-
log("OK", "
|
|
53
|
+
log("OK", "配置完成", "green");
|
|
77
54
|
}
|
|
78
55
|
|
|
79
56
|
async function isPortOpen() {
|
|
@@ -85,8 +62,6 @@ async function isPortOpen() {
|
|
|
85
62
|
});
|
|
86
63
|
}
|
|
87
64
|
|
|
88
|
-
// ─── 2. 启动服务(前台黑框) ────────────────────────────────
|
|
89
|
-
|
|
90
65
|
async function startServer() {
|
|
91
66
|
const serverPath = path.join(__dirname, "server.mjs");
|
|
92
67
|
try {
|
|
@@ -97,111 +72,68 @@ async function startServer() {
|
|
|
97
72
|
}
|
|
98
73
|
} catch {}
|
|
99
74
|
|
|
100
|
-
log("..", "
|
|
75
|
+
log("..", "正在启动代理...", "yellow");
|
|
101
76
|
if (process.platform === "win32") {
|
|
102
77
|
execSync(`start "Pan Router" cmd /c "node ${serverPath} & pause"`, { stdio: "pipe" });
|
|
103
78
|
} else {
|
|
104
|
-
|
|
105
|
-
child.unref();
|
|
79
|
+
spawn("node", [serverPath], { cwd: __dirname, stdio: "ignore", detached: true }).unref();
|
|
106
80
|
}
|
|
107
81
|
|
|
108
82
|
for (let i = 0; i < 15; i++) {
|
|
109
83
|
if (await isPortOpen()) break;
|
|
110
84
|
await new Promise(rs => setTimeout(rs, 1000));
|
|
111
85
|
}
|
|
112
|
-
log("OK", "Pan Router
|
|
86
|
+
log("OK", "Pan Router 运行中,可以执行 claude 命令了", "green");
|
|
113
87
|
}
|
|
114
88
|
|
|
115
|
-
// ─── 3. 托盘模式(后台静默) ────────────────────────────────
|
|
116
|
-
|
|
117
89
|
async function startTray() {
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
90
|
+
const psPath = path.join(__dirname, "tray-daemon.ps1");
|
|
91
|
+
log("..", "正在后台启动代理与托盘...", "yellow");
|
|
92
|
+
|
|
93
|
+
// 【核心改变】:彻底抛弃 VBS,利用 Node 原生的 windowsHide 属性无黑框直接启动 PowerShell
|
|
94
|
+
const tray = spawn("powershell.exe", [
|
|
95
|
+
"-NoProfile",
|
|
96
|
+
"-ExecutionPolicy", "Bypass",
|
|
97
|
+
"-WindowStyle", "Hidden",
|
|
98
|
+
"-File", psPath
|
|
99
|
+
], {
|
|
100
|
+
cwd: __dirname,
|
|
101
|
+
stdio: "ignore",
|
|
102
|
+
windowsHide: true,
|
|
103
|
+
detached: true,
|
|
104
|
+
env: { ...process.env, PANROUTER_NODE: process.execPath }
|
|
129
105
|
});
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
// 【核心修复2】:将 Node 的绝对路径通过环境变量塞给 VBS/PowerShell
|
|
133
|
-
process.env.PANROUTER_NODE_PATH = process.execPath;
|
|
134
|
-
|
|
135
|
-
// 启动托盘 UI
|
|
136
|
-
if (fs.existsSync(vbsPath)) {
|
|
137
|
-
const tray = spawn("wscript.exe", ["//B", "//NoLogo", vbsPath], {
|
|
138
|
-
cwd: __dirname,
|
|
139
|
-
stdio: "ignore",
|
|
140
|
-
windowsHide: true,
|
|
141
|
-
detached: true,
|
|
142
|
-
env: process.env // 传递环境变量
|
|
143
|
-
});
|
|
144
|
-
tray.unref();
|
|
145
|
-
}
|
|
106
|
+
tray.unref();
|
|
146
107
|
|
|
147
|
-
// 验证端口
|
|
148
|
-
let ok = false;
|
|
149
108
|
for (let i = 0; i < 15; i++) {
|
|
150
|
-
if (await isPortOpen()) {
|
|
109
|
+
if (await isPortOpen()) {
|
|
110
|
+
log("OK", "后台服务及托盘已启动!(端口 50816)", "green");
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
151
113
|
await new Promise(rs => setTimeout(rs, 1000));
|
|
152
114
|
}
|
|
153
|
-
|
|
154
|
-
if (ok) {
|
|
155
|
-
log("OK", "Pan Router 代理已在后台运行 (端口: 50816)!", "green");
|
|
156
|
-
console.log(" 托盘图标应该已出现。如果系统原因导致图标没出来,代理依然可用。");
|
|
157
|
-
} else {
|
|
158
|
-
log("!!", "代理启动超时,请检查端口 50816 是否被占用", "red");
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// ─── 主流程 ──────────────────────────────────────────────────
|
|
163
|
-
|
|
164
|
-
function printBanner() {
|
|
165
|
-
console.log(`\n\x1b[36m=== Pan Router - Claude Code 代理 ===\x1b[0m\n`);
|
|
115
|
+
log("!!", "服务启动超时", "red");
|
|
166
116
|
}
|
|
167
117
|
|
|
168
118
|
async function main() {
|
|
169
119
|
const args = process.argv.slice(2);
|
|
170
|
-
|
|
171
120
|
if (args.includes("--help") || args.includes("-h")) {
|
|
172
121
|
console.log("用法:\n panrouter --server (带命令行窗口运行)\n panrouter --tray (后台隐藏运行 + 托盘)\n panrouter --tray-install (安装配置 + 托盘)");
|
|
173
122
|
return;
|
|
174
123
|
}
|
|
175
124
|
|
|
176
|
-
if (args.includes("--
|
|
177
|
-
|
|
178
|
-
if (!installClaudeCode()) process.exit(1);
|
|
179
|
-
writeConfig();
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
if (args.includes("--server") || args.includes("-s")) {
|
|
184
|
-
await startServer();
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
125
|
+
if (args.includes("--server") || args.includes("-s")) { await startServer(); return; }
|
|
126
|
+
if (args.includes("--tray") || args.includes("-t")) { await startTray(); return; }
|
|
187
127
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
}
|
|
128
|
+
console.log(`\n\x1b[36m=== Pan Router - Claude Code ===\x1b[0m\n`);
|
|
129
|
+
if (!installClaudeCode()) return process.exit(1);
|
|
130
|
+
writeConfig();
|
|
192
131
|
|
|
193
132
|
if (args.includes("--tray-install") || args.includes("-ti")) {
|
|
194
|
-
printBanner();
|
|
195
|
-
if (!installClaudeCode()) process.exit(1);
|
|
196
|
-
writeConfig();
|
|
197
133
|
await startTray();
|
|
198
|
-
|
|
134
|
+
} else {
|
|
135
|
+
await startServer();
|
|
199
136
|
}
|
|
200
|
-
|
|
201
|
-
printBanner();
|
|
202
|
-
if (!installClaudeCode()) process.exit(1);
|
|
203
|
-
writeConfig();
|
|
204
|
-
await startServer();
|
|
205
137
|
}
|
|
206
138
|
|
|
207
139
|
main();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "panrouter",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "让 Claude Code 免费使用 DeepSeek 等模型,无需 API Key",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -9,8 +9,7 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
"cli.mjs",
|
|
11
11
|
"server.mjs",
|
|
12
|
-
"tray-daemon.ps1"
|
|
13
|
-
"panrouter-tray.vbs"
|
|
12
|
+
"tray-daemon.ps1"
|
|
14
13
|
],
|
|
15
14
|
"license": "MIT"
|
|
16
15
|
}
|
package/tray-daemon.ps1
CHANGED
|
@@ -1,153 +1,97 @@
|
|
|
1
1
|
<#
|
|
2
2
|
.SYNOPSIS
|
|
3
|
-
Pan Router 托盘守护脚本 (
|
|
3
|
+
Pan Router 托盘守护脚本 (极简直连版)
|
|
4
4
|
#>
|
|
5
|
-
|
|
6
5
|
$ErrorActionPreference = "SilentlyContinue"
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
try {
|
|
13
|
-
Add-Type -AssemblyName System.Windows.Forms
|
|
14
|
-
Add-Type -AssemblyName System.Drawing
|
|
15
|
-
} catch {
|
|
16
|
-
"Failed to load assemblies: $($_.Exception.Message)" | Out-File $logFile -Append
|
|
17
|
-
Exit
|
|
18
|
-
}
|
|
7
|
+
Add-Type -AssemblyName System.Windows.Forms
|
|
8
|
+
Add-Type -AssemblyName System.Drawing
|
|
9
|
+
[System.Windows.Forms.Application]::EnableVisualStyles()
|
|
19
10
|
|
|
20
|
-
$
|
|
21
|
-
$scriptDir = Split-Path $scriptPath -Parent
|
|
11
|
+
$scriptDir = Split-Path $MyInvocation.MyCommand.Path -Parent
|
|
22
12
|
$serverPath = Join-Path $scriptDir "server.mjs"
|
|
23
13
|
|
|
24
|
-
#
|
|
25
|
-
$nodePath = $env:
|
|
26
|
-
if ([string]::IsNullOrWhiteSpace($nodePath)) {
|
|
27
|
-
$nodePath = "node"
|
|
28
|
-
}
|
|
29
|
-
"Node Path identified as: $nodePath" | Out-File $logFile -Append
|
|
30
|
-
|
|
31
|
-
# ─── 定义重启服务的逻辑 ────────────────────────────────────
|
|
32
|
-
function Restart-Server {
|
|
33
|
-
"Executing Restart-Server..." | Out-File $logFile -Append
|
|
34
|
-
try {
|
|
35
|
-
# 使用更为稳健的 WMI 查询关闭进程
|
|
36
|
-
Get-WmiObject Win32_Process -Filter "Name = 'node.exe'" |
|
|
37
|
-
Where-Object { $_.CommandLine -match "server\.mjs" } |
|
|
38
|
-
ForEach-Object { Stop-Process -Id $_.ProcessId -Force }
|
|
39
|
-
} catch {}
|
|
40
|
-
|
|
41
|
-
try {
|
|
42
|
-
Start-Process -FilePath $nodePath -ArgumentList "`"$serverPath`"" -WorkingDirectory $scriptDir -WindowStyle Hidden
|
|
43
|
-
"Server process spawned successfully." | Out-File $logFile -Append
|
|
44
|
-
} catch {
|
|
45
|
-
"Server start error: $($_.Exception.Message)" | Out-File $logFile -Append
|
|
46
|
-
}
|
|
47
|
-
}
|
|
14
|
+
# 拿到 Node 路径
|
|
15
|
+
$nodePath = $env:PANROUTER_NODE
|
|
16
|
+
if ([string]::IsNullOrWhiteSpace($nodePath)) { $nodePath = "node" }
|
|
48
17
|
|
|
49
|
-
# ───
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
$isRunning = $true
|
|
56
|
-
break
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
} catch {}
|
|
18
|
+
# ─── 管理后台代理 ──────────────────────────────────────────
|
|
19
|
+
function Start-Backend {
|
|
20
|
+
# 杀掉旧代理
|
|
21
|
+
Get-WmiObject Win32_Process -Filter "Name = 'node.exe'" |
|
|
22
|
+
Where-Object CommandLine -match "server\.mjs" |
|
|
23
|
+
ForEach-Object { Stop-Process -Id $_.ProcessId -Force }
|
|
60
24
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
Restart-Server
|
|
25
|
+
# 启动新代理
|
|
26
|
+
Start-Process -FilePath $nodePath -ArgumentList "`"$serverPath`"" -WorkingDirectory $scriptDir -WindowStyle Hidden
|
|
64
27
|
}
|
|
65
28
|
|
|
66
|
-
|
|
29
|
+
Start-Backend
|
|
30
|
+
|
|
31
|
+
# ─── 初始化托盘 ────────────────────────────────────────────
|
|
32
|
+
$notifyIcon = New-Object System.Windows.Forms.NotifyIcon
|
|
33
|
+
$notifyIcon.Text = "Pan Router (:50816)"
|
|
34
|
+
|
|
35
|
+
# 绘图报错是导致图标出不来的元凶之一,这里做强兜底
|
|
67
36
|
try {
|
|
68
37
|
$bmp = New-Object System.Drawing.Bitmap(16, 16)
|
|
69
38
|
$g = [System.Drawing.Graphics]::FromImage($bmp)
|
|
70
|
-
$g.
|
|
71
|
-
$
|
|
72
|
-
$
|
|
73
|
-
$
|
|
74
|
-
$font = New-Object System.Drawing.Font("Segoe UI", 8, [System.Drawing.FontStyle]::Bold)
|
|
75
|
-
$fg = New-Object System.Drawing.SolidBrush([System.Drawing.Color]::White)
|
|
76
|
-
$g.DrawString("P", $font, $fg, 3, 2)
|
|
77
|
-
|
|
78
|
-
$hIcon = $bmp.GetHicon()
|
|
79
|
-
$icon = [System.Drawing.Icon]::FromHandle($hIcon)
|
|
39
|
+
$g.Clear([System.Drawing.Color]::FromArgb(0, 120, 215))
|
|
40
|
+
$font = New-Object System.Drawing.Font("Arial", 8, [System.Drawing.FontStyle]::Bold)
|
|
41
|
+
$g.DrawString("P", $font, [System.Drawing.Brushes]::White, 2, 1)
|
|
42
|
+
$notifyIcon.Icon = [System.Drawing.Icon]::FromHandle($bmp.GetHicon())
|
|
80
43
|
} catch {
|
|
81
|
-
|
|
82
|
-
|
|
44
|
+
# 终极兜底:如果上面的画图代码因为 GDI 环境被拦,直接用系统自带的安全盾牌图标
|
|
45
|
+
$notifyIcon.Icon = [System.Drawing.SystemIcons]::Shield
|
|
83
46
|
}
|
|
84
47
|
|
|
85
|
-
# ───
|
|
86
|
-
$notifyIcon = New-Object System.Windows.Forms.NotifyIcon
|
|
87
|
-
$notifyIcon.Icon = $icon
|
|
88
|
-
$notifyIcon.Text = "Pan Router (端口: 50816)"
|
|
89
|
-
|
|
48
|
+
# ─── 菜单配置 ──────────────────────────────────────────────
|
|
90
49
|
$menu = New-Object System.Windows.Forms.ContextMenuStrip
|
|
91
50
|
|
|
92
|
-
$titleItem =
|
|
51
|
+
$titleItem = $menu.Items.Add("Pan Router | :50816")
|
|
93
52
|
$titleItem.Enabled = $false
|
|
94
|
-
$
|
|
95
|
-
$menu.Items.Add($titleItem) | Out-Null
|
|
96
|
-
$menu.Items.Add((New-Object System.Windows.Forms.ToolStripSeparator)) | Out-Null
|
|
53
|
+
$menu.Items.Add("-") | Out-Null
|
|
97
54
|
|
|
98
|
-
$restartItem =
|
|
55
|
+
$restartItem = $menu.Items.Add("重启服务")
|
|
99
56
|
$restartItem.Add_Click({
|
|
100
|
-
|
|
101
|
-
$notifyIcon.ShowBalloonTip(2000, "Pan Router", "
|
|
57
|
+
Start-Backend
|
|
58
|
+
$notifyIcon.ShowBalloonTip(2000, "Pan Router", "服务已重启", [System.Windows.Forms.ToolTipIcon]::Info)
|
|
102
59
|
})
|
|
103
|
-
$menu.Items.Add($restartItem) | Out-Null
|
|
104
|
-
|
|
105
|
-
$autoItem = New-Object System.Windows.Forms.ToolStripMenuItem("开机自启动")
|
|
106
|
-
try {
|
|
107
|
-
$regKey = Get-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run" -Name PanRouter
|
|
108
|
-
$autoItem.Checked = ($regKey -ne $null)
|
|
109
|
-
} catch {}
|
|
110
60
|
|
|
61
|
+
$autoItem = $menu.Items.Add("开机自启动")
|
|
62
|
+
try { $autoItem.Checked = (Get-ItemProperty "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run" -Name PanRouter) -ne $null } catch {}
|
|
111
63
|
$autoItem.Add_Click({
|
|
112
64
|
if ($autoItem.Checked) {
|
|
113
65
|
reg delete "HKCU\Software\Microsoft\Windows\CurrentVersion\Run" /v PanRouter /f 2>&1 | Out-Null
|
|
114
66
|
$autoItem.Checked = $false
|
|
115
67
|
} else {
|
|
116
|
-
|
|
117
|
-
$cmd = "
|
|
68
|
+
# 开机自启也摒弃 VBS,直接用隐藏模式的 PowerShell
|
|
69
|
+
$cmd = "powershell.exe -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$($MyInvocation.MyCommand.Path)`""
|
|
118
70
|
reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Run" /v PanRouter /t REG_SZ /d $cmd /f 2>&1 | Out-Null
|
|
119
71
|
$autoItem.Checked = $true
|
|
120
72
|
}
|
|
121
73
|
})
|
|
122
|
-
$menu.Items.Add($autoItem) | Out-Null
|
|
123
|
-
$menu.Items.Add((New-Object System.Windows.Forms.ToolStripSeparator)) | Out-Null
|
|
124
74
|
|
|
125
|
-
$
|
|
75
|
+
$menu.Items.Add("-") | Out-Null
|
|
76
|
+
|
|
77
|
+
$exitItem = $menu.Items.Add("退出")
|
|
126
78
|
$exitItem.Add_Click({
|
|
127
79
|
$notifyIcon.Visible = $false
|
|
128
|
-
|
|
129
|
-
Get-WmiObject Win32_Process -Filter "Name = 'node.exe'" | Where-Object { $_.CommandLine -match "server\.mjs" } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force }
|
|
130
|
-
} catch {}
|
|
80
|
+
Get-WmiObject Win32_Process -Filter "Name = 'node.exe'" | Where-Object CommandLine -match "server\.mjs" | ForEach-Object { Stop-Process -Id $_.ProcessId -Force }
|
|
131
81
|
[System.Windows.Forms.Application]::Exit()
|
|
132
82
|
})
|
|
133
|
-
$menu.Items.Add($exitItem) | Out-Null
|
|
134
83
|
|
|
135
84
|
$notifyIcon.ContextMenuStrip = $menu
|
|
136
85
|
|
|
86
|
+
# 左键点击直接弹出原生菜单(去除了那些花里胡哨的反射代码)
|
|
137
87
|
$notifyIcon.Add_MouseClick({
|
|
138
88
|
if ($_.Button -eq [System.Windows.Forms.MouseButtons]::Left) {
|
|
139
|
-
$
|
|
140
|
-
$mi.Invoke($notifyIcon, $null)
|
|
89
|
+
$notifyIcon.ContextMenuStrip.Show([System.Windows.Forms.Cursor]::Position)
|
|
141
90
|
}
|
|
142
91
|
})
|
|
143
92
|
|
|
144
93
|
$notifyIcon.Visible = $true
|
|
145
94
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
[System.Windows.Forms.Application]::Run($appContext)
|
|
150
|
-
|
|
151
|
-
"Exiting..." | Out-File $logFile -Append
|
|
152
|
-
$notifyIcon.Visible = $false
|
|
153
|
-
$notifyIcon.Dispose()
|
|
95
|
+
# 保持进程存活的最纯粹方式
|
|
96
|
+
$ctx = New-Object System.Windows.Forms.ApplicationContext
|
|
97
|
+
[System.Windows.Forms.Application]::Run($ctx)
|
package/panrouter-tray.vbs
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
' Pan Router Tray Launcher
|
|
2
|
-
' 由 cli.mjs --tray 通过 cmd /c start /B 启动
|
|
3
|
-
' 与 daemon 进程树无关, 独立 Window Station
|
|
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
|
-
' 后台隐藏启动 PS 托盘 (自包含: 启动 server + 图标 + 菜单)
|
|
12
|
-
' 0 = 隐藏窗口, False = 不等待返回
|
|
13
|
-
WshShell.Run "powershell -ExecutionPolicy Bypass -WindowStyle Hidden -STA -File """ & ScriptDir & "\tray-daemon.ps1" & """", 0, False
|