pantheon-opencode 1.2.0-beta.9.f05c809 → 1.2.1
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 +101 -3
- package/bin/pantheon-init.mjs +79 -0
- package/commands/pantheon-vision.md +67 -0
- package/opencode.json +13 -9
- package/package.json +9 -2
- package/scripts/_pantheon_paths.py +0 -2
- package/scripts/doctor.mjs +4 -2
- package/scripts/install/health-check.mjs +101 -1
- package/scripts/install/model-picker.mjs +118 -0
- package/scripts/install/opencode.mjs +102 -19
- package/scripts/install/venv.mjs +25 -2
- package/scripts/install-mcp.mjs +98 -13
- package/scripts/mcp_persistence_server.py +1 -1
- package/scripts/secret-scan.mjs +88 -0
- package/scripts/uninstall.mjs +3 -3
- package/scripts/validate-routing.mjs +15 -0
- package/src/agents/hermes.md +2 -2
- package/src/agents/mnemosyne.md +1 -1
- package/src/agents/zeus.md +1 -1
- package/src/mcp/_pantheon_paths.py +0 -2
- package/src/mcp/install-mcp.mjs +6 -913
- package/src/mcp/mcp_persistence_server.py +2 -1
- package/src/mcp/pantheon_vision_server.py +495 -0
- package/src/mcp/requirements-vision.txt +3 -0
- package/src/pantheon/auto-wake.ts +91 -0
- package/src/pantheon/background-job-board.ts +568 -0
- package/src/pantheon/file-persistence.ts +95 -0
- package/src/pantheon/presets.d.mts +108 -0
- package/src/pantheon/presets.mjs +554 -0
- package/src/pantheon/vision.ts +1437 -0
- package/src/plugin.ts +69 -25
- package/src/plugins/tui/package.json +3 -3
- package/src/plugins/tui/src/index.tsx +866 -121
- package/src/routing.yml +148 -0
- package/src/skills/context-compression/SKILL.md +1 -1
- package/src/skills/loop-engineering/SKILL.md +1 -1
- package/src/skills/orchestration-workflow/SKILL.md +11 -11
- package/scripts/scrub_secrets.py +0 -281
- package/src/plugins/tui/dist/tui.d.ts +0 -8
- package/src/plugins/tui/dist/tui.d.ts.map +0 -1
- package/src/plugins/tui/dist/tui.js +0 -609
- package/src/plugins/tui/dist/tui.js.map +0 -1
- package/src/plugins/tui/dist/tui.tsx +0 -444
|
@@ -1,444 +0,0 @@
|
|
|
1
|
-
// biome-ignore-all lint/suspicious/noExplicitAny: necessary for TuiPluginApi dynamic state access
|
|
2
|
-
// biome-ignore-all lint/a11y/noStaticElementInteractions: TUI elements, not DOM
|
|
3
|
-
/** @jsxImportSource @opentui/solid */
|
|
4
|
-
|
|
5
|
-
import type { TuiPlugin, TuiPluginApi, TuiPluginModule } from '@opencode-ai/plugin/tui'
|
|
6
|
-
import { createMemo, createResource, createSignal, For, onCleanup, onMount, Show } from 'solid-js'
|
|
7
|
-
|
|
8
|
-
/* ─── Constants ─────────────────────────────────────────── */
|
|
9
|
-
|
|
10
|
-
const BAR_WIDTH = 20
|
|
11
|
-
|
|
12
|
-
const COMMANDS = [
|
|
13
|
-
{ name: '/pantheon', desc: 'Council synthesis' },
|
|
14
|
-
{ name: '/pantheon-status', desc: 'System status' },
|
|
15
|
-
{ name: '/pantheon-audit', desc: 'Full audit' },
|
|
16
|
-
{ name: '/pantheon-bg', desc: 'List background tasks' },
|
|
17
|
-
{ name: '/pantheon-cancel', desc: 'Cancel task' },
|
|
18
|
-
{ name: '/pantheon-deepwork', desc: 'Deep work mode' },
|
|
19
|
-
{ name: '/pantheon-focus', desc: 'Focus on scope' },
|
|
20
|
-
{ name: '/pantheon-remember', desc: 'Memory store/recall' },
|
|
21
|
-
{ name: '/pantheon-search', desc: 'Memory search' },
|
|
22
|
-
{ name: '/pantheon-consolidate', desc: 'Merge memories' },
|
|
23
|
-
{ name: '/pantheon-forget', desc: 'Compress memories' },
|
|
24
|
-
] as const
|
|
25
|
-
|
|
26
|
-
const AGENTS = [
|
|
27
|
-
{ name: 'zeus', tier: 'default' as const, role: 'Orchestrator' },
|
|
28
|
-
{ name: 'athena', tier: 'premium' as const, role: 'Strategic planner' },
|
|
29
|
-
{ name: 'apollo', tier: 'fast' as const, role: 'Codebase discovery' },
|
|
30
|
-
{ name: 'hermes', tier: 'default' as const, role: 'Backend' },
|
|
31
|
-
{ name: 'aphrodite', tier: 'default' as const, role: 'Frontend' },
|
|
32
|
-
{ name: 'demeter', tier: 'default' as const, role: 'Database' },
|
|
33
|
-
{ name: 'themis', tier: 'premium' as const, role: 'Quality & security' },
|
|
34
|
-
{ name: 'prometheus', tier: 'default' as const, role: 'Infrastructure' },
|
|
35
|
-
{ name: 'hephaestus', tier: 'default' as const, role: 'AI pipelines' },
|
|
36
|
-
{ name: 'nyx', tier: 'fast' as const, role: 'Observability' },
|
|
37
|
-
{ name: 'gaia', tier: 'fast' as const, role: 'Remote sensing' },
|
|
38
|
-
{ name: 'iris', tier: 'fast' as const, role: 'GitHub operations' },
|
|
39
|
-
{ name: 'mnemosyne', tier: 'fast' as const, role: 'Memory bank' },
|
|
40
|
-
{ name: 'talos', tier: 'fast' as const, role: 'Hotfixes' },
|
|
41
|
-
] as const
|
|
42
|
-
|
|
43
|
-
/* ─── Helpers ───────────────────────────────────────────── */
|
|
44
|
-
|
|
45
|
-
function safeNum(v: unknown): number {
|
|
46
|
-
return typeof v === 'number' && Number.isFinite(v) ? v : 0
|
|
47
|
-
}
|
|
48
|
-
|
|
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
|
-
* Try order:
|
|
83
|
-
* 1. api.client.file.read package.json
|
|
84
|
-
* 2. git describe --tags
|
|
85
|
-
* 3. opencode --version
|
|
86
|
-
* 4. null */
|
|
87
|
-
|
|
88
|
-
async function detectVersion(api: TuiPluginApi): Promise<string | null> {
|
|
89
|
-
// Try 1: package.json do projeto (via worktree path)
|
|
90
|
-
try {
|
|
91
|
-
const wt = ((api.state as any).path?.worktree ?? '') as string
|
|
92
|
-
const fp = wt ? `${wt}/package.json` : 'package.json'
|
|
93
|
-
const result = await api.client.file.read({ query: { path: fp } })
|
|
94
|
-
const match = String(result?.content ?? '').match(/"version":\s*"([^"]+)"/)
|
|
95
|
-
if (match?.[1]) return match[1]
|
|
96
|
-
} catch { /* fall through */ }
|
|
97
|
-
|
|
98
|
-
// Try 2: git describe --tags
|
|
99
|
-
try {
|
|
100
|
-
const proc = (api as any).client?.process
|
|
101
|
-
if (typeof proc?.exec === 'function') {
|
|
102
|
-
const r = await proc.exec({ command: 'git', args: ['describe', '--tags', '--always'], timeoutMs: 3000 })
|
|
103
|
-
const stdout = (r.stdout ?? r.output ?? '') as string
|
|
104
|
-
const tag = stdout.trim().replace(/^v/, '').replace(/-\d+-g[0-9a-f]+$/, '')
|
|
105
|
-
if (tag && tag !== '5.0.0') return tag
|
|
106
|
-
}
|
|
107
|
-
} catch { /* fall through */ }
|
|
108
|
-
|
|
109
|
-
// Try 3: opencode --version
|
|
110
|
-
try {
|
|
111
|
-
const proc = (api as any).client?.process
|
|
112
|
-
if (typeof proc?.exec === 'function') {
|
|
113
|
-
const r = await proc.exec({ command: 'opencode', args: ['--version'], timeoutMs: 3000 })
|
|
114
|
-
const stdout = (r.stdout ?? r.output ?? '') as string
|
|
115
|
-
const ver = stdout.trim().replace(/^v/, '')
|
|
116
|
-
if (ver) return ver
|
|
117
|
-
}
|
|
118
|
-
} catch { /* fall through */ }
|
|
119
|
-
|
|
120
|
-
return null // sem fallback — versão não aparece
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/* ─── Context Bar ──────────────────────────────────────────
|
|
124
|
-
* Inspired by streetturtle/opencode-better-sidebar (context-progress)
|
|
125
|
-
* Shows: progress bar, token usage / limit, session cost */
|
|
126
|
-
|
|
127
|
-
function ContextBar(props: { api: TuiPluginApi; sessionID: string }) {
|
|
128
|
-
const messages = createMemo((): any[] => {
|
|
129
|
-
try { return (props.api.state.session as any).messages(props.sessionID) ?? [] }
|
|
130
|
-
catch { return [] }
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
const usage = createMemo(() => {
|
|
134
|
-
const msgs = messages()
|
|
135
|
-
const lastAssistant = [...msgs].reverse().find((m: any) => {
|
|
136
|
-
const role = m?.role ?? m?.info?.role
|
|
137
|
-
return role === 'assistant' && safeNum(m?.tokens?.output) > 0
|
|
138
|
-
})
|
|
139
|
-
if (!lastAssistant) return null
|
|
140
|
-
|
|
141
|
-
const tokens = msgTokens(lastAssistant)
|
|
142
|
-
const pid = lastAssistant?.providerID ?? lastAssistant?.info?.providerID
|
|
143
|
-
const mid = lastAssistant?.modelID ?? lastAssistant?.info?.modelID
|
|
144
|
-
const provider = pid
|
|
145
|
-
? ((props.api.state as any).provider ?? []).find((p: any) => p.id === pid)
|
|
146
|
-
: null
|
|
147
|
-
const ctxLimit = safeNum(provider?.models?.[mid ?? '']?.limit?.context)
|
|
148
|
-
const limit = ctxLimit > 0 ? ctxLimit : 200000
|
|
149
|
-
return { tokens, limit, percent: ctxLimit > 0 ? Math.round((tokens / ctxLimit) * 100) : 0 }
|
|
150
|
-
})
|
|
151
|
-
|
|
152
|
-
const totalCost = createMemo(() => {
|
|
153
|
-
const state = ((props.api.state as any).session)?.get?.(props.sessionID)
|
|
154
|
-
const fromState = msgCost(state)
|
|
155
|
-
if (fromState > 0) return fromState
|
|
156
|
-
return messages().reduce((sum: number, m: any) => sum + msgCost(m), 0)
|
|
157
|
-
})
|
|
158
|
-
|
|
159
|
-
const theme = () => props.api.theme.current
|
|
160
|
-
|
|
161
|
-
return (
|
|
162
|
-
<Show when={usage()}>
|
|
163
|
-
{(u) => {
|
|
164
|
-
const pct = u().percent
|
|
165
|
-
const c = theme()
|
|
166
|
-
const barColor = pct >= 90 ? c.error : pct >= 70 ? c.warning : c.accent
|
|
167
|
-
const costStr = fmtCost(totalCost())
|
|
168
|
-
return (
|
|
169
|
-
<box flexDirection="column">
|
|
170
|
-
<box flexDirection="row" gap={1}>
|
|
171
|
-
<text fg={c.text} attributes={{ bold: true }}>Context</text>
|
|
172
|
-
<text fg={barColor}>{buildBar(pct)}</text>
|
|
173
|
-
<text fg={barColor}>{`${pct}%`}</text>
|
|
174
|
-
</box>
|
|
175
|
-
<text fg={c.textMuted}>
|
|
176
|
-
{`${fmtInt(u().tokens)} / ${fmtInt(u().limit)} tok${costStr ? ` ${costStr}` : ''}`}
|
|
177
|
-
</text>
|
|
178
|
-
</box>
|
|
179
|
-
)
|
|
180
|
-
}}
|
|
181
|
-
</Show>
|
|
182
|
-
)
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/* ─── Session Row (single session with live status indicator) ─ */
|
|
186
|
-
|
|
187
|
-
function SessionRow(props: { api: TuiPluginApi; session: any }) {
|
|
188
|
-
const theme = () => props.api.theme.current
|
|
189
|
-
|
|
190
|
-
const status = createMemo(() => {
|
|
191
|
-
try {
|
|
192
|
-
const s = (props.api.state as any).session?.status?.(props.session.id)
|
|
193
|
-
if (s?.type) return s.type as string
|
|
194
|
-
} catch { /* ignore */ }
|
|
195
|
-
return null
|
|
196
|
-
})
|
|
197
|
-
|
|
198
|
-
const statusIcon = createMemo(() => {
|
|
199
|
-
switch (status()) {
|
|
200
|
-
case 'busy': return '\u25cf ' // ● — running
|
|
201
|
-
case 'retry': return '\u26a0 ' // ⚠ — needs attention
|
|
202
|
-
default: return '' // idle/unknown — no icon
|
|
203
|
-
}
|
|
204
|
-
})
|
|
205
|
-
|
|
206
|
-
return (
|
|
207
|
-
<text fg={theme().textMuted}>
|
|
208
|
-
{`${statusIcon()}${props.session?.id?.slice(0, 8) ?? '????'}... ${props.session?.title?.slice(0, 26) ?? '(untitled)'}`}
|
|
209
|
-
</text>
|
|
210
|
-
)
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/* ─── Main Sidebar View ──────────────────────────────────── */
|
|
214
|
-
|
|
215
|
-
function View(props: { api: TuiPluginApi; sessionID: string; version: string | null }) {
|
|
216
|
-
const [showCommands, setShowCommands] = createSignal(false)
|
|
217
|
-
const [showAgents, setShowAgents] = createSignal(false)
|
|
218
|
-
const [showConfig, setShowConfig] = createSignal(false)
|
|
219
|
-
const [showSessions, setShowSessions] = createSignal(false)
|
|
220
|
-
|
|
221
|
-
const theme = () => props.api.theme.current
|
|
222
|
-
|
|
223
|
-
const branch = createMemo(() =>
|
|
224
|
-
props.api.state.vcs?.branch ? `\u2387 ${props.api.state.vcs.branch}` : null,
|
|
225
|
-
)
|
|
226
|
-
|
|
227
|
-
// ── Sessions: fetch via api.client.session.list (not proc.exec) ──
|
|
228
|
-
const [sessionList, { refetch: refetchSessions }] = createResource(async () => {
|
|
229
|
-
try {
|
|
230
|
-
const result = await (props.api.client as any)?.session?.list?.({ limit: 100 })
|
|
231
|
-
return result ?? { data: [] }
|
|
232
|
-
} catch {
|
|
233
|
-
return { data: [] }
|
|
234
|
-
}
|
|
235
|
-
})
|
|
236
|
-
|
|
237
|
-
// Total top-level sessions (excludes subagent sessions)
|
|
238
|
-
const totalSessions = createMemo(() => {
|
|
239
|
-
const result = sessionList()
|
|
240
|
-
if (!result) return 0
|
|
241
|
-
const data: any[] = (result as any).data ?? result
|
|
242
|
-
if (!Array.isArray(data)) return 0
|
|
243
|
-
return data.filter((s: any) => !s.parentID).length
|
|
244
|
-
})
|
|
245
|
-
|
|
246
|
-
// Recent top-level sessions, sorted by time.updated descending
|
|
247
|
-
const recentSessions = createMemo(() => {
|
|
248
|
-
const result = sessionList()
|
|
249
|
-
if (!result) return []
|
|
250
|
-
const data: any[] = (result as any).data ?? result
|
|
251
|
-
if (!Array.isArray(data)) return []
|
|
252
|
-
return data
|
|
253
|
-
.filter((s: any) => !s.parentID && s.id !== props.sessionID)
|
|
254
|
-
.sort((a: any, b: any) => {
|
|
255
|
-
const ta = a.time?.updated ?? a.updated ?? 0
|
|
256
|
-
const tb = b.time?.updated ?? b.updated ?? 0
|
|
257
|
-
return tb - ta
|
|
258
|
-
})
|
|
259
|
-
.slice(0, 8)
|
|
260
|
-
})
|
|
261
|
-
|
|
262
|
-
// ── Memory: starts at null (= "N/A"), tracks via events ──
|
|
263
|
-
const [memoryCount, setMemoryCount] = createSignal<number | null>(null)
|
|
264
|
-
|
|
265
|
-
// ── Event subscriptions for live session/memory updates ──
|
|
266
|
-
onMount(() => {
|
|
267
|
-
const cleanup: (() => void)[] = []
|
|
268
|
-
|
|
269
|
-
// Session events — triggers resource refetch
|
|
270
|
-
try {
|
|
271
|
-
cleanup.push(props.api.event.on('session.status', refetchSessions))
|
|
272
|
-
cleanup.push(props.api.event.on('session.created', refetchSessions))
|
|
273
|
-
cleanup.push(props.api.event.on('session.updated', refetchSessions))
|
|
274
|
-
cleanup.push(props.api.event.on('session.deleted', refetchSessions))
|
|
275
|
-
} catch { /* events API not available in this runtime */ }
|
|
276
|
-
|
|
277
|
-
// Memory events — tracks running counter (no api.state.memory)
|
|
278
|
-
try {
|
|
279
|
-
const ev = (props.api.event as any)
|
|
280
|
-
if (typeof ev?.on === 'function') {
|
|
281
|
-
cleanup.push(
|
|
282
|
-
ev.on('memory.created', () => setMemoryCount((c) => (c !== null ? c + 1 : 1))),
|
|
283
|
-
)
|
|
284
|
-
cleanup.push(
|
|
285
|
-
ev.on('memory.deleted', () => setMemoryCount((c) => (c !== null ? Math.max(0, c - 1) : 0))),
|
|
286
|
-
)
|
|
287
|
-
}
|
|
288
|
-
} catch { /* memory events not available */ }
|
|
289
|
-
|
|
290
|
-
onCleanup(() => cleanup.forEach((fn) => fn()))
|
|
291
|
-
})
|
|
292
|
-
|
|
293
|
-
// ── Config summary ──
|
|
294
|
-
const configSummary = createMemo(() => {
|
|
295
|
-
const cfg = (props.api.state as any).config
|
|
296
|
-
if (!cfg) return null
|
|
297
|
-
return {
|
|
298
|
-
mcpCount: cfg.mcp ? Object.keys(cfg.mcp).length : 0,
|
|
299
|
-
pluginCount: Array.isArray(cfg.plugin) ? cfg.plugin.length : 0,
|
|
300
|
-
autoCompaction: cfg.compaction?.auto === true,
|
|
301
|
-
}
|
|
302
|
-
})
|
|
303
|
-
|
|
304
|
-
const HR = () => <text fg={theme().textMuted}>{'\u2500'.repeat(28)}</text>
|
|
305
|
-
|
|
306
|
-
return (
|
|
307
|
-
<box flexDirection="column" width="100%">
|
|
308
|
-
{/* ── Header ── */}
|
|
309
|
-
<text fg={theme().accent} attributes={{ bold: true }}>
|
|
310
|
-
{`Pantheon${props.version ? ` v${props.version}` : ''}`}
|
|
311
|
-
</text>
|
|
312
|
-
<Show when={branch()}>
|
|
313
|
-
{(b) => <text fg={theme().textMuted}>{b()}</text>}
|
|
314
|
-
</Show>
|
|
315
|
-
|
|
316
|
-
<HR />
|
|
317
|
-
|
|
318
|
-
{/* ── Context Bar (always visible) ── */}
|
|
319
|
-
<ContextBar api={props.api} sessionID={props.sessionID} />
|
|
320
|
-
|
|
321
|
-
<HR />
|
|
322
|
-
|
|
323
|
-
{/* ── Sessions ── */}
|
|
324
|
-
<box onMouseDown={() => setShowSessions((x) => !x)}>
|
|
325
|
-
<text fg={theme().text} attributes={{ bold: true }}>
|
|
326
|
-
{`${showSessions() ? '\u25bc' : '\u25b6'} Sessions`}
|
|
327
|
-
</text>
|
|
328
|
-
<text fg={theme().textMuted}>{` (${String(totalSessions())})`}</text>
|
|
329
|
-
</box>
|
|
330
|
-
<Show when={showSessions()}>
|
|
331
|
-
<Show
|
|
332
|
-
when={recentSessions().length > 0}
|
|
333
|
-
fallback={<box marginLeft={1}><text fg={theme().textMuted}>No recent sessions</text></box>}
|
|
334
|
-
>
|
|
335
|
-
<box marginLeft={1} flexDirection="column">
|
|
336
|
-
<For each={recentSessions()}>
|
|
337
|
-
{(ses: any) => <SessionRow api={props.api} session={ses} />}
|
|
338
|
-
</For>
|
|
339
|
-
</box>
|
|
340
|
-
</Show>
|
|
341
|
-
</Show>
|
|
342
|
-
|
|
343
|
-
{/* ── Commands ── */}
|
|
344
|
-
<box onMouseDown={() => setShowCommands((x) => !x)}>
|
|
345
|
-
<text fg={theme().text} attributes={{ bold: true }}>
|
|
346
|
-
{`${showCommands() ? '\u25bc' : '\u25b6'} Commands`}
|
|
347
|
-
</text>
|
|
348
|
-
<text fg={theme().textMuted}>{` (${String(COMMANDS.length)})`}</text>
|
|
349
|
-
</box>
|
|
350
|
-
<Show when={showCommands()}>
|
|
351
|
-
<box marginLeft={1} flexDirection="column">
|
|
352
|
-
<For each={COMMANDS}>
|
|
353
|
-
{(cmd) => (
|
|
354
|
-
<box onMouseDown={(e) => {
|
|
355
|
-
e.stopPropagation()
|
|
356
|
-
try {
|
|
357
|
-
const cmdApi = (props.api as any).command
|
|
358
|
-
const cmdName = cmd.name.replace('/', '')
|
|
359
|
-
if (cmdApi?.trigger?.(cmdName)) return
|
|
360
|
-
} catch { /* fall through to toast */ }
|
|
361
|
-
props.api.ui?.toast?.({ title: 'Command', message: `Type ${cmd.name} in chat` })
|
|
362
|
-
}}>
|
|
363
|
-
<text fg={cmd.name === '/pantheon' ? theme().accent : theme().textMuted}>
|
|
364
|
-
{cmd.name}
|
|
365
|
-
</text>
|
|
366
|
-
<text fg={theme().textMuted}>{` \u2014 ${cmd.desc}`}</text>
|
|
367
|
-
</box>
|
|
368
|
-
)}
|
|
369
|
-
</For>
|
|
370
|
-
</box>
|
|
371
|
-
</Show>
|
|
372
|
-
|
|
373
|
-
{/* ── Agents ── */}
|
|
374
|
-
<box onMouseDown={() => setShowAgents((x) => !x)}>
|
|
375
|
-
<text fg={theme().text} attributes={{ bold: true }}>
|
|
376
|
-
{`${showAgents() ? '\u25bc' : '\u25b6'} Agents`}
|
|
377
|
-
</text>
|
|
378
|
-
<text fg={theme().textMuted}>{` (${String(AGENTS.length)})`}</text>
|
|
379
|
-
</box>
|
|
380
|
-
<Show when={showAgents()}>
|
|
381
|
-
<box marginLeft={1} flexDirection="column">
|
|
382
|
-
<For each={AGENTS}>
|
|
383
|
-
{(agent) => (
|
|
384
|
-
<box>
|
|
385
|
-
<text fg={agent.tier === 'premium' ? theme().accent : theme().textMuted}>
|
|
386
|
-
{`${agent.tier === 'premium' ? '\u2726 ' : '\u00b7 '}${agent.name}`}
|
|
387
|
-
</text>
|
|
388
|
-
<text fg={theme().textMuted}>{` \u2014 ${agent.role}`}</text>
|
|
389
|
-
</box>
|
|
390
|
-
)}
|
|
391
|
-
</For>
|
|
392
|
-
</box>
|
|
393
|
-
</Show>
|
|
394
|
-
|
|
395
|
-
{/* ── Config ── */}
|
|
396
|
-
<box onMouseDown={() => setShowConfig((x) => !x)}>
|
|
397
|
-
<text fg={theme().text} attributes={{ bold: true }}>
|
|
398
|
-
{`${showConfig() ? '\u25bc' : '\u25b6'} Config`}
|
|
399
|
-
</text>
|
|
400
|
-
</box>
|
|
401
|
-
<Show when={showConfig()}>
|
|
402
|
-
<Show when={configSummary()} fallback={<box marginLeft={1}><text fg={theme().textMuted}>No config data</text></box>}>
|
|
403
|
-
{(cfg) => (
|
|
404
|
-
<box marginLeft={1} flexDirection="column">
|
|
405
|
-
<text fg={theme().textMuted}>{`MCP: ${String(cfg().mcpCount)} Plugins: ${String(cfg().pluginCount)}`}</text>
|
|
406
|
-
<text fg={theme().textMuted}>{`Auto-compaction: ${cfg().autoCompaction ? 'ON' : 'OFF'}`}</text>
|
|
407
|
-
</box>
|
|
408
|
-
)}
|
|
409
|
-
</Show>
|
|
410
|
-
</Show>
|
|
411
|
-
|
|
412
|
-
{/* ── Memory (always visible summary) ── */}
|
|
413
|
-
<box marginTop={1}>
|
|
414
|
-
<text fg={theme().textMuted}>
|
|
415
|
-
{memoryCount() !== null
|
|
416
|
-
? `Memory: ${fmtInt(memoryCount()!)} entries`
|
|
417
|
-
: 'Memory: N/A'}
|
|
418
|
-
</text>
|
|
419
|
-
</box>
|
|
420
|
-
</box>
|
|
421
|
-
)
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
/* ─── Plugin Registration ────────────────────────────────── */
|
|
425
|
-
|
|
426
|
-
const tui: TuiPlugin = async (api, _options, _meta) => {
|
|
427
|
-
const version = await detectVersion(api)
|
|
428
|
-
|
|
429
|
-
api.slots.register({
|
|
430
|
-
order: 900,
|
|
431
|
-
slots: {
|
|
432
|
-
sidebar_content(_ctx, props) {
|
|
433
|
-
return <View api={api} sessionID={props.session_id} version={version} />
|
|
434
|
-
},
|
|
435
|
-
},
|
|
436
|
-
})
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
const plugin: TuiPluginModule & { id: string } = {
|
|
440
|
-
id: 'pantheon.tui',
|
|
441
|
-
tui,
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
export default plugin
|