From 53eb65bcac1834fad02d49fcbef75ac6cc7df6c8 Mon Sep 17 00:00:00 2001 From: "prompt.ac/@jeffrey" Date: Mon, 28 Sep 2026 13:41:56 -0700 Subject: [PATCH] Aesel: the account's braincells at the bottom right MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After this thread's tokens and price, the status line shows the account's braincells — today's free allowance left plus bought — read from /api/easel-credits at start, on sign-in and after every turn. Every session shows the same number, so it says where the whole account stands. Co-Authored-By: Claude Opus 5.5 --- aesel/src/render.mjs | 21 +++++++++++++-------- aesel/src/tui.mjs | 21 +++++++++++++++++++++ 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/aesel/src/render.mjs b/aesel/src/render.mjs index 03a2b12696..a1b0a07c95 100644 --- a/aesel/src/render.mjs +++ b/aesel/src/render.mjs @@ -1143,7 +1143,7 @@ export function proStatus(state, width, useColor, shape = state.layout || {}) { // the machine is doing is the last thing to be cut. const keep = new Set(status); // The thread's cost holds its corner; the place and the engine give way first. - const spend = spendText(state.spend); + const spend = spendText(state.spend, state.braincells); const reserve = spend ? textWidth(spend) + 3 : 0; const measure = () => [...keep].reduce((n, name) => (plain[name] ? n + textWidth(plain[name]) + (n ? textWidth(separator) : 0) : n), 1) + reserve; for (const name of ["engine", "media", "workspace", "model", "handle", "mode", "inbox"]) { @@ -1178,13 +1178,18 @@ export function proStatus(state, width, useColor, shape = state.layout || {}) { return { line, spans }; } -function spendText(spend) { - if (!spend?.tokens) return ""; - const n = spend.tokens; - const tokens = n >= 1e6 ? `${(n / 1e6).toFixed(1)}M` : n >= 1e3 ? `${(n / 1e3).toFixed(1)}k` : `${n}`; - if (!spend.billed) return `${tokens} tok`; - const usd = spend.usd; - return `${tokens} tok · $${usd < 0.01 ? usd.toFixed(4) : usd < 1 ? usd.toFixed(3) : usd.toFixed(2)}`; +const compact = (n) => n >= 1e6 ? `${(n / 1e6).toFixed(1)}M` : n >= 1e3 ? `${(n / 1e3).toFixed(1)}k` : `${n}`; +// This thread's tokens and, when billed, its dollars; then the account's +// braincells — free today plus bought — which every session shares, so it +// is the one number that says where the whole account stands. +function spendText(spend, braincells) { + const parts = []; + if (spend?.tokens) { + const usd = spend.usd; + parts.push(spend.billed ? `${compact(spend.tokens)} tok · $${usd < 0.01 ? usd.toFixed(4) : usd < 1 ? usd.toFixed(3) : usd.toFixed(2)}` : `${compact(spend.tokens)} tok`); + } + if (Number.isFinite(braincells)) parts.push(`${compact(Math.max(0, Math.round(braincells)))} braincells`); + return parts.join(" · "); } export function transcriptLineCount(state, columns = 80, rows = 24, useColor = true) { diff --git a/aesel/src/tui.mjs b/aesel/src/tui.mjs index d68845ecb2..8862d27307 100755 --- a/aesel/src/tui.mjs +++ b/aesel/src/tui.mjs @@ -306,6 +306,8 @@ const state = { // Tokens and dollars on this thread. The dollars are what the provider // billed (OpenRouter reports it per response), never an estimate. spend: { thread: "", tokens: 0, usd: 0, billed: false }, + // The account's braincells (free today + bought), shared by every session. + braincells: null, qr: null, // The prompt rock in the menu bar draws this session's code at real pixel // resolution, so the transcript does not spend seventeen rows on a worse @@ -1131,6 +1133,22 @@ end repeat`; spawn("/bin/sh", ["-c", `sleep 0.7; /usr/bin/osascript -e '${script.replace(/'/g, "'\\''")}'`], { detached: true, stdio: "ignore" }).unref(); } +// Read the account's braincells: what is left of today's free allowance plus +// what was bought. Quietly keeps the last number when the site can't be reached. +let braincellsAt = 0; +async function refreshBraincells() { + if (!session.signedIn || Date.now() - braincellsAt < 3000) return; + braincellsAt = Date.now(); + try { + const token = await session.token(); + const response = await fetch(`${SITE}/api/easel-credits`, { headers: { Authorization: `Bearer ${token}`, "User-Agent": USER_AGENT }, signal: AbortSignal.timeout(6000) }); + if (!response.ok) return; + const body = await response.json(); + const total = (Number(body.remaining) || 0) + (Number(body.purchased) || 0); + if (Number.isFinite(total)) { state.braincells = total; redraw(); } + } catch {} +} + async function finish(code = 0) { if (closing || finishing) return; finishing = true; @@ -1501,6 +1519,7 @@ function handleNotification({ method, params = {} }) { break; } case "turn/completed": { + void refreshBraincells(); // Asked to close out: let the goodbye land, then shut down. if (closeAfterTurn) setTimeout(() => void closeOut(), 900); // Codex reports what it spent on the turn that closes rather than in a @@ -2867,6 +2886,7 @@ process.on("SIGHUP", () => finish(129)); // A sign-in or sign-out anywhere in the AC suite shows up here live. session.watch().on("change", () => { + braincellsAt = 0; void refreshBraincells(); if (!session.signedIn || !session.handle) { void finish(); return; } refreshAccount(true); redraw(); @@ -3030,6 +3050,7 @@ try { replaceInput(initialPrompt); await submitInput(); } + void refreshBraincells(); // Inbox lines that arrived before the bridge was up have been waiting. drainQueue(); } catch (error) { -- 2.51.2