dsh-office-kit 0.3.2 → 0.4.0

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 CHANGED
@@ -93,32 +93,34 @@ never guessed.
93
93
 
94
94
  ## Renderer dependency
95
95
 
96
- - `docx`, `pptx` and `xlsx` rendering needs an office suite: Microsoft Office
97
- (Word/PowerPoint/Excel over COM), WPS Office (`KWps`/`KWpp`/`Ket` over COM) or
98
- LibreOffice (headless conversion). `render` names the one it used in its
99
- `renderer` field (`microsoft-office`, `kingsoft-wps`, `libreoffice`). With none
100
- installed it exits non-zero: `no document renderer found: install LibreOffice,
101
- Microsoft Office, or WPS Office`.
102
- - `xlsx recalc` needs a real calculation engine: Excel or WPS Spreadsheets over
103
- COM. It reports `renderer` and the count of cached values it filled in.
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 no renderer
121
- is installed (a missing renderer is otherwise only a warning).
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 WPS — `renderer: kingsoft-wps`, DOCX 2 pages, PPTX 3 pages, PNGs opened and checked |
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 WPS — `renderer: kingsoft-wps`, 34 cached values filled, `A3 = 7` after `CalculateFull` |
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/Linux
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 Microsoft Excel or WPS Spreadsheets; macOS/Linux
154
- installation and renderer behaviour (only Windows was exercised).
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 经 Microsoft Office、WPS Office 或 LibreOffice 导出 PDF 再栅格化,PDF 直接用 PyMuPDF;SVG/PDF 素材可用 `ensure_raster_image.py` 转成 PNG 后嵌入。
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 (none of Microsoft Word, WPS Writer, or LibreOffice), ask the user to install one, 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.
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 a renderer the command exits 3 with `no_renderer`; that is the expected result on a machine with neither LibreOffice nor Word, and the rest of the test still stands.
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 a real Word engine (Microsoft Word, WPS Writer, or
9
- LibreOffice). When none is installed the script reports exactly that and exits 3,
10
- because a placeholder image would be worse than no image.
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 installed, 2 bad input.
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 Microsoft Office, WPS Office, or LibreOffice, then re-run this command."})
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 Microsoft Office, WPS Office, or LibreOffice, then re-run."})
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 a PDF and one PNG per page, and prints their paths plus the renderer it used (Word via COM, or LibreOffice). Use the same command through `render_docx.py` when you want the JSON report and page list:
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.libreoffice: null` and no Office installation. `render` then exits non-zero with a JSON error naming the missing renderer, and `render_docx.py` exits 3 with `reason: no_renderer`.
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 offer to install Microsoft Office, WPS Office, or LibreOffice instead of describing a page you did not see.
35
+ - Tell the user plainly that layout was not visually verified, and that the fix is installing the plugin inside the harness tree (or setting `DSH_LIBREOFFICE_KIT_CLI`), 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,28 @@
6
6
  "<python>" "<cli>" status
7
7
  ```
8
8
 
9
- Look at `renderers.libreoffice` and `microsoft_office`. Both null means `docx render` will fail — not with a corrupt file, but with `no document renderer found`. That is a setup problem, not a document problem.
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
- ## LibreOffice
11
+ ## What renders
12
12
 
13
- Install LibreOffice (Windows: winget `TheDocumentFoundation.LibreOffice`), then check:
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
- ```bash
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.
20
-
21
- ## Microsoft Office
15
+ The plugin finds the kit by walking up from its own directory through `node_modules/@deepseek-ai/libreoffice-kit/lib/cli.js`. So:
22
16
 
23
- When Word is installed, the CLI can drive it through COM and does not need LibreOffice. Office must be activated and able to launch without a first-run dialog; a pending activation or a modal "sign in" blocks the render forever.
17
+ - Installed inside the harness tree → the kit is found automatically.
18
+ - Installed anywhere else → install the plugin where the harness can see its dependencies, or set `DSH_LIBREOFFICE_KIT_CLI` to the kit's `lib/cli.js`.
19
+ - `DSH_NODE` overrides the node binary if `node` is not on `PATH`.
24
20
 
25
21
  ## Common failures
26
22
 
27
23
  | Message | Cause | Fix |
28
24
  |---|---|---|
29
- | `no document renderer found` | Neither installed / not on PATH | Install one, re-check `status` |
30
- | Render hangs | A dialog is waiting in the headless process | Kill the leftover process, add the `--headless --norestore` flags the CLI uses |
31
- | Blank pages | Missing font | Install the font or substitute it |
25
+ | `the LibreOffice renderer is not available` | Kit not found from the plugin directory | Put the plugin inside the harness tree, or set `DSH_LIBREOFFICE_KIT_CLI` |
26
+ | `cannot start the LibreOffice kit` / `EEXIST` | Interrupted install, or a stale staging directory | Re-run the plugin postinstall; delete leftover `.kit-render-*` directories |
27
+ | `Unsupported LibreOfficeKit host` | Unsupported platform/arch (the kit ships Windows x64/arm64, macOS x64/arm64, Linux via WASM) | Render on a supported host |
28
+ | `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 |
29
+ | Blank pages | Missing font | See the font warning above |
32
30
  | 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
