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 +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 +13 -16
- 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/client.js +3 -3
- package/dist/client.js.map +1 -1
- package/dist/index.js +79 -59
- package/package.json +2 -1
- package/postinstall.mjs +26 -10
- package/python/office_cli.py +330 -273
package/dist/index.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
2
3
|
import { readFile } from "node:fs/promises";
|
|
3
4
|
import { dirname, join } from "node:path";
|
|
4
5
|
import process from "node:process";
|
|
5
6
|
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { promisify } from "node:util";
|
|
6
8
|
import { BUNDLED_SKILL_RANK } from "@deepseek-ai/dsh-skill";
|
|
7
9
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
8
|
-
import { execFile } from "node:child_process";
|
|
9
10
|
import { homedir } from "node:os";
|
|
10
11
|
//#region src/features.ts
|
|
11
12
|
/** The four entries, in menu order. */
|
|
@@ -104,11 +105,16 @@ function legacyDataDir() {
|
|
|
104
105
|
const home = homedir();
|
|
105
106
|
return process.platform === "win32" ? join(process.env.LOCALAPPDATA ?? join(home, "AppData", "Local"), "dsh-office-kit") : process.platform === "darwin" ? join(home, "Library", "Application Support", "dsh-office-kit") : join(process.env.XDG_DATA_HOME ?? join(home, ".local", "share"), "dsh-office-kit");
|
|
106
107
|
}
|
|
108
|
+
/** Config location written by versions that ignored the platform branch. */
|
|
109
|
+
function legacyConfigFile() {
|
|
110
|
+
return process.platform === "win32" ? join(configDir(), "config.json") : join(process.env.APPDATA ?? join(homedir(), "AppData", "Roaming"), "dsh-office-kit", "config.json");
|
|
111
|
+
}
|
|
107
112
|
/** Resolve per-OS config/data directories and bundled resources. */
|
|
108
113
|
function officePaths() {
|
|
109
114
|
const runtimeDir = join(homedir(), ".dsh", "office", "runtime");
|
|
110
115
|
return {
|
|
111
116
|
configFile: join(configDir(), "config.json"),
|
|
117
|
+
legacyConfigFile: legacyConfigFile(),
|
|
112
118
|
venv: join(runtimeDir, "venv"),
|
|
113
119
|
legacyVenv: join(legacyDataDir(), "venv"),
|
|
114
120
|
cli: join(PACKAGE_ROOT$1, "python", "office_cli.py"),
|
|
@@ -208,25 +214,33 @@ function failedInspection(path, error) {
|
|
|
208
214
|
error: error instanceof Error ? error.message : String(error)
|
|
209
215
|
};
|
|
210
216
|
}
|
|
211
|
-
/**
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
217
|
+
/**
|
|
218
|
+
* Read the interpreter path postinstall recorded; malformed files are ignored.
|
|
219
|
+
* The legacy location is read second so an upgrade does not orphan a ready venv.
|
|
220
|
+
*/
|
|
221
|
+
function savedPython(configFile, legacyConfigFile) {
|
|
222
|
+
const files = legacyConfigFile == null ? [configFile] : [configFile, legacyConfigFile];
|
|
223
|
+
for (const file of files) {
|
|
224
|
+
if (!isFile(file)) continue;
|
|
225
|
+
try {
|
|
226
|
+
const parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
227
|
+
if (typeof parsed.python === "string" && parsed.python.length > 0) return parsed.python;
|
|
228
|
+
} catch {}
|
|
219
229
|
}
|
|
220
230
|
}
|
|
231
|
+
/** The interpreter inside a virtual environment, which differs per platform. */
|
|
232
|
+
function venvPython(venv) {
|
|
233
|
+
return join(venv, process.platform === "win32" ? "Scripts/python.exe" : "bin/python");
|
|
234
|
+
}
|
|
221
235
|
/** PATH-independent candidate list, plugin-owned interpreters first. */
|
|
222
236
|
function candidates(configFile) {
|
|
223
237
|
const paths = officePaths();
|
|
224
238
|
const found = [];
|
|
225
239
|
const env = process.env.DSH_OFFICE_PYTHON;
|
|
226
240
|
if (env != null && env.length > 0) found.push(env);
|
|
227
|
-
const saved = savedPython(configFile);
|
|
241
|
+
const saved = savedPython(configFile, paths.legacyConfigFile);
|
|
228
242
|
if (saved != null) found.push(saved);
|
|
229
|
-
found.push(paths.venv, paths.legacyVenv);
|
|
243
|
+
found.push(venvPython(paths.venv), venvPython(paths.legacyVenv));
|
|
230
244
|
const marker = join(PACKAGE_ROOT$1, ".python-path");
|
|
231
245
|
if (isFile(marker)) {
|
|
232
246
|
const text = readFileSync(marker, "utf8").trim();
|
|
@@ -391,55 +405,61 @@ function parseSkill(raw, path) {
|
|
|
391
405
|
content: raw.slice(match[0].length).trim()
|
|
392
406
|
};
|
|
393
407
|
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
const
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
].filter(Boolean);
|
|
409
|
-
function wpsInstalled(binary) {
|
|
410
|
-
for (const root of WPS_ROOTS) {
|
|
411
|
-
if (existsSync(join(root, "office6", binary))) return true;
|
|
412
|
-
try {
|
|
413
|
-
for (const version of readdirSync(root)) if (existsSync(join(root, version, "office6", binary))) return true;
|
|
414
|
-
} catch {}
|
|
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));
|
|
410
|
+
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;
|
|
415
422
|
}
|
|
416
|
-
return false;
|
|
417
423
|
}
|
|
418
|
-
function
|
|
419
|
-
const
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
const officeRoots = ["C:\\Program Files\\Microsoft Office\\root\\Office16", "C:\\Program Files (x86)\\Microsoft Office\\root\\Office16"];
|
|
428
|
-
return {
|
|
429
|
-
libreoffice,
|
|
430
|
-
microsoftOffice: {
|
|
431
|
-
word: officeRoots.some((root) => existsSync(join(root, "WINWORD.EXE"))),
|
|
432
|
-
powerpoint: officeRoots.some((root) => existsSync(join(root, "POWERPNT.EXE"))),
|
|
433
|
-
excel: officeRoots.some((root) => existsSync(join(root, "EXCEL.EXE")))
|
|
434
|
-
},
|
|
435
|
-
kingsoftWps: {
|
|
436
|
-
writer: wpsInstalled(WPS_BINARIES.writer),
|
|
437
|
-
presentation: wpsInstalled(WPS_BINARIES.presentation),
|
|
438
|
-
spreadsheet: wpsInstalled(WPS_BINARIES.spreadsheet)
|
|
439
|
-
}
|
|
424
|
+
async function kitFacts() {
|
|
425
|
+
const cli = findKitCli();
|
|
426
|
+
if (cli == null) return {
|
|
427
|
+
available: false,
|
|
428
|
+
node: null,
|
|
429
|
+
cli: null,
|
|
430
|
+
version: null,
|
|
431
|
+
backend: null,
|
|
432
|
+
remedy: KIT_MISSING
|
|
440
433
|
};
|
|
434
|
+
const node = process.env.DSH_NODE ?? process.execPath;
|
|
435
|
+
try {
|
|
436
|
+
const { stdout } = await runKit(node, [
|
|
437
|
+
cli,
|
|
438
|
+
"capabilities",
|
|
439
|
+
"--json"
|
|
440
|
+
], {
|
|
441
|
+
timeout: 12e4,
|
|
442
|
+
maxBuffer: 8 * 1024 * 1024
|
|
443
|
+
});
|
|
444
|
+
const parsed = JSON.parse(stdout);
|
|
445
|
+
return {
|
|
446
|
+
available: true,
|
|
447
|
+
node,
|
|
448
|
+
cli,
|
|
449
|
+
version: typeof parsed.runtime?.version === "string" ? parsed.runtime.version : null,
|
|
450
|
+
backend: typeof parsed.runtime?.backend === "string" ? parsed.runtime.backend : null
|
|
451
|
+
};
|
|
452
|
+
} catch (error) {
|
|
453
|
+
return {
|
|
454
|
+
available: true,
|
|
455
|
+
node,
|
|
456
|
+
cli,
|
|
457
|
+
version: null,
|
|
458
|
+
backend: null,
|
|
459
|
+
error: error instanceof Error ? error.message.split("\n")[0] ?? error.message : String(error)
|
|
460
|
+
};
|
|
461
|
+
}
|
|
441
462
|
}
|
|
442
|
-
const PACKAGE_ROOT = fileURLToPath(new URL("..", import.meta.url));
|
|
443
463
|
function pluginVersion() {
|
|
444
464
|
try {
|
|
445
465
|
const parsed = JSON.parse(readFileSync(join(PACKAGE_ROOT, "package.json"), "utf8"));
|
|
@@ -521,7 +541,7 @@ function apply(ctx) {
|
|
|
521
541
|
ctx.effect(() => ctx.skills.registerProvider(() => provider), "office-suite: skills");
|
|
522
542
|
ctx.effect(() => ctx.tools.register(defineTool({
|
|
523
543
|
name: "office_status",
|
|
524
|
-
description: "Report the Office runtime state: compatible Python, installed Python packages, helper CLI path, and document renderers (
|
|
544
|
+
description: "Report the Office runtime state: compatible Python, installed Python packages, helper CLI path, and document renderers (the DSH LibreOffice kit). Call this before Office file work.",
|
|
525
545
|
parameters: {},
|
|
526
546
|
output: {
|
|
527
547
|
schema: { type: "string" },
|
|
@@ -560,7 +580,7 @@ function apply(ctx) {
|
|
|
560
580
|
exists: cliExists()
|
|
561
581
|
},
|
|
562
582
|
packages: runtime.packages,
|
|
563
|
-
renderers:
|
|
583
|
+
renderers: await kitFacts(),
|
|
564
584
|
skills: candidates.map((candidate) => candidate.name)
|
|
565
585
|
};
|
|
566
586
|
return JSON.stringify(report, null, 2);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-office-kit",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.4.0",
|
|
5
5
|
"description": "Office documents, PDF, presentations, and spreadsheets for DeepSeek Harness",
|
|
6
6
|
"author": "Hairy",
|
|
7
7
|
"license": "MIT",
|
|
@@ -110,6 +110,7 @@
|
|
|
110
110
|
"@deepseek-ai/dsh-util-code-language": "0.2.1-rc.2",
|
|
111
111
|
"@deepseek-ai/dsh-util-values": "0.2.1-rc.2",
|
|
112
112
|
"@deepseek-ai/dsh-workspace": "0.2.1-rc.2",
|
|
113
|
+
"@deepseek-ai/libreoffice-kit": "^0.1.5",
|
|
113
114
|
"@types/node": "^26.1.1",
|
|
114
115
|
"@types/react": "^18.2.0",
|
|
115
116
|
"@types/react-dom": "^18.2.0",
|
package/postinstall.mjs
CHANGED
|
@@ -35,8 +35,21 @@ const LEGACY_DATA_DIR = IS_WINDOWS
|
|
|
35
35
|
: join(process.env.XDG_DATA_HOME || join(homedir(), '.local', 'share'), 'dsh-office-kit')
|
|
36
36
|
export const LEGACY_VENV_DIR = join(LEGACY_DATA_DIR, 'venv')
|
|
37
37
|
const LEGACY_VENV_PYTHON = join(LEGACY_VENV_DIR, IS_WINDOWS ? 'Scripts/python.exe' : 'bin/python')
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
// Must mirror `configDir()` in src/python.ts: the host half reads this file on
|
|
39
|
+
// every runtime probe, so a platform mismatch here makes a ready venv look missing.
|
|
40
|
+
export const CONFIG_DIR = IS_WINDOWS
|
|
41
|
+
? join(process.env.APPDATA || join(homedir(), 'AppData', 'Roaming'), 'dsh-office-kit')
|
|
42
|
+
: process.platform === 'darwin'
|
|
43
|
+
? join(homedir(), 'Library', 'Application Support', 'dsh-office-kit')
|
|
44
|
+
: join(process.env.XDG_CONFIG_HOME || join(homedir(), '.config'), 'dsh-office-kit')
|
|
45
|
+
export const CONFIG_FILE = join(CONFIG_DIR, 'config.json')
|
|
46
|
+
/**
|
|
47
|
+
* Legacy config location written by versions without the platform branch.
|
|
48
|
+
* Read as a fallback so an existing runtime is not rebuilt on upgrade.
|
|
49
|
+
*/
|
|
50
|
+
const LEGACY_CONFIG_FILE = IS_WINDOWS
|
|
51
|
+
? CONFIG_FILE
|
|
52
|
+
: join(process.env.APPDATA || join(homedir(), 'AppData', 'Roaming'), 'dsh-office-kit', 'config.json')
|
|
40
53
|
const REQUIREMENTS = join(PACKAGE_ROOT, 'python', 'requirements.txt')
|
|
41
54
|
const LOG_PREFIX = '[dsh-office-kit]'
|
|
42
55
|
|
|
@@ -271,15 +284,18 @@ function verifyImports(python) {
|
|
|
271
284
|
}
|
|
272
285
|
|
|
273
286
|
function readConfig() {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
287
|
+
for (const file of [CONFIG_FILE, LEGACY_CONFIG_FILE]) {
|
|
288
|
+
if (!existsSync(file)) {
|
|
289
|
+
continue
|
|
290
|
+
}
|
|
291
|
+
try {
|
|
292
|
+
return JSON.parse(readFileSync(file, 'utf8'))
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
// A malformed file must not hide a usable legacy one.
|
|
296
|
+
}
|
|
282
297
|
}
|
|
298
|
+
return {}
|
|
283
299
|
}
|
|
284
300
|
|
|
285
301
|
function writeConfig(config) {
|