@wonderwhy-er/desktop-commander 0.2.35 → 0.2.36

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 (115) hide show
  1. package/README.md +2 -0
  2. package/dist/handlers/filesystem-handlers.js +58 -11
  3. package/dist/handlers/history-handlers.d.ts +7 -0
  4. package/dist/handlers/history-handlers.js +33 -1
  5. package/dist/server.js +30 -4
  6. package/dist/tools/docx/builders/image.d.ts +14 -0
  7. package/dist/tools/docx/builders/image.js +84 -0
  8. package/dist/tools/docx/builders/index.d.ts +9 -3
  9. package/dist/tools/docx/builders/index.js +9 -3
  10. package/dist/tools/docx/builders/paragraph.d.ts +12 -0
  11. package/dist/tools/docx/builders/paragraph.js +29 -0
  12. package/dist/tools/docx/builders/table.d.ts +8 -0
  13. package/dist/tools/docx/builders/table.js +94 -0
  14. package/dist/tools/docx/builders/utils.d.ts +5 -0
  15. package/dist/tools/docx/builders/utils.js +18 -0
  16. package/dist/tools/docx/constants.d.ts +28 -32
  17. package/dist/tools/docx/constants.js +56 -52
  18. package/dist/tools/docx/create.d.ts +21 -0
  19. package/dist/tools/docx/create.js +386 -0
  20. package/dist/tools/docx/dom.d.ts +66 -0
  21. package/dist/tools/docx/dom.js +228 -0
  22. package/dist/tools/docx/index.d.ts +8 -12
  23. package/dist/tools/docx/index.js +8 -14
  24. package/dist/tools/docx/modify.d.ts +28 -0
  25. package/dist/tools/docx/modify.js +271 -0
  26. package/dist/tools/docx/ops/delete-paragraph-at-body-index.d.ts +11 -0
  27. package/dist/tools/docx/ops/delete-paragraph-at-body-index.js +23 -0
  28. package/dist/tools/docx/ops/header-replace-text-exact.d.ts +13 -0
  29. package/dist/tools/docx/ops/header-replace-text-exact.js +55 -0
  30. package/dist/tools/docx/ops/index.d.ts +17 -0
  31. package/dist/tools/docx/ops/index.js +67 -0
  32. package/dist/tools/docx/ops/insert-image-after-text.d.ts +24 -0
  33. package/dist/tools/docx/ops/insert-image-after-text.js +128 -0
  34. package/dist/tools/docx/ops/insert-paragraph-after-text.d.ts +12 -0
  35. package/dist/tools/docx/ops/insert-paragraph-after-text.js +74 -0
  36. package/dist/tools/docx/ops/insert-table-after-text.d.ts +19 -0
  37. package/dist/tools/docx/ops/insert-table-after-text.js +57 -0
  38. package/dist/tools/docx/ops/replace-hyperlink-url.d.ts +12 -0
  39. package/dist/tools/docx/ops/replace-hyperlink-url.js +37 -0
  40. package/dist/tools/docx/ops/replace-paragraph-at-body-index.d.ts +9 -0
  41. package/dist/tools/docx/ops/replace-paragraph-at-body-index.js +25 -0
  42. package/dist/tools/docx/ops/replace-paragraph-text-exact.d.ts +9 -0
  43. package/dist/tools/docx/ops/replace-paragraph-text-exact.js +21 -0
  44. package/dist/tools/docx/ops/set-color-for-paragraph-exact.d.ts +8 -0
  45. package/dist/tools/docx/ops/set-color-for-paragraph-exact.js +23 -0
  46. package/dist/tools/docx/ops/set-color-for-style.d.ts +9 -0
  47. package/dist/tools/docx/ops/set-color-for-style.js +27 -0
  48. package/dist/tools/docx/ops/set-paragraph-style-at-body-index.d.ts +8 -0
  49. package/dist/tools/docx/ops/set-paragraph-style-at-body-index.js +57 -0
  50. package/dist/tools/docx/ops/table-set-cell-text.d.ts +9 -0
  51. package/dist/tools/docx/ops/table-set-cell-text.js +72 -0
  52. package/dist/tools/docx/read.d.ts +27 -0
  53. package/dist/tools/docx/read.js +188 -0
  54. package/dist/tools/docx/relationships.d.ts +22 -0
  55. package/dist/tools/docx/relationships.js +76 -0
  56. package/dist/tools/docx/types.d.ts +174 -104
  57. package/dist/tools/docx/types.js +2 -5
  58. package/dist/tools/docx/validate.d.ts +33 -0
  59. package/dist/tools/docx/validate.js +49 -0
  60. package/dist/tools/docx/write.d.ts +17 -0
  61. package/dist/tools/docx/write.js +88 -0
  62. package/dist/tools/docx/zip.d.ts +21 -0
  63. package/dist/tools/docx/zip.js +35 -0
  64. package/dist/tools/schemas.d.ts +13 -0
  65. package/dist/tools/schemas.js +5 -0
  66. package/dist/types.d.ts +10 -0
  67. package/dist/ui/contracts.d.ts +14 -0
  68. package/dist/ui/contracts.js +18 -0
  69. package/dist/ui/file-preview/index.html +16 -0
  70. package/dist/ui/file-preview/preview-runtime.js +13977 -0
  71. package/dist/ui/file-preview/shared/preview-file-types.d.ts +5 -0
  72. package/dist/ui/file-preview/shared/preview-file-types.js +57 -0
  73. package/dist/ui/file-preview/src/app.d.ts +4 -0
  74. package/dist/ui/file-preview/src/app.js +800 -0
  75. package/dist/ui/file-preview/src/components/code-viewer.d.ts +6 -0
  76. package/dist/ui/file-preview/src/components/code-viewer.js +73 -0
  77. package/dist/ui/file-preview/src/components/highlighting.d.ts +2 -0
  78. package/dist/ui/file-preview/src/components/highlighting.js +54 -0
  79. package/dist/ui/file-preview/src/components/html-renderer.d.ts +9 -0
  80. package/dist/ui/file-preview/src/components/html-renderer.js +63 -0
  81. package/dist/ui/file-preview/src/components/markdown-renderer.d.ts +1 -0
  82. package/dist/ui/file-preview/src/components/markdown-renderer.js +21 -0
  83. package/dist/ui/file-preview/src/components/toolbar.d.ts +6 -0
  84. package/dist/ui/file-preview/src/components/toolbar.js +75 -0
  85. package/dist/ui/file-preview/src/image-preview.d.ts +3 -0
  86. package/dist/ui/file-preview/src/image-preview.js +21 -0
  87. package/dist/ui/file-preview/src/main.d.ts +1 -0
  88. package/dist/ui/file-preview/src/main.js +5 -0
  89. package/dist/ui/file-preview/src/types.d.ts +1 -0
  90. package/dist/ui/file-preview/src/types.js +1 -0
  91. package/dist/ui/file-preview/styles.css +764 -0
  92. package/dist/ui/resources.d.ts +21 -0
  93. package/dist/ui/resources.js +72 -0
  94. package/dist/ui/shared/escape-html.d.ts +4 -0
  95. package/dist/ui/shared/escape-html.js +11 -0
  96. package/dist/ui/shared/host-lifecycle.d.ts +16 -0
  97. package/dist/ui/shared/host-lifecycle.js +35 -0
  98. package/dist/ui/shared/rpc-client.d.ts +14 -0
  99. package/dist/ui/shared/rpc-client.js +72 -0
  100. package/dist/ui/shared/theme-adaptation.d.ts +10 -0
  101. package/dist/ui/shared/theme-adaptation.js +118 -0
  102. package/dist/ui/shared/tool-header.d.ts +9 -0
  103. package/dist/ui/shared/tool-header.js +25 -0
  104. package/dist/ui/shared/tool-shell.d.ts +16 -0
  105. package/dist/ui/shared/tool-shell.js +65 -0
  106. package/dist/ui/shared/widget-state.d.ts +28 -0
  107. package/dist/ui/shared/widget-state.js +60 -0
  108. package/dist/utils/capture.d.ts +1 -0
  109. package/dist/utils/capture.js +6 -0
  110. package/dist/utils/files/docx.d.ts +8 -15
  111. package/dist/utils/files/docx.js +76 -176
  112. package/dist/utils/files/text.js +9 -1
  113. package/dist/version.d.ts +1 -1
  114. package/dist/version.js +1 -1
  115. package/package.json +5 -2
