dsh-multi-chat 0.4.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 +163 -0
- package/bin/dsh-multi-chat.mjs +417 -0
- package/package.json +45 -0
- package/patches/multi-wall.yml +10 -0
- package/plugin/dsh-client-ui-multi-wall/README.md +45 -0
- package/plugin/dsh-client-ui-multi-wall/lib/client.js +755 -0
- package/plugin/dsh-client-ui-multi-wall/lib/index.js +790 -0
- package/plugin/dsh-client-ui-multi-wall/lib/invariant.js +27 -0
- package/plugin/dsh-client-ui-multi-wall/lib/types/client/WallToggle.d.ts +10 -0
- package/plugin/dsh-client-ui-multi-wall/lib/types/client/WallView.d.ts +16 -0
- package/plugin/dsh-client-ui-multi-wall/lib/types/client/index.d.ts +39 -0
- package/plugin/dsh-client-ui-multi-wall/lib/types/client/locales.d.ts +94 -0
- package/plugin/dsh-client-ui-multi-wall/lib/types/client/store.d.ts +27 -0
- package/plugin/dsh-client-ui-multi-wall/lib/types/client/wall-injected.d.ts +53 -0
- package/plugin/dsh-client-ui-multi-wall/lib/types/index.d.ts +67 -0
- package/plugin/dsh-client-ui-multi-wall/lib/types/invariant.d.ts +17 -0
- package/plugin/dsh-client-ui-multi-wall/package.json +84 -0
- package/scripts/gateway-hidden.vbs +35 -0
- package/scripts/gateway-start.ps1 +21 -0
- package/scripts/gateway-stop.ps1 +34 -0
- package/scripts/gateway.mjs +511 -0
- package/scripts/install-plugin.ps1 +65 -0
- package/scripts/start-multi.ps1 +180 -0
- package/scripts/stop-multi.ps1 +42 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
#requires -Version 5.1
|
|
2
|
+
<#
|
|
3
|
+
.SYNOPSIS
|
|
4
|
+
启动多个 DSH web 实例(多窗口墙直接内嵌在官方界面里,无需独立服务器)。
|
|
5
|
+
|
|
6
|
+
.DESCRIPTION
|
|
7
|
+
依次启动 `dsh web --port <n>`(默认 3080..3083 共 4 个实例)。每个实例的
|
|
8
|
+
侧边栏底部都有「多窗口墙」按钮(安装 plugin/dsh-client-ui-multi-wall 后),
|
|
9
|
+
点击即可在官方界面内看到所有实例并排显示。所有子进程 PID 记录到状态文件,
|
|
10
|
+
供 stop-multi.ps1 一键停止。
|
|
11
|
+
|
|
12
|
+
官方 CLI 出于安全禁止 `--host 0.0.0.0`(会向网络暴露远程代码执行),因此
|
|
13
|
+
手机/远程访问请加 -Remote:脚本会在每个实例前启动一个带令牌认证的网关
|
|
14
|
+
(scripts/gateway.mjs),网关监听 0.0.0.0,实例本身保持仅本机回环。手机在
|
|
15
|
+
同一内网打开 http://<局域网IP>:<网关端口>/,输入令牌即可登录使用。
|
|
16
|
+
|
|
17
|
+
.PARAMETER Ports
|
|
18
|
+
逗号分隔的端口列表,例如 -Ports "3080,3081,3082,3084"。
|
|
19
|
+
默认 "3080,3081,3082,3083"。
|
|
20
|
+
|
|
21
|
+
.PARAMETER Patch
|
|
22
|
+
启用多窗口墙的 patch 文件(默认 patches/multi-wall.yml);若已把 insert
|
|
23
|
+
写进 profile 的 cordis.patch.yml,可传 $null 跳过。
|
|
24
|
+
|
|
25
|
+
.PARAMETER Remote
|
|
26
|
+
为每个实例启动一个带令牌认证的网关(0.0.0.0),用于手机/远程访问。
|
|
27
|
+
|
|
28
|
+
.PARAMETER Token
|
|
29
|
+
网关登录令牌;-Remote 下必填(缺省则自动生成并打印)。
|
|
30
|
+
|
|
31
|
+
.PARAMETER GatewayPorts
|
|
32
|
+
网关外部端口(逗号分隔,数量须与实例一致);默认 = 实例端口 + 5000,
|
|
33
|
+
若落在 Windows 排除端口段(Hyper-V/WinNAT)则自动顺延。
|
|
34
|
+
|
|
35
|
+
.PARAMETER TlsCert / TlsKey
|
|
36
|
+
提供 PEM 证书与私钥时,网关以 HTTPS(加密)对外服务。
|
|
37
|
+
|
|
38
|
+
.PARAMETER NoOpen
|
|
39
|
+
不自动打开浏览器。
|
|
40
|
+
|
|
41
|
+
.EXAMPLE
|
|
42
|
+
.\start-multi.ps1 -Ports "3080,3081,3082,3084"
|
|
43
|
+
.\start-multi.ps1 -Ports "3080,3081" -NoOpen
|
|
44
|
+
.\start-multi.ps1 -Ports "3080,3081" -Remote -Token "my-secret"
|
|
45
|
+
.\start-multi.ps1 -Ports "3080" -Remote -Token "s3cret" -TlsCert cert.pem -TlsKey key.pem
|
|
46
|
+
#>
|
|
47
|
+
param(
|
|
48
|
+
[string]$Ports = "3080,3081,3082,3083",
|
|
49
|
+
[string]$Patch = "",
|
|
50
|
+
[switch]$Remote,
|
|
51
|
+
[string]$Token = "",
|
|
52
|
+
[string]$GatewayPorts = "",
|
|
53
|
+
[string]$TlsCert = "",
|
|
54
|
+
[string]$TlsKey = "",
|
|
55
|
+
[string]$Name = "DSH",
|
|
56
|
+
[switch]$NoOpen
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
$ErrorActionPreference = "Stop"
|
|
60
|
+
$root = Split-Path -Parent $PSScriptRoot
|
|
61
|
+
$stateFile = Join-Path $root ".wall-pids.json"
|
|
62
|
+
|
|
63
|
+
# 1) 解析端口列表
|
|
64
|
+
$portList = @($Ports -split "," | ForEach-Object { $_.Trim() } | Where-Object { $_ -match '^\d+$' })
|
|
65
|
+
if ($portList.Count -eq 0) { throw "没有有效的端口列表:$Ports" }
|
|
66
|
+
|
|
67
|
+
# 2) 解析 dsh 的真实 JS bin(npm 全局装的 dsh 在 PATH 里是 .ps1/.cmd shim,
|
|
68
|
+
# node 不能直接执行 shim,必须解析出真正的 bin.js)
|
|
69
|
+
function Get-DshJsBin {
|
|
70
|
+
$cmd = Get-Command dsh -ErrorAction SilentlyContinue
|
|
71
|
+
if (-not $cmd) { return $null }
|
|
72
|
+
$source = $cmd.Source
|
|
73
|
+
if ($source -match 'bin\.js$') { return $source }
|
|
74
|
+
if ($source -match '\.ps1$|\.cmd$') {
|
|
75
|
+
$content = Get-Content $source -Raw
|
|
76
|
+
if ($content -match 'node_modules[\\/]@deepseek-ai[\\/]dsh[\\/]([^\s"''%]+)') {
|
|
77
|
+
$rel = $Matches[1] -replace '/', '\'
|
|
78
|
+
$candidate = Join-Path (Split-Path $source) "node_modules\@deepseek-ai\dsh\$rel"
|
|
79
|
+
if (Test-Path $candidate) { return $candidate }
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return $null
|
|
83
|
+
}
|
|
84
|
+
$dshJs = Get-DshJsBin
|
|
85
|
+
if (-not $dshJs) {
|
|
86
|
+
Write-Host "警告:无法解析 dsh 的 JS bin,将尝试直接调用 dsh 命令。"
|
|
87
|
+
$dshCmd = Get-Command dsh -ErrorAction SilentlyContinue
|
|
88
|
+
if (-not $dshCmd) { throw "未找到 dsh 命令,请先安装 @deepseek-ai/dsh(npm i -g @deepseek-ai/dsh)" }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
# 3) 远程模式:令牌与网关端口
|
|
92
|
+
if ($Remote -and [string]::IsNullOrEmpty($Token)) {
|
|
93
|
+
$Token = -join ((48..57) + (97..122) | Get-Random -Count 24 | ForEach-Object { [char]$_ })
|
|
94
|
+
Write-Host "已生成网关令牌:$Token (请保存!)"
|
|
95
|
+
}
|
|
96
|
+
$gwPortList = @()
|
|
97
|
+
if ($Remote) {
|
|
98
|
+
if (-not [string]::IsNullOrEmpty($GatewayPorts)) {
|
|
99
|
+
$gwPortList = @($GatewayPorts -split "," | ForEach-Object { $_.Trim() } | Where-Object { $_ -match '^\d+$' })
|
|
100
|
+
if ($gwPortList.Count -lt $portList.Count) { throw "GatewayPorts 数量($($gwPortList.Count))少于实例数量($($portList.Count))" }
|
|
101
|
+
} else {
|
|
102
|
+
# 默认实例端口 + 5000,避开 Windows 排除端口段与已占用端口
|
|
103
|
+
$excluded = @()
|
|
104
|
+
netsh interface ipv4 show excludedportrange protocol=tcp 2>$null |
|
|
105
|
+
Select-String '^\s*\d+\s+\d+\s*$' |
|
|
106
|
+
ForEach-Object {
|
|
107
|
+
if ($_ -match '^\s*(\d+)\s+(\d+)\s*$') { $excluded += ,@([int]$Matches[1], [int]$Matches[2]) }
|
|
108
|
+
}
|
|
109
|
+
foreach ($p in $portList) {
|
|
110
|
+
$candidate = [int]$p + 5000
|
|
111
|
+
for (; $candidate -lt [int]$p + 5500; $candidate++) {
|
|
112
|
+
$inUse = Get-NetTCPConnection -LocalPort $candidate -State Listen -ErrorAction SilentlyContinue
|
|
113
|
+
if ($inUse) { continue }
|
|
114
|
+
$blocked = $false
|
|
115
|
+
foreach ($range in $excluded) {
|
|
116
|
+
if ($candidate -ge $range[0] -and $candidate -le $range[1]) { $blocked = $true; break }
|
|
117
|
+
}
|
|
118
|
+
if (-not $blocked) { break }
|
|
119
|
+
}
|
|
120
|
+
$gwPortList += $candidate
|
|
121
|
+
if ($candidate -ne ([int]$p + 5000)) { Write-Host "网关端口 $([int]$p + 5000) 不可用,改用 $candidate" }
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
# 4) 启动每个 dsh web 实例
|
|
127
|
+
$pids = @()
|
|
128
|
+
$started = @()
|
|
129
|
+
$gateways = @()
|
|
130
|
+
foreach ($p in $portList) {
|
|
131
|
+
if ($dshJs) {
|
|
132
|
+
$args = @($dshJs, "web", "--port", $p)
|
|
133
|
+
$proc = Start-Process -FilePath (Get-Command node).Source -ArgumentList $args -PassThru -WindowStyle Hidden
|
|
134
|
+
} else {
|
|
135
|
+
$args = @("web", "--port", $p)
|
|
136
|
+
$proc = Start-Process -FilePath (Get-Command dsh).Source -ArgumentList $args -PassThru -WindowStyle Hidden
|
|
137
|
+
}
|
|
138
|
+
$pids += $proc.Id
|
|
139
|
+
$started += $p
|
|
140
|
+
Write-Host "dsh web --port $p (pid $($proc.Id))"
|
|
141
|
+
Start-Sleep -Milliseconds 800
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
# 5) 远程模式:每个实例前启动一个带令牌认证的网关
|
|
145
|
+
if ($Remote) {
|
|
146
|
+
$gatewayScript = Join-Path $root "scripts\gateway.mjs"
|
|
147
|
+
if (-not (Test-Path $gatewayScript)) { throw "未找到网关脚本:$gatewayScript" }
|
|
148
|
+
for ($i = 0; $i -lt $portList.Count; $i++) {
|
|
149
|
+
$gArgs = @($gatewayScript, "--target", "127.0.0.1:$($portList[$i])", "--listen", "0.0.0.0:$($gwPortList[$i])", "--token", $Token, "--name", $Name)
|
|
150
|
+
if ($TlsCert -and $TlsKey) { $gArgs += @("--tls-cert", $TlsCert, "--tls-key", $TlsKey) }
|
|
151
|
+
$gwProc = Start-Process -FilePath (Get-Command node).Source -ArgumentList $gArgs -PassThru -WindowStyle Hidden
|
|
152
|
+
$gateways += ,@{ port = [int]$portList[$i]; gatewayPort = [int]$gwPortList[$i]; pid = $gwProc.Id }
|
|
153
|
+
Write-Host "网关 :$($gwPortList[$i]) -> 127.0.0.1:$($portList[$i]) (pid $($gwProc.Id))"
|
|
154
|
+
Start-Sleep -Milliseconds 300
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
# 6) 记录 PID 状态
|
|
159
|
+
@{ pid = $pids; ports = $started; gateways = $gateways; startedAt = (Get-Date).ToString("o") } | ConvertTo-Json -Depth 4 | Set-Content -Path $stateFile -Encoding UTF8
|
|
160
|
+
Write-Host "状态已保存到 $stateFile"
|
|
161
|
+
|
|
162
|
+
# 7) 打印手机/远程访问地址 + 打开第一个实例
|
|
163
|
+
if ($Remote) {
|
|
164
|
+
$ips = @(Get-NetIPAddress -AddressFamily IPv4 -ErrorAction SilentlyContinue | Where-Object { -not $_.IPAddress.StartsWith("127.") -and $_.PrefixOrigin -ne "WellKnown" } | Select-Object -ExpandProperty IPAddress -Unique)
|
|
165
|
+
if ($ips.Count -eq 0) { $ips = @("localhost") }
|
|
166
|
+
$scheme = "http"
|
|
167
|
+
if ($TlsCert -and $TlsKey) { $scheme = "https" }
|
|
168
|
+
Write-Host ""
|
|
169
|
+
Write-Host "手机/远程访问(需输入令牌):"
|
|
170
|
+
for ($i = 0; $i -lt $gateways.Count; $i++) {
|
|
171
|
+
$g = $gateways[$i]
|
|
172
|
+
$urls = ($ips | ForEach-Object { "$scheme`://$_`:$($g.gatewayPort)/" }) -join " "
|
|
173
|
+
Write-Host " $urls -> dsh :$($g.port)"
|
|
174
|
+
}
|
|
175
|
+
Write-Host " 令牌:$Token"
|
|
176
|
+
if (-not $NoOpen) { Start-Process "$scheme`://127.0.0.1`:$($gwPortList[0])" }
|
|
177
|
+
} elseif (-not $NoOpen) {
|
|
178
|
+
Start-Process "http://127.0.0.1:$($started[0])"
|
|
179
|
+
}
|
|
180
|
+
Write-Host "完成:$($started -join ', ')。每个实例侧边栏底部都有「多窗口墙」按钮。"
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#requires -Version 5.1
|
|
2
|
+
<#
|
|
3
|
+
.SYNOPSIS
|
|
4
|
+
停止 start-multi.ps1 启动的全部进程(多个 dsh web 实例 + 认证网关)。
|
|
5
|
+
|
|
6
|
+
.DESCRIPTION
|
|
7
|
+
读取 .wall-pids.json 中的 PID 列表(含 gateways 里的网关 PID),逐个停止;
|
|
8
|
+
结束后删除状态文件。只停止本脚本记录过的进程,不会误杀其它 dsh 实例。
|
|
9
|
+
|
|
10
|
+
.EXAMPLE
|
|
11
|
+
.\stop-multi.ps1
|
|
12
|
+
#>
|
|
13
|
+
param()
|
|
14
|
+
|
|
15
|
+
$ErrorActionPreference = "Stop"
|
|
16
|
+
$root = Split-Path -Parent $PSScriptRoot
|
|
17
|
+
$stateFile = Join-Path $root ".wall-pids.json"
|
|
18
|
+
|
|
19
|
+
if (-not (Test-Path $stateFile)) {
|
|
20
|
+
Write-Host "没有找到状态文件 $stateFile —— 可能尚未通过 start-multi.ps1 启动。"
|
|
21
|
+
exit 0
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
$state = Get-Content $stateFile -Raw | ConvertFrom-Json
|
|
25
|
+
$stopped = 0
|
|
26
|
+
|
|
27
|
+
$gatewayPids = @()
|
|
28
|
+
foreach ($g in @($state.gateways)) { $gatewayPids += [int]$g.pid }
|
|
29
|
+
foreach ($id in @($state.pid) + $gatewayPids) {
|
|
30
|
+
$proc = Get-Process -Id $id -ErrorAction SilentlyContinue
|
|
31
|
+
if ($proc) {
|
|
32
|
+
Stop-Process -Id $id -Force -ErrorAction SilentlyContinue
|
|
33
|
+
$stopped++
|
|
34
|
+
Write-Host "已停止 pid $id"
|
|
35
|
+
} else {
|
|
36
|
+
Write-Host "pid $id 已不存在,跳过"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
Remove-Item $stateFile -Force -ErrorAction SilentlyContinue
|
|
41
|
+
$ports = @($state.ports) -join ", "
|
|
42
|
+
Write-Host "已停止 $stopped 个进程(dsh 实例 $ports + 认证网关)。"
|