UNPKG

openai-cli-unofficial

Version:

A powerful OpenAI CLI Coding Agent built with TypeScript

576 lines 25.9 kB
"use strict"; 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; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.CommandManager = void 0; const chalk_1 = __importDefault(require("chalk")); const path = __importStar(require("path")); const checkpoint_1 = require("../../services/checkpoint"); const history_1 = require("../../services/history"); const language_1 = require("../../services/language"); const token_calculator_1 = require("../../utils/token-calculator"); const history_editor_1 = require("./history-editor"); class CommandManager { constructor(messages) { // 历史记录管理状态 this.hasExportedHistory = false; this.isWaitingForFileImport = false; this.isWaitingForOverwriteConfirm = false; this.pendingImportFilePath = null; this.messages = messages; this.commands = this.initializeCommands(); } initializeCommands() { const mainCommands = this.messages.main.commands; return [ { value: '/exit', name: mainCommands.exit.name, description: mainCommands.exit.description }, { value: '/clear', name: mainCommands.clear.name, description: mainCommands.clear.description }, { value: '/help', name: mainCommands.help.name, description: mainCommands.help.description }, { value: '/history', name: mainCommands.history.name, description: mainCommands.history.description }, { value: '/edit-history', name: mainCommands.editHistory.name, description: mainCommands.editHistory.description }, { value: '/init', name: mainCommands.init.name, description: mainCommands.init.description }, { value: '/export-history', name: mainCommands.exportHistory.name, description: mainCommands.exportHistory.description }, { value: '/import-history', name: mainCommands.importHistory.name, description: mainCommands.importHistory.description }, { value: '/checkpoint', name: mainCommands.checkpoint.name, description: mainCommands.checkpoint.description } ]; } getCommands() { return [...this.commands]; } filterCommands(query) { if (!query.startsWith('/')) return []; const searchTerm = query.slice(1).toLowerCase(); if (searchTerm === '') return this.commands; return this.commands.filter(cmd => cmd.value.slice(1).toLowerCase().includes(searchTerm) || cmd.description.toLowerCase().includes(searchTerm)); } updateLanguage(messages) { this.messages = messages; this.commands = this.initializeCommands(); } // 重置所有状态 resetStates() { this.hasExportedHistory = false; this.isWaitingForFileImport = false; this.isWaitingForOverwriteConfirm = false; this.pendingImportFilePath = null; } // 获取导出状态 getHasExportedHistory() { return this.hasExportedHistory; } // 设置导出状态 setHasExportedHistory(value) { this.hasExportedHistory = value; } // 处理用户输入,返回执行结果 async handleInput(userInput, currentMessages) { // 检查是否在等待覆盖确认状态 if (this.isWaitingForOverwriteConfirm && this.pendingImportFilePath) { return await this.handleOverwriteConfirmation(userInput, currentMessages); } // 检查是否在等待文件导入状态 if (this.isWaitingForFileImport) { return await this.handleFileImportWaiting(userInput, currentMessages); } // 检查是否是文件导入格式(@文件路径) if (userInput.startsWith('@') && userInput.endsWith('.json')) { return await this.handleDirectFileImport(userInput, currentMessages); } // 检查是否是标准命令 if (userInput.startsWith('/')) { return await this.handleStandardCommand(userInput, currentMessages); } // 不是命令,返回未处理 return { handled: false }; } // 处理覆盖确认 async handleOverwriteConfirmation(userInput, currentMessages) { const choice = userInput.toLowerCase().trim(); const historyMgmt = this.messages.main.historyManagement; if (choice === 'y' || choice === 'yes' || choice === '是') { // 用户确认覆盖,强制导入 this.isWaitingForOverwriteConfirm = false; const filePath = this.pendingImportFilePath; this.pendingImportFilePath = null; const importedMessages = await history_1.HistoryService.forceImportHistoryFromFile(filePath, this.messages); if (importedMessages) { this.hasExportedHistory = false; const successMsg = historyMgmt.importFromFileSuccess .replace('{filePath}', filePath) .replace('{count}', importedMessages.length.toString()); console.log(chalk_1.default.green(successMsg)); return { handled: true, shouldContinue: true, newMessages: importedMessages }; } } else if (choice === 'n' || choice === 'no' || choice === '否') { // 用户取消覆盖 this.isWaitingForOverwriteConfirm = false; this.pendingImportFilePath = null; console.log(chalk_1.default.yellow(historyMgmt.importCancel)); return { handled: true, shouldContinue: true }; } else { // 无效输入,重新提示 console.log(chalk_1.default.red(historyMgmt.overwriteInvalidInput)); return { handled: true, shouldContinue: true }; } return { handled: true, shouldContinue: true }; } // 处理文件导入等待状态 async handleFileImportWaiting(userInput, currentMessages) { const historyMgmt = this.messages.main.historyManagement; if (userInput.startsWith('@')) { // 用户通过文件选择器或直接输入了文件路径 return await this.handleDirectFileImport(userInput, currentMessages); } else { // 用户输入了其他内容,取消文件导入模式 this.isWaitingForFileImport = false; console.log(chalk_1.default.gray(historyMgmt.fileImportCancelled)); // 继续正常处理用户输入,但不作为命令处理 return { handled: false }; } } // 处理直接文件导入 async handleDirectFileImport(userInput, currentMessages) { const filePath = userInput.slice(1); // 移除 @ 前缀 this.isWaitingForFileImport = false; // 重置等待状态 return await this.importHistoryFromFile(filePath, currentMessages); } // 处理标准命令 async handleStandardCommand(userInput, currentMessages) { if (userInput.startsWith('/checkpoint')) { return await this.handleCheckpointCommand(); } switch (userInput) { case '/export-history': return await this.handleExportHistory(currentMessages); case '/import-history': return this.handleImportHistory(); case '/edit-history': return await this.handleEditHistory(currentMessages); case '/clear': this.hasExportedHistory = false; // 清空历史时重置导出状态 return { handled: true, shouldReload: true }; case '/history': { await this.showHistory(currentMessages); return { handled: true, shouldContinue: true }; } default: return { handled: false }; } } // 处理检查点命令 async handleCheckpointCommand() { const checkpointService = checkpoint_1.CheckpointService.getInstance(); const tasks = checkpointService.getCheckpointsByTask(); if (tasks.size === 0) { console.log(chalk_1.default.yellow('No checkpoints found.')); return { handled: true, shouldContinue: true }; } const taskChoices = Array.from(tasks.entries()) .sort((a, b) => { const timeA = new Date(a[1][0]?.timestamp || 0).getTime(); const timeB = new Date(b[1][0]?.timestamp || 0).getTime(); return timeB - timeA; }) .map(([taskId, checkpoints]) => ({ id: taskId, name: this.formatTaskName(checkpoints), checkpoints: checkpoints, })); const specialChoices = [ { id: 'clear_all', name: '🧹 Clear All Checkpoints' }, { id: 'close', name: '❌ Close Menu' }, ]; const allChoices = [...taskChoices, ...specialChoices]; let currentIndex = 0; let isRunning = true; let isFirstRender = true; const header = () => { console.log(chalk_1.default.bold.cyan('=== Checkpoint Manager ===')); console.log(chalk_1.default.gray('Use UP/DOWN arrows to navigate, ENTER to select, Q to quit.')); console.log(chalk_1.default.gray('─'.repeat(80))); console.log(); }; const renderMenu = () => { if (!isFirstRender) { process.stdout.write('\x1B[?25l'); const linesToMove = allChoices.length; process.stdout.write(`\x1B[${linesToMove}A`); process.stdout.write('\x1B[0J'); } allChoices.forEach((choice, index) => { const isSelected = index === currentIndex; const indicator = isSelected ? chalk_1.default.cyan('●') : chalk_1.default.gray('○'); const name = isSelected ? chalk_1.default.white.bold(choice.name) : chalk_1.default.gray(choice.name); console.log(` ${indicator} ${name}`); }); process.stdout.write('\x1B[?25h'); isFirstRender = false; }; return new Promise((resolve) => { const cleanup = () => { isRunning = false; process.stdin.removeAllListeners('data'); if (process.stdin.isTTY) { process.stdin.setRawMode(false); } process.stdin.pause(); process.stdout.write('\x1B[?25h'); // Show cursor // Clear only the menu UI instead of the whole console const headerHeight = 4; const menuHeight = allChoices.length; const totalHeight = headerHeight + menuHeight; // Move cursor up to the start of the header process.stdout.write(`\x1B[${totalHeight}A`); // Clear from cursor to the end of the screen process.stdout.write(`\x1B[0J`); }; const keyHandler = async (key) => { if (!isRunning) return; if (key === '\x1B[A') { // Up currentIndex = (currentIndex - 1 + allChoices.length) % allChoices.length; renderMenu(); } else if (key === '\x1B[B') { // Down currentIndex = (currentIndex + 1) % allChoices.length; renderMenu(); } else if (key === '\r' || key === '\n') { // Enter const selection = allChoices[currentIndex].id; cleanup(); if (selection === 'close') { // Just exit } else if (selection === 'clear_all') { await checkpointService.clearAllCheckpoints(); console.log(chalk_1.default.green('\n✅ All checkpoints have been cleared.')); } else { const success = await checkpointService.restoreByTask(selection); if (success) { console.log(chalk_1.default.green(`\n✅ Task ${selection} restored successfully.`)); console.log(chalk_1.default.yellow('It is recommended to restart the application to reload file states.')); } else { console.log(chalk_1.default.red(`\n❌ Failed to restore task ${selection}. Check logs for details.`)); } } resolve({ handled: true, shouldContinue: true }); } else if (key === 'q' || key === '\x1b' || key.charCodeAt(0) === 3) { // q, esc, ctrl+c cleanup(); resolve({ handled: true, shouldContinue: true }); } }; if (process.stdin.isTTY) { process.stdin.setRawMode(true); } process.stdin.resume(); process.stdin.setEncoding('utf8'); process.stdin.on('data', keyHandler); header(); renderMenu(); }); } formatTaskName(checkpoints) { if (!checkpoints || checkpoints.length === 0) return "Invalid Task"; const first = checkpoints[0]; const time = new Date(first.timestamp).toLocaleString(); const fileList = checkpoints.map(c => path.basename(c.originalPath)).join(', '); return `${chalk_1.default.cyan(time)} - ${first.description.substring(0, 50)}... (${chalk_1.default.yellow(checkpoints.length)} files: ${fileList.substring(0, 40)}...)`; } // 处理导出历史记录 async handleExportHistory(currentMessages) { const exportSuccess = await history_1.HistoryService.exportHistory(currentMessages, this.messages); if (exportSuccess) { this.hasExportedHistory = true; } return { handled: true, shouldContinue: true }; } // 处理导入历史记录命令 handleImportHistory() { const historyMgmt = this.messages.main.historyManagement; console.log(chalk_1.default.cyan(historyMgmt.importInstructions)); console.log(chalk_1.default.white(historyMgmt.importStep1)); console.log(chalk_1.default.white(historyMgmt.importStep2)); console.log(chalk_1.default.white(historyMgmt.importStep3)); console.log(chalk_1.default.gray(historyMgmt.importExample)); console.log(); // 设置等待文件导入状态 this.isWaitingForFileImport = true; console.log(chalk_1.default.yellow(historyMgmt.fileImportWaiting)); console.log(chalk_1.default.gray(historyMgmt.fileImportWaitingTip)); return { handled: true, shouldContinue: true }; } // 处理编辑历史记录 async handleEditHistory(currentMessages) { if (currentMessages.length === 0) { const historyMgmt = this.messages.main.historyManagement; console.log(chalk_1.default.yellow(historyMgmt.editor.noHistoryToEdit)); return { handled: true, shouldContinue: true }; } const editor = new history_editor_1.HistoryEditor(this.messages, currentMessages); const result = await editor.start(); if (result.saved) { this.hasExportedHistory = false; // 编辑后重置导出状态 return { handled: true, shouldContinue: true, newMessages: result.messages }; } else { return { handled: true, shouldContinue: true }; } } // 从文件导入历史记录 async importHistoryFromFile(filePath, currentMessages) { const importedMessages = await history_1.HistoryService.importHistoryFromFile(filePath, currentMessages, this.messages); if (importedMessages === 'need_confirm') { // 需要用户确认是否覆盖现有历史记录 const historyMgmt = this.messages.main.historyManagement; console.log(chalk_1.default.yellow(historyMgmt.importOverwrite)); console.log(chalk_1.default.gray(historyMgmt.overwriteConfirmOptions)); // 设置等待覆盖确认状态 this.isWaitingForOverwriteConfirm = true; this.pendingImportFilePath = filePath; return { handled: true, shouldContinue: true }; } else if (importedMessages) { // 成功导入 this.hasExportedHistory = false; // 导入新历史后重置导出状态 const historyMgmt = this.messages.main.historyManagement; const successMsg = historyMgmt.importFromFileSuccess .replace('{filePath}', filePath) .replace('{count}', importedMessages.length.toString()); console.log(chalk_1.default.green(successMsg)); return { handled: true, shouldContinue: true, newMessages: importedMessages }; } else { // 导入失败 const historyMgmt = this.messages.main.historyManagement; const failedMsg = historyMgmt.importFromFileFailed.replace('{filePath}', filePath); console.log(chalk_1.default.red(failedMsg)); return { handled: true, shouldContinue: true }; } } // 处理退出前的历史记录导出检查 async handleExitWithHistoryCheck(currentMessages) { // 如果已经导出过历史记录,或者没有历史记录,直接退出 if (this.hasExportedHistory || currentMessages.length === 0) { return 'skip'; } const historyMgmt = this.messages.main.historyManagement; // 显示提示信息 console.log(chalk_1.default.yellow(historyMgmt.confirmExitPrompt)); console.log(chalk_1.default.gray(historyMgmt.confirmExitOptions)); return new Promise((resolve) => { // 设置原始模式处理单个按键 if (process.stdin.isTTY) { process.stdin.setRawMode(true); } process.stdin.resume(); process.stdin.setEncoding('utf8'); const onKeyPress = async (key) => { const keyCode = key.charCodeAt(0); // 清理输入监听器 const cleanup = () => { process.stdin.removeAllListeners('data'); process.stdin.removeAllListeners('error'); process.stdin.removeAllListeners('end'); if (process.stdin.isTTY) { try { process.stdin.setRawMode(false); } catch (error) { // 忽略错误 } } process.stdin.pause(); }; if (keyCode === 89 || keyCode === 121) { // Y 或 y cleanup(); process.stdout.write('y\n'); try { const exportSuccess = await history_1.HistoryService.exportHistory(currentMessages, this.messages); if (exportSuccess) { this.hasExportedHistory = true; } resolve('export'); } catch (error) { console.error(chalk_1.default.red(historyMgmt.exportFailedDirectExit)); resolve('export'); } } else if (keyCode === 78 || keyCode === 110) { // N 或 n cleanup(); process.stdout.write('n\n'); this.hasExportedHistory = true; // 用户选择跳过,也标记为已处理 resolve('skip'); } else if (keyCode === 67 || keyCode === 99 || keyCode === 3) { // C 或 c 或 Ctrl+C cleanup(); process.stdout.write('c\n'); resolve('cancel'); } else if (keyCode === 13) { // Enter - 默认选择导出 cleanup(); process.stdout.write('y\n'); try { const exportSuccess = await history_1.HistoryService.exportHistory(currentMessages, this.messages); if (exportSuccess) { this.hasExportedHistory = true; } resolve('export'); } catch (error) { console.error(chalk_1.default.red(historyMgmt.exportFailedDirectExit)); resolve('export'); } } // 忽略其他按键 }; // 错误处理 const onError = () => { process.stdin.removeAllListeners('data'); process.stdin.removeAllListeners('error'); process.stdin.removeAllListeners('end'); if (process.stdin.isTTY) { try { process.stdin.setRawMode(false); } catch (error) { // 忽略错误 } } process.stdin.pause(); resolve('skip'); }; process.stdin.on('data', onKeyPress); process.stdin.on('error', onError); process.stdin.on('end', onError); }); } // 检查是否在等待文件导入 isWaitingForFileImportState() { return this.isWaitingForFileImport; } /** * 显示历史记录 */ async showHistory(messages) { const historyMessages = language_1.languageService.getMessages(); console.log(chalk_1.default.bold.yellow(`\n--- ${historyMessages.main.messages.historyTitle} ---`)); if (messages.length === 0) { console.log(chalk_1.default.gray(historyMessages.main.messages.noHistory)); } else { messages.forEach((msg) => { const time = msg.timestamp.toLocaleTimeString(historyMessages.main.messages.format.timeLocale, { hour: '2-digit', minute: '2-digit', }); const role = chalk_1.default.bold(msg.type.toUpperCase()); let content = ''; if (typeof msg.content === 'string') { content = msg.content.length > 100 ? msg.content.substring(0, 97) + '...' : msg.content; } else if (msg.tool_calls) { content = `[TOOL CALLS: ${msg.tool_calls.map((tc) => tc.function.name).join(', ')}]`; } else if (msg.content) { content = '[OBJECT CONTENT]'; } console.log(`${chalk_1.default.cyan(time)} [${role}] - ${content}`); }); // 显示Token使用统计 const stats = await token_calculator_1.TokenCalculator.getContextUsageStats(messages, '', 0.8); const statsMessage = historyMessages.main.messages.tokenUsage.tokenStats .replace('{used}', stats.used.toString()) .replace('{max}', stats.maxAllowed.toString()) .replace('{percentage}', stats.percentage.toString()); console.log(chalk_1.default.bold.yellow(`\n--- Token Usage ---`)); console.log(statsMessage); if (stats.isNearLimit) { console.log(chalk_1.default.yellow(historyMessages.main.messages.tokenUsage.nearLimit)); } } console.log(chalk_1.default.bold.yellow('---------------------\n')); } } exports.CommandManager = CommandManager; //# sourceMappingURL=commands.js.map