Eigent DOCX Skill 深度解析:基于 SKILL.md 实现 Word 文档的创建、编辑与修订
2026/9/14 17:45:23 网站建设 项目流程

Eigent DOCX Skill 深度解析:基于 SKILL.md 实现 Word 文档的创建、编辑与修订

【免费下载链接】eigentEigent: The Open Source Cowork Desktop - Local and Free Alternative to Claude Cowork and Codex项目地址: https://gitcode.com/GitHub_Trending/ei/eigent

本篇技术文章以 Eigent 仓库内置的docx示例技能(resources/example-skills/docx/SKILL.md)为主体,完整讲解该技能如何指导 Agent 创建、编辑和分析 Word 文档(.docx):涵盖 docx-js 生成新文档的关键参数(页幅、样式、列表、表格、图片、目录)、"解包 → 改 XML → 重打包"的编辑流程、修订(tracked changes)与批注的 OOXML 写法,并结合 Eigent 后端的技能同步机制说明该技能是如何被加载、分发和初始化的,读完即可掌握在 Eigent 中为 Agent 装配文档处理能力的全链路实践。

1. 技能定位:SKILL.md 的 frontmatter 与触发规则

Eigent 的技能(Skill)本质是一个目录,目录内的SKILL.md是技能说明书,供 Agent 在对话中判断"什么时候该用这套能力"。docx 技能的 frontmatter 定义如下:

--- name: docx description: "Use this skill whenever the user wants to create, read, edit, or manipulate Word documents (.docx files). Triggers include: any mention of \"Word doc\", \"word document\", \".docx\", or requests to produce professional documents with formatting like tables of contents, headings, page numbers, or letterheads. Also use when extracting or reorganizing content from .docx files, inserting or replacing images in documents, performing find-and-replace in Word files, working with tracked changes or comments, or converting content into a polished Word document. If the user asks for a \"report\", \"memo\", \"letter\", \"template\", or similar deliverable as a Word or .docx file, use this skill. Do NOT use for PDFs, spreadsheets, Google Docs, or general coding tasks unrelated to document generation." license: Proprietary. LICENSE.txt has complete terms ---

从 frontmatter 可以看到三个设计要点:

  • name:技能唯一标识docx,Eigent 后端解析 frontmatter 时正是提取namedescription两个字段(见 skill_service.py 的 _parse_skill_frontmatter)。
  • description 即触发器:用自然语言列明触发条件("Word doc"、".docx"、report/memo/letter/template 等交付物),同时也明确排除场景(PDF、表格、Google Docs、与文档生成无关的编码任务)——这是让 Agent 精准路由到该技能的关键。
  • license:声明专有许可,完整条款在同目录的 LICENSE.txt 中。

该技能目录的完整结构如下:

resources/example-skills/docx/ ├── LICENSE.txt ├── SKILL.md # 技能说明书(本文主体) └── scripts/ ├── accept_changes.py # 接受全部修订,输出干净文档 ├── comment.py # 跨多个 XML 文件写入批注样板 └── office/ ├── pack.py # 重新打包 DOCX(含自动修复与校验) ├── unpack.py # 解包 + XML 美化 + run 合并 ├── validate.py # XSD Schema 校验 ├── soffice.py # LibreOffice 封装(沙箱环境自适应) ├── helpers/ # merge_runs / simplify_redlines ├── schemas/ # ISO-IEC29500-4 等 XSD Schema └── validators/ # docx/pptx/redlining 校验器

2. 技能的分发与初始化:从 example-skills 到 ~/.eigent/skills

