opencode-mask-j0k3r-dev-rgl 2.0.20 → 2.0.22

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 (3) hide show
  1. package/components.tsx +11 -19
  2. package/package.json +1 -1
  3. package/tui.tsx +75 -26
package/components.tsx CHANGED
@@ -1,7 +1,6 @@
1
1
  // @ts-nocheck
2
2
  /** @jsxImportSource @opentui/solid */
3
- import type { TuiThemeCurrent, TuiSidebarFileItem, TuiSidebarMcpItem, TuiSidebarLspItem, TuiSidebarTodoItem } from "@opencode-ai/plugin/tui"
4
- import type { Message } from "@opencode-ai/sdk/v2"
3
+ import type { TuiThemeCurrent, TuiSidebarMcpItem, TuiSidebarLspItem, TuiSidebarTodoItem } from "@opencode-ai/plugin/tui"
5
4
  import type { Cfg } from "./config"
6
5
  import { getOSName, getProviders } from "./detection"
7
6
  import {
@@ -77,14 +76,13 @@ const ProgressBar = (props: {
77
76
  export const SidebarArch = (props: {
78
77
  theme: TuiThemeCurrent
79
78
  config: Cfg
80
- sessionID?: string
81
79
  branch?: string
82
- files?: ReadonlyArray<TuiSidebarFileItem>
83
80
  mcpItems?: ReadonlyArray<TuiSidebarMcpItem>
84
81
  lspItems?: ReadonlyArray<TuiSidebarLspItem>
85
82
  todos?: ReadonlyArray<TuiSidebarTodoItem>
86
- messages?: ReadonlyArray<Message>
87
- contextLimit?: number
83
+ contextTokens: number
84
+ contextCost: number
85
+ contextLimit: number
88
86
  }) => {
89
87
  if (!props.config.show_sidebar) return null
90
88
 
@@ -107,14 +105,10 @@ export const SidebarArch = (props: {
107
105
  const lspActive = lspItems.filter(l => l.status === "idle" || l.status === "running").length
108
106
  const lspTotal = lspItems.length
109
107
 
110
- // ── Tokens & Cost ─────────────────────────────────────────────────────────
111
- const messages = props.messages ?? []
112
- const assistantMsgs = messages.filter(m => m.role === "assistant")
113
- const contextTokens = assistantMsgs.reduce((max, m) => Math.max(max, m.tokens?.input ?? 0), 0)
114
- const totalCost = assistantMsgs.reduce((s, m) => s + (m.cost ?? 0), 0)
115
-
116
- // context window real del modelo via api.state.provider → model.limit.context
117
- const contextLimit = props.contextLimit ?? 1_000_000
108
+ // ── Context valores ya calculados en tui.tsx via eventos ────────────────
109
+ const contextTokens = props.contextTokens
110
+ const totalCost = props.contextCost
111
+ const contextLimit = props.contextLimit
118
112
  const contextPct = Math.min(100, Math.round((contextTokens / contextLimit) * 100))
119
113
  const costPct = Math.min(100, Math.round(totalCost * 100))
120
114
 
@@ -145,9 +139,8 @@ export const SidebarArch = (props: {
145
139
  </box>
146
140
  )}
147
141
 
148
- {/* ── Context (tokens + % used + cost) ── */}
149
- {contextTokens > 0 && (
150
- <box flexDirection="column" alignItems="center" marginTop={1}>
142
+ {/* ── Context (tokens + % used + cost) ── siempre visible */}
143
+ <box flexDirection="column" alignItems="center" marginTop={1}>
151
144
  <text fg={t.textMuted} bold={true}>Context</text>
152
145
 
153
146
  {/* tokens */}
@@ -170,8 +163,7 @@ export const SidebarArch = (props: {
170
163
  <text fg={t.textMuted}>spent</text>
171
164
  </box>
172
165
  <ProgressBar value={costPct} width={18} fillColor="#ffd166" emptyColor="#3a3a3a" theme={t} />
173
- </box>
174
- )}
166
+ </box>
175
167
 
176
168
  {/* ── Todos ── */}
177
169
  {totalTodos > 0 && (
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "opencode-mask-j0k3r-dev-rgl",
4
- "version": "2.0.20",
4
+ "version": "2.0.22",
5
5
  "description": "Arch Linux TUI mask for OpenCode — hot pink theme with prominent ASCII logo and j0k3r-dev-rgl@latest legend",
6
6
  "type": "module",
7
7
  "exports": {
package/tui.tsx CHANGED
@@ -1,6 +1,7 @@
1
1
  // @ts-nocheck
2
2
  /** @jsxImportSource @opentui/solid */
3
3
  import type { TuiPlugin, TuiPluginModule } from "@opencode-ai/plugin/tui"
4
+ import { createSignal } from "solid-js"
4
5
  import { cfg } from "./config"
5
6
  import { HomeLogo, SidebarArch, DetectedEnv } from "./components"
6
7
 
@@ -15,68 +16,116 @@ const tui: TuiPlugin = async (api, options) => {
15
16
  const boot = cfg(rec(options))
16
17
  if (!boot.enabled) return
17
18
 
18
- // Theme setup (wrapped in try-catch)
19
+ // Theme setup
19
20
  try {
20
21
  await api.theme.install("./themes/j0k3r-dev-rgl.json")
21
- if (boot.set_theme) {
22
- api.theme.set(boot.theme)
23
- }
22
+ if (boot.set_theme) api.theme.set(boot.theme)
24
23
  } catch (error) {
25
24
  console.error("[j0k3r-dev-rgl] Theme setup failed:", error)
26
25
  }
27
26
 
27
+ // ── Reactive context tracker via events ──────────────────────────────────
28
+ // sessionID → { tokens, cost }
29
+ type CtxStats = { tokens: number; cost: number }
30
+ const ctxStore = new Map<string, CtxStats>()
31
+ const msgCostTracker = new Map<string, number>() // `sid:msgId` → cost
32
+
33
+ const [ctxTick, setCtxTick] = createSignal(0)
34
+
35
+ let debugLogged = false
36
+ api.event.on("message.updated", (event) => {
37
+ // Debug: loguear estructura completa la primera vez
38
+ if (!debugLogged) {
39
+ debugLogged = true
40
+ const propsKeys = Object.keys((event as any).properties ?? {}).join(",")
41
+ const infoKeys = Object.keys(((event as any).properties?.info ?? (event as any).properties) ?? {}).join(",")
42
+ console.error("[j0k3r-mask] event.properties keys:", propsKeys)
43
+ console.error("[j0k3r-mask] info/msg keys:", infoKeys)
44
+ console.error("[j0k3r-mask] full event:", JSON.stringify(event).slice(0, 800))
45
+ api.ui.toast({ variant: "info", message: `props: ${propsKeys} | info: ${infoKeys}`, duration: 8000 })
46
+ }
47
+
48
+ // Intentar ambas estructuras: { properties: { info: Message } } o { properties: Message }
49
+ const props = (event as any).properties
50
+ const msg = props?.info ?? props
51
+ if (!msg || msg.role !== "assistant") return
52
+
53
+ const sid: string | undefined = msg.sessionID
54
+ if (!sid) return
55
+
56
+ const t = msg.tokens ?? {}
57
+ const tokens = t.input ?? 0
58
+ const msgId: string = msg.id ?? `${sid}-unknown`
59
+ msgCostTracker.set(`${sid}:${msgId}`, msg.cost ?? 0)
60
+
61
+ const cost = Array.from(msgCostTracker.entries())
62
+ .filter(([k]) => k.startsWith(`${sid}:`))
63
+ .reduce((s, [, v]) => s + v, 0)
64
+
65
+ const prev = ctxStore.get(sid) ?? { tokens: 0, cost: 0 }
66
+ ctxStore.set(sid, { tokens: Math.max(prev.tokens, tokens), cost })
67
+
68
+ console.error(`[j0k3r-mask] sid=${sid} tokens=${tokens} cost=${cost}`)
69
+ setCtxTick(v => v + 1)
70
+ })
71
+
72
+ // Resolver context window real del modelo activo
73
+ const getContextLimit = (): number => {
74
+ try {
75
+ const modelStr = api.state.config?.model ?? ""
76
+ const [providerID, modelID] = modelStr.split("/")
77
+ const provider = api.state.provider.find(p => p.id === providerID)
78
+ const model = provider?.models?.[modelID]
79
+ if (model?.limit?.context) return model.limit.context
80
+ } catch (_) {}
81
+ return 1_000_000
82
+ }
83
+
28
84
  // Slot registration
29
85
  api.slots.register({
30
86
  slots: {
31
- // Home screen: large Arch logo + j0k3r-dev-rgl@latest legend
32
87
  home_logo(ctx) {
33
88
  return <HomeLogo theme={ctx.theme.current} />
34
89
  },
35
90
 
36
- // Below the prompt: environment detection
37
91
  home_bottom(ctx) {
38
92
  return <DetectedEnv theme={ctx.theme.current} providers={api.state.provider} config={boot} />
39
93
  },
40
94
 
41
- // Sidebar: Arch logo + live stats with progress bars
42
95
  sidebar_content(ctx, value) {
43
96
  const sessionID = value?.session_id
97
+
98
+ // Registrar sesión en el tracker si es nueva
99
+ if (sessionID && !ctxStore.has(sessionID)) {
100
+ ctxStore.set(sessionID, { tokens: 0, cost: 0 })
101
+ }
102
+
103
+ // Suscribirse a la señal reactiva
104
+ const _ = ctxTick()
105
+ const stats = sessionID
106
+ ? (ctxStore.get(sessionID) ?? { tokens: 0, cost: 0 })
107
+ : { tokens: 0, cost: 0 }
108
+
44
109
  const branch = api.state.vcs?.branch
45
- const files = sessionID ? api.state.session.diff(sessionID) : []
46
110
  const todos = sessionID ? api.state.session.todo(sessionID) : []
47
- const messages = sessionID ? api.state.session.messages(sessionID) : []
48
111
  const mcpItems = api.state.mcp()
49
112
  const lspItems = api.state.lsp()
50
113
 
51
- // Resolver el context window real del modelo activo
52
- // api.state.config.model = "providerID/modelID"
53
- // api.state.provider = ReadonlyArray<Provider> donde Provider.models[modelID].limit.context
54
- let contextLimit: number | undefined
55
- try {
56
- const modelStr = api.state.config?.model ?? ""
57
- const [providerID, modelID] = modelStr.split("/")
58
- const provider = api.state.provider.find(p => p.id === providerID)
59
- const model = provider?.models?.[modelID]
60
- if (model?.limit?.context) contextLimit = model.limit.context
61
- } catch (_) {}
62
-
63
114
  return (
64
115
  <SidebarArch
65
116
  theme={ctx.theme.current}
66
117
  config={boot}
67
- sessionID={sessionID}
68
118
  branch={branch}
69
- files={files}
70
119
  todos={todos}
71
- messages={messages}
72
120
  mcpItems={mcpItems}
73
121
  lspItems={lspItems}
74
- contextLimit={contextLimit}
122
+ contextTokens={stats.tokens}
123
+ contextCost={stats.cost}
124
+ contextLimit={getContextLimit()}
75
125
  />
76
126
  )
77
127
  },
78
128
 
79
- // Prompt bar right side: session info
80
129
  session_prompt_right(ctx, value) {
81
130
  const t = ctx.theme.current
82
131
  return (