real-browser-cli 0.16.2__tar.gz → 0.16.4__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 (242) hide show
  1. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/.gitea/workflows/testing.yml +2 -2
  2. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/PKG-INFO +16 -28
  3. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/README.md +15 -27
  4. real_browser_cli-0.16.4/browser_cli/auth/server_identity.py +57 -0
  5. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/client/auth.py +0 -4
  6. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/client/core.py +29 -0
  7. real_browser_cli-0.16.4/browser_cli/commands/remote.py +153 -0
  8. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/commands/serve_http.py +42 -12
  9. real_browser_cli-0.16.4/browser_cli/compat/auth.py +30 -0
  10. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/compat/commands.py +19 -16
  11. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/markdown/html.py +26 -4
  12. real_browser_cli-0.16.4/browser_cli/remote/known_hosts.py +108 -0
  13. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/remote/registry.py +28 -7
  14. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/remote/transport.py +7 -1
  15. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/serve/challenge.py +4 -0
  16. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/manifest.json +1 -1
  17. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/src/commands/tabs-query.ts +22 -2
  18. real_browser_cli-0.16.4/extension/test/url-match.test.ts +43 -0
  19. real_browser_cli-0.16.4/n8n-nodes-browser-cli/.claude/settings.local.json +7 -0
  20. real_browser_cli-0.16.4/n8n-nodes-browser-cli/.gitignore +4 -0
  21. real_browser_cli-0.16.4/n8n-nodes-browser-cli/README.md +124 -0
  22. real_browser_cli-0.16.4/n8n-nodes-browser-cli/credentials/BrowserCliApi.credentials.ts +102 -0
  23. real_browser_cli-0.16.4/n8n-nodes-browser-cli/nodes/BrowserCli/BrowserCli.node.ts +870 -0
  24. real_browser_cli-0.16.4/n8n-nodes-browser-cli/nodes/BrowserCli/browserCli.svg +31 -0
  25. real_browser_cli-0.16.4/n8n-nodes-browser-cli/nodes/BrowserCli/protocol.ts +380 -0
  26. real_browser_cli-0.16.4/n8n-nodes-browser-cli/nodes/BrowserCli/request.ts +303 -0
  27. real_browser_cli-0.16.4/n8n-nodes-browser-cli/nodes/BrowserCli/serveClient.ts +157 -0
  28. real_browser_cli-0.16.4/n8n-nodes-browser-cli/package-lock.json +1825 -0
  29. real_browser_cli-0.16.4/n8n-nodes-browser-cli/package.json +45 -0
  30. real_browser_cli-0.16.4/n8n-nodes-browser-cli/scripts/copy-assets.mjs +17 -0
  31. real_browser_cli-0.16.4/n8n-nodes-browser-cli/scripts/run-tests.mjs +27 -0
  32. real_browser_cli-0.16.4/n8n-nodes-browser-cli/test/protocol.test.ts +180 -0
  33. real_browser_cli-0.16.4/n8n-nodes-browser-cli/test/request.test.ts +189 -0
  34. real_browser_cli-0.16.4/n8n-nodes-browser-cli/test-dist/protocol.test.mjs +376 -0
  35. real_browser_cli-0.16.4/n8n-nodes-browser-cli/test-dist/request.test.mjs +413 -0
  36. real_browser_cli-0.16.4/n8n-nodes-browser-cli/tsconfig.json +19 -0
  37. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/package-lock.json +107 -107
  38. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/pyproject.toml +1 -1
  39. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/tests/test_client.py +53 -8
  40. real_browser_cli-0.16.4/tests/test_compat.py +48 -0
  41. real_browser_cli-0.16.4/tests/test_known_hosts.py +64 -0
  42. real_browser_cli-0.16.4/tests/test_markdown_security.py +50 -0
  43. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/tests/test_new_feature_commands.py +42 -0
  44. real_browser_cli-0.16.4/tests/test_remote_registry.py +42 -0
  45. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/tests/test_serve.py +42 -27
  46. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/tests/test_serve_security.py +45 -0
  47. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/uv.lock +166 -181
  48. real_browser_cli-0.16.2/browser_cli/commands/remote.py +0 -78
  49. real_browser_cli-0.16.2/browser_cli/compat/auth.py +0 -50
  50. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/.claude/settings.local.json +0 -0
  51. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/.gitea/workflows/package-extension.yml +0 -0
  52. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/.gitea/workflows/publish.yml +0 -0
  53. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/.gitignore +0 -0
  54. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/.python-version +0 -0
  55. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/CLAUDE.md +0 -0
  56. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/LICENSE +0 -0
  57. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/PRIVACY.md +0 -0
  58. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/__init__.py +0 -0
  59. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/async_sdk.py +0 -0
  60. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/auth/__init__.py +0 -0
  61. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/auth/agent.py +0 -0
  62. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/auth/keys.py +0 -0
  63. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/auth/pq.py +0 -0
  64. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/auth/signing.py +0 -0
  65. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/cli.py +0 -0
  66. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/client/__init__.py +0 -0
  67. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/client/messages.py +0 -0
  68. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/client/targets.py +0 -0
  69. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/command_security.py +0 -0
  70. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/commands/__init__.py +0 -0
  71. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/commands/auth.py +0 -0
  72. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/commands/clients.py +0 -0
  73. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/commands/completion.py +0 -0
  74. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/commands/doctor.py +0 -0
  75. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/commands/dom.py +0 -0
  76. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/commands/events.py +0 -0
  77. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/commands/extension.py +0 -0
  78. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/commands/extract.py +0 -0
  79. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/commands/groups.py +0 -0
  80. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/commands/install.py +0 -0
  81. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/commands/navigate.py +0 -0
  82. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/commands/page.py +0 -0
  83. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/commands/perf.py +0 -0
  84. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/commands/raw.py +0 -0
  85. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/commands/rendering/__init__.py +0 -0
  86. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/commands/rendering/common.py +0 -0
  87. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/commands/rendering/labels.py +0 -0
  88. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/commands/rendering/tabs_tree.py +0 -0
  89. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/commands/rendering/windows_tree.py +0 -0
  90. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/commands/script.py +0 -0
  91. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/commands/search.py +0 -0
  92. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/commands/serve.py +0 -0
  93. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/commands/session.py +0 -0
  94. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/commands/storage.py +0 -0
  95. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/commands/tabs.py +0 -0
  96. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/commands/watch.py +0 -0
  97. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/commands/windows.py +0 -0
  98. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/commands/workspace.py +0 -0
  99. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/compat/__init__.py +0 -0
  100. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/constants.py +0 -0
  101. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/endpoints.py +0 -0
  102. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/errors.py +0 -0
  103. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/framing.py +0 -0
  104. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/local_transport.py +0 -0
  105. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/markdown/__init__.py +0 -0
  106. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/markdown/render.py +0 -0
  107. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/models.py +0 -0
  108. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/native/__init__.py +0 -0
  109. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/native/host.py +0 -0
  110. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/native/local_server.py +0 -0
  111. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/native/protocol.py +0 -0
  112. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/platform.py +0 -0
  113. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/registry.py +0 -0
  114. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/remote/__init__.py +0 -0
  115. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/remote/auth.py +0 -0
  116. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/remote/pool.py +0 -0
  117. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/remote/socket.py +0 -0
  118. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/sdk/__init__.py +0 -0
  119. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/sdk/base.py +0 -0
  120. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/sdk/browser_data.py +0 -0
  121. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/sdk/decorators.py +0 -0
  122. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/sdk/dom.py +0 -0
  123. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/sdk/extension.py +0 -0
  124. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/sdk/factories.py +0 -0
  125. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/sdk/groups.py +0 -0
  126. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/sdk/navigation.py +0 -0
  127. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/sdk/perf.py +0 -0
  128. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/sdk/routing.py +0 -0
  129. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/sdk/session.py +0 -0
  130. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/sdk/tabs.py +0 -0
  131. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/sdk/windows.py +0 -0
  132. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/sdk/workflow_decorators.py +0 -0
  133. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/search/__init__.py +0 -0
  134. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/search/engines.py +0 -0
  135. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/serve/__init__.py +0 -0
  136. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/serve/auth.py +0 -0
  137. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/serve/control.py +0 -0
  138. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/serve/logging.py +0 -0
  139. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/serve/proxy.py +0 -0
  140. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/serve/runtime.py +0 -0
  141. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/serve/security.py +0 -0
  142. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/transport/__init__.py +0 -0
  143. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/transport/binary.py +0 -0
  144. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/transport/codecs.py +0 -0
  145. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/browser_cli/version_manager.py +0 -0
  146. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/examples/demo.py +0 -0
  147. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/examples/demo.sh +0 -0
  148. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/content.js +0 -0
  149. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/icon.svg +0 -0
  150. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/icons/icon-128.png +0 -0
  151. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/icons/icon-16.png +0 -0
  152. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/icons/icon-32.png +0 -0
  153. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/icons/icon-48.png +0 -0
  154. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/src/browser-api.ts +0 -0
  155. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/src/classes/CommandGroup.ts +0 -0
  156. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/src/classes/CommandRegistry.ts +0 -0
  157. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/src/classes/JobManager.ts +0 -0
  158. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/src/classes/NativeConnection.ts +0 -0
  159. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/src/commands/autosave.ts +0 -0
  160. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/src/commands/browser-data.ts +0 -0
  161. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/src/commands/dom.ts +0 -0
  162. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/src/commands/extension.ts +0 -0
  163. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/src/commands/groups.ts +0 -0
  164. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/src/commands/injected.ts +0 -0
  165. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/src/commands/navigation.ts +0 -0
  166. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/src/commands/perf.ts +0 -0
  167. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/src/commands/session-snapshot.ts +0 -0
  168. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/src/commands/session.ts +0 -0
  169. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/src/commands/tabs.ts +0 -0
  170. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/src/commands/windows.ts +0 -0
  171. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/src/content/dispatch.ts +0 -0
  172. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/src/content/dom-ops.ts +0 -0
  173. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/src/content/extract.ts +0 -0
  174. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/src/content/markdown/code.ts +0 -0
  175. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/src/content/markdown/index.ts +0 -0
  176. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/src/content/markdown/renderer.ts +0 -0
  177. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/src/content/markdown/root.ts +0 -0
  178. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/src/content/markdown/utils.ts +0 -0
  179. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/src/content/page-info.ts +0 -0
  180. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/src/content-dispatch.ts +0 -0
  181. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/src/core/errors.ts +0 -0
  182. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/src/core/group-helpers.ts +0 -0
  183. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/src/core/index.ts +0 -0
  184. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/src/core/scripting.ts +0 -0
  185. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/src/core/storage.ts +0 -0
  186. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/src/core/tab-groups.ts +0 -0
  187. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/src/core/tab-helpers.ts +0 -0
  188. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/src/core/throttle.ts +0 -0
  189. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/src/index.ts +0 -0
  190. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/src/types/command-args.ts +0 -0
  191. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/src/types/index.ts +0 -0
  192. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/src/types/jobs.ts +0 -0
  193. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/src/types/json.ts +0 -0
  194. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/src/types/messages.ts +0 -0
  195. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/src/types/session.ts +0 -0
  196. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/src/types/tabs.ts +0 -0
  197. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/src/types/webextension.ts +0 -0
  198. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/test/autosave.test.ts +0 -0
  199. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/test/browser-api.test.ts +0 -0
  200. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/test/chrome-mock.ts +0 -0
  201. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/test/clients.test.ts +0 -0
  202. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/test/jobs.test.ts +0 -0
  203. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/test/navigation.test.ts +0 -0
  204. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/extension/test/paging.test.ts +0 -0
  205. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/justfile +0 -0
  206. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/package.json +0 -0
  207. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/scripts/gen_extension_key.py +0 -0
  208. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/scripts/package_extension.py +0 -0
  209. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/scripts/package_verified_crx.sh +0 -0
  210. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/scripts/setup_verified_crx_key.sh +0 -0
  211. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/shell.nix +0 -0
  212. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/tests/conftest.py +0 -0
  213. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/tests/test_api.py +0 -0
  214. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/tests/test_auth.py +0 -0
  215. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/tests/test_cli.py +0 -0
  216. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/tests/test_commands_cli.py +0 -0
  217. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/tests/test_dom.py +0 -0
  218. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/tests/test_extension_error_page_handling.py +0 -0
  219. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/tests/test_extension_packaging.py +0 -0
  220. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/tests/test_extract.py +0 -0
  221. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/tests/test_framing.py +0 -0
  222. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/tests/test_groups.py +0 -0
  223. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/tests/test_native_host.py +0 -0
  224. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/tests/test_nav.py +0 -0
  225. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/tests/test_page.py +0 -0
  226. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/tests/test_perf.py +0 -0
  227. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/tests/test_performance_integration.py +0 -0
  228. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/tests/test_platform.py +0 -0
  229. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/tests/test_refactor_boundaries.py +0 -0
  230. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/tests/test_registry.py +0 -0
  231. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/tests/test_remote_pool.py +0 -0
  232. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/tests/test_remote_protocol_matrix.py +0 -0
  233. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/tests/test_rendering.py +0 -0
  234. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/tests/test_session.py +0 -0
  235. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/tests/test_storage.py +0 -0
  236. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/tests/test_tabs.py +0 -0
  237. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/tests/test_transport.py +0 -0
  238. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/tests/test_windows.py +0 -0
  239. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/tsconfig.json +0 -0
  240. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/webstore-assets/screenshots/screenshot-1.png +0 -0
  241. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/webstore-assets/screenshots/screenshot-2.png +0 -0
  242. {real_browser_cli-0.16.2 → real_browser_cli-0.16.4}/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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: real-browser-cli
