agentmb 0.1.1 → 0.3.2
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 +1002 -153
- package/dist/browser/actions.d.ts +25 -2
- package/dist/browser/actions.d.ts.map +1 -1
- package/dist/browser/actions.js +122 -22
- package/dist/browser/actions.js.map +1 -1
- package/dist/browser/manager.d.ts +35 -0
- package/dist/browser/manager.d.ts.map +1 -1
- package/dist/browser/manager.js +244 -16
- package/dist/browser/manager.js.map +1 -1
- package/dist/cli/commands/actions.d.ts.map +1 -1
- package/dist/cli/commands/actions.js +342 -80
- package/dist/cli/commands/actions.js.map +1 -1
- package/dist/cli/commands/browser-launch.d.ts +7 -0
- package/dist/cli/commands/browser-launch.d.ts.map +1 -0
- package/dist/cli/commands/browser-launch.js +116 -0
- package/dist/cli/commands/browser-launch.js.map +1 -0
- package/dist/cli/commands/session.d.ts.map +1 -1
- package/dist/cli/commands/session.js +76 -4
- package/dist/cli/commands/session.js.map +1 -1
- package/dist/cli/index.js +3 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/daemon/index.js +2 -2
- package/dist/daemon/index.js.map +1 -1
- package/dist/daemon/routes/actions.d.ts.map +1 -1
- package/dist/daemon/routes/actions.js +516 -78
- package/dist/daemon/routes/actions.js.map +1 -1
- package/dist/daemon/routes/interaction.d.ts.map +1 -1
- package/dist/daemon/routes/interaction.js +10 -1
- package/dist/daemon/routes/interaction.js.map +1 -1
- package/dist/daemon/routes/sessions.d.ts.map +1 -1
- package/dist/daemon/routes/sessions.js +314 -3
- package/dist/daemon/routes/sessions.js.map +1 -1
- package/dist/daemon/routes/state.d.ts.map +1 -1
- package/dist/daemon/routes/state.js +26 -0
- package/dist/daemon/routes/state.js.map +1 -1
- package/dist/daemon/server.js +1 -1
- package/dist/daemon/session.d.ts +19 -0
- package/dist/daemon/session.d.ts.map +1 -1
- package/dist/daemon/session.js +13 -0
- package/dist/daemon/session.js.map +1 -1
- package/dist/policy/types.d.ts.map +1 -1
- package/dist/policy/types.js +14 -12
- package/dist/policy/types.js.map +1 -1
- package/package.json +4 -2
- package/skills/agentmb/SKILL.md +541 -0
- package/skills/agentmb/references/authentication.md +180 -0
- package/skills/agentmb/references/browser-modes.md +167 -0
- package/skills/agentmb/references/commands.md +231 -0
- package/skills/agentmb/references/locator-modes.md +254 -0
- package/skills/agentmb/references/session-management.md +260 -0
|
@@ -11,6 +11,27 @@ const client_1 = require("../client");
|
|
|
11
11
|
function collectValues(val, prev) {
|
|
12
12
|
return prev.concat([val]);
|
|
13
13
|
}
|
|
14
|
+
// T09: extension → MIME mapping for upload auto-inference
|
|
15
|
+
const EXT_TO_MIME = {
|
|
16
|
+
jpg: 'image/jpeg', jpeg: 'image/jpeg', png: 'image/png', gif: 'image/gif',
|
|
17
|
+
webp: 'image/webp', avif: 'image/avif', svg: 'image/svg+xml',
|
|
18
|
+
pdf: 'application/pdf',
|
|
19
|
+
mp4: 'video/mp4', mov: 'video/quicktime', avi: 'video/x-msvideo', webm: 'video/webm',
|
|
20
|
+
mp3: 'audio/mpeg', wav: 'audio/wav', ogg: 'audio/ogg', m4a: 'audio/mp4',
|
|
21
|
+
txt: 'text/plain', csv: 'text/csv', html: 'text/html', css: 'text/css', js: 'text/javascript',
|
|
22
|
+
json: 'application/json', xml: 'application/xml',
|
|
23
|
+
zip: 'application/zip', gz: 'application/gzip', tar: 'application/x-tar',
|
|
24
|
+
doc: 'application/msword',
|
|
25
|
+
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
26
|
+
xls: 'application/vnd.ms-excel',
|
|
27
|
+
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
28
|
+
ppt: 'application/vnd.ms-powerpoint',
|
|
29
|
+
pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
|
30
|
+
};
|
|
31
|
+
function inferMime(filePath) {
|
|
32
|
+
const ext = path_1.default.extname(filePath).slice(1).toLowerCase();
|
|
33
|
+
return EXT_TO_MIME[ext] ?? 'application/octet-stream';
|
|
34
|
+
}
|
|
14
35
|
function printDiagnostics(res) {
|
|
15
36
|
console.error('Error:', res.error);
|
|
16
37
|
if (res.url)
|
|
@@ -29,11 +50,12 @@ function actionCommands(program) {
|
|
|
29
50
|
.command('navigate <session-id> <url>')
|
|
30
51
|
.description('Navigate to URL')
|
|
31
52
|
.option('--wait-until <event>', 'Wait until event (load|networkidle|commit)', 'load')
|
|
53
|
+
.option('--page-id <id>', 'Target a specific page/tab by page_id (default: active tab)')
|
|
32
54
|
.action(async (sessionId, url, opts) => {
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
});
|
|
55
|
+
const body = { url, wait_until: opts.waitUntil };
|
|
56
|
+
if (opts.pageId)
|
|
57
|
+
body.page_id = opts.pageId;
|
|
58
|
+
const res = await (0, client_1.apiPost)(`/api/v1/sessions/${sessionId}/navigate`, body);
|
|
37
59
|
if (res.error) {
|
|
38
60
|
console.error('Error:', res.error);
|
|
39
61
|
process.exit(1);
|
|
@@ -47,11 +69,12 @@ function actionCommands(program) {
|
|
|
47
69
|
.option('-o, --out <file>', 'Output file path', './screenshot.png')
|
|
48
70
|
.option('--full-page', 'Capture full page')
|
|
49
71
|
.option('--format <fmt>', 'Format: png|jpeg', 'png')
|
|
72
|
+
.option('--page-id <id>', 'Target a specific page/tab by page_id (default: active tab)')
|
|
50
73
|
.action(async (sessionId, opts) => {
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
});
|
|
74
|
+
const body = { format: opts.format, full_page: opts.fullPage };
|
|
75
|
+
if (opts.pageId)
|
|
76
|
+
body.page_id = opts.pageId;
|
|
77
|
+
const res = await (0, client_1.apiPost)(`/api/v1/sessions/${sessionId}/screenshot`, body);
|
|
55
78
|
if (res.error) {
|
|
56
79
|
printDiagnostics(res);
|
|
57
80
|
process.exit(1);
|
|
@@ -63,8 +86,12 @@ function actionCommands(program) {
|
|
|
63
86
|
program
|
|
64
87
|
.command('eval <session-id> <expression>')
|
|
65
88
|
.description('Evaluate JavaScript expression in browser')
|
|
66
|
-
.
|
|
67
|
-
|
|
89
|
+
.option('--page-id <id>', 'Target a specific page/tab by page_id (default: active tab)')
|
|
90
|
+
.action(async (sessionId, expression, opts) => {
|
|
91
|
+
const body = { expression };
|
|
92
|
+
if (opts.pageId)
|
|
93
|
+
body.page_id = opts.pageId;
|
|
94
|
+
const res = await (0, client_1.apiPost)(`/api/v1/sessions/${sessionId}/eval`, body);
|
|
68
95
|
if (res.error) {
|
|
69
96
|
printDiagnostics(res);
|
|
70
97
|
process.exit(1);
|
|
@@ -91,11 +118,16 @@ function actionCommands(program) {
|
|
|
91
118
|
});
|
|
92
119
|
program
|
|
93
120
|
.command('click <session-id> <selector-or-eid>')
|
|
94
|
-
.description('Click an element (use --element-id
|
|
121
|
+
.description('Click an element (use --element-id or --ref-id to identify element)')
|
|
95
122
|
.option('--element-id', 'Treat selector-or-eid as an element_id from element-map')
|
|
123
|
+
.option('--ref-id', 'Treat selector-or-eid as a snapshot ref_id (snap_XXXXXX:eN)')
|
|
124
|
+
.option('--page-id <id>', 'Target a specific page/tab by page_id (default: active tab)')
|
|
96
125
|
.action(async (sessionId, selectorOrEid, opts) => {
|
|
97
|
-
|
|
98
|
-
|
|
126
|
+
const body = opts.refId
|
|
127
|
+
? { ref_id: selectorOrEid }
|
|
128
|
+
: opts.elementId ? { element_id: selectorOrEid } : { selector: selectorOrEid };
|
|
129
|
+
if (opts.pageId)
|
|
130
|
+
body.page_id = opts.pageId;
|
|
99
131
|
const res = await (0, client_1.apiPost)(`/api/v1/sessions/${sessionId}/click`, body);
|
|
100
132
|
if (res.error) {
|
|
101
133
|
console.error('Error:', res.error);
|
|
@@ -105,13 +137,25 @@ function actionCommands(program) {
|
|
|
105
137
|
});
|
|
106
138
|
program
|
|
107
139
|
.command('fill <session-id> <selector-or-eid> <value>')
|
|
108
|
-
.description('Fill a form field (use --element-id
|
|
140
|
+
.description('Fill a form field (use --element-id or --ref-id to identify element)')
|
|
109
141
|
.option('--element-id', 'Treat selector-or-eid as an element_id from element-map')
|
|
142
|
+
.option('--ref-id', 'Treat selector-or-eid as a snapshot ref_id (snap_XXXXXX:eN)')
|
|
143
|
+
.option('--fill-strategy <type>', 'Fill strategy: normal (default) | type (simulate keystrokes)', 'normal')
|
|
144
|
+
.option('--char-delay-ms <ms>', 'Delay between characters when --fill-strategy=type', '0')
|
|
145
|
+
.option('--page-id <id>', 'Target a specific page/tab by page_id (default: active tab)')
|
|
110
146
|
.action(async (sessionId, selectorOrEid, value, opts) => {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
147
|
+
const body = opts.refId
|
|
148
|
+
? { ref_id: selectorOrEid, value }
|
|
149
|
+
: opts.elementId
|
|
150
|
+
? { element_id: selectorOrEid, value }
|
|
151
|
+
: { selector: selectorOrEid, value };
|
|
152
|
+
if (opts.fillStrategy && opts.fillStrategy !== 'normal')
|
|
153
|
+
body.fill_strategy = opts.fillStrategy;
|
|
154
|
+
const charDelay = parseInt(opts.charDelayMs ?? '0');
|
|
155
|
+
if (charDelay > 0)
|
|
156
|
+
body.char_delay_ms = charDelay;
|
|
157
|
+
if (opts.pageId)
|
|
158
|
+
body.page_id = opts.pageId;
|
|
115
159
|
const res = await (0, client_1.apiPost)(`/api/v1/sessions/${sessionId}/fill`, body);
|
|
116
160
|
if (res.error) {
|
|
117
161
|
console.error('Error:', res.error);
|
|
@@ -155,27 +199,48 @@ function actionCommands(program) {
|
|
|
155
199
|
console.log(`✓ Session ${sessionId} returned to headless mode. Automation can resume.`);
|
|
156
200
|
});
|
|
157
201
|
program
|
|
158
|
-
.command('type <session-id> <selector> <text>')
|
|
159
|
-
.description('Type text
|
|
202
|
+
.command('type <session-id> <selector-or-eid> <text>')
|
|
203
|
+
.description('Type text character by character (use --element-id or --ref-id to identify element)')
|
|
204
|
+
.option('--element-id', 'Treat selector-or-eid as an element_id from element-map')
|
|
205
|
+
.option('--ref-id', 'Treat selector-or-eid as a snapshot ref_id (snap_XXXXXX:eN)')
|
|
160
206
|
.option('--delay-ms <ms>', 'Delay between keystrokes (ms)', '0')
|
|
161
|
-
.
|
|
162
|
-
|
|
207
|
+
.option('--page-id <id>', 'Target a specific page/tab by page_id (default: active tab)')
|
|
208
|
+
.action(async (sessionId, selectorOrEid, text, opts) => {
|
|
209
|
+
const delay_ms = parseInt(opts.delayMs);
|
|
210
|
+
const body = opts.refId
|
|
211
|
+
? { ref_id: selectorOrEid, text, delay_ms }
|
|
212
|
+
: opts.elementId
|
|
213
|
+
? { element_id: selectorOrEid, text, delay_ms }
|
|
214
|
+
: { selector: selectorOrEid, text, delay_ms };
|
|
215
|
+
if (opts.pageId)
|
|
216
|
+
body.page_id = opts.pageId;
|
|
217
|
+
const res = await (0, client_1.apiPost)(`/api/v1/sessions/${sessionId}/type`, body);
|
|
163
218
|
if (res.error) {
|
|
164
219
|
printDiagnostics(res);
|
|
165
220
|
process.exit(1);
|
|
166
221
|
}
|
|
167
|
-
console.log(`✓ Typed into "${
|
|
222
|
+
console.log(`✓ Typed into "${selectorOrEid}" (${res.duration_ms}ms)`);
|
|
168
223
|
});
|
|
169
224
|
program
|
|
170
|
-
.command('press <session-id> <selector> <key>')
|
|
171
|
-
.description('Press a key or combo (e.g. Enter, Tab, Control+a)')
|
|
172
|
-
.
|
|
173
|
-
|
|
225
|
+
.command('press <session-id> <selector-or-eid> <key>')
|
|
226
|
+
.description('Press a key or combo (e.g. Enter, Tab, Control+a) (use --element-id or --ref-id to identify element)')
|
|
227
|
+
.option('--element-id', 'Treat selector-or-eid as an element_id from element-map')
|
|
228
|
+
.option('--ref-id', 'Treat selector-or-eid as a snapshot ref_id (snap_XXXXXX:eN)')
|
|
229
|
+
.option('--page-id <id>', 'Target a specific page/tab by page_id (default: active tab)')
|
|
230
|
+
.action(async (sessionId, selectorOrEid, key, opts) => {
|
|
231
|
+
const body = opts.refId
|
|
232
|
+
? { ref_id: selectorOrEid, key }
|
|
233
|
+
: opts.elementId
|
|
234
|
+
? { element_id: selectorOrEid, key }
|
|
235
|
+
: { selector: selectorOrEid, key };
|
|
236
|
+
if (opts.pageId)
|
|
237
|
+
body.page_id = opts.pageId;
|
|
238
|
+
const res = await (0, client_1.apiPost)(`/api/v1/sessions/${sessionId}/press`, body);
|
|
174
239
|
if (res.error) {
|
|
175
240
|
printDiagnostics(res);
|
|
176
241
|
process.exit(1);
|
|
177
242
|
}
|
|
178
|
-
console.log(`✓ Pressed "${key}" on "${
|
|
243
|
+
console.log(`✓ Pressed "${key}" on "${selectorOrEid}" (${res.duration_ms}ms)`);
|
|
179
244
|
});
|
|
180
245
|
program
|
|
181
246
|
.command('select <session-id> <selector> <value...>')
|
|
@@ -189,15 +254,22 @@ function actionCommands(program) {
|
|
|
189
254
|
console.log(`✓ Selected [${res.selected.join(', ')}] in "${selector}" (${res.duration_ms}ms)`);
|
|
190
255
|
});
|
|
191
256
|
program
|
|
192
|
-
.command('hover <session-id> <selector>')
|
|
193
|
-
.description('Hover over an element')
|
|
194
|
-
.
|
|
195
|
-
|
|
257
|
+
.command('hover <session-id> <selector-or-eid>')
|
|
258
|
+
.description('Hover over an element (use --element-id or --ref-id to identify element)')
|
|
259
|
+
.option('--element-id', 'Treat selector-or-eid as an element_id from element-map')
|
|
260
|
+
.option('--ref-id', 'Treat selector-or-eid as a snapshot ref_id (snap_XXXXXX:eN)')
|
|
261
|
+
.action(async (sessionId, selectorOrEid, opts) => {
|
|
262
|
+
const body = opts.refId
|
|
263
|
+
? { ref_id: selectorOrEid }
|
|
264
|
+
: opts.elementId
|
|
265
|
+
? { element_id: selectorOrEid }
|
|
266
|
+
: { selector: selectorOrEid };
|
|
267
|
+
const res = await (0, client_1.apiPost)(`/api/v1/sessions/${sessionId}/hover`, body);
|
|
196
268
|
if (res.error) {
|
|
197
269
|
printDiagnostics(res);
|
|
198
270
|
process.exit(1);
|
|
199
271
|
}
|
|
200
|
-
console.log(`✓ Hovered over "${
|
|
272
|
+
console.log(`✓ Hovered over "${selectorOrEid}" (${res.duration_ms}ms)`);
|
|
201
273
|
});
|
|
202
274
|
program
|
|
203
275
|
.command('wait-selector <session-id> <selector>')
|
|
@@ -246,33 +318,45 @@ function actionCommands(program) {
|
|
|
246
318
|
});
|
|
247
319
|
program
|
|
248
320
|
.command('upload <session-id> <selector> <file>')
|
|
249
|
-
.description('Upload a local file to a file input element')
|
|
250
|
-
.option('--mime-type <type>', 'MIME type
|
|
321
|
+
.description('Upload a local file to a file input element (MIME auto-inferred from extension)')
|
|
322
|
+
.option('--mime-type <type>', 'Override MIME type (default: inferred from file extension)')
|
|
251
323
|
.action(async (sessionId, selector, file, opts) => {
|
|
252
324
|
const buf = fs_1.default.readFileSync(file);
|
|
325
|
+
const mime_type = opts.mimeType ?? inferMime(file);
|
|
253
326
|
const res = await (0, client_1.apiPost)(`/api/v1/sessions/${sessionId}/upload`, {
|
|
254
327
|
selector,
|
|
255
328
|
content: buf.toString('base64'),
|
|
256
329
|
filename: path_1.default.basename(file),
|
|
257
|
-
mime_type
|
|
330
|
+
mime_type,
|
|
258
331
|
});
|
|
259
332
|
if (res.error) {
|
|
260
333
|
printDiagnostics(res);
|
|
261
334
|
process.exit(1);
|
|
262
335
|
}
|
|
263
|
-
console.log(`✓ Uploaded "${res.filename}" (${res.size_bytes} bytes, ${res.duration_ms}ms)`);
|
|
336
|
+
console.log(`✓ Uploaded "${res.filename}" (${res.size_bytes} bytes, mime=${mime_type}, ${res.duration_ms}ms)`);
|
|
264
337
|
});
|
|
265
338
|
program
|
|
266
|
-
.command('download <session-id> <selector>')
|
|
267
|
-
.description('Click a download link and save the file')
|
|
339
|
+
.command('download <session-id> <selector-or-eid>')
|
|
340
|
+
.description('Click a download link and save the file (requires session --accept-downloads; use --element-id or --ref-id to identify element)')
|
|
268
341
|
.option('-o, --out <file>', 'Output file path (default: suggested filename)')
|
|
269
342
|
.option('--timeout-ms <ms>', 'Timeout in ms', '30000')
|
|
270
|
-
.
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
343
|
+
.option('--element-id', 'Treat selector-or-eid as an element_id from element-map')
|
|
344
|
+
.option('--ref-id', 'Treat selector-or-eid as a snapshot ref_id (snap_XXXXXX:eN)')
|
|
345
|
+
.action(async (sessionId, selectorOrEid, opts) => {
|
|
346
|
+
const body = opts.refId
|
|
347
|
+
? { ref_id: selectorOrEid }
|
|
348
|
+
: opts.elementId ? { element_id: selectorOrEid } : { selector: selectorOrEid };
|
|
349
|
+
body.timeout_ms = parseInt(opts.timeoutMs);
|
|
350
|
+
const res = await (0, client_1.apiPost)(`/api/v1/sessions/${sessionId}/download`, body);
|
|
274
351
|
if (res.error) {
|
|
275
|
-
|
|
352
|
+
if (res.error === 'download_not_enabled') {
|
|
353
|
+
console.error('Error: Downloads are disabled for this session.');
|
|
354
|
+
console.error(' Create the session with --accept-downloads flag:');
|
|
355
|
+
console.error(' agentmb session new --accept-downloads');
|
|
356
|
+
}
|
|
357
|
+
else {
|
|
358
|
+
printDiagnostics(res);
|
|
359
|
+
}
|
|
276
360
|
process.exit(1);
|
|
277
361
|
}
|
|
278
362
|
const outPath = opts.out ?? res.filename;
|
|
@@ -361,14 +445,20 @@ function actionCommands(program) {
|
|
|
361
445
|
// ---------------------------------------------------------------------------
|
|
362
446
|
program
|
|
363
447
|
.command('element-map <session-id>')
|
|
364
|
-
.description('Scan the page and return a numbered element map (assigns stable element IDs)')
|
|
448
|
+
.description('Scan the page and return a numbered element map (assigns stable element IDs). Note: icon-only elements without aria-label/title/text will have an empty label unless --include-unlabeled is set.')
|
|
365
449
|
.option('--scope <selector>', 'Limit scan to elements inside this CSS selector')
|
|
366
450
|
.option('--limit <n>', 'Max elements to return', '500')
|
|
451
|
+
.option('--include-unlabeled', 'Include icon-only elements with no accessible text; synthesizes [tag @ x,y] label as fallback')
|
|
367
452
|
.option('--json', 'Output raw JSON instead of a table')
|
|
453
|
+
.option('--page-id <id>', 'Target a specific page/tab by page_id (default: active tab)')
|
|
368
454
|
.action(async (sessionId, opts) => {
|
|
369
455
|
const body = { limit: parseInt(opts.limit) };
|
|
370
456
|
if (opts.scope)
|
|
371
457
|
body.scope = opts.scope;
|
|
458
|
+
if (opts.includeUnlabeled)
|
|
459
|
+
body.include_unlabeled = true;
|
|
460
|
+
if (opts.pageId)
|
|
461
|
+
body.page_id = opts.pageId;
|
|
372
462
|
const res = await (0, client_1.apiPost)(`/api/v1/sessions/${sessionId}/element_map`, body);
|
|
373
463
|
if (res.error) {
|
|
374
464
|
console.error('Error:', res.error);
|
|
@@ -386,8 +476,9 @@ function actionCommands(program) {
|
|
|
386
476
|
console.log(`Found ${elements.length} element(s) on ${res.url}:`);
|
|
387
477
|
for (const el of elements) {
|
|
388
478
|
const blocked = el.overlay_blocked ? ' [overlay-blocked]' : '';
|
|
389
|
-
const
|
|
390
|
-
|
|
479
|
+
const label = String(el.label ?? el.text ?? '').slice(0, 60).replace(/\n/g, ' ');
|
|
480
|
+
const src = el.label_source && el.label_source !== 'none' ? ` [${el.label_source}]` : '';
|
|
481
|
+
console.log(` ${el.element_id} <${el.tag}> role=${el.role}${blocked}${src} ${label}`);
|
|
391
482
|
}
|
|
392
483
|
});
|
|
393
484
|
program
|
|
@@ -395,9 +486,13 @@ function actionCommands(program) {
|
|
|
395
486
|
.description('Read a property from an element (text|html|value|attr|count|box)')
|
|
396
487
|
.option('--attr-name <name>', 'Attribute name (required when property=attr)')
|
|
397
488
|
.option('--element-id', 'Treat selector-or-eid as an element_id from element-map')
|
|
489
|
+
.option('--ref-id', 'Treat selector-or-eid as a snapshot ref_id (snap_XXXXXX:eN)')
|
|
398
490
|
.action(async (sessionId, property, target, opts) => {
|
|
399
491
|
const body = { property };
|
|
400
|
-
if (opts.
|
|
492
|
+
if (opts.refId) {
|
|
493
|
+
body.ref_id = target;
|
|
494
|
+
}
|
|
495
|
+
else if (opts.elementId) {
|
|
401
496
|
body.element_id = target;
|
|
402
497
|
}
|
|
403
498
|
else {
|
|
@@ -416,10 +511,14 @@ function actionCommands(program) {
|
|
|
416
511
|
.command('assert <session-id> <property> <selector-or-eid>')
|
|
417
512
|
.description('Assert element state: visible|enabled|checked')
|
|
418
513
|
.option('--element-id', 'Treat selector-or-eid as an element_id from element-map')
|
|
514
|
+
.option('--ref-id', 'Treat selector-or-eid as a snapshot ref_id (snap_XXXXXX:eN)')
|
|
419
515
|
.option('--expected <bool>', 'Expected value (true|false)', 'true')
|
|
420
516
|
.action(async (sessionId, property, target, opts) => {
|
|
421
517
|
const body = { property, expected: opts.expected !== 'false' };
|
|
422
|
-
if (opts.
|
|
518
|
+
if (opts.refId) {
|
|
519
|
+
body.ref_id = target;
|
|
520
|
+
}
|
|
521
|
+
else if (opts.elementId) {
|
|
423
522
|
body.element_id = target;
|
|
424
523
|
}
|
|
425
524
|
else {
|
|
@@ -460,14 +559,22 @@ function actionCommands(program) {
|
|
|
460
559
|
// ---------------------------------------------------------------------------
|
|
461
560
|
program
|
|
462
561
|
.command('snapshot-map <session-id>')
|
|
463
|
-
.description('Snapshot the page element map with page_rev tracking (returns ref_ids for stable targeting)'
|
|
562
|
+
.description('Snapshot the page element map with page_rev tracking (returns ref_ids for stable targeting).\n' +
|
|
563
|
+
'Limitation: elements with no accessible text (aria-label/title/placeholder/innerText) will have\n' +
|
|
564
|
+
'an empty label. Use --include-unlabeled to synthesize a [tag @ x,y] fallback for icon-only elements.')
|
|
464
565
|
.option('--scope <selector>', 'Limit scan to elements inside this CSS selector')
|
|
465
566
|
.option('--limit <n>', 'Max elements to return', '500')
|
|
567
|
+
.option('--include-unlabeled', 'Include icon-only elements with no accessible text; synthesizes [tag @ x,y] fallback label')
|
|
466
568
|
.option('--json', 'Output raw JSON instead of a table')
|
|
569
|
+
.option('--page-id <id>', 'Target a specific page/tab by page_id (default: active tab)')
|
|
467
570
|
.action(async (sessionId, opts) => {
|
|
468
571
|
const body = { limit: parseInt(opts.limit) };
|
|
469
572
|
if (opts.scope)
|
|
470
573
|
body.scope = opts.scope;
|
|
574
|
+
if (opts.includeUnlabeled)
|
|
575
|
+
body.include_unlabeled = true;
|
|
576
|
+
if (opts.pageId)
|
|
577
|
+
body.page_id = opts.pageId;
|
|
471
578
|
const res = await (0, client_1.apiPost)(`/api/v1/sessions/${sessionId}/snapshot_map`, body);
|
|
472
579
|
if (res.error) {
|
|
473
580
|
console.error('Error:', res.error);
|
|
@@ -481,8 +588,9 @@ function actionCommands(program) {
|
|
|
481
588
|
console.log(`Snapshot ${res.snapshot_id} (page_rev=${res.page_rev}) — ${elements.length} element(s) on ${res.url}:`);
|
|
482
589
|
for (const el of elements) {
|
|
483
590
|
const blocked = el.overlay_blocked ? ' [overlay-blocked]' : '';
|
|
484
|
-
const
|
|
485
|
-
|
|
591
|
+
const label = String(el.label ?? el.text ?? '').slice(0, 60).replace(/\n/g, ' ');
|
|
592
|
+
const src = el.label_source && el.label_source !== 'none' ? ` [${el.label_source}]` : '';
|
|
593
|
+
console.log(` ${el.ref_id} <${el.tag}> role=${el.role}${blocked}${src} ${label}`);
|
|
486
594
|
}
|
|
487
595
|
});
|
|
488
596
|
// ---------------------------------------------------------------------------
|
|
@@ -492,11 +600,14 @@ function actionCommands(program) {
|
|
|
492
600
|
.command('dblclick <session-id> <selector-or-eid>')
|
|
493
601
|
.description('Double-click an element')
|
|
494
602
|
.option('--element-id', 'Treat selector-or-eid as an element_id from element-map')
|
|
603
|
+
.option('--ref-id', 'Treat selector-or-eid as a snapshot ref_id (snap_XXXXXX:eN)')
|
|
495
604
|
.option('--timeout-ms <ms>', 'Timeout in ms', '5000')
|
|
496
605
|
.action(async (sessionId, selectorOrEid, opts) => {
|
|
497
|
-
const body = opts.
|
|
498
|
-
? {
|
|
499
|
-
:
|
|
606
|
+
const body = opts.refId
|
|
607
|
+
? { ref_id: selectorOrEid }
|
|
608
|
+
: opts.elementId
|
|
609
|
+
? { element_id: selectorOrEid }
|
|
610
|
+
: { selector: selectorOrEid };
|
|
500
611
|
body.timeout_ms = parseInt(opts.timeoutMs);
|
|
501
612
|
const res = await (0, client_1.apiPost)(`/api/v1/sessions/${sessionId}/dblclick`, body);
|
|
502
613
|
if (res.error) {
|
|
@@ -509,10 +620,13 @@ function actionCommands(program) {
|
|
|
509
620
|
.command('focus <session-id> <selector-or-eid>')
|
|
510
621
|
.description('Focus an element')
|
|
511
622
|
.option('--element-id', 'Treat selector-or-eid as an element_id from element-map')
|
|
623
|
+
.option('--ref-id', 'Treat selector-or-eid as a snapshot ref_id (snap_XXXXXX:eN)')
|
|
512
624
|
.action(async (sessionId, selectorOrEid, opts) => {
|
|
513
|
-
const body = opts.
|
|
514
|
-
? {
|
|
515
|
-
:
|
|
625
|
+
const body = opts.refId
|
|
626
|
+
? { ref_id: selectorOrEid }
|
|
627
|
+
: opts.elementId
|
|
628
|
+
? { element_id: selectorOrEid }
|
|
629
|
+
: { selector: selectorOrEid };
|
|
516
630
|
const res = await (0, client_1.apiPost)(`/api/v1/sessions/${sessionId}/focus`, body);
|
|
517
631
|
if (res.error) {
|
|
518
632
|
console.error('Error:', res.error);
|
|
@@ -524,10 +638,13 @@ function actionCommands(program) {
|
|
|
524
638
|
.command('check <session-id> <selector-or-eid>')
|
|
525
639
|
.description('Check a checkbox or radio button')
|
|
526
640
|
.option('--element-id', 'Treat selector-or-eid as an element_id from element-map')
|
|
641
|
+
.option('--ref-id', 'Treat selector-or-eid as a snapshot ref_id (snap_XXXXXX:eN)')
|
|
527
642
|
.action(async (sessionId, selectorOrEid, opts) => {
|
|
528
|
-
const body = opts.
|
|
529
|
-
? {
|
|
530
|
-
:
|
|
643
|
+
const body = opts.refId
|
|
644
|
+
? { ref_id: selectorOrEid }
|
|
645
|
+
: opts.elementId
|
|
646
|
+
? { element_id: selectorOrEid }
|
|
647
|
+
: { selector: selectorOrEid };
|
|
531
648
|
const res = await (0, client_1.apiPost)(`/api/v1/sessions/${sessionId}/check`, body);
|
|
532
649
|
if (res.error) {
|
|
533
650
|
console.error('Error:', res.error);
|
|
@@ -539,10 +656,13 @@ function actionCommands(program) {
|
|
|
539
656
|
.command('uncheck <session-id> <selector-or-eid>')
|
|
540
657
|
.description('Uncheck a checkbox')
|
|
541
658
|
.option('--element-id', 'Treat selector-or-eid as an element_id from element-map')
|
|
659
|
+
.option('--ref-id', 'Treat selector-or-eid as a snapshot ref_id (snap_XXXXXX:eN)')
|
|
542
660
|
.action(async (sessionId, selectorOrEid, opts) => {
|
|
543
|
-
const body = opts.
|
|
544
|
-
? {
|
|
545
|
-
:
|
|
661
|
+
const body = opts.refId
|
|
662
|
+
? { ref_id: selectorOrEid }
|
|
663
|
+
: opts.elementId
|
|
664
|
+
? { element_id: selectorOrEid }
|
|
665
|
+
: { selector: selectorOrEid };
|
|
546
666
|
const res = await (0, client_1.apiPost)(`/api/v1/sessions/${sessionId}/uncheck`, body);
|
|
547
667
|
if (res.error) {
|
|
548
668
|
console.error('Error:', res.error);
|
|
@@ -554,14 +674,20 @@ function actionCommands(program) {
|
|
|
554
674
|
.command('scroll <session-id> <selector-or-eid>')
|
|
555
675
|
.description('Scroll an element by delta pixels')
|
|
556
676
|
.option('--element-id', 'Treat selector-or-eid as an element_id from element-map')
|
|
677
|
+
.option('--ref-id', 'Treat selector-or-eid as a snapshot ref_id (snap_XXXXXX:eN)')
|
|
557
678
|
.option('--dx <px>', 'Horizontal scroll delta', '0')
|
|
558
679
|
.option('--dy <px>', 'Vertical scroll delta', '300')
|
|
680
|
+
.option('--page-id <id>', 'Target a specific page/tab by page_id (default: active tab)')
|
|
559
681
|
.action(async (sessionId, selectorOrEid, opts) => {
|
|
560
|
-
const body = opts.
|
|
561
|
-
? {
|
|
562
|
-
:
|
|
682
|
+
const body = opts.refId
|
|
683
|
+
? { ref_id: selectorOrEid }
|
|
684
|
+
: opts.elementId
|
|
685
|
+
? { element_id: selectorOrEid }
|
|
686
|
+
: { selector: selectorOrEid };
|
|
563
687
|
body.delta_x = parseInt(opts.dx);
|
|
564
688
|
body.delta_y = parseInt(opts.dy);
|
|
689
|
+
if (opts.pageId)
|
|
690
|
+
body.page_id = opts.pageId;
|
|
565
691
|
const res = await (0, client_1.apiPost)(`/api/v1/sessions/${sessionId}/scroll`, body);
|
|
566
692
|
if (res.error) {
|
|
567
693
|
console.error('Error:', res.error);
|
|
@@ -573,10 +699,13 @@ function actionCommands(program) {
|
|
|
573
699
|
.command('scroll-into-view <session-id> <selector-or-eid>')
|
|
574
700
|
.description('Scroll element into view')
|
|
575
701
|
.option('--element-id', 'Treat selector-or-eid as an element_id from element-map')
|
|
702
|
+
.option('--ref-id', 'Treat selector-or-eid as a snapshot ref_id (snap_XXXXXX:eN)')
|
|
576
703
|
.action(async (sessionId, selectorOrEid, opts) => {
|
|
577
|
-
const body = opts.
|
|
578
|
-
? {
|
|
579
|
-
:
|
|
704
|
+
const body = opts.refId
|
|
705
|
+
? { ref_id: selectorOrEid }
|
|
706
|
+
: opts.elementId
|
|
707
|
+
? { element_id: selectorOrEid }
|
|
708
|
+
: { selector: selectorOrEid };
|
|
580
709
|
const res = await (0, client_1.apiPost)(`/api/v1/sessions/${sessionId}/scroll_into_view`, body);
|
|
581
710
|
if (res.error) {
|
|
582
711
|
console.error('Error:', res.error);
|
|
@@ -586,26 +715,68 @@ function actionCommands(program) {
|
|
|
586
715
|
});
|
|
587
716
|
program
|
|
588
717
|
.command('drag <session-id> <source> <target>')
|
|
589
|
-
.description('Drag
|
|
718
|
+
.description('Drag from source to target. <source>/<target> are CSS selectors unless --source-ref-id/--target-ref-id used.')
|
|
719
|
+
.option('--source-ref-id <ref_id>', 'Use snapshot ref_id as drag source instead of CSS selector')
|
|
720
|
+
.option('--target-ref-id <ref_id>', 'Use snapshot ref_id as drag target instead of CSS selector')
|
|
590
721
|
.option('--timeout-ms <ms>', 'Timeout in ms', '5000')
|
|
591
722
|
.action(async (sessionId, source, target, opts) => {
|
|
592
|
-
const
|
|
723
|
+
const body = { timeout_ms: parseInt(opts.timeoutMs) };
|
|
724
|
+
if (opts.sourceRefId) {
|
|
725
|
+
body.source_ref_id = opts.sourceRefId;
|
|
726
|
+
}
|
|
727
|
+
else {
|
|
728
|
+
body.source = source;
|
|
729
|
+
}
|
|
730
|
+
if (opts.targetRefId) {
|
|
731
|
+
body.target_ref_id = opts.targetRefId;
|
|
732
|
+
}
|
|
733
|
+
else {
|
|
734
|
+
body.target = target;
|
|
735
|
+
}
|
|
736
|
+
const res = await (0, client_1.apiPost)(`/api/v1/sessions/${sessionId}/drag`, body);
|
|
593
737
|
if (res.error) {
|
|
594
738
|
console.error('Error:', res.error);
|
|
595
739
|
process.exit(1);
|
|
596
740
|
}
|
|
597
|
-
|
|
741
|
+
const srcLabel = opts.sourceRefId ?? source;
|
|
742
|
+
const tgtLabel = opts.targetRefId ?? target;
|
|
743
|
+
console.log(`✓ Dragged "${srcLabel}" → "${tgtLabel}" (${res.duration_ms}ms)`);
|
|
598
744
|
});
|
|
599
745
|
program
|
|
600
|
-
.command('mouse-move <session-id>
|
|
601
|
-
.description('Move mouse to
|
|
602
|
-
.
|
|
603
|
-
|
|
746
|
+
.command('mouse-move <session-id> [x] [y]')
|
|
747
|
+
.description('Move mouse to coordinates or element center (use --selector/--element-id/--ref-id to resolve from element)')
|
|
748
|
+
.option('--selector <sel>', 'Move to center of element matching CSS selector')
|
|
749
|
+
.option('--element-id <id>', 'Move to center of element by element_id from element-map')
|
|
750
|
+
.option('--ref-id <id>', 'Move to center of element by snapshot ref_id (snap_XXXXXX:eN)')
|
|
751
|
+
.option('--steps <n>', 'Number of intermediate mouse steps for smooth movement', '1')
|
|
752
|
+
.action(async (sessionId, x, y, opts) => {
|
|
753
|
+
const body = {};
|
|
754
|
+
if (opts.refId) {
|
|
755
|
+
body.ref_id = opts.refId;
|
|
756
|
+
}
|
|
757
|
+
else if (opts.elementId) {
|
|
758
|
+
body.element_id = opts.elementId;
|
|
759
|
+
}
|
|
760
|
+
else if (opts.selector) {
|
|
761
|
+
body.selector = opts.selector;
|
|
762
|
+
}
|
|
763
|
+
else if (x !== undefined && y !== undefined) {
|
|
764
|
+
body.x = parseFloat(x);
|
|
765
|
+
body.y = parseFloat(y);
|
|
766
|
+
}
|
|
767
|
+
else {
|
|
768
|
+
console.error('Error: provide x/y or --selector/--element-id/--ref-id');
|
|
769
|
+
process.exit(1);
|
|
770
|
+
}
|
|
771
|
+
const steps = parseInt(opts.steps ?? '1');
|
|
772
|
+
if (steps > 1)
|
|
773
|
+
body.steps = steps;
|
|
774
|
+
const res = await (0, client_1.apiPost)(`/api/v1/sessions/${sessionId}/mouse_move`, body);
|
|
604
775
|
if (res.error) {
|
|
605
776
|
console.error('Error:', res.error);
|
|
606
777
|
process.exit(1);
|
|
607
778
|
}
|
|
608
|
-
console.log(`✓ Mouse moved to (${x},${y}) (${res.duration_ms}ms)`);
|
|
779
|
+
console.log(`✓ Mouse moved to (${res.x},${res.y}) steps=${res.steps ?? 1} (${res.duration_ms}ms)`);
|
|
609
780
|
});
|
|
610
781
|
program
|
|
611
782
|
.command('mouse-down <session-id>')
|
|
@@ -745,6 +916,7 @@ function actionCommands(program) {
|
|
|
745
916
|
.option('--max-scrolls <n>', 'Maximum scroll steps', '50')
|
|
746
917
|
.option('--scroll-delta <px>', 'Pixels per scroll step', '300')
|
|
747
918
|
.option('--stall-ms <ms>', 'Stop if page height unchanged for this many ms', '1500')
|
|
919
|
+
.option('--step-delay-ms <ms>', 'Delay in ms between each scroll step', '0')
|
|
748
920
|
.action(async (sessionId, opts) => {
|
|
749
921
|
const body = {
|
|
750
922
|
direction: opts.direction,
|
|
@@ -752,6 +924,9 @@ function actionCommands(program) {
|
|
|
752
924
|
scroll_delta: parseInt(opts.scrollDelta),
|
|
753
925
|
stall_ms: parseInt(opts.stallMs),
|
|
754
926
|
};
|
|
927
|
+
const stepDelay = parseInt(opts.stepDelayMs ?? '0');
|
|
928
|
+
if (stepDelay > 0)
|
|
929
|
+
body.step_delay_ms = stepDelay;
|
|
755
930
|
if (opts.scrollSelector)
|
|
756
931
|
body.scroll_selector = opts.scrollSelector;
|
|
757
932
|
if (opts.stopSelector)
|
|
@@ -983,10 +1158,13 @@ function actionCommands(program) {
|
|
|
983
1158
|
// ---------------------------------------------------------------------------
|
|
984
1159
|
program
|
|
985
1160
|
.command('bbox <session-id> <selector-or-eid>')
|
|
986
|
-
.description('Return the bounding box of an element (selector or
|
|
1161
|
+
.description('Return the bounding box of an element (selector, element_id, or ref_id)')
|
|
987
1162
|
.option('--element-id', 'Treat arg as element_id from element-map')
|
|
1163
|
+
.option('--ref-id', 'Treat arg as a snapshot ref_id (snap_XXXXXX:eN)')
|
|
988
1164
|
.action(async (sessionId, target, opts) => {
|
|
989
|
-
const body = opts.
|
|
1165
|
+
const body = opts.refId
|
|
1166
|
+
? { ref_id: target }
|
|
1167
|
+
: opts.elementId ? { element_id: target } : { selector: target };
|
|
990
1168
|
const res = await (0, client_1.apiPost)(`/api/v1/sessions/${sessionId}/bbox`, body);
|
|
991
1169
|
if (res.error) {
|
|
992
1170
|
console.error('Error:', res.error);
|
|
@@ -1097,5 +1275,89 @@ function actionCommands(program) {
|
|
|
1097
1275
|
const r = await (0, client_1.apiDelete)(`/api/v1/sessions/${sessionId}/network_conditions`);
|
|
1098
1276
|
console.log(`✓ Network conditions reset (status ${r.statusCode})`);
|
|
1099
1277
|
});
|
|
1278
|
+
// ---------------------------------------------------------------------------
|
|
1279
|
+
// r08-c07 P2 — CLI 新命令补齐
|
|
1280
|
+
// ---------------------------------------------------------------------------
|
|
1281
|
+
program
|
|
1282
|
+
.command('find <session-id> <query-type> <query>')
|
|
1283
|
+
.description('Semantic element find: query-type is role|text|label|placeholder|alt_text')
|
|
1284
|
+
.option('--nth <n>', 'Zero-based index when multiple matches exist', '0')
|
|
1285
|
+
.option('--json', 'Output raw JSON')
|
|
1286
|
+
.action(async (sessionId, queryType, query, opts) => {
|
|
1287
|
+
const res = await (0, client_1.apiPost)(`/api/v1/sessions/${sessionId}/find`, {
|
|
1288
|
+
query_type: queryType,
|
|
1289
|
+
query,
|
|
1290
|
+
nth: parseInt(opts.nth ?? '0'),
|
|
1291
|
+
});
|
|
1292
|
+
if (res.error) {
|
|
1293
|
+
console.error('Error:', res.error);
|
|
1294
|
+
process.exit(1);
|
|
1295
|
+
}
|
|
1296
|
+
if (opts.json) {
|
|
1297
|
+
console.log(JSON.stringify(res, null, 2));
|
|
1298
|
+
return;
|
|
1299
|
+
}
|
|
1300
|
+
if (!res.found) {
|
|
1301
|
+
console.log(`(not found: ${queryType}="${query}")`);
|
|
1302
|
+
return;
|
|
1303
|
+
}
|
|
1304
|
+
const b = res.bbox;
|
|
1305
|
+
console.log(`✓ Found: tag=${res.tag} text="${res.text ?? ''}" query_type=${res.query_type}${b ? ` bbox=(${b.x},${b.y} ${b.width}×${b.height})` : ''} (${res.duration_ms}ms)`);
|
|
1306
|
+
});
|
|
1307
|
+
program
|
|
1308
|
+
.command('settings <session-id>')
|
|
1309
|
+
.description('Get current browser settings for a session (viewport, UA, url, headless, profile)')
|
|
1310
|
+
.option('--json', 'Output raw JSON')
|
|
1311
|
+
.action(async (sessionId, opts) => {
|
|
1312
|
+
const res = await (0, client_1.apiGet)(`/api/v1/sessions/${sessionId}/settings`);
|
|
1313
|
+
if (res.error) {
|
|
1314
|
+
console.error('Error:', res.error);
|
|
1315
|
+
process.exit(1);
|
|
1316
|
+
}
|
|
1317
|
+
if (opts.json) {
|
|
1318
|
+
console.log(JSON.stringify(res, null, 2));
|
|
1319
|
+
return;
|
|
1320
|
+
}
|
|
1321
|
+
const vp = res.viewport ?? {};
|
|
1322
|
+
console.log(`Session: ${res.session_id}`);
|
|
1323
|
+
console.log(` Viewport: ${vp.width ?? '?'}×${vp.height ?? '?'}`);
|
|
1324
|
+
console.log(` URL: ${res.url ?? '(none)'}`);
|
|
1325
|
+
console.log(` Headless: ${res.headless}`);
|
|
1326
|
+
console.log(` Profile: ${res.profile}`);
|
|
1327
|
+
console.log(` UA: ${res.user_agent ?? '(default)'}`);
|
|
1328
|
+
});
|
|
1329
|
+
program
|
|
1330
|
+
.command('cookie-delete <session-id> <name>')
|
|
1331
|
+
.description('Delete a cookie by name for a session')
|
|
1332
|
+
.option('--domain <domain>', 'Restrict deletion to this domain')
|
|
1333
|
+
.action(async (sessionId, name, opts) => {
|
|
1334
|
+
const body = { name };
|
|
1335
|
+
if (opts.domain)
|
|
1336
|
+
body.domain = opts.domain;
|
|
1337
|
+
const res = await (0, client_1.apiPost)(`/api/v1/sessions/${sessionId}/cookies/delete`, body);
|
|
1338
|
+
if (res.error) {
|
|
1339
|
+
console.error('Error:', res.error);
|
|
1340
|
+
process.exit(1);
|
|
1341
|
+
}
|
|
1342
|
+
console.log(`✓ Deleted ${res.deleted_count} cookie(s) named "${name}" (${res.duration_ms}ms)`);
|
|
1343
|
+
});
|
|
1344
|
+
program
|
|
1345
|
+
.command('upload-url <session-id> <url> <selector-or-eid>')
|
|
1346
|
+
.description('Fetch an asset from URL and upload it to a file input element')
|
|
1347
|
+
.option('--element-id', 'Treat selector-or-eid as element_id from element-map')
|
|
1348
|
+
.option('--ref-id', 'Treat selector-or-eid as snapshot ref_id (snap_XXXXXX:eN)')
|
|
1349
|
+
.action(async (sessionId, url, selectorOrEid, opts) => {
|
|
1350
|
+
const body = opts.refId
|
|
1351
|
+
? { url, ref_id: selectorOrEid }
|
|
1352
|
+
: opts.elementId
|
|
1353
|
+
? { url, element_id: selectorOrEid }
|
|
1354
|
+
: { url, selector: selectorOrEid };
|
|
1355
|
+
const res = await (0, client_1.apiPost)(`/api/v1/sessions/${sessionId}/upload_url`, body);
|
|
1356
|
+
if (res.error) {
|
|
1357
|
+
console.error('Error:', res.error);
|
|
1358
|
+
process.exit(1);
|
|
1359
|
+
}
|
|
1360
|
+
console.log(`✓ Uploaded from URL: ${res.filename} (${res.size_bytes}B, ${res.mime_type ?? 'unknown'}, ${res.duration_ms}ms)`);
|
|
1361
|
+
});
|
|
1100
1362
|
}
|
|
1101
1363
|
//# sourceMappingURL=actions.js.map
|