dsh-plugin-subscription-oauth 0.10.2 → 0.12.0

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/README.md CHANGED
@@ -2,180 +2,35 @@
2
2
 
3
3
  # dsh-plugin-subscription-oauth
4
4
 
5
- [![Listed on dsh-plugin.org](https://dsh-plugin.org/badges/listed.svg)](https://dsh-plugin.org/plugins/EricFL1998/dsh-subscription-oauth)
6
- [![npm](https://img.shields.io/npm/v/dsh-plugin-subscription-oauth.svg)](https://www.npmjs.com/package/dsh-plugin-subscription-oauth)
7
- [![Licence: MIT](https://img.shields.io/badge/licence-MIT-blue.svg)](LICENSE)
8
-
9
- **Use the AI subscriptions you already pay for as LLM providers in DeepSeek
10
- Harness — no API key.** Sign in once per provider from **Settings →
11
- Subscriptions** (订阅) and that account's models join the session model picker,
12
- with its plan quota shown next to the connection.
13
-
14
- ```sh
15
- dsh plugin --profile web add dsh-plugin-subscription-oauth
16
- ```
17
-
18
- The same package installs straight from the repository, which needs no registry:
19
-
20
- ```sh
21
- dsh plugin --profile web add github:EricFL1998/dsh-subscription-oauth
22
- ```
23
-
24
- | | |
25
- | --- | --- |
26
- | **Capability** | 模型与推理 / Model & inference — it registers LLM provider routes |
27
- | **Licence** | [MIT](LICENSE) |
28
- | **Built for** | DeepSeek Harness `0.2.0-rc.2` (`@deepseek-ai/dsh-llm`), Node ≥ 20 |
29
- | **Platforms** | Windows, macOS and Linux; nothing platform-specific beyond Node itself |
30
- | **Accounts** | your own subscriptions only — credentials stay on your machine |
31
- | **Network** | the providers' own hosts and the OAuth endpoints listed below; no telemetry, no third-party relay |
32
-
33
- | Route | Provider | Login | Wire |
34
- | --- | --- | --- | --- |
35
- | `kimi` | Kimi Code | OAuth **device flow** (RFC 8628) | OpenAI **Responses** — `https://api.kimi.com/coding/v1/responses` |
36
- | `grok` | Grok (X Premium) | OIDC **authorization code + PKCE** on a loopback callback | OpenAI **Responses** — `https://api.x.ai/v1/responses` |
37
- | `antigravity` | Google Antigravity | Google **authorization code + PKCE** (`/oauth-callback`) plus Cloud AI Companion project discovery | Cloud Code Assist `v1internal` — `streamGenerateContent?alt=sse` |
38
- | `glm` | GLM (BigModel) | ZCode's **browser sign-in with a polled flow** (China region) | OpenAI **Responses** — `https://open.bigmodel.cn/api/coding/paas/v4/responses` |
39
- | `ark` | Volcengine Ark | Volcengine **SSO** (authorization code + PKCE on a loopback callback) | OpenAI **Responses** — `https://ark.cn-beijing.volces.com/api/coding/v3/responses` |
40
- | `auto` | Auto | none — it holds no account of its own | delegates to whichever route owns the model it picks |
41
-
42
- The Kimi device flow is the one `opencodex` uses; the Grok flow is the one its
43
- CLI uses (`auth.x.ai` discovery, client `b1a00492-…`, port `56121`, scope
44
- `grok-cli:access`); the Antigravity flow is Google's desktop OAuth client
45
- (`accounts.google.com`, port `51121`, scopes including `cclog` and
46
- `experimentsandconfigs`), with the account's project discovered through
47
- `loadCodeAssist`.
48
-
49
- **GLM signs in the way ZCode does**, not with an API key: the plugin asks
50
- `zcode.z.ai` for a login flow, opens `bigmodel.cn/login` in the browser, and polls
51
- the flow until the subscription's tokens come back. Nothing has to receive a
52
- `zcode://oauth/callback` redirect and no app secret is involved, because the
53
- callback is completed server-side and the flow is authenticated by a secret the
54
- plugin generates itself. Allowances are read from BigModel's own
55
- `/api/monitor/usage/quota/limit`, which meters a rolling five-hour window and a
56
- weekly one.
57
-
58
- **Ark signs in with Volcengine SSO** (authorization code + PKCE on a loopback
59
- callback, the flow its own CLI uses) and then has to satisfy two planes that want
60
- different credentials. The **data plane** takes only an ordinary API key as a plain
61
- bearer — it refuses STS credentials outright — while the **control plane**, a
62
- different host, is signed with the temporary STS credentials that the SSO token
63
- response carries. So a sign-in mints one API key through the control plane (reusing
64
- a key this plugin already created, if there is one) and keeps the STS set for the
65
- quota, which is read per subscription period and per model's free allowance.
66
-
67
- ## What it registers
68
-
69
- | Surface | What appears |
70
- | --- | --- |
71
- | `ctx.llm` | the `kimi`, `grok`, `antigravity`, `glm` and `auto` provider routes (models, reasoning efforts, streaming) |
72
- | Settings | a **Subscriptions** section: one card per provider — sign in, sign out, default account, token refresh, plan quota, model catalog, manage dialog |
73
- | Model picker | each provider's own models, plus **`auto` as its own route** spanning them — Kimi ships `kimi-for-coding`, `k3`, `k3[1m]`, `k3-256k`; Grok seeds `grok-4.6`, `grok-4.5` and adds whatever its live catalog reports; Antigravity lists the agent-callable subset of what its endpoint reports (`fetchAvailableModels`), filtering out tabs, commit-message and transcription surfaces; GLM ships the `glm-5.3` family (`glm-5.3`, `glm-5.3-flash`, `glm-5.2`, `glm-5.1`, `glm-5`) and adds whatever its coding host lists |
74
-
75
- Credentials live at `~/.dsh/plugins/kimi-oauth/auth.json` (mode `0600`,
76
- written atomically, one section per provider). The directory keeps the name this
77
- plugin had when it served Kimi Code alone: it holds **live logins**, so it is left
78
- in place rather than renamed, and every profile that mounted the earlier version
79
- finds its accounts exactly where it left them. This machine's Kimi device id
80
- lives beside it in `kimi-device-id`; it is stable across restarts, because the
81
- service correlates usage to it.
5
+ **Use the AI subscriptions you already pay for as LLM providers in DeepSeek Harness — no API key.**
82
6
 
83
- ### Token lifetimes, and why the card may say "expired"
7
+ Sign in once per provider from **Settings → Subscriptions** and that provider's models join the session model picker, with its plan quota shown live next to the connection. Tokens stay on your machine; every request streams through the provider's own API.
84
8
 
85
- Kimi Code access tokens live **15 minutes**; the refresh token lives **30 days**.
86
- Grok access tokens are similar. Every request refreshes automatically when the
87
- access token is within five minutes of lapsing, and the Settings card refreshes
88
- eagerly while you are looking at it — so what it shows is the live expiry, and
89
- "token expired" appears only when a refresh genuinely failed and the account
90
- needs a new sign-in. Access-token expiry is normal and does not mean the login
91
- is gone.
9
+ [![npm](https://img.shields.io/npm/v/dsh-plugin-subscription-oauth.svg)](https://www.npmjs.com/package/dsh-plugin-subscription-oauth)
10
+ [![Licence: MIT](https://img.shields.io/badge/licence-MIT-blue.svg)](LICENSE)
92
11
 
93
- ## Settings page
12
+ ## Supported providers
94
13
 
95
- | Every route folded | A subscription card open | A card with its catalog |
14
+ | Provider | What you get | Sign-in |
96
15
  | --- | --- | --- |
97
- | ![Folded provider list](docs/images/providers-folded.png) | ![GLM card with plan quota](docs/images/glm-card-open.png) | ![Kimi card with its catalog](docs/images/kimi-card-open.png) |
98
-
99
- The page leads with **`auto`**, the way to reach every other route at once; the
100
- routes it stands in for follow in the order they were configured. Each route is one
101
- **folded row** — status dot, name, route key, a one-line summary of the connection,
102
- and an **Edit** button — so the section reads as a list of providers and opens one
103
- on demand. **Every visit starts folded** — an open card is a gesture within one
104
- visit, not a stored setting — so re-opening Settings always shows the same compact
105
- list. Inside an open card are the
106
- account rows (★ sets the default account, `退出登录`/`Sign out` removes one), the
107
- model catalog, and the plan quota. Controls follow the host's own settings-form
108
- vocabulary — a 6 px radius (`--dsw-radius-sm`), a transparent fill, a hairline
109
- border, and the theme's hover/active/focus tokens — declared in one scoped
110
- stylesheet, so the page keeps that look instead of falling back to the browser's
111
- default button chrome.
112
-
113
- The **Models** heading is its own disclosure, **folded on every visit** too: click it to open the
114
- catalog. **Each model has its own switch** (a toggle,
115
- not a checkbox), which decides whether the session model picker offers it. The
116
- choice is stored on the host (`~/.dsh/plugins/kimi-oauth/preferences.json`) as a
117
- hidden list, so it survives restarts and every session sees it; a model added by a
118
- later catalog refresh appears automatically rather than arriving hidden. A hidden
119
- model is listed dimmed and stays **resolvable** — an existing session that already
120
- selected it keeps working — so hiding is a picker-visibility choice, not a
121
- revocation. `· 已隐藏 N 个` in the header counts the hidden ones.
122
-
123
- **Add model** publishes an id this build has never heard of, so a model released
124
- after the plugin was built is usable at once. The id is checked for shape, not
125
- against a list — the endpoint is the only authority on what it serves — and it is
126
- stored per route, published with conservative metadata, and listed with a `×` so a
127
- typo can be removed again. **Drag a row** by its ⠿ grip (or press ↑ / ↓ while the
128
- grip is focused) to re-order the list; that order is what the picker follows for
129
- that route.
130
-
131
- **`auto` is a provider of its own** (route `auto`), not a model inside each one: it
132
- stands in for the enabled models of *every* provider the harness serves in one list,
133
- each row badged with the provider that owns it. That includes the routes this plugin
134
- registers **and** the ones it does not — the built-in DeepSeek providers and any
135
- provider you added yourself — because it enumerates the harness roster
136
- (`ctx.llm.listProviders()` / `listModels()`) and dispatches through it rather than
137
- looking only at its own routes. It leads both the Settings section and the model
138
- picker, because it is the one entry that reaches every route. It has no account, so
139
- its card offers no connect or manage — only the list. A switch takes a model out of
140
- Auto **without** hiding it from the picker, drag re-orders what Auto tries first, and
141
- the arrangement is stored separately (`auto.order` / `auto.off`). Switch every model
142
- off and Auto is simply not offered in the picker.
143
-
144
- A provider with no signed-in account contributes no rows: `listModels` answers with
145
- nothing until a route has a session, so signing out removes that route's models from
146
- the picker and from Auto at the same time, and its card hides the catalog instead of
147
- offering models that cannot be called.
148
-
149
- Auto falls through to the next model when the provider refuses the preferred one
150
- (renamed or gone, rate-limited, out of allowance, or a transport failure). A
151
- concrete model id never falls back — it dispatches to exactly that model. Once a
152
- stream has produced any output the turn belongs to that model, so a later failure
153
- is reported instead of being papered over by a second model's text. Every fallback
154
- is logged (`model X unavailable … falling back to Y`).
16
+ | **Kimi Code** | `kimi-for-coding`, `k3`, `k3[1m]`, `k3-256k` + live catalog | OAuth device flow (show code, confirm in browser) |
17
+ | **Grok (X Premium)** | `grok-4.7`, `grok-4.6`, … + live catalog, **⚡ lightspeed** fast variant | Browser OAuth (auto-redirect) |
18
+ | **Google Antigravity** | Claude Sonnet/Opus, Gemini Flash, GPT-OSS, with reasoning tiers | Google account OAuth |
19
+ | **GLM (BigModel)** | `glm-5.3`, `glm-5.3-flash`, `glm-5.2`, `glm-5.1`, `glm-5` | ZCode browser sign-in (polled) |
20
+ | **Volcengine Ark** | Ark coding models + live catalog | Volcengine SSO |
21
+ | **Xiaomi MiMo** | MiMo Pro, MiMo Flash | Xiaomi account (QR / password) |
22
+ | **WorkBuddy (国内版)** | Hy, GLM, Kimi, DeepSeek, MiniMax models | Browser OAuth (Tencent) |
155
23
 
156
- **Plan and usage** shows the subscription tier and every allowance the provider
157
- states, with a meter and a reset time: Kimi's rolling 5-hour window, weekly window
158
- and included credit (`GET /coding/v1/usages`); Grok's weekly included credit
159
- (`cli-chat-proxy.grok.com/v1/billing?format=credits`, plus the plan tier from the
160
- token's own `tier` claim); and Antigravity's **four** buckets — Gemini and Claude,
161
- each rolling and weekly — read from `v1internal:retrieveUserQuotaSummary`, falling
162
- back to the per-model `quotaInfo` of `fetchAvailableModels` (worst-spent model per
163
- family, classified by id, display name and tier, so an `Opus` row counts as
164
- Claude). A provider that states nothing says so instead of inventing a figure.
24
+ Every provider also feeds the **`auto`** route, which spans them all: pick `auto` and the harness walks your enabled models in order until one answers.
165
25
 
166
- A reset is stated by how far off it is, on every route: **minutes** within the hour
167
- (`40 分钟后重置`), **hours** within the day (`4 小时后重置`), and the date beyond that
168
- (`重置时间 2026/10/17 13:54`) — the count is what a reader acts on when deciding whether
169
- there is room left, and a date is what they need when it is a week away. Because the
170
- page re-reads the figures on a slow interval, the count follows along while it is
171
- open.
26
+ ## Features
172
27
 
173
- **Manage** opens a dialog with per-account token refresh, the credential-file
174
- path, and the resolved endpoints.
175
-
176
- While a device authorization is pending the card shows the code, its expiry, and
177
- the buttons to open or copy it, and the status dot turns amber. A loopback
178
- provider (Grok) instead shows a sign-in link and waits for the browser redirect.
28
+ - **One sign-in per provider** — no API keys to find, paste, or rotate. The plugin runs the provider's own OAuth/device flow and keeps the token on disk (`~/.dsh/plugins/kimi-oauth/auth.json`, mode `0600`).
29
+ - **Live plan usage** — the Settings card and the composer chip show each account's remaining quota (Kimi's 5-hour / weekly windows + credit, Grok's weekly credit, Antigravity's Gemini + Claude buckets, GLM/Ark/MiMo/WorkBuddy credits), with a meter and reset time.
30
+ - **Model picker integration** — each provider's models appear in the session picker with context windows and reasoning tiers; hide or re-order them per provider.
31
+ - **⚡ Fast / lightspeed variants** — for models that have one (e.g. Grok 4.7 → `grok-4.7-build-fast`), a bolt button beside the model selector switches to the fast variant; it reads as a reasoning rung so the base model always stays selectable.
32
+ - **Cross-account pool** — sign in to several accounts on one provider; the plugin draws on the next account when one's allowance runs out.
33
+ - **Auto fallback** — `auto` moves to the next enabled model when a provider refuses the preferred one (renamed, rate-limited, out of quota), and stops falling back once a stream has started.
179
34
 
180
35
  ## Install
181
36
 
@@ -183,252 +38,56 @@ provider (Grok) instead shows a sign-in link and waits for the browser redirect.
183
38
  dsh plugin --profile web add dsh-plugin-subscription-oauth
184
39
  ```
185
40
 
186
- `web` is the profile that serves the Settings page; use `desktop` (or any other
187
- profile name) if that is the one you run.
41
+ Then restart DeepSeek Harness and open **Settings → Subscriptions** (订阅).
188
42
 
189
- The CLI forwards its arguments to pnpm, so a pinned version, a git URL and a local
190
- path are all spelled the same way:
43
+ To install a specific version, or straight from the repository:
191
44
 
192
45
  ```sh
193
- dsh plugin --profile web add dsh-plugin-subscription-oauth@0.2.2
46
+ dsh plugin --profile web add dsh-plugin-subscription-oauth@0.10.2
194
47
  dsh plugin --profile web add github:EricFL1998/dsh-subscription-oauth
195
- dsh plugin --profile web add /path/to/dsh-plugin-subscription-oauth
196
48
  ```
197
49
 
198
- Restart `dsh web` (or the desktop app) afterwards so the new bundle loads. A local
199
- directory is installed as a `link:`, so afterwards `npm run build` in the checkout
200
- is enough to publish a change; restart again to pick it up.
201
-
202
- The host packages (`@deepseek-ai/dsh-llm` and the rest) are **optional** peers: the
203
- harness provides them at runtime, so installing this plugin does not fetch a second
204
- copy of the host it is running inside.
205
-
206
- ### Releasing (maintainers)
50
+ ## How to use
207
51
 
208
- `prepublishOnly` rebuilds `lib/` — the TypeScript output plus the browser bundle — so
209
- a published tarball can never carry a stale client:
210
-
211
- ```sh
212
- npm version patch # or minor/major
213
- npm publish
214
- ```
52
+ 1. **Open Settings → Subscriptions.** You see one card per provider; signed-in providers sort to the front, `auto` leads.
53
+ 2. **Connect** a provider. A browser window (or a device code) starts the provider's own sign-in. Finish it there — the card updates by itself.
54
+ 3. **Pick a model.** The provider's models now appear in the composer model picker, and the chip shows live usage.
55
+ 4. **Expand a card** to manage accounts (set default, sign out, refresh token), toggle which models appear, re-order them, add a model id this build doesn't know yet, and see the credential file / endpoints.
215
56
 
216
- For a headless composition that only needs the model route (no Settings page):
57
+ > **Tip:** to use a different account on a provider, sign out first, then connect again — the browser sign-in follows whichever account the provider's page is signed in with.
217
58
 
218
- ```sh
219
- dsh --profile headless --patch /path/to/dsh-plugin-subscription-oauth/overlay.yml "your task"
220
- ```
59
+ ## What it registers
221
60
 
222
- where `overlay.yml` is a one-entry patch naming
223
- `/path/to/dsh-plugin-subscription-oauth/lib/index.js`. Log in once from a web profile
224
- first — the token file is shared.
61
+ | Surface | What appears |
62
+ | --- | --- |
63
+ | `ctx.llm` | the `kimi`, `grok`, `antigravity`, `glm`, `ark`, `mimo`, `workbuddy` and `auto` provider routes (models, reasoning, streaming) |
64
+ | Settings | a **Subscriptions** section: one card per provider |
65
+ | Model picker | each provider's own models, plus **`auto`** spanning them all |
66
+ | Composer chip | live plan usage for the provider in force |
225
67
 
226
- ## Hiding a built-in provider from the picker
68
+ ## Hiding a provider
227
69
 
228
- A plugin cannot unregister another bundle's route (`ctx.llm` has no unregister
229
- API), so hiding a built-in provider is a profile-patch change. To hide the
230
- **DeepSeek API-key** route and its models while keeping the **DeepSeek account**
231
- (sign-in) route:
70
+ To keep a provider's models out of the picker without uninstalling, drop it from the route list in your profile's `cordis.patch.yml`:
232
71
 
233
- ```yaml
72
+ ```yml
234
73
  # $DSH_HOME/profiles/<profile>/cordis.patch.yml
235
- - id: llm-deepseek
236
- name: "@deepseek-ai/dsh-llm-deepseek-api-key"
237
- config:
238
- models: []
74
+ plugins:
75
+ dsh-plugin-subscription-oauth:
76
+ providers: [kimi, grok, antigravity, glm, ark, mimo, workbuddy]
239
77
  ```
240
78
 
241
- `models` replaces the adapter's default catalog, so `deepseek-official` reports
242
- no models and the picker hides the empty group (`Empty groups are hidden`); the
243
- route stays registered, so a session that already saved one of its model ids
244
- keeps working. Leave `llm-deepseek-account` alone to keep the account route.
245
- Restart the profile (or let HMR reload) for the change to apply.
246
-
247
- ## Use
248
-
249
- 1. Open **Settings → Subscriptions** (订阅) and press **Connect** on the card you want.
250
- 2. Finish the sign-in the card shows: Kimi Code and (when answered) a device code
251
- you approve in the browser; Grok and Antigravity open a sign-in page and wait for
252
- the redirect. The section updates by itself while an attempt is pending.
253
- 3. Open the model picker in any session and choose a model under that provider —
254
- or leave it on **Auto** to let the enabled models be tried in order.
255
-
256
- Not signed in? The provider stays out of the picker, and a request fails with
257
- `INVALID_CREDENTIAL` pointing at the Settings page. Nothing else breaks.
258
-
259
- Multiple accounts are supported: connect again to add another. The ★ default
260
- account serves requests; use **Use by default** to switch.
261
-
262
- ## Config
263
-
264
- ```yaml
265
- - id: llm-subscription-oauth
266
- name: dsh-plugin-subscription-oauth
267
- config:
268
- providers: [kimi, grok, antigravity] # routes to register; all are default
269
- enabled: true
270
- discovery: true # consult each endpoint's live model list
271
- streamIdleTimeoutMs: 300000 # abandon a silent stream after this
272
- defaultModel: kimi-for-coding # model the picker preselects on the Kimi route
273
- extraModels: [] # Kimi ids to publish without shipped metadata
274
- # oauthHost: https://auth.kimi.com
275
- # apiBase: https://api.kimi.com/coding/v1
276
- ```
277
-
278
- Only the Kimi route takes `oauthHost`/`apiBase`/`defaultModel`/`extraModels`: the
279
- Grok and Antigravity endpoints are fixed by their own clients, and their model
280
- lists come from the live catalog. Omitting a route from `providers` simply leaves
281
- it unregistered.
282
-
283
- `oauthHost` and `apiBase` can also come from the environment
284
- (`KIMI_CODE_OAUTH_HOST` / `KIMI_OAUTH_HOST`, `KIMI_CODE_API_BASE` /
285
- `KIMI_API_BASE`); an explicit config value wins. `apiBase` accepts either the
286
- bare base or the full `.../responses` URL.
287
-
288
- ## How it works
289
-
290
- The package is a standard DeepSeek Harness plugin: `lib/index.js` exports
291
- `apply(ctx)`, which registers one `ctx.llm` adapter per route and mounts the
292
- Settings section through `ctx.slots`. `lib/client.js` is the browser half — the
293
- section itself, loaded by the harness bundle loader. Nothing runs outside the
294
- harness process, and uninstalling the bundle removes every route it added.
295
-
296
- **Login** is the RFC 8628 device authorization grant against
297
- `auth.kimi.com`. The plugin requests a device code, shows it, and polls the
298
- token endpoint with `authorization_pending` / `slow_down` / `access_denied` /
299
- `expired_token` handling until the browser approves. Tokens are stored per
300
- account, keyed by the `user_id` (or `sub`) claim inside Kimi's JWTs.
301
-
302
- **Grok** signs in with the flow its own CLI uses: OIDC discovery at
303
- `auth.x.ai`, an authorization-code exchange with PKCE on a loopback callback
304
- (`127.0.0.1:56121/callback`), and a refresh grant that keeps the client identity.
305
- **Antigravity** is Google's desktop OAuth client on `127.0.0.1:51121`, whose
306
- scopes include `cloud-platform`; login is only complete once `loadCodeAssist`
307
- reports the account's Cloud AI Companion project, because every later
308
- `v1internal` call is scoped to it.
309
-
310
- **Requests** carry three identity layers:
311
-
312
- * the bearer access token, refreshed ahead of expiry (5-minute preempt) with one
313
- in-flight refresh shared per account;
314
- * Kimi's `X-Msh-*` client identity (`KimiCLI/<version>`, platform, device id) —
315
- the service admits the request by this family;
316
- * the harness attribution product. Because Kimi admits a request by the CLI
317
- product token while the harness requires its own attribution on every provider
318
- request, the two are joined into one RFC 9110 `User-Agent` value rather than
319
- sent as two competing fields.
320
-
321
- **The wire** is OpenAI Responses: `instructions` + `input` items,
322
- `function_call` / `function_call_output` kept adjacent (the endpoint requires
323
- it), `stream: true`, `store: false`, `prompt_cache_key` from the session id for
324
- cache affinity, and `reasoning.effort` mapped from the harness's effort id.
325
- Stored reasoning is never replayed as text — the endpoint keeps a subscription
326
- account's thinking encrypted server-side, so each turn stands on its own visible
327
- content.
328
-
329
- Kimi locks temperature and sampling penalties on subscription coding models, so a
330
- caller-supplied `temperature` is dropped rather than rejected upstream.
331
-
332
- ## Troubleshooting
333
-
334
- | Symptom | Cause |
335
- | --- | --- |
336
- | `<provider> login expired; sign in again…` | the refresh token was rejected; connect again |
337
- | No models in the picker | not signed in, or the route was not re-announced — check **Settings → Subscriptions** |
338
- | No models at all for Antigravity | the account's `fetchAvailableModels` returned nothing; the route ships no curated list, so press **Refresh** after signing in |
339
- | `model not found` | the id is not served by your plan; use **Refresh** |
340
- | The code card never clears | the attempt is still pending or expired; press **Cancel** and connect again |
341
- | `model discovery failed … using the built-in catalog` | the live list was unreachable; the shipped catalog still works (Kimi and Grok ship one, Antigravity does not) |
342
- | Rows cannot be dragged | the layout is switched off in Settings, or the drag started on the checkbox — grab the ⠿ grip |
79
+ Omit the ones you don't want. The list defaults to every implemented route.
343
80
 
344
- ## Security notes
81
+ ## Security
345
82
 
346
- ### What it stores, and where
347
-
348
- | Path | Contents | Mode |
349
- | --- | --- | --- |
350
- | `~/.dsh/plugins/kimi-oauth/auth.json` | one section per provider: access token, refresh token, expiry, account id, plan | `0600`, written atomically |
351
- | `~/.dsh/plugins/kimi-oauth/preferences.json` | model visibility, your ordering, and Auto's chain — no secrets | `0600` |
352
- | `~/.dsh/plugins/kimi-oauth/kimi-device-id` | the stable device id Kimi's API expects | `0600` |
353
-
354
- The directory keeps the name this plugin had when it served Kimi Code alone: it
355
- holds **live logins**, so renaming it would sign everyone out. Nothing is stored
356
- outside `~/.dsh`, and nothing is uploaded anywhere except to the providers
357
- themselves.
358
-
359
- ### What it talks to
360
-
361
- | Route | Hosts contacted | Why |
362
- | --- | --- | --- |
363
- | `kimi` | `auth.kimi.com`, `api.kimi.com` | device-flow sign-in, then the coding API |
364
- | `grok` | `auth.x.ai`, `api.x.ai`, `cli-chat-proxy.grok.com` | OIDC sign-in, the Responses API, the plan's credit figure |
365
- | `antigravity` | `accounts.google.com`, `oauth2.googleapis.com`, `cloudcode-pa.googleapis.com` | Google sign-in, the account's project, then Cloud Code Assist (including its quota accounting) |
366
- | `glm` | `bigmodel.cn`, `zcode.z.ai`, `open.bigmodel.cn` | browser sign-in brokered by ZCode, then the coding-plan API and its allowance |
367
- | `ark` | `signin.volcengine.com`, `ark.cn-beijing.volces.com`, `ark.cn-beijing.volcengineapi.com` | Volcengine SSO, then the coding-plan API (data plane) and the signed control plane that mints the API key and reports the allowance |
368
-
369
- There is no analytics, no update ping and no relay through anyone else's server:
370
- every request goes to the host that owns the account. No request body, prompt or
371
- token is ever logged.
372
-
373
- ### Boundaries worth knowing
374
-
375
- * The token file is written with mode `0600`; only the account id, email, host,
376
- and device id are ever shown in the UI — never a token.
377
- * The Kimi and GLM flows need no listener on this machine at all: Kimi polls, and
378
- GLM's browser callback is completed server-side by the provider. Grok and
379
- Antigravity hold one loopback listener **only** while a login attempt is pending,
380
- on `127.0.0.1:56121` and `127.0.0.1:51121`, and it is closed as soon as the
381
- attempt settles or lapses. A loopback destination is never proxied.
382
- * Outbound requests honour `HTTPS_PROXY` / `HTTP_PROXY` / `ALL_PROXY` and
383
- `NO_PROXY`.
384
- * Signing in authorizes this machine for that account — the provider's own consent
385
- screen is the consent step, and it is never completed on your behalf.
386
- * A plugin cannot unregister another bundle's provider (`ctx.llm` has no unregister
387
- API), so this plugin never hides anything you had before; see
388
- [Hiding a built-in provider from the picker](#hiding-a-built-in-provider-from-the-picker)
389
- if that is what you want.
390
- * **Known limits**: OpenAI-compatible API keys are not supported by design (this is
391
- a subscription plugin); GLM's flow has no refresh grant, so a lapsed sign-in asks
392
- you to sign in again rather than renewing silently; Antigravity ships no curated
393
- model list and depends on its endpoint answering.
83
+ - Credentials are stored locally at `~/.dsh/plugins/kimi-oauth/auth.json` (mode `0600`, written atomically), one section per provider.
84
+ - The plugin talks only to the providers' own hosts and OAuth endpoints — no telemetry, no third-party relay.
85
+ - Your subscriptions only; nothing is shared across machines.
394
86
 
395
87
  ## Develop
396
88
 
397
- ```sh
398
- npm install # development baseline: published DSH 0.2.0-rc.2 packages
399
- npm run build # tsc (lib/) + tsdown (lib/client.js browser bundle)
400
- npm test # node --test over the compiled unit specs
401
- npm run typecheck # tsc --noEmit
402
- ```
403
-
404
- `tools/probe-kimi-oauth.mjs` performs a live check that Kimi accepts the shipped
405
- client id and identity headers; it starts a device authorization and immediately
406
- abandons it, so no account is touched (`npm run build` first).
407
-
408
- ### Reviewing the settings UI without a host
409
-
410
- `tools/preview-entry.tsx` mounts the real section with a stubbed RPC caller, so
411
- a design change can be screenshotted on its own. Query flags drive the states:
412
- `?dark=1`, `?grok=1`, `?ag=1`, `?busy=1`, `?hide=1`, `?collapse=1`, `?manage=1`.
413
-
414
- ```sh
415
- npm run preview # bundles probe/preview.js
416
- # then open probe/preview.html in a browser, or screenshot it headlessly:
417
- msedge --headless=new --user-data-dir=%TEMP%\shot --window-size=620,1900 \
418
- --screenshot=shot.png "file:///<checkout>/probe/preview.html?dark=1&grok=1&ag=1"
419
- ```
420
-
421
- The harness deliberately styles no `button` of its own: the shell gives its
422
- controls chrome through a CSS class plugin markup does not carry, so a preview
423
- that decorated bare buttons would hide exactly the regression this page had.
424
-
425
- Query flags: `dark=1` (dark theme), `compact=1` (model list collapsed),
426
- `busy=1` (pending device authorization), `manage=1` (manage dialog open).
427
- `probe/` holds only generated preview output and is gitignored; the committed
428
- screenshots live in `docs/images/`.
429
-
430
- After `npm run build`, restart `dsh web` to pick up the change.
89
+ See the [developer README](README.dev.md) for the full wire protocol, token lifetimes, troubleshooting, and how to run the test suite.
431
90
 
432
91
  ## License
433
92
 
434
- MIT
93
+ [MIT](LICENSE)