首页
/ caveman CLI 终端交互契约:有界内联 UX、管道安全输出与诚实度量标签的实现解析

caveman CLI 终端交互契约:有界内联 UX、管道安全输出与诚实度量标签的实现解析

2026-09-04 10:25:12作者:凤尚柏Louis

本文围绕 caveman 仓库中的 packages/cli/TERMINAL_UX.md 展开:它定义了 caveman 命令行工具的整体验示契约——有界内联终端 UI、机器输出管道安全、以及"节省量永远标注 inferred、永不换算成美元"的诚实度量规则。读完本文,你将理解 caveman CLI 如何在不引入任何运行时依赖的前提下实现交互式 learn 体验,其四个"人机四问"输出原则如何落到 --plain/--json/--md/--all 等具体开关上,并能对照 learn-tui.tsindex.ts 的源码验证每一条契约的实现方式。

一、契约的定位与总方向

TERMINAL_UX.md 开篇就划定了边界:该文件只定义演示(presentation)契约——命令行为、安全门控、机器输出格式和退出码仍由代码与产品规格拥有。这意味着它不是一份"功能需求文档",而是一份"输出纪律文档",约束的是终端里"长什么样",而不是"做什么"。

核心方向可以概括为一条反直觉的原则:使用有界的内联终端 UI(bounded inline terminal UI)。具体包含四个要素:

  • 短小的分节(short sections);
  • 状态面板(status panels);
  • 工作运行时的进度反馈(progress while work runs);
  • 需要用户选择时,至多一个键盘选择器(one keyboard picker)。

同时明确禁止三样东西:全屏应用(full-screen app)、备用屏幕缓冲区(alternate-screen buffer)、常驻仪表盘(persistent dashboard)。这与很多 TUI 工具走"接管整个终端"的路线相反——caveman 的立场是 CLI 输出随时可被复制、管道、截取。

交互库 Clack(@clack/prompts)只服务于交互式 learn。它在构建期被打包进一个约 25 KB 的懒加载 chunk,因此发布的 npm 包运行时依赖仍为零,普通命令启动时不会加载任何 UI 库。这一点可以在仓库中直接验证:

  • package.json"dependencies": {} 为空,而 @clack/prompts: 1.7.0 位于 devDependencies
  • bundle-tui.mjs 用 esbuild 把 src/learn-tui.ts 打包为 dist/learn-tui.jsbundle: trueminify: true,banner 注释明确写着 "@clack/prompts remains a build-time dependency only";
  • CLAUDE.md 的 Gotchas 一节进一步固化了这条纪律:"Published runtime dependencies stay zero. TUI libraries must be bundled, lazy-loaded, and measured; do not move them onto ordinary command startup."

二、人机四问:每条命令输出的固定顺序

文档要求每条面向人的命令按固定顺序回答四个问题:

  1. What happened?(发生了什么)
  2. What matters?(什么重要)
  3. What should I do next?(我接下来该做什么)
  4. Where can I inspect full detail?(去哪里看完整细节)

默认输出保持有界;细节从不消失,只是移动到 --all--verbose--json--md 或命名子命令背后。这个设计保证了两种读者各取所需:人读到的是摘要加下一步动作,脚本读到的是完整数据。

在源码中能看到这条原则的直接体现。renderLearnPlanindex.ts#L13257-L13321)在默认模式下只输出 Setup Score、来源行、top moves 摘要、protected 行和固定的 next: / details: 指引两行;而 --all 模式才追加 per-repo 块与 Advanced 页脚,最后始终输出 report: <路径> 一行——这正是第四个问题"去哪里看完整细节"的代码化回答。

三、Surface Map:每个命令面的呈现规格

文档的 "Surface map" 一节是整个契约的主体,逐条规定了 CLI 每个表面的呈现方式。以下按原貌完整继承并结合源码补充:

3.1 caveman / --help

帮助输出只展示四个"瓷面"(porcelain)任务,按 run / understand / connect / more 分组。从 CLAUDE.md 可以确认这四个瓷面动词是 runlearnloginstatus 加上 agent 快捷入口,且 porcelain 被硬性封顶在"四个动词 + agent 快捷 + 恰好两个命名空间";toolscloud 各自最多打印 15 个动词(当前分别为 15 和 14),第五个动词或第 16 个打印动词需要一次"退役决策"。

