@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.
@@ -49,11 +49,12 @@ WORKDIR /app
49
49
  COPY package.json ./
50
50
  RUN npm install --production
51
51
 
52
- COPY server-camoufox.js ./
52
+ COPY server.js ./
53
+ COPY lib/ ./lib/
53
54
 
54
55
  ENV NODE_ENV=production
55
- ENV PORT=3000
56
+ ENV CAMOFOX_PORT=3000
56
57
 
57
58
  EXPOSE 3000
58
59
 
59
- CMD ["node", "server-camoufox.js"]
60
+ CMD ["node", "server.js"]
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  <div align="center">
2
- <img src="camofox.png" alt="camofox-browser" width="200" />
2
+ <img src="fox.png" alt="camofox-browser" width="200" />
3
3
  <h1>camofox-browser</h1>
4
- <p><strong>Headless browser server for AI agents with C++ anti-detection</strong></p>
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
- Powered by <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 scraping.
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
- - **Docker Ready** — production Dockerfile with pre-baked Camoufox binary
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 -f Dockerfile.camoufox -t camofox-browser .
69
- docker run -p 3000:3000 camofox-browser
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:3000/tabs \
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 page snapshot with element refs
80
- curl "http://localhost:3000/tabs/TAB_ID/snapshot?userId=agent1"
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:3000/tabs/TAB_ID/click \
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
- # Search with macros
88
- curl -X POST http://localhost:3000/tabs/TAB_ID/navigate \
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
- | `POST` | `/tabs` | Create tab |
97
- | `GET` | `/tabs/:id/snapshot` | Accessibility snapshot with refs |
98
- | `POST` | `/tabs/:id/click` | Click element by ref |
99
- | `POST` | `/tabs/:id/type` | Type into element |
100
- | `POST` | `/tabs/:id/navigate` | Navigate to URL or macro |
101
- | `POST` | `/tabs/:id/scroll` | Scroll page |
102
- | `GET` | `/tabs/:id/screenshot` | Screenshot |
103
- | `GET` | `/tabs/:id/links` | All links on page |
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
- | `DELETE` | `/tabs/:id` | Close tab |
108
- | `GET` | `/tabs?userId=X` | List tabs |
109
- | `DELETE` | `/sessions/:userId` | Close all user tabs |
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 # e2e tests
134
- npm run test:live # live Google tests
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
- - [Amp](https://ampcode.com) — AI coding agent
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
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "camofox-browser",
3
- "name": "Camoufox Browser",
3
+ "name": "Camofox Browser",
4
4
  "description": "Anti-detection browser automation for AI agents using Camoufox (Firefox-based)",
5
5
  "version": "1.0.11",
6
6
  "configSchema": {
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@askjo/camofox-browser",
3
- "version": "1.0.12",
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-camoufox.js",
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-camoufox.js",
35
+ "server.js",
36
36
  "lib/",
37
37
  "plugin.ts",
38
38
  "openclaw.plugin.json",
39
- "run-camoufox.sh",
40
- "Dockerfile.camoufox",
39
+ "run.sh",
40
+ "Dockerfile",
41
41
  "README.md",
42
42
  "LICENSE"
43
43
  ],
44
44
  "openclaw": {
45
- "extensions": ["plugin.ts"]
45
+ "extensions": [
46
+ "plugin.ts"
47
+ ]
46
48
  },
47
49
  "scripts": {
48
- "start": "node server-camoufox.js",
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-camoufox.js");
108
+ const serverPath = join(pluginDir, "server.js");
109
109
  const proc = spawn("node", [serverPath], {
110
110
  cwd: pluginDir,
111
- env: { ...process.env, PORT: String(port) },
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 result = await fetchApi(baseUrl, `/tabs/${tabId}/screenshot?userId=${userId}`);
381
- return toToolResult(result);
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 camoufox-browser
3
- # Usage: ./run-camoufox.sh [-p port]
4
- # Example: ./run-camoufox.sh -p 3001
2
+ # Local development script for camofox-browser
3
+ # Usage: ./run.sh [-p port]
4
+ # Example: ./run.sh -p 3001
5
5
 
6
- PORT=3000
6
+ CAMOFOX_PORT=3000
7
7
  while getopts "p:" opt; do
8
8
  case $opt in
9
- p) PORT="$OPTARG" ;;
9
+ p) CAMOFOX_PORT="$OPTARG" ;;
10
10
  *) echo "Usage: $0 [-p port]"; exit 1 ;;
11
11
  esac
12
12
  done
13
- export PORT
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 camoufox-browser on http://localhost:$PORT (with auto-reload)"
34
- echo "Logs: /tmp/camoufox-browser.log"
35
- nodemon --watch server-camoufox.js --exec "node server-camoufox.js" 2>&1 | while IFS= read -r line; do
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/camoufox-browser.log
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
- process.on('SIGTERM', async () => {
1273
- console.log('Shutting down...');
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.PORT || 9377;
1282
- app.listen(PORT, async () => {
1283
- console.log(`🦊 camoufox-browser listening on port ${PORT}`);
1284
- // Pre-launch browser so it's ready for first request
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
+ });