dsh-codex-subscription 1.3.0 → 1.4.1

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 installation and profile. Use `web` only when it is the user's target.
9
- - Use the exact `1.3.0` package below; do not install a moving branch.
9
+ - Use the exact `1.4.1` package below; do not install a moving branch.
10
10
  - Never print OAuth credentials, account IDs, authorization callbacks, or credential-store contents.
11
11
  - Preserve the DSH profile, unrelated plugins, sessions, and saved sign-in.
12
12
  - Do not start, stop, or restart DSH without explicit permission.
@@ -14,23 +14,19 @@ Use this guide when a user asks an Agent to install, update, verify, or remove
14
14
 
15
15
  ## Locate DSH
16
16
 
17
- On Windows, check the command and common Portable entry points without recursively scanning disks:
17
+ On Windows, check the standard DSH and official npm entry points without recursively scanning disks:
18
18
 
19
19
  ```powershell
20
20
  Get-Command dsh -ErrorAction SilentlyContinue
21
21
  Get-Command npx -ErrorAction SilentlyContinue
22
- Test-Path -LiteralPath '.\dsh.exe' -PathType Leaf
23
- Test-Path -LiteralPath "$env:USERPROFILE\Downloads\DSH-Portable\dsh.exe" -PathType Leaf
24
22
  Get-CimInstance Win32_Process -Filter "Name = 'node.exe'" |
25
23
  Where-Object { $_.CommandLine -match '@deepseek-ai[\\/]dsh[\\/]lib[\\/]bin\.js' } |
26
24
  Select-Object ExecutablePath, CommandLine
27
25
  ```
28
26
 
29
- A current DSH-Portable includes `dsh.exe`. Missing system Node.js or pnpm is normal; do not install
30
- either globally for this plugin. If the Portable copy does not contain `dsh.exe`, update DSH-Portable
31
- instead of recreating its package-manager environment in this installer. For a matching running process,
32
- the Portable root is the parent of `runtime`; use its `dsh.exe`. If several targets remain possible, stop
33
- and ask the user which one to change.
27
+ DSH-Portable exposes the same standard `dsh plugin` contract. Missing system pnpm is normal; do not
28
+ install it globally for this plugin. If several targets remain possible, stop and ask the user which one
29
+ to change.
34
30
 
35
31
  ## Install or update
36
32
 
@@ -38,19 +34,13 @@ For an official DSH installation run through npm, keep the complete `npx` prefix
38
34
  run command does not create a global `dsh` command:
39
35
 
40
36
  ```sh
41
- npx -y @deepseek-ai/dsh@0.1.1-rc.2 plugin --profile web add dsh-codex-subscription@1.3.0
37
+ npx -y @deepseek-ai/dsh@0.1.1-rc.2 plugin --profile web add dsh-codex-subscription@1.4.1
42
38
  ```
43
39
 
44
40
  With an existing global `dsh` command:
45
41
 
46
42
  ```sh
47
- dsh plugin --profile web add dsh-codex-subscription@1.3.0
48
- ```
49
-
50
- From a DSH-Portable folder:
51
-
52
- ```powershell
53
- .\dsh.exe plugin --profile web add dsh-codex-subscription@1.3.0
43
+ dsh plugin --profile web add dsh-codex-subscription@1.4.1
54
44
  ```
55
45
 
56
46
  Use the same `add` command to update or repair. This is the complete package-changing operation.
@@ -66,11 +56,9 @@ dsh plugin --profile web list dsh-codex-subscription --depth 0
66
56
  dsh --profile web --dump-config
67
57
  ```
68
58
 
69
- For DSH-Portable, replace `dsh` with `.\dsh.exe`. Static acceptance requires:
70
-
71
59
  For the official npm route, run the same checks with `npx -y @deepseek-ai/dsh@0.1.1-rc.2` in place of `dsh`.
72
60
 
73
- 1. `dsh-codex-subscription` version `1.3.0` appears exactly once.
61
+ 1. `dsh-codex-subscription` version `1.4.1` appears exactly once.
74
62
  2. `codex-subscription` appears exactly once in the composed config.
75
63
  3. No unrelated plugin or profile was changed and DSH was not restarted.
76
64
 
@@ -85,12 +73,6 @@ the user explicitly asks for a live model, search, or image-generation check.
85
73
  dsh plugin --profile web remove dsh-codex-subscription
86
74
  ```
87
75
 
88
- For DSH-Portable:
89
-
90
- ```powershell
91
- .\dsh.exe plugin --profile web remove dsh-codex-subscription
92
- ```
93
-
94
76
  For the official npm route:
95
77
 
96
78
  ```sh
package/README.md CHANGED
@@ -37,7 +37,7 @@ With an existing `dsh` command, install with:
37
37
  dsh plugin --profile web add dsh-codex-subscription
38
38
  ```
39
39
 
40
- From a DSH-Portable product folder, run `./dsh plugin ...` (`.\dsh.exe plugin ...` in Windows PowerShell). See below for the complete official npm, Agent, update, and uninstall routes.
40
+ DSH-Portable exposes the same standard plugin command, so the command above also applies there. See below for the complete official npm, Agent, update, and uninstall routes.
41
41
 
42
42
  ## Why this plugin
43
43
 
