UNPKG

@every-env/cli

Version:

Multi-agent orchestrator for AI-powered development workflows

152 lines 4.88 kB
import { promises as fs } from "fs"; import { join, dirname } from "path"; import { RuntimeConfigSchema, DEFAULT_RUNTIME_CONFIG } from "../types/config.js"; /** * Read runtime config from file if it exists */ export async function readRuntimeConfigIfExists(configPath) { try { const content = await fs.readFile(configPath, "utf-8"); const parsed = JSON.parse(content); return RuntimeConfigSchema.parse(parsed); } catch (error) { if (error.code === "ENOENT") { return null; } throw new Error(`Failed to read runtime config: ${error instanceof Error ? error.message : String(error)}`); } } /** * Validate runtime config against schema */ export function validateRuntimeConfig(config) { try { return RuntimeConfigSchema.parse(config); } catch (error) { throw new Error(`Invalid runtime config: ${error instanceof Error ? error.message : String(error)}`); } } /** * Deep merge runtime configs, preserving unknown keys and following merge rules */ export function deepMergeRuntimeConfig(existing, updates) { if (!existing) { return { ...DEFAULT_RUNTIME_CONFIG, ...updates, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }; } // Preserve createdAt, update updatedAt const merged = { ...existing, ...updates, createdAt: existing.createdAt, // Always preserve original creation time updatedAt: new Date().toISOString(), }; // Deep merge agents while preserving unknown agent keys if (updates.agents) { merged.agents = { ...existing.agents, ...updates.agents, }; // For each known agent, do field-wise merge to avoid overwriting unspecified fields for (const agentName of ["claude", "copy-clipboard", "amp", "codex"]) { if (existing.agents?.[agentName] && updates.agents[agentName]) { merged.agents[agentName] = { ...existing.agents[agentName], ...updates.agents[agentName], }; } } } return merged; } /** * Write runtime config atomically with deterministic formatting */ export async function writeRuntimeConfigAtomic(configPath, config) { // Ensure directory exists const configDir = dirname(configPath); await fs.mkdir(configDir, { recursive: true }); // Sort keys deterministically for consistent output const sortedConfig = sortObjectKeys(config); // Pretty print with 2 spaces const content = JSON.stringify(sortedConfig, null, 2) + "\n"; // Atomic write: write to temp file then rename const tempPath = `${configPath}.tmp`; try { await fs.writeFile(tempPath, content, "utf-8"); await fs.rename(tempPath, configPath); } catch (error) { // Clean up temp file if rename fails try { await fs.unlink(tempPath); } catch { // Ignore cleanup errors } throw error; } } /** * Sort object keys recursively for deterministic output */ function sortObjectKeys(obj) { if (obj === null || typeof obj !== "object" || Array.isArray(obj)) { return obj; } const record = obj; const sorted = {}; const keys = Object.keys(record).sort(); for (const key of keys) { sorted[key] = sortObjectKeys(record[key]); } return sorted; } /** * Validate environment non-blocking (check CLI availability and env vars) */ export async function validateEnvironmentNonBlocking(config) { const warnings = []; for (const [agentName, raw] of Object.entries(config.agents)) { const agentConfig = raw; // Check CLI availability if (typeof agentConfig.cli === "string") { const cli = agentConfig.cli; const isAvailable = await checkCommandAvailable(cli); if (!isAvailable) { warnings.push(`CLI command '${cli}' for agent '${agentName}' not found in PATH`); } } } return warnings; } /** * Check if a command is available in PATH */ async function checkCommandAvailable(command) { try { const { exec } = await import("child_process"); const { promisify } = await import("util"); const execAsync = promisify(exec); const platform = process.platform; const whichCommand = platform === "win32" ? "where" : "which"; await execAsync(`${whichCommand} ${command}`); return true; } catch { return false; } } /** * Get default runtime config path */ export function getDefaultRuntimeConfigPath(cwd = process.cwd()) { return join(cwd, ".every-env", "config.json"); } //# sourceMappingURL=runtime-config.js.map