3
- Version: 0.16.2
3
+ Version: 0.16.4
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
@@ -131,7 +131,6 @@ terminal / python script / remote client
131
131
  No local server needs to be running beforehand. The browser manages the native host's lifecycle. For cross-machine control, `browser-cli serve` starts an explicit TCP listener protected by Ed25519 public-key authentication unless you opt out with `--no-auth`.
132
132
 
133
133
  **Message format**
134
-
135
134
  Every command is a JSON object:
136
135
  ```json
137
136
  { "id": "uuid", "command": "tabs.list", "args": {} }
@@ -144,7 +143,6 @@ Every response:
144
143
  ---
145
144
 
146
145
  ## Installation
147
-
148
146
  **Requirements:** Python 3.10+, [uv](https://github.com/astral-sh/uv), Chrome, Chromium, Brave, Edge, Vivaldi, or Firefox
149
147
 
150
148
  browser-cli has two parts: the **CLI / native host** (a Python package) and the **browser extension** (published on the public stores).
@@ -203,7 +201,6 @@ Only the `browser-cli` command needs to be on your `PATH`. The browser launches
203
201
  ---
204
202
 
205
203
  ## Project structure
206
-
207
204
  ```text
208
205
  browser-cli/
209
206
  ├── browser_cli/
@@ -239,7 +236,6 @@ browser-cli/
239
236
  ---
