@wonderwhy-er/desktop-commander 0.2.34 → 0.2.35

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 (78) hide show
  1. package/dist/tools/docx/builders/html-builder.d.ts +17 -0
  2. package/dist/tools/docx/builders/html-builder.js +92 -0
  3. package/dist/tools/docx/builders/index.d.ts +5 -0
  4. package/dist/tools/docx/builders/index.js +5 -0
  5. package/dist/tools/docx/builders/markdown-builder.d.ts +2 -0
  6. package/dist/tools/docx/builders/markdown-builder.js +260 -0
  7. package/dist/tools/docx/constants.d.ts +36 -0
  8. package/dist/tools/docx/constants.js +57 -0
  9. package/dist/tools/docx/converters/markdown-to-html.d.ts +17 -0
  10. package/dist/tools/docx/converters/markdown-to-html.js +111 -0
  11. package/dist/tools/docx/errors.d.ts +28 -0
  12. package/dist/tools/docx/errors.js +48 -0
  13. package/dist/tools/docx/extractors/images.d.ts +14 -0
  14. package/dist/tools/docx/extractors/images.js +40 -0
  15. package/dist/tools/docx/extractors/metadata.d.ts +14 -0
  16. package/dist/tools/docx/extractors/metadata.js +64 -0
  17. package/dist/tools/docx/extractors/sections.d.ts +14 -0
  18. package/dist/tools/docx/extractors/sections.js +61 -0
  19. package/dist/tools/docx/html.d.ts +17 -0
  20. package/dist/tools/docx/html.js +111 -0
  21. package/dist/tools/docx/index.d.ts +14 -0
  22. package/dist/tools/docx/index.js +16 -0
  23. package/dist/tools/docx/markdown.d.ts +84 -0
  24. package/dist/tools/docx/markdown.js +507 -0
  25. package/dist/tools/docx/operations/handlers/index.d.ts +39 -0
  26. package/dist/tools/docx/operations/handlers/index.js +152 -0
  27. package/dist/tools/docx/operations/html-manipulator.d.ts +24 -0
  28. package/dist/tools/docx/operations/html-manipulator.js +352 -0
  29. package/dist/tools/docx/operations/index.d.ts +14 -0
  30. package/dist/tools/docx/operations/index.js +61 -0
  31. package/dist/tools/docx/operations/operation-handlers.d.ts +3 -0
  32. package/dist/tools/docx/operations/operation-handlers.js +67 -0
  33. package/dist/tools/docx/operations/preprocessor.d.ts +14 -0
  34. package/dist/tools/docx/operations/preprocessor.js +44 -0
  35. package/dist/tools/docx/operations/xml-replacer.d.ts +9 -0
  36. package/dist/tools/docx/operations/xml-replacer.js +35 -0
  37. package/dist/tools/docx/operations.d.ts +13 -0
  38. package/dist/tools/docx/operations.js +13 -0
  39. package/dist/tools/docx/parsers/image-extractor.d.ts +18 -0
  40. package/dist/tools/docx/parsers/image-extractor.js +61 -0
  41. package/dist/tools/docx/parsers/index.d.ts +9 -0
  42. package/dist/tools/docx/parsers/index.js +9 -0
  43. package/dist/tools/docx/parsers/paragraph-parser.d.ts +2 -0
  44. package/dist/tools/docx/parsers/paragraph-parser.js +88 -0
  45. package/dist/tools/docx/parsers/table-parser.d.ts +9 -0
  46. package/dist/tools/docx/parsers/table-parser.js +72 -0
  47. package/dist/tools/docx/parsers/xml-parser.d.ts +25 -0
  48. package/dist/tools/docx/parsers/xml-parser.js +71 -0
  49. package/dist/tools/docx/parsers/zip-reader.d.ts +23 -0
  50. package/dist/tools/docx/parsers/zip-reader.js +52 -0
  51. package/dist/tools/docx/structure.d.ts +25 -0
  52. package/dist/tools/docx/structure.js +102 -0
  53. package/dist/tools/docx/styled-html-parser.d.ts +23 -0
  54. package/dist/tools/docx/styled-html-parser.js +1262 -0
  55. package/dist/tools/docx/types.d.ts +114 -0
  56. package/dist/tools/docx/types.js +8 -0
  57. package/dist/tools/docx/utils/escaping.d.ts +13 -0
  58. package/dist/tools/docx/utils/escaping.js +26 -0
  59. package/dist/tools/docx/utils/images.d.ts +9 -0
  60. package/dist/tools/docx/utils/images.js +26 -0
  61. package/dist/tools/docx/utils/index.d.ts +12 -0
  62. package/dist/tools/docx/utils/index.js +17 -0
  63. package/dist/tools/docx/utils/markdown.d.ts +13 -0
  64. package/dist/tools/docx/utils/markdown.js +32 -0
  65. package/dist/tools/docx/utils/paths.d.ts +15 -0
  66. package/dist/tools/docx/utils/paths.js +27 -0
  67. package/dist/tools/docx/utils/versioning.d.ts +25 -0
  68. package/dist/tools/docx/utils/versioning.js +55 -0
  69. package/dist/tools/docx/utils.d.ts +101 -0
  70. package/dist/tools/docx/utils.js +299 -0
  71. package/dist/tools/docx/validators.d.ts +13 -0
  72. package/dist/tools/docx/validators.js +40 -0
  73. package/dist/utils/capture.js +4 -4
  74. package/dist/utils/files/docx.d.ts +41 -0
  75. package/dist/utils/files/docx.js +245 -0
  76. package/dist/version.d.ts +1 -1
  77. package/dist/version.js +1 -1
  78. package/package.json +1 -1
