dsh-windows-tray 1.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/LICENSE +21 -0
- package/README.md +138 -0
- package/bin/dsh-tray.js +100 -0
- package/build/build-tray-icons.ps1 +98 -0
- package/build/dsh-tray-running-preview.png +0 -0
- package/build/dsh-tray-stopped-preview.png +0 -0
- package/build/whale-512.png +0 -0
- package/build/whale-render.html +9 -0
- package/docs/system-tray-guide.md +102 -0
- package/docs/tray-dev-archive.md +57 -0
- package/install.ps1 +110 -0
- package/launcher/bh-menu.bat +4 -0
- package/launcher/bh-menu.ps1 +56 -0
- package/launcher/dsh-actions.ps1 +50 -0
- package/launcher/dsh-all-menu.bat +4 -0
- package/launcher/dsh-all-menu.ps1 +106 -0
- package/launcher/dsh-control.bat +10 -0
- package/launcher/dsh-control.ps1 +40 -0
- package/launcher/dsh-lib.ps1 +165 -0
- package/launcher/dsh-menu.bat +11 -0
- package/launcher/dsh-menu.ps1 +93 -0
- package/launcher/dsh-tray.bat +10 -0
- package/launcher/dsh-tray.ps1 +418 -0
- package/launcher/dsh-update.ps1 +116 -0
- package/launcher/icons/dsh-tray-running.ico +0 -0
- package/launcher/icons/dsh-tray-stopped.ico +0 -0
- package/package.json +36 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# dsh 黑洞模拟器启动器 —— 网页版交互黑洞(oseiskar/black-hole)
|
|
2
|
+
# 用法: 双击 bh-menu.bat 或 powershell -File bh-menu.ps1 -Cmd start|stop|status
|
|
3
|
+
param([string]$Cmd)
|
|
4
|
+
$ErrorActionPreference = 'Stop'
|
|
5
|
+
chcp 65001 > $null
|
|
6
|
+
|
|
7
|
+
$PORT = 8137
|
|
8
|
+
$URL = 'http://127.0.0.1:8137'
|
|
9
|
+
$DIR = "$env:USERPROFILE\.dsh\gen-wallpaper\bh-shader"
|
|
10
|
+
|
|
11
|
+
function Test-Port([int]$port) {
|
|
12
|
+
$c = New-Object System.Net.Sockets.TcpClient
|
|
13
|
+
try { if ($c.BeginConnect('127.0.0.1', $port, $null, $null).AsyncWaitHandle.WaitOne(500) -and $c.Connected) { return $true } } catch {}
|
|
14
|
+
finally { $c.Close() }
|
|
15
|
+
return $false
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function Start-BH {
|
|
19
|
+
if (Test-Port $PORT) { Write-Output "[bh] 服务已在运行: $URL"; Start-Process $URL; return }
|
|
20
|
+
Start-Process node -ArgumentList 'serve.mjs' -WorkingDirectory $DIR -WindowStyle Hidden
|
|
21
|
+
Start-Sleep -Seconds 3
|
|
22
|
+
if (Test-Port $PORT) { Write-Output "[bh] 启动成功: $URL"; Start-Process $URL }
|
|
23
|
+
else { Write-Output "[bh] 启动失败, 请手工运行: cd $DIR; node serve.mjs" }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function Stop-BH {
|
|
27
|
+
$pids = @((Get-NetTCPConnection -LocalPort $PORT -State Listen -ErrorAction SilentlyContinue).OwningProcess | Select-Object -Unique)
|
|
28
|
+
if ($pids.Count -eq 0) { Write-Output '[bh] 服务未运行'; return }
|
|
29
|
+
foreach ($p in $pids) { Write-Output "[bh] 停止进程 $p"; & taskkill /PID $p /F 2>$null | Out-Null }
|
|
30
|
+
Write-Output '[bh] 已停止'
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if ($Cmd) {
|
|
34
|
+
switch ($Cmd.ToLower()) {
|
|
35
|
+
'start' { Start-BH }
|
|
36
|
+
'stop' { Stop-BH }
|
|
37
|
+
'status'{ if (Test-Port $PORT) { Write-Output "[bh] 运行中: $URL" } else { Write-Output '[bh] 未运行' } }
|
|
38
|
+
default { Write-Output '用法: bh-menu.ps1 -Cmd start|stop|status' }
|
|
39
|
+
}
|
|
40
|
+
exit
|
|
41
|
+
}
|
|
42
|
+
while ($true) {
|
|
43
|
+
Write-Output ''
|
|
44
|
+
Write-Output '======== 黑洞模拟器 ========'
|
|
45
|
+
if (Test-Port $PORT) { Write-Output '状态: 运行中' } else { Write-Output '状态: 未运行' }
|
|
46
|
+
Write-Output '[1] 启动并打开浏览器'
|
|
47
|
+
Write-Output '[2] 停止'
|
|
48
|
+
Write-Output '[0] 退出'
|
|
49
|
+
$choice = Read-Host '请选择'
|
|
50
|
+
switch ($choice) {
|
|
51
|
+
'1' { Start-BH }
|
|
52
|
+
'2' { Stop-BH }
|
|
53
|
+
'0' { exit }
|
|
54
|
+
default { Write-Output '无效选择' }
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# dsh-actions.ps1 —— 托盘菜单动作执行器 (由 dsh-tray.ps1 以独立隐藏进程拉起)
|
|
2
|
+
# 用法: pwsh -NoProfile -File dsh-actions.ps1 -Action start|stop|restart|tui-start|tui-stop
|
|
3
|
+
# 动作在独立进程执行, 托盘 UI 线程保持响应; 日志写入 ~/.dsh/logs/dsh-tray-actions.log
|
|
4
|
+
param(
|
|
5
|
+
[Parameter(Mandatory = $true)]
|
|
6
|
+
[ValidateSet('start', 'stop', 'restart', 'tui-start', 'tui-stop')]
|
|
7
|
+
[string]$Action
|
|
8
|
+
)
|
|
9
|
+
$ErrorActionPreference = 'Stop'
|
|
10
|
+
|
|
11
|
+
. (Join-Path $PSScriptRoot 'dsh-lib.ps1')
|
|
12
|
+
|
|
13
|
+
$logFile = Join-Path $LOG_DIR 'dsh-tray-actions.log'
|
|
14
|
+
function Log([string]$msg) {
|
|
15
|
+
$line = ('[{0}] {1}' -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $msg)
|
|
16
|
+
Add-Content -Path $logFile -Value $line -ErrorAction SilentlyContinue
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
switch ($Action) {
|
|
21
|
+
'start' {
|
|
22
|
+
Log '启动 DSH (来自系统托盘)'
|
|
23
|
+
Start-Dsh -OpenBrowser:$false
|
|
24
|
+
Log '启动动作完成'
|
|
25
|
+
}
|
|
26
|
+
'stop' {
|
|
27
|
+
Log '停止 DSH (来自系统托盘)'
|
|
28
|
+
Stop-Dsh
|
|
29
|
+
Log '停止动作完成'
|
|
30
|
+
}
|
|
31
|
+
'restart' {
|
|
32
|
+
Log '重启 DSH (来自系统托盘)'
|
|
33
|
+
Restart-Dsh
|
|
34
|
+
Log '重启动作完成'
|
|
35
|
+
}
|
|
36
|
+
'tui-start' {
|
|
37
|
+
Log '启动 TUI (来自系统托盘)'
|
|
38
|
+
Start-Tui
|
|
39
|
+
Log 'TUI 启动动作完成'
|
|
40
|
+
}
|
|
41
|
+
'tui-stop' {
|
|
42
|
+
Log '停止 TUI (来自系统托盘)'
|
|
43
|
+
Stop-Tui
|
|
44
|
+
Log 'TUI 停止动作完成'
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
} catch {
|
|
48
|
+
Log ("动作 {0} 失败: {1}" -f $Action, $_.Exception.Message)
|
|
49
|
+
exit 1
|
|
50
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# dsh 一体化控制台 —— 同时管理 dsh web (3080) 和黑洞模拟器 (8137)
|
|
2
|
+
# 用法: 双击 dsh-一体化菜单.lnk / dsh-all-menu.bat 进入交互菜单
|
|
3
|
+
# 命令行: dsh-all-menu.ps1 -Cmd all-start|all-stop|dsh-start|dsh-stop|bh-start|bh-stop|status|open
|
|
4
|
+
param([string]$Cmd)
|
|
5
|
+
$ErrorActionPreference = 'Stop'
|
|
6
|
+
chcp 65001 > $null
|
|
7
|
+
|
|
8
|
+
$DshMenu = "$PSScriptRoot\dsh-menu.ps1"
|
|
9
|
+
$BhMenu = "$PSScriptRoot\bh-menu.ps1"
|
|
10
|
+
$DshUrl = 'http://127.0.0.1:3080'
|
|
11
|
+
$BhUrl = 'http://127.0.0.1:8137'
|
|
12
|
+
|
|
13
|
+
function Test-Port([int]$port) {
|
|
14
|
+
$c = New-Object System.Net.Sockets.TcpClient
|
|
15
|
+
try { if ($c.BeginConnect('127.0.0.1', $port, $null, $null).AsyncWaitHandle.WaitOne(500) -and $c.Connected) { return $true } } catch {}
|
|
16
|
+
finally { $c.Close() }
|
|
17
|
+
return $false
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function StatusLine {
|
|
21
|
+
$d = if (Test-Port 3080) { "运行中: $DshUrl" } else { '未运行' }
|
|
22
|
+
$b = if (Test-Port 8137) { "运行中: $BhUrl" } else { '未运行' }
|
|
23
|
+
Write-Host " [dsh] $d" -ForegroundColor $(if (Test-Port 3080) { 'Green' } else { 'Red' })
|
|
24
|
+
Write-Host " [bh ] $b" -ForegroundColor $(if (Test-Port 8137) { 'Green' } else { 'Red' })
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function All-Start {
|
|
28
|
+
Write-Host '启动 dsh web ...' -ForegroundColor Cyan
|
|
29
|
+
& powershell -NoProfile -ExecutionPolicy Bypass -File $DshMenu -Cmd start | Out-Host
|
|
30
|
+
Write-Host '启动 黑洞模拟器 ...' -ForegroundColor Cyan
|
|
31
|
+
& powershell -NoProfile -ExecutionPolicy Bypass -File $BhMenu -Cmd start | Out-Host
|
|
32
|
+
}
|
|
33
|
+
function All-Stop {
|
|
34
|
+
Write-Host '停止 dsh web ...' -ForegroundColor Yellow
|
|
35
|
+
& powershell -NoProfile -ExecutionPolicy Bypass -File $DshMenu -Cmd stop | Out-Host
|
|
36
|
+
Write-Host '停止 黑洞模拟器 ...' -ForegroundColor Yellow
|
|
37
|
+
& powershell -NoProfile -ExecutionPolicy Bypass -File $BhMenu -Cmd stop | Out-Host
|
|
38
|
+
}
|
|
39
|
+
function Pause-Back { Read-Host '按回车键返回菜单' | Out-Null }
|
|
40
|
+
|
|
41
|
+
function Show-Detail {
|
|
42
|
+
Write-Host ' ── dsh web ──' -ForegroundColor Cyan
|
|
43
|
+
if (Test-Port 3080) {
|
|
44
|
+
$p = (Get-NetTCPConnection -LocalPort 3080 -State Listen | Select-Object -First 1).OwningProcess
|
|
45
|
+
Write-Host " 状态: 运行中 PID: $p" -ForegroundColor Green
|
|
46
|
+
Write-Host " 地址: $DshUrl" -ForegroundColor White
|
|
47
|
+
} else { Write-Host ' 状态: 未运行' -ForegroundColor Red }
|
|
48
|
+
Write-Host ' ── 黑洞模拟器 ──' -ForegroundColor Cyan
|
|
49
|
+
if (Test-Port 8137) {
|
|
50
|
+
$p = (Get-NetTCPConnection -LocalPort 8137 -State Listen | Select-Object -First 1).OwningProcess
|
|
51
|
+
Write-Host " 状态: 运行中 PID: $p" -ForegroundColor Green
|
|
52
|
+
Write-Host " 地址: $BhUrl" -ForegroundColor White
|
|
53
|
+
} else { Write-Host ' 状态: 未运行' -ForegroundColor Red }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if ($Cmd) {
|
|
57
|
+
switch ($Cmd.ToLower()) {
|
|
58
|
+
'all-start' { All-Start }
|
|
59
|
+
'all-stop' { All-Stop }
|
|
60
|
+
'dsh-start' { & powershell -NoProfile -ExecutionPolicy Bypass -File $DshMenu -Cmd start | Out-Host }
|
|
61
|
+
'dsh-stop' { & powershell -NoProfile -ExecutionPolicy Bypass -File $DshMenu -Cmd stop | Out-Host }
|
|
62
|
+
'bh-start' { & powershell -NoProfile -ExecutionPolicy Bypass -File $BhMenu -Cmd start | Out-Host }
|
|
63
|
+
'bh-stop' { & powershell -NoProfile -ExecutionPolicy Bypass -File $BhMenu -Cmd stop | Out-Host }
|
|
64
|
+
'status' { StatusLine }
|
|
65
|
+
'open' { if (Test-Port 3080) { Start-Process $DshUrl } else { Write-Host '[dsh] 未运行' -ForegroundColor Red }
|
|
66
|
+
if (Test-Port 8137) { Start-Process $BhUrl } else { Write-Host '[bh] 未运行' -ForegroundColor Red } }
|
|
67
|
+
default { Write-Output '用法: dsh-all-menu.ps1 -Cmd all-start|all-stop|dsh-start|dsh-stop|bh-start|bh-stop|status|open' }
|
|
68
|
+
}
|
|
69
|
+
exit
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
while ($true) {
|
|
73
|
+
Clear-Host
|
|
74
|
+
Write-Host ''
|
|
75
|
+
Write-Host ' ╔═══════════════════════════════════════╗' -ForegroundColor Cyan
|
|
76
|
+
Write-Host ' ║ dsh 一体化控制台 v1 ║' -ForegroundColor Cyan
|
|
77
|
+
Write-Host ' ╚═══════════════════════════════════════╝' -ForegroundColor Cyan
|
|
78
|
+
Write-Host ''
|
|
79
|
+
StatusLine
|
|
80
|
+
Write-Host ''
|
|
81
|
+
Write-Host ' [1] 全部启动 (dsh + 黑洞) 并打开浏览器' -ForegroundColor White
|
|
82
|
+
Write-Host ' [2] 全部停止' -ForegroundColor White
|
|
83
|
+
Write-Host ' [3] 只启动 dsh [4] 只停止 dsh' -ForegroundColor White
|
|
84
|
+
Write-Host ' [5] 只启动黑洞 [6] 只停止黑洞' -ForegroundColor White
|
|
85
|
+
Write-Host ' [7] 服务详情 (PID/端口)' -ForegroundColor White
|
|
86
|
+
Write-Host ' [8] 打开 dsh 页面 [9] 打开黑洞页面' -ForegroundColor White
|
|
87
|
+
Write-Host ' [0] 退出' -ForegroundColor White
|
|
88
|
+
Write-Host ''
|
|
89
|
+
$choice = Read-Host ' 请选择'
|
|
90
|
+
Write-Host ''
|
|
91
|
+
switch ($choice) {
|
|
92
|
+
'1' { All-Start; Pause-Back }
|
|
93
|
+
'2' { All-Stop; Pause-Back }
|
|
94
|
+
'3' { & powershell -NoProfile -ExecutionPolicy Bypass -File $DshMenu -Cmd start | Out-Host; Pause-Back }
|
|
95
|
+
'4' { & powershell -NoProfile -ExecutionPolicy Bypass -File $DshMenu -Cmd stop | Out-Host; Pause-Back }
|
|
96
|
+
'5' { & powershell -NoProfile -ExecutionPolicy Bypass -File $BhMenu -Cmd start | Out-Host; Pause-Back }
|
|
97
|
+
'6' { & powershell -NoProfile -ExecutionPolicy Bypass -File $BhMenu -Cmd stop | Out-Host; Pause-Back }
|
|
98
|
+
'7' { Show-Detail; Pause-Back }
|
|
99
|
+
'8' { if (Test-Port 3080) { Start-Process $DshUrl } else { Write-Host ' [dsh] 未运行, 请先启动' -ForegroundColor Red }
|
|
100
|
+
Pause-Back }
|
|
101
|
+
'9' { if (Test-Port 8137) { Start-Process $BhUrl } else { Write-Host ' [bh] 未运行, 请先启动' -ForegroundColor Red }
|
|
102
|
+
Pause-Back }
|
|
103
|
+
'0' { Write-Host ' 再见!' -ForegroundColor Cyan; exit }
|
|
104
|
+
default { Write-Host ' 无效选择, 请重试' -ForegroundColor Red; Start-Sleep -Milliseconds 800 }
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
@echo off
|
|
2
|
+
chcp 65001 >nul
|
|
3
|
+
rem DSH 控制台 - double click to open menu
|
|
4
|
+
rem prefer pwsh (PowerShell 7), fallback to powershell 5.1
|
|
5
|
+
where pwsh >nul 2>nul
|
|
6
|
+
if %errorlevel%==0 (
|
|
7
|
+
pwsh -NoProfile -ExecutionPolicy Bypass -File "%~dp0dsh-control.ps1"
|
|
8
|
+
) else (
|
|
9
|
+
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0dsh-control.ps1"
|
|
10
|
+
)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# DSH 控制台 —— 启动/重启 Web + 启动 TUI + 状态查看 + 系统托盘
|
|
2
|
+
# 部署:2026-08-26;双击桌面「DSH 控制台」或 dsh-control.bat
|
|
3
|
+
# 坑:本脚本由用户独立窗口运行(非 web 子进程),杀/重启 web 安全(PITFALLS §12.2)
|
|
4
|
+
# 2026-08-28: 启动/停止/重启/托盘逻辑抽到 dsh-lib.ps1(与系统托盘共享同一份代码)
|
|
5
|
+
$ErrorActionPreference = 'Stop'
|
|
6
|
+
chcp 65001 > $null
|
|
7
|
+
|
|
8
|
+
. (Join-Path $PSScriptRoot 'dsh-lib.ps1')
|
|
9
|
+
|
|
10
|
+
while ($true) {
|
|
11
|
+
Clear-Host
|
|
12
|
+
Write-Host ''
|
|
13
|
+
Write-Host ' ┌──────────────────────────────┐'
|
|
14
|
+
Write-Host ' │ DSH 控制台 │'
|
|
15
|
+
Write-Host ' ├──────────────────────────────┤'
|
|
16
|
+
Write-Host ' │ 1) 启动 / 打开 Web (3088) │'
|
|
17
|
+
Write-Host ' │ 2) 重启 Web │'
|
|
18
|
+
Write-Host ' │ 3) 启动 TUI(新终端窗口) │'
|
|
19
|
+
Write-Host ' │ 4) 查看状态 │'
|
|
20
|
+
Write-Host ' │ 5) 启动系统托盘 │'
|
|
21
|
+
Write-Host ' │ 0) 退出 │'
|
|
22
|
+
Write-Host ' └──────────────────────────────┘'
|
|
23
|
+
$c = Read-Host ' 请选择'
|
|
24
|
+
switch ($c) {
|
|
25
|
+
'1' { Start-Dsh }
|
|
26
|
+
'2' { Restart-Dsh }
|
|
27
|
+
'3' { Start-Tui }
|
|
28
|
+
'4' { Show-Status }
|
|
29
|
+
'5' {
|
|
30
|
+
if (Ensure-DshTray) {
|
|
31
|
+
Write-Host '[tray] 系统托盘已在运行(蓝鲸=运行中 / 灰鲸=未运行,右键可控制)' -ForegroundColor Green
|
|
32
|
+
} else {
|
|
33
|
+
Write-Host '[tray] 系统托盘已启动(蓝鲸=运行中 / 灰鲸=未运行,右键可控制)' -ForegroundColor Green
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
'0' { Write-Host ' 再见!'; break }
|
|
37
|
+
default { Write-Host " 无效选择: $c" -ForegroundColor Red }
|
|
38
|
+
}
|
|
39
|
+
if ($c -ne '0') { Write-Host ''; Read-Host ' 按回车返回菜单' }
|
|
40
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# dsh-lib.ps1 —— DSH 启动/停止/重启/托盘 公共库
|
|
2
|
+
# 被 dsh-control.ps1 (控制台) 与 dsh-tray.ps1 (系统托盘) 点源引用 (dot-source)
|
|
3
|
+
# 所有路径/端口/进程管理逻辑的单一事实来源: 修改此处, 两个入口同时生效
|
|
4
|
+
$ErrorActionPreference = 'Stop'
|
|
5
|
+
|
|
6
|
+
$DSH_CMD = 'D:\Program Files (x86)\dsh\dsh.cmd'
|
|
7
|
+
$NODE = 'C:\Program Files\nodejs\node.exe'
|
|
8
|
+
$BIN = 'D:\Program Files (x86)\dsh\node_modules\@deepseek-ai\dsh\lib\bin.js'
|
|
9
|
+
$WORKDIR = 'D:\Program Files (x86)\dsh'
|
|
10
|
+
$PORT = 3088
|
|
11
|
+
$URL = "http://127.0.0.1:$PORT"
|
|
12
|
+
$LOG_DIR = "$env:USERPROFILE\.dsh\logs"
|
|
13
|
+
$WT = "$env:LOCALAPPDATA\Microsoft\WindowsApps\wt.exe"
|
|
14
|
+
$PWSH = 'D:\Program Files (x86)\PowerShell-7.6.4\pwsh.exe'
|
|
15
|
+
$TRAY_VERSION = '1.0.0' # 托盘版本 (与 npm 包版本同步, 用于日志/排查)
|
|
16
|
+
$TRAY_PKG = 'dsh-windows-tray' # npm 包名 (托盘自更新)
|
|
17
|
+
$TRAY_SCRIPT = Join-Path $PSScriptRoot 'dsh-tray.ps1'
|
|
18
|
+
|
|
19
|
+
function Test-Port([int]$port) {
|
|
20
|
+
$client = New-Object System.Net.Sockets.TcpClient
|
|
21
|
+
try {
|
|
22
|
+
$async = $client.BeginConnect('127.0.0.1', $port, $null, $null)
|
|
23
|
+
if ($async.AsyncWaitHandle.WaitOne(500) -and $client.Connected) { return $true }
|
|
24
|
+
return $false
|
|
25
|
+
} catch { return $false }
|
|
26
|
+
finally { $client.Close() }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function Get-WebPid {
|
|
30
|
+
@((Get-NetTCPConnection -LocalPort $PORT -State Listen -ErrorAction SilentlyContinue).OwningProcess | Select-Object -Unique)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function Start-Dsh {
|
|
34
|
+
param([switch]$OpenBrowser = $true)
|
|
35
|
+
$trayStarted = -not (Ensure-DshTray) # 先保证系统托盘在(幂等)
|
|
36
|
+
$pids = @(Get-WebPid)
|
|
37
|
+
if ($pids.Count -gt 0) {
|
|
38
|
+
Write-Host "[dsh] 已在运行: $URL (PID $($pids -join ','))" -ForegroundColor Green
|
|
39
|
+
if ($trayStarted) { Write-Host '[tray] 系统托盘已自动启动(右键可控制 DSH)' -ForegroundColor DarkGray }
|
|
40
|
+
if ($OpenBrowser) { Start-Process $URL }
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
Write-Host "[dsh] 启动中 (端口 $PORT)..."
|
|
44
|
+
$out = Join-Path $LOG_DIR 'web-menu.out.log'
|
|
45
|
+
$err = Join-Path $LOG_DIR 'web-menu.err.log'
|
|
46
|
+
Start-Process -FilePath $NODE -ArgumentList "`"$BIN`"",'web','--host','127.0.0.1','--port',"$PORT",'--no-open' `
|
|
47
|
+
-WorkingDirectory $WORKDIR -WindowStyle Hidden `
|
|
48
|
+
-RedirectStandardOutput $out -RedirectStandardError $err | Out-Null
|
|
49
|
+
for ($i = 0; $i -lt 12; $i++) {
|
|
50
|
+
Start-Sleep -Seconds 1
|
|
51
|
+
if (Test-Port $PORT) { break }
|
|
52
|
+
}
|
|
53
|
+
if (Test-Port $PORT) {
|
|
54
|
+
Write-Host "[dsh] 启动成功: $URL" -ForegroundColor Green
|
|
55
|
+
if ($trayStarted) { Write-Host '[tray] 系统托盘已自动启动(右键可控制 DSH)' -ForegroundColor DarkGray }
|
|
56
|
+
Start-Sleep -Seconds 2
|
|
57
|
+
if ($OpenBrowser) { Start-Process $URL }
|
|
58
|
+
} else {
|
|
59
|
+
Write-Host "[dsh] 启动失败: 12 秒内端口 $PORT 未监听" -ForegroundColor Red
|
|
60
|
+
Write-Host "[dsh] 错误日志: $err(末尾 10 行)"
|
|
61
|
+
Get-Content $err -Tail 10 -ErrorAction SilentlyContinue
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function Stop-Dsh {
|
|
66
|
+
$pids = @(Get-WebPid)
|
|
67
|
+
if ($pids.Count -eq 0) { Write-Host '[dsh] 未在运行'; return }
|
|
68
|
+
foreach ($p in $pids) {
|
|
69
|
+
Write-Host "[dsh] 停止进程 $p"
|
|
70
|
+
& taskkill /PID $p /T /F 2>$null | Out-Null
|
|
71
|
+
}
|
|
72
|
+
for ($i = 0; $i -lt 10; $i++) {
|
|
73
|
+
Start-Sleep -Seconds 1
|
|
74
|
+
if (-not (Test-Port $PORT)) { break }
|
|
75
|
+
}
|
|
76
|
+
if (Test-Port $PORT) { Write-Host '[dsh] 端口未释放,请稍后重试' -ForegroundColor Red }
|
|
77
|
+
else { Write-Host '[dsh] 已停止' -ForegroundColor Yellow }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function Restart-Dsh {
|
|
81
|
+
Stop-Dsh
|
|
82
|
+
Start-Sleep -Seconds 1
|
|
83
|
+
Start-Dsh
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function Start-Tui {
|
|
87
|
+
$existing = @(Get-TuiProcesses)
|
|
88
|
+
if ($existing.Count -gt 0) {
|
|
89
|
+
Write-Host "[tui] TUI 已在运行 (PID $($existing.ProcessId -join ',')), 不重复启动" -ForegroundColor Yellow
|
|
90
|
+
return
|
|
91
|
+
}
|
|
92
|
+
$cmdLine = "& '$DSH_CMD' --profile dsh-tui"
|
|
93
|
+
if (Test-Path $WT) {
|
|
94
|
+
$args = "new-tab --title `"DSH TUI`" pwsh -NoExit -Command `"$cmdLine`""
|
|
95
|
+
Start-Process $WT -ArgumentList $args
|
|
96
|
+
Write-Host '[tui] 已在 Windows Terminal 新标签页启动 DSH TUI' -ForegroundColor Green
|
|
97
|
+
} elseif (Test-Path $PWSH) {
|
|
98
|
+
Start-Process $PWSH -ArgumentList '-NoExit','-Command',$cmdLine
|
|
99
|
+
Write-Host '[tui] 已在新 PowerShell 窗口启动 DSH TUI' -ForegroundColor Green
|
|
100
|
+
} else {
|
|
101
|
+
Start-Process 'powershell' -ArgumentList '-NoExit','-Command',$cmdLine
|
|
102
|
+
Write-Host '[tui] 已在新 PowerShell 窗口启动 DSH TUI' -ForegroundColor Green
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function Get-TuiProcesses {
|
|
107
|
+
Get-CimInstance Win32_Process -Filter "Name='node.exe'" -ErrorAction SilentlyContinue |
|
|
108
|
+
Where-Object { $_.CommandLine -match 'dsh-tui' }
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function Stop-Tui {
|
|
112
|
+
$tui = @(Get-TuiProcesses)
|
|
113
|
+
if ($tui.Count -eq 0) { Write-Host '[tui] TUI: 未在运行'; return }
|
|
114
|
+
foreach ($p in $tui) {
|
|
115
|
+
Write-Host "[tui] 停止 TUI 进程 $($p.ProcessId)"
|
|
116
|
+
& taskkill /PID $p.ProcessId /T /F 2>$null | Out-Null
|
|
117
|
+
}
|
|
118
|
+
Start-Sleep -Seconds 1
|
|
119
|
+
if (@(Get-TuiProcesses).Count -gt 0) { Write-Host '[tui] TUI 进程未完全退出,请稍后重试' -ForegroundColor Red }
|
|
120
|
+
else { Write-Host '[tui] TUI 已停止' -ForegroundColor Yellow }
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function Show-Status {
|
|
124
|
+
$pids = @(Get-WebPid)
|
|
125
|
+
if ($pids.Count -gt 0) {
|
|
126
|
+
Write-Host "[dsh] Web: 运行中 $URL (PID $($pids -join ','))" -ForegroundColor Green
|
|
127
|
+
} else {
|
|
128
|
+
Write-Host '[dsh] Web: 未运行' -ForegroundColor Yellow
|
|
129
|
+
}
|
|
130
|
+
$tui = @(Get-TuiProcesses)
|
|
131
|
+
if ($tui.Count -gt 0) {
|
|
132
|
+
Write-Host "[tui] TUI: 运行中 (PID $($tui.ProcessId -join ','))" -ForegroundColor Green
|
|
133
|
+
} else {
|
|
134
|
+
Write-Host '[tui] TUI: 未运行(按 3 启动)' -ForegroundColor Yellow
|
|
135
|
+
}
|
|
136
|
+
$tray = @(Get-TrayProcesses)
|
|
137
|
+
if ($tray.Count -gt 0) {
|
|
138
|
+
Write-Host "[tray] 托盘: 运行中 (PID $($tray.ProcessId -join ','))" -ForegroundColor Green
|
|
139
|
+
} else {
|
|
140
|
+
Write-Host '[tray] 托盘: 未运行(按 5 启动)' -ForegroundColor Yellow
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
# ---- 系统托盘 ----
|
|
145
|
+
|
|
146
|
+
function Get-TrayProcesses {
|
|
147
|
+
Get-CimInstance Win32_Process -ErrorAction SilentlyContinue | Where-Object {
|
|
148
|
+
($_.Name -eq 'pwsh.exe' -or $_.Name -eq 'powershell.exe') -and
|
|
149
|
+
$_.CommandLine -and $_.CommandLine -match 'dsh-tray\.ps1' -and
|
|
150
|
+
$_.ProcessId -ne $PID
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
# 确保托盘在运行(幂等): 已在运行返回 $true; 刚拉起返回 $false
|
|
155
|
+
function Ensure-DshTray {
|
|
156
|
+
$existing = @(Get-TrayProcesses)
|
|
157
|
+
if ($existing.Count -gt 0) { return $true }
|
|
158
|
+
$args = @('-NoProfile','-ExecutionPolicy','Bypass','-WindowStyle','Hidden','-File',$TRAY_SCRIPT)
|
|
159
|
+
if (Test-Path $PWSH) {
|
|
160
|
+
Start-Process -FilePath $PWSH -ArgumentList $args -WindowStyle Hidden | Out-Null
|
|
161
|
+
} else {
|
|
162
|
+
Start-Process -FilePath 'powershell.exe' -ArgumentList $args -WindowStyle Hidden | Out-Null
|
|
163
|
+
}
|
|
164
|
+
return $false
|
|
165
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
@echo off
|
|
2
|
+
chcp 65001 >nul
|
|
3
|
+
rem dsh (DeepSeek Harness) launcher - double click to open menu
|
|
4
|
+
rem prefer pwsh (PowerShell 7), fallback to powershell 5.1
|
|
5
|
+
where pwsh >nul 2>nul
|
|
6
|
+
if %errorlevel%==0 (
|
|
7
|
+
pwsh -NoProfile -ExecutionPolicy Bypass -File "%~dp0dsh-menu.ps1"
|
|
8
|
+
) else (
|
|
9
|
+
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0dsh-menu.ps1"
|
|
10
|
+
)
|
|
11
|
+
pause
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# dsh (DeepSeek Harness) 启动器 —— 公司机部署 2026-08-14
|
|
2
|
+
# 用法:
|
|
3
|
+
# 交互菜单: 双击 dsh-menu.bat
|
|
4
|
+
# 非交互: dsh-menu.ps1 -Cmd start|stop|status
|
|
5
|
+
# 坑: 不要在本脚本内对 Start-Process 用 -RedirectStandardOutput
|
|
6
|
+
# (父进程 stdout 被重定向时挂起, 见 LEARNED #53)
|
|
7
|
+
param(
|
|
8
|
+
[string]$Cmd
|
|
9
|
+
)
|
|
10
|
+
$ErrorActionPreference = 'Stop'
|
|
11
|
+
|
|
12
|
+
$DSH_CMD = 'D:\Program Files (x86)\dsh\dsh.cmd'
|
|
13
|
+
$PORT = 3080
|
|
14
|
+
$URL = 'http://127.0.0.1:3080'
|
|
15
|
+
|
|
16
|
+
function Test-Port([int]$port) {
|
|
17
|
+
$client = New-Object System.Net.Sockets.TcpClient
|
|
18
|
+
try {
|
|
19
|
+
$async = $client.BeginConnect('127.0.0.1', $port, $null, $null)
|
|
20
|
+
if ($async.AsyncWaitHandle.WaitOne(500) -and $client.Connected) { return $true }
|
|
21
|
+
return $false
|
|
22
|
+
} catch { return $false }
|
|
23
|
+
finally { $client.Close() }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function Start-Dsh {
|
|
27
|
+
if (Test-Port $PORT) {
|
|
28
|
+
Write-Output "[dsh] 端口 $PORT 已监听, 服务已在运行, 不重复启动"
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
Start-Process -FilePath $DSH_CMD -ArgumentList 'web' -WindowStyle Hidden
|
|
32
|
+
Start-Sleep -Seconds 6
|
|
33
|
+
if (Test-Port $PORT) {
|
|
34
|
+
Write-Output "[dsh] 启动成功: $URL"
|
|
35
|
+
} else {
|
|
36
|
+
Start-Sleep -Seconds 5
|
|
37
|
+
if (Test-Port $PORT) {
|
|
38
|
+
Write-Output "[dsh] 启动成功: $URL"
|
|
39
|
+
} else {
|
|
40
|
+
Write-Output "[dsh] 启动失败: 端口 $PORT 未监听, 请手工运行 dsh web 查看报错"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function Stop-Dsh {
|
|
46
|
+
$pids = @((Get-NetTCPConnection -LocalPort $PORT -State Listen -ErrorAction SilentlyContinue).OwningProcess | Select-Object -Unique)
|
|
47
|
+
if ($pids.Count -eq 0) {
|
|
48
|
+
Write-Output '[dsh] 服务未在运行'
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
foreach ($p in $pids) {
|
|
52
|
+
Write-Output "[dsh] 停止进程 $p"
|
|
53
|
+
& taskkill /PID $p /T /F 2>$null | Out-Null
|
|
54
|
+
}
|
|
55
|
+
Start-Sleep -Seconds 2
|
|
56
|
+
if (Test-Port $PORT) { Write-Output '[dsh] 停止失败: 端口仍在监听' }
|
|
57
|
+
else { Write-Output '[dsh] 已停止' }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function Show-Status {
|
|
61
|
+
if (Test-Port $PORT) { Write-Output "[dsh] 运行中: $URL" }
|
|
62
|
+
else { Write-Output '[dsh] 未运行' }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
# ---------- 非交互模式 ----------
|
|
66
|
+
if ($Cmd) {
|
|
67
|
+
switch ($Cmd.ToLower()) {
|
|
68
|
+
'start' { Start-Dsh }
|
|
69
|
+
'stop' { Stop-Dsh }
|
|
70
|
+
'status' { Show-Status }
|
|
71
|
+
default { Write-Output '用法: dsh-menu.ps1 -Cmd start|stop|status' }
|
|
72
|
+
}
|
|
73
|
+
exit
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
# ---------- 交互菜单 ----------
|
|
77
|
+
while ($true) {
|
|
78
|
+
Write-Output ''
|
|
79
|
+
Write-Output '======== dsh 启动器 ========'
|
|
80
|
+
Show-Status
|
|
81
|
+
Write-Output '[1] 启动并打开浏览器'
|
|
82
|
+
Write-Output '[2] 停止'
|
|
83
|
+
Write-Output '[3] 状态'
|
|
84
|
+
Write-Output '[0] 退出'
|
|
85
|
+
$choice = Read-Host '请选择'
|
|
86
|
+
switch ($choice) {
|
|
87
|
+
'1' { Start-Dsh; Start-Process $URL; }
|
|
88
|
+
'2' { Stop-Dsh }
|
|
89
|
+
'3' { Show-Status }
|
|
90
|
+
'0' { exit }
|
|
91
|
+
default { Write-Output '无效选择' }
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
@echo off
|
|
2
|
+
chcp 65001 >nul
|
|
3
|
+
rem DSH 系统托盘 - double click to start (blue whale = running, gray = stopped)
|
|
4
|
+
rem prefer pwsh (PowerShell 7), fallback to powershell 5.1
|
|
5
|
+
where pwsh >nul 2>nul
|
|
6
|
+
if %errorlevel%==0 (
|
|
7
|
+
start "" pwsh -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File "%~dp0dsh-tray.ps1"
|
|
8
|
+
) else (
|
|
9
|
+
start "" powershell -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File "%~dp0dsh-tray.ps1"
|
|
10
|
+
)
|