Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
53e5ea8
experiments with repl and terminal
wonderwhy-er May 3, 2025
d2ffa12
bit more
wonderwhy-er May 3, 2025
38c0a6e
bit more changes
wonderwhy-er May 4, 2025
cf9ede0
Merge branch 'main' into repl-ssh-terminal-support
wonderwhy-er May 22, 2025
ac617e0
Merge remote-tracking branch 'origin/main' into repl-ssh-terminal-sup…
wonderwhy-er Jun 2, 2025
6df3c2f
Updates to working with interactive processes
wonderwhy-er Jun 2, 2025
2668146
Improved prompt for repls
wonderwhy-er Jun 2, 2025
b7d1ffc
Cleanup
wonderwhy-er Jun 2, 2025
e62d588
Improve chunked writing prompt
wonderwhy-er Jun 4, 2025
41144d3
Improve tests
wonderwhy-er Jun 4, 2025
471bc0c
Add support for negative file reads
wonderwhy-er Jun 4, 2025
5a638b2
Improve chunked writing prompt
wonderwhy-er Jun 4, 2025
9f187ba
Add support for negative file reads
wonderwhy-er Jun 4, 2025
4176ede
Improve tests
wonderwhy-er Jun 4, 2025
0478dca
Fix for tests
wonderwhy-er Jun 4, 2025
4d5cabd
Test fix
wonderwhy-er Jun 4, 2025
d0f109d
Cleanup
wonderwhy-er Jun 5, 2025
f94f5dc
Readme updates
wonderwhy-er Jun 5, 2025
af885a4
Cleanup
wonderwhy-er Jun 5, 2025
aa4392a
Merge remote-tracking branch 'origin/main' into file-write-read-impro…
wonderwhy-er Jun 5, 2025
4e06da3
Updates tp readme/faq/website
wonderwhy-er Jun 5, 2025
20e2b5a
Merge branch 'repl-ssh-terminal-support' into test
wonderwhy-er Jun 6, 2025
3f5c4f2
Updates tp readme/faq/website
wonderwhy-er Jun 7, 2025
2652b3a
Improve work on windows
wonderwhy-er Jun 7, 2025
f42b5c5
Merge branch 'repl-ssh-terminal-support' into test
wonderwhy-er Jun 7, 2025
065a4b4
Detect and adjust prompts based on OS
wonderwhy-er Jun 7, 2025
46664c4
Merge remote-tracking branch 'origin/main' into improve-prompts-for-c…
wonderwhy-er Jun 25, 2025
9f30599
Prompt cleanups
wonderwhy-er Jun 29, 2025
456370a
Improvements for interactive shells
wonderwhy-er Jun 29, 2025
a56c90e
ssh fixes
wonderwhy-er Jun 30, 2025
9dc81bf
Add remaining lines to read file responses
wonderwhy-er Jul 2, 2025
9e9c386
Truncate search results when too big, currently too big responses not…
wonderwhy-er Jul 2, 2025
ab7d5e5
Improve truncation for search and other commands, some cleanups
wonderwhy-er Jul 2, 2025
9e4e173
Add support for negative file reads
wonderwhy-er Jun 4, 2025
bf6cc39
experiments with repl and terminal
wonderwhy-er May 3, 2025
1b1cec5
bit more
wonderwhy-er May 3, 2025
2aadfae
bit more changes
wonderwhy-er May 4, 2025
e84eb8c
Updates to working with interactive processes
wonderwhy-er Jun 2, 2025
c3b2534
Improved prompt for repls
wonderwhy-er Jun 2, 2025
367cc76
Cleanup
wonderwhy-er Jun 2, 2025
e2b570b
Cleanup
wonderwhy-er Jun 5, 2025
7e3a748
Cleanup
wonderwhy-er Jun 5, 2025
cb1ef89
Updates tp readme/faq/website
wonderwhy-er Jun 7, 2025
71ee551
Detect and adjust prompts based on OS
wonderwhy-er Jun 7, 2025
51ff7df
Prompt cleanups
wonderwhy-er Jun 29, 2025
a472410
Improvements for interactive shells
wonderwhy-er Jun 29, 2025
f4becdb
ssh fixes
wonderwhy-er Jun 30, 2025
808f04a
Add remaining lines to read file responses
wonderwhy-er Jul 2, 2025
ca4438a
Truncate search results when too big, currently too big responses not…
wonderwhy-er Jul 2, 2025
0f8c507
Improve truncation for search and other commands, some cleanups
wonderwhy-er Jul 2, 2025
1a6af62
Merge fixes
wonderwhy-er Jul 2, 2025
29c6e47
Add missing test files for enhanced file reading and search truncation
wonderwhy-er Jul 2, 2025
25c4ccd
Add back OS detection
wonderwhy-er Jul 2, 2025
1969cf3
Merge remote-tracking branch 'origin/improve-prompts-for-cross-os-wor…
wonderwhy-er Jul 2, 2025
7656e75
Improve process output formatting with consistent emoji usage
wonderwhy-er Jul 2, 2025
2a898e0
Remove wrong files added by accident
wonderwhy-er Jul 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,7 @@ coverage/
.idea
.history