31
 
35
32
  ## Working without a renderer
36
33
 
@@ -38,7 +35,7 @@ Structure, text and metadata are all verifiable headlessly (`docx read`, the aud
38
35
 
39
36
  1. Do the structural verification.
40
37
  2. Tell the user that page layout was not visually checked, and why.
41
- 3. Offer the install, or ask them to open the file and say what looks wrong.
38
+ 3. Ask them to fix the kit install, or open the file and say what looks wrong.
42
39
 
43
40
  Never describe a page you did not render.
44
41
 
@@ -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
- | `no document renderer found: install LibreOffice, Microsoft Office, or WPS Office` | Media conversion for docx/pptx/xlsx is unavailable | PDF rendering still works (PyMuPDF); for a PDF this is harmless. For other formats report that appearance could not be checked. |
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 drives Microsoft
66
- PowerPoint, WPS Presentation, or LibreOffice, whichever is installed — and falls
67
- back to a draft preview when none is, so this one command is always the right
68
- entry point. The JSON names the engine it used in `engine`.
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
- - No Microsoft Office, WPS Office, or LibreOffice installed → no true render;
153
- use `--engine draft` and say the check was geometric. Installing any of them
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. Installing the
154
+ plugin inside the harness tree (or setting `DSH_LIBREOFFICE_KIT_CLI`) fixes it;
155
+ 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 drives
4
- Microsoft Office, WPS Office, or LibreOffice, whichever is installed. When none
5
- is, fall back to the draft geometry renderer in `preview_render.py` and say so
6
- loudly in the JSON — a draft preview proves placement, not typography.
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": "no Microsoft Office, WPS Office, or LibreOffice renderer is available"}}
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 no Microsoft Office, WPS Office, or LibreOffice installed
88
- (`office_status` → `renderers.kingsoft_wps` / `microsoft_office` / `libreoffice`),
89
- `render_slides.py` falls back to `--engine draft`, a PyMuPDF geometry preview. It
90
- shows placement, overflow and overlap — not typography or theme chrome. Say so
91
- when reporting: "verified as a geometry preview" is honest, "looks great" from an
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 — Microsoft Excel or WPS Spreadsheets via `xlsx recalc`, 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.
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>"`. On a host without Microsoft Excel or WPS Spreadsheets the recalc fails and `recalc_gate.py` 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 Excel, WPS Spreadsheets, or LibreOffice; with none installed it fails explicitly: report the file as structurally verified but visually unchecked, and offer to rebuild from a spec. WPS in particular needs its print area and page setup right before exporting, so a layout complaint may be the export settings rather than the sheet.
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 Microsoft
5
- Excel or WPS Spreadsheets over COM), then classifies every formula cell: a real
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 a calculation engine the recomputation cannot happen; `--no-recalc` then
11
- inspects the cached values a previous producer left in the file instead of failing
12
- outright. A file whose formulas carry no cached value at all cannot be certified.
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 a calculation engine on this host — Microsoft Excel or WPS
14
- Spreadsheets, driven over COM. Without one the verb fails with a plain message
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 or Office, so a PDF annotation is never blocked by a missing
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 or Office;
238
- without one, say the change is verified structurally but not visually. Formula
239
- results stay uncalculated unless `xlsx recalc` runs on Windows with Microsoft
240
- Excel; do not present openpyxl's empty cache as a computed value.
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, Microsoft Office, or WPS Office; "
37
- "pdf rendering works without any of them (PyMuPDF)"
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
- office = renderers.get("microsoft_office") or {}
126
- wps = renderers.get("kingsoft_wps") or {}
125
+ kit = renderers.get("kit") or {}
126
+ can_render = renderers.get("can_render") or {}
127
127
  available = {
128
- "libreoffice": renderers.get("libreoffice") or None,
129
- "microsoft_office": {k: office.get(k) or None for k in ("word", "powerpoint", "excel")},
130
- "kingsoft_wps": {k: wps.get(k) or None for k in ("writer", "presentation", "spreadsheet")},
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
- print(f"renderer libreoffice={r['libreoffice'] or 'none'}; "
248
- f"office={r['microsoft_office']}; wps={r['kingsoft_wps']}")
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`, `soffice` or `pdftoppm`.
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: `no document renderer found: install LibreOffice, Microsoft Office, or WPS Office` | Create/read/edit work, but docx/pptx/xlsx cannot be rasterised here | Do not claim you inspected it. Say the file was built and validated structurally (read verb), but this machine has no renderer so you could not check its appearance; the preview stays unavailable until Microsoft Office, WPS Office, or LibreOffice is installed. `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. |
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 — installing this plugin inside the harness tree (or setting `DSH_LIBREOFFICE_KIT_CLI`) fixes it; a standalone LibreOffice/Office/WPS install does not. `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 with *requires Microsoft Excel or WPS Spreadsheets* | openpyxl cannot evaluate formulas | Report formula cells as uncalculated; do not invent computed values. |
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 (install the plugin inside the harness tree) 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