@crewhaus/tool-mouse-keyboard 0.1.4 → 0.1.5
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/index.d.ts +36 -0
- package/dist/index.js +120 -0
- package/package.json +12 -9
- package/src/index.test.ts +0 -210
- package/src/index.ts +0 -159
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Catalog R4 `tool-mouse-keyboard` — Section 25 BROW.
|
|
3
|
+
*
|
|
4
|
+
* Wraps `computer-use-driver` mouse + keyboard methods 1:1 as
|
|
5
|
+
* registered tools. ALL FOUR are `destructive: true` so the §3
|
|
6
|
+
* permission-engine refuses to grant `allow` in default mode without
|
|
7
|
+
* an explicit `alwaysAllow` rule. The smoke's permission-floor probe
|
|
8
|
+
* exercises this path: same spec WITHOUT alwaysAllow rules → denial
|
|
9
|
+
* cites the destructive flag + missing rule.
|
|
10
|
+
*
|
|
11
|
+
* Tools:
|
|
12
|
+
* - `Click(x, y, button?)` — `left | right | middle` click.
|
|
13
|
+
* - `Type(text)` — typed input.
|
|
14
|
+
* - `Key(combo)` — special-key combos ("Enter", "Tab", "Control+a").
|
|
15
|
+
* - `Scroll(dx, dy)` — wheel scroll in pixels.
|
|
16
|
+
*/
|
|
17
|
+
import type { Driver } from "@crewhaus/computer-use-driver";
|
|
18
|
+
import { CrewhausError } from "@crewhaus/errors";
|
|
19
|
+
import type { RegisteredTool } from "@crewhaus/tool-catalog";
|
|
20
|
+
export declare class MouseKeyboardError extends CrewhausError {
|
|
21
|
+
readonly name = "MouseKeyboardError";
|
|
22
|
+
constructor(message: string, cause?: unknown);
|
|
23
|
+
}
|
|
24
|
+
export type CreateMouseKeyboardToolsOptions = {
|
|
25
|
+
readonly driver: Driver;
|
|
26
|
+
};
|
|
27
|
+
export declare function createClickTool(opts: CreateMouseKeyboardToolsOptions): RegisteredTool;
|
|
28
|
+
export declare function createTypeTool(opts: CreateMouseKeyboardToolsOptions): RegisteredTool;
|
|
29
|
+
export declare function createKeyTool(opts: CreateMouseKeyboardToolsOptions): RegisteredTool;
|
|
30
|
+
export declare function createScrollTool(opts: CreateMouseKeyboardToolsOptions): RegisteredTool;
|
|
31
|
+
export declare function createAllMouseKeyboardTools(opts: CreateMouseKeyboardToolsOptions): {
|
|
32
|
+
click: RegisteredTool;
|
|
33
|
+
type: RegisteredTool;
|
|
34
|
+
key: RegisteredTool;
|
|
35
|
+
scroll: RegisteredTool;
|
|
36
|
+
};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { CrewhausError } from "@crewhaus/errors";
|
|
2
|
+
import { buildTool } from "@crewhaus/tool-builder";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
export class MouseKeyboardError extends CrewhausError {
|
|
5
|
+
name = "MouseKeyboardError";
|
|
6
|
+
constructor(message, cause) {
|
|
7
|
+
super("tool", message, cause);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
const clickSchema = z
|
|
11
|
+
.object({
|
|
12
|
+
x: z.number().int().min(0),
|
|
13
|
+
y: z.number().int().min(0),
|
|
14
|
+
button: z.enum(["left", "right", "middle"]).optional(),
|
|
15
|
+
})
|
|
16
|
+
.strict();
|
|
17
|
+
const typeSchema = z.object({ text: z.string() }).strict();
|
|
18
|
+
const keySchema = z
|
|
19
|
+
.object({
|
|
20
|
+
combo: z
|
|
21
|
+
.string()
|
|
22
|
+
.min(1)
|
|
23
|
+
.describe("Single key or combo, e.g. 'Enter', 'Tab', 'Control+a'. Uses Playwright's key naming."),
|
|
24
|
+
})
|
|
25
|
+
.strict();
|
|
26
|
+
const scrollSchema = z
|
|
27
|
+
.object({
|
|
28
|
+
dx: z.number().int(),
|
|
29
|
+
dy: z.number().int(),
|
|
30
|
+
})
|
|
31
|
+
.strict();
|
|
32
|
+
export function createClickTool(opts) {
|
|
33
|
+
return buildTool({
|
|
34
|
+
name: "Click",
|
|
35
|
+
description: "Click at viewport pixel coordinates (x, y). Defaults to left button. Use FindElement(description) to get coordinates from a natural-language target. DESTRUCTIVE: requires explicit alwaysAllow rule.",
|
|
36
|
+
inputSchema: clickSchema,
|
|
37
|
+
readOnly: false,
|
|
38
|
+
destructive: true,
|
|
39
|
+
concurrencySafe: false,
|
|
40
|
+
classifyOutput: false,
|
|
41
|
+
execute: async (input) => {
|
|
42
|
+
const button = input.button ?? "left";
|
|
43
|
+
try {
|
|
44
|
+
await opts.driver.click(input.x, input.y, button);
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
return `[Click error] ${err.message ?? String(err)}`;
|
|
48
|
+
}
|
|
49
|
+
return `Clicked ${button} at (${input.x}, ${input.y}).`;
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
export function createTypeTool(opts) {
|
|
54
|
+
return buildTool({
|
|
55
|
+
name: "Type",
|
|
56
|
+
description: "Type text at the focused input. Click an input first to focus it. DESTRUCTIVE: requires explicit alwaysAllow rule.",
|
|
57
|
+
inputSchema: typeSchema,
|
|
58
|
+
readOnly: false,
|
|
59
|
+
destructive: true,
|
|
60
|
+
concurrencySafe: false,
|
|
61
|
+
classifyOutput: false,
|
|
62
|
+
execute: async (input) => {
|
|
63
|
+
try {
|
|
64
|
+
await opts.driver.type(input.text);
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
return `[Type error] ${err.message ?? String(err)}`;
|
|
68
|
+
}
|
|
69
|
+
return `Typed ${input.text.length} chars.`;
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
export function createKeyTool(opts) {
|
|
74
|
+
return buildTool({
|
|
75
|
+
name: "Key",
|
|
76
|
+
description: "Press a single key or combo (e.g. 'Enter', 'Tab', 'Control+a'). Uses Playwright's key naming. DESTRUCTIVE: requires explicit alwaysAllow rule.",
|
|
77
|
+
inputSchema: keySchema,
|
|
78
|
+
readOnly: false,
|
|
79
|
+
destructive: true,
|
|
80
|
+
concurrencySafe: false,
|
|
81
|
+
classifyOutput: false,
|
|
82
|
+
execute: async (input) => {
|
|
83
|
+
try {
|
|
84
|
+
await opts.driver.key(input.combo);
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
return `[Key error] ${err.message ?? String(err)}`;
|
|
88
|
+
}
|
|
89
|
+
return `Pressed ${input.combo}.`;
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
export function createScrollTool(opts) {
|
|
94
|
+
return buildTool({
|
|
95
|
+
name: "Scroll",
|
|
96
|
+
description: "Scroll by pixel deltas. Positive dy scrolls down. DESTRUCTIVE: requires explicit alwaysAllow rule.",
|
|
97
|
+
inputSchema: scrollSchema,
|
|
98
|
+
readOnly: false,
|
|
99
|
+
destructive: true,
|
|
100
|
+
concurrencySafe: false,
|
|
101
|
+
classifyOutput: false,
|
|
102
|
+
execute: async (input) => {
|
|
103
|
+
try {
|
|
104
|
+
await opts.driver.scroll(input.dx, input.dy);
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
return `[Scroll error] ${err.message ?? String(err)}`;
|
|
108
|
+
}
|
|
109
|
+
return `Scrolled (${input.dx}, ${input.dy}).`;
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
export function createAllMouseKeyboardTools(opts) {
|
|
114
|
+
return {
|
|
115
|
+
click: createClickTool(opts),
|
|
116
|
+
type: createTypeTool(opts),
|
|
117
|
+
key: createKeyTool(opts),
|
|
118
|
+
scroll: createScrollTool(opts),
|
|
119
|
+
};
|
|
120
|
+
}
|
package/package.json
CHANGED
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crewhaus/tool-mouse-keyboard",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Click / Type / Key / Scroll tools — destructive: true (Section 25 BROW)",
|
|
6
|
-
"main": "
|
|
7
|
-
"types": "
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
8
|
"exports": {
|
|
9
|
-
".":
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
10
13
|
},
|
|
11
14
|
"scripts": {
|
|
12
15
|
"test": "bun test src"
|
|
13
16
|
},
|
|
14
17
|
"dependencies": {
|
|
15
|
-
"@crewhaus/computer-use-driver": "0.1.
|
|
16
|
-
"@crewhaus/errors": "0.1.
|
|
17
|
-
"@crewhaus/tool-builder": "0.1.
|
|
18
|
-
"@crewhaus/tool-catalog": "0.1.
|
|
18
|
+
"@crewhaus/computer-use-driver": "0.1.5",
|
|
19
|
+
"@crewhaus/errors": "0.1.5",
|
|
20
|
+
"@crewhaus/tool-builder": "0.1.5",
|
|
21
|
+
"@crewhaus/tool-catalog": "0.1.5",
|
|
19
22
|
"zod": "^3.23.8"
|
|
20
23
|
},
|
|
21
24
|
"license": "Apache-2.0",
|
|
@@ -36,5 +39,5 @@
|
|
|
36
39
|
"publishConfig": {
|
|
37
40
|
"access": "public"
|
|
38
41
|
},
|
|
39
|
-
"files": ["
|
|
42
|
+
"files": ["dist", "README.md", "LICENSE", "NOTICE"]
|
|
40
43
|
}
|
package/src/index.test.ts
DELETED
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import type { Driver } from "@crewhaus/computer-use-driver";
|
|
3
|
-
import {
|
|
4
|
-
MouseKeyboardError,
|
|
5
|
-
createAllMouseKeyboardTools,
|
|
6
|
-
createClickTool,
|
|
7
|
-
createKeyTool,
|
|
8
|
-
createScrollTool,
|
|
9
|
-
createTypeTool,
|
|
10
|
-
} from "./index.js";
|
|
11
|
-
|
|
12
|
-
function recordingDriver(): { driver: Driver; calls: string[] } {
|
|
13
|
-
const calls: string[] = [];
|
|
14
|
-
const driver: Driver = {
|
|
15
|
-
backend: "chromium",
|
|
16
|
-
async connect() {},
|
|
17
|
-
async goto() {},
|
|
18
|
-
async screenshot() {
|
|
19
|
-
return new Uint8Array();
|
|
20
|
-
},
|
|
21
|
-
async click(x, y, button = "left") {
|
|
22
|
-
calls.push(`click ${button} ${x} ${y}`);
|
|
23
|
-
},
|
|
24
|
-
async type(text) {
|
|
25
|
-
calls.push(`type ${text}`);
|
|
26
|
-
},
|
|
27
|
-
async key(combo) {
|
|
28
|
-
calls.push(`key ${combo}`);
|
|
29
|
-
},
|
|
30
|
-
async scroll(dx, dy) {
|
|
31
|
-
calls.push(`scroll ${dx} ${dy}`);
|
|
32
|
-
},
|
|
33
|
-
async getViewport() {
|
|
34
|
-
return { width: 800, height: 600, devicePixelRatio: 1 };
|
|
35
|
-
},
|
|
36
|
-
async disconnect() {},
|
|
37
|
-
};
|
|
38
|
-
return { driver, calls };
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// A driver whose single named method rejects; all others are inert no-ops.
|
|
42
|
-
// Lets each tool's error-catch branch be exercised in isolation.
|
|
43
|
-
function throwingDriver(method: "click" | "type" | "key" | "scroll", message: string): Driver {
|
|
44
|
-
const base: Driver = {
|
|
45
|
-
backend: "chromium",
|
|
46
|
-
async connect() {},
|
|
47
|
-
async goto() {},
|
|
48
|
-
async screenshot() {
|
|
49
|
-
return new Uint8Array();
|
|
50
|
-
},
|
|
51
|
-
async click() {},
|
|
52
|
-
async type() {},
|
|
53
|
-
async key() {},
|
|
54
|
-
async scroll() {},
|
|
55
|
-
async getViewport() {
|
|
56
|
-
return { width: 0, height: 0, devicePixelRatio: 1 };
|
|
57
|
-
},
|
|
58
|
-
async disconnect() {},
|
|
59
|
-
};
|
|
60
|
-
return {
|
|
61
|
-
...base,
|
|
62
|
-
[method]: async () => {
|
|
63
|
-
throw new Error(message);
|
|
64
|
-
},
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
describe("Mouse + keyboard tools (T1 wrapping)", () => {
|
|
69
|
-
test("Click forwards (x, y, button) to driver.click", async () => {
|
|
70
|
-
const { driver, calls } = recordingDriver();
|
|
71
|
-
const tool = createClickTool({ driver });
|
|
72
|
-
const r = await tool.execute({ x: 100, y: 200, button: "left" }, {});
|
|
73
|
-
expect(calls).toEqual(["click left 100 200"]);
|
|
74
|
-
expect(typeof r === "string" && r.includes("Clicked left at (100, 200)")).toBe(true);
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
test("Click default button is left", async () => {
|
|
78
|
-
const { driver, calls } = recordingDriver();
|
|
79
|
-
const tool = createClickTool({ driver });
|
|
80
|
-
await tool.execute({ x: 1, y: 2 }, {});
|
|
81
|
-
expect(calls).toEqual(["click left 1 2"]);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
test("Type forwards text", async () => {
|
|
85
|
-
const { driver, calls } = recordingDriver();
|
|
86
|
-
const tool = createTypeTool({ driver });
|
|
87
|
-
await tool.execute({ text: "hello" }, {});
|
|
88
|
-
expect(calls).toEqual(["type hello"]);
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
test("Key forwards combo", async () => {
|
|
92
|
-
const { driver, calls } = recordingDriver();
|
|
93
|
-
const tool = createKeyTool({ driver });
|
|
94
|
-
await tool.execute({ combo: "Enter" }, {});
|
|
95
|
-
expect(calls).toEqual(["key Enter"]);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
test("Scroll forwards dx/dy", async () => {
|
|
99
|
-
const { driver, calls } = recordingDriver();
|
|
100
|
-
const tool = createScrollTool({ driver });
|
|
101
|
-
await tool.execute({ dx: 0, dy: 100 }, {});
|
|
102
|
-
expect(calls).toEqual(["scroll 0 100"]);
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
test("All four tools are destructive: true (T8 — permission floor)", () => {
|
|
106
|
-
const { driver } = recordingDriver();
|
|
107
|
-
const tools = createAllMouseKeyboardTools({ driver });
|
|
108
|
-
expect(tools.click.destructive).toBe(true);
|
|
109
|
-
expect(tools.type.destructive).toBe(true);
|
|
110
|
-
expect(tools.key.destructive).toBe(true);
|
|
111
|
-
expect(tools.scroll.destructive).toBe(true);
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
test("createAllMouseKeyboardTools returns the four named tools", () => {
|
|
115
|
-
const { driver } = recordingDriver();
|
|
116
|
-
const tools = createAllMouseKeyboardTools({ driver });
|
|
117
|
-
expect(tools.click.name).toBe("Click");
|
|
118
|
-
expect(tools.type.name).toBe("Type");
|
|
119
|
-
expect(tools.key.name).toBe("Key");
|
|
120
|
-
expect(tools.scroll.name).toBe("Scroll");
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
test("driver errors are surfaced as tool result strings (not thrown)", async () => {
|
|
124
|
-
const driver: Driver = {
|
|
125
|
-
backend: "chromium",
|
|
126
|
-
async connect() {},
|
|
127
|
-
async goto() {},
|
|
128
|
-
async screenshot() {
|
|
129
|
-
return new Uint8Array();
|
|
130
|
-
},
|
|
131
|
-
async click() {
|
|
132
|
-
throw new Error("driver explosion");
|
|
133
|
-
},
|
|
134
|
-
async type() {},
|
|
135
|
-
async key() {},
|
|
136
|
-
async scroll() {},
|
|
137
|
-
async getViewport() {
|
|
138
|
-
return { width: 0, height: 0, devicePixelRatio: 1 };
|
|
139
|
-
},
|
|
140
|
-
async disconnect() {},
|
|
141
|
-
};
|
|
142
|
-
const tool = createClickTool({ driver });
|
|
143
|
-
const r = await tool.execute({ x: 1, y: 1 }, {});
|
|
144
|
-
expect(typeof r === "string" && r.includes("[Click error]")).toBe(true);
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
test("Type driver errors are surfaced as a [Type error] result string", async () => {
|
|
148
|
-
const driver = throwingDriver("type", "type blew up");
|
|
149
|
-
const tool = createTypeTool({ driver });
|
|
150
|
-
const r = await tool.execute({ text: "hi" }, {});
|
|
151
|
-
expect(typeof r === "string" && r.includes("[Type error] type blew up")).toBe(true);
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
test("Key driver errors are surfaced as a [Key error] result string", async () => {
|
|
155
|
-
const driver = throwingDriver("key", "key blew up");
|
|
156
|
-
const tool = createKeyTool({ driver });
|
|
157
|
-
const r = await tool.execute({ combo: "Enter" }, {});
|
|
158
|
-
expect(typeof r === "string" && r.includes("[Key error] key blew up")).toBe(true);
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
test("Scroll driver errors are surfaced as a [Scroll error] result string", async () => {
|
|
162
|
-
const driver = throwingDriver("scroll", "scroll blew up");
|
|
163
|
-
const tool = createScrollTool({ driver });
|
|
164
|
-
const r = await tool.execute({ dx: 0, dy: 10 }, {});
|
|
165
|
-
expect(typeof r === "string" && r.includes("[Scroll error] scroll blew up")).toBe(true);
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
test("non-Error throw still yields a result string (String(err) fallback)", async () => {
|
|
169
|
-
// err.message is undefined → falls through to String(err).
|
|
170
|
-
const driver: Driver = {
|
|
171
|
-
backend: "chromium",
|
|
172
|
-
async connect() {},
|
|
173
|
-
async goto() {},
|
|
174
|
-
async screenshot() {
|
|
175
|
-
return new Uint8Array();
|
|
176
|
-
},
|
|
177
|
-
async click() {
|
|
178
|
-
throw "raw string failure";
|
|
179
|
-
},
|
|
180
|
-
async type() {},
|
|
181
|
-
async key() {},
|
|
182
|
-
async scroll() {},
|
|
183
|
-
async getViewport() {
|
|
184
|
-
return { width: 0, height: 0, devicePixelRatio: 1 };
|
|
185
|
-
},
|
|
186
|
-
async disconnect() {},
|
|
187
|
-
};
|
|
188
|
-
const tool = createClickTool({ driver });
|
|
189
|
-
const r = await tool.execute({ x: 1, y: 1 }, {});
|
|
190
|
-
expect(typeof r === "string" && r.includes("[Click error] raw string failure")).toBe(true);
|
|
191
|
-
});
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
describe("MouseKeyboardError", () => {
|
|
195
|
-
test("constructs with the 'tool' error code and a stable name", () => {
|
|
196
|
-
const err = new MouseKeyboardError("input device unavailable");
|
|
197
|
-
expect(err).toBeInstanceOf(Error);
|
|
198
|
-
expect(err.name).toBe("MouseKeyboardError");
|
|
199
|
-
expect(err.message).toBe("input device unavailable");
|
|
200
|
-
expect(err.code).toBe("tool");
|
|
201
|
-
expect(err.cause).toBeUndefined();
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
test("threads an underlying cause through to the base error", () => {
|
|
205
|
-
const root = new Error("driver socket closed");
|
|
206
|
-
const err = new MouseKeyboardError("scroll failed", root);
|
|
207
|
-
expect(err.cause).toBe(root);
|
|
208
|
-
expect(err.code).toBe("tool");
|
|
209
|
-
});
|
|
210
|
-
});
|
package/src/index.ts
DELETED
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Catalog R4 `tool-mouse-keyboard` — Section 25 BROW.
|
|
3
|
-
*
|
|
4
|
-
* Wraps `computer-use-driver` mouse + keyboard methods 1:1 as
|
|
5
|
-
* registered tools. ALL FOUR are `destructive: true` so the §3
|
|
6
|
-
* permission-engine refuses to grant `allow` in default mode without
|
|
7
|
-
* an explicit `alwaysAllow` rule. The smoke's permission-floor probe
|
|
8
|
-
* exercises this path: same spec WITHOUT alwaysAllow rules → denial
|
|
9
|
-
* cites the destructive flag + missing rule.
|
|
10
|
-
*
|
|
11
|
-
* Tools:
|
|
12
|
-
* - `Click(x, y, button?)` — `left | right | middle` click.
|
|
13
|
-
* - `Type(text)` — typed input.
|
|
14
|
-
* - `Key(combo)` — special-key combos ("Enter", "Tab", "Control+a").
|
|
15
|
-
* - `Scroll(dx, dy)` — wheel scroll in pixels.
|
|
16
|
-
*/
|
|
17
|
-
import type { Driver, MouseButton } from "@crewhaus/computer-use-driver";
|
|
18
|
-
import { CrewhausError } from "@crewhaus/errors";
|
|
19
|
-
import { buildTool } from "@crewhaus/tool-builder";
|
|
20
|
-
import type { RegisteredTool } from "@crewhaus/tool-catalog";
|
|
21
|
-
import { z } from "zod";
|
|
22
|
-
|
|
23
|
-
export class MouseKeyboardError extends CrewhausError {
|
|
24
|
-
override readonly name = "MouseKeyboardError";
|
|
25
|
-
constructor(message: string, cause?: unknown) {
|
|
26
|
-
super("tool", message, cause);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export type CreateMouseKeyboardToolsOptions = {
|
|
31
|
-
readonly driver: Driver;
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
const clickSchema = z
|
|
35
|
-
.object({
|
|
36
|
-
x: z.number().int().min(0),
|
|
37
|
-
y: z.number().int().min(0),
|
|
38
|
-
button: z.enum(["left", "right", "middle"]).optional(),
|
|
39
|
-
})
|
|
40
|
-
.strict();
|
|
41
|
-
|
|
42
|
-
const typeSchema = z.object({ text: z.string() }).strict();
|
|
43
|
-
|
|
44
|
-
const keySchema = z
|
|
45
|
-
.object({
|
|
46
|
-
combo: z
|
|
47
|
-
.string()
|
|
48
|
-
.min(1)
|
|
49
|
-
.describe(
|
|
50
|
-
"Single key or combo, e.g. 'Enter', 'Tab', 'Control+a'. Uses Playwright's key naming.",
|
|
51
|
-
),
|
|
52
|
-
})
|
|
53
|
-
.strict();
|
|
54
|
-
|
|
55
|
-
const scrollSchema = z
|
|
56
|
-
.object({
|
|
57
|
-
dx: z.number().int(),
|
|
58
|
-
dy: z.number().int(),
|
|
59
|
-
})
|
|
60
|
-
.strict();
|
|
61
|
-
|
|
62
|
-
export function createClickTool(opts: CreateMouseKeyboardToolsOptions): RegisteredTool {
|
|
63
|
-
return buildTool({
|
|
64
|
-
name: "Click",
|
|
65
|
-
description:
|
|
66
|
-
"Click at viewport pixel coordinates (x, y). Defaults to left button. Use FindElement(description) to get coordinates from a natural-language target. DESTRUCTIVE: requires explicit alwaysAllow rule.",
|
|
67
|
-
inputSchema: clickSchema,
|
|
68
|
-
readOnly: false,
|
|
69
|
-
destructive: true,
|
|
70
|
-
concurrencySafe: false,
|
|
71
|
-
classifyOutput: false,
|
|
72
|
-
execute: async (input) => {
|
|
73
|
-
const button: MouseButton = input.button ?? "left";
|
|
74
|
-
try {
|
|
75
|
-
await opts.driver.click(input.x, input.y, button);
|
|
76
|
-
} catch (err) {
|
|
77
|
-
return `[Click error] ${(err as Error).message ?? String(err)}`;
|
|
78
|
-
}
|
|
79
|
-
return `Clicked ${button} at (${input.x}, ${input.y}).`;
|
|
80
|
-
},
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export function createTypeTool(opts: CreateMouseKeyboardToolsOptions): RegisteredTool {
|
|
85
|
-
return buildTool({
|
|
86
|
-
name: "Type",
|
|
87
|
-
description:
|
|
88
|
-
"Type text at the focused input. Click an input first to focus it. DESTRUCTIVE: requires explicit alwaysAllow rule.",
|
|
89
|
-
inputSchema: typeSchema,
|
|
90
|
-
readOnly: false,
|
|
91
|
-
destructive: true,
|
|
92
|
-
concurrencySafe: false,
|
|
93
|
-
classifyOutput: false,
|
|
94
|
-
execute: async (input) => {
|
|
95
|
-
try {
|
|
96
|
-
await opts.driver.type(input.text);
|
|
97
|
-
} catch (err) {
|
|
98
|
-
return `[Type error] ${(err as Error).message ?? String(err)}`;
|
|
99
|
-
}
|
|
100
|
-
return `Typed ${input.text.length} chars.`;
|
|
101
|
-
},
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
export function createKeyTool(opts: CreateMouseKeyboardToolsOptions): RegisteredTool {
|
|
106
|
-
return buildTool({
|
|
107
|
-
name: "Key",
|
|
108
|
-
description:
|
|
109
|
-
"Press a single key or combo (e.g. 'Enter', 'Tab', 'Control+a'). Uses Playwright's key naming. DESTRUCTIVE: requires explicit alwaysAllow rule.",
|
|
110
|
-
inputSchema: keySchema,
|
|
111
|
-
readOnly: false,
|
|
112
|
-
destructive: true,
|
|
113
|
-
concurrencySafe: false,
|
|
114
|
-
classifyOutput: false,
|
|
115
|
-
execute: async (input) => {
|
|
116
|
-
try {
|
|
117
|
-
await opts.driver.key(input.combo);
|
|
118
|
-
} catch (err) {
|
|
119
|
-
return `[Key error] ${(err as Error).message ?? String(err)}`;
|
|
120
|
-
}
|
|
121
|
-
return `Pressed ${input.combo}.`;
|
|
122
|
-
},
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
export function createScrollTool(opts: CreateMouseKeyboardToolsOptions): RegisteredTool {
|
|
127
|
-
return buildTool({
|
|
128
|
-
name: "Scroll",
|
|
129
|
-
description:
|
|
130
|
-
"Scroll by pixel deltas. Positive dy scrolls down. DESTRUCTIVE: requires explicit alwaysAllow rule.",
|
|
131
|
-
inputSchema: scrollSchema,
|
|
132
|
-
readOnly: false,
|
|
133
|
-
destructive: true,
|
|
134
|
-
concurrencySafe: false,
|
|
135
|
-
classifyOutput: false,
|
|
136
|
-
execute: async (input) => {
|
|
137
|
-
try {
|
|
138
|
-
await opts.driver.scroll(input.dx, input.dy);
|
|
139
|
-
} catch (err) {
|
|
140
|
-
return `[Scroll error] ${(err as Error).message ?? String(err)}`;
|
|
141
|
-
}
|
|
142
|
-
return `Scrolled (${input.dx}, ${input.dy}).`;
|
|
143
|
-
},
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
export function createAllMouseKeyboardTools(opts: CreateMouseKeyboardToolsOptions): {
|
|
148
|
-
click: RegisteredTool;
|
|
149
|
-
type: RegisteredTool;
|
|
150
|
-
key: RegisteredTool;
|
|
151
|
-
scroll: RegisteredTool;
|
|
152
|
-
} {
|
|
153
|
-
return {
|
|
154
|
-
click: createClickTool(opts),
|
|
155
|
-
type: createTypeTool(opts),
|
|
156
|
-
key: createKeyTool(opts),
|
|
157
|
-
scroll: createScrollTool(opts),
|
|
158
|
-
};
|
|
159
|
-
}
|