ot-cli 0.2.1__py3-none-any.whl
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.
- openterminal/__init__.py +3 -0
- openterminal/agent/__init__.py +39 -0
- openterminal/agent/backend.py +180 -0
- openterminal/agent/build.py +115 -0
- openterminal/agent/middleware.py +111 -0
- openterminal/agent/model.py +41 -0
- openterminal/agent/prompt.py +63 -0
- openterminal/agent/runner.py +340 -0
- openterminal/agent/sysprobe.py +164 -0
- openterminal/agent/token_est.py +49 -0
- openterminal/app.py +52 -0
- openterminal/approval.py +7 -0
- openterminal/backend.py +3 -0
- openterminal/banner.py +7 -0
- openterminal/cli.py +426 -0
- openterminal/cmdset.py +7 -0
- openterminal/config.py +322 -0
- openterminal/connections/__init__.py +20 -0
- openterminal/connections/db.py +240 -0
- openterminal/connections/models.py +289 -0
- openterminal/connections/secrets.py +68 -0
- openterminal/connections_db.py +6 -0
- openterminal/core/__init__.py +41 -0
- openterminal/core/ai_flow.py +781 -0
- openterminal/core/auth.py +499 -0
- openterminal/core/cmdset_runner.py +352 -0
- openterminal/core/exec_events.py +650 -0
- openterminal/core/history.py +79 -0
- openterminal/core/hook_gate.py +415 -0
- openterminal/core/messages.py +193 -0
- openterminal/core/nested_ssh.py +178 -0
- openterminal/core/pipeline.py +1187 -0
- openterminal/core/pump.py +190 -0
- openterminal/core/transcript.py +26 -0
- openterminal/frontend/__init__.py +21 -0
- openterminal/frontend/approval.py +40 -0
- openterminal/frontend/banner.py +40 -0
- openterminal/frontend/headless.py +290 -0
- openterminal/frontend/render.py +41 -0
- openterminal/frontend/taskview.py +184 -0
- openterminal/frontend/term.py +1417 -0
- openterminal/headless.py +7 -0
- openterminal/history_db.py +7 -0
- openterminal/local_pty.py +3 -0
- openterminal/local_win.py +3 -0
- openterminal/policy.py +362 -0
- openterminal/render.py +7 -0
- openterminal/secrets_store.py +5 -0
- openterminal/session/__init__.py +17 -0
- openterminal/session/base.py +328 -0
- openterminal/session/local_posix.py +184 -0
- openterminal/session/local_win.py +173 -0
- openterminal/session/ssh.py +321 -0
- openterminal/shell/__init__.py +17 -0
- openterminal/shell/cmdset.py +49 -0
- openterminal/shell/integration.py +1298 -0
- openterminal/shell_integration.py +7 -0
- openterminal/shell_session.py +8 -0
- openterminal/skills/__init__.py +45 -0
- openterminal/skills/markdown-tables/SKILL.md +247 -0
- openterminal/ssh_pty.py +6 -0
- openterminal/sysprobe.py +7 -0
- openterminal/taskview.py +7 -0
- openterminal/term_frontend.py +7 -0
- openterminal/token_est.py +3 -0
- openterminal/transcript.py +7 -0
- openterminal/web/__init__.py +1 -0
- openterminal/web/files_api.py +789 -0
- openterminal/web/frontend/index.html +37 -0
- openterminal/web/frontend/static/app.js +3632 -0
- openterminal/web/frontend/static/block_stream.js +274 -0
- openterminal/web/frontend/static/block_view.js +383 -0
- openterminal/web/frontend/static/files_panel.js +782 -0
- openterminal/web/frontend/static/md_lite.js +127 -0
- openterminal/web/frontend/static/modal.js +122 -0
- openterminal/web/frontend/static/pad_ledger.js +46 -0
- openterminal/web/frontend/static/style.css +915 -0
- openterminal/web/frontend/static/ui/ui.css +1 -0
- openterminal/web/frontend/static/ui/ui.js +6292 -0
- openterminal/web/frontend/vendor/addon-fit.js +2 -0
- openterminal/web/frontend/vendor/xterm.css +8 -0
- openterminal/web/frontend/vendor/xterm.js +8 -0
- openterminal/web/protocol.py +41 -0
- openterminal/web/server.py +322 -0
- openterminal/web/worker.py +62 -0
- ot_cli-0.2.1.dist-info/METADATA +439 -0
- ot_cli-0.2.1.dist-info/RECORD +91 -0
- ot_cli-0.2.1.dist-info/WHEEL +5 -0
- ot_cli-0.2.1.dist-info/entry_points.txt +3 -0
- ot_cli-0.2.1.dist-info/licenses/LICENSE +202 -0
- ot_cli-0.2.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ot-cli
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: Natural-language terminal agent over local or SSH shells
|
|
5
|
+
License-Expression: Apache-2.0
|
|
6
|
+
Classifier: Development Status :: 3 - Alpha
|
|
7
|
+
Classifier: Environment :: Console
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: Intended Audience :: System Administrators
|
|
10
|
+
Classifier: Operating System :: MacOS
|
|
11
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
12
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Topic :: System :: Shells
|
|
17
|
+
Classifier: Topic :: System :: Systems Administration
|
|
18
|
+
Requires-Python: >=3.11
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: deepagents>=0.7.15
|
|
22
|
+
Requires-Dist: langchain-anthropic>=1.7.2
|
|
23
|
+
Requires-Dist: langchain-openai>=1.0
|
|
24
|
+
Requires-Dist: asyncssh>=2.24.0
|
|
25
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
26
|
+
Requires-Dist: prompt_toolkit>=3.0.0
|
|
27
|
+
Requires-Dist: rich>=13.0.0
|
|
28
|
+
Requires-Dist: socksio>=1.0.0
|
|
29
|
+
Requires-Dist: pywinpty>=2.2; sys_platform == "win32"
|
|
30
|
+
Requires-Dist: keyring>=25.0.0
|
|
31
|
+
Requires-Dist: fastapi>=0.110
|
|
32
|
+
Requires-Dist: uvicorn>=0.27
|
|
33
|
+
Requires-Dist: tiktoken>=0.7
|
|
34
|
+
Provides-Extra: dev
|
|
35
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
36
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == "dev"
|
|
37
|
+
Requires-Dist: httpx>=0.27; extra == "dev"
|
|
38
|
+
Dynamic: license-file
|
|
39
|
+
|
|
40
|
+
# OpenTerminal
|
|
41
|
+
|
|
42
|
+
[](https://github.com/jeean404/OpenTerminal/actions/workflows/ci.yml)
|
|
43
|
+
[](LICENSE)
|
|
44
|
+
[中文文档](README.zh-CN.md)
|
|
45
|
+
|
|
46
|
+
**Tell your terminal what you want in plain language — it does the work.**
|
|
47
|
+
|
|
48
|
+
OpenTerminal lives in your terminal (and your browser). Type a normal sentence
|
|
49
|
+
like *"check which files are eating the most space"* and it figures out the
|
|
50
|
+
right commands, runs them, and sums up the results. Plain commands still work
|
|
51
|
+
exactly as you'd expect. It can hop to your remote servers over SSH too, so
|
|
52
|
+
you can leave Xshell behind.
|
|
53
|
+
|
|
54
|
+
<div align="center">
|
|
55
|
+
<img src="docs/images/demo-terminal.gif" width="860" alt="OpenTerminal terminal demo: plain command → natural language task → streaming analysis → approval → execution → summary"><br><br>
|
|
56
|
+
<img src="docs/images/demo-web.gif" width="860" alt="OpenTerminal demo: natural language task → analysis → approval → execution → summary">
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
Both GIFs show the same flow — the first in the plain terminal (`ot`), the
|
|
60
|
+
second in the browser (`ot web`). Here's what happens:
|
|
61
|
+
|
|
62
|
+
1. A plain command first — `ls /tmp/ot-demo` runs in your real shell,
|
|
63
|
+
colored output and all;
|
|
64
|
+
2. then a plain-language task: *"看看 /tmp/ot-demo 下哪些目录最占空间,
|
|
65
|
+
给我一张表"*;
|
|
66
|
+
3. the AI streams its **thinking** and **analysis** — what it's about to
|
|
67
|
+
do and why;
|
|
68
|
+
4. an **approval panel** pops up with the exact command — nothing runs
|
|
69
|
+
without your OK;
|
|
70
|
+
5. you hit **执行 (Execute)** — the command runs in the same terminal,
|
|
71
|
+
output shown verbatim;
|
|
72
|
+
6. a **summary** card wraps up, its markdown table built from the real
|
|
73
|
+
output.
|
|
74
|
+
|
|
75
|
+
> ⚠️ **Beta**: OpenTerminal runs real commands on real machines — including
|
|
76
|
+
> production servers. Every state-changing command needs your approval (see
|
|
77
|
+
> [Security model](#security-model)), but please still read what you're approving.
|
|
78
|
+
> Security-sensitive disclosures: [SECURITY.md](SECURITY.md).
|
|
79
|
+
|
|
80
|
+
## Why you might like it
|
|
81
|
+
|
|
82
|
+
- **Say it, don't grep it.** Natural language in, correct commands out — with
|
|
83
|
+
the analysis shown before anything runs.
|
|
84
|
+
- **It knows your systems.** Ubuntu→apt, CentOS 7→yum, Rocky/Fedora→dnf,
|
|
85
|
+
Alpine→apk, macOS→brew. Same request, right dialect for each machine.
|
|
86
|
+
- **It doesn't get sloppy.** Read-only commands run by themselves; anything
|
|
87
|
+
destructive waits for your approval; catastrophic commands (`rm -rf /`,
|
|
88
|
+
fork bombs, …) are refused outright. Failed attempts get a bounded
|
|
89
|
+
self-correction budget (10 turns), not infinite retry loops.
|
|
90
|
+
- **Your shell stays yours.** Persistent session (`cd`, exports, venv all
|
|
91
|
+
survive), and full-screen programs like vim/top/tmux take over the
|
|
92
|
+
terminal natively — the pipeline is a true PTY passthrough, so
|
|
93
|
+
they just work inline.
|
|
94
|
+
- **Two ways in.** A single-pipeline terminal (`ot`), or `ot web` — a
|
|
95
|
+
browser UI with a server sidebar and multi-tab terminals, sharing the
|
|
96
|
+
same core.
|
|
97
|
+
|
|
98
|
+
## Features
|
|
99
|
+
|
|
100
|
+
**Natural-language tasks**
|
|
101
|
+
|
|
102
|
+
- Streaming **thinking → analysis → approval → execution → summary** for
|
|
103
|
+
every task; summaries are markdown built from the real command output
|
|
104
|
+
- Plain commands and plain sentences share one input line — `!` forces a
|
|
105
|
+
command, `?` forces a task; Ctrl+C interrupts a running task
|
|
106
|
+
- Slash commands: `/help` list them · `/target` switch host · `/system`
|
|
107
|
+
override dialect · `/clear` new task · `/model` show model · `/exit` quit
|
|
108
|
+
- Bounded self-correction: a failed command is retried at most once,
|
|
109
|
+
tool-call turns are budget-capped
|
|
110
|
+
- Session token usage on the status line — exact when the gateway reports
|
|
111
|
+
it, a local tiktoken estimate otherwise
|
|
112
|
+
|
|
113
|
+
**Shells & systems**
|
|
114
|
+
|
|
115
|
+
- Persistent sessions — `cd`, exports, and venvs survive across tasks
|
|
116
|
+
- Full-screen programs (vim / top / tmux) run inline via true PTY
|
|
117
|
+
passthrough
|
|
118
|
+
- Dialects auto-detected: Ubuntu/Debian→apt, CentOS 7→yum, Rocky/Fedora→dnf,
|
|
119
|
+
Alpine→apk, Arch→pacman, macOS→brew — with `/system` manual override
|
|
120
|
+
- Local shells on macOS & Linux (PTY) and Windows (ConPTY + PowerShell)
|
|
121
|
+
|
|
122
|
+
**Agent skills**
|
|
123
|
+
|
|
124
|
+
- Skill packs ship inside the package (e.g. `markdown-tables`); their
|
|
125
|
+
`SKILL.md` files are mounted at `/skills/` and the model pulls one in
|
|
126
|
+
only when the task calls for it
|
|
127
|
+
|
|
128
|
+
**SSH & connections**
|
|
129
|
+
|
|
130
|
+
- Hosts from `~/.ssh/config`, ad-hoc `user@host:port`, or remembered
|
|
131
|
+
connections
|
|
132
|
+
- Password and key auth; passwords live in the OS keyring, never in
|
|
133
|
+
plaintext files
|
|
134
|
+
- Host-key TOFU confirmation; per-target history is replayed into the
|
|
135
|
+
shell on reconnect
|
|
136
|
+
- **Post-connect command sets** — per-target lists that run on login
|
|
137
|
+
(jump-host hops, `su - deploy`, …). An inline `> @name=secret` line is
|
|
138
|
+
stripped into the keyring on save and answered by reference; anything
|
|
139
|
+
else is asked in a dialog. Progress is shown per line, and on the web a
|
|
140
|
+
line stuck waiting for input can be resumed by hand.
|
|
141
|
+
- A password you type by hand is offered for remembering once it verifies;
|
|
142
|
+
nested `ssh` / `sudo` / `su` prompts then fill themselves in from the
|
|
143
|
+
keyring
|
|
144
|
+
|
|
145
|
+
**Safety** — details in [Security model](#security-model)
|
|
146
|
+
|
|
147
|
+
- Three-tier command policy (auto / approve / deny) enforced as agent
|
|
148
|
+
middleware, plus custom exact-match rules (`auto_extra` / `approve_extra`
|
|
149
|
+
/ `deny_extra`)
|
|
150
|
+
- Every input, command, approval, and summary lands in a JSONL transcript
|
|
151
|
+
under `~/.openterminal/sessions/`
|
|
152
|
+
|
|
153
|
+
**Web UI** (`ot web`)
|
|
154
|
+
|
|
155
|
+
- Server sidebar + multi-tab terminals; Agent view and plain Shell view
|
|
156
|
+
per tab, each tab showing its own connection status
|
|
157
|
+
- Browser-dialog password and host-key entry; optional LAN access with a
|
|
158
|
+
mandatory token for non-loopback binds
|
|
159
|
+
|
|
160
|
+
**Headless / scripting**
|
|
161
|
+
|
|
162
|
+
- `ot exec -t <target> -c "<cmd>" --output json` — one policy-gated
|
|
163
|
+
command per invocation, with machine-readable output and exit codes
|
|
164
|
+
(`APPROVAL_REQUIRED` / `DENIED` / `HOST_KEY_UNKNOWN`), safe to wire
|
|
165
|
+
into cron or CI
|
|
166
|
+
- `ot list targets --output json`
|
|
167
|
+
|
|
168
|
+
**Model gateways**
|
|
169
|
+
|
|
170
|
+
- Any Anthropic-compatible or OpenAI-compatible gateway — protocol, base
|
|
171
|
+
URL, model, and key env var all configurable
|
|
172
|
+
- The web model picker reads its options from `[model] models`
|
|
173
|
+
|
|
174
|
+
## Installation
|
|
175
|
+
|
|
176
|
+
Pick whichever suits you — a pre-built binary (no Python needed), a package
|
|
177
|
+
manager, or from source.
|
|
178
|
+
|
|
179
|
+
### Pre-built binaries (macOS & Windows)
|
|
180
|
+
|
|
181
|
+
Download from [Releases](https://github.com/jeean404/OpenTerminal/releases),
|
|
182
|
+
unpack, and put the `ot` executable on your `PATH`. Replace `vX.Y.Z` with the
|
|
183
|
+
latest tag:
|
|
184
|
+
|
|
185
|
+
| Platform | Asset |
|
|
186
|
+
|---|---|
|
|
187
|
+
| macOS (Apple Silicon) | `ot-vX.Y.Z-macos-arm64.tar.gz` |
|
|
188
|
+
| macOS (Intel) | `ot-vX.Y.Z-macos-x86_64.tar.gz` |
|
|
189
|
+
| Windows (x64) | `ot-vX.Y.Z-windows-x64.zip` |
|
|
190
|
+
|
|
191
|
+
```sh
|
|
192
|
+
# macOS
|
|
193
|
+
tar -xzf ot-vX.Y.Z-macos-arm64.tar.gz
|
|
194
|
+
sudo mv ot/ot /usr/local/bin/ot # or any directory on your PATH
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
On Windows, unzip and add the `ot` folder to your `PATH`, then run `ot.exe`.
|
|
198
|
+
|
|
199
|
+
> **Intel Macs**: GitHub retired its Intel macOS runners, so the x86_64 binary
|
|
200
|
+
> is built and attached manually per release. If a release is missing it,
|
|
201
|
+
> install from PyPI with `pipx install ot-cli` (see below), which works
|
|
202
|
+
> on both architectures.
|
|
203
|
+
|
|
204
|
+
> **macOS Gatekeeper** — the binaries are ad-hoc signed (not notarized), so
|
|
205
|
+
> the first launch may be blocked with *“ot cannot be opened.”* Clear the
|
|
206
|
+
> quarantine flag once with `xattr -cr "$(command -v ot)"`, or right-click →
|
|
207
|
+
> Open. The first run also triggers a one-time OS security scan; later runs
|
|
208
|
+
> start in under a second (the AI stack is loaded lazily on the first AI
|
|
209
|
+
> task, which takes a few extra seconds once).
|
|
210
|
+
|
|
211
|
+
> **Windows SmartScreen** — you may see *“Windows protected your PC”* (the
|
|
212
|
+
> binary is unsigned). Click **More info → Run anyway**.
|
|
213
|
+
|
|
214
|
+
### pipx / uv (from PyPI)
|
|
215
|
+
|
|
216
|
+
The Python package is `ot-cli` (it installs the `ot` command; the importable
|
|
217
|
+
module remains `openterminal`):
|
|
218
|
+
|
|
219
|
+
```sh
|
|
220
|
+
pipx install ot-cli
|
|
221
|
+
# or
|
|
222
|
+
uv tool install ot-cli
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
> Previously published as `open-terminal-agent` (0.1.0 only) — that name was
|
|
226
|
+
> retired in favor of `ot-cli`. Use `pipx install ot-cli` for current releases.
|
|
227
|
+
|
|
228
|
+
Requires Python 3.11+.
|
|
229
|
+
|
|
230
|
+
### From source (development)
|
|
231
|
+
|
|
232
|
+
```sh
|
|
233
|
+
conda create -n openterminal python=3.12 -y # or any virtualenv
|
|
234
|
+
conda activate openterminal
|
|
235
|
+
pip install -e .
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Then set up your model gateway (see below) and run it:
|
|
239
|
+
|
|
240
|
+
```sh
|
|
241
|
+
mkdir -p ~/.openterminal && cp .env.example ~/.openterminal/.env
|
|
242
|
+
# edit ~/.openterminal/.env — point it at a model gateway
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## Quick start
|
|
246
|
+
|
|
247
|
+
```sh
|
|
248
|
+
ot # terminal — local shell + main menu (connect / manage hosts)
|
|
249
|
+
ot web # browser terminal — server sidebar + multi-tab UI (default http://127.0.0.1:8080)
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
More ways to connect:
|
|
253
|
+
|
|
254
|
+
```sh
|
|
255
|
+
ot connect prod-web # a host from ~/.ssh/config
|
|
256
|
+
ot ssh deploy@host:2222 # or user@host:port directly
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## Model gateway configuration
|
|
260
|
+
|
|
261
|
+
OpenTerminal talks to any Anthropic-compatible gateway. On startup it loads
|
|
262
|
+
`.env` from the current directory, then `~/.openterminal/.env`, then
|
|
263
|
+
`~/.openterminal/config.toml`. **None of these are required** — defaults:
|
|
264
|
+
|
|
265
|
+
- Protocol: `anthropic` (default) or `openai` (OpenAI-compatible protocol)
|
|
266
|
+
- Gateway: `http://127.0.0.1:15721` (Anthropic-compatible protocol; the `openai`
|
|
267
|
+
provider defaults to the official endpoint)
|
|
268
|
+
- Model: `claude-sonnet-4-6` (`gpt-5` under the `openai` provider)
|
|
269
|
+
- API key: from the `ANTHROPIC_API_KEY` environment variable
|
|
270
|
+
(`OPENAI_API_KEY` under the `openai` provider)
|
|
271
|
+
|
|
272
|
+
Optional `~/.openterminal/config.toml` (every field may be omitted):
|
|
273
|
+
|
|
274
|
+
```toml
|
|
275
|
+
[model]
|
|
276
|
+
provider = "anthropic" # anthropic | openai (OpenAI-compatible protocol)
|
|
277
|
+
base_url = "http://127.0.0.1:15721"
|
|
278
|
+
model = "claude-sonnet-4-6"
|
|
279
|
+
api_key_env = "ANTHROPIC_API_KEY"
|
|
280
|
+
models = ["claude-sonnet-4-6", "claude-opus-4-6", "claude-haiku-4-5"] # web model picker
|
|
281
|
+
|
|
282
|
+
# OpenAI-compatible protocol example (any OpenAI-compatible gateway works;
|
|
283
|
+
# the defaults of the fields above switch to an openai set with the
|
|
284
|
+
# provider — explicit fields always win):
|
|
285
|
+
# provider = "openai"
|
|
286
|
+
# base_url = "https://api.openai.com/v1"
|
|
287
|
+
# model = "gpt-5"
|
|
288
|
+
# api_key_env = "OPENAI_API_KEY"
|
|
289
|
+
|
|
290
|
+
[shell]
|
|
291
|
+
timeout_default = 120 # per-command wait budget (seconds)
|
|
292
|
+
max_output_bytes = 102400 # per-command output truncation
|
|
293
|
+
max_tool_turns = 10 # agent tool-call turn budget
|
|
294
|
+
|
|
295
|
+
[policy]
|
|
296
|
+
mode = "tiered" # tiered | approve-all | deny-all
|
|
297
|
+
# auto_extra / approve_extra / deny_extra append exact-match custom rules
|
|
298
|
+
|
|
299
|
+
[target.prod-web]
|
|
300
|
+
mode = "ssh"
|
|
301
|
+
host = "prod-web.example.com"
|
|
302
|
+
user = "deploy"
|
|
303
|
+
# Post-connect command set — runs line by line right after login.
|
|
304
|
+
# A "> @name=secret" line stores the secret in the keyring and is kept as
|
|
305
|
+
# "> @name"; the password box then answers from that reference.
|
|
306
|
+
commands = [
|
|
307
|
+
"su - deploy",
|
|
308
|
+
"> @deploy_pw",
|
|
309
|
+
]
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
Config directory can be moved with `OPENTERMINAL_HOME` (the test suite uses
|
|
313
|
+
this for isolation). Transcripts go to `~/.openterminal/sessions/`; host
|
|
314
|
+
profiles are cached in `~/.openterminal/hosts.toml`.
|
|
315
|
+
|
|
316
|
+
## Day-to-day use
|
|
317
|
+
|
|
318
|
+
**In the terminal** — just type. A normal sentence becomes a task; a plain
|
|
319
|
+
command runs in your real shell. `!` forces a command, `?` forces a task.
|
|
320
|
+
`/help` lists the slash commands: `/target` switch host, `/system` override
|
|
321
|
+
the detected dialect, `/clear` new task, `/model` show model, `/exit` quit.
|
|
322
|
+
Ctrl+C interrupts a running task and returns you to the prompt.
|
|
323
|
+
|
|
324
|
+
**Post-connect command sets** run themselves the moment a target is up —
|
|
325
|
+
handy for jump hosts and `su -` switches. Keep a password out of the file by
|
|
326
|
+
writing `> @name=secret` once; the stored line is just `> @name` and the
|
|
327
|
+
prompt is answered from the keyring. If a line stops to ask for something,
|
|
328
|
+
the web status bar offers a Resume button instead of leaving you stuck
|
|
329
|
+
(the terminal CLI has no resume entry yet).
|
|
330
|
+
|
|
331
|
+
**In the browser** (`ot web`) — pick a server from the sidebar (local or
|
|
332
|
+
direct SSH), open as many tabs as you like, and work in the Agent view or
|
|
333
|
+
plain Shell view. Passwords and host keys are entered in browser dialogs;
|
|
334
|
+
remembered passwords go through the OS credential store, so reconnecting is
|
|
335
|
+
password-free.
|
|
336
|
+
|
|
337
|
+
**LAN access**: `ot web --host 0.0.0.0 --token <TOKEN>` — a token is
|
|
338
|
+
mandatory for non-loopback binds. Config: `[web] host/port/token` in
|
|
339
|
+
`config.toml`.
|
|
340
|
+
|
|
341
|
+
**In scripts**: `ot exec` runs one command against a target behind the
|
|
342
|
+
same policy gate and speaks JSON — read-only commands execute; anything
|
|
343
|
+
needing approval exits with `APPROVAL_REQUIRED` (67) instead of running,
|
|
344
|
+
and refused commands exit with `DENIED` (66), so automation can react
|
|
345
|
+
instead of guessing:
|
|
346
|
+
|
|
347
|
+
```sh
|
|
348
|
+
ot exec -t prod-web -c "df -h /" --output json
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
## Security model
|
|
352
|
+
|
|
353
|
+
The rule of thumb: **looking is free; changing things needs your OK; disasters
|
|
354
|
+
are refused.**
|
|
355
|
+
|
|
356
|
+
Three tiers (`src/openterminal/policy.py`):
|
|
357
|
+
|
|
358
|
+
- **auto** — read-only stuff runs on its own: `ls`/`cat`/`df`/`ps`,
|
|
359
|
+
`git status/log`, `find` without `-exec/-delete`, …
|
|
360
|
+
- **approve** — anything that modifies the system: deleting files, writing
|
|
361
|
+
files, installing packages, `systemctl`, `sudo`, network config, plus
|
|
362
|
+
anything the static rules can't confidently judge. A panel opens:
|
|
363
|
+
`y` execute / `e` edit / `n` reject / `a` allow for this session (in the
|
|
364
|
+
web UI it's a button, with a second confirm for high-risk commands).
|
|
365
|
+
- **deny** — the catastrophic stuff is refused and the reason is fed back to
|
|
366
|
+
the model so it changes course: `rm -rf /`, mkfs, `dd` to block devices,
|
|
367
|
+
fork bombs, shutdown, redirects to /dev devices, …
|
|
368
|
+
|
|
369
|
+
Rejected and failed commands aren't retried verbatim: the agent is prompted
|
|
370
|
+
to retry a failure at most once and never re-skin a rejected command, and
|
|
371
|
+
tool-call turns are budget-bounded.
|
|
372
|
+
|
|
373
|
+
## Architecture
|
|
374
|
+
|
|
375
|
+
<div align="center">
|
|
376
|
+
<img src="docs/images/architecture.svg" width="860" alt="OpenTerminal architecture">
|
|
377
|
+
</div>
|
|
378
|
+
|
|
379
|
+
Full docs: [Architecture](docs/architecture.md) · [Design notes](docs/design.md) ·
|
|
380
|
+
[Flow diagrams](docs/flows.md)
|
|
381
|
+
|
|
382
|
+
Three layers: the natural-language front end (terminal or web), a
|
|
383
|
+
deepagents (LangGraph) agent, and persistent shell sessions. Natural
|
|
384
|
+
language goes straight to the agent for multi-turn tool use; command
|
|
385
|
+
execution is a sub-capability of the agent, over the same session. The
|
|
386
|
+
tiered policy is enforced as agent middleware, so the approval gate can't
|
|
387
|
+
be bypassed by prompt content. Built-in skills are mounted as files under
|
|
388
|
+
`/skills/`, so the agent loads them the same way it loads any other
|
|
389
|
+
reference file.
|
|
390
|
+
|
|
391
|
+
Display is a **single pipeline**: PTY bytes go straight to the terminal,
|
|
392
|
+
shell-integration hooks keep the books via in-band OSC markers (history /
|
|
393
|
+
exit codes / AI context), and agent tool commands are injected into the
|
|
394
|
+
same PTY via `__ot_exec__`, so their output lands in place. A per-session
|
|
395
|
+
lock keeps commands strictly serial — one PTY is one interactive shell, so
|
|
396
|
+
concurrent agent `execute` calls queue up. BEGIN/END sentinel slicing
|
|
397
|
+
(`session/base.py`) remains only for non-display paths such as headless
|
|
398
|
+
`ot exec` and capability probes.
|
|
399
|
+
|
|
400
|
+
There is **no model-level long-term memory** — the agent's checkpointer is
|
|
401
|
+
in-memory and cleared on restart. What persists is factual state:
|
|
402
|
+
|
|
403
|
+
| Store | Location | Contents |
|
|
404
|
+
|---|---|---|
|
|
405
|
+
| Config | `~/.openterminal/config.toml` | gateway / shell budgets / policy / targets |
|
|
406
|
+
| Connections | `~/.openterminal/connections.db` | remembered connections and their command sets |
|
|
407
|
+
| Command history | `~/.openterminal/history.db` | per-target history, replayed into the shell on reconnect |
|
|
408
|
+
| Passwords | OS credential store (keyring) | connection passwords and command-set secrets — never written to plaintext files |
|
|
409
|
+
| Host profiles | `~/.openterminal/hosts.toml` | per-host system profile cache |
|
|
410
|
+
| Transcripts | `~/.openterminal/sessions/<date>/` | JSONL of inputs/commands/approvals/summaries |
|
|
411
|
+
| Host keys | `~/.ssh/known_hosts` | written after TOFU confirmation |
|
|
412
|
+
|
|
413
|
+
## Testing
|
|
414
|
+
|
|
415
|
+
```sh
|
|
416
|
+
pytest -q # Python suite (platform-gated tests auto-skip)
|
|
417
|
+
node --test tests/js/*.test.cjs # static frontend
|
|
418
|
+
cd src/openterminal/web/frontend/ui && npm ci && npm test # React island
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
Real-sshd tests are opt-in:
|
|
422
|
+
|
|
423
|
+
```sh
|
|
424
|
+
OT_TEST_SSH=1 OT_TEST_SSH_HOST=127.0.0.1 OT_TEST_SSH_PORT=2222 pytest -m ssh
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
## Contributing
|
|
428
|
+
|
|
429
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md). Issues and pull requests are welcome.
|
|
430
|
+
|
|
431
|
+
## Acknowledgments
|
|
432
|
+
|
|
433
|
+
- [deepagents](https://github.com/langchain-ai/deepagents) & LangGraph — agent runtime
|
|
434
|
+
- [xterm.js](https://xtermjs.org/) — browser terminal (vendored in `web/frontend/vendor/`)
|
|
435
|
+
- [asyncssh](https://github.com/ronf/asyncssh) — SSH client
|
|
436
|
+
|
|
437
|
+
## License
|
|
438
|
+
|
|
439
|
+
[Apache License 2.0](LICENSE)
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
openterminal/__init__.py,sha256=W2HfbEnZuWNHGuOfjRGV6cdCPVI-0MFs9VEs6KgYAUk,74
|
|
2
|
+
openterminal/app.py,sha256=EoLxFxc2yjb9g-rXlIh6Ma1JYJNhnf7CUahvkJAzvoI,1612
|
|
3
|
+
openterminal/approval.py,sha256=iax1orgzFzvC0GWNey9QMqZpQdJhvuynvweR_4DhjI8,247
|
|
4
|
+
openterminal/backend.py,sha256=2Jc-JoM6uaIeNFtNKGHAo_BxP6xUGx4HCZAiptRJa1Q,138
|
|
5
|
+
openterminal/banner.py,sha256=uVw6SpVsi-rWwEt5d-MaQzF-ja8z7RKhJbiCXHxiG6E,241
|
|
6
|
+
openterminal/cli.py,sha256=-WSpVFcF3V3X0m2igebCzSjfMlkV3FSotxWqZV99u8w,18432
|
|
7
|
+
openterminal/cmdset.py,sha256=AliiPJln4_DIp4BID_y8ZUL-_shPE1XKLTSLlYXwM34,267
|
|
8
|
+
openterminal/config.py,sha256=VqG--n4Gve-Er6LkV4oJe1daZrUoDJhq-z96a6UrPJw,13516
|
|
9
|
+
openterminal/connections_db.py,sha256=aAsxcpZ00J0xudT3R6EV85HZDgL_-YofD6ZUTXPXn6o,245
|
|
10
|
+
openterminal/headless.py,sha256=QuozIu6Sy4nSmyW06soCSuqea8pWzUrpUy879S6w9pk,247
|
|
11
|
+
openterminal/history_db.py,sha256=ykMY_qvQ-jcG9h-KHzekrrIQYK6IRqj5W5D5ZTrYUxc,235
|
|
12
|
+
openterminal/local_pty.py,sha256=lhGO7_6iE2zaEscE0hkszc6Rn5SzwEuiw9UZJLsHTt4,150
|
|
13
|
+
openterminal/local_win.py,sha256=i6FmmDcG_iFQGIW8Jhnzy9SOjJtIddI2PM-stlCjJ1Q,144
|
|
14
|
+
openterminal/policy.py,sha256=trYnYnlsAfpH7OcHZrRuNEikyOFJ77Vvt-15l1DpY1M,17234
|
|
15
|
+
openterminal/render.py,sha256=nNDI6FEIluzy94-LSecxRHMdXUco4k_qBj5uZb5IbZk,241
|
|
16
|
+
openterminal/secrets_store.py,sha256=fsPl9i7tCdNVN61UOrknI75aGnt6OAzTT8G0vQNlwn4,211
|
|
17
|
+
openterminal/shell_integration.py,sha256=sULK7dt2HL4PFF_YemcFjemn7eFM9aRdgGuU_Nmh-Us,282
|
|
18
|
+
openterminal/shell_session.py,sha256=73s4bWJV-Pl12KdQIRjpB9sj1sFLsrhjNVrp74R-NP8,358
|
|
19
|
+
openterminal/ssh_pty.py,sha256=SBRa3yQ9X5AFMWpZZWWcUGU1C4pi0U66QvHXw7ASlFE,210
|
|
20
|
+
openterminal/sysprobe.py,sha256=IbQ_zLjqt3RDYRfoevlLvsivU2wpq1BX0NMm-lQ_BVE,238
|
|
21
|
+
openterminal/taskview.py,sha256=moKy5uj7VJ1Bz5ApkyOFc6olPBAwWb7oQpEO9I8t59E,247
|
|
22
|
+
openterminal/term_frontend.py,sha256=Ur8YYlxXsDm06lP9ZY_ZtLwFU3A58cPkfzOHqbO_bHw,235
|
|
23
|
+
openterminal/token_est.py,sha256=v-6ZjfSmTTdT3hCKZxRuYgKGUkBj7XIODtjMtJqotsM,139
|
|
24
|
+
openterminal/transcript.py,sha256=FRNMIIMkCWvSh_YgXO10Ty0gIEOBx9A2Z7FNEkU0-7A,244
|
|
25
|
+
openterminal/agent/__init__.py,sha256=1R0d_qL07X_3VWyzNLmCVpp_BGJXNTF-zdf9MPsV3PE,1889
|
|
26
|
+
openterminal/agent/backend.py,sha256=GTWUUjHvpmw98khZBuD8_jDGtA-FJzDkbwwZDrnPYoQ,8604
|
|
27
|
+
openterminal/agent/build.py,sha256=QA0cNVYn6ooW6yubwMFrkYFM2r7YsGZkqx8TB2lX7mY,4536
|
|
28
|
+
openterminal/agent/middleware.py,sha256=6azk9v-hn2CycZtRwHvB3WhTdef5SyCZNUMTPQG_44c,4386
|
|
29
|
+
openterminal/agent/model.py,sha256=fEM6SGfSc1Rz3jjSANzBzo7QpoL-DpEmPyUnZH_BTr8,1436
|
|
30
|
+
openterminal/agent/prompt.py,sha256=TAZZoTDST1MmFbN8ATJkVQiY6pTSm42Zimw_HruysAw,4123
|
|
31
|
+
openterminal/agent/runner.py,sha256=YVqJEmaHItSrEWzNzon3rr807ahkbG-TSkBX8GWMWDs,15846
|
|
32
|
+
openterminal/agent/sysprobe.py,sha256=PfBE-ex91em4wfy-OB4jzVT2Fyoze3bpRKA4ZOEHj8w,6286
|
|
33
|
+
openterminal/agent/token_est.py,sha256=zA83nz6mQV3K9VKiOrZuIc10MO4ZwCEmJVVIK8kiHcI,1782
|
|
34
|
+
openterminal/connections/__init__.py,sha256=Gu5XPoAfkvMUa7G8PCMEoJmzR_eboybDfSh6bKh1b3w,1054
|
|
35
|
+
openterminal/connections/db.py,sha256=aubuhYU5XKJwnZxCxrN3AFnic0KgnSrRY_cw9vDuZX0,9487
|
|
36
|
+
openterminal/connections/models.py,sha256=1RJ13rJT2cQnnAd1A1SN1hhR39x3C9R9hDsdP5NYZts,11522
|
|
37
|
+
openterminal/connections/secrets.py,sha256=cBPhjV5kJOm1NEpctfff_TCHWuCVdTVhuf03-LrIIGk,2872
|
|
38
|
+
openterminal/core/__init__.py,sha256=vSYu7Y6hRobn8zYk4iW987-eXT0GCgg3WnDry6tcbpM,1572
|
|
39
|
+
openterminal/core/ai_flow.py,sha256=18ds4t_X_H2wifJsNTWeqrvyjDkm9_Q7bTL1NlvoX4Q,45706
|
|
40
|
+
openterminal/core/auth.py,sha256=j3l1fV3SCaJnCjfKy7LsSOcpK6gc56o7b9Iy9MtgPIU,26571
|
|
41
|
+
openterminal/core/cmdset_runner.py,sha256=xwxDsUqwyvN6tcqx-JO46yPc0B_Xzzq741YDtFqy4zQ,19827
|
|
42
|
+
openterminal/core/exec_events.py,sha256=itzwQNvl6HPH2miMT_VHGuWE9qd9ROFQsdCVUxKITKM,39107
|
|
43
|
+
openterminal/core/history.py,sha256=lg_rvvcjADLo5RrlEYluHWB_WFnafSCAbRM33gK86mU,2608
|
|
44
|
+
openterminal/core/hook_gate.py,sha256=W7W5dl_W3lGjUPt_-NlhrmfVsyrtGqsuj6ww9sNWFKg,23344
|
|
45
|
+
openterminal/core/messages.py,sha256=7dJIhReKZhc7TU6q42TzD9yfnWNS_Y-ScwcKe2T3rnc,10041
|
|
46
|
+
openterminal/core/nested_ssh.py,sha256=BxWHGDXTxeoZksCkQyed1MGWWQaMcE9-aM4lCojo9RQ,8458
|
|
47
|
+
openterminal/core/pipeline.py,sha256=O4C_eJ4QjPkIDpoURbNpppewCDHcbqGHHofQ6E3sluM,60514
|
|
48
|
+
openterminal/core/pump.py,sha256=6pozIAFc2th6mO64H4RRpeYanq6H1XQD0JPBUAvKxY4,9552
|
|
49
|
+
openterminal/core/transcript.py,sha256=2Hyc8nReSUzFJhMwbwjiwH_RCgzyU1-geA88jX62lkA,754
|
|
50
|
+
openterminal/frontend/__init__.py,sha256=ETcwuq_2NBc6bPG4cMl1ct24Jrx4eKonMbOUCUHmxus,1164
|
|
51
|
+
openterminal/frontend/approval.py,sha256=L6zB4EyYcK_7eV6GEpmVnNLlWJBQi9i4ap01X-aFcXs,996
|
|
52
|
+
openterminal/frontend/banner.py,sha256=Q6Ei9NyJJPX0Nmr-tjNlb5oLWZ0OnH9OiLPZHzUeYk8,1317
|
|
53
|
+
openterminal/frontend/headless.py,sha256=lKtnR1TS_s9K4Msg6o6DZnPOub1-puyglbeO3ktJwEo,11991
|
|
54
|
+
openterminal/frontend/render.py,sha256=Ii2jpBUGJVfjq9e9FTmVQWoboscp_4OE63HnMHMvF18,1436
|
|
55
|
+
openterminal/frontend/taskview.py,sha256=L_jG6jL7pLq4QuMgJHdIYmNl-bUeHwRQ1GQyMhaGK_w,6740
|
|
56
|
+
openterminal/frontend/term.py,sha256=ranF0cjxcGNrFo6QFdFGf2oYwUAX5aFAMNmyz2T8A-4,64993
|
|
57
|
+
openterminal/session/__init__.py,sha256=fu4SXmOKvyXB6Ss_3k1zg5F-LFPhZ0dWesuEyODPIUk,888
|
|
58
|
+
openterminal/session/base.py,sha256=6bi7rt1YiAjfphe0IyYRIO2u4nuU_8Lc7lH0uoiatsc,14035
|
|
59
|
+
openterminal/session/local_posix.py,sha256=sSRalvSY0vKA1pZGQ8O0KCwGuIcG5OJE17c_AISjCRQ,7431
|
|
60
|
+
openterminal/session/local_win.py,sha256=5cSyZpUZFymRoL6kP1O36PTlokhCbDPSjvA1ytyUNns,7250
|
|
61
|
+
openterminal/session/ssh.py,sha256=IG_Yac4XZ21ZwzXPJ1mzGiK2nJdEv9SAaR5ILNSJWqg,13496
|
|
62
|
+
openterminal/shell/__init__.py,sha256=m0vtMEfXthUvBCFiLbvrBSSJJvCeaZlsRanbHNuuD2g,824
|
|
63
|
+
openterminal/shell/cmdset.py,sha256=2rzWn3xDKI69iOtaw8Sdqg8Ojk79XZOz1Skz3H1vthc,1947
|
|
64
|
+
openterminal/shell/integration.py,sha256=-qrIvVkSc5zQF7SfMNJk6ACeK2d6u_zvdZV__vxhdhk,67988
|
|
65
|
+
openterminal/skills/__init__.py,sha256=paQkblbBiOrnmRcJ4ZyFwxEne162FSWMbqvFP-QONL0,1689
|
|
66
|
+
openterminal/skills/markdown-tables/SKILL.md,sha256=UZ3DiZyJ1BQ-KbW6GQhIQCTohGEKQhmRXrf2N3sYiws,4778
|
|
67
|
+
openterminal/web/__init__.py,sha256=iUbsd7yvvO2lMAKJaDJz5i0M--LBNLYfo_gJNGbYEKA,65
|
|
68
|
+
openterminal/web/files_api.py,sha256=YLd4HS0UtoEqs3wCT_KxC-GRAjkw1PZx_BN7Vrm6v0o,33922
|
|
69
|
+
openterminal/web/protocol.py,sha256=tF9ZLpgH7YyJ2A6cjJVdBIHauTYMIDZV67ftNFaBZDU,1578
|
|
70
|
+
openterminal/web/server.py,sha256=PTFhjY6v4zfwstSMqP7xt8-XCkWrp37lPJHb5uLvLNw,13247
|
|
71
|
+
openterminal/web/worker.py,sha256=YV7VQt_LFUD5Nd0JLForD6qMtqdQ-W8nTP8cHn-ANTA,2803
|
|
72
|
+
openterminal/web/frontend/index.html,sha256=cVCQoBPlYwg18v3BAEx6OFNgnxR7XDiJxkZJEQ2XgwU,1379
|
|
73
|
+
openterminal/web/frontend/static/app.js,sha256=6JdYzlpN8oNRBYhqFSMXVOVjhdnDAZJtoIi3Vgrvgy0,177766
|
|
74
|
+
openterminal/web/frontend/static/block_stream.js,sha256=l5wAZlpS253ftF4SRNYTw9BWSqdNX5QqZYHqbqtJSCQ,11935
|
|
75
|
+
openterminal/web/frontend/static/block_view.js,sha256=wdeQniltMKffMVwpifQqsiKdUbKLnxCBQB8r6mXDdK4,14291
|
|
76
|
+
openterminal/web/frontend/static/files_panel.js,sha256=MD6K1n_u6nMLf4L0oFqeX68yk-yqSbBWI3dJorRrPFs,31097
|
|
77
|
+
openterminal/web/frontend/static/md_lite.js,sha256=KUhIg4rRTD-LJYh9ZsRO8TFZxaZLYMUztOF1VqtV2Ew,4928
|
|
78
|
+
openterminal/web/frontend/static/modal.js,sha256=PZ_ic_CwkIvq1783jmO07LlOmMniBrQi2k0Hr6-Xa0M,4747
|
|
79
|
+
openterminal/web/frontend/static/pad_ledger.js,sha256=9RKbyuslse3ENOTGToGJ7onEToBJQxJyDDxIC0E8vw8,2444
|
|
80
|
+
openterminal/web/frontend/static/style.css,sha256=cltlCiOM4ztuCcECC1oZQ5J26lmJ2O4EvcO8hVdWjnk,42645
|
|
81
|
+
openterminal/web/frontend/static/ui/ui.css,sha256=WzE070ddUjWQxz6v0fBCE_YsLGsbxHmi7kYdDI1LKtc,1895
|
|
82
|
+
openterminal/web/frontend/static/ui/ui.js,sha256=XuhdKNhBqsTex1DuJuOki6hKbq7GrOQAHQEq3_AT00I,239628
|
|
83
|
+
openterminal/web/frontend/vendor/addon-fit.js,sha256=va76Nwsb_ELuiNRv5gckAJAqTUstRc2TQ43amyPJcIk,1497
|
|
84
|
+
openterminal/web/frontend/vendor/xterm.css,sha256=9_ckrqK7Ygpkgr-45L3s-uEVKwx_rO9V-9ph87bP7bI,2877
|
|
85
|
+
openterminal/web/frontend/vendor/xterm.js,sha256=QZbiQu8c9MKt6tjZf0p3KmlXYHb3CwleAEtKu7BJ578,289714
|
|
86
|
+
ot_cli-0.2.1.dist-info/licenses/LICENSE,sha256=IcCygJg1rQWskJC_SS6vxqjmcY3h2KMslUXwSMAwL5A,11541
|
|
87
|
+
ot_cli-0.2.1.dist-info/METADATA,sha256=XotLRR4neO-cgg7F4LArtR3iwuY7ElF7JeuEPFT8YRA,18106
|
|
88
|
+
ot_cli-0.2.1.dist-info/WHEEL,sha256=a-udL2m4V_6Yn8b-uKzWb12RjRxwwMo6ohYFM24tpaY,91
|
|
89
|
+
ot_cli-0.2.1.dist-info/entry_points.txt,sha256=XPwj1chDeLmNZyfXTx_nLRlpNxsYZndYehJgaI5b9Tg,82
|
|
90
|
+
ot_cli-0.2.1.dist-info/top_level.txt,sha256=YvEJw5ACKW5NfMyF026spIfjX5UHigr36TpoJM2lQLk,13
|
|
91
|
+
ot_cli-0.2.1.dist-info/RECORD,,
|