@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
4.0.1- Published
- Sep 30, 2026
- Downloads
- 1,242/mo · 438/wk
- Author
- itc-steve
- License
- MIT
- Types
- extension
- Size
- 283.2 KB
- Dependencies
- 10 dependencies · 3 peers
Pi manifest JSON
{
"extensions": [
"./src/index.ts"
]
}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
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 CloakBrowser 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
pi install npm:@itc-steve/pi-web-complete
From a local checkout:
pi install /path/to/pi-web-complete
2. Configure
Configuration and secrets live in separate files:
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
# edit web.json, then paste your keys into web.env
~/.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=replace-with-github-token
Project overrides can live in .pi/web.json and .pi/web.env. Project JSON overrides global top-level settings while backends merge per backend; project secrets overlay global secrets. Keep key values in .env, never 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" })
Toggle web_search / web_cowork per session
Both tools start enabled in every new session. Disable one for the rest of a session 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. Toggles last for the session only; set the per-session default in web.json:
{
"search": { "enabled": false },
"cowork": { "enabled": false }
}
Both default to true when absent, so a missing config section keeps auto-enable on.
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 CloakBrowser 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": complete main content, capped around 12k characters in chat.savePathorsaveDir: full extract goes to disk; chat receives a short summary.mode: "browser": force CloakBrowser 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. 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
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 CloakBrowser 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": "~/.cloakbrowser/cowork-profile",
"downloadDir": "~/Downloads",
"headless": false
}
}
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 to enable discovery.
The launching agent owns browser lifetime; keep it running. Other agents disconnect on exit without closing its window. 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.
CLI Assist integration check (isolated temporary profile; worker load, panel CSS/JS, denied attachment without permission):
npx tsx test/cowork-extensions.check.ts /absolute/path/to/CLI-Assist/extension
This check does not certify actual 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 installed CloakBrowser; 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" })
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 only when a Context7 key resolves.
- 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.
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
- Node.js 20.18.1+ is required.
postinstallrunscloakbrowser installand stores stealth Chromium under~/.cloakbrowser/.- CloakBrowser checks for browser updates at launch. Tagged update logs are hidden because direct console output corrupts Pi's TUI; set
DEBUG=1to show them orCLOAKBROWSER_AUTO_UPDATE=falseto disable checks. - 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.
License
CloakBrowser's JavaScript wrapper is MIT-licensed. Its downloaded Chromium binary uses CloakBrowser's separate binary license; see its LICENSE and BINARY-LICENSE.md.