我的 Pi 插件
- # ai
- # pi
- # coding-agent
- # extension
这篇文章还在编辑中,内容可能会继续补充、调整或重写。
最近开始整理自己的 Pi 插件。这里的 Pi 是一个运行在终端里的 coding agent,也是一个尽量保持精简的 agent harness。它提供对话、模型调用、会话管理和基础工具,但不会预先替每个人决定完整的工作流。
这也是 Pi 和不少 coding agent 不太一样的地方。它把很多能力留给扩展系统:需要新的工具、命令、快捷键、事件处理或终端界面时,可以通过 TypeScript 模块补上;需要复用提示词、技能和主题时,也可以把它们组织成一个 package。使用者不必修改 Pi 的核心代码,就能让它逐渐贴近自己的工作方式。
这篇文章先记录插件的基础结构。具体包含哪些工具、每个工具解决什么问题,以及实际使用中的调整,会在后面继续补充。
Pi 里的“插件”是什么
“插件”是我对这套自定义能力的统称。在 Pi 的官方文档里,更准确的概念有两个:
- Extension:一个 TypeScript 模块,可以注册工具、命令、快捷键和事件,也能修改终端界面。
- Pi package:一个可以通过 npm 或 Git 安装的资源包,用来组合 extensions、skills、prompt templates 和 themes。
这两个概念解决的问题不同。Extension 负责改变 Pi 运行时的行为,package 负责把一组相关资源组织和分发出去。一个只在本地使用的小功能,写成单文件 extension 就够了;当扩展越来越多,或者还需要同时携带 skill 和 prompt 时,再整理成 package 会更合适。
我的 Pi 插件会按后一种方式组织。它不是对 Pi 源码的 fork,而是独立于核心的配置与扩展层。这样 Pi 本身可以继续升级,个人工作流也能单独维护。
Extension 如何接入 Pi
一个 extension 的入口是默认导出的函数。Pi 加载模块后,会把 ExtensionAPI 传进来,扩展再通过这个对象注册自己需要的能力。
import type { ExtensionAPI } from '@earendil-works/pi-coding-agent'
export default function(pi: ExtensionAPI) {
pi.on('session_start', (_event, ctx) => {
ctx.ui.notify('Extension loaded', 'info')
})
}
这里的重点并不是这段通知代码,而是 extension 的接入方式:它没有独立启动一个常驻服务,也不需要修改 Pi 的内部实现,而是在 Pi 的生命周期里注册行为。除了监听 session_start,扩展还可以处理模型调用、工具执行、会话切换和上下文压缩等事件。
Pi 使用 jiti 加载 extension,因此 TypeScript 文件可以直接运行,不需要先单独编译。开发时可以通过 -e 临时加载一个文件:
pi -e ./my-extension.ts
稳定之后,再把文件放进自动发现目录。全局扩展放在 ~/.pi/agent/extensions/,会对所有项目生效;只服务于某个仓库的扩展放在项目的 .pi/extensions/。自动发现的扩展还可以通过 /reload 重新加载,修改后不必退出整个会话。
全局能力和项目能力分开
组织个人插件时,一个容易忽略的问题是作用域。并不是所有能力都应该默认出现在每个项目里。
与个人操作习惯有关、不会依赖某个仓库结构的功能,适合放在全局插件中。依赖项目命令、目录约定或部署环境的能力,更适合跟随项目保存。这样做可以减少无关上下文,也能避免一个项目的假设意外影响另一个项目。
Pi 会区分全局资源和项目资源。项目里的 .pi 配置与 extension 只有在项目被信任后才会加载。这不是多余的确认,因为 extension 运行时拥有当前用户的系统权限,可以读写文件,也可以执行任意代码。第三方 package 在安装前需要检查源码,自己的插件也应该控制依赖和执行边界。
整理成 Pi package
当插件不再是一个文件时,可以在 package.json 里使用 pi 字段声明资源入口:
{
"name": "my-pi-plugin",
"keywords": ["pi-package"],
"pi": {
"extensions": ["./extensions"],
"skills": ["./skills"],
"prompts": ["./prompts"],
"themes": ["./themes"]
}
}
目录不一定要一次建全。只有真正需要某类资源时再加入对应入口,比先搭一个很大的空框架更容易维护。如果没有显式编写 pi 字段,Pi 也会从 extensions/、skills/、prompts/ 和 themes/ 等约定目录中自动发现资源。
package 可以保留在本地,也可以通过 npm 或 Git 安装。对我来说,先把它作为个人配置维护更实际:每加入一项能力,都记录它解决的问题、触发方式和使用边界。等结构稳定以后,再决定哪些部分适合公开或复用。
后面补充具体工具时,我会沿用同一个顺序:先说明原本的问题,再介绍扩展做了什么,最后记录它不适用的场景。这样这篇文章不只是插件清单,也能保留每项能力为什么存在。