dsh-codex-subscription 1.0.0 → 1.0.5

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