@leo000001/opencode-quota-sidebar 3.0.1 → 3.0.3

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.
Files changed (44) hide show
  1. package/CHANGELOG.md +14 -2
  2. package/CONTRIBUTING.md +4 -1
  3. package/README.md +210 -514
  4. package/README.zh-CN.md +337 -0
  5. package/SECURITY.md +2 -2
  6. package/assets/OpenCode-Quota-Sidebar.png +0 -0
  7. package/dist/cost.d.ts +3 -3
  8. package/dist/cost.js +258 -169
  9. package/dist/format.js +10 -3
  10. package/dist/index.js +4 -3
  11. package/dist/providers/common.d.ts +6 -0
  12. package/dist/providers/common.js +32 -12
  13. package/dist/providers/core/anthropic.d.ts +1 -1
  14. package/dist/providers/core/anthropic.js +43 -39
  15. package/dist/providers/core/kimi_for_coding.d.ts +1 -1
  16. package/dist/providers/core/kimi_for_coding.js +44 -64
  17. package/dist/providers/core/minimax_cn_coding_plan.d.ts +2 -0
  18. package/dist/providers/core/minimax_cn_coding_plan.js +214 -0
  19. package/dist/providers/core/zhipu_coding_plan.d.ts +1 -1
  20. package/dist/providers/core/zhipu_coding_plan.js +41 -61
  21. package/dist/providers/index.d.ts +3 -3
  22. package/dist/providers/index.js +5 -5
  23. package/dist/providers/third_party/rightcode.d.ts +1 -1
  24. package/dist/providers/third_party/rightcode.js +41 -61
  25. package/dist/providers/third_party/xyai.d.ts +2 -0
  26. package/dist/providers/third_party/{xyai_vibe.js → xyai.js} +113 -79
  27. package/dist/quota.d.ts +2 -2
  28. package/dist/quota.js +24 -18
  29. package/dist/quota_render.d.ts +1 -1
  30. package/dist/quota_render.js +23 -17
  31. package/dist/storage_parse.js +1 -0
  32. package/dist/title.js +7 -7
  33. package/dist/title_apply.js +18 -1
  34. package/dist/tools.d.ts +13 -8
  35. package/dist/tools.js +4 -2
  36. package/dist/tui.tsx +2 -1
  37. package/dist/tui_helpers.d.ts +2 -1
  38. package/dist/tui_helpers.js +6 -1
  39. package/dist/types.d.ts +2 -0
  40. package/package.json +11 -3
  41. package/quota-sidebar.config.example.json +45 -45
  42. package/dist/providers/third_party/buzz.d.ts +0 -2
  43. package/dist/providers/third_party/buzz.js +0 -156
  44. package/dist/providers/third_party/xyai_vibe.d.ts +0 -2