SKILL.md 中的命令都以scripts/...相对路径书写,这些脚本在技能被同步到用户目录后随之可用。Eigent 后端负责这一同步过程:

  • skill_service.py 定义了SKILLS_ROOT = ~/.eigent/skills,并维护一个.eigent-example-skill标记文件(EXAMPLE_SKILL_MARKER)用于识别"受管理的示例技能"。
  • sync_example_skills()(skill_service.py#L159-L209)会遍历示例技能根目录(优先环境变量EIGENT_EXAMPLE_SKILLS_DIR,其次打包应用的Resources/example-skills或开发仓库的 resources/example-skills),对每个含SKILL.md的目录:不存在则整目录复制到~/.eigent/skills/<name>/并写入标记文件;已存在且仍为受管理状态且内容不一致则删除后重新复制。若用户已本地修改过同名技能,则会跳过并记录日志,避免覆盖用户改动。
  • 初始化配置脚本 backend/scripts/init_skills_config.py 会扫描~/.eigent/skills/,用正则^---\s*\nname:\s*(.+?)\s*\n从 frontmatter 提取技能名,并生成形如{"version": 1, "skills": {<name>: {"enabled": true, "scope": "global", ...}}}skills-config.json(全局或按用户/项目维度)。仓库根部的 resources/example-skills/default-config.json 即该配置模板。

适用前提:技能脚本在技能目录内相对执行,因此运行 SKILL.md 中的命令时工作目录应为技能目录(如~/.eigent/skills/docx/)。

3. 核心模型:.docx 是 ZIP 包里的 XML

SKILL.md 的第一原则是"A .docx file is a ZIP archive containing XML files"。基于此,技能给出三任务决策表:

任务方案
读取/分析内容pandoc或直接解包看原始 XML
创建新文档使用docx-js(见第 4 节)
编辑现有文档解包 → 编辑 XML → 重新打包(见第 5 节)

3.1 旧版 .doc 的转换

Legacy.doc文件必须先转换才能编辑:

python scripts/office/soffice.py --headless --convert-to docx document.doc

soffice.py 是对 LibreOffice 的封装,按 SKILL.md 依赖说明,它针对沙箱环境做了自动配置。

3.2 内容读取

# 带修订记录(tracked changes)的文本提取 pandoc --track-changes=all document.docx -o output.md # 直接获取原始 XML python scripts/office/unpack.py document.docx unpacked/

unpack.py 的实际行为比"解包"更丰富:解压 ZIP 后对所有*.xml/*.rels文件做美化输出;对.docx额外执行"简化同一作者的相邻修订"(simplify_redlines)和"合并相邻格式相同的 run"(merge_runs),便于后续用字符串替换直接编辑。它支持--merge-runs false跳过 run 合并,且仅接受.docx/.pptx/.xlsx三种后缀。

3.3 转图片(可视化验证)

python scripts/office/soffice.py --headless --convert-to pdf document.docx pdftoppm -jpeg -r 150 document.pdf page

先转 PDF,再用 Poppler 的pdftoppm以 150 DPI 渲染每页 JPEG,用于肉眼验证排版。

3.4 接受全部修订

python scripts/accept_changes.py input.docx output.docx

依赖 LibreOffice,产出一份所有修订均被接受的干净文档。

4. 创建新文档:docx-js 全参数实战

创建流程为"JavaScript 生成 → 校验",依赖安装:npm install -g docx

4.1 基本骨架

const { Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell, ImageRun, Header, Footer, AlignmentType, PageOrientation, LevelFormat, ExternalHyperlink, TableOfContents, HeadingLevel, BorderStyle, WidthType, ShadingType, VerticalAlign, PageNumber, PageBreak } = require('docx'); const doc = new Document({ sections: [{ children: [/* content */] }] }); Packer.toBuffer(doc).then(buffer => fs.writeFileSync("doc.docx", buffer));

生成后必须校验,失败则走"解包 → 修 XML → 重打包"路径:

python scripts/office/validate.py doc.docx

validate.py 支持直接传入打包好的.docx(内部先解压到临时目录),也支持传入已解包的目录;--auto-repair可自动修复两类问题:paraId/durableId超出 OOXML 上限(如>= 0x7FFFFFFF)以及带空白字符的<w:t>缺失xml:space="preserve";Schema 依据来自 schemas/ 下的 ISO-IEC29500-4_2016、ECMA、Microsoft 扩展等 XSD 文件。

4.2 页幅与边距(DXA 单位体系)

docx-js 默认 A4 而非 US Letter,必须显式设置页幅:

