Skip to content

feat(term): full-screen terminal console for agents, sessions and cost - #236

Open
Ar9av wants to merge 2 commits into
mainfrom
feat/term-console
Open

feat(term): full-screen terminal console for agents, sessions and cost#236
Ar9av wants to merge 2 commits into
mainfrom
feat/term-console

Conversation

@Ar9av

@Ar9av Ar9av commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Adds prismor term — a curses console over the data Prismor already collects.

What it is

A two-level tree of agents → their sessions, with a live event tail scoped to whatever is selected, plus policy precedence and per-session token cost.

 >_ PRISMOR  TERM  │ Org: …  │ Policy: default  │ Agents: 35  │ prices live  │ FOLLOW
 Agents ▸ sessions  ↓last run   │  Detail
   All agents                   │  Session   d981011d-6f54-…
 ▾ ○ claude            512/229  │  Risk      70/100 · 1 findings
     6a5d59a6-e  r90 2f   — 1d  │  Immunity  active
     d981011d-6  r70 1f $426 2d │  Cost      $426.00 est · 1629 turns · claude-opus-5
 ▸ ○ codex              64/32   │  Tokens    in 3.1k · out 1.4M · cache r687.6M/w7.4M

Keysj/k move · →/← expand/collapse · Tab switch pane · [ / ] page · Enter event detail · s sort (last run / risk / findings / cost) · f follow · v verdict filter · p policy · P pause/resume session immunity · R resume session in Claude Code · r refresh · q quit.

It is a renderer only: every number comes from prismor.runtime.store, which already fans out across workspace DBs and re-cloaks secrets on read. No new SQL. With no tty it prints a plain table, so it stays scriptable.

Cost

The event store records what an agent did but never how many tokens it took — there is no usage column and no usage payload in any event. So prismor/runtime/cost.py joins two outside sources:

  • Prices — the published feed at aipricing.guru/api/pricing.json, cached to ~/.prismor/pricing-cache.json (12h TTL, 6s timeout, serves stale when offline, reports prices n/a when there is no cache at all).
  • Usage — Claude Code's own transcripts, which carry message.usage per turn and are keyed by the same session ids Prismor already records.

Deliberate honesty in the numbers:

  • Sessions with no transcript render , never $0.00, and sort last under cost order. Sessions not yet priced render ·.
  • Agent totals say "across N of M loaded sessions" rather than implying full history.
  • The feed publishes no cache-write rate, so that component applies Anthropic's standard 1.25x input multiplier. That is the 5-minute-TTL figure; 1-hour-TTL caching bills 2x, so totals slightly understate and are labelled est.
  • These are list-price API costs — on a subscription the real marginal cost differs.

Responsiveness

Measured rather than assumed:

before after
time to first paint 0.93s 0.11s
events per keypress 55ms 11ms
base fetch 51ms 0.7ms
  • get_aggregate_stats (~900ms cold) is off the startup and draw paths entirely — the KPI panel paints instantly showing computing 24h totals… and fills in on idle (30s TTL).
  • Events paginate to the visible row count instead of a fixed 200; get_events_page scales its scan with page x limit, so a screenful is 4-5x cheaper.
  • Navigation is debounced 120ms — 40 keypresses with zero gap settle in 1.2s with no queued queries.
  • Session pricing runs 6-per-idle-tick; the screen repaints only when something changed (it was redrawing ~16x/sec while idle).

Paging honesty

get_events_page derives total/pages from an internal window that grows with page depth (200 → 207 → 276 → 621 as you page deeper). Those are a floor, not a count, so the header shows page 3 · 23 of 207+, ] stops at a genuinely short page, and G does not jump to a fabricated last page. Session-scoped events are sliced locally from a complete list and do show exact page 1/3 of 60.

Testing

Driven through a real pty at 120x34 against live local data (35 agents, 685 sessions, 61k events): tree expand/collapse, event tail rescoping, event detail, policy overlay, verdict filter, all four sorts, paging, key-flood, idle CPU (1.9%), and the non-tty fallback.

  • Pause/resume was round-tripped on a dormant session — paused → on-disk paused=True → resumed → paused=False, left in its original state.
  • Claude Code resume was verified end to end: curses releases the tty via def_prog_mode() / endwin(), claude renders correctly, and reset_prog_mode() plus a repaint restores the tree. It refuses with a reason for non-Claude frameworks, a missing claude binary, or a deleted workspace.
  • Secrets stay cloaked throughout — evidence panes show the @@SECRET:...@@ placeholder form.

One gap worth naming: I could not script Claude Code's exit through the test pty, so restore-after-real-claude is proven with a stub binary rather than with claude itself. Worth one manual check.

No new dependencies — stdlib curses and urllib.

Ar9av added 2 commits July 31, 2026 22:14
Adds `prismor term`, a curses console over the existing store: an
agent -> session tree, a live event tail scoped to the selection,
policy precedence, and per-session token cost.

Renderer only — every number comes from prismor.runtime.store, so
secrets stay cloaked and no new SQL is introduced. Degrades to a plain
table when stdout isn't a tty, so it stays scriptable.

Cost (prismor/runtime/cost.py) joins two sources, because the event
store records what an agent did but never how many tokens it took:
live prices from aipricing.guru's published feed (cached to
~/.prismor/pricing-cache.json, 12h TTL, serves stale when offline) and
usage from Claude Code's own transcripts, which key on the same session
ids Prismor already records. Sessions without a transcript render as
unknown, never $0.00. The feed publishes no cache-write rate, so that
component uses Anthropic's standard 1.25x input multiplier and totals
are labelled estimates.

Kept responsive under measurement, not assumption:
  - get_aggregate_stats (~900ms) is off the startup and draw paths
    entirely; the KPI panel paints immediately and fills in on idle
  - events paginate to the visible row count rather than a fixed 200
    (55ms -> 11ms per keypress)
  - navigation is debounced 120ms, so holding j/k queues no queries
  - session pricing runs in small batches on idle
  - the screen repaints only when something changed
Time to first paint 0.93s -> 0.11s; 40 keys with no gap settle in 1.2s.

get_events_page derives total/pages from a window that grows with page
depth, so those numbers are reported as a floor ("page 3 of 207+")
rather than presented as an exact count. Session-scoped events are
sliced locally and do show exact pages.
Full page for `prismor term`: layout, the complete key map, how session
loading and its 50-per-agent limit behave, pausing a session, resuming one
in Claude Code, and how cost is derived.

Documents the caveats the UI already marks rather than leaving them to be
discovered: cost exists only for agents that keep transcripts (unknown is
rendered as "—", never $0.00), agent totals cover loaded sessions only,
cache-write uses the 1.25x multiplier because the price feed omits that
rate, and the figures are list-price API costs rather than a bill. Also
records why event counts read "207+" for agent scope but are exact for
session scope.

Linked from the CLI reference, the dashboard doc (same underlying store),
and the README doc list.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant