开源终端 Agent opencode 界面用熟:键位、主题与区块含义
本文基于 opencode 仓库 commit 7fe9938(2026-08-04)梳理,该项目仍在高频迭代,具体行为以仓库 https://github.com/anomalyco/opencode 最新代码与文档为准。
opencode 的终端界面不需要你背键位,它需要你先看懂屏幕上每一块在报告什么状态——看懂了,键位自然就记住了;没看懂,背下来的快捷键也只是在盲操。 这是把它用熟和用得磕磕绊绊之间最大的分水岭。
opencode 是一个 MIT 许可证(LICENSE,Copyright 2025 opencode)的开源项目,仓库地址是 https://github.com/anomalyco/opencode 。它跑在终端里,直接读写你的工作目录、执行 shell 命令、把代码内容发给模型服务商。所以「界面」在这里不是装饰问题,而是你判断它此刻在动什么、花了多少、有没有在等你批准的唯一窗口。
站内已经写过几篇相邻的终端 Agent 文章,分工是这样的:Hermes Agent 终端界面拆解 讲的是另一个常驻自托管 Agent 的终端上手路径,pi 的终端界面为什么不闪 专攻差分渲染这一个技术点,Claude Code 使用教程 是另一款商业工具的完整教程;这一篇只管 opencode 自己的界面语义、键位体系和主题机制,不做产品优劣排序。
一、先认屏幕:底部状态条是你的仪表盘
打开 opencode 后最容易被忽略的是最底下那一行。它的实现在 packages/tui/src/routes/session/footer.tsx,读一遍代码就知道它固定在报告哪几件事。
左边是当前工作目录。右边从左到右依次是:待处理权限数(有待批准的权限请求时才出现,用警告色和一个三角符号标出,并按数量写成 1 Permission 或 2 Permissions)、已登记的 LSP 数量、已连接的 MCP server 数量,最后跟一个 /status 的提示文字。两者的口径其实不一样:LSP 数是直接数当前登记了多少个,前面的小圆点只在数量大于零时变成成功色;MCP 数则是只统计状态为已连接的那些,而且只要有任何一个 server 处于失败状态,前面那个圆点就换成错误色。
这套信息的价值在于:当 opencode 表现得”不对劲”时,八成问题都能在这一行里先排掉。它改了文件却没走类型检查?看 LSP 数是不是 0。你配的 MCP 工具它一次都没调用?看 MCP 那一项的颜色。它半天不动?看有没有 Permission 计数在等你——权限对话框可能被别的视图挡住了。
另外还有一个细节:当它检测到尚未连上任何 provider 时,底栏会周期性地在提示区闪出 Get started /connect。这不是随机弹窗,是它在告诉你”我现在还没有可用的模型”。
二、侧栏:把”这轮对话到底烧了什么”摊开给你看
侧栏默认可以用 <leader>b 开合。它不是一整块写死的面板,而是由若干个内置插件往一个叫 sidebar_content 的槽位里注册视图拼出来的。你在 packages/tui/src/feature-plugins/sidebar/ 下能直接看到这些文件:context.tsx、files.tsx、todo.tsx、lsp.tsx、mcp.tsx、footer.tsx。
其中最该盯的是 Context 这一块。它的渲染逻辑很短,短到值得贴出来:
<text fg={theme().textMuted}>{state().tokens.toLocaleString()} tokens</text>
<text fg={theme().textMuted}>{state().percent ?? 0}% used</text>
<text fg={theme().textMuted}>{money.format(cost())} spent</text>
三行分别是:当前上下文占用的 token 总量、占该模型上下文容量的百分比、这个会话累计花费。往上翻它是怎么算出来的会更有意思——它取的是最近一条有输出的 assistant 消息,把 input、output、reasoning、cache 读、cache 写全部加起来当作 tokens,再除以该模型登记的上下文上限得到百分比。
这个算法决定了你该怎么读这个数字。它是快照不是累加:反映的是最近一次请求带进去的上下文有多满,而不是整个会话的历史总和。所以你压缩过一次上下文之后,这个百分比会掉下来,属于正常。而下面那行花费才是累加的会话成本。看惯了这两个数的关系,你就能自己判断什么时候该 /compact(<leader>c),什么时候该干脆开个新会话(<leader>n)。
三、键位体系:leader 键 + which-key,设计目标是让你不用背
opencode 的键位默认走 leader 键,默认 leader 是 ctrl+x。逻辑是先按 ctrl+x,松开,再按下一个键。这么设计的直接原因是终端里能用的组合键太少,纯 ctrl+X 单键极易和终端本身、和 tmux、和 shell 抢键。
默认表在两个地方能查到:文档 packages/web/src/content/docs/keybinds.mdx 里有一份完整的 tui.json 示例,代码里的权威定义在 packages/tui/src/config/keybind.ts。后者的写法是每个键都带一句说明:
sidebar_toggle: keybind("<leader>b", "Toggle sidebar"),
status_view: keybind("<leader>s", "View status"),
这句说明文字不是注释,它会被界面直接用掉。按下 leader 键之后停住不动,which-key 面板就会弹出来,列出此刻所有可用的后续键和它们的说明。实现在 packages/tui/src/feature-plugins/system/which-key.tsx,它支持 dock 和 overlay 两种布局,可以分组翻页、上下滚动,对应的开关键是 which_key_toggle(默认 ctrl+alt+k)和 which_key_layout_toggle。
于是记键位这件事就退化成记一小撮高频的:<leader>n 新会话、<leader>l 会话列表、<leader>m 模型列表、<leader>a agent 列表、<leader>t 主题列表、<leader>c 压缩、<leader>u 撤销、<leader>r 重做、<leader>e 外部编辑器、<leader>x 导出、<leader>s 状态、<leader>b 侧栏。剩下的全部交给 which-key 现查。
还有一条并行的入口:命令面板,默认 ctrl+p(command_list),实现在 packages/tui/src/component/command-palette.tsx。文档明确说了,一部分视图开关(比如是否在消息里显示你的用户名)只能从命令面板里搜出来,并且这类设置会持久化到下次启动。所以”找不到某个开关”的时候,先开命令面板搜关键词,比翻文档快。
/ 开头的斜杠命令是第三个入口,和键位是同一批动作的两种触发方式。/compact(别名 /summarize)、/undo、/redo、/sessions(别名 /resume、/continue)、/models、/export、/editor、/init、/share、/unshare、/thinking、/details、/connect、/help、/exit(别名 /quit、/q)都在文档里列全了。
四、主题:从换一个到自己写一份
主题切换的入口是 <leader>t(theme_list),对应的斜杠命令在命令表里注册为 themes;主题文档里把它写作 /theme,以 /help 实际列出的为准。内置主题的 JSON 文件放在 packages/tui/src/theme/assets/,当前目录下有 33 个文件,opencode.json、tokyonight.json、gruvbox.json、nord.json、catppuccin.json、kanagawa.json、matrix.json、one-dark.json 这些都在里面,默认用的是 opencode。
有一个特殊主题叫 system,行为和别的都不一样:它不写死颜色,而是根据你终端的背景色生成灰阶,用标准 ANSI 颜色(0-15)做语法高亮和 UI 元素,文字和背景色直接用 none 继承终端默认值。如果你的终端本来就有一套精心调过的配色,system 是唯一不会和它打架的选项。
想自己写一份,先要知道加载顺序。文档写得很清楚,后面的目录覆盖前面的:
- 内置主题(编译进二进制)
- 用户配置目录
~/.config/opencode/themes/*.json或$XDG_CONFIG_HOME/opencode/themes/*.json - 项目根目录
<project-root>/.opencode/themes/*.json - 当前工作目录
./.opencode/themes/*.json
同名主题以优先级高的为准。这个层级设计的实际用途是:你可以给某个特定项目单独配一套配色,跟同事共用的仓库里放一份,个人偏好放用户目录。
主题 JSON 的取值支持四种写法:十六进制色 "#ffffff"、ANSI 色号(0-255 的整数)、颜色引用(在可选的 defs 段里定义可复用色名再引用)、以及 {"dark": "...", "light": "..."} 这种明暗双变体。"none" 表示继承终端默认色或透明。色槽本身覆盖得相当细,除了 primary、text、background、border 这些基础项,还专门给 diff 渲染留了 diffAdded、diffRemoved、diffContext、diffHunkHeader、diffHighlightAdded、diffAddedLineNumberBg 一整组,Markdown 和语法高亮也各有一组。
要让这些颜色真的显示出来,终端得支持 truecolor(24 位色)。文档给的自查办法是 echo $COLORTERM,输出应当是 truecolor 或 24bit;不支持的话会回退到最接近的 256 色近似,主题看起来就会发灰发脏。
界面行为本身也可配。TUI 的配置文件是 tui.json(或 tui.jsonc),和配置服务端/运行时行为的 opencode.json 是两份不同的东西:
{
"$schema": "https://opencode.ai/tui.json",
"theme": "opencode",
"leader_timeout": 2000,
"keybinds": {
"leader": "ctrl+x",
"command_list": "ctrl+p"
},
"scroll_speed": 3,
"diff_style": "auto",
"mouse": true
}
keybinds 是和内置默认值合并的,你只需要写想改的那几条。diff_style 控制 diff 怎么排版,"auto" 会随终端宽度自适应,"stacked" 强制单列。mouse 关掉之后,终端原生的鼠标选中和滚动行为会被保留回来。要用非默认路径的配置文件,走 OPENCODE_TUI_CONFIG 环境变量。
五、界面各块速查
| 组成部分 | 它负责什么 | 仓库位置 | 你什么时候会碰到它 |
|---|---|---|---|
| 底部状态条 | 工作目录、待处理权限数、LSP 数、MCP 连接数与状态色、/status 提示 | packages/tui/src/routes/session/footer.tsx | 每一屏都在;排查”它怎么不动了”第一眼看这里 |
| 侧栏 Context 面板 | 最近一次请求的 token 合计、占上下文容量百分比、会话累计花费 | packages/tui/src/feature-plugins/sidebar/context.tsx | 决定该压缩还是该开新会话时 |
| 侧栏其余槽位 | 文件、待办、LSP、MCP 等分块视图,按插件注册进同一个槽位 | packages/tui/src/feature-plugins/sidebar/ | <leader>b 开合侧栏之后 |
| which-key 面板 | 按下 leader 后列出可用后续键及其说明,支持分组、翻页、两种布局 | packages/tui/src/feature-plugins/system/which-key.tsx | 忘了键位、或想知道当前上下文还能按什么 |
| 命令面板 | 搜索命令与视图开关,部分设置只在这里出现且会持久化 | packages/tui/src/component/command-palette.tsx | 找不到某个开关入口时,默认 ctrl+p |
| 键位默认表 | 每个动作的默认键与说明文字,which-key 直接复用这些说明 | packages/tui/src/config/keybind.ts | 想改键、或想确认某个键到底绑了什么 |
| 内置主题文件 | 33 份主题 JSON,可照着改成自己的 | packages/tui/src/theme/assets/ | 换主题、或仿写自定义主题时 |
| 各类对话框 | 模型、agent、会话列表、主题列表、MCP、技能、状态等独立弹窗 | packages/tui/src/component/ | 按下对应 leader 组合键时 |
六、边界与代价:这套设计明确不管什么
它不替你保证键位不冲突。 默认表里就存在同一组合键出现在多处的情况:<leader>h 同时是消息区的 messages_toggle_conceal 和主界面的 tips_toggle;ctrl+d 同时出现在 app_exit、session_delete、stash_delete、input_delete 里;ctrl+a、ctrl+f 也各自在输入区和对话框里有不同含义。这些不是 bug,而是按上下文分层生效的设计,但代价就是你必须靠 which-key 确认”此刻这个键是什么意思”,不能凭肌肉记忆跨上下文乱按。
文档表和代码表会漂移。 keybinds.mdx 里那份 tui.json 是一份文档快照,而 packages/tui/src/config/keybind.ts 是运行时真正的默认值来源。我把两份对读时发现,代码里定义的 session_quick_switch_1 到 session_quick_switch_9(<leader>1 到 <leader>9)和 session_queued_prompts 并没有出现在文档那份示例里。所以要确认某个动作的默认键,以 which-key 面板和 /help 显示的为准,别拿文档当唯一依据。
它不做 GUI 那套所见即所得。 终端渲染意味着颜色受终端能力限制、鼠标交互能力有限、宽度变化会影响 diff 排版方式。你把 mouse 打开,就拿不回终端原生的文本选中;关掉,就得用键盘滚动。这是一个必须二选一的取舍,不存在两全。
主题只管显示,不管语义。 主题文件能改的是颜色槽位,改不了信息布局,也改不了哪一块显示什么。侧栏显示哪些内容是由注册进槽位的那些插件决定的,不在主题的职责范围内。
撤销依赖 Git,不是它自己的快照系统。 /undo 和 /redo 的文件回滚是用 Git 实现的,文档里明确写了你的项目必须是一个 Git 仓库。不是 Git 仓库的目录里,你会失去这条安全网。
七、上手与避坑清单
别在非 Git 目录里放手让它改文件。 会踩是因为 /undo 看起来像编辑器的撤销栈,你会下意识以为随时能退回去。实际它靠 Git 管理文件变更,非 Git 目录里这层保护不存在,改错了只能手动收拾。开工前先 git init 并保证工作区干净,这样每一轮改动你都能用 git diff 复核。
别把权限提示当噪音一路点过去。 会踩是因为底栏的 Permission 计数很小,而连续批准的手感很顺。但这类工具是真的在你机器上执行 shell、真的在改你的文件——消息前面加 ! 就是直接跑一条 shell 命令,输出会作为工具结果进对话。权限放太松的后果不是理论风险:误删、误改、把不该动的分支动了,都发生在”顺手批准”里。权限模型怎么收紧,见 agent-quanxian-taida。
别在含密钥的目录里随手用 @ 引用。 会踩是因为 @ 是模糊文件搜索,输几个字符就能命中,太顺手了;而文档写得很明白,被引用文件的内容会自动加进对话。这等于把这个文件发给了模型服务商。.env、私钥、客户数据文件都可能被模糊匹配捞出来。习惯性做法是引用前先看清补全出来的完整路径,敏感文件走人工摘录而不是整文件引用。外泄面的系统性分析见 ai-shuju-anquan-fengxian。
别在配 EDITOR 时漏掉阻塞参数。 会踩是因为 /editor 和 /export 都依赖 EDITOR 环境变量,而 GUI 编辑器默认是启动后立刻返回的。文档专门提示了这一点:VS Code 这类编辑器要写成 code --wait,否则编辑器刚打开进程就返回了,你还没写完它就认为你写完了。终端编辑器(vim、nano、nvim)没这个问题。
别指望改完 tui.json 就万事大吉,先确认 leader 超时。 会踩是因为 leader_timeout 默认 2000 毫秒,如果你习惯按完 leader 停顿思考,超时之后后续键就会被当成普通输入打进输入框。要么把这个值调大,要么练成连按。反过来,如果你觉得 which-key 面板弹得太慢,调小它。
别在 Windows 上照搬 POSIX 的假设。 会踩是因为文档给的默认表是通用的,但 Windows 上有两条不一样:input_undo 在未显式配置时默认变成 ctrl+z,ctrl+-,super+z,多绑了 ctrl+z;terminal_suspend 被强制为 none。原因是原生 Windows 终端不支持 POSIX 挂起。另外 shift+enter 在部分终端里不会带修饰键发送,Windows Terminal 需要在 settings.json 里手动加一条 sendInput 动作把它映射成转义序列,文档里给了完整的 JSON 片段。
别忽略 truecolor 自查。 会踩是因为主题不显示颜色时,人的第一反应是”这个主题设计得难看”,而不是”我的终端不支持 24 位色”。先跑 echo $COLORTERM 确认,再评价配色。
把这几件事做完,你基本就把 opencode 的界面吃透了:底栏能一眼看出连接与权限状态,侧栏能读出上下文压力和成本,leader 加 which-key 让键位从”背”变成”查”,主题按四级目录覆盖规则各管各的项目。
给自己留一份三行自检:第一,当前目录是不是 Git 仓库,工作区干净吗;第二,底栏的 LSP 和 MCP 数量符合你的预期吗;第三,侧栏 Context 的百分比是不是已经高到该压缩了。
接下来该读哪个文件?如果你要深度定制键位,直接读 packages/tui/src/config/keybind.ts,它比文档更新更及时,而且每一条都自带说明文字。如果你要写主题,先把 packages/tui/src/theme/assets/opencode.json 和文档里那份 Nord 示例对着看一遍,色槽名称一一对上再动手。如果你想搞清楚侧栏还能塞什么,packages/tui/src/feature-plugins/sidebar/ 下六个文件加起来不长,读完你会发现那些分块其实都是往同一个槽位注册的插件——这也意味着它是可以扩展的。但扩展这条路要连着风险一起看:能往界面槽位里注册视图的插件,和你接进来的那些 MCP server 一样,都是运行在你本机、跟着这个 Agent 一起加载的第三方代码。它们能读到什么、能顺着 Agent 的权限做什么,取决于你给的边界而不是它们的自觉。装之前先看清来源和它到底要哪些权限,别在放着密钥和客户数据的目录里随手试一个来路不明的插件。
本篇属于一个把开源AI 编程 Agent 项目 opencode逐层拆开讲的系列,整体地图见 opencode 是什么:终端里的开源 AI 编程 Agent 全景地图;沿着这条线往下,还可以看 终端编码 Agent opencode 命令行:交互模式与脚本化执行 和 开源终端 Agent 项目 opencode 的多 agent 怎么配。