@hystudio/dsh-quota-pill 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md ADDED
@@ -0,0 +1,217 @@
1
+ # Agent setup guide — dsh-quota-pill
2
+
3
+ This file is for **AI agents** helping a human install and configure the plugin.
4
+ Follow it top to bottom. It is written so that you can run the whole flow, asking
5
+ the human only for things only they can supply (an API key, an OAuth login).
6
+
7
+ > **Rule for the whole flow: never invent a quota number.** If a provider cannot
8
+ > be read, say so and explain what is missing. A wrong number is worse than "—".
9
+
10
+ ---
11
+
12
+ ## 0. What this plugin does
13
+
14
+ It shows how much quota is **left** for the LLM subscriptions the user has, in a
15
+ small pill in the DeepSeek Harness web GUI.
16
+
17
+ Architecture — two pieces, both required:
18
+
19
+ ```
20
+ DSH web GUI ──► client.js (the pill; renders JSON)
21
+ │
22
+ ▼
23
+ sidecar (Node process; reads credentials + upstream APIs)
24
+ ```
25
+
26
+ The pill shows nothing until the sidecar runs. That is by design: credentials
27
+ must never reach the browser.
28
+
29
+ There are **two credential families**, and this matters for every step:
30
+
31
+ | Family | Providers | What the user needs |
32
+ |---|---|---|
33
+ | API key | `kimi`, `opencode`, `commandcode` | a key pasted into `~/.dsh/.credentials.yaml` |
34
+ | OAuth via CLIProxyAPI | `xai`, `codex`, `agy` | **CLIProxyAPI installed and logged in once** |
35
+
36
+ ---
37
+
38
+ ## 1. Verify the sidecar runs
39
+
40
+ ```bash
41
+ npx @hystudio/dsh-quota-pill
42
+ ```
43
+
44
+ Expected first lines:
45
+
46
+ ```
47
+ [quota-pill] listening on http://127.0.0.1:8319
48
+ [quota-pill] credentials: /Users/<user>/.dsh/.credentials.yaml
49
+ [quota-pill] CLIProxyAPI: /Users/<user>/.cli-proxy-api ← or "not set up"
50
+ ```
51
+
52
+ Then, in another shell:
53
+
54
+ ```bash
55
+ curl -s http://127.0.0.1:8319/health
56
+ # {"ok":true,"port":8319,"providers":["kimi","opencode","commandcode","xai","codex","agy"]}
57
+ ```
58
+
59
+ If `CLIProxyAPI: not set up`, the three OAuth providers will report "needs
60
+ CLIProxyAPI". That is expected on a fresh machine — tell the human that
61
+ `kimi` / `opencode` / `commandcode` work without it, and ask whether they want
62
+ the OAuth three as well.
63
+
64
+ **Keep it running.** Do not leave it in the foreground of a terminal the human
65
+ will close. Offer a background service (launchd on macOS, systemd on Linux):
66
+
67
+ ```bash
68
+ # macOS: ~/Library/LaunchAgents/dev.quota-pill.plist
69
+ # ProgramArguments: ["/usr/bin/env","node","<abs path>/bin/quota-pill-server.js"]
70
+ # RunAtLoad: true
71
+ # KeepAlive: true
72
+ ```
73
+
74
+ Ask before installing a service — that is a persistent change to their machine.
75
+
76
+ ---
77
+
78
+ ## 2. Ask which providers the user actually has
79
+
80
+ Do **not** guess. Ask, in one message, listing the options with what each needs:
81
+
82
+ > I can read quota for six providers. Which do you have?
83
+ >
84
+ > - **Kimi Coding** — needs a `KIMI_CODING_API_KEY`
85
+ > - **OpenCode Go** — needs an `OPENCODE_GO_API_KEY`
86
+ > - **Command Code** — needs a `COMMANDCODE_API_KEY`
87
+ > - **xAI / Grok**, **Codex / ChatGPT Plus**, **Antigravity** — need CLIProxyAPI installed and logged in
88
+ >
89
+ > Tell me which ones, and paste the key for any of the first three.
90
+
91
+ If they have none of them: stop and say the plugin has nothing to show yet.
92
+ Do not install a fake provider to "make it look working".
93
+
94
+ ---
95
+
96
+ ## 3. Write the API keys
97
+
98
+ Keys live in `~/.dsh/.credentials.yaml` under `refs`. **Read the file first** —
99
+ it usually already exists and contains other credentials that must not be lost.
100
+
101
+ ```yaml
102
+ refs:
103
+ KIMI_CODING_API_KEY: sk-...
104
+ OPENCODE_GO_API_KEY: sk-...
105
+ COMMANDCODE_API_KEY: sk-...
106
+ ```
107
+
108
+ Rules:
109
+
110
+ - **Append or edit one line at a time.** Never rewrite the whole file from a
111
+ template — that would delete the user's other credentials.
112
+ - **Never echo a key back** into the chat, a log, or a commit. After writing,
113
+ verify only that the key is *present*, not what it is.
114
+ - If the file does not exist, create it with `chmod 600`.
115
+
116
+ Verify each one without printing it:
117
+
118
+ ```bash
119
+ python3 - <<'PY'
120
+ import yaml, os
121
+ d = yaml.safe_load(open(os.path.expanduser('~/.dsh/.credentials.yaml')))
122
+ refs = d.get('refs') or {}
123
+ for k in ('KIMI_CODING_API_KEY','OPENCODE_GO_API_KEY','COMMANDCODE_API_KEY'):
124
+ print(f'{k}: ' + ('present' if refs.get(k) else 'MISSING'))
125
+ PY
126
+ ```
127
+
128
+ ---
129
+
130
+ ## 4. Configure the OAuth three (only if asked for)
131
+
132
+ These read token files that **CLIProxyAPI** writes. This plugin never performs
133
+ the OAuth flow itself.
134
+
135
+ 1. Check whether CLIProxyAPI is already there:
136
+
137
+ ```bash
138
+ ls ~/.cli-proxy-api/ 2>/dev/null
139
+ ```
140
+
141
+ 2. If the directory is missing, tell the human:
142
+
143
+ > xAI / Codex / Antigravity need CLIProxyAPI installed and logged in once.
144
+ > It writes tokens to `~/.cli-proxy-api/`, which is what I read.
145
+ > Install it, log into those services, then tell me — or skip them for now.
146
+
147
+ 3. Once logged in, confirm the files appeared:
148
+
149
+ ```bash
150
+ ls ~/.cli-proxy-api/ | grep -E '^(xai|codex|antigravity)-'
151
+ ```
152
+
153
+ 4. **Codex needs CLIProxyAPI to be *running***, not just installed. Codex quota
154
+ is read from CLIProxyAPI's management API (`/v0/management/auth-files`), which
155
+ reports the `X-Codex-*` headers CLIProxyAPI observed. Without it running, the
156
+ fallback hits `chatgpt.com`, which blocks non-browser TLS and returns 403.
157
+
158
+ ---
159
+
160
+ ## 5. Verify end to end
161
+
162
+ ```bash
163
+ curl -s 'http://127.0.0.1:8319/quota/all?fresh=1' | python3 -m json.tool | head -40
164
+ ```
165
+
166
+ For each provider look at `kind`:
167
+
168
+ | `kind` | Meaning |
169
+ |---|---|
170
+ | `subscription-quota` | working — `remainingPct` and `windows` are populated |
171
+ | `none` | not configured — the `error` field says exactly what to add |
172
+ | `probe-failed` | configured but the upstream call failed — see `error` |
173
+ | `payg` | metered billing with no ceiling; no percentage exists |
174
+
175
+ Cross-check one provider against its official dashboard if the human is unsure.
176
+ **The plugin reports remaining quota, not used.**
177
+
178
+ If a provider came back `none` with `no <REF>`, the key did not get written —
179
+ go back to step 3. Do not report success while any asked-for provider is `none`.
180
+
181
+ ---
182
+
183
+ ## 6. Report back
184
+
185
+ Tell the human, per provider: how much is left and when it resets. Then state
186
+ plainly:
187
+
188
+ - which providers are working
189
+ - which are skipped, and the single action that would enable each
190
+
191
+ Do not paste raw JSON. Do not claim a provider works if it returned `none` or
192
+ `probe-failed`.
193
+
194
+ ---
195
+
196
+ ## Troubleshooting
197
+
198
+ **Pill shows "quota service is not running"**
199
+ The sidecar is not up, or is on another port. Start it; if it must live on a
200
+ different port, set `window.__QUOTA_PILL_PORT__` or run with `QUOTA_PILL_PORT=…`
201
+ and set the same on the client side.
202
+
203
+ **`probe-failed` with http 403 / ECONNRESET**
204
+ Some upstreams (`chatgpt.com`, `googleapis`, `grok.com`) need either CLIProxyAPI
205
+ or a local proxy. Codex additionally needs CLIProxyAPI *running* for its
206
+ observed-headers route.
207
+
208
+ **`none` with "no … in ~/.dsh/.credentials.yaml"**
209
+ The key is missing. Steps above show the exact `refs.` name to add.
210
+
211
+ **Everything is `none` even though keys exist**
212
+ Check the sidecar's printed `credentials:` path — it may be reading a different
213
+ `DSH_HOME` than the one the user edited.
214
+
215
+ **Numbers look stale**
216
+ The sidecar caches for 60s. `?fresh=1` forces an upstream read. The pill does
217
+ this automatically when the session context changes.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 598934321
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,208 @@
1
- # Temporary Holding Version
1
+ # dsh-quota-pill
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ A quota pill for the [DeepSeek Harness](https://github.com/deepseek-ai) web GUI:
4
+ **how much is left** on every LLM subscription you have, in one collapsible pill
5
+ next to the composer.
6
+
7
+ ![quota pill](docs/screenshot.png)
8
+
9
+ It sits in the composer toolbar, shows the tightest window at a glance, and opens
10
+ into a per-provider breakdown with reset times. Switch sessions and it follows
11
+ the model — showing that provider's last reading while it refreshes, then easing
12
+ into the live number.
13
+
14
+ ## What it reads
15
+
16
+ | Provider | Credential |
17
+ |---|---|
18
+ | **Kimi Coding** | `refs.KIMI_CODING_API_KEY` in `~/.dsh/.credentials.yaml` |
19
+ | **OpenCode Go** | `refs.OPENCODE_GO_API_KEY` |
20
+ | **Command Code** | `refs.COMMANDCODE_API_KEY` |
21
+ | **xAI (Grok)** | OAuth token from [CLIProxyAPI](https://github.com/router-for-me/CLIProxyAPI) |
22
+ | **Codex (ChatGPT Plus)** | same — and CLIProxyAPI must be **running** |
23
+ | **Antigravity** | same |
24
+
25
+ The three API-key providers need nothing but the key. The three OAuth providers
26
+ need CLIProxyAPI installed and logged in, because that is what holds the tokens;
27
+ this plugin only reads them and never runs an OAuth flow itself.
28
+
29
+ Every provider is optional. Unconfigured ones say exactly what is missing instead
30
+ of showing a fake zero.
31
+
32
+ ## Install
33
+
34
+ ```bash
35
+ # 1. the DSH plugin
36
+ dsh plugin add @hystudio/dsh-quota-pill
37
+
38
+ # 2. the sidecar (reads credentials, talks to upstream APIs)
39
+ npx @hystudio/dsh-quota-pill
40
+ ```
41
+
42
+ The pill shows **"quota service is not running"** with that exact command until
43
+ the sidecar is up. Keep it running in the background — see
44
+ [Running it as a service](#running-it-as-a-service).
45
+
46
+ Then add whichever keys you have:
47
+
48
+ ```yaml
49
+ # ~/.dsh/.credentials.yaml
50
+ refs:
51
+ KIMI_CODING_API_KEY: sk-...
52
+ OPENCODE_GO_API_KEY: sk-...
53
+ COMMANDCODE_API_KEY: sk-...
54
+ ```
55
+
56
+ Reload the DSH window. Done.
57
+
58
+ ### Let an agent do it
59
+
60
+ The repo ships [`AGENTS.md`](AGENTS.md) — a step-by-step guide written for an AI
61
+ agent. Ask your agent to set the plugin up and it will detect what you already
62
+ have, ask only for what it cannot know (your keys), write them safely, and verify
63
+ each provider end to end.
64
+
65
+ ```text
66
+ Read AGENTS.md in this repo and set up dsh-quota-pill for me.
67
+ ```
68
+
69
+ ## Running it as a service
70
+
71
+ `npx` in a terminal dies with the terminal. Pick one:
72
+
73
+ **macOS (launchd)** — `~/Library/LaunchAgents/dev.quota-pill.plist`
74
+
75
+ ```xml
76
+ <?xml version="1.0" encoding="UTF-8"?>
77
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
78
+ "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
79
+ <plist version="1.0"><dict>
80
+ <key>Label</key><string>dev.quota-pill</string>
81
+ <key>ProgramArguments</key>
82
+ <array>
83
+ <string>/usr/bin/env</string><string>node</string>
84
+ <string>/absolute/path/to/bin/quota-pill-server.js</string>
85
+ </array>
86
+ <key>RunAtLoad</key><true/>
87
+ <key>KeepAlive</key><true/>
88
+ </dict></plist>
89
+ ```
90
+
91
+ ```bash
92
+ launchctl load ~/Library/LaunchAgents/dev.quota-pill.plist
93
+ ```
94
+
95
+ **Linux (systemd)** — `~/.config/systemd/user/quota-pill.service`
96
+
97
+ ```ini
98
+ [Unit]
99
+ Description=dsh-quota-pill sidecar
100
+
101
+ [Service]
102
+ ExecStart=/usr/bin/env node /absolute/path/to/bin/quota-pill-server.js
103
+ Restart=always
104
+
105
+ [Install]
106
+ WantedBy=default.target
107
+ ```
108
+
109
+ ```bash
110
+ systemctl --user enable --now quota-pill
111
+ ```
112
+
113
+ ## How it works
114
+
115
+ ```
116
+ DSH web GUI
117
+ └─ client.js renders the pill; never sees a credential
118
+ │ HTTP (localhost)
119
+ ▼
120
+ sidecar reads ~/.dsh/.credentials.yaml and ~/.cli-proxy-api/*.json,
121
+ calls each provider's quota endpoint, caches 60s
122
+ ```
123
+
124
+ Two processes because credentials must never reach the browser, and because
125
+ upstream calls need real timeouts and a TLS stack Node's `fetch` does not have
126
+ for some hosts.
127
+
128
+ ### Endpoints
129
+
130
+ | Path | Purpose |
131
+ |---|---|
132
+ | `GET /health` | liveness + provider list |
133
+ | `GET /quota?model=<id>` | one provider's reading, selected by model prefix |
134
+ | `GET /quota/all` | every provider, concurrently |
135
+ | `GET /setup` | what each provider still needs |
136
+
137
+ `?fresh=1` bypasses the 60-second cache.
138
+
139
+ ### Response
140
+
141
+ ```jsonc
142
+ {
143
+ "kind": "subscription-quota", // subscription-quota | payg | none | probe-failed
144
+ "provider": "opencode",
145
+ "displayName": "OpenCode Go",
146
+ "remainingPct": 17, // remaining, never used
147
+ "window": "monthly", // the binding window
148
+ "resetAt": "2026-11-12T00:00:00.000Z",
149
+ "products": { "rolling": 9, "weekly": 67, "monthly": 83 }, // per-window used%
150
+ "windows": [ // one row each in the UI
151
+ { "key": "monthly", "remainPct": 17, "resetAt": "..." }
152
+ ],
153
+ "cached": false,
154
+ "at": 1792500000000
155
+ }
156
+ ```
157
+
158
+ `kind` is meant to be exhaustive so the UI never has to guess:
159
+
160
+ - `subscription-quota` — a real percentage exists
161
+ - `payg` — metered billing, no ceiling (shows spend, not a percentage)
162
+ - `none` — not configured; `error` names the missing credential
163
+ - `probe-failed` — configured but the upstream call failed
164
+
165
+ ## Notes on specific providers
166
+
167
+ **Codex** is read through CLIProxyAPI's management API, which reports the
168
+ `X-Codex-*` headers CLIProxyAPI observed while proxying real traffic. The direct
169
+ `chatgpt.com/backend-api/codex/usage` endpoint sits behind Cloudflare and rejects
170
+ non-browser TLS fingerprints, so it only works as a fallback. CLIProxyAPI must be
171
+ *running*, not merely installed.
172
+
173
+ **xAI** has three account generations: grandfathered plans report a percentage
174
+ directly, current plans only reveal allowance in `x-ratelimit-*` response headers
175
+ of a one-token completion, and unified-billing accounts have no ceiling at all
176
+ (reported as `payg`).
177
+
178
+ **Antigravity** shares one quota pool across all Gemini models, so it reports a
179
+ single aggregate row — per-model rows would all be the same number and the label
180
+ would flicker between models you do not even have configured.
181
+
182
+ **Command Code** does not expose a monthly ceiling. The adapter derives it from
183
+ `monthlyCap = weekly.cap × 2`, an identity verified against two different plans
184
+ (31.6 + 38.4 = 70 with `weekly.cap` 35; 0.225 + 9.775 = 10 with `weekly.cap` 6).
185
+
186
+ ## Privacy
187
+
188
+ - Credentials are read from disk, used for the upstream call, and never logged.
189
+ - The sidecar binds `127.0.0.1` only.
190
+ - Nothing is sent anywhere except the provider's own quota endpoint.
191
+ - The client shows no credential, and the sidecar's JSON carries no token.
192
+
193
+ ## Development
194
+
195
+ ```bash
196
+ git clone https://github.com/598934321/dsh-quota-pill
197
+ cd dsh-quota-pill
198
+ npm test # 26 tests, offline
199
+ npm start # sidecar on :8319
200
+ ```
201
+
202
+ Tests are offline by design: they point the credential layer at a nonexistent
203
+ directory so they never touch a live account. Fixtures in `test/fixtures.js` are
204
+ synthetic — no real emails, account ids, or balances.
205
+
206
+ ## License
207
+
208
+ MIT
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ /** Entry point: `npx @hystudio/dsh-quota-pill` or the `dsh-quota-pill` bin. */
3
+ import '../src/server.js';