@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,215 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ import { existsSync } from 'fs';
4
+ import { CONFIG_FILE } from '../config.js';
5
+ import { logger } from './logger.js';
6
+ class FeatureFlagManager {
7
+ constructor() {
8
+ this.flags = {};
9
+ this.lastFetch = 0;
10
+ this.cacheMaxAge = 30 * 60 * 1000;
11
+ this.refreshInterval = null;
12
+ // Track fresh fetch status for A/B tests that need network flags
13
+ this.freshFetchPromise = null;
14
+ this.resolveFreshFetch = null;
15
+ this.loadedFromCache = false;
16
+ const configDir = path.dirname(CONFIG_FILE);
17
+ this.cachePath = path.join(configDir, 'feature-flags.json');
18
+ // Use production flags (v2 supports weighted variants)
19
+ this.flagUrl = process.env.DC_FLAG_URL ||
20
+ 'https://desktopcommander.app/flags/v2/production.json';
21
+ // Set up promise for waiting on fresh fetch
22
+ this.freshFetchPromise = new Promise((resolve) => {
23
+ this.resolveFreshFetch = resolve;
24
+ });
25
+ }
26
+ /**
27
+ * Initialize - load from cache and start background refresh
28
+ */
29
+ async initialize() {
30
+ try {
31
+ // Load from cache immediately (non-blocking)
32
+ await this.loadFromCache();
33
+ // Fetch in background (don't block startup)
34
+ this.fetchFlags().then(() => {
35
+ // Signal that fresh flags are now available
36
+ if (this.resolveFreshFetch) {
37
+ this.resolveFreshFetch();
38
+ }
39
+ }).catch(err => {
40
+ logger.debug('Initial flag fetch failed:', err.message);
41
+ // Still resolve the promise so waiters don't hang forever
42
+ if (this.resolveFreshFetch) {
43
+ this.resolveFreshFetch();
44
+ }
45
+ });
46
+ // Start periodic refresh every 5 minutes
47
+ this.refreshInterval = setInterval(() => {
48
+ this.fetchFlags().catch(err => {
49
+ logger.debug('Periodic flag fetch failed:', err.message);
50
+ });
51
+ }, this.cacheMaxAge);
52
+ // Allow process to exit even if interval is pending
53
+ // This is critical for proper cleanup when MCP client disconnects
54
+ this.refreshInterval.unref();
55
+ logger.info(`Feature flags initialized (refresh every ${this.cacheMaxAge / 1000}s)`);
56
+ }
57
+ catch (error) {
58
+ logger.warning('Failed to initialize feature flags:', error);
59
+ }
60
+ }
61
+ /**
62
+ * Get a flag value
63
+ */
64
+ get(flagName, defaultValue = false) {
65
+ return this.flags[flagName] !== undefined ? this.flags[flagName] : defaultValue;
66
+ }
67
+ /**
68
+ * Get all flags for debugging
69
+ */
70
+ getAll() {
71
+ return { ...this.flags };
72
+ }
73
+ /**
74
+ * Manually refresh flags immediately (for testing)
75
+ */
76
+ async refresh() {
77
+ try {
78
+ await this.fetchFlags();
79
+ return true;
80
+ }
81
+ catch (error) {
82
+ logger.error('Manual refresh failed:', error);
83
+ return false;
84
+ }
85
+ }
86
+ /**
87
+ * Check if flags were loaded from cache (vs fresh fetch)
88
+ */
89
+ wasLoadedFromCache() {
90
+ return this.loadedFromCache;
91
+ }
92
+ /**
93
+ * Wait for fresh flags to be fetched from network.
94
+ * Use this when you need to ensure flags are loaded before making decisions
95
+ * (e.g., A/B test assignments for new users who don't have a cache yet)
96
+ *
97
+ * Has a hard timeout to prevent blocking MCP startup if the fetch hangs.
98
+ * See: https://github.com/wonderwhy-er/DesktopCommanderMCP/issues/465
99
+ */
100
+ async waitForFreshFlags() {
101
+ if (this.freshFetchPromise) {
102
+ let safetyTimeoutHandle;
103
+ try {
104
+ const safetyTimeout = new Promise((resolve) => {
105
+ safetyTimeoutHandle = setTimeout(resolve, 5000);
106
+ });
107
+ await Promise.race([this.freshFetchPromise, safetyTimeout]);
108
+ }
109
+ finally {
110
+ if (safetyTimeoutHandle) {
111
+ clearTimeout(safetyTimeoutHandle);
112
+ }
113
+ }
114
+ }
115
+ }
116
+ /**
117
+ * Load flags from local cache
118
+ */
119
+ async loadFromCache() {
120
+ try {
121
+ if (!existsSync(this.cachePath)) {
122
+ logger.debug('No feature flag cache found');
123
+ this.loadedFromCache = false;
124
+ return;
125
+ }
126
+ const data = await fs.readFile(this.cachePath, 'utf8');
127
+ const config = JSON.parse(data);
128
+ if (config.flags) {
129
+ this.flags = config.flags;
130
+ this.lastFetch = Date.now();
131
+ this.loadedFromCache = true;
132
+ logger.debug(`Loaded ${Object.keys(this.flags).length} feature flags from cache`);
133
+ }
134
+ }
135
+ catch (error) {
136
+ logger.warning('Failed to load feature flags from cache:', error);
137
+ this.loadedFromCache = false;
138
+ }
139
+ }
140
+ /**
141
+ * Fetch flags from remote URL
142
+ */
143
+ async fetchFlags() {
144
+ const FETCH_TIMEOUT_MS = 3000;
145
+ const controller = new AbortController();
146
+ const abortTimeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
147
+ let hardTimeoutHandle;
148
+ try {
149
+ // Don't log here - runs async and can interfere with MCP clients
150
+ // Use Promise.race as a hard timeout safety net.
151
+ // On some platforms (Windows + Node 24 / undici 7.x), AbortController.abort()
152
+ // fails to interrupt an in-progress TCP connect — the fetch hangs until the
153
+ // OS-level TCP timeout (~30s on Windows). Promise.race guarantees we reject
154
+ // at the JS level regardless of AbortController behavior.
155
+ // See: https://github.com/wonderwhy-er/DesktopCommanderMCP/issues/465
156
+ const fetchPromise = fetch(this.flagUrl, {
157
+ signal: controller.signal,
158
+ headers: {
159
+ 'Cache-Control': 'no-cache',
160
+ }
161
+ });
162
+ const hardTimeout = new Promise((_, reject) => hardTimeoutHandle = setTimeout(() => reject(new Error('Feature flags fetch timed out')), FETCH_TIMEOUT_MS));
163
+ const response = await Promise.race([fetchPromise, hardTimeout]);
164
+ if (!response.ok) {
165
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
166
+ }
167
+ const config = await response.json();
168
+ // Update flags
169
+ if (config.flags) {
170
+ this.flags = config.flags;
171
+ this.lastFetch = Date.now();
172
+ // Save to cache (silently - don't log during async operations
173
+ // as it can interfere with MCP clients that close quickly)
174
+ await this.saveToCache(config);
175
+ }
176
+ }
177
+ catch (error) {
178
+ logger.debug('Failed to fetch feature flags:', error.message);
179
+ // Continue with cached values
180
+ }
181
+ finally {
182
+ clearTimeout(abortTimeout);
183
+ if (hardTimeoutHandle) {
184
+ clearTimeout(hardTimeoutHandle);
185
+ }
186
+ }
187
+ }
188
+ /**
189
+ * Save flags to local cache
190
+ */
191
+ async saveToCache(config) {
192
+ try {
193
+ const configDir = path.dirname(this.cachePath);
194
+ if (!existsSync(configDir)) {
195
+ await fs.mkdir(configDir, { recursive: true });
196
+ }
197
+ await fs.writeFile(this.cachePath, JSON.stringify(config, null, 2), 'utf8');
198
+ // Don't log here - this runs async and can cause issues with MCP clients
199
+ }
200
+ catch (error) {
201
+ logger.warning('Failed to save feature flags to cache:', error);
202
+ }
203
+ }
204
+ /**
205
+ * Cleanup on shutdown
206
+ */
207
+ destroy() {
208
+ if (this.refreshInterval) {
209
+ clearInterval(this.refreshInterval);
210
+ this.refreshInterval = null;
211
+ }
212
+ }
213
+ }
214
+ // Export singleton instance
215
+ export const featureFlagManager = new FeatureFlagManager();
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Base interfaces and types for file handling system
3
+ * All file handlers implement the FileHandler interface
4
+ */
5
+ /**
6
+ * Base interface that all file handlers must implement
7
+ */
8
+ export interface FileHandler {
9
+ /**
10
+ * Read file content
11
+ * @param path Validated file path
12
+ * @param options Read options (offset, length, sheet, etc.)
13
+ * @returns File result with content and metadata
14
+ */
15
+ read(path: string, options?: ReadOptions): Promise<FileResult>;
16
+ /**
17
+ * Write file (complete rewrite or append)
18
+ * @param path Validated file path
19
+ * @param content Content to write
20
+ * @param mode Write mode: 'rewrite' (default) or 'append'
21
+ */
22
+ write(path: string, content: any, mode?: 'rewrite' | 'append'): Promise<void>;
23
+ /**
24
+ * Edit a specific range (bulk rewrite)
25
+ * PRIMARY METHOD for structured file editing (Excel, etc.)
26
+ * Simpler and more powerful than location-based edits
27
+ * Supports:
28
+ * - Cell ranges: "Sheet1!A1:C10" with 2D array content
29
+ * - Whole sheets: "Sheet1" to replace entire sheet
30
+ * - Chunking: Update 1000 rows at a time for large files
31
+ *
32
+ * Currently implemented by: ExcelFileHandler
33
+ * TECHNICAL DEBT: TextFileHandler should also implement this for search/replace
34
+ * (logic currently in src/tools/edit.ts - see comments there)
35
+ *
36
+ * @param path Validated file path
37
+ * @param range Range identifier (e.g., "Sheet1!A1:C10" or "Sheet1")
38
+ * @param content New content for the range (2D array for Excel)
39
+ * @param options Additional format-specific options
40
+ * @returns Result with success status
41
+ */
42
+ editRange?(path: string, range: string, content: any, options?: Record<string, any>): Promise<EditResult>;
43
+ /**
44
+ * Get file metadata
45
+ * @param path Validated file path
46
+ * @returns File information including type-specific metadata
47
+ */
48
+ getInfo(path: string): Promise<FileInfo>;
49
+ /**
50
+ * Check if this handler can handle the given file
51
+ * @param path File path
52
+ * @returns true if this handler supports this file type (can be async for content-based checks)
53
+ */
54
+ canHandle(path: string): boolean | Promise<boolean>;
55
+ }
56
+ /**
57
+ * Options for reading files
58
+ */
59
+ export interface ReadOptions {
60
+ /** Whether the path is a URL */
61
+ isUrl?: boolean;
62
+ /** Starting line/row number (for text/excel) */
63
+ offset?: number;
64
+ /** Maximum number of lines/rows to read */
65
+ length?: number;
66
+ /** Excel-specific: Sheet name or index */
67
+ sheet?: string | number;
68
+ /** Excel-specific: Cell range (e.g., "A1:C10") */
69
+ range?: string;
70
+ /** Whether to include status messages (default: true) */
71
+ includeStatusMessage?: boolean;
72
+ /** Optional AbortSignal to cancel an in-flight read (frees fd/thread on timeout). */
73
+ signal?: AbortSignal;
74
+ }
75
+ /**
76
+ * Result from reading a file
77
+ */
78
+ export interface FileResult {
79
+ /** File content (string for text/csv, Buffer for binary, base64 string for images) */
80
+ content: string | Buffer;
81
+ /** MIME type of the content */
82
+ mimeType: string;
83
+ /** Type-specific metadata */
84
+ metadata?: FileMetadata;
85
+ }
86
+ /**
87
+ * File-type specific metadata
88
+ */
89
+ export interface FileMetadata {
90
+ /** For images */
91
+ isImage?: boolean;
92
+ /** For directories (read_file fallback to listDirectory) */
93
+ isDirectory?: boolean;
94
+ /** For binary files */
95
+ isBinary?: boolean;
96
+ /** For Excel files */
97
+ isExcelFile?: boolean;
98
+ sheets?: ExcelSheet[];
99
+ fileSize?: number;
100
+ isLargeFile?: boolean;
101
+ /** For text files */
102
+ lineCount?: number;
103
+ /** For PDF files */
104
+ isPdf?: boolean;
105
+ author?: string;
106
+ title?: string;
107
+ totalPages?: number;
108
+ pages?: PdfPageItem[];
109
+ /** For DOCX files */
110
+ isDocx?: boolean;
111
+ /** Error information if operation failed */
112
+ error?: boolean;
113
+ errorMessage?: string;
114
+ }
115
+ /**
116
+ * PDF page content item
117
+ */
118
+ export interface PdfPageItem {
119
+ pageNumber: number;
120
+ text: string;
121
+ images: Array<{
122
+ data: string;
123
+ mimeType: string;
124
+ }>;
125
+ }
126
+ /**
127
+ * Excel sheet metadata
128
+ */
129
+ export interface ExcelSheet {
130
+ /** Sheet name */
131
+ name: string;
132
+ /** Number of rows in sheet */
133
+ rowCount: number;
134
+ /** Number of columns in sheet */
135
+ colCount: number;
136
+ }
137
+ /**
138
+ * Result from edit operation (used by editRange)
139
+ */
140
+ export interface EditResult {
141
+ /** Whether all edits succeeded */
142
+ success: boolean;
143
+ /** Number of edits successfully applied */
144
+ editsApplied: number;
145
+ /** Errors that occurred during editing */
146
+ errors?: Array<{
147
+ location: string;
148
+ error: string;
149
+ }>;
150
+ }
151
+ /**
152
+ * File information and metadata
153
+ */
154
+ export interface FileInfo {
155
+ /** File size in bytes */
156
+ size: number;
157
+ /** Creation time */
158
+ created: Date;
159
+ /** Last modification time */
160
+ modified: Date;
161
+ /** Last access time */
162
+ accessed: Date;
163
+ /** Is this a directory */
164
+ isDirectory: boolean;
165
+ /** Is this a regular file */
166
+ isFile: boolean;
167
+ /** File permissions (octal string) */
168
+ permissions: string;
169
+ /** File type classification */
170
+ fileType: 'text' | 'excel' | 'image' | 'binary' | 'docx';
171
+ /** Type-specific metadata */
172
+ metadata?: FileMetadata;
173
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Base interfaces and types for file handling system
3
+ * All file handlers implement the FileHandler interface
4
+ */
5
+ export {};
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Binary file handler
3
+ * Handles binary files that aren't supported by other handlers (Excel, Image)
4
+ * Uses isBinaryFile for content-based detection
5
+ * Returns instructions to use start_process with appropriate tools
6
+ */
7
+ import { FileHandler, ReadOptions, FileResult, FileInfo } from './base.js';
8
+ /**
9
+ * Binary file handler implementation
10
+ * Uses content-based detection via isBinaryFile
11
+ */
12
+ export declare class BinaryFileHandler implements FileHandler {
13
+ canHandle(filePath: string): Promise<boolean>;
14
+ read(filePath: string, options?: ReadOptions): Promise<FileResult>;
15
+ write(path: string, content: any): Promise<void>;
16
+ getInfo(path: string): Promise<FileInfo>;
17
+ /**
18
+ * Generate instructions for handling binary files
19
+ */
20
+ private getBinaryInstructions;
21
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Binary file handler
3
+ * Handles binary files that aren't supported by other handlers (Excel, Image)
4
+ * Uses isBinaryFile for content-based detection
5
+ * Returns instructions to use start_process with appropriate tools
6
+ */
7
+ import fs from "fs/promises";
8
+ import path from "path";
9
+ import { isBinaryFile } from 'isbinaryfile';
10
+ /**
11
+ * Binary file handler implementation
12
+ * Uses content-based detection via isBinaryFile
13
+ */
14
+ export class BinaryFileHandler {
15
+ async canHandle(filePath) {
16
+ // Content-based binary detection using isBinaryFile
17
+ try {
18
+ return await isBinaryFile(filePath);
19
+ }
20
+ catch (error) {
21
+ // If we can't check (file doesn't exist, etc.), don't handle it
22
+ return false;
23
+ }
24
+ }
25
+ async read(filePath, options) {
26
+ const instructions = this.getBinaryInstructions(filePath);
27
+ return {
28
+ content: instructions,
29
+ mimeType: 'text/plain',
30
+ metadata: {
31
+ isBinary: true
32
+ }
33
+ };
34
+ }
35
+ async write(path, content) {
36
+ throw new Error('Cannot write binary files directly. Use start_process with appropriate tools (Python, Node.js libraries, command-line utilities).');
37
+ }
38
+ async getInfo(path) {
39
+ const stats = await fs.stat(path);
40
+ return {
41
+ size: stats.size,
42
+ created: stats.birthtime,
43
+ modified: stats.mtime,
44
+ accessed: stats.atime,
45
+ isDirectory: stats.isDirectory(),
46
+ isFile: stats.isFile(),
47
+ permissions: stats.mode.toString(8).slice(-3),
48
+ fileType: 'binary',
49
+ metadata: {
50
+ isBinary: true
51
+ }
52
+ };
53
+ }
54
+ /**
55
+ * Generate instructions for handling binary files
56
+ */
57
+ getBinaryInstructions(filePath) {
58
+ const fileName = path.basename(filePath);
59
+ return `Cannot read binary file as text: ${fileName}
60
+
61
+ Use start_process + interact_with_process to analyze binary files with appropriate tools (Node.js or Python libraries, command-line utilities, etc.).
62
+
63
+ The read_file tool only handles text files, images, and Excel files.`;
64
+ }
65
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * DOCX File Handler
3
+ *
4
+ * Approach: expose DOCX as filtered/raw XML through existing read_file + edit_block.
5
+ *
6
+ * READ (default): Returns a text-bearing outline — skips shapes, drawings, SVG noise.
7
+ * Shows paragraphs with text, tables with cell content, style info, and image refs.
8
+ * Each element shows its raw XML tag context so Claude can target it for editing.
9
+ *
10
+ * READ (with offset/length): Returns raw pretty-printed XML with line pagination,
11
+ * so Claude can drill into specific sections when the outline isn't enough.
12
+ *
13
+ * EDIT (old_string/new_string): Find/replace on the pretty-printed XML, then
14
+ * compact and repack into valid DOCX. Works exactly like text file editing.
15
+ *
16
+ * Round-trip: DOCX → unzip → pretty-print → [outline or raw] → edit → compact → repack
17
+ */
18
+ import { FileHandler, FileResult, FileInfo, ReadOptions, EditResult } from './base.js';
19
+ export declare class DocxFileHandler implements FileHandler {
20
+ private readonly extensions;
21
+ canHandle(path: string): boolean;
22
+ /**
23
+ * Read DOCX content.
24
+ *
25
+ * Default (offset=0, no explicit length or default length): returns outline
26
+ * With offset/length: returns raw pretty-printed XML with line pagination
27
+ */
28
+ read(path: string, options?: ReadOptions): Promise<FileResult>;
29
+ /**
30
+ * Write/create a DOCX file.
31
+ * Content is plain text — each line becomes a paragraph.
32
+ * Lines starting with # become headings (# = Heading1, ## = Heading2, etc.)
33
+ */
34
+ write(path: string, content: any, mode?: 'rewrite' | 'append'): Promise<void>;
35
+ /**
36
+ * Edit DOCX via find/replace on pretty-printed XML.
37
+ *
38
+ * Works on the same representation that read() returns when using offset/length,
39
+ * so XML fragments copied from read output work as search strings.
40
+ * After editing, XML is compacted and repacked into the DOCX.
41
+ */
42
+ editRange(path: string, _range: string, content: any, options?: Record<string, any>): Promise<EditResult>;
43
+ /**
44
+ * Get DOCX file info
45
+ */
46
+ getInfo(path: string): Promise<FileInfo>;
47
+ }