首页
/ pi Pi 包完全指南:pi install 安装管理、npm/git 源、pi manifest 与资源过滤实战

pi Pi 包完全指南:pi install 安装管理、npm/git 源、pi manifest 与资源过滤实战

2026-09-06 13:32:40作者:齐添朝

本文围绕 pi 的包(Pi Package)机制展开:讲解如何用 pi install / pi remove / pi list / pi update 管理来自 npm、git 和本地的包,如何编写 package.json 中的 pi manifest 来发布自己的包,以及如何使用 settings 中的过滤语法精确控制包内资源的加载。读完本文,你可以独立制作、安装、过滤和升级 pi 包,并理解 pi 包管理器在源码层面的安装路径约定、去重规则与更新语义。

安全提示:Pi 包拥有完整的系统访问权限。扩展(extensions)会执行任意代码,而技能(skills)可以指示模型执行包括运行可执行程序在内的任意操作。安装第三方包之前,务必审查其源代码。

一、安装与管理:install、remove、list、update 四个核心命令

pi 包通过四个命令完成全生命周期管理。默认情况下,installremove 会写入用户级设置文件 ~/.pi/agent/settings.json;加上 -l 参数则写入项目级设置 .pi/settings.json。项目级设置可以随仓库共享给团队,pi 在项目被信任(trust)后启动时会自动安装 settings 中声明但本地缺失的包——这一点在源码 package-manager.tsresolve() 中得到印证:解析阶段发现 npm/git 源本地不存在时会回调 onMissing 并触发安装。

pi install npm:@foo/bar@1.0.0
pi install git:github.com/user/repo@v1
pi install https://github.com/user/repo  # raw URLs work too
pi install /absolute/path/to/package
pi install ./relative/path/to/package

pi remove npm:@foo/bar
pi list                     # show installed packages from settings
pi update                   # update pi only
pi update --all             # update pi, update packages, and reconcile pinned git refs
pi update --extensions      # update packages and reconcile pinned git refs only
pi update --models          # refresh model catalogs only
pi update --self            # update pi only
pi update --self --force    # reinstall pi even if current
pi update npm:@foo/bar      # update one package
pi update --extension npm:@foo/bar

这些命令负责 pi 包的管理,其中 pi update 还可以顺带更新 pi CLI 本身。从 package-manager-cli.ts 的实现可以看到几个关键行为:

  • 更新目标解析:update 命令的参数解析器(parsePackageCommand)会校验目标冲突,例如 --all 不能与 --self--extensions--models--extension 或位置参数同时使用,pi / self 可作为别名表示"只更新 pi 自身"。
  • 托管安装(experimental installer-managed)的更新语义:对由官方安装器托管的安装,pi update 会把精确校验过的版本安装到一个带 lockfile 的 staging 目录中,验证通过后才激活;如果更新失败,当前版本保持完好。托管安装不支持 --force——源码中遇到该组合会直接报错并提示重新运行安装器来修复(runManagedSelfUpdateverifyManagedRelease 会先执行 npm ci、再运行 --version 冒烟校验)。
  • 非 npm 包更新被跳过时的提示:直接运行 pi update(不指定目标)只更新 pi 自身,此时会提示 "Extensions are skipped. Run pi update --extensions to update extensions."
  • --models 的实现:pi update --models 通过 ModelRuntime.refresh({ allowNetwork: true, force: true }) 强制刷新模型目录,内置 15 秒超时保护。

卸载 pi 本身的方式请参考 Quickstart 文档

不落地安装:-e / --extension 临时试用

想在不安装的情况下试用一个包,可以用 --extension(或 -e)。它会把包装载到临时目录,仅在当前这一次运行中生效:

pi -e npm:@foo/bar
pi -e git:github.com/user/repo

从源码看,临时安装的路径由 getExtensionTempFoldergetTemporaryDir 生成,位于 ~/.pi/agent/tmp/extensions/ 下,并用源标识的 SHA-256 哈希前 8 位命名,权限固定为 0700——即每次运行互不污染,也永远不会写入 settings。

交互式开关资源:pi config

使用 pi config 可以从图形化 TUI 中启用/禁用已安装包与本地目录中的扩展、技能、提示模板和主题:

  • pi config 从全局设置(~/.pi/agent/settings.json)开始,在 TUI 中按 Tab 键可在全局与项目本地模式之间切换;
  • pi config -l 则直接以项目覆盖配置 .pi/settings.json 为起点启动,全局继承来的资源会以暗淡(dimed)样式显示。

