PiloTY logo

# PiloTY PiloTY (PTY for your AI Copilot) is an MCP server that gives an agent a persistent, interactive terminal. If you have used Claude Code / Codex to run shell commands, you have probably hit the same wall: tool calls tend to be stateless. Each call starts "fresh", so environment variables disappear, interactive programs cannot be driven reliably, and long-running processes get cut off or orphaned while the agent is thinking. PiloTY exists to make the agent's terminal behave more like a human's: start something in a real terminal, come back later, and keep going. Warning: PiloTY exposes unrestricted terminal access. Treat it like giving the agent your keyboard. ## What it enables - Long-running commands: builds, installs, migrations, test suites. Start once, check output later. - Log monitoring: `tail -f`, `journalctl -f`, `kubectl logs -f`, CI logs, service restarts. - "Vibe debugging": keep a REPL/debugger open while the agent reads code and tries ideas (`python`, `ipython`, `pdb`). - Privileged operations: handle interactive password prompts (`sudo`, SSH passwords, key passphrases). - SSH-based devops: keep a remote login session alive across tool calls; run remote commands in the same shell. - Terminal UIs: `less`, `man`, `top`, `vim` can work, but cursor-heavy programs often require screen snapshots instead of plain text output. ## Quickstart PiloTY is meant to be launched by an MCP client over stdio. Add it to Codex CLI as an MCP server: ```bash codex mcp add piloty -- uvx --from git+https://github.com/yiwenlu66/PiloTY.git piloty ``` If you prefer SSH-based Git fetch: ```bash codex mcp add piloty -- uvx --from git+ssh://git@github.com/yiwenlu66/PiloTY.git piloty ``` If you already have a local clone: ```bash codex mcp add piloty -- uv --directory /path/to/PiloTY run piloty ``` Run the server command directly (without adding it to an MCP client): ```bash uvx --from git+https://github.com/yiwenlu66/PiloTY.git piloty ``` ## Mental model One session is one real interactive terminal that stays alive across tool calls. - State persists: cwd, environment variables, foreground process, remote SSH connection, REPL/debugger state. - PiloTY tracks both the raw output stream and a rendered screen/scrollback view. PiloTY keeps two representations: - `output`: incremental text stream (optionally ANSI-stripped) - Rendered screen/scrollback: what a human would see in a terminal Public MCP results separate call outcome from terminal interpretation: - `outcome`: `success`, `deadline_exceeded`, `eof`, `error`, `invalid_session`, or `terminated` - `terminal_state`: best-effort rendered-state classification after the call (`running`, `ready`, `password`, `confirm`, `repl`, `editor`, `pager`, `unknown`) Sessions are addressed by a `session_id` string. Reusing the same id is what keeps state. ## Integration notes (for MCP integrators) MCP does not expose a standard "client cwd" field, so the first step is always to create a session with an explicit working directory, then reuse the same `session_id` for subsequent calls. Typical agent workflow: - Create a session (explicit cwd) and reuse the same `session_id`. - Use `send_line` to submit a newline-terminated command, `send_text` for raw bytes, and `send_control` / `send_signal` for interrupts. - Use `wait_for_output` for temporal PTY output waiting, `wait_for_regex` for content-based waits, and `wait_for_shell_prompt` after `ssh` or similar login flows. - Use `snapshot_screen` / `snapshot_scrollback` when layout matters. Snapshot tools are passive and do not ingest fresh PTY bytes. - If prompt detection is wrong, configure a custom shell-prompt regex. - Use `send_password` for secret entry; terminate the session when done. For exact tool names, arguments, and return fields, use your MCP client's tool schema or read `piloty/mcp_server.py`. ## Limitations - `deadline_s` is a wall-clock budget. On send/wait tools, it is not "process completion time". - Drain-based tools (`send_line`, `send_text`, `send_control`, `send_password`, `send_signal`, `wait_for_output`) stop after the server quiescence policy (`PILOTY_QUIESCENCE_MS`, default `1000`) or when `deadline_s` expires. - `wait_for_output` can return partial output with `outcome=deadline_exceeded` if output started but the PTY never went quiet before the deadline. - `wait_for_regex` first checks already-rendered scrollback, then waits on new PTY bytes. - `wait_for_shell_prompt` consumes PTY output while it waits and returns the consumed bytes in `output`. - Terminal-state detection is best-effort and can be wrong (especially for custom prompts and cursor-heavy TUIs). - Plain text output can be misleading for full-screen programs; use screen snapshots when layout matters. - `send_password()` suppresses transcript logging and terminal echo for that send. It does not prevent other prompts/programs from echoing secrets later. - Quiescence-based output collection can be confused by programs that print periodic noise. Tune with `PILOTY_QUIESCENCE_MS` (default `1000`). ## Logs Each server instance writes session logs under `~/.piloty/`: - `~/.piloty/servers//sessions//transcript.log`: raw PTY bytes (combined stdout/stderr) - `~/.piloty/servers//sessions//commands.log`: inputs sent (best-effort) - `~/.piloty/servers//sessions//interaction.log`: inputs plus captured output (best-effort) - `~/.piloty/servers//sessions//session.json`: metadata snapshot - `~/.piloty/active//`: symlink to the current session directory (when symlinks are supported) Server logs default to `/tmp/piloty.log`. `tools/session_viewer.py` can inspect sessions: ```bash python tools/session_viewer.py list python tools/session_viewer.py info / python tools/session_viewer.py tail -f / ``` ## Development Repository layout: ``` piloty/ core.py # PTY + terminal renderer + session logs mcp_server.py # MCP tools + state inference tests/ tools/ pty_playground.py session_viewer.py ``` Run tests: ```bash python -m pytest -q ``` License: Apache License 2.0, see `LICENSE`.