@skillful-agents/agent-computer 0.0.4 → 0.0.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/bin/ac-core-darwin-arm64 +0 -0
- package/bin/ac-core-darwin-x64 +0 -0
- package/bin/ac-core-win32-arm64.exe +0 -0
- package/bin/ac-core-win32-x64.exe +0 -0
- package/dist/src/platform/resolve.d.ts.map +1 -1
- package/dist/src/platform/resolve.js +17 -4
- package/dist/src/platform/resolve.js.map +1 -1
- package/dist-cjs/bin/ac.js +127 -0
- package/dist-cjs/package.json +1 -0
- package/dist-cjs/src/bridge.js +693 -0
- package/dist-cjs/src/cdp/ax-tree.js +162 -0
- package/dist-cjs/src/cdp/bounds.js +66 -0
- package/dist-cjs/src/cdp/client.js +272 -0
- package/dist-cjs/src/cdp/connection.js +285 -0
- package/dist-cjs/src/cdp/diff.js +55 -0
- package/dist-cjs/src/cdp/discovery.js +91 -0
- package/dist-cjs/src/cdp/index.js +27 -0
- package/dist-cjs/src/cdp/interactions.js +301 -0
- package/dist-cjs/src/cdp/port-manager.js +68 -0
- package/dist-cjs/src/cdp/role-map.js +102 -0
- package/dist-cjs/src/cdp/types.js +2 -0
- package/dist-cjs/src/cli/commands/apps.js +63 -0
- package/dist-cjs/src/cli/commands/batch.js +37 -0
- package/dist-cjs/src/cli/commands/click.js +61 -0
- package/dist-cjs/src/cli/commands/clipboard.js +31 -0
- package/dist-cjs/src/cli/commands/dialog.js +45 -0
- package/dist-cjs/src/cli/commands/drag.js +26 -0
- package/dist-cjs/src/cli/commands/find.js +99 -0
- package/dist-cjs/src/cli/commands/menu.js +36 -0
- package/dist-cjs/src/cli/commands/screenshot.js +27 -0
- package/dist-cjs/src/cli/commands/scroll.js +77 -0
- package/dist-cjs/src/cli/commands/session.js +27 -0
- package/dist-cjs/src/cli/commands/snapshot.js +24 -0
- package/dist-cjs/src/cli/commands/type.js +69 -0
- package/dist-cjs/src/cli/commands/windowmgmt.js +62 -0
- package/dist-cjs/src/cli/commands/windows.js +10 -0
- package/dist-cjs/src/cli/commands.js +215 -0
- package/dist-cjs/src/cli/output.js +253 -0
- package/dist-cjs/src/cli/parser.js +128 -0
- package/dist-cjs/src/config.js +79 -0
- package/dist-cjs/src/daemon.js +183 -0
- package/dist-cjs/src/errors.js +118 -0
- package/dist-cjs/src/index.js +24 -0
- package/dist-cjs/src/platform/index.js +16 -0
- package/dist-cjs/src/platform/resolve.js +83 -0
- package/dist-cjs/src/refs.js +91 -0
- package/dist-cjs/src/sdk.js +288 -0
- package/dist-cjs/src/types.js +11 -0
- package/package.json +4 -2
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CDPInteractions = void 0;
|
|
4
|
+
// Modifier flag values for CDP Input events
|
|
5
|
+
const MODIFIER_FLAGS = {
|
|
6
|
+
alt: 1,
|
|
7
|
+
ctrl: 2,
|
|
8
|
+
meta: 4, // Cmd on Mac
|
|
9
|
+
shift: 8,
|
|
10
|
+
};
|
|
11
|
+
// Key name mapping for special keys
|
|
12
|
+
const KEY_MAP = {
|
|
13
|
+
enter: { key: 'Enter', code: 'Enter', keyCode: 13 },
|
|
14
|
+
return: { key: 'Enter', code: 'Enter', keyCode: 13 },
|
|
15
|
+
tab: { key: 'Tab', code: 'Tab', keyCode: 9 },
|
|
16
|
+
escape: { key: 'Escape', code: 'Escape', keyCode: 27 },
|
|
17
|
+
esc: { key: 'Escape', code: 'Escape', keyCode: 27 },
|
|
18
|
+
backspace: { key: 'Backspace', code: 'Backspace', keyCode: 8 },
|
|
19
|
+
delete: { key: 'Delete', code: 'Delete', keyCode: 46 },
|
|
20
|
+
space: { key: ' ', code: 'Space', keyCode: 32 },
|
|
21
|
+
up: { key: 'ArrowUp', code: 'ArrowUp', keyCode: 38 },
|
|
22
|
+
down: { key: 'ArrowDown', code: 'ArrowDown', keyCode: 40 },
|
|
23
|
+
left: { key: 'ArrowLeft', code: 'ArrowLeft', keyCode: 37 },
|
|
24
|
+
right: { key: 'ArrowRight', code: 'ArrowRight', keyCode: 39 },
|
|
25
|
+
home: { key: 'Home', code: 'Home', keyCode: 36 },
|
|
26
|
+
end: { key: 'End', code: 'End', keyCode: 35 },
|
|
27
|
+
pageup: { key: 'PageUp', code: 'PageUp', keyCode: 33 },
|
|
28
|
+
pagedown: { key: 'PageDown', code: 'PageDown', keyCode: 34 },
|
|
29
|
+
// Letters a-z will be handled dynamically
|
|
30
|
+
};
|
|
31
|
+
class CDPInteractions {
|
|
32
|
+
connection;
|
|
33
|
+
constructor(connection) {
|
|
34
|
+
this.connection = connection;
|
|
35
|
+
}
|
|
36
|
+
/** Click at element center (CSS viewport coords). Options: right, double, modifiers */
|
|
37
|
+
async click(backendDOMNodeId, cssBounds, options = {}) {
|
|
38
|
+
const x = cssBounds[0] + cssBounds[2] / 2;
|
|
39
|
+
const y = cssBounds[1] + cssBounds[3] / 2;
|
|
40
|
+
await this.clickAt(x, y, options);
|
|
41
|
+
}
|
|
42
|
+
/** Click at specific CSS viewport coords */
|
|
43
|
+
async clickAt(x, y, options = {}) {
|
|
44
|
+
const button = options.right ? 'right' : 'left';
|
|
45
|
+
const clickCount = options.double ? 2 : (options.count ?? 1);
|
|
46
|
+
const modifiers = this.computeModifiers(options.modifiers);
|
|
47
|
+
// Move mouse first
|
|
48
|
+
await this.connection.send('Input.dispatchMouseEvent', {
|
|
49
|
+
type: 'mouseMoved',
|
|
50
|
+
x, y,
|
|
51
|
+
modifiers,
|
|
52
|
+
});
|
|
53
|
+
// For double click, send two full click sequences
|
|
54
|
+
for (let i = 0; i < clickCount; i++) {
|
|
55
|
+
await this.connection.send('Input.dispatchMouseEvent', {
|
|
56
|
+
type: 'mousePressed',
|
|
57
|
+
x, y,
|
|
58
|
+
button,
|
|
59
|
+
clickCount: i + 1,
|
|
60
|
+
modifiers,
|
|
61
|
+
});
|
|
62
|
+
await this.connection.send('Input.dispatchMouseEvent', {
|
|
63
|
+
type: 'mouseReleased',
|
|
64
|
+
x, y,
|
|
65
|
+
button,
|
|
66
|
+
clickCount: i + 1,
|
|
67
|
+
modifiers,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/** Hover over element (move mouse to center) */
|
|
72
|
+
async hover(cssBounds) {
|
|
73
|
+
const x = cssBounds[0] + cssBounds[2] / 2;
|
|
74
|
+
const y = cssBounds[1] + cssBounds[3] / 2;
|
|
75
|
+
await this.connection.send('Input.dispatchMouseEvent', {
|
|
76
|
+
type: 'mouseMoved',
|
|
77
|
+
x, y,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
/** Focus an element by backendNodeId */
|
|
81
|
+
async focus(backendDOMNodeId) {
|
|
82
|
+
await this.connection.send('DOM.focus', { backendNodeId: backendDOMNodeId });
|
|
83
|
+
}
|
|
84
|
+
/** Insert text at current focus (fast, no key events) */
|
|
85
|
+
async type(text) {
|
|
86
|
+
await this.connection.send('Input.insertText', { text });
|
|
87
|
+
}
|
|
88
|
+
/** Type with per-character key events and optional delay */
|
|
89
|
+
async typeWithDelay(text, delay) {
|
|
90
|
+
for (const char of text) {
|
|
91
|
+
await this.connection.send('Input.dispatchKeyEvent', {
|
|
92
|
+
type: 'keyDown',
|
|
93
|
+
key: char,
|
|
94
|
+
text: char,
|
|
95
|
+
});
|
|
96
|
+
await this.connection.send('Input.dispatchKeyEvent', {
|
|
97
|
+
type: 'keyUp',
|
|
98
|
+
key: char,
|
|
99
|
+
});
|
|
100
|
+
if (delay > 0) {
|
|
101
|
+
await sleep(delay);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/** Fill: focus element, select all, insert text. Fallback: set .value via Runtime */
|
|
106
|
+
async fill(backendDOMNodeId, text) {
|
|
107
|
+
// Focus the element
|
|
108
|
+
await this.focus(backendDOMNodeId);
|
|
109
|
+
await sleep(50);
|
|
110
|
+
// Select all (Cmd+A on Mac)
|
|
111
|
+
await this.key('cmd+a');
|
|
112
|
+
await sleep(50);
|
|
113
|
+
// Insert text (replaces selection)
|
|
114
|
+
await this.type(text);
|
|
115
|
+
}
|
|
116
|
+
/** Press a key combination (e.g., "cmd+a", "shift+enter", "ctrl+c") */
|
|
117
|
+
async key(combo, repeat) {
|
|
118
|
+
const times = repeat ?? 1;
|
|
119
|
+
const { modifiers, keyInfo } = this.parseCombo(combo);
|
|
120
|
+
for (let i = 0; i < times; i++) {
|
|
121
|
+
// Press modifier keys
|
|
122
|
+
for (const mod of modifiers) {
|
|
123
|
+
await this.connection.send('Input.dispatchKeyEvent', {
|
|
124
|
+
type: 'rawKeyDown',
|
|
125
|
+
key: this.modifierKeyName(mod),
|
|
126
|
+
modifiers: this.computeModifiers(modifiers),
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
// Press the main key
|
|
130
|
+
const modifierFlags = this.computeModifiers(modifiers);
|
|
131
|
+
await this.connection.send('Input.dispatchKeyEvent', {
|
|
132
|
+
type: 'rawKeyDown',
|
|
133
|
+
key: keyInfo.key,
|
|
134
|
+
code: keyInfo.code,
|
|
135
|
+
windowsVirtualKeyCode: keyInfo.keyCode,
|
|
136
|
+
modifiers: modifierFlags,
|
|
137
|
+
});
|
|
138
|
+
// If it's a printable character with no modifiers (or just shift), send char event
|
|
139
|
+
if (keyInfo.key.length === 1 && modifiers.every(m => m === 'shift')) {
|
|
140
|
+
await this.connection.send('Input.dispatchKeyEvent', {
|
|
141
|
+
type: 'char',
|
|
142
|
+
key: keyInfo.key,
|
|
143
|
+
text: keyInfo.key,
|
|
144
|
+
modifiers: modifierFlags,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
await this.connection.send('Input.dispatchKeyEvent', {
|
|
148
|
+
type: 'keyUp',
|
|
149
|
+
key: keyInfo.key,
|
|
150
|
+
code: keyInfo.code,
|
|
151
|
+
windowsVirtualKeyCode: keyInfo.keyCode,
|
|
152
|
+
modifiers: modifierFlags,
|
|
153
|
+
});
|
|
154
|
+
// Release modifier keys (reverse order)
|
|
155
|
+
for (const mod of [...modifiers].reverse()) {
|
|
156
|
+
await this.connection.send('Input.dispatchKeyEvent', {
|
|
157
|
+
type: 'keyUp',
|
|
158
|
+
key: this.modifierKeyName(mod),
|
|
159
|
+
modifiers: 0,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
if (i < times - 1)
|
|
163
|
+
await sleep(50);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/** Scroll using mouseWheel events */
|
|
167
|
+
async scroll(direction, amount = 3, atX, atY) {
|
|
168
|
+
const x = atX ?? 300; // center of typical window
|
|
169
|
+
const y = atY ?? 250;
|
|
170
|
+
const pixelAmount = amount * 100; // Each "amount" unit = 100px
|
|
171
|
+
let deltaX = 0;
|
|
172
|
+
let deltaY = 0;
|
|
173
|
+
switch (direction) {
|
|
174
|
+
case 'up':
|
|
175
|
+
deltaY = -pixelAmount;
|
|
176
|
+
break;
|
|
177
|
+
case 'down':
|
|
178
|
+
deltaY = pixelAmount;
|
|
179
|
+
break;
|
|
180
|
+
case 'left':
|
|
181
|
+
deltaX = -pixelAmount;
|
|
182
|
+
break;
|
|
183
|
+
case 'right':
|
|
184
|
+
deltaX = pixelAmount;
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
await this.connection.send('Input.dispatchMouseEvent', {
|
|
188
|
+
type: 'mouseWheel',
|
|
189
|
+
x, y,
|
|
190
|
+
deltaX,
|
|
191
|
+
deltaY,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
/** Select a value in a <select> element */
|
|
195
|
+
async select(backendDOMNodeId, value) {
|
|
196
|
+
// Resolve to a remote object first
|
|
197
|
+
const { object } = await this.connection.send('DOM.resolveNode', {
|
|
198
|
+
backendNodeId: backendDOMNodeId,
|
|
199
|
+
});
|
|
200
|
+
await this.connection.send('Runtime.callFunctionOn', {
|
|
201
|
+
objectId: object.objectId,
|
|
202
|
+
functionDeclaration: `function(val) {
|
|
203
|
+
for (let i = 0; i < this.options.length; i++) {
|
|
204
|
+
if (this.options[i].value === val || this.options[i].text === val) {
|
|
205
|
+
this.selectedIndex = i;
|
|
206
|
+
this.dispatchEvent(new Event('change', { bubbles: true }));
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return false;
|
|
211
|
+
}`,
|
|
212
|
+
arguments: [{ value }],
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
/** Check a checkbox (click if not already checked) */
|
|
216
|
+
async check(backendDOMNodeId, cssBounds) {
|
|
217
|
+
const checked = await this.getCheckedState(backendDOMNodeId);
|
|
218
|
+
if (!checked) {
|
|
219
|
+
await this.click(backendDOMNodeId, cssBounds);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
/** Uncheck a checkbox (click if currently checked) */
|
|
223
|
+
async uncheck(backendDOMNodeId, cssBounds) {
|
|
224
|
+
const checked = await this.getCheckedState(backendDOMNodeId);
|
|
225
|
+
if (checked) {
|
|
226
|
+
await this.click(backendDOMNodeId, cssBounds);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
/** Get .checked state of an element via Runtime */
|
|
230
|
+
async getCheckedState(backendDOMNodeId) {
|
|
231
|
+
const { object } = await this.connection.send('DOM.resolveNode', {
|
|
232
|
+
backendNodeId: backendDOMNodeId,
|
|
233
|
+
});
|
|
234
|
+
const result = await this.connection.send('Runtime.callFunctionOn', {
|
|
235
|
+
objectId: object.objectId,
|
|
236
|
+
functionDeclaration: 'function() { return this.checked; }',
|
|
237
|
+
returnByValue: true,
|
|
238
|
+
});
|
|
239
|
+
return result.result.value === true;
|
|
240
|
+
}
|
|
241
|
+
/** Parse key combo string like "cmd+shift+a" */
|
|
242
|
+
parseCombo(combo) {
|
|
243
|
+
const parts = combo.toLowerCase().split('+');
|
|
244
|
+
const modifiers = [];
|
|
245
|
+
let mainKey = '';
|
|
246
|
+
for (const part of parts) {
|
|
247
|
+
const p = part.trim();
|
|
248
|
+
if (p === 'cmd' || p === 'command' || p === 'meta') {
|
|
249
|
+
modifiers.push('meta');
|
|
250
|
+
}
|
|
251
|
+
else if (p === 'ctrl' || p === 'control') {
|
|
252
|
+
modifiers.push('ctrl');
|
|
253
|
+
}
|
|
254
|
+
else if (p === 'alt' || p === 'option' || p === 'opt') {
|
|
255
|
+
modifiers.push('alt');
|
|
256
|
+
}
|
|
257
|
+
else if (p === 'shift') {
|
|
258
|
+
modifiers.push('shift');
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
mainKey = p;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
const keyInfo = KEY_MAP[mainKey] ?? this.letterKeyInfo(mainKey);
|
|
265
|
+
return { modifiers, keyInfo };
|
|
266
|
+
}
|
|
267
|
+
letterKeyInfo(key) {
|
|
268
|
+
if (key.length === 1) {
|
|
269
|
+
const upper = key.toUpperCase();
|
|
270
|
+
return {
|
|
271
|
+
key: key,
|
|
272
|
+
code: `Key${upper}`,
|
|
273
|
+
keyCode: upper.charCodeAt(0),
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
// Unknown key - pass through
|
|
277
|
+
return { key, code: key, keyCode: 0 };
|
|
278
|
+
}
|
|
279
|
+
modifierKeyName(mod) {
|
|
280
|
+
switch (mod) {
|
|
281
|
+
case 'meta': return 'Meta';
|
|
282
|
+
case 'ctrl': return 'Control';
|
|
283
|
+
case 'alt': return 'Alt';
|
|
284
|
+
case 'shift': return 'Shift';
|
|
285
|
+
default: return mod;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
computeModifiers(mods) {
|
|
289
|
+
if (!mods)
|
|
290
|
+
return 0;
|
|
291
|
+
let flags = 0;
|
|
292
|
+
for (const mod of mods) {
|
|
293
|
+
flags |= MODIFIER_FLAGS[mod] ?? 0;
|
|
294
|
+
}
|
|
295
|
+
return flags;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
exports.CDPInteractions = CDPInteractions;
|
|
299
|
+
function sleep(ms) {
|
|
300
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
301
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.findFreePort = findFreePort;
|
|
37
|
+
const net = __importStar(require("net"));
|
|
38
|
+
const PORT_RANGE_START = 19200;
|
|
39
|
+
const PORT_RANGE_END = 19299;
|
|
40
|
+
/** Find a free port in the CDP range by probing for one not in use. */
|
|
41
|
+
async function findFreePort() {
|
|
42
|
+
for (let port = PORT_RANGE_START; port <= PORT_RANGE_END; port++) {
|
|
43
|
+
if (await isPortAvailable(port)) {
|
|
44
|
+
return port;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
throw new Error(`No available CDP ports in range ${PORT_RANGE_START}-${PORT_RANGE_END}`);
|
|
48
|
+
}
|
|
49
|
+
/** Check if a port is available by attempting a TCP connection */
|
|
50
|
+
function isPortAvailable(port) {
|
|
51
|
+
return new Promise((resolve) => {
|
|
52
|
+
const socket = new net.Socket();
|
|
53
|
+
socket.setTimeout(500);
|
|
54
|
+
socket.on('connect', () => {
|
|
55
|
+
socket.destroy();
|
|
56
|
+
resolve(false); // Port is in use
|
|
57
|
+
});
|
|
58
|
+
socket.on('timeout', () => {
|
|
59
|
+
socket.destroy();
|
|
60
|
+
resolve(true); // Timeout = nothing listening = available
|
|
61
|
+
});
|
|
62
|
+
socket.on('error', () => {
|
|
63
|
+
socket.destroy();
|
|
64
|
+
resolve(true); // Connection refused = available
|
|
65
|
+
});
|
|
66
|
+
socket.connect(port, '127.0.0.1');
|
|
67
|
+
});
|
|
68
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Maps CDP ARIA roles to normalized roles used by the agent-computer Element format.
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.INTERACTIVE_ROLES = void 0;
|
|
5
|
+
exports.mapCDPRole = mapCDPRole;
|
|
6
|
+
const CDP_ROLE_MAP = {
|
|
7
|
+
// Interactive controls
|
|
8
|
+
button: 'button',
|
|
9
|
+
textbox: 'textfield',
|
|
10
|
+
searchbox: 'textfield',
|
|
11
|
+
textfield: 'textfield',
|
|
12
|
+
link: 'link',
|
|
13
|
+
checkbox: 'checkbox',
|
|
14
|
+
radio: 'radio',
|
|
15
|
+
slider: 'slider',
|
|
16
|
+
spinbutton: 'slider',
|
|
17
|
+
combobox: 'dropdown',
|
|
18
|
+
listbox: 'dropdown',
|
|
19
|
+
popupbutton: 'dropdown',
|
|
20
|
+
// Media
|
|
21
|
+
img: 'image',
|
|
22
|
+
image: 'image',
|
|
23
|
+
// Text content
|
|
24
|
+
heading: 'text',
|
|
25
|
+
StaticText: 'text',
|
|
26
|
+
LabelText: 'text',
|
|
27
|
+
paragraph: 'text',
|
|
28
|
+
status: 'text',
|
|
29
|
+
// Tabs
|
|
30
|
+
tab: 'tab',
|
|
31
|
+
tablist: 'tabgroup',
|
|
32
|
+
tabpanel: 'group',
|
|
33
|
+
// Menus
|
|
34
|
+
menuitem: 'menuitem',
|
|
35
|
+
menu: 'group',
|
|
36
|
+
menubar: 'group',
|
|
37
|
+
// Tables
|
|
38
|
+
table: 'table',
|
|
39
|
+
grid: 'table',
|
|
40
|
+
row: 'row',
|
|
41
|
+
cell: 'row',
|
|
42
|
+
gridcell: 'row',
|
|
43
|
+
columnheader: 'row',
|
|
44
|
+
rowheader: 'row',
|
|
45
|
+
// Trees
|
|
46
|
+
tree: 'treeview',
|
|
47
|
+
treeitem: 'menuitem',
|
|
48
|
+
// Misc widgets
|
|
49
|
+
scrollbar: 'scrollarea',
|
|
50
|
+
progressbar: 'progress',
|
|
51
|
+
// Generic / presentational
|
|
52
|
+
generic: 'generic',
|
|
53
|
+
none: 'generic',
|
|
54
|
+
presentation: 'generic',
|
|
55
|
+
separator: 'generic',
|
|
56
|
+
// Document / web areas
|
|
57
|
+
WebArea: 'webarea',
|
|
58
|
+
RootWebArea: 'webarea',
|
|
59
|
+
document: 'webarea',
|
|
60
|
+
// Landmark & structural → group
|
|
61
|
+
navigation: 'group',
|
|
62
|
+
region: 'group',
|
|
63
|
+
main: 'group',
|
|
64
|
+
form: 'group',
|
|
65
|
+
section: 'group',
|
|
66
|
+
article: 'group',
|
|
67
|
+
banner: 'group',
|
|
68
|
+
complementary: 'group',
|
|
69
|
+
contentinfo: 'group',
|
|
70
|
+
list: 'group',
|
|
71
|
+
listitem: 'group',
|
|
72
|
+
group: 'group',
|
|
73
|
+
toolbar: 'group',
|
|
74
|
+
dialog: 'group',
|
|
75
|
+
alertdialog: 'group',
|
|
76
|
+
alert: 'group',
|
|
77
|
+
};
|
|
78
|
+
/** Map a CDP ARIA role string to a normalized role. Unknown roles become 'generic'. */
|
|
79
|
+
function mapCDPRole(cdpRole) {
|
|
80
|
+
return CDP_ROLE_MAP[cdpRole] ?? 'generic';
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* CDP roles considered interactive — used to filter the AX tree in interactive mode.
|
|
84
|
+
* These are CDP/ARIA role strings (keys in CDP_ROLE_MAP), not normalized roles.
|
|
85
|
+
*/
|
|
86
|
+
exports.INTERACTIVE_ROLES = new Set([
|
|
87
|
+
'button',
|
|
88
|
+
'textbox',
|
|
89
|
+
'searchbox',
|
|
90
|
+
'textfield',
|
|
91
|
+
'link',
|
|
92
|
+
'checkbox',
|
|
93
|
+
'radio',
|
|
94
|
+
'slider',
|
|
95
|
+
'spinbutton',
|
|
96
|
+
'combobox',
|
|
97
|
+
'listbox',
|
|
98
|
+
'popupbutton',
|
|
99
|
+
'tab',
|
|
100
|
+
'menuitem',
|
|
101
|
+
'treeitem',
|
|
102
|
+
]);
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const commands_js_1 = require("../commands.js");
|
|
4
|
+
(0, commands_js_1.registerCommand)('apps', async (args, bridge) => {
|
|
5
|
+
const params = {};
|
|
6
|
+
if (args.flags['running'])
|
|
7
|
+
params.running = true;
|
|
8
|
+
const result = await bridge.send('apps', params);
|
|
9
|
+
return { data: result, exitCode: 0 };
|
|
10
|
+
});
|
|
11
|
+
(0, commands_js_1.registerCommand)('launch', async (args, bridge) => {
|
|
12
|
+
const name = args.positional[0];
|
|
13
|
+
if (!name) {
|
|
14
|
+
return { data: { error: 'Usage: agent-computer launch <name> [--wait] [--background]' }, exitCode: 1 };
|
|
15
|
+
}
|
|
16
|
+
const params = {
|
|
17
|
+
name,
|
|
18
|
+
wait: args.flags['wait'] === true,
|
|
19
|
+
background: args.flags['background'] === true,
|
|
20
|
+
};
|
|
21
|
+
if (args.flags['open']) {
|
|
22
|
+
params.open = args.flags['open'];
|
|
23
|
+
}
|
|
24
|
+
const result = await bridge.send('launch', params);
|
|
25
|
+
return { data: result, exitCode: 0 };
|
|
26
|
+
});
|
|
27
|
+
(0, commands_js_1.registerCommand)('relaunch', async (args, bridge) => {
|
|
28
|
+
const name = args.positional[0];
|
|
29
|
+
if (!name) {
|
|
30
|
+
return { data: { error: 'Usage: agent-computer relaunch <name>' }, exitCode: 1 };
|
|
31
|
+
}
|
|
32
|
+
const result = await bridge.send('relaunch', { name });
|
|
33
|
+
return { data: result, exitCode: 0 };
|
|
34
|
+
});
|
|
35
|
+
(0, commands_js_1.registerCommand)('quit', async (args, bridge) => {
|
|
36
|
+
const name = args.positional[0];
|
|
37
|
+
if (!name) {
|
|
38
|
+
return { data: { error: 'Usage: agent-computer quit <name> [--force]' }, exitCode: 1 };
|
|
39
|
+
}
|
|
40
|
+
const result = await bridge.send('quit', { name, force: args.flags['force'] === true });
|
|
41
|
+
return { data: result, exitCode: 0 };
|
|
42
|
+
});
|
|
43
|
+
(0, commands_js_1.registerCommand)('hide', async (args, bridge) => {
|
|
44
|
+
const name = args.positional[0];
|
|
45
|
+
if (!name)
|
|
46
|
+
return { data: { error: 'Usage: agent-computer hide <name>' }, exitCode: 1 };
|
|
47
|
+
const result = await bridge.send('hide', { name });
|
|
48
|
+
return { data: result, exitCode: 0 };
|
|
49
|
+
});
|
|
50
|
+
(0, commands_js_1.registerCommand)('unhide', async (args, bridge) => {
|
|
51
|
+
const name = args.positional[0];
|
|
52
|
+
if (!name)
|
|
53
|
+
return { data: { error: 'Usage: agent-computer unhide <name>' }, exitCode: 1 };
|
|
54
|
+
const result = await bridge.send('unhide', { name });
|
|
55
|
+
return { data: result, exitCode: 0 };
|
|
56
|
+
});
|
|
57
|
+
(0, commands_js_1.registerCommand)('switch', async (args, bridge) => {
|
|
58
|
+
const name = args.positional[0];
|
|
59
|
+
if (!name)
|
|
60
|
+
return { data: { error: 'Usage: agent-computer switch <name>' }, exitCode: 1 };
|
|
61
|
+
const result = await bridge.send('switch', { name });
|
|
62
|
+
return { data: result, exitCode: 0 };
|
|
63
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const commands_js_1 = require("../commands.js");
|
|
4
|
+
(0, commands_js_1.registerCommand)('batch', async (args, bridge) => {
|
|
5
|
+
const jsonStr = args.positional[0] || args.subcommand;
|
|
6
|
+
if (!jsonStr) {
|
|
7
|
+
return { data: { error: 'Usage: agent-computer batch \'[["method", ...args], ...]\'' }, exitCode: 1 };
|
|
8
|
+
}
|
|
9
|
+
let commands;
|
|
10
|
+
try {
|
|
11
|
+
commands = JSON.parse(jsonStr);
|
|
12
|
+
if (!Array.isArray(commands))
|
|
13
|
+
throw new Error('not array');
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return { data: { error: 'Invalid JSON. Expected array of command arrays.' }, exitCode: 1 };
|
|
17
|
+
}
|
|
18
|
+
const params = { commands };
|
|
19
|
+
if (args.flags['no-stop-on-error'])
|
|
20
|
+
params.stop_on_error = false;
|
|
21
|
+
const result = await bridge.send('batch', params);
|
|
22
|
+
return { data: result, exitCode: 0 };
|
|
23
|
+
});
|
|
24
|
+
(0, commands_js_1.registerCommand)('changed', async (args, bridge) => {
|
|
25
|
+
const params = {};
|
|
26
|
+
if (args.flags['app'])
|
|
27
|
+
params.app = args.flags['app'];
|
|
28
|
+
const result = await bridge.send('changed', params);
|
|
29
|
+
return { data: result, exitCode: 0 };
|
|
30
|
+
});
|
|
31
|
+
(0, commands_js_1.registerCommand)('diff', async (args, bridge) => {
|
|
32
|
+
const params = {};
|
|
33
|
+
if (args.flags['app'])
|
|
34
|
+
params.app = args.flags['app'];
|
|
35
|
+
const result = await bridge.send('diff', params);
|
|
36
|
+
return { data: result, exitCode: 0 };
|
|
37
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const commands_js_1 = require("../commands.js");
|
|
4
|
+
const parser_js_1 = require("../parser.js");
|
|
5
|
+
(0, commands_js_1.registerCommand)('click', async (args, bridge) => {
|
|
6
|
+
const sel = args.positional[0];
|
|
7
|
+
if (!sel) {
|
|
8
|
+
return { data: { error: 'Usage: agent-computer click <ref|x,y|label> [--right] [--double] [--count N] [--modifiers keys]' }, exitCode: 1 };
|
|
9
|
+
}
|
|
10
|
+
const params = {};
|
|
11
|
+
const parsed = (0, parser_js_1.parseSelector)(sel);
|
|
12
|
+
if (parsed.type === 'ref') {
|
|
13
|
+
params.ref = parsed.ref;
|
|
14
|
+
}
|
|
15
|
+
else if (parsed.type === 'coords') {
|
|
16
|
+
params.x = parsed.x;
|
|
17
|
+
params.y = parsed.y;
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
// Label-based click — not yet implemented, pass as ref for now
|
|
21
|
+
params.ref = parsed.label;
|
|
22
|
+
}
|
|
23
|
+
if (args.flags['right'])
|
|
24
|
+
params.right = true;
|
|
25
|
+
if (args.flags['double'])
|
|
26
|
+
params.double = true;
|
|
27
|
+
if (args.flags['count'])
|
|
28
|
+
params.count = parseInt(args.flags['count'], 10);
|
|
29
|
+
if (args.flags['modifiers']) {
|
|
30
|
+
const mods = args.flags['modifiers'];
|
|
31
|
+
params.modifiers = mods.split(',').map(m => m.trim());
|
|
32
|
+
}
|
|
33
|
+
if (args.flags['wait'])
|
|
34
|
+
params.wait = true;
|
|
35
|
+
if (args.flags['human'])
|
|
36
|
+
params.human = true;
|
|
37
|
+
const result = await bridge.send('click', params);
|
|
38
|
+
return { data: result, exitCode: 0 };
|
|
39
|
+
});
|
|
40
|
+
(0, commands_js_1.registerCommand)('hover', async (args, bridge) => {
|
|
41
|
+
const sel = args.positional[0];
|
|
42
|
+
if (!sel) {
|
|
43
|
+
return { data: { error: 'Usage: agent-computer hover <ref|x,y>' }, exitCode: 1 };
|
|
44
|
+
}
|
|
45
|
+
const params = {};
|
|
46
|
+
const parsed = (0, parser_js_1.parseSelector)(sel);
|
|
47
|
+
if (parsed.type === 'ref') {
|
|
48
|
+
params.ref = parsed.ref;
|
|
49
|
+
}
|
|
50
|
+
else if (parsed.type === 'coords') {
|
|
51
|
+
params.x = parsed.x;
|
|
52
|
+
params.y = parsed.y;
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
params.ref = parsed.label;
|
|
56
|
+
}
|
|
57
|
+
if (args.flags['human'])
|
|
58
|
+
params.human = true;
|
|
59
|
+
const result = await bridge.send('hover', params);
|
|
60
|
+
return { data: result, exitCode: 0 };
|
|
61
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const commands_js_1 = require("../commands.js");
|
|
4
|
+
(0, commands_js_1.registerCommand)('clipboard', async (args, bridge) => {
|
|
5
|
+
const sub = args.subcommand;
|
|
6
|
+
switch (sub) {
|
|
7
|
+
case 'set': {
|
|
8
|
+
const text = args.positional[0];
|
|
9
|
+
if (!text) {
|
|
10
|
+
return { data: { error: 'Usage: agent-computer clipboard set <text>' }, exitCode: 1 };
|
|
11
|
+
}
|
|
12
|
+
const result = await bridge.send('clipboard_set', { text });
|
|
13
|
+
return { data: result, exitCode: 0 };
|
|
14
|
+
}
|
|
15
|
+
case 'copy': {
|
|
16
|
+
const result = await bridge.send('clipboard_copy');
|
|
17
|
+
return { data: result, exitCode: 0 };
|
|
18
|
+
}
|
|
19
|
+
case 'paste': {
|
|
20
|
+
const result = await bridge.send('paste', { text: '' });
|
|
21
|
+
// Actually clipboard paste is just Cmd+V
|
|
22
|
+
const pasteResult = await bridge.send('key', { combo: 'cmd+v' });
|
|
23
|
+
return { data: pasteResult, exitCode: 0 };
|
|
24
|
+
}
|
|
25
|
+
default: {
|
|
26
|
+
// Read clipboard
|
|
27
|
+
const result = await bridge.send('clipboard_read');
|
|
28
|
+
return { data: result, exitCode: 0 };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
});
|