violetdream/dsh-officecli

dsh-officecli

dsh插件-结合officecli命令实现在Deepseek Harness中操作office文档

Uncategorizeddsh-plugin-marketdsh-plugins

dsh-officecli

DeepSeek-Harness 插件:让 Agent 用自然语言创建、编辑 Office 文档(docx / xlsx / pptx),并把结果实时回显到 DSH Web 侧边栏。

底层调用本地 OfficeCLI 命令行;在此之上插件自建了一套 PPT 设计层——坐标、字号、配色、网格全部由插件计算,模型只填内容,从而把「AI 生成的 PPT 排版崩坏」这个老大难问题关进笼子里。


目录


1. 项目定位

OfficeCLI 是一个「没有布局引擎」的命令行工具——官方原话是 "Positioning is explicit — no layout engine, you own the grid math"。这句话的直接后果是:如果让模型直接调用底层 add shape 并自己填坐标,它几乎必然摆歪。

本插件因此在中间插了一层:

模型(只写内容)  →  插件设计层(算坐标/字号/配色)  →  officecli(执行)  →  pptx

模型看到的接口是一个 DeckSpec JSON:{"theme":"tech-cyan","slides":[{"layout":"cover","title":"..."}]}。它不需要知道 1pt 等于多少、卡片该放第几栏。设计层负责把这份意图编译成上百条带单位的 add shape 命令,再一次性 batch 原子提交。

对 docx / xlsx,插件提供的是贴近 OfficeCLI 原语的底层工具(路径寻址 + 属性读写),因为这两种文档的结构化编辑不需要布局数学。


2. 核心特性

特性说明
16 个 AI 工具12 个底层原语工具 + 4 个高层 PPT 工具(含 office_deck_lint),覆盖文档全生命周期
PPT 设计层960×540pt 画布、12 栏网格、8pt 基线、8 套主题、19 种版式模板、12 套专业模板;坐标与字号由插件计算
原生元素层除 shape 外接入 officecli 的 chart(18 种图表)/ table / picture / diagram(mermaid)/ connector / animation / notes 七类原生元素,图表表格不再是手拼形状
设计门禁字数上下限、叙事角色预算、非对称占比、数据必须落点;office_deck_lint 在生成前后做结构化体检
模板库deck.template 一键套用专业感外衣(咨询简报/产品发布/学术答辩/极简/政务报告/年度报告…),含内容页装饰、页码格式与默认转场;支持 templates.json 外置自定义与企业 VI
样式协议deck.style 注入演示文稿元数据、默认转场、默认背景、"{n} / {total}" 页码格式;每页可单独覆盖背景/转场/隐藏
--prop 富注入形状级支持渐变/图案/不透明度/线宽线型/箭头/字距/高亮/大小写/列表/链接等 20+ 项 OfficeCLI 属性面
字数红线逐字段的字数上限随设计指南下发给模型,从源头抑制溢出
视觉自检闭环office_screenshot 渲染 PNG 并把图片回传到对话,模型能真正看到自己的产出并修正
主题继承office_slide_add 追加页面时自动还原原文件配色,不会突然换肤
实时预览侧边栏 iframe 嵌入 OfficeCLI watch 页面,Agent 编辑后内容自动刷新(不重载)
边改边看挂钩 DSH 工具事件(tools/execute + tools/result),新生成/编辑的文件自动打开预览、面板显示 Agent 忙碌态与页数徽标
宿主视觉一致预览面板与侧栏按钮全部使用 DSH 主题变量(--dsw-*)与 dsh-client-ui-primitives 图标/状态点,随宿主浅深色主题自动适配
双通道 SSE插件自有通道传元事件(文件增删改 + 工具状态 + watch 状态),OfficeCLI watch 通道传内容刷新
会话隔离每个对话会话独立工作区;生成的文件直接落进 DSH 会话工作区(exec.agent.session.cwd),无 cwd 时回退 workspaceDir/系统临时目录
安全执行spawn 参数数组执行,无 shell 拼接;文件名白名单校验防路径逃逸
可选 HTTP无 HTTP 的 profile(如 headless 一次性任务)下工具照常工作,只是没有侧边栏

3. 前置要求