@@ -46,6 +46,7 @@ From a DSH-Portable product folder, run `./dsh plugin ...` (`.\dsh.exe plugin ..
46
46
  | **Subscription models** | Sign in to ChatGPT and use Codex without an OpenAI API key or Codex CLI |
47
47
  | **Recoverable and diagnosable** | Sign-in state reconciles automatically; Settings can create a support report without credentials or account identifiers |
48
48
  | **Visible quota** | Keep backend-provided standard Codex, Spark, and other limits separate |
49
+ | **Composer quota** | Choose a compact percentage, progress bar, or no inline quota display |
49
50
  | **Safe quota reset** | Use a reset only for an exhausted Codex quota, with a cooldown, checkbox, and typed confirmation |
50
51
  | **Subscription search** | Explicitly choose DSH default search or Codex subscription search |
51
52
  | **Codex image generation** | Describe an image in a DSH conversation and view the generated result in that session |
@@ -116,7 +117,7 @@ npx -y @deepseek-ai/dsh@0.1.1-rc.2 --profile web --dump-config
116
117
  </details>
117
118
 
118
119
  <details>
119
- <summary>An existing <code>dsh</code> command or a DSH-Portable folder</summary>
120
+ <summary>An existing <code>dsh</code> command</summary>
120
121
 
121
122
  ```sh
122
123
  dsh plugin --profile web add dsh-codex-subscription
@@ -124,8 +125,7 @@ dsh plugin --profile web list dsh-codex-subscription --depth 0
124
125
  dsh --profile web --dump-config
125
126
  ```
126
127
 
127
- For DSH-Portable, replace `dsh` with `./dsh` (`.\dsh.exe` in PowerShell). The plugin list should contain
128
- one `dsh-codex-subscription`, and the config should contain one `codex-subscription` entry.
128
+ The plugin list should contain one `dsh-codex-subscription`, and the config should contain one `codex-subscription` entry.
129
129
 
130
130
  </details>
131
131
 
@@ -144,7 +144,7 @@ Restart DSH manually after installation, then:
144
144
  - Actual backend-provided quota, reset time, and freshness;
145
145
  - Separate standard Codex, Codex-Spark, Credits, and other independent limits;
146
146
  - Deliberate quota-reset redemption for exhausted Codex limits, with layered confirmation and no automatic retry;
147
- - Optional composer quota for the selected Codex model (Beta, off by default);
147
+ - Optional percentage or progress bar for the selected Codex model (off by default);
148
148
  - Standard or Fast mode for supported Codex models directly in the composer (Beta);
149
149
  - A copyable support report in Settings that excludes OAuth credentials, account identifiers, and authorization timestamps;
150
150
  - Visible errors when subscription routing is unavailable, with no silent paid fallback.
@@ -155,8 +155,8 @@ Restart DSH manually after installation, then:
155
155
  <img src="https://raw.githubusercontent.com/WSL043/dsh-codex-subscription/main/docs/assets/composer-quota-en.png" width="800" alt="Codex quota inside the composer">
156
156
  </p>
157
157
 
158
- The compact percentage appears only for a selected Codex model. Standard Codex uses the lowest remaining
159
- window returned by the service; Spark uses its independent quota. The plugin does not hard-code a
158
+ Choose Off, Percent, or Progress bar in Settings. The compact display appears only for a selected Codex model.
159
+ Standard Codex uses the lowest remaining window returned by the service; Spark uses its independent quota. The plugin does not hard-code a
160
160
  “5-hour + weekly” layout or invent Credits and spending caps that the service did not return.
161
161
 
162
162
  ### Safe quota reset
@@ -197,14 +197,10 @@ dsh plugin --profile web remove dsh-codex-subscription
197
197
 
198
198
  </details>
199
199
 
200
- From a DSH-Portable folder, replace `dsh` above with `.\dsh.exe`.
201
-
202
200
  ## Troubleshooting
203
201
 
204
202
  - **`dsh` is not recognized:** the official npm route does not create a global `dsh` command; use the complete `npx -y @deepseek-ai/dsh@0.1.1-rc.2 ...` command above;
205
- - **`dsh.exe` is not recognized:** that file is not in the current folder; enter the DSH-Portable folder first or use the one-line Windows setup;
206
- - **DSH-Portable is not found:** enter its folder and rerun setup, or run `.\dsh.exe plugin --profile web add dsh-codex-subscription` directly;
207
- - **More than one DSH exists:** the one-line setup combines running copies with Portable folders detected in common locations; enter its number. For an Agent, use the intended folder or pass `-DshPath` explicitly;
203
+ - **More than one DSH exists:** run the standard command from the intended DSH environment. For an Agent, specify the target or pass `-DshPath` explicitly;
208
204
  - **Setup still fails:** send the Agent guide above to an Agent. Do not delete the profile or change the system PATH to force an install.
209
205
  - **Need to report a problem:** generate a **Support diagnostics** report at the bottom of Settings, then open the [bug report form](https://github.com/WSL043/dsh-codex-subscription/issues/new?template=install-problem.yml). The form collects exact DSH/plugin versions and reproduction steps. The generated report excludes credentials and account identifiers; still do not attach sign-in URLs, authorization codes, or browser callback addresses.
210
206
 
package/README.zh-CN.md CHANGED
@@ -38,7 +38,7 @@
38
38
  dsh plugin --profile web add dsh-codex-subscription
39
39
  ```
40
40
 
41
- DSH-Portable 用户在产品目录中运行 `./dsh plugin ...`,Windows PowerShell 使用 `.\dsh.exe plugin ...`。完整的官方 npm、Agent 安装、更新和卸载方式见下文。
41
+ DSH-Portable 也提供相同的标准插件命令,因此同样使用上面的命令。完整的官方 npm、Agent 安装、更新和卸载方式见下文。
42
42
 
43
43
  ## 核心优势
44
44
 
@@ -47,6 +47,7 @@ DSH-Portable 用户在产品目录中运行 `./dsh plugin ...`,Windows PowerSh
47
47
  | **订阅模型直连** | 登录 ChatGPT 后直接使用 Codex,不需要 OpenAI API Key 或 Codex CLI |
48
48
  | **可恢复、可诊断** | 登录状态会自动对账;设置页可生成不含凭据和账号标识的支持报告 |
49
49
  | **额度可见** | 普通 Codex、Spark 等服务端实际返回的额度分开显示 |
50
+ | **输入框额度** | 可选择紧凑百分比、进度条或完全关闭输入框额度显示 |
50
51
  | **安全额度重置** | 仅在 Codex 额度用尽后使用重置,并经过冷静期、勾选和输入确认 |
51
52
  | **订阅搜索** | 可在 DSH 默认搜索与 Codex 订阅搜索之间明确切换 |
52
53
  | **Codex 图片生成** | 在 DSH 对话里直接描述画面,生成结果会显示在当前会话中 |
@@ -116,7 +117,7 @@ npx -y @deepseek-ai/dsh@0.1.1-rc.2 --profile web --dump-config
116
117
  </details>
117
118
 
118
119
  <details>
119
- <summary>已经能运行 <code>dsh</code>,或位于 DSH-Portable 目录</summary>
120
+ <summary>已经能运行 <code>dsh</code></summary>
120
121
 
121
122
  ```sh
122
123
  dsh plugin --profile web add dsh-codex-subscription
@@ -124,8 +125,7 @@ dsh plugin --profile web list dsh-codex-subscription --depth 0
124
125
  dsh --profile web --dump-config
125
126
  ```
126
127
 
127
- DSH-Portable 请把 `dsh` 换成 `./dsh`(PowerShell 使用 `.\dsh.exe`)。安装列表中应只有一个
128
- `dsh-codex-subscription`,配置中应只有一个 `codex-subscription` 条目。
128
+ 安装列表中应只有一个 `dsh-codex-subscription`,配置中应只有一个 `codex-subscription` 条目。
129
129
 
130
130
  </details>
131
131
 
@@ -144,7 +144,7 @@ DSH-Portable 请把 `dsh` 换成 `./dsh`(PowerShell 使用 `.\dsh.exe`)。
144
144
  - 设置页显示服务端返回的额度、重置时间和更新时间;
145
145
  - 普通 Codex、Codex-Spark、Credits 等独立额度分开显示;
146
146
  - 已用尽的 Codex 额度可主动使用重置卡,并经过分层确认且不会自动重试;
147
- - 输入框可显示当前 Codex 模型的剩余额度(Beta,默认关闭);
147
+ - 输入框可用百分比或进度条显示当前 Codex 模型的剩余额度(默认关闭);
148
148
  - 输入框可为支持的 Codex 模型切换标准或高速模式(Beta);
149
149
  - 设置页可生成并复制无敏感信息的支持诊断,报告不包含 OAuth 凭据、账号标识或授权时间;
150
150
  - 订阅路由不可用时明确报错,不会静默切换到其他付费路由。
@@ -155,7 +155,7 @@ DSH-Portable 请把 `dsh` 换成 `./dsh`(PowerShell 使用 `.\dsh.exe`)。
155
155
  <img src="https://raw.githubusercontent.com/WSL043/dsh-codex-subscription/main/docs/assets/composer-quota-en.png" width="800" alt="输入框内的 Codex 剩余额度">
156
156
  </p>
157
157
 
158
- 快捷百分比只在选择 Codex 模型时显示。普通 Codex 使用服务端返回窗口中剩余最少的一项,
158
+ 可在设置中选择关闭、百分比或进度条;紧凑额度只在选择 Codex 模型时显示。普通 Codex 使用服务端返回窗口中剩余最少的一项,
159
159
  Spark 使用独立额度。插件不会写死“5 小时 + 每周”,也不会虚构服务端没有返回的 Credits 或消费上限。
160
160
 
161
161
  ### 安全使用额度重置
@@ -195,14 +195,10 @@ dsh plugin --profile web remove dsh-codex-subscription
195
195
 
196
196
  </details>
197
197
 
198
- DSH-Portable 在其目录中把上述 `dsh` 换成 `.\dsh.exe`。
199
-
200
198
  ## 常见问题
201
199
 
202
200
  - **`dsh` 无法识别**:官方 npm 方式本来就不会创建全局 `dsh` 命令,请使用上面的完整 `npx -y @deepseek-ai/dsh@0.1.1-rc.2 ...` 命令;
203
- - **`dsh.exe` 无法识别**:当前目录没有该文件。请先进入 DSH-Portable 目录,或使用 Windows 单行助手;
204
- - **找不到 DSH-Portable**:进入它的目录后重新执行安装命令,或直接运行 `.\dsh.exe plugin --profile web add dsh-codex-subscription`;
205
- - **电脑上有多个 DSH**:单行助手会合并正在运行的副本与常用目录内检测到的 Portable,输入对应编号即可;交给 Agent 时让它进入目标目录,或明确传入 `-DshPath`;
201
+ - **电脑上有多个 DSH**:请在目标 DSH 环境中运行标准命令;交给 Agent 时明确目标,或传入 `-DshPath`;
206
202
  - **安装仍然失败**:把上面的 Agent 文档链接发给 Agent,不要删除 profile 或随意修改系统 PATH。
207
203
  - **需要提交问题**:在设置页底部生成“支持诊断”,然后打开[使用问题表单](https://github.com/WSL043/dsh-codex-subscription/issues/new?template=install-problem.yml)。表单会收集准确的 DSH/插件版本和复现步骤;报告不含凭据和账号标识,仍不要附上登录链接、授权码或浏览器回调地址。
208
204
 
Binary file
Binary file
Binary file
package/lib/client.js CHANGED
@@ -50,7 +50,9 @@ window.__ModuleLoader__.load({
50
50
  //#endregion
51
51
  //#region src/settings-contract.js
52
52
  const SETTINGS_NAMESPACE = "codex-subscription";
53
- const QUICK_QUOTA_FIELD = "quickQuotaVisible";
53
+ const QUICK_QUOTA_MODE_FIELD = "quickQuotaMode";
54
+ const LEGACY_QUICK_QUOTA_FIELD = "quickQuotaVisible";
55
+ const QUICK_QUOTA_MODE_PERCENT = "percent";
54
56
  const SEARCH_PROVIDER_FIELD = "searchProvider";
55
57
  const SEARCH_PROVIDER_CODEX = "codex";
56
58
  const SPEED_MODE_FIELD = "speedMode";
@@ -59,6 +61,11 @@ window.__ModuleLoader__.load({
59
61
  const DEFAULT_SPEED_MODE = SPEED_MODE_STANDARD;
60
62
  const normalizeSearchProvider = (value) => ["dsh", "codex"].includes(value) ? value : "dsh";
61
63
  const normalizeSpeedMode = (value) => ["standard", "fast"].includes(value) ? value : DEFAULT_SPEED_MODE;
64
+ const normalizeQuickQuotaMode = (value, legacyVisible = false) => [
65
+ "off",
66
+ "percent",
67
+ "bar"
68
+ ].includes(value) ? value : legacyVisible === true ? QUICK_QUOTA_MODE_PERCENT : "off";
62
69
  const supportsCodexFastMode = (modelId) => typeof modelId === "string" && (/^gpt-5\.(?:5|6)(?:$|-)/u.test(modelId) || modelId === "gpt-5.4");
63
70
  //#endregion
64
71
  //#region src/sidebar-quota.js
@@ -203,7 +210,9 @@ window.__ModuleLoader__.load({
203
210
  spendReached: "Credits 月度消费上限已用尽。",
204
211
  unavailable: "暂无数据",
205
212
  quickQuotaSetting: "输入框额度",
206
- quickQuotaBeta: "Beta",
213
+ quickQuotaOff: "关闭",
214
+ quickQuotaPercent: "百分比",
215
+ quickQuotaBar: "进度条",
207
216
  quickQuotaStatus: "Codex 剩余额度 {value}%",
208
217
  speedTitle: "速度",
209
218
  speedStandard: "标准",
@@ -314,7 +323,9 @@ window.__ModuleLoader__.load({
314
323
  spendReached: "The monthly Credits spending cap has been reached.",
315
324
  unavailable: "No data yet",
316
325
  quickQuotaSetting: "Composer quota",
317
- quickQuotaBeta: "Beta",
326
+ quickQuotaOff: "Off",
327
+ quickQuotaPercent: "Percent",
328
+ quickQuotaBar: "Progress bar",
318
329
  quickQuotaStatus: "Codex quota: {value}% remaining",
319
330
  speedTitle: "Speed",
320
331
  speedStandard: "Standard",
@@ -352,6 +363,7 @@ window.__ModuleLoader__.load({
352
363
  .codexSubscriptionNote{font-size:13px;line-height:20px;color:var(--dsw-alias-label-tertiary)}
353
364
  .codexSubscriptionCard{border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-layer-1);padding:14px 16px;display:flex;flex-direction:column;gap:12px}
354
365
  .codexSubscriptionUsageCard{padding:12px 14px;gap:9px}.codexSubscriptionPreferencesCard{padding:12px 14px;gap:10px}.codexSubscriptionPreference{min-height:32px;box-sizing:border-box;display:flex;align-items:center;justify-content:space-between;gap:12px;color:var(--dsw-alias-label-primary);font-size:13px;line-height:20px}.codexSubscriptionPreferenceCopy{display:flex;min-width:0;flex-direction:column;gap:2px}.codexSubscriptionPreferenceLabel{display:flex;align-items:center;gap:6px}
366
+ .codexSubscriptionQuotaModes{display:flex;align-items:center;gap:3px;padding:2px;border-radius:9px;background:var(--dsw-alias-bg-module-platform)}.codexSubscriptionQuotaMode{position:relative;display:flex;align-items:center;justify-content:center;min-height:26px;padding:0 9px;border-radius:7px;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;cursor:pointer}.codexSubscriptionQuotaMode:has(input:checked){background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);box-shadow:0 0 0 1px var(--dsw-alias-border-l3)}.codexSubscriptionQuotaMode:has(input:focus-visible){outline:2px solid var(--dsw-alias-border-l3);outline-offset:1px}.codexSubscriptionQuotaMode:has(input:disabled){cursor:not-allowed;opacity:.5}.codexSubscriptionQuotaMode input{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none}
355
367
  .codexSubscriptionSwitch{position:relative;flex:0 0 auto;width:32px;height:18px;padding:0;border:1px solid var(--dsw-alias-border-l3);border-radius:999px;background:var(--dsw-alias-bg-module-platform);cursor:pointer}.codexSubscriptionSwitch:disabled{cursor:not-allowed;opacity:.5}.codexSubscriptionSwitch[aria-checked=true]{background:var(--dsw-alias-label-secondary);border-color:var(--dsw-alias-label-secondary)}.codexSubscriptionSwitchKnob{position:absolute;top:2px;left:2px;width:12px;height:12px;border-radius:50%;background:var(--dsw-alias-bg-layer-1);transition:transform 120ms var(--ds-ease-in-out)}.codexSubscriptionSwitch[aria-checked=true] .codexSubscriptionSwitchKnob{transform:translateX(14px)}
356
368
  .codexSubscriptionSearch{display:flex;flex-direction:column;gap:7px}.codexSubscriptionSearchChoices{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:6px}.codexSubscriptionSearchChoice{display:grid;grid-template-columns:14px minmax(0,1fr);align-items:center;column-gap:8px;min-width:0;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-primary);padding:9px 10px;text-align:left;cursor:pointer}.codexSubscriptionSearchChoice:has(input:disabled){cursor:not-allowed;opacity:.5}.codexSubscriptionSearchChoice:has(input:checked){border-color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2)}.codexSubscriptionSearchChoice:has(input:focus-visible){outline:2px solid var(--dsw-alias-border-l3);outline-offset:2px}.codexSubscriptionSearchInput{width:14px;height:14px;margin:0;accent-color:var(--dsw-alias-label-primary);cursor:inherit}.codexSubscriptionSearchCopy{display:block;min-width:0;pointer-events:none}.codexSubscriptionSearchCopy strong,.codexSubscriptionSearchCopy span{display:block}.codexSubscriptionSearchCopy strong{font-size:12px;line-height:18px;font-weight:500;color:var(--dsw-alias-label-secondary)}.codexSubscriptionSearchChoice:has(input:checked) strong{color:var(--dsw-alias-label-primary)}.codexSubscriptionSearchCopy span{font-size:11px;line-height:16px;color:var(--dsw-alias-label-tertiary)}.codexSubscriptionDivider{height:1px;background:var(--dsw-alias-border-l2)}
357
369
  .codexSubscriptionAccountRow,.codexSubscriptionSectionHead{display:flex;align-items:center;justify-content:space-between;gap:12px}.codexSubscriptionStatus{display:flex;align-items:center;gap:8px;font-size:14px;line-height:22px;font-weight:500}
@@ -372,7 +384,7 @@ window.__ModuleLoader__.load({
372
384
  .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}
373
385
  .codexSubscriptionResetSummary{display:flex;align-items:center;justify-content:space-between;gap:10px}.codexSubscriptionResetBalance{display:flex;flex-direction:column;gap:8px}.codexSubscriptionResetBalance .codexSubscriptionActions{justify-content:flex-start}.codexSubscriptionResetFlow{display:flex;flex-direction:column;gap:9px;border:1px solid var(--dsw-alias-state-warning-border,var(--dsw-alias-border-l2));border-radius:10px;padding:11px 12px;background:var(--dsw-alias-bg-module-platform)}.codexSubscriptionResetFlow h4{margin:0;font-size:13px;line-height:20px;font-weight:600}.codexSubscriptionResetWarning{font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary)}.codexSubscriptionResetCheck{display:flex;align-items:flex-start;gap:8px;font-size:12px;line-height:18px;color:var(--dsw-alias-label-primary);cursor:pointer}.codexSubscriptionResetCheck input{margin:3px 0 0;accent-color:var(--dsw-alias-label-primary)}.codexSubscriptionResetPhrase{font:600 12px/18px ui-monospace,SFMono-Regular,Consolas,monospace}.codexSubscriptionResetFinal{border-color:var(--dsw-alias-state-error-primary)!important;color:var(--dsw-alias-state-error-primary)!important}.codexSubscriptionResetResult{font-size:12px;line-height:18px;color:var(--dsw-alias-state-success-primary)}
374
386
  .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}
375
- .codexComposerQuota{display:inline-flex;align-items:center;flex:0 0 auto;height:28px;box-sizing:border-box;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}
387
+ .codexComposerQuota{display:inline-flex;align-items:center;flex:0 0 auto;height:28px;box-sizing:border-box;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}.codexComposerQuotaBar{display:block;width:40px;height:4px;border:0;border-radius:999px;overflow:hidden;background:var(--dsw-alias-border-l3);accent-color:var(--dsw-alias-label-secondary);-webkit-appearance:none;appearance:none}.codexComposerQuotaBar::-webkit-progress-bar{background:var(--dsw-alias-border-l3);border-radius:999px}.codexComposerQuotaBar::-webkit-progress-value{background:var(--dsw-alias-label-secondary);border-radius:999px}.codexComposerQuotaBar::-moz-progress-bar{background:var(--dsw-alias-label-secondary);border-radius:999px}
376
388
  .codexModelSelect{position:relative;min-width:0}.codexModelSelectTrigger{display:flex;align-items:center;gap:4px;min-width:0;max-width:min(360px,45cqw);height:28px;padding:0 4px 0 8px;border:0;border-radius:24px;outline:0;background:transparent;color:var(--dsw-alias-label-secondary);font-size:13px;font-weight:500;line-height:20px;cursor:pointer}.codexModelSelectTrigger:hover:not(:disabled),.codexModelSelectTrigger[aria-expanded=true]{background:var(--dsw-alias-interactive-bg-hover)}.codexModelSelectTrigger:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-border-l3)}.codexModelSelectTrigger:disabled{color:var(--dsw-alias-label-dimmed);cursor:default}.codexModelSelectBolt{display:block;flex:none;width:14px;height:14px;color:var(--dsw-alias-label-primary)}.codexModelSelectLabel{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.codexModelSelectEffort{flex:none;color:var(--dsw-alias-label-caption)}.codexModelSelectChevron{flex:none;color:var(--dsw-alias-label-caption);transition:transform 120ms}.codexModelSelectTrigger[aria-expanded=true] .codexModelSelectChevron{transform:rotate(180deg)}
377
389
  .codexModelSelectMenu,.codexModelSelectSubmenu{position:absolute;z-index:30;box-sizing:border-box;width:max-content;min-width:min(240px,calc(100vw - 32px));max-width:min(420px,calc(100vw - 32px));max-height:min(360px,calc(100vh - 96px));padding:4px;border:1px solid var(--dsw-alias-border-inverted);border-radius:12px;background:var(--dsw-specific-menu);box-shadow:var(--dsw-shadow-lv3);color:var(--dsw-alias-label-primary);overflow:hidden}.codexModelSelectMenu{right:0;bottom:calc(100% + 8px)}.codexModelSelectSubmenu{right:calc(100% + 8px);bottom:0;min-width:min(230px,calc(100vw - 32px))}.codexModelSelectCell{display:flex;align-items:center;gap:8px;width:100%;min-width:100%;height:40px;box-sizing:border-box;padding:0 10px;border:0;border-radius:10px;background:transparent;color:inherit;font-size:14px;line-height:22px;text-align:left;cursor:pointer}.codexModelSelectCell:hover,.codexModelSelectCell:focus-visible,.codexModelSelectCell[data-open=true]{background:var(--dsw-alias-interactive-bg-hover);outline:0}.codexModelSelectCell:disabled{color:var(--dsw-alias-label-dimmed);cursor:default}.codexModelSelectCellLabel{flex:none;white-space:nowrap}.codexModelSelectCellValue{flex:auto;min-width:0;overflow:hidden;color:var(--dsw-alias-label-tertiary);text-align:right;text-overflow:ellipsis;white-space:nowrap}.codexModelSelectCellChevron{flex:none;color:var(--dsw-alias-label-tertiary)}.codexModelSelectGroups{min-height:0;max-height:352px;overflow-y:auto}.codexModelSelectGroup+.codexModelSelectGroup{margin-top:4px}.codexModelSelectGroupTitle{position:sticky;top:0;z-index:1;padding:5px 8px 3px;background:var(--dsw-specific-menu);color:var(--dsw-alias-label-tertiary);font-size:12px;font-weight:500;line-height:18px}.codexModelSelectOption{display:flex;align-items:center;gap:8px;width:100%;min-width:100%;min-height:38px;box-sizing:border-box;padding:6px 8px;border:0;border-radius:10px;outline:0;background:transparent;color:inherit;text-align:left;cursor:pointer}.codexModelSelectOption:hover:not(:disabled),.codexModelSelectOption:focus-visible{background:var(--dsw-alias-interactive-bg-hover)}.codexModelSelectOption:disabled{color:var(--dsw-alias-label-dimmed);cursor:default}.codexModelSelectOptionCopy{display:flex;flex:1;min-width:0;flex-direction:column}.codexModelSelectOptionName{overflow:hidden;color:inherit;font-size:14px;font-weight:500;line-height:20px;text-overflow:ellipsis;white-space:nowrap}.codexModelSelectOptionDescription{overflow:hidden;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;text-overflow:ellipsis;white-space:nowrap}.codexModelSelectCheck{display:grid;place-items:center;flex:0 0 18px;color:var(--dsw-alias-label-primary)}.codexModelSelectStatus,.codexModelSelectEmpty{padding:10px;color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:20px}.codexModelSelectError,.codexModelSelectWarning{display:flex;align-items:flex-start;justify-content:space-between;gap:8px;margin-bottom:4px;padding:7px 8px;border-radius:8px;background:var(--dsw-alias-interactive-bg-hover-danger);color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px}.codexModelSelectWarning{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-state-warn-label)}.codexModelSelectRetry{flex:none;padding:0;border:0;background:transparent;color:inherit;font:inherit;font-weight:600;cursor:pointer}
378
390
  .codexModelSelectMenu{overflow:visible}
@@ -539,7 +551,7 @@ window.__ModuleLoader__.load({
539
551
  const current = native.status === "ready" ? native : fallbackStatus === "ready" ? fallback : native;
540
552
  return Object.freeze({
541
553
  status: updating ? "updating" : current.status,
542
- visible: current.value?.[QUICK_QUOTA_FIELD] === true,
554
+ quickQuotaMode: normalizeQuickQuotaMode(current.value?.[QUICK_QUOTA_MODE_FIELD], current.value?.[LEGACY_QUICK_QUOTA_FIELD]),
543
555
  searchProvider: normalizeSearchProvider(current.value?.[SEARCH_PROVIDER_FIELD]),
544
556
  speedMode: normalizeSpeedMode(current.value?.[SPEED_MODE_FIELD]),
545
557
  writable: !updating && current.status === "ready" && current.writable === true,
@@ -562,7 +574,7 @@ window.__ModuleLoader__.load({
562
574
  fallback = {
563
575
  status: "ready",
564
576
  value: {
565
- [QUICK_QUOTA_FIELD]: value?.[QUICK_QUOTA_FIELD] === true,
577
+ [QUICK_QUOTA_MODE_FIELD]: normalizeQuickQuotaMode(value?.[QUICK_QUOTA_MODE_FIELD], value?.[LEGACY_QUICK_QUOTA_FIELD]),
566
578
  [SEARCH_PROVIDER_FIELD]: normalizeSearchProvider(value?.[SEARCH_PROVIDER_FIELD]),
567
579
  [SPEED_MODE_FIELD]: normalizeSpeedMode(value?.[SPEED_MODE_FIELD])
568
580
  },
@@ -683,33 +695,36 @@ window.__ModuleLoader__.load({
683
695
  }
684
696
  function QuickQuotaPreference({ preference, t }) {
685
697
  const snapshot = usePreferenceSnapshot(preference);
686
- const visible = snapshot.visible;
687
698
  const writable = snapshot.status === "ready" && snapshot.writable === true;
699
+ const choice = (value, label) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
700
+ className: "codexSubscriptionQuotaMode",
701
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
702
+ type: "radio",
703
+ name: "codex-subscription-quota-mode",
704
+ checked: snapshot.quickQuotaMode === value,
705
+ disabled: !writable,
706
+ onChange: () => {
707
+ preference.set({ [QUICK_QUOTA_MODE_FIELD]: value });
708
+ }
709
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: label })]
710
+ });
688
711
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
689
712
  className: "codexSubscriptionPreference",
690
713
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
691
714
  className: "codexSubscriptionPreferenceCopy",
692
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
715
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
693
716
  className: "codexSubscriptionPreferenceLabel",
694
- children: [t("quickQuotaSetting"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
695
- className: "codexSubscriptionTag",
696
- children: t("quickQuotaBeta")
697
- })]
717
+ children: t("quickQuotaSetting")
698
718
  })
699
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
700
- className: "codexSubscriptionSwitch",
701
- type: "button",
702
- role: "switch",
703
- "aria-checked": visible,
719
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
720
+ className: "codexSubscriptionQuotaModes",
721
+ role: "radiogroup",
704
722
  "aria-label": t("quickQuotaSetting"),
705
- disabled: !writable,
706
- onClick: () => {
707
- preference.set({ [QUICK_QUOTA_FIELD]: !visible });
708
- },
709
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
710
- className: "codexSubscriptionSwitchKnob",
711
- "aria-hidden": "true"
712
- })
723
+ children: [
724
+ choice("off", t("quickQuotaOff")),
725
+ choice(QUICK_QUOTA_MODE_PERCENT, t("quickQuotaPercent")),
726
+ choice("bar", t("quickQuotaBar"))
727
+ ]
713
728
  })]
714
729
  });