package/README.md CHANGED
@@ -16,6 +16,8 @@ Work with code and text, run processes, and automate tasks, going far beyond oth
16
16
  <img width="380" height="200" src="https://glama.ai/mcp/servers/zempur9oh4/badge" alt="Desktop Commander MCP" />
17
17
  </a>
18
18
 
19
+ ## 👋 We’re hiring — come build with us: https://desktopcommander.app/careers/
20
+
19
21
  ## Table of Contents
20
22
  - [Features](#features)
21
23
  - [How to install](#how-to-install)
@@ -3,11 +3,34 @@ import { withTimeout } from '../utils/withTimeout.js';
3
3
  import { createErrorResponse } from '../error-handlers.js';
4
4
  import { configManager } from '../config-manager.js';
5
5
  import { ReadFileArgsSchema, ReadMultipleFilesArgsSchema, WriteFileArgsSchema, CreateDirectoryArgsSchema, ListDirectoryArgsSchema, MoveFileArgsSchema, GetFileInfoArgsSchema, WritePdfArgsSchema } from '../tools/schemas.js';
6
+ import path from 'path';
7
+ import os from 'os';
8
+ import { buildUiToolMeta, FILE_PREVIEW_RESOURCE_URI } from '../ui/contracts.js';
9
+ import { resolvePreviewFileType } from '../ui/file-preview/shared/preview-file-types.js';
10
+ /**
11
+ * Expand home directory (~) in a file path
12
+ */
13
+ function expandHome(filePath) {
14
+ if (filePath === '~' || filePath.startsWith('~/') || filePath.startsWith(`~${path.sep}`)) {
15
+ return path.join(os.homedir(), filePath.slice(1));
16
+ }
17
+ return filePath;
18
+ }
19
+ /**
20
+ * Resolve a file path to an absolute path for use in structured content.
21
+ * This ensures "Open in folder" always has a valid absolute path.
22
+ */
23
+ function resolveAbsolutePath(filePath) {
24
+ const expanded = expandHome(filePath);
25
+ return path.isAbsolute(expanded)
26
+ ? path.resolve(expanded)
27
+ : path.resolve(process.cwd(), expanded);
28
+ }
6
29
  /**
7
30
  * Helper function to check if path contains an error
8
31
  */
9
- function isErrorPath(path) {
10
- return path.startsWith('__ERROR__:');
32
+ function isErrorPath(filePath) {
33
+ return filePath.startsWith('__ERROR__:');
11
34
  }
12
35
  /**
13
36
  * Extract error message from error path
@@ -44,6 +67,10 @@ export async function handleReadFile(args) {
44
67
  sheet: sheetParam,
45
68
  range: parsed.range
46
69
  };
70
+ // Resolve to absolute path for local files (not URLs) so "Open in folder" works
71
+ const resolvedFilePath = parsed.isUrl
72
+ ? parsed.path
73
+ : resolveAbsolutePath(parsed.path);
47
74
  const fileResult = await readFile(parsed.path, options);
48
75
  // Handle PDF files
49
76
  if (fileResult.metadata?.isPdf) {
@@ -68,28 +95,40 @@ export async function handleReadFile(args) {
68
95
  text: `PDF file: ${parsed.path}${author}${title} (${meta?.totalPages} pages) \n`
69
96
  },
70
97
  ...pdfContent
71
- ]
98
+ ],
99
+ structuredContent: {
100
+ fileName: path.basename(resolvedFilePath),
101
+ filePath: resolvedFilePath,
102
+ fileType: 'unsupported',
103
+ content: ''
104
+ },
105
+ _meta: buildUiToolMeta(FILE_PREVIEW_RESOURCE_URI, true)
72
106
  };
73
107
  }
74
108
  // Handle image files
75
109
  if (fileResult.metadata?.isImage) {
76
- // For image files, return as an image content type
77
- // Content should already be base64-encoded string from handler
110
+ // For image files, keep content payload text-only for broad host compatibility.
111
+ // The preview widget reads image bytes from structuredContent.
78
112
  const imageData = typeof fileResult.content === 'string'
79
113
  ? fileResult.content
80
114
  : fileResult.content.toString('base64');
115
+ const imageSummary = `Image file: ${parsed.path} (${fileResult.mimeType})\n`;
81
116
  return {
82
117
  content: [
83
118
  {
84
119
  type: "text",
85
- text: `Image file: ${parsed.path} (${fileResult.mimeType})\n`
86
- },
87
- {
88
- type: "image",
89
- data: imageData,
90
- mimeType: fileResult.mimeType
120
+ text: imageSummary
91
121
  }
92
122
  ],
123
+ structuredContent: {
124
+ fileName: path.basename(resolvedFilePath),
125
+ filePath: resolvedFilePath,
126
+ fileType: 'image',
127
+ content: imageSummary,
128
+ imageData,
129
+ mimeType: fileResult.mimeType
130
+ },
131
+ _meta: buildUiToolMeta(FILE_PREVIEW_RESOURCE_URI, true)
93
132
  };
94
133
  }
95
134
  else {
@@ -97,8 +136,16 @@ export async function handleReadFile(args) {
97
136
  const textContent = typeof fileResult.content === 'string'
98
137
  ? fileResult.content
99
138
  : fileResult.content.toString('utf8');
139
+ const previewFileType = resolvePreviewFileType(resolvedFilePath);
100
140
  return {
101
141
  content: [{ type: "text", text: textContent }],
142
+ structuredContent: {
143
+ fileName: path.basename(resolvedFilePath),
144
+ filePath: resolvedFilePath,
145
+ fileType: previewFileType,
146
+ content: textContent
147
+ },
148
+ _meta: buildUiToolMeta(FILE_PREVIEW_RESOURCE_URI, true)
102
149
  };
103
150
  }
104
151
  };
@@ -1,5 +1,12 @@
1
1
  import { ServerResult } from '../types.js';
2
+ type TrackUiEventParams = Record<string, string | number | boolean | null>;
3
+ export declare function buildTrackUiEventCapturePayload(event: string, component: string, params: TrackUiEventParams): Record<string, string | number | boolean | null>;
2
4
  /**
3
5
  * Handle get_recent_tool_calls command
4
6
  */
5
7
  export declare function handleGetRecentToolCalls(args: unknown): Promise<ServerResult>;
8
+ /**
9
+ * Handle track_ui_event command
10
+ */
11
+ export declare function handleTrackUiEvent(args: unknown): Promise<ServerResult>;
12
+ export {};
@@ -1,5 +1,13 @@
1
1
  import { toolHistory } from '../utils/toolHistory.js';
2
- import { GetRecentToolCallsArgsSchema } from '../tools/schemas.js';
2
+ import { GetRecentToolCallsArgsSchema, TrackUiEventArgsSchema } from '../tools/schemas.js';
3
+ import { capture_ui_event } from '../utils/capture.js';
4
+ export function buildTrackUiEventCapturePayload(event, component, params) {
5
+ return {
6
+ ...params,
7
+ component,
8
+ event
9
+ };
10
+ }
3
11
  /**
4
12
  * Handle get_recent_tool_calls command
5
13
  */
@@ -33,3 +41,27 @@ export async function handleGetRecentToolCalls(args) {
33
41
  };
34
42
  }
35
43
  }
44
+ /**
45
+ * Handle track_ui_event command
46
+ */
47
+ export async function handleTrackUiEvent(args) {
48
+ try {
49
+ const parsed = TrackUiEventArgsSchema.parse(args);
50
+ await capture_ui_event('mcp_ui_event', buildTrackUiEventCapturePayload(parsed.event, parsed.component, parsed.params));
51
+ return {
52
+ content: [{
53
+ type: "text",
54
+ text: `Tracked UI event: ${parsed.event}`
55
+ }]
56
+ };
57
+ }
58
+ catch (error) {
59
+ return {
60
+ content: [{
61
+ type: "text",
62
+ text: `Error tracking UI event: ${error instanceof Error ? error.message : String(error)}`
63
+ }],
64
+ isError: true
65
+ };
66
+ }
67
+ }
package/dist/server.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
- import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ListPromptsRequestSchema, InitializeRequestSchema, LATEST_PROTOCOL_VERSION, SUPPORTED_PROTOCOL_VERSIONS, } from "@modelcontextprotocol/sdk/types.js";
2
+ import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ListResourceTemplatesRequestSchema, ListPromptsRequestSchema, InitializeRequestSchema, LATEST_PROTOCOL_VERSION, SUPPORTED_PROTOCOL_VERSIONS, } from "@modelcontextprotocol/sdk/types.js";
3
3
  import { zodToJsonSchema } from "zod-to-json-schema";