3.2 caveman <agent>:安静的启动横幅

caveman claude 这类 agent 快捷命令启动时只打印一个安静的启动横幅(quiet launch banner):显示模式、首个 blocking/off 状态、仅当新安装了 loadout 时才列出,然后把控制权交给 agent。明确禁止"重复的能力倾倒"(no repeated capability dump)。

3.3 首次运行(first run)

首次运行体验的规格在文档中写得很细:

  • 每台机器一次、仅 TTY 下触发;未展示过可经由未打印(unprinted)的 caveman welcome 重放;
  • 词标(wordmark)渐显动画;
  • 30 天回溯扫描运行期间的 spinner(该扫描对本地会话日志是只读的);
  • tokens sent / would-have-cut 的数字滚动(count-up)揭示,附 family 分解与 inferred/tokens-only 标签;
  • 遥测披露行;
  • 最后一个 [y/N] 的账户问题;
  • 任何失败或空历史都降级为一行暗色文字;被包裹的 agent 始终会启动
  • 非 TTY、CAVEMAN_PLAIN=1TERM=dumb 会静默跳过整个时刻。

CLAUDE.md 补充了实现细节:首次运行由 config 中的 firstRunAt 标记保证每机器一次;30 天回溯扫描走 caveman-proxy learn scan --retro,基线 pass 上限 20 秒、retro pass 上限 60 秒、子进程超时 90 秒含余量;揭示的 tokens 数字是"对扫描会话求和、绝不外推",sent 按每个 API 响应去重一次,且永不把 tokens 换算成美元

3.4 caveman learn:交互式学习体验

learn 是本契约下最复杂的表面,文档规定其完整序列:

  1. 动画扫描(animated scan);
  2. Setup Score 卡片;
  3. source/session 作用域行;
  4. 至多三张 top-move 卡片;
  5. 分组后的 recurring context;
  6. 受保护的 load-bearing 基线;
  7. 键盘动作菜单:implement / details / report / done

完整 sink ids 与检测器细节放在 --all 下;--plain 禁用交互 UI。由于当前 proxy 会从同一份 plan 写出可视化报告,前台只做一次完整分析

Portfolio 输出只提升一个"最佳下一步":它的具体 top sink 作为标题、fix 标签作为 kind、测量置信度保持可见。已记录的修复出现在 confirmed 区段,带应用日期、修复前后单位、修复后会话数与纵向结论(verdict)。TUI 中的 confirmed 计数提示指向 caveman learn --all,后者还会追加一个 per-repository 块,该块只报告会话数、dumbzone 百分比与中位上下文——不虚构每仓库分数

