@itc-steve/pi-web-complete

Pi extension: multi-backend web_search, local web_read with query-ranked excerpts, and web_cowork shared browser control.

Packages

Package details

extension

Install @itc-steve/pi-web-complete from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:@itc-steve/pi-web-complete
Package
@itc-steve/pi-web-complete
Version
5.0.0
Published
Nov 3, 2026
Downloads
492/mo · 151/wk
Author
itc-steve
License
MIT
Types
extension
Size
299.3 KB
Dependencies
9 dependencies · 4 peers
Pi manifest JSON
{
  "extensions": [
    "./src/index.ts"
  ],
  "changelogPath": "./CHANGELOG.md"
}

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

README

pi-web-complete

Give Pi one extension for web discovery, clean local extraction, browser interaction and debugging, and version-current framework docs.

pi install npm:@itc-steve/pi-web-complete

Upgrading to v5

Version 5.0.0 requires Pi 1.5.0+ and Node.js 22.19.0+. It replaces CloakBrowser with stock Playwright Chromium and changes codemode results from Markdown strings to structured objects.

  • /reload every Pi agent, then close/reopen browser sessions. Reloading the browser owner closes its shared browser.
  • The new default profile is ~/.pi/web-cowork/profile; log in again. Explicit profile paths still apply, and old profiles are never copied or deleted.
  • Update codemode scripts for the structured result shapes. Direct tool text remains available.
  • Stock Chromium does not provide CloakBrowser's stealth/fingerprint guarantees.

See 5.0.0 release notes for the full migration details.

One extension, four jobs

Tool Use it for What makes it useful
web_search Current facts and discovery Brave, Serper, Tavily, Exa, and Linkup with shuffled fallback
web_read Reading a URL Local extraction with query-ranked excerpts by default; web_fetch alias included
web_cowork Browser interaction and debugging External-window or headless Playwright Chromium with shared control, DevTools inspection, Chrome device-mode emulation, and raw CDP
context7 Library and framework APIs Version-current documentation, registered only when configured

web_search finds the page. web_read turns it into focused context. web_cowork handles pages that need a person or browser UI. context7 keeps implementation work grounded in current docs.

Quick start

1. Install

Requires Pi 1.5.0 or newer and Node.js 22.19.0 or newer. postinstall runs playwright install chromium (playwright ^1.64.1).

pi install npm:@itc-steve/pi-web-complete

From a local checkout:

pi install /path/to/pi-web-complete

If npm skipped or blocked lifecycle scripts, run this from the installed extension directory:

npx playwright install chromium

After upgrading, /reload Pi and close/reopen any live cowork or browser-rendered read session.

2. Configure

Configuration and secrets live in separate files. For a new setup (do not overwrite existing configuration or keys):

mkdir -p ~/.pi/agent
cp /path/to/pi-web-complete/web.json.example ~/.pi/agent/web.json
cp /path/to/pi-web-complete/web.env.example ~/.pi/agent/web.env
chmod 600 ~/.pi/agent/web.env
# enable at least one backend in web.json; uncomment its key line in web.env

The example config starts with every search backend disabled. Enable only providers you have keys for; the shorter example below enables all five, but auto mode skips providers without a key. web_read and web_cowork need no API key. Context7 is optional.

~/.pi/agent/web.json:

{
  "defaultBackend": "auto",
  "allowPrivateHosts": [],
  "search": { "enabled": true },
  "cowork": { "enabled": true },
  "backends": {
    "brave":  { "enabled": true, "apiKeyEnv": "BRAVE_API_KEY" },
    "serper": { "enabled": true, "apiKeyEnv": "SERPER_API_KEY" },
    "tavily": { "enabled": true, "apiKeyEnv": "TAVILY_API_KEY" },
    "exa":    { "enabled": true, "apiKeyEnv": "EXA_API_KEY" },
    "linkup": { "enabled": true, "apiKeyEnv": "LINKUP_API_KEY", "depth": "standard" }
  },
  "context7": { "enabled": true, "apiKeyEnv": "CONTEXT7_API_KEY" }
}

