import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'; import { homedir } from 'node:os'; import { dirname, resolve } from 'node:path'; import { type Config, ConfigSchema } from './types.ts'; const DEFAULT_CONFIG_PATH = resolve(homedir(), '.nanobot', 'config.json'); export function getConfigPath(override?: string): string { return override ?? process.env['NANOBOT_CONFIG'] ?? DEFAULT_CONFIG_PATH; } export function loadConfig(configPath?: string): Config { const path = getConfigPath(configPath); if (!existsSync(path)) { return ConfigSchema.parse({}); } const raw = readFileSync(path, 'utf8'); let json: unknown; try { json = JSON.parse(raw); } catch { console.error(`Failed to parse config at ${path}`); return ConfigSchema.parse({}); } // Apply NANOBOT_ env var overrides before validation const merged = applyEnvOverrides(json as Record); return ConfigSchema.parse(merged); } export function saveConfig(config: Config, configPath?: string): void { const path = getConfigPath(configPath); mkdirSync(dirname(path), { recursive: true }); writeFileSync(path, JSON.stringify(config, null, 2), 'utf8'); } /** Resolve `~` in workspace path to the real home directory. */ export function resolveWorkspacePath(raw: string): string { if (raw.startsWith('~/') || raw === '~') { return resolve(homedir(), raw.slice(2)); } return resolve(raw); } function applyEnvOverrides(json: Record): Record { const out = structuredClone(json); const model = process.env['NANOBOT_MODEL']; if (model) { const agent = (out['agent'] as Record | undefined) ?? {}; agent['model'] = model; out['agent'] = agent; } const workspace = process.env['NANOBOT_WORKSPACE']; if (workspace) { const agent = (out['agent'] as Record | undefined) ?? {}; agent['workspacePath'] = workspace; out['agent'] = agent; } return out; }