#!/usr/bin/env node import { createQdrantHttpClient } from '../memory-v2-qdrant.mjs'; const DEFAULT_URL_ENV = 'MEMORY_QDRANT_URL'; function usage() { return ` Usage: node scripts/smoke-memory-v2-qdrant.mjs [options] Runs a read-only Qdrant search smoke test. It does not create collections or write points. Options: --collection Collection name. Default: MEMORY_QDRANT_COLLECTION or memind_memory. --vector Query vector CSV. Default: 1,0,0. --limit Result limit. Default: 1. --url-env Env var containing Qdrant URL. Default: ${DEFAULT_URL_ENV}. --api-key-env Env var containing Qdrant API key. Default: MEMORY_QDRANT_API_KEY. --timeout-ms Request timeout. Default: 3000. -h, --help Show this help. `.trim(); } function parseVector(value) { const vector = String(value ?? '1,0,0') .split(',') .map((item) => Number(item.trim())); if (!vector.length || vector.some((item) => !Number.isFinite(item))) { throw new Error('--vector must be a comma-separated list of numbers'); } return vector; } export function parseMemoryQdrantSmokeArgs(argv = [], env = process.env) { const options = { collection: env.MEMORY_QDRANT_COLLECTION || 'memind_memory', vector: [1, 0, 0], limit: 1, urlEnv: DEFAULT_URL_ENV, apiKeyEnv: 'MEMORY_QDRANT_API_KEY', timeoutMs: 3000, help: false, }; for (let i = 0; i < argv.length; i += 1) { const arg = argv[i]; switch (arg) { case '--collection': options.collection = argv[++i] ?? ''; break; case '--vector': options.vector = parseVector(argv[++i]); break; case '--limit': options.limit = Number(argv[++i]); break; case '--url-env': options.urlEnv = argv[++i] ?? ''; break; case '--api-key-env': options.apiKeyEnv = argv[++i] ?? ''; break; case '--timeout-ms': options.timeoutMs = Number(argv[++i]); break; case '-h': case '--help': options.help = true; break; default: throw new Error(`Unknown argument: ${arg}`); } } if (!options.collection) throw new Error('--collection requires a value'); if (!Number.isInteger(options.limit) || options.limit < 1) { throw new Error('--limit requires a positive integer'); } if (!options.urlEnv) throw new Error('--url-env requires a value'); if (!options.apiKeyEnv) throw new Error('--api-key-env requires a value'); if (!Number.isFinite(options.timeoutMs) || options.timeoutMs < 1) { throw new Error('--timeout-ms requires a positive number'); } return options; } export async function runMemoryQdrantSmokeCli({ argv = process.argv.slice(2), env = process.env, stdout = process.stdout, fetchImpl = globalThis.fetch, } = {}) { const options = parseMemoryQdrantSmokeArgs(argv, env); if (options.help) { stdout.write(`${usage()}\n`); return { ok: true, mode: 'help' }; } const url = env[options.urlEnv]; if (!url) throw new Error(`Qdrant smoke requires ${options.urlEnv} to be set`); const client = createQdrantHttpClient({ url, apiKey: env[options.apiKeyEnv], fetchImpl, timeoutMs: options.timeoutMs, }); const points = await client.search({ collection: options.collection, vector: options.vector, limit: options.limit, }); const report = { ok: true, checkedAt: new Date().toISOString(), collection: options.collection, resultCount: points.length, readOnly: true, }; stdout.write(`${JSON.stringify(report, null, 2)}\n`); return report; } if (import.meta.url === `file://${process.argv[1]}`) { runMemoryQdrantSmokeCli().then((result) => { process.exitCode = result.ok ? 0 : 1; }).catch((err) => { console.error(err instanceof Error ? err.message : err); process.exitCode = 1; }); }