240
237
 
241
238
  ## CLI reference
242
-
243
239
  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`.
244
240
 
245
241
  If exactly one browser instance is connected, commands auto-target it. Use `--browser ALIAS` when multiple browser instances are connected. `tabs list`, `tabs count`, `groups list`, `groups count`, `windows list`, and `session list` aggregate across all active browsers when `--browser` is omitted; in that mode they show the source browser alias or UUID. When local and saved remote browsers are mixed, tables group rows by source (`local` or the remote endpoint) and indent the browser profile below that group. You can inspect active instances with `browser-cli clients` and assign a persistent profile alias from inside the target browser with `browser-cli clients rename --browser <current-alias> <new-alias>`. Closed browsers are removed from the client registry automatically.
@@ -247,7 +243,6 @@ If exactly one browser instance is connected, commands auto-target it. Use `--br
247
243
  Important: profile aliases are browser-instance aliases, not window aliases. Window aliases created with `windows rename` are only for targeting windows in commands like `nav open --window work`. If a browser instance has no explicit profile alias set, the native host gives it a generated UUID alias so multiple unaliased browsers stay distinct.
248
244
 
249
245
  ### Navigation (`nav`)
250
-
251
246
  ```sh
252
247
  # Open a URL (no focus stealing by default)
253
248
  browser-cli nav open https://example.com
@@ -269,7 +264,6 @@ browser-cli nav focus github # focuses first tab whose URL contains "
269
264
  ```
270
265
 
271
266
  ### Search
272
-
273
267
  Each search command opens the search results in your browser using the same flags as `nav open`.
274
268
 
275
269
  ```sh
@@ -293,12 +287,12 @@ browser-cli search so click choices
293
287
  ```
