@moejay/wrightty 0.0.0
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.
- package/.github/workflows/ci.yml +90 -0
- package/.github/workflows/release.yml +177 -0
- package/Cargo.lock +2662 -0
- package/Cargo.toml +38 -0
- package/PROTOCOL.md +1351 -0
- package/README.md +386 -0
- package/agents/ceo/AGENTS.md +24 -0
- package/agents/ceo/HEARTBEAT.md +72 -0
- package/agents/ceo/SOUL.md +33 -0
- package/agents/ceo/TOOLS.md +3 -0
- package/agents/founding-engineer/AGENTS.md +44 -0
- package/crates/wrightty/Cargo.toml +43 -0
- package/crates/wrightty/src/client_cmds.rs +366 -0
- package/crates/wrightty/src/discover.rs +78 -0
- package/crates/wrightty/src/main.rs +100 -0
- package/crates/wrightty/src/server.rs +100 -0
- package/crates/wrightty/src/term.rs +338 -0
- package/crates/wrightty-bridge-ghostty/Cargo.toml +27 -0
- package/crates/wrightty-bridge-ghostty/src/ghostty.rs +422 -0
- package/crates/wrightty-bridge-ghostty/src/lib.rs +2 -0
- package/crates/wrightty-bridge-ghostty/src/main.rs +146 -0
- package/crates/wrightty-bridge-ghostty/src/rpc.rs +307 -0
- package/crates/wrightty-bridge-kitty/Cargo.toml +26 -0
- package/crates/wrightty-bridge-kitty/src/kitty.rs +269 -0
- package/crates/wrightty-bridge-kitty/src/lib.rs +2 -0
- package/crates/wrightty-bridge-kitty/src/main.rs +124 -0
- package/crates/wrightty-bridge-kitty/src/rpc.rs +304 -0
- package/crates/wrightty-bridge-tmux/Cargo.toml +26 -0
- package/crates/wrightty-bridge-tmux/src/lib.rs +2 -0
- package/crates/wrightty-bridge-tmux/src/main.rs +119 -0
- package/crates/wrightty-bridge-tmux/src/rpc.rs +291 -0
- package/crates/wrightty-bridge-tmux/src/tmux.rs +215 -0
- package/crates/wrightty-bridge-wezterm/Cargo.toml +26 -0
- package/crates/wrightty-bridge-wezterm/src/lib.rs +2 -0
- package/crates/wrightty-bridge-wezterm/src/main.rs +119 -0
- package/crates/wrightty-bridge-wezterm/src/rpc.rs +339 -0
- package/crates/wrightty-bridge-wezterm/src/wezterm.rs +190 -0
- package/crates/wrightty-bridge-zellij/Cargo.toml +27 -0
- package/crates/wrightty-bridge-zellij/src/lib.rs +2 -0
- package/crates/wrightty-bridge-zellij/src/main.rs +125 -0
- package/crates/wrightty-bridge-zellij/src/rpc.rs +328 -0
- package/crates/wrightty-bridge-zellij/src/zellij.rs +199 -0
- package/crates/wrightty-client/Cargo.toml +16 -0
- package/crates/wrightty-client/src/client.rs +254 -0
- package/crates/wrightty-client/src/lib.rs +2 -0
- package/crates/wrightty-core/Cargo.toml +21 -0
- package/crates/wrightty-core/src/input.rs +212 -0
- package/crates/wrightty-core/src/lib.rs +4 -0
- package/crates/wrightty-core/src/screen.rs +325 -0
- package/crates/wrightty-core/src/session.rs +249 -0
- package/crates/wrightty-core/src/session_manager.rs +77 -0
- package/crates/wrightty-protocol/Cargo.toml +13 -0
- package/crates/wrightty-protocol/src/error.rs +8 -0
- package/crates/wrightty-protocol/src/events.rs +138 -0
- package/crates/wrightty-protocol/src/lib.rs +4 -0
- package/crates/wrightty-protocol/src/methods.rs +321 -0
- package/crates/wrightty-protocol/src/types.rs +201 -0
- package/crates/wrightty-server/Cargo.toml +23 -0
- package/crates/wrightty-server/src/lib.rs +2 -0
- package/crates/wrightty-server/src/main.rs +65 -0
- package/crates/wrightty-server/src/rpc.rs +455 -0
- package/crates/wrightty-server/src/state.rs +39 -0
- package/examples/basic_command.py +53 -0
- package/examples/interactive_tui.py +86 -0
- package/examples/record_session.py +96 -0
- package/install.sh +81 -0
- package/package.json +24 -0
- package/sdks/node/package-lock.json +85 -0
- package/sdks/node/package.json +44 -0
- package/sdks/node/src/client.ts +94 -0
- package/sdks/node/src/index.ts +19 -0
- package/sdks/node/src/terminal.ts +258 -0
- package/sdks/node/src/types.ts +105 -0
- package/sdks/node/tsconfig.json +17 -0
- package/sdks/python/README.md +96 -0
- package/sdks/python/pyproject.toml +42 -0
- package/sdks/python/wrightty/__init__.py +6 -0
- package/sdks/python/wrightty/cli.py +210 -0
- package/sdks/python/wrightty/client.py +136 -0
- package/sdks/python/wrightty/mcp_server.py +434 -0
- package/sdks/python/wrightty/terminal.py +333 -0
- package/skills/wrightty/SKILL.md +261 -0
- package/src/lib.rs +1 -0
- package/tests/integration_test.rs +618 -0
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
"""Wrightty MCP Server — expose terminal control as tools for AI agents.
|
|
2
|
+
|
|
3
|
+
Run with:
|
|
4
|
+
python -m wrightty.mcp_server
|
|
5
|
+
# or via MCP config in Claude/Cursor/etc.
|
|
6
|
+
|
|
7
|
+
This exposes the following tools to AI agents:
|
|
8
|
+
- run_command: Run a shell command and return output
|
|
9
|
+
- read_terminal: Read the current terminal screen
|
|
10
|
+
- send_keys: Send keystrokes (for TUI apps)
|
|
11
|
+
- screenshot: Take a terminal screenshot (SVG for rendering)
|
|
12
|
+
- wait_for_text: Wait until specific text appears
|
|
13
|
+
- terminal_info: Get terminal info and dimensions
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import asyncio
|
|
19
|
+
import json
|
|
20
|
+
import os
|
|
21
|
+
import re
|
|
22
|
+
import time
|
|
23
|
+
from typing import Any
|
|
24
|
+
|
|
25
|
+
from mcp.server import Server
|
|
26
|
+
from mcp.server.stdio import stdio_server
|
|
27
|
+
from mcp.types import TextContent, ImageContent, Tool
|
|
28
|
+
|
|
29
|
+
from wrightty.client import WrighttyClient
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# Global client state.
|
|
33
|
+
_client: WrighttyClient | None = None
|
|
34
|
+
_session_id: str = "0"
|
|
35
|
+
_prompt_pattern = r"[$#>%]\s*$"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def get_client() -> WrighttyClient:
|
|
39
|
+
global _client
|
|
40
|
+
if _client is None:
|
|
41
|
+
url = os.environ.get("WRIGHTTY_SOCKET", "ws://127.0.0.1:9420")
|
|
42
|
+
_client = WrighttyClient.connect(url)
|
|
43
|
+
return _client
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def read_screen() -> str:
|
|
47
|
+
client = get_client()
|
|
48
|
+
result = client.request("Screen.getText", {"sessionId": _session_id})
|
|
49
|
+
return result["text"]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
async def wait_for_prompt(timeout: float = 10) -> str:
|
|
53
|
+
deadline = time.monotonic() + timeout
|
|
54
|
+
while time.monotonic() < deadline:
|
|
55
|
+
screen = await asyncio.to_thread(read_screen)
|
|
56
|
+
if re.search(_prompt_pattern, screen):
|
|
57
|
+
return screen
|
|
58
|
+
await asyncio.sleep(0.2)
|
|
59
|
+
return await asyncio.to_thread(read_screen)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
app = Server("wrightty")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@app.list_tools()
|
|
66
|
+
async def list_tools() -> list[Tool]:
|
|
67
|
+
return [
|
|
68
|
+
Tool(
|
|
69
|
+
name="run_command",
|
|
70
|
+
description=(
|
|
71
|
+
"Run a shell command in the terminal and return its output. "
|
|
72
|
+
"The command is typed into a real terminal, executed, and the output "
|
|
73
|
+
"is captured after the command completes (when the prompt returns). "
|
|
74
|
+
"Use this for any shell operation: building, testing, file manipulation, git, etc."
|
|
75
|
+
),
|
|
76
|
+
inputSchema={
|
|
77
|
+
"type": "object",
|
|
78
|
+
"properties": {
|
|
79
|
+
"command": {
|
|
80
|
+
"type": "string",
|
|
81
|
+
"description": "The shell command to run",
|
|
82
|
+
},
|
|
83
|
+
"timeout": {
|
|
84
|
+
"type": "number",
|
|
85
|
+
"description": "Max seconds to wait for the command to finish (default: 30)",
|
|
86
|
+
"default": 30,
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
"required": ["command"],
|
|
90
|
+
},
|
|
91
|
+
),
|
|
92
|
+
Tool(
|
|
93
|
+
name="read_terminal",
|
|
94
|
+
description=(
|
|
95
|
+
"Read the current visible content of the terminal screen. "
|
|
96
|
+
"Returns the text currently displayed, including any running program's output. "
|
|
97
|
+
"Useful for checking the state of long-running processes, TUI apps, or "
|
|
98
|
+
"reading content that was printed before."
|
|
99
|
+
),
|
|
100
|
+
inputSchema={
|
|
101
|
+
"type": "object",
|
|
102
|
+
"properties": {},
|
|
103
|
+
},
|
|
104
|
+
),
|
|
105
|
+
Tool(
|
|
106
|
+
name="send_keys",
|
|
107
|
+
description=(
|
|
108
|
+
"Send keystrokes to the terminal. Use this for interactive programs like vim, "
|
|
109
|
+
"htop, less, or any TUI application. Supports special keys and modifiers.\n\n"
|
|
110
|
+
"Key names: Enter, Tab, Escape, Backspace, Delete, ArrowUp, ArrowDown, "
|
|
111
|
+
"ArrowLeft, ArrowRight, Home, End, PageUp, PageDown, F1-F12\n"
|
|
112
|
+
"Modifiers: Ctrl+c, Alt+x, Shift+Tab\n"
|
|
113
|
+
"Single characters: a, b, 1, /, etc."
|
|
114
|
+
),
|
|
115
|
+
inputSchema={
|
|
116
|
+
"type": "object",
|
|
117
|
+
"properties": {
|
|
118
|
+
"keys": {
|
|
119
|
+
"type": "array",
|
|
120
|
+
"items": {"type": "string"},
|
|
121
|
+
"description": 'List of keys to send, e.g. ["Escape", ":", "w", "q", "Enter"]',
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
"required": ["keys"],
|
|
125
|
+
},
|
|
126
|
+
),
|
|
127
|
+
Tool(
|
|
128
|
+
name="send_text",
|
|
129
|
+
description=(
|
|
130
|
+
"Send raw text to the terminal without any key interpretation. "
|
|
131
|
+
"Use \\n for newline. Useful for pasting content or sending multi-line input."
|
|
132
|
+
),
|
|
133
|
+
inputSchema={
|
|
134
|
+
"type": "object",
|
|
135
|
+
"properties": {
|
|
136
|
+
"text": {
|
|
137
|
+
"type": "string",
|
|
138
|
+
"description": "The text to send",
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
"required": ["text"],
|
|
142
|
+
},
|
|
143
|
+
),
|
|
144
|
+
Tool(
|
|
145
|
+
name="screenshot",
|
|
146
|
+
description=(
|
|
147
|
+
"Take a screenshot of the terminal. Returns an SVG image showing the terminal "
|
|
148
|
+
"with colors, fonts, and styling. Useful for understanding visual layout of TUI apps."
|
|
149
|
+
),
|
|
150
|
+
inputSchema={
|
|
151
|
+
"type": "object",
|
|
152
|
+
"properties": {
|
|
153
|
+
"format": {
|
|
154
|
+
"type": "string",
|
|
155
|
+
"enum": ["text", "svg"],
|
|
156
|
+
"default": "svg",
|
|
157
|
+
"description": "Screenshot format: 'text' for plain text, 'svg' for styled image",
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
),
|
|
162
|
+
Tool(
|
|
163
|
+
name="wait_for_text",
|
|
164
|
+
description=(
|
|
165
|
+
"Wait until specific text appears on the terminal screen. "
|
|
166
|
+
"Blocks until the text is found or timeout is reached. "
|
|
167
|
+
"Useful for waiting for compilation, test results, prompts, etc."
|
|
168
|
+
),
|
|
169
|
+
inputSchema={
|
|
170
|
+
"type": "object",
|
|
171
|
+
"properties": {
|
|
172
|
+
"pattern": {
|
|
173
|
+
"type": "string",
|
|
174
|
+
"description": "Text to wait for",
|
|
175
|
+
},
|
|
176
|
+
"timeout": {
|
|
177
|
+
"type": "number",
|
|
178
|
+
"description": "Max seconds to wait (default: 30)",
|
|
179
|
+
"default": 30,
|
|
180
|
+
},
|
|
181
|
+
"regex": {
|
|
182
|
+
"type": "boolean",
|
|
183
|
+
"description": "Treat pattern as regex (default: false)",
|
|
184
|
+
"default": False,
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
"required": ["pattern"],
|
|
188
|
+
},
|
|
189
|
+
),
|
|
190
|
+
Tool(
|
|
191
|
+
name="terminal_info",
|
|
192
|
+
description="Get terminal information: dimensions, server version, capabilities.",
|
|
193
|
+
inputSchema={
|
|
194
|
+
"type": "object",
|
|
195
|
+
"properties": {},
|
|
196
|
+
},
|
|
197
|
+
),
|
|
198
|
+
Tool(
|
|
199
|
+
name="start_recording",
|
|
200
|
+
description=(
|
|
201
|
+
"Start recording the terminal session. Records raw PTY I/O in asciicast v2 format "
|
|
202
|
+
"(compatible with asciinema). Also optionally records all wrightty actions as a "
|
|
203
|
+
"replayable Python script. Returns recording IDs to pass to stop_recording."
|
|
204
|
+
),
|
|
205
|
+
inputSchema={
|
|
206
|
+
"type": "object",
|
|
207
|
+
"properties": {
|
|
208
|
+
"include_input": {
|
|
209
|
+
"type": "boolean",
|
|
210
|
+
"description": "Also record input keystrokes (default: false)",
|
|
211
|
+
"default": False,
|
|
212
|
+
},
|
|
213
|
+
"record_actions": {
|
|
214
|
+
"type": "boolean",
|
|
215
|
+
"description": "Also record API actions as a Python script (default: true)",
|
|
216
|
+
"default": True,
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
),
|
|
221
|
+
Tool(
|
|
222
|
+
name="stop_recording",
|
|
223
|
+
description=(
|
|
224
|
+
"Stop recording and return the session recording (asciicast) and/or action script. "
|
|
225
|
+
"The asciicast data can be saved to a .cast file and played with asciinema."
|
|
226
|
+
),
|
|
227
|
+
inputSchema={
|
|
228
|
+
"type": "object",
|
|
229
|
+
"properties": {
|
|
230
|
+
"session_recording_id": {
|
|
231
|
+
"type": "string",
|
|
232
|
+
"description": "Recording ID from start_recording (session)",
|
|
233
|
+
},
|
|
234
|
+
"action_recording_id": {
|
|
235
|
+
"type": "string",
|
|
236
|
+
"description": "Recording ID from start_recording (actions)",
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
),
|
|
241
|
+
Tool(
|
|
242
|
+
name="capture_screen_frame",
|
|
243
|
+
description=(
|
|
244
|
+
"Capture a single screen frame as SVG. Use this to take snapshots at key moments "
|
|
245
|
+
"during a session. Each call returns one frame with a timestamp."
|
|
246
|
+
),
|
|
247
|
+
inputSchema={
|
|
248
|
+
"type": "object",
|
|
249
|
+
"properties": {
|
|
250
|
+
"format": {
|
|
251
|
+
"type": "string",
|
|
252
|
+
"enum": ["svg", "text"],
|
|
253
|
+
"default": "svg",
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
),
|
|
258
|
+
]
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
@app.call_tool()
|
|
262
|
+
async def call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent | ImageContent]:
|
|
263
|
+
client = get_client()
|
|
264
|
+
|
|
265
|
+
if name == "run_command":
|
|
266
|
+
command = arguments["command"]
|
|
267
|
+
timeout = arguments.get("timeout", 30)
|
|
268
|
+
|
|
269
|
+
# Send command.
|
|
270
|
+
await asyncio.to_thread(
|
|
271
|
+
client.request, "Input.sendText", {"sessionId": _session_id, "text": command + "\n"}
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
# Wait for prompt to return.
|
|
275
|
+
deadline = time.monotonic() + timeout
|
|
276
|
+
while time.monotonic() < deadline:
|
|
277
|
+
screen = await asyncio.to_thread(read_screen)
|
|
278
|
+
lines = screen.strip().split("\n")
|
|
279
|
+
if lines and re.search(_prompt_pattern, lines[-1]) and command not in lines[-1]:
|
|
280
|
+
break
|
|
281
|
+
await asyncio.sleep(0.3)
|
|
282
|
+
|
|
283
|
+
# Read final screen and extract output.
|
|
284
|
+
screen = await asyncio.to_thread(read_screen)
|
|
285
|
+
lines = screen.strip().split("\n")
|
|
286
|
+
|
|
287
|
+
output_lines = []
|
|
288
|
+
found_cmd = False
|
|
289
|
+
for line in lines:
|
|
290
|
+
if not found_cmd:
|
|
291
|
+
if command in line:
|
|
292
|
+
found_cmd = True
|
|
293
|
+
continue
|
|
294
|
+
if re.search(_prompt_pattern, line):
|
|
295
|
+
break
|
|
296
|
+
output_lines.append(line)
|
|
297
|
+
|
|
298
|
+
output = "\n".join(output_lines) if output_lines else screen
|
|
299
|
+
return [TextContent(type="text", text=output)]
|
|
300
|
+
|
|
301
|
+
elif name == "read_terminal":
|
|
302
|
+
screen = await asyncio.to_thread(read_screen)
|
|
303
|
+
return [TextContent(type="text", text=screen)]
|
|
304
|
+
|
|
305
|
+
elif name == "send_keys":
|
|
306
|
+
keys = arguments["keys"]
|
|
307
|
+
await asyncio.to_thread(
|
|
308
|
+
client.request, "Input.sendKeys", {"sessionId": _session_id, "keys": keys}
|
|
309
|
+
)
|
|
310
|
+
await asyncio.sleep(0.3)
|
|
311
|
+
screen = await asyncio.to_thread(read_screen)
|
|
312
|
+
return [TextContent(type="text", text=screen)]
|
|
313
|
+
|
|
314
|
+
elif name == "send_text":
|
|
315
|
+
text = arguments["text"]
|
|
316
|
+
await asyncio.to_thread(
|
|
317
|
+
client.request, "Input.sendText", {"sessionId": _session_id, "text": text}
|
|
318
|
+
)
|
|
319
|
+
await asyncio.sleep(0.3)
|
|
320
|
+
screen = await asyncio.to_thread(read_screen)
|
|
321
|
+
return [TextContent(type="text", text=screen)]
|
|
322
|
+
|
|
323
|
+
elif name == "screenshot":
|
|
324
|
+
fmt = arguments.get("format", "svg")
|
|
325
|
+
result = await asyncio.to_thread(
|
|
326
|
+
client.request, "Screen.screenshot", {"sessionId": _session_id, "format": fmt}
|
|
327
|
+
)
|
|
328
|
+
return [TextContent(type="text", text=result["data"])]
|
|
329
|
+
|
|
330
|
+
elif name == "wait_for_text":
|
|
331
|
+
pattern = arguments["pattern"]
|
|
332
|
+
timeout = arguments.get("timeout", 30)
|
|
333
|
+
is_regex = arguments.get("regex", False)
|
|
334
|
+
|
|
335
|
+
deadline = time.monotonic() + timeout
|
|
336
|
+
while time.monotonic() < deadline:
|
|
337
|
+
screen = await asyncio.to_thread(read_screen)
|
|
338
|
+
if is_regex:
|
|
339
|
+
if re.search(pattern, screen):
|
|
340
|
+
return [TextContent(type="text", text=screen)]
|
|
341
|
+
else:
|
|
342
|
+
if pattern in screen:
|
|
343
|
+
return [TextContent(type="text", text=screen)]
|
|
344
|
+
await asyncio.sleep(0.3)
|
|
345
|
+
|
|
346
|
+
return [TextContent(type="text", text=f"Timeout: '{pattern}' not found after {timeout}s")]
|
|
347
|
+
|
|
348
|
+
elif name == "terminal_info":
|
|
349
|
+
info = await asyncio.to_thread(client.request, "Wrightty.getInfo")
|
|
350
|
+
size = await asyncio.to_thread(
|
|
351
|
+
client.request, "Terminal.getSize", {"sessionId": _session_id}
|
|
352
|
+
)
|
|
353
|
+
info["size"] = size
|
|
354
|
+
return [TextContent(type="text", text=json.dumps(info, indent=2))]
|
|
355
|
+
|
|
356
|
+
elif name == "start_recording":
|
|
357
|
+
results = {}
|
|
358
|
+
|
|
359
|
+
include_input = arguments.get("include_input", False)
|
|
360
|
+
result = await asyncio.to_thread(
|
|
361
|
+
client.request,
|
|
362
|
+
"Recording.startSession",
|
|
363
|
+
{"sessionId": _session_id, "includeInput": include_input},
|
|
364
|
+
)
|
|
365
|
+
results["sessionRecordingId"] = result["recordingId"]
|
|
366
|
+
|
|
367
|
+
if arguments.get("record_actions", True):
|
|
368
|
+
result = await asyncio.to_thread(
|
|
369
|
+
client.request,
|
|
370
|
+
"Recording.startActions",
|
|
371
|
+
{"sessionId": _session_id, "format": "python"},
|
|
372
|
+
)
|
|
373
|
+
results["actionRecordingId"] = result["recordingId"]
|
|
374
|
+
|
|
375
|
+
return [TextContent(type="text", text=json.dumps(results, indent=2))]
|
|
376
|
+
|
|
377
|
+
elif name == "stop_recording":
|
|
378
|
+
results = {}
|
|
379
|
+
|
|
380
|
+
session_id = arguments.get("session_recording_id")
|
|
381
|
+
if session_id:
|
|
382
|
+
result = await asyncio.to_thread(
|
|
383
|
+
client.request, "Recording.stopSession", {"recordingId": session_id}
|
|
384
|
+
)
|
|
385
|
+
results["session"] = {
|
|
386
|
+
"format": result.get("format"),
|
|
387
|
+
"duration": result.get("duration"),
|
|
388
|
+
"events": result.get("events"),
|
|
389
|
+
"data_length": len(result.get("data", "")),
|
|
390
|
+
}
|
|
391
|
+
import tempfile
|
|
392
|
+
cast_file = tempfile.NamedTemporaryFile(suffix=".cast", delete=False, mode="w")
|
|
393
|
+
cast_file.write(result["data"])
|
|
394
|
+
cast_file.close()
|
|
395
|
+
results["session"]["file"] = cast_file.name
|
|
396
|
+
|
|
397
|
+
action_id = arguments.get("action_recording_id")
|
|
398
|
+
if action_id:
|
|
399
|
+
result = await asyncio.to_thread(
|
|
400
|
+
client.request, "Recording.stopActions", {"recordingId": action_id}
|
|
401
|
+
)
|
|
402
|
+
results["actions"] = {
|
|
403
|
+
"format": result.get("format"),
|
|
404
|
+
"actions": result.get("actions"),
|
|
405
|
+
"duration": result.get("duration"),
|
|
406
|
+
"script": result.get("data"),
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return [TextContent(type="text", text=json.dumps(results, indent=2))]
|
|
410
|
+
|
|
411
|
+
elif name == "capture_screen_frame":
|
|
412
|
+
fmt = arguments.get("format", "svg")
|
|
413
|
+
result = await asyncio.to_thread(
|
|
414
|
+
client.request,
|
|
415
|
+
"Recording.captureScreen",
|
|
416
|
+
{"sessionId": _session_id, "format": fmt},
|
|
417
|
+
)
|
|
418
|
+
return [TextContent(type="text", text=result.get("data", ""))]
|
|
419
|
+
|
|
420
|
+
else:
|
|
421
|
+
return [TextContent(type="text", text=f"Unknown tool: {name}")]
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
async def serve():
|
|
425
|
+
async with stdio_server() as (read_stream, write_stream):
|
|
426
|
+
await app.run(read_stream, write_stream, app.create_initialization_options())
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def main():
|
|
430
|
+
asyncio.run(serve())
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
if __name__ == "__main__":
|
|
434
|
+
main()
|