@wonderwhy-er/desktop-commander 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Eduard Ruzga
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,106 @@
1
+ # Claude Computer Commander
2
+
3
+ Short version. Two key things. Terminal commands and diff based file editing.
4
+
5
+ This server that allows Claude desktop app to execute long-running terminal commands on your computer and manage processes through Model Context Protocol (MCP) + Built on top of [MCP Filesystem Server](https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem) to provide additional file editing capabilities .
6
+
7
+ ## Features
8
+
9
+ - Execute terminal commands with output streaming
10
+ - Command timeout and background execution support
11
+ - Process management (list and kill processes)
12
+ - Session management for long-running commands
13
+ - Full filesystem operations:
14
+ - Read/write files
15
+ - Create/list directories
16
+ - Move files/directories
17
+ - Search files
18
+ - Get file metadata
19
+ - Code editing capabilities:
20
+ - Surgical text replacements for small changes
21
+ - Full file rewrites for major changes
22
+ - Multiple file support
23
+ - Pattern-based replacements
24
+
25
+ ## Setup
26
+
27
+ 1. Clone and build:
28
+ ```bash
29
+ git clone https://github.com/wonderwhy-er/ClaudeComputerCommander.git
30
+ cd ClaudeComputerCommander
31
+ npm run setup
32
+ ```
33
+
34
+ The setup command will:
35
+ - Install dependencies
36
+ - Build the server
37
+ - Configure Claude's desktop app
38
+ - Add MCP servers to Claude's config
39
+
40
+ ## Usage
41
+
42
+ The server provides these tool categories:
43
+
44
+ ### Terminal Tools
45
+ - `execute_command`: Run commands with configurable timeout
46
+ - `read_output`: Get output from long-running commands
47
+ - `force_terminate`: Stop running command sessions
48
+ - `list_sessions`: View active command sessions
49
+ - `list_processes`: View system processes
50
+ - `kill_process`: Terminate processes by PID
51
+ - `block_command`/`unblock_command`: Manage command blacklist
52
+
53
+ ### Filesystem Tools
54
+ - `read_file`/`write_file`: File operations
55
+ - `create_directory`/`list_directory`: Directory management
56
+ - `move_file`: Move/rename files
57
+ - `search_files`: Pattern-based file search
58
+ - `get_file_info`: File metadata
59
+
60
+ ### Edit Tools
61
+ - `edit_block`: Apply surgical text replacements (best for changes <20% of file size)
62
+ - `write_file`: Complete file rewrites (best for large changes >20% or when edit_block fails)
63
+
64
+ Search/Replace Block Format:
65
+ ```
66
+ filepath.ext
67
+ <<<<<<< SEARCH
68
+ existing code to replace
69
+ =======
70
+ new code to insert
71
+ >>>>>>> REPLACE
72
+ ```
73
+
74
+ Example:
75
+ ```
76
+ src/main.js
77
+ <<<<<<< SEARCH
78
+ console.log("old message");
79
+ =======
80
+ console.log("new message");
81
+ >>>>>>> REPLACE
82
+ ```
83
+
84
+ ## Handling Long-Running Commands
85
+
86
+ For commands that may take a while:
87
+
88
+ 1. `execute_command` returns after timeout with initial output
89
+ 2. Command continues in background
90
+ 3. Use `read_output` with PID to get new output
91
+ 4. Use `force_terminate` to stop if needed
92
+
93
+ ## Model Context Protocol Integration
94
+
95
+ This project extends the MCP Filesystem Server to enable:
96
+ - Local server support in Claude Desktop
97
+ - Full system command execution
98
+ - Process management
99
+ - File operations
100
+ - Code editing with search/replace blocks
101
+
102
+ Created as part of exploring Claude MCPs: https://youtube.com/live/TlbjFDbl5Us
103
+
104
+ ## License
105
+
106
+ MIT
@@ -0,0 +1,11 @@
1
+ declare class CommandManager {
2
+ private blockedCommands;
3
+ loadBlockedCommands(): Promise<void>;
4
+ saveBlockedCommands(): Promise<void>;
5
+ validateCommand(command: string): boolean;
6
+ blockCommand(command: string): Promise<boolean>;
7
+ unblockCommand(command: string): Promise<boolean>;
8
+ listBlockedCommands(): string[];
9
+ }
10
+ export declare const commandManager: CommandManager;
11
+ export {};
@@ -0,0 +1,54 @@
1
+ import fs from 'fs/promises';
2
+ import { CONFIG_FILE } from './config.js';
3
+ class CommandManager {
4
+ constructor() {
5
+ this.blockedCommands = new Set();
6
+ }
7
+ async loadBlockedCommands() {
8
+ try {
9
+ const configData = await fs.readFile(CONFIG_FILE, 'utf-8');
10
+ const config = JSON.parse(configData);
11
+ this.blockedCommands = new Set(config.blockedCommands);
12
+ }
13
+ catch (error) {
14
+ this.blockedCommands = new Set();
15
+ }
16
+ }
17
+ async saveBlockedCommands() {
18
+ try {
19
+ const config = {
20
+ blockedCommands: Array.from(this.blockedCommands)
21
+ };
22
+ await fs.writeFile(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf-8');
23
+ }
24
+ catch (error) {
25
+ // Handle error if needed
26
+ }
27
+ }
28
+ validateCommand(command) {
29
+ const baseCommand = command.split(' ')[0].toLowerCase().trim();
30
+ return !this.blockedCommands.has(baseCommand);
31
+ }
32
+ async blockCommand(command) {
33
+ command = command.toLowerCase().trim();
34
+ if (this.blockedCommands.has(command)) {
35
+ return false;
36
+ }
37
+ this.blockedCommands.add(command);
38
+ await this.saveBlockedCommands();
39
+ return true;
40
+ }
41
+ async unblockCommand(command) {
42
+ command = command.toLowerCase().trim();
43
+ if (!this.blockedCommands.has(command)) {
44
+ return false;
45
+ }
46
+ this.blockedCommands.delete(command);
47
+ await this.saveBlockedCommands();
48
+ return true;
49
+ }
50
+ listBlockedCommands() {
51
+ return Array.from(this.blockedCommands).sort();
52
+ }
53
+ }
54
+ export const commandManager = new CommandManager();
@@ -0,0 +1,4 @@
1
+ export declare const CONFIG_FILE: string;
2
+ export declare const LOG_FILE: string;
3
+ export declare const ERROR_LOG_FILE: string;
4
+ export declare const DEFAULT_COMMAND_TIMEOUT = 1000;
package/dist/config.js ADDED
@@ -0,0 +1,6 @@
1
+ import path from 'path';
2
+ import process from 'process';
3
+ export const CONFIG_FILE = path.join(process.cwd(), 'config.json');
4
+ export const LOG_FILE = path.join(process.cwd(), 'server.log');
5
+ export const ERROR_LOG_FILE = path.join(process.cwd(), 'error.log');
6
+ export const DEFAULT_COMMAND_TIMEOUT = 1000; // milliseconds
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env node
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { server } from './server.js';
4
+ import { commandManager } from './command-manager.js';
5
+ async function runServer() {
6
+ try {
7
+ // Handle uncaught exceptions
8
+ process.on('uncaughtException', async (error) => {
9
+ const errorMessage = error instanceof Error ? error.message : String(error);
10
+ process.exit(1);
11
+ });
12
+ // Handle unhandled rejections
13
+ process.on('unhandledRejection', async (reason) => {
14
+ const errorMessage = reason instanceof Error ? reason.message : String(reason);
15
+ process.exit(1);
16
+ });
17
+ const transport = new StdioServerTransport();
18
+ // Load blocked commands from config file
19
+ await commandManager.loadBlockedCommands();
20
+ await server.connect(transport);
21
+ }
22
+ catch (error) {
23
+ const errorMessage = error instanceof Error ? error.message : String(error);
24
+ process.stderr.write(JSON.stringify({
25
+ type: 'error',
26
+ timestamp: new Date().toISOString(),
27
+ message: `Failed to start server: ${errorMessage}`
28
+ }) + '\n');
29
+ process.exit(1);
30
+ }
31
+ }
32
+ runServer().catch(async (error) => {
33
+ const errorMessage = error instanceof Error ? error.message : String(error);
34
+ process.stderr.write(JSON.stringify({
35
+ type: 'error',
36
+ timestamp: new Date().toISOString(),
37
+ message: `Fatal error running server: ${errorMessage}`
38
+ }) + '\n');
39
+ process.exit(1);
40
+ });
@@ -0,0 +1,2 @@
1
+ export declare function logToFile(message: string): Promise<void>;
2
+ export declare function logError(message: string): Promise<void>;
@@ -0,0 +1,28 @@
1
+ import { appendFileSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import { dirname } from 'path';
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = dirname(__filename);
7
+ const LOG_FILE = join(__dirname, '..', 'server.log');
8
+ const ERROR_LOG_FILE = join(__dirname, '..', 'error.log');
9
+ export async function logToFile(message) {
10
+ const timestamp = new Date().toISOString();
11
+ const logMessage = `${timestamp} - ${message}\n`;
12
+ try {
13
+ appendFileSync(LOG_FILE, logMessage);
14
+ }
15
+ catch (err) {
16
+ console.error('Failed to write to log file:', err);
17
+ }
18
+ }
19
+ export async function logError(message) {
20
+ const timestamp = new Date().toISOString();
21
+ const logMessage = `${timestamp} - ERROR: ${message}\n`;
22
+ try {
23
+ appendFileSync(ERROR_LOG_FILE, logMessage);
24
+ }
25
+ catch (err) {
26
+ console.error('Failed to write to error log file:', err);
27
+ }
28
+ }
@@ -0,0 +1,20 @@
1
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
+ export declare const server: Server<{
3
+ method: string;
4
+ params?: import("zod").objectOutputType<{
5
+ _meta: import("zod").ZodOptional<import("zod").ZodObject<{
6
+ progressToken: import("zod").ZodOptional<import("zod").ZodUnion<[import("zod").ZodString, import("zod").ZodNumber]>>;
7
+ }, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{
8
+ progressToken: import("zod").ZodOptional<import("zod").ZodUnion<[import("zod").ZodString, import("zod").ZodNumber]>>;
9
+ }, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{
10
+ progressToken: import("zod").ZodOptional<import("zod").ZodUnion<[import("zod").ZodString, import("zod").ZodNumber]>>;
11
+ }, import("zod").ZodTypeAny, "passthrough">>>;
12
+ }, import("zod").ZodTypeAny, "passthrough"> | undefined;
13
+ }, {
14
+ method: string;
15
+ params?: import("zod").objectOutputType<{
16
+ _meta: import("zod").ZodOptional<import("zod").ZodObject<{}, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{}, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{}, import("zod").ZodTypeAny, "passthrough">>>;
17
+ }, import("zod").ZodTypeAny, "passthrough"> | undefined;
18
+ }, import("zod").objectOutputType<{
19
+ _meta: import("zod").ZodOptional<import("zod").ZodObject<{}, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{}, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{}, import("zod").ZodTypeAny, "passthrough">>>;
20
+ }, import("zod").ZodTypeAny, "passthrough">>;
package/dist/server.js ADDED
@@ -0,0 +1,287 @@
1
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
3
+ import { zodToJsonSchema } from "zod-to-json-schema";
4
+ import { commandManager } from './command-manager.js';
5
+ import { ExecuteCommandArgsSchema, ReadOutputArgsSchema, ForceTerminateArgsSchema, ListSessionsArgsSchema, KillProcessArgsSchema, BlockCommandArgsSchema, UnblockCommandArgsSchema, ReadFileArgsSchema, ReadMultipleFilesArgsSchema, WriteFileArgsSchema, CreateDirectoryArgsSchema, ListDirectoryArgsSchema, MoveFileArgsSchema, SearchFilesArgsSchema, GetFileInfoArgsSchema, EditBlockArgsSchema, } from './tools/schemas.js';
6
+ import { executeCommand, readOutput, forceTerminate, listSessions } from './tools/execute.js';
7
+ import { listProcesses, killProcess } from './tools/process.js';
8
+ import { readFile, readMultipleFiles, writeFile, createDirectory, listDirectory, moveFile, searchFiles, getFileInfo, listAllowedDirectories, } from './tools/filesystem.js';
9
+ import { parseEditBlock, performSearchReplace } from './tools/edit.js';
10
+ export const server = new Server({
11
+ name: "secure-terminal-server",
12
+ version: "0.2.0",
13
+ }, {
14
+ capabilities: {
15
+ tools: {},
16
+ },
17
+ });
18
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
19
+ return {
20
+ tools: [
21
+ // Terminal tools
22
+ {
23
+ name: "execute_command",
24
+ description: "Execute a terminal command with timeout. Command will continue running in background if it doesn't complete within timeout.",
25
+ inputSchema: zodToJsonSchema(ExecuteCommandArgsSchema),
26
+ },
27
+ {
28
+ name: "read_output",
29
+ description: "Read new output from a running terminal session.",
30
+ inputSchema: zodToJsonSchema(ReadOutputArgsSchema),
31
+ },
32
+ {
33
+ name: "force_terminate",
34
+ description: "Force terminate a running terminal session.",
35
+ inputSchema: zodToJsonSchema(ForceTerminateArgsSchema),
36
+ },
37
+ {
38
+ name: "list_sessions",
39
+ description: "List all active terminal sessions.",
40
+ inputSchema: zodToJsonSchema(ListSessionsArgsSchema),
41
+ },
42
+ {
43
+ name: "list_processes",
44
+ description: "List all running processes. Returns process information including PID, " +
45
+ "command name, CPU usage, and memory usage.",
46
+ inputSchema: {
47
+ type: "object",
48
+ properties: {},
49
+ required: [],
50
+ },
51
+ },
52
+ {
53
+ name: "kill_process",
54
+ description: "Terminate a running process by PID. Use with caution as this will " +
55
+ "forcefully terminate the specified process.",
56
+ inputSchema: zodToJsonSchema(KillProcessArgsSchema),
57
+ },
58
+ {
59
+ name: "block_command",
60
+ description: "Add a command to the blacklist. Once blocked, the command cannot be executed until unblocked.",
61
+ inputSchema: zodToJsonSchema(BlockCommandArgsSchema),
62
+ },
63
+ {
64
+ name: "unblock_command",
65
+ description: "Remove a command from the blacklist. Once unblocked, the command can be executed normally.",
66
+ inputSchema: zodToJsonSchema(UnblockCommandArgsSchema),
67
+ },
68
+ {
69
+ name: "list_blocked_commands",
70
+ description: "List all currently blocked commands.",
71
+ inputSchema: {
72
+ type: "object",
73
+ properties: {},
74
+ required: [],
75
+ },
76
+ },
77
+ // Filesystem tools
78
+ {
79
+ name: "read_file",
80
+ description: "Read the complete contents of a file from the file system. " +
81
+ "Handles various text encodings and provides detailed error messages " +
82
+ "if the file cannot be read. Only works within allowed directories.",
83
+ inputSchema: zodToJsonSchema(ReadFileArgsSchema),
84
+ },
85
+ {
86
+ name: "read_multiple_files",
87
+ description: "Read the contents of multiple files simultaneously. " +
88
+ "Each file's content is returned with its path as a reference. " +
89
+ "Failed reads for individual files won't stop the entire operation. " +
90
+ "Only works within allowed directories.",
91
+ inputSchema: zodToJsonSchema(ReadMultipleFilesArgsSchema),
92
+ },
93
+ {
94
+ name: "write_file",
95
+ description: "Completely replace file contents. Best for large changes (>20% of file) or when edit_block fails. " +
96
+ "Use with caution as it will overwrite existing files. Only works within allowed directories.",
97
+ inputSchema: zodToJsonSchema(WriteFileArgsSchema),
98
+ },
99
+ {
100
+ name: "create_directory",
101
+ description: "Create a new directory or ensure a directory exists. Can create multiple " +
102
+ "nested directories in one operation. Only works within allowed directories.",
103
+ inputSchema: zodToJsonSchema(CreateDirectoryArgsSchema),
104
+ },
105
+ {
106
+ name: "list_directory",
107
+ description: "Get a detailed listing of all files and directories in a specified path. " +
108
+ "Results distinguish between files and directories with [FILE] and [DIR] prefixes. " +
109
+ "Only works within allowed directories.",
110
+ inputSchema: zodToJsonSchema(ListDirectoryArgsSchema),
111
+ },
112
+ {
113
+ name: "move_file",
114
+ description: "Move or rename files and directories. Can move files between directories " +
115
+ "and rename them in a single operation. Both source and destination must be " +
116
+ "within allowed directories.",
117
+ inputSchema: zodToJsonSchema(MoveFileArgsSchema),
118
+ },
119
+ {
120
+ name: "search_files",
121
+ description: "Recursively search for files and directories matching a pattern. " +
122
+ "Searches through all subdirectories from the starting path. " +
123
+ "Only searches within allowed directories.",
124
+ inputSchema: zodToJsonSchema(SearchFilesArgsSchema),
125
+ },
126
+ {
127
+ name: "get_file_info",
128
+ description: "Retrieve detailed metadata about a file or directory including size, " +
129
+ "creation time, last modified time, permissions, and type. " +
130
+ "Only works within allowed directories.",
131
+ inputSchema: zodToJsonSchema(GetFileInfoArgsSchema),
132
+ },
133
+ {
134
+ name: "list_allowed_directories",
135
+ description: "Returns the list of directories that this server is allowed to access.",
136
+ inputSchema: {
137
+ type: "object",
138
+ properties: {},
139
+ required: [],
140
+ },
141
+ },
142
+ {
143
+ name: "edit_block",
144
+ description: "Apply surgical text replacements to files. Best for small changes (<20% of file size). " +
145
+ "Multiple blocks can be used for separate changes. Will verify changes after application. " +
146
+ "Format: filepath, then <<<<<<< SEARCH, content to find, =======, new content, >>>>>>> REPLACE.",
147
+ inputSchema: zodToJsonSchema(EditBlockArgsSchema),
148
+ },
149
+ ],
150
+ };
151
+ });
152
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
153
+ try {
154
+ const { name, arguments: args } = request.params;
155
+ switch (name) {
156
+ // Terminal tools
157
+ case "execute_command": {
158
+ const parsed = ExecuteCommandArgsSchema.parse(args);
159
+ return executeCommand(parsed);
160
+ }
161
+ case "read_output": {
162
+ const parsed = ReadOutputArgsSchema.parse(args);
163
+ return readOutput(parsed);
164
+ }
165
+ case "force_terminate": {
166
+ const parsed = ForceTerminateArgsSchema.parse(args);
167
+ return forceTerminate(parsed);
168
+ }
169
+ case "list_sessions":
170
+ return listSessions();
171
+ case "list_processes":
172
+ return listProcesses();
173
+ case "kill_process": {
174
+ const parsed = KillProcessArgsSchema.parse(args);
175
+ return killProcess(parsed);
176
+ }
177
+ case "block_command": {
178
+ const parsed = BlockCommandArgsSchema.parse(args);
179
+ const blockResult = await commandManager.blockCommand(parsed.command);
180
+ return {
181
+ content: [{ type: "text", text: blockResult }],
182
+ };
183
+ }
184
+ case "unblock_command": {
185
+ const parsed = UnblockCommandArgsSchema.parse(args);
186
+ const unblockResult = await commandManager.unblockCommand(parsed.command);
187
+ return {
188
+ content: [{ type: "text", text: unblockResult }],
189
+ };
190
+ }
191
+ case "list_blocked_commands": {
192
+ const blockedCommands = await commandManager.listBlockedCommands();
193
+ return {
194
+ content: [{ type: "text", text: blockedCommands.join('\n') }],
195
+ };
196
+ }
197
+ // Filesystem tools
198
+ case "edit_block": {
199
+ const parsed = EditBlockArgsSchema.parse(args);
200
+ const { filePath, searchReplace } = await parseEditBlock(parsed.blockContent);
201
+ await performSearchReplace(filePath, searchReplace);
202
+ return {
203
+ content: [{ type: "text", text: `Successfully applied edit to ${filePath}` }],
204
+ };
205
+ }
206
+ case "read_file": {
207
+ const parsed = ReadFileArgsSchema.parse(args);
208
+ const content = await readFile(parsed.path);
209
+ return {
210
+ content: [{ type: "text", text: content }],
211
+ };
212
+ }
213
+ case "read_multiple_files": {
214
+ const parsed = ReadMultipleFilesArgsSchema.parse(args);
215
+ const results = await readMultipleFiles(parsed.paths);
216
+ return {
217
+ content: [{ type: "text", text: results.join("\n---\n") }],
218
+ };
219
+ }
220
+ case "write_file": {
221
+ const parsed = WriteFileArgsSchema.parse(args);
222
+ await writeFile(parsed.path, parsed.content);
223
+ return {
224
+ content: [{ type: "text", text: `Successfully wrote to ${parsed.path}` }],
225
+ };
226
+ }
227
+ case "create_directory": {
228
+ const parsed = CreateDirectoryArgsSchema.parse(args);
229
+ await createDirectory(parsed.path);
230
+ return {
231
+ content: [{ type: "text", text: `Successfully created directory ${parsed.path}` }],
232
+ };
233
+ }
234
+ case "list_directory": {
235
+ const parsed = ListDirectoryArgsSchema.parse(args);
236
+ const entries = await listDirectory(parsed.path);
237
+ return {
238
+ content: [{ type: "text", text: entries.join('\n') }],
239
+ };
240
+ }
241
+ case "move_file": {
242
+ const parsed = MoveFileArgsSchema.parse(args);
243
+ await moveFile(parsed.source, parsed.destination);
244
+ return {
245
+ content: [{ type: "text", text: `Successfully moved ${parsed.source} to ${parsed.destination}` }],
246
+ };
247
+ }
248
+ case "search_files": {
249
+ const parsed = SearchFilesArgsSchema.parse(args);
250
+ const results = await searchFiles(parsed.path, parsed.pattern);
251
+ return {
252
+ content: [{ type: "text", text: results.length > 0 ? results.join('\n') : "No matches found" }],
253
+ };
254
+ }
255
+ case "get_file_info": {
256
+ const parsed = GetFileInfoArgsSchema.parse(args);
257
+ const info = await getFileInfo(parsed.path);
258
+ return {
259
+ content: [{
260
+ type: "text",
261
+ text: Object.entries(info)
262
+ .map(([key, value]) => `${key}: ${value}`)
263
+ .join('\n')
264
+ }],
265
+ };
266
+ }
267
+ case "list_allowed_directories": {
268
+ const directories = listAllowedDirectories();
269
+ return {
270
+ content: [{
271
+ type: "text",
272
+ text: `Allowed directories:\n${directories.join('\n')}`
273
+ }],
274
+ };
275
+ }
276
+ default:
277
+ throw new Error(`Unknown tool: ${name}`);
278
+ }
279
+ }
280
+ catch (error) {
281
+ const errorMessage = error instanceof Error ? error.message : String(error);
282
+ return {
283
+ content: [{ type: "text", text: `Error: ${errorMessage}` }],
284
+ isError: true,
285
+ };
286
+ }
287
+ });
@@ -0,0 +1,9 @@
1
+ import { CommandExecutionResult, ActiveSession } from './types.js';
2
+ export declare class TerminalManager {
3
+ private sessions;
4
+ executeCommand(command: string, timeoutMs?: number): Promise<CommandExecutionResult>;
5
+ getNewOutput(pid: number): string | null;
6
+ forceTerminate(pid: number): boolean;
7
+ listActiveSessions(): ActiveSession[];
8
+ }
9
+ export declare const terminalManager: TerminalManager;