@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.
- package/README.md +240 -100
- package/dist/command-manager.js +6 -3
- package/dist/config-field-definitions.d.ts +41 -0
- package/dist/config-field-definitions.js +37 -0
- package/dist/config-manager.d.ts +2 -0
- package/dist/config-manager.js +22 -2
- package/dist/handlers/filesystem-handlers.js +6 -11
- package/dist/handlers/macos-control-handlers.d.ts +16 -0
- package/dist/handlers/macos-control-handlers.js +81 -0
- package/dist/lib.d.ts +10 -0
- package/dist/lib.js +10 -0
- package/dist/remote-device/remote-channel.d.ts +8 -3
- package/dist/remote-device/remote-channel.js +68 -21
- package/dist/search-manager.d.ts +13 -0
- package/dist/search-manager.js +146 -0
- package/dist/server.js +29 -1
- package/dist/test-docx.d.ts +1 -0
- package/dist/tools/config.d.ts +71 -0
- package/dist/tools/config.js +117 -2
- package/dist/tools/docx/builders/table.d.ts +2 -0
- package/dist/tools/docx/builders/table.js +60 -16
- package/dist/tools/docx/dom.d.ts +74 -1
- package/dist/tools/docx/dom.js +221 -1
- package/dist/tools/docx/index.d.ts +2 -2
- package/dist/tools/docx/ops/index.js +3 -0
- package/dist/tools/docx/ops/replace-paragraph-text-exact.d.ts +15 -3
- package/dist/tools/docx/ops/replace-paragraph-text-exact.js +25 -10
- package/dist/tools/docx/ops/replace-table-cell-text.d.ts +25 -0
- package/dist/tools/docx/ops/replace-table-cell-text.js +85 -0
- package/dist/tools/docx/ops/set-color-for-paragraph-exact.d.ts +2 -1
- package/dist/tools/docx/ops/set-color-for-paragraph-exact.js +9 -8
- package/dist/tools/docx/ops/set-color-for-style.d.ts +4 -0
- package/dist/tools/docx/ops/set-color-for-style.js +11 -7
- package/dist/tools/docx/ops/table-set-cell-text.js +8 -40
- package/dist/tools/docx/read.d.ts +2 -2
- package/dist/tools/docx/read.js +137 -17
- package/dist/tools/docx/types.d.ts +32 -3
- package/dist/tools/docx/xml-view-test.d.ts +1 -0
- package/dist/tools/docx/xml-view-test.js +63 -0
- package/dist/tools/docx/xml-view.d.ts +56 -0
- package/dist/tools/docx/xml-view.js +169 -0
- package/dist/tools/edit.js +57 -27
- package/dist/tools/macos-control/ax-adapter.d.ts +55 -0
- package/dist/tools/macos-control/ax-adapter.js +438 -0
- package/dist/tools/macos-control/cdp-adapter.d.ts +23 -0
- package/dist/tools/macos-control/cdp-adapter.js +402 -0
- package/dist/tools/macos-control/orchestrator.d.ts +77 -0
- package/dist/tools/macos-control/orchestrator.js +136 -0
- package/dist/tools/macos-control/role-aliases.d.ts +5 -0
- package/dist/tools/macos-control/role-aliases.js +34 -0
- package/dist/tools/macos-control/types.d.ts +129 -0
- package/dist/tools/macos-control/types.js +1 -0
- package/dist/tools/schemas.d.ts +3 -0
- package/dist/tools/schemas.js +2 -1
- package/dist/types.d.ts +0 -1
- package/dist/ui/config-editor/config-editor-runtime.js +14181 -0
- package/dist/ui/config-editor/index.html +13 -0
- package/dist/ui/config-editor/src/app.d.ts +43 -0
- package/dist/ui/config-editor/src/app.js +840 -0
- package/dist/ui/config-editor/src/array-modal.d.ts +19 -0
- package/dist/ui/config-editor/src/array-modal.js +185 -0
- package/dist/ui/config-editor/src/main.d.ts +1 -0
- package/dist/ui/config-editor/src/main.js +2 -0
- package/dist/ui/config-editor/styles.css +586 -0
- package/dist/ui/file-preview/preview-runtime.js +13337 -752
- package/dist/ui/file-preview/shared/preview-file-types.js +3 -1
- package/dist/ui/file-preview/src/app.d.ts +5 -1
- package/dist/ui/file-preview/src/app.js +114 -200
- package/dist/ui/file-preview/src/components/html-renderer.d.ts +1 -5
- package/dist/ui/file-preview/src/components/html-renderer.js +11 -27
- package/dist/ui/file-preview/styles.css +117 -83
- package/dist/ui/resources.d.ts +7 -0
- package/dist/ui/resources.js +16 -2
- package/dist/ui/shared/compact-row.d.ts +11 -0
- package/dist/ui/shared/compact-row.js +18 -0
- package/dist/ui/shared/host-context.d.ts +15 -0
- package/dist/ui/shared/host-context.js +51 -0
- package/dist/ui/shared/tool-bridge.d.ts +30 -0
- package/dist/ui/shared/tool-bridge.js +137 -0
- package/dist/ui/shared/tool-shell.d.ts +9 -0
- package/dist/ui/shared/tool-shell.js +46 -4
- package/dist/ui/shared/ui-event-tracker.d.ts +9 -0
- package/dist/ui/shared/ui-event-tracker.js +27 -0
- package/dist/utils/capture.js +173 -11
- package/dist/utils/files/base.d.ts +3 -1
- package/dist/utils/files/docx.d.ts +28 -15
- package/dist/utils/files/docx.js +622 -88
- package/dist/utils/files/factory.d.ts +6 -5
- package/dist/utils/files/factory.js +18 -6
- package/dist/utils/system-info.js +1 -1
- package/dist/utils/usageTracker.js +5 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- 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
|
|
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
|
|
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<
|
|
40
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) {
|
package/dist/search-manager.d.ts
CHANGED
|
@@ -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
|
*/
|
package/dist/search-manager.js
CHANGED
|
@@ -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 {};
|