From 5558833a9b37d1e6297e230872ce7bfa3c06db39 Mon Sep 17 00:00:00 2001 From: Robby Date: Sat, 6 Jun 2026 20:26:34 +0200 Subject: [PATCH 1/8] refactor: strip plugin to minimum needed #12 --- README.md | 13 ++-- package.json | 12 ++-- scripts/build.ts | 28 +------- src/tui.tsx | 184 ++++++++++++----------------------------------- 4 files changed, 57 insertions(+), 180 deletions(-) diff --git a/README.md b/README.md index 77738d2..79dcb1d 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,6 @@ opencode Plugin: shows DeepSeek API balance in the TUI sidebar. -## Status - -Wave P13, Beta. - ## Installation ```bash @@ -14,7 +10,7 @@ bun install @four-bytes/four-opencode-deepseek-meter ## Configuration -Requires `DEEPSEEK_API_KEY` environment variable. +Reads the DeepSeek API key from the opencode provider configuration — no `DEEPSEEK_API_KEY` env var needed. Load in opencode via directory path (dual server + tui plugin): @@ -28,15 +24,14 @@ Load in opencode via directory path (dual server + tui plugin): ## Usage -Start opencode and you'll see `DEEPSEEK` in the right-hand sidebar showing your current balance. Polls every 60 seconds. +Start opencode with a DeepSeek provider configured. You'll see `DEEPSEEK` in the right-hand sidebar showing your current balance. Polls every 60 seconds. ## Build ```bash -bun run build # server plugin -bun run build:tui # TUI sidebar component +bun run build ``` ## License -Apache-2.0 — see [LICENSE](../LICENSE) +Apache-2.0 diff --git a/package.json b/package.json index e1dd45a..c3d0db2 100644 --- a/package.json +++ b/package.json @@ -19,9 +19,6 @@ "balance" ], "exports": { - ".": { - "import": "./dist/four-opencode-deepseek-meter.js" - }, "./server": { "import": "./dist/four-opencode-deepseek-meter.js", "config": { @@ -29,7 +26,7 @@ } }, "./tui": { - "import": "./dist/four-opencode-deepseek-meter-tui.jsx", + "import": "./dist/tui.tsx", "config": { "enabled": true, "sidebar": true @@ -37,10 +34,9 @@ } }, "dependencies": { - "@opencode-ai/plugin": "1.16.2", - "@opentui/core": "0.3.2", - "@opentui/solid": "0.3.2", - "solid-js": "1.9.13" + "@opencode-ai/plugin": "^1.4.3", + "@opentui/solid": "^0.2.2", + "solid-js": "^1.9.12" }, "devDependencies": { "@types/bun": "^1.3.8", diff --git a/scripts/build.ts b/scripts/build.ts index 6fe89a1..292e048 100644 --- a/scripts/build.ts +++ b/scripts/build.ts @@ -1,26 +1,4 @@ -const server = await Bun.build({ - entrypoints: ["src/four-opencode-deepseek-meter.ts"], - outdir: "dist", - target: "bun", - external: ["@opencode-ai/*"], - minify: process.env.NODE_ENV === "production", -}); +await Bun.write("dist/four-opencode-deepseek-meter.js", Bun.file("src/four-opencode-deepseek-meter.ts")); +await Bun.write("dist/tui.tsx", Bun.file("src/tui.tsx")); -const tui = await Bun.build({ - entrypoints: ["src/tui.tsx"], - outdir: "dist", - target: "bun", - naming: "four-opencode-deepseek-meter-tui.jsx", - external: ["@opencode-ai/*", "@opentui/*", "solid-js"], - minify: process.env.NODE_ENV === "production", -}); - -if (!server.success || !tui.success) { - for (const log of [...server.logs, ...tui.logs]) console.error(log); - process.exit(1); -} - -for (const out of [...server.outputs, ...tui.outputs]) { - console.log(` ${out.path.padEnd(46)} ${(out.size / 1024).toFixed(2)} KB`); -} -console.log(`\n✅ Built 2 files`); +console.log(`✅ Copied 2 files`); diff --git a/src/tui.tsx b/src/tui.tsx index 12b8df3..da8f0fd 100644 --- a/src/tui.tsx +++ b/src/tui.tsx @@ -1,140 +1,75 @@ /** @jsxImportSource @opentui/solid */ -import { createSignal, createMemo, onCleanup, onMount, Show } from "solid-js"; -import type { TuiPlugin } from "@opencode-ai/plugin/tui"; +import { createSignal, onCleanup, onMount } from "solid-js"; +import type { TuiPlugin, TuiPluginApi, TuiPluginModule } from "@opencode-ai/plugin/tui"; const BALANCE_URL = "https://api.deepseek.com/user/balance"; -const REFRESH_INTERVAL_MS = 30_000; +const REFRESH_INTERVAL_MS = 60_000; const SIDEBAR_ORDER = 55; -interface Palette { - subtle: string; - text: string; - muted: string; - accent: string; - warning: string; +interface BalanceResponse { + balance_infos: { currency: string; total_balance: string; topped_up_balance: string }[]; } -const getPalette = (theme: Record): Palette => { - const get = (name: string, fallback: string): string => { - const value = theme[name]; - if (typeof value === "string") return value; - return fallback; - }; +const GREEN = "#22c55e"; +const ORANGE = "#f97316"; +const RED = "#ef4444"; - return { - subtle: get("borderSubtle", "#2a2a2a"), - text: get("text", "#f0f0f0"), - muted: get("textMuted", "#a5a5a5"), - accent: get("primary", "#5f87ff"), - warning: get("warning", "#d7a94b"), - }; -}; - -interface BalanceInfo { - currency: string; - total_balance: string; - granted_balance: string; - topped_up_balance: string; +function isDeepSeek(p: any): boolean { + const id = String(p.id ?? "").toLowerCase(); + if (id.includes("deepseek")) return true; + const bu = String(p.options?.baseURL ?? p.options?.baseUrl ?? "").toLowerCase(); + return bu.includes("deepseek.com") || bu.includes("deepseek.ai"); } -interface BalanceResponse { - is_available: boolean; - balance_infos: BalanceInfo[]; +function findDeepSeekKey(api: TuiPluginApi): string | undefined { + const provs = api.state.provider ?? []; + for (const p of provs) { + if (!isDeepSeek(p)) continue; + if (p.key) return p.key; + if (typeof p.options?.apiKey === "string" && p.options.apiKey) return p.options.apiKey; + } + return undefined; } -const BALANCE_GREEN = "#22c55e"; -const BALANCE_YELLOW = "#eab308"; -const BALANCE_ORANGE = "#f97316"; -const BALANCE_RED = "#ef4444"; - -const BalanceRow = (props: { palette: Palette; label: string; value: string; color?: string }) => ( - - - {props.label} - - - {props.value} - - -); +function DeepseekView(props: { api: TuiPluginApi }) { + const theme = () => props.api.theme.current; -const DeepseekBalance = (props: { palette: Palette }) => { - const [balance, setBalance] = createSignal(null); - const [isAvailable, setIsAvailable] = createSignal(true); - const [loading, setLoading] = createSignal(true); - const [errorMsg, setErrorMsg] = createSignal(""); + const [status, setStatus] = createSignal("loading..."); + const [statusColor, setStatusColor] = createSignal(theme().textMuted); const fetchBalance = async () => { - const apiKey = process.env.DEEPSEEK_API_KEY; + const apiKey = findDeepSeekKey(props.api); if (!apiKey) { - setErrorMsg("DEEPSEEK_API_KEY not set"); - setLoading(false); + setStatus("no key"); + setStatusColor(ORANGE); return; } try { const res = await fetch(BALANCE_URL, { - headers: { - Authorization: `Bearer ${apiKey}`, - Accept: "application/json", - }, + headers: { Authorization: `Bearer ${apiKey}`, Accept: "application/json" }, }); if (!res.ok) { - setErrorMsg(`DeepSeek API ${res.status}`); - setLoading(false); + setStatus(`API ${res.status}`); + setStatusColor(RED); return; } const data = (await res.json()) as BalanceResponse; const info = data.balance_infos?.[0]; if (info) { - setBalance(info); - setIsAvailable(data.is_available); - setErrorMsg(""); + const amount = parseFloat(info.topped_up_balance || info.total_balance).toFixed(2); + setStatus(`${info.currency} ${amount}`); + setStatusColor(GREEN); } else { - setErrorMsg("No balance data"); + setStatus("no data"); + setStatusColor(ORANGE); } } catch { - if (!balance()) { - setErrorMsg("DeepSeek API unreachable"); - } - } finally { - setLoading(false); + setStatus("unreachable"); + setStatusColor(RED); } }; - const totalNum = createMemo(() => { - const b = balance(); - if (!b) return 0; - return parseFloat(b.total_balance) || 0; - }); - - const balanceColor = createMemo(() => { - const n = totalNum(); - if (n <= 0) return BALANCE_RED; - if (n < 1) return BALANCE_ORANGE; - if (n < 10) return BALANCE_YELLOW; - return BALANCE_GREEN; - }); - - const formatBalance = (value: string) => { - const n = parseFloat(value); - if (isNaN(n)) return value; - return n.toFixed(2); - }; - - const toppedUp = createMemo(() => { - const b = balance(); - if (!b) return ""; - const amount = b.topped_up_balance || b.total_balance; - return `${b.currency} ${formatBalance(amount)}`; - }); - - const granted = createMemo(() => { - const b = balance(); - if (!b?.granted_balance) return ""; - return `${b.currency} ${formatBalance(b.granted_balance)}`; - }); - onMount(() => { void fetchBalance(); const timer = setInterval(() => void fetchBalance(), REFRESH_INTERVAL_MS); @@ -144,50 +79,23 @@ const DeepseekBalance = (props: { palette: Palette }) => { return ( - - DEEPSEEK - - - ... - + DEEPSEEK + {status()} - - - {(msg) => {msg()}} - - - - - - - - - ⚠ unavailable - - ); -}; - -const SidebarBalance = (props: { theme: Record }) => { - const palette = createMemo(() => getPalette(props.theme)); - return ; -}; +} -const tui: TuiPlugin = (api) => { +const tui: TuiPlugin = async (api) => { api.slots.register({ order: SIDEBAR_ORDER, - slots: { - sidebar_content(ctx) { - return } />; - }, - }, + slots: { sidebar_content(_ctx, _props) { return ; } }, }); - - return Promise.resolve(); }; -export default { +const plugin: TuiPluginModule & { id: string } = { id: "four-opencode-deepseek-meter", tui, }; + +export default plugin; From 77274325eac3b68d070c31a869ff3b32bf069bda Mon Sep 17 00:00:00 2001 From: Robby Date: Sat, 6 Jun 2026 20:31:14 +0200 Subject: [PATCH 2/8] refactor: replace polling with session-reactive balance check #12 - Use createEffect + createMemo on session messages instead of setInterval - 10s MIN_REFRESH_INTERVAL_MS throttle to avoid API spam - Pass session_id from sidebar slot props to DeepseekView - Removes onCleanup (no timer to clean) --- src/tui.tsx | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/src/tui.tsx b/src/tui.tsx index da8f0fd..adf2bb8 100644 --- a/src/tui.tsx +++ b/src/tui.tsx @@ -1,9 +1,9 @@ /** @jsxImportSource @opentui/solid */ -import { createSignal, onCleanup, onMount } from "solid-js"; +import { createEffect, createMemo, createSignal, onMount } from "solid-js"; import type { TuiPlugin, TuiPluginApi, TuiPluginModule } from "@opencode-ai/plugin/tui"; const BALANCE_URL = "https://api.deepseek.com/user/balance"; -const REFRESH_INTERVAL_MS = 60_000; +const MIN_REFRESH_INTERVAL_MS = 10_000; const SIDEBAR_ORDER = 55; interface BalanceResponse { @@ -31,12 +31,14 @@ function findDeepSeekKey(api: TuiPluginApi): string | undefined { return undefined; } -function DeepseekView(props: { api: TuiPluginApi }) { +function DeepseekView(props: { api: TuiPluginApi; session_id?: string }) { const theme = () => props.api.theme.current; const [status, setStatus] = createSignal("loading..."); const [statusColor, setStatusColor] = createSignal(theme().textMuted); + let lastFetch = 0; + const fetchBalance = async () => { const apiKey = findDeepSeekKey(props.api); if (!apiKey) { @@ -70,10 +72,26 @@ function DeepseekView(props: { api: TuiPluginApi }) { } }; + const throttledFetch = () => { + const now = Date.now(); + if (now - lastFetch < MIN_REFRESH_INTERVAL_MS) return; + lastFetch = now; + void fetchBalance(); + }; + + const lastMsg = createMemo(() => { + if (!props.session_id) return null; + const msgs = props.api.state.session.messages(props.session_id); + return msgs[msgs.length - 1]; + }); + + createEffect(() => { + const last = lastMsg(); + if (last?.role === "assistant") throttledFetch(); + }); + onMount(() => { void fetchBalance(); - const timer = setInterval(() => void fetchBalance(), REFRESH_INTERVAL_MS); - onCleanup(() => clearInterval(timer)); }); return ( @@ -89,7 +107,11 @@ function DeepseekView(props: { api: TuiPluginApi }) { const tui: TuiPlugin = async (api) => { api.slots.register({ order: SIDEBAR_ORDER, - slots: { sidebar_content(_ctx, _props) { return ; } }, + slots: { + sidebar_content(_ctx, props) { + return ; + }, + }, }); }; From a6d5b6895981cdfcd0fbe2c4693423a68b5495d4 Mon Sep 17 00:00:00 2001 From: Robby Date: Sat, 6 Jun 2026 20:37:10 +0200 Subject: [PATCH 3/8] build: restore Bun.build() with dual entrypoints #12 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Server → dist/four-opencode-deepseek-meter.js - TUI → dist/four-opencode-deepseek-meter-tui.jsx - Single step: bun run build --- package.json | 10 +++------- scripts/build.ts | 28 +++++++++++++++++++++++++--- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index c3d0db2..fb3e951 100644 --- a/package.json +++ b/package.json @@ -20,16 +20,12 @@ ], "exports": { "./server": { - "import": "./dist/four-opencode-deepseek-meter.js", - "config": { - "enabled": true - } + "import": "./dist/four-opencode-deepseek-meter.js" }, "./tui": { - "import": "./dist/tui.tsx", + "import": "./dist/four-opencode-deepseek-meter-tui.jsx", "config": { - "enabled": true, - "sidebar": true + "sidebar": "true" } } }, diff --git a/scripts/build.ts b/scripts/build.ts index 292e048..6fe89a1 100644 --- a/scripts/build.ts +++ b/scripts/build.ts @@ -1,4 +1,26 @@ -await Bun.write("dist/four-opencode-deepseek-meter.js", Bun.file("src/four-opencode-deepseek-meter.ts")); -await Bun.write("dist/tui.tsx", Bun.file("src/tui.tsx")); +const server = await Bun.build({ + entrypoints: ["src/four-opencode-deepseek-meter.ts"], + outdir: "dist", + target: "bun", + external: ["@opencode-ai/*"], + minify: process.env.NODE_ENV === "production", +}); -console.log(`✅ Copied 2 files`); +const tui = await Bun.build({ + entrypoints: ["src/tui.tsx"], + outdir: "dist", + target: "bun", + naming: "four-opencode-deepseek-meter-tui.jsx", + external: ["@opencode-ai/*", "@opentui/*", "solid-js"], + minify: process.env.NODE_ENV === "production", +}); + +if (!server.success || !tui.success) { + for (const log of [...server.logs, ...tui.logs]) console.error(log); + process.exit(1); +} + +for (const out of [...server.outputs, ...tui.outputs]) { + console.log(` ${out.path.padEnd(46)} ${(out.size / 1024).toFixed(2)} KB`); +} +console.log(`\n✅ Built 2 files`); From d09566e72adf9ab3cf65b8b1cebae5fd21d818f4 Mon Sep 17 00:00:00 2001 From: Robby Date: Sat, 6 Jun 2026 20:38:01 +0200 Subject: [PATCH 4/8] build: use >= minimum versions, move deps to peerDependencies #12 --- package.json | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index fb3e951..4783e78 100644 --- a/package.json +++ b/package.json @@ -29,13 +29,16 @@ } } }, - "dependencies": { - "@opencode-ai/plugin": "^1.4.3", - "@opentui/solid": "^0.2.2", - "solid-js": "^1.9.12" + "peerDependencies": { + "@opencode-ai/plugin": ">=1.4.3", + "@opentui/solid": ">=0.2.2", + "solid-js": ">=1.9.12" }, "devDependencies": { - "@types/bun": "^1.3.8", - "typescript": "^5.7.3" + "@opencode-ai/plugin": ">=1.4.3", + "@opentui/solid": ">=0.2.2", + "solid-js": ">=1.9.12", + "@types/bun": ">=1.3.8", + "typescript": ">=5.7.3" } } From dd87f25f56886d22203c6b4e1e2bc4f62d2e2528 Mon Sep 17 00:00:00 2001 From: Robby Date: Sat, 6 Jun 2026 20:49:54 +0200 Subject: [PATCH 5/8] fix: use theme colors instead of hex strings, fixing RGBA type errors #12 Replaced hardcoded hex constants (GREEN/ORANGE/RED) with theme().success, theme().warning, theme().error - matching the RGBA type expected by fg prop. Also removed the old getPalette() compat layer that was silently converting RGBA to fallback strings. --- src/tui.tsx | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/tui.tsx b/src/tui.tsx index adf2bb8..8c949ed 100644 --- a/src/tui.tsx +++ b/src/tui.tsx @@ -10,10 +10,6 @@ interface BalanceResponse { balance_infos: { currency: string; total_balance: string; topped_up_balance: string }[]; } -const GREEN = "#22c55e"; -const ORANGE = "#f97316"; -const RED = "#ef4444"; - function isDeepSeek(p: any): boolean { const id = String(p.id ?? "").toLowerCase(); if (id.includes("deepseek")) return true; @@ -43,7 +39,7 @@ function DeepseekView(props: { api: TuiPluginApi; session_id?: string }) { const apiKey = findDeepSeekKey(props.api); if (!apiKey) { setStatus("no key"); - setStatusColor(ORANGE); + setStatusColor(theme().warning); return; } @@ -53,7 +49,7 @@ function DeepseekView(props: { api: TuiPluginApi; session_id?: string }) { }); if (!res.ok) { setStatus(`API ${res.status}`); - setStatusColor(RED); + setStatusColor(theme().error); return; } const data = (await res.json()) as BalanceResponse; @@ -61,14 +57,14 @@ function DeepseekView(props: { api: TuiPluginApi; session_id?: string }) { if (info) { const amount = parseFloat(info.topped_up_balance || info.total_balance).toFixed(2); setStatus(`${info.currency} ${amount}`); - setStatusColor(GREEN); + setStatusColor(theme().success); } else { setStatus("no data"); - setStatusColor(ORANGE); + setStatusColor(theme().warning); } } catch { setStatus("unreachable"); - setStatusColor(RED); + setStatusColor(theme().error); } }; From 6d52413eae283a24272a16f1c93f750551ca7676 Mon Sep 17 00:00:00 2001 From: Robby Date: Sat, 6 Jun 2026 20:55:26 +0200 Subject: [PATCH 6/8] fix: restore boolean config, root export, peer dep versions, add defensive guards #12 package.json: - sidebar: change from string "true" back to boolean true - add back enabled: true to both server and tui configs - restore "." root export - tighten peerDeps to >=1.16.2/0.3.2/1.9.13 (matching last working versions) tui.tsx: - add ?. guards on api.state and api.state.session - prevents TypeError if state/session API surface is missing --- bun.lock | 18 ++++++++++-------- package.json | 26 ++++++++++++++++++-------- scripts/build.ts | 19 +++++++------------ src/tui.tsx | 4 ++-- 4 files changed, 37 insertions(+), 30 deletions(-) diff --git a/bun.lock b/bun.lock index 09db3e3..728a83f 100644 --- a/bun.lock +++ b/bun.lock @@ -4,15 +4,17 @@ "workspaces": { "": { "name": "@four-bytes/four-opencode-deepseek-meter", - "dependencies": { - "@opencode-ai/plugin": "1.16.2", - "@opentui/core": "0.3.2", - "@opentui/solid": "0.3.2", - "solid-js": "1.9.13", - }, "devDependencies": { - "@types/bun": "^1.3.8", - "typescript": "^5.7.3", + "@opencode-ai/plugin": ">=1.4.3", + "@opentui/solid": ">=0.2.2", + "@types/bun": ">=1.3.8", + "solid-js": ">=1.9.12", + "typescript": ">=5.7.3", + }, + "peerDependencies": { + "@opencode-ai/plugin": ">=1.4.3", + "@opentui/solid": ">=0.2.2", + "solid-js": ">=1.9.12", }, }, }, diff --git a/package.json b/package.json index 4783e78..a0f73ea 100644 --- a/package.json +++ b/package.json @@ -19,25 +19,35 @@ "balance" ], "exports": { - "./server": { + ".": { + "types": "./dist/four-opencode-deepseek-meter.js", "import": "./dist/four-opencode-deepseek-meter.js" }, + "./server": { + "types": "./dist/four-opencode-deepseek-meter.js", + "import": "./dist/four-opencode-deepseek-meter.js", + "config": { + "enabled": true + } + }, "./tui": { + "types": "./dist/four-opencode-deepseek-meter-tui.jsx", "import": "./dist/four-opencode-deepseek-meter-tui.jsx", "config": { - "sidebar": "true" + "enabled": true, + "sidebar": true } } }, "peerDependencies": { - "@opencode-ai/plugin": ">=1.4.3", - "@opentui/solid": ">=0.2.2", - "solid-js": ">=1.9.12" + "@opencode-ai/plugin": ">=1.16.2", + "@opentui/solid": ">=0.3.2", + "solid-js": ">=1.9.13" }, "devDependencies": { - "@opencode-ai/plugin": ">=1.4.3", - "@opentui/solid": ">=0.2.2", - "solid-js": ">=1.9.12", + "@opencode-ai/plugin": ">=1.16.2", + "@opentui/solid": ">=0.3.2", + "solid-js": ">=1.9.13", "@types/bun": ">=1.3.8", "typescript": ">=5.7.3" } diff --git a/scripts/build.ts b/scripts/build.ts index 6fe89a1..5806659 100644 --- a/scripts/build.ts +++ b/scripts/build.ts @@ -6,21 +6,16 @@ const server = await Bun.build({ minify: process.env.NODE_ENV === "production", }); -const tui = await Bun.build({ - entrypoints: ["src/tui.tsx"], - outdir: "dist", - target: "bun", - naming: "four-opencode-deepseek-meter-tui.jsx", - external: ["@opencode-ai/*", "@opentui/*", "solid-js"], - minify: process.env.NODE_ENV === "production", -}); - -if (!server.success || !tui.success) { - for (const log of [...server.logs, ...tui.logs]) console.error(log); +if (!server.success) { + for (const log of server.logs) console.error(log); process.exit(1); } -for (const out of [...server.outputs, ...tui.outputs]) { +// TUI: raw copy — opencode loads TSX with @opentui/solid pragma at runtime +await Bun.write("dist/four-opencode-deepseek-meter-tui.jsx", Bun.file("src/tui.tsx")); + +for (const out of server.outputs) { console.log(` ${out.path.padEnd(46)} ${(out.size / 1024).toFixed(2)} KB`); } +console.log(` ${"dist/four-opencode-deepseek-meter-tui.jsx".padEnd(46)} ${(Bun.file("dist/four-opencode-deepseek-meter-tui.jsx").size / 1024).toFixed(2)} KB`); console.log(`\n✅ Built 2 files`); diff --git a/src/tui.tsx b/src/tui.tsx index 8c949ed..fc56ec6 100644 --- a/src/tui.tsx +++ b/src/tui.tsx @@ -18,7 +18,7 @@ function isDeepSeek(p: any): boolean { } function findDeepSeekKey(api: TuiPluginApi): string | undefined { - const provs = api.state.provider ?? []; + const provs = api.state?.provider ?? []; for (const p of provs) { if (!isDeepSeek(p)) continue; if (p.key) return p.key; @@ -77,7 +77,7 @@ function DeepseekView(props: { api: TuiPluginApi; session_id?: string }) { const lastMsg = createMemo(() => { if (!props.session_id) return null; - const msgs = props.api.state.session.messages(props.session_id); + const msgs = props.api.state?.session?.messages(props.session_id); return msgs[msgs.length - 1]; }); From 4d0d6b0ee2b48d7e1f774f14bbb94b8a6de53389 Mon Sep 17 00:00:00 2001 From: Robby Date: Sat, 6 Jun 2026 22:48:42 +0200 Subject: [PATCH 7/8] refactor: tsc+bun build, warning color <3.00 #12 - Build: tsc (jsx:preserve) for TUI + Bun.build for server - TUI: theme.warning color when balance < 3.00 - package.json: proper .d.ts types, simplified exports - Remove config blocks, move deps back to dependencies --- bun.lock | 33 ++++++++++++++++++++++----------- package.json | 35 +++++++++++------------------------ scripts/build.ts | 24 +++++++++++++++++++----- src/tui.tsx | 5 +++-- 4 files changed, 55 insertions(+), 42 deletions(-) diff --git a/bun.lock b/bun.lock index 728a83f..5fd684a 100644 --- a/bun.lock +++ b/bun.lock @@ -4,17 +4,14 @@ "workspaces": { "": { "name": "@four-bytes/four-opencode-deepseek-meter", - "devDependencies": { - "@opencode-ai/plugin": ">=1.4.3", - "@opentui/solid": ">=0.2.2", - "@types/bun": ">=1.3.8", - "solid-js": ">=1.9.12", - "typescript": ">=5.7.3", + "dependencies": { + "@opencode-ai/plugin": "^1.4.3", + "@opentui/solid": "^0.2.2", + "solid-js": "^1.9.12", }, - "peerDependencies": { - "@opencode-ai/plugin": ">=1.4.3", - "@opentui/solid": ">=0.2.2", - "solid-js": ">=1.9.12", + "devDependencies": { + "@types/bun": "^1.3.8", + "typescript": "^5.7.3", }, }, }, @@ -119,7 +116,7 @@ "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.3.2", "", { "os": "win32", "cpu": "x64" }, "sha512-q8xqMhW1jlJVzos+A5+FXRquH01j1ZHmrNi/9++W1Ebz3LQYn+8Z8j7rcV/meIiDuo9nyRHigQQT+cy9xV4N2g=="], - "@opentui/solid": ["@opentui/solid@0.3.2", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.3.2", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.12", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.12" } }, "sha512-Yff0gSwIY/o0XeMciYeAUkQtea8bWzR0UjjVglmcBe13hWuJZt/GfjbDMdNNQ8zCrLubLEh04an5fYXCd7NMYQ=="], + "@opentui/solid": ["@opentui/solid@0.2.16", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.2.16", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.12", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.12" } }, "sha512-2Q+v1PPpXXr+sALi9Aj6I5Jvo7xDfbmstYjRLL7lW3Hghh9i7ONQKpt/gyDDRbhSsYrhxKYTNenF9OxgoXkTHg=="], "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], @@ -299,6 +296,8 @@ "zod": ["zod@4.1.8", "", {}, "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ=="], + "@opentui/solid/@opentui/core": ["@opentui/core@0.2.16", "", { "dependencies": { "bun-ffi-structs": "0.2.2", "diff": "9.0.0", "marked": "17.0.1", "string-width": "7.2.0", "strip-ansi": "7.1.2", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@opentui/core-darwin-arm64": "0.2.16", "@opentui/core-darwin-x64": "0.2.16", "@opentui/core-linux-arm64": "0.2.16", "@opentui/core-linux-x64": "0.2.16", "@opentui/core-win32-arm64": "0.2.16", "@opentui/core-win32-x64": "0.2.16" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-4vWN15Zc3nsXJlOiHhhpqkBXD+wrNFKxCPtiTiillZYDRre+XsZogVTOOGUDwaBIC23OSxq7imezLmmtShVBEA=="], + "babel-plugin-jsx-dom-expressions/@babel/helper-module-imports": ["@babel/helper-module-imports@7.18.6", "", { "dependencies": { "@babel/types": "^7.18.6" } }, "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA=="], "parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], @@ -306,5 +305,17 @@ "path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], "path-scurry/minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], + + "@opentui/solid/@opentui/core/@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.2.16", "", { "os": "darwin", "cpu": "arm64" }, "sha512-aFb2Yp+oqDu3h6VCWi7xpQ9yjpKSQcROzGGfHgqC6Nd3U+uiLfPJBkmiI87iK0opCggCFj5TkKI004050DmGjg=="], + + "@opentui/solid/@opentui/core/@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.2.16", "", { "os": "darwin", "cpu": "x64" }, "sha512-KimiHE0j7EsTB5P8doW0lr1eH5iZKLPKWQO+tmy1VcdYr/TzqhdHSvGuJXrZvfTFi9/rV57Eq0d7964Ri9O0vQ=="], + + "@opentui/solid/@opentui/core/@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.2.16", "", { "os": "linux", "cpu": "arm64" }, "sha512-4fCwRCfTtUgS/5QcSEkSuBjgQymSOUWXgrXG2ycrf3Swi0QhKDA/pVjwLrUJ6eF+/8mQyQSEV72T8MxMO3M2qg=="], + + "@opentui/solid/@opentui/core/@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.2.16", "", { "os": "linux", "cpu": "x64" }, "sha512-KgQBGjiucw4e7gM+R8qOzHWBFhjCY1IfCrGjW3Wzxv2hKUlL+mPhelaeJwnEqtNxMUdVTYjlwlu3IHxslXMJWQ=="], + + "@opentui/solid/@opentui/core/@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.2.16", "", { "os": "win32", "cpu": "arm64" }, "sha512-C6WqEI3VkXatXraMgSFXZjEXq0pzURGjRpFAJZYmuVDmpqE57o7E80Np2UkdZ6m5kpJDt4mRyu3krc/P825iNQ=="], + + "@opentui/solid/@opentui/core/@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.2.16", "", { "os": "win32", "cpu": "x64" }, "sha512-kCX3CMTns6DMCFDNTDV4sjmBKyA/iEvzaVhl/jYi4JRIVT2zcy1lo+lhXT5mPgYHmJZu8Uye6j3Zi3c7Z2Me5A=="], } } diff --git a/package.json b/package.json index a0f73ea..e7f470c 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "license": "Apache-2.0", "type": "module", "main": "dist/four-opencode-deepseek-meter.js", + "types": "dist/four-opencode-deepseek-meter.d.ts", "scripts": { "build": "NODE_ENV=production bun run scripts/build.ts", "dev": "bun run scripts/build.ts", @@ -19,36 +20,22 @@ "balance" ], "exports": { - ".": { - "types": "./dist/four-opencode-deepseek-meter.js", - "import": "./dist/four-opencode-deepseek-meter.js" - }, "./server": { - "types": "./dist/four-opencode-deepseek-meter.js", - "import": "./dist/four-opencode-deepseek-meter.js", - "config": { - "enabled": true - } + "types": "./dist/four-opencode-deepseek-meter.d.ts", + "import": "./dist/four-opencode-deepseek-meter.js" }, "./tui": { - "types": "./dist/four-opencode-deepseek-meter-tui.jsx", - "import": "./dist/four-opencode-deepseek-meter-tui.jsx", - "config": { - "enabled": true, - "sidebar": true - } + "types": "./dist/tui.d.ts", + "import": "./dist/tui.jsx" } }, - "peerDependencies": { - "@opencode-ai/plugin": ">=1.16.2", - "@opentui/solid": ">=0.3.2", - "solid-js": ">=1.9.13" + "dependencies": { + "@opencode-ai/plugin": "^1.4.3", + "@opentui/solid": "^0.2.2", + "solid-js": "^1.9.12" }, "devDependencies": { - "@opencode-ai/plugin": ">=1.16.2", - "@opentui/solid": ">=0.3.2", - "solid-js": ">=1.9.13", - "@types/bun": ">=1.3.8", - "typescript": ">=5.7.3" + "@types/bun": "^1.3.8", + "typescript": "^5.7.3" } } diff --git a/scripts/build.ts b/scripts/build.ts index 5806659..4979790 100644 --- a/scripts/build.ts +++ b/scripts/build.ts @@ -1,3 +1,13 @@ +import { rmSync } from "node:fs"; + +// Prune dist before building +rmSync("dist", { recursive: true, force: true }); + +// 1. TUI build: tsc strips types, preserves JSX → .jsx + .d.ts +// (also compiles server.ts, but Bun.build overrides it below) +await Bun.$`bunx tsc`; + +// 2. Server build: Bun bundler for optimized output const server = await Bun.build({ entrypoints: ["src/four-opencode-deepseek-meter.ts"], outdir: "dist", @@ -11,11 +21,15 @@ if (!server.success) { process.exit(1); } -// TUI: raw copy — opencode loads TSX with @opentui/solid pragma at runtime -await Bun.write("dist/four-opencode-deepseek-meter-tui.jsx", Bun.file("src/tui.tsx")); - +// Report outputs for (const out of server.outputs) { console.log(` ${out.path.padEnd(46)} ${(out.size / 1024).toFixed(2)} KB`); } -console.log(` ${"dist/four-opencode-deepseek-meter-tui.jsx".padEnd(46)} ${(Bun.file("dist/four-opencode-deepseek-meter-tui.jsx").size / 1024).toFixed(2)} KB`); -console.log(`\n✅ Built 2 files`); +for (const f of ["dist/tui.jsx", "dist/tui.d.ts", "dist/four-opencode-deepseek-meter.d.ts"]) { + const file = Bun.file(f); + if (await file.exists()) { + const size = (await file.arrayBuffer()).byteLength; + console.log(` ${f.padEnd(46)} ${(size / 1024).toFixed(2)} KB`); + } +} +console.log(`\n✅ Built (tsc TUI + Bun server)`); diff --git a/src/tui.tsx b/src/tui.tsx index fc56ec6..93bdc92 100644 --- a/src/tui.tsx +++ b/src/tui.tsx @@ -55,9 +55,10 @@ function DeepseekView(props: { api: TuiPluginApi; session_id?: string }) { const data = (await res.json()) as BalanceResponse; const info = data.balance_infos?.[0]; if (info) { - const amount = parseFloat(info.topped_up_balance || info.total_balance).toFixed(2); + const raw = parseFloat(info.topped_up_balance || info.total_balance); + const amount = raw.toFixed(2); setStatus(`${info.currency} ${amount}`); - setStatusColor(theme().success); + setStatusColor(raw < 3.0 ? theme().warning : theme().success); } else { setStatus("no data"); setStatusColor(theme().warning); From c0b168afa62f9ec3ffffee5d4f597177bd044ac7 Mon Sep 17 00:00:00 2001 From: Robby Date: Sat, 6 Jun 2026 22:49:56 +0200 Subject: [PATCH 8/8] test: add minimal server smoke tests #12 --- src/server.test.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/server.test.ts diff --git a/src/server.test.ts b/src/server.test.ts new file mode 100644 index 0000000..fa9a62c --- /dev/null +++ b/src/server.test.ts @@ -0,0 +1,17 @@ +import { describe, expect, test } from "bun:test"; +import plugin from "./four-opencode-deepseek-meter.js"; + +describe("four-opencode-deepseek-meter", () => { + test("exports id", () => { + expect(plugin.id).toBe("four-opencode-deepseek-meter"); + }); + + test("exports server function", () => { + expect(typeof plugin.server).toBe("function"); + }); + + test("server returns expected shape", async () => { + const result = await (plugin.server as () => Promise)(); + expect(result).toBeDefined(); + }); +});