@askjo/camofox-browser 1.0.12 → 1.0.13
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 +69 -40
- package/openclaw.plugin.json +1 -1
- package/package.json +9 -8
- package/plugin.ts +2 -2
- package/{run-camoufox.sh → run.sh} +10 -10
- package/{server-camoufox.js → server.js} +32 -8
|
@@ -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,8 +9,8 @@
|
|
|
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
|
-
The same engine behind <a href="https://askjo.ai">askjo.ai</a>'s web
|
|
12
|
+
Built on <a href="https://camoufox.com">Camoufox</a> — a Firefox fork with fingerprint spoofing at the C++ level.<br/>
|
|
13
|
+
The same engine behind <a href="https://askjo.ai">askjo.ai</a>'s web browsing.
|
|
14
14
|
</p>
|
|
15
15
|
</div>
|
|
16
16
|
|
|
@@ -28,10 +28,10 @@ This project wraps that engine in a REST API built for agents: accessibility sna
|
|
|
28
28
|
|
|
29
29
|
- **C++ Anti-Detection** — bypasses Google, Cloudflare, and most bot detection
|
|
30
30
|
- **Element Refs** — stable `e1`, `e2`, `e3` identifiers for reliable interaction
|
|
31
|
-
- **Token-Efficient** — accessibility snapshots are 90% smaller than raw HTML
|
|
31
|
+
- **Token-Efficient** — accessibility snapshots are ~90% smaller than raw HTML
|
|
32
32
|
- **Session Isolation** — separate cookies/storage per user
|
|
33
33
|
- **Search Macros** — `@google_search`, `@youtube_search`, `@amazon_search`, and 10 more
|
|
34
|
-
- **
|
|
34
|
+
- **Deploy Anywhere** — Docker, Fly.io, Railway
|
|
35
35
|
|
|
36
36
|
## Quick Start
|
|
37
37
|
|
|
@@ -41,16 +41,6 @@ This project wraps that engine in a REST API built for agents: accessibility sna
|
|
|
41
41
|
openclaw plugins install @askjo/camofox-browser
|
|
42
42
|
```
|
|
43
43
|
|
|
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
44
|
**Tools:** `camofox_create_tab` · `camofox_snapshot` · `camofox_click` · `camofox_type` · `camofox_navigate` · `camofox_scroll` · `camofox_screenshot` · `camofox_close_tab` · `camofox_list_tabs`
|
|
55
45
|
|
|
56
46
|
### Standalone
|
|
@@ -62,52 +52,84 @@ npm install
|
|
|
62
52
|
npm start # downloads Camoufox on first run (~300MB)
|
|
63
53
|
```
|
|
64
54
|
|
|
55
|
+
Default port is `9377`. Set `CAMOFOX_PORT` to override.
|
|
56
|
+
|
|
65
57
|
### Docker
|
|
66
58
|
|
|
67
59
|
```bash
|
|
68
|
-
docker build -
|
|
69
|
-
docker run -p
|
|
60
|
+
docker build -t camofox-browser .
|
|
61
|
+
docker run -p 9377:9377 camofox-browser
|
|
70
62
|
```
|
|
71
63
|
|
|
64
|
+
### Fly.io / Railway
|
|
65
|
+
|
|
66
|
+
`fly.toml` and `railway.toml` are included. Deploy with `fly deploy` or connect the repo to Railway.
|
|
67
|
+
|
|
72
68
|
## Usage
|
|
73
69
|
|
|
74
70
|
```bash
|
|
75
71
|
# Create a tab
|
|
76
|
-
curl -X POST http://localhost:
|
|
72
|
+
curl -X POST http://localhost:9377/tabs \
|
|
73
|
+
-H 'Content-Type: application/json' \
|
|
77
74
|
-d '{"userId": "agent1", "sessionKey": "task1", "url": "https://example.com"}'
|
|
78
75
|
|
|
79
|
-
# Get
|
|
80
|
-
curl "http://localhost:
|
|
76
|
+
# Get accessibility snapshot with element refs
|
|
77
|
+
curl "http://localhost:9377/tabs/TAB_ID/snapshot?userId=agent1"
|
|
81
78
|
# → { "snapshot": "[button e1] Submit [link e2] Learn more", ... }
|
|
82
79
|
|
|
83
80
|
# Click by ref
|
|
84
|
-
curl -X POST http://localhost:
|
|
81
|
+
curl -X POST http://localhost:9377/tabs/TAB_ID/click \
|
|
82
|
+
-H 'Content-Type: application/json' \
|
|
85
83
|
-d '{"userId": "agent1", "ref": "e1"}'
|
|
86
84
|
|
|
87
|
-
#
|
|
88
|
-
curl -X POST http://localhost:
|
|
85
|
+
# Type into an element
|
|
86
|
+
curl -X POST http://localhost:9377/tabs/TAB_ID/type \
|
|
87
|
+
-H 'Content-Type: application/json' \
|
|
88
|
+
-d '{"userId": "agent1", "ref": "e2", "text": "hello", "pressEnter": true}'
|
|
89
|
+
|
|
90
|
+
# Navigate with a search macro
|
|
91
|
+
curl -X POST http://localhost:9377/tabs/TAB_ID/navigate \
|
|
92
|
+
-H 'Content-Type: application/json' \
|
|
89
93
|
-d '{"userId": "agent1", "macro": "@google_search", "query": "best coffee beans"}'
|
|
90
94
|
```
|
|
91
95
|
|
|
92
96
|
## API
|
|
93
97
|
|
|
98
|
+
### Tab Lifecycle
|
|
99
|
+
|
|
94
100
|
| Method | Endpoint | Description |
|
|
95
101
|
|--------|----------|-------------|
|
|
96
|
-
| `POST` | `/tabs` | Create tab |
|
|
97
|
-
| `GET` | `/tabs
|
|
98
|
-
| `
|
|
99
|
-
| `
|
|
100
|
-
| `
|
|
101
|
-
| `
|
|
102
|
-
|
|
103
|
-
|
|
102
|
+
| `POST` | `/tabs` | Create tab with initial URL |
|
|
103
|
+
| `GET` | `/tabs?userId=X` | List open tabs |
|
|
104
|
+
| `GET` | `/tabs/:id/stats` | Tab stats (tool calls, visited URLs) |
|
|
105
|
+
| `DELETE` | `/tabs/:id` | Close tab |
|
|
106
|
+
| `DELETE` | `/tabs/group/:groupId` | Close all tabs in a group |
|
|
107
|
+
| `DELETE` | `/sessions/:userId` | Close all tabs for a user |
|
|
108
|
+
|
|
109
|
+
### Page Interaction
|
|
110
|
+
|
|
111
|
+
| Method | Endpoint | Description |
|
|
112
|
+
|--------|----------|-------------|
|
|
113
|
+
| `GET` | `/tabs/:id/snapshot` | Accessibility snapshot with element refs |
|
|
114
|
+
| `POST` | `/tabs/:id/click` | Click element by ref or CSS selector |
|
|
115
|
+
| `POST` | `/tabs/:id/type` | Type text into element |
|
|
116
|
+
| `POST` | `/tabs/:id/press` | Press a keyboard key |
|
|
117
|
+
| `POST` | `/tabs/:id/scroll` | Scroll page (up/down/left/right) |
|
|
118
|
+
| `POST` | `/tabs/:id/navigate` | Navigate to URL or search macro |
|
|
119
|
+
| `POST` | `/tabs/:id/wait` | Wait for selector or timeout |
|
|
120
|
+
| `GET` | `/tabs/:id/links` | Extract all links on page |
|
|
121
|
+
| `GET` | `/tabs/:id/screenshot` | Take screenshot |
|
|
104
122
|
| `POST` | `/tabs/:id/back` | Go back |
|
|
105
123
|
| `POST` | `/tabs/:id/forward` | Go forward |
|
|
106
|
-
| `POST` | `/tabs/:id/refresh` | Refresh |
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
124
|
+
| `POST` | `/tabs/:id/refresh` | Refresh page |
|
|
125
|
+
|
|
126
|
+
### Server
|
|
127
|
+
|
|
128
|
+
| Method | Endpoint | Description |
|
|
129
|
+
|--------|----------|-------------|
|
|
110
130
|
| `GET` | `/health` | Health check |
|
|
131
|
+
| `POST` | `/start` | Start browser engine |
|
|
132
|
+
| `POST` | `/stop` | Stop browser engine |
|
|
111
133
|
|
|
112
134
|
## Search Macros
|
|
113
135
|
|
|
@@ -116,7 +138,7 @@ curl -X POST http://localhost:3000/tabs/TAB_ID/navigate \
|
|
|
116
138
|
## Architecture
|
|
117
139
|
|
|
118
140
|
```
|
|
119
|
-
Browser Instance
|
|
141
|
+
Browser Instance (Camoufox)
|
|
120
142
|
└── User Session (BrowserContext) — isolated cookies/storage
|
|
121
143
|
├── Tab Group (sessionKey: "conv1")
|
|
122
144
|
│ ├── Tab (google.com)
|
|
@@ -130,15 +152,22 @@ Sessions auto-expire after 30 minutes of inactivity.
|
|
|
130
152
|
## Testing
|
|
131
153
|
|
|
132
154
|
```bash
|
|
133
|
-
npm test #
|
|
134
|
-
npm run test:
|
|
155
|
+
npm test # all tests
|
|
156
|
+
npm run test:e2e # e2e tests only
|
|
157
|
+
npm run test:live # live site tests (Google, macros)
|
|
135
158
|
npm run test:debug # with server output
|
|
136
159
|
```
|
|
137
160
|
|
|
161
|
+
## npm
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
npm install @askjo/camofox-browser
|
|
165
|
+
```
|
|
166
|
+
|
|
138
167
|
## Credits
|
|
139
168
|
|
|
140
169
|
- [Camoufox](https://camoufox.com) — Firefox-based browser with C++ anti-detection
|
|
141
|
-
- [
|
|
170
|
+
- [OpenClaw](https://openclaw.ai) — Open-source AI agent framework
|
|
142
171
|
|
|
143
172
|
## License
|
|
144
173
|
|
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.13",
|
|
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",
|
package/plugin.ts
CHANGED
|
@@ -105,10 +105,10 @@ 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: { ...process.env,
|
|
111
|
+
env: { ...process.env, CAMOFOX_PORT: String(port) },
|
|
112
112
|
stdio: ["ignore", "pipe", "pipe"],
|
|
113
113
|
detached: false,
|
|
114
114
|
});
|
|
@@ -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
|
|
@@ -1269,20 +1269,44 @@ app.post('/act', async (req, res) => {
|
|
|
1269
1269
|
});
|
|
1270
1270
|
|
|
1271
1271
|
// Graceful shutdown
|
|
1272
|
-
|
|
1273
|
-
|
|
1272
|
+
let shuttingDown = false;
|
|
1273
|
+
|
|
1274
|
+
async function gracefulShutdown(signal) {
|
|
1275
|
+
if (shuttingDown) return;
|
|
1276
|
+
shuttingDown = true;
|
|
1277
|
+
console.log(`${signal} received, shutting down...`);
|
|
1278
|
+
|
|
1279
|
+
const forceTimeout = setTimeout(() => {
|
|
1280
|
+
console.error('Shutdown timed out after 10s, forcing exit');
|
|
1281
|
+
process.exit(1);
|
|
1282
|
+
}, 10000);
|
|
1283
|
+
forceTimeout.unref();
|
|
1284
|
+
|
|
1285
|
+
server.close();
|
|
1286
|
+
|
|
1274
1287
|
for (const [userId, session] of sessions) {
|
|
1275
1288
|
await session.context.close().catch(() => {});
|
|
1276
1289
|
}
|
|
1277
1290
|
if (browser) await browser.close().catch(() => {});
|
|
1278
1291
|
process.exit(0);
|
|
1279
|
-
}
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
|
|
1295
|
+
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
|
|
1280
1296
|
|
|
1281
|
-
const PORT = process.env.
|
|
1282
|
-
app.listen(PORT,
|
|
1283
|
-
console.log(
|
|
1284
|
-
|
|
1285
|
-
await ensureBrowser().catch(err => {
|
|
1297
|
+
const PORT = process.env.CAMOFOX_PORT || 9377;
|
|
1298
|
+
const server = app.listen(PORT, () => {
|
|
1299
|
+
console.log(`camofox-browser listening on port ${PORT}`);
|
|
1300
|
+
ensureBrowser().catch(err => {
|
|
1286
1301
|
console.error('Failed to pre-launch browser:', err.message);
|
|
1287
1302
|
});
|
|
1288
1303
|
});
|
|
1304
|
+
|
|
1305
|
+
server.on('error', (err) => {
|
|
1306
|
+
if (err.code === 'EADDRINUSE') {
|
|
1307
|
+
console.error(`FATAL: Port ${PORT} is already in use. Set CAMOFOX_PORT env var to use a different port.`);
|
|
1308
|
+
process.exit(1);
|
|
1309
|
+
}
|
|
1310
|
+
console.error('Server error:', err);
|
|
1311
|
+
process.exit(1);
|
|
1312
|
+
});
|