@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 +21 -0
- package/README.md +106 -0
- package/dist/command-manager.d.ts +11 -0
- package/dist/command-manager.js +54 -0
- package/dist/config.d.ts +4 -0
- package/dist/config.js +6 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +40 -0
- package/dist/logging.d.ts +2 -0
- package/dist/logging.js +28 -0
- package/dist/server.d.ts +20 -0
- package/dist/server.js +287 -0
- package/dist/terminal-manager.d.ts +9 -0
- package/dist/terminal-manager.js +90 -0
- package/dist/tools/command-block.d.ts +18 -0
- package/dist/tools/command-block.js +62 -0
- package/dist/tools/edit.d.ts +10 -0
- package/dist/tools/edit.js +33 -0
- package/dist/tools/execute.d.ts +24 -0
- package/dist/tools/execute.js +60 -0
- package/dist/tools/filesystem.d.ts +10 -0
- package/dist/tools/filesystem.js +133 -0
- package/dist/tools/process.d.ts +12 -0
- package/dist/tools/process.js +47 -0
- package/dist/tools/schemas.d.ts +119 -0
- package/dist/tools/schemas.js +54 -0
- package/dist/types.d.ts +24 -0
- package/dist/types.js +1 -0
- package/package.json +56 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import { DEFAULT_COMMAND_TIMEOUT } from './config.js';
|
|
3
|
+
export class TerminalManager {
|
|
4
|
+
constructor() {
|
|
5
|
+
this.sessions = new Map();
|
|
6
|
+
}
|
|
7
|
+
async executeCommand(command, timeoutMs = DEFAULT_COMMAND_TIMEOUT) {
|
|
8
|
+
const process = spawn(command, [], { shell: true });
|
|
9
|
+
let output = '';
|
|
10
|
+
// Ensure process.pid is defined before proceeding
|
|
11
|
+
if (!process.pid) {
|
|
12
|
+
throw new Error('Failed to get process ID');
|
|
13
|
+
}
|
|
14
|
+
const session = {
|
|
15
|
+
pid: process.pid,
|
|
16
|
+
process,
|
|
17
|
+
lastOutput: '',
|
|
18
|
+
isBlocked: false,
|
|
19
|
+
startTime: new Date()
|
|
20
|
+
};
|
|
21
|
+
this.sessions.set(process.pid, session);
|
|
22
|
+
return new Promise((resolve) => {
|
|
23
|
+
process.stdout.on('data', (data) => {
|
|
24
|
+
const text = data.toString();
|
|
25
|
+
output += text;
|
|
26
|
+
session.lastOutput += text;
|
|
27
|
+
});
|
|
28
|
+
process.stderr.on('data', (data) => {
|
|
29
|
+
const text = data.toString();
|
|
30
|
+
output += text;
|
|
31
|
+
session.lastOutput += text;
|
|
32
|
+
});
|
|
33
|
+
setTimeout(() => {
|
|
34
|
+
session.isBlocked = true;
|
|
35
|
+
resolve({
|
|
36
|
+
pid: process.pid,
|
|
37
|
+
output,
|
|
38
|
+
isBlocked: true
|
|
39
|
+
});
|
|
40
|
+
}, timeoutMs);
|
|
41
|
+
process.on('exit', () => {
|
|
42
|
+
if (process.pid) {
|
|
43
|
+
this.sessions.delete(process.pid);
|
|
44
|
+
}
|
|
45
|
+
resolve({
|
|
46
|
+
pid: process.pid,
|
|
47
|
+
output,
|
|
48
|
+
isBlocked: false
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
getNewOutput(pid) {
|
|
54
|
+
const session = this.sessions.get(pid);
|
|
55
|
+
if (!session) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
const output = session.lastOutput;
|
|
59
|
+
session.lastOutput = '';
|
|
60
|
+
return output;
|
|
61
|
+
}
|
|
62
|
+
forceTerminate(pid) {
|
|
63
|
+
const session = this.sessions.get(pid);
|
|
64
|
+
if (!session) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
session.process.kill('SIGINT');
|
|
69
|
+
setTimeout(() => {
|
|
70
|
+
if (this.sessions.has(pid)) {
|
|
71
|
+
session.process.kill('SIGKILL');
|
|
72
|
+
}
|
|
73
|
+
}, 1000);
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
console.error(`Failed to terminate process ${pid}:`, error);
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
listActiveSessions() {
|
|
82
|
+
const now = new Date();
|
|
83
|
+
return Array.from(this.sessions.values()).map(session => ({
|
|
84
|
+
pid: session.pid,
|
|
85
|
+
isBlocked: session.isBlocked,
|
|
86
|
+
runtime: now.getTime() - session.startTime.getTime()
|
|
87
|
+
}));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
export const terminalManager = new TerminalManager();
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare function blockCommand(args: unknown): Promise<{
|
|
2
|
+
content: {
|
|
3
|
+
type: string;
|
|
4
|
+
text: string;
|
|
5
|
+
}[];
|
|
6
|
+
}>;
|
|
7
|
+
export declare function unblockCommand(args: unknown): Promise<{
|
|
8
|
+
content: {
|
|
9
|
+
type: string;
|
|
10
|
+
text: string;
|
|
11
|
+
}[];
|
|
12
|
+
}>;
|
|
13
|
+
export declare function listBlockedCommands(): Promise<{
|
|
14
|
+
content: {
|
|
15
|
+
type: string;
|
|
16
|
+
text: string;
|
|
17
|
+
}[];
|
|
18
|
+
}>;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { commandManager } from '../command-manager.js';
|
|
2
|
+
import { logToFile } from '../logging.js';
|
|
3
|
+
import { BlockCommandArgsSchema, UnblockCommandArgsSchema } from './schemas.js';
|
|
4
|
+
export async function blockCommand(args) {
|
|
5
|
+
await logToFile('Processing block_command request');
|
|
6
|
+
const parsed = BlockCommandArgsSchema.safeParse(args);
|
|
7
|
+
if (!parsed.success) {
|
|
8
|
+
throw new Error(`Invalid arguments for block_command: ${parsed.error}`);
|
|
9
|
+
}
|
|
10
|
+
const success = await commandManager.blockCommand(parsed.data.command);
|
|
11
|
+
if (!success) {
|
|
12
|
+
return {
|
|
13
|
+
content: [{
|
|
14
|
+
type: "text",
|
|
15
|
+
text: `Command '${parsed.data.command}' is already blocked.`
|
|
16
|
+
}],
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
await logToFile(`Added '${parsed.data.command}' to blocked commands`);
|
|
20
|
+
return {
|
|
21
|
+
content: [{
|
|
22
|
+
type: "text",
|
|
23
|
+
text: `Command '${parsed.data.command}' has been blocked. Current blocked commands: ${commandManager.listBlockedCommands().join(', ')}`
|
|
24
|
+
}],
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export async function unblockCommand(args) {
|
|
28
|
+
await logToFile('Processing unblock_command request');
|
|
29
|
+
const parsed = UnblockCommandArgsSchema.safeParse(args);
|
|
30
|
+
if (!parsed.success) {
|
|
31
|
+
throw new Error(`Invalid arguments for unblock_command: ${parsed.error}`);
|
|
32
|
+
}
|
|
33
|
+
const success = await commandManager.unblockCommand(parsed.data.command);
|
|
34
|
+
if (!success) {
|
|
35
|
+
return {
|
|
36
|
+
content: [{
|
|
37
|
+
type: "text",
|
|
38
|
+
text: `Command '${parsed.data.command}' is not blocked.`
|
|
39
|
+
}],
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
await logToFile(`Removed '${parsed.data.command}' from blocked commands`);
|
|
43
|
+
return {
|
|
44
|
+
content: [{
|
|
45
|
+
type: "text",
|
|
46
|
+
text: `Command '${parsed.data.command}' has been unblocked. Current blocked commands: ${commandManager.listBlockedCommands().join(', ')}`
|
|
47
|
+
}],
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
export async function listBlockedCommands() {
|
|
51
|
+
await logToFile('Processing list_blocked_commands request');
|
|
52
|
+
const blockedList = commandManager.listBlockedCommands();
|
|
53
|
+
await logToFile(`Listed ${blockedList.length} blocked commands`);
|
|
54
|
+
return {
|
|
55
|
+
content: [{
|
|
56
|
+
type: "text",
|
|
57
|
+
text: blockedList.length > 0
|
|
58
|
+
? `Currently blocked commands:\n${blockedList.join('\n')}`
|
|
59
|
+
: "No commands are currently blocked."
|
|
60
|
+
}],
|
|
61
|
+
};
|
|
62
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
interface SearchReplace {
|
|
2
|
+
search: string;
|
|
3
|
+
replace: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function performSearchReplace(filePath: string, block: SearchReplace): Promise<void>;
|
|
6
|
+
export declare function parseEditBlock(blockContent: string): Promise<{
|
|
7
|
+
filePath: string;
|
|
8
|
+
searchReplace: SearchReplace;
|
|
9
|
+
}>;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { readFile, writeFile } from './filesystem.js';
|
|
2
|
+
export async function performSearchReplace(filePath, block) {
|
|
3
|
+
const content = await readFile(filePath);
|
|
4
|
+
// Find first occurrence
|
|
5
|
+
const searchIndex = content.indexOf(block.search);
|
|
6
|
+
if (searchIndex === -1) {
|
|
7
|
+
throw new Error(`Search content not found in ${filePath}`);
|
|
8
|
+
}
|
|
9
|
+
// Replace content
|
|
10
|
+
const newContent = content.substring(0, searchIndex) +
|
|
11
|
+
block.replace +
|
|
12
|
+
content.substring(searchIndex + block.search.length);
|
|
13
|
+
await writeFile(filePath, newContent);
|
|
14
|
+
}
|
|
15
|
+
export async function parseEditBlock(blockContent) {
|
|
16
|
+
const lines = blockContent.split('\n');
|
|
17
|
+
// First line should be the file path
|
|
18
|
+
const filePath = lines[0].trim();
|
|
19
|
+
// Find the markers
|
|
20
|
+
const searchStart = lines.indexOf('<<<<<<< SEARCH');
|
|
21
|
+
const divider = lines.indexOf('=======');
|
|
22
|
+
const replaceEnd = lines.indexOf('>>>>>>> REPLACE');
|
|
23
|
+
if (searchStart === -1 || divider === -1 || replaceEnd === -1) {
|
|
24
|
+
throw new Error('Invalid edit block format - missing markers');
|
|
25
|
+
}
|
|
26
|
+
// Extract search and replace content
|
|
27
|
+
const search = lines.slice(searchStart + 1, divider).join('\n');
|
|
28
|
+
const replace = lines.slice(divider + 1, replaceEnd).join('\n');
|
|
29
|
+
return {
|
|
30
|
+
filePath,
|
|
31
|
+
searchReplace: { search, replace }
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export declare function executeCommand(args: unknown): Promise<{
|
|
2
|
+
content: {
|
|
3
|
+
type: string;
|
|
4
|
+
text: string;
|
|
5
|
+
}[];
|
|
6
|
+
}>;
|
|
7
|
+
export declare function readOutput(args: unknown): Promise<{
|
|
8
|
+
content: {
|
|
9
|
+
type: string;
|
|
10
|
+
text: string;
|
|
11
|
+
}[];
|
|
12
|
+
}>;
|
|
13
|
+
export declare function forceTerminate(args: unknown): Promise<{
|
|
14
|
+
content: {
|
|
15
|
+
type: string;
|
|
16
|
+
text: string;
|
|
17
|
+
}[];
|
|
18
|
+
}>;
|
|
19
|
+
export declare function listSessions(): Promise<{
|
|
20
|
+
content: {
|
|
21
|
+
type: string;
|
|
22
|
+
text: string;
|
|
23
|
+
}[];
|
|
24
|
+
}>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { terminalManager } from '../terminal-manager.js';
|
|
2
|
+
import { commandManager } from '../command-manager.js';
|
|
3
|
+
import { ExecuteCommandArgsSchema, ReadOutputArgsSchema, ForceTerminateArgsSchema } from './schemas.js';
|
|
4
|
+
export async function executeCommand(args) {
|
|
5
|
+
const parsed = ExecuteCommandArgsSchema.safeParse(args);
|
|
6
|
+
if (!parsed.success) {
|
|
7
|
+
throw new Error(`Invalid arguments for execute_command: ${parsed.error}`);
|
|
8
|
+
}
|
|
9
|
+
if (!commandManager.validateCommand(parsed.data.command)) {
|
|
10
|
+
throw new Error(`Command not allowed: ${parsed.data.command}`);
|
|
11
|
+
}
|
|
12
|
+
const result = await terminalManager.executeCommand(parsed.data.command, parsed.data.timeout_ms);
|
|
13
|
+
return {
|
|
14
|
+
content: [{
|
|
15
|
+
type: "text",
|
|
16
|
+
text: `Command started with PID ${result.pid}\nInitial output:\n${result.output}${result.isBlocked ? '\nCommand is still running. Use read_output to get more output.' : ''}`
|
|
17
|
+
}],
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export async function readOutput(args) {
|
|
21
|
+
const parsed = ReadOutputArgsSchema.safeParse(args);
|
|
22
|
+
if (!parsed.success) {
|
|
23
|
+
throw new Error(`Invalid arguments for read_output: ${parsed.error}`);
|
|
24
|
+
}
|
|
25
|
+
const output = terminalManager.getNewOutput(parsed.data.pid);
|
|
26
|
+
return {
|
|
27
|
+
content: [{
|
|
28
|
+
type: "text",
|
|
29
|
+
text: output === null
|
|
30
|
+
? `No session found for PID ${parsed.data.pid}`
|
|
31
|
+
: output || 'No new output available'
|
|
32
|
+
}],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export async function forceTerminate(args) {
|
|
36
|
+
const parsed = ForceTerminateArgsSchema.safeParse(args);
|
|
37
|
+
if (!parsed.success) {
|
|
38
|
+
throw new Error(`Invalid arguments for force_terminate: ${parsed.error}`);
|
|
39
|
+
}
|
|
40
|
+
const success = terminalManager.forceTerminate(parsed.data.pid);
|
|
41
|
+
return {
|
|
42
|
+
content: [{
|
|
43
|
+
type: "text",
|
|
44
|
+
text: success
|
|
45
|
+
? `Successfully initiated termination of session ${parsed.data.pid}`
|
|
46
|
+
: `No active session found for PID ${parsed.data.pid}`
|
|
47
|
+
}],
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
export async function listSessions() {
|
|
51
|
+
const sessions = terminalManager.listActiveSessions();
|
|
52
|
+
return {
|
|
53
|
+
content: [{
|
|
54
|
+
type: "text",
|
|
55
|
+
text: sessions.length === 0
|
|
56
|
+
? 'No active sessions'
|
|
57
|
+
: sessions.map(s => `PID: ${s.pid}, Blocked: ${s.isBlocked}, Runtime: ${Math.round(s.runtime / 1000)}s`).join('\n')
|
|
58
|
+
}],
|
|
59
|
+
};
|
|
60
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare function validatePath(requestedPath: string): Promise<string>;
|
|
2
|
+
export declare function readFile(filePath: string): Promise<string>;
|
|
3
|
+
export declare function writeFile(filePath: string, content: string): Promise<void>;
|
|
4
|
+
export declare function readMultipleFiles(paths: string[]): Promise<string[]>;
|
|
5
|
+
export declare function createDirectory(dirPath: string): Promise<void>;
|
|
6
|
+
export declare function listDirectory(dirPath: string): Promise<string[]>;
|
|
7
|
+
export declare function moveFile(sourcePath: string, destinationPath: string): Promise<void>;
|
|
8
|
+
export declare function searchFiles(rootPath: string, pattern: string): Promise<string[]>;
|
|
9
|
+
export declare function getFileInfo(filePath: string): Promise<Record<string, any>>;
|
|
10
|
+
export declare function listAllowedDirectories(): string[];
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import fs from "fs/promises";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import os from 'os';
|
|
4
|
+
// Store allowed directories
|
|
5
|
+
const allowedDirectories = [
|
|
6
|
+
process.cwd(), // Current working directory
|
|
7
|
+
os.homedir() // User's home directory
|
|
8
|
+
];
|
|
9
|
+
// Normalize all paths consistently
|
|
10
|
+
function normalizePath(p) {
|
|
11
|
+
return path.normalize(p).toLowerCase();
|
|
12
|
+
}
|
|
13
|
+
function expandHome(filepath) {
|
|
14
|
+
if (filepath.startsWith('~/') || filepath === '~') {
|
|
15
|
+
return path.join(os.homedir(), filepath.slice(1));
|
|
16
|
+
}
|
|
17
|
+
return filepath;
|
|
18
|
+
}
|
|
19
|
+
// Security utilities
|
|
20
|
+
export async function validatePath(requestedPath) {
|
|
21
|
+
const expandedPath = expandHome(requestedPath);
|
|
22
|
+
const absolute = path.isAbsolute(expandedPath)
|
|
23
|
+
? path.resolve(expandedPath)
|
|
24
|
+
: path.resolve(process.cwd(), expandedPath);
|
|
25
|
+
const normalizedRequested = normalizePath(absolute);
|
|
26
|
+
// Check if path is within allowed directories
|
|
27
|
+
const isAllowed = allowedDirectories.some(dir => normalizedRequested.startsWith(normalizePath(dir)));
|
|
28
|
+
if (!isAllowed) {
|
|
29
|
+
throw new Error(`Access denied - path outside allowed directories: ${absolute}`);
|
|
30
|
+
}
|
|
31
|
+
// Handle symlinks by checking their real path
|
|
32
|
+
try {
|
|
33
|
+
const realPath = await fs.realpath(absolute);
|
|
34
|
+
const normalizedReal = normalizePath(realPath);
|
|
35
|
+
const isRealPathAllowed = allowedDirectories.some(dir => normalizedReal.startsWith(normalizePath(dir)));
|
|
36
|
+
if (!isRealPathAllowed) {
|
|
37
|
+
throw new Error("Access denied - symlink target outside allowed directories");
|
|
38
|
+
}
|
|
39
|
+
return realPath;
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
// For new files that don't exist yet, verify parent directory
|
|
43
|
+
const parentDir = path.dirname(absolute);
|
|
44
|
+
try {
|
|
45
|
+
const realParentPath = await fs.realpath(parentDir);
|
|
46
|
+
const normalizedParent = normalizePath(realParentPath);
|
|
47
|
+
const isParentAllowed = allowedDirectories.some(dir => normalizedParent.startsWith(normalizePath(dir)));
|
|
48
|
+
if (!isParentAllowed) {
|
|
49
|
+
throw new Error("Access denied - parent directory outside allowed directories");
|
|
50
|
+
}
|
|
51
|
+
return absolute;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
throw new Error(`Parent directory does not exist: ${parentDir}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// File operation tools
|
|
59
|
+
export async function readFile(filePath) {
|
|
60
|
+
const validPath = await validatePath(filePath);
|
|
61
|
+
return fs.readFile(validPath, "utf-8");
|
|
62
|
+
}
|
|
63
|
+
export async function writeFile(filePath, content) {
|
|
64
|
+
const validPath = await validatePath(filePath);
|
|
65
|
+
await fs.writeFile(validPath, content, "utf-8");
|
|
66
|
+
}
|
|
67
|
+
export async function readMultipleFiles(paths) {
|
|
68
|
+
return Promise.all(paths.map(async (filePath) => {
|
|
69
|
+
try {
|
|
70
|
+
const validPath = await validatePath(filePath);
|
|
71
|
+
const content = await fs.readFile(validPath, "utf-8");
|
|
72
|
+
return `${filePath}:\n${content}\n`;
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
76
|
+
return `${filePath}: Error - ${errorMessage}`;
|
|
77
|
+
}
|
|
78
|
+
}));
|
|
79
|
+
}
|
|
80
|
+
export async function createDirectory(dirPath) {
|
|
81
|
+
const validPath = await validatePath(dirPath);
|
|
82
|
+
await fs.mkdir(validPath, { recursive: true });
|
|
83
|
+
}
|
|
84
|
+
export async function listDirectory(dirPath) {
|
|
85
|
+
const validPath = await validatePath(dirPath);
|
|
86
|
+
const entries = await fs.readdir(validPath, { withFileTypes: true });
|
|
87
|
+
return entries.map((entry) => `${entry.isDirectory() ? "[DIR]" : "[FILE]"} ${entry.name}`);
|
|
88
|
+
}
|
|
89
|
+
export async function moveFile(sourcePath, destinationPath) {
|
|
90
|
+
const validSourcePath = await validatePath(sourcePath);
|
|
91
|
+
const validDestPath = await validatePath(destinationPath);
|
|
92
|
+
await fs.rename(validSourcePath, validDestPath);
|
|
93
|
+
}
|
|
94
|
+
export async function searchFiles(rootPath, pattern) {
|
|
95
|
+
const results = [];
|
|
96
|
+
async function search(currentPath) {
|
|
97
|
+
const entries = await fs.readdir(currentPath, { withFileTypes: true });
|
|
98
|
+
for (const entry of entries) {
|
|
99
|
+
const fullPath = path.join(currentPath, entry.name);
|
|
100
|
+
try {
|
|
101
|
+
await validatePath(fullPath);
|
|
102
|
+
if (entry.name.toLowerCase().includes(pattern.toLowerCase())) {
|
|
103
|
+
results.push(fullPath);
|
|
104
|
+
}
|
|
105
|
+
if (entry.isDirectory()) {
|
|
106
|
+
await search(fullPath);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const validPath = await validatePath(rootPath);
|
|
115
|
+
await search(validPath);
|
|
116
|
+
return results;
|
|
117
|
+
}
|
|
118
|
+
export async function getFileInfo(filePath) {
|
|
119
|
+
const validPath = await validatePath(filePath);
|
|
120
|
+
const stats = await fs.stat(validPath);
|
|
121
|
+
return {
|
|
122
|
+
size: stats.size,
|
|
123
|
+
created: stats.birthtime,
|
|
124
|
+
modified: stats.mtime,
|
|
125
|
+
accessed: stats.atime,
|
|
126
|
+
isDirectory: stats.isDirectory(),
|
|
127
|
+
isFile: stats.isFile(),
|
|
128
|
+
permissions: stats.mode.toString(8).slice(-3),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
export function listAllowedDirectories() {
|
|
132
|
+
return allowedDirectories;
|
|
133
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { exec } from 'child_process';
|
|
2
|
+
import { promisify } from 'util';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import { KillProcessArgsSchema } from './schemas.js';
|
|
5
|
+
const execAsync = promisify(exec);
|
|
6
|
+
export async function listProcesses() {
|
|
7
|
+
const command = os.platform() === 'win32' ? 'tasklist' : 'ps aux';
|
|
8
|
+
try {
|
|
9
|
+
const { stdout } = await execAsync(command);
|
|
10
|
+
const processes = stdout.split('\n')
|
|
11
|
+
.slice(1)
|
|
12
|
+
.filter(Boolean)
|
|
13
|
+
.map(line => {
|
|
14
|
+
const parts = line.split(/\s+/);
|
|
15
|
+
return {
|
|
16
|
+
pid: parseInt(parts[1]),
|
|
17
|
+
command: parts[parts.length - 1],
|
|
18
|
+
cpu: parts[2],
|
|
19
|
+
memory: parts[3],
|
|
20
|
+
};
|
|
21
|
+
});
|
|
22
|
+
return {
|
|
23
|
+
content: [{
|
|
24
|
+
type: "text",
|
|
25
|
+
text: processes.map(p => `PID: ${p.pid}, Command: ${p.command}, CPU: ${p.cpu}, Memory: ${p.memory}`).join('\n')
|
|
26
|
+
}],
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
throw new Error('Failed to list processes');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export async function killProcess(args) {
|
|
34
|
+
const parsed = KillProcessArgsSchema.safeParse(args);
|
|
35
|
+
if (!parsed.success) {
|
|
36
|
+
throw new Error(`Invalid arguments for kill_process: ${parsed.error}`);
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
process.kill(parsed.data.pid);
|
|
40
|
+
return {
|
|
41
|
+
content: [{ type: "text", text: `Successfully terminated process ${parsed.data.pid}` }],
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
throw new Error(`Failed to kill process: ${error instanceof Error ? error.message : String(error)}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const ExecuteCommandArgsSchema: z.ZodObject<{
|
|
3
|
+
command: z.ZodString;
|
|
4
|
+
timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
5
|
+
}, "strip", z.ZodTypeAny, {
|
|
6
|
+
command: string;
|
|
7
|
+
timeout_ms?: number | undefined;
|
|
8
|
+
}, {
|
|
9
|
+
command: string;
|
|
10
|
+
timeout_ms?: number | undefined;
|
|
11
|
+
}>;
|
|
12
|
+
export declare const ReadOutputArgsSchema: z.ZodObject<{
|
|
13
|
+
pid: z.ZodNumber;
|
|
14
|
+
}, "strip", z.ZodTypeAny, {
|
|
15
|
+
pid: number;
|
|
16
|
+
}, {
|
|
17
|
+
pid: number;
|
|
18
|
+
}>;
|
|
19
|
+
export declare const ForceTerminateArgsSchema: z.ZodObject<{
|
|
20
|
+
pid: z.ZodNumber;
|
|
21
|
+
}, "strip", z.ZodTypeAny, {
|
|
22
|
+
pid: number;
|
|
23
|
+
}, {
|
|
24
|
+
pid: number;
|
|
25
|
+
}>;
|
|
26
|
+
export declare const ListSessionsArgsSchema: z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>;
|
|
27
|
+
export declare const KillProcessArgsSchema: z.ZodObject<{
|
|
28
|
+
pid: z.ZodNumber;
|
|
29
|
+
}, "strip", z.ZodTypeAny, {
|
|
30
|
+
pid: number;
|
|
31
|
+
}, {
|
|
32
|
+
pid: number;
|
|
33
|
+
}>;
|
|
34
|
+
export declare const BlockCommandArgsSchema: z.ZodObject<{
|
|
35
|
+
command: z.ZodString;
|
|
36
|
+
}, "strip", z.ZodTypeAny, {
|
|
37
|
+
command: string;
|
|
38
|
+
}, {
|
|
39
|
+
command: string;
|
|
40
|
+
}>;
|
|
41
|
+
export declare const UnblockCommandArgsSchema: z.ZodObject<{
|
|
42
|
+
command: z.ZodString;
|
|
43
|
+
}, "strip", z.ZodTypeAny, {
|
|
44
|
+
command: string;
|
|
45
|
+
}, {
|
|
46
|
+
command: string;
|
|
47
|
+
}>;
|
|
48
|
+
export declare const ReadFileArgsSchema: z.ZodObject<{
|
|
49
|
+
path: z.ZodString;
|
|
50
|
+
}, "strip", z.ZodTypeAny, {
|
|
51
|
+
path: string;
|
|
52
|
+
}, {
|
|
53
|
+
path: string;
|
|
54
|
+
}>;
|
|
55
|
+
export declare const ReadMultipleFilesArgsSchema: z.ZodObject<{
|
|
56
|
+
paths: z.ZodArray<z.ZodString, "many">;
|
|
57
|
+
}, "strip", z.ZodTypeAny, {
|
|
58
|
+
paths: string[];
|
|
59
|
+
}, {
|
|
60
|
+
paths: string[];
|
|
61
|
+
}>;
|
|
62
|
+
export declare const WriteFileArgsSchema: z.ZodObject<{
|
|
63
|
+
path: z.ZodString;
|
|
64
|
+
content: z.ZodString;
|
|
65
|
+
}, "strip", z.ZodTypeAny, {
|
|
66
|
+
path: string;
|
|
67
|
+
content: string;
|
|
68
|
+
}, {
|
|
69
|
+
path: string;
|
|
70
|
+
content: string;
|
|
71
|
+
}>;
|
|
72
|
+
export declare const CreateDirectoryArgsSchema: z.ZodObject<{
|
|
73
|
+
path: z.ZodString;
|
|
74
|
+
}, "strip", z.ZodTypeAny, {
|
|
75
|
+
path: string;
|
|
76
|
+
}, {
|
|
77
|
+
path: string;
|
|
78
|
+
}>;
|
|
79
|
+
export declare const ListDirectoryArgsSchema: z.ZodObject<{
|
|
80
|
+
path: z.ZodString;
|
|
81
|
+
}, "strip", z.ZodTypeAny, {
|
|
82
|
+
path: string;
|
|
83
|
+
}, {
|
|
84
|
+
path: string;
|
|
85
|
+
}>;
|
|
86
|
+
export declare const MoveFileArgsSchema: z.ZodObject<{
|
|
87
|
+
source: z.ZodString;
|
|
88
|
+
destination: z.ZodString;
|
|
89
|
+
}, "strip", z.ZodTypeAny, {
|
|
90
|
+
source: string;
|
|
91
|
+
destination: string;
|
|
92
|
+
}, {
|
|
93
|
+
source: string;
|
|
94
|
+
destination: string;
|
|
95
|
+
}>;
|
|
96
|
+
export declare const SearchFilesArgsSchema: z.ZodObject<{
|
|
97
|
+
path: z.ZodString;
|
|
98
|
+
pattern: z.ZodString;
|
|
99
|
+
}, "strip", z.ZodTypeAny, {
|
|
100
|
+
path: string;
|
|
101
|
+
pattern: string;
|
|
102
|
+
}, {
|
|
103
|
+
path: string;
|
|
104
|
+
pattern: string;
|
|
105
|
+
}>;
|
|
106
|
+
export declare const GetFileInfoArgsSchema: z.ZodObject<{
|
|
107
|
+
path: z.ZodString;
|
|
108
|
+
}, "strip", z.ZodTypeAny, {
|
|
109
|
+
path: string;
|
|
110
|
+
}, {
|
|
111
|
+
path: string;
|
|
112
|
+
}>;
|
|
113
|
+
export declare const EditBlockArgsSchema: z.ZodObject<{
|
|
114
|
+
blockContent: z.ZodString;
|
|
115
|
+
}, "strip", z.ZodTypeAny, {
|
|
116
|
+
blockContent: string;
|
|
117
|
+
}, {
|
|
118
|
+
blockContent: string;
|
|
119
|
+
}>;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
// Terminal tools schemas
|
|
3
|
+
export const ExecuteCommandArgsSchema = z.object({
|
|
4
|
+
command: z.string(),
|
|
5
|
+
timeout_ms: z.number().optional(),
|
|
6
|
+
});
|
|
7
|
+
export const ReadOutputArgsSchema = z.object({
|
|
8
|
+
pid: z.number(),
|
|
9
|
+
});
|
|
10
|
+
export const ForceTerminateArgsSchema = z.object({
|
|
11
|
+
pid: z.number(),
|
|
12
|
+
});
|
|
13
|
+
export const ListSessionsArgsSchema = z.object({});
|
|
14
|
+
export const KillProcessArgsSchema = z.object({
|
|
15
|
+
pid: z.number(),
|
|
16
|
+
});
|
|
17
|
+
export const BlockCommandArgsSchema = z.object({
|
|
18
|
+
command: z.string(),
|
|
19
|
+
});
|
|
20
|
+
export const UnblockCommandArgsSchema = z.object({
|
|
21
|
+
command: z.string(),
|
|
22
|
+
});
|
|
23
|
+
// Filesystem tools schemas
|
|
24
|
+
export const ReadFileArgsSchema = z.object({
|
|
25
|
+
path: z.string(),
|
|
26
|
+
});
|
|
27
|
+
export const ReadMultipleFilesArgsSchema = z.object({
|
|
28
|
+
paths: z.array(z.string()),
|
|
29
|
+
});
|
|
30
|
+
export const WriteFileArgsSchema = z.object({
|
|
31
|
+
path: z.string(),
|
|
32
|
+
content: z.string(),
|
|
33
|
+
});
|
|
34
|
+
export const CreateDirectoryArgsSchema = z.object({
|
|
35
|
+
path: z.string(),
|
|
36
|
+
});
|
|
37
|
+
export const ListDirectoryArgsSchema = z.object({
|
|
38
|
+
path: z.string(),
|
|
39
|
+
});
|
|
40
|
+
export const MoveFileArgsSchema = z.object({
|
|
41
|
+
source: z.string(),
|
|
42
|
+
destination: z.string(),
|
|
43
|
+
});
|
|
44
|
+
export const SearchFilesArgsSchema = z.object({
|
|
45
|
+
path: z.string(),
|
|
46
|
+
pattern: z.string(),
|
|
47
|
+
});
|
|
48
|
+
export const GetFileInfoArgsSchema = z.object({
|
|
49
|
+
path: z.string(),
|
|
50
|
+
});
|
|
51
|
+
// Edit tools schemas
|
|
52
|
+
export const EditBlockArgsSchema = z.object({
|
|
53
|
+
blockContent: z.string(),
|
|
54
|
+
});
|