dsh-office-kit 0.2.0 → 0.3.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.
Files changed (197) hide show
  1. package/CAPABILITY.md +147 -68
  2. package/README.md +3 -1
  3. package/assets/skills/excel-live-control/SKILL.md +234 -0
  4. package/assets/skills/excel-live-control/container_tools/live_excel.py +1589 -0
  5. package/assets/skills/excel-live-control/domain_guidance/financial_models.md +86 -0
  6. package/assets/skills/excel-live-control/domain_guidance/healthcare.md +58 -0
  7. package/assets/skills/excel-live-control/domain_guidance/marketing_advertising.md +53 -0
  8. package/assets/skills/excel-live-control/domain_guidance/scientific_research.md +53 -0
  9. package/assets/skills/excel-live-control/references/office-automation.md +207 -0
  10. package/assets/skills/excel-live-control/references/read_only_qna.md +59 -0
  11. package/assets/skills/excel-live-control/style_guidelines.md +87 -0
  12. package/assets/skills/excel-live-control/workflows/create_workflows.md +78 -0
  13. package/assets/skills/excel-live-control/workflows/edit_workflows.md +72 -0
  14. package/assets/skills/office-documents/SKILL.md +90 -77
  15. package/assets/skills/office-documents/examples/end_to_end_smoke_test.md +86 -0
  16. package/assets/skills/office-documents/ooxml/comments.md +40 -0
  17. package/assets/skills/office-documents/ooxml/hyperlinks_and_fields.md +66 -0
  18. package/assets/skills/office-documents/ooxml/rels_and_content_types.md +62 -0
  19. package/assets/skills/office-documents/ooxml/tracked_changes.md +53 -0
  20. package/assets/skills/office-documents/references/docx-structure.md +83 -0
  21. package/assets/skills/office-documents/references/template-elicitation.md +49 -0
  22. package/assets/skills/office-documents/references/writing-quality.md +49 -0
  23. package/assets/skills/office-documents/render_docx.py +92 -0
  24. package/assets/skills/office-documents/scripts/a11y_audit.py +194 -0
  25. package/assets/skills/office-documents/scripts/accept_tracked_changes.py +151 -0
  26. package/assets/skills/office-documents/scripts/add_tracked_replacements.py +148 -0
  27. package/assets/skills/office-documents/scripts/append_blocks.py +108 -0
  28. package/assets/skills/office-documents/scripts/apply_template_styles.py +104 -0
  29. package/assets/skills/office-documents/scripts/captions_and_crossrefs.py +102 -0
  30. package/assets/skills/office-documents/scripts/comments_add.py +136 -0
  31. package/assets/skills/office-documents/scripts/comments_apply_patch.py +114 -0
  32. package/assets/skills/office-documents/scripts/comments_extract.py +85 -0
  33. package/assets/skills/office-documents/scripts/comments_strip.py +74 -0
  34. package/assets/skills/office-documents/scripts/content_controls.py +192 -0
  35. package/assets/skills/office-documents/scripts/core_properties.py +124 -0
  36. package/assets/skills/office-documents/scripts/docx_kit.py +587 -0
  37. package/assets/skills/office-documents/scripts/docx_ooxml_patch.py +225 -0
  38. package/assets/skills/office-documents/scripts/docx_table_to_csv.py +99 -0
  39. package/assets/skills/office-documents/scripts/fields_materialize.py +66 -0
  40. package/assets/skills/office-documents/scripts/fields_report.py +59 -0
  41. package/assets/skills/office-documents/scripts/flatten_ref_fields.py +62 -0
  42. package/assets/skills/office-documents/scripts/footnotes_report.py +77 -0
  43. package/assets/skills/office-documents/scripts/google_docs_title_sanitize.py +96 -0
  44. package/assets/skills/office-documents/scripts/heading_audit.py +81 -0
  45. package/assets/skills/office-documents/scripts/images_audit.py +80 -0
  46. package/assets/skills/office-documents/scripts/insert_note.py +126 -0
  47. package/assets/skills/office-documents/scripts/insert_ref_fields.py +90 -0
  48. package/assets/skills/office-documents/scripts/insert_toc.py +84 -0
  49. package/assets/skills/office-documents/scripts/internal_nav.py +146 -0
  50. package/assets/skills/office-documents/scripts/make_fixtures.py +169 -0
  51. package/assets/skills/office-documents/scripts/merge_docx_append.py +239 -0
  52. package/assets/skills/office-documents/scripts/privacy_scrub.py +131 -0
  53. package/assets/skills/office-documents/scripts/redact_docx.py +113 -0
  54. package/assets/skills/office-documents/scripts/render_and_diff.py +101 -0
  55. package/assets/skills/office-documents/scripts/replace_text.py +154 -0
  56. package/assets/skills/office-documents/scripts/section_audit.py +91 -0
  57. package/assets/skills/office-documents/scripts/set_protection.py +92 -0
  58. package/assets/skills/office-documents/scripts/style_lint.py +111 -0
  59. package/assets/skills/office-documents/scripts/style_normalize.py +103 -0
  60. package/assets/skills/office-documents/scripts/table_geometry.py +167 -0
  61. package/assets/skills/office-documents/scripts/watermark_add.py +48 -0
  62. package/assets/skills/office-documents/scripts/watermark_audit_remove.py +67 -0
  63. package/assets/skills/office-documents/scripts/xlsx_to_docx_table.py +117 -0
  64. package/assets/skills/office-documents/tasks/accessibility_a11y.md +45 -0
  65. package/assets/skills/office-documents/tasks/captions_crossrefs.md +49 -0
  66. package/assets/skills/office-documents/tasks/clean_tracked_changes.md +49 -0
  67. package/assets/skills/office-documents/tasks/comments_manage.md +59 -0
  68. package/assets/skills/office-documents/tasks/compare_diff.md +40 -0
  69. package/assets/skills/office-documents/tasks/create_edit.md +107 -0
  70. package/assets/skills/office-documents/tasks/equations.md +59 -0
  71. package/assets/skills/office-documents/tasks/fields_update.md +44 -0
  72. package/assets/skills/office-documents/tasks/fixtures_edge_cases.md +50 -0
  73. package/assets/skills/office-documents/tasks/footnotes_endnotes.md +38 -0
  74. package/assets/skills/office-documents/tasks/forms_content_controls.md +48 -0
  75. package/assets/skills/office-documents/tasks/headings_numbering.md +55 -0
  76. package/assets/skills/office-documents/tasks/images_figures.md +63 -0
  77. package/assets/skills/office-documents/tasks/multi_doc_merge.md +43 -0
  78. package/assets/skills/office-documents/tasks/navigation_internal_links.md +38 -0
  79. package/assets/skills/office-documents/tasks/privacy_scrub_metadata.md +45 -0
  80. package/assets/skills/office-documents/tasks/protection_restrict_editing.md +41 -0
  81. package/assets/skills/office-documents/tasks/read_review.md +50 -0
  82. package/assets/skills/office-documents/tasks/redaction_anonymization.md +42 -0
  83. package/assets/skills/office-documents/tasks/sections_layout.md +54 -0
  84. package/assets/skills/office-documents/tasks/style_lint_normalize.md +44 -0
  85. package/assets/skills/office-documents/tasks/tables_spreadsheets.md +64 -0
  86. package/assets/skills/office-documents/tasks/templates_style_packs.md +53 -0
  87. package/assets/skills/office-documents/tasks/toc_workflow.md +39 -0
  88. package/assets/skills/office-documents/tasks/verify_render.md +50 -0
  89. package/assets/skills/office-documents/tasks/watermarks_background.md +35 -0
  90. package/assets/skills/office-documents/troubleshooting/libreoffice_headless.md +47 -0
  91. package/assets/skills/office-documents/troubleshooting/run_splitting.md +36 -0
  92. package/assets/skills/office-pdf/SKILL.md +73 -56
  93. package/assets/skills/office-pdf/references/form-fields.md +56 -0
  94. package/assets/skills/office-pdf/references/geometry.md +44 -0
  95. package/assets/skills/office-pdf/references/layout-quality.md +41 -0
  96. package/assets/skills/office-pdf/references/limits.md +58 -0
  97. package/assets/skills/office-pdf/scripts/extract_text.py +172 -0
  98. package/assets/skills/office-pdf/scripts/forms.py +556 -0
  99. package/assets/skills/office-pdf/scripts/metadata.py +161 -0
  100. package/assets/skills/office-pdf/scripts/page_ops.py +198 -0
  101. package/assets/skills/office-pdf/scripts/redact_region.py +347 -0
  102. package/assets/skills/office-pdf/scripts/render_pages.py +207 -0
  103. package/assets/skills/office-pdf/scripts/split_pages.py +155 -0
  104. package/assets/skills/office-pdf/scripts/stamp_overlay.py +199 -0
  105. package/assets/skills/office-pdf/tasks/annotations.md +98 -0
  106. package/assets/skills/office-pdf/tasks/create.md +75 -0
  107. package/assets/skills/office-pdf/tasks/edit-pages.md +71 -0
  108. package/assets/skills/office-pdf/tasks/forms.md +67 -0
  109. package/assets/skills/office-pdf/tasks/read.md +62 -0
  110. package/assets/skills/office-pdf/tasks/redact.md +72 -0
  111. package/assets/skills/office-pdf/tasks/stamp.md +60 -0
  112. package/assets/skills/office-presentations/SKILL.md +141 -87
  113. package/assets/skills/office-presentations/container_tools/add_slides.py +136 -0
  114. package/assets/skills/office-presentations/container_tools/core_properties.py +120 -0
  115. package/assets/skills/office-presentations/container_tools/create_montage.py +126 -0
  116. package/assets/skills/office-presentations/container_tools/inspect_presentation_layout_geometry.py +285 -0
  117. package/assets/skills/office-presentations/container_tools/inspect_presentation_package_integrity.py +157 -0
  118. package/assets/skills/office-presentations/container_tools/native_chart_title_gate.py +144 -0
  119. package/assets/skills/office-presentations/container_tools/native_quantitative_chart_gate.py +199 -0
  120. package/assets/skills/office-presentations/container_tools/native_table_arithmetic_gate.py +212 -0
  121. package/assets/skills/office-presentations/container_tools/ooxml_helpers.py +345 -0
  122. package/assets/skills/office-presentations/container_tools/preview_render.py +375 -0
  123. package/assets/skills/office-presentations/container_tools/render_slides.py +139 -0
  124. package/assets/skills/office-presentations/container_tools/replace_text.py +168 -0
  125. package/assets/skills/office-presentations/container_tools/template_source_fidelity_gate.py +229 -0
  126. package/assets/skills/office-presentations/domain_guidance/finance.md +66 -0
  127. package/assets/skills/office-presentations/recipes/README.md +71 -0
  128. package/assets/skills/office-presentations/recipes/autolayout.md +88 -0
  129. package/assets/skills/office-presentations/recipes/connectors.md +83 -0
  130. package/assets/skills/office-presentations/recipes/images.md +80 -0
  131. package/assets/skills/office-presentations/recipes/master-and-layouts.md +127 -0
  132. package/assets/skills/office-presentations/recipes/native-charts.md +87 -0
  133. package/assets/skills/office-presentations/recipes/rich-text-runs.md +86 -0
  134. package/assets/skills/office-presentations/recipes/speaker-notes.md +65 -0
  135. package/assets/skills/office-presentations/recipes/tables.md +88 -0
  136. package/assets/skills/office-presentations/recipes/textbox-at-coordinate.md +71 -0
  137. package/assets/skills/office-presentations/recipes/theme-tokens.md +87 -0
  138. package/assets/skills/office-presentations/references/authoring-workflow.md +65 -0
  139. package/assets/skills/office-presentations/references/finalization.md +63 -0
  140. package/assets/skills/office-presentations/references/native-bullets.md +78 -0
  141. package/assets/skills/office-presentations/references/native-evidence.md +67 -0
  142. package/assets/skills/office-presentations/references/style-guidelines.md +66 -0
  143. package/assets/skills/office-presentations/references/templates.md +76 -0
  144. package/assets/skills/office-presentations/routing/google_slides.md +45 -0
  145. package/assets/skills/office-presentations/tasks/apply-annotations.md +83 -0
  146. package/assets/skills/office-presentations/tasks/charts-tables-notes.md +79 -0
  147. package/assets/skills/office-presentations/tasks/create-deck.md +106 -0
  148. package/assets/skills/office-presentations/tasks/edit-deck.md +93 -0
  149. package/assets/skills/office-presentations/tasks/inspect-and-verify.md +107 -0
  150. package/assets/skills/office-presentations/tasks/template-following.md +132 -0
  151. package/assets/skills/office-presentations/template_following_scripts/check_template_fidelity.py +176 -0
  152. package/assets/skills/office-presentations/template_following_scripts/inspect_template_deck.py +154 -0
  153. package/assets/skills/office-presentations/template_following_scripts/make_contact_sheet.py +111 -0
  154. package/assets/skills/office-presentations/template_following_scripts/prepare_template_starter_deck.py +239 -0
  155. package/assets/skills/office-presentations/template_following_scripts/validate_template_plan.py +185 -0
  156. package/assets/skills/office-spreadsheets/SKILL.md +105 -101
  157. package/assets/skills/office-spreadsheets/container_tools/add_chart.py +236 -0
  158. package/assets/skills/office-spreadsheets/container_tools/add_image.py +107 -0
  159. package/assets/skills/office-spreadsheets/container_tools/append_rows.py +122 -0
  160. package/assets/skills/office-spreadsheets/container_tools/diff_workbooks.py +306 -0
  161. package/assets/skills/office-spreadsheets/container_tools/inspect_workbook.py +398 -0
  162. package/assets/skills/office-spreadsheets/container_tools/recalc_gate.py +275 -0
  163. package/assets/skills/office-spreadsheets/container_tools/set_cells.py +135 -0
  164. package/assets/skills/office-spreadsheets/container_tools/sheet_ops.py +134 -0
  165. package/assets/skills/office-spreadsheets/container_tools/style_cells.py +133 -0
  166. package/assets/skills/office-spreadsheets/recipes/charts.md +66 -0
  167. package/assets/skills/office-spreadsheets/recipes/conditional-formats.md +69 -0
  168. package/assets/skills/office-spreadsheets/recipes/data-tables.md +58 -0
  169. package/assets/skills/office-spreadsheets/recipes/named-ranges.md +41 -0
  170. package/assets/skills/office-spreadsheets/recipes/number-formats.md +72 -0
  171. package/assets/skills/office-spreadsheets/recipes/sparklines.md +43 -0
  172. package/assets/skills/office-spreadsheets/references/charts.md +63 -0
  173. package/assets/skills/office-spreadsheets/references/domain/financial-models.md +40 -0
  174. package/assets/skills/office-spreadsheets/references/domain/healthcare.md +22 -0
  175. package/assets/skills/office-spreadsheets/references/domain/marketing.md +28 -0
  176. package/assets/skills/office-spreadsheets/references/domain/research.md +22 -0
  177. package/assets/skills/office-spreadsheets/references/formula-patterns.md +78 -0
  178. package/assets/skills/office-spreadsheets/references/style-guidelines.md +69 -0
  179. package/assets/skills/office-spreadsheets/references/workbook-structure.md +60 -0
  180. package/assets/skills/office-spreadsheets/routing/google-sheets.md +36 -0
  181. package/assets/skills/office-spreadsheets/tasks/analyze-workbook.md +37 -0
  182. package/assets/skills/office-spreadsheets/tasks/annotations.md +51 -0
  183. package/assets/skills/office-spreadsheets/tasks/create-workbook.md +66 -0
  184. package/assets/skills/office-spreadsheets/tasks/diff-workbooks.md +38 -0
  185. package/assets/skills/office-spreadsheets/tasks/edit-workbook.md +69 -0
  186. package/assets/skills/office-spreadsheets/tasks/inspect-workbook.md +38 -0
  187. package/assets/skills/office-spreadsheets/tasks/recalc-and-check.md +57 -0
  188. package/assets/skills/shared/annotation-recipes.md +252 -0
  189. package/assets/skills/shared/check_runtime.py +253 -0
  190. package/assets/skills/shared/common-workflow.md +139 -0
  191. package/assets/skills/shared/scripts/ensure_raster_image.py +187 -0
  192. package/dist/client.js +4 -4
  193. package/dist/client.js.map +1 -1
  194. package/dist/index.js +239 -89
  195. package/package.json +1 -1
  196. package/postinstall.mjs +92 -82
  197. package/python/office_cli.py +171 -246
