dsh-office-kit 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +99 -0
- package/assets/skills/office-documents/scripts/a11y_audit.py +201 -0
- package/assets/skills/office-documents/scripts/accept_tracked_changes.py +158 -0
- package/assets/skills/office-documents/scripts/add_tracked_replacements.py +155 -0
- package/assets/skills/office-documents/scripts/append_blocks.py +115 -0
- package/assets/skills/office-documents/scripts/apply_template_styles.py +111 -0
- package/assets/skills/office-documents/scripts/captions_and_crossrefs.py +109 -0
- package/assets/skills/office-documents/scripts/comments_add.py +143 -0
- package/assets/skills/office-documents/scripts/comments_apply_patch.py +121 -0
- package/assets/skills/office-documents/scripts/comments_extract.py +92 -0
- package/assets/skills/office-documents/scripts/comments_strip.py +81 -0
- package/assets/skills/office-documents/scripts/content_controls.py +199 -0
- package/assets/skills/office-documents/scripts/core_properties.py +131 -0
- package/assets/skills/office-documents/scripts/docx_kit.py +594 -0
- package/assets/skills/office-documents/scripts/docx_ooxml_patch.py +232 -0
- package/assets/skills/office-documents/scripts/docx_table_to_csv.py +106 -0
- package/assets/skills/office-documents/scripts/fields_materialize.py +73 -0
- package/assets/skills/office-documents/scripts/fields_report.py +66 -0
- package/assets/skills/office-documents/scripts/flatten_ref_fields.py +69 -0
- package/assets/skills/office-documents/scripts/footnotes_report.py +84 -0
- package/assets/skills/office-documents/scripts/google_docs_title_sanitize.py +103 -0
- package/assets/skills/office-documents/scripts/heading_audit.py +88 -0
- package/assets/skills/office-documents/scripts/images_audit.py +87 -0
- package/assets/skills/office-documents/scripts/insert_note.py +133 -0
- package/assets/skills/office-documents/scripts/insert_ref_fields.py +97 -0
- package/assets/skills/office-documents/scripts/insert_toc.py +91 -0
- package/assets/skills/office-documents/scripts/internal_nav.py +153 -0
- package/assets/skills/office-documents/scripts/make_fixtures.py +176 -0
- package/assets/skills/office-documents/scripts/merge_docx_append.py +246 -0
- package/assets/skills/office-documents/scripts/privacy_scrub.py +138 -0
- package/assets/skills/office-documents/scripts/redact_docx.py +120 -0
- package/assets/skills/office-documents/scripts/render_and_diff.py +108 -0
- package/assets/skills/office-documents/scripts/replace_text.py +161 -0
- package/assets/skills/office-documents/scripts/section_audit.py +98 -0
- package/assets/skills/office-documents/scripts/set_protection.py +99 -0
- package/assets/skills/office-documents/scripts/style_lint.py +118 -0
- package/assets/skills/office-documents/scripts/style_normalize.py +110 -0
- package/assets/skills/office-documents/scripts/table_geometry.py +174 -0
- package/assets/skills/office-documents/scripts/watermark_add.py +55 -0
- package/assets/skills/office-documents/scripts/watermark_audit_remove.py +74 -0
- package/assets/skills/office-documents/scripts/xlsx_to_docx_table.py +124 -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 +143 -0
- package/assets/skills/office-presentations/container_tools/core_properties.py +127 -0
- package/assets/skills/office-presentations/container_tools/create_montage.py +133 -0
- package/assets/skills/office-presentations/container_tools/inspect_presentation_layout_geometry.py +292 -0
- package/assets/skills/office-presentations/container_tools/inspect_presentation_package_integrity.py +164 -0
- package/assets/skills/office-presentations/container_tools/native_chart_title_gate.py +151 -0
- package/assets/skills/office-presentations/container_tools/native_quantitative_chart_gate.py +206 -0
- package/assets/skills/office-presentations/container_tools/native_table_arithmetic_gate.py +219 -0
- package/assets/skills/office-presentations/container_tools/ooxml_helpers.py +352 -0
- package/assets/skills/office-presentations/container_tools/preview_render.py +382 -0
- package/assets/skills/office-presentations/container_tools/render_slides.py +146 -0
- package/assets/skills/office-presentations/container_tools/replace_text.py +175 -0
- package/assets/skills/office-presentations/container_tools/template_source_fidelity_gate.py +236 -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 +183 -0
- package/assets/skills/office-presentations/template_following_scripts/inspect_template_deck.py +161 -0
- package/assets/skills/office-presentations/template_following_scripts/make_contact_sheet.py +118 -0
- package/assets/skills/office-presentations/template_following_scripts/prepare_template_starter_deck.py +246 -0
- package/assets/skills/office-presentations/template_following_scripts/validate_template_plan.py +192 -0
- package/assets/skills/office-spreadsheets/SKILL.md +105 -101
- package/assets/skills/office-spreadsheets/container_tools/add_chart.py +243 -0
- package/assets/skills/office-spreadsheets/container_tools/add_image.py +114 -0
- package/assets/skills/office-spreadsheets/container_tools/append_rows.py +129 -0
- package/assets/skills/office-spreadsheets/container_tools/diff_workbooks.py +313 -0
- package/assets/skills/office-spreadsheets/container_tools/inspect_workbook.py +405 -0
- package/assets/skills/office-spreadsheets/container_tools/recalc_gate.py +287 -0
- package/assets/skills/office-spreadsheets/container_tools/set_cells.py +142 -0
- package/assets/skills/office-spreadsheets/container_tools/sheet_ops.py +141 -0
- package/assets/skills/office-spreadsheets/container_tools/style_cells.py +140 -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 +260 -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 +246 -89
- package/package.json +1 -1
- package/postinstall.mjs +92 -82
- package/python/office_cli.py +213 -254
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,98 @@ 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
|
+
/** A cached `ok` is only worth reusing while its interpreter still exists on disk. */
|
|
328
|
+
function runtimeAlive(status) {
|
|
329
|
+
return status.state !== "ok" || status.python != null && isFile(status.python.path);
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Resolve a runtime that is proven to work, self-healing via postinstall.mjs when it
|
|
333
|
+
* is missing or incomplete. Never returns a bare interpreter without dependencies.
|
|
334
|
+
* Pass `force` for an explicit status check that must not trust the process cache.
|
|
335
|
+
*/
|
|
336
|
+
function ensureRuntime(configFile = officePaths().configFile, force = false) {
|
|
337
|
+
if (!force && cache != null) {
|
|
338
|
+
if (cache.status.state === "ok" ? runtimeAlive(cache.status) : Date.now() - cache.at < FAILURE_RETRY_DELAY) return Promise.resolve(cache.status);
|
|
339
|
+
}
|
|
340
|
+
healing ??= (async () => {
|
|
341
|
+
const probed = await inspectRuntime(configFile);
|
|
342
|
+
if (probed.state === "ok") {
|
|
343
|
+
cache = {
|
|
344
|
+
status: probed,
|
|
345
|
+
at: Date.now()
|
|
346
|
+
};
|
|
347
|
+
return probed;
|
|
348
|
+
}
|
|
349
|
+
const healError = await runPostinstall();
|
|
350
|
+
const status = {
|
|
351
|
+
...await inspectRuntime(configFile),
|
|
352
|
+
heal: {
|
|
353
|
+
attempted: true,
|
|
354
|
+
...healError == null ? {} : { error: healError }
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
cache = {
|
|
358
|
+
status,
|
|
359
|
+
at: Date.now()
|
|
360
|
+
};
|
|
361
|
+
return status;
|
|
362
|
+
})().finally(() => {
|
|
363
|
+
healing = void 0;
|
|
364
|
+
});
|
|
365
|
+
return healing;
|
|
233
366
|
}
|
|
234
367
|
/** Whether the helper CLI file shipped with this package. */
|
|
235
368
|
function cliExists() {
|
|
@@ -237,7 +370,6 @@ function cliExists() {
|
|
|
237
370
|
}
|
|
238
371
|
//#endregion
|
|
239
372
|
//#region src/index.ts
|
|
240
|
-
const execFileAsync = promisify(execFile);
|
|
241
373
|
/** Services required by this host half. */
|
|
242
374
|
const inject = ["skills", "tools"];
|
|
243
375
|
/** Cordis plugin identity used in loader diagnostics. */
|
|
@@ -257,6 +389,26 @@ function findFile(candidates) {
|
|
|
257
389
|
for (const path of candidates) if (existsSync(path)) return path;
|
|
258
390
|
return null;
|
|
259
391
|
}
|
|
392
|
+
const WPS_BINARIES = {
|
|
393
|
+
writer: "wps.exe",
|
|
394
|
+
presentation: "wpp.exe",
|
|
395
|
+
spreadsheet: "et.exe"
|
|
396
|
+
};
|
|
397
|
+
const WPS_ROOTS = [
|
|
398
|
+
process.env.WPS_OFFICE_HOME ?? "",
|
|
399
|
+
"C:\\Program Files\\Kingsoft\\WPS Office",
|
|
400
|
+
"C:\\Program Files (x86)\\Kingsoft\\WPS Office",
|
|
401
|
+
join(process.env.LOCALAPPDATA ?? "", "Kingsoft", "WPS Office")
|
|
402
|
+
].filter(Boolean);
|
|
403
|
+
function wpsInstalled(binary) {
|
|
404
|
+
for (const root of WPS_ROOTS) {
|
|
405
|
+
if (existsSync(join(root, "office6", binary))) return true;
|
|
406
|
+
try {
|
|
407
|
+
for (const version of readdirSync(root)) if (existsSync(join(root, version, "office6", binary))) return true;
|
|
408
|
+
} catch {}
|
|
409
|
+
}
|
|
410
|
+
return false;
|
|
411
|
+
}
|
|
260
412
|
function rendererFacts() {
|
|
261
413
|
const libreoffice = findFile([
|
|
262
414
|
process.env.SOFFICE ?? "",
|
|
@@ -273,36 +425,30 @@ function rendererFacts() {
|
|
|
273
425
|
word: officeRoots.some((root) => existsSync(join(root, "WINWORD.EXE"))),
|
|
274
426
|
powerpoint: officeRoots.some((root) => existsSync(join(root, "POWERPNT.EXE"))),
|
|
275
427
|
excel: officeRoots.some((root) => existsSync(join(root, "EXCEL.EXE")))
|
|
428
|
+
},
|
|
429
|
+
kingsoftWps: {
|
|
430
|
+
writer: wpsInstalled(WPS_BINARIES.writer),
|
|
431
|
+
presentation: wpsInstalled(WPS_BINARIES.presentation),
|
|
432
|
+
spreadsheet: wpsInstalled(WPS_BINARIES.spreadsheet)
|
|
276
433
|
}
|
|
277
434
|
};
|
|
278
435
|
}
|
|
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) {
|
|
436
|
+
const PACKAGE_ROOT = fileURLToPath(new URL("..", import.meta.url));
|
|
437
|
+
function pluginVersion() {
|
|
292
438
|
try {
|
|
293
|
-
const
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
return { error: error instanceof Error ? error.message : String(error) };
|
|
439
|
+
const parsed = JSON.parse(readFileSync(join(PACKAGE_ROOT, "package.json"), "utf8"));
|
|
440
|
+
return typeof parsed.version === "string" ? parsed.version : "unknown";
|
|
441
|
+
} catch {
|
|
442
|
+
return "unknown";
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
/** Human-readable reason for a runtime that failed verification. */
|
|
446
|
+
function runtimeReason(runtime) {
|
|
447
|
+
if (runtime.state === "missing-dependencies") {
|
|
448
|
+
const names = runtime.missing.map((entry) => entry.package).join(", ");
|
|
449
|
+
return `Python ${runtime.python?.version ?? ""} is installed but missing packages: ${names}`.replace(" ", " ");
|
|
305
450
|
}
|
|
451
|
+
return "no compatible Python 3.10–3.13 interpreter was found";
|
|
306
452
|
}
|
|
307
453
|
/** Register the four skills and a status tool, and expose the runtime paths in skill bodies. */
|
|
308
454
|
function apply(ctx) {
|
|
@@ -350,12 +496,14 @@ function apply(ctx) {
|
|
|
350
496
|
return;
|
|
351
497
|
}
|
|
352
498
|
const parsed = parseSkill(raw, path);
|
|
353
|
-
const
|
|
354
|
-
const pythonLine =
|
|
355
|
-
python: python.path,
|
|
499
|
+
const runtime = await ensureRuntime(paths.configFile);
|
|
500
|
+
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({
|
|
501
|
+
python: runtime.python.path,
|
|
356
502
|
cli: paths.cli,
|
|
357
|
-
|
|
358
|
-
|
|
503
|
+
skill: dirname(path).split("\\").join("/"),
|
|
504
|
+
shared: join(paths.skillsDir, "shared").split("\\").join("/"),
|
|
505
|
+
version: runtime.python.version
|
|
506
|
+
}, 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
507
|
const { rank: _rank, locator: _locator, ...summary } = candidate;
|
|
360
508
|
return {
|
|
361
509
|
...summary,
|
|
@@ -367,7 +515,7 @@ function apply(ctx) {
|
|
|
367
515
|
ctx.effect(() => ctx.skills.registerProvider(() => provider), "office-suite: skills");
|
|
368
516
|
ctx.effect(() => ctx.tools.register(defineTool({
|
|
369
517
|
name: "office_status",
|
|
370
|
-
description: "Report the Office runtime state: compatible Python, installed Python packages, helper CLI path, and document renderers (LibreOffice or
|
|
518
|
+
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
519
|
parameters: {},
|
|
372
520
|
output: {
|
|
373
521
|
schema: { type: "string" },
|
|
@@ -376,27 +524,36 @@ function apply(ctx) {
|
|
|
376
524
|
text: value
|
|
377
525
|
}]
|
|
378
526
|
},
|
|
379
|
-
timeoutMs:
|
|
527
|
+
timeoutMs: 6e5,
|
|
380
528
|
async execute() {
|
|
381
|
-
const
|
|
382
|
-
const seen = await (python?.compatible === true ? probePackages(python.path) : Promise.resolve(null));
|
|
529
|
+
const runtime = await ensureRuntime(paths.configFile, true);
|
|
383
530
|
const report = {
|
|
384
531
|
plugin: {
|
|
385
532
|
name,
|
|
386
|
-
version:
|
|
533
|
+
version: pluginVersion()
|
|
534
|
+
},
|
|
535
|
+
runtime: {
|
|
536
|
+
state: runtime.state,
|
|
537
|
+
venv: paths.venv,
|
|
538
|
+
...runtime.state === "ok" ? {} : {
|
|
539
|
+
reason: runtimeReason(runtime),
|
|
540
|
+
remedy: runtime.remedy
|
|
541
|
+
},
|
|
542
|
+
...runtime.error == null ? {} : { error: runtime.error },
|
|
543
|
+
...runtime.heal == null ? {} : { heal: runtime.heal }
|
|
387
544
|
},
|
|
388
|
-
python: python == null ? { found: false } : {
|
|
545
|
+
python: runtime.python == null ? { found: false } : {
|
|
389
546
|
found: true,
|
|
390
|
-
path: python.path,
|
|
391
|
-
version: python.version,
|
|
392
|
-
compatible: python.compatible,
|
|
393
|
-
...python.error == null ? {} : { error: python.error }
|
|
547
|
+
path: runtime.python.path,
|
|
548
|
+
version: runtime.python.version,
|
|
549
|
+
compatible: runtime.python.compatible,
|
|
550
|
+
...runtime.python.error == null ? {} : { error: runtime.python.error }
|
|
394
551
|
},
|
|
395
552
|
cli: {
|
|
396
553
|
path: paths.cli,
|
|
397
554
|
exists: cliExists()
|
|
398
555
|
},
|
|
399
|
-
packages:
|
|
556
|
+
packages: runtime.packages,
|
|
400
557
|
renderers: rendererFacts(),
|
|
401
558
|
skills: candidates.map((candidate) => candidate.name)
|
|
402
559
|
};
|