@arandevcode/opencode-quota-extended 0.1.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.
Files changed (97) hide show
  1. package/LICENSE +21 -0
  2. package/README-es.md +153 -0
  3. package/README.md +154 -0
  4. package/bin/quota.mjs +105 -0
  5. package/dist/config/defaults.d.ts +16 -0
  6. package/dist/config/defaults.d.ts.map +1 -0
  7. package/dist/config/defaults.js +15 -0
  8. package/dist/config/defaults.js.map +1 -0
  9. package/dist/config/index.html +26 -0
  10. package/dist/config/loader.d.ts +10 -0
  11. package/dist/config/loader.d.ts.map +1 -0
  12. package/dist/config/loader.js +89 -0
  13. package/dist/config/loader.js.map +1 -0
  14. package/dist/config/schema.d.ts +16 -0
  15. package/dist/config/schema.d.ts.map +1 -0
  16. package/dist/config/schema.js +28 -0
  17. package/dist/config/schema.js.map +1 -0
  18. package/dist/index.d.ts +14 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.html +29 -0
  21. package/dist/index.js +8 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/lib/auth.d.ts +45 -0
  24. package/dist/lib/auth.d.ts.map +1 -0
  25. package/dist/lib/auth.js +68 -0
  26. package/dist/lib/auth.js.map +1 -0
  27. package/dist/lib/http.d.ts +15 -0
  28. package/dist/lib/http.d.ts.map +1 -0
  29. package/dist/lib/http.js +30 -0
  30. package/dist/lib/http.js.map +1 -0
  31. package/dist/lib/index.html +46 -0
  32. package/dist/lib/opencode-go-config.d.ts +22 -0
  33. package/dist/lib/opencode-go-config.d.ts.map +1 -0
  34. package/dist/lib/opencode-go-config.js +92 -0
  35. package/dist/lib/opencode-go-config.js.map +1 -0
  36. package/dist/lib/reset-format.d.ts +9 -0
  37. package/dist/lib/reset-format.d.ts.map +1 -0
  38. package/dist/lib/reset-format.js +83 -0
  39. package/dist/lib/reset-format.js.map +1 -0
  40. package/dist/lib/sanitize.d.ts +10 -0
  41. package/dist/lib/sanitize.d.ts.map +1 -0
  42. package/dist/lib/sanitize.js +14 -0
  43. package/dist/lib/sanitize.js.map +1 -0
  44. package/dist/lib/throttle.d.ts +18 -0
  45. package/dist/lib/throttle.d.ts.map +1 -0
  46. package/dist/lib/throttle.js +26 -0
  47. package/dist/lib/throttle.js.map +1 -0
  48. package/dist/lib/toast-format.d.ts +24 -0
  49. package/dist/lib/toast-format.d.ts.map +1 -0
  50. package/dist/lib/toast-format.js +79 -0
  51. package/dist/lib/toast-format.js.map +1 -0
  52. package/dist/lib/types.d.ts +50 -0
  53. package/dist/lib/types.d.ts.map +1 -0
  54. package/dist/lib/types.js +10 -0
  55. package/dist/lib/types.js.map +1 -0
  56. package/dist/providers/copilot.d.ts +3 -0
  57. package/dist/providers/copilot.d.ts.map +1 -0
  58. package/dist/providers/copilot.js +303 -0
  59. package/dist/providers/copilot.js.map +1 -0
  60. package/dist/providers/index.html +34 -0
  61. package/dist/providers/openai.d.ts +10 -0
  62. package/dist/providers/openai.d.ts.map +1 -0
  63. package/dist/providers/openai.js +152 -0
  64. package/dist/providers/openai.js.map +1 -0
  65. package/dist/providers/opencode-go.d.ts +12 -0
  66. package/dist/providers/opencode-go.d.ts.map +1 -0
  67. package/dist/providers/opencode-go.js +102 -0
  68. package/dist/providers/opencode-go.js.map +1 -0
  69. package/dist/providers/registry.d.ts +20 -0
  70. package/dist/providers/registry.d.ts.map +1 -0
  71. package/dist/providers/registry.js +76 -0
  72. package/dist/providers/registry.js.map +1 -0
  73. package/dist/providers/types.d.ts +27 -0
  74. package/dist/providers/types.d.ts.map +1 -0
  75. package/dist/providers/types.js +2 -0
  76. package/dist/providers/types.js.map +1 -0
  77. package/dist/server.d.ts +10 -0
  78. package/dist/server.d.ts.map +1 -0
  79. package/dist/server.js +4 -0
  80. package/dist/server.js.map +1 -0
  81. package/dist/tui.d.ts +7 -0
  82. package/dist/tui.d.ts.map +1 -0
  83. package/dist/tui.tsx +121 -0
  84. package/dist/ui/SidebarPanel.d.ts +10 -0
  85. package/dist/ui/SidebarPanel.d.ts.map +1 -0
  86. package/dist/ui/SidebarPanel.tsx +36 -0
  87. package/dist/ui/cards.d.ts +13 -0
  88. package/dist/ui/cards.d.ts.map +1 -0
  89. package/dist/ui/cards.tsx +117 -0
  90. package/dist/ui/index.html +24 -0
  91. package/dist/ui/progress-bar.d.ts +27 -0
  92. package/dist/ui/progress-bar.d.ts.map +1 -0
  93. package/dist/ui/progress-bar.js +48 -0
  94. package/dist/ui/progress-bar.js.map +1 -0
  95. package/images/opencode_quota_extended_cli.png +0 -0
  96. package/images/opencode_quota_extended_sidebar.png +0 -0
  97. package/package.json +74 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 arandevcode
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README-es.md ADDED
@@ -0,0 +1,153 @@
1
+ <p align="center">
2
+ <img src="images/opencode_quota_extended_sidebar.png" alt="Barra lateral TUI" width="420">
3
+ <br>
4
+ <sub><em>-sidebar example-</em></sub>
5
+ </p>
6
+
7
+ # @arandevcode/opencode-quota-extended
8
+
9
+ > 🇬🇧 Also available in [English](README.md)
10
+
11
+ Tarjetas de uso de cuota para la barra lateral TUI — barras de progreso por ventana con colores de semáforo para OpenAI, GitHub Copilot y OpenCode Go.
12
+
13
+ - Barra de progreso para cada ventana de cuota (5h móvil, semanal, mensual, code review...)
14
+ - Muestra cuándo se reinicia — tiempo restante y fecha exacta
15
+ - Indicador de salud por colores (verde = ok, naranja = advertencia, rojo = crítico)
16
+ - OpenCode Go capable!
17
+
18
+ Inspirado por [`@slkiser/opencode-quota`](https://github.com/slkiser/opencode-quota). Ambos coexisten sin conflicto.
19
+
20
+ <br>
21
+ <p align="center">🐟 <em>&ldquo;Quien quiera peces que se moje el culo&rdquo;</em> <sub>— para mi padre, que me enseñó a arremangarme y hacer que las cosas pasen</sub>
22
+ </p>
23
+
24
+ <p align="center">
25
+ <a href="https://buymeacoffee.com/arandevcode"><strong>☕ Invítame un café</strong></a>
26
+ <br>
27
+ <sub><a href="https://github.com/sponsors/arandevcode">❤️ Sponsor en GitHub</a></sub>
28
+ </p>
29
+
30
+ ## Instalación
31
+
32
+ ```sh
33
+ opencode plugin @arandevcode/opencode-quota-extended
34
+ ```
35
+
36
+ O instala desde GitHub para seguir el desarrollo más reciente:
37
+
38
+ ```json
39
+ // ~/.config/opencode/opencode.json
40
+ {
41
+ "plugin": [
42
+ "https://github.com/arandevcode/opencode-plugins/tree/main/opencode-quota-extended"
43
+ ]
44
+ }
45
+ ```
46
+
47
+ ## CLI
48
+
49
+ Visualiza la cuota desde la terminal sin abrir el TUI:
50
+
51
+ ```sh
52
+ npx opencode-quota # ejecutar vía npm (sin instalar)
53
+ opencode-quota # si está instalado globalmente
54
+ node bin/quota.mjs # desde el directorio del repo (sin build)
55
+ npm run quota # desde el directorio del repo
56
+ ```
57
+
58
+ <p align="center">
59
+ <img src="images/opencode_quota_extended_cli.png" alt="Salida CLI" width="620">
60
+ <br>
61
+ <sub><em>-cli example-</em></sub>
62
+ </p>
63
+
64
+ Colores de semáforo: verde (ok), naranja (advertencia), rojo (error).
65
+
66
+ ## Configuración
67
+
68
+ Archivo sidecar opcional en `~/.config/opencode/opencode-quota-extended.json`:
69
+
70
+ | Campo | Tipo | Por defecto | Descripción |
71
+ |---|---|---|---|
72
+ | `enabledProviders` | string[] | `["openai","github-copilot","opencode-go"]` | Proveedores a consultar |
73
+ | `refreshIntervalMs` | int | `60000` | Intervalo de sondeo |
74
+ | `cardsSidebar.order` | int | `145` | Posición en la barra lateral (menor = más arriba) |
75
+
76
+ ## Más Información
77
+
78
+ ### Cómo funciona
79
+
80
+ Registra un plugin TUI que renderiza un panel `sidebar_content` en la posición 145, mostrando tarjetas de cuota para cada proveedor activo. Cada proveedor obtiene datos de cuota independientemente desde su propia API.
81
+
82
+ | Proveedor | Ventanas | Fuente de autenticación |
83
+ |---|---|---|
84
+ | **OpenAI** | 5h móvil, Semanal, Code Review | `auth.json` → entrada `openai` / `chatgpt` |
85
+ | **GitHub Copilot** | Solicitudes Premium (ventana única) | `auth.json` → entrada `github-copilot` |
86
+ | **OpenCode Go** | 5h, Semanal, Mensual | Archivo de configuración o vars de entorno |
87
+
88
+ Colores de semáforo: ≥50% verde (`accent`), 25-49% amarillo (`warning`), <25% rojo (`error`).
89
+
90
+ ### Autenticación
91
+
92
+ Lee credenciales del archivo de autenticación de OpenCode (`~/.local/share/opencode/auth.json`). **No** almacena, solicita ni distribuye secretos.
93
+
94
+ **OpenCode Go** requiere configuración adicional — es un poco tricky porque no hay API oficial todavía.
95
+
96
+ Crea `~/.config/opencode/opencode-quota/opencode-go.json`:
97
+
98
+ ```json
99
+ {
100
+ "workspaceId": "wrk_abc123...",
101
+ "authCookie": "Fe26.2..."
102
+ }
103
+ ```
104
+
105
+ | Campo | Descripción | Cómo obtenerlo |
106
+ |---|---|---|
107
+ | `workspaceId` | ID de tu workspace en OpenCode | Entra en [opencode.ai/go](https://opencode.ai/go). La URL es `https://opencode.ai/workspace/wrk_XXX/go` — copia la parte `wrk_XXX`. |
108
+ | `authCookie` | Cookie de sesión para autenticación | Abre DevTools (F12) → Application → Cookies → `opencode.ai` → busca la cookie `auth` (empieza con `Fe26.2...`). O ejecuta en la consola: `document.cookie.match(/(?:^\|;\s*)auth=([^;]+)/)?.[1]` |
109
+
110
+ **Setup automatizado** (recomendado):
111
+ ```sh
112
+ npm run setup
113
+ ```
114
+ Te solicita ambos valores y crea el archivo automáticamente. También muestra un bookmarklet que puedes arrastrar a la barra de favoritos y pinchar en opencode.ai para extraer la auth cookie con un clic.
115
+
116
+ **Consejo:** mantén una pestaña del navegador con sesión iniciada en opencode.ai mientras usas este plugin — la auth cookie puede expirar.
117
+
118
+ ### Relación con [`@slkiser/opencode-quota`](https://github.com/slkiser/opencode-quota)
119
+
120
+ **Sin dependencia.** Ambos plugins obtienen cuota independientemente y renderizan sus propios paneles laterales. Puedes tener uno, ambos o ninguno — no interfieren.
121
+
122
+ ### Solución de problemas
123
+
124
+ - **El panel no aparece**: `grep '@arandevcode/opencode-quota-extended' ~/.local/share/opencode/log/*.log`
125
+ - **OpenCode Go muestra "skipped"**: confirma que el archivo sidecar o vars de entorno están configurados
126
+ - **Bug antiguo de pinning `@opentui/solid`**: actualiza a `^0.4.1` y reinstala limpio (`rm -rf node_modules && npm ci`)
127
+
128
+ ### Desarrollo
129
+
130
+ ```sh
131
+ npm run build # compilar TS + copiar assets TUI
132
+ npm run build:watch # modo watch
133
+ npm test # vitest
134
+ npm run typecheck # type-check sin emitir
135
+ ```
136
+
137
+ ### Estructura del proyecto
138
+
139
+ ```
140
+ src/
141
+ index.ts # Plugin module (entrada server)
142
+ server.ts # Plugin server (mínimo, sin herramientas)
143
+ tui.tsx # Plugin TUI: slot sidebar_content
144
+ config/ # defaults, schema, loader
145
+ lib/ # types, auth, http, formatting, credential resolver
146
+ providers/ # openai.ts, copilot.ts, opencode-go.ts, registry.ts
147
+ ui/ # SidebarPanel, cards, progress-bar
148
+ tests/ # tests unitarios vitest
149
+ ```
150
+
151
+ ## Licencia
152
+
153
+ MIT
package/README.md ADDED
@@ -0,0 +1,154 @@
1
+ <p align="center">
2
+ <img src="images/opencode_quota_extended_sidebar.png" alt="TUI sidebar" width="420">
3
+ <br>
4
+ <sub><em>-sidebar example-</em></sub>
5
+ </p>
6
+
7
+ # @arandevcode/opencode-quota-extended
8
+
9
+ > 🇪🇸 Also available in [Spanish](README-es.md)
10
+
11
+ Quota usage cards for the TUI sidebar — per-window progress bars with traffic-light colors for OpenAI, GitHub Copilot, and OpenCode Go.
12
+
13
+ - Progress bar for each quota window (5h rolling, weekly, monthly, code review...)
14
+ - Shows when it resets — time remaining and exact date
15
+ - Color-coded health indication (green = ok, orange = warning, red = critical)
16
+ - OpenCode Go capable!
17
+
18
+ Heavily inspired by [`@slkiser/opencode-quota`](https://github.com/slkiser/opencode-quota). Both coexist without conflict.
19
+
20
+ <br>
21
+ <p align="center">🐟 <em>&ldquo;Quien quiera peces que se moje el culo&rdquo;</em> >
22
+ <em>&ldquo;If you want fish, get your ass wet&rdquo;</em> <sub>— for my father, who taught me to roll up my sleeves and get it done</sub>
23
+ </p>
24
+
25
+ <p align="center">
26
+ <a href="https://buymeacoffee.com/arandevcode"><strong>☕ Buy me a coffee</strong></a>
27
+ <br>
28
+ <sub><a href="https://github.com/sponsors/arandevcode">❤️ Sponsor on GitHub</a></sub>
29
+ </p>
30
+
31
+ ## Install
32
+
33
+ ```sh
34
+ opencode plugin @arandevcode/opencode-quota-extended
35
+ ```
36
+
37
+ Or install from GitHub source to follow latest development:
38
+
39
+ ```json
40
+ // ~/.config/opencode/opencode.json
41
+ {
42
+ "plugin": [
43
+ "https://github.com/arandevcode/opencode-plugins/tree/main/opencode-quota-extended"
44
+ ]
45
+ }
46
+ ```
47
+
48
+ ## CLI
49
+
50
+ View quota from the terminal without opening the TUI:
51
+
52
+ ```sh
53
+ npx opencode-quota # run via npm (no install)
54
+ opencode-quota # if installed globally
55
+ node bin/quota.mjs # from the repo directory (no build needed)
56
+ npm run quota # from the repo directory
57
+ ```
58
+
59
+ <p align="center">
60
+ <img src="images/opencode_quota_extended_cli.png" alt="CLI output" width="620">
61
+ <br>
62
+ <sub><em>-cli example-</em></sub>
63
+ </p>
64
+
65
+ Traffic-light colors: green (ok), orange (warning), red (error).
66
+
67
+ ## Configuration
68
+
69
+ Optional sidecar at `~/.config/opencode/opencode-quota-extended.json`:
70
+
71
+ | Field | Type | Default | Description |
72
+ |---|---|---|---|
73
+ | `enabledProviders` | string[] | `["openai","github-copilot","opencode-go"]` | Providers to fetch |
74
+ | `refreshIntervalMs` | int | `60000` | Polling interval |
75
+ | `cardsSidebar.order` | int | `145` | Sidebar position (lower = higher) |
76
+
77
+ ## More Information
78
+
79
+ ### How it works
80
+
81
+ Registers a TUI plugin that renders a `sidebar_content` panel at order 145, showing quota cards for every enabled provider. Each provider fetches quota data independently from its own API.
82
+
83
+ | Provider | Windows | Auth source |
84
+ |---|---|---|
85
+ | **OpenAI** | 5h rolling, Weekly, Code Review | `auth.json` → `openai` / `chatgpt` entry |
86
+ | **GitHub Copilot** | Premium requests (single window) | `auth.json` → `github-copilot` entry |
87
+ | **OpenCode Go** | 5h, Weekly, Monthly | Config file or env vars |
88
+
89
+ Traffic-light colors: ≥50% green (`accent`), 25-49% yellow (`warning`), <25% red (`error`).
90
+
91
+ ### Authentication
92
+
93
+ Reads credentials from OpenCode's runtime auth file (`~/.local/share/opencode/auth.json`). Does **not** store, prompt for, or distribute secrets.
94
+
95
+ **OpenCode Go** requires additional setup — it's a bit tricky because there's no official API yet.
96
+
97
+ Create `~/.config/opencode/opencode-quota/opencode-go.json`:
98
+
99
+ ```json
100
+ {
101
+ "workspaceId": "wrk_abc123...",
102
+ "authCookie": "Fe26.2..."
103
+ }
104
+ ```
105
+
106
+ | Field | Description | How to get it |
107
+ |---|---|---|
108
+ | `workspaceId` | Your OpenCode workspace ID | Log in at [opencode.ai/go](https://opencode.ai/go). The URL is `https://opencode.ai/workspace/wrk_XXX/go` — copy the `wrk_XXX` part. |
109
+ | `authCookie` | Session cookie for auth | Open DevTools (F12) → Application → Cookies → `opencode.ai` → find the `auth` cookie (starts with `Fe26.2...`). Or run this in the console: `document.cookie.match(/(?:^\|;\s*)auth=([^;]+)/)?.[1]` |
110
+
111
+ **Automated setup** (recommended):
112
+ ```sh
113
+ npm run setup
114
+ ```
115
+ This prompts for both values and creates the file for you. It also prints a bookmarklet you can drag to your bookmarks bar and click while on opencode.ai to extract the auth cookie with one click.
116
+
117
+ **Tip:** keep a browser tab logged into opencode.ai while using this plugin — the auth cookie may expire.
118
+
119
+ ### Relationship with [`@slkiser/opencode-quota`](https://github.com/slkiser/opencode-quota)
120
+
121
+ **No dependency.** Both plugins fetch quota independently and render their own sidebar panels. You can have one, both, or none — they do not interfere.
122
+
123
+ ### Troubleshooting
124
+
125
+ - **Panel doesn't appear**: `grep '@arandevcode/opencode-quota-extended' ~/.local/share/opencode/log/*.log`
126
+ - **OpenCode Go shows "skipped"**: confirm sidecar file or env vars are set
127
+ - **Old `@opentui/solid` pinning bug**: update to `^0.4.1` and clean-install (`rm -rf node_modules && npm ci`)
128
+
129
+ ### Development
130
+
131
+ ```sh
132
+ npm run build # compile TS + copy TUI assets
133
+ npm run build:watch # watch mode
134
+ npm test # vitest
135
+ npm run typecheck # type-check without emitting
136
+ ```
137
+
138
+ ### Project structure
139
+
140
+ ```
141
+ src/
142
+ index.ts # Plugin module (server entry)
143
+ server.ts # Server plugin (minimal, no tools)
144
+ tui.tsx # TUI plugin: sidebar_content slot
145
+ config/ # defaults, schema, loader
146
+ lib/ # types, auth, http, formatting, credential resolver
147
+ providers/ # openai.ts, copilot.ts, opencode-go.ts, registry.ts
148
+ ui/ # SidebarPanel, cards, progress-bar
149
+ tests/ # vitest unit tests
150
+ ```
151
+
152
+ ## License
153
+
154
+ MIT
package/bin/quota.mjs ADDED
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { fileURLToPath } from "url";
4
+ import { dirname } from "path";
5
+ import { loadCardsConfig } from "../dist/config/loader.js";
6
+ import { collectSnapshot } from "../dist/providers/registry.js";
7
+ import { renderProgressBar, trafficLight } from "../dist/ui/progress-bar.js";
8
+ import { formatResetCombined } from "../dist/lib/reset-format.js";
9
+
10
+ const INDENT = 4;
11
+
12
+ const COLORS = {
13
+ ok: "\x1b[38;2;107;131;35m",
14
+ warning: "\x1b[38;2;255;165;0m",
15
+ error: "\x1b[38;2;255;107;107m",
16
+ };
17
+ const RESET = "\x1b[0m";
18
+ const BOLD = "\x1b[1m";
19
+ const DIM = "\x1b[2m";
20
+
21
+ function stripAnsi(s) {
22
+ return s.replace(/\x1b\[[0-9;]*m/g, "");
23
+ }
24
+ function cols() {
25
+ return process.stdout.columns ?? 80;
26
+ }
27
+ function usedPct(pct) {
28
+ return `${Math.round(100 - pct)}% used`;
29
+ }
30
+ function windowLabel(window) {
31
+ if (
32
+ typeof window.remaining === "number" &&
33
+ typeof window.total === "number" &&
34
+ Number.isFinite(window.remaining) &&
35
+ Number.isFinite(window.total)
36
+ ) {
37
+ return `${window.remaining}/${window.total}`;
38
+ }
39
+ return window.label;
40
+ }
41
+ function padRight(left, right) {
42
+ const gap = Math.max(2, cols() - INDENT - stripAnsi(left).length - stripAnsi(right).length);
43
+ return left + " ".repeat(gap) + right;
44
+ }
45
+
46
+ async function main() {
47
+ const __dirname = dirname(fileURLToPath(import.meta.url));
48
+
49
+ const loadResult = await loadCardsConfig();
50
+ const config = loadResult.config;
51
+ const barWidth = cols() - INDENT;
52
+
53
+ console.log(`\n${BOLD}Quota${RESET}\n`);
54
+
55
+ const snapshot = await collectSnapshot(config);
56
+
57
+ for (const provider of snapshot.providers) {
58
+ if (provider.status === "skipped") continue;
59
+
60
+ if (provider.status === "no-auth") {
61
+ console.log(` ${BOLD}${provider.label}${RESET} ${DIM}no auth${RESET}`);
62
+ continue;
63
+ }
64
+
65
+ if (provider.status === "error") {
66
+ console.log(
67
+ ` ${BOLD}${provider.label}${RESET} ${COLORS.error}${provider.error ?? "error"}${RESET}`,
68
+ );
69
+ continue;
70
+ }
71
+
72
+ if (provider.windows.length === 0) {
73
+ console.log(` ${BOLD}${provider.label}${RESET} ${DIM}no data${RESET}`);
74
+ continue;
75
+ }
76
+
77
+ console.log(` ${BOLD}${provider.label}${RESET}`);
78
+
79
+ for (const [i, win] of provider.windows.entries()) {
80
+ if (i > 0) console.log();
81
+ const pct = win.percentRemaining;
82
+ const light = trafficLight(pct);
83
+ const color = COLORS[light];
84
+ const bar = renderProgressBar(pct, barWidth);
85
+ const resetT = formatResetCombined(win.resetTimeIso);
86
+ const resetStyle = resetT ? (light === "error" ? `${color}${resetT}${RESET}` : `${DIM}${resetT}${RESET}`) : "";
87
+
88
+ const labelStr = windowLabel(win);
89
+ console.log(" ".repeat(INDENT) + padRight(labelStr, resetStyle));
90
+ console.log(" ".repeat(INDENT) + color + bar.filled + bar.empty + RESET);
91
+ const combined = `${usedPct(pct)} / `;
92
+ const leftStr = `${pct}% left`;
93
+ const pad = Math.max(0, cols() - INDENT - stripAnsi(combined + leftStr).length);
94
+ console.log(" ".repeat(INDENT + pad) + combined + color + leftStr + RESET);
95
+ }
96
+ console.log();
97
+ }
98
+
99
+ console.log(`${DIM}Generated at: ${snapshot.generatedAt}${RESET}`);
100
+ }
101
+
102
+ main().catch((err) => {
103
+ console.error("Error:", err.message);
104
+ process.exit(1);
105
+ });
@@ -0,0 +1,16 @@
1
+ export interface CardsPluginConfig {
2
+ enabled: boolean;
3
+ enabledProviders: Array<"openai" | "github-copilot" | "opencode-go">;
4
+ refreshIntervalMs: number;
5
+ requestTimeoutMs: number;
6
+ opencodeGoTimeoutMs: number;
7
+ showOnlyMultiWindow: boolean;
8
+ cardsSidebar: {
9
+ enabled: boolean;
10
+ order: number;
11
+ };
12
+ opencodeGoWindows: Array<"5h" | "weekly" | "monthly">;
13
+ }
14
+ export declare const DEFAULT_CONFIG: CardsPluginConfig;
15
+ export declare const SIDECAR_FILENAME = "opencode-quota-extended.json";
16
+ //# sourceMappingURL=defaults.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../../src/config/defaults.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,gBAAgB,EAAE,KAAK,CAAC,QAAQ,GAAG,gBAAgB,GAAG,aAAa,CAAC,CAAC;IACrE,iBAAiB,EAAE,MAAM,CAAC;IAC1B,gBAAgB,EAAE,MAAM,CAAC;IACzB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,mBAAmB,EAAE,OAAO,CAAC;IAC7B,YAAY,EAAE;QACZ,OAAO,EAAE,OAAO,CAAC;QACjB,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;IACF,iBAAiB,EAAE,KAAK,CAAC,IAAI,GAAG,QAAQ,GAAG,SAAS,CAAC,CAAC;CACvD;AAED,eAAO,MAAM,cAAc,EAAE,iBAY5B,CAAC;AAEF,eAAO,MAAM,gBAAgB,iCAAiC,CAAC"}
@@ -0,0 +1,15 @@
1
+ export const DEFAULT_CONFIG = {
2
+ enabled: true,
3
+ enabledProviders: ["openai", "github-copilot", "opencode-go"],
4
+ refreshIntervalMs: 60_000,
5
+ requestTimeoutMs: 8_000,
6
+ opencodeGoTimeoutMs: 10_000,
7
+ showOnlyMultiWindow: false,
8
+ cardsSidebar: {
9
+ enabled: true,
10
+ order: 145,
11
+ },
12
+ opencodeGoWindows: ["5h", "weekly", "monthly"],
13
+ };
14
+ export const SIDECAR_FILENAME = "opencode-quota-extended.json";
15
+ //# sourceMappingURL=defaults.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defaults.js","sourceRoot":"","sources":["../../src/config/defaults.ts"],"names":[],"mappings":"AAcA,MAAM,CAAC,MAAM,cAAc,GAAsB;IAC/C,OAAO,EAAE,IAAI;IACb,gBAAgB,EAAE,CAAC,QAAQ,EAAE,gBAAgB,EAAE,aAAa,CAAC;IAC7D,iBAAiB,EAAE,MAAM;IACzB,gBAAgB,EAAE,KAAK;IACvB,mBAAmB,EAAE,MAAM;IAC3B,mBAAmB,EAAE,KAAK;IAC1B,YAAY,EAAE;QACZ,OAAO,EAAE,IAAI;QACb,KAAK,EAAE,GAAG;KACX;IACD,iBAAiB,EAAE,CAAC,IAAI,EAAE,QAAQ,EAAE,SAAS,CAAC;CAC/C,CAAC;AAEF,MAAM,CAAC,MAAM,gBAAgB,GAAG,8BAA8B,CAAC"}
@@ -0,0 +1,26 @@
1
+ <!DOCTYPE HTML>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>Directory listing for /opencode-personal-projects/opencode-quota-extended/dist/config/</title>
6
+ </head>
7
+ <body>
8
+ <h1>Directory listing for /opencode-personal-projects/opencode-quota-extended/dist/config/</h1>
9
+ <hr>
10
+ <ul>
11
+ <li><a href="defaults.d.ts">defaults.d.ts</a></li>
12
+ <li><a href="defaults.d.ts.map">defaults.d.ts.map</a></li>
13
+ <li><a href="defaults.js">defaults.js</a></li>
14
+ <li><a href="defaults.js.map">defaults.js.map</a></li>
15
+ <li><a href="loader.d.ts">loader.d.ts</a></li>
16
+ <li><a href="loader.d.ts.map">loader.d.ts.map</a></li>
17
+ <li><a href="loader.js">loader.js</a></li>
18
+ <li><a href="loader.js.map">loader.js.map</a></li>
19
+ <li><a href="schema.d.ts">schema.d.ts</a></li>
20
+ <li><a href="schema.d.ts.map">schema.d.ts.map</a></li>
21
+ <li><a href="schema.js">schema.js</a></li>
22
+ <li><a href="schema.js.map">schema.js.map</a></li>
23
+ </ul>
24
+ <hr>
25
+ </body>
26
+ </html>
@@ -0,0 +1,10 @@
1
+ import { type CardsPluginConfig } from "./defaults.js";
2
+ export type LoadConfigSource = "defaults" | "cwd" | "opencode-config" | "xdg-config" | "xdg-config-fallback" | "env-override";
3
+ export interface LoadConfigResult {
4
+ config: CardsPluginConfig;
5
+ source: LoadConfigSource;
6
+ path: string | null;
7
+ error?: string;
8
+ }
9
+ export declare function loadCardsConfig(): Promise<LoadConfigResult>;
10
+ //# sourceMappingURL=loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/config/loader.ts"],"names":[],"mappings":"AAMA,OAAO,EAGL,KAAK,iBAAiB,EACvB,MAAM,eAAe,CAAC;AAGvB,MAAM,MAAM,gBAAgB,GACxB,UAAU,GACV,KAAK,GACL,iBAAiB,GACjB,YAAY,GACZ,qBAAqB,GACrB,cAAc,CAAC;AAEnB,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,iBAAiB,CAAC;IAC1B,MAAM,EAAE,gBAAgB,CAAC;IACzB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA8CD,wBAAsB,eAAe,IAAI,OAAO,CAAC,gBAAgB,CAAC,CAsDjE"}
@@ -0,0 +1,89 @@
1
+ import { access, readFile } from "fs/promises";
2
+ import { homedir } from "os";
3
+ import { join } from "path";
4
+ import { xdgConfig } from "xdg-basedir";
5
+ import { DEFAULT_CONFIG, SIDECAR_FILENAME, } from "./defaults.js";
6
+ import { parseCardsConfig } from "./schema.js";
7
+ function getOpencodeConfigDir() {
8
+ if (process.env.OPENCODE_CONFIG_DIR) {
9
+ return process.env.OPENCODE_CONFIG_DIR;
10
+ }
11
+ if (xdgConfig) {
12
+ return join(xdgConfig, "opencode");
13
+ }
14
+ return join(homedir(), ".config", "opencode");
15
+ }
16
+ async function fileExists(filePath) {
17
+ try {
18
+ await access(filePath);
19
+ return true;
20
+ }
21
+ catch {
22
+ return false;
23
+ }
24
+ }
25
+ async function tryLoad(filePath, source) {
26
+ try {
27
+ const raw = await readFile(filePath, "utf8");
28
+ const parsed = JSON.parse(raw);
29
+ const config = parseCardsConfig(parsed);
30
+ return { ok: true, result: { config, source, path: filePath } };
31
+ }
32
+ catch (err) {
33
+ const message = err instanceof Error ? err.message : String(err);
34
+ return { ok: false, error: `${source}: ${message}` };
35
+ }
36
+ }
37
+ export async function loadCardsConfig() {
38
+ const errors = [];
39
+ const candidates = [];
40
+ const envPath = process.env.OPENCODE_QUOTA_CARDS_CONFIG;
41
+ if (envPath) {
42
+ candidates.push({ path: envPath, source: "env-override" });
43
+ }
44
+ candidates.push({
45
+ path: join(process.cwd(), SIDECAR_FILENAME),
46
+ source: "cwd",
47
+ });
48
+ const opencodeConfigDir = getOpencodeConfigDir();
49
+ if (opencodeConfigDir) {
50
+ candidates.push({
51
+ path: join(opencodeConfigDir, SIDECAR_FILENAME),
52
+ source: "opencode-config",
53
+ });
54
+ }
55
+ if (xdgConfig) {
56
+ candidates.push({
57
+ path: join(xdgConfig, SIDECAR_FILENAME),
58
+ source: "xdg-config",
59
+ });
60
+ }
61
+ else {
62
+ candidates.push({
63
+ path: join(homedir(), ".config", SIDECAR_FILENAME),
64
+ source: "xdg-config-fallback",
65
+ });
66
+ }
67
+ for (const candidate of candidates) {
68
+ if (!(await fileExists(candidate.path)))
69
+ continue;
70
+ const attempt = await tryLoad(candidate.path, candidate.source);
71
+ if (attempt.ok) {
72
+ if (errors.length > 0) {
73
+ return { ...attempt.result, error: errors.join("; ") };
74
+ }
75
+ return attempt.result;
76
+ }
77
+ errors.push(attempt.error);
78
+ }
79
+ if (errors.length > 0) {
80
+ return {
81
+ config: DEFAULT_CONFIG,
82
+ source: "defaults",
83
+ path: null,
84
+ error: errors.join("; "),
85
+ };
86
+ }
87
+ return { config: DEFAULT_CONFIG, source: "defaults", path: null };
88
+ }
89
+ //# sourceMappingURL=loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.js","sourceRoot":"","sources":["../../src/config/loader.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Zod schemas for validating the plugin sidecar configuration file.
3
+ *
4
+ * The root schema `cardsConfigSchema` uses `.passthrough()` to allow
5
+ * documentation keys prefixed with `_` (e.g. `_comment`).
6
+ */
7
+ import { z } from "zod";
8
+ import type { CardsPluginConfig } from "./defaults.js";
9
+ export declare const WindowIdForOpenCodeGo: z.ZodEnum<{
10
+ "5h": "5h";
11
+ weekly: "weekly";
12
+ monthly: "monthly";
13
+ }>;
14
+ export declare const cardsConfigSchema: z.ZodType<CardsPluginConfig>;
15
+ export declare function parseCardsConfig(input: unknown): CardsPluginConfig;
16
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/config/schema.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAIvD,eAAO,MAAM,qBAAqB;;;;EAAsC,CAAC;AAkBzE,eAAO,MAAM,iBAAiB,EAAE,CAAC,CAAC,OAAO,CAAC,iBAAiB,CACU,CAAC;AAEtE,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,iBAAiB,CAElE"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Zod schemas for validating the plugin sidecar configuration file.
3
+ *
4
+ * The root schema `cardsConfigSchema` uses `.passthrough()` to allow
5
+ * documentation keys prefixed with `_` (e.g. `_comment`).
6
+ */
7
+ import { z } from "zod";
8
+ const providerIdSchema = z.enum(["openai", "github-copilot", "opencode-go"]);
9
+ export const WindowIdForOpenCodeGo = z.enum(["5h", "weekly", "monthly"]);
10
+ const cardsSidebarSchema = z.object({
11
+ enabled: z.boolean(),
12
+ order: z.number().int(),
13
+ });
14
+ const cardsConfigBaseSchema = z.object({
15
+ enabled: z.boolean(),
16
+ enabledProviders: z.array(providerIdSchema).min(1),
17
+ refreshIntervalMs: z.coerce.number().int().positive(),
18
+ requestTimeoutMs: z.coerce.number().int().positive(),
19
+ opencodeGoTimeoutMs: z.coerce.number().int().positive(),
20
+ showOnlyMultiWindow: z.boolean(),
21
+ cardsSidebar: cardsSidebarSchema,
22
+ opencodeGoWindows: z.array(WindowIdForOpenCodeGo).min(1),
23
+ });
24
+ export const cardsConfigSchema = cardsConfigBaseSchema.passthrough();
25
+ export function parseCardsConfig(input) {
26
+ return cardsConfigSchema.parse(input);
27
+ }
28
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/config/schema.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,MAAM,gBAAgB,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,gBAAgB,EAAE,aAAa,CAAC,CAAC,CAAC;AAE7E,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC;AAEzE,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IAClC,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE;IACpB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE;CACxB,CAAC,CAAC;AAEH,MAAM,qBAAqB,GAAG,CAAC,CAAC,MAAM,CAAC;IACrC,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE;IACpB,gBAAgB,EAAE,CAAC,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAClD,iBAAiB,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;IACrD,gBAAgB,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;IACpD,mBAAmB,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;IACvD,mBAAmB,EAAE,CAAC,CAAC,OAAO,EAAE;IAChC,YAAY,EAAE,kBAAkB;IAChC,iBAAiB,EAAE,CAAC,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;CACzD,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,iBAAiB,GAC5B,qBAAqB,CAAC,WAAW,EAAkC,CAAC;AAEtE,MAAM,UAAU,gBAAgB,CAAC,KAAc;IAC7C,OAAO,iBAAiB,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;AACxC,CAAC"}