@moejay/wrightty 0.0.0 → 0.1.1

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 (94) hide show
  1. package/dist/cli.d.ts +3 -0
  2. package/dist/cli.js +144 -0
  3. package/dist/client.d.ts +14 -0
  4. package/dist/client.js +83 -0
  5. package/dist/index.d.ts +3 -0
  6. package/dist/index.js +8 -0
  7. package/dist/terminal.d.ts +48 -0
  8. package/dist/terminal.js +210 -0
  9. package/dist/types.d.ts +90 -0
  10. package/dist/types.js +3 -0
  11. package/package.json +38 -15
  12. package/.github/workflows/ci.yml +0 -90
  13. package/.github/workflows/release.yml +0 -177
  14. package/Cargo.lock +0 -2662
  15. package/Cargo.toml +0 -38
  16. package/PROTOCOL.md +0 -1351
  17. package/README.md +0 -386
  18. package/agents/ceo/AGENTS.md +0 -24
  19. package/agents/ceo/HEARTBEAT.md +0 -72
  20. package/agents/ceo/SOUL.md +0 -33
  21. package/agents/ceo/TOOLS.md +0 -3
  22. package/agents/founding-engineer/AGENTS.md +0 -44
  23. package/crates/wrightty/Cargo.toml +0 -43
  24. package/crates/wrightty/src/client_cmds.rs +0 -366
  25. package/crates/wrightty/src/discover.rs +0 -78
  26. package/crates/wrightty/src/main.rs +0 -100
  27. package/crates/wrightty/src/server.rs +0 -100
  28. package/crates/wrightty/src/term.rs +0 -338
  29. package/crates/wrightty-bridge-ghostty/Cargo.toml +0 -27
  30. package/crates/wrightty-bridge-ghostty/src/ghostty.rs +0 -422
  31. package/crates/wrightty-bridge-ghostty/src/lib.rs +0 -2
  32. package/crates/wrightty-bridge-ghostty/src/main.rs +0 -146
  33. package/crates/wrightty-bridge-ghostty/src/rpc.rs +0 -307
  34. package/crates/wrightty-bridge-kitty/Cargo.toml +0 -26
  35. package/crates/wrightty-bridge-kitty/src/kitty.rs +0 -269
  36. package/crates/wrightty-bridge-kitty/src/lib.rs +0 -2
  37. package/crates/wrightty-bridge-kitty/src/main.rs +0 -124
  38. package/crates/wrightty-bridge-kitty/src/rpc.rs +0 -304
  39. package/crates/wrightty-bridge-tmux/Cargo.toml +0 -26
  40. package/crates/wrightty-bridge-tmux/src/lib.rs +0 -2
  41. package/crates/wrightty-bridge-tmux/src/main.rs +0 -119
  42. package/crates/wrightty-bridge-tmux/src/rpc.rs +0 -291
  43. package/crates/wrightty-bridge-tmux/src/tmux.rs +0 -215
  44. package/crates/wrightty-bridge-wezterm/Cargo.toml +0 -26
  45. package/crates/wrightty-bridge-wezterm/src/lib.rs +0 -2
  46. package/crates/wrightty-bridge-wezterm/src/main.rs +0 -119
  47. package/crates/wrightty-bridge-wezterm/src/rpc.rs +0 -339
  48. package/crates/wrightty-bridge-wezterm/src/wezterm.rs +0 -190
  49. package/crates/wrightty-bridge-zellij/Cargo.toml +0 -27
  50. package/crates/wrightty-bridge-zellij/src/lib.rs +0 -2
  51. package/crates/wrightty-bridge-zellij/src/main.rs +0 -125
  52. package/crates/wrightty-bridge-zellij/src/rpc.rs +0 -328
  53. package/crates/wrightty-bridge-zellij/src/zellij.rs +0 -199
  54. package/crates/wrightty-client/Cargo.toml +0 -16
  55. package/crates/wrightty-client/src/client.rs +0 -254
  56. package/crates/wrightty-client/src/lib.rs +0 -2
  57. package/crates/wrightty-core/Cargo.toml +0 -21
  58. package/crates/wrightty-core/src/input.rs +0 -212
  59. package/crates/wrightty-core/src/lib.rs +0 -4
  60. package/crates/wrightty-core/src/screen.rs +0 -325
  61. package/crates/wrightty-core/src/session.rs +0 -249
  62. package/crates/wrightty-core/src/session_manager.rs +0 -77
  63. package/crates/wrightty-protocol/Cargo.toml +0 -13
  64. package/crates/wrightty-protocol/src/error.rs +0 -8
  65. package/crates/wrightty-protocol/src/events.rs +0 -138
  66. package/crates/wrightty-protocol/src/lib.rs +0 -4
  67. package/crates/wrightty-protocol/src/methods.rs +0 -321
  68. package/crates/wrightty-protocol/src/types.rs +0 -201
  69. package/crates/wrightty-server/Cargo.toml +0 -23
  70. package/crates/wrightty-server/src/lib.rs +0 -2
  71. package/crates/wrightty-server/src/main.rs +0 -65
  72. package/crates/wrightty-server/src/rpc.rs +0 -455
  73. package/crates/wrightty-server/src/state.rs +0 -39
  74. package/examples/basic_command.py +0 -53
  75. package/examples/interactive_tui.py +0 -86
  76. package/examples/record_session.py +0 -96
  77. package/install.sh +0 -81
  78. package/sdks/node/package-lock.json +0 -85
  79. package/sdks/node/package.json +0 -44
  80. package/sdks/node/src/client.ts +0 -94
  81. package/sdks/node/src/index.ts +0 -19
  82. package/sdks/node/src/terminal.ts +0 -258
  83. package/sdks/node/src/types.ts +0 -105
  84. package/sdks/node/tsconfig.json +0 -17
  85. package/sdks/python/README.md +0 -96
  86. package/sdks/python/pyproject.toml +0 -42
  87. package/sdks/python/wrightty/__init__.py +0 -6
  88. package/sdks/python/wrightty/cli.py +0 -210
  89. package/sdks/python/wrightty/client.py +0 -136
  90. package/sdks/python/wrightty/mcp_server.py +0 -434
  91. package/sdks/python/wrightty/terminal.py +0 -333
  92. package/skills/wrightty/SKILL.md +0 -261
  93. package/src/lib.rs +0 -1
  94. package/tests/integration_test.rs +0 -618
