@wonderwhy-er/desktop-commander 0.2.36 → 0.2.38

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 (94) hide show
  1. package/README.md +240 -100
  2. package/dist/command-manager.js +6 -3
  3. package/dist/config-field-definitions.d.ts +41 -0
  4. package/dist/config-field-definitions.js +37 -0
  5. package/dist/config-manager.d.ts +2 -0
  6. package/dist/config-manager.js +22 -2
  7. package/dist/handlers/filesystem-handlers.js +6 -11
  8. package/dist/handlers/macos-control-handlers.d.ts +16 -0
  9. package/dist/handlers/macos-control-handlers.js +81 -0
  10. package/dist/lib.d.ts +10 -0
  11. package/dist/lib.js +10 -0
  12. package/dist/remote-device/remote-channel.d.ts +8 -3
  13. package/dist/remote-device/remote-channel.js +68 -21
  14. package/dist/search-manager.d.ts +13 -0
  15. package/dist/search-manager.js +146 -0
  16. package/dist/server.js +29 -1
  17. package/dist/test-docx.d.ts +1 -0
  18. package/dist/tools/config.d.ts +71 -0
  19. package/dist/tools/config.js +117 -2
  20. package/dist/tools/docx/builders/table.d.ts +2 -0
  21. package/dist/tools/docx/builders/table.js +60 -16
  22. package/dist/tools/docx/dom.d.ts +74 -1
  23. package/dist/tools/docx/dom.js +221 -1
  24. package/dist/tools/docx/index.d.ts +2 -2
  25. package/dist/tools/docx/ops/index.js +3 -0
  26. package/dist/tools/docx/ops/replace-paragraph-text-exact.d.ts +15 -3
  27. package/dist/tools/docx/ops/replace-paragraph-text-exact.js +25 -10
  28. package/dist/tools/docx/ops/replace-table-cell-text.d.ts +25 -0
  29. package/dist/tools/docx/ops/replace-table-cell-text.js +85 -0
  30. package/dist/tools/docx/ops/set-color-for-paragraph-exact.d.ts +2 -1
  31. package/dist/tools/docx/ops/set-color-for-paragraph-exact.js +9 -8
  32. package/dist/tools/docx/ops/set-color-for-style.d.ts +4 -0
  33. package/dist/tools/docx/ops/set-color-for-style.js +11 -7
  34. package/dist/tools/docx/ops/table-set-cell-text.js +8 -40
  35. package/dist/tools/docx/read.d.ts +2 -2
  36. package/dist/tools/docx/read.js +137 -17
  37. package/dist/tools/docx/types.d.ts +32 -3
  38. package/dist/tools/docx/xml-view-test.d.ts +1 -0
  39. package/dist/tools/docx/xml-view-test.js +63 -0
  40. package/dist/tools/docx/xml-view.d.ts +56 -0
  41. package/dist/tools/docx/xml-view.js +169 -0
  42. package/dist/tools/edit.js +57 -27
  43. package/dist/tools/macos-control/ax-adapter.d.ts +55 -0
  44. package/dist/tools/macos-control/ax-adapter.js +438 -0
  45. package/dist/tools/macos-control/cdp-adapter.d.ts +23 -0
  46. package/dist/tools/macos-control/cdp-adapter.js +402 -0
  47. package/dist/tools/macos-control/orchestrator.d.ts +77 -0
  48. package/dist/tools/macos-control/orchestrator.js +136 -0
  49. package/dist/tools/macos-control/role-aliases.d.ts +5 -0
  50. package/dist/tools/macos-control/role-aliases.js +34 -0
  51. package/dist/tools/macos-control/types.d.ts +129 -0
  52. package/dist/tools/macos-control/types.js +1 -0
  53. package/dist/tools/schemas.d.ts +3 -0
  54. package/dist/tools/schemas.js +2 -1
  55. package/dist/types.d.ts +0 -1
  56. package/dist/ui/config-editor/config-editor-runtime.js +14181 -0
  57. package/dist/ui/config-editor/index.html +13 -0
  58. package/dist/ui/config-editor/src/app.d.ts +43 -0
  59. package/dist/ui/config-editor/src/app.js +840 -0
  60. package/dist/ui/config-editor/src/array-modal.d.ts +19 -0
  61. package/dist/ui/config-editor/src/array-modal.js +185 -0
  62. package/dist/ui/config-editor/src/main.d.ts +1 -0
  63. package/dist/ui/config-editor/src/main.js +2 -0
  64. package/dist/ui/config-editor/styles.css +586 -0
  65. package/dist/ui/file-preview/preview-runtime.js +13337 -752
  66. package/dist/ui/file-preview/shared/preview-file-types.js +3 -1
  67. package/dist/ui/file-preview/src/app.d.ts +5 -1
  68. package/dist/ui/file-preview/src/app.js +114 -200
  69. package/dist/ui/file-preview/src/components/html-renderer.d.ts +1 -5
  70. package/dist/ui/file-preview/src/components/html-renderer.js +11 -27
  71. package/dist/ui/file-preview/styles.css +117 -83
  72. package/dist/ui/resources.d.ts +7 -0
  73. package/dist/ui/resources.js +16 -2
  74. package/dist/ui/shared/compact-row.d.ts +11 -0
  75. package/dist/ui/shared/compact-row.js +18 -0
  76. package/dist/ui/shared/host-context.d.ts +15 -0
  77. package/dist/ui/shared/host-context.js +51 -0
  78. package/dist/ui/shared/tool-bridge.d.ts +30 -0
  79. package/dist/ui/shared/tool-bridge.js +137 -0
  80. package/dist/ui/shared/tool-shell.d.ts +9 -0
  81. package/dist/ui/shared/tool-shell.js +46 -4
  82. package/dist/ui/shared/ui-event-tracker.d.ts +9 -0
  83. package/dist/ui/shared/ui-event-tracker.js +27 -0
  84. package/dist/utils/capture.js +173 -11
  85. package/dist/utils/files/base.d.ts +3 -1
  86. package/dist/utils/files/docx.d.ts +28 -15
  87. package/dist/utils/files/docx.js +622 -88
  88. package/dist/utils/files/factory.d.ts +6 -5
  89. package/dist/utils/files/factory.js +18 -6
  90. package/dist/utils/system-info.js +1 -1
  91. package/dist/utils/usageTracker.js +5 -0
  92. package/dist/version.d.ts +1 -1
  93. package/dist/version.js +1 -1
  94. package/package.json +8 -3