package/dist/index.js CHANGED
@@ -1,13 +1,12 @@
1
- import { execFile } from "node:child_process";
2
- import { existsSync, readFileSync, statSync } from "node:fs";
1
+ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
3
2
  import { readFile } from "node:fs/promises";
4
- import { join } from "node:path";
3
+ import { dirname, join } from "node:path";
5
4
  import process from "node:process";
6
- import { promisify } from "node:util";
5
+ import { fileURLToPath } from "node:url";
7
6
  import { BUNDLED_SKILL_RANK } from "@deepseek-ai/dsh-skill";
8
7
  import { defineTool } from "@deepseek-ai/dsh-tools";
8
+ import { execFile } from "node:child_process";
9
9
  import { homedir } from "node:os";
10
- import { fileURLToPath } from "node:url";
11
10
  //#region src/features.ts
12
11
  /** The four entries, in menu order. */
13
12
  const OFFICE_FEATURES = [
@@ -91,19 +90,35 @@ const PYTHON_PACKAGES = [
91
90
  package: "mathml2omml"
92
91
  }
93
92
  ];
93
+ const PACKAGE_ROOT$1 = fileURLToPath(new URL("..", import.meta.url));
94
+ const POSTINSTALL_SCRIPT = join(PACKAGE_ROOT$1, "postinstall.mjs");
95
+ /** Upper bound for one self-heal run; the remedy command remains available afterwards. */
96
+ const POSTINSTALL_TIMEOUT = 540 * 1e3;
97
+ /** Failed probes are retried after this delay instead of re-running pip on every status call. */
98
+ const FAILURE_RETRY_DELAY = 300 * 1e3;
99
+ function configDir() {
100
+ const home = homedir();
101
+ return process.platform === "win32" ? join(process.env.APPDATA ?? join(home, "AppData", "Roaming"), "dsh-office-kit") : process.platform === "darwin" ? join(home, "Library", "Application Support", "dsh-office-kit") : join(process.env.XDG_CONFIG_HOME ?? join(home, ".config"), "dsh-office-kit");
102
+ }
103
+ function legacyDataDir() {
104
+ const home = homedir();
105
+ 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
+ }
94
107
  /** Resolve per-OS config/data directories and bundled resources. */
