browser-control-and-automation-cli 0.1.1.dev3__py3-none-any.whl

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.
Files changed (104) hide show
  1. browser_cli/__init__.py +7 -0
  2. browser_cli/actions/__init__.py +5 -0
  3. browser_cli/actions/cli_specs.py +868 -0
  4. browser_cli/agent_scope/__init__.py +14 -0
  5. browser_cli/automation/__init__.py +11 -0
  6. browser_cli/automation/api/__init__.py +5 -0
  7. browser_cli/automation/api/server.py +321 -0
  8. browser_cli/automation/hooks.py +42 -0
  9. browser_cli/automation/loader.py +134 -0
  10. browser_cli/automation/models.py +127 -0
  11. browser_cli/automation/persistence/__init__.py +5 -0
  12. browser_cli/automation/persistence/store.py +682 -0
  13. browser_cli/automation/projections.py +279 -0
  14. browser_cli/automation/publisher.py +153 -0
  15. browser_cli/automation/scheduler/__init__.py +5 -0
  16. browser_cli/automation/scheduler/schedule.py +113 -0
  17. browser_cli/automation/service/__init__.py +8 -0
  18. browser_cli/automation/service/__main__.py +64 -0
  19. browser_cli/automation/service/client.py +232 -0
  20. browser_cli/automation/service/runtime.py +388 -0
  21. browser_cli/automation/toml.py +35 -0
  22. browser_cli/automation/web/__init__.py +5 -0
  23. browser_cli/automation/web/assets.py +377 -0
  24. browser_cli/browser/__init__.py +1 -0
  25. browser_cli/browser/models.py +44 -0
  26. browser_cli/browser/network_capture.py +392 -0
  27. browser_cli/browser/service.py +1519 -0
  28. browser_cli/browser/session.py +158 -0
  29. browser_cli/browser/snapshot.py +35 -0
  30. browser_cli/browser/stealth.py +450 -0
  31. browser_cli/cli/__init__.py +1 -0
  32. browser_cli/cli/error_hints.py +26 -0
  33. browser_cli/cli/main.py +298 -0
  34. browser_cli/commands/__init__.py +1 -0
  35. browser_cli/commands/action.py +25 -0
  36. browser_cli/commands/automation.py +304 -0
  37. browser_cli/commands/doctor.py +336 -0
  38. browser_cli/commands/install_skills.py +138 -0
  39. browser_cli/commands/paths.py +31 -0
  40. browser_cli/commands/read.py +33 -0
  41. browser_cli/commands/reload.py +51 -0
  42. browser_cli/commands/status.py +444 -0
  43. browser_cli/commands/task.py +71 -0
  44. browser_cli/constants.py +97 -0
  45. browser_cli/daemon/__init__.py +1 -0
  46. browser_cli/daemon/__main__.py +34 -0
  47. browser_cli/daemon/app.py +941 -0
  48. browser_cli/daemon/browser_service.py +998 -0
  49. browser_cli/daemon/client.py +280 -0
  50. browser_cli/daemon/models.py +63 -0
  51. browser_cli/daemon/runtime_presentation.py +167 -0
  52. browser_cli/daemon/server.py +103 -0
  53. browser_cli/daemon/state.py +15 -0
  54. browser_cli/daemon/transport.py +77 -0
  55. browser_cli/drivers/__init__.py +6 -0
  56. browser_cli/drivers/_extension/__init__.py +1 -0
  57. browser_cli/drivers/_extension/artifact_actions.py +154 -0
  58. browser_cli/drivers/_extension/dialog_actions.py +35 -0
  59. browser_cli/drivers/_extension/input_actions.py +63 -0
  60. browser_cli/drivers/_extension/locator_actions.py +100 -0
  61. browser_cli/drivers/_extension/observe_actions.py +285 -0
  62. browser_cli/drivers/_extension/page_actions.py +210 -0
  63. browser_cli/drivers/_extension/state_actions.py +134 -0
  64. browser_cli/drivers/base.py +391 -0
  65. browser_cli/drivers/extension_driver.py +33 -0
  66. browser_cli/drivers/models.py +21 -0
  67. browser_cli/drivers/playwright_driver.py +499 -0
  68. browser_cli/error_codes.py +27 -0
  69. browser_cli/errors.py +146 -0
  70. browser_cli/exit_codes.py +9 -0
  71. browser_cli/extension/__init__.py +32 -0
  72. browser_cli/extension/protocol.py +222 -0
  73. browser_cli/extension/session.py +393 -0
  74. browser_cli/network.py +147 -0
  75. browser_cli/outputs/__init__.py +1 -0
  76. browser_cli/outputs/json.py +10 -0
  77. browser_cli/outputs/render.py +7 -0
  78. browser_cli/packaged_skills/__init__.py +1 -0
  79. browser_cli/packaged_skills/browser-cli-converge/SKILL.md +62 -0
  80. browser_cli/packaged_skills/browser-cli-delivery/SKILL.md +61 -0
  81. browser_cli/packaged_skills/browser-cli-explore/SKILL.md +77 -0
  82. browser_cli/profiles/__init__.py +1 -0
  83. browser_cli/profiles/discovery.py +185 -0
  84. browser_cli/refs/__init__.py +17 -0
  85. browser_cli/refs/generator.py +386 -0
  86. browser_cli/refs/models.py +127 -0
  87. browser_cli/refs/registry.py +51 -0
  88. browser_cli/refs/resolver.py +214 -0
  89. browser_cli/tabs/__init__.py +5 -0
  90. browser_cli/tabs/registry.py +283 -0
  91. browser_cli/task_runtime/__init__.py +31 -0
  92. browser_cli/task_runtime/client.py +107 -0
  93. browser_cli/task_runtime/entrypoint.py +110 -0
  94. browser_cli/task_runtime/errors.py +17 -0
  95. browser_cli/task_runtime/flow.py +171 -0
  96. browser_cli/task_runtime/models.py +97 -0
  97. browser_cli/task_runtime/read.py +71 -0
  98. browser_cli/task_runtime/templates.py +58 -0
  99. browser_control_and_automation_cli-0.1.1.dev3.dist-info/METADATA +340 -0
  100. browser_control_and_automation_cli-0.1.1.dev3.dist-info/RECORD +104 -0
  101. browser_control_and_automation_cli-0.1.1.dev3.dist-info/WHEEL +5 -0
  102. browser_control_and_automation_cli-0.1.1.dev3.dist-info/entry_points.txt +2 -0
  103. browser_control_and_automation_cli-0.1.1.dev3.dist-info/licenses/LICENSE +21 -0
  104. browser_control_and_automation_cli-0.1.1.dev3.dist-info/top_level.txt +1 -0
