dsh-subscriptions 0.2.0 → 0.2.2

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.
Files changed (99) hide show
  1. package/README.md +212 -180
  2. package/lib/auth/claude-code-creds.js +31 -9
  3. package/lib/auth/device-flow.js +16 -9
  4. package/lib/auth/oauth-flow.js +13 -10
  5. package/lib/auth/rpc.js +74 -23
  6. package/lib/auth/store.js +80 -31
  7. package/lib/client/CursorCard.d.ts +1 -1
  8. package/lib/client/CursorCard.js +50 -16
  9. package/lib/client/ExternalUsageCards.d.ts +1 -1
  10. package/lib/client/ExternalUsageCards.js +37 -16
  11. package/lib/client/ImageGallery.d.ts +2 -2
  12. package/lib/client/ImageGallery.js +92 -33
  13. package/lib/client/ImageGenerateToolview.js +50 -14
  14. package/lib/client/OllamaCard.d.ts +1 -1
  15. package/lib/client/OllamaCard.js +26 -8
  16. package/lib/client/OllamaSettingsManager.js +113 -30
  17. package/lib/client/ProviderAccountManager.d.ts +1 -1
  18. package/lib/client/ProviderAccountManager.js +164 -49
  19. package/lib/client/ProviderModelEditor.js +107 -26
  20. package/lib/client/SpeedSelect.d.ts +62 -14
  21. package/lib/client/SpeedSelect.js +257 -46
  22. package/lib/client/SubscriptionUsageBadge.d.ts +4 -10
  23. package/lib/client/SubscriptionUsageBadge.js +300 -112
  24. package/lib/client/SubscriptionsSection.js +258 -85
  25. package/lib/client/UsageBadgeDisplaySetting.js +35 -12
  26. package/lib/client/UsageMeter.d.ts +1 -1
  27. package/lib/client/UsageMeter.js +62 -24
  28. package/lib/client/VideoGenerateToolview.js +69 -17
  29. package/lib/client/account-preferences.d.ts +8 -0
  30. package/lib/client/account-preferences.js +20 -5
  31. package/lib/client/host-contract.d.ts +11 -2
  32. package/lib/client/host-contract.js +20 -3
  33. package/lib/client/index.d.ts +5 -5
  34. package/lib/client/index.js +34 -12
  35. package/lib/client/locales.d.ts +10 -0
  36. package/lib/client/locales.js +61 -51
  37. package/lib/client/ollama-draft.js +21 -12
  38. package/lib/client/reset-action.js +6 -4
  39. package/lib/client/reset-credits-view.d.ts +1 -1
  40. package/lib/client/reset-credits-view.js +66 -17
  41. package/lib/client/subscription-card-styles.js +94 -24
  42. package/lib/client/usage-color-preferences.js +4 -2
  43. package/lib/client/usage-pace.js +30 -12
  44. package/lib/client.js +287 -47
  45. package/lib/client.js.map +1 -1
  46. package/lib/http.js +1 -1
  47. package/lib/index.d.ts +3 -3
  48. package/lib/index.js +118 -64
  49. package/lib/model-defaults.js +4 -2
  50. package/lib/provider-settings.d.ts +12 -0
  51. package/lib/provider-settings.js +58 -16
  52. package/lib/providers/account-preferences.js +51 -22
  53. package/lib/providers/accounts.js +15 -9
  54. package/lib/providers/antigravity-oauth-client.js +2 -2
  55. package/lib/providers/antigravity.js +141 -68
  56. package/lib/providers/catalog-store.js +120 -41
  57. package/lib/providers/claude-cli-version.js +3 -3
  58. package/lib/providers/claude.d.ts +9 -2
  59. package/lib/providers/claude.js +153 -90
  60. package/lib/providers/codex-client-version.js +3 -3
  61. package/lib/providers/codex-search.js +40 -13
  62. package/lib/providers/codex.js +196 -112
  63. package/lib/providers/common.js +71 -39
  64. package/lib/providers/copilot.js +143 -82
  65. package/lib/providers/cursor-adapter.js +42 -18
  66. package/lib/providers/cursor-auth.d.ts +1 -1
  67. package/lib/providers/cursor-auth.js +69 -24
  68. package/lib/providers/cursor-model-settings.js +14 -5
  69. package/lib/providers/cursor-usage.js +27 -13
  70. package/lib/providers/external-usage-controller.js +14 -5
  71. package/lib/providers/external-usage.js +59 -23
  72. package/lib/providers/grok.js +152 -89
  73. package/lib/providers/image-pool.js +29 -11
  74. package/lib/providers/minimax-usage.js +31 -11
  75. package/lib/providers/npm-cli-version.js +10 -3
  76. package/lib/providers/ollama-settings.js +24 -8
  77. package/lib/providers/ollama-web.js +29 -11
  78. package/lib/providers/ollama.js +129 -66
  79. package/lib/providers/pool-family.js +11 -3
  80. package/lib/providers/pool-health.js +28 -8
  81. package/lib/providers/pool-usage.js +16 -9
  82. package/lib/providers/pool.d.ts +8 -0
  83. package/lib/providers/pool.js +81 -42
  84. package/lib/providers/rate-limit.js +8 -2
  85. package/lib/providers/replay.js +40 -17
  86. package/lib/providers/reset-redemption.js +18 -6
  87. package/lib/tools/image-generate.js +123 -65
  88. package/lib/tools/registration.js +5 -2
  89. package/lib/tools/video-generate.js +51 -31
  90. package/lib/tools/x-search.js +50 -22
  91. package/lib/translate/anthropic.js +85 -37
  92. package/lib/translate/antigravity-schema.js +71 -28
  93. package/lib/translate/antigravity-thinking.js +22 -9
  94. package/lib/translate/antigravity.js +141 -50
  95. package/lib/translate/chat-completions.js +67 -23
  96. package/lib/translate/resolved.js +55 -21
  97. package/lib/translate/responses.js +69 -29
  98. package/lib/translate/sse.js +4 -1
  99. package/package.json +26 -4
