把开源编程 Agent pi 调顺手:配置、键位、主题、终端四处各改哪里

2026-07-29

本文基于 pi v0.82.1(仓库 commit 027a584,2026-07-28)梳理,该项目仍在快速迭代,具体行为以官方文档 https://pi.dev/docs/latest 与仓库最新代码为准。

**pi 的定制能力被切成了四层,而绝大多数”调不顺手”的抱怨,都是因为改错了层。**你按了 Shift+Enter 想换行却直接把消息发出去了——这不是键位配置的问题,是终端根本没把这个键传给 pi;你想让某个项目用不同的模型,改全局 settings.json 是白改,那一项应该落在项目目录里。搞清楚这四层各管什么,剩下的就是查表。

pi 是 earendil-works 开源的编程 Agent,MIT 许可证,主仓库在 https://github.com/earendil-works/pi ,截至 2026 年 7 月 GitHub 上约 8 万 star。它是终端 TUI 形态的工具,所以定制这件事天然比 IDE 插件多了一层——终端本身。

站内已有的 cursor-rules 最佳实践CLAUDE.md 怎么写 讲的是通用方法论:怎么把项目约定写成 Agent 能读懂的规则。这篇不重复那些,讲的是一个具体项目把”可配置”这件事落到实处时,文件放在哪、优先级怎么定、改完怎么生效。方法论和实现细节是两回事,后者只能一个项目一个项目地看。

组成部分它负责什么对应仓库位置你什么时候会碰到它
设置文件模型、思考等级、压缩、重试、Shell、资源加载路径等运行行为packages/coding-agent/docs/settings.md第一天装完就该看一遍
键位文件所有键盘动作的绑定,包括编辑器光标、会话操作、树导航packages/coding-agent/docs/keybindings.md手指记忆和默认键打架时
主题文件TUI 全部配色,51 个必需颜色令牌packages/coding-agent/docs/themes.mdpackages/coding-agent/src/modes/interactive/theme/dark.json换了终端配色方案看不清字时
终端配置让修饰键能被传进 pi 的进程packages/coding-agent/docs/terminal-setup.mdShift+EnterAlt+Enter 不工作时
环境变量一次性覆盖、关闭联网行为等开关packages/coding-agent/docs/environment-variables.md想临时改行为又不想动配置文件时

这张表的顺序不是随便排的。前三行都在 pi 自己的地盘里,改完 pi 说了算;终端配置那一行在 pi 的地盘之外,你改再多 pi 的配置都没用。排查问题时从下往上想,比从上往下试省时间。最后一行的环境变量不算独立的一层,它是横跨前面几层的临时开关——同一件事你既可以写进设置文件长期生效,也可以用一个 PI_ 开头的变量只在这一次运行里生效,下面提到的硬件光标就是这种两条路都通的例子。

一、设置文件:两层覆盖,加一道信任闸

pi 的设置是 JSON,分两个位置:~/.pi/agent/settings.json 是全局的,.pi/settings.json 是当前目录的项目级设置。项目覆盖全局,且嵌套对象是合并而不是整体替换。文档里给的例子很能说明问题:全局写了 theme: darkcompaction 的两项,项目里只写 compaction.reserveTokens,结果是主题保留、compaction.enabled 保留、只有 reserveTokens 被换掉。这意味着项目级文件可以写得很薄,只放真正需要偏离的那几项。

改法有两种:直接编辑文件,或者在交互模式里用 /settings 挑常用项。

项目级设置带来一个安全问题:你 clone 别人的仓库,里面藏了 .pi/settings.json,pi 该不该照着执行?pi 的答案是加一道信任闸。交互式启动时,如果一个文件夹带有项目级设置、资源或项目 .agents/skills,而 ~/.pi/agent/trust.json 里对它和它的父目录都没有存过决定,pi 会先问你。信任之后,pi 才会加载 .pi/settings.json.pi 资源、安装缺失的项目包、执行项目扩展。

