browser-tools 0.2.0__tar.gz → 0.3.0__tar.gz

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 (183) hide show
  1. {browser_tools-0.2.0 → browser_tools-0.3.0}/PKG-INFO +1 -1
  2. {browser_tools-0.2.0 → browser_tools-0.3.0}/pyproject.toml +1 -1
  3. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/cli.py +52 -9
  4. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/launcher.py +45 -8
  5. browser_tools-0.3.0/src/browser_tools/core/supervisor.py +858 -0
  6. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/curated.py +26 -63
  7. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/events.py +16 -68
  8. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/lifecycle.py +37 -1
  9. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/list_verbs.py +20 -71
  10. browser_tools-0.3.0/src/browser_tools/one_shot.py +174 -0
  11. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/passthrough.py +96 -62
  12. browser_tools-0.3.0/src/browser_tools/user_settings.py +70 -0
  13. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/test_cli_lifecycle.py +45 -3
  14. browser_tools-0.3.0/tests/test_core_launcher.py +255 -0
  15. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/test_curated_verbs.py +16 -9
  16. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/test_events.py +20 -4
  17. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/test_list_verbs.py +20 -4
  18. browser_tools-0.3.0/tests/test_one_shot.py +375 -0
  19. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/test_passthrough.py +122 -13
  20. browser_tools-0.3.0/tests/test_supervisor_document_liveness.py +356 -0
  21. browser_tools-0.3.0/tests/test_supervisor_never_pauses.py +299 -0
  22. browser_tools-0.3.0/tests/test_window_marking.py +348 -0
  23. browser_tools-0.2.0/src/browser_tools/core/supervisor.py +0 -293
  24. browser_tools-0.2.0/tests/test_core_launcher.py +0 -128
  25. browser_tools-0.2.0/tests/test_window_marking.py +0 -140
  26. {browser_tools-0.2.0 → browser_tools-0.3.0}/.gitignore +0 -0
  27. {browser_tools-0.2.0 → browser_tools-0.3.0}/LICENSE +0 -0
  28. {browser_tools-0.2.0 → browser_tools-0.3.0}/NOTICE +0 -0
  29. {browser_tools-0.2.0 → browser_tools-0.3.0}/README.md +0 -0
  30. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/__init__.py +0 -0
  31. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/attach_chrome.sh +0 -0
  32. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/automation_backend.py +0 -0
  33. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/browser_session.py +0 -0
  34. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/browser_state.py +0 -0
  35. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/camoufox_runner.py +0 -0
  36. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/camoufox_session.py +0 -0
  37. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/cdp_client.py +0 -0
  38. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/cdp_constants.py +0 -0
  39. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/cdp_handler.py +0 -0
  40. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/chrome_config.py +0 -0
  41. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/chrome_utils.py +0 -0
  42. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/__init__.py +0 -0
  43. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/attach.py +0 -0
  44. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/cdp_client.py +0 -0
  45. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/connection.py +0 -0
  46. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/__init__.py +0 -0
  47. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/accessibility.py +0 -0
  48. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/animation.py +0 -0
  49. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/audits.py +0 -0
  50. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/autofill.py +0 -0
  51. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/background_service.py +0 -0
  52. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/bluetooth_emulation.py +0 -0
  53. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/browser.py +0 -0
  54. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/cache_storage.py +0 -0
  55. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/cast.py +0 -0
  56. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/console.py +0 -0
  57. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/css.py +0 -0
  58. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/debugger.py +0 -0
  59. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/device_access.py +0 -0
  60. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/device_orientation.py +0 -0
  61. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/dom.py +0 -0
  62. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/dom_debugger.py +0 -0
  63. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/dom_snapshot.py +0 -0
  64. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/dom_storage.py +0 -0
  65. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/emulation.py +0 -0
  66. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/event_breakpoints.py +0 -0
  67. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/extensions.py +0 -0
  68. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/fed_cm.py +0 -0
  69. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/fetch.py +0 -0
  70. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/file_system.py +0 -0
  71. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/headless_experimental.py +0 -0
  72. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/heap_profiler.py +0 -0
  73. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/indexed_db.py +0 -0
  74. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/input.py +0 -0
  75. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/inspector.py +0 -0
  76. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/io.py +0 -0
  77. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/layer_tree.py +0 -0
  78. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/log.py +0 -0
  79. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/media.py +0 -0
  80. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/memory.py +0 -0
  81. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/network.py +0 -0
  82. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/overlay.py +0 -0
  83. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/page.py +0 -0
  84. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/performance.py +0 -0
  85. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/performance_timeline.py +0 -0
  86. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/preload.py +0 -0
  87. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/profiler.py +0 -0
  88. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/pwa.py +0 -0
  89. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/runtime.py +0 -0
  90. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/schema.py +0 -0
  91. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/security.py +0 -0
  92. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/service_worker.py +0 -0
  93. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/smart_card_emulation.py +0 -0
  94. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/storage.py +0 -0
  95. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/system_info.py +0 -0
  96. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/target.py +0 -0
  97. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/tethering.py +0 -0
  98. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/tracing.py +0 -0
  99. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/web_audio.py +0 -0
  100. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/domains/web_authn.py +0 -0
  101. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/errors.py +0 -0
  102. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/fingerprint.py +0 -0
  103. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/instance_status.py +0 -0
  104. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/protocol.py +0 -0
  105. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/registry.py +0 -0
  106. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/session.py +0 -0
  107. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/core/utils.py +0 -0
  108. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/daemon_client.py +0 -0
  109. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/daemon_supervisor.py +0 -0
  110. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/detect_interstitial.js +0 -0
  111. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/extras.py +0 -0
  112. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/frame_manager.py +0 -0
  113. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/interstitial.py +0 -0
  114. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/live_chrome.py +0 -0
  115. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/mcp_broker.py +0 -0
  116. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/mcp_daemon.py +0 -0
  117. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/mcp_response.py +0 -0
  118. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/mcp_session.py +0 -0
  119. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/native_interaction.py +0 -0
  120. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/native_snapshot.py +0 -0
  121. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/page_selection.py +0 -0
  122. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/persistent-session-template.mjs +0 -0
  123. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/persistent_browser.py +0 -0
  124. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/process_utils.py +0 -0
  125. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/profile_catalog.py +0 -0
  126. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/profiler.py +0 -0
  127. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/project_identity.py +0 -0
  128. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/screencast.py +0 -0
  129. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/screenshot_utils.py +0 -0
  130. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/session_layout.py +0 -0
  131. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/session_reaper.py +0 -0
  132. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/session_store.py +0 -0
  133. {browser_tools-0.2.0 → browser_tools-0.3.0}/src/browser_tools/tool_registry.py +0 -0
  134. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/conftest.py +0 -0
  135. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/parity/CORPUS.md +0 -0
  136. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/parity/conftest.py +0 -0
  137. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/parity/fixtures/dynamic.html +0 -0
  138. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/parity/fixtures/form.html +0 -0
  139. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/parity/fixtures/iframe.html +0 -0
  140. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/parity/fixtures/iframe_child.html +0 -0
  141. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/parity/fixtures/plain.html +0 -0
  142. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/parity/fixtures/shadow.html +0 -0
  143. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/parity/live_chromium.py +0 -0
  144. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/parity/parity_comparison.py +0 -0
  145. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/parity/parity_corpus.py +0 -0
  146. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/parity/parity_engines.py +0 -0
  147. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/parity/run_baseline.py +0 -0
  148. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/parity/test_parity_baseline.py +0 -0
  149. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/parity/test_parity_corpus.py +0 -0
  150. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/parity/test_parity_engine.py +0 -0
  151. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/parity/test_parity_gate.py +0 -0
  152. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/parity/test_parity_native.py +0 -0
  153. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/parity/test_parity_native_interaction.py +0 -0
  154. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/parity/test_parity_operator.py +0 -0
  155. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/test_attach_browser.py +0 -0
  156. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/test_auth_identity.py +0 -0
  157. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/test_automation_backend.py +0 -0
  158. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/test_browser_tools.py +0 -0
  159. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/test_camoufox.py +0 -0
  160. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/test_cdp_client.py +0 -0
  161. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/test_close_and_real_mode.py +0 -0
  162. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/test_daemonless_cli.py +0 -0
  163. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/test_dispatch.py +0 -0
  164. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/test_e2e_backend_seam.py +0 -0
  165. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/test_e2e_camoufox.py +0 -0
  166. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/test_frame_manager.py +0 -0
  167. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/test_inspect_mode.py +0 -0
  168. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/test_interstitial.py +0 -0
  169. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/test_live_chrome.py +0 -0
  170. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/test_mcp_daemon.py +0 -0
  171. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/test_mcp_surface_contract.py +0 -0
  172. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/test_native_backend.py +0 -0
  173. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/test_native_interaction.py +0 -0
  174. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/test_native_snapshot.py +0 -0
  175. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/test_new_tools.py +0 -0
  176. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/test_packaging.py +0 -0
  177. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/test_page_selection.py +0 -0
  178. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/test_persistent_browser.py +0 -0
  179. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/test_profile_liveness.py +0 -0
  180. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/test_profiler.py +0 -0
  181. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/test_profiles.py +0 -0
  182. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/test_session_dispatch.py +0 -0
  183. {browser_tools-0.2.0 → browser_tools-0.3.0}/tests/test_session_lifecycle.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: browser-tools
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Browser automation, debugging, and anti-detect browsing CLI
5
5
  Project-URL: Homepage, https://github.com/dungle-scrubs/browser-tools
