Repository: @jeffreyjyz/opencode-context — provider-agnostic opencode plugin showing where a session's context window went. Sits beside cmduse, oc-cmd-compare (mpc) and reqshape in ~/dev/cmdcode-tools/; shares no code with them, not Command-Code-specific.
src/index.ts server half (v2 `{ id, setup }`): the `session.context` hook that
captures system-prompt/tool-definition sizes, plus the
`context_breakdown` tool
src/tui.tsx TUI half (`./tui`): the `/context` slash command + dialog
src/breakdown.ts pure: replayable messages -> buckets. The ONE classification point
src/collect.ts TUI collector: session.context + model limit + agent system + capture
src/capture.ts cross-process sizes cache ($XDG_CACHE_HOME/opencode-context/<session>.json)
src/dialog.tsx the dialog JSX (flex layout, stacked bar, rows)
src/format.ts bytes / tokens / percent
src/constants/ module-scope data constants: ids.ts (plugin/command ids),
dialog.ts (palette, column widths, row glyphs, child cap),
tool.ts (context_breakdown tool description)
scripts/preview.tsx headless frame preview: `bun run scripts/preview.tsx [width] [spans]`
scripts/opentui-clip-space-bug.tsx standalone upstream repro (see Traps)
index.js, tui.js root shims for local-directory plugin loading (see Traps)
- One classification point:
breakdown.ts. Both halves feed it same shapes; never classify parts elsewhere, or dialog and tool disagree. - System prompt and tool definitions are not in the message store. Assembled per request in server process. Server half measures them in
session.context, writes sizes only to capture file; TUI dialog reads it. When capture missing (no request yet), System Prompt row falls back toAgentInfo.system, Tool Definitions hidden — never invent those numbers. session.contextis the replayable context, not full history. Post-compaction, exactly what provider would receive. Read viactx.client.session.context({ sessionID })in TUI andctx.session.context({ sessionID })in server so breakdown describes loaded window.- Bytes are UTF-8; tokens are a 4-chars/token estimate. Header's
used / limitis provider's measured number (tokens.input + output + reasoning + cache.read + cache.writeof last assistant turn). Never present per-row estimate as exact. - Two halves share nothing but the filesystem. Run in different processes (server half's state not TUI-visible), so capture is a file, not plugin storage.
- Local-directory plugins resolve
index/server/tuibeside package root, not theexportsmap. Repo ships rootindex.js/tui.jsshims re-exportingdist/; npm installs useexports. Keep shims in sync withdist/. <span fg={…}>silently ignored; spans need<span style={{ fg }}>. Pass colours per segment, not per text node.- TUI app-slot
rendermust return void, notnull—nullleaves an empty box painting a stray line in the prompt area.ctx.keymap.layer()must be called inside a render, so the app slot mounts no-op and registers there. - Custom dialog JSX works:
ctx.ui.dialog.show(() => <X/>)thenctx.ui.dialog.set({ size: "large" }); notalert()-only. - Keymap command has no session prop. Active session from
ctx.ui.router.current()— handleroute.type === "session", else toast. model.listreturns{ location, data }, not a bare array — read.data.Tool.Contextgives toolssessionID(andagent);AgentInfocarriessystem.biome check .aborts on a nested root configuration when.delta/is present; this repo'sbiome.jsonexcludes**/.delta.- Build TUI with
@opentui/solid's transform (scripts/build-tui.ts), not plainbun build: plain JSX evaluates props at element creation and the panel freezes at mount.@opentui/*andsolid-jsstay external because the host rewrites the entry's imports to its own module instances. - Dialog lays out with flexbox, not fixed-width strings. Rows fill
width="100%", label is aflexGrowspacer, size/percent are fixed columns pushed right — apadEnd/padStartblock floats or squishes because the dialog's inner width is not known to render. Bar is a row offlexGrow-weighted boxes withbackgroundColor, so it spans the dialog; a repeated glyph run cannot. captureCharFrame()cannot show the bar's background colour — a plain frame renders blanks. UsecaptureSpans()(sumspan.width, count distinctspan.bg) orbun run scripts/preview.tsx <width> spans;test/dialog-render.test.tsxis the guard.- ASCII space inside flex-row
<text>can be painted/captured as a stray character. Cause is clipping: the glyph appears only when the render frame is shorter than the content, height-parity dependent. Fix is U+00A0 —dialog.tsxrenderslabel.replace(/ /g, "\u00a0"), lays out identically and paints correctly; the render test normalises NBSP back to space. Flat row list / spacer box did not fix it — only NBSP did.testRender's fixed frame clips; live dialog is content-sized and never clips, so NBSP is harmless insurance, not a live fix. Repro:bun run scripts/opentui-clip-space-bug.tsx. Not filed, on purpose —testRender-only artifact.
bun install
bun test # breakdown + plugin-shape tests, hermetic
bun run typecheck
bun run build # server bundle + solid-transformed tui + declarationsdist/index.js must import only node builtins at runtime (no @opencode/*), so a fresh opencode start does not install the provider/effect graph before the plugin appears. @opencode/plugin is devDependency, type-only.
Same policy as sibling repos: local commits only until user says push. Publishing from this account must go through npm stage publish + user's npm stage approve — bare npm publish leaves ghost versions (see the root AGENTS.md for the full trap). Version this package independently. After publish, opencode's per-package install cache can lag npm: npm cache clean, remove ~/.cache/opencode/npm/@jeffreyjyz/opencode-context@latest, then user restarts. Never restart or reload opencode yourself.
npm stage publishfailingE401 "authentication token seems to be invalid"is a dead~/.npmrctoken, not a staging problem. Confirm withnpm whoami(also 401). This account's token is invalidated by npm's bypass-2FA restriction; fix interactively — user runsnpm login(browser), then re-stage. Do not log in from the agent shell, do not paste/log the token (rotate if leaked into transcript).