openai-cli-unofficial
Version:
A powerful OpenAI CLI Coding Agent built with TypeScript
576 lines • 25.9 kB
JavaScript
"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