openai-cli-unofficial
Version:
A powerful OpenAI CLI Coding Agent built with TypeScript
506 lines • 28.5 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.TerminalService = void 0;
const child_process_1 = require("child_process");
const prompts_1 = require("@inquirer/prompts");
const path = __importStar(require("path"));
const base_service_1 = require("../base-service");
const language_1 = require("../../services/language");
const storage_1 = require("../../services/storage");
const messages = language_1.languageService.getMessages();
const config = storage_1.StorageService.getApiConfig();
// 存储后台运行的进程
const backgroundProcesses = new Map();
class TerminalService extends base_service_1.BaseMCPService {
constructor() {
super('terminal', '1.0.0');
}
getTools() {
return [
{
name: 'execute_command',
description: 'Execute a shell command and get the output. Supports automatic detection of long-running commands and background execution.',
inputSchema: {
type: 'object',
properties: {
command: {
type: 'string',
description: 'The command to execute. Example: "ls -la"'
},
cwd: {
type: 'string',
description: 'The working directory to execute the command in. Defaults to the project root. Example: "src/services"',
default: '.'
},
interactive: {
type: 'boolean',
description: 'Whether this command requires user interaction. For interactive commands like npm create, npm init, etc. Defaults to false.',
default: false
},
background: {
type: 'boolean',
description: 'Whether to run this command in the background. Useful for long-running commands like dev servers. Defaults to false.',
default: false
},
timeout: {
type: 'number',
description: 'Timeout in seconds. If command runs longer than this, user will be asked to move it to background. Defaults to 60 seconds.',
default: 60
}
},
required: ['command']
}
},
{
name: 'list_background_processes',
description: 'List all running background processes',
inputSchema: {
type: 'object',
properties: {},
required: []
}
},
{
name: 'kill_background_process',
description: 'Kill a background process by its ID',
inputSchema: {
type: 'object',
properties: {
processId: {
type: 'string',
description: 'The ID of the process to kill'
}
},
required: ['processId']
}
}
];
}
async handleRequest(request) {
try {
switch (request.method) {
case 'execute_command':
return await this.handleExecuteCommand(request);
case 'list_background_processes':
return await this.handleListBackgroundProcesses(request);
case 'kill_background_process':
return await this.handleKillBackgroundProcess(request);
default:
return this.createErrorResponse(request.id, -32601, `Unsupported method: ${request.method}`);
}
}
catch (error) {
return this.createErrorResponse(request.id, -32603, 'Internal server error', error instanceof Error ? error.message : String(error));
}
}
async handleListBackgroundProcesses(request) {
const processes = Array.from(backgroundProcesses.entries()).map(([id, proc]) => ({
id,
command: proc.command,
cwd: proc.cwd,
startTime: proc.startTime,
pid: proc.child.pid
}));
const terminalMessages = messages.terminal;
const processInfo = processes.map(p => `**${terminalMessages.execution.processId}:** ${p.id}\n**${terminalMessages.processManagement.command}:** \`${p.command}\`\n**${terminalMessages.processManagement.directory}:** ${p.cwd}\n**${terminalMessages.execution.pid}:** ${p.pid}\n**${terminalMessages.execution.startTime}:** ${p.startTime}`).join('\n\n');
const message = processes.length > 0
? `🔄 **${terminalMessages.processManagement.listTitle}:**\n\n${processInfo}`
: `📭 **${terminalMessages.processManagement.noProcesses}**`;
return this.createSuccessResponse(request.id, message);
}
async handleKillBackgroundProcess(request) {
const { processId } = request.params;
const terminalMessages = messages.terminal;
if (!backgroundProcesses.has(processId)) {
return this.createErrorResponse(request.id, -32602, terminalMessages.processManagement.processNotExists.replace('{processId}', processId));
}
const proc = backgroundProcesses.get(processId);
try {
proc.child.kill('SIGTERM');
backgroundProcesses.delete(processId);
return this.createSuccessResponse(request.id, `✅ **${terminalMessages.processManagement.processTerminated}**\n\n**${terminalMessages.execution.processId}:** ${processId}\n**${terminalMessages.processManagement.command}:** \`${proc.command}\`\n**${terminalMessages.execution.pid}:** ${proc.child.pid}`);
}
catch (error) {
return this.createErrorResponse(request.id, -32603, `${terminalMessages.processManagement.terminationFailed}: ${error instanceof Error ? error.message : String(error)}`);
}
}
async handleExecuteCommand(request) {
const validationError = this.validateParams(request.params, ['command']);
if (validationError) {
return this.createErrorResponse(request.id, -32602, validationError);
}
const params = request.params;
const cwd = path.resolve(params.cwd || '.');
// 检查函数是否需要手动确认
const functionName = 'terminal_execute_command';
const needsConfirmation = storage_1.StorageService.isFunctionConfirmationRequired(functionName);
const execCommand = true;
if (!needsConfirmation) {
// 检查敏感词,如果携带*通配符就模糊匹配
const sensitiveWords = config.terminalSensitiveWords || [];
const command = params.command.toLowerCase();
// 找到匹配的敏感词
const matchedSensitiveWord = sensitiveWords.find(word => {
const lowerWord = word.toLowerCase();
// 如果敏感词包含*通配符,进行模糊匹配
if (lowerWord.includes('*')) {
// 直接在这里处理通配符匹配
const regexPattern = lowerWord
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // 转义特殊字符
.replace(/\\\*/g, '.*'); // 将 \* 替换为 .*
const regex = new RegExp(regexPattern);
return regex.test(command);
}
else {
// 普通匹配
return command.includes(lowerWord);
}
});
if (matchedSensitiveWord) {
// 提示用户是否继续执行,显示匹配到的敏感词
const confirmation = await (0, prompts_1.confirm)({
message: `${messages.config.messages.terminalSensitiveWordsEditorPrompt}(${matchedSensitiveWord}): ${params.command}`,
default: false
});
if (!confirmation) {
return this.createSuccessResponse(request.id, messages.terminal.responses.commandRefused);
}
}
}
if (execCommand) {
// 如果明确指定后台运行
if (params.background) {
return this.executeBackgroundCommand(params, cwd, request.id);
}
// 检查是否为明确的交互式命令(只检查几个最常见的)
const isInteractive = params.interactive || this.isKnownInteractiveCommand(params.command);
if (isInteractive) {
return this.executeInteractiveCommand(params, cwd, request.id);
}
else {
// 使用智能检测方式执行命令
return this.executeCommandWithSmartDetection(params, cwd, request.id);
}
}
return this.createErrorResponse(request.id, -32602, 'Command contains sensitive words');
}
/**
* 检查是否为已知的交互式命令(只检查最常见的几个)
*/
isKnownInteractiveCommand(command) {
const interactiveKeywords = [
'npm init',
'yarn init',
'git commit -',
'git rebase -i'
];
const lowerCommand = command.toLowerCase();
return interactiveKeywords.some(keyword => lowerCommand.includes(keyword));
}
/**
* 智能检测执行命令(基于超时检测)
*/
executeCommandWithSmartDetection(params, cwd, requestId) {
return new Promise((resolve) => {
const timeout = (params.timeout || 60) * 1000; // 转换为毫秒
const terminalMessages = messages.terminal;
console.log(`🔄 ${terminalMessages.execution.executing}: ${params.command}`);
console.log(`📁 ${terminalMessages.execution.workingDirectory}: ${cwd}`);
console.log(`⏱️ ${terminalMessages.execution.timeoutDetection}: ${timeout / 1000}${terminalMessages.smartDetection.timeoutPrompt}`);
console.log(terminalMessages.execution.separator);
// 创建子进程
const child = (0, child_process_1.spawn)(params.command, {
cwd,
shell: true
});
let stdout = '';
let stderr = '';
let isCompleted = false;
let timeoutHandle;
// 实时输出 stdout
if (child.stdout) {
child.stdout.on('data', (data) => {
const text = data.toString();
stdout += text;
process.stdout.write(text);
});
}
// 实时输出 stderr
if (child.stderr) {
child.stderr.on('data', (data) => {
const text = data.toString();
stderr += text;
process.stderr.write(text);
});
}
// 设置超时检测
timeoutHandle = setTimeout(async () => {
if (!isCompleted) {
console.log('\n' + terminalMessages.execution.separator);
console.log(`⏰ ${terminalMessages.smartDetection.longRunningDetected.replace('{timeout}', (timeout / 1000).toString())}`);
try {
const choice = await (0, prompts_1.select)({
message: terminalMessages.smartDetection.choicePrompt,
choices: [
{
name: terminalMessages.smartDetection.options.background.name,
value: 'background',
description: terminalMessages.smartDetection.options.background.description
},
{
name: terminalMessages.smartDetection.options.wait.name,
value: 'wait',
description: terminalMessages.smartDetection.options.wait.description
},
{
name: terminalMessages.smartDetection.options.kill.name,
value: 'kill',
description: terminalMessages.smartDetection.options.kill.description
}
]
});
switch (choice) {
case 'background':
// 转为后台运行
const processId = `bg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const processInfo = {
child,
command: params.command,
cwd,
startTime: new Date().toLocaleString(),
processId
};
backgroundProcesses.set(processId, processInfo);
console.log(`🚀 ${terminalMessages.smartDetection.backgroundMoved} (${terminalMessages.execution.processId}: ${processId})`);
const resultMessage = `🚀 **${terminalMessages.smartDetection.backgroundMoved}**\n\n**${terminalMessages.processManagement.command}:** \`${params.command}\`\n**${terminalMessages.execution.processId}:** ${processId}\n**${terminalMessages.execution.pid}:** ${child.pid}\n\n💡 ${terminalMessages.backgroundProcess.listCommand}\n💡 ${terminalMessages.backgroundProcess.killCommand}`;
resolve(this.createSuccessResponse(requestId, resultMessage));
isCompleted = true;
break;
case 'wait':
console.log(`⏳ ${terminalMessages.smartDetection.continueWaiting}`);
// 不做任何操作,继续等待
break;
case 'kill':
child.kill('SIGTERM');
console.log(`🛑 ${terminalMessages.smartDetection.userTerminated}`);
resolve(this.createSuccessResponse(requestId, `🛑 **${terminalMessages.smartDetection.userTerminated}**`));
isCompleted = true;
break;
}
}
catch (error) {
// 如果用户选择出错,默认转为后台运行
const processId = `bg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const processInfo = {
child,
command: params.command,
cwd,
startTime: new Date().toLocaleString(),
processId
};
backgroundProcesses.set(processId, processInfo);
console.log(`🚀 ${terminalMessages.smartDetection.autoBackground} (${terminalMessages.execution.processId}: ${processId})`);
const resultMessage = `🚀 **${terminalMessages.smartDetection.autoBackground}**\n\n**${terminalMessages.processManagement.command}:** \`${params.command}\`\n**${terminalMessages.execution.processId}:** ${processId}`;
resolve(this.createSuccessResponse(requestId, resultMessage));
isCompleted = true;
}
}
}, timeout);
child.on('error', (error) => {
if (!isCompleted) {
clearTimeout(timeoutHandle);
isCompleted = true;
console.log(terminalMessages.execution.separator);
console.error(`❌ ${terminalMessages.execution.failed}: ${error.message}`);
const resultMessage = `❌ **${terminalMessages.responses.commandFailed}**\n\n**${terminalMessages.processManagement.command}:** \`${params.command}\`\n**${terminalMessages.execution.error}:** ${error.message}`;
resolve(this.createSuccessResponse(requestId, resultMessage));
}
});
child.on('exit', (code, signal) => {
if (!isCompleted) {
clearTimeout(timeoutHandle);
isCompleted = true;
console.log(terminalMessages.execution.separator);
if (code === 0) {
console.log(`✅ ${terminalMessages.execution.success} (${terminalMessages.execution.exitCode}: ${code})`);
const resultMessage = `✅ **${terminalMessages.responses.commandSuccess}**\n\n**${terminalMessages.processManagement.command}:** \`${params.command}\`\n**${terminalMessages.execution.exitCode}:** ${code}\n\n**${terminalMessages.responses.outputSummary}:**\n\`\`\`\n${stdout.slice(-1000)}\n\`\`\`${stderr ? `\n**${terminalMessages.responses.errorOutput}:**\n\`\`\`\n${stderr.slice(-500)}\n\`\`\`` : ''}`;
resolve(this.createSuccessResponse(requestId, resultMessage));
}
else {
const signalInfo = signal ? ` (${terminalMessages.execution.signal}: ${signal})` : '';
console.log(`❌ ${terminalMessages.execution.failed} (${terminalMessages.execution.exitCode}: ${code}${signalInfo})`);
const resultMessage = `❌ **${terminalMessages.responses.commandFailed}**\n\n**${terminalMessages.processManagement.command}:** \`${params.command}\`\n**${terminalMessages.execution.exitCode}:** ${code}${signal ? `\n**${terminalMessages.execution.signal}:** ${signal}` : ''}\n\n**Output:**\n\`\`\`\n${stdout}\n\`\`\`\n**${terminalMessages.responses.errorOutput}:**\n\`\`\`\n${stderr}\n\`\`\``;
resolve(this.createSuccessResponse(requestId, resultMessage));
}
}
});
});
}
/**
* 执行交互式命令
*/
executeInteractiveCommand(params, cwd, requestId) {
return new Promise((resolve) => {
const terminalMessages = messages.terminal;
console.log(`🔄 ${terminalMessages.interactiveProcess.executing}: ${params.command}`);
console.log(`📁 ${terminalMessages.execution.workingDirectory}: ${cwd}`);
console.log(`💡 ${terminalMessages.execution.interactiveMode}`);
console.log(terminalMessages.execution.separator);
// 创建子进程
const child = (0, child_process_1.spawn)(params.command, {
cwd,
stdio: 'inherit', // 继承父进程的 stdio,允许用户直接交互
shell: true
});
let exitCode = null;
let hasError = false;
child.on('error', (error) => {
hasError = true;
console.error(`\n❌ ${terminalMessages.execution.failed}: ${error.message}`);
const resultMessage = `❌ **${terminalMessages.responses.interactiveFailed}**\n\n**${terminalMessages.processManagement.command}:** \`${params.command}\`\n**${terminalMessages.execution.error}:** ${error.message}`;
resolve(this.createSuccessResponse(requestId, resultMessage));
});
child.on('exit', (code, signal) => {
if (hasError)
return; // 已经处理过错误
exitCode = code;
console.log(terminalMessages.execution.separator);
if (code === 0) {
console.log(`✅ ${terminalMessages.execution.success} (${terminalMessages.execution.exitCode}: ${code})`);
const resultMessage = `✅ **${terminalMessages.responses.interactiveSuccess}**\n\n**${terminalMessages.processManagement.command}:** \`${params.command}\`\n**${terminalMessages.execution.exitCode}:** ${code}`;
resolve(this.createSuccessResponse(requestId, resultMessage));
}
else {
const signalInfo = signal ? ` (${terminalMessages.execution.signal}: ${signal})` : '';
console.log(`❌ ${terminalMessages.execution.failed} (${terminalMessages.execution.exitCode}: ${code}${signalInfo})`);
const resultMessage = `❌ **${terminalMessages.interactiveProcess.executionFailed}**\n\n**${terminalMessages.processManagement.command}:** \`${params.command}\`\n**${terminalMessages.execution.exitCode}:** ${code}${signal ? `\n**${terminalMessages.execution.signal}:** ${signal}` : ''}`;
resolve(this.createSuccessResponse(requestId, resultMessage));
}
});
});
}
/**
* 执行后台命令
*/
executeBackgroundCommand(params, cwd, requestId) {
return new Promise((resolve) => {
const processId = `bg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const terminalMessages = messages.terminal;
console.log(`🚀 ${terminalMessages.backgroundProcess.launching}: ${params.command}`);
console.log(`📁 ${terminalMessages.execution.workingDirectory}: ${cwd}`);
console.log(`🆔 ${terminalMessages.execution.processId}: ${processId}`);
console.log(`💡 ${terminalMessages.execution.backgroundMode}`);
console.log(terminalMessages.execution.separator);
// 创建子进程
const child = (0, child_process_1.spawn)(params.command, {
cwd,
shell: true,
detached: true, // 分离进程
stdio: ['ignore', 'pipe', 'pipe'] // 捕获输出但不继承stdin
});
// 存储进程信息
const processInfo = {
child,
command: params.command,
cwd,
startTime: new Date().toLocaleString(),
processId
};
backgroundProcesses.set(processId, processInfo);
let hasStarted = false;
let startupOutput = '';
// 监听初始输出以确认启动
if (child.stdout) {
child.stdout.on('data', (data) => {
const text = data.toString();
if (!hasStarted) {
startupOutput += text;
// 输出前几行启动信息
if (startupOutput.length < 1000) {
process.stdout.write(text);
}
}
});
}
if (child.stderr) {
child.stderr.on('data', (data) => {
const text = data.toString();
if (!hasStarted) {
startupOutput += text;
// 输出前几行启动信息
if (startupOutput.length < 1000) {
process.stderr.write(text);
}
}
});
}
child.on('error', (error) => {
backgroundProcesses.delete(processId);
console.log(terminalMessages.execution.separator);
console.error(`❌ ${terminalMessages.backgroundProcess.startupFailed}: ${error.message}`);
const resultMessage = `❌ **${terminalMessages.responses.backgroundFailed}**\n\n**${terminalMessages.processManagement.command}:** \`${params.command}\`\n**${terminalMessages.execution.processId}:** ${processId}\n**${terminalMessages.execution.error}:** ${error.message}`;
resolve(this.createSuccessResponse(requestId, resultMessage));
});
child.on('exit', (code, signal) => {
backgroundProcesses.delete(processId);
console.log(terminalMessages.execution.separator);
if (code === 0) {
console.log(`✅ ${terminalMessages.backgroundProcess.normalExit} (${terminalMessages.execution.processId}: ${processId}, ${terminalMessages.execution.exitCode}: ${code})`);
}
else {
const signalInfo = signal ? ` (${terminalMessages.execution.signal}: ${signal})` : '';
console.log(`❌ ${terminalMessages.backgroundProcess.abnormalExit} (${terminalMessages.execution.processId}: ${processId}, ${terminalMessages.execution.exitCode}: ${code}${signalInfo})`);
}
});
// 等待一小段时间确认进程启动
setTimeout(() => {
hasStarted = true;
console.log(terminalMessages.execution.separator);
if (child.pid && !child.killed) {
console.log(`✅ ${terminalMessages.backgroundProcess.launched} (${terminalMessages.execution.processId}: ${processId}, ${terminalMessages.execution.pid}: ${child.pid})`);
const resultMessage = `✅ **${terminalMessages.responses.backgroundSuccess}**\n\n**${terminalMessages.processManagement.command}:** \`${params.command}\`\n**${terminalMessages.execution.processId}:** ${processId}\n**${terminalMessages.execution.pid}:** ${child.pid}\n**${terminalMessages.execution.workingDirectory}:** ${cwd}\n\n💡 **${terminalMessages.backgroundProcess.managementTips}:**\n- ${terminalMessages.backgroundProcess.listCommand}\n- ${terminalMessages.backgroundProcess.killCommand}\n\n**${terminalMessages.backgroundProcess.startupOutput}:**\n\`\`\`\n${startupOutput.slice(0, 500)}${startupOutput.length > 500 ? '...' : ''}\n\`\`\``;
resolve(this.createSuccessResponse(requestId, resultMessage));
}
else {
const resultMessage = `❌ **${terminalMessages.responses.backgroundFailed}**\n\n**${terminalMessages.processManagement.command}:** \`${params.command}\`\n**${terminalMessages.execution.processId}:** ${processId}\n\n${terminalMessages.backgroundProcess.maybeExited}`;
resolve(this.createSuccessResponse(requestId, resultMessage));
}
}, 2000); // 等待2秒确认启动
});
}
}
exports.TerminalService = TerminalService;
//# sourceMappingURL=terminal-service.js.map