294
288
 
295
289
  ### Tabs
296
-
297
290
  ```sh
298
291
  browser-cli tabs list # list all open tabs (all windows)
299
292
  browser-cli tabs count # count all tabs
300
- browser-cli tabs count youtube # count tabs matching URL pattern
301
- browser-cli tabs filter youtube # list tabs matching URL pattern
293
+ browser-cli tabs count youtube # count tabs whose URL contains "youtube"
294
+ browser-cli tabs filter youtube # list tabs whose URL contains "youtube"
295
+ browser-cli tabs filter 'twitch.tv/*' # glob: list every twitch.tv tab
302
296
  browser-cli tabs query "pull request" # search tabs by URL or title
303
297
 
304
298
  browser-cli tabs active 1234 # switch browser focus to tab
@@ -320,8 +314,11 @@ browser-cli tabs sort --by time
320
314
  browser-cli tabs merge-windows # pull all tabs into the current window
321
315
  ```
322
316
 
323
- ### Tab groups
317
+ > URL patterns for `tabs filter` / `tabs count` match against the full tab URL.
318
+ > A plain string is a case-sensitive substring (`youtube`); a pattern containing
319
+ > `*` or `?` is treated as a glob (`twitch.tv/*`, `*.twitch.tv`).
324
320
 
321
+ ### Tab groups
325
322
  ```sh
326
323
  browser-cli groups list # list all tab groups
327
324
  browser-cli groups count # count groups
@@ -343,7 +340,6 @@ browser-cli groups move 42 -l # short left alias
343
340
  ```
344
341
 
345
342
  ### Windows
346
-
347
343
  ```sh
348
344
  browser-cli windows list # list all windows
349
345
  browser-cli windows open # open a new window
@@ -353,7 +349,6 @@ browser-cli windows close 1 # close a window
353
349
  ```
354
350
 
355
351
  ### DOM
356
-
357
352
  These commands run on the **active tab**. The tab must be on a regular `http://` or `https://` page — not a browser internal page like `brave://newtab`.
358
353
 
359
354
  ```sh
@@ -366,7 +361,6 @@ browser-cli dom type "#search" "hello" # type text into an input
366
361
  ```
367
362
 
368
363
  ### Extract
369
-
370
364
  ```sh
371
365
  browser-cli extract links # all <a href> links on the page
372
366
  browser-cli extract images # all <img> tags (src + alt)
@@ -378,7 +372,6 @@ browser-cli extract markdown --selector "article" # specific DOM subtree as Ma
378
372
  ```
379
373
 
380
374
  ### Sessions
381
-
382
375
  A session is a snapshot of all open tab URLs, stored inside the extension via `chrome.storage.local`. Sessions survive browser restarts but are lost if the extension is uninstalled or extension data is cleared.
383
376
 
384
377
  ```sh
@@ -392,7 +385,6 @@ browser-cli session auto-save off
392
385
  ```
393
386
 
394
387
  ### Misc
395
-
396
388
  ```sh
397
389
  browser-cli clients # show connected browser info from the registry
398
390
  browser-cli clients rename --browser abcd1234 work # rename one connected browser instance
@@ -403,7 +395,6 @@ browser-cli completion zsh --script # output raw completion script
403
395
  ```
404
396
 
405
397
  ### Remote control, auth, and gateways
406
-
407
398
  ```sh
408
399
  # On the machine with the browser
409
400
  browser-cli auth keygen --output ~/.config/browser-cli/client.key
@@ -428,22 +419,24 @@ browser-cli serve-http --port 8766
428
419
  curl -H "Authorization: Bearer <token>" http://127.0.0.1:8766/tabs
429
420
  ```
430
421
 
431
- Remote auth uses Ed25519 challenge/response. `--remote` domains default to port 443; explicit `host:port` endpoints are also supported. Saved remote endpoints participate in aggregate list/count commands, where output is grouped by endpoint.
422
+ Remote auth uses Ed25519 challenge/response. `--remote` domains default to port 443; explicit `host:port` endpoints are also supported. Use `browser-cli remote trust ENDPOINT KEY` to remember a key for later calls. Saved remote endpoints participate in aggregate list/count commands, where output is grouped by endpoint.
432
423
 