package/README.md CHANGED
@@ -11,46 +11,45 @@ reported quota in **Settings → Subscriptions**.
11
11
  ## What it adds
12
12
 
13
13
  - **Six subscription model sources:** Connect ChatGPT/Codex, Claude, Grok,
14
- GitHub Copilot, Google Antigravity, or Cursor and choose their models in
15
- DSH's picker. These routes use account sign-in, without provider API keys.
14
+ GitHub Copilot, Google Antigravity, or Cursor and choose their models in DSH's
15
+ picker. These routes use account sign-in, without provider API keys.
16
16
  - **Ollama Cloud key route:** Add the `ollama-cloud` route with an
17
- `OLLAMA_API_KEY` credential. The route ships no model catalog:
18
- configure it on the Settings → Subscriptions card, which fetches the
19
- cloud list, ticks the models to keep, and edits each one's display
20
- name, order, default reasoning effort, and local context window.
21
- The picker shows exactly the ticked list, so an unconfigured route
22
- offers no model. The card also edits the base URL, the credential
23
- reference, and the web search/fetch switch, and persists them under
24
- the DSH home directory. No OAuth sign-in. A hand-written `llm-pi-ai`
25
- route with the same id collides with this one: delete the manual
26
- block when enabling the plugin route. Stop sequences are unsupported
27
- on this route, consistent with the other adapters. Web search/fetch
28
- stay off until the card's switch is on (or a profile sets
29
- `ollama.web`), so untouched installs keep their existing web
30
- provider; a profile still pins them to `ollama-cloud`. Not ported
31
- from the standalone plugin: its transient-error reclassification,
32
- per-image request quotas, and per-model usage counts. See the
17
+ `OLLAMA_API_KEY` credential. The route ships no model catalog: configure it on
18
+ the Settings → Subscriptions card, which fetches the cloud list, ticks the
19
+ models to keep, and edits each one's display name, order, default reasoning
20
+ effort, and local context window. The picker shows exactly the ticked list, so
21
+ an unconfigured route offers no model. The card also edits the base URL, the
22
+ credential reference, and the web search/fetch switch, and persists them under
23
+ the DSH home directory. No OAuth sign-in. A hand-written `llm-pi-ai` route
24
+ with the same id collides with this one: delete the manual block when enabling
25
+ the plugin route. Stop sequences are unsupported on this route, consistent
26
+ with the other adapters. Web search/fetch stay off until the card's switch is
27
+ on (or a profile sets `ollama.web`), so untouched installs keep their existing
28
+ web provider; a profile still pins them to `ollama-cloud`. Not ported from the
29
+ standalone plugin: its transient-error reclassification, per-image request
30
+ quotas, and per-model usage counts. See the
33
31
  [key-route note](.agents/notes/implemented/provider/2026-10-10-ollama-cloud-key-route.md).
34
- - **Account and model control:** Refresh catalogs and choose visible models.
35
- For Codex, Claude, Grok, Copilot, and Antigravity, manage multiple accounts
36
- and same-provider pools. Pools can use available quota to select an account
37
- and fail over before a reply starts. See [account management](docs/account-management.md).
32
+ - **Account and model control:** Refresh catalogs and choose visible models. For
33
+ Codex, Claude, Grok, Copilot, and Antigravity, manage multiple accounts and
34
+ same-provider pools. Pools can use available quota to select an account and
35
+ fail over before a reply starts. See
36
+ [account management](docs/account-management.md).
38
37
  - **Quota while you work:** See reported usage windows and reset times on
39
38
  provider cards and in an optional session-footer pill. The same view reads
40
- OpenCode Go and Kimi Code usage through keys already configured in
41
- **Settings → Models** (their model routes remain built into DSH), and
42
- Ollama Cloud usage through its `OLLAMA_API_KEY` credential (its route
43
- ships in this plugin). GitHub Copilot has no usage endpoint.
44
- - **Provider tools:** Use Codex web search, Grok X search, ChatGPT or Grok
45
- image generation and editing, and Grok video generation when the matching
46
- provider is enabled.
39
+ OpenCode Go and Kimi Code usage through keys already configured in **Settings
40
+ → Models** (their model routes remain built into DSH), and Ollama Cloud usage
41
+ through its `OLLAMA_API_KEY` credential (its route ships in this plugin).
42
+ GitHub Copilot has no usage endpoint.
43
+ - **Provider tools:** Use Codex web search, Grok X search, ChatGPT or Grok image
44
+ generation and editing, and Grok video generation when the matching provider
45
+ is enabled.
47
46
 
48
47
  ## Screenshots
49
48
 
