@hcu-lab.me/mcp-device 1.0.5 → 1.0.7

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 (57) hide show
  1. package/README.md +7 -71
  2. package/dist/bootstrap.js +1 -1
  3. package/dist/config-manager.js +56 -19
  4. package/dist/config.js +1 -1
  5. package/dist/custom-stdio.js +1 -2
  6. package/dist/data/onboarding-prompts.json +22 -11
  7. package/dist/device/device-state.d.ts +2 -5
  8. package/dist/device/device-state.js +3 -85
  9. package/dist/device/device.d.ts +1 -1
  10. package/dist/device/device.js +69 -9
  11. package/dist/device/execution-engine.d.ts +11 -1
  12. package/dist/device/execution-engine.js +85 -126
  13. package/dist/device/gateway-channel.d.ts +19 -1
  14. package/dist/device/gateway-channel.js +107 -21
  15. package/dist/device/gateway-identity.d.ts +1 -0
  16. package/dist/device/gateway-identity.js +4 -0
  17. package/dist/device/gateway-tool-adapter.d.ts +3 -3
  18. package/dist/device/gateway-tool-adapter.js +12 -11
  19. package/dist/device/linux-service.d.ts +6 -0
  20. package/dist/device/linux-service.js +12 -5
  21. package/dist/device/runtime-owner.d.ts +1 -0
  22. package/dist/device/runtime-owner.js +6 -6
  23. package/dist/device/self-update.d.ts +107 -12
  24. package/dist/device/self-update.js +660 -62
  25. package/dist/device/update-helper.cjs +343 -0
  26. package/dist/device/windows-service.d.ts +3 -4
  27. package/dist/device/windows-service.js +5 -44
  28. package/dist/handlers/filesystem-handlers.js +3 -0
  29. package/dist/index.js +7 -3
  30. package/dist/npm-scripts/remote.d.ts +5 -0
  31. package/dist/npm-scripts/remote.js +50 -3
  32. package/dist/server.js +83 -167
  33. package/dist/terminal-manager.js +41 -1
  34. package/dist/tools/config.d.ts +1 -1
  35. package/dist/tools/filesystem.js +3 -3
  36. package/dist/tools/pdf/markdown.js +1 -1
  37. package/dist/tools/schemas.d.ts +2 -3
  38. package/dist/tools/schemas.js +0 -10
  39. package/dist/ui/config-editor/index.html +1 -1
  40. package/dist/ui/file-preview/index.html +1 -1
  41. package/dist/ui/file-preview/styles.css +2 -2
  42. package/dist/utils/capture.d.ts +5 -28
  43. package/dist/utils/capture.js +11 -455
  44. package/dist/utils/feature-flags.d.ts +0 -47
  45. package/dist/utils/feature-flags.js +5 -199
  46. package/dist/utils/files/docx.d.ts +2 -2
  47. package/dist/utils/files/docx.js +2 -2
  48. package/dist/utils/files/factory.js +20 -0
  49. package/dist/utils/files/text.d.ts +1 -0
  50. package/dist/utils/files/text.js +78 -34
  51. package/dist/utils/trackTools.js +23 -2
  52. package/dist/utils/usageTracker.js +2 -24
  53. package/dist/version.d.ts +1 -1
  54. package/dist/version.js +1 -1
  55. package/package.json +6 -7
  56. package/dist/tools/feedback.d.ts +0 -8
  57. package/dist/tools/feedback.js +0 -132
@@ -1,215 +1,21 @@
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
1
  class FeatureFlagManager {
7
2
  constructor() {
8
3
  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
4
  }
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
- */
5
+ async initialize() { }
64
6
  get(flagName, defaultValue = false) {
65
7
  return this.flags[flagName] !== undefined ? this.flags[flagName] : defaultValue;
66
8
  }
67
- /**
68
- * Get all flags for debugging
69
- */
70
9
  getAll() {
71
10
  return { ...this.flags };
72
11
  }
73
- /**
74
- * Manually refresh flags immediately (for testing)
75
- */
76
12
  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
- }
13
+ return true;
85
14
  }
