web-browser
SKILL.md
Web Browser
When to use
- Navigate or interact with live websites.
- Click buttons, fill forms, or extract page content.
- Evaluate JavaScript in a real browser context.
- Capture screenshots for debugging or review.
- UI validation and smoke testing in a real browser.
- Measure and gate latency regressions in web-browser tools with the local benchmark scripts.
Requirements
- Run commands from
codex/skills/web-browser/(so./tools/*.jsresolves). - Node.js (ESM + top-level
await) and repo deps installed (notablypuppeteer-core). - Chrome/Chromium installed, or set
CODEX_CHROME_PATH.
Quick start
# Start Chrome with CDP on :9222
./tools/start.js
# Safer (recommended on your laptop): avoid killing an existing Chrome session
./tools/start.js --no-kill
# Open a deterministic tab, then operate on it
./tools/nav.js https://example.com --new
./tools/eval.js 'document.title'
./tools/screenshot.js
Loop: take small steps → inspect state (eval.js / screenshot.js) → repeat.
Performance pass ($lift)
Use this when optimizing or validating web-browser tool latency.
./tools/start.js --no-kill
./tools/nav.js https://example.com --new
cd tools
npm run -s bench:all -- --warmup 5 --samples 30 --screenshot-samples 20
For stricter CI-style gating, add p95 budgets:
cd tools
npm run -s bench:all -- \
--warmup 5 \
--samples 30 \
--screenshot-samples 20 \
--budget-nav-p95-ms 310 \
--budget-eval-p95-ms 220 \
--budget-screenshot-p95-ms 320 \
--budget-start-p95-ms 140
Configuration
- Flags
start.js:--port,--user-data-dir,--chrome-path,--profile,--no-killnav.js/eval.js/screenshot.js/pick.js:--port,--browser-url
- Environment
CODEX_BROWSER_URL: CDP URL (used when no CLI--port/--browser-url)CODEX_BROWSER_PORT(alias:CODEX_CDP_PORT): CDP port for localhostCODEX_BROWSER_USER_DATA_DIR: Chrome user data dir (used bystart.js)CODEX_CHROME_PATH(aliases:CHROME_PATH,PUPPETEER_EXECUTABLE_PATH): Chrome/Chromium executable
Targeting (active tab)
All tools operate on the “active tab”, defined as the last page returned by puppeteer.pages() (roughly: the most recently opened tab).
- Prefer
./tools/nav.js <url> --newwhen you want deterministic targeting. - If actions hit the “wrong” tab, close extra tabs or open a fresh one.
Common commands
# See all options (safe; does not start/kill Chrome)
./tools/start.js --help
# Use a non-default port
./tools/start.js --port 9223 --no-kill
./tools/nav.js --port 9223 https://example.com --new
# Or configure once via env
export CODEX_BROWSER_URL=http://localhost:9223
./tools/eval.js 'document.title'
# Wait for a selector (polling with timeout; good for SPAs)
./tools/eval.js '(async () => { for (let i = 0; i < 50; i++) { const el = document.querySelector("button[type=submit]"); if (el) return true; await new Promise(r => setTimeout(r, 100)); } return false; })()'
# Screenshot current viewport
# Prints a PNG filepath in your system temp dir
./tools/screenshot.js
# Pick elements interactively
# Prints tag/id/class/text/html/parents for one element (or many via Cmd/Ctrl+click)
./tools/pick.js "Click the submit button"
Tip: use pick.js to inspect attributes/text, then craft a selector for eval.js.
Recipes
# Scrape structured data (return an array of objects for readable output)
./tools/eval.js 'Array.from(document.querySelectorAll("a"), a => ({ href: a.href, text: a.textContent?.trim() }))'
- Login flows
- Dedicated automation profile: run
./tools/start.js, log in once, then reuse the persisted profile in your user-data-dir (default:~/.cache/scraping). - Default-profile bootstrap: run
./tools/start.js --profilewhen you truly need existing cookies/logins.
- Dedicated automation profile: run
Security & privacy
./tools/start.js --profileusesrsync -a --deleteto copy your default Chrome profile into your user-data-dir (cookies/sessions/PII).- Treat your user-data-dir (default:
~/.cache/scraping) as sensitive; avoid on shared machines and delete it when done if needed.
Troubleshooting
✗ Failed to connect to Chrome via CDP: run./tools/start.js(or setCODEX_BROWSER_URL/--portto match your Chrome).✗ No active tab found: open a page first (e.g.,./tools/nav.js https://example.com --new).- Port
9222is busy: pick a new one (./tools/start.js --port 9223). - Selector flakiness: use the polling pattern above; SPAs often need a wait after
domcontentloaded. - Chrome path not found: set
CODEX_CHROME_PATHor pass--chrome-path.
Pitfalls
./tools/start.jsdefaults to killing Chrome processes (use--no-killto avoid this).- Use single quotes around JS to avoid shell-escaping issues.
Prove $lift uses Zig
When you need objective proof that the local $lift tooling is Zig-based:
bench_stats --help 2>&1 | sed -n '1,6p'
perf_report --help 2>&1 | sed -n '1,6p'
brew cat tkersey/tap/lift | rg -n 'depends_on \"zig\" => :build|build-exe|bench_stats.zig|perf_report.zig'
References
codex/skills/web-browser/tools/start.jscodex/skills/web-browser/tools/nav.jscodex/skills/web-browser/tools/eval.jscodex/skills/web-browser/tools/screenshot.jscodex/skills/web-browser/tools/pick.jscodex/skills/web-browser/tools/bench-eval.jscodex/skills/web-browser/tools/bench-all.js
Weekly Installs
39
Repository
tkersey/dotfilesGitHub Stars
43
First Seen
Feb 20, 2026
Security Audits
Installed on
opencode39
github-copilot38
codex38
kimi-cli38
gemini-cli38
cursor38