@stackmemoryai/stackmemory
Version:
Project-scoped memory for AI coding tools. Durable context across sessions with MCP integration, frames, smart retrieval, Claude Code skills, and automatic hooks.
193 lines (192 loc) • 6.76 kB
JavaScript
import { fileURLToPath as __fileURLToPath } from 'url';
import { dirname as __pathDirname } from 'path';
const __filename = __fileURLToPath(import.meta.url);
const __dirname = __pathDirname(__filename);
import { ChromaClient } from "chromadb";
import * as fs from "fs";
import * as path from "path";
import * as os from "os";
import * as readline from "readline";
import dotenv from "dotenv";
dotenv.config();
async function testChromaDBConnection() {
const apiKey = process.env.CHROMADB_API_KEY;
const tenant = process.env.CHROMADB_TENANT;
const database = process.env.CHROMADB_DATABASE || "stackmemory";
if (!apiKey || !tenant) {
console.error("\u274C Missing ChromaDB credentials in .env");
console.log(" CHROMADB_API_KEY:", apiKey ? "\u2713 Set" : "\u2717 Missing");
console.log(" CHROMADB_TENANT:", tenant ? "\u2713 Set" : "\u2717 Missing");
console.log(" CHROMADB_DATABASE:", database);
return null;
}
console.log("\u{1F504} Connecting to ChromaDB Cloud...");
console.log(" Tenant:", tenant);
console.log(" Database:", database);
try {
const client = new ChromaClient({
ssl: true,
host: "api.trychroma.com",
port: 443,
headers: {
"X-Chroma-Token": apiKey
},
tenant,
database
});
const heartbeat = await client.heartbeat();
console.log("\u2705 ChromaDB connection successful:", heartbeat);
return client;
} catch (error) {
console.error("\u274C Failed to connect to ChromaDB:", error.message);
return null;
}
}
async function getOrCreateCollection(client) {
const collectionName = "stackmemory_contexts";
try {
const collections = await client.listCollections();
console.log("\u{1F4DA} Existing collections:", collections.map((c) => c.name).join(", ") || "none");
const collection = await client.getOrCreateCollection({
name: collectionName,
metadata: {
description: "StackMemory context storage",
created_by: "test-chromadb-sync",
version: "2.0.0"
}
});
console.log(`\u2705 Collection '${collectionName}' ready`);
const count = await collection.count();
console.log(` Current entries: ${count}`);
return collection;
} catch (error) {
console.error("\u274C Failed to create/get collection:", error.message);
return null;
}
}
async function loadLocalContexts() {
const storageFile = path.join(os.homedir(), ".stackmemory", "context-storage", "contexts.jsonl");
if (!fs.existsSync(storageFile)) {
console.log("\u26A0\uFE0F No local contexts found");
return [];
}
const contexts = [];
const fileStream = fs.createReadStream(storageFile);
const rl = readline.createInterface({
input: fileStream,
crlfDelay: Infinity
});
for await (const line of rl) {
if (line.trim()) {
try {
contexts.push(JSON.parse(line));
} catch (error) {
console.warn("Failed to parse line:", line.substring(0, 50));
}
}
}
console.log(`\u{1F4C1} Loaded ${contexts.length} local contexts`);
return contexts;
}
async function syncContextsToChromaDB(collection, contexts) {
console.log(`
\u{1F504} Syncing ${contexts.length} contexts to ChromaDB...`);
let synced = 0;
let failed = 0;
const batchSize = 10;
for (let i = 0; i < contexts.length; i += batchSize) {
const batch = contexts.slice(i, Math.min(i + batchSize, contexts.length));
const ids = [];
const documents = [];
const metadatas = [];
for (const ctx of batch) {
ids.push(ctx.id || `ctx_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`);
documents.push(ctx.content || JSON.stringify(ctx));
const metadata = {
timestamp: ctx.timestamp || ctx.stored_at || (/* @__PURE__ */ new Date()).toISOString(),
type: ctx.type || "context",
user_id: ctx.user_id || process.env.USER || "default",
project: ctx.project || "stackmemory"
};
if (ctx.session_id) metadata.session_id = ctx.session_id;
if (ctx.metadata) {
Object.entries(ctx.metadata).forEach(([key, value]) => {
if (value !== void 0 && value !== null) {
metadata[key] = String(value);
}
});
}
metadatas.push(metadata);
}
try {
await collection.upsert({
ids,
documents,
metadatas
});
synced += batch.length;
console.log(` \u2713 Batch ${Math.floor(i / batchSize) + 1}: ${batch.length} contexts synced`);
} catch (error) {
failed += batch.length;
console.error(` \u2717 Batch ${Math.floor(i / batchSize) + 1} failed:`, error.message);
}
}
console.log(`
\u{1F4CA} Sync complete:`);
console.log(` \u2705 Synced: ${synced}`);
console.log(` \u274C Failed: ${failed}`);
return { synced, failed };
}
async function queryRecentContexts(collection) {
console.log("\n\u{1F50D} Querying recent contexts...");
try {
const results = await collection.query({
queryTexts: ["task complete code change"],
nResults: 5
});
if (results.ids && results.ids[0].length > 0) {
console.log(`Found ${results.ids[0].length} relevant contexts:`);
for (let i = 0; i < results.ids[0].length; i++) {
const metadata = results.metadatas[0][i];
const document = results.documents[0][i];
console.log(`
\u{1F4C4} Context ${i + 1}:`);
console.log(` ID: ${results.ids[0][i]}`);
console.log(` Type: ${metadata.type}`);
console.log(` Timestamp: ${metadata.timestamp}`);
console.log(` Content: ${document.substring(0, 100)}...`);
}
} else {
console.log("No contexts found");
}
} catch (error) {
console.error("Failed to query contexts:", error.message);
}
}
async function main() {
console.log("\u{1F680} ChromaDB Sync Test\n");
const client = await testChromaDBConnection();
if (!client) {
console.error("\n\u274C Cannot proceed without ChromaDB connection");
console.log("\n\u{1F4DD} To fix:");
console.log("1. Ensure you have a ChromaDB Cloud account");
console.log("2. Add credentials to .env:");
console.log(" CHROMADB_API_KEY=your-api-key");
console.log(" CHROMADB_TENANT=your-tenant-id");
console.log(" CHROMADB_DATABASE=stackmemory");
process.exit(1);
}
const collection = await getOrCreateCollection(client);
if (!collection) {
process.exit(1);
}
const contexts = await loadLocalContexts();
if (contexts.length > 0) {
await syncContextsToChromaDB(collection, contexts);
}
await queryRecentContexts(collection);
console.log("\n\u2705 Test complete!");
}
main().catch(console.error);
//# sourceMappingURL=test-chromadb-sync.js.map