Files
cleveragents-core/docs/api/tui.md
T
freemo d72993d908
CI / lint (pull_request) Successful in 29s
CI / typecheck (pull_request) Successful in 50s
CI / security (pull_request) Successful in 1m3s
CI / quality (pull_request) Successful in 33s
CI / build (pull_request) Successful in 23s
CI / helm (pull_request) Successful in 24s
CI / unit_tests (pull_request) Failing after 7m12s
CI / docker (pull_request) Has been skipped
CI / e2e_tests (pull_request) Successful in 17m51s
CI / integration_tests (pull_request) Failing after 23m31s
CI / coverage (pull_request) Successful in 10m44s
CI / status-check (pull_request) Failing after 1s
CI / benchmark-publish (pull_request) Has been skipped
CI / benchmark-regression (pull_request) Successful in 56m49s
docs: add shell safety API, invariant reconciliation architecture, and new feature highlights
- docs/api/tui.md: Document ShellDangerLevel, DangerousPattern, DEFAULT_PATTERNS,
  DangerousPatternDetector, ShellSafetyService, and SafetyCheckResult with
  full API reference, parameter tables, and usage examples
- docs/architecture.md: Add Invariant Reconciliation section covering the
  builtin/invariant-reconciliation actor, four-scope algorithm, failure
  behaviour, and DI registration
- README.md: Add Invariant Reconciliation, TUI shell danger detection, and
  UKO provenance tracking to the Highlights section

ISSUES CLOSED: #3377
2026-04-05 19:36:58 +00:00

13 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)

Shell Danger Detection

Module: cleveragents.tui.shell_safety

The TUI shell mode (! prefix) checks commands against a configurable pattern registry before execution. Commands that match a dangerous pattern surface a warning overlay; commands at or above the configured block_level are blocked automatically unless a warn_callback is provided.

ShellDangerLevel

from cleveragents.tui.shell_safety import ShellDangerLevel

IntEnum with four severity levels (ordered least to most severe):

Level Value Description
LOW 1 Minor risk — generally recoverable (e.g. chmod 777 on a single file)
MEDIUM 2 Moderate risk — can cause data loss or security exposure (e.g. sudo rm, wget | sh)
HIGH 3 High risk — significant, hard-to-reverse damage (e.g. dd if=, mkfs)
CRITICAL 4 Critical risk — can destroy the system or create a fork bomb (e.g. rm -rf /, :(){ :|:& };:)

Because ShellDangerLevel is an IntEnum, numeric comparisons work naturally: level >= ShellDangerLevel.HIGH.


DangerousPattern

from cleveragents.tui.shell_safety import DangerousPattern

    name="rm_rf_root",
    pattern=r"rm\s+-[rRfF]*\s+/",
    level=ShellDangerLevel.CRITICAL,
    description="Recursively deletes the root filesystem.",
)

Immutable frozen dataclass describing a single dangerous shell pattern.

Attribute Type Description
name str Short human-readable identifier
pattern str Regex matched against the command text
level ShellDangerLevel Severity classification
description str Human-readable explanation of the risk
case_sensitive bool When True, regex is compiled without re.IGNORECASE (default: False)

matches(command: str) → bool — returns True if command matches the compiled pattern.


Built-in Patterns (DEFAULT_PATTERNS)

from cleveragents.tui.shell_safety.pattern_registry import DEFAULT_PATTERNS

The default pattern set covers the most common dangerous shell operations:

Name Level Trigger
rm_rf_root CRITICAL rm -rf /
rm_rf_wildcard CRITICAL rm -rf /* or similar
fork_bomb CRITICAL `:(){ :
dd_if_device HIGH dd if=
mkfs HIGH mkfs
shred_device HIGH shred /dev/ or shred --remove
chmod_777 MEDIUM chmod 777
sudo_rm MEDIUM sudo rm
wget_pipe_sh MEDIUM wget … | sh
curl_pipe_sh MEDIUM curl … | sh
wget_pipe_bash MEDIUM wget … | bash
curl_pipe_bash MEDIUM curl … | bash
git_push_force LOW git push --force
chmod_recursive_permissive LOW chmod -R 6xx/7xx

DangerousPatternDetector

from cleveragents.tui.shell_safety import DangerousPatternDetector

detector = DangerousPatternDetector()
warning = detector.check_first("rm -rf /")
# → DangerousCommandWarning or None

Checks a command string against all registered patterns and returns the first match as a DangerousCommandWarning, or None if the command is safe.

Method Description
check_first(command) → DangerousCommandWarning | None Return the first matching warning
check_all(command) → list[DangerousCommandWarning] Return all matching warnings
add_pattern(pattern) Register an additional DangerousPattern

ShellSafetyService

from cleveragents.tui.shell_safety import ShellSafetyService, ShellDangerLevel

service = ShellSafetyService(block_level=ShellDangerLevel.MEDIUM)
result = service.check_command("rm -rf /")
if not result.allowed:
    print(result.warning.message)

Application service that checks shell commands before execution.

Constructor parameters:

Parameter Type Default Description
detector DangerousPatternDetector | None None Custom detector; a default one is created when None
block_level ShellDangerLevel MEDIUM Minimum level that triggers automatic blocking when no callback is provided
warn_callback Callable[[DangerousCommandWarning], bool] | None None Optional callback invoked for dangerous commands; return True to allow, False to block
extra_patterns list[DangerousPattern] | None None Additional patterns registered on top of the defaults

Methods:

Method Returns Description
check_command(command) SafetyCheckResult Check a command and return a structured result
is_safe(command) bool Convenience wrapper — returns True if the command passes

SafetyCheckResult — import path and attributes:

from cleveragents.tui.shell_safety.safety_service import SafetyCheckResult
Attribute Type Description
command str The command that was checked
warning DangerousCommandWarning | None Warning if a dangerous pattern matched, otherwise None
allowed bool True if the command is allowed to proceed

Example — custom block level and callback:

from cleveragents.tui.shell_safety import ShellSafetyService, ShellDangerLevel

def my_callback(warning):
    # Show a UI prompt; return True to allow, False to block
    return ask_user(f"⚠️ {warning.message}. Proceed?")

service = ShellSafetyService(
    block_level=ShellDangerLevel.HIGH,
    warn_callback=my_callback,
)
result = service.check_command("sudo rm /etc/hosts")

Built-in pattern categories:

  • Destructive filesystem operations (rm -rf, shred, wipe)
  • Privilege escalation (sudo, su, chmod 777 /)
  • Network exfiltration (curl \| sh, wget \| bash, nc reverse shells)
  • Fork bombs and resource exhaustion
  • Disk-level writes (dd if=, mkfs)

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.