4
4
  import { getSystemInfo, getOSSpecificGuidance, getPathGuidance, getDevelopmentToolGuidance } from './utils/system-info.js';
5
5
  // Get system information once at startup
@@ -21,6 +21,8 @@ import { handleWelcomePageOnboarding } from './utils/welcome-onboarding.js';
21
21
  import { VERSION } from './version.js';
22
22
  import { capture, capture_call_tool } from "./utils/capture.js";
23
23
  import { logToStderr, logger } from './utils/logger.js';
24
+ import { buildUiToolMeta, FILE_PREVIEW_RESOURCE_URI } from './ui/contracts.js';
25
+ import { listUiResources, readUiResource } from './ui/resources.js';
24
26
  // Store startup messages to send after initialization
25
27
  const deferredMessages = [];
26
28
  function deferLog(level, message) {
@@ -47,11 +49,18 @@ export const server = new Server({
47
49
  });
48
50
  // Add handler for resources/list method
49
51
  server.setRequestHandler(ListResourcesRequestSchema, async () => {
50
- // Return an empty list of resources
51
52
  return {
52
- resources: [],
53
+ resources: listUiResources(),
53
54
  };
54
55
  });
56
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
57
+ const { uri } = request.params;
58
+ const response = await readUiResource(uri);
59
+ if (response) {
60
+ return response;
61
+ }
62
+ throw new Error(`Unknown resource URI: ${uri}`);
63
+ });
55
64
  // Add handler for prompts/list method