95
108
  function officePaths() {
96
- const packageRoot = fileURLToPath(new URL("..", import.meta.url));
97
- const home = homedir();
98
- const configDir = process.platform === "win32" ? join(process.env.APPDATA ?? join(home, "AppData", "Roaming"), "dsh-office-kit") : process.platform === "darwin" ? join(home, "Library", "Application Support", "dsh-office-kit") : join(process.env.XDG_CONFIG_HOME ?? join(home, ".config"), "dsh-office-kit");
99
- const dataDir = 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");
109
+ const runtimeDir = join(homedir(), ".dsh", "office", "runtime");
100
110
  return {
101
- configFile: join(configDir, "config.json"),
102
- dataDir,
103
- cli: join(packageRoot, "python", "office_cli.py"),
104
- skillsDir: join(packageRoot, "assets", "skills")
111
+ configFile: join(configDir(), "config.json"),
112
+ venv: join(runtimeDir, "venv"),
113
+ legacyVenv: join(legacyDataDir(), "venv"),
114
+ cli: join(PACKAGE_ROOT$1, "python", "office_cli.py"),
115
+ skillsDir: join(PACKAGE_ROOT$1, "assets", "skills")
105
116
  };
106
117
  }
118
+ /** One-liner the user can run to repair the runtime without pnpm. */
119
+ function runtimeRemedy() {
120
+ return `node "${POSTINSTALL_SCRIPT}"`;
121
+ }
107
122
  function isFile(path) {
108
123
  try {
109
124
  return statSync(path).isFile();
@@ -130,34 +145,68 @@ function run(file, args, timeout = 15e3) {
130
145
  }).stdin?.end();
131
146
  });
