import fs from 'node:fs'; import path from 'node:path'; export const MEMIND_RUNTIME_PROFILES = new Set(['local', 'split-service', 'production']); /** * Load Memind env files in standard order. Later files only fill unset keys. */ export function loadMemindEnvFiles(rootDir, env = process.env) { const root = path.resolve(rootDir); for (const relativePath of ['../../.env.local', '.env']) { loadEnvFile(path.join(root, relativePath), env); } } function loadEnvFile(filePath, env = process.env) { if (!fs.existsSync(filePath)) return; for (const line of fs.readFileSync(filePath, 'utf8').split('\n')) { const trimmed = line.trim(); if (!trimmed || trimmed.startsWith('#')) continue; const separator = trimmed.indexOf('='); if (separator < 0) continue; const key = trimmed.slice(0, separator).trim(); const value = trimmed.slice(separator + 1).trim(); if (!env[key]) env[key] = value; } } function expandEnvReference(value, env = process.env) { const raw = String(value ?? '').trim(); const match = raw.match(/^\$\{([A-Z0-9_]+)\}$/); if (!match) return raw; return String(env[match[1]] ?? '').trim() || raw; } export function resolveMemindRuntimeProfile(env = process.env) { const explicit = String(env.MEMIND_RUNTIME_PROFILE ?? '').trim().toLowerCase(); if (explicit) { if (!MEMIND_RUNTIME_PROFILES.has(explicit)) { throw new Error( `Unsupported MEMIND_RUNTIME_PROFILE "${explicit}". Expected: ${[...MEMIND_RUNTIME_PROFILES].join(', ')}`, ); } return explicit; } if (String(env.NODE_ENV ?? '').trim().toLowerCase() === 'production') { return 'production'; } return 'local'; } /** * Apply environment-specific defaults after .env files are loaded. * * - local: monolith MindSpace adapter, repo-local storage (pnpm dev default) * - split-service: remote adapter against standalone MindSpace on 8082 * - production: no overrides; 103 .env is source of truth */ export function applyMemindRuntimeProfile({ rootDir = process.cwd(), env = process.env, profile, } = {}) { const resolvedProfile = profile ?? resolveMemindRuntimeProfile(env); env.MEMIND_RUNTIME_PROFILE = resolvedProfile; if (resolvedProfile === 'local') { env.MINDSPACE_SERVER_ADAPTER = 'local'; if (!env.MINDSPACE_STORAGE_ROOT) { env.MINDSPACE_STORAGE_ROOT = path.join(path.resolve(rootDir), 'data', 'mindspace'); } return { profile: resolvedProfile, mindspaceAdapter: 'local', storageRoot: env.MINDSPACE_STORAGE_ROOT, }; } if (resolvedProfile === 'split-service') { env.MINDSPACE_SERVER_ADAPTER = 'remote'; env.MINDSPACE_REMOTE_BASE_URL = String( env.MINDSPACE_REMOTE_BASE_URL ?? 'http://127.0.0.1:8082', ).trim(); const expandedToken = expandEnvReference(env.MINDSPACE_REMOTE_AUTH_TOKEN, env); if (expandedToken) { env.MINDSPACE_REMOTE_AUTH_TOKEN = expandedToken; } else if (env.TKMIND_SERVER__SECRET_KEY) { env.MINDSPACE_REMOTE_AUTH_TOKEN = String(env.TKMIND_SERVER__SECRET_KEY).trim(); } if (!env.MINDSPACE_STORAGE_ROOT) { env.MINDSPACE_STORAGE_ROOT = '/Users/john/MindSpace/data/mindspace'; } return { profile: resolvedProfile, mindspaceAdapter: 'remote', remoteBaseUrl: env.MINDSPACE_REMOTE_BASE_URL, storageRoot: env.MINDSPACE_STORAGE_ROOT, }; } return { profile: resolvedProfile, mindspaceAdapter: String(env.MINDSPACE_SERVER_ADAPTER ?? 'local').trim().toLowerCase() || 'local', storageRoot: env.MINDSPACE_STORAGE_ROOT ?? null, }; } export function describeMemindRuntimeProfile(env = process.env) { const profile = resolveMemindRuntimeProfile(env); const adapter = String(env.MINDSPACE_SERVER_ADAPTER ?? 'local').trim().toLowerCase() || 'local'; const parts = [`MEMIND_RUNTIME_PROFILE=${profile}`, `MINDSPACE_SERVER_ADAPTER=${adapter}`]; if (adapter === 'remote') { parts.push(`MINDSPACE_REMOTE_BASE_URL=${env.MINDSPACE_REMOTE_BASE_URL ?? '(unset)'}`); } if (env.MINDSPACE_STORAGE_ROOT) { parts.push(`MINDSPACE_STORAGE_ROOT=${env.MINDSPACE_STORAGE_ROOT}`); } return parts.join(', '); }