dsh-office-kit 0.4.0 → 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/assets/skills/office-documents/tasks/verify_render.md +1 -1
- package/assets/skills/office-documents/troubleshooting/libreoffice_headless.md +7 -5
- package/assets/skills/office-presentations/SKILL.md +3 -3
- package/assets/skills/shared/common-workflow.md +2 -2
- package/dist/index.js +42 -23
- package/package.json +1 -1
- package/python/office_cli.py +6 -5
|
@@ -32,7 +32,7 @@ The runtime reports `renderers.can_render.docx: false` because the DSH LibreOffi
|
|
|
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 that the fix is
|
|
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
|
|
@@ -12,17 +12,19 @@ Look at `renderers.kit` and `renderers.can_render`. `can_render.docx: false` mea
|
|
|
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
|
-
The plugin
|
|
15
|
+
The plugin resolves the kit in this order:
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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.
|
|
20
|
+
|
|
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`.
|
|
20
22
|
|
|
21
23
|
## Common failures
|
|
22
24
|
|
|
23
25
|
| Message | Cause | Fix |
|
|
24
26
|
|---|---|---|
|
|
25
|
-
| `the LibreOffice renderer is not available` | Kit not found
|
|
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` |
|
|
26
28
|
| `cannot start the LibreOffice kit` / `EEXIST` | Interrupted install, or a stale staging directory | Re-run the plugin postinstall; delete leftover `.kit-render-*` directories |
|
|
27
29
|
| `Unsupported LibreOfficeKit host` | Unsupported platform/arch (the kit ships Windows x64/arm64, macOS x64/arm64, Linux via WASM) | Render on a supported host |
|
|
28
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 |
|
|
@@ -150,9 +150,9 @@ Guidance: [authoring workflow](references/authoring-workflow.md) ·
|
|
|
150
150
|
## Genuine gaps
|
|
151
151
|
|
|
152
152
|
- The DSH LibreOffice kit missing (`renderers.can_render.pptx: false`) → no true
|
|
153
|
-
render; use `--engine draft` and say the check was geometric.
|
|
154
|
-
|
|
155
|
-
a standalone Office/LibreOffice install does not.
|
|
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.
|
|
156
156
|
- Fonts the user's machine lacks are substituted by any renderer; the pack
|
|
157
157
|
cannot install fonts.
|
|
158
158
|
- Google Slides import fidelity is out of our control; see
|
|
@@ -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: `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
|
|
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 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 (
|
|
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
|
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
1
2
|
import { execFile } from "node:child_process";
|
|
2
3
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
3
4
|
import { readFile } from "node:fs/promises";
|
|
4
5
|
import { dirname, join } from "node:path";
|
|
5
6
|
import process from "node:process";
|
|
6
|
-
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { promisify } from "node:util";
|
|
8
8
|
import { BUNDLED_SKILL_RANK } from "@deepseek-ai/dsh-skill";
|
|
9
9
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
10
11
|
import { homedir } from "node:os";
|
|
11
12
|
//#region src/features.ts
|
|
12
13
|
/** The four entries, in menu order. */
|
|
@@ -48,6 +49,33 @@ const OFFICE_FEATURES = [
|
|
|
48
49
|
extensions: [".xlsx"]
|
|
49
50
|
}
|
|
50
51
|
];
|
|
52
|
+
//#endregion
|
|
53
|
+
//#region src/kit.ts
|
|
54
|
+
/** Package root of the installed plugin (dist/ during a build, src/ in a checkout). */
|
|
55
|
+
const PACKAGE_ROOT$1 = fileURLToPath(new URL("..", import.meta.url));
|
|
56
|
+
const KIT_CLI = "@deepseek-ai/libreoffice-kit/cli";
|
|
57
|
+
/**
|
|
58
|
+
* Locate the LibreOffice kit CLI that DeepSeek Harness ships.
|
|
59
|
+
*
|
|
60
|
+
* DSH installs third-party plugins into a profile (`~/.dsh/profiles/<name>/node_modules`)
|
|
61
|
+
* while the kit lives in the harness dependency tree, so resolving relative to this
|
|
62
|
+
* package cannot find it. Resolving from the harness entry point (`process.argv[1]`,
|
|
63
|
+
* e.g. `<dsh>/node_modules/@deepseek-ai/dsh/lib/bin.js`) walks the tree the harness
|
|
64
|
+
* itself loaded from, and resolving from this package covers a checkout or a
|
|
65
|
+
* plugin-local install. `DSH_LIBREOFFICE_KIT_CLI` overrides both.
|
|
66
|
+
*/
|
|
67
|
+
function resolveKitCli(entry = process.argv[1]) {
|
|
68
|
+
const override = process.env.DSH_LIBREOFFICE_KIT_CLI;
|
|
69
|
+
if (override != null && existsSync(override)) return override;
|
|
70
|
+
for (const anchor of [entry, join(PACKAGE_ROOT$1, "package.json")]) {
|
|
71
|
+
if (anchor == null) continue;
|
|
72
|
+
try {
|
|
73
|
+
const cli = createRequire(anchor).resolve(KIT_CLI);
|
|
74
|
+
if (existsSync(cli)) return cli;
|
|
75
|
+
} catch {}
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
51
79
|
/** Python packages the helper CLI needs. */
|
|
52
80
|
const PYTHON_PACKAGES = [
|
|
53
81
|
{
|
|
@@ -91,8 +119,8 @@ const PYTHON_PACKAGES = [
|
|
|
91
119
|
package: "mathml2omml"
|
|
92
120
|
}
|
|
93
121
|
];
|
|
94
|
-
const PACKAGE_ROOT
|
|
95
|
-
const POSTINSTALL_SCRIPT = join(PACKAGE_ROOT
|
|
122
|
+
const PACKAGE_ROOT = fileURLToPath(new URL("..", import.meta.url));
|
|
123
|
+
const POSTINSTALL_SCRIPT = join(PACKAGE_ROOT, "postinstall.mjs");
|
|
96
124
|
/** Upper bound for one self-heal run; the remedy command remains available afterwards. */
|
|
97
125
|
const POSTINSTALL_TIMEOUT = 540 * 1e3;
|
|
98
126
|
/** Failed probes are retried after this delay instead of re-running pip on every status call. */
|
|
@@ -117,8 +145,8 @@ function officePaths() {
|
|
|
117
145
|
legacyConfigFile: legacyConfigFile(),
|
|
118
146
|
venv: join(runtimeDir, "venv"),
|
|
119
147
|
legacyVenv: join(legacyDataDir(), "venv"),
|
|
120
|
-
cli: join(PACKAGE_ROOT
|
|
121
|
-
skillsDir: join(PACKAGE_ROOT
|
|
148
|
+
cli: join(PACKAGE_ROOT, "python", "office_cli.py"),
|
|
149
|
+
skillsDir: join(PACKAGE_ROOT, "assets", "skills")
|
|
122
150
|
};
|
|
123
151
|
}
|
|
124
152
|
/** One-liner the user can run to repair the runtime without pnpm. */
|
|
@@ -241,7 +269,7 @@ function candidates(configFile) {
|
|
|
241
269
|
const saved = savedPython(configFile, paths.legacyConfigFile);
|
|
242
270
|
if (saved != null) found.push(saved);
|
|
243
271
|
found.push(venvPython(paths.venv), venvPython(paths.legacyVenv));
|
|
244
|
-
const marker = join(PACKAGE_ROOT
|
|
272
|
+
const marker = join(PACKAGE_ROOT, ".python-path");
|
|
245
273
|
if (isFile(marker)) {
|
|
246
274
|
const text = readFileSync(marker, "utf8").trim();
|
|
247
275
|
if (text.length > 0) found.push(text);
|
|
@@ -405,24 +433,10 @@ function parseSkill(raw, path) {
|
|
|
405
433
|
content: raw.slice(match[0].length).trim()
|
|
406
434
|
};
|
|
407
435
|
}
|
|
408
|
-
const KIT_MISSING = "the DSH LibreOffice kit (@deepseek-ai/libreoffice-kit) was not found:
|
|
409
|
-
const PACKAGE_ROOT = fileURLToPath(new URL("..", import.meta.url));
|
|
436
|
+
const KIT_MISSING = "the DSH LibreOffice kit (@deepseek-ai/libreoffice-kit) was not found: DeepSeek Harness normally injects DSH_LIBREOFFICE_KIT_CLI, so point that variable at <kit>/lib/cli.js if this plugin runs outside the harness";
|
|
410
437
|
const runKit = promisify(execFile);
|
|
411
|
-
/** Locate the kit the same way python/office_cli.py does, so both halves agree. */
|
|
412
|
-
function findKitCli() {
|
|
413
|
-
const override = process.env.DSH_LIBREOFFICE_KIT_CLI;
|
|
414
|
-
if (override != null && existsSync(override)) return override;
|
|
415
|
-
let directory = PACKAGE_ROOT;
|
|
416
|
-
for (;;) {
|
|
417
|
-
const candidate = join(directory, "node_modules", "@deepseek-ai", "libreoffice-kit", "lib", "cli.js");
|
|
418
|
-
if (existsSync(candidate)) return candidate;
|
|
419
|
-
const parent = dirname(directory);
|
|
420
|
-
if (parent === directory) return null;
|
|
421
|
-
directory = parent;
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
438
|
async function kitFacts() {
|
|
425
|
-
const cli =
|
|
439
|
+
const cli = resolveKitCli();
|
|
426
440
|
if (cli == null) return {
|
|
427
441
|
available: false,
|
|
428
442
|
node: null,
|
|
@@ -462,7 +476,7 @@ async function kitFacts() {
|
|
|
462
476
|
}
|
|
463
477
|
function pluginVersion() {
|
|
464
478
|
try {
|
|
465
|
-
const parsed = JSON.parse(readFileSync(join(PACKAGE_ROOT, "package.json"), "utf8"));
|
|
479
|
+
const parsed = JSON.parse(readFileSync(join(PACKAGE_ROOT$1, "package.json"), "utf8"));
|
|
466
480
|
return typeof parsed.version === "string" ? parsed.version : "unknown";
|
|
467
481
|
} catch {
|
|
468
482
|
return "unknown";
|
|
@@ -480,6 +494,11 @@ function runtimeReason(runtime) {
|
|
|
480
494
|
function apply(ctx) {
|
|
481
495
|
const paths = officePaths();
|
|
482
496
|
const candidates = [];
|
|
497
|
+
const kitCli = resolveKitCli();
|
|
498
|
+
if (kitCli != null) {
|
|
499
|
+
process.env.DSH_LIBREOFFICE_KIT_CLI ??= kitCli;
|
|
500
|
+
process.env.DSH_NODE ??= process.execPath;
|
|
501
|
+
}
|
|
483
502
|
for (const feature of OFFICE_FEATURES) {
|
|
484
503
|
const directory = join(paths.skillsDir, feature.skill);
|
|
485
504
|
const path = join(directory, "SKILL.md");
|
package/package.json
CHANGED
package/python/office_cli.py
CHANGED
|
@@ -93,17 +93,18 @@ KIT_RELATIVE = Path("node_modules") / "@deepseek-ai" / "libreoffice-kit" / "lib"
|
|
|
93
93
|
#: Actionable explanation used whenever the kit cannot be located.
|
|
94
94
|
KIT_MISSING = (
|
|
95
95
|
"the LibreOffice renderer is not available: the DSH LibreOffice kit was not found. "
|
|
96
|
-
"
|
|
97
|
-
"
|
|
96
|
+
"DeepSeek Harness normally injects DSH_LIBREOFFICE_KIT_CLI when it loads this plugin; "
|
|
97
|
+
"if you run the helper by hand, point that variable at <kit>/lib/cli.js of "
|
|
98
|
+
"@deepseek-ai/libreoffice-kit"
|
|
98
99
|
)
|
|
99
100
|
|
|
100
101
|
|
|
101
102
|
def find_kit() -> tuple[str, str] | None:
|
|
102
103
|
"""Return (node, kit CLI) for the bundled renderer, or None when it is absent.
|
|
103
104
|
|
|
104
|
-
|
|
105
|
-
a
|
|
106
|
-
|
|
105
|
+
DeepSeek Harness exports `DSH_LIBREOFFICE_KIT_CLI` (and `DSH_NODE`) before any
|
|
106
|
+
agent shell runs, which covers a profile install where the kit lives in the
|
|
107
|
+
harness tree. The walk-up covers a checkout or a plugin-local dependency.
|
|
107
108
|
"""
|
|
108
109
|
candidates: list[Path] = []
|
|
109
110
|
override = os.environ.get("DSH_LIBREOFFICE_KIT_CLI")
|