132
147
  }
133
- const VERSION_SCRIPT = "import json,sys;print(json.dumps({\"version\":sys.version.split()[0],\"executable\":sys.executable}))";
134
- /** Inspect an absolute interpreter without trusting its path alone. */
135
- async function inspectPython(path) {
136
- try {
137
- const raw = await run(path, ["-c", VERSION_SCRIPT]);
138
- const parsed = JSON.parse(raw);
139
- if (typeof parsed.version !== "string" || typeof parsed.executable !== "string") throw new Error("unexpected interpreter response");
140
- const [majorText, minorText] = parsed.version.split(".");
141
- const major = Number(majorText);
142
- const minor = Number(minorText);
143
- const compatible = major === 3 && minor >= 10 && minor < 14;
144
- return {
145
- path: parsed.executable,
146
- version: parsed.version,
147
- major,
148
- minor,
149
- compatible
150
- };
151
- } catch (error) {
152
- return {
153
- path,
154
- version: "",
155
- major: 0,
156
- minor: 0,
157
- compatible: false,
158
- error: error instanceof Error ? error.message : String(error)
159
- };
160
- }
148
+ const RUNTIME_SCRIPT = `
149
+ import importlib, json, sys
150
+ targets = ${JSON.stringify(PYTHON_PACKAGES)}
151
+ packages = {}
152
+ for item in targets:
153
+ try:
154
+ module = importlib.import_module(item["importName"])
155
+ packages[item["package"]] = {"installed": True, "version": str(getattr(module, "__version__", ""))}
156
+ except Exception as exc:
157
+ packages[item["package"]] = {"installed": False, "error": f"{type(exc).__name__}: {exc}"}
158
+ print(json.dumps({"python": sys.executable, "version": sys.version.split()[0], "packages": packages}))
159
+ `.trim();
160
+ /** Inspect an absolute interpreter: version plus every required import. */
161
+ function inspectPython(path) {
162
+ return run(path, ["-c", RUNTIME_SCRIPT], 6e4).then((raw) => {
163
+ try {
164
+ const parsed = JSON.parse(raw);
165
+ if (typeof parsed.version !== "string" || typeof parsed.python !== "string") throw new Error("unexpected interpreter response");
166
+ const [majorText, minorText] = parsed.version.split(".");
167
+ const major = Number(majorText);
168
+ const minor = Number(minorText);
169
+ const incompatible = major !== 3 || minor < 10 || minor >= 14;
170
+ const packages = PYTHON_PACKAGES.map(({ importName, package: name }) => {
171
+ const entry = parsed.packages?.[name];
172
+ return {
173
+ package: name,
174
+ importName,
175
+ installed: entry?.installed === true,
176
+ ...typeof entry?.version === "string" && entry.version.length > 0 ? { version: entry.version } : {},
177
+ ...typeof entry?.error === "string" && entry.error.length > 0 ? { error: entry.error } : {}
178
+ };
179
+ });
180
+ const missing = packages.filter((entry) => !entry.installed);
181
+ const inspection = {
182
+ path: parsed.python,
183
+ version: parsed.version,
184
+ major,
185
+ minor,
186
+ compatible: !incompatible,
187
+ packages,
188
+ missing: incompatible ? [] : missing
189
+ };
190
+ return incompatible ? {
191
+ ...inspection,
192
+ error: `unsupported Python ${parsed.version}`
193
+ } : inspection;
194
+ } catch (error) {
195
+ return failedInspection(path, error);
196
+ }
197
+ }, (error) => failedInspection(path, error));
198
+ }
199
+ function failedInspection(path, error) {
200
+ return {
201
+ path,
202
+ version: "",
203
+ major: 0,
204
+ minor: 0,
205
+ compatible: false,
206
+ packages: [],
207
+ missing: [],
208
+ error: error instanceof Error ? error.message : String(error)
209
+ };
161
210
  }
162
211
  /** Read the interpreter path postinstall recorded; malformed files are ignored. */
163
212
  function savedPython(configFile) {
@@ -169,14 +218,16 @@ function savedPython(configFile) {
169
218
  return;
170
219
  }
171
220
  }
172
- /** PATH-independent candidate list, most specific first. */
221
+ /** PATH-independent candidate list, plugin-owned interpreters first. */
173
222
  function candidates(configFile) {
223
+ const paths = officePaths();
174
224
  const found = [];
175
225
  const env = process.env.DSH_OFFICE_PYTHON;
176
226
  if (env != null && env.length > 0) found.push(env);
177
227
  const saved = savedPython(configFile);
178
228
  if (saved != null) found.push(saved);
179
- const marker = join(fileURLToPath(new URL("..", import.meta.url)), ".python-path");
229
+ found.push(paths.venv, paths.legacyVenv);
230
+ const marker = join(PACKAGE_ROOT$1, ".python-path");
180
231
  if (isFile(marker)) {
181
232
  const text = readFileSync(marker, "utf8").trim();
182
233
  if (text.length > 0) found.push(text);
@@ -220,16 +271,91 @@ async function launcherInterpreters() {
220
271
  return [];
221
272
  }
222
273
  }
223
- /** Find the first runnable, compatible interpreter; return the best attempt otherwise. */
224
- async function resolvePython(configFile = officePaths().configFile) {
274
+ function statusOf(python) {
275
+ return {
276
+ state: python.compatible && python.missing.length === 0 ? "ok" : "missing-dependencies",
277
+ python,
278
+ packages: python.packages,
279
+ missing: python.missing,
280
+ remedy: runtimeRemedy()
281
+ };
282
+ }
283
+ /** Inspect every candidate; return the first interpreter that proves it has all dependencies. */
284
+ async function inspectRuntime(configFile = officePaths().configFile) {
285
+ let compatible;
225
286
  let last;
226
287
  for (const candidate of [...candidates(configFile), ...await launcherInterpreters()]) for (const expanded of await expandLauncher(candidate)) {
227
288
  if (expanded !== candidate && !isFile(expanded)) continue;
228
289
  const inspection = await inspectPython(expanded);
229
- if (inspection.compatible) return inspection;
290
+ if (inspection.compatible && inspection.missing.length === 0) return statusOf(inspection);
291
+ if (inspection.compatible && compatible == null) compatible = inspection;
230
292
  last = inspection;
231
293
  }
232
- return last;
294
+ if (compatible != null) return statusOf(compatible);
295
+ return {
296
+ state: "no-interpreter",
297
+ packages: [],
298
+ missing: [],
299
+ remedy: runtimeRemedy(),
300
+ ...last?.error == null ? {} : { error: last.error }
301
+ };
302
+ }
303
+ /** Run postinstall.mjs as a child process; resolve with an error summary or undefined. */
304
+ function runPostinstall() {
305
+ return new Promise((resolve) => {
306
+ execFile(process.execPath, [POSTINSTALL_SCRIPT], {
307
+ timeout: POSTINSTALL_TIMEOUT,
308
+ windowsHide: true,
309
+ maxBuffer: 8 * 1024 * 1024,
310
+ env: {
311
+ ...process.env,
312
+ ELECTRON_RUN_AS_NODE: "1",
313
+ DSH_OFFICE_POSTINSTALL: "1"
314
+ }
315
+ }, (error, stdout, stderr) => {
316
+ if (error == null) {
317
+ resolve(void 0);
318
+ return;
319
+ }
320
+ const detail = String(stderr || stdout || error.message).trim().split(/\r?\n/u).slice(-6).join("\n");
321
+ resolve(detail.length > 0 ? detail : error.message);
322
+ });
323
+ });
324
+ }
325
+ let cache;
326
+ let healing;
327
+ /**
328
+ * Resolve a runtime that is proven to work, self-healing via postinstall.mjs when it
329
+ * is missing or incomplete. Never returns a bare interpreter without dependencies.
330
+ */
331
+ function ensureRuntime(configFile = officePaths().configFile) {
332
+ if (cache != null && (cache.status.state === "ok" || Date.now() - cache.at < FAILURE_RETRY_DELAY)) return Promise.resolve(cache.status);
333
+ healing ??= (async () => {
334
+ const fresh = await inspectRuntime(configFile);
335
+ if (fresh.state === "ok") {
336
+ cache = {
337
+ status: fresh,
338
+ at: Date.now()
339
+ };
340
+ return fresh;
341
+ }
342
+ const healError = await runPostinstall();
343
+ const status = {
344
+ ...await inspectRuntime(configFile),
345
+ heal: {
346
+ attempted: true,
347
+ ...healError == null ? {} : { error: healError }
348
+ }
349
+ };
350
+ cache = {
351
+ status,
352
+ at: Date.now()
353
+ };
354
+ return status;
355
+ })().finally(() => {
356
+ healing = void 0;
357
+ });
358
+ return healing;
233
359
  }
234
360
  /** Whether the helper CLI file shipped with this package. */
235
361
  function cliExists() {
@@ -237,7 +363,6 @@ function cliExists() {
237
363
  }
238
364
  //#endregion
239
365
  //#region src/index.ts
240
- const execFileAsync = promisify(execFile);
241
366
  /** Services required by this host half. */
242
367
  const inject = ["skills", "tools"];
243
368
  /** Cordis plugin identity used in loader diagnostics. */
@@ -257,6 +382,26 @@ function findFile(candidates) {
257
382
  for (const path of candidates) if (existsSync(path)) return path;
258
383
  return null;
259
384
  }
385
+ const WPS_BINARIES = {
386
+ writer: "wps.exe",
387
+ presentation: "wpp.exe",
388
+ spreadsheet: "et.exe"
389
+ };
390
+ const WPS_ROOTS = [
391
+ process.env.WPS_OFFICE_HOME ?? "",
392
+ "C:\\Program Files\\Kingsoft\\WPS Office",
393
+ "C:\\Program Files (x86)\\Kingsoft\\WPS Office",
394
+ join(process.env.LOCALAPPDATA ?? "", "Kingsoft", "WPS Office")
395
+ ].filter(Boolean);
396
+ function wpsInstalled(binary) {
397
+ for (const root of WPS_ROOTS) {
398
+ if (existsSync(join(root, "office6", binary))) return true;
399
+ try {
400
+ for (const version of readdirSync(root)) if (existsSync(join(root, version, "office6", binary))) return true;
401
+ } catch {}
402
+ }
403
+ return false;
404
+ }
260
405
  function rendererFacts() {
261
406
  const libreoffice = findFile([
262
407
  process.env.SOFFICE ?? "",
@@ -273,36 +418,30 @@ function rendererFacts() {
273
418
  word: officeRoots.some((root) => existsSync(join(root, "WINWORD.EXE"))),
274
419
  powerpoint: officeRoots.some((root) => existsSync(join(root, "POWERPNT.EXE"))),
275
420
  excel: officeRoots.some((root) => existsSync(join(root, "EXCEL.EXE")))
421
+ },
422
+ kingsoftWps: {
423
+ writer: wpsInstalled(WPS_BINARIES.writer),
424
+ presentation: wpsInstalled(WPS_BINARIES.presentation),
425
+ spreadsheet: wpsInstalled(WPS_BINARIES.spreadsheet)
276
426
  }
277
427
  };
278
428
  }
279
- const PACKAGE_PROBE = `
280
- import importlib, json, sys
281
- targets = ${JSON.stringify(PYTHON_PACKAGES)}
282
- out = {}
283
- for item in targets:
284
- try:
285
- module = importlib.import_module(item["importName"])
286
- out[item["package"]] = {"installed": True, "version": str(getattr(module, "__version__", ""))}
287
- except Exception as exc:
288
- out[item["package"]] = {"installed": False, "error": f"{type(exc).__name__}: {exc}"}
289
- print(json.dumps({"python": sys.executable, "version": sys.version.split()[0], "packages": out}))
290
- `.trim();
291
- async function probePackages(python) {
429
+ const PACKAGE_ROOT = fileURLToPath(new URL("..", import.meta.url));
430
+ function pluginVersion() {
292
431
  try {
293
- const { stdout } = await execFileAsync(python, ["-c", PACKAGE_PROBE], {
294
- timeout: 3e4,
295
- windowsHide: true,
296
- maxBuffer: 2 * 1024 * 1024,
297
- env: {
298
- ...process.env,
299
- PYTHONIOENCODING: "utf-8"
300
- }
301
- });
302
- return JSON.parse(String(stdout).trim());
303
- } catch (error) {
304
- return { error: error instanceof Error ? error.message : String(error) };
432
+ const parsed = JSON.parse(readFileSync(join(PACKAGE_ROOT, "package.json"), "utf8"));
433
+ return typeof parsed.version === "string" ? parsed.version : "unknown";
434
+ } catch {
435
+ return "unknown";
436
+ }
437
+ }
438
+ /** Human-readable reason for a runtime that failed verification. */
439
+ function runtimeReason(runtime) {
440
+ if (runtime.state === "missing-dependencies") {
441
+ const names = runtime.missing.map((entry) => entry.package).join(", ");
442
+ return `Python ${runtime.python?.version ?? ""} is installed but missing packages: ${names}`.replace(" ", " ");
305
443
  }
444
+ return "no compatible Python 3.10–3.13 interpreter was found";
306
445
  }
307
446
  /** Register the four skills and a status tool, and expose the runtime paths in skill bodies. */
308
447
  function apply(ctx) {
@@ -350,12 +489,14 @@ function apply(ctx) {
350
489
  return;
351
490
  }
352
491
  const parsed = parseSkill(raw, path);
353
- const python = await resolvePython(paths.configFile);
354
- const pythonLine = python?.compatible === true ? `\n\n## Office runtime\n\nUse these verified absolute paths for every helper command. Quote paths that contain spaces.\n\n\`\`\`json\n${JSON.stringify({
355
- python: python.path,
492
+ const runtime = await ensureRuntime(paths.configFile);
493
+ const pythonLine = runtime.state === "ok" && runtime.python != null ? `\n\n## Office runtime\n\nUse these verified absolute paths for every helper command. Quote paths that contain spaces. \`skill\` is this skill's own directory: every script it names lives at \`<skill>/scripts/...\`; \`shared\` holds the cross-format workflow notes.\n\n\`\`\`json\n${JSON.stringify({
494
+ python: runtime.python.path,
356
495
  cli: paths.cli,
357
- version: python.version
358
- }, null, 2)}\n\`\`\`` : "\n\n## Office runtime\n\nNo compatible Python was found. Ask the user to run `pnpm rebuild dsh-office-kit` (or `npm rebuild dsh-office-kit`) and check the install output before attempting file work.";
496
+ skill: dirname(path).split("\\").join("/"),
497
+ shared: join(paths.skillsDir, "shared").split("\\").join("/"),
498
+ version: runtime.python.version
499
+ }, null, 2)}\n\`\`\`` : `\n\n## Office runtime\n\nNo working Python runtime is available: ${runtimeReason(runtime)}. Do not run helper commands or claim success. Tell the user to run \`${runtime.remedy}\`, then call \`office_status\` again.`;
359
500
  const { rank: _rank, locator: _locator, ...summary } = candidate;
360
501
  return {
361
502
  ...summary,
@@ -367,7 +508,7 @@ function apply(ctx) {
367
508
  ctx.effect(() => ctx.skills.registerProvider(() => provider), "office-suite: skills");
368
509
  ctx.effect(() => ctx.tools.register(defineTool({
369
510
  name: "office_status",
370
- description: "Report the Office runtime state: compatible Python, installed Python packages, helper CLI path, and document renderers (LibreOffice or Microsoft Office). Call this before Office file work.",
511
+ description: "Report the Office runtime state: compatible Python, installed Python packages, helper CLI path, and document renderers (LibreOffice, Microsoft Office, or WPS Office). Call this before Office file work.",
371
512
  parameters: {},
372
513
  output: {
373
514
  schema: { type: "string" },
@@ -376,27 +517,36 @@ function apply(ctx) {
376
517
  text: value
377
518
  }]
378
519
  },
379
- timeoutMs: 45e3,
520
+ timeoutMs: 6e5,
380
521
  async execute() {
381
- const python = await resolvePython(paths.configFile);
382
- const seen = await (python?.compatible === true ? probePackages(python.path) : Promise.resolve(null));
522
+ const runtime = await ensureRuntime(paths.configFile);
383
523
  const report = {
384
524
  plugin: {
385
525
  name,
386
- version: "0.1.0"
526
+ version: pluginVersion()
527
+ },
528
+ runtime: {
529
+ state: runtime.state,
530
+ venv: paths.venv,
531
+ ...runtime.state === "ok" ? {} : {
532
+ reason: runtimeReason(runtime),
533
+ remedy: runtime.remedy
534
+ },
535
+ ...runtime.error == null ? {} : { error: runtime.error },
536
+ ...runtime.heal == null ? {} : { heal: runtime.heal }
387
537
  },
388
- python: python == null ? { found: false } : {
538
+ python: runtime.python == null ? { found: false } : {
389
539
  found: true,
390
- path: python.path,
391
- version: python.version,
392
- compatible: python.compatible,
393
- ...python.error == null ? {} : { error: python.error }
540
+ path: runtime.python.path,
541
+ version: runtime.python.version,
542
+ compatible: runtime.python.compatible,
543
+ ...runtime.python.error == null ? {} : { error: runtime.python.error }
394
544
  },
395
545
  cli: {
396
546
  path: paths.cli,
397
547
  exists: cliExists()
398
548
  },
399
- packages: seen,
549
+ packages: runtime.packages,
400
550
  renderers: rendererFacts(),
401
551
  skills: candidates.map((candidate) => candidate.name)
402
552
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-office-kit",
3
3
  "type": "module",
4
- "version": "0.2.0",
4
+ "version": "0.3.0",
5
5
  "description": "Office documents, PDF, presentations, and spreadsheets for DeepSeek Harness",
6
6
  "author": "Hairy",
7
7
  "license": "MIT",