非交互模式(-p--mode json--mode rpc)不会弹提示——没人在那儿点确认。这时候走的是全局设置里的 defaultProjectTrust,取值 askalwaysnever,默认 askasknever 在非交互下都是忽略项目资源,只有 always 会信任。单次运行可以用 --approve/-a--no-approve/-na 覆盖。pi config 和包管理命令走同一套流程,例外是 pi update 从不提示。

想把决定存下来,在交互模式里用 /trust。有个细节值得记:它只写 ~/.pi/agent/trust.json,当前会话不会重新加载,得重启 pi 才生效。

值得先改的几项

defaultProviderdefaultModeldefaultThinkingLevel 是三件套,思考等级从 offminimallowmediumhighxhigh 一直到 max。如果你嫌思考块占屏,hideThinkingBlock 设为 true;如果反过来想知道提示缓存什么时候没命中,showCacheMissNotices 打开。

enabledModels 决定 Ctrl+P 循环时能切到哪些模型,格式和 --models 命令行参数一致,支持通配:

{
  "enabledModels": ["claude-*", "gpt-4o", "gemini-2*"]
}

externalEditor 是我认为最该第一天就改的一项。它决定 Ctrl+G 打开哪个编辑器,优先级高于 $VISUAL$EDITOR,缺省在 Windows 上是记事本、其它平台是 nano。用 VS Code 的话必须带 --wait,否则 pi 不知道你什么时候编辑完:

{
  "externalEditor": "code --wait"
}

显示相关的几项是小调整但很影响观感:editorPaddingX 是输入框的水平内边距(0 到 3),outputPad 控制消息和思考块的水平留白(只能是 0 或 1),autocompleteMaxVisible 是补全下拉最多显示几条(3 到 20),quietStartup 藏掉启动头,doubleEscapeAction 决定双击 Esc 干什么(treeforknone)。

Shell 那组适合有特殊环境的人:shellPath 指定自定义 shell(文档举的例子是 Windows 上的 Cygwin),shellCommandPrefix 给每条 bash 命令加前缀(例子是 shopt -s expand_aliases),npmCommand 是 argv 数组,用于所有 npm 包管理操作,比如用 mise 管 Node 版本时,写成 ["mise", "exec", "node@<版本>", "--", "npm"] 这种形式,每一项就是进程启动时的一个 argv。用户级 npm 包装在 ~/.pi/agent/npm/,项目级装在 .pi/npm/

有一项建议你别动

retry.provider.maxRetries 默认是 0,文档明确建议保持不动。理由是:把它调到 0 以上会让 SDK 层的重试先于 pi 处理掉超额度错误,某些情况下会让 Agent 一直卡在那里等服务商配额恢复。这是典型的”看起来更稳其实更糟”的设置——重试逻辑应该由知道上下文的那一层来做,而不是最底下那层。pi 自己的 Agent 级重试由 retry.enabledretry.maxRetriesretry.baseDelayMs 控制,指数退避默认 2 秒起。另有一项 retry.provider.maxRetryDelayMs,当服务商要求的重试延迟超过它时,请求会立刻带着明确错误失败,而不是无声地干等;设为 0 可以取消这个上限。

上下文压缩那组(compaction.enabledcompaction.reserveTokenscompaction.keepRecentTokens)值得单独理解,它直接影响长会话的表现,思路和 Agent 上下文预算怎么定 里讲的那套是相通的。

二、键位:命名空间 id,改完 /reload

键位配置在 ~/.pi/agent/keybindings.json,每个动作可以绑一个键或一个键数组,用户配置覆盖默认值。改完不用重启,在 pi 里跑 /reload 就生效——这一点比设置文件友好。

配置用的是带命名空间的 id,和 pi 内部、以及扩展作者在 keyHint() 和注入的 keybindings 管理器里用的是同一套。老配置里的 cursorUpexpandTools 这类没命名空间的写法,启动时会自动迁移。

