agentic-browser 2.6.1 → 2.7.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.
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import { r as createCliRuntime } from "../runtime-BzdbtFF4.mjs";
2
+ import { a as isConnectionError, o as ensurePrivateDir, r as createCliRuntime, s as writePrivateFileSync } from "../runtime-CfQ0xGAm.mjs";
3
3
  import fs from "node:fs";
4
4
  import path from "node:path";
5
5
  import crypto from "node:crypto";
6
6
  import { Command } from "commander";
7
- //#region src/cli/commands/agent.ts
7
+ // src/cli/commands/agent.ts
8
8
  const STATE_FILE_NAME = "agent-state.json";
9
9
  function stateFilePath(runtime) {
10
10
  return path.join(runtime.context.config.dataDir, STATE_FILE_NAME);
@@ -20,8 +20,8 @@ function loadState(runtime) {
20
20
  }
21
21
  function saveState(runtime, state) {
22
22
  const dir = path.dirname(stateFilePath(runtime));
23
- fs.mkdirSync(dir, { recursive: true });
24
- fs.writeFileSync(stateFilePath(runtime), JSON.stringify(state, null, 2));
23
+ ensurePrivateDir(dir);
24
+ writePrivateFileSync(stateFilePath(runtime), JSON.stringify(state, null, 2));
25
25
  }
26
26
  function requireSessionId(runtime) {
27
27
  const state = loadState(runtime);
@@ -45,12 +45,24 @@ async function agentStart(runtime) {
45
45
  sessionId: session.sessionId
46
46
  };
47
47
  }
48
+ /** Best-effort page state; never fails the command it decorates. */
49
+ async function tryPageState(runtime, sessionId) {
50
+ try {
51
+ return await runtime.api.getPageState(sessionId);
52
+ } catch {
53
+ return;
54
+ }
55
+ }
48
56
  async function agentStatus(runtime) {
49
57
  const sessionId = requireSessionId(runtime);
58
+ const session = runtime.api.getSession(sessionId);
59
+ const page = session.status === "ready" ? await tryPageState(runtime, sessionId) : void 0;
50
60
  return {
51
61
  ok: true,
52
62
  action: "status",
53
- ...runtime.api.getSession(sessionId)
63
+ ...session,
64
+ url: page?.url,
65
+ title: page?.title
54
66
  };
55
67
  }
56
68
  async function agentStop(runtime) {
@@ -63,6 +75,12 @@ async function agentStop(runtime) {
63
75
  sessionId
64
76
  };
65
77
  }
78
+ /** Only connection-level failures justify a restart + re-run. */
79
+ function isRetryableFailure(message) {
80
+ if (isConnectionError(message)) return true;
81
+ const lower = message.toLowerCase();
82
+ return lower.includes("session") && (lower.includes("dead") || lower.includes("terminated"));
83
+ }
66
84
  async function executeAgentCommand(runtime, input) {
67
85
  const sessionId = requireSessionId(runtime);
68
86
  await ensureReady(runtime, sessionId);
@@ -71,16 +89,24 @@ async function executeAgentCommand(runtime, input) {
71
89
  type: input.type,
72
90
  payload: input.payload
73
91
  });
74
- try {
75
- const first = await attempt();
76
- if (first.resultStatus === "failed") throw new Error(first.resultMessage ?? "Command failed");
77
- return first;
78
- } catch {
92
+ const retryAfterRestart = async () => {
79
93
  await runtime.api.restartSession(sessionId);
80
94
  const second = await attempt();
81
95
  if (second.resultStatus === "failed") throw new Error(second.resultMessage ?? "Command failed after retry");
82
96
  return second;
97
+ };
98
+ const noRetry = input.type === "restart" || input.type === "terminate";
99
+ let first;
100
+ try {
101
+ first = await attempt();
102
+ } catch (error) {
103
+ if (noRetry || !isRetryableFailure(error.message)) throw error;
104
+ return await retryAfterRestart();
83
105
  }
106
+ if (first.resultStatus !== "failed") return first;
107
+ const message = first.resultMessage ?? "Command failed";
108
+ if (noRetry || !isRetryableFailure(message)) throw new Error(message);
109
+ return await retryAfterRestart();
84
110
  }
85
111
  async function agentNavigate(runtime, input) {
86
112
  const result = await executeAgentCommand(runtime, {
@@ -93,10 +119,12 @@ async function agentNavigate(runtime, input) {
93
119
  const dismissed = await runtime.api.dismissCookieBanner(sessionId);
94
120
  if (dismissed.dismissed) cookieBanner = dismissed;
95
121
  } catch {}
122
+ const page = await tryPageState(runtime, result.sessionId);
96
123
  return {
97
124
  ok: true,
98
125
  action: "navigate",
99
126
  ...result,
127
+ url: page?.url,
100
128
  cookieBanner
101
129
  };
102
130
  }
@@ -105,10 +133,155 @@ async function agentInteract(runtime, input) {
105
133
  type: "interact",
106
134
  payload: input
107
135
  });
136
+ const page = await tryPageState(runtime, result.sessionId);
108
137
  return {
109
138
  ok: true,
110
139
  action: input.action,
111
- ...result
140
+ ...result,
141
+ url: page?.url
142
+ };
143
+ }
144
+ const INTERACTION_RESULT_PREFIX = "Interaction result: ";
145
+ /** Evaluates a JavaScript expression in the page (or a frame) and returns its value. */
146
+ async function agentEval(runtime, input) {
147
+ const result = await agentInteract(runtime, {
148
+ action: "evaluate",
149
+ text: input.expression,
150
+ frame: input.frame
151
+ });
152
+ const raw = result.resultMessage?.startsWith(INTERACTION_RESULT_PREFIX) ? result.resultMessage.slice(20) : result.resultMessage ?? "";
153
+ let value = raw;
154
+ try {
155
+ value = raw === "" || raw === "undefined" ? null : JSON.parse(raw);
156
+ } catch {}
157
+ return {
158
+ ok: true,
159
+ action: "eval",
160
+ sessionId: result.sessionId,
161
+ commandId: result.commandId,
162
+ result: value,
163
+ url: result.url
164
+ };
165
+ }
166
+ /** `/re/flags` = regex, `*` = glob, otherwise substring. */
167
+ function matchesUrlPattern(url, pattern) {
168
+ const regex = /^\/(.+)\/([a-z]*)$/.exec(pattern);
169
+ if (regex) return new RegExp(regex[1], regex[2]).test(url);
170
+ if (pattern.includes("*")) {
171
+ const escaped = pattern.split("*").map((part) => part.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join(".*");
172
+ return new RegExp(`^${escaped}$`).test(url);
173
+ }
174
+ return url.includes(pattern);
175
+ }
176
+ const WAIT_POLL_MS = 200;
177
+ async function agentWait(runtime, input) {
178
+ if (!input.selector && !input.text && !input.url && !input.load) throw new Error("Nothing to wait for: pass a selector, --text, --url or --load");
179
+ if (input.selector && input.text) throw new Error("Pass either a selector or --text, not both");
180
+ const sessionId = requireSessionId(runtime);
181
+ await ensureReady(runtime, sessionId);
182
+ const timeoutMs = input.timeoutMs ?? 1e4;
183
+ const started = Date.now();
184
+ const remaining = () => timeoutMs - (Date.now() - started);
185
+ const pollPage = async (done, describe) => {
186
+ let last;
187
+ for (;;) {
188
+ last = await tryPageState(runtime, sessionId) ?? last;
189
+ if (last && done(last)) return last;
190
+ if (remaining() <= 0) throw new Error(`wait timeout after ${timeoutMs}ms: ${describe} (current url: ${last?.url ?? "unknown"}, readyState: ${last?.readyState ?? "unknown"})`);
191
+ await new Promise((resolve) => setTimeout(resolve, Math.min(WAIT_POLL_MS, remaining())));
192
+ }
193
+ };
194
+ if (input.url) {
195
+ const pattern = input.url;
196
+ await pollPage((page) => matchesUrlPattern(page.url, pattern), `url did not match "${pattern}"`);
197
+ }
198
+ if (input.load) await pollPage((page) => page.readyState === "complete", "page did not finish loading");
199
+ if (input.selector || input.text) await agentInteract(runtime, {
200
+ action: "waitFor",
201
+ selector: input.selector ?? `text=${input.text}`,
202
+ frame: input.frame,
203
+ timeoutMs: Math.max(remaining(), 1)
204
+ });
205
+ const page = await tryPageState(runtime, sessionId);
206
+ return {
207
+ ok: true,
208
+ action: "wait",
209
+ sessionId,
210
+ elapsedMs: Date.now() - started,
211
+ url: page?.url,
212
+ title: page?.title,
213
+ readyState: page?.readyState
214
+ };
215
+ }
216
+ /** Accepts a full tab id or a unique prefix of one. */
217
+ async function resolveTabId(runtime, sessionId, idOrPrefix) {
218
+ const tabs = await runtime.api.listTabs(sessionId);
219
+ const exact = tabs.find((tab) => tab.id === idOrPrefix);
220
+ if (exact) return exact.id;
221
+ const matches = tabs.filter((tab) => tab.id.startsWith(idOrPrefix));
222
+ if (matches.length === 1) return matches[0].id;
223
+ if (matches.length > 1) throw new Error(`Ambiguous tab id prefix: ${idOrPrefix}`);
224
+ throw new Error(`Tab not found: ${idOrPrefix}`);
225
+ }
226
+ async function agentTabs(runtime) {
227
+ const sessionId = requireSessionId(runtime);
228
+ await ensureReady(runtime, sessionId);
229
+ return {
230
+ ok: true,
231
+ action: "tabs",
232
+ sessionId,
233
+ tabs: (await runtime.api.listTabs(sessionId)).map(({ id, url, title, active }) => ({
234
+ id,
235
+ url,
236
+ title,
237
+ active
238
+ }))
239
+ };
240
+ }
241
+ async function agentTabSwitch(runtime, input) {
242
+ const sessionId = requireSessionId(runtime);
243
+ await ensureReady(runtime, sessionId);
244
+ const tab = await runtime.api.switchTab(sessionId, await resolveTabId(runtime, sessionId, input.id));
245
+ return {
246
+ ok: true,
247
+ action: "tab",
248
+ sessionId,
249
+ id: tab.id,
250
+ url: tab.url,
251
+ title: tab.title
252
+ };
253
+ }
254
+ async function agentTabNew(runtime, input) {
255
+ const sessionId = requireSessionId(runtime);
256
+ await ensureReady(runtime, sessionId);
257
+ const tab = await runtime.api.newTab(sessionId);
258
+ if (!input.url) return {
259
+ ok: true,
260
+ action: "tab-new",
261
+ sessionId,
262
+ id: tab.id,
263
+ url: tab.url
264
+ };
265
+ return {
266
+ ...await agentNavigate(runtime, { url: input.url }),
267
+ action: "tab-new",
268
+ id: tab.id
269
+ };
270
+ }
271
+ async function agentTabClose(runtime, input) {
272
+ const sessionId = requireSessionId(runtime);
273
+ await ensureReady(runtime, sessionId);
274
+ const result = await runtime.api.closeTab(sessionId, await resolveTabId(runtime, sessionId, input.id));
275
+ return {
276
+ ok: true,
277
+ action: "tab-close",
278
+ sessionId,
279
+ closed: result.closed,
280
+ active: {
281
+ id: result.active.id,
282
+ url: result.active.url,
283
+ title: result.active.title
284
+ }
112
285
  };
113
286
  }
114
287
  async function agentRestart(runtime) {
@@ -156,6 +329,36 @@ async function agentContent(runtime, input) {
156
329
  })
157
330
  };
158
331
  }