6
6
  Project-URL: Repository, https://github.com/dungle-scrubs/browser-tools
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "browser-tools"
3
- version = "0.2.0"
3
+ version = "0.3.0"
4
4
  description = "Browser automation, debugging, and anti-detect browsing CLI"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -27,7 +27,7 @@ import argparse
27
27
  import json
28
28
  import sys
29
29
 
30
- from . import curated, events, lifecycle, list_verbs, passthrough
30
+ from . import curated, events, lifecycle, list_verbs, passthrough, user_settings
31
31
  from .lifecycle import LifecycleError
32
32
  from .passthrough import UsageError as PassthroughUsageError
33
33
 
@@ -48,6 +48,7 @@ _KNOWN_VERBS = {
48
48
  "stop",
49
49
  "cleanup",
50
50
  "guide",
51
+ "window-border",
51
52
  "help",
52
53
  "attach",
53
54
  "wait",
@@ -89,7 +90,10 @@ def build_parser() -> argparse.ArgumentParser:
89
90
  launch.add_argument(
90
91
  "--no-window-border",
91
92
  action="store_true",
92
- help="Do not draw the agent-window marking border",
93
+ help=(
94
+ "Do not mark this window at all (no tab-title prefix, no border). To keep "
95
+ "the title prefix and only hide the border, use: window-border off"
96
+ ),
93
97
  )
94
98
  launch.add_argument(
95
99
  "browser_args",
@@ -108,6 +112,19 @@ def build_parser() -> argparse.ArgumentParser:
108
112
  sub.add_parser("cleanup", help="Remove stale registry entries and session dirs")
109
113
  sub.add_parser("guide", help="Print the bundled agent manual")
110
114
 
115
+ border = sub.add_parser(
116
+ "window-border",
117
+ help="Show, or persistently turn on/off, the border drawn in marked windows",
118
+ description=(
119
+ "The window marker draws a colored border and a corner badge over the page "
120
+ "of every window it marks. They cover the page's outer edge and top-left "
121
+ "corner. 'off' removes them from every running browser within a second and "
122
+ "keeps them off for later launches, until 'on'. The tab-title prefix is not "
123
+ "affected. With no argument, prints the current setting."
124
+ ),
125
+ )
126
+ border.add_argument("state", nargs="?", choices=["on", "off"], help="New setting")
127
+
111
128
  help_cmd = sub.add_parser(
112
129
  "help", help="Live CDP protocol help from a running instance, or static usage"
113
130
  )
@@ -262,13 +279,27 @@ def _print_json(payload: object) -> None:
262
279
  print(json.dumps(payload, indent=2))
263
280
 
264
281
 
265
- def _strip_arg_separator(browser_args: list[str] | None) -> list[str]:
266
- """Drop the leading ``--`` argparse.REMAINDER keeps in front of BROWSER_ARGS."""
267
- if not browser_args:
282
+ def _browser_args_from_remainder(remainder: list[str] | None) -> list[str]:
283
+ """Return the verbatim BROWSER_ARGS that follow ``--``, or reject the remainder.
284
+
285
+ ``launch`` takes no positional arguments. argparse.REMAINDER starts
286
+ collecting at the first bare token and swallows every option after it, so
287
+ ``bt launch e2e-01 --headless`` would silently hand ``e2e-01 --headless``
288
+ to Chrome: Chrome opens ``http://e2e-01/`` and runs headless while
289
+ browser-tools believes the launch is headed (spawning the supervisor,
290
+ marking the window). A remainder that does not start with ``--`` is
291
+ therefore a usage error, not browser args.
292
+ """
293
+ if not remainder:
268
294
  return []
269
- if browser_args[0] == "--":
270
- return browser_args[1:]
271
- return browser_args
295
+ if remainder[0] == "--":
296
+ return remainder[1:]
297
+ raise PassthroughUsageError(
298
+ f"launch takes no positional arguments (got {remainder[0]!r}); instance "
299
+ f"names are assigned by the registry; use --profile NAME to bind a named "
300
+ f"profile. Flags for the browser itself go after '--', e.g. "
301
+ f"bt launch --headless -- --proxy-server=host:port"
302
+ )
272
303
 
273
304
 
274
305
  def _run(args: argparse.Namespace) -> int:
@@ -284,7 +315,7 @@ def _run(args: argparse.Namespace) -> int:
284
315
  port=args.port,
285
316
  fingerprint=args.fingerprint,
286
317
  window_border=not args.no_window_border,
287
- browser_args=_strip_arg_separator(args.browser_args),
318
+ browser_args=_browser_args_from_remainder(args.browser_args),
288
319
  registry_path=registry_path,
289
320
  )
290
321
  _print_json(
@@ -322,6 +353,18 @@ def _run(args: argparse.Namespace) -> int:
322
353
  print(lifecycle.guide_text())
323
354
  return EXIT_OK
324
355
 
356
+ if args.command == "window-border":
357
+ if args.state is not None:
358
+ user_settings.set_window_border(args.state == user_settings.ON)
359
+ enabled = user_settings.window_border_enabled()
360
+ _print_json(
361
+ {
362
+ "window_border": user_settings.ON if enabled else user_settings.OFF,
363
+ "settings_path": str(user_settings.settings_path()),
364
+ }
365
+ )
366
+ return EXIT_OK
367
+
325
368
  if args.command == "help":
326
369
  instance, query = passthrough.resolve_help_args(args.args, registry_path=registry_path)
327
370
  passthrough.run_help(instance, query, registry_path=registry_path)
@@ -7,9 +7,12 @@
7
7
  # `binary` parameter was added to launch_browser so the CLI front can resolve
8
8
  # the --channel policy flag to a Chrome binary before launch, and (#36) a
9
9
  # `user_data_dir` parameter so the CLI front can bind a persistent per-profile
10
- # user-data-dir instead of the throwaway session dir. Intra-package imports are
11
- # rewritten to browser_tools.core. Otherwise unchanged from chrome-agent
12
- # v0.5.7. See RFC-01, section "Vendoring rules".
10
+ # user-data-dir instead of the throwaway session dir. A headed launch starts
11
+ # with no window (--no-startup-window) and opens its first window in the
12
+ # background over CDP, so the launch never takes the user's focus (see
13
+ # _open_first_window). Intra-package imports are rewritten to
14
+ # browser_tools.core. Otherwise unchanged from chrome-agent v0.5.7. See RFC-01,
15
+ # section "Vendoring rules".
13
16
 
14
17
  """Browser launch and session management.
15
18
 
@@ -167,6 +170,8 @@ async def launch_browser(
167
170
  ]
168
171
  if headless:
169
172
  args.append("--headless=new")
173
+ else:
174
+ args.append("--no-startup-window")
170
175
  if extra_args:
171
176
  args.extend(extra_args)
172
177
 
@@ -214,6 +219,19 @@ async def launch_browser(
214
219
  process.kill()
215
220
  raise TimeoutError("Browser did not start within 30 seconds")
216
221
 
222
+ if not headless:
223
+ try:
224
+ await _open_first_window(port=port)
225
+ except BaseException as exc:
226
+ # Nothing is registered yet, so a browser left running here would be
227
+ # an orphan no verb can see or stop. BaseException, not Exception: a
228
+ # launch cancelled while this awaits (Ctrl-C, a cancelled task)
229
+ # leaves the same orphan, and CancelledError is not an Exception.
230
+ process.kill()
231
+ if isinstance(exc, asyncio.CancelledError):
232
+ raise
233
+ raise RuntimeError(f"Browser started but its first window did not open: {exc}") from exc
234
+
217
235
  # Phase 6: Pin to desktop (Linux/X11, best-effort)
218
236
  if pin_to_desktop and not headless:
219
237
  await _move_to_launching_desktop(pid=process.pid)
@@ -236,12 +254,13 @@ async def launch_browser(
236
254
  # detached process -- it must survive the caller exiting (fire-and-forget
237
255
  # launch model) -- that holds a CDP connection and, when the browser/window
238
256
  # closes, retires the instance from the registry (and removes its session
239
- # dir). While the browser is alive it also draws the window border, unless:
257
+ # dir). While the browser is alive it also marks the window (tab title
258
+ # prefix), unless:
240
259
  # - --no-window-border (window_border is False), or
241
- # - a fingerprint profile is active: the in-page border/badge/title are
242
- # page-observable (a findable host element + a modified document.title),
243
- # and bot-defended sites -- exactly where fingerprinting is used -- are
244
- # where DOM/title-diffing detectors live. See the detection audit.
260
+ # - a fingerprint profile is active: the title prefix is page-observable
261
+ # (a modified document.title), and bot-defended sites -- exactly where
262
+ # fingerprinting is used -- are where title-diffing detectors live. See
263
+ # the detection audit.
245
264
  # Headless launches get no supervisor (no window to close or mark); their
246
265
  # registry entries are reclaimed by the launch-time prune above / cleanup.
247
266
  if not headless:
@@ -256,6 +275,24 @@ async def launch_browser(
256
275
  return instance_info
257
276
 
258
277
 
278
+ async def _open_first_window(*, port: int) -> None:
279
+ """Open the browser's first window in the background.
280
+
281
+ Chrome started normally opens a window and makes itself the active app,
282
+ which takes keyboard focus and the window manager's focus from whatever
283
+ the user is doing (measured on macOS; ``open -g`` does not prevent it).
284
+ A browser started with ``--no-startup-window`` opens nothing, and a window
285
+ created over CDP with ``background`` set leaves focus where it is.
286
+ """
287
+ from .cdp_client import CDPClient, get_ws_url
288
+
289
+ async with CDPClient(ws_url=get_ws_url(port=port, target_type="browser")) as cdp:
290
+ await cdp.send(
291
+ method="Target.createTarget",
292
+ params={"url": "about:blank", "newWindow": True, "background": True},
293
+ )
294
+
295
+
259
296
  def cleanup_sessions(registry_path: str | None = None) -> list[str]:
260
297
  """Remove stale instances and their session directories.
261
298