~/.pi/agent/web.env:

BRAVE_API_KEY=replace-with-brave-key
SERPER_API_KEY=replace-with-serper-key
TAVILY_API_KEY=replace-with-tavily-key
EXA_API_KEY=replace-with-exa-key
LINKUP_API_KEY=replace-with-linkup-key
CONTEXT7_API_KEY=replace-with-context7-key

GITHUB_TOKEN (or GH_TOKEN) is optional for GitHub issue/PR reads. Supply it in the environment of the process that starts Pi (for example, through your shell or secret manager); GitHub reads do not load tokens from web.env. No token is needed for ordinary public pages.

Project overrides can live in .pi/web.json and .pi/web.env. Project JSON overrides global top-level settings while backends merge by backend name. Nested objects such as read, cowork, context7, and individual backend entries are replaced, not deeply merged; include all settings you want to retain. Project secrets overlay global secrets. Keep search/Context7 keys in web.env, never JSON, and exclude secret files from version control. allowPrivateHosts and cowork.extensionPaths are accepted only from the preferred global web.json.

3. Use

web_search({ query: "Node.js fetch timeout patterns", compact: true })
web_read({ url: "https://example.com/guide", query: "authentication setup" })
web_cowork({ action: "open", url: "https://example.com/login" })
context7({ library: "next.js", query: "app router middleware auth" })

Configuration reference

See web.json.example and web.env.example. These are defaults when a setting is omitted, not necessarily the values chosen in the example:

Setting Default / behavior
defaultBackend, numResults, compact "auto", 10 (1–20), false
showStatus true; show used services in the footer
search.enabled, cowork.enabled true; initial tool registration defaults
allowPrivateHosts []; exact hostnames, global-only
backends.<name>.enabled, apiKeyEnv Opt in and name the environment variable containing its key
backends.<name>.timeout, maxResults 30000 ms (1000–120000); optional result cap (1–20), otherwise the requested numResults
backends.linkup.depth "standard"; optionally "deep"
read.defaultMode, defaultFormat, defaultReturn "auto", "markdown", "excerpts"
read.onlyMainContent, removeImages true; images stripped for chat, retained in saved files unless explicitly overridden
read.maxChars, excerptMaxChars Full chat defaults to 12000 characters; excerpts to 6000. Saves are unlimited unless the call passes maxChars.
read.maxBytes, timeoutSeconds, headless 5242880 bytes (2 MB floor), 30 seconds (5–300), true
context7.enabled, apiKeyEnv, timeout, fast Enabled when a key resolves; name its key variable; 30000 ms; false
cowork.userDataDir, downloadDir ~/.pi/web-cowork/profile, ~/Downloads
cowork.headless Visible on desktops; headless on Linux without a display. Omit for automatic detection.
cowork.extensionPaths []; trusted unpacked MV3 directories, global-only; close/reopen to apply

read.timeoutSeconds is in seconds; backend and Context7 timeout values are in milliseconds. Browser profile, extension, and visibility settings apply when creating a session. See runtime behavior for installation and restart guidance.

Toggle web_search / web_cowork per session

Both tools default to enabled unless disabled in configuration. Pi's explicit CLI tool selections and restored session/branch selections take precedence. Toggle either tool with the /web command:

/web                # status: search / cowork / shared browser
/web search off     # hide web_search until re-enabled
/web cowork         # flip web_cowork on or off
/web                # status again

A bare target flips the current state; on / off set it explicitly. Disabling web_cowork hides the tool but leaves any open shared browser session running. Pi preserves tool selections on resume, reload, and branch navigation; set the initial registration defaults in web.json:

{
  "search": { "enabled": false },
  "cowork": { "enabled": false }
}

Both default to true when absent. Defaults do not re-enable tools explicitly disabled by the user.

Read pages without flooding context