86
- /**
87
- * Check if flags were loaded from cache (vs fresh fetch)
88
- */
89
15
  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
- }
16
+ return false;
212
17
  }
18
+ async waitForFreshFlags() { }
19
+ destroy() { }
213
20
  }
214
- // Export singleton instance
215
21
  export const featureFlagManager = new FeatureFlagManager();
@@ -5,10 +5,10 @@
5
5
  *
6
6
  * READ (default): Returns a text-bearing outline — skips shapes, drawings, SVG noise.
7
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.
8
+ * Each element shows its raw XML tag context so an agent can target it for editing.
9
9
  *
10
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.
11
+ * so an agent can drill into specific sections when the outline isn't enough.
12
12
  *
13
13
  * EDIT (old_string/new_string): Find/replace on the pretty-printed XML, then
14
14
  * compact and repack into valid DOCX. Works exactly like text file editing.
@@ -5,10 +5,10 @@
5
5
  *
6
6
  * READ (default): Returns a text-bearing outline — skips shapes, drawings, SVG noise.
7
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.
8
+ * Each element shows its raw XML tag context so an agent can target it for editing.
9
9
  *
10
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.
11
+ * so an agent can drill into specific sections when the outline isn't enough.
12
12
  *
13
13
  * EDIT (old_string/new_string): Find/replace on the pretty-printed XML, then
14
14
  * compact and repack into valid DOCX. Works exactly like text file editing.
@@ -5,6 +5,7 @@
5
5
  * Each handler implements canHandle() which can be sync (extension-based)
6
6
  * or async (content-based like BinaryFileHandler using isBinaryFile)
7
7
  */
8
+ import fs from 'fs/promises';
8
9
  import { TextFileHandler } from './text.js';
9
10
  import { ImageFileHandler } from './image.js';
10
11
  import { BinaryFileHandler } from './binary.js';
