CheinTian / notes
Clipping · 4 分钟

使用 pi 打造属于你自己的 Coding CLI

x.com Unknown

当前主流的 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

bash
npm install -g @mariozechner/pi-coding-agent

安装完成后即可使用 pi 命令。

pi 支持从 npm 直接安装配置包:

bash
pi install npm:safe-coder

这会将 safe-coder 的所有扩展、Skill、Prompt 模板加载到 pi 中。

在项目中启动 pi

bash
cd /your/project
pi .

pi 会自动扫描项目目录下的配置,加载所有扩展和 Skill,进入交互界面。

🔌 三、开发 Extensions:给 Pi 加装自定义机制

Extension 是 pi 最核心的能力。你可以用 TypeScript 编写函数,监听 AI 的每一次工具调用,决定放行、拦截或弹出确认框由用户决定。

Extension 的基本结构

每个 Extension 是一个默认导出的函数,接收 ExtensionAPI 对象:

typescript
// ~/.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 最核心的扩展,包含两部分功能:

  1. 危险命令检测:拦截 rm -rf、sudo、chmod 777 等高危操作,弹出确认框
  2. 工作目录边界保护:AI 尝试读写 pi 启动目录之外的文件时,必须先经过确认
typescript
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/ 只拦截写入和编辑操作。

typescript
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

json
{
  "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

markdown
---
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

markdown
---
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 的工作原理

  1. 启动时,pi 扫描所有 Skill 目录,读取每个 Skill 的名称和描述注入系统提示词
  2. 当用户任务匹配某个 Skill 时,AI 自动加载完整的 SKILL.md 文件
  3. AI 按照 Skill 中的指令,使用相对路径调用 Skill 自带的脚本和资源

这种机制称为"渐进式披露"(Progressive Disclosure):平时只占用少量上下文,需要时才加载全部内容。

一个完整的 Skill 结构

bash
brave-search/
├── SKILL.md         # 必须:frontmatter + 操作指南
├── search.js        # 辅助脚本
└── content.js       # 辅助脚本

SKILL.md 格式:

markdown
---
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

好的描述:

markdown
description: Extracts text and tables from PDF files, fills PDF forms,
             and merges multiple PDFs. Use when working with PDF documents.

差的描述:

markdown
description: Helps with PDFs.

也可以手动触发某个 Skill,不依赖 AI 的自动判断:

bash
/skill:brave-search            # 加载并执行 brave-search Skill
/skill:pdf-tools extract       # 带参数调用

Skills 的加载位置

bash
~/.pi/agent/skills/         # 全局(所有项目)
~/.agents/skills/            # 全局备选
.pi/skills/                  # 项目级
.agents/skills/              # 项目级备选
package.json 的 pi.skills 字段  # 包级

🗂️ 六、项目结构一览

以 safe-coder 为例,一个完整的 pi 配置包的结构如下:

bash
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 将所有组件关联在一起:

bash
{
  "name": "safe-coder",
  "keywords": ["pi-package"],
  "pi": {
    "extensions": ["./extensions"],
    "skills":     ["./skills"],
    "prompts":    ["./prompts"],
    "themes":     ["./themes"]
  }
}

🚀 七、打造你自己的配置包

Step 1:克隆 safe-coder 作为模板

bash
git clone <your-repo-url> my-coding-cli
cd my-coding-cli
pnpm install

Step 2:添加你的 Extension

在 extensions/ 目录下新建 .ts 文件,pi 会自动加载。

例如,禁止 AI 在工作时间之外执行部署命令:

typescript
// 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 描述:

markdown
---
description: 按照公司编码规范进行 Code Review
---
你是一名资深工程师,负责对代码变更进行严格审查。

## 必须检查的项目

1. 所有函数必须有 JSDoc 注释
2. 变量名必须使用 camelCase,类名使用 PascalCase
3. 禁止使用 \`any\` 类型
4. 所有异步操作必须有错误处理
...

Step 4:创建你的 Skill

在 skills/ 下新建目录,添加 SKILL.md:

markdown
---
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 包,分享给团队

bash
npm publish
# 团队成员只需要:
pi install npm:your-coding-cli

📚 参考资源

如果这篇文章对你有帮助,欢迎点赞转发。也欢迎在评论区分享你定制的 Extension 或 Skill。

#AI编程 #开发者工具 #CodingAgent #TypeScript #开源