@wonderwhy-er/desktop-commander 0.2.41 → 0.2.42
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 +0 -2
- package/dist/handlers/filesystem-handlers.js +22 -3
- package/dist/server.d.ts +2 -1
- package/dist/server.js +49 -13
- package/dist/setup-claude-server.js +56 -50
- package/dist/terminal-manager.js +46 -0
- package/dist/tools/edit.js +7 -1
- package/dist/tools/filesystem.d.ts +5 -0
- package/dist/tools/filesystem.js +43 -0
- package/dist/tools/pdf/markdown.d.ts +13 -0
- package/dist/tools/pdf/markdown.js +93 -29
- package/dist/track-installation.js +57 -38
- package/dist/types.d.ts +3 -0
- package/dist/ui/contracts.d.ts +1 -1
- package/dist/ui/contracts.js +4 -1
- package/dist/ui/file-preview/preview-runtime.js +111 -113
- package/dist/ui/file-preview/src/app.js +19 -17
- package/dist/ui/file-preview/src/host/external-actions.d.ts +0 -11
- package/dist/ui/file-preview/src/host/external-actions.js +0 -39
- package/dist/uninstall-claude-server.js +54 -47
- package/dist/utils/ab-test.d.ts +4 -0
- package/dist/utils/ab-test.js +6 -0
- package/dist/utils/capture.d.ts +10 -2
- package/dist/utils/capture.js +80 -54
- package/dist/utils/mcp-ui-ab-test.d.ts +13 -0
- package/dist/utils/mcp-ui-ab-test.js +62 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -1
|
@@ -1,48 +1,76 @@
|
|
|
1
1
|
import fs from 'fs/promises';
|
|
2
|
-
import { existsSync } from 'fs';
|
|
3
|
-
import {
|
|
4
|
-
import { join } from 'path';
|
|
2
|
+
import { existsSync, readdirSync } from 'fs';
|
|
3
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from 'path';
|
|
5
4
|
import { mdToPdf } from 'md-to-pdf';
|
|
6
5
|
import { pdf2md } from './lib/pdf2md.js';
|
|
6
|
+
import { CONFIG_FILE } from '../../config.js';
|
|
7
7
|
const isUrl = (source) => source.startsWith('http://') || source.startsWith('https://');
|
|
8
8
|
// Cached Chrome path to avoid repeated lookups
|
|
9
9
|
let cachedChromePath = null; // null = not checked yet
|
|
10
10
|
let chromeCheckPromise = null;
|
|
11
11
|
/**
|
|
12
|
-
* Get
|
|
12
|
+
* Get Desktop Commander's private Puppeteer cache directory.
|
|
13
13
|
*/
|
|
14
14
|
function getPuppeteerCacheDir() {
|
|
15
|
-
return join(
|
|
15
|
+
return join(dirname(CONFIG_FILE), 'puppeteer-cache');
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Get the cache path where Puppeteer stores Chrome for Testing builds.
|
|
19
|
+
*/
|
|
20
|
+
function getPuppeteerChromeDir(cacheDir = getPuppeteerCacheDir()) {
|
|
21
|
+
return join(cacheDir, 'chrome');
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Find the platform-specific executable within a cached Chrome build directory.
|
|
25
|
+
*/
|
|
26
|
+
function getChromeExecutablePath(chromeDir, version) {
|
|
27
|
+
const chromePath = process.platform === 'win32'
|
|
28
|
+
? join(chromeDir, version, 'chrome-win64', 'chrome.exe')
|
|
29
|
+
: process.platform === 'darwin'
|
|
30
|
+
? join(chromeDir, version, 'chrome-mac-x64', 'Google Chrome for Testing.app', 'Contents', 'MacOS', 'Google Chrome for Testing')
|
|
31
|
+
: join(chromeDir, version, 'chrome-linux64', 'chrome');
|
|
32
|
+
if (existsSync(chromePath)) {
|
|
33
|
+
return chromePath;
|
|
34
|
+
}
|
|
35
|
+
// Also check for arm64 mac
|
|
36
|
+
if (process.platform === 'darwin') {
|
|
37
|
+
const armPath = join(chromeDir, version, 'chrome-mac-arm64', 'Google Chrome for Testing.app', 'Contents', 'MacOS', 'Google Chrome for Testing');
|
|
38
|
+
if (existsSync(armPath)) {
|
|
39
|
+
return armPath;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Resolve the cached Chrome build directory that owns an executable path.
|
|
46
|
+
*/
|
|
47
|
+
function getCachedChromeBuildDir(chromeDir, executablePath) {
|
|
48
|
+
const relativePath = relative(chromeDir, executablePath);
|
|
49
|
+
if (relativePath === '..' || relativePath.startsWith(`..${sep}`) || isAbsolute(relativePath)) {
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
const [buildDir] = relativePath.split(sep);
|
|
53
|
+
return buildDir ? join(chromeDir, buildDir) : undefined;
|
|
16
54
|
}
|
|
17
55
|
/**
|
|
18
56
|
* Find Chrome in puppeteer's cache directory
|
|
19
57
|
* Returns the executable path if found, undefined otherwise
|
|
20
58
|
*/
|
|
21
|
-
function findPuppeteerChrome() {
|
|
22
|
-
const
|
|
23
|
-
const chromeDir = join(cacheDir, 'chrome');
|
|
59
|
+
export function findPuppeteerChrome(cacheDir = getPuppeteerCacheDir()) {
|
|
60
|
+
const chromeDir = getPuppeteerChromeDir(cacheDir);
|
|
24
61
|
if (!existsSync(chromeDir)) {
|
|
25
62
|
return undefined;
|
|
26
63
|
}
|
|
27
64
|
try {
|
|
28
65
|
// Look for chrome directories (e.g., win64-143.0.7499.169)
|
|
29
|
-
const
|
|
30
|
-
|
|
66
|
+
const versions = readdirSync(chromeDir, { withFileTypes: true })
|
|
67
|
+
.filter(entry => entry.isDirectory())
|
|
68
|
+
.map(entry => entry.name)
|
|
69
|
+
.sort((a, b) => b.localeCompare(a, undefined, { numeric: true }));
|
|
31
70
|
for (const version of versions) {
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
? join(chromeDir, version, 'chrome-mac-x64', 'Google Chrome for Testing.app', 'Contents', 'MacOS', 'Google Chrome for Testing')
|
|
36
|
-
: join(chromeDir, version, 'chrome-linux64', 'chrome');
|
|
37
|
-
if (existsSync(chromePath)) {
|
|
38
|
-
return chromePath;
|
|
39
|
-
}
|
|
40
|
-
// Also check for arm64 mac
|
|
41
|
-
if (process.platform === 'darwin') {
|
|
42
|
-
const armPath = join(chromeDir, version, 'chrome-mac-arm64', 'Google Chrome for Testing.app', 'Contents', 'MacOS', 'Google Chrome for Testing');
|
|
43
|
-
if (existsSync(armPath)) {
|
|
44
|
-
return armPath;
|
|
45
|
-
}
|
|
71
|
+
const executablePath = getChromeExecutablePath(chromeDir, version);
|
|
72
|
+
if (executablePath) {
|
|
73
|
+
return { executablePath };
|
|
46
74
|
}
|
|
47
75
|
}
|
|
48
76
|
}
|
|
@@ -51,6 +79,37 @@ function findPuppeteerChrome() {
|
|
|
51
79
|
}
|
|
52
80
|
return undefined;
|
|
53
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* Remove stale Puppeteer Chrome builds while preserving the active build.
|
|
84
|
+
*/
|
|
85
|
+
export async function pruneOldPuppeteerChromeBuilds(activeExecutablePath, cacheDir = getPuppeteerCacheDir()) {
|
|
86
|
+
const chromeDir = getPuppeteerChromeDir(cacheDir);
|
|
87
|
+
const activeBuildDir = getCachedChromeBuildDir(chromeDir, activeExecutablePath);
|
|
88
|
+
if (!activeBuildDir) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
let entries;
|
|
92
|
+
try {
|
|
93
|
+
entries = await fs.readdir(chromeDir, { withFileTypes: true });
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
await Promise.all(entries
|
|
99
|
+
.filter(entry => entry.isDirectory())
|
|
100
|
+
.map(async (entry) => {
|
|
101
|
+
const buildDir = join(chromeDir, entry.name);
|
|
102
|
+
if (resolve(buildDir) === resolve(activeBuildDir)) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
await fs.rm(buildDir, { recursive: true, force: true });
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
console.error(`Failed to remove old Chrome cache build at ${buildDir}:`, error);
|
|
110
|
+
}
|
|
111
|
+
}));
|
|
112
|
+
}
|
|
54
113
|
/**
|
|
55
114
|
* Find system-installed Chrome/Chromium browser
|
|
56
115
|
* Returns the executable path if found, undefined otherwise
|
|
@@ -91,6 +150,7 @@ async function installChrome() {
|
|
|
91
150
|
const platform = detectBrowserPlatform();
|
|
92
151
|
const buildId = await resolveBuildId(Browser.CHROME, platform, 'stable');
|
|
93
152
|
console.error('Downloading Chrome for PDF generation (this may take a few minutes)...');
|
|
153
|
+
await fs.mkdir(cacheDir, { recursive: true });
|
|
94
154
|
const installedBrowser = await install({
|
|
95
155
|
browser: Browser.CHROME,
|
|
96
156
|
buildId,
|
|
@@ -101,7 +161,9 @@ async function installChrome() {
|
|
|
101
161
|
},
|
|
102
162
|
});
|
|
103
163
|
console.error('\nChrome download complete.');
|
|
104
|
-
return
|
|
164
|
+
return {
|
|
165
|
+
executablePath: installedBrowser.executablePath,
|
|
166
|
+
};
|
|
105
167
|
}
|
|
106
168
|
/**
|
|
107
169
|
* Find or install Chrome for PDF generation
|
|
@@ -122,8 +184,9 @@ async function getChromePath() {
|
|
|
122
184
|
// 1. Check puppeteer cache first (exact compatible version)
|
|
123
185
|
const cachedChrome = findPuppeteerChrome();
|
|
124
186
|
if (cachedChrome) {
|
|
125
|
-
|
|
126
|
-
|
|
187
|
+
await pruneOldPuppeteerChromeBuilds(cachedChrome.executablePath);
|
|
188
|
+
cachedChromePath = cachedChrome.executablePath;
|
|
189
|
+
return cachedChrome.executablePath;
|
|
127
190
|
}
|
|
128
191
|
// 2. Check system Chrome
|
|
129
192
|
const systemChrome = findSystemChrome();
|
|
@@ -134,8 +197,9 @@ async function getChromePath() {
|
|
|
134
197
|
// 3. Install Chrome as last resort
|
|
135
198
|
try {
|
|
136
199
|
const installedChrome = await installChrome();
|
|
137
|
-
|
|
138
|
-
|
|
200
|
+
await pruneOldPuppeteerChromeBuilds(installedChrome.executablePath);
|
|
201
|
+
cachedChromePath = installedChrome.executablePath;
|
|
202
|
+
return installedChrome.executablePath;
|
|
139
203
|
}
|
|
140
204
|
catch (error) {
|
|
141
205
|
console.error('Failed to install Chrome:', error);
|
|
@@ -73,10 +73,9 @@ async function getClientId() {
|
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
//
|
|
77
|
-
const
|
|
78
|
-
const
|
|
79
|
-
const GA_BASE_URL = `https://www.google-analytics.com/mp/collect?measurement_id=${GA_MEASUREMENT_ID}&api_secret=${GA_API_SECRET}`;
|
|
76
|
+
// Telemetry proxy configuration (same as setup script)
|
|
77
|
+
const TELEMETRY_PROXY_URL = 'https://telemetry.desktopcommander.app/mp/collect';
|
|
78
|
+
const TELEMETRY_PROXY_FALLBACK_URL = 'https://dc-telemetry-proxy-83847352264.europe-west1.run.app/mp/collect';
|
|
80
79
|
|
|
81
80
|
/**
|
|
82
81
|
* Detect installation source from environment and process context
|
|
@@ -267,18 +266,13 @@ async function detectInstallationSource() {
|
|
|
267
266
|
}
|
|
268
267
|
|
|
269
268
|
/**
|
|
270
|
-
* Send installation tracking to
|
|
269
|
+
* Send installation tracking to telemetry proxy
|
|
271
270
|
*/
|
|
272
271
|
async function trackInstallation(installationData) {
|
|
273
|
-
if (!GA_MEASUREMENT_ID || !GA_API_SECRET) {
|
|
274
|
-
debug('Analytics not configured, skipping tracking');
|
|
275
|
-
return;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
272
|
try {
|
|
279
273
|
const uniqueUserId = await getClientId();
|
|
280
274
|
log("user id", uniqueUserId)
|
|
281
|
-
// Prepare
|
|
275
|
+
// Prepare telemetry payload
|
|
282
276
|
const payload = {
|
|
283
277
|
client_id: uniqueUserId,
|
|
284
278
|
non_personalized_ads: false,
|
|
@@ -308,33 +302,11 @@ async function trackInstallation(installationData) {
|
|
|
308
302
|
}
|
|
309
303
|
};
|
|
310
304
|
|
|
311
|
-
await
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
reject(new Error('Request timeout'));
|
|
317
|
-
}, 5000);
|
|
318
|
-
|
|
319
|
-
req.on('error', (error) => {
|
|
320
|
-
clearTimeout(timeoutId);
|
|
321
|
-
debug(`Analytics error: ${error.message}`);
|
|
322
|
-
resolve(); // Don't fail installation on analytics error
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
req.on('response', (res) => {
|
|
326
|
-
clearTimeout(timeoutId);
|
|
327
|
-
// Consume the response data to complete the request
|
|
328
|
-
res.on('data', () => {}); // Ignore response data
|
|
329
|
-
res.on('end', () => {
|
|
330
|
-
log(`Installation tracked: ${installationData.source}`);
|
|
331
|
-
resolve();
|
|
332
|
-
});
|
|
333
|
-
});
|
|
334
|
-
|
|
335
|
-
req.write(postData);
|
|
336
|
-
req.end();
|
|
337
|
-
});
|
|
305
|
+
const sent = await postTelemetryPayload(TELEMETRY_PROXY_URL, postData, options);
|
|
306
|
+
if (!sent) {
|
|
307
|
+
await postTelemetryPayload(TELEMETRY_PROXY_FALLBACK_URL, postData, options);
|
|
308
|
+
}
|
|
309
|
+
log(`Installation tracked: ${installationData.source}`);
|
|
338
310
|
|
|
339
311
|
} catch (error) {
|
|
340
312
|
debug(`Failed to track installation: ${error.message}`);
|
|
@@ -342,6 +314,53 @@ async function trackInstallation(installationData) {
|
|
|
342
314
|
}
|
|
343
315
|
}
|
|
344
316
|
|
|
317
|
+
// TODO(dedup): postTelemetryPayload is copy-pasted across setup/uninstall/track
|
|
318
|
+
// scripts + a variant in src/utils/capture.ts. Consolidate into one shared module.
|
|
319
|
+
// TODO(timeout): per-endpoint timeout here is 5s, but ensureTrackingCompleted caps
|
|
320
|
+
// the whole flow at 6s — so if the primary times out, the fallback gets ~1s and
|
|
321
|
+
// almost never completes. Lower per-endpoint timeout (capture.ts uses 3s) or raise
|
|
322
|
+
// the overall budget so the fallback is actually usable.
|
|
323
|
+
async function postTelemetryPayload(endpoint, postData, options) {
|
|
324
|
+
return await new Promise((resolve) => {
|
|
325
|
+
let settled = false;
|
|
326
|
+
let timeoutId;
|
|
327
|
+
const finish = (result) => {
|
|
328
|
+
if (settled) return;
|
|
329
|
+
settled = true;
|
|
330
|
+
clearTimeout(timeoutId);
|
|
331
|
+
resolve(result);
|
|
332
|
+
};
|
|
333
|
+
const req = https.request(endpoint, options);
|
|
334
|
+
|
|
335
|
+
timeoutId = setTimeout(() => {
|
|
336
|
+
req.destroy();
|
|
337
|
+
finish(false);
|
|
338
|
+
}, 5000);
|
|
339
|
+
|
|
340
|
+
req.on('error', (error) => {
|
|
341
|
+
debug(`Telemetry error: ${error.message}`);
|
|
342
|
+
finish(false);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
req.on('response', (res) => {
|
|
346
|
+
res.on('data', () => {});
|
|
347
|
+
res.on('error', (error) => {
|
|
348
|
+
debug(`Telemetry response error: ${error.message}`);
|
|
349
|
+
finish(false);
|
|
350
|
+
});
|
|
351
|
+
res.on('end', () => {
|
|
352
|
+
finish(res.statusCode >= 200 && res.statusCode < 300);
|
|
353
|
+
});
|
|
354
|
+
res.on('close', () => {
|
|
355
|
+
finish(false);
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
req.write(postData);
|
|
360
|
+
req.end();
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
|
|
345
364
|
/**
|
|
346
365
|
* Main execution
|
|
347
366
|
*/
|
package/dist/types.d.ts
CHANGED
|
@@ -65,6 +65,9 @@ export interface FilePreviewStructuredContent {
|
|
|
65
65
|
fileName: string;
|
|
66
66
|
filePath: string;
|
|
67
67
|
fileType: PreviewFileType;
|
|
68
|
+
sourceTool?: 'read_file' | 'write_file' | 'edit_block';
|
|
69
|
+
defaultEditorName?: string;
|
|
70
|
+
defaultEditorPath?: string;
|
|
68
71
|
content?: string;
|
|
69
72
|
imageData?: string;
|
|
70
73
|
mimeType?: string;
|
package/dist/ui/contracts.d.ts
CHANGED
|
@@ -11,4 +11,4 @@ export interface UiToolMeta extends Record<string, unknown> {
|
|
|
11
11
|
};
|
|
12
12
|
'openai/widgetAccessible'?: boolean;
|
|
13
13
|
}
|
|
14
|
-
export declare function buildUiToolMeta(resourceUri: string, widgetAccessible?: boolean): UiToolMeta;
|
|
14
|
+
export declare function buildUiToolMeta(resourceUri: string, widgetAccessible?: boolean, showMcpUiPreviews?: boolean): UiToolMeta | undefined;
|
package/dist/ui/contracts.js
CHANGED
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const FILE_PREVIEW_RESOURCE_URI = 'ui://desktop-commander/file-preview';
|
|
5
5
|
export const CONFIG_EDITOR_RESOURCE_URI = 'ui://desktop-commander/config-editor';
|
|
6
|
-
export function buildUiToolMeta(resourceUri, widgetAccessible = false) {
|
|
6
|
+
export function buildUiToolMeta(resourceUri, widgetAccessible = false, showMcpUiPreviews = true) {
|
|
7
|
+
if (!showMcpUiPreviews) {
|
|
8
|
+
return undefined;
|
|
9
|
+
}
|
|
7
10
|
const meta = {
|
|
8
11
|
'ui/resourceUri': resourceUri,
|
|
9
12
|
'openai/outputTemplate': resourceUri,
|