termpilot-plugin 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. termpilot_plugin-0.1.0/.gitignore +231 -0
  2. termpilot_plugin-0.1.0/.python-version +1 -0
  3. termpilot_plugin-0.1.0/LICENSE +21 -0
  4. termpilot_plugin-0.1.0/PKG-INFO +345 -0
  5. termpilot_plugin-0.1.0/README.md +332 -0
  6. termpilot_plugin-0.1.0/README.zh-CN.md +310 -0
  7. termpilot_plugin-0.1.0/docs/superpowers/plans/2026-09-16-chatgpt-tunnel.md +105 -0
  8. termpilot_plugin-0.1.0/docs/superpowers/specs/2026-09-16-chatgpt-tunnel-design.md +102 -0
  9. termpilot_plugin-0.1.0/pyproject.toml +43 -0
  10. termpilot_plugin-0.1.0/specs/001-chatgpt-terminal-actions/checklists/requirements.md +35 -0
  11. termpilot_plugin-0.1.0/specs/001-chatgpt-terminal-actions/contracts/mcp-tools.md +152 -0
  12. termpilot_plugin-0.1.0/specs/001-chatgpt-terminal-actions/data-model.md +110 -0
  13. termpilot_plugin-0.1.0/specs/001-chatgpt-terminal-actions/plan.md +81 -0
  14. termpilot_plugin-0.1.0/specs/001-chatgpt-terminal-actions/quickstart.md +110 -0
  15. termpilot_plugin-0.1.0/specs/001-chatgpt-terminal-actions/research.md +81 -0
  16. termpilot_plugin-0.1.0/specs/001-chatgpt-terminal-actions/spec.md +109 -0
  17. termpilot_plugin-0.1.0/specs/001-chatgpt-terminal-actions/tasks.md +211 -0
  18. termpilot_plugin-0.1.0/src/termpilot/__init__.py +3 -0
  19. termpilot_plugin-0.1.0/src/termpilot/adapters/__init__.py +1 -0
  20. termpilot_plugin-0.1.0/src/termpilot/adapters/iterm2.py +589 -0
  21. termpilot_plugin-0.1.0/src/termpilot/chatgpt.py +342 -0
  22. termpilot_plugin-0.1.0/src/termpilot/errors.py +68 -0
  23. termpilot_plugin-0.1.0/src/termpilot/main.py +37 -0
  24. termpilot_plugin-0.1.0/src/termpilot/mcp_server.py +153 -0
  25. termpilot_plugin-0.1.0/src/termpilot/models.py +203 -0
  26. termpilot_plugin-0.1.0/src/termpilot/services/__init__.py +1 -0
  27. termpilot_plugin-0.1.0/src/termpilot/services/commands.py +118 -0
  28. termpilot_plugin-0.1.0/src/termpilot/services/sessions.py +131 -0
  29. termpilot_plugin-0.1.0/tests/contract/test_mcp_tools.py +142 -0
  30. termpilot_plugin-0.1.0/tests/integration/test_iterm2_live.py +21 -0
  31. termpilot_plugin-0.1.0/tests/unit/test_chatgpt.py +283 -0
  32. termpilot_plugin-0.1.0/tests/unit/test_commands.py +172 -0
  33. termpilot_plugin-0.1.0/tests/unit/test_errors.py +27 -0
  34. termpilot_plugin-0.1.0/tests/unit/test_iterm2_adapter.py +406 -0
  35. termpilot_plugin-0.1.0/tests/unit/test_main.py +58 -0
  36. termpilot_plugin-0.1.0/tests/unit/test_models.py +102 -0
  37. termpilot_plugin-0.1.0/tests/unit/test_sessions.py +121 -0
  38. termpilot_plugin-0.1.0/uv.lock +855 -0