@@ -0,0 +1,499 @@
1
+ """Playwright-backed driver adapter."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from browser_cli.browser.service import BrowserService as PlaywrightBrowserService
8
+ from browser_cli.profiles.discovery import ChromeEnvironment
9
+ from browser_cli.refs.models import LocatorSpec, SnapshotInput
10
+
11
+ from .base import BrowserDriver
12
+ from .models import DriverHealth, TabState
13
+
14
+
15
+ class PlaywrightDriver(BrowserDriver):
16
+ name = "playwright"
17
+
18
+ def __init__(
19
+ self,
20
+ chrome_environment: ChromeEnvironment | None = None,
21
+ *,
22
+ headless: bool | None = None,
23
+ ) -> None:
24
+ self._service = PlaywrightBrowserService(
25
+ chrome_environment=chrome_environment, headless=headless
26
+ )
27
+
28
+ @property
29
+ def chrome_environment(self) -> ChromeEnvironment | None:
30
+ return self._service.chrome_environment
31
+
32
+ def configure_environment(self, chrome_environment: ChromeEnvironment) -> None:
33
+ self._service.configure_environment(chrome_environment)
34
+
35
+ @staticmethod
36
+ def build_search_url(query: str, engine: str = "duckduckgo") -> str:
37
+ return PlaywrightBrowserService._build_search_url(query, engine)
38
+
39
+ async def ensure_started(self) -> None:
40
+ await self._service.ensure_started()
41
+
42
+ async def stop(self) -> dict[str, Any]:
43
+ return await self._service.stop()
44
+
45
+ async def health(self) -> DriverHealth:
46
+ environment = self.chrome_environment
47
+ return DriverHealth(
48
+ name=self.name,
49
+ available=True,
50
+ details={
51
+ "chrome_environment": (environment.source if environment is not None else None),
52
+ "user_data_dir": str(environment.user_data_dir)
53
+ if environment is not None
54
+ else None,
55
+ "profile_directory": environment.profile_directory
56
+ if environment is not None
57
+ else None,
58
+ },
59
+ )
60
+
61
+ async def new_tab(
62
+ self,
63
+ *,
64
+ page_id: str,
65
+ url: str | None = None,
66
+ wait_until: str = "load",
67
+ timeout_seconds: float | None = None,
68
+ ) -> dict[str, Any]:
69
+ return await self._service.new_tab(
70
+ page_id=page_id,
71
+ url=url,
72
+ wait_until=wait_until,
73
+ timeout_seconds=timeout_seconds,
74
+ )
75
+
76
+ async def close_tab(self, page_id: str) -> dict[str, Any]:
77
+ return await self._service.close_tab(page_id)
78
+
79
+ async def list_tabs(self) -> list[TabState]:
80
+ results: list[TabState] = []
81
+ for page_id in sorted(self._service._pages.keys()): # noqa: SLF001
82
+ summary = await self._service.get_page_summary(page_id)
83
+ results.append(
84
+ TabState(
85
+ page_id=page_id,
86
+ url=str(summary["url"]),
87
+ title=str(summary["title"]),
88
+ active=False,
89
+ )
90
+ )
91
+ return results
92
+
93
+ async def switch_tab(self, page_id: str) -> dict[str, Any]:
94
+ page = self._service._require_page(page_id) # noqa: SLF001
95
+ await page.bring_to_front()
96
+ return await self._service.get_page_summary(page_id)
97
+
98
+ async def get_page_summary(self, page_id: str) -> dict[str, Any]:
99
+ return await self._service.get_page_summary(page_id)
100
+
101
+ async def get_page_info(self, page_id: str) -> dict[str, Any]:
102
+ return await self._service.get_page_info(page_id)
103
+
104
+ async def capture_html(self, page_id: str) -> dict[str, Any]:
105
+ return await self._service.capture_html(page_id)
106
+
107
+ async def capture_snapshot_input(
108
+ self,
109
+ page_id: str,
110
+ *,
111
+ interactive: bool = False,
112
+ full_page: bool = True,
113
+ ) -> SnapshotInput:
114
+ return await self._service.capture_snapshot_input(
115
+ page_id,
116
+ interactive=interactive,
117
+ full_page=full_page,
118
+ )
119
+
120
+ async def navigate(
121
+ self,
122
+ page_id: str,
123
+ url: str,
124
+ *,
125
+ wait_until: str = "load",
126
+ timeout_seconds: float = 30.0,
127
+ ) -> dict[str, Any]:
128
+ return await self._service.navigate(
129
+ page_id,
130
+ url,
131
+ wait_until=wait_until,
132
+ timeout_seconds=timeout_seconds,
133
+ )
134
+
135
+ async def reload(
136
+ self,
137
+ page_id: str,
138
+ *,
139
+ wait_until: str = "load",
140
+ timeout_seconds: float = 30.0,
141
+ ) -> dict[str, Any]:
142
+ return await self._service.reload(
143
+ page_id, wait_until=wait_until, timeout_seconds=timeout_seconds
144
+ )
145
+
146
+ async def go_back(self, page_id: str) -> dict[str, Any]:
147
+ return await self._service.go_back(page_id)
148
+
149
+ async def go_forward(self, page_id: str) -> dict[str, Any]:
150
+ return await self._service.go_forward(page_id)
151
+
152
+ async def resize(self, page_id: str, *, width: int, height: int) -> dict[str, Any]:
153
+ return await self._service.resize(page_id, width=width, height=height)
154
+
155
+ async def click(self, page_id: str, locator: LocatorSpec) -> dict[str, Any]:
156
+ return await self._service.click_locator(page_id, locator)
157
+
158
+ async def double_click(self, page_id: str, locator: LocatorSpec) -> dict[str, Any]:
159
+ return await self._service.double_click_locator(page_id, locator)
160
+
161
+ async def hover(self, page_id: str, locator: LocatorSpec) -> dict[str, Any]:
162
+ return await self._service.hover_locator(page_id, locator)
163
+
164
+ async def focus(self, page_id: str, locator: LocatorSpec) -> dict[str, Any]:
165
+ return await self._service.focus_locator(page_id, locator)
166
+
167
+ async def fill(
168
+ self,
169
+ page_id: str,
170
+ locator: LocatorSpec,
171
+ text: str,
172
+ *,
173
+ submit: bool = False,
174
+ ) -> dict[str, Any]:
175
+ return await self._service.fill_locator(page_id, locator, text, submit=submit)
176
+
177
+ async def select_option(self, page_id: str, locator: LocatorSpec, text: str) -> dict[str, Any]:
178
+ return await self._service.select_locator(page_id, locator, text)
179
+
180
+ async def list_options(self, page_id: str, locator: LocatorSpec) -> dict[str, Any]:
181
+ return await self._service.list_locator_options(page_id, locator)
182
+
183
+ async def check(self, page_id: str, locator: LocatorSpec, *, checked: bool) -> dict[str, Any]:
184
+ return await self._service.set_checked_locator(page_id, locator, checked=checked)
185
+
186
+ async def scroll_to(self, page_id: str, locator: LocatorSpec) -> dict[str, Any]:
187
+ return await self._service.scroll_to_locator(page_id, locator)
188
+
189
+ async def drag(
190
+ self, page_id: str, start_locator: LocatorSpec, end_locator: LocatorSpec
191
+ ) -> dict[str, Any]:
192
+ return await self._service.drag_between_locators(page_id, start_locator, end_locator)
193
+
194
+ async def upload(self, page_id: str, locator: LocatorSpec, file_path: str) -> dict[str, Any]:
195
+ return await self._service.upload_to_locator(page_id, locator, file_path)
196
+
197
+ async def evaluate(self, page_id: str, code: str) -> dict[str, Any]:
198
+ return await self._service.evaluate(page_id, code)
199
+
200
+ async def evaluate_on(self, page_id: str, locator: LocatorSpec, code: str) -> dict[str, Any]:
201
+ return await self._service.evaluate_on_locator(page_id, locator, code)
202
+
203
+ async def wait(
204
+ self,
205
+ page_id: str,
206
+ *,
207
+ seconds: float | None = None,
208
+ text: str | None = None,
209
+ gone: bool = False,
210
+ exact: bool = False,
211
+ ) -> dict[str, Any]:
212
+ return await self._service.wait(
213
+ page_id,
214
+ seconds=seconds,
215
+ text=text,
216
+ gone=gone,
217
+ exact=exact,
218
+ )
219
+
220
+ async def wait_for_network_idle(
221
+ self, page_id: str, *, timeout_seconds: float = 30.0
222
+ ) -> dict[str, Any]:
223
+ return await self._service.wait_for_network_idle(page_id, timeout_seconds=timeout_seconds)
224
+
225
+ async def start_console_capture(self, page_id: str) -> dict[str, Any]:
226
+ return await self._service.start_console_capture(page_id)
227
+
228
+ async def get_console_messages(
229
+ self,
230
+ page_id: str,
231
+ *,
232
+ message_type: str | None = None,
233
+ clear: bool = True,
234
+ ) -> dict[str, Any]:
235
+ return await self._service.get_console_messages(
236
+ page_id, message_type=message_type, clear=clear
237
+ )
238
+
239
+ async def stop_console_capture(self, page_id: str) -> dict[str, Any]:
240
+ return await self._service.stop_console_capture(page_id)
241
+
242
+ async def start_network_capture(self, page_id: str) -> dict[str, Any]:
243
+ return await self._service.start_network_capture(page_id)
244
+
245
+ async def wait_for_network_record(
246
+ self,
247
+ page_id: str,
248
+ *,
249
+ url_contains: str | None = None,
250
+ url_regex: str | None = None,
251
+ method: str | None = None,
252
+ status: int | None = None,
253
+ resource_type: str | None = None,
254
+ mime_contains: str | None = None,
255
+ include_static: bool = False,
256
+ timeout_seconds: float = 30.0,
257
+ ) -> dict[str, Any]:
258
+ return await self._service.wait_for_network_record(
259
+ page_id,
260
+ url_contains=url_contains,
261
+ url_regex=url_regex,
262
+ method=method,
263
+ status=status,
264
+ resource_type=resource_type,
265
+ mime_contains=mime_contains,
266
+ include_static=include_static,
267
+ timeout_seconds=timeout_seconds,
268
+ )
269
+
270
+ async def get_network_records(
271
+ self,
272
+ page_id: str,
273
+ *,
274
+ url_contains: str | None = None,
275
+ url_regex: str | None = None,
276
+ method: str | None = None,
277
+ status: int | None = None,
278
+ resource_type: str | None = None,
279
+ mime_contains: str | None = None,
280
+ include_static: bool = False,
281
+ clear: bool = True,
282
+ ) -> dict[str, Any]:
283
+ return await self._service.get_network_records(
284
+ page_id,
285
+ url_contains=url_contains,
286
+ url_regex=url_regex,
287
+ method=method,
288
+ status=status,
289
+ resource_type=resource_type,
290
+ mime_contains=mime_contains,
291
+ include_static=include_static,
292
+ clear=clear,
293
+ )
294
+
295
+ async def stop_network_capture(self, page_id: str) -> dict[str, Any]:
296
+ return await self._service.stop_network_capture(page_id)
297
+
298
+ async def get_cookies(
299
+ self,
300
+ page_id: str,
301
+ *,
302
+ name: str | None = None,
303
+ domain: str | None = None,
304
+ path: str | None = None,
305
+ ) -> dict[str, Any]:
306
+ return await self._service.get_cookies(page_id, name=name, domain=domain, path=path)
307
+
308
+ async def set_cookie(
309
+ self,
310
+ page_id: str,
311
+ *,
312
+ name: str,
313
+ value: str,
314
+ domain: str | None = None,
315
+ path: str = "/",
316
+ expires: float | None = None,
317
+ http_only: bool = False,
318
+ secure: bool = False,
319
+ same_site: str | None = None,
320
+ ) -> dict[str, Any]:
321
+ return await self._service.set_cookie(
322
+ page_id,
323
+ name=name,
324
+ value=value,
325
+ domain=domain,
326
+ path=path,
327
+ expires=expires,
328
+ http_only=http_only,
329
+ secure=secure,
330
+ same_site=same_site,
331
+ )
332
+
333
+ async def clear_cookies(
334
+ self,
335
+ page_id: str,
336
+ *,
337
+ name: str | None = None,
338
+ domain: str | None = None,
339
+ path: str | None = None,
340
+ ) -> dict[str, Any]:
341
+ return await self._service.clear_cookies(page_id, name=name, domain=domain, path=path)
342
+
343
+ async def save_storage_state(self, page_id: str, *, path: str | None = None) -> dict[str, Any]:
344
+ return await self._service.save_storage_state(page_id, path=path)
345
+
346
+ async def load_storage_state(self, page_id: str, *, path: str) -> dict[str, Any]:
347
+ return await self._service.load_storage_state(page_id, path=path)
348
+
349
+ async def verify_text(
350
+ self,
351
+ page_id: str,
352
+ *,
353
+ text: str,
354
+ exact: bool = False,
355
+ timeout_seconds: float = 5.0,
356
+ ) -> dict[str, Any]:
357
+ return await self._service.verify_text(
358
+ page_id,
359
+ text=text,
360
+ exact=exact,
361
+ timeout_seconds=timeout_seconds,
362
+ )
363
+
364
+ async def verify_url(
365
+ self, page_id: str, *, expected: str, exact: bool = False
366
+ ) -> dict[str, Any]:
367
+ return await self._service.verify_url(page_id, expected=expected, exact=exact)
368
+
369
+ async def verify_title(
370
+ self, page_id: str, *, expected: str, exact: bool = False
371
+ ) -> dict[str, Any]:
372
+ return await self._service.verify_title(page_id, expected=expected, exact=exact)
373
+
374
+ async def verify_visible(
375
+ self,
376
+ page_id: str,
377
+ *,
378
+ role: str,
379
+ name: str,
380
+ timeout_seconds: float = 5.0,
381
+ ) -> dict[str, Any]:
382
+ return await self._service.verify_visible(
383
+ page_id,
384
+ role=role,
385
+ name=name,
386
+ timeout_seconds=timeout_seconds,
387
+ )
388
+
389
+ async def verify_state(
390
+ self, page_id: str, *, locator: LocatorSpec, state: str
391
+ ) -> dict[str, Any]:
392
+ return await self._service.verify_state_locator(page_id, locator, state=state)
393
+
394
+ async def verify_value(
395
+ self, page_id: str, *, locator: LocatorSpec, expected: str
396
+ ) -> dict[str, Any]:
397
+ return await self._service.verify_value_locator(page_id, locator, expected=expected)
398
+
399
+ async def type_text(self, page_id: str, text: str, *, submit: bool = False) -> dict[str, Any]:
400
+ return await self._service.type_text(page_id, text, submit=submit)
401
+
402
+ async def press_key(self, page_id: str, key: str) -> dict[str, Any]:
403
+ return await self._service.press_key(page_id, key)
404
+
405
+ async def key_down(self, page_id: str, key: str) -> dict[str, Any]:
406
+ return await self._service.key_down(page_id, key)
407
+
408
+ async def key_up(self, page_id: str, key: str) -> dict[str, Any]:
409
+ return await self._service.key_up(page_id, key)
410
+
411
+ async def wheel(self, page_id: str, *, dx: int = 0, dy: int = 700) -> dict[str, Any]:
412
+ return await self._service.wheel(page_id, dx=dx, dy=dy)
413
+
414
+ async def mouse_move(self, page_id: str, *, x: int, y: int) -> dict[str, Any]:
415
+ return await self._service.mouse_move(page_id, x=x, y=y)
416
+
417
+ async def mouse_click(
418
+ self,
419
+ page_id: str,
420
+ *,
421
+ x: int,
422
+ y: int,
423
+ button: str = "left",
424
+ count: int = 1,
425
+ ) -> dict[str, Any]:
426
+ return await self._service.mouse_click(page_id, x=x, y=y, button=button, count=count)
427
+
428
+ async def mouse_drag(
429
+ self, page_id: str, *, x1: int, y1: int, x2: int, y2: int
430
+ ) -> dict[str, Any]:
431
+ return await self._service.mouse_drag(page_id, x1=x1, y1=y1, x2=x2, y2=y2)
432
+
433
+ async def mouse_down(self, page_id: str, *, button: str = "left") -> dict[str, Any]:
434
+ return await self._service.mouse_down(page_id, button=button)
435
+
436
+ async def mouse_up(self, page_id: str, *, button: str = "left") -> dict[str, Any]:
437
+ return await self._service.mouse_up(page_id, button=button)
438
+
439
+ async def screenshot(
440
+ self, page_id: str, *, path: str, full_page: bool = False
441
+ ) -> dict[str, Any]:
442
+ return await self._service.screenshot(page_id, path=path, full_page=full_page)
443
+
444
+ async def save_pdf(self, page_id: str, *, path: str) -> dict[str, Any]:
445
+ return await self._service.save_pdf(page_id, path=path)
446
+
447
+ async def setup_dialog_handler(
448
+ self,
449
+ page_id: str,
450
+ *,
451
+ default_action: str = "accept",
452
+ default_prompt_text: str | None = None,
453
+ ) -> dict[str, Any]:
454
+ return await self._service.setup_dialog_handler(
455
+ page_id,
456
+ default_action=default_action,
457
+ default_prompt_text=default_prompt_text,
458
+ )
459
+
460
+ async def handle_dialog(
461
+ self,
462
+ page_id: str,
463
+ *,
464
+ accept: bool,
465
+ prompt_text: str | None = None,
466
+ ) -> dict[str, Any]:
467
+ return await self._service.handle_dialog(page_id, accept=accept, prompt_text=prompt_text)
468
+
469
+ async def remove_dialog_handler(self, page_id: str) -> dict[str, Any]:
470
+ return await self._service.remove_dialog_handler(page_id)
471
+
472
+ async def start_tracing(
473
+ self,
474
+ page_id: str,
475
+ *,
476
+ screenshots: bool = True,
477
+ snapshots: bool = True,
478
+ sources: bool = False,
479
+ ) -> dict[str, Any]:
480
+ return await self._service.start_tracing(
481
+ page_id,
482
+ screenshots=screenshots,
483
+ snapshots=snapshots,
484
+ sources=sources,
485
+ )
486
+
487
+ async def add_trace_chunk(self, page_id: str, *, title: str | None = None) -> dict[str, Any]:
488
+ return await self._service.add_trace_chunk(page_id, title=title)
489
+
490
+ async def stop_tracing(self, page_id: str, *, path: str | None = None) -> dict[str, Any]:
491
+ return await self._service.stop_tracing(page_id, path=path)
492
+
493
+ async def start_video(
494
+ self, page_id: str, *, width: int | None = None, height: int | None = None
495
+ ) -> dict[str, Any]:
496
+ return await self._service.start_video(page_id, width=width, height=height)
497
+
498
+ async def stop_video(self, page_id: str, *, path: str | None = None) -> dict[str, Any]:
499
+ return await self._service.stop_video(page_id, path=path)
@@ -0,0 +1,27 @@
1
+ """Stable machine-readable error codes."""
2
+
3
+ DAEMON_NOT_AVAILABLE = "DAEMON_NOT_AVAILABLE"
4
+ NO_ACTIVE_TAB = "NO_ACTIVE_TAB"
5
+ NO_VISIBLE_TABS = "NO_VISIBLE_TABS"
6
+ AGENT_ACTIVE_TAB_BUSY = "AGENT_ACTIVE_TAB_BUSY"
7
+ TAB_NOT_FOUND = "TAB_NOT_FOUND"
8
+ REF_NOT_FOUND = "REF_NOT_FOUND"
9
+ STALE_SNAPSHOT = "STALE_SNAPSHOT"
10
+ NO_SNAPSHOT_CONTEXT = "NO_SNAPSHOT_CONTEXT"
11
+ AMBIGUOUS_REF = "AMBIGUOUS_REF"
12
+ INVALID_INPUT = "INVALID_INPUT"
13
+ OPERATION_FAILED = "OPERATION_FAILED"
14
+ INTERNAL_ERROR = "INTERNAL_ERROR"
15
+ BROWSER_UNAVAILABLE = "BROWSER_UNAVAILABLE"
16
+ PROFILE_UNAVAILABLE = "PROFILE_UNAVAILABLE"
17
+ TEMPORARY_FAILURE = "TEMPORARY_FAILURE"
18
+ EMPTY_CONTENT = "EMPTY_CONTENT"
19
+ DRIVER_UNSUPPORTED = "DRIVER_UNSUPPORTED"
20
+ EXTENSION_UNAVAILABLE = "EXTENSION_UNAVAILABLE"
21
+ EXTENSION_CAPABILITY_INCOMPLETE = "EXTENSION_CAPABILITY_INCOMPLETE"
22
+ EXTENSION_REQUEST_FAILED = "EXTENSION_REQUEST_FAILED"
23
+ EXTENSION_DISCONNECTED = "EXTENSION_DISCONNECTED"
24
+ AUTOMATION_SERVICE_NOT_AVAILABLE = "AUTOMATION_SERVICE_NOT_AVAILABLE"
25
+ AUTOMATION_INVALID = "AUTOMATION_INVALID"
26
+ AUTOMATION_RUN_FAILED = "AUTOMATION_RUN_FAILED"
27
+ AUTOMATION_RUN_TIMEOUT = "AUTOMATION_RUN_TIMEOUT"
browser_cli/errors.py ADDED
@@ -0,0 +1,146 @@
1
+ """Project-level error types."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+
7
+ from . import error_codes, exit_codes
8
+
9
+
10
+ @dataclass(slots=True)
11
+ class BrowserCliError(Exception):
12
+ """Base typed error surfaced to the CLI."""
13
+
14
+ message: str
15
+ exit_code: int = exit_codes.INTERNAL_ERROR
16
+ error_code: str = error_codes.INTERNAL_ERROR
17
+
18
+ def __str__(self) -> str:
19
+ return self.message
20
+
21
+
22
+ class BrowserUnavailableError(BrowserCliError):
23
+ def __init__(self, message: str) -> None:
24
+ super().__init__(message, exit_codes.BROWSER_UNAVAILABLE, error_codes.BROWSER_UNAVAILABLE)
25
+
26
+
27
+ class ProfileUnavailableError(BrowserCliError):
28
+ def __init__(self, message: str) -> None:
29
+ super().__init__(message, exit_codes.PROFILE_UNAVAILABLE, error_codes.PROFILE_UNAVAILABLE)
30
+
31
+
32
+ class TemporaryReadError(BrowserCliError):
33
+ def __init__(self, message: str) -> None:
34
+ super().__init__(message, exit_codes.TEMPORARY_FAILURE, error_codes.TEMPORARY_FAILURE)
35
+
36
+
37
+ class EmptyContentError(BrowserCliError):
38
+ def __init__(self, message: str = "Read completed but produced no content.") -> None:
39
+ super().__init__(message, exit_codes.EMPTY_CONTENT, error_codes.EMPTY_CONTENT)
40
+
41
+
42
+ class InvalidInputError(BrowserCliError):
43
+ def __init__(self, message: str, *, error_code: str = error_codes.INVALID_INPUT) -> None:
44
+ super().__init__(message, exit_codes.USAGE_ERROR, error_code)
45
+
46
+
47
+ class DaemonNotAvailableError(BrowserCliError):
48
+ def __init__(self, message: str = "Browser daemon is not available.") -> None:
49
+ super().__init__(message, exit_codes.TEMPORARY_FAILURE, error_codes.DAEMON_NOT_AVAILABLE)
50
+
51
+
52
+ class NoActiveTabError(BrowserCliError):
53
+ def __init__(self, message: str = "No active tab is available for this agent.") -> None:
54
+ super().__init__(message, exit_codes.TEMPORARY_FAILURE, error_codes.NO_ACTIVE_TAB)
55
+
56
+
57
+ class NoVisibleTabsError(BrowserCliError):
58
+ def __init__(self, message: str = "No visible tabs are available for this agent.") -> None:
59
+ super().__init__(message, exit_codes.TEMPORARY_FAILURE, error_codes.NO_VISIBLE_TABS)
60
+
61
+
62
+ class BusyTabError(BrowserCliError):
63
+ def __init__(self, message: str | None = None) -> None:
64
+ super().__init__(
65
+ message
66
+ or "The current active tab is already being operated on. Create a new tab or retry after the other command finishes.",
67
+ exit_codes.TEMPORARY_FAILURE,
68
+ error_codes.AGENT_ACTIVE_TAB_BUSY,
69
+ )
70
+
71
+
72
+ class TabNotFoundError(BrowserCliError):
73
+ def __init__(
74
+ self, message: str = "The requested tab is not visible or does not exist."
75
+ ) -> None:
76
+ super().__init__(message, exit_codes.TEMPORARY_FAILURE, error_codes.TAB_NOT_FOUND)
77
+
78
+
79
+ class RefNotFoundError(BrowserCliError):
80
+ def __init__(
81
+ self, message: str = "The requested ref is not known in the latest snapshot."
82
+ ) -> None:
83
+ super().__init__(message, exit_codes.TEMPORARY_FAILURE, error_codes.REF_NOT_FOUND)
84
+
85
+
86
+ class NoSnapshotContextError(BrowserCliError):
87
+ def __init__(
88
+ self,
89
+ message: str = "No snapshot context is available for the active tab. Capture a snapshot first.",
90
+ ) -> None:
91
+ super().__init__(message, exit_codes.TEMPORARY_FAILURE, error_codes.NO_SNAPSHOT_CONTEXT)
92
+
93
+
94
+ class StaleSnapshotError(BrowserCliError):
95
+ def __init__(
96
+ self, message: str = "The requested ref is stale. Capture a new snapshot and retry."
97
+ ) -> None:
98
+ super().__init__(message, exit_codes.TEMPORARY_FAILURE, error_codes.STALE_SNAPSHOT)
99
+
100
+
101
+ class AmbiguousRefError(BrowserCliError):
102
+ def __init__(
103
+ self,
104
+ message: str = "The requested ref resolved to multiple elements. Capture a new snapshot and retry.",
105
+ ) -> None:
106
+ super().__init__(message, exit_codes.TEMPORARY_FAILURE, error_codes.AMBIGUOUS_REF)
107
+
108
+
109
+ class OperationFailedError(BrowserCliError):
110
+ def __init__(self, message: str, *, error_code: str = error_codes.OPERATION_FAILED) -> None:
111
+ super().__init__(message, exit_codes.TEMPORARY_FAILURE, error_code)
112
+
113
+
114
+ class AutomationServiceNotAvailableError(BrowserCliError):
115
+ def __init__(self, message: str = "Automation service is not available.") -> None:
116
+ super().__init__(
117
+ message,
118
+ exit_codes.TEMPORARY_FAILURE,
119
+ error_codes.AUTOMATION_SERVICE_NOT_AVAILABLE,
120
+ )
121
+
122
+
123
+ class AutomationServiceError(BrowserCliError):
124
+ def __init__(self, payload: dict[str, object]) -> None:
125
+ error_code = str(payload.get("error_code") or error_codes.INTERNAL_ERROR)
126
+ exit_code = (
127
+ exit_codes.USAGE_ERROR
128
+ if error_code in {error_codes.INVALID_INPUT, error_codes.AUTOMATION_INVALID}
129
+ else exit_codes.TEMPORARY_FAILURE
130
+ )
131
+ super().__init__(
132
+ str(payload.get("error_message") or "Automation service request failed."),
133
+ exit_code,
134
+ error_code,
135
+ )
136
+ self.payload = payload
137
+
138
+
139
+ class AutomationInvalidError(BrowserCliError):
140
+ def __init__(self, message: str) -> None:
141
+ super().__init__(message, exit_codes.USAGE_ERROR, error_codes.AUTOMATION_INVALID)
142
+
143
+
144
+ class AutomationRunTimeoutError(OperationFailedError):
145
+ def __init__(self, message: str = "Automation run timed out.") -> None:
146
+ super().__init__(message, error_code=error_codes.AUTOMATION_RUN_TIMEOUT)
@@ -0,0 +1,9 @@
1
+ """Stable CLI exit codes."""
2
+
3
+ SUCCESS = 0
4
+ INTERNAL_ERROR = 1
5
+ USAGE_ERROR = 2
6
+ EMPTY_CONTENT = 66
7
+ BROWSER_UNAVAILABLE = 69
8
+ PROFILE_UNAVAILABLE = 73
9
+ TEMPORARY_FAILURE = 75