@askjo/camofox-browser 1.0.12 → 1.0.14
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.
- package/{Dockerfile.camoufox → Dockerfile} +4 -3
- package/README.md +77 -40
- package/openclaw.plugin.json +1 -1
- package/package.json +9 -9
- package/plugin.ts +24 -4
- package/{run-camoufox.sh → run.sh} +10 -10
- package/{server-camoufox.js → server.js} +32 -9
|
@@ -49,11 +49,12 @@ WORKDIR /app
|
|
|
49
49
|
COPY package.json ./
|
|
50
50
|
RUN npm install --production
|
|
51
51
|
|
|
52
|
-
COPY server
|
|
52
|
+
COPY server.js ./
|
|
53
|
+
COPY lib/ ./lib/
|
|
53
54
|
|
|
54
55
|
ENV NODE_ENV=production
|
|
55
|
-
ENV
|
|
56
|
+
ENV CAMOFOX_PORT=3000
|
|
56
57
|
|
|
57
58
|
EXPOSE 3000
|
|
58
59
|
|
|
59
|
-
CMD ["node", "server
|
|
60
|
+
CMD ["node", "server.js"]
|
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<div align="center">
|
|
2
|
-
<img src="
|
|
2
|
+
<img src="fox.png" alt="camofox-browser" width="200" />
|
|
3
3
|
<h1>camofox-browser</h1>
|
|
4
|
-
<p><strong>
|
|
4
|
+
<p><strong>Anti-detection browser server for AI agents, powered by Camoufox</strong></p>
|
|
5
5
|
<p>
|
|
6
6
|
<a href="https://github.com/jo-inc/camofox-browser/actions"><img src="https://img.shields.io/badge/build-passing-brightgreen" alt="Build" /></a>
|
|
7
7
|
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/license-MIT-blue" alt="License" /></a>
|
|
@@ -9,11 +9,14 @@
|
|
|
9
9
|
<a href="https://hub.docker.com"><img src="https://img.shields.io/badge/docker-ready-blue" alt="Docker" /></a>
|
|
10
10
|
</p>
|
|
11
11
|
<p>
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
Standing on the mighty shoulders of <a href="https://camoufox.com">Camoufox</a> — a Firefox fork with fingerprint spoofing at the C++ level.
|
|
13
|
+
<br/><br/>
|
|
14
|
+
The same engine behind <a href="https://askjo.ai">askjo.ai</a>'s web browsing.
|
|
14
15
|
</p>
|
|
15
16
|
</div>
|
|
16
17
|
|
|
18
|
+
<br/>
|
|
19
|
+
|
|
17
20
|
---
|
|
18
21
|
|
|
19
22
|
## Why
|
|
@@ -28,10 +31,10 @@ This project wraps that engine in a REST API built for agents: accessibility sna
|
|
|
28
31
|
|
|
29
32
|
- **C++ Anti-Detection** — bypasses Google, Cloudflare, and most bot detection
|
|
30
33
|
- **Element Refs** — stable `e1`, `e2`, `e3` identifiers for reliable interaction
|
|
31
|
-
- **Token-Efficient** — accessibility snapshots are 90% smaller than raw HTML
|
|
34
|
+
- **Token-Efficient** — accessibility snapshots are ~90% smaller than raw HTML
|
|
32
35
|
- **Session Isolation** — separate cookies/storage per user
|
|
33
36
|
- **Search Macros** — `@google_search`, `@youtube_search`, `@amazon_search`, and 10 more
|
|
34
|
-
- **
|
|
37
|
+
- **Deploy Anywhere** — Docker, Fly.io, Railway
|
|
35
38
|
|
|
36
39
|
## Quick Start
|
|
37
40
|
|
|
@@ -41,16 +44,6 @@ This project wraps that engine in a REST API built for agents: accessibility sna
|
|
|
41
44
|
openclaw plugins install @askjo/camofox-browser
|
|
42
45
|
```
|
|
43
46
|
|
|
44
|
-
```yaml
|
|
45
|
-
# ~/.openclaw/openclaw.json
|
|
46
|
-
plugins:
|
|
47
|
-
entries:
|
|
48
|
-
camofox-browser:
|
|
49
|
-
enabled: true
|
|
50
|
-
config:
|
|
51
|
-
url: "http://localhost:9377"
|
|
52
|
-
```
|
|
53
|
-
|
|
54
47
|
**Tools:** `camofox_create_tab` · `camofox_snapshot` · `camofox_click` · `camofox_type` · `camofox_navigate` · `camofox_scroll` · `camofox_screenshot` · `camofox_close_tab` · `camofox_list_tabs`
|
|
55
48
|
|
|
56
49
|
### Standalone
|
|
@@ -62,52 +55,84 @@ npm install
|
|
|
62
55
|
npm start # downloads Camoufox on first run (~300MB)
|
|
63
56
|
```
|
|
64
57
|
|
|
58
|
+
Default port is `9377`. Set `CAMOFOX_PORT` to override.
|
|
59
|
+
|
|
65
60
|
### Docker
|
|
66
61
|
|
|
67
62
|
```bash
|
|
68
|
-
docker build -
|
|
69
|
-
docker run -p
|
|
63
|
+
docker build -t camofox-browser .
|
|
64
|
+
docker run -p 9377:9377 camofox-browser
|
|
70
65
|
```
|
|
71
66
|
|
|
67
|
+
### Fly.io / Railway
|
|
68
|
+
|
|
69
|
+
`fly.toml` and `railway.toml` are included. Deploy with `fly deploy` or connect the repo to Railway.
|
|
70
|
+
|
|
72
71
|
## Usage
|
|
73
72
|
|
|
74
73
|
```bash
|
|
75
74
|
# Create a tab
|
|
76
|
-
curl -X POST http://localhost:
|
|
75
|
+
curl -X POST http://localhost:9377/tabs \
|
|
76
|
+
-H 'Content-Type: application/json' \
|
|
77
77
|
-d '{"userId": "agent1", "sessionKey": "task1", "url": "https://example.com"}'
|
|
78
78
|
|
|
79
|
-
# Get
|
|
80
|
-
curl "http://localhost:
|
|
79
|
+
# Get accessibility snapshot with element refs
|
|
80
|
+
curl "http://localhost:9377/tabs/TAB_ID/snapshot?userId=agent1"
|
|
81
81
|
# → { "snapshot": "[button e1] Submit [link e2] Learn more", ... }
|
|
82
82
|
|
|
83
83
|
# Click by ref
|
|
84
|
-
curl -X POST http://localhost:
|
|
84
|
+
curl -X POST http://localhost:9377/tabs/TAB_ID/click \
|
|
85
|
+
-H 'Content-Type: application/json' \
|
|
85
86
|
-d '{"userId": "agent1", "ref": "e1"}'
|
|
86
87
|
|
|
87
|
-
#
|
|
88
|
-
curl -X POST http://localhost:
|
|
88
|
+
# Type into an element
|
|
89
|
+
curl -X POST http://localhost:9377/tabs/TAB_ID/type \
|
|
90
|
+
-H 'Content-Type: application/json' \
|
|
91
|
+
-d '{"userId": "agent1", "ref": "e2", "text": "hello", "pressEnter": true}'
|
|
92
|
+
|
|
93
|
+
# Navigate with a search macro
|
|
94
|
+
curl -X POST http://localhost:9377/tabs/TAB_ID/navigate \
|
|
95
|
+
-H 'Content-Type: application/json' \
|
|
89
96
|
-d '{"userId": "agent1", "macro": "@google_search", "query": "best coffee beans"}'
|
|
90
97
|
```
|
|
91
98
|
|
|
92
99
|
## API
|
|
93
100
|
|
|
101
|
+
### Tab Lifecycle
|
|
102
|
+
|
|
103
|
+
| Method | Endpoint | Description |
|
|
104
|
+
|--------|----------|-------------|
|
|
105
|
+
| `POST` | `/tabs` | Create tab with initial URL |
|
|
106
|
+
| `GET` | `/tabs?userId=X` | List open tabs |
|
|
107
|
+
| `GET` | `/tabs/:id/stats` | Tab stats (tool calls, visited URLs) |
|
|
108
|
+
| `DELETE` | `/tabs/:id` | Close tab |
|
|
109
|
+
| `DELETE` | `/tabs/group/:groupId` | Close all tabs in a group |
|
|
110
|
+
| `DELETE` | `/sessions/:userId` | Close all tabs for a user |
|
|
111
|
+
|
|
112
|
+
### Page Interaction
|
|
113
|
+
|
|
94
114
|
| Method | Endpoint | Description |
|
|
95
115
|
|--------|----------|-------------|
|
|
96
|
-
| `
|
|
97
|
-
| `
|
|
98
|
-
| `POST` | `/tabs/:id/
|
|
99
|
-
| `POST` | `/tabs/:id/
|
|
100
|
-
| `POST` | `/tabs/:id/
|
|
101
|
-
| `POST` | `/tabs/:id/
|
|
102
|
-
| `
|
|
103
|
-
| `GET` | `/tabs/:id/links` |
|
|
116
|
+
| `GET` | `/tabs/:id/snapshot` | Accessibility snapshot with element refs |
|
|
117
|
+
| `POST` | `/tabs/:id/click` | Click element by ref or CSS selector |
|
|
118
|
+
| `POST` | `/tabs/:id/type` | Type text into element |
|
|
119
|
+
| `POST` | `/tabs/:id/press` | Press a keyboard key |
|
|
120
|
+
| `POST` | `/tabs/:id/scroll` | Scroll page (up/down/left/right) |
|
|
121
|
+
| `POST` | `/tabs/:id/navigate` | Navigate to URL or search macro |
|
|
122
|
+
| `POST` | `/tabs/:id/wait` | Wait for selector or timeout |
|
|
123
|
+
| `GET` | `/tabs/:id/links` | Extract all links on page |
|
|
124
|
+
| `GET` | `/tabs/:id/screenshot` | Take screenshot |
|
|
104
125
|
| `POST` | `/tabs/:id/back` | Go back |
|
|
105
126
|
| `POST` | `/tabs/:id/forward` | Go forward |
|
|
106
|
-
| `POST` | `/tabs/:id/refresh` | Refresh |
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
127
|
+
| `POST` | `/tabs/:id/refresh` | Refresh page |
|
|
128
|
+
|
|
129
|
+
### Server
|
|
130
|
+
|
|
131
|
+
| Method | Endpoint | Description |
|
|
132
|
+
|--------|----------|-------------|
|
|
110
133
|
| `GET` | `/health` | Health check |
|
|
134
|
+
| `POST` | `/start` | Start browser engine |
|
|
135
|
+
| `POST` | `/stop` | Stop browser engine |
|
|
111
136
|
|
|
112
137
|
## Search Macros
|
|
113
138
|
|
|
@@ -116,7 +141,7 @@ curl -X POST http://localhost:3000/tabs/TAB_ID/navigate \
|
|
|
116
141
|
## Architecture
|
|
117
142
|
|
|
118
143
|
```
|
|
119
|
-
Browser Instance
|
|
144
|
+
Browser Instance (Camoufox)
|
|
120
145
|
└── User Session (BrowserContext) — isolated cookies/storage
|
|
121
146
|
├── Tab Group (sessionKey: "conv1")
|
|
122
147
|
│ ├── Tab (google.com)
|
|
@@ -130,15 +155,27 @@ Sessions auto-expire after 30 minutes of inactivity.
|
|
|
130
155
|
## Testing
|
|
131
156
|
|
|
132
157
|
```bash
|
|
133
|
-
npm test #
|
|
134
|
-
npm run test:
|
|
158
|
+
npm test # all tests
|
|
159
|
+
npm run test:e2e # e2e tests only
|
|
160
|
+
npm run test:live # live site tests (Google, macros)
|
|
135
161
|
npm run test:debug # with server output
|
|
136
162
|
```
|
|
137
163
|
|
|
164
|
+
## npm
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
npm install @askjo/camofox-browser
|
|
168
|
+
```
|
|
169
|
+
|
|
138
170
|
## Credits
|
|
139
171
|
|
|
140
172
|
- [Camoufox](https://camoufox.com) — Firefox-based browser with C++ anti-detection
|
|
141
|
-
- [
|
|
173
|
+
- [Donate to Camoufox's original creator daijro](https://camoufox.com/about/)
|
|
174
|
+
- [OpenClaw](https://openclaw.ai) — Open-source AI agent framework
|
|
175
|
+
|
|
176
|
+
## Crypto Scam Warning
|
|
177
|
+
|
|
178
|
+
Sketchy people are doing sketchy things with crypto tokens named "Camofox" now that this project is getting attention. **Camofox is not a crypto project and will never be one.** Any token, coin, or NFT using the Camofox name has nothing to do with us.
|
|
142
179
|
|
|
143
180
|
## License
|
|
144
181
|
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askjo/camofox-browser",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.14",
|
|
4
4
|
"description": "Headless browser automation server and OpenClaw plugin for AI agents - anti-detection, element refs, and session isolation",
|
|
5
|
-
"main": "server
|
|
5
|
+
"main": "server.js",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "Jo Inc <oss@askjo.ai>",
|
|
8
8
|
"homepage": "https://github.com/jo-inc/camofox-browser#readme",
|
|
@@ -32,21 +32,22 @@
|
|
|
32
32
|
"node": ">=18"
|
|
33
33
|
},
|
|
34
34
|
"files": [
|
|
35
|
-
"server
|
|
35
|
+
"server.js",
|
|
36
36
|
"lib/",
|
|
37
37
|
"plugin.ts",
|
|
38
38
|
"openclaw.plugin.json",
|
|
39
|
-
"run
|
|
40
|
-
"Dockerfile
|
|
39
|
+
"run.sh",
|
|
40
|
+
"Dockerfile",
|
|
41
41
|
"README.md",
|
|
42
42
|
"LICENSE"
|
|
43
43
|
],
|
|
44
44
|
"openclaw": {
|
|
45
|
-
"extensions": [
|
|
45
|
+
"extensions": [
|
|
46
|
+
"plugin.ts"
|
|
47
|
+
]
|
|
46
48
|
},
|
|
47
49
|
"scripts": {
|
|
48
|
-
"start": "node server
|
|
49
|
-
"start:chrome": "node server.js",
|
|
50
|
+
"start": "node server.js",
|
|
50
51
|
"test": "jest --runInBand --forceExit",
|
|
51
52
|
"test:e2e": "jest --runInBand --forceExit tests/e2e",
|
|
52
53
|
"test:live": "RUN_LIVE_TESTS=1 jest --runInBand --forceExit tests/live",
|
|
@@ -55,7 +56,6 @@
|
|
|
55
56
|
},
|
|
56
57
|
"dependencies": {
|
|
57
58
|
"camoufox-js": "^0.8.5",
|
|
58
|
-
"dotenv": "^17.2.3",
|
|
59
59
|
"express": "^4.18.2",
|
|
60
60
|
"playwright": "^1.50.0",
|
|
61
61
|
"playwright-core": "^1.58.0",
|
package/plugin.ts
CHANGED
|
@@ -105,10 +105,15 @@ async function startServer(
|
|
|
105
105
|
port: number,
|
|
106
106
|
log: PluginApi["log"]
|
|
107
107
|
): Promise<ChildProcess> {
|
|
108
|
-
const serverPath = join(pluginDir, "server
|
|
108
|
+
const serverPath = join(pluginDir, "server.js");
|
|
109
109
|
const proc = spawn("node", [serverPath], {
|
|
110
110
|
cwd: pluginDir,
|
|
111
|
-
env: {
|
|
111
|
+
env: {
|
|
112
|
+
PATH: process.env.PATH,
|
|
113
|
+
HOME: process.env.HOME,
|
|
114
|
+
NODE_ENV: process.env.NODE_ENV,
|
|
115
|
+
CAMOFOX_PORT: String(port),
|
|
116
|
+
},
|
|
112
117
|
stdio: ["ignore", "pipe", "pipe"],
|
|
113
118
|
detached: false,
|
|
114
119
|
});
|
|
@@ -377,8 +382,23 @@ export default function register(api: PluginApi) {
|
|
|
377
382
|
async execute(_id, params) {
|
|
378
383
|
const { tabId } = params as { tabId: string };
|
|
379
384
|
const userId = ctx.agentId || "openclaw";
|
|
380
|
-
const
|
|
381
|
-
|
|
385
|
+
const url = `${baseUrl}/tabs/${tabId}/screenshot?userId=${userId}`;
|
|
386
|
+
const res = await fetch(url);
|
|
387
|
+
if (!res.ok) {
|
|
388
|
+
const text = await res.text();
|
|
389
|
+
throw new Error(`${res.status}: ${text}`);
|
|
390
|
+
}
|
|
391
|
+
const arrayBuffer = await res.arrayBuffer();
|
|
392
|
+
const base64 = Buffer.from(arrayBuffer).toString("base64");
|
|
393
|
+
return {
|
|
394
|
+
content: [
|
|
395
|
+
{
|
|
396
|
+
type: "image",
|
|
397
|
+
data: base64,
|
|
398
|
+
mimeType: "image/png",
|
|
399
|
+
},
|
|
400
|
+
],
|
|
401
|
+
};
|
|
382
402
|
},
|
|
383
403
|
}));
|
|
384
404
|
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
# Local development script for
|
|
3
|
-
# Usage: ./run
|
|
4
|
-
# Example: ./run
|
|
2
|
+
# Local development script for camofox-browser
|
|
3
|
+
# Usage: ./run.sh [-p port]
|
|
4
|
+
# Example: ./run.sh -p 3001
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
CAMOFOX_PORT=3000
|
|
7
7
|
while getopts "p:" opt; do
|
|
8
8
|
case $opt in
|
|
9
|
-
p)
|
|
9
|
+
p) CAMOFOX_PORT="$OPTARG" ;;
|
|
10
10
|
*) echo "Usage: $0 [-p port]"; exit 1 ;;
|
|
11
11
|
esac
|
|
12
12
|
done
|
|
13
|
-
export
|
|
13
|
+
export CAMOFOX_PORT
|
|
14
14
|
|
|
15
15
|
# Install deps if needed
|
|
16
16
|
if [ ! -d "node_modules" ]; then
|
|
@@ -30,8 +30,8 @@ if ! command -v nodemon &> /dev/null; then
|
|
|
30
30
|
npm install -g nodemon
|
|
31
31
|
fi
|
|
32
32
|
|
|
33
|
-
echo "Starting
|
|
34
|
-
echo "Logs: /tmp/
|
|
35
|
-
nodemon --watch server
|
|
33
|
+
echo "Starting camofox-browser on http://localhost:$CAMOFOX_PORT (with auto-reload)"
|
|
34
|
+
echo "Logs: /tmp/camofox-browser.log"
|
|
35
|
+
nodemon --watch server.js --exec "node server.js" 2>&1 | while IFS= read -r line; do
|
|
36
36
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $line"
|
|
37
|
-
done | tee -a /tmp/
|
|
37
|
+
done | tee -a /tmp/camofox-browser.log
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
require('dotenv').config();
|
|
2
1
|
const { Camoufox, launchOptions } = require('camoufox-js');
|
|
3
2
|
const { firefox } = require('playwright-core');
|
|
4
3
|
const express = require('express');
|
|
@@ -1269,20 +1268,44 @@ app.post('/act', async (req, res) => {
|
|
|
1269
1268
|
});
|
|
1270
1269
|
|
|
1271
1270
|
// Graceful shutdown
|
|
1272
|
-
|
|
1273
|
-
|
|
1271
|
+
let shuttingDown = false;
|
|
1272
|
+
|
|
1273
|
+
async function gracefulShutdown(signal) {
|
|
1274
|
+
if (shuttingDown) return;
|
|
1275
|
+
shuttingDown = true;
|
|
1276
|
+
console.log(`${signal} received, shutting down...`);
|
|
1277
|
+
|
|
1278
|
+
const forceTimeout = setTimeout(() => {
|
|
1279
|
+
console.error('Shutdown timed out after 10s, forcing exit');
|
|
1280
|
+
process.exit(1);
|
|
1281
|
+
}, 10000);
|
|
1282
|
+
forceTimeout.unref();
|
|
1283
|
+
|
|
1284
|
+
server.close();
|
|
1285
|
+
|
|
1274
1286
|
for (const [userId, session] of sessions) {
|
|
1275
1287
|
await session.context.close().catch(() => {});
|
|
1276
1288
|
}
|
|
1277
1289
|
if (browser) await browser.close().catch(() => {});
|
|
1278
1290
|
process.exit(0);
|
|
1279
|
-
}
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
|
|
1294
|
+
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
|
|
1280
1295
|
|
|
1281
|
-
const PORT = process.env.
|
|
1282
|
-
app.listen(PORT,
|
|
1283
|
-
console.log(
|
|
1284
|
-
|
|
1285
|
-
await ensureBrowser().catch(err => {
|
|
1296
|
+
const PORT = process.env.CAMOFOX_PORT || 9377;
|
|
1297
|
+
const server = app.listen(PORT, () => {
|
|
1298
|
+
console.log(`camofox-browser listening on port ${PORT}`);
|
|
1299
|
+
ensureBrowser().catch(err => {
|
|
1286
1300
|
console.error('Failed to pre-launch browser:', err.message);
|
|
1287
1301
|
});
|
|
1288
1302
|
});
|
|
1303
|
+
|
|
1304
|
+
server.on('error', (err) => {
|
|
1305
|
+
if (err.code === 'EADDRINUSE') {
|
|
1306
|
+
console.error(`FATAL: Port ${PORT} is already in use. Set CAMOFOX_PORT env var to use a different port.`);
|
|
1307
|
+
process.exit(1);
|
|
1308
|
+
}
|
|
1309
|
+
console.error('Server error:', err);
|
|
1310
|
+
process.exit(1);
|
|
1311
|
+
});
|