Skip to content

samoht9277/firecode

Repository files navigation

firecode

Browser automation for AI agents. Gives Claude Code (and other AI coding tools) autonomous access to a real Firefox browser so they can verify their own work, debug UI issues, and test web apps without asking you what's on screen.

What it does

You're building a web app with an AI agent. Instead of you being the middleman ("what does the page look like?", "is the button there?", "any errors?"), the agent launches Firefox, navigates to your app, reads the page via ARIA snapshots, clicks buttons, fills forms, checks for console errors and failed network requests, takes screenshots, and verifies everything works. Autonomously.

You: "build a signup page and make sure it works"

Agent: *writes the code*
Agent: firecode browse app navigate "http://localhost:3000/signup"
Agent: firecode snapshot app
       → sees textbox "Email" [ref=e3], button "Sign Up" [ref=e5]
Agent: firecode browse app fill e3 "test@example.com"
Agent: firecode browse app click e5
Agent: firecode snapshot app
       → sees "Welcome! Check your email."
Agent: firecode console app
       → No console errors
Agent: firecode network app
       → No failed requests
Agent: "signup page is working, no errors"

Install

Prerequisites: Node.js 20+ and pnpm (npm install -g pnpm).

macOS / Linux

git clone https://github.com/samoht9277/firecode.git
cd firecode
./install.sh

Windows (PowerShell)

git clone https://github.com/samoht9277/firecode.git
cd firecode
./install.ps1

The installer builds the CLI, downloads the Playwright Firefox browser, puts firecode on your PATH, and installs the Claude Code skill. Pass --no-skill (bash) or -NoSkill (PowerShell) to skip the skill.

If firecode isn't found after install, open a new terminal (the installer added a directory to your PATH). On macOS/Linux that's ~/.local/bin — make sure it's on your PATH.

Using it with Claude Code: the installer symlinks the skill into ~/.claude/skills/firecode. In any Claude Code session, just ask Claude to verify something in the browser (e.g. "open localhost:3000 and check the signup flow works") and it'll use firecode automatically.

Manual install

If you'd rather not run the script:

pnpm install
pnpm build
pnpm --filter @firecode/server exec playwright install firefox
# put packages/cli/dist/index.js on your PATH as `firecode`, e.g.:
ln -sf "$(pwd)/packages/cli/dist/index.js" ~/.local/bin/firecode
# (optional) install the Claude Code skill:
ln -sfn "$(pwd)/skills/firecode" ~/.claude/skills/firecode

Uninstall

./uninstall.sh      # macOS / Linux
./uninstall.ps1     # Windows

Quick start

# Start Firefox (auto-starts in headless mode if you skip this)
firecode start

# Navigate to your app
firecode browse main navigate "http://localhost:3000"

# See the page structure
firecode snapshot main

# Interact using ref IDs from the snapshot
firecode browse main click e4
firecode browse main fill e5 "hello@example.com"

# Check for problems
firecode console main          # JS errors, warnings
firecode network main          # failed HTTP requests
firecode screenshot main       # visual check

# Done
firecode stop

Commands

Server

Command Description
firecode start [--headless] [--extension <xpi>] Launch Firefox and start the server
firecode stop Shut down (force kills if unresponsive)
firecode status Show server state and open pages
firecode wallet import [--seed-file <path>] Onboard MetaMask from $FIRECODE_WALLET_SEED (throwaway seeds only)
firecode wallet unlock Unlock MetaMask with $FIRECODE_WALLET_PASSWORD
firecode wallet approve Approve the pending MetaMask popup

Browsing

Command Description
browse <page> navigate <url> Go to a URL (creates page if needed)
browse <page> click <ref> [--force] Click an element by ref ID
browse <page> fill <ref> <value> Clear and fill a text input
browse <page> type <ref> <text> Type text character by character
browse <page> select <ref> <value> Select a dropdown option
browse <page> hover <ref> Hover over an element
browse <page> click-text "<text>" Click by visible text (no snapshot needed)
browse <page> keyboard <key> Press a key (ArrowRight, Enter, Tab, etc.)
browse <page> scroll down|up|<ref> Scroll the page or to an element
browse <page> evaluate "<js>" Run JavaScript and get the result
browse <page> viewport mobile|tablet|desktop|<w> <h> Set viewport size
browse <page> reload Refresh the page
browse <page> back / forward Browser history navigation
browse <page> wait <ms> Wait for a duration
browse <page> wait-for "<text>" Wait for text to appear
browse <page> wait-idle Wait for network to be idle
browse <page> assert-text "<text>" Check if text exists (fails if not)
browse <page> upload "<trigger>" <file> Upload a file: click the trigger (text/selector) and answer the file dialog
browse <page> upload "<input-sel>" <file> --input Set an <input type=file> directly (searches all frames; - = any)

Observing

Command Description
snapshot <page> ARIA accessibility tree with ref IDs
screenshot <page> [path] Capture PNG
screenshot <page> [path] --diff <baseline> Pixel-level visual comparison
text <page> Visible text content (lighter than snapshot)
console <page> [--clear] Browser console logs
network <page> [--all] [--clear] Network requests (failures by default)
cookies <page> Page cookies
storage <page> [--session] [--clear] localStorage/sessionStorage
pdf <page> [path] Export page as PDF (headless only)

Recording

Command Description
record start <page> Start recording actions
record stop <page> Stop and show captured steps
record save <page> <path> Save recording to JSON
replay <page> <path> Replay a saved recording

Testing

