cloakbrowser-agent 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.
- cloakbrowser_agent-0.1.0/.github/workflows/publish.yml +61 -0
- cloakbrowser_agent-0.1.0/.gitignore +6 -0
- cloakbrowser_agent-0.1.0/LICENSE +21 -0
- cloakbrowser_agent-0.1.0/LICENSE-jev-ultrafast +21 -0
- cloakbrowser_agent-0.1.0/PKG-INFO +262 -0
- cloakbrowser_agent-0.1.0/README.md +248 -0
- cloakbrowser_agent-0.1.0/cloak_agent/__init__.py +6 -0
- cloakbrowser_agent-0.1.0/cloak_agent/agent.py +99 -0
- cloakbrowser_agent-0.1.0/cloak_agent/browser.py +201 -0
- cloakbrowser_agent-0.1.0/cloak_agent/cli.py +66 -0
- cloakbrowser_agent-0.1.0/cloak_agent/markdown.js +38 -0
- cloakbrowser_agent-0.1.0/cloak_agent/mcp_server.py +169 -0
- cloakbrowser_agent-0.1.0/cloak_agent/model.py +266 -0
- cloakbrowser_agent-0.1.0/cloak_agent/questions.py +31 -0
- cloakbrowser_agent-0.1.0/cloak_agent/snapshot.js +147 -0
- cloakbrowser_agent-0.1.0/pyproject.toml +29 -0
- cloakbrowser_agent-0.1.0/tests/test_model.py +100 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
name: Publish
|
|
2
|
+
|
|
3
|
+
# Push a v* tag (matching pyproject.toml's version) → test → validate → publish to PyPI.
|
|
4
|
+
# Mirrors CloakHQ/CloakBrowser's publish.yml (same pinned actions, OIDC trusted publishing).
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
push:
|
|
8
|
+
tags:
|
|
9
|
+
- 'v*'
|
|
10
|
+
workflow_dispatch: # manual re-run of a failed publish
|
|
11
|
+
|
|
12
|
+
concurrency:
|
|
13
|
+
group: publish
|
|
14
|
+
cancel-in-progress: false
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
test:
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
21
|
+
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
22
|
+
with:
|
|
23
|
+
python-version: "3.12"
|
|
24
|
+
- name: Tests and lint (offline, no browser, no paid API calls)
|
|
25
|
+
run: |
|
|
26
|
+
pip install -e . pytest ruff
|
|
27
|
+
ruff check .
|
|
28
|
+
pytest tests/ -v
|
|
29
|
+
|
|
30
|
+
validate-version:
|
|
31
|
+
if: startsWith(github.ref, 'refs/tags/')
|
|
32
|
+
runs-on: ubuntu-latest
|
|
33
|
+
steps:
|
|
34
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
35
|
+
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
36
|
+
with:
|
|
37
|
+
python-version: "3.12"
|
|
38
|
+
- name: Check tag matches pyproject version
|
|
39
|
+
run: |
|
|
40
|
+
TAG="${GITHUB_REF_NAME#v}"
|
|
41
|
+
PY=$(python -c 'import tomllib; print(tomllib.load(open("pyproject.toml", "rb"))["project"]["version"])')
|
|
42
|
+
echo "Tag: $TAG | pyproject: $PY"
|
|
43
|
+
[ "$TAG" = "$PY" ] || { echo "ERROR: tag v$TAG != pyproject.toml version $PY"; exit 1; }
|
|
44
|
+
|
|
45
|
+
publish-pypi:
|
|
46
|
+
needs: [test, validate-version]
|
|
47
|
+
if: always() && needs.test.result == 'success' && (needs.validate-version.result == 'success' || needs.validate-version.result == 'skipped')
|
|
48
|
+
runs-on: ubuntu-latest
|
|
49
|
+
permissions:
|
|
50
|
+
id-token: write # OIDC trusted publishing — no PYPI_TOKEN needed
|
|
51
|
+
steps:
|
|
52
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
53
|
+
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
54
|
+
with:
|
|
55
|
+
python-version: "3.12"
|
|
56
|
+
- name: Build
|
|
57
|
+
run: |
|
|
58
|
+
pip install build
|
|
59
|
+
python -m build
|
|
60
|
+
- name: Publish to PyPI
|
|
61
|
+
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 CloakHQ
|
|
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.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Browser Use
|
|
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.
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: cloakbrowser-agent
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Jev-powered stealth browser agent: TypeSafe Jev decides each step, CloakBrowser carries it out. MCP server, CLI and Python API.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
License-File: LICENSE-jev-ultrafast
|
|
8
|
+
Keywords: ai-agent,browser-agent,browser-automation,chromium,cloakbrowser,jev,mcp,mcp-server,model-context-protocol,playwright,stealth-browser,system-one,typesafe,web-agent
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Requires-Dist: cloakbrowser==0.5.11
|
|
11
|
+
Requires-Dist: httpx<1,>=0.27
|
|
12
|
+
Requires-Dist: mcp<3,>=2.2
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# CloakBrowser Agent: a Jev-powered stealth browser agent
|
|
16
|
+
|
|
17
|
+
**Give it a goal in plain language. [TypeSafe Jev](https://docs.typesafe.ai/introduction) decides every step in ~0.3 s, [CloakBrowser](https://github.com/CloakHQ/CloakBrowser) carries it out like a human, and you get the result back as markdown.**
|
|
18
|
+
|
|
19
|
+
```text
|
|
20
|
+
browse(goal="Search Google for 'CloakBrowser GitHub', open the CloakHQ/CloakBrowser repository on GitHub
|
|
21
|
+
from the results, and find how many stars it has and what the latest release is.",
|
|
22
|
+
url="https://www.google.com")
|
|
23
|
+
|
|
24
|
+
status: done
|
|
25
|
+
tab_id: t1 (still open)
|
|
26
|
+
url: https://github.com/CloakHQ/cloakbrowser/releases
|
|
27
|
+
title: Releases · CloakHQ/CloakBrowser
|
|
28
|
+
steps: 3 actions, 5 decisions, 12918 ms
|
|
29
|
+
actions taken (p = Jev's probability for the chosen target; runner-ups in brackets):
|
|
30
|
+
1. fill 'Išči' = 'CloakBrowser GitHub' p=1.0
|
|
31
|
+
2. click 'cloakbrowser github' p=0.59 ['Iskanje Google' p=0.25, 'cloakhq cloakbrowser github' p=0.13]
|
|
32
|
+
3. click 'CloakHQ/CloakBrowser' p=0.96 ['Open Išči' p=0.04]
|
|
33
|
+
...
|
|
34
|
+
<untrusted_page_content>
|
|
35
|
+
[Star 31.8k](...)
|
|
36
|
+
# Releases: CloakHQ/CloakBrowser
|
|
37
|
+
## Chromium v152.0.7977.82.1 — ... [Latest](https://github.com/CloakHQ/CloakBrowser/releases/latest)
|
|
38
|
+
...
|
|
39
|
+
```
|
|
40
|
+
*A real run, trimmed. Google's labels are in Slovenian because of where the test machine is.*
|
|
41
|
+
|
|
42
|
+
It works as an **MCP server** (Claude Code, Cursor, Claude Desktop, any MCP client), a **CLI**, or a **Python library**.
|
|
43
|
+
It runs on [CloakBrowser](https://github.com/CloakHQ/CloakBrowser), a stealth Chromium with human-like mouse and keyboard input.
|
|
44
|
+
|
|
45
|
+
## Why Jev
|
|
46
|
+
|
|
47
|
+
Most browser agents ask a large language model to *write* the next action, which costs seconds per step.
|
|
48
|
+
[Jev](https://docs.typesafe.ai/introduction) is TypeSafe's first **System One** model. It doesn't generate text: you give it the current state plus typed questions, and it returns an answer with calibrated probabilities.
|
|
49
|
+
A browser step is exactly that kind of question: *which of these controls, doing what?*
|
|
50
|
+
|
|
51
|
+
- **One request per step.** Jev answers "which operation" and "which element" together in one request (speculative fan-out: a target question for each possible operation, and only the chosen one is used).
|
|
52
|
+
- **Fast.** In our runs, Jev decisions averaged 0.28–0.43 s each, about 1 s for a whole search task. A text model is called only when a field needs typing.
|
|
53
|
+
- **Probabilities, not prose.** Every decision comes with a distribution and a confidence, so the code can see when the model is unsure.
|
|
54
|
+
- **Nothing to parse.** Answers are typed choices from options we built, so there's no free-form output to go wrong.
|
|
55
|
+
- **Jev ranks the result too.** When the task is done, Jev scores every section of the final page against the goal, and only the relevant sections come back.
|
|
56
|
+
|
|
57
|
+
## How it works
|
|
58
|
+
|
|
59
|
+
```text
|
|
60
|
+
goal ─► OBSERVE read the page → numbered table of the controls a user can actually reach
|
|
61
|
+
▲
|
|
62
|
+
│ DECIDE one Jev request: which operation (CLICK, TYPE_TEXT, SELECT, SCROLL, WAIT, DONE, BLOCKED)
|
|
63
|
+
│ and which element, answered together
|
|
64
|
+
│ TYPE_TEXT → a small text model writes only the value for that one field
|
|
65
|
+
│
|
|
66
|
+
└─ ACT human-like click / typing, after checking the page did not change
|
|
67
|
+
DONE → the page is turned into markdown, Jev scores each section against the goal, the best sections are returned
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
- **No site-specific code.** The same loop runs on every site and re-plans from the current page after every action. Cookie walls, popups and changed layouts are just more elements to choose from.
|
|
71
|
+
- **Model output never becomes code.** The model picks from indices we built. It never writes selectors, coordinates or scripts.
|
|
72
|
+
- **Nothing is injected into the page.** The page is read without adding anything to it that the site's scripts can see.
|
|
73
|
+
- **Only reachable controls are offered.** Hidden, disabled, and covered elements (for example, behind a modal) aren't in the table.
|
|
74
|
+
- **A DONE answer isn't taken as proof.** Check results that matter.
|
|
75
|
+
|
|
76
|
+
## Requirements
|
|
77
|
+
|
|
78
|
+
- Python 3.10+
|
|
79
|
+
- A [TypeSafe API key](https://docs.typesafe.ai/introduction) (Jev)
|
|
80
|
+
- A key for any OpenAI-compatible chat model (OpenRouter, OpenAI, DeepSeek, …). It is used only to write field values.
|
|
81
|
+
- A [CloakBrowser](https://cloakbrowser.dev) license key for the latest stealth build. **A free key takes one GitHub sign-in:** run `cloakbrowser login` or go to [cloakbrowser.dev/free](https://cloakbrowser.dev/free). A free key allows one browser session at a time, and a paid key raises that limit. Without any key, the older build is used.
|
|
82
|
+
|
|
83
|
+
The browser binary downloads automatically on first use. Node is not needed.
|
|
84
|
+
|
|
85
|
+
## Install
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
pip install cloakbrowser-agent
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
This installs the `cloak-agent` and `cloak-agent-mcp` commands, plus `cloakbrowser` (for `cloakbrowser login`).
|
|
92
|
+
For MCP clients you don't even need to install it: the configs below use [`uvx`](https://docs.astral.sh/uv/), which fetches and runs it on demand.
|
|
93
|
+
|
|
94
|
+
From source: `git clone https://github.com/CloakHQ/CloakBrowser-Agent && cd CloakBrowser-Agent && pip install -e .`
|
|
95
|
+
|
|
96
|
+
## Configure
|
|
97
|
+
|
|
98
|
+
| Variable | Required | Meaning |
|
|
99
|
+
|---|---|---|
|
|
100
|
+
| `TYPESAFE_API_KEY` | yes | Jev decisions |
|
|
101
|
+
| `TYPESAFE_MODEL` | no | default `jev-latest` |
|
|
102
|
+
| `TEXT_MODEL_BASE_URL` | yes | OpenAI-compatible base URL, e.g. `https://openrouter.ai/api/v1` |
|
|
103
|
+
| `TEXT_MODEL_API_KEY` | yes | key for that endpoint |
|
|
104
|
+
| `TEXT_MODEL` | yes | model id, e.g. a small fast model |
|
|
105
|
+
| `TEXT_MODEL_REASONING` | no | sent as `reasoning_effort` (`low` / `medium` / `high`); `none` omits it |
|
|
106
|
+
| `TEXT_MODEL_HEADERS` | no | JSON object of extra request headers, if your provider needs any |
|
|
107
|
+
| `CLOAKBROWSER_LICENSE_KEY` | recommended | CloakBrowser license key (`cb_...`). Instead of setting it here, you can run `cloakbrowser login` once: the saved key is picked up automatically |
|
|
108
|
+
|
|
109
|
+
## Use as an MCP server
|
|
110
|
+
|
|
111
|
+
**Claude Code**
|
|
112
|
+
```bash
|
|
113
|
+
claude mcp add cloak-agent --scope user \
|
|
114
|
+
-e TYPESAFE_API_KEY=... \
|
|
115
|
+
-e TEXT_MODEL_BASE_URL=https://openrouter.ai/api/v1 -e TEXT_MODEL=... -e TEXT_MODEL_API_KEY=... \
|
|
116
|
+
-e CLOAKBROWSER_LICENSE_KEY=cb_... \
|
|
117
|
+
-- uvx --from cloakbrowser-agent cloak-agent-mcp
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
**Cursor / Claude Desktop** (`mcpServers` in the client's config)
|
|
121
|
+
```json
|
|
122
|
+
{
|
|
123
|
+
"mcpServers": {
|
|
124
|
+
"cloak-agent": {
|
|
125
|
+
"command": "uvx",
|
|
126
|
+
"args": ["--from", "cloakbrowser-agent", "cloak-agent-mcp"],
|
|
127
|
+
"env": {
|
|
128
|
+
"TYPESAFE_API_KEY": "...",
|
|
129
|
+
"TEXT_MODEL_BASE_URL": "https://openrouter.ai/api/v1",
|
|
130
|
+
"TEXT_MODEL": "...",
|
|
131
|
+
"TEXT_MODEL_API_KEY": "...",
|
|
132
|
+
"CLOAKBROWSER_LICENSE_KEY": "cb_..."
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Tools
|
|
140
|
+
|
|
141
|
+
**`browse(goal, url?, tab_id?)`** runs one whole task and returns:
|
|
142
|
+
- `status`: `done`, `blocked` (no control can make progress), `needs_input` (the goal lacks a value a field needs), `budget` (step limit reached), or `error`
|
|
143
|
+
- `tab_id` (the tab stays open), the final URL and title, and the number of actions, decisions and milliseconds
|
|
144
|
+
- **every step taken:** what was clicked or typed, Jev's probability `p` for it, and the top runner-ups in brackets. A low `p`, or a runner-up close behind, shows where the agent was unsure.
|
|
145
|
+
- **stale retries**, if any: steps re-decided because the page changed before acting, with what changed
|
|
146
|
+
- the relevant page content as markdown, fenced as `<untrusted_page_content>`
|
|
147
|
+
|
|
148
|
+
While a call runs, each step is also sent as a live **progress notification** (clients that show MCP progress display it).
|
|
149
|
+
|
|
150
|
+
**`close_tab(tab_id)`** closes a tab.
|
|
151
|
+
|
|
152
|
+
#### Working with tabs
|
|
153
|
+
| Call | What happens |
|
|
154
|
+
|---|---|
|
|
155
|
+
| `browse(goal, url)` | New tab, opens `url`, runs the goal |
|
|
156
|
+
| `browse(goal, tab_id="t1")` | Continues on the page tab `t1` is showing |
|
|
157
|
+
| `browse(goal, url, tab_id="t1")` | Navigates tab `t1` to `url`, then runs the goal |
|
|
158
|
+
| `browse(goal)` | Error: give a `url` or a `tab_id` |
|
|
159
|
+
|
|
160
|
+
A `tab_id` stays valid until you `close_tab` it, close it in the browser, or the browser idles out. After that, `browse` answers `status: error (tab 't1' is gone; open tabs: ...)`.
|
|
161
|
+
|
|
162
|
+
#### Multi-call examples
|
|
163
|
+
```text
|
|
164
|
+
# 1. A task that needs values the goal did not include
|
|
165
|
+
browse(goal="Fill in the pizza order form and submit it.", url="https://httpbin.org/forms/post")
|
|
166
|
+
→ status: needs_input (No value in the goal for field: Customer name:) tab_id: t1
|
|
167
|
+
browse(goal="Fill in the pizza order form with customer name Jane Doe, telephone 555-0100, "
|
|
168
|
+
"email jane@example.com, size medium, and submit it.", tab_id="t1")
|
|
169
|
+
→ status: done (fills all four fields on the same form, clicks 'Submit order')
|
|
170
|
+
|
|
171
|
+
# 2. A follow-up step on the page the last task ended on
|
|
172
|
+
browse(goal="Search Google for 'CloakBrowser GitHub' and open the CloakHQ/CloakBrowser repository.",
|
|
173
|
+
url="https://www.google.com")
|
|
174
|
+
→ status: done tab_id: t2
|
|
175
|
+
browse(goal="Open the Issues tab of this repository and list the titles of the newest issues.", tab_id="t2")
|
|
176
|
+
→ status: done (1 action: click 'Issues')
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Tips:
|
|
180
|
+
- Put every value the task needs into the goal (search terms, form values), because fields are filled from the goal.
|
|
181
|
+
- Several `browse` calls can run at once. Each gets its own tab in the same browser.
|
|
182
|
+
|
|
183
|
+
### Browser lifecycle
|
|
184
|
+
|
|
185
|
+
- The browser starts on the first call, headed by default so you can watch it.
|
|
186
|
+
- Tabs stay open after a task so you can see the result.
|
|
187
|
+
- After `CLOAK_AGENT_IDLE_MINUTES` without calls (default 5), the browser closes itself along with its tabs, and it relaunches on the next call. It also closes when the MCP server stops.
|
|
188
|
+
- An open browser holds one CloakBrowser session. On a free key (one session), close it or let it idle out before running another CloakBrowser script.
|
|
189
|
+
|
|
190
|
+
| Variable | Default | Meaning |
|
|
191
|
+
|---|---|---|
|
|
192
|
+
| `CLOAK_AGENT_IDLE_MINUTES` | `5` | close the browser after this long without calls |
|
|
193
|
+
| `CLOAK_AGENT_HEADLESS` | off | `1` = run headless |
|
|
194
|
+
| `CLOAK_AGENT_HUMANIZE` | on | `0` = instant clicks and typing (faster, less human-like) |
|
|
195
|
+
| `CLOAK_AGENT_PROFILE` | `~/.cloakbrowser-agent/profile` | persistent browser profile (cookies and consent choices survive) |
|
|
196
|
+
| `CLOAK_AGENT_CDP` | unset | attach to an already running browser, e.g. `http://127.0.0.1:9222` |
|
|
197
|
+
|
|
198
|
+
A profile can only be open in one browser at a time. Give each instance its own `CLOAK_AGENT_PROFILE`.
|
|
199
|
+
|
|
200
|
+
## Use from the command line
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
export TYPESAFE_API_KEY=... TEXT_MODEL_BASE_URL=https://openrouter.ai/api/v1 TEXT_MODEL=... TEXT_MODEL_API_KEY=...
|
|
204
|
+
export CLOAKBROWSER_LICENSE_KEY=cb_... # or run `cloakbrowser login` once
|
|
205
|
+
|
|
206
|
+
# one task, own browser (closes at the end; add --keep-open to inspect)
|
|
207
|
+
cloak-agent run --url https://en.wikipedia.org --goal "Open the Wikipedia article about Gödel's incompleteness theorems."
|
|
208
|
+
|
|
209
|
+
# debugging: keep one headed browser running, then run tasks against it
|
|
210
|
+
cloak-agent browser --port 9222
|
|
211
|
+
cloak-agent run --cdp http://127.0.0.1:9222 --url https://www.google.com --goal "Search Google for 'CloakBrowser'"
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
`--no-humanize` switches to instant input.
|
|
215
|
+
|
|
216
|
+
## Use from Python
|
|
217
|
+
|
|
218
|
+
Uses the same environment variables as above, including `CLOAKBROWSER_LICENSE_KEY` (or the key saved by `cloakbrowser login`).
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
import asyncio
|
|
222
|
+
from cloak_agent import Session, run
|
|
223
|
+
|
|
224
|
+
async def main():
|
|
225
|
+
session = await Session.launch(headless=False) # or: await Session.connect("http://127.0.0.1:9222")
|
|
226
|
+
page = await session.new_tab("https://en.wikipedia.org")
|
|
227
|
+
result = await run(session, "Open the Wikipedia article about Gödel's incompleteness theorems.", page=page)
|
|
228
|
+
print(result["status"], result["url"])
|
|
229
|
+
print(result["markdown"])
|
|
230
|
+
await session.close()
|
|
231
|
+
|
|
232
|
+
asyncio.run(main())
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## Speed
|
|
236
|
+
|
|
237
|
+
Most of a humanized run is spent moving the mouse and typing at a human pace. That's on purpose, because behavior is scored on protected sites.
|
|
238
|
+
|
|
239
|
+
| Task (single runs, one machine) | Humanized | `--no-humanize` |
|
|
240
|
+
|---|---|---|
|
|
241
|
+
| Google search → results | 11.4 s | 3.4 s |
|
|
242
|
+
| Wikipedia search → open article | ~12 s | — |
|
|
243
|
+
|
|
244
|
+
Jev decisions take ~0.3 s each, roughly 1 s per task. Filling a field with the text model adds 0.5–2 s depending on the model.
|
|
245
|
+
|
|
246
|
+
## Limits
|
|
247
|
+
|
|
248
|
+
- Frames, closed shadow roots, canvas apps, file uploads, links that open new tabs, and CAPTCHAs aren't handled.
|
|
249
|
+
- Native `<select>` values are set programmatically, not through a mouse-driven dropdown.
|
|
250
|
+
- Autocomplete: after typing, it may pick a close suggestion instead of submitting the exact text.
|
|
251
|
+
- Password fields are never read or offered to the model.
|
|
252
|
+
- A valid action can still be the wrong one, and `done` means the model saw the goal as met. Check results that matter.
|
|
253
|
+
|
|
254
|
+
## Security
|
|
255
|
+
|
|
256
|
+
- Page content is untrusted. It's returned fenced as `<untrusted_page_content>` so the calling agent doesn't treat it as instructions.
|
|
257
|
+
- API keys stay in the server's environment and are only sent to the endpoints you configure.
|
|
258
|
+
- For Google result links, the real target is resolved with one direct request per link, since Google hides it.
|
|
259
|
+
|
|
260
|
+
## Credits
|
|
261
|
+
|
|
262
|
+
The observe → choose → act loop, the element snapshot and the Jev instructions are adapted from [browser-use/jev-ultrafast](https://github.com/browser-use/jev-ultrafast) (MIT, see `LICENSE-jev-ultrafast`).
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
# CloakBrowser Agent: a Jev-powered stealth browser agent
|
|
2
|
+
|
|
3
|
+
**Give it a goal in plain language. [TypeSafe Jev](https://docs.typesafe.ai/introduction) decides every step in ~0.3 s, [CloakBrowser](https://github.com/CloakHQ/CloakBrowser) carries it out like a human, and you get the result back as markdown.**
|
|
4
|
+
|
|
5
|
+
```text
|
|
6
|
+
browse(goal="Search Google for 'CloakBrowser GitHub', open the CloakHQ/CloakBrowser repository on GitHub
|
|
7
|
+
from the results, and find how many stars it has and what the latest release is.",
|
|
8
|
+
url="https://www.google.com")
|
|
9
|
+
|
|
10
|
+
status: done
|
|
11
|
+
tab_id: t1 (still open)
|
|
12
|
+
url: https://github.com/CloakHQ/cloakbrowser/releases
|
|
13
|
+
title: Releases · CloakHQ/CloakBrowser
|
|
14
|
+
steps: 3 actions, 5 decisions, 12918 ms
|
|
15
|
+
actions taken (p = Jev's probability for the chosen target; runner-ups in brackets):
|
|
16
|
+
1. fill 'Išči' = 'CloakBrowser GitHub' p=1.0
|
|
17
|
+
2. click 'cloakbrowser github' p=0.59 ['Iskanje Google' p=0.25, 'cloakhq cloakbrowser github' p=0.13]
|
|
18
|
+
3. click 'CloakHQ/CloakBrowser' p=0.96 ['Open Išči' p=0.04]
|
|
19
|
+
...
|
|
20
|
+
<untrusted_page_content>
|
|
21
|
+
[Star 31.8k](...)
|
|
22
|
+
# Releases: CloakHQ/CloakBrowser
|
|
23
|
+
## Chromium v152.0.7977.82.1 — ... [Latest](https://github.com/CloakHQ/CloakBrowser/releases/latest)
|
|
24
|
+
...
|
|
25
|
+
```
|
|
26
|
+
*A real run, trimmed. Google's labels are in Slovenian because of where the test machine is.*
|
|
27
|
+
|
|
28
|
+
It works as an **MCP server** (Claude Code, Cursor, Claude Desktop, any MCP client), a **CLI**, or a **Python library**.
|
|
29
|
+
It runs on [CloakBrowser](https://github.com/CloakHQ/CloakBrowser), a stealth Chromium with human-like mouse and keyboard input.
|
|
30
|
+
|
|
31
|
+
## Why Jev
|
|
32
|
+
|
|
33
|
+
Most browser agents ask a large language model to *write* the next action, which costs seconds per step.
|
|
34
|
+
[Jev](https://docs.typesafe.ai/introduction) is TypeSafe's first **System One** model. It doesn't generate text: you give it the current state plus typed questions, and it returns an answer with calibrated probabilities.
|
|
35
|
+
A browser step is exactly that kind of question: *which of these controls, doing what?*
|
|
36
|
+
|
|
37
|
+
- **One request per step.** Jev answers "which operation" and "which element" together in one request (speculative fan-out: a target question for each possible operation, and only the chosen one is used).
|
|
38
|
+
- **Fast.** In our runs, Jev decisions averaged 0.28–0.43 s each, about 1 s for a whole search task. A text model is called only when a field needs typing.
|
|
39
|
+
- **Probabilities, not prose.** Every decision comes with a distribution and a confidence, so the code can see when the model is unsure.
|
|
40
|
+
- **Nothing to parse.** Answers are typed choices from options we built, so there's no free-form output to go wrong.
|
|
41
|
+
- **Jev ranks the result too.** When the task is done, Jev scores every section of the final page against the goal, and only the relevant sections come back.
|
|
42
|
+
|
|
43
|
+
## How it works
|
|
44
|
+
|
|
45
|
+
```text
|
|
46
|
+
goal ─► OBSERVE read the page → numbered table of the controls a user can actually reach
|
|
47
|
+
▲
|
|
48
|
+
│ DECIDE one Jev request: which operation (CLICK, TYPE_TEXT, SELECT, SCROLL, WAIT, DONE, BLOCKED)
|
|
49
|
+
│ and which element, answered together
|
|
50
|
+
│ TYPE_TEXT → a small text model writes only the value for that one field
|
|
51
|
+
│
|
|
52
|
+
└─ ACT human-like click / typing, after checking the page did not change
|
|
53
|
+
DONE → the page is turned into markdown, Jev scores each section against the goal, the best sections are returned
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
- **No site-specific code.** The same loop runs on every site and re-plans from the current page after every action. Cookie walls, popups and changed layouts are just more elements to choose from.
|
|
57
|
+
- **Model output never becomes code.** The model picks from indices we built. It never writes selectors, coordinates or scripts.
|
|
58
|
+
- **Nothing is injected into the page.** The page is read without adding anything to it that the site's scripts can see.
|
|
59
|
+
- **Only reachable controls are offered.** Hidden, disabled, and covered elements (for example, behind a modal) aren't in the table.
|
|
60
|
+
- **A DONE answer isn't taken as proof.** Check results that matter.
|
|
61
|
+
|
|
62
|
+
## Requirements
|
|
63
|
+
|
|
64
|
+
- Python 3.10+
|
|
65
|
+
- A [TypeSafe API key](https://docs.typesafe.ai/introduction) (Jev)
|
|
66
|
+
- A key for any OpenAI-compatible chat model (OpenRouter, OpenAI, DeepSeek, …). It is used only to write field values.
|
|
67
|
+
- A [CloakBrowser](https://cloakbrowser.dev) license key for the latest stealth build. **A free key takes one GitHub sign-in:** run `cloakbrowser login` or go to [cloakbrowser.dev/free](https://cloakbrowser.dev/free). A free key allows one browser session at a time, and a paid key raises that limit. Without any key, the older build is used.
|
|
68
|
+
|
|
69
|
+
The browser binary downloads automatically on first use. Node is not needed.
|
|
70
|
+
|
|
71
|
+
## Install
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
pip install cloakbrowser-agent
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
This installs the `cloak-agent` and `cloak-agent-mcp` commands, plus `cloakbrowser` (for `cloakbrowser login`).
|
|
78
|
+
For MCP clients you don't even need to install it: the configs below use [`uvx`](https://docs.astral.sh/uv/), which fetches and runs it on demand.
|
|
79
|
+
|
|
80
|
+
From source: `git clone https://github.com/CloakHQ/CloakBrowser-Agent && cd CloakBrowser-Agent && pip install -e .`
|
|
81
|
+
|
|
82
|
+
## Configure
|
|
83
|
+
|
|
84
|
+
| Variable | Required | Meaning |
|
|
85
|
+
|---|---|---|
|
|
86
|
+
| `TYPESAFE_API_KEY` | yes | Jev decisions |
|
|
87
|
+
| `TYPESAFE_MODEL` | no | default `jev-latest` |
|
|
88
|
+
| `TEXT_MODEL_BASE_URL` | yes | OpenAI-compatible base URL, e.g. `https://openrouter.ai/api/v1` |
|
|
89
|
+
| `TEXT_MODEL_API_KEY` | yes | key for that endpoint |
|
|
90
|
+
| `TEXT_MODEL` | yes | model id, e.g. a small fast model |
|
|
91
|
+
| `TEXT_MODEL_REASONING` | no | sent as `reasoning_effort` (`low` / `medium` / `high`); `none` omits it |
|
|
92
|
+
| `TEXT_MODEL_HEADERS` | no | JSON object of extra request headers, if your provider needs any |
|
|
93
|
+
| `CLOAKBROWSER_LICENSE_KEY` | recommended | CloakBrowser license key (`cb_...`). Instead of setting it here, you can run `cloakbrowser login` once: the saved key is picked up automatically |
|
|
94
|
+
|
|
95
|
+
## Use as an MCP server
|
|
96
|
+
|
|
97
|
+
**Claude Code**
|
|
98
|
+
```bash
|
|
99
|
+
claude mcp add cloak-agent --scope user \
|
|
100
|
+
-e TYPESAFE_API_KEY=... \
|
|
101
|
+
-e TEXT_MODEL_BASE_URL=https://openrouter.ai/api/v1 -e TEXT_MODEL=... -e TEXT_MODEL_API_KEY=... \
|
|
102
|
+
-e CLOAKBROWSER_LICENSE_KEY=cb_... \
|
|
103
|
+
-- uvx --from cloakbrowser-agent cloak-agent-mcp
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**Cursor / Claude Desktop** (`mcpServers` in the client's config)
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"mcpServers": {
|
|
110
|
+
"cloak-agent": {
|
|
111
|
+
"command": "uvx",
|
|
112
|
+
"args": ["--from", "cloakbrowser-agent", "cloak-agent-mcp"],
|
|
113
|
+
"env": {
|
|
114
|
+
"TYPESAFE_API_KEY": "...",
|
|
115
|
+
"TEXT_MODEL_BASE_URL": "https://openrouter.ai/api/v1",
|
|
116
|
+
"TEXT_MODEL": "...",
|
|
117
|
+
"TEXT_MODEL_API_KEY": "...",
|
|
118
|
+
"CLOAKBROWSER_LICENSE_KEY": "cb_..."
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Tools
|
|
126
|
+
|
|
127
|
+
**`browse(goal, url?, tab_id?)`** runs one whole task and returns:
|
|
128
|
+
- `status`: `done`, `blocked` (no control can make progress), `needs_input` (the goal lacks a value a field needs), `budget` (step limit reached), or `error`
|
|
129
|
+
- `tab_id` (the tab stays open), the final URL and title, and the number of actions, decisions and milliseconds
|
|
130
|
+
- **every step taken:** what was clicked or typed, Jev's probability `p` for it, and the top runner-ups in brackets. A low `p`, or a runner-up close behind, shows where the agent was unsure.
|
|
131
|
+
- **stale retries**, if any: steps re-decided because the page changed before acting, with what changed
|
|
132
|
+
- the relevant page content as markdown, fenced as `<untrusted_page_content>`
|
|
133
|
+
|
|
134
|
+
While a call runs, each step is also sent as a live **progress notification** (clients that show MCP progress display it).
|
|
135
|
+
|
|
136
|
+
**`close_tab(tab_id)`** closes a tab.
|
|
137
|
+
|
|
138
|
+
#### Working with tabs
|
|
139
|
+
| Call | What happens |
|
|
140
|
+
|---|---|
|
|
141
|
+
| `browse(goal, url)` | New tab, opens `url`, runs the goal |
|
|
142
|
+
| `browse(goal, tab_id="t1")` | Continues on the page tab `t1` is showing |
|
|
143
|
+
| `browse(goal, url, tab_id="t1")` | Navigates tab `t1` to `url`, then runs the goal |
|
|
144
|
+
| `browse(goal)` | Error: give a `url` or a `tab_id` |
|
|
145
|
+
|
|
146
|
+
A `tab_id` stays valid until you `close_tab` it, close it in the browser, or the browser idles out. After that, `browse` answers `status: error (tab 't1' is gone; open tabs: ...)`.
|
|
147
|
+
|
|
148
|
+
#### Multi-call examples
|
|
149
|
+
```text
|
|
150
|
+
# 1. A task that needs values the goal did not include
|
|
151
|
+
browse(goal="Fill in the pizza order form and submit it.", url="https://httpbin.org/forms/post")
|
|
152
|
+
→ status: needs_input (No value in the goal for field: Customer name:) tab_id: t1
|
|
153
|
+
browse(goal="Fill in the pizza order form with customer name Jane Doe, telephone 555-0100, "
|
|
154
|
+
"email jane@example.com, size medium, and submit it.", tab_id="t1")
|
|
155
|
+
→ status: done (fills all four fields on the same form, clicks 'Submit order')
|
|
156
|
+
|
|
157
|
+
# 2. A follow-up step on the page the last task ended on
|
|
158
|
+
browse(goal="Search Google for 'CloakBrowser GitHub' and open the CloakHQ/CloakBrowser repository.",
|
|
159
|
+
url="https://www.google.com")
|
|
160
|
+
→ status: done tab_id: t2
|
|
161
|
+
browse(goal="Open the Issues tab of this repository and list the titles of the newest issues.", tab_id="t2")
|
|
162
|
+
→ status: done (1 action: click 'Issues')
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Tips:
|
|
166
|
+
- Put every value the task needs into the goal (search terms, form values), because fields are filled from the goal.
|
|
167
|
+
- Several `browse` calls can run at once. Each gets its own tab in the same browser.
|
|
168
|
+
|
|
169
|
+
### Browser lifecycle
|
|
170
|
+
|
|
171
|
+
- The browser starts on the first call, headed by default so you can watch it.
|
|
172
|
+
- Tabs stay open after a task so you can see the result.
|
|
173
|
+
- After `CLOAK_AGENT_IDLE_MINUTES` without calls (default 5), the browser closes itself along with its tabs, and it relaunches on the next call. It also closes when the MCP server stops.
|
|
174
|
+
- An open browser holds one CloakBrowser session. On a free key (one session), close it or let it idle out before running another CloakBrowser script.
|
|
175
|
+
|
|
176
|
+
| Variable | Default | Meaning |
|
|
177
|
+
|---|---|---|
|
|
178
|
+
| `CLOAK_AGENT_IDLE_MINUTES` | `5` | close the browser after this long without calls |
|
|
179
|
+
| `CLOAK_AGENT_HEADLESS` | off | `1` = run headless |
|
|
180
|
+
| `CLOAK_AGENT_HUMANIZE` | on | `0` = instant clicks and typing (faster, less human-like) |
|
|
181
|
+
| `CLOAK_AGENT_PROFILE` | `~/.cloakbrowser-agent/profile` | persistent browser profile (cookies and consent choices survive) |
|
|
182
|
+
| `CLOAK_AGENT_CDP` | unset | attach to an already running browser, e.g. `http://127.0.0.1:9222` |
|
|
183
|
+
|
|
184
|
+
A profile can only be open in one browser at a time. Give each instance its own `CLOAK_AGENT_PROFILE`.
|
|
185
|
+
|
|
186
|
+
## Use from the command line
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
export TYPESAFE_API_KEY=... TEXT_MODEL_BASE_URL=https://openrouter.ai/api/v1 TEXT_MODEL=... TEXT_MODEL_API_KEY=...
|
|
190
|
+
export CLOAKBROWSER_LICENSE_KEY=cb_... # or run `cloakbrowser login` once
|
|
191
|
+
|
|
192
|
+
# one task, own browser (closes at the end; add --keep-open to inspect)
|
|
193
|
+
cloak-agent run --url https://en.wikipedia.org --goal "Open the Wikipedia article about Gödel's incompleteness theorems."
|
|
194
|
+
|
|
195
|
+
# debugging: keep one headed browser running, then run tasks against it
|
|
196
|
+
cloak-agent browser --port 9222
|
|
197
|
+
cloak-agent run --cdp http://127.0.0.1:9222 --url https://www.google.com --goal "Search Google for 'CloakBrowser'"
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
`--no-humanize` switches to instant input.
|
|
201
|
+
|
|
202
|
+
## Use from Python
|
|
203
|
+
|
|
204
|
+
Uses the same environment variables as above, including `CLOAKBROWSER_LICENSE_KEY` (or the key saved by `cloakbrowser login`).
|
|
205
|
+
|
|
206
|
+
```python
|
|
207
|
+
import asyncio
|
|
208
|
+
from cloak_agent import Session, run
|
|
209
|
+
|
|
210
|
+
async def main():
|
|
211
|
+
session = await Session.launch(headless=False) # or: await Session.connect("http://127.0.0.1:9222")
|
|
212
|
+
page = await session.new_tab("https://en.wikipedia.org")
|
|
213
|
+
result = await run(session, "Open the Wikipedia article about Gödel's incompleteness theorems.", page=page)
|
|
214
|
+
print(result["status"], result["url"])
|
|
215
|
+
print(result["markdown"])
|
|
216
|
+
await session.close()
|
|
217
|
+
|
|
218
|
+
asyncio.run(main())
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Speed
|
|
222
|
+
|
|
223
|
+
Most of a humanized run is spent moving the mouse and typing at a human pace. That's on purpose, because behavior is scored on protected sites.
|
|
224
|
+
|
|
225
|
+
| Task (single runs, one machine) | Humanized | `--no-humanize` |
|
|
226
|
+
|---|---|---|
|
|
227
|
+
| Google search → results | 11.4 s | 3.4 s |
|
|
228
|
+
| Wikipedia search → open article | ~12 s | — |
|
|
229
|
+
|
|
230
|
+
Jev decisions take ~0.3 s each, roughly 1 s per task. Filling a field with the text model adds 0.5–2 s depending on the model.
|
|
231
|
+
|
|
232
|
+
## Limits
|
|
233
|
+
|
|
234
|
+
- Frames, closed shadow roots, canvas apps, file uploads, links that open new tabs, and CAPTCHAs aren't handled.
|
|
235
|
+
- Native `<select>` values are set programmatically, not through a mouse-driven dropdown.
|
|
236
|
+
- Autocomplete: after typing, it may pick a close suggestion instead of submitting the exact text.
|
|
237
|
+
- Password fields are never read or offered to the model.
|
|
238
|
+
- A valid action can still be the wrong one, and `done` means the model saw the goal as met. Check results that matter.
|
|
239
|
+
|
|
240
|
+
## Security
|
|
241
|
+
|
|
242
|
+
- Page content is untrusted. It's returned fenced as `<untrusted_page_content>` so the calling agent doesn't treat it as instructions.
|
|
243
|
+
- API keys stay in the server's environment and are only sent to the endpoints you configure.
|
|
244
|
+
- For Google result links, the real target is resolved with one direct request per link, since Google hides it.
|
|
245
|
+
|
|
246
|
+
## Credits
|
|
247
|
+
|
|
248
|
+
The observe → choose → act loop, the element snapshot and the Jev instructions are adapted from [browser-use/jev-ultrafast](https://github.com/browser-use/jev-ultrafast) (MIT, see `LICENSE-jev-ultrafast`).
|