dsh-codex-subscription 0.3.7 → 1.0.4

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/AGENTS.md CHANGED
@@ -6,7 +6,7 @@ Use this guide when a user asks an Agent to install, update, verify, or remove
6
6
  ## Safety
7
7
 
8
8
  - Confirm the target DSH profile; use `web` only when it is the user's target.
9
- - Use the pinned `v0.3.7` release assets for a first install, never a moving branch.
9
+ - Use the pinned `v1.0.4` release assets for a first install, never a moving branch.
10
10
  - Never print OAuth credentials, account IDs, authorization callbacks, or the
11
11
  credential store.
12
12
  - Do not start, stop, or restart DSH without explicit permission.
@@ -51,7 +51,7 @@ the user which installation is the target.
51
51
  Download the fixed release asset to a visible file, then invoke the requested action:
52
52
 
53
53
  ```powershell
54
- curl.exe -fL https://github.com/WSL043/dsh-codex-subscription/releases/download/v0.3.7/dsh-codex.ps1 -o "$env:TEMP\dsh-codex.ps1"
54
+ curl.exe -fL https://github.com/WSL043/dsh-codex-subscription/releases/download/v1.0.4/dsh-codex.ps1 -o "$env:TEMP\dsh-codex.ps1"
55
55
  powershell.exe -NoProfile -ExecutionPolicy Bypass -File "$env:TEMP\dsh-codex.ps1" Install
56
56
  ```
57
57
 
@@ -59,7 +59,7 @@ If `curl.exe` is unavailable, download the same pinned asset without executing
59
59
  remote text in memory:
60
60
 
61
61
  ```powershell
62
- Invoke-WebRequest -UseBasicParsing -Uri 'https://github.com/WSL043/dsh-codex-subscription/releases/download/v0.3.7/dsh-codex.ps1' -OutFile "$env:TEMP\dsh-codex.ps1"
62
+ Invoke-WebRequest -UseBasicParsing -Uri 'https://github.com/WSL043/dsh-codex-subscription/releases/download/v1.0.4/dsh-codex.ps1' -OutFile "$env:TEMP\dsh-codex.ps1"
63
63
  powershell.exe -NoProfile -ExecutionPolicy Bypass -File "$env:TEMP\dsh-codex.ps1" Install
64
64
  ```
65
65
 
@@ -93,7 +93,7 @@ When `dsh`, Node.js, and pnpm are already available, install the pinned npm
93
93
  package directly:
94
94
 
95
95
  ```sh
96
- dsh plugin --profile web add dsh-codex-subscription@0.3.7
96
+ dsh plugin --profile web add dsh-codex-subscription@1.0.4
97
97
  ```
98
98
 
99
99
  Update with `dsh plugin --profile web update dsh-codex-subscription`. When
package/README.en.md CHANGED
@@ -13,7 +13,7 @@ tools, permissions, image generation, search choice, and quota stay in one place
13
13
 
