@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.
- package/dist/tools/docx/builders/html-builder.d.ts +17 -0
- package/dist/tools/docx/builders/html-builder.js +92 -0
- package/dist/tools/docx/builders/index.d.ts +5 -0
- package/dist/tools/docx/builders/index.js +5 -0
- package/dist/tools/docx/builders/markdown-builder.d.ts +2 -0
- package/dist/tools/docx/builders/markdown-builder.js +260 -0
- package/dist/tools/docx/constants.d.ts +36 -0
- package/dist/tools/docx/constants.js +57 -0
- package/dist/tools/docx/converters/markdown-to-html.d.ts +17 -0
- package/dist/tools/docx/converters/markdown-to-html.js +111 -0
- package/dist/tools/docx/errors.d.ts +28 -0
- package/dist/tools/docx/errors.js +48 -0
- package/dist/tools/docx/extractors/images.d.ts +14 -0
- package/dist/tools/docx/extractors/images.js +40 -0
- package/dist/tools/docx/extractors/metadata.d.ts +14 -0
- package/dist/tools/docx/extractors/metadata.js +64 -0
- package/dist/tools/docx/extractors/sections.d.ts +14 -0
- package/dist/tools/docx/extractors/sections.js +61 -0
- package/dist/tools/docx/html.d.ts +17 -0
- package/dist/tools/docx/html.js +111 -0
- package/dist/tools/docx/index.d.ts +14 -0
- package/dist/tools/docx/index.js +16 -0
- package/dist/tools/docx/markdown.d.ts +84 -0
- package/dist/tools/docx/markdown.js +507 -0
- package/dist/tools/docx/operations/handlers/index.d.ts +39 -0
- package/dist/tools/docx/operations/handlers/index.js +152 -0
- package/dist/tools/docx/operations/html-manipulator.d.ts +24 -0
- package/dist/tools/docx/operations/html-manipulator.js +352 -0
- package/dist/tools/docx/operations/index.d.ts +14 -0
- package/dist/tools/docx/operations/index.js +61 -0
- package/dist/tools/docx/operations/operation-handlers.d.ts +3 -0
- package/dist/tools/docx/operations/operation-handlers.js +67 -0
- package/dist/tools/docx/operations/preprocessor.d.ts +14 -0
- package/dist/tools/docx/operations/preprocessor.js +44 -0
- package/dist/tools/docx/operations/xml-replacer.d.ts +9 -0
- package/dist/tools/docx/operations/xml-replacer.js +35 -0
- package/dist/tools/docx/operations.d.ts +13 -0
- package/dist/tools/docx/operations.js +13 -0
- package/dist/tools/docx/parsers/image-extractor.d.ts +18 -0
- package/dist/tools/docx/parsers/image-extractor.js +61 -0
- package/dist/tools/docx/parsers/index.d.ts +9 -0
- package/dist/tools/docx/parsers/index.js +9 -0
- package/dist/tools/docx/parsers/paragraph-parser.d.ts +2 -0
- package/dist/tools/docx/parsers/paragraph-parser.js +88 -0
- package/dist/tools/docx/parsers/table-parser.d.ts +9 -0
- package/dist/tools/docx/parsers/table-parser.js +72 -0
- package/dist/tools/docx/parsers/xml-parser.d.ts +25 -0
- package/dist/tools/docx/parsers/xml-parser.js +71 -0
- package/dist/tools/docx/parsers/zip-reader.d.ts +23 -0
- package/dist/tools/docx/parsers/zip-reader.js +52 -0
- package/dist/tools/docx/structure.d.ts +25 -0
- package/dist/tools/docx/structure.js +102 -0
- package/dist/tools/docx/styled-html-parser.d.ts +23 -0
- package/dist/tools/docx/styled-html-parser.js +1262 -0
- package/dist/tools/docx/types.d.ts +114 -0
- package/dist/tools/docx/types.js +8 -0
- package/dist/tools/docx/utils/escaping.d.ts +13 -0
- package/dist/tools/docx/utils/escaping.js +26 -0
- package/dist/tools/docx/utils/images.d.ts +9 -0
- package/dist/tools/docx/utils/images.js +26 -0
- package/dist/tools/docx/utils/index.d.ts +12 -0
- package/dist/tools/docx/utils/index.js +17 -0
- package/dist/tools/docx/utils/markdown.d.ts +13 -0
- package/dist/tools/docx/utils/markdown.js +32 -0
- package/dist/tools/docx/utils/paths.d.ts +15 -0
- package/dist/tools/docx/utils/paths.js +27 -0
- package/dist/tools/docx/utils/versioning.d.ts +25 -0
- package/dist/tools/docx/utils/versioning.js +55 -0
- package/dist/tools/docx/utils.d.ts +101 -0
- package/dist/tools/docx/utils.js +299 -0
- package/dist/tools/docx/validators.d.ts +13 -0
- package/dist/tools/docx/validators.js +40 -0
- package/dist/utils/capture.js +4 -4
- package/dist/utils/files/docx.d.ts +41 -0
- package/dist/utils/files/docx.js +245 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- 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
|
+
}
|
package/dist/utils/capture.js
CHANGED
|
@@ -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-
|
|
230
|
-
const GA_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-
|
|
237
|
-
const GA_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.
|
|
1
|
+
export declare const VERSION = "0.2.35";
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '0.2.
|
|
1
|
+
export const VERSION = '0.2.35';
|
package/package.json
CHANGED