@sethdouglasford/claude-flow
Version:
Claude Code Flow - Advanced AI-powered development workflows with SPARC methodology
258 lines ⢠9.62 kB
JavaScript
/**
* Memory management commands
*/
import { promises as fs } from "node:fs";
import { Command } from "../cliffy-compat.js";
import { colors } from "../cliffy-compat.js";
import { existsSync } from "node:fs";
export class SimpleMemoryManager {
filePath = "./memory/memory-store.json";
data = {};
async load() {
if (existsSync(this.filePath)) {
const content = await fs.readFile(this.filePath, "utf-8");
this.data = JSON.parse(content);
}
}
async save() {
// Ensure directory exists
const dir = this.filePath.split("/").slice(0, -1).join("/");
if (dir && !existsSync(dir)) {
await fs.mkdir(dir, { recursive: true });
}
await fs.writeFile(this.filePath, JSON.stringify(this.data, null, 2), "utf-8");
}
async store(key, value, namespace = "default") {
await this.load();
if (!this.data[namespace]) {
this.data[namespace] = [];
}
// Check if key already exists and update it
const existingIndex = this.data[namespace].findIndex(entry => entry.key === key);
const entry = {
key,
value,
namespace,
timestamp: Date.now(),
};
if (existingIndex >= 0) {
this.data[namespace][existingIndex] = entry;
}
else {
this.data[namespace].push(entry);
}
await this.save();
}
async query(search, namespace) {
await this.load();
const results = [];
const namespaces = namespace ? [namespace] : Object.keys(this.data);
for (const ns of namespaces) {
if (this.data[ns]) {
const entries = this.data[ns];
// Handle both array and object formats
if (Array.isArray(entries)) {
for (const entry of entries) {
if (entry.key.includes(search) || entry.value.includes(search)) {
results.push(entry);
}
}
}
else if (typeof entries === "object" && entries !== null) {
// For object format, skip non-array namespaces for now
// Could be enhanced to search object properties if needed
continue;
}
}
}
return results;
}
async getStats() {
await this.load();
let totalEntries = 0;
const namespaceStats = {};
for (const [namespace, entries] of Object.entries(this.data)) {
// Handle both array and object formats
if (Array.isArray(entries)) {
namespaceStats[namespace] = entries.length;
totalEntries += entries.length;
}
else if (typeof entries === "object" && entries !== null) {
// For object format, count the number of properties
const count = Object.keys(entries).length;
namespaceStats[namespace] = count;
totalEntries += count;
}
else {
namespaceStats[namespace] = 0;
}
}
return {
totalEntries,
namespaces: Object.keys(this.data).length,
namespaceStats,
sizeBytes: new TextEncoder().encode(JSON.stringify(this.data)).length,
};
}
async exportData(filePath) {
await this.load();
await fs.writeFile(filePath, JSON.stringify(this.data, null, 2), "utf-8");
}
async importData(filePath) {
const content = await fs.readFile(filePath, "utf-8");
this.data = JSON.parse(content);
await this.save();
}
async cleanup(daysOld = 30) {
await this.load();
const cutoffTime = Date.now() - (daysOld * 24 * 60 * 60 * 1000);
let removedCount = 0;
for (const namespace of Object.keys(this.data)) {
const before = this.data[namespace].length;
this.data[namespace] = this.data[namespace].filter(e => e.timestamp > cutoffTime);
removedCount += before - this.data[namespace].length;
}
await this.save();
return removedCount;
}
}
export const memoryCommand = new Command()
.name("memory")
.description("Manage memory bank")
.action(() => {
memoryCommand.outputHelp();
});
// Add subcommands properly
memoryCommand
.command("store")
.description("Store information in memory")
.arguments("<key> <value>")
.option("-n, --namespace <namespace>", "Target namespace", "default")
.action(async (key, value, options) => {
try {
const memory = new SimpleMemoryManager();
await memory.store(key, value, options.namespace);
console.log(colors.green("ā
Stored successfully"));
console.log(`š Key: ${key}`);
console.log(`š¦ Namespace: ${options.namespace}`);
console.log(`š¾ Size: ${new TextEncoder().encode(value).length} bytes`);
}
catch (error) {
console.error(colors.red("Failed to store:"), error.message);
}
});
memoryCommand
.command("query")
.description("Search memory entries")
.arguments("<search>")
.option("-n, --namespace <namespace>", "Filter by namespace")
.option("-l, --limit <limit>", "Limit results", "10")
.action(async (search, options) => {
try {
const memory = new SimpleMemoryManager();
const results = await memory.query(search, options.namespace);
if (results.length === 0) {
console.log(colors.yellow("No results found"));
return;
}
console.log(colors.green(`ā
Found ${results.length} results:`));
const limit = parseInt(options.limit) || 10;
const limited = results.slice(0, limit);
for (const entry of limited) {
console.log(colors.blue(`\nš ${entry.key}`));
console.log(` Namespace: ${entry.namespace}`);
console.log(` Value: ${entry.value.substring(0, 100)}${entry.value.length > 100 ? "..." : ""}`);
console.log(` Stored: ${new Date(entry.timestamp).toLocaleString()}`);
}
if (results.length > limit) {
console.log(colors.gray(`\n... and ${results.length - limit} more results`));
}
}
catch (error) {
console.error(colors.red("Failed to query:"), error.message);
}
});
memoryCommand
.command("export")
.description("Export memory to file")
.arguments("<file>")
.action(async (file, options) => {
try {
const memory = new SimpleMemoryManager();
await memory.exportData(file);
const stats = await memory.getStats();
console.log(colors.green("ā
Memory exported successfully"));
console.log(`š File: ${file}`);
console.log(`š Entries: ${stats.totalEntries}`);
console.log(`š¾ Size: ${(stats.sizeBytes / 1024).toFixed(2)} KB`);
}
catch (error) {
console.error(colors.red("Failed to export:"), error.message);
}
});
memoryCommand
.command("import")
.description("Import memory from file")
.arguments("<file>")
.action(async (file, options) => {
try {
const memory = new SimpleMemoryManager();
await memory.importData(file);
const stats = await memory.getStats();
console.log(colors.green("ā
Memory imported successfully"));
console.log(`š File: ${file}`);
console.log(`š Entries: ${stats.totalEntries}`);
console.log(`šļø Namespaces: ${stats.namespaces}`);
}
catch (error) {
console.error(colors.red("Failed to import:"), error.message);
}
});
memoryCommand
.command("stats")
.description("Show memory statistics")
.option("--json", "Output in JSON format")
.action(async (options) => {
try {
const memory = new SimpleMemoryManager();
const stats = await memory.getStats();
if (options.json) {
console.log(JSON.stringify(stats, null, 2));
return;
}
console.log(colors.green("š Memory Bank Statistics:"));
console.log(` Total Entries: ${stats.totalEntries}`);
console.log(` Namespaces: ${stats.namespaces}`);
console.log(` Size: ${(stats.sizeBytes / 1024).toFixed(2)} KB`);
if (stats.namespaces > 0) {
console.log(colors.blue("\nš Namespace Breakdown:"));
for (const [namespace, count] of Object.entries(stats.namespaceStats)) {
console.log(` ${namespace}: ${count} entries`);
}
}
}
catch (error) {
if (options.json) {
console.log(JSON.stringify({ error: error instanceof Error ? error.message : String(error) }, null, 2));
}
else {
console.error(colors.red("Failed to get stats:"), error.message);
}
}
});
memoryCommand
.command("cleanup")
.description("Clean up old entries")
.option("-d, --days <days>", "Entries older than n days", "30")
.action(async (options) => {
try {
const memory = new SimpleMemoryManager();
const removed = await memory.cleanup(parseInt(options.days) || 30);
console.log(colors.green("ā
Cleanup completed"));
console.log(`šļø Removed: ${removed} entries older than ${options.days} days`);
}
catch (error) {
console.error(colors.red("Failed to cleanup:"), error.message);
}
});
//# sourceMappingURL=memory.js.map