pantheon-opencode 1.1.1-beta.0 → 1.1.1-beta.10
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.
- package/README.md +7 -2
- package/bin/pantheon-init.mjs +19 -6
- package/package.json +1 -1
- package/scripts/install/cli-ui.mjs +160 -0
- package/scripts/install/component.mjs +24 -0
- package/scripts/install/interactive.mjs +395 -0
- package/scripts/install/migrate-config.mjs +308 -0
- package/scripts/install/migrate.mjs +210 -30
- package/scripts/install/opencode.mjs +96 -77
- package/scripts/install/plugin.mjs +139 -0
- package/scripts/install/state.mjs +194 -0
- package/src/plugins/tui/dist/tui.d.ts +8 -0
- package/src/plugins/tui/dist/tui.d.ts.map +1 -0
- package/src/plugins/tui/dist/tui.js +557 -137
- package/src/plugins/tui/dist/tui.js.map +1 -0
- package/src/plugins/tui/dist/tui.tsx +242 -127
- package/src/plugins/tui/package-lock.json +2879 -0
- package/src/plugins/tui/package.json +11 -8
- package/src/plugins/tui/src/index.tsx +242 -127
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tui.js","names":[],"sources":[],"mappings":""}
|
|
@@ -3,30 +3,11 @@
|
|
|
3
3
|
/** @jsxImportSource @opentui/solid */
|
|
4
4
|
|
|
5
5
|
import type { TuiPlugin, TuiPluginApi, TuiPluginModule } from '@opencode-ai/plugin/tui'
|
|
6
|
-
import { createMemo,
|
|
6
|
+
import { createMemo, createResource, createSignal, For, Show } from 'solid-js'
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
try {
|
|
10
|
-
const worktree = ((api.state as any).path?.worktree ?? '') as string
|
|
11
|
-
const filePath = worktree ? `${worktree}/package.json` : 'package.json'
|
|
12
|
-
const result = await api.client.file.read({ query: { path: filePath } })
|
|
13
|
-
const content = typeof result.content === 'string' ? result.content : String(result.content ?? '')
|
|
14
|
-
const match = content.match(/"version":\s*"([^"]+)"/)
|
|
15
|
-
if (match?.[1]) return match[1]
|
|
16
|
-
} catch { /* fall through */ }
|
|
17
|
-
|
|
18
|
-
try {
|
|
19
|
-
const proc = (api as any).client?.process
|
|
20
|
-
if (typeof proc?.exec === 'function') {
|
|
21
|
-
const result = await proc.exec({ command: 'git', args: ['describe', '--tags', '--always'] })
|
|
22
|
-
const stdout = (result.stdout ?? result.output ?? '') as string
|
|
23
|
-
const tag = stdout.trim().replace(/^v/, '')
|
|
24
|
-
if (tag) return tag
|
|
25
|
-
}
|
|
26
|
-
} catch { /* fall through */ }
|
|
8
|
+
/* ─── Constants ─────────────────────────────────────────── */
|
|
27
9
|
|
|
28
|
-
|
|
29
|
-
}
|
|
10
|
+
const BAR_WIDTH = 20
|
|
30
11
|
|
|
31
12
|
const COMMANDS = [
|
|
32
13
|
{ name: '/pantheon', desc: 'Council synthesis' },
|
|
@@ -59,16 +40,159 @@ const AGENTS = [
|
|
|
59
40
|
{ name: 'talos', tier: 'fast' as const, role: 'Hotfixes' },
|
|
60
41
|
] as const
|
|
61
42
|
|
|
43
|
+
/* ─── Helpers ───────────────────────────────────────────── */
|
|
44
|
+
|
|
62
45
|
function safeNum(v: unknown): number {
|
|
63
46
|
return typeof v === 'number' && Number.isFinite(v) ? v : 0
|
|
64
47
|
}
|
|
65
48
|
|
|
66
|
-
function
|
|
49
|
+
function fmtInt(n: number): string {
|
|
50
|
+
return Intl.NumberFormat('en-US').format(Math.max(0, Math.round(n)))
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function fmtCost(v: number): string {
|
|
54
|
+
if (v <= 0) return ''
|
|
55
|
+
if (v < 0.01) return '<$0.01'
|
|
56
|
+
return `$${v.toFixed(2)}`
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function msgTokens(msg: any): number {
|
|
60
|
+
const t = msg?.tokens ?? msg?.info?.tokens ?? {}
|
|
61
|
+
return safeNum(t.input) + safeNum(t.output) + safeNum(t.reasoning)
|
|
62
|
+
+ safeNum(t?.cache?.read) + safeNum(t?.cache?.write)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function msgCost(src: any): number {
|
|
66
|
+
for (const c of [src?.cost, src?.info?.cost, src?.usage?.cost]) {
|
|
67
|
+
if (typeof c === 'number' && c > 0) return c
|
|
68
|
+
}
|
|
69
|
+
return 0
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function buildBar(pct: number): string {
|
|
73
|
+
const clamped = Math.max(0, Math.min(100, pct))
|
|
74
|
+
const filled = Math.max(0, Math.min(BAR_WIDTH, Math.round((clamped / 100) * BAR_WIDTH)))
|
|
75
|
+
return '█'.repeat(filled) + '░'.repeat(BAR_WIDTH - filled)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/* ─── Version Detection ────────────────────────────────────
|
|
79
|
+
* 100% dinâmico — zero fallback hardcoded.
|
|
80
|
+
* Se falhar, retorna null e a View omite a versão. */
|
|
81
|
+
|
|
82
|
+
async function detectVersion(api: TuiPluginApi): Promise<string | null> {
|
|
83
|
+
// Try 1: package.json do projeto (via worktree path)
|
|
84
|
+
try {
|
|
85
|
+
const wt = ((api.state as any).path?.worktree ?? '') as string
|
|
86
|
+
const fp = wt ? `${wt}/package.json` : 'package.json'
|
|
87
|
+
const result = await api.client.file.read({ query: { path: fp } })
|
|
88
|
+
const match = String(result?.content ?? '').match(/"version":\s*"([^"]+)"/)
|
|
89
|
+
if (match?.[1]) return match[1]
|
|
90
|
+
} catch { /* fall through */ }
|
|
91
|
+
|
|
92
|
+
// Try 2: git describe --tags
|
|
93
|
+
try {
|
|
94
|
+
const proc = (api as any).client?.process
|
|
95
|
+
if (typeof proc?.exec === 'function') {
|
|
96
|
+
const r = await proc.exec({ command: 'git', args: ['describe', '--tags', '--always'], timeoutMs: 3000 })
|
|
97
|
+
const stdout = (r.stdout ?? r.output ?? '') as string
|
|
98
|
+
const tag = stdout.trim().replace(/^v/, '').replace(/-\d+-g[0-9a-f]+$/, '')
|
|
99
|
+
if (tag && tag !== '5.0.0') return tag
|
|
100
|
+
}
|
|
101
|
+
} catch { /* fall through */ }
|
|
102
|
+
|
|
103
|
+
return null // sem fallback — versão não aparece
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* ─── Context Bar ──────────────────────────────────────────
|
|
107
|
+
* Inspired by streetturtle/opencode-better-sidebar (context-progress)
|
|
108
|
+
* Shows: progress bar, token usage / limit, session cost */
|
|
109
|
+
|
|
110
|
+
function ContextBar(props: { api: TuiPluginApi; sessionID: string }) {
|
|
111
|
+
const messages = createMemo((): any[] => {
|
|
112
|
+
try { return (props.api.state.session as any).messages(props.sessionID) ?? [] }
|
|
113
|
+
catch { return [] }
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
const usage = createMemo(() => {
|
|
117
|
+
const msgs = messages()
|
|
118
|
+
const lastAssistant = [...msgs].reverse().find((m: any) => {
|
|
119
|
+
const role = m?.role ?? m?.info?.role
|
|
120
|
+
return role === 'assistant' && safeNum(m?.tokens?.output) > 0
|
|
121
|
+
})
|
|
122
|
+
if (!lastAssistant) return null
|
|
123
|
+
|
|
124
|
+
const tokens = msgTokens(lastAssistant)
|
|
125
|
+
const pid = lastAssistant?.providerID ?? lastAssistant?.info?.providerID
|
|
126
|
+
const mid = lastAssistant?.modelID ?? lastAssistant?.info?.modelID
|
|
127
|
+
const provider = pid
|
|
128
|
+
? ((props.api.state as any).provider ?? []).find((p: any) => p.id === pid)
|
|
129
|
+
: null
|
|
130
|
+
const ctxLimit = safeNum(provider?.models?.[mid ?? '']?.limit?.context)
|
|
131
|
+
const limit = ctxLimit > 0 ? ctxLimit : 200000
|
|
132
|
+
return { tokens, limit, percent: ctxLimit > 0 ? Math.round((tokens / ctxLimit) * 100) : 0 }
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
const totalCost = createMemo(() => {
|
|
136
|
+
const state = ((props.api.state as any).session)?.get?.(props.sessionID)
|
|
137
|
+
const fromState = msgCost(state)
|
|
138
|
+
if (fromState > 0) return fromState
|
|
139
|
+
return messages().reduce((sum: number, m: any) => sum + msgCost(m), 0)
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
const theme = () => props.api.theme.current
|
|
143
|
+
|
|
144
|
+
return (
|
|
145
|
+
<Show when={usage()}>
|
|
146
|
+
{(u) => {
|
|
147
|
+
const pct = u().percent
|
|
148
|
+
const c = theme()
|
|
149
|
+
const barColor = pct >= 90 ? c.error : pct >= 70 ? c.warning : c.accent
|
|
150
|
+
const costStr = fmtCost(totalCost())
|
|
151
|
+
return (
|
|
152
|
+
<box flexDirection="column">
|
|
153
|
+
<box flexDirection="row" gap={1}>
|
|
154
|
+
<text fg={c.text} attributes={{ bold: true }}>Context</text>
|
|
155
|
+
<text fg={barColor}>{buildBar(pct)}</text>
|
|
156
|
+
<text fg={barColor}>{`${pct}%`}</text>
|
|
157
|
+
</box>
|
|
158
|
+
<text fg={c.textMuted}>
|
|
159
|
+
{`${fmtInt(u().tokens)} / ${fmtInt(u().limit)} tok${costStr ? ` ${costStr}` : ''}`}
|
|
160
|
+
</text>
|
|
161
|
+
</box>
|
|
162
|
+
)
|
|
163
|
+
}}
|
|
164
|
+
</Show>
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/* ─── Sessions List (loaded on expand) ───────────────────── */
|
|
169
|
+
|
|
170
|
+
async function fetchRecentSessions(api: TuiPluginApi, sessionID: string): Promise<any[] | null> {
|
|
171
|
+
try {
|
|
172
|
+
const proc = (api as any).client?.process
|
|
173
|
+
if (typeof proc?.exec !== 'function') return null
|
|
174
|
+
const result = await proc.exec({
|
|
175
|
+
command: 'opencode',
|
|
176
|
+
args: ['session', 'list', '--format', 'json', '--max-count', '20'],
|
|
177
|
+
timeoutMs: 5000,
|
|
178
|
+
})
|
|
179
|
+
const stdout = (result.stdout ?? result.output ?? '') as string
|
|
180
|
+
const sessions = JSON.parse(stdout)
|
|
181
|
+
if (!Array.isArray(sessions)) return null
|
|
182
|
+
return sessions
|
|
183
|
+
.filter((s: any) => s.id !== sessionID)
|
|
184
|
+
.sort((a: any, b: any) => (b.updated ?? 0) - (a.updated ?? 0))
|
|
185
|
+
.slice(0, 8)
|
|
186
|
+
} catch { return null }
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/* ─── Main Sidebar View ──────────────────────────────────── */
|
|
190
|
+
|
|
191
|
+
function View(props: { api: TuiPluginApi; sessionID: string; version: string | null }) {
|
|
67
192
|
const [showCommands, setShowCommands] = createSignal(false)
|
|
68
193
|
const [showAgents, setShowAgents] = createSignal(false)
|
|
69
194
|
const [showConfig, setShowConfig] = createSignal(false)
|
|
70
|
-
const [
|
|
71
|
-
const [showSubagents, setShowSubagents] = createSignal(false)
|
|
195
|
+
const [showSessions, setShowSessions] = createSignal(false)
|
|
72
196
|
|
|
73
197
|
const theme = () => props.api.theme.current
|
|
74
198
|
|
|
@@ -76,166 +200,157 @@ function View(props: { api: TuiPluginApi; sessionID: string; version: string })
|
|
|
76
200
|
props.api.state.vcs?.branch ? `\u2387 ${props.api.state.vcs.branch}` : null,
|
|
77
201
|
)
|
|
78
202
|
|
|
79
|
-
const
|
|
203
|
+
const totalSessions = createMemo(() => {
|
|
204
|
+
try { return props.api.state.session.count() } catch { return 0 }
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
const memoryCount = createMemo(() => {
|
|
208
|
+
const mem = (props.api.state as any).memory
|
|
209
|
+
return safeNum(mem?.entries) || safeNum(mem?.count)
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
const configSummary = createMemo(() => {
|
|
80
213
|
const cfg = (props.api.state as any).config
|
|
81
214
|
if (!cfg) return null
|
|
82
215
|
return {
|
|
83
|
-
plugins: Array.isArray(cfg.plugin) ? cfg.plugin : [],
|
|
84
216
|
mcpCount: cfg.mcp ? Object.keys(cfg.mcp).length : 0,
|
|
217
|
+
pluginCount: Array.isArray(cfg.plugin) ? cfg.plugin.length : 0,
|
|
85
218
|
autoCompaction: cfg.compaction?.auto === true,
|
|
86
219
|
}
|
|
87
220
|
})
|
|
88
221
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
return entries > 0 ? { entries } : null
|
|
94
|
-
}
|
|
95
|
-
return null
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
const [subagents] = createResource(
|
|
99
|
-
() => showSubagents(),
|
|
100
|
-
async () => {
|
|
101
|
-
try {
|
|
102
|
-
const proc = (props.api as any).client?.process
|
|
103
|
-
if (typeof proc?.exec !== 'function') return null
|
|
104
|
-
const result = await proc.exec({
|
|
105
|
-
command: 'opencode',
|
|
106
|
-
args: ['session', 'list', '--format', 'json'],
|
|
107
|
-
timeoutMs: 5000,
|
|
108
|
-
})
|
|
109
|
-
const stdout = (result.stdout ?? result.output ?? '') as string
|
|
110
|
-
const sessions = JSON.parse(stdout)
|
|
111
|
-
if (!Array.isArray(sessions)) return null
|
|
112
|
-
return sessions
|
|
113
|
-
.filter((s: any) => s.id !== props.sessionID)
|
|
114
|
-
.sort((a: any, b: any) => (b.updated ?? 0) - (a.updated ?? 0))
|
|
115
|
-
.slice(0, 10)
|
|
116
|
-
} catch { return null }
|
|
117
|
-
},
|
|
222
|
+
// Load session list only when expanded
|
|
223
|
+
const [sessions] = createResource(
|
|
224
|
+
() => showSessions(),
|
|
225
|
+
() => fetchRecentSessions(props.api, props.sessionID),
|
|
118
226
|
)
|
|
119
227
|
|
|
228
|
+
const HR = () => <text fg={theme().textMuted}>{'\u2500'.repeat(28)}</text>
|
|
229
|
+
|
|
120
230
|
return (
|
|
121
231
|
<box flexDirection="column" width="100%">
|
|
122
|
-
{/* Header */}
|
|
232
|
+
{/* ── Header ── */}
|
|
123
233
|
<text fg={theme().accent} attributes={{ bold: true }}>
|
|
124
|
-
{`Pantheon v${props.version}`}
|
|
234
|
+
{`Pantheon${props.version ? ` v${props.version}` : ''}`}
|
|
125
235
|
</text>
|
|
126
|
-
|
|
127
236
|
<Show when={branch()}>
|
|
128
|
-
{(b) =>
|
|
237
|
+
{(b) => <text fg={theme().textMuted}>{b()}</text>}
|
|
129
238
|
</Show>
|
|
130
239
|
|
|
131
|
-
|
|
132
|
-
|
|
240
|
+
<HR />
|
|
241
|
+
|
|
242
|
+
{/* ── Context Bar (always visible) ── */}
|
|
243
|
+
<ContextBar api={props.api} sessionID={props.sessionID} />
|
|
244
|
+
|
|
245
|
+
<HR />
|
|
246
|
+
|
|
247
|
+
{/* ── Sessions ── */}
|
|
248
|
+
<box onMouseDown={() => setShowSessions((x) => !x)}>
|
|
133
249
|
<text fg={theme().text} attributes={{ bold: true }}>
|
|
134
|
-
{`${
|
|
250
|
+
{`${showSessions() ? '\u25bc' : '\u25b6'} Sessions`}
|
|
135
251
|
</text>
|
|
136
|
-
<text fg={theme().textMuted}>{` (${String(
|
|
252
|
+
<text fg={theme().textMuted}>{` (${String(totalSessions())})`}</text>
|
|
137
253
|
</box>
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
</Show>
|
|
254
|
+
<Show when={showSessions() && sessions()}>
|
|
255
|
+
{(s) => (
|
|
256
|
+
<Show when={s().length > 0} fallback={<box marginLeft={1}><text fg={theme().textMuted}>No recent sessions</text></box>}>
|
|
257
|
+
<box marginLeft={1} flexDirection="column">
|
|
258
|
+
<For each={s()}>
|
|
259
|
+
{(ses: any) => (
|
|
260
|
+
<text fg={theme().textMuted}>
|
|
261
|
+
{`${ses.id.slice(0, 8)}... ${ses.title?.slice(0, 28) ?? '(untitled)'}`}
|
|
262
|
+
</text>
|
|
263
|
+
)}
|
|
264
|
+
</For>
|
|
265
|
+
</box>
|
|
266
|
+
</Show>
|
|
267
|
+
)}
|
|
153
268
|
</Show>
|
|
154
269
|
|
|
155
|
-
{/* Commands */}
|
|
156
|
-
<box
|
|
270
|
+
{/* ── Commands ── */}
|
|
271
|
+
<box onMouseDown={() => setShowCommands((x) => !x)}>
|
|
157
272
|
<text fg={theme().text} attributes={{ bold: true }}>
|
|
158
273
|
{`${showCommands() ? '\u25bc' : '\u25b6'} Commands`}
|
|
159
274
|
</text>
|
|
160
275
|
<text fg={theme().textMuted}>{` (${String(COMMANDS.length)})`}</text>
|
|
161
276
|
</box>
|
|
162
|
-
|
|
163
277
|
<Show when={showCommands()}>
|
|
164
|
-
<
|
|
165
|
-
{
|
|
166
|
-
|
|
167
|
-
e
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
278
|
+
<box marginLeft={1} flexDirection="column">
|
|
279
|
+
<For each={COMMANDS}>
|
|
280
|
+
{(cmd) => (
|
|
281
|
+
<box onMouseDown={(e) => {
|
|
282
|
+
e.stopPropagation()
|
|
283
|
+
try {
|
|
284
|
+
const cmdApi = (props.api as any).command
|
|
285
|
+
const cmdName = cmd.name.replace('/', '')
|
|
286
|
+
if (cmdApi?.trigger?.(cmdName)) return
|
|
287
|
+
} catch { /* fall through to toast */ }
|
|
288
|
+
props.api.ui?.toast?.({ title: 'Command', message: `Type ${cmd.name} in chat` })
|
|
289
|
+
}}>
|
|
290
|
+
<text fg={cmd.name === '/pantheon' ? theme().accent : theme().textMuted}>
|
|
291
|
+
{cmd.name}
|
|
292
|
+
</text>
|
|
293
|
+
<text fg={theme().textMuted}>{` \u2014 ${cmd.desc}`}</text>
|
|
294
|
+
</box>
|
|
295
|
+
)}
|
|
296
|
+
</For>
|
|
297
|
+
</box>
|
|
180
298
|
</Show>
|
|
181
299
|
|
|
182
|
-
{/* Agents */}
|
|
183
|
-
<box
|
|
300
|
+
{/* ── Agents ── */}
|
|
301
|
+
<box onMouseDown={() => setShowAgents((x) => !x)}>
|
|
184
302
|
<text fg={theme().text} attributes={{ bold: true }}>
|
|
185
303
|
{`${showAgents() ? '\u25bc' : '\u25b6'} Agents`}
|
|
186
304
|
</text>
|
|
187
305
|
<text fg={theme().textMuted}>{` (${String(AGENTS.length)})`}</text>
|
|
188
306
|
</box>
|
|
189
|
-
|
|
190
307
|
<Show when={showAgents()}>
|
|
191
|
-
<
|
|
192
|
-
{
|
|
193
|
-
|
|
194
|
-
<
|
|
195
|
-
{
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
308
|
+
<box marginLeft={1} flexDirection="column">
|
|
309
|
+
<For each={AGENTS}>
|
|
310
|
+
{(agent) => (
|
|
311
|
+
<box>
|
|
312
|
+
<text fg={agent.tier === 'premium' ? theme().accent : theme().textMuted}>
|
|
313
|
+
{`${agent.tier === 'premium' ? '\u2726 ' : '\u00b7 '}${agent.name}`}
|
|
314
|
+
</text>
|
|
315
|
+
<text fg={theme().textMuted}>{` \u2014 ${agent.role}`}</text>
|
|
316
|
+
</box>
|
|
317
|
+
)}
|
|
318
|
+
</For>
|
|
319
|
+
</box>
|
|
201
320
|
</Show>
|
|
202
321
|
|
|
203
|
-
{/* Config */}
|
|
204
|
-
<box
|
|
322
|
+
{/* ── Config ── */}
|
|
323
|
+
<box onMouseDown={() => setShowConfig((x) => !x)}>
|
|
205
324
|
<text fg={theme().text} attributes={{ bold: true }}>
|
|
206
325
|
{`${showConfig() ? '\u25bc' : '\u25b6'} Config`}
|
|
207
326
|
</text>
|
|
208
327
|
</box>
|
|
209
|
-
|
|
210
328
|
<Show when={showConfig()}>
|
|
211
|
-
<Show when={
|
|
329
|
+
<Show when={configSummary()} fallback={<box marginLeft={1}><text fg={theme().textMuted}>No config data</text></box>}>
|
|
212
330
|
{(cfg) => (
|
|
213
331
|
<box marginLeft={1} flexDirection="column">
|
|
214
|
-
<text fg={theme().textMuted}>{`MCP
|
|
332
|
+
<text fg={theme().textMuted}>{`MCP: ${String(cfg().mcpCount)} Plugins: ${String(cfg().pluginCount)}`}</text>
|
|
215
333
|
<text fg={theme().textMuted}>{`Auto-compaction: ${cfg().autoCompaction ? 'ON' : 'OFF'}`}</text>
|
|
216
334
|
</box>
|
|
217
335
|
)}
|
|
218
336
|
</Show>
|
|
219
337
|
</Show>
|
|
220
338
|
|
|
221
|
-
{/* Memory */}
|
|
222
|
-
<box marginTop={
|
|
223
|
-
<text fg={theme().
|
|
224
|
-
{
|
|
339
|
+
{/* ── Memory (always visible summary) ── */}
|
|
340
|
+
<box marginTop={1}>
|
|
341
|
+
<text fg={theme().textMuted}>
|
|
342
|
+
{`Memory: ${memoryCount() > 0 ? `${fmtInt(memoryCount())} entries` : '0 entries'}`}
|
|
225
343
|
</text>
|
|
226
344
|
</box>
|
|
227
|
-
|
|
228
|
-
<Show when={showMemory()}>
|
|
229
|
-
<Show when={memoryInfo()} fallback={<box marginLeft={1}><text fg={theme().textMuted}>(no data)</text></box>}>
|
|
230
|
-
{(mem) => (<box marginLeft={1}><text fg={theme().textMuted}>{`Entries: ${String(mem().entries)}`}</text></box>)}
|
|
231
|
-
</Show>
|
|
232
|
-
</Show>
|
|
233
345
|
</box>
|
|
234
346
|
)
|
|
235
347
|
}
|
|
236
348
|
|
|
349
|
+
/* ─── Plugin Registration ────────────────────────────────── */
|
|
350
|
+
|
|
237
351
|
const tui: TuiPlugin = async (api, _options, _meta) => {
|
|
238
|
-
const version = await
|
|
352
|
+
const version = await detectVersion(api)
|
|
353
|
+
|
|
239
354
|
api.slots.register({
|
|
240
355
|
order: 900,
|
|
241
356
|
slots: {
|