real-browser-cli 0.16.3__tar.gz → 0.16.8__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 (256) hide show
  1. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/.gitea/workflows/testing.yml +2 -2
  2. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/CLAUDE.md +1 -1
  3. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/PKG-INFO +99 -4
  4. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/README.md +95 -2
  5. real_browser_cli-0.16.8/browser_cli/auth/server_identity.py +57 -0
  6. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/client/core.py +37 -5
  7. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/client/messages.py +2 -2
  8. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/extract.py +25 -19
  9. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/page.py +5 -4
  10. real_browser_cli-0.16.8/browser_cli/commands/remote.py +153 -0
  11. real_browser_cli-0.16.8/browser_cli/compat/auth.py +30 -0
  12. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/compat/commands.py +19 -16
  13. real_browser_cli-0.16.8/browser_cli/mcp/__init__.py +5 -0
  14. real_browser_cli-0.16.8/browser_cli/mcp/naming.py +39 -0
  15. real_browser_cli-0.16.8/browser_cli/mcp/serialization.py +19 -0
  16. real_browser_cli-0.16.8/browser_cli/mcp/server.py +292 -0
  17. real_browser_cli-0.16.8/browser_cli/mcp/targets.py +16 -0
  18. real_browser_cli-0.16.8/browser_cli/remote/known_hosts.py +108 -0
  19. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/remote/pool.py +23 -0
  20. real_browser_cli-0.16.8/browser_cli/remote/registry.py +100 -0
  21. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/remote/transport.py +7 -1
  22. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/sdk/dom.py +25 -23
  23. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/serve/challenge.py +4 -0
  24. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/serve/security.py +30 -1
  25. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/manifest.json +1 -1
  26. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/classes/JobManager.ts +10 -0
  27. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/commands/dom.ts +2 -2
  28. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/commands/perf.ts +15 -11
  29. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/commands/tabs-query.ts +22 -2
  30. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/commands/windows.ts +29 -2
  31. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/test/jobs.test.ts +16 -1
  32. real_browser_cli-0.16.8/extension/test/url-match.test.ts +43 -0
  33. real_browser_cli-0.16.8/extension/test/windows.test.ts +69 -0
  34. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/n8n-nodes-browser-cli/README.md +42 -7
  35. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/n8n-nodes-browser-cli/credentials/BrowserCliApi.credentials.ts +17 -0
  36. real_browser_cli-0.16.8/n8n-nodes-browser-cli/nodes/BrowserCli/BrowserCli.node.ts +870 -0
  37. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/n8n-nodes-browser-cli/nodes/BrowserCli/browserCli.svg +1 -1
  38. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/n8n-nodes-browser-cli/nodes/BrowserCli/protocol.ts +78 -0
  39. real_browser_cli-0.16.8/n8n-nodes-browser-cli/nodes/BrowserCli/request.ts +303 -0
  40. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/n8n-nodes-browser-cli/nodes/BrowserCli/serveClient.ts +12 -2
  41. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/n8n-nodes-browser-cli/package-lock.json +1385 -292
  42. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/n8n-nodes-browser-cli/package.json +6 -6
  43. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/n8n-nodes-browser-cli/test/protocol.test.ts +39 -0
  44. real_browser_cli-0.16.8/n8n-nodes-browser-cli/test/request.test.ts +189 -0
  45. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/n8n-nodes-browser-cli/test-dist/protocol.test.mjs +91 -0
  46. real_browser_cli-0.16.8/n8n-nodes-browser-cli/test-dist/request.test.mjs +413 -0
  47. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/n8n-nodes-browser-cli/tsconfig.json +2 -2
  48. real_browser_cli-0.16.8/package-lock.json +917 -0
  49. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/package.json +3 -3
  50. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/pyproject.toml +5 -1
  51. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/conftest.py +12 -0
  52. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_api.py +14 -3
  53. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_cli.py +52 -13
  54. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_client.py +66 -15
  55. real_browser_cli-0.16.8/tests/test_compat.py +48 -0
  56. real_browser_cli-0.16.8/tests/test_known_hosts.py +64 -0
  57. real_browser_cli-0.16.8/tests/test_mcp.py +305 -0
  58. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_remote_pool.py +33 -0
  59. real_browser_cli-0.16.8/tests/test_remote_registry.py +59 -0
  60. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_serve.py +0 -27
  61. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_serve_security.py +17 -0
  62. real_browser_cli-0.16.8/uv.lock +1559 -0
  63. real_browser_cli-0.16.3/browser_cli/commands/remote.py +0 -78
  64. real_browser_cli-0.16.3/browser_cli/compat/auth.py +0 -50
  65. real_browser_cli-0.16.3/browser_cli/remote/registry.py +0 -53
  66. real_browser_cli-0.16.3/n8n-nodes-browser-cli/nodes/BrowserCli/BrowserCli.node.ts +0 -382
  67. real_browser_cli-0.16.3/n8n-nodes-browser-cli/nodes/BrowserCli/request.ts +0 -129
  68. real_browser_cli-0.16.3/n8n-nodes-browser-cli/test/request.test.ts +0 -73
  69. real_browser_cli-0.16.3/n8n-nodes-browser-cli/test-dist/request.test.mjs +0 -147
  70. real_browser_cli-0.16.3/package-lock.json +0 -556
  71. real_browser_cli-0.16.3/uv.lock +0 -705
  72. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/.claude/settings.local.json +0 -0
  73. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/.gitea/workflows/package-extension.yml +0 -0
  74. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/.gitea/workflows/publish.yml +0 -0
  75. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/.gitignore +0 -0
  76. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/.python-version +0 -0
  77. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/LICENSE +0 -0
  78. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/PRIVACY.md +0 -0
  79. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/__init__.py +0 -0
  80. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/async_sdk.py +0 -0
  81. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/auth/__init__.py +0 -0
  82. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/auth/agent.py +0 -0
  83. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/auth/keys.py +0 -0
  84. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/auth/pq.py +0 -0
  85. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/auth/signing.py +0 -0
  86. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/cli.py +0 -0
  87. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/client/__init__.py +0 -0
  88. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/client/auth.py +0 -0
  89. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/client/targets.py +0 -0
  90. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/command_security.py +0 -0
  91. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/__init__.py +0 -0
  92. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/auth.py +0 -0
  93. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/clients.py +0 -0
  94. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/completion.py +0 -0
  95. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/doctor.py +0 -0
  96. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/dom.py +0 -0
  97. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/events.py +0 -0
  98. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/extension.py +0 -0
  99. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/groups.py +0 -0
  100. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/install.py +0 -0
  101. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/navigate.py +0 -0
  102. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/perf.py +0 -0
  103. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/raw.py +0 -0
  104. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/rendering/__init__.py +0 -0
  105. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/rendering/common.py +0 -0
  106. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/rendering/labels.py +0 -0
  107. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/rendering/tabs_tree.py +0 -0
  108. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/rendering/windows_tree.py +0 -0
  109. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/script.py +0 -0
  110. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/search.py +0 -0
  111. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/serve.py +0 -0
  112. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/serve_http.py +0 -0
  113. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/session.py +0 -0
  114. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/storage.py +0 -0
  115. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/tabs.py +0 -0
  116. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/watch.py +0 -0
  117. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/windows.py +0 -0
  118. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/workspace.py +0 -0
  119. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/compat/__init__.py +0 -0
  120. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/constants.py +0 -0
  121. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/endpoints.py +0 -0
  122. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/errors.py +0 -0
  123. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/framing.py +0 -0
  124. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/local_transport.py +0 -0
  125. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/markdown/__init__.py +0 -0
  126. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/markdown/html.py +0 -0
  127. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/markdown/render.py +0 -0
  128. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/models.py +0 -0
  129. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/native/__init__.py +0 -0
  130. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/native/host.py +0 -0
  131. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/native/local_server.py +0 -0
  132. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/native/protocol.py +0 -0
  133. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/platform.py +0 -0
  134. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/registry.py +0 -0
  135. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/remote/__init__.py +0 -0
  136. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/remote/auth.py +0 -0
  137. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/remote/socket.py +0 -0
  138. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/sdk/__init__.py +0 -0
  139. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/sdk/base.py +0 -0
  140. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/sdk/browser_data.py +0 -0
  141. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/sdk/decorators.py +0 -0
  142. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/sdk/extension.py +0 -0
  143. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/sdk/factories.py +0 -0
  144. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/sdk/groups.py +0 -0
  145. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/sdk/navigation.py +0 -0
  146. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/sdk/perf.py +0 -0
  147. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/sdk/routing.py +0 -0
  148. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/sdk/session.py +0 -0
  149. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/sdk/tabs.py +0 -0
  150. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/sdk/windows.py +0 -0
  151. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/sdk/workflow_decorators.py +0 -0
  152. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/search/__init__.py +0 -0
  153. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/search/engines.py +0 -0
  154. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/serve/__init__.py +0 -0
  155. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/serve/auth.py +0 -0
  156. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/serve/control.py +0 -0
  157. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/serve/logging.py +0 -0
  158. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/serve/proxy.py +0 -0
  159. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/serve/runtime.py +0 -0
  160. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/transport/__init__.py +0 -0
  161. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/transport/binary.py +0 -0
  162. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/transport/codecs.py +0 -0
  163. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/version_manager.py +0 -0
  164. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/examples/demo.py +0 -0
  165. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/examples/demo.sh +0 -0
  166. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/content.js +0 -0
  167. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/icon.svg +0 -0
  168. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/icons/icon-128.png +0 -0
  169. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/icons/icon-16.png +0 -0
  170. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/icons/icon-32.png +0 -0
  171. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/icons/icon-48.png +0 -0
  172. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/browser-api.ts +0 -0
  173. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/classes/CommandGroup.ts +0 -0
  174. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/classes/CommandRegistry.ts +0 -0
  175. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/classes/NativeConnection.ts +0 -0
  176. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/commands/autosave.ts +0 -0
  177. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/commands/browser-data.ts +0 -0
  178. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/commands/extension.ts +0 -0
  179. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/commands/groups.ts +0 -0
  180. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/commands/injected.ts +0 -0
  181. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/commands/navigation.ts +0 -0
  182. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/commands/session-snapshot.ts +0 -0
  183. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/commands/session.ts +0 -0
  184. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/commands/tabs.ts +0 -0
  185. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/content/dispatch.ts +0 -0
  186. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/content/dom-ops.ts +0 -0
  187. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/content/extract.ts +0 -0
  188. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/content/markdown/code.ts +0 -0
  189. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/content/markdown/index.ts +0 -0
  190. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/content/markdown/renderer.ts +0 -0
  191. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/content/markdown/root.ts +0 -0
  192. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/content/markdown/utils.ts +0 -0
  193. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/content/page-info.ts +0 -0
  194. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/content-dispatch.ts +0 -0
  195. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/core/errors.ts +0 -0
  196. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/core/group-helpers.ts +0 -0
  197. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/core/index.ts +0 -0
  198. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/core/scripting.ts +0 -0
  199. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/core/storage.ts +0 -0
  200. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/core/tab-groups.ts +0 -0
  201. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/core/tab-helpers.ts +0 -0
  202. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/core/throttle.ts +0 -0
  203. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/index.ts +0 -0
  204. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/types/command-args.ts +0 -0
  205. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/types/index.ts +0 -0
  206. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/types/jobs.ts +0 -0
  207. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/types/json.ts +0 -0
  208. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/types/messages.ts +0 -0
  209. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/types/session.ts +0 -0
  210. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/types/tabs.ts +0 -0
  211. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/types/webextension.ts +0 -0
  212. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/test/autosave.test.ts +0 -0
  213. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/test/browser-api.test.ts +0 -0
  214. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/test/chrome-mock.ts +0 -0
  215. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/test/clients.test.ts +0 -0
  216. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/test/navigation.test.ts +0 -0
  217. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/test/paging.test.ts +0 -0
  218. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/justfile +0 -0
  219. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/n8n-nodes-browser-cli/.claude/settings.local.json +0 -0
  220. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/n8n-nodes-browser-cli/.gitignore +0 -0
  221. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/n8n-nodes-browser-cli/scripts/copy-assets.mjs +0 -0
  222. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/n8n-nodes-browser-cli/scripts/run-tests.mjs +0 -0
  223. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/scripts/gen_extension_key.py +0 -0
  224. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/scripts/package_extension.py +0 -0
  225. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/scripts/package_verified_crx.sh +0 -0
  226. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/scripts/setup_verified_crx_key.sh +0 -0
  227. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/shell.nix +0 -0
  228. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_auth.py +0 -0
  229. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_commands_cli.py +0 -0
  230. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_dom.py +0 -0
  231. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_extension_error_page_handling.py +0 -0
  232. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_extension_packaging.py +0 -0
  233. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_extract.py +0 -0
  234. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_framing.py +0 -0
  235. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_groups.py +0 -0
  236. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_markdown_security.py +0 -0
  237. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_native_host.py +0 -0
  238. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_nav.py +0 -0
  239. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_new_feature_commands.py +0 -0
  240. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_page.py +0 -0
  241. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_perf.py +0 -0
  242. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_performance_integration.py +0 -0
  243. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_platform.py +0 -0
  244. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_refactor_boundaries.py +0 -0
  245. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_registry.py +0 -0
  246. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_remote_protocol_matrix.py +0 -0
  247. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_rendering.py +0 -0
  248. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_session.py +0 -0
  249. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_storage.py +0 -0
  250. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_tabs.py +0 -0
  251. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_transport.py +0 -0
  252. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_windows.py +0 -0
  253. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tsconfig.json +0 -0
  254. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/webstore-assets/screenshots/screenshot-1.png +0 -0
  255. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/webstore-assets/screenshots/screenshot-2.png +0 -0
  256. {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/webstore-assets/screenshots/screenshot-3.png +0 -0
@@ -28,8 +28,8 @@ jobs:
28
28
  fail-fast: false
29
29
  matrix:
30
30
  browser-cli-client-version:
31
- - "0.9.3"
32
- - "0.9.5"
31
+ - "0.15.0"
32
+ - "0.16.0"
33
33
 
34
34
  steps:
35
35
  - name: Checkout
@@ -3,7 +3,7 @@
3
3
  ## Memory laden
4
4
  Beim Start dieser Session: Memory-Datei lesen für vollständigen Codebase-Kontext:
5
5
  ```
6
- /home/daniel156161/Dokumente/Nodes - Obsidian/AI-Worker/03 Bereiche/Open Source & Side Projects/browser-cli.md
6
+ 03 Bereiche/Open Source & Side Projects/browser-cli.md
7
7
  ```
8
8
 
9
9
  Memory nach Änderungen aktualisieren (neue Commands, Architektur-Änderungen, neue Patterns, Version-Bumps).
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: real-browser-cli
3
- Version: 0.16.3
3
+ Version: 0.16.8
4
4
  Summary: Control your real running browser from the terminal or Python SDK
5
5
  Project-URL: Homepage, https://git.yiprawr.dev/Automatisation/browser-cli
6
6
  Project-URL: Repository, https://git.yiprawr.dev/Automatisation/browser-cli
@@ -90,6 +90,8 @@ Requires-Dist: questionary>=2
90
90
  Requires-Dist: rich>=13
91
91
  Provides-Extra: fast
92
92
  Requires-Dist: zstandard>=0.22; extra == 'fast'
93
+ Provides-Extra: mcp
94
+ Requires-Dist: mcp<3,>=2; extra == 'mcp'
93
95
  Description-Content-Type: text/markdown
94
96
 
95
97
  # browser-cli
@@ -164,6 +166,11 @@ For better remote-response compression, install the optional `fast` extra:
164
166
  uv tool install "real-browser-cli[fast]"
165
167
  ```
166
168
 
169
+ To expose the conservative MCP tool surface, install the optional `mcp` extra:
170
+ ```sh
171
+ uv tool install "real-browser-cli[mcp]"
172
+ ```
173
+
167
174
  To upgrade later:
168
175
 
169
176
  ```sh
@@ -235,6 +242,89 @@ browser-cli/
235
242
 
236
243
  ---
237
244
 
245
+ ## Stateless MCP server
246
+ The optional MCP adapter exposes a small, typed subset of the Python SDK for
247
+ MCP hosts such as Claude Desktop, Claude Code, Cursor, or VS Code. It controls
248
+ the same real browser; it does not launch a headless browser or duplicate the
249
+ browser command implementation.
250
+
251
+ Install and run the local stdio server:
252
+ ```sh
253
+ uv tool install "real-browser-cli[mcp]"
254
+ browser-cli-mcp
255
+ ```
256
+
257
+ Example MCP host configuration:
258
+ ```json
259
+ {
260
+ "mcpServers": {
261
+ "browser-cli": {
262
+ "command": "browser-cli-mcp"
263
+ }
264
+ }
265
+ }
266
+ ```
267
+
268
+ The server is stateless at the MCP layer. Every tool call creates a fresh
269
+ `BrowserCLI` SDK client, and browser state remains in the real browser. Pass
270
+ `browser`, `remote`, and `key` on a tool call when a specific local profile or
271
+ authenticated browser-cli remote is required.
272
+
273
+ `browser_navigate`, `browser_tabs_close`, and `browser_screenshot` take an
274
+ optional `tab_id` and act on the active tab when it is omitted, so a caller
275
+ does not need a preceding `browser_tabs_list` round trip. `browser_tabs_close`
276
+ reports the tab it closed.
277
+
278
+ Available tools:
279
+ - `browser_tabs_list`, `browser_tabs_open`, `browser_tabs_close`
280
+ - `browser_navigate`, `browser_page_info`
281
+ - `browser_extract_text`, `browser_extract_markdown`
282
+ - `browser_dom_query`, `browser_dom_click`, `browser_dom_type`
283
+ - `browser_screenshot`
284
+
285
+ Generic JavaScript evaluation, raw browser commands, storage writes, and
286
+ session import are intentionally not exposed.
287
+
288
+ ### Pinning one browser and naming tools
289
+ Set `BROWSER_CLI_PROFILE` to pin every call from an MCP server to one browser,
290
+ so tools do not have to pass `browser` and listing tools do not fan out across
291
+ all connected browsers:
292
+ ```json
293
+ {
294
+ "mcpServers": {
295
+ "browser-cli-testing": {
296
+ "command": "browser-cli-mcp",
297
+ "env": {
298
+ "BROWSER_CLI_PROFILE": "testing",
299
+ "BROWSER_CLI_MCP_TOOL_PREFIX": ""
300
+ }
301
+ }
302
+ }
303
+ }
304
+ ```
305
+ `BROWSER_CLI_REMOTE` and `BROWSER_CLI_KEY` pin an authenticated remote the same
306
+ way. Explicit `browser`, `remote`, and `key` tool arguments still win.
307
+
308
+ Tool names carry a `browser_` prefix by default so they stay unambiguous in
309
+ hosts that expose raw MCP tool names. Hosts that already prefix tools with the
310
+ server name produce stutter such as `browser_cli_testing_browser_tabs_list`;
311
+ setting `BROWSER_CLI_MCP_TOOL_PREFIX` to an empty string drops the built-in
312
+ prefix and yields `browser_cli_testing_tabs_list`. Any other value replaces the
313
+ prefix.
314
+
315
+ For local development and testing, Streamable HTTP is also available:
316
+ ```sh
317
+ browser-cli-mcp --transport streamable-http --port 8000
318
+ # endpoint: http://127.0.0.1:8000/mcp
319
+ ```
320
+
321
+ HTTP uses stateless JSON responses and intentionally refuses non-loopback bind
322
+ addresses because this MCP endpoint has no independent authentication. For a
323
+ browser on another machine, keep MCP local and pass an authenticated
324
+ browser-cli `remote` target to each tool call.
325
+
326
+ ---
327
+
238
328
  ## CLI reference
239
329
  During source development, commands are usually run as `uv run browser-cli [--browser ALIAS] <command>`. After tool installation, use `browser-cli ...` directly. Add `--remote HOST[:PORT]` and optionally `--key PATH` to target a browser exposed by `browser-cli serve`.
240
330
 
@@ -290,8 +380,9 @@ browser-cli search so click choices
290
380
  ```sh
291
381
  browser-cli tabs list # list all open tabs (all windows)
292
382
  browser-cli tabs count # count all tabs
293
- browser-cli tabs count youtube # count tabs matching URL pattern
294
- browser-cli tabs filter youtube # list tabs matching URL pattern
383
+ browser-cli tabs count youtube # count tabs whose URL contains "youtube"
384
+ browser-cli tabs filter youtube # list tabs whose URL contains "youtube"
385
+ browser-cli tabs filter 'twitch.tv/*' # glob: list every twitch.tv tab
295
386
  browser-cli tabs query "pull request" # search tabs by URL or title
296
387
 
297
388
  browser-cli tabs active 1234 # switch browser focus to tab
@@ -313,6 +404,10 @@ browser-cli tabs sort --by time
313
404
  browser-cli tabs merge-windows # pull all tabs into the current window
314
405
  ```
315
406
 
407
+ > URL patterns for `tabs filter` / `tabs count` match against the full tab URL.
408
+ > A plain string is a case-sensitive substring (`youtube`); a pattern containing
409
+ > `*` or `?` is treated as a glob (`twitch.tv/*`, `*.twitch.tv`).
410
+
316
411
  ### Tab groups
317
412
  ```sh
318
413
  browser-cli groups list # list all tab groups
@@ -70,6 +70,11 @@ For better remote-response compression, install the optional `fast` extra:
70
70
  uv tool install "real-browser-cli[fast]"
71
71
  ```
72
72
 
73
+ To expose the conservative MCP tool surface, install the optional `mcp` extra:
74
+ ```sh
75
+ uv tool install "real-browser-cli[mcp]"
76
+ ```
77
+
73
78
  To upgrade later:
74
79
 
75
80
  ```sh
@@ -141,6 +146,89 @@ browser-cli/
141
146
 
142
147
  ---
143
148
 
149
+ ## Stateless MCP server
150
+ The optional MCP adapter exposes a small, typed subset of the Python SDK for
151
+ MCP hosts such as Claude Desktop, Claude Code, Cursor, or VS Code. It controls
152
+ the same real browser; it does not launch a headless browser or duplicate the
153
+ browser command implementation.
154
+
155
+ Install and run the local stdio server:
156
+ ```sh
157
+ uv tool install "real-browser-cli[mcp]"
158
+ browser-cli-mcp
159
+ ```
160
+
161
+ Example MCP host configuration:
162
+ ```json
163
+ {
164
+ "mcpServers": {
165
+ "browser-cli": {
166
+ "command": "browser-cli-mcp"
167
+ }
168
+ }
169
+ }
170
+ ```
171
+
172
+ The server is stateless at the MCP layer. Every tool call creates a fresh
173
+ `BrowserCLI` SDK client, and browser state remains in the real browser. Pass
174
+ `browser`, `remote`, and `key` on a tool call when a specific local profile or
175
+ authenticated browser-cli remote is required.
176
+
177
+ `browser_navigate`, `browser_tabs_close`, and `browser_screenshot` take an
178
+ optional `tab_id` and act on the active tab when it is omitted, so a caller
179
+ does not need a preceding `browser_tabs_list` round trip. `browser_tabs_close`
180
+ reports the tab it closed.
181
+
182
+ Available tools:
183
+ - `browser_tabs_list`, `browser_tabs_open`, `browser_tabs_close`
184
+ - `browser_navigate`, `browser_page_info`
185
+ - `browser_extract_text`, `browser_extract_markdown`
186
+ - `browser_dom_query`, `browser_dom_click`, `browser_dom_type`
187
+ - `browser_screenshot`
188
+
189
+ Generic JavaScript evaluation, raw browser commands, storage writes, and
190
+ session import are intentionally not exposed.
191
+
192
+ ### Pinning one browser and naming tools
193
+ Set `BROWSER_CLI_PROFILE` to pin every call from an MCP server to one browser,
194
+ so tools do not have to pass `browser` and listing tools do not fan out across
195
+ all connected browsers:
196
+ ```json
197
+ {
198
+ "mcpServers": {
199
+ "browser-cli-testing": {
200
+ "command": "browser-cli-mcp",
201
+ "env": {
202
+ "BROWSER_CLI_PROFILE": "testing",
203
+ "BROWSER_CLI_MCP_TOOL_PREFIX": ""
204
+ }
205
+ }
206
+ }
207
+ }
208
+ ```
209
+ `BROWSER_CLI_REMOTE` and `BROWSER_CLI_KEY` pin an authenticated remote the same
210
+ way. Explicit `browser`, `remote`, and `key` tool arguments still win.
211
+
212
+ Tool names carry a `browser_` prefix by default so they stay unambiguous in
213
+ hosts that expose raw MCP tool names. Hosts that already prefix tools with the
214
+ server name produce stutter such as `browser_cli_testing_browser_tabs_list`;
215
+ setting `BROWSER_CLI_MCP_TOOL_PREFIX` to an empty string drops the built-in
216
+ prefix and yields `browser_cli_testing_tabs_list`. Any other value replaces the
217
+ prefix.
218
+
219
+ For local development and testing, Streamable HTTP is also available:
220
+ ```sh
221
+ browser-cli-mcp --transport streamable-http --port 8000
222
+ # endpoint: http://127.0.0.1:8000/mcp
223
+ ```
224
+
225
+ HTTP uses stateless JSON responses and intentionally refuses non-loopback bind
226
+ addresses because this MCP endpoint has no independent authentication. For a
227
+ browser on another machine, keep MCP local and pass an authenticated
228
+ browser-cli `remote` target to each tool call.
229
+
230
+ ---
231
+
144
232
  ## CLI reference
145
233
  During source development, commands are usually run as `uv run browser-cli [--browser ALIAS] <command>`. After tool installation, use `browser-cli ...` directly. Add `--remote HOST[:PORT]` and optionally `--key PATH` to target a browser exposed by `browser-cli serve`.
146
234
 
@@ -196,8 +284,9 @@ browser-cli search so click choices
196
284
  ```sh
197
285
  browser-cli tabs list # list all open tabs (all windows)
198
286
  browser-cli tabs count # count all tabs
199
- browser-cli tabs count youtube # count tabs matching URL pattern
200
- browser-cli tabs filter youtube # list tabs matching URL pattern
287
+ browser-cli tabs count youtube # count tabs whose URL contains "youtube"
288
+ browser-cli tabs filter youtube # list tabs whose URL contains "youtube"
289
+ browser-cli tabs filter 'twitch.tv/*' # glob: list every twitch.tv tab
201
290
  browser-cli tabs query "pull request" # search tabs by URL or title
202
291
 
203
292
  browser-cli tabs active 1234 # switch browser focus to tab
@@ -219,6 +308,10 @@ browser-cli tabs sort --by time
219
308
  browser-cli tabs merge-windows # pull all tabs into the current window
220
309
  ```
221
310
 
311
+ > URL patterns for `tabs filter` / `tabs count` match against the full tab URL.
312
+ > A plain string is a case-sensitive substring (`youtube`); a pattern containing
313
+ > `*` or `?` is treated as a glob (`twitch.tv/*`, `*.twitch.tv`).
314
+
222
315
  ### Tab groups
223
316
  ```sh
224
317
  browser-cli groups list # list all tab groups
@@ -0,0 +1,57 @@
1
+ """Persistent server identity keys for SSH-style remote host pinning."""
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ from pathlib import Path
6
+
7
+ from cryptography.exceptions import InvalidSignature
8
+ from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey, Ed25519PublicKey
9
+ from cryptography.hazmat.primitives.serialization import Encoding, NoEncryption, PrivateFormat, PublicFormat, load_pem_private_key
10
+
11
+ from browser_cli.constants import CONFIG_DIR
12
+
13
+ SERVER_IDENTITY_PATH = CONFIG_DIR / "server_identity.pem"
14
+
15
+ def load_or_create_server_identity(path: Path = SERVER_IDENTITY_PATH) -> Ed25519PrivateKey:
16
+ """Load the persistent serve identity key, creating it on first start."""
17
+ if path.exists():
18
+ key = load_pem_private_key(path.read_bytes(), password=None)
19
+ if not isinstance(key, Ed25519PrivateKey):
20
+ raise ValueError(f"server identity key is not Ed25519: {path}")
21
+ return key
22
+
23
+ path.parent.mkdir(parents=True, exist_ok=True)
24
+ key = Ed25519PrivateKey.generate()
25
+ pem = key.private_bytes(Encoding.PEM, PrivateFormat.PKCS8, NoEncryption())
26
+ fd = path.open("xb")
27
+ try:
28
+ fd.write(pem)
29
+ finally:
30
+ fd.close()
31
+ path.chmod(0o600)
32
+ return key
33
+
34
+ def public_key_hex(key: Ed25519PrivateKey) -> str:
35
+ return key.public_key().public_bytes(Encoding.Raw, PublicFormat.Raw).hex()
36
+
37
+ def _signed_challenge_fields(challenge: dict) -> dict:
38
+ return {key: value for key, value in challenge.items() if key != "server_sig"}
39
+
40
+ def challenge_payload(challenge: dict) -> bytes:
41
+ """Canonical bytes signed by the server identity key."""
42
+ return json.dumps(_signed_challenge_fields(challenge), sort_keys=True, separators=(",", ":")).encode("utf-8")
43
+
44
+ def sign_challenge(challenge: dict, key: Ed25519PrivateKey) -> str:
45
+ return key.sign(challenge_payload(challenge)).hex()
46
+
47
+ def verify_challenge_signature(challenge: dict) -> bool:
48
+ pub_hex = challenge.get("server_pubkey")
49
+ sig_hex = challenge.get("server_sig")
50
+ if not isinstance(pub_hex, str) or not isinstance(sig_hex, str):
51
+ return False
52
+ try:
53
+ pub = Ed25519PublicKey.from_public_bytes(bytes.fromhex(pub_hex))
54
+ pub.verify(bytes.fromhex(sig_hex), challenge_payload(challenge))
55
+ return True
56
+ except (InvalidSignature, ValueError):
57
+ return False
@@ -151,21 +151,25 @@ def active_browser_targets(*, include_remotes: bool = True, key=None, suppress_p
151
151
  targets.extend(_remote_browser_targets(key=key, suppress_pq_warning=suppress_pq_warning))
152
152
  return targets
153
153
 
154
- def _cached_client_row(target: BrowserTarget) -> dict | None:
154
+ def _cached_client_row(target: BrowserTarget, *, scoped: bool = False) -> dict | None:
155
155
  """Build a clients row from a target's discovery data, skipping a roundtrip.
156
156
 
157
157
  Returns None when the remote didn't advertise its version (older serve), so
158
- callers fall back to an explicit ``clients.list`` query.
158
+ callers fall back to an explicit ``clients.list`` query. When *scoped* is
159
+ true, the caller already selected one remote host, so render profile-only
160
+ labels instead of adding a host group header.
159
161
  """
160
162
  if target.version is None and target.extension_version is None:
161
163
  return None
162
- return {
163
- "profile": target.display_name,
164
- "profileGroup": target.display_group,
164
+ row = {
165
+ "profile": target.profile if scoped else target.display_name,
165
166
  "name": target.browser_name or "",
166
167
  "version": target.version or "",
167
168
  "extensionVersion": target.extension_version or "",
168
169
  }
170
+ if target.display_group and not scoped:
171
+ row["profileGroup"] = target.display_group
172
+ return row
169
173
 
170
174
  def _rows_from_result(result, label: str, profile_group: str | None) -> list[dict]:
171
175
  rows = []
@@ -243,6 +247,34 @@ def collect_browser_clients(
243
247
  return rows
244
248
 
245
249
  if remote:
250
+ targets = remote_browser_targets(remote, key=key)
251
+ if browser_alias:
252
+ targets = [target for target in targets if target.profile == browser_alias or target.display_name == browser_alias]
253
+ if targets:
254
+ uncached = []
255
+ for target in targets:
256
+ cached = _cached_client_row(target, scoped=True)
257
+ if cached is not None:
258
+ rows.append(cached)
259
+ else:
260
+ uncached.append(target)
261
+ results = _run_concurrent([
262
+ (lambda t=t: _client_rows_async(
263
+ t.profile,
264
+ profile=t.profile,
265
+ remote=remote,
266
+ key=key,
267
+ ))
268
+ for t in uncached
269
+ ])
270
+ for result in results:
271
+ if isinstance(result, (BrowserNotConnected, RuntimeError)):
272
+ continue
273
+ if isinstance(result, BaseException):
274
+ raise result
275
+ rows.extend(result)
276
+ return rows
277
+
246
278
  result = send_command("clients.list", profile=browser_alias, remote=remote, key=key)
247
279
  for item in result or []:
248
280
  row = dict(item)
@@ -5,8 +5,8 @@ import uuid
5
5
  from typing import Any
6
6
 
7
7
  from browser_cli import transport
8
- from browser_cli.endpoints import _normalize_endpoint
9
8
  from browser_cli.errors import BrowserNotConnected
9
+ from browser_cli.remote.registry import resolve_remote_endpoint
10
10
 
11
11
  def base_message(command: str, args: dict | None) -> dict:
12
12
  return {"id": str(uuid.uuid4()), "command": command, "args": args or {}}
@@ -14,7 +14,7 @@ def base_message(command: str, args: dict | None) -> dict:
14
14
  def requested_target(profile: str | None, remote: str | None) -> tuple[str | None, str | None]:
15
15
  requested_profile = profile or os.environ.get("BROWSER_CLI_PROFILE")
16
16
  remote_endpoint = remote or os.environ.get("BROWSER_CLI_REMOTE")
17
- return requested_profile, _normalize_endpoint(remote_endpoint) if remote_endpoint else None
17
+ return requested_profile, resolve_remote_endpoint(remote_endpoint) if remote_endpoint else None
18
18
 
19
19
  def encode_payload(msg: dict) -> bytes:
20
20
  return json.dumps(msg).encode("utf-8")
@@ -1,7 +1,7 @@
1
1
  import json
2
2
 
3
3
  import click
4
- from browser_cli.commands import client_from_ctx, handle_errors
4
+ from browser_cli.commands import client_from_ctx, handle_errors, tab_option
5
5
  from rich.console import Console
6
6
  from rich.table import Table
7
7
 
@@ -12,10 +12,11 @@ def extract_group():
12
12
  """Extract content from the active tab."""
13
13
 
14
14
  @extract_group.command("links")
15
+ @tab_option
15
16
  @handle_errors
16
- def extract_links():
17
- """Extract all links from the active tab."""
18
- links = client_from_ctx().extract.links()
17
+ def extract_links(tab_id):
18
+ """Extract all links from the active tab or --tab."""
19
+ links = client_from_ctx().extract.links(tab_id)
19
20
  if not links:
20
21
  console.print("[yellow]No links found[/yellow]")
21
22
  return
@@ -27,10 +28,11 @@ def extract_links():
27
28
  console.print(table)
28
29
 
29
30
  @extract_group.command("images")
31
+ @tab_option
30
32
  @handle_errors
31
- def extract_images():
32
- """Extract all images from the active tab."""
33
- images = client_from_ctx().extract.images()
33
+ def extract_images(tab_id):
34
+ """Extract all images from the active tab or --tab."""
35
+ images = client_from_ctx().extract.images(tab_id)
34
36
  if not images:
35
37
  console.print("[yellow]No images found[/yellow]")
36
38
  return
@@ -42,29 +44,33 @@ def extract_images():
42
44
  console.print(table)
43
45
 
44
46
  @extract_group.command("text")
47
+ @tab_option
45
48
  @handle_errors
46
- def extract_text():
47
- """Extract all visible text from the active tab."""
48
- console.print(client_from_ctx().extract.text())
49
+ def extract_text(tab_id):
50
+ """Extract all visible text from the active tab or --tab."""
51
+ console.print(client_from_ctx().extract.text(tab_id))
49
52
 
50
53
  @extract_group.command("json")
51
54
  @click.argument("selector")
55
+ @tab_option
52
56
  @handle_errors
53
- def extract_json(selector):
54
- """Parse and pretty-print JSON content inside SELECTOR."""
55
- data = client_from_ctx().extract.json(selector)
57
+ def extract_json(selector, tab_id):
58
+ """Parse and pretty-print JSON content inside SELECTOR in the active tab or --tab."""
59
+ data = client_from_ctx().extract.json(selector, tab_id)
56
60
  console.print_json(json.dumps(data))
57
61
 
58
62
  @extract_group.command("html")
63
+ @tab_option
59
64
  @handle_errors
60
- def extract_html():
61
- """Print the full HTML of the active tab to stdout."""
62
- click.echo(client_from_ctx().extract.html())
65
+ def extract_html(tab_id):
66
+ """Print the full HTML of the active tab or --tab to stdout."""
67
+ click.echo(client_from_ctx().extract.html(tab_id))
63
68
 
64
69
  @extract_group.command("markdown")
65
70
  @click.option("--selector", help="Extract only the DOM subtree matching this CSS selector.")
71
+ @tab_option
66
72
  @handle_errors
67
- def extract_markdown(selector):
68
- """Extract the page's main content as Markdown."""
69
- markdown = client_from_ctx().extract.markdown(selector)
73
+ def extract_markdown(selector, tab_id):
74
+ """Extract the page's main content as Markdown from the active tab or --tab."""
75
+ markdown = client_from_ctx().extract.markdown(selector, tab_id)
70
76
  click.echo(markdown or "", nl=not (markdown or "").endswith("\n"))
@@ -1,5 +1,5 @@
1
1
  import click
2
- from browser_cli.commands import client_from_ctx, handle_errors
2
+ from browser_cli.commands import client_from_ctx, handle_errors, tab_option
3
3
  from rich.console import Console
4
4
  from rich.table import Table
5
5
 
@@ -10,10 +10,11 @@ def page_group():
10
10
  """Inspect current page metadata."""
11
11
 
12
12
  @page_group.command("info")
13
+ @tab_option
13
14
  @handle_errors
14
- def page_info():
15
- """Show title, URL, readyState, language, and meta tags of the active tab."""
16
- info = client_from_ctx().page.info()
15
+ def page_info(tab_id):
16
+ """Show title, URL, readyState, language, and meta tags of the active tab or --tab."""
17
+ info = client_from_ctx().page.info(tab_id)
17
18
  table = Table(show_header=False)
18
19
  table.add_column("Field", style="bold cyan", no_wrap=True)
19
20
  table.add_column("Value")