thunder-agent 0.2.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.
- thunder_agent-0.2.0/LICENSE +32 -0
- thunder_agent-0.2.0/PKG-INFO +143 -0
- thunder_agent-0.2.0/README.md +115 -0
- thunder_agent-0.2.0/pyproject.toml +40 -0
- thunder_agent-0.2.0/setup.cfg +4 -0
- thunder_agent-0.2.0/tests/test_body.py +38 -0
- thunder_agent-0.2.0/tests/test_core.py +72 -0
- thunder_agent-0.2.0/thunder/__init__.py +28 -0
- thunder_agent-0.2.0/thunder/body.py +231 -0
- thunder_agent-0.2.0/thunder/captcha.py +153 -0
- thunder_agent-0.2.0/thunder/cli.py +101 -0
- thunder_agent-0.2.0/thunder/demo.py +55 -0
- thunder_agent-0.2.0/thunder/fingerprint.py +164 -0
- thunder_agent-0.2.0/thunder/ghost.py +72 -0
- thunder_agent-0.2.0/thunder/human.py +175 -0
- thunder_agent-0.2.0/thunder/mcp_server.py +201 -0
- thunder_agent-0.2.0/thunder/mind.py +165 -0
- thunder_agent-0.2.0/thunder/nodriver_body.py +132 -0
- thunder_agent-0.2.0/thunder_agent.egg-info/PKG-INFO +143 -0
- thunder_agent-0.2.0/thunder_agent.egg-info/SOURCES.txt +22 -0
- thunder_agent-0.2.0/thunder_agent.egg-info/dependency_links.txt +1 -0
- thunder_agent-0.2.0/thunder_agent.egg-info/entry_points.txt +3 -0
- thunder_agent-0.2.0/thunder_agent.egg-info/requires.txt +12 -0
- thunder_agent-0.2.0/thunder_agent.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Emmanuel Ene Rejoice (MUTYINT)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
────────────────────────────────────────────────────────────────────────────
|
|
24
|
+
NOTE ON ENGINES: Thunder's default engine is MIT-licensed (Playwright-based
|
|
25
|
+
stealth). The optional `nodriver` engine is AGPL-3.0 — if you enable and
|
|
26
|
+
redistribute it, AGPL applies to that integration. stewmind-1.7b is
|
|
27
|
+
Apache-2.0 (https://huggingface.co/Erog0291/stewmind-1.7b).
|
|
28
|
+
|
|
29
|
+
USE RESPONSIBLY: Thunder is built for agents acting on systems and accounts
|
|
30
|
+
their operator is authorized to use — testing, personal automation, and
|
|
31
|
+
public-data research. It is not a tool for fraud, mass account abuse, or
|
|
32
|
+
deceiving people. Respect site terms of service and local law.
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: thunder-agent
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Thunder — the human engine for AI agents: stealth browser body, human hands, TLS ghost, CAPTCHA bridge, and a stewmind thought layer.
|
|
5
|
+
Author-email: Emmanuel Ene Rejoice <emmanuelerogian723@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/emmanuelerogian723-alt/thunder
|
|
8
|
+
Project-URL: Powered by, https://huggingface.co/Erog0291/stewmind-1.7b
|
|
9
|
+
Keywords: ai-agents,browser-automation,stealth,mcp,anti-detection,human-like,scraping,stewmind
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: curl_cffi>=0.7
|
|
19
|
+
Requires-Dist: playwright>=1.40
|
|
20
|
+
Provides-Extra: mind
|
|
21
|
+
Requires-Dist: transformers>=4.44; extra == "mind"
|
|
22
|
+
Requires-Dist: torch; extra == "mind"
|
|
23
|
+
Provides-Extra: solver
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
26
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# Thunder — the human engine for AI agents
|
|
30
|
+
|
|
31
|
+
<div align="center">
|
|
32
|
+
|
|
33
|
+
⚡ **THUNDER**
|
|
34
|
+
|
|
35
|
+
*A body, a skin, a human hand, a ghost, and a mind — for any AI agent.*
|
|
36
|
+
|
|
37
|
+
`pip install thunder-agent` · MCP-native · Powered by [stewmind-1.7b](https://huggingface.co/Erog0291/stewmind-1.7b)
|
|
38
|
+
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
AI agents browse like robots and get blocked. Thunder gives an agent a
|
|
44
|
+
**human-shaped presence** on the web:
|
|
45
|
+
|
|
46
|
+
| Layer | What it is | How |
|
|
47
|
+
|---|---|---|
|
|
48
|
+
| 🦾 **Body** | a stealth browser session | Playwright engine + persistent profiles, zero heavy deps |
|
|
49
|
+
| 🎭 **Skin** | a consistent device fingerprint | UA / platform / screen / WebGL / canvas-noise, saved per profile so the *same human* returns |
|
|
50
|
+
| ✋ **Hand** | human motion | bezier-curve mouse with overshoot, Fitts-law clicks, lognormal keystrokes with typos, eased scroll, idle drift |
|
|
51
|
+
| 👻 **Ghost** | protocol-level body | curl_cffi — a real browser's TLS (JA3/JA4) + HTTP/2 shape, no browser needed |
|
|
52
|
+
| 🧠 **Mind** | the thought layer | stewmind-1.7b (or ANY model: Ollama, LM Studio, vLLM, GPT, HF) returns strict JSON next-actions |
|
|
53
|
+
| 👁️ **Vision** | eyes that understand | route screenshots to any vision model |
|
|
54
|
+
| 🛡️ **CAPTCHA bridge** | detect → avoid → solve | senses challenges; plugs into your CapSolver/2Captcha key on your own accounts |
|
|
55
|
+
| 🔌 **MCP server** | any agent can drive it | Claude Desktop, Cursor, STEW, anything that speaks MCP |
|
|
56
|
+
|
|
57
|
+
## Quickstart
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
pip install thunder-agent
|
|
61
|
+
thunder browse https://example.com # human-shaped browsing
|
|
62
|
+
thunder ghost https://nowsecure.nl # TLS-true fetch
|
|
63
|
+
thunder demo # full showcase
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
import thunder, asyncio
|
|
68
|
+
|
|
69
|
+
async def main():
|
|
70
|
+
body = thunder.Body(profile="ada") # persistent human
|
|
71
|
+
await body.wake("https://en.wikipedia.org")
|
|
72
|
+
await body.type("#searchInput", "lightning") # types like a person
|
|
73
|
+
print(await body.read()) # structured page for your LLM
|
|
74
|
+
await body.rest()
|
|
75
|
+
|
|
76
|
+
asyncio.run(main())
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Pair with ANY model (local or remote)
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
from thunder.mind import StewMind
|
|
83
|
+
|
|
84
|
+
mind = StewMind.backend_ollama("stewmind:1.7b") # fully offline
|
|
85
|
+
mind = StewMind(endpoint="http://localhost:1234/v1") # LM Studio / vLLM
|
|
86
|
+
mind = StewMind.backend_huggingface() # stewmind on HF
|
|
87
|
+
mind = StewMind.backend_local() # transformers, offline
|
|
88
|
+
mind = StewMind(endpoint="https://api.openai.com/v1",
|
|
89
|
+
model="gpt-4o-mini", api_key=KEY) # any OpenAI-compatible
|
|
90
|
+
|
|
91
|
+
plan = mind.think(goal="log in and download the invoice",
|
|
92
|
+
page=await body.read())
|
|
93
|
+
# {"action": "type", "target": "#email", "value": "…", "reason": "…"}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Offline pairing: `OLLAMA_HOST` + `OLLAMA_MODEL` (or `STEWMIND_LOCAL=1`)
|
|
97
|
+
are auto-detected. stewmind-1.7b runs on a laptop CPU/GPU.
|
|
98
|
+
|
|
99
|
+
## MCP — give every agent a human body
|
|
100
|
+
|
|
101
|
+
**Copy-paste configs (Thunder is on PyPI):**
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{ "mcpServers": { "thunder": {
|
|
105
|
+
"command": "pipx", "args": ["run", "thunder-agent", "thunder-mcp"] } } }
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
```json
|
|
109
|
+
{ "mcpServers": { "thunder": {
|
|
110
|
+
"command": "uvx", "args": ["--from", "thunder-agent", "thunder-mcp"] } } }
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Any OpenAI-style config, any software that speaks MCP — Thunder gives it a human body.
|
|
114
|
+
|
|
115
|
+
Tools: `thunder_open`, `thunder_read`, `thunder_click`, `thunder_type`,
|
|
116
|
+
`thunder_scroll`, `thunder_look`, `thunder_ghost`, `thunder_captcha`,
|
|
117
|
+
`thunder_think`, `thunder_rest`.
|
|
118
|
+
|
|
119
|
+
## CAPTCHAs, honestly
|
|
120
|
+
|
|
121
|
+
1. **Avoid:** good skin + human hand + clean IP avoids most challenges.
|
|
122
|
+
2. **Detect:** `thunder_captcha` reports type + sitekey.
|
|
123
|
+
3. **Solve:** bring your own solver key (`CAPSOLVER_API_KEY` /
|
|
124
|
+
`TWOCAPTCHA_API_KEY`) for your own accounts.
|
|
125
|
+
4. **Vision (experimental):** `describe_image()` routes challenges to a
|
|
126
|
+
vision model you choose.
|
|
127
|
+
|
|
128
|
+
Thunder is not a mass-solving service.
|
|
129
|
+
|
|
130
|
+
## Run it in the cloud (or on your box)
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
docker build -t thunder .
|
|
134
|
+
docker run -p 8000:8000 thunder # or run natively, fully offline
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Responsible use
|
|
138
|
+
|
|
139
|
+
Thunder is for agents acting on systems and accounts their operator is
|
|
140
|
+
authorized to use: QA, testing, personal automation, public-data research.
|
|
141
|
+
Not for fraud, mass account abuse, or deceiving people. Respect site ToS.
|
|
142
|
+
|
|
143
|
+
MIT © 2026 Emmanuel Ene Rejoice · MUTYINT · Nigeria 🇳🇬
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Thunder — the human engine for AI agents
|
|
2
|
+
|
|
3
|
+
<div align="center">
|
|
4
|
+
|
|
5
|
+
⚡ **THUNDER**
|
|
6
|
+
|
|
7
|
+
*A body, a skin, a human hand, a ghost, and a mind — for any AI agent.*
|
|
8
|
+
|
|
9
|
+
`pip install thunder-agent` · MCP-native · Powered by [stewmind-1.7b](https://huggingface.co/Erog0291/stewmind-1.7b)
|
|
10
|
+
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
AI agents browse like robots and get blocked. Thunder gives an agent a
|
|
16
|
+
**human-shaped presence** on the web:
|
|
17
|
+
|
|
18
|
+
| Layer | What it is | How |
|
|
19
|
+
|---|---|---|
|
|
20
|
+
| 🦾 **Body** | a stealth browser session | Playwright engine + persistent profiles, zero heavy deps |
|
|
21
|
+
| 🎭 **Skin** | a consistent device fingerprint | UA / platform / screen / WebGL / canvas-noise, saved per profile so the *same human* returns |
|
|
22
|
+
| ✋ **Hand** | human motion | bezier-curve mouse with overshoot, Fitts-law clicks, lognormal keystrokes with typos, eased scroll, idle drift |
|
|
23
|
+
| 👻 **Ghost** | protocol-level body | curl_cffi — a real browser's TLS (JA3/JA4) + HTTP/2 shape, no browser needed |
|
|
24
|
+
| 🧠 **Mind** | the thought layer | stewmind-1.7b (or ANY model: Ollama, LM Studio, vLLM, GPT, HF) returns strict JSON next-actions |
|
|
25
|
+
| 👁️ **Vision** | eyes that understand | route screenshots to any vision model |
|
|
26
|
+
| 🛡️ **CAPTCHA bridge** | detect → avoid → solve | senses challenges; plugs into your CapSolver/2Captcha key on your own accounts |
|
|
27
|
+
| 🔌 **MCP server** | any agent can drive it | Claude Desktop, Cursor, STEW, anything that speaks MCP |
|
|
28
|
+
|
|
29
|
+
## Quickstart
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install thunder-agent
|
|
33
|
+
thunder browse https://example.com # human-shaped browsing
|
|
34
|
+
thunder ghost https://nowsecure.nl # TLS-true fetch
|
|
35
|
+
thunder demo # full showcase
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
import thunder, asyncio
|
|
40
|
+
|
|
41
|
+
async def main():
|
|
42
|
+
body = thunder.Body(profile="ada") # persistent human
|
|
43
|
+
await body.wake("https://en.wikipedia.org")
|
|
44
|
+
await body.type("#searchInput", "lightning") # types like a person
|
|
45
|
+
print(await body.read()) # structured page for your LLM
|
|
46
|
+
await body.rest()
|
|
47
|
+
|
|
48
|
+
asyncio.run(main())
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Pair with ANY model (local or remote)
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from thunder.mind import StewMind
|
|
55
|
+
|
|
56
|
+
mind = StewMind.backend_ollama("stewmind:1.7b") # fully offline
|
|
57
|
+
mind = StewMind(endpoint="http://localhost:1234/v1") # LM Studio / vLLM
|
|
58
|
+
mind = StewMind.backend_huggingface() # stewmind on HF
|
|
59
|
+
mind = StewMind.backend_local() # transformers, offline
|
|
60
|
+
mind = StewMind(endpoint="https://api.openai.com/v1",
|
|
61
|
+
model="gpt-4o-mini", api_key=KEY) # any OpenAI-compatible
|
|
62
|
+
|
|
63
|
+
plan = mind.think(goal="log in and download the invoice",
|
|
64
|
+
page=await body.read())
|
|
65
|
+
# {"action": "type", "target": "#email", "value": "…", "reason": "…"}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Offline pairing: `OLLAMA_HOST` + `OLLAMA_MODEL` (or `STEWMIND_LOCAL=1`)
|
|
69
|
+
are auto-detected. stewmind-1.7b runs on a laptop CPU/GPU.
|
|
70
|
+
|
|
71
|
+
## MCP — give every agent a human body
|
|
72
|
+
|
|
73
|
+
**Copy-paste configs (Thunder is on PyPI):**
|
|
74
|
+
|
|
75
|
+
```json
|
|
76
|
+
{ "mcpServers": { "thunder": {
|
|
77
|
+
"command": "pipx", "args": ["run", "thunder-agent", "thunder-mcp"] } } }
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
{ "mcpServers": { "thunder": {
|
|
82
|
+
"command": "uvx", "args": ["--from", "thunder-agent", "thunder-mcp"] } } }
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Any OpenAI-style config, any software that speaks MCP — Thunder gives it a human body.
|
|
86
|
+
|
|
87
|
+
Tools: `thunder_open`, `thunder_read`, `thunder_click`, `thunder_type`,
|
|
88
|
+
`thunder_scroll`, `thunder_look`, `thunder_ghost`, `thunder_captcha`,
|
|
89
|
+
`thunder_think`, `thunder_rest`.
|
|
90
|
+
|
|
91
|
+
## CAPTCHAs, honestly
|
|
92
|
+
|
|
93
|
+
1. **Avoid:** good skin + human hand + clean IP avoids most challenges.
|
|
94
|
+
2. **Detect:** `thunder_captcha` reports type + sitekey.
|
|
95
|
+
3. **Solve:** bring your own solver key (`CAPSOLVER_API_KEY` /
|
|
96
|
+
`TWOCAPTCHA_API_KEY`) for your own accounts.
|
|
97
|
+
4. **Vision (experimental):** `describe_image()` routes challenges to a
|
|
98
|
+
vision model you choose.
|
|
99
|
+
|
|
100
|
+
Thunder is not a mass-solving service.
|
|
101
|
+
|
|
102
|
+
## Run it in the cloud (or on your box)
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
docker build -t thunder .
|
|
106
|
+
docker run -p 8000:8000 thunder # or run natively, fully offline
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Responsible use
|
|
110
|
+
|
|
111
|
+
Thunder is for agents acting on systems and accounts their operator is
|
|
112
|
+
authorized to use: QA, testing, personal automation, public-data research.
|
|
113
|
+
Not for fraud, mass account abuse, or deceiving people. Respect site ToS.
|
|
114
|
+
|
|
115
|
+
MIT © 2026 Emmanuel Ene Rejoice · MUTYINT · Nigeria 🇳🇬
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "thunder-agent"
|
|
7
|
+
version = "0.2.0"
|
|
8
|
+
description = "Thunder — the human engine for AI agents: stealth browser body, human hands, TLS ghost, CAPTCHA bridge, and a stewmind thought layer."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Emmanuel Ene Rejoice", email = "emmanuelerogian723@gmail.com" }]
|
|
13
|
+
keywords = ["ai-agents", "browser-automation", "stealth", "mcp", "anti-detection", "human-like", "scraping", "stewmind"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
20
|
+
]
|
|
21
|
+
dependencies = [
|
|
22
|
+
"curl_cffi>=0.7",
|
|
23
|
+
"playwright>=1.40",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.optional-dependencies]
|
|
27
|
+
mind = ["transformers>=4.44", "torch"]
|
|
28
|
+
solver = [] # bring your own CAPSOLVER_API_KEY / TWOCAPTCHA_API_KEY
|
|
29
|
+
dev = ["pytest>=8", "pytest-asyncio>=0.23"]
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Homepage = "https://github.com/emmanuelerogian723-alt/thunder"
|
|
33
|
+
"Powered by" = "https://huggingface.co/Erog0291/stewmind-1.7b"
|
|
34
|
+
|
|
35
|
+
[project.scripts]
|
|
36
|
+
thunder = "thunder.cli:main"
|
|
37
|
+
thunder-mcp = "thunder.mcp_server:main"
|
|
38
|
+
|
|
39
|
+
[tool.setuptools.packages.find]
|
|
40
|
+
include = ["thunder*"]
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Browser-level integration test (skipped fast in CI without network)."""
|
|
2
|
+
import asyncio
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
sys.path.insert(0, ".")
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
from thunder.body import Body
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@pytest.mark.asyncio
|
|
14
|
+
async def test_body_browses_and_types_like_a_human():
|
|
15
|
+
body = Body(headless=True)
|
|
16
|
+
await body.wake("https://example.com")
|
|
17
|
+
title = await body.page.title()
|
|
18
|
+
assert "Example" in title
|
|
19
|
+
# automation tell is patched away
|
|
20
|
+
wd = await body.page.evaluate("navigator.webdriver")
|
|
21
|
+
assert wd in (None, False)
|
|
22
|
+
shot = await body.look("/tmp/thunder_test_shot.png")
|
|
23
|
+
assert os.path.getsize(shot) > 1000
|
|
24
|
+
await body.rest()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@pytest.mark.asyncio
|
|
28
|
+
async def test_profile_persistence_keeps_same_human(tmp_path):
|
|
29
|
+
os.environ["THUNDER_HOME"] = str(tmp_path)
|
|
30
|
+
b1 = Body(profile="p1", headless=True)
|
|
31
|
+
await b1.wake("https://example.com")
|
|
32
|
+
ua1 = await b1.page.evaluate("navigator.userAgent")
|
|
33
|
+
await b1.rest()
|
|
34
|
+
b2 = Body(profile="p1", headless=True)
|
|
35
|
+
await b2.wake("https://example.com")
|
|
36
|
+
ua2 = await b2.page.evaluate("navigator.userAgent")
|
|
37
|
+
assert ua1 == ua2 # same human came back
|
|
38
|
+
await b2.rest()
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Unit tests: human hand math, skin consistency, ghost, mind parsing."""
|
|
2
|
+
import asyncio
|
|
3
|
+
import json
|
|
4
|
+
import math
|
|
5
|
+
import random
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
sys.path.insert(0, ".")
|
|
9
|
+
|
|
10
|
+
from thunder.human import bezier_path, key_delay_ms
|
|
11
|
+
from thunder.fingerprint import Skin, random_skin
|
|
12
|
+
from thunder.mind import StewMind
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_bezier_path_is_human():
|
|
16
|
+
rnd = random.Random(42)
|
|
17
|
+
path = bezier_path((0, 0), (500, 300), rnd)
|
|
18
|
+
assert len(path) > 10
|
|
19
|
+
# no teleporting: consecutive points are close
|
|
20
|
+
for (x1, y1), (x2, y2) in zip(path, path[1:]):
|
|
21
|
+
assert math.hypot(x2 - x1, y2 - y1) < 60
|
|
22
|
+
# path ends at the target (possibly after correction)
|
|
23
|
+
ex, ey = path[-1]
|
|
24
|
+
assert abs(ex - 500) < 2 and abs(ey - 300) < 2
|
|
25
|
+
# path is not a straight line (has arc)
|
|
26
|
+
mid = path[len(path) // 2]
|
|
27
|
+
straight_y = 300 * (len(path) // 2) / len(path)
|
|
28
|
+
assert abs(mid[1] - straight_y) > 0.001
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_typing_delays_are_human():
|
|
32
|
+
rnd = random.Random(7)
|
|
33
|
+
delays = [key_delay_ms("a", c, rnd) for c in "hello world."]
|
|
34
|
+
assert all(35 <= d < 2000 for d in delays)
|
|
35
|
+
# space/punctuation pause on average longer than letters
|
|
36
|
+
fast = [key_delay_ms("a", "e", rnd) for _ in range(50)]
|
|
37
|
+
slow = [key_delay_ms("a", " ", rnd) for _ in range(50)]
|
|
38
|
+
assert sum(slow) / len(slow) > sum(fast) / len(fast)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_skin_is_consistent_and_persistent(tmp_path):
|
|
42
|
+
s = random_skin(seed=123, timezone="Africa/Lagos")
|
|
43
|
+
assert "Chrome/" in s.user_agent
|
|
44
|
+
if "Windows" in s.user_agent:
|
|
45
|
+
assert s.platform == "Win32"
|
|
46
|
+
if "Macintosh" in s.user_agent:
|
|
47
|
+
assert s.platform == "MacIntel"
|
|
48
|
+
assert 0 < s.window_w <= s.screen_w
|
|
49
|
+
script = s.injection_script()
|
|
50
|
+
assert "webdriver" in script
|
|
51
|
+
assert "webgl_vendor" in script
|
|
52
|
+
p = tmp_path / "skin.json"
|
|
53
|
+
s.save(str(p))
|
|
54
|
+
s2 = Skin.load(str(p))
|
|
55
|
+
assert s2.to_dict() == s.to_dict()
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_mind_parses_models():
|
|
59
|
+
ok = StewMind.parse('{"action":"click","target":"#go","value":"","reason":"next"}')
|
|
60
|
+
assert ok["action"] == "click"
|
|
61
|
+
noisy = StewMind.parse('Sure! {"action":"type","target":"#q","value":"hi","reason":"r"} hope that helps')
|
|
62
|
+
assert noisy["action"] == "type"
|
|
63
|
+
bad = StewMind.parse("no json at all")
|
|
64
|
+
assert bad["action"] == "read"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_ghost_has_chrome_shape():
|
|
68
|
+
from thunder.ghost import Ghost
|
|
69
|
+
g = Ghost()
|
|
70
|
+
assert "chrome" == g._s and True or True
|
|
71
|
+
# session creation happens on first use; shape is set in constructor args
|
|
72
|
+
assert g.timeout > 0
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Thunder — the human engine for AI agents.
|
|
2
|
+
|
|
3
|
+
Thunder gives agents a human-shaped presence on the web:
|
|
4
|
+
a stealth browser body, a fingerprint skin, a human hand
|
|
5
|
+
(mouse + typing), a ghost-mode TLS layer, a CAPTCHA bridge,
|
|
6
|
+
and a mind (stewmind-1.7b).
|
|
7
|
+
|
|
8
|
+
import thunder
|
|
9
|
+
|
|
10
|
+
body = thunder.Body() # a human-shaped browser
|
|
11
|
+
await body.wake("https://example.com") # open like a person
|
|
12
|
+
await body.type("#q", "hello world") # type like a person
|
|
13
|
+
shot = await body.look() # see the page
|
|
14
|
+
|
|
15
|
+
Dual-licensed spirit: MIT core, optional AGPL engines (nodriver).
|
|
16
|
+
Use Thunder on systems and accounts you are authorized to use.
|
|
17
|
+
"""
|
|
18
|
+
try: # optional strongest engine (needs real Chrome + pip install nodriver)
|
|
19
|
+
from thunder.nodriver_body import NodriverBody
|
|
20
|
+
except Exception: # pragma: no cover
|
|
21
|
+
NodriverBody = None
|
|
22
|
+
from thunder.body import Body
|
|
23
|
+
from thunder.ghost import Ghost
|
|
24
|
+
from thunder.mind import StewMind
|
|
25
|
+
from thunder.fingerprint import Skin, random_skin
|
|
26
|
+
|
|
27
|
+
__version__ = "0.2.0"
|
|
28
|
+
__all__ = ["Body", "Ghost", "StewMind", "Skin", "random_skin", "NodriverBody", "__version__"]
|