pi-simple-web-tools
Minimal Pi extension: web_search via Exa + fetch_content (markdown content-negotiation, Readability, lazy Playwright fallback).
Package details
Install pi-simple-web-tools from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:pi-simple-web-tools- Package
pi-simple-web-tools- Version
0.1.0- Published
- Jul 10, 2026
- Downloads
- not available
- Author
- jilles
- License
- MIT
- Types
- extension
- Size
- 39.9 KB
- Dependencies
- 5 dependencies · 3 peers
Pi manifest JSON
{
"extensions": [
"./index.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
pi-simple-web-tools
A deliberately small Pi extension that adds exactly two tools:
web_search— web search via Exa (API key required).fetch_content— fetch a URL (web page or PDF) and return clean markdown, using HTTP content negotiation → Readability → a lazy headless-browser fallback for JavaScript-rendered pages. Large content is saved to a temp file and returned as a preview + path.
It's a focused alternative to the excellent but feature-rich
pi-web-access by Nico Bailon — if
you only want search + fetch and nothing else (no video, YouTube, GitHub cloning,
curator UI, or multi-provider routing), this is that.
Install
pi install npm:pi-simple-web-tools
Or clone into your extensions directory for local development:
git clone https://github.com/jillesme/pi-simple-web-tools \
~/.pi/agent/extensions/pi-simple-web-tools
cd ~/.pi/agent/extensions/pi-simple-web-tools && npm install
Restart Pi (or /reload).
Note:
web_searchandfetch_contentare common tool names. If you also runpi-web-access, disable its tools to avoid a collision — add"extensions": []to its entry in~/.pi/agent/settings.json.
Configuration
Set an Exa API key (get one here) via environment variable:
export EXA_API_KEY="exa-..."
or in ~/.pi/web-tools.json:
{
"exaApiKey": "exa-...",
"ssrf": { "allowRanges": ["198.18.0.0/15"] }
}
ssrf.allowRanges is optional and only needed if a TUN/fake-IP proxy (Surge,
Clash, Mihomo, …) resolves public domains into reserved IP ranges that the SSRF
guard would otherwise block.
Config precedence: EXA_API_KEY env var → exaApiKey in the config file.
Override the config path with PI_WEB_TOOLS_CONFIG.
Tools
web_search
web_search({ query: "typescript structural typing" })
web_search({ queries: ["react server components", "next.js app router caching"] })
web_search({ query: "llm evals", numResults: 10, recencyFilter: "month" })
web_search({ query: "vite plugins", domainFilter: ["github.com", "-medium.com"] })
| Parameter | Description |
|---|---|
query / queries |
Single query, or multiple searched independently |
numResults |
Results per query (default 5, max 20) |
recencyFilter |
day, week, month, or year |
domainFilter |
Limit to domains (prefix with - to exclude) |
fetch_content
fetch_content({ url: "https://example.com/article" })
fetch_content({ urls: ["https://a.com", "https://b.com"] })
| Parameter | Description |
|---|---|
url / urls |
Single URL or multiple (fetched in parallel, max 3 concurrent) |
How fetch_content works
fetch_content(url)
→ SSRF guard (blocks localhost / private / reserved IPs)
→ HTTP fetch with `Accept: text/markdown, text/html;q=0.9, …`
→ PDF (Content-Type or .pdf)? unpdf text extraction (with page markers)
→ Content-Type: text/markdown? return as-is (skip Readability entirely)
→ HTML? Readability → Turndown → markdown
→ text / json / plain? return as-is
→ still empty / looks JS-rendered? render with headless Chromium (Playwright),
then Readability, falling back to full <body>
→ large result (> 100k chars)? write to a temp file, return a preview + path
Markdown content negotiation. Many docs sites (Cloudflare, Vercel, Mintlify, Fastly, HackMD, …) now return
Content-Type: text/markdownwhen asked. When they do, the response is already clean and Readability/Turndown are skipped. See theAccept: text/markdownconvention.Playwright fallback is lazy. It's an optional dependency, imported at runtime only when a page can't be extracted otherwise. To enable it:
cd ~/.pi/agent/extensions/pi-simple-web-tools npm i -D playwright && npx playwright install chromiumWithout it, JavaScript-rendered pages return an error with this hint instead of silently failing.
PDFs are extracted to text with
unpdf, one page at a time with<!-- Page N -->markers so you can jump to a section. Text-based extraction only — no OCR for scanned/image-only PDFs.Large content is never truncated away. Anything over 100k chars is written to
$TMPDIR/pi-web-tools/<host>-<hash>.md; the tool returns a short preview plus the file path so the agent reads exactly the slice it needs with the built-inreadtool (offset/limit) instead of loading the whole page into context.
Files
| File | Purpose |
|---|---|
index.ts |
Extension entry — registers the two tools |
exa.ts |
Exa search client + markdown result formatting |
fetch.ts |
Fetch → content negotiation → PDF/Readability/Turndown → Playwright |
ssrf.ts |
SSRF guard (DNS resolution + private/reserved IP checks) |
config.ts |
Loads ~/.pi/web-tools.json and EXA_API_KEY |
Credits
This project is derived from and inspired by
pi-web-access by
Nico Bailon (MIT). The ssrf.ts guard is used
essentially verbatim, and the fetch/Exa logic follows patterns from that project.
Go check out pi-web-access if you want the full-featured version.
License
MIT — see LICENSE.