web_read acquires the full page locally, then returns only the most relevant chunks for the query. Automatic mode escalates through fast HTTP, TLS-fingerprint fetch, alternate links, Readability, and Playwright Chromium only when earlier paths are blocked or too sparse. A confirmed block (401/403/429/503 or cf-mitigated) lifts a per-host floor for the rest of the session so later reads of that host skip the failed tier. Residual challenge pages are reported as blocked, not returned as content. It also follows short meta-refresh redirects up to five hops.

web_read({ url, query: "HTTP caching Cache-Control" })
  • Default: ranked excerpts with a roughly 6k-character budget.
  • No query: compact page outline.
  • return: "full": main content with a default 12k-character chat budget; override with maxChars.
  • savePath or saveDir: full extract goes to disk; chat and structured results receive only a short summary/preview. Paths accept ~/ and an optional leading @. Writes share Pi's per-file mutation queue; existing files are still overwritten.
  • mode: "browser": force Playwright Chromium rendering.
  • PDFs: text extraction when the file has a text layer. Scanned PDFs stay a short placeholder (no OCR).
  • Dead links: archive=auto (default) tries the nearest Wayback snapshot and labels its date. archive=never skips. Lookup failures (including HTTP 429 rate limits) produce a warning alongside the original result/error; retry later or disable archive lookup.
  • stitch=true joins same-origin rel=next pages (max three extra) with part markers.
  • GitHub issues and pull requests: clean bodies and comments through GitHub REST API; optional process-environment GITHUB_TOKEN or GH_TOKEN raises limits and enables private repositories.
  • Metadata when available: author, publication date, site, and language.
  • maxBytes: download cap with a 2 MB floor and 5 MB default; oversized bodies truncate instead of failing.
web_read({ url, mode: "browser", saveDir: "~/vault/http-caching" })

URLs are restricted to HTTP(S). Requests to localhost, private IP ranges (including IPv4-mapped IPv6), and common internal or metadata hostnames are refused by default. This check is hostname-level and does not resolve DNS (127.0.0.1.nip.io is not treated as loopback). To test a trusted local app, explicitly allow its exact hostname (not a URL or wildcard) in global ~/.pi/agent/web.json:

{ "allowPrivateHosts": ["localhost", "127.0.0.1"] }

The allowlist applies to initial URLs and redirects in both web_read and web_cowork. Project config cannot relax it. Only allow hosts you trust because pages can access services on every port permitted by the URL guard.

Search with fallback

Auto mode shuffles enabled backends that have resolvable keys. Empty results and provider failures move to the next backend; aborts stop immediately.

  • Pin a provider with backend: "brave", "serper", "tavily", "exa", or "linkup".
  • Use compact: true for title-and-URL results while exploring.
  • Limit numResults from 1 to 20.
  • Set defaults in web.json.

Key resolution order for every apiKeyEnv:

  1. process.env[apiKeyEnv]
  2. .pi/web.env
  3. ~/.pi/agent/web.env
  4. Legacy literal apiKey in JSON

Legacy JSON paths remain supported when the new paths are absent: ~/.pi/agent/extensions/search.json and .pi/search.json.

Work together in an external or headless browser

web_cowork keeps one persistent Playwright Chromium session open. Default mode is an external desktop window shared by agent and user; Linux without DISPLAY/WAYLAND_DISPLAY defaults to headless. Set headless: true for automation. Explicit headless: false without a display returns an actionable error (no virtual display is created). Headless is create-time only: a live session is reused even if a later open passes a different flag. Close first to switch. wait needs a visible window. Use web_read for one-shot extraction.

web_cowork({ action: "open", url: "https://example.com/login" })
web_cowork({ action: "wait", message: "Log in, then continue" })
web_cowork({
  action: "batch",
  fills: [
    { ref: "@e3", text: "name@example.com" },
    { ref: "@e4", text: "hello" }
  ],
  clickRef: "@e5"
})
web_cowork({ action: "close" })

State-changing actions return fresh, bounded interactive refs. snapshot supports interactive, content, and both modes. Password and secret-looking values appear as [redacted].

