oc-plugin-litellm-budget 0.3.0 → 0.5.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 (2) hide show
  1. package/package.json +1 -1
  2. package/tui.tsx +38 -10
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "oc-plugin-litellm-budget",
4
- "version": "0.3.0",
4
+ "version": "0.5.0",
5
5
  "description": "Display LiteLLM budget usage in OpenCode TUI sidebar and footer",
6
6
  "type": "module",
7
7
  "exports": {
package/tui.tsx CHANGED
@@ -11,10 +11,36 @@ type QuotaData = {
11
11
  tpm: number
12
12
  }
13
13
 
14
- const getConfig = () => {
15
- const binetzToken = process.env.BINETZ_REMOTE_CONFIG_TOKEN || ""
16
- const url = process.env.LITELLM_BASE_URL || process.env.OPENAI_BASE_URL || (binetzToken ? "https://litellm.binetz.com" : "")
17
- const key = process.env.LITELLM_API_KEY || process.env.OPENAI_API_KEY || binetzToken || ""
14
+ const readKeyFromConfig = async (): Promise<string> => {
15
+ try {
16
+ const home = process.env.HOME || process.env.USERPROFILE || ""
17
+ if (!home) return ""
18
+ const base = process.env.XDG_CONFIG_HOME || `${home}/.config`
19
+ for (const name of ["opencode.json", "opencode.jsonc"]) {
20
+ try {
21
+ const raw = await Bun.file(`${base}/opencode/${name}`).text()
22
+ if (!raw) continue
23
+ const d = JSON.parse(raw)
24
+ const k = d?.provider?.binetz?.options?.apiKey
25
+ if (k && typeof k === "string" && !k.startsWith("{env:")) return k
26
+ } catch {
27
+ try {
28
+ const raw = await Bun.file(`${base}/opencode/${name}`).text()
29
+ const clean = raw.replace(/^\s*\/\/.*$/gm, "").replace(/,(\s*[}\]])/g, "$1")
30
+ const d = JSON.parse(clean)
31
+ const k = d?.provider?.binetz?.options?.apiKey
32
+ if (k && typeof k === "string" && !k.startsWith("{env:")) return k
33
+ } catch {}
34
+ }
35
+ }
36
+ } catch {}
37
+ return ""
38
+ }
39
+
40
+ const getConfig = async () => {
41
+ const envKey = process.env.LITELLM_API_KEY || process.env.OPENAI_API_KEY || process.env.BINETZ_REMOTE_CONFIG_TOKEN || ""
42
+ const key = envKey || await readKeyFromConfig()
43
+ const url = process.env.LITELLM_BASE_URL || process.env.OPENAI_BASE_URL || (key ? "https://litellm.binetz.com" : "")
18
44
  return { url: url.replace(/\/+$/, ""), key }
19
45
  }
20
46
 
@@ -56,12 +82,14 @@ const fmtTpm = (n: number): string =>
56
82
  n >= 1_000_000 ? `${(n / 1_000_000).toFixed(0)}M` : n >= 1_000 ? `${(n / 1_000).toFixed(0)}K` : `${n}`
57
83
 
58
84
  const useQuota = () => {
59
- const { url, key } = getConfig()
60
85
  const [data, setData] = createSignal<QuotaData | null>(null)
61
- const [err, setErr] = createSignal(!url || !key)
86
+ const [err, setErr] = createSignal(false)
87
+ const [configured, setConfigured] = createSignal(true)
62
88
 
63
89
  const poll = async () => {
64
- if (!url || !key) { setErr(true); return }
90
+ const { url, key } = await getConfig()
91
+ if (!url || !key) { setConfigured(false); setErr(true); return }
92
+ setConfigured(true)
65
93
  const q = await fetchQuota(url, key)
66
94
  if (q) { setData(q); setErr(false) }
67
95
  else setErr(true)
@@ -71,7 +99,7 @@ const useQuota = () => {
71
99
  const timer = setInterval(poll, POLL_MS)
72
100
  onCleanup(() => clearInterval(timer))
73
101
 
74
- return { data, err, configured: !!url && !!key }
102
+ return { data, err, configured }
75
103
  }
76
104
 
77
105
  const colorForPct = (pct: number, theme: any) =>
@@ -83,7 +111,7 @@ const Sidebar = (props: { theme: any }) => {
83
111
  return (
84
112
  <box paddingTop={1} width="100%" flexDirection="column">
85
113
  <text bold fg={props.theme.current.primary}>Quota</text>
86
- <Show when={configured} fallback={
114
+ <Show when={configured()} fallback={
87
115
  <text fg={props.theme.current.textMuted}>Not configured</text>
88
116
  }>
89
117
  <Show when={!err()} fallback={
@@ -118,7 +146,7 @@ const FooterQuota = (props: { theme: any }) => {
118
146
 
119
147
  return (
120
148
  <box>
121
- <Show when={configured} fallback={
149
+ <Show when={configured()} fallback={
122
150
  <text fg={props.theme.current.textMuted}>Quota: not configured</text>
123
151
  }>
124
152
  <Show when={!err() && data()} fallback={