使用 pi 打造属于你自己的 Coding CLI
当前主流的 AI 编程 CLI 工具,工程复杂度已经远超必要。
最基础的交互无非是发送 prompt、获取回答,但层层封装的 harness 架构把这些工具变成了吞金巨兽。发一句 hi 就消耗掉当天 10% 的模型配额;MCP 服务器空挂着不做事,常驻占用 15% 左右的上下文窗口;Plan Mode、Subagents 本质上只是上下文管理的技巧。
为了让 AI 辅助编程,你真的需要这些吗?
🧠 一、pi 是什么
pi 由 mariozechner 开发的一个开放、模块化的 AI Coding Agent CLI 工具
目前主流的 AI 编程工具(Claude Code、Cursor、Copilot)都是黑盒:系统提示词里写了什么你不知道,.env 何时被读取你无法控制。每一个"高级功能"的背后,都在消耗你看不见的上下文配额。
pi 将所有接口和 hooks 完全开放,提供 100% 的定制能力:
- 🔌 可扩展(Extensions):用 TypeScript 编写插件,拦截每一次工具调用,在 AI 执行前进行审查
- 📝 可定制(Prompts):将工程规范、代码风格、最佳实践编写为 Prompt 模板,每次对话自动注入
- 🛠️ 可复用(Skills):将常用的复杂工作流打包为 Skill,让 AI 按照标准流程自动执行
- 📦 可分发(Packages):打包成 npm 包,一行命令分享给团队
你甚至可以用 pi 来改造 pi 本身。
本文使用的示例项目 safe-coder,是一个专为 pi 设计的安全防护配置包。下面逐一拆解它的组成部分。
📦 二、安装 pi
前提条件
- Node.js(建议 LTS 版本)
- npm / pnpm / yarn
全局安装 pi
npm install -g @mariozechner/pi-coding-agent
安装完成后即可使用 pi 命令。
pi 支持从 npm 直接安装配置包:
pi install npm:safe-coder
这会将 safe-coder 的所有扩展、Skill、Prompt 模板加载到 pi 中。
在项目中启动 pi
cd /your/project
pi .
pi 会自动扫描项目目录下的配置,加载所有扩展和 Skill,进入交互界面。
🔌 三、开发 Extensions:给 Pi 加装自定义机制
Extension 是 pi 最核心的能力。你可以用 TypeScript 编写函数,监听 AI 的每一次工具调用,决定放行、拦截或弹出确认框由用户决定。
Extension 的基本结构
每个 Extension 是一个默认导出的函数,接收 ExtensionAPI 对象:
// ~/.pi/agent/extensions/my-extension.ts
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
export default function (pi: ExtensionAPI) {
pi.on("tool_call", async (event, ctx) => {
// event.toolName: "bash" | "read" | "write" | "edit" ...
// event.input: 工具的输入参数
// ctx.cwd: pi 启动时的工作目录
// ctx.ui: 用于弹窗、通知等 UI 操作
// 返回 undefined 表示放行
// 返回 { block: true, reason: "..." } 表示拦截
});
}
Extension 文件支持直接用 TypeScript 编写,无需编译。pi 内部使用 jiti 即时加载。
实例一:权限门(Permission Gate)
文件:extensions/permission-gate.ts
这是 safe-coder 最核心的扩展,包含两部分功能:
- 危险命令检测:拦截 rm -rf、sudo、chmod 777 等高危操作,弹出确认框
- 工作目录边界保护:AI 尝试读写 pi 启动目录之外的文件时,必须先经过确认
import path from "node:path";
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
/** 判断目标路径是否在工作目录之外 */
function isOutsideCwd(cwd: string, targetPath: string): boolean {
const resolved = path.resolve(cwd, targetPath);
const rel = path.relative(cwd, resolved);
return rel.startsWith("..") || path.isAbsolute(rel);
}
export default function (pi: ExtensionAPI) {
// 危险命令的正则匹配规则
const dangerousPatterns = [
/\brm\s+(-rf?|--recursive)/i,
/\bsudo\b/i,
/\b(chmod|chown)\b.*777/i,
];
// 启发式检测:命令是否可能触碰工作目录之外的路径
const outsideCwdPatterns = [
/(\s|^|["'\`])\/\S+/, // 绝对路径
/(\s|^|["'\`])~\//, // home 目录路径
/\.\.\//, // 相对上级路径
];
pi.on("tool_call", async (event, ctx) => {
const cwd = path.resolve(ctx.cwd);
let reasonLabel: string | null = null;
let detail = "";
// 检查文件操作是否跨越工作目录边界
if (["read", "write", "edit"].includes(event.toolName)) {
const targetPath = event.input.path as string | undefined;
if (targetPath && isOutsideCwd(cwd, targetPath)) {
reasonLabel = "Path is outside current working directory";
detail = \`${event.toolName}: ${targetPath}\n\nCWD: ${cwd}\`;
}
}
// 检查 bash 命令是否包含危险操作
if (event.toolName === "bash" && !reasonLabel) {
const command = event.input.command as string;
if (dangerousPatterns.some((p) => p.test(command))) {
reasonLabel = "Dangerous command";
detail = command;
} else if (outsideCwdPatterns.some((p) => p.test(command))) {
reasonLabel = "Command may touch paths outside current working directory";
detail = \`${command}\n\nCWD: ${cwd}\`;
}
}
if (!reasonLabel) return undefined; // 放行
// 无 UI 环境(如 CI)则直接拦截
if (!ctx.hasUI) {
return { block: true, reason: \`${reasonLabel} (no UI for confirmation)\` };
}
// 弹出确认框,由用户决定
const choice = await ctx.ui.select(
\`⚠️ ${reasonLabel}:\n\n${detail}\n\nAllow?\`,
["Yes", "No"]
);
return choice !== "Yes" ? { block: true, reason: "Blocked by user" } : undefined;
});
}
实例二:路径保护(Protected Paths)
文件:extensions/protected-paths.ts
对 .env 文件的任何操作(读、写、编辑)全部拦截;对 .git/ 和 node_modules/ 只拦截写入和编辑操作。
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
export default function (pi: ExtensionAPI) {
const protectedPaths = [".env", ".git/", "node_modules/"];
pi.on("tool_call", async (event, ctx) => {
const path = event.input.path as string;
// .env 文件:任何操作都拦截
if (path.includes(".env")) {
if (ctx.hasUI) {
ctx.ui.notify(\`Blocked access to protected path: ${path}\`, "warning");
}
return { block: true, reason: \`Path "${path}" is fully protected\` };
}
// 其他受保护路径:只拦截写入和编辑
if (event.toolName !== "write" && event.toolName !== "edit") {
return undefined;
}
const isProtected = protectedPaths.some(
(p) => path.includes(p) && p !== ".env"
);
if (isProtected) {
if (ctx.hasUI) {
ctx.ui.notify(\`Blocked write to protected path: ${path}\`, "warning");
}
return { block: true, reason: \`Path "${path}" is protected\` };
}
return undefined;
});
}
如何在 package.json 中注册 Extension
{
"name": "safe-coder",
"keywords": ["pi-package"],
"pi": {
"extensions": ["./extensions"],
"skills": ["./skills"],
"prompts": ["./prompts"]
}
}
pi 启动时会自动扫描 extensions/ 目录下的所有 .ts 文件并加载。
📝 四、定制 Prompt 模板:将工程规范转化为 AI 的行为准则
pi 的 Prompt 模板系统允许你为不同任务类型定义专属的系统提示词。当 AI 执行某类任务时,对应的 Prompt 模板自动注入上下文,确保 AI 按照既定标准执行。
Prompt 模板的格式
每个模板是一个 Markdown 文件,用 YAML frontmatter 声明描述,正文是提示词内容。$@ 是用户输入的占位符。
示例:代码审查模板 prompts/review.md
---
description: Code review for staged changes or the latest commit
---
You are a senior code reviewer performing a thorough, professional code review.
## Input
Optional focus area or context: $@
## Review Target
1. First, check for **staged changes**: run \`git diff --cached\`.
2. If no staged changes are found, compare the **latest commit**: run \`git diff HEAD~1\`.
## Review Checklist
### Correctness
- [ ] Logic errors, off-by-one errors, null/undefined handling
- [ ] Race conditions or concurrency issues
### Security
- [ ] Injection vulnerabilities (SQL, XSS, command injection)
- [ ] Sensitive data exposure (secrets, tokens, PII in logs)
...(更多检查项)
## Output Format
### [SEVERITY] Issue Title
- **File**: \`path/to/file.ext:LINE\`
- **Category**: Correctness | Security | ...
- **Description**: 问题描述
- **Suggestion**: 修复建议(附代码片段)
示例:代码实现模板 prompts/implement.md
---
description: Implement code from a spec or documentation with clean, modular practices
---
You are a senior software engineer implementing production-quality code.
## Input
The spec, doc, or feature description to implement: $@
## Implementation Principles
- **Readability First**: Write code that a mid-level developer can understand at first glance.
- **Single Responsibility**: Each function does one thing. Each module owns one concern.
- **No file may exceed 1000 lines.** If approaching this limit, split into logically cohesive modules.
- **No function may exceed 50 lines.** Extract helpers for complex logic.
## Process
1. Read and Understand
2. Plan the Implementation
3. Implement Incrementally
4. Wire It Together
5. Validate(运行构建和测试)
## Rules
- Follow the project's existing conventions strictly.
- Do NOT introduce new dependencies without explicit justification.
- Commit-ready quality: every file you produce must be mergeable as-is.
🛠️ 五、使用 Skills:为 AI 加载可复用的专业能力
Skill 是 pi 对 Agent Skills 标准 的实现,本质上是一个自包含的能力包:将某类任务的完整工作流、所需脚本、参考文档打包在一起,AI 按需加载,按步骤执行。
Skills 的工作原理
- 启动时,pi 扫描所有 Skill 目录,读取每个 Skill 的名称和描述注入系统提示词
- 当用户任务匹配某个 Skill 时,AI 自动加载完整的 SKILL.md 文件
- AI 按照 Skill 中的指令,使用相对路径调用 Skill 自带的脚本和资源
这种机制称为"渐进式披露"(Progressive Disclosure):平时只占用少量上下文,需要时才加载全部内容。
一个完整的 Skill 结构
brave-search/
├── SKILL.md # 必须:frontmatter + 操作指南
├── search.js # 辅助脚本
└── content.js # 辅助脚本
SKILL.md 格式:
---
name: brave-search
description: Web search and content extraction via Brave Search API.
Use for searching documentation, facts, or any web content.
---
# Brave Search
## Setup
Run once before first use:
bash
cd /path/to/brave-search && npm install
## Search
bash
./search.js "query" # 基础搜索
./search.js "query" --content # 包含页面内容
## Extract Page Content
bash
./content.js https://example.com
Skill 的命名规范
规则说明长度1 ~ 64 个字符字符集只允许小写字母、数字、连字符命名,描述质量描述越具体,AI 越能准确判断何时使用该 Skill
好的描述:
description: Extracts text and tables from PDF files, fills PDF forms,
and merges multiple PDFs. Use when working with PDF documents.
差的描述:
description: Helps with PDFs.
也可以手动触发某个 Skill,不依赖 AI 的自动判断:
/skill:brave-search # 加载并执行 brave-search Skill
/skill:pdf-tools extract # 带参数调用
Skills 的加载位置
~/.pi/agent/skills/ # 全局(所有项目)
~/.agents/skills/ # 全局备选
.pi/skills/ # 项目级
.agents/skills/ # 项目级备选
package.json 的 pi.skills 字段 # 包级
🗂️ 六、项目结构一览
以 safe-coder 为例,一个完整的 pi 配置包的结构如下:
safe-coder/
├── package.json # pi 配置入口
├── extensions/
│ ├── permission-gate.ts # 权限门:拦截危险命令 + 边界保护
│ └── protected-paths.ts # 路径保护:锁定 .env 等敏感文件
├── prompts/
│ ├── architect.md # 架构设计 Prompt
│ ├── implement.md # 代码实现 Prompt
│ ├── review.md # Code Review Prompt
│ ├── debug.md # 调试 Prompt
│ └── ...(更多模板)
└── skills/
└── skills.txt # Skills 说明文档
package.json 将所有组件关联在一起:
{
"name": "safe-coder",
"keywords": ["pi-package"],
"pi": {
"extensions": ["./extensions"],
"skills": ["./skills"],
"prompts": ["./prompts"],
"themes": ["./themes"]
}
}
🚀 七、打造你自己的配置包
Step 1:克隆 safe-coder 作为模板
git clone <your-repo-url> my-coding-cli
cd my-coding-cli
pnpm install
Step 2:添加你的 Extension
在 extensions/ 目录下新建 .ts 文件,pi 会自动加载。
例如,禁止 AI 在工作时间之外执行部署命令:
// extensions/business-hours-guard.ts
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
export default function (pi: ExtensionAPI) {
pi.on("tool_call", async (event, ctx) => {
if (event.toolName !== "bash") return undefined;
const command = event.input.command as string;
const isDeployCommand = /\b(deploy|release|publish)\b/i.test(command);
if (!isDeployCommand) return undefined;
const hour = new Date().getHours();
const isBusinessHours = hour >= 9 && hour < 18;
if (!isBusinessHours) {
if (ctx.hasUI) {
ctx.ui.notify("⛔ 非工作时间,禁止部署!", "warning");
}
return { block: true, reason: "Deploy blocked outside business hours" };
}
return undefined;
});
}
Step 3:编写你的 Prompt 模板
在 prompts/ 下新建 Markdown 文件,添加 frontmatter 描述:
---
description: 按照公司编码规范进行 Code Review
---
你是一名资深工程师,负责对代码变更进行严格审查。
## 必须检查的项目
1. 所有函数必须有 JSDoc 注释
2. 变量名必须使用 camelCase,类名使用 PascalCase
3. 禁止使用 \`any\` 类型
4. 所有异步操作必须有错误处理
...
Step 4:创建你的 Skill
在 skills/ 下新建目录,添加 SKILL.md:
---
name: database-migration
description: Run and rollback database migrations using Flyway.
Use when asked to migrate, rollback, or check migration status.
---
## Run Migration
bash
./scripts/migrate.sh up
## Rollback
bash
./scripts/migrate.sh down <version>
Step 5:发布为 npm 包,分享给团队
npm publish
# 团队成员只需要:
pi install npm:your-coding-cli
📚 参考资源
- pi 官方 npm 页面
- Agent Skills 标准规范
- safe-coder 项目源码
- Pi Skills 社区仓库(网络搜索、浏览器自动化、Google APIs 等现成 Skill)
如果这篇文章对你有帮助,欢迎点赞转发。也欢迎在评论区分享你定制的 Extension 或 Skill。