@aaronkyriesenbach/pi-substitute-commands
Pi extension that hard-blocks agent-issued bash tool calls containing disallowed commands and suggests replacements.
Package details
Install @aaronkyriesenbach/pi-substitute-commands from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:@aaronkyriesenbach/pi-substitute-commands- Package
@aaronkyriesenbach/pi-substitute-commands- Version
1.0.0- Published
- Aug 5, 2026
- Downloads
- 228/mo · 19/wk
- Author
- aaronkyriesenbach
- License
- MIT
- Types
- extension
- Size
- 17.7 KB
- Dependencies
- 1 dependency · 1 peer
Pi manifest JSON
{
"extensions": [
"./extensions/index.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
pi-substitute-commands
A pi extension that hard-blocks agent-issued bash tool
calls containing a disallowed command, telling the agent to use the
recommended replacement instead. Ships with one Substitution Pair (find/
grep family → fd/rg), structured so future pairs are a small code
change.
Why
Agent instructions frequently say "use fd/rg instead of find/grep" —
they're faster, respect .gitignore, and have saner defaults — but a prose
instruction is easy for an agent to forget once a command gets wrapped in
sudo, piped through xargs, buried inside a bash -c string, or spawned
from a find -exec. This extension enforces that guidance structurally: it
parses every bash tool call the agent issues, resolves the command down to
what would actually execute, and blocks the call outright if find/grep/
egrep/fgrep/zgrep would run — no matter how deeply it's nested inside
wrappers, subshells, or command substitutions.
What it blocks
The check parses the command with unbash
and looks for a blocked command name in Command Position — the leading
word of a command, or the resolved sub-command of a supported wrapper —
anywhere in the parsed structure. A few examples:
| Command | Blocked because |
|---|---|
grep -rn "TODO" src/ |
grep in Command Position — use rg |
find . -name "*.ts" |
find in Command Position — use fd |
sudo grep -r "secret" /etc |
grep behind a sudo passthrough wrapper |
find . -type f -exec grep -l foo {} \; |
grep inside find -exec's sub-command |
bash -c "cat a.txt | grep foo" |
grep inside a bash -c nested script |
echo "$(grep -c foo file)" |
grep inside a $(...) command substitution |
git grep/git-grep is exempt — it invokes git's own pattern search, not
the standalone grep binary. If the command can't be parsed with
confidence (a syntax error, or anything unbash reports parse errors for),
the check fails open: the call is allowed through unblocked rather than
guessed at. The extension only ever inspects the command — it never
rewrites or mutates it.
Resolution follows Wrapper Unwrapping through:
- Passthrough wrappers:
sudo,xargs,nice,nohup,env,strace - Flag wrappers with a nested script:
bash -c,sh -c,zsh -c - Exec wrappers:
find -exec/-ok,fd -x/--exec/-X/--exec-batch
and recurses into command substitutions ($(...) and backticks) and
subshells ((...)) wherever they appear, so a blocked command nested
several layers deep is still caught.
When a call is blocked, the agent sees a reason naming every distinct disallowed command found and its recommended replacement, e.g.:
Blocked: this command uses disallowed command(s): `grep` (use `rg` instead), `find` (use `fd` instead).
Install
pi install npm:@aaronkyriesenbach/pi-substitute-commands
Or add it to .pi/settings.json / ~/.pi/agent/settings.json:
{
"packages": ["npm:@aaronkyriesenbach/pi-substitute-commands"]
}
Development
bun install
bun run typecheck
bun run lint
bun run format:check
bun run test:coverage
License
MIT