@arandevcode/opencode-quota-extended 0.2.2 → 0.2.4

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 (49) hide show
  1. package/README-es.md +19 -13
  2. package/README.md +18 -12
  3. package/bin/quota.mjs +8 -1
  4. package/dist/lib/balance.d.ts +16 -0
  5. package/dist/lib/balance.d.ts.map +1 -0
  6. package/dist/lib/balance.js +45 -0
  7. package/dist/lib/balance.js.map +1 -0
  8. package/dist/lib/opencode-go-config.d.ts +6 -1
  9. package/dist/lib/opencode-go-config.d.ts.map +1 -1
  10. package/dist/lib/opencode-go-config.js +29 -3
  11. package/dist/lib/opencode-go-config.js.map +1 -1
  12. package/dist/lib/pace.d.ts +28 -0
  13. package/dist/lib/pace.d.ts.map +1 -0
  14. package/dist/lib/pace.js +46 -0
  15. package/dist/lib/pace.js.map +1 -0
  16. package/dist/lib/refresh-scheduler.d.ts +20 -0
  17. package/dist/lib/refresh-scheduler.d.ts.map +1 -0
  18. package/dist/lib/refresh-scheduler.js +57 -0
  19. package/dist/lib/refresh-scheduler.js.map +1 -0
  20. package/dist/lib/types.d.ts +19 -0
  21. package/dist/lib/types.d.ts.map +1 -1
  22. package/dist/providers/opencode-go.d.ts +8 -4
  23. package/dist/providers/opencode-go.d.ts.map +1 -1
  24. package/dist/providers/opencode-go.js +167 -43
  25. package/dist/providers/opencode-go.js.map +1 -1
  26. package/dist/tui.d.ts.map +1 -1
  27. package/dist/tui.tsx +58 -83
  28. package/dist/ui/SidebarPanel.d.ts.map +1 -1
  29. package/dist/ui/SidebarPanel.tsx +2 -13
  30. package/dist/ui/cards.d.ts.map +1 -1
  31. package/dist/ui/cards.tsx +24 -4
  32. package/images/opencode_quota_extended_cli.png +0 -0
  33. package/images/opencode_quota_extended_sidebar.png +0 -0
  34. package/package.json +7 -3
  35. package/scripts/clean.mjs +8 -0
  36. package/scripts/postbuild.mjs +56 -0
  37. package/scripts/setup.mjs +53 -0
  38. package/dist/lib/sanitize.d.ts +0 -10
  39. package/dist/lib/sanitize.d.ts.map +0 -1
  40. package/dist/lib/sanitize.js +0 -14
  41. package/dist/lib/sanitize.js.map +0 -1
  42. package/dist/lib/throttle.d.ts +0 -18
  43. package/dist/lib/throttle.d.ts.map +0 -1
  44. package/dist/lib/throttle.js +0 -26
  45. package/dist/lib/throttle.js.map +0 -1
  46. package/dist/lib/toast-format.d.ts +0 -24
  47. package/dist/lib/toast-format.d.ts.map +0 -1
  48. package/dist/lib/toast-format.js +0 -79
  49. package/dist/lib/toast-format.js.map +0 -1
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env node
2
+ import { copyFile, readdir, readFile, rm, mkdir } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ const ROOT = fileURLToPath(new URL("..", import.meta.url));
7
+ const SRC = join(ROOT, "src");
8
+ const DIST = join(ROOT, "dist");
9
+
10
+ /**
11
+ * Strategy: TSC handles the .ts → .js transformation and emits .d.ts files
12
+ * for IDE consumers. JSX/TSX files are NOT compiled — @opentui/solid's JSX
13
+ * runtime is shipped as TS-only and must be transformed by OpenCode's host
14
+ * runtime (Bun). We copy the .tsx sources verbatim so OpenCode's plugin
15
+ * loader can compile them on import.
16
+ *
17
+ * We also strip the .jsx artifacts tsc leaves behind when jsx=preserve is on,
18
+ * plus the auto-generated index.html "directory listing" files the OpenCode
19
+ * TUI static server writes into dist — they leak the local build path.
20
+ */
21
+ async function walk(dir) {
22
+ const entries = await readdir(dir, { withFileTypes: true });
23
+ for (const e of entries) {
24
+ const p = join(dir, e.name);
25
+ if (e.isDirectory()) {
26
+ await walk(p);
27
+ } else if (e.name.endsWith(".jsx")) {
28
+ await rm(p);
29
+ } else if (e.name.endsWith(".jsx.map")) {
30
+ await rm(p);
31
+ } else if (e.name === "index.html" && (await readFile(p, "utf8")).includes("Directory listing")) {
32
+ await rm(p);
33
+ }
34
+ }
35
+ }
36
+
37
+ async function copyTsxRecursive(srcDir, distDir, rel = "") {
38
+ const entries = await readdir(srcDir, { withFileTypes: true });
39
+ for (const e of entries) {
40
+ const sp = join(srcDir, e.name);
41
+ const dp = join(distDir, e.name);
42
+ if (e.isDirectory()) {
43
+ await mkdir(dp, { recursive: true });
44
+ await copyTsxRecursive(sp, dp, rel + e.name + "/");
45
+ } else if (e.name.endsWith(".tsx")) {
46
+ await rm(dp.replace(/\.tsx$/, ".js"), { force: true });
47
+ await rm(dp.replace(/\.tsx$/, ".js.map"), { force: true });
48
+ await copyFile(sp, dp);
49
+ console.log(`[postbuild] copied ${rel}${e.name} (uncompiled, runtime will transform)`);
50
+ }
51
+ }
52
+ }
53
+
54
+ await walk(DIST);
55
+ await copyTsxRecursive(SRC, DIST);
56
+ console.log("[postbuild] done");
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env node
2
+ import { createInterface } from "node:readline";
3
+ import { mkdir, writeFile, chmod } from "node:fs/promises";
4
+ import { homedir } from "node:os";
5
+ import { join } from "node:path";
6
+
7
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
8
+ const ask = (q) => new Promise((r) => rl.question(q, r));
9
+
10
+ const outDir = join(homedir(), ".config", "opencode", "opencode-quota");
11
+ const argSuffix = process.argv[2]?.trim();
12
+
13
+ console.log("");
14
+ console.log("== OpenCode Go — setup ==");
15
+ console.log("");
16
+ console.log("Necesitas una API key personal de OpenCode Go:");
17
+ console.log(" 1. Abre https://opencode.ai/console");
18
+ console.log(" 2. Ve a 'Personal API keys' y crea una key");
19
+ console.log(" 3. Permisos: 'All' (es la unica forma de leer cuotas, gasto y balance)");
20
+ console.log(" No uses una service account: no puede gestionar billing.");
21
+ console.log(" 4. Copia el valor (empieza con oc_sk_...)");
22
+ console.log("");
23
+ console.log("La key se guarda solo en tu equipo, con permisos 600.");
24
+ console.log("");
25
+
26
+ const suffixInput = argSuffix ?? (await ask("Nombre de cuenta (opcional, ej. work): ")).trim();
27
+ const suffix = suffixInput
28
+ .replace(/[^A-Za-z0-9._-]+/g, "-")
29
+ .replace(/^-+|-+$/g, "");
30
+ if (suffix === "active" || suffix.endsWith("-key")) {
31
+ console.error("Ese nombre esta reservado por el switch de cuentas. Usa otro.");
32
+ process.exit(1);
33
+ }
34
+
35
+ const apiKey = (await ask("Pega la API key (oc_sk_...): ")).trim();
36
+ rl.close();
37
+
38
+ if (!apiKey) {
39
+ console.error("La API key es obligatoria. Abortando.");
40
+ process.exit(1);
41
+ }
42
+ if (!apiKey.startsWith("oc_sk_")) {
43
+ console.warn("Aviso: la key no empieza por 'oc_sk_'. Se guarda igualmente.");
44
+ }
45
+
46
+ const outFile = join(outDir, suffix ? `opencode-go-${suffix}.json` : "opencode-go.json");
47
+
48
+ await mkdir(outDir, { recursive: true });
49
+ await writeFile(outFile, JSON.stringify({ apiKey }, null, 2) + "\n", { mode: 0o600 });
50
+ await chmod(outFile, 0o600);
51
+
52
+ console.log(`\nConfiguracion guardada en: ${outFile}`);
53
+ console.log("Reinicia OpenCode y el panel 'Quota cards' aparecera.");
@@ -1,10 +0,0 @@
1
- /**
2
- * Display sanitization for user-visible toast output.
3
- *
4
- * Mirrors the upstream `@slkiser/opencode-quota` helper of the same name:
5
- * strips ANSI escape sequences and control characters (except newline/tab)
6
- * so that provider error text cannot inject terminal control codes into
7
- * toasts or transcript output.
8
- */
9
- export declare function sanitizeDisplayText(text: string): string;
10
- //# sourceMappingURL=sanitize.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../../src/lib/sanitize.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAExD"}
@@ -1,14 +0,0 @@
1
- /**
2
- * Display sanitization for user-visible toast output.
3
- *
4
- * Mirrors the upstream `@slkiser/opencode-quota` helper of the same name:
5
- * strips ANSI escape sequences and control characters (except newline/tab)
6
- * so that provider error text cannot inject terminal control codes into
7
- * toasts or transcript output.
8
- */
9
- // eslint-disable-next-line no-control-regex
10
- const DISPLAY_CONTROL_RE = /\x1B\[[0-9;]*[A-Za-z]|[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g;
11
- export function sanitizeDisplayText(text) {
12
- return text.replace(DISPLAY_CONTROL_RE, "");
13
- }
14
- //# sourceMappingURL=sanitize.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"sanitize.js","sourceRoot":"","sources":["../../src/lib/sanitize.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,4CAA4C;AAC5C,MAAM,kBAAkB,GAAG,yDAAyD,CAAC;AAErF,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,OAAO,IAAI,CAAC,OAAO,CAAC,kBAAkB,EAAE,EAAE,CAAC,CAAC;AAC9C,CAAC"}
@@ -1,18 +0,0 @@
1
- /**
2
- * Simple time-since-last-fire throttle gate.
3
- *
4
- * Used by the toast feature so a flurry of `session.idle` /
5
- * `session.compacted` events does not spam the user. No caching, no
6
- * deduplication — callers ask "may I fire?" and, when they actually fire,
7
- * call `record()`.
8
- */
9
- export interface Throttle {
10
- /** Returns true if the configured min interval has elapsed since the last `record()`. */
11
- shouldFire(nowMs?: number): boolean;
12
- /** Mark a fire as having occurred at the given time (defaults to now). */
13
- record(nowMs?: number): void;
14
- /** Reset the gate so the next `shouldFire()` returns true. Useful for tests. */
15
- reset(): void;
16
- }
17
- export declare function createThrottle(minIntervalMs: number): Throttle;
18
- //# sourceMappingURL=throttle.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"throttle.d.ts","sourceRoot":"","sources":["../../src/lib/throttle.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,WAAW,QAAQ;IACvB,yFAAyF;IACzF,UAAU,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACpC,0EAA0E;IAC1E,MAAM,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,gFAAgF;IAChF,KAAK,IAAI,IAAI,CAAC;CACf;AAED,wBAAgB,cAAc,CAAC,aAAa,EAAE,MAAM,GAAG,QAAQ,CAe9D"}
@@ -1,26 +0,0 @@
1
- /**
2
- * Simple time-since-last-fire throttle gate.
3
- *
4
- * Used by the toast feature so a flurry of `session.idle` /
5
- * `session.compacted` events does not spam the user. No caching, no
6
- * deduplication — callers ask "may I fire?" and, when they actually fire,
7
- * call `record()`.
8
- */
9
- export function createThrottle(minIntervalMs) {
10
- let lastFireMs = Number.NEGATIVE_INFINITY;
11
- return {
12
- shouldFire(nowMs) {
13
- if (minIntervalMs <= 0)
14
- return true;
15
- const now = nowMs ?? Date.now();
16
- return now - lastFireMs >= minIntervalMs;
17
- },
18
- record(nowMs) {
19
- lastFireMs = nowMs ?? Date.now();
20
- },
21
- reset() {
22
- lastFireMs = Number.NEGATIVE_INFINITY;
23
- },
24
- };
25
- }
26
- //# sourceMappingURL=throttle.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"throttle.js","sourceRoot":"","sources":["../../src/lib/throttle.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAWH,MAAM,UAAU,cAAc,CAAC,aAAqB;IAClD,IAAI,UAAU,GAAG,MAAM,CAAC,iBAAiB,CAAC;IAC1C,OAAO;QACL,UAAU,CAAC,KAAc;YACvB,IAAI,aAAa,IAAI,CAAC;gBAAE,OAAO,IAAI,CAAC;YACpC,MAAM,GAAG,GAAG,KAAK,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;YAChC,OAAO,GAAG,GAAG,UAAU,IAAI,aAAa,CAAC;QAC3C,CAAC;QACD,MAAM,CAAC,KAAc;YACnB,UAAU,GAAG,KAAK,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;QACnC,CAAC;QACD,KAAK;YACH,UAAU,GAAG,MAAM,CAAC,iBAAiB,CAAC;QACxC,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -1,24 +0,0 @@
1
- /**
2
- * Toast message formatter.
3
- *
4
- * Renders a `CardsSnapshot` (the same data structure the TUI sidebar
5
- * consumes) into a compact multi-line string suitable for OpenCode's
6
- * `tui.showToast`. The shape is loosely modelled on upstream's
7
- * `formatQuotaRowsGrouped` — provider header followed by one line per
8
- * usage window — but kept narrower since our snapshot has fewer fields.
9
- *
10
- * Returns `null` when no provider has any window worth showing.
11
- */
12
- import type { CardsSnapshot } from "./types.js";
13
- export interface FormatToastMessageOptions {
14
- /** Override the current time for deterministic tests. */
15
- now?: Date;
16
- /** Maximum chars per line (lines longer than this are clipped). Default 60. */
17
- maxLineWidth?: number;
18
- }
19
- /**
20
- * Render a CardsSnapshot to a multi-line toast string, or `null` if there
21
- * is nothing meaningful to display.
22
- */
23
- export declare function formatToastMessage(snapshot: CardsSnapshot, opts?: FormatToastMessageOptions): string | null;
24
- //# sourceMappingURL=toast-format.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"toast-format.d.ts","sourceRoot":"","sources":["../../src/lib/toast-format.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,aAAa,EAAiB,MAAM,YAAY,CAAC;AAG/D,MAAM,WAAW,yBAAyB;IACxC,yDAAyD;IACzD,GAAG,CAAC,EAAE,IAAI,CAAC;IACX,+EAA+E;IAC/E,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAiED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,aAAa,EACvB,IAAI,GAAE,yBAA8B,GACnC,MAAM,GAAG,IAAI,CAyBf"}
@@ -1,79 +0,0 @@
1
- import { formatResetRelative, clampPercent } from "./reset-format.js";
2
- function padRight(text, width) {
3
- if (text.length >= width)
4
- return text;
5
- return text + " ".repeat(width - text.length);
6
- }
7
- function padLeft(text, width) {
8
- if (text.length >= width)
9
- return text;
10
- return " ".repeat(width - text.length) + text;
11
- }
12
- function formatProviderHeader(provider) {
13
- return provider.label;
14
- }
15
- function formatWindowLine(provider, window, now) {
16
- const label = padRight(window.label, 7);
17
- const countdown = formatResetRelative(window.resetTimeIso, now);
18
- const reset = padRight(countdown, 11);
19
- const percent = `${clampPercent(window.percentRemaining)}%`;
20
- const percentCell = padLeft(percent, 4);
21
- // Copilot exposes remaining/total counts — surface them after the percent.
22
- let suffix = "";
23
- if (provider.id === "github-copilot" &&
24
- typeof window.remaining === "number" &&
25
- typeof window.total === "number") {
26
- suffix = ` (${window.remaining}/${window.total})`;
27
- }
28
- return ` ${label} ${reset} ${percentCell}${suffix}`;
29
- }
30
- function formatProviderBlock(provider, now) {
31
- if (provider.status !== "ok" || provider.windows.length === 0)
32
- return [];
33
- const lines = [{ text: formatProviderHeader(provider) }];
34
- for (const window of provider.windows) {
35
- lines.push({ text: formatWindowLine(provider, window, now) });
36
- }
37
- return lines;
38
- }
39
- function formatProviderError(provider) {
40
- if (provider.status === "ok")
41
- return null;
42
- // Skip purely "skipped" providers (no auth configured) — they would spam
43
- // the toast every time it fires. Surface only real errors.
44
- if (provider.status === "skipped" || provider.status === "no-auth") {
45
- return null;
46
- }
47
- const reason = provider.error ?? provider.status;
48
- return { text: `${provider.label}: ${reason}` };
49
- }
50
- /**
51
- * Render a CardsSnapshot to a multi-line toast string, or `null` if there
52
- * is nothing meaningful to display.
53
- */
54
- export function formatToastMessage(snapshot, opts = {}) {
55
- const now = opts.now ?? new Date();
56
- const maxWidth = opts.maxLineWidth ?? 60;
57
- const lines = [];
58
- for (const provider of snapshot.providers) {
59
- const block = formatProviderBlock(provider, now);
60
- if (block.length > 0) {
61
- lines.push(...block);
62
- }
63
- }
64
- const errors = [];
65
- for (const provider of snapshot.providers) {
66
- const err = formatProviderError(provider);
67
- if (err)
68
- errors.push(err);
69
- }
70
- if (errors.length > 0) {
71
- if (lines.length > 0)
72
- lines.push({ text: "" });
73
- lines.push(...errors);
74
- }
75
- if (lines.length === 0)
76
- return null;
77
- return lines.map((l) => l.text.slice(0, maxWidth)).join("\n");
78
- }
79
- //# sourceMappingURL=toast-format.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"toast-format.js","sourceRoot":"","sources":["../../src/lib/toast-format.ts"],"names":[],"mappings":"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"}