@@ -0,0 +1,299 @@
1
+ /**
2
+ * DOCX Utility Functions
3
+ * Helper functions for DOCX operations, validation, and data transformation
4
+ */
5
+ import fs from 'fs/promises';
6
+ import path from 'path';
7
+ // Import types from docx for image handling
8
+ // @ts-ignore - docx library has incomplete type definitions but exports exist at runtime
9
+ import * as docx from 'docx';
10
+ const { ImageRun, } = docx;
11
+ import { DocxError, DocxErrorCode, withErrorContext } from './errors.js';
12
+ /**
13
+ * Check if a string is a valid data URL
14
+ */
15
+ export function isDataUrl(src) {
16
+ return src.startsWith('data:') && src.includes('base64,');
17
+ }
18
+ /**
19
+ * Check if a source is a URL (http/https)
20
+ */
21
+ export function isUrl(src) {
22
+ return src.startsWith('http://') || src.startsWith('https://');
23
+ }
24
+ /**
25
+ * Parse data URL into buffer
26
+ * @returns Buffer if successful, null if invalid
27
+ */
28
+ export function parseDataUrl(dataUrl) {
29
+ try {
30
+ const match = dataUrl.match(/^data:([^;]+);base64,(.+)$/);
31
+ if (!match)
32
+ return null;
33
+ return Buffer.from(match[2], 'base64');
34
+ }
35
+ catch {
36
+ return null;
37
+ }
38
+ }
39
+ /**
40
+ * Resolve image path relative to base directory
41
+ */
42
+ export function resolveImagePath(imagePath, baseDir) {
43
+ if (path.isAbsolute(imagePath) || isUrl(imagePath) || isDataUrl(imagePath)) {
44
+ return imagePath;
45
+ }
46
+ if (!baseDir) {
47
+ return path.resolve(imagePath);
48
+ }
49
+ return path.join(baseDir, imagePath);
50
+ }
51
+ /**
52
+ * Check if a file exists
53
+ */
54
+ export async function fileExists(filePath) {
55
+ try {
56
+ await fs.access(filePath);
57
+ return true;
58
+ }
59
+ catch {
60
+ return false;
61
+ }
62
+ }
63
+ /**
64
+ * Validate image file
65
+ */
66
+ export async function validateImageFile(imagePath) {
67
+ if (isDataUrl(imagePath)) {
68
+ const buffer = parseDataUrl(imagePath);
69
+ if (!buffer) {
70
+ return { valid: false, error: 'Invalid data URL format' };
71
+ }
72
+ return { valid: true };
73
+ }
74
+ if (isUrl(imagePath)) {
75
+ // For URLs, we'll validate during fetch
76
+ return { valid: true };
77
+ }
78
+ const exists = await fileExists(imagePath);
79
+ if (!exists) {
80
+ return { valid: false, error: `Image file not found: ${imagePath}` };
81
+ }
82
+ // Check file extension
83
+ const ext = path.extname(imagePath).toLowerCase();
84
+ const validExtensions = ['.png', '.jpg', '.jpeg', '.gif', '.bmp', '.webp', '.svg'];
85
+ if (!validExtensions.includes(ext)) {
86
+ return { valid: false, error: `Unsupported image format: ${ext}` };
87
+ }
88
+ return { valid: true };
89
+ }
90
+ /**
91
+ * Escape special regex characters for literal string matching
92
+ */
93
+ export function escapeRegExp(str) {
94
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
95
+ }
96
+ /**
97
+ * Validate markdown table format
98
+ */
99
+ export function isValidMarkdownTable(markdown) {
100
+ const lines = markdown.trim().split('\n');
101
+ if (lines.length < 2)
102
+ return false;
103
+ // Check header row
104
+ const headerLine = lines[0].trim();
105
+ if (!headerLine.startsWith('|') || !headerLine.endsWith('|'))
106
+ return false;
107
+ // Check separator row
108
+ const separatorLine = lines[1].trim();
109
+ if (!separatorLine.startsWith('|') || !separatorLine.endsWith('|'))
110
+ return false;
111
+ // Verify separator contains dashes
112
+ const separatorPattern = /^(\|\s*:?-{2,}\s*:?\s*)+\|$/;
113
+ if (!separatorPattern.test(separatorLine))
114
+ return false;
115
+ return true;
116
+ }
117
+ /**
118
+ * Build a markdown table from a 2D array of rows
119
+ */
120
+ export function buildMarkdownTableFromRows(rows) {
121
+ if (!rows || rows.length === 0) {
122
+ return '';
123
+ }
124
+ const header = rows[0];
125
+ const dataRows = rows.slice(1);
126
+ const headerLine = `| ${header.join(' | ')} |`;
127
+ const separatorLine = `| ${header.map(() => '---').join(' | ')} |`;
128
+ const dataLines = dataRows.map((r) => `| ${r.join(' | ')} |`);
129
+ return [headerLine, separatorLine, ...dataLines].join('\n');
130
+ }
131
+ /**
132
+ * Parse markdown table into 2D array
133
+ */
134
+ export function parseMarkdownTable(markdown) {
135
+ if (!isValidMarkdownTable(markdown)) {
136
+ return null;
137
+ }
138
+ const lines = markdown.trim().split('\n');
139
+ const rows = [];
140
+ for (let i = 0; i < lines.length; i++) {
141
+ // Skip separator line
142
+ if (i === 1)
143
+ continue;
144
+ const line = lines[i].trim();
145
+ const cells = line
146
+ .replace(/^\|/, '')
147
+ .replace(/\|$/, '')
148
+ .split('|')
149
+ .map(cell => cell.trim());
150
+ rows.push(cells);
151
+ }
152
+ return rows;
153
+ }
154
+ /**
155
+ * Get MIME type from file extension or data URL
156
+ */
157
+ export function getMimeType(source) {
158
+ if (isDataUrl(source)) {
159
+ const match = source.match(/^data:([^;]+);/);
160
+ return match ? match[1] : null;
161
+ }
162
+ const ext = path.extname(source).toLowerCase();
163
+ const mimeTypes = {
164
+ '.png': 'image/png',
165
+ '.jpg': 'image/jpeg',
166
+ '.jpeg': 'image/jpeg',
167
+ '.gif': 'image/gif',
168
+ '.bmp': 'image/bmp',
169
+ '.webp': 'image/webp',
170
+ '.svg': 'image/svg+xml',
171
+ };
172
+ return mimeTypes[ext] || null;
173
+ }
174
+ /**
175
+ * Format file size in human-readable format
176
+ */
177
+ export function formatFileSize(bytes) {
178
+ if (bytes === 0)
179
+ return '0 Bytes';
180
+ const k = 1024;
181
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
182
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
183
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
184
+ }
185
+ /**
186
+ * Normalize line endings to \n
187
+ */
188
+ export function normalizeLineEndings(text) {
189
+ return text.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
190
+ }
191
+ /**
192
+ * Split markdown into lines, preserving empty lines
193
+ */
194
+ export function splitMarkdownLines(markdown) {
195
+ return normalizeLineEndings(markdown).split('\n');
196
+ }
197
+ /**
198
+ * Validate DOCX file path
199
+ */
200
+ export function isDocxPath(filePath) {
201
+ return filePath.toLowerCase().endsWith('.docx');
202
+ }
203
+ /**
204
+ * Extract file name without extension
205
+ */
206
+ export function getFileNameWithoutExtension(filePath) {
207
+ const basename = path.basename(filePath);
208
+ return basename.replace(/\.docx$/i, '');
209
+ }
210
+ // Re-export error handling from errors module
211
+ export { DocxError, DocxErrorCode, withErrorContext } from './errors.js';
212
+ /**
213
+ * Load and prepare an image for DOCX embedding
214
+ * Handles both local files and data URLs
215
+ *
216
+ * @param imagePath - Path to image file or data URL
217
+ * @param altText - Alternative text for the image
218
+ * @param baseDir - Base directory for resolving relative paths
219
+ * @returns Prepared image data ready for embedding
220
+ */
221
+ export async function prepareImageForDocx(imagePath, altText = '', baseDir) {
222
+ return withErrorContext(async () => {
223
+ let buffer;
224
+ let mimeType = 'image/png'; // Default
225
+ // Handle data URL
226
+ if (isDataUrl(imagePath)) {
227
+ const parsed = parseDataUrl(imagePath);
228
+ if (!parsed) {
229
+ throw new DocxError('Invalid data URL format', DocxErrorCode.INVALID_IMAGE_DATA_URL, { imagePath: imagePath.substring(0, 50) });
230
+ }
231
+ buffer = parsed;
232
+ // Extract MIME type from data URL
233
+ const mimeMatch = imagePath.match(/^data:([^;]+);/);
234
+ if (mimeMatch) {
235
+ mimeType = mimeMatch[1];
236
+ }
237
+ }
238
+ else {
239
+ // Handle file path
240
+ const resolved = resolveImagePath(imagePath, baseDir);
241
+ // Validate image file
242
+ const validation = await validateImageFile(resolved);
243
+ if (!validation.valid) {
244
+ throw new DocxError(validation.error || 'Invalid image file', DocxErrorCode.INVALID_IMAGE_FILE, { imagePath: resolved });
245
+ }
246
+ // Read image file
247
+ try {
248
+ buffer = await fs.readFile(resolved);
249
+ }
250
+ catch (error) {
251
+ throw new DocxError(`Failed to read image file: ${error instanceof Error ? error.message : String(error)}`, DocxErrorCode.IMAGE_READ_FAILED, { imagePath: resolved });
252
+ }
253
+ // Determine MIME type from file extension
254
+ const ext = path.extname(resolved).toLowerCase();
255
+ const mimeTypes = {
256
+ '.png': 'image/png',
257
+ '.jpg': 'image/jpeg',
258
+ '.jpeg': 'image/jpeg',
259
+ '.gif': 'image/gif',
260
+ '.bmp': 'image/bmp',
261
+ '.webp': 'image/webp',
262
+ };
263
+ mimeType = mimeTypes[ext] || 'image/png';
264
+ }
265
+ return {
266
+ buffer,
267
+ altText: altText || path.basename(imagePath),
268
+ mimeType,
269
+ // Default dimensions - can be customized
270
+ width: 600,
271
+ height: 400,
272
+ };
273
+ }, DocxErrorCode.IMAGE_PREPARATION_FAILED, { imagePath });
274
+ }
275
+ /**
276
+ * Create an ImageRun instance for embedding in DOCX paragraphs
277
+ * This is a lower-level utility for direct image insertion
278
+ *
279
+ * @param imageData - Prepared image data
280
+ * @returns ImageRun instance ready to be added to a paragraph
281
+ */
282
+ export function createImageRun(imageData) {
283
+ try {
284
+ return new ImageRun({
285
+ data: imageData.buffer,
286
+ transformation: {
287
+ width: imageData.width || 600,
288
+ height: imageData.height || 400,
289
+ },
290
+ altText: {
291
+ title: imageData.altText,
292
+ description: imageData.altText,
293
+ },
294
+ });
295
+ }
296
+ catch (error) {
297
+ throw new DocxError(`Failed to create image run: ${error instanceof Error ? error.message : String(error)}`, DocxErrorCode.IMAGE_RUN_CREATION_FAILED);
298
+ }
299
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * DOCX Validation Utilities
3
+ *
4
+ * Input validation for paths, operations arrays, and image dimensions.
5
+ *
6
+ * @module docx/validators
7
+ */
8
+ /** Validate that a DOCX file path is a non-empty string ending in `.docx`. */
9
+ export declare function validateDocxPath(path: string): void;
10
+ /** Validate that an operations array is a non-empty array. */
11
+ export declare function validateOperations(operations: unknown[]): void;
12
+ /** Validate optional image width and height (must be positive and finite). */
13
+ export declare function validateImageDimensions(width?: number, height?: number): void;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * DOCX Validation Utilities
3
+ *
4
+ * Input validation for paths, operations arrays, and image dimensions.
5
+ *
6
+ * @module docx/validators
7
+ */
8
+ import { DocxError, DocxErrorCode } from './errors.js';
9
+ import { isDocxPath } from './utils/paths.js';
10
+ /** Validate that a DOCX file path is a non-empty string ending in `.docx`. */
11
+ export function validateDocxPath(path) {
12
+ if (!path || typeof path !== 'string') {
13
+ throw new DocxError('DOCX path must be a non-empty string', DocxErrorCode.INVALID_PATH, { path });
14
+ }
15
+ const normalised = path.trim();
16
+ if (!normalised) {
17
+ throw new DocxError('DOCX path cannot be empty', DocxErrorCode.INVALID_PATH, { path });
18
+ }
19
+ if (!isDocxPath(normalised)) {
20
+ throw new DocxError('Invalid DOCX path: must end with .docx', DocxErrorCode.INVALID_PATH, { path: normalised });
21
+ }
22
+ }
23
+ /** Validate that an operations array is a non-empty array. */
24
+ export function validateOperations(operations) {
25
+ if (!Array.isArray(operations)) {
26
+ throw new DocxError('Operations must be an array', DocxErrorCode.OPERATION_FAILED, { operations });
27
+ }
28
+ if (operations.length === 0) {
29
+ throw new DocxError('Operations array cannot be empty', DocxErrorCode.OPERATION_FAILED, { operations });
30
+ }
31
+ }
32
+ /** Validate optional image width and height (must be positive and finite). */
33
+ export function validateImageDimensions(width, height) {
34
+ if (width !== undefined && (typeof width !== 'number' || width <= 0 || !Number.isFinite(width))) {
35
+ throw new DocxError('Image width must be a positive finite number', DocxErrorCode.INVALID_IMAGE_FILE, { width });
36
+ }
37
+ if (height !== undefined && (typeof height !== 'number' || height <= 0 || !Number.isFinite(height))) {
38
+ throw new DocxError('Image height must be a positive finite number', DocxErrorCode.INVALID_IMAGE_FILE, { height });
39
+ }
40
+ }
@@ -226,15 +226,15 @@ export const captureBase = async (captureURL, event, properties) => {
226
226
  }
227
227
  };
