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.
@@ -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 installing the plugin inside the harness tree (or setting `DSH_LIBREOFFICE_KIT_CLI`), not installing Word/LibreOffice.
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 finds the kit by walking up from its own directory through `node_modules/@deepseek-ai/libreoffice-kit/lib/cli.js`. So:
15
+ The plugin resolves the kit in this order:
16
16
 
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`.
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 from the plugin directory | Put the plugin inside the harness tree, or set `DSH_LIBREOFFICE_KIT_CLI` |
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. Installing the
154
- plugin inside the harness tree (or setting `DSH_LIBREOFFICE_KIT_CLI`) fixes it;
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 — 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. |
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 (install the plugin inside the harness tree) and re-run. |
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$1 = fileURLToPath(new URL("..", import.meta.url));
95
- const POSTINSTALL_SCRIPT = join(PACKAGE_ROOT$1, "postinstall.mjs");
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$1, "python", "office_cli.py"),
121
- skillsDir: join(PACKAGE_ROOT$1, "assets", "skills")
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$1, ".python-path");
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: install this plugin inside the harness tree, or point DSH_LIBREOFFICE_KIT_CLI at <kit>/lib/cli.js";
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 = findKitCli();
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-office-kit",
3
3
  "type": "module",
4
- "version": "0.4.0",
4
+ "version": "0.4.1",
5
5
  "description": "Office documents, PDF, presentations, and spreadsheets for DeepSeek Harness",
6
6
  "author": "Hairy",
7
7
  "license": "MIT",
@@ -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
- "It ships with DeepSeek Harness as @deepseek-ai/libreoffice-kit; install the plugin "
97
- "inside the harness tree, or point DSH_LIBREOFFICE_KIT_CLI at <kit>/lib/cli.js"
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
- Resolution walks up from this file, which covers both layouts that matter:
105
- a plugin-local dependency (`<plugin>/node_modules/...`) and the harness
106
- install (`<dsh>/node_modules/dsh-office-kit/../../@deepseek-ai/...`).
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")