@webdevtoday/grok-cli
Version:
A sophisticated CLI tool for interacting with xAI Grok 4, featuring conversation history, file reference, custom commands, memory system, and genetic development workflows
251 lines • 9.35 kB
JavaScript
;
/**
* Conversation history management for Grok CLI
* Handles conversation persistence, loading, and continuation
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.HistoryManager = void 0;
const fs_extra_1 = require("fs-extra");
const path_1 = require("path");
const os_1 = require("os");
/**
* History manager for conversation persistence
*/
class HistoryManager {
constructor() {
this.conversations = new Map();
this.historyDir = (0, path_1.join)((0, os_1.homedir)(), '.grok-cli', 'history');
this.historyIndex = (0, path_1.join)(this.historyDir, 'conversations.json');
}
/**
* Initialize history directory and load conversation index
*/
async initialize() {
await (0, fs_extra_1.ensureDir)(this.historyDir);
await this.loadConversationIndex();
}
/**
* Save a conversation session to history
*/
async saveConversation(session, messages) {
if (messages.length === 0)
return;
await this.initialize();
const conversationFile = (0, path_1.join)(this.historyDir, `${session.id}.json`);
const now = new Date();
const historySession = {
session,
messages,
metadata: {
totalMessages: messages.length,
startTime: session.startTime,
lastUpdateTime: now,
cwd: session.cwd,
},
};
await (0, fs_extra_1.writeFile)(conversationFile, JSON.stringify(historySession, null, 2));
// Update conversation index
const firstMessage = messages[0]?.content || '';
const lastMessage = messages[messages.length - 1]?.content || '';
const conversationHistory = {
id: session.id,
startTime: session.startTime,
lastUpdateTime: now,
cwd: session.cwd,
messageCount: messages.length,
firstMessage: firstMessage.slice(0, 100),
lastMessage: lastMessage.slice(0, 100),
filePath: conversationFile,
};
this.conversations.set(session.id, conversationHistory);
await this.saveConversationIndex();
}
/**
* Load a conversation from history
*/
async loadConversation(sessionId) {
await this.initialize();
const conversation = this.conversations.get(sessionId);
if (!conversation || !(await (0, fs_extra_1.pathExists)(conversation.filePath))) {
return null;
}
try {
const content = await (0, fs_extra_1.readFile)(conversation.filePath, 'utf-8');
const historySession = JSON.parse(content);
// Convert date strings back to Date objects
historySession.session.startTime = new Date(historySession.session.startTime);
historySession.metadata.startTime = new Date(historySession.metadata.startTime);
historySession.metadata.lastUpdateTime = new Date(historySession.metadata.lastUpdateTime);
historySession.messages.forEach(message => {
message.timestamp = new Date(message.timestamp);
if (message.toolCalls) {
message.toolCalls.forEach(toolCall => {
toolCall.timestamp = new Date(toolCall.timestamp);
});
}
});
return historySession;
}
catch (error) {
console.error(`Failed to load conversation ${sessionId}:`, error);
return null;
}
}
/**
* Get the most recent conversation
*/
async getLastConversation() {
await this.initialize();
if (this.conversations.size === 0) {
return null;
}
const conversations = Array.from(this.conversations.values());
conversations.sort((a, b) => b.lastUpdateTime.getTime() - a.lastUpdateTime.getTime());
return conversations[0] || null;
}
/**
* List recent conversations
*/
async getRecentConversations(limit = 10) {
await this.initialize();
const conversations = Array.from(this.conversations.values());
conversations.sort((a, b) => b.lastUpdateTime.getTime() - a.lastUpdateTime.getTime());
return conversations.slice(0, limit);
}
/**
* Search conversations by content
*/
async searchConversations(query, limit = 10) {
await this.initialize();
const queryLower = query.toLowerCase();
const matchingConversations = Array.from(this.conversations.values()).filter(conv => {
return conv.firstMessage.toLowerCase().includes(queryLower) ||
conv.lastMessage.toLowerCase().includes(queryLower);
});
matchingConversations.sort((a, b) => b.lastUpdateTime.getTime() - a.lastUpdateTime.getTime());
return matchingConversations.slice(0, limit);
}
/**
* Delete a conversation from history
*/
async deleteConversation(sessionId) {
await this.initialize();
const conversation = this.conversations.get(sessionId);
if (!conversation) {
return false;
}
try {
if (await (0, fs_extra_1.pathExists)(conversation.filePath)) {
await require('fs-extra').remove(conversation.filePath);
}
this.conversations.delete(sessionId);
await this.saveConversationIndex();
return true;
}
catch (error) {
console.error(`Failed to delete conversation ${sessionId}:`, error);
return false;
}
}
/**
* Get conversation statistics
*/
async getStatistics() {
await this.initialize();
const conversations = Array.from(this.conversations.values());
if (conversations.length === 0) {
return {
totalConversations: 0,
totalMessages: 0,
oldestConversation: null,
newestConversation: null,
avgMessagesPerConversation: 0,
};
}
const totalMessages = conversations.reduce((sum, conv) => sum + conv.messageCount, 0);
const dates = conversations.map(conv => conv.startTime);
const oldestConversation = new Date(Math.min(...dates.map(d => d.getTime())));
const newestConversation = new Date(Math.max(...dates.map(d => d.getTime())));
return {
totalConversations: conversations.length,
totalMessages,
oldestConversation,
newestConversation,
avgMessagesPerConversation: Math.round(totalMessages / conversations.length),
};
}
/**
* Clean up old conversations
*/
async cleanupOldConversations(maxAge = 30) {
await this.initialize();
const maxAgeMs = maxAge * 24 * 60 * 60 * 1000; // Convert days to milliseconds
const cutoffDate = new Date(Date.now() - maxAgeMs);
const toDelete = Array.from(this.conversations.values()).filter(conv => conv.lastUpdateTime < cutoffDate);
let deletedCount = 0;
for (const conversation of toDelete) {
if (await this.deleteConversation(conversation.id)) {
deletedCount++;
}
}
return deletedCount;
}
/**
* Export conversation history
*/
async exportConversations(outputPath) {
await this.initialize();
const exportData = {
exportDate: new Date(),
conversations: Array.from(this.conversations.values()),
totalConversations: this.conversations.size,
};
await (0, fs_extra_1.ensureDir)((0, path_1.dirname)(outputPath));
await (0, fs_extra_1.writeFile)(outputPath, JSON.stringify(exportData, null, 2));
}
/**
* Load conversation index from disk
*/
async loadConversationIndex() {
if (!(await (0, fs_extra_1.pathExists)(this.historyIndex))) {
return;
}
try {
const content = await (0, fs_extra_1.readFile)(this.historyIndex, 'utf-8');
const data = JSON.parse(content);
this.conversations.clear();
for (const conv of data.conversations || []) {
// Convert date strings back to Date objects
conv.startTime = new Date(conv.startTime);
conv.lastUpdateTime = new Date(conv.lastUpdateTime);
this.conversations.set(conv.id, conv);
}
}
catch (error) {
console.error('Failed to load conversation index:', error);
}
}
/**
* Save conversation index to disk
*/
async saveConversationIndex() {
const data = {
lastUpdated: new Date(),
conversations: Array.from(this.conversations.values()),
};
try {
await (0, fs_extra_1.writeFile)(this.historyIndex, JSON.stringify(data, null, 2));
}
catch (error) {
console.error('Failed to save conversation index:', error);
}
}
/**
* Get history directory path
*/
getHistoryDir() {
return this.historyDir;
}
}
exports.HistoryManager = HistoryManager;
//# sourceMappingURL=history.js.map