@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.
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +144 -0
- 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 +38 -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
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/** wrightty CLI for Node.js — control terminals from the command line. */
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
const terminal_1 = require("./terminal");
|
|
6
|
+
const args = process.argv.slice(2);
|
|
7
|
+
const command = args[0];
|
|
8
|
+
function usage() {
|
|
9
|
+
console.log(`wrightty-js — Playwright for terminals (Node.js)
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
wrightty-js run <command> [--timeout <s>] Run a command and print output
|
|
13
|
+
wrightty-js read Read the terminal screen
|
|
14
|
+
wrightty-js send-text <text> Send raw text
|
|
15
|
+
wrightty-js send-keys <key> [<key>...] Send keystrokes
|
|
16
|
+
wrightty-js screenshot [--format svg|text] Take a screenshot
|
|
17
|
+
wrightty-js wait-for <pattern> [--timeout] Wait for text on screen
|
|
18
|
+
wrightty-js info Show server info
|
|
19
|
+
wrightty-js size Get terminal dimensions
|
|
20
|
+
wrightty-js discover Find running servers
|
|
21
|
+
|
|
22
|
+
Options:
|
|
23
|
+
--url <url> Server URL (default: auto-discover)
|
|
24
|
+
--session <id> Session ID (default: first available)
|
|
25
|
+
--help Show this help`);
|
|
26
|
+
}
|
|
27
|
+
function getOpt(name) {
|
|
28
|
+
const idx = args.indexOf(name);
|
|
29
|
+
return idx >= 0 && idx + 1 < args.length ? args[idx + 1] : undefined;
|
|
30
|
+
}
|
|
31
|
+
function hasFlag(name) {
|
|
32
|
+
return args.includes(name);
|
|
33
|
+
}
|
|
34
|
+
async function getTerminal() {
|
|
35
|
+
const url = getOpt("--url");
|
|
36
|
+
const sessionId = getOpt("--session");
|
|
37
|
+
return terminal_1.Terminal.connect({ url, sessionId });
|
|
38
|
+
}
|
|
39
|
+
async function main() {
|
|
40
|
+
if (!command || hasFlag("--help") || hasFlag("-h")) {
|
|
41
|
+
usage();
|
|
42
|
+
process.exit(0);
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
switch (command) {
|
|
46
|
+
case "discover": {
|
|
47
|
+
const servers = await terminal_1.Terminal.discover();
|
|
48
|
+
if (servers.length === 0) {
|
|
49
|
+
console.log("No wrightty servers found.");
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
for (const s of servers) {
|
|
53
|
+
console.log(` ${s.url} ${s.implementation} v${s.version}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
case "run": {
|
|
59
|
+
const cmd = args[1];
|
|
60
|
+
if (!cmd) {
|
|
61
|
+
console.error("Usage: wrightty-js run <command>");
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
const timeout = parseInt(getOpt("--timeout") ?? "30", 10) * 1000;
|
|
65
|
+
const term = await getTerminal();
|
|
66
|
+
const output = await term.run(cmd, timeout);
|
|
67
|
+
console.log(output);
|
|
68
|
+
term.close();
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
case "read": {
|
|
72
|
+
const term = await getTerminal();
|
|
73
|
+
console.log(await term.readScreen());
|
|
74
|
+
term.close();
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
case "send-text": {
|
|
78
|
+
const text = args[1];
|
|
79
|
+
if (!text) {
|
|
80
|
+
console.error("Usage: wrightty-js send-text <text>");
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
const term = await getTerminal();
|
|
84
|
+
await term.sendText(text.replace(/\\n/g, "\n"));
|
|
85
|
+
term.close();
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
case "send-keys": {
|
|
89
|
+
const keys = args.slice(1).filter(k => !k.startsWith("--"));
|
|
90
|
+
if (keys.length === 0) {
|
|
91
|
+
console.error("Usage: wrightty-js send-keys <key> [...]");
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
const term = await getTerminal();
|
|
95
|
+
await term.sendKeys(...keys);
|
|
96
|
+
term.close();
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
case "screenshot": {
|
|
100
|
+
const format = (getOpt("--format") ?? "text");
|
|
101
|
+
const term = await getTerminal();
|
|
102
|
+
const result = await term.screenshot(format);
|
|
103
|
+
console.log(result.data);
|
|
104
|
+
term.close();
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
case "wait-for": {
|
|
108
|
+
const pattern = args[1];
|
|
109
|
+
if (!pattern) {
|
|
110
|
+
console.error("Usage: wrightty-js wait-for <pattern>");
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
const timeout = parseInt(getOpt("--timeout") ?? "30", 10) * 1000;
|
|
114
|
+
const term = await getTerminal();
|
|
115
|
+
const screen = await term.waitFor(pattern, timeout);
|
|
116
|
+
console.log(screen);
|
|
117
|
+
term.close();
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
case "info": {
|
|
121
|
+
const term = await getTerminal();
|
|
122
|
+
console.log(JSON.stringify(await term.getInfo(), null, 2));
|
|
123
|
+
term.close();
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
case "size": {
|
|
127
|
+
const term = await getTerminal();
|
|
128
|
+
const [cols, rows] = await term.getSize();
|
|
129
|
+
console.log(`${cols}x${rows}`);
|
|
130
|
+
term.close();
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
default:
|
|
134
|
+
console.error(`Unknown command: ${command}`);
|
|
135
|
+
usage();
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
console.error(`Error: ${err.message}`);
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
main();
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Low-level WebSocket JSON-RPC 2.0 client for the Wrightty protocol. */
|
|
2
|
+
export declare class WrighttyError extends Error {
|
|
3
|
+
code: number;
|
|
4
|
+
constructor(code: number, message: string);
|
|
5
|
+
}
|
|
6
|
+
export declare class WrighttyClient {
|
|
7
|
+
private ws;
|
|
8
|
+
private nextId;
|
|
9
|
+
private pending;
|
|
10
|
+
private constructor();
|
|
11
|
+
static connect(url: string, timeoutMs?: number): Promise<WrighttyClient>;
|
|
12
|
+
request(method: string, params?: Record<string, any>): Promise<any>;
|
|
13
|
+
close(): void;
|
|
14
|
+
}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/** Low-level WebSocket JSON-RPC 2.0 client for the Wrightty protocol. */
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.WrighttyClient = exports.WrighttyError = void 0;
|
|
8
|
+
const ws_1 = __importDefault(require("ws"));
|
|
9
|
+
class WrighttyError extends Error {
|
|
10
|
+
code;
|
|
11
|
+
constructor(code, message) {
|
|
12
|
+
super(`[${code}] ${message}`);
|
|
13
|
+
this.code = code;
|
|
14
|
+
this.name = "WrighttyError";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
exports.WrighttyError = WrighttyError;
|
|
18
|
+
class WrighttyClient {
|
|
19
|
+
ws;
|
|
20
|
+
nextId = 1;
|
|
21
|
+
pending = new Map();
|
|
22
|
+
constructor(ws) {
|
|
23
|
+
this.ws = ws;
|
|
24
|
+
ws.on("message", (data) => {
|
|
25
|
+
try {
|
|
26
|
+
const msg = JSON.parse(data.toString());
|
|
27
|
+
const entry = this.pending.get(msg.id);
|
|
28
|
+
if (!entry)
|
|
29
|
+
return;
|
|
30
|
+
this.pending.delete(msg.id);
|
|
31
|
+
if (msg.error) {
|
|
32
|
+
entry.reject(new WrighttyError(msg.error.code ?? -1, msg.error.message ?? "Unknown error"));
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
entry.resolve(msg.result);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// Ignore malformed messages
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
ws.on("close", () => {
|
|
43
|
+
for (const [id, entry] of this.pending) {
|
|
44
|
+
entry.reject(new Error("Connection closed"));
|
|
45
|
+
this.pending.delete(id);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
static connect(url, timeoutMs = 5000) {
|
|
50
|
+
return new Promise((resolve, reject) => {
|
|
51
|
+
const ws = new ws_1.default(url);
|
|
52
|
+
const timer = setTimeout(() => {
|
|
53
|
+
ws.close();
|
|
54
|
+
reject(new Error(`Connection timeout after ${timeoutMs}ms: ${url}`));
|
|
55
|
+
}, timeoutMs);
|
|
56
|
+
ws.on("open", () => {
|
|
57
|
+
clearTimeout(timer);
|
|
58
|
+
resolve(new WrighttyClient(ws));
|
|
59
|
+
});
|
|
60
|
+
ws.on("error", (err) => {
|
|
61
|
+
clearTimeout(timer);
|
|
62
|
+
reject(err);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
async request(method, params = {}) {
|
|
67
|
+
const id = this.nextId++;
|
|
68
|
+
const msg = JSON.stringify({ jsonrpc: "2.0", id, method, params });
|
|
69
|
+
return new Promise((resolve, reject) => {
|
|
70
|
+
this.pending.set(id, { resolve, reject });
|
|
71
|
+
this.ws.send(msg, (err) => {
|
|
72
|
+
if (err) {
|
|
73
|
+
this.pending.delete(id);
|
|
74
|
+
reject(err);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
close() {
|
|
80
|
+
this.ws.close();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
exports.WrighttyClient = WrighttyClient;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { Terminal } from "./terminal";
|
|
2
|
+
export { WrighttyClient, WrighttyError } from "./client";
|
|
3
|
+
export type { ServerInfo, Capabilities, ScreenshotFormat, SessionInfo, KeyInput, KeyEvent, TextMatch, WaitForTextResult, ScreenshotResult, RecordingResult, SessionRecordingData, ActionRecordingData, DiscoveredServer, ConnectOptions, SpawnOptions, } from "./types";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WrighttyError = exports.WrighttyClient = exports.Terminal = void 0;
|
|
4
|
+
var terminal_1 = require("./terminal");
|
|
5
|
+
Object.defineProperty(exports, "Terminal", { enumerable: true, get: function () { return terminal_1.Terminal; } });
|
|
6
|
+
var client_1 = require("./client");
|
|
7
|
+
Object.defineProperty(exports, "WrighttyClient", { enumerable: true, get: function () { return client_1.WrighttyClient; } });
|
|
8
|
+
Object.defineProperty(exports, "WrighttyError", { enumerable: true, get: function () { return client_1.WrighttyError; } });
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/** High-level Terminal API for AI agents and automation. */
|
|
2
|
+
import type { ConnectOptions, DiscoveredServer, ScreenshotFormat, ScreenshotResult, SpawnOptions, SessionRecordingData, ActionRecordingData } from "./types";
|
|
3
|
+
export declare class Terminal {
|
|
4
|
+
private client;
|
|
5
|
+
private sessionId;
|
|
6
|
+
private promptPattern;
|
|
7
|
+
private constructor();
|
|
8
|
+
/** Scan for running wrightty servers on ports 9420-9520. */
|
|
9
|
+
static discover(host?: string): Promise<DiscoveredServer[]>;
|
|
10
|
+
/** Connect to a wrightty server. Auto-discovers if no URL given. */
|
|
11
|
+
static connect(options?: ConnectOptions): Promise<Terminal>;
|
|
12
|
+
/** Connect to a headless server and create a new session. */
|
|
13
|
+
static spawn(options?: SpawnOptions): Promise<Terminal>;
|
|
14
|
+
/** Close the connection. */
|
|
15
|
+
close(): void;
|
|
16
|
+
/** Run a command and return its output. */
|
|
17
|
+
run(command: string, timeoutMs?: number): Promise<string>;
|
|
18
|
+
/** Send raw text to the terminal. */
|
|
19
|
+
sendText(text: string): Promise<void>;
|
|
20
|
+
/** Send structured keystrokes. */
|
|
21
|
+
sendKeys(...keys: string[]): Promise<void>;
|
|
22
|
+
/** Read the current visible screen as text. */
|
|
23
|
+
readScreen(): Promise<string>;
|
|
24
|
+
/** Take a screenshot. */
|
|
25
|
+
screenshot(format?: ScreenshotFormat): Promise<ScreenshotResult>;
|
|
26
|
+
/** Wait until a pattern appears on screen. */
|
|
27
|
+
waitFor(pattern: string | RegExp, timeoutMs?: number): Promise<string>;
|
|
28
|
+
/** Wait for the shell prompt to appear. */
|
|
29
|
+
waitForPrompt(timeoutMs?: number): Promise<string>;
|
|
30
|
+
/** Override the regex used to detect the shell prompt. */
|
|
31
|
+
setPromptPattern(pattern: RegExp): void;
|
|
32
|
+
/** Get terminal dimensions as [cols, rows]. */
|
|
33
|
+
getSize(): Promise<[number, number]>;
|
|
34
|
+
/** Resize the terminal. */
|
|
35
|
+
resize(cols: number, rows: number): Promise<void>;
|
|
36
|
+
/** Get server info and capabilities. */
|
|
37
|
+
getInfo(): Promise<Record<string, any>>;
|
|
38
|
+
/** Start recording raw PTY I/O (asciicast v2 format). */
|
|
39
|
+
startSessionRecording(includeInput?: boolean): Promise<string>;
|
|
40
|
+
/** Stop a session recording and return asciicast data. */
|
|
41
|
+
stopSessionRecording(recordingId: string): Promise<SessionRecordingData>;
|
|
42
|
+
/** Start recording wrightty API calls as a replayable script. */
|
|
43
|
+
startActionRecording(format?: "python" | "json" | "cli"): Promise<string>;
|
|
44
|
+
/** Stop action recording and return the generated script. */
|
|
45
|
+
stopActionRecording(recordingId: string): Promise<ActionRecordingData>;
|
|
46
|
+
/** Capture a single screen frame. */
|
|
47
|
+
captureScreen(format?: ScreenshotFormat): Promise<Record<string, any>>;
|
|
48
|
+
}
|
package/dist/terminal.js
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/** High-level Terminal API for AI agents and automation. */
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.Terminal = void 0;
|
|
5
|
+
const client_1 = require("./client");
|
|
6
|
+
const PORT_RANGE_START = 9420;
|
|
7
|
+
const PORT_RANGE_END = 9520;
|
|
8
|
+
class Terminal {
|
|
9
|
+
client;
|
|
10
|
+
sessionId;
|
|
11
|
+
promptPattern = /[$#>%]\s*$/;
|
|
12
|
+
constructor(client, sessionId) {
|
|
13
|
+
this.client = client;
|
|
14
|
+
this.sessionId = sessionId;
|
|
15
|
+
}
|
|
16
|
+
/** Scan for running wrightty servers on ports 9420-9520. */
|
|
17
|
+
static async discover(host = "127.0.0.1") {
|
|
18
|
+
const found = [];
|
|
19
|
+
const checks = [];
|
|
20
|
+
for (let port = PORT_RANGE_START; port <= PORT_RANGE_END; port++) {
|
|
21
|
+
const url = `ws://${host}:${port}`;
|
|
22
|
+
checks.push(client_1.WrighttyClient.connect(url, 200)
|
|
23
|
+
.then(async (client) => {
|
|
24
|
+
try {
|
|
25
|
+
const info = await client.request("Wrightty.getInfo");
|
|
26
|
+
found.push({
|
|
27
|
+
url,
|
|
28
|
+
port,
|
|
29
|
+
version: info.version ?? "unknown",
|
|
30
|
+
implementation: info.implementation ?? "unknown",
|
|
31
|
+
capabilities: info.capabilities ?? {},
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
finally {
|
|
35
|
+
client.close();
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
.catch(() => {
|
|
39
|
+
/* port not listening */
|
|
40
|
+
}));
|
|
41
|
+
}
|
|
42
|
+
await Promise.all(checks);
|
|
43
|
+
return found.sort((a, b) => a.port - b.port);
|
|
44
|
+
}
|
|
45
|
+
/** Connect to a wrightty server. Auto-discovers if no URL given. */
|
|
46
|
+
static async connect(options = {}) {
|
|
47
|
+
let url = options.url;
|
|
48
|
+
if (!url) {
|
|
49
|
+
const servers = await Terminal.discover();
|
|
50
|
+
if (servers.length === 0) {
|
|
51
|
+
throw new Error("No wrightty server found. Start one with:\n" +
|
|
52
|
+
" wrightty term --headless\n" +
|
|
53
|
+
" wrightty term --bridge-tmux\n" +
|
|
54
|
+
" wrightty term --bridge-wezterm");
|
|
55
|
+
}
|
|
56
|
+
url = servers[0].url;
|
|
57
|
+
}
|
|
58
|
+
const client = await client_1.WrighttyClient.connect(url, options.timeout ?? 5000);
|
|
59
|
+
let sessionId = options.sessionId;
|
|
60
|
+
if (!sessionId) {
|
|
61
|
+
const result = await client.request("Session.list");
|
|
62
|
+
const sessions = result.sessions ?? [];
|
|
63
|
+
sessionId = sessions.length > 0 ? sessions[0].sessionId : "0";
|
|
64
|
+
}
|
|
65
|
+
return new Terminal(client, sessionId);
|
|
66
|
+
}
|
|
67
|
+
/** Connect to a headless server and create a new session. */
|
|
68
|
+
static async spawn(options = {}) {
|
|
69
|
+
const url = options.serverUrl ?? "ws://127.0.0.1:9420";
|
|
70
|
+
const client = await client_1.WrighttyClient.connect(url);
|
|
71
|
+
const result = await client.request("Session.create", {
|
|
72
|
+
cols: options.cols ?? 120,
|
|
73
|
+
rows: options.rows ?? 40,
|
|
74
|
+
shell: options.shell,
|
|
75
|
+
cwd: options.cwd,
|
|
76
|
+
});
|
|
77
|
+
const term = new Terminal(client, result.sessionId);
|
|
78
|
+
await term.waitForPrompt(5000);
|
|
79
|
+
return term;
|
|
80
|
+
}
|
|
81
|
+
/** Close the connection. */
|
|
82
|
+
close() {
|
|
83
|
+
this.client.close();
|
|
84
|
+
}
|
|
85
|
+
// --- High-level API ---
|
|
86
|
+
/** Run a command and return its output. */
|
|
87
|
+
async run(command, timeoutMs = 30000) {
|
|
88
|
+
await this.sendText(command + "\n");
|
|
89
|
+
await this.waitForPrompt(timeoutMs);
|
|
90
|
+
const screen = await this.readScreen();
|
|
91
|
+
const lines = screen.trim().split("\n");
|
|
92
|
+
const outputLines = [];
|
|
93
|
+
let foundCmd = false;
|
|
94
|
+
for (const line of lines) {
|
|
95
|
+
if (!foundCmd) {
|
|
96
|
+
if (line.includes(command))
|
|
97
|
+
foundCmd = true;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (this.promptPattern.test(line))
|
|
101
|
+
break;
|
|
102
|
+
outputLines.push(line);
|
|
103
|
+
}
|
|
104
|
+
return outputLines.join("\n");
|
|
105
|
+
}
|
|
106
|
+
/** Send raw text to the terminal. */
|
|
107
|
+
async sendText(text) {
|
|
108
|
+
await this.client.request("Input.sendText", {
|
|
109
|
+
sessionId: this.sessionId,
|
|
110
|
+
text,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
/** Send structured keystrokes. */
|
|
114
|
+
async sendKeys(...keys) {
|
|
115
|
+
await this.client.request("Input.sendKeys", {
|
|
116
|
+
sessionId: this.sessionId,
|
|
117
|
+
keys,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
/** Read the current visible screen as text. */
|
|
121
|
+
async readScreen() {
|
|
122
|
+
const result = await this.client.request("Screen.getText", {
|
|
123
|
+
sessionId: this.sessionId,
|
|
124
|
+
});
|
|
125
|
+
return result.text;
|
|
126
|
+
}
|
|
127
|
+
/** Take a screenshot. */
|
|
128
|
+
async screenshot(format = "svg") {
|
|
129
|
+
return this.client.request("Screen.screenshot", {
|
|
130
|
+
sessionId: this.sessionId,
|
|
131
|
+
format,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
/** Wait until a pattern appears on screen. */
|
|
135
|
+
async waitFor(pattern, timeoutMs = 30000) {
|
|
136
|
+
const isRegex = pattern instanceof RegExp;
|
|
137
|
+
const patternStr = isRegex ? pattern.source : pattern;
|
|
138
|
+
const result = await this.client.request("Screen.waitForText", {
|
|
139
|
+
sessionId: this.sessionId,
|
|
140
|
+
pattern: patternStr,
|
|
141
|
+
isRegex,
|
|
142
|
+
timeout: timeoutMs,
|
|
143
|
+
interval: 50,
|
|
144
|
+
});
|
|
145
|
+
if (!result.found) {
|
|
146
|
+
throw new Error(`Pattern ${patternStr} not found within ${timeoutMs}ms`);
|
|
147
|
+
}
|
|
148
|
+
return this.readScreen();
|
|
149
|
+
}
|
|
150
|
+
/** Wait for the shell prompt to appear. */
|
|
151
|
+
async waitForPrompt(timeoutMs = 10000) {
|
|
152
|
+
return this.waitFor(this.promptPattern, timeoutMs);
|
|
153
|
+
}
|
|
154
|
+
/** Override the regex used to detect the shell prompt. */
|
|
155
|
+
setPromptPattern(pattern) {
|
|
156
|
+
this.promptPattern = pattern;
|
|
157
|
+
}
|
|
158
|
+
/** Get terminal dimensions as [cols, rows]. */
|
|
159
|
+
async getSize() {
|
|
160
|
+
const result = await this.client.request("Terminal.getSize", {
|
|
161
|
+
sessionId: this.sessionId,
|
|
162
|
+
});
|
|
163
|
+
return [result.cols, result.rows];
|
|
164
|
+
}
|
|
165
|
+
/** Resize the terminal. */
|
|
166
|
+
async resize(cols, rows) {
|
|
167
|
+
await this.client.request("Terminal.resize", {
|
|
168
|
+
sessionId: this.sessionId,
|
|
169
|
+
cols,
|
|
170
|
+
rows,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
/** Get server info and capabilities. */
|
|
174
|
+
async getInfo() {
|
|
175
|
+
return this.client.request("Wrightty.getInfo");
|
|
176
|
+
}
|
|
177
|
+
// --- Recording ---
|
|
178
|
+
/** Start recording raw PTY I/O (asciicast v2 format). */
|
|
179
|
+
async startSessionRecording(includeInput = false) {
|
|
180
|
+
const result = await this.client.request("Recording.startSession", {
|
|
181
|
+
sessionId: this.sessionId,
|
|
182
|
+
includeInput,
|
|
183
|
+
});
|
|
184
|
+
return result.recordingId;
|
|
185
|
+
}
|
|
186
|
+
/** Stop a session recording and return asciicast data. */
|
|
187
|
+
async stopSessionRecording(recordingId) {
|
|
188
|
+
return this.client.request("Recording.stopSession", { recordingId });
|
|
189
|
+
}
|
|
190
|
+
/** Start recording wrightty API calls as a replayable script. */
|
|
191
|
+
async startActionRecording(format = "python") {
|
|
192
|
+
const result = await this.client.request("Recording.startActions", {
|
|
193
|
+
sessionId: this.sessionId,
|
|
194
|
+
format,
|
|
195
|
+
});
|
|
196
|
+
return result.recordingId;
|
|
197
|
+
}
|
|
198
|
+
/** Stop action recording and return the generated script. */
|
|
199
|
+
async stopActionRecording(recordingId) {
|
|
200
|
+
return this.client.request("Recording.stopActions", { recordingId });
|
|
201
|
+
}
|
|
202
|
+
/** Capture a single screen frame. */
|
|
203
|
+
async captureScreen(format = "svg") {
|
|
204
|
+
return this.client.request("Recording.captureScreen", {
|
|
205
|
+
sessionId: this.sessionId,
|
|
206
|
+
format,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
exports.Terminal = Terminal;
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/** Wrightty protocol types — mirrors wrightty-protocol Rust crate. */
|
|
2
|
+
export interface ServerInfo {
|
|
3
|
+
version: string;
|
|
4
|
+
implementation: string;
|
|
5
|
+
capabilities: Capabilities;
|
|
6
|
+
}
|
|
7
|
+
export interface Capabilities {
|
|
8
|
+
screenshot: ScreenshotFormat[];
|
|
9
|
+
maxSessions: number;
|
|
10
|
+
supportsResize: boolean;
|
|
11
|
+
supportsScrollback: boolean;
|
|
12
|
+
supportsMouse: boolean;
|
|
13
|
+
supportsSessionCreate: boolean;
|
|
14
|
+
supportsColorPalette: boolean;
|
|
15
|
+
supportsRawOutput: boolean;
|
|
16
|
+
supportsShellIntegration: boolean;
|
|
17
|
+
events: string[];
|
|
18
|
+
}
|
|
19
|
+
export type ScreenshotFormat = "text" | "svg" | "png" | "json";
|
|
20
|
+
export interface SessionInfo {
|
|
21
|
+
sessionId: string;
|
|
22
|
+
title: string;
|
|
23
|
+
cwd?: string;
|
|
24
|
+
cols: number;
|
|
25
|
+
rows: number;
|
|
26
|
+
pid?: number;
|
|
27
|
+
running: boolean;
|
|
28
|
+
alternateScreen: boolean;
|
|
29
|
+
}
|
|
30
|
+
export type KeyInput = string | KeyEvent;
|
|
31
|
+
export interface KeyEvent {
|
|
32
|
+
key: string;
|
|
33
|
+
char?: string;
|
|
34
|
+
n?: number;
|
|
35
|
+
modifiers: string[];
|
|
36
|
+
}
|
|
37
|
+
export interface TextMatch {
|
|
38
|
+
text: string;
|
|
39
|
+
row: number;
|
|
40
|
+
col: number;
|
|
41
|
+
length: number;
|
|
42
|
+
}
|
|
43
|
+
export interface WaitForTextResult {
|
|
44
|
+
found: boolean;
|
|
45
|
+
matches: TextMatch[];
|
|
46
|
+
elapsed: number;
|
|
47
|
+
}
|
|
48
|
+
export interface ScreenshotResult {
|
|
49
|
+
format: ScreenshotFormat;
|
|
50
|
+
data: string;
|
|
51
|
+
width?: number;
|
|
52
|
+
height?: number;
|
|
53
|
+
}
|
|
54
|
+
export interface RecordingResult {
|
|
55
|
+
recordingId: string;
|
|
56
|
+
}
|
|
57
|
+
export interface SessionRecordingData {
|
|
58
|
+
format: string;
|
|
59
|
+
data: string;
|
|
60
|
+
duration: number;
|
|
61
|
+
events: number;
|
|
62
|
+
}
|
|
63
|
+
export interface ActionRecordingData {
|
|
64
|
+
format: string;
|
|
65
|
+
data: string;
|
|
66
|
+
actions: number;
|
|
67
|
+
duration: number;
|
|
68
|
+
}
|
|
69
|
+
export interface DiscoveredServer {
|
|
70
|
+
url: string;
|
|
71
|
+
port: number;
|
|
72
|
+
version: string;
|
|
73
|
+
implementation: string;
|
|
74
|
+
capabilities: Capabilities;
|
|
75
|
+
}
|
|
76
|
+
export interface ConnectOptions {
|
|
77
|
+
/** Server URL (default: auto-discover) */
|
|
78
|
+
url?: string;
|
|
79
|
+
/** Session ID (default: first available) */
|
|
80
|
+
sessionId?: string;
|
|
81
|
+
/** Connection timeout in ms (default: 5000) */
|
|
82
|
+
timeout?: number;
|
|
83
|
+
}
|
|
84
|
+
export interface SpawnOptions {
|
|
85
|
+
shell?: string;
|
|
86
|
+
cols?: number;
|
|
87
|
+
rows?: number;
|
|
88
|
+
cwd?: string;
|
|
89
|
+
serverUrl?: string;
|
|
90
|
+
}
|
package/dist/types.js
ADDED
package/package.json
CHANGED
|
@@ -1,24 +1,47 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moejay/wrightty",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"url": "https://github.com/moejay/wrightty/issues"
|
|
8
|
-
},
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Node.js SDK for Wrightty terminal automation protocol",
|
|
5
|
+
"author": "Moe Jay",
|
|
6
|
+
"homepage": "https://github.com/moejay/wrightty",
|
|
9
7
|
"repository": {
|
|
10
8
|
"type": "git",
|
|
11
|
-
"url": "
|
|
9
|
+
"url": "https://github.com/moejay/wrightty.git",
|
|
10
|
+
"directory": "sdks/node"
|
|
12
11
|
},
|
|
13
|
-
"
|
|
14
|
-
|
|
15
|
-
"type": "commonjs",
|
|
16
|
-
"main": "index.js",
|
|
17
|
-
"directories": {
|
|
18
|
-
"example": "examples",
|
|
19
|
-
"test": "tests"
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/moejay/wrightty/issues"
|
|
20
14
|
},
|
|
15
|
+
"main": "dist/index.js",
|
|
16
|
+
"types": "dist/index.d.ts",
|
|
17
|
+
"bin": {
|
|
18
|
+
"wrightty-js": "dist/cli.js"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist"
|
|
22
|
+
],
|
|
21
23
|
"scripts": {
|
|
22
|
-
"
|
|
24
|
+
"build": "tsc",
|
|
25
|
+
"prepublishOnly": "npm run build"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"terminal",
|
|
29
|
+
"automation",
|
|
30
|
+
"tui",
|
|
31
|
+
"playwright",
|
|
32
|
+
"testing",
|
|
33
|
+
"ai",
|
|
34
|
+
"agent"
|
|
35
|
+
],
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"ws": "^8.0.0"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@types/ws": "^8.0.0",
|
|
42
|
+
"typescript": "^5.0.0"
|
|
43
|
+
},
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=18.0.0"
|
|
23
46
|
}
|
|
24
47
|
}
|