@@ -1,333 +0,0 @@
1
- """High-level Terminal API for AI agents and automation."""
2
-
3
- from __future__ import annotations
4
-
5
- import re
6
- import time
7
- from typing import Any
8
-
9
- from wrightty.client import WrighttyClient, WrighttyError
10
-
11
- PORT_RANGE_START = 9420
12
- PORT_RANGE_END = 9520
13
-
14
-
15
- class Terminal:
16
- """High-level terminal automation interface.
17
-
18
- Usage:
19
- # Connect to a running wrightty server (daemon or native emulator)
20
- term = Terminal.connect()
21
- output = term.run("ls -la")
22
- print(output)
23
-
24
- # Spawn a new session on a wrightty-server daemon
25
- term = Terminal.spawn()
26
- term.run("echo hello")
27
- term.close()
28
- """
29
-
30
- def __init__(self, client: WrighttyClient, session_id: str):
31
- self._client = client
32
- self._session_id = session_id
33
- self._prompt_pattern = r"[$#>%]\s*$"
34
-
35
- @staticmethod
36
- def discover(host: str = "127.0.0.1") -> list[dict]:
37
- """Scan for running wrightty servers on ports 9420-9520.
38
-
39
- Returns a list of dicts with keys: url, version, implementation, capabilities.
40
-
41
- Example:
42
- servers = Terminal.discover()
43
- for s in servers:
44
- print(f"{s['url']} — {s['implementation']}")
45
- # ws://127.0.0.1:9420 — wrightty (headless)
46
- # ws://127.0.0.1:9421 — wrightty (wezterm bridge)
47
- """
48
- found = []
49
- for port in range(PORT_RANGE_START, PORT_RANGE_END + 1):
50
- url = f"ws://{host}:{port}"
51
- try:
52
- client = WrighttyClient.connect(url)
53
- info = client.request("Wrightty.getInfo")
54
- client.close()
55
- found.append({
56
- "url": url,
57
- "port": port,
58
- "version": info.get("version", "unknown"),
59
- "implementation": info.get("implementation", "unknown"),
60
- "capabilities": info.get("capabilities", {}),
61
- })
62
- except (ConnectionError, ConnectionRefusedError, OSError, WrighttyError):
63
- continue
64
- return found
65
-
66
- @classmethod
67
- def connect(
68
- cls,
69
- url: str | None = None,
70
- session_id: str | None = None,
71
- ) -> Terminal:
72
- """Connect to a wrightty server.
73
-
74
- If no URL is given, auto-discovers the first available server
75
- by scanning ports 9420-9440.
76
- """
77
- if url is None:
78
- servers = cls.discover()
79
- if not servers:
80
- raise ConnectionError(
81
- "No wrightty server found. Start one with:\n"
82
- " wrightty term --headless\n"
83
- " wrightty term --bridge-tmux\n"
84
- " wrightty term --bridge-wezterm"
85
- )
86
- url = servers[0]["url"]
87
-
88
- client = WrighttyClient.connect(url)
89
-
90
- if session_id is None:
91
- result = client.request("Session.list")
92
- sessions = result.get("sessions", [])
93
- if sessions:
94
- session_id = sessions[0]["sessionId"]
95
- else:
96
- session_id = "0"
97
-
98
- return cls(client, session_id)
99
-
100
- @classmethod
101
- def spawn(
102
- cls,
103
- shell: str | None = None,
104
- cols: int = 120,
105
- rows: int = 40,
106
- cwd: str | None = None,
107
- server_url: str = "ws://127.0.0.1:9420",
108
- ) -> Terminal:
109
- """Connect to a wrightty-server daemon and create a new session."""
110
- client = WrighttyClient.connect(server_url)
111
-
112
- params: dict[str, Any] = {"cols": cols, "rows": rows}
113
- if shell:
114
- params["shell"] = shell
115
- if cwd:
116
- params["cwd"] = cwd
117
-
118
- result = client.request("Session.create", params)
119
- session_id = result["sessionId"]
120
-
121
- term = cls(client, session_id)
122
- term.wait_for_prompt(timeout=5)
123
- return term
124
-
125
- def close(self):
126
- """Close the connection."""
127
- self._client.close()
128
-
129
- def __enter__(self):
130
- return self
131
-
132
- def __exit__(self, *args):
133
- self.close()
134
-
135
- # --- High-level API ---
136
-
137
- def run(self, command: str, timeout: float = 30) -> str:
138
- """Run a command and return its output.
139
-
140
- Sends the command, waits for the prompt to reappear, and returns
141
- everything between the command echo and the next prompt.
142
- """
143
- self.send_text(command + "\n")
144
-
145
- # Wait for prompt to come back.
146
- self.wait_for_prompt(timeout=timeout)
147
-
148
- # Read screen and extract output.
149
- screen = self.read_screen()
150
- lines = screen.strip().split("\n")
151
-
152
- output_lines = []
153
- found_cmd = False
154
- for line in lines:
155
- if not found_cmd:
156
- if command in line:
157
- found_cmd = True
158
- continue
159
- if re.search(self._prompt_pattern, line):
160
- break
161
- output_lines.append(line)
162
-
163
- return "\n".join(output_lines)
164
-
165
- def send_text(self, text: str):
166
- """Send raw text to the terminal."""
167
- self._client.request("Input.sendText", {"sessionId": self._session_id, "text": text})
168
-
169
- def send_keys(self, *keys: str):
170
- """Send structured keystrokes.
171
-
172
- Examples:
173
- term.send_keys("Ctrl+c")
174
- term.send_keys("ArrowUp", "Enter")
175
- term.send_keys("Escape", ":", "w", "q", "Enter") # vim :wq
176
- """
177
- self._client.request(
178
- "Input.sendKeys", {"sessionId": self._session_id, "keys": list(keys)}
179
- )
180
-
181
- def read_screen(self) -> str:
182
- """Read the current visible screen as text."""
183
- result = self._client.request("Screen.getText", {"sessionId": self._session_id})
184
- return result["text"]
185
-
186
- def screenshot(self, format: str = "svg") -> str | bytes:
187
- """Take a screenshot. Returns str for text/svg, bytes for png."""
188
- result = self._client.request(
189
- "Screen.screenshot", {"sessionId": self._session_id, "format": format}
190
- )
191
- data = result["data"]
192
- if format == "png":
193
- import base64
194
- return base64.b64decode(data)
195
- return data
196
-
197
- def wait_for(self, pattern: str, timeout: float = 30, regex: bool = False) -> str:
198
- """Wait until a pattern appears on screen. Returns the screen text when found."""
199
- deadline = time.monotonic() + timeout
200
- while time.monotonic() < deadline:
201
- screen = self.read_screen()
202
- if regex:
203
- if re.search(pattern, screen):
204
- return screen
205
- else:
206
- if pattern in screen:
207
- return screen
208
- time.sleep(0.2)
209
- raise TimeoutError(f"Pattern {pattern!r} not found within {timeout}s")
210
-
211
- def wait_for_prompt(self, timeout: float = 10) -> str:
212
- """Wait for the shell prompt to appear."""
213
- return self.wait_for(self._prompt_pattern, timeout=timeout, regex=True)
214
-
215
- def set_prompt_pattern(self, pattern: str):
216
- """Override the regex used to detect the shell prompt."""
217
- self._prompt_pattern = pattern
218
-
219
- def get_size(self) -> tuple[int, int]:
220
- """Get terminal dimensions as (cols, rows)."""
221
- result = self._client.request("Terminal.getSize", {"sessionId": self._session_id})
222
- return result["cols"], result["rows"]
223
-
224
- def resize(self, cols: int, rows: int):
225
- """Resize the terminal."""
226
- self._client.request(
227
- "Terminal.resize", {"sessionId": self._session_id, "cols": cols, "rows": rows}
228
- )
229
-
230
- def get_info(self) -> dict:
231
- """Get server info and capabilities."""
232
- return self._client.request("Wrightty.getInfo")
233
-
234
- # --- Recording ---
235
-
236
- def start_session_recording(self, include_input: bool = False) -> str:
237
- """Start recording raw PTY I/O (asciicast v2 format).
238
-
239
- Returns a recording ID. Stop with stop_session_recording().
240
- The recording can be played back with `asciinema play`.
241
- """
242
- result = self._client.request(
243
- "Recording.startSession",
244
- {"sessionId": self._session_id, "includeInput": include_input},
245
- )
246
- return result["recordingId"]
247
-
248
- def stop_session_recording(self, recording_id: str) -> dict:
249
- """Stop a session recording and return asciicast data.
250
-
251
- Returns dict with keys: format, data, duration, events.
252
- Save `data` to a .cast file for asciinema playback.
253
- """
254
- return self._client.request("Recording.stopSession", {"recordingId": recording_id})
255
-
256
- def start_action_recording(self, format: str = "python") -> str:
257
- """Start recording wrightty API calls as a replayable script.
258
-
259
- Args:
260
- format: "python", "json", or "cli"
261
-
262
- Returns a recording ID. Stop with stop_action_recording().
263
- """
264
- result = self._client.request(
265
- "Recording.startActions",
266
- {"sessionId": self._session_id, "format": format},
267
- )
268
- return result["recordingId"]
269
-
270
- def stop_action_recording(self, recording_id: str) -> dict:
271
- """Stop action recording and return the generated script.
272
-
273
- Returns dict with keys: format, data, actions, duration.
274
- """
275
- return self._client.request("Recording.stopActions", {"recordingId": recording_id})
276
-
277
- def capture_screen(self, format: str = "svg") -> dict:
278
- """Capture a single screen frame.
279
-
280
- Returns dict with keys: frameId, timestamp, format, data.
281
- """
282
- return self._client.request(
283
- "Recording.captureScreen",
284
- {"sessionId": self._session_id, "format": format},
285
- )
286
-
287
- def start_screen_recording(self, interval_ms: int = 1000, format: str = "svg") -> str:
288
- """Start periodic screen capture.
289
-
290
- Args:
291
- interval_ms: Capture interval in milliseconds (default: 1000)
292
- format: "svg", "text", or "png"
293
-
294
- Returns a recording ID. Stop with stop_screen_recording().
295
- """
296
- result = self._client.request(
297
- "Recording.startScreenCapture",
298
- {"sessionId": self._session_id, "intervalMs": interval_ms, "format": format},
299
- )
300
- return result["recordingId"]
301
-
302
- def stop_screen_recording(self, recording_id: str) -> dict:
303
- """Stop screen capture and return all frames.
304
-
305
- Returns dict with keys: frames, duration, frameCount, format.
306
- """
307
- return self._client.request("Recording.stopScreenCapture", {"recordingId": recording_id})
308
-
309
- def start_video(self, fps: int = 30, format: str = "mp4") -> str:
310
- """Start framebuffer video recording (native emulators only).
311
-
312
- Captures the actual rendered pixels via glReadPixels and pipes to ffmpeg.
313
- Requires ffmpeg installed on the system.
314
-
315
- Args:
316
- fps: Frames per second (default: 30)
317
- format: "mp4", "webm", or "gif"
318
-
319
- Returns a recording ID. Stop with stop_video().
320
- """
321
- result = self._client.request(
322
- "Recording.startVideo",
323
- {"sessionId": self._session_id, "fps": fps, "format": format},
324
- )
325
- return result["recordingId"]
326
-
327
- def stop_video(self, recording_id: str) -> dict:
328
- """Stop video recording and return metadata.
329
-
330
- Returns dict with keys: format, path, duration, frames, width, height, size.
331
- The video file is at the returned `path`.
332
- """
333
- return self._client.request("Recording.stopVideo", {"recordingId": recording_id})
@@ -1,261 +0,0 @@
1
- ---
2
- name: wrightty
3
- description: Control terminal emulators programmatically via the wrightty protocol. Use when you need to run commands in a real terminal, interact with TUI applications, read terminal screen output, take screenshots, send keystrokes, or automate terminal workflows. Triggers on tasks involving terminal automation, running shell commands through a real PTY, controlling vim/htop/other TUI apps, or capturing terminal output.
4
- license: MIT
5
- metadata:
6
- author: moejay
7
- version: "0.1.0"
8
- ---
9
-
10
- # wrightty — Terminal Automation Protocol
11
-
12
- Wrightty lets you control terminal emulators over WebSocket JSON-RPC. You can run commands, read the screen, send keystrokes, and take screenshots — just like a human would interact with a terminal, but programmatically.
13
-
14
- ## When to use
15
-
16
- - Running shell commands in a real terminal (not subprocess) where you need the full PTY environment
17
- - Interacting with TUI applications (vim, htop, less, etc.)
18
- - Reading terminal screen output with colors and formatting
19
- - Taking terminal screenshots (SVG)
20
- - Automating interactive terminal workflows
21
- - Waiting for specific output to appear before proceeding
22
-
23
- ## Setup
24
-
25
- Wrightty needs a terminal backend. Pick one:
26
-
27
- **Alacritty (native, zero overhead):**
28
- ```bash
29
- git clone -b wrightty-support https://github.com/moejay/alacritty.git
30
- cd alacritty && cargo build --features wrightty
31
- ./target/debug/alacritty --wrightty # listens on ws://127.0.0.1:9420
32
- ```
33
-
34
- **WezTerm (via bridge):**
35
- ```bash
36
- # Start WezTerm normally, then:
37
- cargo run -p wrightty-bridge-wezterm # listens on ws://127.0.0.1:9421
38
-
39
- # For flatpak:
40
- WEZTERM_CMD="flatpak run --command=wezterm org.wezfurlong.wezterm" cargo run -p wrightty-bridge-wezterm
41
- ```
42
-
43
- **Headless daemon (no GUI):**
44
- ```bash
45
- cargo run -p wrightty-server # listens on ws://127.0.0.1:9420
46
- ```
47
-
48
- ## Python SDK
49
-
50
- The SDK has zero external dependencies.
51
-
52
- ```python
53
- from wrightty import Terminal
54
-
55
- # Connect to a running terminal
56
- term = Terminal.connect() # ws://127.0.0.1:9420
57
- term = Terminal.connect("ws://127.0.0.1:9421") # WezTerm bridge
58
-
59
- # Run a command and get output
60
- output = term.run("cargo test", timeout=120)
61
-
62
- # Read the current screen
63
- screen = term.read_screen()
64
-
65
- # Wait for text to appear
66
- term.wait_for("tests passed", timeout=60)
67
- term.wait_for(r"error\[\w+\]", regex=True)
68
-
69
- # Send keystrokes (TUI apps)
70
- term.send_keys("Escape", ":", "w", "q", "Enter") # vim: save and quit
71
- term.send_keys("Ctrl+c") # interrupt
72
- term.send_keys("ArrowUp", "Enter") # repeat last command
73
-
74
- # Screenshots
75
- svg = term.screenshot("svg")
76
-
77
- # Terminal info
78
- cols, rows = term.get_size()
79
-
80
- term.close()
81
- ```
82
-
83
- Install: `pip install /path/to/wrightty/sdks/python` or set `PYTHONPATH=/path/to/wrightty/sdks/python`.
84
-
85
- ## CLI
86
-
87
- ```bash
88
- wrightty run "ls -la" # run command, print output
89
- wrightty run "cargo test" --timeout 120 # with timeout
90
- wrightty read # dump current screen
91
- wrightty send-text "echo hello\n" # send raw text
92
- wrightty send-keys Ctrl+c # send keystroke
93
- wrightty send-keys Escape : w q Enter # vim: :wq
94
- wrightty wait-for "BUILD SUCCESS" --timeout 60 # block until text appears
95
- wrightty screenshot --format svg -o terminal.svg # take screenshot
96
- wrightty info # server capabilities
97
- wrightty size # terminal dimensions
98
- wrightty --url ws://127.0.0.1:9421 run "ls" # connect to different server
99
- ```
100
-
101
- ## MCP Server
102
-
103
- Add to your MCP config (Claude, Cursor, etc.):
104
-
105
- ```json
106
- {
107
- "mcpServers": {
108
- "wrightty": {
109
- "command": "python3",
110
- "args": ["-m", "wrightty.mcp_server"],
111
- "env": {
112
- "PYTHONPATH": "/path/to/wrightty/sdks/python",
113
- "WRIGHTTY_SOCKET": "ws://127.0.0.1:9420"
114
- }
115
- }
116
- }
117
- }
118
- ```
119
-
120
- Available MCP tools:
121
-
122
- | Tool | Description |
123
- |------|-------------|
124
- | `run_command` | Run a shell command, return output |
125
- | `read_terminal` | Read current screen text |
126
- | `send_keys` | Send keystrokes (`["Ctrl+c"]`, `["Escape", ":", "q", "Enter"]`) |
127
- | `send_text` | Send raw text (use `\n` for newline) |
128
- | `screenshot` | Terminal screenshot as SVG |
129
- | `wait_for_text` | Block until pattern appears on screen |
130
- | `terminal_info` | Terminal dimensions and capabilities |
131
-
132
- ## Protocol reference
133
-
134
- WebSocket JSON-RPC 2.0 on `ws://127.0.0.1:9420`. Full spec: [PROTOCOL.md](https://github.com/moejay/wrightty/blob/main/PROTOCOL.md).
135
-
136
- ### Core methods
137
-
138
- **Run a command:**
139
- ```json
140
- {"jsonrpc":"2.0","id":1,"method":"Input.sendText","params":{"sessionId":"0","text":"ls -la\n"}}
141
- ```
142
-
143
- **Read the screen:**
144
- ```json
145
- {"jsonrpc":"2.0","id":2,"method":"Screen.getText","params":{"sessionId":"0"}}
146
- // → {"result":{"text":"$ ls -la\ntotal 140\ndrwxr-xr-x 8 user user 4096 ...\n$"}}
147
- ```
148
-
149
- **Send keystrokes:**
150
- ```json
151
- {"jsonrpc":"2.0","id":3,"method":"Input.sendKeys","params":{"sessionId":"0","keys":["Ctrl+c"]}}
152
- ```
153
-
154
- **Take a screenshot:**
155
- ```json
156
- {"jsonrpc":"2.0","id":4,"method":"Screen.screenshot","params":{"sessionId":"0","format":"svg"}}
157
- // → {"result":{"format":"svg","data":"<svg ...>"}}
158
- ```
159
-
160
- **Get terminal size:**
161
- ```json
162
- {"jsonrpc":"2.0","id":5,"method":"Terminal.getSize","params":{"sessionId":"0"}}
163
- // → {"result":{"cols":120,"rows":40}}
164
- ```
165
-
166
- **Server capabilities:**
167
- ```json
168
- {"jsonrpc":"2.0","id":6,"method":"Wrightty.getInfo","params":{}}
169
- ```
170
-
171
- ### Key names for sendKeys
172
-
173
- Single characters: `a`, `1`, `/`, `.`
174
- Special keys: `Enter`, `Tab`, `Escape`, `Backspace`, `Delete`, `ArrowUp`, `ArrowDown`, `ArrowLeft`, `ArrowRight`, `Home`, `End`, `PageUp`, `PageDown`, `F1`-`F12`
175
- Modifiers: `Ctrl+c`, `Alt+x`, `Shift+Tab`
176
-
177
- ### Session management (headless daemon only)
178
-
179
- ```json
180
- // Create session
181
- {"jsonrpc":"2.0","id":1,"method":"Session.create","params":{"cols":80,"rows":24}}
182
- // → {"result":{"sessionId":"abc-123"}}
183
-
184
- // List sessions
185
- {"jsonrpc":"2.0","id":2,"method":"Session.list","params":{}}
186
-
187
- // Destroy session
188
- {"jsonrpc":"2.0","id":3,"method":"Session.destroy","params":{"sessionId":"abc-123"}}
189
- ```
190
-
191
- Native emulator mode uses `"sessionId":"0"` for the active terminal. Session create/destroy is not supported — the emulator manages its own sessions.
192
-
193
- ## Recording
194
-
195
- Three types of recording:
196
-
197
- ### Session recording (asciicast)
198
- Records raw PTY I/O with timestamps. Compatible with `asciinema play`.
199
-
200
- ```python
201
- rec_id = term.start_session_recording(include_input=True)
202
- # ... do stuff ...
203
- result = term.stop_session_recording(rec_id)
204
- with open("session.cast", "w") as f:
205
- f.write(result["data"])
206
- # Play back: asciinema play session.cast
207
- ```
208
-
209
- ### Action recording (script generation)
210
- Records wrightty API calls as a replayable Python/JSON/CLI script. Like Playwright codegen.
211
-
212
- ```python
213
- rec_id = term.start_action_recording(format="python")
214
- # ... do stuff ...
215
- result = term.stop_action_recording(rec_id)
216
- print(result["data"]) # prints a Python script that replays the actions
217
- ```
218
-
219
- ### Screen capture
220
- Capture individual frames or record periodic screenshots.
221
-
222
- ```python
223
- # Single frame
224
- frame = term.capture_screen(format="svg")
225
-
226
- # Periodic capture
227
- rec_id = term.start_screen_recording(interval_ms=500, format="svg")
228
- # ... do stuff ...
229
- result = term.stop_screen_recording(rec_id)
230
- # result["frames"] is a list of SVG frames with timestamps
231
- ```
232
-
233
- ### Video recording (native emulators only, requires ffmpeg)
234
- Captures the actual rendered framebuffer — pixel perfect, including colors, fonts, sixel images, everything on screen.
235
-
236
- ```python
237
- rec_id = term.start_video(fps=30, format="mp4")
238
- # ... do stuff ...
239
- result = term.stop_video(rec_id)
240
- print(result["path"]) # /tmp/wrightty-rec_001.mp4
241
- ```
242
-
243
- ### CLI
244
- ```bash
245
- wrightty record -o session.cast # Ctrl+C to stop
246
- wrightty record-actions -o script.py # generates Python script
247
- wrightty record-actions --format cli # generates shell commands
248
- ```
249
-
250
- ### MCP tools
251
- - `start_recording` — starts session + action recording
252
- - `stop_recording` — stops and returns asciicast data + Python script
253
- - `capture_screen_frame` — single SVG snapshot
254
-
255
- ## Tips
256
-
257
- - Use `term.run()` for most commands — it sends the command and waits for the prompt to return
258
- - Use `term.send_keys()` for interactive/TUI apps where you need specific keystrokes
259
- - Use `term.wait_for()` before reading output from slow commands (builds, tests)
260
- - The default prompt detection pattern is `[$#>%]\s*$` — override with `term.set_prompt_pattern()` if your prompt is different
261
- - Screenshots are SVG by default — good for rendering in browsers and docs, and readable by vision models
package/src/lib.rs DELETED
@@ -1 +0,0 @@
1
- // Root package exists only for integration tests.