pantheon-opencode 1.1.3-beta.0 → 1.2.0-beta.9.7bef03e
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 +74 -1
- package/bin/pantheon-init.mjs +79 -0
- package/commands/pantheon-vision.md +67 -0
- package/opencode.json +12 -41
- package/package.json +5 -1
- 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 +98 -17
- package/scripts/install-mcp.mjs +98 -13
- package/scripts/mcp_persistence_server.py +331 -1
- package/scripts/secret-scan.mjs +76 -0
- package/scripts/validate-routing.mjs +15 -0
- package/src/agents/aphrodite.md +7 -0
- package/src/agents/demeter.md +7 -0
- package/src/agents/hephaestus.md +7 -0
- package/src/agents/hermes.md +7 -0
- package/src/agents/zeus.md +31 -18
- package/src/instructions/agent-return-format.instructions.md +1 -0
- package/src/instructions/zeus-anti-stall.instructions.md +43 -5
- package/src/instructions/zeus-timeout-retry.instructions.md +47 -10
- package/src/mcp/install-mcp.mjs +6 -913
- package/src/mcp/mcp_persistence_server.py +332 -0
- 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 +99 -0
- package/src/pantheon/presets.mjs +491 -0
- package/src/pantheon/vision.ts +1340 -0
- package/src/plugin.ts +40 -25
- package/src/plugins/tui/dist/tui.d.ts.map +1 -1
- package/src/plugins/tui/dist/tui.js +806 -222
- package/src/plugins/tui/dist/tui.tsx +866 -121
- package/src/plugins/tui/package.json +2 -2
- package/src/plugins/tui/src/index.tsx +866 -121
- package/src/routing.yml +179 -0
|
@@ -1,8 +1,53 @@
|
|
|
1
1
|
import { createComponent, createElement, createTextNode, effect, insert, insertNode, memo, setProp } from "@opentui/solid";
|
|
2
|
+
import { Buffer } from "node:buffer";
|
|
3
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
2
6
|
import { For, Show, createMemo, createResource, createSignal, onCleanup, onMount } from "solid-js";
|
|
3
7
|
//#region src/index.tsx
|
|
4
8
|
/** @jsxImportSource @opentui/solid */
|
|
5
|
-
|
|
9
|
+
/**
|
|
10
|
+
* pantheon-tui — Pantheon TUI plugin for opencode.
|
|
11
|
+
*
|
|
12
|
+
* Single self-contained entry file: `tsdown && cp src/index.tsx dist/tui.tsx`
|
|
13
|
+
* keeps the no-build load path working (dist/tui.tsx is raw source, transpiled
|
|
14
|
+
* on the fly by opencode). All feature code lives in this one file.
|
|
15
|
+
*
|
|
16
|
+
* Slots:
|
|
17
|
+
* - sidebar_content (order 900) — Pantheon sidebar (header/version/
|
|
18
|
+
* branch, Sessions, Commands, Agents, Config, Memory).
|
|
19
|
+
* - app_bottom (order 60) — AI subscription usage gauges
|
|
20
|
+
* (Anthropic/OpenAI quotas, OpenCode Go/Zen dollar limits + provider
|
|
21
|
+
* status incidents).
|
|
22
|
+
*
|
|
23
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
24
|
+
* VENDORED FEATURES (MIT) — incorporated with their license headers preserved:
|
|
25
|
+
*
|
|
26
|
+
* • satas20/opencode-usage-bar (MIT)
|
|
27
|
+
* https://github.com/satas20/opencode-usage-bar
|
|
28
|
+
*
|
|
29
|
+
* v1.2.0 added an OpenCode Go/Zen usage provider (`opencodego`) on top of
|
|
30
|
+
* the vendored base — dollar-denominated rolling limits ($12/5h, $30/7d,
|
|
31
|
+
* $60/month) via the same key used for inference. Still MIT-credited to
|
|
32
|
+
* satas20 for the original bar/config/poll code.
|
|
33
|
+
*
|
|
34
|
+
* satas20/opencode-todo-progress was vendored in v1.1.0 alongside the
|
|
35
|
+
* usage-bar but has since been REMOVED as redundant — the native session
|
|
36
|
+
* footer already surfaces todo/context state, so its todo bar slot was
|
|
37
|
+
* dropped entirely. Only usage-bar code remains.
|
|
38
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
39
|
+
*
|
|
40
|
+
* LIMITATION — native context/tokens statusline:
|
|
41
|
+
* The opencode session footer natively shows a context/tokens statusline
|
|
42
|
+
* next to the prompt. There is NO config option to hide it: the tui.json
|
|
43
|
+
* schema (opencode.ai/tui.json) only exposes `theme`, `keybinds`,
|
|
44
|
+
* `scroll_speed`, `scroll_acceleration`, `diff_style`, `mouse` and
|
|
45
|
+
* `attention`, and the SDK `Config` type has no statusline/tokens display
|
|
46
|
+
* flag (checked against @opencode-ai/sdk `Config` and the published
|
|
47
|
+
* schema). The Pantheon sidebar therefore no longer duplicates that footer
|
|
48
|
+
* (the old ContextBar was removed) and we deliberately do NOT hack the
|
|
49
|
+
* native footer. Track upstream: opencode statusline config.
|
|
50
|
+
*/
|
|
6
51
|
const COMMANDS = [
|
|
7
52
|
{
|
|
8
53
|
name: "/pantheon",
|
|
@@ -49,6 +94,9 @@ const COMMANDS = [
|
|
|
49
94
|
desc: "Compress memories"
|
|
50
95
|
}
|
|
51
96
|
];
|
|
97
|
+
/** How often the sidebar re-reads .pantheon/active-preset.json so `set-tier`
|
|
98
|
+
* changes made while opencode is open show up within ~30s. */
|
|
99
|
+
const PRESET_REFRESH_MS = 3e4;
|
|
52
100
|
const AGENTS = [
|
|
53
101
|
{
|
|
54
102
|
name: "zeus",
|
|
@@ -121,39 +169,67 @@ const AGENTS = [
|
|
|
121
169
|
role: "Hotfixes"
|
|
122
170
|
}
|
|
123
171
|
];
|
|
124
|
-
function safeNum(v) {
|
|
125
|
-
return typeof v === "number" && Number.isFinite(v) ? v : 0;
|
|
126
|
-
}
|
|
127
172
|
function fmtInt(n) {
|
|
128
173
|
return Intl.NumberFormat("en-US").format(Math.max(0, Math.round(n)));
|
|
129
174
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
175
|
+
/** Read .pantheon/active-preset.json — mirrors the presets.mjs file leg:
|
|
176
|
+
* first existing candidate wins; malformed JSON or a `preset` that is
|
|
177
|
+
* missing, empty or "none" → null (no fall-through to lower candidates). */
|
|
178
|
+
async function readActivePresetFile(cwd) {
|
|
179
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config");
|
|
180
|
+
const candidates = [
|
|
181
|
+
join(cwd, ".pantheon", "active-preset.json"),
|
|
182
|
+
join(xdgConfig, "opencode", ".pantheon", "active-preset.json"),
|
|
183
|
+
join(homedir(), ".opencode", ".pantheon", "active-preset.json")
|
|
184
|
+
];
|
|
185
|
+
for (const candidate of candidates) {
|
|
186
|
+
let raw;
|
|
187
|
+
try {
|
|
188
|
+
raw = await readFile(candidate, "utf8");
|
|
189
|
+
} catch {
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
try {
|
|
193
|
+
const parsed = JSON.parse(raw);
|
|
194
|
+
const name = parsed && typeof parsed === "object" ? parsed.preset : void 0;
|
|
195
|
+
if (typeof name !== "string" || name.length === 0 || name === "none") return null;
|
|
196
|
+
return {
|
|
197
|
+
name,
|
|
198
|
+
source: "file"
|
|
199
|
+
};
|
|
200
|
+
} catch {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return null;
|
|
138
205
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
206
|
+
/** Env leg of resolution: PANTHEON_MODEL_PRESET set and !== 'none' wins. */
|
|
207
|
+
function presetFromEnv(env) {
|
|
208
|
+
const name = env.PANTHEON_MODEL_PRESET;
|
|
209
|
+
if (name !== void 0 && name !== "" && name !== "none") return {
|
|
210
|
+
name,
|
|
211
|
+
source: "env"
|
|
212
|
+
};
|
|
213
|
+
return {
|
|
214
|
+
name: null,
|
|
215
|
+
source: null
|
|
216
|
+
};
|
|
146
217
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
218
|
+
/** Resolve the active preset for the sidebar: env > file > default. */
|
|
219
|
+
async function resolvePresetForTui(env, cwd) {
|
|
220
|
+
const envPreset = presetFromEnv(env);
|
|
221
|
+
if (envPreset.source === "env") return envPreset;
|
|
222
|
+
return await readActivePresetFile(cwd) ?? {
|
|
223
|
+
name: null,
|
|
224
|
+
source: null
|
|
225
|
+
};
|
|
150
226
|
}
|
|
151
227
|
async function detectVersion(api) {
|
|
152
228
|
try {
|
|
153
229
|
const wt = api.state.path?.worktree ?? "";
|
|
154
230
|
const fp = wt ? `${wt}/package.json` : "package.json";
|
|
155
|
-
const result = await api.client.file.read({
|
|
156
|
-
const match = String(result?.content ?? "").match(/"version":\s*"([^"]+)"/);
|
|
231
|
+
const result = await api.client.file.read({ path: fp });
|
|
232
|
+
const match = String(result?.data?.content ?? "").match(/"version":\s*"([^"]+)"/);
|
|
157
233
|
if (match?.[1]) return match[1];
|
|
158
234
|
} catch {}
|
|
159
235
|
try {
|
|
@@ -186,74 +262,532 @@ async function detectVersion(api) {
|
|
|
186
262
|
} catch {}
|
|
187
263
|
return null;
|
|
188
264
|
}
|
|
189
|
-
|
|
190
|
-
|
|
265
|
+
/**
|
|
266
|
+
* usage-bar — AI subscription usage gauge for the opencode TUI.
|
|
267
|
+
*
|
|
268
|
+
* Renders a compact usage strip in the `app_bottom` slot — a full-width row
|
|
269
|
+
* just below the session footer:
|
|
270
|
+
*
|
|
271
|
+
* ▓▓▓▓░░ 65% · 0h 11m (one window)
|
|
272
|
+
* cld ▓▓▓▓░ 65% · 0h 11m 7d ▓░░░░ 19% · 1d 11h oai ▓░░░░ 12% · 3h 4m
|
|
273
|
+
* ! cld ▓▓▓▓░ 65% · 0h 11m (anthropic incident)
|
|
274
|
+
*
|
|
275
|
+
* Providers:
|
|
276
|
+
* anthropic — Claude Pro/Max via the OAuth token in ~/.claude/.credentials.json
|
|
277
|
+
* openai — ChatGPT Plus/Pro via the Codex CLI login in ~/.codex/auth.json
|
|
278
|
+
* opencodego— OpenCode Go/Zen dollar limits ($12/5h, $30/7d, $60/mo) via
|
|
279
|
+
* PANTHEON_OPENCODE_API_KEY (fallback OPENCODE_API_KEY). No
|
|
280
|
+
* status page — the provider hides silently when unconfigured
|
|
281
|
+
* or when the (not yet public) usage endpoint is unavailable.
|
|
282
|
+
*
|
|
283
|
+
* When a provider's public status page reports an incident, a colored `!`
|
|
284
|
+
* marker appears next to its prefix (red = major/critical, amber = minor,
|
|
285
|
+
* cyan = maintenance). The usage bar itself is unaffected. Disable with
|
|
286
|
+
* `show_status = false` under `[ui]`.
|
|
287
|
+
*
|
|
288
|
+
* Configured via ~/.config/opencode/usage-bar.toml (auto-created with
|
|
289
|
+
* commented defaults on first run; read once at startup). Tokens/keys are
|
|
290
|
+
* only ever sent to their own provider's API host.
|
|
291
|
+
*
|
|
292
|
+
* Loaded via tui.json, e.g.:
|
|
293
|
+
* { "plugin": ["@satas/opencode-usage-bar"] } // published npm package
|
|
294
|
+
* { "plugin": ["/abs/path/to/src/index.tsx"] } // local file (no build)
|
|
295
|
+
*/
|
|
296
|
+
const POLL_MS = 12e4;
|
|
297
|
+
const FETCH_TIMEOUT_MS = 1e4;
|
|
298
|
+
const CONFIG_FILE = "usage-bar.toml";
|
|
299
|
+
/** Which quota window a value belongs to; toggled per provider in config. */
|
|
300
|
+
/** Provider health from the vendor's public status page (Statuspage schema). */
|
|
301
|
+
function expandTilde(p) {
|
|
302
|
+
return p === "~" ? homedir() : p.startsWith("~/") ? join(homedir(), p.slice(2)) : p;
|
|
303
|
+
}
|
|
304
|
+
function fmtDuration(ms) {
|
|
305
|
+
const totalMinutes = Math.max(0, Math.ceil(ms / 6e4));
|
|
306
|
+
const days = Math.floor(totalMinutes / 1440);
|
|
307
|
+
const hours = Math.floor(totalMinutes % 1440 / 60);
|
|
308
|
+
const minutes = totalMinutes % 60;
|
|
309
|
+
if (days > 0) return `${days}d ${hours}h`;
|
|
310
|
+
return `${hours}h ${minutes}m`;
|
|
311
|
+
}
|
|
312
|
+
/** Decode a JWT's `exp` claim (unix seconds) without verifying. 0 on failure. */
|
|
313
|
+
function jwtExp(token) {
|
|
314
|
+
try {
|
|
315
|
+
const payload = token.split(".")[1];
|
|
316
|
+
if (!payload) return 0;
|
|
317
|
+
const claims = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
|
|
318
|
+
return typeof claims.exp === "number" ? claims.exp : 0;
|
|
319
|
+
} catch {
|
|
320
|
+
return 0;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
async function fetchJson(url, headers) {
|
|
324
|
+
try {
|
|
325
|
+
const res = await fetch(url, {
|
|
326
|
+
headers,
|
|
327
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
328
|
+
});
|
|
329
|
+
if (!res.ok) return null;
|
|
330
|
+
return await res.json();
|
|
331
|
+
} catch {
|
|
332
|
+
return null;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
/** Fetch a vendor's overall status from its public Statuspage JSON endpoint.
|
|
336
|
+
* Returns the `indicator` ("none" when healthy), or `null` when the fetch
|
|
337
|
+
* itself failed — so a network blip never clears a known incident. */
|
|
338
|
+
async function fetchStatus(url) {
|
|
339
|
+
const data = await fetchJson(url, {});
|
|
340
|
+
if (!data?.status) return null;
|
|
341
|
+
const indicator = data.status.indicator;
|
|
342
|
+
return indicator === "minor" || indicator === "major" || indicator === "critical" || indicator === "maintenance" ? indicator : "none";
|
|
343
|
+
}
|
|
344
|
+
/** Set from `api.state.path.state` at startup; default matches opencode's
|
|
345
|
+
* XDG data dir. */
|
|
346
|
+
let opencodeAuthFile = join(homedir(), ".local", "share", "opencode", "auth.json");
|
|
347
|
+
/** Read a provider's entry from opencode's own auth store
|
|
348
|
+
* (`opencode auth login`). Returns null when missing/unreadable. */
|
|
349
|
+
async function opencodeAuth(...ids) {
|
|
350
|
+
try {
|
|
351
|
+
const auth = JSON.parse(await readFile(opencodeAuthFile, "utf8"));
|
|
352
|
+
for (const id of ids) {
|
|
353
|
+
const entry = auth[id];
|
|
354
|
+
if (entry) return entry;
|
|
355
|
+
}
|
|
356
|
+
return null;
|
|
357
|
+
} catch {
|
|
358
|
+
return null;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
/** Claude Pro/Max — Anthropic's OAuth usage endpoint (same one Claude Code's
|
|
362
|
+
* `/usage` uses). Token from ~/.claude/.credentials.json, falling back to
|
|
363
|
+
* opencode's own auth store; sent only to api.anthropic.com. */
|
|
364
|
+
const anthropicProvider = {
|
|
365
|
+
id: "anthropic",
|
|
366
|
+
short: "cld",
|
|
367
|
+
statusUrl: "https://status.anthropic.com/api/v2/status.json",
|
|
368
|
+
async fetchUsage(cfg) {
|
|
369
|
+
let token;
|
|
191
370
|
try {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
371
|
+
const path = expandTilde(cfg.credentialsPath ?? join(homedir(), ".claude", ".credentials.json"));
|
|
372
|
+
const oauth = JSON.parse(await readFile(path, "utf8")).claudeAiOauth;
|
|
373
|
+
if (oauth?.accessToken && !(oauth.expiresAt && Date.now() >= oauth.expiresAt)) token = oauth.accessToken;
|
|
374
|
+
} catch {}
|
|
375
|
+
if (!token) {
|
|
376
|
+
const entry = await opencodeAuth("anthropic");
|
|
377
|
+
if (entry?.access && !(entry.expires && Date.now() >= entry.expires)) token = entry.access;
|
|
195
378
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
379
|
+
if (!token) return null;
|
|
380
|
+
const data = await fetchJson("https://api.anthropic.com/api/oauth/usage", {
|
|
381
|
+
authorization: `Bearer ${token}`,
|
|
382
|
+
"anthropic-beta": "oauth-2025-04-20"
|
|
200
383
|
});
|
|
201
|
-
if (!
|
|
202
|
-
const
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
limit:
|
|
209
|
-
|
|
384
|
+
if (!data || !Array.isArray(data.limits)) return null;
|
|
385
|
+
const windows = [];
|
|
386
|
+
for (const limit of data.limits) {
|
|
387
|
+
if (!limit || typeof limit.percent !== "number" || !Number.isFinite(limit.percent)) continue;
|
|
388
|
+
if (!limit.kind || !limit.resets_at) continue;
|
|
389
|
+
const resetsAt = Date.parse(limit.resets_at);
|
|
390
|
+
if (Number.isNaN(resetsAt)) continue;
|
|
391
|
+
const category = limit.kind === "session" ? "5h" : limit.kind === "weekly_all" ? "7d" : "model";
|
|
392
|
+
const label = category === "model" ? limit.scope?.model?.display_name ?? "model" : category;
|
|
393
|
+
windows.push({
|
|
394
|
+
category,
|
|
395
|
+
label,
|
|
396
|
+
percent: limit.percent,
|
|
397
|
+
resetsAt
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
windows.sort((a, b) => Number(b.category === "5h") - Number(a.category === "5h"));
|
|
401
|
+
return windows.length > 0 ? windows : null;
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
/** ChatGPT Plus/Pro (Codex) — reads the Codex CLI login and asks the wham
|
|
405
|
+
* usage endpoint. Read-only: never refreshes/rewrites auth.json; when the
|
|
406
|
+
* token is expired we simply hide (Codex CLI refreshes the file itself). */
|
|
407
|
+
const openaiProvider = {
|
|
408
|
+
id: "openai",
|
|
409
|
+
short: "oai",
|
|
410
|
+
statusUrl: "https://status.openai.com/api/v2/status.json",
|
|
411
|
+
async fetchUsage(cfg) {
|
|
412
|
+
let accessToken;
|
|
413
|
+
let accountId;
|
|
414
|
+
try {
|
|
415
|
+
const path = expandTilde(cfg.codexAuthPath ?? join(homedir(), ".codex", "auth.json"));
|
|
416
|
+
const tokens = JSON.parse(await readFile(path, "utf8")).tokens;
|
|
417
|
+
const exp = tokens?.access_token ? jwtExp(tokens.access_token) : 0;
|
|
418
|
+
if (tokens?.access_token && !(exp > 0 && exp * 1e3 <= Date.now() + 6e4)) {
|
|
419
|
+
accessToken = tokens.access_token;
|
|
420
|
+
accountId = tokens.account_id;
|
|
421
|
+
}
|
|
422
|
+
} catch {}
|
|
423
|
+
if (!accessToken) {
|
|
424
|
+
const entry = await opencodeAuth("openai");
|
|
425
|
+
if (entry?.access && !(entry.expires && Date.now() >= entry.expires)) {
|
|
426
|
+
accessToken = entry.access;
|
|
427
|
+
accountId = entry.accountId;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
if (!accessToken) return null;
|
|
431
|
+
const headers = {
|
|
432
|
+
authorization: `Bearer ${accessToken}`,
|
|
433
|
+
"user-agent": "codex-cli"
|
|
210
434
|
};
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
435
|
+
if (accountId) headers["chatgpt-account-id"] = accountId;
|
|
436
|
+
const data = await fetchJson("https://chatgpt.com/backend-api/wham/usage", headers);
|
|
437
|
+
if (!data?.rate_limit) return null;
|
|
438
|
+
const windows = [];
|
|
439
|
+
const primary = parseWhamWindow(data.rate_limit.primary_window, "5h");
|
|
440
|
+
if (primary) windows.push(primary);
|
|
441
|
+
const secondary = parseWhamWindow(data.rate_limit.secondary_window, "7d");
|
|
442
|
+
if (secondary) windows.push(secondary);
|
|
443
|
+
windows.sort((a, b) => Number(b.category === "5h") - Number(a.category === "5h"));
|
|
444
|
+
return windows.length > 0 ? windows : null;
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
function parseWhamWindow(w, fallback) {
|
|
448
|
+
if (!w || typeof w.used_percent !== "number" || !Number.isFinite(w.used_percent)) return null;
|
|
449
|
+
let resetsAt;
|
|
450
|
+
if (typeof w.reset_at === "number") resetsAt = w.reset_at * 1e3;
|
|
451
|
+
else if (typeof w.reset_after_seconds === "number") resetsAt = Date.now() + w.reset_after_seconds * 1e3;
|
|
452
|
+
if (!resetsAt || !Number.isFinite(resetsAt)) return null;
|
|
453
|
+
const category = typeof w.limit_window_seconds === "number" && w.limit_window_seconds > 0 ? w.limit_window_seconds <= 21600 ? "5h" : "7d" : fallback;
|
|
454
|
+
return {
|
|
455
|
+
category,
|
|
456
|
+
label: category,
|
|
457
|
+
percent: w.used_percent,
|
|
458
|
+
resetsAt
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
const providers = [
|
|
462
|
+
anthropicProvider,
|
|
463
|
+
openaiProvider,
|
|
464
|
+
{
|
|
465
|
+
id: "opencodego",
|
|
466
|
+
short: "go",
|
|
467
|
+
async fetchUsage(_cfg) {
|
|
468
|
+
const token = process.env.PANTHEON_OPENCODE_API_KEY ?? process.env.OPENCODE_API_KEY;
|
|
469
|
+
if (!token) return null;
|
|
470
|
+
const data = await fetchJson("https://opencode.ai/zen/go/v1/usage", { authorization: `Bearer ${token}` });
|
|
471
|
+
if (!data) return null;
|
|
472
|
+
const windows = [];
|
|
473
|
+
const push = (w, category) => {
|
|
474
|
+
if (!w) return;
|
|
475
|
+
if (typeof w.usagePercent !== "number" || !Number.isFinite(w.usagePercent)) return;
|
|
476
|
+
if (typeof w.resetInSec !== "number" || !Number.isFinite(w.resetInSec)) return;
|
|
477
|
+
windows.push({
|
|
478
|
+
category,
|
|
479
|
+
label: category,
|
|
480
|
+
percent: Math.round(w.usagePercent),
|
|
481
|
+
resetsAt: Date.now() + w.resetInSec * 1e3
|
|
253
482
|
});
|
|
254
|
-
|
|
255
|
-
|
|
483
|
+
};
|
|
484
|
+
push(data.rolling5h, "5h");
|
|
485
|
+
push(data.weekly, "7d");
|
|
486
|
+
push(data.monthly, "1m");
|
|
487
|
+
return windows.length > 0 ? windows : null;
|
|
256
488
|
}
|
|
489
|
+
}
|
|
490
|
+
];
|
|
491
|
+
const DEFAULT_TOML = `# opencode-usage-bar configuration
|
|
492
|
+
# Read once at startup — restart opencode after editing.
|
|
493
|
+
|
|
494
|
+
[ui]
|
|
495
|
+
show_bars = true # render ▓▓░░ mini-bars (false = text only)
|
|
496
|
+
show_status = true # show a ! marker next to a provider during incidents
|
|
497
|
+
# bar_width = 6 # override bar width (default: 6 for a single window, 5 otherwise)
|
|
498
|
+
|
|
499
|
+
[anthropic]
|
|
500
|
+
enabled = true # Claude Pro/Max via ~/.claude/.credentials.json
|
|
501
|
+
show_5h = true # rolling 5-hour session window
|
|
502
|
+
show_7d = false # weekly cap across all models
|
|
503
|
+
show_model = false # per-model weekly windows (e.g. Fable)
|
|
504
|
+
# credentials_path = "~/.claude/.credentials.json"
|
|
505
|
+
|
|
506
|
+
[openai]
|
|
507
|
+
enabled = false # ChatGPT Plus/Pro via the Codex CLI login
|
|
508
|
+
show_5h = true
|
|
509
|
+
show_7d = false
|
|
510
|
+
# codex_auth_path = "~/.codex/auth.json"
|
|
511
|
+
|
|
512
|
+
[opencodego]
|
|
513
|
+
enabled = true # OpenCode Go/Zen dollar usage via PANTHEON_OPENCODE_API_KEY
|
|
514
|
+
show_5h = true # rolling 5-hour window ($12)
|
|
515
|
+
show_7d = true # rolling 7-day window ($30)
|
|
516
|
+
show_1m = false # subscription-month window ($60) — opt-in
|
|
517
|
+
# Falls back to OPENCODE_API_KEY when PANTHEON_OPENCODE_API_KEY is unset.
|
|
518
|
+
`;
|
|
519
|
+
function defaultConfig() {
|
|
520
|
+
const show = (over = {}) => ({
|
|
521
|
+
"5h": true,
|
|
522
|
+
"7d": false,
|
|
523
|
+
"1m": false,
|
|
524
|
+
model: false,
|
|
525
|
+
...over
|
|
526
|
+
});
|
|
527
|
+
return {
|
|
528
|
+
showBars: true,
|
|
529
|
+
showStatus: true,
|
|
530
|
+
providers: {
|
|
531
|
+
anthropic: {
|
|
532
|
+
enabled: true,
|
|
533
|
+
show: show()
|
|
534
|
+
},
|
|
535
|
+
openai: {
|
|
536
|
+
enabled: false,
|
|
537
|
+
show: show()
|
|
538
|
+
},
|
|
539
|
+
opencodego: {
|
|
540
|
+
enabled: true,
|
|
541
|
+
show: show({ "7d": true })
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
function asTable(v) {
|
|
547
|
+
return v && typeof v === "object" && !Array.isArray(v) ? v : {};
|
|
548
|
+
}
|
|
549
|
+
function bool(v, fallback) {
|
|
550
|
+
return typeof v === "boolean" ? v : fallback;
|
|
551
|
+
}
|
|
552
|
+
function str(v) {
|
|
553
|
+
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
554
|
+
}
|
|
555
|
+
function parseConfig(raw) {
|
|
556
|
+
const toml = globalThis.Bun?.TOML;
|
|
557
|
+
if (!toml?.parse) return defaultConfig();
|
|
558
|
+
const root = asTable(toml.parse(raw));
|
|
559
|
+
const cfg = defaultConfig();
|
|
560
|
+
const ui = asTable(root["ui"]);
|
|
561
|
+
cfg.showBars = bool(ui["show_bars"], cfg.showBars);
|
|
562
|
+
cfg.showStatus = bool(ui["show_status"], cfg.showStatus);
|
|
563
|
+
const rawWidth = ui["bar_width"];
|
|
564
|
+
if (typeof rawWidth === "number" && Number.isFinite(rawWidth) && rawWidth >= 1) cfg.barWidth = Math.min(40, Math.floor(rawWidth));
|
|
565
|
+
for (const id of [
|
|
566
|
+
"anthropic",
|
|
567
|
+
"openai",
|
|
568
|
+
"opencodego"
|
|
569
|
+
]) {
|
|
570
|
+
const t = asTable(root[id]);
|
|
571
|
+
const p = cfg.providers[id];
|
|
572
|
+
p.enabled = bool(t["enabled"], p.enabled);
|
|
573
|
+
p.show["5h"] = bool(t["show_5h"], p.show["5h"]);
|
|
574
|
+
p.show["7d"] = bool(t["show_7d"], p.show["7d"]);
|
|
575
|
+
p.show["1m"] = bool(t["show_1m"], p.show["1m"]);
|
|
576
|
+
p.show.model = bool(t["show_model"], p.show.model);
|
|
577
|
+
const credentialsPath = str(t["credentials_path"]);
|
|
578
|
+
if (credentialsPath !== void 0) p.credentialsPath = credentialsPath;
|
|
579
|
+
const codexAuthPath = str(t["codex_auth_path"]);
|
|
580
|
+
if (codexAuthPath !== void 0) p.codexAuthPath = codexAuthPath;
|
|
581
|
+
}
|
|
582
|
+
return cfg;
|
|
583
|
+
}
|
|
584
|
+
/** Load `<config dir>/usage-bar.toml`, creating it with commented defaults on
|
|
585
|
+
* first run. Any failure falls back to defaults without touching an existing
|
|
586
|
+
* file. */
|
|
587
|
+
async function loadConfig(configPath) {
|
|
588
|
+
const dir = configPath && !configPath.endsWith(".json") ? configPath : configPath ? dirname(configPath) : join(homedir(), ".config", "opencode");
|
|
589
|
+
const file = join(dir, CONFIG_FILE);
|
|
590
|
+
try {
|
|
591
|
+
const raw = await readFile(file, "utf8");
|
|
592
|
+
try {
|
|
593
|
+
return parseConfig(raw);
|
|
594
|
+
} catch {
|
|
595
|
+
return defaultConfig();
|
|
596
|
+
}
|
|
597
|
+
} catch (err) {
|
|
598
|
+
if (err?.code === "ENOENT") try {
|
|
599
|
+
await mkdir(dir, { recursive: true });
|
|
600
|
+
await writeFile(file, DEFAULT_TOML, { flag: "wx" });
|
|
601
|
+
} catch {}
|
|
602
|
+
return defaultConfig();
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
/** Vendored usage-bar plugin init: reads config, seeds the kv cache, starts
|
|
606
|
+
* the poll loop and registers the `app_bottom` slot. Skips everything (and
|
|
607
|
+
* registers nothing) when no provider is enabled — mirrors the standalone
|
|
608
|
+
* plugin's early return. */
|
|
609
|
+
async function setupUsageBar(api) {
|
|
610
|
+
const config = await loadConfig(api.state.path?.config);
|
|
611
|
+
if (api.state.path?.state) opencodeAuthFile = join(api.state.path.state, "auth.json");
|
|
612
|
+
const enabled = providers.filter((p) => config.providers[p.id].enabled);
|
|
613
|
+
if (enabled.length === 0) return;
|
|
614
|
+
const seed = {};
|
|
615
|
+
for (const p of enabled) {
|
|
616
|
+
const cached = api.kv.get(`usage-bar.${p.id}.windows`, void 0);
|
|
617
|
+
if (cached) seed[p.id] = cached;
|
|
618
|
+
}
|
|
619
|
+
const [byProvider, setByProvider] = createSignal(seed);
|
|
620
|
+
const [byStatus, setByStatus] = createSignal({});
|
|
621
|
+
const [now, setNow] = createSignal(Date.now());
|
|
622
|
+
setInterval(() => setNow(Date.now()), 1e3);
|
|
623
|
+
for (const p of enabled) {
|
|
624
|
+
const cfg = config.providers[p.id];
|
|
625
|
+
const poll = async () => {
|
|
626
|
+
const statusP = config.showStatus && p.statusUrl ? fetchStatus(p.statusUrl) : null;
|
|
627
|
+
const [all, status] = await Promise.all([p.fetchUsage(cfg), statusP]);
|
|
628
|
+
if (all) {
|
|
629
|
+
const windows = all.filter((w) => cfg.show[w.category] && w.resetsAt > Date.now());
|
|
630
|
+
setByProvider((prev) => ({
|
|
631
|
+
...prev,
|
|
632
|
+
[p.id]: windows
|
|
633
|
+
}));
|
|
634
|
+
api.kv.set(`usage-bar.${p.id}.windows`, windows);
|
|
635
|
+
}
|
|
636
|
+
if (status !== null) setByStatus((prev) => ({
|
|
637
|
+
...prev,
|
|
638
|
+
[p.id]: status
|
|
639
|
+
}));
|
|
640
|
+
setTimeout(poll, all ? POLL_MS : POLL_MS * 3);
|
|
641
|
+
};
|
|
642
|
+
poll();
|
|
643
|
+
}
|
|
644
|
+
api.slots.register({
|
|
645
|
+
order: 60,
|
|
646
|
+
slots: { app_bottom() {
|
|
647
|
+
const theme = () => api.theme.current;
|
|
648
|
+
const groups = createMemo(() => {
|
|
649
|
+
const map = byProvider();
|
|
650
|
+
const statusMap = byStatus();
|
|
651
|
+
const out = [];
|
|
652
|
+
for (const p of enabled) {
|
|
653
|
+
const windows = (map[p.id] ?? []).filter((w) => w.resetsAt > now());
|
|
654
|
+
if (windows.length > 0) out.push({
|
|
655
|
+
short: p.short,
|
|
656
|
+
status: statusMap[p.id] ?? "none",
|
|
657
|
+
windows
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
return out;
|
|
661
|
+
});
|
|
662
|
+
const totalWindows = createMemo(() => groups().reduce((sum, g) => sum + g.windows.length, 0));
|
|
663
|
+
const barWidth = createMemo(() => config.barWidth ?? (totalWindows() === 1 ? 6 : 5));
|
|
664
|
+
const multiProvider = createMemo(() => groups().length >= 2);
|
|
665
|
+
const pctOf = (w) => Math.min(100, Math.max(0, Math.round(w.percent)));
|
|
666
|
+
const filledOf = (w) => Math.min(barWidth(), Math.max(0, Math.round(pctOf(w) / 100 * barWidth())));
|
|
667
|
+
const colorOf = (w) => {
|
|
668
|
+
const t = theme();
|
|
669
|
+
const pct = pctOf(w);
|
|
670
|
+
if (pct > 85) return t.error;
|
|
671
|
+
if (pct >= 50) return t.warning;
|
|
672
|
+
return t.success;
|
|
673
|
+
};
|
|
674
|
+
const statusColor = (s) => {
|
|
675
|
+
const t = theme();
|
|
676
|
+
if (s === "critical" || s === "major") return t.error;
|
|
677
|
+
if (s === "minor") return t.warning;
|
|
678
|
+
return t.info;
|
|
679
|
+
};
|
|
680
|
+
return createComponent(Show, {
|
|
681
|
+
get when() {
|
|
682
|
+
return groups().length > 0;
|
|
683
|
+
},
|
|
684
|
+
get children() {
|
|
685
|
+
var _el$ = createElement("box");
|
|
686
|
+
setProp(_el$, "flexDirection", "row");
|
|
687
|
+
setProp(_el$, "gap", 3);
|
|
688
|
+
setProp(_el$, "alignItems", "center");
|
|
689
|
+
setProp(_el$, "width", "100%");
|
|
690
|
+
setProp(_el$, "paddingLeft", 1);
|
|
691
|
+
insert(_el$, createComponent(For, {
|
|
692
|
+
get each() {
|
|
693
|
+
return groups();
|
|
694
|
+
},
|
|
695
|
+
children: (g) => (() => {
|
|
696
|
+
var _el$2 = createElement("box");
|
|
697
|
+
setProp(_el$2, "flexDirection", "row");
|
|
698
|
+
setProp(_el$2, "gap", 2);
|
|
699
|
+
setProp(_el$2, "alignItems", "center");
|
|
700
|
+
setProp(_el$2, "flexShrink", 0);
|
|
701
|
+
insert(_el$2, createComponent(Show, {
|
|
702
|
+
get when() {
|
|
703
|
+
return memo(() => !!config.showStatus)() && g.status !== "none";
|
|
704
|
+
},
|
|
705
|
+
get children() {
|
|
706
|
+
var _el$3 = createElement("text");
|
|
707
|
+
insertNode(_el$3, createTextNode(`!`));
|
|
708
|
+
effect((_$p) => setProp(_el$3, "fg", statusColor(g.status), _$p));
|
|
709
|
+
return _el$3;
|
|
710
|
+
}
|
|
711
|
+
}), null);
|
|
712
|
+
insert(_el$2, createComponent(Show, {
|
|
713
|
+
get when() {
|
|
714
|
+
return multiProvider();
|
|
715
|
+
},
|
|
716
|
+
get children() {
|
|
717
|
+
var _el$5 = createElement("text");
|
|
718
|
+
insert(_el$5, () => g.short);
|
|
719
|
+
effect((_$p) => setProp(_el$5, "fg", theme().textMuted, _$p));
|
|
720
|
+
return _el$5;
|
|
721
|
+
}
|
|
722
|
+
}), null);
|
|
723
|
+
insert(_el$2, createComponent(For, {
|
|
724
|
+
get each() {
|
|
725
|
+
return g.windows;
|
|
726
|
+
},
|
|
727
|
+
children: (w) => (() => {
|
|
728
|
+
var _el$6 = createElement("box"), _el$1 = createElement("text"), _el$10 = createTextNode(`%`), _el$11 = createElement("text");
|
|
729
|
+
insertNode(_el$6, _el$1);
|
|
730
|
+
insertNode(_el$6, _el$11);
|
|
731
|
+
setProp(_el$6, "flexDirection", "row");
|
|
732
|
+
setProp(_el$6, "gap", 1);
|
|
733
|
+
setProp(_el$6, "alignItems", "center");
|
|
734
|
+
setProp(_el$6, "flexShrink", 0);
|
|
735
|
+
insert(_el$6, createComponent(Show, {
|
|
736
|
+
get when() {
|
|
737
|
+
return g.windows.length >= 2;
|
|
738
|
+
},
|
|
739
|
+
get children() {
|
|
740
|
+
var _el$7 = createElement("text");
|
|
741
|
+
insert(_el$7, () => w.label);
|
|
742
|
+
effect((_$p) => setProp(_el$7, "fg", theme().textMuted, _$p));
|
|
743
|
+
return _el$7;
|
|
744
|
+
}
|
|
745
|
+
}), _el$1);
|
|
746
|
+
insert(_el$6, createComponent(Show, {
|
|
747
|
+
get when() {
|
|
748
|
+
return config.showBars;
|
|
749
|
+
},
|
|
750
|
+
get children() {
|
|
751
|
+
var _el$8 = createElement("box"), _el$9 = createElement("text"), _el$0 = createElement("text");
|
|
752
|
+
insertNode(_el$8, _el$9);
|
|
753
|
+
insertNode(_el$8, _el$0);
|
|
754
|
+
setProp(_el$8, "flexDirection", "row");
|
|
755
|
+
insert(_el$9, () => "▓".repeat(filledOf(w)));
|
|
756
|
+
insert(_el$0, () => "░".repeat(barWidth() - filledOf(w)));
|
|
757
|
+
effect((_p$) => {
|
|
758
|
+
var _v$ = colorOf(w), _v$2 = theme().textMuted;
|
|
759
|
+
_v$ !== _p$.e && (_p$.e = setProp(_el$9, "fg", _v$, _p$.e));
|
|
760
|
+
_v$2 !== _p$.t && (_p$.t = setProp(_el$0, "fg", _v$2, _p$.t));
|
|
761
|
+
return _p$;
|
|
762
|
+
}, {
|
|
763
|
+
e: void 0,
|
|
764
|
+
t: void 0
|
|
765
|
+
});
|
|
766
|
+
return _el$8;
|
|
767
|
+
}
|
|
768
|
+
}), _el$1);
|
|
769
|
+
insertNode(_el$1, _el$10);
|
|
770
|
+
insert(_el$1, () => pctOf(w), _el$10);
|
|
771
|
+
insert(_el$11, () => `· ${fmtDuration(w.resetsAt - now())}`);
|
|
772
|
+
effect((_p$) => {
|
|
773
|
+
var _v$3 = theme().text, _v$4 = theme().textMuted;
|
|
774
|
+
_v$3 !== _p$.e && (_p$.e = setProp(_el$1, "fg", _v$3, _p$.e));
|
|
775
|
+
_v$4 !== _p$.t && (_p$.t = setProp(_el$11, "fg", _v$4, _p$.t));
|
|
776
|
+
return _p$;
|
|
777
|
+
}, {
|
|
778
|
+
e: void 0,
|
|
779
|
+
t: void 0
|
|
780
|
+
});
|
|
781
|
+
return _el$6;
|
|
782
|
+
})()
|
|
783
|
+
}), null);
|
|
784
|
+
return _el$2;
|
|
785
|
+
})()
|
|
786
|
+
}));
|
|
787
|
+
return _el$;
|
|
788
|
+
}
|
|
789
|
+
});
|
|
790
|
+
} }
|
|
257
791
|
});
|
|
258
792
|
}
|
|
259
793
|
function SessionRow(props) {
|
|
@@ -273,10 +807,10 @@ function SessionRow(props) {
|
|
|
273
807
|
}
|
|
274
808
|
});
|
|
275
809
|
return (() => {
|
|
276
|
-
var _el$
|
|
277
|
-
insert(_el$
|
|
278
|
-
effect((_$p) => setProp(_el$
|
|
279
|
-
return _el$
|
|
810
|
+
var _el$12 = createElement("text");
|
|
811
|
+
insert(_el$12, () => `${statusIcon()}${props.session?.id?.slice(0, 8) ?? "????"}... ${props.session?.title?.slice(0, 26) ?? "(untitled)"}`);
|
|
812
|
+
effect((_$p) => setProp(_el$12, "fg", theme().textMuted, _$p));
|
|
813
|
+
return _el$12;
|
|
280
814
|
})();
|
|
281
815
|
}
|
|
282
816
|
function View(props) {
|
|
@@ -286,6 +820,23 @@ function View(props) {
|
|
|
286
820
|
const [showSessions, setShowSessions] = createSignal(false);
|
|
287
821
|
const theme = () => props.api.theme.current;
|
|
288
822
|
const branch = createMemo(() => props.api.state.vcs?.branch ? `\u2387 ${props.api.state.vcs.branch}` : null);
|
|
823
|
+
const [preset, setPreset] = createSignal(presetFromEnv(process.env));
|
|
824
|
+
onMount(() => {
|
|
825
|
+
const cwd = (props.api.state.path?.worktree ?? "") || process.cwd();
|
|
826
|
+
let cancelled = false;
|
|
827
|
+
const refresh = async () => {
|
|
828
|
+
try {
|
|
829
|
+
const info = await resolvePresetForTui(process.env, cwd);
|
|
830
|
+
if (!cancelled) setPreset(info);
|
|
831
|
+
} catch {}
|
|
832
|
+
};
|
|
833
|
+
refresh();
|
|
834
|
+
const timer = setInterval(() => void refresh(), PRESET_REFRESH_MS);
|
|
835
|
+
onCleanup(() => {
|
|
836
|
+
cancelled = true;
|
|
837
|
+
clearInterval(timer);
|
|
838
|
+
});
|
|
839
|
+
});
|
|
289
840
|
const [sessionList, { refetch: refetchSessions }] = createResource(async () => {
|
|
290
841
|
try {
|
|
291
842
|
return await props.api.client?.session?.list?.({ limit: 100 }) ?? { data: [] };
|
|
@@ -326,7 +877,9 @@ function View(props) {
|
|
|
326
877
|
cleanup.push(ev.on("memory.deleted", () => setMemoryCount((c) => c !== null ? Math.max(0, c - 1) : 0)));
|
|
327
878
|
}
|
|
328
879
|
} catch {}
|
|
329
|
-
onCleanup(() => cleanup.forEach((fn) =>
|
|
880
|
+
onCleanup(() => cleanup.forEach((fn) => {
|
|
881
|
+
fn();
|
|
882
|
+
}));
|
|
330
883
|
});
|
|
331
884
|
const configSummary = createMemo(() => {
|
|
332
885
|
const cfg = props.api.state.config;
|
|
@@ -338,51 +891,81 @@ function View(props) {
|
|
|
338
891
|
};
|
|
339
892
|
});
|
|
340
893
|
const HR = () => (() => {
|
|
341
|
-
var _el$
|
|
342
|
-
insertNode(_el$
|
|
343
|
-
effect((_$p) => setProp(_el$
|
|
344
|
-
return _el$
|
|
894
|
+
var _el$13 = createElement("text");
|
|
895
|
+
insertNode(_el$13, createTextNode(`────────────────────────────`));
|
|
896
|
+
effect((_$p) => setProp(_el$13, "fg", theme().textMuted, _$p));
|
|
897
|
+
return _el$13;
|
|
345
898
|
})();
|
|
346
899
|
return (() => {
|
|
347
|
-
var _el$
|
|
348
|
-
insertNode(_el$
|
|
349
|
-
insertNode(_el$
|
|
350
|
-
insertNode(_el$
|
|
351
|
-
insertNode(_el$
|
|
352
|
-
insertNode(_el$
|
|
353
|
-
insertNode(_el$
|
|
354
|
-
setProp(_el$
|
|
355
|
-
setProp(_el$
|
|
356
|
-
setProp(_el$
|
|
357
|
-
insert(_el$
|
|
358
|
-
insert(_el$
|
|
900
|
+
var _el$15 = createElement("box"), _el$16 = createElement("text"), _el$17 = createElement("box"), _el$18 = createElement("text"), _el$19 = createElement("text"), _el$21 = createElement("box"), _el$22 = createElement("text"), _el$23 = createElement("text"), _el$25 = createElement("box"), _el$26 = createElement("text"), _el$27 = createElement("text"), _el$29 = createElement("box"), _el$30 = createElement("text"), _el$31 = createElement("box"), _el$32 = createElement("text");
|
|
901
|
+
insertNode(_el$15, _el$16);
|
|
902
|
+
insertNode(_el$15, _el$17);
|
|
903
|
+
insertNode(_el$15, _el$21);
|
|
904
|
+
insertNode(_el$15, _el$25);
|
|
905
|
+
insertNode(_el$15, _el$29);
|
|
906
|
+
insertNode(_el$15, _el$31);
|
|
907
|
+
setProp(_el$15, "flexDirection", "column");
|
|
908
|
+
setProp(_el$15, "width", "100%");
|
|
909
|
+
setProp(_el$16, "attributes", 1);
|
|
910
|
+
insert(_el$16, () => `Pantheon${props.version ? ` v${props.version}` : ""}`);
|
|
911
|
+
insert(_el$15, createComponent(Show, {
|
|
359
912
|
get when() {
|
|
360
913
|
return branch();
|
|
361
914
|
},
|
|
362
915
|
children: (b) => (() => {
|
|
363
|
-
var _el$
|
|
364
|
-
insert(_el$
|
|
365
|
-
effect((_$p) => setProp(_el$
|
|
366
|
-
return _el$
|
|
916
|
+
var _el$33 = createElement("text");
|
|
917
|
+
insert(_el$33, b);
|
|
918
|
+
effect((_$p) => setProp(_el$33, "fg", theme().textMuted, _$p));
|
|
919
|
+
return _el$33;
|
|
367
920
|
})()
|
|
368
|
-
}), _el$
|
|
369
|
-
insert(_el$
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
return props.api;
|
|
921
|
+
}), _el$17);
|
|
922
|
+
insert(_el$15, createComponent(Show, {
|
|
923
|
+
get when() {
|
|
924
|
+
return preset().name;
|
|
373
925
|
},
|
|
374
|
-
get
|
|
375
|
-
return
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
926
|
+
get fallback() {
|
|
927
|
+
return (() => {
|
|
928
|
+
var _el$34 = createElement("box"), _el$35 = createElement("text");
|
|
929
|
+
insertNode(_el$34, _el$35);
|
|
930
|
+
setProp(_el$34, "flexDirection", "row");
|
|
931
|
+
setProp(_el$34, "gap", 1);
|
|
932
|
+
insertNode(_el$35, createTextNode(`Preset: default`));
|
|
933
|
+
effect((_$p) => setProp(_el$35, "fg", theme().textMuted, _$p));
|
|
934
|
+
return _el$34;
|
|
935
|
+
})();
|
|
936
|
+
},
|
|
937
|
+
children: (name) => (() => {
|
|
938
|
+
var _el$37 = createElement("box"), _el$38 = createElement("text"), _el$40 = createElement("text"), _el$41 = createElement("text");
|
|
939
|
+
insertNode(_el$37, _el$38);
|
|
940
|
+
insertNode(_el$37, _el$40);
|
|
941
|
+
insertNode(_el$37, _el$41);
|
|
942
|
+
setProp(_el$37, "flexDirection", "row");
|
|
943
|
+
setProp(_el$37, "gap", 1);
|
|
944
|
+
insertNode(_el$38, createTextNode(`⚡ Preset:`));
|
|
945
|
+
insert(_el$40, name);
|
|
946
|
+
insert(_el$41, () => `(${preset().source ?? ""})`);
|
|
947
|
+
effect((_p$) => {
|
|
948
|
+
var _v$12 = theme().textMuted, _v$13 = theme().accent, _v$14 = theme().textMuted;
|
|
949
|
+
_v$12 !== _p$.e && (_p$.e = setProp(_el$38, "fg", _v$12, _p$.e));
|
|
950
|
+
_v$13 !== _p$.t && (_p$.t = setProp(_el$40, "fg", _v$13, _p$.t));
|
|
951
|
+
_v$14 !== _p$.a && (_p$.a = setProp(_el$41, "fg", _v$14, _p$.a));
|
|
952
|
+
return _p$;
|
|
953
|
+
}, {
|
|
954
|
+
e: void 0,
|
|
955
|
+
t: void 0,
|
|
956
|
+
a: void 0
|
|
957
|
+
});
|
|
958
|
+
return _el$37;
|
|
959
|
+
})()
|
|
960
|
+
}), _el$17);
|
|
961
|
+
insert(_el$15, createComponent(HR, {}), _el$17);
|
|
962
|
+
insertNode(_el$17, _el$18);
|
|
963
|
+
insertNode(_el$17, _el$19);
|
|
964
|
+
setProp(_el$17, "onMouseDown", () => setShowSessions((x) => !x));
|
|
965
|
+
setProp(_el$18, "attributes", 1);
|
|
966
|
+
insert(_el$18, () => `${showSessions() ? "▼" : "▶"} Sessions`);
|
|
967
|
+
insert(_el$19, () => ` (${String(totalSessions())})`);
|
|
968
|
+
insert(_el$15, createComponent(Show, {
|
|
386
969
|
get when() {
|
|
387
970
|
return showSessions();
|
|
388
971
|
},
|
|
@@ -393,19 +976,19 @@ function View(props) {
|
|
|
393
976
|
},
|
|
394
977
|
get fallback() {
|
|
395
978
|
return (() => {
|
|
396
|
-
var _el$
|
|
397
|
-
insertNode(_el$
|
|
398
|
-
setProp(_el$
|
|
399
|
-
insertNode(_el$
|
|
400
|
-
effect((_$p) => setProp(_el$
|
|
401
|
-
return _el$
|
|
979
|
+
var _el$42 = createElement("box"), _el$43 = createElement("text");
|
|
980
|
+
insertNode(_el$42, _el$43);
|
|
981
|
+
setProp(_el$42, "marginLeft", 1);
|
|
982
|
+
insertNode(_el$43, createTextNode(`No recent sessions`));
|
|
983
|
+
effect((_$p) => setProp(_el$43, "fg", theme().textMuted, _$p));
|
|
984
|
+
return _el$42;
|
|
402
985
|
})();
|
|
403
986
|
},
|
|
404
987
|
get children() {
|
|
405
|
-
var _el$
|
|
406
|
-
setProp(_el$
|
|
407
|
-
setProp(_el$
|
|
408
|
-
insert(_el$
|
|
988
|
+
var _el$20 = createElement("box");
|
|
989
|
+
setProp(_el$20, "marginLeft", 1);
|
|
990
|
+
setProp(_el$20, "flexDirection", "column");
|
|
991
|
+
insert(_el$20, createComponent(For, {
|
|
409
992
|
get each() {
|
|
410
993
|
return recentSessions();
|
|
411
994
|
},
|
|
@@ -416,32 +999,32 @@ function View(props) {
|
|
|
416
999
|
session: ses
|
|
417
1000
|
})
|
|
418
1001
|
}));
|
|
419
|
-
return _el$
|
|
1002
|
+
return _el$20;
|
|
420
1003
|
}
|
|
421
1004
|
});
|
|
422
1005
|
}
|
|
423
|
-
}), _el$
|
|
424
|
-
insertNode(_el$
|
|
425
|
-
insertNode(_el$
|
|
426
|
-
setProp(_el$
|
|
427
|
-
setProp(_el$
|
|
428
|
-
insert(_el$
|
|
429
|
-
insert(_el$
|
|
430
|
-
insert(_el$
|
|
1006
|
+
}), _el$21);
|
|
1007
|
+
insertNode(_el$21, _el$22);
|
|
1008
|
+
insertNode(_el$21, _el$23);
|
|
1009
|
+
setProp(_el$21, "onMouseDown", () => setShowCommands((x) => !x));
|
|
1010
|
+
setProp(_el$22, "attributes", 1);
|
|
1011
|
+
insert(_el$22, () => `${showCommands() ? "▼" : "▶"} Commands`);
|
|
1012
|
+
insert(_el$23, () => ` (${String(COMMANDS.length)})`);
|
|
1013
|
+
insert(_el$15, createComponent(Show, {
|
|
431
1014
|
get when() {
|
|
432
1015
|
return showCommands();
|
|
433
1016
|
},
|
|
434
1017
|
get children() {
|
|
435
|
-
var _el$
|
|
436
|
-
setProp(_el$
|
|
437
|
-
setProp(_el$
|
|
438
|
-
insert(_el$
|
|
1018
|
+
var _el$24 = createElement("box");
|
|
1019
|
+
setProp(_el$24, "marginLeft", 1);
|
|
1020
|
+
setProp(_el$24, "flexDirection", "column");
|
|
1021
|
+
insert(_el$24, createComponent(For, {
|
|
439
1022
|
each: COMMANDS,
|
|
440
1023
|
children: (cmd) => (() => {
|
|
441
|
-
var _el$
|
|
442
|
-
insertNode(_el$
|
|
443
|
-
insertNode(_el$
|
|
444
|
-
setProp(_el$
|
|
1024
|
+
var _el$45 = createElement("box"), _el$46 = createElement("text"), _el$47 = createElement("text");
|
|
1025
|
+
insertNode(_el$45, _el$46);
|
|
1026
|
+
insertNode(_el$45, _el$47);
|
|
1027
|
+
setProp(_el$45, "onMouseDown", (e) => {
|
|
445
1028
|
e.stopPropagation();
|
|
446
1029
|
try {
|
|
447
1030
|
const cmdApi = props.api.command;
|
|
@@ -453,65 +1036,65 @@ function View(props) {
|
|
|
453
1036
|
message: `Type ${cmd.name} in chat`
|
|
454
1037
|
});
|
|
455
1038
|
});
|
|
456
|
-
insert(_el$
|
|
457
|
-
insert(_el$
|
|
1039
|
+
insert(_el$46, () => cmd.name);
|
|
1040
|
+
insert(_el$47, () => ` \u2014 ${cmd.desc}`);
|
|
458
1041
|
effect((_p$) => {
|
|
459
|
-
var _v$
|
|
460
|
-
_v$
|
|
461
|
-
_v$
|
|
1042
|
+
var _v$15 = cmd.name === "/pantheon" ? theme().accent : theme().textMuted, _v$16 = theme().textMuted;
|
|
1043
|
+
_v$15 !== _p$.e && (_p$.e = setProp(_el$46, "fg", _v$15, _p$.e));
|
|
1044
|
+
_v$16 !== _p$.t && (_p$.t = setProp(_el$47, "fg", _v$16, _p$.t));
|
|
462
1045
|
return _p$;
|
|
463
1046
|
}, {
|
|
464
1047
|
e: void 0,
|
|
465
1048
|
t: void 0
|
|
466
1049
|
});
|
|
467
|
-
return _el$
|
|
1050
|
+
return _el$45;
|
|
468
1051
|
})()
|
|
469
1052
|
}));
|
|
470
|
-
return _el$
|
|
1053
|
+
return _el$24;
|
|
471
1054
|
}
|
|
472
|
-
}), _el$
|
|
473
|
-
insertNode(_el$
|
|
474
|
-
insertNode(_el$
|
|
475
|
-
setProp(_el$
|
|
476
|
-
setProp(_el$
|
|
477
|
-
insert(_el$
|
|
478
|
-
insert(_el$
|
|
479
|
-
insert(_el$
|
|
1055
|
+
}), _el$25);
|
|
1056
|
+
insertNode(_el$25, _el$26);
|
|
1057
|
+
insertNode(_el$25, _el$27);
|
|
1058
|
+
setProp(_el$25, "onMouseDown", () => setShowAgents((x) => !x));
|
|
1059
|
+
setProp(_el$26, "attributes", 1);
|
|
1060
|
+
insert(_el$26, () => `${showAgents() ? "▼" : "▶"} Agents`);
|
|
1061
|
+
insert(_el$27, () => ` (${String(AGENTS.length)})`);
|
|
1062
|
+
insert(_el$15, createComponent(Show, {
|
|
480
1063
|
get when() {
|
|
481
1064
|
return showAgents();
|
|
482
1065
|
},
|
|
483
1066
|
get children() {
|
|
484
|
-
var _el$
|
|
485
|
-
setProp(_el$
|
|
486
|
-
setProp(_el$
|
|
487
|
-
insert(_el$
|
|
1067
|
+
var _el$28 = createElement("box");
|
|
1068
|
+
setProp(_el$28, "marginLeft", 1);
|
|
1069
|
+
setProp(_el$28, "flexDirection", "column");
|
|
1070
|
+
insert(_el$28, createComponent(For, {
|
|
488
1071
|
each: AGENTS,
|
|
489
1072
|
children: (agent) => (() => {
|
|
490
|
-
var _el$
|
|
491
|
-
insertNode(_el$
|
|
492
|
-
insertNode(_el$
|
|
493
|
-
insert(_el$
|
|
494
|
-
insert(_el$
|
|
1073
|
+
var _el$48 = createElement("box"), _el$49 = createElement("text"), _el$50 = createElement("text");
|
|
1074
|
+
insertNode(_el$48, _el$49);
|
|
1075
|
+
insertNode(_el$48, _el$50);
|
|
1076
|
+
insert(_el$49, () => `${agent.tier === "premium" ? "✦ " : "· "}${agent.name}`);
|
|
1077
|
+
insert(_el$50, () => ` \u2014 ${agent.role}`);
|
|
495
1078
|
effect((_p$) => {
|
|
496
|
-
var _v$
|
|
497
|
-
_v$
|
|
498
|
-
_v$
|
|
1079
|
+
var _v$17 = agent.tier === "premium" ? theme().accent : theme().textMuted, _v$18 = theme().textMuted;
|
|
1080
|
+
_v$17 !== _p$.e && (_p$.e = setProp(_el$49, "fg", _v$17, _p$.e));
|
|
1081
|
+
_v$18 !== _p$.t && (_p$.t = setProp(_el$50, "fg", _v$18, _p$.t));
|
|
499
1082
|
return _p$;
|
|
500
1083
|
}, {
|
|
501
1084
|
e: void 0,
|
|
502
1085
|
t: void 0
|
|
503
1086
|
});
|
|
504
|
-
return _el$
|
|
1087
|
+
return _el$48;
|
|
505
1088
|
})()
|
|
506
1089
|
}));
|
|
507
|
-
return _el$
|
|
1090
|
+
return _el$28;
|
|
508
1091
|
}
|
|
509
|
-
}), _el$
|
|
510
|
-
insertNode(_el$
|
|
511
|
-
setProp(_el$
|
|
512
|
-
setProp(_el$
|
|
513
|
-
insert(_el$
|
|
514
|
-
insert(_el$
|
|
1092
|
+
}), _el$29);
|
|
1093
|
+
insertNode(_el$29, _el$30);
|
|
1094
|
+
setProp(_el$29, "onMouseDown", () => setShowConfig((x) => !x));
|
|
1095
|
+
setProp(_el$30, "attributes", 1);
|
|
1096
|
+
insert(_el$30, () => `${showConfig() ? "▼" : "▶"} Config`);
|
|
1097
|
+
insert(_el$15, createComponent(Show, {
|
|
515
1098
|
get when() {
|
|
516
1099
|
return showConfig();
|
|
517
1100
|
},
|
|
@@ -522,53 +1105,53 @@ function View(props) {
|
|
|
522
1105
|
},
|
|
523
1106
|
get fallback() {
|
|
524
1107
|
return (() => {
|
|
525
|
-
var _el$
|
|
526
|
-
insertNode(_el$
|
|
527
|
-
setProp(_el$
|
|
528
|
-
insertNode(_el$
|
|
529
|
-
effect((_$p) => setProp(_el$
|
|
530
|
-
return _el$
|
|
1108
|
+
var _el$51 = createElement("box"), _el$52 = createElement("text");
|
|
1109
|
+
insertNode(_el$51, _el$52);
|
|
1110
|
+
setProp(_el$51, "marginLeft", 1);
|
|
1111
|
+
insertNode(_el$52, createTextNode(`No config data`));
|
|
1112
|
+
effect((_$p) => setProp(_el$52, "fg", theme().textMuted, _$p));
|
|
1113
|
+
return _el$51;
|
|
531
1114
|
})();
|
|
532
1115
|
},
|
|
533
1116
|
children: (cfg) => (() => {
|
|
534
|
-
var _el$
|
|
535
|
-
insertNode(_el$
|
|
536
|
-
insertNode(_el$
|
|
537
|
-
setProp(_el$
|
|
538
|
-
setProp(_el$
|
|
539
|
-
insert(_el$
|
|
540
|
-
insert(_el$
|
|
1117
|
+
var _el$54 = createElement("box"), _el$55 = createElement("text"), _el$56 = createElement("text");
|
|
1118
|
+
insertNode(_el$54, _el$55);
|
|
1119
|
+
insertNode(_el$54, _el$56);
|
|
1120
|
+
setProp(_el$54, "marginLeft", 1);
|
|
1121
|
+
setProp(_el$54, "flexDirection", "column");
|
|
1122
|
+
insert(_el$55, () => `MCP: ${String(cfg().mcpCount)} Plugins: ${String(cfg().pluginCount)}`);
|
|
1123
|
+
insert(_el$56, () => `Auto-compaction: ${cfg().autoCompaction ? "ON" : "OFF"}`);
|
|
541
1124
|
effect((_p$) => {
|
|
542
|
-
var _v$
|
|
543
|
-
_v$
|
|
544
|
-
_v$
|
|
1125
|
+
var _v$19 = theme().textMuted, _v$20 = theme().textMuted;
|
|
1126
|
+
_v$19 !== _p$.e && (_p$.e = setProp(_el$55, "fg", _v$19, _p$.e));
|
|
1127
|
+
_v$20 !== _p$.t && (_p$.t = setProp(_el$56, "fg", _v$20, _p$.t));
|
|
545
1128
|
return _p$;
|
|
546
1129
|
}, {
|
|
547
1130
|
e: void 0,
|
|
548
1131
|
t: void 0
|
|
549
1132
|
});
|
|
550
|
-
return _el$
|
|
1133
|
+
return _el$54;
|
|
551
1134
|
})()
|
|
552
1135
|
});
|
|
553
1136
|
}
|
|
554
|
-
}), _el$
|
|
555
|
-
insertNode(_el$
|
|
556
|
-
setProp(_el$
|
|
557
|
-
insert(_el$
|
|
1137
|
+
}), _el$31);
|
|
1138
|
+
insertNode(_el$31, _el$32);
|
|
1139
|
+
setProp(_el$31, "marginTop", 1);
|
|
1140
|
+
insert(_el$32, (() => {
|
|
558
1141
|
var _c$ = memo(() => memoryCount() !== null);
|
|
559
1142
|
return () => _c$() ? `Memory: ${fmtInt(memoryCount())} entries` : "Memory: N/A";
|
|
560
1143
|
})());
|
|
561
1144
|
effect((_p$) => {
|
|
562
|
-
var _v$
|
|
563
|
-
_v$
|
|
564
|
-
_v$
|
|
565
|
-
_v$
|
|
566
|
-
_v$
|
|
567
|
-
_v$
|
|
568
|
-
_v$
|
|
569
|
-
_v$
|
|
570
|
-
_v$
|
|
571
|
-
_v$
|
|
1145
|
+
var _v$5 = theme().accent, _v$6 = theme().text, _v$7 = theme().textMuted, _v$8 = theme().text, _v$9 = theme().textMuted, _v$0 = theme().text, _v$1 = theme().textMuted, _v$10 = theme().text, _v$11 = theme().textMuted;
|
|
1146
|
+
_v$5 !== _p$.e && (_p$.e = setProp(_el$16, "fg", _v$5, _p$.e));
|
|
1147
|
+
_v$6 !== _p$.t && (_p$.t = setProp(_el$18, "fg", _v$6, _p$.t));
|
|
1148
|
+
_v$7 !== _p$.a && (_p$.a = setProp(_el$19, "fg", _v$7, _p$.a));
|
|
1149
|
+
_v$8 !== _p$.o && (_p$.o = setProp(_el$22, "fg", _v$8, _p$.o));
|
|
1150
|
+
_v$9 !== _p$.i && (_p$.i = setProp(_el$23, "fg", _v$9, _p$.i));
|
|
1151
|
+
_v$0 !== _p$.n && (_p$.n = setProp(_el$26, "fg", _v$0, _p$.n));
|
|
1152
|
+
_v$1 !== _p$.s && (_p$.s = setProp(_el$27, "fg", _v$1, _p$.s));
|
|
1153
|
+
_v$10 !== _p$.h && (_p$.h = setProp(_el$30, "fg", _v$10, _p$.h));
|
|
1154
|
+
_v$11 !== _p$.r && (_p$.r = setProp(_el$32, "fg", _v$11, _p$.r));
|
|
572
1155
|
return _p$;
|
|
573
1156
|
}, {
|
|
574
1157
|
e: void 0,
|
|
@@ -581,11 +1164,12 @@ function View(props) {
|
|
|
581
1164
|
h: void 0,
|
|
582
1165
|
r: void 0
|
|
583
1166
|
});
|
|
584
|
-
return _el$
|
|
1167
|
+
return _el$15;
|
|
585
1168
|
})();
|
|
586
1169
|
}
|
|
587
1170
|
const tui = async (api, _options, _meta) => {
|
|
588
1171
|
const version = await detectVersion(api);
|
|
1172
|
+
setupUsageBar(api);
|
|
589
1173
|
api.slots.register({
|
|
590
1174
|
order: 900,
|
|
591
1175
|
slots: { sidebar_content(_ctx, props) {
|