@hcu-lab.me/mcp-device 1.0.0

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 (261) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +51 -0
  3. package/dist/bootstrap.d.ts +1 -0
  4. package/dist/bootstrap.js +22 -0
  5. package/dist/command-manager.d.ts +8 -0
  6. package/dist/command-manager.js +241 -0
  7. package/dist/config-field-definitions.d.ts +41 -0
  8. package/dist/config-field-definitions.js +37 -0
  9. package/dist/config-manager.d.ts +102 -0
  10. package/dist/config-manager.js +291 -0
  11. package/dist/config.d.ts +6 -0
  12. package/dist/config.js +10 -0
  13. package/dist/custom-stdio.d.ts +51 -0
  14. package/dist/custom-stdio.js +358 -0
  15. package/dist/data/official-app-ca.pem +12 -0
  16. package/dist/data/onboarding-prompts.json +70 -0
  17. package/dist/device/device-state.d.ts +13 -0
  18. package/dist/device/device-state.js +100 -0
  19. package/dist/device/device-status.d.ts +81 -0
  20. package/dist/device/device-status.js +156 -0
  21. package/dist/device/device.d.ts +16 -0
  22. package/dist/device/device.js +190 -0
  23. package/dist/device/execution-engine.d.ts +144 -0
  24. package/dist/device/execution-engine.js +195 -0
  25. package/dist/device/gateway-channel.d.ts +48 -0
  26. package/dist/device/gateway-channel.js +384 -0
  27. package/dist/device/gateway-config.d.ts +40 -0
  28. package/dist/device/gateway-config.js +207 -0
  29. package/dist/device/gateway-identity.d.ts +35 -0
  30. package/dist/device/gateway-identity.js +178 -0
  31. package/dist/device/gateway-pairing.d.ts +25 -0
  32. package/dist/device/gateway-pairing.js +105 -0
  33. package/dist/device/gateway-secure-transport.d.ts +25 -0
  34. package/dist/device/gateway-secure-transport.js +117 -0
  35. package/dist/device/gateway-tool-adapter.d.ts +17 -0
  36. package/dist/device/gateway-tool-adapter.js +284 -0
  37. package/dist/device/gateway-url-policy.d.ts +3 -0
  38. package/dist/device/gateway-url-policy.js +43 -0
  39. package/dist/device/linux-service.d.ts +70 -0
  40. package/dist/device/linux-service.js +268 -0
  41. package/dist/device/official-trust.d.ts +7 -0
  42. package/dist/device/official-trust.js +49 -0
  43. package/dist/device/project-inspection.d.ts +1 -0
  44. package/dist/device/project-inspection.js +180 -0
  45. package/dist/device/runtime-owner.d.ts +41 -0
  46. package/dist/device/runtime-owner.js +177 -0
  47. package/dist/device/windows-dpapi.d.ts +11 -0
  48. package/dist/device/windows-dpapi.js +67 -0
  49. package/dist/device/windows-service.d.ts +56 -0
  50. package/dist/device/windows-service.js +238 -0
  51. package/dist/error-handlers.d.ts +7 -0
  52. package/dist/error-handlers.js +15 -0
  53. package/dist/handlers/edit-search-handlers.d.ts +6 -0
  54. package/dist/handlers/edit-search-handlers.js +6 -0
  55. package/dist/handlers/filesystem-handlers.d.ts +38 -0
  56. package/dist/handlers/filesystem-handlers.js +440 -0
  57. package/dist/handlers/history-handlers.d.ts +12 -0
  58. package/dist/handlers/history-handlers.js +67 -0
  59. package/dist/handlers/index.d.ts +6 -0
  60. package/dist/handlers/index.js +7 -0
  61. package/dist/handlers/process-handlers.d.ts +9 -0
  62. package/dist/handlers/process-handlers.js +15 -0
  63. package/dist/handlers/search-handlers.d.ts +17 -0
  64. package/dist/handlers/search-handlers.js +224 -0
  65. package/dist/handlers/terminal-handlers.d.ts +21 -0
  66. package/dist/handlers/terminal-handlers.js +35 -0
  67. package/dist/index.d.ts +2 -0
  68. package/dist/index.js +138 -0
  69. package/dist/mcp-device.d.ts +2 -0
  70. package/dist/mcp-device.js +7 -0
  71. package/dist/npm-scripts/remote.d.ts +3 -0
  72. package/dist/npm-scripts/remote.js +314 -0
  73. package/dist/search-manager.d.ts +147 -0
  74. package/dist/search-manager.js +828 -0
  75. package/dist/server.d.ts +46 -0
  76. package/dist/server.js +1563 -0
  77. package/dist/terminal-manager.d.ts +104 -0
  78. package/dist/terminal-manager.js +635 -0
  79. package/dist/tools/config.d.ts +96 -0
  80. package/dist/tools/config.js +260 -0
  81. package/dist/tools/edit.d.ts +38 -0
  82. package/dist/tools/edit.js +435 -0
  83. package/dist/tools/feedback.d.ts +8 -0
  84. package/dist/tools/feedback.js +132 -0
  85. package/dist/tools/filesystem.d.ts +79 -0
  86. package/dist/tools/filesystem.js +947 -0
  87. package/dist/tools/fuzzySearch.d.ts +12 -0
  88. package/dist/tools/fuzzySearch.js +74 -0
  89. package/dist/tools/fuzzySearchCore.d.ts +52 -0
  90. package/dist/tools/fuzzySearchCore.js +125 -0
  91. package/dist/tools/improved-process-tools.d.ts +24 -0
  92. package/dist/tools/improved-process-tools.js +627 -0
  93. package/dist/tools/mime-types.d.ts +3 -0
  94. package/dist/tools/mime-types.js +28 -0
  95. package/dist/tools/pdf/extract-images.d.ts +34 -0
  96. package/dist/tools/pdf/extract-images.js +132 -0
  97. package/dist/tools/pdf/index.d.ts +6 -0
  98. package/dist/tools/pdf/index.js +3 -0
  99. package/dist/tools/pdf/lib/pdf2md.d.ts +36 -0
  100. package/dist/tools/pdf/lib/pdf2md.js +76 -0
  101. package/dist/tools/pdf/manipulations.d.ts +13 -0
  102. package/dist/tools/pdf/manipulations.js +99 -0
  103. package/dist/tools/pdf/markdown.d.ts +26 -0
  104. package/dist/tools/pdf/markdown.js +275 -0
  105. package/dist/tools/pdf/utils.d.ts +12 -0
  106. package/dist/tools/pdf/utils.js +34 -0
  107. package/dist/tools/process.d.ts +3 -0
  108. package/dist/tools/process.js +56 -0
  109. package/dist/tools/prompts.d.ts +28 -0
  110. package/dist/tools/prompts.js +259 -0
  111. package/dist/tools/schemas.d.ts +454 -0
  112. package/dist/tools/schemas.js +242 -0
  113. package/dist/tools/usage.d.ts +5 -0
  114. package/dist/tools/usage.js +24 -0
  115. package/dist/types.d.ts +83 -0
  116. package/dist/types.js +1 -0
  117. package/dist/ui/config-editor/config-editor-runtime.js +150 -0
  118. package/dist/ui/config-editor/index.html +13 -0
  119. package/dist/ui/config-editor/src/app.d.ts +43 -0
  120. package/dist/ui/config-editor/src/app.js +831 -0
  121. package/dist/ui/config-editor/src/array-modal.d.ts +19 -0
  122. package/dist/ui/config-editor/src/array-modal.js +185 -0
  123. package/dist/ui/config-editor/src/main.d.ts +1 -0
  124. package/dist/ui/config-editor/src/main.js +2 -0
  125. package/dist/ui/config-editor/styles.css +587 -0
  126. package/dist/ui/contracts.d.ts +14 -0
  127. package/dist/ui/contracts.js +21 -0
  128. package/dist/ui/file-preview/index.html +16 -0
  129. package/dist/ui/file-preview/preview-runtime.js +469 -0
  130. package/dist/ui/file-preview/shared/preview-file-types.d.ts +5 -0
  131. package/dist/ui/file-preview/shared/preview-file-types.js +59 -0
  132. package/dist/ui/file-preview/src/app.d.ts +4 -0
  133. package/dist/ui/file-preview/src/app.js +680 -0
  134. package/dist/ui/file-preview/src/components/code-viewer.d.ts +6 -0
  135. package/dist/ui/file-preview/src/components/code-viewer.js +73 -0
  136. package/dist/ui/file-preview/src/components/highlighting.d.ts +2 -0
  137. package/dist/ui/file-preview/src/components/highlighting.js +54 -0
  138. package/dist/ui/file-preview/src/components/html-renderer.d.ts +5 -0
  139. package/dist/ui/file-preview/src/components/html-renderer.js +47 -0
  140. package/dist/ui/file-preview/src/components/markdown-renderer.d.ts +1 -0
  141. package/dist/ui/file-preview/src/components/markdown-renderer.js +59 -0
  142. package/dist/ui/file-preview/src/directory-controller.d.ts +8 -0
  143. package/dist/ui/file-preview/src/directory-controller.js +240 -0
  144. package/dist/ui/file-preview/src/document-layout.d.ts +20 -0
  145. package/dist/ui/file-preview/src/document-layout.js +109 -0
  146. package/dist/ui/file-preview/src/document-outline.d.ts +17 -0
  147. package/dist/ui/file-preview/src/document-outline.js +97 -0
  148. package/dist/ui/file-preview/src/document-workspace.d.ts +19 -0
  149. package/dist/ui/file-preview/src/document-workspace.js +33 -0
  150. package/dist/ui/file-preview/src/file-type-handlers.d.ts +10 -0
  151. package/dist/ui/file-preview/src/file-type-handlers.js +109 -0
  152. package/dist/ui/file-preview/src/host/external-actions.d.ts +8 -0
  153. package/dist/ui/file-preview/src/host/external-actions.js +55 -0
  154. package/dist/ui/file-preview/src/host/selection-context.d.ts +9 -0
  155. package/dist/ui/file-preview/src/host/selection-context.js +106 -0
  156. package/dist/ui/file-preview/src/image-preview.d.ts +3 -0
  157. package/dist/ui/file-preview/src/image-preview.js +21 -0
  158. package/dist/ui/file-preview/src/main.d.ts +1 -0
  159. package/dist/ui/file-preview/src/main.js +5 -0
  160. package/dist/ui/file-preview/src/markdown/conflict-dialog.d.ts +40 -0
  161. package/dist/ui/file-preview/src/markdown/conflict-dialog.js +163 -0
  162. package/dist/ui/file-preview/src/markdown/controller.d.ts +44 -0
  163. package/dist/ui/file-preview/src/markdown/controller.js +1043 -0
  164. package/dist/ui/file-preview/src/markdown/editor.d.ts +131 -0
  165. package/dist/ui/file-preview/src/markdown/editor.js +1479 -0
  166. package/dist/ui/file-preview/src/markdown/linking.d.ts +16 -0
  167. package/dist/ui/file-preview/src/markdown/linking.js +228 -0
  168. package/dist/ui/file-preview/src/markdown/outline.d.ts +2 -0
  169. package/dist/ui/file-preview/src/markdown/outline.js +16 -0
  170. package/dist/ui/file-preview/src/markdown/parser.d.ts +30 -0
  171. package/dist/ui/file-preview/src/markdown/parser.js +38 -0
  172. package/dist/ui/file-preview/src/markdown/preview.d.ts +1 -0
  173. package/dist/ui/file-preview/src/markdown/preview.js +20 -0
  174. package/dist/ui/file-preview/src/markdown/slugify.d.ts +3 -0
  175. package/dist/ui/file-preview/src/markdown/slugify.js +31 -0
  176. package/dist/ui/file-preview/src/markdown/utils.d.ts +1 -0
  177. package/dist/ui/file-preview/src/markdown/utils.js +15 -0
  178. package/dist/ui/file-preview/src/model.d.ts +35 -0
  179. package/dist/ui/file-preview/src/model.js +1 -0
  180. package/dist/ui/file-preview/src/panel-actions.d.ts +17 -0
  181. package/dist/ui/file-preview/src/panel-actions.js +182 -0
  182. package/dist/ui/file-preview/src/path-utils.d.ts +6 -0
  183. package/dist/ui/file-preview/src/path-utils.js +64 -0
  184. package/dist/ui/file-preview/src/payload-utils.d.ts +11 -0
  185. package/dist/ui/file-preview/src/payload-utils.js +119 -0
  186. package/dist/ui/file-preview/src/types.d.ts +1 -0
  187. package/dist/ui/file-preview/src/types.js +1 -0
  188. package/dist/ui/file-preview/styles.css +1631 -0
  189. package/dist/ui/resources.d.ts +28 -0
  190. package/dist/ui/resources.js +86 -0
  191. package/dist/ui/shared/compact-row.d.ts +11 -0
  192. package/dist/ui/shared/compact-row.js +18 -0
  193. package/dist/ui/shared/escape-html.d.ts +4 -0
  194. package/dist/ui/shared/escape-html.js +11 -0
  195. package/dist/ui/shared/host-context.d.ts +15 -0
  196. package/dist/ui/shared/host-context.js +51 -0
  197. package/dist/ui/shared/tool-bridge.d.ts +30 -0
  198. package/dist/ui/shared/tool-bridge.js +137 -0
  199. package/dist/ui/shared/tool-shell.d.ts +25 -0
  200. package/dist/ui/shared/tool-shell.js +107 -0
  201. package/dist/ui/shared/ui-event-tracker.d.ts +9 -0
  202. package/dist/ui/shared/ui-event-tracker.js +27 -0
  203. package/dist/ui/shared/widget-state.d.ts +33 -0
  204. package/dist/ui/shared/widget-state.js +158 -0
  205. package/dist/utils/ab-test.d.ts +12 -0
  206. package/dist/utils/ab-test.js +104 -0
  207. package/dist/utils/capture.d.ts +35 -0
  208. package/dist/utils/capture.js +481 -0
  209. package/dist/utils/dockerPrompt.d.ts +17 -0
  210. package/dist/utils/dockerPrompt.js +53 -0
  211. package/dist/utils/feature-flags.d.ts +59 -0
  212. package/dist/utils/feature-flags.js +215 -0
  213. package/dist/utils/files/base.d.ts +173 -0
  214. package/dist/utils/files/base.js +5 -0
  215. package/dist/utils/files/binary.d.ts +21 -0
  216. package/dist/utils/files/binary.js +65 -0
  217. package/dist/utils/files/docx.d.ts +47 -0
  218. package/dist/utils/files/docx.js +679 -0
  219. package/dist/utils/files/excel.d.ts +24 -0
  220. package/dist/utils/files/excel.js +437 -0
  221. package/dist/utils/files/factory.d.ts +41 -0
  222. package/dist/utils/files/factory.js +113 -0
  223. package/dist/utils/files/image.d.ts +21 -0
  224. package/dist/utils/files/image.js +78 -0
  225. package/dist/utils/files/index.d.ts +10 -0
  226. package/dist/utils/files/index.js +13 -0
  227. package/dist/utils/files/pdf.d.ts +32 -0
  228. package/dist/utils/files/pdf.js +142 -0
  229. package/dist/utils/files/text.d.ts +63 -0
  230. package/dist/utils/files/text.js +370 -0
  231. package/dist/utils/fuzzySearchLogger.d.ts +30 -0
  232. package/dist/utils/fuzzySearchLogger.js +126 -0
  233. package/dist/utils/lineEndingHandler.d.ts +21 -0
  234. package/dist/utils/lineEndingHandler.js +77 -0
  235. package/dist/utils/logger.d.ts +32 -0
  236. package/dist/utils/logger.js +72 -0
  237. package/dist/utils/mcp-ui-ab-test.d.ts +13 -0
  238. package/dist/utils/mcp-ui-ab-test.js +62 -0
  239. package/dist/utils/open-browser.d.ts +9 -0
  240. package/dist/utils/open-browser.js +42 -0
  241. package/dist/utils/process-detection.d.ts +23 -0
  242. package/dist/utils/process-detection.js +150 -0
  243. package/dist/utils/ripgrep-resolver.d.ts +9 -0
  244. package/dist/utils/ripgrep-resolver.js +74 -0
  245. package/dist/utils/system-info.d.ts +71 -0
  246. package/dist/utils/system-info.js +712 -0
  247. package/dist/utils/toolHistory.d.ts +86 -0
  248. package/dist/utils/toolHistory.js +257 -0
  249. package/dist/utils/trackTools.d.ts +6 -0
  250. package/dist/utils/trackTools.js +54 -0
  251. package/dist/utils/unsupportedParams.d.ts +24 -0
  252. package/dist/utils/unsupportedParams.js +66 -0
  253. package/dist/utils/usageTracker.d.ts +133 -0
  254. package/dist/utils/usageTracker.js +477 -0
  255. package/dist/utils/welcome-onboarding.d.ts +11 -0
  256. package/dist/utils/welcome-onboarding.js +99 -0
  257. package/dist/utils/withTimeout.d.ts +28 -0
  258. package/dist/utils/withTimeout.js +85 -0
  259. package/dist/version.d.ts +1 -0
  260. package/dist/version.js +1 -0
  261. package/package.json +131 -0