依赖版本/说明
OfficeCLI需在 PATH 中,或在插件配置里指定绝对路径。本机:C:\Users\刘仙伟\AppData\Local\OfficeCLI\officecli.exe
DeepSeek-Harness0.2.1-rc.2 ~ 0.2.x(package.json 的 engines.dsh 声明,插件侧 peer 依赖同步钉在 ^0.2.1-rc.2)
Node.jsv18+(用到 matchAll、顶层 await 等)
PowerPoint(可选)仅当你想用 native 渲染后端或校验产物可打开性时需要;COM 自动化可用即可

验证 OfficeCLI:

officecli --version

兼容性(0.3.0 起):package.json 用 engines.dsh 单方面声明可运行的宿主版本区间 >=0.2.1-rc.2 <0.3.0-0,DSH 装插件前会按这条区间校验(engines.dsh 存在时 peer 依赖不参与判定)。 0.2.x 相对 0.1.0-rc.8 的破坏性变化已在 0.3.0 里吸收:ctx.slots 改由 dsh-client-ui-renderer 增补、ctx.sessions 改由 dsh-api-session-controller 增补、图标从 Icon*16 改名为 Icon*Regular、JsonValue 移到 dsh-util-values、dsh-client-runtime 包已不存在。


4. 安装与构建

cd D:\workspace\deepseek-harness\dsh-officecli

# 安装依赖(--ignore-workspace 避免被父仓库 workspace 吸收)
pnpm install --ignore-workspace

# Windows 上若 pnpm 建符号链接失败(node_modules/@deepseek-ai/* 是空的、
# tsc 报「Cannot find module」),改用实体目录布局:
pnpm run install:deps     # = pnpm install --config.node-linker=hoisted

# 构建:宿主半 tsc + 客户端半 tsdown
pnpm build

单独构建某一半:

pnpm build:host      # 仅 tsc → lib/*.js + lib/types/*.d.ts
pnpm build:client    # 仅 tsdown → lib/client.js

⚠️ 改完 src/ 必须重新 pnpm build 并重启 DSH。profile 通过 link: 指向本目录,但 imports 走 package.json 的 exports["."] → ./lib/index.js,运行时加载的是编译产物而非源码。HMR 在 base bundle 的 cordis.patch.yml 里默认 disabled: true。


5. 挂载到 DSH Profile

dsh web 是硬编码别名(等价于 --profile web),子命令不接受 --profile 选项。 若启动报端口占用:netstat -ano | grep :3080 找到 PID 后 taskkill /F /PID <pid>。

挂载需要三步,缺一不可:

5.1 安装插件包到 profile

在 DSH 仓库根目录执行(dsh plugin add 只负责安装,不会自动写入配置):

cd D:\workspace\deepseek-harness
node --import tsx/esm apps/cli/src/bin.ts plugin --profile web add "D:\workspace\deepseek-harness\dsh-officecli"

安装结果为软链到 $DSH_HOME/profiles/web/node_modules/dsh-officecli(本机 $DSH_HOME = C:\Users\刘仙伟\.dsh)。

5.2 在 profile 的 patch 层插入插件行

编辑 $DSH_HOME/profiles/web/cordis.patch.yml(不要改 cordis.yml,它是空的组合入口):

- insert:
    - id: dsh-officecli
      name: dsh-officecli
      config:
        officecliPath: officecli
        workspaceDir: ""
        watchPort: 0
        commandTimeoutMs: 30000
        batchTimeoutMs: 60000

5.3 启动 / 重启 DSH

cd D:\workspace\deepseek-harness
pnpm dsh web

pnpm dsh → 根 package.json 的 script node --import tsx/esm apps/cli/src/bin.ts,尾参透传,因此与上面的原生命令等价。


6. 验证与排错

6.1 验证清单

# ① 宿主半:返回 {"files":[...]} 说明 HTTP 路由已注册
curl "http://127.0.0.1:3080/api/officecli/files?session=probe"

# ② 客户端半:boot 页里出现 dsh-officecli/client.js 说明 bundle 进了启动图
curl "http://127.0.0.1:3080/" | grep -c "dsh-officecli/client.js"

# ③ 服务树:确认 dsh-officecli 与 attachment-local 同级挂载
node --import tsx/esm apps/cli/src/bin.ts --profile web --dump-config

6.2 判定插件路由是否在册

这一招在排查预览 404 时极其有用:

curl -s -D - -o /dev/null "http://127.0.0.1:3080/api/officecli/zzz"
响应含义
content-type: application/json,body {"error":"未知路由..."}✅ 插件路由在册,只是这个路径不存在
content-type: text/plain❌ 落到了 SPA fallback —— 插件路由根本没注册