Command Description
test [--target unstaged|branch|changes] Generate and run tests from git diff
test --base-url <url> Set app URL (default: localhost:3000)
test -y Skip plan review

How snapshots work

firecode snapshot returns a YAML-like accessibility tree. Interactive elements get [ref=eN] tags:

- navigation:
  - link "Dashboard" [ref=e1]
  - link "Settings" [ref=e2]
- main:
  - heading "Profile" [ref=e3]
  - textbox "Name" [ref=e4]
  - button "Save" [ref=e5]
  - button [ref=e6]:

Use refs to interact: firecode browse main click e5 clicks "Save". Unnamed elements (like buttons with HTML content) also get refs.

Refs are tied to the last snapshot. If the page changes, take a new snapshot.

Viewport presets

Preset Size
mobile 375x812
tablet 768x1024
desktop 1920x1080
desktop-hd 3840x2160

Or custom: firecode browse main viewport 1440 900

Visual regression testing

# Take a baseline
firecode screenshot app /tmp/before.png

# Make changes, then compare
firecode screenshot app /tmp/after.png --diff /tmp/before.png
# → "Changed: 2.3% (1847/80000 pixels differ)"
# → Diff image saved to /tmp/after-diff.png

Auto-start

If the server isn't running when you run a command, it auto-starts in headless mode. No need to manually firecode start every time.

Browser extensions & wallets

Load a signed Firefox extension (.xpi) into firecode's browser. The main use case is MetaMask: it injects window.ethereum into every page, so wallet-gated dapps that require a provider to be present (e.g. app.safe.global) will render and let you reach their "Connect wallet" step instead of failing outright.

# Download signed MetaMask from Mozilla Add-ons
curl -L -o metamask.xpi "https://addons.mozilla.org/firefox/downloads/latest/ether-metamask/latest.xpi"

firecode start --extension metamask.xpi
# → Extension loaded: webextension@metamask.io → moz-extension://<uuid>/

The extension loads from a persistent profile at ~/.firecode/profile-<instance>, so its state survives restarts. Delete that directory for a factory reset. --extension is repeatable for multiple extensions. The moz-extension://<uuid>/ base URL (also shown in firecode status) is stable across runs.

Verify the provider is present on any dapp:

firecode browse main navigate "https://app.safe.global"
firecode browse main evaluate "return window.ethereum && window.ethereum.isMetaMask"   # → true

Known limitation: the extension UI can't be scripted

Playwright's Firefox build cannot navigate a tab to a moz-extension:// URLpage.goto() throws NS_ERROR_NOT_AVAILABLE in both headless and headed mode. Because MetaMask's onboarding, unlock, and confirmation popups all live behind such URLs, firecode cannot script the MetaMask UI itself (importing a seed, unlocking, or approving a connect/signature popup). The extension loading and provider injection above still work; only in-extension UI automation is blocked.

The firecode wallet import|unlock|approve commands exist but will report this limitation when run. Automated wallet-UI flows are a Chromium-only capability today (see Synpress/dappwright, which rely on Chromium's --load-extension); a Chromium backend for firecode would be the path to full support.

MetaMask wallet automation (Chromium)

Firefox can inject window.ethereum but cannot script the MetaMask UI (Playwright's Firefox can't open moz-extension:// pages). For full wallet automation — importing a seed, unlocking, approving popups — use the Chromium wallet engine:

# One-time: install Chromium for Playwright
pnpm exec playwright install chromium   # or: npx playwright install chromium

# MetaMask's Chrome build (GitHub release .zip or an unpacked dir)
firecode start --wallet --extension metamask-chrome-13.x.zip
# → Extension loaded: <id> → chrome-extension://<id>/
# → Engine: chromium

export FIRECODE_WALLET_SEED="test test ... junk"   # throwaway/testnet only
export FIRECODE_WALLET_PASSWORD="some-password"     # min 8 chars

firecode browse main navigate https://app.safe.global
firecode wallet import        # onboards MetaMask from the seed
firecode browse main click-text "Connect"
firecode wallet approve       # approves the connect popup

--wallet requires --extension and runs a dedicated Chromium instance (headed by default; --headless supported). Firefox stays the default engine for all non-wallet work.

Use throwaway/testnet seeds only if you experiment with these commands. Secrets come from the environment ($FIRECODE_WALLET_SEED, $FIRECODE_WALLET_PASSWORD) or --seed-file, never from arguments.

Architecture

firecode/
  packages/
    server/     Playwright Firefox + Fastify HTTP API
    cli/        Commander.js CLI
    testgen/    Git diff → test generation
  skills/
    firecode/   Claude Code skill instructions

Single Firefox instance, one OS window with tabs. The CLI talks to the server over HTTP. State file at ~/.firecode/server.json.

Built with

Why Firefox?

Firefox is the default engine for all browsing, verification, and testing. Chromium is used only for MetaMask wallet automation (start --wallet), because Playwright's Firefox can't script moz-extension:// pages; see "MetaMask wallet automation (Chromium)" above.

Running two agents at once

Each firecode server is isolated by instance. To run two Claude sessions against firecode on the same machine without collisions, set FIRECODE_INSTANCE per session:

FIRECODE_INSTANCE=projectA firecode browse main navigate "http://localhost:3000"
FIRECODE_INSTANCE=projectB firecode browse main navigate "http://localhost:4000"

Each instance gets its own Firefox process, port, auth token, and pages.

License

MIT — see LICENSE.

About

No description, website, or topics provided.

Resources

License

Stars

2 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors