@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,86 @@
1
+ import { ServerResult } from '../types.js';
2
+ export interface ToolCallRecord {
3
+ timestamp: string;
4
+ toolName: string;
5
+ arguments: any;
6
+ output: ServerResult;
7
+ duration?: number;
8
+ }
9
+ interface FormattedToolCallRecord extends Omit<ToolCallRecord, 'timestamp'> {
10
+ timestamp: string;
11
+ }
12
+ declare class ToolHistory {
13
+ private history;
14
+ private readonly MAX_ENTRIES;
15
+ private readonly MAX_HISTORY_FILE_SIZE_BYTES;
16
+ private readonly HISTORY_FILE_TRIM_TARGET_BYTES;
17
+ private readonly historyFile;
18
+ private writeQueue;
19
+ private isWriting;
20
+ private writeInterval?;
21
+ constructor();
22
+ /**
23
+ * Load history from disk (all instances share the same file)
24
+ */
25
+ private loadFromDisk;
26
+ /**
27
+ * Trim the on-disk history file to stay under the size cap by dropping the
28
+ * oldest entries (lines) until the kept tail fits within the trim target.
29
+ * Returns true only when the file was actually rewritten with a smaller
30
+ * tail, so callers can fall through to their normal path on failure or
31
+ * no-op rather than mutating in-memory state.
32
+ *
33
+ * Always keeps at least the most recent entry, even if a single record
34
+ * exceeds the trim target — there is no useful state below that.
35
+ */
36
+ private trimHistoryFileIfTooLarge;
37
+ /**
38
+ * Trim history file to prevent it from growing indefinitely
39
+ */
40
+ private trimHistoryFile;
41
+ /**
42
+ * Async write processor - batches writes to avoid blocking
43
+ */
44
+ private startWriteProcessor;
45
+ /**
46
+ * Flush queued writes to disk
47
+ */
48
+ private flushToDisk;
49
+ /**
50
+ * Add a tool call to history
51
+ */
52
+ addCall(toolName: string, args: any, output: ServerResult, duration?: number): void;
53
+ /**
54
+ * Get recent tool calls with filters
55
+ */
56
+ getRecentCalls(options: {
57
+ maxResults?: number;
58
+ toolName?: string;
59
+ since?: string;
60
+ }): ToolCallRecord[];
61
+ /**
62
+ * Get recent calls formatted with local timezone
63
+ */
64
+ getRecentCallsFormatted(options: {
65
+ maxResults?: number;
66
+ toolName?: string;
67
+ since?: string;
68
+ }): FormattedToolCallRecord[];
69
+ /**
70
+ * Get current stats
71
+ */
72
+ getStats(): {
73
+ totalEntries: number;
74
+ oldestEntry: string;
75
+ newestEntry: string;
76
+ historyFile: string;
77
+ queuedWrites: number;
78
+ };
79
+ /**
80
+ * Cleanup method - clears interval and flushes pending writes
81
+ * Call this during shutdown or in tests
82
+ */
83
+ cleanup(): Promise<void>;
84
+ }
85
+ export declare const toolHistory: ToolHistory;
86
+ export {};
@@ -0,0 +1,257 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { CONFIG_DIR } from '../config.js';
4
+ // Format timestamp in local timezone for display
5
+ function formatLocalTimestamp(isoTimestamp) {
6
+ const date = new Date(isoTimestamp);
7
+ return date.toLocaleString('en-US', {
8
+ year: 'numeric',
9
+ month: '2-digit',
10
+ day: '2-digit',
11
+ hour: '2-digit',
12
+ minute: '2-digit',
13
+ second: '2-digit',
14
+ hour12: false
15
+ });
16
+ }
17
+ class ToolHistory {
18
+ constructor() {
19
+ this.history = [];
20
+ this.MAX_ENTRIES = 1000;
21
+ this.MAX_HISTORY_FILE_SIZE_BYTES = 5 * 1024 * 1024;
22
+ // When the file exceeds the cap we trim it down to this target instead of
23
+ // all the way to zero, so a single overflow doesn't cause every subsequent
24
+ // flush to re-trim.
25
+ this.HISTORY_FILE_TRIM_TARGET_BYTES = 4 * 1024 * 1024;
26
+ this.writeQueue = [];
27
+ this.isWriting = false;
28
+ // Store history under the canonical MCP Device state root.
29
+ const historyDir = path.join(CONFIG_DIR, 'logs');
30
+ // Ensure directory exists
31
+ if (!fs.existsSync(historyDir)) {
32
+ fs.mkdirSync(historyDir, { recursive: true });
33
+ }
34
+ // Use append-only JSONL format (JSON Lines).
35
+ this.historyFile = path.join(historyDir, 'history.jsonl');
36
+ // Load existing history on startup
37
+ this.loadFromDisk();
38
+ // Start async write processor
39
+ this.startWriteProcessor();
40
+ }
41
+ /**
42
+ * Load history from disk (all instances share the same file)
43
+ */
44
+ loadFromDisk() {
45
+ try {
46
+ if (!fs.existsSync(this.historyFile)) {
47
+ return;
48
+ }
49
+ // If the file is over the cap, trim it down before reading so we
50
+ // load a bounded amount.
51
+ this.trimHistoryFileIfTooLarge();
52
+ const content = fs.readFileSync(this.historyFile, 'utf-8');
53
+ const lines = content.trim().split('\n').filter(line => line.trim());
54
+ // Parse each line as JSON
55
+ const records = [];
56
+ for (const line of lines) {
57
+ try {
58
+ records.push(JSON.parse(line));
59
+ }
60
+ catch (e) {
61
+ // Silently skip invalid lines
62
+ }
63
+ }
64
+ // Keep only last 1000 entries
65
+ this.history = records.slice(-this.MAX_ENTRIES);
66
+ // If file is getting too large, trim it
67
+ if (lines.length > this.MAX_ENTRIES * 2) {
68
+ this.trimHistoryFile();
69
+ }
70
+ }
71
+ catch (error) {
72
+ // Silently fail
73
+ }
74
+ }
75
+ /**
76
+ * Trim the on-disk history file to stay under the size cap by dropping the
77
+ * oldest entries (lines) until the kept tail fits within the trim target.
78
+ * Returns true only when the file was actually rewritten with a smaller
79
+ * tail, so callers can fall through to their normal path on failure or
80
+ * no-op rather than mutating in-memory state.
81
+ *
82
+ * Always keeps at least the most recent entry, even if a single record
83
+ * exceeds the trim target — there is no useful state below that.
84
+ */
85
+ trimHistoryFileIfTooLarge() {
86
+ let stats;
87
+ try {
88
+ if (!fs.existsSync(this.historyFile)) {
89
+ return false;
90
+ }
91
+ stats = fs.statSync(this.historyFile);
92
+ if (stats.size <= this.MAX_HISTORY_FILE_SIZE_BYTES) {
93
+ return false;
94
+ }
95
+ }
96
+ catch (error) {
97
+ return false;
98
+ }
99
+ try {
100
+ const content = fs.readFileSync(this.historyFile, 'utf-8');
101
+ const lines = content.split('\n').filter(line => line.length > 0);
102
+ if (lines.length === 0) {
103
+ return false;
104
+ }
105
+ // Walk lines from newest to oldest, accumulating bytes (line + '\n'),
106
+ // and keep as many as fit within the trim target. Always keep at
107
+ // least the last line.
108
+ const kept = [];
109
+ let bytes = 0;
110
+ for (let i = lines.length - 1; i >= 0; i--) {
111
+ const lineBytes = Buffer.byteLength(lines[i], 'utf-8') + 1; // +1 for '\n'
112
+ if (kept.length > 0 && bytes + lineBytes > this.HISTORY_FILE_TRIM_TARGET_BYTES) {
113
+ break;
114
+ }
115
+ kept.push(lines[i]);
116
+ bytes += lineBytes;
117
+ }
118
+ kept.reverse();
119
+ fs.writeFileSync(this.historyFile, kept.join('\n') + '\n', 'utf-8');
120
+ return true;
121
+ }
122
+ catch (error) {
123
+ // Trim failed; do not claim the file was changed.
124
+ return false;
125
+ }
126
+ }
127
+ /**
128
+ * Trim history file to prevent it from growing indefinitely
129
+ */
130
+ trimHistoryFile() {
131
+ try {
132
+ // Keep last 1000 entries in memory
133
+ const keepEntries = this.history.slice(-this.MAX_ENTRIES);
134
+ // Write them back
135
+ const lines = keepEntries.map(entry => JSON.stringify(entry)).join('\n') + '\n';
136
+ fs.writeFileSync(this.historyFile, lines, 'utf-8');
137
+ }
138
+ catch (error) {
139
+ // Silently fail
140
+ }
141
+ }
142
+ /**
143
+ * Async write processor - batches writes to avoid blocking
144
+ */
145
+ startWriteProcessor() {
146
+ this.writeInterval = setInterval(() => {
147
+ if (this.writeQueue.length > 0 && !this.isWriting) {
148
+ this.flushToDisk();
149
+ }
150
+ }, 1000); // Flush every second
151
+ // Prevent interval from keeping process alive during shutdown/tests
152
+ this.writeInterval.unref();
153
+ }
154
+ /**
155
+ * Flush queued writes to disk
156
+ */
157
+ async flushToDisk() {
158
+ if (this.isWriting || this.writeQueue.length === 0)
159
+ return;
160
+ this.isWriting = true;
161
+ const toWrite = [...this.writeQueue];
162
+ this.writeQueue = [];
163
+ try {
164
+ // If the on-disk file has grown past the cap, trim it down to the
165
+ // target size (keeping the most recent entries) before appending.
166
+ // The in-memory cache is unaffected — it is already bounded by
167
+ // MAX_ENTRIES via addCall.
168
+ this.trimHistoryFileIfTooLarge();
169
+ // Append to file (atomic append operation)
170
+ const lines = toWrite.map(entry => JSON.stringify(entry)).join('\n') + '\n';
171
+ fs.appendFileSync(this.historyFile, lines, 'utf-8');
172
+ }
173
+ catch (error) {
174
+ // Put back in queue on failure
175
+ this.writeQueue.unshift(...toWrite);
176
+ }
177
+ finally {
178
+ this.isWriting = false;
179
+ }
180
+ }
181
+ /**
182
+ * Add a tool call to history
183
+ */
184
+ addCall(toolName, args, output, duration) {
185
+ const record = {
186
+ timestamp: new Date().toISOString(),
187
+ toolName,
188
+ arguments: args,
189
+ output,
190
+ duration
191
+ };
192
+ this.history.push(record);
193
+ // Keep only last 1000 in memory
194
+ if (this.history.length > this.MAX_ENTRIES) {
195
+ this.history.shift();
196
+ }
197
+ // Queue for async write
198
+ this.writeQueue.push(record);
199
+ }
200
+ /**
201
+ * Get recent tool calls with filters
202
+ */
203
+ getRecentCalls(options) {
204
+ let results = [...this.history];
205
+ // Filter by tool name
206
+ if (options.toolName) {
207
+ results = results.filter(r => r.toolName === options.toolName);
208
+ }
209
+ // Filter by timestamp
210
+ if (options.since) {
211
+ const sinceDate = new Date(options.since);
212
+ results = results.filter(r => new Date(r.timestamp) >= sinceDate);
213
+ }
214
+ // Limit results (default 50, max 1000)
215
+ const limit = Math.min(options.maxResults || 50, 1000);
216
+ return results.slice(-limit);
217
+ }
218
+ /**
219
+ * Get recent calls formatted with local timezone
220
+ */
221
+ getRecentCallsFormatted(options) {
222
+ const calls = this.getRecentCalls(options);
223
+ // Format timestamps to local timezone
224
+ return calls.map(call => ({
225
+ ...call,
226
+ timestamp: formatLocalTimestamp(call.timestamp)
227
+ }));
228
+ }
229
+ /**
230
+ * Get current stats
231
+ */
232
+ getStats() {
233
+ return {
234
+ totalEntries: this.history.length,
235
+ oldestEntry: this.history[0]?.timestamp,
236
+ newestEntry: this.history[this.history.length - 1]?.timestamp,
237
+ historyFile: this.historyFile,
238
+ queuedWrites: this.writeQueue.length
239
+ };
240
+ }
241
+ /**
242
+ * Cleanup method - clears interval and flushes pending writes
243
+ * Call this during shutdown or in tests
244
+ */
245
+ async cleanup() {
246
+ // Clear the interval
247
+ if (this.writeInterval) {
248
+ clearInterval(this.writeInterval);
249
+ this.writeInterval = undefined;
250
+ }
251
+ // Flush any remaining writes
252
+ if (this.writeQueue.length > 0) {
253
+ await this.flushToDisk();
254
+ }
255
+ }
256
+ }
257
+ export const toolHistory = new ToolHistory();
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Track tool calls and save them to a log file
3
+ * @param toolName Name of the tool being called
4
+ * @param args Arguments passed to the tool (optional)
5
+ */
6
+ export declare function trackToolCall(toolName: string, args?: unknown): Promise<void>;
@@ -0,0 +1,54 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { TOOL_CALL_FILE, TOOL_CALL_FILE_MAX_SIZE } from '../config.js';
4
+ // Ensure the directory for the log file exists
5
+ const logDir = path.dirname(TOOL_CALL_FILE);
6
+ await fs.promises.mkdir(logDir, { recursive: true });
7
+ /**
8
+ * Track tool calls and save them to a log file
9
+ * @param toolName Name of the tool being called
10
+ * @param args Arguments passed to the tool (optional)
11
+ */
12
+ export async function trackToolCall(toolName, args) {
13
+ try {
14
+ // Get current timestamp
15
+ const timestamp = new Date().toISOString();
16
+ // Format the log entry
17
+ const logEntry = `${timestamp} | ${toolName.padEnd(20, ' ')}${args ? `\t| Arguments: ${JSON.stringify(args)}` : ''}\n`;
18
+ // Check if file exists and get its size
19
+ let fileSize = 0;
20
+ try {
21
+ const stats = await fs.promises.stat(TOOL_CALL_FILE);
22
+ fileSize = stats.size;
23
+ }
24
+ catch (err) {
25
+ // File doesn't exist yet, size remains 0
26
+ }
27
+ // If file size is 10MB or larger, rotate the log file
28
+ if (fileSize >= TOOL_CALL_FILE_MAX_SIZE) {
29
+ const fileExt = path.extname(TOOL_CALL_FILE);
30
+ const fileBase = path.basename(TOOL_CALL_FILE, fileExt);
31
+ const dirName = path.dirname(TOOL_CALL_FILE);
32
+ // Create a timestamp-based filename for the old log
33
+ const date = new Date();
34
+ const rotateTimestamp = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}_${String(date.getHours()).padStart(2, '0')}-${String(date.getMinutes()).padStart(2, '0')}-${String(date.getSeconds()).padStart(2, '0')}`;
35
+ const newFileName = path.join(dirName, `${fileBase}_${rotateTimestamp}${fileExt}`);
36
+ // Rename the current file
37
+ await fs.promises.rename(TOOL_CALL_FILE, newFileName);
38
+ }
39
+ // Append to log file (if file was renamed, this will create a new file)
40
+ await fs.promises.appendFile(TOOL_CALL_FILE, logEntry, 'utf8');
41
+ }
42
+ catch (error) {
43
+ const errorMessage = error instanceof Error ? error.message : String(error);
44
+ const { capture } = await import('./capture.js');
45
+ // Send a final telemetry event noting that the user has opted out
46
+ // This helps us track opt-out rates while respecting the user's choice
47
+ await capture('server_track_tool_call_error', {
48
+ error: errorMessage,
49
+ toolName
50
+ });
51
+ // Don't let logging errors affect the main functionality
52
+ console.error(`Error logging tool call: ${error instanceof Error ? error.message : String(error)}`);
53
+ }
54
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Utility for detecting parameters a caller sent that a tool's Zod schema does
3
+ * not accept.
4
+ *
5
+ * Zod's default `z.object()` silently strips unknown keys, so a model that
6
+ * invents a parameter (e.g. `view_range`) gets a normal-looking response with no
7
+ * signal that its input was ignored. These helpers let the dispatcher surface a
8
+ * corrective warning instead.
9
+ *
10
+ * Detection is top-level only. That is sufficient here because none of the tool
11
+ * arg schemas use a top-level `.passthrough()`/catchall — free-form fields
12
+ * (`options`, `params`, `pdfOptions`) are named keys whose contents are
13
+ * intentionally unvalidated.
14
+ */
15
+ /** List the parameter names a tool schema accepts (empty array if it takes none). */
16
+ export declare function getSupportedParams(schema: unknown): string[];
17
+ /**
18
+ * Return the names of top-level keys in `args` that the schema does not accept.
19
+ * Returns [] when args is not a plain object or the schema's params can't be
20
+ * determined (so we never warn on something we can't reason about).
21
+ */
22
+ export declare function detectUnsupportedParams(args: unknown, schema: unknown): string[];
23
+ /** Build the corrective warning string shown to the model. */
24
+ export declare function buildUnsupportedParamsWarning(toolName: string, unsupported: string[], supported: string[]): string;
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Utility for detecting parameters a caller sent that a tool's Zod schema does
3
+ * not accept.
4
+ *
5
+ * Zod's default `z.object()` silently strips unknown keys, so a model that
6
+ * invents a parameter (e.g. `view_range`) gets a normal-looking response with no
7
+ * signal that its input was ignored. These helpers let the dispatcher surface a
8
+ * corrective warning instead.
9
+ *
10
+ * Detection is top-level only. That is sufficient here because none of the tool
11
+ * arg schemas use a top-level `.passthrough()`/catchall — free-form fields
12
+ * (`options`, `params`, `pdfOptions`) are named keys whose contents are
13
+ * intentionally unvalidated.
14
+ */
15
+ /**
16
+ * Resolve a Zod schema down to the field map of its underlying object schema,
17
+ * unwrapping wrappers (effects/optional/default/nullable/branded) defensively.
18
+ * Returns null when the schema is not (and does not wrap) a plain object schema,
19
+ * in which case we cannot determine the supported parameters and must not warn.
20
+ */
21
+ function getObjectShape(schema) {
22
+ let current = schema;
23
+ for (let i = 0; current && current._def && i < 10; i++) {
24
+ const typeName = current._def.typeName;
25
+ if (typeName === 'ZodObject') {
26
+ const shape = typeof current.shape === 'function' ? current.shape() : current.shape;
27
+ return shape ?? null;
28
+ }
29
+ if (typeName === 'ZodEffects')
30
+ current = current._def.schema;
31
+ else if (typeName === 'ZodOptional' || typeName === 'ZodNullable' || typeName === 'ZodDefault') {
32
+ current = typeof current._def.innerType === 'function' ? current._def.innerType() : current._def.innerType;
33
+ }
34
+ else if (typeName === 'ZodBranded')
35
+ current = current._def.type;
36
+ else
37
+ return null;
38
+ }
39
+ return null;
40
+ }
41
+ /** List the parameter names a tool schema accepts (empty array if it takes none). */
42
+ export function getSupportedParams(schema) {
43
+ const shape = getObjectShape(schema);
44
+ return shape ? Object.keys(shape) : [];
45
+ }
46
+ /**
47
+ * Return the names of top-level keys in `args` that the schema does not accept.
48
+ * Returns [] when args is not a plain object or the schema's params can't be
49
+ * determined (so we never warn on something we can't reason about).
50
+ */
51
+ export function detectUnsupportedParams(args, schema) {
52
+ if (!args || typeof args !== 'object' || Array.isArray(args))
53
+ return [];
54
+ const shape = getObjectShape(schema);
55
+ if (shape === null)
56
+ return [];
57
+ const supported = new Set(Object.keys(shape));
58
+ return Object.keys(args).filter((k) => !supported.has(k));
59
+ }
60
+ /** Build the corrective warning string shown to the model. */
61
+ export function buildUnsupportedParamsWarning(toolName, unsupported, supported) {
62
+ const ignored = unsupported.join(', ');
63
+ const supportedList = supported.length > 0 ? supported.join(', ') : '(none)';
64
+ return `You sent parameters not supported by this tool, which were ignored: ${ignored}. `
65
+ + `Supported parameters for ${toolName}: ${supportedList}.`;
66
+ }
@@ -0,0 +1,133 @@
1
+ export interface ToolUsageStats {
2
+ filesystemOperations: number;
3
+ terminalOperations: number;
4
+ editOperations: number;
5
+ searchOperations: number;
6
+ configOperations: number;
7
+ processOperations: number;
8
+ totalToolCalls: number;
9
+ successfulCalls: number;
10
+ failedCalls: number;
11
+ toolCounts: Record<string, number>;
12
+ firstUsed: number;
13
+ lastUsed: number;
14
+ totalSessions: number;
15
+ lastFeedbackPrompt: number;
16
+ lastFeedbackPromptDate?: string;
17
+ feedbackAttempts?: number;
18
+ }
19
+ export interface OnboardingState {
20
+ promptsUsed: boolean;
21
+ attemptsShown: number;
22
+ lastShownAt: number;
23
+ }
24
+ export interface UsageSession {
25
+ sessionStart: number;
26
+ lastActivity: number;
27
+ commandsInSession: number;
28
+ promptedThisSession: boolean;
29
+ }
30
+ declare class UsageTracker {
31
+ private currentSession;
32
+ /**
33
+ * Get default usage stats
34
+ */
35
+ private getDefaultStats;
36
+ /**
37
+ * Get current usage stats from config
38
+ */
39
+ getStats(): Promise<ToolUsageStats>;
40
+ /**
41
+ * Save usage stats to config.
42
+ * Non-blocking: the tool-call return path must not wait on a disk write. The
43
+ * in-memory stats are already updated by the caller (getStats returns the live
44
+ * config object), so persistence is coalesced in the background. This is what
45
+ * keeps a saturated libuv threadpool from gating tool responses on every call.
46
+ */
47
+ private saveStats;
48
+ /**
49
+ * Determine which category a tool belongs to
50
+ */
51
+ private getToolCategory;
52
+ /**
53
+ * Check if we're in a new session
54
+ */
55
+ private isNewSession;
56
+ /**
57
+ * Update session tracking
58
+ */
59
+ private updateSession;
60
+ /**
61
+ * Track a successful tool call
62
+ */
63
+ trackSuccess(toolName: string): Promise<ToolUsageStats>;
64
+ /**
65
+ * Track a failed tool call
66
+ */
67
+ trackFailure(toolName: string): Promise<ToolUsageStats>;
68
+ /**
69
+ * Check if user should be prompted for feedback based on usage patterns
70
+ */
71
+ shouldPromptForFeedback(): Promise<boolean>;
72
+ /**
73
+ * Get a random feedback prompt message with strong CTAs and clear actions
74
+ * NEW 2025 Survey - 3 variants for fresh analytics tracking
75
+ */
76
+ getFeedbackPromptMessage(): Promise<{
77
+ variant: string;
78
+ message: string;
79
+ }>;
80
+ /**
81
+ * Check if user should be prompted for error feedback
82
+ */
83
+ shouldPromptForErrorFeedback(): Promise<boolean>;
84
+ /**
85
+ * Mark that user was prompted for feedback
86
+ */
87
+ markFeedbackPrompted(): Promise<void>;
88
+ /**
89
+ * Mark that user has given feedback
90
+ */
91
+ markFeedbackGiven(): Promise<void>;
92
+ /**
93
+ * Get usage summary for debugging/admin purposes
94
+ */
95
+ getUsageSummary(): Promise<string>;
96
+ /**
97
+ * Get onboarding state from config
98
+ */
99
+ getOnboardingState(): Promise<OnboardingState>;
100
+ /**
101
+ * Save onboarding state to config
102
+ */
103
+ saveOnboardingState(state: OnboardingState): Promise<void>;
104
+ /**
105
+ * Check if user should see onboarding invitation - SIMPLE VERSION
106
+ */
107
+ shouldShowOnboarding(): Promise<boolean>;
108
+ /**
109
+ * Get onboarding message for new users - DIRECT 5-OPTION LIST (V2)
110
+ */
111
+ getOnboardingMessage(): Promise<{
112
+ variant: string;
113
+ message: string;
114
+ }>;
115
+ /**
116
+ * Mark that onboarding message was shown - SIMPLE VERSION
117
+ */
118
+ markOnboardingShown(variant: string): Promise<void>;
119
+ /**
120
+ * Mark that user used prompts after seeing onboarding invitation - SIMPLE VERSION
121
+ */
122
+ markOnboardingPromptsUsed(): Promise<void>;
123
+ /**
124
+ * Mark that user has used a specific prompt (for analytics)
125
+ */
126
+ markPromptUsed(promptId: string, category: string): Promise<void>;
127
+ /**
128
+ * Reset onboarding state for testing purposes - SIMPLE VERSION
129
+ */
130
+ resetOnboardingState(): Promise<void>;
131
+ }
132
+ export declare const usageTracker: UsageTracker;
133
+ export {};