diff --git a/aipa-plans/docs/ssh-tui-architecture-spec.md b/aipa-plans/docs/ssh-tui-architecture-spec.md new file mode 100644 index 0000000..2f9257b --- /dev/null +++ b/aipa-plans/docs/ssh-tui-architecture-spec.md @@ -0,0 +1,427 @@ +--- +created: '2026-04-15T05:41:53+00:00' +id: e8450555-eab3-4d48-a654-b5f1708f0517 +keywords: +- SSH-TUI +- AIPA +- architecture +- Textual +- asyncssh +- MCP +- Linus +- TUI +- terminal +- AI copilot +- Pydantic +- aipa-ssh-tui +- graceful degradation +- command detection +- PTY +links: [] +modified: '2026-04-15T05:41:53+00:00' +summary: '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.' +tags: +- aipa-plans +- ssh-tui +- architecture +- specification +- textual +- asyncssh +- linus +title: 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) + +1. **Escape always returns focus to SSH** — panic button for when something goes wrong. The sysadmin needs `Ctrl+C` in the shell immediately. +2. **Focus cycling:** `Tab` cycles SSH → Commands → Chat (skips Intent, it's read-only). `Ctrl+J`/`Ctrl+K` for up/down cycling. +3. **Intent is a living checklist** — uses ✓ (done), ▸ (current), ○ (pending). Not overwritten each turn; steps are added/updated incrementally. +4. **Suggested commands are editable** — press `e` to inline-edit before sending. `Enter` sends the selected command. +5. **AI-sent commands are visually distinct** — in the SSH panel, commands from the suggestions are prefixed with `→` and dimmed. +6. **Scroll isolation** — each panel scrolls only when focused. Unfocused panels show a `▼ N new lines` indicator. +7. **Graceful degradation** — if AI drops, Intent shows "AI offline", Commands show "(commands may be stale)", Chat queues messages, but SSH stays fully functional. +8. **Minimal chrome** — single-line borders, single-character labels. This lives next to `vim` and `htop` — 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_update` messages (1 second) +- Queues `chat` messages when offline, sends on reconnect +- Discards `terminal_update` messages 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: + +1. **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). +2. **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. +3. **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 + +```toml +# 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 + +```toml +# 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 + +1. **No credential exposure** — SSH passwords and keys never appear in MCP messages. The MCP client only sends `ssh_host` and `ssh_user` in the context object. +2. **User approval required** — every AI-suggested command requires explicit `Enter` to send. No auto-execution. +3. **Command editing** — the user can modify any suggested command before sending. +4. **Risk labels** — every command carries a `risk` level (`low`, `medium`, `high`). High-risk commands are visually highlighted. +5. **Audit trail** — AI-suggested commands are visually distinct in the SSH panel (prefixed with `→`). +6. **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](aipa-plans/docs/ssh-tui-mcp-interface-contract.md) — detailed message format specification +- [Textual Documentation](https://textual.textualize.io/) +- [asyncssh Documentation](https://asyncssh.readthedocs.io/) +- [MCP Specification](https://modelcontextprotocol.io/) \ No newline at end of file