Pi Extensions 进阶:从安装社区扩展到自己定制
前一篇笔记讲了 extension 是什么、有什么示例、怎么写。这篇记录实际使用——怎么从官方和社区安装、以及怎么根据需求自己改造。
一、先自己写个简单的:show-system-prompt
动机
Agent 的系统提示词由多个来源拼合而成——角色定位、工具列表、AGENTS.md、项目上下文、技能清单。Claude/Openclaw 这样的 Agent 内置系统提示词就能吃掉几千个 token。但是像 Pi 这种我们可以轻松写一个 Extention 调试提示词效果,看一眼"LLM 当前实际收到的 prompt 到底是什么"。一个 /system-prompt 命令,把完整内容打印到终端。写
主要就两步:
捕获提示词。 通过 before_agent_start 事件捕获 Pi 发给 LLM 的完整 system prompt,存到变量里。这是"post-chained"的值——即所有 extension 修改之后、实际发送之前的版本。如果命令被调用时还没有 turn 跑过,就降级用 ctx.getSystemPrompt()。
全屏覆盖层显示。 用 ctx.ui.custom() 打开一个覆盖层,渲染 prompt 全文。handleInput 监听 Escape 或 q 关闭,render 用 wrapTextWithAnsi 处理终端宽度换行。加了宽度缓存,只在终端尺寸变化时重新计算。
示例代码
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
import { Key, matchesKey, wrapTextWithAnsi } from "@earendil-works/pi-tui";
export default function (pi: ExtensionAPI) {
let lastPrompt: string | undefined;
pi.on("before_agent_start", (event) => {
lastPrompt = event.systemPrompt;
});
pi.registerCommand("system-prompt", {
description: "Print the current session's system prompt",
handler: async (_args, ctx) => {
const prompt = lastPrompt ?? ctx.getSystemPrompt();
if (ctx.mode !== "tui") {
ctx.ui.notify(prompt, "info");
return;
}
await ctx.ui.custom<void>((_tui, _theme, _kb, done) => {
let cachedWidth = 0;
let cachedLines: string[] = [];
return {
render(width: number): string[] {
if (width !== cachedWidth) {
cachedWidth = width;
cachedLines = wrapTextWithAnsi(prompt, width);
}
return cachedLines;
},
handleInput(data: string): void {
if (matchesKey(data, Key.escape) || data === "q") done();
},
invalidate(): void { cachedWidth = 0; },
};
});
},
});
}
结果
输入 /system-prompt 后,终端全屏显示 Pi 当前 session 的完整系统提示词:
You are an expert coding assistant operating inside pi,
a coding agent harness. You help users by reading files,
executing commands, editing code, and writing new files.
Available tools:
- read: Read file contents
- bash: Execute bash commands
- edit: Make precise file edits
- write: Create or overwrite files
<project_context>
<project_instructions path="/path/to/AGENTS.md">
## Behavioral Guidelines
1. Think Before Coding
2. Simplicity First
...
</project_instructions>
</project_context>
The following skills provide specialized instructions:
<available_skills>
<skill>
<name>librarian</name>
<description>Research open-source libraries...</description>
<location>.../pi-web-access/skills/librarian/SKILL.md</location>
</skill>
</available_skills>
Current date: 2026-07-02
Current working directory: /path/to/myproject
按 Esc 或 q 关闭。
二、最值得装的官方示例扩展:permission-gate
为什么装它
Pi 默认不拦截任何工具调用。LLM 理论上可以执行 sudo rm -rf /,或者用 find / 扫描整个文件系统。这在日常使用中很少发生(现在模型足够智能),但是万一发生了。。。安全底线应该自己做,而不是依赖模型的判断。
permission-gate.ts 是官方示例之一,专门做这件事:拦截危险操作,弹窗确认。
安装
# 从官方示例目录复制
cp ~/.nvm/versions/node/v24.6.0/lib/node_modules/@earendil-works/pi-coding-agent/examples/extensions/permission-gate.ts \
~/.pi/agent/extensions/
# /reload 生效
它做了什么
const rules: DangerousRule[] = [
{ pattern: /\bfind\s+\/(?![a-zA-Z0-9_./])/i, label: "find-root", reason: "..." },
];
监听 tool_call 事件,检查 bash 命令是否匹配危险模式,以及 find 工具的 path 参数是否为 /。两种场景分开处理:
- 非交互模式(
ctx.hasUI === false):直接 block,不弹窗 - 交互模式:弹出
ctx.ui.select()让用户选择放行或阻止
简单改造
原始规则只写了几条示例命令规则,可以根据自己实际需求加了规则:
const rules: DangerousRule[] = [
// 全盘扫描
{ pattern: /\bfind\s+\/(?![a-zA-Z0-9_./])/i, label: "find-root", reason: "Full-filesystem scan" },
// 删除命令
{ pattern: /\brm\s+-rf\b/, label: "rm-rf", reason: "Recursive force delete" },
// sudo
{ pattern: /\bsudo\b/, label: "sudo", reason: "Privilege escalation" },
// 写入系统目录
{ pattern: /\b(write|cp|mv|tee)\s+.*\/(etc|usr|opt|var)\//, label: "sys-write", reason: "Writing to system path" },
// curl/wget 到管道执行的模式
{ pattern: /\bcurl\s+\S+\s*\|/, label: "curl-pipe", reason: "curl piped to shell" },
];
纯数据改动,不需要改逻辑。这个扩展设计得很好——规则是声明式的列表,加新规则就增加一行对象。不过纯基于正则表达式过于简单了,现在聪明一点模型其实很容易绕过。
还可以怎么改
- 加白名单:某些项目目录里可以放行特定操作
- 加日志:block 或 pass 时记录一个日志文件,用于审计
- 加"记住选择":和
project-trust事件联动,一个项目里放行过一次就不再问 - 加学习模式:如果用户总是对某个模式选 Yes,自动把它加入默认放行列表
三、其他建议装的官方示例插件
官方 70 多个示例不要都装。有的是演示特定 API 的教学代码,有的功能已被主线集成,有的对日常工作价值不大。
已被主线集成,没必要装
| 示例 | 被什么替代 |
|---|---|
shutdown-command.ts |
Pi 内置 /quit |
session-name.ts |
Pi 内置 /name |
bookmark.ts |
/tree 中 Shift+L 已支持标签 |
这三份源码仍然有学习价值(看底层 API setSessionName、setLabel 的用法),但安装是多余的。
必装(安全底线)
permission-gate.ts — 上面讲过了。这是唯一一个我建议所有用户都装的 extension。Pi 不替你做安全判断,你得自己做。
强烈推荐(日常高频使用)
question.ts — LLM 可调用的选择/填空工具。不需要 LLM 猜测你的意图,也用不着一轮对话来回确认,直接弹窗问。支持选项列表 + 自定义输入,按 Enter 选择,Esc 取消,上下键导航。结果是结构化的 { answer, wasCustom },LLM 能可靠地解析。
安装方式一样:从官方 examples 目录 cp 到 ~/.pi/agent/extensions/。
plan-mode/ — Claude Code 风格的 plan mode。/plan 切换只读探索模式,禁用编辑/写入工具,bash 只放行 ls、grep、cat 等安全命令。Agent 输出 Plan: 开头的编号步骤后,可以选择进入执行模式。执行过程中用 [DONE:n] 标记完成进度。
安装时注意是目录,需要整个复制:
cp -r ~/.nvm/versions/.../examples/extensions/plan-mode ~/.pi/agent/extensions/
todo.ts — LLM 可调用的 todo 工具。Agent 可以 add / list / toggle / clear,用户用 /todos 命令查看列表。状态持久化在 session entry 的 details 里,fork 时自动正确。
handoff.ts — 会话交接。当你一个 session 聊得太多上下文太长,/handoff <目标> 会调用一个小模型(默认)把当前上下文总结成一个精炼的新 prompt,创建新 session 并在编辑器里预填。比直接 /new 再口头复述高效得多。
安装:
cp ~/.nvm/versions/.../examples/extensions/handoff.ts ~/.pi/agent/extensions/
按需选择
| 场景 | 推荐 |
|---|---|
| 用 Git 且切换 session 频繁 | dirty-repo-guard.ts + git-checkpoint.ts |
| 想换一种压缩策略 | custom-compaction.ts |
| 有自定义 Provider | custom-provider-anthropic/ 参考 |
| 想在终端玩贪吃蛇 | snake.ts |
| 研究 extension 怎么写 UI | message-renderer.ts、question.ts、custom-footer.ts |
四、从社区安装 extention: 以 pi-md-export 为例
它是什么
我最常用的一个社区 extension,源自 npm 的 pi-md-export)。它提供一个 /md 命令,把当前 session 的对话导出为 Markdown 文件,我改造了一下,让它放到我的 Obsidian vault 的 pi/ 目录里。
文件名格式:<YYYY-MM-DD>-<项目名>-<slug>.md
安装
pi install npm:pi-md-export
# /reload
它能做什么
/md 命令支持多种模式:
/md— 导出当前 branch(不含 tool calls 和 thinking)/md tc— 包含所有 tool calls/md tc -bash -read— 排除 bash 和 read 的 tool calls/md tc +question— 只要 question 的 tool calls/md t— 包含 thinking block/md all— 整个 session 文件的所有消息(不仅仅是当前 branch)/md 3— 只导出最近 3 轮对话
执行后弹出选择:保存为 .md 文件还是复制到剪贴板。
我的改造
原始代码默认输出到一个目录,我改成写到我的 Obsidian vault 的目录。
- 支持
PI_MD_VAULT_DIR环境变量 — 不改代码,通过环境变量覆盖输出路径export PI_MD_VAULT_DIR="/path/to/obsidian/ingest/pi" - 文件名改进 — 加上
YYYYMMDD-前缀 - slug 自动建议 — 从对话第一句用户消息提取关键词,作为 slug 的默认值,用户可以直接按 Enter 接受
装它解决了什么
我在 Obsidian 里的工作流是:Pi 聊完一段内容后,/md 导出 → 自动保存到 obsidian/pi/ → Obsidian 自动索引 → 可以在笔记库里查看对话。这个过程从"手动复制粘贴"变成了"一个命令+按 Enter"。
五、当前的扩展清单
~/.pi/agent/extensions/
├── show-system-prompt.ts # 自写:查看系统提示词
├── permission-gate.ts # 官方:危险操作拦截(已改造)
├── question.ts # 官方:LLM 可调用的选择工具
├── todo.ts # 官方:Todo 管理
├── handoff.ts # 官方:会话交接
├── plan-mode/ # 官方:只读探索模式
│ ├── index.ts
│ ├── utils.ts
│ └── README.md
└── pi-markdown-export.ts # 社区: 可以导出到 Obsidian vault