feat(term): full-screen terminal console for agents, sessions and cost - #236
Open
Ar9av wants to merge 2 commits into
Open
feat(term): full-screen terminal console for agents, sessions and cost#236Ar9av wants to merge 2 commits into
Ar9av wants to merge 2 commits into
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.
Keys —
j/kmove ·→/←expand/collapse ·Tabswitch pane ·[/]page ·Enterevent detail ·ssort (last run / risk / findings / cost) ·ffollow ·vverdict filter ·ppolicy ·Ppause/resume session immunity ·Rresume session in Claude Code ·rrefresh ·qquit.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.pyjoins two outside sources:aipricing.guru/api/pricing.json, cached to~/.prismor/pricing-cache.json(12h TTL, 6s timeout, serves stale when offline, reportsprices n/awhen there is no cache at all).message.usageper turn and are keyed by the same session ids Prismor already records.Deliberate honesty in the numbers:
—, never$0.00, and sort last under cost order. Sessions not yet priced render·.est.Responsiveness
Measured rather than assumed:
get_aggregate_stats(~900ms cold) is off the startup and draw paths entirely — the KPI panel paints instantly showingcomputing 24h totals…and fills in on idle (30s TTL).get_events_pagescales its scan withpage x limit, so a screenful is 4-5x cheaper.Paging honesty
get_events_pagederivestotal/pagesfrom 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 showspage 3 · 23 of 207+,]stops at a genuinely short page, andGdoes not jump to a fabricated last page. Session-scoped events are sliced locally from a complete list and do show exactpage 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.
paused=True→ resumed →paused=False, left in its original state.def_prog_mode()/endwin(), claude renders correctly, andreset_prog_mode()plus a repaint restores the tree. It refuses with a reason for non-Claude frameworks, a missingclaudebinary, or a deleted workspace.@@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
cursesandurllib.