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 +218 -73
- package/package.json +43 -35
- package/src/auth.ts +30 -7
- package/src/cache.ts +2 -2
- package/src/context/collect.ts +251 -0
- package/src/context/dialog.tsx +184 -0
- package/src/context/estimate.ts +36 -0
- package/src/context/format.ts +272 -0
- package/src/context/grid.ts +90 -0
- package/src/context/headroom.ts +42 -0
- package/src/context/system.ts +147 -0
- package/src/context/types.ts +51 -0
- package/src/fallback.ts +2 -2
- package/src/index.ts +33 -15
- package/src/normalize.ts +1 -1
- package/src/providers/chatgpt.ts +194 -0
- package/src/providers/copilot.ts +234 -0
- package/src/providers/index.ts +15 -5
- package/src/providers/kimi.ts +6 -10
- package/src/providers/opencode-go.ts +9 -6
- package/src/quota-lines.ts +116 -0
- package/src/render.ts +10 -5
- package/src/report.ts +11 -8
- package/src/tui-format.ts +2 -4
- package/src/tui.tsx +98 -127
- package/src/types.ts +6 -1
- package/src/warn.ts +5 -4
package/README.md
CHANGED
|
@@ -4,63 +4,85 @@
|
|
|
4
4
|
[](https://github.com/kaanchinar/opencode-usage-report/actions/workflows/ci.yml)
|
|
5
5
|
[](./LICENSE)
|
|
6
6
|
|
|
7
|
-
An [opencode](https://opencode.ai) plugin that
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
**
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
-
|
|
20
|
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
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
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
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": [
|
|
63
|
+
"plugin": ["opencode-usage-report"],
|
|
46
64
|
}
|
|
47
65
|
```
|
|
48
66
|
|
|
49
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
80
|
-
|
|
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
|
-
##
|
|
164
|
+
## Options
|
|
84
165
|
|
|
85
|
-
|
|
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
|
-
|
|
96
|
-
|
|
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
|
-
|
|
175
|
+
TUI only:
|
|
99
176
|
|
|
100
|
-
| Option
|
|
101
|
-
|
|
|
102
|
-
| `
|
|
103
|
-
| `
|
|
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
|
-
|
|
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
|
|
112
|
-
minutes, sharing
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
`
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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.
|
|
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
|
-
"
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
"
|
|
17
|
-
|
|
18
|
-
|
|
28
|
+
"type": "module",
|
|
29
|
+
"main": "src/index.ts",
|
|
30
|
+
"exports": {
|
|
31
|
+
".": "./src/index.ts",
|
|
32
|
+
"./tui": "./src/tui.tsx"
|
|
19
33
|
},
|
|
20
|
-
"
|
|
21
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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": "^
|
|
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": "^
|
|
54
|
-
"vitest": "^
|
|
59
|
+
"typescript": "^7.0.2",
|
|
60
|
+
"vitest": "^5.0.1"
|
|
55
61
|
},
|
|
56
|
-
"
|
|
57
|
-
|
|
58
|
-
"
|
|
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
|
|
4
|
-
import { dataHome } from "./paths
|
|
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}
|
|
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
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
|
5
|
-
import { pluginStateDir } from "./paths
|
|
4
|
+
import type { AdapterResult } from "./types";
|
|
5
|
+
import { pluginStateDir } from "./paths";
|
|
6
6
|
|
|
7
7
|
export interface CacheEntry {
|
|
8
8
|
fetchedAt: string;
|