Files
cleveragents-core/docs/api/tui.md
T
freemo d66887766f
CI / lint (pull_request) Successful in 20s
CI / quality (pull_request) Successful in 54s
CI / typecheck (pull_request) Successful in 58s
CI / security (pull_request) Successful in 59s
CI / build (pull_request) Successful in 24s
CI / helm (pull_request) Successful in 30s
CI / benchmark-publish (pull_request) Has been skipped
CI / unit_tests (pull_request) Failing after 6m59s
CI / docker (pull_request) Has been skipped
CI / coverage (pull_request) Successful in 11m7s
CI / e2e_tests (pull_request) Failing after 16m35s
CI / integration_tests (pull_request) Failing after 21m56s
CI / status-check (pull_request) Failing after 1s
CI / benchmark-regression (pull_request) Successful in 54m46s
docs(tui): document PermissionQuestionWidget and add CHANGELOG entry
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
2026-04-03 06:39:51 +00:00

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 Space to expand/collapse
  • Muted styling distinguishes thought blocks from regular messages
  • Backed by ThoughtBlock domain model with configurable max_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.