#!/usr/bin/env node const BACKEND_SPECS = [ { name: 'pgvector', env: ['MEMORY_ENABLED', 'MEMORY_BACKEND', 'MEMORY_VECTOR_ENABLED', 'MEMORY_PGVECTOR_DATABASE_URL', 'MEMORY_PGVECTOR_EMBEDDING_MODULE'], recommended: { MEMORY_ENABLED: '1', MEMORY_BACKEND: 'pgvector', MEMORY_VECTOR_ENABLED: '1', MEMORY_PGVECTOR_DATABASE_URL: 'postgresql://john@127.0.0.1:5432/memind_memory', MEMORY_PGVECTOR_EMBEDDING_MODULE: './scripts/embed-memory-v2-local-hash.mjs', }, }, { name: 'qdrant', env: ['MEMORY_ENABLED', 'MEMORY_BACKEND', 'MEMORY_QDRANT_ENABLED', 'MEMORY_QDRANT_URL', 'MEMORY_QDRANT_EMBEDDING_MODULE'], recommended: { MEMORY_ENABLED: '1', MEMORY_BACKEND: 'qdrant', MEMORY_QDRANT_ENABLED: '1', MEMORY_QDRANT_URL: 'http://127.0.0.1:6333', MEMORY_QDRANT_EMBEDDING_MODULE: './scripts/embed-memory-v2-local-hash.mjs', }, }, { name: 'weaviate', env: ['MEMORY_ENABLED', 'MEMORY_BACKEND', 'MEMORY_WEAVIATE_ENABLED', 'MEMORY_WEAVIATE_URL', 'MEMORY_WEAVIATE_EMBEDDING_MODULE'], recommended: { MEMORY_ENABLED: '1', MEMORY_BACKEND: 'weaviate', MEMORY_WEAVIATE_ENABLED: '1', MEMORY_WEAVIATE_URL: 'http://127.0.0.1:8080', MEMORY_WEAVIATE_EMBEDDING_MODULE: './scripts/embed-memory-v2-local-hash.mjs', }, }, { name: 'mem0', env: ['MEMORY_ENABLED', 'MEMORY_BACKEND', 'MEMORY_MEM0_ENABLED', 'MEMORY_MEM0_API_KEY'], recommended: { MEMORY_ENABLED: '1', MEMORY_BACKEND: 'mem0', MEMORY_MEM0_ENABLED: '1', MEMORY_MEM0_API_KEY: 'replace-me', }, }, { name: 'letta', env: ['MEMORY_ENABLED', 'MEMORY_BACKEND', 'MEMORY_LETTA_ENABLED', 'MEMORY_LETTA_API_KEY', 'MEMORY_LETTA_AGENT_ID'], recommended: { MEMORY_ENABLED: '1', MEMORY_BACKEND: 'letta', MEMORY_LETTA_ENABLED: '1', MEMORY_LETTA_API_KEY: 'replace-me', MEMORY_LETTA_AGENT_ID: 'agent_1', }, }, { name: 'neo4j', env: ['MEMORY_ENABLED', 'MEMORY_BACKEND', 'MEMORY_NEO4J_ENABLED', 'MEMORY_NEO4J_HTTP_URL', 'MEMORY_NEO4J_USER', 'MEMORY_NEO4J_PASSWORD'], recommended: { MEMORY_ENABLED: '1', MEMORY_BACKEND: 'neo4j', MEMORY_NEO4J_ENABLED: '1', MEMORY_NEO4J_HTTP_URL: 'http://127.0.0.1:7474', MEMORY_NEO4J_USER: 'neo4j', MEMORY_NEO4J_PASSWORD: 'replace-me', }, }, { name: 'redis-streams', env: ['MEMORY_ENABLED', 'MEMORY_BACKEND', 'MEMORY_REDIS_STREAMS_ENABLED', 'MEMORY_REDIS_STREAMS_URL'], recommended: { MEMORY_ENABLED: '1', MEMORY_BACKEND: 'redis-streams', MEMORY_REDIS_STREAMS_ENABLED: '1', MEMORY_REDIS_STREAMS_URL: 'redis://127.0.0.1:6379', }, }, { name: 'langgraph', env: ['MEMORY_ENABLED', 'MEMORY_BACKEND', 'MEMORY_LANGGRAPH_ENABLED', 'MEMORY_LANGGRAPH_URL'], recommended: { MEMORY_ENABLED: '1', MEMORY_BACKEND: 'langgraph', MEMORY_LANGGRAPH_ENABLED: '1', MEMORY_LANGGRAPH_URL: 'http://127.0.0.1:2024', }, }, ]; function usage() { return `Usage: node scripts/check-memory-v2-config-gaps.mjs [options] Options: --backend Limit output to one backend. --format json | shell. Default: json. -h, --help Show this help. `; } export function parseMemoryV2ConfigGapArgs(argv = []) { const options = { backend: null, format: 'json', help: false, }; for (let i = 0; i < argv.length; i += 1) { const arg = argv[i]; if (arg === '--backend') { options.backend = argv[++i] ?? ''; } else if (arg === '--format') { options.format = argv[++i] ?? ''; } else if (arg === '-h' || arg === '--help') { options.help = true; } else { throw new Error(`Unknown argument: ${arg}`); } } if (options.backend && !BACKEND_SPECS.some((item) => item.name === options.backend)) { throw new Error(`--backend must be one of: ${BACKEND_SPECS.map((item) => item.name).join(', ')}`); } if (!['json', 'shell'].includes(options.format)) { throw new Error('--format must be json or shell'); } return options; } function inspectBackend(spec, env = process.env) { const present = {}; const missing = []; for (const key of spec.env) { const value = String(env[key] ?? '').trim(); if (value) present[key] = value; else missing.push(key); } return { backend: spec.name, ready: missing.length === 0, missing, presentKeys: Object.keys(present), recommended: spec.recommended, }; } function renderShell(reports) { const chunks = []; for (const report of reports) { chunks.push(`# ${report.backend}`); for (const [key, value] of Object.entries(report.recommended)) { chunks.push(`export ${key}='${String(value).replaceAll("'", "'\\''")}'`); } chunks.push(''); } return chunks.join('\n').trimEnd(); } export async function runMemoryV2ConfigGapCli({ argv = process.argv.slice(2), env = process.env, stdout = process.stdout, } = {}) { const options = parseMemoryV2ConfigGapArgs(argv); if (options.help) { stdout.write(usage()); return 0; } const reports = BACKEND_SPECS .filter((spec) => !options.backend || spec.name === options.backend) .map((spec) => inspectBackend(spec, env)); if (options.format === 'shell') { stdout.write(`${renderShell(reports)}\n`); return reports.every((item) => item.ready) ? 0 : 1; } const output = { ok: reports.every((item) => item.ready), checkedAt: new Date().toISOString(), reports, }; stdout.write(`${JSON.stringify(output, null, 2)}\n`); return output.ok ? 0 : 1; } if (import.meta.url === `file://${process.argv[1]}`) { runMemoryV2ConfigGapCli().then((code) => { process.exitCode = code; }).catch((err) => { console.error(err instanceof Error ? err.message : err); process.exitCode = 1; }); }