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.
- termpilot_plugin-0.1.0/.gitignore +231 -0
- termpilot_plugin-0.1.0/.python-version +1 -0
- termpilot_plugin-0.1.0/LICENSE +21 -0
- termpilot_plugin-0.1.0/PKG-INFO +345 -0
- termpilot_plugin-0.1.0/README.md +332 -0
- termpilot_plugin-0.1.0/README.zh-CN.md +310 -0
- termpilot_plugin-0.1.0/docs/superpowers/plans/2026-09-16-chatgpt-tunnel.md +105 -0
- termpilot_plugin-0.1.0/docs/superpowers/specs/2026-09-16-chatgpt-tunnel-design.md +102 -0
- termpilot_plugin-0.1.0/pyproject.toml +43 -0
- termpilot_plugin-0.1.0/specs/001-chatgpt-terminal-actions/checklists/requirements.md +35 -0
- termpilot_plugin-0.1.0/specs/001-chatgpt-terminal-actions/contracts/mcp-tools.md +152 -0
- termpilot_plugin-0.1.0/specs/001-chatgpt-terminal-actions/data-model.md +110 -0
- termpilot_plugin-0.1.0/specs/001-chatgpt-terminal-actions/plan.md +81 -0
- termpilot_plugin-0.1.0/specs/001-chatgpt-terminal-actions/quickstart.md +110 -0
- termpilot_plugin-0.1.0/specs/001-chatgpt-terminal-actions/research.md +81 -0
- termpilot_plugin-0.1.0/specs/001-chatgpt-terminal-actions/spec.md +109 -0
- termpilot_plugin-0.1.0/specs/001-chatgpt-terminal-actions/tasks.md +211 -0
- termpilot_plugin-0.1.0/src/termpilot/__init__.py +3 -0
- termpilot_plugin-0.1.0/src/termpilot/adapters/__init__.py +1 -0
- termpilot_plugin-0.1.0/src/termpilot/adapters/iterm2.py +589 -0
- termpilot_plugin-0.1.0/src/termpilot/chatgpt.py +342 -0
- termpilot_plugin-0.1.0/src/termpilot/errors.py +68 -0
- termpilot_plugin-0.1.0/src/termpilot/main.py +37 -0
- termpilot_plugin-0.1.0/src/termpilot/mcp_server.py +153 -0
- termpilot_plugin-0.1.0/src/termpilot/models.py +203 -0
- termpilot_plugin-0.1.0/src/termpilot/services/__init__.py +1 -0
- termpilot_plugin-0.1.0/src/termpilot/services/commands.py +118 -0
- termpilot_plugin-0.1.0/src/termpilot/services/sessions.py +131 -0
- termpilot_plugin-0.1.0/tests/contract/test_mcp_tools.py +142 -0
- termpilot_plugin-0.1.0/tests/integration/test_iterm2_live.py +21 -0
- termpilot_plugin-0.1.0/tests/unit/test_chatgpt.py +283 -0
- termpilot_plugin-0.1.0/tests/unit/test_commands.py +172 -0
- termpilot_plugin-0.1.0/tests/unit/test_errors.py +27 -0
- termpilot_plugin-0.1.0/tests/unit/test_iterm2_adapter.py +406 -0
- termpilot_plugin-0.1.0/tests/unit/test_main.py +58 -0
- termpilot_plugin-0.1.0/tests/unit/test_models.py +102 -0
- termpilot_plugin-0.1.0/tests/unit/test_sessions.py +121 -0
- 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.
|