diff --git a/.gitignore b/.gitignore index 3d37b737..617bf578 100644 --- a/.gitignore +++ b/.gitignore @@ -43,4 +43,15 @@ plans/ planning/ # Test output files -test/test_output/ \ No newline at end of file +test/test_output/ + +# MCPB related files +*.mcpb +mcpb-bundle/ +.mcpregistry_github_token +.mcpregistry_registry_token + +# Test files +test_files/ +test_fix.js +test_search.js \ No newline at end of file diff --git a/README.md b/README.md index 19804946..97c239c4 100644 --- a/README.md +++ b/README.md @@ -954,7 +954,7 @@ External telemetry (sent to analytics services) is enabled by default but can be **Note:** This only disables external telemetry. Local usage analytics remain active for tool functionality but is not share externally -For complete details about data collection, please see our [Privacy Policy](PRIVACY.md). +For complete details about data collection, please see our [Privacy Policy](https://legal.desktopcommander.app/privacy_desktop_commander_mcp). ## Verifications [![Verified on MseeP](https://mseep.ai/badge.svg)](https://mseep.ai/app/25ff7a06-58bc-40b8-bd79-ebb715140f1a) diff --git a/icon.png b/icon.png new file mode 100644 index 00000000..5358230e Binary files /dev/null and b/icon.png differ diff --git a/manifest.template.json b/manifest.template.json new file mode 100644 index 00000000..3e49d9b4 --- /dev/null +++ b/manifest.template.json @@ -0,0 +1,161 @@ +{ + "$schema": "../../dist/mcpb-manifest.schema.json", + "manifest_version": "0.1", + "name": "desktop-commander", + "display_name": "Desktop Commander", + "version": "{{VERSION}}", + "description": "Build, explore, and automate on your local machine with access to files and terminal.", + "long_description": "Combine local filesystem access with full terminal control to handle technical tasks through natural language. Desktop Commander empowers you to build, explore, and automate - from organizing repositories to creating complete applications:\n* **Build from scratch** - Create features and applications with simple commands\n* **Manage development environments** - Set up servers, configure systems, and handle processes\n* **Manage context and documentation** - Keep track of project details and technical specifications\n* **Explore existing codebases and projects** - Navigate and understand complex repositories\n\nThis extension bridges technical skill gaps by providing full command-line superpowers through an interface that understands your intent and handles complexity automatically.", + "author": { + "name": "Desktop Commander Team", + "email": "er@desktopcommander.app", + "url": "https://desktopcommander.app/" + }, + "repository": { + "type": "git", + "url": "https://github.com/wonderwhy-er/DesktopCommanderMCP.git" + }, + "homepage": "https://github.com/wonderwhy-er/DesktopCommanderMCP", + "documentation": "https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/FAQ.md", + "support": "https://github.com/wonderwhy-er/DesktopCommanderMCP/issues", + "icon": "icon.png", + "server": { + "type": "node", + "entry_point": "dist/index.js", + "mcp_config": { + "command": "node", + "args": [ + "${__dirname}/dist/index.js" + ], + "env": { + "MCP_DXT": "true", + "NODE_ENV": "production" + } + } + }, + "tools": [ + { + "name": "get_config", + "description": "Get the complete server configuration including blocked commands, allowed directories, file limits, telemetry settings, client info, and system information." + }, + { + "name": "set_config_value", + "description": "Set a specific configuration value by key. WARNING: Should be used in a separate chat from file operations to prevent security issues." + }, + { + "name": "read_file", + "description": "Read the contents of a file from the file system or URL with optional offset and length parameters. Supports partial reading and handles images." + }, + { + "name": "read_multiple_files", + "description": "Read the contents of multiple files simultaneously. Handles text files and renders images." + }, + { + "name": "write_file", + "description": "Write or append to file contents. Always write files in chunks of 25-30 lines maximum for best performance." + }, + { + "name": "create_directory", + "description": "Create a new directory or ensure a directory exists. Can create multiple nested directories in one operation." + }, + { + "name": "list_directory", + "description": "Get a detailed listing of all files and directories in a specified path." + }, + { + "name": "move_file", + "description": "Move or rename files and directories between locations." + }, + { + "name": "start_search", + "description": "Start a streaming search for files by name or content within files. Returns results progressively with session ID." + }, + { + "name": "get_more_search_results", + "description": "Get more results from an active search with offset-based pagination." + }, + { + "name": "stop_search", + "description": "Stop an active search gracefully when you've found what you need." + }, + { + "name": "list_searches", + "description": "List all active searches with their status and runtime information." + }, + { + "name": "get_file_info", + "description": "Retrieve detailed metadata about a file or directory including size, timestamps, permissions, and line counts." + }, + { + "name": "edit_block", + "description": "Apply surgical text replacements to files. Make small, focused edits with minimal context for precision." + }, + { + "name": "start_process", + "description": "Start a new terminal process with intelligent state detection. Primary tool for local file analysis (CSV, JSON, logs, etc.)." + }, + { + "name": "read_process_output", + "description": "Read output from a running process with intelligent completion detection and REPL prompt recognition." + }, + { + "name": "interact_with_process", + "description": "Send input to a running process and automatically receive the response. Critical tool for all local file analysis." + }, + { + "name": "force_terminate", + "description": "Force terminate a running terminal session." + }, + { + "name": "list_sessions", + "description": "List all active terminal sessions with status information including blocked state and runtime." + }, + { + "name": "list_processes", + "description": "List all running processes with PID, command name, CPU usage, and memory usage." + }, + { + "name": "kill_process", + "description": "Terminate a running process by PID. Use with caution." + }, + { + "name": "get_usage_stats", + "description": "Get usage statistics for debugging and analysis including tool usage and performance metrics." + }, + { + "name": "give_feedback_to_desktop_commander", + "description": "Open feedback form in browser to provide feedback about Desktop Commander." + }, + { + "name": "get_prompts", + "description": "Browse and retrieve curated Desktop Commander prompts for various tasks and workflows." + } + ], + "keywords": [ + "orchestration", + "workflow", + "development", + "prototyping", + "terminal", + "processes", + "file-management", + "automation", + "productivity", + "end-to-end", + "data-analysis" + ], + "license": "MIT", + "privacy_policies": [ + "https://legal.desktopcommander.app/privacy_desktop_commander_mcp" + ], + "compatibility": { + "platforms": [ + "darwin", + "win32", + "linux" + ], + "runtimes": { + "node": ">=18.0.0" + } + } +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 43b107ed..95cc18ea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@wonderwhy-er/desktop-commander", - "version": "0.2.14", + "version": "0.2.15", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@wonderwhy-er/desktop-commander", - "version": "0.2.14", + "version": "0.2.15", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 795ae5db..9f990ee8 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "link:local": "npm run build && npm link", "unlink:local": "npm unlink", "inspector": "npx @modelcontextprotocol/inspector dist/index.js", + "build:mcpb": "node scripts/build-mcpb.cjs", "logs:view": "npm run build && node scripts/view-fuzzy-logs.js", "logs:analyze": "npm run build && node scripts/analyze-fuzzy-logs.js", "logs:clear": "npm run build && node scripts/clear-fuzzy-logs.js", diff --git a/scripts/build-mcpb.cjs b/scripts/build-mcpb.cjs new file mode 100755 index 00000000..d29fa869 --- /dev/null +++ b/scripts/build-mcpb.cjs @@ -0,0 +1,168 @@ +#!/usr/bin/env node + +/** + * Build script for creating Desktop Commander MCPB bundle + * + * This script: + * 1. Builds the TypeScript project + * 2. Creates a bundle directory structure + * 3. Generates a proper MCPB manifest.json + * 4. Copies the built server and dependencies + * 5. Uses mcpb CLI to create the final .mcpb bundle + */ + +const fs = require('fs'); +const path = require('path'); +const { execSync } = require('child_process'); + +const PROJECT_ROOT = path.resolve(__dirname, '..'); +const BUNDLE_DIR = path.join(PROJECT_ROOT, 'mcpb-bundle'); +const MANIFEST_PATH = path.join(BUNDLE_DIR, 'manifest.json'); + +console.log('🏗️ Building Desktop Commander MCPB Bundle...'); + +// Step 1: Build the TypeScript project +console.log('📦 Building TypeScript project...'); +try { + execSync('npm run build', { cwd: PROJECT_ROOT, stdio: 'inherit' }); + console.log('✅ TypeScript build completed'); +} catch (error) { + console.error('❌ TypeScript build failed:', error.message); + process.exit(1); +} + +// Step 2: Clean and create bundle directory +if (fs.existsSync(BUNDLE_DIR)) { + fs.rmSync(BUNDLE_DIR, { recursive: true }); +}fs.mkdirSync(BUNDLE_DIR, { recursive: true }); + +// Step 3: Read package.json for version and metadata +const packageJson = JSON.parse(fs.readFileSync(path.join(PROJECT_ROOT, 'package.json'), 'utf8')); + +// Step 4: Load and process manifest template +console.log('📝 Processing manifest template...'); + +const manifestTemplatePath = path.join(PROJECT_ROOT, 'manifest.template.json'); +console.log(`📄 Using manifest: manifest.template.json`); + +let manifestTemplate; +try { + manifestTemplate = fs.readFileSync(manifestTemplatePath, 'utf8'); +} catch (error) { + console.error('❌ Failed to read manifest template:', manifestTemplatePath); + process.exit(1); +} + +// Replace template variables +const manifestContent = manifestTemplate.replace('{{VERSION}}', packageJson.version); + +// Parse and validate the resulting manifest +let manifest; +try { + manifest = JSON.parse(manifestContent); +} catch (error) { + console.error('❌ Invalid JSON in manifest template:', error.message); + process.exit(1); +} + +// Write manifest +fs.writeFileSync(MANIFEST_PATH, JSON.stringify(manifest, null, 2)); +console.log('✅ Created manifest.json'); +// Step 5: Copy necessary files +const filesToCopy = [ + 'dist', + 'package.json', + 'README.md', + 'LICENSE', + 'PRIVACY.md', + 'icon.png' +]; + +filesToCopy.forEach(file => { + const srcPath = path.join(PROJECT_ROOT, file); + const destPath = path.join(BUNDLE_DIR, file); + + if (fs.existsSync(srcPath)) { + if (fs.statSync(srcPath).isDirectory()) { + // Copy directory recursively + fs.cpSync(srcPath, destPath, { recursive: true }); + } else { + // Copy file + fs.copyFileSync(srcPath, destPath); + } + console.log(`✅ Copied ${file}`); + } else { + console.log(`⚠️ Skipped ${file} (not found)`); + } +}); + +// Step 6: Create package.json in bundle with production dependencies +const bundlePackageJson = { + name: manifest.name, + version: manifest.version, + description: manifest.description, + main: "dist/index.js", + author: manifest.author, + license: manifest.license, + repository: manifest.repository, dependencies: { + "@modelcontextprotocol/sdk": "^1.9.0", + "@vscode/ripgrep": "^1.15.9", + "cross-fetch": "^4.1.0", + "fastest-levenshtein": "^1.0.16", + "glob": "^10.3.10", + "isbinaryfile": "^5.0.4", + "zod": "^3.24.1", + "zod-to-json-schema": "^3.23.5" + } +}; + +fs.writeFileSync( + path.join(BUNDLE_DIR, 'package.json'), + JSON.stringify(bundlePackageJson, null, 2) +); + +// Step 6b: Install dependencies in bundle directory +console.log('📦 Installing production dependencies in bundle...'); +try { + execSync('npm install --omit=dev --production', { cwd: BUNDLE_DIR, stdio: 'inherit' }); + console.log('✅ Dependencies installed'); +} catch (error) { + console.error('❌ Failed to install dependencies:', error.message); + process.exit(1); +} + +// Step 7: Validate manifest +console.log('🔍 Validating manifest...'); +try { + execSync(`mcpb validate "${MANIFEST_PATH}"`, { stdio: 'inherit' }); + console.log('✅ Manifest validation passed'); +} catch (error) { + console.error('❌ Manifest validation failed:', error.message); + process.exit(1); +} +// Step 8: Pack the bundle +console.log('📦 Creating .mcpb bundle...'); +const outputFile = path.join(PROJECT_ROOT, `${manifest.name}-${manifest.version}.mcpb`); + +try { + execSync(`mcpb pack "${BUNDLE_DIR}" "${outputFile}"`, { stdio: 'inherit' }); + console.log('✅ MCPB bundle created successfully!'); + console.log(`📁 Bundle location: ${outputFile}`); +} catch (error) { + console.error('❌ Bundle creation failed:', error.message); + process.exit(1); +} + +console.log(''); +console.log('🎉 Desktop Commander MCPB bundle is ready!'); +console.log(''); +console.log('Next steps:'); +console.log('1. Test the bundle by installing it in Claude Desktop:'); +console.log(' Settings → Extensions → Advanced Settings → Install Extension'); +console.log(`2. Select the file: ${outputFile}`); +console.log('3. Configure any settings and test the functionality'); +console.log(''); +console.log('To submit to Anthropic directory:'); +console.log('- Ensure privacy policy is accessible at the GitHub URL'); +console.log('- Complete destructive operation annotations (✅ Done)'); +console.log('- Submit via Anthropic desktop extensions interest form'); \ No newline at end of file diff --git a/src/server.ts b/src/server.ts index 7fa8a710..7b3c8d8d 100644 --- a/src/server.ts +++ b/src/server.ts @@ -169,6 +169,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => { - systemInfo (operating system and environment details) ${CMD_PREFIX_DESCRIPTION}`, inputSchema: zodToJsonSchema(GetConfigArgsSchema), + annotations: { + title: "Get Configuration", + readOnlyHint: true, + }, }, { name: "set_config_value", @@ -191,6 +195,12 @@ server.setRequestHandler(ListToolsRequestSchema, async () => { ${CMD_PREFIX_DESCRIPTION}`, inputSchema: zodToJsonSchema(SetConfigValueArgsSchema), + annotations: { + title: "Set Configuration Value", + readOnlyHint: false, + destructiveHint: true, + openWorldHint: false, + }, }, // Filesystem tools @@ -230,6 +240,11 @@ server.setRequestHandler(ListToolsRequestSchema, async () => { ${PATH_GUIDANCE} ${CMD_PREFIX_DESCRIPTION}`, inputSchema: zodToJsonSchema(ReadFileArgsSchema), + annotations: { + title: "Read File or URL", + readOnlyHint: true, + openWorldHint: true, + }, }, { name: "read_multiple_files", @@ -246,6 +261,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => { ${PATH_GUIDANCE} ${CMD_PREFIX_DESCRIPTION}`, inputSchema: zodToJsonSchema(ReadMultipleFilesArgsSchema), + annotations: { + title: "Read Multiple Files", + readOnlyHint: true, + }, }, { name: "write_file", @@ -279,6 +298,12 @@ server.setRequestHandler(ListToolsRequestSchema, async () => { ${PATH_GUIDANCE} ${CMD_PREFIX_DESCRIPTION}`, inputSchema: zodToJsonSchema(WriteFileArgsSchema), + annotations: { + title: "Write File", + readOnlyHint: false, + destructiveHint: true, + openWorldHint: false, + }, }, { name: "create_directory", @@ -304,6 +329,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => { ${PATH_GUIDANCE} ${CMD_PREFIX_DESCRIPTION}`, inputSchema: zodToJsonSchema(ListDirectoryArgsSchema), + annotations: { + title: "List Directory Contents", + readOnlyHint: true, + }, }, { name: "move_file", @@ -316,6 +345,12 @@ server.setRequestHandler(ListToolsRequestSchema, async () => { ${PATH_GUIDANCE} ${CMD_PREFIX_DESCRIPTION}`, inputSchema: zodToJsonSchema(MoveFileArgsSchema), + annotations: { + title: "Move/Rename File", + readOnlyHint: false, + destructiveHint: true, + openWorldHint: false, + }, }, { name: "start_search", @@ -425,6 +460,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => { ${CMD_PREFIX_DESCRIPTION}`, inputSchema: zodToJsonSchema(GetMoreSearchResultsArgsSchema), + annotations: { + title: "Get Search Results", + readOnlyHint: true, + }, }, { name: "stop_search", @@ -452,6 +491,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => { ${CMD_PREFIX_DESCRIPTION}`, inputSchema: zodToJsonSchema(ListSearchesArgsSchema), + annotations: { + title: "List Active Searches", + readOnlyHint: true, + }, }, { name: "get_file_info", @@ -471,6 +514,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => { ${PATH_GUIDANCE} ${CMD_PREFIX_DESCRIPTION}`, inputSchema: zodToJsonSchema(GetFileInfoArgsSchema), + annotations: { + title: "Get File Information", + readOnlyHint: true, + }, }, // Note: list_allowed_directories removed - use get_config to check allowedDirectories @@ -511,6 +558,12 @@ server.setRequestHandler(ListToolsRequestSchema, async () => { ${PATH_GUIDANCE} ${CMD_PREFIX_DESCRIPTION}`, inputSchema: zodToJsonSchema(EditBlockArgsSchema), + annotations: { + title: "Edit Text Block", + readOnlyHint: false, + destructiveHint: true, + openWorldHint: false, + }, }, // Terminal tools @@ -568,6 +621,12 @@ server.setRequestHandler(ListToolsRequestSchema, async () => { ${PATH_GUIDANCE} ${CMD_PREFIX_DESCRIPTION}`, inputSchema: zodToJsonSchema(StartProcessArgsSchema), + annotations: { + title: "Start Terminal Process", + readOnlyHint: false, + destructiveHint: true, + openWorldHint: true, + }, }, { name: "read_process_output", @@ -595,6 +654,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => { ${CMD_PREFIX_DESCRIPTION}`, inputSchema: zodToJsonSchema(ReadProcessOutputArgsSchema), + annotations: { + title: "Read Process Output", + readOnlyHint: true, + }, }, { name: "interact_with_process", @@ -647,6 +710,12 @@ server.setRequestHandler(ListToolsRequestSchema, async () => { ${CMD_PREFIX_DESCRIPTION}`, inputSchema: zodToJsonSchema(InteractWithProcessArgsSchema), + annotations: { + title: "Send Input to Process", + readOnlyHint: false, + destructiveHint: true, + openWorldHint: true, + }, }, { name: "force_terminate", @@ -655,6 +724,12 @@ server.setRequestHandler(ListToolsRequestSchema, async () => { ${CMD_PREFIX_DESCRIPTION}`, inputSchema: zodToJsonSchema(ForceTerminateArgsSchema), + annotations: { + title: "Force Terminate Process", + readOnlyHint: false, + destructiveHint: true, + openWorldHint: false, + }, }, { name: "list_sessions", @@ -673,6 +748,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => { ${CMD_PREFIX_DESCRIPTION}`, inputSchema: zodToJsonSchema(ListSessionsArgsSchema), + annotations: { + title: "List Terminal Sessions", + readOnlyHint: true, + }, }, { name: "list_processes", @@ -683,6 +762,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => { ${CMD_PREFIX_DESCRIPTION}`, inputSchema: zodToJsonSchema(ListProcessesArgsSchema), + annotations: { + title: "List Running Processes", + readOnlyHint: true, + }, }, { name: "kill_process", @@ -693,6 +776,12 @@ server.setRequestHandler(ListToolsRequestSchema, async () => { ${CMD_PREFIX_DESCRIPTION}`, inputSchema: zodToJsonSchema(KillProcessArgsSchema), + annotations: { + title: "Kill Process", + readOnlyHint: false, + destructiveHint: true, + openWorldHint: false, + }, }, { name: "get_usage_stats", @@ -703,6 +792,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => { ${CMD_PREFIX_DESCRIPTION}`, inputSchema: zodToJsonSchema(GetUsageStatsArgsSchema), + annotations: { + title: "Get Usage Statistics", + readOnlyHint: true, + }, }, { name: "give_feedback_to_desktop_commander", diff --git a/src/tools/config.ts b/src/tools/config.ts index 7f52012d..81ab3e05 100644 --- a/src/tools/config.ts +++ b/src/tools/config.ts @@ -16,17 +16,7 @@ export async function getConfig() { const configWithSystemInfo = { ...config, currentClient, - systemInfo: { - platform: systemInfo.platform, - platformName: systemInfo.platformName, - defaultShell: systemInfo.defaultShell, - pathSeparator: systemInfo.pathSeparator, - isWindows: systemInfo.isWindows, - isMacOS: systemInfo.isMacOS, - isLinux: systemInfo.isLinux, - docker: systemInfo.docker, - examplePaths: systemInfo.examplePaths - } + systemInfo }; console.error(`getConfig result: ${JSON.stringify(configWithSystemInfo, null, 2)}`); diff --git a/src/utils/system-info.ts b/src/utils/system-info.ts index 2de1bd9e..862e2b0a 100644 --- a/src/utils/system-info.ts +++ b/src/utils/system-info.ts @@ -37,6 +37,18 @@ export interface SystemInfo { isMacOS: boolean; isLinux: boolean; docker: ContainerInfo; + isDXT: boolean; + nodeInfo?: { + version: string; + path: string; + npmVersion?: string; + }; + processInfo: { + pid: number; + arch: string; + platform: string; + versions: NodeJS.ProcessVersions; + }; examplePaths: { home: string; temp: string; @@ -380,6 +392,30 @@ function getContainerEnvironment(containerType: ContainerInfo['containerType']): return Object.keys(env).length > 0 ? env : undefined; } +/** + * Detect Node.js installation and version from current process + */ +function detectNodeInfo(): SystemInfo['nodeInfo'] { + try { + // Get Node.js version from current process + const version = process.version.replace('v', ''); // Remove 'v' prefix + + // Get Node.js executable path from current process + const path = process.execPath; + + // Get npm version from environment if available + const npmVersion = process.env.npm_version; + + return { + version, + path, + ...(npmVersion && { npmVersion }) + }; + } catch (error) { + return undefined; + } +} + /** * Get comprehensive system information for tool prompts */ @@ -471,6 +507,17 @@ export function getSystemInfo(): SystemInfo { } } + // Detect Node.js installation from current process + const nodeInfo = detectNodeInfo(); + + // Get process information + const processInfo = { + pid: process.pid, + arch: process.arch, + platform: process.platform, + versions: process.versions + }; + return { platform, platformName, @@ -489,6 +536,9 @@ export function getSystemInfo(): SystemInfo { mountPoints, containerEnvironment: getContainerEnvironment(containerDetection.containerType) }, + isDXT: !!process.env.MCP_DXT, + nodeInfo, + processInfo, examplePaths }; } @@ -626,32 +676,52 @@ LINUX-SPECIFIC NOTES: * Get common development tool guidance based on OS */ export function getDevelopmentToolGuidance(systemInfo: SystemInfo): string { - const { isWindows, isMacOS, isLinux, platformName } = systemInfo; + const { isWindows, isMacOS, isLinux, platformName, nodeInfo, processInfo } = systemInfo; + + // Add detected Node.js info to guidance + const nodeGuidance = nodeInfo + ? `Node.js: v${nodeInfo.version} (${nodeInfo.path})${nodeInfo.npmVersion ? ` | npm: v${nodeInfo.npmVersion}` : ''}` + : 'Node.js: Not detected'; + + // Add process environment info + const envInfo = ` +Current Process Environment: +- Node: v${processInfo.versions.node} +- V8: v${processInfo.versions.v8} +- Architecture: ${processInfo.arch} +- Platform: ${processInfo.platform} +- Process ID: ${processInfo.pid}`; if (isWindows) { return ` COMMON WINDOWS DEVELOPMENT TOOLS: -- Node.js: Usually installed globally, accessible from any shell +- ${nodeGuidance} - Python: May be 'python' or 'py' command, check both - Git: Git Bash provides Unix-like environment - WSL: Windows Subsystem for Linux available for Unix tools -- Visual Studio tools: cl, msbuild for C++ compilation`; +- Visual Studio tools: cl, msbuild for C++ compilation + +${envInfo}`; } else if (isMacOS) { return ` COMMON MACOS DEVELOPMENT TOOLS: - Xcode Command Line Tools: Required for many development tools - Homebrew: Primary package manager for development tools +- ${nodeGuidance} - Python: Usually python3, check if python points to Python 2 -- Node.js: Available via brew or direct installer -- Ruby: System Ruby available, rbenv/rvm for version management`; +- Ruby: System Ruby available, rbenv/rvm for version management + +${envInfo}`; } else { return ` COMMON LINUX DEVELOPMENT TOOLS: - Package managers: Install tools via distribution package manager - Python: Usually python3, python may point to Python 2 -- Node.js: Available via package manager or NodeSource repository +- ${nodeGuidance} - Build tools: gcc, make typically available or easily installed -- Container tools: docker, podman common for development`; +- Container tools: docker, podman common for development + +${envInfo}`; } }