@itc-steve/pi-web-complete
Pi extension: multi-backend web_search, local web_read with query-ranked excerpts, and web_cowork shared browser control.
Package details
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.
/reloadevery 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 withmaxChars.savePathorsaveDir: 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=neverskips. Lookup failures (including HTTP 429 rate limits) produce a warning alongside the original result/error; retry later or disable archive lookup. stitch=truejoins same-originrel=nextpages (max three extra) with part markers.- GitHub issues and pull requests: clean bodies and comments through GitHub REST API; optional process-environment
GITHUB_TOKENorGH_TOKENraises 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: truefor title-and-URL results while exploring. - Limit
numResultsfrom 1 to 20. - Set defaults in
web.json.
Key resolution order for every apiKeyEnv:
process.env[apiKeyEnv].pi/web.env~/.pi/agent/web.env- Legacy literal
apiKeyin 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 withmobile:true(viewport meta, overlay scrollbars, text autosizing), touch events, and a mobile UA plusSec-CH-UA-Mobile. Reloads so the server sees the new UA.device=desktop— restore. Reloads.- Optional
deviceonopenso 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.0or@v14.3.0. - Results are capped at 12k characters; narrow the query when truncated.
fast: trueskips LLM reranking for lower latency and lower relevance.- Set
"enabled": falseto 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. Explicitcowork.userDataDirwins. Older CloakBrowser profiles are left untouched (never inspected, copied, or deleted). - After upgrading:
/reloadPi and close/reopen cowork or browser-rendered read sessions. postinstallrunsplaywright install chromium. If scripts were skipped or blocked by npm lifecycle approval, runnpx playwright install chromiumfrom 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": falseto disable footer updates. - Set
"read": { "headless": false }or passheadless: falseto 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.