ai-browser-toolkit 0.2.2__tar.gz → 0.2.3__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.
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/AGENTS.md +4 -1
- ai_browser_toolkit-0.2.2/README.md → ai_browser_toolkit-0.2.3/PKG-INFO +97 -28
- ai_browser_toolkit-0.2.2/PKG-INFO → ai_browser_toolkit-0.2.3/README.md +63 -48
- ai_browser_toolkit-0.2.3/pyproject.toml +91 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/mcp.py +105 -9
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/server.py +31 -2
- ai_browser_toolkit-0.2.2/pyproject.toml +0 -49
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/.gitattributes +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/.github/workflows/check-secrets.yml +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/.github/workflows/ci.yml +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/.github/workflows/release.yml +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/.gitignore +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/.sheet-url +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/CLAUDE.md +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/GEMINI.md +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/LICENSE +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/docs/TODO.md +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/docs/engine-choice-for-profile-sessions.md +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/docs/known-issues.md +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/docs/packaging.md +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/docs/playwright-spike-2026-08-19.md +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/docs/session-review-20260806-linkedin.md +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/docs/superpowers/plans/2026-08-19-browser-server-decoupling.md +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/docs/superpowers/plans/2026-08-20-packaging-and-distribution.md +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/docs/superpowers/specs/2026-08-03-selenium-browser-api-design.md +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/docs/superpowers/specs/2026-08-19-browser-server-decoupling-design.md +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/docs/superpowers/specs/2026-08-19-profile-sessions-design.md +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/docs/superpowers/specs/2026-08-20-packaging-and-distribution-design.md +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/guidelines/README.md +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/guidelines/docs.google.com/docs.md +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/guidelines/docs.google.com/meta.json +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/guidelines/docs.google.com/sheets.md +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/guidelines/index.json +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/guidelines/messenger.com/messenger.md +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/guidelines/messenger.com/meta.json +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/guidelines/script.google.com/forms-apps-script.md +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/guidelines/script.google.com/meta.json +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/guidelines/toolkit-workflow.md +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/packaging/aur/PKGBUILD.template +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/packaging/aur/aibrowsertoolkit-bin.install +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/packaging/build_index.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/packaging/bundle.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/packaging/homebrew/formula.rb.template +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/packaging/nfpm.yaml +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/packaging/postinstall.sh +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/packaging/preremove.sh +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/packaging/scoop/manifest.json.template +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/packaging/smoke.sh +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/packaging/windows/abt.iss +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/__init__.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/__main__.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/autostart.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/browser.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/cli.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/diff.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/doctor.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/engine.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/errors.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/frames.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/guidelines.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/launch.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/messenger.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/ops/__init__.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/ops/control.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/ops/inspect.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/ops/interact.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/ops/navigate.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/ops/read.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/ops/tabs.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/paths.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/proc.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/pwdriver.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/recorder.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/refs.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/schema.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/shadow.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/shots.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/targeting.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/viewer.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/start-server.bat +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/start-server.sh +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/conftest.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/fixtures/actionable.html +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/fixtures/canvas.html +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/fixtures/cards.html +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/fixtures/chainfetch.html +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/fixtures/controlled.html +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/fixtures/controlled_trusted.html +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/fixtures/delayed.html +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/fixtures/form.html +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/fixtures/framed_button.html +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/fixtures/framed_preload.html +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/fixtures/frames.html +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/fixtures/frames_cross.html +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/fixtures/frames_order.html +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/fixtures/frames_swap.html +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/fixtures/inspect.html +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/fixtures/late.html +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/fixtures/links.html +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/fixtures/messenger.html +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/fixtures/nav.html +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/fixtures/overlay.html +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/fixtures/repeated.html +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/fixtures/shadow.html +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/fixtures/slowfetch.html +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_actionable.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_autostart.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_bundle.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_channel_names.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_click_at.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_diff.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_doctor.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_engine.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_frames.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_guidelines.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_hit_test.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_inspect.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_launch.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_lifecycle.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_lifecycle_live.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_mcp.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_messenger.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_ops_click_options.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_ops_interact.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_ops_navigate.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_ops_read.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_ops_tabs.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_paths.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_proc.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_recorder.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_schema.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_server.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_server_logs.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_settle.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_shadow.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_shots.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_shots_live.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_targeting.py +0 -0
- {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_workflows.py +0 -0
|
@@ -161,7 +161,10 @@ guessing entirely. Point your client at it:
|
|
|
161
161
|
{"command": "abt", "args": ["mcp"]}
|
|
162
162
|
```
|
|
163
163
|
|
|
164
|
-
It forwards to the same server, so start `abt
|
|
164
|
+
It forwards to the same server, so start one first with `abt up` (never `abt
|
|
165
|
+
serve` from a tool call — it is a command loop that never returns). The tools
|
|
166
|
+
carry their own schemas, `browser_guidelines` reads this document and any site
|
|
167
|
+
playbook, and the connection opens with instructions covering the rest.
|
|
165
168
|
|
|
166
169
|
## Site playbooks
|
|
167
170
|
|
|
@@ -1,30 +1,96 @@
|
|
|
1
|
-
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: ai-browser-toolkit
|
|
3
|
+
Version: 0.2.3
|
|
4
|
+
Summary: Drive a real Chrome/Edge from your coding agent - CLI, MCP and HTTP. For Claude Code, Codex, Cursor, OpenCode.
|
|
5
|
+
Project-URL: Homepage, https://github.com/skssmd/Ai-Browser-Toolkit
|
|
6
|
+
Project-URL: Repository, https://github.com/skssmd/Ai-Browser-Toolkit
|
|
7
|
+
Project-URL: Issues, https://github.com/skssmd/Ai-Browser-Toolkit/issues
|
|
8
|
+
Project-URL: Playbooks, https://github.com/skssmd/ABT-Playbooks
|
|
9
|
+
License-Expression: Apache-2.0
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: agentic,ai-agent,browser-automation,chrome,claude-code,cli,codex,cursor,mcp,model-context-protocol,opencode,playwright,selenium
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Browsers
|
|
21
|
+
Classifier: Topic :: Software Development :: Testing :: Acceptance
|
|
22
|
+
Classifier: Topic :: Utilities
|
|
23
|
+
Requires-Python: >=3.11
|
|
24
|
+
Requires-Dist: fastapi>=0.110
|
|
25
|
+
Requires-Dist: httpx>=0.27
|
|
26
|
+
Requires-Dist: playwright>=1.50
|
|
27
|
+
Requires-Dist: pydantic>=2.6
|
|
28
|
+
Requires-Dist: selenium>=4.20
|
|
29
|
+
Requires-Dist: typer>=0.12
|
|
30
|
+
Requires-Dist: uvicorn>=0.29
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
33
|
+
Requires-Dist: pyyaml>=6.0; extra == 'dev'
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
|
|
36
|
+
# AI Browser Toolkit
|
|
37
|
+
|
|
38
|
+
**Give your coding agent a real browser.** `abt` drives an actual Chrome or Edge
|
|
39
|
+
window — the one with your logins already in it — and hands back what changed
|
|
40
|
+
after every action, in a form a model can act on directly.
|
|
41
|
+
|
|
42
|
+
Built for agent harnesses: **Claude Code, Codex, OpenCode, Cursor, Gemini CLI,
|
|
43
|
+
Copilot**, or anything you write yourself. Three ways in, same browser behind
|
|
44
|
+
all of them:
|
|
45
|
+
|
|
46
|
+
| | Use it when |
|
|
47
|
+
|---|---|
|
|
48
|
+
| **CLI** — `abt goto`, `abt find`, `abt click` | The agent already has a shell. Nothing to configure. |
|
|
49
|
+
| **MCP** — `abt mcp` over stdio | Your client speaks MCP. Typed schemas, no shell quoting. |
|
|
50
|
+
| **HTTP** — `POST /command` on :8765 | You are writing the integration yourself. |
|
|
2
51
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
52
|
+
Why not a headless scraper: it keeps **one long-lived browser on a persistent
|
|
53
|
+
profile**. Log into Gmail, your CRM, your ticket system once, by hand — every
|
|
54
|
+
agent session after that is already signed in, and the window outlives your
|
|
55
|
+
editor.
|
|
6
56
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
57
|
+
```
|
|
58
|
+
abt up ──starts──> server :8765 ──owns──> Chrome/Edge (your profile)
|
|
59
|
+
^
|
|
60
|
+
abt CLI / abt mcp / your HTTP client
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
> **Agents: read the workflow before driving anything** — `abt guidelines show
|
|
64
|
+
> toolkit-workflow`, or [`guidelines/toolkit-workflow.md`](https://github.com/skssmd/Ai-Browser-Toolkit/blob/main/guidelines/toolkit-workflow.md)
|
|
65
|
+
> in this repo. Not "if your site looks tricky", not "if there is a playbook for
|
|
66
|
+
> it" — always. Most sites have no playbook, and that file is the whole answer
|
|
67
|
+
> for them.
|
|
12
68
|
>
|
|
13
|
-
> Skipping it costs
|
|
14
|
-
>
|
|
15
|
-
>
|
|
69
|
+
> Skipping it costs the things that are hard to guess: that `find` hands back
|
|
70
|
+
> refs you act on directly, that a click already reports what changed so you
|
|
71
|
+
> need not re-read the page, and that ops can be sent in batches.
|
|
72
|
+
|
|
73
|
+
## Install
|
|
16
74
|
|
|
75
|
+
Needs **Python 3.11+** and **Google Chrome** or **Microsoft Edge** installed.
|
|
76
|
+
Drivers are resolved automatically — nothing to download by hand.
|
|
17
77
|
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
abt / curl / your agent ──┘ POST /command | POST /commands
|
|
78
|
+
```bash
|
|
79
|
+
pip install ai-browser-toolkit
|
|
80
|
+
abt doctor # what browsers are installed, and where
|
|
22
81
|
```
|
|
23
82
|
|
|
24
|
-
|
|
83
|
+
Also on **winget** (`winget install skssmd.AIBrowserToolkit`), **Scoop**,
|
|
84
|
+
**Homebrew** (`brew install skssmd/tap/abt`) and the **AUR**.
|
|
85
|
+
|
|
86
|
+
Point an MCP client at it:
|
|
25
87
|
|
|
26
|
-
|
|
27
|
-
|
|
88
|
+
```json
|
|
89
|
+
{"mcpServers": {"browser": {"command": "abt", "args": ["mcp"]}}}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
<details>
|
|
93
|
+
<summary>From a source checkout instead</summary>
|
|
28
94
|
|
|
29
95
|
```bash
|
|
30
96
|
git clone https://github.com/skssmd/Ai-Browser-Toolkit
|
|
@@ -33,12 +99,7 @@ cd Ai-Browser-Toolkit
|
|
|
33
99
|
python -m venv .venv
|
|
34
100
|
.venv/Scripts/python -m pip install -e ".[dev]" # Windows
|
|
35
101
|
# .venv/bin/python -m pip install -e ".[dev]" # macOS / Linux
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
Check it worked:
|
|
39
|
-
|
|
40
|
-
```bash
|
|
41
|
-
.venv/Scripts/python -m pytest -q # 485 tests, needs Chrome
|
|
102
|
+
.venv/Scripts/python -m pytest -q # 485 tests, needs Chrome
|
|
42
103
|
```
|
|
43
104
|
|
|
44
105
|
The `abt` command lands in the venv. Either activate it
|
|
@@ -46,6 +107,8 @@ The `abt` command lands in the venv. Either activate it
|
|
|
46
107
|
PATH, or call it explicitly as `.venv/Scripts/python -m abt.cli …` everywhere
|
|
47
108
|
below.
|
|
48
109
|
|
|
110
|
+
</details>
|
|
111
|
+
|
|
49
112
|
## Run
|
|
50
113
|
|
|
51
114
|
> **Start the server as a separate process.** `abt serve` is the command loop —
|
|
@@ -262,7 +325,7 @@ returns only what arrived since your last read of that thread — matched by
|
|
|
262
325
|
content, not position, because Messenger trims the top of a long thread as it
|
|
263
326
|
grows.
|
|
264
327
|
|
|
265
|
-
Full details and the traps behind them: [guidelines/messenger.com/messenger.md](guidelines/messenger.com/messenger.md).
|
|
328
|
+
Full details and the traps behind them: [guidelines/messenger.com/messenger.md](https://github.com/skssmd/Ai-Browser-Toolkit/blob/main/guidelines/messenger.com/messenger.md).
|
|
266
329
|
|
|
267
330
|
## Console and network
|
|
268
331
|
|
|
@@ -615,7 +678,7 @@ needed to turn something you can read into something you can act on.
|
|
|
615
678
|
**The limit:** `mode: "closed"` makes `.shadowRoot` null. No JavaScript can read
|
|
616
679
|
such a root or prove it exists, so nothing here can either. "Not found" means
|
|
617
680
|
nothing reachable has it — see the search ladder in
|
|
618
|
-
[`guidelines/toolkit-workflow.md`](guidelines/toolkit-workflow.md).
|
|
681
|
+
[`guidelines/toolkit-workflow.md`](https://github.com/skssmd/Ai-Browser-Toolkit/blob/main/guidelines/toolkit-workflow.md).
|
|
619
682
|
|
|
620
683
|
### The manual check
|
|
621
684
|
|
|
@@ -877,10 +940,16 @@ guessed parameter name — `label` on a `select`, `diff` on a `get_text`, a
|
|
|
877
940
|
the JSON never meets a shell, which on Windows is its own source of quoting
|
|
878
941
|
failures.
|
|
879
942
|
|
|
880
|
-
|
|
943
|
+
Seventeen tools rather than one per op, since every schema sits in the model's
|
|
881
944
|
context for the whole session. `browser_batch` sends a whole sequence in one
|
|
882
945
|
call, and `browser_command` passes through any raw op the named tools miss.
|
|
883
946
|
|
|
947
|
+
The server also answers `initialize` with MCP `instructions` — how the pieces
|
|
948
|
+
fit together, which most clients place in the system prompt once. That is where
|
|
949
|
+
the orientation goes, because it is paid for once rather than re-sent with the
|
|
950
|
+
schemas on every turn. `browser_guidelines` reads the rest: the whole
|
|
951
|
+
workflow document, or a fuzzy search for a site's playbook.
|
|
952
|
+
|
|
884
953
|
## Tests
|
|
885
954
|
|
|
886
955
|
```bash
|
|
@@ -1,49 +1,61 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
Requires-Dist: playwright>=1.50
|
|
11
|
-
Requires-Dist: pydantic>=2.6
|
|
12
|
-
Requires-Dist: selenium>=4.20
|
|
13
|
-
Requires-Dist: typer>=0.12
|
|
14
|
-
Requires-Dist: uvicorn>=0.29
|
|
15
|
-
Provides-Extra: dev
|
|
16
|
-
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
17
|
-
Requires-Dist: pyyaml>=6.0; extra == 'dev'
|
|
18
|
-
Description-Content-Type: text/markdown
|
|
19
|
-
|
|
20
|
-
# aibrowsertoolkit
|
|
21
|
-
|
|
22
|
-
An HTTP server that lets an AI agent drive a real browser — **Chrome or Edge** — by
|
|
23
|
-
sending JSON. The server process is the loop: it opens the chosen browser with a persistent
|
|
24
|
-
profile, stays up waiting for commands, and only stops when you send `shutdown`.
|
|
25
|
-
|
|
26
|
-
> **Agents: read [`guidelines/toolkit-workflow.md`](guidelines/toolkit-workflow.md)
|
|
27
|
-
> before driving anything.** Not "if your site looks tricky", not "if there is a
|
|
28
|
-
> playbook for it" — always. Most sites have no playbook, and that file is the
|
|
29
|
-
> whole answer for them. Then, only if one exists, add the site playbook from
|
|
30
|
-
> [`guidelines/`](guidelines/README.md).
|
|
31
|
-
>
|
|
32
|
-
> Skipping it costs you the things that are hard to guess: that `find` hands
|
|
33
|
-
> back refs you act on directly, that a click already reports what changed so
|
|
34
|
-
> you need not re-read the page, and that ops can be sent in batches.
|
|
1
|
+
# AI Browser Toolkit
|
|
2
|
+
|
|
3
|
+
**Give your coding agent a real browser.** `abt` drives an actual Chrome or Edge
|
|
4
|
+
window — the one with your logins already in it — and hands back what changed
|
|
5
|
+
after every action, in a form a model can act on directly.
|
|
6
|
+
|
|
7
|
+
Built for agent harnesses: **Claude Code, Codex, OpenCode, Cursor, Gemini CLI,
|
|
8
|
+
Copilot**, or anything you write yourself. Three ways in, same browser behind
|
|
9
|
+
all of them:
|
|
35
10
|
|
|
11
|
+
| | Use it when |
|
|
12
|
+
|---|---|
|
|
13
|
+
| **CLI** — `abt goto`, `abt find`, `abt click` | The agent already has a shell. Nothing to configure. |
|
|
14
|
+
| **MCP** — `abt mcp` over stdio | Your client speaks MCP. Typed schemas, no shell quoting. |
|
|
15
|
+
| **HTTP** — `POST /command` on :8765 | You are writing the integration yourself. |
|
|
16
|
+
|
|
17
|
+
Why not a headless scraper: it keeps **one long-lived browser on a persistent
|
|
18
|
+
profile**. Log into Gmail, your CRM, your ticket system once, by hand — every
|
|
19
|
+
agent session after that is already signed in, and the window outlives your
|
|
20
|
+
editor.
|
|
36
21
|
|
|
37
22
|
```
|
|
38
|
-
abt
|
|
39
|
-
|
|
40
|
-
abt /
|
|
23
|
+
abt up ──starts──> server :8765 ──owns──> Chrome/Edge (your profile)
|
|
24
|
+
^
|
|
25
|
+
abt CLI / abt mcp / your HTTP client
|
|
41
26
|
```
|
|
42
27
|
|
|
28
|
+
> **Agents: read the workflow before driving anything** — `abt guidelines show
|
|
29
|
+
> toolkit-workflow`, or [`guidelines/toolkit-workflow.md`](https://github.com/skssmd/Ai-Browser-Toolkit/blob/main/guidelines/toolkit-workflow.md)
|
|
30
|
+
> in this repo. Not "if your site looks tricky", not "if there is a playbook for
|
|
31
|
+
> it" — always. Most sites have no playbook, and that file is the whole answer
|
|
32
|
+
> for them.
|
|
33
|
+
>
|
|
34
|
+
> Skipping it costs the things that are hard to guess: that `find` hands back
|
|
35
|
+
> refs you act on directly, that a click already reports what changed so you
|
|
36
|
+
> need not re-read the page, and that ops can be sent in batches.
|
|
37
|
+
|
|
43
38
|
## Install
|
|
44
39
|
|
|
45
|
-
Needs **Python 3.11+** and **Google Chrome** or **Microsoft Edge** installed.
|
|
46
|
-
|
|
40
|
+
Needs **Python 3.11+** and **Google Chrome** or **Microsoft Edge** installed.
|
|
41
|
+
Drivers are resolved automatically — nothing to download by hand.
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install ai-browser-toolkit
|
|
45
|
+
abt doctor # what browsers are installed, and where
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Also on **winget** (`winget install skssmd.AIBrowserToolkit`), **Scoop**,
|
|
49
|
+
**Homebrew** (`brew install skssmd/tap/abt`) and the **AUR**.
|
|
50
|
+
|
|
51
|
+
Point an MCP client at it:
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{"mcpServers": {"browser": {"command": "abt", "args": ["mcp"]}}}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
<details>
|
|
58
|
+
<summary>From a source checkout instead</summary>
|
|
47
59
|
|
|
48
60
|
```bash
|
|
49
61
|
git clone https://github.com/skssmd/Ai-Browser-Toolkit
|
|
@@ -52,12 +64,7 @@ cd Ai-Browser-Toolkit
|
|
|
52
64
|
python -m venv .venv
|
|
53
65
|
.venv/Scripts/python -m pip install -e ".[dev]" # Windows
|
|
54
66
|
# .venv/bin/python -m pip install -e ".[dev]" # macOS / Linux
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
Check it worked:
|
|
58
|
-
|
|
59
|
-
```bash
|
|
60
|
-
.venv/Scripts/python -m pytest -q # 485 tests, needs Chrome
|
|
67
|
+
.venv/Scripts/python -m pytest -q # 485 tests, needs Chrome
|
|
61
68
|
```
|
|
62
69
|
|
|
63
70
|
The `abt` command lands in the venv. Either activate it
|
|
@@ -65,6 +72,8 @@ The `abt` command lands in the venv. Either activate it
|
|
|
65
72
|
PATH, or call it explicitly as `.venv/Scripts/python -m abt.cli …` everywhere
|
|
66
73
|
below.
|
|
67
74
|
|
|
75
|
+
</details>
|
|
76
|
+
|
|
68
77
|
## Run
|
|
69
78
|
|
|
70
79
|
> **Start the server as a separate process.** `abt serve` is the command loop —
|
|
@@ -281,7 +290,7 @@ returns only what arrived since your last read of that thread — matched by
|
|
|
281
290
|
content, not position, because Messenger trims the top of a long thread as it
|
|
282
291
|
grows.
|
|
283
292
|
|
|
284
|
-
Full details and the traps behind them: [guidelines/messenger.com/messenger.md](guidelines/messenger.com/messenger.md).
|
|
293
|
+
Full details and the traps behind them: [guidelines/messenger.com/messenger.md](https://github.com/skssmd/Ai-Browser-Toolkit/blob/main/guidelines/messenger.com/messenger.md).
|
|
285
294
|
|
|
286
295
|
## Console and network
|
|
287
296
|
|
|
@@ -634,7 +643,7 @@ needed to turn something you can read into something you can act on.
|
|
|
634
643
|
**The limit:** `mode: "closed"` makes `.shadowRoot` null. No JavaScript can read
|
|
635
644
|
such a root or prove it exists, so nothing here can either. "Not found" means
|
|
636
645
|
nothing reachable has it — see the search ladder in
|
|
637
|
-
[`guidelines/toolkit-workflow.md`](guidelines/toolkit-workflow.md).
|
|
646
|
+
[`guidelines/toolkit-workflow.md`](https://github.com/skssmd/Ai-Browser-Toolkit/blob/main/guidelines/toolkit-workflow.md).
|
|
638
647
|
|
|
639
648
|
### The manual check
|
|
640
649
|
|
|
@@ -896,10 +905,16 @@ guessed parameter name — `label` on a `select`, `diff` on a `get_text`, a
|
|
|
896
905
|
the JSON never meets a shell, which on Windows is its own source of quoting
|
|
897
906
|
failures.
|
|
898
907
|
|
|
899
|
-
|
|
908
|
+
Seventeen tools rather than one per op, since every schema sits in the model's
|
|
900
909
|
context for the whole session. `browser_batch` sends a whole sequence in one
|
|
901
910
|
call, and `browser_command` passes through any raw op the named tools miss.
|
|
902
911
|
|
|
912
|
+
The server also answers `initialize` with MCP `instructions` — how the pieces
|
|
913
|
+
fit together, which most clients place in the system prompt once. That is where
|
|
914
|
+
the orientation goes, because it is paid for once rather than re-sent with the
|
|
915
|
+
schemas on every turn. `browser_guidelines` reads the rest: the whole
|
|
916
|
+
workflow document, or a fuzzy search for a site's playbook.
|
|
917
|
+
|
|
903
918
|
## Tests
|
|
904
919
|
|
|
905
920
|
```bash
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ai-browser-toolkit"
|
|
7
|
+
version = "0.2.3"
|
|
8
|
+
# One line, and it is what PyPI search results show. It has to answer "for
|
|
9
|
+
# whom" as well as "what": a browser-automation library is a crowded shelf, and
|
|
10
|
+
# the thing that makes this one different is that a coding agent drives it,
|
|
11
|
+
# through whichever of the three interfaces that agent already speaks.
|
|
12
|
+
description = "Drive a real Chrome/Edge from your coding agent - CLI, MCP and HTTP. For Claude Code, Codex, Cursor, OpenCode."
|
|
13
|
+
keywords = [
|
|
14
|
+
"mcp",
|
|
15
|
+
"model-context-protocol",
|
|
16
|
+
"browser-automation",
|
|
17
|
+
"ai-agent",
|
|
18
|
+
"agentic",
|
|
19
|
+
"claude-code",
|
|
20
|
+
"codex",
|
|
21
|
+
"opencode",
|
|
22
|
+
"cursor",
|
|
23
|
+
"playwright",
|
|
24
|
+
"selenium",
|
|
25
|
+
"chrome",
|
|
26
|
+
"cli",
|
|
27
|
+
]
|
|
28
|
+
classifiers = [
|
|
29
|
+
"Development Status :: 4 - Beta",
|
|
30
|
+
"Environment :: Console",
|
|
31
|
+
"Intended Audience :: Developers",
|
|
32
|
+
"License :: OSI Approved :: Apache Software License",
|
|
33
|
+
"Operating System :: OS Independent",
|
|
34
|
+
"Programming Language :: Python :: 3.11",
|
|
35
|
+
"Programming Language :: Python :: 3.12",
|
|
36
|
+
"Programming Language :: Python :: 3.13",
|
|
37
|
+
"Topic :: Internet :: WWW/HTTP :: Browsers",
|
|
38
|
+
"Topic :: Software Development :: Testing :: Acceptance",
|
|
39
|
+
"Topic :: Utilities",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
# PyPI renders this as the whole project page. Without it the page is a blank
|
|
43
|
+
# stub carrying only the one-line description above -- which is what every
|
|
44
|
+
# `pip install ai-browser-toolkit` sends people to look at.
|
|
45
|
+
readme = "README.md"
|
|
46
|
+
license = "Apache-2.0"
|
|
47
|
+
requires-python = ">=3.11"
|
|
48
|
+
dependencies = [
|
|
49
|
+
# Playwright is the default engine. Selenium is still required rather
|
|
50
|
+
# than optional because `engine.py` derives the neutral exception names
|
|
51
|
+
# from Selenium's classes at import time -- making it opt-in means
|
|
52
|
+
# defining those natively first. See docs/TODO.md.
|
|
53
|
+
"playwright>=1.50",
|
|
54
|
+
"selenium>=4.20",
|
|
55
|
+
"fastapi>=0.110",
|
|
56
|
+
"uvicorn>=0.29",
|
|
57
|
+
"pydantic>=2.6",
|
|
58
|
+
"httpx>=0.27",
|
|
59
|
+
"typer>=0.12",
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
# The PyPI sidebar. Without these the page offers no way back to the source,
|
|
63
|
+
# and a reader who wants to know what a playbook actually contains before
|
|
64
|
+
# trusting one has nowhere to go.
|
|
65
|
+
[project.urls]
|
|
66
|
+
Homepage = "https://github.com/skssmd/Ai-Browser-Toolkit"
|
|
67
|
+
Repository = "https://github.com/skssmd/Ai-Browser-Toolkit"
|
|
68
|
+
Issues = "https://github.com/skssmd/Ai-Browser-Toolkit/issues"
|
|
69
|
+
Playbooks = "https://github.com/skssmd/ABT-Playbooks"
|
|
70
|
+
|
|
71
|
+
[project.optional-dependencies]
|
|
72
|
+
dev = ["pytest>=8.0", "pyyaml>=6.0"]
|
|
73
|
+
|
|
74
|
+
[project.scripts]
|
|
75
|
+
abt = "abt.cli:app"
|
|
76
|
+
|
|
77
|
+
[tool.hatch.build.targets.wheel]
|
|
78
|
+
packages = ["src/abt"]
|
|
79
|
+
# onehr names a real organisation, its routes and its staff records. It is
|
|
80
|
+
# gitignored, but a build reads the working tree rather than git -- so without
|
|
81
|
+
# this, anyone building from a checkout that still has those files would ship
|
|
82
|
+
# them to PyPI. A test asserts they are absent from the built wheel.
|
|
83
|
+
exclude = ["**/onehr/**", "**/*onehr*"]
|
|
84
|
+
|
|
85
|
+
[tool.hatch.build.targets.wheel.force-include]
|
|
86
|
+
# Shipped inside the package so every channel carries them, and so the server
|
|
87
|
+
# can serve one to an agent that is already talking to it.
|
|
88
|
+
"guidelines" = "abt/guidelines"
|
|
89
|
+
|
|
90
|
+
[tool.pytest.ini_options]
|
|
91
|
+
testpaths = ["tests"]
|
|
@@ -36,6 +36,48 @@ import httpx
|
|
|
36
36
|
PROTOCOL_VERSION = "2025-06-18"
|
|
37
37
|
DEFAULT_API = "http://127.0.0.1:8765"
|
|
38
38
|
|
|
39
|
+
# Sent once, in the initialize reply, and clients put it in the system prompt.
|
|
40
|
+
# That is the cheap slot: per-tool descriptions are re-sent every turn, so
|
|
41
|
+
# anything that is true of the *toolkit* rather than of one tool belongs here
|
|
42
|
+
# and is paid for once. Without it a model knows what each tool does and still
|
|
43
|
+
# does not know that the browser is a separate step from the server, or that
|
|
44
|
+
# the reply it already holds contains the page.
|
|
45
|
+
INSTRUCTIONS = """\
|
|
46
|
+
Browser automation against one long-lived Chrome. The server is a separate
|
|
47
|
+
process that outlives this session; it owns the browser and this connection
|
|
48
|
+
owns nothing.
|
|
49
|
+
|
|
50
|
+
Start here:
|
|
51
|
+
|
|
52
|
+
browser_session {"action":"start"} -- the server runs WITHOUT a browser on
|
|
53
|
+
purpose, and nothing starts one for you. Every page tool fails with
|
|
54
|
+
browser_dead until you do this. It can take up to two minutes on a profile
|
|
55
|
+
that has logins in it. browser_session {"action":"status"} says what is up.
|
|
56
|
+
|
|
57
|
+
Then read what you are given. Every tool that changes the page returns a
|
|
58
|
+
dom_diff: the text that appeared, and dom_diff.actionable listing new controls
|
|
59
|
+
with refs. Re-reading the page to see what happened is the most common and most
|
|
60
|
+
expensive mistake made with this toolkit -- the answer is already in the reply.
|
|
61
|
+
|
|
62
|
+
Addressing an element: exactly ONE of ref, css, xpath or text. Refs come from
|
|
63
|
+
browser_find and from a diff's actionable list, and they die on navigation.
|
|
64
|
+
|
|
65
|
+
browser_find searches the document, every frame and (with shadow=true) open
|
|
66
|
+
shadow roots. count=0 is an answer, not a bad selector -- the control does not
|
|
67
|
+
exist yet, so click whatever creates it. Do not fall back to browser_run_js to
|
|
68
|
+
scan the DOM.
|
|
69
|
+
|
|
70
|
+
browser_batch runs a sequence you already know in one round trip. Prefer it
|
|
71
|
+
over one call per step.
|
|
72
|
+
|
|
73
|
+
Site playbooks: before driving an unfamiliar site call browser_guidelines with
|
|
74
|
+
that domain. Some sites have a written procedure, and it is shorter than
|
|
75
|
+
discovering the page. browser_guidelines {"name":"toolkit-workflow"} is the
|
|
76
|
+
full workflow document.
|
|
77
|
+
|
|
78
|
+
Every failure carries a `hint` saying what to do next. Read it before retrying.
|
|
79
|
+
"""
|
|
80
|
+
|
|
39
81
|
_TARGET = {
|
|
40
82
|
"ref": {"type": "string", "description": "Ref from a find, or from a diff's actionable list."},
|
|
41
83
|
"css": {"type": "string"},
|
|
@@ -221,6 +263,19 @@ TOOLS: list[dict] = [
|
|
|
221
263
|
),
|
|
222
264
|
"inputSchema": _schema({"command": {"type": "object"}}, ["command"]),
|
|
223
265
|
},
|
|
266
|
+
{
|
|
267
|
+
"name": "browser_guidelines",
|
|
268
|
+
"description": (
|
|
269
|
+
"The toolkit's own docs. domain=<host> asks whether a written "
|
|
270
|
+
"playbook exists for a site -- fuzzy, so 'sheets' finds "
|
|
271
|
+
'docs.google.com. name="toolkit-workflow" returns the full '
|
|
272
|
+
"workflow. Neither argument lists what is installed."
|
|
273
|
+
),
|
|
274
|
+
"inputSchema": _schema({
|
|
275
|
+
"domain": {"type": "string", "description": "Site to look up. Fuzzy."},
|
|
276
|
+
"name": {"type": "string", "description": "Playbook to read in full."},
|
|
277
|
+
}),
|
|
278
|
+
},
|
|
224
279
|
{
|
|
225
280
|
"name": "browser_session",
|
|
226
281
|
"description": (
|
|
@@ -246,6 +301,22 @@ TOOLS: list[dict] = [
|
|
|
246
301
|
]
|
|
247
302
|
|
|
248
303
|
|
|
304
|
+
def _version() -> str:
|
|
305
|
+
"""The installed version, or a placeholder from a source tree.
|
|
306
|
+
|
|
307
|
+
Hardcoding it here meant every release since 0.1.0 announced itself as
|
|
308
|
+
0.1.0 to every MCP client.
|
|
309
|
+
"""
|
|
310
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
311
|
+
|
|
312
|
+
from . import paths
|
|
313
|
+
|
|
314
|
+
try:
|
|
315
|
+
return version(paths.DIST_NAME)
|
|
316
|
+
except PackageNotFoundError:
|
|
317
|
+
return "0"
|
|
318
|
+
|
|
319
|
+
|
|
249
320
|
def _one_of(args: dict, *names: str) -> dict:
|
|
250
321
|
return {name: args[name] for name in names if args.get(name) is not None}
|
|
251
322
|
|
|
@@ -336,19 +407,40 @@ class Bridge:
|
|
|
336
407
|
|
|
337
408
|
def call(self, tool: str, args: dict) -> tuple[str, bool]:
|
|
338
409
|
"""Returns (text, is_error). Never raises -- an agent needs a message."""
|
|
410
|
+
# Playbooks are reads, not ops: they live behind GET /guidelines, and
|
|
411
|
+
# the server never serves an untrusted one. Routed here rather than in
|
|
412
|
+
# to_op because to_op's whole output shape is "an op".
|
|
413
|
+
if tool == "browser_guidelines":
|
|
414
|
+
if args.get("name"):
|
|
415
|
+
request = ("GET", f"/guidelines/{args['name']}", None)
|
|
416
|
+
elif args.get("domain"):
|
|
417
|
+
# /search, not /lookup: lookup is exact-domain only, and an
|
|
418
|
+
# agent that has just landed somewhere has a hostname it is
|
|
419
|
+
# guessing at. No match comes back as an empty list.
|
|
420
|
+
request = ("GET", "/guidelines/search", {"q": args["domain"]})
|
|
421
|
+
else:
|
|
422
|
+
request = ("GET", "/guidelines", None)
|
|
423
|
+
else:
|
|
424
|
+
try:
|
|
425
|
+
payload = to_op(tool, args)
|
|
426
|
+
except KeyError:
|
|
427
|
+
return f"no such tool: {tool}", True
|
|
428
|
+
endpoint = "/commands" if tool == "browser_batch" else "/command"
|
|
429
|
+
request = ("POST", endpoint, payload)
|
|
430
|
+
|
|
431
|
+
method, path, body = request
|
|
339
432
|
try:
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
endpoint = "/commands" if tool == "browser_batch" else "/command"
|
|
345
|
-
try:
|
|
346
|
-
response = self.client.post(f"{self.api}{endpoint}", json=payload)
|
|
433
|
+
if method == "GET":
|
|
434
|
+
response = self.client.get(f"{self.api}{path}", params=body)
|
|
435
|
+
else:
|
|
436
|
+
response = self.client.post(f"{self.api}{path}", json=body)
|
|
347
437
|
except httpx.RequestError as exc:
|
|
348
438
|
return (
|
|
349
439
|
f"cannot reach the toolkit at {self.api} ({exc}). It is a separate, "
|
|
350
440
|
"long-lived process that owns the browser -- start it with "
|
|
351
|
-
"`abt
|
|
441
|
+
"`abt up` and wait for GET /status to answer. Never run "
|
|
442
|
+
"`abt serve` from a tool call: it is a command loop that never "
|
|
443
|
+
"returns, and whatever launched it hangs.",
|
|
352
444
|
True,
|
|
353
445
|
)
|
|
354
446
|
|
|
@@ -385,7 +477,11 @@ class Server:
|
|
|
385
477
|
return self._ok(request_id, {
|
|
386
478
|
"protocolVersion": PROTOCOL_VERSION,
|
|
387
479
|
"capabilities": {"tools": {}},
|
|
388
|
-
"serverInfo": {"name": "aibrowsertoolkit", "version":
|
|
480
|
+
"serverInfo": {"name": "aibrowsertoolkit", "version": _version()},
|
|
481
|
+
# Where the orientation goes. Clients that honour it put this
|
|
482
|
+
# in the system prompt once; the rest ignore it and lose
|
|
483
|
+
# nothing, because every tool still describes itself.
|
|
484
|
+
"instructions": INSTRUCTIONS,
|
|
389
485
|
})
|
|
390
486
|
|
|
391
487
|
if method == "tools/list":
|
|
@@ -353,9 +353,27 @@ def create_app(
|
|
|
353
353
|
|
|
354
354
|
found = await run_in_threadpool(g.lookup, domain)
|
|
355
355
|
if found is None:
|
|
356
|
-
return fail(
|
|
356
|
+
return fail(
|
|
357
|
+
OpError(
|
|
358
|
+
"element_not_found",
|
|
359
|
+
f"no playbook for {domain}",
|
|
360
|
+
hint=(
|
|
361
|
+
"This is an exact-domain lookup. For anything else -- a "
|
|
362
|
+
"product name, a subdomain, a guess -- use "
|
|
363
|
+
"GET /guidelines/search?q=, which is fuzzy. A site with "
|
|
364
|
+
"no playbook is normal: drive it directly."
|
|
365
|
+
),
|
|
366
|
+
)
|
|
367
|
+
)
|
|
357
368
|
return ok(found)
|
|
358
369
|
|
|
370
|
+
@app.get("/guidelines/search")
|
|
371
|
+
async def guidelines_search(q: str):
|
|
372
|
+
"""Fuzzy, and never an error: no match is an answer, not a failure."""
|
|
373
|
+
from . import guidelines as g
|
|
374
|
+
|
|
375
|
+
return ok(await run_in_threadpool(g.search, q))
|
|
376
|
+
|
|
359
377
|
@app.get("/guidelines/{name:path}")
|
|
360
378
|
async def guidelines_read(name: str):
|
|
361
379
|
from . import guidelines as g
|
|
@@ -366,7 +384,18 @@ def create_app(
|
|
|
366
384
|
# --pending` is the reviewing path, and it warns.
|
|
367
385
|
return ok({"name": name, "markdown": g.read(name)})
|
|
368
386
|
except KeyError:
|
|
369
|
-
return fail(
|
|
387
|
+
return fail(
|
|
388
|
+
OpError(
|
|
389
|
+
"element_not_found",
|
|
390
|
+
f"no playbook named {name}",
|
|
391
|
+
hint=(
|
|
392
|
+
"GET /guidelines lists what is installed and readable. "
|
|
393
|
+
"A playbook that was pulled but not trusted is not "
|
|
394
|
+
"served here at all -- `abt guidelines trust <domain>` "
|
|
395
|
+
"after a person has read it."
|
|
396
|
+
),
|
|
397
|
+
)
|
|
398
|
+
)
|
|
370
399
|
|
|
371
400
|
# --- browser lifecycle ----------------------------------------------------
|
|
372
401
|
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
[build-system]
|
|
2
|
-
requires = ["hatchling"]
|
|
3
|
-
build-backend = "hatchling.build"
|
|
4
|
-
|
|
5
|
-
[project]
|
|
6
|
-
name = "ai-browser-toolkit"
|
|
7
|
-
version = "0.2.2"
|
|
8
|
-
description = "Agentic browser automation CLI for AI agents"
|
|
9
|
-
# PyPI renders this as the whole project page. Without it the page is a blank
|
|
10
|
-
# stub carrying only the one-line description above -- which is what every
|
|
11
|
-
# `pip install ai-browser-toolkit` sends people to look at.
|
|
12
|
-
readme = "README.md"
|
|
13
|
-
license = "Apache-2.0"
|
|
14
|
-
requires-python = ">=3.11"
|
|
15
|
-
dependencies = [
|
|
16
|
-
# Playwright is the default engine. Selenium is still required rather
|
|
17
|
-
# than optional because `engine.py` derives the neutral exception names
|
|
18
|
-
# from Selenium's classes at import time -- making it opt-in means
|
|
19
|
-
# defining those natively first. See docs/TODO.md.
|
|
20
|
-
"playwright>=1.50",
|
|
21
|
-
"selenium>=4.20",
|
|
22
|
-
"fastapi>=0.110",
|
|
23
|
-
"uvicorn>=0.29",
|
|
24
|
-
"pydantic>=2.6",
|
|
25
|
-
"httpx>=0.27",
|
|
26
|
-
"typer>=0.12",
|
|
27
|
-
]
|
|
28
|
-
|
|
29
|
-
[project.optional-dependencies]
|
|
30
|
-
dev = ["pytest>=8.0", "pyyaml>=6.0"]
|
|
31
|
-
|
|
32
|
-
[project.scripts]
|
|
33
|
-
abt = "abt.cli:app"
|
|
34
|
-
|
|
35
|
-
[tool.hatch.build.targets.wheel]
|
|
36
|
-
packages = ["src/abt"]
|
|
37
|
-
# onehr names a real organisation, its routes and its staff records. It is
|
|
38
|
-
# gitignored, but a build reads the working tree rather than git -- so without
|
|
39
|
-
# this, anyone building from a checkout that still has those files would ship
|
|
40
|
-
# them to PyPI. A test asserts they are absent from the built wheel.
|
|
41
|
-
exclude = ["**/onehr/**", "**/*onehr*"]
|
|
42
|
-
|
|
43
|
-
[tool.hatch.build.targets.wheel.force-include]
|
|
44
|
-
# Shipped inside the package so every channel carries them, and so the server
|
|
45
|
-
# can serve one to an agent that is already talking to it.
|
|
46
|
-
"guidelines" = "abt/guidelines"
|
|
47
|
-
|
|
48
|
-
[tool.pytest.ini_options]
|
|
49
|
-
testpaths = ["tests"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/docs/engine-choice-for-profile-sessions.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/docs/session-review-20260806-linkedin.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/guidelines/script.google.com/meta.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/packaging/aur/aibrowsertoolkit-bin.install
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/packaging/homebrew/formula.rb.template
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/packaging/scoop/manifest.json.template
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/fixtures/controlled_trusted.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|