pi-edit-approval
v0.2.0
Published
Pi extension: confirm before write/edit with profiles, diff preview, and persistent allow/deny memory. Reads always allowed.
Maintainers
Readme
pi-edit-approval
Pi coding-agent extension that asks before the agent modifies files.
- Reads stay free —
read/grep/find/lsare never prompted - Writes need approval — built-in
write/edit, plus common mutator tool names (e.g. MCPreplace) - Profiles —
strict/workspace/review/offpresets - Diff preview — approval UI shows a compact unified-style diff for edits
- Persistent memory — always allow/deny a file or directory across sessions
- Protected paths — hard-deny for
.env, keys, etc. - Workspace-aware — separate policies inside vs outside the git root (or cwd)
Keywords: pi-package, pi, pi-extension, pi-coding-agent.
Pi has no built-in “confirm before edit” switch. This package adds that gate via the extension
tool_callhook.
Install
Requires Pi (@earendil-works/pi-coding-agent).
You can install from npm or GitHub.
From npm (recommended)
pi install npm:pi-edit-approvalPin a version:
pi install npm:[email protected]From GitHub
pi install git:github.com/ShawnMa123/pi-edit-approvalpi install https://github.com/ShawnMa123/pi-edit-approval
pi install git:github.com/ShawnMa123/[email protected]Try once without installing
pi -e npm:pi-edit-approval
pi -e git:github.com/ShawnMa123/pi-edit-approvalLocal path (development)
pi install /path/to/pi-edit-approval
pi -e ./extensions/edit-approval.tsProject-local install
pi install -l npm:pi-edit-approval
pi install -l git:github.com/ShawnMa123/pi-edit-approvalAfter install, start a new session or run /reload.
If you previously copied edit-approval.ts into ~/.pi/agent/extensions/, remove that copy to avoid loading the extension twice.
Profiles
| Profile | Inside workspace | Outside workspace | Notes |
|---------|------------------|-------------------|--------|
| strict (default) | ask | ask | Safest everyday default |
| workspace | allow | ask | Codex-style workspace-write |
| review | ask | ask | Larger diff preview (diffMaxLines: 80) |
| off | allow | allow | Disables write gating |
Switch live (persists to config):
/edit-approval profile workspace
/edit-approval profile strictDefault behavior
| Tool / path | Default (strict) |
|-------------|--------------------|
| read, grep, find, ls | Always allow |
| write / edit inside workspace | Ask (+ diff preview) |
| write / edit outside workspace | Ask (labeled OUTSIDE workspace) |
| Paths matching protectedPaths | Deny (no prompt) |
| Paths in persistent allow memory | Allow |
| Paths in persistent deny memory | Deny |
| bash | Not gated (see gateBash) |
| Non-interactive (-p / no UI) when policy is ask | Block |
Workspace root = git rev-parse --show-toplevel when available, otherwise the session cwd.
Prompt choices
- Yes — this call only
- Yes, this file this session
- Yes, all workspace writes this session (if enabled)
- Yes, all outside writes this session (if enabled)
- Yes, always allow this file — persists to memory
- Yes, always allow this directory — persists prefix allow
- No
- No, this file this session
- No, always deny this file — persists to memory
Configuration
Optional file:
~/.pi/agent/edit-approval.jsonCopy the example from the package:
# from a clone / install tree
cp edit-approval.example.json ~/.pi/agent/edit-approval.jsonCopy-Item edit-approval.example.json $env:USERPROFILE\.pi\agent\edit-approval.jsonExample: strict (default)
{
"profile": "strict",
"persistentMemory": true,
"diffMaxLines": 40,
"protectedPaths": [".env", ".env.", "id_rsa", "id_ed25519", ".npmrc", "credentials"],
"gateBash": false,
"extraMutatingTools": []
}Example: Codex-style workspace-write
{
"profile": "workspace"
}Or:
{
"profile": "strict",
"workspaceWrites": "allow",
"outsideWrites": "ask"
}Explicit workspaceWrites / outsideWrites override the profile baseline when set in the JSON file./edit-approval profile <name> rewrites those fields to match the preset.
Options
| Key | Type | Default | Meaning |
|-----|------|---------|---------|
| profile | strict | workspace | review | off | strict | Named preset |
| workspaceWrites | allow | ask | deny | from profile | Inside workspace |
| outsideWrites | allow | ask | deny | from profile | Outside workspace |
| allowSessionBypassInWorkspace | boolean | true | Offer session-wide workspace allow |
| allowSessionBypassOutside | boolean | false | Offer session-wide outside allow |
| persistentMemory | boolean | true | Offer always-allow / always-deny |
| diffMaxLines | number | 40 | Diff/preview size in the prompt |
| protectedPaths | string[] | see example | Substring / *.ext hard-deny |
| gateBash | boolean | false | Confirm every bash (noisy) |
| extraMutatingTools | string[] | [] | Extra tool names to gate |
Reload without restarting Pi:
/edit-approval reloadPersistent memory
File:
~/.pi/agent/edit-approval-memory.json{
"allowPaths": [],
"allowPrefixes": [],
"denyPaths": [],
"denyPrefixes": []
}Managed from the approval UI or:
/edit-approval allow path/to/file
/edit-approval deny path/to/file
/edit-approval forget path/to/file
/edit-approval memoryOrder of checks (first match wins where applicable):
- Session deny
- Protected paths → deny
- Memory deny → deny
- Memory allow → allow
- Session allow / session workspace|outside bypass → allow
- Policy
allow/ask/deny
Commands
| Command | Action |
|---------|--------|
| /edit-approval | Status (profile, policies, session, memory counts) |
| /edit-approval reset | Clear session allow/deny bypasses |
| /edit-approval reload | Re-read config + memory files |
| /edit-approval profile <name> | Set profile and persist |
| /edit-approval allow <path> | Persist allow path |
| /edit-approval deny <path> | Persist deny path |
| /edit-approval forget <path> | Remove path from memory |
| /edit-approval memory | Dump memory entries |
What gets gated
Always treated as mutators:
- Built-in
write,edit
Also matched by name (case-insensitive), including MCP-style names:
replace,create_file,delete_file,apply_patch,str_replace- Patterns like
fastctx_replace,mcp_foo_write, …
Path fields inspected:path, file_path, filePath, filepath, target, target_path, filename, paths[]
Edit preview also understands oldText / newText and old_string / new_string.
Limitations
bashcan bypass file gates unlessgateBashistrue. Redirects are not parsed by default.- Not an OS sandbox — only Pi tool calls this extension sees.
- Diff preview is a compact summary, not a full side-by-side reviewer (
pi-show-diffsis complementary). - In print / headless modes without UI,
askbecomes a hard block.
For stronger isolation, use containers or a sandbox package; see Pi security docs.
Package layout
pi-edit-approval/
├── package.json
├── extensions/edit-approval.ts
├── edit-approval.example.json
├── CHANGELOG.md
├── LICENSE
└── README.md{
"keywords": ["pi-package", "pi", "pi-extension", "pi-coding-agent"],
"pi": { "extensions": ["./extensions"] }
}The pi-package keyword is used by the Pi package gallery.
Development
git clone https://github.com/ShawnMa123/pi-edit-approval.git
cd pi-edit-approval
pi -e ./extensions/edit-approval.ts
# or
pi install .No build step: Pi loads the TypeScript extension directly.
Uninstall
pi remove npm:pi-edit-approval
pi remove git:github.com/ShawnMa123/pi-edit-approvalOptional cleanup:
rm ~/.pi/agent/edit-approval.json
rm ~/.pi/agent/edit-approval-memory.jsonLicense
Related
- Pi packages documentation
- Pi extensions documentation
- Upstream examples:
permission-gate.ts,protected-paths.ts - Complementary:
pi-show-diffs(full diff UI) - Heavier alternatives: various
pi-permission-*packages