sections: [{ properties: { page: { size: { width: 12240, // 8.5 inches in DXA height: 15840 // 11 inches in DXA }, margin: { top: 1440, right: 1440, bottom: 1440, left: 1440 } // 1 inch margins } }, children: [/* content */] }]

常用纸张(1440 DXA = 1 英寸):

纸张内容宽(1 英寸边距)
US Letter12,24015,8409,360
A4(默认值)11,90616,8389,026

横向(Landscape)方向:docx-js 内部会交换宽高,因此始终传纵向尺寸、短边作width,再声明方向即可,由库在 XML 层完成交换:

size: { width: 12240, // Pass SHORT edge as width height: 15840, // Pass LONG edge as height orientation: PageOrientation.LANDSCAPE // docx-js swaps them in the XML }, // 内容宽 = 15840 - 左边距 - 右边距(用的是长边)

4.3 样式:覆盖内建标题样式

建议默认字体用 Arial(通用性最好)、标题保持黑色以保证可读性。关键点是用精确的样式 ID"Heading1"而非"Heading 1")覆盖内建样式,且必须带outlineLevel才能被目录收录:

const doc = new Document({ styles: { default: { document: { run: { font: "Arial", size: 24 } } }, // 12pt default paragraphStyles: [ // IMPORTANT: Use exact IDs to override built-in styles { id: "Heading1", name: "Heading 1", basedOn: "Normal", next: "Normal", quickFormat: true, run: { size: 32, bold: true, font: "Arial" }, paragraph: { spacing: { before: 240, after: 240 }, outlineLevel: 0 } }, // outlineLevel required for TOC { id: "Heading2", name: "Heading 2", basedOn: "Normal", next: "Normal", quickFormat: true, run: { size: 28, bold: true, font: "Arial" }, paragraph: { spacing: { before: 180, after: 180 }, outlineLevel: 1 } }, ] }, sections: [{ children: [ new Paragraph({ heading: HeadingLevel.HEADING_1, children: [new TextRun("Title")] }), ] }] });

4.4 列表:严禁手写 Unicode 项目符号

// ❌ WRONG - never manually insert bullet characters new Paragraph({ children: [new TextRun("• Item")] }) // BAD new Paragraph({ children: [new TextRun("\u2022 Item")] }) // BAD // ✅ CORRECT - use numbering config with LevelFormat.BULLET const doc = new Document({ numbering: { config: [ { reference: "bullets", levels: [{ level: 0, format: LevelFormat.BULLET, text: "•", alignment: AlignmentType.LEFT, style: { paragraph: { indent: { left: 720, hanging: 360 } } } }] }, { reference: "numbers", levels: [{ level: 0, format: LevelFormat.DECIMAL, text: "%1.", alignment: AlignmentType.LEFT, style: { paragraph: { indent: { left: 720, hanging: 360 } } } }] }, ] }, sections: [{ children: [ new Paragraph({ numbering: { reference: "bullets", level: 0 }, children: [new TextRun("Bullet item")] }), new Paragraph({ numbering: { reference: "numbers", level: 0 }, children: [new TextRun("Numbered item")] }), ] }] });

reference决定编号独立性:同一 reference 编号延续(1,2,3 之后接 4,5,6);不同 reference 编号重启(1,2,3 之后又 1,2,3)。

4.5 表格:双宽度规则

表格必须双宽度——表格级columnWidths与每个单元格的width都要设置且一致,否则在部分平台上渲染错乱。同时ShadingType必须用CLEAR(而非SOLID,后者会产生黑底):

// CRITICAL: Always set table width for consistent rendering // CRITICAL: Use ShadingType.CLEAR (not SOLID) to prevent black backgrounds const border = { style: BorderStyle.SINGLE, size: 1, color: "CCCCCC" }; const borders = { top: border, bottom: border, left: border, right: border }; new Table({ width: { size: 9360, type: WidthType.DXA }, // Always use DXA (percentages break in Google Docs) columnWidths: [4680, 4680], // Must sum to table width (DXA: 1440 = 1 inch) rows: [ new TableRow({ children: [ new TableCell({ borders, width: { size: 4680, type: WidthType.DXA }, // Also set on each cell shading: { fill: "D5E8F0", type: ShadingType.CLEAR }, // CLEAR not SOLID margins: { top: 80, bottom: 80, left: 120, right: 120 }, // Cell padding (internal, not added to width) children: [new Paragraph({ children: [new TextRun("Cell")] })] }) ] }) ] })

宽度计算规则(US Letter + 1 英寸边距:12240 - 2880 = 9360):

// Table width = sum of columnWidths = content width width: { size: 9360, type: WidthType.DXA }, columnWidths: [7000, 2360] // Must sum to table width

宽度铁律:

  • 永远用WidthType.DXA,不用WidthType.PERCENTAGE(后者在 Google Docs 中会坏);
  • 表格宽必须等于columnWidths之和;
  • 单元格的width必须与对应columnWidth一致;
  • 单元格margins是内部填充——只缩小内容区,不增加单元格宽;
  • 全宽表格使用"页宽减左右边距"的内容宽。

4.6 图片、分页符与目录

// CRITICAL: type parameter is REQUIRED new Paragraph({ children: [new ImageRun({ type: "png", // Required: png, jpg, jpeg, gif, bmp, svg data: fs.readFileSync("image.png"), transformation: { width: 200, height: 150 }, altText: { title: "Title", description: "Desc", name: "Name" } // All three required })] }) // CRITICAL: PageBreak must be inside a Paragraph new Paragraph({ children: [new PageBreak()] }) // Or use pageBreakBefore new Paragraph({ pageBreakBefore: true, children: [new TextRun("New page")] }) // CRITICAL: Headings must use HeadingLevel ONLY - no custom styles new TableOfContents("Table of Contents", { hyperlink: true, headingStyleRange: "1-3" })

4.7 页眉/页脚

sections: [{ properties: { page: { margin: { top: 1440, right: 1440, bottom: 1440, left: 1440 } } // 1440 = 1 inch }, headers: { default: new Header({ children: [new Paragraph({ children: [new TextRun("Header")] })] }) }, footers: { default: new Footer({ children: [new Paragraph({ children: [new TextRun("Page "), new TextRun({ children: [PageNumber.CURRENT] })] })] }) }, children: [/* content */] }]

4.8 docx-js 关键规则汇总

  • 显式设置页幅——docx-js 默认 A4;US 文档用 12240 × 15840 DXA;
  • 横向传纵向尺寸——短边作width、长边作height,设orientation: PageOrientation.LANDSCAPE
  • 禁用\n——换行用独立 Paragraph;
  • 禁用 Unicode 项目符号——用LevelFormat.BULLET+ numbering 配置;
  • PageBreak 必须在 Paragraph 内——孤立使用会生成非法 XML;
  • ImageRun 必须带type——显式 png/jpg 等;
  • 表格宽一律 DXA——不用WidthType.PERCENTAGE(Google Docs 不兼容);
  • 表格双宽度——columnWidths数组与单元格width都要设且相等;
  • 表宽 = columnWidths 之和——DXA 下必须精确相加;
  • 单元格必须加 margins——推荐margins: { top: 80, bottom: 80, left: 120, right: 120 }
  • 底纹用ShadingType.CLEAR——永不用 SOLID;
  • 目录只认 HeadingLevel——标题段落不能套自定义样式;
  • 覆盖内建样式用精确 ID——"Heading1""Heading2"
  • 必须带outlineLevel——目录收录的前提(H1 为 0,H2 为 1,依此类推)。

5. 编辑现有文档:解包 → 改 XML → 重打包

三步顺序执行,不可跳过。

Step 1:解包

python scripts/office/unpack.py document.docx unpacked/

除解压外,unpack 会美化 XML、合并相邻 run,并把 smart quotes 转成 XML 实体(&#x201C;等)使其在编辑中存活;--merge-runs false可跳过 run 合并。从 unpack.py 源码 可以看到,smart quote 替换表恰含四种实体&#x201C;/&#x201D;/&#x2018;/&#x2019;,与下文的实体表一一对应。

Step 2:编辑 XML

unpacked/word/下编辑,模式参照第 6 节 XML Reference。SKILL.md 给出三条纪律:

  1. 修订与批注的作者默认用 "Claude"(除非用户明确要求其他名字);
  2. 直接用字符串替换工具改字符串,不要写 Python 脚本——脚本引入不必要的复杂度,而直接替换能精确展示被替换的内容;
  3. 新内容必须用 smart quotes——带撇号/引号的文本要写成 XML 实体,保证排版专业:
<!-- Use these entities for professional typography --> <w:t>Here&#x2019;s a quote: &#x201C;Hello&#x201D;</w:t>
实体字符
&#x2018;'(左单引号)
&#x2019;'(右单引号 / 撇号)
&#x201C;"(左双引号)
&#x201D;"(右双引号)

添加批注用 comment.py 处理跨多个 XML 文件的样板代码(传入文本须是已转义的 XML),批注模板存于 templates/:

python scripts/comment.py unpacked/ 0 "Comment text with &amp; and &#x2019;" python scripts/comment.py unpacked/ 1 "Reply text" --parent 0 # reply to comment 0 python scripts/comment.py unpacked/ 0 "Text" --author "Custom Author" # custom author name

之后还需在document.xml中插入标记(见第 6 节 Comments 部分)。

Step 3:重打包

python scripts/office/pack.py unpacked/ output.docx --original document.docx

打包时执行"自动修复 + 校验 + XML 压缩 + 生成 DOCX";--validate false可跳过校验。自动修复处理:durableId >= 0x7FFFFFFF(重新生成合法 ID)、带空白<w:t>缺失xml:space="preserve"不能处理:非法 XML、错误嵌套、缺失 relationships、Schema 违规。

常见坑

  • 整体替换<w:r>元素:添加修订时,要把整个<w:r>...</w:r>块替换为并列的<w:del>...<w:ins>...,不要往 run 内部注入修订标签;
  • 保留<w:rPr>格式:把原 run 的<w:rPr>块复制进新的修订 run,以维持粗体、字号等格式。

6. XML Reference:修订、批注与图片的 OOXML 写法

6.1 Schema 合规三要点

  • <w:pPr>内元素顺序<w:pStyle><w:numPr><w:spacing><w:ind><w:jc><w:rPr>必须最后;
  • 空白处理:前后带空格的<w:t>要加xml:space="preserve"
  • RSID 必须是 8 位十六进制(如00AB1234)。

6.2 Tracked Changes(修订)

插入:

<w:ins w:id="1" w:author="Claude" w:date="2025-01-01T00:00:00Z"> <w:r><w:t>inserted text</w:t></w:r> </w:ins>

删除:

<w:del w:id="2" w:author="Claude" w:date="2025-01-01T00:00:00Z"> <w:r><w:delText>deleted text</w:delText></w:r> </w:del>

<w:del>内部:<w:t>换成<w:delText><w:instrText>换成<w:delInstrText>

最小化编辑——只标记真正变化的部分(把 "30 days" 改成 "60 days"):

<!-- Change "30 days" to "60 days" --> <w:r><w:t>The term is </w:t></w:r> <w:del w:id="1" w:author="Claude" w:date="..."> <w:r><w:delText>30</w:delText></w:r> </w:del> <w:ins w:id="2" w:author="Claude" w:date="..."> <w:r><w:t>60</w:t></w:r> </w:ins> <w:r><w:t> days.</w:t></w:r>

整段/整列表项删除——删光段落内容时,还要把段落标记本身标记为删除,让它与下一段合并。做法是在<w:pPr><w:rPr>里加<w:del/>

<w:p> <w:pPr> <w:numPr>...</w:numPr> <!-- list numbering if present --> <w:rPr> <w:del w:id="1" w:author="Claude" w:date="2025-01-01T00:00:00Z"/> </w:rPr> </w:pPr> <w:del w:id="2" w:author="Claude" w:date="2025-01-01T00:00:00Z"> <w:r><w:delText>Entire paragraph content being deleted...</w:delText></w:r> </w:del> </w:p>

漏掉<w:pPr><w:rPr>里的<w:del/>,接受修订后会留下空段落/空列表项。

拒绝他人插入——把删除嵌套进对方的插入中:

<w:ins w:author="Jane" w:id="5"> <w:del w:author="Claude" w:id="10"> <w:r><w:delText>their inserted text</w:delText></w:r> </w:del> </w:ins>

恢复他人删除——在其删除之后新增插入(不要改动对方的删除):

<w:del w:author="Jane" w:id="5"> <w:r><w:delText>deleted text</w:delText></w:r> </w:del> <w:ins w:author="Claude" w:id="10"> <w:r><w:t>deleted text</w:t></w:r> </w:ins>

6.3 批注(Comments)

运行comment.py后,在document.xml中加标记;回复用--parent,标记嵌套在父批注标记内。关键:<w:commentRangeStart><w:commentRangeEnd><w:r>的兄弟节点,绝不能放进<w:r>内部

<!-- Comment markers are direct children of w:p, never inside w:r --> <w:commentRangeStart w:id="0"/> <w:del w:id="1" w:author="Claude" w:date="2025-01-01T00:00:00Z"> <w:r><w:delText>deleted</w:delText></w:r> </w:del> <w:r><w:t> more text</w:t></w:r> <w:commentRangeEnd w:id="0"/> <w:r><w:rPr><w:rStyle w:val="CommentReference"/></w:rPr><w:commentReference w:id="0"/></w:r> <!-- Comment 0 with reply 1 nested inside --> <w:commentRangeStart w:id="0"/> <w:commentRangeStart w:id="1"/> <w:r><w:t>text</w:t></w:r> <w:commentRangeEnd w:id="1"/> <w:commentRangeEnd w:id="0"/> <w:r><w:rPr><w:rStyle w:val="CommentReference"/></w:rPr><w:commentReference w:id="0"/></w:r> <w:r><w:rPr><w:rStyle w:val="CommentReference"/></w:rPr><w:commentReference w:id="1"/></w:r>

6.4 手工插图(直接改 XML 路径)

  1. 图片文件放入word/media/
  2. word/_rels/document.xml.rels加关系:
<Relationship Id="rId5" Type=".../image" Target="media/image1.png"/>
  1. [Content_Types].xml加内容类型:
<Default Extension="png" ContentType="image/png"/>
  1. document.xml中引用:
<w:drawing> <wp:inline> <wp:extent cx="914400" cy="914400"/> <!-- EMUs: 914400 = 1 inch --> <a:graphic> <a:graphicData uri=".../picture"> <pic:pic> <pic:blipFill><a:blip r:embed="rId5"/></pic:blipFill> </pic:pic> </a:graphicData> </a:graphic> </wp:inline> </w:drawing>

7. 依赖与运行环境

依赖用途
pandoc文本提取
docxnpm install -g docx创建新文档
LibreOfficePDF 转换(经 scripts/office/soffice.py 为沙箱环境自动配置)
Popplerpdftoppm图片渲染

8. 小结与延伸

这份 SKILL.md 的价值在于把"Word 文档自动化"的全部易错点沉淀成了可执行规则:docx 侧的 DXA 单位体系、双宽度表格、numbering 列表、样式 ID 精确覆盖;XML 侧的 smart quote 实体、<w:del>/<w:ins>兄弟替换、批注标记位置,以及"自动修复能修什么、不能修什么"的明确边界。配合 unpack.py、pack.py、validate.py 三个脚本构成的"解包-编辑-校验"闭环,Agent 可以在无人工干预下交付跨 Word/Google Docs 表现一致的 .docx。

同目录下还有同系列的 pdf、pptx、xlsx 技能,均复用同一套scripts/office工具链与 XSD Schema(该工具链甚至支持 pptx/xlsx 的校验器扩展),以及 skill-creator(用于创建新技能)与 skill-security-auditor(用于技能安全审计)——阅读 docx 技能掌握的模式可以直接迁移到这些兄弟技能上。

【免费下载链接】eigentEigent: The Open Source Cowork Desktop - Local and Free Alternative to Claude Cowork and Codex项目地址: https://gitcode.com/GitHub_Trending/ei/eigent

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询