dsh-codex-subscription 1.0.4 → 1.0.6

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/dsh-codex.ps1 DELETED
@@ -1,917 +0,0 @@
1
- [CmdletBinding()]
2
- param(
3
- [ValidateSet('Install', 'Update', 'Uninstall')]
4
- [string] $Action = 'Install',
5
-
6
- [ValidatePattern('^[A-Za-z0-9._-]+$')]
7
- [string] $Profile = 'web',
8
-
9
- [string] $PortableRoot,
10
-
11
- [string] $CommandRoot,
12
-
13
- [switch] $NoModifyPath,
14
-
15
- [switch] $Managed,
16
-
17
- [switch] $SkipSelfUpdate,
18
-
19
- [switch] $DryRun
20
- )
21
-
22
- $ErrorActionPreference = 'Stop'
23
- Set-StrictMode -Version 2.0
24
- $Utf8NoBom = New-Object System.Text.UTF8Encoding($false)
25
- [Console]::OutputEncoding = $Utf8NoBom
26
- $OutputEncoding = $Utf8NoBom
27
-
28
- if ($Managed -and -not $PSBoundParameters.ContainsKey('Action')) {
29
- Write-Host 'Usage: dsh-codex <install|update|uninstall>'
30
- exit 0
31
- }
32
-
33
- $PackageName = 'dsh-codex-subscription'
34
- $LegacyPackageName = '@wsl043/dsh-codex-subscription'
35
- $ManagerScriptName = 'dsh-codex-manager.ps1'
36
- $LegacyManagerScriptName = 'dsh-codex.ps1'
37
- $ManagerShimName = 'dsh-codex.cmd'
38
- $ManagerStateName = 'install-state.json'
39
- $PackageVersion = '1.0.4'
40
- $PackageSpec = 'https://github.com/WSL043/dsh-codex-subscription/releases/download/v1.0.4/dsh-codex-subscription.tgz'
41
- $PnpmVersion = '11.19.0'
42
- $PnpmUrl = 'https://registry.npmjs.org/pnpm/-/pnpm-11.19.0.tgz'
43
- $PnpmSha512 = '7881F3ED590D472C4A955E2B88B2121791116066DCC88CBCA3849EC9B60F1BBAA6D2CCB221FA91DA4E1C65BEF2BCBE379365AEA7AC539C7BF86DEDC3A1B22DCE'
44
- $ReleaseApi = if ($env:DSH_CODEX_RELEASE_API) { $env:DSH_CODEX_RELEASE_API } else { 'https://api.github.com/repos/WSL043/dsh-codex-subscription/releases/latest' }
45
- $ReleaseBase = if ($env:DSH_CODEX_RELEASE_BASE) { $env:DSH_CODEX_RELEASE_BASE.TrimEnd('/') } else { 'https://github.com/WSL043/dsh-codex-subscription/releases/download' }
46
-
47
- function Get-FileDigest {
48
- param(
49
- [Parameter(Mandatory = $true)][string] $Path,
50
- [ValidateSet('SHA256', 'SHA512')][string] $Algorithm
51
- )
52
-
53
- $hasher = if ($Algorithm -eq 'SHA512') {
54
- [System.Security.Cryptography.SHA512]::Create()
55
- } else {
56
- [System.Security.Cryptography.SHA256]::Create()
57
- }
58
- $stream = [System.IO.File]::OpenRead($Path)
59
- try {
60
- return ([System.BitConverter]::ToString($hasher.ComputeHash($stream))).Replace('-', '')
61
- } finally {
62
- $stream.Dispose()
63
- $hasher.Dispose()
64
- }
65
- }
66
-
67
- function Get-ManagerCommandRoot {
68
- if ($CommandRoot) { return Resolve-FullPath $CommandRoot }
69
- if ($Managed -and $PSCommandPath) { return Split-Path -Parent (Resolve-FullPath $PSCommandPath) }
70
- if (-not $env:LOCALAPPDATA) { throw 'LOCALAPPDATA is required to install the dsh-codex command.' }
71
- return Join-Path $env:LOCALAPPDATA 'Programs\dsh-codex'
72
- }
73
-
74
- function Invoke-LatestManager {
75
- param([Parameter(Mandatory = $true)][string] $InstalledCommandRoot)
76
-
77
- Write-Host 'Checking the latest immutable release...'
78
- $releaseSeparator = if ($ReleaseApi.Contains('?')) { '&' } else { '?' }
79
- $releaseUri = $ReleaseApi + $releaseSeparator + 'cache_bust=' + [DateTime]::UtcNow.Ticks
80
- $releaseResponse = Invoke-WebRequest -UseBasicParsing -Uri $releaseUri -Headers @{
81
- Accept = 'application/vnd.github+json'
82
- 'Cache-Control' = 'no-cache'
83
- Pragma = 'no-cache'
84
- 'User-Agent' = 'dsh-codex'
85
- }
86
- try {
87
- $release = $releaseResponse.Content | ConvertFrom-Json
88
- } catch {
89
- throw 'GitHub returned unreadable latest-release metadata.'
90
- }
91
- $tag = [string] $release.tag_name
92
- if ($tag -notmatch '^v[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?$') {
93
- throw "GitHub returned an invalid release tag: $tag"
94
- }
95
-
96
- $stage = Join-Path ([System.IO.Path]::GetTempPath()) ('dsh-codex-update-' + [guid]::NewGuid().ToString('N'))
97
- $latestScript = Join-Path $stage 'dsh-codex.ps1'
98
- $checksumFile = Join-Path $stage 'dsh-codex.ps1.sha256'
99
- New-Item -ItemType Directory -Path $stage | Out-Null
100
- try {
101
- $assetBase = "$ReleaseBase/$tag"
102
- Invoke-WebRequest -UseBasicParsing -Uri "$assetBase/dsh-codex.ps1" -OutFile $latestScript
103
- Invoke-WebRequest -UseBasicParsing -Uri "$assetBase/dsh-codex.ps1.sha256" -OutFile $checksumFile
104
- $checksumText = Get-Content -LiteralPath $checksumFile -Raw
105
- $match = [regex]::Match($checksumText, '(?im)^\s*([a-f0-9]{64})\s+\*?dsh-codex\.ps1\s*$')
106
- if (-not $match.Success) { throw 'The release manager checksum file is invalid.' }
107
- $expectedHash = $match.Groups[1].Value.ToUpperInvariant()
108
- $actualHash = Get-FileDigest -Algorithm SHA256 -Path $latestScript
109
- if ($actualHash -ne $expectedHash) {
110
- throw "Release manager checksum mismatch. Expected $expectedHash, received $actualHash."
111
- }
112
-
113
- $arguments = @(
114
- '-NoLogo', '-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass',
115
- '-File', $latestScript,
116
- '-Managed', '-SkipSelfUpdate', '-Action', $Action,
117
- '-Profile', $Profile,
118
- '-CommandRoot', $InstalledCommandRoot
119
- )
120
- if ($PortableRoot) { $arguments += @('-PortableRoot', $PortableRoot) }
121
- if ($NoModifyPath) { $arguments += '-NoModifyPath' }
122
- & powershell.exe @arguments
123
- if ($LASTEXITCODE -ne 0) { throw "The latest release manager failed with exit code $LASTEXITCODE." }
124
- } finally {
125
- if (Test-Path -LiteralPath $stage) {
126
- Remove-Item -LiteralPath $stage -Recurse -Force -ErrorAction SilentlyContinue
127
- }
128
- }
129
- }
130
-
131
- function Test-SamePath {
132
- param(
133
- [Parameter(Mandatory = $true)][string] $Left,
134
- [Parameter(Mandatory = $true)][string] $Right
135
- )
136
- try {
137
- $leftPath = (Resolve-FullPath $Left).TrimEnd('\')
138
- $rightPath = (Resolve-FullPath $Right).TrimEnd('\')
139
- return [string]::Equals($leftPath, $rightPath, [System.StringComparison]::OrdinalIgnoreCase)
140
- } catch {
141
- return [string]::Equals($Left.Trim(), $Right.Trim(), [System.StringComparison]::OrdinalIgnoreCase)
142
- }
143
- }
144
-
145
- function Add-UserPathEntry {
146
- param(
147
- [AllowNull()][AllowEmptyString()][string] $UserPath,
148
- [Parameter(Mandatory = $true)][string] $Directory
149
- )
150
-
151
- foreach ($entry in @(([string] $UserPath).Split(';'))) {
152
- if ($entry.Trim() -and (Test-SamePath -Left $entry -Right $Directory)) {
153
- return [string] $UserPath
154
- }
155
- }
156
- if (-not $UserPath) { return $Directory }
157
-
158
- # Always add our own separator. If the existing value already ends in one,
159
- # removing this suffix can still restore the original text byte-for-byte.
160
- return $UserPath + ';' + $Directory
161
- }
162
-
163
- function Remove-UserPathEntry {
164
- param(
165
- [AllowNull()][AllowEmptyString()][string] $UserPath,
166
- [Parameter(Mandatory = $true)][string] $Directory
167
- )
168
-
169
- if (-not $UserPath) { return [string] $UserPath }
170
-
171
- $lastSeparator = $UserPath.LastIndexOf(';')
172
- if ($lastSeparator -ge 0) {
173
- $lastEntry = $UserPath.Substring($lastSeparator + 1)
174
- if ($lastEntry.Trim() -and (Test-SamePath -Left $lastEntry -Right $Directory)) {
175
- return $UserPath.Substring(0, $lastSeparator)
176
- }
177
- } elseif (Test-SamePath -Left $UserPath -Right $Directory) {
178
- return ''
179
- }
180
-
181
- $kept = New-Object System.Collections.Generic.List[string]
182
- foreach ($entry in $UserPath.Split(';')) {
183
- if ($entry.Trim() -and (Test-SamePath -Left $entry -Right $Directory)) { continue }
184
- $kept.Add($entry)
185
- }
186
- return $kept -join ';'
187
- }
188
-
189
- function Publish-UserPathChange {
190
- try {
191
- if (-not ('DshCodex.NativeMethods' -as [type])) {
192
- Add-Type -TypeDefinition @'
193
- using System;
194
- using System.Runtime.InteropServices;
195
- namespace DshCodex {
196
- public static class NativeMethods {
197
- [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
198
- public static extern IntPtr SendMessageTimeout(
199
- IntPtr hWnd,
200
- uint message,
201
- UIntPtr wParam,
202
- string lParam,
203
- uint flags,
204
- uint timeout,
205
- out UIntPtr result);
206
- }
207
- }
208
- '@
209
- }
210
- [UIntPtr] $result = [UIntPtr]::Zero
211
- # HWND_BROADCAST + WM_SETTINGCHANGE with SMTO_ABORTIFHUNG.
212
- [void] [DshCodex.NativeMethods]::SendMessageTimeout(
213
- [IntPtr] 0xffff,
214
- 0x001A,
215
- [UIntPtr]::Zero,
216
- 'Environment',
217
- 0x0002,
218
- 2000,
219
- [ref] $result
220
- )
221
- } catch {
222
- Write-Warning 'The user PATH was updated, but Windows did not accept the environment refresh notification.'
223
- }
224
- }
225
-
226
- function Add-ManagerToUserPath {
227
- param([Parameter(Mandatory = $true)][string] $Directory)
228
-
229
- $userPath = [Environment]::GetEnvironmentVariable('Path', 'User')
230
- $updated = Add-UserPathEntry -UserPath $userPath -Directory $Directory
231
- if ([string]::Equals([string] $updated, [string] $userPath, [System.StringComparison]::Ordinal)) { return $false }
232
- [Environment]::SetEnvironmentVariable('Path', $updated, 'User')
233
- Publish-UserPathChange
234
- return $true
235
- }
236
-
237
- function Remove-ManagerFromUserPath {
238
- param([Parameter(Mandatory = $true)][string] $Directory)
239
-
240
- $userPath = [Environment]::GetEnvironmentVariable('Path', 'User')
241
- if (-not $userPath) { return }
242
- $updated = Remove-UserPathEntry -UserPath $userPath -Directory $Directory
243
- if ([string]::Equals([string] $updated, [string] $userPath, [System.StringComparison]::Ordinal)) { return }
244
- [Environment]::SetEnvironmentVariable('Path', $updated, 'User')
245
- Publish-UserPathChange
246
- }
247
-
248
- function Start-ManagerCleanup {
249
- param([Parameter(Mandatory = $true)][string] $Directory)
250
-
251
- $installedScript = Join-Path $Directory $ManagerScriptName
252
- $installedShim = Join-Path $Directory $ManagerShimName
253
- if (-not (Test-Path -LiteralPath $installedScript -PathType Leaf) -or
254
- -not (Test-Path -LiteralPath $installedShim -PathType Leaf)) {
255
- throw 'Refusing to clean an unrecognized manager command directory.'
256
- }
257
-
258
- $cleanupScript = Join-Path ([System.IO.Path]::GetTempPath()) ('dsh-codex-cleanup-' + [guid]::NewGuid().ToString('N') + '.ps1')
259
- $cleanup = @'
260
- param(
261
- [int] $ParentId,
262
- [string] $DirectoryBase64,
263
- [string] $CleanupBase64
264
- )
265
- $ErrorActionPreference = 'SilentlyContinue'
266
- $directory = [Text.Encoding]::Unicode.GetString([Convert]::FromBase64String($DirectoryBase64))
267
- $cleanupScript = [Text.Encoding]::Unicode.GetString([Convert]::FromBase64String($CleanupBase64))
268
- try {
269
- Wait-Process -Id $ParentId -ErrorAction SilentlyContinue
270
- Start-Sleep -Milliseconds 300
271
- foreach ($name in @('dsh-codex-manager.ps1', 'dsh-codex.ps1', 'dsh-codex.cmd', 'install-state.json')) {
272
- Remove-Item -LiteralPath (Join-Path $directory $name) -Force -ErrorAction SilentlyContinue
273
- }
274
- if ((Test-Path -LiteralPath $directory -PathType Container) -and
275
- -not (Get-ChildItem -LiteralPath $directory -Force -ErrorAction SilentlyContinue | Select-Object -First 1)) {
276
- Remove-Item -LiteralPath $directory -Force -ErrorAction SilentlyContinue
277
- }
278
- } finally {
279
- Remove-Item -LiteralPath $cleanupScript -Force -ErrorAction SilentlyContinue
280
- }
281
- '@
282
- [System.IO.File]::WriteAllText($cleanupScript, $cleanup, $Utf8NoBom)
283
- $directoryBase64 = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($Directory))
284
- $cleanupBase64 = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($cleanupScript))
285
- $argumentLine = "-NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -File `"$cleanupScript`" -ParentId $PID -DirectoryBase64 $directoryBase64 -CleanupBase64 $cleanupBase64"
286
- Start-Process -FilePath 'powershell.exe' -ArgumentList $argumentLine -WindowStyle Hidden | Out-Null
287
- }
288
-
289
- function Remove-ManagerCommand {
290
- param(
291
- [Parameter(Mandatory = $true)][string] $Directory,
292
- [bool] $RemovePath
293
- )
294
-
295
- if ($RemovePath) { Remove-ManagerFromUserPath -Directory $Directory }
296
- if (Test-Path -LiteralPath $Directory -PathType Container) {
297
- $installedScript = Join-Path $Directory $ManagerScriptName
298
- if ($Managed -and $PSCommandPath -and (Test-SamePath -Left $PSCommandPath -Right $installedScript)) {
299
- Start-ManagerCleanup -Directory $Directory
300
- return
301
- }
302
- foreach ($ownedFile in @($ManagerScriptName, $LegacyManagerScriptName, $ManagerShimName, $ManagerStateName)) {
303
- $ownedPath = Join-Path $Directory $ownedFile
304
- if (Test-Path -LiteralPath $ownedPath -PathType Leaf) {
305
- Remove-Item -LiteralPath $ownedPath -Force
306
- }
307
- }
308
- if (-not (Get-ChildItem -LiteralPath $Directory -Force -ErrorAction SilentlyContinue | Select-Object -First 1)) {
309
- Remove-Item -LiteralPath $Directory -Force
310
- }
311
- }
312
- }
313
-
314
- function Install-ManagerCommand {
315
- param([Parameter(Mandatory = $true)][string] $Directory)
316
-
317
- if (-not $PSCommandPath -or -not (Test-Path -LiteralPath $PSCommandPath -PathType Leaf)) {
318
- throw 'The manager command can only be installed from a downloaded script file.'
319
- }
320
- New-Item -ItemType Directory -Force -Path $Directory | Out-Null
321
- $installedScript = Join-Path $Directory $ManagerScriptName
322
- if (-not (Test-SamePath -Left $PSCommandPath -Right $installedScript)) {
323
- $stagedScript = Join-Path $Directory ('.dsh-codex-' + [guid]::NewGuid().ToString('N') + '.ps1')
324
- try {
325
- Copy-Item -LiteralPath $PSCommandPath -Destination $stagedScript
326
- Move-Item -LiteralPath $stagedScript -Destination $installedScript -Force
327
- } finally {
328
- if (Test-Path -LiteralPath $stagedScript) {
329
- Remove-Item -LiteralPath $stagedScript -Force -ErrorAction SilentlyContinue
330
- }
331
- }
332
- }
333
-
334
- $legacyInstalledScript = Join-Path $Directory $LegacyManagerScriptName
335
- if (Test-Path -LiteralPath $legacyInstalledScript -PathType Leaf) {
336
- Remove-Item -LiteralPath $legacyInstalledScript -Force
337
- }
338
-
339
- $shim = @'
340
- @echo off
341
- powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0dsh-codex-manager.ps1" -Managed %*
342
- exit /b %ERRORLEVEL%
343
- '@
344
- [System.IO.File]::WriteAllText((Join-Path $Directory $ManagerShimName), $shim, [System.Text.Encoding]::ASCII)
345
- if ($NoModifyPath) { return $false }
346
- return Add-ManagerToUserPath -Directory $Directory
347
- }
348
-
349
- function Resolve-FullPath {
350
- param([Parameter(Mandatory = $true)][string] $Path)
351
- return [System.IO.Path]::GetFullPath([Environment]::ExpandEnvironmentVariables($Path))
352
- }
353
-
354
- function Read-ManagerState {
355
- param([Parameter(Mandatory = $true)][string] $Directory)
356
-
357
- $path = Join-Path $Directory $ManagerStateName
358
- if (-not (Test-Path -LiteralPath $path -PathType Leaf)) { return $null }
359
- try {
360
- $state = Get-Content -LiteralPath $path -Raw | ConvertFrom-Json
361
- } catch {
362
- throw 'The saved dsh-codex installation target is unreadable. Re-run setup and choose DSH again.'
363
- }
364
- if ($state.schemaVersion -ne 1 -or $state.mode -notin @('portable', 'global') -or
365
- [string] $state.profile -notmatch '^[A-Za-z0-9._-]+$' -or
366
- $state.pathOwned -isnot [bool]) {
367
- throw 'The saved dsh-codex installation target is invalid. Re-run setup and choose DSH again.'
368
- }
369
- if ($state.mode -eq 'portable' -and -not [string] $state.portableRoot) {
370
- throw 'The saved DSH-Portable target is missing. Re-run setup and choose DSH again.'
371
- }
372
- if ($state.mode -eq 'global' -and
373
- (($state.PSObject.Properties['globalDsh'] -and -not [string] $state.globalDsh) -or
374
- ($state.PSObject.Properties['globalNode'] -and -not [string] $state.globalNode))) {
375
- throw 'The saved global DSH target is invalid. Re-run setup and choose DSH again.'
376
- }
377
- return $state
378
- }
379
-
380
- function Write-ManagerState {
381
- param(
382
- [Parameter(Mandatory = $true)][string] $Directory,
383
- [Parameter(Mandatory = $true)] $Target,
384
- [Parameter(Mandatory = $true)][string] $SelectedProfile,
385
- [Parameter(Mandatory = $true)][bool] $PathOwned
386
- )
387
-
388
- $path = Join-Path $Directory $ManagerStateName
389
- $staged = Join-Path $Directory ('.install-state-' + [guid]::NewGuid().ToString('N') + '.json')
390
- $state = [ordered]@{
391
- schemaVersion = 1
392
- mode = $Target.Mode
393
- portableRoot = if ($Target.Mode -eq 'portable') { $Target.Layout.Root } else { $null }
394
- globalDsh = if ($Target.Mode -eq 'global') { Resolve-FullPath $Target.Executable } else { $null }
395
- globalNode = if ($Target.Mode -eq 'global') { Resolve-FullPath $Target.Node } else { $null }
396
- profile = $SelectedProfile
397
- pathOwned = $PathOwned
398
- } | ConvertTo-Json -Compress
399
- try {
400
- [System.IO.File]::WriteAllText($staged, $state, $Utf8NoBom)
401
- Move-Item -LiteralPath $staged -Destination $path -Force
402
- } finally {
403
- if (Test-Path -LiteralPath $staged) {
404
- Remove-Item -LiteralPath $staged -Force -ErrorAction SilentlyContinue
405
- }
406
- }
407
- }
408
-
409
- function Get-PortableLayout {
410
- param([Parameter(Mandatory = $true)][string] $Root)
411
-
412
- $resolvedRoot = Resolve-FullPath $Root
413
- $node = Join-Path $resolvedRoot 'runtime\node\node.exe'
414
- $dsh = Join-Path $resolvedRoot 'app\node_modules\@deepseek-ai\dsh\lib\bin.js'
415
- $portableCli = Join-Path $resolvedRoot 'dsh.exe'
416
- if (-not (Test-Path -LiteralPath $node -PathType Leaf) -or
417
- -not (Test-Path -LiteralPath $dsh -PathType Leaf)) {
418
- return $null
419
- }
420
-
421
- $stateRoot = $resolvedRoot
422
- $installedMode = Join-Path $resolvedRoot 'installed-mode.json'
423
- if (Test-Path -LiteralPath $installedMode -PathType Leaf) {
424
- $mode = Get-Content -LiteralPath $installedMode -Raw | ConvertFrom-Json
425
- if (-not $mode.stateRoot) { throw "Invalid installed-mode.json: stateRoot is missing." }
426
- $stateRoot = Resolve-FullPath ([string] $mode.stateRoot)
427
- }
428
-
429
- return [pscustomobject]@{
430
- Root = $resolvedRoot
431
- StateRoot = $stateRoot
432
- Node = $node
433
- Dsh = $dsh
434
- PortableCli = if (Test-Path -LiteralPath $portableCli -PathType Leaf) { $portableCli } else { $null }
435
- DshHome = Join-Path $stateRoot 'data\dsh-home'
436
- }
437
- }
438
-
439
- function New-PortableTarget {
440
- param([Parameter(Mandatory = $true)] $Layout)
441
- $usesPortableCli = $null -ne $Layout.PortableCli
442
- return [pscustomobject]@{
443
- Mode = 'portable'
444
- Layout = $Layout
445
- Executable = if ($usesPortableCli) { $Layout.PortableCli } else { $Layout.Node }
446
- Node = $Layout.Node
447
- UsesPortableCli = $usesPortableCli
448
- }
449
- }
450
-
451
- function Find-PortableFromCurrentDirectory {
452
- $directory = [System.IO.DirectoryInfo]::new((Get-Location).Path)
453
- while ($null -ne $directory) {
454
- $layout = Get-PortableLayout $directory.FullName
455
- if ($null -ne $layout) { return $layout }
456
- $directory = $directory.Parent
457
- }
458
- return $null
459
- }
460
-
461
- function Find-RunningPortables {
462
- try {
463
- $processes = Get-CimInstance Win32_Process -Filter "Name = 'node.exe'" -ErrorAction Stop
464
- foreach ($process in $processes) {
465
- $executable = [string] $process.ExecutablePath
466
- if (-not $executable) { continue }
467
- $nodeDirectory = Split-Path -Parent $executable
468
- $runtimeDirectory = Split-Path -Parent $nodeDirectory
469
- $root = Split-Path -Parent $runtimeDirectory
470
- $layout = Get-PortableLayout $root
471
- if ($null -eq $layout) { continue }
472
- if (-not [string]::Equals(
473
- (Resolve-FullPath $executable),
474
- $layout.Node,
475
- [System.StringComparison]::OrdinalIgnoreCase
476
- )) { continue }
477
- $commandLine = [string] $process.CommandLine
478
- if ($commandLine -match '(?i)@deepseek-ai[\\/]dsh[\\/]lib[\\/]bin\.js') {
479
- Write-Output $layout
480
- }
481
- }
482
- } catch {
483
- # Process discovery is only one optional location hint.
484
- }
485
- return $null
486
- }
487
-
488
- function Find-CommonPortables {
489
- $candidates = New-Object System.Collections.Generic.List[string]
490
- if ($env:LOCALAPPDATA) {
491
- $candidates.Add((Join-Path $env:LOCALAPPDATA 'Programs\DeepSeek-Herness'))
492
- }
493
- if ($env:USERPROFILE) {
494
- $candidates.Add((Join-Path $env:USERPROFILE 'Downloads\DSH-Portable'))
495
- $candidates.Add((Join-Path $env:USERPROFILE 'Desktop\DSH-Portable'))
496
- foreach ($parent in @(
497
- (Join-Path $env:USERPROFILE 'Downloads'),
498
- (Join-Path $env:USERPROFILE 'Desktop')
499
- )) {
500
- if (Test-Path -LiteralPath $parent -PathType Container) {
501
- foreach ($directory in Get-ChildItem -LiteralPath $parent -Directory -ErrorAction SilentlyContinue) {
502
- $candidates.Add($directory.FullName)
503
- }
504
- }
505
- }
506
- }
507
-
508
- foreach ($candidate in $candidates | Select-Object -Unique) {
509
- $layout = Get-PortableLayout $candidate
510
- if ($null -ne $layout) { Write-Output $layout }
511
- }
512
- }
513
-
514
- function Get-ManagerTarget {
515
- param(
516
- [AllowNull()][string] $PreferredMode,
517
- [AllowNull()] $SavedState
518
- )
519
-
520
- if ($PortableRoot) {
521
- $layout = Get-PortableLayout $PortableRoot
522
- if ($null -eq $layout) {
523
- throw "The selected DSH-Portable folder is incomplete: $PortableRoot"
524
- }
525
- return New-PortableTarget $layout
526
- }
527
-
528
- if ($PreferredMode -eq 'global') {
529
- $savedDshPath = if ($null -ne $SavedState -and $SavedState.PSObject.Properties['globalDsh']) {
530
- [string] $SavedState.globalDsh
531
- } else { $null }
532
- $savedNodePath = if ($null -ne $SavedState -and $SavedState.PSObject.Properties['globalNode']) {
533
- [string] $SavedState.globalNode
534
- } else { $null }
535
- if ($savedDshPath -and $savedNodePath) {
536
- if (-not (Test-Path -LiteralPath $savedDshPath -PathType Leaf) -or
537
- -not (Test-Path -LiteralPath $savedNodePath -PathType Leaf)) {
538
- throw 'The saved global DSH target is no longer available. Re-run setup and choose DSH again.'
539
- }
540
- return [pscustomobject]@{
541
- Mode = 'global'
542
- Layout = $null
543
- Executable = Resolve-FullPath $savedDshPath
544
- Node = Resolve-FullPath $savedNodePath
545
- UsesPortableCli = $false
546
- }
547
- }
548
- $savedGlobalDsh = Get-Command dsh -ErrorAction SilentlyContinue
549
- $savedGlobalNode = Get-Command node -ErrorAction SilentlyContinue
550
- if ($null -eq $savedGlobalDsh -or $null -eq $savedGlobalNode) {
551
- throw 'The saved global DSH target is no longer available on PATH. Re-run setup and choose DSH again.'
552
- }
553
- return [pscustomobject]@{
554
- Mode = 'global'
555
- Layout = $null
556
- Executable = $savedGlobalDsh.Source
557
- Node = $savedGlobalNode.Source
558
- UsesPortableCli = $false
559
- }
560
- }
561
-
562
- $layout = Find-PortableFromCurrentDirectory
563
- if ($null -ne $layout) {
564
- return New-PortableTarget $layout
565
- }
566
-
567
- $runningLayouts = @(Find-RunningPortables | Sort-Object -Property Root -Unique)
568
- if ($runningLayouts.Count -gt 1) {
569
- $paths = ($runningLayouts | ForEach-Object { "- $($_.Root)" }) -join [Environment]::NewLine
570
- throw "More than one running DSH-Portable was found. Re-run with -PortableRoot:`n$paths"
571
- }
572
- if ($runningLayouts.Count -eq 1) {
573
- $layout = $runningLayouts[0]
574
- return New-PortableTarget $layout
575
- }
576
-
577
- $globalDsh = Get-Command dsh -ErrorAction SilentlyContinue
578
- $commonLayouts = @(Find-CommonPortables | Sort-Object -Property Root -Unique)
579
- if ($commonLayouts.Count -gt 1) {
580
- $paths = ($commonLayouts | ForEach-Object { "- $($_.Root)" }) -join [Environment]::NewLine
581
- throw "More than one DSH-Portable folder was found. Re-run with -PortableRoot:`n$paths"
582
- }
583
- if ($null -ne $globalDsh -and $commonLayouts.Count -eq 1) {
584
- throw "Both a global dsh command and DSH-Portable were found. Run from the intended portable folder, or pass -PortableRoot '$($commonLayouts[0].Root)'."
585
- }
586
- if ($commonLayouts.Count -eq 1) {
587
- $layout = $commonLayouts[0]
588
- return New-PortableTarget $layout
589
- }
590
- if ($null -ne $globalDsh) {
591
- $globalNode = Get-Command node -ErrorAction SilentlyContinue
592
- if ($null -eq $globalNode) { throw 'The dsh command exists, but Node.js is not available on PATH.' }
593
- return [pscustomobject]@{ Mode = 'global'; Layout = $null; Executable = $globalDsh.Source; Node = $globalNode.Source; UsesPortableCli = $false }
594
- }
595
-
596
- throw @'
597
- DeepSeek Harness was not found.
598
-
599
- If you use DSH-Portable, start it once or run this command from inside its
600
- folder. You can also pass -PortableRoot "C:\path\to\DSH-Portable".
601
- '@
602
- }
603
-
604
- function Get-PnpmDirectory {
605
- param([Parameter(Mandatory = $true)] $Target)
606
- if ($Target.UsesPortableCli) { return $null }
607
- if ($Target.Mode -eq 'portable') {
608
- return Join-Path $Target.Layout.StateRoot "data\runtime\dsh-codex-tools\pnpm-$PnpmVersion"
609
- }
610
- if (-not $env:LOCALAPPDATA) { throw 'LOCALAPPDATA is required to cache the plugin manager.' }
611
- return Join-Path $env:LOCALAPPDATA "dsh-codex-subscription\tools\pnpm-$PnpmVersion"
612
- }
613
-
614
- function Get-PnpmStore {
615
- param([Parameter(Mandatory = $true)] $Target)
616
- if ($Target.Mode -eq 'portable') {
617
- return Join-Path $Target.Layout.StateRoot 'data\pnpm-store'
618
- }
619
- return $null
620
- }
621
-
622
- function Test-PnpmDirectory {
623
- param(
624
- [Parameter(Mandatory = $true)][string] $Directory,
625
- [Parameter(Mandatory = $true)][string] $Node
626
- )
627
- $entry = Join-Path $Directory 'package\bin\pnpm.cjs'
628
- if (-not (Test-Path -LiteralPath $entry -PathType Leaf)) { return $false }
629
- try {
630
- $reported = (& $Node $entry '--version' 2>$null | Select-Object -First 1)
631
- return ([string] $reported).Trim() -eq $PnpmVersion
632
- } catch {
633
- return $false
634
- }
635
- }
636
-
637
- function Install-PnpmTool {
638
- param(
639
- [Parameter(Mandatory = $true)][string] $Directory,
640
- [Parameter(Mandatory = $true)][string] $Node
641
- )
642
-
643
- if (Test-PnpmDirectory -Directory $Directory -Node $Node) { return }
644
-
645
- $parent = Split-Path -Parent $Directory
646
- New-Item -ItemType Directory -Force -Path $parent | Out-Null
647
- $stage = Join-Path $parent ('.pnpm-' + [guid]::NewGuid().ToString('N'))
648
- $archive = Join-Path $stage 'pnpm.tgz'
649
- New-Item -ItemType Directory -Path $stage | Out-Null
650
- try {
651
- Write-Host "Preparing the bundled plugin manager (pnpm $PnpmVersion)..."
652
- Invoke-WebRequest -UseBasicParsing -Uri $PnpmUrl -OutFile $archive
653
- $actualHash = Get-FileDigest -Algorithm SHA512 -Path $archive
654
- if ($actualHash -ne $PnpmSha512) {
655
- throw "pnpm download checksum mismatch. Expected $PnpmSha512, received $actualHash."
656
- }
657
- $tar = Get-Command tar.exe -ErrorAction SilentlyContinue
658
- if ($null -eq $tar) { throw 'Windows tar.exe is required to unpack the verified pnpm package.' }
659
- & $tar.Source '-xzf' $archive '-C' $stage
660
- if ($LASTEXITCODE -ne 0) { throw "tar.exe failed with exit code $LASTEXITCODE." }
661
- Remove-Item -LiteralPath $archive -Force
662
- if (-not (Test-PnpmDirectory -Directory $stage -Node $Node)) {
663
- throw "The extracted pnpm package did not report version $PnpmVersion."
664
- }
665
- $shim = "@echo off`r`n`"%DSH_CODEX_NODE%`" `"%~dp0package\bin\pnpm.cjs`" %*`r`n"
666
- [System.IO.File]::WriteAllText((Join-Path $stage 'pnpm.cmd'), $shim, [System.Text.Encoding]::ASCII)
667
- if (Test-Path -LiteralPath $Directory) {
668
- Remove-Item -LiteralPath $Directory -Recurse -Force
669
- }
670
- Move-Item -LiteralPath $stage -Destination $Directory
671
- } finally {
672
- if (Test-Path -LiteralPath $stage) {
673
- Remove-Item -LiteralPath $stage -Recurse -Force -ErrorAction SilentlyContinue
674
- }
675
- }
676
- }
677
-
678
- function Get-ActionArguments {
679
- param(
680
- [Parameter(Mandatory = $true)][string] $SelectedAction,
681
- [AllowNull()][string] $Store,
682
- [string] $SelectedPackage = $PackageName
683
- )
684
- if ($SelectedAction -eq 'Uninstall') {
685
- $arguments = @('plugin', '--profile', $Profile, 'remove', $SelectedPackage)
686
- } else {
687
- $arguments = @('plugin', '--profile', $Profile, 'add', $PackageSpec)
688
- }
689
- if ($Store) { $arguments += @('--store-dir', $Store) }
690
- $arguments += @('--loglevel', 'error')
691
- return $arguments
692
- }
693
-
694
- function Invoke-DshCommand {
695
- param(
696
- [Parameter(Mandatory = $true)] $Target,
697
- [Parameter(Mandatory = $true)][string[]] $Arguments,
698
- [switch] $Capture
699
- )
700
- $allArguments = if ($Target.Mode -eq 'portable' -and -not $Target.UsesPortableCli) {
701
- @($Target.Layout.Dsh) + $Arguments
702
- } else {
703
- $Arguments
704
- }
705
- if ($Capture) {
706
- $output = & $Target.Executable @allArguments 2>&1
707
- $exitCode = $LASTEXITCODE
708
- if ($exitCode -ne 0) { throw "dsh failed with exit code $exitCode.`n$($output -join [Environment]::NewLine)" }
709
- return ($output -join [Environment]::NewLine)
710
- }
711
- & $Target.Executable @allArguments
712
- if ($LASTEXITCODE -ne 0) { throw "dsh failed with exit code $LASTEXITCODE." }
713
- }
714
-
715
- function Get-InstalledPackages {
716
- param([Parameter(Mandatory = $true)] $Target)
717
-
718
- $json = Invoke-DshCommand -Target $Target -Arguments @(
719
- 'plugin', '--profile', $Profile, 'list', '--depth', '0', '--json', '--loglevel', 'error'
720
- ) -Capture
721
- try {
722
- # Windows PowerShell 5.1 preserves a top-level JSON array as one pipeline
723
- # object. Assign first so @() expands the resulting Object[] correctly.
724
- $parsedProjects = $json | ConvertFrom-Json
725
- $projects = @($parsedProjects)
726
- } catch {
727
- throw 'DSH returned an unreadable plugin list.'
728
- }
729
-
730
- foreach ($project in $projects) {
731
- $dependenciesProperty = $project.PSObject.Properties['dependencies']
732
- if ($null -eq $dependenciesProperty -or $null -eq $dependenciesProperty.Value) { continue }
733
- foreach ($property in $dependenciesProperty.Value.PSObject.Properties) {
734
- $version = $null
735
- if ($null -ne $property.Value) {
736
- $versionProperty = $property.Value.PSObject.Properties['version']
737
- if ($null -ne $versionProperty -and $null -ne $versionProperty.Value) {
738
- $version = [string] $versionProperty.Value
739
- }
740
- }
741
- Write-Output ([pscustomobject]@{
742
- Name = [string] $property.Name
743
- Version = $version
744
- })
745
- }
746
- }
747
- }
748
-
749
- $managerCommandRoot = Get-ManagerCommandRoot
750
- $managerState = Read-ManagerState -Directory $managerCommandRoot
751
- if ($Managed -and $null -ne $managerState) {
752
- $explicitPortableRoot = $PSBoundParameters.ContainsKey('PortableRoot')
753
- $sameSavedPortable = $explicitPortableRoot -and $managerState.mode -eq 'portable' -and
754
- (Test-SamePath -Left $PortableRoot -Right ([string] $managerState.portableRoot))
755
- if (-not $PSBoundParameters.ContainsKey('Profile') -and
756
- (-not $explicitPortableRoot -or $sameSavedPortable)) {
757
- $Profile = [string] $managerState.profile
758
- }
759
- if (-not $PSBoundParameters.ContainsKey('PortableRoot') -and $managerState.mode -eq 'portable') {
760
- $PortableRoot = [string] $managerState.portableRoot
761
- }
762
- }
763
- $legacyManagedPathOwned = $false
764
- if ($Managed -and $null -eq $managerState -and
765
- (Test-Path -LiteralPath (Join-Path $managerCommandRoot $ManagerScriptName) -PathType Leaf)) {
766
- $currentUserPath = [Environment]::GetEnvironmentVariable('Path', 'User')
767
- $legacyManagedPathOwned = [string]::Equals(
768
- [string] (Add-UserPathEntry -UserPath $currentUserPath -Directory $managerCommandRoot),
769
- [string] $currentUserPath,
770
- [System.StringComparison]::Ordinal
771
- )
772
- }
773
- if ($Managed -and $Action -eq 'Update' -and -not $SkipSelfUpdate -and -not $DryRun) {
774
- Invoke-LatestManager -InstalledCommandRoot $managerCommandRoot
775
- exit 0
776
- }
777
-
778
- $preferredMode = if ($Managed -and $null -ne $managerState) { [string] $managerState.mode } else { $null }
779
- $target = Get-ManagerTarget -PreferredMode $preferredMode -SavedState $managerState
780
- $managerCommand = Join-Path $managerCommandRoot 'dsh-codex.cmd'
781
- $pnpmDirectory = Get-PnpmDirectory $target
782
- $pnpmStore = Get-PnpmStore $target
783
- $actionArguments = Get-ActionArguments -SelectedAction $Action -Store $pnpmStore
784
- $arguments = if ($target.Mode -eq 'portable' -and -not $target.UsesPortableCli) {
785
- @($target.Layout.Dsh) + $actionArguments
786
- } else {
787
- $actionArguments
788
- }
789
-
790
- if ($DryRun) {
791
- [ordered]@{
792
- mode = $target.Mode
793
- action = $Action
794
- executable = $target.Executable
795
- arguments = $arguments
796
- dshHome = if ($target.Mode -eq 'portable') { $target.Layout.DshHome } else { $null }
797
- pnpmVersion = $PnpmVersion
798
- pnpmDirectory = $pnpmDirectory
799
- pnpmStore = $pnpmStore
800
- packageName = $PackageName
801
- legacyPackageName = $LegacyPackageName
802
- packageVersion = $PackageVersion
803
- packageSpec = $PackageSpec
804
- managerCommand = $managerCommand
805
- installsManagerCommand = $Action -ne 'Uninstall'
806
- modifiesUserPath = ($Action -ne 'Uninstall') -and (-not $NoModifyPath)
807
- removesProfile = $false
808
- } | ConvertTo-Json -Depth 4 -Compress
809
- exit 0
810
- }
811
-
812
- $oldPath = $env:PATH
813
- $oldDshHome = $env:DSH_HOME
814
- $oldDshPortable = $env:DSH_PORTABLE
815
- $oldTelemetry = $env:DSH_TELEMETRY_MODE
816
- $oldManagerNode = $env:DSH_CODEX_NODE
817
- $oldPnpmStore = $env:npm_config_store_dir
818
- $oldPnpmNotifier = $env:npm_config_update_notifier
819
- try {
820
- if (-not $target.UsesPortableCli) {
821
- Install-PnpmTool -Directory $pnpmDirectory -Node $target.Node
822
- }
823
- $env:DSH_CODEX_NODE = $target.Node
824
- if (-not $target.UsesPortableCli) {
825
- $env:PATH = $pnpmDirectory + [System.IO.Path]::PathSeparator + (Split-Path -Parent $target.Node) + [System.IO.Path]::PathSeparator + $oldPath
826
- }
827
- $env:npm_config_update_notifier = 'false'
828
- if ($target.Mode -eq 'portable') {
829
- New-Item -ItemType Directory -Force -Path $target.Layout.DshHome | Out-Null
830
- $env:DSH_HOME = $target.Layout.DshHome
831
- $env:DSH_PORTABLE = '1'
832
- $env:DSH_TELEMETRY_MODE = 'DISABLED'
833
- $env:npm_config_store_dir = $pnpmStore
834
- }
835
-
836
- $installedBefore = @(Get-InstalledPackages -Target $target)
837
- $installedBeforeNames = @($installedBefore | ForEach-Object { $_.Name })
838
- $hadPackage = $installedBeforeNames -contains $PackageName
839
- $hadLegacyPackage = $installedBeforeNames -contains $LegacyPackageName
840
-
841
- if ($Action -eq 'Uninstall') {
842
- if ($hadPackage) {
843
- Invoke-DshCommand -Target $target -Arguments (
844
- Get-ActionArguments -SelectedAction 'Uninstall' -Store $pnpmStore -SelectedPackage $PackageName
845
- )
846
- }
847
- if ($hadLegacyPackage) {
848
- Invoke-DshCommand -Target $target -Arguments (
849
- Get-ActionArguments -SelectedAction 'Uninstall' -Store $pnpmStore -SelectedPackage $LegacyPackageName
850
- )
851
- }
852
- } else {
853
- Invoke-DshCommand -Target $target -Arguments $actionArguments
854
- if ($hadLegacyPackage) {
855
- try {
856
- Invoke-DshCommand -Target $target -Arguments (
857
- Get-ActionArguments -SelectedAction 'Uninstall' -Store $pnpmStore -SelectedPackage $LegacyPackageName
858
- )
859
- } catch {
860
- if (-not $hadPackage) {
861
- try {
862
- Invoke-DshCommand -Target $target -Arguments (
863
- Get-ActionArguments -SelectedAction 'Uninstall' -Store $pnpmStore -SelectedPackage $PackageName
864
- )
865
- } catch {
866
- # Preserve the original migration error.
867
- }
868
- }
869
- throw
870
- }
871
- }
872
- }
873
-
874
- $config = Invoke-DshCommand -Target $target -Arguments @('--profile', $Profile, '--dump-config') -Capture
875
- $entryCount = ([regex]::Matches($config, '(?<![A-Za-z0-9_-])codex-subscription(?![A-Za-z0-9_-])')).Count
876
- $legacyEntryCount = ([regex]::Matches($config, '(?<![A-Za-z0-9_-])wsl043-codex-subscription(?![A-Za-z0-9_-])')).Count
877
- $installedAfter = @(Get-InstalledPackages -Target $target)
878
- $installedAfterNames = @($installedAfter | ForEach-Object { $_.Name })
879
- if ($Action -eq 'Uninstall') {
880
- if (($installedAfterNames -contains $PackageName) -or ($installedAfterNames -contains $LegacyPackageName)) {
881
- throw 'The plugin package is still present after uninstall.'
882
- }
883
- if ($entryCount -ne 0 -or $legacyEntryCount -ne 0) {
884
- throw 'The plugin package was removed, but its profile entry is still present.'
885
- }
886
- $removePath = (-not $NoModifyPath) -and (
887
- ($null -ne $managerState -and [bool] $managerState.pathOwned) -or
888
- ($null -eq $managerState -and $legacyManagedPathOwned)
889
- )
890
- Remove-ManagerCommand -Directory $managerCommandRoot -RemovePath $removePath
891
- Write-Host 'Uninstalled. The DSH profile and saved credentials were kept.'
892
- } else {
893
- $installedPackage = @($installedAfter | Where-Object { $_.Name -eq $PackageName } | Select-Object -First 1)
894
- if ($installedPackage.Count -eq 0) { throw 'The installed package did not appear in the DSH plugin list.' }
895
- if ($installedPackage[0].Version -ne $PackageVersion) {
896
- $foundVersion = if ($installedPackage[0].Version) { $installedPackage[0].Version } else { 'unknown' }
897
- throw "DSH did not install the requested package version: expected $PackageVersion, found $foundVersion."
898
- }
899
- if ($installedAfterNames -contains $LegacyPackageName) { throw 'The legacy package is still present after migration.' }
900
- if ($entryCount -ne 1) { throw "Expected one plugin profile entry, found $entryCount." }
901
- if ($legacyEntryCount -ne 0) { throw 'The legacy plugin profile entry is still present after migration.' }
902
- $addedPath = Install-ManagerCommand -Directory $managerCommandRoot
903
- $pathOwned = ($null -ne $managerState -and [bool] $managerState.pathOwned) -or
904
- $legacyManagedPathOwned -or [bool] $addedPath
905
- Write-ManagerState -Directory $managerCommandRoot -Target $target -SelectedProfile $Profile -PathOwned $pathOwned
906
- $verb = if ($Action -eq 'Update') { 'Updated.' } else { 'Installed.' }
907
- Write-Host "$verb Restart DSH manually to load the change if it is currently running."
908
- }
909
- } finally {
910
- $env:PATH = $oldPath
911
- $env:DSH_HOME = $oldDshHome
912
- $env:DSH_PORTABLE = $oldDshPortable
913
- $env:DSH_TELEMETRY_MODE = $oldTelemetry
914
- $env:DSH_CODEX_NODE = $oldManagerNode
915
- $env:npm_config_store_dir = $oldPnpmStore
916
- $env:npm_config_update_notifier = $oldPnpmNotifier
917
- }