433
- #### Security model
424
+ #### n8n integration
425
+ The n8n community node is published as [`n8n-nodes-browser-cli`](https://www.npmjs.com/package/n8n-nodes-browser-cli) on npm. It talks directly to a remote `browser-cli serve` endpoint over the same Ed25519-authenticated, ML-KEM-encrypted TCP protocol as the CLI remote client. Install it from n8n's Community Nodes UI, run `browser-cli serve` on the browser machine, paste the client key into the node credential, and drive tabs/DOM/extraction/raw commands from a workflow. See [`n8n-nodes-browser-cli/README.md`](n8n-nodes-browser-cli/README.md).
434
426
 
435
- - **`serve` (TCP)** authenticates every connection with an Ed25519 signature over a fresh server nonce and, for modern clients, wraps the transport in an ML-KEM-768 (post-quantum) AEAD channel. Commands are gated by a **safe-only policy by default** — even a trusted key can only run read-only status/listing commands until you open more with `--allow-read-page`, `--allow-control`, `--allow-dangerous`, or `--allow-all` (full control, including `dom.eval`/`storage.*`). `--no-auth` is rejected on non-loopback hosts.
436
- - **Per-key authorization:** a key in `authorized_keys` can carry an optional `allow:` token (`<pubkey> <name> allow:read-page,control`) listing its categories (`all`, `safe`, `read-page`, `control`, `dangerous`). That key uses its own policy, overriding the server-wide `--allow-*` default; keys without a token fall back to the default. Set it with `auth trust <pubkey> --allow-control …` (works locally and over `--remote`); `auth keys` shows each key's policy.
427
+ #### Security model
428
+ - **`serve` (TCP)** authenticates every connection with an Ed25519 signature over a fresh server nonce and, for modern clients, wraps the transport in an ML-KEM-768 (post-quantum) AEAD channel. Commands are gated by a **safe-only policy by default** — even a trusted key can only run read-only status/listing commands until you open more with `--allow-read-page`, `--allow-control`, `--allow-dangerous`, `--allow-keys`, or `--allow-all` (full control, including `dom.eval`/`storage.*`). `--no-auth` is rejected on non-loopback hosts.
429
+ - **Per-key authorization:** a key in `authorized_keys` can carry an optional `allow:` token (`<pubkey> <name> allow:read-page,control`) listing its categories (`all`, `safe`, `read-page`, `control`, `dangerous`, `keys`). That key uses its own policy, overriding the server-wide `--allow-*` default; keys without a token fall back to the default. Set it with `auth trust <pubkey> --allow-control …` when adding a key, or change it later with `auth policy <pubkey|name> …` (interactive picker when run with no args; `--safe`/`--server-default`/`--allow-*` for scripting). Both work locally and over `--remote`; `auth keys` shows each key's policy.
430
+ - **Key-management is its own category:** listing/trusting/repolicing keys (`auth keys`/`auth trust`/`auth policy` over `--remote`) requires the `keys` category. A key trusted only for browsing — even with full `control`+`dangerous` — cannot manage the trust store unless granted `allow:keys` (or `allow:all`). This prevents a compromised browser key from escalating by trusting its own.
437
431
  - **Rate limiting:** `--rate-limit N` caps commands/second per client key (token bucket, default `100`, `0` disables) so a compromised key can't hammer the browser.
438
432
  - **Audit logging:** request logs include the acting key (its name from `authorized_keys` plus a short pubkey), not just the client address.
439
- - **`serve-http`** is a convenience gateway with the inverse trade-off: commands are gated by the same `--allow-*` policy (safe-only by default), but the bearer token travels in **clear text over plain HTTP**. It binds to loopback by default; `--no-auth` is only permitted there. If you must expose it beyond loopback, put it behind a TLS-terminating reverse proxy — never send the token over an untrusted network unencrypted.
433
+ - **`serve-http`** is a convenience gateway with the inverse trade-off: commands are gated by the same `--allow-*` policy (safe-only by default) and requests are throttled per client address (`--rate-limit`, default `100`/s) with an 8 MB body cap, but the bearer token travels in **clear text over plain HTTP**. It binds to loopback by default; `--no-auth` is only permitted there, and binding beyond loopback prints a loud cleartext warning. If you must expose it, put it behind a TLS-terminating reverse proxy — never send the token over an untrusted network unencrypted, and prefer `serve` (encrypted) for real remote use.
440
434
 
441
435
  For low latency, an authenticated encrypted remote connection is kept open and reused for further commands in the same process — so SDK scripts and multi-browser fan-out avoid repeating the TCP/TLS/challenge handshake on every command. Aggregate commands also fan out to remote targets concurrently. Both degrade gracefully against older servers that handle one command per connection.
442
436
 
443
437
  ---
444
438
 
445
439
  ## Python SDK
446
-
447
440
  ```python
448
441
  from browser_cli import AsyncBrowserCLI, BrowserCLI
449
442
 
@@ -579,7 +572,6 @@ raw = b.command("tabs.count", {"pattern": "github"}) # escape hatch for raw com
579
572
  ```
580
573
 
581
574
  **Error handling**
582
-
583
575
  ```python
584
576
  from browser_cli import BrowserCLI, BrowserNotConnected
585
577
 
@@ -611,7 +603,6 @@ if isinstance(counts, BrowserCounts):
611
603
  ---
612
604
 
613
605
  ## Example scripts
614
-
615
606
  See `examples/demo.py` (Python) and `examples/demo.sh` (Bash) for full walkthroughs covering tabs, groups, DOM extraction, and session management.
616
607
 
617
608
  ```sh
@@ -622,7 +613,6 @@ bash examples/demo.sh
622
613
  ---
623
614
 
624
615
  ## Development
625
-
626
616
  ```sh
627
617
  npm ci
628
618
  npm run check:extension # type-check, build extension bundles, syntax-check bundle
@@ -663,7 +653,6 @@ For Firefox temporary testing via `about:debugging#/runtime/this-firefox`, run `
663
653
  ---
664
654
 
665
655
  ## Limitations
666
-
667
656
  - **Browser internal pages** (`chrome://`, `brave://`, `edge://`, `about:`) cannot be scripted. DOM and extract commands only work on regular `http://` and `https://` pages.
668
657
  - **Multiple browser instances can be auto-distinguished, but generated aliases are temporary**. Unaliased browsers get UUID aliases from the native host, which avoids collisions but is less ergonomic than setting a stable alias with `browser-cli clients rename --browser <current-alias> <new-alias>`.
669
658
  - **Supported install targets are explicit, not “all Chromium browsers”**. The installer currently supports Chrome, Chromium, Brave, Edge, Vivaldi, and Firefox. Other Chromium-based browsers may use different or shared native messaging manifest locations, so they need browser-specific verification before being added safely.
@@ -672,7 +661,6 @@ For Firefox temporary testing via `about:debugging#/runtime/this-firefox`, run `
672
661
  ---
673
662
 
674
663
  ## License
675
-
676
664
  PolyForm Noncommercial License 1.0.0. See [LICENSE](LICENSE).
677
665
 
678
666
  Commercial use is not permitted under this license. For commercial licensing, contact the project maintainer.
@@ -37,7 +37,6 @@ terminal / python script / remote client
37
37
  No local server needs to be running beforehand. The browser manages the native host's lifecycle. For cross-machine control, `browser-cli serve` starts an explicit TCP listener protected by Ed25519 public-key authentication unless you opt out with `--no-auth`.
38
38
 
39
39
  **Message format**
40
-
41
40
  Every command is a JSON object:
42
41
  ```json
43
42
  { "id": "uuid", "command": "tabs.list", "args": {} }
@@ -50,7 +49,6 @@ Every response:
50
49
  ---
51
50
 
52
51
  ## Installation
53
-
54
52
  **Requirements:** Python 3.10+, [uv](https://github.com/astral-sh/uv), Chrome, Chromium, Brave, Edge, Vivaldi, or Firefox
55
53
 
56
54
  browser-cli has two parts: the **CLI / native host** (a Python package) and the **browser extension** (published on the public stores).
@@ -109,7 +107,6 @@ Only the `browser-cli` command needs to be on your `PATH`. The browser launches
109
107
  ---
110
108
 
111
109
  ## Project structure
112
-
113
110
  ```text
114
111
  browser-cli/
115
112
  ├── browser_cli/
@@ -145,7 +142,6 @@ browser-cli/
145
142
  ---
146
143
 
147
144
  ## CLI reference
148
-
149
145
  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`.
150
146
 
151
147
  If exactly one browser instance is connected, commands auto-target it. Use `--browser ALIAS` when multiple browser instances are connected. `tabs list`, `tabs count`, `groups list`, `groups count`, `windows list`, and `session list` aggregate across all active browsers when `--browser` is omitted; in that mode they show the source browser alias or UUID. When local and saved remote browsers are mixed, tables group rows by source (`local` or the remote endpoint) and indent the browser profile below that group. You can inspect active instances with `browser-cli clients` and assign a persistent profile alias from inside the target browser with `browser-cli clients rename --browser <current-alias> <new-alias>`. Closed browsers are removed from the client registry automatically.
@@ -153,7 +149,6 @@ If exactly one browser instance is connected, commands auto-target it. Use `--br
153
149
  Important: profile aliases are browser-instance aliases, not window aliases. Window aliases created with `windows rename` are only for targeting windows in commands like `nav open --window work`. If a browser instance has no explicit profile alias set, the native host gives it a generated UUID alias so multiple unaliased browsers stay distinct.
154
150
 
155
151
  ### Navigation (`nav`)
156
-
157
152
  ```sh
158
153
  # Open a URL (no focus stealing by default)
159
154
  browser-cli nav open https://example.com
@@ -175,7 +170,6 @@ browser-cli nav focus github # focuses first tab whose URL contains "
175
170
  ```
176
171
 
177
172
  ### Search
178
-
179
173
  Each search command opens the search results in your browser using the same flags as `nav open`.
180
174
 
181
175
  ```sh
@@ -199,12 +193,12 @@ browser-cli search so click choices
199
193
  ```
200
194
 
201
195
  ### Tabs
202
-
203
196
  ```sh
204
197
  browser-cli tabs list # list all open tabs (all windows)
205
198
  browser-cli tabs count # count all tabs
206
- browser-cli tabs count youtube # count tabs matching URL pattern
207
- browser-cli tabs filter youtube # list tabs matching URL pattern
199
+ browser-cli tabs count youtube # count tabs whose URL contains "youtube"
200
+ browser-cli tabs filter youtube # list tabs whose URL contains "youtube"
201
+ browser-cli tabs filter 'twitch.tv/*' # glob: list every twitch.tv tab
208
202
  browser-cli tabs query "pull request" # search tabs by URL or title
209
203
 
210
204
  browser-cli tabs active 1234 # switch browser focus to tab
@@ -226,8 +220,11 @@ browser-cli tabs sort --by time
226
220
  browser-cli tabs merge-windows # pull all tabs into the current window
227
221
  ```
228
222
 
229
- ### Tab groups
223
+ > URL patterns for `tabs filter` / `tabs count` match against the full tab URL.
224
+ > A plain string is a case-sensitive substring (`youtube`); a pattern containing
225
+ > `*` or `?` is treated as a glob (`twitch.tv/*`, `*.twitch.tv`).
230
226
 
227
+ ### Tab groups
231
228
  ```sh
232
229
  browser-cli groups list # list all tab groups
233
230
  browser-cli groups count # count groups
@@ -249,7 +246,6 @@ browser-cli groups move 42 -l # short left alias
249
246
  ```
250
247
 
251
248
  ### Windows
252
-
253
249
  ```sh
254
250
  browser-cli windows list # list all windows
255
251
  browser-cli windows open # open a new window
@@ -259,7 +255,6 @@ browser-cli windows close 1 # close a window
259
255
  ```
260
256
 
261
257
  ### DOM
262
-
263
258
  These commands run on the **active tab**. The tab must be on a regular `http://` or `https://` page — not a browser internal page like `brave://newtab`.
264
259
 
265
260
  ```sh
@@ -272,7 +267,6 @@ browser-cli dom type "#search" "hello" # type text into an input
272
267
  ```
273
268
 
274
269
  ### Extract
275
-
276
270
  ```sh
277
271
  browser-cli extract links # all <a href> links on the page
278
272
  browser-cli extract images # all <img> tags (src + alt)
@@ -284,7 +278,6 @@ browser-cli extract markdown --selector "article" # specific DOM subtree as Ma
284
278
  ```
285
279
 
286
280
  ### Sessions
287
-
288
281
  A session is a snapshot of all open tab URLs, stored inside the extension via `chrome.storage.local`. Sessions survive browser restarts but are lost if the extension is uninstalled or extension data is cleared.
289
282
 
290
283
  ```sh
@@ -298,7 +291,6 @@ browser-cli session auto-save off
298
291
  ```
299
292
 
300
293
  ### Misc
301
-
302
294
  ```sh
303
295
  browser-cli clients # show connected browser info from the registry
304
296
  browser-cli clients rename --browser abcd1234 work # rename one connected browser instance
@@ -309,7 +301,6 @@ browser-cli completion zsh --script # output raw completion script
309
301
  ```
310
302
 
311
303
  ### Remote control, auth, and gateways
312
-
313
304
  ```sh
314
305
  # On the machine with the browser
315
306
  browser-cli auth keygen --output ~/.config/browser-cli/client.key
@@ -334,22 +325,24 @@ browser-cli serve-http --port 8766
334
325
  curl -H "Authorization: Bearer <token>" http://127.0.0.1:8766/tabs
335
326
  ```
336
327
 
337
- Remote auth uses Ed25519 challenge/response. `--remote` domains default to port 443; explicit `host:port` endpoints are also supported. Saved remote endpoints participate in aggregate list/count commands, where output is grouped by endpoint.
328
+ Remote auth uses Ed25519 challenge/response. `--remote` domains default to port 443; explicit `host:port` endpoints are also supported. Use `browser-cli remote trust ENDPOINT KEY` to remember a key for later calls. Saved remote endpoints participate in aggregate list/count commands, where output is grouped by endpoint.
338
329
 
339
- #### Security model
330
+ #### n8n integration
331
+ The n8n community node is published as [`n8n-nodes-browser-cli`](https://www.npmjs.com/package/n8n-nodes-browser-cli) on npm. It talks directly to a remote `browser-cli serve` endpoint over the same Ed25519-authenticated, ML-KEM-encrypted TCP protocol as the CLI remote client. Install it from n8n's Community Nodes UI, run `browser-cli serve` on the browser machine, paste the client key into the node credential, and drive tabs/DOM/extraction/raw commands from a workflow. See [`n8n-nodes-browser-cli/README.md`](n8n-nodes-browser-cli/README.md).
340
332
 
341
- - **`serve` (TCP)** authenticates every connection with an Ed25519 signature over a fresh server nonce and, for modern clients, wraps the transport in an ML-KEM-768 (post-quantum) AEAD channel. Commands are gated by a **safe-only policy by default** — even a trusted key can only run read-only status/listing commands until you open more with `--allow-read-page`, `--allow-control`, `--allow-dangerous`, or `--allow-all` (full control, including `dom.eval`/`storage.*`). `--no-auth` is rejected on non-loopback hosts.
342
- - **Per-key authorization:** a key in `authorized_keys` can carry an optional `allow:` token (`<pubkey> <name> allow:read-page,control`) listing its categories (`all`, `safe`, `read-page`, `control`, `dangerous`). That key uses its own policy, overriding the server-wide `--allow-*` default; keys without a token fall back to the default. Set it with `auth trust <pubkey> --allow-control …` (works locally and over `--remote`); `auth keys` shows each key's policy.
333
+ #### Security model
334
+ - **`serve` (TCP)** authenticates every connection with an Ed25519 signature over a fresh server nonce and, for modern clients, wraps the transport in an ML-KEM-768 (post-quantum) AEAD channel. Commands are gated by a **safe-only policy by default** — even a trusted key can only run read-only status/listing commands until you open more with `--allow-read-page`, `--allow-control`, `--allow-dangerous`, `--allow-keys`, or `--allow-all` (full control, including `dom.eval`/`storage.*`). `--no-auth` is rejected on non-loopback hosts.
335
+ - **Per-key authorization:** a key in `authorized_keys` can carry an optional `allow:` token (`<pubkey> <name> allow:read-page,control`) listing its categories (`all`, `safe`, `read-page`, `control`, `dangerous`, `keys`). That key uses its own policy, overriding the server-wide `--allow-*` default; keys without a token fall back to the default. Set it with `auth trust <pubkey> --allow-control …` when adding a key, or change it later with `auth policy <pubkey|name> …` (interactive picker when run with no args; `--safe`/`--server-default`/`--allow-*` for scripting). Both work locally and over `--remote`; `auth keys` shows each key's policy.
336
+ - **Key-management is its own category:** listing/trusting/repolicing keys (`auth keys`/`auth trust`/`auth policy` over `--remote`) requires the `keys` category. A key trusted only for browsing — even with full `control`+`dangerous` — cannot manage the trust store unless granted `allow:keys` (or `allow:all`). This prevents a compromised browser key from escalating by trusting its own.
343
337
  - **Rate limiting:** `--rate-limit N` caps commands/second per client key (token bucket, default `100`, `0` disables) so a compromised key can't hammer the browser.
344
338
  - **Audit logging:** request logs include the acting key (its name from `authorized_keys` plus a short pubkey), not just the client address.
345
- - **`serve-http`** is a convenience gateway with the inverse trade-off: commands are gated by the same `--allow-*` policy (safe-only by default), but the bearer token travels in **clear text over plain HTTP**. It binds to loopback by default; `--no-auth` is only permitted there. If you must expose it beyond loopback, put it behind a TLS-terminating reverse proxy — never send the token over an untrusted network unencrypted.
339
+ - **`serve-http`** is a convenience gateway with the inverse trade-off: commands are gated by the same `--allow-*` policy (safe-only by default) and requests are throttled per client address (`--rate-limit`, default `100`/s) with an 8 MB body cap, but the bearer token travels in **clear text over plain HTTP**. It binds to loopback by default; `--no-auth` is only permitted there, and binding beyond loopback prints a loud cleartext warning. If you must expose it, put it behind a TLS-terminating reverse proxy — never send the token over an untrusted network unencrypted, and prefer `serve` (encrypted) for real remote use.
346
340
 
347
341
  For low latency, an authenticated encrypted remote connection is kept open and reused for further commands in the same process — so SDK scripts and multi-browser fan-out avoid repeating the TCP/TLS/challenge handshake on every command. Aggregate commands also fan out to remote targets concurrently. Both degrade gracefully against older servers that handle one command per connection.
348
342
 
349
343
  ---
350
344
 
351
345
  ## Python SDK
352
-
353
346
  ```python
354
347
  from browser_cli import AsyncBrowserCLI, BrowserCLI
355
348
 
@@ -485,7 +478,6 @@ raw = b.command("tabs.count", {"pattern": "github"}) # escape hatch for raw com
485
478
  ```
486
479
 
487
480
  **Error handling**
488
-
489
481
  ```python
490
482
  from browser_cli import BrowserCLI, BrowserNotConnected
491
483
 
@@ -517,7 +509,6 @@ if isinstance(counts, BrowserCounts):
517
509
  ---
518
510
 
519
511
  ## Example scripts
520
-
521
512
  See `examples/demo.py` (Python) and `examples/demo.sh` (Bash) for full walkthroughs covering tabs, groups, DOM extraction, and session management.
522
513
 
523
514
  ```sh
@@ -528,7 +519,6 @@ bash examples/demo.sh
528
519
  ---
529
520
 
530
521
  ## Development
531
-
532
522
  ```sh
533
523
  npm ci
534
524
  npm run check:extension # type-check, build extension bundles, syntax-check bundle
@@ -569,7 +559,6 @@ For Firefox temporary testing via `about:debugging#/runtime/this-firefox`, run `
569
559
  ---
570
560
 
571
561
  ## Limitations
572
-
573
562
  - **Browser internal pages** (`chrome://`, `brave://`, `edge://`, `about:`) cannot be scripted. DOM and extract commands only work on regular `http://` and `https://` pages.
574
563
  - **Multiple browser instances can be auto-distinguished, but generated aliases are temporary**. Unaliased browsers get UUID aliases from the native host, which avoids collisions but is less ergonomic than setting a stable alias with `browser-cli clients rename --browser <current-alias> <new-alias>`.
575
564
  - **Supported install targets are explicit, not “all Chromium browsers”**. The installer currently supports Chrome, Chromium, Brave, Edge, Vivaldi, and Firefox. Other Chromium-based browsers may use different or shared native messaging manifest locations, so they need browser-specific verification before being added safely.
@@ -578,7 +567,6 @@ For Firefox temporary testing via `about:debugging#/runtime/this-firefox`, run `
578
567
  ---
579
568
 
580
569
  ## License
581
-
582
570
  PolyForm Noncommercial License 1.0.0. See [LICENSE](LICENSE).
583
571
 
584
572
  Commercial use is not permitted under this license. For commercial licensing, contact the project maintainer.
@@ -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
@@ -35,8 +35,6 @@ def add_remote_auth_fields(msg: dict, command: str, requested_profile: str | Non
35
35
  msg["accept_encoding"] = transport.client_accept_encoding()
36
36
  key_spec = key if key is not None else remote_registry.key_for_remote(remote_endpoint)
37
37
  private_key = load_private_key(key_spec)
38
- if key is not None:
39
- remote_registry.save_remote_key(remote_endpoint, str(key))
40
38
 
41
39
  route_profile = requested_profile
42
40
  if not route_profile and command not in NO_ROUTE_COMMANDS:
@@ -52,8 +50,6 @@ async def add_remote_auth_fields_async(msg: dict, command: str, requested_profil
52
50
  msg["accept_encoding"] = transport.client_accept_encoding()
53
51
  key_spec = key if key is not None else await asyncio.to_thread(remote_registry.key_for_remote, remote_endpoint)
54
52
  private_key = await asyncio.to_thread(load_private_key, key_spec)
55
- if key is not None:
56
- await asyncio.to_thread(remote_registry.save_remote_key, remote_endpoint, str(key))
57
53
 
58
54
  route_profile = requested_profile
59
55
  if not route_profile and command not in NO_ROUTE_COMMANDS:
@@ -243,6 +243,35 @@ def collect_browser_clients(
243
243
  return rows
244
244
 
245
245
  if remote:
246
+ targets = remote_browser_targets(remote, key=key)
247
+ if browser_alias:
248
+ targets = [target for target in targets if target.profile == browser_alias or target.display_name == browser_alias]
249
+ if targets:
250
+ uncached = []
251
+ for target in targets:
252
+ cached = _cached_client_row(target)
253
+ if cached is not None:
254
+ rows.append(cached)
255
+ else:
256
+ uncached.append(target)
257
+ results = _run_concurrent([
258
+ (lambda t=t: _client_rows_async(
259
+ t.display_name,
260
+ profile=t.profile,
261
+ remote=remote,
262
+ key=key,
263
+ profile_group=t.display_group,
264
+ ))
265
+ for t in uncached
266
+ ])
267
+ for result in results:
268
+ if isinstance(result, (BrowserNotConnected, RuntimeError)):
269
+ continue
270
+ if isinstance(result, BaseException):
271
+ raise result
272
+ rows.extend(result)
273
+ return rows
274
+
246
275
  result = send_command("clients.list", profile=browser_alias, remote=remote, key=key)
247
276
  for item in result or []:
248
277
  row = dict(item)