server.log
server.log

# Local planning/documentation directories
plans/
34 changes: 28 additions & 6 deletions src/handlers/edit-search-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,17 @@ export async function handleSearchCode(args: unknown): Promise<ServerResult> {
const parsed = SearchCodeArgsSchema.parse(args);
const timeoutMs = parsed.timeoutMs || 30000; // 30 seconds default

// Limit maxResults to prevent overwhelming responses
const safeMaxResults = parsed.maxResults ? Math.min(parsed.maxResults, 5000) : 2000; // Default to 2000 instead of 1000

// Apply timeout at the handler level
const searchOperation = async () => {
return await searchTextInFiles({
rootPath: parsed.path,
pattern: parsed.pattern,
filePattern: parsed.filePattern,
ignoreCase: parsed.ignoreCase,
maxResults: parsed.maxResults,
maxResults: safeMaxResults,
includeHidden: parsed.includeHidden,
contextLines: parsed.contextLines,
// Don't pass timeoutMs down to the implementation
Expand Down Expand Up @@ -72,17 +75,36 @@ export async function handleSearchCode(args: unknown): Promise<ServerResult> {
};
}

// Format the results in a VS Code-like format
// Format the results in a VS Code-like format with early truncation
let currentFile = "";
let formattedResults = "";
const MAX_RESPONSE_SIZE = 900000; // 900KB limit - well below the 1MB API limit
let resultsProcessed = 0;
let totalResults = results.length;

results.forEach(result => {
for (const result of results) {
// Check if adding this result would exceed our limit
const newFileHeader = result.file !== currentFile ? `\n${result.file}:\n` : '';
const newLine = ` ${result.line}: ${result.match}\n`;
const potentialAddition = newFileHeader + newLine;

// If adding this would exceed the limit, truncate here
if (formattedResults.length + potentialAddition.length > MAX_RESPONSE_SIZE) {
const remainingResults = totalResults - resultsProcessed;
const avgResultLength = formattedResults.length / Math.max(resultsProcessed, 1);
const estimatedRemainingChars = remainingResults * avgResultLength;
const truncationMessage = `\n\n[Results truncated - ${remainingResults} more results available (approximately ${Math.round(estimatedRemainingChars).toLocaleString()} more characters). Try refining your search pattern or using a more specific file pattern to get fewer results.]`;
formattedResults += truncationMessage;
break;
}

if (result.file !== currentFile) {
formattedResults += `\n${result.file}:\n`;
formattedResults += newFileHeader;
currentFile = result.file;
}
formattedResults += ` ${result.line}: ${result.match}\n`;
});
formattedResults += newLine;
resultsProcessed++;
}

return {
content: [{type: "text", text: formattedResults.trim()}],
Expand Down
87 changes: 25 additions & 62 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@ import {
type CallToolRequest,
} from "@modelcontextprotocol/sdk/types.js";
import {zodToJsonSchema} from "zod-to-json-schema";
import { getSystemInfo, getOSSpecificGuidance, getPathGuidance, getDevelopmentToolGuidance } from './utils/system-info.js';

// Shared constants for tool descriptions
const PATH_GUIDANCE = `IMPORTANT: Always use absolute paths (starting with '/' or drive letter like 'C:\\') for reliability. Relative paths may fail as they depend on the current working directory. Tilde paths (~/...) might not work in all contexts. Unless the user explicitly asks for relative paths, use absolute paths.`;
// Get system information once at startup
const SYSTEM_INFO = getSystemInfo();
const OS_GUIDANCE = getOSSpecificGuidance(SYSTEM_INFO);
const DEV_TOOL_GUIDANCE = getDevelopmentToolGuidance(SYSTEM_INFO);
const PATH_GUIDANCE = `IMPORTANT: ${getPathGuidance(SYSTEM_INFO)} Relative paths may fail as they depend on the current working directory. Tilde paths (~/...) might not work in all contexts. Unless the user explicitly asks for relative paths, use absolute paths.`;

const CMD_PREFIX_DESCRIPTION = `This command can be referenced as "DC: ..." or "use Desktop Commander to ..." in your instructions.`;

Expand Down Expand Up @@ -79,15 +83,6 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
console.error("Generating tools list...");
return {
tools: [
// 🚨🚨🚨 IMPORTANT: LOCAL FILE ANALYSIS TOOL SELECTION 🚨🚨🚨
//
// FOR ANY LOCAL FILE WORK (CSV, JSON, logs, data analysis):
// ✅ ALWAYS USE: start_process + interact_with_process (with Python, R, etc.)
// ❌ NEVER USE: Analysis/REPL tool (CANNOT access local files and WILL FAIL)
//
// The analysis tool is browser-only and cannot read files from the user's computer.
// Always use processes for local file analysis!

// Configuration tools
{
name: "get_config",
Expand All @@ -99,7 +94,8 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
- fileReadLineLimit (max lines for read_file, default 1000)
- fileWriteLineLimit (max lines per write_file call, default 50)
- telemetryEnabled (boolean for telemetry opt-in/out)
- version (version of the DesktopCommander)
- version (version of the DesktopCommander)
- systemInfo (operating system and environment details)
${CMD_PREFIX_DESCRIPTION}`,
inputSchema: zodToJsonSchema(GetConfigArgsSchema),
},
Expand Down Expand Up @@ -185,15 +181,15 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
description: `
Write or append to file contents.

🎯 CHUNKING IS STANDARD PRACTICE: Always write files in chunks of 25-30 lines maximum.
CHUNKING IS STANDARD PRACTICE: Always write files in chunks of 25-30 lines maximum.
This is the normal, recommended way to write files - not an emergency measure.

STANDARD PROCESS FOR ANY FILE:
1. FIRST → write_file(filePath, firstChunk, {mode: 'rewrite'}) [≤30 lines]
2. THEN → write_file(filePath, secondChunk, {mode: 'append'}) [≤30 lines]
3. CONTINUE → write_file(filePath, nextChunk, {mode: 'append'}) [≤30 lines]

⚠️ ALWAYS CHUNK PROACTIVELY - don't wait for performance warnings!
ALWAYS CHUNK PROACTIVELY - don't wait for performance warnings!

WHEN TO CHUNK (always be proactive):
1. Any file expected to be longer than 25-30 lines
Expand Down Expand Up @@ -347,19 +343,13 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
description: `
Start a new terminal process with intelligent state detection.

🚨 PRIMARY TOOL FOR FILE ANALYSIS AND DATA PROCESSING
PRIMARY TOOL FOR FILE ANALYSIS AND DATA PROCESSING
This is the ONLY correct tool for analyzing local files (CSV, JSON, logs, etc.).
The analysis tool CANNOT access local files and WILL FAIL - always use processes for file-based work.

⚠️ CRITICAL RULE: For ANY local file work, ALWAYS use this tool + interact_with_process, NEVER use analysis/REPL tool.
CRITICAL RULE: For ANY local file work, ALWAYS use this tool + interact_with_process, NEVER use analysis/REPL tool.

🪟 WINDOWS SHELL TROUBLESHOOTING:
If Node.js or Python commands fail with "not recognized" errors on Windows:
- Try different shells: specify shell parameter as "cmd" or "powershell.exe"
- PowerShell may have execution policy restrictions for some tools
- CMD typically has better compatibility with development tools like Node.js/Python
- Example: start_process("node --version", shell="cmd") if PowerShell fails
- Use set_config_value to change defaultShell if needed
${OS_GUIDANCE}

REQUIRED WORKFLOW FOR LOCAL FILES:
1. start_process("python3 -i") - Start Python REPL for data analysis
Expand Down Expand Up @@ -389,12 +379,12 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
- Early exit prevents unnecessary waiting

STATES DETECTED:
🔄 Process waiting for input (shows prompt)
Process finished execution
Process running (use read_process_output)
Process waiting for input (shows prompt)
Process finished execution
Process running (use read_process_output)

ALWAYS USE FOR: Local file analysis, CSV processing, data exploration, system commands
NEVER USE ANALYSIS TOOL FOR: Local file access (analysis tool is browser-only and WILL FAIL)
ALWAYS USE FOR: Local file analysis, CSV processing, data exploration, system commands
NEVER USE ANALYSIS TOOL FOR: Local file access (analysis tool is browser-only and WILL FAIL)

${PATH_GUIDANCE}
${CMD_PREFIX_DESCRIPTION}`,
Expand All @@ -420,9 +410,9 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
- Works with Python, Node.js, R, Julia, etc.

DETECTION STATES:
🔄 Process waiting for input (ready for interact_with_process)
Process finished execution
⏱️ Timeout reached (may still be running)
Process waiting for input (ready for interact_with_process)
Process finished execution
Timeout reached (may still be running)

${CMD_PREFIX_DESCRIPTION}`,
inputSchema: zodToJsonSchema(ReadProcessOutputArgsSchema),
Expand All @@ -432,11 +422,11 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
description: `
Send input to a running process and automatically receive the response.

🚨 CRITICAL: THIS IS THE PRIMARY TOOL FOR ALL LOCAL FILE ANALYSIS
CRITICAL: THIS IS THE PRIMARY TOOL FOR ALL LOCAL FILE ANALYSIS
For ANY local file analysis (CSV, JSON, data processing), ALWAYS use this instead of the analysis tool.
The analysis tool CANNOT access local files and WILL FAIL - use processes for ALL file-based work.

📁 FILE ANALYSIS PRIORITY ORDER (MANDATORY):
FILE ANALYSIS PRIORITY ORDER (MANDATORY):
1. ALWAYS FIRST: Use this tool (start_process + interact_with_process) for local data analysis
2. ALTERNATIVE: Use command-line tools (cut, awk, grep) for quick processing
3. NEVER EVER: Use analysis tool for local file access (IT WILL FAIL)
Expand Down Expand Up @@ -470,8 +460,8 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {

Returns execution result with status indicators.

ALWAYS USE FOR: CSV analysis, JSON processing, file statistics, data visualization prep, ANY local file work
NEVER USE ANALYSIS TOOL FOR: Local file access (it cannot read files from disk and WILL FAIL)
ALWAYS USE FOR: CSV analysis, JSON processing, file statistics, data visualization prep, ANY local file work
NEVER USE ANALYSIS TOOL FOR: Local file access (it cannot read files from disk and WILL FAIL)

${CMD_PREFIX_DESCRIPTION}`,
inputSchema: zodToJsonSchema(InteractWithProcessArgsSchema),
Expand Down Expand Up @@ -522,33 +512,6 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
${CMD_PREFIX_DESCRIPTION}`,
inputSchema: zodToJsonSchema(KillProcessArgsSchema),
},

// 🚨🚨🚨 CRITICAL TOOL SELECTION GUIDANCE 🚨🚨🚨
//
// FOR LOCAL FILE ANALYSIS (CSV, JSON, logs, data files):
// ✅ ALWAYS USE: start_process + interact_with_process
// ✅ ALTERNATIVE: Command-line tools (read_file, search_code, cut, awk, grep)
// ❌ NEVER EVER USE: Analysis tool (CANNOT access local files and WILL FAIL)
//
// 🔴 ANALYSIS/REPL TOOL LIMITATIONS:
// - CANNOT read files from user's computer/file system
// - ONLY works with uploaded files or web data
// - Browser-based JavaScript runtime only
// - NO access to pandas, numpy, or local Python libraries
//
// 🟢 PROCESS TOOLS ADVANTAGES:
// - CAN access ALL local files
// - Full system power (Python, R, databases, etc.)
// - Handle files of ANY size
// - Access to all installed libraries and tools
//
// MANDATORY WORKFLOW FOR LOCAL FILES:
// 1. start_process("python3 -i")
// 2. interact_with_process(pid, "import pandas as pd")
// 3. interact_with_process(pid, "df = pd.read_csv('/path/to/file.csv')")
// 4. interact_with_process(pid, "print(df.head())")
//
// REMEMBER: "For local file analysis, ALWAYS use processes, NEVER use analysis tool"
],
};
} catch (error) {
Expand Down
90 changes: 71 additions & 19 deletions src/terminal-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { TerminalSession, CommandExecutionResult, ActiveSession } from './types.
import { DEFAULT_COMMAND_TIMEOUT } from './config.js';
import { configManager } from './config-manager.js';
import {capture} from "./utils/capture.js";
import { analyzeProcessState } from './utils/process-detection.js';

interface CompletedSession {
pid: number;
Expand Down Expand Up @@ -57,16 +58,28 @@ export class TerminalManager {

// For REPL interactions, we need to ensure stdin, stdout, and stderr are properly configured
// Note: No special stdio options needed here, Node.js handles pipes by default
const spawnOptions = {
shell: shellToUse

// Enhance SSH commands automatically
let enhancedCommand = command;
if (command.trim().startsWith('ssh ') && !command.includes(' -t')) {
enhancedCommand = command.replace(/^ssh /, 'ssh -t ');
console.log(`Enhanced SSH command: ${enhancedCommand}`);
}

const spawnOptions: any = {
shell: shellToUse,
env: {
...process.env,
TERM: 'xterm-256color' // Better terminal compatibility
}
};

// Spawn the process with an empty array of arguments and our options
const process = spawn(command, [], spawnOptions);
const childProcess = spawn(enhancedCommand, [], spawnOptions);
let output = '';

// Ensure process.pid is defined before proceeding
if (!process.pid) {
// Ensure childProcess.pid is defined before proceeding
if (!childProcess.pid) {
// Return a consistent error object instead of throwing
return {
pid: -1, // Use -1 to indicate an error state
Expand All @@ -76,42 +89,81 @@ export class TerminalManager {
}

const session: TerminalSession = {
pid: process.pid,
process,
pid: childProcess.pid,
process: childProcess,
lastOutput: '',
isBlocked: false,
startTime: new Date()
};

this.sessions.set(process.pid, session);
this.sessions.set(childProcess.pid, session);

return new Promise((resolve) => {
process.stdout.on('data', (data) => {
let resolved = false;
let periodicCheck: NodeJS.Timeout | null = null;

// Quick prompt patterns for immediate detection
const quickPromptPatterns = />>>\s*$|>\s*$|\$\s*$|#\s*$/;

const resolveOnce = (result: CommandExecutionResult) => {
if (resolved) return;
resolved = true;
if (periodicCheck) clearInterval(periodicCheck);
resolve(result);
};

childProcess.stdout.on('data', (data: any) => {
const text = data.toString();
output += text;
session.lastOutput += text;

// Immediate check for obvious prompts
if (quickPromptPatterns.test(text)) {
session.isBlocked = true;
resolveOnce({
pid: childProcess.pid!,
output,
isBlocked: true
});
}
});

process.stderr.on('data', (data) => {
childProcess.stderr.on('data', (data: any) => {
const text = data.toString();
output += text;
session.lastOutput += text;
});

// Periodic comprehensive check every 100ms
periodicCheck = setInterval(() => {
if (output.trim()) {
const processState = analyzeProcessState(output, childProcess.pid);
if (processState.isWaitingForInput) {
session.isBlocked = true;
resolveOnce({
pid: childProcess.pid!,
output,
isBlocked: true
});
}
}
}, 100);

// Timeout fallback
setTimeout(() => {
session.isBlocked = true;
resolve({
pid: process.pid!,
resolveOnce({
pid: childProcess.pid!,
output,
isBlocked: true
});
}, timeoutMs);

process.on('exit', (code) => {
if (process.pid) {
childProcess.on('exit', (code: any) => {
if (childProcess.pid) {
// Store completed session before removing active session
this.completedSessions.set(process.pid, {
pid: process.pid,
this.completedSessions.set(childProcess.pid, {
pid: childProcess.pid,
output: output + session.lastOutput, // Combine all output
exitCode: code,
startTime: session.startTime,
Expand All @@ -124,10 +176,10 @@ export class TerminalManager {
this.completedSessions.delete(oldestKey);
}

this.sessions.delete(process.pid);
this.sessions.delete(childProcess.pid);
}
resolve({
pid: process.pid!,
resolveOnce({
pid: childProcess.pid!,
output,
isBlocked: false
});
Expand Down
Loading