键的写法是 modifier+key,修饰键有 ctrlshiftalt 且可组合,键名覆盖字母、数字、escape/enter/tab/space/home/end/pageUp/pageDown 这些特殊键、f1f12,以及一批符号。文档里直接给了 Emacs 和 Vim 两套现成配置,Emacs 那套长这样:

{
  "tui.editor.cursorUp": ["up", "ctrl+p"],
  "tui.editor.cursorDown": ["down", "ctrl+n"],
  "tui.editor.cursorLeft": ["left", "ctrl+b"],
  "tui.editor.cursorRight": ["right", "ctrl+f"],
  "tui.editor.cursorWordLeft": ["alt+left", "alt+b"],
  "tui.editor.cursorWordRight": ["alt+right", "alt+f"],
  "tui.editor.deleteCharForward": ["delete", "ctrl+d"],
  "tui.editor.deleteCharBackward": ["backspace", "ctrl+h"],
  "tui.input.newLine": ["shift+enter", "ctrl+j"]
}

默认键位里藏着的信息

翻一遍默认表,能看出这个 TUI 的设计取向。编辑器那组几乎照搬了 Readline/Emacs 的习惯:ctrl+a/ctrl+e 行首行尾,ctrl+w 删词,ctrl+u/ctrl+k 删到行首行尾,还带一套 kill ring(ctrl+y 粘贴最近删除的内容,alt+y 在删除历史里循环)。用惯 shell 的人基本不用改。

更要留心的是同一个键在不同上下文里是不同动作ctrl+d 在编辑器为空时是退出应用,在会话列表里是删除会话,在 /tree 里是把过滤器设回默认视图;ctrl+p 平时是切下一个模型,在会话列表里是切换路径显示。这套设计省了键位空间,代价是你得知道自己现在在哪个界面里;改绑定时也要认准 id,改 app.model.cycleForward 不会影响 app.session.togglePath

有四个动作默认没有绑定app.session.newapp.session.treeapp.session.forkapp.session.resume,分别对应 /new/tree/fork/resume。如果你会话切得频繁,这四个是最值得自己补绑的——文档里它们的默认值那一栏就写着「无」,这块键位空间等着你自己安排。

另一个平台差异:app.suspend(默认 ctrl+z)在原生 Windows 上没有默认绑定,因为 Windows 终端不支持 Unix 作业控制。你手动绑了也不会挂起,pi 会显示一条状态消息。在 WSL 里 ctrl+z/fg 的正常 Linux 行为仍然有效。粘贴图片也分平台:app.clipboard.pasteImage 在多数平台是 ctrl+v,Windows 上是 alt+v

三、主题:51 个令牌,vars 是省力的关键

