browsirai 0.1.0 → 0.2.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/README.md +60 -1
- package/dist/bin.js +44 -43
- package/dist/bin.js.map +1 -1
- package/dist/cli/commands/act.js +1226 -0
- package/dist/cli/commands/act.js.map +1 -0
- package/dist/cli/commands/nav.js +739 -0
- package/dist/cli/commands/nav.js.map +1 -0
- package/dist/cli/commands/net.js +556 -0
- package/dist/cli/commands/net.js.map +1 -0
- package/dist/cli/commands/obs.js +1049 -0
- package/dist/cli/commands/obs.js.map +1 -0
- package/dist/cli/run.js +727 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/cli.js +44 -43
- package/dist/cli.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,1226 @@
|
|
|
1
|
+
// src/cli/run.ts
|
|
2
|
+
import pc from "picocolors";
|
|
3
|
+
|
|
4
|
+
// src/chrome-launcher.ts
|
|
5
|
+
import { execSync, spawn } from "child_process";
|
|
6
|
+
import { existsSync, readFileSync, mkdirSync, copyFileSync, readdirSync, statSync } from "fs";
|
|
7
|
+
import http from "http";
|
|
8
|
+
import { join } from "path";
|
|
9
|
+
import { homedir, tmpdir } from "os";
|
|
10
|
+
import { createConnection } from "net";
|
|
11
|
+
|
|
12
|
+
// src/cli/run.ts
|
|
13
|
+
function parseFlags(args) {
|
|
14
|
+
const flags = {};
|
|
15
|
+
let positionalIndex = 0;
|
|
16
|
+
for (let i = 0; i < args.length; i++) {
|
|
17
|
+
const arg = args[i];
|
|
18
|
+
if (arg.startsWith("--")) {
|
|
19
|
+
const eqIdx = arg.indexOf("=");
|
|
20
|
+
if (eqIdx !== -1) {
|
|
21
|
+
const key2 = arg.slice(2, eqIdx);
|
|
22
|
+
const value = arg.slice(eqIdx + 1);
|
|
23
|
+
flags[key2] = value;
|
|
24
|
+
} else {
|
|
25
|
+
const key2 = arg.slice(2);
|
|
26
|
+
const next = args[i + 1];
|
|
27
|
+
if (next && !next.startsWith("-")) {
|
|
28
|
+
flags[key2] = next;
|
|
29
|
+
i++;
|
|
30
|
+
} else {
|
|
31
|
+
flags[key2] = "true";
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
} else if (arg.startsWith("-") && arg.length > 1 && !/^-\d/.test(arg)) {
|
|
35
|
+
const chars = arg.slice(1);
|
|
36
|
+
if (chars.length === 1) {
|
|
37
|
+
const next = args[i + 1];
|
|
38
|
+
if (next && !next.startsWith("-")) {
|
|
39
|
+
flags[chars] = next;
|
|
40
|
+
i++;
|
|
41
|
+
} else {
|
|
42
|
+
flags[chars] = "true";
|
|
43
|
+
}
|
|
44
|
+
} else {
|
|
45
|
+
for (const ch of chars) {
|
|
46
|
+
flags[ch] = "true";
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
} else {
|
|
50
|
+
flags[`_${positionalIndex}`] = arg;
|
|
51
|
+
positionalIndex++;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return flags;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// src/tools/browser-click.ts
|
|
58
|
+
var MODIFIER_BITS = {
|
|
59
|
+
Alt: 1,
|
|
60
|
+
Control: 2,
|
|
61
|
+
Meta: 4,
|
|
62
|
+
Shift: 8
|
|
63
|
+
};
|
|
64
|
+
var REF_PATTERN = /^@e(\d+)$/;
|
|
65
|
+
function calculateCenter(content) {
|
|
66
|
+
const x = (content[0] + content[2] + content[4] + content[6]) / 4;
|
|
67
|
+
const y = (content[1] + content[3] + content[5] + content[7]) / 4;
|
|
68
|
+
return { x: Math.round(x), y: Math.round(y) };
|
|
69
|
+
}
|
|
70
|
+
async function resolveElementCoordinates(cdp, params) {
|
|
71
|
+
let backendNodeId;
|
|
72
|
+
let nodeId;
|
|
73
|
+
if (params.ref) {
|
|
74
|
+
const match = REF_PATTERN.exec(params.ref);
|
|
75
|
+
if (!match) {
|
|
76
|
+
throw new Error(`Invalid ref format: ${params.ref}`);
|
|
77
|
+
}
|
|
78
|
+
backendNodeId = parseInt(match[1], 10);
|
|
79
|
+
await cdp.send("DOM.resolveNode", {
|
|
80
|
+
backendNodeId
|
|
81
|
+
});
|
|
82
|
+
} else if (params.selector) {
|
|
83
|
+
const docResponse = await cdp.send("DOM.getDocument");
|
|
84
|
+
const queryResponse = await cdp.send("DOM.querySelector", {
|
|
85
|
+
nodeId: docResponse.root.nodeId,
|
|
86
|
+
selector: params.selector
|
|
87
|
+
});
|
|
88
|
+
if (!queryResponse.nodeId || queryResponse.nodeId === 0) {
|
|
89
|
+
throw new Error(
|
|
90
|
+
`Element not found: no element matches selector "${params.selector}"`
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
nodeId = queryResponse.nodeId;
|
|
94
|
+
} else {
|
|
95
|
+
throw new Error("Either ref, selector, or coordinates (x, y) must be provided");
|
|
96
|
+
}
|
|
97
|
+
const scrollParams = {};
|
|
98
|
+
if (backendNodeId !== void 0) {
|
|
99
|
+
scrollParams.backendNodeId = backendNodeId;
|
|
100
|
+
} else if (nodeId !== void 0) {
|
|
101
|
+
scrollParams.nodeId = nodeId;
|
|
102
|
+
}
|
|
103
|
+
await cdp.send(
|
|
104
|
+
"DOM.scrollIntoViewIfNeeded",
|
|
105
|
+
scrollParams
|
|
106
|
+
);
|
|
107
|
+
const boxParams = {};
|
|
108
|
+
if (backendNodeId !== void 0) {
|
|
109
|
+
boxParams.backendNodeId = backendNodeId;
|
|
110
|
+
} else if (nodeId !== void 0) {
|
|
111
|
+
boxParams.nodeId = nodeId;
|
|
112
|
+
}
|
|
113
|
+
const boxResponse = await cdp.send("DOM.getBoxModel", boxParams);
|
|
114
|
+
const { content, width, height } = boxResponse.model;
|
|
115
|
+
if (width === 0 && height === 0) {
|
|
116
|
+
throw new Error(
|
|
117
|
+
"Element is not visible: zero-size box model. The element may be hidden or not rendered."
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
return calculateCenter(content);
|
|
121
|
+
}
|
|
122
|
+
function computeModifiers(modifiers) {
|
|
123
|
+
if (!modifiers || modifiers.length === 0) return 0;
|
|
124
|
+
let bits = 0;
|
|
125
|
+
for (const mod of modifiers) {
|
|
126
|
+
const bit = MODIFIER_BITS[mod];
|
|
127
|
+
if (bit !== void 0) {
|
|
128
|
+
bits |= bit;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return bits;
|
|
132
|
+
}
|
|
133
|
+
function delay(ms) {
|
|
134
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
135
|
+
}
|
|
136
|
+
async function browserClick(cdp, params) {
|
|
137
|
+
let x;
|
|
138
|
+
let y;
|
|
139
|
+
if (params.x !== void 0 && params.y !== void 0) {
|
|
140
|
+
x = params.x;
|
|
141
|
+
y = params.y;
|
|
142
|
+
} else {
|
|
143
|
+
const coords = await resolveElementCoordinates(cdp, params);
|
|
144
|
+
x = coords.x;
|
|
145
|
+
y = coords.y;
|
|
146
|
+
}
|
|
147
|
+
const button = params.button ?? "left";
|
|
148
|
+
let effectiveModifiers = params.modifiers ? [...params.modifiers] : [];
|
|
149
|
+
if (params.newTab) {
|
|
150
|
+
const isMac = typeof process !== "undefined" && process.platform === "darwin";
|
|
151
|
+
const newTabModifier = isMac ? "Meta" : "Control";
|
|
152
|
+
if (!effectiveModifiers.includes(newTabModifier)) {
|
|
153
|
+
effectiveModifiers.push(newTabModifier);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const modifiers = computeModifiers(effectiveModifiers);
|
|
157
|
+
if (params.doubleClick) {
|
|
158
|
+
await performDoubleClick(cdp, x, y, button, modifiers);
|
|
159
|
+
} else {
|
|
160
|
+
await performClick(cdp, x, y, button, modifiers);
|
|
161
|
+
}
|
|
162
|
+
return { success: true };
|
|
163
|
+
}
|
|
164
|
+
async function performClick(cdp, x, y, button, modifiers, clickCount = 1) {
|
|
165
|
+
await cdp.send("Input.dispatchMouseEvent", {
|
|
166
|
+
type: "mouseMoved",
|
|
167
|
+
x,
|
|
168
|
+
y,
|
|
169
|
+
modifiers
|
|
170
|
+
});
|
|
171
|
+
await cdp.send("Input.dispatchMouseEvent", {
|
|
172
|
+
type: "mousePressed",
|
|
173
|
+
x,
|
|
174
|
+
y,
|
|
175
|
+
button,
|
|
176
|
+
clickCount,
|
|
177
|
+
modifiers
|
|
178
|
+
});
|
|
179
|
+
await delay(50);
|
|
180
|
+
await cdp.send("Input.dispatchMouseEvent", {
|
|
181
|
+
type: "mouseReleased",
|
|
182
|
+
x,
|
|
183
|
+
y,
|
|
184
|
+
button,
|
|
185
|
+
clickCount,
|
|
186
|
+
modifiers
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
async function performDoubleClick(cdp, x, y, button, modifiers) {
|
|
190
|
+
await performClick(cdp, x, y, button, modifiers, 1);
|
|
191
|
+
await performClick(cdp, x, y, button, modifiers, 2);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// src/tools/browser-fill-form.ts
|
|
195
|
+
function parseRef(ref) {
|
|
196
|
+
const match = ref.match(/@?e(\d+)/);
|
|
197
|
+
if (!match) {
|
|
198
|
+
throw new Error(`Invalid ref format: ${ref}`);
|
|
199
|
+
}
|
|
200
|
+
return parseInt(match[1], 10);
|
|
201
|
+
}
|
|
202
|
+
async function resolveObjectId(cdp, field) {
|
|
203
|
+
if (field.ref) {
|
|
204
|
+
const backendNodeId = parseRef(field.ref);
|
|
205
|
+
const result = await cdp.send("DOM.resolveNode", {
|
|
206
|
+
backendNodeId
|
|
207
|
+
});
|
|
208
|
+
return result.object.objectId;
|
|
209
|
+
}
|
|
210
|
+
if (field.selector) {
|
|
211
|
+
const evalResult = await cdp.send("Runtime.evaluate", {
|
|
212
|
+
expression: `document.querySelector(${JSON.stringify(field.selector)})`,
|
|
213
|
+
returnByValue: false
|
|
214
|
+
});
|
|
215
|
+
if (evalResult.result.objectId) {
|
|
216
|
+
return evalResult.result.objectId;
|
|
217
|
+
}
|
|
218
|
+
const docResult = await cdp.send(
|
|
219
|
+
"DOM.getDocument",
|
|
220
|
+
{}
|
|
221
|
+
);
|
|
222
|
+
const queryResult = await cdp.send("DOM.querySelector", {
|
|
223
|
+
nodeId: docResult.root.nodeId,
|
|
224
|
+
selector: field.selector
|
|
225
|
+
});
|
|
226
|
+
const resolveResult = await cdp.send("DOM.resolveNode", {
|
|
227
|
+
nodeId: queryResult.nodeId
|
|
228
|
+
});
|
|
229
|
+
return resolveResult.object.objectId;
|
|
230
|
+
}
|
|
231
|
+
throw new Error(`Field "${field.name}" has neither ref nor selector`);
|
|
232
|
+
}
|
|
233
|
+
async function isReadonlyOrDisabled(cdp, objectId) {
|
|
234
|
+
const result = await cdp.send("Runtime.callFunctionOn", {
|
|
235
|
+
objectId,
|
|
236
|
+
functionDeclaration: `function() { return this.readOnly || this.disabled; }`,
|
|
237
|
+
returnByValue: true
|
|
238
|
+
});
|
|
239
|
+
return result.result.value === true;
|
|
240
|
+
}
|
|
241
|
+
async function clickElement(cdp, field) {
|
|
242
|
+
let backendNodeId;
|
|
243
|
+
if (field.ref) {
|
|
244
|
+
backendNodeId = parseRef(field.ref);
|
|
245
|
+
}
|
|
246
|
+
if (backendNodeId !== void 0) {
|
|
247
|
+
await cdp.send("DOM.scrollIntoViewIfNeeded", {
|
|
248
|
+
backendNodeId
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
const boxResult = await cdp.send("DOM.getBoxModel", {
|
|
252
|
+
backendNodeId
|
|
253
|
+
});
|
|
254
|
+
const quad = boxResult.model.content;
|
|
255
|
+
const centerX = (quad[0] + quad[2] + quad[4] + quad[6]) / 4;
|
|
256
|
+
const centerY = (quad[1] + quad[3] + quad[5] + quad[7]) / 4;
|
|
257
|
+
await cdp.send("Input.dispatchMouseEvent", {
|
|
258
|
+
type: "mousePressed",
|
|
259
|
+
x: centerX,
|
|
260
|
+
y: centerY,
|
|
261
|
+
button: "left",
|
|
262
|
+
clickCount: 1
|
|
263
|
+
});
|
|
264
|
+
await cdp.send("Input.dispatchMouseEvent", {
|
|
265
|
+
type: "mouseReleased",
|
|
266
|
+
x: centerX,
|
|
267
|
+
y: centerY,
|
|
268
|
+
button: "left",
|
|
269
|
+
clickCount: 1
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
async function fillTextbox(cdp, field, objectId) {
|
|
273
|
+
if (field.ref) {
|
|
274
|
+
const backendNodeId = parseRef(field.ref);
|
|
275
|
+
await cdp.send("DOM.focus", {
|
|
276
|
+
backendNodeId
|
|
277
|
+
});
|
|
278
|
+
} else {
|
|
279
|
+
await cdp.send("Runtime.callFunctionOn", {
|
|
280
|
+
objectId,
|
|
281
|
+
functionDeclaration: `function() { this.focus(); }`,
|
|
282
|
+
returnByValue: true
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
await cdp.send("Runtime.callFunctionOn", {
|
|
286
|
+
objectId,
|
|
287
|
+
functionDeclaration: `function() { this.value = ''; this.dispatchEvent(new Event('input', { bubbles: true })); }`,
|
|
288
|
+
returnByValue: true
|
|
289
|
+
});
|
|
290
|
+
await cdp.send("Input.insertText", {
|
|
291
|
+
text: field.value
|
|
292
|
+
});
|
|
293
|
+
await cdp.send("Runtime.callFunctionOn", {
|
|
294
|
+
objectId,
|
|
295
|
+
functionDeclaration: `function() {
|
|
296
|
+
this.dispatchEvent(new Event('input', { bubbles: true }));
|
|
297
|
+
this.dispatchEvent(new Event('change', { bubbles: true }));
|
|
298
|
+
}`,
|
|
299
|
+
returnByValue: true
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
async function fillCombobox(cdp, _field, objectId, value) {
|
|
303
|
+
await cdp.send("Runtime.callFunctionOn", {
|
|
304
|
+
objectId,
|
|
305
|
+
functionDeclaration: `function(val) {
|
|
306
|
+
this.value = val;
|
|
307
|
+
this.dispatchEvent(new Event('input', { bubbles: true }));
|
|
308
|
+
this.dispatchEvent(new Event('change', { bubbles: true }));
|
|
309
|
+
return [val];
|
|
310
|
+
}`,
|
|
311
|
+
arguments: [{ value }],
|
|
312
|
+
returnByValue: true
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
async function fillSlider(cdp, _field, objectId, value) {
|
|
316
|
+
await cdp.send("Runtime.callFunctionOn", {
|
|
317
|
+
objectId,
|
|
318
|
+
functionDeclaration: `function(val) {
|
|
319
|
+
this.value = val;
|
|
320
|
+
this.dispatchEvent(new Event('input', { bubbles: true }));
|
|
321
|
+
this.dispatchEvent(new Event('change', { bubbles: true }));
|
|
322
|
+
}`,
|
|
323
|
+
arguments: [{ value }],
|
|
324
|
+
returnByValue: true
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
async function browserFillForm(cdp, params) {
|
|
328
|
+
let filledCount = 0;
|
|
329
|
+
const errors = [];
|
|
330
|
+
for (const field of params.fields) {
|
|
331
|
+
try {
|
|
332
|
+
const objectId = await resolveObjectId(cdp, field);
|
|
333
|
+
switch (field.type) {
|
|
334
|
+
case "textbox": {
|
|
335
|
+
const isBlocked = await isReadonlyOrDisabled(cdp, objectId);
|
|
336
|
+
if (isBlocked) {
|
|
337
|
+
errors.push({
|
|
338
|
+
field: field.name,
|
|
339
|
+
error: `Cannot fill readonly or disabled field "${field.name}"`
|
|
340
|
+
});
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
await fillTextbox(cdp, field, objectId);
|
|
344
|
+
filledCount++;
|
|
345
|
+
break;
|
|
346
|
+
}
|
|
347
|
+
case "checkbox": {
|
|
348
|
+
await clickElement(cdp, field);
|
|
349
|
+
filledCount++;
|
|
350
|
+
break;
|
|
351
|
+
}
|
|
352
|
+
case "radio": {
|
|
353
|
+
await clickElement(cdp, field);
|
|
354
|
+
filledCount++;
|
|
355
|
+
break;
|
|
356
|
+
}
|
|
357
|
+
case "combobox": {
|
|
358
|
+
await fillCombobox(cdp, field, objectId, field.value);
|
|
359
|
+
filledCount++;
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
362
|
+
case "slider": {
|
|
363
|
+
await fillSlider(cdp, field, objectId, field.value);
|
|
364
|
+
filledCount++;
|
|
365
|
+
break;
|
|
366
|
+
}
|
|
367
|
+
default: {
|
|
368
|
+
await fillTextbox(cdp, field, objectId);
|
|
369
|
+
filledCount++;
|
|
370
|
+
break;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
} catch (err) {
|
|
374
|
+
errors.push({
|
|
375
|
+
field: field.name,
|
|
376
|
+
error: err instanceof Error ? err.message : `Unknown error filling ${field.name}`
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
return {
|
|
381
|
+
success: errors.length === 0,
|
|
382
|
+
filledCount,
|
|
383
|
+
errors: errors.length > 0 ? errors : void 0
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// src/tools/browser-type.ts
|
|
388
|
+
function parseRef2(ref) {
|
|
389
|
+
const match = ref.match(/@e(\d+)/);
|
|
390
|
+
if (!match) {
|
|
391
|
+
throw new Error(`Invalid ref format: ${ref}`);
|
|
392
|
+
}
|
|
393
|
+
return parseInt(match[1], 10);
|
|
394
|
+
}
|
|
395
|
+
function getKeyCode(key2) {
|
|
396
|
+
if (key2.length === 1) {
|
|
397
|
+
return key2.toUpperCase().charCodeAt(0);
|
|
398
|
+
}
|
|
399
|
+
const codes = {
|
|
400
|
+
Enter: 13,
|
|
401
|
+
Tab: 9,
|
|
402
|
+
Backspace: 8
|
|
403
|
+
};
|
|
404
|
+
return codes[key2] ?? 0;
|
|
405
|
+
}
|
|
406
|
+
function getCode(key2) {
|
|
407
|
+
if (key2.length === 1) {
|
|
408
|
+
const upper = key2.toUpperCase();
|
|
409
|
+
if (upper >= "A" && upper <= "Z") return `Key${upper}`;
|
|
410
|
+
if (upper >= "0" && upper <= "9") return `Digit${upper}`;
|
|
411
|
+
}
|
|
412
|
+
return key2;
|
|
413
|
+
}
|
|
414
|
+
function delay2(ms) {
|
|
415
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
416
|
+
}
|
|
417
|
+
async function dispatchCharEvents(cdp, char) {
|
|
418
|
+
const keyCode = getKeyCode(char);
|
|
419
|
+
const code = getCode(char);
|
|
420
|
+
await cdp.send("Input.dispatchKeyEvent", {
|
|
421
|
+
type: "keyDown",
|
|
422
|
+
key: char,
|
|
423
|
+
code,
|
|
424
|
+
windowsVirtualKeyCode: keyCode,
|
|
425
|
+
text: char
|
|
426
|
+
});
|
|
427
|
+
await cdp.send("Input.dispatchKeyEvent", {
|
|
428
|
+
type: "char",
|
|
429
|
+
key: char,
|
|
430
|
+
code,
|
|
431
|
+
windowsVirtualKeyCode: keyCode,
|
|
432
|
+
text: char
|
|
433
|
+
});
|
|
434
|
+
await cdp.send("Input.dispatchKeyEvent", {
|
|
435
|
+
type: "keyUp",
|
|
436
|
+
key: char,
|
|
437
|
+
code,
|
|
438
|
+
windowsVirtualKeyCode: keyCode
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
async function browserType(cdp, params) {
|
|
442
|
+
if (params.ref) {
|
|
443
|
+
const backendNodeId = parseRef2(params.ref);
|
|
444
|
+
await cdp.send("DOM.focus", {
|
|
445
|
+
backendNodeId
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
if (!params.ref && params.selector) {
|
|
449
|
+
const evalResult = await cdp.send("Runtime.evaluate", {
|
|
450
|
+
expression: `document.querySelector(${JSON.stringify(params.selector)})`,
|
|
451
|
+
returnByValue: false
|
|
452
|
+
});
|
|
453
|
+
if (evalResult.result.objectId) {
|
|
454
|
+
await cdp.send("Runtime.callFunctionOn", {
|
|
455
|
+
objectId: evalResult.result.objectId,
|
|
456
|
+
functionDeclaration: `function() { this.focus(); }`,
|
|
457
|
+
returnByValue: true
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
if (params.slowly) {
|
|
462
|
+
for (let i = 0; i < params.text.length; i++) {
|
|
463
|
+
await dispatchCharEvents(cdp, params.text[i]);
|
|
464
|
+
if (i < params.text.length - 1) {
|
|
465
|
+
await delay2(50);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
} else {
|
|
469
|
+
await cdp.send("Input.insertText", {
|
|
470
|
+
text: params.text
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
if (params.submit) {
|
|
474
|
+
await cdp.send("Input.dispatchKeyEvent", {
|
|
475
|
+
type: "keyDown",
|
|
476
|
+
key: "Enter",
|
|
477
|
+
code: "Enter",
|
|
478
|
+
windowsVirtualKeyCode: 13
|
|
479
|
+
});
|
|
480
|
+
await cdp.send("Input.dispatchKeyEvent", {
|
|
481
|
+
type: "keyUp",
|
|
482
|
+
key: "Enter",
|
|
483
|
+
code: "Enter",
|
|
484
|
+
windowsVirtualKeyCode: 13
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
return { success: true };
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// src/tools/browser-press-key.ts
|
|
491
|
+
var KEY_CODE_MAP = {
|
|
492
|
+
Enter: 13,
|
|
493
|
+
Tab: 9,
|
|
494
|
+
Escape: 27,
|
|
495
|
+
Backspace: 8,
|
|
496
|
+
Delete: 46,
|
|
497
|
+
ArrowLeft: 37,
|
|
498
|
+
ArrowUp: 38,
|
|
499
|
+
ArrowRight: 39,
|
|
500
|
+
ArrowDown: 40,
|
|
501
|
+
Home: 36,
|
|
502
|
+
End: 35,
|
|
503
|
+
PageUp: 33,
|
|
504
|
+
PageDown: 34,
|
|
505
|
+
Insert: 45,
|
|
506
|
+
Space: 32,
|
|
507
|
+
" ": 32,
|
|
508
|
+
F1: 112,
|
|
509
|
+
F2: 113,
|
|
510
|
+
F3: 114,
|
|
511
|
+
F4: 115,
|
|
512
|
+
F5: 116,
|
|
513
|
+
F6: 117,
|
|
514
|
+
F7: 118,
|
|
515
|
+
F8: 119,
|
|
516
|
+
F9: 120,
|
|
517
|
+
F10: 121,
|
|
518
|
+
F11: 122,
|
|
519
|
+
F12: 123,
|
|
520
|
+
Control: 17,
|
|
521
|
+
Shift: 16,
|
|
522
|
+
Alt: 18,
|
|
523
|
+
Meta: 91
|
|
524
|
+
};
|
|
525
|
+
var KEY_TO_CODE = {
|
|
526
|
+
Enter: "Enter",
|
|
527
|
+
Tab: "Tab",
|
|
528
|
+
Escape: "Escape",
|
|
529
|
+
Backspace: "Backspace",
|
|
530
|
+
Delete: "Delete",
|
|
531
|
+
ArrowLeft: "ArrowLeft",
|
|
532
|
+
ArrowUp: "ArrowUp",
|
|
533
|
+
ArrowRight: "ArrowRight",
|
|
534
|
+
ArrowDown: "ArrowDown",
|
|
535
|
+
Home: "Home",
|
|
536
|
+
End: "End",
|
|
537
|
+
PageUp: "PageUp",
|
|
538
|
+
PageDown: "PageDown",
|
|
539
|
+
Insert: "Insert",
|
|
540
|
+
Space: "Space",
|
|
541
|
+
" ": "Space",
|
|
542
|
+
Control: "ControlLeft",
|
|
543
|
+
Shift: "ShiftLeft",
|
|
544
|
+
Alt: "AltLeft",
|
|
545
|
+
Meta: "MetaLeft"
|
|
546
|
+
};
|
|
547
|
+
var KEY_TEXT_MAP = {
|
|
548
|
+
Enter: "\r",
|
|
549
|
+
Tab: " ",
|
|
550
|
+
Escape: "",
|
|
551
|
+
Space: " ",
|
|
552
|
+
Backspace: "\b"
|
|
553
|
+
};
|
|
554
|
+
var MODIFIER_BITS2 = {
|
|
555
|
+
Alt: 1,
|
|
556
|
+
Control: 2,
|
|
557
|
+
Meta: 4,
|
|
558
|
+
Shift: 8
|
|
559
|
+
};
|
|
560
|
+
function getKeyCode2(key2) {
|
|
561
|
+
if (KEY_CODE_MAP[key2] !== void 0) {
|
|
562
|
+
return KEY_CODE_MAP[key2];
|
|
563
|
+
}
|
|
564
|
+
if (key2.length === 1) {
|
|
565
|
+
return key2.toUpperCase().charCodeAt(0);
|
|
566
|
+
}
|
|
567
|
+
return 0;
|
|
568
|
+
}
|
|
569
|
+
function getCode2(key2) {
|
|
570
|
+
if (KEY_TO_CODE[key2] !== void 0) {
|
|
571
|
+
return KEY_TO_CODE[key2];
|
|
572
|
+
}
|
|
573
|
+
if (key2.length === 1) {
|
|
574
|
+
const upper = key2.toUpperCase();
|
|
575
|
+
if (upper >= "A" && upper <= "Z") {
|
|
576
|
+
return `Key${upper}`;
|
|
577
|
+
}
|
|
578
|
+
if (upper >= "0" && upper <= "9") {
|
|
579
|
+
return `Digit${upper}`;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
return key2;
|
|
583
|
+
}
|
|
584
|
+
function getKeyText(key2) {
|
|
585
|
+
if (KEY_TEXT_MAP[key2] !== void 0) {
|
|
586
|
+
return KEY_TEXT_MAP[key2] || void 0;
|
|
587
|
+
}
|
|
588
|
+
if (key2.length === 1) {
|
|
589
|
+
return key2;
|
|
590
|
+
}
|
|
591
|
+
return void 0;
|
|
592
|
+
}
|
|
593
|
+
async function browserPressKey(cdp, params) {
|
|
594
|
+
const parts = params.key.split("+");
|
|
595
|
+
const mainKey = parts[parts.length - 1];
|
|
596
|
+
const modifierKeys = parts.slice(0, -1);
|
|
597
|
+
let modifiers = 0;
|
|
598
|
+
for (const mod of modifierKeys) {
|
|
599
|
+
if (MODIFIER_BITS2[mod] !== void 0) {
|
|
600
|
+
modifiers |= MODIFIER_BITS2[mod];
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
for (const mod of modifierKeys) {
|
|
604
|
+
await cdp.send("Input.dispatchKeyEvent", {
|
|
605
|
+
type: "keyDown",
|
|
606
|
+
key: mod,
|
|
607
|
+
code: getCode2(mod),
|
|
608
|
+
windowsVirtualKeyCode: getKeyCode2(mod),
|
|
609
|
+
modifiers
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
const text = getKeyText(mainKey);
|
|
613
|
+
const keyDownParams = {
|
|
614
|
+
type: "keyDown",
|
|
615
|
+
key: mainKey,
|
|
616
|
+
code: getCode2(mainKey),
|
|
617
|
+
windowsVirtualKeyCode: getKeyCode2(mainKey),
|
|
618
|
+
modifiers
|
|
619
|
+
};
|
|
620
|
+
if (text !== void 0) {
|
|
621
|
+
keyDownParams.text = text;
|
|
622
|
+
}
|
|
623
|
+
await cdp.send("Input.dispatchKeyEvent", keyDownParams);
|
|
624
|
+
if (text && modifierKeys.length === 0) {
|
|
625
|
+
await cdp.send("Input.dispatchKeyEvent", {
|
|
626
|
+
type: "char",
|
|
627
|
+
key: mainKey,
|
|
628
|
+
code: getCode2(mainKey),
|
|
629
|
+
windowsVirtualKeyCode: getKeyCode2(mainKey),
|
|
630
|
+
modifiers,
|
|
631
|
+
text
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
await cdp.send("Input.dispatchKeyEvent", {
|
|
635
|
+
type: "keyUp",
|
|
636
|
+
key: mainKey,
|
|
637
|
+
code: getCode2(mainKey),
|
|
638
|
+
windowsVirtualKeyCode: getKeyCode2(mainKey),
|
|
639
|
+
modifiers
|
|
640
|
+
});
|
|
641
|
+
for (let i = modifierKeys.length - 1; i >= 0; i--) {
|
|
642
|
+
const mod = modifierKeys[i];
|
|
643
|
+
await cdp.send("Input.dispatchKeyEvent", {
|
|
644
|
+
type: "keyUp",
|
|
645
|
+
key: mod,
|
|
646
|
+
code: getCode2(mod),
|
|
647
|
+
windowsVirtualKeyCode: getKeyCode2(mod),
|
|
648
|
+
modifiers: 0
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
return { success: true };
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// src/tools/browser-hover.ts
|
|
655
|
+
var REF_PATTERN2 = /^@?e(\d+)$/;
|
|
656
|
+
function calculateCenter2(content) {
|
|
657
|
+
const x = (content[0] + content[2] + content[4] + content[6]) / 4;
|
|
658
|
+
const y = (content[1] + content[3] + content[5] + content[7]) / 4;
|
|
659
|
+
return { x: Math.round(x), y: Math.round(y) };
|
|
660
|
+
}
|
|
661
|
+
async function resolveElementCoordinates2(cdp, params) {
|
|
662
|
+
let backendNodeId;
|
|
663
|
+
let nodeId;
|
|
664
|
+
if (params.ref) {
|
|
665
|
+
const match = REF_PATTERN2.exec(params.ref);
|
|
666
|
+
if (!match) {
|
|
667
|
+
throw new Error(`Invalid ref format: ${params.ref}`);
|
|
668
|
+
}
|
|
669
|
+
backendNodeId = parseInt(match[1], 10);
|
|
670
|
+
await cdp.send("DOM.resolveNode", {
|
|
671
|
+
backendNodeId
|
|
672
|
+
});
|
|
673
|
+
} else if (params.selector) {
|
|
674
|
+
const docResponse = await cdp.send("DOM.getDocument");
|
|
675
|
+
const queryResponse = await cdp.send("DOM.querySelector", {
|
|
676
|
+
nodeId: docResponse.root.nodeId,
|
|
677
|
+
selector: params.selector
|
|
678
|
+
});
|
|
679
|
+
if (!queryResponse.nodeId || queryResponse.nodeId === 0) {
|
|
680
|
+
throw new Error(
|
|
681
|
+
`Element not found: no element matches selector "${params.selector}"`
|
|
682
|
+
);
|
|
683
|
+
}
|
|
684
|
+
nodeId = queryResponse.nodeId;
|
|
685
|
+
} else {
|
|
686
|
+
throw new Error("Either ref or selector must be provided");
|
|
687
|
+
}
|
|
688
|
+
const scrollParams = {};
|
|
689
|
+
if (backendNodeId !== void 0) {
|
|
690
|
+
scrollParams.backendNodeId = backendNodeId;
|
|
691
|
+
} else if (nodeId !== void 0) {
|
|
692
|
+
scrollParams.nodeId = nodeId;
|
|
693
|
+
}
|
|
694
|
+
await cdp.send("DOM.scrollIntoViewIfNeeded", scrollParams);
|
|
695
|
+
const boxParams = {};
|
|
696
|
+
if (backendNodeId !== void 0) {
|
|
697
|
+
boxParams.backendNodeId = backendNodeId;
|
|
698
|
+
} else if (nodeId !== void 0) {
|
|
699
|
+
boxParams.nodeId = nodeId;
|
|
700
|
+
}
|
|
701
|
+
const boxResponse = await cdp.send("DOM.getBoxModel", boxParams);
|
|
702
|
+
return calculateCenter2(boxResponse.model.content);
|
|
703
|
+
}
|
|
704
|
+
async function browserHover(cdp, params) {
|
|
705
|
+
const { x, y } = await resolveElementCoordinates2(cdp, params);
|
|
706
|
+
await cdp.send("Input.dispatchMouseEvent", {
|
|
707
|
+
type: "mouseMoved",
|
|
708
|
+
x,
|
|
709
|
+
y
|
|
710
|
+
});
|
|
711
|
+
return { success: true };
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// src/tools/browser-drag.ts
|
|
715
|
+
var REF_PATTERN3 = /^@?e(\d+)$/;
|
|
716
|
+
function calculateCenter3(content) {
|
|
717
|
+
const x = (content[0] + content[2] + content[4] + content[6]) / 4;
|
|
718
|
+
const y = (content[1] + content[3] + content[5] + content[7]) / 4;
|
|
719
|
+
return { x: Math.round(x), y: Math.round(y) };
|
|
720
|
+
}
|
|
721
|
+
async function resolveRefCoordinates(cdp, ref) {
|
|
722
|
+
const match = REF_PATTERN3.exec(ref);
|
|
723
|
+
if (!match) {
|
|
724
|
+
throw new Error(`Invalid ref format: ${ref}`);
|
|
725
|
+
}
|
|
726
|
+
const backendNodeId = parseInt(match[1], 10);
|
|
727
|
+
const opts = { timeout: 5e3 };
|
|
728
|
+
await cdp.send("DOM.resolveNode", {
|
|
729
|
+
backendNodeId
|
|
730
|
+
}, opts);
|
|
731
|
+
try {
|
|
732
|
+
await cdp.send("DOM.scrollIntoViewIfNeeded", {
|
|
733
|
+
backendNodeId
|
|
734
|
+
}, opts);
|
|
735
|
+
} catch {
|
|
736
|
+
}
|
|
737
|
+
const boxResponse = await cdp.send("DOM.getBoxModel", {
|
|
738
|
+
backendNodeId
|
|
739
|
+
}, opts);
|
|
740
|
+
return calculateCenter3(boxResponse.model.content);
|
|
741
|
+
}
|
|
742
|
+
function delay3(ms) {
|
|
743
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
744
|
+
}
|
|
745
|
+
function interpolatePoints(startX, startY, endX, endY, steps = 8) {
|
|
746
|
+
const points = [];
|
|
747
|
+
for (let i = 1; i < steps; i++) {
|
|
748
|
+
const t = i / steps;
|
|
749
|
+
points.push({
|
|
750
|
+
x: Math.round(startX + (endX - startX) * t),
|
|
751
|
+
y: Math.round(startY + (endY - startY) * t)
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
return points;
|
|
755
|
+
}
|
|
756
|
+
async function browserDrag(cdp, params) {
|
|
757
|
+
let startX;
|
|
758
|
+
let startY;
|
|
759
|
+
if (params.startX !== void 0 && params.startY !== void 0) {
|
|
760
|
+
startX = params.startX;
|
|
761
|
+
startY = params.startY;
|
|
762
|
+
} else if (params.startRef) {
|
|
763
|
+
const coords = await resolveRefCoordinates(cdp, params.startRef);
|
|
764
|
+
startX = coords.x;
|
|
765
|
+
startY = coords.y;
|
|
766
|
+
} else {
|
|
767
|
+
throw new Error("Either startRef or startX/startY must be provided");
|
|
768
|
+
}
|
|
769
|
+
let endX;
|
|
770
|
+
let endY;
|
|
771
|
+
if (params.endX !== void 0 && params.endY !== void 0) {
|
|
772
|
+
endX = params.endX;
|
|
773
|
+
endY = params.endY;
|
|
774
|
+
} else if (params.endRef) {
|
|
775
|
+
const coords = await resolveRefCoordinates(cdp, params.endRef);
|
|
776
|
+
endX = coords.x;
|
|
777
|
+
endY = coords.y;
|
|
778
|
+
} else {
|
|
779
|
+
throw new Error("Either endRef or endX/endY must be provided");
|
|
780
|
+
}
|
|
781
|
+
const mouseOpts = { timeout: 3e3 };
|
|
782
|
+
await cdp.send("Input.dispatchMouseEvent", {
|
|
783
|
+
type: "mouseMoved",
|
|
784
|
+
x: startX,
|
|
785
|
+
y: startY
|
|
786
|
+
}, mouseOpts);
|
|
787
|
+
await cdp.send("Input.dispatchMouseEvent", {
|
|
788
|
+
type: "mousePressed",
|
|
789
|
+
x: startX,
|
|
790
|
+
y: startY,
|
|
791
|
+
button: "left",
|
|
792
|
+
clickCount: 1
|
|
793
|
+
}, mouseOpts);
|
|
794
|
+
const intermediatePoints = interpolatePoints(startX, startY, endX, endY, 4);
|
|
795
|
+
for (const point of intermediatePoints) {
|
|
796
|
+
await cdp.send("Input.dispatchMouseEvent", {
|
|
797
|
+
type: "mouseMoved",
|
|
798
|
+
x: point.x,
|
|
799
|
+
y: point.y
|
|
800
|
+
}, mouseOpts);
|
|
801
|
+
await delay3(10);
|
|
802
|
+
}
|
|
803
|
+
await cdp.send("Input.dispatchMouseEvent", {
|
|
804
|
+
type: "mouseMoved",
|
|
805
|
+
x: endX,
|
|
806
|
+
y: endY
|
|
807
|
+
}, mouseOpts);
|
|
808
|
+
await cdp.send("Input.dispatchMouseEvent", {
|
|
809
|
+
type: "mouseReleased",
|
|
810
|
+
x: endX,
|
|
811
|
+
y: endY,
|
|
812
|
+
button: "left",
|
|
813
|
+
clickCount: 1
|
|
814
|
+
}, mouseOpts);
|
|
815
|
+
return { success: true };
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
// src/tools/browser-select-option.ts
|
|
819
|
+
function refToBackendNodeId(ref) {
|
|
820
|
+
const match = /^@?e(\d+)$/.exec(ref);
|
|
821
|
+
if (!match) {
|
|
822
|
+
throw new Error(`Invalid ref format: ${ref}`);
|
|
823
|
+
}
|
|
824
|
+
return parseInt(match[1], 10);
|
|
825
|
+
}
|
|
826
|
+
async function browserSelectOption(cdp, params) {
|
|
827
|
+
const backendNodeId = refToBackendNodeId(params.ref);
|
|
828
|
+
const resolveResponse = await cdp.send("DOM.resolveNode", {
|
|
829
|
+
backendNodeId
|
|
830
|
+
});
|
|
831
|
+
const objectId = resolveResponse.object.objectId;
|
|
832
|
+
const valuesJson = JSON.stringify(params.values);
|
|
833
|
+
const result = await cdp.send("Runtime.callFunctionOn", {
|
|
834
|
+
objectId,
|
|
835
|
+
functionDeclaration: `function() {
|
|
836
|
+
var select = this;
|
|
837
|
+
if (select.tagName !== 'SELECT') {
|
|
838
|
+
throw new Error('Element is not a SELECT');
|
|
839
|
+
}
|
|
840
|
+
var values = ${valuesJson};
|
|
841
|
+
var matched = [];
|
|
842
|
+
|
|
843
|
+
for (var i = 0; i < select.options.length; i++) {
|
|
844
|
+
var option = select.options[i];
|
|
845
|
+
var isMatch = values.indexOf(option.value) !== -1 ||
|
|
846
|
+
values.indexOf(option.textContent.trim()) !== -1;
|
|
847
|
+
option.selected = isMatch;
|
|
848
|
+
if (isMatch) {
|
|
849
|
+
matched.push(option.value);
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
select.dispatchEvent(new Event('input', { bubbles: true }));
|
|
854
|
+
select.dispatchEvent(new Event('change', { bubbles: true }));
|
|
855
|
+
|
|
856
|
+
return matched;
|
|
857
|
+
}`,
|
|
858
|
+
returnByValue: true
|
|
859
|
+
});
|
|
860
|
+
const selected = result.result.value ?? [];
|
|
861
|
+
return {
|
|
862
|
+
success: true,
|
|
863
|
+
selected
|
|
864
|
+
};
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
// src/tools/browser-file-upload.ts
|
|
868
|
+
var REF_PATTERN4 = /^@e(\d+)$/;
|
|
869
|
+
async function browserFileUpload(cdp, params) {
|
|
870
|
+
const match = REF_PATTERN4.exec(params.ref);
|
|
871
|
+
if (!match) {
|
|
872
|
+
return {
|
|
873
|
+
success: false,
|
|
874
|
+
filesCount: 0,
|
|
875
|
+
error: `Invalid ref format: ${params.ref}. Expected @eN pattern (e.g. @e5).`
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
const backendNodeId = parseInt(match[1], 10);
|
|
879
|
+
const resolved = await cdp.send("DOM.resolveNode", {
|
|
880
|
+
backendNodeId
|
|
881
|
+
});
|
|
882
|
+
const objectId = resolved.object.objectId;
|
|
883
|
+
await cdp.send("DOM.setFileInputFiles", {
|
|
884
|
+
files: params.paths,
|
|
885
|
+
objectId
|
|
886
|
+
});
|
|
887
|
+
return {
|
|
888
|
+
success: true,
|
|
889
|
+
filesCount: params.paths.length
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
// src/tools/browser-handle-dialog.ts
|
|
894
|
+
async function browserHandleDialog(cdp, params) {
|
|
895
|
+
const dialogParams = {
|
|
896
|
+
accept: params.accept
|
|
897
|
+
};
|
|
898
|
+
if (params.promptText !== void 0) {
|
|
899
|
+
dialogParams.promptText = params.promptText;
|
|
900
|
+
}
|
|
901
|
+
try {
|
|
902
|
+
await cdp.send("Page.handleJavaScriptDialog", dialogParams);
|
|
903
|
+
return { success: true };
|
|
904
|
+
} catch (err) {
|
|
905
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
906
|
+
if (message.includes("No dialog is showing") || message.includes("no dialog")) {
|
|
907
|
+
return {
|
|
908
|
+
success: false,
|
|
909
|
+
error: "No JavaScript dialog is currently pending. A dialog must be open before it can be handled."
|
|
910
|
+
};
|
|
911
|
+
}
|
|
912
|
+
return {
|
|
913
|
+
success: false,
|
|
914
|
+
error: `Failed to handle dialog: ${message}`
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
// src/cli/commands/act.ts
|
|
920
|
+
function normaliseRef(input) {
|
|
921
|
+
const cleaned = input.startsWith("@") ? input.slice(1) : input;
|
|
922
|
+
const match = /^e(\d+)$/i.exec(cleaned);
|
|
923
|
+
if (!match) {
|
|
924
|
+
throw new Error(`Invalid ref format: ${input}. Expected @eN (e.g. @e5).`);
|
|
925
|
+
}
|
|
926
|
+
return `@e${match[1]}`;
|
|
927
|
+
}
|
|
928
|
+
function looksLikeRef(arg) {
|
|
929
|
+
return /^@?e\d+$/i.test(arg);
|
|
930
|
+
}
|
|
931
|
+
var click = {
|
|
932
|
+
name: "click",
|
|
933
|
+
description: "Click an element by ref or CSS selector",
|
|
934
|
+
usage: "browsirai click <ref-or-selector> [--newTab]",
|
|
935
|
+
async run(cdp, args) {
|
|
936
|
+
const flags = parseFlags(args);
|
|
937
|
+
const target = flags._0;
|
|
938
|
+
if (!target) {
|
|
939
|
+
console.error("Usage: browsirai click <ref-or-selector> [--newTab]");
|
|
940
|
+
console.error(" Provide an @eN ref or CSS selector as the first argument.");
|
|
941
|
+
process.exit(1);
|
|
942
|
+
}
|
|
943
|
+
const newTab = flags.newTab === "true";
|
|
944
|
+
try {
|
|
945
|
+
if (looksLikeRef(target)) {
|
|
946
|
+
const ref = normaliseRef(target);
|
|
947
|
+
await browserClick(cdp, { ref, newTab });
|
|
948
|
+
console.log(`Clicked ${ref}`);
|
|
949
|
+
} else {
|
|
950
|
+
await browserClick(cdp, { selector: target, newTab });
|
|
951
|
+
console.log(`Clicked ${target}`);
|
|
952
|
+
}
|
|
953
|
+
} catch (err) {
|
|
954
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
955
|
+
console.error(`Click failed: ${msg}`);
|
|
956
|
+
process.exit(1);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
};
|
|
960
|
+
var fill = {
|
|
961
|
+
name: "fill",
|
|
962
|
+
description: "Fill a form field with a value",
|
|
963
|
+
usage: "browsirai fill <ref-or-selector> <value>",
|
|
964
|
+
async run(cdp, args) {
|
|
965
|
+
const flags = parseFlags(args);
|
|
966
|
+
const target = flags._0;
|
|
967
|
+
const value = flags._1;
|
|
968
|
+
if (!target || value === void 0) {
|
|
969
|
+
console.error("Usage: browsirai fill <ref-or-selector> <value>");
|
|
970
|
+
console.error(" Provide an @eN ref or CSS selector and a value.");
|
|
971
|
+
process.exit(1);
|
|
972
|
+
}
|
|
973
|
+
try {
|
|
974
|
+
const isRef = looksLikeRef(target);
|
|
975
|
+
const ref = isRef ? normaliseRef(target) : void 0;
|
|
976
|
+
const selector = isRef ? void 0 : target;
|
|
977
|
+
const label = isRef ? normaliseRef(target) : target;
|
|
978
|
+
await browserFillForm(cdp, {
|
|
979
|
+
fields: [
|
|
980
|
+
{
|
|
981
|
+
name: label,
|
|
982
|
+
type: "textbox",
|
|
983
|
+
ref,
|
|
984
|
+
selector,
|
|
985
|
+
value
|
|
986
|
+
}
|
|
987
|
+
]
|
|
988
|
+
});
|
|
989
|
+
const displayValue = value.length > 40 ? value.slice(0, 40) + "..." : value;
|
|
990
|
+
console.log(`Filled ${label} with '${displayValue}'`);
|
|
991
|
+
} catch (err) {
|
|
992
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
993
|
+
console.error(`Fill failed: ${msg}`);
|
|
994
|
+
process.exit(1);
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
};
|
|
998
|
+
var type = {
|
|
999
|
+
name: "type",
|
|
1000
|
+
description: "Type text into the focused or specified element",
|
|
1001
|
+
usage: "browsirai type <text> [--ref=@e3] [--submit] [--slowly]",
|
|
1002
|
+
async run(cdp, args) {
|
|
1003
|
+
const flags = parseFlags(args);
|
|
1004
|
+
const text = flags._0;
|
|
1005
|
+
if (!text) {
|
|
1006
|
+
console.error("Usage: browsirai type <text> [--ref=@e3] [--submit] [--slowly]");
|
|
1007
|
+
console.error(" Provide the text to type as the first argument.");
|
|
1008
|
+
process.exit(1);
|
|
1009
|
+
}
|
|
1010
|
+
const ref = flags.ref ? normaliseRef(flags.ref) : void 0;
|
|
1011
|
+
const selector = flags.selector;
|
|
1012
|
+
const submit = flags.submit === "true";
|
|
1013
|
+
const slowly = flags.slowly === "true";
|
|
1014
|
+
try {
|
|
1015
|
+
await browserType(cdp, { text, ref, selector, slowly, submit });
|
|
1016
|
+
const displayText = text.length > 40 ? text.slice(0, 40) + "..." : text;
|
|
1017
|
+
const target = ref ?? selector ?? "focused element";
|
|
1018
|
+
console.log(`Typed '${displayText}' into ${target}`);
|
|
1019
|
+
} catch (err) {
|
|
1020
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1021
|
+
console.error(`Type failed: ${msg}`);
|
|
1022
|
+
process.exit(1);
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
};
|
|
1026
|
+
var key = {
|
|
1027
|
+
name: "press",
|
|
1028
|
+
aliases: ["key"],
|
|
1029
|
+
description: "Press a key or key combination",
|
|
1030
|
+
usage: "browsirai press <key-combo>",
|
|
1031
|
+
async run(cdp, args) {
|
|
1032
|
+
const flags = parseFlags(args);
|
|
1033
|
+
const keyCombo = flags._0;
|
|
1034
|
+
if (!keyCombo) {
|
|
1035
|
+
console.error("Usage: browsirai key <key-combo>");
|
|
1036
|
+
console.error(" Examples: Enter, Tab, Control+c, Shift+Tab, Meta+a");
|
|
1037
|
+
process.exit(1);
|
|
1038
|
+
}
|
|
1039
|
+
try {
|
|
1040
|
+
await browserPressKey(cdp, { key: keyCombo });
|
|
1041
|
+
console.log(`Pressed ${keyCombo}`);
|
|
1042
|
+
} catch (err) {
|
|
1043
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1044
|
+
console.error(`Key press failed: ${msg}`);
|
|
1045
|
+
process.exit(1);
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
};
|
|
1049
|
+
var hover = {
|
|
1050
|
+
name: "hover",
|
|
1051
|
+
description: "Hover over an element by ref or selector",
|
|
1052
|
+
usage: "browsirai hover <ref-or-selector>",
|
|
1053
|
+
async run(cdp, args) {
|
|
1054
|
+
const flags = parseFlags(args);
|
|
1055
|
+
const target = flags._0;
|
|
1056
|
+
if (!target) {
|
|
1057
|
+
console.error("Usage: browsirai hover <ref-or-selector>");
|
|
1058
|
+
console.error(" Provide an @eN ref or CSS selector.");
|
|
1059
|
+
process.exit(1);
|
|
1060
|
+
}
|
|
1061
|
+
try {
|
|
1062
|
+
if (looksLikeRef(target)) {
|
|
1063
|
+
const ref = normaliseRef(target);
|
|
1064
|
+
await browserHover(cdp, { ref });
|
|
1065
|
+
console.log(`Hovered ${ref}`);
|
|
1066
|
+
} else {
|
|
1067
|
+
await browserHover(cdp, { selector: target });
|
|
1068
|
+
console.log(`Hovered ${target}`);
|
|
1069
|
+
}
|
|
1070
|
+
} catch (err) {
|
|
1071
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1072
|
+
console.error(`Hover failed: ${msg}`);
|
|
1073
|
+
process.exit(1);
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
};
|
|
1077
|
+
var drag = {
|
|
1078
|
+
name: "drag",
|
|
1079
|
+
description: "Drag from one element to another",
|
|
1080
|
+
usage: "browsirai drag <startRef> <endRef>",
|
|
1081
|
+
async run(cdp, args) {
|
|
1082
|
+
const flags = parseFlags(args);
|
|
1083
|
+
const startArg = flags._0;
|
|
1084
|
+
const endArg = flags._1;
|
|
1085
|
+
if (!startArg || !endArg) {
|
|
1086
|
+
console.error("Usage: browsirai drag <startRef> <endRef>");
|
|
1087
|
+
console.error(" Provide two @eN refs (source and target).");
|
|
1088
|
+
process.exit(1);
|
|
1089
|
+
}
|
|
1090
|
+
try {
|
|
1091
|
+
const startRef = normaliseRef(startArg);
|
|
1092
|
+
const endRef = normaliseRef(endArg);
|
|
1093
|
+
await browserDrag(cdp, { startRef, endRef });
|
|
1094
|
+
console.log(`Dragged ${startRef} \u2192 ${endRef}`);
|
|
1095
|
+
} catch (err) {
|
|
1096
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1097
|
+
console.error(`Drag failed: ${msg}`);
|
|
1098
|
+
process.exit(1);
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
};
|
|
1102
|
+
var select = {
|
|
1103
|
+
name: "select",
|
|
1104
|
+
description: "Select option(s) in a <select> element",
|
|
1105
|
+
usage: "browsirai select <ref> <value1> [value2...]",
|
|
1106
|
+
async run(cdp, args) {
|
|
1107
|
+
const flags = parseFlags(args);
|
|
1108
|
+
const refArg = flags._0;
|
|
1109
|
+
if (!refArg) {
|
|
1110
|
+
console.error("Usage: browsirai select <ref> <value1> [value2...]");
|
|
1111
|
+
console.error(" Provide an @eN ref and one or more values to select.");
|
|
1112
|
+
process.exit(1);
|
|
1113
|
+
}
|
|
1114
|
+
const values = [];
|
|
1115
|
+
let i = 1;
|
|
1116
|
+
while (flags[`_${i}`] !== void 0) {
|
|
1117
|
+
values.push(flags[`_${i}`]);
|
|
1118
|
+
i++;
|
|
1119
|
+
}
|
|
1120
|
+
if (values.length === 0) {
|
|
1121
|
+
console.error("Usage: browsirai select <ref> <value1> [value2...]");
|
|
1122
|
+
console.error(" Provide at least one value to select.");
|
|
1123
|
+
process.exit(1);
|
|
1124
|
+
}
|
|
1125
|
+
try {
|
|
1126
|
+
const ref = normaliseRef(refArg);
|
|
1127
|
+
const result = await browserSelectOption(cdp, {
|
|
1128
|
+
ref,
|
|
1129
|
+
values,
|
|
1130
|
+
element: ref
|
|
1131
|
+
});
|
|
1132
|
+
const displayValues = result.selected.length > 0 ? result.selected.map((v) => `'${v}'`).join(", ") : values.map((v) => `'${v}'`).join(", ");
|
|
1133
|
+
console.log(`Selected ${displayValues} in ${ref}`);
|
|
1134
|
+
} catch (err) {
|
|
1135
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1136
|
+
console.error(`Select failed: ${msg}`);
|
|
1137
|
+
process.exit(1);
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
};
|
|
1141
|
+
var upload = {
|
|
1142
|
+
name: "upload",
|
|
1143
|
+
description: "Upload file(s) to a file input element",
|
|
1144
|
+
usage: "browsirai upload <ref> <file1> [file2...]",
|
|
1145
|
+
async run(cdp, args) {
|
|
1146
|
+
const flags = parseFlags(args);
|
|
1147
|
+
const refArg = flags._0;
|
|
1148
|
+
if (!refArg) {
|
|
1149
|
+
console.error("Usage: browsirai upload <ref> <file1> [file2...]");
|
|
1150
|
+
console.error(" Provide an @eN ref and one or more file paths.");
|
|
1151
|
+
process.exit(1);
|
|
1152
|
+
}
|
|
1153
|
+
const paths = [];
|
|
1154
|
+
let i = 1;
|
|
1155
|
+
while (flags[`_${i}`] !== void 0) {
|
|
1156
|
+
paths.push(flags[`_${i}`]);
|
|
1157
|
+
i++;
|
|
1158
|
+
}
|
|
1159
|
+
if (paths.length === 0) {
|
|
1160
|
+
console.error("Usage: browsirai upload <ref> <file1> [file2...]");
|
|
1161
|
+
console.error(" Provide at least one file path to upload.");
|
|
1162
|
+
process.exit(1);
|
|
1163
|
+
}
|
|
1164
|
+
try {
|
|
1165
|
+
const ref = normaliseRef(refArg);
|
|
1166
|
+
const result = await browserFileUpload(cdp, { ref, paths });
|
|
1167
|
+
if (result.success) {
|
|
1168
|
+
console.log(`Uploaded ${result.filesCount} file(s) to ${ref}`);
|
|
1169
|
+
} else {
|
|
1170
|
+
console.error(`Upload failed: ${result.error}`);
|
|
1171
|
+
process.exit(1);
|
|
1172
|
+
}
|
|
1173
|
+
} catch (err) {
|
|
1174
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1175
|
+
console.error(`Upload failed: ${msg}`);
|
|
1176
|
+
process.exit(1);
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
};
|
|
1180
|
+
var dialog = {
|
|
1181
|
+
name: "dialog",
|
|
1182
|
+
description: "Accept or dismiss a JavaScript dialog",
|
|
1183
|
+
usage: "browsirai dialog <accept|dismiss> [--text=...]",
|
|
1184
|
+
async run(cdp, args) {
|
|
1185
|
+
const flags = parseFlags(args);
|
|
1186
|
+
const action = flags._0;
|
|
1187
|
+
if (!action || action !== "accept" && action !== "dismiss") {
|
|
1188
|
+
console.error("Usage: browsirai dialog <accept|dismiss> [--text=...]");
|
|
1189
|
+
console.error(" First argument must be 'accept' or 'dismiss'.");
|
|
1190
|
+
process.exit(1);
|
|
1191
|
+
}
|
|
1192
|
+
const accept = action === "accept";
|
|
1193
|
+
const promptText = flags.text;
|
|
1194
|
+
try {
|
|
1195
|
+
const result = await browserHandleDialog(cdp, {
|
|
1196
|
+
accept,
|
|
1197
|
+
promptText
|
|
1198
|
+
});
|
|
1199
|
+
if (result.success) {
|
|
1200
|
+
console.log(`Dialog ${accept ? "accepted" : "dismissed"}`);
|
|
1201
|
+
} else {
|
|
1202
|
+
console.error(`Dialog handling failed: ${result.error}`);
|
|
1203
|
+
process.exit(1);
|
|
1204
|
+
}
|
|
1205
|
+
} catch (err) {
|
|
1206
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1207
|
+
console.error(`Dialog failed: ${msg}`);
|
|
1208
|
+
process.exit(1);
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
};
|
|
1212
|
+
var actCommands = [
|
|
1213
|
+
click,
|
|
1214
|
+
fill,
|
|
1215
|
+
type,
|
|
1216
|
+
key,
|
|
1217
|
+
hover,
|
|
1218
|
+
drag,
|
|
1219
|
+
select,
|
|
1220
|
+
upload,
|
|
1221
|
+
dialog
|
|
1222
|
+
];
|
|
1223
|
+
export {
|
|
1224
|
+
actCommands
|
|
1225
|
+
};
|
|
1226
|
+
//# sourceMappingURL=act.js.map
|