@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.
@@ -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,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
- 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
+ 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
- - **Docker Ready** — production Dockerfile with pre-baked Camoufox binary
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 -f Dockerfile.camoufox -t camofox-browser .
69
- docker run -p 3000:3000 camofox-browser
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:3000/tabs \
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 page snapshot with element refs
80
- curl "http://localhost:3000/tabs/TAB_ID/snapshot?userId=agent1"
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:3000/tabs/TAB_ID/click \
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
- # Search with macros
88
- curl -X POST http://localhost:3000/tabs/TAB_ID/navigate \
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/: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 |
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
- | `DELETE` | `/tabs/:id` | Close tab |
108
- | `GET` | `/tabs?userId=X` | List tabs |
109
- | `DELETE` | `/sessions/:userId` | Close all user tabs |
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 # e2e tests
134
- npm run test:live # live Google tests
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
- - [Amp](https://ampcode.com) — AI coding agent
170
+ - [OpenClaw](https://openclaw.ai) — Open-source AI agent framework
142
171
 
143
172
  ## License
144
173
 
@@ -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.13",
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",
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-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: { ...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 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
@@ -1269,20 +1269,44 @@ app.post('/act', async (req, res) => {
1269
1269
  });
1270
1270
 
1271
1271
  // Graceful shutdown
1272
- process.on('SIGTERM', async () => {
1273
- console.log('Shutting down...');
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.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 => {
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
+ });