regen.mde 0.2.2

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 (60) hide show
  1. package/LICENSE +16 -0
  2. package/README.md +295 -0
  3. package/bin/build-corpus-editor.js +81 -0
  4. package/bin/build-corpus.js +41 -0
  5. package/bin/postinstall.js +187 -0
  6. package/bin/regen-mdeditor-install.js +27 -0
  7. package/bin/regen-mdeditor-uninstall.js +19 -0
  8. package/bin/validate-katex.js +93 -0
  9. package/desktop/BuildCorpusEditor/BuildCorpusBridge.cs +270 -0
  10. package/desktop/BuildCorpusEditor/BuildCorpusEditor.csproj +22 -0
  11. package/desktop/BuildCorpusEditor/EditorForm.cs +540 -0
  12. package/desktop/BuildCorpusEditor/Program.cs +81 -0
  13. package/desktop/BuildCorpusEditor/app.manifest +16 -0
  14. package/dist/release/regen.mde-0.2.2-win-x64-setup.exe +0 -0
  15. package/dist/release/regen.mde-0.2.2-win-x64.zip +0 -0
  16. package/dist/windows-editor/BuildCorpusEditor.deps.json +83 -0
  17. package/dist/windows-editor/BuildCorpusEditor.dll +0 -0
  18. package/dist/windows-editor/BuildCorpusEditor.exe +0 -0
  19. package/dist/windows-editor/BuildCorpusEditor.pdb +0 -0
  20. package/dist/windows-editor/BuildCorpusEditor.runtimeconfig.json +19 -0
  21. package/dist/windows-editor/Microsoft.Web.WebView2.Core.dll +0 -0
  22. package/dist/windows-editor/Microsoft.Web.WebView2.Core.xml +6817 -0
  23. package/dist/windows-editor/Microsoft.Web.WebView2.WinForms.dll +0 -0
  24. package/dist/windows-editor/Microsoft.Web.WebView2.WinForms.xml +510 -0
  25. package/dist/windows-editor/Microsoft.Web.WebView2.Wpf.dll +0 -0
  26. package/dist/windows-editor/Microsoft.Web.WebView2.Wpf.xml +1902 -0
  27. package/dist/windows-editor/WebView2Loader.dll +0 -0
  28. package/dist/windows-editor/runtimes/win-x64/native/WebView2Loader.dll +0 -0
  29. package/dist/windows-editor/wwwroot/assets/index-DjJ6xmhy.js +326 -0
  30. package/dist/windows-editor/wwwroot/assets/index-_dwMNNsm.css +1 -0
  31. package/dist/windows-editor/wwwroot/index.html +22 -0
  32. package/editor-web/index.html +21 -0
  33. package/editor-web/src/main.jsx +399 -0
  34. package/editor-web/src/styles.css +602 -0
  35. package/editor-web/vite.config.js +13 -0
  36. package/examples/build-corpus.config.example.json +21 -0
  37. package/installer/install-regen-mde.ps1 +175 -0
  38. package/installer/regen-mde.nsi +81 -0
  39. package/package.json +86 -0
  40. package/pyproject.toml +33 -0
  41. package/requirements.txt +4 -0
  42. package/scripts/build-windows-editor.ps1 +47 -0
  43. package/scripts/package-windows-editor.ps1 +90 -0
  44. package/scripts/run-corpus.ps1 +28 -0
  45. package/scripts/run-editor-implementation-plane.ps1 +203 -0
  46. package/scripts/run-required-tests.ps1 +98 -0
  47. package/scripts/run-smoke.ps1 +28 -0
  48. package/src/build_corpus/__init__.py +3 -0
  49. package/src/build_corpus/docx_exporter.py +798 -0
  50. package/src/build_corpus/exporter.py +1195 -0
  51. package/src/build_corpus/ppt_exporter.py +532 -0
  52. package/src/build_corpus/templates/__init__.py +1 -0
  53. package/src/build_corpus/templates/md-to-word-template.dotx +0 -0
  54. package/src/build_corpus/validate_assets.py +46 -0
  55. package/tools/audit_corpus.py +203 -0
  56. package/tools/collect_microsoft_word_templates.py +228 -0
  57. package/tools/collect_online_docx_corpus.py +272 -0
  58. package/tools/collect_online_pptx_corpus.py +252 -0
  59. package/tools/compare_pptx_inputs_outputs.py +87 -0
  60. package/tools/roundtrip_docx_corpus.py +171 -0
