第 12 章 · Themes 主题定制
本章目标:理解 pi 主题的 JSON 结构与 51 个必需颜色 token,学会安装/切换主题,并从零编写一个可分享的完整自定义主题。
12.1 主题是什么、放在哪里
pi 的主题就是定义 TUI 配色的 JSON 文件。加载位置按优先级覆盖:
| 来源 | 路径 | 说明 |
|---|---|---|
| 内置 | — | dark、light 两套 |
| 全局 | ~/.pi/agent/themes/*.json | 所有项目可用 |
| 项目 | .pi/themes/*.json | 项目被信任后才加载 |
| 包 | 包内 themes/ 目录或 package.json 的 pi.themes | 随包分发 |
| 设置 | settings.json 的 themes 数组 | 指定文件或目录 |
| CLI | --theme <路径>(可重复) | 临时追加 |
用 --no-themes 可以禁用所有发现机制,只留内置主题。
12.2 切换主题的三种方式
# 方式一:交互内切换(立即生效并保存)
/settings # 在菜单中选择主题
# 方式二:写进 settings.json
{
"theme": "my-theme"
}
# 方式三:仅本次运行生效(不改保存的设置)
pi --use-theme light一个贴心细节——跟随终端外观自动选择:
pi --use-theme light/dark
# 斜杠语法:浅色终端用 light,深色终端用 dark首次运行时 pi 会检测终端背景色,默认选 dark 或 light。
12.3 从零做一个完整主题
mkdir -p ~/.pi/agent/themes创建 ~/.pi/agent/themes/solar-mint.json:
{
"$schema": "https://raw.githubusercontent.com/earendil-works/pi/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
"name": "solar-mint",
"vars": {
"mint": "#2dd4a7",
"ink": "#1b2430",
"paper": "#f5f3ec",
"gray": 242,
"amber": "#d97706"
},
"colors": {
"accent": "mint",
"border": "mint",
"borderAccent": "#14b8a6",
"borderMuted": "gray",
"success": "#16a34a",
"error": "#dc2626",
"warning": "amber",
"muted": "gray",
"dim": 240,
"text": "",
"thinkingText": "gray",
"selectedBg": "#e7f6f0",
"userMessageBg": "#eef2f7",
"userMessageText": "",
"customMessageBg": "#f0fdf9",
"customMessageText": "",
"customMessageLabel": "mint",
"toolPendingBg": "#fffbeb",
"toolSuccessBg": "#f0fdf4",
"toolErrorBg": "#fef2f2",
"toolTitle": "mint",
"toolOutput": "",
"mdHeading": "amber",
"mdLink": "mint",
"mdLinkUrl": "gray",
"mdCode": "#0f766e",
"mdCodeBlock": "",
"mdCodeBlockBorder": "gray",
"mdQuote": "gray",
"mdQuoteBorder": "mint",
"mdHr": "gray",
"mdListBullet": "mint",
"toolDiffAdded": "#16a34a",
"toolDiffRemoved": "#dc2626",
"toolDiffContext": "gray",
"syntaxComment": "gray",
"syntaxKeyword": "#0369a1",
"syntaxFunction": "#7c3aed",
"syntaxVariable": "amber",
"syntaxString": "#15803d",
"syntaxNumber": "#be185d",
"syntaxType": "#0369a1",
"syntaxOperator": "#475569",
"syntaxPunctuation": "gray",
"thinkingOff": "gray",
"thinkingMinimal": "mint",
"thinkingLow": "#0ea5e9",
"thinkingMedium": "#06b6d4",
"thinkingHigh": "#8b5cf6",
"thinkingXhigh": "#d946ef",
"bashMode": "amber"
}
}要点解析:
$schema让编辑器获得自动补全与校验;vars定义可复用颜色变量,colors里用名字引用;colors必须定义全部 51 个必需 token;可选 token(thinkingMax、scrollbarThumb、searchMatchBg/Text)缺省时有回退值(如scrollbarThumb回退到selectedBg)。
热重载
编辑当前激活的自定义主题文件后,pi 会自动重新加载,改色即时可见,非常适合调配色。
12.4 颜色值的四种格式与终端兼容性
| 格式 | 示例 | 说明 |
|---|---|---|
| Hex | "#ff0000" | 6 位十六进制 RGB |
| 256 色 | 39 | xterm 256 色板索引(0-255) |
| 变量引用 | "primary" | 指向 vars 中的条目 |
| 终端默认 | "" | 使用终端默认前景/背景 |
256 色索引计算:基础 ANSI 色 0-15;RGB 立方体 16 + 36×R + 6×G + B(R/G/B 取 0-5);232-255 是灰阶渐变。
echo $COLORTERM
# 输出 truecolor 或 24bit 说明终端支持真彩pi 使用 24-bit RGB;老终端只有 256 色时会自动降级为最近似颜色。VS Code 用户建议把 terminal.integrated.minimumContrastRatio 设为 1,避免 VS Code 私自调整你的配色。
12.5 分享主题与编程式操作
分享就是把 JSON 放进 pi 包(下一章详解):包内放 themes/ 目录,或在 package.json 里声明:
{
"name": "my-themes-pack",
"keywords": ["pi-package"],
"pi": {
"themes": ["./themes"]
}
}扩展里也可以编程式查询和切换:
export default function (pi: ExtensionAPI) {
pi.registerCommand("theme-light", {
description: "切到浅色主题",
handler: async (_args, ctx) => {
const themes = ctx.ui.getAllThemes(); // [{ name, path? }, ...]
const light = ctx.ui.getTheme("light"); // 加载但不切换
const result = ctx.ui.setTheme(light!); // 或 setTheme("light") 按名切换
if (!result.success) {
ctx.ui.notify(`切换失败: ${result.error}`, "error");
}
ctx.ui.theme.fg("accent", "已切换"); // 当前主题对象可直接取色渲染
},
});
}注意 RPC 模式下主题 API 会降级(getAllThemes() 返回空数组等),这是官方文档明确列出的限制。
本章小结
- 主题 = 带
$schema的 JSON;51 个必需 token + 少量可选 token(有回退规则); - 三处安装位置(全局/项目/包)+
--use-theme单次生效 +light/dark跟随终端语法; - 四种颜色格式:hex / 256 色索引 / vars 引用 / 终端默认
""; - 当前主题文件支持热重载,配色调试体验极佳;
- 扩展可通过
ctx.ui.getAllThemes()/getTheme()/setTheme()编程式管理主题。
🧪 随堂测验
点击你认为正确的选项。答错时会展示正确答案与原因解析。
1. 一个合法的 pi 自定义主题必须定义多少个必需颜色 token?
2. 正在使用的自定义主题文件被修改后会发生什么?
3. pi --use-theme light/dark 的含义是?
4. 主题 JSON 中颜色值写成 39 是什么含义?
🛠️ 动手实践
- 以你喜欢的配色体系(Nord/Gruvbox/Tokyo Night 等)为基础,用
vars组织出一套完整主题并通过/settings启用。 - 故意删掉主题里的
mdHeadingtoken,观察 pi 报什么错,再补回来验证校验逻辑。 - 编写一个小扩展:注册
/theme-cycle命令,每次调用就在getAllThemes()列表里循环切换主题并用notify提示当前主题名。