npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@johansja/pi-permission-gate

v0.7.0

Published

LLM-powered safety gate for pi: classifies bash and MCP tool calls by risk before execution, with CWD-aware judgments.

Readme

pi-permission-gate

LLM-powered safety gate for pi. Instead of maintaining regex patterns, a fast model judges each bash command and MCP tool call by risk level before execution — with CWD-aware context so project-local operations are treated as less risky than system-wide equivalents.

Install

pi install npm:@johansja/pi-permission-gate

Or from git (pinned-ref friendly):

pi install git:github.com/johansja/pi-permission-gate

Try without installing:

pi -e npm:@johansja/pi-permission-gate

Update:

pi update --extensions

How it works

Each tool_call for bash or mcp is classified by a fast/cheap model via ctx.modelRegistry.complete(). The model returns {risk, reason}. Risk is compared to your blockLevel threshold:

  • safe — auto-allowed (read-only: ls, cat, git status, git log, …)
  • low — reversible/CWD-scoped (rm -rf ./build, npm install, git commit, git checkout, …)
  • medium — significant/external (git push, kubectl apply, helm install, npm publish, …)
  • high — destructive/irreversible (sudo, rm -rf /etc, DROP TABLE, git push --force, shutdown, …)

At or above blockLevel → confirm via TUI prompt (or block in headless). Below → allow. safe is always allowed even at blockLevel=safe (carve-out prevents threshold-0 false blocks).

CWD is passed to the model so rm -rf ./build is low but rm -rf /etc is high — no post-hoc heuristics.

The runtime resolves auth and endpoints, so OAuth-only providers (Claude Pro/Max, ChatGPT Plus, Copilot) and env-scoped provider configs classify correctly, not just API-key providers.

Configuration (precedence: settings.json > default)

~/.pi/agent/settings.json:

{
  "permissionGate": {
    "model": "anthropic/claude-sonnet-4-5",
    "blockLevel": "low",
    "maxTokens": 4096,
    "temperature": 0
  }
}

| Field | Default | Description | |---|---|---| | model | session model | Model for classification (provider/modelId or bare id) | | blockLevel | low | Minimum risk to block: low | medium | high | | fallback | confirm | If LLM fails: allow | block | confirm | | maxTokens | 4096 | Max tokens for the classification call | | temperature | unset | Sampling temperature (e.g. 0 or 0.1) |

Retry and timeout (retry.provider)

The gate's retry/timeout budget comes from pi's own retry.provider block — the same config that governs chat turns — read once per tool call and forwarded into complete(). See ADR 0006 (amends 0004/0005).

{
  "retry": {
    "provider": {
      "maxRetries": 5
    }
  }
}

| retry.provider field | Default | Description | |---|---|---| | maxRetries | 0 | Retries on transient HTTP 429/5xx and per-attempt timeout. Note the default: with no retry.provider block the gate makes a single attempt, then fallback applies. | | maxRetryDelayMs | 60000 | Ceiling on server-requested Retry-After. If the server requests a longer delay, retryProviderRequest throws (→ fallback) — it does not clamp-and-retry. Exponential backoff (no Retry-After header) is hardcoded by pi-ai (min(0.5·2ⁿ, 8)s), independent of this field. 429 and 503 are treated identically. | | timeoutMs | SDK default | Per-attempt timeout in ms. Timeout is retried alongside 429/5xx; not a whole-session envelope. See ADR 0005. |

The budget is shared with chat turns — tuning it for one tunes both. Note the gate consumes it synchronously per tool call: a large maxRetries × timeoutMs product delays every command during a provider incident before fallback fires.

Agent-level retry.* (enabled/maxRetries/baseDelayMs) does not apply to the gate: it wraps whole chat turns in pi's agent loop, which extension complete() calls never enter.

Migrating from <0.7.0: permissionGate.maxRetries, permissionGate.maxRetryDelayMs, and permissionGate.timeout were removed and are silently ignored (previously defaulted to 3, 5000, and 10000). permissionGate.thinkingLevel was also removed — it never reached the model (ctx.modelRegistry.complete() routes to the provider's stream, which drops reasoning; the clamping streamSimple path is not exposed to extensions), and the classifier always runs the model's intrinsic reasoning. Move maxRetries to retry.provider.maxRetries — set it explicitly if you want any retries, since the pi default is 0 — and maxRetryDelayMs/timeoutMs likewise if you don't want the pi/SDK defaults.

blockLevel semantics

| Level | Blocks | Allows | |---|---|---| | low | low, medium, high | safe (safest, most confirms) | | medium | medium, high | safe, low | | high | high | safe, low, medium (fewest confirms) |

fallback semantics (LLM call failed)

| Policy | UI | Headless | |---|---|---| | allow | allow | allow | | block | block | block | | confirm | confirm (unknown risk) | block (headless can't prompt; fail-closed) |

Default confirm is safety-favoring: headless classifier-failures fail-closed, not fail-open.

Logging

Decisions are appended to ~/.pi/pi-permission-gate.jsonl (timestamp, command, risk, blockLevel, decision, reason; raw LLM response attached only on parse failure, capped at 2000 chars).

Development

npm install
npm test

Tests cover the risk taxonomy, the fallback × hasUI decision matrix, the threshold safe-edge carve-out, and the hardened JSON verdict parser (reasoning models wrapping JSON in prose). No build step — pi loads .ts via tsx at runtime.

License

MIT