Persist sessions and choose a download directory with:

{
  "cowork": {
    "userDataDir": "~/.pi/web-cowork/profile",
    "downloadDir": "~/Downloads",
    "headless": false
  }
}

The default profile is a fresh directory at ~/.pi/web-cowork/profile. Explicit cowork.userDataDir is honored as written. This upgrade never inspects, copies, or deletes older CloakBrowser profiles (for example under ~/.cloakbrowser/); that data stays untouched. Fresh defaults require logging in again. If an existing config still points at an old path and you want the new default, change that setting yourself.

Agents using the same cowork.userDataDir automatically attach to the existing browser through status, pages, open, or any page action. No separate attach command. Different profiles remain separate. After upgrading, /reload Pi in each agent and close/reopen the old browser once.

The launching agent owns browser lifetime; keep it running. Pi shutdown events (quit, reload, or session replacement) release this agent's browser resources: an owner closes its browser, while an attached guest only disconnects. Coordinate owner reloads/session switches with other agents. Explicit action=close closes the browser for everyone. Tab selection, refs, and captured DevTools buffers are agent-local: run pages → select → snapshot in each agent, and coordinate page mutations. Do not pass @eN refs between agents. Emulation labels track changes made by that agent, not changes from peers.

Cross-agent regression check (isolated temporary profile): npx tsx test/cowork-sharing.check.ts.

Downloads default to ~/Downloads and apply to cowork sessions and browser-rendered reads.

Trusted local browser extensions

In global ~/.pi/agent/web.json only, add unpacked Manifest V3 directories:

{
  "cowork": {
    "extensionPaths": ["/absolute/path/to/extension"]
  }
}

Project and legacy configs cannot enable browser extensions. Paths must be absolute (or ~/), contain no commas/control characters, and include a Manifest V3 manifest.json. Maximum eight directories. This is a create-time option: close/reopen cowork after changing it; when updating pi-web-complete code, /reload Pi first.

This removes Chromium's extension blocker and restricts loading to the named extensions. Extension service-worker IDs are remembered so their own local scripts/styles can load. Website service workers remain blocked, and normal website request checks remain unchanged. MV3 extensions without a background worker have not been verified. Native side-panel opening still requires a browser user gesture; activeTab still requires clicking the extension action on the intended tab.

Only enable trusted code: extension APIs/background networking are privileged and are not fully sandboxed by cowork's page URL guard. No credentials are copied into extensions by this option. Existing live windows are never restarted automatically. Browser-specific sideload restrictions still apply.

Extension loading is covered by a deterministic self-contained check (npx tsx test/cowork-extensions.check.ts; isolated temporary profile). It does not certify native side-panel placement or device command execution.

Developer actions expose console and network capture, JavaScript evaluation, screenshots, accessibility trees, tab selection, Chrome device-mode emulation, and raw Chrome DevTools Protocol on page or browser targets. Cowork opens an ephemeral loopback-only CDP port for cross-agent attachment and publishes discovery metadata with owner-only file permissions. CDP has no authentication: use only on a trusted local machine; never forward or expose this port. The launching agent retains URL guarding for attached agents. Blocked: Fetch interception, Target create/attach/close, and Browser/Page crash/close (case-insensitive). Raw CDP can still read cookies and run Runtime.evaluate against the persistent profile.

Mobile vs desktop

Cowork defaults to desktop Chrome. Resizing the window is not mobile: desktop Chromium ignores viewport meta, keeps (hover: hover) / (pointer: fine), and sends a desktop UA. action=emulate is Chrome DevTools device mode:

open and emulate report the observed CSS viewport, not just the preset. Screenshots use the same CDP session as emulation so capture cannot restore stale desktop metrics. Device labels follow the active tab; new tabs start desktop. A page without responsive viewport metadata can still have a wide mobile layout. Chrome Android emulation is not physical iPhone/Safari evidence.

