Skip to content

Repository files navigation

agentparts

Terminal UI components for agent harnesses.

Chat components exist. Harness chrome does not. agentparts is the second kind: the status row that tells you the agent is alive and what it is costing you, the approval prompt that gates a click on your real desktop, the home screen that shows which model and which screen you are about to point at something. Those are the parts that make a harness feel like a harness, and every project currently rebuilds them.

Status: early but complete for v0.1. Eleven components.

The demo mid-run: transcript, thinking block, tool calls, an approval prompt gating a click, the turn status row, and the prompt box

Why it is split in two

Package What it is Runtime
@agentparts/core Models and layout arithmetic, no renderer node and bun
@agentparts/opentui The components, for OpenTUI + React bun only

The split is not decoration. A Python prototype of this interface shipped four UI bugs, and all four were the same kind: a border one cell short, a status row one cell too wide, a dropdown reserving eight rows for two matches, a prompt box three rows taller than its contents. None were bugs about what to show.

So core owns everything that can be wrong by one cell, and proves it with plain unit tests that need no terminal. opentui maps the result to coloured text and hangs event handlers on it. @opentui/react requires bun; core deliberately does not, so an Ink adapter or a plain node CLI stays possible.

Install

Not on npm yet. Until the packages are published, install from a checkout:

git clone https://github.com/arthurkatcher/agentparts
cd agentparts
bun install

Your app can then live in the workspace and depend on @agentparts/opentui (and @agentparts/core) with "workspace:*", or copy packages/ into your own repo; the code is MIT and laid out to be vendored. npm publishing is planned, and the packages are already shaped for it.

Use

import { createCliRenderer } from "@opentui/core"
import { createRoot } from "@opentui/react"
import { ThemeProvider, TurnStatus } from "@agentparts/opentui"

function App() {
  return (
    <ThemeProvider>
      <TurnStatus
        width={process.stdout.columns}
        activity="click(640, 400)"
        phase="acting"
        phaseStartedAt={startedThisPhase}
        turnStartedAt={startedThisTurn}
        tokens={12437}
        tick={frame}
        onStop={() => session.stop()}
      />
    </ThemeProvider>
  )
}

const renderer = await createCliRenderer()
createRoot(renderer).render(<App />)

Renders, at 80 columns:

⠋ click(640, 400) 1m20s                          1m20s ⇣12.4k [stop]

Components take props, never events. A TurnStatus is handed an activity string and two timestamps, not a RunStartEvent, because that is what lets the same component serve a Python harness, a TypeScript one, and a test. Adapters from a specific event stream are a separate, optional thing.

Theming

Every component reads a Theme from context: a nine-colour palette, a border style, a glyph set, and a density. Restyling never means forking behavior.

import { ThemeProvider, extendTheme, defaultTheme, monoTheme } from "@agentparts/opentui"

const brand = extendTheme(defaultTheme, { palette: { accent: "#ff6b35" } })

<ThemeProvider theme={brand}></ThemeProvider>

monoTheme drops every colour and swaps to ascii glyphs. It is not a second colour scheme: a terminal without colour should look plainly unstyled rather than like a theme that failed to load. Layout is identical either way, which is why themeForEnv(process.env) can honour NO_COLOR safely.

Components

Component What it is
HarnessShell Four bottom-anchored regions: main, interrupt, status, prompt
Transcript Sticky scrollback over structured entries
UserMessage What the human said, with a coloured gutter
AssistantMessage What the agent said, plus a streaming cursor
ThinkingBlock Reasoning, collapsed by default
ToolCall Marker, call, args, duration, result
ApprovalPrompt "May I do this?", with the safe option first
TurnStatus Cue, activity, both clocks, tokens, stop
PromptInput Bordered input with an in-flow slash-command menu
HomeScreen What you are pointed at, and what you ran last
ShortcutsBar The bottom row, sheddable, with a notice mode

Four of these do not exist anywhere else: TurnStatus, HomeScreen, ShortcutsBar and HarnessShell. They are what make a harness feel like a harness, and they are the reason this is not another chat-component library.

Try it

bun install
bun run demo

The home screen: logo, model and screen configuration, and recent sessions

A fake harness with no model, screen or network. Enter runs a scripted session; partway through, an approval stops everything until you answer. Ctrl+T toggles the monochrome theme. See examples/demo.

› open a terminal and report the kernel version
▾ thought for 2.1s
• The screen is a bare desktop. The usual shortcut is ctrl+alt+t, so try that
  before hunting for a menu.
✓ key(ctrl+alt+t) 0.8s
  no change
╭─ allow this action? ───────────────────────────────────────────────────────╮
│ right_click(640, 400)                                                      │
│ on x11:managed                                                             │
│                                                                            │
│  [y] allow     run this one action                                         │
│  [n] deny      skip it and tell the agent why                              │
│  [a] always    stop asking for this session                                │
╰────────────────────────────────────────────────────────────────────────────╯
◉ waiting on you 0.5s                                        4.0s ⇣4.1k [stop]
╭─ demo-model-v0-1 ──────────────────────────────────────────────────────────╮
│ › send a message                                                           │
╰────────────────────────────────────────────────────────────────────────────╯
enter send   ctrl+c quit   / commands   ctrl+t theme

A finished session: a denied command, a successful tool call, a refused file read, and a rendered edit diff

Development

bun install
bun run check    # build, typecheck, bun tests, node tests

Individually: bun run build, bun run typecheck, bun test, bun run test:node.

Tests come in three kinds, and each catches something the others cannot:

  • Unit tests on core, no renderer. Where width-exactness is proven.
  • Render tests via OpenTUI's testRender, asserting on captureCharFrame() and, for colour-only distinctions, captureSpans(). Where "it fits in 40 columns" is proven against an actual frame buffer rather than against arithmetic that agrees with itself.
  • Snapshots at 40, 80 and 132 columns, committed. CI fails if a build rewrites one, because an unreviewed snapshot records nothing.

The render tests earned their place. Three bugs they caught that unit tests could not: an approval prompt that read its selection from a stale render closure, so arrow-then-enter granted permission the user had not given; an overlay that painted over the transcript without an opaque background, interleaving into open aPERMISSIONaREQUIREDname -a; and a transcript that grew downward from the top of the pane, moving the newest line every turn.

Roadmap

v0.1 is done: eleven components and a demo.

Next, in rough order:

  • the computer-use components no chat-shaped library will build: ScreenPane, ActionOverlay, StepCard
  • an Ink adapter, so node users get the components too. core staying runtime-clean is what keeps that door open
  • shadcn-registry distribution, so presentation can be copied and edited rather than only configured

License

MIT

About

Terminal UI components for agent harnesses: turn status, approval prompts, harness shell. OpenTUI + React, renderer-free core.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages