@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
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.
  • savePath or saveDir: 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=never skips.
  • 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 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 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 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" })

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.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.

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.
  • postinstall runs cloakbrowser install and 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=1 to show them or CLOAKBROWSER_AUTO_UPDATE=false to 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": false to disable footer updates.
  • Set "read": { "headless": false } or pass headless: false to show browser-rendered one-shot reads.

License

MIT

CloakBrowser's JavaScript wrapper is MIT-licensed. Its downloaded Chromium binary uses CloakBrowser's separate binary license; see its LICENSE and BINARY-LICENSE.md.