这些规格在源码里逐条可查:

  • buildLearnTuiModel 把 proxy 返回的 LearnPlan 转换为 TUI 视图模型:score 仅在存在 recurring_context 类 sink 时取 cave_score.score,否则为 null(此时 TUI 渲染 "LEARN RESULT" 而非 "SETUP SCORE" 卡片,见 learn-tui.ts#L102-L117);protected 行文案为 "included in score, never auto-fixed";findings 为 sink 总数。
  • learnScoreBar 实现 24 格宽度的分数条( 填充、 余量),分数先被钳制到 0–100 再取整,这对应了"分数不越界、不猜测"的输出纪律。
  • renderLearnTui 的动作菜单与文档一致:有 moves 才出现 implement(hint 为 "Claude Code or Codex · approval before edits"),findings > 0 才出现 detailsreportdone 恒在;选择 implement 后还会弹一个可选 focus 文本框,最终返回 { action, focus? } 供主流程分派。
  • renderLearnConfirmed 渲染 confirmed 区段:每个条目带 ✓/·/! 符号、sink_id、修复前后数值与单位、修复后会话数、verdict 与应用日期;注释专门强调"测量方式要跟着数字走——没有归因信息的 confirmed 行会显得比实际证据更强"。
  • renderLearnRepos 的 per-repo 行恰好是三个字段:${repo} · N sessions · dumbzone X% · median context ~Y,与文档"不发明每仓库分数"的约束一致。

3.5 caveman learn implement

文档规定:选择 Claude Code 或 Codex、安装缺失的 caveman-learn 指南,然后带着当前报告与可选的用户 focus 启动 agent;agent 在每次编辑前都要询问,load-bearing 发现永不被编辑

源码侧的实现链条(index.ts#L13673-L13763):

  • learnImplementPrompt 生成的提示词逐条复述安全规则:"Work through selected fixes one at a time. Never edit load_bearing findings."、"Show the proposed diff and before → after token count, ask before every edit, apply only approved changes, then verify the reduction and any recall path."、"Keep every local savings claim labeled inferred and never attach currency."——安全约束从演示层写进了交给 agent 的提示词本身;
  • ensureLearnAgentGuide 对 claude 写入 <cwd>/.claude/skills/caveman-learn/SKILL.md,对 codex 写入 ~/.codex/skills/caveman-learn/SKILL.md,内容来自仓库内置的 skills/caveman-learn/SKILL.md 编译产物,已存在则不覆盖;
  • chooseLearnAgent 在未指定 agent 且本地装了两个候选时,才走键盘选择器,且前提是 learnTuiTerminal() 为真——非 TTY 直接报 usage 并以退出码 2 结束。

3.6 caveman statustoolscloud 与错误面

  • caveman status:today、off 状态、account/config 状态、一个下一步动作;
  • caveman tools:本地命令按 think / remember / execute / inspect 分组,默认停在 15 条,内部进阶面走 caveman help tools --all
  • caveman cloud:已连接命令按 account / evidence / governance 分组,login 是明确的起始动作;
  • errors:一个问题、一个最可能的纠正、一个帮助指针,不打印堆栈

这四项与 CLAUDE.md 中的命令面描述互证:tools 上限 15 个打印动词、cloud 上限 15(当前 14),内部动词(如 shrink-hookpracticescheck)保持可调用但不打印。

四、输出契约(Output contracts):六条硬规则

文档 "Output contracts" 一节给出六条规则,是全文约束力最强的部分,可整理为:

规则 说明 源码印证
机器输出管道安全 JSON、Markdown、压缩字节、recipes 与被委托命令的输出不加任何装饰或 prompt learn--json/--md 分支直接 process.stdout.write(planRaw)index.ts#L13886-L13889
交互 UI 要求三个 TTY stdin、stdout、stderr 均须为 TTY;非 TTY 路径保持稳定的紧凑文本 learnTuiTerminal() 检查 interactive()process.stdout.isTTYTERM !== "dumb"CAVEMAN_PLAINindex.ts#L13765-L13770
降级开关 --plainCAVEMAN_PLAIN=1TERM=dumb 禁用动画与键盘输入;NO_COLOR 禁用颜色 learnTuiEnabled 同时排除 --plain/--json/--md/--all/--verbose 任一参数(index.ts#L13772-L13775);learn-tui.ts#L27-L32color = !NO_COLOR && stdout.isTTY
本地节省保持 inferred learn 中永不出现货币,绝不从每日数据外推月度 渲染文案固定带 "basis: inferred (local sessions, not billed spend)"(index.ts#L13279-L13291
速率与总量严格分开 tokens/day 是前向速率,tokens observed 是历史窗口总量;Learn 分别渲染,永不相加、永不把观测总量描述为速率 buildLearnTuiModel 中 scope 行固定为 "local setup · inferred · not billed spend · separate from org Cave Score"(index.ts#L13212
未知值保持缺席 不造合成零、不猜状态 learnMeasureValue 对 undefined/非有限数返回 "?" 而非 0index.ts#L13423-L13427);分数卡片 score === null 时整体改走 "LEARN RESULT" 分支

最后一条在 TUI 中还有对应呈现:learnScoreBodymodel.score === null 时不画分数条,只输出会话来源行与状态行——"未知值缺席"而不显示 0 分。

五、懒加载机制:交互式与非交互式的分叉点

learn 是演示契约最重要的实现现场,其分叉逻辑在 index.ts#L13829-L13899

  1. learnTuiEnabled(rest) 判定是否走 TUI:必须真终端 + 无机器模式参数;
  2. TUI 路径await import("./learn-tui.js") 动态导入 25 KB 的 Clack chunk,创建 spinner 并在 proxy 的 learn scan --write-report 输出上驱动 progress.update("Reading Claude Code and Codex sessions");扫描完成后以"report token/mtime 证明本次 proxy 是否真的写了报告"做一次防御性校验(旧版 proxy 会忽略未知 flag),必要时回退到 learn report --json;随后 renderLearnTui 按用户选择分派:implement → 复用 learnImplementdetailsrenderLearnPlan 的 verbose+all 全量文本,report → 打开本地报告文件;
  3. 非 TUI 路径proxyExecLearn 同步执行同样的扫描命令,仅当 interactive() 且非机器模式时向 stderr 写一行 "Learning from local sessions…" 提示——装饰走 stderr、数据走 stdout 的管道安全纪律。

两条路径共享同一个数据源:caveman-proxy learn scan。这正对应文档所述"当前 proxy 从同一份 plan 写出可视化报告,所以前台只执行一次完整分析"——TUI 不是另起炉灶的第二分析器,只是同一 plan 的另一种渲染。

关于子命令的机器可读面,learn 的分派(index.ts#L13779-L13824)也遵循契约:export/reconcile 注释明确写着"两者都是 inspect-before-you-act 的面,因此保持机器可读",直接透传 JSON;applied/simulate 同理。

测试侧,tests/learn-tui.runtime.mjstests/cohesive-ux.runtime.mjs 使用 node --test 运行时测试套件拉起构建产物验证这些路径,CLAUDE.md 亦要求"Piped/non-interactive paths remain plain; runtime tests assert them"——管道与非交互路径的"纯文本"性质是被测试断言的,而非口头约定。

六、度量契约(Measurement):遥测的披露与边界

文档最后一节规定了遥测契约:

  • 披露且可退出(disclosed opt-out)的 cli/v1 事件覆盖命令的曝光/结果/失败,加上无内容的本地 Engine 会话聚合
  • CI/非 TTY 下默认关闭,所有 kill switch 生效;implement 是白名单子命令;
  • 遥测永不包含:prompts、argv 值、报告内容、sink ids、路径、provider/model/account/request/session ID、哈希、美元金额;
  • 产品负责人把匿名遥测与本地 ClickHouse/运行时证据并排审阅;任何产品决策都不单独依据匿名遥测

"Unknown values stay absent" 的原则在这里同样适用:宁可数据缺席,不可合成填充。配合前文的 inferred 标签规则,可以概括出 caveman CLI 度量呈现的一条主线——数字必须携带它的证据级别tokens/day(前向速率)与 tokens observed(历史窗口)分栏渲染;本地节省标 inferred、永不标货币;per-repo 块只报可测的三量;confirmed 行必须带归因方法与置信度。

七、如何验证本文所述行为

在只读查看当前仓库之外,以下命令可在具备 Node ≥ 22.13 的本地环境复现契约行为(构建与运行方式见 CLAUDE.md 的 Conventions 一节,本地安装入口为仓库根的 scripts/install-local-cli.sh / scripts/install-local-cli.ps1):

# 构建 CLI(tsc + delegate/TUI bundle + shebang)
pnpm --filter @caveman-ai/cli build

# 交互 TUI(真终端下)
node packages/cli/dist/index.js learn

# 降级路径:纯文本 / 机器模式
node packages/cli/dist/index.js learn --plain
node packages/cli/dist/index.js learn --json | jq .   # 管道安全,无装饰
CAVEMAN_PLAIN=1 TERM=dumb node packages/cli/dist/index.js learn   # 静默跳过所有动画

# 完整细节面
node packages/cli/dist/index.js learn --all

预期行为:--json 输出不含 spinner、颜色或 prompt 前缀;非 TTY 下 learn 不弹出任何键盘菜单;--all 输出末尾出现 per-repo 与 Advanced 页脚——分别对应本文第三节与第四节的契约条目。

小结

TERMINAL_UX.md 用一页篇幅为整个 CLI 立下了三条可检验的纪律:输出有界(细节退居 --all/--json/--md 之后)、机器面纯净(管道输出零装饰、三 TTY 才允许交互)、度量诚实(inferred 标签、速率与总量分离、未知值缺席、本地节省永不货币化)。配合零运行时依赖 + 25 KB 懒加载 Clack chunk 的构建策略(package.jsonbundle-tui.mjs),这套契约保证了 caveman 在"给人看的终端"与"给脚本用的管道"两种身份间零成本切换——这也是其 src/learn-tui.tssrc/index.ts 中每一处渲染分支所共同遵守的底层约束。

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

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
987
504
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384