@hystudio/dsh-quota-pill 0.2.7 → 0.2.11
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 +188 -20
- package/README.md +85 -17
- package/bin/opendesign-login.js +80 -0
- package/client.js +145 -27
- package/docs/screenshot.png +0 -0
- package/index.js +12 -4
- package/package.json +5 -3
- package/src/adapters-apikey.js +8 -3
- package/src/adapters-opendesign.js +199 -57
- package/src/autostart.js +64 -0
- package/src/credentials.js +60 -9
- package/src/http.js +45 -0
- package/src/opendesign-auth.js +95 -0
- package/src/providers.js +112 -0
- package/src/server.js +71 -55
- package/src/user-adapters.js +172 -0
package/AGENTS.md
CHANGED
|
@@ -9,6 +9,39 @@ the human only for things only they can supply (an API key, an OAuth login).
|
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
+
## Before 1: install the bundle into DSH
|
|
13
|
+
|
|
14
|
+
This guide assumes the **bundle** is installed and enabled, not just the sidecar.
|
|
15
|
+
For a fresh DSH Desktop:
|
|
16
|
+
|
|
17
|
+
1. If `dsh` is not on PATH: DSH Desktop → Settings → **Manage dsh Command**.
|
|
18
|
+
2. Install a **pinned** build, never the bare name:
|
|
19
|
+
- GUI: **Plugins → Install** → `@hystudio/dsh-quota-pill@0.2.11`
|
|
20
|
+
- CLI: `dsh plugin add @hystudio/dsh-quota-pill@0.2.11 --profile desktop`
|
|
21
|
+
- Until 0.2.11 is on npm:
|
|
22
|
+
`github:598934321/dsh-quota-pill#feat/user-adapters-0.2.11`
|
|
23
|
+
3. Confirm the dependency and the bundle are present:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
python3 - <<'PY'
|
|
27
|
+
import json, os
|
|
28
|
+
p = os.path.expanduser('~/.dsh/profiles/desktop/package.json')
|
|
29
|
+
d = json.load(open(p))
|
|
30
|
+
print(d.get('dependencies', {}).get('@hystudio/dsh-quota-pill'))
|
|
31
|
+
print('enabled:', '@hystudio/dsh-quota-pill' in d.get('dsh', {}).get('profile', {}).get('bundles', []))
|
|
32
|
+
PY
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**Why pinned:** pnpm's `minimumReleaseAge` can resolve an unpinned install to the
|
|
36
|
+
broken 0.2.2 (its client half fails to import and the web GUI shows "Failed to
|
|
37
|
+
load plugins"). 0.2.11 is the first version with all providers, Open Design
|
|
38
|
+
Control Keys, sidecar auto-start, and user-defined adapters.
|
|
39
|
+
|
|
40
|
+
The host half starts the sidecar when nothing is listening on :8319, so a normal
|
|
41
|
+
desktop install does **not** need a separate `npx` or launchd unit.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
12
45
|
## 0. What this plugin does
|
|
13
46
|
|
|
14
47
|
It shows how much quota is **left** for the LLM subscriptions the user has, in a
|
|
@@ -33,7 +66,7 @@ There are **two credential families**, and this matters for every step:
|
|
|
33
66
|
| DSH Platform sign-in | `deepseek` | **nothing** — works if they are signed in to DeepSeek Platform in DSH |
|
|
34
67
|
| API key | `kimi`, `opencode`, `commandcode` | a key pasted into `~/.dsh/.credentials.yaml` |
|
|
35
68
|
| OAuth via CLIProxyAPI | `xai`, `codex`, `agy` | **CLIProxyAPI installed and logged in once** |
|
|
36
|
-
|
|
|
69
|
+
| Open Design Control Key | `opendesign` | a `vela_ctrl_…` Control Key from the Open Design device sign-in, plus the workspace id (optional); a `__Secure-better-auth.session_token` console cookie is a fallback |
|
|
37
70
|
|
|
38
71
|
**Check DeepSeek first.** It is the official account and needs no setup beyond a
|
|
39
72
|
DSH Platform sign-in, so it is usually the one the user actually wants. If it
|
|
@@ -44,6 +77,10 @@ DSH account menu, then re-run step 5.
|
|
|
44
77
|
|
|
45
78
|
## 1. Verify the sidecar runs
|
|
46
79
|
|
|
80
|
+
The 0.2.11 host plugin starts it automatically when :8319 is free. Give DSH a few
|
|
81
|
+
seconds, then check. If the host half could not start it (or you opted out with
|
|
82
|
+
`QUOTA_PILL_NO_AUTOSTART=1`), run it manually:
|
|
83
|
+
|
|
47
84
|
```bash
|
|
48
85
|
npx @hystudio/dsh-quota-pill
|
|
49
86
|
```
|
|
@@ -60,7 +97,7 @@ Then, in another shell:
|
|
|
60
97
|
|
|
61
98
|
```bash
|
|
62
99
|
curl -s http://127.0.0.1:8319/health
|
|
63
|
-
# {"ok":true,"port":8319,"providers":["kimi","opencode","commandcode","xai","codex","agy"]}
|
|
100
|
+
# {"ok":true,"port":8319,"providers":["deepseek","kimi","opencode","commandcode","xai","codex","agy","opendesign"]}
|
|
64
101
|
```
|
|
65
102
|
|
|
66
103
|
If `CLIProxyAPI: not set up`, the three OAuth providers will report "needs
|
|
@@ -68,36 +105,114 @@ CLIProxyAPI". That is expected on a fresh machine — tell the human that
|
|
|
68
105
|
`kimi` / `opencode` / `commandcode` work without it, and ask whether they want
|
|
69
106
|
the OAuth three as well.
|
|
70
107
|
|
|
71
|
-
**
|
|
72
|
-
|
|
108
|
+
**Auto-start is the default from 0.2.11**; do not add a service unless the human
|
|
109
|
+
asks for one or DSH is headless. If you do add one, set
|
|
110
|
+
`QUOTA_PILL_NO_AUTOSTART=1` so the plugin and the service do not race, and use
|
|
111
|
+
the **absolute** Node path (launchd's PATH does not include nvm/homebrew):
|
|
73
112
|
|
|
74
113
|
```bash
|
|
75
114
|
# macOS: ~/Library/LaunchAgents/dev.quota-pill.plist
|
|
76
|
-
# ProgramArguments: ["/
|
|
115
|
+
# ProgramArguments: ["/absolute/path/to/node","<abs path>/bin/quota-pill-server.js"]
|
|
77
116
|
# RunAtLoad: true
|
|
78
117
|
# KeepAlive: true
|
|
118
|
+
# EnvironmentVariables: { QUOTA_PILL_NO_AUTOSTART: "1" }
|
|
79
119
|
```
|
|
80
120
|
|
|
81
121
|
Ask before installing a service — that is a persistent change to their machine.
|
|
82
122
|
|
|
83
123
|
---
|
|
84
124
|
|
|
85
|
-
## 2.
|
|
125
|
+
## 2. Discover and configure automatically
|
|
126
|
+
|
|
127
|
+
**Do not ask the user which providers they have.** Enumerate DSH's catalog
|
|
128
|
+
yourself, build the worklist, and configure everything that is possible. The only
|
|
129
|
+
things to ask for are secrets/approvals that the agent cannot supply.
|
|
130
|
+
|
|
131
|
+
1. **DSH catalog + supported adapters + required refs:**
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
curl -s 'http://127.0.0.1:8319/setup?fresh=1' | python3 -m json.tool
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Read catalog (DSH's live session/modelCatalog), unadaptedCatalog,
|
|
138
|
+
userAdapters, and each entry in providers (inCatalog, requires, credential).
|
|
139
|
+
This is the authoritative "what can DSH route to" list.
|
|
140
|
+
|
|
141
|
+
2. **What is already readable:**
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
curl -s 'http://127.0.0.1:8319/quota/all?fresh=1' | python3 -m json.tool
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
3. **Credential files** (names only; never print values):
|
|
148
|
+
~/.dsh/.credentials.yaml refs, and ~/.cli-proxy-api/* token files.
|
|
149
|
+
|
|
150
|
+
Then act on the classification — do not present a menu:
|
|
151
|
+
|
|
152
|
+
| Situation | Agent action |
|
|
153
|
+
|---|---|
|
|
154
|
+
| Already readable | verify it, include the number in the report |
|
|
155
|
+
| Supported + in catalog, missing credential | configure it; if a secret is required, ask for that exact ref only |
|
|
156
|
+
| Unadapted catalog id / provider "unknown" | do not fake a reading; follow §2b and add it to the final report |
|
|
157
|
+
| In catalog but not supported and user does not want to adapt | say so; leave it out |
|
|
158
|
+
|
|
159
|
+
Only these need the human: paste a missing API key, install/login CLIProxyAPI,
|
|
160
|
+
or approve the Open Design device page. DeepSeek needs nothing. Do not ask
|
|
161
|
+
"which provider would you like me to set up?" — set up every available one.
|
|
162
|
+
|
|
163
|
+
If the user truly has no providers at all: stop and say the plugin has nothing to
|
|
164
|
+
show yet. Do not install a fake provider to "make it look working".
|
|
86
165
|
|
|
87
|
-
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## 2b. Adapting a provider the plugin does not know
|
|
169
|
+
|
|
170
|
+
When the Current page cannot read a model's provider, it shows a hint and a
|
|
171
|
+
"复制提示词 / Copy prompt" button. That button copies the full instruction below
|
|
172
|
+
into the clipboard; the human can paste it into a new session. As an agent, you
|
|
173
|
+
can also start from this section directly.
|
|
174
|
+
|
|
175
|
+
The adaptation is a **user adapter**, not a package edit:
|
|
176
|
+
|
|
177
|
+
- Directory: ~/.dsh/quota-pill/adapters (created by the sidecar)
|
|
178
|
+
- File: <provider>.mjs
|
|
179
|
+
- It is outside the npm package, so **plugin updates never overwrite it**
|
|
180
|
+
- The sidecar discovers it on the next request/Refresh; no restart
|
|
181
|
+
|
|
182
|
+
Steps:
|
|
183
|
+
|
|
184
|
+
1. curl /setup?fresh=1; find the unknown id in catalog / unadaptedCatalog, and
|
|
185
|
+
check existing files in ~/.dsh/quota-pill/adapters (do not overwrite a working
|
|
186
|
+
adapter for a different provider).
|
|
187
|
+
2. Find the provider's real quota API and credential. An inference key is not a
|
|
188
|
+
quota key; never infer a number from model prices or a guess.
|
|
189
|
+
3. Write ~/.dsh/quota-pill/adapters/<provider>.mjs:
|
|
190
|
+
|
|
191
|
+
```js
|
|
192
|
+
export const provider = 'acme';
|
|
193
|
+
export const catalogIds = ['acme-go']; // DSH catalog/model ids
|
|
194
|
+
export const displayName = 'Acme';
|
|
195
|
+
export const credential = { kind: 'api-key', refs: ['ACME_API_KEY'] };
|
|
196
|
+
export async function read(ctx) {
|
|
197
|
+
// ctx.model, ctx.readRef, ctx.readFirstRef, ctx.getJson,
|
|
198
|
+
// ctx.getJsonViaCurl, ctx.postJsonViaCurl, ctx.env, ctx.dshHome
|
|
199
|
+
const key = ctx.readFirstRef(['ACME_API_KEY']);
|
|
200
|
+
if (!key) return { kind: 'none', provider: 'acme', error: 'no ACME_API_KEY' };
|
|
201
|
+
const r = await ctx.getJson('https://api.acme.example/usage', { token: key });
|
|
202
|
+
if (!r.ok) return { kind: 'probe-failed', provider: 'acme', error: r.error };
|
|
203
|
+
return { kind: 'subscription-quota', provider: 'acme', remainingPct: 80, window: '5h' };
|
|
204
|
+
}
|
|
205
|
+
```
|
|
88
206
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
> - **xAI / Grok**, **Codex / ChatGPT Plus**, **Antigravity** — need CLIProxyAPI installed and logged in
|
|
96
|
-
>
|
|
97
|
-
> Tell me which ones, and paste the key for any of the first three.
|
|
207
|
+
Return the same kinds as built-ins: subscription-quota (with
|
|
208
|
+
remainingPct/windows), payg (with credits.remain), usage-only, none (missing
|
|
209
|
+
credential; explain in error) or probe-failed. Never invent a number.
|
|
210
|
+
4. Verify with curl 'http://127.0.0.1:8319/quota?model=<model>&fresh=1'; if it
|
|
211
|
+
fails, say exactly what is missing.
|
|
212
|
+
5. Run /quota/all?fresh=1 and /setup?fresh=1 and report adapted/unadapted.
|
|
98
213
|
|
|
99
|
-
|
|
100
|
-
Do not
|
|
214
|
+
The user adapter contract is also documented in src/user-adapters.js; read it
|
|
215
|
+
before writing the file. Do not put user adapters inside the installed package.
|
|
101
216
|
|
|
102
217
|
---
|
|
103
218
|
|
|
@@ -110,7 +225,7 @@ it usually already exists and contains other credentials that must not be lost.
|
|
|
110
225
|
refs:
|
|
111
226
|
KIMI_CODING_API_KEY: sk-...
|
|
112
227
|
OPENCODE_GO_API_KEY: sk-...
|
|
113
|
-
COMMANDCODE_API_KEY: sk-...
|
|
228
|
+
COMMANDCODE_API_KEY: sk-... # or reuse COMMANDCODE_GOAT_API_KEY
|
|
114
229
|
```
|
|
115
230
|
|
|
116
231
|
Rules:
|
|
@@ -128,7 +243,7 @@ python3 - <<'PY'
|
|
|
128
243
|
import yaml, os
|
|
129
244
|
d = yaml.safe_load(open(os.path.expanduser('~/.dsh/.credentials.yaml')))
|
|
130
245
|
refs = d.get('refs') or {}
|
|
131
|
-
for k in ('KIMI_CODING_API_KEY','OPENCODE_GO_API_KEY','COMMANDCODE_API_KEY'):
|
|
246
|
+
for k in ('KIMI_CODING_API_KEY','OPENCODE_GO_API_KEY','COMMANDCODE_API_KEY','COMMANDCODE_GOAT_API_KEY'):
|
|
132
247
|
print(f'{k}: ' + ('present' if refs.get(k) else 'MISSING'))
|
|
133
248
|
PY
|
|
134
249
|
```
|
|
@@ -163,6 +278,52 @@ the OAuth flow itself.
|
|
|
163
278
|
reports the `X-Codex-*` headers CLIProxyAPI observed. Without it running, the
|
|
164
279
|
fallback hits `chatgpt.com`, which blocks non-browser TLS and returns 403.
|
|
165
280
|
|
|
281
|
+
### Open Design (optional)
|
|
282
|
+
|
|
283
|
+
Open Design quota uses a **Control Key** (`vela_ctrl_…`), not the `sk-amr-…`
|
|
284
|
+
inference key. Run the bundled device sign-in and approve its URL in the user's
|
|
285
|
+
browser:
|
|
286
|
+
|
|
287
|
+
```bash
|
|
288
|
+
node bin/opendesign-login.js --write
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
That writes `OPENDESIGN_CONTROL_KEY` and `OPENDESIGN_WORKSPACE_ID` into
|
|
292
|
+
`~/.dsh/.credentials.yaml`. A console session cookie
|
|
293
|
+
(`OPENDESIGN_SESSION_COOKIE`) is still accepted as a fallback. If the user only
|
|
294
|
+
has an inference key, skip Open Design — an inference key returns 401 on the
|
|
295
|
+
control plane.
|
|
296
|
+
|
|
297
|
+
---
|
|
298
|
+
|
|
299
|
+
## 4b. Adding or removing a provider later
|
|
300
|
+
|
|
301
|
+
No restart/reinstall is needed for supported providers:
|
|
302
|
+
|
|
303
|
+
| Change | When it appears |
|
|
304
|
+
|---|---|
|
|
305
|
+
| New credential for a supported provider already in the DSH catalog | next `/quota/all` (All refresh); no sidecar restart |
|
|
306
|
+
| New provider group added to DSH's catalog | within the 2-minute catalog TTL, or immediately with `/quota/all?fresh=1` / **Refresh** / reopening All |
|
|
307
|
+
| Provider removed from DSH | same: next refresh removes it |
|
|
308
|
+
| Brand-new provider **type** (not one of the eight adapters) | needs a user adapter in ~/.dsh/quota-pill/adapters (see §2b); the catalog alone is not enough |
|
|
309
|
+
|
|
310
|
+
The All page reads fresh on open, on **Refresh**, and when DSH regains focus; it
|
|
311
|
+
is not a background poll. /setup?fresh=1 is the quickest way for an agent to see
|
|
312
|
+
the current catalog, `unadaptedCatalog`, and which supported provider it has no
|
|
313
|
+
credential for.
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
## 4c. After a plugin update
|
|
318
|
+
|
|
319
|
+
1. **Do not delete** ~/.dsh/quota-pill/adapters; it is outside the package and
|
|
320
|
+
is the user's adapted providers. An update must never overwrite it.
|
|
321
|
+
2. Restart DSH/sidecar so the new code loads (the host half starts the sidecar).
|
|
322
|
+
3. Run /setup?fresh=1 and compare catalog with the adapted/user providers.
|
|
323
|
+
4. For every id still in unadaptedCatalog, run §2b (add or update the user
|
|
324
|
+
adapter). Do not remove a working adapter just because the package changed.
|
|
325
|
+
5. Run /quota/all?fresh=1 and report the full provider list again.
|
|
326
|
+
|
|
166
327
|
---
|
|
167
328
|
|
|
168
329
|
## 5. Verify end to end
|
|
@@ -195,6 +356,13 @@ plainly:
|
|
|
195
356
|
|
|
196
357
|
- which providers are working
|
|
197
358
|
- which are skipped, and the single action that would enable each
|
|
359
|
+
- the DSH catalog and which ids are still in `unadaptedCatalog` (if any)
|
|
360
|
+
|
|
361
|
+
**Initial-install summary must include the unknown-provider path:** if
|
|
362
|
+
`unadaptedCatalog` is non-empty (or a current model's provider is unknown), tell
|
|
363
|
+
the user: "这些 provider 还没适配;打开(或重开)当前会话的额度面板,Current 页有
|
|
364
|
+
「复制提示词」按钮,复制到新会话让 agent 按 §2b 适配即可。" The button copies the
|
|
365
|
+
full adapter flow, so the user does not need to explain anything.
|
|
198
366
|
|
|
199
367
|
Do not paste raw JSON. Do not claim a provider works if it returned `none` or
|
|
200
368
|
`probe-failed`.
|
package/README.md
CHANGED
|
@@ -18,11 +18,11 @@ into the live number.
|
|
|
18
18
|
| **DeepSeek** (official) | the Platform sign-in DSH already holds — nothing to add |
|
|
19
19
|
| **Kimi Coding** | `refs.KIMI_CODING_API_KEY` in `~/.dsh/.credentials.yaml` |
|
|
20
20
|
| **OpenCode Go** | `refs.OPENCODE_GO_API_KEY` |
|
|
21
|
-
| **Command Code** | `refs.COMMANDCODE_API_KEY` |
|
|
21
|
+
| **Command Code** | `refs.COMMANDCODE_API_KEY` or `refs.COMMANDCODE_GOAT_API_KEY` (DSH names it after its `apiKeyEnv`) |
|
|
22
22
|
| **xAI (Grok)** | OAuth token from [CLIProxyAPI](https://github.com/router-for-me/CLIProxyAPI) |
|
|
23
23
|
| **Codex (ChatGPT Plus)** | same — and CLIProxyAPI must be **running** |
|
|
24
24
|
| **Antigravity** | same |
|
|
25
|
-
| **Open Design** (optional) | `refs.
|
|
25
|
+
| **Open Design** (optional) | `refs.OPENDESIGN_CONTROL_KEY` (`vela_ctrl_…`) + `refs.OPENDESIGN_WORKSPACE_ID`; a console session cookie (`OPENDESIGN_SESSION_COOKIE`) also works as a fallback |
|
|
26
26
|
|
|
27
27
|
**DeepSeek works with no setup at all** if you are signed in to DeepSeek Platform
|
|
28
28
|
in DSH — that is the official account, and it is the one most users care about.
|
|
@@ -36,22 +36,72 @@ the pill reads DSH's `session/modelCatalog` to learn which providers the app can
|
|
|
36
36
|
route to, so removing a provider from DSH removes it from the pill too. A provider
|
|
37
37
|
that is present but has no credential is skipped rather than shown as an empty row.
|
|
38
38
|
|
|
39
|
+
### What "auto-detect" means
|
|
40
|
+
|
|
41
|
+
- **A new key for a supported provider** (e.g. adding `OPENCODE_GO_API_KEY`)
|
|
42
|
+
appears on the next All refresh — no DSH restart, no sidecar restart.
|
|
43
|
+
- **A provider newly added to DSH's catalog** appears within the 2-minute catalog
|
|
44
|
+
TTL, or immediately when you press **Refresh** / reopen the All page (a forced
|
|
45
|
+
read drops the catalog cache). The All page also re-reads when DSH regains focus.
|
|
46
|
+
- **A brand-new provider type** that is not one of the eight adapters needs a
|
|
47
|
+
**user adapter** in `~/.dsh/quota-pill/adapters/` (see below); a catalog entry
|
|
48
|
+
alone cannot invent its quota API.
|
|
49
|
+
|
|
50
|
+
### Unknown providers and user adapters
|
|
51
|
+
|
|
52
|
+
If the Current page cannot read the session's provider, it shows a short hint and
|
|
53
|
+
a **复制提示词 / Copy prompt** button. The copied prompt contains the whole
|
|
54
|
+
adaptation flow; paste it into a new session and an agent can add the provider
|
|
55
|
+
without touching package files.
|
|
56
|
+
|
|
57
|
+
User adapters live in:
|
|
58
|
+
|
|
59
|
+
```text
|
|
60
|
+
~/.dsh/quota-pill/adapters/<provider>.mjs
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
They are outside the npm package, so **updating the plugin never overwrites
|
|
64
|
+
them**. The sidecar discovers a new/changed file on the next Refresh (no restart).
|
|
65
|
+
`/setup?fresh=1` reports `unadaptedCatalog` and `userAdapters`, and after a
|
|
66
|
+
plugin update the same check tells the agent which providers still need an
|
|
67
|
+
adapter. The module contract (exports, `read(ctx)`, standard kinds) is documented
|
|
68
|
+
in [`src/user-adapters.js`](src/user-adapters.js) and in AGENTS.md §2b.
|
|
69
|
+
|
|
39
70
|
## Install
|
|
40
71
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
72
|
+
**DSH Desktop (recommended).** Open **Plugins → Install**, paste a spec, enable
|
|
73
|
+
the bundle. **Pin the version**: an unpinned install can resolve 0.2.2, which
|
|
74
|
+
breaks web boot. 0.2.11 is the first version with every provider, Open Design
|
|
75
|
+
Control Keys, sidecar auto-start, and **user-defined adapters** for providers the
|
|
76
|
+
package does not ship.
|
|
44
77
|
|
|
45
|
-
|
|
46
|
-
|
|
78
|
+
```text
|
|
79
|
+
@hystudio/dsh-quota-pill@0.2.11
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Until 0.2.11 is on npm, install the branch:
|
|
83
|
+
|
|
84
|
+
```text
|
|
85
|
+
github:598934321/dsh-quota-pill#feat/user-adapters-0.2.11
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**CLI.** If `dsh` is not on PATH, open DSH Desktop → Settings → **Manage dsh
|
|
89
|
+
Command** first. Then:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
dsh plugin add @hystudio/dsh-quota-pill@0.2.11
|
|
47
93
|
```
|
|
48
94
|
|
|
49
95
|
> The npm package is `@hystudio/...` while the repo lives at `598934321/...` —
|
|
50
96
|
> they are the same author under two account names.
|
|
51
97
|
|
|
52
|
-
The
|
|
53
|
-
|
|
98
|
+
**The sidecar now starts automatically with the plugin** (the host half spawns it
|
|
99
|
+
when nothing is listening on :8319), so there is no second command for a normal
|
|
100
|
+
desktop install. If you prefer to manage it yourself, run
|
|
101
|
+
`npx @hystudio/dsh-quota-pill` or a service unit — see
|
|
54
102
|
[Running it as a service](#running-it-as-a-service).
|
|
103
|
+
The pill says **"quota service is not running"** only if :8319 is still closed a
|
|
104
|
+
few seconds after DSH starts.
|
|
55
105
|
|
|
56
106
|
Then add whichever keys you have:
|
|
57
107
|
|
|
@@ -78,6 +128,10 @@ Read AGENTS.md in this repo and set up dsh-quota-pill for me.
|
|
|
78
128
|
|
|
79
129
|
## Running it as a service
|
|
80
130
|
|
|
131
|
+
The host plugin already starts the sidecar; this section is only for users who
|
|
132
|
+
want it independent of DSH (server, headless, multiple app profiles). Set
|
|
133
|
+
`QUOTA_PILL_NO_AUTOSTART=1` so the plugin does not race the service.
|
|
134
|
+
|
|
81
135
|
`npx` in a terminal dies with the terminal. Pick one:
|
|
82
136
|
|
|
83
137
|
**macOS (launchd)** — `~/Library/LaunchAgents/dev.quota-pill.plist`
|
|
@@ -193,13 +247,27 @@ would flicker between models you do not even have configured.
|
|
|
193
247
|
`monthlyCap = weekly.cap × 2`, an identity verified against two different plans
|
|
194
248
|
(31.6 + 38.4 = 70 with `weekly.cap` 35; 0.225 + 9.775 = 10 with `weekly.cap` 6).
|
|
195
249
|
|
|
196
|
-
**Open Design** keeps its allowance in the cloud
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
`
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
250
|
+
**Open Design** keeps its allowance in the cloud control plane, not in the
|
|
251
|
+
`sk-amr-…` inference key. The preferred credential is a **Control Key**
|
|
252
|
+
(`vela_ctrl_…`), which is accepted by the Bearer-only
|
|
253
|
+
`/billing/preflight` and `/wallet/balance` endpoints alongside
|
|
254
|
+
`x-vela-workspace-id`. Get one with the bundled device sign-in (the same flow
|
|
255
|
+
as the official `vela login`):
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
npx -p @hystudio/dsh-quota-pill dsh-quota-pill-opendesign-login --write
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
It prints an activation URL, waits for you to approve it in a signed-in browser,
|
|
262
|
+
then writes `OPENDESIGN_CONTROL_KEY` and `OPENDESIGN_WORKSPACE_ID` into
|
|
263
|
+
`~/.dsh/.credentials.yaml`. The workspace id is optional when using a Control
|
|
264
|
+
Key — it is otherwise resolved from `/workspaces/current`.
|
|
265
|
+
|
|
266
|
+
The older console-session route (`__Secure-better-auth.session_token` cookie,
|
|
267
|
+
browser-session-only `/billing/coding-plan-usage`) is still supported as a
|
|
268
|
+
fallback; that cookie is HttpOnly, so copy it from devtools → Application →
|
|
269
|
+
Cookies. Both routes go through system curl because Cloudflare rejects Node's
|
|
270
|
+
TLS fingerprint on `amr-api` with `error code: 1010`.
|
|
203
271
|
|
|
204
272
|
**DeepSeek** is a prepaid CNY wallet, so there is no percentage to show — the pill
|
|
205
273
|
shows the balance and, separately, any bonus credit. Two traps worth recording:
|
|
@@ -221,7 +289,7 @@ down to wallet currency and balance. We never handle the Platform token ourselve
|
|
|
221
289
|
```bash
|
|
222
290
|
git clone https://github.com/598934321/dsh-quota-pill
|
|
223
291
|
cd dsh-quota-pill
|
|
224
|
-
npm test #
|
|
292
|
+
npm test # 70 tests, offline
|
|
225
293
|
npm start # sidecar on :8319
|
|
226
294
|
```
|
|
227
295
|
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Sign in to Open Design and write the Control Key the quota pill needs.
|
|
4
|
+
*
|
|
5
|
+
* node bin/opendesign-login.js # print the values and a YAML snippet
|
|
6
|
+
* node bin/opendesign-login.js --write # add/update the two refs in ~/.dsh/.credentials.yaml
|
|
7
|
+
*
|
|
8
|
+
* The browser approval page is the same one the Open Design CLI (`vela login`)
|
|
9
|
+
* and magpie use. Only the Control Key is stored by `--write`; the runtime key
|
|
10
|
+
* is not needed by the quota pill and is not written.
|
|
11
|
+
*/
|
|
12
|
+
import fs from 'node:fs';
|
|
13
|
+
import os from 'node:os';
|
|
14
|
+
import path from 'node:path';
|
|
15
|
+
import { loginOpenDesign } from '../src/opendesign-auth.js';
|
|
16
|
+
|
|
17
|
+
const write = process.argv.includes('--write');
|
|
18
|
+
const credentialsFile = path.join(
|
|
19
|
+
process.env.DSH_HOME || path.join(os.homedir(), '.dsh'),
|
|
20
|
+
'.credentials.yaml',
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
function setRef(text, key, value) {
|
|
24
|
+
const lines = String(text || '').split('\n');
|
|
25
|
+
const lineRe = new RegExp('^(\\s*)' + key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + ':');
|
|
26
|
+
const existing = lines.findIndex((line) => lineRe.test(line));
|
|
27
|
+
if (existing >= 0) {
|
|
28
|
+
lines[existing] = lines[existing].replace(/^(\s*).*$/, '$1' + key + ': ' + value);
|
|
29
|
+
return lines.join('\n');
|
|
30
|
+
}
|
|
31
|
+
const refsAt = lines.findIndex((line) => /^refs:\s*$/.test(line));
|
|
32
|
+
if (refsAt >= 0) {
|
|
33
|
+
lines.splice(refsAt + 1, 0, ' ' + key + ': ' + value);
|
|
34
|
+
return lines.join('\n');
|
|
35
|
+
}
|
|
36
|
+
const base = text && !text.endsWith('\n') ? text + '\n' : String(text || '');
|
|
37
|
+
return base + 'refs:\n ' + key + ': ' + value + '\n';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function mask(value) {
|
|
41
|
+
if (value.length <= 12) return value;
|
|
42
|
+
return value.slice(0, 10) + '…' + value.slice(-4);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const result = await loginOpenDesign({
|
|
46
|
+
onDevice(device) {
|
|
47
|
+
console.log('Open Design sign-in');
|
|
48
|
+
console.log(' 1. Open: ' + (device.activationUrl || '(activation URL missing)'));
|
|
49
|
+
console.log(' 2. Confirm code: ' + device.userCode);
|
|
50
|
+
console.log(' 3. Approve the request in the browser.');
|
|
51
|
+
console.log('Waiting for approval…');
|
|
52
|
+
},
|
|
53
|
+
onPending() {},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
console.log('');
|
|
57
|
+
console.log('Signed in' + (result.email ? ' as ' + result.email : '') + '.');
|
|
58
|
+
console.log('Control Key: ' + (write ? mask(result.controlKey) : result.controlKey));
|
|
59
|
+
console.log('Workspace ID: ' + result.workspaceId);
|
|
60
|
+
|
|
61
|
+
if (!write) {
|
|
62
|
+
console.log('');
|
|
63
|
+
console.log('Add these to ' + credentialsFile + ' (keep the others already there):');
|
|
64
|
+
console.log('refs:');
|
|
65
|
+
console.log(' OPENDESIGN_CONTROL_KEY: ' + result.controlKey);
|
|
66
|
+
console.log(' OPENDESIGN_WORKSPACE_ID: ' + result.workspaceId);
|
|
67
|
+
} else {
|
|
68
|
+
let text = '';
|
|
69
|
+
try { text = fs.readFileSync(credentialsFile, 'utf8'); } catch { /* new file */ }
|
|
70
|
+
text = setRef(text, 'OPENDESIGN_CONTROL_KEY', result.controlKey);
|
|
71
|
+
text = setRef(text, 'OPENDESIGN_WORKSPACE_ID', result.workspaceId);
|
|
72
|
+
fs.mkdirSync(path.dirname(credentialsFile), { recursive: true });
|
|
73
|
+
fs.writeFileSync(credentialsFile, text, { mode: 0o600 });
|
|
74
|
+
try { fs.chmodSync(credentialsFile, 0o600); } catch { /* best effort */ }
|
|
75
|
+
console.log('');
|
|
76
|
+
console.log('Written to ' + credentialsFile);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
console.log('');
|
|
80
|
+
console.log('Restart the quota-pill sidecar (or launch it again) to pick up the new refs.');
|