@deepseek-ai/dsh-skill-office 0.2.1-alpha.1 → 0.2.1-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.i18n.yaml CHANGED
@@ -3,41 +3,38 @@
3
3
  # After editing either side, bring the other along and re-record with:
4
4
  # pnpm run verify-translation-pairing --write packages/skill/skill-office/README.md
5
5
  /:
6
- en: cc4e875ac37d9354
7
- zh: e121819cde2ce76c
6
+ en: b65118b967447a79
7
+ zh: 83afe7dbc1a82c44
8
8
  /deepseek-ai-dsh-skill-office:
9
9
  en: f855ac5e12b4e604
10
10
  zh: b2e875d31d56cb2e
11
11
  /deepseek-ai-dsh-skill-office/summary:
12
- en: 4fe3cb4544ce7d9d
13
- zh: 2749b1d0ffa468a1
12
+ en: 3f41e39809756048
13
+ zh: 5ec8623eeb617755
14
14
  /deepseek-ai-dsh-skill-office/table-of-contents:
15
- en: d152484eb41ac6b4
16
- zh: 43ad67236ccda19a
15
+ en: c361195a73cf5755
16
+ zh: 745b04e7be0be369
17
17
  /deepseek-ai-dsh-skill-office/use-this-package:
18
- en: 6987594a0cf6e53d
19
- zh: ecf4c4f52e7a50eb
20
- /deepseek-ai-dsh-skill-office/use-this-package/minimal-configuration:
21
- en: 088f223318dff9d7
22
- zh: ce944bf61fb1f821
23
- /deepseek-ai-dsh-skill-office/use-this-package/structural-checks:
24
- en: 1bb872d63bdd78b7
25
- zh: 355dee19bdfb3798
18
+ en: bc0460f837676d8a
19
+ zh: be1692483d7f5daf
20
+ /deepseek-ai-dsh-skill-office/use-this-package/progressive-loading:
21
+ en: b20b58bfaab4d3f6
22
+ zh: 084528c543b8746e
26
23
  /deepseek-ai-dsh-skill-office/understand-the-implementation:
27
- en: 9c45ee3de51adb4d
28
- zh: 0f24d44dfa58df25
24
+ en: 6946e30f3663c72f
25
+ zh: 81dd5de9c7e61562
29
26
  /deepseek-ai-dsh-skill-office/further-exploration:
30
- en: b08fc992559d8fda
31
- zh: ad74e037ecd81c0b
27
+ en: 23830c100287ffb8
28
+ zh: 7a4100e87057bc20
32
29
  /deepseek-ai-dsh-skill-office/model-experience:
33
- en: f9fe51841bd9de8d
34
- zh: 773af8cc0e0335e4
30
+ en: bbd979fd20e7b308
31
+ zh: 7e6b6e04f2aedb02
35
32
  /deepseek-ai-dsh-skill-office/model-experience/kv-cache-effect:
36
- en: c77beb1c0d28f221
37
- zh: 99643101d2b915b9
33
+ en: dc9e9205d989f1d0
34
+ zh: 01bdbbfba743813a
38
35
  /deepseek-ai-dsh-skill-office/known-limitations-and-deferred-work:
39
- en: 22d0a8196c2c48c3
40
- zh: ef2e38a0a91476a8
36
+ en: 1f718fc3bb4352d9
37
+ zh: c1aa108ad2b05fdd
41
38
  /deepseek-ai-dsh-skill-office/known-limitations-and-deferred-work/dev-note:
42
39
  en: e67f633dc6fe7773
43
- zh: 3bb0c20c93f16f98
40
+ zh: 8ec3e73528ddb1a6
package/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: "Bundled Word, PowerPoint, and Excel instructions for deployments providing Office file authoring and structural checks."
2
+ description: "Progressive Office authoring and CLI operation skills for document workflows."
3
3
  kind: "package-reference"
4
4
  ---
5
5
 
@@ -9,11 +9,12 @@ English | [中文](README.zh.md)
9
9
 
10
10
  ## Summary
11
11
 
12
- Agents can load Word, PowerPoint, and Excel workflows that use the bundled Python environment by default and respect explicit user or AGENTS.md environment choices. The skills cover creation, focused edits, structural checks, and file delivery. When supplied by the environment, the LibreOffice Kit CLI directly renders selected pages or worksheet ranges, converts requested PDFs, and recalculates workbooks into new files. Visual QA requires image input support, reuses unchanged-source previews, and ends when applicable checks pass; ordinary delivery does not require installing a renderer.
12
+ The catalog lists the Office router and six authoring or operation skills. Full instructions load on demand. Authoring skills use the selected Python libraries; operation skills supply the configured Node and LibreOffice Kit CLI paths. SDK and Desktop mount the skill provider and dependency query separately.
13
13
 
14
14
  ## Table of Contents
15
15
 
