opencode-usage-report 0.2.0 → 0.4.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
@@ -4,63 +4,85 @@
4
4
  [![CI](https://github.com/kaanchinar/opencode-usage-report/actions/workflows/ci.yml/badge.svg)](https://github.com/kaanchinar/opencode-usage-report/actions/workflows/ci.yml)
5
5
  [![License: MIT](https://img.shields.io/npm/l/opencode-usage-report)](./LICENSE)
6
6
 
7
- An [opencode](https://opencode.ai) plugin that adds a `/usage` command (and a
8
- `usage_report` tool) showing the quota windows (5-hour, weekly, monthly) of your
9
- inference subscriptions — currently **Kimi Code** (both regional plans:
10
- `kimi-code-plan-global` on kimi.ai and `kimi-code-plan-cn` on kimi.com) and
11
- **OpenCode Go** (`opencode-go`). It fetches from each provider's API, caches
12
- results on disk, and can fall back to a local estimate when the API is
13
- unreachable. It also emits background low-quota warnings in the TUI.
7
+ An [opencode](https://opencode.ai) plugin that shows how much of your inference
8
+ subscriptions you have left. It adds a `/usage` command and a `usage_report`
9
+ tool covering both the **context window** of the current session and the
10
+ **quota windows** (5-hour, weekly, monthly) of your provider plans —
11
+ **Kimi Code** (`kimi-code-plan-global` on kimi.ai and `kimi-code-plan-cn` on
12
+ kimi.com), **OpenCode Go** (`opencode-go`), **GitHub Copilot**
13
+ (`github-copilot`) and **ChatGPT** (`openai`). Results are cached on disk, fall
14
+ back to a local estimate when an API is unreachable, and raise background
15
+ low-quota toasts in the TUI.
14
16
 
15
17
  ## Features
16
18
 
17
19
  - `/usage` command and `usage_report` tool for on-demand quota reports, with
18
- JSON and single-provider filtering.
19
- - TUI sidebar panel with live progress bars, `NN%` usage, and reset countdowns
20
- for each quota window.
21
- - On-disk caching with a configurable TTL, plus `--refresh` to bypass it.
22
- - Local fallback estimate when the provider API is unreachable and no cache
20
+ JSON output and single-provider filtering.
21
+ - **Context usage panel** (new in 0.4.0) — the exact prompt-token total against
22
+ the model's context window, a cell grid that fills by category, the
23
+ auto-compaction point with live headroom, and session cost.
24
+ - TUI sidebar panel with live progress bars, `NN%` usage and reset countdowns
25
+ for every quota window.
26
+ - On-disk caching with a configurable TTL, plus an explicit refresh that
27
+ bypasses it.
28
+ - Local fallback estimate when a provider API is unreachable and no cache
23
29
  exists.
24
- - Background low-quota TUI warnings on startup and `session.idle`.
30
+ - Low-quota warnings on startup and on `session.idle`.
25
31
  - Privacy-first: API keys are never logged, cached, or rendered.
26
32
 
27
33
  ## Install
28
34
 
29
- ### Server plugin (`/usage` command + warnings)
35
+ The plugin exposes **two entrypoints** and opencode loads them from two
36
+ different config files. Install both for the full experience:
30
37
 
31
- Add the plugin to `opencode.json` / `opencode.jsonc` and restart opencode:
38
+ | Config | Entrypoint | Provides |
39
+ | --------------- | ------------- | ------------------------------------------------------- |
40
+ | `opencode.json` | server plugin | `usage_report` tool, low-quota warnings, prompt capture |
41
+ | `tui.json` | TUI plugin | `/usage` command, sidebar panel |
42
+
43
+ The quickest path is opencode's own installer, which detects both entrypoints
44
+ and writes both configs for you:
45
+
46
+ ```sh
47
+ opencode plugin opencode-usage-report # project scope (./.opencode)
48
+ opencode plugin -g opencode-usage-report # global scope
49
+ ```
50
+
51
+ Or add the plugin manually to each file and restart opencode:
32
52
 
33
53
  ```jsonc
54
+ // opencode.json — server plugin
34
55
  {
35
- "plugin": ["opencode-usage-report"]
56
+ "plugin": ["opencode-usage-report"],
36
57
  }
37
58
  ```
38
59
 
39
- Credentials are read from the same places your other opencode providers already
40
- use (see [Data sources & privacy](#data-sources--privacy)). Optionally pass
41
- options in the tuple form:
42
-
43
60
  ```jsonc
61
+ // tui.json — TUI plugin
44
62
  {
45
- "plugin": [["opencode-usage-report", { "thresholdPercent": 75 }]]
63
+ "plugin": ["opencode-usage-report"],
46
64
  }
47
65
  ```
48
66
 
49
- ### TUI sidebar panel
67
+ > **Use the bare package name in both files.** opencode reads the package
68
+ > `exports` map itself and resolves `exports["./tui"]` for the TUI and
69
+ > `exports["./server"]` (or `main`) for the server. A subpath spec such as
70
+ > `opencode-usage-report/tui` is **not** valid and will silently load nothing.
71
+ > `opencode plugin opencode-usage-report` prints `Detected server + tui targets`
72
+ > when the resolution works.
50
73
 
51
- Add the plugin to `tui.json` and restart opencode:
74
+ Options can be passed in the tuple form, independently per entrypoint:
52
75
 
53
76
  ```jsonc
54
77
  {
55
- "plugin": ["opencode-usage-report"]
78
+ "plugin": [["opencode-usage-report", { "thresholdPercent": 75 }]],
56
79
  }
57
80
  ```
58
81
 
59
- Use the bare package name — opencode resolves the `./tui` entrypoint from the
60
- package `exports` on its own. (`opencode-usage-report/tui` is **not** a valid
61
- spec and will silently not load.)
82
+ ### Local development
62
83
 
63
- For local development, point the configs at the source instead:
84
+ Point the configs at the source instead of the published package. Config is read
85
+ once at startup and is not hot-reloaded, so restart opencode after every edit:
64
86
 
65
87
  ```jsonc
66
88
  // opencode.json
@@ -69,68 +91,158 @@ For local development, point the configs at the source instead:
69
91
  { "plugin": ["file:///abs/path/to/opencode-usage-report/src/tui.tsx"] }
70
92
  ```
71
93
 
72
- The panel renders under opencode's native Context block in the session sidebar
73
- (order 150) and shows a colored progress bar per quota window, `NN%`, and a
74
- live reset countdown — refreshed every 60s, on `session.idle`, and on demand via
75
- the `Usage: refresh now` command (default binding `ctrl+shift+u`). It reuses the
76
- same cache/fallback pipeline as the command; stale results are marked `(stale)`
77
- and local estimates `(est)`.
94
+ ## Commands
95
+
96
+ - `/usage` — open the report dialog: the context panel for the current session
97
+ on top, and every configured provider's quota windows below. This is a local
98
+ TUI command, so it costs no model tokens. Requires the TUI plugin. Providers
99
+ whose credential cannot be resolved are skipped in this view.
100
+ - `/usage` dialog keys — `esc` close, `r` refresh now, `tab` cycle provider
101
+ scope (all → each provider), `j` toggle the raw JSON view.
102
+ - `usage_report` tool — the same report for agents and headless runs. Takes
103
+ `provider` (exact id: `kimi-code-plan-global`, `kimi-code-plan-cn`,
104
+ `opencode-go`, `github-copilot` or `openai`; an unknown id returns an error
105
+ listing the known ids), `json` (raw `ProviderReport[]`) and `refresh` (bypass
106
+ the on-disk cache).
107
+
108
+ > **Changed in 0.4.0.** `/usage` used to be an LLM-mediated prompt template that
109
+ > routed through the model, and it accepted `--json` / `--refresh` / a provider
110
+ > id as slash arguments. The TUI command now owns the name, so `/usage` reaches
111
+ > no model and those capabilities moved to the dialog keys and the
112
+ > `usage_report` tool arguments. Running `/usage --json` is no longer supported.
113
+
114
+ ## Context usage panel
115
+
116
+ The top of the `/usage` dialog reports how full the model's context window is
117
+ for the current session:
78
118
 
79
- TUI options (tuple form): `providers`, `cacheTtlSeconds`, `thresholdPercent`,
80
- `refreshIntervalSeconds` (default `60`), `barWidth` (default `14`).
119
+ ```
120
+ Context
121
+ Kimi K2 (High) · 42,318 / 200,000 tokens (21.2%)
122
+
123
+ ████████████████████████████ ████████████████████████████████ ████████████ ░░░░░░░░░░░░░░
124
+ ████████████████████████████ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░
125
+ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░
126
+ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░
127
+ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░
128
+ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░
129
+
130
+ ● User messages 8,204 4.1% ● System & tools 8,144 4.1%
131
+ ● Agent responses 11,650 5.8% ● Free space 157,682 78.8%
132
+ ● Reasoning 2,140 1.1%
133
+ ● Tool calls 12,180 6.1%
134
+
135
+ Auto-compacts at 180,000 · 137,682 headroom $0.42 spent
136
+ ```
81
137
 
138
+ **What is exact.** The headline total, the percentage, free space, the
139
+ compaction point, the cost and the number of pruned tool outputs all come from
140
+ numbers the provider and opencode report. The total is the prompt size —
141
+ `input + cache.read + cache.write` — which is what actually occupies the context
142
+ window. opencode's own sidebar Context block sums five fields including the
143
+ completion, so it reads slightly higher than this panel by design.
144
+
145
+ **What is estimated.** The per-category rows. opencode records no token count
146
+ per message or part, so each category is estimated with the same `characters / 4`
147
+ heuristic opencode itself uses, then normalized so the rows agree with the exact
148
+ total.
149
+
150
+ **System & tools.** opencode assembles the system prompt and tool definitions on
151
+ every request and never persists them. The server plugin measures the real
152
+ system string through the `experimental.chat.system.transform` hook and stores
153
+ only its size, so the row can be split into `system prompt` and
154
+ `tools & framing`. That hook is undocumented, so it is treated as best-effort:
155
+ if it stops firing, the sidecar goes stale, or anything fails to validate, the
156
+ panel falls back to the derived residual and labels the row `(derived)`. The
157
+ plugin never breaks a request over it.
158
+
159
+ **Compaction headroom.** `Auto-compacts at N` mirrors opencode's own overflow
160
+ check, including the configured `compaction.reserved` buffer. This is the point
161
+ at which opencode automatically summarizes the session — a number opencode
162
+ exposes nowhere else.
82
163
 
83
- ## Commands
164
+ ## Options
84
165
 
85
- - `/usage` — show every configured provider's quota windows. Providers without a
86
- resolved credential are skipped in this view.
87
- - `/usage kimi-code-plan-global` — filter to a single provider. The argument must
88
- match a registered provider id **exactly** (`kimi-code-plan-global`,
89
- `kimi-code-plan-cn` or `opencode-go`); an unknown id (e.g. `/usage kimi`)
90
- returns a helpful error listing the known ids. An explicitly requested
91
- provider with no credential is reported as an error row.
92
- - `/usage --json` — emit the raw `ProviderReport[]` JSON.
93
- - `/usage --refresh` — bypass the on-disk cache and fetch live.
166
+ Shared by both entrypoints:
94
167
 
95
- The command simply calls the `usage_report` tool with those arguments, so the
96
- tool can also be invoked directly by an agent.
168
+ | Option | Type | Default | Meaning |
169
+ | ------------------ | ------------------ | ------- | ------------------------------------------------------------ |
170
+ | `thresholdPercent` | `number` | `80` | Warn when a window is at/above this percent used. |
171
+ | `cacheTtlSeconds` | `number` | `120` | How long a cached API result is considered fresh. |
172
+ | `providers` | `string[] \| null` | `null` | Providers to report; `null` = every registered adapter. |
173
+ | `fallback` | `boolean` | `true` | Use a local estimate when the API fails and no cache exists. |
97
174
 
98
- ## Options
175
+ TUI only:
99
176
 
100
- | Option | Type | Default | Meaning |
101
- | ------------------ | ---------- | ------- | -------------------------------------------------------------- |
102
- | `thresholdPercent` | `number` | `80` | Warn when a window is at/above this percent used. |
103
- | `cacheTtlSeconds` | `number` | `120` | How long a cached API result is considered fresh. |
104
- | `providers` | `string[] \| null` | `null` | Providers to report; `null` = every registered adapter. |
105
- | `fallback` | `boolean` | `true` | Use a local estimate when the API fails and no cache exists. |
177
+ | Option | Type | Default | Meaning |
178
+ | ------------------------ | -------- | ------- | -------------------------------------------------- |
179
+ | `refreshIntervalSeconds` | `number` | `60` | Sidebar/dialog refresh cadence, clamped to 5–3600. |
180
+ | `barWidth` | `number` | `14` | Progress-bar width in cells, clamped to 4–40. |
106
181
 
107
- Malformed option values are ignored and the defaults are kept.
182
+ `fallback` is fixed at `true` for the TUI entrypoint. Malformed option values
183
+ are ignored and the defaults are kept.
108
184
 
109
185
  ## Warnings
110
186
 
111
- On startup and again on `session.idle` (both throttled to at most once every 10
112
- minutes, sharing the same timer), the plugin checks the cached reports. Any
113
- window at or above `thresholdPercent`, or whose
114
- status is `rate-limited` / `frozen`, produces a TUI toast. A warning fires once
115
- per window until that window resets; it re-arms after usage drops below
116
- `thresholdPercent - 10`. Toast failures (headless/server mode) are swallowed.
187
+ On startup and again on `session.idle` (both throttled to at most once every
188
+ 10 minutes, sharing one timer), the plugin checks the cached reports. Any window
189
+ at or above `thresholdPercent`, or whose status is `rate-limited` / `frozen`,
190
+ produces a TUI toast. A warning fires once per window until that window resets;
191
+ it re-arms after usage drops below `thresholdPercent - 10`. Toast failures
192
+ (headless/server mode) are swallowed.
117
193
 
118
194
  ## Data sources & privacy
119
195
 
120
- - Credential resolution order: `OPENCODE_USAGE_<ID>_KEY` env override, then
121
- `~/.local/share/opencode/auth.json` (`type: "api"` `.key`, or `type: "oauth"`
122
- `.access`). `$OPENCODE_DATA_HOME` overrides the data directory.
123
- - APIs: `https://api.kimi.ai/coding/v1/usages` (global plan),
124
- `https://api.kimi.com/coding/v1/usages` (China plan) and
125
- `https://opencode.ai/zen/go/v1/usage` (custom `User-Agent` is required by the
126
- latter).
127
- - Cache and state live in `<data-home>/usage-report/` (TTL cache, session id,
128
- warn state).
129
- - Local fallback reads `opencode.db` read-only.
196
+ - **Credential resolution order.** First `OPENCODE_USAGE_<ID>_KEY` (optionally
197
+ paired with `OPENCODE_USAGE_<ID>_ACCOUNT_ID`, e.g.
198
+ `OPENCODE_USAGE_OPENAI_ACCOUNT_ID`, to supply the ChatGPT account id), then
199
+ `~/.local/share/opencode/auth.json`: a `type: "api"` entry's `.key`, or a
200
+ `type: "oauth"` entry's `.access` (falling back to `.refresh` for GitHub
201
+ Copilot, which stores its token there). For ChatGPT the oauth entry's
202
+ `.accountId` is read automatically. `$OPENCODE_DATA_HOME` overrides the data
203
+ directory.
204
+ - **APIs.** `https://api.kimi.ai/coding/v1/usages` (global plan),
205
+ `https://api.kimi.com/coding/v1/usages` (China plan),
206
+ `https://opencode.ai/zen/go/v1/usage` (requires a custom `User-Agent`),
207
+ `https://api.github.com/copilot_internal/user` (GitHub Copilot) and
208
+ `https://chatgpt.com/backend-api/wham/usage` (ChatGPT).
209
+ - **Auth.** Run `opencode auth login` and pick **GitHub Copilot** or **OpenAI
210
+ ChatGPT**. ChatGPT additionally needs the account id that login writes to
211
+ `auth.json`; the plugin reads it automatically.
212
+ - **Cache and state** live in `<data-home>/usage-report/`: the TTL cache, the
213
+ session id, the warn state, and `context/<sessionID>.json` — the system-prompt
214
+ size sidecar, which holds counts only and never prompt text.
215
+ - **Local fallback** reads `opencode.db` read-only.
130
216
  - **API keys are never logged, cached, or rendered.** Adapter errors are
131
217
  sanitized (key substrings replaced with `<redacted>`) before they reach any
132
218
  output, and the live smoke script never prints credentials.
133
219
 
220
+ ## Troubleshooting
221
+
222
+ **`/usage` does not appear in the command list.**
223
+ Both configs are required — the command comes from the TUI plugin, not the
224
+ server plugin. Check that `tui.json` lists `"opencode-usage-report"` (bare name)
225
+ and restart opencode; config is not hot-reloaded. To confirm the package exposes
226
+ what opencode expects, run `opencode plugin opencode-usage-report` — it should
227
+ print `Detected server + tui targets`.
228
+
229
+ **The sidebar is empty and no providers are reported.**
230
+ Every window belongs to a provider whose credential could not be resolved, so it
231
+ is skipped. Run `opencode auth login`, or set
232
+ `OPENCODE_USAGE_<ID>_KEY` for the provider in question.
233
+
234
+ **An old version keeps loading after you upgrade.**
235
+ opencode caches installed plugin packages under
236
+ `~/.cache/opencode/packages/<name>@latest/` and re-resolves versions on install,
237
+ not on every start. Re-run `opencode plugin opencode-usage-report`, or delete
238
+ that cache directory, to force a refresh. This is the usual cause of "I
239
+ published a new version but opencode still runs the old one".
240
+
241
+ **Quota numbers look stale or marked `(stale)` / `(est)`.**
242
+ `(stale)` means a cached result was served past its TTL without a successful
243
+ refresh; `(est)` means the API was unreachable and a local estimate was used.
244
+ Press `r` in the dialog (or `ctrl+shift+u` in the sidebar) to force a refetch.
245
+
134
246
  ## Extending
135
247
 
136
248
  New providers are trivial:
@@ -147,16 +259,49 @@ Reference endpoints for future adapters (not built in v1):
147
259
  - **Claude**: `api.anthropic.com/api/oauth/usage` (requires
148
260
  `anthropic-beta: oauth-2025-04-20`, the claude-code User-Agent, and
149
261
  `~/.claude/.credentials.json`).
150
- - **Codex**: the ChatGPT backend usage endpoint.
151
262
 
152
263
  ## Development
153
264
 
154
265
  ```sh
266
+ npm run lint # oxlint
267
+ npm run lint:fix # oxlint --fix
268
+ npm run format # oxfmt (write in place)
269
+ npm run format:check # oxfmt --check
155
270
  npm test # vitest, no network
156
271
  npm run typecheck # tsc --noEmit
272
+ npm run check # lint + format:check + typecheck + test
157
273
  npm run smoke -- --yes-live # manual live check (real keys; opt-in)
158
274
  ```
159
275
 
276
+ ## Releasing
277
+
278
+ Releases are **staged**, not published directly. Pushing a `v*` tag triggers
279
+ `.github/workflows/publish.yml`, which verifies that the tag matches
280
+ `package.json`, runs `npm ci`, and uploads the tarball with
281
+ `npm stage publish --provenance --access public` using npm Trusted Publishing
282
+ (OIDC — no `NPM_TOKEN` secret). Nothing goes live until a maintainer approves
283
+ the staged release with 2FA:
284
+
285
+ ```sh
286
+ git tag v0.4.0 && git push origin main v0.4.0
287
+ npm stage list # find the stage id
288
+ npm stage approve <stage-id> # 2FA prompt; or approve in the npm UI
289
+ ```
290
+
291
+ `prepublishOnly` runs lint, format check, typecheck and tests before the upload,
292
+ so a failing check blocks the stage.
293
+
294
+ ## Changelog
295
+
296
+ - **0.4.0** — context usage panel (`/usage` now shows context breakdown, grid,
297
+ compaction headroom and cost); `/usage` became a local TUI command that costs
298
+ no model tokens; sidebar rendering extracted to `src/quota-lines.ts`.
299
+ - **0.3.0** — GitHub Copilot and ChatGPT (OpenAI) adapters.
300
+ - **0.2.0** — Kimi Code split into the kimi.ai (`kimi-code-plan-global`) and
301
+ kimi.com (`kimi-code-plan-cn`) plans.
302
+
160
303
  ## License
161
304
 
162
305
  MIT © [Kaan Chinar](https://github.com/kaanchinar)
306
+
307
+ Security policy: [SECURITY.md](./SECURITY.md)
package/package.json CHANGED
@@ -1,60 +1,68 @@
1
1
  {
2
2
  "name": "opencode-usage-report",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "opencode plugin: /usage command showing subscription quota windows for configured providers",
5
+ "keywords": [
6
+ "kimi",
7
+ "opencode",
8
+ "opencode-plugin",
9
+ "quota",
10
+ "rate-limit",
11
+ "tui",
12
+ "usage"
13
+ ],
14
+ "homepage": "https://github.com/kaanchinar/opencode-usage-report#readme",
15
+ "bugs": {
16
+ "url": "https://github.com/kaanchinar/opencode-usage-report/issues"
17
+ },
18
+ "license": "MIT",
5
19
  "author": "Kaan Chinar",
6
- "type": "module",
7
- "main": "src/index.ts",
8
- "exports": {
9
- ".": "./src/index.ts",
10
- "./tui": "./src/tui.tsx"
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/kaanchinar/opencode-usage-report.git"
11
23
  },
12
24
  "files": [
13
25
  "src",
14
26
  "README.md"
15
27
  ],
16
- "repository": {
17
- "type": "git",
18
- "url": "git+https://github.com/kaanchinar/opencode-usage-report.git"
28
+ "type": "module",
29
+ "main": "src/index.ts",
30
+ "exports": {
31
+ ".": "./src/index.ts",
32
+ "./tui": "./src/tui.tsx"
19
33
  },
20
- "bugs": {
21
- "url": "https://github.com/kaanchinar/opencode-usage-report/issues"
34
+ "publishConfig": {
35
+ "access": "public"
22
36
  },
23
- "homepage": "https://github.com/kaanchinar/opencode-usage-report#readme",
24
- "keywords": [
25
- "opencode",
26
- "opencode-plugin",
27
- "usage",
28
- "quota",
29
- "rate-limit",
30
- "kimi",
31
- "tui"
32
- ],
33
37
  "scripts": {
34
- "test": "vitest run",
38
+ "lint": "oxlint",
39
+ "lint:fix": "oxlint --fix",
40
+ "format": "oxfmt",
41
+ "format:check": "oxfmt --check",
35
42
  "typecheck": "tsc --noEmit",
43
+ "test": "vitest run",
44
+ "test:watch": "vitest",
36
45
  "smoke": "tsx scripts/live-smoke.ts",
37
- "prepublishOnly": "npm run typecheck && npm test"
38
- },
39
- "peerDependencies": {
40
- "@opencode-ai/plugin": "*",
41
- "@opentui/core": ">=0.5.11",
42
- "@opentui/solid": ">=0.5.11",
43
- "solid-js": ">=1.9.0"
46
+ "check": "npm run lint && npm run format:check && npm run typecheck && npm test",
47
+ "prepublishOnly": "npm run lint && npm run format:check && npm run typecheck && npm test"
44
48
  },
45
49
  "devDependencies": {
46
50
  "@opencode-ai/plugin": "^1.0.0",
47
51
  "@opentui/core": "^0.5.11",
48
52
  "@opentui/keymap": "^0.5.11",
49
53
  "@opentui/solid": "^0.5.11",
50
- "@types/node": "^22.0.0",
54
+ "@types/node": "^26.6.2",
55
+ "oxfmt": "^0.70.0",
56
+ "oxlint": "^1.85.0",
51
57
  "solid-js": "1.9.12",
52
58
  "tsx": "^4.19.0",
53
- "typescript": "^5.6.0",
54
- "vitest": "^3.0.0"
59
+ "typescript": "^7.0.2",
60
+ "vitest": "^5.0.1"
55
61
  },
56
- "license": "MIT",
57
- "publishConfig": {
58
- "access": "public"
62
+ "peerDependencies": {
63
+ "@opencode-ai/plugin": "*",
64
+ "@opentui/core": ">=0.5.11",
65
+ "@opentui/solid": ">=0.5.11",
66
+ "solid-js": ">=1.9.0"
59
67
  }
60
68
  }
package/src/auth.ts CHANGED
@@ -1,12 +1,21 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
- import type { Credential } from "./types.js";
4
- import { dataHome } from "./paths.js";
3
+ import type { Credential } from "./types";
4
+ import { dataHome } from "./paths";
5
5
 
6
6
  /** "kimi-code-plan-global" -> "OPENCODE_USAGE_KIMI_CODE_PLAN_GLOBAL_KEY" (non-alphanumerics become "_", uppercased). */
7
7
  export function envVarName(providerId: string): string {
8
+ return providerEnvVarName(providerId, "KEY");
9
+ }
10
+
11
+ /** "openai" -> "OPENCODE_USAGE_OPENAI_ACCOUNT_ID" (same normalization as envVarName). */
12
+ export function accountIdEnvVarName(providerId: string): string {
13
+ return providerEnvVarName(providerId, "ACCOUNT_ID");
14
+ }
15
+
16
+ function providerEnvVarName(providerId: string, suffix: string): string {
8
17
  const normalized = providerId.replace(/[^a-zA-Z0-9]/g, "_").toUpperCase();
9
- return `OPENCODE_USAGE_${normalized}_KEY`;
18
+ return `OPENCODE_USAGE_${normalized}_${suffix}`;
10
19
  }
11
20
 
12
21
  /**
@@ -23,7 +32,10 @@ export function resolveCredential(
23
32
 
24
33
  const override = env[envVarName(providerId)];
25
34
  if (typeof override === "string" && override.trim() !== "") {
26
- return { type: "api", key: override };
35
+ const accountId = env[accountIdEnvVarName(providerId)];
36
+ return typeof accountId === "string" && accountId.trim() !== ""
37
+ ? { type: "api", key: override, accountId }
38
+ : { type: "api", key: override };
27
39
  }
28
40
 
29
41
  const dir = opts.dataHomeDir ?? dataHome(env);
@@ -40,10 +52,21 @@ export function resolveCredential(
40
52
  return typeof key === "string" && key !== "" ? { type: "api", key } : null;
41
53
  }
42
54
  if (record.type === "oauth") {
55
+ // Prefer the access token. opencode stores the GitHub Copilot token in
56
+ // `refresh` (there is no access token), so fall back to it there only;
57
+ // other oauth providers keep the access-only behavior.
43
58
  const access = record.access;
44
- return typeof access === "string" && access !== ""
45
- ? { type: "oauth", key: access }
46
- : null;
59
+ let key = typeof access === "string" && access !== "" ? access : null;
60
+ if (key === null && providerId === "github-copilot") {
61
+ const refresh = record.refresh;
62
+ if (typeof refresh === "string" && refresh !== "") key = refresh;
63
+ }
64
+ if (key === null) return null;
65
+
66
+ const accountId = record.accountId;
67
+ return typeof accountId === "string" && accountId !== ""
68
+ ? { type: "oauth", key, accountId }
69
+ : { type: "oauth", key };
47
70
  }
48
71
  return null;
49
72
  } catch {
package/src/cache.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { join } from "node:path";
4
- import type { AdapterResult } from "./types.js";
5
- import { pluginStateDir } from "./paths.js";
4
+ import type { AdapterResult } from "./types";
5
+ import { pluginStateDir } from "./paths";
6
6
 
7
7
  export interface CacheEntry {
8
8
  fetchedAt: string;