Real-browser regression check: npx tsx test/cowork-browser.check.ts (requires Playwright Chromium; asserts CSS/media-query widths before/after screenshots and desktop restore).

  • device=mobile — Pixel 7 metrics with mobile:true (viewport meta, overlay scrollbars, text autosizing), touch events, and a mobile UA plus Sec-CH-UA-Mobile. Reloads so the server sees the new UA.
  • device=desktop — restore. Reloads.
  • Optional device on open so the first load is already mobile.

Local apps need the host in allowPrivateHosts. Then:

web_cowork({ action: "open", url: "http://127.0.0.1:3000" })
web_cowork({ action: "screenshot" })
web_cowork({ action: "emulate", device: "mobile" })
web_cowork({ action: "screenshot" })
web_cowork({ action: "emulate", device: "desktop" })

Screenshots return a saved PNG path and inline image content, so Pi and the model can view them without a separate file read. Files are written under ~/.pi/web-cowork/shots. In codemode, web_cowork returns a { content, details } envelope; pass its image blocks to image() or models.classify({ images }).

This matches Chrome's device toolbar, not a real phone (GPU, software keyboard, 100vh URL bar, iOS Safari/WebKit). status reports the current Device.

Get current library docs

context7 resolves a plain package name or accepts a Context7 library ID, then returns documentation ranked for the task. Get an API key at context7.com/dashboard.

context7({ library: "next.js", query: "app router middleware auth" })
context7({ library: "/vercel/next.js/v14.3.0", query: "server actions form validation" })
  • Registered when a Context7 key resolves, including project configuration discovered at session start.
  • IDs can be version-pinned with /v14.3.0 or @v14.3.0.
  • Results are capped at 12k characters; narrow the query when truncated.
  • fast: true skips LLM reranking for lower latency and lower relevance.
  • Set "enabled": false to keep the key configured while hiding the tool.

Pi tool integration

Search, read, and Context7 results expose output schemas and structured values to codemode, while direct calls retain their text output. Existing codemode scripts must handle objects instead of Markdown strings: search returns results, Context7 returns text, and cowork returns { content, details }.

Read results use ok: successful reads include content, or savePath/preview when saving; guarded/deadline failures return { ok: false, error }. Other failures reject the tool call. Read excerpts and documentation stay bounded; saving a page never returns its full saved body through structured output.

Tool rows use compact summaries, elapsed time, and Pi's configured expand key; expand a result to see its text. web_fetch remains an active compatibility alias but no longer duplicates web_read's prompt guidelines.

Search and Context7 declare read-only annotations. Read/save and cowork retain potentially-destructive hints because they can overwrite files or change browser/application state; annotations are not a sandbox.

Tool reference

Tool Parameters
web_search query, numResults, backend, compact
web_read / web_fetch url, query, return, mode, format, onlyMainContent, maxChars, maxBytes, headless, savePath, saveDir, archive, stitch
web_cowork action, dialog, dialogText, url, mode, ref, role, name, selector, text, value, clear, fills, clickRef, key, deltaY, query, maxChars, message, timeoutMs, headless, pageIndex, expression, method, cdpParams/params, target, filter, fullPage, device
context7 library, query, fast

Cowork actions

Action Purpose
open, navigate Open or move the shared browser and return fresh refs
emulate Chrome device mode (device=mobile / device=desktop). Default desktop. Not a window resize. Optional device on open.
wait Pause for user input, then return optional note and fresh refs
snapshot Read interactive refs, content, or both
click, type, press, scroll Act on the latest ref; role and name are fallbacks
batch Fill 1–10 fields, then optionally click once
console, network Drain captured developer events; optional substring filter
evaluate, screenshot, a11y Inspect page runtime, pixels, or accessibility tree
pages List tabs and their zero-based indexes
select Switch tabs with pageIndex, or set a native <select> with a target ref/role/name/selector plus text and/or value
cdp Send raw CDP method and parameters to page or browser target
status, close Inspect or end the session

JS dialogs

