pi-read-guard

Pi extension that prevents massive unbudgeted full-file reads, steering the agent to offset/limit or search tools. Zero prompt tokens.

Packages

Package details

extension

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

$ pi install npm:pi-read-guard
Package
pi-read-guard
Version
0.1.13
Published
Nov 2, 2026
Downloads
1,543/mo · 75/wk
Author
greenspand
License
MIT
Types
extension
Size
19.8 KB
Dependencies
0 dependencies · 1 peer
Pi manifest JSON
{
  "extensions": [
    "./extensions/read-guard.ts"
  ]
}

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

README

pi-read-guard

npm npm downloads CI License: MIT Audit Fuzz

A pi extension that prevents massive unbudgeted full-file reads, steering the agent to offset/limit or search tools.

Enforcement lives in a tool_call hook, so it adds zero tokens to the system prompt.

Rules

  • read on any file with more than maxReadLines (default 200) without offset or limit is blocked.
  • Images, PDFs and any binary file are never blocked (a NUL byte in the first 8 KB, or an image/PDF extension): their newline bytes are noise, and blocking them would break screenshots.
  • The model is steered to read a slice with offset/limit, or to search first (rg, codegraph_search).

Install

From npm:

pi install npm:pi-read-guard

Or from git:

pi install git:github.com/sorinirimies/pi-read-guard

Previews

Recorded from a real pi with only this plugin loaded; a small scripted mock model plays the agent, so you see the real guard reacting to real tool calls.

A full read of a large file is blocked, the agent reads a slice (60 lines instead of 319):

The read is blocked and the agent reads lines 1 to 60

No bypass by spelling: ~/… and @… paths (which pi expands before reading) are blocked just like the plain one:

Reading via ~/ and @ is blocked too

Commands: status and block count, off / on, and allow <path> for one deliberate full read:

/read-guard status, off, on and allow

Commands

  • /read-guard: status and block count
  • /read-guard off / on: disable / enable for this session
  • /read-guard allow <path>: allow one full read of <path> this session

Config (optional)

~/.pi/agent/read-guard.json:

{ "maxReadLines": 200, "ignore": ["*.md", "*.json"] }

Security notes

  • A token saver, not a sandbox. It guards pi's read tool. An agent can still read files through bash (cat); the guard keeps accidental full-file reads out of your context, nothing more.
  • Same path as the tool. Paths are resolved exactly like pi's own read (@file, ~/file, file://, Unicode spaces, Windows shell paths, and the macOS filename variants it falls back to), so those spellings can't slip past it. A contract test compares it with pi's real resolveToCwd.
  • Fails open. pi blocks a tool when a tool_call handler throws, so any unexpected error here means "allow".
  • Constant memory. Line counts are streamed (64 KB chunks, early exit), never the whole file.
  • No network access, no shell commands, zero runtime dependencies.

Development

bun install
just check          # typecheck + tests + pack check + nushell tests (what CI runs)
just test           # bun test only (with coverage)
just coverage       # tests + a coverage table
just push-all       # push main to every configured remote (origin + the Gitea instances); `just --list` has pull-all, push-tags-all, release-all, the *-force and sync-gitea-* variants

Coverage: every bun test collects coverage (bunfig.toml) and fails below 95% lines / 95% functions. CI shows the table on the run page and uploads lcov.info.

CI scripts are nushell (scripts/), the same ones locally and in GitHub / Gitea Actions. just --list shows every task.

Demo recordings

The GIFs above live in examples/vhs/generated/ and are stored with Git LFS (git lfs install once). They are recorded with VHS from a real pi that loads only this extension, on synthetic data (examples/vhs/fixture.sh), never from real files, sessions or credentials. The agent is a scripted mock model (examples/vhs/mock-llm.ts), so the recording is repeatable.

just vhs-all          # every tape (examples/vhs/*.tape): needs vhs, ttyd, ffmpeg, pi, bun, python3
just vhs-tape overview  # one tape
just vhs-list         # list the tapes
just demo             # try it yourself in a real pi on the same synthetic data

Releases (automatic)

Workflow When What
CI push / PR quality gate on Linux; tests on macOS and Windows
Fuzz nightly (GitHub 04:00 UTC, Gitea 04:30), and when the extension changes seeded randomised robustness tests (tests/fuzz.test.ts), 200 000 runs per property; a failure prints the seed that reproduces it
Security nightly (GitHub 03:00 UTC, Gitea 03:30), and when dependencies change bun audit: fails on any known vulnerability
Auto-merge library updates CI finished on a Dependabot PR patch and minor updates (GitHub Actions) are merged automatically, but only after CI is green on that exact commit; a major update waits for you. Needs the GH_PAT secret: action bumps change .github/workflows/*, which GITHUB_TOKEN may not merge
Gitea Actions Update Mondays 03:15 UTC Gitea has no Dependabot: moves the sha pins in .gitea/workflows to the newest release of the same major and commits ci(deps); a new major is only reported
Nightly Dependency Update every night (GitHub 02:00 UTC) and after each auto-merge bun update within ranges, verify on all platforms, commit chore(deps); then build, tag and publish a new patch whenever a library was upgraded or merged, or feat/fix/perf commits are waiting since the last tag
Mirror GitHub (Gitea) hourly Gitea has no nightly of its own: fast-forwards its main to GitHub's and pushes the new tags (starting Gitea's release for each), so the hosts never diverge; fails loudly if they did
Release tag vX.Y.Z, or run it with a bump validates the tag against package.json, runs the gate, npm publish (idempotent, with provenance), creates the release

So library updates need no human: they are merged once CI passes, built, versioned and published as a patch. A downgrade, a failing check, or a major update stops the chain and waits for review.

Release from CI: Actions → Release → Run workflow with a bump (patch, minor, major), or just release-ci minor: runs the gate, bumps, writes the changelog, tags, pushes and publishes. Manual release: just release patch (or minor / major / X.Y.Z) bumps, tags and pushes to GitHub; just release-all patch pushes to every configured remote; just push-release-all pushes an existing bump.

Secrets (repo settings): NPM_TOKEN is an npm granular access token with publish rights and "bypass 2FA" (required for CI publishing). GH_PAT (classic PAT, repo + workflow: needed to auto-merge GitHub Actions bumps; also lets a tag push trigger the release itself). Optional: GITEA_TOKEN (Gitea).

Commits follow Conventional Commits; the changelog is generated by git-cliff.

Related plugins

Four small pi extensions that save tokens without adding anything to the system prompt:

Plugin What it does
pi-edit-first Blocks whole-file write rewrites and hand-written project manifests, steering the agent to targeted edit calls and scaffolders
pi-read-guard Blocks full reads of large files, steering the agent to offset/limit or search
pi-tokenburn Live token and cost counter in pi's footer, with charts and budgets
pi-context-diet Drops stale tool results from the context when the prompt cache is already cold, so long sessions cost less
pi install npm:pi-edit-first
pi install npm:pi-read-guard
pi install npm:pi-tokenburn
pi install npm:pi-context-diet

MIT