import { memo as _$memo } from "opentui:runtime-module:%40opentui%2Fsolid"; import { createTextNode as _$createTextNode } from "opentui:runtime-module:%40opentui%2Fsolid"; import { createComponent as _$createComponent } from "opentui:runtime-module:%40opentui%2Fsolid"; import { effect as _$effect } from "opentui:runtime-module:%40opentui%2Fsolid"; import { insertNode as _$insertNode } from "opentui:runtime-module:%40opentui%2Fsolid"; import { insert as _$insert } from "opentui:runtime-module:%40opentui%2Fsolid"; import { setProp as _$setProp } from "opentui:runtime-module:%40opentui%2Fsolid"; import { createElement as _$createElement } from "opentui:runtime-module:%40opentui%2Fsolid"; /** @jsxImportSource @opentui/solid */ import { readFileSync } from "node:fs"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; import { formatQuotaMoney } from "@cortexkit/anthropic-auth-core"; import { createEffect, createSignal, For, onCleanup, Show } from "opentui:runtime-module:solid-js"; import { createRpcClient } from "./rpc/rpc-client.js"; import { getRpcDir } from "./rpc/rpc-dir.js"; import { computeQuotaPacing, DEFAULT_SIDEBAR_STATE, FIVE_HOUR_MS, formatScopedQuotaLabel, getCollapsedQuotaSummary, getFableRecoverySummary, getSidebarState, resolveActiveAccount, SEVEN_DAY_MS } from "./sidebar-state.js"; import { openCommandDialog } from "./tui/command-dialogs.js"; import { computeEffectiveOrder, DEFAULT_SLOT_ORDER, PLUGIN_KEY, queueTuiPreferenceUpdate, readTuiPreferencesFile, resolveAnthropicAuthPrefs, watchTuiPreferences } from "./tui-preferences.js"; const RPC_POLL_MS = 500; let rpcPollStarted = false; const ID = 'cortexkit.anthropic-auth'; // Read package metadata from either the raw src/ entry or its generated // src/tui-compiled/ counterpart. Avoid a JSON import because package.json sits // outside the declaration build's rootDir. const PLUGIN_VERSION = (() => { const here = dirname(fileURLToPath(import.meta.url)); for (const packageFile of [join(here, '..', 'package.json'), join(here, '..', '..', 'package.json')]) { try { const raw = readFileSync(packageFile, 'utf8'); const version = JSON.parse(raw).version; if (version) return version; } catch { // Try the path for the other TUI entry layout. } } return ''; })(); // biome-ignore lint/suspicious/noExplicitAny: opentui border prop not typed in plugin tui surface const SINGLE_BORDER = { type: 'single' }; // Tone -> theme token. Theme tokens only — never hardcoded colors. Fallbacks // resolve to other theme tokens so the sidebar always tracks the active theme. function toneColor(theme, tone) { switch (tone) { case 'ok': return theme.success ?? theme.accent; case 'warn': return theme.warning ?? theme.accent; case 'err': return theme.error ?? theme.accent; case 'muted': return theme.textMuted ?? theme.text; case 'accent': return theme.accent ?? theme.text; default: return theme.text; } } function usageTone(usedPct, appearance) { if (usedPct < appearance.warnThreshold) return 'ok'; if (usedPct < appearance.errorThreshold) return 'warn'; return 'err'; } const PACE_RESERVE_CHAR = '\u2592'; const PACE_DEFICIT_CHAR = '\u2593'; // Variant C pacing bar: normal fill up to min(used, pace), then a pace // segment covering the gap — headroom being banked (reserve, ok tone) or the // overshoot itself (deficit, err tone) — then empty cells. Without pacing the // bar renders as a single fill+empty pair, identical to the pre-pacing look. // Empty text nodes still occupy a phantom cell in the opentui flex row, so // zero-length segments are dropped from every return path (plain and pacing). function quotaBarSegments(usedPct, appearance, pacing) { const width = appearance.barWidth; const cells = pct => Math.max(0, Math.min(Math.round(pct / 100 * width), width)); const usedCells = cells(usedPct); const fillTone = usageTone(usedPct, appearance); const plain = [{ text: appearance.barFilledChar.repeat(usedCells), tone: fillTone }, { text: appearance.barEmptyChar.repeat(width - usedCells), tone: fillTone }]; if (!pacing) return plain.filter(segment => segment.text.length > 0); const paceCells = cells(pacing.pacePercent); const lo = Math.min(usedCells, paceCells); const hi = Math.max(usedCells, paceCells); if (hi === lo) return plain.filter(segment => segment.text.length > 0); const overspent = usedCells > paceCells; return [{ text: appearance.barFilledChar.repeat(lo), tone: fillTone }, { text: (overspent ? PACE_DEFICIT_CHAR : PACE_RESERVE_CHAR).repeat(hi - lo), tone: overspent ? 'err' : 'ok' }, { text: appearance.barEmptyChar.repeat(width - hi), tone: fillTone }].filter(segment => segment.text.length > 0); } function formatResetIn(resetsAt) { if (!resetsAt) return ''; const ms = new Date(resetsAt).getTime() - Date.now(); if (ms <= 0) return 'now'; const mins = Math.floor(ms / 60_000); if (mins < 60) return `${mins}m`; const hrs = Math.floor(mins / 60); if (hrs < 24) { const rm = mins % 60; return rm > 0 ? `${hrs}h${rm}m` : `${hrs}h`; } const days = Math.floor(hrs / 24); const rh = hrs % 24; return rh > 0 ? `${days}d${rh}h` : `${days}d`; } function formatUntil(until) { if (!until) return ''; const ms = until - Date.now(); if (ms <= 0) return 'now'; const mins = Math.ceil(ms / 60_000); if (mins < 60) return `${mins}m`; const hrs = Math.floor(mins / 60); const rm = mins % 60; return rm > 0 ? `${hrs}h${rm}m` : `${hrs}h`; } // --- Reusable components (aft-style) --------------------------------------- function SectionHeader(props) { return (() => { var _el$ = _$createElement("box"), _el$2 = _$createElement("text"), _el$3 = _$createElement("b"); _$insertNode(_el$, _el$2); _$setProp(_el$, "width", '100%'); _$insertNode(_el$2, _el$3); _$insert(_el$3, () => props.title); _$effect(_p$ => { var _v$ = props.marginTop ?? 1, _v$2 = props.theme.text; _v$ !== _p$.e && (_p$.e = _$setProp(_el$, "marginTop", _v$, _p$.e)); _v$2 !== _p$.t && (_p$.t = _$setProp(_el$2, "fg", _v$2, _p$.t)); return _p$; }, { e: undefined, t: undefined }); return _el$; })(); } function StatRow(props) { return (() => { var _el$4 = _$createElement("box"), _el$5 = _$createElement("text"), _el$6 = _$createElement("text"), _el$7 = _$createElement("b"); _$insertNode(_el$4, _el$5); _$insertNode(_el$4, _el$6); _$setProp(_el$4, "width", '100%'); _$setProp(_el$4, "flexDirection", 'row'); _$setProp(_el$4, "justifyContent", 'space-between'); _$insert(_el$5, () => props.label); _$insertNode(_el$6, _el$7); _$insert(_el$7, () => props.value); _$effect(_p$ => { var _v$3 = props.theme.textMuted, _v$4 = toneColor(props.theme, props.tone ?? 'text'); _v$3 !== _p$.e && (_p$.e = _$setProp(_el$5, "fg", _v$3, _p$.e)); _v$4 !== _p$.t && (_p$.t = _$setProp(_el$6, "fg", _v$4, _p$.t)); return _p$; }, { e: undefined, t: undefined }); return _el$4; })(); } // Compact row for the collapsed view: muted label left, caller-provided value // right. Mirrors StatRow's layout so columns line up. function CollapsedRow(props) { return (() => { var _el$8 = _$createElement("box"), _el$9 = _$createElement("text"); _$insertNode(_el$8, _el$9); _$setProp(_el$8, "width", '100%'); _$setProp(_el$8, "flexDirection", 'row'); _$setProp(_el$8, "justifyContent", 'space-between'); _$insert(_el$9, () => props.label); _$insert(_el$8, () => props.children, null); _$effect(_$p => _$setProp(_el$9, "fg", props.theme.textMuted, _$p)); return _el$8; })(); } // Quota window row: muted label left, tone-colored bar + percentage right, // with an optional muted reset suffix. When pacing data is present, the bar // gains a pace segment and an off-pace window adds a muted subline with the // reserve/deficit delta and the projected runout. function QuotaRow(props) { const used = () => props.window?.usedPercent ?? 0; const reset = () => formatResetIn(props.window?.resetsAt); const paceLine = () => { const pacing = props.pacing; if (!pacing || pacing.state === 'on-pace') return null; const pct = Math.round(Math.abs(pacing.deltaPercent)); if (pacing.state === 'reserve') return `reserve ${pct}% \u00b7 lasts`; return pacing.runsOutAt ? `deficit ${pct}% \u00b7 out in ${formatResetIn(pacing.runsOutAt)}` : `deficit ${pct}% \u00b7 lasts`; }; return _$createComponent(Show, { get when() { return props.window; }, get fallback() { return (() => { var _el$17 = _$createElement("box"), _el$18 = _$createElement("text"), _el$19 = _$createElement("text"); _$insertNode(_el$17, _el$18); _$insertNode(_el$17, _el$19); _$setProp(_el$17, "width", '100%'); _$setProp(_el$17, "flexDirection", 'row'); _$insert(_el$18, () => props.label.padEnd(3)); _$insertNode(_el$19, _$createTextNode(`—`)); _$effect(_p$ => { var _v$9 = props.theme.textMuted, _v$0 = props.theme.textMuted; _v$9 !== _p$.e && (_p$.e = _$setProp(_el$18, "fg", _v$9, _p$.e)); _v$0 !== _p$.t && (_p$.t = _$setProp(_el$19, "fg", _v$0, _p$.t)); return _p$; }, { e: undefined, t: undefined }); return _el$17; })(); }, get children() { return [(() => { var _el$0 = _$createElement("box"), _el$1 = _$createElement("box"), _el$10 = _$createElement("text"), _el$11 = _$createElement("text"); _$insertNode(_el$0, _el$1); _$setProp(_el$0, "width", '100%'); _$setProp(_el$0, "flexDirection", 'row'); _$setProp(_el$0, "justifyContent", 'space-between'); _$insertNode(_el$1, _el$10); _$insertNode(_el$1, _el$11); _$setProp(_el$1, "flexDirection", 'row'); _$insert(_el$10, () => props.label.padEnd(3)); _$insert(_el$1, _$createComponent(For, { get each() { return quotaBarSegments(used(), props.appearance, props.pacing); }, children: segment => (() => { var _el$21 = _$createElement("text"); _$insert(_el$21, () => segment.text); _$effect(_$p => _$setProp(_el$21, "fg", toneColor(props.theme, segment.tone), _$p)); return _el$21; })() }), _el$11); _$insert(_el$11, () => ` ${String(Math.round(used())).padStart(3)}%${props.binding ? ' •' : ''}`); _$insert(_el$0, _$createComponent(Show, { get when() { return reset(); }, get children() { var _el$12 = _$createElement("text"); _$insert(_el$12, reset); _$effect(_$p => _$setProp(_el$12, "fg", props.theme.textMuted, _$p)); return _el$12; } }), null); _$effect(_p$ => { var _v$5 = props.theme.textMuted, _v$6 = toneColor(props.theme, usageTone(used(), props.appearance)); _v$5 !== _p$.e && (_p$.e = _$setProp(_el$10, "fg", _v$5, _p$.e)); _v$6 !== _p$.t && (_p$.t = _$setProp(_el$11, "fg", _v$6, _p$.t)); return _p$; }, { e: undefined, t: undefined }); return _el$0; })(), _$createComponent(Show, { get when() { return paceLine(); }, get children() { var _el$13 = _$createElement("box"), _el$14 = _$createElement("text"), _el$16 = _$createElement("text"); _$insertNode(_el$13, _el$14); _$insertNode(_el$13, _el$16); _$setProp(_el$13, "width", '100%'); _$setProp(_el$13, "flexDirection", 'row'); _$insertNode(_el$14, _$createTextNode(` `)); _$insert(_el$16, paceLine); _$effect(_p$ => { var _v$7 = props.theme.textMuted, _v$8 = toneColor(props.theme, props.pacing?.state === 'deficit' ? 'warn' : 'muted'); _v$7 !== _p$.e && (_p$.e = _$setProp(_el$14, "fg", _v$7, _p$.e)); _v$8 !== _p$.t && (_p$.t = _$setProp(_el$16, "fg", _v$8, _p$.t)); return _p$; }, { e: undefined, t: undefined }); return _el$13; } })]; } }); } // Account block: header row (name + status word) then per-window quota rows. // Pacing is computed here — per window, against the wall clock at render time // (re-evaluated on every state poll) — and disabled by passing false. function AccountBlock(props) { const statusWord = () => props.needsReauth ? 're-login' : props.active ? 'active' : 'idle'; const statusTone = () => props.needsReauth ? 'err' : props.active ? 'ok' : 'muted'; const pacingFor = (window, windowMs) => props.pacingEnabled && window ? computeQuotaPacing(window, windowMs, Date.now()) : null; return (() => { var _el$22 = _$createElement("box"), _el$23 = _$createElement("box"), _el$24 = _$createElement("text"), _el$25 = _$createElement("b"), _el$26 = _$createElement("text"), _el$27 = _$createElement("b"); _$insertNode(_el$22, _el$23); _$setProp(_el$22, "width", '100%'); _$setProp(_el$22, "flexDirection", 'column'); _$insertNode(_el$23, _el$24); _$insertNode(_el$23, _el$26); _$setProp(_el$23, "width", '100%'); _$setProp(_el$23, "flexDirection", 'row'); _$setProp(_el$23, "justifyContent", 'space-between'); _$insertNode(_el$24, _el$25); _$insert(_el$25, () => `${props.name}${props.tierLabel ? ` · ${props.tierLabel}` : ''}`); _$insertNode(_el$26, _el$27); _$insert(_el$27, statusWord); _$insert(_el$22, _$createComponent(Show, { get when() { return props.quota; }, get fallback() { return (() => { var _el$30 = _$createElement("text"); _$insertNode(_el$30, _$createTextNode(`checking…`)); _$effect(_$p => _$setProp(_el$30, "fg", props.theme.textMuted, _$p)); return _el$30; })(); }, get children() { return [_$createComponent(QuotaRow, { get theme() { return props.theme; }, get appearance() { return props.appearance; }, label: "5h", get window() { return props.quota?.five_hour; }, get pacing() { return pacingFor(props.quota?.five_hour, FIVE_HOUR_MS); }, get binding() { return props.quota?.bindingWindow === 'five_hour'; } }), _$createComponent(QuotaRow, { get theme() { return props.theme; }, get appearance() { return props.appearance; }, label: "7d", get window() { return props.quota?.seven_day; }, get pacing() { return pacingFor(props.quota?.seven_day, SEVEN_DAY_MS); }, get binding() { return props.quota?.bindingWindow === 'seven_day'; } }), _$createComponent(For, { get each() { return props.quota?.scoped ?? []; }, children: window => _$createComponent(QuotaRow, { get theme() { return props.theme; }, get appearance() { return props.appearance; }, get label() { return formatScopedQuotaLabel(window.title); }, window: window, get pacing() { return pacingFor(window, SEVEN_DAY_MS); }, get binding() { return props.quota?.bindingWindow === window.id; } }) }), _$createComponent(Show, { get when() { return props.quota?.extraUsage; }, children: extraUsage => (() => { var _el$32 = _$createElement("text"); _$insert(_el$32, () => `credits ${formatQuotaMoney(extraUsage().used)}/${formatQuotaMoney(extraUsage().limit)}${extraUsage().exhausted ? ' · exhausted' : ''}`); _$effect(_$p => _$setProp(_el$32, "fg", toneColor(props.theme, extraUsage().severity === 'critical' ? 'err' : extraUsage().severity === 'warning' || extraUsage().exhausted ? 'warn' : 'muted'), _$p)); return _el$32; })() }), _$createComponent(Show, { get when() { return props.quota?.fallbackAdvised === true; }, get children() { var _el$28 = _$createElement("text"); _$insertNode(_el$28, _$createTextNode(`→ fallback advised`)); _$effect(_$p => _$setProp(_el$28, "fg", props.theme.textMuted, _$p)); return _el$28; } })]; } }), null); _$effect(_p$ => { var _v$1 = props.marginTop ?? 0, _v$10 = props.theme.text, _v$11 = toneColor(props.theme, statusTone()); _v$1 !== _p$.e && (_p$.e = _$setProp(_el$22, "marginTop", _v$1, _p$.e)); _v$10 !== _p$.t && (_p$.t = _$setProp(_el$24, "fg", _v$10, _p$.t)); _v$11 !== _p$.a && (_p$.a = _$setProp(_el$26, "fg", _v$11, _p$.a)); return _p$; }, { e: undefined, t: undefined, a: undefined }); return _el$22; })(); } export { formatQuotaMoney }; // --- Quota dialog content --------------------------------------------------- // Renders the same rich visualization as the sidebar (account blocks with // quota bars + pacing) so the modal matches the sidebar look. function QuotaDialogContent(props) { const prefs = props.controller.prefs; const [state, setState] = createSignal(DEFAULT_SIDEBAR_STATE); let lastUpdated = 0; async function refresh() { const next = await readStateFromFile(); if (next.lastUpdated !== lastUpdated) { lastUpdated = next.lastUpdated; setState(next); } } createEffect(() => { const timer = setInterval(refresh, prefs().pollMs); onCleanup(() => clearInterval(timer)); }); setTimeout(refresh, 0); const theme = () => props.api.theme.current; const enabledFallbacks = () => (state().fallbacks ?? []).filter(f => f.enabled); return (() => { var _el$33 = _$createElement("box"), _el$34 = _$createElement("box"), _el$35 = _$createElement("box"), _el$36 = _$createElement("text"), _el$37 = _$createElement("b"); _$insertNode(_el$33, _el$34); _$setProp(_el$33, "flexDirection", 'column'); _$setProp(_el$33, "padding", 2); _$setProp(_el$33, "width", '100%'); _$setProp(_el$33, "alignItems", 'center'); _$insertNode(_el$34, _el$35); _$setProp(_el$34, "flexDirection", 'column'); _$setProp(_el$34, "width", 58); _$insertNode(_el$35, _el$36); _$setProp(_el$35, "width", '100%'); _$setProp(_el$35, "justifyContent", 'center'); _$setProp(_el$35, "marginBottom", 1); _$insertNode(_el$36, _el$37); _$insertNode(_el$37, _$createTextNode(`Quota`)); _$insert(_el$34, _$createComponent(AccountBlock, { get theme() { return theme(); }, get appearance() { return prefs().appearance; }, name: "main", get quota() { return state().main?.quota; }, get active() { return state().activeId === 'main'; }, get pacingEnabled() { return prefs().sections.pacing; }, get tierLabel() { return state().main?.tierLabel; } }), null); _$insert(_el$34, _$createComponent(Show, { get when() { return prefs().sections.fallbackAccounts; }, get children() { return _$createComponent(For, { get each() { return enabledFallbacks(); }, children: fb => _$createComponent(AccountBlock, { get theme() { return theme(); }, get appearance() { return prefs().appearance; }, get name() { return fb.label ?? fb.id; }, get quota() { return fb.quota; }, get active() { return state().activeId === fb.id; }, get pacingEnabled() { return prefs().sections.pacing; }, get tierLabel() { return fb.tierLabel; }, get needsReauth() { return fb.needsReauth; }, marginTop: 1 }) }); } }), null); _$effect(_$p => _$setProp(_el$36, "fg", theme().text, _$p)); return _el$33; })(); } // --- State plumbing --------------------------------------------------------- async function readStateFromFile() { return getSidebarState(); } // The TUI may unmount and remount sidebar_content when the user switches // views (e.g. main -> subagent -> main). A remount re-runs the component // body, so any signal created inside the component would reset to its // seed. The controller lives in the plugin closure (process lifetime) // and owns the durable prefs/collapse signals plus the single shared // watcher subscription, so collapse and live pref reloads survive the // remount. No effects or memos here — those need a Solid owner, so the // poll-interval createEffect stays inside the component. function createSidebarController(initialPrefs) { const [prefs, setPrefs] = createSignal(initialPrefs); const seedCollapsed = initialPrefs.rememberCollapsed && initialPrefs.collapsed != null ? initialPrefs.collapsed : initialPrefs.startCollapsed; const [collapsed, setCollapsed] = createSignal(seedCollapsed); let lastPersistedCollapsed = initialPrefs.collapsed; let lastApplied = JSON.stringify(initialPrefs); // The watcher lives for the plugin/process lifetime — it is intentionally // never disposed. Collapse guard mirrors the race-fix in toggleCollapsed: // lastPersistedCollapsed is advanced only once our own write lands, so // watcher echoes of the previous persisted value are rejected by the // `!==` check and cannot revert a user click. watchTuiPreferences(() => { void (async () => { const next = resolveAnthropicAuthPrefs(await readTuiPreferencesFile()); const serialized = JSON.stringify(next); if (serialized === lastApplied) return; lastApplied = serialized; setPrefs(next); if (next.rememberCollapsed && next.collapsed != null && next.collapsed !== lastPersistedCollapsed) { lastPersistedCollapsed = next.collapsed; setCollapsed(next.collapsed); } })(); }); function toggleCollapsed() { const next = !collapsed(); setCollapsed(next); if (prefs().rememberCollapsed) { void queueTuiPreferenceUpdate(PLUGIN_KEY, ['collapsed'], next).then(() => { lastPersistedCollapsed = next; }); } } return { prefs, collapsed, toggleCollapsed }; } function QuotaSidebar(props) { const prefs = props.controller.prefs; const collapsed = props.controller.collapsed; const [state, setState] = createSignal(DEFAULT_SIDEBAR_STATE); let lastUpdated = 0; let debounce = null; async function refresh() { const next = await readStateFromFile(); if (next.lastUpdated !== lastUpdated) { lastUpdated = next.lastUpdated; setState(next); } } function scheduleRefresh() { if (debounce) clearTimeout(debounce); debounce = setTimeout(() => { debounce = null; void refresh(); }, prefs().refreshDebounceMs); } // Background poller (server and TUI run as separate module instances, so we // sync through the state file) plus event-driven refreshes for low latency. // The interval rebuilds whenever pollMs changes. createEffect(() => { const timer = setInterval(refresh, prefs().pollMs); onCleanup(() => clearInterval(timer)); }); const unsubs = [props.api.event.on('session.updated', scheduleRefresh), props.api.event.on('message.updated', scheduleRefresh)]; setTimeout(refresh, 300); onCleanup(() => { if (debounce) clearTimeout(debounce); for (const u of unsubs) u(); }); const theme = () => props.api.theme.current; const enabledFallbacks = () => (state().fallbacks ?? []).filter(f => f.enabled); const hasData = () => state().main?.quota != null || enabledFallbacks().length > 0; const headerLabel = () => { const name = prefs().header.label; return !hasData() ? name : collapsed() ? `\u25b6 ${name}` : `\u25bc ${name}`; }; const activeAccount = () => resolveActiveAccount(state()); const activeQuotaSummary = () => getCollapsedQuotaSummary(activeAccount().quota); const activePacingDeficit = () => { if (!prefs().sections.pacing) return false; const quota = activeAccount().quota; if (!quota) return false; const now = Date.now(); const windows = [[quota.five_hour, FIVE_HOUR_MS], [quota.seven_day, SEVEN_DAY_MS], ...(quota.scoped ?? []).map(window => [window, SEVEN_DAY_MS])]; return windows.some(([w, ms]) => w != null && computeQuotaPacing(w, ms, now)?.state === 'deficit'); }; const activeQuotaTone = () => { const summary = activeQuotaSummary(); const values = [summary.fiveHourUsedPercent, summary.sevenDayUsedPercent, ...summary.scopedUsedPercents].filter(value => value != null); // Pacing deficit is an advisory projection, not actual quota exhaustion, // so it can only BUMP the usage tone up to warn at most — never soften a // real warn/err usage reading and never paint a true red. const base = values.length > 0 ? usageTone(Math.max(...values), prefs().appearance) : 'muted'; if (!activePacingDeficit()) return base; return base === 'ok' || base === 'muted' ? 'warn' : base; }; const quotaBackedOff = () => state().main?.quotaBackedOff === true; const refreshBackedOff = () => state().main?.refreshBackedOff === true; const needsReauth = () => enabledFallbacks().some(f => f.needsReauth); const degraded = () => quotaBackedOff() || refreshBackedOff() || needsReauth(); const fableRecoverySummary = () => getFableRecoverySummary(state(), props.sessionId); const fableRecoveryTone = () => state().fableRecoveries?.find(recovery => recovery.sessionId === props.sessionId)?.mode === 'opus' ? 'warn' : 'ok'; const cacheKeep = () => state().cacheKeep; const showCache = () => prefs().sections.cache && cacheKeep() != null && cacheKeep()?.window != null; const relayValue = () => { const r = state().relay; if (!r) return '\u2014'; return `${r.transport} \u00b7 ${r.enabled ? 'on' : 'off'}`; }; return (() => { var _el$39 = _$createElement("box"), _el$40 = _$createElement("box"), _el$41 = _$createElement("box"), _el$42 = _$createElement("text"), _el$43 = _$createElement("b"); _$insertNode(_el$39, _el$40); _$setProp(_el$39, "width", '100%'); _$setProp(_el$39, "flexDirection", 'column'); _$setProp(_el$39, "border", SINGLE_BORDER); _$setProp(_el$39, "paddingTop", 1); _$setProp(_el$39, "paddingBottom", 1); _$setProp(_el$39, "paddingLeft", 1); _$setProp(_el$39, "paddingRight", 1); _$insertNode(_el$40, _el$41); _$setProp(_el$40, "width", '100%'); _$setProp(_el$40, "flexDirection", 'row'); _$setProp(_el$40, "justifyContent", 'space-between'); _$setProp(_el$40, "alignItems", 'center'); _$setProp(_el$40, "onMouseDown", () => { if (hasData()) props.controller.toggleCollapsed(); }); _$insertNode(_el$41, _el$42); _$setProp(_el$41, "paddingLeft", 1); _$setProp(_el$41, "paddingRight", 1); _$insertNode(_el$42, _el$43); _$insert(_el$43, headerLabel); _$insert(_el$40, _$createComponent(Show, { get when() { return degraded(); }, get fallback() { return _$createComponent(Show, { get when() { return prefs().header.showVersion && PLUGIN_VERSION !== ''; }, get children() { var _el$53 = _$createElement("text"); _$insert(_el$53, `v${PLUGIN_VERSION}`); _$effect(_$p => _$setProp(_el$53, "fg", theme().textMuted, _$p)); return _el$53; } }); }, get children() { var _el$44 = _$createElement("box"), _el$45 = _$createElement("text"), _el$46 = _$createElement("b"); _$insertNode(_el$44, _el$45); _$setProp(_el$44, "paddingLeft", 1); _$setProp(_el$44, "paddingRight", 1); _$insertNode(_el$45, _el$46); _$insertNode(_el$46, _$createTextNode(`LIMITED`)); _$effect(_p$ => { var _v$12 = theme().warning, _v$13 = theme().background; _v$12 !== _p$.e && (_p$.e = _$setProp(_el$44, "backgroundColor", _v$12, _p$.e)); _v$13 !== _p$.t && (_p$.t = _$setProp(_el$45, "fg", _v$13, _p$.t)); return _p$; }, { e: undefined, t: undefined }); return _el$44; } }), null); _$insert(_el$39, _$createComponent(Show, { get when() { return _$memo(() => !!collapsed())() && hasData(); }, get children() { return [_$createComponent(CollapsedRow, { get theme() { return theme(); }, get label() { return activeAccount().name; }, get children() { return _$createComponent(Show, { get when() { return activeQuotaSummary().text != null; }, get fallback() { return (() => { var _el$54 = _$createElement("text"); _$insertNode(_el$54, _$createTextNode(`—`)); _$effect(_$p => _$setProp(_el$54, "fg", theme().textMuted, _$p)); return _el$54; })(); }, get children() { var _el$48 = _$createElement("text"), _el$49 = _$createElement("b"); _$insertNode(_el$48, _el$49); _$insert(_el$49, () => activeQuotaSummary().text); _$effect(_$p => _$setProp(_el$48, "fg", toneColor(theme(), activeQuotaTone()), _$p)); return _el$48; } }); } }), _$createComponent(Show, { get when() { return state().fastMode; }, get children() { return _$createComponent(CollapsedRow, { get theme() { return theme(); }, label: "Mode", get children() { var _el$50 = _$createElement("text"), _el$51 = _$createElement("b"); _$insertNode(_el$50, _el$51); _$insertNode(_el$51, _$createTextNode(`fast`)); _$effect(_$p => _$setProp(_el$50, "fg", toneColor(theme(), 'accent'), _$p)); return _el$50; } }); } }), _$createComponent(Show, { get when() { return fableRecoverySummary(); }, children: summary => _$createComponent(CollapsedRow, { get theme() { return theme(); }, label: "Recovery", get children() { var _el$56 = _$createElement("text"), _el$57 = _$createElement("b"); _$insertNode(_el$56, _el$57); _$insert(_el$57, summary); _$effect(_$p => _$setProp(_el$56, "fg", toneColor(theme(), fableRecoveryTone()), _$p)); return _el$56; } }) })]; } }), null); _$insert(_el$39, _$createComponent(Show, { get when() { return !collapsed() || !hasData(); }, get children() { return [_$createComponent(Show, { get when() { return hasData(); }, get fallback() { return (() => { var _el$58 = _$createElement("box"), _el$59 = _$createElement("text"); _$insertNode(_el$58, _el$59); _$setProp(_el$58, "marginTop", 1); _$setProp(_el$58, "width", '100%'); _$insertNode(_el$59, _$createTextNode(`Waiting for quota…`)); _$effect(_$p => _$setProp(_el$59, "fg", theme().textMuted, _$p)); return _el$58; })(); }, get children() { return _$createComponent(Show, { get when() { return prefs().sections.quota; }, get children() { return [_$createComponent(SectionHeader, { get theme() { return theme(); }, title: "Quota" }), _$createComponent(AccountBlock, { get theme() { return theme(); }, get appearance() { return prefs().appearance; }, name: "main", get quota() { return state().main?.quota; }, get active() { return state().activeId === 'main'; }, get pacingEnabled() { return prefs().sections.pacing; }, get tierLabel() { return state().main?.tierLabel; } }), _$createComponent(Show, { get when() { return prefs().sections.fallbackAccounts; }, get children() { return _$createComponent(For, { get each() { return enabledFallbacks(); }, children: fb => _$createComponent(AccountBlock, { get theme() { return theme(); }, get appearance() { return prefs().appearance; }, get name() { return fb.label ?? fb.id; }, get quota() { return fb.quota; }, get active() { return state().activeId === fb.id; }, get pacingEnabled() { return prefs().sections.pacing; }, get tierLabel() { return fb.tierLabel; }, get needsReauth() { return fb.needsReauth; }, marginTop: 1 }) }); } })]; } }); } }), _$createComponent(Show, { get when() { return prefs().sections.routing; }, get children() { return [_$createComponent(SectionHeader, { get theme() { return theme(); }, title: "Routing" }), _$createComponent(StatRow, { get theme() { return theme(); }, label: "Route", get value() { return state().route; }, tone: "accent" }), _$createComponent(StatRow, { get theme() { return theme(); }, label: "Mode", get value() { return state().fastMode ? 'fast' : 'std'; }, get tone() { return state().fastMode ? 'accent' : 'muted'; } }), _$createComponent(StatRow, { get theme() { return theme(); }, label: "Relay", get value() { return relayValue(); }, get tone() { return state().relay?.enabled ? 'ok' : 'muted'; } })]; } }), _$createComponent(Show, { get when() { return fableRecoverySummary(); }, children: summary => _$createComponent(StatRow, { get theme() { return theme(); }, label: "Recovery", get value() { return summary(); }, get tone() { return fableRecoveryTone(); } }) }), _$createComponent(Show, { get when() { return showCache(); }, get children() { return [_$createComponent(SectionHeader, { get theme() { return theme(); }, title: "Cache" }), _$createComponent(StatRow, { get theme() { return theme(); }, label: "1h cache", get value() { return `${cacheKeep()?.window} \u00b7 ${cacheKeep()?.enabled ? 'on' : 'off'}`; }, get tone() { return cacheKeep()?.enabled ? 'ok' : 'muted'; } }), _$createComponent(Show, { get when() { return (cacheKeep()?.trackedSessions ?? 0) > 0; }, get children() { return _$createComponent(StatRow, { get theme() { return theme(); }, label: "Tracked", get value() { return String(cacheKeep()?.trackedSessions); }, tone: "text" }); } })]; } }), _$createComponent(Show, { get when() { return _$memo(() => !!degraded())() && prefs().sections.health; }, get children() { return [_$createComponent(SectionHeader, { get theme() { return theme(); }, title: "Health" }), _$createComponent(Show, { get when() { return quotaBackedOff(); }, get children() { return _$createComponent(StatRow, { get theme() { return theme(); }, label: "Quota API", get value() { return `backoff ${formatUntil(state().main?.quotaBackoffUntil)}`; }, tone: "warn" }); } }), _$createComponent(Show, { get when() { return refreshBackedOff(); }, get children() { return _$createComponent(StatRow, { get theme() { return theme(); }, label: "Token refresh", get value() { return `backoff ${formatUntil(state().main?.refreshBackoffUntil)}`; }, tone: "warn" }); } })]; } })]; } }), null); _$effect(_p$ => { var _v$14 = theme().borderActive, _v$15 = theme().accent, _v$16 = theme().background; _v$14 !== _p$.e && (_p$.e = _$setProp(_el$39, "borderColor", _v$14, _p$.e)); _v$15 !== _p$.t && (_p$.t = _$setProp(_el$41, "backgroundColor", _v$15, _p$.t)); _v$16 !== _p$.a && (_p$.a = _$setProp(_el$42, "fg", _v$16, _p$.a)); return _p$; }, { e: undefined, t: undefined, a: undefined }); return _el$39; })(); } const tui = async api => { const root = await readTuiPreferencesFile(); const controller = createSidebarController(resolveAnthropicAuthPrefs(root)); api.slots.register({ order: computeEffectiveOrder(root, PLUGIN_KEY, DEFAULT_SLOT_ORDER), slots: { sidebar_content(_ctx, props) { return _$createComponent(QuotaSidebar, { api: api, controller: controller, get sessionId() { return props.session_id; } }); } } }); if (!rpcPollStarted) { rpcPollStarted = true; const rpcClient = createRpcClient(getRpcDir(api.state.path.directory ?? ''), process.pid); let lastNotificationId = 0; let rpcInFlight = false; setInterval(() => { if (rpcInFlight) return; const current = api.route.current; const resolved = typeof current === 'function' ? current() : current; const sessionId = resolved?.params?.sessionID; rpcInFlight = true; void rpcClient.pending(lastNotificationId, sessionId).then(messages => { for (const message of [...messages].sort((a, b) => a.id - b.id)) { lastNotificationId = Math.max(lastNotificationId, message.id); if (message.payload.command === 'claude-quota') { api.ui.dialog.setSize('xlarge'); api.ui.dialog.replace(() => _$createComponent(QuotaDialogContent, { api: api, controller: controller })); continue; } openCommandDialog(api, message.payload, (command, args) => rpcClient.apply({ command, arguments: args, sessionId })); } }).catch(() => {}).finally(() => { rpcInFlight = false; }); }, RPC_POLL_MS); } }; const plugin = { id: ID, tui }; export default plugin;