6.3 常见故障

现象原因与处理
/plugins/dsh-officecli/client.js 404,但 API 正常package.json 的 exports 未暴露 "./package.json";client-modules 用 require.resolve('<pkg>/package.json') 解析包元信息,被 exports 门拦截后该行静默不进启动图
预览面板里点文件报 HTTP 404见 15.4 —— watch 页面的根相对 URL 漏改写,请求打到 DSH 自身 origin
侧边栏没有 Office 按钮客户端 bundle 未进启动图,或 slots / sessions 服务不可用
启动即报端口占用已有 DSH 实例占用 3080
改动 patch 后无变化需要重启 DSH,配置在启动时组合
改了 src/ 但行为没变忘了 pnpm build,运行时加载的是 lib/
组件报 "Invalid hook call"client.build.mjs 的 config: false 被去掉了,tsdown 合并了父仓库配置,react 被整包内联

7. 使用方式

7.1 侧边栏

  1. 点击 DSH Web UI 侧栏底部的「📄 Office 预览」按钮(宽/窄两种形态自适应)
  2. 面板上半部分是当前会话的文档列表(含类型、大小、修改时间)
  3. 点击任一文件,下半部分 iframe 加载 OfficeCLI watch 页面
  4. Agent 编辑文档后,列表项会高亮闪烁,预览自动刷新(免手动刷新)

7.1.1 跟随模式(边改边看)

工具栏右侧的胶囊按钮在 跟随中 / 已锁定 之间切换(选择按会话记在 localStorage):

状态行为
跟随中(默认)Agent 每改一次文件,预览就自动跳到那个文件并重载 —— 对话里改一页,右边马上看到
已锁定锁定当前预览的文件;Agent 改别的文件时列表照常高亮,但不抢用户的屏

刷新的可靠性来自三步:

  1. 工具写盘后 notify() 广播 file-updated,同时 WatchManager.applyUpdate() 介入;
  2. 正在看的就是被改的文件 → 向 watch 服务器 POST /api/switch 指向同一个文件。 officecli watch 自述「external edits are not detected」,而插件是独立进程改盘写回的, 等它自己发现并不可靠;实测 switch 同文件会重新打开文档(status.version 归零并广播 update),等于一次确定的刷新;
  3. 客户端收到 file-updated / watch-switched 后 bump iframe 的 key 强制重建, 双保险,不依赖 watch 自己的 SSE 是否触发。

7.2 对话调用

底层原语(docx / xlsx 为主):

创建一个 report.docx,写两段自我介绍。
在 report.docx 的第二段后插入一个表格,包含姓名、年龄、职业三列。
读取 report.docx 的全部内容。
列出当前会话中的所有 Office 文档。

高层 PPT 生成(推荐流程,先读指南再生成,最后截图自检):

请先用 office_design_guide 查看设计规范,然后用 office_deck_create 生成一份 7 页的
中文 PPT,主题为《2026 年 AI 办公趋势洞察》,文件名 ai趋势洞察.pptx,主题用 tech-cyan。
生成后用 office_screenshot 逐页截图自查,如发现文字溢出或对比度问题就用 office_batch
修正,最多 3 轮。

一个最小 DeckSpec:

{
  "theme": "business-blue",
  "footer": "内部资料",
  "slides": [
    { "layout": "cover", "title": "年度技术规划", "subtitle": "2026 · 平台架构组", "eyebrow": "2026 年度规划" },
    { "layout": "kpi", "title": "核心指标", "metrics": [
      { "value": "3.2x", "label": "吞吐提升" },
      { "value": "92%", "label": "自动化率" }
    ]},
    { "layout": "bullets", "title": "三个重点", "items": [
      { "title": "统一网关", "desc": "收敛 17 个入口到单一网关层" },
      { "title": "可观测", "desc": "链路追踪覆盖核心链路" }
    ]},
    { "layout": "ending" }
  ]
}

用户的口语要求可以直接落进参数,不必先让模型查表:

"生成一份科技蓝风格的 PPT"        → deck.theme = "科技蓝风格"(解析为 tech-cyan)
"用我们公司的主色 #0F5EA6"        → deck.theme = "#0F5EA6"(派生整套配色)
"字能不能再大点"                  → deck.style = {"typography":{"scale":1.12}}
"标题换成微软雅黑"                → deck.style = {"fonts":{"title":"微软雅黑"}}
"背景深色一点"                    → deck.style = {"background":"#0F172A"}
"按公司的汇报模板来"              → deck.template = "corp-vi"(用户自定义模板)