16
16
  - [Use this package](#use-this-package)
17
+ - [Progressive loading](#progressive-loading)
17
18
  - [Understand the implementation](#understand-the-implementation)
18
19
  - [Further Exploration](#further-exploration)
19
20
  - [Model Experience](#model-experience)
@@ -25,29 +26,56 @@ Agents can load Word, PowerPoint, and Excel workflows that use the bundled Pytho
25
26
  <a id="use-this-package"></a>
26
27
  ## Use this package
27
28
 
28
- Mount this provider beside the skill registry and `dsh-tool-skill` to expose `office-docx`, `office-pptx`, and `office-xlsx` in the session catalog. The provider supplies instructions and scripts; the deployment supplies its interpreters, authoring libraries, execution tools, and file delivery tool.
29
-
30
- ### Minimal configuration
29
+ Mount the skill provider beside the skill registry and `dsh-tool-skill`. The provider supplies skill instructions; operation skills append absolute interpreter and CLI paths when loaded.
31
30
 
32
31
  ```yaml
33
32
  - name: '@deepseek-ai/dsh-skill-office'
33
+
34
34
  ```
35
35
 
36
36
  | Field | Default | Meaning |
37
37
  |---|---|---|
38
- | `assetRoot` | Packaged `assets/` | Absolute resource directory containing the three skill folders and shared `scripts/`; deployments can place it outside an application archive. |
39
- | `node` | Current standalone Node process | Absolute Node executable; Electron and SDK executables must supply a standalone Node. |
40
- | `cli` | Installed kit’s `lib/cli.js` | Absolute CLI entry; `false` explicitly disables the CLI. |
41
-
42
- Relative paths, missing resources, and skill files without a YAML frontmatter description reject activation. Disposing the plugin removes its candidates. Project and user skill precedence remains owned by the skill registry.
43
-
44
- Loaded skills append an `Installed LibreOffice Kit` section with absolute `libreofficeKit.node` and `libreofficeKit.cli` paths. Agents use the bundled binaries unless the user explicitly opts out, passing the CLI as Node’s first argument without relying on the working directory or searching PATH. A bundled CLI failure is reported rather than replaced with a system executable. Activation checks both paths are files. npm deployments use their installed kit by default; Desktop supplies standalone Node and the complete unpacked Office dependencies; the Python SDK supplies standalone Node and its adjacent Office directory. Custom Python-only deployments must set `cli: false` or supply standalone Node.
45
-
46
- ### Structural checks
47
-
48
- The shared Python checker reads DOCX, PPTX, or XLSX without modifying the source. It recognizes Transitional and Strict OOXML namespaces, validates ZIP/XML and internal package relationships, reports document structure, and optionally checks required text or slide/sheet count. DOCX text assertions cover the main body, section-referenced headers and footers, and body-referenced footnotes and endnotes; comments, glossary text, and unreferenced parts or notes do not satisfy them. It uses only the Python standard library. Invalid packages, corrupt or encrypted ZIP members, and report-file write failures produce a JSON failure report on stdout. A passing report does not establish appearance, feature preservation, or calculated formula results.
38
+ | `assetRoot` | Carrier resources or packaged `assets/` | Absolute directory containing the seven skill folders. |
39
+ | `node` | Host Node.js | Standalone Node executable for operation skills; packaged carriers supply it explicitly. |
40
+ | `cli` | Installed Kit CLI | Absolute CLI entry; `false` disables operation execution. |
41
+
42
+ Invalid skill assets reject registration. Authoring instructions do not resolve renderer paths; loading operation instructions validates existing interpreter and CLI files without preparing engines. Bash or PowerShell owns approvals, sandboxing, cancellation, and output management. Resources and execution tools must refer to the same host.
43
+
44
+ <a id="progressive-loading"></a>
45
+ ### Progressive loading
46
+
47
+ The model chooses skills from the task; the host does not inject complete instructions based on file extensions. All seven skills are listed and loadable by name through `skill` or explicit user invocation. Requested appearance changes, including spreadsheet cell fills and text alignment, require rendering even without an explicit visual-check request; an explicit request to skip rendering takes precedence. Pure operations can enter the corresponding skill directly.
48
+
49
+ ```mermaid
50
+ flowchart TD
51
+ A["Initial catalog: seven Office summaries"]
52
+ A -->|Non-Office task| N["Continue without loading Office instructions"]
53
+ A -->|Office task| B["skill(office): task routing"]
54
+
55
+ B -->|Create or edit| C["skill(office-docx / office-pptx / office-xlsx)"]
56
+ C --> D["Author and validate content, structure and data"]
57
+ D -->|No additional operation needed| F["Deliver"]
58
+
59
+ B -->|Images, visual inspection, or appearance changes| R["skill(office-render)"]
60
+ D -->|Images, visual inspection, or appearance changes| R
61
+ R --> RC["Shell: render into a private workspace temporary directory"]
62
+ RC -->|Usable preview| V["Inspect requested images"]
63
+ V --> F
64
+ RC -->|Failed or blank preview| L["Report content checks and unfinished visual inspection separately"]
65
+ L --> F
66
+
67
+ B -->|Formula recalculation| X["skill(office-recalculate)"]
68
+ D -->|Formula recalculation| X
69
+ X --> XC["Shell: CLI recalculate and verify values"]
70
+ XC --> F
71
+
72
+ B -->|PDF export| P["skill(office-export-pdf)"]
73
+ D -->|PDF export| P
74
+ P --> PC["Shell: CLI convert"]
75
+ PC --> F
76
+ ```
49
77
 
50
- Excel data and formula tasks skip visual inspection; formatting, layout, chart appearance, printing, or known display problems require checking relevant regions. Basic styling does not itself trigger inspection, and formula recalculation and requested exports remain independent operations. Blank or transparent previews do not pass visual inspection. A failed preview ends visual checking without diagnostic workbooks, PDF conversion, or unrequested print-setting changes; the usable workbook is delivered with the inspection limitation.
78
+ New files and content, data, or formula edits do not require rendering solely because they include incidental basic formatting. Preview images use a private, randomly named temporary directory inside the workspace and are removed after inspection. Loaded instructions remain in session history after an operation finishes. The diagram describes ordinary presets across supported clients; it does not change the shell-only definition of minimal or sdk-minimal.
51
79
 
52
80
  -----
53
81
 
@@ -57,12 +85,15 @@ Excel data and formula tasks skip visual inspection; formatting, layout, chart a
57
85
  <details>
58
86
  <summary>Implementation internals — click to expand</summary>
59
87
 
60
- The provider registers three bundled candidates and reads their descriptions from the shipped YAML frontmatter at activation. Loaded instruction bodies exclude that metadata. Each loaded skill exposes its own filesystem directory, so the execution tool can resolve the shared checker without relying on the task working directory. Configurable external resources support carriers whose application archive is not readable by Python.
88
+ The provider reads descriptions at registration and bodies on demand. Registry precedence, scope, and invocation permissions apply to every skill. Each loaded skill includes its resource directory for relative references. Disposal unregisters all candidates.
89
+
90
+ The provider registers no tools or activation projection. Loaded skill results remain logged through the generic skill loader. In PTC programs, print or return the full `tools.skill(...)` result so its instructions reach the model. Resume and fork use existing session history and tool declaration reconciliation. Interpreter and CLI paths are resolved only when an operation skill is loaded.
61
91
 
62
92
  | File | Responsibility |
63
93
  |---|---|
64
- | [`src/index.ts`](src/index.ts) | Provider registration and configured resource paths. |
65
- | [`assets/`](assets/) | Three workflows and the read-only OOXML checker. |
94
+ | [`src/index.ts`](src/index.ts) | Resource registration and unchanged skill bodies. |
95
+
96
+ | [`assets/`](assets/) | Router and six subskills. |
66
97
 
67
98
  </details>
68
99
 
@@ -72,7 +103,7 @@ The provider registers three bundled candidates and reads their descriptions fro
72
103
  ## Further Exploration
73
104
 
74
105
  - [Skill registry](../skill/README.md) — discovery and precedence.
75
- - [Skill tool](../tool-skill/README.md) — model-visible catalogs and bodies.
106
+ - [Skill tool](../tool-skill/README.md) — model catalogs and loaded bodies.
76
107
  - [File delivery](../../deliverables/tool-present/README.md) — current source-path delivery.
77
108
 
78
109
  -----
@@ -80,19 +111,19 @@ The provider registers three bundled candidates and reads their descriptions fro
80
111
  <a id="model-experience"></a>
81
112
  ## Model Experience
82
113
 
83
- Indirectly, through `dsh-tool-skill`, which renders the catalog entries and selected instruction body.
114
+ Indirectly, through `dsh-tool-skill`, which renders the initial Office summary and returns routing or selected subskill instructions from `skill(name)`.
84
115
 
85
116
  #### KV Cache effect
86
117
 
87
- Mounting the provider adds three catalog entries; loading a skill adds its body at the existing skill-tool insertion point. The provider does not add separate prompt sections.
118
+ Skill results append to history without changing tool declarations, and loaded text or images remain after an operation finishes. Character estimates use `ceil(characters / 4)`: the summary is at most 50 tokens and the router body at most 200; authoring bodies are at least 35% smaller than the combined authoring and operation guides. Skill wrappers, the generic catalog, shell declarations, and `load_workspace_dependencies` are separate costs, and these estimates are not server usage.
88
119
 
89
120
  ## Known Limitations and Deferred Work
90
121
 
91
122
  <a id="known-limitations-and-deferred-work"></a>
92
123
 
93
- - The provider depends on LibreOffice Kit but does not install Python or authoring libraries. The checker requires Python 3.9 or later.
94
- - Structural checks do not judge pagination, clipping, fonts, chart appearance, or Excel recalculation.
95
- - The checker accepts DOCX, PPTX, and XLSX only; legacy, encrypted, and macro-enabled formats require an appropriate separate workflow.
124
+ - The provider does not install authoring libraries.
125
+ - LibreOffice output can differ from native Office applications.
126
+ - Legacy, encrypted, and macro-enabled files need an appropriate separate workflow.
96
127
 
97
128
  <a id="dev-note"></a>
98
129
  ### Dev Note
package/README.zh.md CHANGED
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: "随包附带的 Word、PowerPoint 和 Excel 指令,供需要 Office 文件编写与结构检查能力的部署使用。"
2
+ description: "为文档工作流提供渐进加载的 Office 创作与 CLI 操作技能。"
3
3
  kind: "package-reference"
4
4
  ---
5
5
 
@@ -9,15 +9,16 @@ kind: "package-reference"
9
9
 
10
10
  ## 概述
11
11
 
12
- Agent(智能体)可以加载 Word、PowerPoint 和 Excel 工作流,默认使用内置 Python 环境,并遵循用户或 AGENTS.md 明确指定的环境。这些 skill(技能)涵盖创建、局部编辑、结构检查和文件交付。环境提供 LibreOffice Kit CLI 时,可直接渲染选定页面或工作表区域、转换所需 PDF,并将重新计算后的工作簿保存为新文件。视觉检查要求模型支持图片输入,复用源文件未变时的预览,并在适用检查通过后结束;普通交付不要求安装渲染器。
12
+ 目录列出 Office 路由及六个创作或操作技能,完整说明按需加载。创作技能使用选定的 Python 库;操作技能提供配置的 Node 与 LibreOffice Kit CLI 路径。SDK 和 Desktop 分别挂载技能提供者和依赖查询。
13
13
 
14
14
  ## 目录
15
15
 
16
16
  - [使用本包](#use-this-package)
17
- - [了解实现](#understand-the-implementation)
18
- - [延伸阅读](#further-exploration)
17
+ - [渐进加载](#progressive-loading)
18
+ - [理解实现](#understand-the-implementation)
19
+ - [进一步探索](#further-exploration)
19
20
  - [模型体验](#model-experience)
20
- - [已知限制与暂缓事项](#known-limitations-and-deferred-work)
21
+ - [已知限制与后续工作](#known-limitations-and-deferred-work)
21
22
  - [开发备注](#dev-note)
22
23
 
23
24
  -----
@@ -25,80 +26,110 @@ Agent(智能体)可以加载 Word、PowerPoint 和 Excel 工作流,默认
25
26
  <a id="use-this-package"></a>
26
27
  ## 使用本包
27
28
 
28
- 将本提供方与 skill 注册表及 `dsh-tool-skill` 一同挂载,即可在会话目录中提供 `office-docx`、`office-pptx` 和 `office-xlsx`。提供方携带指令和脚本;部署提供解释器、编写库、执行工具与文件交付工具。
29
-
30
- ### 最小配置
29
+ 在技能注册表和 `dsh-tool-skill` 旁挂载技能提供者。提供者负责技能说明;操作技能在加载时附加解释器和 CLI 的绝对路径。
31
30
 
32
31
  ```yaml
33
32
  - name: '@deepseek-ai/dsh-skill-office'
33
+
34
34
  ```
35
35
 
36
36
  | 字段 | 默认值 | 含义 |
37
37
  |---|---|---|
38
- | `assetRoot` | 包内的 `assets/` | 包含三个 skill 文件夹和共享 `scripts/` 的绝对资源目录;部署可将其放在应用归档之外。 |
39
- | `node` | 当前独立 Node 进程 | Node 可执行文件的绝对路径;Electron 和独立 SDK 可执行文件必须显式提供。 |
40
- | `cli` | 已安装 kit 的 `lib/cli.js` | CLI 入口的绝对路径;`false` 显式禁用 CLI。 |
41
-
42
- 相对路径、资源缺失或 skill 文件的 YAML frontmatter 缺少描述都会导致激活失败。卸载插件会移除其候选项。项目和用户 skill 的优先级仍由 skill 注册表负责。
43
-
44
- 加载的技能在 `Installed LibreOffice Kit` 段落中提供 `libreofficeKit.node` 和 `libreofficeKit.cli` 的绝对路径。除非用户明确要求不用内置二进制,Agent 将内置 CLI 作为 Node 的第一个参数运行,无需依赖工作目录或搜索 PATH。内置 CLI 失败时报告错误,不替换为系统可执行文件。激活时检查两个路径是否为实际文件。npm 部署默认使用已安装的 kit;Desktop 提供独立 Node 和 ASAR 外的完整 Office 依赖;Python SDK 提供独立 Node 和可执行文件旁的 Office 目录。仅有 Python 的自定义部署必须配置 `cli: false`,或提供独立 Node。
45
-
46
- ### 结构检查
47
-
48
- 共享 Python 检查器读取 DOCX、PPTX 或 XLSX,不修改源文件。它识别 Transitional 与 Strict OOXML 命名空间,验证 ZIP/XML 和包内引用关系,报告文档结构,并可检查必需文本或幻灯片/工作表数量。DOCX 文本断言覆盖正文、分节引用的页眉和页脚,以及正文引用的脚注和尾注;批注、词库文本与未引用的部件或脚注/尾注不能满足断言。它只使用 Python 标准库。无效包、损坏或加密的 ZIP 成员和报告文件写入失败都会在标准输出中产生 JSON 失败报告。检查通过不代表外观、特性保留或公式计算结果已得到验证。
38
+ | `assetRoot` | 载体资源或包内 `assets/` | 包含七个技能目录的绝对路径。 |
39
+ | `node` | 宿主 Node.js | 操作技能使用的独立 Node 可执行程序;打包载体显式提供。 |
40
+ | `cli` | 已安装 Kit CLI | CLI 入口的绝对路径;`false` 禁用操作执行。 |
41
+
42
+ 无效技能资源会阻止注册。创作说明不解析渲染器路径;操作说明加载时校验现有解释器和 CLI 文件,不准备引擎。Bash 或 PowerShell 负责审批、沙箱、取消和输出管理。资源与执行工具必须位于同一主机。
43
+
44
+ <a id="progressive-loading"></a>
45
+ ### 渐进加载
46
+
47
+ 模型依据任务选择技能;宿主不会根据文件扩展名注入完整说明。七个技能均列入目录,可通过 `skill` 按名称加载或由用户显式调用。用户要求的外观修改,包括电子表格单元格填充和文本对齐,即使没有明确要求视觉检查,也需要渲染;用户明确要求跳过渲染时以该要求为准。纯操作任务可以直接进入对应技能。
48
+
49
+ ```mermaid
50
+ flowchart TD
51
+ A["Initial catalog: seven Office summaries"]
52
+ A -->|Non-Office task| N["Continue without loading Office instructions"]
53
+ A -->|Office task| B["skill(office): task routing"]
54
+
55
+ B -->|Create or edit| C["skill(office-docx / office-pptx / office-xlsx)"]
56
+ C --> D["Author and validate content, structure and data"]
57
+ D -->|No additional operation needed| F["Deliver"]
58
+
59
+ B -->|Images, visual inspection, or appearance changes| R["skill(office-render)"]
60
+ D -->|Images, visual inspection, or appearance changes| R
61
+ R --> RC["Shell: render into a private workspace temporary directory"]
62
+ RC -->|Usable preview| V["Inspect requested images"]
63
+ V --> F
64
+ RC -->|Failed or blank preview| L["Report content checks and unfinished visual inspection separately"]
65
+ L --> F
66
+
67
+ B -->|Formula recalculation| X["skill(office-recalculate)"]
68
+ D -->|Formula recalculation| X
69
+ X --> XC["Shell: CLI recalculate and verify values"]
70
+ XC --> F
71
+
72
+ B -->|PDF export| P["skill(office-export-pdf)"]
73
+ D -->|PDF export| P
74
+ P --> PC["Shell: CLI convert"]
75
+ PC --> F
76
+ ```
49
77
 
50
- Excel 数据和公式任务跳过视觉检查;涉及格式、排版、图表外观、打印或已知显示问题时,检查相关区域。基础样式设置本身不触发视觉检查,公式重算和用户要求的导出仍是独立操作。 空白或透明预览不能通过视觉检查。预览失败时结束视觉检查,不创建诊断工作簿、不转换 PDF,也不擅自修改打印设置;交付可用工作簿并说明检查限制。
78
+ 新建文件以及内容、数据或公式修改,不会仅因附带基础格式调整而需要渲染。预览图片保存在工作区内随机命名的私有临时目录中,并在检查后删除。操作结束后,已加载的说明仍属于会话历史。图中描述各端常规预设,不改变 minimal 或 sdk-minimal 的纯 shell 定义。
51
79
 
52
80
  -----
53
81
 
54
82
  <a id="understand-the-implementation"></a>
55
- ## 了解实现
83
+ ## 理解实现
56
84
 
57
85
  <details>
58
- <summary>实现细节——点击展开</summary>
86
+ <summary>实现细节 — 点击展开</summary>
59
87
 
60
- 提供方注册三个内置候选项,并在激活时从随包 YAML frontmatter 读取描述。加载后的指令正文不包含这些元数据。加载后的每个 skill 暴露自己的文件系统目录,因此执行工具可以定位共享检查器,而不依赖任务工作目录。可配置的外部资源支持 Python 无法读取应用归档的分发方式。
88
+ 提供者在注册时读取摘要,按需读取正文。注册表优先级、作用域和调用权限适用于全部技能。加载结果包含用于解析相对引用的资源目录。卸载提供者会移除全部候选技能。
89
+
90
+ 提供者不注册工具或激活投影。通用技能加载器记录已加载技能的结果。PTC 程序需输出或返回完整的 `tools.skill(...)` 结果,使说明进入模型上下文。恢复和 fork 使用现有会话历史及工具声明更新机制。解释器和 CLI 路径仅在加载操作技能时解析。
61
91
 
62
92
  | 文件 | 职责 |
63
93
  |---|---|
64
- | [`src/index.ts`](src/index.ts) | 提供方注册与配置资源路径。 |
65
- | [`assets/`](assets/) | 三种工作流和只读 OOXML 检查器。 |
94
+ | [`src/index.ts`](src/index.ts) | 注册资源并返回原始技能正文。 |
95
+
96
+ | [`assets/`](assets/) | 路由与六个子技能。 |
66
97
 
67
98
  </details>
68
99
 
69
100
  -----
70
101
 
71
102
  <a id="further-exploration"></a>
72
- ## 延伸阅读
103
+ ## 进一步探索
73
104
 
74
- - [skill 注册表](../skill/README.zh.md)——发现与优先级。
75
- - [skill 工具](../tool-skill/README.zh.md)——模型可见目录与正文。
76
- - [文件交付](../../deliverables/tool-present/README.zh.md)——当前源路径交付。
105
+ - [技能注册表](../skill/README.zh.md) — 发现和优先级。
106
+ - [技能工具](../tool-skill/README.zh.md) — 模型目录和加载正文。
107
+ - [文件交付](../../deliverables/tool-present/README.zh.md) — 当前源文件路径交付。
77
108
 
78
109
  -----
79
110
 
80
111
  <a id="model-experience"></a>
81
112
  ## 模型体验
82
113
 
83
- 通过 `dsh-tool-skill` 间接呈现,由其渲染目录项与选中的指令正文。
114
+ 间接影响:`dsh-tool-skill` 渲染初始 Office 摘要,并返回 `skill(name)` 选择的路由或子技能说明。
84
115
 
85
- #### KV 缓存影响
116
+ #### KV Cache 影响
86
117
 
87
- 挂载提供方会增加三个目录项;加载 skill 时,其正文进入既有 skill 工具的插入位置。提供方不另增提示词分区。
118
+ 技能结果追加到历史中且不改变工具声明,操作完成后已加载文本或图片仍保留在历史中。字符估算采用 `ceil(字符数 / 4)`:摘要最多 50 token,路由正文最多 200 token;创作正文比合并后的创作与操作指南至少减少 35%。技能包装、通用目录、shell 声明和 `load_workspace_dependencies` 单独计费;这些估算不等同于服务端 usage。
88
119
 
89
- ## 已知限制与暂缓事项
120
+ ## 已知限制与后续工作
90
121
 
91
122
  <a id="known-limitations-and-deferred-work"></a>
92
123
 
93
- - 提供方依赖 LibreOffice Kit,但不安装 Python 或编写库。检查器要求 Python 3.9 或更高版本。
94
- - 结构检查不判断分页、裁切、字体、图表外观或 Excel 重算。
95
- - 检查器只接受 DOCX、PPTX 和 XLSX;传统格式、加密文件和启用宏的格式需要合适的其他工作流。
124
+ - 提供者不安装创作库。
125
+ - LibreOffice 输出可能不同于原生 Office 应用。
126
+ - 旧格式、加密和启用宏的文件需要适用的独立工作流。
96
127
 
97
128
  <a id="dev-note"></a>
98
129
  ### 开发备注
99
130
 
100
131
  <details>
101
- <summary>维护者的工作上下文——点击展开</summary>
132
+ <summary>维护者工作上下文 — 点击展开</summary>
102
133
 
103
134
  无。
104
135
 
@@ -0,0 +1,15 @@
1
+ ---
2
+ name: office
3
+ description: Create, edit, inspect, or convert Office documents, spreadsheets, presentations, and PDFs. Load to choose the relevant workflow.
4
+ ---
5
+
6
+ # Office task routing
7
+
8
+ Call the `skill` tool with the format skill name before creating, reading, or editing Office files. Standalone rendering, PDF export, and recalculation need only their operation skill.
9
+
10
+ - Create, read, or edit: `office-docx` (Word), `office-pptx` (PowerPoint), or `office-xlsx` (Excel).
11
+ - Images, visual inspection, or tasks primarily about layout, spacing, sizing, or alignment: `office-render`, even without an explicit visual-check request.
12
+ - Refresh spreadsheet formula results: `office-recalculate`.
13
+ - Export an Office file to PDF: `office-export-pdf`.
14
+
15
+ Honor requests to skip rendering. New files, content edits, formulas, and incidental formatting such as bold headers or number formats need no render unless visual inspection is requested. Do not reload skills.
@@ -0,0 +1,17 @@
1
+ # Missing Office sidecar
2
+
3
+ These instructions apply in Web, Desktop, and SDK sessions. First check whether an existing LibreOffice Kit installation can serve the task; a missing `dsoffice` command on PATH alone does not establish that the sidecar is absent. If the required sidecar is missing and the user has not already authorized its download, use the available ask-question tool to ask whether they want to download the Office conversion engine. If that tool is unavailable, ask in the conversation. Wait for explicit agreement before downloading or installing; if the user declines, continue work that does not need the engine and report the unavailable operation.
4
+
5
+ After agreement, install `@deepseek-ai/libreoffice-kit` together with the matching engine package, using the engine version declared by the main package's optional dependencies. Use an available npm or pnpm in a task-local tools directory, with `--ignore-scripts`: the packages provide prebuilt binaries and do not need installation scripts. Keep the package manager's cache or store in that writable task directory too (for npm, pass `--cache <task-tools-directory>/.npm-cache`). Select the package for the operating system and architecture of the Node.js process that will run the CLI, including when Node runs under emulation:
6
+
7
+ | Runtime platform | Engine package |
8
+ | --- | --- |
9
+ | Windows x64 | `@deepseek-ai/libreoffice-kit-win32-x64` |
10
+ | Windows ARM64 | `@deepseek-ai/libreoffice-kit-win32-arm64` |
11
+ | macOS Apple Silicon | `@deepseek-ai/libreoffice-kit-darwin-arm64` |
12
+ | macOS Intel | `@deepseek-ai/libreoffice-kit-darwin-x64` |
13
+ | No matching native package, including Linux | `@deepseek-ai/libreoffice-kit-wasm` |
14
+
15
+ Prefer the matching native package; otherwise use WASM when its package supports the current platform. Check package platform restrictions before installation rather than forcing an incompatible package. The published WASM package currently declares Linux support.
16
+
17
+ Reuse the bundled Node.js and pnpm paths from `load_workspace_dependencies` when available, or the user's configured Node.js and package manager. Permission to download the Office sidecar does not include installing Python, Node.js, or a package manager. If those prerequisites are missing, explain what is needed. Run the new installation's `dsoffice` entry, or its `lib/cli.js` with the selected Node executable, by path; do not assume installation changed the session's PATH. Check `dsoffice capabilities` before continuing the requested operation.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: office-docx
3
- description: Create, read, edit, and check Word documents (.docx), including reports, letters, and formatted tables. Use when a DOCX file is an input or requested deliverable. Load this skill before running Office commands. Use only bundled LibreOffice unless the user explicitly opts out; without that opt-out, do not search for another LibreOffice executable.
3
+ description: Create, read, edit, and validate Word documents (.docx).
4
4
  ---
5
5
 
6
6
  # Word documents
@@ -9,9 +9,11 @@ Use `python-docx` for DOCX creation and ordinary edits. Follow an explicit user
9
9
 
10
10
  Keep source scripts, intermediate files, and final documents in the task workspace; the runtime and this skill directory are read-only resources. Use the user's requested language and preserve an existing document's design unless a redesign is requested.
11
11
 
12
+ Name Python scripts for their task, such as `edit_docx.py`; avoid module names such as `inspect.py`, `docx.py`, or `zipfile.py` that shadow imported libraries.
13
+
12
14
  ## Create and edit
13
15
 
14
- For existing files, inspect paragraphs, runs, tables, sections, headers, and footers before changing the affected content. Save to a new file unless the user requests an in-place edit. Replacing a paragraph's `.text` destroys its run formatting; change the relevant runs when formatting must survive. Reconstructing the whole document can lose features outside python-docx's supported editing API.
16
+ For text edits, locate the target by its text or position and inspect only its runs and formatting. A title can be an ordinary paragraph without a Title style or document-property title. Update the relevant runs; assigning paragraph `.text` destroys run formatting. Save a new file unless an in-place edit is requested. Read other structures only when the edit involves them; rebuilding a document can lose unsupported features.
15
17
 
16
18
  Use paragraph styles for headings and body text. Size tables for the section that contains them, and account for merged cells and nested tables. Chinese, Japanese, and Korean text may need an explicit `w:eastAsia` font assignment in addition to `run.font.name`; font names alone do not establish glyph availability or rendered appearance.
17
19
 
@@ -27,31 +29,12 @@ document.add_paragraph("The requested findings go here.")
27
29
  document.save("report.docx")
28
30
  ```
29
31
 
30
- python-docx does not paginate or render documents. Do not represent ordinary replacement, colored text, or comments as tracked changes. When real revisions or unsupported OOXML features matter, preserve their package parts and verify the requested operation rather than silently discarding them.
32
+ python-docx does not paginate or render documents. Do not represent ordinary replacement, colored text, or comments as tracked changes. When real revisions or unsupported OOXML features matter, preserve their package parts and verify the requested operation rather than silently discarding them. When writing package parts directly, create a new ZIP and copy each unchanged member once; appending a replacement member duplicates its name and corrupts the package.
31
33
 
32
34
  ## Check and deliver
33
35
 
34
- Run the shared checker with the selected Python executable; `<skill-directory>` is this loaded skill's resource base:
35
-
36
- ```text
37
- <python> <skill-directory>/../scripts/check_office.py <document.docx> --out <checks.json>
38
- ```
39
-
40
- It checks ZIP/XML integrity and internal relationships, and reports paragraphs, logical table dimensions, and sections. Optional `--contains TEXT` arguments assert required text. A successful structural check does not verify pagination, clipping, fonts, or visual appearance. Compare the summary and reopened document with the user's request, including unchanged content that matters to an edit.
41
-
42
- Render for a requested image/PDF deliverable or an actionable layout check. Before generating images only for inspection, establish that the current model accepts images. If image input is unavailable, finish structural and content checks and state that visual layout was not inspected; do not generate unreadable previews, retry `read_image`, or choose a second model.
43
-
44
- Use the bundled LibreOffice binaries unless the user explicitly asks not to use them. Without that explicit opt-out, never search for or invoke system LibreOffice, `soffice`, an application-installed binary, or a downloaded replacement. If the bundled CLI is unavailable or fails, report that failure instead of substituting another executable.
45
-
46
- Use the `libreofficeKit.node` and `libreofficeKit.cli` absolute paths supplied in the loaded skill's **Installed LibreOffice Kit** section. Run Node with the CLI entry as its first argument, then `capabilities --json` once. For the bundled runtime, these paths work from the task directory; do not search PATH, guess a package directory, use `npx`, or install a renderer. Quote each path separately; PowerShell also requires the `&` invocation operator. The examples below use `<node>` and `<cli>` for those two returned paths. Direct rendering avoids an intermediate PDF:
47
-
48
- ```sh
49
- "<node>" "<cli>" render --input report.docx --output-dir preview-v1 --pages 1,3 --dpi 144
50
- "<node>" "<cli>" convert --input report.docx --output report.pdf
51
- ```
52
-
53
- Choose pages relevant to the requested edit; inspect all pages when whole-document layout is required. Batch selected pages in one call and use the returned manifest's page count, image paths, and missing-font diagnostics. Output directories and converted files must be new. Reuse images for the same saved document; render again only after a source change, for an unrendered page, or to diagnose a concrete failure. Check page breaks, clipped text, headings, and table widths. Do not repeatedly export unchanged documents or infer visual overflow solely from guessed text geometry.
36
+ Reopen the saved document with python-docx and verify the requested content, related formatting, and unchanged content that must be preserved. Deliver when these checks pass. Investigate package-level details only for a concrete failure, ambiguous edit target, unsupported feature, or explicit user requirement.
54
37
 
55
- If this deployment explicitly disables the CLI but `render_document` is provided, use it without `pages` first and read its returned images only on a ready result; `status: "skipped"` ends visual checking. If neither renderer is available or rendering fails, preserve the usable source and report the inspection limit. Do not search for COM automation, install conversion packages, or build another rendering pipeline for routine QA. LibreOffice pagination can differ from Microsoft Word. Once the requested content and applicable checks pass, deliver the document.
38
+ Creating a file or changing content with incidental basic formatting does not require additional operations. Load `office-render` for requested images or visual inspection. When styling, font color, spacing, sizing, or text alignment is the requested edit, load `office-render` and inspect the changed page even without a separate visual-check request, unless the user explicitly skips rendering. For PDF output load `office-export-pdf`.
56
39
 
57
40
  Call `present({"files":[{"path":"report.docx"}]})` with the actual final DOCX path. It exposes the current source file without copying or preserving its bytes, so keep that file in place and do not present temporary QA reports unless requested. If `present` is unavailable, provide the final workspace path using the session's supported file delivery method.
@@ -0,0 +1,18 @@
1
+ ---
2
+ name: office-export-pdf
3
+ description: Convert an Office document to a requested PDF with LibreOffice Kit.
4
+ ---
5
+
6
+ # Export an Office document to PDF
7
+
8
+ Use the installed LibreOffice Kit CLI through the shell with the absolute Node and CLI paths supplied below. In Bash, invoke `"<node>" "<cli>"`; in PowerShell, invoke `& "<node>" "<cli>"`. Prefer bundled LibreOffice unless the user requests another installation. If the Office sidecar is missing, follow [sidecar setup](../office/references/sidecar.md): ask whether the user wants to download it and install only after they agree. Do not invoke system soffice, use npx, or install a replacement to work around a failed conversion with an installed sidecar.
9
+
10
+ Convert the final saved Office file to a new PDF in the task workspace. Preserve the source. Quote each input and output path.
11
+
12
+ ```text
13
+ "<node>" "<cli>" convert --input <final-office-file> --output <new-file.pdf>
14
+ ```
15
+
16
+ Check the command result, nonempty output, and `%PDF-` file signature. If a PDF reader library is already available, compare page count and extractable text with the requested content. If it is unavailable, report the extent of validation; do not generate images or install extra tools to validate a conversion. Spreadsheet PDF pages follow workbook print settings; change those settings only when requested. Conversion may differ from the native application's pagination or fonts.
17
+
18
+ If export fails, preserve the usable source and report the export limitation. Do not repeatedly convert an unchanged source or use conversion to investigate a failed preview. For PDF export alone, stop after these checks. Only a request for images or visual inspection calls for `office-render`; load it before that separate operation. Deliver the requested PDF through `present` when available, keeping the file in place.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: office-pptx
3
- description: Create, read, edit, and check PowerPoint presentations (.pptx), including slide text, tables, images, and charts. Use when a PPTX file is an input or requested deliverable. Load this skill before running Office commands. Use only bundled LibreOffice unless the user explicitly opts out; without that opt-out, do not search for another LibreOffice executable.
3
+ description: Create, read, edit, and validate PowerPoint presentations (.pptx).
4
4
  ---
5
5
 
6
6
  # PowerPoint presentations
@@ -9,6 +9,8 @@ Follow an explicit user or applicable AGENTS.md requirement for an environment o
9
9
 
10
10
  Keep scripts and output files in the task workspace. The runtime and skill directory contain shared read-only resources. Match the requested slide language and the supplied presentation's design when editing it.
11
11
 
12
+ Name Python scripts for their task, such as `edit_pptx.py`; avoid module names such as `inspect.py`, `pptx.py`, or `zipfile.py` that shadow imported libraries.
13
+
12
14
  ## Create and edit
13
15
 
14
16
  Use `python-pptx` to inspect or modify an existing presentation. Inspect slide layouts, text runs, images, tables, and charts before editing. Change only the requested content, preserve mixed text formatting, and save to a new file unless the user requests an in-place edit. Rebuilding slides can discard unsupported animation, SmartArt, or other extension content.
@@ -51,31 +53,12 @@ for shape in presentation.slides[0].shapes:
51
53
  presentation.save("report-edited.pptx")
52
54
  ```
53
55
 
54
- Check that text and images fit the slide dimensions, titles form a useful sequence, and chart labels agree with source values. A native chart's embedded workbook is part of the deliverable and must contain the intended data. Library support for writing PPTX is not a rendering engine or a guarantee that every PowerPoint feature survives editing.
56
+ Check that text and images fit the slide dimensions, titles form a useful sequence, and chart labels agree with source values. Paragraph alignment does not move a text box: when a title must be centered on the slide, center both its paragraph and the text box against `presentation.slide_width`, then reopen to verify both. A native chart's embedded workbook is part of the deliverable and must contain the intended data. Library support for writing PPTX is not a rendering engine or a guarantee that every PowerPoint feature survives editing.
55
57
 
56
58
  ## Check and deliver
57
59
 
58
- Run the shared checker with the selected Python executable; `<skill-directory>` is this loaded skill's resource base:
59
-
60
- ```text
61
- <python> <skill-directory>/../scripts/check_office.py <presentation.pptx> --out <checks.json>
62
- ```
63
-
64
- It checks ZIP/XML integrity and internal relationships and reports slide count and extracted text. Use repeated `--contains TEXT` arguments for required slide text and `--count N` for a requested slide count; `--contains` excludes chart text and speaker notes. Reopen the file to check the requested edits, chart data, and notes. Structural success does not establish text fit, alignment, readable contrast, or rendering fidelity.
65
-
66
- Render for requested images/PDFs or an actionable layout check. Before generating images only for inspection, establish that the current model accepts images. If it does not, finish structural and content checks and state that visual layout was not inspected; do not rasterize slides the model cannot read, retry `read_image`, or choose a second model.
67
-
68
- Use the bundled LibreOffice binaries unless the user explicitly asks not to use them. Without that explicit opt-out, never search for or invoke system LibreOffice, `soffice`, an application-installed binary, or a downloaded replacement. If the bundled CLI is unavailable or fails, report that failure instead of substituting another executable.
69
-
70
- Use the `libreofficeKit.node` and `libreofficeKit.cli` absolute paths supplied in the loaded skill's **Installed LibreOffice Kit** section. Run Node with the CLI entry as its first argument, then `capabilities --json` once. For the bundled runtime, these paths work from the task directory; do not search PATH, guess a package directory, use `npx`, or install a renderer. Quote each path separately; PowerShell also requires the `&` invocation operator. The examples below use `<node>` and `<cli>` for those two returned paths. Render slides directly:
71
-
72
- ```sh
73
- "<node>" "<cli>" render --input report.pptx --output-dir preview-v1 --pages 1,3,4 --dpi 144
74
- "<node>" "<cli>" convert --input report.pptx --output report.pdf
75
- ```
76
-
77
- Batch affected slides in one call; inspect every slide for a new deck or a whole-deck design review. Read the returned manifest and selected image paths, including missing-font diagnostics. Outputs must be new directories/files. Retain and reuse previews for the same saved source; rerender only after edits, for missing slides, or to diagnose a concrete failure. Check clipping, alignment, contrast, and chart labels. Do not substitute guessed geometry for visual evidence or repeat successful exports of unchanged slides.
60
+ Reopen the saved presentation with python-pptx to verify the requested slide count, content, formatting, chart data, and notes, including unchanged content that must be preserved. These checks do not establish text fit, readable contrast, or rendering fidelity.
78
61
 
79
- If this deployment explicitly disables the CLI but `render_document` is provided, call it without `pages` first and inspect images only on a ready result; `status: "skipped"` ends visual checking. If rendering is unavailable or fails, retain the usable source and report the inspection limit. Do not search for COM automation, install conversion packages, or build another renderer for routine QA. LibreOffice previews do not certify pixel-identical PowerPoint or Keynote output, animation, or media playback. Deliver once the requested content and applicable checks pass.
62
+ Deliver after the requested edits are verified. Creating a file or changing content with incidental basic formatting does not require additional operations. Load `office-render` for requested images or visual inspection and for appearance-focused styling, spacing, sizing, or alignment changes unless the user explicitly skips rendering. For PDF output load `office-export-pdf`.
80
63
 
81
64
  Call `present({"files":[{"path":"report-edited.pptx"}]})` with the actual final PPTX path. It exposes the current source file without copying or preserving its bytes, so keep that file in place and leave intermediate images and reports out of delivery unless requested. If `present` is unavailable, use the session's supported file delivery method.
@@ -0,0 +1,18 @@
1
+ ---
2
+ name: office-recalculate
3
+ description: Refresh spreadsheet formula caches and verify calculated values with LibreOffice Kit.
4
+ ---
5
+
6
+ # Recalculate spreadsheet formulas
7
+
8
+ Use the installed LibreOffice Kit CLI through the shell with the absolute Node and CLI paths supplied below. In Bash, invoke `"<node>" "<cli>"`; in PowerShell, invoke `& "<node>" "<cli>"`. Prefer bundled LibreOffice unless the user requests another installation. If the Office sidecar is missing, follow [sidecar setup](../office/references/sidecar.md): ask whether the user wants to download it and install only after they agree. Do not invoke system soffice, use npx, or install a replacement to work around a failed recalculation with an installed sidecar.
9
+
10
+ Keep the original workbook and write a new file in the task workspace. Quote each input and output path.
11
+
12
+ ```text
13
+ "<node>" "<cli>" recalculate --input <workbook.xlsx> --output <recalculated.xlsx>
14
+ ```
15
+
16
+ Verify the saved result with openpyxl, using `load_workspace_dependencies` for the Python path unless a project environment is required. Open it with both `data_only=False` and `data_only=True`. Check that formulas survive and cached results match the requested logic, expected totals, and cell types. Check required unchanged sheets, styles, and data. Recalculation does not validate business logic or guarantee Excel-specific feature compatibility.
17
+
18
+ If no supported recalculation engine is available or the command fails, preserve the workbook and explain that calculated values could not be refreshed. Do not replace requested formulas with constants or present stale/absent caches as fresh results. Deliver the verified output through `present` when available, keeping the file in place.
@@ -0,0 +1,31 @@
1
+ ---
2
+ name: office-render
3
+ description: Render requested document images or inspect visual layout with LibreOffice Kit.
4
+ ---
5
+
6
+ # Document images and visual inspection
7
+
8
+ Use the installed LibreOffice Kit CLI through the shell with the absolute Node and CLI paths supplied below. In Bash, invoke `"<node>" "<cli>"`; in PowerShell, invoke `& "<node>" "<cli>"`. Prefer bundled LibreOffice unless the user requests another installation. If the Office sidecar is missing, follow [sidecar setup](../office/references/sidecar.md): ask whether the user wants to download it and install only after they agree. Do not invoke system soffice, use npx, install a replacement renderer, or build an alternative rendering pipeline to work around a failed render with an installed sidecar.
9
+
10
+ Before generating images only for inspection, establish that the current model accepts images. Otherwise finish the applicable content and structure checks and report the requested visual check as unavailable. Do not generate unreadable previews, retry image reads, or choose a second model. Requested image exports can still be delivered.
11
+
12
+ ## Render the saved deliverable
13
+
14
+ Keep the saved deliverable in the task workspace. For requested image exports, use the requested output directory and retain the images. For inspection-only previews, create a private, uniquely named temporary parent inside that workspace and pass a child path that does not yet exist as `--output-dir`; the CLI creates that child. On POSIX, use `preview_root=$(mktemp -d "./.dsh-office.XXXXXXXX")` (owner-only permissions) and `--output-dir "$preview_root/render"`. On PowerShell, create a parent named `.dsh-office-` plus `[guid]::NewGuid().ToString('N')` under the task workspace and pass an uncreated `render` child; it inherits the workspace ACL. Quote each input and output path. Retain the parent until image inspection finishes, then remove only that parent, including after a failed render. Never use a fixed directory name or remove the workspace itself.
15
+
16
+ ```text
17
+ "<node>" "<cli>" render --input <final-file> --output-dir <new-directory> --pages "1,3,4"
18
+ "<node>" "<cli>" render --input <final-workbook.xlsx> --output-dir <new-directory> --sheet <exact-name> --range <A1-range>
19
+ ```
20
+
21
+ For DOCX, PPTX, or PDF, batch requested pages/slides in one call. In PowerShell, quote comma-separated `--pages` values so they remain one argument. Inspect all pages only for a requested whole-document review. For worksheets, choose the exact affected range, including chart/drawing regions; worksheet numbers are not PDF page numbers. Inspect the final saved file, reuse existing images for an unchanged source, and rerender only after requested source changes or for previously unrendered regions. Change print areas or scaling only when the user requests print layout.
22
+
23
+ Read the returned JSON manifest for image paths, page counts, fragment rectangles, and missing-font diagnostics. Use the session's image-reading tool to inspect the requested images. Check clipping, alignment, headings, table widths, contrast, chart labels, and number formats as relevant to the task. A successful command or image read alone does not establish a usable preview; guessed text geometry does not establish overflow.
24
+
25
+ ## Failed or blank previews
26
+
27
+ For populated content, a fully transparent or uniformly blank image is a renderer failure. If a pixel check is needed, composite RGBA onto white first; raw RGB under transparent pixels can look nonblank. Check once and do not reread the same image. Stop visual checks for the task immediately after a blank result. This overrides the rerender-after-edit guidance above: do not render or read images again, even after editing or copying the source, or try another range, page, sheet, DPI, output format, diagnostic export, or print setting. Preserve the usable source and requested outputs and finish applicable content/data checks.
28
+
29
+ If content and structure checks pass, name the saved file and verified edits as needed, then include exactly one visual-status sentence: "Content and structure checks passed; visual inspection was not completed." in English, or "内容与结构检查通过;视觉检查未完成。" in Chinese. Do not add a render or preview bullet, explain why inspection stopped, or describe the image or pixels. Report content or structure failures separately; never say all checks passed or claim visual verification.
30
+
31
+ LibreOffice output can differ from Word, PowerPoint, or Excel. Images do not certify animation/media playback, refresh formula caches in the source workbook, or guarantee native application fidelity. Deliver requested files through `present` when available; keep the files in place. Do not deliver inspection images or reports unless requested.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: office-xlsx
3
- description: Read, create, and modify Excel workbooks (.xlsx), including data, formulas, formatting, and pandas analysis. Use for Excel inputs or deliverables. Load before running Office commands. Data and formula tasks skip visual inspection; inspect only for formatting or layout needs. Use only bundled LibreOffice unless the user explicitly opts out; without that opt-out, do not search for another LibreOffice executable.
3
+ description: Read, create, edit, and validate Excel workbooks, data, and formulas (.xlsx).
4
4
  ---
5
5
 
6
6
  # Excel workbooks
@@ -9,6 +9,8 @@ Follow an explicit user or applicable AGENTS.md requirement for an environment o
9
9
 
10
10
  Keep scripts, working files, and outputs in the task workspace. The runtime and skill directory are shared read-only resources. Save to a new workbook unless the user requests an in-place edit.
11
11
 
12
+ Name Python scripts for their task, such as `edit_xlsx.py`; avoid module names such as `inspect.py`, `openpyxl.py`, or `pandas.py` that shadow imported libraries.
13
+
12
14
  ## Read and modify
13
15
 
14
16
  Use `openpyxl` for existing XLSX workbooks and targeted cell or style changes. Load with `data_only=False` when formulas must survive. Inspect sheet names, the affected cell types, formulas, styles, merged ranges, and referenced ranges before editing. Check the saved file by reopening it, including unchanged content that the request requires preserving.
@@ -35,40 +37,16 @@ workbook.save("report.xlsx")
35
37
 
36
38
  Preserve numbers, dates, booleans, and identifiers as the intended cell types; formatting is not a type conversion. For modifications, load the existing file with `openpyxl.load_workbook("input.xlsx", data_only=False)`, change the requested ranges, and save a separate result. Avoid a DataFrame round trip when workbook features must survive.
37
39
 
38
- Writing a formula does not calculate its result. openpyxl and XlsxWriter do not evaluate Excel formulas; `data_only=True` returns stored cached values that may be absent or stale. Read the installed CLI paths and capabilities as described below. When it supports `recalculate`, use `"<node>" "<cli>" recalculate --input report.xlsx --output recalculated.xlsx` to save formulas and refreshed cached results to a new workbook. Reopen that result with both `data_only=False` and `data_only=True` to check formulas and calculated values against the requested logic. Recalculation does not validate business logic or guarantee Excel-specific feature compatibility. Without a recalculation engine, state the limit; do not replace requested formulas with constants or report old cached values as newly calculated results.
40
+ Writing a formula does not calculate its result. openpyxl and XlsxWriter do not evaluate formulas; `data_only=True` returns cached values that may be absent or stale. When calculated results must be refreshed, load `office-recalculate`. Do not replace requested formulas with constants or report stale values as newly calculated results.
39
41
 
40
42
  Do not rename `.xls`, `.xlsb`, encrypted files, or macro-enabled files to `.xlsx`. They need an appropriate supported operation. `keep_vba=True` can preserve VBA package content in an XLSM workflow, but does not execute or edit macros and does not guarantee every advanced workbook feature survives. Preserve the original and verify such requirements explicitly.
41
43
 
42
44
  ## Check and deliver
43
45
 
44
- Run the shared checker with the selected Python executable; `<skill-directory>` is this loaded skill's resource base:
45
-
46
- ```text
47
- <python> <skill-directory>/../scripts/check_office.py <workbook.xlsx> --out <checks.json>
48
- ```
49
-
50
- It checks ZIP/XML integrity and internal relationships, and reports sheet names, populated-cell counts, and formula counts. Repeated `--contains TEXT` arguments check string cells and sheet names, and `--count N` checks sheet count. `--contains` excludes numeric cells and does not validate formula results; verify those separately with openpyxl. The checker does not calculate formulas or judge workbook appearance. Compare relevant values, types, formulas, styles, and totals with the task's source data.
51
-
52
- For data and formula tasks, deliver after structural and data checks pass. Skip preview rendering and visual inspection, and do not add an unrequested caveat that visual inspection was omitted. Basic styling such as bold headers or number formats does not itself require visual inspection. Formula recalculation is independent of rendering and remains necessary when calculated results must be refreshed.
53
-
54
- Inspect relevant regions when the task concerns formatting, layout, chart appearance, print layout, or a known display problem. Before generating inspection images, establish that the current model accepts images. If it does not, finish structural and data checks and report the requested visual check as unavailable; do not rasterize unreadable previews, retry `read_image`, or choose a second model. Export requested images or PDFs even when visual inspection is unnecessary.
55
-
56
- A successful command or image read does not prove that a preview contains workbook content. For a populated range, a fully transparent or uniformly blank image is a renderer failure, not evidence that the workbook needs fixing. Stop all visual QA for that workbook immediately; do not try a wider range, another sheet, a different DPI, or another output format. Check suspicious previews with the bundled Pillow library, compositing RGBA onto white before comparing pixels. Do not create diagnostic workbooks, repeatedly retry rendering, modify print settings, or convert to PDF solely to investigate a failed preview. Preserve the requested workbook changes, finish structural and data checks, and report the visual check as unavailable. Only change print areas or scaling when the user requests printed layout.
57
-
58
- ## LibreOffice operations
59
-
60
- Use the bundled LibreOffice binaries unless the user explicitly asks not to use them. Without that explicit opt-out, never search for or invoke system LibreOffice, `soffice`, an application-installed binary, or a downloaded replacement. If the bundled CLI is unavailable or fails, report that failure instead of substituting another executable.
61
-
62
- When recalculation, a requested export, or visual inspection is needed, use the `libreofficeKit.node` and `libreofficeKit.cli` absolute paths supplied in the loaded skill's **Installed LibreOffice Kit** section. Run Node with the CLI entry as its first argument, then `capabilities --json` once. For the bundled runtime, these paths work from the task directory; do not search PATH, guess a package directory, use `npx`, or install a renderer. Quote each path separately; PowerShell also requires the `&` invocation operator. The examples below use `<node>` and `<cli>` for those two returned paths. For worksheet rendering, select an exact sheet name and the relevant cell range:
63
-
64
- ```sh
65
- "<node>" "<cli>" render --input report.xlsx --output-dir preview-v1 --sheet Summary --range A1:D20 --dpi 144
66
- ```
67
-
68
- For a user-requested PDF export, use `"<node>" "<cli>" convert --input report.xlsx --output report.pdf`. PDF conversion is not a fallback for failed visual QA.
46
+ Reopen the saved workbook with openpyxl and verify the requested sheet names, values, types, formulas, styles, and totals against the source data, including unchanged content that must be preserved. These checks do not calculate formulas or judge workbook appearance.
69
47
 
70
- Use `--sheet`/`--range` for worksheets, not `--pages`. Direct worksheet images cover cell regions; PDF pages follow print settings and are not worksheet numbers. Include chart/drawing regions explicitly. Read the manifest's image paths, fragment rectangles, and missing-font diagnostics; large regions may split across images. Outputs must be new directories/files. Reuse previews for the same saved workbook and inspect additional regions only when needed. Check widths, formats, clipping, and charts; set print areas/scaling when printed layout is requested. Render each relevant region once. Rerender only after requested source changes; an empty or failed preview ends visual checking for the whole workbook.
48
+ Load `office-render` for requested images or visual inspection and whenever the user's primary task targets appearance, including cell fill colors, text alignment, spacing, or sizing. These appearance changes require rendering even without a separate visual-check request, unless the user explicitly skips it. New workbooks and data or formula edits do not require rendering solely because they include incidental basic formatting. In particular, bold headers and number formats accompanying data edits do not require loading `office-render` or running its commands.
71
49
 
72
- When visual inspection is needed and this deployment explicitly disables the CLI but provides `render_document`, call it without `pages` first and inspect returned images only on a ready result; `status: "skipped"` ends visual checking. If required rendering is unavailable or fails, preserve the usable workbook and report the inspection or export limit. Do not search for COM automation, install conversion packages, or build another renderer for routine QA. Preview rendering does not refresh the original workbook's formula caches or certify native Excel appearance. Deliver once the requested data and applicable checks pass.
50
+ Load `office-recalculate` when refreshed calculated values are needed and `office-export-pdf` for PDF output. Deliver after the requested data checks pass and the required operations finish.
73
51
 
74
52
  Call `present({"files":[{"path":"report.xlsx"}]})` with the actual final workbook path. It exposes the current source file without copying or preserving its bytes, so keep that file in place and omit intermediate scripts and reports unless requested. If `present` is unavailable, use the session's supported file delivery method.
package/lib/index.js CHANGED
@@ -1,17 +1,21 @@
1
1
  import { readFileSync, statSync } from "node:fs";
2
2
  import { readFile } from "node:fs/promises";
3
- import { dirname, isAbsolute, join } from "node:path";
3
+ import { dirname, isAbsolute, join, sep } from "node:path";
4
4
  import { isSea } from "node:sea";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import z from "@deepseek-ai/schemastery";
7
7
  import { BUNDLED_SKILL_RANK } from "@deepseek-ai/dsh-skill";
8
8
  import { parse } from "yaml";
9
9
  //#region lib/types/index.js
10
- /** Bundled Office workflows and filesystem resources for document authoring and checks. */
10
+ /** Bundled Office workflows and filesystem resources for document authoring and operations. */
11
11
  const SKILL_NAMES = [
12
+ "office",
12
13
  "office-docx",
13
14
  "office-pptx",
14
- "office-xlsx"
15
+ "office-xlsx",
16
+ "office-render",
17
+ "office-recalculate",
18
+ "office-export-pdf"
15
19
  ];
16
20
  /** Validated resource configuration. */
17
21
  const Config = z.object({
@@ -54,8 +58,6 @@ function officeRuntime(config) {
54
58
  function apply(ctx, config = {}) {
55
59
  const assetRoot = config.assetRoot ?? fileURLToPath(new URL("../assets/", import.meta.url));
56
60
  if (!isAbsolute(assetRoot)) throw new Error("skill-office: assetRoot must be an absolute directory");
57
- if (!statSync(join(assetRoot, "scripts", "check_office.py")).isFile()) throw new Error("skill-office: assets must contain scripts/check_office.py");
58
- const runtime = officeRuntime(config);
59
61
  const candidates = SKILL_NAMES.map((skillName) => {
60
62
  const directory = join(assetRoot, skillName);
61
63
  const path = join(directory, "SKILL.md");
@@ -72,7 +74,7 @@ function apply(ctx, config = {}) {
72
74
  rank: BUNDLED_SKILL_RANK,
73
75
  resourceBase: {
74
76
  kind: "directory",
75
- path: directory
77
+ path: directory.split(sep).join("/")
76
78
  },
77
79
  locator: path
78
80
  };
@@ -82,13 +84,17 @@ function apply(ctx, config = {}) {
82
84
  list: () => Promise.resolve(candidates),
83
85
  async get(candidate, options) {
84
86
  const { rank: _rank, locator, ...summary } = candidate;
85
- const raw = await readFile(locator, {
87
+ const content = parseSkill(await readFile(locator, {
86
88
  encoding: "utf8",
87
89
  signal: options.signal
88
- });
90
+ }), locator).content + ([
91
+ "office-render",
92
+ "office-recalculate",
93
+ "office-export-pdf"
94
+ ].includes(candidate.name) ? officeRuntime(config) : "");
89
95
  return {
90
96
  ...summary,
91
- content: parseSkill(raw, locator).content + runtime
97
+ content
92
98
  };
93
99
  }
94
100
  };
@@ -1,9 +1,9 @@
1
- /** Bundled Office workflows and filesystem resources for document authoring and checks. */
1
+ /** Bundled Office workflows and filesystem resources for document authoring and operations. */
2
2
  import type { Context } from '@deepseek-ai/cordis';
3
3
  import z from '@deepseek-ai/schemastery';
4
4
  /** Office skill resource location. */
5
5
  export interface Config {
6
- /** Absolute assets directory containing the three skill folders and shared scripts; defaults to packaged assets. */
6
+ /** Absolute assets directory containing skill folders; defaults to packaged assets. */
7
7
  assetRoot?: string;
8
8
  /** Standalone Node executable; defaults to the current executable outside Electron and SEA. */
9
9
  node?: string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-skill-office",
3
3
  "description": "Bundled Word, PowerPoint, and Excel workflows and structural checks",
4
- "version": "0.2.1-alpha.1",
4
+ "version": "0.2.1-alpha.3",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -27,18 +27,18 @@
27
27
  ],
28
28
  "license": "MIT",
29
29
  "peerDependencies": {
30
- "@deepseek-ai/dsh-skill": "0.2.1-alpha.1",
31
- "@deepseek-ai/cordis": "~4.0.5-alpha.1"
30
+ "@deepseek-ai/dsh-skill": "0.2.1-alpha.3",
31
+ "@deepseek-ai/cordis": "~4.0.5-alpha.2"
32
32
  },
33
33
  "devDependencies": {
34
- "@deepseek-ai/dsh-skill": "0.2.1-alpha.1",
35
- "@deepseek-ai/cordis": "~4.0.5-alpha.1",
36
- "@deepseek-ai/cordis-plugin-loader": "~1.0.6-alpha.1",
37
- "@deepseek-ai/cordis-plugin-include": "~1.0.10-alpha.1"
34
+ "@deepseek-ai/dsh-skill": "0.2.1-alpha.3",
35
+ "@deepseek-ai/cordis": "~4.0.5-alpha.2",
36
+ "@deepseek-ai/cordis-plugin-loader": "~1.0.6-alpha.2",
37
+ "@deepseek-ai/cordis-plugin-include": "~1.0.10-alpha.2"
38
38
  },
39
39
  "dependencies": {
40
40
  "@deepseek-ai/libreoffice-kit": "^0.1.5",
41
- "yaml": "^2.4.2",
42
- "@deepseek-ai/schemastery": "~3.18.5-alpha.1"
41
+ "@deepseek-ai/schemastery": "~3.18.5-alpha.2",
42
+ "yaml": "^2.4.2"
43
43
  }
44
44
  }
@@ -1,279 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Read-only OOXML checks and structural summaries; no rendering or formula evaluation.
3
-
4
- Run with INPUT.docx, INPUT.pptx, or INPUT.xlsx. Optional --contains assertions
5
- check extracted text; DOCX excludes comments, glossary text, and unreferenced parts or notes.
6
- --count checks slides or sheets. JSON escapes non-ASCII
7
- characters and goes to stdout and optionally --out. Exit 0 means the requested
8
- structural checks passed, 1 means a document, assertion, or report write failed, and 2 means
9
- invalid command-line arguments.
10
- """
11
-
12
- from __future__ import annotations
13
-
14
- import argparse
15
- import json
16
- import posixpath
17
- import sys
18
- import zipfile
19
- import zlib
20
- from pathlib import Path
21
- from urllib.parse import unquote, urlsplit
22
- from xml.etree import ElementTree as ET
23
-
24
- W_NAMESPACES = (
25
- "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
26
- "http://purl.oclc.org/ooxml/wordprocessingml/main",
27
- )
28
- A_NAMESPACES = (
29
- "http://schemas.openxmlformats.org/drawingml/2006/main",
30
- "http://purl.oclc.org/ooxml/drawingml/main",
31
- )
32
- P_NAMESPACES = (
33
- "http://schemas.openxmlformats.org/presentationml/2006/main",
34
- "http://purl.oclc.org/ooxml/presentationml/main",
35
- )
36
- S_NAMESPACES = (
37
- "http://schemas.openxmlformats.org/spreadsheetml/2006/main",
38
- "http://purl.oclc.org/ooxml/spreadsheetml/main",
39
- )
40
- R_NAMESPACES = (
41
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
42
- "http://purl.oclc.org/ooxml/officeDocument/relationships",
43
- )
44
- MAIN_PARTS = {
45
- ".docx": ("word/document.xml", "wordprocessingml.document.main+xml"),
46
- ".pptx": ("ppt/presentation.xml", "presentationml.presentation.main+xml"),
47
- ".xlsx": ("xl/workbook.xml", "spreadsheetml.sheet.main+xml"),
48
- }
49
-
50
-
51
- def namespace(root: ET.Element, supported: tuple[str, ...], part: str) -> str:
52
- """Return the main XML namespace after checking its OOXML variant."""
53
- uri = root.tag[1:].split("}", 1)[0] if root.tag.startswith("{") else ""
54
- if uri not in supported:
55
- raise ValueError(f"{part} uses unsupported XML namespace: {uri or '(none)'}")
56
- return "{" + uri + "}"
57
-
58
-
59
- def relationship_id(node: ET.Element, part: str) -> str:
60
- """Read an office-document relationship id from Transitional or Strict OOXML."""
61
- for uri in R_NAMESPACES:
62
- value = node.get("{" + uri + "}id")
63
- if value is not None:
64
- return value
65
- raise ValueError(f"{part} has a reference without a relationship id")
66
-
67
-
68
- def relationship_types(kind: str) -> set[str]:
69
- """Return the Transitional and Strict relationship type names for one role."""
70
- return {f"{uri}/{kind}" for uri in R_NAMESPACES}
71
-
72
-
73
- def iter_namespaces(root: ET.Element, namespaces: tuple[str, ...], local_name: str):
74
- """Iterate matching elements across Transitional and Strict namespaces."""
75
- for uri in namespaces:
76
- yield from root.iter("{" + uri + "}" + local_name)
77
-
78
-
79
- def relationship_target(part: str, target: str) -> str:
80
- """Resolve a package relationship without fetching external resources."""
81
- path = unquote(urlsplit(target).path)
82
- return posixpath.normpath(path.lstrip("/") if path.startswith("/") else posixpath.join(posixpath.dirname(part), path))
83
-
84
-
85
- def relationships(part: str, xml: dict[str, ET.Element], types: set[str] | None = None) -> dict[str, str]:
86
- path = posixpath.join(posixpath.dirname(part), "_rels", posixpath.basename(part) + ".rels")
87
- root = xml.get(path)
88
- if root is None:
89
- return {}
90
- return {
91
- rel.attrib["Id"]: relationship_target(part, rel.attrib["Target"])
92
- for rel in root if rel.get("TargetMode") != "External" and (types is None or rel.get("Type") in types)
93
- }
94
-
95
-
96
- def related_xml(part: str, reference: str, links: dict[str, str], xml: dict[str, ET.Element]) -> ET.Element:
97
- """Read a related XML part with diagnostics naming its source and reference."""
98
- if reference not in links:
99
- raise ValueError(f"{part} references missing relationship: {reference}")
100
- target = links[reference]
101
- if target not in xml:
102
- raise ValueError(f"{part} relationship {reference} targets a non-XML member: {target}")
103
- return xml[target]
104
-
105
-
106
- def inspect_docx(xml: dict[str, ET.Element]) -> tuple[dict, str]:
107
- part = "word/document.xml"
108
- root = xml[part]
109
- w = namespace(root, W_NAMESPACES, part)
110
- body = root.find(f"{w}body")
111
- if body is None:
112
- raise ValueError("word/document.xml has no document body")
113
- tables = []
114
- for table in body.iter(f"{w}tbl"):
115
- grid = table.findall(f"{w}tblGrid/{w}gridCol")
116
- rows = table.findall(f"{w}tr")
117
- # Merged cells span logical grid columns; counting physical cells loses them.
118
- columns = len(grid) if grid else max((sum(
119
- int(cell.find(f"{w}tcPr/{w}gridSpan").get(f"{w}val", "1"))
120
- if cell.find(f"{w}tcPr/{w}gridSpan") is not None else 1
121
- for cell in row.findall(f"{w}tc")
122
- ) for row in rows), default=0)
123
- tables.append({"rows": len(rows), "columns": columns})
124
- sections = []
125
- for section in body.iter(f"{w}sectPr"):
126
- size = section.find(f"{w}pgSz")
127
- margins = section.find(f"{w}pgMar")
128
- sections.append({
129
- "page_twips": {} if size is None else {key.removeprefix(w): value for key, value in size.attrib.items()},
130
- "margins_twips": {} if margins is None else {key.removeprefix(w): value for key, value in margins.attrib.items()},
131
- })
132
- text_parts = [body]
133
- for kind in ("header", "footer"):
134
- links = relationships(part, xml, relationship_types(kind))
135
- for section in body.iter(f"{w}sectPr"):
136
- for reference in section.findall(f"{w}{kind}Reference"):
137
- text_parts.append(related_xml(part, relationship_id(reference, part), links, xml))
138
- for kind in ("footnote", "endnote"):
139
- references = {node.attrib[f"{w}id"] for node in body.iter(f"{w}{kind}Reference")}
140
- if not references:
141
- continue
142
- links = relationships(part, xml, relationship_types(f"{kind}s"))
143
- for reference in links:
144
- tree = related_xml(part, reference, links, xml)
145
- text_parts.extend(note for note in tree.findall(f"{w}{kind}") if note.get(f"{w}id") in references)
146
- text = "\n".join("".join(node.text or "" for node in paragraph.iter(f"{w}t")) for tree in text_parts
147
- for paragraph in tree.iter(f"{w}p"))
148
- return {"paragraphs": len(list(body.iter(f"{w}p"))), "tables": tables, "sections": sections}, text
149
-
150
-
151
- def inspect_pptx(xml: dict[str, ET.Element]) -> tuple[dict, str]:
152
- part = "ppt/presentation.xml"
153
- links = relationships(part, xml)
154
- root = xml[part]
155
- p = namespace(root, P_NAMESPACES, part)
156
- slides = root.findall(f"{p}sldIdLst/{p}sldId")
157
- texts = []
158
- for slide in slides:
159
- reference = relationship_id(slide, part)
160
- tree = related_xml(part, reference, links, xml)
161
- texts.append("\n".join("".join(node.text or "" for node in iter_namespaces(paragraph, A_NAMESPACES, "t"))
162
- for paragraph in iter_namespaces(tree, A_NAMESPACES, "p")))
163
- return {"slides": len(slides)}, "\n".join(texts)
164
-
165
-
166
- def inspect_xlsx(xml: dict[str, ET.Element]) -> tuple[dict, str]:
167
- part = "xl/workbook.xml"
168
- links = relationships(part, xml)
169
- root = xml[part]
170
- s = namespace(root, S_NAMESPACES, part)
171
- sheets = []
172
- texts = []
173
- shared = xml.get("xl/sharedStrings.xml")
174
- shared_s = s if shared is None else namespace(shared, S_NAMESPACES, "xl/sharedStrings.xml")
175
- shared_strings = [] if shared is None else [
176
- "".join(node.text or "" for node in item.iter(f"{shared_s}t")) for item in shared.iter(f"{shared_s}si")
177
- ]
178
- for sheet in root.findall(f"{s}sheets/{s}sheet"):
179
- reference = relationship_id(sheet, part)
180
- tree = related_xml(part, reference, links, xml)
181
- sheet_s = namespace(tree, S_NAMESPACES, links[reference])
182
- cells = list(tree.iter(f"{sheet_s}c"))
183
- formulas = sum(cell.find(f"{sheet_s}f") is not None for cell in cells)
184
- sheets.append({"name": sheet.attrib["name"], "cells": len(cells), "formulas": formulas})
185
- for cell in cells:
186
- if cell.get("t") == "s":
187
- value = cell.findtext(f"{sheet_s}v", "")
188
- location = f"{links[reference]} cell {cell.get('r', '(no reference)')}"
189
- try:
190
- index = int(value)
191
- except ValueError as error:
192
- raise ValueError(f"{location}: invalid shared string index {value!r}") from error
193
- if not 0 <= index < len(shared_strings):
194
- raise ValueError(f"{location}: shared string index out of range: {index}")
195
- texts.append(shared_strings[index])
196
- texts.extend("".join(node.text or "" for node in cell.iter(f"{sheet_s}t")) for cell in cells)
197
- texts.extend(cell.findtext(f"{sheet_s}v", "") for cell in cells if cell.get("t") == "str")
198
- texts.append(sheet.attrib["name"])
199
- return {"sheets": sheets, "formulas_evaluated": False}, "\n".join(texts)
200
-
201
-
202
- def inspect(path: Path) -> tuple[dict, str]:
203
- """Validate package members and relationships before inspecting the main part."""
204
- main, content_type = MAIN_PARTS[path.suffix.lower()]
205
- with zipfile.ZipFile(path) as archive:
206
- members = archive.namelist()
207
- if len(members) != len(set(members)):
208
- raise ValueError("ZIP contains duplicate member names")
209
- corrupt = archive.testzip()
210
- if corrupt is not None:
211
- raise ValueError(f"ZIP member failed its CRC check: {corrupt}")
212
- xml = {name: ET.fromstring(archive.read(name)) for name in members
213
- if name.endswith((".xml", ".rels"))}
214
- if main not in xml:
215
- raise ValueError(f"missing main part: {main}")
216
- types = xml.get("[Content_Types].xml")
217
- if types is None or not any(node.get("PartName") == "/" + main
218
- and node.get("ContentType", "").endswith(content_type) for node in types):
219
- raise ValueError(f"[Content_Types].xml does not declare {main} as {path.suffix.lower()}")
220
- for name, tree in xml.items():
221
- if not name.endswith(".rels"):
222
- continue
223
- source = "" if name == "_rels/.rels" else posixpath.join(posixpath.dirname(posixpath.dirname(name)), posixpath.basename(name)[:-5])
224
- for rel in tree:
225
- if rel.get("TargetMode") == "External":
226
- continue
227
- target = relationship_target(source, rel.attrib["Target"])
228
- if target not in members:
229
- raise ValueError(f"{name} references missing package member: {target}")
230
- if path.suffix.lower() == ".docx":
231
- return inspect_docx(xml)
232
- if path.suffix.lower() == ".pptx":
233
- return inspect_pptx(xml)
234
- return inspect_xlsx(xml)
235
-
236
-
237
- def main() -> int:
238
- parser = argparse.ArgumentParser(description=__doc__)
239
- parser.add_argument("input", type=Path)
240
- parser.add_argument("--out", type=Path)
241
- parser.add_argument("--contains", action="append", default=[], metavar="TEXT")
242
- parser.add_argument("--count", type=int, help="expected slide or sheet count")
243
- args = parser.parse_args()
244
- if args.input.suffix.lower() not in MAIN_PARTS:
245
- parser.error("input must be .docx, .pptx, or .xlsx; converting the filename does not convert its contents")
246
- if args.count is not None and (args.count < 0 or args.input.suffix.lower() == ".docx"):
247
- parser.error("--count must be non-negative and applies only to slides or sheets")
248
- if args.out is not None and args.out.resolve() == args.input.resolve():
249
- parser.error("--out must differ from the input document")
250
- checks = []
251
- summary = {}
252
- try:
253
- summary, text = inspect(args.input)
254
- checks.append({"id": "package", "status": "pass"})
255
- for required in args.contains:
256
- checks.append({"id": "contains", "status": "pass" if required in text else "fail", "text": required})
257
- if args.count is not None:
258
- actual = summary.get("slides", len(summary.get("sheets", [])))
259
- checks.append({"id": "count", "status": "pass" if actual == args.count else "fail", "expected": args.count, "actual": actual})
260
- except (OSError, ValueError, KeyError, RuntimeError, ET.ParseError, zipfile.BadZipFile, zlib.error) as error:
261
- checks.append({"id": "package", "status": "fail", "detail": str(error)})
262
- failed = any(check["status"] == "fail" for check in checks)
263
- report = {"format": args.input.suffix.lower()[1:], "verdict": "fail" if failed else "pass", "checks": checks, "summary": summary}
264
- output = json.dumps(report, ensure_ascii=True, indent=2) + "\n"
265
- if args.out is not None:
266
- try:
267
- args.out.parent.mkdir(parents=True, exist_ok=True)
268
- args.out.write_text(output, encoding="utf-8")
269
- except OSError as error:
270
- failed = True
271
- report["verdict"] = "fail"
272
- checks.append({"id": "output", "status": "fail", "detail": str(error)})
273
- output = json.dumps(report, ensure_ascii=True, indent=2) + "\n"
274
- sys.stdout.write(output)
275
- return 1 if failed else 0
276
-
277
-
278
- if __name__ == "__main__":
279
- sys.exit(main())