@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,24 @@
1
+ /**
2
+ * Excel file handler using ExcelJS
3
+ * Handles reading, writing, and editing Excel files (.xlsx, .xls, .xlsm)
4
+ */
5
+ import { FileHandler, ReadOptions, FileResult, EditResult, FileInfo } from './base.js';
6
+ /**
7
+ * Excel file handler implementation using ExcelJS
8
+ * Supports: .xlsx, .xls, .xlsm files
9
+ */
10
+ export declare class ExcelFileHandler implements FileHandler {
11
+ canHandle(path: string): boolean;
12
+ read(path: string, options?: ReadOptions): Promise<FileResult>;
13
+ write(path: string, content: any, mode?: 'rewrite' | 'append'): Promise<void>;
14
+ editRange(path: string, range: string, content: any, options?: Record<string, any>): Promise<EditResult>;
15
+ getInfo(path: string): Promise<FileInfo>;
16
+ private checkFileSize;
17
+ private extractMetadata;
18
+ private worksheetToArray;
19
+ private writeDataToSheet;
20
+ private writeRowsStartingAt;
21
+ private parseRange;
22
+ private parseCellRange;
23
+ private columnToNumber;
24
+ }
@@ -0,0 +1,437 @@
1
+ /**
2
+ * Excel file handler using ExcelJS
3
+ * Handles reading, writing, and editing Excel files (.xlsx, .xls, .xlsm)
4
+ */
5
+ import ExcelJS from 'exceljs';
6
+ import fs from 'fs/promises';
7
+ // File size limit: 10MB
8
+ const FILE_SIZE_LIMIT = 10 * 1024 * 1024;
9
+ /**
10
+ * Excel file handler implementation using ExcelJS
11
+ * Supports: .xlsx, .xls, .xlsm files
12
+ */
13
+ export class ExcelFileHandler {
14
+ canHandle(path) {
15
+ const ext = path.toLowerCase();
16
+ return ext.endsWith('.xlsx') || ext.endsWith('.xls') || ext.endsWith('.xlsm');
17
+ }
18
+ async read(path, options) {
19
+ await this.checkFileSize(path);
20
+ const workbook = new ExcelJS.Workbook();
21
+ await workbook.xlsx.readFile(path);
22
+ const metadata = await this.extractMetadata(workbook, path);
23
+ const { sheetName, data, totalRows, returnedRows } = this.worksheetToArray(workbook, options?.sheet, options?.range, options?.offset, options?.length);
24
+ // Format output with sheet info header, usage hint, and JSON data
25
+ const paginationInfo = totalRows > returnedRows
26
+ ? `\n[Showing rows ${(options?.offset || 0) + 1}-${(options?.offset || 0) + returnedRows} of ${totalRows} total. Use offset/length to paginate.]`
27
+ : '';
28
+ const sheetHasSpace = /\s/.test(sheetName);
29
+ const exampleSheet = sheetHasSpace ? sheetName : 'Sheet1';
30
+ const content = `[Sheet: '${sheetName}' from ${path}]${paginationInfo}
31
+ [To MODIFY cells: use edit_block with range param, e.g., edit_block(path, {range: "${exampleSheet}!E5", content: [[newValue]]}). read_file accepts the same range form, or pass sheet + range separately.]
32
+
33
+ ${JSON.stringify(data)}`;
34
+ return {
35
+ content,
36
+ mimeType: 'application/json',
37
+ metadata: {
38
+ isExcelFile: true,
39
+ sheets: metadata.sheets,
40
+ fileSize: metadata.fileSize,
41
+ isLargeFile: metadata.isLargeFile
42
+ }
43
+ };
44
+ }
45
+ async write(path, content, mode) {
46
+ // Check existing file size if it exists
47
+ try {
48
+ await this.checkFileSize(path);
49
+ }
50
+ catch (error) {
51
+ // File doesn't exist - that's fine for write
52
+ if (error.code !== 'ENOENT' &&
53
+ !(error instanceof Error && error.message.includes('ENOENT'))) {
54
+ throw error;
55
+ }
56
+ }
57
+ // Parse content
58
+ let parsedContent = content;
59
+ if (typeof content === 'string') {
60
+ try {
61
+ parsedContent = JSON.parse(content);
62
+ }
63
+ catch {
64
+ throw new Error('Invalid content format. Expected JSON string with 2D array or object with sheet names.');
65
+ }
66
+ }
67
+ // Handle append mode by finding last row and writing after it
68
+ if (mode === 'append') {
69
+ try {
70
+ const workbook = new ExcelJS.Workbook();
71
+ await workbook.xlsx.readFile(path);
72
+ if (Array.isArray(parsedContent)) {
73
+ // Append to Sheet1
74
+ let worksheet = workbook.getWorksheet('Sheet1');
75
+ if (!worksheet) {
76
+ worksheet = workbook.addWorksheet('Sheet1');
77
+ }
78
+ const startRow = (worksheet.actualRowCount || 0) + 1;
79
+ this.writeRowsStartingAt(worksheet, startRow, parsedContent);
80
+ }
81
+ else if (typeof parsedContent === 'object' && parsedContent !== null) {
82
+ // Append to each named sheet
83
+ for (const [sheetName, data] of Object.entries(parsedContent)) {
84
+ if (Array.isArray(data)) {
85
+ let worksheet = workbook.getWorksheet(sheetName);
86
+ if (!worksheet) {
87
+ worksheet = workbook.addWorksheet(sheetName);
88
+ }
89
+ const startRow = (worksheet.actualRowCount || 0) + 1;
90
+ this.writeRowsStartingAt(worksheet, startRow, data);
91
+ }
92
+ }
93
+ }
94
+ await workbook.xlsx.writeFile(path);
95
+ return;
96
+ }
97
+ catch (error) {
98
+ // File doesn't exist - fall through to create new file
99
+ if (error.code !== 'ENOENT' &&
100
+ !(error instanceof Error && error.message.includes('ENOENT'))) {
101
+ throw error;
102
+ }
103
+ }
104
+ }
105
+ // Rewrite mode (or append to non-existent file): create new workbook
106
+ const workbook = new ExcelJS.Workbook();
107
+ if (Array.isArray(parsedContent)) {
108
+ // Single sheet from 2D array
109
+ this.writeDataToSheet(workbook, 'Sheet1', parsedContent);
110
+ }
111
+ else if (typeof parsedContent === 'object' && parsedContent !== null) {
112
+ // Object with sheet names as keys
113
+ for (const [sheetName, data] of Object.entries(parsedContent)) {
114
+ if (Array.isArray(data)) {
115
+ this.writeDataToSheet(workbook, sheetName, data);
116
+ }
117
+ }
118
+ }
119
+ else {
120
+ throw new Error('Invalid content format. Expected 2D array or object with sheet names.');
121
+ }
122
+ await workbook.xlsx.writeFile(path);
123
+ }
124
+ async editRange(path, range, content, options) {
125
+ // Verify file exists and check size
126
+ try {
127
+ await this.checkFileSize(path);
128
+ }
129
+ catch (error) {
130
+ if (error.code === 'ENOENT' ||
131
+ (error instanceof Error && error.message.includes('ENOENT'))) {
132
+ throw new Error(`File not found: ${path}`);
133
+ }
134
+ throw error;
135
+ }
136
+ // Validate content
137
+ if (!Array.isArray(content)) {
138
+ throw new Error('Content must be a 2D array for range editing');
139
+ }
140
+ // Parse range: "Sheet1!A1:C10" or "Sheet1"
141
+ const [sheetName, cellRange] = this.parseRange(range);
142
+ const workbook = new ExcelJS.Workbook();
143
+ await workbook.xlsx.readFile(path);
144
+ // Get or create sheet
145
+ let worksheet = workbook.getWorksheet(sheetName);
146
+ if (!worksheet) {
147
+ worksheet = workbook.addWorksheet(sheetName);
148
+ }
149
+ if (cellRange) {
150
+ // Write to specific range
151
+ const { startRow, startCol } = this.parseCellRange(cellRange);
152
+ for (let r = 0; r < content.length; r++) {
153
+ const rowData = content[r];
154
+ if (!Array.isArray(rowData))
155
+ continue;
156
+ for (let c = 0; c < rowData.length; c++) {
157
+ const cell = worksheet.getCell(startRow + r, startCol + c);
158
+ const value = rowData[c];
159
+ if (typeof value === 'string' && value.startsWith('=')) {
160
+ cell.value = { formula: value.substring(1) };
161
+ }
162
+ else {
163
+ cell.value = value;
164
+ }
165
+ }
166
+ }
167
+ }
168
+ else {
169
+ // Replace entire sheet content
170
+ // Clear existing data
171
+ worksheet.eachRow((row, rowNumber) => {
172
+ row.eachCell((cell) => {
173
+ cell.value = null;
174
+ });
175
+ });
176
+ // Write new data
177
+ for (let r = 0; r < content.length; r++) {
178
+ const rowData = content[r];
179
+ if (!Array.isArray(rowData))
180
+ continue;
181
+ const row = worksheet.getRow(r + 1);
182
+ for (let c = 0; c < rowData.length; c++) {
183
+ const value = rowData[c];
184
+ if (typeof value === 'string' && value.startsWith('=')) {
185
+ row.getCell(c + 1).value = { formula: value.substring(1) };
186
+ }
187
+ else {
188
+ row.getCell(c + 1).value = value;
189
+ }
190
+ }
191
+ row.commit();
192
+ }
193
+ }
194
+ await workbook.xlsx.writeFile(path);
195
+ return { success: true, editsApplied: 1 };
196
+ }
197
+ async getInfo(path) {
198
+ const stats = await fs.stat(path);
199
+ try {
200
+ const workbook = new ExcelJS.Workbook();
201
+ await workbook.xlsx.readFile(path);
202
+ const metadata = await this.extractMetadata(workbook, path);
203
+ return {
204
+ size: stats.size,
205
+ created: stats.birthtime,
206
+ modified: stats.mtime,
207
+ accessed: stats.atime,
208
+ isDirectory: stats.isDirectory(),
209
+ isFile: stats.isFile(),
210
+ permissions: stats.mode.toString(8).slice(-3),
211
+ fileType: 'excel',
212
+ metadata: {
213
+ isExcelFile: true,
214
+ sheets: metadata.sheets,
215
+ fileSize: metadata.fileSize,
216
+ isLargeFile: metadata.isLargeFile
217
+ }
218
+ };
219
+ }
220
+ catch (error) {
221
+ return {
222
+ size: stats.size,
223
+ created: stats.birthtime,
224
+ modified: stats.mtime,
225
+ accessed: stats.atime,
226
+ isDirectory: stats.isDirectory(),
227
+ isFile: stats.isFile(),
228
+ permissions: stats.mode.toString(8).slice(-3),
229
+ fileType: 'excel',
230
+ metadata: {
231
+ isExcelFile: true,
232
+ fileSize: stats.size,
233
+ error: true,
234
+ errorMessage: error instanceof Error ? error.message : String(error)
235
+ }
236
+ };
237
+ }
238
+ }
239
+ // ========== Private Helpers ==========
240
+ async checkFileSize(path) {
241
+ const stats = await fs.stat(path);
242
+ if (stats.size > FILE_SIZE_LIMIT) {
243
+ const sizeMB = (stats.size / 1024 / 1024).toFixed(1);
244
+ throw new Error(`Excel file size (${sizeMB}MB) exceeds 10MB limit. ` +
245
+ `Consider using specialized tools for large file processing.`);
246
+ }
247
+ }
248
+ async extractMetadata(workbook, path) {
249
+ const stats = await fs.stat(path);
250
+ const sheets = workbook.worksheets.map(ws => ({
251
+ name: ws.name,
252
+ rowCount: ws.actualRowCount || 0,
253
+ colCount: ws.actualColumnCount || 0
254
+ }));
255
+ return {
256
+ sheets,
257
+ fileSize: stats.size,
258
+ isLargeFile: stats.size > FILE_SIZE_LIMIT
259
+ };
260
+ }
261
+ worksheetToArray(workbook, sheetRef, range, offset, length) {
262
+ if (workbook.worksheets.length === 0) {
263
+ return { sheetName: '', data: [], totalRows: 0, returnedRows: 0 };
264
+ }
265
+ // Accept range with embedded sheet prefix (parity with edit_block).
266
+ // E.g. range:"Sheet1!A1:B2" or "'My Sheet'!A1" — strip the sheet
267
+ // prefix and, when the caller did not pass an explicit sheet, use it.
268
+ let cellRangeOnly = range;
269
+ if (range && range.includes('!')) {
270
+ const [sheetFromRange, cellsFromRange] = this.parseRange(range);
271
+ cellRangeOnly = cellsFromRange ?? undefined;
272
+ if (sheetRef === undefined && sheetFromRange) {
273
+ sheetRef = sheetFromRange;
274
+ }
275
+ }
276
+ // Find target worksheet
277
+ let worksheet;
278
+ let sheetName;
279
+ if (sheetRef === undefined) {
280
+ worksheet = workbook.worksheets[0];
281
+ sheetName = worksheet.name;
282
+ }
283
+ else if (typeof sheetRef === 'number') {
284
+ if (sheetRef < 0 || sheetRef >= workbook.worksheets.length) {
285
+ throw new Error(`Sheet index ${sheetRef} out of range (0-${workbook.worksheets.length - 1})`);
286
+ }
287
+ worksheet = workbook.worksheets[sheetRef];
288
+ sheetName = worksheet.name;
289
+ }
290
+ else {
291
+ worksheet = workbook.getWorksheet(sheetRef);
292
+ if (!worksheet) {
293
+ const available = workbook.worksheets.map(ws => ws.name).join(', ');
294
+ throw new Error(`Sheet "${sheetRef}" not found. Available sheets: ${available}`);
295
+ }
296
+ sheetName = sheetRef;
297
+ }
298
+ // Determine range to read
299
+ let startRow = 1;
300
+ let endRow = worksheet.actualRowCount || 1;
301
+ let startCol = 1;
302
+ let endCol = worksheet.actualColumnCount || 1;
303
+ if (cellRangeOnly) {
304
+ const parsed = this.parseCellRange(cellRangeOnly);
305
+ startRow = parsed.startRow;
306
+ startCol = parsed.startCol;
307
+ if (parsed.endRow)
308
+ endRow = parsed.endRow;
309
+ if (parsed.endCol)
310
+ endCol = parsed.endCol;
311
+ }
312
+ // Calculate total rows before pagination
313
+ const totalRows = endRow - startRow + 1;
314
+ // Apply offset/length pagination (row-based, matching text file behavior)
315
+ if (offset !== undefined) {
316
+ if (offset < 0) {
317
+ // Negative offset: last N rows (like text files)
318
+ // offset: -10 means "last 10 rows"
319
+ const lastNRows = Math.abs(offset);
320
+ startRow = Math.max(startRow, endRow - lastNRows + 1);
321
+ }
322
+ else if (offset > 0) {
323
+ // Positive offset: skip first N rows
324
+ startRow = startRow + offset;
325
+ }
326
+ }
327
+ // Apply length limit (only for positive offset or no offset)
328
+ if (length !== undefined && length > 0 && (offset === undefined || offset >= 0)) {
329
+ endRow = Math.min(endRow, startRow + length - 1);
330
+ }
331
+ // Ensure valid range
332
+ if (startRow > endRow) {
333
+ return { sheetName, data: [], totalRows, returnedRows: 0 };
334
+ }
335
+ // Build 2D array (preserving types)
336
+ const data = [];
337
+ for (let r = startRow; r <= endRow; r++) {
338
+ const row = worksheet.getRow(r);
339
+ const rowData = [];
340
+ for (let c = startCol; c <= endCol; c++) {
341
+ const cell = row.getCell(c);
342
+ let value = null;
343
+ if (cell.value !== null && cell.value !== undefined) {
344
+ if (typeof cell.value === 'object') {
345
+ // Handle formula results, rich text, etc.
346
+ if ('result' in cell.value) {
347
+ value = cell.value.result ?? null;
348
+ }
349
+ else if ('richText' in cell.value) {
350
+ value = cell.value.richText.map((rt) => rt.text).join('');
351
+ }
352
+ else if ('text' in cell.value) {
353
+ value = cell.value.text;
354
+ }
355
+ else if (cell.value instanceof Date) {
356
+ value = cell.value.toISOString();
357
+ }
358
+ else {
359
+ value = String(cell.value);
360
+ }
361
+ }
362
+ else {
363
+ // Preserve native types (string, number, boolean)
364
+ value = cell.value;
365
+ }
366
+ }
367
+ rowData.push(value);
368
+ }
369
+ data.push(rowData);
370
+ }
371
+ return { sheetName, data, totalRows, returnedRows: data.length };
372
+ }
373
+ writeDataToSheet(workbook, sheetName, data) {
374
+ // Remove existing sheet if it exists
375
+ const existing = workbook.getWorksheet(sheetName);
376
+ if (existing) {
377
+ workbook.removeWorksheet(existing.id);
378
+ }
379
+ const worksheet = workbook.addWorksheet(sheetName);
380
+ this.writeRowsStartingAt(worksheet, 1, data);
381
+ }
382
+ writeRowsStartingAt(worksheet, startRow, data) {
383
+ for (let r = 0; r < data.length; r++) {
384
+ const rowData = data[r];
385
+ if (!Array.isArray(rowData))
386
+ continue;
387
+ const row = worksheet.getRow(startRow + r);
388
+ for (let c = 0; c < rowData.length; c++) {
389
+ const value = rowData[c];
390
+ if (typeof value === 'string' && value.startsWith('=')) {
391
+ row.getCell(c + 1).value = { formula: value.substring(1) };
392
+ }
393
+ else {
394
+ row.getCell(c + 1).value = value;
395
+ }
396
+ }
397
+ row.commit();
398
+ }
399
+ }
400
+ parseRange(range) {
401
+ if (range.includes('!')) {
402
+ const idx = range.indexOf('!');
403
+ let sheetName = range.slice(0, idx);
404
+ const cellRange = range.slice(idx + 1);
405
+ // Strip Excel-native single quotes around sheet names with spaces:
406
+ // 'My Sheet'!A1 → My Sheet, A1
407
+ if (sheetName.length >= 2 && sheetName.startsWith("'") && sheetName.endsWith("'")) {
408
+ sheetName = sheetName.slice(1, -1).replace(/''/g, "'");
409
+ }
410
+ return [sheetName, cellRange];
411
+ }
412
+ return [range, null];
413
+ }
414
+ parseCellRange(range) {
415
+ // Parse A1 or A1:C10 format
416
+ const match = range.match(/^([A-Z]+)(\d+)(?::([A-Z]+)(\d+))?$/i);
417
+ if (!match) {
418
+ throw new Error(`Invalid cell range: "${range}". Expected forms: "A1", "A1:C10", or "SheetName!A1:C10" ` +
419
+ `(single-quote sheet names containing spaces: "'My Sheet'!A1:C10").`);
420
+ }
421
+ const startCol = this.columnToNumber(match[1]);
422
+ const startRow = parseInt(match[2], 10);
423
+ if (match[3] && match[4]) {
424
+ const endCol = this.columnToNumber(match[3]);
425
+ const endRow = parseInt(match[4], 10);
426
+ return { startRow, startCol, endRow, endCol };
427
+ }
428
+ return { startRow, startCol };
429
+ }
430
+ columnToNumber(col) {
431
+ let result = 0;
432
+ for (let i = 0; i < col.length; i++) {
433
+ result = result * 26 + col.charCodeAt(i) - 64;
434
+ }
435
+ return result;
436
+ }
437
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Factory pattern for creating appropriate file handlers
3
+ * Routes file operations to the correct handler based on file type
4
+ *
5
+ * Each handler implements canHandle() which can be sync (extension-based)
6
+ * or async (content-based like BinaryFileHandler using isBinaryFile)
7
+ */
8
+ import { FileHandler } from './base.js';
9
+ /**
10
+ * Get the appropriate file handler for a given file path
11
+ *
12
+ * Each handler's canHandle() determines if it can process the file.
13
+ * Extension-based handlers (Excel, Image) return sync boolean.
14
+ * BinaryFileHandler uses async isBinaryFile for content-based detection.
15
+ *
16
+ * Priority order:
17
+ * 1. DOCX files (extension based)
18
+ * 2. PDF files (extension based)
19
+ * 3. Excel files (xlsx, xls, xlsm) - extension based
20
+ * 4. Image files (png, jpg, gif, webp) - extension based
21
+ * 5. Binary files - content-based detection via isBinaryFile
22
+ * 6. Text files (default)
23
+ *
24
+ * @param filePath File path to get handler for
25
+ * @returns FileHandler instance that can handle this file
26
+ */
27
+ export declare function getFileHandler(filePath: string): Promise<FileHandler>;
28
+ /**
29
+ * Check if a file path is an Excel file
30
+ * Delegates to ExcelFileHandler.canHandle to avoid duplicating extension logic
31
+ * @param path File path
32
+ * @returns true if file is Excel format
33
+ */
34
+ export declare function isExcelFile(path: string): boolean;
35
+ /**
36
+ * Check if a file path is an image file
37
+ * Delegates to ImageFileHandler.canHandle to avoid duplicating extension logic
38
+ * @param path File path
39
+ * @returns true if file is an image format
40
+ */
41
+ export declare function isImageFile(path: string): boolean;
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Factory pattern for creating appropriate file handlers
3
+ * Routes file operations to the correct handler based on file type
4
+ *
5
+ * Each handler implements canHandle() which can be sync (extension-based)
6
+ * or async (content-based like BinaryFileHandler using isBinaryFile)
7
+ */
8
+ import { TextFileHandler } from './text.js';
9
+ import { ImageFileHandler } from './image.js';
10
+ import { BinaryFileHandler } from './binary.js';
11
+ import { ExcelFileHandler } from './excel.js';
12
+ import { PdfFileHandler } from './pdf.js';
13
+ import { DocxFileHandler } from './docx.js';
14
+ // Singleton instances of each handler
15
+ let excelHandler = null;
16
+ let imageHandler = null;
17
+ let textHandler = null;
18
+ let binaryHandler = null;
19
+ let pdfHandler = null;
20
+ let docxHandler = null;
21
+ /**
22
+ * Initialize handlers (lazy initialization)
23
+ */
24
+ function getExcelHandler() {
25
+ if (!excelHandler)
26
+ excelHandler = new ExcelFileHandler();
27
+ return excelHandler;
28
+ }
29
+ function getImageHandler() {
30
+ if (!imageHandler)
31
+ imageHandler = new ImageFileHandler();
32
+ return imageHandler;
33
+ }
34
+ function getTextHandler() {
35
+ if (!textHandler)
36
+ textHandler = new TextFileHandler();
37
+ return textHandler;
38
+ }
39
+ function getBinaryHandler() {
40
+ if (!binaryHandler)
41
+ binaryHandler = new BinaryFileHandler();
42
+ return binaryHandler;
43
+ }
44
+ function getPdfHandler() {
45
+ if (!pdfHandler)
46
+ pdfHandler = new PdfFileHandler();
47
+ return pdfHandler;
48
+ }
49
+ function getDocxHandler() {
50
+ if (!docxHandler)
51
+ docxHandler = new DocxFileHandler();
52
+ return docxHandler;
53
+ }
54
+ /**
55
+ * Get the appropriate file handler for a given file path
56
+ *
57
+ * Each handler's canHandle() determines if it can process the file.
58
+ * Extension-based handlers (Excel, Image) return sync boolean.
59
+ * BinaryFileHandler uses async isBinaryFile for content-based detection.
60
+ *
61
+ * Priority order:
62
+ * 1. DOCX files (extension based)
63
+ * 2. PDF files (extension based)
64
+ * 3. Excel files (xlsx, xls, xlsm) - extension based
65
+ * 4. Image files (png, jpg, gif, webp) - extension based
66
+ * 5. Binary files - content-based detection via isBinaryFile
67
+ * 6. Text files (default)
68
+ *
69
+ * @param filePath File path to get handler for
70
+ * @returns FileHandler instance that can handle this file
71
+ */
72
+ export async function getFileHandler(filePath) {
73
+ // Check DOCX first (extension-based, sync)
74
+ if (getDocxHandler().canHandle(filePath)) {
75
+ return getDocxHandler();
76
+ }
77
+ // Check PDF (extension-based, sync)
78
+ if (getPdfHandler().canHandle(filePath)) {
79
+ return getPdfHandler();
80
+ }
81
+ // Check Excel (extension-based, sync)
82
+ if (getExcelHandler().canHandle(filePath)) {
83
+ return getExcelHandler();
84
+ }
85
+ // Check Image (extension-based, sync - images are binary but handled specially)
86
+ if (getImageHandler().canHandle(filePath)) {
87
+ return getImageHandler();
88
+ }
89
+ // Check Binary (content-based, async via isBinaryFile)
90
+ if (await getBinaryHandler().canHandle(filePath)) {
91
+ return getBinaryHandler();
92
+ }
93
+ // Default to text handler
94
+ return getTextHandler();
95
+ }
96
+ /**
97
+ * Check if a file path is an Excel file
98
+ * Delegates to ExcelFileHandler.canHandle to avoid duplicating extension logic
99
+ * @param path File path
100
+ * @returns true if file is Excel format
101
+ */
102
+ export function isExcelFile(path) {
103
+ return getExcelHandler().canHandle(path);
104
+ }
105
+ /**
106
+ * Check if a file path is an image file
107
+ * Delegates to ImageFileHandler.canHandle to avoid duplicating extension logic
108
+ * @param path File path
109
+ * @returns true if file is an image format
110
+ */
111
+ export function isImageFile(path) {
112
+ return getImageHandler().canHandle(path);
113
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Image file handler
3
+ * Handles reading image files and converting to base64
4
+ */
5
+ import { FileHandler, ReadOptions, FileResult, FileInfo } from './base.js';
6
+ /**
7
+ * Image file handler implementation
8
+ * Supports: PNG, JPEG, GIF, WebP, BMP, SVG
9
+ */
10
+ export declare class ImageFileHandler implements FileHandler {
11
+ private static readonly IMAGE_EXTENSIONS;
12
+ private static readonly IMAGE_MIME_TYPES;
13
+ canHandle(path: string): boolean;
14
+ read(path: string, options?: ReadOptions): Promise<FileResult>;
15
+ write(path: string, content: Buffer | string): Promise<void>;
16
+ getInfo(path: string): Promise<FileInfo>;
17
+ /**
18
+ * Get MIME type for image based on file extension
19
+ */
20
+ private getMimeType;
21
+ }