PiAgent 完全指南:从零到写扩展

工具选型与安装第 5 / 5 篇

写作日期:2026-08-07|跨版本复验:2026-08-18 版本锚定:pi 0.81.1(@earendil-works/pi-coding-agent)首测,0.84.2 已全面复验。pi 迭代很快,读到本文时若版本已变,请以 pi --version 和本机 docs/ 为准。

📌 2026-08-18 跨版本复验结论(0.81.1 → 0.84.2,跨 3 个小版本)

本文的核心结论 0.84.2 复验
三个「静默失败」坑 一个都没修,全部仍成立
坑一 provider 静默回退 ⚠️ 仍在,但触发条件被修正了(见 2.4,不是“拼错”而是“没给 --model”)
坑二 skills 卸载静默失败 ⚠️ 仍在,但根因查明 + 有解(见 4.3,scope 不匹配,加 -g)
坑二副题「安装摘要路径错报」 ✅ 已被上游修好(唯一一处修掉的)
坑三 pi -p 挂住 ✅ 仍成立
--help 写 default: google ✅ 仍然不一致,未修
「模型名写错退出码 0」 ❌ 这条口径本来就错,退出码取决于下游而非 pi(见 2.5)
registerProvider ✅ 本次已实测跑通,从 ⚠️ 升为 ✅(见 8.3)
pi-subagents 内置 agent 🚨 0.50.0 从 9 个砍到 6 个,planner 被彻底删除(见 10.1)

也就是说:入门篇和主线结论跨 4 个版本仍然有效,可以放心照做;要留意的只有 pi-subagents 升级和上面那两处表述修正。

🔔 别靠「感觉过了挺久」判断要不要重验,跑这两条(零 API 成本):

pi --version                          # 本文复验基线:0.84.2
pi --help | grep -i 'default: *google'   # 仍命中 → 「--help 与实际默认不一致」那条仍成立

⭐ 第二条比版本号有用:它直接检查结论。pi 迭代快,光看版本号会天天误报; 而这条只在上游真把那处不一致修掉时才变。 ✅ 2026-08-27 复核:npm latest 已是 0.84.3(比复验基线多一个补丁版), default: google 仍在包里,claude-haiku-4.5 这个模型标识也仍然有效。主线结论未变。 实测环境:macOS(Darwin 25.5)。Windows 未测,理论上走 WSL,本文不覆盖。 取代:PiAgent/260724-PiAgent-小白教程-从入门到精通.md(入门部分)与 PiAgent/260805-PiAgent-扩展实战-视频笔记全流程教程.md(扩展部分)。那两份保留作存档,其中若干结论已被本文推翻。

本文的标注约定

全文每一条结论都带来源标记,请按标记决定信任程度:

标记 含义
✅ 本机实测过,命令、输出、报错都是真跑出来的
📖 官方文档有明确出处,本文给出文件名和行号,但没亲自跑
⚠️ 未验证或有前提条件,照做前请自己确认

入门篇(第 0–5 章)的每一条操作步骤都是 ✅——小白照着敲,错一步就卡死,这部分不允许出现「据说」。


速查表

真正天天要翻的就这一页。

五条命令

pi                                  # 启动交互界面(TUI)
pi -p "把这个目录里的图片按日期改名" </dev/null   # 跑一次就退出,适合脚本
pi -c                               # 接着上次的会话继续
pi --list-models                    # 看当前能用哪些模型
pi list                             # 看装了哪些扩展包

✅ -p 后面那个 </dev/null 不是可有可无的。 在脚本、CI、或任何非交互环境里,pi -p 会一直等 stdin 的 EOF 而挂住。本文写作过程中我自己就先踩了一次,一条命令跑了两分钟没退出。手动在终端里敲则不受影响。

四个目录

