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.
Files changed (91) hide show
  1. openterminal/__init__.py +3 -0
  2. openterminal/agent/__init__.py +39 -0
  3. openterminal/agent/backend.py +180 -0
  4. openterminal/agent/build.py +115 -0
  5. openterminal/agent/middleware.py +111 -0
  6. openterminal/agent/model.py +41 -0
  7. openterminal/agent/prompt.py +63 -0
  8. openterminal/agent/runner.py +340 -0
  9. openterminal/agent/sysprobe.py +164 -0
  10. openterminal/agent/token_est.py +49 -0
  11. openterminal/app.py +52 -0
  12. openterminal/approval.py +7 -0
  13. openterminal/backend.py +3 -0
  14. openterminal/banner.py +7 -0
  15. openterminal/cli.py +426 -0
  16. openterminal/cmdset.py +7 -0
  17. openterminal/config.py +322 -0
  18. openterminal/connections/__init__.py +20 -0
  19. openterminal/connections/db.py +240 -0
  20. openterminal/connections/models.py +289 -0
  21. openterminal/connections/secrets.py +68 -0
  22. openterminal/connections_db.py +6 -0
  23. openterminal/core/__init__.py +41 -0
  24. openterminal/core/ai_flow.py +781 -0
  25. openterminal/core/auth.py +499 -0
  26. openterminal/core/cmdset_runner.py +352 -0
  27. openterminal/core/exec_events.py +650 -0
  28. openterminal/core/history.py +79 -0
  29. openterminal/core/hook_gate.py +415 -0
  30. openterminal/core/messages.py +193 -0
  31. openterminal/core/nested_ssh.py +178 -0
  32. openterminal/core/pipeline.py +1187 -0
  33. openterminal/core/pump.py +190 -0
  34. openterminal/core/transcript.py +26 -0
  35. openterminal/frontend/__init__.py +21 -0
  36. openterminal/frontend/approval.py +40 -0
  37. openterminal/frontend/banner.py +40 -0
  38. openterminal/frontend/headless.py +290 -0
  39. openterminal/frontend/render.py +41 -0
  40. openterminal/frontend/taskview.py +184 -0
  41. openterminal/frontend/term.py +1417 -0
  42. openterminal/headless.py +7 -0
  43. openterminal/history_db.py +7 -0
  44. openterminal/local_pty.py +3 -0
  45. openterminal/local_win.py +3 -0
  46. openterminal/policy.py +362 -0
  47. openterminal/render.py +7 -0
  48. openterminal/secrets_store.py +5 -0
  49. openterminal/session/__init__.py +17 -0
  50. openterminal/session/base.py +328 -0
  51. openterminal/session/local_posix.py +184 -0
  52. openterminal/session/local_win.py +173 -0
  53. openterminal/session/ssh.py +321 -0
  54. openterminal/shell/__init__.py +17 -0
  55. openterminal/shell/cmdset.py +49 -0
  56. openterminal/shell/integration.py +1298 -0
  57. openterminal/shell_integration.py +7 -0
  58. openterminal/shell_session.py +8 -0
  59. openterminal/skills/__init__.py +45 -0
  60. openterminal/skills/markdown-tables/SKILL.md +247 -0
  61. openterminal/ssh_pty.py +6 -0
  62. openterminal/sysprobe.py +7 -0
  63. openterminal/taskview.py +7 -0
  64. openterminal/term_frontend.py +7 -0
  65. openterminal/token_est.py +3 -0
  66. openterminal/transcript.py +7 -0
  67. openterminal/web/__init__.py +1 -0
  68. openterminal/web/files_api.py +789 -0
  69. openterminal/web/frontend/index.html +37 -0
  70. openterminal/web/frontend/static/app.js +3632 -0
  71. openterminal/web/frontend/static/block_stream.js +274 -0
  72. openterminal/web/frontend/static/block_view.js +383 -0
  73. openterminal/web/frontend/static/files_panel.js +782 -0
  74. openterminal/web/frontend/static/md_lite.js +127 -0
  75. openterminal/web/frontend/static/modal.js +122 -0
  76. openterminal/web/frontend/static/pad_ledger.js +46 -0
  77. openterminal/web/frontend/static/style.css +915 -0
  78. openterminal/web/frontend/static/ui/ui.css +1 -0
  79. openterminal/web/frontend/static/ui/ui.js +6292 -0
  80. openterminal/web/frontend/vendor/addon-fit.js +2 -0
  81. openterminal/web/frontend/vendor/xterm.css +8 -0
  82. openterminal/web/frontend/vendor/xterm.js +8 -0
  83. openterminal/web/protocol.py +41 -0
  84. openterminal/web/server.py +322 -0
  85. openterminal/web/worker.py +62 -0
  86. ot_cli-0.2.1.dist-info/METADATA +439 -0
  87. ot_cli-0.2.1.dist-info/RECORD +91 -0
  88. ot_cli-0.2.1.dist-info/WHEEL +5 -0
  89. ot_cli-0.2.1.dist-info/entry_points.txt +3 -0
  90. ot_cli-0.2.1.dist-info/licenses/LICENSE +202 -0
  91. 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
+ [![CI](https://github.com/jeean404/OpenTerminal/actions/workflows/ci.yml/badge.svg)](https://github.com/jeean404/OpenTerminal/actions/workflows/ci.yml)
43
+ [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](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,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.2.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ openterminal = openterminal.app:main
3
+ ot = openterminal.app:main