14
14
  [Agent install](#let-an-agent-install-it-recommended) · [Windows install](#manual-windows-install) · [Update or uninstall](#update-and-uninstall)
15
15
 
16
- ![Codex subscription settings in DeepSeek Harness](https://github.com/WSL043/dsh-codex-subscription/releases/latest/download/settings-en.png)
16
+ ![Codex subscription settings in DeepSeek Harness](https://raw.githubusercontent.com/WSL043/dsh-codex-subscription/main/docs/assets/settings-en.png)
17
17
 
18
18
  Settings shows sign-in, search source, and the separate standard Codex and Spark
19
19
  quota returned by the service.
@@ -33,7 +33,7 @@ If this helps, open the [GitHub repository](https://github.com/WSL043/dsh-codex-
33
33
 
34
34
  ## Prepare DSH
35
35
 
36
- This plugin supports DeepSeek Harness `0.1.0-rc.6` and `0.1.0-rc.7`, and requires a ChatGPT account
36
+ This plugin supports DeepSeek Harness `0.1.0-rc.6`, `0.1.0-rc.7`, and `0.1.0-rc.8`, and requires a ChatGPT account
37
37
  that currently has Codex access.
38
38
 
39
39
  - Do not want to configure Node.js? Use the [community DSH-Portable package](https://github.com/WSL043/DSH-Portable).
@@ -57,22 +57,44 @@ https://raw.githubusercontent.com/WSL043/dsh-codex-subscription/main/AGENTS.md
57
57
 
58
58
  ### Manual Windows install
59
59
 
60
- Open PowerShell and paste these two lines in order. They support both a normal DSH install
61
- and DSH-Portable. Portable users do not need a system Node.js or pnpm.
60
+ Open PowerShell and paste this one line:
62
61
 
63
62
  ```powershell
64
- curl.exe -fL https://github.com/WSL043/dsh-codex-subscription/releases/latest/download/dsh-codex.ps1 -o "$env:TEMP\dsh-codex.ps1"
65
- powershell.exe -NoProfile -ExecutionPolicy Bypass -File "$env:TEMP\dsh-codex.ps1" Install
63
+ curl.exe -fL https://github.com/WSL043/dsh-codex-subscription/releases/latest/download/dsh-codex-setup.ps1 -o "$env:TEMP\dsh-codex-setup.ps1"; if ($LASTEXITCODE -eq 0) { powershell.exe -NoProfile -ExecutionPolicy Bypass -File "$env:TEMP\dsh-codex-setup.ps1" }
66
64
  ```
67
65
 
66
+ The setup asks you to choose **中文(简体)** or **English** first, then discovers DSH:
67
+
68
+ - one DSH installation: it shows the target and continues automatically;
69
+ - multiple DSH installations: it shows a numbered list with status and paths instead of throwing a PowerShell exception;
70
+ - no existing plugin: the operation is clearly shown as **Install**;
71
+ - an existing current or legacy plugin: the operation is clearly shown as **Update**, using the existing safe migration path;
72
+ - success: it shows the result and asks you to restart DSH manually; it never restarts DSH on its own.
73
+
68
74
  `Bypass` applies only to this child process and does not change the system
69
75
  PowerShell execution policy.
70
76
 
71
- The commands support both a normal DSH installation and DSH-Portable. Portable
72
- users do not need a separate Node.js or pnpm installation, and no administrator
73
- access is required. The installer finds DSH, verifies the result, and adds the
74
- per-user `dsh-codex` command. It does not change the system execution policy or
75
- restart DSH on its own.
77
+ Both a normal DSH installation and DSH-Portable are supported. Portable users do
78
+ not need a system Node.js or pnpm, and administrator access is not required. The
79
+ friendly setup downloads the core manager, verifies its Release SHA-256, then
80
+ lets the existing manager perform installation, update, verification, and PATH
81
+ handling.
82
+
83
+ <details>
84
+ <summary>Non-interactive / compatibility install</summary>
85
+
86
+ For scripting, Agent workflows, or an explicit `Install` action, the original
87
+ core manager remains available:
88
+
89
+ ```powershell
90
+ curl.exe -fL https://github.com/WSL043/dsh-codex-subscription/releases/latest/download/dsh-codex.ps1 -o "$env:TEMP\dsh-codex.ps1"
91
+ powershell.exe -NoProfile -ExecutionPolicy Bypass -File "$env:TEMP\dsh-codex.ps1" Install
92
+ ```
93
+
94
+ When more than one DSH-Portable copy exists, pass `-PortableRoot` explicitly.
95
+ This entry point stays non-interactive for automation.
96
+
97
+ </details>
76
98
 
77
99
  If the portable folder was renamed or moved, start the intended DSH-Portable copy
78
100
  once and install again. Restart DSH manually after installation.
@@ -108,7 +130,7 @@ subscription search when wanted.
108
130
 
109
131
  ## How quota is shown
110
132
 
111
- ![Codex quota inside the composer](https://github.com/WSL043/dsh-codex-subscription/releases/latest/download/composer-quota-en.png)
133
+ ![Codex quota inside the composer](https://raw.githubusercontent.com/WSL043/dsh-codex-subscription/main/docs/assets/composer-quota-en.png)
112
134
 
113
135
  - Settings always shows the detailed quota returned by the backend. The compact percentage is Beta and off by default;
114
136
  - When enabled, it appears inside the composer before the model name and only while a Codex model is selected;
@@ -132,7 +154,8 @@ Windows installer users only need these short commands:
132
154
  Update verifies the latest Release manager script with SHA-256. Uninstall removes
133
155
  the plugin and `dsh-codex` command while preserving the DSH profile, unrelated
134
156
  plugins, and saved sign-in. If an older installation has no short command, run
135
- the two Windows first-install commands above once.
157
+ the Windows setup line above once; the friendly setup detects the existing plugin
158
+ and automatically shows **Update**.
136
159
 
137
160
  <details>
138
161
  <summary>Update or uninstall with an existing <code>dsh</code> command</summary>
@@ -166,9 +189,8 @@ If DSH is running, restart it manually after installation or update.
166
189
 
167
190
  - If `dsh-codex` is not found after installation, close and reopen PowerShell;
168
191
  - If DSH-Portable is not found, start the intended portable copy once and retry;
169
- - If `curl.exe` is missing, more than one DSH is present, or the command still
170
- fails, send the Agent guide URL above to an Agent. Do not change the machine
171
- PATH or execution policy, and do not delete a profile to force an install.
192
+ - If multiple DSH installations are found, choose the intended one from the numbered list;
193
+ - If `curl.exe` is missing or setup still fails, send the Agent guide URL above to an Agent. Do not change the machine PATH or execution policy, and do not delete a profile to force an install.
172
194
 
173
195
  ## Boundaries and support
174
196
 
package/README.md CHANGED
@@ -5,12 +5,14 @@
5
5
  [![License: MIT](https://img.shields.io/badge/license-MIT-111111.svg)](LICENSE)
6
6
  [![GitHub stars](https://img.shields.io/github/stars/WSL043/dsh-codex-subscription?style=flat&logo=github&label=stars)](https://github.com/WSL043/dsh-codex-subscription/stargazers)
7
7
 
8
+ [English](https://github.com/WSL043/dsh-codex-subscription/blob/main/README.en.md)
9
+
8
10
  在 DeepSeek Harness 中直接登录 ChatGPT 并使用 Codex 订阅,不需要 OpenAI API Key,
9
11
  也不依赖 Codex CLI。保留 DSH 原有的会话、工具和权限,还能生成图片、切换联网搜索来源并查看额度。
10
12
 
11
13
  [交给 Agent 安装](#交给-agent推荐) · [Windows 手动安装](#windows-手动安装) · [更新与卸载](#更新与卸载) · [English](#english)
12
14
 
13
- ![DeepSeek Harness 的 Codex 订阅设置](https://github.com/WSL043/dsh-codex-subscription/releases/latest/download/settings.png)
15
+ ![DeepSeek Harness 的 Codex 订阅设置](https://raw.githubusercontent.com/WSL043/dsh-codex-subscription/main/docs/assets/settings.png)
14
16
 
15
17
  设置页会显示登录状态、搜索来源,以及服务端实际返回的普通 Codex 与 Spark 独立额度。
16
18
 
@@ -29,7 +31,7 @@
29
31
 
30
32
  ## 准备 DSH
31
33
 
32
- 本插件适配 DeepSeek Harness `0.1.0-rc.6` 与 `0.1.0-rc.7`,还需要一个当前具有 Codex 使用资格的
34
+ 本插件适配 DeepSeek Harness `0.1.0-rc.6`、`0.1.0-rc.7` 与 `0.1.0-rc.8`,还需要一个当前具有 Codex 使用资格的
33
35
  ChatGPT 账户。
34
36
 
35
37
  - 不想配置 Node.js:使用 [DSH-Portable(社区便携包)](https://github.com/WSL043/DSH-Portable);
@@ -52,16 +54,37 @@ https://raw.githubusercontent.com/WSL043/dsh-codex-subscription/main/AGENTS.md
52
54
 
53
55
  ### Windows 手动安装
54
56
 
55
- 打开 PowerShell,依次粘贴下面两行:
57
+ 打开 PowerShell,只需要复制这一行:
58
+
59
+ ```powershell
60
+ curl.exe -fL https://github.com/WSL043/dsh-codex-subscription/releases/latest/download/dsh-codex-setup.ps1 -o "$env:TEMP\dsh-codex-setup.ps1"; if ($LASTEXITCODE -eq 0) { powershell.exe -NoProfile -ExecutionPolicy Bypass -File "$env:TEMP\dsh-codex-setup.ps1" }
61
+ ```
62
+
63
+ 安装助手会先让你选择 **中文(简体)** 或 **English**,然后自动寻找 DSH:
64
+
65
+ - 只找到一个 DSH:直接显示目标并继续;
66
+ - 找到多个 DSH:显示编号、状态和路径,让你输入编号选择,不再直接抛英文报错;
67
+ - 没有安装过插件:明确显示 **安装**;
68
+ - 已经安装当前版或旧版插件:明确显示 **更新**,旧包会按原有安全迁移流程处理;
69
+ - 成功后只提示结果和需要手动重启 DSH,不会擅自重启程序。
70
+
71
+ 普通安装版和 DSH-Portable 都能使用。便携版不需要另装 Node.js 或 pnpm,整个过程不需要
72
+ 管理员权限。安装助手下载核心管理脚本后会校验 Release 提供的 SHA-256,再交给原有管理器
73
+ 执行安装、更新、验证和 PATH 处理。
74
+
75
+ <details>
76
+ <summary>非交互安装 / 兼容旧方式</summary>
77
+
78
+ 需要脚本化安装、Agent 操作,或想明确指定 `Install` 时,仍然可以使用原来的核心管理器:
56
79
 
57
80
  ```powershell
58
81
  curl.exe -fL https://github.com/WSL043/dsh-codex-subscription/releases/latest/download/dsh-codex.ps1 -o "$env:TEMP\dsh-codex.ps1"
59
82
  powershell.exe -NoProfile -ExecutionPolicy Bypass -File "$env:TEMP\dsh-codex.ps1" Install
60
83
  ```
61
84
 
62
- 普通安装版和 DSH-Portable 都能使用。便携版不需要另装 Node.js 或 pnpm,整个过程不需要
63
- 管理员权限。安装器会自动寻找 DSH、验证安装结果,并添加当前用户的 `dsh-codex` 命令;
64
- 它不会修改系统执行策略或擅自重启 DSH。
85
+ 如果有多个 DSH-Portable,也可以继续显式传入 `-PortableRoot`。这个入口保持非交互,适合自动化。
86
+
87
+ </details>
65
88
 
66
89
  如果便携文件夹改过名字或位置,先启动一次正确的 DSH-Portable,再执行安装。完成后手动
67
90
  重启 DSH,让插件生效。
@@ -95,7 +118,7 @@ Codex 订阅搜索复用同一份 ChatGPT 登录,不需要 OpenAI API Key。
95
118
 
96
119
  ## 额度如何显示
97
120
 
98
- ![输入框内的 Codex 剩余额度](https://github.com/WSL043/dsh-codex-subscription/releases/latest/download/composer-quota-en.png)
121
+ ![输入框内的 Codex 剩余额度](https://raw.githubusercontent.com/WSL043/dsh-codex-subscription/main/docs/assets/composer-quota-en.png)
99
122
 
100
123
  - 设置页始终展示服务端返回的详细额度;快捷百分比是 Beta 功能,默认关闭;
101
124
  - 开启后,百分比会显示在输入框内、模型名称左侧,只在选择 Codex 模型时出现;
@@ -118,7 +141,7 @@ Windows 安装器用户只需要两条短命令:
118
141
 
119
142
  更新会校验最新 Release 管理脚本的 SHA-256。卸载会移除插件和 `dsh-codex` 命令,但保留
120
143
  DSH profile、其他插件和已保存的登录信息。旧版本如果还没有短命令,重新执行一次上面的
121
- Windows 首次安装命令即可。
144
+ Windows 首次安装命令即可;安装助手检测到已有插件时会自动显示并执行“更新”。
122
145
 
123
146
  <details>
124
147
  <summary>使用现有 <code>dsh</code> 命令更新或卸载</summary>
@@ -151,8 +174,8 @@ dsh plugin --profile web remove @wsl043/dsh-codex-subscription
151
174
 
152
175
  - 找不到 `dsh-codex`:关闭并重新打开 PowerShell;
153
176
  - 找不到 DSH-Portable:先启动一次正确的便携版,再重新安装;
154
- - 没有 `curl.exe`、检测到多个 DSH,或仍然失败:把 Agent 文档链接发给 Agent,
155
- 不要自行修改系统 PATH、执行策略或删除 profile。
177
+ - 检测到多个 DSH:新版安装助手会列出编号和路径,选你准备使用的那个即可;
178
+ - 没有 `curl.exe` 或仍然失败:把 Agent 文档链接发给 Agent,不要自行修改系统 PATH、执行策略或删除 profile。
156
179
 
157
180
  ## 边界与支持
158
181
 
@@ -176,15 +199,16 @@ service.
176
199
  [installation guide](https://raw.githubusercontent.com/WSL043/dsh-codex-subscription/main/AGENTS.md)
177
200
  to your Agent.
178
201
 
179
- **Windows install:** open PowerShell and run these two lines:
202
+ **Windows install:** open PowerShell and run this one line:
180
203
 
181
204
  ```powershell
182
- curl.exe -fL https://github.com/WSL043/dsh-codex-subscription/releases/latest/download/dsh-codex.ps1 -o "$env:TEMP\dsh-codex.ps1"
183
- powershell.exe -NoProfile -ExecutionPolicy Bypass -File "$env:TEMP\dsh-codex.ps1" Install
205
+ curl.exe -fL https://github.com/WSL043/dsh-codex-subscription/releases/latest/download/dsh-codex-setup.ps1 -o "$env:TEMP\dsh-codex-setup.ps1"; if ($LASTEXITCODE -eq 0) { powershell.exe -NoProfile -ExecutionPolicy Bypass -File "$env:TEMP\dsh-codex-setup.ps1" }
184
206
  ```
185
207
 
186
- DSH-Portable is supported without a separate Node.js or pnpm installation.
187
- After the first install, use `dsh-codex update` to update and
208
+ The setup asks for Chinese or English first, turns multiple DSH copies into a
209
+ numbered choice, and automatically shows **Install** or **Update** based on the
210
+ selected DSH. DSH-Portable is supported without a separate Node.js or pnpm
211
+ installation. After the first install, use `dsh-codex update` to update and
188
212
  `dsh-codex uninstall` to remove it. Open **Settings -> Codex**, sign in with a
189
213
  ChatGPT account that has Codex access, choose a search source, and select a Codex
190
214
  model. For full usage and troubleshooting, see the
package/dsh-codex.ps1 CHANGED
@@ -32,8 +32,12 @@ if ($Managed -and -not $PSBoundParameters.ContainsKey('Action')) {
32
32
 
33
33
  $PackageName = 'dsh-codex-subscription'
34
34
  $LegacyPackageName = '@wsl043/dsh-codex-subscription'
35
- $PackageVersion = '0.3.7'
36
- $PackageSpec = 'https://github.com/WSL043/dsh-codex-subscription/releases/download/v0.3.7/dsh-codex-subscription.tgz'
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'
37
41
  $PnpmVersion = '11.19.0'
38
42
  $PnpmUrl = 'https://registry.npmjs.org/pnpm/-/pnpm-11.19.0.tgz'
39
43
  $PnpmSha512 = '7881F3ED590D472C4A955E2B88B2121791116066DCC88CBCA3849EC9B60F1BBAA6D2CCB221FA91DA4E1C65BEF2BCBE379365AEA7AC539C7BF86DEDC3A1B22DCE'
@@ -224,9 +228,10 @@ function Add-ManagerToUserPath {
224
228
 
225
229
  $userPath = [Environment]::GetEnvironmentVariable('Path', 'User')
226
230
  $updated = Add-UserPathEntry -UserPath $userPath -Directory $Directory
227
- if ([string]::Equals([string] $updated, [string] $userPath, [System.StringComparison]::Ordinal)) { return }
231
+ if ([string]::Equals([string] $updated, [string] $userPath, [System.StringComparison]::Ordinal)) { return $false }
228
232
  [Environment]::SetEnvironmentVariable('Path', $updated, 'User')
229
233
  Publish-UserPathChange
234
+ return $true
230
235
  }
231
236
 
232
237
  function Remove-ManagerFromUserPath {
@@ -243,8 +248,8 @@ function Remove-ManagerFromUserPath {
243
248
  function Start-ManagerCleanup {
244
249
  param([Parameter(Mandatory = $true)][string] $Directory)
245
250
 
246
- $installedScript = Join-Path $Directory 'dsh-codex.ps1'
247
- $installedShim = Join-Path $Directory 'dsh-codex.cmd'
251
+ $installedScript = Join-Path $Directory $ManagerScriptName
252
+ $installedShim = Join-Path $Directory $ManagerShimName
248
253
  if (-not (Test-Path -LiteralPath $installedScript -PathType Leaf) -or
249
254
  -not (Test-Path -LiteralPath $installedShim -PathType Leaf)) {
250
255
  throw 'Refusing to clean an unrecognized manager command directory.'
@@ -263,8 +268,9 @@ $cleanupScript = [Text.Encoding]::Unicode.GetString([Convert]::FromBase64String(
263
268
  try {
264
269
  Wait-Process -Id $ParentId -ErrorAction SilentlyContinue
265
270
  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
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
+ }
268
274
  if ((Test-Path -LiteralPath $directory -PathType Container) -and
269
275
  -not (Get-ChildItem -LiteralPath $directory -Force -ErrorAction SilentlyContinue | Select-Object -First 1)) {
270
276
  Remove-Item -LiteralPath $directory -Force -ErrorAction SilentlyContinue
@@ -281,16 +287,19 @@ try {
281
287
  }
282
288
 
283
289
  function Remove-ManagerCommand {
284
- param([Parameter(Mandatory = $true)][string] $Directory)
290
+ param(
291
+ [Parameter(Mandatory = $true)][string] $Directory,
292
+ [bool] $RemovePath
293
+ )
285
294
 
286
- if (-not $NoModifyPath) { Remove-ManagerFromUserPath -Directory $Directory }
295
+ if ($RemovePath) { Remove-ManagerFromUserPath -Directory $Directory }
287
296
  if (Test-Path -LiteralPath $Directory -PathType Container) {
288
- $installedScript = Join-Path $Directory 'dsh-codex.ps1'
297
+ $installedScript = Join-Path $Directory $ManagerScriptName
289
298
  if ($Managed -and $PSCommandPath -and (Test-SamePath -Left $PSCommandPath -Right $installedScript)) {
290
299
  Start-ManagerCleanup -Directory $Directory
291
300
  return
292
301
  }
293
- foreach ($ownedFile in @('dsh-codex.ps1', 'dsh-codex.cmd')) {
302
+ foreach ($ownedFile in @($ManagerScriptName, $LegacyManagerScriptName, $ManagerShimName, $ManagerStateName)) {
294
303
  $ownedPath = Join-Path $Directory $ownedFile
295
304
  if (Test-Path -LiteralPath $ownedPath -PathType Leaf) {
296
305
  Remove-Item -LiteralPath $ownedPath -Force
@@ -309,7 +318,7 @@ function Install-ManagerCommand {
309
318
  throw 'The manager command can only be installed from a downloaded script file.'
310
319
  }
311
320
  New-Item -ItemType Directory -Force -Path $Directory | Out-Null
312
- $installedScript = Join-Path $Directory 'dsh-codex.ps1'
321
+ $installedScript = Join-Path $Directory $ManagerScriptName
313
322
  if (-not (Test-SamePath -Left $PSCommandPath -Right $installedScript)) {
314
323
  $stagedScript = Join-Path $Directory ('.dsh-codex-' + [guid]::NewGuid().ToString('N') + '.ps1')
315
324
  try {
@@ -322,13 +331,19 @@ function Install-ManagerCommand {
322
331
  }
323
332
  }
324
333
 
334
+ $legacyInstalledScript = Join-Path $Directory $LegacyManagerScriptName
335
+ if (Test-Path -LiteralPath $legacyInstalledScript -PathType Leaf) {
336
+ Remove-Item -LiteralPath $legacyInstalledScript -Force
337
+ }
338
+
325
339
  $shim = @'
326
340
  @echo off
327
- powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0dsh-codex.ps1" -Managed %*
341
+ powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0dsh-codex-manager.ps1" -Managed %*
328
342
  exit /b %ERRORLEVEL%
329
343
  '@
330
- [System.IO.File]::WriteAllText((Join-Path $Directory 'dsh-codex.cmd'), $shim, [System.Text.Encoding]::ASCII)
331
- if (-not $NoModifyPath) { Add-ManagerToUserPath -Directory $Directory }
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
332
347
  }
333
348
 
334
349
  function Resolve-FullPath {
@@ -336,6 +351,61 @@ function Resolve-FullPath {
336
351
  return [System.IO.Path]::GetFullPath([Environment]::ExpandEnvironmentVariables($Path))
337
352
  }
338
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
+
339
409
  function Get-PortableLayout {
340
410
  param([Parameter(Mandatory = $true)][string] $Root)
341
411
 
@@ -442,6 +512,11 @@ function Find-CommonPortables {
442
512
  }
443
513
 
444
514
  function Get-ManagerTarget {
515
+ param(
516
+ [AllowNull()][string] $PreferredMode,
517
+ [AllowNull()] $SavedState
518
+ )
519
+
445
520
  if ($PortableRoot) {
446
521
  $layout = Get-PortableLayout $PortableRoot
447
522
  if ($null -eq $layout) {
@@ -450,6 +525,40 @@ function Get-ManagerTarget {
450
525
  return New-PortableTarget $layout
451
526
  }
452
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
+
453
562
  $layout = Find-PortableFromCurrentDirectory
454
563
  if ($null -ne $layout) {
455
564
  return New-PortableTarget $layout
@@ -638,12 +747,36 @@ function Get-InstalledPackages {
638
747
  }
639
748
 
640
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
+ }
641
773
  if ($Managed -and $Action -eq 'Update' -and -not $SkipSelfUpdate -and -not $DryRun) {
642
774
  Invoke-LatestManager -InstalledCommandRoot $managerCommandRoot
643
775
  exit 0
644
776
  }
645
777
 
646
- $target = Get-ManagerTarget
778
+ $preferredMode = if ($Managed -and $null -ne $managerState) { [string] $managerState.mode } else { $null }
779
+ $target = Get-ManagerTarget -PreferredMode $preferredMode -SavedState $managerState
647
780
  $managerCommand = Join-Path $managerCommandRoot 'dsh-codex.cmd'
648
781
  $pnpmDirectory = Get-PnpmDirectory $target
649
782
  $pnpmStore = Get-PnpmStore $target
@@ -750,7 +883,11 @@ try {
750
883
  if ($entryCount -ne 0 -or $legacyEntryCount -ne 0) {
751
884
  throw 'The plugin package was removed, but its profile entry is still present.'
752
885
  }
753
- Remove-ManagerCommand -Directory $managerCommandRoot
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
754
891
  Write-Host 'Uninstalled. The DSH profile and saved credentials were kept.'
755
892
  } else {
756
893
  $installedPackage = @($installedAfter | Where-Object { $_.Name -eq $PackageName } | Select-Object -First 1)
@@ -762,7 +899,10 @@ try {
762
899
  if ($installedAfterNames -contains $LegacyPackageName) { throw 'The legacy package is still present after migration.' }
763
900
  if ($entryCount -ne 1) { throw "Expected one plugin profile entry, found $entryCount." }
764
901
  if ($legacyEntryCount -ne 0) { throw 'The legacy plugin profile entry is still present after migration.' }
765
- Install-ManagerCommand -Directory $managerCommandRoot
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
766
906
  $verb = if ($Action -eq 'Update') { 'Updated.' } else { 'Installed.' }
767
907
  Write-Host "$verb Restart DSH manually to load the change if it is currently running."
768
908
  }
package/lib/client.js CHANGED
@@ -6,7 +6,6 @@ window.__ModuleLoader__.load({
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
7
  let react = require("react");
8
8
  let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
9
- let _deepseek_ai_dsh_client_ui_attachment = require("@deepseek-ai/dsh-client-ui-attachment");
10
9
  let react_jsx_runtime = require("react/jsx-runtime");
11
10
  //#region src/settings-contract.js
12
11
  const SETTINGS_NAMESPACE = "codex-subscription";
@@ -203,7 +202,7 @@ window.__ModuleLoader__.load({
203
202
  .codexSubscriptionCreditBalance{display:flex;flex-direction:column;gap:6px}.codexSubscriptionCreditBalance span,.codexSubscriptionCreditLabel{font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary)}.codexSubscriptionCreditBalance strong{font:600 18px/24px ui-monospace,SFMono-Regular,Consolas,monospace;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}
204
203
  .codexSubscriptionSpendLimit{display:flex;flex-direction:column;gap:8px}.codexSubscriptionSpendTop{display:flex;align-items:baseline;justify-content:space-between;gap:12px}.codexSubscriptionSpendTop strong{font:600 16px/22px ui-monospace,SFMono-Regular,Consolas,monospace;font-variant-numeric:tabular-nums}.codexSubscriptionSpendLimit progress{width:100%;height:6px;border:0;border-radius:999px;overflow:hidden;background:var(--dsw-alias-border-l3);accent-color:var(--dsw-alias-brand-primary,#3964fe);-webkit-appearance:none;appearance:none}.codexSubscriptionSpendLimit progress::-webkit-progress-bar{background:var(--dsw-alias-border-l3);border-radius:999px}.codexSubscriptionSpendLimit progress::-webkit-progress-value{background:var(--dsw-alias-brand-primary,#3964fe);border-radius:999px}.codexSubscriptionSpendLimit progress::-moz-progress-bar{background:var(--dsw-alias-brand-primary,#3964fe);border-radius:999px}
205
204
  .codexComposerQuota{display:inline-flex;align-items:center;flex:0 0 auto;height:28px;box-sizing:border-box;margin-right:-4px;padding:0;color:var(--dsw-alias-label-secondary);font-family:inherit;font-size:12px;line-height:20px;font-weight:500;font-variant-numeric:tabular-nums;white-space:nowrap;user-select:none}
206
- .codexImageTool{display:flex;flex-direction:column;gap:8px;margin:4px 0;color:var(--dsw-alias-label-primary)}.codexImageToolRow{display:flex;align-items:center;min-height:24px;gap:8px;font-size:13px;line-height:20px}.codexImageToolIcon{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;color:var(--dsw-alias-label-secondary)}.codexImageToolIcon::before{content:'';width:8px;height:8px;border:1.5px solid currentColor;border-radius:3px}.codexImageTool[data-state=running] .codexImageToolIcon::before{border-radius:50%;border-right-color:transparent;animation:codexImageSpin 800ms linear infinite}.codexImageTool[data-state=error] .codexImageToolIcon::before{border-color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-state-error-primary)}.codexImageToolTitle{font-weight:500}.codexImageToolState{color:var(--dsw-alias-label-tertiary)}.codexImageToolError{margin:0 0 0 24px;font-size:12px;line-height:18px;color:var(--dsw-alias-state-error-primary)}.codexImageToolGallery{margin-left:24px}@keyframes codexImageSpin{to{transform:rotate(360deg)}}
205
+ .codexImageTool{display:flex;flex-direction:column;gap:8px;margin:4px 0;color:var(--dsw-alias-label-primary)}.codexImageToolRow{display:flex;align-items:center;min-height:24px;gap:8px;font-size:13px;line-height:20px}.codexImageToolIcon{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;color:var(--dsw-alias-label-secondary)}.codexImageToolIcon::before{content:'';width:8px;height:8px;border:1.5px solid currentColor;border-radius:3px}.codexImageTool[data-state=running] .codexImageToolIcon::before{border-radius:50%;border-right-color:transparent;animation:codexImageSpin 800ms linear infinite}.codexImageTool[data-state=error] .codexImageToolIcon::before{border-color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-state-error-primary)}.codexImageToolTitle{font-weight:500}.codexImageToolState{color:var(--dsw-alias-label-tertiary)}.codexImageToolError{margin:0 0 0 24px;font-size:12px;line-height:18px;color:var(--dsw-alias-state-error-primary)}.codexImageToolGallery{margin-left:24px}.codexGeneratedImageFrame{display:flex;align-items:center;justify-content:center;width:min(240px,100%);min-height:120px;padding:0;overflow:hidden;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-tertiary);cursor:pointer}.codexGeneratedImageFrame img{display:block;width:100%;max-height:240px;object-fit:contain}.codexGeneratedImageRetry{min-height:36px;padding:0 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);cursor:pointer}.codexGeneratedImageLightbox{position:fixed;inset:0;z-index:1000;display:flex;align-items:center;justify-content:center;padding:32px;border:0;background:rgba(0,0,0,.72)}.codexGeneratedImageLightbox img{display:block;max-width:min(1100px,calc(100vw - 64px));max-height:calc(100vh - 64px);object-fit:contain}.codexGeneratedImageClose{position:absolute;top:16px;right:16px;width:36px;height:36px;border:1px solid rgba(255,255,255,.35);border-radius:50%;background:rgba(0,0,0,.48);color:#fff;font-size:20px;line-height:1;cursor:pointer}@keyframes codexImageSpin{to{transform:rotate(360deg)}}
207
206
  @container (max-width:560px){.codexSubscriptionCreditRows{grid-template-columns:1fr}}
208
207
  @container (max-width:480px){.codexSubscriptionAccountRow,.codexSubscriptionSectionHead{align-items:flex-start;flex-direction:column}.codexSubscriptionActions{width:100%}.codexSubscriptionSearchChoices{grid-template-columns:1fr}}
209
208
  @media(max-width:640px){.codexSubscriptionCard{padding:14px}}
@@ -228,17 +227,88 @@ window.__ModuleLoader__.load({
228
227
  const date = new Date(value);
229
228
  return Number.isFinite(date.getTime()) ? date : void 0;
230
229
  };
231
- const imageGalleryLabels = (t) => ({
232
- image: t("imageLabel"),
233
- open: t("imageOpen"),
234
- openNamed: (value) => fill(t("imageOpenNamed"), { value }),
235
- loading: t("imageLoading"),
236
- loadFailed: t("imageLoadFailed"),
237
- lightbox: {
238
- dialog: t("imagePreview"),
239
- close: t("imageClosePreview")
240
- }
241
- });
230
+ function CodexGeneratedImage({ attachment, loadImage, t }) {
231
+ const [attempt, setAttempt] = (0, react.useState)(0);
232
+ const [error, setError] = (0, react.useState)(false);
233
+ const [open, setOpen] = (0, react.useState)(false);
234
+ const [src, setSrc] = (0, react.useState)();
235
+ const closeRef = (0, react.useRef)(null);
236
+ const triggerRef = (0, react.useRef)(null);
237
+ (0, react.useEffect)(() => {
238
+ let live = true;
239
+ setError(false);
240
+ setSrc(void 0);
241
+ loadImage(attachment).then((value) => {
242
+ if (live) setSrc(value);
243
+ }).catch(() => {
244
+ if (live) setError(true);
245
+ });
246
+ return () => {
247
+ live = false;
248
+ };
249
+ }, [
250
+ attachment,
251
+ loadImage,
252
+ attempt
253
+ ]);
254
+ (0, react.useEffect)(() => {
255
+ if (!open) return void 0;
256
+ const handleKeyDown = (event) => {
257
+ if (event.key === "Escape") {
258
+ event.preventDefault();
259
+ setOpen(false);
260
+ } else if (event.key === "Tab") {
261
+ event.preventDefault();
262
+ closeRef.current?.focus();
263
+ }
264
+ };
265
+ document.addEventListener("keydown", handleKeyDown);
266
+ closeRef.current?.focus();
267
+ return () => {
268
+ document.removeEventListener("keydown", handleKeyDown);
269
+ triggerRef.current?.focus();
270
+ };
271
+ }, [open]);
272
+ const label = attachment.name ?? t("imageLabel");
273
+ if (error) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
274
+ type: "button",
275
+ className: "codexGeneratedImageRetry",
276
+ onClick: () => setAttempt((value) => value + 1),
277
+ children: t("imageLoadFailed")
278
+ });
279
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
280
+ ref: triggerRef,
281
+ type: "button",
282
+ className: "codexGeneratedImageFrame",
283
+ title: t("imageOpen"),
284
+ "aria-label": fill(t("imageOpenNamed"), { value: label }),
285
+ onClick: () => {
286
+ if (src !== void 0) setOpen(true);
287
+ },
288
+ children: src === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("imageLoading") }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
289
+ src,
290
+ alt: label
291
+ })
292
+ }), !open || src === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
293
+ className: "codexGeneratedImageLightbox",
294
+ role: "dialog",
295
+ "aria-modal": "true",
296
+ "aria-label": t("imagePreview"),
297
+ onClick: () => setOpen(false),
298
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
299
+ src,
300
+ alt: label,
301
+ onClick: (event) => event.stopPropagation()
302
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
303
+ ref: closeRef,
304
+ type: "button",
305
+ className: "codexGeneratedImageClose",
306
+ "aria-label": t("imageClosePreview"),
307
+ onClick: () => setOpen(false),
308
+ children: "×"
309
+ })]
310
+ })] });
311
+ }
242
312
  function CodexImageToolRow({ block, loadImage, t }) {
243
313
  const settled = block?.kind === "tool-result";
244
314
  const image = settled ? block.content.find((item) => item?.type === "image" && item.attachment !== void 0) : void 0;
@@ -269,11 +339,10 @@ window.__ModuleLoader__.load({
269
339
  }),
270
340
  image === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
271
341
  className: "codexImageToolGallery",
272
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_attachment.ImageGallery, {
273
- images: [{ attachment: image.attachment }],
274
- load: loadImage,
275
- align: "start",
276
- labels: imageGalleryLabels(t)
342
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CodexGeneratedImage, {
343
+ attachment: image.attachment,
344
+ loadImage,
345
+ t
277
346
  })
278
347
  }),
279
348
  error === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
package/lib/index.js CHANGED
@@ -55,7 +55,7 @@ var DshOAuthCredentialStore = class {
55
55
  });
56
56
  return current;
57
57
  }
58
- async read(providerId, options) {
58
+ async #read(providerId, options) {
59
59
  assertProvider(providerId);
60
60
  abortIfNeeded(options);
61
61
  let hit = await this.credentials.resolve(this.ref);
@@ -72,6 +72,9 @@ var DshOAuthCredentialStore = class {
72
72
  if (hit?.value === void 0 || hit.value === "") return void 0;
73
73
  return parseOAuthCredential(hit.value);
74
74
  }
75
+ read(providerId, options) {
76
+ return this.#enqueue(providerId, () => this.#read(providerId, options), options);
77
+ }
75
78
  async list(options) {
76
79
  abortIfNeeded(options);
77
80
  return await this.read(PROVIDER$1, options) === void 0 ? [] : [{
@@ -81,7 +84,7 @@ var DshOAuthCredentialStore = class {
81
84
  }
82
85
  modify(providerId, update, options) {
83
86
  return this.#enqueue(providerId, async () => {
84
- const current = await this.read(providerId, options);
87
+ const current = await this.#read(providerId, options);
85
88
  const next = await update(clone(current));
86
89
  abortIfNeeded(options);
87
90
  if (next === void 0) return current;
@@ -460,6 +463,7 @@ function openaiCodexSubscriptionProvider() {
460
463
  })
461
464
  });
462
465
  }
466
+ const USER_AGENT = `dsh-codex-subscription/1.0.4`;
463
467
  //#endregion
464
468
  //#region src/codex-search.js
465
469
  const CODEX_SEARCH_PROVIDER_ID = "codex-subscription";
@@ -540,7 +544,7 @@ function createCodexSearchProvider(options) {
540
544
  accept: "application/json",
541
545
  "content-type": "application/json",
542
546
  originator: "pi",
543
- "user-agent": "dsh-codex-subscription/0.3.7"
547
+ "user-agent": USER_AGENT
544
548
  },
545
549
  body: JSON.stringify({
546
550
  id,
@@ -743,7 +747,7 @@ function createCodexImageTool(options) {
743
747
  "content-type": "application/json",
744
748
  originator: "pi",
745
749
  "x-codex-image-turn-id": String(exec.callId),
746
- "user-agent": "dsh-codex-subscription"
750
+ "user-agent": USER_AGENT
747
751
  },
748
752
  body: JSON.stringify({
749
753
  prompt,
@@ -915,6 +919,7 @@ function createCodexUsageReader(options) {
915
919
  const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
916
920
  let cached;
917
921
  let inFlight;
922
+ let generation = 0;
918
923
  const load = async (signal) => {
919
924
  const auth = await getAuth({ signal });
920
925
  const credential = await readCredential({ signal });
@@ -929,7 +934,7 @@ function createCodexUsageReader(options) {
929
934
  "chatgpt-account-id": accountId,
930
935
  accept: "application/json",
931
936
  "cache-control": "no-store",
932
- "user-agent": "dsh-codex-subscription/0.3.7"
937
+ "user-agent": USER_AGENT
933
938
  },
934
939
  signal: requestSignal(signal, timeoutMs)
935
940
  });
@@ -949,8 +954,9 @@ function createCodexUsageReader(options) {
949
954
  read({ force = false, signal } = {}) {
950
955
  if (!force && cached !== void 0 && now() - cached.fetchedAt < ttlMs) return Promise.resolve(structuredClone(cached));
951
956
  if (inFlight !== void 0) return inFlight.then(structuredClone);
957
+ const currentGeneration = generation;
952
958
  const current = load(signal).then((value) => {
953
- cached = structuredClone(value);
959
+ if (generation === currentGeneration) cached = structuredClone(value);
954
960
  return structuredClone(value);
955
961
  }).finally(() => {
956
962
  if (inFlight === current) inFlight = void 0;
@@ -959,7 +965,9 @@ function createCodexUsageReader(options) {
959
965
  return current;
960
966
  },
961
967
  clear() {
968
+ generation += 1;
962
969
  cached = void 0;
970
+ inFlight = void 0;
963
971
  }
964
972
  });
965
973
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-codex-subscription",
3
- "version": "0.3.7",
3
+ "version": "1.0.4",
4
4
  "packageManager": "pnpm@11.19.0",
5
5
  "description": "ChatGPT and Codex subscriptions for DeepSeek Harness with OAuth login, image generation, quota, and subscription web search",
6
6
  "type": "module",
@@ -80,46 +80,44 @@
80
80
  },
81
81
  "peerDependencies": {
82
82
  "@deepseek-ai/cordis": "4.0.1",
83
- "@deepseek-ai/dsh-api-remotes": "0.1.0-rc.6 || 0.1.0-rc.7",
84
- "@deepseek-ai/dsh-client-connection": "0.1.0-rc.6 || 0.1.0-rc.7",
85
- "@deepseek-ai/dsh-client-locale": "0.1.0-rc.6 || 0.1.0-rc.7",
86
- "@deepseek-ai/dsh-client-runtime": "0.1.0-rc.6 || 0.1.0-rc.7",
87
- "@deepseek-ai/dsh-client-ui-attachment": "0.1.0-rc.6 || 0.1.0-rc.7",
88
- "@deepseek-ai/dsh-client-ui-conversation": "0.1.0-rc.6 || 0.1.0-rc.7",
89
- "@deepseek-ai/dsh-client-ui-model-selection": "0.1.0-rc.6 || 0.1.0-rc.7",
90
- "@deepseek-ai/dsh-client-ui-primitives": "0.1.0-rc.6 || 0.1.0-rc.7",
91
- "@deepseek-ai/dsh-client-ui-settings": "0.1.0-rc.6 || 0.1.0-rc.7",
92
- "@deepseek-ai/dsh-client-ui-slots": "0.1.0-rc.6 || 0.1.0-rc.7",
93
- "@deepseek-ai/dsh-client-ui-tool": "0.1.0-rc.6 || 0.1.0-rc.7",
94
- "@deepseek-ai/dsh-credentials": "0.1.0-rc.6 || 0.1.0-rc.7",
95
- "@deepseek-ai/dsh-llm": "0.1.0-rc.6 || 0.1.0-rc.7",
96
- "@deepseek-ai/dsh-llm-pi-ai": "0.1.0-rc.6 || 0.1.0-rc.7",
97
- "@deepseek-ai/dsh-settings": "0.1.0-rc.6 || 0.1.0-rc.7",
98
- "@deepseek-ai/dsh-tools": "0.1.0-rc.6 || 0.1.0-rc.7",
99
- "@deepseek-ai/dsh-web": "0.1.0-rc.6 || 0.1.0-rc.7",
83
+ "@deepseek-ai/dsh-api-remotes": "0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8",
84
+ "@deepseek-ai/dsh-client-connection": "0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8",
85
+ "@deepseek-ai/dsh-client-locale": "0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8",
86
+ "@deepseek-ai/dsh-client-runtime": "0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8",
87
+ "@deepseek-ai/dsh-client-ui-conversation": "0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8",
88
+ "@deepseek-ai/dsh-client-ui-model-selection": "0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8",
89
+ "@deepseek-ai/dsh-client-ui-primitives": "0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8",
90
+ "@deepseek-ai/dsh-client-ui-settings": "0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8",
91
+ "@deepseek-ai/dsh-client-ui-slots": "0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8",
92
+ "@deepseek-ai/dsh-client-ui-tool": "0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8",
93
+ "@deepseek-ai/dsh-credentials": "0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8",
94
+ "@deepseek-ai/dsh-llm": "0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8",
95
+ "@deepseek-ai/dsh-llm-pi-ai": "0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8",
96
+ "@deepseek-ai/dsh-settings": "0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8",
97
+ "@deepseek-ai/dsh-tools": "0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8",
98
+ "@deepseek-ai/dsh-web": "0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8",
100
99
  "@deepseek-ai/schemastery": "3.18.1",
101
100
  "@earendil-works/pi-ai": "0.82.1",
102
101
  "react": "^18.2.0"
103
102
  },
104
103
  "devDependencies": {
105
104
  "@deepseek-ai/cordis": "4.0.1",
106
- "@deepseek-ai/dsh-api-remotes": "0.1.0-rc.7",
107
- "@deepseek-ai/dsh-client-connection": "0.1.0-rc.7",
108
- "@deepseek-ai/dsh-client-locale": "0.1.0-rc.7",
109
- "@deepseek-ai/dsh-client-runtime": "0.1.0-rc.7",
110
- "@deepseek-ai/dsh-client-ui-attachment": "0.1.0-rc.7",
111
- "@deepseek-ai/dsh-client-ui-conversation": "0.1.0-rc.7",
112
- "@deepseek-ai/dsh-client-ui-model-selection": "0.1.0-rc.7",
113
- "@deepseek-ai/dsh-client-ui-primitives": "0.1.0-rc.7",
114
- "@deepseek-ai/dsh-client-ui-settings": "0.1.0-rc.7",
115
- "@deepseek-ai/dsh-client-ui-slots": "0.1.0-rc.7",
116
- "@deepseek-ai/dsh-client-ui-tool": "0.1.0-rc.7",
117
- "@deepseek-ai/dsh-credentials": "0.1.0-rc.7",
118
- "@deepseek-ai/dsh-llm": "0.1.0-rc.7",
119
- "@deepseek-ai/dsh-llm-pi-ai": "0.1.0-rc.7",
120
- "@deepseek-ai/dsh-settings": "0.1.0-rc.7",
121
- "@deepseek-ai/dsh-tools": "0.1.0-rc.7",
122
- "@deepseek-ai/dsh-web": "0.1.0-rc.7",
105
+ "@deepseek-ai/dsh-api-remotes": "0.1.0-rc.8",
106
+ "@deepseek-ai/dsh-client-connection": "0.1.0-rc.8",
107
+ "@deepseek-ai/dsh-client-locale": "0.1.0-rc.8",
108
+ "@deepseek-ai/dsh-client-runtime": "0.1.0-rc.8",
109
+ "@deepseek-ai/dsh-client-ui-conversation": "0.1.0-rc.8",
110
+ "@deepseek-ai/dsh-client-ui-model-selection": "0.1.0-rc.8",
111
+ "@deepseek-ai/dsh-client-ui-primitives": "0.1.0-rc.8",
112
+ "@deepseek-ai/dsh-client-ui-settings": "0.1.0-rc.8",
113
+ "@deepseek-ai/dsh-client-ui-slots": "0.1.0-rc.8",
114
+ "@deepseek-ai/dsh-client-ui-tool": "0.1.0-rc.8",
115
+ "@deepseek-ai/dsh-credentials": "0.1.0-rc.8",
116
+ "@deepseek-ai/dsh-llm": "0.1.0-rc.8",
117
+ "@deepseek-ai/dsh-llm-pi-ai": "0.1.0-rc.8",
118
+ "@deepseek-ai/dsh-settings": "0.1.0-rc.8",
119
+ "@deepseek-ai/dsh-tools": "0.1.0-rc.8",
120
+ "@deepseek-ai/dsh-web": "0.1.0-rc.8",
123
121
  "@deepseek-ai/schemastery": "3.18.1",
124
122
  "@earendil-works/pi-ai": "0.82.1",
125
123
  "react": "18.3.1",