real-browser-cli 0.16.4__tar.gz → 0.16.9__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.4 → real_browser_cli-0.16.9}/CLAUDE.md +1 -1
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/PKG-INFO +116 -2
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/README.md +112 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/client/core.py +11 -8
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/client/messages.py +2 -2
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/commands/extract.py +25 -19
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/commands/navigate.py +8 -6
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/commands/page.py +5 -4
- real_browser_cli-0.16.9/browser_cli/mcp/__init__.py +5 -0
- real_browser_cli-0.16.9/browser_cli/mcp/naming.py +39 -0
- real_browser_cli-0.16.9/browser_cli/mcp/serialization.py +19 -0
- real_browser_cli-0.16.9/browser_cli/mcp/server.py +314 -0
- real_browser_cli-0.16.9/browser_cli/mcp/targets.py +16 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/remote/pool.py +23 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/remote/registry.py +26 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/sdk/dom.py +25 -23
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/sdk/navigation.py +35 -21
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/sdk/tabs.py +17 -1
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/serve/security.py +30 -1
- real_browser_cli-0.16.9/browser_cli/url_match.py +138 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/manifest.json +1 -1
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/src/classes/JobManager.ts +10 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/src/commands/dom.ts +2 -2
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/src/commands/perf.ts +15 -11
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/src/commands/windows.ts +29 -2
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/test/jobs.test.ts +16 -1
- real_browser_cli-0.16.9/extension/test/windows.test.ts +69 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/n8n-nodes-browser-cli/package-lock.json +1385 -292
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/n8n-nodes-browser-cli/package.json +6 -6
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/n8n-nodes-browser-cli/tsconfig.json +2 -2
- real_browser_cli-0.16.9/package-lock.json +917 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/package.json +3 -3
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/pyproject.toml +5 -1
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/tests/conftest.py +12 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/tests/test_api.py +14 -3
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/tests/test_cli.py +52 -13
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/tests/test_client.py +19 -17
- real_browser_cli-0.16.9/tests/test_mcp.py +340 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/tests/test_new_feature_commands.py +44 -5
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/tests/test_remote_pool.py +33 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/tests/test_remote_registry.py +17 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/tests/test_serve_security.py +17 -0
- real_browser_cli-0.16.9/tests/test_url_match.py +84 -0
- real_browser_cli-0.16.9/uv.lock +1582 -0
- real_browser_cli-0.16.4/package-lock.json +0 -556
- real_browser_cli-0.16.4/uv.lock +0 -690
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/.claude/settings.local.json +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/.gitea/workflows/package-extension.yml +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/.gitea/workflows/publish.yml +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/.gitea/workflows/testing.yml +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/.gitignore +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/.python-version +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/LICENSE +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/PRIVACY.md +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/__init__.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/async_sdk.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/auth/__init__.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/auth/agent.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/auth/keys.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/auth/pq.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/auth/server_identity.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/auth/signing.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/cli.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/client/__init__.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/client/auth.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/client/targets.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/command_security.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/commands/__init__.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/commands/auth.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/commands/clients.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/commands/completion.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/commands/doctor.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/commands/dom.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/commands/events.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/commands/extension.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/commands/groups.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/commands/install.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/commands/perf.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/commands/raw.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/commands/remote.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/commands/rendering/__init__.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/commands/rendering/common.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/commands/rendering/labels.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/commands/rendering/tabs_tree.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/commands/rendering/windows_tree.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/commands/script.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/commands/search.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/commands/serve.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/commands/serve_http.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/commands/session.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/commands/storage.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/commands/tabs.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/commands/watch.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/commands/windows.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/commands/workspace.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/compat/__init__.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/compat/auth.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/compat/commands.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/constants.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/endpoints.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/errors.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/framing.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/local_transport.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/markdown/__init__.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/markdown/html.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/markdown/render.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/models.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/native/__init__.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/native/host.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/native/local_server.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/native/protocol.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/platform.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/registry.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/remote/__init__.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/remote/auth.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/remote/known_hosts.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/remote/socket.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/remote/transport.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/sdk/__init__.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/sdk/base.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/sdk/browser_data.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/sdk/decorators.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/sdk/extension.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/sdk/factories.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/sdk/groups.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/sdk/perf.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/sdk/routing.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/sdk/session.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/sdk/windows.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/sdk/workflow_decorators.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/search/__init__.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/search/engines.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/serve/__init__.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/serve/auth.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/serve/challenge.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/serve/control.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/serve/logging.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/serve/proxy.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/serve/runtime.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/transport/__init__.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/transport/binary.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/transport/codecs.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/browser_cli/version_manager.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/examples/demo.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/examples/demo.sh +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/content.js +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/icon.svg +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/icons/icon-128.png +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/icons/icon-16.png +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/icons/icon-32.png +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/icons/icon-48.png +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/src/browser-api.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/src/classes/CommandGroup.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/src/classes/CommandRegistry.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/src/classes/NativeConnection.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/src/commands/autosave.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/src/commands/browser-data.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/src/commands/extension.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/src/commands/groups.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/src/commands/injected.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/src/commands/navigation.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/src/commands/session-snapshot.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/src/commands/session.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/src/commands/tabs-query.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/src/commands/tabs.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/src/content/dispatch.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/src/content/dom-ops.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/src/content/extract.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/src/content/markdown/code.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/src/content/markdown/index.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/src/content/markdown/renderer.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/src/content/markdown/root.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/src/content/markdown/utils.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/src/content/page-info.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/src/content-dispatch.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/src/core/errors.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/src/core/group-helpers.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/src/core/index.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/src/core/scripting.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/src/core/storage.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/src/core/tab-groups.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/src/core/tab-helpers.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/src/core/throttle.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/src/index.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/src/types/command-args.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/src/types/index.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/src/types/jobs.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/src/types/json.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/src/types/messages.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/src/types/session.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/src/types/tabs.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/src/types/webextension.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/test/autosave.test.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/test/browser-api.test.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/test/chrome-mock.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/test/clients.test.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/test/navigation.test.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/test/paging.test.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/extension/test/url-match.test.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/justfile +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/n8n-nodes-browser-cli/.claude/settings.local.json +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/n8n-nodes-browser-cli/.gitignore +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/n8n-nodes-browser-cli/README.md +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/n8n-nodes-browser-cli/credentials/BrowserCliApi.credentials.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/n8n-nodes-browser-cli/nodes/BrowserCli/BrowserCli.node.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/n8n-nodes-browser-cli/nodes/BrowserCli/browserCli.svg +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/n8n-nodes-browser-cli/nodes/BrowserCli/protocol.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/n8n-nodes-browser-cli/nodes/BrowserCli/request.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/n8n-nodes-browser-cli/nodes/BrowserCli/serveClient.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/n8n-nodes-browser-cli/scripts/copy-assets.mjs +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/n8n-nodes-browser-cli/scripts/run-tests.mjs +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/n8n-nodes-browser-cli/test/protocol.test.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/n8n-nodes-browser-cli/test/request.test.ts +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/n8n-nodes-browser-cli/test-dist/protocol.test.mjs +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/n8n-nodes-browser-cli/test-dist/request.test.mjs +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/scripts/gen_extension_key.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/scripts/package_extension.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/scripts/package_verified_crx.sh +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/scripts/setup_verified_crx_key.sh +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/shell.nix +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/tests/test_auth.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/tests/test_commands_cli.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/tests/test_compat.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/tests/test_dom.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/tests/test_extension_error_page_handling.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/tests/test_extension_packaging.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/tests/test_extract.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/tests/test_framing.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/tests/test_groups.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/tests/test_known_hosts.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/tests/test_markdown_security.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/tests/test_native_host.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/tests/test_nav.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/tests/test_page.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/tests/test_perf.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/tests/test_performance_integration.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/tests/test_platform.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/tests/test_refactor_boundaries.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/tests/test_registry.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/tests/test_remote_protocol_matrix.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/tests/test_rendering.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/tests/test_serve.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/tests/test_session.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/tests/test_storage.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/tests/test_tabs.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/tests/test_transport.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/tests/test_windows.py +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/tsconfig.json +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/webstore-assets/screenshots/screenshot-1.png +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/webstore-assets/screenshots/screenshot-2.png +0 -0
- {real_browser_cli-0.16.4 → real_browser_cli-0.16.9}/webstore-assets/screenshots/screenshot-3.png +0 -0
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
## Memory laden
|
|
4
4
|
Beim Start dieser Session: Memory-Datei lesen für vollständigen Codebase-Kontext:
|
|
5
5
|
```
|
|
6
|
-
|
|
6
|
+
03 Bereiche/Open Source & Side Projects/browser-cli.md
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|
Memory nach Änderungen aktualisieren (neue Commands, Architektur-Änderungen, neue Patterns, Version-Bumps).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: real-browser-cli
|
|
3
|
-
Version: 0.16.
|
|
3
|
+
Version: 0.16.9
|
|
4
4
|
Summary: Control your real running browser from the terminal or Python SDK
|
|
5
5
|
Project-URL: Homepage, https://git.yiprawr.dev/Automatisation/browser-cli
|
|
6
6
|
Project-URL: Repository, https://git.yiprawr.dev/Automatisation/browser-cli
|
|
@@ -90,6 +90,8 @@ Requires-Dist: questionary>=2
|
|
|
90
90
|
Requires-Dist: rich>=13
|
|
91
91
|
Provides-Extra: fast
|
|
92
92
|
Requires-Dist: zstandard>=0.22; extra == 'fast'
|
|
93
|
+
Provides-Extra: mcp
|
|
94
|
+
Requires-Dist: mcp<3,>=2; extra == 'mcp'
|
|
93
95
|
Description-Content-Type: text/markdown
|
|
94
96
|
|
|
95
97
|
# browser-cli
|
|
@@ -164,6 +166,11 @@ For better remote-response compression, install the optional `fast` extra:
|
|
|
164
166
|
uv tool install "real-browser-cli[fast]"
|
|
165
167
|
```
|
|
166
168
|
|
|
169
|
+
To expose the conservative MCP tool surface, install the optional `mcp` extra:
|
|
170
|
+
```sh
|
|
171
|
+
uv tool install "real-browser-cli[mcp]"
|
|
172
|
+
```
|
|
173
|
+
|
|
167
174
|
To upgrade later:
|
|
168
175
|
|
|
169
176
|
```sh
|
|
@@ -235,6 +242,104 @@ browser-cli/
|
|
|
235
242
|
|
|
236
243
|
---
|
|
237
244
|
|
|
245
|
+
## Stateless MCP server
|
|
246
|
+
The optional MCP adapter exposes a small, typed subset of the Python SDK for
|
|
247
|
+
MCP hosts such as Claude Desktop, Claude Code, Cursor, or VS Code. It controls
|
|
248
|
+
the same real browser; it does not launch a headless browser or duplicate the
|
|
249
|
+
browser command implementation.
|
|
250
|
+
|
|
251
|
+
Install and run the local stdio server:
|
|
252
|
+
```sh
|
|
253
|
+
uv tool install "real-browser-cli[mcp]"
|
|
254
|
+
browser-cli-mcp
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Example MCP host configuration:
|
|
258
|
+
```json
|
|
259
|
+
{
|
|
260
|
+
"mcpServers": {
|
|
261
|
+
"browser-cli": {
|
|
262
|
+
"command": "browser-cli-mcp"
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
The server is stateless at the MCP layer. Every tool call creates a fresh
|
|
269
|
+
`BrowserCLI` SDK client, and browser state remains in the real browser. Pass
|
|
270
|
+
`browser`, `remote`, and `key` on a tool call when a specific local profile or
|
|
271
|
+
authenticated browser-cli remote is required.
|
|
272
|
+
|
|
273
|
+
`browser_navigate`, `browser_tabs_close`, and `browser_screenshot` take an
|
|
274
|
+
optional `tab_id` and act on the active tab when it is omitted, so a caller
|
|
275
|
+
does not need a preceding `browser_tabs_list` round trip. `browser_tabs_close`
|
|
276
|
+
reports the tab it closed.
|
|
277
|
+
|
|
278
|
+
`browser_tabs_open` does not duplicate pages. It takes over an already-open tab
|
|
279
|
+
whose URL is nearly the same and reports `reused` next to the tab metadata, so
|
|
280
|
+
an agent that re-opens a page keeps working in the tab the user already has.
|
|
281
|
+
The `reuse` argument selects the match:
|
|
282
|
+
|
|
283
|
+
| `reuse` | Matches |
|
|
284
|
+
|---------|---------|
|
|
285
|
+
| `similar` (default) | Same site and page, ignoring fragment, trailing slash, query order, `www.` and default ports — plus any tab of the site when the requested URL is its root |
|
|
286
|
+
| `exact` | Only a character-for-character identical URL |
|
|
287
|
+
| `origin` | Any tab of the same site, whatever its path |
|
|
288
|
+
| `none` | Never reuse, always open a new tab |
|
|
289
|
+
|
|
290
|
+
A reused tab is only navigated when it is not already on the requested page, so
|
|
291
|
+
no reload throws away page state.
|
|
292
|
+
|
|
293
|
+
Available tools:
|
|
294
|
+
- `browser_tabs_list`, `browser_tabs_open`, `browser_tabs_close`
|
|
295
|
+
- `browser_navigate`, `browser_page_info`
|
|
296
|
+
- `browser_extract_text`, `browser_extract_markdown`
|
|
297
|
+
- `browser_dom_query`, `browser_dom_click`, `browser_dom_type`
|
|
298
|
+
- `browser_screenshot`
|
|
299
|
+
|
|
300
|
+
Generic JavaScript evaluation, raw browser commands, storage writes, and
|
|
301
|
+
session import are intentionally not exposed.
|
|
302
|
+
|
|
303
|
+
### Pinning one browser and naming tools
|
|
304
|
+
Set `BROWSER_CLI_PROFILE` to pin every call from an MCP server to one browser,
|
|
305
|
+
so tools do not have to pass `browser` and listing tools do not fan out across
|
|
306
|
+
all connected browsers:
|
|
307
|
+
```json
|
|
308
|
+
{
|
|
309
|
+
"mcpServers": {
|
|
310
|
+
"browser-cli-testing": {
|
|
311
|
+
"command": "browser-cli-mcp",
|
|
312
|
+
"env": {
|
|
313
|
+
"BROWSER_CLI_PROFILE": "testing",
|
|
314
|
+
"BROWSER_CLI_MCP_TOOL_PREFIX": ""
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
`BROWSER_CLI_REMOTE` and `BROWSER_CLI_KEY` pin an authenticated remote the same
|
|
321
|
+
way. Explicit `browser`, `remote`, and `key` tool arguments still win.
|
|
322
|
+
|
|
323
|
+
Tool names carry a `browser_` prefix by default so they stay unambiguous in
|
|
324
|
+
hosts that expose raw MCP tool names. Hosts that already prefix tools with the
|
|
325
|
+
server name produce stutter such as `browser_cli_testing_browser_tabs_list`;
|
|
326
|
+
setting `BROWSER_CLI_MCP_TOOL_PREFIX` to an empty string drops the built-in
|
|
327
|
+
prefix and yields `browser_cli_testing_tabs_list`. Any other value replaces the
|
|
328
|
+
prefix.
|
|
329
|
+
|
|
330
|
+
For local development and testing, Streamable HTTP is also available:
|
|
331
|
+
```sh
|
|
332
|
+
browser-cli-mcp --transport streamable-http --port 8000
|
|
333
|
+
# endpoint: http://127.0.0.1:8000/mcp
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
HTTP uses stateless JSON responses and intentionally refuses non-loopback bind
|
|
337
|
+
addresses because this MCP endpoint has no independent authentication. For a
|
|
338
|
+
browser on another machine, keep MCP local and pass an authenticated
|
|
339
|
+
browser-cli `remote` target to each tool call.
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
238
343
|
## CLI reference
|
|
239
344
|
During source development, commands are usually run as `uv run browser-cli [--browser ALIAS] <command>`. After tool installation, use `browser-cli ...` directly. Add `--remote HOST[:PORT]` and optionally `--key PATH` to target a browser exposed by `browser-cli serve`.
|
|
240
345
|
|
|
@@ -250,6 +355,12 @@ browser-cli nav open https://example.com --focus # bring opened tab/w
|
|
|
250
355
|
browser-cli nav open https://example.com --window work # into a named window
|
|
251
356
|
browser-cli nav open https://example.com --group research # into a tab group (name or ID)
|
|
252
357
|
|
|
358
|
+
# Reuse an open tab instead of duplicating a page
|
|
359
|
+
browser-cli nav open https://example.com/app --reuse # nearly identical URL (fragment, trailing slash, query order, www)
|
|
360
|
+
browser-cli nav open https://example.com/app --reuse-exact # only a character-for-character match
|
|
361
|
+
browser-cli nav open https://example.com/app --reuse-domain # any tab of the same site
|
|
362
|
+
browser-cli nav open https://example.com/app --reuse-title "App" # tab whose title contains the text
|
|
363
|
+
|
|
253
364
|
# Reload
|
|
254
365
|
browser-cli nav reload # reload active tab
|
|
255
366
|
browser-cli nav reload 1234 # reload tab by ID
|
|
@@ -462,6 +573,9 @@ b.nav.search("google", "python asyncio")
|
|
|
462
573
|
tabs = b.tabs.list() # list[Tab]; in multi-browser mode each tab.browser is set
|
|
463
574
|
tab = b.tabs.open("https://example.com") # returns a bound Tab object
|
|
464
575
|
tab = b.tabs.open("https://example.com", wait=True, timeout=10)
|
|
576
|
+
tab = b.tabs.open("https://example.com/app", reuse=True) # reuse a tab with a nearly identical URL
|
|
577
|
+
tab = b.tabs.open("https://example.com/app", reuse="origin") # reuse any tab of that site
|
|
578
|
+
existing = b.nav.reusable_tab("https://example.com/app", reuse=True) # Tab or None, without touching the browser
|
|
465
579
|
active = b.tabs.active() # active Tab object
|
|
466
580
|
tab = b.tabs.get(1234) # tab by ID
|
|
467
581
|
tab = b.tabs.first("github") # first matching tab or None
|
|
@@ -70,6 +70,11 @@ For better remote-response compression, install the optional `fast` extra:
|
|
|
70
70
|
uv tool install "real-browser-cli[fast]"
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
+
To expose the conservative MCP tool surface, install the optional `mcp` extra:
|
|
74
|
+
```sh
|
|
75
|
+
uv tool install "real-browser-cli[mcp]"
|
|
76
|
+
```
|
|
77
|
+
|
|
73
78
|
To upgrade later:
|
|
74
79
|
|
|
75
80
|
```sh
|
|
@@ -141,6 +146,104 @@ browser-cli/
|
|
|
141
146
|
|
|
142
147
|
---
|
|
143
148
|
|
|
149
|
+
## Stateless MCP server
|
|
150
|
+
The optional MCP adapter exposes a small, typed subset of the Python SDK for
|
|
151
|
+
MCP hosts such as Claude Desktop, Claude Code, Cursor, or VS Code. It controls
|
|
152
|
+
the same real browser; it does not launch a headless browser or duplicate the
|
|
153
|
+
browser command implementation.
|
|
154
|
+
|
|
155
|
+
Install and run the local stdio server:
|
|
156
|
+
```sh
|
|
157
|
+
uv tool install "real-browser-cli[mcp]"
|
|
158
|
+
browser-cli-mcp
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Example MCP host configuration:
|
|
162
|
+
```json
|
|
163
|
+
{
|
|
164
|
+
"mcpServers": {
|
|
165
|
+
"browser-cli": {
|
|
166
|
+
"command": "browser-cli-mcp"
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
The server is stateless at the MCP layer. Every tool call creates a fresh
|
|
173
|
+
`BrowserCLI` SDK client, and browser state remains in the real browser. Pass
|
|
174
|
+
`browser`, `remote`, and `key` on a tool call when a specific local profile or
|
|
175
|
+
authenticated browser-cli remote is required.
|
|
176
|
+
|
|
177
|
+
`browser_navigate`, `browser_tabs_close`, and `browser_screenshot` take an
|
|
178
|
+
optional `tab_id` and act on the active tab when it is omitted, so a caller
|
|
179
|
+
does not need a preceding `browser_tabs_list` round trip. `browser_tabs_close`
|
|
180
|
+
reports the tab it closed.
|
|
181
|
+
|
|
182
|
+
`browser_tabs_open` does not duplicate pages. It takes over an already-open tab
|
|
183
|
+
whose URL is nearly the same and reports `reused` next to the tab metadata, so
|
|
184
|
+
an agent that re-opens a page keeps working in the tab the user already has.
|
|
185
|
+
The `reuse` argument selects the match:
|
|
186
|
+
|
|
187
|
+
| `reuse` | Matches |
|
|
188
|
+
|---------|---------|
|
|
189
|
+
| `similar` (default) | Same site and page, ignoring fragment, trailing slash, query order, `www.` and default ports — plus any tab of the site when the requested URL is its root |
|
|
190
|
+
| `exact` | Only a character-for-character identical URL |
|
|
191
|
+
| `origin` | Any tab of the same site, whatever its path |
|
|
192
|
+
| `none` | Never reuse, always open a new tab |
|
|
193
|
+
|
|
194
|
+
A reused tab is only navigated when it is not already on the requested page, so
|
|
195
|
+
no reload throws away page state.
|
|
196
|
+
|
|
197
|
+
Available tools:
|
|
198
|
+
- `browser_tabs_list`, `browser_tabs_open`, `browser_tabs_close`
|
|
199
|
+
- `browser_navigate`, `browser_page_info`
|
|
200
|
+
- `browser_extract_text`, `browser_extract_markdown`
|
|
201
|
+
- `browser_dom_query`, `browser_dom_click`, `browser_dom_type`
|
|
202
|
+
- `browser_screenshot`
|
|
203
|
+
|
|
204
|
+
Generic JavaScript evaluation, raw browser commands, storage writes, and
|
|
205
|
+
session import are intentionally not exposed.
|
|
206
|
+
|
|
207
|
+
### Pinning one browser and naming tools
|
|
208
|
+
Set `BROWSER_CLI_PROFILE` to pin every call from an MCP server to one browser,
|
|
209
|
+
so tools do not have to pass `browser` and listing tools do not fan out across
|
|
210
|
+
all connected browsers:
|
|
211
|
+
```json
|
|
212
|
+
{
|
|
213
|
+
"mcpServers": {
|
|
214
|
+
"browser-cli-testing": {
|
|
215
|
+
"command": "browser-cli-mcp",
|
|
216
|
+
"env": {
|
|
217
|
+
"BROWSER_CLI_PROFILE": "testing",
|
|
218
|
+
"BROWSER_CLI_MCP_TOOL_PREFIX": ""
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
`BROWSER_CLI_REMOTE` and `BROWSER_CLI_KEY` pin an authenticated remote the same
|
|
225
|
+
way. Explicit `browser`, `remote`, and `key` tool arguments still win.
|
|
226
|
+
|
|
227
|
+
Tool names carry a `browser_` prefix by default so they stay unambiguous in
|
|
228
|
+
hosts that expose raw MCP tool names. Hosts that already prefix tools with the
|
|
229
|
+
server name produce stutter such as `browser_cli_testing_browser_tabs_list`;
|
|
230
|
+
setting `BROWSER_CLI_MCP_TOOL_PREFIX` to an empty string drops the built-in
|
|
231
|
+
prefix and yields `browser_cli_testing_tabs_list`. Any other value replaces the
|
|
232
|
+
prefix.
|
|
233
|
+
|
|
234
|
+
For local development and testing, Streamable HTTP is also available:
|
|
235
|
+
```sh
|
|
236
|
+
browser-cli-mcp --transport streamable-http --port 8000
|
|
237
|
+
# endpoint: http://127.0.0.1:8000/mcp
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
HTTP uses stateless JSON responses and intentionally refuses non-loopback bind
|
|
241
|
+
addresses because this MCP endpoint has no independent authentication. For a
|
|
242
|
+
browser on another machine, keep MCP local and pass an authenticated
|
|
243
|
+
browser-cli `remote` target to each tool call.
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
144
247
|
## CLI reference
|
|
145
248
|
During source development, commands are usually run as `uv run browser-cli [--browser ALIAS] <command>`. After tool installation, use `browser-cli ...` directly. Add `--remote HOST[:PORT]` and optionally `--key PATH` to target a browser exposed by `browser-cli serve`.
|
|
146
249
|
|
|
@@ -156,6 +259,12 @@ browser-cli nav open https://example.com --focus # bring opened tab/w
|
|
|
156
259
|
browser-cli nav open https://example.com --window work # into a named window
|
|
157
260
|
browser-cli nav open https://example.com --group research # into a tab group (name or ID)
|
|
158
261
|
|
|
262
|
+
# Reuse an open tab instead of duplicating a page
|
|
263
|
+
browser-cli nav open https://example.com/app --reuse # nearly identical URL (fragment, trailing slash, query order, www)
|
|
264
|
+
browser-cli nav open https://example.com/app --reuse-exact # only a character-for-character match
|
|
265
|
+
browser-cli nav open https://example.com/app --reuse-domain # any tab of the same site
|
|
266
|
+
browser-cli nav open https://example.com/app --reuse-title "App" # tab whose title contains the text
|
|
267
|
+
|
|
159
268
|
# Reload
|
|
160
269
|
browser-cli nav reload # reload active tab
|
|
161
270
|
browser-cli nav reload 1234 # reload tab by ID
|
|
@@ -368,6 +477,9 @@ b.nav.search("google", "python asyncio")
|
|
|
368
477
|
tabs = b.tabs.list() # list[Tab]; in multi-browser mode each tab.browser is set
|
|
369
478
|
tab = b.tabs.open("https://example.com") # returns a bound Tab object
|
|
370
479
|
tab = b.tabs.open("https://example.com", wait=True, timeout=10)
|
|
480
|
+
tab = b.tabs.open("https://example.com/app", reuse=True) # reuse a tab with a nearly identical URL
|
|
481
|
+
tab = b.tabs.open("https://example.com/app", reuse="origin") # reuse any tab of that site
|
|
482
|
+
existing = b.nav.reusable_tab("https://example.com/app", reuse=True) # Tab or None, without touching the browser
|
|
371
483
|
active = b.tabs.active() # active Tab object
|
|
372
484
|
tab = b.tabs.get(1234) # tab by ID
|
|
373
485
|
tab = b.tabs.first("github") # first matching tab or None
|
|
@@ -151,21 +151,25 @@ def active_browser_targets(*, include_remotes: bool = True, key=None, suppress_p
|
|
|
151
151
|
targets.extend(_remote_browser_targets(key=key, suppress_pq_warning=suppress_pq_warning))
|
|
152
152
|
return targets
|
|
153
153
|
|
|
154
|
-
def _cached_client_row(target: BrowserTarget) -> dict | None:
|
|
154
|
+
def _cached_client_row(target: BrowserTarget, *, scoped: bool = False) -> dict | None:
|
|
155
155
|
"""Build a clients row from a target's discovery data, skipping a roundtrip.
|
|
156
156
|
|
|
157
157
|
Returns None when the remote didn't advertise its version (older serve), so
|
|
158
|
-
callers fall back to an explicit ``clients.list`` query.
|
|
158
|
+
callers fall back to an explicit ``clients.list`` query. When *scoped* is
|
|
159
|
+
true, the caller already selected one remote host, so render profile-only
|
|
160
|
+
labels instead of adding a host group header.
|
|
159
161
|
"""
|
|
160
162
|
if target.version is None and target.extension_version is None:
|
|
161
163
|
return None
|
|
162
|
-
|
|
163
|
-
"profile": target.display_name,
|
|
164
|
-
"profileGroup": target.display_group,
|
|
164
|
+
row = {
|
|
165
|
+
"profile": target.profile if scoped else target.display_name,
|
|
165
166
|
"name": target.browser_name or "",
|
|
166
167
|
"version": target.version or "",
|
|
167
168
|
"extensionVersion": target.extension_version or "",
|
|
168
169
|
}
|
|
170
|
+
if target.display_group and not scoped:
|
|
171
|
+
row["profileGroup"] = target.display_group
|
|
172
|
+
return row
|
|
169
173
|
|
|
170
174
|
def _rows_from_result(result, label: str, profile_group: str | None) -> list[dict]:
|
|
171
175
|
rows = []
|
|
@@ -249,18 +253,17 @@ def collect_browser_clients(
|
|
|
249
253
|
if targets:
|
|
250
254
|
uncached = []
|
|
251
255
|
for target in targets:
|
|
252
|
-
cached = _cached_client_row(target)
|
|
256
|
+
cached = _cached_client_row(target, scoped=True)
|
|
253
257
|
if cached is not None:
|
|
254
258
|
rows.append(cached)
|
|
255
259
|
else:
|
|
256
260
|
uncached.append(target)
|
|
257
261
|
results = _run_concurrent([
|
|
258
262
|
(lambda t=t: _client_rows_async(
|
|
259
|
-
t.
|
|
263
|
+
t.profile,
|
|
260
264
|
profile=t.profile,
|
|
261
265
|
remote=remote,
|
|
262
266
|
key=key,
|
|
263
|
-
profile_group=t.display_group,
|
|
264
267
|
))
|
|
265
268
|
for t in uncached
|
|
266
269
|
])
|
|
@@ -5,8 +5,8 @@ import uuid
|
|
|
5
5
|
from typing import Any
|
|
6
6
|
|
|
7
7
|
from browser_cli import transport
|
|
8
|
-
from browser_cli.endpoints import _normalize_endpoint
|
|
9
8
|
from browser_cli.errors import BrowserNotConnected
|
|
9
|
+
from browser_cli.remote.registry import resolve_remote_endpoint
|
|
10
10
|
|
|
11
11
|
def base_message(command: str, args: dict | None) -> dict:
|
|
12
12
|
return {"id": str(uuid.uuid4()), "command": command, "args": args or {}}
|
|
@@ -14,7 +14,7 @@ def base_message(command: str, args: dict | None) -> dict:
|
|
|
14
14
|
def requested_target(profile: str | None, remote: str | None) -> tuple[str | None, str | None]:
|
|
15
15
|
requested_profile = profile or os.environ.get("BROWSER_CLI_PROFILE")
|
|
16
16
|
remote_endpoint = remote or os.environ.get("BROWSER_CLI_REMOTE")
|
|
17
|
-
return requested_profile,
|
|
17
|
+
return requested_profile, resolve_remote_endpoint(remote_endpoint) if remote_endpoint else None
|
|
18
18
|
|
|
19
19
|
def encode_payload(msg: dict) -> bytes:
|
|
20
20
|
return json.dumps(msg).encode("utf-8")
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import json
|
|
2
2
|
|
|
3
3
|
import click
|
|
4
|
-
from browser_cli.commands import client_from_ctx, handle_errors
|
|
4
|
+
from browser_cli.commands import client_from_ctx, handle_errors, tab_option
|
|
5
5
|
from rich.console import Console
|
|
6
6
|
from rich.table import Table
|
|
7
7
|
|
|
@@ -12,10 +12,11 @@ def extract_group():
|
|
|
12
12
|
"""Extract content from the active tab."""
|
|
13
13
|
|
|
14
14
|
@extract_group.command("links")
|
|
15
|
+
@tab_option
|
|
15
16
|
@handle_errors
|
|
16
|
-
def extract_links():
|
|
17
|
-
"""Extract all links from the active tab."""
|
|
18
|
-
links = client_from_ctx().extract.links()
|
|
17
|
+
def extract_links(tab_id):
|
|
18
|
+
"""Extract all links from the active tab or --tab."""
|
|
19
|
+
links = client_from_ctx().extract.links(tab_id)
|
|
19
20
|
if not links:
|
|
20
21
|
console.print("[yellow]No links found[/yellow]")
|
|
21
22
|
return
|
|
@@ -27,10 +28,11 @@ def extract_links():
|
|
|
27
28
|
console.print(table)
|
|
28
29
|
|
|
29
30
|
@extract_group.command("images")
|
|
31
|
+
@tab_option
|
|
30
32
|
@handle_errors
|
|
31
|
-
def extract_images():
|
|
32
|
-
"""Extract all images from the active tab."""
|
|
33
|
-
images = client_from_ctx().extract.images()
|
|
33
|
+
def extract_images(tab_id):
|
|
34
|
+
"""Extract all images from the active tab or --tab."""
|
|
35
|
+
images = client_from_ctx().extract.images(tab_id)
|
|
34
36
|
if not images:
|
|
35
37
|
console.print("[yellow]No images found[/yellow]")
|
|
36
38
|
return
|
|
@@ -42,29 +44,33 @@ def extract_images():
|
|
|
42
44
|
console.print(table)
|
|
43
45
|
|
|
44
46
|
@extract_group.command("text")
|
|
47
|
+
@tab_option
|
|
45
48
|
@handle_errors
|
|
46
|
-
def extract_text():
|
|
47
|
-
"""Extract all visible text from the active tab."""
|
|
48
|
-
console.print(client_from_ctx().extract.text())
|
|
49
|
+
def extract_text(tab_id):
|
|
50
|
+
"""Extract all visible text from the active tab or --tab."""
|
|
51
|
+
console.print(client_from_ctx().extract.text(tab_id))
|
|
49
52
|
|
|
50
53
|
@extract_group.command("json")
|
|
51
54
|
@click.argument("selector")
|
|
55
|
+
@tab_option
|
|
52
56
|
@handle_errors
|
|
53
|
-
def extract_json(selector):
|
|
54
|
-
"""Parse and pretty-print JSON content inside SELECTOR."""
|
|
55
|
-
data = client_from_ctx().extract.json(selector)
|
|
57
|
+
def extract_json(selector, tab_id):
|
|
58
|
+
"""Parse and pretty-print JSON content inside SELECTOR in the active tab or --tab."""
|
|
59
|
+
data = client_from_ctx().extract.json(selector, tab_id)
|
|
56
60
|
console.print_json(json.dumps(data))
|
|
57
61
|
|
|
58
62
|
@extract_group.command("html")
|
|
63
|
+
@tab_option
|
|
59
64
|
@handle_errors
|
|
60
|
-
def extract_html():
|
|
61
|
-
"""Print the full HTML of the active tab to stdout."""
|
|
62
|
-
click.echo(client_from_ctx().extract.html())
|
|
65
|
+
def extract_html(tab_id):
|
|
66
|
+
"""Print the full HTML of the active tab or --tab to stdout."""
|
|
67
|
+
click.echo(client_from_ctx().extract.html(tab_id))
|
|
63
68
|
|
|
64
69
|
@extract_group.command("markdown")
|
|
65
70
|
@click.option("--selector", help="Extract only the DOM subtree matching this CSS selector.")
|
|
71
|
+
@tab_option
|
|
66
72
|
@handle_errors
|
|
67
|
-
def extract_markdown(selector):
|
|
68
|
-
"""Extract the page's main content as Markdown."""
|
|
69
|
-
markdown = client_from_ctx().extract.markdown(selector)
|
|
73
|
+
def extract_markdown(selector, tab_id):
|
|
74
|
+
"""Extract the page's main content as Markdown from the active tab or --tab."""
|
|
75
|
+
markdown = client_from_ctx().extract.markdown(selector, tab_id)
|
|
70
76
|
click.echo(markdown or "", nl=not (markdown or "").endswith("\n"))
|
|
@@ -13,13 +13,14 @@ def nav_group():
|
|
|
13
13
|
@click.option("--focus", is_flag=True, help="Bring the opened tab/window to the front")
|
|
14
14
|
@click.option("--window", "window_name", default=None, help="Open in named window")
|
|
15
15
|
@click.option("--group", "group_name", default=None, help="Open directly into a tab group (name or ID)")
|
|
16
|
-
@click.option("--reuse", is_flag=True, help="Reuse an existing tab with
|
|
16
|
+
@click.option("--reuse", is_flag=True, help="Reuse an existing tab with a nearly identical URL (ignores fragment, trailing slash, query order)")
|
|
17
|
+
@click.option("--reuse-exact", is_flag=True, help="Reuse an existing tab only when its URL matches character for character")
|
|
17
18
|
@click.option("--reuse-domain", is_flag=True, help="Reuse an existing tab with the same domain")
|
|
18
19
|
@click.option("--reuse-title", default=None, metavar="TEXT", help="Reuse an existing tab whose title contains TEXT")
|
|
19
20
|
@handle_errors
|
|
20
|
-
def cmd_open(url, focus, window_name, group_name, reuse, reuse_domain, reuse_title):
|
|
21
|
+
def cmd_open(url, focus, window_name, group_name, reuse, reuse_exact, reuse_domain, reuse_title):
|
|
21
22
|
"""Open URL in a new tab without stealing focus by default."""
|
|
22
|
-
client_from_ctx().nav.open(url, focus=focus, window=window_name, group=group_name, reuse=reuse, reuse_domain=reuse_domain, reuse_title=reuse_title)
|
|
23
|
+
client_from_ctx().nav.open(url, focus=focus, window=window_name, group=group_name, reuse="exact" if reuse_exact else reuse, reuse_domain=reuse_domain, reuse_title=reuse_title)
|
|
23
24
|
suffix = ""
|
|
24
25
|
if group_name:
|
|
25
26
|
suffix = f" in group '{group_name}'"
|
|
@@ -76,13 +77,14 @@ def cmd_focus(pattern):
|
|
|
76
77
|
@click.option("--focus", is_flag=True, help="Bring the opened tab/window to the front")
|
|
77
78
|
@click.option("--window", "window_name", default=None, help="Open in named window")
|
|
78
79
|
@click.option("--group", "group_name", default=None, help="Open in tab group")
|
|
79
|
-
@click.option("--reuse", is_flag=True, help="Reuse an existing tab with
|
|
80
|
+
@click.option("--reuse", is_flag=True, help="Reuse an existing tab with a nearly identical URL (ignores fragment, trailing slash, query order)")
|
|
81
|
+
@click.option("--reuse-exact", is_flag=True, help="Reuse an existing tab only when its URL matches character for character")
|
|
80
82
|
@click.option("--reuse-domain", is_flag=True, help="Reuse an existing tab with the same domain")
|
|
81
83
|
@click.option("--reuse-title", default=None, metavar="TEXT", help="Reuse an existing tab whose title contains TEXT")
|
|
82
84
|
@handle_errors
|
|
83
|
-
def cmd_open_wait(url, timeout, focus, window_name, group_name, reuse, reuse_domain, reuse_title):
|
|
85
|
+
def cmd_open_wait(url, timeout, focus, window_name, group_name, reuse, reuse_exact, reuse_domain, reuse_title):
|
|
84
86
|
"""Open URL in a new tab and wait until fully loaded."""
|
|
85
|
-
tab = client_from_ctx().nav.open_wait(url, timeout=timeout, focus=focus, window=window_name, group=group_name, reuse=reuse, reuse_domain=reuse_domain, reuse_title=reuse_title)
|
|
87
|
+
tab = client_from_ctx().nav.open_wait(url, timeout=timeout, focus=focus, window=window_name, group=group_name, reuse="exact" if reuse_exact else reuse, reuse_domain=reuse_domain, reuse_title=reuse_title)
|
|
86
88
|
console.print(f"[green]Loaded:[/green] {url}" + (f" — {tab.title}" if tab.title else ""))
|
|
87
89
|
|
|
88
90
|
@nav_group.command("wait")
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import click
|
|
2
|
-
from browser_cli.commands import client_from_ctx, handle_errors
|
|
2
|
+
from browser_cli.commands import client_from_ctx, handle_errors, tab_option
|
|
3
3
|
from rich.console import Console
|
|
4
4
|
from rich.table import Table
|
|
5
5
|
|
|
@@ -10,10 +10,11 @@ def page_group():
|
|
|
10
10
|
"""Inspect current page metadata."""
|
|
11
11
|
|
|
12
12
|
@page_group.command("info")
|
|
13
|
+
@tab_option
|
|
13
14
|
@handle_errors
|
|
14
|
-
def page_info():
|
|
15
|
-
"""Show title, URL, readyState, language, and meta tags of the active tab."""
|
|
16
|
-
info = client_from_ctx().page.info()
|
|
15
|
+
def page_info(tab_id):
|
|
16
|
+
"""Show title, URL, readyState, language, and meta tags of the active tab or --tab."""
|
|
17
|
+
info = client_from_ctx().page.info(tab_id)
|
|
17
18
|
table = Table(show_header=False)
|
|
18
19
|
table.add_column("Field", style="bold cyan", no_wrap=True)
|
|
19
20
|
table.add_column("Value")
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Tool-name prefixing for the MCP surface.
|
|
2
|
+
|
|
3
|
+
Hosts differ in how they namespace MCP tools. Hosts that already prefix tool
|
|
4
|
+
names with the server name turn the built-in ``browser_`` prefix into stutter
|
|
5
|
+
(``browser_cli_testing_browser_tabs_list``), while hosts that expose raw names
|
|
6
|
+
need the prefix to keep ``navigate`` or ``screenshot`` unambiguous. The prefix
|
|
7
|
+
is therefore configurable per MCP server process.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
import re
|
|
13
|
+
|
|
14
|
+
TOOL_PREFIX_ENV = "BROWSER_CLI_MCP_TOOL_PREFIX"
|
|
15
|
+
DEFAULT_TOOL_PREFIX = "browser_"
|
|
16
|
+
|
|
17
|
+
_VALID_PREFIX = re.compile(r"\A[a-z][a-z0-9_]*\Z")
|
|
18
|
+
|
|
19
|
+
def resolve_tool_prefix(environ: dict[str, str] | None = None) -> str:
|
|
20
|
+
"""Return the configured tool-name prefix, defaulting to ``browser_``.
|
|
21
|
+
|
|
22
|
+
An empty value disables prefixing for hosts that namespace tools themselves.
|
|
23
|
+
"""
|
|
24
|
+
configured = (environ if environ is not None else os.environ).get(TOOL_PREFIX_ENV)
|
|
25
|
+
if configured is None:
|
|
26
|
+
return DEFAULT_TOOL_PREFIX
|
|
27
|
+
prefix = configured.strip()
|
|
28
|
+
if not prefix:
|
|
29
|
+
return ""
|
|
30
|
+
if not _VALID_PREFIX.match(prefix):
|
|
31
|
+
raise ValueError(
|
|
32
|
+
f"{TOOL_PREFIX_ENV} must be lowercase letters, digits, and underscores starting "
|
|
33
|
+
f"with a letter, or empty to disable prefixing; got {configured!r}"
|
|
34
|
+
)
|
|
35
|
+
return prefix if prefix.endswith("_") else f"{prefix}_"
|
|
36
|
+
|
|
37
|
+
def tool_name(base: str, prefix: str) -> str:
|
|
38
|
+
"""Apply *prefix* to a bare tool name such as ``tabs_list``."""
|
|
39
|
+
return f"{prefix}{base}"
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Convert browser-cli SDK models into MCP structured-output values."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from dataclasses import fields, is_dataclass
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
def structured(value: Any) -> Any:
|
|
8
|
+
"""Return JSON-compatible data without private SDK binding fields."""
|
|
9
|
+
if is_dataclass(value) and not isinstance(value, type):
|
|
10
|
+
return {
|
|
11
|
+
field.name: structured(getattr(value, field.name))
|
|
12
|
+
for field in fields(value)
|
|
13
|
+
if not field.name.startswith("_")
|
|
14
|
+
}
|
|
15
|
+
if isinstance(value, dict):
|
|
16
|
+
return {str(key): structured(item) for key, item in value.items()}
|
|
17
|
+
if isinstance(value, (list, tuple, set)):
|
|
18
|
+
return [structured(item) for item in value]
|
|
19
|
+
return value
|