spagents 0.1.0__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.
- spagents-0.1.0/.github/workflows/publish.yml +27 -0
- spagents-0.1.0/.gitignore +10 -0
- spagents-0.1.0/.python-version +1 -0
- spagents-0.1.0/LICENSE.md +63 -0
- spagents-0.1.0/PKG-INFO +316 -0
- spagents-0.1.0/README.md +233 -0
- spagents-0.1.0/pyproject.toml +38 -0
- spagents-0.1.0/src/spagents/__init__.py +23 -0
- spagents-0.1.0/src/spagents/actions/__init__.py +0 -0
- spagents-0.1.0/src/spagents/actions/discovery.py +40 -0
- spagents-0.1.0/src/spagents/browser/__init__.py +0 -0
- spagents-0.1.0/src/spagents/browser/manager.py +61 -0
- spagents-0.1.0/src/spagents/browser/page.py +112 -0
- spagents-0.1.0/src/spagents/browser/session.py +73 -0
- spagents-0.1.0/src/spagents/cli/__init__.py +0 -0
- spagents-0.1.0/src/spagents/cli/main.py +326 -0
- spagents-0.1.0/src/spagents/detection/__init__.py +0 -0
- spagents-0.1.0/src/spagents/detection/ready.py +167 -0
- spagents-0.1.0/src/spagents/extraction/__init__.py +0 -0
- spagents-0.1.0/src/spagents/extraction/extractor.py +86 -0
- spagents-0.1.0/src/spagents/extraction/models.py +102 -0
- spagents-0.1.0/src/spagents/js/__init__.py +26 -0
- spagents-0.1.0/src/spagents/js/content_heuristic.js +41 -0
- spagents-0.1.0/src/spagents/js/discover_actions.js +305 -0
- spagents-0.1.0/src/spagents/js/extract_articles.js +210 -0
- spagents-0.1.0/src/spagents/js/extract_links.js +40 -0
- spagents-0.1.0/src/spagents/js/extract_main_text.js +6 -0
- spagents-0.1.0/src/spagents/js/extract_metadata.js +18 -0
- spagents-0.1.0/src/spagents/js/mutation_observer.js +20 -0
- spagents-0.1.0/src/spagents/mcp/__init__.py +0 -0
- spagents-0.1.0/src/spagents/mcp/server.py +236 -0
- spagents-0.1.0/src/spagents/py.typed +0 -0
- spagents-0.1.0/tests/__init__.py +0 -0
- spagents-0.1.0/tests/conftest.py +72 -0
- spagents-0.1.0/tests/test_actions.py +99 -0
- spagents-0.1.0/tests/test_detection.py +30 -0
- spagents-0.1.0/tests/test_extraction.py +81 -0
- spagents-0.1.0/tests/test_js_loader.py +45 -0
- spagents-0.1.0/tests/test_models.py +121 -0
- spagents-0.1.0/tests/test_page.py +117 -0
- spagents-0.1.0/tests/test_session.py +63 -0
- spagents-0.1.0/tests/test_spa/index.html +315 -0
- spagents-0.1.0/uv.lock +2025 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
|
|
6
|
+
jobs:
|
|
7
|
+
publish:
|
|
8
|
+
runs-on: ubuntu-latest
|
|
9
|
+
if: github.ref == 'refs/heads/main'
|
|
10
|
+
permissions:
|
|
11
|
+
id-token: write
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
|
|
15
|
+
- name: Install uv
|
|
16
|
+
uses: astral-sh/setup-uv@v6
|
|
17
|
+
|
|
18
|
+
- name: Set up Python
|
|
19
|
+
uses: actions/setup-python@v5
|
|
20
|
+
with:
|
|
21
|
+
python-version: "3.12"
|
|
22
|
+
|
|
23
|
+
- name: Build package
|
|
24
|
+
run: uv build
|
|
25
|
+
|
|
26
|
+
- name: Publish to PyPI
|
|
27
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.10
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# spagents License
|
|
2
|
+
|
|
3
|
+
## MIT License (Amended)
|
|
4
|
+
|
|
5
|
+
Copyright (c) 2026 Moses Wynn
|
|
6
|
+
|
|
7
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
in the Software without restriction, including without limitation the rights
|
|
10
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
furnished to do so, subject to the following conditions:
|
|
13
|
+
|
|
14
|
+
The above copyright notice and this permission notice shall be included in all
|
|
15
|
+
copies or substantial portions of the Software.
|
|
16
|
+
|
|
17
|
+
### Additional Condition: Community Contribution Requirement
|
|
18
|
+
|
|
19
|
+
Organizations with **more than 100 employees** that use this Software in
|
|
20
|
+
any capacity (internal tools, products, services, or development workflows)
|
|
21
|
+
must make a one-time donation of **$1.00 USD per employee** to one or more
|
|
22
|
+
of the following organizations:
|
|
23
|
+
|
|
24
|
+
- **ACLU (American Civil Liberties Union)** — [donate.aclu.org](https://donate.aclu.org/)
|
|
25
|
+
- **SPLC (Southern Poverty Law Center)** — [donate.splcenter.org](https://donate.splcenter.org/sslpage.aspx?pid=463)
|
|
26
|
+
- **DSA (Democratic Socialists of America)** — [dsausa.org/donate](https://www.dsausa.org/donate/)
|
|
27
|
+
- **Appalachian OUTReach** — [appalachianoutreach.org/donate](https://www.appalachianoutreach.org/donate)
|
|
28
|
+
|
|
29
|
+
The donation may be split across multiple organizations.
|
|
30
|
+
|
|
31
|
+
Upon making the donation, the organization must submit a **pull request** to
|
|
32
|
+
this repository adding their name to the [Approved Organizations](#approved-organizations)
|
|
33
|
+
section below, including proof of donation (receipt, confirmation number, or
|
|
34
|
+
screenshot with sensitive information redacted).
|
|
35
|
+
|
|
36
|
+
Organizations that believe they should be **exempt** from this requirement
|
|
37
|
+
(e.g., nonprofits, educational institutions, organizations with fewer
|
|
38
|
+
resources than their headcount suggests) may submit a pull request adding
|
|
39
|
+
their name to the Approved Organizations section with a written explanation
|
|
40
|
+
of why they believe an exemption is warranted. Exemptions are granted at the
|
|
41
|
+
sole discretion of the copyright holder.
|
|
42
|
+
|
|
43
|
+
**Use of this Software by qualifying organizations without compliance
|
|
44
|
+
constitutes a violation of this license.**
|
|
45
|
+
|
|
46
|
+
### Approved Organizations
|
|
47
|
+
|
|
48
|
+
<!-- Add your organization below via pull request -->
|
|
49
|
+
<!-- Format: | Organization Name | Date | Donation or Exemption | -->
|
|
50
|
+
|
|
51
|
+
| Organization | Date | Status |
|
|
52
|
+
|---|---|---|
|
|
53
|
+
| | | |
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
58
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
59
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
60
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
61
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
62
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
63
|
+
SOFTWARE.
|
spagents-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: spagents
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: SPA-aware browsing library for AI agents
|
|
5
|
+
Author-email: Moses Wynn <accounts@moseswynn.com>
|
|
6
|
+
License: # spagents License
|
|
7
|
+
|
|
8
|
+
## MIT License (Amended)
|
|
9
|
+
|
|
10
|
+
Copyright (c) 2026 Moses Wynn
|
|
11
|
+
|
|
12
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
13
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
14
|
+
in the Software without restriction, including without limitation the rights
|
|
15
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
16
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
17
|
+
furnished to do so, subject to the following conditions:
|
|
18
|
+
|
|
19
|
+
The above copyright notice and this permission notice shall be included in all
|
|
20
|
+
copies or substantial portions of the Software.
|
|
21
|
+
|
|
22
|
+
### Additional Condition: Community Contribution Requirement
|
|
23
|
+
|
|
24
|
+
Organizations with **more than 100 employees** that use this Software in
|
|
25
|
+
any capacity (internal tools, products, services, or development workflows)
|
|
26
|
+
must make a one-time donation of **$1.00 USD per employee** to one or more
|
|
27
|
+
of the following organizations:
|
|
28
|
+
|
|
29
|
+
- **ACLU (American Civil Liberties Union)** — [donate.aclu.org](https://donate.aclu.org/)
|
|
30
|
+
- **SPLC (Southern Poverty Law Center)** — [donate.splcenter.org](https://donate.splcenter.org/sslpage.aspx?pid=463)
|
|
31
|
+
- **DSA (Democratic Socialists of America)** — [dsausa.org/donate](https://www.dsausa.org/donate/)
|
|
32
|
+
- **Appalachian OUTReach** — [appalachianoutreach.org/donate](https://www.appalachianoutreach.org/donate)
|
|
33
|
+
|
|
34
|
+
The donation may be split across multiple organizations.
|
|
35
|
+
|
|
36
|
+
Upon making the donation, the organization must submit a **pull request** to
|
|
37
|
+
this repository adding their name to the [Approved Organizations](#approved-organizations)
|
|
38
|
+
section below, including proof of donation (receipt, confirmation number, or
|
|
39
|
+
screenshot with sensitive information redacted).
|
|
40
|
+
|
|
41
|
+
Organizations that believe they should be **exempt** from this requirement
|
|
42
|
+
(e.g., nonprofits, educational institutions, organizations with fewer
|
|
43
|
+
resources than their headcount suggests) may submit a pull request adding
|
|
44
|
+
their name to the Approved Organizations section with a written explanation
|
|
45
|
+
of why they believe an exemption is warranted. Exemptions are granted at the
|
|
46
|
+
sole discretion of the copyright holder.
|
|
47
|
+
|
|
48
|
+
**Use of this Software by qualifying organizations without compliance
|
|
49
|
+
constitutes a violation of this license.**
|
|
50
|
+
|
|
51
|
+
### Approved Organizations
|
|
52
|
+
|
|
53
|
+
<!-- Add your organization below via pull request -->
|
|
54
|
+
<!-- Format: | Organization Name | Date | Donation or Exemption | -->
|
|
55
|
+
|
|
56
|
+
| Organization | Date | Status |
|
|
57
|
+
|---|---|---|
|
|
58
|
+
| | | |
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
63
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
64
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
65
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
66
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
67
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
68
|
+
SOFTWARE.
|
|
69
|
+
License-File: LICENSE.md
|
|
70
|
+
Requires-Python: >=3.10
|
|
71
|
+
Requires-Dist: beautifulsoup4>=4.12
|
|
72
|
+
Requires-Dist: fastmcp>=2.0
|
|
73
|
+
Requires-Dist: lxml>=5.0
|
|
74
|
+
Requires-Dist: playwright>=1.40
|
|
75
|
+
Requires-Dist: pydantic>=2.0
|
|
76
|
+
Requires-Dist: typer>=0.9
|
|
77
|
+
Provides-Extra: dev
|
|
78
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
79
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
80
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
81
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
82
|
+
Description-Content-Type: text/markdown
|
|
83
|
+
|
|
84
|
+
<p align="center">
|
|
85
|
+
<h1 align="center">spagents</h1>
|
|
86
|
+
<p align="center">
|
|
87
|
+
<strong>Give your AI agents eyes for the modern web.</strong>
|
|
88
|
+
</p>
|
|
89
|
+
<p align="center">
|
|
90
|
+
<a href="#installation">Installation</a> ·
|
|
91
|
+
<a href="#quick-start">Quick Start</a> ·
|
|
92
|
+
<a href="#mcp-server">MCP Server</a> ·
|
|
93
|
+
<a href="#python-sdk">Python SDK</a> ·
|
|
94
|
+
<a href="#how-it-works">How It Works</a>
|
|
95
|
+
</p>
|
|
96
|
+
</p>
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
**AI agents are blind to Single Page Applications.** Tools like `fetch` and `requests` return empty HTML shells — no articles, no content, no interactive elements. SPAs render everything via JavaScript *after* the page loads.
|
|
101
|
+
|
|
102
|
+
**spagents** fixes this. It launches a real browser, intelligently waits for SPAs to finish rendering, and returns structured, agent-friendly data — articles, actions, inputs, navigation — ready for your agent to use.
|
|
103
|
+
|
|
104
|
+
### The problem
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
# What fetch/requests sees on a SPA:
|
|
108
|
+
<div id="app"></div>
|
|
109
|
+
<script src="bundle.js"></script>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### The spagents solution
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
$ spagents browse "https://news.kagi.com" --format text
|
|
116
|
+
|
|
117
|
+
Title: World | Kagi News
|
|
118
|
+
Content Ready: True
|
|
119
|
+
|
|
120
|
+
=== 12 Articles ===
|
|
121
|
+
MIDDLE EAST: Update: Iran rejects US truce plan, sets terms
|
|
122
|
+
TECH ACCOUNTABILITY: US jury holds Meta, YouTube liable in addiction case
|
|
123
|
+
ARCHAEOLOGY: Possible d'Artagnan remains found beneath Maastricht church
|
|
124
|
+
...
|
|
125
|
+
|
|
126
|
+
=== 45 Actions ===
|
|
127
|
+
[1] (navigate) Listitem: Technology
|
|
128
|
+
[2] (click) Button: Expand story (Iran rejects US truce plan...)
|
|
129
|
+
[3] (input) Input: Search
|
|
130
|
+
...
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Key features
|
|
134
|
+
|
|
135
|
+
- **Smart content detection** — Knows when a SPA is done rendering (not just `sleep(5)`)
|
|
136
|
+
- **Structured extraction** — Returns articles, links, metadata as typed Pydantic models
|
|
137
|
+
- **Full interaction** — Click, type, scroll, press keys, navigate — like a real user
|
|
138
|
+
- **Action discovery** — Finds every interactive element: buttons, inputs, ARIA roles, custom components
|
|
139
|
+
- **Session persistence** — Cookies, localStorage, and auth state preserved across navigations
|
|
140
|
+
- **Three interfaces** — Python SDK, CLI, and MCP server for Claude Desktop / AI agents
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Installation
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
pip install spagents
|
|
148
|
+
playwright install chromium
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### From source
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
git clone https://github.com/your-username/spagents.git
|
|
155
|
+
cd spagents
|
|
156
|
+
uv sync
|
|
157
|
+
uv run playwright install chromium
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Quick start
|
|
161
|
+
|
|
162
|
+
### CLI
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
# Structured JSON output (default)
|
|
166
|
+
spagents browse "https://news.kagi.com"
|
|
167
|
+
|
|
168
|
+
# Human-readable text
|
|
169
|
+
spagents browse "https://news.kagi.com" --format text
|
|
170
|
+
|
|
171
|
+
# Interactive REPL session
|
|
172
|
+
spagents interactive "https://news.kagi.com"
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Python SDK
|
|
176
|
+
|
|
177
|
+
```python
|
|
178
|
+
import asyncio
|
|
179
|
+
from spagents import BrowserManager
|
|
180
|
+
|
|
181
|
+
async def main():
|
|
182
|
+
async with BrowserManager() as browser:
|
|
183
|
+
session = await browser.new_session()
|
|
184
|
+
|
|
185
|
+
# Browse a SPA — content is fully rendered
|
|
186
|
+
state = await session.navigate("https://news.kagi.com")
|
|
187
|
+
for article in state.content.articles:
|
|
188
|
+
print(f"{article.category}: {article.headline}")
|
|
189
|
+
|
|
190
|
+
# Interact with the page
|
|
191
|
+
for action in state.actions:
|
|
192
|
+
if "Technology" in action.description:
|
|
193
|
+
state = await session.click(action.selector)
|
|
194
|
+
break
|
|
195
|
+
|
|
196
|
+
# Type into inputs, press keys
|
|
197
|
+
state = await session.type_text("#search", "climate change")
|
|
198
|
+
state = await session.press_key("Enter")
|
|
199
|
+
|
|
200
|
+
await session.close()
|
|
201
|
+
|
|
202
|
+
asyncio.run(main())
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### MCP Server
|
|
206
|
+
|
|
207
|
+
Connect spagents to Claude Desktop or any MCP-compatible AI agent:
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
spagents mcp
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Add to your Claude Desktop config (`claude_desktop_config.json`):
|
|
214
|
+
|
|
215
|
+
```json
|
|
216
|
+
{
|
|
217
|
+
"mcpServers": {
|
|
218
|
+
"spagents": {
|
|
219
|
+
"command": "spagents",
|
|
220
|
+
"args": ["mcp"]
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Now Claude can browse any SPA:
|
|
227
|
+
|
|
228
|
+
> **You:** "What's the top story on Kagi News today?"
|
|
229
|
+
>
|
|
230
|
+
> **Claude:** *uses `browse` tool* → *reads structured articles* → gives you the answer
|
|
231
|
+
|
|
232
|
+
#### MCP tools
|
|
233
|
+
|
|
234
|
+
| Tool | Description |
|
|
235
|
+
|---|---|
|
|
236
|
+
| `browse` | Navigate to a URL, return rendered content + interactive actions |
|
|
237
|
+
| `click` | Click an element by CSS selector |
|
|
238
|
+
| `type_text` | Type into an input field |
|
|
239
|
+
| `press_key` | Press a keyboard key (Enter, Tab, Escape, etc.) |
|
|
240
|
+
| `list_actions` | Discover all interactive elements on the page |
|
|
241
|
+
| `navigate` | Go to a new URL within an existing session |
|
|
242
|
+
| `scroll` | Scroll up or down, trigger infinite scroll |
|
|
243
|
+
| `extract_content` | Re-extract content from the current page |
|
|
244
|
+
| `close_session` | Close a browser session and free resources |
|
|
245
|
+
|
|
246
|
+
## Interactive REPL commands
|
|
247
|
+
|
|
248
|
+
```
|
|
249
|
+
click <n> Click action by number
|
|
250
|
+
click <selector> Click by CSS selector
|
|
251
|
+
type <n> <text> Type text into an input by action number
|
|
252
|
+
type "<selector>" <text> Type text into an input by CSS selector
|
|
253
|
+
press <key> Press a key (Enter, Escape, Tab, ArrowDown, etc.)
|
|
254
|
+
select <n> <value> Select dropdown option by action number
|
|
255
|
+
scroll [down|up] Scroll the page
|
|
256
|
+
actions List all interactive elements
|
|
257
|
+
extract Re-extract page content
|
|
258
|
+
navigate <url> Navigate to a new URL
|
|
259
|
+
json Dump current state as JSON
|
|
260
|
+
quit Exit the session
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
## How it works
|
|
264
|
+
|
|
265
|
+
spagents wraps [Playwright](https://playwright.dev/python/) and adds three intelligent layers:
|
|
266
|
+
|
|
267
|
+
### 1. Content ready detection
|
|
268
|
+
|
|
269
|
+
A multi-signal detector that knows when a SPA has *actually* finished rendering:
|
|
270
|
+
|
|
271
|
+
| Signal | What it checks |
|
|
272
|
+
|---|---|
|
|
273
|
+
| **Network quiescence** | No pending XHR/fetch requests for 500ms (ignoring analytics noise) |
|
|
274
|
+
| **DOM stabilization** | MutationObserver sees no changes for 300ms after initial render |
|
|
275
|
+
| **Content heuristic** | Meaningful text exists, no loading spinners, real links present |
|
|
276
|
+
|
|
277
|
+
This replaces naive approaches like `sleep(5)` or Playwright's `networkidle` (which breaks on long-polling and WebSocket connections).
|
|
278
|
+
|
|
279
|
+
### 2. Content extraction
|
|
280
|
+
|
|
281
|
+
Extracts structured data from the rendered DOM:
|
|
282
|
+
|
|
283
|
+
- **Articles** with headlines, summaries, sources, highlights, quotes, and sections
|
|
284
|
+
- **Links** with surrounding context (which heading or section they're under)
|
|
285
|
+
- **Metadata** from OG tags, meta descriptions, and page title
|
|
286
|
+
|
|
287
|
+
### 3. Action discovery
|
|
288
|
+
|
|
289
|
+
Finds *every* interactive element on the page through four phases:
|
|
290
|
+
|
|
291
|
+
1. **Semantic HTML** — `<a>`, `<button>`, `<input>`, `<select>`
|
|
292
|
+
2. **ARIA roles** — `role="button"`, `role="tab"`, `role="listitem"`, etc.
|
|
293
|
+
3. **Custom components** — `tabindex`, `onclick`, `cursor: pointer`
|
|
294
|
+
4. **Disambiguation** — Duplicate labels get context from parent containers
|
|
295
|
+
|
|
296
|
+
## CLI reference
|
|
297
|
+
|
|
298
|
+
```
|
|
299
|
+
spagents browse <url> [OPTIONS]
|
|
300
|
+
--format, -f Output format: json (default) or text
|
|
301
|
+
--timeout, -t Content detection timeout in ms (default: 15000)
|
|
302
|
+
--no-headless Run browser with visible window
|
|
303
|
+
|
|
304
|
+
spagents interactive <url> [OPTIONS]
|
|
305
|
+
--timeout, -t Content detection timeout in ms (default: 15000)
|
|
306
|
+
--no-headless Run browser with visible window
|
|
307
|
+
|
|
308
|
+
spagents mcp [OPTIONS]
|
|
309
|
+
--transport Transport: stdio (default) or sse
|
|
310
|
+
--port, -p Port for SSE transport (default: 8000)
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## License
|
|
314
|
+
|
|
315
|
+
MIT with an amended community contribution requirement for organizations with
|
|
316
|
+
more than 100 employees. See [LICENSE.md](LICENSE.md) for details.
|
spagents-0.1.0/README.md
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<h1 align="center">spagents</h1>
|
|
3
|
+
<p align="center">
|
|
4
|
+
<strong>Give your AI agents eyes for the modern web.</strong>
|
|
5
|
+
</p>
|
|
6
|
+
<p align="center">
|
|
7
|
+
<a href="#installation">Installation</a> ·
|
|
8
|
+
<a href="#quick-start">Quick Start</a> ·
|
|
9
|
+
<a href="#mcp-server">MCP Server</a> ·
|
|
10
|
+
<a href="#python-sdk">Python SDK</a> ·
|
|
11
|
+
<a href="#how-it-works">How It Works</a>
|
|
12
|
+
</p>
|
|
13
|
+
</p>
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
**AI agents are blind to Single Page Applications.** Tools like `fetch` and `requests` return empty HTML shells — no articles, no content, no interactive elements. SPAs render everything via JavaScript *after* the page loads.
|
|
18
|
+
|
|
19
|
+
**spagents** fixes this. It launches a real browser, intelligently waits for SPAs to finish rendering, and returns structured, agent-friendly data — articles, actions, inputs, navigation — ready for your agent to use.
|
|
20
|
+
|
|
21
|
+
### The problem
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
# What fetch/requests sees on a SPA:
|
|
25
|
+
<div id="app"></div>
|
|
26
|
+
<script src="bundle.js"></script>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### The spagents solution
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
$ spagents browse "https://news.kagi.com" --format text
|
|
33
|
+
|
|
34
|
+
Title: World | Kagi News
|
|
35
|
+
Content Ready: True
|
|
36
|
+
|
|
37
|
+
=== 12 Articles ===
|
|
38
|
+
MIDDLE EAST: Update: Iran rejects US truce plan, sets terms
|
|
39
|
+
TECH ACCOUNTABILITY: US jury holds Meta, YouTube liable in addiction case
|
|
40
|
+
ARCHAEOLOGY: Possible d'Artagnan remains found beneath Maastricht church
|
|
41
|
+
...
|
|
42
|
+
|
|
43
|
+
=== 45 Actions ===
|
|
44
|
+
[1] (navigate) Listitem: Technology
|
|
45
|
+
[2] (click) Button: Expand story (Iran rejects US truce plan...)
|
|
46
|
+
[3] (input) Input: Search
|
|
47
|
+
...
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Key features
|
|
51
|
+
|
|
52
|
+
- **Smart content detection** — Knows when a SPA is done rendering (not just `sleep(5)`)
|
|
53
|
+
- **Structured extraction** — Returns articles, links, metadata as typed Pydantic models
|
|
54
|
+
- **Full interaction** — Click, type, scroll, press keys, navigate — like a real user
|
|
55
|
+
- **Action discovery** — Finds every interactive element: buttons, inputs, ARIA roles, custom components
|
|
56
|
+
- **Session persistence** — Cookies, localStorage, and auth state preserved across navigations
|
|
57
|
+
- **Three interfaces** — Python SDK, CLI, and MCP server for Claude Desktop / AI agents
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Installation
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pip install spagents
|
|
65
|
+
playwright install chromium
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### From source
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
git clone https://github.com/your-username/spagents.git
|
|
72
|
+
cd spagents
|
|
73
|
+
uv sync
|
|
74
|
+
uv run playwright install chromium
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Quick start
|
|
78
|
+
|
|
79
|
+
### CLI
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
# Structured JSON output (default)
|
|
83
|
+
spagents browse "https://news.kagi.com"
|
|
84
|
+
|
|
85
|
+
# Human-readable text
|
|
86
|
+
spagents browse "https://news.kagi.com" --format text
|
|
87
|
+
|
|
88
|
+
# Interactive REPL session
|
|
89
|
+
spagents interactive "https://news.kagi.com"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Python SDK
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
import asyncio
|
|
96
|
+
from spagents import BrowserManager
|
|
97
|
+
|
|
98
|
+
async def main():
|
|
99
|
+
async with BrowserManager() as browser:
|
|
100
|
+
session = await browser.new_session()
|
|
101
|
+
|
|
102
|
+
# Browse a SPA — content is fully rendered
|
|
103
|
+
state = await session.navigate("https://news.kagi.com")
|
|
104
|
+
for article in state.content.articles:
|
|
105
|
+
print(f"{article.category}: {article.headline}")
|
|
106
|
+
|
|
107
|
+
# Interact with the page
|
|
108
|
+
for action in state.actions:
|
|
109
|
+
if "Technology" in action.description:
|
|
110
|
+
state = await session.click(action.selector)
|
|
111
|
+
break
|
|
112
|
+
|
|
113
|
+
# Type into inputs, press keys
|
|
114
|
+
state = await session.type_text("#search", "climate change")
|
|
115
|
+
state = await session.press_key("Enter")
|
|
116
|
+
|
|
117
|
+
await session.close()
|
|
118
|
+
|
|
119
|
+
asyncio.run(main())
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### MCP Server
|
|
123
|
+
|
|
124
|
+
Connect spagents to Claude Desktop or any MCP-compatible AI agent:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
spagents mcp
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Add to your Claude Desktop config (`claude_desktop_config.json`):
|
|
131
|
+
|
|
132
|
+
```json
|
|
133
|
+
{
|
|
134
|
+
"mcpServers": {
|
|
135
|
+
"spagents": {
|
|
136
|
+
"command": "spagents",
|
|
137
|
+
"args": ["mcp"]
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Now Claude can browse any SPA:
|
|
144
|
+
|
|
145
|
+
> **You:** "What's the top story on Kagi News today?"
|
|
146
|
+
>
|
|
147
|
+
> **Claude:** *uses `browse` tool* → *reads structured articles* → gives you the answer
|
|
148
|
+
|
|
149
|
+
#### MCP tools
|
|
150
|
+
|
|
151
|
+
| Tool | Description |
|
|
152
|
+
|---|---|
|
|
153
|
+
| `browse` | Navigate to a URL, return rendered content + interactive actions |
|
|
154
|
+
| `click` | Click an element by CSS selector |
|
|
155
|
+
| `type_text` | Type into an input field |
|
|
156
|
+
| `press_key` | Press a keyboard key (Enter, Tab, Escape, etc.) |
|
|
157
|
+
| `list_actions` | Discover all interactive elements on the page |
|
|
158
|
+
| `navigate` | Go to a new URL within an existing session |
|
|
159
|
+
| `scroll` | Scroll up or down, trigger infinite scroll |
|
|
160
|
+
| `extract_content` | Re-extract content from the current page |
|
|
161
|
+
| `close_session` | Close a browser session and free resources |
|
|
162
|
+
|
|
163
|
+
## Interactive REPL commands
|
|
164
|
+
|
|
165
|
+
```
|
|
166
|
+
click <n> Click action by number
|
|
167
|
+
click <selector> Click by CSS selector
|
|
168
|
+
type <n> <text> Type text into an input by action number
|
|
169
|
+
type "<selector>" <text> Type text into an input by CSS selector
|
|
170
|
+
press <key> Press a key (Enter, Escape, Tab, ArrowDown, etc.)
|
|
171
|
+
select <n> <value> Select dropdown option by action number
|
|
172
|
+
scroll [down|up] Scroll the page
|
|
173
|
+
actions List all interactive elements
|
|
174
|
+
extract Re-extract page content
|
|
175
|
+
navigate <url> Navigate to a new URL
|
|
176
|
+
json Dump current state as JSON
|
|
177
|
+
quit Exit the session
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## How it works
|
|
181
|
+
|
|
182
|
+
spagents wraps [Playwright](https://playwright.dev/python/) and adds three intelligent layers:
|
|
183
|
+
|
|
184
|
+
### 1. Content ready detection
|
|
185
|
+
|
|
186
|
+
A multi-signal detector that knows when a SPA has *actually* finished rendering:
|
|
187
|
+
|
|
188
|
+
| Signal | What it checks |
|
|
189
|
+
|---|---|
|
|
190
|
+
| **Network quiescence** | No pending XHR/fetch requests for 500ms (ignoring analytics noise) |
|
|
191
|
+
| **DOM stabilization** | MutationObserver sees no changes for 300ms after initial render |
|
|
192
|
+
| **Content heuristic** | Meaningful text exists, no loading spinners, real links present |
|
|
193
|
+
|
|
194
|
+
This replaces naive approaches like `sleep(5)` or Playwright's `networkidle` (which breaks on long-polling and WebSocket connections).
|
|
195
|
+
|
|
196
|
+
### 2. Content extraction
|
|
197
|
+
|
|
198
|
+
Extracts structured data from the rendered DOM:
|
|
199
|
+
|
|
200
|
+
- **Articles** with headlines, summaries, sources, highlights, quotes, and sections
|
|
201
|
+
- **Links** with surrounding context (which heading or section they're under)
|
|
202
|
+
- **Metadata** from OG tags, meta descriptions, and page title
|
|
203
|
+
|
|
204
|
+
### 3. Action discovery
|
|
205
|
+
|
|
206
|
+
Finds *every* interactive element on the page through four phases:
|
|
207
|
+
|
|
208
|
+
1. **Semantic HTML** — `<a>`, `<button>`, `<input>`, `<select>`
|
|
209
|
+
2. **ARIA roles** — `role="button"`, `role="tab"`, `role="listitem"`, etc.
|
|
210
|
+
3. **Custom components** — `tabindex`, `onclick`, `cursor: pointer`
|
|
211
|
+
4. **Disambiguation** — Duplicate labels get context from parent containers
|
|
212
|
+
|
|
213
|
+
## CLI reference
|
|
214
|
+
|
|
215
|
+
```
|
|
216
|
+
spagents browse <url> [OPTIONS]
|
|
217
|
+
--format, -f Output format: json (default) or text
|
|
218
|
+
--timeout, -t Content detection timeout in ms (default: 15000)
|
|
219
|
+
--no-headless Run browser with visible window
|
|
220
|
+
|
|
221
|
+
spagents interactive <url> [OPTIONS]
|
|
222
|
+
--timeout, -t Content detection timeout in ms (default: 15000)
|
|
223
|
+
--no-headless Run browser with visible window
|
|
224
|
+
|
|
225
|
+
spagents mcp [OPTIONS]
|
|
226
|
+
--transport Transport: stdio (default) or sse
|
|
227
|
+
--port, -p Port for SSE transport (default: 8000)
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## License
|
|
231
|
+
|
|
232
|
+
MIT with an amended community contribution requirement for organizations with
|
|
233
|
+
more than 100 employees. See [LICENSE.md](LICENSE.md) for details.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "spagents"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "SPA-aware browsing library for AI agents"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = { file = "LICENSE.md" }
|
|
7
|
+
authors = [
|
|
8
|
+
{ name = "Moses Wynn", email = "accounts@moseswynn.com" }
|
|
9
|
+
]
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"playwright>=1.40",
|
|
13
|
+
"pydantic>=2.0",
|
|
14
|
+
"typer>=0.9",
|
|
15
|
+
"beautifulsoup4>=4.12",
|
|
16
|
+
"lxml>=5.0",
|
|
17
|
+
"fastmcp>=2.0",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[project.scripts]
|
|
21
|
+
spagents = "spagents.cli.main:app"
|
|
22
|
+
|
|
23
|
+
[build-system]
|
|
24
|
+
requires = ["hatchling"]
|
|
25
|
+
build-backend = "hatchling.build"
|
|
26
|
+
|
|
27
|
+
[project.optional-dependencies]
|
|
28
|
+
dev = [
|
|
29
|
+
"pytest>=8.0",
|
|
30
|
+
"pytest-asyncio>=0.23",
|
|
31
|
+
"ruff>=0.4",
|
|
32
|
+
"mypy>=1.10",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[tool.pytest.ini_options]
|
|
36
|
+
asyncio_mode = "auto"
|
|
37
|
+
asyncio_default_fixture_loop_scope = "function"
|
|
38
|
+
testpaths = ["tests"]
|