@@ -86,6 +87,25 @@ export async function getFileHandler(filePath) {
86
87
  if (getImageHandler().canHandle(filePath)) {
87
88
  return getImageHandler();
88
89
  }
90
+ // UTF-16LE text contains many NUL bytes and generic binary sniffers
91
+ // often classify it as binary. A BOM is authoritative, so route it to the
92
+ // text handler before content-based binary detection.
93
+ try {
94
+ const fd = await fs.open(filePath, 'r');
95
+ try {
96
+ const bom = Buffer.alloc(2);
97
+ const { bytesRead } = await fd.read(bom, 0, bom.length, 0);
98
+ if (bytesRead === 2 && bom[0] === 0xff && bom[1] === 0xfe) {
99
+ return getTextHandler();
100
+ }
101
+ }
102
+ finally {
103
+ await fd.close();
104
+ }
105
+ }
106
+ catch {
107
+ // Preserve existing fallback behavior if the file cannot be sampled.
108
+ }
89
109
  // Check Binary (content-based, async via isBinaryFile)
90
110
  if (await getBinaryHandler().canHandle(filePath)) {
91
111
  return getBinaryHandler();
@@ -19,6 +19,7 @@ import { FileHandler, ReadOptions, FileResult, FileInfo } from './base.js';
19
19
  */
20
20
  export declare class TextFileHandler implements FileHandler {
21
21
  canHandle(_path: string): boolean;
22
+ private detectTextEncoding;
22
23
  read(filePath: string, options?: ReadOptions): Promise<FileResult>;
23
24
  write(path: string, content: string, mode?: 'rewrite' | 'append'): Promise<void>;
24
25
  getInfo(path: string): Promise<FileInfo>;
@@ -38,6 +38,17 @@ export class TextFileHandler {
38
38
  // The factory routes binary files to BinaryFileHandler before reaching here
39
39
  return true;
40
40
  }
41
+ async detectTextEncoding(filePath) {
42
+ const fd = await fs.open(filePath, 'r');
43
+ try {
44
+ const bom = Buffer.alloc(2);
45
+ const { bytesRead } = await fd.read(bom, 0, bom.length, 0);
46
+ return bytesRead === 2 && bom[0] === 0xff && bom[1] === 0xfe ? 'utf16le' : 'utf8';
47
+ }
48
+ finally {
49
+ await fd.close();
50
+ }
51
+ }
41
52
  async read(filePath, options) {
42
53
  const offset = options?.offset ?? 0;
43
54
  const length = options?.length ?? 1000; // Default from config
@@ -69,7 +80,11 @@ export class TextFileHandler {
69
80
  // For text files that aren't too large, count lines
70
81
  if (stats.isFile() && stats.size < FILE_SIZE_LIMITS.LINE_COUNT_LIMIT) {
71
82
  try {
72
- const content = await fs.readFile(path, 'utf8');
83
+ const encoding = await this.detectTextEncoding(path);
84
+ let content = await fs.readFile(path, encoding);
85
+ if (encoding === 'utf16le' && content.charCodeAt(0) === 0xfeff) {
86
+ content = content.slice(1);
87
+ }
73
88
  const lineCount = TextFileHandler.countLines(content);
74
89
  info.metadata.lineCount = lineCount;
75
90
  }
@@ -100,11 +115,14 @@ export class TextFileHandler {
100
115
  /**
101
116
  * Get file line count (for files under size limit)
102
117
  */
103
- async getFileLineCount(filePath, signal) {
118
+ async getFileLineCount(filePath, signal, encoding = 'utf8') {
104
119
  try {
105
120
  const stats = await fs.stat(filePath);
106
121
  if (stats.size < FILE_SIZE_LIMITS.LINE_COUNT_LIMIT) {
107
- const content = await fs.readFile(filePath, { encoding: 'utf8', signal });
122
+ let content = await fs.readFile(filePath, { encoding, signal });
123
+ if (encoding === 'utf16le' && content.charCodeAt(0) === 0xfeff) {
124
+ content = content.slice(1);
125
+ }
108
126
  return TextFileHandler.countLines(content);
109
127
  }
110
128
  }
@@ -182,7 +200,16 @@ export class TextFileHandler {
182
200
  async readFileWithSmartPositioning(filePath, offset, length, mimeType, includeStatusMessage = true, signal) {
183
201
  const stats = await fs.stat(filePath);
184
202
  const fileSize = stats.size;
185
- const totalLines = await this.getFileLineCount(filePath, signal);
203
+ const encoding = await this.detectTextEncoding(filePath);
204
+ const totalLines = await this.getFileLineCount(filePath, signal, encoding);
205
+ // UTF-16LE needs character-aware streaming. Skip the BOM and preserve
206
+ // the same line pagination/status semantics as UTF-8.
207
+ if (encoding === 'utf16le') {
208
+ if (offset < 0) {
209
+ return await this.readFromEndWithReadline(filePath, Math.abs(offset), mimeType, includeStatusMessage, totalLines, signal, 'utf16le', 2);
210
+ }
211
+ return await this.readFromStartWithReadline(filePath, offset, length, mimeType, includeStatusMessage, totalLines, signal, 'utf16le', 2);
212
+ }
186
213
  // For negative offsets (tail behavior), use reverse reading
187
214
  if (offset < 0) {
188
215
  const requestedLines = Math.abs(offset);
@@ -252,9 +279,9 @@ export class TextFileHandler {
252
279
  /**
253
280
  * Read from end using readline with circular buffer
254
281
  */
255
- async readFromEndWithReadline(filePath, requestedLines, mimeType, includeStatusMessage = true, fileTotalLines, signal) {
282
+ async readFromEndWithReadline(filePath, requestedLines, mimeType, includeStatusMessage = true, fileTotalLines, signal, encoding, start) {
256
283
  const rl = createInterface({
257
- input: createReadStream(filePath, { signal }),
284
+ input: createReadStream(filePath, { signal, encoding, start }),
258
285
  crlfDelay: Infinity
259
286
  });
260
287
  const buffer = new Array(requestedLines);
@@ -284,22 +311,28 @@ export class TextFileHandler {
284
311
  /**
285
312
  * Read from start/middle using readline
286
313
  */
287
- async readFromStartWithReadline(filePath, offset, length, mimeType, includeStatusMessage = true, fileTotalLines, signal) {
314
+ async readFromStartWithReadline(filePath, offset, length, mimeType, includeStatusMessage = true, fileTotalLines, signal, encoding, start) {
315
+ const stream = createReadStream(filePath, { signal, encoding, start });
288
316
  const rl = createInterface({
289
- input: createReadStream(filePath, { signal }),
317
+ input: stream,
290
318
  crlfDelay: Infinity
291
319
  });
292
320
  const result = [];
293
321
  let lineNumber = 0;
294
- for await (const line of rl) {
295
- if (lineNumber >= offset && result.length < length) {
296
- result.push(line);
322
+ try {
323
+ for await (const line of rl) {
324
+ if (lineNumber >= offset && result.length < length) {
325
+ result.push(line);
326
+ }
327
+ if (result.length >= length)
328
+ break;
329
+ lineNumber++;
297
330
  }
298
- if (result.length >= length)
299
- break;
300
- lineNumber++;
301
331
  }
302
- rl.close();
332
+ finally {
333
+ rl.close();
334
+ stream.destroy();
335
+ }
303
336
  if (includeStatusMessage) {
304
337
  const statusMessage = this.generateEnhancedStatusMessage(result.length, offset, fileTotalLines, false);
305
338
  const content = `${statusMessage}\n\n${result.join('\n')}`;
@@ -314,20 +347,26 @@ export class TextFileHandler {
314
347
  * Read from estimated byte position for very large files
315
348
  */
316
349
  async readFromEstimatedPosition(filePath, offset, length, mimeType, includeStatusMessage = true, fileTotalLines, signal) {
317
- // First, do a quick scan to estimate lines per byte
350
+ // First, do a quick scan to estimate lines per byte.
351
+ const sampleStream = createReadStream(filePath, { signal });
318
352
  const rl = createInterface({
319
- input: createReadStream(filePath, { signal }),
353
+ input: sampleStream,
320
354
  crlfDelay: Infinity
321
355
  });
322
356
  let sampleLines = 0;
323
357
  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;
358
+ try {
359
+ for await (const line of rl) {
360
+ bytesRead += Buffer.byteLength(line, 'utf-8') + 1;
361
+ sampleLines++;
362
+ if (bytesRead >= READ_PERFORMANCE_THRESHOLDS.SAMPLE_SIZE)
363
+ break;
364
+ }
365
+ }
366
+ finally {
367
+ rl.close();
368
+ sampleStream.destroy();
329
369
  }
330
- rl.close();
331
370
  if (sampleLines === 0) {
332
371
  return await this.readFromStartWithReadline(filePath, offset, length, mimeType, includeStatusMessage, fileTotalLines, signal);
333
372
  }
@@ -345,19 +384,24 @@ export class TextFileHandler {
345
384
  });
346
385
  const result = [];
347
386
  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;
387
+ try {
388
+ for await (const line of rl2) {
389
+ if (!firstLineSkipped && startPosition > 0) {
390
+ firstLineSkipped = true;
391
+ continue;
392
+ }
393
+ if (result.length < length) {
394
+ result.push(line);
395
+ }
396
+ else {
397
+ break;
398
+ }
358
399
  }
359
400
  }
360
- rl2.close();
401
+ finally {
402
+ rl2.close();
403
+ stream.destroy();
404
+ }
361
405
  const content = includeStatusMessage
362
406
  ? `${this.generateEnhancedStatusMessage(result.length, offset, fileTotalLines, false)}\n\n${result.join('\n')}`
363
407
  : result.join('\n');
@@ -13,8 +13,29 @@ export async function trackToolCall(toolName, args) {
13
13
  try {
14
14
  // Get current timestamp
15
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`;
16
+ // Remote MCP Device calls may contain commands, file contents, tokens or
17
+ // other secrets. Persist only bounded metadata in remote mode.
18
+ const remote = process.env.MCP_DEVICE_REMOTE === 'true';
19
+ let suffix = '';
20
+ if (args !== undefined) {
21
+ if (remote) {
22
+ let argBytes = 0;
23
+ let argCount = 0;
24
+ try {
25
+ const serialized = JSON.stringify(args);
26
+ argBytes = Buffer.byteLength(serialized || '', 'utf8');
27
+ argCount = args && typeof args === 'object' && !Array.isArray(args)
28
+ ? Object.keys(args).length
29
+ : 1;
30
+ }
31
+ catch { /* metadata-only logging must never affect tool execution */ }
32
+ suffix = `\t| Args: count=${argCount} bytes=${argBytes}`;
33
+ }
34
+ else {
35
+ suffix = `\t| Arguments: ${JSON.stringify(args)}`;
36
+ }
37
+ }
38
+ const logEntry = `${timestamp} | ${toolName.padEnd(20, ' ')}${suffix}\n`;
18
39
  // Check if file exists and get its size
19
40
  let fileSize = 0;
20
41
  try {
@@ -52,6 +52,8 @@ class UsageTracker {
52
52
  * keeps a saturated libuv threadpool from gating tool responses on every call.
53
53
  */
54
54
  async saveStats(stats) {
55
+ if (process.env.MCP_DEVICE_REMOTE === 'true')
56
+ return;
55
57
  await configManager.setValueNonBlocking('usageStats', stats);
56
58
  }
57
59
  /**
@@ -160,18 +162,6 @@ class UsageTracker {
160
162
  if (!feedbackEnabled) {
161
163
  return false;
162
164
  }
163
- // Check if client is desktop-commander (disable for this client)
164
- //return true;
165
- try {
166
- const { currentClient } = await import('../server.js');
167
- if (currentClient?.name === 'desktop-commander') {
168
- return false;
169
- }
170
- }
171
- catch (error) {
172
- // If we can't import server, continue with other checks
173
- console.log('[FEEDBACK DEBUG] Could not check client name, continuing...');
174
- }
175
165
  const stats = await this.getStats();
176
166
  // Don't prompt if feedback already given (check top-level config)
177
167
  const feedbackGiven = await configManager.getValue('feedbackGiven');
@@ -337,7 +327,6 @@ class UsageTracker {
337
327
  // within seconds, so when the flag is ON onboarding still fires on a
338
328
  // later call while the user is new. Deliberately no waiting here to keep
339
329
  // tool calls latency-free.
340
- // See: https://github.com/wonderwhy-er/DesktopCommanderMCP/issues/538
341
330
  const onboardingEnabled = featureFlagManager.get('onboarding_injection', false);
342
331
  if (!onboardingEnabled) {
343
332
  return false;
@@ -351,17 +340,6 @@ class UsageTracker {
351
340
  if (global.disableOnboarding) {
352
341
  return false;
353
342
  }
354
- // Check if client is desktop-commander (disable for this client)
355
- try {
356
- const { currentClient } = await import('../server.js');
357
- if (currentClient?.name === 'desktop-commander') {
358
- return false;
359
- }
360
- }
361
- catch (error) {
362
- // If we can't import server, continue with other checks
363
- console.log('[ONBOARDING DEBUG] Could not check client name, continuing...');
364
- }
365
343
  const stats = await this.getStats();
366
344
  const onboardingState = await this.getOnboardingState();
367
345
  const now = Date.now();
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "1.0.5";
1
+ export declare const VERSION = "1.0.7";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = '1.0.5';
1
+ export const VERSION = '1.0.7';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hcu-lab.me/mcp-device",
3
- "version": "1.0.5",
4
- "description": "MCP Device securely connects a user-owned computer to an authenticated Model Context Protocol gateway.",
3
+ "version": "1.0.7",
4
+ "description": "MCP Device runtime.",
5
5
  "license": "MIT",
6
6
  "author": "Hirohimeow",
7
7
  "homepage": "https://github.com/Hiroshimeow/agent-mcp-device",
@@ -26,18 +26,18 @@
26
26
  "device:start": "tsx src/device/device.ts",
27
27
  "device:start:dev": "nodemon --watch src/device --exec tsx src/device/device.ts",
28
28
  "sync-version": "node scripts/sync-version.js",
29
- "version": "node scripts/sync-version.js && git add server.json src/version.ts package-lock.json",
29
+ "version": "node scripts/sync-version.js && git add src/version.ts package-lock.json",
30
30
  "release": "node scripts/release.js patch",
31
31
  "release:minor": "node scripts/release.js minor",
32
32
  "release:major": "node scripts/release.js major",
33
33
  "bump": "node scripts/sync-version.js --bump",
34
34
  "bump:minor": "node scripts/sync-version.js --bump --minor",
35
35
  "bump:major": "node scripts/sync-version.js --bump --major",
36
- "build": "shx rm -rf dist/remote-device && shx rm -f dist/device/desktop-commander-integration.js dist/device/desktop-commander-integration.d.ts dist/device/windows-dpapi.js dist/device/windows-dpapi.d.ts && tsc && shx chmod +x dist/*.js && shx mkdir -p dist/data && shx cp src/data/onboarding-prompts.json dist/data/ && node scripts/copy-official-ca.cjs && node scripts/build-ui-runtime.cjs",
36
+ "build": "shx rm -rf dist && tsc && shx chmod +x dist/*.js && shx cp src/device/update-helper.cjs dist/device/update-helper.cjs && shx mkdir -p dist/data && shx cp src/data/onboarding-prompts.json dist/data/ && node scripts/copy-official-ca.cjs && node scripts/build-ui-runtime.cjs",
37
37
  "watch": "tsc --watch",
38
38
  "start": "node dist/mcp-device.js",
39
39
  "start:debug": "node --inspect-brk=9229 dist/mcp-device.js",
40
- "prepack": "node scripts/verify-production-trust.cjs && shx rm -rf dist/remote-device && shx rm -f dist/device/desktop-commander-integration.js dist/device/desktop-commander-integration.d.ts dist/device/windows-dpapi.js dist/device/windows-dpapi.d.ts dist/hcu-device.js dist/hcu-device.d.ts dist/track-installation.js dist/setup-claude-server.js dist/uninstall-claude-server.js dist/npm-scripts/setup.js dist/npm-scripts/setup.d.ts dist/npm-scripts/uninstall.js dist/npm-scripts/uninstall.d.ts dist/npm-scripts/verify-ripgrep.js dist/npm-scripts/verify-ripgrep.d.ts",
40
+ "prepack": "node scripts/verify-production-trust.cjs",
41
41
  "prepare": "npm run build",
42
42
  "clean": "shx rm -rf dist",
43
43
  "test": "npm run build && node test/run-all-tests.js",
@@ -57,7 +57,6 @@
57
57
  "mcp",
58
58
  "model-context-protocol",
59
59
  "terminal",
60
- "claude",
61
60
  "ai",
62
61
  "command-line",
63
62
  "process-management",
@@ -73,7 +72,7 @@
73
72
  "file-operations"
74
73
  ],
75
74
  "dependencies": {
76
- "@modelcontextprotocol/sdk": "^1.9.0",
75
+ "@modelcontextprotocol/sdk": "1.30.0",
77
76
  "@opendocsg/pdf2md": "^0.2.2",
78
77
  "@tiptap/core": "^3.31.3",
79
78
  "@tiptap/extension-image": "^3.31.3",
@@ -1,8 +0,0 @@
1
- import { ServerResult } from '../types.js';
2
- interface FeedbackParams {
3
- }
4
- /**
5
- * Open feedback form in browser with optional pre-filled data
6
- */
7
- export declare function giveFeedbackToDesktopCommander(params?: FeedbackParams): Promise<ServerResult>;
8
- export {};