50
- Captured against DSH 0.2.0-rc.2 in the web UI with a local stub serving
51
- frozen provider responses, so each image shows a product state rather than
52
- a real account. Identities in the images are fabricated, and the quota
53
- percentages, catalogs, and reset countdowns are sample data.
49
+ Captured against DSH 0.2.0-rc.2 in the web UI with a local stub serving frozen
50
+ provider responses, so each image shows a product state rather than a real
51
+ account. Identities in the images are fabricated, and the quota percentages,
52
+ catalogs, and reset countdowns are sample data.
54
53
 
55
54
  ### Subscriptions
56
55
 
@@ -65,23 +64,23 @@ Logged-in providers join the session model picker with their live catalogs.
65
64
 
66
65
  ![Model picker with subscription models](docs/images/model-picker.png)
67
66
 
68
- Models that advertise reasoning levels get an **Effort** selector in the
69
- same menu. Levels and defaults come from the provider's live catalog;
70
- without discovery Codex falls back to a built-in list, and a configured
71
- default overrides either.
67
+ Models that advertise reasoning levels get an **Effort** selector in the same
68
+ menu. Levels and defaults come from the provider's live catalog; without
69
+ discovery Codex falls back to a built-in list, and a configured default
70
+ overrides either.
72
71
 
73
72
  ![Reasoning effort selector](docs/images/model-effort.png)
74
73
 
75
- Codex models whose catalog advertises the fast tier get a **Speed** toggle
76
- in the composer's tool row: Standard or Fast, per session.
74
+ Codex models whose catalog advertises the fast tier get a **Speed** toggle in
75
+ the composer's tool row: Standard or Fast, per session.
77
76
 
78
77
  ![Speed toggle with the Standard/Fast menu open](docs/images/speed-toggle.png)
79
78
 
80
- **Settings → Subscriptions → provider → Manage** edits account aliases,
81
- pool participation, model allowlists, and the per-model effort, context,
82
- and tool settings. A context value only lowers the window a session plans
83
- against, never above what the provider advertises; Codex and Claude support
84
- it, and it does not enlarge the API's capacity.
79
+ **Settings → Subscriptions → provider → Manage** edits account aliases, pool
80
+ participation, model allowlists, and the per-model effort, context, and tool
81
+ settings. A context value only lowers the window a session plans against, never
82
+ above what the provider advertises; Codex and Claude support it, and it does not
83
+ enlarge the API's capacity.
85
84
 
86
85
  ![Model settings](docs/images/model-settings.png)
87
86
 
@@ -89,85 +88,81 @@ it, and it does not enlarge the API's capacity.
89
88
 
90
89
  ### Quota while you work
91
90
 
92
- The composer's stats row gains a **provider usage** pill showing the
93
- used percentage and reset window for the provider of the session's current
94
- model. It shows at most one provider; switching to a non-subscription model
95
- keeps the most recent subscription selected in the mounted conversation
96
- view, or stays hidden if there is none. **Always show** instead rotates
97
- through the providers that report usage every 10 seconds while the current
98
- model has no quota source, so the pill never disappears.
91
+ The composer's stats row gains a **provider usage** pill showing the used
92
+ percentage and reset window for the provider of the session's current model. It
93
+ shows at most one provider; switching to a non-subscription model keeps the most
94
+ recent subscription selected in the mounted conversation view, or stays hidden
95
+ if there is none. **Always show** instead rotates through the providers that
96
+ report usage every 10 seconds while the current model has no quota source, so
97
+ the pill never disappears.
99
98
 
100
99
  ![Provider usage pill in the stats row](docs/images/usage-pill.png)
101
100
 
102
- Click the pill to expand every provider and account that reports usage —
103
- the default account is starred, and the current provider is listed first.
104
- A provider with no usage endpoint, such as GitHub Copilot, has no row, and
105
- neither do accounts without a usage window. Antigravity previews only the
106
- current model's windows; the other model windows stay in a closed
107
- disclosure.
108
-
109
- Quota bars include a dark elapsed-time cursor whenever the provider's
110
- timing places the window in time, including at 0% used and for stale or old
111
- readings. Compare the colored fill (quota used) with the cursor (time
112
- elapsed); hover for the percentages and reset countdown. Standard coloring
113
- turns the fill orange when a fresh reading leads elapsed time by 10 points,
114
- and Relaxed uses 15; 90% used turns it red and takes priority. Windows
115
- without known timing omit the cursor, and a stale or old reading keeps its
116
- cursor without a pace warning. Choose Standard, Relaxed, or Remaining colors
117
- in **Settings → Subscriptions**.
101
+ Click the pill to expand every provider and account that reports usage — the
102
+ default account is starred, and the current provider is listed first. A provider
103
+ with no usage endpoint, such as GitHub Copilot, has no row, and neither do
104
+ accounts without a usage window. Antigravity previews only the current model's
105
+ windows; the other model windows stay in a closed disclosure.
106
+
107
+ Quota bars include a dark elapsed-time cursor whenever the provider's timing
108
+ places the window in time, including at 0% used and for stale or old readings.
109
+ Compare the colored fill (quota used) with the cursor (time elapsed); hover for
110
+ the percentages and reset countdown. Standard coloring turns the fill orange
111
+ when a fresh reading leads elapsed time by 10 points, and Relaxed uses 15; 90%
112
+ used turns it red and takes priority. Windows without known timing omit the
113
+ cursor, and a stale or old reading keeps its cursor without a pace warning.
114
+ Choose Standard, Relaxed, or Remaining colors in **Settings → Subscriptions**.
118
115
 