主题是 JSON 文件。pi 从这些地方找主题:内置的 darklight、全局的 ~/.pi/agent/themes/*.json、项目的 .pi/themes/*.json(要项目被信任之后)、包里的 themes/ 目录或 package.json 中的 pi.themes 条目、设置里的 themes 数组,以及命令行 --theme <path>(可重复)。--no-themes 关掉发现。首次运行时 pi 会检测终端背景,自动选 darklight

一个主题文件里 name 必填、要唯一、不能带 /colors 必须定义全部 51 个必需令牌,thinkingMax 是唯一的例外,可选,省略时回退到 thinkingXhigh。51 个听着吓人,但 vars 让它变成可控的活:先定义一小组基色,再在 colors 里引用。内置的 dark.json 就是这么写的,它只定义了十几个变量,剩下全是引用。下面是它的节选,只挑了几对变量和引用,真正的文件里 colors 那一段要把令牌一个不落地列全:

{
  "name": "dark",
  "vars": {
    "cyan": "#00d7ff",
    "blue": "#5f87ff",
    "green": "#b5bd68",
    "red": "#cc6666",
    "gray": "#808080"
  },
  "colors": {
    "accent": "accent",
    "border": "blue",
    "borderAccent": "cyan",
    "success": "green",
    "error": "red",
    "muted": "gray"
  }
}

颜色值支持四种格式:六位十六进制 "#ff0000"、xterm 256 色调色板索引(数字 0 到 255)、指向 vars 条目的变量名,以及空字符串 "" 表示用终端的默认色。text 这类令牌一般就写 "",跟着终端走。

51 个令牌按用途分组:核心 UI 11 个、背景与内容 11 个、Markdown 10 个、工具 diff 3 个、语法高亮 9 个、思考等级边框 6 必需加 1 可选、bash 模式 1 个。思考等级那组挺有意思——输入框边框颜色会随当前思考等级变化,从 thinkingOffthinkingMax 形成视觉梯度,等于把一个状态变量画在了你天天盯着的那个框上。bashMode 则是你用 ! 前缀进入 bash 模式时的边框色。另有一个可选的 export 段,控制 /export 导出 HTML 的几处背景色,省略时从 userMessageBg 推导。

两个实用点。一是热重载:编辑当前正在用的自定义主题文件,pi 会自动重新加载,调色时不用来回重启。二是文件顶部那个 $schema 字段,指向仓库里的 theme-schema.json,加上它编辑器就能补全和校验——51 个令牌手写漏一个很正常。

pi 用 24 位真彩色,老终端只有 256 色时会回退到最接近的近似色,想确认可以看 $COLORTERM 是不是 truecolor24bit

四、终端环境:这一层不在 pi 的控制范围内

pi 用 Kitty 键盘协议 来可靠地识别修饰键。这是理解终端这一层的关键:传统终端把 Shift+EnterEnter 发成同一串字节,程序无从分辨。Kitty 协议扩展了编码,让修饰键组合能被区分出来。

Kitty 和 iTerm2 开箱即用。其它的分几档:

Apple Terminal 在可用时 pi 会启用增强按键上报;如果它仍然把 Shift+Enter 发成普通 Return,pi 有一个 macOS 本地修饰键回退,把那个 Return 当成 Shift+Enter。这个回退只在 pi 跟 Terminal.app 跑在同一台 Mac 上时有效,走 SSH 到远端就检测不到本地键盘了。

Ghostty 需要加一条配置(macOS 在 ~/Library/Application Support/com.mitchellh.ghostty/config,Linux 在 ~/.config/ghostty/config):

keybind = alt+backspace=text:\x1b\x7f

Ghostty 这里还有一个坑:早期 Claude Code 版本可能让你加过 keybind = shift+enter=text:\n。这条映射发的是裸的换行字节,在 pi 里跟 Ctrl+J 无法区分,结果是 tmux 和 pi 都收不到真正的 shift+enter 事件。如果加它纯粹是为了较新版本的 Claude Code,可以删掉——除非你要在 tmux 里用 Claude Code,文档说那种场景下它仍然需要这条映射。pi 把 Ctrl+J 绑成了默认的换行别名,所以就算保留那条映射,Shift+Enter 在 tmux 里靠这个别名也还能用。

WezTerm 通常靠 xterm modifyOtherKeys 就能处理 Shift+Enter,想显式用 Kitty 协议就在 ~/.wezterm.lua 里设 config.enable_kitty_keyboard = true。macOS 上它默认把 Option+Enter 绑给了全屏,要拿来做 pi 的跟进消息排队,得覆盖成发送 \x1b[13;3u。Alacritty 是同样的故事,macOS 上 Option+Enter 可能变成普通 Enter,需要在 alacritty.toml 里加一条 chars = "\u001b[13;3u" 的绑定,改完要重启 Alacritty。

Windows Terminal 要在 settings.jsonactions 里加两条转发:

{
  "actions": [
    {
      "command": { "action": "sendInput", "input": "\u001b[13;2u" },
      "keys": "shift+enter"
    },
    {
      "command": { "action": "sendInput", "input": "\u001b[13;3u" },
      "keys": "alt+enter"
    }
  ]
}

第一条让 Shift+Enter 插入换行;第二条是因为 Windows Terminal 默认把 Alt+Enter 绑给了全屏,抢走了 pi 用来排队跟进消息的键。如果旧的全屏行为还在,把 Windows Terminal 完全关掉重开。

VS Code 集成终端从某个版本起默认启用了 Kitty 协议,Shift+Enter 开箱可用,具体分界在哪一版文档里写着,装之前对一眼;比那更老的版本要在自己的 keybindings.json 里加一条 workbench.action.terminal.sendSequence,发 \u001b[13;2u,条件写 terminalFocus。这个 keybindings.json 在 macOS、Linux、Windows 上各在各的用户配置目录,文档三条路径都列了;注意别和 pi 自己的 ~/.pi/agent/keybindings.json 弄混——两个文件同名,管的是完全不同的两层。

最后是明确不行的那一档:xfce4-terminal 和 terminator 的转义序列支持有限,Ctrl+EnterShift+Enter 这类修饰过的 Enter 跟普通 Enter 分不开,所以像把提交键改成 ctrl+enter 这种自定义键位在这些终端里根本不可能工作。IntelliJ IDEA 的内置终端同样如此,Shift+EnterEnter 无法区分。这两种情况下再怎么改 keybindings.json 都是无用功——问题在下面那层。

顺带一提 IME:某些终端需要可见的硬件光标才能给中日韩输入法定位候选窗。WSL 上的 WezTerm 就属于这种,如果候选框不跟着光标走,设 PI_HARDWARE_CURSOR=1 或把 showHardwareCursor 设成 true。pi 默认藏起硬件光标、自己画一个假光标,这是为了兼容性,代价就是这个 IME 场景。

五、边界与代价:这套设计放弃了什么

值得先说清楚它明确不管的事。

**没有 GUI 配置面板。**交互模式里的 /settings 只覆盖常用选项,完整的设置面靠手写 JSON。好处是配置可以进版本库、被脚本生成、在机器之间同步;代价是新手要在文档和文件之间来回翻,写错一个键名不会有人拦着你。

**主题必须全量定义。**51 个令牌一个都不能少(thinkingMax 除外),它没有提供”继承 dark 然后只改三个颜色”的机制,微调内置主题就得整份复制过来改。好处是主题文件自包含、没有隐式依赖;vars 能缓解维护成本但消不掉。

**终端能力它兜不住。**pi 在 Apple Terminal 上做了一个 macOS 本地回退,但那是特例且在 SSH 下失效。对 xfce4-terminal、terminator、IntelliJ 内置终端,文档给的是”换个终端”。这是个取舍:依赖 Kitty 协议换来修饰键的可靠识别,代价是把一部分终端排除在完整体验之外。选工具时这属于要提前知道的硬约束,和 Agent 框架怎么对比选型 里那些要提前问清楚的问题是一类。

项目级配置被信任闸卡着,几个层之间也不互通。.pi/themes/*.json 只在项目被信任后才加载,项目扩展和项目包安装同理,团队里推统一配置时每个人第一次进目录都得点一次确认——安全换来的摩擦。而改主题不会影响键位、改键位不会影响设置、/reload 能重载键位却不重载信任决定,这种正交性让排查变简单,但也意味着没有”一键导出我的全部配置”这种东西。

六、上手清单:容易踩的地方和怎么绕开

**改完设置没生效,先确认改的是哪一份。**项目目录里如果有 .pi/settings.json,它会覆盖全局同名项。人在项目里改全局文件、然后奇怪为什么没变,是最常见的一次。排查顺序:先看当前目录有没有 .pi/settings.json,再确认这个项目有没有被信任——没被信任的话项目配置压根没加载,你改哪份都没用。

**/trust 之后当前会话不变。**它只写 trust.json,当前会话不重新加载。信任完发现项目主题还是没出来,别急着怀疑配置写错了,先重启 pi。