放什么 放哪
全局 skill ~/.pi/agent/skills/<名字>/SKILL.md
全局 extension ~/.pi/agent/extensions/*.ts
模型/网关配置 ~/.pi/agent/models.json
包登记、主题 ~/.pi/agent/settings.json

✅ 换配置目录用环境变量 PI_CODING_AGENT_DIR(📖 docs/usage.md:296)。想干净试验又不弄脏现有配置,就用它——本文全部入门实测都跑在 PI_CODING_AGENT_DIR=/tmp/pi-fresh 里。

🚨 一条能省钱的规矩

--provider 和 --model 永远一起写。 只写 --provider 时,这个参数根本不参与校验——写错也不报错,静默回退到最贵的 Opus。

✅ 实测对照(0.81.1 首测,0.84.2 复验行为一致):

① --provider anthropic --model claude-haiku-4.5  →  anthropic / claude-haiku-4.5      退出码 0  ✔
② --provider xyzzy      (不给 --model)          →  anthropic / claude-opus-4-8       退出码 0  ✘ 静默
③ 两个参数都不给                                  →  anthropic / claude-opus-4-8       退出码 0  ✘ 静默
④ --provider xyzzy --model claude-haiku-4.5      →  Error: Unknown provider "xyzzy"   退出码 1  ✔ 报错

🚨 关键不是「名字拼错」,而是「有没有同时给 --model」——②④ 用的是同一个错名 xyzzy,行为却完全相反。两个一起写,pi 会主动替你拦下拼写错误;只写一个,等于把模型选择权交回默认值:你以为在用便宜的 haiku,实际每句话都在烧 Opus。机制与发现过程见 2.4。

常见报错对照

现象 真实文案 退出码 怎么办
没配 key No API key found for the selected model.(0.84.2 改成 ...for anthropic.)+ 指向 docs ✅ 1 见第 2 章配 provider
模型名写错 Warning: Model "xxx" not found for provider "anthropic". Using custom model id. 然后由服务端报错 ⚠️ 取决于下游(首测 0,经 OneHub 复测 1) pi 不校验模型名,把错名当自定义 id 直接发出去(见 2.5)
provider 名写错,且没给 --model 没有任何报错,静默走默认 Opus ✅ 0 🚨 最烧钱的一条,见 2.4
provider 名写错,同时给了 --model Error: Unknown provider "xxx". ✅ 1 两个参数一起写,pi 就会帮你拦下拼写错误
装完 skill 用不上 无报错 — 改完 skill 必须退出重进,/reload 不管用(第 5 章)
卸载 skill 卸不掉 Found 0 unique installed skill(s) / No skills found to remove. 但文件还在 ✅ 0(成功也是 0) 全局装的要带 -g;仍要手动核对(第 4 章)

怎么读这份文档

这份文档分两半,中间有一条明确的停止线:

  • 入门篇(第 0–5 章,约 400 行):读完你就能真用起来,全程不需要会写代码。
  • 🛑 停止线:有一份 5 题自测清单。做得出来,说明入门篇的目的已经达到,可以合上文档去用了。
  • 进阶篇(第 6–13 章):写 TypeScript 扩展、拦截工具调用、多 agent 协作、打包分发、迁移到真实团队项目。等你真的撞上问题再回来看,不必一口气读完。

入门篇

第 0 章:给谁看,需要准备什么

这份文档假设你:

  • 能打开终端,会跟着敲命令
  • 听说过 Claude Code / Codex 之类的东西,但不一定用过
  • 不需要会写代码(入门篇结束前不出现一行 TypeScript)

你需要准备:

  1. macOS 或 Linux(✅ 本文实测在 macOS)
  2. Node.js 18+(node -v 检查)
  3. 一个 LLM 的 API key——Anthropic / OpenAI / Google / xAI / OpenRouter 任选其一即可

你不需要:

  • 订阅 Claude Pro 或 ChatGPT Plus(用 API 更便宜,也更适合命令行)
  • 任何网页版工具

第 1 章:pi 到底是什么

一句话

An autonomous agent is just an LLM + tools + a loop.

pi 是一个跑在终端里的编程助手:它能读文件、执行命令、改代码、写新文件,然后根据结果决定下一步——循环,直到任务完成。

和 Claude Code / Codex 的区别

pi 的取舍是核心保持小,其余推给扩展。官方把话说得很直白:

📖 docs/usage.md:309:It intentionally does not include built-in MCP, sub-agents, permission popups, plan mode, to-dos, or background bash.

这六项是故意不做的,不是还没做。别指望以后官方内置。想要,就自己装扩展或者自己写——这也正是本文进阶篇在讲的事。

⚠️ 一个必须先说清的混淆:oh-my-pi ≠ pi

网上(尤其是视频教程里)常出现 oh-my-pi,它是 pi 的一个 fork,自带大量插件。它的代码结构和原生 pi 不一样——比如 oh-my-pi 自带 MCP 支持,而原生 pi 没有。

✅ 实测:原生 pi 的 dist/ 里 grep 不到 mcpServers。

看教程时先确认对方跑的是哪一个,否则会照着 fork 的结构去找原生 pi 里根本不存在的文件。


第 2 章:装好,并且真的跑起来

2.1 装

npm i -g @earendil-works/pi-coding-agent
pi --version     # 应该输出 0.81.1 或更高

📖 安装命令来自官方 README。⚠️ 本文实测的机器上 pi 是随另一套工具链装的,这一条我没有从零重装验证过。

2.2 第一次运行会看到什么

✅ 实测——一个 key 都没配的情况下跑 pi,会得到:

No API key found for the selected model.

Use /login to log into a provider via OAuth or API key. See:
  <pi安装路径>/docs/providers.md
  <pi安装路径>/docs/models.md

退出码 1。

✅ 同时它会在配置目录下建这些东西,仅此而已:

~/.pi/agent/
├── auth.json            2 字节,内容是 {}
├── models-store.json    2 字节,内容是 {}     ← 0.84.2 新增,0.81.1 没有
└── sessions/

注意没有 settings.json——那个文件是你第一次装扩展包时才会被创建的。很多人对着教程找 settings.json 找不到,就是这个原因。✅ 这一条 0.84.2 复验仍然成立。

⚠️ 0.84.2 的报错文案略有变化:0.81.1 说的是 No API key found for the selected model.,0.84.2 改成了 No API key found for anthropic.(点出具体 provider 名)。指向的两个文档路径不变。

别拿报错文案做脚本判断的依据——退出码和文案两样里,文案是更容易随版本变的那个。

2.3 配 provider:两条路

路线 A:官方 API(推荐新手)

最简单的方式是环境变量。📖 docs/providers.md:63-86 给了完整对照表,常用的几个:

服务商 环境变量 --provider 的值
Anthropic ANTHROPIC_API_KEY anthropic
OpenAI OPENAI_API_KEY openai
Google Gemini GEMINI_API_KEY google
xAI XAI_API_KEY xai
OpenRouter OPENROUTER_API_KEY openrouter
DeepSeek DEEPSEEK_API_KEY deepseek
export ANTHROPIC_API_KEY=sk-ant-...
pi --provider anthropic --model claude-haiku-4.5

也可以在 TUI 里用 /login 走 OAuth 或粘贴 key(📖 docs/providers.md)。

路线 B:走中转网关(自建/公司网关)

如果你的 key 不是直连官方,而是走一台中转网关,需要覆盖 baseUrl。在 ~/.pi/agent/models.json 里写:

{
  "providers": {
    "anthropic": {
      "baseUrl": "https://<你的网关地址>/claude",
      "models": [
        {
          "id": "claude-haiku-4.5",
          "name": "Claude Haiku 4.5 (网关)",
          "reasoning": false,
          "input": ["text", "image"],
          "contextWindow": 200000,
          "maxTokens": 16000
        }
      ]
    }
  }
}

然后照常用环境变量给 key:

ANTHROPIC_API_KEY="<网关的key>" pi --provider anthropic --model claude-haiku-4.5

✅ 本文所有实测都走的这条路线,确认可用。

几个要点:

  • 📖 docs/models.md:145:非内置 provider 的配置必须有 baseUrl 和 api;apiKey 不必写进文件,可以由 /login、auth.json、--api-key 或环境变量提供。没有 auth 时模型仍会加载,但在 /model 里显示为不可用——这个状态很容易被误读成「配置没生效」。
  • ⚠️ 模型 ID 有三层,别混:/model 面板里显示的是 pi 内建目录的写法(claude-opus-4-8,横线);models.json 里写的是发给网关的名字;网关自己认什么名字取决于它的实现。有的网关会做横线↔点的映射,有的不会。

2.4 🚨 这两个参数必须显式写全

这是本文最想让你记住的一条,因为它直接关系到账单。

✅ 实测(0.81.1 首测,0.84.2 复验行为完全一致),同一台机器、同一份配置,三组对照:

① --provider anthropic --model claude-haiku-4.5
   → 实际用了 provider=anthropic  model=claude-haiku-4.5   ✔ 符合预期

② --provider anthropik      (少打一个字母 c,且不给 --model)
   → 实际用了 provider=anthropic  model=claude-opus-4-8    ✘ 静默换成 Opus,退出码 0

③ 两个参数都不给
   → 实际用了 provider=anthropic  model=claude-opus-4-8    ✘ 静默用 Opus

🚨 2026-08-18 补测,找到了真正的触发条件——它不是「名字拼错」,而是「有没有同时给 --model」。

发现过程:拿一个完全不存在的 provider 名 probe-local 去跑(配了 --model),预期它会像 ② 一样静默降级,结果它明确报错并退出码 1。同是不存在的名字,行为相反,于是做控变量对照——同一个 xyzzy,唯一差别是有无 --model:

--provider xyzzy                            → provider=anthropic model=claude-opus-4-8   退出码 0  (静默)
--provider xyzzy --model claude-haiku-4.5   → Error: Unknown provider "xyzzy".            退出码 1  (明确报错)

机制因此是清楚的:

  • 单独给 --provider 时,这个参数根本不参与校验——写什么都一样被忽略,回退到「当前有 auth 可用的 provider + 它的旗舰模型」
  • 一旦同时给 --model,provider 名就会被严格校验,未知名字立刻 Unknown provider + 退出码 1

这让结论反而更好记:永远两个参数一起写。 一起写时 pi 会主动替你拦下拼写错误;只写一个,就等于把模型选择权交回给默认值——你以为在用便宜的 haiku,实际每句话都在烧 Opus。

⚠️ 另外,pi --help 里写着 --provider <name> Provider name (default: google),但 ✅ 实测什么都不给时用的是 anthropic 而不是 google——帮助文本和实际行为不一致,它实际是挑了个有可用 auth 的 provider。0.84.2 里这行帮助文本仍未修。以实测为准。

怎么确认自己到底在用哪个模型:TUI 里敲 /model 看当前值;脚本里用 --mode json,返回的 assistant 消息里带 provider 和 model 字段,那是最硬的证据。

2.5 模型名写错会怎样

✅ 实测:pi 不校验模型名。

$ pi --provider anthropic --model claude-opus-9 -p "hi" </dev/null
Warning: Model "claude-opus-9" not found for provider "anthropic". Using custom model id.
503 {"type":"..._error", ... "当前分组 xx 下对于模型 claude-opus-9 无可用渠道 ..."}

它只给一句 Warning,然后把这个不存在的名字当成“自定义模型 id”直接发给服务端,最后由服务端报错。

⚠️ 2026-08-18 更正这一条的口径。 原文写「退出码是 0,靠退出码判断成败会漏掉这种失败」——那个 0 是当时下游的行为,不是 pi 的固有行为。08-18 用同样的写法经 OneHub 复测,网关直接回 503「无可用渠道」,pi 的退出码是 1。

准确的说法是:pi 侧只警告、不拦截,成败完全由服务端决定。 所以脚本里两头都不能信——不能靠退出码判成功(下游放行时它是 0),也不能只看有没有 Warning(有 Warning 也可能真跑通,比如服务端确实认识这个自定义 id)。唯一可靠的是看实际返回内容。

2.6 第一个任务

pi --provider anthropic --model claude-haiku-4.5

进 TUI 后直接说人话:

看看当前目录里有什么文件,按大小排前五个
把这个目录下所有 .jpeg 改成 .jpg

pi 会自己决定调 bash 还是 read,执行前你能看到它要跑什么。

⚠️ pi 默认放行所有工具调用,读和写都不弹权限确认。它没有 Claude Code 那种 acceptEdits / bypassPermissions 模式。想限制,用 --tools <白名单> 或 --exclude-tools <黑名单>。

这一条推翻了 260724 旧版教程里「pi 有安全模式,每次要批准」的说法——那是错的。

2.7 会话续接

pi -c              # 接着上一次继续
pi -r              # 列出历史会话,选一个恢复

会话存在 ~/.pi/agent/sessions/,按项目目录分文件夹。不用每次从头交代背景。


第 3 章:日常怎么用

两种运行模式(入门只需要这两种)

模式 命令 什么时候用
交互 pi 日常干活,能看到它每一步做什么
一次性 pi -p "..." </dev/null 写进脚本、批处理、定时任务

还有 --mode json 和 RPC 模式,那是给程序集成用的,进阶篇再说。

常用 slash 命令

在 TUI 里敲:

命令 作用
/model 看/换当前模型
/login 配置 provider 认证
/reload 重载 extension(⚠️ 对 skill 无效,见第 5 章)
/help 全部命令

让它闭嘴少花钱的几个习惯

  • 简单活儿显式指定 --model claude-haiku-4.5,别让它默认跑 Opus(见 2.4)
  • 一次只交代一件事,别在一个会话里堆五个不相干的任务——上下文越长越贵
  • 长任务用 -c 续接,而不是每次重新描述背景

第 4 章:装别人写好的 skill

skill 就是一个 SKILL.md 文件(可以带脚本),告诉模型「遇到某类任务时按这个套路做」。装别人的 skill 是投入产出比最高的一步。

4.1 用 skills CLI 装(主流入口)

npx skills add <仓库>              # 装整个仓库的 skill
npx skills add <仓库> -l           # 只列出有什么,不装
npx skills add <仓库> --skill <名字> --agent pi -g -y   # 只装指定的一个
npx skills list                    # 看装了什么
npx skills find <关键词>           # 搜索

✅ 实测三个来源:

仓库 内容
badlogic/pi-skills pi 官方 8 个:brave-search、browser-tools、gccli、gdcli、gmcli、transcribe、vscode、youtube-transcript
anthropics/skills Anthropic 官方,含 skill-creator(帮你写 skill 的 skill)
jimliu/baoyu-skills 社区热门,中文场景友好

同一份 skill 可以被 Claude Code / pi / Codex 共用——它们读同一批目录。

4.2 装的时候会看到什么

✅ 实测装 badlogic/pi-skills 的 vscode:

●   claude-code_2-1-220_agent  Agent detected — installing non-interactively
◇  Source: https://github.com/badlogic/pi-skills.git
◇  Found 8 skills
●  Selected 1 skill: vscode
◇  Installation Summary ────╮
│  ~/.agents/skills/vscode  │
│    copy → Pi              │
├───────────────────────────╯
◇  Security Risk Assessments ────────────────────────────╮
│          Gen               Socket            Snyk      │
│  vscode  Safe              0 alerts          Low Risk  │
├────────────────────────────────────────────────────────╯

两个值得注意的地方:

  • 它会自动检测你在什么 agent 里跑,检测到就转非交互模式,不再弹确认
  • 装之前给三家的安全评估(Gen / Socket / Snyk)。装陌生仓库前先看这一行

4.3 🚨 两个实测踩到的坑

坑一:安装摘要显示的路径是错的。 —— ✅ 2026-08-18 复测:这条已被上游修好(当时 skills CLI 版本未记,复测版本 1.5.22)。

原始现象:摘要写 ~/.agents/skills/vscode,但实际落点是 ~/.pi/agent/skills/vscode(真目录 copy,不是软链),而摘要里那个路径根本不存在。

复测结果:project scope 装 → 摘要写 ./.pi/skills/gccli,实际就在那;global scope 装(-g)→ 摘要写 ~/.pi/agent/skills/gccli,实际也就在那。声称与实际已一致。

习惯仍值得保留:装完 ls 一下确认落点,成本十秒。

坑二:卸载会静默失败。 —— ⚠️ 2026-08-18 复测:坑仍在,但根因查明了,而且有解。

原始现象是这样的:

$ npx skills remove vscode
◇  Found 0 unique installed skill(s)
└  No skills found to remove.

$ ls ~/.pi/agent/skills/vscode
~/.pi/agent/skills/vscode     ← 还在!

我当时把原因写成「add 的落点和 remove 的扫描路径对不上」。✅ 08-18 补测发现真正的原因是 scope 不匹配:

# 用 -g 装到 global(落 ~/.pi/agent/skills/)
$ npx skills remove gdcli -y          →  Found 0 unique installed skill(s)   退出码 0,文件仍在  ✘
$ npx skills remove gdcli -g -y       →  Found 1 unique installed skill(s)   文件真删了        ✔

remove 默认只扫 project scope。 你用 -g 装到全局的 skill,卸载时也必须带 -g;不带就报「找到 0 个」然后什么也不做。

🚨 最坑的一点:成功和失败的退出码都是 0。 脚本里无法靠退出码区分这两种情况,只能自己核对文件系统。

所以规矩是:

npx skills remove <名字> -g -y        # 装到全局的,卸载必须带 -g
ls ~/.pi/agent/skills                 # 必须自己核对一遍
rm -rf ~/.pi/agent/skills/<名字>      # 没卸干净就手动删

这两个坑(连同第 2 章的 provider 静默回退)有个共同点:工具不报错,但你以为的和实际发生的不是一回事。用 pi 的过程里,「没报错」永远不等于「成功了」——多花十秒 ls 一下,能省掉后面一小时的困惑。

🚨 08-18 复验又给这条加了一层:成功和失败的退出码可以是同一个值。坑二里 remove 删成功是 0、报「找到 0 个」什么都没做也是 0。这种情况下退出码不只是“不够用”,而是会主动误导你——脚本里写 remove && echo 已卸载 会稳定地打印一句假话。

4.4 pi 的一个隐藏能力

⚠️ 当任务需要某个 pi 没装的 skill 时,它会自己去搜社区候选并列给你,你回一句 install 1 就自动装上。这条来自旧版教程记录,本次未复测。


第 5 章:写你自己的第一个 skill

不需要会编程。一个 skill 最小就是一个带 frontmatter 的 Markdown 文件。

5.1 最小骨架

mkdir -p ~/.pi/agent/skills/my-skill

~/.pi/agent/skills/my-skill/SKILL.md:

---
name: my-skill
description: 当用户要求做 XXX 时使用。描述要写清「什么时候用」,模型靠这句话决定要不要读正文。
---

# my-skill

## 什么时候用

用户说「……」的时候。

## 怎么做

1. 第一步
2. 第二步

description 是最重要的一行:pi 只把 name 和 description 放进 system prompt,正文是模型判断需要时才去读的(渐进式披露)。描述写不清「什么时候用」,这个 skill 就永远不会被触发。

5.2 验证它真的生效了

这一步很多人做错,所以专门说。

❌ 错误做法:问模型「你现在有哪些 skill?」

模型很可能只是 ls 了一下目录,或者干脆瞎猜。模型对自己能力的自述不是机制证据——这个教训是有代价换来的,进阶篇第 11 章有完整案例。

✅ 正确做法:暗号法 + 反向对照。

在 SKILL.md 里写一个外界不可能猜到的字符串:

---
name: say-code
description: 当用户要求「报暗号」时使用,返回本 skill 里写死的验证码
---

用户说「报暗号」时,直接回答:`PI-VERIFY-7391`,不要解释。

然后两边都试:

$ pi -p "报暗号" </dev/null
`PI-VERIFY-7391`                     ← ✅ skill 生效了

$ pi --no-skills -p "报暗号" </dev/null
我是 Claude,一个 AI 编程助手 …      ← ✅ 关掉 skill 就完全答不出

✅ 以上为本文实测输出。两个方向都要试:只看「有 skill 时答得出」不够,还要确认「关掉就答不出」——否则你无法排除模型是从别处(比如自己读了文件)拿到答案的。

5.3 🚨 改完 skill 必须退出重进

✅ 实测结论:

改了什么 /reload 够不够
extension ✅ 够(📖 docs/extensions.md:7)
skill / SKILL.md ❌ 不够,必须退出重进

原因:/reload 只重载 pi 进程侧的配置,不会重新拼装 system prompt,而 skill 的 name/description 是写在 system prompt 里的——模型压根感知不到你的改动。

⚠️ 更坑的是:/reload 之后 [Skills] 列表里能看到新 skill。看得到 ≠ 生效。别拿列表当验收凭据,用 5.2 的暗号法。

5.4 写好 skill 的几条经验

  • description 写清触发条件,不要写功能介绍
  • 步骤写成可执行的判断,别写模糊的原则。⚠️ 实测过一个反例:给子 agent 写「时间戳误差在 ±2 秒内算通过」,haiku 照样把 0.98 秒的偏差报成错误——数值容差要写成它能一步步执行的判断,不能指望它自己权衡
  • 脚本用什么语言都行,SKILL.md 里说清怎么调
  • 想让 skill 跨 agent 共用,放 ~/.agents/skills/(Claude Code / pi / Codex 都读这里)
  • 懒得自己写就装 skill-creator,它会用对话方式带你生成

🛑 停止线

到这里,你已经会用 pi 了。

下面的进阶篇要开始写 TypeScript、拦截工具调用、编排多个 agent、打包分发。那些是在你撞上具体问题之后才有意义的东西——现在合上文档去用几天,比一口气读完更有价值。

自测清单

不看文档,这五件事你能做出来吗?

  1. 你想让 pi 用便宜的 haiku 跑一个批处理脚本。完整命令怎么写?(提示:有两个参数必须显式写,还有一个重定向不能少)
  2. 你写了个 skill,改了 description,怎么确认模型真的感知到了改动?
  3. npx skills remove xxx 说卸载成功了,你下一步该做什么?
  4. 怎么在完全不动现有配置的前提下,起一个干净的 pi 环境做试验?
  5. 你的账单突然暴涨,怀疑模型跑错了。用什么命令拿到最硬的证据?

答案分别在:2.4 + 速查表 / 5.2 / 4.3 / 速查表「四个目录」 / 2.4 结尾。

五题里有三题的答案是「别信它说成功了,自己去核对」——这不是巧合,是 pi 这类工具的通用相处方式。


进阶篇

从这里开始需要读得懂一点 TypeScript。不需要精通——本章的例子都在 30 行以内。

第 6 章:pi 的扩展面到底有几个

官方 README 宣传「六类扩展」,✅ 实际核对下来它们不在同一层,真实结构是 4 + 1 + 1:

TypeScript extension(唯一的代码扩展面)
├── pi.registerTool()      注册工具,模型自行决定调不调
├── pi.registerCommand()   注册 /命令,用户主动触发
├── pi.on(事件, handler)   挂生命周期钩子(session_start / tool_call …)
└── pi.registerProvider()  ← README 单列的「custom provider」其实就是这一条

skill / prompt template / theme      纯文件配置,不写代码
pi package                            分发层,把上面这些打包

README 自己也承认「model provider 本质上就是一个 extension 文件」。所以**「六类」是营销口径,实际你只需要学会一个 .ts 文件里的四个方法**。

⚠️ 视频教程里作者口播的「五种扩展」是 extension 内部的细分,和 README 的「六类」不是同一套数,两边对不上很正常。

加载位置

📖 docs/extensions.md:109、skills.md:24、prompt-templates.md:9:

类型 全局 项目级
extension ~/.pi/agent/extensions/*.ts(或 */index.ts) .pi/extensions/(需 trust)
skill ~/.pi/agent/skills/*/SKILL.md .pi/skills/、.agents/skills/(需 trust)
prompt template ~/.pi/agent/prompts/*.md .pi/prompts/
subagent 定义 ~/.pi/agent/agents/*.md .pi/agents/

✅ 全局目录不需要任何登记,放进去就会被自动发现——本文第 7 章的实测可以证明这一点。


第 7 章:第一个 extension

7.1 完整代码

~/.pi/agent/extensions/hello.ts:

import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
import { Type } from "typebox";

export default function (pi: ExtensionAPI) {
	pi.on("session_start", async (_event, ctx) => {
		ctx.ui.notify("hello 扩展已加载", "info");
	});

	// 最小可用的自定义工具:返回本机当前时间。
	// 选它是因为结果能跟系统时间对照,能验证「模型真的调用了工具」而不是自己编的。
	pi.registerTool({
		name: "local_time",
		label: "本机时间",
		description: "返回运行 pi 的这台机器的当前时间,格式 YYYY-MM-DD HH:MM:SS",
		parameters: Type.Object({}),
		async execute(_toolCallId, _params, _signal, _onUpdate, _ctx) {
			const d = new Date();
			const p = (n: number) => String(n).padStart(2, "0");
			const text = `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
			return { content: [{ type: "text", text }], details: {} };
		},
	});

	pi.registerCommand("hello", {
		description: "打个招呼",
		handler: async (args, ctx) => {
			ctx.ui.notify(`你好 ${args || "世界"}`, "info");
		},
	});
}

三个要点:

  • 默认导出一个函数,参数是 pi(ExtensionAPI)
  • 参数 schema 用 typebox 的 Type,pi 自带这个依赖,不用自己 npm install
  • execute 的返回值形状固定:{ content: [{type, text}], details: {} }

7.2 实测:它真的被调用了

放进 extensions/ 目录,不需要任何登记,直接问:

$ date '+%Y-%m-%d %H:%M:%S'
2026-08-07 20:35:04                    ← 系统时间基准

$ pi -p "调用 local_time 工具,把它返回的原样告诉我" </dev/null
2026-08-07 20:35:09                    ← 模型返回

✅ 相差 5 秒(模型往返耗时),证明这是工具真实执行的结果,不是模型编的。

这个「用可对照的外部事实做验证」的思路,比问模型「你调用工具了吗」可靠得多。全文反复用它。

7.3 快速试验的两种方式

pi -e ./my-extension.ts       # 临时加载,适合调试(⚠️ 这种方式不支持 /reload)

📖 docs/extensions.md:7:放在自动发现目录里的 extension 可以用 /reload 热重载;pi -e 临时加载的不行。

改了什么 /reload 够不够
自动发现目录里的 extension ✅ 够
pi -e 临时加载的 ❌ 不适用
启动后动态 registerTool() ✅ 连 reload 都不用(📖 docs/extensions.md:1339)
skill ❌ 必须退出重进(第 5.3 章)

第 8 章:另外三种 API

8.1 registerCommand:注册 /命令

上面代码里的 hello 就是。用户敲 /hello 张三 触发。

✅ 实测坑:registerCommand 注册的命令在 -p 模式下完全没有输出。因为提示走的是 ctx.ui,而 -p 模式没有 TUI。写自动化脚本时别指望它。

8.2 pi.on:挂生命周期钩子

常用事件:

事件 时机 典型用途
session_start 会话开始 注入项目上下文、打招呼
tool_call 模型要调工具前 拦截、改写、阻断(第 9 章)

tool_call 的 handler 返回 { block: true, reason: "..." } 就能拦下这次调用。

8.3 registerProvider:接入自定义模型服务

✅ 2026-08-18 已实测跑通(0.84.2)。原先这一节标的是「未实测」,理由是“需要再准备一个模型服务”——后来发现根本不用,自己起一个二十行的假端点就够,而且比用真网关更能证明问题。

最小可用写法(放进 <配置目录>/extensions/probe-provider.ts,自动发现,无需登记):

import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";

export default function (pi: ExtensionAPI) {
	pi.registerProvider("probe-local", {
		baseUrl: "http://127.0.0.1:8799",
		apiKey: "$PROBE_KEY",          // $ 前缀 = 读同名环境变量
		api: "anthropic-messages",     // 用哪套流式协议
		models: [{
			id: "probe-model-1",
			name: "Probe Model 1",
			reasoning: false,
			input: ["text"],
			cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
			contextWindow: 200000,
			maxTokens: 4096,
		}],
	});
}

实测方法:拿一个假端点当“下游”。 用 Python 起一个 HTTP server 监听 8799,把收到的请求记下来,再回一段合法的 Anthropic SSE 流(里面塞一句暗号)。然后:

PROBE_KEY=whatever pi -p "say hello" --mode json \
  --provider probe-local --model probe-model-1 </dev/null

✅ 三层同时对上,才算真跑通:

验证层 实测结果
pi 侧自述 provider=probe-local model=probe-model-1 api=anthropic-messages
下游真收到 POST /v1/messages,body 里 model=probe-model-1、max_tokens=4096、用户原文
认证注入 请求头带 x-api-key,长度与 $PROBE_KEY 的值一致 → 环境变量引用生效
pi 能解析回包 终端输出了假端点塞进 SSE 的暗号 PROVIDER-PROBE-OK-7391

顺带确认两件事:baseUrl 不用自己带 /v1(写 http://127.0.0.1:8799,pi 按 anthropic-messages 协议自动拼成 /v1/messages);extension 放进目录即生效,不需要写进 settings.json。

⚠️ 必须做反向对照:把这个 extension 移走,同一条命令立刻变成 Error: Unknown provider "probe-local" + 退出码 1,假端点零命中。证实和证伪用同一把尺子量过,才能说是 extension 让它生效的。

这个反向对照还顺手挖出了第 2.4 章那条坑的真实触发条件——本来只是想确认 extension 有没有生效,结果撞见「给了 --model 就报错、不给就静默降级」的矛盾。便宜的对照实验经常比主实验更值钱。

而这一条老提醒依然有效,只是现在有了更好的做法:

注册 provider ≠ 请求真的走过去。 ~/.pi/agent/settings.json 里的 defaultProvider / defaultModel(📖 docs/settings.md:30-31)记着首次配置的值,注册一个新 provider 不会自动改它。

有人遇到过这种情况:CLI 输出一切正常,实际请求还在走旧 provider,最后靠翻网关后台日志的时间戳才发现。排查一律从下游验证,别信 CLI 的正常输出。

而“从下游验证”最省事的形态就是上面这个假端点:它把下游变成你自己的日志文件,不用求网关后台的权限,也不花一分钱 token。

这跟第 2.4 章 provider 静默回退是同一类问题的两个面:pi 在「你以为配了」和「真的生效了」之间,不会主动提醒你有落差。


第 9 章:拦截工具调用,以及它的天花板

9.1 怎么拦

pi.on("tool_call", async (event, ctx) => {
	if (event.toolName === "bash" && event.input.command?.includes("rm -rf")) {
		const ok = await ctx.ui.confirm("危险操作", "确定要执行 rm -rf 吗?");
		if (!ok) return { block: true, reason: "用户拒绝" };
	}
});

✅ 实测有效:write / edit / bash 里带 rm 的三类调用都能拦住。

9.2 🚨 天花板:它是启发式,不是沙箱

✅ 实测:上面这种基于关键词的拦截,一句话就能绕过:

python3 -c 'open("被保护的文件","w").write("")'

这条命令不含 rm、不含 mv、不含 >,关键词匹配全部落空,文件照样被清空。

更值得记的是当时的第二个发现:模型在这次操作后说「应该先创建目录」,暗示自己失败了——但文件实际已经被覆盖。

🚨 模型对自己动作后果的自述不可信,要看文件系统。

9.3 两条硬规矩

规矩一:真正的防线在 OS 层。

chflags uchg <文件>      # macOS:锁定,python 写入和 rm 都挡得住
chattr +i <文件>         # Linux 对应命令

✅ 实测确认锁定后 python 写入和 rm 都失败。extension 层的拦截只能挡住「模型不小心」,挡不住「命令绕道」。

规矩二:ctx.hasUI 为假时必须 fail-safe 阻断。

拦截逻辑里如果要弹确认框,一定要先判断有没有 UI:

if (!ctx.hasUI) return { block: true, reason: "无人值守环境,一律阻断" };

否则在 -p 模式、CI、定时任务里,你的保护恰好等于不存在——最需要保护的自动化场景反而裸奔。


第 10 章:多 agent 分工

pi 官方不内置 sub-agents(见第 1 章那句 intentionally),要装扩展。

10.1 两个方向相反的方案

pi-subagents pi-dynamic-workflows
抽象方式 预定义角色(内置 9 个) 主 agent 现场生成 JS 脚本,在 Node VM 沙箱里编排
上下文 fresh(干净)/ fork(继承主会话)可选 完全不继承主会话
短板 角色要事先定义好 无持久化、无断点恢复

✅ 本文实测用的是 pi-subagents 0.40.0(内置 agent 9 个)。

🚨 2026-08-18 版本核查:0.50.0 把内置 agent 从 9 个砍到 6 个,升级前务必看这张表。

0.40.0(9 个) 0.50.0 里的下场
oracle、worker、delegate、researcher、reviewer、scout ✅ 保留(6 个)
advisor ⚠️ 并入 oracle 成为它的别名——名字还能继续用,但拿到的是 oracle 的定义
context-builder、planner ❌ 彻底删除,连别名都没留——脚本里点名调用会失败

planner 的消失影响最大,它是 0.40.0 里四个 fork 型 agent 之一。如果你的编排里有「先让 planner 出方案」这一步,升级到 0.50.0 会直接断掉。

机制本身没变(pi-subagents 字段读取、settings.packages 收集包根、builtin < package < user < project 优先级全部照旧),但源码行号全变了(350→367、488→505),本文其它地方引用行号时以 0.40.0 为准。

⚠️ dynamic-workflows 完全不继承主会话,背景信息必须重复写进提示词,否则子 agent 基于空白上下文瞎猜。

⚠️ 选型提醒:pi-dynamic-workflows 的仓库 About 为空、无文档、写作时已两个月未更新。

10.2 🚨 用工具白名单做权限约束,别用提示词

这是最值得搬到任何多 agent 系统的一条:

---
name: security-reviewer
tools: [read, grep, find, ls, bash]     # ← 没有 write,它想改也改不了
---

比在 prompt 里写「请不要修改文件」可靠一个数量级。 提示词是请求,白名单是物理限制。

同理,审查类 agent 必须用 fresh 而不是 fork——继承了主会话就等于让它自己批准自己。

控制这件事的字段叫 defaultContext(写 fork 才继承,不写就是 fresh)。✅ 08-18 逐个读 frontmatter 核过一遍:

  • 0.40.0:advisor / oracle / planner / worker 四个是 fork,其余五个 fresh
  • 0.50.0:只剩 oracle / worker 两个是 fork(另两个 fork 型的 advisor、planner 一个并成别名、一个被删)

⚠️ 本文早期版本这里写的是「oracle / planner / worker 是 fork」——漏了 advisor,已更正。这个数是逐个 agent 读 frontmatter 数出来的,不是凭印象写的。

10.3 实测得到的三条经验

  • ✅ 子 agent 很慢:单条核对要几分钟;连续两次 10 分钟超时是常态。--mode json 还会整体缓冲,超时的话一个字节都拿不到
  • ✅ 便宜模型不守数值规则:给 haiku 写「误差 ±2 秒内算通过」,0.98 秒的偏差照样被报成错误。容差要写成它能一步步执行的判断步骤
  • ✅ 给 LLM 用的工具,报错信息要具体到字段和类型:报 each meta phase must have a title string,模型一次就自己改对了;只报 invalid workflow 它就只能瞎猜。这是「让模型现场写脚本」这种范式能跑通的前提

第 11 章:打包分发

把 extension + skill + prompt + agent 定义打成一个包,换台机器一条命令装好。

11.1 包长什么样

~/pi/my-package/
├── package.json
├── extensions/my-tool.ts
├── skills/my-skill/SKILL.md
├── prompts/my-prompt.md
└── agents/my-agent.md

package.json:

{
  "name": "my-package",
  "version": "0.1.0",
  "private": true,
  "pi": {
    "extensions": ["./extensions/my-tool.ts"],
    "skills": ["./skills"],
    "prompts": ["./prompts"]
  },
  "pi-subagents": {
    "agents": ["./agents"]
  }
}

安装:

pi install /绝对路径/my-package      # 本地路径登记,改包即生效,不用重装
pi list                              # 确认登记上了

装完 ~/.pi/agent/settings.json 会出现:

{ "packages": ["...", "../../pi/my-package"] }

11.2 ✅ 四类资源全部随包走

唯一的开关是 settings.json 里的 packages 登记,不需要任何软链接。

✅ 零软链状态下的三组对照:

配置 结果
有包登记 agent 可发现,source=package,路径直指包内
无包登记 直接消失
有软链 + 有登记 12 个,但三个 agent 报成 source=user(软链目录赢)

优先级是 builtin < package < user < project。⚠️ 注意第三行:同名只保留优先级最高的那份,所以有软链时 [package] 一栏根本不出现——这会让你完全看不出包分发到底通没通。

11.3 🚨 一个价值很高的翻车案例

这一节讲的是怎么验证,比结论本身更重要。

当初的错误结论:「包里的 agents 不生效,稳妥做法是软链到 ~/.pi/agent/agents/」。

错在判据——当时拿「问模型:你有哪些自定义 subagent?」当探针:

  • 软链之前,模型答「没找到」→ 判定包机制不生效
  • 软链之后,模型报得出名字 → 判定软链才是关键

真相是:pi-subagents 的工具描述根本不列举任何 agent 名字(连内置的 9 个也不列),模型只能靠 {action:"list"} 主动查。它没查,多半只是 ls 了一下那个空目录。以为在测包机制,实际在测那个目录里有没有文件。

🚨 最该记住的一条:当时已经写下「不能排除它自己读了那个 md」的警惕,却只用在「软链后模型报得出」这一侧,没用在「软链前答不出」那一侧。

同一个探针,证实和证伪两个方向必须用同一把尺子量。单向的怀疑等于没怀疑。

正确做法:别拿模型的自然语言回答当机制探针,直接调发现函数。

// 直接调 pi-subagents 的 discoverAgents,绕开模型看 agent 到底从哪加载
const agents = await jiti.import(path.join(SUBAGENTS, "src", "agents", "agents.ts"));
const found = agents.discoverAgents(cwd, "both").agents;
// 按 source 分组打印:builtin / package / user / project

⚠️ 技术细节:不能用 node --experimental-strip-types 直接跑包里的 .ts(Node 拒绝为 node_modules 下的文件剥类型,报 ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING),借 pi-subagents 自带的 jiti 加载即可。

✅ 用这个探针做的最终验证(删掉全部软链前后各跑一次):

删除前:三个 agent  source=user     路径 ~/.pi/agent/agents/*.md
删除后:三个 agent  source=package  路径 ~/pi/my-package/agents/*.md
总数都是 12 个,只有来源变了。

11.4 验收自检

装完跑这几条才算真装好:

pi list                                   # 包在登记里
pi -p "调用 <你的工具名>" </dev/null      # 工具真被调用(用可对照的外部事实验证)
pi -p "报暗号" </dev/null                 # skill 生效(配合 --no-skills 反向对照)
node tools/probe-agents.mjs               # agent 的 source 是 package 而不是 user

第 12 章:迁移到真实团队项目

前面都在自己的一亩三分地。搬进一个多人协作的大仓库,关注点完全不同。

本章实测目标:某内网 Unity 游戏仓库,191 GB,git 实际跟踪约 10 万个文件,团队协作,已有 27 个自建 skill 和一份大号 AGENTS.md。测于 2026-08-05。(具体标识已脱敏,实测数字原样保留。)

12.1 ✅ 性能不是问题

10 万文件的仓库,pi 启动只要 5.8 秒(含一次模型往返)。

「大仓库会拖慢 agent」这个担心可以划掉。

12.2 ✅ trust 前后的差别(实测了一半)

不加 --approve 时:

$ pi -p '(1) 你能看到几个 skill;(2) 上下文里有没有 AGENTS.md'
(1) 看到 15 个 skill,项目里那 27 个一个都没有
(2) 有 AGENTS.md 的内容

两条都印证了官方文档:

  • 项目级 skill 需要 trust 才加载——那 15 个全是全局的
  • ⚠️ AGENTS.md / CLAUDE.md 不管 trust 与否都加载(📖 docs/security.md:27)。每次启动固定进上下文,这部分省不掉
    • ✅ 而且它会长大:同一个文件 08-05 实测 26 KB,08-07 复查已经 29.2 KB(29,905 字节)。团队仓库的 AGENTS.md 只增不减是常态,这是一笔每次启动都要付、且在悄悄变贵的固定成本

✅ 跑完确认副作用:trust.json 不存在、项目里没多出 .pi/ 目录。没 trust 就不写任何东西。

🚧 带 --approve 的那一半没测:这个动作会加载并执行该项目 27 个未经审核的本地 skill,超出「只读探查」的授权范围,因此停下了。

这次停下本身就说明了要点:--approve 的粒度很粗——它一次性信任项目里所有 agent 可执行内容(.pi/extensions、.pi/skills、.agents/skills、.pi/settings.json),不是逐个批准。

12.3 🚨 真正的风险是这两件事

风险一:.pi/extensions 是团队仓库里的可执行代码。

📖 docs/security.md:31:pi 没有内置沙箱,extension 以你的完整系统权限运行。

任何人往仓库提交一个 .ts,只要你 trust 过这个项目,它就在你的机器上跑。 内网协作仓库尤其要当心——你信任的是「这个项目」,不是「今天提交这个文件的人」。

风险二:编辑器会让工作树一直在动。

✅ 实测:Unity 编辑器开着的时候,工作树持续出现改动(99 个改动来自编辑器的自动导入和 shader 编译)。

所以给 agent 的第一条项目规矩应该是:不碰 git 写操作。 否则它会把编辑器生成的一堆临时改动一起提交上去。

12.4 迁移清单

# 1. 只读探查(先别加 --approve)
cd <项目>
pi -p "这个项目的结构是什么" </dev/null

# 2. 决定要不要 trust —— trust 前先看清楚要执行什么
ls .pi/extensions .pi/skills .agents/skills 2>/dev/null

# 3. 全局资源天然可用,不需要 trust
#    你打包好的工具/skill/agent 都是全局的,进任何项目都能用

# 4. 尽量什么都别往项目里加
#    trust 是项目级开关:你不加 .pi/extensions,拦不住队友加

第 13 章:贯穿案例——把一条真实流程装进 pi

前面 12 章的能力,合起来能干什么?这是一个真实跑通的例子:视频 → 带可核验时间戳的学习笔记。

一个包(~/pi/pi-video-note/)里装了四类资源:

资源 做什么 用到的章节
extension video-context.ts 注册 video_context 工具,按时间查转录原文和帧 第 7 章
skill video-note/ 固化笔记规矩(时间戳必须有来源、数字要核对、覆盖率按最坏情况声明) 第 5 章
prompt video-note.md 一句 /video-note 启动整条流程 第 6 章
3 个 agent 时间戳核对 / 数字核对 / 读帧,全用便宜模型、无 write 权限、fresh 第 10 章
tool_call 拦截 保护转录产物不被误删 第 9 章

从这个案例里得到的通用教训

🚨 「格式完好、数值漂亮的索引」也可能是假权威值。

当初用 ffmpeg 的 fps=1/N,showinfo 抽帧,打印出来的 pts 数值非常漂亮(0、N、2N…,帧数对得上,偏移为 0),于是直接当权威值写进了文档和索引文件。

当天就被推翻:那个 pts 是滤镜输出轴的时刻,不是源视频里的时刻。用另一种方法抽帧,pts 完全相同,但 53 张图里有 50 张内容不一样。

凡是「元数据自称的时刻」,都要用「按这个时刻去取一次」反过来验证内容。

当时验了帧数、间隔、偏移三项全中,唯独没验第四项——图到底是不是来自那个时刻。

这条教训的通用形式,和第 11 章的探针翻车、第 4 章的卸载静默失败是同一件事:验证要打在「结果」上,不能打在「过程看起来对不对」上。


附录 A:这份文档推翻了什么

合并两份旧文档时,以下结论被实测推翻,请勿再参考旧版:

旧说法 出处 真实情况
「pi 有安全模式,每次调用要批准」 260724 ❌ pi 默认放行所有工具调用。要限制用 --tools / --exclude-tools
「--approve 是权限开关」 260724 ❌ 它是信任项目本地文件的开关,粒度很粗
「本机自带 31 篇官方文档」 260724 ❌ 0.81.1 复点是 29 篇;0.84.2 已是 30 篇(这个数会随版本变,别当常量记)
「改完 skill /reload 就行」 通行说法 ❌ 对 skill 无效,必须退出重进
「包里的 agents 不生效,要软链」 260805 初版 ❌ 四类资源全部随包走,开关是 settings.packages
「10 万文件的仓库会拖慢启动」 260804 归档 ❌ 实测 5.8 秒(含模型往返)
「用 grep -F 查引文定位时间戳」 260804 笔记 ❌ 只能单行匹配,跨字幕条的引文一律漏
「--provider 默认是 google」 pi --help ❌ 实测默认走 anthropic + 旗舰模型(挑有 auth 的)

这张表本身就是一条方法论:这些错误没有一条是「查文档不仔细」造成的,全都是**「看起来成功了」和「真的成功了」之间的落差**。


附录 B:覆盖率声明

本文实测覆盖的部分(✅):

  • 零配置首次启动的完整行为(报错文案、退出码、生成的文件)
  • provider 配置(走中转网关路线)、provider/模型名写错的三组对照
  • extension 自动发现、registerTool 真实调用验证
  • skill 编写、生效验证(暗号法 + --no-skills 反向对照)
  • skills CLI 装 / 列 / 卸的完整流程,含两个静默失败的坑
  • 包机制的三组对照与 discoverAgents 探针
  • PI_CODING_AGENT_DIR 隔离环境

没有实测、按标记信任的部分:

  • 📖 npm i -g 从零安装(本机 pi 是随其他工具链装的)
  • 📖 官方 API 直连(本文实测走的是中转网关,机制相同但未直接验证)
  • 📖 /login 的 OAuth 流程
  • ⚠️ --approve 之后的完整行为(涉及执行未审核的第三方 skill,主动停下了)
  • ⚠️ pi 的「自动推荐 skill」能力(旧记录,本次未复测)

2026-08-18 补测后从这份清单里移出的:

  • ✅ registerProvider 自定义 provider —— 已实测跑通,见 8.3 节。当初标 ⚠️ 的理由(“需另备一个模型服务”)是个错误的前提:用一个不到百行的 Python 标准库 HTTP server 起个假端点就够,还比真网关更能验证。「缺条件所以没测」这种判断,值得先怀疑一下条件是不是真的缺。

本文没有覆盖的:Windows / WSL、RPC 与 SDK 集成模式、theme 定制、MCP 接入(pi 原生不支持,需装 pi-mcp-adapter)。


附录 C:延伸阅读

  • 本机自带全套官方文档(查 pi 的问题优先看这里,别搜网): <pi安装路径>/docs/,与本机版本完全对齐。0.81.1 是 29 篇,0.84.2 是 30 篇——这个数随版本涨,用 ls docs/*.md | wc -l 现查,别照抄。 常用:extensions.md(API 全集,0.81.1 是 2953 行、0.84.2 是 2992 行)、custom-provider.md(774 行,8.3 节那套写法的出处)、usage.md、skills.md、settings.md、providers.md、models.md、security.md、packages.md
  • 存档文档(本文取代了它们,但保留了更细的实测记录):
    • PiAgent/260724-PiAgent-小白教程-从入门到精通.md
    • PiAgent/260805-PiAgent-扩展实战-视频笔记全流程教程.md(含完整的证据链和逐步实测记录)
  • 视频笔记:PiAgent/260804-{1,2,3,4}-…_学习笔记.md(P1–P4 四期,各自独立成篇)