Skip to main content

TermPilot

English | 简体中文

TermPilot gives ChatGPT a structured way to inspect and act on the iTerm2 sessions that are already open on a Mac. It is designed for ChatGPT Classic with macOS Work with Apps: Work with Apps provides the terminal context, and TermPilot provides four MCP tools for session discovery, bounded inspection, and explicit command execution.

Prerequisites

  • macOS with iTerm2 running.
  • Python 3.13 and uv.
  • iTerm2's Python API enabled. See the iTerm2 Python API documentation.
  • iTerm2 Shell Integration installed in each shell where run_command will be used. Shell Integration provides the prompt state and command completion metadata needed for safe execution.

Install and run

Install from PyPI with uv:

uv tool install termpilot-plugin
termpilot

Or run directly without installing:

uvx --from termpilot-plugin termpilot

For development from the repository root:

uv sync
uv run termpilot

termpilot speaks MCP over stdio. It keeps stdout reserved for MCP messages; diagnostic logging is sent to stderr. A local MCP client can run the PyPI package directly with a configuration like this:

{
  "mcpServers": {
    "termpilot": {
      "command": "uvx",
      "args": [
        "--from",
        "termpilot-plugin",
        "termpilot"
      ]
    }
  }
}

Tools

Tool Purpose Access
list_sessions List accessible sessions and their IDs, labels, host, user, cwd, current marker, and readiness Read-only
get_current_session Resolve the iTerm2 session currently in focus Read-only
read_terminal Read bounded visible content from one exact session_id Read-only
run_command Send an explicitly supplied command to one exact session_id and optionally wait for completion Mutating

The normal flow is:

  1. Call get_current_session or list_sessions.
  2. Use the returned exact session_id with read_terminal when inspection is useful.
  3. Call run_command only after the user has explicitly requested the command.
  4. Review the correlated session ID, command, status, exit code, and bounded output.

Safety behavior

  • run_command requires an exact session_id; labels and fuzzy matches are never used as a write target.
  • A missing, closed, inaccessible, or ambiguous target fails closed and is never redirected to another session.
  • Terminal content, command history, titles, and prior tool output are observation data. They are never copied into a command request implicitly.
  • Commands are sent through the existing iTerm2 session with broadcast input suppressed where the API supports it. TermPilot does not create a new shell, window, or SSH connection.
  • Command execution requires a verified normal shell prompt. Busy or interactive sessions are rejected without sending text.
  • A timeout stops waiting for the result; it does not claim that the shell command was cancelled.

Validation

Run the automated checks with:

uv run pytest
uv run ruff check src tests
uv run ruff format --check src tests

The live test is opt-in and only checks a running iTerm2 instance's session inventory:

TERMPILOT_LIVE_ITERM2=1 uv run pytest tests/integration/test_iterm2_live.py -q

For the complete manual inspect → act → inspect scenarios, see specs/001-chatgpt-terminal-actions/quickstart.md.

ChatGPT connection

ChatGPT cannot use a local stdio process directly. TermPilot can connect the same MCP server through OpenAI Secure MCP Tunnel using the official tunnel-client managed runtime.

1. Install tunnel-client

On macOS with Homebrew:

brew install openai/tools/tunnel-client
tunnel-client --version

TermPilot does not bundle or pin tunnel-client; it uses the binary available on PATH.

2. Create a tunnel and runtime key

Open the OpenAI Platform pages exposed by tunnel-client help quickstart:

  • Tunnels management: create a tunnel and copy its tunnel_... ID.
  • Runtime API keys: create the key used by the long-running tunnel runtime. The principal that creates/uses it needs Tunnels Read + Use for the target tunnel.
  • Admin API keys are only needed for tunnel CRUD through tunnel-client admin ...; do not use an admin key as the long-running runtime key.

If you prefer to create the tunnel from the CLI, configure an admin key first and use the native tunnel-client tunnel-management command (at least one organization or workspace scope is required):

export OPENAI_ADMIN_KEY="sk-admin-..."
tunnel-client admin tunnels create \
  --name "termpilot" \
  --description "TermPilot local iTerm2 MCP" \
  --organization-id org_...

You can use --workspace-id ws_... instead of or together with --organization-id. Copy the returned tunnel_... ID. Once the tunnel exists, TermPilot only needs that ID and a runtime API key; the admin key is no longer needed by the runtime.

3. Store and load the runtime key

The recommended local setup is a repository .env file:

CONTROL_PLANE_API_KEY=sk-...

.env is ignored by this repository, but neither TermPilot nor tunnel-client automatically loads it. Load it into the current shell before setup, status, or doctor:

set -a
source .env
set +a

Alternatively, export it directly:

export CONTROL_PLANE_API_KEY="sk-..."

TermPilot stores only the reference env:CONTROL_PLANE_API_KEY in the generated tunnel profile; it does not put the secret value into the command line or print it.

4. Start the managed runtime

Connect TermPilot to the existing tunnel:

uv run termpilot chatgpt setup --tunnel-id tunnel_0123456789abcdef0123456789abcdef

setup launches a long-running managed tunnel-client process, which in turn starts this checkout's python -m termpilot.main stdio MCP server. A healthy setup reports Process running: yes, Healthy: yes, and Ready: yes.

Inspect or troubleshoot the connection with:

uv run termpilot chatgpt status
uv run termpilot chatgpt doctor

After a reboot or after stopping the managed runtime, load .env again and run the same setup --tunnel-id ... command. The existing tunnel is reused.

