browse97

extensionmaintained

Chrome browser automation for pi coding agent via CDP. Open tabs, snapshot, click, fill forms, upload files, evaluate JS.

by · v1.0.3 · published 2mo ago

$ pi install npm:browse97
downloads/mo
0
stars
1
last push
2mo ago
open issues
0

Signals

license: MITtestspi manifest: missinginstall size: —deps: 0peer deps: 0

Download trend

515 downloads · last 12 weeks (weekly)

README

browse97

Chrome browser automation for pi coding agent. Control Chrome via Chrome DevTools Protocol (CDP) — open tabs, snapshot pages, click elements, fill forms, upload files, evaluate JavaScript.

No external dependencies. Uses Node.js built-in WebSocket.

Setup

Launch Chrome with remote debugging:

google-chrome-stable --remote-debugging-port=9222 --no-first-run --user-data-dir=data/chrome-profile &

Custom port via environment variable:

BROWSE97_PORT=9223 google-chrome-stable --remote-debugging-port=9223 ...

Install

pi install git:github.com/nerkn/browse97

Tools

ToolDescription
browse97_startOpen a new browser tab and connect to it. Call this first.
browse97_alltabsList all open tabs (id, url, title)
browse97_switchtabConnect agent to an existing tab by id
browse97_navigateNavigate the owned tab to a URL
browse97_snapshotList interactive elements. Scope with container, filter with filter
browse97_clickClick by CSS selector or visible text
browse97_fillFill multiple form fields by fuzzy matching name/id/placeholder/label
browse97_uploadUpload file to a file input (blob injection for HTTPS)
browse97_evalEvaluate JavaScript in the browser page context
browse97_waitWait for an element or text to appear

Ownership Model

  • browse97_start creates a new tab → your pi session owns it
  • All other tools operate only on your owned tab
  • Tab stays open in Chrome after session ends
  • Each pi session gets its own tab — safe for multi-project use

Usage Examples

Open google.com and search:
  → browse97_start({url: "https://google.com"})
  → browse97_snapshot({filter: "input,button"})
  → browse97_fill({fields: {"q": "pi coding agent"}})
  → browse97_click({target: "Google Search"})

Fill a job application form:
  → browse97_start({url: "https://apply.example.com"})
  → browse97_snapshot({container: ".application-form"})
  → browse97_fill({fields: {"first_name": "Erkin", "last_name": "Tek", "email": "me@example.com"}})
  → browse97_upload({selector: "#cv", file: "assets/CV.pdf"})
  → browse97_click({target: "Submit"})

Extract data from a page:
  → browse97_eval({expression: "[...document.querySelectorAll('.job-card')].map(c => c.textContent.trim()).join('\\n')"})

Connect to an already-open tab:
  → browse97_alltabs({})
  → browse97_switchtab({id: "ABC123"})
  → browse97_snapshot({})

Wait for dynamic content:
  → browse97_wait({target: ".results-loaded", timeout: 5000})

snapshot Parameters

ParameterExampleDescription
(none){}All interactive elements on page
container{container: ".main"}Scope to elements inside .main
filter{filter: "a,button"}Only show links and buttons
both{container: ".form", filter: "input,select"}Inputs and selects inside .form

click Auto-Detection

  • Starts with . # [ ( → CSS selector
  • Contains > + ~ * → CSS selector
  • Otherwise → matches visible text of buttons, links, submit inputs

fill Fuzzy Matching

Field keys are matched case-insensitively against: name, id, placeholder, aria-label, associated <label> text.

browse97_fill({fields: {"email": "x@y.com"}})
  → matches: name="email", id="email", placeholder="Email address", label "Email"

File Upload on HTTPS

DOM.setFileInputFiles only works on file:// pages. browse97 works around this by injecting files as blobs via DataTransfer.

Limitation: Files set via blob don't survive native form submission (browser security).

Workaround: Use browse97_eval with FormData + fetch() for form submission that includes files:

const form = document.querySelector('form');
const formData = new FormData(form);
// blob already injected by browse97_upload
const response = await fetch(form.action, { method: 'POST', body: formData });

Your Context is Safe

browse97_snapshot and browse97_eval truncate results at 5000 characters by default. When truncated, a message suggests scoping with container/filter or passing allowWhole: true.

snapshot({allowWhole: true})                          // no truncation
browse97_eval({expression: "...", allowWhole: true})  // no truncation

License

MIT