119
116
  ![Usage dialog with elapsed-time cursors](docs/images/usage-badge.png)
120
117
 
121
118
  #### Codex usage limit resets
122
119
 
123
- The Codex account card shows banked full-reset credits and their expirations
124
- in a closed disclosure. A failed optional lookup reports an error without
125
- hiding ordinary quota windows. **Use…** selects the earliest-expiring available
126
- credit and requires a fresh unscoped weekly reading of at least 80%, then an
127
- explicit confirmation and acknowledgment. Confirmation expires after 60
128
- seconds; the server rechecks usage and credit availability before submission.
120
+ The Codex account card shows banked full-reset credits and their expirations in
121
+ a closed disclosure. A failed optional lookup reports an error without hiding
122
+ ordinary quota windows. **Use…** selects the earliest-expiring available credit
123
+ and requires a fresh unscoped weekly reading of at least 80%, then an explicit
124
+ confirmation and acknowledgment. Confirmation expires after 60 seconds; the
125
+ server rechecks usage and credit availability before submission.
129
126
 
130
127
  One reset consumes one credit and resets weekly and 5-hour limits. This
131
- interface cannot undo it. An unconfirmed outcome blocks further redemption
132
- for that account in the current host process. Restarting clears that block,
133
- but must not be used to bypass it: check credits and limits in Codex before
134
- retrying, because the first submission may already have spent a credit.
135
- The private endpoint is covered by offline fakes, not live-account checks.
136
- See the [manual reset decision](.agents/notes/implemented/provider/2026-10-04-codex-manual-reset.md)
128
+ interface cannot undo it. An unconfirmed outcome blocks further redemption for
129
+ that account in the current host process. Restarting clears that block, but must
130
+ not be used to bypass it: check credits and limits in Codex before retrying,
131
+ because the first submission may already have spent a credit. The private
132
+ endpoint is covered by offline fakes, not live-account checks. See the
133
+ [manual reset decision](.agents/notes/implemented/provider/2026-10-04-codex-manual-reset.md)
137
134
  for the retry boundary and verification limits.
138
135
 
139
136
  #### Claude usage limit resets
140
137
 
141
- The Claude account card shows banked limit resets from Anthropic's
142
- `cedar_ember` program, in the same closed disclosure Codex uses. It lists one
143
- row per grant, not one per credit, and the summary counts remaining resets by
144
- summing `resetsLeft` over grants that are neither expired nor paused. Each row
145
- states whether that grant is available, not yet usable, paused, exhausted, or
146
- expired, with its remaining count and when it lapses. That time is the grant's
147
- expiry: it is not a window reset and does not move the elapsed-time cursor.
138
+ The Claude account card shows banked limit resets from Anthropic's `cedar_ember`
139
+ program, in the same closed disclosure Codex uses. It lists one row per grant,
140
+ not one per credit, and the summary counts remaining resets by summing
141
+ `resetsLeft` over grants that are neither expired nor paused. Each row states
142
+ whether that grant is available, not yet usable, paused, exhausted, or expired,
143
+ with its remaining count and when it lapses. That time is the grant's expiry: it
144
+ is not a window reset and does not move the elapsed-time cursor.
148
145
 
149
146
  This is **display only**. `claimable` is shown as data on a text row, and the
150
147
  page has no control that spends a Claude reset, because the response shape is
151
148
  not an Anthropic contract. An ineligible account, a grant-less block, or a
152
- rejected optional query leaves ordinary session and weekly windows untouched
153
- and shows no list; a failed lookup shows a fixed sentence rather than the
154
- provider's text. No live account was used to verify what Anthropic returns. See
149
+ rejected optional query leaves ordinary session and weekly windows untouched and
150
+ shows no list; a failed lookup shows a fixed sentence rather than the provider's
151
+ text. No live account was used to verify what Anthropic returns. See
155
152
  [the evidence and limits](docs/claude-banked-resets.md) and the
156
153
  [display decision](.agents/notes/implemented/provider/2026-10-08-claude-banked-reset-display.md).
157
154
 
158
155
  ## Install
159
156
 
160
- Current source is tested with DSH `0.1.7-rc.2`, `0.2.0-rc.1`, and
161
- `0.2.0-rc.2`. The package's peers admit `>=0.1.7-rc.2 <0.3.0-0`: untested
162
- versions inside that range install but are unsupported, and versions
163
- outside it need an explicit `dsh plugin allow-version` exemption.
164
- `dsh plugin add` checks those ranges with `includePrerelease` semantics, so
165
- the admitted set covers every host build from `0.1.7-rc.2` through the
166
- whole `0.2.x` line, prereleases included, and blocks the `0.3.0` line. See
167
- [docs/compatibility.md](docs/compatibility.md)
168
- for the support window and [docs/testing.md](docs/testing.md) for the test
169
- layers. With `dsh` available, install the plugin into the web profile from
170
- npm:
157
+ Current source is tested with DSH `0.1.7-rc.2`, `0.2.0-rc.2`, and `0.2.1-rc.2`.
158
+ The package's peers admit `>=0.1.7-rc.2 <0.3.0-0`: untested versions inside that
159
+ range install but are unsupported, and versions outside it need an explicit
160
+ `dsh plugin allow-version` exemption. `dsh plugin add` checks those ranges with
161
+ `includePrerelease` semantics, so the admitted set covers every host build from
162
+ `0.1.7-rc.2` through the whole `0.2.x` line, prereleases included, and blocks
163
+ the `0.3.0` line. See [docs/compatibility.md](docs/compatibility.md) for the
164
+ support window and [docs/testing.md](docs/testing.md) for the test layers. With
165
+ `dsh` available, install the plugin into the web profile from npm:
171
166
 