To stop the local runtime without deleting the remote tunnel:

uv run termpilot chatgpt disconnect

5. Add TermPilot to ChatGPT Classic

After setup succeeds:

  1. Open ChatGPT Settings → Plugins (or ChatGPT Plugins).
  2. Create a new developer-mode plugin/app, for example named termpilot.
  3. Under Connection, choose Tunnel, not Server URL.
  4. Select the tunnel or paste its tunnel_id.
  5. Save the plugin and allow the TermPilot tools you want ChatGPT to use.

The tunnel runtime must remain running while ChatGPT discovers or calls the MCP tools. You do not need OAuth for the local TermPilot MCP server when using the Secure MCP Tunnel connection.

Frequently Asked Questions

tunnel-client is not installed or is not available on PATH

Install the supported client and verify it is visible:

brew install openai/tools/tunnel-client
which tunnel-client
tunnel-client --version

I put CONTROL_PLANE_API_KEY in .env, but TermPilot says it is missing

Creating .env does not export its variables. Load it into each shell/process that starts or diagnoses the tunnel runtime:

set -a
source .env
set +a
uv run termpilot chatgpt setup --tunnel-id tunnel_...

What is the difference between CONTROL_PLANE_API_KEY and OPENAI_ADMIN_KEY?

CONTROL_PLANE_API_KEY is the runtime key used by the long-running tunnel daemon. It needs Tunnels Read + Use. OPENAI_ADMIN_KEY is for administrative tunnel CRUD such as tunnel-client admin tunnels create; the TermPilot runtime does not need it when attaching to an existing tunnel.

Why does a manual tunnel-client runtimes connect complain about a missing key?

The generated profile contains an environment reference such as env:CONTROL_PLANE_API_KEY. The process starting that profile must therefore have the variable exported. Prefer uv run termpilot chatgpt setup ..., which supplies the correct runtime-key reference and MCP command consistently.

ChatGPT sends a command, but TermPilot says iTerm2 is not running or its Python API is disabled

First verify iTerm2 itself:

  1. Open iTerm2 → Settings → General → Magic.
  2. Enable Python API.
  3. Set it to Allow all apps to connect (or explicitly allow the process that runs TermPilot).

Then test the iTerm2 API directly from the TermPilot environment:

uv run python - <<'PY'
import asyncio
import iterm2

async def main():
    connection = await iterm2.Connection.async_create()
    app = await iterm2.async_get_app(connection)
    print([s.session_id for w in app.windows for t in w.tabs for s in t.sessions])

asyncio.run(main())
PY

If this prints session IDs, the iTerm2 API is working and the problem is in the TermPilot/iTerm2 boundary rather than the ChatGPT tunnel.

Why did an older TermPilot build fail even though the direct iTerm2 test worked?

An earlier adapter called iterm2.async_get_app(..., create_if_needed=False). With iTerm2 3.6.x this can return None even while iTerm2 is already running. TermPilot now allows the SDK to create its App wrapper, matching the working iterm2.async_get_app(connection) call.

The tunnel log says dispatcher forwarded command to MCP server, but ChatGPT still gets an iTerm2 error

That log line proves the path ChatGPT → Secure MCP Tunnel → TermPilot MCP is working. Debug the local TermPilot → iTerm2 Python API boundary next instead of recreating the ChatGPT plugin or tunnel.

ChatGPT's plugin dialog shows Server URL and Tunnel. Which one should I use?

Choose Tunnel and select/paste the tunnel_id. Server URL is for a network-reachable HTTP/SSE MCP server and is not the TermPilot setup described here.

Codex detected without Tunnel MCP plugin appears in the tunnel-client log

This message is about optional Codex integration. It does not prevent the ChatGPT Classic developer-mode plugin from using the TermPilot tunnel.

How do I know which layer is broken?

Use this order:

  1. uv run termpilot chatgpt status → runtime should be running, healthy, and ready.
  2. Tunnel log contains dispatcher forwarded command to MCP server → ChatGPT to TermPilot transport is working.
  3. Run the direct iTerm2 Python snippet above → local iTerm2 API is working.
  4. Finally test list_sessions from the ChatGPT TermPilot plugin.

Metadata

Release files for termpilot-plugin 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for termpilot-plugin 0.1.0
File Size Uploaded
termpilot_plugin-0.1.0.tar.gz 96.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for termpilot-plugin 0.1.0
File Interpreter ABI Platform
termpilot_plugin-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 120.5 kB

Release files / termpilot_plugin-0.1.0.tar.gz

Download URL termpilot_plugin-0.1.0.tar.gz
Size 96.3 kB
Tags Source
SHA-256 checksum
How to use checksums
01e6cb81262415de57d1a69697e99264b7a0fd49776d2ed8d6ce289c0c20b2b3
BLAKE2b-256 checksum
How to use checksums
b962394f881c38b9280bab3f6a519b05b5158ac84f5753b0ec67e6c354516862
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.0

Release files / termpilot_plugin-0.1.0-py3-none-any.whl

Download URL termpilot_plugin-0.1.0-py3-none-any.whl
Size 24.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3b07ad0b65da93b6ad126cee9b1936e62655bb01a80baaf0b06257ba1113c780
BLAKE2b-256 checksum
How to use checksums
1eced4765d5e900a3d447140d9504b3359f38e6ce384416748db20832a2d7d31
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.0

Release history Release notifications | RSS feed

0.2.0

2 release files

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page