该命令的实现在 package-manager-cli.tshandleConfigCommand 中:它会分别解析全局与项目两套 ResolvedPaths 交给 selectConfig 组件,且当项目未被信任时会拒绝 -l 模式,要求使用 --approve 显式批准。

二、包来源(Package Sources):npm、git 与本地路径

pi 在 settings 与 pi install 中接受三种来源类型,解析逻辑集中在 DefaultPackageManager.parseSource()(见 package-manager.ts):先尝试 npm: 前缀,再判断是否为本地路径,最后回退到 git URL 解析。

npm 来源

npm:@scope/pkg@1.2.3
npm:pkg
  • 精确版本会被钉住(pinned),包更新时跳过。源码中 pinned 的判定是 semver.valid(version) !== null,即只有完全版本号(如 1.0.0)算 pinned,^1.0.0latest 等范围不算;updateConfiguredSourcesif (!parsed.pinned) 才进入 npm 更新候选。
  • 用户级安装位于 ~/.pi/agent/npm/,项目级安装位于 .pi/npm/。对应源码 getNpmInstallRoot:project 作用域为 <cwd>/.pi/npm,用户作用域为 <agentDir>/npm(agentDir 即 ~/.pi/agent)。
  • settings.json 中设置 npmCommand 可以把 npm 包的查询与安装操作固定到某个包装命令(如 miseasdf)。这是 settings-manager.ts 中定义的全局设置项(argv 形式),源码的 getNpmCommand() 会把数组第一个元素作为可执行程序、其余作为参数拼接执行,例如:
{
  "npmCommand": ["mise", "exec", "node@20", "--", "npm"]
}

一个值得注意的细节:源码 getNpmInstallArgs 会根据 npmCommand 解析出的实际包管理器名称(npm/pnpm/bun)选择不同的安装参数——npm 用 --legacy-peer-deps,pnpm 关闭 auto-install-peers 与严格 peer 检查,bun 用 --omit=peer。原因是 pi 包运行在 pi 进程内,pi 核心 API 通过 loader 别名/虚拟模块注入,不允许包管理器自行解决 @earendil-works/pi-* 的 peer 依赖。

git 来源

