Skip to content

Commit e1d6632

Browse files
committed
feat(deploy/windows): 系统托盘运行(零依赖 NotifyIcon + W 波浪图标)
双击 tray.bat/tray.vbs 在系统托盘运行,无黑色控制台窗口。右键菜单:打开面板 / 复制面板密码 / 复制 API Key / 状态 / 重启 / 退出;双击图标开网页。 - tray.ps1: .NET WinForms NotifyIcon(零依赖)+ 消息泵定时器监督 node(退出码 75/0 重启、崩溃退避 5 次停),直接 spawn node(不经 cmd /c,避免重定向串被 误解析)+ 异步抽干日志。首次运行生成 API_KEY+DASHBOARD_PASSWORD 写 .env、 大字提示、自动开面板。复制密码/APIKey 用 Clipboard(clip.exe 兜底),每次 启动从 .env 读(非首次也能复制)。 - tray.vbs: 隐藏无窗启动(★必须无 BOM,否则 wscript 报 Invalid character)。 - gen-icon.ps1: 纯 .NET System.Drawing 画 Windsurf W 波浪生成多尺寸 .ico(零 依赖,无需 SVG 转换工具)。windsurfapi.ico(7 尺寸)托盘/任务栏图标。 - 全程本地验证:无黑窗、图标加载、复制密码回读匹配、dashboard 200。
1 parent 72955de commit e1d6632

5 files changed

Lines changed: 371 additions & 0 deletions

File tree

