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.
- package/CAPABILITY.md +147 -68
- package/README.md +3 -1
- package/assets/skills/excel-live-control/SKILL.md +234 -0
- package/assets/skills/excel-live-control/container_tools/live_excel.py +1589 -0
- package/assets/skills/excel-live-control/domain_guidance/financial_models.md +86 -0
- package/assets/skills/excel-live-control/domain_guidance/healthcare.md +58 -0
- package/assets/skills/excel-live-control/domain_guidance/marketing_advertising.md +53 -0
- package/assets/skills/excel-live-control/domain_guidance/scientific_research.md +53 -0
- package/assets/skills/excel-live-control/references/office-automation.md +207 -0
- package/assets/skills/excel-live-control/references/read_only_qna.md +59 -0
- package/assets/skills/excel-live-control/style_guidelines.md +87 -0
- package/assets/skills/excel-live-control/workflows/create_workflows.md +78 -0
- package/assets/skills/excel-live-control/workflows/edit_workflows.md +72 -0
- package/assets/skills/office-documents/SKILL.md +90 -77
- package/assets/skills/office-documents/examples/end_to_end_smoke_test.md +86 -0
- package/assets/skills/office-documents/ooxml/comments.md +40 -0
- package/assets/skills/office-documents/ooxml/hyperlinks_and_fields.md +66 -0
- package/assets/skills/office-documents/ooxml/rels_and_content_types.md +62 -0
- package/assets/skills/office-documents/ooxml/tracked_changes.md +53 -0
- package/assets/skills/office-documents/references/docx-structure.md +83 -0
- package/assets/skills/office-documents/references/template-elicitation.md +49 -0
- package/assets/skills/office-documents/references/writing-quality.md +49 -0
- package/assets/skills/office-documents/render_docx.py +92 -0
- package/assets/skills/office-documents/scripts/a11y_audit.py +194 -0
- package/assets/skills/office-documents/scripts/accept_tracked_changes.py +151 -0
- package/assets/skills/office-documents/scripts/add_tracked_replacements.py +148 -0
- package/assets/skills/office-documents/scripts/append_blocks.py +108 -0
- package/assets/skills/office-documents/scripts/apply_template_styles.py +104 -0
- package/assets/skills/office-documents/scripts/captions_and_crossrefs.py +102 -0
- package/assets/skills/office-documents/scripts/comments_add.py +136 -0
- package/assets/skills/office-documents/scripts/comments_apply_patch.py +114 -0
- package/assets/skills/office-documents/scripts/comments_extract.py +85 -0
- package/assets/skills/office-documents/scripts/comments_strip.py +74 -0
- package/assets/skills/office-documents/scripts/content_controls.py +192 -0
- package/assets/skills/office-documents/scripts/core_properties.py +124 -0
- package/assets/skills/office-documents/scripts/docx_kit.py +587 -0
- package/assets/skills/office-documents/scripts/docx_ooxml_patch.py +225 -0
- package/assets/skills/office-documents/scripts/docx_table_to_csv.py +99 -0
- package/assets/skills/office-documents/scripts/fields_materialize.py +66 -0
- package/assets/skills/office-documents/scripts/fields_report.py +59 -0
- package/assets/skills/office-documents/scripts/flatten_ref_fields.py +62 -0
- package/assets/skills/office-documents/scripts/footnotes_report.py +77 -0
- package/assets/skills/office-documents/scripts/google_docs_title_sanitize.py +96 -0
- package/assets/skills/office-documents/scripts/heading_audit.py +81 -0
- package/assets/skills/office-documents/scripts/images_audit.py +80 -0
- package/assets/skills/office-documents/scripts/insert_note.py +126 -0
- package/assets/skills/office-documents/scripts/insert_ref_fields.py +90 -0
- package/assets/skills/office-documents/scripts/insert_toc.py +84 -0
- package/assets/skills/office-documents/scripts/internal_nav.py +146 -0
- package/assets/skills/office-documents/scripts/make_fixtures.py +169 -0
- package/assets/skills/office-documents/scripts/merge_docx_append.py +239 -0
- package/assets/skills/office-documents/scripts/privacy_scrub.py +131 -0
- package/assets/skills/office-documents/scripts/redact_docx.py +113 -0
- package/assets/skills/office-documents/scripts/render_and_diff.py +101 -0
- package/assets/skills/office-documents/scripts/replace_text.py +154 -0
- package/assets/skills/office-documents/scripts/section_audit.py +91 -0
- package/assets/skills/office-documents/scripts/set_protection.py +92 -0
- package/assets/skills/office-documents/scripts/style_lint.py +111 -0
- package/assets/skills/office-documents/scripts/style_normalize.py +103 -0
- package/assets/skills/office-documents/scripts/table_geometry.py +167 -0
- package/assets/skills/office-documents/scripts/watermark_add.py +48 -0
- package/assets/skills/office-documents/scripts/watermark_audit_remove.py +67 -0
- package/assets/skills/office-documents/scripts/xlsx_to_docx_table.py +117 -0
- package/assets/skills/office-documents/tasks/accessibility_a11y.md +45 -0
- package/assets/skills/office-documents/tasks/captions_crossrefs.md +49 -0
- package/assets/skills/office-documents/tasks/clean_tracked_changes.md +49 -0
- package/assets/skills/office-documents/tasks/comments_manage.md +59 -0
- package/assets/skills/office-documents/tasks/compare_diff.md +40 -0
- package/assets/skills/office-documents/tasks/create_edit.md +107 -0
- package/assets/skills/office-documents/tasks/equations.md +59 -0
- package/assets/skills/office-documents/tasks/fields_update.md +44 -0
- package/assets/skills/office-documents/tasks/fixtures_edge_cases.md +50 -0
- package/assets/skills/office-documents/tasks/footnotes_endnotes.md +38 -0
- package/assets/skills/office-documents/tasks/forms_content_controls.md +48 -0
- package/assets/skills/office-documents/tasks/headings_numbering.md +55 -0
- package/assets/skills/office-documents/tasks/images_figures.md +63 -0
- package/assets/skills/office-documents/tasks/multi_doc_merge.md +43 -0
- package/assets/skills/office-documents/tasks/navigation_internal_links.md +38 -0
- package/assets/skills/office-documents/tasks/privacy_scrub_metadata.md +45 -0
- package/assets/skills/office-documents/tasks/protection_restrict_editing.md +41 -0
- package/assets/skills/office-documents/tasks/read_review.md +50 -0
- package/assets/skills/office-documents/tasks/redaction_anonymization.md +42 -0
- package/assets/skills/office-documents/tasks/sections_layout.md +54 -0
- package/assets/skills/office-documents/tasks/style_lint_normalize.md +44 -0
- package/assets/skills/office-documents/tasks/tables_spreadsheets.md +64 -0
- package/assets/skills/office-documents/tasks/templates_style_packs.md +53 -0
- package/assets/skills/office-documents/tasks/toc_workflow.md +39 -0
- package/assets/skills/office-documents/tasks/verify_render.md +50 -0
- package/assets/skills/office-documents/tasks/watermarks_background.md +35 -0
- package/assets/skills/office-documents/troubleshooting/libreoffice_headless.md +47 -0
- package/assets/skills/office-documents/troubleshooting/run_splitting.md +36 -0
- package/assets/skills/office-pdf/SKILL.md +73 -56
- package/assets/skills/office-pdf/references/form-fields.md +56 -0
- package/assets/skills/office-pdf/references/geometry.md +44 -0
- package/assets/skills/office-pdf/references/layout-quality.md +41 -0
- package/assets/skills/office-pdf/references/limits.md +58 -0
- package/assets/skills/office-pdf/scripts/extract_text.py +172 -0
- package/assets/skills/office-pdf/scripts/forms.py +556 -0
- package/assets/skills/office-pdf/scripts/metadata.py +161 -0
- package/assets/skills/office-pdf/scripts/page_ops.py +198 -0
- package/assets/skills/office-pdf/scripts/redact_region.py +347 -0
- package/assets/skills/office-pdf/scripts/render_pages.py +207 -0
- package/assets/skills/office-pdf/scripts/split_pages.py +155 -0
- package/assets/skills/office-pdf/scripts/stamp_overlay.py +199 -0
- package/assets/skills/office-pdf/tasks/annotations.md +98 -0
- package/assets/skills/office-pdf/tasks/create.md +75 -0
- package/assets/skills/office-pdf/tasks/edit-pages.md +71 -0
- package/assets/skills/office-pdf/tasks/forms.md +67 -0
- package/assets/skills/office-pdf/tasks/read.md +62 -0
- package/assets/skills/office-pdf/tasks/redact.md +72 -0
- package/assets/skills/office-pdf/tasks/stamp.md +60 -0
- package/assets/skills/office-presentations/SKILL.md +141 -87
- package/assets/skills/office-presentations/container_tools/add_slides.py +136 -0
- package/assets/skills/office-presentations/container_tools/core_properties.py +120 -0
- package/assets/skills/office-presentations/container_tools/create_montage.py +126 -0
- package/assets/skills/office-presentations/container_tools/inspect_presentation_layout_geometry.py +285 -0
- package/assets/skills/office-presentations/container_tools/inspect_presentation_package_integrity.py +157 -0
- package/assets/skills/office-presentations/container_tools/native_chart_title_gate.py +144 -0
- package/assets/skills/office-presentations/container_tools/native_quantitative_chart_gate.py +199 -0
- package/assets/skills/office-presentations/container_tools/native_table_arithmetic_gate.py +212 -0
- package/assets/skills/office-presentations/container_tools/ooxml_helpers.py +345 -0
- package/assets/skills/office-presentations/container_tools/preview_render.py +375 -0
- package/assets/skills/office-presentations/container_tools/render_slides.py +139 -0
- package/assets/skills/office-presentations/container_tools/replace_text.py +168 -0
- package/assets/skills/office-presentations/container_tools/template_source_fidelity_gate.py +229 -0
- package/assets/skills/office-presentations/domain_guidance/finance.md +66 -0
- package/assets/skills/office-presentations/recipes/README.md +71 -0
- package/assets/skills/office-presentations/recipes/autolayout.md +88 -0
- package/assets/skills/office-presentations/recipes/connectors.md +83 -0
- package/assets/skills/office-presentations/recipes/images.md +80 -0
- package/assets/skills/office-presentations/recipes/master-and-layouts.md +127 -0
- package/assets/skills/office-presentations/recipes/native-charts.md +87 -0
- package/assets/skills/office-presentations/recipes/rich-text-runs.md +86 -0
- package/assets/skills/office-presentations/recipes/speaker-notes.md +65 -0
- package/assets/skills/office-presentations/recipes/tables.md +88 -0
- package/assets/skills/office-presentations/recipes/textbox-at-coordinate.md +71 -0
- package/assets/skills/office-presentations/recipes/theme-tokens.md +87 -0
- package/assets/skills/office-presentations/references/authoring-workflow.md +65 -0
- package/assets/skills/office-presentations/references/finalization.md +63 -0
- package/assets/skills/office-presentations/references/native-bullets.md +78 -0
- package/assets/skills/office-presentations/references/native-evidence.md +67 -0
- package/assets/skills/office-presentations/references/style-guidelines.md +66 -0
- package/assets/skills/office-presentations/references/templates.md +76 -0
- package/assets/skills/office-presentations/routing/google_slides.md +45 -0
- package/assets/skills/office-presentations/tasks/apply-annotations.md +83 -0
- package/assets/skills/office-presentations/tasks/charts-tables-notes.md +79 -0
- package/assets/skills/office-presentations/tasks/create-deck.md +106 -0
- package/assets/skills/office-presentations/tasks/edit-deck.md +93 -0
- package/assets/skills/office-presentations/tasks/inspect-and-verify.md +107 -0
- package/assets/skills/office-presentations/tasks/template-following.md +132 -0
- package/assets/skills/office-presentations/template_following_scripts/check_template_fidelity.py +176 -0
- package/assets/skills/office-presentations/template_following_scripts/inspect_template_deck.py +154 -0
- package/assets/skills/office-presentations/template_following_scripts/make_contact_sheet.py +111 -0
- package/assets/skills/office-presentations/template_following_scripts/prepare_template_starter_deck.py +239 -0
- package/assets/skills/office-presentations/template_following_scripts/validate_template_plan.py +185 -0
- package/assets/skills/office-spreadsheets/SKILL.md +105 -101
- package/assets/skills/office-spreadsheets/container_tools/add_chart.py +236 -0
- package/assets/skills/office-spreadsheets/container_tools/add_image.py +107 -0
- package/assets/skills/office-spreadsheets/container_tools/append_rows.py +122 -0
- package/assets/skills/office-spreadsheets/container_tools/diff_workbooks.py +306 -0
- package/assets/skills/office-spreadsheets/container_tools/inspect_workbook.py +398 -0
- package/assets/skills/office-spreadsheets/container_tools/recalc_gate.py +275 -0
- package/assets/skills/office-spreadsheets/container_tools/set_cells.py +135 -0
- package/assets/skills/office-spreadsheets/container_tools/sheet_ops.py +134 -0
- package/assets/skills/office-spreadsheets/container_tools/style_cells.py +133 -0
- package/assets/skills/office-spreadsheets/recipes/charts.md +66 -0
- package/assets/skills/office-spreadsheets/recipes/conditional-formats.md +69 -0
- package/assets/skills/office-spreadsheets/recipes/data-tables.md +58 -0
- package/assets/skills/office-spreadsheets/recipes/named-ranges.md +41 -0
- package/assets/skills/office-spreadsheets/recipes/number-formats.md +72 -0
- package/assets/skills/office-spreadsheets/recipes/sparklines.md +43 -0
- package/assets/skills/office-spreadsheets/references/charts.md +63 -0
- package/assets/skills/office-spreadsheets/references/domain/financial-models.md +40 -0
- package/assets/skills/office-spreadsheets/references/domain/healthcare.md +22 -0
- package/assets/skills/office-spreadsheets/references/domain/marketing.md +28 -0
- package/assets/skills/office-spreadsheets/references/domain/research.md +22 -0
- package/assets/skills/office-spreadsheets/references/formula-patterns.md +78 -0
- package/assets/skills/office-spreadsheets/references/style-guidelines.md +69 -0
- package/assets/skills/office-spreadsheets/references/workbook-structure.md +60 -0
- package/assets/skills/office-spreadsheets/routing/google-sheets.md +36 -0
- package/assets/skills/office-spreadsheets/tasks/analyze-workbook.md +37 -0
- package/assets/skills/office-spreadsheets/tasks/annotations.md +51 -0
- package/assets/skills/office-spreadsheets/tasks/create-workbook.md +66 -0
- package/assets/skills/office-spreadsheets/tasks/diff-workbooks.md +38 -0
- package/assets/skills/office-spreadsheets/tasks/edit-workbook.md +69 -0
- package/assets/skills/office-spreadsheets/tasks/inspect-workbook.md +38 -0
- package/assets/skills/office-spreadsheets/tasks/recalc-and-check.md +57 -0
- package/assets/skills/shared/annotation-recipes.md +252 -0
- package/assets/skills/shared/check_runtime.py +253 -0
- package/assets/skills/shared/common-workflow.md +139 -0
- package/assets/skills/shared/scripts/ensure_raster_image.py +187 -0
- package/dist/client.js +4 -4
- package/dist/client.js.map +1 -1
- package/dist/index.js +239 -89
- package/package.json +1 -1
- package/postinstall.mjs +92 -82
- package/python/office_cli.py +171 -246
package/dist/index.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import {
|
|
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 {
|
|
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
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
224
|
-
|
|
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
|
|
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
|
|
280
|
-
|
|
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
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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
|
|
354
|
-
const pythonLine =
|
|
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
|
-
|
|
358
|
-
|
|
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
|
|
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:
|
|
520
|
+
timeoutMs: 6e5,
|
|
380
521
|
async execute() {
|
|
381
|
-
const
|
|
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:
|
|
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:
|
|
549
|
+
packages: runtime.packages,
|
|
400
550
|
renderers: rendererFacts(),
|
|
401
551
|
skills: candidates.map((candidate) => candidate.name)
|
|
402
552
|
};
|