@bacnh85/pi-web

extensionmaintained

Pi extension for web search, page extraction, Firecrawl scraping/crawling, Crawl4AI headless browser crawling, real-browser interaction (trusted click/type/evaluate via CDP), Gemini web-tier research, free upstream image generation (Gemini/ChatGPT web/Z.a

by — · v0.17.6 · published 1d ago

$ pi install npm:@bacnh85/pi-web
downloads/mo
0
stars
29
last push
18h ago
open issues
0

Signals

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

Download trend

No downloads in the last 12 weeks.

README

@bacnh85/pi-web

Pi extension for unified web search, content extraction, site crawling, page capture, Gemini web-tier research, free upstream image generation, and one-off gateway chat.

Auto-selects the best backend from SearXNG (self-hosted), Brave Search, Firecrawl, Crawl4AI, and agy (Gemini/Claude, when installed) — so agents don't have to know which backend to use. Search selection is adaptive: broad discovery prefers self-hosted SearXNG, while precision-sensitive searches and inline content prefer Brave. web_research adds AI-synthesized research with citations via your gemini.google.com session.

Install

pi install npm:@bacnh85/pi-web

Configuration

Environment lookup order:

  1. Process environment
  2. Current working directory .env.local
  3. Current working directory .env
  4. Pi global config ~/.pi/agent/.env.local
  5. Pi global config ~/.pi/agent/.env

Variables:

VariableRequiredDefaultNotes
BRAVE_API_KEYNo (1)—Brave Search API key
SEARXNG_BASE_URLNohttp://127.0.0.1:8888Self-hosted SearXNG
FIRECRAWL_API_URLNohttps://api.firecrawl.dev/v2Self-hosted or hosted
FIRECRAWL_TIMEOUT_MSNo60000Firecrawl request timeout (integer ≥ 1000)
FIRECRAWL_API_KEYNo (2)—Required for hosted Firecrawl
CRAWL4AI_API_URLNohttp://127.0.0.1:11235Self-hosted Crawl4AI
CRAWL4AI_API_TOKENNo (3)—Required if Crawl4AI auth enabled
CRAWL4AI_API_TIMEOUT_MSNo60000Crawl4AI request timeout (integer ≥ 1000)
GEMINI_WEB_SECURE_1PSIDNo (4)—__Secure-1PSID cookie from gemini.google.com — enables authed web_research (Deep Research)
GEMINI_WEB_PROXYNo—Proxy URL for Gemini web calls (escape hatch if Google blocks the IP)
GEMINI_WEB_SECURE_1PSIDTSNo (5)—Rotating __Secure-1PSIDTS cookie — bootstrap only; keep the source browser session closed so it isn't superseded (see "Keeping the session alive")
GEMINI_WEB_COOKIE_STORENo~/.pi/agent/gemini-web-cookies.jsonWhere the pasted/rotated cookie state persists (0600)
GEMINI_WEB_KEEPALIVENo (6)offSet 1 to opt in to experimental background cookie rotation (see "Keeping the session alive")
GEMINI_WEB_ROTATE_INTERVAL_MSNo600000Keepalive rotation cadence (min 60000)
ZAI_API_KEYNo (7)—Z.ai API key — enables the web_image zai provider (GLM-Image via the official api.z.ai); Z_AI_API_KEY also accepted
WEB_IMAGE_API_BASE_URLNo—web_image custom provider: any OpenAI-compatible images endpoint (e.g. https://api.openai.com/v1)
WEB_IMAGE_API_KEYNo—Bearer key for the custom endpoint
WEB_IMAGE_API_LABELNo—Display label for the custom endpoint (default: host name)
WEB_IMAGE_MIN_INTERVAL_MSNo5000Min interval between web_image calls per provider
WEB_IMAGE_DAILY_CAPNo20Daily soft cap for the Gemini web tier and ChatGPT web web_image providers (keyed APIs stay uncapped)
CHATGPT_WEB_AUTH_KEYNo—ChatGPT web tier: the OAuth tokens JSON from codex login (~/.codex/auth.json) or a bare access-token JWT. Unset → falls back to ~/.codex/auth.json, then Pi auth.json openai-codex
CHATGPT_WEB_CODEX_AUTHNo~/.codex/auth.jsonAlternative codex-login file path to read
CHATGPT_WEB_AUTH_STORENo~/.pi/agent/chatgpt-web-auth.jsonWhere rotated refresh tokens persist when the source can't be rewritten (0600)
CHATGPT_WEB_MODELNogpt-5.5Default web_chat model on the ChatGPT web surface
WEB_CHAT_API_BASE_URLNo—web_chat gateway provider: any OpenAI-compatible /chat/completions gateway (https://api.openai.com/v1, …)
WEB_CHAT_API_KEYNo—Bearer key for the web_chat gateway

(1) At least one search backend (SearXNG, Brave, or Firecrawl) must be configured for web_search. (2) Required for hosted Firecrawl; optional for self-hosted instances without auth. (3) Required for Crawl4AI v0.9+ default config. (4) Without it web_research mode=ask still works in guest mode (Flash-only); mode=research errors with setup steps. (5) Copy the current value from DevTools (Application → Cookies) alongside __Secure-1PSID; it rotates, so refresh it when auth degrades. (6) Off by default; experimental. Only arm it when instructed for a diagnosed dying session (see "Keeping the session alive"). (7) web_image's Gemini provider is currently gated server-side (browser-grade TLS fingerprint required — generation refuses over plain Node even with a valid cookie); zai activates when ZAI_API_KEY is present and is the reliable path, custom when WEB_IMAGE_API_BASE_URL is set.

Secrets are never printed; web_status reports only presence/source.

Always-on routing guidance

When any web_* tool is active, pi-web injects a condensed backend-selection protocol (SearXNG → Brave → Firecrawl ordering, Firecrawl precision/scrape caveats, source-citation rule) into the system prompt via a before_agent_start hook. This travels with the package — no edits to ~/.pi/agent/AGENTS.md are required — and carries zero overhead when pi-web is not loaded.

Tools

Shared per-call overrides (in addition to each tool's own params): Firecrawl-backed tools (web_map, web_crawl light) accept firecrawl_api_key / firecrawl_api_url; Crawl4AI-backed tools (web_extract full/agy, web_crawl full, web_screenshot, web_pdf) accept crawl4ai_api_url / crawl4ai_api_token; every tool accepts timeout_ms. These override the env configuration for that single call.

web_search — Unified search

Searches the web. Auto-selects backends adaptively: SearXNG for broad self-hosted discovery, Brave for precision-sensitive queries and include_content, Firecrawl as last resort.

web_search query="ansible podman quadlet" count=5
web_search query="ansible documentation" backend=brave count=10
web_search query="latest python release" engines="google,github"
web_search query="riven media" include_content=true

Parameters:

ParameterTypeDefaultDescription
querystring—Search query
countnumber5Number of results (backend-dependent cap: SearXNG 50, Firecrawl 100; Brave passed through verbatim)
freshnessstring—Time filter: pw, pm, py, or YYYY-MM-DDtoYYYY-MM-DD
countrystringUSTwo-letter country code
backendstringautoForce backend: auto, searxng, brave, firecrawl
enginesstring—SearXNG engine override, e.g. google,github
include_contentbooleanfalseFetch page content alongside results
content_charsnumber5000Max content chars per result
timeout_msnumberper-backendRequest timeout in ms (SearXNG/static 15000, Firecrawl/Crawl4AI 60000)

Auto-selection behavior:

  1. SearXNG — first for broad/general discovery, especially when engines is supplied.
  2. Brave — first for precision-sensitive queries (site:, quoted phrases, docs/API/source lookups, short proper-name queries) and whenever include_content is true. Requires BRAVE_API_KEY.
  3. Firecrawl Search — last resort. ⚠️ Poor semantic accuracy on domain-specific/ambiguous queries (e.g., "Riven" returns League of Legends results). Prefer SearXNG or Brave for precision.

Tool output includes search diagnostics showing attempted backends and the selected backend.

Use backend parameter to force a specific backend when needed.

web_extract — Unified content extraction

Extracts readable content from a URL. Auto-selects backend: static (JSDOM) → dynamic (Firecrawl) → full (Crawl4AI) → agy (model-backed), with extraction diagnostics showing fallback attempts.

web_extract url="https://docs.ansible.com/..."
web_extract url="https://riven.tv/" mode=static
web_extract url="https://example.com" mode=dynamic prompt="Extract pricing plans"
web_extract url="https://blocked.example.com" mode=agy

Parameters:

ParameterTypeDefaultDescription
urlstring—URL to extract
modestringautoauto, static, dynamic, full, or agy
promptstring—Prompt for JSON extraction (dynamic/agy modes)
schemaany—JSON schema for structured extraction (dynamic/agy modes)
content_charsnumber20000Max content chars
wait_fornumber—Milliseconds to wait for Firecrawl dynamic rendering. Crawl4AI /md full mode may ignore this.
mobilebooleanfalseEmulate mobile viewport (dynamic mode)

Mode behavior:

ModeBackendBest forAPI key needed
staticJSDOM+ReadabilitySimple static pages, blog posts, docsNo
dynamicFirecrawl ScrapeJS-rendered pages, dynamic contentMaybe
fullCrawl4AIJS-heavy SPA, complex renderingMaybe
agyagy (Gemini/Claude)Bot-protected / anti-AI-scraping pagesagy CLI installed
auto (default)static → dynamic → full → agyUnknown page typeMaybe

In auto mode, fallbacks are noted in the output (e.g., [Extraction fell back to Firecrawl Scrape (dynamic mode)]). If static extraction fails, the tool gracefully escalates to heavier backends.

⚠️ Note on Firecrawl Scrape: Fails on bot-protected sites (Ansible docs, many CDN-backed doc sites). Falls back to full mode (Crawl4AI) in auto mode, and to agy mode as a last resort.

agy mode (optional): Uses the Antigravity CLI with Gemini/Claude — its native read_url browser tool can fetch pages that block Firecrawl/Crawl4AI. Install with curl -fsSL https://antigravity.google/cli/install.sh | bash, authenticate once with agy, then auto mode falls back to it automatically. If agy is not installed, auto mode skips it silently; web_status reports agy.installed.

web_map — Site URL discovery

Discovers URLs from a site using Firecrawl Map. Best on base domains; may return fewer results on sub-paths.

web_map url="https://riven.tv"
web_map url="https://docs.example.com" sitemap=only

Parameters: url, limit (default 100), include_subdomains, search, sitemap, use_index, ignore_cache.

web_crawl — Site crawl

Crawls pages from a site. Two modes:

  • light (default): Firecrawl Crawl — conservative, docs-focused, single URL.
  • full: Crawl4AI Crawl — headless browser, rendered data, media, links, up to 100 URLs.
web_crawl url="https://docs.example.com" limit=10          # Firecrawl light mode
web_crawl urls=["https://a.com","https://b.com"] mode=full  # Crawl4AI full mode
web_crawl url="https://example.com" mode=light poll=true    # Poll for completion

web_screenshot — Page screenshot

Captures a full-page PNG screenshot using the Crawl4AI daemon, or local headless Chrome for localhost/LAN/file URLs (auto-detected; see Local capture). Returns the PNG inline as an image block (multimodal models see it); text summary includes engine/MIME/size.

web_screenshot url="https://example.com"
web_screenshot url="https://example.com" wait_for=5 wait_for_images=true
web_screenshot url="http://localhost:3000"           # local Chrome, auto-detected
web_screenshot url="http://localhost:3000" full_page=true width=1280
web_screenshot url="https://example.com" engine="daemon"  # force the daemon

Local-engine params: width (default 1280), height (default 800), full_page (captures a tall 8000px window — the Chrome CLI has no true full-page flag), reduced_motion (forces --force-prefers-reduced-motion — staggered page-load reveals otherwise screenshot as blank sections).

Honest mobile widths: headless Chrome clamps --window-size to 500px, so a width=390 capture would render at 500 and crop. web_screenshot routes width < 500 through CDP device-metrics emulation instead and reports the truth in text: Viewport: 390x844 (device-emulated) + Probe: scrollWidth X / innerWidth Y (scrollWidth > width ⇒ — CONTENT OVERFLOWS).

web_interact — real-browser interaction

Drives local headless Chrome over a zero-dependency CDP client (Node ≥22): open url, run steps in order, get per-step results, a final inline PNG, and a scrollWidth/innerWidth probe. One call = one browser lifecycle; steps stop at the first failure with the reason.

web_interact url="http://localhost:5173" viewport={width:390,height:844} reduced_motion=true \
  grant=["clipboard-read","clipboard-write"] \
  steps=[{click:"#copy"},{evaluate:"document.getElementById('status').textContent",label:"status"},
         {type:{selector:"#email",text:"a@b.co"}},{press:"Enter"},{wait_for:"[data-success]"}]
StepBehavior
{click: "selector"}Trusted CDP mouse click at the element center (scrolled into view) — user activation is granted, so execCommand('copy'), logins, and gated APIs behave like a real user
{type: {selector, text}}Focus the element, then insert text
{press: "key"}Enter, Tab, Escape, Backspace, Delete, arrows, Space, or a single character
{evaluate: "expr", label}JS expression; the resolved value is returned (awaitPromise on)
{wait_for: "selector" | ms}Poll for a selector (5s budget) or sleep ms
{dialog: "accept" | "dismiss"}The answer for the NEXT native dialog (confirm/alert/prompt/beforeunload); dialogs are auto-DISMISSED without it (destructive actions stay blocked) and every answer is reported on the step result, e.g. confirm("Delete?") → dismissed
{screenshot: true}Capture now; the last screenshot is returned inline

Options: viewport {width, height, device_scale_factor} (honest device-metrics emulation — the probe's scrollWidth > width means overflowing CSS), reduced_motion, grant (browser permissions), wait_for (settle seconds after load), timeout_ms (per-step budget, default 60s — a step stuck past it fails with the reason instead of hanging the call).

web_pdf — Page PDF

Generates a PDF document using the Crawl4AI daemon, or local headless Chrome for localhost/LAN/file URLs (auto-detected). Returns base64-encoded PDF.

web_pdf url="https://example.com/article"
web_pdf url="http://localhost:3000"   # local Chrome, auto-detected

Local capture

The Crawl4AI daemon's browser runs on the daemon host — it cannot reach (and SSRF-blocks) your localhost. pi-web therefore routes private URLs to a locally installed Chrome/Chromium in headless mode:

URLEngine
localhost, 127.0.0.1, LAN IPs (10/8, 172.16/12, 192.168/16, 169.254/16), file://local Chrome
public URLsCrawl4AI daemon
daemon SSRF-blocks a URLautomatic local-Chrome retry

Override with engine="local" / engine="daemon". Binary discovery: CHROME_PATH env, then standard Chrome/Chromium paths per OS (Edge as a Windows fallback). Captures use an isolated temp profile, a 30s timeout, and --virtual-time-budget for wait_for. web_interact uses the same Chrome discovery plus a CDP websocket session (--remote-debugging-port=0, target created over the websocket — not the /json/new HTTP endpoint).

web_status — Provider status

Shows all provider configuration status and Crawl4AI server health.

web_status

Typical output:

{
  "brave": { "apiKeyFound": true, "apiKeySource": "process.env" },
  "searxng": { "baseUrl": "http://127.0.0.1:8888", ... },
  "firecrawl": { "baseUrl": "http://127.0.0.1:3002/v2", ... },
  "crawl4ai": {
    "baseUrl": "http://127.0.0.1:11235",
    ...
    "health": { "status": "healthy", "version": "0.5.0", ... }
  },
  "agy": { "installed": true },
  "geminiWeb": { "configured": true, "cookieSource": "process.env", "proxy": false },
  "localChrome": { "path": "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" }
}

web_research — Gemini web-tier research

AI-synthesized research through your gemini.google.com session (powered by the gemini-reverse client, lazy-loaded).

web_research(query="compare the top 3 cloud providers' AI offerings", mode="research", timeout_ms=600000)
  • mode: "ask" (default) — quick grounded answer (Gemini auto-grounds with Google Search). Works without any cookie in guest mode (Flash-only). Sent as a temporary chat so your Gemini history stays clean.
  • mode: "research" — full Gemini Deep Research: plan → autonomous web browsing (minutes) → cited report. Requires the cookie and a fresh session. Runs a plan turn, a "Start research" confirm turn, then polls conversation turns until the report lands. Plan/confirm run even on degraded sessions, but report polling needs the live-session XSRF token — on stale sessions the tool returns an honest partial result (plan + transcript + note) instead of failing. Default timeout 600 s, cap 1 800 000.

Both modes return the text plus Sources — URLs extracted from the answer/report markdown (the web protocol exposes no structured citations field).

Setup (authed mode):

  1. Sign in at gemini.google.com.

  2. F12 → Application → Cookies → https://gemini.google.com.

  3. Copy the __Secure-1PSID value into ~/.pi/agent/.env.local:

    GEMINI_WEB_SECURE_1PSID=your-cookie-value
    # optional, if Google blocks your IP:
    # GEMINI_WEB_PROXY=http://host:port
    
  4. Restart pi; web_status shows geminiWeb.configured: true.

Paste from a fresh incognito login (sign in, copy both __Secure-1PSID and __Secure-1PSIDTS, close the window). Cookies copied from your daily browser are short-lived: Chrome's Device Bound Session Credentials caps them at a few hours, and an open Gemini tab keeps rotating the value under you.

Keeping the session alive

Live testing (2026-09-14) produced a result that reverses the earlier auto-rotation design: a __Secure-1PSIDTS obtained from Google's own RotateCookies endpoint is rejected by gemini.google.com's privileged surfaces (Deep Research returns no plan, image generation 403s), while the original pasted cookie keeps working indefinitely — as long as the browser session it came from doesn't rotate it again.

So the default recipe is:

  1. Harvest from a fresh incognito login (sign in, copy __Secure-1PSID + __Secure-1PSIDTS, close the window).
  2. Paste into ~/.pi/agent/.env.local, restart pi.
  3. Never open gemini.google.com in that Google session's browser — an open Gemini tab supersedes the pasted cookie within minutes (verified).

Under those conditions the pasted cookie stays valid for as long as the incognito session lives server-side (observed: 19+ hours of authed ask). web_status reports the cookie store under geminiWeb.cookieStore.

Auto-rotation is now opt-in (GEMINI_WEB_KEEPALIVE=1): it rotates via POST accounts.google.com/RotateCookies every 10 minutes and persists the result, but the rotated value is rejected by gemini's privileged surfaces — use it only if you accept losing Deep Research / image generation on that session. Rotation failures never delete your stored paste cookie.

Smoke the rotation directly (no prompt needed):

npx tsx extensions/scripts/gemini-smoke.ts x auth

Live verification script (also proves the header-cap patch end-to-end — an authed failure would surface HPE_HEADER_OVERFLOW):

npx tsx extensions/scripts/gemini-smoke.ts "test query"            # ask (authed or guest)
npx tsx extensions/scripts/gemini-smoke.ts "topic" research        # Deep Research

⚠️ Unofficial, at your own risk. Cookie auth uses your real Google session against gemini.google.com's internal web API and may not comply with Google's ToS; the protocol can break when Google changes it. ask mode errors map to actionable steps (expired cookie → re-copy; IP block → set GEMINI_WEB_PROXY).

Troubleshooting:

  • "session expired" — re-copy __Secure-1PSID + __Secure-1PSIDTS from a fresh incognito login. If this returns often, your daily browser is competing for the same session — keep using the incognito cookie and never open gemini.google.com there.
  • "unauthorized (400/401)" from rotation — the pasted generation was superseded (usually by the daily browser). Content calls may still work; rotation retries later. Your stored paste cookie is never deleted by this.
  • "temporarily blocked this IP" — set GEMINI_WEB_PROXY.
  • research mode returns a partial result ("report could not be retrieved") — the plan/confirm turns ran, but report polling needs a live-session token: re-copy __Secure-1PSID + __Secure-1PSIDTS from a fresh incognito login and retry. The report remains in your Gemini web history for the returned chat id. ask mode is unaffected.

web_image — free upstream image generation

Text → image with automatic provider fallback (all direct-to-upstream, no self-host services):

web_image(prompt="isometric cutaway of a container ship, technical illustration")
web_image(prompt="...", provider="zai")                       # pin GLM-Image via api.z.ai
web_image(prompt="...", model="glm-image", n=2, out_dir="/tmp/imgs")
web_image(prompt="...", provider="zai", size="960x1728")       # portrait aspect

size (zai/custom only) is passed through as WxH. glm-image enums: 1280x1280 (default), 1568x1056, 1056x1568, 1472x1088, 1088x1472, 1728x960, 960x1728 — portrait prompts should pick a portrait size, or the server default gives a square.

Provider chain (provider: "auto" tries in order; pin one to skip):

ProviderUpstreamAuthNotes
gemini (default)gemini.google.com web tiernone (guest) or GEMINI_WEB_SECURE_1PSIDcurrently refused to non-browser clients (server-side TLS-fingerprint gate, verified 2026-09-14) — zai is the working path
chatgptchatgpt.com/backend-api/codex/responses (Codex surface, ChatGPT subscription)CHATGPT_WEB_AUTH_KEY / codex login / Pi auth.jsonthe image_generation Responses tool — same gpt-image family as chatgpt.com/images/; bills the metered Codex-usage bucket
zaihttps://api.z.ai/api/paas/v4 (official API)ZAI_API_KEYGLM-Image (model default), fully ToS-compliant
customany OpenAI-compatible /images/generations endpointWEB_IMAGE_API_KEYe.g. official OpenAI https://api.openai.com/v1

Results are saved to out_dir (default: fresh temp dir) and returned as file paths plus inline image blocks (multimodal models see the render immediately). details reports the winning provider, model, and fallback attempts.

n (1–4) applies to the API providers (zai/custom, and chatgpt — one image per call, sequentially); the Gemini web tier returns its own image count (surfaced as a provider note when fewer than n).

Guardrails (soft, in-memory): per-provider WEB_IMAGE_MIN_INTERVAL_MS (default 5 s) and a WEB_IMAGE_DAILY_CAP (default 20/day, applied to the Gemini web tier and the ChatGPT web provider — the latter bills the subscription's metered Codex-usage bucket; keyed APIs are billed upstream and stay uncapped). Counters reset on restart; web_status.imageProviders.rate shows usage.

⚠️ ToS reality (read once): every AI chatbot's terms prohibit automated access to its web UI (Google, OpenAI, xAI "unauthorized automated or non-human means", Z.ai alike). This tool therefore follows a risk ladder:

  1. Official APIs (zai, custom) — fully compliant; prefer them when a key exists.
  2. Guest mode (Gemini without a cookie) — no account at stake, lowest risk, Flash-tier.
  3. Personal cookie (Gemini authed) — your own account, single session, low volume; same accepted-risk stance as the web bridges: use a burner/low-value account, never a valued one. No account pools, no commercial use, keep volume human-scale.

Smoke test: npx tsx extensions/scripts/gemini-smoke.ts "a red cube on white background" image (or … zai for the Z.ai path, … chatgpt-image for the ChatGPT web path).

web_chat — ChatGPT web / one-off gateway chat

Single non-streaming chat completion — via the ChatGPT web tier (your subscription, through the same backend-api/codex/responses surface the Codex CLI uses) or any OpenAI-compatible gateway. The in-session equivalent of "ask another model quickly" without switching your main provider:

web_chat(prompt="In one sentence: why is idempotency key needed here?")
web_chat(prompt="Summarize", provider="chatgpt", model="gpt-5.5", system="Be terse")
web_chat(prompt="Summarize", provider="gateway", model="gpt-5.3-mini")

ChatGPT web provider (provider: "chatgpt", the default when a credential is found): credential resolution order is CHATGPT_WEB_AUTH_KEY (the tokens JSON from codex login's ~/.codex/auth.json, or a bare access-token JWT) → CHATGPT_WEB_CODEX_AUTH/~/.codex/auth.json → Pi auth.json openai-codex. Expired tokens auto-refresh via auth.openai.com (rotated tokens persist back to the codex file, or to ~/.pi/agent/chatgpt-web-auth.json when the source is read-only). The literal chatgpt.com web UI is Cloudflare-Turnstile-gated and unreachable headless — this surface is the reachable headless path on the same subscription, and it bills the metered Codex-usage limits (the 5-hour/ weekly windows pi-sub displays), not the general chat quota. Image requests on the free plan typically answer 429 The usage limit has been reached.

Gateway provider (provider: "gateway") — configure once in ~/.pi/agent/.env.local, then restart pi:

WEB_CHAT_API_BASE_URL=https://api.openai.com/v1   # or any OpenAI-compatible gateway
WEB_CHAT_API_KEY=sk-...                           # if the gateway needs a key

web_status.webChat / web_status.chatgptWeb show configuration without printing secrets. Chat-only by design (no tool calling); for grounded research with sources use web_research, and /model switches your main model.

Smoke test: npx tsx extensions/scripts/gemini-smoke.ts x chatgpt-auth, … "reply pong" chatgpt.

Library structure

ModuleContents
lib/config.tsEnvironment loading, config helpers for all providers
lib/format.tsText sanitization, truncation, crawl/scrape result formatting
lib/content.tsReadable content extraction (JSDOM + Readability + Turndown)
lib/retry.tsRetry with exponential backoff for transient HTTP failures
lib/brave.tsBrave Search API fetch client (internal)
lib/searxng.tsSearXNG metasearch fetch client (internal)
lib/firecrawl.tsFirecrawl API fetch client (internal)
lib/crawl4ai.tsCrawl4AI Docker API fetch client (internal)
lib/chrome.tsLocal headless Chrome capture for localhost/private/file:// URLs the Crawl4AI daemon can't reach
lib/cdp.tsMinimal Chrome DevTools Protocol client over Node's native WebSocket — powers web_interact
lib/chatapi.tsGeneric OpenAI-compatible chat client for web_chat (custom gateway endpoint)
lib/chatgpt.tsDirect ChatGPT web-tier client — chat + image generation via the Codex backend surface
lib/gemini.tsGemini web-tier research backend, cookie-authed via __Secure-1PSID
lib/gemini-auth.tsGemini web cookie auto-refresh — rotates __Secure-1PSIDTS on a 10-min keepalive
lib/gemini-dr.tsPure-Node Deep Research client for gemini.google.com (plan → confirm → poll)
lib/imageapi.tsGeneric OpenAI-compatible images client + fallback chain for web_image (zai, custom)
lib/agy.tsagy (Antigravity CLI) spawn helper — read_url extraction via Gemini/Claude
lib/search.tsUnified search orchestrator — probes backends, fallback chain
lib/extract.tsUnified extraction orchestrator — mode-based backend selection

Migration from 0.3.x

v0.4 replaces the 14 individual backend-specific tools with 7 unified tools:

v0.3 toolv0.4 replacement
brave_searchweb_search with backend: "brave"
searxng_searchweb_search with backend: "searxng"
firecrawl_searchweb_search with backend: "firecrawl"
web_contentweb_extract with mode: "static"
firecrawl_scrapeweb_extract with mode: "dynamic"
crawl4ai_scrapeweb_extract with mode: "full"
firecrawl_mapweb_map (same behavior)
firecrawl_crawlweb_crawl with mode: "light"
crawl4ai_crawlweb_crawl with mode: "full"
crawl4ai_stream(removed — use web_crawl with mode: "full")
crawl4ai_screenshotweb_screenshot (same behavior)
crawl4ai_pdfweb_pdf (same behavior)
crawl4ai_statusMerged into web_status
web_statusweb_status (enhanced with Crawl4AI health)

All v0.3 tool names were removed in v0.4. Update any agent instructions or skills that reference the old names.

Changelog

See CHANGELOG.md for release history.

Development

# Run all tests
npm test