脚本里跑 pi 时项目配置会被静默忽略。-p--mode json--mode rpc 都不弹信任提示,走 defaultProjectTrust,默认 ask 在非交互下等同于忽略。CI 里发现项目扩展没加载多半是这个。要么单次加 --approve,要么明确设全局值——但设成 always 意味着任何目录里的 .pi 都会被执行,这个决定得想清楚。

**别把 retry.provider.maxRetries 往上调。**直觉上”多重试几次更稳”,实际上会让 SDK 层抢先处理掉超额度错误,Agent 可能一直挂在那儿等配额恢复。要加重试就加 pi 自己那层的 retry.maxRetries

**externalEditor 忘了写 --wait。**用 VS Code 时 Ctrl+G 打开编辑器,pi 会立刻以为你编完了。这个错很难自己想明白,因为编辑器确实打开了,看起来一切正常。

**Ghostty 里那条老的 shift+enter=text:\n。**它是历史遗留,发的裸换行字节和 Ctrl+J 无法区分,装了 pi 之后反而让真正的 shift+enter 事件传不进来。如果不是为了在 tmux 里用 Claude Code,删掉它。

**Windows Terminal 和 macOS 上的 WezTerm 都抢了 Alt+Enter。**两边都默认拿它做全屏,于是 pi 的跟进消息排队键收不到。解法是在终端里把这个键重映射成发送对应的转义序列,而不是去改 pi 的键位——改 pi 的键位只是绕开问题,等你换台机器又得重来。