package/README.md CHANGED
@@ -3,53 +3,86 @@
3
3
  [![npm version](https://img.shields.io/npm/v/@leo000001/opencode-quota-sidebar.svg)](https://www.npmjs.com/package/@leo000001/opencode-quota-sidebar)
4
4
  [![license](https://img.shields.io/npm/l/@leo000001/opencode-quota-sidebar.svg)](https://github.com/xihuai18/opencode-quota-sidebar/blob/main/LICENSE)
5
5
 
6
- OpenCode plugin: show token usage and subscription quota in TUI sidebar panels and compact shared session titles.
6
+ [简体中文](./README.zh-CN.md)
7
+
8
+ OpenCode plugin that shows token usage and provider quota in the TUI sidebar, while keeping the shared session title compact and readable across Desktop, Web, and TUI.
7
9
 
8
10
  ![Example sidebar title with usage and quota](./assets/OpenCode-Quota-Sidebar.png)
9
11
 
10
- ## Install
12
+ The screenshot above comes from [`./assets/OpenCode-Quota-Sidebar.png`](./assets/OpenCode-Quota-Sidebar.png) and shows the actual TUI sidebar layout this plugin renders.
11
13
 
12
- The package manifest advertises both `server` and `tui` targets, but OpenCode loads those targets from different config files at runtime.
14
+ ## What It Does
13
15
 
14
- If you configure the plugin manually, you must add the server entry to `opencode.json` and the TUI entry to `tui.json`:
16
+ - Renders dedicated `TITLE`, `USAGE`, and `QUOTA` blocks in the TUI sidebar
17
+ - Keeps the shared `session.title` on a compact single line instead of pushing multiline telemetry into every client
18
+ - Aggregates usage for `session`, `day`, `week`, and `month`
19
+ - Supports provider quota/balance fetchers for OpenAI, Copilot, Anthropic, Kimi, Zhipu, MiniMax, RightCode, and XYAI
20
+ - Can include descendant subagent sessions in session-scoped usage/quota totals
21
+ - Exposes `quota_summary` and `quota_show` tools for reports and title toggling
15
22
 
16
- `opencode.json`
23
+ ## Architecture Overview
17
24
 
18
- ```json
19
- {
20
- "plugin": ["@leo000001/opencode-quota-sidebar@2.0.26"]
21
- }
22
- ```
25
+ This repository is a pure plugin implementation. It does not modify OpenCode core.
23
26
 
24
- `tui.json`
27
+ - Server layer: aggregates usage, fetches quota snapshots, stores state, manages title refresh, and exposes tools
28
+ - TUI layer: renders the sidebar panel UI and reads persisted sidebar-panel payloads
29
+ - Persistence layer: stores global state plus day-partitioned session chunks so historical sessions can render without full rescans
30
+ - Provider adapter layer: maps each provider to a common `QuotaSnapshot` shape through a registry of adapters
25
31
 
26
- ```json
27
- {
28
- "plugin": ["@leo000001/opencode-quota-sidebar@2.0.26"]
29
- }
30
- ```
32
+ The implementation is built on top of `@opencode-ai/plugin` and `@opencode-ai/sdk`.
31
33
 
32
- Note for OpenCode `>=1.2.15`: TUI settings and TUI plugins live in `tui.json`, while server plugins stay in `opencode.json`.
34
+ ## How It Works
33
35
 
34
- If you use an installer flow that reads `oc-plugin` targets and patches config for you, it can populate both files automatically. Simply having the package installed in `node_modules` or listed only in `opencode.json` is not enough for the TUI runtime to load `./tui`.
35
- This plugin also accepts both `config.providers` and older `provider.list` runtime shapes when discovering provider options.
36
+ This plugin has two display layers:
36
37
 
37
- If you prefer automatic upgrades, you can still use `@latest`, but pinning an exact version makes behavior easier to reproduce when debugging.
38
+ - TUI sidebar panel: renders structured `TITLE`, `USAGE`, and `QUOTA` blocks
39
+ - Shared session title: stays compact so Desktop, Web, and TUI can all read the same title cleanly
38
40
 
39
- ## Development (build from source)
41
+ In `sidebar.titleMode="auto"`, the shared title remains a compact single line. The richer multiline layout is handled by the dedicated TUI plugin instead of being written into `session.title` for every client.
40
42
 
41
- ```bash
42
- npm install
43
- npm run build
44
- ```
43
+ Session-scoped aggregation can include descendant subagent sessions when `sidebar.includeChildren=true`. Day/week/month summaries do not merge descendants.
44
+
45
+ ## Supported Providers
46
+
47
+ Built-in quota adapters:
48
+
49
+ | Provider | Endpoint family | Auth | Quota shape | Notes |
50
+ | ------------------- | ---------------------------------------------------------- | --------------------- | -------------------------- | --------------------------------------------------------- |
51
+ | OpenAI Codex | `chatgpt.com/backend-api/wham/usage` | OAuth | Multi-window subscription | Reads ChatGPT usage windows such as short-term + weekly |
52
+ | GitHub Copilot | `api.github.com/copilot_internal/user` | OAuth | Monthly subscription | Uses the Copilot internal user endpoint |
53
+ | Anthropic | `api.anthropic.com/api/oauth/usage` | OAuth | Multi-window subscription | Supports plan-based usage windows |
54
+ | Kimi For Coding | `api.kimi.com/coding/v1/usages` | API key | Multi-window subscription | Typically `5h` + weekly windows |
55
+ | Zhipu Coding Plan | `bigmodel.cn/api/monitor/usage/quota/limit` | API key | Token quota | Coding-plan style quota window |
56
+ | MiniMax Coding Plan | `www.minimaxi.com/v1/api/openplatform/coding_plan/remains` | API key | Multi-window subscription | Typically `5h` + weekly windows |
57
+ | RightCode | `www.right.codes/account/summary` | API key | Daily quota and/or balance | Prefix-based subscription matching, with balance fallback |
58
+ | XYAI | `new.xychatai.com/frontend-api/*` | Login -> session auth | Daily balance | Disabled by default, configured in `quota.providers.xyai` |
59
+
60
+ Generic providers without a built-in quota endpoint can still contribute usage totals, but they will not show quota/balance unless an adapter exists.
61
+
62
+ Provider notes:
63
+
64
+ - OpenAI, Copilot, and Anthropic quota support is based on OAuth/session auth, not generic API-key billing endpoints
65
+ - RightCode can show both a daily allowance line and a balance line
66
+ - XYAI requires login credentials in config so the plugin can obtain and cache session auth
67
+ - Copilot quota is supported, but API-equivalent cost is intentionally not shown because runtime pricing is not reliable enough
68
+
69
+ ## Display Rules
70
+
71
+ - Sidebar quota lines show providers actually used in the current session and recognized by an adapter
72
+ - `quota_summary` fetches default quota providers even if they were not used in the current session
73
+ - The TUI sidebar reads persisted `sidebarPanel` / usage state first, so historical sessions can render quickly on open or resume
74
+ - Compact-title quota parsing is only a fallback path; the TUI panel prefers persisted structured data
75
+ - `quota_show` toggles title decoration on or off, but the TUI panel remains the main rich display path
76
+
77
+ ## Install
45
78
 
46
- Add the built server file to your `opencode.json` and the TUI file to your `tui.json`:
79
+ OpenCode loads the server plugin from `opencode.json` and the TUI plugin from `tui.json`.
47
80
 
48
81
  `opencode.json`
49
82
 
50
83
  ```json
51
84
  {
52
- "plugin": ["file:///ABSOLUTE/PATH/opencode-quota-sidebar/dist/index.js"]
85
+ "plugin": ["@leo000001/opencode-quota-sidebar@latest"]
53
86
  }
54
87
  ```
55
88
 
@@ -57,298 +90,121 @@ Add the built server file to your `opencode.json` and the TUI file to your `tui.
57
90
 
58
91
  ```json
59
92
  {
60
- "plugin": ["file:///ABSOLUTE/PATH/opencode-quota-sidebar/dist/tui.tsx"]
93
+ "plugin": ["@leo000001/opencode-quota-sidebar@latest"]
61
94
  }
62
95
  ```
63
96
 
64
- On Windows, use forward slashes, for example:
65
-
66
- - `file:///D:/Lab/opencode-quota-sidebar/dist/index.js`
67
- - `file:///D:/Lab/opencode-quota-sidebar/dist/tui.tsx`
68
-
69
- ## Supported quota providers
70
-
71
- | Provider | Endpoint | Auth | Status |
72
- | --------------- | -------------------------------------- | --------------------- | --------------------------------------- |
73
- | OpenAI Codex | `chatgpt.com/backend-api/wham/usage` | OAuth (ChatGPT) | Multi-window (short-term + weekly) |
74
- | GitHub Copilot | `api.github.com/copilot_internal/user` | OAuth | Monthly quota |
75
- | Kimi For Coding | `api.kimi.com/coding/v1/usages` | API key | Multi-window subscription (5h + weekly) |
76
- | RightCode | `www.right.codes/account/summary` | API key | Subscription or balance (by prefix) |
77
- | Buzz | `buzzai.cc/v1/dashboard/billing/*` | API key | Balance only (computed from total-used) |
78
- | Anthropic | `api.anthropic.com/api/oauth/usage` | OAuth | Multi-window (5h + weekly / plan-based) |
79
- | XYAI Vibe | `new.xychatai.com/frontend-api/*` | Login -> session auth | Daily balance quota with reset time |
80
-
81
- Want to add support for another provider (Google Antigravity, Zhipu AI, Firmware AI, etc.)? See [CONTRIBUTING.md](CONTRIBUTING.md).
82
-
83
- ## Features
84
-
85
- - TUI sidebar can render a dedicated block layout instead of stuffing telemetry into the shared title:
86
- - `TITLE`: clean base session title
87
- - `USAGE`: compact request/input/output/cache lines such as `R184 I189k O53.2k`, `CR31.4k CW3.2k Cd66%`, `Est $12.8`
88
- - `QUOTA`: one compact provider group per provider, with indented continuation lines for multi-window quotas or balances
89
- - while active, the TUI plugin temporarily deactivates the built-in `internal:sidebar-context` block so the custom panel does not duplicate it
90
- - Shared `session.title` now stays compact in `auto` mode. Desktop, Web UI / `serve`, and TUI all keep the shared title on a compact single line such as `<base> | OAI 5h80 R16:20 W70 R04-03 | RC D88.9/60 B260 | Cd66% | Est$0.12`.
91
- - TUI panel data is read from persisted day-chunk session state (`usage` + `sidebarPanel`) so entering or resuming a session can render from persistence first; compact-title parsing remains only a defensive fallback.
92
- - `sidebar.titleMode=multiline` is still available as a legacy fallback when you explicitly want the old multiline title decoration path.
93
- - `sidebar.titleMode` can force `auto`, `multiline`, or `compact` if the heuristic does not match your workflow.
94
- - Multi-client caveat: the shared title is still one `session.title` for every client. The new TUI sidebar blocks avoid polluting that shared title, but Desktop/Web still see the compact shared title rather than a sidebar panel.
95
- - Session-scoped usage/quota can include descendant subagent sessions (enabled by default via `sidebar.includeChildren=true`). Traversal is bounded by `childrenMaxDepth` (default 6), `childrenMaxSessions` (default 128), and `childrenConcurrency` (default 5); truncation is logged when `OPENCODE_QUOTA_DEBUG=1`. Day/week/month ranges never merge children — only session scope does.
96
- - Toast message can include four sections: `Token Usage`, `Cost as API` (per provider), `Provider Cache` (when provider-level cached ratios are available), and `Quota`
97
- - Expiry reminders are shown in a separate `Expiry Soon` toast section only for providers with real subscription expiry timestamps, and each session shows that auto-reminder at most once
98
- - `quota_summary` markdown / toast also include `Cached` summary lines when cache activity is available
99
- - Quota snapshots are de-duplicated before rendering to avoid repeated provider lines
100
- - Custom tools:
101
- - `quota_summary` — generate usage report for session/day/week/month (full markdown report + toast). The markdown report and toast keep the full human-readable wording; they do not switch to compact sidebar tokens.
102
- - `quota_show` — toggle shared title decoration on/off (state persists across sessions)
103
- - After startup, titles are restored immediately when persisted display mode is OFF; when persisted display mode is ON, touched titles refresh on startup and the rest update on the next relevant session/message event or when `quota_show` is toggled
104
- - Quota connectors:
105
- - OpenAI Codex OAuth (`/backend-api/wham/usage`)
106
- - GitHub Copilot OAuth (`/copilot_internal/user`)
107
- - Kimi For Coding API key (`/usages`, built-in `kimi-for-coding` provider)
108
- - RightCode API key (`/account/summary`)
109
- - Buzz API key (`/v1/dashboard/billing/subscription` + `/v1/dashboard/billing/usage`)
110
- - Anthropic Claude OAuth (`/api/oauth/usage`, with beta header)
111
- - XYAI Vibe account login (`/frontend-api/login` -> cached `share-session` -> `/frontend-api/vibe-code/quota`)
112
- - OpenAI OAuth quota checks auto-refresh expired access token (using refresh token)
113
- - Generic API key providers without quota endpoints still show usage aggregation only; built-in adapters such as Kimi For Coding, RightCode, Buzz, and XYAI Vibe also show quota or balance details.
114
- - Incremental usage aggregation — only processes new messages since last cursor
115
- - Sidebar token units are adaptive (`k`/`m` with one decimal where applicable)
116
-
117
- ### Kimi For Coding notes
118
-
119
- - OpenCode's built-in provider ID is `kimi-for-coding` and its runtime base URL is `https://api.kimi.com/coding/v1`.
120
- - The plugin treats Kimi as a subscription quota source, not a balance source.
121
- - Quota data is read from `GET https://api.kimi.com/coding/v1/usages`.
122
- - The current implementation maps the short rolling window in `limits[]` to `5h` and the top-level `usage` block to `Weekly`.
123
- - Rendering follows the same compact reset formatting as OpenAI: short windows show `Rst MM-DD HH:MM` when they cross days, and longer windows show `Rst MM-DD`.
124
-
125
- ### XYAI Vibe notes
126
-
127
- - Enable it explicitly under `quota.providers.xyai-vibe.enabled`; it is not enabled by default.
128
- - Configure login credentials in `quota-sidebar.config.json`, not in source code.
129
- - The adapter logs in via `POST https://new.xychatai.com/frontend-api/login`, caches the returned `share-session`, and retries quota fetches with that session.
130
- - Quota data is read from `GET https://new.xychatai.com/frontend-api/vibe-code/quota`.
131
- - Compact displays show the daily balance and the true reset time when present; expiry stays as secondary report/toast metadata.
132
-
133
- ## Storage layout
134
-
135
- The plugin stores lightweight global state and date-partitioned session chunks.
136
-
137
- - Global metadata: `<opencode-data>/quota-sidebar.state.json`
138
- - `titleEnabled`
139
- - `sessionDateMap` (sessionID -> `YYYY-MM-DD`)
140
- - `quotaCache`
141
- - Session chunks: `<opencode-data>/quota-sidebar-sessions/YYYY/MM/DD.json`
142
- - per-session title state (`baseTitle`, `lastAppliedTitle`)
143
- - `createdAt`
144
- - `parentID` (when the session is a subagent child session)
145
- - `expiryToastShown` (session-level dedupe for automatic expiry reminders)
146
- - cached usage summary used by `quota_summary`, including session-level and provider-level `cacheBuckets` for cached-ratio reporting and legacy cache classification
147
- - `sidebarPanel` cache used by the TUI sidebar plugin (`version`, cached usage, compact quota snapshots`) so `TITLE / USAGE / QUOTA` can render from persisted structure on session open/resume
148
- - incremental aggregation cursor
149
-
150
- Notes on cache bucket persistence:
151
-
152
- - Older cached usage written before `cacheBuckets` existed can only be approximated from top-level `cache_read` / `cache_write` totals.
153
- - In those legacy cases, mixed read-only + read-write cache traffic may be attributed to a single fallback bucket until the session is recomputed from messages.
154
-
155
- Example tree:
156
-
157
- ```text
158
- ~/.local/share/opencode/
159
- quota-sidebar.state.json
160
- quota-sidebar-sessions/
161
- 2026/
162
- 02/
163
- 23.json
164
- 24.json
165
- ```
166
-
167
- Sessions older than `retentionDays` (default 730 days / 2 years) are evicted from
168
- memory on startup. Chunk files remain on disk for historical range scans.
169
-
170
- ## Compatibility
171
-
172
- - Node.js: >= 18 (for `fetch` + `AbortController`)
173
- - OpenCode: plugin SDK `@opencode-ai/plugin` ^1.3.5
174
- - OpenCode config split: if you are on `>=1.2.15`, keep this plugin in `opencode.json` and keep TUI-only keys in `tui.json`.
97
+ For OpenCode `>=1.2.15`, keep server plugins in `opencode.json` and TUI plugins in `tui.json`.
175
98
 
176
- ## Force refresh after npm update
99
+ ## Sidebar Demo
177
100
 
178
- If `npm view @leo000001/opencode-quota-sidebar version` shows a newer version but OpenCode still behaves like an older release, OpenCode/Bun is usually reusing an older installed copy.
101
+ Typical TUI sidebar layout:
179
102
 
180
- Recommended recovery steps:
181
-
182
- 1. Pin the target plugin version in every config file that loads it (`opencode.json` and, if used manually, `tui.json`).
183
- 2. Fully exit OpenCode.
184
- 3. Delete any cached installed copies of the plugin.
185
- 4. Start OpenCode again so it reinstalls the package.
186
- 5. Verify the actual installed `package.json` version under the plugin directory.
187
-
188
- Common install/cache locations:
189
-
190
- - `~/.cache/opencode/node_modules/@leo000001/opencode-quota-sidebar`
191
- - `~/node_modules/@leo000001/opencode-quota-sidebar`
192
-
193
- Windows PowerShell example:
194
-
195
- ```powershell
196
- Remove-Item -Recurse -Force "$HOME\.cache\opencode\node_modules\@leo000001\opencode-quota-sidebar" -ErrorAction SilentlyContinue
197
- Remove-Item -Recurse -Force "$HOME\node_modules\@leo000001\opencode-quota-sidebar" -ErrorAction SilentlyContinue
103
+ ```text
104
+ TITLE
105
+ Fix quota adapter matching
106
+ USAGE
107
+ R184 I189k O53.2k
108
+ CR31.4k CW3.2k Cd66%
109
+ Est $12.8
110
+ QUOTA
111
+ OAI 5h80 R16:20
112
+ W70 R04-03
113
+ Cop M78 R04-01
114
+ RC D$88.9/$60 E02-27
115
+ B260
198
116
  ```
199
117
 
200
- macOS / Linux example:
118
+ Compact shared title example:
201
119
 
202
- ```bash
203
- rm -rf ~/.cache/opencode/node_modules/@leo000001/opencode-quota-sidebar
204
- rm -rf ~/node_modules/@leo000001/opencode-quota-sidebar
120
+ ```text
121
+ Fix quota adapter matching | OAI 5h80 R16:20 W70 R04-03 | RC D$88.9/$60 B260 | Cd66% | Est$12.8
205
122
  ```
206
123
 
207
- ## Optional commands
124
+ Another compact title example with multiple providers:
208
125
 
209
- You can add these command templates in `opencode.json` so you can run `/qday`, `/qweek`, `/qmonth`, `/qtoggle`:
210
-
211
- ```json
212
- {
213
- "command": {
214
- "qday": {
215
- "description": "Show today's usage and quota",
216
- "template": "Call tool quota_summary with period=day and toast=true."
217
- },
218
- "qweek": {
219
- "description": "Show this week's usage and quota",
220
- "template": "Call tool quota_summary with period=week and toast=true."
221
- },
222
- "qmonth": {
223
- "description": "Show this month's usage and quota",
224
- "template": "Call tool quota_summary with period=month and toast=true."
225
- },
226
- "qtoggle": {
227
- "description": "Toggle sidebar usage display on/off",
228
- "template": "Call tool quota_show (no arguments, it toggles)."
229
- }
230
- }
231
- }
126
+ ```text
127
+ Add XYAI quota adapter | Ant 5h100 W77 O7d60 | Cop M78 R04-01 | Cd52% | Est$2.34
232
128
  ```
233
129
 
234
- When calling `quota_summary`, make sure the client shows the returned markdown report directly to the user. The tool already returns the full report body; do not replace it with a compact summary.
130
+ ## Tool Report Demo
235
131
 
236
- ## Configuration files
132
+ Example `quota_summary` markdown output shape:
237
133
 
238
- Recommended global config:
134
+ ```md
135
+ ## Session Usage
239
136
 
240
- - `~/.config/opencode/quota-sidebar.config.json`
241
-
242
- Optional project overrides:
137
+ - Requests: 184
138
+ - Input: 189k
139
+ - Output: 53.2k
140
+ - Cache Read: 31.4k
141
+ - Cache Write: 3.2k
142
+ - Cost as API: $12.8
243
143
 
244
- - `<worktree>/quota-sidebar.config.json`
245
- - `<directory>/quota-sidebar.config.json` (when different from `worktree`)
246
- - `<worktree>/.opencode/quota-sidebar.config.json`
247
- - `<directory>/.opencode/quota-sidebar.config.json` (when different from `worktree`)
144
+ ## Quota
248
145
 
249
- Optional explicit override:
146
+ - OpenAI: 5h 80% (reset 16:20), Weekly 70% (reset 04-03)
147
+ - Copilot: Monthly 78% (reset 04-01)
148
+ - RightCode: Daily $88.9/$60 (exp 02-27), Balance $260
149
+ ```
250
150
 
251
- - `OPENCODE_QUOTA_CONFIG=/absolute/path/to/config.json`
151
+ The tool already returns full markdown. Clients should display that report directly instead of replacing it with a short summary.
252
152
 
253
- Optional config-home override:
153
+ Sidebar output uses compact tokens. Toasts and markdown reports keep fuller human-readable wording.
254
154
 
255
- - `OPENCODE_QUOTA_CONFIG_HOME=/absolute/path/to/config-home`
155
+ ## Why The TUI Panel Exists
256
156
 
257
- Resolution order (low -> high):
157
+ OpenCode renders the sidebar title as plain text inside a single styled title field. That means the shared title is great for compact telemetry, but not ideal for a rich multi-section layout.
258
158
 
259
- 1. Global config (`~/.config/opencode/...`)
260
- 2. `<worktree>/quota-sidebar.config.json`
261
- 3. `<directory>/quota-sidebar.config.json`
262
- 4. `<worktree>/.opencode/quota-sidebar.config.json`
263
- 5. `<directory>/.opencode/quota-sidebar.config.json`
264
- 6. `OPENCODE_QUOTA_CONFIG`
159
+ This plugin therefore uses:
265
160
 
266
- Values are layered; later sources override earlier ones.
161
+ - shared compact titles for cross-client compatibility
162
+ - a dedicated TUI sidebar panel for the detailed block layout
267
163
 
268
- ## Configuration
164
+ That split avoids polluting Desktop/Web titles while still giving TUI users a readable quota dashboard.
269
165
 
270
- If you do not provide any config file, the plugin uses the built-in defaults below.
166
+ ## Abbreviations
271
167
 
272
- ### Built-in defaults
168
+ Usage tokens:
273
169
 
274
- Sidebar defaults:
170
+ - `R`: requests
171
+ - `I`: input tokens
172
+ - `O`: output tokens, including reasoning tokens
173
+ - `CR`: cache read tokens
174
+ - `CW`: cache write tokens
175
+ - `Cd`: cached ratio / cache coverage
176
+ - `Est`: API-equivalent cost estimate
275
177
 
276
- - `sidebar.enabled`: `true`
277
- - `sidebar.width`: `36` (clamped to `20`-`60`)
278
- - `sidebar.titleMode`: `auto` (`auto`/`multiline`/`compact`)
279
- - `sidebar.multilineTitle`: `true` (legacy compatibility field; title style is now chosen automatically)
280
- - `sidebar.showCost`: `true`
281
- - `sidebar.showQuota`: `true`
282
- - `sidebar.wrapQuotaLines`: `true`
283
- - `sidebar.includeChildren`: `true`
284
- - `sidebar.childrenMaxDepth`: `6` (clamped to `1`-`32`)
285
- - `sidebar.childrenMaxSessions`: `128` (clamped to `0`-`2000`)
286
- - `sidebar.childrenConcurrency`: `5` (clamped to `1`-`10`)
287
- - `sidebar.desktopCompact.recentRequests`: `50` (compact single-line titles)
288
- - `sidebar.desktopCompact.recentMinutes`: `60` (compact single-line titles)
178
+ Quota tokens:
289
179
 
290
- Quota defaults:
180
+ - `OAI`: OpenAI
181
+ - `Cop`: GitHub Copilot
182
+ - `Ant`: Anthropic
183
+ - `RC`: RightCode
184
+ - `B`: balance
185
+ - `D`: daily window
186
+ - `W`: weekly window
187
+ - `M`: monthly window
188
+ - `R16:20`: reset at `16:20`
189
+ - `R04-03`: reset on `04-03`
190
+ - `E02-27`: expiry on `02-27`
291
191
 
292
- - `quota.refreshMs`: `300000` (clamped to `>=30000`)
293
- - `quota.includeOpenAI`: `true`
294
- - `quota.includeCopilot`: `true`
295
- - `quota.includeAnthropic`: `true`
296
- - `quota.providers`: `{}` (per-adapter switches and adapter-specific config, for example `rightcode.enabled` or `xyai-vibe.login.username/password`)
297
- - `quota.refreshAccessToken`: `false`
298
- - `quota.requestTimeoutMs`: `8000` (clamped to `>=1000`)
192
+ Example compact quota fragments:
299
193
 
300
- Other defaults:
194
+ - `OAI 5h80 R16:20`: OpenAI short window, 80% remaining, resets at `16:20`
195
+ - `Cop M78 R04-01`: Copilot monthly quota, 78% remaining, resets on `04-01`
196
+ - `RC D$88.9/$60 E02-27 B260`: RightCode daily quota plus balance
301
197
 
302
- - `toast.durationMs`: `12000` (clamped to `>=1000`)
303
- - `retentionDays`: `730`
198
+ ## Config
304
199
 
305
- ### Full example config
200
+ Recommended config file locations:
306
201
 
307
- ```json
308
- {
309
- "sidebar": {
310
- "enabled": true,
311
- "width": 36,
312
- "titleMode": "auto",
313
- "multilineTitle": true,
314
- "showCost": true,
315
- "showQuota": true,
316
- "wrapQuotaLines": true,
317
- "includeChildren": true,
318
- "childrenMaxDepth": 6,
319
- "childrenMaxSessions": 128,
320
- "childrenConcurrency": 5,
321
- "desktopCompact": {
322
- "recentRequests": 50,
323
- "recentMinutes": 60
324
- }
325
- },
326
- "quota": {
327
- "refreshMs": 300000,
328
- "includeOpenAI": true,
329
- "includeCopilot": true,
330
- "includeAnthropic": true,
331
- "providers": {
332
- "buzz": {
333
- "enabled": true
334
- },
335
- "rightcode": {
336
- "enabled": true
337
- }
338
- },
339
- "refreshAccessToken": false,
340
- "requestTimeoutMs": 8000
341
- },
342
- "toast": {
343
- "durationMs": 12000
344
- },
345
- "retentionDays": 730
346
- }
347
- ```
348
-
349
- ### Common minimal configs
202
+ - `~/.config/opencode/quota-sidebar.config.json`
203
+ - `<worktree>/quota-sidebar.config.json`
204
+ - `<worktree>/.opencode/quota-sidebar.config.json`
205
+ - `OPENCODE_QUOTA_CONFIG=/absolute/path/to/config.json`
350
206
 
351
- Keep the shared title compact and let TUI render the structured panel:
207
+ Minimal example:
352
208
 
353
209
  ```json
354
210
  {
@@ -362,268 +218,108 @@ Keep the shared title compact and let TUI render the structured panel:
362
218
  }
363
219
  ```
364
220
 
365
- Enable XYAI Vibe quota with account login:
366
-
367
- ```json
368
- {
369
- "quota": {
370
- "providers": {
371
- "xyai-vibe": {
372
- "enabled": true,
373
- "login": {
374
- "username": "your-account",
375
- "password": "your-password"
376
- }
377
- }
378
- }
379
- }
380
- }
381
- ```
221
+ See [`quota-sidebar.config.example.json`](./quota-sidebar.config.example.json) for a fuller config example.
382
222
 
383
- ### Notes
384
-
385
- - `sidebar.showCost` controls API-cost visibility in the TUI `USAGE` block, the compact shared title, `quota_summary` markdown report, and toast message.
386
- - `quota_summary` follows the same reset compaction rules for short windows in its subscription section (`5h` / `1d` / `Daily` show time, long windows show date, RightCode `Exp` stays date-only).
387
- - `sidebar.width` is measured in terminal cells. CJK/emoji truncation is best-effort to avoid sidebar overflow.
388
- - `sidebar.titleMode` defaults to `auto`: the shared `session.title` stays compact for Desktop, Web UI / `serve`, and TUI alike. The rich TUI layout comes from the dedicated TUI plugin slots instead of a multiline shared title. Use `multiline` only if you explicitly want the legacy decorated-title path, or `compact` to force compact titles everywhere.
389
- - The TUI plugin renders `TITLE`, `USAGE`, and `QUOTA` blocks in the sidebar and temporarily disables the built-in `internal:sidebar-context` block while it is active.
390
- - The shared `session.title` is still one string per session for all clients. TUI sidebar blocks avoid polluting that title, but Desktop/Web still see the compact shared title rather than a TUI panel.
391
- - `sidebar.multilineTitle` is kept for backward compatibility, but `sidebar.titleMode` now controls the active policy.
392
- - `sidebar.wrapQuotaLines` controls quota line wrapping and continuation indentation (default: `true`).
393
- - `sidebar.includeChildren` controls whether session-scoped usage/quota includes descendant subagent sessions (default: `true`).
394
- - `sidebar.childrenMaxDepth` limits how many levels of nested subagents are traversed (default: `6`, clamped 1–32).
395
- - `sidebar.childrenMaxSessions` caps the total number of descendant sessions aggregated (default: `128`, clamped 0–2000).
396
- - `sidebar.childrenConcurrency` controls parallel fetches for descendant session messages (default: `5`, clamped 1–10).
397
- - `sidebar.desktopCompact.recentRequests` and `sidebar.desktopCompact.recentMinutes` control which recently used providers remain visible in compact single-line titles.
398
- - `sidebar.desktopCompact.recentRequests` and `sidebar.desktopCompact.recentMinutes` only control provider filtering inside compact titles; they do not affect the dedicated TUI sidebar panel.
399
- - `output` includes reasoning tokens (`output = tokens.output + tokens.reasoning`). Reasoning is not rendered as a separate line.
400
- - API cost bills reasoning tokens at the output rate (same as completion tokens).
401
- - API cost is computed from OpenCode model pricing metadata, not from `message.cost`. This keeps subscription-backed providers such as OpenAI OAuth usable for API-equivalent cost estimation even when OpenCode's measured cost is `0`.
402
- - When OpenCode exposes a long-context tier like `context_over_200k`, the plugin uses that premium rate for the whole request once `input > 200000`, matching OpenCode's current pricing schema.
403
- - `quota.providers` is the extensible per-adapter switch map.
404
- - If API Cost is `$0.00`, it usually means the model/provider has no pricing mapping in OpenCode at the moment, so equivalent API cost cannot be estimated.
405
- - Usage chunks cache both measured `cost` and computed `apiCost`. `quota_summary` (`/qday`, `/qweek`, `/qmonth`) recomputes range totals from session messages so period filtering follows message completion time; refreshed full-session usage may then be persisted back into day chunks when billing-cache refresh is needed.
406
-
407
- ### Buzz provider example
408
-
409
- Buzz matching is based on the provider `baseURL`, similar to RightCode. Any OpenAI-compatible provider that points at `https://buzzai.cc` will be recognized by the Buzz adapter and rendered as a balance-only quota source.
410
-
411
- Provider options example:
223
+ Important config notes:
412
224
 
413
- ```json
414
- {
415
- "id": "openai",
416
- "options": {
417
- "baseURL": "https://buzzai.cc",
418
- "apiKey": "sk-..."
419
- }
420
- }
421
- ```
225
+ - `sidebar.titleMode`: `auto`, `compact`, or `multiline`
226
+ - `sidebar.showCost`: controls API-equivalent cost in sidebar, title, markdown report, and toast
227
+ - `sidebar.wrapQuotaLines`: wraps long quota lines with indentation instead of dropping fields
228
+ - `sidebar.includeChildren`: includes descendant subagent sessions for `period=session`
229
+ - `quota.providers.xyai.enabled`: must be explicitly enabled if you want XYAI quota
230
+ - `quota.providers.xyai.login.username/password`: used to fetch and refresh XYAI session auth
422
231
 
423
- The adapter also tolerates `https://buzzai.cc/v1`, but `https://buzzai.cc` is the recommended example.
232
+ Config is layered. The later source overrides the earlier one:
424
233
 
425
- With that setup, the sidebar/toast quota line will look like:
234
+ 1. global config
235
+ 2. worktree config
236
+ 3. directory config
237
+ 4. worktree `.opencode` config
238
+ 5. directory `.opencode` config
239
+ 6. `OPENCODE_QUOTA_CONFIG`
426
240
 
427
- ```text
428
- Buzz B¥10.17
429
- ```
241
+ ## Persistence And Aggregation
430
242
 
431
- ## Rendering examples
243
+ The plugin stores:
432
244
 
433
- These examples show the quota block portion of the sidebar title.
245
+ - global state in `<opencode-data>/quota-sidebar.state.json`
246
+ - session chunks in `<opencode-data>/quota-sidebar-sessions/YYYY/MM/DD.json`
434
247
 
435
- ### TUI layout
248
+ Those persisted chunks keep title state, cached usage, sidebar-panel payloads, and quota cache data. This lets the TUI sidebar render from stored structured state on session open/resume instead of depending entirely on live message scans.
436
249
 
437
- The default TUI layout is a real sidebar panel, not a multiline shared title. Desktop and Web UI / `serve` clients keep the compact shared title unless you force `sidebar.titleMode = "multiline"`.
250
+ Usage aggregation is incremental. The plugin tracks a cursor per session and processes only new messages when possible. If message history changes in a way that invalidates the incremental view, it can rescan and refresh persisted usage.
438
251
 
439
- Typical layout:
252
+ ## Tools
440
253
 
441
- ```text
442
- TITLE
443
- Fix quota adapter matching
444
- USAGE
445
- R184 I189k O53.2k
446
- CR31.4k CW3.2k Cd66%
447
- Est $12.8
448
- QUOTA
449
- OAI 5h80 R16:20
450
- W70 R04-03
451
- Cop M78 R04-01
452
- RC D$88.9/$60 E02-27
453
- B260
454
- ```
254
+ - `quota_summary`: shows usage and quota for `session`, `day`, `week`, or `month`
255
+ - `quota_show`: toggles title decoration on or off
455
256
 
456
- ### Force modes
257
+ Behavior notes:
457
258
 
458
- If you prefer a stable policy instead of the auto heuristic:
259
+ - `quota_summary` returns the full markdown report body
260
+ - `quota_summary` can show a toast in addition to returning markdown
261
+ - `quota_summary(includeChildren=true)` only changes `period=session`
262
+ - `quota_show(enabled=true|false)` can explicitly force a state instead of toggling
459
263
 
460
- ```json
461
- {
462
- "sidebar": {
463
- "titleMode": "compact"
464
- }
465
- }
466
- ```
264
+ Example command aliases:
467
265
 
468
266
  ```json
469
267
  {
470
- "sidebar": {
471
- "titleMode": "multiline"
268
+ "command": {
269
+ "qday": {
270
+ "description": "Show today's usage and quota",
271
+ "template": "Call tool quota_summary with period=day and toast=true."
272
+ },
273
+ "qweek": {
274
+ "description": "Show this week's usage and quota",
275
+ "template": "Call tool quota_summary with period=week and toast=true."
276
+ },
277
+ "qmonth": {
278
+ "description": "Show this month's usage and quota",
279
+ "template": "Call tool quota_summary with period=month and toast=true."
280
+ },
281
+ "qtoggle": {
282
+ "description": "Toggle sidebar usage display on/off",
283
+ "template": "Call tool quota_show (no arguments, it toggles)."
284
+ }
472
285
  }
473
286
  }
474
287
  ```
475
288
 
476
- 0 providers (no quota data):
477
-
478
- ```text
479
- (no quota block)
480
- ```
481
-
482
- 1 provider, 1 window (fits):
483
-
484
- ```text
485
- Cop M78 R04-01
486
- ```
487
-
488
- 1 provider, multi-window (for example OpenAI 5h + Weekly):
489
-
490
- ```text
491
- OAI 5h78 R05:05 W73 R03-12
492
- ```
493
-
494
- 1 provider, multi-window on narrow width:
495
-
496
- ```text
497
- OAI 5h78 R05:05
498
- W73 R03-12
499
- ```
500
-
501
- 1 provider, short window crossing into the next day:
502
-
503
- ```text
504
- Ant 5h0 R03-10 01:00 W46 R03-15
505
- ```
506
-
507
- 2+ providers (even if each provider is single-window):
508
-
509
- ```text
510
- OAI 5h78 R05:05
511
- Cop M78 R04-01
512
- ```
513
-
514
- 2+ providers mixed (multi-window + single-window):
289
+ ## Development
515
290
 
516
- ```text
517
- OAI 5h78 R05:05 W73 R03-12
518
- Cop M78 R04-01
519
- ```
520
-
521
- 2+ providers mixed (window providers + Buzz balance):
522
-
523
- ```text
524
- OAI 5h78 R05:05
525
- Cop M78 R04-01
526
- Buzz B¥10.2
527
- ```
528
-
529
- Balance-style quota:
530
-
531
- ```text
532
- RC B260
533
- ```
534
-
535
- Buzz balance quota:
536
-
537
- ```text
538
- Buzz B¥10.17
539
- ```
540
-
541
- Multi-detail quota (window + balance):
542
-
543
- ```text
544
- RC D$88.9/$60 E02-27 B260
291
+ ```bash
292
+ npm install
293
+ npm run build
294
+ npm test
545
295
  ```
546
296
 
547
- Provider status / quota (examples):
548
-
549
- ```text
550
- Ant 5h80
551
- Cop unavailable
552
- OAI ?
553
- ```
297
+ For local development, load:
554
298
 
555
- ### Compact shared title mode
299
+ - `file:///ABSOLUTE/PATH/opencode-quota-sidebar/dist/index.js` in `opencode.json`
300
+ - `file:///ABSOLUTE/PATH/opencode-quota-sidebar/dist/tui.tsx` in `tui.json`
556
301
 
557
- Desktop and Web UI / `serve` use a compact monitoring-style single-line shared title in `auto` mode. Recently used providers are selected from the last `50` assistant requests or last `60` minutes, and each selected provider expands all of its windows and balances in shorthand. To survive upstream truncation better, quota segments are emitted before usage summary signals:
302
+ On Windows, use forward slashes in `file:///` URLs.
558
303
 
559
- ```text
560
- <base> | OAI 5h80 R16:20 W70 R04-03 | Cop M78 R04-01 | RC D88.9/60 B260 | Buzz B¥10.2 | Cd66% | Est$0.12
561
- ```
304
+ ## Compatibility And Caveats
562
305
 
563
- Shorthand rules:
564
-
565
- - `5h80` = `5h 80%`
566
- - `W70` / `M78` / `D46` = weekly / monthly / daily window remaining percent
567
- - `R16:20` / `R04-03` = reset time/date for that quota window
568
- - `D88.9/60` = daily remaining / daily total
569
- - `B260` / `B¥10.2` = balance
570
- - `Cd66%` = cached ratio (`cache.read / (input + cache.read)`)
571
- - `Est$0.12` = equivalent API cost estimate
572
- - Compact shared titles omit `R/I/O/CR/CW`; the dedicated TUI sidebar keeps the richer `TITLE / USAGE / QUOTA` breakdown.
573
- - Order is `base | quota... | usage-summary` for compact shared titles.
574
-
575
- `quota_summary` also supports an optional `includeChildren` flag (only effective for `period=session`) to override the config per call. For `day`/`week`/`month` periods, children are never merged — each session is counted independently.
576
-
577
- ## Billing cache behavior
578
-
579
- - Cached per-session usage stores token totals, measured `cost`, computed `apiCost`, provider breakdowns, and the incremental cursor.
580
- - Session-scoped sidebar aggregation can merge descendant subagents when `sidebar.includeChildren=true` (default). Measured `cost` stays aligned with the root session's OpenCode `message.cost`, while API-equivalent cost still includes descendant usage.
581
- - Range tools such as `/qday`, `/qweek`, and `/qmonth` do not merge children. They aggregate each session independently across the selected time window.
582
- - When API-cost logic changes, the plugin bumps an internal billing-cache version so historical range reports are recomputed with the new rules the next time they are queried.
583
-
584
- ## Debug logging
585
-
586
- Set `OPENCODE_QUOTA_DEBUG=1` to enable debug logging to stderr. This logs:
587
-
588
- - Chunk I/O operations
589
- - Auth refresh attempts and failures
590
- - Session eviction counts
591
- - Symlink write refusals
592
-
593
- ## Security & privacy notes
594
-
595
- - The plugin reads OpenCode credentials from `<opencode-data>/auth.json`.
596
- - If enabled, quota checks call external endpoints:
597
- - OpenAI Codex: `https://chatgpt.com/backend-api/wham/usage`
598
- - GitHub Copilot: `https://api.github.com/copilot_internal/user`
599
- - Kimi For Coding: `https://api.kimi.com/coding/v1/usages`
600
- - RightCode: `https://www.right.codes/account/summary`
601
- - Buzz: `https://buzzai.cc/v1/dashboard/billing/subscription` and `https://buzzai.cc/v1/dashboard/billing/usage`
602
- - Anthropic: `https://api.anthropic.com/api/oauth/usage`
603
- - **Screen-sharing warning**: Session titles and toasts surface usage/quota
604
- information. If you are screen-sharing or recording, consider toggling the
605
- sidebar display off (`/qtoggle` or `quota_show` tool) to avoid leaking
606
- subscription details.
607
- - State is persisted under `<opencode-data>/quota-sidebar.state.json` and
608
- `<opencode-data>/quota-sidebar-sessions/` (see Storage layout).
609
- - OpenAI OAuth token refresh is disabled by default; set
610
- `quota.refreshAccessToken=true` if you want the plugin to refresh access
611
- tokens when expired.
612
- - Anthropic quota currently uses a beta/internal-style OAuth usage endpoint and
613
- request header; response fields may change without notice.
614
- - Kimi For Coding quota uses the current `/usages` response shape exposed by the Kimi coding service; if Kimi changes that payload, window parsing may need to be updated.
615
- - State/chunk file writes refuse to write through symlinked targets (best-effort defense-in-depth).
616
- - The `OPENCODE_QUOTA_DATA_HOME` env var overrides the OpenCode data directory
617
- path (for testing); do not set this in production.
618
- - The `OPENCODE_QUOTA_CONFIG_HOME` env var overrides global config directory
619
- lookup (`<config-home>/opencode`).
620
- - The `OPENCODE_QUOTA_CONFIG` env var points to an explicit config file and
621
- applies as the highest-priority override.
306
+ - Node.js `>=18`
307
+ - OpenCode plugin SDK `@opencode-ai/plugin` / `@opencode-ai/sdk` `^1.3.5`
308
+ - For OpenCode `>=1.2.15`, TUI config belongs in `tui.json`
309
+ - The shared title is still one `session.title` value for all clients
310
+ - The plugin avoids ANSI styling in sidebar titles to keep resize behavior stable
311
+ - Some providers expose true quota windows, others only expose balance data
622
312
 
623
313
  ## Contributing
624
314
 
625
- Contributions are welcome — especially new quota provider connectors. See [CONTRIBUTING.md](CONTRIBUTING.md) for a step-by-step guide on adding support for a new provider.
315
+ - Changelog: [CHANGELOG.md](./CHANGELOG.md)
316
+ - Adapter and architecture notes: [CONTRIBUTING.md](./CONTRIBUTING.md)
317
+ - Security policy: [SECURITY.md](./SECURITY.md)
626
318
 
627
- ## License
319
+ ## Documentation Navigation
628
320
 
629
- MIT. See `LICENSE`.
321
+ - English README: [`README.md`](./README.md)
322
+ - Simplified Chinese README: [`README.zh-CN.md`](./README.zh-CN.md)
323
+ - Changelog: [`CHANGELOG.md`](./CHANGELOG.md)
324
+ - Contributing guide: [`CONTRIBUTING.md`](./CONTRIBUTING.md)
325
+ - Security policy: [`SECURITY.md`](./SECURITY.md)