package/LICENSE ADDED
@@ -0,0 +1,16 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+
4
+ Copyright 2026 LIFE AI
5
+
6
+ Licensed under the Apache License, Version 2.0 (the "License");
7
+ you may not use this file except in compliance with the License.
8
+ You may obtain a copy of the License at
9
+
10
+ https://www.apache.org/licenses/LICENSE-2.0
11
+
12
+ Unless required by applicable law or agreed to in writing, software
13
+ distributed under the License is distributed on an "AS IS" BASIS,
14
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ See the License for the specific language governing permissions and
16
+ limitations under the License.
package/README.md ADDED
@@ -0,0 +1,295 @@
1
+ # regen.mde
2
+
3
+ regen.mde is the Windows editor and conversion suite for Markdown, Word, and PowerPoint files. Its `build-corpus` CLI converts `.docx`, `.pptx`, and `.ppt` files to Markdown while preserving the pieces that usually break in generic converters:
4
+
5
+ - Word OMML equations as KaTeX-readable TeX
6
+ - embedded images as local assets, base64 data URIs, or S3/R2-hosted URLs
7
+ - Markdown tables for simple Word tables
8
+ - HTML table fallback for complex tables
9
+ - headings, lists, links, bold, italic, inline code, and code-style paragraphs
10
+ - PowerPoint slide extraction with slide title detection, table mapping, and repetitive footer suppression
11
+
12
+ ## Install
13
+
14
+ Python is the native runtime:
15
+
16
+ ```powershell
17
+ pip install build-corpus
18
+ ```
19
+
20
+ The npm package ships the Windows installer plus the conversion CLI:
21
+
22
+ ```powershell
23
+ npm pack regen.mde
24
+ ```
25
+
26
+ Extract the package and run `dist\release\regen.mde-<version>-win-x64-setup.exe` for a normal Windows install. The installer creates Start Menu entries for `regen.mde` and `Uninstall regen.mde`, registers right-click Explorer verbs for `.docx` and `.md`, and removes those entries during uninstall.
27
+
28
+ The legacy global npm command path is still supported for automation:
29
+
30
+ ```powershell
31
+ npm install -g regen.mde
32
+ ```
33
+
34
+ On Windows, the installer and supported automation paths add right-click Explorer menus for `.docx` and `.md` files under `Life AI`:
35
+
36
+ - `Life AI -> Open in regen.mde`
37
+ - opens `.md` directly and opens `.docx` by converting it into editable Markdown first
38
+ - `Life AI -> Convert to Markdown`
39
+ - runs `build-corpus "%1" --out-same-dir`
40
+ - writes `.md`, `assets`, and reports beside the source document
41
+ - `Life AI -> Convert to Word`
42
+ - runs `build-corpus "%1" --to word --out-same-dir`
43
+ - writes `.docx` and export report beside the source document
44
+
45
+ Set `BUILD_CORPUS_SKIP_WINDOWS_MENU=1` before a global npm install if you do not want the Explorer menu.
46
+ Set `BUILD_CORPUS_SKIP_EDITOR=1` before a global npm install if you want the CLI conversion verbs but not the editor open verbs.
47
+
48
+ To remove the Windows Explorer menus without uninstalling the package:
49
+
50
+ ```powershell
51
+ build-corpus --uninstall-windows
52
+ ```
53
+
54
+ If you uninstall the global npm package, `build-corpus` now removes those Explorer menu entries automatically during uninstall.
55
+
56
+ For a project-local install, use `npx`:
57
+
58
+ ```powershell
59
+ npm install regen.mde
60
+ npx build-corpus --help
61
+ ```
62
+
63
+ On Windows, if `build-corpus` launches a Python executable and fails with `ModuleNotFoundError`, a stale pip install is shadowing the npm command. Remove it with:
64
+
65
+ ```powershell
66
+ py -3 -m pip uninstall build-corpus
67
+ ```
68
+
69
+ For S3/R2 image upload support:
70
+
71
+ ```powershell
72
+ pip install "build-corpus[s3]"
73
+ ```
74
+
75
+ ## Basic Usage
76
+
77
+ ```powershell
78
+ build-corpus input.docx --out out
79
+ build-corpus deck.pptx --out out
80
+ build-corpus input.md --to word --out out
81
+ build-corpus input.md --to word --word-template C:\path\custom.dotx --out out
82
+ regen.mde input.md
83
+ regen.mdeditor input.md
84
+ regen-mdeditor input.md
85
+ build-corpus editor input.md
86
+ build-corpus editor input.docx
87
+ ```
88
+
89
+ ## regen.mde
90
+
91
+ regen.mde is a Windows WebView2 desktop app bundled with the package. It uses the same local Build Corpus conversion engine as the CLI:
92
+
93
+ - Markdown opens directly.
94
+ - Word and PowerPoint files open by converting into Markdown.
95
+ - Save writes Markdown.
96
+ - Save As writes a new Markdown file.
97
+ - Export DOCX writes Word output through the Markdown-to-Word route.
98
+
99
+ Build the Windows executable locally:
100
+
101
+ ```powershell
102
+ npm run editor:windows
103
+ ```
104
+
105
+ The executable is written to:
106
+
107
+ ```text
108
+ dist\windows-editor\BuildCorpusEditor.exe
109
+ ```
110
+
111
+ Convert every `.docx` in a folder:
112
+
113
+ ```powershell
114
+ build-corpus ./word-files --out ./markdown
115
+ ```
116
+
117
+ Convert every supported file type in a folder (`.docx`, `.pptx`, `.ppt`):
118
+
119
+ ```powershell
120
+ build-corpus ./source-files --out ./markdown
121
+ ```
122
+
123
+ Write Markdown beside each source document:
124
+
125
+ ```powershell
126
+ build-corpus ./word-files --out-same-dir
127
+ ```
128
+
129
+ ## Image Modes
130
+
131
+ Local asset files, the default:
132
+
133
+ ```powershell
134
+ build-corpus input.docx --images assets
135
+ ```
136
+
137
+ Single-file Markdown with base64 image data URIs:
138
+
139
+ ```powershell
140
+ build-corpus input.docx --images base64
141
+ ```
142
+
143
+ Upload images to S3-compatible storage and write public URLs:
144
+
145
+ ```powershell
146
+ build-corpus input.docx --images s3 --config examples\build-corpus.config.example.json
147
+ ```
148
+
149
+ Cloudflare R2 uses the same `s3` mode. Set `endpoint_url` to:
150
+
151
+ ```text
152
+ https://ACCOUNT_ID.r2.cloudflarestorage.com
153
+ ```
154
+
155
+ ## Config
156
+
157
+ Copy `examples/build-corpus.config.example.json` and edit it for your environment.
158
+
159
+ ```json
160
+ {
161
+ "conversion": {
162
+ "equations": "tex",
163
+ "images": "s3"
164
+ },
165
+ "output": {
166
+ "out": "out",
167
+ "out_same_dir": false
168
+ },
169
+ "s3": {
170
+ "bucket": "build-corpus-assets",
171
+ "public_base_url": "https://assets.example.com",
172
+ "prefix": "knowledge-base",
173
+ "endpoint_url": "https://ACCOUNT_ID.r2.cloudflarestorage.com",
174
+ "region_name": "auto",
175
+ "access_key_id": "%R2_ACCESS_KEY_ID%",
176
+ "secret_access_key": "%R2_SECRET_ACCESS_KEY%"
177
+ }
178
+ }
179
+ ```
180
+
181
+ Build Corpus expands environment variables in JSON string values, so credentials do not need to be committed.
182
+
183
+ ### Output Placement
184
+
185
+ There are two output modes.
186
+
187
+ Write all converted Markdown into one output tree:
188
+
189
+ ```json
190
+ {
191
+ "output": {
192
+ "out": "./markdown",
193
+ "out_same_dir": false
194
+ }
195
+ }
196
+ ```
197
+
198
+ Write each `.md`, asset folder, and report beside the source `.docx`:
199
+
200
+ ```json
201
+ {
202
+ "output": {
203
+ "out_same_dir": true
204
+ }
205
+ }
206
+ ```
207
+
208
+ The same-dir mode is equivalent to:
209
+
210
+ ```powershell
211
+ build-corpus ./word-files --out-same-dir
212
+ ```
213
+
214
+ ## Markdown to Word Templates
215
+
216
+ Markdown -> Word conversion uses this template precedence:
217
+
218
+ 1. `--word-template <path>`
219
+ 2. `word.template` in the JSON config
220
+ 3. the bundled installed package template
221
+ 4. built-in fallback styles if no template can be found
222
+
223
+ Template files are treated as style sources. Build Corpus creates a fresh output document body, then applies the template's Word styles, numbering, theme, fonts, and settings. It does not reuse the template body content as the exported document.
224
+
225
+ ## Equations
226
+
227
+ The default equation mode is parseable TeX:
228
+
229
+ ```powershell
230
+ build-corpus input.docx --equations tex
231
+ ```
232
+
233
+ Equation images are only for visual debugging:
234
+
235
+ ```powershell
236
+ build-corpus input.docx --equations image
237
+ ```
238
+
239
+ ## PowerPoint Notes
240
+
241
+ - `.pptx` is processed directly.
242
+ - `.ppt` is converted to `.pptx` first using LibreOffice (`soffice --headless --convert-to pptx`).
243
+ - Repeated boilerplate blocks that appear on most slides are removed from the emitted Markdown.
244
+ - Slide images are exported from the original package binaries (`ppt/media/*`), not screen-captured display rasters.
245
+ - Markdown output uses size-aware HTML image tags (`<img ... width= height=>`) based on OOXML display extents (`a:xfrm/a:ext`).
246
+ - The export report includes `low_dpi_images` to flag images whose effective on-slide DPI is under 150.
247
+
248
+ ## Validation
249
+
250
+ The package includes a KaTeX validator for emitted Markdown math:
251
+
252
+ ```powershell
253
+ build-corpus-katex out
254
+ ```
255
+
256
+ ## Repeatable Test Wrappers
257
+
258
+ Run a single known DOCX through conversion plus validators:
259
+
260
+ ```powershell
261
+ .\scripts\run-smoke.ps1 -Docx ".\fixtures\sample.docx" -Out ".tmp\smoke" -Images assets
262
+ ```
263
+
264
+ Run a whole folder corpus:
265
+
266
+ ```powershell
267
+ .\scripts\run-corpus.ps1 -Source ".\fixtures\wordtest" -Out ".tmp\wordtest" -Images base64
268
+ ```
269
+
270
+ Build a public online DOCX corpus for regression testing:
271
+
272
+ ```powershell
273
+ python .\tools\collect_online_docx_corpus.py --out ".tmp\online-docx\source-docx" --target 50
274
+ .\scripts\run-corpus.ps1 -Source ".tmp\online-docx\source-docx" -Out ".tmp\online-docx\markdown"
275
+ ```
276
+
277
+ Build a public online PPTX corpus and compare input/output extraction:
278
+
279
+ ```powershell
280
+ python .\tools\collect_online_pptx_corpus.py --out ".tmp\online-pptx\source-pptx" --target 20
281
+ .\scripts\run-corpus.ps1 -Source ".tmp\online-pptx\source-pptx" -Out ".tmp\online-pptx\markdown"
282
+ python .\tools\compare_pptx_inputs_outputs.py --manifest ".tmp\online-pptx\source-pptx\online-pptx-manifest.json" --out ".tmp\online-pptx\markdown" --report ".tmp\online-pptx\markdown\pptx-io-compare.json"
283
+ ```
284
+
285
+ ## Failed Documents
286
+
287
+ If a document does not convert correctly, open an issue with:
288
+
289
+ - the `.docx` file if it is safe to share
290
+ - the generated `.md`
291
+ - the `export-report.json`
292
+ - the command and config used
293
+ - a screenshot of the expected Word output if layout is the issue
294
+
295
+ For confidential files, strip or replace sensitive content before sharing. The useful part is the broken DOCX structure, not the private text.
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env node
2
+ const path = require("node:path");
3
+ const { spawn } = require("node:child_process");
4
+ const fs = require("node:fs");
5
+
6
+ const root = path.resolve(__dirname, "..");
7
+ const callerCwd = process.cwd();
8
+ const candidates = [
9
+ path.join(root, "dist", "windows-editor", "BuildCorpusEditor.exe"),
10
+ path.join(root, "desktop", "BuildCorpusEditor", "bin", "Release", "net8.0-windows", "win-x64", "BuildCorpusEditor.exe"),
11
+ ];
12
+ const exe = candidates.find((candidate) => fs.existsSync(candidate)) || candidates[0];
13
+ const args = process.argv.slice(2);
14
+
15
+ if (args.includes("--help") || args.includes("-h")) {
16
+ console.log(`regen.mde
17
+
18
+ Usage:
19
+ regen.mde [document.md|document.docx] [options]
20
+ regen.mdeditor [document.md|document.docx] [options]
21
+ regen-mdeditor [document.md|document.docx] [options]
22
+ build-corpus-editor [document.md|document.docx] [options]
23
+
24
+ Options:
25
+ --foreground, --visible Show the editor as an attached foreground process.
26
+ --background Launch hidden/offscreen for smoke checks.
27
+ --self-test Verify the editor bridge can open the document.
28
+ --document-self-test Open, edit, save Markdown, export Word, and reconvert.
29
+ --smoke-ui Run the hidden UI smoke test.
30
+ --out <dir> Output directory for document self-test.
31
+ --help, -h Show this help.
32
+ `);
33
+ process.exit(0);
34
+ }
35
+
36
+ const visible = args.includes("--foreground") || args.includes("--visible");
37
+ const background = args.includes("--background") || args.includes("--smoke-ui");
38
+ const launchArgs = normalizeLaunchArgs(args.filter((arg) => arg !== "--foreground" && arg !== "--visible"));
39
+
40
+ const child = spawn(exe, launchArgs, {
41
+ cwd: root,
42
+ stdio: "inherit",
43
+ windowsHide: background,
44
+ detached: background && !visible,
45
+ env: {
46
+ ...process.env,
47
+ BUILD_CORPUS_ROOT: root,
48
+ },
49
+ });
50
+
51
+ child.on("error", (error) => {
52
+ console.error(`Build Corpus Editor executable not found: ${exe}`);
53
+ console.error("Build it with: npm run editor:windows");
54
+ console.error(error.message);
55
+ process.exit(1);
56
+ });
57
+
58
+ child.on("exit", (code) => process.exit(code ?? 0));
59
+
60
+ function normalizeLaunchArgs(rawArgs) {
61
+ const normalized = [];
62
+ for (let index = 0; index < rawArgs.length; index += 1) {
63
+ const arg = rawArgs[index];
64
+ if (arg === "--out" && index < rawArgs.length - 1) {
65
+ normalized.push(arg, resolveFromCaller(rawArgs[index + 1]));
66
+ index += 1;
67
+ continue;
68
+ }
69
+ if (index === 0 && !arg.startsWith("--")) {
70
+ normalized.push(resolveFromCaller(arg));
71
+ continue;
72
+ }
73
+ normalized.push(arg);
74
+ }
75
+ return normalized;
76
+ }
77
+
78
+ function resolveFromCaller(value) {
79
+ if (!value || path.isAbsolute(value)) return value;
80
+ return path.resolve(callerCwd, value);
81
+ }
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env node
2
+ const { spawnSync } = require("node:child_process");
3
+ const path = require("node:path");
4
+
5
+ const root = path.resolve(__dirname, "..");
6
+ const candidates = process.platform === "win32" ? ["py", "python"] : ["python3", "python"];
7
+ const env = { ...process.env };
8
+ env.PYTHONPATH = env.PYTHONPATH ? `${path.join(root, "src")}${path.delimiter}${env.PYTHONPATH}` : path.join(root, "src");
9
+ const args = process.argv.slice(2);
10
+
11
+ if (args[0] === "editor") {
12
+ const editor = path.join(root, "bin", "build-corpus-editor.js");
13
+ const result = spawnSync(process.execPath, [editor, ...args.slice(1)], {
14
+ stdio: "inherit",
15
+ cwd: root,
16
+ env: { ...env, BUILD_CORPUS_ROOT: root },
17
+ });
18
+ process.exit(result.status ?? 1);
19
+ }
20
+
21
+ if (process.platform === "win32" && args.includes("--uninstall-windows")) {
22
+ const result = spawnSync(process.execPath, [path.join(root, "bin", "postinstall.js"), "--uninstall"], {
23
+ stdio: "inherit",
24
+ cwd: root,
25
+ env,
26
+ });
27
+ process.exit(result.status ?? 1);
28
+ }
29
+
30
+ let result = null;
31
+ for (const candidate of candidates) {
32
+ const args = candidate === "py"
33
+ ? ["-3", "-m", "build_corpus.exporter", ...process.argv.slice(2)]
34
+ : ["-m", "build_corpus.exporter", ...process.argv.slice(2)];
35
+ result = spawnSync(candidate, args, { stdio: "inherit", cwd: root, env });
36
+ if (result.error && result.error.code === "ENOENT") continue;
37
+ process.exit(result.status ?? 1);
38
+ }
39
+
40
+ console.error("Build Corpus requires Python 3.10+ on PATH.");
41
+ process.exit(1);
@@ -0,0 +1,187 @@
1
+ #!/usr/bin/env node
2
+ const { spawnSync } = require("node:child_process");
3
+ const path = require("node:path");
4
+
5
+ const UNINSTALL_MODE = process.argv.includes("--uninstall");
6
+ const SKIP_PIP_INSTALL = process.env.BUILD_CORPUS_SKIP_PIP_INSTALL === "1";
7
+
8
+ const packageRoot = path.resolve(__dirname, "..");
9
+ const candidates = process.platform === "win32" ? ["py", "python"] : ["python3", "python"];
10
+ const requirements = path.join(packageRoot, "requirements.txt");
11
+
12
+ function isGlobalInstall() {
13
+ return process.env.npm_config_global === "true";
14
+ }
15
+
16
+ function printUsage() {
17
+ const isGlobal = isGlobalInstall();
18
+ if (isGlobal) {
19
+ console.log("Build Corpus installed globally. Run: build-corpus --help");
20
+ return;
21
+ }
22
+
23
+ console.log("Build Corpus installed locally.");
24
+ console.log("Run with: npx build-corpus --help");
25
+ }
26
+
27
+ function warnOnShadowedWindowsCommand() {
28
+ if (process.platform !== "win32") return;
29
+
30
+ const whereResult = spawnSync("where.exe", ["build-corpus"], { encoding: "utf8" });
31
+ if (whereResult.error || whereResult.status !== 0) return;
32
+
33
+ const matches = whereResult.stdout
34
+ .split(/\r?\n/)
35
+ .map((line) => line.trim())
36
+ .filter(Boolean);
37
+ if (matches.length === 0) return;
38
+
39
+ const firstMatch = matches[0].toLowerCase();
40
+ if (!firstMatch.includes("python") || !firstMatch.endsWith("build-corpus.exe")) return;
41
+
42
+ console.warn("");
43
+ console.warn("WARNING: 'build-corpus' currently resolves to a Python executable:");
44
+ console.warn(` ${matches[0]}`);
45
+ console.warn("That usually means a stale pip install is shadowing the npm command.");
46
+ console.warn("Fix it with: py -3 -m pip uninstall build-corpus");
47
+ console.warn("Then rerun your npm install or use: npx build-corpus --help");
48
+ }
49
+
50
+ function installWindowsContextMenu() {
51
+ if (process.platform !== "win32") return;
52
+ if (!isGlobalInstall()) return;
53
+ if (process.env.BUILD_CORPUS_SKIP_WINDOWS_MENU === "1") return;
54
+
55
+ const prefix = process.env.npm_config_prefix || path.join(process.env.APPDATA || "", "npm");
56
+ const commandPath = path.join(prefix, "build-corpus.cmd");
57
+ const editorPath = path.join(prefix, "build-corpus-editor.cmd");
58
+ const nativeEditorPath = path.join(packageRoot, "dist", "windows-editor", "BuildCorpusEditor.exe");
59
+ const editorCommandPath = fileExists(nativeEditorPath) ? nativeEditorPath : editorPath;
60
+ const menus = [
61
+ {
62
+ keyName: "BuildCorpusToMarkdown",
63
+ label: "Convert to Markdown",
64
+ commandValue: `"${commandPath}" "%1" --out-same-dir`,
65
+ extension: ".docx",
66
+ },
67
+ ...(process.env.BUILD_CORPUS_SKIP_EDITOR === "1" ? [] : [
68
+ {
69
+ keyName: "BuildCorpusOpenEditor",
70
+ label: "Open in regen.mde",
71
+ commandValue: `"${editorCommandPath}" "%1"`,
72
+ extension: ".docx",
73
+ },
74
+ {
75
+ keyName: "BuildCorpusOpenEditor",
76
+ label: "Open in regen.mde",
77
+ commandValue: `"${editorCommandPath}" "%1"`,
78
+ extension: ".md",
79
+ },
80
+ ]),
81
+ {
82
+ keyName: "BuildCorpusToWord",
83
+ label: "Convert to Word",
84
+ commandValue: `"${commandPath}" "%1" --to word --out-same-dir`,
85
+ extension: ".md",
86
+ },
87
+ ];
88
+
89
+ for (const menu of menus) {
90
+ const commands = [
91
+ ["delete", `HKCU\\Software\\Classes\\SystemFileAssociations\\${menu.extension}\\shell\\Life AI`, "/f"],
92
+ ["delete", `HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\CommandStore\\shell\\LifeAI.${menu.keyName}`, "/f"],
93
+ ["delete", `HKCU\\Software\\LifeAI\\ShellCommands\\${menu.keyName}`, "/f"],
94
+ ["add", `HKCU\\Software\\Classes\\SystemFileAssociations\\${menu.extension}\\shell\\${menu.keyName}`, "/v", "MUIVerb", "/d", menu.label, "/f"],
95
+ ["add", `HKCU\\Software\\Classes\\SystemFileAssociations\\${menu.extension}\\shell\\${menu.keyName}`, "/v", "Icon", "/t", "REG_EXPAND_SZ", "/d", menu.keyName === "BuildCorpusOpenEditor" ? editorCommandPath : commandPath, "/f"],
96
+ ["add", `HKCU\\Software\\Classes\\SystemFileAssociations\\${menu.extension}\\shell\\${menu.keyName}\\command`, "/ve", "/d", menu.commandValue, "/f"],
97
+ ];
98
+
99
+ for (const args of commands) {
100
+ const result = spawnSync("reg.exe", args, { stdio: "inherit" });
101
+ const status = result.status ?? 1;
102
+ if (args[0] === "delete" && status === 1 && !result.error) {
103
+ continue;
104
+ }
105
+ if (result.error || status !== 0) {
106
+ console.warn(`Build Corpus could not add the Windows ${menu.extension} context menu.`);
107
+ return;
108
+ }
109
+ }
110
+ }
111
+
112
+ console.log("Windows Explorer menus added for .docx and .md");
113
+ }
114
+
115
+ function fileExists(candidate) {
116
+ try {
117
+ return require("node:fs").existsSync(candidate);
118
+ } catch {
119
+ return false;
120
+ }
121
+ }
122
+
123
+ function uninstallWindowsContextMenu() {
124
+ if (process.platform !== "win32") return 0;
125
+
126
+ const menus = [
127
+ { extension: ".docx", keyName: "BuildCorpusToMarkdown" },
128
+ { extension: ".docx", keyName: "BuildCorpusOpenEditor" },
129
+ { extension: ".md", keyName: "BuildCorpusOpenEditor" },
130
+ { extension: ".md", keyName: "BuildCorpusToWord" },
131
+ ];
132
+
133
+ let exitCode = 0;
134
+ for (const menu of menus) {
135
+ const removals = [
136
+ `HKCU\\Software\\Classes\\SystemFileAssociations\\${menu.extension}\\shell\\BuildCorpus`,
137
+ `HKCU\\Software\\Classes\\SystemFileAssociations\\${menu.extension}\\shell\\${menu.keyName}`,
138
+ `HKCU\\Software\\Classes\\SystemFileAssociations\\${menu.extension}\\shell\\Life AI`,
139
+ `HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\CommandStore\\shell\\LifeAI.${menu.keyName}`,
140
+ `HKCU\\Software\\LifeAI\\ShellCommands\\${menu.keyName}`,
141
+ ];
142
+
143
+ for (const key of removals) {
144
+ const result = spawnSync("reg.exe", ["delete", key, "/f"], { stdio: "inherit" });
145
+ const status = result.status ?? 1;
146
+ if (result.error || (status !== 0 && status !== 1)) {
147
+ console.warn(`Build Corpus could not remove the Windows ${menu.extension} context menu.`);
148
+ exitCode = 1;
149
+ }
150
+ }
151
+ }
152
+
153
+ console.log("Windows Explorer menus removed for .docx and .md");
154
+ return exitCode;
155
+ }
156
+
157
+ if (UNINSTALL_MODE) {
158
+ process.exit(uninstallWindowsContextMenu());
159
+ }
160
+
161
+ if (SKIP_PIP_INSTALL) {
162
+ printUsage();
163
+ installWindowsContextMenu();
164
+ warnOnShadowedWindowsCommand();
165
+ process.exit(0);
166
+ }
167
+
168
+ for (const candidate of candidates) {
169
+ const args = candidate === "py"
170
+ ? ["-3", "-m", "pip", "install", "-r", requirements]
171
+ : ["-m", "pip", "install", "-r", requirements];
172
+ const result = spawnSync(candidate, args, { stdio: "inherit" });
173
+ if (result.error && result.error.code === "ENOENT") continue;
174
+ if ((result.status ?? 1) !== 0) {
175
+ process.exit(result.status ?? 1);
176
+ }
177
+
178
+ printUsage();
179
+ installWindowsContextMenu();
180
+ warnOnShadowedWindowsCommand();
181
+ process.exit(0);
182
+ }
183
+
184
+ console.warn("Build Corpus could not find Python to install Python dependencies.");
185
+ printUsage();
186
+ installWindowsContextMenu();
187
+ process.exit(0);
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+ const { spawnSync } = require("node:child_process");
3
+ const path = require("node:path");
4
+
5
+ if (process.platform !== "win32") {
6
+ console.error("regen.mde installer is only available on Windows.");
7
+ process.exit(1);
8
+ }
9
+
10
+ const root = path.resolve(__dirname, "..");
11
+ const script = path.join(root, "installer", "install-regen-mde.ps1");
12
+ const result = spawnSync("powershell.exe", [
13
+ "-NoProfile",
14
+ "-ExecutionPolicy",
15
+ "Bypass",
16
+ "-File",
17
+ script,
18
+ "-PackageRoot",
19
+ root,
20
+ ...process.argv.slice(2),
21
+ ], {
22
+ stdio: "inherit",
23
+ cwd: root,
24
+ windowsHide: true,
25
+ });
26
+
27
+ process.exit(result.status ?? 1);
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+ const { spawnSync } = require("node:child_process");
3
+ const path = require("node:path");
4
+
5
+ if (process.platform !== "win32") {
6
+ console.error("regen.mde uninstaller is only available on Windows.");
7
+ process.exit(1);
8
+ }
9
+
10
+ const root = path.resolve(__dirname, "..");
11
+ const result = spawnSync(process.execPath, [
12
+ path.join(root, "bin", "postinstall.js"),
13
+ "--uninstall",
14
+ ], {
15
+ stdio: "inherit",
16
+ cwd: root,
17
+ });
18
+
19
+ process.exit(result.status ?? 1);