172
167
  ```sh
173
168
  dsh plugin --profile web add dsh-subscriptions
@@ -180,47 +175,47 @@ dsh plugin --profile web add github:goodboys-ai/dsh-subscriptions
180
175
  ```
181
176
 
182
177
  Git installs run this package's `prepare` script to build the host and browser
183
- bundles. If a Git install stops with `ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED`
184
- (DSH `0.1.7-rc.2` does this by default), add this package to the web
185
- profile's `pnpm-workspace.yaml` (`~/.dsh/profiles/web/` unless `DSH_HOME` is
186
- set) and repeat the command:
178
+ bundles. If a Git install stops with `ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED` (DSH
179
+ `0.1.7-rc.2` does this by default), add this package to the web profile's
180
+ `pnpm-workspace.yaml` (`~/.dsh/profiles/web/` unless `DSH_HOME` is set) and
181
+ repeat the command:
187
182
 
188
183
  ```yaml
189
184
  allowBuilds:
190
185
  'dsh-subscriptions': true
191
186
  ```
192
187
 
193
- (If a previous Git install used the old scoped name, replace its
194
- `allowBuilds` key with the one above.)
188
+ (If a previous Git install used the old scoped name, replace its `allowBuilds`
189
+ key with the one above.)
195
190
 
196
191
  Restart `dsh web` after installation. Open **Settings → Subscriptions** to
197
- connect providers, manage accounts and model lists, and inspect available
198
- quota. The session footer quota pill can be shown or hidden there for the
199
- current browser. OpenCode Go, Kimi Code, and Ollama Cloud cards show
200
- usage when their API keys are configured.
192
+ connect providers, manage accounts and model lists, and inspect available quota.
193
+ The session footer quota pill can be shown or hidden there for the current
194
+ browser. OpenCode Go, Kimi Code, and Ollama Cloud cards show usage when their
195
+ API keys are configured.
201
196
 
202
- To update an installation, run the same `dsh plugin --profile web add` command again and restart `dsh web`.
197
+ To update an installation, run the same `dsh plugin --profile web add` command
198
+ again and restart `dsh web`.
203
199
 
