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