开源项目 OfficeCLI 的 11 个技能包:里面写了什么,和命令行工具怎么分工
本文基于 OfficeCLI 仓库 commit 459b1a4(2026-08-04)梳理,该项目仍在高频迭代,具体行为以仓库 https://github.com/iOfficeAI/OfficeCLI 最新代码与文档为准。
开源项目 OfficeCLI(GitHub 上 iOfficeAI/OfficeCLI 这个仓库)自带的 11 个技能包里,几乎没有”命令怎么写”,写的是”什么才算做完”。 命令怎么写属于另一套东西——同一个仓库里 152 份 JSON schema 撑起来的 help 系统。搞混这两者,你会得到一个既啰嗦又过时的技能包;分清了,技能包能一直薄下去,而工具那边随便怎么改都不用回头改文档。
先交代一下这是什么项目。OfficeCLI 是一个 Apache-2.0 许可的开源命令行工具,NOTICE 文件署 Copyright 2026 OfficeCLI,由 goworm 创建维护,仓库在 https://github.com/iOfficeAI/OfficeCLI 。它读写 .docx / .xlsx / .pptx 这三种 Office 文件格式,单个二进制,不需要机器上装办公软件——它和微软没有任何从属或授权关系,下文提到 Word / Excel / PowerPoint 时指的都是文件格式和打开它们的应用。
站内已经写过几篇技能机制的文章,分工不同:Superpowers 的技能文件契约 讲的是单个 SKILL.md 该长什么样,OpenWork 里技能、插件与 MCP 的区别 讲的是三种扩展位怎么选,三家技能机制横向对比 讲的是不同 harness 的加载模型差异。这篇只钉一个点:当技能包和一个真实的 CLI 工具捆在同一个二进制里发布时,两边各自该装什么。
一、11 个目录、10 个可加载名字,差的那一个是故意的
skills/ 下有 11 个目录,每个目录一份 SKILL.md:officecli、officecli-docx、officecli-pptx、officecli-xlsx、officecli-academic-paper、officecli-word-form、officecli-pitch-deck、officecli-financial-model、officecli-data-dashboard、morph-ppt、morph-ppt-3d。
但 src/officecli/Core/SkillInstaller.cs 里的 SkillMap 只有 10 条映射:pptx、word、excel、morph-ppt、morph-ppt-3d、pitch-deck、academic-paper、data-dashboard、financial-model、word-form。少的那个是 officecli 这份总纲。源码里用常量 UmbrellaFolder 单独持有它,注释写得很直白:故意不放进 SkillMap,好让 skills list 和 load_skill 只暴露子技能。
这个安排的含义是:总纲那份是”进门就该有”的东西,装机时铺到每个 agent 的技能目录里;10 个子技能是”按需才拉”的。officecli/SKILL.md 里的 Strategy 一节明确了触发时机——遇到融资演示、学术论文、财务模型、仪表盘、Morph 动效,先 load_skill 一次再动手。(Morph 是演示文稿里的”平滑切换”:相邻两页放同名同形的元素,播放时由软件自动补出位移与缩放的过渡,看上去像镜头在动而不是页面在翻。它对元素命名和摆位的要求比普通幻灯片高得多,所以单独占了两个技能目录。)
技能名到目录名的映射不是恒等的:word 指向 officecli-docx,excel 指向 officecli-xlsx。这层别名对使用者友好,对代码来说则是一个必须维护的对照表。同一个文件里还有 SkillTriggers,给 10 个名字各配了一句触发词(比如 excel → spreadsheets, financial models, dashboards)。
二、SKILL.md 里到底写了什么
拆开看,一份技能包的正文基本由四类内容组成,四类都不是命令手册。
第一类,交付标准。 officecli-xlsx/SKILL.md 有一节叫 Requirements for Outputs,规定每个交付的工作簿必须零公式错误——#REF!、#DIV/0!、#VALUE!、#NAME?、#N/A 一个都不许留;能由其他单元格算出来的数就必须是公式,把 =SUM(B2:B9) 写成硬编码的 5000 被它称为”破坏了工作簿保持鲜活的契约”。还有一条”视觉交付底线”:任何单元格出现 ### 都算没做完(列宽不够,而这个工具没有自动列宽);$fy$24、{var}、<TODO>、xxxx 这类构建期占位符渲染成数据也算没做完。
第二类,领域约定。 同一份 xlsx 技能包里有一段只对财务模型生效的五色规范:蓝色字 0000FF 是硬编码输入,黑色字是全部公式,绿色字 008000 是本工作簿内跨表引用,红色字 FF0000 是外部文件链接,黄色底 FFFF00 是待复核的关键假设。它管这叫”沟通契约,不是装饰”——审阅者应当只看颜色就知道一个单元格是什么。数字格式同理:年份当文本(2026 不能显示成 2,026),零显示成 -,负数用括号,估值倍数用 0.0x。
officecli-financial-model/SKILL.md 在此之上加了三区架构硬规则:Inputs → Calc → Outputs,塌成一层就不可审计,并附了一段 shell 检查,在 Calc 表上数出硬编码数字单元格,非零就直接 reject。
第三类,带坐标的版式配方。 officecli-pitch-deck/SKILL.md 是 11 份里最厚的一份(810 行)。它先给一张阶段判定表:Seed 页数 10–12、Series A 是 12–16、Series B 是 18–22、Series C 是 20–24、Bridge/SAFE 是 8–10,每一行还带该阶段的叙事权重、必备数据和常见红旗。再给 5 个赛道的叙事骨架(B2B SaaS、消费、深科技、平台型、生命科学),然后是 6 个版式(C.1 封面到 C.5b 四宫格),每个版式给的是精确到厘米的坐标表。
它连网格算术都写出来了。三卡横排,画布宽 33.87cm,左右各 1.5cm 边距、卡间距 0.76cm,于是 usable = 33.87 − 3 − 2·0.76 = 29.35,col = 9.78cm,三个 x 坐标是 1.5 / 12.04 / 22.58。C.3 那节还挂了一条折行警告:60pt 字号配 7cm 宽度时,$9.4M 这种同时带 $ 和小数点的写法在衬线粗体下会折成两行、毁掉整个数字卡片,安全形状是 $9M、$96B 这类 3 到 4 个字符的。
第四类,可以直接跑的 QA 门。 这是最容易被忽略的一类。pitch-deck 的 Gate 6 是一整段 shell:6.1 用 view text 加 grep 扫 TBD/lorem/placeholder;6.2 用 query 'shape:contains("TAM")' 配 jq 数命中数;6.4 检查融资页有没有 Use of Funds;6.5 用 grep 数团队页有没有 ex-/former/prior/previously 这类前司信号;6.6 从 query 'chart' --json 里挑 axisMin 为 0 的图表。
其中 Gate 2b 特别有意思:zsh 会把双引号里的 $35M 静默吞成空,页面上不留任何残渣,普通的占位符扫描抓不到。于是这一关反过来 grep 吞掉之后剩下的形状—— M ARR、Series B · M、runway · M。这已经不是”文档写法建议”了,是把一类已知失败模式固化成了检测脚本。
技能包自己也承认这些门存在的理由。pitch-deck 那节 Honest limit 写道:validate 能查 schema 错误,查不出融资错误;一份 y 轴从当前值 80% 起跳的曲棍球棒图、一个没有方法论来源的 $500B TAM(五千亿美元的市场规模宣称),都能干干净净地通过 validate。
三、技能包与工具的分界线
分工规则被写死在每份技能包顶部。officecli-xlsx 的 Help-First Rule 说:help 反映的是你实际装的那个版本,本技能包与 help 冲突时以 help 为准。pitch-deck 那份更短:help wins。总纲 officecli/SKILL.md 则规定,属性名、取值格式、命令语法拿不准时,永远去跑 help,别猜——一次 help 查询胜过一轮猜-失败-重试。
底下的 schema 是这么组织的:schemas/ 下 153 个受版本控制的文件,其中 152 份 JSON,按 schemas/help/{docx,pptx,xlsx}/<element>.json 分格式分元素摆放,同一层还有 schemas/help/_schema.json(一份 draft 2020-12 的元 schema,用来约束前面那些能力描述文件自己长什么样)和 schemas/help/_shared/ 里跨格式复用的共享片段。schemas/README.md 说明它有三个消费方:运行时 --help --json 输出、契约测试、发布期 wiki 生成。契约测试那条是关键——每一条 schema 声明都要对着真实 handler 实现验证,标了 enforcement: strict 的属性一旦漂移就挂 CI,标 report 的只记日志。README 还有一句:schema 在构建期嵌进二进制,运行时不依赖文件系统路径或网络。
技能包本身也走同一条路。src/officecli/officecli.csproj 第 47 行一条 EmbeddedResource Include="../../skills/**/*",把整个 skills 目录编译进二进制,LogicalName 里把反斜杠统一替换成正斜杠,好让 Windows 上算出来的资源名和 Linux 一致;第 48 行把 .glb 排除掉。所以 load_skill 读的从来不是磁盘上的文件,是嵌入资源。
| 组成部分 | 它负责什么 | 仓库位置 | 你什么时候会碰到它 |
|---|---|---|---|
| 元素能力 schema | 属性名、枚举值、写进去之后再读出来(readback)会长什么样,以及是否支持 add/set/get | schemas/help/(152 份 JSON) | 不确定某个属性叫什么时,跑 help <格式> <元素> |
| 技能包正文 | 什么算做完、领域约定、版式坐标、可执行 QA 门 | skills/<名字>/SKILL.md(11 份) | 动手建一份新文档之前 |
| 技能包附带资源 | 视觉风格库、辅助脚本、模板文件 | skills/morph-ppt/reference/ | 需要指定视觉风格时按相对路径取 |
| 名字与路由 | 技能名到目录名的映射、一句话触发词 | src/officecli/Core/SkillInstaller.cs 的 SkillMap / SkillTriggers | 不知道有哪些技能、或怀疑选错了 |
| 加载与安装 | 读取内容、剥掉安装段、追加资源清单、落盘到各 agent 目录 | 同上文件的 LoadSkillContent / InstallSkillFiles | 跑 load_skill 或 skills install |
| MCP 单参入口 | 把一整行命令原样透传给 CLI | src/officecli/McpServer.cs | 在 MCP 客户端里调用它 |
四、加载路径上的几个设计动作
officecli load_skill 不带名字打印目录,带名字打印那份 SKILL.md,加 --path <相对路径> 取一份附带文件。CLI 与 MCP 走的是同一套语义:Program.cs 里的早期分发和 McpServer.cs 的 HandleSkillCommand 是镜像实现,注释也点明了这一点。
LoadSkillContent 的返回值不是文件原文,而是 StripSetupSection(content) + BuildReferenceManifest(skillName),两个动作都有理由。
StripSetupSection 砍掉 ## Setup 段落。理由写在注释里:能调 load_skill 的人显然已经装好了这个工具,那段 curl 安装说明纯属噪音,白吃 agent 的上下文。而磁盘上和 GitHub 上的原文保留这一段,给人看。
BuildReferenceManifest 反过来是加东西:一份 SKILL.md 是入口,细节推给附带的参考文件,正文里那些 reference/… 指针如果不给清单,每一条都是死链。生成清单时它还做了折叠——路径段数在 2 以内的文件逐个列出,更深的树按二级目录归成一行并计数,但任意深度的 INDEX.md 都单独列出来,因为那是进入折叠树的入口。
morph-ppt 正好是需要这个的那一个。它一个目录就有 111 个文件:1 份 SKILL.md,reference/ 下 4 个顶层文件(decision-rules.md、pptx-design.md,以及 .py 和 .sh 两份辅助脚本),加上 reference/styles/ 里的 106 个文件——那里面是 51 个风格目录外加一份 INDEX.md。其他 10 个技能目录都只有 1 份 SKILL.md。
还有两处细节值得抄走。一是二进制文件:BinarySkillExtensions 列了 .pptx、.docx、.xlsx、.png、.jpg、.jpeg、.gif、.webp、.glb、.pdf、.zip、.ico,走文本通道会被读坏,所以 LoadSkillFile 直接拒绝,并在错误信息里告诉你改用 skills install 落到磁盘上。二是路径遏制:相对路径按 / 切段后,任何一段是 .. 或 . 都拒绝,把访问限死在该技能目录内。
装机侧的 Tools 数组有 12 行,覆盖 Claude Code、GitHub Copilot、Codex CLI、Cursor、Pi、Windsurf、MiniMax CLI、OpenCode、Hermes Agent、OpenClaw、NanoBot、ZeroClaw,各自的探测目录和技能目录都不一样(OpenCode 在 .config/opencode/skills,Pi 在 .pi/agent/skills,NanoBot 和 ZeroClaw 在 workspace/skills)。Pi 那行还带一段注释解释为什么选 ~/.pi 而不是它同时支持的 ~/.agents——后者已经被 Codex CLI 那行覆盖了。
最后是发现问题。BuildSkillTriggerSummary 把 10 个名字加触发词拼成一行,注入到 MCP 工具描述里,始终在场但很小;完整路由信息留在 load_skill 里按需拉。这个”推最小触发、拉完整细节”的做法背后有一条实测注释:信息性措辞(“完整指南见……”)连能力强的模型都会忽略,直接跳去 create/add 然后猜 schema;改成祈使句”在你对任何 Office 文件 create/add/set/remove 之前,先跑 load_skill <X>”才真的触发加载。这条经验对任何写工具描述的人都通用,可以和工具描述该怎么写对照看。
五、边界与代价
这套设计放弃了几样东西,也有明确不管的部分。
技能包不承担 schema 的准确性。 冲突时以 help 为准,意味着技能包里那些 --prop 例子是有保质期的。pitch-deck 自称每个 --prop 都对着 help 逐一 grep 核过,但也直说:后续版本改名或新增,信 help。你读到的技能包正文和你机器上装的二进制之间永远存在一个可能的缝。
技能包管不了内容对不对。 xlsx 那份的 Honest limit 说得很清楚:validate 查的是 schema 错误,不是设计错误,一个每个数字都错的工作簿照样能通过校验。文中列的公式核对清单——随机抽两三个公式手算一遍缓存值、每个数值列抽查一格、检查 SUM(B2:B12) 是不是漏了第 13 行——是人或 agent 必须自己走的一遍,工具替不了。
一次只能加载一个技能。 总纲的 Loading rule 规定:场景技能里已经包含了格式默认技能的规则,一份产物只加载一个,永远不要叠加;规则跨轮次持续生效,不必每轮重载;两份不同的产物就加载两次。这个约束换来了确定性,代价是你没法把”融资演示 + Morph 动效”两套规则合起来用——得自己选一个主轴。
它会直接改你磁盘上的真文件。 这一点必须说清楚。set、add、remove 改的是你传进去的那个路径本身,不是副本;要留底就自己先复制一份。常驻模式下落盘时机更绕:每条命令首次访问会自动起一个常驻进程(60 秒空闲超时),显式 open / close 是 12 分钟;officecli 自己的读永远看得到最新编辑,所以中途不用存,但只要下一个读文件的是别的程序(python-docx、openpyxl、办公软件、渲染器、上传流程),就必须先 save 或 close。想每次改动都立刻落盘,设 OFFICECLI_RESIDENT_FLUSH=each;想彻底关掉自动常驻,设 OFFICECLI_NO_AUTO_RESIDENT=1。
批量操作的失败语义值得单独记:batch 默认是原子的,每一项都会跑完并汇报,但只要有一项失败,整批回滚,磁盘上的文件与批处理前逐字节相同;想要”成功的先留着”,得显式加 --best-effort。回滚过的批处理会在 JSON 汇总里带上 atomicRolledBack 标记。
还有两处外部暴露面。 一是 watch 会起一个本地预览服务器(默认端口 26315),浏览器连上去能点选形状,CLI 反过来能读当前选中项——这条链路在你机器上开了一个可被本地访问的面。二是插图来源:pptx 的 picture 元素 schema 里写明,src 由 ImageSource 解析,接受文件路径、URL、data-URI 和裸字节。也就是说一条 add --type picture 可以按指令去外网拉图。技能包里给的例子都是本地文件名,但这个能力是开着的,指令从哪来就值得留意。至于安装本身,两份技能包给的都是 curl … | bash 和 irm … | iex,是否接受这种安装方式由你判断。
六、上手与避坑清单
- 别把总纲 SKILL.md 的技能表当成技能全集。
officecli/SKILL.md底部那三张 Specialized Skills 表列了 9 个名字,word-form在整份文件里一次都没出现,但它确实在SkillMap和SkillTriggers里(自己 grep 一遍就能复现)。为什么会踩:人习惯读文档正文的表。怎么避:拿officecli load_skill不带参数打印的目录当权威列表,那份是从SkillMap遍历生成的。 - 别照抄技能包里的属性名就开跑。 为什么会踩:例子写得太具体太可信,看着就像 API 文档。怎么避:先跑一次
officecli help <格式> <元素>,冲突以它为准——这是技能包自己定的规矩,不是我加的谨慎。 - 给任何含
$的文本值用单引号。 为什么会踩:双引号里 shell 会把$35M展开成空,页面上不留残渣,肉眼和占位符扫描都发现不了。怎么避:单引号;跨表公式里的!同理会被搅成\!,用单引号定界符的 heredoc 走batch,写完get回读确认那个!前面没有反斜杠。 - 路径里的方括号一律加引号。 为什么会踩:
/slide[1]会被 shell 当通配符展开。怎么避:'/slide[1]'或"/slide[1]"。另外记住两套下标并存:路径里的[N]是 1 起,--index是 0 起,而 Excel 加行加列时的--index又回到 1 起。 - 交接给别的程序之前显式 flush。 为什么会踩:officecli 自己读得到的东西,别人不一定读得到,中途不存感觉一切正常。怎么避:把
save/close当成交接动作而不是保存动作,只在”下一个读它的不是 officecli”这个边界上执行。 - 深目录技能别硬啃全文。 为什么会踩:
morph-ppt一次load_skill拉回来的是 SKILL.md 加一份折叠清单,看着像残缺。怎么避:那正是设计,按清单里的路径用--path单取需要的那一份;.pptx、.glb这类二进制资源取不出来,改用skills install落到磁盘。 - 先想清楚要不要它写你的原文件。 为什么会踩:这类工具容易被当成只读的分析器。怎么避:批量改动前复制一份原始文件;把
--best-effort当例外而不是默认,默认的整批回滚才是你想要的那个语义。
回到开头那个判断。这 11 个技能包真正的产物不是”教 agent 用 CLI”,而是把三类平时只存在于老手脑子里的东西落成了文件:交付底线、领域约定、以及一批固化成脚本的已知失败模式。命令语法那部分被彻底推给了 152 份 schema 和 help,配合契约测试防漂移——这才是技能包能一直保持”讲判断不讲语法”的原因。
想自己验一遍,建议按这个顺序读:先 skills/officecli/SKILL.md 看总纲怎么分流,再 skills/officecli-xlsx/SKILL.md 看交付标准长什么样,然后 skills/officecli-pitch-deck/SKILL.md 从第 690 行的 Delivery Gate 往下看可执行 QA,最后 src/officecli/Core/SkillInstaller.cs 看这一切是怎么被装配和分发的。这四份读完,你大概率会想回头改一遍自己项目里的技能文件。相关的方法论可以参考技能文件怎么写。
本篇属于一个把开源Office 文档读写套件 OfficeCLI逐层拆开讲的系列,整体地图见 OfficeCLI 是什么:给 AI Agent 用的开源文档读写套件;沿着这条线往下,还可以看 开源项目 OfficeCLI 的 watch 预览:让 Agent 改文档时你在浏览器里同步看到 和 开源项目 OfficeCLI 的两套 SDK:什么时候别直接调命令。