204
- Without a version suffix, the npm command installs the `latest` dist-tag and
205
- the GitHub command installs the current default branch at install time. Pin
206
- a release by appending `@<version>` to the npm spec or the tag to the GitHub
207
- spec (`#vX.Y.Z`); see the
208
- [release policy](docs/compatibility.md#plugin-versioning).
200
+ Without a version suffix, the npm command installs the `latest` dist-tag and the
201
+ GitHub command installs the current default branch at install time. Pin a
202
+ release by appending `@<version>` to the npm spec or the tag to the GitHub spec
203
+ (`#vX.Y.Z`); see the [release policy](docs/compatibility.md#plugin-versioning).
209
204
 
210
205
  ## Verification and limits
211
206
 
212
- CI builds, tests, and boots a packed tarball on all supported DSH versions;
213
- it does not run the GitHub source install's `prepare` step. Offline
214
- provider tests check recorded API responses, so a passing CI run cannot prove
215
- that a provider still accepts live sign-in or model requests. See
216
- [testing](docs/testing.md) for the checks and their limits.
207
+ CI builds, tests, and boots a packed tarball on all supported DSH versions; it
208
+ does not run the GitHub source install's `prepare` step. Offline provider tests
209
+ check recorded API responses, so a passing CI run cannot prove that a provider
210
+ still accepts live sign-in or model requests. See [testing](docs/testing.md) for
211
+ the checks and their limits.
217
212
 
218
- - **ChatGPT/Codex, Claude, Grok, GitHub Copilot, and Google Antigravity:**
219
- The port includes the tool-message and icon fixes tracked in
213
+ - **ChatGPT/Codex, Claude, Grok, GitHub Copilot, and Google Antigravity:** The
214
+ port includes the tool-message and icon fixes tracked in
220
215
  [upstream issue #117](https://github.com/V1ki/dsh-plugin-subscriptions/issues/117).
221
- A manual check in an isolated `0.1.7-rc.2` web profile found the client
222
- bundle served and the auth and usage-status RPCs answering. Live sign-in
223
- and model requests on the supported DSH lines remain to be checked.
216
+ A manual check in an isolated `0.1.7-rc.2` web profile found the client bundle
217
+ served and the auth and usage-status RPCs answering. Live sign-in and model
218
+ requests on the supported DSH lines remain to be checked.
224
219
  - **Cursor:** Browser sign-in and live model discovery were verified in an
225
220
  isolated `0.1.7-rc.2` profile. A successful live model request remains to be
226
221
  verified. Its transport comes from
@@ -230,33 +225,36 @@ that a provider still accepts live sign-in or model requests. See
230
225
  - **OpenCode Go and Kimi Code:** Live usage responses were verified. Their API
231
226
  keys stay on the DSH host; the browser receives configured status and quota
232
227
  windows, not the keys.
233
- - **Ollama Cloud:** Unit and fixture coverage only (chat wire, discovery
234
- mapping and wire-id normalization, the settings store and its RPC
235
- endpoints, usage parsing, web providers, host E2E fixtures). The card's
236
- rendering has no browser test. Live chat, listing, usage, and web calls
237
- against `ollama.com` remain to be verified with a real key.
238
-
239
- Cursor uses one connected account and a live model catalog. Its model
240
- visibility selection persists under the DSH home directory, and automatic
241
- display follows newly discovered models. It ships no fallback model list and
242
- does not import the local `cursor-agent` credential cache. Individual usage
243
- comes from Cursor dashboard endpoints; the documented Admin API covers team
244
- usage.
245
-
246
- The plugin follows DSH network settings. Its former proxy configuration and
247
- UI have been retired; an old plugin proxy config file is ignored.
228
+ - **Ollama Cloud:** Unit and fixture coverage only (chat wire, discovery mapping
229
+ and wire-id normalization, the settings store and its RPC endpoints, usage
230
+ parsing, web providers, host E2E fixtures). The card's rendering has no
231
+ browser test. Live chat, listing, usage, and web calls against `ollama.com`
232
+ remain to be verified with a real key.
233
+
234
+ Cursor uses one connected account and a live model catalog. Its model visibility
235
+ selection persists under the DSH home directory, and automatic display follows
236
+ newly discovered models. It ships no fallback model list and does not import the
237
+ local `cursor-agent` credential cache. Individual usage comes from Cursor
238
+ dashboard endpoints; the documented Admin API covers team usage.
239
+
240
+ The plugin follows DSH network settings. Its former proxy configuration and UI
241
+ have been retired; an old plugin proxy config file is ignored.
248
242
 
249
243
  ## Claude prompt-cache TTL
250
244
 
251
245
  Set `claudePromptCacheTtl: '5m'` (default) or `'1h'` in the plugin config and
252
- restart DSH. This affects Claude only; other providers keep their own caching.
253
- Five minutes omits the wire `ttl` field, preserving the existing request. One
254
- hour adds `ttl: "1h"` to the tools+system prefix and up to three conversation
255
- breakpoints. All marks share one TTL: Anthropic rejects a one-hour mark after
256
- a five-minute mark. Requests classified with `purpose` as compaction or
257
- session-title keep five minutes. Unclassified calls, including subagents and
258
- calls from hosts that do not supply `purpose`, follow the configured TTL.
259
- The TTL setting does not require a newer host capability or dependency.
246
+ restart DSH. The Claude **Manage** dialog can override this for the Claude
247
+ route: its "Prompt cache lifetime" picker offers Follow config, 5 minutes, or 1
248
+ hour, and a saved choice applies from the next request without a restart. Follow
249
+ config leaves the config value in charge. This affects Claude only; other
250
+ providers keep their own caching. Five minutes omits the wire `ttl` field,
251
+ preserving the existing request. One hour adds `ttl: "1h"` to the tools+system
252
+ prefix and up to three conversation breakpoints. All marks share one TTL:
253
+ Anthropic rejects a one-hour mark after a five-minute mark. Requests classified
254
+ with `purpose` as compaction or session-title keep five minutes. Unclassified
255
+ calls, including subagents and calls from hosts that do not supply `purpose`,
256
+ follow the configured TTL. The TTL setting does not require a newer host
257
+ capability or dependency.
260
258
 
261
259
  Cache reads have the same API price for either TTL. One-hour writes are priced
262
260
  at 2× base input instead of 1.25×. They may pay off with reusable prefixes and
@@ -277,35 +275,58 @@ live-account verification of this fork.
277
275
 
278
276
  **Settings → Subscriptions → Usage coloring** offers one browser-local dropdown:
279
277
 
280
- - **Standard** (default): yellow when used quota leads elapsed time by at least 10 percentage points.
278
+ - **Standard** (default): yellow when used quota leads elapsed time by at least
279
+ 10 percentage points.
281
280
  - **Relaxed**: yellow at a lead of at least 15 points.
282
281
  - **Remaining quota only**: no yellow pace warnings.
283
282
 
284
- Every preset shows reported usage of 90% or more in red, before considering pace. For example, 40% used after 25% of a window is a 15-point lead, not a consumption forecast. Preferences apply to all meters, survive reloads and synchronize across tabs of the same origin. They change display only, never account routing or quota enforcement.
285
-
286
- A vertical marker shows elapsed time only for an explicit valid start/end interval or a provider-verified fixed duration. A session/weekly label alone does not establish a fixed window; rolling or unknown intervals have no marker. Claude's recognized five-hour/seven-day buckets and Codex's reported quota durations supply fixed-window metadata. Unknown timing falls back to green below 90%, without a marker or yellow warning.
287
-
288
- The marker depends on the window's timing alone, not on the reading: it stays at 0% used, for an invalid percentage, and when a refresh failed or the reading is more than five minutes old. Such a reading is not set against the current time, so it gets no yellow pace warning and its tooltip omits the ahead/behind comparison. It retains the last reported green/red color with an accessible stale label; refresh to confirm it. Once the reset time has passed the window has no position and the marker disappears. Invalid percentages show an unavailable state without a fabricated fill. Tooltips explain remaining quota, time progress and reset countdown where available. See the [decision record](.agents/notes/implemented/architecture/2026-10-01-usage-pace-colors.md) and the [cursor note](.agents/notes/implemented/architecture/2026-10-06-usage-cursor-timing-only.md) for rationale and verification limits.
283
+ Every preset shows reported usage of 90% or more in red, before considering
284
+ pace. For example, 40% used after 25% of a window is a 15-point lead, not a
285
+ consumption forecast. Preferences apply to all meters, survive reloads and
286
+ synchronize across tabs of the same origin. They change display only, never
287
+ account routing or quota enforcement.
288
+
289
+ A vertical marker shows elapsed time only for an explicit valid start/end
290
+ interval or a provider-verified fixed duration. A session/weekly label alone
291
+ does not establish a fixed window; rolling or unknown intervals have no marker.
292
+ Claude's recognized five-hour/seven-day buckets and Codex's reported quota
293
+ durations supply fixed-window metadata. Unknown timing falls back to green below
294
+ 90%, without a marker or yellow warning.
295
+
296
+ The marker depends on the window's timing alone, not on the reading: it stays at
297
+ 0% used, for an invalid percentage, and when a refresh failed or the reading is
298
+ more than five minutes old. Such a reading is not set against the current time,
299
+ so it gets no yellow pace warning and its tooltip omits the ahead/behind
300
+ comparison. It retains the last reported green/red color with an accessible
301
+ stale label; refresh to confirm it. Once the reset time has passed the window
302
+ has no position and the marker disappears. Invalid percentages show an
303
+ unavailable state without a fabricated fill. Tooltips explain remaining quota,
304
+ time progress and reset countdown where available. See the
305
+ [decision record](.agents/notes/implemented/architecture/2026-10-01-usage-pace-colors.md)
306
+ and the
307
+ [cursor note](.agents/notes/implemented/architecture/2026-10-06-usage-cursor-timing-only.md)
308
+ for rationale and verification limits.
289
309
 
290
310
  ### MiniMax subscription usage
291
311
 
292
- Configure `MINIMAX_API_KEY` for the global service or
293
- `MINIMAX_CN_API_KEY` for China, using DSH's standard model-key configuration.
294
- The plugin reuses those credential references; no separate subscription ref is
295
- required. Quota reads require subscription keys, not `sk-api-*` pay-as-you-go keys. The plugin reads the
296
- region's `/v1/token_plan/remains` endpoint and displays finite per-model quotas
297
- with API-provided start/end times. It does not add model routing or OAuth login.
298
- Rows are labelled with the model name, and the plugin shows the windows the API
299
- returns for each model. MiniMax documents a five-hour and a weekly window for
300
- non-video models and only a weekly one for video. Standard (non-video) models
301
- on time-based plans report an explicit remaining percentage beside zero counts,
302
- and that percentage is enough for a row. Unlimited, boosted-above-100% and
303
- unsupported quotas are omitted (a status-3 row also covers a model the plan
304
- does not include); if none remain, the card reports unavailable rather than
305
- inventing a percentage. Explicit remaining percentages take priority; older
306
- responses follow the official CLI's remaining-count compatibility convention. No
307
- live-account response was used in verification, so a missing model may still
308
- come from the account or the plan rather than from this plugin.
312
+ Configure `MINIMAX_API_KEY` for the global service or `MINIMAX_CN_API_KEY` for
313
+ China, using DSH's standard model-key configuration. The plugin reuses those
314
+ credential references; no separate subscription ref is required. Quota reads
315
+ require subscription keys, not `sk-api-*` pay-as-you-go keys. The plugin reads
316
+ the region's `/v1/token_plan/remains` endpoint and displays finite per-model
317
+ quotas with API-provided start/end times. It does not add model routing or OAuth
318
+ login. Rows are labelled with the model name, and the plugin shows the windows
319
+ the API returns for each model. MiniMax documents a five-hour and a weekly
320
+ window for non-video models and only a weekly one for video. Standard
321
+ (non-video) models on time-based plans report an explicit remaining percentage
322
+ beside zero counts, and that percentage is enough for a row. Unlimited,
323
+ boosted-above-100% and unsupported quotas are omitted (a status-3 row also
324
+ covers a model the plan does not include); if none remain, the card reports
325
+ unavailable rather than inventing a percentage. Explicit remaining percentages
326
+ take priority; older responses follow the official CLI's remaining-count
327
+ compatibility convention. No live-account response was used in verification, so
328
+ a missing model may still come from the account or the plan rather than from
329
+ this plugin.
309
330
 
310
331
  ## Development
311
332
 
@@ -315,16 +336,27 @@ pnpm build
315
336
  pnpm test
316
337
  ```
317
338
 
318
- See [CONTRIBUTING.md](CONTRIBUTING.md) for the PR workflow, test boundaries,
319
- and isolated DSH checks.
339
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for the PR workflow, test boundaries, and
340
+ isolated DSH checks.
320
341
 
321
342
  The tests and checks (unit and integration tests, the host-export and host
322
- contract checks, the boot smoke, and the host E2E) are documented in [docs/testing.md](docs/testing.md); the DSH
323
- support window and release process in
324
- [docs/compatibility.md](docs/compatibility.md).
325
-
326
- The package is published to npm as `dsh-subscriptions`, starting with the first release under the new name (GitHub source installation remains supported). The `cordis.patch.yml` bundle entry and browser module ID use the same package name. Earlier releases were published as `@goodboys-ai/dsh-subscription-hub`; that name is deprecated and points here. If the old scoped package is installed, remove it before adding the new one — installing both registers the same adapters twice and breaks plugin load.
343
+ contract checks, the boot smoke, and the host E2E) are documented in
344
+ [docs/testing.md](docs/testing.md); the DSH support window and release process
345
+ in [docs/compatibility.md](docs/compatibility.md).
346
+
347
+ The package is published to npm as `dsh-subscriptions`, starting with the first
348
+ release under the new name (GitHub source installation remains supported). The
349
+ `cordis.patch.yml` bundle entry and browser module ID use the same package name.
350
+ Earlier releases were published as `@goodboys-ai/dsh-subscription-hub`; that
351
+ name is deprecated and points here. If the old scoped package is installed,
352
+ remove it before adding the new one — installing both registers the same
353
+ adapters twice and breaks plugin load.
327
354
 
328
355
  ## Origins
329
356
 
330
- This project started from [V1ki/dsh-plugin-subscriptions](https://github.com/V1ki/dsh-plugin-subscriptions) at `090d964` under its MIT license and now includes upstream `0.9.5` (`75a8346`). The upstream English and Chinese READMEs are kept in [`docs/upstream-README.md`](docs/upstream-README.md) and [`docs/upstream-README.zh.md`](docs/upstream-README.zh.md).
357
+ This project started from
358
+ [V1ki/dsh-plugin-subscriptions](https://github.com/V1ki/dsh-plugin-subscriptions)
359
+ at `090d964` under its MIT license and now includes upstream `0.9.5`
360
+ (`75a8346`). The upstream English and Chinese READMEs are kept in
361
+ [`docs/upstream-README.md`](docs/upstream-README.md) and
362
+ [`docs/upstream-README.zh.md`](docs/upstream-README.zh.md).
@@ -1,5 +1,5 @@
1
1
  import { execFileSync } from 'node:child_process';
2
- import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync, } from 'node:fs';
3
3
  import { homedir } from 'node:os';
4
4
  import { dirname, join } from 'node:path';
5
5
  const PRIMARY_SERVICE = 'Claude Code-credentials';
@@ -8,19 +8,30 @@ function toSession(data) {
8
8
  // Empty-string tokens (seen from a corrupted Keychain item left by a Claude
9
9
  // Code logout) pass the typeof gate but are useless and would poison the
10
10
  // auth store — a single such entry fails every provider's status read.
11
- if (typeof data.accessToken !== 'string' || data.accessToken.length === 0
12
- || typeof data.refreshToken !== 'string' || data.refreshToken.length === 0
13
- || typeof data.expiresAt !== 'number' || !Number.isFinite(data.expiresAt)) {
11
+ if (typeof data.accessToken !== 'string' ||
12
+ data.accessToken.length === 0 ||
13
+ typeof data.refreshToken !== 'string' ||
14
+ data.refreshToken.length === 0 ||
15
+ typeof data.expiresAt !== 'number' ||
16
+ !Number.isFinite(data.expiresAt)) {
14
17
  return undefined;
15
18
  }
16
- const scopes = Array.isArray(data.scopes) ? data.scopes.join(' ') : typeof data.scopes === 'string' ? data.scopes : DEFAULT_SCOPES;
19
+ const scopes = Array.isArray(data.scopes)
20
+ ? data.scopes.join(' ')
21
+ : typeof data.scopes === 'string'
22
+ ? data.scopes
23
+ : DEFAULT_SCOPES;
17
24
  return {
18
25
  accessToken: data.accessToken,
19
26
  refreshToken: data.refreshToken,
20
27
  expiresAt: Math.trunc(data.expiresAt),
21
28
  scopes,
22
- ...typeof data.emailAddress === 'string' ? { emailAddress: data.emailAddress } : {},
23
- ...typeof data.subscriptionType === 'string' ? { subscriptionType: data.subscriptionType } : {},
29
+ ...(typeof data.emailAddress === 'string'
30
+ ? { emailAddress: data.emailAddress }
31
+ : {}),
32
+ ...(typeof data.subscriptionType === 'string'
33
+ ? { subscriptionType: data.subscriptionType }
34
+ : {}),
24
35
  };
25
36
  }
26
37
  function parseBlob(raw) {
@@ -117,7 +128,16 @@ export function writeBackClaudeCodeCredentials(next, expectedPriorAccessToken) {
117
128
  return false;
118
129
  const account = getKeychainAccountName() ?? PRIMARY_SERVICE;
119
130
  try {
120
- execFileSync('/usr/bin/security', ['add-generic-password', '-s', PRIMARY_SERVICE, '-a', account, '-w', updated, '-U'], {
131
+ execFileSync('/usr/bin/security', [
132
+ 'add-generic-password',
133
+ '-s',
134
+ PRIMARY_SERVICE,
135
+ '-a',
136
+ account,
137
+ '-w',
138
+ updated,
139
+ '-U',
140
+ ], {
121
141
  timeout: 2000,
122
142
  stdio: 'ignore',
123
143
  });
@@ -163,7 +183,9 @@ export function writeBackClaudeCodeCredentials(next, expectedPriorAccessToken) {
163
183
  */
164
184
  export async function refreshClaudeSynced(session, doRefresh) {
165
185
  const fromSource = readClaudeCodeCredentials();
166
- const base = fromSource !== undefined && fromSource.accessToken !== session.accessToken ? fromSource : session;
186
+ const base = fromSource !== undefined && fromSource.accessToken !== session.accessToken
187
+ ? fromSource
188
+ : session;
167
189
  if (base.expiresAt > Date.now() + 60_000)
168
190
  return base;
169
191
  const next = await doRefresh(base);