8. 配置参考

在 $DSH_HOME/profiles/<profile>/cordis.patch.yml 的插件行 config 中覆盖,改动后需重启 DSH:

- insert:
    - id: dsh-officecli
      name: dsh-officecli
      config:
        officecliPath: officecli     # 命令名,或绝对路径
        workspaceDir: ""             # 兜底目录:会话无 cwd 时用;有会话 cwd 时自动写进 DSH 工作区
        watchPort: 0                 # 0 = OS 自动分配
        commandTimeoutMs: 30000      # 单条命令超时
        batchTimeoutMs: 60000        # batch 命令超时
字段类型默认说明
officecliPathstringofficecli可执行文件。找不到时报错会提示本机常见安装位置
workspaceDirstring""兜底工作区根目录。目录优先级:DSH 会话 cwd(exec.agent.session.cwd,生成的文件直接落进用户工作区)→ 本字段(填了必须是绝对路径,否则相对路径会让 officecli 在自身 cwd 下二次解析,拼出 <sid>/<sid>/ 的重复层级)→ 系统临时目录
watchPortnumber0watch 服务器端口,0 表示由 OS 分配(推荐)
commandTimeoutMsnumber30000单条 officecli 命令超时(毫秒)
batchTimeoutMsnumber60000batch 命令超时;office_deck_create 内部另用 120s

9. 工具清单

9.1 底层原语工具(12 个)

贴近 OfficeCLI 原语,docx / xlsx 的主力,pptx 的细粒度修也成为它服务。

工具参数说明
office_createfilename, type (docx/xlsx/pptx)创建新文档,返回根路径
office_list—列出当前会话所有文档(名/类型/大小/时间)
office_viewfilename, mode, page?, range?按模式读内容:text / annotated / outline / stats / issues
office_getfilename, path?, depth?按路径读结构化 JSON(如 /body/p[3]、/Sheet1/A1)
office_queryfilename, selector选择器查询,比 office_get 更精确定位
office_setfilename, path, props修改指定路径元素属性
office_addfilename, parent, type, props, after?, before?插入子元素
office_removefilename, path删除元素
office_movefilename, path, to移动元素
office_batchfilename, commands原子批量执行(任一失败全部回滚),命令经 stdin 以 JSON 传入
office_dumpfilename, path?导出完整 JSON 树
office_screenshotfilename, page?渲染某页为 PNG,并把图片回传进对话

office_* 工具执行成功后都会广播 file-updated 元事件,侧边栏据此高亮闪烁。

9.2 高层 PPT 工具(4 个)

排版数学由插件承担,模型只填内容。

工具参数说明
office_design_guidesection?返回模板清单、主题清单、可用元素能力、叙事与密度门禁、字数红线、字号阶梯、DeckSpec 契约、视觉自检清单。section 可取 templates / themes / style / custom / elements / story / limits / scale / spec / checklist
office_deck_createfilename, deck, overwrite?一步生成整份 .pptx。deck 为 DeckSpec 对象(也容忍 JSON 字符串),支持 template / style / 单页样式覆盖 / 演讲者备注 / 入场动画。生成前自动跑结构体检,错误级问题直接拦下
office_deck_lintdeck只体检不生成:密度下限、版式多样性、非对称占比、数据落点、配图存在性。返回 error / warning 两级
office_slide_addfilename, slides, at?, theme?往已有 PPT 追加页面,默认自动沿用原文件配色

10. PPT 设计层

这是本项目最有分量的部分,位于 src/pptx/。

10.1 画布与网格

常量值说明
画布960 × 540 pt与 officecli create 产出的 12192000×6858000 EMU 完全一致(16:9)
页边距32 pt左右各 32,内容区宽 896 pt
栏数12栏宽 60 pt、槽宽 16 pt:12×60 + 11×16 = 896 ✓
基线8 pt所有 y / h 对齐到 8 的倍数
母版 A 区y 0–90标题块
母版 B 区y 90–495内容区(实际可用 110–478,上下各留 20 呼吸位)
母版 C 区y 495–540页脚条

单位统一为 pt。officecli 的 EmuConverter 原生接受 xxpt 写法,所以布局数学算出的值可以直接下发,无需换算。

10.2 字号阶梯