715
730
  }
@@ -778,17 +793,23 @@ window.__ModuleLoader__.load({
778
793
  const preferenceSnapshot = usePreferenceSnapshot(preference);
779
794
  const current = (0, react.useSyncExternalStore)((listener) => directory.subscribe(listener), () => directory.getSnapshot()).current;
780
795
  const codex = current?.provider === "openai-codex";
781
- const quotaEnabled = preferenceSnapshot.status === "ready" && preferenceSnapshot.visible && codex;
796
+ const quotaEnabled = preferenceSnapshot.status === "ready" && preferenceSnapshot.quickQuotaMode !== "off" && codex;
782
797
  const quota = useQuickQuota(rpc, quotaEnabled, current?.model);
783
798
  if (!quotaEnabled || quota === void 0) return null;
784
- const value = percent(quota.remainingPercent);
785
- const label = fill(t("quickQuotaStatus"), { value });
786
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
799
+ const value = Math.round(Number(quota.remainingPercent) * 10) / 10;
800
+ const display = percent(value);
801
+ const label = fill(t("quickQuotaStatus"), { value: display });
802
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
787
803
  className: "codexComposerQuota",
788
804
  role: "status",
789
805
  "aria-label": label,
790
806
  title: label,
791
- children: [value, "%"]
807
+ children: preferenceSnapshot.quickQuotaMode === "bar" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("progress", {
808
+ className: "codexComposerQuotaBar",
809
+ max: 100,
810
+ value,
811
+ "aria-hidden": "true"
812
+ }) : `${display}%`
792
813
  });
