rich-claude-code-tui-clone — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited rich-claude-code-tui-clone (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Build a Python terminal interface that recreates the interaction grammar of a modern coding agent: conversation-first layout, streaming responses, compact tool summaries, permission gates, rich diffs, transcript navigation, and restrained motion.
Do not treat Rich as a drop-in replacement for Ink. Rich is the rendering layer. Input editing, keyboard dispatch, viewport state, search, and modal ownership must be implemented by the application or delegated to an optional widget framework.
Activate when:
For a generic TypeScript/OpenRouter scaffold, use create-agent-tui instead. This skill is specifically for the Python/Rich implementation path.
Use:
Do not copy proprietary product code, trademarks, or mascot art. Recreate interaction patterns and visual hierarchy. Use project-owned names and artwork unless the user has rights to branded assets. Toad is AGPL-licensed: study its architecture, but do not copy implementation code into an incompatibly licensed project.
| Level | Use when | Core approach |
|---|---|---|
scrollback | Fast CLI MVP, terminal history matters most | Console.print() for completed blocks and a transient Live spinner |
rich-screen | Full-screen conversation UI with controlled regions | Layout + Live(screen=True) + application-owned state and input loop |
textual-app | Claude Code-level prompt editor, mouse events, modal overlays, fuzzy pickers, or virtualized widgets | Textual application with Rich renderables inside widgets |
Start at the lowest level that meets the acceptance criteria. A full Claude Code visual clone normally ends at textual-app; a convincing first milestone can remain rich-screen.
Read references/rich-api-map.md before selecting a level.
Use one-way state flow:
agent events + terminal input + timers
|
v
reduce(state, event)
|
v
immutable AppState
|
v
render_app(state)
|
v
Rich renderable treeKeep transport, state transitions, and rendering separate. Never let an agent callback write directly to stdout while a Live display owns the terminal.
Recommended project shape:
src/
app.py
state.py
theme.py
runtime/
agent_events.py
input.py
terminal.py
transcript.py
renderables/
logo.py
messages.py
tools.py
diff.py
prompt.py
footer.py
dialogs.py
tests/
test_render_snapshots.py
test_reducer.py
test_transcript.pyFor the component map and state model, read references/claude-code-parity-roadmap.md.
Record the requested fidelity before coding:
## TUI Parity Contract
- Fidelity level: scrollback | rich-screen | textual-app
- Required modes: prompt | transcript | fullscreen
- Required message types:
- Required tool renderers:
- Prompt features: multiline | history | slash completion | vim | paste
- Permission flows:
- Diff requirements: unified | split | word-level
- Theme targets: dark | light | daltonized | ANSI
- Motion policy: standard | reduced
- Terminal targets: Linux | macOS | Windows Terminal | WSL
- Brand assets authorized: yes | noUse a custom Theme, then implement renderables in this order:
Use Text, Panel, Group, Table.grid, Rule, Markdown, and Syntax before creating custom renderables. Implement __rich_console__ only when composition is no longer enough.
For rich-screen, keep one Live owner:
with Live(
render_app(state),
console=console,
screen=True,
refresh_per_second=20,
vertical_overflow="crop",
) as live:
while state.running:
event = await event_queue.get()
state = reduce(state, event)
live.update(render_app(state))Use auto_refresh=False and live.update(..., refresh=True) when deterministic repaint timing matters. Use a transient non-screen Live display for the scrollback MVP.
Do not re-render the entire transcript for every token:
Rich Markdown is sufficient for complete blocks. A production streaming renderer may need a stable prefix plus an active suffix, especially for open code fences and long responses.
Rich does not provide a multiline prompt editor, fuzzy completion, Vim mode, cursor editing, mouse selection, or modal focus management.
Choose one:
asyncio plus a small raw-terminal input adapterDo not fake widget behavior with scattered ANSI writes inside render functions.
For transcript export, use a recording console:
recording = Console(record=True, width=width)
recording.print(render_transcript(state))
plain_text = recording.export_text(clear=True, styles=False)For alternate-screen rendering, use Live(screen=True) or Console.screen(). Keep transcript search, viewport slicing, sticky prompts, and unseen-message counters in application state.
Snapshot renderables at 60, 100, and 140 columns. Test:
Read references/input-fullscreen-testing.md for the detailed test matrix.
A finished implementation:
a full input widget system.
screen=True only when the application should own the viewport.
events.
Layout does not virtualize a long transcript.suffix.
ship ASCII fallbacks.
a project with incompatible distribution terms.
Created: 2026-06-02 Version: 1.0.0
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.