**主题少写一个令牌,或者在 VS Code 里颜色不对劲。**前者是 51 个令牌手写必漏,文件第一行就把 $schema 加上,让编辑器实时告诉你缺哪个;后者往往不是主题写错了,是 VS Code 会自动调整终端对比度,把 terminal.integrated.minimumContrastRatio 设成 1 就好。

**改了键位不知道要 /reload。**键位文件支持热生效,但不会自己监听——记得敲一下。主题文件反过来,编辑当前生效的自定义主题是自动重载的,两者行为不一样,别混。

收尾:一份三十分钟的自检

装完 pi 之后按这个顺序过一遍,大部分不顺手就消掉了。先确认终端这层通了:在你日常用的终端里试 Shift+Enter 能不能换行、Alt+Enter 能不能排队跟进消息,不通就去查终端配置那一档,别先动 pi。然后在全局 settings.json 里定下模型三件套和 externalEditor。接着把 app.session.newapp.session.treeapp.session.forkapp.session.resume 这四个默认空绑定挑你会用的补上。主题先用内置的撑着,等真觉得刺眼再复制一份改,配合热重载调色。最后,需要在项目里定制时只写要偏离的那几项,并且记住信任闸的存在。

想再往下挖,packages/coding-agent/docs/ 目录下还有 environment-variables.md(所有 PI_ 开头的开关)、packages.md(包机制怎么分发主题和技能)、tui.md(扩展怎么渲染自定义组件)。配置这件事的边界就到这儿,再往后就是扩展开发的范畴了。

涉及模型服务商时补一句:海外服务商官方对中国大陆存在区域限制,不支持直连;市面上有第三方中转,这里不做背书也不给具体渠道。各家的计费、限流与可用模型规则不同且会调整,以官方最新说明为准。想系统整理这块,可以看 多模型混搭怎么组织

本篇属于一个把开源编程 Agent 项目 pi 逐层拆开讲的系列,整体地图见开源编程 Agent pi 是什么;沿着这条线往下,还可以看 开源编程 Agent pi 的会话怎么存、怎么续、怎么翻回去看开源编程 Agent pi 的本地模型接入

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。