deploy/windows/gen-icon.ps1

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# 生成 windsurfapi.ico —— 纯 .NET System.Drawing 画 Windsurf 的 W 波浪标志,
2+
# 零依赖(无需 SVG 转换工具)。圆头粗笔画走 5 个点(下-上-下-上-下 的 W/波浪),
3+
# 多尺寸(16/24/32/48/64/128/256)打进一个 .ico。托盘图标偏小,用深色描边 +
4+
# 透明底,浅色任务栏也清晰。
5+
$ErrorActionPreference = 'Stop'
6+
Add-Type -AssemblyName System.Drawing
7+
8+
$OutIco = Join-Path $PSScriptRoot 'windsurfapi.ico'
9+
$sizes = @(16, 24, 32, 48, 64, 128, 256)
10+
11+
# 对照参考图的 Windsurf 波浪(不对称,非字母 W):左侧一道陡长斜线从高处下探到
12+
# 第一个圆谷 → 升到中间高峰 → 再下探到第二个圆谷 → 右侧短促上翘收尾。右峰比中峰
13+
# 矮、更靠右上,整体像流动水波而非对称字母。y 越大越靠下。
14+
$pts = @(
15+
[System.Drawing.PointF]::new(210, 380), # 左上起点(高)
16+
[System.Drawing.PointF]::new(430, 760), # 第一个谷(圆底,长斜线下探)
17+
[System.Drawing.PointF]::new(545, 400), # 中间高峰(圆顶)
18+
[System.Drawing.PointF]::new(730, 740), # 第二个谷
19+
[System.Drawing.PointF]::new(800, 470) # 右侧短上翘收尾(比中峰矮,收在画布内)
20+
)
21+
22+
function New-WBitmap([int]$px, [System.Drawing.Color]$color) {
23+
$bmp = New-Object System.Drawing.Bitmap($px, $px, [System.Drawing.Imaging.PixelFormat]::Format32bppArgb)
24+
$g = [System.Drawing.Graphics]::FromImage($bmp)
25+
$g.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::AntiAlias
26+
$g.Clear([System.Drawing.Color]::Transparent)
27+
# 逻辑 1000 → 像素 px 的缩放
28+
$s = $px / 1000.0
29+
$scaled = $pts | ForEach-Object { [System.Drawing.PointF]::new($_.X * $s, $_.Y * $s) }
30+
# 笔画宽度 ~ 图标的 19%,圆头圆角(留边距防截断)
31+
$penW = [float]($px * 0.19)
32+
$pen = New-Object System.Drawing.Pen($color, $penW)
33+
$pen.StartCap = [System.Drawing.Drawing2D.LineCap]::Round
34+
$pen.EndCap = [System.Drawing.Drawing2D.LineCap]::Round
35+
$pen.LineJoin = [System.Drawing.Drawing2D.LineJoin]::Round
36+
$g.DrawLines($pen, [System.Drawing.PointF[]]$scaled)
37+
$pen.Dispose(); $g.Dispose()
38+
return $bmp
39+
}
40+
41+
# 用深色(与参考图一致的近黑)。托盘/任务栏多为浅底,深色最清晰。
42+
$col = [System.Drawing.Color]::FromArgb(255, 17, 17, 17)
43+
44+
# 每个尺寸渲染成 PNG 字节(现代 .ico 支持内嵌 PNG,大尺寸最省且无色深问题)。
45+
$pngs = @()
46+
foreach ($px in $sizes) {
47+
$bmp = New-WBitmap $px $col
48+
$ms = New-Object System.IO.MemoryStream
49+
$bmp.Save($ms, [System.Drawing.Imaging.ImageFormat]::Png)
50+
$pngs += ,($ms.ToArray())
51+
$bmp.Dispose(); $ms.Dispose()
52+
}
53+
54+
# ── 手写 .ico 容器 ──
55+
# ICONDIR(6) + N × ICONDIRENTRY(16) + 各 PNG 数据。
56+
$fs = New-Object System.IO.MemoryStream
57+
$bw = New-Object System.IO.BinaryWriter($fs)
58+
$bw.Write([uint16]0) # reserved
59+
$bw.Write([uint16]1) # type = 1 (icon)
60+
$bw.Write([uint16]$sizes.Count)
61+
$offset = 6 + 16 * $sizes.Count
62+
for ($i = 0; $i -lt $sizes.Count; $i++) {
63+
$px = $sizes[$i]
64+
$len = $pngs[$i].Length
65+
$bw.Write([byte]($(if ($px -ge 256) { 0 } else { $px }))) # width (0 = 256)
66+
$bw.Write([byte]($(if ($px -ge 256) { 0 } else { $px }))) # height
67+
$bw.Write([byte]0) # color count
68+
$bw.Write([byte]0) # reserved
69+
$bw.Write([uint16]1) # color planes
70+
$bw.Write([uint16]32) # bits per pixel
71+
$bw.Write([uint32]$len) # bytes of image data
72+
$bw.Write([uint32]$offset)
73+
$offset += $len
74+
}
75+
foreach ($png in $pngs) { $bw.Write($png) }
76+
$bw.Flush()
77+
[System.IO.File]::WriteAllBytes($OutIco, $fs.ToArray())
78+
$bw.Dispose(); $fs.Dispose()
79+
80+
# 校验:重新加载确认是合法 .ico。
81+
$check = New-Object System.Drawing.Icon($OutIco)
82+
Write-Host ("已生成 " + $OutIco + " (" + $sizes.Count + " 尺寸, " + (Get-Item $OutIco).Length + " 字节, 加载 OK: " + $check.Width + "x" + $check.Height + ")")
83+
$check.Dispose()
84+

deploy/windows/tray.bat

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
@echo off
2+
REM WindsurfAPI - launch as a system-tray app (hidden, no console window).
3+
REM Double-click this: a tray icon appears (右键: 打开面板/状态/重启/退出).
4+
REM Delegates to tray.vbs so no black console lingers. ASCII-only by design.
5+
wscript "%~dp0tray.vbs"

