pi-lean-portal
Interactive Playwright web browsing for Pi. Chromium and Firefox built in; author stealth backends on either engine. Snapshots and screenshots cache to disk, saving context. Configurable profiles/cookies. Agents write site guides, auto-matched by domain.
Package details
Install pi-lean-portal from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:pi-lean-portal- Package
pi-lean-portal- Version
0.6.0- Published
- Sep 16, 2026
- Downloads
- 375/mo · 164/wk
- Author
- sofuego
- License
- AGPL-3.0-only
- Types
- extension
- Size
- 523.1 KB
- Dependencies
- 4 dependencies · 4 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-lean-portal User Guide
pi-lean-portal gives the Pi coding agent interactive web browsing via Playwright Chromium/Firefox, accessibility-tree snapshots with
@eelement refs, persistent profiles, cookies, and domain-aware navigation guides. A/webtoggle removes the tools from the agent's context when switched off, so web browsing doesn't consume tokens on sessions that aren't doing web work. If a site blocks the shipped browsers, drop in your own backend (e.g. Camoufox).Part of the pi-lean-dimension web-tools suite. For SearXNG search support, install
pi-lean-search.
Quick Start
pi install npm:pi-lean-portal
Then run /web install inside pi to download browser binaries (these are not installed via npm).
Once loaded, you'll see a notification like:
🌐 Browser extension loaded (plugins: chromium, firefox). Try: web-fetch for static pages or browser-navigate for interactive browsing.
The browser tools are enabled by default: web-fetch for static pages,
browser-navigate (plus click/type/scroll/screenshots via @e refs) for
interactive browsing.
/web Command — Browser Toggle & Profiles
The /web command controls whether web tools are visible to the AI agent,
toggles guide-saving mode, and manages browser profiles.
Three-State Toggle
| Command | Effect |
|---|---|
/web on |
Browsing only: all interactive browser tools + web-fetch are available. web-learn is hidden. If pi-lean-search is also installed, web-search is enabled too. |
/web learn |
Browsing + guide-saving: same as on, plus the web-learn tool is available so the AI can save/update navigation guides on request. |
/web off |
All web tools hidden: saves ~1500–2000 tokens per turn by removing tool schemas from the prompt. web-fetch and web-search are also hidden. |
Check Current State
| Command | Effect |
|---|---|
/web |
Show current toggle status and available sub-commands. |
/web status |
Detailed runtime status including toggle state, plugin health, browser-binary presence, active sessions, and profiles on disk. |
/web install |
Install browser binaries via the bundled playwright CLI (checkbox dialog in the TUI; prints the manual command elsewhere). |
/web install chromium|firefox |
Directly download one engine, no dialog. |
🌐 Browser tools: ✅ on | 📖 Learn mode: ❌ off
────────────────────────────────────────
Status: idle
Plugins: chromium, firefox, chromium-py (disabled), firefox-py (disabled)
Browsers: chromium ✓ firefox ✓
Use web-fetch for stateless HTTP fetches.
Active sessions: 1
PW [chromium] https://example.com — Example Domain [profile: session]
Profiles: 1 on disk (named)
shopping (0.3 KB) ← active
Session profiles: 1 (manage with /web profile)
Persistence
Toggle state persists across /reload, /resume, /fork, and /tree — it's
stored in the conversation's branch history. Fresh conversations start from
the toolsetDefaults block in settings.json
(Configuration).
Profiles — Persistent Sessions
Profiles let the AI agent maintain persistent browser state (cookies, localStorage) across calls, conversations, and even across different subagents.
Profile Modes
| Mode | browser-navigate profile= |
Behavior |
|---|---|---|
| None | "none" |
Clean slate every time (no cookies or state) |
| Session | "session" (default) |
Persists state for the current conversation; survives /reload and /resume |
| Named | "shopping", "work", etc. |
Shared across conversations and subagents, similar to browser tabs sharing a profile |
Managing Profiles with /web profile
| Sub-command | Effect |
|---|---|
/web profile list |
List all profiles on disk with their state size |
/web profile create shopping |
Create a new named profile |
/web profile session |
Set conversation-scoped default to session mode |
/web profile none |
Reset default to ephemeral (no persistence) |
/web profile shopping |
Switch default profile to an existing named profile |
/web profile clear shopping |
Delete the saved state for a profile (keeps the directory) |
/web profile clear-all --confirm |
Clear ALL profile states |
/web profile prune --confirm |
Remove stale session profiles for ended conversations |
How it works: Profile state is stored at
~/.pi/agent/pi-lean-portal/browser-state/<profile-name>/storage-state.json. Session-scoped profiles are auto-cleaned when the pi conversation ends.
Cookie Management
The /web cookies command lets you inspect and clear session cookies:
| Sub-command | Effect |
|---|---|
/web cookies list |
List all cookies in the current session (name, value, domain, expiry, flags) |
/web cookies clear --confirm |
Clear ALL cookies for the current session |
Cookies are saved as part of profile state. When you switch profiles, the cookies from the old profile are preserved and the new profile's cookies are loaded.
All 12 Tools
pi-lean-portal registers 12 tools. Three are stateless; the rest require
a browser session (created by browser-navigate).
| Tool | What it does | State |
|---|---|---|
web-fetch |
Fetch a URL → Markdown, no browser session | stateless |
web-guide / web-learn |
Read / save navigation guides (learn mode via /web learn) |
stateless |
browser-navigate |
Open a page → accessibility tree with @e element refs |
session |
browser-snapshot |
Refresh the tree (full=true returns the uncompacted tree) |
session |
browser-click / browser-type / browser-scroll / browser-back / browser-press |
Interact via @e refs (click, type, scroll, history, keyboard) |
session |
browser-console |
Read captured console messages; also evaluates JS in the page | session |
browser-inspect |
Targeted element/text queries without loading a full snapshot | session |
Automatic Artifacts
Session tools save their full output to disk so the agent never needs a second call:
- Screenshots:
browser-navigateandbrowser-snapshotauto-capture a viewport-sized (1280×720) JPEG to/tmp/pi-lean-portal/screenshot-<taskId>.jpg[^1]. - Full Snapshots: Any snapshot compacted for size is cached in full at
/tmp/pi-lean-portal/snapshot-*.txt(last 2 per task).
Both file paths are surfaced as hints in the tool output, which the agent can follow up with the read tool.
[^1]: Not full-page.
Navigation Guides
Built-in Pattern Guides
pi-lean-portal ships with four built-in pattern guides that appear in the guide footer when relevant:
| Guide | Trigger | What It Covers |
|---|---|---|
bot-detection |
Bot blocking detected (botDetected) |
Cloudflare, challenge pages, what NOT to do |
cookie-consent |
Dialog detected (dialogDetected) |
Accept/Reject buttons, Escape key, verification |
pagination |
On-demand | Next buttons, infinite scroll, pages |
search |
On-demand | Search boxes, comboboxes, result lists |
Overriding Built-in Guides
A same-named .md file in ~/.pi/agent/pi-lean-portal/web-guides/ (e.g.
bot-detection.md) shadows the builtin entirely; the whole guide is
replaced, not field-merged. To keep a pattern guide firing, include
trigger.signal: botDetected (or dialogDetected) in the frontmatter;
omitting it disables the trigger.
---
category: pattern
trigger.signal: botDetected
icon: 🤖
shortName: my bot guide
---
Custom guidance text for bot challenges.
Site guides and pattern guides live in disjoint namespaces — a site guide
for www.botdetection.com does not collide with the bot-detection pattern
guide; both fire when applicable.
Domain Matching
A guide's declared domain matches the exact hostname or any subdomain of
it. Declaring reddit.com covers www.reddit.com, old.reddit.com, and
any other subdomain, so a guide saved once resurfaces across a site's URL
variations. This is what makes guides cheap to author: you rarely need more
than the apex domain. A domain may match multiple guides (a web guide plus
one or more API guides) — all matching guides surface together, sorted
host-first (API guides before web guides).
API guides from pi-lean-host (co-install)
When pi-lean-host is installed alongside portal and /api is on, its
user-authored API guides also surface in the navigate footer alongside
your web guides using the same reactive mechanism, with no extra setup. API guides sort
first (API access is cheaper than browsing) but both always appear, so a
partial-coverage API keeps its web guide for the gaps the API doesn't cover.
The footer routes API guides to api-guide({domain, guide}) rather than
web-guide. See the
host README
for the host side of the contract.
Backend Architecture
pi-lean-portal uses a plugin-based architecture. The core framework is
backend-agnostic; plugins implement a standard BrowserPlugin interface,
and the router dispatches tool calls to the right plugin based on a
strategy parameter.
Four Shipped Backends
| Backend | Engine | Type | Default |
|---|---|---|---|
chromium |
Chromium | Node/Playwright | Enabled (auto strategy) |
firefox |
Firefox | Node/Playwright | Enabled |
chromium-py |
Chromium | Python/Playwright | Disabled |
firefox-py |
Firefox | Python/Playwright | Disabled |
Install Firefox: run
/web install firefoxinside pi to download the Firefox binary for the Nodefirefoxbackend. For the Python parity backends, install Playwright insidebackends/python-base/.venv.
All four shipped backends support screenshots (viewport-sized, not
full-page), console capture, JS evaluation, bot detection, and dialog
auto-dismissal. The one divergence:
AbortSignal is advertised but silently ignored on the Python -py backends.
The
-pybackends are disabled by default, as they ship as parity references for the Python bridge contract and as templates for authoring your own Python-based backends.
How Plugin Selection Works
- The order of plugins in the config array determines priority; the first enabled plugin is the "auto" default (typically Chromium).
- The AI agent explicitly selects which backend to use via the
strategyparameter inbrowser-navigate:strategy="auto"→ uses the first enabled plugin (typically Chromium)strategy="firefox"→ uses the Firefox Node backendstrategy="chromium-py"→ uses the Python Chromium backend
- No automatic fallbacks and no mid-session transitions. If a plugin fails, the agent decides what to do next.
Stealth & Custom Browser Backends
Stealth engines like Camoufox are intentionally left to users to
author: drop a bridge.py into
~/.pi/agent/pi-lean-portal/user-backends/<name>-py/ — a separate tree
from the package's backends/ directory (never edited after install,
so custom backends survive updates) and never in the default fallback list.
Register one in browser.plugins with an absolute pythonPath (see
Configuration).
The full install flow, the quirks schema, and the worked Camoufox template
live in
contributed/README.md;
contributed/CHOOSING.md
covers when to reach for one at all (most users never need one).
Node-based custom backends follow the same shape via PlaywrightPluginBase.
Configuration (settings.json)
Browser settings are read from ~/.pi/agent/settings.json (global) and
.pi/settings.json (project-local, overrides global).
browser.plugins Array
Controls which browser backends are loaded. Entries are processed in order
(the first enabled plugin is the "auto" default).
{
"browser": {
"plugins": [
{
"name": "chromium", // Required: unique plugin identifier
"dir": "chromium", // Required: backend directory
"enabled": true, // Optional, defaults to true
"config": {} // Optional, passed to the plugin's init()
}
]
}
}
A user-installed stealth backend additionally puts an absolute pythonPath
and a launch object into config — see
contributed/README.md.
browser.defaultProfile
The profile mode or named profile used when browser-navigate doesn't specify
a profile parameter:
{
"browser": {
"defaultProfile": "session" // "none", "session", or a named profile string
}
}
toolsetDefaults
Whether browser tools are enabled on fresh conversations. Read by the
pi-tool-masking library at restore time, between the chat-branch tier and
the toolset's packaged default:
{
"toolsetDefaults": {
"toolset-state:pi-lean-dimension.web": { "enabled": true },
"toolset-state:pi-lean-dimension.web-learn": { "enabled": false },
"toolset-state:pi-lean-dimension.search": { "enabled": true }
}
}
- Keys are the toolsets'
persistKeyvalues (toolset-state:<id>). - Omit a
toolsetDefaultskey to use the toolset's packaged default (webandsearchdefaulttrue;web-learndefaultsfalse). - The
searchkey only applies whenpi-lean-searchis installed. - Pins do not apply in spawned subagent children (see pi-tool-masking
1.3.0's
piToolMasking.childPolicyfor the opt-out).
browser.maxStorageStateSize
Size threshold for profile state warnings (default: 10 MB):
{
"browser": {
"maxStorageStateSize": 10485760
}
}
Tips & Best Practices
When to use web-fetch vs browser-navigate
Use web-fetch |
Use browser-navigate |
|---|---|
| Static content, docs, READMEs | Interactive pages, JS-heavy SPAs |
| Quick lookups, no session needed | Form filling, clicking, authentication |
| Content you want as clean Markdown | Visual inspection (auto-captured screenshots), pages where you need the accessibility tree |
Working with @e Element References & Large Pages
The agent interacts via @e1, @e2 refs from the accessibility tree —
no CSS selectors or XPath. Snapshots are auto-compacted to ~2500 chars;
full trees and screenshots spill to /tmp/pi-lean-portal/ (paths are
surfaced in tool output).
Bot Detection
When a page triggers anti-automation, the agent sees a warning plus a bot-detection guide footer, and challenge pages with <5 visible elements fail hard rather than being interacted with. If a site consistently blocks the shipped Chromium/Firefox, that's the trigger to install a stealth backend (see Stealth & Custom Browser Backends) — not something to configure up front.
Security
- URLs are parsed with
new URL()as input validation. Malformed URLs are rejected, but no SSRF boundary is enforced (a coding agent already has filesystem and shell access, so blocking localhost or private IPs would be theater) - Profile state is stored with restricted file permissions (0700 dirs, 0600 files)
License: AGPL-3.0-only