git:github.com/user/repo@v1
git:git@github.com:user/repo@v1
https://github.com/user/repo@v1
ssh://git@github.com/user/repo@v1
  • 不带 git: 前缀时,只接受带协议的 URL(https://http://ssh://git://);
  • git: 前缀时接受简写形式,包括 github.com/user/repogit@github.com:user/repo;
  • HTTPS 与 SSH 均支持;SSH URL 自动使用你配置的 SSH 密钥(遵循 ~/.ssh/config);
  • 非交互运行(如 CI)可以设置 GIT_TERMINAL_PROMPT=0 禁用凭据提示,并设置 GIT_SSH_COMMAND(例如 ssh -o BatchMode=yes -o ConnectTimeout=5)以快速失败。源码中所有 git 远端命令(runGitRemoteCommand)都会默认带上 GIT_TERMINAL_PROMPT=0;
  • ref 是钉住的 tag 或 commitpi update --extensions / --all 不会把它移动到更新的 ref,但会把已有克隆对齐(reconcile)到配置的 ref。因此移动 ref 需要显式执行 pi install git:host/user/repo@new-ref,这会同时更新 settings 并让现有包迁到新的 pinned ref;
  • 克隆目录:全局为 ~/.pi/agent/git/<host>/<path>,项目为 .pi/git/<host>/<path>(对应 getGitInstallRoot / getGitInstallPath);
  • 当 reconcile 改变了 checkout 时,pi 会 reset 并清理该克隆,若存在 package.json 则执行 npm install(源码 getGitDependencyInstallArgs:未配置 npmCommand 时为 install --omit=dev,配置了 wrapper 时退化为纯 install 以兼容)。

SSH 示例:

# git@host:path shorthand (requires git: prefix)
pi install git:git@github.com:user/repo

# ssh:// protocol format
pi install ssh://git@github.com/user/repo

# With version ref
pi install git:git@github.com:user/repo@v1.0.0

本地路径来源

/absolute/path/to/package
./relative/path/to/package

本地路径指向磁盘上的文件或目录,写入 settings 时不复制,只是登记。相对路径会相对于它所在的那个 settings 文件解析(源码 normalizePackageSourceForSettings 甚至会把绝对路径规约成相对 baseDir 的相对形式再写入)。如果路径是文件,它会被当作单个扩展加载;如果是目录,pi 按包规则(见下文"包结构")加载其中的资源。

三、创建自己的 Pi 包

package.json 中添加 pi manifest,或直接使用约定目录。为了可被检索,建议包含 pi-package 关键字:

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

manifest 的解析实现在 pi-manifest.ts:readPiManifest() 只会接受 extensionsskillspromptsthemes 四个字段,且每个字段必须是纯字符串数组,非法字段会被静默忽略(整个 manifest 解析失败则返回 null,回退到约定目录发现)。

关于路径的几个规则:

  • 路径相对于包根目录;
  • 数组支持 glob 模式与 !exclusions;
  • 正向 manifest glob 只会发现可见路径,按字典序排列(源码 expandPackageGlob 使用 globSync,且显式过滤掉任何路径段以 . 开头的结果);
  • 需要包含点前缀(dot-prefixed)路径时,直接以精确路径列出;
  • 如果某个 glob 需要"穿过"一个符号链接才能到达资源,直接列出该符号链接的资源根目录(符号链接目录不会被自动穿透)。

画廊元数据(Gallery Metadata)

pi 的包画廊会展示打了 pi-package 标签的包。为包添加 videoimage 字段可以展示预览:

{
  "name": "my-package",
  "keywords": ["pi-package"],
  "pi": {
    "extensions": ["./extensions"],
    "video": "https://example.com/demo.mp4",
    "image": "https://example.com/screenshot.png"
  }
}
  • video:仅支持 MP4。桌面上悬停自动播放,点击打开全屏播放器;
  • image:支持 PNG、JPEG、GIF、WebP,作为静态预览显示;
  • 两者同时设置时,video 优先。

(注意:video / image 是供画廊展示使用的字段,不属于 readPiManifest 识别的资源字段,不影响本地加载。)

四、包结构:约定目录自动发现

如果包里没有 pi manifest,pi 从以下约定目录自动发现资源:

目录 加载规则
extensions/ 加载 .ts.js 文件
skills/ 递归查找 SKILL.md 文件夹,并加载顶层 .md 文件作为技能
prompts/ 加载 .md 文件
themes/ 加载 .json 文件

源码 package-manager.ts 中的 FILE_PATTERNS 常量与上表完全一致(extensions: /\.(ts|js)$/skills/prompts: /\.md$/themes: /\.json$/)。技能发现由 collectSkillEntries 实现:目录中直接存在 SKILL.md 时该目录即构成一个技能(不再向下递归);否则顶层的 .md 文件会被收集为技能,子目录继续递归查找 SKILL.md

自动发现时还有两条隐含规则,来自目录扫描代码:

  • 点前缀目录/文件与 node_modules 一律跳过(除非通过 manifest 精确列出);
  • 各目录内的 .gitignore.ignore.fdignore 规则会被读取并应用于资源收集(addIgnoreRules),所以你可以用 ignore 文件排除生成物。

扩展目录的发现比其它三类更"智能":collectAutoExtensionEntries 会先检查目录自身——若其 package.jsonpi.extensions 非空,或存在 index.ts / index.js,则以该入口为准;否则才递归扫描目录下的 .ts / .js 文件(子目录若自带 index.ts 也会作为独立扩展入口)。

五、依赖(Dependencies)管理

第三方运行时依赖应写在 package.jsondependencies 中;不注册扩展/技能/提示/主题的依赖同样属于 dependencies。因为 pi 从 npm 或 git 安装包时会执行 npm install,这些依赖会被自动安装。git 包的默认安装是生产依赖安装(npm install --omit=dev),所以**devDependencies 在运行期不可用**——只有配置了 npmCommand 时 git 包才退化为普通 install

pi 自身为扩展与技能内置捆绑(core bundled)了一组基础包。如果你 import 了下列任何一项,应在 peerDependencies 中以 "*" 范围声明,并且不要打包进自己的 tarball:

  • @earendil-works/pi-ai
  • @earendil-works/pi-agent-core
  • @earendil-works/pi-coding-agent
  • @earendil-works/pi-tui
  • typebox

其他 pi 包则必须打进你的 tarball:加入 dependenciesbundledDependencies,然后通过 node_modules/ 路径引用其资源。因为 pi 以相互独立的模块根(module roots)加载各个包,不同包的安装之间不会发生模块碰撞或共享。

示例:

{
  "dependencies": {
    "shitty-extensions": "^1.0.1"
  },
  "bundledDependencies": ["shitty-extensions"],
  "pi": {
    "extensions": ["extensions", "node_modules/shitty-extensions/extensions"],
    "skills": ["skills", "node_modules/shitty-extensions/skills"]
  }
}

六、包过滤(Package Filtering):精确控制加载范围

在 settings 中使用对象形式的包条目,可以为每个资源类型指定过滤模式:

{
  "packages": [
    "npm:simple-pkg",
    {
      "source": "npm:my-package",
      "extensions": ["extensions/*.ts", "!extensions/legacy.ts"],
      "skills": [],
      "prompts": ["prompts/review.md"],
      "themes": ["+themes/legacy.json"]
    }
  ]
}

其中 +path-path相对包根目录的精确路径。过滤语义汇总:

写法 含义
省略某个 key 加载该类型的全部资源
[] 一个都不加载
!pattern 排除匹配项(glob)
+path 强制包含一个精确路径(可覆盖排除)
-path 强制排除一个精确路径(优先级最高)
普通 glob / 精确路径 包含匹配项
autoload: false 该条目改为"从零开始、只应用显式模式"的增量模式(见下文)

过滤叠加在 manifest 之上:它们只能收窄 manifest 已允许的范围,不能引入 manifest 未声明的资源。这一流程在源码 applyPatterns() 中分四步执行:①应用 include 模式(无 include 则全量)→ ②应用 ! 排除 → ③+ 强制包含(从全量中捞回)→ ④- 强制排除(最后生效)。精确匹配(matchesAnyExactPattern)会剥掉 ./ 前缀后比较,对技能资源还支持以 SKILL.md 的父目录路径/名称作为匹配目标,因此你可以按"技能文件夹"粒度过滤技能。

七、作用域与去重(Scope and Deduplication)

包可以同时出现在全局(用户)设置与项目设置中。去重与身份(identity)规则在 dedupePackages()getPackageIdentity() 中实现,规则如下:

身份判定(用于识别"同一个包"):

  • npm:包名(npm:<name>);
  • git:去掉 ref 的仓库 URL,即 git:<host>/<path>——因此同一仓库的 SSH 与 HTTPS 两种写法被视为同一包;
  • local:解析后的绝对路径。

合并规则:

  • 同一包同时出现在全局与项目设置时,项目条目胜出;
  • 例外:当项目条目带有 autoload: false 时,它不是替换全局条目,而是作为增量(delta)叠加在全局条目之上——全局条目继续加载,项目条目只贡献其显式声明的资源过滤。源码中该逻辑由 findAutoloadDeltaBase 实现:project 作用域、对象形式、autoload === false 三者同时满足时,会定位到同身份的全局条目作为 delta 基座。

这一机制适合"团队共享一份基础包配置,个别项目在本地追加或收窄"的场景:项目里写 { "source": "npm:xxx", "autoload": false, "extensions": ["extensions/review.ts"] } 即可在全局加载之上精准追加一个扩展,而不必把其余资源全部排除。

八、源码导读:包管理器的关键调用链

如果你需要进一步深入,以下路径串联了 pi 包机制的完整实现:

  • 命令行入口与参数解析:package-manager-cli.ts 中的 handlePackageCommand(install/remove/list/update)与 handleConfigCommand(pi config);
  • 核心包管理器:package-manager.ts 中的 DefaultPackageManager——parseSource()(三种来源解析)、installAndPersist / removeAndPersist(安装并写入 settings)、updateConfiguredSources()(npm 跳过 pinned 精确版本、git 对齐 pinned ref)、dedupePackages()(作用域去重)、applyPatterns()(过滤四步法);
  • manifest 解析:pi-manifest.ts 中的 readPiManifest();
  • 全局设置项(如 npmCommand):settings-manager.ts;
  • 扩展/技能/主题等资源的类型定义与加载约定可继续参考 extensions 文档skills 文档

补充一个运维技巧:设置环境变量 PI_OFFLINE=1(或 true/yes)可进入离线模式——updateConfiguredSourcescheckForAvailableUpdates 与自动安装逻辑都会短路跳过网络操作,适合在断网环境中使用已缓存的包。

小结

pi 包机制 = 四种资源(extensions / skills / prompts / themes)+ 三种来源(npm / git / local)+ 两级作用域(user / project)。manifest 声明资源、settings 过滤收窄、pinned 版本/ref 保证可复现、项目级 autoload: false 提供增量叠加——理解这四层之后,pi installpi updatepi config 的所有行为都可以从 package-manager.ts 的源码中找到一一对应的实现。

登录后查看全文
热门项目推荐
相关项目推荐