real-browser-cli 0.15.5__tar.gz → 0.16.2__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.15.5 → real_browser_cli-0.16.2}/PKG-INFO +96 -62
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/README.md +94 -61
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/auth/__init__.py +6 -0
- real_browser_cli-0.16.2/browser_cli/auth/keys.py +125 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/cli.py +1 -19
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/client/__init__.py +2 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/client/core.py +203 -8
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/client/targets.py +5 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/command_security.py +16 -2
- real_browser_cli-0.16.2/browser_cli/commands/__init__.py +155 -0
- real_browser_cli-0.16.2/browser_cli/commands/auth.py +436 -0
- real_browser_cli-0.16.2/browser_cli/commands/clients.py +106 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/commands/doctor.py +3 -20
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/commands/groups.py +9 -23
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/commands/install.py +38 -17
- real_browser_cli-0.16.2/browser_cli/commands/raw.py +21 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/commands/remote.py +18 -8
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/commands/rendering/__init__.py +2 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/commands/rendering/common.py +53 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/commands/script.py +5 -7
- real_browser_cli-0.16.2/browser_cli/commands/search.py +28 -0
- real_browser_cli-0.16.2/browser_cli/commands/serve.py +194 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/commands/serve_http.py +8 -7
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/commands/session.py +10 -14
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/commands/tabs.py +4 -7
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/commands/windows.py +4 -7
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/compat/auth.py +8 -2
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/constants.py +20 -2
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/models.py +20 -23
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/native/host.py +6 -4
- real_browser_cli-0.16.2/browser_cli/remote/pool.py +103 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/remote/socket.py +7 -2
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/remote/transport.py +28 -2
- real_browser_cli-0.16.2/browser_cli/sdk/factories.py +124 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/sdk/navigation.py +1 -1
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/sdk/routing.py +28 -11
- real_browser_cli-0.16.2/browser_cli/search/__init__.py +1 -0
- real_browser_cli-0.16.2/browser_cli/search/engines.py +53 -0
- real_browser_cli-0.16.2/browser_cli/serve/control.py +126 -0
- real_browser_cli-0.16.2/browser_cli/serve/logging.py +24 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/serve/proxy.py +5 -4
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/serve/runtime.py +87 -6
- real_browser_cli-0.16.2/browser_cli/serve/security.py +115 -0
- real_browser_cli-0.16.2/browser_cli/version_manager.py +33 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/manifest.json +1 -1
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/src/classes/CommandRegistry.ts +19 -3
- real_browser_cli-0.16.2/extension/test/paging.test.ts +41 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/justfile +12 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/pyproject.toml +2 -1
- real_browser_cli-0.16.2/tests/conftest.py +58 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/tests/test_api.py +51 -16
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/tests/test_cli.py +174 -18
- real_browser_cli-0.16.2/tests/test_client.py +684 -0
- real_browser_cli-0.16.2/tests/test_native_host.py +440 -0
- real_browser_cli-0.16.2/tests/test_new_feature_commands.py +405 -0
- real_browser_cli-0.16.2/tests/test_remote_pool.py +52 -0
- real_browser_cli-0.16.2/tests/test_remote_protocol_matrix.py +277 -0
- real_browser_cli-0.16.2/tests/test_serve.py +652 -0
- real_browser_cli-0.16.2/tests/test_serve_security.py +163 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/uv.lock +36 -1
- real_browser_cli-0.15.5/browser_cli/auth/keys.py +0 -59
- real_browser_cli-0.15.5/browser_cli/commands/__init__.py +0 -81
- real_browser_cli-0.15.5/browser_cli/commands/auth.py +0 -157
- real_browser_cli-0.15.5/browser_cli/commands/clients.py +0 -173
- real_browser_cli-0.15.5/browser_cli/commands/raw.py +0 -23
- real_browser_cli-0.15.5/browser_cli/commands/search.py +0 -79
- real_browser_cli-0.15.5/browser_cli/commands/serve.py +0 -117
- real_browser_cli-0.15.5/browser_cli/sdk/factories.py +0 -117
- real_browser_cli-0.15.5/browser_cli/serve/control.py +0 -67
- real_browser_cli-0.15.5/browser_cli/serve/logging.py +0 -16
- real_browser_cli-0.15.5/browser_cli/version_manager.py +0 -17
- real_browser_cli-0.15.5/tests/conftest.py +0 -52
- real_browser_cli-0.15.5/tests/test_client.py +0 -559
- real_browser_cli-0.15.5/tests/test_native_host.py +0 -420
- real_browser_cli-0.15.5/tests/test_new_feature_commands.py +0 -188
- real_browser_cli-0.15.5/tests/test_remote_protocol_matrix.py +0 -213
- real_browser_cli-0.15.5/tests/test_serve.py +0 -530
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/.claude/settings.local.json +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/.gitea/workflows/package-extension.yml +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/.gitea/workflows/publish.yml +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/.gitea/workflows/testing.yml +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/.gitignore +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/.python-version +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/CLAUDE.md +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/LICENSE +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/PRIVACY.md +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/__init__.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/async_sdk.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/auth/agent.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/auth/pq.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/auth/signing.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/client/auth.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/client/messages.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/commands/completion.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/commands/dom.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/commands/events.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/commands/extension.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/commands/extract.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/commands/navigate.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/commands/page.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/commands/perf.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/commands/rendering/labels.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/commands/rendering/tabs_tree.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/commands/rendering/windows_tree.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/commands/storage.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/commands/watch.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/commands/workspace.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/compat/__init__.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/compat/commands.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/endpoints.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/errors.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/framing.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/local_transport.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/markdown/__init__.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/markdown/html.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/markdown/render.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/native/__init__.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/native/local_server.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/native/protocol.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/platform.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/registry.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/remote/__init__.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/remote/auth.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/remote/registry.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/sdk/__init__.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/sdk/base.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/sdk/browser_data.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/sdk/decorators.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/sdk/dom.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/sdk/extension.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/sdk/groups.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/sdk/perf.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/sdk/session.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/sdk/tabs.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/sdk/windows.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/sdk/workflow_decorators.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/serve/__init__.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/serve/auth.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/serve/challenge.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/transport/__init__.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/transport/binary.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/browser_cli/transport/codecs.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/examples/demo.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/examples/demo.sh +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/content.js +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/icon.svg +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/icons/icon-128.png +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/icons/icon-16.png +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/icons/icon-32.png +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/icons/icon-48.png +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/src/browser-api.ts +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/src/classes/CommandGroup.ts +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/src/classes/JobManager.ts +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/src/classes/NativeConnection.ts +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/src/commands/autosave.ts +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/src/commands/browser-data.ts +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/src/commands/dom.ts +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/src/commands/extension.ts +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/src/commands/groups.ts +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/src/commands/injected.ts +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/src/commands/navigation.ts +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/src/commands/perf.ts +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/src/commands/session-snapshot.ts +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/src/commands/session.ts +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/src/commands/tabs-query.ts +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/src/commands/tabs.ts +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/src/commands/windows.ts +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/src/content/dispatch.ts +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/src/content/dom-ops.ts +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/src/content/extract.ts +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/src/content/markdown/code.ts +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/src/content/markdown/index.ts +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/src/content/markdown/renderer.ts +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/src/content/markdown/root.ts +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/src/content/markdown/utils.ts +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/src/content/page-info.ts +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/src/content-dispatch.ts +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/src/core/errors.ts +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/src/core/group-helpers.ts +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/src/core/index.ts +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/src/core/scripting.ts +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/src/core/storage.ts +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/src/core/tab-groups.ts +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/src/core/tab-helpers.ts +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/src/core/throttle.ts +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/src/index.ts +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/src/types/command-args.ts +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/src/types/index.ts +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/src/types/jobs.ts +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/src/types/json.ts +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/src/types/messages.ts +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/src/types/session.ts +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/src/types/tabs.ts +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/src/types/webextension.ts +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/test/autosave.test.ts +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/test/browser-api.test.ts +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/test/chrome-mock.ts +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/test/clients.test.ts +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/test/jobs.test.ts +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/extension/test/navigation.test.ts +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/package-lock.json +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/package.json +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/scripts/gen_extension_key.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/scripts/package_extension.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/scripts/package_verified_crx.sh +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/scripts/setup_verified_crx_key.sh +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/shell.nix +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/tests/test_auth.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/tests/test_commands_cli.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/tests/test_dom.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/tests/test_extension_error_page_handling.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/tests/test_extension_packaging.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/tests/test_extract.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/tests/test_framing.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/tests/test_groups.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/tests/test_nav.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/tests/test_page.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/tests/test_perf.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/tests/test_performance_integration.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/tests/test_platform.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/tests/test_refactor_boundaries.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/tests/test_registry.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/tests/test_rendering.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/tests/test_session.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/tests/test_storage.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/tests/test_tabs.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/tests/test_transport.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/tests/test_windows.py +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/tsconfig.json +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/webstore-assets/screenshots/screenshot-1.png +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/webstore-assets/screenshots/screenshot-2.png +0 -0
- {real_browser_cli-0.15.5 → real_browser_cli-0.16.2}/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.
|
|
3
|
+
Version: 0.16.2
|
|
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
|
|
@@ -86,28 +86,30 @@ Requires-Python: >=3.10
|
|
|
86
86
|
Requires-Dist: click>=8
|
|
87
87
|
Requires-Dist: cryptography>=48
|
|
88
88
|
Requires-Dist: msgpack>=1
|
|
89
|
+
Requires-Dist: questionary>=2
|
|
89
90
|
Requires-Dist: rich>=13
|
|
90
91
|
Provides-Extra: fast
|
|
91
92
|
Requires-Dist: zstandard>=0.22; extra == 'fast'
|
|
92
93
|
Description-Content-Type: text/markdown
|
|
93
94
|
|
|
94
95
|
# browser-cli
|
|
95
|
-
Control your real, running browser from the terminal or
|
|
96
|
+
Control your real, running browser from the terminal, Python SDK, or a trusted remote client — no headless browser, no Playwright, no virtual display. Your actual open tabs, windows, and tab groups respond to your commands.
|
|
96
97
|
|
|
97
98
|
---
|
|
98
99
|
|
|
99
100
|
## What it does
|
|
100
101
|
You have 40 tabs open. You want to close all the duplicates, group the GitHub ones, save your session before a meeting, and open a few URLs into a specific group — all from a script. That is what browser-cli is for.
|
|
101
102
|
|
|
102
|
-
It works by pairing a small browser extension with a Python package that provides both a CLI and SDK. The extension has full access to your browser's tabs, windows, groups, and page DOM. The CLI and SDK talk to it in real time over a local IPC channel.
|
|
103
|
+
It works by pairing a small browser extension with a Python package that provides both a CLI and SDK. The extension has full access to your browser's tabs, windows, groups, and page DOM. The CLI and SDK talk to it in real time over a local IPC channel, or through the optional authenticated TCP remote bridge.
|
|
103
104
|
|
|
104
105
|
---
|
|
105
106
|
|
|
106
107
|
## How it works
|
|
107
108
|
```
|
|
108
|
-
terminal / python script
|
|
109
|
+
terminal / python script / remote client
|
|
109
110
|
│
|
|
110
111
|
│ Local IPC (Unix socket on Linux/macOS, named pipe on Windows)
|
|
112
|
+
│ or TCP remote bridge (Ed25519 auth, optional compression)
|
|
111
113
|
▼
|
|
112
114
|
Native Messaging Host (Python process, launched by the browser)
|
|
113
115
|
│
|
|
@@ -126,7 +128,7 @@ terminal / python script
|
|
|
126
128
|
4. CLI commands connect to that socket, send a JSON command, and wait for the result.
|
|
127
129
|
5. The native host relays the command to the extension via stdout, receives the result via stdin, and sends it back to the CLI.
|
|
128
130
|
|
|
129
|
-
No server needs to be running beforehand. The browser manages the native host's lifecycle.
|
|
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`.
|
|
130
132
|
|
|
131
133
|
**Message format**
|
|
132
134
|
|
|
@@ -145,8 +147,10 @@ Every response:
|
|
|
145
147
|
|
|
146
148
|
**Requirements:** Python 3.10+, [uv](https://github.com/astral-sh/uv), Chrome, Chromium, Brave, Edge, Vivaldi, or Firefox
|
|
147
149
|
|
|
150
|
+
browser-cli has two parts: the **CLI / native host** (a Python package) and the **browser extension** (published on the public stores).
|
|
151
|
+
|
|
148
152
|
### Install with uv
|
|
149
|
-
|
|
153
|
+
Install the CLI from PyPI as a uv tool, then register the native host:
|
|
150
154
|
|
|
151
155
|
```sh
|
|
152
156
|
uv tool install real-browser-cli
|
|
@@ -168,21 +172,31 @@ To upgrade later:
|
|
|
168
172
|
uv tool upgrade real-browser-cli
|
|
169
173
|
```
|
|
170
174
|
|
|
175
|
+
### Add the browser extension
|
|
176
|
+
Install the extension from its public store listing (the `install` command prints the right link for you):
|
|
177
|
+
|
|
178
|
+
- Chrome / Chromium / Brave / Edge / Vivaldi — [Chrome Web Store](https://chromewebstore.google.com/detail/browser-cli/hekaebjhbhhdbmakimmaklbblbmccahp)
|
|
179
|
+
- Firefox — [Firefox Add-ons](https://addons.mozilla.org/firefox/addon/browser-cli/)
|
|
180
|
+
|
|
181
|
+
The native host manifest trusts both the published store ID and the unpacked development ID, so the store extension works out of the box. If you are hacking on the extension yourself, run `browser-cli install <browser> --dev` for the unpacked / temporary-add-on load steps instead.
|
|
182
|
+
|
|
171
183
|
### Install from source
|
|
172
184
|
```sh
|
|
173
185
|
git clone <repo>
|
|
174
186
|
cd browser-cli
|
|
175
187
|
uv sync
|
|
176
|
-
|
|
188
|
+
npm ci && npm run build:extension # build the unpacked extension bundles
|
|
189
|
+
uv run browser-cli install brave --dev # --dev prints unpacked-load steps; or: chrome, chromium, edge, vivaldi, firefox
|
|
177
190
|
```
|
|
178
191
|
|
|
192
|
+
Omit `--dev` to be pointed at the public store listing instead of loading the unpacked build.
|
|
193
|
+
|
|
179
194
|
The `install` command will:
|
|
180
|
-
1.
|
|
181
|
-
2.
|
|
182
|
-
3.
|
|
183
|
-
4. Copy the native host into an internal `libexec` directory and create a small wrapper outside your `PATH`
|
|
195
|
+
1. Write the native messaging manifest to your OS so the browser can find the host
|
|
196
|
+
2. Copy the native host into an internal `libexec` directory and create a small wrapper outside your `PATH`
|
|
197
|
+
3. Print the public store link for installing the extension (or, with `--dev`, the unpacked / temporary-add-on load steps)
|
|
184
198
|
|
|
185
|
-
After install, **fully restart your browser** (Quit and reopen — not just close the window). The extension will connect to the native host automatically on startup.
|
|
199
|
+
After install, add the extension from the store link above and **fully restart your browser** (Quit and reopen — not just close the window). The extension will connect to the native host automatically on startup.
|
|
186
200
|
|
|
187
201
|
Only the `browser-cli` command needs to be on your `PATH`. The browser launches the native host wrapper directly from its absolute path in the native messaging manifest, and that wrapper imports the installed `browser_cli.native.host` entry point. On Windows the install command also registers the host in the current user's Registry for the selected browser.
|
|
188
202
|
|
|
@@ -193,62 +207,42 @@ Only the `browser-cli` command needs to be on your `PATH`. The browser launches
|
|
|
193
207
|
```text
|
|
194
208
|
browser-cli/
|
|
195
209
|
├── browser_cli/
|
|
196
|
-
│ ├── __init__.py
|
|
197
|
-
│ ├──
|
|
198
|
-
│ ├──
|
|
199
|
-
│
|
|
200
|
-
│
|
|
201
|
-
│
|
|
202
|
-
│
|
|
203
|
-
│ ├──
|
|
204
|
-
│ ├──
|
|
205
|
-
│
|
|
206
|
-
│
|
|
207
|
-
│
|
|
208
|
-
│
|
|
209
|
-
│ │ ├── transport.py # TCP/TLS remote transport
|
|
210
|
-
│ │ └── registry.py # Saved remote endpoints/keys
|
|
211
|
-
│ └── commands/
|
|
212
|
-
│ ├── navigate.py # nav open/reload/back/forward/focus
|
|
213
|
-
│ ├── search.py # search engine shortcuts
|
|
214
|
-
│ ├── tabs.py # tab management
|
|
215
|
-
│ ├── groups.py # tab group management
|
|
216
|
-
│ ├── windows.py # window management
|
|
217
|
-
│ ├── dom.py # DOM querying and interaction
|
|
218
|
-
│ ├── extract.py # content extraction
|
|
219
|
-
│ └── session.py # session save/load
|
|
210
|
+
│ ├── __init__.py # Public sync SDK: BrowserCLI and namespace wiring
|
|
211
|
+
│ ├── async_sdk.py # AsyncBrowserCLI
|
|
212
|
+
│ ├── cli.py # Click root command and native-host entry point
|
|
213
|
+
│ ├── client/ # send_command path, local/remote routing, message helpers
|
|
214
|
+
│ ├── sdk/ # SDK namespaces: nav, tabs, groups, windows, dom, session, ...
|
|
215
|
+
│ ├── commands/ # CLI presentation layer over the SDK namespaces
|
|
216
|
+
│ ├── native/ # Browser-launched Native Messaging host + local IPC server
|
|
217
|
+
│ ├── remote/ # TCP remote client transport and saved endpoint registry
|
|
218
|
+
│ ├── serve/ # Authenticated TCP server runtime
|
|
219
|
+
│ ├── transport/ # JSON/msgpack response encoding and compression helpers
|
|
220
|
+
│ ├── markdown/ # HTML-to-Markdown extraction helpers
|
|
221
|
+
│ ├── auth/ # Ed25519 keys, signing, SSH-agent/YubiKey helpers, PQ KEX
|
|
222
|
+
│ └── models.py # Tab, Group, BrowserCounts dataclasses
|
|
220
223
|
├── extension/
|
|
221
|
-
│ ├── manifest.json
|
|
222
|
-
│
|
|
223
|
-
│
|
|
224
|
-
│ ├──
|
|
225
|
-
│
|
|
226
|
-
├──
|
|
227
|
-
│
|
|
228
|
-
|
|
229
|
-
├──
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
│ ├── test_extract.py
|
|
235
|
-
│ ├── test_groups.py
|
|
236
|
-
│ ├── test_nav.py
|
|
237
|
-
│ ├── test_session.py
|
|
238
|
-
│ ├── test_tabs.py
|
|
239
|
-
│ └── test_windows.py
|
|
240
|
-
├── com.browsercli.host.json # native messaging manifest template
|
|
241
|
-
├── pyproject.toml # package metadata and CLI entry point
|
|
242
|
-
└── uv.lock # locked dependencies for uv
|
|
224
|
+
│ ├── manifest.json # Chromium MV3 manifest
|
|
225
|
+
│ └── src/ # TypeScript WebExtension source
|
|
226
|
+
│ ├── index.ts # Background/service-worker bundle entry
|
|
227
|
+
│ ├── content-dispatch.ts
|
|
228
|
+
│ ├── commands/ # Browser-side command implementations
|
|
229
|
+
│ ├── content/ # DOM/extract/Markdown logic injected into pages
|
|
230
|
+
│ └── core/ # Shared extension helpers
|
|
231
|
+
├── examples/ # Python and shell walkthroughs
|
|
232
|
+
├── scripts/ # Packaging and release helper scripts
|
|
233
|
+
├── tests/ # pytest suite
|
|
234
|
+
├── package.json # Extension build/test/package scripts
|
|
235
|
+
├── pyproject.toml # Python package metadata
|
|
236
|
+
└── uv.lock # locked Python dependencies
|
|
243
237
|
```
|
|
244
238
|
|
|
245
239
|
---
|
|
246
240
|
|
|
247
241
|
## CLI reference
|
|
248
242
|
|
|
249
|
-
|
|
243
|
+
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`.
|
|
250
244
|
|
|
251
|
-
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. You can inspect
|
|
245
|
+
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.
|
|
252
246
|
|
|
253
247
|
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.
|
|
254
248
|
|
|
@@ -408,6 +402,44 @@ browser-cli completion zsh # print setup instructions
|
|
|
408
402
|
browser-cli completion zsh --script # output raw completion script
|
|
409
403
|
```
|
|
410
404
|
|
|
405
|
+
### Remote control, auth, and gateways
|
|
406
|
+
|
|
407
|
+
```sh
|
|
408
|
+
# On the machine with the browser
|
|
409
|
+
browser-cli auth keygen --output ~/.config/browser-cli/client.key
|
|
410
|
+
PUBKEY=$(browser-cli auth show --key ~/.config/browser-cli/client.key | tail -n1)
|
|
411
|
+
browser-cli auth trust "$PUBKEY"
|
|
412
|
+
browser-cli serve --host 0.0.0.0 --port 8765 --authorized-keys ~/.config/browser-cli/authorized_keys
|
|
413
|
+
|
|
414
|
+
# Allow remote browser control (navigation, clicks); safe-only otherwise
|
|
415
|
+
browser-cli serve --authorized-keys ~/.config/browser-cli/authorized_keys --allow-control
|
|
416
|
+
|
|
417
|
+
# Per-key authorization (inline in authorized_keys) + a tighter rate limit
|
|
418
|
+
browser-cli auth trust "$PUBKEY" --name ci-bot --allow-read-page --allow-control
|
|
419
|
+
browser-cli serve --authorized-keys ~/.config/browser-cli/authorized_keys --rate-limit 20
|
|
420
|
+
|
|
421
|
+
# From another machine
|
|
422
|
+
browser-cli --remote browser-host.example:8765 --key ~/.config/browser-cli/client.key tabs list
|
|
423
|
+
browser-cli remote trust browser-host.example:8765 ~/.config/browser-cli/client.key
|
|
424
|
+
browser-cli --remote browser-host.example:8765 clients
|
|
425
|
+
|
|
426
|
+
# Local HTTP JSON gateway for small integrations
|
|
427
|
+
browser-cli serve-http --port 8766
|
|
428
|
+
curl -H "Authorization: Bearer <token>" http://127.0.0.1:8766/tabs
|
|
429
|
+
```
|
|
430
|
+
|
|
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.
|
|
432
|
+
|
|
433
|
+
#### Security model
|
|
434
|
+
|
|
435
|
+
- **`serve` (TCP)** authenticates every connection with an Ed25519 signature over a fresh server nonce and, for modern clients, wraps the transport in an ML-KEM-768 (post-quantum) AEAD channel. Commands are gated by a **safe-only policy by default** — even a trusted key can only run read-only status/listing commands until you open more with `--allow-read-page`, `--allow-control`, `--allow-dangerous`, or `--allow-all` (full control, including `dom.eval`/`storage.*`). `--no-auth` is rejected on non-loopback hosts.
|
|
436
|
+
- **Per-key authorization:** a key in `authorized_keys` can carry an optional `allow:` token (`<pubkey> <name> allow:read-page,control`) listing its categories (`all`, `safe`, `read-page`, `control`, `dangerous`). That key uses its own policy, overriding the server-wide `--allow-*` default; keys without a token fall back to the default. Set it with `auth trust <pubkey> --allow-control …` (works locally and over `--remote`); `auth keys` shows each key's policy.
|
|
437
|
+
- **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
|
+
- **Audit logging:** request logs include the acting key (its name from `authorized_keys` plus a short pubkey), not just the client address.
|
|
439
|
+
- **`serve-http`** is a convenience gateway with the inverse trade-off: commands are gated by the same `--allow-*` policy (safe-only by default), but the bearer token travels in **clear text over plain HTTP**. It binds to loopback by default; `--no-auth` is only permitted there. If you must expose it beyond loopback, put it behind a TLS-terminating reverse proxy — never send the token over an untrusted network unencrypted.
|
|
440
|
+
|
|
441
|
+
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
|
+
|
|
411
443
|
---
|
|
412
444
|
|
|
413
445
|
## Python SDK
|
|
@@ -418,7 +450,7 @@ from browser_cli import AsyncBrowserCLI, BrowserCLI
|
|
|
418
450
|
b = BrowserCLI()
|
|
419
451
|
```
|
|
420
452
|
|
|
421
|
-
Commands are grouped into namespaces on the client (`b.tabs`, `b.dom`, `b.session`, ...). Each sync call blocks until the browser responds and returns the data directly as a Python object. For asyncio programs, `AsyncBrowserCLI` exposes the same namespaces as native awaitable methods over async Unix/TCP transport.
|
|
453
|
+
Commands are grouped into namespaces on the client (`b.tabs`, `b.dom`, `b.session`, ...). Each sync call blocks until the browser responds and returns the data directly as a Python object. Create `BrowserCLI(remote="host:8765", key="client.key")` to target a remote server. For asyncio programs, `AsyncBrowserCLI` exposes the same namespaces as native awaitable methods over async Unix/TCP transport.
|
|
422
454
|
|
|
423
455
|
```python
|
|
424
456
|
# Navigation ── b.nav
|
|
@@ -573,6 +605,7 @@ counts = b.tabs.count()
|
|
|
573
605
|
if isinstance(counts, BrowserCounts):
|
|
574
606
|
print(counts.total)
|
|
575
607
|
print(counts.by_browser)
|
|
608
|
+
print(counts.browser_groups) # e.g. {"local:work": "local", "remote:work": "remote"}
|
|
576
609
|
```
|
|
577
610
|
|
|
578
611
|
---
|
|
@@ -603,11 +636,12 @@ nix-shell # automatically runs npm ci when node_modules is missing/outdated
|
|
|
603
636
|
npm run check:extension
|
|
604
637
|
```
|
|
605
638
|
|
|
606
|
-
The extension source lives in `extension/src/`. `extension/background.js` and `extension/content-dispatch.js` are generated and ignored by git. Run `npm run build:extension` before
|
|
639
|
+
The extension source lives in `extension/src/`. `extension/background.js` and `extension/content-dispatch.js` are generated and ignored by git. Run `npm run build:extension` before loading the unpacked `extension/` directory; `browser-cli install <browser> --dev` prints the per-browser load steps. On NixOS, use `nix-shell` first if npm is not installed globally.
|
|
607
640
|
|
|
608
641
|
Packaging:
|
|
609
642
|
|
|
610
643
|
```bash
|
|
644
|
+
just publish # build to /tmp/dist-browser-cli and publish with .env credentials
|
|
611
645
|
npm run package:extension # testing/unpacked zip, keeps manifest.key for stable Chromium native-messaging ID
|
|
612
646
|
npm run package:extension:webstore # Chrome Web Store zip, strips manifest.key
|
|
613
647
|
npm run package:extension:webstore:verified # Chrome Web Store CRX signed for verified uploads
|
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
# browser-cli
|
|
2
|
-
Control your real, running browser from the terminal or
|
|
2
|
+
Control your real, running browser from the terminal, Python SDK, or a trusted remote client — no headless browser, no Playwright, no virtual display. Your actual open tabs, windows, and tab groups respond to your commands.
|
|
3
3
|
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
## What it does
|
|
7
7
|
You have 40 tabs open. You want to close all the duplicates, group the GitHub ones, save your session before a meeting, and open a few URLs into a specific group — all from a script. That is what browser-cli is for.
|
|
8
8
|
|
|
9
|
-
It works by pairing a small browser extension with a Python package that provides both a CLI and SDK. The extension has full access to your browser's tabs, windows, groups, and page DOM. The CLI and SDK talk to it in real time over a local IPC channel.
|
|
9
|
+
It works by pairing a small browser extension with a Python package that provides both a CLI and SDK. The extension has full access to your browser's tabs, windows, groups, and page DOM. The CLI and SDK talk to it in real time over a local IPC channel, or through the optional authenticated TCP remote bridge.
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
13
|
## How it works
|
|
14
14
|
```
|
|
15
|
-
terminal / python script
|
|
15
|
+
terminal / python script / remote client
|
|
16
16
|
│
|
|
17
17
|
│ Local IPC (Unix socket on Linux/macOS, named pipe on Windows)
|
|
18
|
+
│ or TCP remote bridge (Ed25519 auth, optional compression)
|
|
18
19
|
▼
|
|
19
20
|
Native Messaging Host (Python process, launched by the browser)
|
|
20
21
|
│
|
|
@@ -33,7 +34,7 @@ terminal / python script
|
|
|
33
34
|
4. CLI commands connect to that socket, send a JSON command, and wait for the result.
|
|
34
35
|
5. The native host relays the command to the extension via stdout, receives the result via stdin, and sends it back to the CLI.
|
|
35
36
|
|
|
36
|
-
No server needs to be running beforehand. The browser manages the native host's lifecycle.
|
|
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`.
|
|
37
38
|
|
|
38
39
|
**Message format**
|
|
39
40
|
|
|
@@ -52,8 +53,10 @@ Every response:
|
|
|
52
53
|
|
|
53
54
|
**Requirements:** Python 3.10+, [uv](https://github.com/astral-sh/uv), Chrome, Chromium, Brave, Edge, Vivaldi, or Firefox
|
|
54
55
|
|
|
56
|
+
browser-cli has two parts: the **CLI / native host** (a Python package) and the **browser extension** (published on the public stores).
|
|
57
|
+
|
|
55
58
|
### Install with uv
|
|
56
|
-
|
|
59
|
+
Install the CLI from PyPI as a uv tool, then register the native host:
|
|
57
60
|
|
|
58
61
|
```sh
|
|
59
62
|
uv tool install real-browser-cli
|
|
@@ -75,21 +78,31 @@ To upgrade later:
|
|
|
75
78
|
uv tool upgrade real-browser-cli
|
|
76
79
|
```
|
|
77
80
|
|
|
81
|
+
### Add the browser extension
|
|
82
|
+
Install the extension from its public store listing (the `install` command prints the right link for you):
|
|
83
|
+
|
|
84
|
+
- Chrome / Chromium / Brave / Edge / Vivaldi — [Chrome Web Store](https://chromewebstore.google.com/detail/browser-cli/hekaebjhbhhdbmakimmaklbblbmccahp)
|
|
85
|
+
- Firefox — [Firefox Add-ons](https://addons.mozilla.org/firefox/addon/browser-cli/)
|
|
86
|
+
|
|
87
|
+
The native host manifest trusts both the published store ID and the unpacked development ID, so the store extension works out of the box. If you are hacking on the extension yourself, run `browser-cli install <browser> --dev` for the unpacked / temporary-add-on load steps instead.
|
|
88
|
+
|
|
78
89
|
### Install from source
|
|
79
90
|
```sh
|
|
80
91
|
git clone <repo>
|
|
81
92
|
cd browser-cli
|
|
82
93
|
uv sync
|
|
83
|
-
|
|
94
|
+
npm ci && npm run build:extension # build the unpacked extension bundles
|
|
95
|
+
uv run browser-cli install brave --dev # --dev prints unpacked-load steps; or: chrome, chromium, edge, vivaldi, firefox
|
|
84
96
|
```
|
|
85
97
|
|
|
98
|
+
Omit `--dev` to be pointed at the public store listing instead of loading the unpacked build.
|
|
99
|
+
|
|
86
100
|
The `install` command will:
|
|
87
|
-
1.
|
|
88
|
-
2.
|
|
89
|
-
3.
|
|
90
|
-
4. Copy the native host into an internal `libexec` directory and create a small wrapper outside your `PATH`
|
|
101
|
+
1. Write the native messaging manifest to your OS so the browser can find the host
|
|
102
|
+
2. Copy the native host into an internal `libexec` directory and create a small wrapper outside your `PATH`
|
|
103
|
+
3. Print the public store link for installing the extension (or, with `--dev`, the unpacked / temporary-add-on load steps)
|
|
91
104
|
|
|
92
|
-
After install, **fully restart your browser** (Quit and reopen — not just close the window). The extension will connect to the native host automatically on startup.
|
|
105
|
+
After install, add the extension from the store link above and **fully restart your browser** (Quit and reopen — not just close the window). The extension will connect to the native host automatically on startup.
|
|
93
106
|
|
|
94
107
|
Only the `browser-cli` command needs to be on your `PATH`. The browser launches the native host wrapper directly from its absolute path in the native messaging manifest, and that wrapper imports the installed `browser_cli.native.host` entry point. On Windows the install command also registers the host in the current user's Registry for the selected browser.
|
|
95
108
|
|
|
@@ -100,62 +113,42 @@ Only the `browser-cli` command needs to be on your `PATH`. The browser launches
|
|
|
100
113
|
```text
|
|
101
114
|
browser-cli/
|
|
102
115
|
├── browser_cli/
|
|
103
|
-
│ ├── __init__.py
|
|
104
|
-
│ ├──
|
|
105
|
-
│ ├──
|
|
106
|
-
│
|
|
107
|
-
│
|
|
108
|
-
│
|
|
109
|
-
│
|
|
110
|
-
│ ├──
|
|
111
|
-
│ ├──
|
|
112
|
-
│
|
|
113
|
-
│
|
|
114
|
-
│
|
|
115
|
-
│
|
|
116
|
-
│ │ ├── transport.py # TCP/TLS remote transport
|
|
117
|
-
│ │ └── registry.py # Saved remote endpoints/keys
|
|
118
|
-
│ └── commands/
|
|
119
|
-
│ ├── navigate.py # nav open/reload/back/forward/focus
|
|
120
|
-
│ ├── search.py # search engine shortcuts
|
|
121
|
-
│ ├── tabs.py # tab management
|
|
122
|
-
│ ├── groups.py # tab group management
|
|
123
|
-
│ ├── windows.py # window management
|
|
124
|
-
│ ├── dom.py # DOM querying and interaction
|
|
125
|
-
│ ├── extract.py # content extraction
|
|
126
|
-
│ └── session.py # session save/load
|
|
116
|
+
│ ├── __init__.py # Public sync SDK: BrowserCLI and namespace wiring
|
|
117
|
+
│ ├── async_sdk.py # AsyncBrowserCLI
|
|
118
|
+
│ ├── cli.py # Click root command and native-host entry point
|
|
119
|
+
│ ├── client/ # send_command path, local/remote routing, message helpers
|
|
120
|
+
│ ├── sdk/ # SDK namespaces: nav, tabs, groups, windows, dom, session, ...
|
|
121
|
+
│ ├── commands/ # CLI presentation layer over the SDK namespaces
|
|
122
|
+
│ ├── native/ # Browser-launched Native Messaging host + local IPC server
|
|
123
|
+
│ ├── remote/ # TCP remote client transport and saved endpoint registry
|
|
124
|
+
│ ├── serve/ # Authenticated TCP server runtime
|
|
125
|
+
│ ├── transport/ # JSON/msgpack response encoding and compression helpers
|
|
126
|
+
│ ├── markdown/ # HTML-to-Markdown extraction helpers
|
|
127
|
+
│ ├── auth/ # Ed25519 keys, signing, SSH-agent/YubiKey helpers, PQ KEX
|
|
128
|
+
│ └── models.py # Tab, Group, BrowserCounts dataclasses
|
|
127
129
|
├── extension/
|
|
128
|
-
│ ├── manifest.json
|
|
129
|
-
│
|
|
130
|
-
│
|
|
131
|
-
│ ├──
|
|
132
|
-
│
|
|
133
|
-
├──
|
|
134
|
-
│
|
|
135
|
-
|
|
136
|
-
├──
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
│ ├── test_extract.py
|
|
142
|
-
│ ├── test_groups.py
|
|
143
|
-
│ ├── test_nav.py
|
|
144
|
-
│ ├── test_session.py
|
|
145
|
-
│ ├── test_tabs.py
|
|
146
|
-
│ └── test_windows.py
|
|
147
|
-
├── com.browsercli.host.json # native messaging manifest template
|
|
148
|
-
├── pyproject.toml # package metadata and CLI entry point
|
|
149
|
-
└── uv.lock # locked dependencies for uv
|
|
130
|
+
│ ├── manifest.json # Chromium MV3 manifest
|
|
131
|
+
│ └── src/ # TypeScript WebExtension source
|
|
132
|
+
│ ├── index.ts # Background/service-worker bundle entry
|
|
133
|
+
│ ├── content-dispatch.ts
|
|
134
|
+
│ ├── commands/ # Browser-side command implementations
|
|
135
|
+
│ ├── content/ # DOM/extract/Markdown logic injected into pages
|
|
136
|
+
│ └── core/ # Shared extension helpers
|
|
137
|
+
├── examples/ # Python and shell walkthroughs
|
|
138
|
+
├── scripts/ # Packaging and release helper scripts
|
|
139
|
+
├── tests/ # pytest suite
|
|
140
|
+
├── package.json # Extension build/test/package scripts
|
|
141
|
+
├── pyproject.toml # Python package metadata
|
|
142
|
+
└── uv.lock # locked Python dependencies
|
|
150
143
|
```
|
|
151
144
|
|
|
152
145
|
---
|
|
153
146
|
|
|
154
147
|
## CLI reference
|
|
155
148
|
|
|
156
|
-
|
|
149
|
+
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`.
|
|
157
150
|
|
|
158
|
-
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. You can inspect
|
|
151
|
+
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.
|
|
159
152
|
|
|
160
153
|
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.
|
|
161
154
|
|
|
@@ -315,6 +308,44 @@ browser-cli completion zsh # print setup instructions
|
|
|
315
308
|
browser-cli completion zsh --script # output raw completion script
|
|
316
309
|
```
|
|
317
310
|
|
|
311
|
+
### Remote control, auth, and gateways
|
|
312
|
+
|
|
313
|
+
```sh
|
|
314
|
+
# On the machine with the browser
|
|
315
|
+
browser-cli auth keygen --output ~/.config/browser-cli/client.key
|
|
316
|
+
PUBKEY=$(browser-cli auth show --key ~/.config/browser-cli/client.key | tail -n1)
|
|
317
|
+
browser-cli auth trust "$PUBKEY"
|
|
318
|
+
browser-cli serve --host 0.0.0.0 --port 8765 --authorized-keys ~/.config/browser-cli/authorized_keys
|
|
319
|
+
|
|
320
|
+
# Allow remote browser control (navigation, clicks); safe-only otherwise
|
|
321
|
+
browser-cli serve --authorized-keys ~/.config/browser-cli/authorized_keys --allow-control
|
|
322
|
+
|
|
323
|
+
# Per-key authorization (inline in authorized_keys) + a tighter rate limit
|
|
324
|
+
browser-cli auth trust "$PUBKEY" --name ci-bot --allow-read-page --allow-control
|
|
325
|
+
browser-cli serve --authorized-keys ~/.config/browser-cli/authorized_keys --rate-limit 20
|
|
326
|
+
|
|
327
|
+
# From another machine
|
|
328
|
+
browser-cli --remote browser-host.example:8765 --key ~/.config/browser-cli/client.key tabs list
|
|
329
|
+
browser-cli remote trust browser-host.example:8765 ~/.config/browser-cli/client.key
|
|
330
|
+
browser-cli --remote browser-host.example:8765 clients
|
|
331
|
+
|
|
332
|
+
# Local HTTP JSON gateway for small integrations
|
|
333
|
+
browser-cli serve-http --port 8766
|
|
334
|
+
curl -H "Authorization: Bearer <token>" http://127.0.0.1:8766/tabs
|
|
335
|
+
```
|
|
336
|
+
|
|
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.
|
|
338
|
+
|
|
339
|
+
#### Security model
|
|
340
|
+
|
|
341
|
+
- **`serve` (TCP)** authenticates every connection with an Ed25519 signature over a fresh server nonce and, for modern clients, wraps the transport in an ML-KEM-768 (post-quantum) AEAD channel. Commands are gated by a **safe-only policy by default** — even a trusted key can only run read-only status/listing commands until you open more with `--allow-read-page`, `--allow-control`, `--allow-dangerous`, or `--allow-all` (full control, including `dom.eval`/`storage.*`). `--no-auth` is rejected on non-loopback hosts.
|
|
342
|
+
- **Per-key authorization:** a key in `authorized_keys` can carry an optional `allow:` token (`<pubkey> <name> allow:read-page,control`) listing its categories (`all`, `safe`, `read-page`, `control`, `dangerous`). That key uses its own policy, overriding the server-wide `--allow-*` default; keys without a token fall back to the default. Set it with `auth trust <pubkey> --allow-control …` (works locally and over `--remote`); `auth keys` shows each key's policy.
|
|
343
|
+
- **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
|
+
- **Audit logging:** request logs include the acting key (its name from `authorized_keys` plus a short pubkey), not just the client address.
|
|
345
|
+
- **`serve-http`** is a convenience gateway with the inverse trade-off: commands are gated by the same `--allow-*` policy (safe-only by default), but the bearer token travels in **clear text over plain HTTP**. It binds to loopback by default; `--no-auth` is only permitted there. If you must expose it beyond loopback, put it behind a TLS-terminating reverse proxy — never send the token over an untrusted network unencrypted.
|
|
346
|
+
|
|
347
|
+
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
|
+
|
|
318
349
|
---
|
|
319
350
|
|
|
320
351
|
## Python SDK
|
|
@@ -325,7 +356,7 @@ from browser_cli import AsyncBrowserCLI, BrowserCLI
|
|
|
325
356
|
b = BrowserCLI()
|
|
326
357
|
```
|
|
327
358
|
|
|
328
|
-
Commands are grouped into namespaces on the client (`b.tabs`, `b.dom`, `b.session`, ...). Each sync call blocks until the browser responds and returns the data directly as a Python object. For asyncio programs, `AsyncBrowserCLI` exposes the same namespaces as native awaitable methods over async Unix/TCP transport.
|
|
359
|
+
Commands are grouped into namespaces on the client (`b.tabs`, `b.dom`, `b.session`, ...). Each sync call blocks until the browser responds and returns the data directly as a Python object. Create `BrowserCLI(remote="host:8765", key="client.key")` to target a remote server. For asyncio programs, `AsyncBrowserCLI` exposes the same namespaces as native awaitable methods over async Unix/TCP transport.
|
|
329
360
|
|
|
330
361
|
```python
|
|
331
362
|
# Navigation ── b.nav
|
|
@@ -480,6 +511,7 @@ counts = b.tabs.count()
|
|
|
480
511
|
if isinstance(counts, BrowserCounts):
|
|
481
512
|
print(counts.total)
|
|
482
513
|
print(counts.by_browser)
|
|
514
|
+
print(counts.browser_groups) # e.g. {"local:work": "local", "remote:work": "remote"}
|
|
483
515
|
```
|
|
484
516
|
|
|
485
517
|
---
|
|
@@ -510,11 +542,12 @@ nix-shell # automatically runs npm ci when node_modules is missing/outdated
|
|
|
510
542
|
npm run check:extension
|
|
511
543
|
```
|
|
512
544
|
|
|
513
|
-
The extension source lives in `extension/src/`. `extension/background.js` and `extension/content-dispatch.js` are generated and ignored by git. Run `npm run build:extension` before
|
|
545
|
+
The extension source lives in `extension/src/`. `extension/background.js` and `extension/content-dispatch.js` are generated and ignored by git. Run `npm run build:extension` before loading the unpacked `extension/` directory; `browser-cli install <browser> --dev` prints the per-browser load steps. On NixOS, use `nix-shell` first if npm is not installed globally.
|
|
514
546
|
|
|
515
547
|
Packaging:
|
|
516
548
|
|
|
517
549
|
```bash
|
|
550
|
+
just publish # build to /tmp/dist-browser-cli and publish with .env credentials
|
|
518
551
|
npm run package:extension # testing/unpacked zip, keeps manifest.key for stable Chromium native-messaging ID
|
|
519
552
|
npm run package:extension:webstore # Chrome Web Store zip, strips manifest.key
|
|
520
553
|
npm run package:extension:webstore:verified # Chrome Web Store CRX signed for verified uploads
|
|
@@ -17,11 +17,14 @@ from browser_cli.auth.agent import (
|
|
|
17
17
|
)
|
|
18
18
|
from browser_cli.auth.keys import (
|
|
19
19
|
add_authorized_key,
|
|
20
|
+
format_authorized_line,
|
|
20
21
|
generate_keypair,
|
|
21
22
|
load_authorized_keys,
|
|
22
23
|
load_authorized_keys_with_names,
|
|
24
|
+
load_authorized_keys_with_policies,
|
|
23
25
|
load_private_key,
|
|
24
26
|
public_key_hex,
|
|
27
|
+
set_authorized_key_policy,
|
|
25
28
|
)
|
|
26
29
|
from browser_cli.auth.pq import (
|
|
27
30
|
new_nonce,
|
|
@@ -51,9 +54,11 @@ __all__ = [
|
|
|
51
54
|
"agent_list_keys",
|
|
52
55
|
"agent_sign_raw",
|
|
53
56
|
"canonical_payload",
|
|
57
|
+
"format_authorized_line",
|
|
54
58
|
"generate_keypair",
|
|
55
59
|
"load_authorized_keys",
|
|
56
60
|
"load_authorized_keys_with_names",
|
|
61
|
+
"load_authorized_keys_with_policies",
|
|
57
62
|
"load_private_key",
|
|
58
63
|
"new_nonce",
|
|
59
64
|
"pq_decrypt",
|
|
@@ -62,6 +67,7 @@ __all__ = [
|
|
|
62
67
|
"pq_kex_server_decapsulate",
|
|
63
68
|
"pq_kex_server_keypair",
|
|
64
69
|
"public_key_hex",
|
|
70
|
+
"set_authorized_key_policy",
|
|
65
71
|
"sign",
|
|
66
72
|
"verify",
|
|
67
73
|
]
|