332
+ async function agentScreenshot(runtime, input) {
333
+ const sessionId = requireSessionId(runtime);
334
+ await ensureReady(runtime, sessionId);
335
+ const format = input.format ?? "png";
336
+ const result = await runtime.api.screenshot(sessionId, {
337
+ format,
338
+ quality: input.quality,
339
+ fullPage: input.fullPage
340
+ });
341
+ const ext = format === "png" ? "png" : "jpg";
342
+ let filePath;
343
+ if (input.out) {
344
+ filePath = path.resolve(input.out);
345
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
346
+ } else {
347
+ const dir = path.join(runtime.context.config.dataDir, "screenshots");
348
+ ensurePrivateDir(dir);
349
+ filePath = path.join(dir, `${sessionId}-${Date.now()}.${ext}`);
350
+ }
351
+ const bytes = Buffer.from(result.data, "base64");
352
+ fs.writeFileSync(filePath, bytes, { mode: 384 });
353
+ return {
354
+ ok: true,
355
+ action: "screenshot",
356
+ sessionId,
357
+ path: filePath,
358
+ mimeType: result.mimeType,
359
+ bytes: bytes.length
360
+ };
361
+ }
159
362
  async function agentElements(runtime, input) {
160
363
  const sessionId = requireSessionId(runtime);
161
364
  await ensureReady(runtime, sessionId);
@@ -166,7 +369,10 @@ async function agentElements(runtime, input) {
166
369
  roles: input.roles,
167
370
  visibleOnly: input.visibleOnly,
168
371
  limit: input.limit,
169
- selector: input.selector
372
+ selector: input.selector,
373
+ href: input.href,
374
+ text: input.text,
375
+ frame: input.frame
170
376
  })
