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.
Files changed (139) hide show
  1. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/AGENTS.md +4 -1
  2. ai_browser_toolkit-0.2.2/README.md → ai_browser_toolkit-0.2.3/PKG-INFO +97 -28
  3. ai_browser_toolkit-0.2.2/PKG-INFO → ai_browser_toolkit-0.2.3/README.md +63 -48
  4. ai_browser_toolkit-0.2.3/pyproject.toml +91 -0
  5. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/mcp.py +105 -9
  6. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/server.py +31 -2
  7. ai_browser_toolkit-0.2.2/pyproject.toml +0 -49
  8. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/.gitattributes +0 -0
  9. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/.github/workflows/check-secrets.yml +0 -0
  10. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/.github/workflows/ci.yml +0 -0
  11. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/.github/workflows/release.yml +0 -0
  12. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/.gitignore +0 -0
  13. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/.sheet-url +0 -0
  14. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/CLAUDE.md +0 -0
  15. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/GEMINI.md +0 -0
  16. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/LICENSE +0 -0
  17. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/docs/TODO.md +0 -0
  18. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/docs/engine-choice-for-profile-sessions.md +0 -0
  19. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/docs/known-issues.md +0 -0
  20. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/docs/packaging.md +0 -0
  21. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/docs/playwright-spike-2026-08-19.md +0 -0
  22. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/docs/session-review-20260806-linkedin.md +0 -0
  23. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/docs/superpowers/plans/2026-08-19-browser-server-decoupling.md +0 -0
  24. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/docs/superpowers/plans/2026-08-20-packaging-and-distribution.md +0 -0
  25. {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
  26. {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
  27. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/docs/superpowers/specs/2026-08-19-profile-sessions-design.md +0 -0
  28. {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
  29. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/guidelines/README.md +0 -0
  30. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/guidelines/docs.google.com/docs.md +0 -0
  31. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/guidelines/docs.google.com/meta.json +0 -0
  32. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/guidelines/docs.google.com/sheets.md +0 -0
  33. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/guidelines/index.json +0 -0
  34. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/guidelines/messenger.com/messenger.md +0 -0
  35. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/guidelines/messenger.com/meta.json +0 -0
  36. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/guidelines/script.google.com/forms-apps-script.md +0 -0
  37. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/guidelines/script.google.com/meta.json +0 -0
  38. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/guidelines/toolkit-workflow.md +0 -0
  39. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/packaging/aur/PKGBUILD.template +0 -0
  40. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/packaging/aur/aibrowsertoolkit-bin.install +0 -0
  41. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/packaging/build_index.py +0 -0
  42. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/packaging/bundle.py +0 -0
  43. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/packaging/homebrew/formula.rb.template +0 -0
  44. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/packaging/nfpm.yaml +0 -0
  45. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/packaging/postinstall.sh +0 -0
  46. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/packaging/preremove.sh +0 -0
  47. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/packaging/scoop/manifest.json.template +0 -0
  48. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/packaging/smoke.sh +0 -0
  49. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/packaging/windows/abt.iss +0 -0
  50. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/__init__.py +0 -0
  51. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/__main__.py +0 -0
  52. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/autostart.py +0 -0
  53. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/browser.py +0 -0
  54. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/cli.py +0 -0
  55. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/diff.py +0 -0
  56. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/doctor.py +0 -0
  57. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/engine.py +0 -0
  58. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/errors.py +0 -0
  59. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/frames.py +0 -0
  60. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/guidelines.py +0 -0
  61. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/launch.py +0 -0
  62. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/messenger.py +0 -0
  63. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/ops/__init__.py +0 -0
  64. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/ops/control.py +0 -0
  65. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/ops/inspect.py +0 -0
  66. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/ops/interact.py +0 -0
  67. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/ops/navigate.py +0 -0
  68. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/ops/read.py +0 -0
  69. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/ops/tabs.py +0 -0
  70. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/paths.py +0 -0
  71. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/proc.py +0 -0
  72. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/pwdriver.py +0 -0
  73. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/recorder.py +0 -0
  74. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/refs.py +0 -0
  75. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/schema.py +0 -0
  76. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/shadow.py +0 -0
  77. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/shots.py +0 -0
  78. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/targeting.py +0 -0
  79. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/src/abt/viewer.py +0 -0
  80. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/start-server.bat +0 -0
  81. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/start-server.sh +0 -0
  82. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/conftest.py +0 -0
  83. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/fixtures/actionable.html +0 -0
  84. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/fixtures/canvas.html +0 -0
  85. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/fixtures/cards.html +0 -0
  86. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/fixtures/chainfetch.html +0 -0
  87. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/fixtures/controlled.html +0 -0
  88. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/fixtures/controlled_trusted.html +0 -0
  89. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/fixtures/delayed.html +0 -0
  90. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/fixtures/form.html +0 -0
  91. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/fixtures/framed_button.html +0 -0
  92. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/fixtures/framed_preload.html +0 -0
  93. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/fixtures/frames.html +0 -0
  94. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/fixtures/frames_cross.html +0 -0
  95. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/fixtures/frames_order.html +0 -0
  96. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/fixtures/frames_swap.html +0 -0
  97. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/fixtures/inspect.html +0 -0
  98. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/fixtures/late.html +0 -0
  99. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/fixtures/links.html +0 -0
  100. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/fixtures/messenger.html +0 -0
  101. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/fixtures/nav.html +0 -0
  102. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/fixtures/overlay.html +0 -0
  103. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/fixtures/repeated.html +0 -0
  104. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/fixtures/shadow.html +0 -0
  105. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/fixtures/slowfetch.html +0 -0
  106. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_actionable.py +0 -0
  107. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_autostart.py +0 -0
  108. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_bundle.py +0 -0
  109. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_channel_names.py +0 -0
  110. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_click_at.py +0 -0
  111. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_diff.py +0 -0
  112. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_doctor.py +0 -0
  113. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_engine.py +0 -0
  114. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_frames.py +0 -0
  115. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_guidelines.py +0 -0
  116. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_hit_test.py +0 -0
  117. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_inspect.py +0 -0
  118. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_launch.py +0 -0
  119. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_lifecycle.py +0 -0
  120. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_lifecycle_live.py +0 -0
  121. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_mcp.py +0 -0
  122. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_messenger.py +0 -0
  123. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_ops_click_options.py +0 -0
  124. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_ops_interact.py +0 -0
  125. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_ops_navigate.py +0 -0
  126. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_ops_read.py +0 -0
  127. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_ops_tabs.py +0 -0
  128. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_paths.py +0 -0
  129. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_proc.py +0 -0
  130. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_recorder.py +0 -0
  131. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_schema.py +0 -0
  132. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_server.py +0 -0
  133. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_server_logs.py +0 -0
  134. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_settle.py +0 -0
  135. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_shadow.py +0 -0
  136. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_shots.py +0 -0
  137. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_shots_live.py +0 -0
  138. {ai_browser_toolkit-0.2.2 → ai_browser_toolkit-0.2.3}/tests/test_targeting.py +0 -0
  139. {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 serve` first.
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
- # aibrowsertoolkit
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
- An HTTP server that lets an AI agent drive a real browser — **Chrome or Edge** — by
4
- sending JSON. The server process is the loop: it opens the chosen browser with a persistent
5
- profile, stays up waiting for commands, and only stops when you send `shutdown`.
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
- > **Agents: read [`guidelines/toolkit-workflow.md`](guidelines/toolkit-workflow.md)
8
- > before driving anything.** Not "if your site looks tricky", not "if there is a
9
- > playbook for it" — always. Most sites have no playbook, and that file is the
10
- > whole answer for them. Then, only if one exists, add the site playbook from
11
- > [`guidelines/`](guidelines/README.md).
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 you the things that are hard to guess: that `find` hands
14
- > back refs you act on directly, that a click already reports what changed so
15
- > you need not re-read the page, and that ops can be sent in batches.
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
- abt serve ──starts──> FastAPI :8765 ──owns──> Chrome/Edge (persistent profile)
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
- ## Install
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
- Needs **Python 3.11+** and **Google Chrome** or **Microsoft Edge** installed. The matching
27
- chromedriver/msedgedriver is resolved automatically by Selenium Manager — nothing to download by hand.
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
- Thirteen tools rather than one per op, since every schema sits in the model's
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
- Metadata-Version: 2.5
2
- Name: ai-browser-toolkit
3
- Version: 0.2.2
4
- Summary: Agentic browser automation CLI for AI agents
5
- License-Expression: Apache-2.0
6
- License-File: LICENSE
7
- Requires-Python: >=3.11
8
- Requires-Dist: fastapi>=0.110
9
- Requires-Dist: httpx>=0.27
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 serve ──starts──> FastAPI :8765 ──owns──> Chrome/Edge (persistent profile)
39
- ^
40
- abt / curl / your agent ──┘ POST /command | POST /commands
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. The matching
46
- chromedriver/msedgedriver is resolved automatically by Selenium Manager — nothing to download by hand.
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
- Thirteen tools rather than one per op, since every schema sits in the model's
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
- payload = to_op(tool, args)
341
- except KeyError:
342
- return f"no such tool: {tool}", True
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 serve --browser chrome` and wait for GET /status to answer.",
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": "0.1.0"},
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(OpError("element_not_found", f"no playbook for {domain}"))
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(OpError("element_not_found", f"no playbook named {name}"))
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"]