'use strict'; // Load persisted config from ~/.memory-bridge/.env (populated by auth.init on first run) const path = require('path'); const os = require('os'); require('dotenv').config({ path: path.join(os.homedir(), '.memory-bridge', '.env') }); const express = require('express'); const auth = require('./auth'); const embedder = require('./embed'); const store = require('./store'); const PORT = Number(process.env.MEMORY_BRIDGE_PORT) || 3722; const VERSION = '1.0.0'; async function main() { auth.init(); await store.init(); const embeddingMode = await embedder.init(); const app = express(); app.use(express.json({ limit: '10mb' })); // ── GET /health (public) ────────────────────────────────────────────────── app.get('/health', async (_req, res) => { try { const memoriesCount = await store.count(); res.json({ status: 'ok', version: VERSION, embeddingMode, memoriesCount }); } catch (err) { res.status(500).json({ status: 'error', error: err.message }); } }); // ── Auth guard ──────────────────────────────────────────────────────────── app.use(auth.middleware); // ── POST /store ─────────────────────────────────────────────────────────── app.post('/store', async (req, res) => { const { text, tags = [], source = '' } = req.body || {}; if (!text || typeof text !== 'string' || !text.trim()) { return res.status(400).json({ error: '`text` is required' }); } try { const vector = await embedder.embed(text.trim()); const result = await store.add({ text: text.trim(), tags, source, vector }); res.status(201).json(result); } catch (err) { console.error('POST /store:', err.message); res.status(500).json({ error: err.message }); } }); // ── POST /recall ────────────────────────────────────────────────────────── app.post('/recall', async (req, res) => { const { query, limit = 10, tags = [] } = req.body || {}; if (!query || typeof query !== 'string' || !query.trim()) { return res.status(400).json({ error: '`query` is required' }); } try { const vector = await embedder.embed(query.trim()); const memories = await store.query({ vector, limit: Math.min(Number(limit) || 10, 100), tags, }); res.json({ memories }); } catch (err) { console.error('POST /recall:', err.message); res.status(500).json({ error: err.message }); } }); // ── POST /forget ────────────────────────────────────────────────────────── app.post('/forget', async (req, res) => { const { id } = req.body || {}; if (!id) return res.status(400).json({ error: '`id` is required' }); try { await store.remove(id); res.json({ message: 'forgotten', id }); } catch (err) { console.error('POST /forget:', err.message); res.status(500).json({ error: err.message }); } }); // ── GET /list ───────────────────────────────────────────────────────────── app.get('/list', async (req, res) => { const page = Math.max(1, parseInt(req.query.page) || 1); const pageSize = Math.min(100, Math.max(1, parseInt(req.query.pageSize) || 50)); try { const result = await store.list({ page, pageSize }); res.json(result); } catch (err) { console.error('GET /list:', err.message); res.status(500).json({ error: err.message }); } }); app.listen(PORT, () => { console.log(`\nmemory-bridge v${VERSION} listening on http://localhost:${PORT}`); console.log(`Embedding : ${embeddingMode}`); console.log(`Storage : ${path.join(os.homedir(), '.memory-bridge')}\n`); }); } main().catch((err) => { console.error('Failed to start memory-bridge:', err); process.exit(1); });