171
377
  };
172
378
  }
@@ -184,16 +390,14 @@ async function agentCleanup(runtime, input) {
184
390
  ...runtime.api.cleanupSessions(input)
185
391
  };
186
392
  }
187
- //#endregion
188
- //#region src/cli/output/result-formatter.ts
393
+ // src/cli/output/result-formatter.ts
189
394
  function formatResult(resultStatus, message) {
190
395
  return {
191
396
  resultStatus,
192
397
  message
193
398
  };
194
399
  }
195
- //#endregion
196
- //#region src/cli/commands/session-start.ts
400
+ // src/cli/commands/session-start.ts
197
401
  async function runSessionStart(runtime, input) {
198
402
  const session = await runtime.api.createSession(input);
199
403
  return {
@@ -201,29 +405,24 @@ async function runSessionStart(runtime, input) {
201
405
  sessionId: session.sessionId
202
406
  };
203
407
  }
204
- //#endregion
205
- //#region src/cli/commands/session-status.ts
408
+ // src/cli/commands/session-status.ts
206
409
  async function runSessionStatus(runtime, input) {
207
410
  return runtime.api.getSession(input.sessionId);
208
411
  }
209
- //#endregion
210
- //#region src/cli/commands/session-stop.ts
412
+ // src/cli/commands/session-stop.ts
211
413
  async function runSessionStop(runtime, input) {
212
414
  await runtime.api.terminateSession(input.sessionId);
213
415
  return { terminated: true };
214
416
  }
215
- //#endregion
216
- //#region src/cli/commands/session-cleanup.ts
417
+ // src/cli/commands/session-cleanup.ts
217
418
  async function runSessionCleanup(runtime, input) {
218
419
  return runtime.api.cleanupSessions(input);
219
420
  }
220
- //#endregion
221
- //#region src/cli/commands/session-restart.ts
421
+ // src/cli/commands/session-restart.ts
222
422
  async function runSessionRestart(runtime, input) {
223
423
  return await runtime.api.restartSession(input.sessionId);
224
424
  }
225
- //#endregion
226
- //#region src/cli/commands/session-auth.ts
425
+ // src/cli/commands/session-auth.ts
227
426
  async function runSessionAuth(runtime, input) {
228
427
  const token = runtime.api.rotateSessionToken(input.sessionId);
229
428
  return {
@@ -231,8 +430,7 @@ async function runSessionAuth(runtime, input) {
231
430
  valid: runtime.context.tokenService.validate(input.sessionId, token)
232
431
  };
233
432
  }
234
- //#endregion
235
- //#region src/cli/commands/command-run.ts
433
+ // src/cli/commands/command-run.ts
236
434
  async function runCommand(runtime, input) {
237
435
  if (!input.sessionId) throw new Error("No active session. Start or restart a session first.");
238
436
  try {
@@ -245,8 +443,7 @@ async function runCommand(runtime, input) {
245
443
  throw new Error(`Command rejected: ${error.message}`, { cause: error });
246
444
  }
247
445
  }
248
- //#endregion
249
- //#region src/cli/commands/page-content.ts
446
+ // src/cli/commands/page-content.ts
250
447
  async function runPageContent(runtime, input) {
251
448
  return await runtime.api.getContent(input.sessionId, {
252
449
  mode: input.mode,
@@ -254,32 +451,35 @@ async function runPageContent(runtime, input) {
254
451
  maxChars: input.maxChars
255
452
  });
256
453
  }
257
- //#endregion
258
- //#region src/cli/commands/memory-search.ts
454
+ // src/cli/commands/memory-search.ts
259
455
  async function runMemorySearch(runtime, input) {
260
456
  if (!input.taskIntent || !input.taskIntent.trim()) throw new Error("taskIntent is required");
261
457
  return runtime.api.searchMemory(input);
262
458
  }
263
- //#endregion
264
- //#region src/cli/commands/memory-inspect.ts
459
+ // src/cli/commands/memory-inspect.ts
265
460
  async function runMemoryInspect(runtime, input) {
266
461
  if (!input.insightId || !input.insightId.trim()) throw new Error("insightId is required");
267
462
  return runtime.api.inspectMemory(input.insightId);
268
463
  }
269
- //#endregion
270
- //#region src/cli/commands/memory-verify.ts
464
+ // src/cli/commands/memory-verify.ts
271
465
  async function runMemoryVerify(runtime, input) {
272
466
  if (!input.insightId || !input.insightId.trim()) throw new Error("insightId is required");
273
467
  return runtime.api.verifyMemory(input.insightId);
274
468
  }
275
- //#endregion
276
- //#region src/cli/commands/memory-stats.ts
469
+ // src/cli/commands/memory-stats.ts
277
470
  async function runMemoryStats(runtime) {
278
471
  return runtime.api.memoryStats();
279
472
  }
280
- //#endregion
281
- //#region src/cli/index.ts
473
+ // src/cli/index.ts
282
474
  const collect = (value, previous = []) => [...previous, value];
475
+ const FRAME_HELP = "iframe selector to run in (cross-origin frames supported)";
476
+ /** Resolves `<selector>` or `--text` (substring; `--exact` for exact match) to one locator. */
477
+ function resolveTarget(selector, options) {
478
+ if (selector && options.text) throw new Error("Pass either a selector or --text, not both");
479
+ if (options.text) return options.exact ? `text=${JSON.stringify(options.text)}` : `text=${options.text}`;
480
+ if (!selector) throw new Error("Missing target: pass a selector or --text <text>");
481
+ return selector;
482
+ }
283
483
  async function main() {
284
484
  const runtime = createCliRuntime();
285
485
  const program = new Command();
@@ -375,19 +575,24 @@ async function main() {
375
575
  const result = await agentNavigate(runtime, { url });
376
576
  console.log(JSON.stringify(result));
377
577
  });
378
- agent.command("click").argument("<selector>").option("--fallback <selector>", "backup CSS selector", collect, []).action(async (selector, options) => {
578
+ agent.command("click").argument("[selector]", "CSS selector or composed locator (a >>> b)").option("--text <text>", "click the element with this visible text instead of a selector").option("--exact", "with --text: require an exact text match").option("--frame <selector>", FRAME_HELP).option("--fallback <selector>", "backup CSS selector", collect, []).action(async (selector, options) => {
379
579
  const result = await agentInteract(runtime, {
380
580
  action: "click",
381
- selector,
581
+ selector: resolveTarget(selector, options),
582
+ frame: options.frame,
382
583
  fallbackSelectors: options.fallback
383
584
  });
384
585
  console.log(JSON.stringify(result));
385
586
  });
386
- agent.command("type").argument("<selector>").argument("<text>").option("--fallback <selector>", "backup CSS selector", collect, []).action(async (selector, text, options) => {
587
+ agent.command("type").argument("<selectorOrText>", "CSS selector, or the text to type when --into-text is used").argument("[text]").option("--into-text <label>", "target the field by its visible text/label/placeholder").option("--frame <selector>", FRAME_HELP).option("--fallback <selector>", "backup CSS selector", collect, []).action(async (first, second, options) => {
588
+ const selector = options.intoText ? `text=${options.intoText}` : first;
589
+ const text = options.intoText ? first : second;
590
+ if (text === void 0) throw new Error("Missing text to type");
387
591
  const result = await agentInteract(runtime, {
388
592
  action: "type",
389
593
  selector,
390
594
  text,
595
+ frame: options.frame,
391
596
  fallbackSelectors: options.fallback
392
597
  });
393
598
  console.log(JSON.stringify(result));
@@ -399,35 +604,41 @@ async function main() {
399
604
  });
400
605
  console.log(JSON.stringify(result));
401
606
  });
402
- agent.command("wait").argument("<selector>").option("--timeout <ms>", "timeout in milliseconds", "2000").action(async (selector, options) => {
403
- const result = await agentInteract(runtime, {
404
- action: "waitFor",
607
+ agent.command("wait").description("Wait for an element, visible text, a URL and/or the page load").argument("[selector]").option("--text <text>", "wait until an element with this text exists").option("--url <pattern>", "wait until the URL matches (substring, * glob, or /regex/)").option("--load", "wait until document.readyState is complete").option("--frame <selector>", FRAME_HELP).option("--timeout <ms>", "timeout in milliseconds", "10000").action(async (selector, options) => {
608
+ const result = await agentWait(runtime, {
405
609
  selector,
610
+ text: options.text,
611
+ url: options.url,
612
+ load: options.load,
613
+ frame: options.frame,
406
614
  timeoutMs: Number.parseInt(options.timeout, 10)
407
615
  });
408
616
  console.log(JSON.stringify(result));
409
617
  });
410
- agent.command("hover").argument("<selector>").option("--fallback <selector>", "backup CSS selector", collect, []).action(async (selector, options) => {
618
+ agent.command("hover").argument("[selector]").option("--text <text>", "hover the element with this visible text").option("--exact", "with --text: require an exact text match").option("--frame <selector>", FRAME_HELP).option("--fallback <selector>", "backup CSS selector", collect, []).action(async (selector, options) => {
411
619
  const result = await agentInteract(runtime, {
412
620
  action: "hover",
413
- selector,
621
+ selector: resolveTarget(selector, options),
622
+ frame: options.frame,
414
623
  fallbackSelectors: options.fallback
415
624
  });
416
625
  console.log(JSON.stringify(result));
417
626
  });
418
- agent.command("select").argument("<selector>").argument("<value>").option("--fallback <selector>", "backup CSS selector", collect, []).action(async (selector, value, options) => {
627
+ agent.command("select").argument("<selector>").argument("<value>").option("--frame <selector>", FRAME_HELP).option("--fallback <selector>", "backup CSS selector", collect, []).action(async (selector, value, options) => {
419
628
  const result = await agentInteract(runtime, {
420
629
  action: "select",
421
630
  selector,
422
631
  value,
632
+ frame: options.frame,
423
633
  fallbackSelectors: options.fallback
424
634
  });
425
635
  console.log(JSON.stringify(result));
426
636
  });
427
- agent.command("toggle").argument("<selector>").option("--fallback <selector>", "backup CSS selector", collect, []).action(async (selector, options) => {
637
+ agent.command("toggle").argument("<selector>").option("--frame <selector>", FRAME_HELP).option("--fallback <selector>", "backup CSS selector", collect, []).action(async (selector, options) => {
428
638
  const result = await agentInteract(runtime, {
429
639
  action: "toggle",
430
640
  selector,
641
+ frame: options.frame,
431
642
  fallbackSelectors: options.fallback
432
643
  });
433
644
  console.log(JSON.stringify(result));
@@ -466,6 +677,29 @@ async function main() {
466
677
  const result = await agentDismissCookies(runtime);
467
678
  console.log(JSON.stringify(result));
468
679
  });
680
+ agent.command("eval").description("Evaluate a JavaScript expression in the page and print its JSON value").argument("<expression>", "expression; promises are awaited").option("--frame <selector>", FRAME_HELP).action(async (expression, options) => {
681
+ const result = await agentEval(runtime, {
682
+ expression,
683
+ frame: options.frame
684
+ });
685
+ console.log(JSON.stringify(result));
686
+ });
687
+ agent.command("tabs").description("List open tabs").action(async () => {
688
+ const result = await agentTabs(runtime);
689
+ console.log(JSON.stringify(result));
690
+ });
691
+ agent.command("tab").description("Switch the session to another tab").argument("<id>", "tab id (or unique prefix) from agent tabs").action(async (id) => {
692
+ const result = await agentTabSwitch(runtime, { id });
693
+ console.log(JSON.stringify(result));
694
+ });
695
+ agent.command("tab-new").description("Open a new tab, switch to it and optionally navigate").argument("[url]").action(async (url) => {
696
+ const result = await agentTabNew(runtime, { url });
697
+ console.log(JSON.stringify(result));
698
+ });
699
+ agent.command("tab-close").description("Close a tab (switches to another tab if it was active)").argument("<id>", "tab id (or unique prefix) from agent tabs").action(async (id) => {
700
+ const result = await agentTabClose(runtime, { id });
701
+ console.log(JSON.stringify(result));
702
+ });
469
703
  agent.command("restart").action(async () => {
470
704
  const result = await agentRestart(runtime);
471
705
  console.log(JSON.stringify(result));
@@ -482,12 +716,25 @@ async function main() {
482
716
  });
483
717
  console.log(JSON.stringify(result));
484
718
  });
485
- agent.command("elements").description("List interactive elements on the current page").option("--roles <roles>", "comma-separated roles filter", (v) => v.split(",")).option("--visible-only", "only visible elements", true).option("--no-visible-only", "include hidden elements").option("--limit <n>", "max elements", "50").option("--selector <selector>", "scope to CSS selector subtree").action(async (options) => {
719
+ agent.command("screenshot").description("Save a screenshot of the current page to a file").option("--out <path>", "output file (default: <data dir>/screenshots/<session>-<ts>.<ext>)").option("--format <format>", "png|jpeg", "png").option("--quality <n>", "jpeg quality 0-100").option("--full-page", "capture the full scrollable page").action(async (options) => {
720
+ if (options.format !== "png" && options.format !== "jpeg") throw new Error(`Invalid --format: ${options.format} (expected png or jpeg)`);
721
+ const result = await agentScreenshot(runtime, {
722
+ out: options.out,
723
+ format: options.format,
724
+ quality: options.quality ? Number.parseInt(options.quality, 10) : void 0,
725
+ fullPage: Boolean(options.fullPage)
726
+ });
727
+ console.log(JSON.stringify(result));
728
+ });
729
+ agent.command("elements").description("List interactive elements on the current page").option("--roles <roles>", "comma-separated roles filter", (v) => v.split(",")).option("--visible-only", "only visible elements", true).option("--no-visible-only", "include hidden elements").option("--limit <n>", "max elements", "50").option("--selector <selector>", "scope to CSS selector subtree").option("--href <substring>", "only links whose href contains this").option("--text <substring>", "only elements whose text/label contains this (case-insensitive)").option("--frame <selector>", FRAME_HELP).action(async (options) => {
486
730
  const result = await agentElements(runtime, {
487
731
  roles: options.roles,
488
732
  visibleOnly: options.visibleOnly,
489
733
  limit: Number.parseInt(options.limit, 10),
490
- selector: options.selector
734
+ selector: options.selector,
735
+ href: options.href,
736
+ text: options.text,
737
+ frame: options.frame
491
738
  });
492
739
  console.log(JSON.stringify(result));
493
740
  });
@@ -513,7 +760,7 @@ async function main() {
513
760
  await startMcpServer();
514
761
  });
515
762
  program.command("setup").description("Configure agentic-browser as MCP server for your AI tool").action(async () => {
516
- const { runSetup } = await import("../setup-DUAjdbQ3.mjs");
763
+ const { runSetup } = await import("../setup-DwFaZOPf.mjs");
517
764
  await runSetup();
518
765
  });
519
766
  await program.parseAsync(process.argv);
@@ -526,5 +773,4 @@ main().then((keepAlive) => {
526
773
  console.error(JSON.stringify({ error: message }));
527
774
  process.exit(1);
528
775
  });
529
- //#endregion
530
776
  export {};