@@ -0,0 +1,132 @@
1
+ import { getDocumentProxy, extractImages } from 'unpdf';
2
+ /**
3
+ * Optimized image extraction from PDF using unpdf's built-in extractImages method
4
+ * @param pdfBuffer PDF file as Uint8Array
5
+ * @param pageNumbers Optional array of specific page numbers to process
6
+ * @param compressionOptions Image compression settings
7
+ * @returns Record of page numbers to extracted images
8
+ */
9
+ export async function extractImagesFromPdf(pdfBuffer, pageNumbers, compressionOptions = {}) {
10
+ const pdfDocument = await getDocumentProxy(pdfBuffer);
11
+ const pagesToProcess = pageNumbers || Array.from({ length: pdfDocument.numPages }, (_, i) => i + 1);
12
+ const pageResults = {};
13
+ try {
14
+ // Process pages in parallel batches for better performance
15
+ const batchSize = 5; // Process 5 pages at a time
16
+ const batches = [];
17
+ for (let i = 0; i < pagesToProcess.length; i += batchSize) {
18
+ batches.push(pagesToProcess.slice(i, i + batchSize));
19
+ }
20
+ for (let batchIndex = 0; batchIndex < batches.length; batchIndex++) {
21
+ const batch = batches[batchIndex];
22
+ const batchPromises = batch.map(async (pageNum) => {
23
+ if (pageNum < 1 || pageNum > pdfDocument.numPages) {
24
+ return { pageNum, images: [] };
25
+ }
26
+ try {
27
+ // Use unpdf's built-in extractImages
28
+ const extractedImages = await extractImages(pdfDocument, pageNum);
29
+ const pageImages = [];
30
+ for (let index = 0; index < extractedImages.length; index++) {
31
+ const img = extractedImages[index];
32
+ const originalSize = img.data.length;
33
+ try {
34
+ const compressionResult = await convertRawImageToBase64(img.data, img.width, img.height, img.channels, compressionOptions);
35
+ if (compressionResult) {
36
+ pageImages.push({
37
+ objId: index, // Use index as objId since unpdf doesn't provide original objId
38
+ width: img.width,
39
+ height: img.height,
40
+ data: compressionResult.data,
41
+ mimeType: compressionResult.mimeType,
42
+ originalSize,
43
+ compressedSize: Math.round(compressionResult.data.length * 0.75) // Approximate base64 overhead
44
+ });
45
+ }
46
+ }
47
+ catch (err) {
48
+ // Ignore conversion errors as requested
49
+ console.warn(`Failed to convert image ${index} on page ${pageNum}:`, err instanceof Error ? err.message : String(err));
50
+ }
51
+ }
52
+ return { pageNum, images: pageImages };
53
+ }
54
+ catch (error) {
55
+ console.warn(`Failed to extract images from page ${pageNum}:`, error instanceof Error ? error.message : String(error));
56
+ return { pageNum, images: [] };
57
+ }
58
+ });
59
+ // Wait for the current batch to complete
60
+ const batchResults = await Promise.all(batchPromises);
61
+ // Store results
62
+ for (const { pageNum, images } of batchResults) {
63
+ pageResults[pageNum] = images;
64
+ }
65
+ }
66
+ }
67
+ finally {
68
+ // Clean up document
69
+ try {
70
+ if (typeof pdfDocument.cleanup === 'function') {
71
+ await pdfDocument.cleanup(false);
72
+ }
73
+ }
74
+ catch (e) { /* Ignore cleanup errors */ }
75
+ try {
76
+ if (typeof pdfDocument.destroy === 'function') {
77
+ await pdfDocument.destroy();
78
+ }
79
+ }
80
+ catch (e) { /* Ignore cleanup errors */ }
81
+ }
82
+ return pageResults;
83
+ }
84
+ /**
85
+ * Convert raw image data to compressed base64 using sharp
86
+ */
87
+ async function convertRawImageToBase64(data, width, height, channels, options = {}) {
88
+ const { format = 'webp', quality = 85, maxDimension = 1200 } = options;
89
+ // Smart resizing - only resize large images
90
+ let targetWidth = width;
91
+ let targetHeight = height;
92
+ if (width > maxDimension || height > maxDimension) {
93
+ const scale = maxDimension / Math.max(width, height);
94
+ targetWidth = Math.round(width * scale);
95
+ targetHeight = Math.round(height * scale);
96
+ }
97
+ try {
98
+ // Try to dynamically import sharp
99
+ const sharp = (await import('sharp')).default;
100
+ // sharp takes Buffer, Uint8Array, etc.
101
+ // unpdf returns Uint8ClampedArray, which works with Buffer.from()
102
+ let pipeline = sharp(Buffer.from(data), {
103
+ raw: {
104
+ width,
105
+ height,
106
+ channels: channels
107
+ }
108
+ });
109
+ if (targetWidth !== width || targetHeight !== height) {
110
+ pipeline = pipeline.resize(targetWidth, targetHeight);
111
+ }
112
+ let outputBuffer;
113
+ let mimeType;
114
+ if (format === 'jpeg') {
115
+ outputBuffer = await pipeline.jpeg({ quality }).toBuffer();
116
+ mimeType = 'image/jpeg';
117
+ }
118
+ else {
119
+ // Default to webp
120
+ outputBuffer = await pipeline.webp({ quality }).toBuffer();
121
+ mimeType = 'image/webp';
122
+ }
123
+ return {
124
+ data: outputBuffer.toString('base64'),
125
+ mimeType
126
+ };
127
+ }
128
+ catch (error) {
129
+ console.warn('Image conversion failed (likely missing sharp or invalid data):', error instanceof Error ? error.message : String(error));
130
+ return null;
131
+ }
132
+ }
@@ -0,0 +1,6 @@
1
+ export { editPdf } from './manipulations.js';
2
+ export type { PdfOperations, PdfInsertOperation, PdfDeleteOperation } from './manipulations.js';
3
+ export { parsePdfToMarkdown, parseMarkdownToPdf } from './markdown.js';
4
+ export type { PdfMetadata, PdfPageItem } from './lib/pdf2md.js';
5
+ export { extractImagesFromPdf } from './extract-images.js';
6
+ export type { ImageInfo, PageImages } from './extract-images.js';
@@ -0,0 +1,3 @@
1
+ export { editPdf } from './manipulations.js';
2
+ export { parsePdfToMarkdown, parseMarkdownToPdf } from './markdown.js';
3
+ export { extractImagesFromPdf } from './extract-images.js';
@@ -0,0 +1,36 @@
1
+ import { ImageInfo } from '../extract-images.js';
2
+ /**
3
+ * PDF metadata structure
4
+ */
5
+ export interface PdfMetadata {
6
+ fileSize?: number;
7
+ totalPages: number;
8
+ title?: string;
9
+ author?: string;
10
+ creator?: string;
11
+ producer?: string;
12
+ version?: string;
13
+ creationDate?: string;
14
+ modificationDate?: string;
15
+ isEncrypted?: boolean;
16
+ }
17
+ export interface PdfPageItem {
18
+ text: string;
19
+ images: ImageInfo[];
20
+ pageNumber: number;
21
+ }
22
+ export interface PdfParseResult {
23
+ pages: PdfPageItem[];
24
+ metadata: PdfMetadata;
25
+ }
26
+ export type PageRange = {
27
+ offset: number;
28
+ length: number;
29
+ };
30
+ /**
31
+ * Reads a PDF and converts it to Markdown, returning structured data.
32
+ * @param pdfBuffer The PDF buffer to convert.
33
+ * @param pageNumbers The page numbers to extract. If empty, all pages are extracted.
34
+ * @returns A Promise that resolves to a PdfParseResult object containing the parsed data.
35
+ */
36
+ export declare function pdf2md(pdfBuffer: Uint8Array, pageNumbers?: number[] | PageRange): Promise<PdfParseResult>;
@@ -0,0 +1,76 @@
1
+ import { createRequire } from 'module';
2
+ import { generatePageNumbers } from '../utils.js';
3
+ import { extractImagesFromPdf } from '../extract-images.js';
4
+ const require = createRequire(import.meta.url);
5
+ const { parse } = require('@opendocsg/pdf2md/lib/util/pdf');
6
+ const { makeTransformations, transform } = require('@opendocsg/pdf2md/lib/util/transformations');
7
+ /**
8
+ * Extracts metadata from a PDF document.
9
+ * @param pdfDocument The PDF document to extract metadata from.
10
+ * @returns A PdfMetadata object containing the extracted metadata.
11
+ */
12
+ const extractMetadata = ({ pdfDocument, metadata }) => ({
13
+ totalPages: pdfDocument.numPages,
14
+ title: metadata.Title,
15
+ author: metadata.Author,
16
+ creator: metadata.Creator,
17
+ producer: metadata.Producer,
18
+ version: metadata.PDFFormatVersion,
19
+ creationDate: metadata.CreationDate,
20
+ modificationDate: metadata.ModDate,
21
+ isEncrypted: metadata.IsEncrypted,
22
+ });
23
+ /**
24
+ * Reads a PDF and converts it to Markdown, returning structured data.
25
+ * @param pdfBuffer The PDF buffer to convert.
26
+ * @param pageNumbers The page numbers to extract. If empty, all pages are extracted.
27
+ * @returns A Promise that resolves to a PdfParseResult object containing the parsed data.
28
+ */
29
+ export async function pdf2md(pdfBuffer, pageNumbers = []) {
30
+ const result = await parse(pdfBuffer);
31
+ const { fonts, pages, pdfDocument } = result;
32
+ // Calculate which pages to process
33
+ const filterPageNumbers = Array.isArray(pageNumbers) ?
34
+ pageNumbers :
35
+ generatePageNumbers(pageNumbers.offset, pageNumbers.length, pages.length);
36
+ // Filter and transform pages
37
+ const pagesToProcess = filterPageNumbers.length === 0 ?
38
+ pages :
39
+ pages.filter((_, index) => filterPageNumbers.includes(index + 1));
40
+ const pageNumberMap = filterPageNumbers.length === 0 ?
41
+ pages.map((_, index) => index + 1) :
42
+ filterPageNumbers.filter(pageNum => pageNum >= 1 && pageNum <= pages.length);
43
+ const transformations = makeTransformations(fonts.map);
44
+ const parseResult = transform(pagesToProcess, transformations);
45
+ // Extract images
46
+ const imagesByPage = await extractImagesFromPdf(pdfBuffer, pageNumberMap, { format: 'webp', quality: 85 });
47
+ // Create pages without images for now
48
+ const processedPages = parseResult.pages.map((page, index) => {
49
+ const pageNumber = pageNumberMap[index];
50
+ return {
51
+ pageNumber,
52
+ text: page.items.join('\n') + '\n',
53
+ images: imagesByPage[pageNumber] || [],
54
+ };
55
+ });
56
+ const metadata = extractMetadata(result);
57
+ try {
58
+ return { pages: processedPages, metadata };
59
+ }
60
+ finally {
61
+ if (pdfDocument) {
62
+ try {
63
+ if (typeof pdfDocument.cleanup === 'function') {
64
+ await pdfDocument.cleanup(false);
65
+ }
66
+ }
67
+ catch (e) { }
68
+ try {
69
+ if (typeof pdfDocument.destroy === 'function') {
70
+ await pdfDocument.destroy();
71
+ }
72
+ }
73
+ catch (e) { }
74
+ }
75
+ }
76
+ }
@@ -0,0 +1,13 @@
1
+ import type { PdfInsertOperationSchema, PdfDeleteOperationSchema, PdfOperationSchema } from '../schemas.js';
2
+ import { z } from 'zod';
3
+ type PdfInsertOperation = z.infer<typeof PdfInsertOperationSchema>;
4
+ type PdfDeleteOperation = z.infer<typeof PdfDeleteOperationSchema>;
5
+ type PdfOperations = z.infer<typeof PdfOperationSchema>;
6
+ export type { PdfOperations, PdfInsertOperation, PdfDeleteOperation };
7
+ /**
8
+ * Edit an existing PDF by deleting or inserting pages
9
+ * @param pdfPath Path to the PDF file to edit
10
+ * @param operations List of operations to perform
11
+ * @returns The modified PDF as a Uint8Array
12
+ */
13
+ export declare function editPdf(pdfPath: string, operations: PdfOperations[]): Promise<Uint8Array>;
@@ -0,0 +1,99 @@
1
+ import fs from 'fs/promises';
2
+ import { createRequire } from 'module';
3
+ import { normalizePageIndexes } from './utils.js';
4
+ import { parseMarkdownToPdf } from './markdown.js';
5
+ // Use createRequire to load pdf-lib as CJS (works around Node 25 ESM resolution issues)
6
+ const require = createRequire(import.meta.url);
7
+ const { PDFDocument } = require('pdf-lib');
8
+ async function loadPdfDocumentFromBuffer(filePathOrBuffer) {
9
+ const buffer = typeof filePathOrBuffer === 'string' ? await fs.readFile(filePathOrBuffer) : filePathOrBuffer;
10
+ const pdfBytes = new Uint8Array(buffer);
11
+ return await PDFDocument.load(pdfBytes);
12
+ }
13
+ /**
14
+ * Delete pages from a PDF document
15
+ * @param pdfDoc PDF document to delete pages from
16
+ * @param pageIndexes Page indices to delete, negative indices are from end
17
+ */
18
+ function deletePages(pdfDoc, pageIndexes) {
19
+ const pageCount = pdfDoc.getPageCount();
20
+ // Transform negative indices to absolute and filter valid ones
21
+ const normalizedIndexes = normalizePageIndexes(pageIndexes, pageCount).sort((a, b) => b - a);
22
+ for (const pageIndex of normalizedIndexes) {
23
+ pdfDoc.removePage(pageIndex);
24
+ }
25
+ return pdfDoc;
26
+ }
27
+ function getPageLayout(page) {
28
+ const { width, height } = page.getSize();
29
+ const mediaBox = page.getMediaBox(); // Full page area
30
+ const cropBox = page.getCropBox(); // Visible area (may indicate margins)
31
+ // Calculate margins (if CropBox differs from MediaBox)
32
+ let marginLeft = cropBox.x - mediaBox.x;
33
+ let marginBottom = cropBox.y - mediaBox.y;
34
+ let marginRight = (mediaBox.x + mediaBox.width) - (cropBox.x + cropBox.width);
35
+ let marginTop = (mediaBox.y + mediaBox.height) - (cropBox.y + cropBox.height);
36
+ if (marginLeft === 0 && marginRight === 0 && marginTop === 0 && marginBottom === 0) {
37
+ marginLeft = 72;
38
+ marginBottom = 72;
39
+ marginRight = 72;
40
+ marginTop = 72;
41
+ }
42
+ // Convert points to inches (1 inch = 72 points)
43
+ // Puppeteer requires standard units and doesn't accept decimal points
44
+ const pointsToInches = (pts) => (pts / 72).toFixed(4);
45
+ return {
46
+ format: undefined, // Explicitly disable format to use custom dimensions
47
+ width: `${pointsToInches(width)}in`,
48
+ height: `${pointsToInches(height)}in`,
49
+ margin: {
50
+ top: `${pointsToInches(marginTop)}in`,
51
+ right: `${pointsToInches(marginRight)}in`,
52
+ bottom: `${pointsToInches(marginBottom)}in`,
53
+ left: `${pointsToInches(marginLeft)}in`
54
+ }
55
+ };
56
+ }
57
+ async function insertPages(destPdfDocument, pageIndex, sourcePdfDocument) {
58
+ let insertPosition = pageIndex < 0 ? destPdfDocument.getPageCount() + pageIndex : pageIndex;
59
+ if (insertPosition < 0 || insertPosition > destPdfDocument.getPageCount()) {
60
+ throw new Error('Invalid page index');
61
+ }
62
+ const copiedPages = await destPdfDocument.copyPages(sourcePdfDocument, sourcePdfDocument.getPageIndices());
63
+ for (let i = 0; i < copiedPages.length; i++) {
64
+ destPdfDocument.insertPage(insertPosition + i, copiedPages[i]);
65
+ }
66
+ return destPdfDocument;
67
+ }
68
+ /**
69
+ * Edit an existing PDF by deleting or inserting pages
70
+ * @param pdfPath Path to the PDF file to edit
71
+ * @param operations List of operations to perform
72
+ * @returns The modified PDF as a Uint8Array
73
+ */
74
+ export async function editPdf(pdfPath, operations) {
75
+ const pdfDoc = await loadPdfDocumentFromBuffer(pdfPath);
76
+ // Get page layout from the ORIGINAL first page
77
+ const pageLayout = pdfDoc.getPageCount() > 0 ? getPageLayout(pdfDoc.getPage(0)) : undefined;
78
+ for (const op of operations) {
79
+ if (op.type === 'delete') {
80
+ deletePages(pdfDoc, op.pageIndexes);
81
+ }
82
+ else if (op.type == 'insert') {
83
+ let sourcePdfDocument;
84
+ if (op.markdown !== undefined) {
85
+ const pdfOptions = pageLayout ? { pdf_options: pageLayout } : undefined;
86
+ const pdfBuffer = await parseMarkdownToPdf(op.markdown, pdfOptions);
87
+ sourcePdfDocument = await loadPdfDocumentFromBuffer(pdfBuffer);
88
+ }
89
+ else if (op.sourcePdfPath) {
90
+ sourcePdfDocument = await loadPdfDocumentFromBuffer(op.sourcePdfPath);
91
+ }
92
+ else {
93
+ throw new Error('No source provided for insert operation');
94
+ }
95
+ await insertPages(pdfDoc, op.pageIndex, sourcePdfDocument);
96
+ }
97
+ }
98
+ return await pdfDoc.save();
99
+ }
@@ -0,0 +1,26 @@
1
+ import type { PageRange } from './lib/pdf2md.js';
2
+ import { PdfParseResult } from './lib/pdf2md.js';
3
+ interface CachedPuppeteerChrome {
4
+ executablePath: string;
5
+ }
6
+ /**
7
+ * Find Chrome in puppeteer's cache directory
8
+ * Returns the executable path if found, undefined otherwise
9
+ */
10
+ export declare function findPuppeteerChrome(cacheDir?: string): CachedPuppeteerChrome | undefined;
11
+ /**
12
+ * Remove stale Puppeteer Chrome builds while preserving the active build.
13
+ */
14
+ export declare function pruneOldPuppeteerChromeBuilds(activeExecutablePath: string, cacheDir?: string): Promise<void>;
15
+ /**
16
+ * Preemptively ensure Chrome is available for PDF generation.
17
+ * Call this at server startup to trigger download in background if needed.
18
+ * Returns immediately, download happens in background.
19
+ */
20
+ export declare function ensureChromeAvailable(): void;
21
+ /**
22
+ * Convert PDF to Markdown using @opendocsg/pdf2md
23
+ */
24
+ export declare function parsePdfToMarkdown(source: string, pageNumbers?: number[] | PageRange): Promise<PdfParseResult>;
25
+ export declare function parseMarkdownToPdf(markdown: string, options?: any): Promise<Buffer>;
26
+ export {};
@@ -0,0 +1,275 @@
1
+ import fs from 'fs/promises';
2
+ import { existsSync, readdirSync } from 'fs';
3
+ import { dirname, isAbsolute, join, relative, resolve, sep } from 'path';
4
+ import { mdToPdf } from 'md-to-pdf';
5
+ import { pdf2md } from './lib/pdf2md.js';
6
+ import { CONFIG_FILE } from '../../config.js';
7
+ const isUrl = (source) => source.startsWith('http://') || source.startsWith('https://');
8
+ // Cached Chrome path to avoid repeated lookups
9
+ let cachedChromePath = null; // null = not checked yet
10
+ let chromeCheckPromise = null;
11
+ /**
12
+ * Get Desktop Commander's private Puppeteer cache directory.
13
+ */
14
+ function getPuppeteerCacheDir() {
15
+ return join(dirname(CONFIG_FILE), 'puppeteer-cache');
16
+ }
17
+ /**
18
+ * Get the cache path where Puppeteer stores Chrome for Testing builds.
19
+ */
20
+ function getPuppeteerChromeDir(cacheDir = getPuppeteerCacheDir()) {
21
+ return join(cacheDir, 'chrome');
22
+ }
23
+ /**
24
+ * Find the platform-specific executable within a cached Chrome build directory.
25
+ */
26
+ function getChromeExecutablePath(chromeDir, version) {
27
+ const chromePath = process.platform === 'win32'
28
+ ? join(chromeDir, version, 'chrome-win64', 'chrome.exe')
29
+ : process.platform === 'darwin'
30
+ ? join(chromeDir, version, 'chrome-mac-x64', 'Google Chrome for Testing.app', 'Contents', 'MacOS', 'Google Chrome for Testing')
31
+ : join(chromeDir, version, 'chrome-linux64', 'chrome');
32
+ if (existsSync(chromePath)) {
33
+ return chromePath;
34
+ }
35
+ // Also check for arm64 mac
36
+ if (process.platform === 'darwin') {
37
+ const armPath = join(chromeDir, version, 'chrome-mac-arm64', 'Google Chrome for Testing.app', 'Contents', 'MacOS', 'Google Chrome for Testing');
38
+ if (existsSync(armPath)) {
39
+ return armPath;
40
+ }
41
+ }
42
+ return undefined;
43
+ }
44
+ /**
45
+ * Resolve the cached Chrome build directory that owns an executable path.
46
+ */
47
+ function getCachedChromeBuildDir(chromeDir, executablePath) {
48
+ const relativePath = relative(chromeDir, executablePath);
49
+ if (relativePath === '..' || relativePath.startsWith(`..${sep}`) || isAbsolute(relativePath)) {
50
+ return undefined;
51
+ }
52
+ const [buildDir] = relativePath.split(sep);
53
+ return buildDir ? join(chromeDir, buildDir) : undefined;
54
+ }
55
+ /**
56
+ * Find Chrome in puppeteer's cache directory
57
+ * Returns the executable path if found, undefined otherwise
58
+ */
59
+ export function findPuppeteerChrome(cacheDir = getPuppeteerCacheDir()) {
60
+ const chromeDir = getPuppeteerChromeDir(cacheDir);
61
+ if (!existsSync(chromeDir)) {
62
+ return undefined;
63
+ }
64
+ try {
65
+ // Look for chrome directories (e.g., win64-143.0.7499.169)
66
+ const versions = readdirSync(chromeDir, { withFileTypes: true })
67
+ .filter(entry => entry.isDirectory())
68
+ .map(entry => entry.name)
69
+ .sort((a, b) => b.localeCompare(a, undefined, { numeric: true }));
70
+ for (const version of versions) {
71
+ const executablePath = getChromeExecutablePath(chromeDir, version);
72
+ if (executablePath) {
73
+ return { executablePath };
74
+ }
75
+ }
76
+ }
77
+ catch {
78
+ // Ignore errors reading cache directory
79
+ }
80
+ return undefined;
81
+ }
82
+ /**
83
+ * Remove stale Puppeteer Chrome builds while preserving the active build.
84
+ */
85
+ export async function pruneOldPuppeteerChromeBuilds(activeExecutablePath, cacheDir = getPuppeteerCacheDir()) {
86
+ const chromeDir = getPuppeteerChromeDir(cacheDir);
87
+ const activeBuildDir = getCachedChromeBuildDir(chromeDir, activeExecutablePath);
88
+ if (!activeBuildDir) {
89
+ return;
90
+ }
91
+ let entries;
92
+ try {
93
+ entries = await fs.readdir(chromeDir, { withFileTypes: true });
94
+ }
95
+ catch {
96
+ return;
97
+ }
98
+ await Promise.all(entries
99
+ .filter(entry => entry.isDirectory())
100
+ .map(async (entry) => {
101
+ const buildDir = join(chromeDir, entry.name);
102
+ if (resolve(buildDir) === resolve(activeBuildDir)) {
103
+ return;
104
+ }
105
+ try {
106
+ await fs.rm(buildDir, { recursive: true, force: true });
107
+ }
108
+ catch (error) {
109
+ console.error(`Failed to remove old Chrome cache build at ${buildDir}:`, error);
110
+ }
111
+ }));
112
+ }
113
+ /**
114
+ * Find system-installed Chrome/Chromium browser
115
+ * Returns the executable path if found, undefined otherwise
116
+ */
117
+ function findSystemChrome() {
118
+ const paths = process.platform === 'win32'
119
+ ? [
120
+ 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
121
+ 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
122
+ `${process.env.LOCALAPPDATA}\\Google\\Chrome\\Application\\chrome.exe`,
123
+ 'C:\\Program Files\\Chromium\\Application\\chrome.exe',
124
+ 'C:\\Program Files (x86)\\Chromium\\Application\\chrome.exe',
125
+ ]
126
+ : process.platform === 'darwin'
127
+ ? [
128
+ '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
129
+ '/Applications/Chromium.app/Contents/MacOS/Chromium',
130
+ '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary',
131
+ ]
132
+ : [
133
+ // Linux paths
134
+ '/usr/bin/google-chrome',
135
+ '/usr/bin/google-chrome-stable',
136
+ '/usr/bin/chromium',
137
+ '/usr/bin/chromium-browser',
138
+ '/snap/bin/chromium',
139
+ ];
140
+ return paths.find(p => existsSync(p));
141
+ }
142
+ /**
143
+ * Download and install Chrome using @puppeteer/browsers
144
+ * Returns the executable path after installation
145
+ */
146
+ async function installChrome() {
147
+ // Dynamic import to avoid loading if not needed
148
+ const { install, Browser, detectBrowserPlatform, resolveBuildId } = await import('@puppeteer/browsers');
149
+ const cacheDir = getPuppeteerCacheDir();
150
+ const platform = detectBrowserPlatform();
151
+ const buildId = await resolveBuildId(Browser.CHROME, platform, 'stable');
152
+ console.error('Downloading Chrome for PDF generation (this may take a few minutes)...');
153
+ await fs.mkdir(cacheDir, { recursive: true });
154
+ const installedBrowser = await install({
155
+ browser: Browser.CHROME,
156
+ buildId,
157
+ cacheDir,
158
+ downloadProgressCallback: (downloadedBytes, totalBytes) => {
159
+ const percent = Math.round((downloadedBytes / totalBytes) * 100);
160
+ process.stderr.write(`\rDownloading Chrome: ${percent}%`);
161
+ },
162
+ });
163
+ console.error('\nChrome download complete.');
164
+ return {
165
+ executablePath: installedBrowser.executablePath,
166
+ };
167
+ }
168
+ /**
169
+ * Find or install Chrome for PDF generation
170
+ * Priority: 1. Puppeteer cache, 2. System Chrome, 3. Install Chrome
171
+ * Results are cached to avoid repeated lookups
172
+ */
173
+ async function getChromePath() {
174
+ // Return cached result if available
175
+ if (cachedChromePath !== null) {
176
+ return cachedChromePath;
177
+ }
178
+ // If a check is already in progress, wait for it
179
+ if (chromeCheckPromise) {
180
+ return chromeCheckPromise;
181
+ }
182
+ // Start the check
183
+ chromeCheckPromise = (async () => {
184
+ // 1. Check puppeteer cache first (exact compatible version)
185
+ const cachedChrome = findPuppeteerChrome();
186
+ if (cachedChrome) {
187
+ await pruneOldPuppeteerChromeBuilds(cachedChrome.executablePath);
188
+ cachedChromePath = cachedChrome.executablePath;
189
+ return cachedChrome.executablePath;
190
+ }
191
+ // 2. Check system Chrome
192
+ const systemChrome = findSystemChrome();
193
+ if (systemChrome) {
194
+ cachedChromePath = systemChrome;
195
+ return systemChrome;
196
+ }
197
+ // 3. Install Chrome as last resort
198
+ try {
199
+ const installedChrome = await installChrome();
200
+ await pruneOldPuppeteerChromeBuilds(installedChrome.executablePath);
201
+ cachedChromePath = installedChrome.executablePath;
202
+ return installedChrome.executablePath;
203
+ }
204
+ catch (error) {
205
+ console.error('Failed to install Chrome:', error);
206
+ cachedChromePath = undefined;
207
+ return undefined;
208
+ }
209
+ })();
210
+ const result = await chromeCheckPromise;
211
+ chromeCheckPromise = null;
212
+ return result;
213
+ }
214
+ /**
215
+ * Preemptively ensure Chrome is available for PDF generation.
216
+ * Call this at server startup to trigger download in background if needed.
217
+ * Returns immediately, download happens in background.
218
+ */
219
+ export function ensureChromeAvailable() {
220
+ // Don't await - let it run in background
221
+ getChromePath().catch((error) => {
222
+ console.error('Background Chrome check failed:', error);
223
+ });
224
+ }
225
+ async function loadPdfToBuffer(source) {
226
+ if (isUrl(source)) {
227
+ const response = await fetch(source);
228
+ return await response.arrayBuffer();
229
+ }
230
+ else {
231
+ return await fs.readFile(source);
232
+ }
233
+ }
234
+ /**
235
+ * Convert PDF to Markdown using @opendocsg/pdf2md
236
+ */
237
+ export async function parsePdfToMarkdown(source, pageNumbers = []) {
238
+ try {
239
+ const data = await loadPdfToBuffer(source);
240
+ // @ts-ignore: Type definition mismatch for ESM usage
241
+ return await pdf2md(new Uint8Array(data), pageNumbers);
242
+ }
243
+ catch (error) {
244
+ console.error("Error converting PDF to Markdown (v3):", error);
245
+ throw error;
246
+ }
247
+ }
248
+ export async function parseMarkdownToPdf(markdown, options = {}) {
249
+ try {
250
+ // Find Chrome: puppeteer cache -> system Chrome -> install
251
+ const chromePath = await getChromePath();
252
+ if (chromePath) {
253
+ options = {
254
+ ...options,
255
+ launch_options: {
256
+ ...options.launch_options,
257
+ executablePath: chromePath,
258
+ }
259
+ };
260
+ }
261
+ const pdf = await mdToPdf({ content: markdown }, options);
262
+ return pdf.content;
263
+ }
264
+ catch (error) {
265
+ // Provide helpful error message if Chrome is not found
266
+ const errorMessage = error instanceof Error ? error.message : String(error);
267
+ if (errorMessage.includes('Could not find Chrome')) {
268
+ throw new Error('PDF generation requires Chrome or Chromium browser. ' +
269
+ 'Please install Google Chrome from https://www.google.com/chrome/ ' +
270
+ 'or Chromium, then try again.');
271
+ }
272
+ console.error('Error creating PDF:', error);
273
+ throw error;
274
+ }
275
+ }