@every-env/cli
Version:
Multi-agent orchestrator for AI-powered development workflows
152 lines • 4.88 kB
JavaScript
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