cleat 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.
- cleat-0.1.0/.github/workflows/ci.yml +38 -0
- cleat-0.1.0/.github/workflows/release.yml +28 -0
- cleat-0.1.0/.gitignore +17 -0
- cleat-0.1.0/LICENSE +27 -0
- cleat-0.1.0/PKG-INFO +159 -0
- cleat-0.1.0/README.md +108 -0
- cleat-0.1.0/client_test.py +57 -0
- cleat-0.1.0/pyproject.toml +45 -0
- cleat-0.1.0/src/cleat/__init__.py +8 -0
- cleat-0.1.0/src/cleat/engine.py +477 -0
- cleat-0.1.0/src/cleat/filewatch.py +86 -0
- cleat-0.1.0/src/cleat/inject.py +107 -0
- cleat-0.1.0/src/cleat/microterm.py +140 -0
- cleat-0.1.0/src/cleat/server.py +126 -0
- cleat-0.1.0/src/cleat/structure.py +183 -0
- cleat-0.1.0/src/cleat/vendor/bash-preexec.LICENSE.md +21 -0
- cleat-0.1.0/src/cleat/vendor/bash-preexec.sh +564 -0
- cleat-0.1.0/tests/test_engine.py +144 -0
- cleat-0.1.0/tests/test_filewatch.py +33 -0
- cleat-0.1.0/tests/test_mcp.py +50 -0
- cleat-0.1.0/tests/test_structure.py +56 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
name: ${{ matrix.os }} / py${{ matrix.python }}
|
|
11
|
+
runs-on: ${{ matrix.os }}
|
|
12
|
+
strategy:
|
|
13
|
+
fail-fast: false
|
|
14
|
+
matrix:
|
|
15
|
+
os: [ubuntu-latest, macos-latest]
|
|
16
|
+
python: ["3.10", "3.12", "3.13"]
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
|
|
20
|
+
- uses: actions/setup-python@v5
|
|
21
|
+
with:
|
|
22
|
+
python-version: ${{ matrix.python }}
|
|
23
|
+
|
|
24
|
+
# fish exercises the native-OSC-133 path; bash/zsh are already present.
|
|
25
|
+
- name: Install fish (Linux)
|
|
26
|
+
if: runner.os == 'Linux'
|
|
27
|
+
run: sudo apt-get update && sudo apt-get install -y fish
|
|
28
|
+
- name: Install fish (macOS)
|
|
29
|
+
if: runner.os == 'macOS'
|
|
30
|
+
run: brew install fish
|
|
31
|
+
|
|
32
|
+
- name: Install package + dev deps
|
|
33
|
+
run: |
|
|
34
|
+
python -m pip install --upgrade pip
|
|
35
|
+
pip install -e ".[dev]"
|
|
36
|
+
|
|
37
|
+
- name: Run tests
|
|
38
|
+
run: pytest -v
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# Publish to PyPI when a version tag is pushed (e.g. v0.1.0).
|
|
4
|
+
# Uses PyPI Trusted Publishing (OIDC) — no API tokens stored anywhere.
|
|
5
|
+
on:
|
|
6
|
+
push:
|
|
7
|
+
tags: ["v*"]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
pypi-publish:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
environment: pypi # configure this name in the PyPI trusted publisher
|
|
13
|
+
permissions:
|
|
14
|
+
id-token: write # required for OIDC trusted publishing
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- uses: actions/setup-python@v5
|
|
19
|
+
with:
|
|
20
|
+
python-version: "3.12"
|
|
21
|
+
|
|
22
|
+
- name: Build sdist + wheel
|
|
23
|
+
run: |
|
|
24
|
+
python -m pip install --upgrade build
|
|
25
|
+
python -m build
|
|
26
|
+
|
|
27
|
+
- name: Publish to PyPI
|
|
28
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
cleat-0.1.0/.gitignore
ADDED
cleat-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 sidyellur
|
|
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.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
This project vendors bash-preexec (src/cleat/vendor/bash-preexec.sh), which is
|
|
26
|
+
licensed separately under the MIT License, Copyright (c) 2017 Ryan Caloras and
|
|
27
|
+
contributors. See src/cleat/vendor/bash-preexec.LICENSE.md.
|
cleat-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cleat
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A headless terminal layer: a persistent PTY + OSC 133 structure source, exposed to AI agents over MCP.
|
|
5
|
+
Project-URL: Homepage, https://github.com/sidyellur/cleat
|
|
6
|
+
Project-URL: Issues, https://github.com/sidyellur/cleat/issues
|
|
7
|
+
Author-email: sidyellur <20009719+sidyellur@users.noreply.github.com>
|
|
8
|
+
License: MIT License
|
|
9
|
+
|
|
10
|
+
Copyright (c) 2026 sidyellur
|
|
11
|
+
|
|
12
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
13
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
14
|
+
in the Software without restriction, including without limitation the rights
|
|
15
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
16
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
17
|
+
furnished to do so, subject to the following conditions:
|
|
18
|
+
|
|
19
|
+
The above copyright notice and this permission notice shall be included in all
|
|
20
|
+
copies or substantial portions of the Software.
|
|
21
|
+
|
|
22
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
23
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
24
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
25
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
26
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
27
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
28
|
+
SOFTWARE.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
This project vendors bash-preexec (src/cleat/vendor/bash-preexec.sh), which is
|
|
33
|
+
licensed separately under the MIT License, Copyright (c) 2017 Ryan Caloras and
|
|
34
|
+
contributors. See src/cleat/vendor/bash-preexec.LICENSE.md.
|
|
35
|
+
License-File: LICENSE
|
|
36
|
+
Keywords: agent,claude,claude-code,mcp,osc133,pty,shell-integration,terminal
|
|
37
|
+
Classifier: Development Status :: 4 - Beta
|
|
38
|
+
Classifier: Intended Audience :: Developers
|
|
39
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
40
|
+
Classifier: Operating System :: POSIX
|
|
41
|
+
Classifier: Programming Language :: Python :: 3
|
|
42
|
+
Classifier: Topic :: Terminals
|
|
43
|
+
Requires-Python: >=3.10
|
|
44
|
+
Requires-Dist: mcp>=1.0
|
|
45
|
+
Requires-Dist: ptyprocess>=0.7
|
|
46
|
+
Requires-Dist: pyte>=0.8
|
|
47
|
+
Provides-Extra: dev
|
|
48
|
+
Requires-Dist: build; extra == 'dev'
|
|
49
|
+
Requires-Dist: pytest>=7; extra == 'dev'
|
|
50
|
+
Description-Content-Type: text/markdown
|
|
51
|
+
|
|
52
|
+
# cleat
|
|
53
|
+
|
|
54
|
+
[](https://github.com/sidyellur/cleat/actions/workflows/ci.yml)
|
|
55
|
+
|
|
56
|
+
**A headless terminal layer for AI agents.** `cleat` runs a *persistent* shell
|
|
57
|
+
session behind a PTY, parses its byte stream for [OSC 133](https://gitlab.freedesktop.org/Per_Bothner/specifications/blob/master/proposals/semantic-prompts.md)
|
|
58
|
+
shell-integration marks, and exposes it to an agent over [MCP](https://modelcontextprotocol.io)
|
|
59
|
+
as **structured results** — `stdout`, real `exit_code`, files touched — instead
|
|
60
|
+
of raw escape-code soup.
|
|
61
|
+
|
|
62
|
+
It is *not* a terminal emulator and *not* a modification to your terminal. It's
|
|
63
|
+
a separate process that runs inside whatever terminal you already use, and it
|
|
64
|
+
stays terminal-agnostic.
|
|
65
|
+
|
|
66
|
+
## Why
|
|
67
|
+
|
|
68
|
+
When an agent drives a terminal, it normally sees a continuous byte river:
|
|
69
|
+
prompt redraws, echoed keystrokes, color codes, and output, with no marker for
|
|
70
|
+
where one command's output ends or whether it succeeded. Worse, when you scrape
|
|
71
|
+
a PTY, **the exit code isn't in the stream at all** — the shell knows `$?` but
|
|
72
|
+
never prints it.
|
|
73
|
+
|
|
74
|
+
`cleat` injects OSC 133 marks into the shells it spawns and parses them back
|
|
75
|
+
out, so the agent gets:
|
|
76
|
+
|
|
77
|
+
```json
|
|
78
|
+
{ "stdout": "...", "exit_code": 0, "completed": true }
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
…and the session is **persistent**: `cd`, `export`, activated venvs, and `ssh`
|
|
82
|
+
sessions all carry across calls — something a fresh `subprocess` per command
|
|
83
|
+
cannot do.
|
|
84
|
+
|
|
85
|
+
## Install
|
|
86
|
+
|
|
87
|
+
Requires Python ≥3.10 on a POSIX system (Linux/macOS). With [uv](https://docs.astral.sh/uv/) installed, register it with Claude Code:
|
|
88
|
+
|
|
89
|
+
```sh
|
|
90
|
+
claude mcp add cleat -- uvx --from git+https://github.com/sidyellur/cleat cleat
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Or add it to a project's `.mcp.json`:
|
|
94
|
+
|
|
95
|
+
```json
|
|
96
|
+
{
|
|
97
|
+
"mcpServers": {
|
|
98
|
+
"cleat": {
|
|
99
|
+
"command": "uvx",
|
|
100
|
+
"args": ["--from", "git+https://github.com/sidyellur/cleat", "cleat"]
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
For local development:
|
|
107
|
+
|
|
108
|
+
```sh
|
|
109
|
+
git clone https://github.com/sidyellur/cleat && cd cleat
|
|
110
|
+
python -m venv .venv && . .venv/bin/activate
|
|
111
|
+
pip install -e .
|
|
112
|
+
cleat # runs the MCP server over stdio
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Tools
|
|
116
|
+
|
|
117
|
+
| Tool | Returns | Use for |
|
|
118
|
+
|------|---------|---------|
|
|
119
|
+
| `run_command(command, timeout)` | `{stdout, exit_code, completed}` (+ `files_changed` if watching) | normal commands — full, exact stdout |
|
|
120
|
+
| `read_output(timeout)` | `{output, exit_code, completed}` | watching a long-running / streaming command |
|
|
121
|
+
| `read_screen()` | `{screen, cursor}` | inspecting a full-screen TUI (vim, top, less) |
|
|
122
|
+
| `send_keys(keys, enter)` | `{screen, cursor, exit_code, completed}` | driving a REPL / TUI / prompt (control chars pass through: ``=Ctrl-C) |
|
|
123
|
+
| `resize(cols, rows)` | `{cols, rows}` | laying out a TUI for a given size |
|
|
124
|
+
| `watch_files(path)` | `{watch_root}` | enable per-command `files_changed` under `path` |
|
|
125
|
+
|
|
126
|
+
`completed: false` means the program is still running or waiting for input (e.g.
|
|
127
|
+
a REPL) — drive it with `send_keys` and poll with `read_output`.
|
|
128
|
+
|
|
129
|
+
## How it works
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
agent ─(MCP)─ server.py ─ engine.py (persistent PTY, ptyprocess)
|
|
133
|
+
├─ structure.py → OSC 133 marks → {stdout, exit_code}
|
|
134
|
+
└─ pyte screen → rendered view for REPLs/TUIs
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
The engine injects OSC 133 per shell without touching your real config: zsh via
|
|
138
|
+
a temp `$ZDOTDIR`, bash via `--rcfile` + vendored [bash-preexec](https://github.com/rcaloras/bash-preexec),
|
|
139
|
+
and fish ≥4 via its native shell integration (older fish is unsupported).
|
|
140
|
+
|
|
141
|
+
## Caveats
|
|
142
|
+
|
|
143
|
+
- **POSIX only.** Uses `pty`/`termios`; no Windows.
|
|
144
|
+
- **It gives the agent a real shell.** Commands run with your user's privileges
|
|
145
|
+
in a persistent session. Run it only where you'd let an agent run shell
|
|
146
|
+
commands.
|
|
147
|
+
- **`files_changed` detects writes, not reads** (create/modify/delete under the
|
|
148
|
+
watched root). Read-tracking needs privileged syscall tracing.
|
|
149
|
+
- **bash:** a command whose *first token* is a subshell `(...)` or brace group
|
|
150
|
+
`{ ...; }` emits no start mark; its exit code is recovered but stdout for that
|
|
151
|
+
one command is not captured.
|
|
152
|
+
- **Full-screen TUIs:** use `read_screen` (the rendered grid), not `run_command`.
|
|
153
|
+
- **Shells:** zsh and bash are fully supported. fish requires **≥4** (native OSC
|
|
154
|
+
133) and is supported for command execution; interactive REPL/TUI *driving* is
|
|
155
|
+
verified on zsh/bash.
|
|
156
|
+
|
|
157
|
+
## License
|
|
158
|
+
|
|
159
|
+
MIT (see [LICENSE](LICENSE)). Vendors bash-preexec, also MIT.
|
cleat-0.1.0/README.md
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# cleat
|
|
2
|
+
|
|
3
|
+
[](https://github.com/sidyellur/cleat/actions/workflows/ci.yml)
|
|
4
|
+
|
|
5
|
+
**A headless terminal layer for AI agents.** `cleat` runs a *persistent* shell
|
|
6
|
+
session behind a PTY, parses its byte stream for [OSC 133](https://gitlab.freedesktop.org/Per_Bothner/specifications/blob/master/proposals/semantic-prompts.md)
|
|
7
|
+
shell-integration marks, and exposes it to an agent over [MCP](https://modelcontextprotocol.io)
|
|
8
|
+
as **structured results** — `stdout`, real `exit_code`, files touched — instead
|
|
9
|
+
of raw escape-code soup.
|
|
10
|
+
|
|
11
|
+
It is *not* a terminal emulator and *not* a modification to your terminal. It's
|
|
12
|
+
a separate process that runs inside whatever terminal you already use, and it
|
|
13
|
+
stays terminal-agnostic.
|
|
14
|
+
|
|
15
|
+
## Why
|
|
16
|
+
|
|
17
|
+
When an agent drives a terminal, it normally sees a continuous byte river:
|
|
18
|
+
prompt redraws, echoed keystrokes, color codes, and output, with no marker for
|
|
19
|
+
where one command's output ends or whether it succeeded. Worse, when you scrape
|
|
20
|
+
a PTY, **the exit code isn't in the stream at all** — the shell knows `$?` but
|
|
21
|
+
never prints it.
|
|
22
|
+
|
|
23
|
+
`cleat` injects OSC 133 marks into the shells it spawns and parses them back
|
|
24
|
+
out, so the agent gets:
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{ "stdout": "...", "exit_code": 0, "completed": true }
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
…and the session is **persistent**: `cd`, `export`, activated venvs, and `ssh`
|
|
31
|
+
sessions all carry across calls — something a fresh `subprocess` per command
|
|
32
|
+
cannot do.
|
|
33
|
+
|
|
34
|
+
## Install
|
|
35
|
+
|
|
36
|
+
Requires Python ≥3.10 on a POSIX system (Linux/macOS). With [uv](https://docs.astral.sh/uv/) installed, register it with Claude Code:
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
claude mcp add cleat -- uvx --from git+https://github.com/sidyellur/cleat cleat
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Or add it to a project's `.mcp.json`:
|
|
43
|
+
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"mcpServers": {
|
|
47
|
+
"cleat": {
|
|
48
|
+
"command": "uvx",
|
|
49
|
+
"args": ["--from", "git+https://github.com/sidyellur/cleat", "cleat"]
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
For local development:
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
git clone https://github.com/sidyellur/cleat && cd cleat
|
|
59
|
+
python -m venv .venv && . .venv/bin/activate
|
|
60
|
+
pip install -e .
|
|
61
|
+
cleat # runs the MCP server over stdio
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Tools
|
|
65
|
+
|
|
66
|
+
| Tool | Returns | Use for |
|
|
67
|
+
|------|---------|---------|
|
|
68
|
+
| `run_command(command, timeout)` | `{stdout, exit_code, completed}` (+ `files_changed` if watching) | normal commands — full, exact stdout |
|
|
69
|
+
| `read_output(timeout)` | `{output, exit_code, completed}` | watching a long-running / streaming command |
|
|
70
|
+
| `read_screen()` | `{screen, cursor}` | inspecting a full-screen TUI (vim, top, less) |
|
|
71
|
+
| `send_keys(keys, enter)` | `{screen, cursor, exit_code, completed}` | driving a REPL / TUI / prompt (control chars pass through: ``=Ctrl-C) |
|
|
72
|
+
| `resize(cols, rows)` | `{cols, rows}` | laying out a TUI for a given size |
|
|
73
|
+
| `watch_files(path)` | `{watch_root}` | enable per-command `files_changed` under `path` |
|
|
74
|
+
|
|
75
|
+
`completed: false` means the program is still running or waiting for input (e.g.
|
|
76
|
+
a REPL) — drive it with `send_keys` and poll with `read_output`.
|
|
77
|
+
|
|
78
|
+
## How it works
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
agent ─(MCP)─ server.py ─ engine.py (persistent PTY, ptyprocess)
|
|
82
|
+
├─ structure.py → OSC 133 marks → {stdout, exit_code}
|
|
83
|
+
└─ pyte screen → rendered view for REPLs/TUIs
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The engine injects OSC 133 per shell without touching your real config: zsh via
|
|
87
|
+
a temp `$ZDOTDIR`, bash via `--rcfile` + vendored [bash-preexec](https://github.com/rcaloras/bash-preexec),
|
|
88
|
+
and fish ≥4 via its native shell integration (older fish is unsupported).
|
|
89
|
+
|
|
90
|
+
## Caveats
|
|
91
|
+
|
|
92
|
+
- **POSIX only.** Uses `pty`/`termios`; no Windows.
|
|
93
|
+
- **It gives the agent a real shell.** Commands run with your user's privileges
|
|
94
|
+
in a persistent session. Run it only where you'd let an agent run shell
|
|
95
|
+
commands.
|
|
96
|
+
- **`files_changed` detects writes, not reads** (create/modify/delete under the
|
|
97
|
+
watched root). Read-tracking needs privileged syscall tracing.
|
|
98
|
+
- **bash:** a command whose *first token* is a subshell `(...)` or brace group
|
|
99
|
+
`{ ...; }` emits no start mark; its exit code is recovered but stdout for that
|
|
100
|
+
one command is not captured.
|
|
101
|
+
- **Full-screen TUIs:** use `read_screen` (the rendered grid), not `run_command`.
|
|
102
|
+
- **Shells:** zsh and bash are fully supported. fish requires **≥4** (native OSC
|
|
103
|
+
133) and is supported for command execution; interactive REPL/TUI *driving* is
|
|
104
|
+
verified on zsh/bash.
|
|
105
|
+
|
|
106
|
+
## License
|
|
107
|
+
|
|
108
|
+
MIT (see [LICENSE](LICENSE)). Vendors bash-preexec, also MIT.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Smoke test: drive the cleat MCP server over the REAL stdio transport.
|
|
3
|
+
|
|
4
|
+
python client_test.py # requires `pip install -e .`
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
from mcp import ClientSession, StdioServerParameters
|
|
11
|
+
from mcp.client.stdio import stdio_client
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _text(result):
|
|
15
|
+
return "".join(c.text for c in result.content if getattr(c, "type", "") == "text")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
async def main():
|
|
19
|
+
params = StdioServerParameters(command=sys.executable, args=["-m", "cleat.server"])
|
|
20
|
+
async with stdio_client(params) as (read, write):
|
|
21
|
+
async with ClientSession(read, write) as session:
|
|
22
|
+
await session.initialize()
|
|
23
|
+
|
|
24
|
+
tools = await session.list_tools()
|
|
25
|
+
print("tools:", [t.name for t in tools.tools])
|
|
26
|
+
|
|
27
|
+
r = await session.call_tool(
|
|
28
|
+
"run_command", {"command": "echo live over stdio; uname -s"})
|
|
29
|
+
print("\nrun #1:\n" + _text(r))
|
|
30
|
+
|
|
31
|
+
# Persistence across SEPARATE MCP tool calls - the subprocess-can't-do-this bit.
|
|
32
|
+
await session.call_tool("run_command", {"command": "export FOO=persisted"})
|
|
33
|
+
await session.call_tool("run_command", {"command": "cd /tmp"})
|
|
34
|
+
r2 = await session.call_tool("run_command", {"command": "echo $FOO; pwd"})
|
|
35
|
+
print("\nrun #2 (after export+cd in earlier calls):\n" + _text(r2))
|
|
36
|
+
|
|
37
|
+
r3 = await session.call_tool("run_command", {"command": "false"})
|
|
38
|
+
print("\nrun #3 (failure):\n" + _text(r3))
|
|
39
|
+
|
|
40
|
+
# Interactive: start a REPL (won't 'complete'), compute, exit.
|
|
41
|
+
r4 = await session.call_tool("run_command", {"command": "python3", "timeout": 8})
|
|
42
|
+
print("\nrun #4 (repl start, completed=False expected):\n" + _text(r4)[-80:])
|
|
43
|
+
r5 = await session.call_tool("send_keys", {"keys": "print(6*7)", "enter": True})
|
|
44
|
+
print("\nrun #5 (send_keys -> 42 expected):\n" + _text(r5))
|
|
45
|
+
r6 = await session.call_tool("send_keys", {"keys": "exit()", "enter": True})
|
|
46
|
+
print("\nrun #6 (repl exit, completed=True expected):\n" + _text(r6))
|
|
47
|
+
|
|
48
|
+
# Full-screen TUI over MCP: open vim, SEE it via read_screen, quit it.
|
|
49
|
+
await session.call_tool("run_command", {"command": "vim -u NONE -N", "timeout": 4})
|
|
50
|
+
r7 = await session.call_tool("read_screen", {})
|
|
51
|
+
print("\nrun #7 (read_screen of vim, '~' rows expected):\n" + _text(r7)[:200])
|
|
52
|
+
r8 = await session.call_tool("send_keys", {"keys": ":q!", "enter": True})
|
|
53
|
+
print("\nrun #8 (vim quit, completed=True expected):\n" + _text(r8)[-120:])
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
if __name__ == "__main__":
|
|
57
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "cleat"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A headless terminal layer: a persistent PTY + OSC 133 structure source, exposed to AI agents over MCP."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { file = "LICENSE" }
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [{ name = "sidyellur", email = "20009719+sidyellur@users.noreply.github.com" }]
|
|
13
|
+
keywords = ["mcp", "terminal", "pty", "osc133", "shell-integration", "agent", "claude", "claude-code"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Operating System :: POSIX",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Topic :: Terminals",
|
|
21
|
+
]
|
|
22
|
+
dependencies = [
|
|
23
|
+
"ptyprocess>=0.7",
|
|
24
|
+
"pyte>=0.8",
|
|
25
|
+
"mcp>=1.0",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.optional-dependencies]
|
|
29
|
+
dev = ["pytest>=7", "build"]
|
|
30
|
+
|
|
31
|
+
[project.scripts]
|
|
32
|
+
cleat = "cleat.server:main"
|
|
33
|
+
|
|
34
|
+
[tool.pytest.ini_options]
|
|
35
|
+
testpaths = ["tests"]
|
|
36
|
+
|
|
37
|
+
[project.urls]
|
|
38
|
+
Homepage = "https://github.com/sidyellur/cleat"
|
|
39
|
+
Issues = "https://github.com/sidyellur/cleat/issues"
|
|
40
|
+
|
|
41
|
+
[tool.hatch.build.targets.wheel]
|
|
42
|
+
packages = ["src/cleat"]
|
|
43
|
+
# The vendored bash-preexec.sh (+ its license) under src/cleat/vendor/ ships
|
|
44
|
+
# automatically (hatchling includes all non-VCS-ignored files in the package);
|
|
45
|
+
# the bash injection rcfile references it by absolute path at runtime.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""cleat - a headless terminal layer for AI agents.
|
|
2
|
+
|
|
3
|
+
A persistent PTY shell session whose byte stream is parsed for OSC 133 marks and
|
|
4
|
+
exposed to an agent over MCP as structured results (stdout, exit code, files
|
|
5
|
+
touched), plus a virtual screen for interactive programs and TUIs.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
__version__ = "0.1.0"
|