UNPKG

echonodesync

Version:

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

119 lines (108 loc) 3.69 kB
#!/usr/bin/env node // 🧠🌸 EchoNodeSync: scanKeys.js // Scans memory keys via /api/scan, supporting pattern/regex/limit, and groups by category. const fetch = require('node-fetch'); const path = require('path'); const os = require('os'); const fs = require('fs'); function stripQuotes(str) { if (!str) return str; return str.replace(/^['"]+|['"]+$/g, '').trim(); } function resolveApiUrl() { if (process.env.ECHONODESYNC_API_URL) return stripQuotes(process.env.ECHONODESYNC_API_URL); 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]); } } } return 'http://localhost:3000/api/memory'; } function resolveSecret() { if (process.env.ECHONODESYNC_SECRET) return stripQuotes(process.env.ECHONODESYNC_SECRET); 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().replace(/\/+$/, ''); const SECRET = resolveSecret(); function parseArgs(argv) { const args = { pattern: null, regex: null, limit: null, plain: false }; for (let i = 0; i < argv.length; ++i) { if (argv[i] === '--pattern') args.pattern = argv[++i]; else if (argv[i] === '--regex') args.regex = argv[++i]; else if (argv[i] === '--limit') args.limit = parseInt(argv[++i], 10); else if (argv[i] === '-S') args.plain = true; } return args; } function groupByCategory(keys) { const groups = {}; for (const key of keys) { const [category] = key.split(':'); if (!groups[category]) groups[category] = []; groups[category].push(key); } return groups; } async function scanKeys({ pattern, regex, limit, plain }) { const url = new URL(BASE_URL.replace(/\/api\/memory$/, '/api/scan')); if (pattern) url.searchParams.append('pattern', pattern); if (regex) url.searchParams.append('regex', regex); if (limit) url.searchParams.append('limit', limit); const headers = {}; if (SECRET) headers['Authorization'] = `Bearer ${SECRET}`; try { const res = await fetch(url.toString(), { headers }); if (!res.ok) throw new Error(`HTTP ${res.status}`); const data = await res.json(); if (!data.keys || !Array.isArray(data.keys)) { console.error('No keys found or invalid response.'); process.exit(1); } if (plain) { for (const key of data.keys) { console.log(key); } } else { const grouped = groupByCategory(data.keys); for (const [cat, keys] of Object.entries(grouped)) { console.log(`\n[${cat}]`); for (const key of keys) { console.log(` ${key}`); } } } } catch (err) { console.error('Failed to scan keys:', err.message); process.exit(1); } } if (require.main === module) { const args = parseArgs(process.argv.slice(2)); if (!args.pattern && !args.regex) { console.error('Usage: scan-keys --pattern <pattern> [--limit N] [-S]\n or: scan-keys --regex <regex> [--limit N] [-S]'); process.exit(1); } scanKeys(args); } module.exports = scanKeys;