Add PermissionQuestionWidget API documentation to docs/api/tui.md including method table, key bindings, PermissionDecisionEvent dataclass, and render_permission_question() helper reference. Add corresponding CHANGELOG entry under [Unreleased] Added section. Refs: #997
7.2 KiB
cleveragents.tui — Interactive Terminal UI
The tui package provides the full-screen Textual-based terminal user interface.
It requires the optional cleveragents[tui] extra.
See ADR-044 (TUI framework), ADR-045 (persona system), and ADR-046 (reference and command system) for design rationale.
Launching the TUI
pip install "cleveragents[tui]"
agents tui
First-Run Experience
is_first_run(registry: PersonaRegistry) → bool
Returns True when no personas are configured — used by app.on_mount() to
decide whether to show the actor selection overlay.
create_default_persona_for_actor(registry: PersonaRegistry, actor: str) → None
Creates and persists a "default" persona bound to actor after the user
completes the first-run selection flow.
ActorSelectionOverlay
from cleveragents.tui.widgets import ActorSelectionOverlay
A centred Textual widget displayed on first launch.
| Method | Description |
|---|---|
show() |
Make the overlay visible |
hide() |
Dismiss the overlay |
move_up() |
Move selection cursor up (wraps) |
move_down() |
Move selection cursor down (wraps) |
set_search(query: str) |
Apply fuzzy filter to actor list |
confirm() → str |
Confirm selection; returns actor name and hides overlay |
render_actor_selection() → str |
Pure rendering function (testable without Textual) |
Default actor list (in display order):
| Actor | Notes |
|---|---|
anthropic/claude-4-sonnet |
Recommended |
anthropic/claude-4-opus |
|
openai/gpt-4o |
|
openai/o3 |
|
google/gemini-2 |
Key bindings inside the overlay:
| Key | Action |
|---|---|
j / ↓ |
Move down |
k / ↑ |
Move up |
/ |
Enter fuzzy search |
Enter |
Confirm selection |
Persona System
Personas are YAML files stored in ~/.config/cleveragents/personas/. Each
persona binds an actor, optional argument presets, and scope references to a
named identity.
PersonaRegistry
from cleveragents.tui.persona import PersonaRegistry
registry = PersonaRegistry()
registry.load() # load all YAML files from config dir
persona = registry.get("default") # retrieve by name
registry.save(persona) # persist changes
registry.ensure_default(actor="openai/gpt-4o") # create default if absent
Persona
@dataclass
class Persona:
name: str
actor: str
presets: list[ArgumentPreset]
scope_refs: list[str]
Input Mode Routing
The prompt auto-detects three input modes from the first character:
| First character | Mode | Handler |
|---|---|---|
| (none / letter) | Normal | Message + @reference expansion |
/ |
Command | Slash command overlay |
! |
Shell | Subprocess passthrough |
InputModeRouter
from cleveragents.tui.routing import InputModeRouter
router = InputModeRouter(container)
await router.dispatch(input_text, session_id)
Slash Commands
67 slash commands across 14 groups are exposed via SlashCommandOverlay.
from cleveragents.tui.commands import SLASH_COMMAND_SPECS
# SLASH_COMMAND_SPECS: dict[str, list[SlashCommandSpec]]
# Keys are group names; values are lists of command specs.
Groups: Session, Persona, Scope, Plan, Project, Actor, Resource, Config, Tool, Skill, Invariant, Profile, Context, Utility.
Session commands (via TuiCommandRouter)
| Command | Description |
|---|---|
/session:export [--format json|md] [path] |
Export session to JSON or Markdown |
/session:import <path> |
Import a session from a JSON file |
# Export as Markdown transcript
/session:export --format md ~/my-session.md
# Export as canonical JSON (default)
/session:export ~/my-session.json
# Import a previously exported session
/session:import ~/my-session.json
Session Export / Import
Session.as_export_markdown() → str
Domain method on Session that renders a human-readable Markdown transcript.
The output is lossy (for sharing/documentation) and cannot be re-imported.
from cleveragents.domain.models.core.session import Session
session: Session = ...
md = session.as_export_markdown()
# Returns a Markdown string with:
# - Header block: session ID, actor, created_at, message count
# - Message history: role | timestamp | content
# - Linked plan references
CLI
# Export as canonical JSON (importable)
agents session export --session-id <ID> --output session.json
# Export as Markdown transcript (human-readable, not importable)
agents session export --session-id <ID> --output session.md --format md
# Import from JSON
agents session import --input session.json
Widgets
ThoughtBlockWidget
Renders actor reasoning traces inline in the conversation stream.
from cleveragents.tui.widgets import ThoughtBlockWidget
- Collapsed by default; press
Spaceto expand/collapse - Muted styling distinguishes thought blocks from regular messages
- Backed by
ThoughtBlockdomain model with configurablemax_lines(default: 10)
PermissionQuestionWidget
Inline permission question widget rendered directly in the conversation stream
for single-file permission requests. For multi-file operations the full
PermissionsScreen is pushed instead.
from cleveragents.tui.widgets import PermissionQuestionWidget
from cleveragents.domain.models.core.inline_permission_question import (
InlinePermissionQuestion,
PermissionDecision,
)
widget = PermissionQuestionWidget(question)
event = widget.handle_key("a") # returns PermissionDecisionEvent or None
| Method | Description |
|---|---|
move_up() |
Move selection cursor up (wraps) |
move_down() |
Move selection cursor down (wraps) |
handle_key(key: str) → PermissionDecisionEvent | None |
Process a key press; returns a decision event when resolved |
Key bindings:
| Key | Action |
|---|---|
a |
Allow once |
A |
Allow always (this session) |
r |
Reject once |
R |
Reject always (this session) |
↑ / ↓ |
Navigate options |
Enter |
Confirm highlighted option |
v |
Open full PermissionsScreen with diff view |
PermissionDecisionEvent — emitted when the user makes a decision:
@dataclass
class PermissionDecisionEvent:
question: InlinePermissionQuestion
decision: PermissionDecision
render_permission_question(question, selected_index=0, *, show_diff=False) → str — pure rendering helper (testable without Textual).
PermissionsScreen
Full-screen overlay for tool permission requests.
from cleveragents.tui.screens import PermissionsScreen
| Key | Action |
|---|---|
a |
Allow once |
A |
Allow always |
r |
Reject once |
R |
Reject always |
d |
Cycle diff display mode (unified → side-by-side → context) |
TUI State Persistence
The TUI persists minimal state to ~/.config/cleveragents/tui-state.yaml:
last_persona: "default"
This is loaded on startup to restore the last active persona.