pi-lean-host

Read-only REST API client for Pi. Your agent authors and calls YAML recipes handling the tedious parts: pagination, OAuth2, rate limits, backoff. Credentials live in a local store, injected in code, never in agent context. Churn-free research + retrieval

Packages

Package details

extension

Install pi-lean-host from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:pi-lean-host
Package
pi-lean-host
Version
0.5.0
Published
Sep 11, 2026
Downloads
1,100/mo · 17/wk
Author
sofuego
License
AGPL-3.0-only
Types
extension
Size
657.3 KB
Dependencies
4 dependencies · 5 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-host User Guide

pi-lean-host gives the Pi coding agent declarative access to REST APIs — recipe-based guides that encode an API's endpoints, auth, pagination, and response shape once, then execute through a small set of managed tools. The agent never hand-rolls auth headers, never picks the wrong Accept, never re-derives pagination from scratch each session. A /api toggle (peer to /web) hides the tools from context on sessions that aren't doing API work, and /api on + /web off gives a pure api-only context with zero browser-* noise for batch structured-data pulls.

Some sites are hostile to scrape but expose a clean, documented API. pi-lean-host is the structured-data answer to that gap — author the API once, reuse it forever.

Part of the pi-lean-dimension web-tools suite. Host-only installs are valid — pi-lean-portal is an optional peer dependency. For interactive browsing, install pi-lean-portal; for SearXNG search, install pi-lean-search.


Table of Contents

  1. Quick Start
  2. The Big Idea: Recipes, Not a Runtime
  3. /api Command — API Toggle
  4. All 7 Tools
  5. Authoring Your First Guide
  6. Reference Recipes (caritas)
  7. Configuration (settings.json)
  8. Co-Installing with pi-lean-portal
  9. Tips & Best Practices
  10. Authentication & Secrets
  11. Security & Scope

Quick Start

pi install npm:pi-lean-host

No browser binaries, no server, no setup wizard. The host tools are enabled by default — you'll see:

📡 API tools enabled. /api learn to make api-learn + api-probe + api-scaffold + api-store + oauth-mint available.

From a fresh install you have no guides yet, so the next step is to get one:

  • Author one — see Authoring Your First Guide for the end-to-end workflow, including auth-gated APIs. The complete authoring manual (field reference, pagination styles, helper contracts) lives in docs/authoring.md.
  • Or copy a reference recipe (see Reference Recipes (caritas)) into ~/.pi/agent/pi-lean-host/api-guides/<slug(shortName)>/ — it loads immediately.

The Big Idea: Recipes, Not a Runtime

The single most important discipline: only files you place in ~/.pi/agent/pi-lean-host/ execute. An API guide is a markdown file with YAML frontmatter declaring endpoints, auth, pagination, and response shape. A managed tool (api-fetch) executes the declared operation through a small fixed set of built-in helpers. The agent never writes TypeScript against the API — it authors (or copies) a recipe, and the tool does the call.

This kills two whole classes of recurring mistakes before they exist:

  • Agents getting the same API wrong every session. The reason they do is they are the ones writing the calls. Move call construction into a reviewed tool + fixed helpers, and those mistakes disappear at the source.
  • Arbitrary eval / egress safety (default path). Declarative recipes run no agent-authored code, and bundled recipes are inert. The exception is local helpers: in /api learn the agent can write helper.ts to your guides directory. These run in-process with the agent's privileges. Since the agent already has shell access, this isn't a privilege escalation, but it introduces persistent code.

Why not just a skill?

A skill is prose the agent re-interprets each turn; this plugin is a fixed executor it calls through. For a single clean endpoint you call occasionally, a few skill lines are the lazier answer. It earns its keep where re-derived curl goes wrong — pagination to exhaustion (cursor, nextLink, OAI-PMH tokens, Wikimedia continuation dicts walked in one call by reviewed code), charset traps (Latin-1 APIs serving bytes without a charset parameter — fixed once in the transport, not re-applied and forgotten), and in-process TypeScript helpers that transform params or responses directly (a skill can only instruct the agent to write and run that code). A skill is also always in context; the guide store loads on demand. A skill is pure text with no in-process eval, whereas a loaded helper.ts runs with the agent's privileges.

