23 KiB
created, id, keywords, links, modified, summary, tags, title
| created | id | keywords | links | modified | summary | tags | title | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 2026-04-15T05:41:53+00:00 | e8450555-eab3-4d48-a654-b5f1708f0517 |
|
2026-04-15T05:41:53+00:00 | Full architecture specification for the AIPA SSH-TUI application: a Textual-based terminal SSH client with integrated AI copilot, covering component design, data flows, project structure, configuration, error handling, testing, and security model. |
|
AIPA SSH-TUI — Architecture Specification |
AIPA SSH-TUI — Architecture Specification
Status: Draft — Pending review
Project: AIPA SSH-TUI
Version: 0.1
Last updated: 2025-07-10
Authors: Linus (Systems Architect), Cara (UI/UX Design)
1. Overview
AIPA SSH-TUI is a terminal-based SSH client with an integrated AI copilot. It wraps SSH sessions in a Textual TUI and connects to the AIPA MCP server to provide real-time AI assistance for system administration tasks. The AI (Linus) monitors terminal output, maintains a plan of action, suggests commands, and responds to natural language queries — but never executes commands without explicit user approval.
Goals
- SSH client first — fully functional SSH terminal even without AI. Graceful degradation if AI is unavailable.
- AI copilot second — enhances the SSH experience with context-aware suggestions, intent tracking, and natural language interaction.
- Safety — the user is always in control. Every AI-suggested command requires explicit approval. Commands are editable before execution.
- Auditability — AI-suggested commands are visually distinct in the terminal so the user always knows who issued what.
Non-Goals (v1)
- Multiple concurrent SSH sessions
- Remote file inspection via AI
- AI-initiated push notifications
- Native SSH key management
- Port forwarding / tunneling UI
- SSH jump host / proxy support (v1 uses local SSH config or
ProxyCommand)
2. Technology Stack
| Component | Technology | Rationale |
|---|---|---|
| TUI Framework | Textual (Python) | Async-native, rich widget system, mature, active development |
| SSH Library | asyncssh | Async, native coroutines, plays well with Textual's event loop |
| MCP Client | mcp Python SDK | Official MCP client library |
| Configuration | TOML | Standard Python config format, human-readable |
| Data Models | Pydantic | Validation, serialization, type safety |
| Python | >=3.11 | Required for asyncssh and modern type syntax |
3. TUI Layout
Designed by Cara (UI/UX). The layout follows sysadmin conventions — minimal chrome, single-line borders, dense information display.
┌─────────────────────────────── Status Bar ────────────────────────────────────┐
│ host: prod-web-03 user: deploy ● connected AI: ● online │
├──────────────────────────────────┬────────────────────────────────────────────┤
│ │ ┌── Intent ──────────────────────────┐ │
│ │ │ ✓ Check overall disk usage │ │
│ SSH Session │ │ ✓ Identify largest directories │ │
│ (main interaction surface) │ │ ▸ List largest files in /var/log │ │
│ │ │ ○ Determine cleanup strategy │ │
│ │ │ ○ Free disk space safely │ │
│ root@prod:~# df -h │ │ │ │
│ Filesystem Size Used ... │ │ ── Confidence: 85% │ │
│ │ └────────────────────────────────────┘ │
│ │ │
│ │ ┌── Suggested Commands ────────────┐ │
│ │ │ 1. [low] ls -lhS /var/log |... │ │
│ │ │ 2. [low] journalctl --disk-use │ │
│ │ │ 3. [med] apt-get clean && ... │ │
│ │ │ │ │
│ │ │ [Enter] Send [e] Edit [d] Del│ │
│ │ └────────────────────────────────────┘ │
├──────────────────────────────────┴────────────────────────────────────────────┤
│ ┌── Chat to AI ──────────────────────────────────────────────────────────┐ │
│ │ > /var is full, what's eating the space? │ │
│ │ AI: Looks like /var/log is the culprit at 4.2G. Queued commands to │ │
│ │ identify the largest files. │ │
│ │ > go ahead with #1 │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
│ > type here... │
└──────────────────────────────────────────────────────────────────────────────┘
Layout Proportions
- Top row: 75% of terminal height
- SSH Session: ~60% width (left)
- Intent + Suggested Commands: ~40% width (right), stacked vertically with Intent taking ~55% and Commands taking ~45%
- Bottom row: 25% of terminal height (minimum 6 lines, resizable)
- Chat to AI: full width
Key UX Rules (from Cara)
- Escape always returns focus to SSH — panic button for when something goes wrong. The sysadmin needs
Ctrl+Cin the shell immediately. - Focus cycling:
Tabcycles SSH → Commands → Chat (skips Intent, it's read-only).Ctrl+J/Ctrl+Kfor up/down cycling. - Intent is a living checklist — uses ✓ (done), ▸ (current), ○ (pending). Not overwritten each turn; steps are added/updated incrementally.
- Suggested commands are editable — press
eto inline-edit before sending.Entersends the selected command. - AI-sent commands are visually distinct — in the SSH panel, commands from the suggestions are prefixed with
→and dimmed. - Scroll isolation — each panel scrolls only when focused. Unfocused panels show a
▼ N new linesindicator. - Graceful degradation — if AI drops, Intent shows "AI offline", Commands show "(commands may be stale)", Chat queues messages, but SSH stays fully functional.
- Minimal chrome — single-line borders, single-character labels. This lives next to
vimandhtop— it should look like it belongs.
Key Bindings
| Key | Context | Action |
|---|---|---|
Escape |
Anywhere | Return focus to SSH session |
Tab |
Anywhere | Cycle focus: SSH → Commands → Chat → SSH |
Ctrl+J |
Anywhere | Focus previous panel |
Ctrl+K |
Anywhere | Focus next panel |
Enter |
SSH Session | Send input (passthrough) |
Enter |
Commands Panel | Send selected command |
Enter |
Chat Panel | Send message to AI |
e |
Commands Panel | Inline-edit selected command |
d |
Commands Panel | Delete selected command |
↑/↓ |
Commands Panel | Select command |
Ctrl+L |
Anywhere | Force redraw |
4. System Architecture
4.1 Component Diagram
┌─────────────────────────────────────────────────────────────────┐
│ SSH-TUI Application │
│ │
│ ┌──────────┐ ┌──────────────┐ ┌────────────────────────┐ │
│ │ TUI │ │ Session │ │ MCP Client │ │
│ │ Layer │◄──┤ Manager │ │ │ │
│ │ │ │ │ │ ┌───────────────────┐ │ │
│ │ ┌──────┐ │ │ ┌────────┐ │ │ │ Connection Mgmt │ │ │
│ │ │SSH │ │ │ │ asyncssh│ │ │ │ Message Queue │ │ │
│ │ │Panel │ │ │ │ session │ │ │ │ Debounce Logic │ │ │
│ │ └──────┘ │ │ └────────┘ │ │ └───────────────────┘ │ │
│ │ ┌──────┐ │ │ ┌────────┐ │ │ │ │
│ │ │Intent│ │ │ │Terminal│ │ └───────────┬─────────────┘ │
│ │ │Panel │ │ │ │Buffer │ │ │ │
│ │ └──────┘ │ │ └────────┘ │ │ │
│ │ ┌──────┐ │ │ ┌────────┐ │ │ │
│ │ │Sugg. │ │ │ │Command │ │ │ │
│ │ │Cmds │ │ │ │Log │ │ │ │
│ │ └──────┘ │ │ └────────┘ │ │ │
│ │ ┌──────┐ │ └──────────────┘ │ │
│ │ │Chat │ │ │ │
│ │ │Panel │ │ │ │
│ │ └──────┘ │ │ │
│ └──────────┘ │ │
└─────────────────────────────────────────────────┼─────────────────┘
│ MCP Protocol
│
┌─────────────────────────────────────────────────┼─────────────────┐
│ AIPA MCP Server │ │
│ ▼ │
│ ┌──────────┐ ┌──────────────┐ ┌────────────────────────┐ │
│ │ Agent │ │ Tool │ │ Linus Agent │ │
│ │ Router │──┤ Registry │──┤ ┌───────────────────┐ │ │
│ │ │ │ │ │ │ linus_chat │ │ │
│ └──────────┘ └──────────────┘ │ │ linus_term_update │ │ │
│ │ │ linus_cmd_exec │ │ │
│ │ │ linus_session_st │ │ │
│ │ └───────────────────┘ │ │
│ └────────────────────────┘ │
└───────────────────────────────────────────────────────────────────┘
4.2 Component Responsibilities
TUI Layer (app.py, panels/)
- Manages the Textual application lifecycle
- Renders all four panels and the status bar
- Handles keyboard input and focus management
- Dispatches user actions to the Session Manager or MCP Client
Session Manager (ssh_session.py)
- Manages the asyncssh connection lifecycle (connect, authenticate, disconnect)
- Creates and manages the PTY (pseudo-terminal) on the remote host
- Captures all terminal output into a ring buffer (configurable, default 2000 lines)
- Tracks the current working directory (by parsing prompts, best-effort)
- Provides a terminal tail snapshot (last N lines) for MCP context
- Tracks command execution: command text, output, exit code, timestamp
- Injects commands into the PTY (from user typing or from sending suggestions)
MCP Client (mcp_client.py)
- Connects to the AIPA MCP server (configurable URL)
- Manages the message queue for outgoing messages
- Implements debouncing for
terminal_updatemessages (1 second) - Queues
chatmessages when offline, sends on reconnect - Discards
terminal_updatemessages when offline (ephemeral) - Parses Linus's structured responses and dispatches updates to the appropriate TUI panels
- Handles connection errors and implements retry with exponential backoff
Data Models (models.py)
- Pydantic models for all message types (see MCP Interface Contract)
- Session state tracking
- Command log entries
4.3 Data Flow
Flow 1: User sends a chat message
User types in Chat Panel → TUI dispatches to MCP Client
→ MCP Client calls linus_chat(message, context)
→ MCP Server routes to Linus
→ Linus processes and returns { chat_response, intent_update, suggested_commands }
→ MCP Client receives response
→ Updates Chat Panel with chat_response.message
→ Updates Intent Panel with intent_update.steps
→ Updates Commands Panel with suggested_commands.commands
Flow 2: User types in SSH session
User types in SSH Panel → TUI passes keystrokes to Session Manager
→ Session Manager writes to asyncssh PTY
→ Remote host processes and produces output
→ Session Manager captures output into ring buffer
→ Session Manager signals MCP Client (terminal output changed)
→ MCP Client debounces (1 second)
→ MCP Client calls linus_terminal_update(new_output, terminal_tail, context)
→ Linus may return { intent_update, suggested_commands } or null
→ If updates: TUI panels refreshed
Flow 3: User sends a suggested command
User selects command + presses Enter → TUI reads selected command
→ TUI sends command to Session Manager
→ Session Manager writes command + newline to asyncssh PTY
→ Session Manager logs: { command, source: "suggested", suggestion_id, was_edited }
→ Output flows back (see Flow 2)
→ When command completes (prompt detected):
→ Session Manager sends to MCP Client
→ MCP Client calls linus_command_executed(command, output, exit_code, context)
→ Linus returns updated { chat_response, intent_update, suggested_commands, clear_suggestions }
4.4 Terminal Output Detection
Detecting when a command has finished executing (to capture exit code and complete output) is non-trivial in a PTY. Strategy:
- Prompt detection (primary): Parse the shell prompt to detect when a new prompt appears after a command. Configurable prompt pattern (default:
$or#at line start). - Exit code capture: Before each user/suggested command, inject
\n echo "EXIT:$?"after the command to capture the exit code. Parse this from the output stream. - Timeout fallback: If no prompt is detected within a configurable timeout (default: 30 seconds), consider the command "still running" and send what output is available.
5. Project Structure
aipa-ssh-tui/
├── pyproject.toml # Project metadata, dependencies, entry point
├── README.md # Quick start, usage, screenshots
├── config/
│ └── default.toml # Default configuration
├── src/
│ └── aipa_ssh_tui/
│ ├── __init__.py
│ ├── app.py # Textual App class, layout, CSS
│ ├── ssh_session.py # asyncssh connection, PTY, output capture
│ ├── mcp_client.py # MCP client, message queue, debouncing
│ ├── command_detector.py # Prompt detection, exit code parsing
│ ├── models.py # Pydantic models for all message types
│ ├── config.py # Configuration loading (TOML)
│ └── panels/
│ ├── __init__.py
│ ├── session.py # SSH session panel (RichLog or similar)
│ ├── intent.py # Intent checklist panel
│ ├── suggestions.py # Suggested commands panel + send/edit
│ ├── chat.py # Chat to AI panel
│ └── status_bar.py # Top status bar
└── tests/
├── conftest.py
├── test_models.py
├── test_ssh_session.py
├── test_mcp_client.py
├── test_command_detector.py
└── test_app.py
6. Configuration
# config/default.toml
[ssh]
# Default SSH settings (can be overridden via CLI args)
host = ""
port = 22
user = ""
# Path to SSH config file (uses ~/.ssh/config by default)
config_file = ""
[mcp]
# AIPA MCP server connection
server_url = "http://localhost:8000"
# Retry settings
retry_base_delay = 1.0 # seconds
retry_max_delay = 30.0 # seconds
retry_max_attempts = 5
[terminal]
# Terminal buffer settings
ring_buffer_size = 2000 # lines to keep in memory
tail_lines = 100 # lines sent to AI as context
# Prompt detection (regex)
prompt_pattern = "^[^@]*@[^:]*:[^#]*[#$] "
[mcp_debounce]
# Debounce settings for terminal updates
terminal_update_interval = 1.0 # seconds
command_complete_timeout = 30.0 # seconds before considering a command "still running"
[ui]
# TUI appearance
theme = "nord" # Textual theme
ai_command_prefix = "→ " # prefix for AI-sent commands in SSH panel
show_line_numbers = false # line numbers in SSH panel
7. CLI Interface
aipa-ssh-tui [user@]hostname [-p port] [-i identity_file] [--config config.toml]
Examples:
aipa-ssh-tui root@10.250.50.70
aipa-ssh-tui deploy@prod-web-03 -p 2222 -i ~/.ssh/id_prod
aipa-ssh-tui web-01 --config ~/my-ssh-tui.toml
If no MCP server is configured or reachable, the TUI operates as a plain SSH client with the Intent and Commands panels hidden or showing "AI not configured".
8. Error Handling
| Scenario | Behavior |
|---|---|
| SSH connection fails | Show error in SSH panel. Offer retry. Chat/Intent/Commands non-functional. |
| SSH connection drops mid-session | Show disconnection in status bar and SSH panel. Attempt reconnect (configurable). Queue chat messages. |
| MCP server unreachable | SSH works normally. Intent shows "AI offline". Commands show "(stale)". Chat queues messages. Auto-retry MCP connection. |
| MCP server returns malformed response | Log error. Skip update for malformed component. Show warning in status bar. |
| asyncssh authentication fails | Show error. Do not retry automatically. Prompt for different credentials or key. |
| Terminal resize | Textual handles natively. SSH PTY resized via window-change request. |
9. Dependencies
# pyproject.toml (core dependencies)
[project]
dependencies = [
"textual>=0.47.0",
"asyncssh>=2.14.0",
"pydantic>=2.0.0",
"tomli>=2.0.0; python_version < '3.11'",
]
[project.optional-dependencies]
dev = [
"pytest>=8.0",
"pytest-asyncio>=0.23.0",
"ruff>=0.4.0",
"mypy>=1.10.0",
]
10. Testing Strategy
| Layer | What to test | Tools |
|---|---|---|
| Data models | Pydantic validation, serialization, defaults | pytest |
| Command detector | Prompt regex matching, exit code parsing | pytest |
| SSH session | Connect/disconnect, PTY I/O, output capture | pytest-asyncio + mock SSH server |
| MCP client | Message queueing, debouncing, offline handling | pytest + mock MCP server |
| TUI panels | Rendering, focus management, key bindings | pytest + Textual's test driver |
11. Security Model
- No credential exposure — SSH passwords and keys never appear in MCP messages. The MCP client only sends
ssh_hostandssh_userin the context object. - User approval required — every AI-suggested command requires explicit
Enterto send. No auto-execution. - Command editing — the user can modify any suggested command before sending.
- Risk labels — every command carries a
risklevel (low,medium,high). High-risk commands are visually highlighted. - Audit trail — AI-suggested commands are visually distinct in the SSH panel (prefixed with
→). - Command logging — all executed commands are logged locally with source, timestamp, and whether they were edited.
12. Future Scope (v2+)
- Multiple concurrent SSH sessions (tabs)
- SSH jump host / ProxyCommand support
- AI-driven file inspection (Linus requests file contents from remote host)
- Push notifications from Linus (e.g., "I detected a kernel OOM in dmesg")
- Bash history integration for smarter suggestions
- Saved session profiles (host + common tasks)
- Shared intent/state between sessions
References
- SSH-TUI MCP Interface Contract — detailed message format specification
- Textual Documentation
- asyncssh Documentation
- MCP Specification