project
AI Browser Fetch
used in prodA drop-in browser fetch for AI agents.
overview
AI agents (Claude Code, Cursor, Cline) all eventually hit pages where their built-in fetch returns nothing useful. JS-rendered SPAs, React apps, pages behind anti-bot checks. Standard curl or fetch() just returns "Please enable JavaScript." I built this while integrating TikTok's Marketing API at work. Their docs are JS-rendered and partially iframed, so standard fetching returned nothing. It started as a local script. I turned it into a proper open-source tool when I realized it solved a general problem. It's published on npm as ai-browser-fetch and installable via npx.
the problem
When an AI agent fetches a URL with a plain HTTP request, it only gets what the server sends initially. For most modern sites that's just an HTML skeleton. The actual content loads client-side via JavaScript, sometimes inside iframes. This became a real problem while building the TikTok lead integration. The API reference pages were unreadable without a real browser, so I couldn't use AI tools to help with research effectively.
how it works
Playwright launches a headless Chromium instance, navigates to the URL, and waits for the page to finish loading. Content is extracted as plain text, markdown (via Mozilla Readability), or JSON depending on flags. Exit codes are documented as part of the API surface so agents can branch on shell status, not just stdout. Heavy dependencies load lazily, only when the relevant flag is passed, to keep startup time low for agent tool calls.
technical decisions
Kept stealth mode off by default. Lighter startup, and stealth itself can trigger detections. Used Readability for markdown extraction with an innerText fallback since Readability fails on dashboards and SPAs. JSON error responses omit text and length fields so errors don't look like successes to the agent. Modular structure under lib/ with fetch.js as the entry point kept the code maintainable across 5 shipped phases.
challenges
networkidle hung on local test servers with no ongoing requests. Fixed by defaulting to load and making networkidle opt-in. Cross-process localhost was unreachable from Playwright's Chromium subprocess. Fixed by switching happy-path tests to use https://example.com. npm publishing changed substantially in 2026. Classic tokens and TOTP removed for new accounts. Published via GitHub Actions with a bypass-2FA granular token, then set up OIDC trusted publishing for future releases.
what i learned
Browser automation has non-obvious failure modes that only appear in real-world testing, not unit tests. Exit codes are underused. Documenting them as part of the API surface makes tools significantly more useful for programmatic callers. Positioning it as a drop-in replacement is more compelling than calling it a wrapper around Playwright.