- TypeScript 99.8%
- Shell 0.2%
|
|
||
|---|---|---|
| extensions | ||
| mise-tasks | ||
| test-utils | ||
| AGENTS.md | ||
| mise.toml | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| renovate.json | ||
| tsconfig.json | ||
| vitest.config.ts | ||
Pi extensions
My dumb Pi extensions.
| Extension | Description |
|---|---|
| bash-permissions | Gates the bash tool behind allow/ask/deny rules with interactive approval |
| edit-diff | Modifies the edit tool to provide the LLM a diff with context after a change has been made |
| edit-permissions | Gates file edits behind user approval |
| plan-mode | Basic plan mode which disables edit tools (but not bash) |
| subagent (disabled) | Allows the LLM to create subagents for performing various tasks (e.g. codebase exploration) with specific sets of tools and models |
| web-tools | Allows the LLM to search the web and fetch content from the web. Supports HTTP pages via Exa or Kagi, and clones GitHub repositories |
bash-permissions
Every bash tool call is parsed with tree-sitter-bash and split into its
constituent simple commands (pipeline segments, &&/;/|| list members,
subshells, command/process substitutions — including ones nested in strings,
heredocs, and environment assignments). Commands embedded in
find -exec/-execdir/-ok/-okdir clauses and rg --pre preprocessors are
extracted and checked as separate commands too, so find * alone never
approves an -exec payload and rg alone never approves a --pre payload.
Each command is checked against the deny/ask/allow rules; anything denied is
blocked, anything matched by an ask rule prompts for confirmation, and
anything unmatched triggers an approval prompt:
- Allow once — run it, persist nothing
- Always allow (project) / Always allow (global) — persist an allow rule
(only offered for unmatched commands;
ask-matched commands can only be allowed once) - Deny (or dismissing the dialog) — block the tool call
A command that fails to parse is treated as one opaque unit and can only be allowed once or denied — no rule is ever persisted from unparseable input.
Output redirections that may create or modify a file (>, >>, >|, &>,
&>>, and file-targeting >&) use the same policy and session decision as the
edit-permissions extension: Allow once, Allow all edits for session,
Deny once, or Deny all edits for session. Edit permission and command
permission are independent, so allowing the write does not approve an
unmatched or ask-matched command. As with edit/write tool calls, the write
check permits redirects when no interactive UI is available unless the session
already has a deny-all decision; ordinary bash no-UI rules still apply.
The edit check is not applied to harmless routes: input redirects,
heredocs/herestrings, descriptor duplication or closure, process substitution,
or known output sinks (/dev/null, standard-output and standard-error device/fd
paths). A dynamic destination is treated conservatively as a possible file
write.
Use /bash-permissions to list the loaded rules and their source files.
Rule files
| Scope | Path |
|---|---|
| project | <cwd>/.pi/bash-permissions.json |
| global | ~/.pi/agent/bash-permissions.json |
Both files are loaded and merged on every check, so hand edits apply immediately. Format:
{
"allow": ["git status", "less *", "npm run *", "sed *"],
"ask": ["rm *"],
"deny": [
"sudo *",
{"command": "perl *", "reason": "use read/edit/write tools instead of perl"}
],
"commandPolicies": {
"sed": {
"normal": "allow",
"inPlace": "ask"
}
}
}
deny entries may be either a plain string or an object { "command": ..., "reason": ... }.
When a reason is present it is appended to the block message (e.g.
bash-permissions: denied by rule: perl -pi file — use read/edit/write tools instead of perl)
and shown next to the rule in /bash-permissions. allow and ask remain
plain strings.
Command policies
commandPolicies applies semantic allow/ask/deny decisions to command modes
which are awkward to express with token-prefix rules. sed is the only
supported command initially:
normal— no in-place option was detected.inPlace—-i,-I(BSD),--in-place, or a bundled/suffixed form such as-nior-i.bakwas detected. A dynamic token before--is classified conservatively asinPlacebecause its runtime value could enable the mode.
The classifier recognizes sed by basename, so /usr/bin/sed is covered too.
Each mode is optional; an unconfigured mode falls back to the ordinary command
rules. Policies and rules combine conservatively using deny > ask > allow,
and project/global policy conflicts use the same ordering. Thus an
"inPlace": "ask" policy still prompts under allow: ["sed *"], while a deny
rule still wins over normal: "allow". Policy-triggered prompts are one-shot and block
when no UI is available.
normal means only that no in-place option was found; it is not a guarantee
that an arbitrary sed program has no side effects. In particular, sed programs
can write with w/W, and some implementations support command execution.
Use ordinary ask/deny rules if untrusted sed programs must not run.
Matching semantics
Rules are whitespace-separated token lists matched against the parsed tokens of each command:
- A
*as the final token matches zero or more remaining arguments:less *matchesless,less foo.txt, andless -N foo.txt, but neverlessc x. Anywhere else,*is a literal. - Without a trailing
*the match is exact:git statusdoes not matchgit status -sb. - Deny decisions win over ask decisions, which win over allow decisions,
including decisions from
commandPolicies. - Tokens are matched as raw source text, quotes included.
Caveats
- Wrapper commands other than
findare matched as themselves:sudo rm -rf /matches rules forsudo, notrm. Don't allow-listsudo *,env *,xargs *,timeout *, etc. unless you mean it. (findandrgare special-cased:find's-exec-style payloads andrg's--prepreprocessor are checked as separate commands.) - Constructs that run nothing by themselves are implicitly allowed:
standalone variable assignments,
[[ … ]], and arithmetic. Any command substitution inside them is still extracted and checked. - The project rules file is honored only when the project is trusted
(
ctx.isProjectTrusted()). In untrusted projects it is ignored entirely and the prompt offers no "Always allow (project)" option.
Flags
--bash-permissions <block|allow> controls what happens to unmatched
commands when no interactive UI is available (e.g. print mode). The default
is block. Ask rules always block when there is no UI to confirm them. This
flag controls command authorization only; redirection writes follow the
edit-permissions session policy described above.
subagent
Each agent's model: frontmatter field names a model type rather than a
concrete model. At spawn time, the type is resolved to an actual model string
and passed to the spawned pi as --model <value>.
Resolution order
- Environment variable
AGENT_MODEL_<TYPE>(case-sensitive, must match the type name exactly). - Entry in
~/.pi/agent/subagent.jsonunder the type name. - For the type
defaultonly: whatever model Pi is currently using (ctx.model.id). This lets an agent inherit the main session's model. - No match → no
--modelflag is set; the spawnedpiuses its own default.
Example
Given an agent with model: small in its frontmatter, this config:
{
"small": "deepseek/deepseek-v4-flash",
"large": "openrouter/minimax/minimax-m3"
}
…or this environment variable:
export AGENT_MODEL_small=deepseek/deepseek-v4-flash
…will cause the subagent to be spawned with --model deepseek/deepseek-v4-flash.
Env vars always take precedence over ~/.pi/agent/subagent.json. Missing or
malformed config files and empty/non-string values are silently ignored.
Config reference (~/.pi/agent/subagent.json)
The file is a flat JSON object. Keys are model type names, values are model
strings (e.g. "openai/gpt-4o-mini", "anthropic/claude-opus-4-5").
web-tools
Supports two search providers: Exa and Kagi.
Select via searchProvider in ~/.pi/agent/web.json (defaults to "exa").
Exa setup
Set EXA_API_KEY env var or exaApiKey in ~/.pi/agent/web.json:
{
"searchProvider": "exa",
"exaApiKey": "abcd123"
}
Kagi setup
Set KAGI_API_KEY env var or kagiApiKey in ~/.pi/agent/web.json:
{
"searchProvider": "kagi",
"kagiApiKey": "abcd123"
}
Config reference (~/.pi/agent/web.json)
| Field | Type | Default | Description |
|---|---|---|---|
searchProvider |
"exa" | "kagi" |
"exa" |
Which search provider to use |
exaApiKey |
string |
— | Exa API key (env: EXA_API_KEY) |
kagiApiKey |
string |
— | Kagi API key (env: KAGI_API_KEY) |
Env vars always take precedence over config file values.