228
228
  export const capture_call_tool = async (event, properties) => {
229
- const GA_MEASUREMENT_ID = 'G-35YKFM782B'; // Replace with your GA4 Measurement ID
230
- const GA_API_SECRET = 'qM5VNk6aQy6NN5s-tCppZw'; // Replace with your GA4 API Secret
229
+ const GA_MEASUREMENT_ID = 'G-8L163XZ1CE'; // Replace with your GA4 Measurement ID
230
+ const GA_API_SECRET = 'hNxh4TK2TnSy4oLZn4RwTA'; // Replace with your GA4 API Secret
231
231
  const GA_BASE_URL = `https://www.google-analytics.com/mp/collect?measurement_id=${GA_MEASUREMENT_ID}&api_secret=${GA_API_SECRET}`;
232
232
  const GA_DEBUG_BASE_URL = `https://www.google-analytics.com/debug/mp/collect?measurement_id=${GA_MEASUREMENT_ID}&api_secret=${GA_API_SECRET}`;
233
233
  return await captureBase(GA_BASE_URL, event, properties);
234
234
  };
235
235
  export const capture = async (event, properties) => {
236
- const GA_MEASUREMENT_ID = 'G-NGGDNL0K4L'; // Replace with your GA4 Measurement ID
237
- const GA_API_SECRET = '5M0mC--2S_6t94m8WrI60A'; // Replace with your GA4 API Secret
236
+ const GA_MEASUREMENT_ID = 'G-F3GK01G39Y'; // Replace with your GA4 Measurement ID
237
+ const GA_API_SECRET = 'SqdcIAweSQS1RQErURMdEA'; // Replace with your GA4 API Secret
238
238
  const GA_BASE_URL = `https://www.google-analytics.com/mp/collect?measurement_id=${GA_MEASUREMENT_ID}&api_secret=${GA_API_SECRET}`;
239
239
  const GA_DEBUG_BASE_URL = `https://www.google-analytics.com/debug/mp/collect?measurement_id=${GA_MEASUREMENT_ID}&api_secret=${GA_API_SECRET}`;
240
240
  return await captureBase(GA_BASE_URL, event, properties);
@@ -0,0 +1,41 @@
1
+ /**
2
+ * DOCX File Handler
3
+ * Implements FileHandler interface for Microsoft Word documents
4
+ */
5
+ import { FileHandler, FileResult, FileInfo, ReadOptions, EditResult } from './base.js';
6
+ /**
7
+ * File handler for DOCX documents
8
+ * Extracts text as markdown with embedded images
9
+ */
10
+ export declare class DocxFileHandler implements FileHandler {
11
+ private readonly extensions;
12
+ /**
13
+ * Check if this handler can handle the given file
14
+ */
15
+ canHandle(path: string): boolean;
16
+ /**
17
+ * Read DOCX content — extracts text as styled HTML (with embedded images).
18
+ * Uses direct DOCX XML parsing for style preservation, with mammoth.js fallback.
19
+ */
20
+ read(path: string, options?: ReadOptions): Promise<FileResult>;
21
+ /**
22
+ * Write DOCX file.
23
+ *
24
+ * - String content + 'rewrite': create new DOCX from HTML/markdown.
25
+ * - String content + 'append': append to existing DOCX → writes to {name}_v1.docx.
26
+ * - Array content: apply DocxOperation[] edits → writes to {name}_v1.docx.
27
+ * Original file is always preserved.
28
+ */
29
+ write(path: string, content: any, mode?: 'rewrite' | 'append'): Promise<void>;
30
+ /**
31
+ * Edit DOCX by applying high-level operations.
32
+ * Writes to {name}_v1.docx unless `options.outputPath` is provided.
33
+ */
34
+ editRange(path: string, range: string, content: any, options?: Record<string, any>): Promise<EditResult>;
35
+ /**
36
+ * Get DOCX file information including metadata.
37
+ */
38
+ getInfo(path: string): Promise<FileInfo>;
39
+ /** Get base directory for resolving relative image paths. */
40
+ private getBaseDir;
41
+ }
@@ -0,0 +1,245 @@
1
+ /**
2
+ * DOCX File Handler
3
+ * Implements FileHandler interface for Microsoft Word documents
4
+ */
5
+ import fs from 'fs/promises';
6
+ import path from 'path';
7
+ import { parseDocxToHtml, createDocxFromHtml, editDocxWithOperations, DocxError } from '../../tools/docx/index.js';
8
+ import { convertToHtmlIfNeeded, generateOutputPath } from '../../tools/docx/utils/index.js';
9
+ /**
10
+ * File handler for DOCX documents
11
+ * Extracts text as markdown with embedded images
12
+ */
13
+ export class DocxFileHandler {
14
+ constructor() {
15
+ this.extensions = ['.docx'];
16
+ }
17
+ /**
18
+ * Check if this handler can handle the given file
19
+ */
20
+ canHandle(path) {
21
+ const ext = path.toLowerCase();
22
+ return this.extensions.some(e => ext.endsWith(e));
23
+ }
24
+ /**
25
+ * Read DOCX content — extracts text as styled HTML (with embedded images).
26
+ * Uses direct DOCX XML parsing for style preservation, with mammoth.js fallback.
27
+ */
28
+ async read(path, options) {
29
+ try {
30
+ const docxResult = await parseDocxToHtml(path, {
31
+ includeImages: true,
32
+ preserveFormatting: true
33
+ });
34
+ // Format the content for MCP response
35
+ let content = docxResult.html;
36
+ // Add status message if requested (default: true)
37
+ const includeStatusMessage = options?.includeStatusMessage !== false;
38
+ if (includeStatusMessage) {
39
+ const statusParts = [];
40
+ if (docxResult.metadata.title) {
41
+ statusParts.push(`Title: "${docxResult.metadata.title}"`);
42
+ }
43
+ if (docxResult.metadata.author) {
44
+ statusParts.push(`Author: ${docxResult.metadata.author}`);
45
+ }
46
+ if (docxResult.images.length > 0) {
47
+ statusParts.push(`${docxResult.images.length} embedded images`);
48
+ }
49
+ if (docxResult.sections) {
50
+ const headings = docxResult.sections.filter(s => s.type === 'heading').length;
51
+ statusParts.push(`${headings} headings`);
52
+ }
53
+ if (statusParts.length > 0) {
54
+ content = `[DOCX: ${statusParts.join(', ')}]\n\n${content}`;
55
+ }
56
+ }
57
+ return {
58
+ content,
59
+ mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
60
+ metadata: {
61
+ isDocx: true,
62
+ title: docxResult.metadata.title,
63
+ author: docxResult.metadata.author,
64
+ subject: docxResult.metadata.subject,
65
+ description: docxResult.metadata.description,
66
+ creationDate: docxResult.metadata.creationDate,
67
+ modificationDate: docxResult.metadata.modificationDate,
68
+ lastModifiedBy: docxResult.metadata.lastModifiedBy,
69
+ revision: docxResult.metadata.revision,
70
+ fileSize: docxResult.metadata.fileSize,
71
+ images: docxResult.images,
72
+ sections: docxResult.sections
73
+ }
74
+ };
75
+ }
76
+ catch (error) {
77
+ const errorMessage = error instanceof Error ? error.message : String(error);
78
+ return {
79
+ content: `Error reading DOCX: ${errorMessage}`,
80
+ mimeType: 'text/plain',
81
+ metadata: {
82
+ error: true,
83
+ errorMessage
84
+ }
85
+ };
86
+ }
87
+ }
88
+ /**
89
+ * Write DOCX file.
90
+ *
91
+ * - String content + 'rewrite': create new DOCX from HTML/markdown.
92
+ * - String content + 'append': append to existing DOCX → writes to {name}_v1.docx.
93
+ * - Array content: apply DocxOperation[] edits → writes to {name}_v1.docx.
94
+ * Original file is always preserved.
95
+ */
96
+ async write(path, content, mode = 'rewrite') {
97
+ const baseDir = path ? this.getBaseDir(path) : process.cwd();
98
+ // String content → treat as HTML (or markdown which will be converted)
99
+ if (typeof content === 'string') {
100
+ if (mode === 'append') {
101
+ // Append HTML/markdown — write to _v1 file, preserve original
102
+ const targetPath = await generateOutputPath(path);
103
+ const operations = [{
104
+ type: 'appendMarkdown',
105
+ markdown: content
106
+ }];
107
+ const buffer = await editDocxWithOperations(path, operations, { baseDir });
108
+ await fs.writeFile(targetPath, buffer);
109
+ }
110
+ else {
111
+ const html = convertToHtmlIfNeeded(content);
112
+ const buffer = await createDocxFromHtml(html, { baseDir });
113
+ await fs.writeFile(path, buffer);
114
+ }
115
+ return;
116
+ }
117
+ // Array content → treat as DocxOperation[], write to _v1 file, preserve original
118
+ if (Array.isArray(content)) {
119
+ try {
120
+ await fs.access(path);
121
+ }
122
+ catch {
123
+ throw new Error(`Cannot modify DOCX: source file does not exist: ${path}. Use string content to create a new DOCX file.`);
124
+ }
125
+ const targetPath = await generateOutputPath(path);
126
+ const operations = content;
127
+ const buffer = await editDocxWithOperations(path, operations, { baseDir });
128
+ await fs.writeFile(targetPath, buffer);
129
+ return;
130
+ }
131
+ throw new Error('Unsupported content type for DOCX write. Expected HTML/markdown string or array of operations.');
132
+ }
133
+ /**
134
+ * Edit DOCX by applying high-level operations.
135
+ * Writes to {name}_v1.docx unless `options.outputPath` is provided.
136
+ */
137
+ async editRange(path, range, content, options) {
138
+ const baseDir = this.getBaseDir(path);
139
+ // Use provided outputPath, otherwise write to _v1 file
140
+ const outputPath = options?.outputPath ?? await generateOutputPath(path);
141
+ try {
142
+ await fs.access(path);
143
+ }
144
+ catch {
145
+ return {
146
+ success: false,
147
+ editsApplied: 0,
148
+ errors: [{
149
+ location: range,
150
+ error: `Cannot edit DOCX: source file does not exist: ${path}`
151
+ }]
152
+ };
153
+ }
154
+ let operations;
155
+ if (typeof content === 'string') {
156
+ // Treat string content as HTML/markdown to append (will be converted to HTML internally)
157
+ operations = [{
158
+ type: 'appendMarkdown',
159
+ markdown: content
160
+ }];
161
+ }
162
+ else if (Array.isArray(content)) {
163
+ operations = content;
164
+ }
165
+ else {
166
+ return {
167
+ success: false,
168
+ editsApplied: 0,
169
+ errors: [{
170
+ location: range,
171
+ error: 'Unsupported content type for DOCX edit. Expected HTML/markdown string or array of operations.'
172
+ }]
173
+ };
174
+ }
175
+ try {
176
+ const buffer = await editDocxWithOperations(path, operations, { baseDir, outputPath });
177
+ await fs.writeFile(outputPath, buffer);
178
+ return {
179
+ success: true,
180
+ editsApplied: operations.length
181
+ };
182
+ }
183
+ catch (error) {
184
+ const errorMessage = error instanceof Error ? error.message : String(error);
185
+ return {
186
+ success: false,
187
+ editsApplied: 0,
188
+ errors: [{
189
+ location: range,
190
+ error: errorMessage
191
+ }]
192
+ };
193
+ }
194
+ }
195
+ /**
196
+ * Get DOCX file information including metadata.
197
+ */
198
+ async getInfo(path) {
199
+ try {
200
+ const stats = await fs.stat(path);
201
+ let metadata = { isDocx: true };
202
+ try {
203
+ const docxResult = await parseDocxToHtml(path, {
204
+ includeImages: false,
205
+ preserveFormatting: false,
206
+ });
207
+ metadata = {
208
+ isDocx: true,
209
+ title: docxResult.metadata.title,
210
+ author: docxResult.metadata.author,
211
+ subject: docxResult.metadata.subject,
212
+ description: docxResult.metadata.description,
213
+ creationDate: docxResult.metadata.creationDate,
214
+ modificationDate: docxResult.metadata.modificationDate,
215
+ lastModifiedBy: docxResult.metadata.lastModifiedBy,
216
+ revision: docxResult.metadata.revision,
217
+ imageCount: docxResult.images.length,
218
+ sectionCount: docxResult.sections?.length,
219
+ };
220
+ }
221
+ catch {
222
+ // Non-critical — return basic info if parsing fails
223
+ }
224
+ return {
225
+ size: stats.size,
226
+ created: stats.birthtime,
227
+ modified: stats.mtime,
228
+ accessed: stats.atime,
229
+ isDirectory: false,
230
+ isFile: true,
231
+ permissions: (stats.mode & 0o777).toString(8),
232
+ fileType: 'binary',
233
+ metadata
234
+ };
235
+ }
236
+ catch (error) {
237
+ const message = error instanceof Error ? error.message : String(error);
238
+ throw new DocxError(`Failed to get DOCX file info: ${message}`, 'GET_INFO_FAILED', { path });
239
+ }
240
+ }
241
+ /** Get base directory for resolving relative image paths. */
242
+ getBaseDir(docxPath) {
243
+ return docxPath ? path.dirname(docxPath) : process.cwd();
244
+ }
245
+ }
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "0.2.34";
1
+ export declare const VERSION = "0.2.35";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = '0.2.34';
1
+ export const VERSION = '0.2.35';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wonderwhy-er/desktop-commander",
3
- "version": "0.2.34",
3
+ "version": "0.2.35",
4
4
  "description": "MCP server for terminal operations and file editing",
5
5
  "mcpName": "io.github.wonderwhy-er/desktop-commander",
6
6
  "license": "MIT",