dsh-office-kit 0.3.3 → 0.4.1
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/CAPABILITY.md +23 -20
- package/README.md +1 -1
- package/assets/skills/office-documents/SKILL.md +1 -1
- package/assets/skills/office-documents/examples/end_to_end_smoke_test.md +1 -1
- package/assets/skills/office-documents/render_docx.py +10 -5
- package/assets/skills/office-documents/scripts/render_and_diff.py +1 -1
- package/assets/skills/office-documents/tasks/verify_render.md +4 -4
- package/assets/skills/office-documents/troubleshooting/libreoffice_headless.md +14 -15
- package/assets/skills/office-pdf/SKILL.md +1 -1
- package/assets/skills/office-presentations/SKILL.md +8 -7
- package/assets/skills/office-presentations/container_tools/render_slides.py +5 -5
- package/assets/skills/office-presentations/tasks/inspect-and-verify.md +5 -6
- package/assets/skills/office-spreadsheets/SKILL.md +3 -3
- package/assets/skills/office-spreadsheets/container_tools/recalc_gate.py +5 -5
- package/assets/skills/office-spreadsheets/tasks/recalc-and-check.md +3 -3
- package/assets/skills/shared/annotation-recipes.md +6 -7
- package/assets/skills/shared/check_runtime.py +19 -14
- package/assets/skills/shared/common-workflow.md +3 -3
- package/dist/index.js +107 -68
- package/package.json +2 -1
- package/postinstall.mjs +26 -10
- package/python/office_cli.py +331 -273
package/CAPABILITY.md
CHANGED
|
@@ -93,32 +93,34 @@ never guessed.
|
|
|
93
93
|
|
|
94
94
|
## Renderer dependency
|
|
95
95
|
|
|
96
|
-
- `docx`, `pptx` and `xlsx` rendering
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
`
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
96
|
+
- `docx`, `pptx` and `xlsx` rendering goes through the LibreOffice kit that
|
|
97
|
+
DeepSeek Harness ships (`@deepseek-ai/libreoffice-kit`: native engine on Windows
|
|
98
|
+
and macOS, WASM on Linux). No system Office, WPS or LibreOffice installation is
|
|
99
|
+
probed or used. `render` names it in `renderer` (`libreoffice-kit`) and the
|
|
100
|
+
engine in `engine` (`native` / `wasm`). When the kit cannot be found the
|
|
101
|
+
command exits non-zero: `the LibreOffice renderer is not available: the DSH
|
|
102
|
+
LibreOffice kit was not found`.
|
|
103
|
+
- Locating the kit: walk up from the plugin directory to
|
|
104
|
+
`node_modules/@deepseek-ai/libreoffice-kit/lib/cli.js`; override with
|
|
105
|
+
`DSH_LIBREOFFICE_KIT_CLI` (and `DSH_NODE` for the node binary).
|
|
106
|
+
- `xlsx recalc` uses the same kit (`recalculate`), on every platform, and reports
|
|
107
|
+
`renderer`, `engine` and the count of cached values it filled in.
|
|
108
|
+
- Render reports `missing_fonts`, `glyph_fallback` and a `warnings` list; a
|
|
109
|
+
missing font is reported instead of silently producing boxes. `--pdf` also
|
|
110
|
+
exports a PDF through the kit.
|
|
104
111
|
- `pdf render` needs no external renderer; PyMuPDF rasterises it directly.
|
|
105
112
|
- The PPTX pack's `container_tools/preview_render.py` is a geometry-only draft:
|
|
106
113
|
it redraws shapes, text boxes and images from the package with PyMuPDF and
|
|
107
114
|
labels itself `draft-preview` / `geometry-only`. It proves placement, not
|
|
108
115
|
typography (bullet glyphs are not drawn), and `render_slides.py` prefers the
|
|
109
116
|
real renderer whenever one exists.
|
|
110
|
-
- **This machine renders through WPS Office.** `office_status` reports
|
|
111
|
-
`kingsoft_wps: {writer, presentation, spreadsheet}` from `wps.exe`/`wpp.exe`/
|
|
112
|
-
`et.exe` under `%LOCALAPPDATA%\Kingsoft\WPS Office\<version>\office6`;
|
|
113
|
-
`docx`/`pptx`/`xlsx render` return `"renderer": "kingsoft-wps"`, and `xlsx
|
|
114
|
-
recalc` fills cached values through `Ket.Application`.
|
|
115
117
|
- `assets/skills/shared/scripts/ensure_raster_image.py` turns SVG, a chosen PDF
|
|
116
118
|
page, or a raster file into a PNG (PyMuPDF for vectors, Pillow for rasters), so
|
|
117
119
|
artwork can be embedded and then looked at. It never overwrites its input.
|
|
118
120
|
- `assets/skills/shared/check_runtime.py` imports every package from
|
|
119
121
|
`python/requirements.txt` and runs `<cli> status`; it exits non-zero on a
|
|
120
|
-
missing package or broken CLI, and 4 with `--require-renderer` when
|
|
121
|
-
|
|
122
|
+
missing package or broken CLI, and 4 with `--require-renderer` when the kit is
|
|
123
|
+
unavailable (otherwise only a warning).
|
|
122
124
|
|
|
123
125
|
## Verification evidence
|
|
124
126
|
|
|
@@ -127,10 +129,10 @@ never guessed.
|
|
|
127
129
|
| Packages on the runtime venv (3.13.12, `~/.dsh/office/runtime/venv`) | 10/10 import — python-docx 1.2.0, openpyxl 3.1.5, python-pptx 1.0.2, pypdf 6.21.1, reportlab 4.5.1, pdfplumber 0.11.9, PyMuPDF 1.28.2, Pillow 11.3.0, latex2mathml 3.81.1, mathml2omml 0.0.2 |
|
|
128
130
|
| `check_runtime.py` (venv / `--require-renderer` / bare `py -3.14`) | exit 0 + renderer warning / exit 4 / exit 1 listing the 8 missing packages and the pip remedy |
|
|
129
131
|
| DOCX create / read / `replace_text.py` / `append_blocks.py` | ok — counts returned, styles preserved, `replacements: 1`, `appended: 1`, equation intact |
|
|
130
|
-
| DOCX / PPTX render | ok through
|
|
132
|
+
| DOCX / PPTX render | ok through the kit — `renderer: libreoffice-kit`, `engine: native`, PNGs opened and checked |
|
|
131
133
|
| PPTX create / read / `add_slides.py` | ok — 3 slides, texts and charts read back |
|
|
132
134
|
| XLSX create / read / `set_cells.py` | ok — formula read back uncached, one cell written |
|
|
133
|
-
| XLSX recalc | ok through
|
|
135
|
+
| XLSX recalc | ok through the kit — `renderer: libreoffice-kit`, `engine: native`, formula cells gained cached values |
|
|
134
136
|
| `ensure_raster_image.py` | ok — 120×60 SVG at `--dpi 300` → 500×250, `--page` on a 2-page PDF, `--width 400` upscale, JSON errors on missing/unwritable input |
|
|
135
137
|
| `excel-live-control/container_tools/live_excel.py` | ok against WPS — attach refusal with remediation when nothing runs; `write_range` A1:B3 (6 cells), `set_formula` C1, `read_range` values `[[10,20,30],[30,40,null],[50,60,null]]`, `save` 9,268 bytes, `--diagnose` lists the workbook/sheet, `close --quit-app` refuses a dirty book, `--discard` leaves zero `et.exe` |
|
|
136
138
|
| PDF create / read / render / `metadata.py` / `page_ops.py` / `redact_region.py` | ok — CJK font applied, metadata written, merge→remove→rotate, region redaction removed 170 chars and 11 drawings, `page-1.png` produced |
|
|
@@ -138,7 +140,7 @@ never guessed.
|
|
|
138
140
|
| Forbidden-vocabulary grep over the delivered docs | empty |
|
|
139
141
|
|
|
140
142
|
Not claimed (no evidence here): installer or self-heal timings, live GUI
|
|
141
|
-
interaction (`/` menu, chip, sidebar preview, annotation overlay), and macOS
|
|
143
|
+
interaction (`/` menu, chip, sidebar preview, annotation overlay), and macOS
|
|
142
144
|
renderer support.
|
|
143
145
|
|
|
144
146
|
## Explicitly out of scope
|
|
@@ -150,5 +152,6 @@ renderer support.
|
|
|
150
152
|
so); OCR and scanned-PDF text extraction.
|
|
151
153
|
- PPTX animations, speaker notes, slide masters; XLSX pivots, macros, data
|
|
152
154
|
validation, conditional formatting — no spec keys or verbs in the helper.
|
|
153
|
-
- Formula evaluation without
|
|
154
|
-
|
|
155
|
+
- Formula evaluation without the DSH LibreOffice kit; macOS/Linux installation
|
|
156
|
+
was exercised in CI (Linux runs the WASM engine), but only Windows was checked
|
|
157
|
+
by hand.
|
package/README.md
CHANGED
|
@@ -37,7 +37,7 @@ dsh plugin add dsh-office-kit
|
|
|
37
37
|
- 📊 **电子表格**:`/spreadsheets` 创建、读取和编辑 XLSX,支持公式、样式、图表、冻结窗格与重算。
|
|
38
38
|
- 🖍️ **侧边栏标注**:在 PDF、DOC/DOCX、PPTX、XLSX 预览中吸附段落或框选区域,批量提交修改要求。
|
|
39
39
|
- 🔗 **会话引用**:通过 Chip 调用文档技能;注释链接可悬停查看文件名与要求,PDF 标注附选区截图。
|
|
40
|
-
- 🖼️ **渲染校验**:DOCX/PPTX/XLSX 经
|
|
40
|
+
- 🖼️ **渲染校验**:DOCX/PPTX/XLSX 经 DeepSeek Harness 自带的 LibreOffice Kit(Windows/macOS 原生引擎,Linux 走 WASM)栅格化,PDF 直接用 PyMuPDF;缺失字体会在报告里点名,不再静默产出豆腐块。SVG/PDF 素材可用 `ensure_raster_image.py` 转成 PNG 后嵌入。
|
|
41
41
|
- 🧮 **活体表格**:`excel-live-control` 技能通过 COM 驱动已经打开的 Excel / WPS 表格(读区、写值、样式、图表、重算、保存)。
|
|
42
42
|
|
|
43
43
|
## 🚀 快速开始
|
|
@@ -45,7 +45,7 @@ Rendering is the only proof that a document looks right.
|
|
|
45
45
|
The command writes a PDF plus one PNG per page and prints the paths. Open each PNG with `read_image` and check: heading hierarchy, table width and cell wrapping, page breaks, header/footer, clipped or overflowing text, missing glyphs (dashes, CJK, symbols), equation rendering, and caption placement.
|
|
46
46
|
|
|
47
47
|
- Iterate until the pages look correct, then deliver. "It parses" is not verification.
|
|
48
|
-
- If the runtime reports no renderer (
|
|
48
|
+
- If the runtime reports no renderer (`renderers.can_render.docx: false` — the DSH LibreOffice kit is missing), ask the user to fix the install, or fall back to `docx read` plus the audit scripts. Say plainly that layout was not visually verified — do not describe a page you did not see.
|
|
49
49
|
- After a script edits XML, re-render. A package can stay valid while the page it produces is broken.
|
|
50
50
|
|
|
51
51
|
## Choosing a route
|
|
@@ -71,7 +71,7 @@ Audits may exit 1 with findings — that is a report, not a failure of the envir
|
|
|
71
71
|
"$PY" "$R/render_docx.py" "$W/with-toc.docx" --output_dir "$W/render" --dpi 150
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
-
Then open each PNG with `read_image`. Without
|
|
74
|
+
Then open each PNG with `read_image`. Without the DSH LibreOffice kit the command exits 3 with `no_renderer`; that is a setup problem, not a document problem, and the rest of the test still stands.
|
|
75
75
|
|
|
76
76
|
## 6. Deliver
|
|
77
77
|
|
|
@@ -5,11 +5,11 @@ Rendering is the only honest check that a document looks right: XML can be valid
|
|
|
5
5
|
and still overlap, clip or reflow badly. This script keeps the plumbing in one
|
|
6
6
|
place so tasks can say "render it, look at the images" and mean a single command.
|
|
7
7
|
|
|
8
|
-
The render verb needs
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
The render verb needs the DSH LibreOffice kit. When the kit cannot be found the
|
|
9
|
+
script reports exactly that and exits 3, because a placeholder image would be
|
|
10
|
+
worse than no image.
|
|
11
11
|
|
|
12
|
-
Exit codes: 0 rendered, 1 render failed, 3 no renderer
|
|
12
|
+
Exit codes: 0 rendered, 1 render failed, 3 no renderer available, 2 bad input.
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
15
|
from __future__ import annotations
|
|
@@ -62,6 +62,8 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
62
62
|
command = ["docx", "render", "--input", str(source), "--out-dir", str(out_dir)]
|
|
63
63
|
if args.dpi:
|
|
64
64
|
command += ["--dpi", str(args.dpi)]
|
|
65
|
+
if args.emit_pdf:
|
|
66
|
+
command += ["--pdf"]
|
|
65
67
|
code, stdout, stderr = kit.run_office_cli(command)
|
|
66
68
|
payload = kit.cli_json(stdout) or kit.cli_json(stderr) or {}
|
|
67
69
|
pages = payload.get("pages") or []
|
|
@@ -72,7 +74,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
72
74
|
lines = [line for line in (stderr or stdout or "").splitlines() if line.strip() not in ("", "{", "}")]
|
|
73
75
|
detail = lines[-1].strip() if lines else "render failed"
|
|
74
76
|
if "renderer" in detail.lower() or "libreoffice" in detail.lower():
|
|
75
|
-
kit.emit({"ok": False, "reason": "no_renderer", "detail": detail, "remedy": "Install
|
|
77
|
+
kit.emit({"ok": False, "reason": "no_renderer", "detail": detail, "remedy": "Install this plugin inside the DeepSeek Harness tree so @deepseek-ai/libreoffice-kit is available, or set DSH_LIBREOFFICE_KIT_CLI, then re-run this command."})
|
|
76
78
|
return 3
|
|
77
79
|
kit.emit({"ok": False, "reason": "render_failed", "detail": detail})
|
|
78
80
|
return 1
|
|
@@ -86,6 +88,9 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
86
88
|
"pages": pages,
|
|
87
89
|
"page_count": len(pages),
|
|
88
90
|
"renderer": payload.get("renderer"),
|
|
91
|
+
"missing_fonts": payload.get("missing_fonts") or [],
|
|
92
|
+
"glyph_fallback": bool(payload.get("glyph_fallback")),
|
|
93
|
+
"warnings": payload.get("warnings") or [],
|
|
89
94
|
}
|
|
90
95
|
if args.emit_pdf and payload.get("pdf"):
|
|
91
96
|
result["pdf"] = payload["pdf"]
|
|
@@ -82,7 +82,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
82
82
|
if code_a != 0 or code_b != 0 or not pages_a or not pages_b:
|
|
83
83
|
detail = detail_a or detail_b or "render failed"
|
|
84
84
|
reason = "no_renderer" if ("renderer" in detail.lower() or "libreoffice" in detail.lower()) else "render_failed"
|
|
85
|
-
kit.emit({"ok": False, "reason": reason, "detail": detail, "remedy": "Install
|
|
85
|
+
kit.emit({"ok": False, "reason": reason, "detail": detail, "remedy": "Install this plugin inside the DeepSeek Harness tree so @deepseek-ai/libreoffice-kit is available, or set DSH_LIBREOFFICE_KIT_CLI, then re-run."})
|
|
86
86
|
return 3 if reason == "no_renderer" else 1
|
|
87
87
|
|
|
88
88
|
pairs = list(zip(pages_a, pages_b))
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"<python>" "<cli>" docx render --input "<file.docx>" --out-dir "<render-dir>"
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
Writes
|
|
11
|
+
Writes one PNG per page and prints their paths plus the renderer it used (`libreoffice-kit`); add `--pdf` to also keep a PDF. Use the same command through `render_docx.py` when you want the JSON report and page list:
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
14
|
"<python>" "<skill>/render_docx.py" "<file.docx>" --output_dir "<render-dir>" --dpi 150
|
|
@@ -27,12 +27,12 @@ Open every PNG with `read_image` and check, in this order:
|
|
|
27
27
|
|
|
28
28
|
## When no renderer exists
|
|
29
29
|
|
|
30
|
-
The runtime reports `renderers.
|
|
30
|
+
The runtime reports `renderers.can_render.docx: false` because the DSH LibreOffice kit was not found. `render` then exits non-zero with a JSON error naming the missing renderer, and `render_docx.py` exits 3 with `reason: no_renderer`.
|
|
31
31
|
|
|
32
32
|
In that state:
|
|
33
33
|
|
|
34
34
|
- Verify structure with `docx read`, `docx_kit.py parts|text|xml` and the audit scripts.
|
|
35
|
-
- Tell the user plainly that layout was not visually verified, and
|
|
35
|
+
- Tell the user plainly that layout was not visually verified, and that the fix is a harness install that ships the kit (the plugin injects `DSH_LIBREOFFICE_KIT_CLI` when it loads), not installing Word/LibreOffice.
|
|
36
36
|
- Do not claim a file "looks good" from XML evidence alone.
|
|
37
37
|
|
|
38
38
|
## After any script edit
|
|
@@ -46,5 +46,5 @@ Re-render. Package-valid is not page-valid: a caption inserted before the paragr
|
|
|
46
46
|
## Pitfalls
|
|
47
47
|
|
|
48
48
|
- Different renderers paginate differently. Compare like with like, and do not promise Word-perfect pagination from a LibreOffice render.
|
|
49
|
-
- A render fails silently into a blank page when a font is missing; a blank page is a finding, not a rendering bug.
|
|
49
|
+
- A render fails silently into a blank page when a font is missing; a blank page is a finding, not a rendering bug. `missing_fonts` / `glyph_fallback` in the render JSON tells you substitution happened.
|
|
50
50
|
- Render into a fresh directory; stale PNGs from an earlier run are the classic false pass.
|
|
@@ -6,31 +6,30 @@
|
|
|
6
6
|
"<python>" "<cli>" status
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
Look at `renderers.
|
|
9
|
+
Look at `renderers.kit` and `renderers.can_render`. `can_render.docx: false` means `docx render` will fail — not with a corrupt file, but with `the LibreOffice renderer is not available`. That is a setup problem, not a document problem.
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## What renders
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
Rendering goes through the LibreOffice kit that DeepSeek Harness ships (`@deepseek-ai/libreoffice-kit`). On macOS and Windows it uses the kit's native engine; on Linux it uses the WASM engine. A separately installed Microsoft Office, WPS Office, or system LibreOffice is **not** used and does not make rendering work on its own.
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
soffice --headless --version
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
If the binary is not on `PATH`, the runtime cannot find it; either add its `program` directory to `PATH` or install into the default location the runtime probes. On Windows, `soffice.com` is the console wrapper — use that one, not `soffice.exe`, when a script needs to wait for exit.
|
|
15
|
+
The plugin resolves the kit in this order:
|
|
20
16
|
|
|
21
|
-
|
|
17
|
+
1. `DSH_LIBREOFFICE_KIT_CLI` — the host half sets it (and `DSH_NODE`) when it loads, so every shell the agent spawns inherits it. That is what makes a profile install work, where the kit lives in the harness tree and not next to the plugin.
|
|
18
|
+
2. The harness entry point (`process.argv[1]`, e.g. `<dsh>/node_modules/@deepseek-ai/dsh/lib/bin.js`) → resolves the kit from the tree the harness itself loaded.
|
|
19
|
+
3. Walking up from the plugin directory through `node_modules/@deepseek-ai/libreoffice-kit/lib/cli.js` — covers a checkout or a plugin-local dependency.
|
|
22
20
|
|
|
23
|
-
|
|
21
|
+
Running the helper by hand outside the harness means setting `DSH_LIBREOFFICE_KIT_CLI` yourself; `DSH_NODE` overrides the node binary if `node` is not on `PATH`.
|
|
24
22
|
|
|
25
23
|
## Common failures
|
|
26
24
|
|
|
27
25
|
| Message | Cause | Fix |
|
|
28
26
|
|---|---|---|
|
|
29
|
-
| `
|
|
30
|
-
|
|
|
31
|
-
|
|
|
27
|
+
| `the LibreOffice renderer is not available` | Kit not found | Run through the plugin (the host injects the path), or set `DSH_LIBREOFFICE_KIT_CLI` to the kit's `lib/cli.js` |
|
|
28
|
+
| `cannot start the LibreOffice kit` / `EEXIST` | Interrupted install, or a stale staging directory | Re-run the plugin postinstall; delete leftover `.kit-render-*` directories |
|
|
29
|
+
| `Unsupported LibreOfficeKit host` | Unsupported platform/arch (the kit ships Windows x64/arm64, macOS x64/arm64, Linux via WASM) | Render on a supported host |
|
|
30
|
+
| `warning: N font(s) used by this file are not installed` | Font substitution — the render succeeded but that text may reflow or show boxes | Install the font or substitute it deliberately, then re-render |
|
|
31
|
+
| Blank pages | Missing font | See the font warning above |
|
|
32
32
|
| Text shifted | Font substitution | Compare with a render from the same renderer the user will use |
|
|
33
|
-
| `soffice already running` | A user profile lock from a crashed run | Remove the stale lock file under the LibreOffice user profile, or wait for the old process to exit |
|
|
34
33
|
|
|
35
34
|
## Working without a renderer
|
|
36
35
|
|
|
@@ -38,7 +37,7 @@ Structure, text and metadata are all verifiable headlessly (`docx read`, the aud
|
|
|
38
37
|
|
|
39
38
|
1. Do the structural verification.
|
|
40
39
|
2. Tell the user that page layout was not visually checked, and why.
|
|
41
|
-
3.
|
|
40
|
+
3. Ask them to fix the kit install, or open the file and say what looks wrong.
|
|
42
41
|
|
|
43
42
|
Never describe a page you did not render.
|
|
44
43
|
|
|
@@ -42,7 +42,7 @@ Decide the route before you type a command. Every route ends in the same render
|
|
|
42
42
|
| --- | --- | --- |
|
|
43
43
|
| `{"ok": false, ...}` and non-zero exit | Invalid argument, missing file, or a refused overwrite | Read the `error` field, fix what it names, re-run. Never strip a step to make it pass. |
|
|
44
44
|
| `encrypted` in the error | The file needs a password | Ask the user for a decrypted copy; do not guess passwords or write one into a command. |
|
|
45
|
-
| `
|
|
45
|
+
| `the LibreOffice renderer is not available` | Media conversion for docx/pptx/xlsx is unavailable because the DSH LibreOffice kit was not found | PDF rendering still works (PyMuPDF); for a PDF this is harmless. For other formats report that appearance could not be checked. |
|
|
46
46
|
| `leftover_chars_in_rect` greater than 0 | The redaction rectangle missed text | Enlarge `--pad` or reconvert the region; re-render before delivering. |
|
|
47
47
|
| `not_applied` is not empty | A form value did not persist after save | Treat as a failure: check the field name, its allowed states, and whether the widget has an appearance stream. |
|
|
48
48
|
| `chars: 0` on a page that shows text | Scanned page, no text layer | Say so; follow `references/limits.md` for the OCR situation. Do not return an empty extraction as success. |
|
|
@@ -62,10 +62,10 @@ Render, look at every page, then deliver — in that order, with nothing skipped
|
|
|
62
62
|
"<python>" "<skill>/container_tools/render_slides.py" --input "<file.pptx>" --out-dir "<render-dir>"
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
-
`render_slides.py` uses the CLI's `pptx render` — which
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
`render_slides.py` uses the CLI's `pptx render` — which renders through the DSH
|
|
66
|
+
LibreOffice kit — and falls back to a draft preview when the kit is missing, so
|
|
67
|
+
this one command is always the right entry point. The JSON names the engine it
|
|
68
|
+
used in `engine` (`libreoffice-kit` means a faithful render).
|
|
69
69
|
|
|
70
70
|
Then open each PNG with `read_image` (or build one sheet with
|
|
71
71
|
`"<skill>/template_following_scripts/make_contact_sheet.py"`). Check: titles
|
|
@@ -149,9 +149,10 @@ Guidance: [authoring workflow](references/authoring-workflow.md) ·
|
|
|
149
149
|
|
|
150
150
|
## Genuine gaps
|
|
151
151
|
|
|
152
|
-
-
|
|
153
|
-
use `--engine draft` and say the check was geometric.
|
|
154
|
-
fixes it
|
|
152
|
+
- The DSH LibreOffice kit missing (`renderers.can_render.pptx: false`) → no true
|
|
153
|
+
render; use `--engine draft` and say the check was geometric. A harness install
|
|
154
|
+
that ships the kit fixes it (the plugin injects `DSH_LIBREOFFICE_KIT_CLI` when it
|
|
155
|
+
loads); a standalone Office/LibreOffice install does not.
|
|
155
156
|
- Fonts the user's machine lacks are substituted by any renderer; the pack
|
|
156
157
|
cannot install fonts.
|
|
157
158
|
- Google Slides import fidelity is out of our control; see
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"""Render a deck to PNGs, one per slide, using whatever engine is available.
|
|
2
2
|
|
|
3
|
-
Purpose: the verification loop needs pixels. Ask the plugin CLI first: it
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
Purpose: the verification loop needs pixels. Ask the plugin CLI first: it renders
|
|
4
|
+
through the DSH LibreOffice kit. When that is unavailable, fall back to the draft
|
|
5
|
+
geometry renderer in `preview_render.py` and say so loudly in the JSON — a draft
|
|
6
|
+
preview proves placement, not typography.
|
|
7
7
|
|
|
8
8
|
Inputs may be a .pptx or a .pdf. Output is `<out-dir>/slide-NN.png` for every
|
|
9
9
|
page, plus `<out-dir>/render.json`.
|
|
@@ -105,7 +105,7 @@ def render(input_path: str | Path, out_dir: str | Path, dpi: int = 110, engine:
|
|
|
105
105
|
notes.append(f"office render failed: {exc}")
|
|
106
106
|
|
|
107
107
|
if engine == "office":
|
|
108
|
-
return {"ok": False, "engine": "office", "notes": notes, "error": {"code": "no_engine", "message": "
|
|
108
|
+
return {"ok": False, "engine": "office", "notes": notes, "error": {"code": "no_engine", "message": "the DSH LibreOffice renderer is not available (see office_status)"}}
|
|
109
109
|
|
|
110
110
|
from preview_render import render_preview
|
|
111
111
|
|
|
@@ -84,12 +84,11 @@ title.
|
|
|
84
84
|
|
|
85
85
|
## 6. When nothing can render
|
|
86
86
|
|
|
87
|
-
With
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
approximate render is not.
|
|
87
|
+
With the DSH LibreOffice kit missing (`office_status` → `renderers.kit` /
|
|
88
|
+
`renderers.can_render`), `render_slides.py` falls back to `--engine draft`, a
|
|
89
|
+
PyMuPDF geometry preview. It shows placement, overflow and overlap — not
|
|
90
|
+
typography or theme chrome. Say so when reporting: "verified as a geometry
|
|
91
|
+
preview" is honest, "looks great" from an approximate render is not.
|
|
93
92
|
|
|
94
93
|
If even the preview cannot run, the delivery is unverified: report the failing
|
|
95
94
|
command, the file's slide count from `pptx read`, and the gate results, and tell
|
|
@@ -4,7 +4,7 @@ description: Create, inspect, edit, recalculate, and verify Excel workbooks (.xl
|
|
|
4
4
|
|
|
5
5
|
# Spreadsheets (.xlsx)
|
|
6
6
|
|
|
7
|
-
A workbook is cells: each one holds a value, a format, and possibly a formula *string*. openpyxl writes all three and evaluates none of them, so a formula you just wrote has no cached result and `xlsx read` shows `=SUM(B2:B9)` where the user expects `1,950,400`. The calculation happens in an engine —
|
|
7
|
+
A workbook is cells: each one holds a value, a format, and possibly a formula *string*. openpyxl writes all three and evaluates none of them, so a formula you just wrote has no cached result and `xlsx read` shows `=SUM(B2:B9)` where the user expects `1,950,400`. The calculation happens in an engine — the DSH LibreOffice via `xlsx recalc` (cross-platform), or whatever app the user opens the file in. Everything below follows from that: write values and formulas, then verify structure by reading back, then verify numbers by recalculating, then verify appearance by rendering.
|
|
8
8
|
|
|
9
9
|
| User job | What is really happening | Fast path |
|
|
10
10
|
| --- | --- | --- |
|
|
@@ -52,8 +52,8 @@ For xlsx the payload is `{ target, fileName, text: "", requirement, region, imag
|
|
|
52
52
|
Three checks, in this order, after every mutation:
|
|
53
53
|
|
|
54
54
|
1. **Structure and values.** `"<python>" "<cli>" xlsx read --input "<file>"` proves the cells you wrote are where you wrote them; `container_tools/inspect_workbook.py` proves charts, tables, conditional formats, names, and merges survived.
|
|
55
|
-
2. **Numbers.** If any formula changed or was added, run `"<python>" "<cli>" xlsx recalc --input "<file>" --out "<file>"` and then `container_tools/recalc_gate.py --input "<file>"`.
|
|
56
|
-
3. **Appearance.** `"<python>" "<cli>" xlsx render --input "<file>" --out-dir "<render-dir>"`, then `read_image` each PNG. Check titles and labels, `###` columns, clipped numbers, charts cut off by the print area, plots with no categories, legends naming `Series 1`, conditional formatting that never fired, and blank pages. The render goes through
|
|
55
|
+
2. **Numbers.** If any formula changed or was added, run `"<python>" "<cli>" xlsx recalc --input "<file>" --out "<file>"` and then `container_tools/recalc_gate.py --input "<file>"`. The kit recalculates on every platform; only when the kit itself is missing does the recalc fail, and `recalc_gate.py` then reports `unverified` — say that plainly instead of presenting an empty cache as a result.
|
|
56
|
+
3. **Appearance.** `"<python>" "<cli>" xlsx render --input "<file>" --out-dir "<render-dir>"`, then `read_image` each PNG. Check titles and labels, `###` columns, clipped numbers, charts cut off by the print area, plots with no categories, legends naming `Series 1`, conditional formatting that never fired, and blank pages. The render goes through the DSH LibreOffice kit; when it is missing the command fails explicitly: report the file as structurally verified but visually unchecked, and offer to rebuild from a spec.
|
|
57
57
|
|
|
58
58
|
Two or three render/fix passes are normal. If a defect survives three, say what still looks wrong.
|
|
59
59
|
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""Gate a workbook on formulas that actually resolve.
|
|
3
3
|
|
|
4
|
-
Recomputes the file through the helper CLI (`xlsx recalc`, which drives
|
|
5
|
-
|
|
4
|
+
Recomputes the file through the helper CLI (`xlsx recalc`, which drives the DSH
|
|
5
|
+
LibreOffice kit on every platform), then classifies every formula cell: a real
|
|
6
6
|
value, a spreadsheet error, or no cached value at all. Also flags formulas that
|
|
7
7
|
point at sheets the workbook does not contain, and formulas whose text already
|
|
8
8
|
carries a `#REF!` literal.
|
|
9
9
|
|
|
10
|
-
Without
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
Without the kit the recomputation cannot happen; `--no-recalc` then inspects the
|
|
11
|
+
cached values a previous producer left in the file instead of failing outright. A
|
|
12
|
+
file whose formulas carry no cached value at all cannot be certified.
|
|
13
13
|
|
|
14
14
|
Exit codes:
|
|
15
15
|
0 verified: every formula resolved to a value
|
|
@@ -10,9 +10,9 @@ every formula cell got a value and none of them is an error.
|
|
|
10
10
|
"<python>" "<skill>/container_tools/recalc_gate.py" --input "<book-recalculated.xlsx>"
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
- `recalc` needs
|
|
14
|
-
|
|
15
|
-
and changes nothing.
|
|
13
|
+
- `recalc` needs the DSH LibreOffice kit (the same renderer used for images);
|
|
14
|
+
it runs on Windows, macOS and Linux. Without the kit the verb fails with a
|
|
15
|
+
plain message and changes nothing.
|
|
16
16
|
- `recalc_gate.py` runs `recalc` for you when given `--cli`: it takes
|
|
17
17
|
`--input`, writes `<stem>-recalculated.xlsx` unless `--out` is given, then
|
|
18
18
|
scans every formula cell.
|
|
@@ -126,9 +126,8 @@ Choose the edit that the helper actually supports:
|
|
|
126
126
|
rebuild as a surgical edit.
|
|
127
127
|
|
|
128
128
|
Verify with `<cli> pdf render --input "report-edited.pdf" --out-dir "render"` and
|
|
129
|
-
`read_image render/page-1.png`. `pdf render` uses PyMuPDF and works without
|
|
130
|
-
LibreOffice
|
|
131
|
-
renderer.
|
|
129
|
+
`read_image render/page-1.png`. `pdf render` uses PyMuPDF and works without the
|
|
130
|
+
LibreOffice kit, so a PDF annotation is never blocked by a missing renderer.
|
|
132
131
|
|
|
133
132
|
## pptx — select the shapes that overlap the region
|
|
134
133
|
|
|
@@ -234,10 +233,10 @@ Forbidden, because both produce plausible-looking edits in the wrong place:
|
|
|
234
233
|
its own zoom and scroll;
|
|
235
234
|
- naming a sheet the screenshot does not show or `xlsx read` cannot match.
|
|
236
235
|
|
|
237
|
-
Visual confirmation needs `xlsx render`, which requires LibreOffice
|
|
238
|
-
without
|
|
239
|
-
results stay uncalculated
|
|
240
|
-
|
|
236
|
+
Visual confirmation needs `xlsx render`, which requires the DSH LibreOffice kit;
|
|
237
|
+
without it, say the change is verified structurally but not visually. Formula
|
|
238
|
+
results stay uncalculated until `xlsx recalc` runs (the kit recalculates on any
|
|
239
|
+
platform); do not present openpyxl's empty cache as a computed value.
|
|
241
240
|
|
|
242
241
|
## When to stop and ask
|
|
243
242
|
|
|
@@ -33,8 +33,8 @@ for _stream in (sys.stdout, sys.stderr):
|
|
|
33
33
|
|
|
34
34
|
DEFAULT_ROOT = Path(__file__).resolve().parents[3]
|
|
35
35
|
RENDERER_HINT = (
|
|
36
|
-
"docx/pptx/xlsx rendering needs LibreOffice,
|
|
37
|
-
"pdf rendering
|
|
36
|
+
"docx/pptx/xlsx rendering needs the DSH LibreOffice kit, which ships with DeepSeek Harness; "
|
|
37
|
+
"pdf rendering needs only PyMuPDF"
|
|
38
38
|
)
|
|
39
39
|
|
|
40
40
|
|
|
@@ -122,18 +122,19 @@ def cli_status(python: Path, cli: Path, timeout: float) -> dict:
|
|
|
122
122
|
def renderer_summary(report: dict) -> dict:
|
|
123
123
|
"""Reduce the CLI renderer block to a simple availability answer."""
|
|
124
124
|
renderers = report.get("renderers") or {}
|
|
125
|
-
|
|
126
|
-
|
|
125
|
+
kit = renderers.get("kit") or {}
|
|
126
|
+
can_render = renderers.get("can_render") or {}
|
|
127
127
|
available = {
|
|
128
|
-
"
|
|
129
|
-
|
|
130
|
-
|
|
128
|
+
"kit": {
|
|
129
|
+
"available": bool(kit.get("available")),
|
|
130
|
+
"backend": kit.get("backend") or None,
|
|
131
|
+
"version": kit.get("version") or None,
|
|
132
|
+
"error": kit.get("error") or None,
|
|
133
|
+
"remedy": kit.get("remedy") or None,
|
|
134
|
+
},
|
|
135
|
+
"can_render": {k: bool(can_render.get(k)) for k in ("docx", "pptx", "xlsx", "pdf")},
|
|
131
136
|
}
|
|
132
|
-
available["any"] = (
|
|
133
|
-
bool(available["libreoffice"])
|
|
134
|
-
or any(available["microsoft_office"].values())
|
|
135
|
-
or any(available["kingsoft_wps"].values())
|
|
136
|
-
)
|
|
137
|
+
available["any"] = bool(available["can_render"]["docx"])
|
|
137
138
|
return available
|
|
138
139
|
|
|
139
140
|
|
|
@@ -244,8 +245,12 @@ def emit(args: argparse.Namespace, report: dict) -> None:
|
|
|
244
245
|
print(f" [{mark}] {item['distribution']:<16} {detail}{modules}")
|
|
245
246
|
if "renderers" in report:
|
|
246
247
|
r = report["renderers"]
|
|
247
|
-
|
|
248
|
-
|
|
248
|
+
kit = r["kit"]
|
|
249
|
+
detail = f"backend={kit['backend'] or 'none'}"
|
|
250
|
+
if kit["error"]:
|
|
251
|
+
detail += f"; error={kit['error']}"
|
|
252
|
+
print(f"renderer libreoffice-kit available={kit['available']} {detail}; "
|
|
253
|
+
f"can_render={r['can_render']}")
|
|
249
254
|
for warning in report["warnings"]:
|
|
250
255
|
print(f"warning {warning}")
|
|
251
256
|
if report.get("error"):
|
|
@@ -15,7 +15,7 @@ appended to the end of the active `SKILL.md`. It is a fenced JSON block:
|
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
- `python` and `cli` are verified absolute paths. Use them verbatim and quote
|
|
18
|
-
them in commands. Never call a bare `python`, `py`,
|
|
18
|
+
them in commands. Never call a bare `python`, `py`, or `pdftoppm`.
|
|
19
19
|
- `skill` is the directory of the format pack; its scripts live under
|
|
20
20
|
`<skill>/scripts/`.
|
|
21
21
|
- `shared` is this directory (workflow and annotation recipes).
|
|
@@ -125,10 +125,10 @@ a blank, clipped or miscoloured raster is invisible inside the host file.
|
|
|
125
125
|
| --- | --- | --- |
|
|
126
126
|
| No `## Office runtime` JSON; skill says *No working Python runtime is available* | The plugin cannot install or find a compatible interpreter | Do not run helper commands, do not write a document by hand. Give the user the remedy command from the skill body, ask them to run it, then call `office_status` again. |
|
|
127
127
|
| `office_status` shows `cli.exists: false`, or `python`/`cli` file not found | The bundled helper or its install is incomplete | Report the missing path. Do not substitute another tool that produces a different file. |
|
|
128
|
-
| `render` fails: `
|
|
128
|
+
| `render` fails: `the LibreOffice renderer is not available` | The kit that DSH ships with was not found; create/read/edit still work but docx/pptx/xlsx cannot be rasterised | Do not claim you inspected it. Say the file was built and validated structurally (read verb), but the renderer is unavailable on this host so you could not check its appearance. The kit ships with DeepSeek Harness and the plugin injects its path (`DSH_LIBREOFFICE_KIT_CLI`) at load time, so this means the harness install lacks the kit or the variable points nowhere; a standalone LibreOffice/Office/WPS install does not help. `pdf render` still works. If the user needs visual confidence now, offer to rebuild from a spec or to present the unverified file explicitly labelled as such. |
|
|
129
129
|
| Annotation region does not land on anything, or the payload says `unmapped: true` | The drawn region cannot be tied to an object in the file | Do not silently shift the region or guess a cell/shape. Say what you could identify (preview page, text, resolved cells) and which item you could not locate, then ask the user for the text or cell. |
|
|
130
130
|
| `{"ok": false, "error": "..."}` and non-zero exit | The spec was rejected (unknown key, bad type, missing file) | Read the error text, fix the spec key it names, re-run the same command. Never strip a failing block to make the command pass — that silently deletes user content. |
|
|
131
|
-
| `xlsx recalc` fails
|
|
131
|
+
| `xlsx recalc` fails because the LibreOffice kit is missing | openpyxl cannot evaluate formulas, and the kit is the only calculation engine this plugin uses | Report formula cells as uncalculated; do not invent computed values. Fix the renderer first (see the row above) and re-run. |
|
|
132
132
|
| The helper has no verb for what the requirement needs | The common path is short; the capability is not limited to it | Take the pack script route, or write a short snippet against `python-docx` / `openpyxl` / `python-pptx` / `pypdf` / `PyMuPDF` / `reportlab`, run it with `<python>`, then `read` the file back and render. Never refuse a job because a verb is missing. |
|
|
133
133
|
| `read` returns an empty structure for a file the user supplied | Unsupported or corrupt input | Say what failed and stop; do not overwrite the user's file with an empty one. |
|
|
134
134
|
|