Three tiers of code, separated by trust

Tier Where it lives Who writes it Ships in tarball? Trust
Built-in helpers package source (core/) maintainers yes reviewed
Local user helpers ~/.pi/agent/pi-lean-host/api-guides/<slug(shortName)>/helper.ts you, or the agent in /api learn no user-owned
Bundled recipes caritas repo (api-guides/<slug(shortName)>/) maintainers no (see Reference Recipes) inert: never auto-executed

Built-in helpers cover the common 90%. Local user helpers cover the weird 10% (computed signatures, strange date transforms, custom auth).


/api Command — API Toggle

Command Effect
/api on API accessapi-guide + api-fetch available. Authoring tools hidden. (Default for new sessions.)
/api learn API access + authoring — adds api-learn + api-probe + api-scaffold + api-store + oauth-mint on top of on. The agent never authors guides unprompted — it must be in learn mode.
/api off All API tools hidden — removes api-* from the agent's context to save tokens on sessions that aren't doing API work.
/api Show current state and available sub-commands.
/api status Detailed runtime status — toggle state, active guide count, domain list, and helper health (disabled helpers surface with a ).
/api helpers List local user helpers (or /api helpers <domain> to view one's source).
/api secrets [<domain> [<name>]] Manage stored API secrets — list, provision, delete (see Authentication & Secrets).
/api verify <domain> [guide] [--force] Run every runnable op against the live API and stamp verified on success — strict: any runnable-op failure → no stamp; skipped ops named in the report (see Recipe drift).
/api delete <domain> [guide] Remove a guide directory and invalidate the guide-store cache — a human-typed recovery gesture (no agent tool surface); interactive confirm for a whole-domain delete.
/api oauth <domain> … OAuth2 token management — init / mint / --status / --refresh / --revoke / --code <code> per token slot (human-typed; the agent's mint path is oauth-mint). init runs an interactive wizard (or headless flags — see --help for --redirect-uri, which must match the provider app registration) and finishes a paste-based auth-code flow via /api oauth init <domain> … --code <code>.
/api bootstrap oauth <domain> <spec> Agent-driven OAuth2 bootstrap — validates args, auto-enables learn when off, injects a research brief into the session, and exits; the agent then researches the provider and calls oauth-mint (the human is the trust root for the secret-bearing endpoint). Refused headless.

Persistence

Toggle state persists via the pi-tool-masking library (persistKey: toolset-state:pi-lean-dimension.api), surviving /reload, /resume, /fork, and /tree navigation. A fresh conversation starts from the toolsetDefaults block in merged Pi settings (see Configuration), falling back to the packaged defaults.

The status bar shows an api glyph (independent of the browser/search slots). It reads ● api when /api is on (colored to reflect learn state) and ○ api when off.


All 7 Tools

pi-lean-host registers 7 tools. api-guide and api-fetch are available under /api on; api-learn, api-probe, api-scaffold, api-store, and oauth-mint are added under /api learn.

1. api-guide — Inspect the Guide Store (local read)

api-guide → list all available API guides (catalog)
api-guide domain="wikipedia.org" → disambiguation menu (two guides claim the domain)
api-guide domain="wikipedia.org" guide="Wikipedia REST" → selected guide
  • No parameters → the full catalog (collapsed by organization:).
  • {domain} → the guide's detailed operation list, or a disambiguation menu when more than one guide claims the domain.
  • {domain, guide} → the selected guide, resolved by shortName.

When a guide is malformed on disk it shows with a ⚠ malformed badge and the error inline rather than silently disappearing — so you can fix the one broken field instead of re-authoring from scratch.

2. api-fetch — Execute a Guided Operation (network read)

api-fetch domain="wikipedia.org" operation="getPageSummary" params={title:"Albert_Einstein"}
api-fetch domain="wikipedia.org" operation="searchPages" params={srsearch:"climate"} gatherAll=true

Parameters:

  • domain — a domain registered in a guide (e.g. "wikipedia.org").
  • operation — an operation name from the guide (e.g. "getPageSummary").
  • params (optional) — path and query parameter values for the operation.
  • gatherAll (optional) — true paginates to gather all items up to the guide's gatherAllMax ceiling (default 1000, overridable per-guide and per-op).
  • fresh (optional) — true bypasses the response cache and forces a full network fetch.

api-fetch resolves every guide claiming domain, finds the named operation across them, and executes it against the matching guide. Exactly one hit executes; zero hits lists the ops from all matching guides (a multi-guide domain — each guide in its own directory — so you can pick); an op name appearing in ≥2 guides is an ambiguous collision the guide authors must fix (re-author via api-learn to rename). The agent never sees an unredacted URL, never sees a header value, never sees the auth scheme. Output is an inline preview (~4000 chars) with larger responses spilled to a temp file under /tmp/pi-lean-host/ (overridable via PI_HOST_TEMP_DIR) — read it with offset/limit for specific sections.

When no guide exists for the domain, the call fails informatively and points at api-guide({}) (to list guided domains) and api-learn (to author one) — turning a failed execute into a discovery moment in one round-trip.

3. api-learn — Author / Update a Guide (local write)

api-learn domain="arxiv.org" new=true           → stages a fresh placeholder template to /tmp/pi-lean-host/arxiv.org/guide.md
api-learn domain="arxiv.org"                    → fetches an existing guide's raw recipe + siblings into the staged dir
api-learn domain="arxiv.org" dir="/tmp/pi-lean-host/arxiv.org"  → validates + mirror-saves the staged dir to disk

The working copy is staged under /tmp/pi-lean-host/ (/tmp self-cleans, so drafts don't accumulate; <domain>/ for starter templates (new: true), <slug(shortName)>/ for fetched recipes) — a fresh fail-closed starter template, an existing guide's raw recipe + siblings for direct editing, or a validated mirror-save of the staged dir into ~/.pi/agent/pi-lean-host/api-guides/<slug(shortName)>/. Every staged pull is prepended with the authoring manual. Full staging, deletion-gate, and slug-collision semantics: docs/authoring.md.

4. api-probe — Discover an Endpoint's Shape (network read, exploratory)

Agent-driven shape discovery for the authoring loop. Fetches a not-yet-guided endpoint over the real transport (same UA, charset, retry, and ETag handling as api-fetch — the sanctioned way to reach even WAF'd hosts), summarizes the JSON shape, and emits a draft YAML operation block to paste into a recipe. It only suggests — it never writes the guide, and a draft still needs confirming against the provider's docs (probe surfaces evidence, not authority).

5. api-scaffold — Bootstrap verify.json / helper.ts (local write)

api-scaffold domain="arxiv.org" verify=true   → writes a starter verify.json with "__FILL_ME__" sentinels to /tmp/pi-lean-host/<slug(shortName)>/verify.json
api-scaffold domain="arxiv.org" helper=true  → writes a commented-out helper.ts stub to /tmp/pi-lean-host/<slug(shortName)>/helper.ts

Bootstrap tool for the two artifacts the authoring loop needs but that api-learn can't draft from the recipe alone: a verify.json with "__FILL_ME__" sentinels for every op with unsatisfiable params (sentinels skip until you replace them; existing real values merge additively) and a commented-out helper.ts stub. Both staged to /tmp — never the guides dir, never overwriting an existing staged sibling. Save the guide first, then scaffold.

6. api-store — Inspect Both Credential Stores (local read, learn-gated)

api-store → orphan view: unscoped secret domains + token domains with no guide
api-store domain="api.github.com" → combined secrets + tokens report for the domain

Read-only inspection of both credential stores in one call — the agent-facing view of /api secrets + /api oauth --status. The authoring question is never "show me tokens" in isolation; it is what credentials exist for a domain, what's declared vs provisioned vs minted, what's expired, and what needs minting next.

  • Bare call → the authoring-bootstrap (orphan) view: unscoped secret domains and token domains with no guide. Token domains resolve through the same store-domain seam minting uses, so an api.-subdomain token maps to its parent's guides instead of false-positive as guideless.
  • With domain (or apiHost, resolved the same way) → the combined per-domain report: provisioned/declared/gap secret names, token slots (issuer, granted scope, expiry, refreshable), and declared-slot gaps — "guide declares client_credentials via tokenUrl: no token minted" — the pointer to oauth-mint that replaces a trial-and-error 401.

Metadata only: accessToken/refreshToken are dropped at the collection boundary and secret values never enter the tool — they can't appear in the rendered text or the structured details. When a token's granted scope wasn't echoed by the provider, the requested scopes render with an "(assumed)" marker (RFC 6749 §5.1). Mint via oauth-mint; refresh/revoke stay human-typed (/api oauth).

7. oauth-mint — Human-in-the-Loop OAuth2 Mint (network write, consented)

oauth-mint domain="github.com" grant="authorization_code" tokenUrl="https://github.com/login/oauth/access_token" ... → prompts the human, then mints + stamps the token store

The learn-gated mint half of the agent-driven OAuth2 bootstrap. The agent supplies researched params (grant, token/authorize URLs, scopes, client credentials as store NAMES); the tool validates fail-closed, prechecks the store names, then prompts the human — token-URL confirm (the human is the trust root for the secret-bearing endpoint), a ✓/○ scopes checklist, and a paste prompt for the redirect URL (which never enters the transcript). Any cancel prints the two-call /api oauth init <domain> … --code escape-hatch hint.


Authoring Your First Guide

The simplest case — a public API with no auth — end to end:

  1. /api learn — enable the authoring tools.
  2. Ask your agent to author a guide — point it at the API's documentation (a saved spec document or an online docs URL) and say which endpoints you want. The agent stages a starter template (api-learn({domain, new: true})), fills in the recipe — apiHost, one operation to start, no pagination needed for a single flat endpoint — and confirms every op block against the spec you supplied. For an endpoint that isn't documented, the agent calls api-probe on the live URL to draft the YAML op block for your review.
  3. Review and save — the agent validates the draft and saves it to ~/.pi/agent/pi-lean-host/api-guides/<slug(shortName)>/guide.md (via api-learn({domain, dir: …})).
  4. Use it — from now on, just ask your agent for data from that API in plain language; it calls api-fetch, which discovers the guide by domain and handles URL construction, auth, and pagination. Then run /api verify <your-domain> to check every op against the live API — a passing run stamps verified on the guide.

Auth-gated API? Two additions, both handled before the guide will fetch successfully:

  • Static key — you provision the value once via /api secrets <domain>; the guide declares it by name with auth.kind: static-key. See Authentication & Secrets.
  • OAuth2/api bootstrap oauth <domain> <spec> injects a research brief and the agent drives oauth-mint; you confirm the token URL, tick the scopes, and paste the redirect URL back. See the /api bootstrap row in the command table.

The complete field reference, pagination styles, and helper contracts live in docs/authoring.md.


Reference Recipes (caritas)

The comprehensive recipe library lives in the caritas repo — real, verified recipes spanning the no-auth and keyed axes, each with a per-recipe verified:-date provenance and the perpetual drift disclaimer. They are inert reference material: nothing executes until you copy a recipe into your own ~/.pi/agent/pi-lean-host/api-guides/<slug(shortName)>/ directory.

Host itself ships only a synthetic axis-guide set under api-guides/ — minimal coverage fixtures (no verified: date, no live endpoints) that keep every guide-driven framework axis exercised via mocked transport. They are framework fixtures for host's own tests, not recipes for you to copy (membership pinned by __tests__/axis-coverage.test.ts).

The domains caritas covers (a discoverability index, may drift from the live repo):

arxiv                         federal-register              mastodon                      twitch-user
boletin-oficial-del-estado    frost-server-sensorthings     musicbrainz                   usgs-earthquake
coingecko                     gbif                          open-food-facts               wayback-availability
data-gov                      github                        open-library                  wayback-cdx-server
datos-gob-es                  gitlab                        pubmed-e-utilities            wikidata
deutsche-nationalbibliothek   inaturalist                   stripe                        wikimedia-action
ecb-data-portal               internet-archive              telegram-bot-api              wikipedia-rest
etherscan                     library-of-congress           twitch                        world-bank-indicators

Several are keyed (auth.kind: static-key), the spread that exercises header-vs-query refs and the required/optional split (see Authentication & Secrets).

To use them, copy the guides into your own directory from a clone of caritas:

git clone https://github.com/coreyryanhanson/caritas.git /tmp/caritas
cp -r /tmp/caritas/api-guides/* ~/.pi/agent/pi-lean-host/api-guides/

To grab a single domain instead of all of them, copy just its folder (e.g. .../api-guides/wikipedia-rest).

A recipe may carry operation.helper: true plus an accompanying helper.ts in its domain subdir as a worked example. See caritas's CONTRIBUTING.md for contributing a recipe to the library; for authoring one for yourself, see docs/authoring.md.

Configuration (settings.json)

Host settings are read from ~/.pi/agent/settings.json (global) and .pi/settings.json (project-local, overrides global).

toolsetDefaults (toolset on/off defaults)

Whether the API 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.api": { "enabled": true },
    "toolset-state:pi-lean-dimension.api-learn": { "enabled": false }
  }
}
  • Keys are the toolsets' persistKey values.
  • Pins do not apply in spawned subagent children (see pi-tool-masking 1.3.0's piToolMasking.childPolicy for the opt-out).
  • Omit a key to use the packaged default (api defaults true; api-learn defaults false).
  • The api-learn toolset requires api, so enabling learn cascades api on; disabling api cascades learn off.

There is no host.* settings block. The transport layer (per-domain undici Agent with retry-on-429, redirect policy, timeouts, ETag/Cache-Control caching) is configured internally; apiHost and operation paths live in the recipe frontmatter, not in settings. Credentials are not stored in settings.json — they live in the per-domain secrets store, provisioned via /api secrets (see Authentication & Secrets).


Co-Installing with pi-lean-portal

Portal is fully optional. When co-installed, host registers a projection of its user-authored ApiGuides with portal's guide-source registry at load time — recipe fields stripped, presentation fields kept — so they surface in the navigate footer in browse mode (/web on + /api on), the same reactive mechanism web guides use. Host→portal opt-in; portal never imports host.

  • Host-first ordering. When both a host guide and a web guide match a domain, the host guide surfaces first (API access is cheaper, more reliable, lower-context than browsing). Both still surface; you choose. Host guides surface only when /api is on; web guides only when /web is on. No suppression — partial-coverage APIs keep their web guide for the gaps the API doesn't cover.
  • The projection carries one extra field, kind: "web" | "api" (default "web"), which portal consumes only for ordering and the footer reason string. No recipe-structured data (apiHost, operations, pagination, auth) ever crosses the boundary.

Host-only installs don't register and lose nothing — reactive surfacing is an enhancement for co-installs, not a host-only requirement. The on-demand path (api-guide({domain})) is fully functional without it.


Tips & Best Practices

api-fetch vs web-fetch vs api-probe

Use api-fetch Use api-probe Use web-fetch (portal)
An API you've guided Discovering shape before authoring a guide A one-off unguided fetch against a known URL
Repeated/paginated pulls Drafting an op block from a live endpoint Reading docs to author a recipe

api-fetch is the guided path — the guide is the product. It has no ad-hoc bare-fetch mode on purpose: that would recreate the exact mistakes the tool exists to prevent. The escape hatches are api-learn (write a guide, then execute through it) and web-fetch (portal) for a one-off.


Authentication & Secrets

Many read APIs authenticate with a static key (X-Api-Key, Authorization: Bearer, or a ?key= query param). pi-lean-host supports these end-to-end without the credential ever appearing in the agent's context — a guide declares the secret by name, you provision the value once, and api-fetch injects it in code.

A candid note on storage and threat model

The secrets store is plaintext JSON at rest — mode 0600, no encryption. That matches pi's own posture for its credentials (an API key in settings.json or the env). The threat this guards against is not another process reading the file; it's accidental transcript exfiltration.

That containment is plugin discipline, not a vault. read/cat on the store file is one tool-call from the agent, which runs with your privileges, so nothing here is a hard guarantee. That's why the real rule is: store read-only keys. Scope every credential to the smallest read surface it needs (e.g. a GitHub fine-grained token with read-only contents). A leaked read key is a data-exposure incident; a leaked write key is a takeover — and the plugin is GET-only, so a read-only key is always enough for what it does.

Guide-side: declare the name, never the value

In guide.md, set auth.kind: static-key and point at store secrets by name — the value never lives in the guide (a real key committed there would be one cat from the agent's context). The full guide-side YAML (secretRefs / secretQueryRefs / secretPathRefs, prefix, optional, and the parser-enforced invariants) is documented in docs/authoring.md.

Migrating a guide from the old flat shape (requires/optional rosters, headerPrefixes map, bare-string refs)? See docs/migration-v1.md.

Provisioning

/api secrets                          list stored domains + secret names (names only)
/api secrets <domain>                 view + provision for a guide (prompts the declared names)
/api secrets <domain> <name>          set a single secret (manual escape valve)
/api secrets <domain> --delete        delete all secrets for a domain (confirm)
/api secrets <domain> <name> --delete delete a single secret

Provisioning is interactive (ctx.ui dialogs — the value is captured transcript-safely and written straight to the store, never returned). On headless hosts there is no dialog, so /api secrets prints the direct file-write instructions instead — write a { "<name>": "<value>" } JSON mapping into the 0600 <domain>.json file yourself before pi starts. Secrets persist at ~/.pi/agent/pi-lean-host/secrets/<domain>.json (mode 0600).

The status footer

Every api-guide / api-fetch result on an auth-bearing guide (static-key secretRefs/secretQueryRefs/secretPathRefs, or an oauth2 token slot) ends with a 🔑 auth: line — ok, requires <name> — not provisioned, or an optional state — showing name and presence only, never the value, so it's safe anywhere it renders.

Security & Scope

SSRF guard (server-supplied URLs only)

Host guards only the one URL path that earns it: paginate's nextLink style, where the next URL comes from the remote API's response body — a URL neither you nor the agent chose. ssrfGuard() (a minimal core/ssrf-guard.ts, no deps, no portal import) blocks loopback, private RFC1918 ranges, and cloud metadata endpoints (169.254.169.254, metadata.google.internal, 100.100.100.200) on that server-supplied nextUrl before it is followed.

Agent-supplied URLs are not guarded. restGet assembles a URL from apiHost + operation.path + query params — all agent- or user-directed. The agent runs on your own machine with your own privileges and has bash, read, and write over the filesystem; blocking http://169.254.169.254/ on restGet while curl is one tool-call away is a sieve with one hole patched. With keyed auth shipped, the guard is now load-bearing: a server-supplied nextUrl to an internal host would leak the attached Authorization header, and any keyed restGet/paginate that redirects is forced through the guarded loop — with injected secrets stripped on a cross-domain hop — so the channel stays closed.

v1 scope (what it is not)

  • GET-read only. No mutation helper. Add one only when a real retrieval guide needs it (e.g. a search-then-fetch POST), behind a real auth-review gate.
  • GET-read auth surface. auth.kind: static-key (store-backed header/query-param secrets) and auth.kind: oauth2 (client_credentials

Mission

pi-lean-host is a navigation and information-retrieval aide. It exists to help an agent obtain information from structured APIs with as little friction as possible — not to author content or automate online actions. Features whose primary value is creating, mutating, or bot-automating will not be added, even when technically feasible, unless they serve retrieval. The local-helper escape hatch means a capable user can build bots on their own machine; the package does not aim to make that easy.


pi-lean-host is part of the pi-lean-dimension web-tools suite.

License: AGPL-3.0-only