@wonderwhy-er/desktop-commander
Version:
MCP server for terminal operations and file editing
95 lines (94 loc) • 4.13 kB
JavaScript
import { getParentDirectory } from '../path-utils.js';
export function shellQuote(value) {
return `'${value.replace(/'/g, `'\\''`)}'`;
}
export function encodePowerShellCommand(script) {
const utf16leBytes = [];
for (let index = 0; index < script.length; index += 1) {
const codeUnit = script.charCodeAt(index);
utf16leBytes.push(codeUnit & 0xff, codeUnit >> 8);
}
let binary = '';
for (const byte of utf16leBytes) {
binary += String.fromCharCode(byte);
}
return btoa(binary);
}
export function buildOpenInFolderCommand(filePath, isLikelyUrl) {
const trimmedPath = filePath.trim();
if (!trimmedPath || isLikelyUrl(trimmedPath)) {
return undefined;
}
const userAgent = navigator.userAgent.toLowerCase();
if (userAgent.includes('win')) {
const escapedForPowerShell = trimmedPath.replace(/'/g, "''");
const script = `Start-Process -FilePath explorer.exe -ArgumentList @('/select,','${escapedForPowerShell}')`;
return `powershell.exe -NoProfile -NonInteractive -EncodedCommand ${encodePowerShellCommand(script)}`;
}
if (userAgent.includes('mac')) {
return `open -R ${shellQuote(trimmedPath)}`;
}
return `xdg-open ${shellQuote(getParentDirectory(trimmedPath))}`;
}
export function buildOpenInEditorCommand(filePath, isLikelyUrl, editorAppCache) {
const trimmedPath = filePath.trim();
if (!trimmedPath || isLikelyUrl(trimmedPath)) {
return undefined;
}
const cachedApp = editorAppCache.get(trimmedPath);
if (cachedApp?.appPath && navigator.userAgent.toLowerCase().includes('mac')) {
return `open -a ${shellQuote(cachedApp.appPath)} ${shellQuote(trimmedPath)}`;
}
const userAgent = navigator.userAgent.toLowerCase();
if (userAgent.includes('win')) {
const escapedForPowerShell = trimmedPath.replace(/'/g, "''");
const script = `Start-Process -FilePath '${escapedForPowerShell}'`;
return `powershell.exe -NoProfile -NonInteractive -EncodedCommand ${encodePowerShellCommand(script)}`;
}
if (userAgent.includes('mac')) {
return `open ${shellQuote(trimmedPath)}`;
}
return `xdg-open ${shellQuote(trimmedPath)}`;
}
export async function detectDefaultMarkdownEditor(options) {
const trimmedPath = options.filePath.trim();
if (!trimmedPath || options.editorAppCache.has(trimmedPath) || options.editorAppPending.has(trimmedPath)) {
return;
}
const userAgent = navigator.userAgent.toLowerCase();
if (!userAgent.includes('mac')) {
return;
}
options.editorAppPending.add(trimmedPath);
try {
const detectCommand = `osascript -e ${shellQuote(`set appAlias to default application of (info for POSIX file "${trimmedPath.replace(/"/g, '\\"')}")
return (name of (info for appAlias)) & linefeed & POSIX path of appAlias`)}`;
const detectResult = await options.callTool?.('start_process', {
command: detectCommand,
timeout_ms: 12000,
});
const text = options.extractToolText(detectResult) ?? '';
if (!text || text.toLowerCase().includes('error') || text.toLowerCase().includes('execution')) {
return;
}
const lines = text.split('\n').map((line) => line.trim()).filter(Boolean);
const appName = lines[lines.length - 2]?.replace(/\.app$/i, '') ?? '';
const appPath = lines[lines.length - 1] ?? '';
if (appName && appPath.startsWith('/')) {
options.editorAppCache.set(trimmedPath, {
appName,
appPath,
});
options.onDetected?.();
}
}
catch {
// Fall back to generic editor label.
}
finally {
options.editorAppPending.delete(trimmedPath);
}
}
export function renderMarkdownEditorAppIcon() {
return '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 20h9"/><path d="M16.5 3.5a2.1 2.1 0 1 1 3 3L7 19l-4 1 1-4Z"/></svg>';
}