@moejay/wrightty 0.0.0 → 0.1.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/dist/client.d.ts +14 -0
- package/dist/client.js +83 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +8 -0
- package/dist/terminal.d.ts +48 -0
- package/dist/terminal.js +210 -0
- package/dist/types.d.ts +90 -0
- package/dist/types.js +3 -0
- package/package.json +35 -15
- package/.github/workflows/ci.yml +0 -90
- package/.github/workflows/release.yml +0 -177
- package/Cargo.lock +0 -2662
- package/Cargo.toml +0 -38
- package/PROTOCOL.md +0 -1351
- package/README.md +0 -386
- package/agents/ceo/AGENTS.md +0 -24
- package/agents/ceo/HEARTBEAT.md +0 -72
- package/agents/ceo/SOUL.md +0 -33
- package/agents/ceo/TOOLS.md +0 -3
- package/agents/founding-engineer/AGENTS.md +0 -44
- package/crates/wrightty/Cargo.toml +0 -43
- package/crates/wrightty/src/client_cmds.rs +0 -366
- package/crates/wrightty/src/discover.rs +0 -78
- package/crates/wrightty/src/main.rs +0 -100
- package/crates/wrightty/src/server.rs +0 -100
- package/crates/wrightty/src/term.rs +0 -338
- package/crates/wrightty-bridge-ghostty/Cargo.toml +0 -27
- package/crates/wrightty-bridge-ghostty/src/ghostty.rs +0 -422
- package/crates/wrightty-bridge-ghostty/src/lib.rs +0 -2
- package/crates/wrightty-bridge-ghostty/src/main.rs +0 -146
- package/crates/wrightty-bridge-ghostty/src/rpc.rs +0 -307
- package/crates/wrightty-bridge-kitty/Cargo.toml +0 -26
- package/crates/wrightty-bridge-kitty/src/kitty.rs +0 -269
- package/crates/wrightty-bridge-kitty/src/lib.rs +0 -2
- package/crates/wrightty-bridge-kitty/src/main.rs +0 -124
- package/crates/wrightty-bridge-kitty/src/rpc.rs +0 -304
- package/crates/wrightty-bridge-tmux/Cargo.toml +0 -26
- package/crates/wrightty-bridge-tmux/src/lib.rs +0 -2
- package/crates/wrightty-bridge-tmux/src/main.rs +0 -119
- package/crates/wrightty-bridge-tmux/src/rpc.rs +0 -291
- package/crates/wrightty-bridge-tmux/src/tmux.rs +0 -215
- package/crates/wrightty-bridge-wezterm/Cargo.toml +0 -26
- package/crates/wrightty-bridge-wezterm/src/lib.rs +0 -2
- package/crates/wrightty-bridge-wezterm/src/main.rs +0 -119
- package/crates/wrightty-bridge-wezterm/src/rpc.rs +0 -339
- package/crates/wrightty-bridge-wezterm/src/wezterm.rs +0 -190
- package/crates/wrightty-bridge-zellij/Cargo.toml +0 -27
- package/crates/wrightty-bridge-zellij/src/lib.rs +0 -2
- package/crates/wrightty-bridge-zellij/src/main.rs +0 -125
- package/crates/wrightty-bridge-zellij/src/rpc.rs +0 -328
- package/crates/wrightty-bridge-zellij/src/zellij.rs +0 -199
- package/crates/wrightty-client/Cargo.toml +0 -16
- package/crates/wrightty-client/src/client.rs +0 -254
- package/crates/wrightty-client/src/lib.rs +0 -2
- package/crates/wrightty-core/Cargo.toml +0 -21
- package/crates/wrightty-core/src/input.rs +0 -212
- package/crates/wrightty-core/src/lib.rs +0 -4
- package/crates/wrightty-core/src/screen.rs +0 -325
- package/crates/wrightty-core/src/session.rs +0 -249
- package/crates/wrightty-core/src/session_manager.rs +0 -77
- package/crates/wrightty-protocol/Cargo.toml +0 -13
- package/crates/wrightty-protocol/src/error.rs +0 -8
- package/crates/wrightty-protocol/src/events.rs +0 -138
- package/crates/wrightty-protocol/src/lib.rs +0 -4
- package/crates/wrightty-protocol/src/methods.rs +0 -321
- package/crates/wrightty-protocol/src/types.rs +0 -201
- package/crates/wrightty-server/Cargo.toml +0 -23
- package/crates/wrightty-server/src/lib.rs +0 -2
- package/crates/wrightty-server/src/main.rs +0 -65
- package/crates/wrightty-server/src/rpc.rs +0 -455
- package/crates/wrightty-server/src/state.rs +0 -39
- package/examples/basic_command.py +0 -53
- package/examples/interactive_tui.py +0 -86
- package/examples/record_session.py +0 -96
- package/install.sh +0 -81
- package/sdks/node/package-lock.json +0 -85
- package/sdks/node/package.json +0 -44
- package/sdks/node/src/client.ts +0 -94
- package/sdks/node/src/index.ts +0 -19
- package/sdks/node/src/terminal.ts +0 -258
- package/sdks/node/src/types.ts +0 -105
- package/sdks/node/tsconfig.json +0 -17
- package/sdks/python/README.md +0 -96
- package/sdks/python/pyproject.toml +0 -42
- package/sdks/python/wrightty/__init__.py +0 -6
- package/sdks/python/wrightty/cli.py +0 -210
- package/sdks/python/wrightty/client.py +0 -136
- package/sdks/python/wrightty/mcp_server.py +0 -434
- package/sdks/python/wrightty/terminal.py +0 -333
- package/skills/wrightty/SKILL.md +0 -261
- package/src/lib.rs +0 -1
- package/tests/integration_test.rs +0 -618
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""record_session.py -- Start a session recording, run commands, save the asciicast file.
|
|
3
|
-
|
|
4
|
-
Demonstrates Wrightty's recording capabilities:
|
|
5
|
-
- Session recording produces an asciicast v2 (.cast) file
|
|
6
|
-
- Compatible with `asciinema play` for playback and sharing
|
|
7
|
-
|
|
8
|
-
Usage:
|
|
9
|
-
python examples/record_session.py
|
|
10
|
-
|
|
11
|
-
Prerequisites:
|
|
12
|
-
- wrightty-server running on ws://127.0.0.1:9420
|
|
13
|
-
Start with: cargo run -p wrightty-server
|
|
14
|
-
|
|
15
|
-
- Python SDK installed (from repo root):
|
|
16
|
-
pip install -e sdks/python
|
|
17
|
-
|
|
18
|
-
- Optional: asciinema installed to play back the recording
|
|
19
|
-
pip install asciinema or brew install asciinema
|
|
20
|
-
"""
|
|
21
|
-
|
|
22
|
-
import json
|
|
23
|
-
import os
|
|
24
|
-
|
|
25
|
-
from wrightty import Terminal
|
|
26
|
-
|
|
27
|
-
OUTPUT_PATH = "/tmp/wrightty_demo.cast"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def main():
|
|
31
|
-
print("Spawning terminal session...")
|
|
32
|
-
|
|
33
|
-
with Terminal.spawn(cols=100, rows=30) as term:
|
|
34
|
-
info = term.get_info()
|
|
35
|
-
print(f"Connected to {info['implementation']} v{info['version']}")
|
|
36
|
-
print()
|
|
37
|
-
|
|
38
|
-
# --- Start session recording ---
|
|
39
|
-
print("Starting session recording...")
|
|
40
|
-
recording_id = term.start_session_recording(include_input=False)
|
|
41
|
-
print(f"Recording ID: {recording_id}")
|
|
42
|
-
print()
|
|
43
|
-
|
|
44
|
-
# --- Run some commands while recording ---
|
|
45
|
-
print("Running commands (these will be captured in the recording)...")
|
|
46
|
-
|
|
47
|
-
output = term.run("echo 'Hello from Wrightty!'")
|
|
48
|
-
print(f" echo output: {output.strip()}")
|
|
49
|
-
|
|
50
|
-
output = term.run("date")
|
|
51
|
-
print(f" date output: {output.strip()}")
|
|
52
|
-
|
|
53
|
-
output = term.run("uname -s")
|
|
54
|
-
print(f" uname output: {output.strip()}")
|
|
55
|
-
|
|
56
|
-
output = term.run("echo 'Recording complete.'")
|
|
57
|
-
print(f" final echo: {output.strip()}")
|
|
58
|
-
print()
|
|
59
|
-
|
|
60
|
-
# --- Stop the recording ---
|
|
61
|
-
print("Stopping recording...")
|
|
62
|
-
result = term.stop_session_recording(recording_id)
|
|
63
|
-
|
|
64
|
-
asciicast_data = result["data"]
|
|
65
|
-
duration = result.get("duration", 0)
|
|
66
|
-
event_count = result.get("events", 0)
|
|
67
|
-
|
|
68
|
-
print(f"Recorded {event_count} events over {duration:.1f}s")
|
|
69
|
-
print()
|
|
70
|
-
|
|
71
|
-
# --- Save the asciicast file ---
|
|
72
|
-
with open(OUTPUT_PATH, "w") as f:
|
|
73
|
-
f.write(asciicast_data)
|
|
74
|
-
|
|
75
|
-
file_size = os.path.getsize(OUTPUT_PATH)
|
|
76
|
-
print(f"Saved asciicast to {OUTPUT_PATH} ({file_size} bytes)")
|
|
77
|
-
print()
|
|
78
|
-
|
|
79
|
-
# Show the asciicast header so the user can inspect it
|
|
80
|
-
lines = asciicast_data.splitlines()
|
|
81
|
-
if lines:
|
|
82
|
-
header = json.loads(lines[0])
|
|
83
|
-
print("Asciicast header:")
|
|
84
|
-
print(f" version: {header.get('version')}")
|
|
85
|
-
print(f" width: {header.get('width')}")
|
|
86
|
-
print(f" height: {header.get('height')}")
|
|
87
|
-
if "title" in header:
|
|
88
|
-
print(f" title: {header['title']}")
|
|
89
|
-
print()
|
|
90
|
-
|
|
91
|
-
print(f"Done. To play back the recording:")
|
|
92
|
-
print(f" asciinema play {OUTPUT_PATH}")
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if __name__ == "__main__":
|
|
96
|
-
main()
|
package/install.sh
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
#!/bin/sh
|
|
2
|
-
# Install wrightty — terminal automation CLI
|
|
3
|
-
# Usage: curl -fsSL https://raw.githubusercontent.com/moejay/wrightty/main/install.sh | sh
|
|
4
|
-
set -e
|
|
5
|
-
|
|
6
|
-
REPO="moejay/wrightty"
|
|
7
|
-
INSTALL_DIR="${WRIGHTTY_INSTALL_DIR:-/usr/local/bin}"
|
|
8
|
-
|
|
9
|
-
# Detect OS and architecture
|
|
10
|
-
OS="$(uname -s)"
|
|
11
|
-
ARCH="$(uname -m)"
|
|
12
|
-
|
|
13
|
-
case "$OS" in
|
|
14
|
-
Linux) OS_NAME="linux" ;;
|
|
15
|
-
Darwin) OS_NAME="macos" ;;
|
|
16
|
-
*)
|
|
17
|
-
echo "error: Unsupported OS: $OS"
|
|
18
|
-
echo "Build from source: cargo install --git https://github.com/$REPO wrightty"
|
|
19
|
-
exit 1
|
|
20
|
-
;;
|
|
21
|
-
esac
|
|
22
|
-
|
|
23
|
-
case "$ARCH" in
|
|
24
|
-
x86_64|amd64) ARCH_NAME="x86_64" ;;
|
|
25
|
-
aarch64|arm64) ARCH_NAME="aarch64" ;;
|
|
26
|
-
*)
|
|
27
|
-
echo "error: Unsupported architecture: $ARCH"
|
|
28
|
-
echo "Build from source: cargo install --git https://github.com/$REPO wrightty"
|
|
29
|
-
exit 1
|
|
30
|
-
;;
|
|
31
|
-
esac
|
|
32
|
-
|
|
33
|
-
ASSET_NAME="wrightty-${OS_NAME}-${ARCH_NAME}"
|
|
34
|
-
|
|
35
|
-
# Get latest release URL
|
|
36
|
-
if [ -n "$WRIGHTTY_VERSION" ]; then
|
|
37
|
-
TAG="v${WRIGHTTY_VERSION}"
|
|
38
|
-
URL="https://github.com/$REPO/releases/download/$TAG/${ASSET_NAME}.tar.gz"
|
|
39
|
-
else
|
|
40
|
-
URL="https://github.com/$REPO/releases/latest/download/${ASSET_NAME}.tar.gz"
|
|
41
|
-
fi
|
|
42
|
-
|
|
43
|
-
echo "Downloading wrightty for ${OS_NAME}/${ARCH_NAME}..."
|
|
44
|
-
TMPDIR="$(mktemp -d)"
|
|
45
|
-
trap 'rm -rf "$TMPDIR"' EXIT
|
|
46
|
-
|
|
47
|
-
if command -v curl >/dev/null 2>&1; then
|
|
48
|
-
curl -fsSL "$URL" -o "$TMPDIR/wrightty.tar.gz"
|
|
49
|
-
elif command -v wget >/dev/null 2>&1; then
|
|
50
|
-
wget -q "$URL" -O "$TMPDIR/wrightty.tar.gz"
|
|
51
|
-
else
|
|
52
|
-
echo "error: curl or wget required"
|
|
53
|
-
exit 1
|
|
54
|
-
fi
|
|
55
|
-
|
|
56
|
-
tar xzf "$TMPDIR/wrightty.tar.gz" -C "$TMPDIR"
|
|
57
|
-
|
|
58
|
-
# Install
|
|
59
|
-
if [ -w "$INSTALL_DIR" ]; then
|
|
60
|
-
mv "$TMPDIR/wrightty" "$INSTALL_DIR/wrightty"
|
|
61
|
-
else
|
|
62
|
-
echo "Installing to $INSTALL_DIR (requires sudo)..."
|
|
63
|
-
sudo mv "$TMPDIR/wrightty" "$INSTALL_DIR/wrightty"
|
|
64
|
-
fi
|
|
65
|
-
|
|
66
|
-
chmod +x "$INSTALL_DIR/wrightty"
|
|
67
|
-
|
|
68
|
-
echo ""
|
|
69
|
-
echo "wrightty installed to $INSTALL_DIR/wrightty"
|
|
70
|
-
echo ""
|
|
71
|
-
echo "Get started:"
|
|
72
|
-
echo " wrightty term --headless # start a headless terminal server"
|
|
73
|
-
echo " wrightty run \"echo hello\" # run a command"
|
|
74
|
-
echo " wrightty discover # find running servers"
|
|
75
|
-
echo ""
|
|
76
|
-
echo "Or bridge to your terminal:"
|
|
77
|
-
echo " wrightty term --bridge-tmux # if you use tmux"
|
|
78
|
-
echo " wrightty term --bridge-wezterm # if you use WezTerm"
|
|
79
|
-
echo " wrightty term --bridge-kitty # if you use Kitty"
|
|
80
|
-
echo " wrightty term --bridge-ghostty # if you use Ghostty"
|
|
81
|
-
echo " wrightty term --bridge-zellij # if you use Zellij"
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "wrightty",
|
|
3
|
-
"version": "0.1.0",
|
|
4
|
-
"lockfileVersion": 3,
|
|
5
|
-
"requires": true,
|
|
6
|
-
"packages": {
|
|
7
|
-
"": {
|
|
8
|
-
"name": "wrightty",
|
|
9
|
-
"version": "0.1.0",
|
|
10
|
-
"license": "MIT",
|
|
11
|
-
"dependencies": {
|
|
12
|
-
"ws": "^8.0.0"
|
|
13
|
-
},
|
|
14
|
-
"devDependencies": {
|
|
15
|
-
"@types/ws": "^8.0.0",
|
|
16
|
-
"typescript": "^5.0.0"
|
|
17
|
-
},
|
|
18
|
-
"engines": {
|
|
19
|
-
"node": ">=18.0.0"
|
|
20
|
-
}
|
|
21
|
-
},
|
|
22
|
-
"node_modules/@types/node": {
|
|
23
|
-
"version": "25.5.2",
|
|
24
|
-
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.2.tgz",
|
|
25
|
-
"integrity": "sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg==",
|
|
26
|
-
"dev": true,
|
|
27
|
-
"license": "MIT",
|
|
28
|
-
"dependencies": {
|
|
29
|
-
"undici-types": "~7.18.0"
|
|
30
|
-
}
|
|
31
|
-
},
|
|
32
|
-
"node_modules/@types/ws": {
|
|
33
|
-
"version": "8.18.1",
|
|
34
|
-
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
|
|
35
|
-
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
|
|
36
|
-
"dev": true,
|
|
37
|
-
"license": "MIT",
|
|
38
|
-
"dependencies": {
|
|
39
|
-
"@types/node": "*"
|
|
40
|
-
}
|
|
41
|
-
},
|
|
42
|
-
"node_modules/typescript": {
|
|
43
|
-
"version": "5.9.3",
|
|
44
|
-
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
|
45
|
-
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
|
46
|
-
"dev": true,
|
|
47
|
-
"license": "Apache-2.0",
|
|
48
|
-
"bin": {
|
|
49
|
-
"tsc": "bin/tsc",
|
|
50
|
-
"tsserver": "bin/tsserver"
|
|
51
|
-
},
|
|
52
|
-
"engines": {
|
|
53
|
-
"node": ">=14.17"
|
|
54
|
-
}
|
|
55
|
-
},
|
|
56
|
-
"node_modules/undici-types": {
|
|
57
|
-
"version": "7.18.2",
|
|
58
|
-
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
|
|
59
|
-
"integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
|
|
60
|
-
"dev": true,
|
|
61
|
-
"license": "MIT"
|
|
62
|
-
},
|
|
63
|
-
"node_modules/ws": {
|
|
64
|
-
"version": "8.20.0",
|
|
65
|
-
"resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz",
|
|
66
|
-
"integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==",
|
|
67
|
-
"license": "MIT",
|
|
68
|
-
"engines": {
|
|
69
|
-
"node": ">=10.0.0"
|
|
70
|
-
},
|
|
71
|
-
"peerDependencies": {
|
|
72
|
-
"bufferutil": "^4.0.1",
|
|
73
|
-
"utf-8-validate": ">=5.0.2"
|
|
74
|
-
},
|
|
75
|
-
"peerDependenciesMeta": {
|
|
76
|
-
"bufferutil": {
|
|
77
|
-
"optional": true
|
|
78
|
-
},
|
|
79
|
-
"utf-8-validate": {
|
|
80
|
-
"optional": true
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
package/sdks/node/package.json
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@moejay/wrightty",
|
|
3
|
-
"version": "0.1.0",
|
|
4
|
-
"description": "Node.js SDK for Wrightty terminal automation protocol",
|
|
5
|
-
"author": "Moe Jay",
|
|
6
|
-
"homepage": "https://github.com/moejay/wrightty",
|
|
7
|
-
"repository": {
|
|
8
|
-
"type": "git",
|
|
9
|
-
"url": "https://github.com/moejay/wrightty.git",
|
|
10
|
-
"directory": "sdks/node"
|
|
11
|
-
},
|
|
12
|
-
"bugs": {
|
|
13
|
-
"url": "https://github.com/moejay/wrightty/issues"
|
|
14
|
-
},
|
|
15
|
-
"main": "dist/index.js",
|
|
16
|
-
"types": "dist/index.d.ts",
|
|
17
|
-
"files": [
|
|
18
|
-
"dist"
|
|
19
|
-
],
|
|
20
|
-
"scripts": {
|
|
21
|
-
"build": "tsc",
|
|
22
|
-
"prepublishOnly": "npm run build"
|
|
23
|
-
},
|
|
24
|
-
"keywords": [
|
|
25
|
-
"terminal",
|
|
26
|
-
"automation",
|
|
27
|
-
"tui",
|
|
28
|
-
"playwright",
|
|
29
|
-
"testing",
|
|
30
|
-
"ai",
|
|
31
|
-
"agent"
|
|
32
|
-
],
|
|
33
|
-
"license": "MIT",
|
|
34
|
-
"dependencies": {
|
|
35
|
-
"ws": "^8.0.0"
|
|
36
|
-
},
|
|
37
|
-
"devDependencies": {
|
|
38
|
-
"@types/ws": "^8.0.0",
|
|
39
|
-
"typescript": "^5.0.0"
|
|
40
|
-
},
|
|
41
|
-
"engines": {
|
|
42
|
-
"node": ">=18.0.0"
|
|
43
|
-
}
|
|
44
|
-
}
|
package/sdks/node/src/client.ts
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
/** Low-level WebSocket JSON-RPC 2.0 client for the Wrightty protocol. */
|
|
2
|
-
|
|
3
|
-
import WebSocket from "ws";
|
|
4
|
-
|
|
5
|
-
export class WrighttyError extends Error {
|
|
6
|
-
constructor(
|
|
7
|
-
public code: number,
|
|
8
|
-
message: string,
|
|
9
|
-
) {
|
|
10
|
-
super(`[${code}] ${message}`);
|
|
11
|
-
this.name = "WrighttyError";
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export class WrighttyClient {
|
|
16
|
-
private ws: WebSocket;
|
|
17
|
-
private nextId = 1;
|
|
18
|
-
private pending = new Map<
|
|
19
|
-
number,
|
|
20
|
-
{
|
|
21
|
-
resolve: (value: any) => void;
|
|
22
|
-
reject: (reason: any) => void;
|
|
23
|
-
}
|
|
24
|
-
>();
|
|
25
|
-
|
|
26
|
-
private constructor(ws: WebSocket) {
|
|
27
|
-
this.ws = ws;
|
|
28
|
-
|
|
29
|
-
ws.on("message", (data: WebSocket.Data) => {
|
|
30
|
-
try {
|
|
31
|
-
const msg = JSON.parse(data.toString());
|
|
32
|
-
const entry = this.pending.get(msg.id);
|
|
33
|
-
if (!entry) return;
|
|
34
|
-
this.pending.delete(msg.id);
|
|
35
|
-
|
|
36
|
-
if (msg.error) {
|
|
37
|
-
entry.reject(
|
|
38
|
-
new WrighttyError(msg.error.code ?? -1, msg.error.message ?? "Unknown error"),
|
|
39
|
-
);
|
|
40
|
-
} else {
|
|
41
|
-
entry.resolve(msg.result);
|
|
42
|
-
}
|
|
43
|
-
} catch {
|
|
44
|
-
// Ignore malformed messages
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
ws.on("close", () => {
|
|
49
|
-
for (const [id, entry] of this.pending) {
|
|
50
|
-
entry.reject(new Error("Connection closed"));
|
|
51
|
-
this.pending.delete(id);
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
static connect(url: string, timeoutMs = 5000): Promise<WrighttyClient> {
|
|
57
|
-
return new Promise((resolve, reject) => {
|
|
58
|
-
const ws = new WebSocket(url);
|
|
59
|
-
const timer = setTimeout(() => {
|
|
60
|
-
ws.close();
|
|
61
|
-
reject(new Error(`Connection timeout after ${timeoutMs}ms: ${url}`));
|
|
62
|
-
}, timeoutMs);
|
|
63
|
-
|
|
64
|
-
ws.on("open", () => {
|
|
65
|
-
clearTimeout(timer);
|
|
66
|
-
resolve(new WrighttyClient(ws));
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
ws.on("error", (err) => {
|
|
70
|
-
clearTimeout(timer);
|
|
71
|
-
reject(err);
|
|
72
|
-
});
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
async request(method: string, params: Record<string, any> = {}): Promise<any> {
|
|
77
|
-
const id = this.nextId++;
|
|
78
|
-
const msg = JSON.stringify({ jsonrpc: "2.0", id, method, params });
|
|
79
|
-
|
|
80
|
-
return new Promise((resolve, reject) => {
|
|
81
|
-
this.pending.set(id, { resolve, reject });
|
|
82
|
-
this.ws.send(msg, (err) => {
|
|
83
|
-
if (err) {
|
|
84
|
-
this.pending.delete(id);
|
|
85
|
-
reject(err);
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
close(): void {
|
|
92
|
-
this.ws.close();
|
|
93
|
-
}
|
|
94
|
-
}
|
package/sdks/node/src/index.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
export { Terminal } from "./terminal";
|
|
2
|
-
export { WrighttyClient, WrighttyError } from "./client";
|
|
3
|
-
export type {
|
|
4
|
-
ServerInfo,
|
|
5
|
-
Capabilities,
|
|
6
|
-
ScreenshotFormat,
|
|
7
|
-
SessionInfo,
|
|
8
|
-
KeyInput,
|
|
9
|
-
KeyEvent,
|
|
10
|
-
TextMatch,
|
|
11
|
-
WaitForTextResult,
|
|
12
|
-
ScreenshotResult,
|
|
13
|
-
RecordingResult,
|
|
14
|
-
SessionRecordingData,
|
|
15
|
-
ActionRecordingData,
|
|
16
|
-
DiscoveredServer,
|
|
17
|
-
ConnectOptions,
|
|
18
|
-
SpawnOptions,
|
|
19
|
-
} from "./types";
|
|
@@ -1,258 +0,0 @@
|
|
|
1
|
-
/** High-level Terminal API for AI agents and automation. */
|
|
2
|
-
|
|
3
|
-
import { WrighttyClient } from "./client";
|
|
4
|
-
import type {
|
|
5
|
-
ConnectOptions,
|
|
6
|
-
DiscoveredServer,
|
|
7
|
-
ScreenshotFormat,
|
|
8
|
-
ScreenshotResult,
|
|
9
|
-
SessionInfo,
|
|
10
|
-
SpawnOptions,
|
|
11
|
-
WaitForTextResult,
|
|
12
|
-
SessionRecordingData,
|
|
13
|
-
ActionRecordingData,
|
|
14
|
-
} from "./types";
|
|
15
|
-
|
|
16
|
-
const PORT_RANGE_START = 9420;
|
|
17
|
-
const PORT_RANGE_END = 9520;
|
|
18
|
-
|
|
19
|
-
export class Terminal {
|
|
20
|
-
private client: WrighttyClient;
|
|
21
|
-
private sessionId: string;
|
|
22
|
-
private promptPattern = /[$#>%]\s*$/;
|
|
23
|
-
|
|
24
|
-
private constructor(client: WrighttyClient, sessionId: string) {
|
|
25
|
-
this.client = client;
|
|
26
|
-
this.sessionId = sessionId;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/** Scan for running wrightty servers on ports 9420-9520. */
|
|
30
|
-
static async discover(host = "127.0.0.1"): Promise<DiscoveredServer[]> {
|
|
31
|
-
const found: DiscoveredServer[] = [];
|
|
32
|
-
|
|
33
|
-
const checks = [];
|
|
34
|
-
for (let port = PORT_RANGE_START; port <= PORT_RANGE_END; port++) {
|
|
35
|
-
const url = `ws://${host}:${port}`;
|
|
36
|
-
checks.push(
|
|
37
|
-
WrighttyClient.connect(url, 200)
|
|
38
|
-
.then(async (client) => {
|
|
39
|
-
try {
|
|
40
|
-
const info = await client.request("Wrightty.getInfo");
|
|
41
|
-
found.push({
|
|
42
|
-
url,
|
|
43
|
-
port,
|
|
44
|
-
version: info.version ?? "unknown",
|
|
45
|
-
implementation: info.implementation ?? "unknown",
|
|
46
|
-
capabilities: info.capabilities ?? {},
|
|
47
|
-
});
|
|
48
|
-
} finally {
|
|
49
|
-
client.close();
|
|
50
|
-
}
|
|
51
|
-
})
|
|
52
|
-
.catch(() => {
|
|
53
|
-
/* port not listening */
|
|
54
|
-
}),
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
await Promise.all(checks);
|
|
59
|
-
return found.sort((a, b) => a.port - b.port);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/** Connect to a wrightty server. Auto-discovers if no URL given. */
|
|
63
|
-
static async connect(options: ConnectOptions = {}): Promise<Terminal> {
|
|
64
|
-
let url = options.url;
|
|
65
|
-
|
|
66
|
-
if (!url) {
|
|
67
|
-
const servers = await Terminal.discover();
|
|
68
|
-
if (servers.length === 0) {
|
|
69
|
-
throw new Error(
|
|
70
|
-
"No wrightty server found. Start one with:\n" +
|
|
71
|
-
" wrightty term --headless\n" +
|
|
72
|
-
" wrightty term --bridge-tmux\n" +
|
|
73
|
-
" wrightty term --bridge-wezterm",
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
url = servers[0].url;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const client = await WrighttyClient.connect(url, options.timeout ?? 5000);
|
|
80
|
-
|
|
81
|
-
let sessionId = options.sessionId;
|
|
82
|
-
if (!sessionId) {
|
|
83
|
-
const result = await client.request("Session.list");
|
|
84
|
-
const sessions: SessionInfo[] = result.sessions ?? [];
|
|
85
|
-
sessionId = sessions.length > 0 ? sessions[0].sessionId : "0";
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return new Terminal(client, sessionId);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/** Connect to a headless server and create a new session. */
|
|
92
|
-
static async spawn(options: SpawnOptions = {}): Promise<Terminal> {
|
|
93
|
-
const url = options.serverUrl ?? "ws://127.0.0.1:9420";
|
|
94
|
-
const client = await WrighttyClient.connect(url);
|
|
95
|
-
|
|
96
|
-
const result = await client.request("Session.create", {
|
|
97
|
-
cols: options.cols ?? 120,
|
|
98
|
-
rows: options.rows ?? 40,
|
|
99
|
-
shell: options.shell,
|
|
100
|
-
cwd: options.cwd,
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
const term = new Terminal(client, result.sessionId);
|
|
104
|
-
await term.waitForPrompt(5000);
|
|
105
|
-
return term;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/** Close the connection. */
|
|
109
|
-
close(): void {
|
|
110
|
-
this.client.close();
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// --- High-level API ---
|
|
114
|
-
|
|
115
|
-
/** Run a command and return its output. */
|
|
116
|
-
async run(command: string, timeoutMs = 30000): Promise<string> {
|
|
117
|
-
await this.sendText(command + "\n");
|
|
118
|
-
await this.waitForPrompt(timeoutMs);
|
|
119
|
-
|
|
120
|
-
const screen = await this.readScreen();
|
|
121
|
-
const lines = screen.trim().split("\n");
|
|
122
|
-
|
|
123
|
-
const outputLines: string[] = [];
|
|
124
|
-
let foundCmd = false;
|
|
125
|
-
for (const line of lines) {
|
|
126
|
-
if (!foundCmd) {
|
|
127
|
-
if (line.includes(command)) foundCmd = true;
|
|
128
|
-
continue;
|
|
129
|
-
}
|
|
130
|
-
if (this.promptPattern.test(line)) break;
|
|
131
|
-
outputLines.push(line);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
return outputLines.join("\n");
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/** Send raw text to the terminal. */
|
|
138
|
-
async sendText(text: string): Promise<void> {
|
|
139
|
-
await this.client.request("Input.sendText", {
|
|
140
|
-
sessionId: this.sessionId,
|
|
141
|
-
text,
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/** Send structured keystrokes. */
|
|
146
|
-
async sendKeys(...keys: string[]): Promise<void> {
|
|
147
|
-
await this.client.request("Input.sendKeys", {
|
|
148
|
-
sessionId: this.sessionId,
|
|
149
|
-
keys,
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/** Read the current visible screen as text. */
|
|
154
|
-
async readScreen(): Promise<string> {
|
|
155
|
-
const result = await this.client.request("Screen.getText", {
|
|
156
|
-
sessionId: this.sessionId,
|
|
157
|
-
});
|
|
158
|
-
return result.text;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/** Take a screenshot. */
|
|
162
|
-
async screenshot(format: ScreenshotFormat = "svg"): Promise<ScreenshotResult> {
|
|
163
|
-
return this.client.request("Screen.screenshot", {
|
|
164
|
-
sessionId: this.sessionId,
|
|
165
|
-
format,
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/** Wait until a pattern appears on screen. */
|
|
170
|
-
async waitFor(pattern: string | RegExp, timeoutMs = 30000): Promise<string> {
|
|
171
|
-
const isRegex = pattern instanceof RegExp;
|
|
172
|
-
const patternStr = isRegex ? pattern.source : pattern;
|
|
173
|
-
|
|
174
|
-
const result: WaitForTextResult = await this.client.request("Screen.waitForText", {
|
|
175
|
-
sessionId: this.sessionId,
|
|
176
|
-
pattern: patternStr,
|
|
177
|
-
isRegex,
|
|
178
|
-
timeout: timeoutMs,
|
|
179
|
-
interval: 50,
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
if (!result.found) {
|
|
183
|
-
throw new Error(`Pattern ${patternStr} not found within ${timeoutMs}ms`);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
return this.readScreen();
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/** Wait for the shell prompt to appear. */
|
|
190
|
-
async waitForPrompt(timeoutMs = 10000): Promise<string> {
|
|
191
|
-
return this.waitFor(this.promptPattern, timeoutMs);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/** Override the regex used to detect the shell prompt. */
|
|
195
|
-
setPromptPattern(pattern: RegExp): void {
|
|
196
|
-
this.promptPattern = pattern;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/** Get terminal dimensions as [cols, rows]. */
|
|
200
|
-
async getSize(): Promise<[number, number]> {
|
|
201
|
-
const result = await this.client.request("Terminal.getSize", {
|
|
202
|
-
sessionId: this.sessionId,
|
|
203
|
-
});
|
|
204
|
-
return [result.cols, result.rows];
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/** Resize the terminal. */
|
|
208
|
-
async resize(cols: number, rows: number): Promise<void> {
|
|
209
|
-
await this.client.request("Terminal.resize", {
|
|
210
|
-
sessionId: this.sessionId,
|
|
211
|
-
cols,
|
|
212
|
-
rows,
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/** Get server info and capabilities. */
|
|
217
|
-
async getInfo(): Promise<Record<string, any>> {
|
|
218
|
-
return this.client.request("Wrightty.getInfo");
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// --- Recording ---
|
|
222
|
-
|
|
223
|
-
/** Start recording raw PTY I/O (asciicast v2 format). */
|
|
224
|
-
async startSessionRecording(includeInput = false): Promise<string> {
|
|
225
|
-
const result = await this.client.request("Recording.startSession", {
|
|
226
|
-
sessionId: this.sessionId,
|
|
227
|
-
includeInput,
|
|
228
|
-
});
|
|
229
|
-
return result.recordingId;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/** Stop a session recording and return asciicast data. */
|
|
233
|
-
async stopSessionRecording(recordingId: string): Promise<SessionRecordingData> {
|
|
234
|
-
return this.client.request("Recording.stopSession", { recordingId });
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/** Start recording wrightty API calls as a replayable script. */
|
|
238
|
-
async startActionRecording(format: "python" | "json" | "cli" = "python"): Promise<string> {
|
|
239
|
-
const result = await this.client.request("Recording.startActions", {
|
|
240
|
-
sessionId: this.sessionId,
|
|
241
|
-
format,
|
|
242
|
-
});
|
|
243
|
-
return result.recordingId;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
/** Stop action recording and return the generated script. */
|
|
247
|
-
async stopActionRecording(recordingId: string): Promise<ActionRecordingData> {
|
|
248
|
-
return this.client.request("Recording.stopActions", { recordingId });
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
/** Capture a single screen frame. */
|
|
252
|
-
async captureScreen(format: ScreenshotFormat = "svg"): Promise<Record<string, any>> {
|
|
253
|
-
return this.client.request("Recording.captureScreen", {
|
|
254
|
-
sessionId: this.sessionId,
|
|
255
|
-
format,
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
}
|