import { loadConfig } from '../config/index.js'; import type { Config } from '../config/index.js'; import { resolve } from 'path'; import { homedir } from 'os'; /** Get the config file path from env or default location. */ export function getConfigPath(): string { return process.env.FLYNN_CONFIG ?? resolve(homedir(), '.config/flynn/config.yaml'); } /** Get the data directory path. */ export function getDataDir(): string { return resolve(homedir(), '.local/share/flynn'); } /** Load config without throwing. Returns { config } or { error }. */ export function loadConfigSafe(configPath?: string): { config?: Config; error?: string } { const path = configPath ?? getConfigPath(); try { const config = loadConfig(path); return { config }; } catch (error) { const message = error instanceof Error ? error.message : String(error); return { error: `Failed to load config from ${path}: ${message}` }; } } /** Deep-clone config and replace sensitive fields with '***'. */ export function redactSecrets(config: Record): Record { const sensitiveKeys = ['bot_token', 'api_key', 'auth_token']; function redact(obj: unknown): unknown { if (obj === null || obj === undefined) return obj; if (Array.isArray(obj)) return obj.map(redact); if (typeof obj === 'object') { const result: Record = {}; for (const [key, value] of Object.entries(obj as Record)) { if (sensitiveKeys.includes(key) && typeof value === 'string') { result[key] = '***'; } else { result[key] = redact(value); } } return result; } return obj; } return redact(config) as Record; } /** Format output for terminal display. */ export function formatStatus( status: 'pass' | 'fail' | 'warn' | 'skip', label: string, detail?: string, ): string { const icons: Record = { pass: '\x1b[32m[PASS]\x1b[0m', fail: '\x1b[31m[FAIL]\x1b[0m', warn: '\x1b[33m[WARN]\x1b[0m', skip: '\x1b[2m[SKIP]\x1b[0m', }; const suffix = detail ? ` ${detail}` : ''; return `${icons[status]} ${label}${suffix}`; }