@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
@@ -5,7 +5,6 @@ import { configManager } from '../config-manager.js';
5
5
  import { ReadFileArgsSchema, ReadMultipleFilesArgsSchema, WriteFileArgsSchema, CreateDirectoryArgsSchema, ListDirectoryArgsSchema, MoveFileArgsSchema, GetFileInfoArgsSchema, WritePdfArgsSchema } from '../tools/schemas.js';
6
6
  import path from 'path';
7
7
  import os from 'os';
8
- import { buildUiToolMeta, FILE_PREVIEW_RESOURCE_URI } from '../ui/contracts.js';
9
8
  import { resolvePreviewFileType } from '../ui/file-preview/shared/preview-file-types.js';
10
9
  /**
11
10
  * Expand home directory (~) in a file path
@@ -100,9 +99,7 @@ export async function handleReadFile(args) {
100
99
  fileName: path.basename(resolvedFilePath),
101
100
  filePath: resolvedFilePath,
102
101
  fileType: 'unsupported',
103
- content: ''
104
102
  },
105
- _meta: buildUiToolMeta(FILE_PREVIEW_RESOURCE_URI, true)
106
103
  };
107
104
  }
108
105
  // Handle image files
@@ -124,28 +121,26 @@ export async function handleReadFile(args) {
124
121
  fileName: path.basename(resolvedFilePath),
125
122
  filePath: resolvedFilePath,
126
123
  fileType: 'image',
127
- content: imageSummary,
128
124
  imageData,
129
125
  mimeType: fileResult.mimeType
130
- },
131
- _meta: buildUiToolMeta(FILE_PREVIEW_RESOURCE_URI, true)
126
+ }
132
127
  };
133
128
  }
134
129
  else {
135
- // For all other files, return as text
130
+ // For all other files, return as text.
131
+ // structuredContent carries only file metadata (no content duplication);
132
+ // the widget reads text from the MCP content array.
136
133
  const textContent = typeof fileResult.content === 'string'
137
134
  ? fileResult.content
138
135
  : fileResult.content.toString('utf8');
139
- const previewFileType = resolvePreviewFileType(resolvedFilePath);
136
+ const fileType = resolvePreviewFileType(resolvedFilePath);
140
137
  return {
141
138
  content: [{ type: "text", text: textContent }],
142
139
  structuredContent: {
143
140
  fileName: path.basename(resolvedFilePath),
144
141
  filePath: resolvedFilePath,
145
- fileType: previewFileType,
146
- content: textContent
142
+ fileType,
147
143
  },
148
- _meta: buildUiToolMeta(FILE_PREVIEW_RESOURCE_URI, true)
149
144
  };
150
145
  }
151
146
  };
@@ -0,0 +1,16 @@
1
+ import { ServerResult } from '../types.js';
2
+ export declare function handleMacosAxStatus(args: unknown): Promise<ServerResult>;
3
+ export declare function handleMacosAxListApps(args: unknown): Promise<ServerResult>;
4
+ export declare function handleMacosAxListElements(args: unknown): Promise<ServerResult>;
5
+ export declare function handleMacosAxFind(args: unknown): Promise<ServerResult>;
6
+ export declare function handleMacosAxGetState(args: unknown): Promise<ServerResult>;
7
+ export declare function handleMacosAxFindAndClick(args: unknown): Promise<ServerResult>;
8
+ export declare function handleMacosAxClick(args: unknown): Promise<ServerResult>;
9
+ export declare function handleMacosAxType(args: unknown): Promise<ServerResult>;
10
+ export declare function handleMacosAxKey(args: unknown): Promise<ServerResult>;
11
+ export declare function handleMacosAxActivate(args: unknown): Promise<ServerResult>;
12
+ export declare function handleMacosAxWaitFor(args: unknown): Promise<ServerResult>;
13
+ export declare function handleMacosAxBatch(args: unknown): Promise<ServerResult>;
14
+ export declare function handleElectronDebugAttach(args: unknown): Promise<ServerResult>;
15
+ export declare function handleElectronDebugEval(args: unknown): Promise<ServerResult>;
16
+ export declare function handleElectronDebugDisconnect(args: unknown): Promise<ServerResult>;
@@ -0,0 +1,81 @@
1
+ import { MacosAxStatusArgsSchema, MacosAxListAppsArgsSchema, MacosAxListElementsArgsSchema, MacosAxFindArgsSchema, MacosAxGetStateArgsSchema, MacosAxFindAndClickArgsSchema, MacosAxClickArgsSchema, MacosAxTypeArgsSchema, MacosAxKeyArgsSchema, MacosAxActivateArgsSchema, MacosAxWaitForArgsSchema, MacosAxBatchArgsSchema, ElectronDebugAttachArgsSchema, ElectronDebugEvalArgsSchema, ElectronDebugDisconnectArgsSchema, } from '../tools/schemas.js';
2
+ import { macosControlOrchestrator } from '../tools/macos-control/orchestrator.js';
3
+ function toServerResult(result) {
4
+ if (!result?.ok) {
5
+ const message = result?.error?.message || 'macOS control request failed';
6
+ const code = result?.error?.code ? ` (${result.error.code})` : '';
7
+ return {
8
+ content: [{
9
+ type: 'text',
10
+ text: `Error${code}: ${message}`,
11
+ }],
12
+ isError: true,
13
+ };
14
+ }
15
+ return {
16
+ content: [{
17
+ type: 'text',
18
+ text: JSON.stringify(result.data ?? {}, null, 2),
19
+ }],
20
+ };
21
+ }
22
+ export async function handleMacosAxStatus(args) {
23
+ MacosAxStatusArgsSchema.parse(args || {});
24
+ return toServerResult(await macosControlOrchestrator.axStatus());
25
+ }
26
+ export async function handleMacosAxListApps(args) {
27
+ MacosAxListAppsArgsSchema.parse(args || {});
28
+ return toServerResult(await macosControlOrchestrator.axListApps());
29
+ }
30
+ export async function handleMacosAxListElements(args) {
31
+ const parsed = MacosAxListElementsArgsSchema.parse(args || {});
32
+ return toServerResult(await macosControlOrchestrator.axListElements(parsed));
33
+ }
34
+ export async function handleMacosAxFind(args) {
35
+ const parsed = MacosAxFindArgsSchema.parse(args);
36
+ return toServerResult(await macosControlOrchestrator.axFind(parsed));
37
+ }
38
+ export async function handleMacosAxGetState(args) {
39
+ const parsed = MacosAxGetStateArgsSchema.parse(args);
40
+ return toServerResult(await macosControlOrchestrator.axGetState(parsed));
41
+ }
42
+ export async function handleMacosAxFindAndClick(args) {
43
+ const parsed = MacosAxFindAndClickArgsSchema.parse(args);
44
+ return toServerResult(await macosControlOrchestrator.axFindAndClick(parsed));
45
+ }
46
+ export async function handleMacosAxClick(args) {
47
+ const parsed = MacosAxClickArgsSchema.parse(args);
48
+ return toServerResult(await macosControlOrchestrator.axClick(parsed));
49
+ }
50
+ export async function handleMacosAxType(args) {
51
+ const parsed = MacosAxTypeArgsSchema.parse(args);
52
+ return toServerResult(await macosControlOrchestrator.axType(parsed.text));
53
+ }
54
+ export async function handleMacosAxKey(args) {
55
+ const parsed = MacosAxKeyArgsSchema.parse(args);
56
+ return toServerResult(await macosControlOrchestrator.axKey(parsed.key, parsed.modifiers ?? []));
57
+ }
58
+ export async function handleMacosAxActivate(args) {
59
+ const parsed = MacosAxActivateArgsSchema.parse(args);
60
+ return toServerResult(await macosControlOrchestrator.axActivate(parsed.app));
61
+ }
62
+ export async function handleMacosAxWaitFor(args) {
63
+ const parsed = MacosAxWaitForArgsSchema.parse(args);
64
+ return toServerResult(await macosControlOrchestrator.axWaitFor(parsed));
65
+ }
66
+ export async function handleMacosAxBatch(args) {
67
+ const parsed = MacosAxBatchArgsSchema.parse(args);
68
+ return toServerResult(await macosControlOrchestrator.axBatch(parsed.commands, parsed.stopOnError));
69
+ }
70
+ export async function handleElectronDebugAttach(args) {
71
+ const parsed = ElectronDebugAttachArgsSchema.parse(args || {});
72
+ return toServerResult(await macosControlOrchestrator.electronDebugAttach(parsed));
73
+ }
74
+ export async function handleElectronDebugEval(args) {
75
+ const parsed = ElectronDebugEvalArgsSchema.parse(args);
76
+ return toServerResult(await macosControlOrchestrator.electronDebugEval(parsed));
77
+ }
78
+ export async function handleElectronDebugDisconnect(args) {
79
+ const parsed = ElectronDebugDisconnectArgsSchema.parse(args);
80
+ return toServerResult(await macosControlOrchestrator.electronDebugDisconnect(parsed));
81
+ }
package/dist/lib.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Library entry point for the DC Electron app worker thread.
3
+ * Exports server, configManager, and MCP SDK classes so the worker
4
+ * can import everything from a single bundled file instead of
5
+ * hundreds of individual module files.
6
+ */
7
+ export { server } from './server.js';
8
+ export { configManager } from './config-manager.js';
9
+ export { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
10
+ export { Client } from '@modelcontextprotocol/sdk/client/index.js';
package/dist/lib.js ADDED
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Library entry point for the DC Electron app worker thread.
3
+ * Exports server, configManager, and MCP SDK classes so the worker
4
+ * can import everything from a single bundled file instead of
5
+ * hundreds of individual module files.
6
+ */
7
+ export { server } from './server.js';
8
+ export { configManager } from './config-manager.js';
9
+ export { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
10
+ export { Client } from '@modelcontextprotocol/sdk/client/index.js';
@@ -36,10 +36,15 @@ export declare class RemoteChannel {
36
36
  id: any;
37
37
  device_name: any;
38
38
  } | null>;
39
- updateDevice(deviceId: string, updates: any): Promise<import("@supabase/postgrest-js").PostgrestSingleResponse<null>>;
40
- createDevice(deviceData: DeviceData): Promise<import("@supabase/postgrest-js").PostgrestSingleResponse<any>>;
39
+ updateDevice(deviceId: string, updates: any): Promise<{
40
+ data: any[] | null;
41
+ error: import("@supabase/postgrest-js").PostgrestError | null;
42
+ }>;
43
+ createDevice(deviceData: DeviceData): Promise<{
44
+ data: any;
45
+ error: null;
46
+ }>;
41
47
  registerDevice(capabilities: any, currentDeviceId: string | undefined, deviceName: string, onToolCall: (payload: any) => void): Promise<void>;
42
- subscribe(deviceId: string, onToolCall: (payload: any) => void): Promise<void>;
43
48
  /**
44
49
  * Create and subscribe to the channel.
45
50
  * This is used for both initial subscription and recreation after socket reconnects.
@@ -28,14 +28,26 @@ export class RemoteChannel {
28
28
  access_token: session.access_token,
29
29
  refresh_token: session.refresh_token || ''
30
30
  });
31
+ if (error) {
32
+ console.error('[DEBUG] Failed to set session:', error.message);
33
+ await captureRemote('remote_channel_set_session_error', { error });
34
+ return { error };
35
+ }
31
36
  // Get user info
32
37
  const { data: { user }, error: userError } = await this.client.auth.getUser();
33
38
  if (userError) {
34
- console.debug('[DEBUG] Failed to get user:', userError.message);
39
+ console.error('[DEBUG] Failed to get user:', userError.message);
40
+ await captureRemote('remote_channel_get_user_error', { error: userError });
35
41
  throw userError;
36
42
  }
43
+ if (!user) {
44
+ const noUserError = new Error('No user returned after setSession');
45
+ console.error('[DEBUG] No user returned:', noUserError.message);
46
+ await captureRemote('remote_channel_get_user_empty', {});
47
+ throw noUserError;
48
+ }
37
49
  this._user = user;
38
- console.debug('[DEBUG] Session set successfully, user:', user?.email);
50
+ console.debug('[DEBUG] Session set successfully, user:', user.email);
39
51
  return { error };
40
52
  }
41
53
  async getSession() {
@@ -52,26 +64,45 @@ export class RemoteChannel {
52
64
  .eq('id', deviceId)
53
65
  .eq('user_id', this.user?.id)
54
66
  .maybeSingle();
55
- if (error)
67
+ if (error) {
68
+ console.error('[DEBUG] Failed to find device:', error.message);
69
+ await captureRemote('remote_channel_find_device_error', { error });
56
70
  throw error;
71
+ }
57
72
  return data;
58
73
  }
59
74
  async updateDevice(deviceId, updates) {
60
75
  if (!this.client)
61
76
  throw new Error('Client not initialized');
62
- return await this.client
77
+ const { data, error } = await this.client
63
78
  .from('mcp_devices')
64
79
  .update(updates)
65
- .eq('id', deviceId);
80
+ .eq('id', deviceId)
81
+ .select();
82
+ if (error) {
83
+ console.error('[DEBUG] Failed to update device:', error.message);
84
+ await captureRemote('remote_channel_update_device_error', { error });
85
+ }
86
+ else {
87
+ console.debug('[DEBUG] Device updated successfully');
88
+ }
89
+ return { data, error };
66
90
  }
67
91
  async createDevice(deviceData) {
68
92
  if (!this.client)
69
93
  throw new Error('Client not initialized');
70
- return await this.client
94
+ const { data, error } = await this.client
71
95
  .from('mcp_devices')
72
96
  .insert(deviceData)
73
97
  .select()
74
98
  .single();
99
+ if (error) {
100
+ console.error('[DEBUG] Failed to create device:', error.message);
101
+ await captureRemote('remote_channel_create_device_error', { error });
102
+ throw error;
103
+ }
104
+ console.debug('[DEBUG] Device created successfully');
105
+ return { data, error };
75
106
  }
76
107
  async registerDevice(capabilities, currentDeviceId, deviceName, onToolCall) {
77
108
  console.debug('[DEBUG] RemoteChannel.registerDevice() called, deviceId:', currentDeviceId);
@@ -95,7 +126,10 @@ export class RemoteChannel {
95
126
  console.debug(`⏳ Subscribing to tool call channel...`);
96
127
  // Create and subscribe to the channel
97
128
  console.debug('[DEBUG] Calling createChannel()');
98
- await this.createChannel();
129
+ // ! Ignore silently in Initialization to reconnect after
130
+ await this.createChannel().catch((error) => {
131
+ console.debug('[DEBUG] Failed to create channel, will retry after socket reconnect', error);
132
+ });
99
133
  }
100
134
  else {
101
135
  console.error(` - ❌ Device not found: ${currentDeviceId}`);
@@ -103,16 +137,6 @@ export class RemoteChannel {
103
137
  throw new Error(`Device not found: ${currentDeviceId}`);
104
138
  }
105
139
  }
106
- async subscribe(deviceId, onToolCall) {
107
- if (!this.client)
108
- throw new Error('Client not initialized');
109
- // Store parameters for channel recreation
110
- this.deviceId = deviceId;
111
- this.onToolCall = onToolCall;
112
- console.debug(`⏳ Subscribing to tool call channel...`);
113
- // Create and subscribe to the channel
114
- await this.createChannel();
115
- }
116
140
  /**
117
141
  * Create and subscribe to the channel.
118
142
  * This is used for both initial subscription and recreation after socket reconnects.
@@ -156,7 +180,7 @@ export class RemoteChannel {
156
180
  reject(err || new Error('Failed to initialize tool call channel subscription'));
157
181
  }
158
182
  else if (status === 'TIMED_OUT') {
159
- console.error('⏱️ Channel subscription timed out');
183
+ console.error('⏱️ Channel subscription timed out, Reconnecting...');
160
184
  this.setOnlineStatus(this.deviceId, 'offline');
161
185
  captureRemote('remote_channel_subscription_timeout', {}).catch(() => { });
162
186
  reject(new Error('Tool call channel subscription timed out'));
@@ -213,10 +237,17 @@ export class RemoteChannel {
213
237
  async markCallExecuting(callId) {
214
238
  if (!this.client)
215
239
  throw new Error('Client not initialized');
216
- await this.client
240
+ const { error } = await this.client
217
241
  .from('mcp_remote_calls')
218
242
  .update({ status: 'executing' })
219
243
  .eq('id', callId);
244
+ if (error) {
245
+ console.error('[DEBUG] Failed to mark call executing:', error.message);
246
+ await captureRemote('remote_channel_mark_call_executing_error', { error });
247
+ }
248
+ else {
249
+ console.debug('[DEBUG] Call marked executing:', callId);
250
+ }
220
251
  }
221
252
  async updateCallResult(callId, status, result = null, errorMessage = null) {
222
253
  if (!this.client)
@@ -229,19 +260,31 @@ export class RemoteChannel {
229
260
  updateData.result = result;
230
261
  if (errorMessage !== null)
231
262
  updateData.error_message = errorMessage;
232
- await this.client
263
+ console.debug('[DEBUG] Updating call result:', updateData);
264
+ const { data, error } = await this.client
233
265
  .from('mcp_remote_calls')
234
266
  .update(updateData)
235
267
  .eq('id', callId);
268
+ if (error) {
269
+ console.error('[DEBUG] Failed to update call result:', error.message);
270
+ await captureRemote('remote_channel_update_call_result_error', { error });
271
+ }
272
+ else {
273
+ console.debug('[DEBUG] Call result updated successfully:', data);
274
+ }
236
275
  }
237
276
  async updateHeartbeat(deviceId) {
238
277
  if (!this.client)
239
278
  return;
240
279
  try {
241
- await this.client
280
+ const { error } = await this.client
242
281
  .from('mcp_devices')
243
282
  .update({ last_seen: new Date().toISOString() })
244
283
  .eq('id', deviceId);
284
+ if (error) {
285
+ console.error('[DEBUG] Heartbeat update failed:', error.message);
286
+ await captureRemote('remote_channel_heartbeat_error', { error });
287
+ }
245
288
  // console.log(`🔌 Heartbeat sent for device: ${deviceId}`);
246
289
  }
247
290
  catch (error) {
@@ -283,12 +326,16 @@ export class RemoteChannel {
283
326
  .update({ status: status, last_seen: new Date().toISOString() })
284
327
  .eq('id', deviceId);
285
328
  if (error) {
329
+ console.error(`[DEBUG] Failed to set status ${status}:`, error.message);
286
330
  if (status == "online") {
287
331
  console.error('Failed to update device status:', error.message);
288
332
  }
289
333
  await captureRemote('remote_channel_status_update_error', { error, status });
290
334
  return;
291
335
  }
336
+ else {
337
+ console.debug(`[DEBUG] Device status set to ${status}`);
338
+ }
292
339
  // console.log(status === 'online' ? `🔌 Device marked as ${status}` : `❌ Device marked as ${status}`);
293
340
  }
294
341
  async setOffline(deviceId) {
@@ -97,6 +97,19 @@ export interface SearchSessionOptions {
97
97
  * Find all Excel files in a directory recursively
98
98
  */
99
99
  private findExcelFiles;
100
+ /**
101
+ * Determine if DOCX search should be included based on context
102
+ */
103
+ private shouldIncludeDocxSearch;
104
+ /**
105
+ * Search DOCX files for content matches
106
+ * Extracts <w:t> text from document.xml and searches it
107
+ */
108
+ private searchDocxFiles;
109
+ /**
110
+ * Find all DOCX files in a directory recursively
111
+ */
112
+ private findDocxFiles;
100
113
  /**
101
114
  * Extract context around a match for display (show surrounding text)
102
115
  */
@@ -5,6 +5,7 @@ import { validatePath } from './tools/filesystem.js';
5
5
  import { capture } from './utils/capture.js';
6
6
  import { getRipgrepPath } from './utils/ripgrep-resolver.js';
7
7
  import { isExcelFile } from './utils/files/index.js';
8
+ import PizZip from 'pizzip';
8
9
  /**
9
10
  * Search Session Manager - handles ripgrep processes like terminal sessions
10
11
  * Supports both file search and content search with progressive results
@@ -105,6 +106,19 @@ import { isExcelFile } from './utils/files/index.js';
105
106
  capture('excel_search_error', { error: err instanceof Error ? err.message : String(err) });
106
107
  });
107
108
  }
109
+ // For content searches, also search DOCX files
110
+ const shouldSearchDocx = options.searchType === 'content' &&
111
+ this.shouldIncludeDocxSearch(options.filePattern, validPath);
112
+ if (shouldSearchDocx) {
113
+ this.searchDocxFiles(validPath, options.pattern, options.ignoreCase !== false, options.maxResults, options.filePattern).then(docxResults => {
114
+ for (const result of docxResults) {
115
+ session.results.push(result);
116
+ session.totalMatches++;
117
+ }
118
+ }).catch((err) => {
119
+ capture('docx_search_error', { error: err instanceof Error ? err.message : String(err) });
120
+ });
121
+ }
108
122
  // Wait for first chunk of data or early completion instead of fixed delay
109
123
  // Excel search runs in background and results are merged via readSearchResults
110
124
  const firstChunk = new Promise(resolve => {
@@ -349,6 +363,138 @@ import { isExcelFile } from './utils/files/index.js';
349
363
  }
350
364
  return excelFiles;
351
365
  }
366
+ /**
367
+ * Determine if DOCX search should be included based on context
368
+ */
369
+ shouldIncludeDocxSearch(filePattern, rootPath) {
370
+ const docxExtensions = ['.docx'];
371
+ if (rootPath) {
372
+ const lowerPath = rootPath.toLowerCase();
373
+ if (docxExtensions.some(ext => lowerPath.endsWith(ext))) {
374
+ return true;
375
+ }
376
+ }
377
+ if (filePattern) {
378
+ const lowerPattern = filePattern.toLowerCase();
379
+ if (docxExtensions.some(ext => lowerPattern.includes(`*${ext}`) || lowerPattern.endsWith(ext))) {
380
+ return true;
381
+ }
382
+ }
383
+ return false;
384
+ }
385
+ /**
386
+ * Search DOCX files for content matches
387
+ * Extracts <w:t> text from document.xml and searches it
388
+ */
389
+ async searchDocxFiles(rootPath, pattern, ignoreCase, maxResults, filePattern) {
390
+ const results = [];
391
+ const flags = ignoreCase ? 'i' : '';
392
+ let regex;
393
+ try {
394
+ regex = new RegExp(pattern, flags);
395
+ }
396
+ catch {
397
+ const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
398
+ regex = new RegExp(escaped, flags);
399
+ }
400
+ let docxFiles = await this.findDocxFiles(rootPath);
401
+ if (filePattern) {
402
+ const patterns = filePattern.split('|').map(p => p.trim()).filter(Boolean);
403
+ docxFiles = docxFiles.filter(filePath => {
404
+ const fileName = path.basename(filePath);
405
+ return patterns.some(pat => {
406
+ if (pat.includes('*')) {
407
+ const regexPat = pat.replace(/\./g, '\\.').replace(/\*/g, '.*');
408
+ return new RegExp(`^${regexPat}$`, 'i').test(fileName);
409
+ }
410
+ return fileName.toLowerCase() === pat.toLowerCase();
411
+ });
412
+ });
413
+ }
414
+ for (const filePath of docxFiles) {
415
+ if (maxResults && results.length >= maxResults)
416
+ break;
417
+ try {
418
+ const buf = await fs.readFile(filePath);
419
+ const zip = new PizZip(buf);
420
+ // Search all XML parts that can contain text
421
+ const xmlParts = ['word/document.xml', 'word/header1.xml', 'word/header2.xml',
422
+ 'word/header3.xml', 'word/footer1.xml', 'word/footer2.xml', 'word/footer3.xml'];
423
+ for (const xmlPath of xmlParts) {
424
+ if (maxResults && results.length >= maxResults)
425
+ break;
426
+ const file = zip.file(xmlPath);
427
+ if (!file)
428
+ continue;
429
+ const xml = file.asText();
430
+ // Extract all <w:t> text with position tracking
431
+ const wtRe = /<w:t(?:\s[^>]*)?>([^<]*)<\/w:t>/g;
432
+ let m;
433
+ let lineNum = 0;
434
+ while ((m = wtRe.exec(xml)) !== null) {
435
+ if (maxResults && results.length >= maxResults)
436
+ break;
437
+ const text = m[1];
438
+ if (!text || !text.trim())
439
+ continue;
440
+ lineNum++;
441
+ if (regex.test(text)) {
442
+ const match = text.match(regex);
443
+ const matchContext = match
444
+ ? this.getMatchContext(text, match.index || 0, match[0].length)
445
+ : text.substring(0, 150);
446
+ const partName = xmlPath === 'word/document.xml' ? '' : `:${xmlPath.replace('word/', '')}`;
447
+ results.push({
448
+ file: `${filePath}${partName}`,
449
+ line: lineNum,
450
+ match: matchContext,
451
+ type: 'content'
452
+ });
453
+ }
454
+ }
455
+ }
456
+ }
457
+ catch {
458
+ continue;
459
+ }
460
+ }
461
+ return results;
462
+ }
463
+ /**
464
+ * Find all DOCX files in a directory recursively
465
+ */
466
+ async findDocxFiles(rootPath) {
467
+ const docxFiles = [];
468
+ const isDocx = (name) => name.toLowerCase().endsWith('.docx');
469
+ async function walk(dir) {
470
+ try {
471
+ const entries = await fs.readdir(dir, { withFileTypes: true });
472
+ for (const entry of entries) {
473
+ const fullPath = path.join(dir, entry.name);
474
+ if (entry.isDirectory()) {
475
+ if (!entry.name.startsWith('.') && entry.name !== 'node_modules') {
476
+ await walk(fullPath);
477
+ }
478
+ }
479
+ else if (entry.isFile() && isDocx(entry.name)) {
480
+ docxFiles.push(fullPath);
481
+ }
482
+ }
483
+ }
484
+ catch { /* skip */ }
485
+ }
486
+ try {
487
+ const stats = await fs.stat(rootPath);
488
+ if (stats.isFile() && isDocx(rootPath)) {
489
+ return [rootPath];
490
+ }
491
+ else if (stats.isDirectory()) {
492
+ await walk(rootPath);
493
+ }
494
+ }
495
+ catch { /* skip */ }
496
+ return docxFiles;
497
+ }
352
498
  /**
353
499
  * Extract context around a match for display (show surrounding text)
354
500
  */
package/dist/server.js CHANGED
@@ -21,7 +21,7 @@ import { handleWelcomePageOnboarding } from './utils/welcome-onboarding.js';
21
21
  import { VERSION } from './version.js';
22
22
  import { capture, capture_call_tool } from "./utils/capture.js";
23
23
  import { logToStderr, logger } from './utils/logger.js';
24
- import { buildUiToolMeta, FILE_PREVIEW_RESOURCE_URI } from './ui/contracts.js';
24
+ import { buildUiToolMeta, CONFIG_EDITOR_RESOURCE_URI, FILE_PREVIEW_RESOURCE_URI } from './ui/contracts.js';
25
25
  import { listUiResources, readUiResource } from './ui/resources.js';
26
26
  // Store startup messages to send after initialization
27
27
  const deferredMessages = [];
@@ -167,6 +167,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
167
167
  - systemInfo (operating system and environment details)
168
168
  ${CMD_PREFIX_DESCRIPTION}`,
169
169
  inputSchema: zodToJsonSchema(GetConfigArgsSchema),
170
+ _meta: buildUiToolMeta(CONFIG_EDITOR_RESOURCE_URI, true),
170
171
  annotations: {
171
172
  title: "Get Configuration",
172
173
  readOnlyHint: true,
@@ -242,6 +243,19 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
242
243
  - PDF: Extracts text content as markdown with page structure
243
244
  * offset/length work as page pagination (0-based)
244
245
  * Includes embedded images when available
246
+ - DOCX (.docx): Two modes depending on parameters:
247
+ * DEFAULT (no offset/length): Returns a text-bearing outline — shows paragraphs with text,
248
+ tables with cell content, styles, image refs. Skips shapes/drawings/SVG noise.
249
+ Each element shows its body index [0], [1], etc.
250
+ * WITH offset/length: Returns raw pretty-printed XML with line pagination.
251
+ Use this to drill into specific sections or see the actual XML for editing.
252
+ * EDITING WORKFLOW: 1) read_file to get outline, 2) read_file with offset/length
253
+ to see raw XML around what you want to edit, 3) edit_block with old_string/new_string
254
+ using XML fragments copied from the read output.
255
+ * IMPORTANT: offset MUST be non-zero to get raw XML (use offset=1 to start from line 1).
256
+ offset=0 always returns the outline regardless of length.
257
+ * For BULK changes (translation, mass replacements): use start_process with Python
258
+ zipfile module to find/replace all <w:t> elements at once.
245
259
 
246
260
  ${PATH_GUIDANCE}
247
261
  ${CMD_PREFIX_DESCRIPTION}`,
@@ -279,6 +293,8 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
279
293
  Write or append to file contents.
280
294
 
281
295
  IMPORTANT: DO NOT use this tool to create PDF files. Use 'write_pdf' for all PDF creation tasks.
296
+ DO NOT use this tool to edit DOCX files. Use 'edit_block' with old_string/new_string instead.
297
+ To CREATE a new DOCX, use write_file with .docx extension — text content with markdown headings (#, ##, ###) is converted to styled DOCX paragraphs.
282
298
 
283
299
  CHUNKING IS STANDARD PRACTICE: Always write files in chunks of 25-30 lines maximum.
284
300
  This is the normal, recommended way to write files - not an emergency measure.
@@ -657,6 +673,17 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
657
673
  - new_string: Replacement text
658
674
  - expected_replacements: Optional number of replacements (default: 1)
659
675
 
676
+ DOCX FILES (.docx) - XML Find/Replace mode:
677
+ Takes same parameters as text files (old_string, new_string, expected_replacements).
678
+ Operates on the pretty-printed XML inside the DOCX — the same XML you see from
679
+ read_file with offset/length. Copy XML fragments from read output as old_string.
680
+ After editing, the XML is repacked into a valid DOCX.
681
+ Also searches headers/footers if not found in document body.
682
+ Examples:
683
+ - Replace text: old_string="<w:t>Old Text</w:t>" new_string="<w:t>New Text</w:t>"
684
+ - Change style: old_string='<w:pStyle w:val="Normal"/>' new_string='<w:pStyle w:val="Heading1"/>'
685
+ - Add content: include surrounding XML context in old_string, add new elements in new_string
686
+
660
687
  By default, replaces only ONE occurrence of the search text.
661
688
  To replace multiple occurrences, provide expected_replacements with
662
689
  the exact number of matches expected.
@@ -1078,6 +1105,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1078
1105
  }
1079
1106
  if (name === 'set_config_value' && args && typeof args === 'object' && 'key' in args) {
1080
1107
  telemetryData.set_config_value_key_name = args.key;
1108
+ telemetryData.call_origin = args.origin === 'ui' ? 'ui' : 'llm';
1081
1109
  }
1082
1110
  if (name === 'get_prompts' && args && typeof args === 'object') {
1083
1111
  const promptArgs = args;
@@ -0,0 +1 @@
1
+ export {};