@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,370 @@
1
+ /**
2
+ * Text file handler
3
+ * Handles reading, writing, and editing text files
4
+ *
5
+ * Binary detection is handled at the factory level (factory.ts) using isBinaryFile.
6
+ * This handler only receives files that have been confirmed as text.
7
+ *
8
+ * TECHNICAL DEBT:
9
+ * This handler is missing editRange() - text search/replace logic currently lives in
10
+ * src/tools/edit.ts (performSearchReplace function) instead of here.
11
+ *
12
+ * For architectural consistency with ExcelFileHandler.editRange(), the fuzzy
13
+ * search/replace logic should be moved here. See comment in src/tools/edit.ts.
14
+ */
15
+ import fs from "fs/promises";
16
+ import { createReadStream } from 'fs';
17
+ import { createInterface } from 'readline';
18
+ // TODO: Centralize these constants with filesystem.ts to avoid silent drift
19
+ // These duplicate concepts from filesystem.ts and should be moved to a shared
20
+ // constants module (e.g., src/utils/files/constants.ts) during reorganization
21
+ const FILE_SIZE_LIMITS = {
22
+ LARGE_FILE_THRESHOLD: 10 * 1024 * 1024, // 10MB
23
+ LINE_COUNT_LIMIT: 10 * 1024 * 1024, // 10MB for line counting
24
+ };
25
+ const READ_PERFORMANCE_THRESHOLDS = {
26
+ SMALL_READ_THRESHOLD: 100, // For very small reads
27
+ DEEP_OFFSET_THRESHOLD: 1000, // For byte estimation
28
+ SAMPLE_SIZE: 10000, // Sample size for estimation
29
+ CHUNK_SIZE: 8192, // 8KB chunks for reverse reading
30
+ };
31
+ /**
32
+ * Text file handler implementation
33
+ * Binary detection is done at the factory level - this handler assumes file is text
34
+ */
35
+ export class TextFileHandler {
36
+ canHandle(_path) {
37
+ // Text handler accepts all files that pass the factory's binary check
38
+ // The factory routes binary files to BinaryFileHandler before reaching here
39
+ return true;
40
+ }
41
+ async read(filePath, options) {
42
+ const offset = options?.offset ?? 0;
43
+ const length = options?.length ?? 1000; // Default from config
44
+ const includeStatusMessage = options?.includeStatusMessage ?? true;
45
+ // Binary detection is done at factory level - just read as text
46
+ return this.readFileWithSmartPositioning(filePath, offset, length, 'text/plain', includeStatusMessage, options?.signal);
47
+ }
48
+ async write(path, content, mode = 'rewrite') {
49
+ if (mode === 'append') {
50
+ await fs.appendFile(path, content);
51
+ }
52
+ else {
53
+ await fs.writeFile(path, content);
54
+ }
55
+ }
56
+ async getInfo(path) {
57
+ const stats = await fs.stat(path);
58
+ const info = {
59
+ size: stats.size,
60
+ created: stats.birthtime,
61
+ modified: stats.mtime,
62
+ accessed: stats.atime,
63
+ isDirectory: stats.isDirectory(),
64
+ isFile: stats.isFile(),
65
+ permissions: stats.mode.toString(8).slice(-3),
66
+ fileType: 'text',
67
+ metadata: {}
68
+ };
69
+ // For text files that aren't too large, count lines
70
+ if (stats.isFile() && stats.size < FILE_SIZE_LIMITS.LINE_COUNT_LIMIT) {
71
+ try {
72
+ const content = await fs.readFile(path, 'utf8');
73
+ const lineCount = TextFileHandler.countLines(content);
74
+ info.metadata.lineCount = lineCount;
75
+ }
76
+ catch (error) {
77
+ // If reading fails, skip line count
78
+ }
79
+ }
80
+ return info;
81
+ }
82
+ // ========================================================================
83
+ // Private Helper Methods (extracted from filesystem.ts)
84
+ // ========================================================================
85
+ /**
86
+ * Count lines in text content
87
+ * Made static and public for use by other modules (e.g., writeFile telemetry in filesystem.ts)
88
+ */
89
+ static countLines(content) {
90
+ if (content === '')
91
+ return 0;
92
+ // A file with N lines has N-1 newline characters.
93
+ // If the file ends with a trailing newline, don't count the empty string after it.
94
+ const lines = content.split('\n');
95
+ if (lines[lines.length - 1] === '') {
96
+ return lines.length - 1;
97
+ }
98
+ return lines.length;
99
+ }
100
+ /**
101
+ * Get file line count (for files under size limit)
102
+ */
103
+ async getFileLineCount(filePath, signal) {
104
+ try {
105
+ const stats = await fs.stat(filePath);
106
+ if (stats.size < FILE_SIZE_LIMITS.LINE_COUNT_LIMIT) {
107
+ const content = await fs.readFile(filePath, { encoding: 'utf8', signal });
108
+ return TextFileHandler.countLines(content);
109
+ }
110
+ }
111
+ catch (error) {
112
+ // If we can't read the file, return undefined
113
+ }
114
+ return undefined;
115
+ }
116
+ /**
117
+ * Generate enhanced status message
118
+ */
119
+ generateEnhancedStatusMessage(readLines, offset, totalLines, isNegativeOffset = false) {
120
+ if (isNegativeOffset) {
121
+ if (totalLines !== undefined) {
122
+ return `[Reading last ${readLines} lines (total: ${totalLines} lines)]`;
123
+ }
124
+ else {
125
+ return `[Reading last ${readLines} lines]`;
126
+ }
127
+ }
128
+ else {
129
+ if (totalLines !== undefined) {
130
+ const endLine = offset + readLines;
131
+ const remainingLines = Math.max(0, totalLines - endLine);
132
+ if (offset === 0) {
133
+ return `[Reading ${readLines} lines from start (total: ${totalLines} lines, ${remainingLines} remaining)]`;
134
+ }
135
+ else {
136
+ return `[Reading ${readLines} lines from line ${offset} (total: ${totalLines} lines, ${remainingLines} remaining)]`;
137
+ }
138
+ }
139
+ else {
140
+ if (offset === 0) {
141
+ return `[Reading ${readLines} lines from start]`;
142
+ }
143
+ else {
144
+ return `[Reading ${readLines} lines from line ${offset}]`;
145
+ }
146
+ }
147
+ }
148
+ }
149
+ /**
150
+ * Split text into lines while preserving line endings
151
+ * Made static and public for use by other modules (e.g., readFileInternal in filesystem.ts)
152
+ */
153
+ static splitLinesPreservingEndings(content) {
154
+ if (!content)
155
+ return [''];
156
+ const lines = [];
157
+ let currentLine = '';
158
+ for (let i = 0; i < content.length; i++) {
159
+ const char = content[i];
160
+ currentLine += char;
161
+ if (char === '\n') {
162
+ lines.push(currentLine);
163
+ currentLine = '';
164
+ }
165
+ else if (char === '\r') {
166
+ if (i + 1 < content.length && content[i + 1] === '\n') {
167
+ currentLine += content[i + 1];
168
+ i++;
169
+ }
170
+ lines.push(currentLine);
171
+ currentLine = '';
172
+ }
173
+ }
174
+ if (currentLine) {
175
+ lines.push(currentLine);
176
+ }
177
+ return lines;
178
+ }
179
+ /**
180
+ * Read file with smart positioning for optimal performance
181
+ */
182
+ async readFileWithSmartPositioning(filePath, offset, length, mimeType, includeStatusMessage = true, signal) {
183
+ const stats = await fs.stat(filePath);
184
+ const fileSize = stats.size;
185
+ const totalLines = await this.getFileLineCount(filePath, signal);
186
+ // For negative offsets (tail behavior), use reverse reading
187
+ if (offset < 0) {
188
+ const requestedLines = Math.abs(offset);
189
+ if (fileSize > FILE_SIZE_LIMITS.LARGE_FILE_THRESHOLD &&
190
+ requestedLines <= READ_PERFORMANCE_THRESHOLDS.SMALL_READ_THRESHOLD) {
191
+ return await this.readLastNLinesReverse(filePath, requestedLines, mimeType, includeStatusMessage, totalLines, signal);
192
+ }
193
+ else {
194
+ return await this.readFromEndWithReadline(filePath, requestedLines, mimeType, includeStatusMessage, totalLines, signal);
195
+ }
196
+ }
197
+ // For positive offsets
198
+ else {
199
+ if (fileSize < FILE_SIZE_LIMITS.LARGE_FILE_THRESHOLD || offset === 0) {
200
+ return await this.readFromStartWithReadline(filePath, offset, length, mimeType, includeStatusMessage, totalLines, signal);
201
+ }
202
+ else {
203
+ if (offset > READ_PERFORMANCE_THRESHOLDS.DEEP_OFFSET_THRESHOLD) {
204
+ return await this.readFromEstimatedPosition(filePath, offset, length, mimeType, includeStatusMessage, totalLines, signal);
205
+ }
206
+ else {
207
+ return await this.readFromStartWithReadline(filePath, offset, length, mimeType, includeStatusMessage, totalLines, signal);
208
+ }
209
+ }
210
+ }
211
+ }
212
+ /**
213
+ * Read last N lines efficiently by reading file backwards
214
+ */
215
+ async readLastNLinesReverse(filePath, n, mimeType, includeStatusMessage = true, fileTotalLines, signal) {
216
+ const fd = await fs.open(filePath, 'r');
217
+ try {
218
+ const stats = await fd.stat();
219
+ const fileSize = stats.size;
220
+ let position = fileSize;
221
+ let lines = [];
222
+ let partialLine = '';
223
+ while (position > 0 && lines.length < n) {
224
+ if (signal?.aborted) {
225
+ const err = new Error('Read aborted');
226
+ err.code = 'ABORT_ERR';
227
+ throw err;
228
+ }
229
+ const readSize = Math.min(READ_PERFORMANCE_THRESHOLDS.CHUNK_SIZE, position);
230
+ position -= readSize;
231
+ const buffer = Buffer.alloc(readSize);
232
+ await fd.read(buffer, 0, readSize, position);
233
+ const chunk = buffer.toString('utf-8');
234
+ const text = chunk + partialLine;
235
+ const chunkLines = text.split('\n');
236
+ partialLine = chunkLines.shift() || '';
237
+ lines = chunkLines.concat(lines);
238
+ }
239
+ if (position === 0 && partialLine) {
240
+ lines.unshift(partialLine);
241
+ }
242
+ const result = lines.slice(-n);
243
+ const content = includeStatusMessage
244
+ ? `${this.generateEnhancedStatusMessage(result.length, -n, fileTotalLines, true)}\n\n${result.join('\n')}`
245
+ : result.join('\n');
246
+ return { content, mimeType, metadata: {} };
247
+ }
248
+ finally {
249
+ await fd.close();
250
+ }
251
+ }
252
+ /**
253
+ * Read from end using readline with circular buffer
254
+ */
255
+ async readFromEndWithReadline(filePath, requestedLines, mimeType, includeStatusMessage = true, fileTotalLines, signal) {
256
+ const rl = createInterface({
257
+ input: createReadStream(filePath, { signal }),
258
+ crlfDelay: Infinity
259
+ });
260
+ const buffer = new Array(requestedLines);
261
+ let bufferIndex = 0;
262
+ let totalLines = 0;
263
+ for await (const line of rl) {
264
+ buffer[bufferIndex] = line;
265
+ bufferIndex = (bufferIndex + 1) % requestedLines;
266
+ totalLines++;
267
+ }
268
+ rl.close();
269
+ let result;
270
+ if (totalLines >= requestedLines) {
271
+ result = [
272
+ ...buffer.slice(bufferIndex),
273
+ ...buffer.slice(0, bufferIndex)
274
+ ].filter(line => line !== undefined);
275
+ }
276
+ else {
277
+ result = buffer.slice(0, totalLines);
278
+ }
279
+ const content = includeStatusMessage
280
+ ? `${this.generateEnhancedStatusMessage(result.length, -requestedLines, fileTotalLines, true)}\n\n${result.join('\n')}`
281
+ : result.join('\n');
282
+ return { content, mimeType, metadata: {} };
283
+ }
284
+ /**
285
+ * Read from start/middle using readline
286
+ */
287
+ async readFromStartWithReadline(filePath, offset, length, mimeType, includeStatusMessage = true, fileTotalLines, signal) {
288
+ const rl = createInterface({
289
+ input: createReadStream(filePath, { signal }),
290
+ crlfDelay: Infinity
291
+ });
292
+ const result = [];
293
+ let lineNumber = 0;
294
+ for await (const line of rl) {
295
+ if (lineNumber >= offset && result.length < length) {
296
+ result.push(line);
297
+ }
298
+ if (result.length >= length)
299
+ break;
300
+ lineNumber++;
301
+ }
302
+ rl.close();
303
+ if (includeStatusMessage) {
304
+ const statusMessage = this.generateEnhancedStatusMessage(result.length, offset, fileTotalLines, false);
305
+ const content = `${statusMessage}\n\n${result.join('\n')}`;
306
+ return { content, mimeType, metadata: {} };
307
+ }
308
+ else {
309
+ const content = result.join('\n');
310
+ return { content, mimeType, metadata: {} };
311
+ }
312
+ }
313
+ /**
314
+ * Read from estimated byte position for very large files
315
+ */
316
+ async readFromEstimatedPosition(filePath, offset, length, mimeType, includeStatusMessage = true, fileTotalLines, signal) {
317
+ // First, do a quick scan to estimate lines per byte
318
+ const rl = createInterface({
319
+ input: createReadStream(filePath, { signal }),
320
+ crlfDelay: Infinity
321
+ });
322
+ let sampleLines = 0;
323
+ let bytesRead = 0;
324
+ for await (const line of rl) {
325
+ bytesRead += Buffer.byteLength(line, 'utf-8') + 1;
326
+ sampleLines++;
327
+ if (bytesRead >= READ_PERFORMANCE_THRESHOLDS.SAMPLE_SIZE)
328
+ break;
329
+ }
330
+ rl.close();
331
+ if (sampleLines === 0) {
332
+ return await this.readFromStartWithReadline(filePath, offset, length, mimeType, includeStatusMessage, fileTotalLines, signal);
333
+ }
334
+ // Estimate position
335
+ const avgLineLength = bytesRead / sampleLines;
336
+ const estimatedBytePosition = Math.floor(offset * avgLineLength);
337
+ const fd = await fs.open(filePath, 'r');
338
+ try {
339
+ const stats = await fd.stat();
340
+ const startPosition = Math.min(estimatedBytePosition, stats.size);
341
+ const stream = createReadStream(filePath, { start: startPosition, signal });
342
+ const rl2 = createInterface({
343
+ input: stream,
344
+ crlfDelay: Infinity
345
+ });
346
+ const result = [];
347
+ let firstLineSkipped = false;
348
+ for await (const line of rl2) {
349
+ if (!firstLineSkipped && startPosition > 0) {
350
+ firstLineSkipped = true;
351
+ continue;
352
+ }
353
+ if (result.length < length) {
354
+ result.push(line);
355
+ }
356
+ else {
357
+ break;
358
+ }
359
+ }
360
+ rl2.close();
361
+ const content = includeStatusMessage
362
+ ? `${this.generateEnhancedStatusMessage(result.length, offset, fileTotalLines, false)}\n\n${result.join('\n')}`
363
+ : result.join('\n');
364
+ return { content, mimeType, metadata: {} };
365
+ }
366
+ finally {
367
+ await fd.close();
368
+ }
369
+ }
370
+ }
@@ -0,0 +1,30 @@
1
+ export interface FuzzySearchLogEntry {
2
+ timestamp: Date;
3
+ searchText: string;
4
+ foundText: string;
5
+ similarity: number;
6
+ executionTime: number;
7
+ exactMatchCount: number;
8
+ expectedReplacements: number;
9
+ fuzzyThreshold: number;
10
+ belowThreshold: boolean;
11
+ diff: string;
12
+ searchLength: number;
13
+ foundLength: number;
14
+ fileExtension: string;
15
+ characterCodes: string;
16
+ uniqueCharacterCount: number;
17
+ diffLength: number;
18
+ }
19
+ declare class FuzzySearchLogger {
20
+ private logPath;
21
+ private initialized;
22
+ constructor();
23
+ private ensureLogFile;
24
+ log(entry: FuzzySearchLogEntry): Promise<void>;
25
+ getLogPath(): Promise<string>;
26
+ getRecentLogs(count?: number): Promise<string[]>;
27
+ clearLog(): Promise<void>;
28
+ }
29
+ export declare const fuzzySearchLogger: FuzzySearchLogger;
30
+ export {};
@@ -0,0 +1,126 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ import { CONFIG_DIR } from '../config.js';
4
+ class FuzzySearchLogger {
5
+ constructor() {
6
+ this.initialized = false;
7
+ // Keep logs under the canonical MCP Device state root.
8
+ const logDir = path.join(CONFIG_DIR, 'logs');
9
+ this.logPath = path.join(logDir, 'fuzzy-search.log');
10
+ }
11
+ async ensureLogFile() {
12
+ if (this.initialized)
13
+ return;
14
+ try {
15
+ // Create log directory if it doesn't exist
16
+ const logDir = path.dirname(this.logPath);
17
+ await fs.mkdir(logDir, { recursive: true });
18
+ // Check if log file exists, create with headers if not
19
+ try {
20
+ await fs.access(this.logPath);
21
+ }
22
+ catch {
23
+ // File doesn't exist, create with headers
24
+ const headers = [
25
+ 'timestamp',
26
+ 'searchText',
27
+ 'foundText',
28
+ 'similarity',
29
+ 'executionTime',
30
+ 'exactMatchCount',
31
+ 'expectedReplacements',
32
+ 'fuzzyThreshold',
33
+ 'belowThreshold',
34
+ 'diff',
35
+ 'searchLength',
36
+ 'foundLength',
37
+ 'fileExtension',
38
+ 'characterCodes',
39
+ 'uniqueCharacterCount',
40
+ 'diffLength'
41
+ ].join('\t');
42
+ await fs.writeFile(this.logPath, headers + '\n');
43
+ }
44
+ this.initialized = true;
45
+ }
46
+ catch (error) {
47
+ console.error('Failed to initialize fuzzy search log file:', error);
48
+ throw error;
49
+ }
50
+ }
51
+ async log(entry) {
52
+ try {
53
+ await this.ensureLogFile();
54
+ // Convert entry to tab-separated string
55
+ const logLine = [
56
+ entry.timestamp.toISOString(),
57
+ entry.searchText.replace(/\n/g, '\\n').replace(/\t/g, '\\t'),
58
+ entry.foundText.replace(/\n/g, '\\n').replace(/\t/g, '\\t'),
59
+ entry.similarity.toString(),
60
+ entry.executionTime.toString(),
61
+ entry.exactMatchCount.toString(),
62
+ entry.expectedReplacements.toString(),
63
+ entry.fuzzyThreshold.toString(),
64
+ entry.belowThreshold.toString(),
65
+ entry.diff.replace(/\n/g, '\\n').replace(/\t/g, '\\t'),
66
+ entry.searchLength.toString(),
67
+ entry.foundLength.toString(),
68
+ entry.fileExtension,
69
+ entry.characterCodes,
70
+ entry.uniqueCharacterCount.toString(),
71
+ entry.diffLength.toString()
72
+ ].join('\t');
73
+ await fs.appendFile(this.logPath, logLine + '\n');
74
+ }
75
+ catch (error) {
76
+ console.error('Failed to write to fuzzy search log:', error);
77
+ }
78
+ }
79
+ async getLogPath() {
80
+ await this.ensureLogFile();
81
+ return this.logPath;
82
+ }
83
+ async getRecentLogs(count = 10) {
84
+ try {
85
+ await this.ensureLogFile();
86
+ const content = await fs.readFile(this.logPath, 'utf-8');
87
+ const lines = content.split('\n').filter(line => line.trim());
88
+ // Return last N lines (excluding header)
89
+ return lines.slice(-count - 1, -1);
90
+ }
91
+ catch (error) {
92
+ console.error('Failed to read fuzzy search logs:', error);
93
+ return [];
94
+ }
95
+ }
96
+ async clearLog() {
97
+ try {
98
+ // Recreate with just headers
99
+ const headers = [
100
+ 'timestamp',
101
+ 'searchText',
102
+ 'foundText',
103
+ 'similarity',
104
+ 'executionTime',
105
+ 'exactMatchCount',
106
+ 'expectedReplacements',
107
+ 'fuzzyThreshold',
108
+ 'belowThreshold',
109
+ 'diff',
110
+ 'searchLength',
111
+ 'foundLength',
112
+ 'fileExtension',
113
+ 'characterCodes',
114
+ 'uniqueCharacterCount',
115
+ 'diffLength'
116
+ ].join('\t');
117
+ await fs.writeFile(this.logPath, headers + '\n');
118
+ console.log('Fuzzy search log cleared');
119
+ }
120
+ catch (error) {
121
+ console.error('Failed to clear fuzzy search log:', error);
122
+ }
123
+ }
124
+ }
125
+ // Singleton instance
126
+ export const fuzzySearchLogger = new FuzzySearchLogger();
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Line ending types
3
+ */
4
+ export type LineEndingStyle = '\r\n' | '\n' | '\r';
5
+ /**
6
+ * Detect the line ending style used in a file - Optimized version
7
+ * This algorithm uses early termination for maximum performance
8
+ */
9
+ export declare function detectLineEnding(content: string): LineEndingStyle;
10
+ /**
11
+ * Normalize line endings to match the target style
12
+ */
13
+ export declare function normalizeLineEndings(text: string, targetLineEnding: LineEndingStyle): string;
14
+ /**
15
+ * Analyze line ending usage in content
16
+ */
17
+ export declare function analyzeLineEndings(content: string): {
18
+ style: LineEndingStyle;
19
+ count: number;
20
+ hasMixed: boolean;
21
+ };
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Detect the line ending style used in a file - Optimized version
3
+ * This algorithm uses early termination for maximum performance
4
+ */
5
+ export function detectLineEnding(content) {
6
+ for (let i = 0; i < content.length; i++) {
7
+ if (content[i] === '\r') {
8
+ if (i + 1 < content.length && content[i + 1] === '\n') {
9
+ return '\r\n';
10
+ }
11
+ return '\r';
12
+ }
13
+ if (content[i] === '\n') {
14
+ return '\n';
15
+ }
16
+ }
17
+ // Default to system line ending if no line endings found
18
+ return process.platform === 'win32' ? '\r\n' : '\n';
19
+ }
20
+ /**
21
+ * Normalize line endings to match the target style
22
+ */
23
+ export function normalizeLineEndings(text, targetLineEnding) {
24
+ // First normalize to LF
25
+ let normalized = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
26
+ // Then convert to target
27
+ if (targetLineEnding === '\r\n') {
28
+ return normalized.replace(/\n/g, '\r\n');
29
+ }
30
+ else if (targetLineEnding === '\r') {
31
+ return normalized.replace(/\n/g, '\r');
32
+ }
33
+ return normalized;
34
+ }
35
+ /**
36
+ * Analyze line ending usage in content
37
+ */
38
+ export function analyzeLineEndings(content) {
39
+ let crlfCount = 0;
40
+ let lfCount = 0;
41
+ let crCount = 0;
42
+ // Count line endings
43
+ for (let i = 0; i < content.length; i++) {
44
+ if (content[i] === '\r') {
45
+ if (i + 1 < content.length && content[i + 1] === '\n') {
46
+ crlfCount++;
47
+ i++; // Skip the LF
48
+ }
49
+ else {
50
+ crCount++;
51
+ }
52
+ }
53
+ else if (content[i] === '\n') {
54
+ lfCount++;
55
+ }
56
+ }
57
+ // Determine predominant style
58
+ const total = crlfCount + lfCount + crCount;
59
+ let style;
60
+ if (crlfCount > lfCount && crlfCount > crCount) {
61
+ style = '\r\n';
62
+ }
63
+ else if (lfCount > crCount) {
64
+ style = '\n';
65
+ }
66
+ else {
67
+ style = '\r';
68
+ }
69
+ // Check for mixed line endings
70
+ const usedStyles = [crlfCount > 0, lfCount > 0, crCount > 0].filter(Boolean).length;
71
+ const hasMixed = usedStyles > 1;
72
+ return {
73
+ style,
74
+ count: total,
75
+ hasMixed
76
+ };
77
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Centralized logging utility for MCP Device
3
+ * Ensures all logging goes through proper channels based on initialization state
4
+ */
5
+ import type { FilteredStdioServerTransport } from '../custom-stdio.js';
6
+ declare global {
7
+ var mcpTransport: FilteredStdioServerTransport | undefined;
8
+ }
9
+ export type LogLevel = 'emergency' | 'alert' | 'critical' | 'error' | 'warning' | 'notice' | 'info' | 'debug';
10
+ /**
11
+ * Log a message using the appropriate method based on MCP initialization state
12
+ */
13
+ export declare function log(level: LogLevel, message: string, data?: any): void;
14
+ /**
15
+ * Convenience functions for different log levels
16
+ */
17
+ export declare const logger: {
18
+ emergency: (message: string, data?: any) => void;
19
+ alert: (message: string, data?: any) => void;
20
+ critical: (message: string, data?: any) => void;
21
+ error: (message: string, data?: any) => void;
22
+ warning: (message: string, data?: any) => void;
23
+ notice: (message: string, data?: any) => void;
24
+ info: (message: string, data?: any) => void;
25
+ debug: (message: string, data?: any) => void;
26
+ };
27
+ /**
28
+ * Log to stderr during early initialization (before MCP is ready)
29
+ * Use this for critical startup messages that must be visible
30
+ * NOTE: This should also be JSON-RPC format
31
+ */
32
+ export declare function logToStderr(level: LogLevel, message: string): void;