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.
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/.gitea/workflows/testing.yml +2 -2
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/CLAUDE.md +1 -1
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/PKG-INFO +99 -4
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/README.md +95 -2
- real_browser_cli-0.16.8/browser_cli/auth/server_identity.py +57 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/client/core.py +37 -5
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/client/messages.py +2 -2
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/extract.py +25 -19
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/page.py +5 -4
- real_browser_cli-0.16.8/browser_cli/commands/remote.py +153 -0
- real_browser_cli-0.16.8/browser_cli/compat/auth.py +30 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/compat/commands.py +19 -16
- real_browser_cli-0.16.8/browser_cli/mcp/__init__.py +5 -0
- real_browser_cli-0.16.8/browser_cli/mcp/naming.py +39 -0
- real_browser_cli-0.16.8/browser_cli/mcp/serialization.py +19 -0
- real_browser_cli-0.16.8/browser_cli/mcp/server.py +292 -0
- real_browser_cli-0.16.8/browser_cli/mcp/targets.py +16 -0
- real_browser_cli-0.16.8/browser_cli/remote/known_hosts.py +108 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/remote/pool.py +23 -0
- real_browser_cli-0.16.8/browser_cli/remote/registry.py +100 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/remote/transport.py +7 -1
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/sdk/dom.py +25 -23
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/serve/challenge.py +4 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/serve/security.py +30 -1
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/manifest.json +1 -1
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/classes/JobManager.ts +10 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/commands/dom.ts +2 -2
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/commands/perf.ts +15 -11
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/commands/tabs-query.ts +22 -2
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/commands/windows.ts +29 -2
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/test/jobs.test.ts +16 -1
- real_browser_cli-0.16.8/extension/test/url-match.test.ts +43 -0
- real_browser_cli-0.16.8/extension/test/windows.test.ts +69 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/n8n-nodes-browser-cli/README.md +42 -7
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/n8n-nodes-browser-cli/credentials/BrowserCliApi.credentials.ts +17 -0
- real_browser_cli-0.16.8/n8n-nodes-browser-cli/nodes/BrowserCli/BrowserCli.node.ts +870 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/n8n-nodes-browser-cli/nodes/BrowserCli/browserCli.svg +1 -1
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/n8n-nodes-browser-cli/nodes/BrowserCli/protocol.ts +78 -0
- real_browser_cli-0.16.8/n8n-nodes-browser-cli/nodes/BrowserCli/request.ts +303 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/n8n-nodes-browser-cli/nodes/BrowserCli/serveClient.ts +12 -2
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/n8n-nodes-browser-cli/package-lock.json +1385 -292
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/n8n-nodes-browser-cli/package.json +6 -6
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/n8n-nodes-browser-cli/test/protocol.test.ts +39 -0
- real_browser_cli-0.16.8/n8n-nodes-browser-cli/test/request.test.ts +189 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/n8n-nodes-browser-cli/test-dist/protocol.test.mjs +91 -0
- real_browser_cli-0.16.8/n8n-nodes-browser-cli/test-dist/request.test.mjs +413 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/n8n-nodes-browser-cli/tsconfig.json +2 -2
- real_browser_cli-0.16.8/package-lock.json +917 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/package.json +3 -3
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/pyproject.toml +5 -1
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/conftest.py +12 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_api.py +14 -3
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_cli.py +52 -13
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_client.py +66 -15
- real_browser_cli-0.16.8/tests/test_compat.py +48 -0
- real_browser_cli-0.16.8/tests/test_known_hosts.py +64 -0
- real_browser_cli-0.16.8/tests/test_mcp.py +305 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_remote_pool.py +33 -0
- real_browser_cli-0.16.8/tests/test_remote_registry.py +59 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_serve.py +0 -27
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_serve_security.py +17 -0
- real_browser_cli-0.16.8/uv.lock +1559 -0
- real_browser_cli-0.16.3/browser_cli/commands/remote.py +0 -78
- real_browser_cli-0.16.3/browser_cli/compat/auth.py +0 -50
- real_browser_cli-0.16.3/browser_cli/remote/registry.py +0 -53
- real_browser_cli-0.16.3/n8n-nodes-browser-cli/nodes/BrowserCli/BrowserCli.node.ts +0 -382
- real_browser_cli-0.16.3/n8n-nodes-browser-cli/nodes/BrowserCli/request.ts +0 -129
- real_browser_cli-0.16.3/n8n-nodes-browser-cli/test/request.test.ts +0 -73
- real_browser_cli-0.16.3/n8n-nodes-browser-cli/test-dist/request.test.mjs +0 -147
- real_browser_cli-0.16.3/package-lock.json +0 -556
- real_browser_cli-0.16.3/uv.lock +0 -705
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/.claude/settings.local.json +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/.gitea/workflows/package-extension.yml +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/.gitea/workflows/publish.yml +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/.gitignore +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/.python-version +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/LICENSE +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/PRIVACY.md +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/__init__.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/async_sdk.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/auth/__init__.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/auth/agent.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/auth/keys.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/auth/pq.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/auth/signing.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/cli.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/client/__init__.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/client/auth.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/client/targets.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/command_security.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/__init__.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/auth.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/clients.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/completion.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/doctor.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/dom.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/events.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/extension.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/groups.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/install.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/navigate.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/perf.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/raw.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/rendering/__init__.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/rendering/common.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/rendering/labels.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/rendering/tabs_tree.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/rendering/windows_tree.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/script.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/search.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/serve.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/serve_http.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/session.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/storage.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/tabs.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/watch.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/windows.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/commands/workspace.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/compat/__init__.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/constants.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/endpoints.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/errors.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/framing.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/local_transport.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/markdown/__init__.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/markdown/html.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/markdown/render.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/models.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/native/__init__.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/native/host.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/native/local_server.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/native/protocol.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/platform.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/registry.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/remote/__init__.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/remote/auth.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/remote/socket.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/sdk/__init__.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/sdk/base.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/sdk/browser_data.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/sdk/decorators.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/sdk/extension.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/sdk/factories.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/sdk/groups.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/sdk/navigation.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/sdk/perf.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/sdk/routing.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/sdk/session.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/sdk/tabs.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/sdk/windows.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/sdk/workflow_decorators.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/search/__init__.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/search/engines.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/serve/__init__.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/serve/auth.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/serve/control.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/serve/logging.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/serve/proxy.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/serve/runtime.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/transport/__init__.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/transport/binary.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/transport/codecs.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/browser_cli/version_manager.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/examples/demo.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/examples/demo.sh +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/content.js +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/icon.svg +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/icons/icon-128.png +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/icons/icon-16.png +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/icons/icon-32.png +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/icons/icon-48.png +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/browser-api.ts +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/classes/CommandGroup.ts +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/classes/CommandRegistry.ts +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/classes/NativeConnection.ts +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/commands/autosave.ts +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/commands/browser-data.ts +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/commands/extension.ts +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/commands/groups.ts +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/commands/injected.ts +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/commands/navigation.ts +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/commands/session-snapshot.ts +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/commands/session.ts +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/commands/tabs.ts +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/content/dispatch.ts +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/content/dom-ops.ts +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/content/extract.ts +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/content/markdown/code.ts +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/content/markdown/index.ts +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/content/markdown/renderer.ts +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/content/markdown/root.ts +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/content/markdown/utils.ts +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/content/page-info.ts +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/content-dispatch.ts +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/core/errors.ts +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/core/group-helpers.ts +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/core/index.ts +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/core/scripting.ts +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/core/storage.ts +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/core/tab-groups.ts +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/core/tab-helpers.ts +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/core/throttle.ts +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/index.ts +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/types/command-args.ts +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/types/index.ts +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/types/jobs.ts +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/types/json.ts +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/types/messages.ts +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/types/session.ts +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/types/tabs.ts +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/src/types/webextension.ts +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/test/autosave.test.ts +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/test/browser-api.test.ts +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/test/chrome-mock.ts +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/test/clients.test.ts +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/test/navigation.test.ts +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/extension/test/paging.test.ts +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/justfile +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/n8n-nodes-browser-cli/.claude/settings.local.json +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/n8n-nodes-browser-cli/.gitignore +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/n8n-nodes-browser-cli/scripts/copy-assets.mjs +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/n8n-nodes-browser-cli/scripts/run-tests.mjs +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/scripts/gen_extension_key.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/scripts/package_extension.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/scripts/package_verified_crx.sh +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/scripts/setup_verified_crx_key.sh +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/shell.nix +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_auth.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_commands_cli.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_dom.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_extension_error_page_handling.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_extension_packaging.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_extract.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_framing.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_groups.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_markdown_security.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_native_host.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_nav.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_new_feature_commands.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_page.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_perf.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_performance_integration.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_platform.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_refactor_boundaries.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_registry.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_remote_protocol_matrix.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_rendering.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_session.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_storage.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_tabs.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_transport.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tests/test_windows.py +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/tsconfig.json +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/webstore-assets/screenshots/screenshot-1.png +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/webstore-assets/screenshots/screenshot-2.png +0 -0
- {real_browser_cli-0.16.3 → real_browser_cli-0.16.8}/webstore-assets/screenshots/screenshot-3.png +0 -0
|
@@ -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
|
-
|
|
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.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: real-browser-cli
|
|
3
|
-
Version: 0.16.
|
|
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
|
|
294
|
-
browser-cli tabs filter youtube # list tabs
|
|
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
|
|
200
|
-
browser-cli tabs filter youtube # list tabs
|
|
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
|
-
|
|
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,
|
|
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")
|