@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
@@ -0,0 +1,152 @@
1
+ import { readAuthFileCached } from "../lib/auth.js";
2
+ import { fetchWithTimeout } from "../lib/http.js";
3
+ import { clampPercent } from "../lib/reset-format.js";
4
+ const USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
5
+ const USER_AGENT = "opencode-quota-extended/0.1.0";
6
+ function pickAuthEntry(auth) {
7
+ if (!auth)
8
+ return null;
9
+ const candidates = [auth.openai, auth.codex, auth.chatgpt];
10
+ for (const entry of candidates) {
11
+ if (!entry)
12
+ continue;
13
+ if (!entry.access || typeof entry.access !== "string" || entry.access.length === 0)
14
+ continue;
15
+ if (entry.type === "api")
16
+ continue;
17
+ return entry;
18
+ }
19
+ return null;
20
+ }
21
+ function buildLabel(planType) {
22
+ if (typeof planType !== "string" || planType.length === 0)
23
+ return "OpenAI";
24
+ const lower = planType.toLowerCase();
25
+ if (lower.includes("pro"))
26
+ return "OpenAI (Pro)";
27
+ if (lower.includes("plus"))
28
+ return "OpenAI (Plus)";
29
+ return `OpenAI (${planType})`;
30
+ }
31
+ function windowResetIso(win) {
32
+ if (!win)
33
+ return undefined;
34
+ const resetAt = win.reset_at;
35
+ if (typeof resetAt === "number" && resetAt > 0) {
36
+ return new Date(resetAt * 1000).toISOString();
37
+ }
38
+ const resetAfter = win.reset_after_seconds;
39
+ if (typeof resetAfter === "number" && resetAfter > 0) {
40
+ return new Date(Date.now() + resetAfter * 1000).toISOString();
41
+ }
42
+ return undefined;
43
+ }
44
+ function windowFromData(id, label, win) {
45
+ if (!win)
46
+ return null;
47
+ const used = win.used_percent;
48
+ if (typeof used !== "number")
49
+ return null;
50
+ return {
51
+ id,
52
+ label,
53
+ percentRemaining: clampPercent(100 - used),
54
+ resetTimeIso: windowResetIso(win),
55
+ };
56
+ }
57
+ async function readResponseText(res) {
58
+ try {
59
+ return await res.text();
60
+ }
61
+ catch {
62
+ return "";
63
+ }
64
+ }
65
+ export const openaiProvider = {
66
+ id: "openai",
67
+ defaultLabel: "OpenAI",
68
+ async isAvailable(_ctx) {
69
+ const auth = await readAuthFileCached(5_000);
70
+ return pickAuthEntry(auth) !== null;
71
+ },
72
+ async fetch(ctx) {
73
+ const fetchedAt = new Date().toISOString();
74
+ try {
75
+ const auth = await readAuthFileCached(5_000);
76
+ const entry = pickAuthEntry(auth);
77
+ if (!entry) {
78
+ return {
79
+ id: "openai",
80
+ label: "OpenAI",
81
+ status: "no-auth",
82
+ windows: [],
83
+ fetchedAt,
84
+ };
85
+ }
86
+ if (typeof entry.expires === "number" && entry.expires < Date.now()) {
87
+ return {
88
+ id: "openai",
89
+ label: "OpenAI",
90
+ status: "error",
91
+ error: "Token expired",
92
+ windows: [],
93
+ fetchedAt,
94
+ };
95
+ }
96
+ const headers = {
97
+ Authorization: `Bearer ${entry.access}`,
98
+ "User-Agent": USER_AGENT,
99
+ };
100
+ if (typeof entry.accountId === "string" && entry.accountId.length > 0) {
101
+ headers["ChatGPT-Account-Id"] = entry.accountId;
102
+ }
103
+ const res = await fetchWithTimeout(USAGE_URL, { headers }, ctx.requestTimeoutMs);
104
+ if (!res.ok) {
105
+ const text = await readResponseText(res);
106
+ const snippet = text.slice(0, 120);
107
+ return {
108
+ id: "openai",
109
+ label: "OpenAI",
110
+ status: "error",
111
+ error: `OpenAI API error ${res.status}: ${snippet}`,
112
+ windows: [],
113
+ fetchedAt,
114
+ };
115
+ }
116
+ const data = (await res.json());
117
+ const planType = (data.plan_type ?? null);
118
+ const label = buildLabel(planType);
119
+ const rateLimit = (data.rate_limit ?? null);
120
+ const codeReview = (data.code_review_rate_limit ?? null);
121
+ const windows = [];
122
+ const primary = windowFromData("5h", "5h", rateLimit?.primary_window);
123
+ if (primary)
124
+ windows.push(primary);
125
+ const secondary = windowFromData("weekly", "Weekly", rateLimit?.secondary_window);
126
+ if (secondary)
127
+ windows.push(secondary);
128
+ const codeReviewPrimary = windowFromData("code-review", "Code Review", codeReview?.primary_window);
129
+ if (codeReviewPrimary)
130
+ windows.push(codeReviewPrimary);
131
+ return {
132
+ id: "openai",
133
+ label,
134
+ status: "ok",
135
+ windows,
136
+ fetchedAt,
137
+ };
138
+ }
139
+ catch (err) {
140
+ const message = err instanceof Error ? err.message : String(err);
141
+ return {
142
+ id: "openai",
143
+ label: "OpenAI",
144
+ status: "error",
145
+ error: message.slice(0, 200),
146
+ windows: [],
147
+ fetchedAt,
148
+ };
149
+ }
150
+ },
151
+ };
152
+ //# sourceMappingURL=openai.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai.js","sourceRoot":"","sources":["../../src/providers/openai.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * OpenCode Go quota provider.
3
+ *
4
+ * Fetches usage data by scraping the opencode.ai workspace dashboard.
5
+ * Requires a workspace ID and auth cookie (either via env vars
6
+ * OPENCODE_GO_WORKSPACE_ID / OPENCODE_GO_AUTH_COOKIE, or a sidecar file at
7
+ * ~/.config/opencode/opencode-quota/opencode-go.json).
8
+ * Returns up to 3 windows: 5h (rolling), Weekly, Monthly.
9
+ */
10
+ import type { Provider } from "./types.js";
11
+ export declare const opencodeGoProvider: Provider;
12
+ //# sourceMappingURL=opencode-go.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opencode-go.d.ts","sourceRoot":"","sources":["../../src/providers/opencode-go.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAmB,MAAM,YAAY,CAAC;AAyC5D,eAAO,MAAM,kBAAkB,EAAE,QA6EhC,CAAC"}
@@ -0,0 +1,102 @@
1
+ import { resolveOpenCodeGoConfigCached } from "../lib/opencode-go-config.js";
2
+ import { fetchWithTimeout } from "../lib/http.js";
3
+ import { clampPercent } from "../lib/reset-format.js";
4
+ const DASHBOARD_URL_PREFIX = "https://opencode.ai/workspace/";
5
+ const DASHBOARD_URL_SUFFIX = "/go";
6
+ const USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) Gecko/20100101 Firefox/148.0";
7
+ const WINDOWS = [
8
+ { field: "rollingUsage", id: "5h", label: "5h" },
9
+ { field: "weeklyUsage", id: "weekly", label: "Weekly" },
10
+ { field: "monthlyUsage", id: "monthly", label: "Monthly" },
11
+ ];
12
+ const RE_FIELD_PCT_FIRST = (field) => new RegExp(`${field}[^=]*=\\s*\\{[^}]*?usagePercent\\s*:\\s*(\\d+)[^}]*?resetInSec\\s*:\\s*(\\d+)`);
13
+ const RE_FIELD_RESET_FIRST = (field) => new RegExp(`${field}[^=]*=\\s*\\{[^}]*?resetInSec\\s*:\\s*(\\d+)[^}]*?usagePercent\\s*:\\s*(\\d+)`);
14
+ function parseWindow(html, field) {
15
+ const m1 = html.match(RE_FIELD_PCT_FIRST(field));
16
+ if (m1 && m1[1] !== undefined && m1[2] !== undefined) {
17
+ return { usagePercent: Number(m1[1]), resetInSec: Number(m1[2]) };
18
+ }
19
+ const m2 = html.match(RE_FIELD_RESET_FIRST(field));
20
+ if (m2 && m2[1] !== undefined && m2[2] !== undefined) {
21
+ return { usagePercent: Number(m2[2]), resetInSec: Number(m2[1]) };
22
+ }
23
+ return null;
24
+ }
25
+ export const opencodeGoProvider = {
26
+ id: "opencode-go",
27
+ defaultLabel: "OpenCode Go",
28
+ async isAvailable() {
29
+ const r = await resolveOpenCodeGoConfigCached();
30
+ return r.state === "configured";
31
+ },
32
+ async fetch(ctx) {
33
+ const fetchedAt = new Date().toISOString();
34
+ const base = {
35
+ id: "opencode-go",
36
+ label: "OpenCode Go",
37
+ status: "no-auth",
38
+ windows: [],
39
+ fetchedAt,
40
+ };
41
+ const r = await resolveOpenCodeGoConfigCached();
42
+ if (r.state === "none")
43
+ return { ...base, status: "skipped" };
44
+ if (r.state === "incomplete") {
45
+ return {
46
+ ...base,
47
+ status: "error",
48
+ error: `Missing ${r.missing} (source: ${r.source})`,
49
+ };
50
+ }
51
+ if (r.state === "invalid") {
52
+ return { ...base, status: "error", error: `Invalid config: ${r.error}` };
53
+ }
54
+ const { workspaceId, authCookie } = r.config;
55
+ try {
56
+ const url = `${DASHBOARD_URL_PREFIX}${encodeURIComponent(workspaceId)}${DASHBOARD_URL_SUFFIX}`;
57
+ const res = await fetchWithTimeout(url, {
58
+ method: "GET",
59
+ headers: {
60
+ "User-Agent": USER_AGENT,
61
+ Accept: "text/html",
62
+ Cookie: `auth=${authCookie}`,
63
+ },
64
+ }, ctx.requestTimeoutMs);
65
+ if (!res.ok) {
66
+ const text = (await res.text()).slice(0, 120);
67
+ return {
68
+ ...base,
69
+ status: "error",
70
+ error: `OpenCode Go dashboard error ${res.status}: ${text}`,
71
+ };
72
+ }
73
+ const html = await res.text();
74
+ const now = Date.now();
75
+ const windows = [];
76
+ for (const spec of WINDOWS) {
77
+ const parsed = parseWindow(html, spec.field);
78
+ if (!parsed)
79
+ continue;
80
+ windows.push({
81
+ id: spec.id,
82
+ label: spec.label,
83
+ percentRemaining: clampPercent(100 - parsed.usagePercent),
84
+ resetTimeIso: new Date(now + parsed.resetInSec * 1000).toISOString(),
85
+ });
86
+ }
87
+ if (windows.length === 0) {
88
+ return {
89
+ ...base,
90
+ status: "error",
91
+ error: "Could not parse any known OpenCode Go dashboard usage windows",
92
+ };
93
+ }
94
+ return { ...base, status: "ok", windows };
95
+ }
96
+ catch (err) {
97
+ const message = err instanceof Error ? err.message : String(err);
98
+ return { ...base, status: "error", error: message.slice(0, 200) };
99
+ }
100
+ },
101
+ };
102
+ //# sourceMappingURL=opencode-go.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opencode-go.js","sourceRoot":"","sources":["../../src/providers/opencode-go.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Provider registry.
3
+ *
4
+ * Holds the list of available providers and exposes a single
5
+ * `collectSnapshot()` that fans out fetches in parallel and returns
6
+ * a CardsSnapshot suitable for the TUI sidebar and the server tool.
7
+ */
8
+ import type { CardsPluginConfig } from "../config/defaults.js";
9
+ import type { Provider, ProviderContext } from "./types.js";
10
+ import type { CardsSnapshot } from "../lib/types.js";
11
+ export declare function getProvider(id: string): Provider | undefined;
12
+ export declare function listProviders(): Provider[];
13
+ /** Build a ProviderContext for a specific provider id. */
14
+ export declare function buildContext(config: CardsPluginConfig, providerId: string): ProviderContext;
15
+ /**
16
+ * Collect a snapshot by fanning out fetches in parallel for all
17
+ * enabled providers that are available.
18
+ */
19
+ export declare function collectSnapshot(config: CardsPluginConfig): Promise<CardsSnapshot>;
20
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/providers/registry.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC5D,OAAO,KAAK,EAAE,aAAa,EAAiB,MAAM,iBAAiB,CAAC;AAYpE,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAE5D;AAED,wBAAgB,aAAa,IAAI,QAAQ,EAAE,CAE1C;AAED,0DAA0D;AAC1D,wBAAgB,YAAY,CAC1B,MAAM,EAAE,iBAAiB,EACzB,UAAU,EAAE,MAAM,GACjB,eAAe,CAMjB;AAED;;;GAGG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,iBAAiB,GACxB,OAAO,CAAC,aAAa,CAAC,CAsDxB"}
@@ -0,0 +1,76 @@
1
+ import { openaiProvider } from "./openai.js";
2
+ import { copilotProvider } from "./copilot.js";
3
+ import { opencodeGoProvider } from "./opencode-go.js";
4
+ const ALL_PROVIDERS = [
5
+ openaiProvider,
6
+ copilotProvider,
7
+ opencodeGoProvider,
8
+ ];
9
+ export function getProvider(id) {
10
+ return ALL_PROVIDERS.find((p) => p.id === id);
11
+ }
12
+ export function listProviders() {
13
+ return ALL_PROVIDERS.slice();
14
+ }
15
+ /** Build a ProviderContext for a specific provider id. */
16
+ export function buildContext(config, providerId) {
17
+ const requestTimeoutMs = providerId === "opencode-go"
18
+ ? config.opencodeGoTimeoutMs
19
+ : config.requestTimeoutMs;
20
+ return { requestTimeoutMs };
21
+ }
22
+ /**
23
+ * Collect a snapshot by fanning out fetches in parallel for all
24
+ * enabled providers that are available.
25
+ */
26
+ export async function collectSnapshot(config) {
27
+ const providers = [];
28
+ const tasks = config.enabledProviders.map(async (id) => {
29
+ const provider = getProvider(id);
30
+ if (!provider)
31
+ return;
32
+ const ctx = buildContext(config, id);
33
+ let available = false;
34
+ try {
35
+ available = await provider.isAvailable(ctx);
36
+ }
37
+ catch {
38
+ available = false;
39
+ }
40
+ if (!available) {
41
+ providers.push({
42
+ id: provider.id,
43
+ label: provider.defaultLabel,
44
+ status: "skipped",
45
+ windows: [],
46
+ fetchedAt: new Date().toISOString(),
47
+ });
48
+ return;
49
+ }
50
+ try {
51
+ const quota = await provider.fetch(ctx);
52
+ providers.push(quota);
53
+ }
54
+ catch (err) {
55
+ providers.push({
56
+ id: provider.id,
57
+ label: provider.defaultLabel,
58
+ status: "error",
59
+ error: err instanceof Error ? err.message : String(err),
60
+ windows: [],
61
+ fetchedAt: new Date().toISOString(),
62
+ });
63
+ }
64
+ });
65
+ await Promise.all(tasks);
66
+ providers.sort((a, b) => {
67
+ const order = config.enabledProviders.indexOf(a.id);
68
+ const orderB = config.enabledProviders.indexOf(b.id);
69
+ return order - orderB;
70
+ });
71
+ return {
72
+ generatedAt: new Date().toISOString(),
73
+ providers,
74
+ };
75
+ }
76
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/providers/registry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Provider interface and shared context.
3
+ */
4
+ import type { ProviderQuota } from "../lib/types.js";
5
+ /** Input passed to a provider's fetch() method. */
6
+ export interface ProviderContext {
7
+ /** Per-request timeout in ms. */
8
+ requestTimeoutMs: number;
9
+ }
10
+ /** A provider fetches quota data from a remote API. */
11
+ export interface Provider {
12
+ /** Stable id, e.g. "openai", "github-copilot", "opencode-go". */
13
+ id: string;
14
+ /** Display name when no label override is available. */
15
+ defaultLabel: string;
16
+ /**
17
+ * Best-effort availability check.
18
+ * Should return true if the provider has credentials configured.
19
+ */
20
+ isAvailable(ctx: ProviderContext): Promise<boolean>;
21
+ /**
22
+ * Fetch quota data.
23
+ * Returns a ProviderQuota with status="no-auth" | "error" | "ok".
24
+ */
25
+ fetch(ctx: ProviderContext): Promise<ProviderQuota>;
26
+ }
27
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/providers/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAErD,mDAAmD;AACnD,MAAM,WAAW,eAAe;IAC9B,iCAAiC;IACjC,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,uDAAuD;AACvD,MAAM,WAAW,QAAQ;IACvB,iEAAiE;IACjE,EAAE,EAAE,MAAM,CAAC;IACX,wDAAwD;IACxD,YAAY,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,WAAW,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACpD;;;OAGG;IACH,KAAK,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;CACrD"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/providers/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * OpenCode Quota Cards — server plugin (minimal).
3
+ *
4
+ * This plugin is primarily a TUI sidebar plugin. The server module
5
+ * is kept registered for compatibility but provides no additional
6
+ * tools or commands.
7
+ */
8
+ import type { Plugin } from "@opencode-ai/plugin";
9
+ export declare const quotaCardsServerPlugin: Plugin;
10
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,MAAM,EAAS,MAAM,qBAAqB,CAAC;AAEzD,eAAO,MAAM,sBAAsB,EAAE,MAEpC,CAAC"}
package/dist/server.js ADDED
@@ -0,0 +1,4 @@
1
+ export const quotaCardsServerPlugin = async () => {
2
+ return {};
3
+ };
4
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AASA,MAAM,CAAC,MAAM,sBAAsB,GAAW,KAAK,IAAoB,EAAE;IACvE,OAAO,EAAE,CAAC;AACZ,CAAC,CAAC"}
package/dist/tui.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ import type { TuiPlugin, TuiPluginModule } from "@opencode-ai/plugin/tui";
2
+ export declare const quotaCardsTuiPlugin: TuiPlugin;
3
+ declare const pluginModule: TuiPluginModule & {
4
+ id: string;
5
+ };
6
+ export default pluginModule;
7
+ //# sourceMappingURL=tui.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tui.d.ts","sourceRoot":"","sources":["../src/tui.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAgB,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAWxF,eAAO,MAAM,mBAAmB,EAAE,SAoGjC,CAAC;AAEF,QAAA,MAAM,YAAY,EAAE,eAAe,GAAG;IAAE,EAAE,EAAE,MAAM,CAAA;CAGjD,CAAC;AAEF,eAAe,YAAY,CAAC"}
package/dist/tui.tsx ADDED
@@ -0,0 +1,121 @@
1
+ /** @jsxImportSource @opentui/solid */
2
+ import { createSignal, onCleanup } from "solid-js";
3
+ import type { TuiPlugin, TuiPluginApi, TuiPluginModule } from "@opencode-ai/plugin/tui";
4
+ import { loadCardsConfig } from "./config/loader.js";
5
+ import { collectSnapshot } from "./providers/registry.js";
6
+ import { SidebarPanel } from "./ui/SidebarPanel.js";
7
+
8
+ const REFRESH_INTERVAL_MS = 60_000;
9
+ const EVENT_REFRESH_DELAYS_MS = [150, 600] as const;
10
+ const MOUNT_RECOVERY_DELAYS_MS = [500, 1500, 4000] as const;
11
+
12
+ type CardsSnapshot = Awaited<ReturnType<typeof collectSnapshot>>;
13
+
14
+ export const quotaCardsTuiPlugin: TuiPlugin = async (api, _options, _meta) => {
15
+ try {
16
+ const loaded = await loadCardsConfig();
17
+ const config = loaded.config;
18
+
19
+ if (!config.enabled || !config.cardsSidebar.enabled) {
20
+ return;
21
+ }
22
+
23
+ const sessionResources = new Map<string, {
24
+ snapshot: () => CardsSnapshot;
25
+ dispose: () => void;
26
+ }>();
27
+
28
+ function acquire(sessionID: string) {
29
+ const existing = sessionResources.get(sessionID);
30
+ if (existing) return existing;
31
+
32
+ const [snapshot, setSnapshot] = createSignal<CardsSnapshot>({
33
+ generatedAt: new Date(0).toISOString(),
34
+ providers: [],
35
+ });
36
+
37
+ let inFlight = false;
38
+ let queued = false;
39
+ let version = 0;
40
+ const timers = new Set<ReturnType<typeof setTimeout>>();
41
+
42
+ const reload = () => {
43
+ if (inFlight) {
44
+ queued = true;
45
+ version += 1;
46
+ return;
47
+ }
48
+ inFlight = true;
49
+ const v = ++version;
50
+ collectSnapshot(config).then((next) => {
51
+ if (v === version) setSnapshot(next);
52
+ }).catch(() => {}).finally(() => {
53
+ inFlight = false;
54
+ if (queued) { queued = false; reload(); }
55
+ });
56
+ };
57
+
58
+ const queueRefresh = (delay: number) => {
59
+ const t = setTimeout(() => { timers.delete(t); reload(); }, delay);
60
+ timers.add(t);
61
+ };
62
+ const scheduleRefresh = () => {
63
+ for (const d of EVENT_REFRESH_DELAYS_MS) queueRefresh(d);
64
+ };
65
+ const scheduleMountRecovery = () => {
66
+ for (const d of MOUNT_RECOVERY_DELAYS_MS) queueRefresh(d);
67
+ };
68
+
69
+ const interval = setInterval(reload, config.refreshIntervalMs ?? REFRESH_INTERVAL_MS);
70
+ const unsubs = [
71
+ api.event.on("session.updated", (e) => { if (e?.properties?.info?.id === sessionID) scheduleRefresh(); }),
72
+ api.event.on("message.updated", (e) => { if (e?.properties?.info?.sessionID === sessionID) scheduleRefresh(); }),
73
+ api.event.on("message.removed", (e) => { if (e?.properties?.sessionID === sessionID) scheduleRefresh(); }),
74
+ api.event.on("tui.session.select", (e) => { if (e?.properties?.sessionID === sessionID) scheduleRefresh(); }),
75
+ ];
76
+
77
+ reload();
78
+ scheduleMountRecovery();
79
+
80
+ const dispose = () => {
81
+ clearInterval(interval);
82
+ for (const t of timers) clearTimeout(t);
83
+ timers.clear();
84
+ for (const u of unsubs) u();
85
+ sessionResources.delete(sessionID);
86
+ };
87
+
88
+ const resource = { snapshot, dispose };
89
+ sessionResources.set(sessionID, resource);
90
+ return resource;
91
+ }
92
+
93
+ function SidebarContentView(props: { sessionID: string }) {
94
+ const r = acquire(props.sessionID);
95
+ onCleanup(() => {
96
+ r.dispose();
97
+ });
98
+
99
+ return <SidebarPanel api={api} config={config} snapshot={r.snapshot} />;
100
+ }
101
+
102
+ api.slots.register({
103
+ order: config.cardsSidebar.order,
104
+ slots: {
105
+ sidebar_content(_ctx: unknown, props: { session_id: string }) {
106
+ return <SidebarContentView sessionID={props.session_id} />;
107
+ },
108
+ },
109
+ });
110
+ } catch (err) {
111
+ const msg = err instanceof Error ? `${err.message}\n${err.stack}` : String(err);
112
+ try { process.stderr.write(`[quota-cards] tui plugin: FATAL ${msg}\n`); } catch {}
113
+ }
114
+ };
115
+
116
+ const pluginModule: TuiPluginModule & { id: string } = {
117
+ id: "@local/opencode-quota-extended",
118
+ tui: quotaCardsTuiPlugin,
119
+ };
120
+
121
+ export default pluginModule;
@@ -0,0 +1,10 @@
1
+ import type { JSX } from "@opentui/solid";
2
+ import type { TuiPluginApi } from "@opencode-ai/plugin/tui";
3
+ import type { CardsPluginConfig } from "../config/defaults.js";
4
+ import type { CardsSnapshot } from "../lib/types.js";
5
+ export declare function SidebarPanel(props: {
6
+ api: TuiPluginApi;
7
+ config: CardsPluginConfig;
8
+ snapshot: () => CardsSnapshot;
9
+ }): JSX.Element;
10
+ //# sourceMappingURL=SidebarPanel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SidebarPanel.d.ts","sourceRoot":"","sources":["../../src/ui/SidebarPanel.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGrD,wBAAgB,YAAY,CAAC,KAAK,EAAE;IAClC,GAAG,EAAE,YAAY,CAAC;IAClB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,QAAQ,EAAE,MAAM,aAAa,CAAC;CAC/B,GAAG,GAAG,CAAC,OAAO,CAsBd"}
@@ -0,0 +1,36 @@
1
+ /** @jsxImportSource @opentui/solid */
2
+ import { For } from "solid-js";
3
+ import { TextAttributes } from "@opentui/core";
4
+ import type { JSX } from "@opentui/solid";
5
+ import type { TuiPluginApi } from "@opencode-ai/plugin/tui";
6
+ import type { CardsPluginConfig } from "../config/defaults.js";
7
+ import type { CardsSnapshot } from "../lib/types.js";
8
+ import { ProviderCards } from "./cards.js";
9
+
10
+ export function SidebarPanel(props: {
11
+ api: TuiPluginApi;
12
+ config: CardsPluginConfig;
13
+ snapshot: () => CardsSnapshot;
14
+ }): JSX.Element {
15
+ const theme = () => props.api.theme.current;
16
+ const titleColor = () => theme().text;
17
+
18
+ const providerCount = () =>
19
+ props.snapshot().providers.filter((p) => p.status === "ok").length;
20
+ const totalCount = () => props.snapshot().providers.length;
21
+
22
+ return (
23
+ <box flexDirection="column" gap={0}>
24
+ <box flexDirection="row" gap={1}>
25
+ <text fg={titleColor()} attributes={TextAttributes.BOLD}>
26
+ Quota ({providerCount()}/{totalCount()})
27
+ </text>
28
+ </box>
29
+ <box flexDirection="column" gap={1}>
30
+ <For each={props.snapshot().providers}>
31
+ {(p) => <ProviderCards api={props.api} provider={p} showOnlyMultiWindow={props.config.showOnlyMultiWindow} />}
32
+ </For>
33
+ </box>
34
+ </box>
35
+ );
36
+ }