@@ -0,0 +1,27 @@
1
+ function normalizeUiEventParams(params) {
2
+ const normalized = {};
3
+ if (!params) {
4
+ return normalized;
5
+ }
6
+ for (const [key, value] of Object.entries(params)) {
7
+ if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean' || value === null) {
8
+ normalized[key] = value;
9
+ }
10
+ }
11
+ return normalized;
12
+ }
13
+ export function createUiEventTracker(callTool, options) {
14
+ const baseParams = options.baseParams ?? {};
15
+ return (event, params = {}) => {
16
+ void callTool('track_ui_event', {
17
+ event,
18
+ component: options.component,
19
+ params: {
20
+ ...baseParams,
21
+ ...normalizeUiEventParams(params),
22
+ },
23
+ }).catch(() => {
24
+ // UI analytics should never block UI interactions.
25
+ });
26
+ };
27
+ }
@@ -1,6 +1,6 @@
1
1
  import { platform } from 'os';
2
2
  import * as https from 'https';
3
- import { configManager } from '../config-manager.js';
3
+ import { configManager, isTelemetryDisabledValue } from '../config-manager.js';
4
4
  import { currentClient } from '../server.js';
5
5
  let VERSION = 'unknown';
6
6
  try {
@@ -12,6 +12,9 @@ catch {
12
12
  }
13
13
  // Will be initialized when needed
14
14
  let uniqueUserId = 'unknown';
15
+ // --- Telemetry Proxy (direct BigQuery ingestion) ---
16
+ const TELEMETRY_PROXY_URL = 'https://dc-telemetry-proxy-83847352264.europe-west1.run.app/mp/collect';
17
+ const TELEMETRY_PROXY_TOKEN = 'Od44UB_fTrVfGPGRPLr5QdVgFhuKdiGaBmvazTdxVdQ';
15
18
  /**
16
19
  * Sanitizes error objects to remove potentially sensitive information like file paths
17
20
  * @param error Error object or string to sanitize
@@ -53,7 +56,7 @@ export const captureBase = async (captureURL, event, properties) => {
53
56
  // Check if telemetry is enabled in config (defaults to true if not set)
54
57
  const telemetryEnabled = await configManager.getValue('telemetryEnabled');
55
58
  // If telemetry is explicitly disabled or GA credentials are missing, don't send
56
- if (telemetryEnabled === false || !captureURL) {
59
+ if (isTelemetryDisabledValue(telemetryEnabled) || !captureURL) {
57
60
  return;
58
61
  }
59
62
  // Get or create the client ID if not already initialized
@@ -225,19 +228,178 @@ export const captureBase = async (captureURL, event, properties) => {
225
228
  // Silently fail - we don't want analytics issues to break functionality
226
229
  }
227
230
  };
231
+ /**
232
+ * Build the standard event properties used by both GA4 and the telemetry proxy.
233
+ * Extracted from captureBase so both paths get identical data.
234
+ */
235
+ const buildEventProperties = async (properties) => {
236
+ if (uniqueUserId === 'unknown') {
237
+ uniqueUserId = await configManager.getOrCreateClientId();
238
+ }
239
+ let clientContext = {};
240
+ if (currentClient) {
241
+ clientContext = {
242
+ client_name: currentClient.name,
243
+ client_version: currentClient.version,
244
+ };
245
+ }
246
+ const sawOnboardingPage = await configManager.getValue('sawOnboardingPage');
247
+ if (sawOnboardingPage !== undefined) {
248
+ clientContext.saw_onboarding_page = sawOnboardingPage;
249
+ }
250
+ let sanitizedProperties;
251
+ try {
252
+ sanitizedProperties = properties ? JSON.parse(JSON.stringify(properties)) : {};
253
+ }
254
+ catch {
255
+ sanitizedProperties = {};
256
+ }
257
+ if (sanitizedProperties.error) {
258
+ if (typeof sanitizedProperties.error === 'object' && sanitizedProperties.error !== null) {
259
+ const sanitized = sanitizeError(sanitizedProperties.error);
260
+ sanitizedProperties.error = sanitized.message;
261
+ if (sanitized.code)
262
+ sanitizedProperties.errorCode = sanitized.code;
263
+ }
264
+ else if (typeof sanitizedProperties.error === 'string') {
265
+ sanitizedProperties.error = sanitizeError(sanitizedProperties.error).message;
266
+ }
267
+ }
268
+ const sensitiveKeys = ['path', 'filePath', 'directory', 'file_path', 'sourcePath', 'destinationPath', 'fullPath', 'rootPath'];
269
+ for (const key of Object.keys(sanitizedProperties)) {
270
+ const lowerKey = key.toLowerCase();
271
+ if (sensitiveKeys.some(sk => lowerKey.includes(sk)) && lowerKey !== 'fileextension') {
272
+ delete sanitizedProperties[key];
273
+ }
274
+ }
275
+ let isDXT = 'false';
276
+ if (process.env.MCP_DXT)
277
+ isDXT = 'true';
278
+ const { getSystemInfo } = await import('./system-info.js');
279
+ const systemInfo = getSystemInfo();
280
+ const isContainer = systemInfo.docker.isContainer ? 'true' : 'false';
281
+ const containerType = systemInfo.docker.containerType || 'none';
282
+ const orchestrator = systemInfo.docker.orchestrator || 'none';
283
+ let containerName = 'none';
284
+ let containerImage = 'none';
285
+ if (systemInfo.docker.isContainer && systemInfo.docker.containerEnvironment) {
286
+ const env = systemInfo.docker.containerEnvironment;
287
+ if (env.containerName) {
288
+ containerName = env.containerName
289
+ .replace(/[0-9a-f]{8,}/gi, 'ID')
290
+ .replace(/[0-9]{8,}/g, 'ID')
291
+ .substring(0, 50);
292
+ }
293
+ if (env.dockerImage) {
294
+ containerImage = env.dockerImage
295
+ .replace(/^[^/]+\/[^/]+\//, '')
296
+ .replace(/^[^/]+\//, '')
297
+ .replace(/@sha256:.*$/, '')
298
+ .substring(0, 100);
299
+ }
300
+ }
301
+ let runtimeSource = 'unknown';
302
+ try {
303
+ const processArgs = process.argv.join(' ');
304
+ if (processArgs.includes('@smithery/cli') || processArgs.includes('smithery')) {
305
+ runtimeSource = 'smithery-runtime';
306
+ }
307
+ else if (processArgs.includes('npx')) {
308
+ runtimeSource = 'npx-runtime';
309
+ }
310
+ else {
311
+ runtimeSource = 'direct-runtime';
312
+ }
313
+ }
314
+ catch { }
315
+ return {
316
+ timestamp: new Date().toISOString(),
317
+ platform: platform(),
318
+ isContainer,
319
+ containerType,
320
+ orchestrator,
321
+ containerName,
322
+ containerImage,
323
+ runtimeSource,
324
+ isDXT,
325
+ app_version: VERSION,
326
+ engagement_time_msec: "100",
327
+ ...clientContext,
328
+ ...sanitizedProperties,
329
+ };
330
+ };
331
+ /**
332
+ * Send event to the telemetry proxy (direct BigQuery ingestion).
333
+ * Runs in parallel with GA4 — used for high-volume events to avoid
334
+ * the 1M/day GA4 BigQuery export limit.
335
+ */
336
+ const sendToTelemetryProxy = async (event, eventProperties) => {
337
+ try {
338
+ const telemetryEnabled = await configManager.getValue('telemetryEnabled');
339
+ if (isTelemetryDisabledValue(telemetryEnabled))
340
+ return;
341
+ const payload = JSON.stringify({
342
+ client_id: uniqueUserId,
343
+ timestamp_micros: Date.now() * 1000,
344
+ events: [{
345
+ name: event,
346
+ params: eventProperties
347
+ }]
348
+ });
349
+ const url = new URL(TELEMETRY_PROXY_URL);
350
+ const options = {
351
+ hostname: url.hostname,
352
+ port: 443,
353
+ path: url.pathname,
354
+ method: 'POST',
355
+ headers: {
356
+ 'Content-Type': 'application/json',
357
+ 'Authorization': `Bearer ${TELEMETRY_PROXY_TOKEN}`,
358
+ 'Content-Length': Buffer.byteLength(payload)
359
+ }
360
+ };
361
+ const req = https.request(options, (res) => {
362
+ res.resume(); // drain response
363
+ });
364
+ req.on('error', () => { }); // silent fail
365
+ req.setTimeout(3000, () => req.destroy());
366
+ req.write(payload);
367
+ req.end();
368
+ }
369
+ catch {
370
+ // Silent fail — telemetry should never break functionality
371
+ }
372
+ };
228
373
  export const capture_call_tool = async (event, properties) => {
229
- const GA_MEASUREMENT_ID = 'G-8L163XZ1CE'; // Replace with your GA4 Measurement ID
230
- const GA_API_SECRET = 'hNxh4TK2TnSy4oLZn4RwTA'; // Replace with your GA4 API Secret
231
- const GA_BASE_URL = `https://www.google-analytics.com/mp/collect?measurement_id=${GA_MEASUREMENT_ID}&api_secret=${GA_API_SECRET}`;
232
- const GA_DEBUG_BASE_URL = `https://www.google-analytics.com/debug/mp/collect?measurement_id=${GA_MEASUREMENT_ID}&api_secret=${GA_API_SECRET}`;
233
- return await captureBase(GA_BASE_URL, event, properties);
374
+ // Old property (G-8L163XZ1CE) keeps lower-volume tool events
375
+ const GA_OLD_ID = 'G-8L163XZ1CE';
376
+ const GA_OLD_SECRET = 'hNxh4TK2TnSy4oLZn4RwTA';
377
+ const GA_OLD_URL = `https://www.google-analytics.com/mp/collect?measurement_id=${GA_OLD_ID}&api_secret=${GA_OLD_SECRET}`;
378
+ // New property (dc_high_volume) receives highest-volume tool events to avoid 1M/day BQ export limit
379
+ const GA_NEW_ID = 'G-ZDF1M5403Z';
380
+ const GA_NEW_SECRET = 'cUEilpa0SpWfc2UjblDtKQ';
381
+ const GA_NEW_URL = `https://www.google-analytics.com/mp/collect?measurement_id=${GA_NEW_ID}&api_secret=${GA_NEW_SECRET}`;
382
+ // Route highest-volume tools to new property, rest to old
383
+ const HIGH_VOLUME_TOOLS = ['start_process', 'track_ui_event'];
384
+ const toolName = properties?.name;
385
+ const gaUrl = HIGH_VOLUME_TOOLS.includes(toolName) ? GA_NEW_URL : GA_OLD_URL;
386
+ // Build properties once, send to GA4 + telemetry proxy in parallel
387
+ const eventProperties = await buildEventProperties(properties);
388
+ await Promise.all([
389
+ captureBase(gaUrl, event, properties), // GA4 (routed by tool name)
390
+ sendToTelemetryProxy(event, eventProperties), // direct BigQuery (all events)
391
+ ]);
234
392
  };
235
393
  export const capture = async (event, properties) => {
236
- const GA_MEASUREMENT_ID = 'G-F3GK01G39Y'; // Replace with your GA4 Measurement ID
237
- const GA_API_SECRET = 'SqdcIAweSQS1RQErURMdEA'; // Replace with your GA4 API Secret
394
+ const GA_MEASUREMENT_ID = 'G-F3GK01G39Y';
395
+ const GA_API_SECRET = 'SqdcIAweSQS1RQErURMdEA';
238
396
  const GA_BASE_URL = `https://www.google-analytics.com/mp/collect?measurement_id=${GA_MEASUREMENT_ID}&api_secret=${GA_API_SECRET}`;
239
- const GA_DEBUG_BASE_URL = `https://www.google-analytics.com/debug/mp/collect?measurement_id=${GA_MEASUREMENT_ID}&api_secret=${GA_API_SECRET}`;
240
- return await captureBase(GA_BASE_URL, event, properties);
397
+ // Build properties once, send to both GA4 and telemetry proxy in parallel
398
+ const eventProperties = await buildEventProperties(properties);
399
+ await Promise.all([
400
+ captureBase(GA_BASE_URL, event, properties), // existing GA4
401
+ sendToTelemetryProxy(event, eventProperties), // new: direct BigQuery
402
+ ]);
241
403
  };
242
404
  export const capture_ui_event = async (event, properties) => {
243
405
  const GA_MEASUREMENT_ID = 'G-MPFSWEGQ0T';
@@ -102,6 +102,8 @@ export interface FileMetadata {
102
102
  title?: string;
103
103
  totalPages?: number;
104
104
  pages?: PdfPageItem[];
105
+ /** For DOCX files */
106
+ isDocx?: boolean;
105
107
  /** Error information if operation failed */
106
108
  error?: boolean;
107
109
  errorMessage?: string;
@@ -161,7 +163,7 @@ export interface FileInfo {
161
163
  /** File permissions (octal string) */
162
164
  permissions: string;
163
165
  /** File type classification */
164
- fileType: 'text' | 'excel' | 'image' | 'binary';
166
+ fileType: 'text' | 'excel' | 'image' | 'binary' | 'docx';
165
167
  /** Type-specific metadata */
166
168
  metadata?: FileMetadata;
167
169
  }
@@ -1,34 +1,47 @@
1
1
  /**
2
2
  * DOCX File Handler
3
- * Implements FileHandler interface for DOCX documents
4
- * Handles reading, writing, and modifying DOCX files while preserving formatting
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
5
17
  */
6
18
  import { FileHandler, FileResult, FileInfo, ReadOptions, EditResult } from './base.js';
7
- /**
8
- * File handler for DOCX documents
9
- * Extracts text and metadata, supports paragraph-based pagination
10
- */
11
19
  export declare class DocxFileHandler implements FileHandler {
12
20
  private readonly extensions;
13
- /**
14
- * Check if this handler can handle the given file
15
- */
16
21
  canHandle(path: string): boolean;
17
22
  /**
18
- * Read DOCX content - returns body XML for LLM modification
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
19
27
  */
20
28
  read(path: string, options?: ReadOptions): Promise<FileResult>;
21
29
  /**
22
- * Write DOCX - NOT SUPPORTED via write_file
23
- * Use write_docx tool instead to preserve styles
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.)
24
33
  */
25
34
  write(path: string, content: any, mode?: 'rewrite' | 'append'): Promise<void>;
26
35
  /**
27
- * Edit DOCX by applying modifications
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.
28
41
  */
29
- editRange(path: string, range: string, content: any, options?: Record<string, any>): Promise<EditResult>;
42
+ editRange(path: string, _range: string, content: any, options?: Record<string, any>): Promise<EditResult>;
30
43
  /**
31
- * Get DOCX file information
44
+ * Get DOCX file info
32
45
  */
33
46
  getInfo(path: string): Promise<FileInfo>;
34
47
  }