@@ -0,0 +1,231 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[codz]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py.cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ # Pipfile.lock
96
+
97
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ # uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ # poetry.lock
109
+ # poetry.toml
110
+
111
+ # pdm
112
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
113
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
114
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
115
+ # pdm.lock
116
+ # pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # pixi
121
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
122
+ # pixi.lock
123
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
124
+ # in the .venv directory. It is recommended not to include this directory in version control.
125
+ .pixi
126
+
127
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
128
+ __pypackages__/
129
+
130
+ # Celery stuff
131
+ celerybeat-schedule
132
+ celerybeat.pid
133
+
134
+ # Redis
135
+ *.rdb
136
+ *.aof
137
+ *.pid
138
+
139
+ # RabbitMQ
140
+ mnesia/
141
+ rabbitmq/
142
+ rabbitmq-data/
143
+
144
+ # ActiveMQ
145
+ activemq-data/
146
+
147
+ # SageMath parsed files
148
+ *.sage.py
149
+
150
+ # Environments
151
+ .env
152
+ .envrc
153
+ .venv
154
+ env/
155
+ venv/
156
+ ENV/
157
+ env.bak/
158
+ venv.bak/
159
+
160
+ # Spyder project settings
161
+ .spyderproject
162
+ .spyproject
163
+
164
+ # Rope project settings
165
+ .ropeproject
166
+
167
+ # mkdocs documentation
168
+ /site
169
+
170
+ # mypy
171
+ .mypy_cache/
172
+ .dmypy.json
173
+ dmypy.json
174
+
175
+ # Pyre type checker
176
+ .pyre/
177
+
178
+ # pytype static type analyzer
179
+ .pytype/
180
+
181
+ # Cython debug symbols
182
+ cython_debug/
183
+
184
+ # PyCharm
185
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
186
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
187
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
188
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
189
+ # .idea/
190
+
191
+ # Abstra
192
+ # Abstra is an AI-powered process automation framework.
193
+ # Ignore directories containing user credentials, local state, and settings.
194
+ # Learn more at https://abstra.io/docs
195
+ .abstra/
196
+
197
+ # Visual Studio Code
198
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
199
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
200
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
201
+ # you could uncomment the following to ignore the entire vscode folder
202
+ # .vscode/
203
+ # Temporary file for partial code execution
204
+ tempCodeRunnerFile.py
205
+
206
+ # Ruff stuff:
207
+ .ruff_cache/
208
+ .DS_Store
209
+ Thumbs.db
210
+ *.tmp
211
+ *.swp
212
+ .vscode/
213
+ .idea/
214
+ .worktrees/
215
+
216
+ # PyPI configuration file
217
+ .pypirc
218
+
219
+ # Marimo
220
+ marimo/_static/
221
+ marimo/_lsp/
222
+ __marimo__/
223
+
224
+ # Streamlit
225
+ .streamlit/secrets.toml
226
+
227
+ # Agents
228
+ .agents/
229
+
230
+ # specify
231
+ .specify/
@@ -0,0 +1 @@
1
+ 3.13
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 blizhan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,345 @@
1
+ Metadata-Version: 2.5
2
+ Name: termpilot-plugin
3
+ Version: 0.1.0
4
+ Summary: Give ChatGPT eyes and hands in your terminal
5
+ Project-URL: Homepage, https://github.com/blizhan/termpilot
6
+ Project-URL: Repository, https://github.com/blizhan/termpilot
7
+ Project-URL: Issues, https://github.com/blizhan/termpilot/issues
8
+ License-File: LICENSE
9
+ Requires-Python: >=3.13
10
+ Requires-Dist: iterm2<3,>=2.2
11
+ Requires-Dist: mcp<3,>=2
12
+ Description-Content-Type: text/markdown
13
+
14
+ # TermPilot
15
+
16
+ [English](README.md) | [简体中文](README.zh-CN.md)
17
+
18
+ TermPilot gives ChatGPT a structured way to inspect and act on the iTerm2
19
+ sessions that are already open on a Mac. It is designed for ChatGPT Classic
20
+ with macOS Work with Apps: Work with Apps provides the terminal context, and
21
+ TermPilot provides four MCP tools for session discovery, bounded inspection,
22
+ and explicit command execution.
23
+
24
+ ## Prerequisites
25
+
26
+ - macOS with iTerm2 running.
27
+ - Python 3.13 and [uv](https://docs.astral.sh/uv/).
28
+ - iTerm2's Python API enabled. See the [iTerm2 Python API
29
+ documentation](https://iterm2.com/python-api/).
30
+ - iTerm2 [Shell Integration](https://iterm2.com/documentation-shell-integration.html)
31
+ installed in each shell where `run_command` will be used. Shell Integration
32
+ provides the prompt state and command completion metadata needed for safe
33
+ execution.
34
+
35
+ ## Install and run
36
+
37
+ Install from PyPI with `uv`:
38
+
39
+ ```bash
40
+ uv tool install termpilot-plugin
41
+ termpilot
42
+ ```
43
+
44
+ Or run directly without installing:
45
+
46
+ ```bash
47
+ uvx --from termpilot-plugin termpilot
48
+ ```
49
+
50
+ For development from the repository root:
51
+
52
+ ```bash
53
+ uv sync
54
+ uv run termpilot
55
+ ```
56
+
57
+ `termpilot` speaks MCP over stdio. It keeps stdout reserved for MCP messages;
58
+ diagnostic logging is sent to stderr. A local MCP client can run the PyPI package
59
+ directly with a configuration like this:
60
+
61
+ ```json
62
+ {
63
+ "mcpServers": {
64
+ "termpilot": {
65
+ "command": "uvx",
66
+ "args": [
67
+ "--from",
68
+ "termpilot-plugin",
69
+ "termpilot"
70
+ ]
71
+ }
72
+ }
73
+ }
74
+ ```
75
+
76
+ ## Tools
77
+
78
+ | Tool | Purpose | Access |
79
+ | --- | --- | --- |
80
+ | `list_sessions` | List accessible sessions and their IDs, labels, host, user, cwd, current marker, and readiness | Read-only |
81
+ | `get_current_session` | Resolve the iTerm2 session currently in focus | Read-only |
82
+ | `read_terminal` | Read bounded visible content from one exact `session_id` | Read-only |
83
+ | `run_command` | Send an explicitly supplied command to one exact `session_id` and optionally wait for completion | Mutating |
84
+
85
+ The normal flow is:
86
+
87
+ 1. Call `get_current_session` or `list_sessions`.
88
+ 2. Use the returned exact `session_id` with `read_terminal` when inspection is useful.
89
+ 3. Call `run_command` only after the user has explicitly requested the command.
90
+ 4. Review the correlated session ID, command, status, exit code, and bounded output.
91
+
92
+ ## Safety behavior
93
+
94
+ - `run_command` requires an exact `session_id`; labels and fuzzy matches are
95
+ never used as a write target.
96
+ - A missing, closed, inaccessible, or ambiguous target fails closed and is
97
+ never redirected to another session.
98
+ - Terminal content, command history, titles, and prior tool output are
99
+ observation data. They are never copied into a command request implicitly.
100
+ - Commands are sent through the existing iTerm2 session with broadcast input
101
+ suppressed where the API supports it. TermPilot does not create a new shell,
102
+ window, or SSH connection.
103
+ - Command execution requires a verified normal shell prompt. Busy or
104
+ interactive sessions are rejected without sending text.
105
+ - A timeout stops waiting for the result; it does not claim that the shell
106
+ command was cancelled.
107
+
108
+ ## Validation
109
+
110
+ Run the automated checks with:
111
+
112
+ ```bash
113
+ uv run pytest
114
+ uv run ruff check src tests
115
+ uv run ruff format --check src tests
116
+ ```
117
+
118
+ The live test is opt-in and only checks a running iTerm2 instance's session
119
+ inventory:
120
+
121
+ ```bash
122
+ TERMPILOT_LIVE_ITERM2=1 uv run pytest tests/integration/test_iterm2_live.py -q
123
+ ```
124
+
125
+ For the complete manual inspect → act → inspect scenarios, see
126
+ [`specs/001-chatgpt-terminal-actions/quickstart.md`](specs/001-chatgpt-terminal-actions/quickstart.md).
127
+
128
+ ## ChatGPT connection
129
+
130
+ ChatGPT cannot use a local stdio process directly. TermPilot can connect the
131
+ same MCP server through [OpenAI Secure MCP Tunnel](https://developers.openai.com/api/docs/guides/secure-mcp-tunnels)
132
+ using the official `tunnel-client` managed runtime.
133
+
134
+ ### 1. Install `tunnel-client`
135
+
136
+ On macOS with Homebrew:
137
+
138
+ ```bash
139
+ brew install openai/tools/tunnel-client
140
+ tunnel-client --version
141
+ ```
142
+
143
+ TermPilot does not bundle or pin `tunnel-client`; it uses the binary available
144
+ on `PATH`.
145
+
146
+ ### 2. Create a tunnel and runtime key
147
+
148
+ Open the OpenAI Platform pages exposed by `tunnel-client help quickstart`:
149
+
150
+ - [Tunnels management](https://platform.openai.com/settings/organization/tunnels):
151
+ create a tunnel and copy its `tunnel_...` ID.
152
+ - [Runtime API keys](https://platform.openai.com/settings/organization/api-keys):
153
+ create the key used by the long-running tunnel runtime. The principal that
154
+ creates/uses it needs **Tunnels Read + Use** for the target tunnel.
155
+ - [Admin API keys](https://platform.openai.com/settings/organization/admin-keys)
156
+ are only needed for tunnel CRUD through `tunnel-client admin ...`; do not use
157
+ an admin key as the long-running runtime key.
158
+
159
+ If you prefer to create the tunnel from the CLI, configure an admin key first
160
+ and use the native `tunnel-client` tunnel-management command (at least one
161
+ organization or workspace scope is required):
162
+
163
+ ```bash
164
+ export OPENAI_ADMIN_KEY="sk-admin-..."
165
+ tunnel-client admin tunnels create \
166
+ --name "termpilot" \
167
+ --description "TermPilot local iTerm2 MCP" \
168
+ --organization-id org_...
169
+ ```
170
+
171
+ You can use `--workspace-id ws_...` instead of or together with
172
+ `--organization-id`. Copy the returned `tunnel_...` ID. Once the tunnel exists,
173
+ TermPilot only needs that ID and a runtime API key; the admin key is no longer
174
+ needed by the runtime.
175
+
176
+ ### 3. Store and load the runtime key
177
+
178
+ The recommended local setup is a repository `.env` file:
179
+
180
+ ```dotenv
181
+ CONTROL_PLANE_API_KEY=sk-...
182
+ ```
183
+
184
+ `.env` is ignored by this repository, but neither TermPilot nor
185
+ `tunnel-client` automatically loads it. Load it into the current shell before
186
+ `setup`, `status`, or `doctor`:
187
+
188
+ ```bash
189
+ set -a
190
+ source .env
191
+ set +a
192
+ ```
193
+
194
+ Alternatively, export it directly:
195
+
196
+ ```bash
197
+ export CONTROL_PLANE_API_KEY="sk-..."
198
+ ```
199
+
200
+ TermPilot stores only the reference `env:CONTROL_PLANE_API_KEY` in the generated
201
+ tunnel profile; it does not put the secret value into the command line or print
202
+ it.
203
+
204
+ ### 4. Start the managed runtime
205
+
206
+ Connect TermPilot to the existing tunnel:
207
+
208
+ ```bash
209
+ uv run termpilot chatgpt setup --tunnel-id tunnel_0123456789abcdef0123456789abcdef
210
+ ```
211
+
212
+ `setup` launches a long-running managed `tunnel-client` process, which in turn
213
+ starts this checkout's `python -m termpilot.main` stdio MCP server. A healthy
214
+ setup reports `Process running: yes`, `Healthy: yes`, and `Ready: yes`.
215
+
216
+ Inspect or troubleshoot the connection with:
217
+
218
+ ```bash
219
+ uv run termpilot chatgpt status
220
+ uv run termpilot chatgpt doctor
221
+ ```
222
+
223
+ After a reboot or after stopping the managed runtime, load `.env` again and run
224
+ the same `setup --tunnel-id ...` command. The existing tunnel is reused.
225
+
226
+ To stop the local runtime without deleting the remote tunnel:
227
+
228
+ ```bash
229
+ uv run termpilot chatgpt disconnect
230
+ ```
231
+
232
+ ### 5. Add TermPilot to ChatGPT Classic
233
+
234
+ After `setup` succeeds:
235
+
236
+ 1. Open **ChatGPT Settings → Plugins** (or
237
+ [ChatGPT Plugins](https://chatgpt.com/plugins)).
238
+ 2. Create a new developer-mode plugin/app, for example named `termpilot`.
239
+ 3. Under **Connection**, choose **Tunnel**, not **Server URL**.
240
+ 4. Select the tunnel or paste its `tunnel_id`.
241
+ 5. Save the plugin and allow the TermPilot tools you want ChatGPT to use.
242
+
243
+ The tunnel runtime must remain running while ChatGPT discovers or calls the MCP
244
+ tools. You do not need OAuth for the local TermPilot MCP server when using the
245
+ Secure MCP Tunnel connection.
246
+
247
+ ## Frequently Asked Questions
248
+
249
+ ### `tunnel-client is not installed or is not available on PATH`
250
+
251
+ Install the supported client and verify it is visible:
252
+
253
+ ```bash
254
+ brew install openai/tools/tunnel-client
255
+ which tunnel-client
256
+ tunnel-client --version
257
+ ```
258
+
259
+ ### I put `CONTROL_PLANE_API_KEY` in `.env`, but TermPilot says it is missing
260
+
261
+ Creating `.env` does not export its variables. Load it into each shell/process
262
+ that starts or diagnoses the tunnel runtime:
263
+
264
+ ```bash
265
+ set -a
266
+ source .env
267
+ set +a
268
+ uv run termpilot chatgpt setup --tunnel-id tunnel_...
269
+ ```
270
+
271
+ ### What is the difference between `CONTROL_PLANE_API_KEY` and `OPENAI_ADMIN_KEY`?
272
+
273
+ `CONTROL_PLANE_API_KEY` is the runtime key used by the long-running tunnel
274
+ daemon. It needs **Tunnels Read + Use**. `OPENAI_ADMIN_KEY` is for administrative
275
+ tunnel CRUD such as `tunnel-client admin tunnels create`; the TermPilot runtime
276
+ does not need it when attaching to an existing tunnel.
277
+
278
+ ### Why does a manual `tunnel-client runtimes connect` complain about a missing key?
279
+
280
+ The generated profile contains an environment reference such as
281
+ `env:CONTROL_PLANE_API_KEY`. The process starting that profile must therefore
282
+ have the variable exported. Prefer `uv run termpilot chatgpt setup ...`, which
283
+ supplies the correct runtime-key reference and MCP command consistently.
284
+
285
+ ### ChatGPT sends a command, but TermPilot says `iTerm2 is not running or its Python API is disabled`
286
+
287
+ First verify iTerm2 itself:
288
+
289
+ 1. Open **iTerm2 → Settings → General → Magic**.
290
+ 2. Enable **Python API**.
291
+ 3. Set it to **Allow all apps to connect** (or explicitly allow the process that
292
+ runs TermPilot).
293
+
294
+ Then test the iTerm2 API directly from the TermPilot environment:
295
+
296
+ ```bash
297
+ uv run python - <<'PY'
298
+ import asyncio
299
+ import iterm2
300
+
301
+ async def main():
302
+ connection = await iterm2.Connection.async_create()
303
+ app = await iterm2.async_get_app(connection)
304
+ print([s.session_id for w in app.windows for t in w.tabs for s in t.sessions])
305
+
306
+ asyncio.run(main())
307
+ PY
308
+ ```
309
+
310
+ If this prints session IDs, the iTerm2 API is working and the problem is in the
311
+ TermPilot/iTerm2 boundary rather than the ChatGPT tunnel.
312
+
313
+ ### Why did an older TermPilot build fail even though the direct iTerm2 test worked?
314
+
315
+ An earlier adapter called `iterm2.async_get_app(..., create_if_needed=False)`.
316
+ With iTerm2 3.6.x this can return `None` even while iTerm2 is already running.
317
+ TermPilot now allows the SDK to create its `App` wrapper, matching the working
318
+ `iterm2.async_get_app(connection)` call.
319
+
320
+ ### The tunnel log says `dispatcher forwarded command to MCP server`, but ChatGPT still gets an iTerm2 error
321
+
322
+ That log line proves the path **ChatGPT → Secure MCP Tunnel → TermPilot MCP** is
323
+ working. Debug the local **TermPilot → iTerm2 Python API** boundary next instead
324
+ of recreating the ChatGPT plugin or tunnel.
325
+
326
+ ### ChatGPT's plugin dialog shows `Server URL` and `Tunnel`. Which one should I use?
327
+
328
+ Choose **Tunnel** and select/paste the `tunnel_id`. `Server URL` is for a
329
+ network-reachable HTTP/SSE MCP server and is not the TermPilot setup described
330
+ here.
331
+
332
+ ### `Codex detected without Tunnel MCP plugin` appears in the tunnel-client log
333
+
334
+ This message is about optional Codex integration. It does not prevent the
335
+ ChatGPT Classic developer-mode plugin from using the TermPilot tunnel.
336
+
337
+ ### How do I know which layer is broken?
338
+
339
+ Use this order:
340
+
341
+ 1. `uv run termpilot chatgpt status` → runtime should be running, healthy, and ready.
342
+ 2. Tunnel log contains `dispatcher forwarded command to MCP server` → ChatGPT to
343
+ TermPilot transport is working.
344
+ 3. Run the direct iTerm2 Python snippet above → local iTerm2 API is working.
345
+ 4. Finally test `list_sessions` from the ChatGPT TermPilot plugin.