793
814
  }
794
815
  function CodexModelSelect({ locked, available, directory, load, select, preference, t }) {
package/lib/index.js CHANGED
@@ -431,13 +431,20 @@ function createCodexRpcHandler(coordinator, options = {}) {
431
431
  //#endregion
432
432
  //#region src/settings-contract.js
433
433
  const SETTINGS_NAMESPACE = "codex-subscription";
434
- const QUICK_QUOTA_FIELD = "quickQuotaVisible";
434
+ const QUICK_QUOTA_MODE_FIELD = "quickQuotaMode";
435
+ const LEGACY_QUICK_QUOTA_FIELD = "quickQuotaVisible";
436
+ const QUICK_QUOTA_MODE_PERCENT = "percent";
435
437
  const SEARCH_PROVIDER_FIELD = "searchProvider";
436
438
  const SEARCH_PROVIDER_CODEX = "codex";
437
439
  const SPEED_MODE_FIELD = "speedMode";
438
440
  const SPEED_MODE_STANDARD = "standard";
439
441
  const SPEED_MODE_FAST = "fast";
440
442
  const DEFAULT_SPEED_MODE = SPEED_MODE_STANDARD;
443
+ const normalizeQuickQuotaMode = (value, legacyVisible = false) => [
444
+ "off",
445
+ "percent",
446
+ "bar"
447
+ ].includes(value) ? value : legacyVisible === true ? QUICK_QUOTA_MODE_PERCENT : "off";
441
448
  const supportsCodexFastMode = (modelId) => typeof modelId === "string" && (/^gpt-5\.(?:5|6)(?:$|-)/u.test(modelId) || modelId === "gpt-5.4");
442
449
  //#endregion
443
450
  //#region src/pi-ai-runtime.js
@@ -496,7 +503,7 @@ function openaiCodexSubscriptionProvider({ resolveSpeedMode = () => void 0 } = {
496
503
  }
497
504
  //#endregion
498
505
  //#region src/version.js
499
- const PACKAGE_VERSION = "1.3.0";
506
+ const PACKAGE_VERSION = "1.4.1";
500
507
  const USER_AGENT = `dsh-codex-subscription/${PACKAGE_VERSION}`;
501
508
  //#endregion
502
509
  //#region src/codex-search.js
@@ -636,6 +643,13 @@ const nonEmpty = (value) => typeof value === "string" && value.trim().length > 0
636
643
  function encodedLimit(decodedBytes) {
637
644
  return Math.ceil(decodedBytes / 3) * 4;
638
645
  }
646
+ function validBase64Body(value, end) {
647
+ for (let index = 0; index < end; index += 1) {
648
+ const code = value.charCodeAt(index);
649
+ if (!(code >= 65 && code <= 90 || code >= 97 && code <= 122 || code >= 48 && code <= 57 || code === 43 || code === 47)) return false;
650
+ }
651
+ return true;
652
+ }
639
653
  async function readJsonWithin(response, maximumBytes) {
640
654
  const contentLength = Number(response.headers.get("content-length"));
641
655
  if (Number.isFinite(contentLength) && contentLength > maximumBytes) throw new Error("Codex image response exceeds the image size limit");
@@ -663,8 +677,8 @@ async function readJsonWithin(response, maximumBytes) {
663
677
  /** Strictly decode one PNG returned by the subscription backend. */
664
678
  function decodeCodexPng(value, maximumBytes) {
665
679
  const encoded = nonEmpty(value);
666
- if (encoded === void 0 || encoded.length % 4 !== 0 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/u.test(encoded)) throw new Error("Codex returned an invalid base64 PNG");
667
- const padding = encoded.endsWith("==") ? 2 : encoded.endsWith("=") ? 1 : 0;
680
+ const padding = encoded?.endsWith("==") ? 2 : encoded?.endsWith("=") ? 1 : 0;
681
+ if (encoded === void 0 || encoded.length % 4 !== 0 || !validBase64Body(encoded, encoded.length - padding)) throw new Error("Codex returned an invalid base64 PNG");
668
682
  const decodedBytes = encoded.length / 4 * 3 - padding;
669
683
  if (decodedBytes > maximumBytes) throw new Error("Codex image exceeds the image size limit");
670
684
  const data = Buffer.from(encoded, "base64");
@@ -1261,9 +1275,13 @@ function createSubscriptionRpcHandler({ authHandler, usageReader, resetCreditSer
1261
1275
  signal.throwIfAborted();
1262
1276
  if (endpoint === "preferences/update") {
1263
1277
  const patch = {};
1264
- if (Object.hasOwn(payload ?? {}, "quickQuotaVisible")) {
1265
- if (typeof payload["quickQuotaVisible"] !== "boolean") return publicError("internal", "Invalid quick quota preference");
1266
- patch[QUICK_QUOTA_FIELD] = payload[QUICK_QUOTA_FIELD];
1278
+ if (Object.hasOwn(payload ?? {}, "quickQuotaMode")) {
1279
+ if (![
1280
+ "off",
1281
+ "percent",
1282
+ "bar"
1283
+ ].includes(payload["quickQuotaMode"])) return publicError("internal", "Invalid quick quota preference");
1284
+ patch[QUICK_QUOTA_MODE_FIELD] = payload[QUICK_QUOTA_MODE_FIELD];
1267
1285
  }
1268
1286
  if (Object.hasOwn(payload ?? {}, "searchProvider")) {
1269
1287
  if (!["dsh", "codex"].includes(payload["searchProvider"])) return publicError("internal", "Invalid search provider preference");
@@ -1355,14 +1373,19 @@ function createSearchProviderSwitcher(loader) {
1355
1373
  }
1356
1374
  function apply(ctx) {
1357
1375
  const settings = ctx.settings.register(settingsNamespace(SETTINGS_NAMESPACE), z.object({
1358
- [QUICK_QUOTA_FIELD]: z.boolean().default(false),
1376
+ [QUICK_QUOTA_MODE_FIELD]: z.union([
1377
+ "off",
1378
+ QUICK_QUOTA_MODE_PERCENT,
1379
+ "bar"
1380
+ ]),
1381
+ [LEGACY_QUICK_QUOTA_FIELD]: z.boolean(),
1359
1382
  [SEARCH_PROVIDER_FIELD]: z.union(["dsh", SEARCH_PROVIDER_CODEX]).default("dsh"),
1360
1383
  [SPEED_MODE_FIELD]: z.union([SPEED_MODE_STANDARD, SPEED_MODE_FAST]).default(DEFAULT_SPEED_MODE)
1361
1384
  }));
1362
1385
  const searchProvider = createSearchProviderSwitcher(ctx.loader);
1363
1386
  const preferences = {
1364
1387
  status: () => ({
1365
- [QUICK_QUOTA_FIELD]: settings.get()[QUICK_QUOTA_FIELD],
1388
+ [QUICK_QUOTA_MODE_FIELD]: normalizeQuickQuotaMode(settings.get()[QUICK_QUOTA_MODE_FIELD], settings.get()[LEGACY_QUICK_QUOTA_FIELD]),
1366
1389
  [SEARCH_PROVIDER_FIELD]: settings.get()[SEARCH_PROVIDER_FIELD],
1367
1390
  [SPEED_MODE_FIELD]: settings.get()[SPEED_MODE_FIELD],
1368
1391
  writable: ctx.settings.writable
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-codex-subscription",
3
- "version": "1.3.0",
3
+ "version": "1.4.1",
4
4
  "description": "Use ChatGPT and Codex subscriptions in DeepSeek Harness with OAuth, quota, safe resets, web search, images, and Fast mode",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",