用途字号
封面主标题54 pt
章节标题44 pt
数字锚点(KPI 巨型数字)64 pt
页面标题28 pt
卡片标题18 pt
正文16 pt
引文22 pt(中文放大到 22 才有分量)
脚注 / 页码13 pt

10.3 主题

8 套内置主题,字体一律取 Windows 必装项(避免 PPT 打开后回退成宋体):

id名称深色底适用场景
business-blue商务蓝否通用汇报、SaaS、金融(默认)
academic-crimson学术深红否论文答辩、人文、文化
tech-cyan科技青是AI、芯片、数据
warm-orange暖橙否教育、消费、生活
gov-red政务红否党政、法律、正式公文
minimal-gray极简灰否设计提案、策略思考
nature-green自然绿否ESG、农业、健康
luxury-black奢华黑金是年度报告、高端发布

每套主题由 10 个色令牌(bg / primary / secondary / accent / text / muted / accent5 / accent6 / hyperlink / heroGradient)+ 2 组字体(标题/正文,各分 latin 与 eastAsia)构成。用色面积有约束:主色 ≤60%、辅色 ≤30%、强调色 ≤10%(hero 页可到 20%)。

主题会通过 themeToProps() 编译成 set / --prop theme.color.* --prop theme.font.* 落到 PPT 的 theme part 上。

口语也能指定配色

deck.theme 不只是 id,resolveTheme() 按四步递进解析:

  1. 精确 id —— tech-cyan(大小写/分隔符容错,"Business Blue" 也能中);
  2. 别名 / 关键词 —— "科技蓝风格"、"政务红"、"深色科技"、"黑金"(最长子串优先, 所以"科技蓝"命中 tech-cyan 而不是 business-blue 的单字"蓝");
  3. 十六进制主色 —— "#0F5EA6" 会当场按色轮派生整套配色(辅色 +32°、强调色接近补色、 底色按明暗取极值),编号 custom-<hex>;
  4. 都识别不了才回落到默认主题。

所以用户在对话里说"要那种科技蓝的感觉",模型把原话填进 deck.theme 就能真的生效,而不是 像以前那样静默退回商务蓝。office_design_guide 的 themes 节会把这张对照表下发给模型。

10.4 版式模板(19 种)

前 15 种是纯形状版式;后 4 种是元素型版式,会下发 officecli 的原生 chart / picture / diagram 元素(见 10.4.1)。

layout字段契约
covertitle, subtitle?, eyebrow?, meta?
sectiontitle, number?, subtitle?
bulletstitle, items:[{title, desc?}], columns?(1–6 条;columns: 2 两栏紧凑模式 1–8 条,书稿/长文推荐)
cardstitle, cards:[{title, desc?, tag?}], columns?(1–6 张;≤3 用 n 列,4 用 2×2,更多 3 列)
kpititle, metrics:[{value, label, note?}](1–4 个巨型数字)
stepstitle, steps:[{title, desc?}](1–5 步)
comparetitle, left:{title, points:[]}, right:{...}(每边 1–6 点)
timelinetitle, events:[{date, title, desc?}](1–5 个节点)
quotequote, author?, role?
tabletitle, headers:[], rows:[[],[]](列 ≤5、行 ≤8)— 原生表格,主色表头 + 交替行
agendatitle, items:[{title, desc?}](1–6 章节目录,编号芯片)
swottitle, s:[], w:[], o:[], t:[](每象限 1–4 条,2×2 矩阵)
pricingtitle, plans:[{name, price, tag?, features:[], highlight?}](1–4 个方案,高亮款渐变头部)
roadmaptitle, phases:[{phase, title, desc?}](1–5 阶段,纵向路线图)
endingtitle?, subtitle?(默认「谢谢」)
charttitle, data, chartType?, insight, bullets?, legend?, dataLabels?, colors?, displayUnits?, source? — 左 7 栏原生图表 + 右 5 栏洞察卡
image-splittitle, image:{src, caption?}, side?, desc?, points? — 一栏大图(7 栏)+ 一栏文字(5 栏)
image-fulltitle?, subtitle?, image:{src}, align?, overlay? — 全幅底图 + 压暗蒙版 + 骑线文字块
diagramtitle, mermaid, caption? — mermaid 源码编译成原生可编辑图形

cover / section / quote / ending / image-full 是整幅铺底的页,不带页脚;hero 页背景用 slide 原生渐变(`set /