JS dialogs are handled immediately, never held for a later call. Default: dismiss. beforeunload defaults to accept during open, navigate, and close. Override for one call with dialog: "accept" or dialog: "dismiss"; dialogText supplies an accepted prompt's answer (omitted: its default value). Results report what happened, for example:

Dialog: confirm "Delete file?" → dismissed

status repeats this agent's last dialog. To proceed after a dismissed confirm, repeat the triggering action with dialog: "accept" and a current ref. Custom DOM modals still use ordinary clicks; there is no action=dialog.

Native dropdowns

web_cowork({ action: "select", pageIndex: 0 })
web_cowork({ action: "select", ref: "@e3", text: "Vanilla" })
web_cowork({ action: "select", ref: "@e4", value: "chocolate" })

Use refs from the latest result. text alone matches an option's label or value; value matches its exact value. If both are supplied, both must match the same option. One option per call; multi-option selection is not supported. Do not combine pageIndex with dropdown arguments. Both forms return fresh refs.

Snapshots show the selected label/value and option count, plus labels for lists of eight or fewer options. A non-native target returns: Not a native <select>. Click an option ref or type into the combobox. Custom ARIA comboboxes/listboxes still use click/type.

Iframe controls

Interactive snapshots automatically include same- and cross-origin iframe controls when accessible, indented beneath the iframe ref:

@e3 iframe "checkout" (src=https://example.com/checkout)
  @e4 textbox "Card number"
  @e5 button "Pay"

Use those child refs directly for actions; no action=frame is needed or available. Role/name/CSS fallbacks search only the main frame. Content snapshots and evaluate also stay in the main frame.

Collection is capped at 120 refs across frames and three iframe levels. Empty or inaccessible frames retain their iframe row with no interactive controls. Refresh the snapshot after page/frame changes; refs remain agent-local.

Runtime behavior

  • Pi 1.5.0+ and Node.js 22.19.0+ are required.
  • Browser work uses stock Playwright Chromium (playwright ^1.64.1). It has no stealth or fingerprint-seed guarantees; challenge and anti-bot behavior can differ from CloakBrowser.
  • Default cowork profile: ~/.pi/web-cowork/profile. Screenshots: ~/.pi/web-cowork/shots. Explicit cowork.userDataDir wins. Older CloakBrowser profiles are left untouched (never inspected, copied, or deleted).
  • After upgrading: /reload Pi and close/reopen cowork or browser-rendered read sessions.
  • postinstall runs playwright install chromium. If scripts were skipped or blocked by npm lifecycle approval, run npx playwright install chromium from the installed extension directory.
  • Missing system libraries: Playwright documents npx playwright install-deps chromium (Debian/Ubuntu). Advisory only — this package does not install OS packages. Other distros may warn that the OS is unsupported.
  • Status chip stays empty until a service is used. Successful providers accumulate as one sorted line below the editor; active reads and cowork share that same line.
  • Set "showStatus": false to disable footer updates.
  • Set "read": { "headless": false } or pass headless: false to show browser-rendered one-shot reads.

Development

npm ci
npm test
npm run typecheck
npm run test:browser

Development uses Pi 1.5.0's actual SDK types rather than local ambient substitutes. Host-provided Pi packages remain peer dependencies, not bundled runtime dependencies.

Use npm test (Node + tsx), not bun test: HTTP regression mocks rely on Undici's Node dispatcher.

npm run test:browser runs every test/*.check.ts against installed Playwright Chromium. Checks use isolated temporary profiles and never read, copy, or delete real user browser data. They include test/cowork-browser.check.ts, test/cowork-forms.check.ts, test/cowork-sharing.check.ts, a deterministic self-contained test/cowork-extensions.check.ts, and test/read-browser.check.ts.

Releases

Keep package.json, package-lock.json, and the dated CHANGELOG.md entry on the same version. Run the checks above plus npm audit and npm pack --dry-run before pushing.

Pushing a v<version> tag triggers the release workflow: it verifies the version, runs tests/typechecking, publishes to npm, and creates a GitHub Release from the changelog. Pushing main alone does not publish.

License

MIT