56
65
  server.setRequestHandler(ListPromptsRequestSchema, async () => {
57
66
  // Return an empty list of prompts
@@ -237,6 +246,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
237
246
  ${PATH_GUIDANCE}
238
247
  ${CMD_PREFIX_DESCRIPTION}`,
239
248
  inputSchema: zodToJsonSchema(ReadFileArgsSchema),
249
+ _meta: buildUiToolMeta(FILE_PREVIEW_RESOURCE_URI, true),
240
250
  annotations: {
241
251
  title: "Read File or URL",
242
252
  readOnlyHint: true,
@@ -1179,6 +1189,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1179
1189
  };
1180
1190
  }
1181
1191
  break;
1192
+ case "track_ui_event":
1193
+ try {
1194
+ result = await handlers.handleTrackUiEvent(args);
1195
+ }
1196
+ catch (error) {
1197
+ capture('server_request_error', { message: `Error in track_ui_event handler: ${error}` });
1198
+ result = {
1199
+ content: [{ type: "text", text: `Error: Failed to track UI event` }],
1200
+ isError: true,
1201
+ };
1202
+ }
1203
+ break;
1182
1204
  case "give_feedback_to_desktop_commander":
1183
1205
  try {
1184
1206
  result = await giveFeedbackToDesktopCommander(args);
@@ -1265,12 +1287,16 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1265
1287
  // Add tool call to history (exclude only get_recent_tool_calls to prevent recursion)
1266
1288
  const duration = Date.now() - startTime;
1267
1289
  const EXCLUDED_TOOLS = [
1268
- 'get_recent_tool_calls'
1290
+ 'get_recent_tool_calls',
1291
+ 'track_ui_event'
1269
1292
  ];
1270
1293
  if (!EXCLUDED_TOOLS.includes(name)) {
1271
1294
  toolHistory.addCall(name, args, result, duration);
1272
1295
  }
1273
1296
  // Track success or failure based on result
1297
+ if (name === 'track_ui_event') {
1298
+ return result;
1299
+ }
1274
1300
  if (result.isError) {
1275
1301
  await usageTracker.trackFailure(name);
1276
1302
  console.log(`[FEEDBACK DEBUG] Tool ${name} failed, not checking feedback`);
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Image builder — creates w:drawing elements and manages image relationships.
3
+ */
4
+ import PizZip from 'pizzip';
5
+ import type { DocxContentImage, InsertImageOp } from '../types.js';
6
+ /**
7
+ * Build an image element and add it to the ZIP archive.
8
+ *
9
+ * @param doc The XML document
10
+ * @param zip The DOCX ZIP archive
11
+ * @param spec The image specification (from content or operation)
12
+ * @returns A w:p element containing the image drawing
13
+ */
14
+ export declare function buildImageElement(doc: Document, zip: PizZip, spec: DocxContentImage | InsertImageOp): Promise<Element>;
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Image builder — creates w:drawing elements and manages image relationships.
3
+ */
4
+ import fs from 'fs/promises';
5
+ import path from 'path';
6
+ import { DOMParser } from '@xmldom/xmldom';
7
+ import { addImageRelationship, ensureContentType } from '../relationships.js';
8
+ import { escapeXmlAttr } from './utils.js';
9
+ import { pixelsToEmu, DEFAULT_IMAGE_WIDTH, DEFAULT_IMAGE_HEIGHT, NAMESPACES } from '../constants.js';
10
+ /**
11
+ * Build an image element and add it to the ZIP archive.
12
+ *
13
+ * @param doc The XML document
14
+ * @param zip The DOCX ZIP archive
15
+ * @param spec The image specification (from content or operation)
16
+ * @returns A w:p element containing the image drawing
17
+ */
18
+ export async function buildImageElement(doc, zip, spec) {
19
+ // Validate image exists
20
+ try {
21
+ await fs.access(spec.imagePath);
22
+ }
23
+ catch {
24
+ throw new Error(`Image file not found: ${spec.imagePath}`);
25
+ }
26
+ // Read image
27
+ const imgBuffer = await fs.readFile(spec.imagePath);
28
+ const ext = path.extname(spec.imagePath).toLowerCase();
29
+ const baseName = path.basename(spec.imagePath);
30
+ // Find next available media filename
31
+ let mediaIndex = 1;
32
+ while (zip.file(`word/media/image${mediaIndex}${ext}`)) {
33
+ mediaIndex++;
34
+ }
35
+ const mediaFileName = `image${mediaIndex}${ext}`;
36
+ // Add image to ZIP
37
+ zip.file(`word/media/${mediaFileName}`, imgBuffer);
38
+ // Add relationship
39
+ const rId = addImageRelationship(zip, mediaFileName);
40
+ // Ensure Content_Types entry
41
+ ensureContentType(zip, ext);
42
+ // Compute dimensions (EMU)
43
+ const widthPx = spec.width ?? DEFAULT_IMAGE_WIDTH;
44
+ const heightPx = spec.height ?? DEFAULT_IMAGE_HEIGHT;
45
+ const widthEmu = pixelsToEmu(widthPx);
46
+ const heightEmu = pixelsToEmu(heightPx);
47
+ // Build drawing XML
48
+ const altText = spec.altText ?? baseName;
49
+ const drawingXmlStr = buildDrawingXml(rId, widthEmu, heightEmu, altText, mediaFileName);
50
+ // Parse drawing XML into a paragraph
51
+ const drawingFragment = new DOMParser().parseFromString(`<w:p xmlns:w="${NAMESPACES.W}">` +
52
+ `<w:r>${drawingXmlStr}</w:r></w:p>`, 'application/xml');
53
+ return doc.importNode(drawingFragment.documentElement, true);
54
+ }
55
+ /**
56
+ * Build the inline w:drawing XML for an image reference.
57
+ */
58
+ function buildDrawingXml(rId, widthEmu, heightEmu, altText, fileName) {
59
+ return (`<w:drawing xmlns:w="${NAMESPACES.W}">` +
60
+ `<wp:inline distT="0" distB="0" distL="0" distR="0" ` +
61
+ `xmlns:wp="${NAMESPACES.WP}">` +
62
+ `<wp:extent cx="${widthEmu}" cy="${heightEmu}"/>` +
63
+ `<wp:docPr id="1" name="${fileName}" descr="${escapeXmlAttr(altText)}"/>` +
64
+ `<a:graphic xmlns:a="${NAMESPACES.A}">` +
65
+ `<a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture">` +
66
+ `<pic:pic xmlns:pic="${NAMESPACES.PIC}">` +
67
+ `<pic:nvPicPr>` +
68
+ `<pic:cNvPr id="0" name="${fileName}" descr="${escapeXmlAttr(altText)}"/>` +
69
+ `<pic:cNvPicPr/>` +
70
+ `</pic:nvPicPr>` +
71
+ `<pic:blipFill>` +
72
+ `<a:blip r:embed="${rId}" xmlns:r="${NAMESPACES.R}"/>` +
73
+ `<a:stretch><a:fillRect/></a:stretch>` +
74
+ `</pic:blipFill>` +
75
+ `<pic:spPr>` +
76
+ `<a:xfrm><a:off x="0" y="0"/><a:ext cx="${widthEmu}" cy="${heightEmu}"/></a:xfrm>` +
77
+ `<a:prstGeom prst="rect"><a:avLst/></a:prstGeom>` +
78
+ `</pic:spPr>` +
79
+ `</pic:pic>` +
80
+ `</a:graphicData>` +
81
+ `</a:graphic>` +
82
+ `</wp:inline>` +
83
+ `</w:drawing>`);
84
+ }
@@ -1,5 +1,11 @@
1
1
  /**
2
- * DOCX Builders
3
- * Centralized exports for all building utilities
2
+ * DOCX element builders — Single Responsibility: build XML elements
3
+ * for paragraphs, tables, and images.
4
+ *
5
+ * These builders are shared between create.ts and ops/ modules to
6
+ * eliminate code duplication and ensure consistency.
4
7
  */
