UNPKG

echonodesync

Version:

Autonomous, pluggable, secure memory bridge for agent/human/ritual ecosystems (DreamWeaver, EchoThreads, etc.)

139 lines (130 loc) 4.21 kB
#!/usr/bin/env node // 🧠🌸 EchoNodeSync: postMemory implementation (HTTP backend only) // Posts a memory value for a given key to the /api/memory HTTP backend. // Accepts either CLI args or a JSON file with { key, value }. const fs = require('fs'); const path = require('path'); const fetch = require('node-fetch'); const os = require('os'); function stripQuotes(str) { if (!str) return str; return str.replace(/^['"]+|['"]+$/g, '').trim(); } function resolveApiUrl() { // 1. Ritual: Check process.env if (process.env.ECHONODESYNC_API_URL) return stripQuotes(process.env.ECHONODESYNC_API_URL); // 2. Ritual: Check .env in cwd, then $HOME const envPaths = [ path.resolve(process.cwd(), '.env'), path.resolve(os.homedir(), '.env') ]; for (const envPath of envPaths) { if (fs.existsSync(envPath)) { const lines = fs.readFileSync(envPath, 'utf8').split(/\r?\n/); for (const line of lines) { const match = line.match(/^\s*ECHONODESYNC_API_URL\s*=\s*(.+)\s*$/); if (match) return stripQuotes(match[1]); } } } // 3. Ritual: Default return 'http://localhost:3000/api/memory'; } function resolveSecret() { // 1. Ritual: Check process.env if (process.env.ECHONODESYNC_SECRET) return stripQuotes(process.env.ECHONODESYNC_SECRET); // 2. Ritual: Check .env in cwd, then $HOME const envPaths = [ path.resolve(process.cwd(), '.env'), path.resolve(os.homedir(), '.env') ]; for (const envPath of envPaths) { if (fs.existsSync(envPath)) { const lines = fs.readFileSync(envPath, 'utf8').split(/\r?\n/); for (const line of lines) { const match = line.match(/^\s*ECHONODESYNC_SECRET\s*=\s*(.+)\s*$/); if (match) return stripQuotes(match[1]); } } } return null; } const BASE_URL = resolveApiUrl(); const SECRET = resolveSecret(); /** * Store a memory value by key. * @param {string} key - The memory key to store. * @param {string} value - The memory content to store. * @returns {Promise<void>} */ async function postMemory(key, value) { const url = `${BASE_URL}`; try { const headers = { 'Content-Type': 'application/json' }; if (SECRET) headers['Authorization'] = `Bearer ${SECRET}`; const res = await fetch(url, { method: 'POST', headers, body: JSON.stringify({ key, value }) }); if (!res.ok) throw new Error(`HTTP ${res.status}`); const data = await res.json(); console.log(`Memory posted for key '${key}'.`); return data; } catch (err) { console.error(`Failed to post memory for key '${key}':`, err.message); process.exit(1); } } function isAbsoluteUrl(url) { try { new URL(url); return true; } catch { return false; } } // CLI support if (require.main === module) { let key, value; // Support --key and --value flags const argv = process.argv.slice(2); if (argv.length === 1 && argv[0].endsWith('.json')) { // Read from JSON file const filePath = path.resolve(argv[0]); if (!fs.existsSync(filePath)) { console.error(`File not found: ${filePath}`); process.exit(1); } const obj = JSON.parse(fs.readFileSync(filePath, 'utf8')); key = obj.key; value = obj.value; if (!key || value === undefined) { console.error('JSON file must contain { "key": ..., "value": ... }'); process.exit(1); } } else if (argv.includes('--key') && argv.includes('--value')) { // Parse --key and --value flags const keyIdx = argv.indexOf('--key'); const valueIdx = argv.indexOf('--value'); key = argv[keyIdx + 1]; value = argv[valueIdx + 1]; if (!key || value === undefined) { console.error('Usage: post-memory --key <key> --value <value>'); process.exit(1); } } else if (argv.length === 2) { // Positional: <key> <value> key = argv[0]; value = argv[1]; } else { console.error('Usage: post-memory <key> <value> | post-memory --key <key> --value <value> | post-memory <file.json>'); process.exit(1); } if (!isAbsoluteUrl(BASE_URL)) { console.error(`BASE_URL is not absolute: ${BASE_URL}`); process.exit(1); } postMemory(key, value); } module.exports = postMemory;