deploy/windows/tray.ps1

Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
# WindsurfAPI 系统托盘监督器(零依赖:纯 .NET WinForms NotifyIcon)。
2+
# 托盘图标 + 右键菜单【打开面板 / 状态 / 重启 / 退出】,双击图标开网页。
3+
# 监督 node:消息泵(Application.Run)+ 定时器轮询子进程退出,按退出码路由
4+
# 75/0 重启、连续崩溃 5 次停。用 `cmd /c node ... >> log` 让 cmd 自己抽干 stdout
5+
# (消息泵下 Register-ObjectEvent 不可靠且可能死锁),$proc.ExitCode 是 node 的
6+
# 退出码(cmd /c 透传),taskkill /T 杀 cmd 会连 node 一起杀。
7+
$ErrorActionPreference = 'Stop'
8+
try { [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 } catch { }
9+
# 早期启动追踪 + 顶层错误捕获:hidden/wscript 下无控制台,任何未捕获错误会静默
10+
# 杀掉进程且不留痕。把致命错误写到 exe 旁的 tray-error.log,便于诊断闪退。
11+
$__traceLog = Join-Path $PSScriptRoot 'tray-error.log'
12+
function __trace($m) { try { Add-Content -Path $__traceLog -Value ("[{0}] {1}" -f (Get-Date -Format 'HH:mm:ss'), $m) -Encoding UTF8 } catch {} }
13+
__trace 'tray.ps1 start'
14+
try {
15+
Add-Type -AssemblyName System.Windows.Forms
16+
Add-Type -AssemblyName System.Drawing
17+
__trace 'assemblies loaded'
18+
19+
$Root = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path
20+
Set-Location $Root
21+
__trace "root=$Root"
22+
$LogDir = Join-Path $Root 'logs'
23+
if (-not (Test-Path $LogDir)) { New-Item -ItemType Directory -Path $LogDir -Force | Out-Null }
24+
$PidFile = Join-Path $LogDir 'windsurfapi.pid'
25+
26+
# 预检 Node
27+
if (-not (Get-Command node -ErrorAction SilentlyContinue)) {
28+
[System.Windows.Forms.MessageBox]::Show('未检测到 Node.js。请从 https://nodejs.org 安装 Node 20+ 后重试。', 'WindsurfAPI', 'OK', 'Error') | Out-Null
29+
exit 1
30+
}
31+
32+
# language server 仅 Linux/macOS;托盘默认走纯 HTTP 主路 + 本机绑定。
33+
if (-not $env:DEVIN_CONNECT -and -not $env:DEVIN_ONLY) { $env:DEVIN_CONNECT = '1' }
34+
if (-not $env:HOST -and -not $env:BIND_HOST) { $env:HOST = '127.0.0.1' }
35+
if (-not $env:LOG_LEVEL) { $env:LOG_LEVEL = 'info' }
36+
37+
# 强随机密钥(与 start.ps1 同格式)。
38+
function New-Secret([int]$bytes = 24) {
39+
$buf = New-Object 'System.Byte[]' $bytes
40+
$rng = [System.Security.Cryptography.RandomNumberGenerator]::Create()
41+
$rng.GetBytes($buf); $rng.Dispose()
42+
return ([Convert]::ToBase64String($buf)) -replace '\+', '-' -replace '/', '_' -replace '=', ''
43+
}
44+
45+
# 首次引导:无 .env 则生成 API_KEY + DASHBOARD_PASSWORD 并写入(无 BOM)。
46+
$envPath = Join-Path $Root '.env'
47+
$genMsg = $null
48+
if (-not (Test-Path $envPath)) {
49+
$apiKey = 'sk-windsurf-' + (New-Secret 24)
50+
$pw = New-Secret 18
51+
$lines = @(
52+
"# --- generated by tray.ps1 $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') ---",
53+
"PORT=3003", "HOST=127.0.0.1", "DEVIN_CONNECT=1",
54+
"API_KEY=$apiKey", "DASHBOARD_PASSWORD=$pw"
55+
)
56+
[System.IO.File]::WriteAllText($envPath, ($lines -join "`r`n") + "`r`n", (New-Object System.Text.UTF8Encoding($false)))
57+
$genMsg = "已生成并写入 .env:`n`n面板密码 (DASHBOARD_PASSWORD):`n $pw`n`nAPI Key (API_KEY):`n $apiKey`n`n请截图妥善保存。"
58+
}
59+
60+
# PLACEHOLDER_TRAY_BODY
61+
62+
# 从 .env 读 PORT / 密码 / API Key(勿硬编码)。每次启动都读,使"复制密码"菜单
63+
# 在非首次启动时也能用(不只首次生成那一刻)。
64+
$Port = 3003
65+
$script:DashPw = ''
66+
$script:ApiKey = ''
67+
foreach ($line in (Get-Content $envPath -EA SilentlyContinue)) {
68+
if ($line -match '^\s*PORT\s*=\s*(\d+)') { $Port = [int]$Matches[1] }
69+
elseif ($line -match '^\s*DASHBOARD_PASSWORD\s*=\s*(.+?)\s*$') { $script:DashPw = $Matches[1] }
70+
elseif ($line -match '^\s*API_KEY\s*=\s*(.+?)\s*$') { $script:ApiKey = $Matches[1] }
71+
}
72+
$DashUrl = "http://127.0.0.1:$Port/dashboard"
73+
74+
# ─── 监督状态 ───
75+
$script:proc = $null # 当前 cmd /c node 子进程
76+
$script:crashStreak = 0
77+
$script:MaxCrash = 5
78+
$script:stopping = $false # 用户请求退出:退出循环不再重拉
79+
$Date = Get-Date -Format 'yyyy-MM-dd'
80+
$SupLog = Join-Path $LogDir "supervisor-$Date.log"
81+
$NodeLog = Join-Path $LogDir "node-$Date.log"
82+
83+
function Write-Sup([string]$m) {
84+
Add-Content -Path $SupLog -Value ("[{0}] {1}" -f (Get-Date -Format 'HH:mm:ss'), $m) -Encoding UTF8
85+
}
86+
87+
# 启动一个 node 子进程。直接 spawn node(不经 cmd /c —— 用 ProcessStartInfo 传
88+
# `>>` 重定向串会被 cmd 误解析报 "syntax incorrect"),CreateNoWindow 无黑窗,
89+
# 异步事件把 stdout/stderr 抽到 node 日志(消息泵下 Register-ObjectEvent 正常触发)。
90+
# 直接 spawn node 时 $proc.ExitCode 可靠(与 run.ps1 同法),75/0 路由才成立。
91+
$script:nodeEvents = @()
92+
function Start-Node {
93+
$psi = New-Object System.Diagnostics.ProcessStartInfo
94+
$psi.FileName = 'node'
95+
$psi.Arguments = 'src\index.js'
96+
$psi.WorkingDirectory = $Root
97+
$psi.UseShellExecute = $false
98+
$psi.RedirectStandardOutput = $true
99+
$psi.RedirectStandardError = $true
100+
$psi.CreateNoWindow = $true
101+
$p = New-Object System.Diagnostics.Process
102+
$p.StartInfo = $psi
103+
$sink = { if ($null -ne $EventArgs.Data) { Add-Content -Path $Event.MessageData -Value $EventArgs.Data -Encoding UTF8 } }
104+
$so = Register-ObjectEvent -InputObject $p -EventName OutputDataReceived -Action $sink -MessageData $NodeLog
105+
$se = Register-ObjectEvent -InputObject $p -EventName ErrorDataReceived -Action $sink -MessageData $NodeLog
106+
$script:nodeEvents = @($so, $se)
107+
[void]$p.Start()
108+
$p.BeginOutputReadLine()
109+
$p.BeginErrorReadLine()
110+
Set-Content -Path $PidFile -Value $p.Id -Encoding ASCII
111+
Write-Sup "node started (pid=$($p.Id))"
112+
return $p
113+
}
114+
115+
# 注销上一次的 stdout/stderr 事件订阅(防跨重启泄漏)。
116+
function Clear-NodeEvents {
117+
foreach ($e in $script:nodeEvents) {
118+
if ($e) { Unregister-Event -SourceIdentifier $e.Name -EA SilentlyContinue }
119+
}
120+
$script:nodeEvents = @()
121+
}
122+
123+
# 停当前 node 子进程:taskkill /T(含可能的 LS 孙进程),超时 /F 兜底。
124+
# K7 后 accounts.json 原子写 + dirty-flush,/F 安全(不截断、最多丢一个 flush 周期)。
125+
function Stop-Node {
126+
if ($script:proc -and -not $script:proc.HasExited) {
127+
try { & taskkill /PID $script:proc.Id /T 2>$null | Out-Null } catch {}
128+
if (-not $script:proc.WaitForExit(3000)) {
129+
try { & taskkill /F /PID $script:proc.Id /T 2>$null | Out-Null } catch {}
130+
}
131+
}
132+
Clear-NodeEvents
133+
if (Test-Path $PidFile) { Remove-Item $PidFile -Force -EA SilentlyContinue }
134+
}
135+
136+
# PLACEHOLDER_TRAY_UI
137+
138+
# ─── 托盘图标 ───
139+
$notify = New-Object System.Windows.Forms.NotifyIcon
140+
# 用系统图标(零资产依赖);有自带 .ico 则优先。
141+
$icoPath = Join-Path $PSScriptRoot 'windsurfapi.ico'
142+
if (Test-Path $icoPath) { $notify.Icon = New-Object System.Drawing.Icon($icoPath) }
143+
else { $notify.Icon = [System.Drawing.SystemIcons]::Application }
144+
$notify.Text = "WindsurfAPI ($DashUrl)" # 悬浮提示(≤63 字符)
145+
$notify.Visible = $true
146+
147+
function Open-Dashboard { Start-Process $DashUrl | Out-Null }
148+
function Notify-Balloon([string]$title, [string]$text) {
149+
$notify.BalloonTipTitle = $title; $notify.BalloonTipText = $text
150+
$notify.ShowBalloonTip(4000)
151+
}
152+
153+
# 复制文本到剪贴板(WinForms Clipboard 需 STA;powershell -File 默认 STA)。
154+
function Copy-ToClipboard([string]$text, [string]$label) {
155+
if ([string]::IsNullOrEmpty($text)) {
156+
Notify-Balloon 'WindsurfAPI' "$label 为空(检查 .env)。"
157+
return
158+
}
159+
try {
160+
[System.Windows.Forms.Clipboard]::SetText($text)
161+
Notify-Balloon 'WindsurfAPI' "$label 已复制到剪贴板。"
162+
} catch {
163+
# 极少数 STA/剪贴板占用失败:退回用 clip.exe 兜底。
164+
try { $text | clip.exe; Notify-Balloon 'WindsurfAPI' "$label 已复制(clip)。" }
165+
catch { Notify-Balloon 'WindsurfAPI' "复制失败:$($_.Exception.Message)" }
166+
}
167+
}
168+
169+
# ─── 右键菜单 ───
170+
$menu = New-Object System.Windows.Forms.ContextMenuStrip
171+
$miOpen = $menu.Items.Add('打开面板 (Open Dashboard)')
172+
$miCopyPw = $menu.Items.Add('复制面板密码 (Copy Password)')
173+
$miCopyKey = $menu.Items.Add('复制 API Key (Copy API Key)')
174+
$miStatus = $menu.Items.Add('状态 (Status)')
175+
$miRestart = $menu.Items.Add('重启 (Restart)')
176+
$menu.Items.Add('-') | Out-Null # 分隔线
177+
$miExit = $menu.Items.Add('退出 (Exit)')
178+
$notify.ContextMenuStrip = $menu
179+
180+
$miOpen.Add_Click({ Open-Dashboard })
181+
$notify.Add_DoubleClick({ Open-Dashboard })
182+
$miCopyPw.Add_Click({ Copy-ToClipboard $script:DashPw '面板密码 (DASHBOARD_PASSWORD)' })
183+
$miCopyKey.Add_Click({ Copy-ToClipboard $script:ApiKey 'API Key' })
184+
185+
$miStatus.Add_Click({
186+
$running = $script:proc -and -not $script:proc.HasExited
187+
$listening = $false
188+
try {
189+
$c = [System.Net.NetworkInformation.IPGlobalProperties]::GetIPGlobalProperties().GetActiveTcpListeners()
190+
$listening = [bool]($c | Where-Object { $_.Port -eq $Port })
191+
} catch {}
192+
$state = if ($running -and $listening) { 'RUNNING' } elseif ($listening) { '监听中(pid 未知)' } else { 'STOPPED' }
193+
Notify-Balloon 'WindsurfAPI 状态' "$state`n端口 $Port ($(if($listening){'监听中'}else{'未监听'}))`n$DashUrl"
194+
})
195+
196+
$miRestart.Add_Click({
197+
Notify-Balloon 'WindsurfAPI' '正在重启…'
198+
Stop-Node
199+
$script:crashStreak = 0
200+
$script:proc = Start-Node
201+
})
202+
203+
$miExit.Add_Click({
204+
$script:stopping = $true
205+
Stop-Node
206+
$notify.Visible = $false
207+
$notify.Dispose()
208+
[System.Windows.Forms.Application]::Exit()
209+
})
210+
211+
# PLACEHOLDER_TRAY_LOOP
212+
213+
# ─── 监督定时器 ───
214+
# 消息泵下不能阻塞 WaitForExit(会冻结 UI),改用 1s 定时器轮询子进程是否退出,
215+
# 按退出码路由重启(75/0)或退避(崩溃),连续崩溃 MaxCrash 次弹窗并停。
216+
$script:backoffUntil = [DateTime]::MinValue
217+
$timer = New-Object System.Windows.Forms.Timer
218+
$timer.Interval = 1000
219+
$timer.Add_Tick({
220+
if ($script:stopping) { return }
221+
if ([DateTime]::Now -lt $script:backoffUntil) { return }
222+
if ($script:proc -and -not $script:proc.HasExited) { return } # 还在跑
223+
224+
# 子进程已退出(或首次尚未启动)。
225+
if ($script:proc) {
226+
$code = $script:proc.ExitCode
227+
Clear-NodeEvents # 注销上个进程的 stdout/stderr 订阅
228+
Write-Sup "node exited code=$code"
229+
if ($code -eq 75 -or $code -eq 0) {
230+
$script:crashStreak = 0 # 面板重启/OTA(75)或优雅(0)→ 重拉
231+
} else {
232+
$script:crashStreak++
233+
if ($script:crashStreak -ge $script:MaxCrash) {
234+
Write-Sup "CRASH LOOP x$($script:crashStreak) (last=$code). Stopping."
235+
Notify-Balloon 'WindsurfAPI 已停止' "连续 $($script:crashStreak) 次异常退出(码 $code)。多为端口占用/配置错。见 logs\node-$Date.log"
236+
$script:proc = $null
237+
$script:stopping = $true # 停止重拉,但托盘留着让用户看/退出
238+
return
239+
}
240+
$script:backoffUntil = [DateTime]::Now.AddSeconds([Math]::Min(2 * $script:crashStreak, 10))
241+
Write-Sup "non-zero exit ($code), streak=$($script:crashStreak) -> backoff"
242+
return
243+
}
244+
}
245+
$script:proc = Start-Node
246+
})
247+
248+
# ─── 启动 ───
249+
$script:proc = Start-Node
250+
$timer.Start()
251+
Notify-Balloon 'WindsurfAPI 已启动' "面板:$DashUrl`n右键托盘图标可打开/重启/退出。"
252+
if ($genMsg) {
253+
[System.Windows.Forms.MessageBox]::Show($genMsg, 'WindsurfAPI 首次运行', 'OK', 'Information') | Out-Null
254+
Start-Process $DashUrl | Out-Null # 首次自动开面板
255+
}
256+
257+
__trace 'entering message pump'
258+
# 消息泵:阻塞直到 Exit()。
259+
[System.Windows.Forms.Application]::Run()
260+
261+
# 兜底清理(Application.Exit 后)。
262+
$timer.Stop()
263+
if (-not $script:proc -or $script:proc.HasExited) { } else { Stop-Node }
264+
if (Test-Path $PidFile) { Remove-Item $PidFile -Force -EA SilentlyContinue }
265+
__trace 'exited cleanly'
266+
} catch {
267+
__trace ("FATAL: " + $_.Exception.Message + " @ " + $_.InvocationInfo.ScriptLineNumber)
268+
try { [System.Windows.Forms.MessageBox]::Show('WindsurfAPI 托盘启动失败:' + $_.Exception.Message, 'WindsurfAPI', 'OK', 'Error') | Out-Null } catch {}
269+
}
270+
271+
272+

deploy/windows/tray.vbs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
' WindsurfAPI tray launcher (hidden, no console window). Double-click to run.
2+
' Launches tray.ps1 windowless via wscript so there is no black console box,
3+
' only the system-tray icon. ASCII-only on purpose: .vbs must NOT have a UTF-8
4+
' BOM (wscript rejects it as "Invalid character"), and non-ASCII in a no-BOM vbs
5+
' can misparse under the shell codepage. All Chinese UI text lives in tray.ps1.
6+
Set sh = CreateObject("WScript.Shell")
7+
Set fso = CreateObject("Scripting.FileSystemObject")
8+
scriptDir = fso.GetParentFolderName(WScript.ScriptFullName)
9+
ps1 = scriptDir & "\tray.ps1"
10+
sh.Run "powershell -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File """ & ps1 & """", 0, False

deploy/windows/windsurfapi.ico

7.2 KB
Binary file not shown.

0 commit comments

Comments
 (0)