5
- export * from './markdown-builder.js';
8
+ export { buildParagraph } from './paragraph.js';
9
+ export { buildTable } from './table.js';
10
+ export { buildImageElement } from './image.js';
11
+ export { escapeXml, escapeXmlAttr } from './utils.js';
@@ -1,5 +1,11 @@
1
1
  /**
2
- * DOCX Builders
3
- * Centralized exports for all building utilities
2
+ * DOCX element builders — Single Responsibility: build XML elements
3
+ * for paragraphs, tables, and images.
4
+ *
5
+ * These builders are shared between create.ts and ops/ modules to
6
+ * eliminate code duplication and ensure consistency.
4
7
  */
5
- export * from './markdown-builder.js';
8
+ export { buildParagraph } from './paragraph.js';
9
+ export { buildTable } from './table.js';
10
+ export { buildImageElement } from './image.js';
11
+ export { escapeXml, escapeXmlAttr } from './utils.js';
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Paragraph builder — creates w:p elements with optional styles.
3
+ */
4
+ import type { DocxContentParagraph } from '../types.js';
5
+ /**
6
+ * Build a paragraph element from content structure.
7
+ *
8
+ * @param doc The XML document
9
+ * @param item The paragraph content item
10
+ * @returns A w:p element
11
+ */
12
+ export declare function buildParagraph(doc: Document, item: DocxContentParagraph): Element;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Paragraph builder — creates w:p elements with optional styles.
3
+ */
4
+ /**
5
+ * Build a paragraph element from content structure.
6
+ *
7
+ * @param doc The XML document
8
+ * @param item The paragraph content item
9
+ * @returns A w:p element
10
+ */
11
+ export function buildParagraph(doc, item) {
12
+ const p = doc.createElement('w:p');
13
+ // Set style if provided
14
+ if (item.style) {
15
+ const pPr = doc.createElement('w:pPr');
16
+ const pStyle = doc.createElement('w:pStyle');
17
+ pStyle.setAttribute('w:val', item.style);
18
+ pPr.appendChild(pStyle);
19
+ p.appendChild(pPr);
20
+ }
21
+ // Add text run
22
+ const r = doc.createElement('w:r');
23
+ const t = doc.createElement('w:t');
24
+ t.setAttribute('xml:space', 'preserve');
25
+ t.textContent = item.text;
26
+ r.appendChild(t);
27
+ p.appendChild(r);
28
+ return p;
29
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Table builder — creates w:tbl elements with headers, rows, and styling.
3
+ */
4
+ import type { DocxContentTable, InsertTableOp } from '../types.js';
5
+ /**
6
+ * Build a table element from content structure or operation.
7
+ */
8
+ export declare function buildTable(doc: Document, spec: DocxContentTable | InsertTableOp): Element;
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Table builder — creates w:tbl elements with headers, rows, and styling.
3
+ */
4
+ /**
5
+ * Build a table element from content structure or operation.
6
+ */
7
+ export function buildTable(doc, spec) {
8
+ const tbl = doc.createElement('w:tbl');
9
+ // Table properties
10
+ const tblPr = doc.createElement('w:tblPr');
11
+ if (spec.style) {
12
+ const tblStyle = doc.createElement('w:tblStyle');
13
+ tblStyle.setAttribute('w:val', spec.style);
14
+ tblPr.appendChild(tblStyle);
15
+ }
16
+ const tblW = doc.createElement('w:tblW');
17
+ tblW.setAttribute('w:w', '0');
18
+ tblW.setAttribute('w:type', 'auto');
19
+ tblPr.appendChild(tblW);
20
+ // Table borders
21
+ const tblBorders = doc.createElement('w:tblBorders');
22
+ for (const side of ['top', 'left', 'bottom', 'right', 'insideH', 'insideV']) {
23
+ const border = doc.createElement(`w:${side}`);
24
+ border.setAttribute('w:val', 'single');
25
+ border.setAttribute('w:sz', '4');
26
+ border.setAttribute('w:space', '0');
27
+ border.setAttribute('w:color', '000000');
28
+ tblBorders.appendChild(border);
29
+ }
30
+ tblPr.appendChild(tblBorders);
31
+ tbl.appendChild(tblPr);
32
+ // Table grid
33
+ const colCount = spec.headers
34
+ ? spec.headers.length
35
+ : spec.rows.length > 0
36
+ ? spec.rows[0].length
37
+ : 0;
38
+ if (colCount > 0) {
39
+ const tblGrid = doc.createElement('w:tblGrid');
40
+ for (let c = 0; c < colCount; c++) {
41
+ const gridCol = doc.createElement('w:gridCol');
42
+ const w = spec.colWidths?.[c] ?? Math.floor(9000 / colCount);
43
+ gridCol.setAttribute('w:w', String(w));
44
+ tblGrid.appendChild(gridCol);
45
+ }
46
+ tbl.appendChild(tblGrid);
47
+ }
48
+ // Helper to build a cell
49
+ const buildCell = (text, isHeader, widthTwips) => {
50
+ const tc = doc.createElement('w:tc');
51
+ if (widthTwips) {
52
+ const tcPr = doc.createElement('w:tcPr');
53
+ const tcW = doc.createElement('w:tcW');
54
+ tcW.setAttribute('w:w', String(widthTwips));
55
+ tcW.setAttribute('w:type', 'dxa');
56
+ tcPr.appendChild(tcW);
57
+ tc.appendChild(tcPr);
58
+ }
59
+ const p = doc.createElement('w:p');
60
+ const r = doc.createElement('w:r');
61
+ if (isHeader) {
62
+ const rPr = doc.createElement('w:rPr');
63
+ const b = doc.createElement('w:b');
64
+ rPr.appendChild(b);
65
+ r.appendChild(rPr);
66
+ }
67
+ const t = doc.createElement('w:t');
68
+ t.setAttribute('xml:space', 'preserve');
69
+ t.textContent = text;
70
+ r.appendChild(t);
71
+ p.appendChild(r);
72
+ tc.appendChild(p);
73
+ return tc;
74
+ };
75
+ // Header row
76
+ if (spec.headers && spec.headers.length > 0) {
77
+ const tr = doc.createElement('w:tr');
78
+ for (let i = 0; i < spec.headers.length; i++) {
79
+ const width = spec.colWidths?.[i];
80
+ tr.appendChild(buildCell(spec.headers[i], true, width));
81
+ }
82
+ tbl.appendChild(tr);
83
+ }
84
+ // Data rows
85
+ for (const row of spec.rows) {
86
+ const tr = doc.createElement('w:tr');
87
+ for (let i = 0; i < row.length; i++) {
88
+ const width = spec.colWidths?.[i];
89
+ tr.appendChild(buildCell(row[i], false, width));
90
+ }
91
+ tbl.appendChild(tr);
92
+ }
93
+ return tbl;
94
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * XML escaping utilities.
3
+ */
4
+ export declare function escapeXml(s: string): string;
5
+ export declare function escapeXmlAttr(s: string): string;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * XML escaping utilities.
3
+ */
4
+ export function escapeXml(s) {
5
+ return s
6
+ .replace(/&/g, '&amp;')
7
+ .replace(/</g, '&lt;')
8
+ .replace(/>/g, '&gt;')
9
+ .replace(/"/g, '&quot;')
10
+ .replace(/'/g, '&apos;');
11
+ }
12
+ export function escapeXmlAttr(s) {
13
+ return s
14
+ .replace(/&/g, '&amp;')
15
+ .replace(/"/g, '&quot;')
16
+ .replace(/</g, '&lt;')
17
+ .replace(/>/g, '&gt;');
18
+ }
@@ -1,36 +1,32 @@
1
1
  /**
2
- * DOCX Constants
3
- *
4
- * Centralised constants shared across the DOCX module.
5
- *
6
- * @module docx/constants
2
+ * DOCX constants — shared values used across the module.
7
3
  */
8
- export declare const DEFAULT_CONVERSION_OPTIONS: {
9
- readonly includeImages: true;
10
- readonly preserveFormatting: true;
11
- readonly styleMap: readonly string[];
12
- };
13
- export declare const DEFAULT_BUILD_OPTIONS: {
14
- readonly font: "Calibri";
15
- readonly fontSize: 11;
16
- readonly orientation: "portrait";
17
- readonly margins: {
18
- readonly top: 1440;
19
- readonly right: 1440;
20
- readonly bottom: 1440;
21
- readonly left: 1440;
22
- readonly header: 720;
23
- readonly footer: 720;
24
- readonly gutter: 0;
25
- };
26
- readonly footer: true;
27
- readonly pageNumber: false;
4
+ export declare const IMAGE_MIME_TYPES: Record<string, string>;
5
+ export declare function getMimeType(ext: string): string;
6
+ /**
7
+ * Convert pixels to EMU (English Metric Units).
8
+ * 1 inch = 914400 EMU, 1 px ≈ 9525 EMU (at 96 DPI)
9
+ */
10
+ export declare const PX_TO_EMU = 9525;
11
+ export declare function pixelsToEmu(px: number): number;
12
+ export declare const NAMESPACES: {
13
+ readonly W: "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
14
+ readonly WP: "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing";
15
+ readonly A: "http://schemas.openxmlformats.org/drawingml/2006/main";
16
+ readonly PIC: "http://schemas.openxmlformats.org/drawingml/2006/picture";
17
+ readonly R: "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
18
+ readonly RELS: "http://schemas.openxmlformats.org/package/2006/relationships";
28
19
  };
29
- export declare const DOCX_NAMESPACES: {
30
- readonly DUBLIN_CORE: "dc";
31
- readonly CUSTOM_PROPERTIES: "cp";
32
- readonly DCTERMS: "dcterms";
20
+ export declare const DEFAULT_IMAGE_WIDTH = 300;
21
+ export declare const DEFAULT_IMAGE_HEIGHT = 200;
22
+ export declare const DOCX_PATHS: {
23
+ readonly CONTENT_TYPES: "[Content_Types].xml";
24
+ readonly DOCUMENT_XML: "word/document.xml";
25
+ readonly DOCUMENT_RELS: "word/_rels/document.xml.rels";
26
+ readonly ROOT_RELS: "_rels/.rels";
27
+ readonly STYLES_XML: "word/styles.xml";
28
+ readonly SETTINGS_XML: "word/settings.xml";
29
+ readonly WEB_SETTINGS_XML: "word/webSettings.xml";
30
+ readonly FONT_TABLE_XML: "word/fontTable.xml";
31
+ readonly MEDIA_FOLDER: "word/media";
33
32
  };
34
- export declare const CORE_PROPERTIES_PATH = "docProps/core.xml";
35
- export declare const IMAGE_MIME_TYPES: Readonly<Record<string, string>>;
36
- export declare const HTML_WRAPPER_TEMPLATE = "<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"UTF-8\">\n</head>\n<body>\n{content}\n</body>\n</html>";