@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
349 lines (335 loc) • 12.5 kB
JavaScript
;
/**
* Memory System for Grok CLI
* Handles GROK.md files and project context injection
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.MemoryManager = void 0;
const fs_1 = require("fs");
const path_1 = __importDefault(require("path"));
const chalk_1 = __importDefault(require("chalk"));
/**
* Memory Manager - handles GROK.md files and project context
*/
class MemoryManager {
constructor(baseDir, config) {
this.memoryFiles = new Map();
this.baseDir = baseDir;
this.config = config;
}
/**
* Load all memory files from the project
*/
async loadMemoryFiles() {
console.log(chalk_1.default.blue('🧠 Loading memory files...'));
this.memoryFiles.clear();
try {
// Load GROK.md from current directory
await this.loadMemoryFile(path_1.default.join(this.baseDir, 'GROK.md'), 'project');
// Load GROK.md from parent directories (up to maxDepth)
await this.loadParentMemoryFiles();
// Load user-level memory
await this.loadUserMemory();
console.log(chalk_1.default.green(`✅ Loaded ${this.memoryFiles.size} memory files`));
}
catch (error) {
console.warn(chalk_1.default.yellow('⚠️ No memory files found or error loading:'), error instanceof Error ? error.message : String(error));
}
}
/**
* Load GROK.md files from parent directories
*/
async loadParentMemoryFiles() {
let currentDir = path_1.default.dirname(this.baseDir);
let depth = 0;
while (depth < this.config.maxDepth && currentDir !== path_1.default.dirname(currentDir)) {
const memoryPath = path_1.default.join(currentDir, 'GROK.md');
try {
await this.loadMemoryFile(memoryPath, 'local');
}
catch {
// File doesn't exist, continue
}
currentDir = path_1.default.dirname(currentDir);
depth++;
}
}
/**
* Load user-level memory from home directory
*/
async loadUserMemory() {
const homeDir = process.env['HOME'] || process.env['USERPROFILE'];
if (!homeDir)
return;
const userMemoryPath = path_1.default.join(homeDir, '.grok-cli', 'GROK.md');
try {
await this.loadMemoryFile(userMemoryPath, 'user');
}
catch {
// User memory file doesn't exist, that's fine
}
}
/**
* Load a specific memory file
*/
async loadMemoryFile(filePath, type) {
try {
const content = await fs_1.promises.readFile(filePath, 'utf-8');
const imports = this.extractImports(content);
const memoryFile = {
path: filePath,
content,
imports,
type
};
this.memoryFiles.set(filePath, memoryFile);
console.log(chalk_1.default.gray(` 📄 Loaded ${type} memory: ${path_1.default.relative(this.baseDir, filePath)}`));
// Load imported files
for (const importPath of imports) {
await this.loadImportedFile(importPath, filePath);
}
}
catch (error) {
throw new Error(`Failed to load memory file ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* Load files referenced by memory files
*/
async loadImportedFile(importPath, fromFile) {
try {
const resolvedPath = path_1.default.resolve(path_1.default.dirname(fromFile), importPath);
// Check if path should be excluded
if (this.shouldExcludePath(resolvedPath)) {
return;
}
const content = await fs_1.promises.readFile(resolvedPath, 'utf-8');
const imports = this.extractImports(content);
const memoryFile = {
path: resolvedPath,
content,
imports,
type: 'local'
};
this.memoryFiles.set(resolvedPath, memoryFile);
console.log(chalk_1.default.gray(` 🔗 Imported: ${path_1.default.relative(this.baseDir, resolvedPath)}`));
}
catch (error) {
console.warn(chalk_1.default.yellow(`⚠️ Failed to load import ${importPath}:`), error instanceof Error ? error.message : String(error));
}
}
/**
* Extract imports from memory file content
*/
extractImports(content) {
const imports = [];
// Look for @import directives
const importRegex = /@import\s+["']([^"']+)["']/g;
let match;
while ((match = importRegex.exec(content)) !== null) {
if (match[1]) {
imports.push(match[1]);
}
}
// Look for file references
const fileRefRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
while ((match = fileRefRegex.exec(content)) !== null) {
const filePath = match[2];
if (filePath && (filePath.startsWith('./') || filePath.startsWith('../') || !filePath.includes('://'))) {
imports.push(filePath);
}
}
return imports;
}
/**
* Check if a path should be excluded based on configuration
*/
shouldExcludePath(filePath) {
const relativePath = path_1.default.relative(this.baseDir, filePath);
return this.config.excludePatterns.some(pattern => {
// Convert glob pattern to regex
const regexPattern = pattern
.replace(/\*\*/g, '.*')
.replace(/\*/g, '[^/]*')
.replace(/\?/g, '[^/]');
const regex = new RegExp(`^${regexPattern}$`);
return regex.test(relativePath);
});
}
/**
* Get compiled memory content for injection into conversations
*/
getMemoryContent() {
if (this.memoryFiles.size === 0) {
return '';
}
const sections = [];
// Add user memory first
const userMemory = Array.from(this.memoryFiles.values()).filter(f => f.type === 'user');
if (userMemory.length > 0) {
sections.push('# User Context\n');
userMemory.forEach(file => {
sections.push(`## ${path_1.default.basename(file.path, '.md')}\n${file.content}\n`);
});
}
// Add project memory
const projectMemory = Array.from(this.memoryFiles.values()).filter(f => f.type === 'project');
if (projectMemory.length > 0) {
sections.push('# Project Context\n');
projectMemory.forEach(file => {
sections.push(`## ${path_1.default.basename(file.path, '.md')}\n${file.content}\n`);
});
}
// Add local memory
const localMemory = Array.from(this.memoryFiles.values()).filter(f => f.type === 'local');
if (localMemory.length > 0) {
sections.push('# Local Context\n');
localMemory.forEach(file => {
const relativePath = path_1.default.relative(this.baseDir, file.path);
sections.push(`## ${relativePath}\n${file.content}\n`);
});
}
return sections.join('\n');
}
/**
* Get memory statistics
*/
getMemoryStats() {
const files = Array.from(this.memoryFiles.values());
return {
totalFiles: files.length,
userFiles: files.filter(f => f.type === 'user').length,
projectFiles: files.filter(f => f.type === 'project').length,
localFiles: files.filter(f => f.type === 'local').length,
totalSize: files.reduce((sum, f) => sum + f.content.length, 0)
};
}
/**
* Get all loaded memory files
*/
getMemoryFiles() {
return Array.from(this.memoryFiles.values());
}
/**
* Add content to memory
*/
async addToMemory(content, type = 'note') {
const memoryPath = path_1.default.join(this.baseDir, 'GROK.md');
try {
let existingContent = '';
try {
existingContent = await fs_1.promises.readFile(memoryPath, 'utf-8');
}
catch {
// File doesn't exist yet
}
const timestamp = new Date().toISOString();
const newEntry = `\n## ${type === 'note' ? 'Note' : 'Context'} - ${timestamp}\n\n${content}\n`;
const updatedContent = existingContent + newEntry;
await fs_1.promises.writeFile(memoryPath, updatedContent, 'utf-8');
// Reload memory files to include the new content
await this.loadMemoryFiles();
console.log(chalk_1.default.green(`✅ Added to memory: ${type}`));
}
catch (error) {
console.error(chalk_1.default.red('❌ Failed to add to memory:'), error instanceof Error ? error.message : String(error));
}
}
/**
* Create a new GROK.md file with template
*/
async createMemoryFile(filePath) {
const targetPath = filePath || path_1.default.join(this.baseDir, 'GROK.md');
const template = `# Grok CLI Memory
## Project Overview
Describe your project goals, architecture, and key concepts here.
## Key Files
List and describe the most important files in your project:
- \`src/\` - Main source code
- \`tests/\` - Test files
- \`docs/\` - Documentation
## Development Workflow
Document your development process:
- Build: \`npm run build\`
- Test: \`npm test\`
- Deploy: \`npm run deploy\`
## Important Notes
Add any important notes, decisions, or context that should be remembered across sessions.
## Custom Commands
Document project-specific workflows and commands:
- Setup: \`/custom run setup-dev\`
- Test: \`/custom run run-tests\`
## Dependencies
Key dependencies and their purposes:
- **Framework**: What framework you're using and why
- **Tools**: Important development tools
- **Services**: External services and APIs
## Architecture Decisions
Record important architectural decisions and their rationales.
`;
try {
// Check if file already exists
try {
await fs_1.promises.access(targetPath);
console.log(chalk_1.default.yellow(`⚠️ Memory file already exists: ${targetPath}`));
return;
}
catch {
// File doesn't exist, proceed
}
await fs_1.promises.writeFile(targetPath, template, 'utf-8');
console.log(chalk_1.default.green(`✅ Created memory file: ${targetPath}`));
// Load the new file
await this.loadMemoryFiles();
}
catch (error) {
console.error(chalk_1.default.red('❌ Failed to create memory file:'), error instanceof Error ? error.message : String(error));
}
}
/**
* Search memory content
*/
searchMemory(query) {
const results = [];
const searchRegex = new RegExp(query, 'gi');
for (const file of this.memoryFiles.values()) {
const lines = file.content.split('\n');
const matches = [];
lines.forEach((line, index) => {
if (searchRegex.test(line)) {
const start = Math.max(0, index - 2);
const end = Math.min(lines.length - 1, index + 2);
const context = lines.slice(start, end + 1).join('\n');
matches.push({
line: index + 1,
content: line,
context
});
}
});
if (matches.length > 0) {
results.push({ file, matches });
}
}
return results;
}
/**
* Clear all loaded memory
*/
clearMemory() {
this.memoryFiles.clear();
console.log(chalk_1.default.blue('🧠 Memory cleared'));
}
/**
* Reload memory files
*/
async reloadMemory() {
console.log(chalk_1.default.blue('🔄 Reloading memory files...'));
await this.loadMemoryFiles();
}
}
exports.MemoryManager = MemoryManager;
//# sourceMappingURL=memory.js.map