{"id":47281312,"url":"https://github.com/forjd/browse","last_synced_at":"2026-04-01T22:24:37.949Z","repository":{"id":344070662,"uuid":"1180330445","full_name":"forjd/browse","owner":"forjd","description":"Fast CLI for browser automation — Playwright + stealth behind a persistent daemon","archived":false,"fork":false,"pushed_at":"2026-03-27T23:11:58.000Z","size":963,"stargazers_count":10,"open_issues_count":6,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-28T02:52:03.573Z","etag":null,"topics":["anti-detection","browser-automation","bun","cli","fingerprinting","headless-chrome","playwright","qa","stealth","testing","typescript","web-scraping"],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/forjd.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-03-12T23:49:42.000Z","updated_at":"2026-03-27T22:45:29.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/forjd/browse","commit_stats":null,"previous_names":["forjd/browse"],"tags_count":20,"template":false,"template_full_name":null,"purl":"pkg:github/forjd/browse","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/forjd%2Fbrowse","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/forjd%2Fbrowse/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/forjd%2Fbrowse/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/forjd%2Fbrowse/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/forjd","download_url":"https://codeload.github.com/forjd/browse/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/forjd%2Fbrowse/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31292639,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-01T21:15:39.731Z","status":"ssl_error","status_checked_at":"2026-04-01T21:15:34.046Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["anti-detection","browser-automation","bun","cli","fingerprinting","headless-chrome","playwright","qa","stealth","testing","typescript","web-scraping"],"created_at":"2026-03-16T00:49:17.806Z","updated_at":"2026-04-01T22:24:37.933Z","avatar_url":"https://github.com/forjd.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# browse\n\n[![CI](https://github.com/forjd/browse/actions/workflows/ci.yml/badge.svg)](https://github.com/forjd/browse/actions/workflows/ci.yml)\n[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)\n[![Bun](https://img.shields.io/badge/runtime-Bun-f9f1e1.svg)](https://bun.sh)\n[![Playwright](https://img.shields.io/badge/browser-Playwright-2ead33.svg)](https://playwright.dev)\n\nA fast CLI for browser automation with built-in stealth. Wraps Playwright behind a persistent daemon on a Unix socket — first call cold-starts in ~3s, subsequent calls run in under 30ms (warm daemon).\n\nBuilt for AI agents doing QA and web scraping, but works just as well by hand.\n\n**[Why Browse?](docs/why-browse.md)** — what makes it different for AI agent builders, DevOps, QA, security, accessibility, and more.\n\n---\n\n## Quick Start\n\n```bash\n# Install\nbrew install forjd/tap/browse\nbunx playwright install chrome\n\n# Navigate and screenshot in 2 commands\nbrowse goto https://example.com\nbrowse screenshot\n\n# Or automate with refs\nbrowse snapshot                    # see @e1, @e2, @e3...\nbrowse fill @e1 \"hello@example.com\"\nbrowse click @e2\n```\n\n---\n\n## Table of Contents\n\n- [Install](#install)\n- [System Requirements](#system-requirements)\n- [Core Concepts](#core-concepts)\n  - [How refs work](#how-refs-work)\n  - [Navigation](#navigation)\n  - [Interaction](#interaction)\n  - [Waiting](#waiting-for-conditions)\n- [AI Agent Integration](#ai-agent-integration)\n- [Common Tasks](#common-tasks)\n  - [Screenshots](#screenshots-and-visual-diff)\n  - [Accessibility](#accessibility-audit)\n  - [Performance](#performance-metrics)\n  - [Security](#security-audit)\n  - [Data Extraction](#data-extraction)\n  - [Responsive Testing](#responsive-testing)\n  - [Flows \u0026 Assertions](#flows-and-assertions)\n- [Advanced](#advanced)\n  - [Named Sessions](#named-sessions)\n  - [Network Interception](#network-interception)\n  - [Multi-browser Support](#multi-browser-support)\n  - [Proxy Support](#proxy-support)\n- [Configuration](#configuration)\n- [Architecture \u0026 Stealth](#architecture--stealth)\n- [Performance](#performance)\n- [Commands Reference](#commands-reference)\n- [Troubleshooting](#troubleshooting)\n- [License](#license)\n\n---\n\n## Install\n\n### Homebrew (recommended)\n\n```bash\nbrew install forjd/tap/browse\nbunx playwright install chrome\n```\n\n### Manual install\n\nRequires [Bun](https://bun.sh) \u003e= 1.0.\n\n```bash\n# Via script\ncurl -fsSL https://raw.githubusercontent.com/forjd/browse/main/install.sh | bash\n\n# Or manually\ngit clone https://github.com/forjd/browse.git\ncd browse\n./setup.sh\n```\n\nThis compiles a self-contained binary to `dist/browse` and symlinks it to `~/.local/bin/browse`.\n\n### Claude Code skill\n\nIf you use [Claude Code](https://docs.anthropic.com/en/docs/claude-code), install the skill:\n\n```bash\nbunx skills add forjd/browse\n```\n\n---\n\n## System Requirements\n\n| Platform | Version | Notes |\n|----------|---------|-------|\n| macOS | 12+ (Monterey) | Apple Silicon or Intel |\n| Linux | glibc 2.31+ | Ubuntu 20.04+, Debian 11+ |\n| Windows | Not supported | Use WSL2 |\n\n**Resources:**\n- Disk: ~500MB for Chromium\n- RAM: ~150MB for daemon, ~300MB per page\n- Socket: Unix domain socket at `/tmp/browse-daemon.sock`\n\n---\n\n## Core Concepts\n\n### How refs work\n\nRefs (`@e1`, `@e2`, ...) are how you target elements. Run `browse snapshot` to assign them, then use them with `click`, `fill`, and `select`. Refs go stale after navigation — just snapshot again.\n\n```bash\nbrowse snapshot                     # assigns @e1, @e2, @e3, ...\nbrowse fill @e3 \"search term\"\nbrowse click @e4\nbrowse snapshot                     # re-assign after the page changes\n```\n\nSample output:\n```\n[page] \"Example Domain\"\n\n@e1 [link] \"Learn more\"\n@e2 [button] \"Submit\"\n@e3 [textbox] \"Email address\"\n```\n\n### Navigation\n\n```bash\nbrowse goto https://example.com     # navigate — daemon starts automatically\nbrowse goto https://example.com --preset mobile  # mobile viewport\nbrowse url                          # print the current page URL\nbrowse back                         # navigate back in history\nbrowse forward                      # navigate forward in history\nbrowse reload                       # reload current page\nbrowse reload --hard                # reload bypassing cache\n```\n\n### Interaction\n\n```bash\nbrowse click @e1                    # click an element by ref\nbrowse hover @e3                    # hover over an element by ref\nbrowse press Tab                    # send keyboard key press\nbrowse press Shift+Tab              # key combination\nbrowse press Escape                 # close modals/popovers\nbrowse fill @e2 \"hello\"             # type into an input\nbrowse upload @e5 /path/to/file.pdf  # set file on a file input\nbrowse scroll down                  # scroll down one viewport height\nbrowse scroll @e3                   # scroll element into view\nbrowse scroll 0 500                 # scroll to absolute x,y coordinates\n```\n\n### Waiting for conditions\n\nUseful for SPAs where client-side navigation doesn't trigger full page loads:\n\n```bash\nbrowse wait url /dashboard        # wait until URL contains string\nbrowse wait text \"Welcome\"        # wait until text appears on page\nbrowse wait visible .dashboard    # wait until element is visible\nbrowse wait hidden .spinner       # wait until element disappears\nbrowse wait network-idle          # wait until no pending requests\nbrowse wait 2000                  # simple delay (last resort)\n```\n\nAll wait subcommands respect `--timeout` and error if the condition isn't met in time.\n\n---\n\n## AI Agent Integration\n\nBrowse is designed as a browser backend for AI agents. The CLI interface, JSON responses, persistent daemon, and built-in stealth make it a drop-in browser layer for agent frameworks like [OpenClaw](https://openclaw.ai), [Claude Code](https://docs.anthropic.com/en/docs/claude-code), and custom pipelines.\n\n**Why agents prefer Browse over Playwright/Selenium directly:**\n\n| Feature | Browse | Raw Playwright |\n|---------|--------|----------------|\n| **Startup time** | ~3s cold, \u003c30ms warm | ~3s every call |\n| **CLI interface** | Yes — easy to shell out | No — requires Node.js wrapper |\n| **Stealth** | Built-in, passes bot detection | Requires patches/plugins |\n| **JSON output** | Native `--json` flag | Manual serialization |\n| **Session management** | Named sessions via CLI | Code-only |\n| **Resource usage** | Shared daemon | New process per call |\n\nAgents get sub-30ms command latency, named sessions for parallel work, and headless Chrome that passes bot detection — no browser config needed.\n\n---\n\n## Common Tasks\n\n### Screenshots and visual diff\n\n```bash\nbrowse screenshot [path]            # full-page (auto-names if no path given)\nbrowse screenshot --viewport        # viewport only\nbrowse screenshot --diff baseline.png          # compare against baseline\nbrowse screenshot --diff baseline.png --threshold 5  # custom sensitivity\nbrowse screenshots list             # list saved screenshots\nbrowse report --out report.html     # generate HTML report from screenshots\n```\n\nThe `--diff` flag compares the new screenshot against a baseline image and produces a similarity score, diff pixel count, and a visual diff image highlighting changed regions in red.\n\n### Accessibility audit\n\n```bash\nbrowse a11y                       # full page audit, human-readable output\nbrowse a11y --standard wcag2aa    # WCAG 2.0 AA rules only\nbrowse a11y --standard wcag21aa   # WCAG 2.1 AA rules\nbrowse a11y @e5                   # audit a specific element by ref\nbrowse a11y --json                # machine-readable output for CI\nbrowse a11y --include \".main\"     # scope to CSS selector\nbrowse a11y --exclude \".ads\"      # exclude regions\n```\n\nOutput lists violations grouped by severity (critical, serious, moderate, minor) with the failing rule, affected elements, and a link to the fix guidance. Powered by [axe-core](https://github.com/dequelabs/axe-core).\n\n### Performance metrics\n\n```bash\nbrowse perf                                  # Core Web Vitals + timing metrics\nbrowse perf --json                           # machine-readable output\nbrowse perf --budget lcp=2500,cls=0.1,fcp=1800  # performance budget check\n```\n\nOutput includes TTFB, FCP, LCP, CLS, DOM Content Loaded, Page Load, resource count, and transfer size. The `--budget` flag checks each metric against a threshold and reports pass/fail.\n\n### Security audit\n\n```bash\nbrowse security                              # full security audit\nbrowse security --json                       # machine-readable output\n```\n\nAudits the current page for:\n- **Security headers:** HSTS, CSP, X-Content-Type-Options, X-Frame-Options, Referrer-Policy, Permissions-Policy\n- **Cookie security:** Secure, HttpOnly, SameSite flags\n- **Mixed content:** HTTP resources loaded on HTTPS pages\n\n### Data extraction\n\n```bash\nbrowse extract table \"table.results\"         # extract HTML table as text\nbrowse extract table \"table.data\" --json     # extract as JSON objects\nbrowse extract table \"table.data\" --csv      # extract as CSV\nbrowse extract links                         # extract all links\nbrowse extract links --filter \"example\"      # filter links by pattern\nbrowse extract meta                          # meta tags, Open Graph, JSON-LD\nbrowse extract meta --json                   # machine-readable metadata\nbrowse extract select \"h2\"                   # extract text of matching elements\nbrowse extract select \"a.nav\" --attr href  # extract attribute values\n```\n\n### Responsive testing\n\n```bash\nbrowse viewport                              # show current viewport size\nbrowse viewport 320 568                      # set exact width x height\nbrowse viewport 320x568                      # alternative format\nbrowse viewport --device \"iPhone SE\"         # use a Playwright device profile\nbrowse viewport --preset mobile              # 375x667\nbrowse viewport --preset tablet              # 768x1024\nbrowse viewport --preset desktop             # 1440x900\nbrowse responsive                            # screenshot at all default breakpoints\nbrowse responsive --breakpoints 320x568,768x1024,1920x1080  # custom breakpoints\n```\n\n### Flows and assertions\n\nDefine reusable flows in `browse.config.json`, then run them:\n\n```bash\nbrowse flow list\nbrowse flow signup --var base_url=https://staging.example.com\nbrowse flow signup --reporter junit          # JUnit XML output for CI\nbrowse flow signup --reporter json           # structured JSON output\nbrowse flow signup --dry-run                 # preview steps without running\nbrowse assert text-contains \"Welcome\"\nbrowse assert visible \".dashboard\"\nbrowse healthcheck --var base_url=https://staging.example.com\n```\n\n---\n\n## Advanced\n\n### Named sessions\n\nRun multiple named sessions within a shared Chromium process:\n\n```bash\nbrowse session create worker-1              # create a session (shared context)\nbrowse session create worker-2 --isolated   # create with isolated browser context\nbrowse --session worker-1 goto https://a.com  # route commands to a session\nbrowse --session worker-2 goto https://b.com\nbrowse session list                         # list all sessions\nbrowse session close worker-1               # close a session\n```\n\nBy default, sessions share the browser context (cookies, storage). Use `--isolated` to create a fully separate browser context with its own cookies, storage, and permissions.\n\n**Pool (library) for multi-agent orchestration:**\n\n```typescript\nimport { createPool } from \"browse/pool\";\n\nconst pool = createPool({ socketPath: \"/tmp/browse-daemon.sock\", maxSessions: 10 });\nconst session = await pool.acquire();\nawait session.exec(\"goto\", \"https://example.com\");\nsession.release();\nawait pool.destroy();\n```\n\n### Network interception\n\n```bash\nbrowse intercept add \"**/api/users\" --body '{\"users\":[]}'   # mock API response\nbrowse intercept add \"**/analytics/**\" --status 204          # block with status\nbrowse intercept list                                        # list active rules\nbrowse intercept remove \"**/api/users\"                       # remove a rule\nbrowse intercept clear                                       # remove all rules\n```\n\n### Multi-browser support\n\nBrowse defaults to Chromium but also supports Firefox and WebKit for cross-browser testing:\n\n```bash\nbrowse --browser firefox goto https://example.com\nbrowse --browser webkit goto https://example.com\n```\n\nOr via environment variable (must be set before the daemon starts):\n\n```bash\nBROWSE_BROWSER=firefox browse goto https://example.com\n```\n\n\u003e **Note:** Stealth features are Chromium-specific and are not applied to Firefox or WebKit.\n\nTo install additional browsers, set `BROWSE_BROWSERS` before running setup:\n\n```bash\nBROWSE_BROWSERS=\"firefox webkit\" ./setup.sh\n```\n\n### Proxy support\n\nRoute all browser traffic through an HTTP or SOCKS proxy:\n\n```bash\nbrowse --proxy http://proxy:8080 goto https://example.com\nbrowse --proxy socks5://proxy:1080 goto https://example.com\n```\n\nOr via environment variable:\n\n```bash\nBROWSE_PROXY=http://proxy:8080 browse goto https://example.com\n```\n\nOr in `browse.config.json` (supports authentication and bypass lists):\n\n```json\n{\n  \"proxy\": {\n    \"server\": \"http://proxy:8080\",\n    \"bypass\": \"localhost,*.internal.com\",\n    \"username\": \"user\",\n    \"password\": \"pass\"\n  }\n}\n```\n\n\u003e **Note:** The daemon must be restarted for proxy changes to take effect. Run `browse quit` first.\n\n### Headed mode\n\nLaunch the browser visibly for debugging:\n\n```bash\nBROWSE_HEADED=1 browse goto https://example.com\n```\n\n\u003e **Note:** Environment variable must be set before the daemon starts. If already running, run `browse quit` first.\n\n---\n\n## Configuration\n\nOptional. Create `browse.config.json` in your project root to configure login environments, reusable flows, permission checks, and health checks.\n\n```json\n{\n  \"environments\": {\n    \"staging\": {\n      \"loginUrl\": \"https://staging.example.com/login\",\n      \"userEnvVar\": \"STAGING_USER\",\n      \"passEnvVar\": \"STAGING_PASS\",\n      \"usernameField\": \"Email\",\n      \"passwordField\": \"Password\",\n      \"submitButton\": \"Sign in\",\n      \"successCondition\": { \"urlContains\": \"/dashboard\" }\n    }\n  },\n  \"flows\": {\n    \"signup\": {\n      \"description\": \"Test the signup flow\",\n      \"variables\": [\"base_url\", \"test_email\"],\n      \"steps\": [\n        { \"goto\": \"{{base_url}}/register\" },\n        { \"fill\": { \"input[name=email]\": \"{{test_email}}\" } },\n        { \"click\": \"button[type=submit]\" },\n        { \"wait\": { \"urlContains\": \"/welcome\" } },\n        { \"assert\": { \"textContains\": \"Welcome\" } }\n      ]\n    }\n  },\n  \"timeout\": 45000\n}\n```\n\nSee [configuration docs](docs/configuration.md) for full options including Playwright passthrough.\n\n---\n\n## Architecture \u0026 Stealth\n\n**Architecture:**\n\n```\nCLI ──JSON──▶ Unix socket ──▶ Daemon ──▶ Playwright ──▶ Chromium (default)\n              TCP socket ──┘                          ├─▶ Firefox\n                                                      └─▶ WebKit\n```\n\nThe daemon spawns on first use and stays alive for 30 minutes of inactivity. It owns a single browser instance and communicates over a Unix socket. The CLI is a thin client that serialises commands as JSON. Named sessions allow multiple page groups to share one browser process.\n\nThe daemon socket is secured with a shared-secret authentication token stored at `$XDG_STATE_HOME/browse/daemon.token`. SIGTERM and SIGINT are trapped for graceful shutdown.\n\nFor remote agent access, the daemon can also listen on a TCP port via `--listen \u003chost\u003e:\u003cport\u003e`.\n\n**Stealth:**\n\nBrowse ships with built-in anti-detection for headless Chrome — no plugins or extra config needed:\n\n- **Navigator patching** — clean user-agent string, consistent `userAgentData` brands/platform via CDP metadata\n- **Screen spoofing** — plausible monitor resolution and taskbar offset to avoid viewport-equals-screen detection\n- **Chrome stubs** — `chrome.app` and `chrome.runtime` stubs matching real Chrome\n- **Worker coverage** — user-agent and fingerprint patches in SharedWorker and ServiceWorker contexts\n- **Iframe protection** — randomised mouse event coordinates to prevent CDP coordinate leaks (Cloudflare Turnstile)\n\nPasses Sannysoft, Intoli, Pixelscan, and BrowserLeaks. Partially evades CreepJS.\n\n---\n\n## Performance\n\nMeasured with `browse benchmark`:\n\n| Command    | p50  | p95  |\n|------------|------|------|\n| goto       | 27ms | 32ms |\n| snapshot   | 1ms  | 11ms |\n| screenshot | 24ms | 25ms |\n| click      | 17ms | 18ms |\n| fill       | 1ms  | 26ms |\n\nTarget: p95 \u003c 200ms for non-screenshot commands.\n\n---\n\n## Commands Reference\n\nBrowse has 90+ commands. Here are the most commonly used:\n\n| Command | Description |\n|---------|-------------|\n| `goto \u003curl\u003e` | Navigate to URL |\n| `url` | Print current page URL |\n| `snapshot` | List elements with refs (@e1, @e2...) |\n| `click @eN` | Click element by ref |\n| `fill @eN \"value\"` | Fill input (clears first) |\n| `screenshot [path]` | Capture page |\n| `a11y` | Accessibility audit |\n| `perf` | Core Web Vitals |\n| `security` | Security audit |\n| `flow \u003cname\u003e` | Run configured flow |\n| `session create \u003cname\u003e` | Create named session |\n| `status` | Daemon status |\n| `quit` | Stop the daemon |\n\n**See the [full commands list](docs/commands.md)** for complete documentation including:\n- DOM inspection (`html`, `title`, `attr`, `element-count`)\n- Data extraction (`extract table\\|links\\|meta\\|select`)\n- Debugging (`console`, `network`, `trace`, `video`)\n- Dialog handling (`dialog accept\\|dismiss`)\n- Iframes (`frame list\\|switch`)\n- Auth (`login`, `auth-state`)\n- Visual regression (`vrt`, `diff`)\n- CI/CD (`ci-init`, `test-matrix`)\n- And more...\n\n---\n\n## Troubleshooting\n\n| Issue | Solution |\n|-------|----------|\n| **Changes not applying** | Run `browse quit` to restart the daemon after binary rebuilds |\n| **Proxy not working** | Daemon must be restarted for proxy changes: `browse quit` then retry |\n| **macOS \"cannot verify developer\"** | Run `xattr -d com.apple.quarantine ~/.local/bin/browse` |\n| **Stealth not working** | Ensure you're using Chromium (default). Stealth doesn't apply to Firefox/WebKit |\n| **Refs stale** | Run `browse snapshot` again after navigation — refs are page-specific |\n| **Session/auth issues** | Run `browse wipe` to clear all cookies and storage |\n| **Port already in use** | Check for zombie daemon: `lsof -i :\u003cport\u003e` or `browse quit` |\n\n---\n\n## License\n\nMIT — see [LICENSE](LICENSE). Copyright (c) 2026 [Forjd.dev](https://forjd.dev)\n\n---\n\n**Questions or issues?** [Open an issue](https://github.com/forjd/browse/issues) or [start a discussion](https://github.com/forjd/browse/discussions).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fforjd%2Fbrowse","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fforjd%2Fbrowse","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fforjd%2Fbrowse/lists"}