camofox-browser
AGENTS.md12k starsChanged 5 days agoMITView on GitHub
Headless browser automation server for AI agents. Run locally or deploy to any cloud provider. Returns: {"tabId": "abc123", "url": "...", "title": "..."} Returns accessibility tree with refs: Use these instead of constructing URLs: Refs like e1, e2 are stable identifiers for page elements: Firefox-based with anti-detection. Bypasses Google captcha.
- Reads credentials
- Installs packages
Trending
More talk lately.about the whole repository
Who's talking about jo-inc/camofox-browser
- Venice“How to Build a Twitter Digest Agent with OpenClaw (GLM 5)”YouTube · Feb 17, 2026 · 5.9k views
- Repocast | GitHub 일일 브리핑“차단 걱정 없는 AI 에이전트 웹 브라우징? camofox-browser!”YouTube · Apr 15, 2026 · 1.8k views
- SkipCI“Camofox: The Stealth Browser That Makes Your AI Agent Invisible to Cloudflare”YouTube · Sep 8, 2026 · 624 views
- The Contextian“Camofox: The AI Browser That Bypasses Bot Detection”YouTube · Jun 21, 2026 · 566 views
- DevPik“This Open Source Browser Beats Cloudflare By Faking Everything”YouTube · Sep 8, 2026 · 400 views
The biggest 5 of 78 mentions linking to the repository that our crawl found. Each line is their own headline.
See all 78
Talked about on GitHub, YouTube, Bluesky, Hacker News and the web (78 times).
On GitHub · 50
- Apr 14, 2026Support Camofox/Camoufox Anti-Detection Browser Backend as alternative to Chrome — karakeep-app/karakeep8 reactions
- Apr 29, 2026docs(plugins): add Camofox Browser community plugin — openclaw/openclaw1 reaction · 2 comments
- Nov 8, 2026build(deps): bump the npx-runtime-minor-patch group across 1 directory with 3 updates — kingsa2026/Neurova0 reactions · 1 comment
- Nov 3, 2026[Transcript] Implement Camofox TranscriptProvider via /youtube/transcript — javiyt/scrapping-service0 reactions · 1 comment
- Nov 1, 2026build(deps): bump the npx-runtime-minor-patch group across 1 directory with 2 updates — kingsa2026/Neurova0 reactions · 2 comments
45 more on GitHub
- Oct 11, 2026build(deps): bump the npx-runtime-minor-patch group across 1 directory with 3 updates — kingsa2026/Neurova0 reactions · 2 comments
- Oct 7, 2026[P10][Camofox] Interactive login via human takeover — bloodf/beans0 reactions · 2 comments
- Oct 7, 2026[P10][Camofox] Authorized file transfer for guest browsing — bloodf/beans0 reactions · 2 comments
- Oct 7, 2026[P10][Camofox] Platform and binary provenance policy — bloodf/beans0 reactions · 2 comments
- Oct 7, 2026[P10][Camofox] Guest-hosted Camofox with readiness integration — bloodf/beans0 reactions · 2 comments
- Oct 7, 2026[P10][Camofox] Telemetry off and tool permissions — bloodf/beans0 reactions · 2 comments
- Oct 7, 2026[P10][Camofox] Per-bean session isolation — bloodf/beans0 reactions · 2 comments
- Oct 7, 2026[P10][Camofox] Optional Camofox provisioning and MCP registration on a Runner — bloodf/beans0 reactions · 2 comments
- Oct 7, 2026[P10][Camofox] Resolve revision pin, binary provenance, bind/auth and license questions — bloodf/beans0 reactions · 5 comments
- Sep 29, 2026build(deps): bump the npx-runtime-minor-patch group across 1 directory with 2 updates — kingsa2026/Neurova0 reactions · 2 comments
- Sep 19, 2026build(deps): bump adm-zip and @askjo/camofox-browser — thiswind/johor0 reactions · 1 comment
- Sep 19, 2026build(deps): bump adm-zip and @askjo/camofox-browser — badMade/hermes-agent0 reactions · 2 comments
- Sep 19, 2026chore(deps): bump adm-zip and @askjo/camofox-browser — sk-surya/tiny-agents0 reactions
- Sep 19, 2026chore(deps): bump adm-zip and @askjo/camofox-browser — nikitistik/hermes-agent0 reactions
- Sep 19, 2026chore(deps): bump adm-zip and @askjo/camofox-browser — dimzyg110/hermes-agent0 reactions · 1 comment
- Sep 14, 2026[Feature] Use CamoFox to evade WAFs like Cloudflare — samugit83/redamon0 reactions
- Sep 9, 2026Check trending repo: jo-inc/camofox-browser — uibm/trending-watcher0 reactions · 2 comments
- Sep 8, 2026Check trending repo: jo-inc/camofox-browser — uibm/trending-watcher0 reactions · 2 comments
- Sep 8, 2026🔥 GitHub Trending AI 早报 · 2026-09-08 · Top 7 项目 — happydog-intj/ai-xiaohongshu-daily0 reactions
- Sep 8, 2026🔥 GitHub Trending AI 早报 · 2026-09-08 · Top 7 项目 — Mystery406/ai-xiaohongshu-daily0 reactions
- Sep 8, 2026Github trending [jo-inc/camofox-browser] 反检测浏览器AI代理利器 — web1992/github-trending0 reactions
- Sep 7, 2026🔥 GitHub Trending AI 早报 · 2026-09-08 · Top 7 项目 — Mystery406/ai-xiaohongshu-daily0 reactions
- Sep 7, 2026🔥 GitHub Trending AI 早报 · 2026-09-08 · Top 7 项目 — happydog-intj/ai-xiaohongshu-daily0 reactions
- Sep 3, 2026build(deps): bump qs and @askjo/camofox-browser — thiswind/johor0 reactions · 1 comment
- Aug 28, 2026docs(browser): add npm setup path, window/headless toggle, and clean-stop persistence — NousResearch/hermes-agent0 reactions · 1 comment
- Aug 28, 2026docs(browser): Camofox section is Docker-only - npm path, window/headless toggle and clean-stop persistence rule undocumented — NousResearch/hermes-agent0 reactions · 2 comments
- Aug 6, 2026enhancement: Stealth browser engine (undetectable Playwright) — Predator04/Hermes-CoAgent0 reactions · 1 comment
- Aug 1, 2026chore(deps): bump brace-expansion and @askjo/camofox-browser — sk-surya/tiny-agents0 reactions
- Aug 1, 2026chore(deps): bump brace-expansion, agent-browser and @askjo/camofox-browser — FOUNDUPS/hermes-agent0 reactions
- Aug 1, 2026chore(deps): bump brace-expansion and @askjo/camofox-browser — rushindrasinha/hermes-agent0 reactions
- Aug 1, 2026chore(deps): bump brace-expansion and @askjo/camofox-browser — cirwel/hermes-agent0 reactions · 3 comments
- Aug 1, 2026chore(deps): bump brace-expansion and @askjo/camofox-browser — cpufreestyle/hermes-agent0 reactions
- Aug 1, 2026chore(deps): bump brace-expansion and @askjo/camofox-browser — Maggot4703/hermes-agent0 reactions
- Aug 1, 2026Bump brace-expansion and @askjo/camofox-browser — tanyewhong-creator/addin0 reactions · 1 comment
- Aug 1, 2026chore(deps): bump brace-expansion and @askjo/camofox-browser — HybridAIWorkforce/hermes-agent0 reactions
- Jul 31, 2026chore(deps): bump brace-expansion and @askjo/camofox-browser — scerbinaalexandr-droid/hermes-agent0 reactions
- Jul 30, 2026VPN compose pins a camofox image that does not exist (ghcr.io/nimblecoai/camofox:latest) — never published, not an org-transfer break — cyborg-garden/swarm-map0 reactions
- Jul 21, 2026chore(deps): bump brace-expansion and @askjo/camofox-browser — Maggot4703/hermes-agent0 reactions · 1 comment
- Jul 21, 2026Bump brace-expansion and @askjo/camofox-browser — tanyewhong-creator/addin0 reactions · 2 comments
- Jul 21, 2026chore(deps): bump brace-expansion and @askjo/camofox-browser — rushindrasinha/hermes-agent0 reactions · 1 comment
- Jul 21, 2026chore(deps): bump brace-expansion and @askjo/camofox-browser — HybridAIWorkforce/hermes-agent0 reactions · 1 comment
- Jul 21, 2026chore(deps): bump brace-expansion, agent-browser and @askjo/camofox-browser — FOUNDUPS/hermes-agent0 reactions · 1 comment
- Jun 23, 2026feat: experimental backends - Crawl4AI, Obscura, and Camoufox loaders — ScrapeGraphAI/Scrapegraph-ai0 reactions
- Jun 18, 2026feat: add CamofoxMiddleware for anti-detection crawling via Camofox` — watercrawl/WaterCrawl0 reactions · 3 comments
- May 24, 2026receipt: add camofox false-success 429 artifact — SirBrenton/pitstop-truth0 reactions
On YouTube · 17
- Feb 17, 2026How to Build a Twitter Digest Agent with OpenClaw (GLM 5) — Venice5.9k views · 14 comments
- Apr 15, 2026차단 걱정 없는 AI 에이전트 웹 브라우징? camofox-browser! — Repocast | GitHub 일일 브리핑1.8k views · 3 comments
- Sep 8, 2026Camofox: The Stealth Browser That Makes Your AI Agent Invisible to Cloudflare — SkipCI624 views
- Jun 21, 2026Camofox: The AI Browser That Bypasses Bot Detection — The Contextian566 views · 1 comment
- Sep 8, 2026This Open Source Browser Beats Cloudflare By Faking Everything — DevPik400 views · 1 comment
12 more on YouTube
- Sep 11, 2026This AI Browser Is Built to Avoid Bot Detection 🤯 | Camofox Browser — TechWithRG27345 views · 1 comment
- Sep 8, 2026jo-inc/camofox-browser - Gource visualisation — Gourcer254 views
- Sep 8, 2026Camofox-browser: stealth headless browser for AI agents — Next Horizon197 views · 1 comment
- Sep 8, 2026How does Camofox-Browser hide from bots? 🦊 — CodeTimelapse180 views
- Oct 30, 2026Camofox Browser: A Free, Open-Source Stealth Browser Built for AI Agents — AutomateNow160 views
- Aug 16, 2026How to Build Automated Backlinks Using AI Agents (Camofox Browser Tutorial) — House OF SEO129 views
- Sep 8, 2026How 490 Commits Created an Unblockable Browser for AI — CodeTimelapse99 views
- Sep 8, 2026카모폭스: AI용 스텔스 브라우저 — Decoded AI87 views
- Sep 10, 2026jo-inc/camofox-browser — The stealth browser your agent needs to reach the real we… 🟢 Clone #Shorts — ForkCast86 views
- Sep 7, 2026Camofox Browser: navegador anti-detecção para agentes de IA — Madruga Sabia20 views
- Oct 10, 2026أداة camofox-browser: تجاوز حماية المواقع بالمتصفح الخفي #Shorts — Manea Alblooshi17 views · 1 comment
- Sep 19, 2026jo-inc/camofox-browser #Shorts — Repo Radar AI12 views · 1 comment
On Bluesky · 8
- Apr 14, 2026Camofox Browser : un web headless, fork de Firefox, conçu pour les agents IA. “Camoufox patches Firefox at the C++ implementation level...… — @bearstech.com13 likes and reposts · 2 replies
- Sep 11, 2026🚀 Skyrocketing! 🚀 (200+ new stars) 📦 jo-inc / camofox-browser ⭐ 10,880 (+451) 🗒 JavaScript Stealth headless browser for AI agents —… — @github-trending-js.bsky.social4 likes and reposts · 1 reply
- Sep 7, 2026🔥 Hot Repo! 🔥 (100+ new stars) 📦 jo-inc / camofox-browser ⭐ 9,310 (+117) 🗒 JavaScript Stealth headless browser for AI agents — bypass… — @github-trending.bsky.social4 likes and reposts
- May 2, 2026🚀 Skyrocketing! 🚀 (200+ new stars) 📦 jo-inc / camofox-browser ⭐ 3,836 (+201) 🗒 JavaScript Stealth headless browser for AI agents —… — @github-trending-js.bsky.social2 likes and reposts · 2 replies
- Apr 26, 2026🔥 Hot Repo! 🔥 (100+ new stars) 📦 jo-inc / camofox-browser ⭐ 3,162 (+155) 🗒 JavaScript Stealth headless browser for AI agents — bypass… — @github-trending-js.bsky.social2 likes and reposts · 1 reply
3 more on Bluesky
- Apr 11, 2026🚀 Skyrocketing! 🚀 (200+ new stars) 📦 jo-inc / camofox-browser ⭐ 1,689 (+352) 🗒 JavaScript Headless browser automation server for AI… — @github-trending-js.bsky.social2 likes and reposts · 2 replies
- Sep 8, 20265. UniMate Foundation Model Animates Arbitrary 3D Skeletons via Diffusion Transformer LINK 6. Camofox Stealth Headless Browser Bypasses… — @hackobar.bsky.social0 likes and reposts · 1 reply
- May 11, 2026🚀 Skyrocketing! 🚀 (200+ new stars) 📦 jo-inc / camofox-browser ⭐ 4,681 (+222) 🗒 JavaScript Stealth headless browser for AI agents —… — @github-trending-js.bsky.social0 likes and reposts · 1 reply
On Hacker News · 2
- Apr 9, 2026Camofox-browser: Anti-detection browser for AI agents, powered by Camoufox3 points
- Feb 11, 2026Anti-detection browser server for AI agents, powered by Camoufox2 points · 1 comment
On the web · 1
- Feb 11, 2026Camofox browser pour OpenClaw le navigateur anti ... — x.com
Where files are headingNew AGENTS.md files have more rules about keeping secrets out of the code, and fewer about code style.
Of the AGENTS.md files we read, the 31,439 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.
The file
Show its rules and text
We split this AGENTS.md into the rules it gives an agent
15 rules. About 0 are common, 2 are distinctive, and 13 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈2
Distinctive · estimate
This author's own choices. Worth a look.
13
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 89
Use these instead of constructing URLs:
- Very specific: line 106
Call /snapshot to get current refs
- Very specific: line 107
Use ref in /click or /type
- Very specific: line 115
Delete all user data: DELETE /sessions/:userId
- Very specific: line 177
Every route handler in server.js has a JSDoc comment block directly above it like:
- Very specific: line 225
Every operation must have tags, summary, and responses
- Very specific: line 231
Run npx jest tests/unit/openapi.test.js to verify coverage -- the test fails if any route is missing from the spec, if a stale route …
- Very specific: line 240
Add or update the tool in mcp/lib/tool-contracts.mjs (schema + buildRequest case).
- Very specific: line 241
Add the tool name to openclaw.plugin.json's contracts.tools.
- Very specific: line 242
Add contract cases in tests/unit/mcp-contracts.test.js and bump the tool count.
- Very specific: line 243
Update the tool list in scripts/test-mcp.mjs (smoke test) and README.md.
- Very specific: line 246
After changes, run: node scripts/test-mcp.mjs (stdio smoke test) and NODE_OPTIONS='--experimental-vm-modules' npx jest …
- Very specific: line 270
When adding features that need env vars or subprocesses, put that code in a lib/ module and import the result into server.js.
- Very specific: line 462
If the file is missing or has no plugins key, all plugins in plugins/ are loaded (backward-compatible).
- Distinctive: line 467
Use the plugin manager to install third-party plugins from git or local paths:
≈ “Common” is an estimate: at least 1 in 5 of the 40,246 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
camofox-browser Agent Guide
Headless browser automation server for AI agents. Run locally or deploy to any cloud provider.
Quick Start for Agents
# Install and start
npm install && npm start
# Server runs on http://localhost:9377Core Workflow
- Create a tab -> Get
tabId - Navigate -> Go to URL or use search macro
- Get snapshot -> Receive page content with element refs (
e1,e2, etc.) - Interact -> Click/type using refs
- Repeat steps 3-4 as needed
API Reference
Create Tab
POST /tabs
{"userId": "agent1", "sessionKey": "task1", "url": "https://example.com"}Returns: {"tabId": "abc123", "url": "...", "title": "..."}
Navigate
POST /tabs/:tabId/navigate
{"userId": "agent1", "url": "https://google.com"}
# Or use macro:
{"userId": "agent1", "macro": "@google_search", "query": "weather today"}Get Snapshot
GET /tabs/:tabId/snapshot?userId=agent1Returns accessibility tree with refs:
[heading] Example Domain
[paragraph] This domain is for use in examples.
[link e1] More information...Click Element
POST /tabs/:tabId/click
{"userId": "agent1", "ref": "e1"}
# Or CSS selector:
{"userId": "agent1", "selector": "button.submit"}Type Text
POST /tabs/:tabId/type
{"userId": "agent1", "ref": "e2", "text": "hello world"}
# Add enter: {"userId": "agent1", "ref": "e2", "text": "search query", "pressEnter": true}Scroll
POST /tabs/:tabId/scroll
{"userId": "agent1", "direction": "down", "amount": 500}Navigation
POST /tabs/:tabId/back {"userId": "agent1"}
POST /tabs/:tabId/forward {"userId": "agent1"}
POST /tabs/:tabId/refresh {"userId": "agent1"}Get Links
GET /tabs/:tabId/links?userId=agent1&limit=50Close Tab
DELETE /tabs/:tabId?userId=agent1Search Macros
Use these instead of constructing URLs:
| Macro | Site |
@google_search | |
@youtube_search | YouTube |
@amazon_search | Amazon |
@reddit_search | |
@wikipedia_search | Wikipedia |
@twitter_search | Twitter/X |
@yelp_search | Yelp |
@linkedin_search |
Element Refs
Refs like e1, e2 are stable identifiers for page elements:
- Call
/snapshotto get current refs - Use ref in
/clickor/type - Refs reset on navigation - get new snapshot after
Session Management
userIdisolates cookies/storage between userssessionKeygroups tabs by conversation/task (legacy:listItemIdalso accepted)- Sessions timeout after 30 minutes of inactivity
- Delete all user data:
DELETE /sessions/:userId
Running Engines
Camoufox (Default)
npm start
# Or: ./run.shFirefox-based with anti-detection. Bypasses Google captcha.
Testing
npm test # All tests (unit + e2e + plugin)
npm run test:plugins # All plugin tests
npm run test:e2e # E2E tests
npm run test:live # Live Google tests
npm run test:debug # With server output
npx jest plugins/youtube # Single plugin's testsDocker
docker build -t camofox-browser .
docker run -p 9377:9377 camofox-browserKey Files
server.js- Camoufox engine (routes + browser logic only -- NOprocess.envorchild_process)lib/openapi.js- OpenAPI spec generation via swagger-jsdoc + docs route setuplib/config.js- Allprocess.envreads centralized hereplugins/youtube/youtube.js- YouTube transcript extraction via yt-dlp (child_processisolated here)lib/launcher.js- Subprocess spawning (child_processisolated here)lib/cookies.js- Cookie file I/Olib/metrics.js- Prometheus metrics (lazy-loaded, off by default -- setPROMETHEUS_ENABLED=1)lib/request-utils.js- HTTP request classification helpers (actionFromReq,classifyError)lib/snapshot.js- Accessibility tree snapshotlib/macros.js- Search macro URL expansionlib/plugins.js- Plugin loader and event buslib/auth.js- Shared auth middleware (API key / loopback)camofox.config.json- Plugin configuration (which plugins to load)plugins/- Plugin directory (loaded per camofox.config.json)plugins/youtube/- Default plugin: YouTube transcript extractionscripts/install-plugin-deps.sh- Installs plugin deps (apt.txt + post-install.sh)plugins/vnc/index.js- VNC plugin routes (nochild_process-- spawning isolated invnc-launcher.js)plugins/vnc/vnc-launcher.js- VNC process management (child_processisolated here)plugins/persistence/index.js- Session persistence lifecycle hookslib/persistence.js- Atomic storage state read/writelib/inflight.js- Inflight request coalescinglib/tmp-cleanup.js- Orphaned temp file cleanuplib/reporter.js- Crash/hang reporter with anonymization + GitHub App auth (see README "Crash Reporter" for setup)Dockerfile- Production container with default plugin deps pre-installed
OpenAPI Spec (REQUIRED for route changes)
The API spec is auto-generated from @openapi JSDoc comments in server.js via swagger-jsdoc. It's served at GET /openapi.json (machine-readable) and GET /docs (swagger-stripey three-panel UI).
When adding, modifying, or removing a route, you MUST update the @openapi JSDoc block above it.
Every route handler in server.js has a JSDoc comment block directly above it like:
/**
* @openapi
* /tabs/{tabId}/click:
* post:
* tags: [Interaction]
* summary: Click an element
* parameters:
* - name: tabId
* in: path
* required: true
* schema:
* type: string
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required: [userId]
* properties:
* userId:
* type: string
* ref:
* type: string
* responses:
* 200:
* description: Click result.
* content:
* application/json:
* schema:
* type: object
* 404:
* description: Tab not found.
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Error'
*/
app.post('/tabs/:tabId/click', async (req, res) => {Rules:
- New routes: add a
@openapiJSDoc block immediately above theapp.get/post/delete(...)call - Path params use
{tabId}syntax (not:tabId) in the JSDoc YAML - Tag must be one of:
System,Tabs,Navigation,Interaction,Content,Sessions,Browser,Legacy - Every operation must have
tags,summary, andresponses - Include
requestBodyfor POST/PUT/DELETE routes that accept JSON - Include
parametersfor path params and required query params - Mark backward-compat endpoints with
deprecated: true - Removing a route: delete the
@openapiblock along with the handler - After any route change, run
npm run generate-openapito regenerate the committedopenapi.json. The test suite will fail if it's stale. - Run
npx jest tests/unit/openapi.test.jsto verify coverage -- the test fails if any route is missing from the spec, if a stale route exists, or ifopenapi.jsonis out of date - Reusable schemas go in
components.schemasinlib/openapi.js(theswaggerDefinition); reference them via$ref: '#/components/schemas/Name'
MCP / OpenClaw Tool Parity (REQUIRED for route changes)
Camofox has two front doors: the HTTP API and the agent tool interface (an MCP server, wrapped by the OpenClaw plugin). Every tool-like browser action must be reachable from both. This is enforced by a build-time gate -- you cannot add a browser action to HTTP and quietly leave it out of the tool set.
- Single source of truth:
mcp/lib/tool-contracts.mjsdefines every tool's name, input schema, and how it maps to an HTTP request (buildRequest).lib/mcp-tool-contracts.mjsjust re-exports it, andopenclaw.plugin.jsonlists the same tool names. Edit the contract once; MCP and OpenClaw both update. - When you add or change an HTTP route that an agent would call (anything tool-like -- navigation, reading the page, interacting, scripting/state), you MUST also:
- Add or update the tool in
mcp/lib/tool-contracts.mjs(schema +buildRequestcase). - Add the tool name to
openclaw.plugin.json'scontracts.tools. - Add contract cases in
tests/unit/mcp-contracts.test.jsand bump the tool count. - Update the tool list in
scripts/test-mcp.mjs(smoke test) andREADME.md.
- Add or update the tool in
- The parity gate:
tests/unit/mcpHttpParity.test.jsfails if anyopenapi.jsonroute is not either (a) reachable by a tool, or (b) explicitly allowlisted. The allowlist has two buckets:INFRA_HTTP_ONLY(health, metrics, start/stop, session admin, traces, download streaming, deprecated aliases -- not agent tools) andTOOL_BACKLOG_HTTP_ONLY(browser actions still missing a tool). The backlog is currently empty and must stay that way -- a genuinely non-tool route goes inINFRA_HTTP_ONLYwith a reason; a tool-like route gets a tool. Do not grow the backlog. - Keep tool schemas matching the real route, not the OpenAPI doc if the two disagree (e.g.
/tabs/:tabId/waitacceptstimeout/waitForNetwork/dismissConsent). Verify against the handler inserver.js. - After changes, run:
node scripts/test-mcp.mjs(stdio smoke test) andNODE_OPTIONS='--experimental-vm-modules' npx jest tests/unit/mcp-contracts.test.js tests/unit/mcpHttpParity.test.js.
Telemetry
No credentials are embedded in this package. lib/reporter.js is a stateless HTTP client that sends anonymized crash/hang telemetry to a Cloudflare Worker endpoint (camofox-telemetry.askjo.workers.dev). The endpoint holds the GitHub App credentials as environment secrets -- see workers/crash-reporter/index.ts. The source is in-repo and auditable.
- Architecture:
lib/reporter.js(client, no secrets, nofs) -> POST -> Cloudflare Worker endpoint -> GitHub Issues lib/reporter.jshas ZERO credentials, ZERO private keys, ZEROfsimports. It only doesfetch()to the telemetry endpoint.lib/resources.jshandlesfs-based resource snapshots (reading /proc on Linux) -- separated from reporter.js so no file-read + network-send pattern exists in any single file. Nochild_processimport.- Anonymization is in
lib/reporter.jsL28-290 -- text scrubbing (anonymize()), URL anonymization (createUrlAnonymizer()), and tab health tracking (createTabHealthTracker()) - Public domain list (~120 entries) determines which domains are shown verbatim vs HMAC-hashed
- Tests:
tests/unit/crashRelay.test.js(telemetry client),tests/unit/crashRelayWorker.test.js(worker contract),tests/unit/noSecrets.test.js(asserts no key material in shipped files) - Self-hosted endpoint: see README "Self-hosted telemetry endpoint" section
- Disable with
CAMOFOX_CRASH_REPORT_ENABLED=false
Code Separation Conventions
The codebase separates concerns across files for clarity and auditability:
- Configuration:
process.envreads live inlib/config.js, which exports a plain config object. No other file reads environment variables directly. - Subprocess management:
child_processusage lives in dedicated launcher modules (lib/launcher.js,plugins/youtube/youtube.js,plugins/vnc/vnc-launcher.js), not in route handlers. - Route handlers:
server.jsdefines Express routes but delegates env/config reads and subprocess spawning to the modules above. - Metrics:
lib/metrics.jslazy-loads prom-client.lib/request-utils.jshandles HTTP method classification.
When adding features that need env vars or subprocesses, put that code in a lib/ module and import the result into server.js.
Plugin System
Plugins extend camofox-browser with new endpoints, background processes, and lifecycle hooks. The server auto-loads all plugins from plugins/<name>/index.js on startup.
Creating a Plugin
plugins/
my-plugin/
index.js Required -- exports register(app, ctx)
apt.txt Optional -- system packages (one per line)
post-install.sh Optional -- executable hook for binary downloads
*.test.js Optional -- Jest tests (auto-discovered)// plugins/my-plugin/index.js
export function register(app, ctx) {
const { sessions, config, log, events, auth, ensureBrowser, getSession, destroySession,
withUserLimit, safePageClose, normalizeUserId, validateUrl, safeError,
buildProxyUrl, proxyPool, failuresTotal } = ctx;
// Register Express routes (auth() enforces API key or loopback)
app.get('/my-endpoint', auth(), async (req, res) => {
const session = sessions.get(req.params.userId);
res.json({ ok: true });
});
// Listen to lifecycle events
events.on('browser:launched', ({ browser, display }) => {
log('info', 'browser is up', { display });
});
events.on('session:created', ({ userId, context }) => {
log('info', 'new session', { userId });
});
events.on('tab:navigated', ({ userId, tabId, url }) => {
log('info', 'navigation', { userId, tabId, url });
});
}Plugin Context (ctx)
| Property | Type | Description |
sessions | Map | Live sessions: userId -> { context, tabGroups, lastAccess } |
config | object | Server CONFIG (port, apiKey, nodeEnv, proxy, etc.) |
log | function | log(level, msg, fields) -- structured JSON logging |
events | EventEmitter | Plugin event bus (29 events -- see below) |
auth | function | auth() returns Express middleware enforcing API key / loopback |
ensureBrowser | async function | Launch browser if not running, return browser instance |
getSession | async function | getSession(userId) -- get or create a session |
destroySession | async function | destroySession(userId, { reason }) -- tear down and await a session close |
withUserLimit | async function | withUserLimit(userId, fn) -- run fn within per-user concurrency limit |
safePageClose | async function | safePageClose(page) -- close a page with timeout guard |
normalizeUserId | function | normalizeUserId(id) -- coerce to string for map keys |
validateUrl | function | validateUrl(url) -- returns error string or null |
safeError | function | safeError(err) -- sanitize error for client response |
buildProxyUrl | function | buildProxyUrl(pool, proxyConfig) -- get proxy URL for external requests |
proxyPool | object|null | Proxy pool instance (null if no proxy configured) |
failuresTotal | Counter | Prometheus counter: failuresTotal.labels(type, action).inc() |
createMetric | async function | Create a Prometheus metric registered to the shared registry (see below) |
metricsRegistry | function | metricsRegistry() -- raw prom-client Registry or null |
Events (29)
28 emitted by core, 1 (session:storage:export) emitted by plugins.
Browser Lifecycle
| Event | Payload | Mutating? |
browser:launching | { options } | (ok) Modify launch options in-place |
browser:launched | { browser, display } | |
browser:restart | { reason } | |
browser:closed | { reason } | |
browser:error | { error } |
Session Lifecycle
| Event | Payload | Mutating? |
session:creating | { userId, contextOptions } | (ok) Modify context options in-place |
session:created | { userId, context } | |
session:destroyed | { userId, reason } | |
session:expired | { userId, idleMs } |
Tab Lifecycle
| Event | Payload |
tab:created | { userId, tabId, page, url } |
tab:navigated | { userId, tabId, url, prevUrl } |
tab:destroyed | { userId, tabId, reason } |
tab:recycled | { userId, tabId } |
tab:error | { userId, tabId, error } |
Content
| Event | Payload |
tab:snapshot | { userId, tabId, snapshot } |
tab:screenshot | { userId, tabId, buffer } |
tab:evaluate | { userId, tabId, expression } |
tab:evaluated | { userId, tabId, result } |
Input
| Event | Payload |
tab:click | { userId, tabId, ref, selector } |
tab:type | { userId, tabId, text, ref, mode } |
tab:scroll | { userId, tabId, direction, amount } |
tab:press | { userId, tabId, key } |
Downloads
| Event | Payload |
tab:download:start | { userId, tabId, filename, url } |
tab:download:complete | { userId, tabId, filename, path, size } |
Cookies / Auth
| Event | Payload |
session:cookies:import | { userId, count } |
session:storage:export | { userId, storageState } |
Server
| Event | Payload |
server:starting | { port } |
server:started | { port, pid } |
server:shutdown | { signal } |
Mutating Hooks
browser:launching, session:creating, session:created, and session:destroyed are emitted via events.emitAsync() -- the server awaits all listeners (including async ones) before proceeding. This ensures async work like loading storage state from disk completes before the context is created.
Other events use regular events.emit() (fire-and-forget).
Modify payload objects in-place:
// Change Xvfb resolution (e.g., for VNC plugin)
events.on('browser:launching', ({ options }) => {
options.virtual_display_resolution = '1920x1080x24';
});
// Inject saved auth state into new sessions
events.on('session:creating', ({ userId, contextOptions }) => {
const saved = loadStorageState(userId);
if (saved) contextOptions.storageState = saved;
});System Packages (apt.txt) and Post-Install Hooks
Plugins that need system packages list them one per line in apt.txt:
# plugins/vnc/apt.txt
x11vnc
novnc
python3-websockifyFor binary downloads or setup not available via apt, add an executable post-install.sh:
# plugins/youtube/post-install.sh
#!/bin/sh
set -e
curl -fL https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp -o /usr/local/bin/yt-dlp
chmod +x /usr/local/bin/yt-dlpBoth are run by scripts/install-plugin-deps.sh during Docker build.
Configuration (camofox.config.json)
camofox.config.json controls which plugins are loaded at runtime and during Docker build:
{
"id": "camofox-browser",
"name": "Camofox Browser",
"version": "1.5.2",
"plugins": ["youtube"]
}plugins-- array of plugin directory names to load. Only these are loaded at startup and have deps installed during build.- If the file is missing or has no
pluginskey, all plugins inplugins/are loaded (backward-compatible). - This is camofox's own config.
openclaw.plugin.jsonis separate -- it tells the OpenClaw Gateway how to configure camofox as an external service.
Installing Plugins
Use the plugin manager to install third-party plugins from git or local paths:
# Install from git
node scripts/plugin.js install https://github.com/user/camofox-screenshot-plugin
node scripts/plugin.js install git:github.com/user/my-plugin
# Install from local directory
node scripts/plugin.js install ./path/to/my-plugin
# List installed plugins
node scripts/plugin.js list
# Remove a plugin
node scripts/plugin.js remove my-pluginThe installer copies the plugin into plugins/, adds it to camofox.config.json, and runs npm install for any npm dependencies. System deps (apt.txt, post-install.sh) are flagged but must be installed manually or via Docker rebuild.
Plugin sources can be:
- Git repos where the root has
index.jswithregister()(installed as one plugin) - Git repos with a
plugins/subdirectory (each subdirectory installed as a separate plugin) - Local directories with
index.jsandregister()
Default Plugins
Three plugins ship by default:
- youtube -- YouTube transcript extraction (enabled by default)
- persistence -- Per-user session state persistence to
~/.camofox/profiles/(enabled by default) - vnc -- Interactive browser login via noVNC (disabled by default, requires
ENABLE_VNC=1)
The youtube plugin ships as a default plugin -- it's listed in camofox.config.json and included in the base Docker image with its deps pre-installed. The base image runs scripts/install-plugin-deps.sh which reads the config and installs apt.txt packages + post-install.sh hooks for listed plugins.
The with-plugins Dockerfile stage is for rebuilding after adding third-party plugins:
docker build --target with-plugins -t camofox-browser .The with-plugins stage re-runs install-plugin-deps.sh to pick up any new plugins added to plugins/.
Code Separation Rules
Plugins follow the same separation conventions as core (see "Code Separation Conventions" above):
- No
process.envin plugin files that also have route handlers -- read config fromctx.config - No
child_processin plugin files that also have route handlers -- spawn from a separatelib/module
Custom Metrics
Plugins create Prometheus metrics via ctx.createMetric(). Returns a no-op stub when Prometheus is disabled -- no null checks needed.
// In register(app, ctx):
const transcriptsTotal = await ctx.createMetric('counter', {
name: 'camofox_youtube_transcripts_total',
help: 'YouTube transcripts extracted',
labelNames: ['method'],
});
// Use anywhere -- works whether Prometheus is enabled or not
transcriptsTotal.labels('yt-dlp').inc();Supported types: 'counter', 'histogram', 'gauge'. Options are standard prom-client options (name, help, labelNames, buckets, etc.). Metrics auto-register to the shared registry and appear on /metrics.
For advanced use, ctx.metricsRegistry() returns the raw prom-client Registry (or null when disabled).
Example: YouTube Transcript Plugin
The YouTube plugin (plugins/youtube/) is the reference implementation. It extracts transcripts via yt-dlp with browser fallback, using ctx helpers for auth, logging, browser access, and concurrency control.
plugins/
youtube/
index.js # register(app, ctx) -- route handler + browser fallback
youtube.js # yt-dlp process management + transcript parsing
youtube.test.js # parser unit tests
apt.txt # python3-minimal (yt-dlp runtime dep)
post-install.sh # downloads yt-dlp binary// plugins/youtube/index.js (simplified)
import { detectYtDlp, hasYtDlp, ensureYtDlp, ytDlpTranscript } from './youtube.js';
import { classifyError } from '../../lib/request-utils.js';
export async function register(app, ctx) {
const { log, config, sessions, ensureBrowser, getSession,
withUserLimit, safePageClose, normalizeUserId,
validateUrl, safeError, buildProxyUrl, proxyPool,
failuresTotal } = ctx;
await detectYtDlp(log);
app.post('/youtube/transcript', ctx.auth(), async (req, res) => {
// ... validate URL, extract videoId, try yt-dlp then browser fallback
});
async function browserTranscript(reqId, url, videoId, lang) {
return await withUserLimit('__yt_transcript__', async () => {
await ensureBrowser();
const session = await getSession('__yt_transcript__');
const page = await session.context.newPage();
// ... intercept captions, parse transcript
await safePageClose(page);
});
}
}Key patterns:
- Auth:
ctx.auth()middleware on the route - Logging:
ctx.log('info', ...)-- neverconsole.log - Browser access:
ctx.ensureBrowser()+ctx.getSession()for browser-backed features - Concurrency:
ctx.withUserLimit()to respect per-user limits - Metrics:
ctx.failuresTotal.labels(...)for core counters,ctx.createMetric()for custom - Code separation:
child_processinyoutube.js, route handler inindex.js-- separate files - System deps:
apt.txtlists packages installed viascripts/install-plugin-deps.sh
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

