@wonderwhy-er/desktop-commander 0.2.35 → 0.2.37
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 +3 -0
- package/dist/handlers/filesystem-handlers.js +58 -11
- package/dist/handlers/history-handlers.d.ts +7 -0
- package/dist/handlers/history-handlers.js +33 -1
- 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 +56 -4
- package/dist/test-docx.d.ts +1 -0
- package/dist/tools/docx/builders/image.d.ts +14 -0
- package/dist/tools/docx/builders/image.js +84 -0
- package/dist/tools/docx/builders/index.d.ts +9 -3
- package/dist/tools/docx/builders/index.js +9 -3
- package/dist/tools/docx/builders/paragraph.d.ts +12 -0
- package/dist/tools/docx/builders/paragraph.js +29 -0
- package/dist/tools/docx/builders/table.d.ts +10 -0
- package/dist/tools/docx/builders/table.js +138 -0
- package/dist/tools/docx/builders/utils.d.ts +5 -0
- package/dist/tools/docx/builders/utils.js +18 -0
- package/dist/tools/docx/constants.d.ts +28 -32
- package/dist/tools/docx/constants.js +56 -52
- package/dist/tools/docx/create.d.ts +21 -0
- package/dist/tools/docx/create.js +386 -0
- package/dist/tools/docx/dom.d.ts +139 -0
- package/dist/tools/docx/dom.js +448 -0
- package/dist/tools/docx/index.d.ts +8 -12
- package/dist/tools/docx/index.js +8 -14
- package/dist/tools/docx/modify.d.ts +28 -0
- package/dist/tools/docx/modify.js +271 -0
- package/dist/tools/docx/ops/delete-paragraph-at-body-index.d.ts +11 -0
- package/dist/tools/docx/ops/delete-paragraph-at-body-index.js +23 -0
- package/dist/tools/docx/ops/header-replace-text-exact.d.ts +13 -0
- package/dist/tools/docx/ops/header-replace-text-exact.js +55 -0
- package/dist/tools/docx/ops/index.d.ts +17 -0
- package/dist/tools/docx/ops/index.js +70 -0
- package/dist/tools/docx/ops/insert-image-after-text.d.ts +24 -0
- package/dist/tools/docx/ops/insert-image-after-text.js +128 -0
- package/dist/tools/docx/ops/insert-paragraph-after-text.d.ts +12 -0
- package/dist/tools/docx/ops/insert-paragraph-after-text.js +74 -0
- package/dist/tools/docx/ops/insert-table-after-text.d.ts +19 -0
- package/dist/tools/docx/ops/insert-table-after-text.js +57 -0
- package/dist/tools/docx/ops/replace-hyperlink-url.d.ts +12 -0
- package/dist/tools/docx/ops/replace-hyperlink-url.js +37 -0
- package/dist/tools/docx/ops/replace-paragraph-at-body-index.d.ts +9 -0
- package/dist/tools/docx/ops/replace-paragraph-at-body-index.js +25 -0
- package/dist/tools/docx/ops/replace-paragraph-text-exact.d.ts +21 -0
- package/dist/tools/docx/ops/replace-paragraph-text-exact.js +36 -0
- 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 +9 -0
- package/dist/tools/docx/ops/set-color-for-paragraph-exact.js +24 -0
- package/dist/tools/docx/ops/set-color-for-style.d.ts +13 -0
- package/dist/tools/docx/ops/set-color-for-style.js +31 -0
- package/dist/tools/docx/ops/set-paragraph-style-at-body-index.d.ts +8 -0
- package/dist/tools/docx/ops/set-paragraph-style-at-body-index.js +57 -0
- package/dist/tools/docx/ops/table-set-cell-text.d.ts +9 -0
- package/dist/tools/docx/ops/table-set-cell-text.js +40 -0
- package/dist/tools/docx/read.d.ts +27 -0
- package/dist/tools/docx/read.js +308 -0
- package/dist/tools/docx/relationships.d.ts +22 -0
- package/dist/tools/docx/relationships.js +76 -0
- package/dist/tools/docx/types.d.ts +202 -103
- package/dist/tools/docx/types.js +2 -5
- package/dist/tools/docx/validate.d.ts +33 -0
- package/dist/tools/docx/validate.js +49 -0
- package/dist/tools/docx/write.d.ts +17 -0
- package/dist/tools/docx/write.js +88 -0
- 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/docx/zip.d.ts +21 -0
- package/dist/tools/docx/zip.js +35 -0
- package/dist/tools/edit.js +57 -27
- package/dist/tools/schemas.d.ts +13 -0
- package/dist/tools/schemas.js +6 -1
- package/dist/types.d.ts +10 -0
- package/dist/ui/contracts.d.ts +14 -0
- package/dist/ui/contracts.js +18 -0
- package/dist/ui/file-preview/index.html +16 -0
- package/dist/ui/file-preview/preview-runtime.js +13983 -0
- package/dist/ui/file-preview/shared/preview-file-types.d.ts +5 -0
- package/dist/ui/file-preview/shared/preview-file-types.js +57 -0
- package/dist/ui/file-preview/src/app.d.ts +4 -0
- package/dist/ui/file-preview/src/app.js +800 -0
- package/dist/ui/file-preview/src/components/code-viewer.d.ts +6 -0
- package/dist/ui/file-preview/src/components/code-viewer.js +73 -0
- package/dist/ui/file-preview/src/components/highlighting.d.ts +2 -0
- package/dist/ui/file-preview/src/components/highlighting.js +54 -0
- package/dist/ui/file-preview/src/components/html-renderer.d.ts +9 -0
- package/dist/ui/file-preview/src/components/html-renderer.js +63 -0
- package/dist/ui/file-preview/src/components/markdown-renderer.d.ts +1 -0
- package/dist/ui/file-preview/src/components/markdown-renderer.js +21 -0
- package/dist/ui/file-preview/src/components/toolbar.d.ts +6 -0
- package/dist/ui/file-preview/src/components/toolbar.js +75 -0
- package/dist/ui/file-preview/src/image-preview.d.ts +3 -0
- package/dist/ui/file-preview/src/image-preview.js +21 -0
- package/dist/ui/file-preview/src/main.d.ts +1 -0
- package/dist/ui/file-preview/src/main.js +5 -0
- package/dist/ui/file-preview/src/types.d.ts +1 -0
- package/dist/ui/file-preview/src/types.js +1 -0
- package/dist/ui/file-preview/styles.css +764 -0
- package/dist/ui/resources.d.ts +21 -0
- package/dist/ui/resources.js +72 -0
- package/dist/ui/shared/escape-html.d.ts +4 -0
- package/dist/ui/shared/escape-html.js +11 -0
- package/dist/ui/shared/host-lifecycle.d.ts +16 -0
- package/dist/ui/shared/host-lifecycle.js +35 -0
- package/dist/ui/shared/rpc-client.d.ts +14 -0
- package/dist/ui/shared/rpc-client.js +72 -0
- package/dist/ui/shared/theme-adaptation.d.ts +10 -0
- package/dist/ui/shared/theme-adaptation.js +118 -0
- package/dist/ui/shared/tool-header.d.ts +9 -0
- package/dist/ui/shared/tool-header.js +25 -0
- package/dist/ui/shared/tool-shell.d.ts +16 -0
- package/dist/ui/shared/tool-shell.js +65 -0
- package/dist/ui/shared/widget-state.d.ts +28 -0
- package/dist/ui/shared/widget-state.js +60 -0
- package/dist/utils/capture.d.ts +1 -0
- package/dist/utils/capture.js +176 -8
- package/dist/utils/files/base.d.ts +3 -1
- package/dist/utils/files/docx.d.ts +28 -22
- package/dist/utils/files/docx.js +630 -196
- package/dist/utils/files/factory.d.ts +6 -5
- package/dist/utils/files/factory.js +18 -6
- package/dist/utils/files/text.js +9 -1
- 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 +6 -2
package/README.md
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
### Search, update, manage files and run terminal commands with AI
|
|
3
3
|
|
|
4
4
|
[](https://www.npmjs.com/package/@wonderwhy-er/desktop-commander)
|
|
5
|
+
[](https://agentaudit.dev/skills/desktop-commander)
|
|
5
6
|
[](https://archestra.ai/mcp-catalog/wonderwhy-er__desktopcommandermcp)
|
|
6
7
|
[](https://smithery.ai/server/@wonderwhy-er/desktop-commander)
|
|
7
8
|
[](https://www.buymeacoffee.com/wonderwhyer)
|
|
@@ -16,6 +17,8 @@ Work with code and text, run processes, and automate tasks, going far beyond oth
|
|
|
16
17
|
<img width="380" height="200" src="https://glama.ai/mcp/servers/zempur9oh4/badge" alt="Desktop Commander MCP" />
|
|
17
18
|
</a>
|
|
18
19
|
|
|
20
|
+
## 👋 We’re hiring — come build with us: https://desktopcommander.app/careers/
|
|
21
|
+
|
|
19
22
|
## Table of Contents
|
|
20
23
|
- [Features](#features)
|
|
21
24
|
- [How to install](#how-to-install)
|
|
@@ -3,11 +3,34 @@ import { withTimeout } from '../utils/withTimeout.js';
|
|
|
3
3
|
import { createErrorResponse } from '../error-handlers.js';
|
|
4
4
|
import { configManager } from '../config-manager.js';
|
|
5
5
|
import { ReadFileArgsSchema, ReadMultipleFilesArgsSchema, WriteFileArgsSchema, CreateDirectoryArgsSchema, ListDirectoryArgsSchema, MoveFileArgsSchema, GetFileInfoArgsSchema, WritePdfArgsSchema } from '../tools/schemas.js';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import os from 'os';
|
|
8
|
+
import { buildUiToolMeta, FILE_PREVIEW_RESOURCE_URI } from '../ui/contracts.js';
|
|
9
|
+
import { resolvePreviewFileType } from '../ui/file-preview/shared/preview-file-types.js';
|
|
10
|
+
/**
|
|
11
|
+
* Expand home directory (~) in a file path
|
|
12
|
+
*/
|
|
13
|
+
function expandHome(filePath) {
|
|
14
|
+
if (filePath === '~' || filePath.startsWith('~/') || filePath.startsWith(`~${path.sep}`)) {
|
|
15
|
+
return path.join(os.homedir(), filePath.slice(1));
|
|
16
|
+
}
|
|
17
|
+
return filePath;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Resolve a file path to an absolute path for use in structured content.
|
|
21
|
+
* This ensures "Open in folder" always has a valid absolute path.
|
|
22
|
+
*/
|
|
23
|
+
function resolveAbsolutePath(filePath) {
|
|
24
|
+
const expanded = expandHome(filePath);
|
|
25
|
+
return path.isAbsolute(expanded)
|
|
26
|
+
? path.resolve(expanded)
|
|
27
|
+
: path.resolve(process.cwd(), expanded);
|
|
28
|
+
}
|
|
6
29
|
/**
|
|
7
30
|
* Helper function to check if path contains an error
|
|
8
31
|
*/
|
|
9
|
-
function isErrorPath(
|
|
10
|
-
return
|
|
32
|
+
function isErrorPath(filePath) {
|
|
33
|
+
return filePath.startsWith('__ERROR__:');
|
|
11
34
|
}
|
|
12
35
|
/**
|
|
13
36
|
* Extract error message from error path
|
|
@@ -44,6 +67,10 @@ export async function handleReadFile(args) {
|
|
|
44
67
|
sheet: sheetParam,
|
|
45
68
|
range: parsed.range
|
|
46
69
|
};
|
|
70
|
+
// Resolve to absolute path for local files (not URLs) so "Open in folder" works
|
|
71
|
+
const resolvedFilePath = parsed.isUrl
|
|
72
|
+
? parsed.path
|
|
73
|
+
: resolveAbsolutePath(parsed.path);
|
|
47
74
|
const fileResult = await readFile(parsed.path, options);
|
|
48
75
|
// Handle PDF files
|
|
49
76
|
if (fileResult.metadata?.isPdf) {
|
|
@@ -68,28 +95,40 @@ export async function handleReadFile(args) {
|
|
|
68
95
|
text: `PDF file: ${parsed.path}${author}${title} (${meta?.totalPages} pages) \n`
|
|
69
96
|
},
|
|
70
97
|
...pdfContent
|
|
71
|
-
]
|
|
98
|
+
],
|
|
99
|
+
structuredContent: {
|
|
100
|
+
fileName: path.basename(resolvedFilePath),
|
|
101
|
+
filePath: resolvedFilePath,
|
|
102
|
+
fileType: 'unsupported',
|
|
103
|
+
content: ''
|
|
104
|
+
},
|
|
105
|
+
_meta: buildUiToolMeta(FILE_PREVIEW_RESOURCE_URI, true)
|
|
72
106
|
};
|
|
73
107
|
}
|
|
74
108
|
// Handle image files
|
|
75
109
|
if (fileResult.metadata?.isImage) {
|
|
76
|
-
// For image files,
|
|
77
|
-
//
|
|
110
|
+
// For image files, keep content payload text-only for broad host compatibility.
|
|
111
|
+
// The preview widget reads image bytes from structuredContent.
|
|
78
112
|
const imageData = typeof fileResult.content === 'string'
|
|
79
113
|
? fileResult.content
|
|
80
114
|
: fileResult.content.toString('base64');
|
|
115
|
+
const imageSummary = `Image file: ${parsed.path} (${fileResult.mimeType})\n`;
|
|
81
116
|
return {
|
|
82
117
|
content: [
|
|
83
118
|
{
|
|
84
119
|
type: "text",
|
|
85
|
-
text:
|
|
86
|
-
},
|
|
87
|
-
{
|
|
88
|
-
type: "image",
|
|
89
|
-
data: imageData,
|
|
90
|
-
mimeType: fileResult.mimeType
|
|
120
|
+
text: imageSummary
|
|
91
121
|
}
|
|
92
122
|
],
|
|
123
|
+
structuredContent: {
|
|
124
|
+
fileName: path.basename(resolvedFilePath),
|
|
125
|
+
filePath: resolvedFilePath,
|
|
126
|
+
fileType: 'image',
|
|
127
|
+
content: imageSummary,
|
|
128
|
+
imageData,
|
|
129
|
+
mimeType: fileResult.mimeType
|
|
130
|
+
},
|
|
131
|
+
_meta: buildUiToolMeta(FILE_PREVIEW_RESOURCE_URI, true)
|
|
93
132
|
};
|
|
94
133
|
}
|
|
95
134
|
else {
|
|
@@ -97,8 +136,16 @@ export async function handleReadFile(args) {
|
|
|
97
136
|
const textContent = typeof fileResult.content === 'string'
|
|
98
137
|
? fileResult.content
|
|
99
138
|
: fileResult.content.toString('utf8');
|
|
139
|
+
const previewFileType = resolvePreviewFileType(resolvedFilePath);
|
|
100
140
|
return {
|
|
101
141
|
content: [{ type: "text", text: textContent }],
|
|
142
|
+
structuredContent: {
|
|
143
|
+
fileName: path.basename(resolvedFilePath),
|
|
144
|
+
filePath: resolvedFilePath,
|
|
145
|
+
fileType: previewFileType,
|
|
146
|
+
content: textContent
|
|
147
|
+
},
|
|
148
|
+
_meta: buildUiToolMeta(FILE_PREVIEW_RESOURCE_URI, true)
|
|
102
149
|
};
|
|
103
150
|
}
|
|
104
151
|
};
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { ServerResult } from '../types.js';
|
|
2
|
+
type TrackUiEventParams = Record<string, string | number | boolean | null>;
|
|
3
|
+
export declare function buildTrackUiEventCapturePayload(event: string, component: string, params: TrackUiEventParams): Record<string, string | number | boolean | null>;
|
|
2
4
|
/**
|
|
3
5
|
* Handle get_recent_tool_calls command
|
|
4
6
|
*/
|
|
5
7
|
export declare function handleGetRecentToolCalls(args: unknown): Promise<ServerResult>;
|
|
8
|
+
/**
|
|
9
|
+
* Handle track_ui_event command
|
|
10
|
+
*/
|
|
11
|
+
export declare function handleTrackUiEvent(args: unknown): Promise<ServerResult>;
|
|
12
|
+
export {};
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { toolHistory } from '../utils/toolHistory.js';
|
|
2
|
-
import { GetRecentToolCallsArgsSchema } from '../tools/schemas.js';
|
|
2
|
+
import { GetRecentToolCallsArgsSchema, TrackUiEventArgsSchema } from '../tools/schemas.js';
|
|
3
|
+
import { capture_ui_event } from '../utils/capture.js';
|
|
4
|
+
export function buildTrackUiEventCapturePayload(event, component, params) {
|
|
5
|
+
return {
|
|
6
|
+
...params,
|
|
7
|
+
component,
|
|
8
|
+
event
|
|
9
|
+
};
|
|
10
|
+
}
|
|
3
11
|
/**
|
|
4
12
|
* Handle get_recent_tool_calls command
|
|
5
13
|
*/
|
|
@@ -33,3 +41,27 @@ export async function handleGetRecentToolCalls(args) {
|
|
|
33
41
|
};
|
|
34
42
|
}
|
|
35
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Handle track_ui_event command
|
|
46
|
+
*/
|
|
47
|
+
export async function handleTrackUiEvent(args) {
|
|
48
|
+
try {
|
|
49
|
+
const parsed = TrackUiEventArgsSchema.parse(args);
|
|
50
|
+
await capture_ui_event('mcp_ui_event', buildTrackUiEventCapturePayload(parsed.event, parsed.component, parsed.params));
|
|
51
|
+
return {
|
|
52
|
+
content: [{
|
|
53
|
+
type: "text",
|
|
54
|
+
text: `Tracked UI event: ${parsed.event}`
|
|
55
|
+
}]
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
return {
|
|
60
|
+
content: [{
|
|
61
|
+
type: "text",
|
|
62
|
+
text: `Error tracking UI event: ${error instanceof Error ? error.message : String(error)}`
|
|
63
|
+
}],
|
|
64
|
+
isError: true
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -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 Inialization 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
|
*/
|