/** * System config command */ import { existsSync, readFileSync } from 'node:fs'; import { dirname, join } from 'node:path'; import { eq } from 'drizzle-orm'; import { getDb } from '../../db/client'; import { systemConfig } from '../../db/schema'; import { askConfirm, withInterviewSession } from '../../services/bus-interview'; import type { SystemConfigSchema } from '../../services/system-config-schema-types'; import { validateKey, validateValue } from '../../services/system-config-validator'; import { getArg, validateRequiredArgs } from '../parser'; import type { CommandResult } from '../types'; /** * Load system config schema from JSON file */ function loadSchema(): SystemConfigSchema { // Try common locations (relative to this file's directory and cwd) const thisDir = dirname(new URL(import.meta.url).pathname); const candidates = [ join(thisDir, '..', '..', '..', 'schemas', 'system_config.json'), // Relative to src/cli/commands/ → apps/celilo/schemas/ './schemas/system_config.json', // From cwd (apps/celilo) join(process.cwd(), 'schemas', 'system_config.json'), ]; for (const candidate of candidates) { if (existsSync(candidate)) { try { return JSON.parse(readFileSync(candidate, 'utf-8')); } catch (error) { throw new Error( `Failed to parse system_config.json schema: ${error instanceof Error ? error.message : 'Invalid JSON'}`, ); } } } throw new Error('Could not find system_config.json schema file'); } /** * Handle system config set command * * Usage: celilo system config set * * @param args - Command arguments * @returns Command result */ export async function handleSystemConfigSet( args: string[], flags: Record = {}, ): Promise { // Validate arguments const error = validateRequiredArgs(args, 2); if (error) { return { success: false, error: `${error}\n\nUsage: celilo system config set `, }; } const key = getArg(args, 0); const value = getArg(args, 1); if (!key || !value) { return { success: false, error: 'Key and value are required', }; } // Load schema const schema = loadSchema(); // Validate key exists const keyValidation = validateKey(key, schema); if (!keyValidation.valid) { return { success: false, error: keyValidation.error }; } // Validate value const valueValidation = validateValue(key, value, schema.properties[key]); if (!valueValidation.valid) { return { success: false, error: valueValidation.error }; } // D5 of hook-jail-config-surface: turning jailing off fleet-wide is a security // posture change, so the write asks first. `auto` and `required` write // directly — `auto` is the status quo default and `required` tightens the // posture. `--force` skips the ask, matching the house flags.force convention // (backup-delete, machine-remove, service-remove, ...). if (key === 'hooks.jail_policy' && value === 'off' && !flags.force) { const confirmed = await withInterviewSession(() => askConfirm({ scope: 'hooks-jail-policy', key: 'set-off', message: 'Set hooks.jail_policy to off? Hooks will run unjailed fleet-wide and the doctor will warn.', defaultValue: false, }), ); if (!confirmed) { return { success: false, error: 'Cancelled by user' }; } } const db = getDb(); // Store value in database const existingConfig = db.select().from(systemConfig).where(eq(systemConfig.key, key)).get(); if (existingConfig) { // Update existing config db.update(systemConfig) .set({ value, updatedAt: new Date(), }) .where(eq(systemConfig.id, existingConfig.id)) .run(); } else { // Insert new config db.insert(systemConfig) .values({ key, value, }) .run(); } return { success: true, message: `Set system config: ${key} = ${value}`, }; } /** * Handle system config get command * * Usage: celilo system config get [key] * * @param args - Command arguments * @returns Command result */ export function handleSystemConfigGet(args: string[]): CommandResult { const key = getArg(args, 0); const db = getDb(); if (key) { // Get specific config value const config = db.select().from(systemConfig).where(eq(systemConfig.key, key)).get(); if (!config) { return { success: false, error: `System config key not found: ${key}`, }; } return { success: true, message: `${key} = ${config.value}`, data: { key, value: config.value }, }; } // Get all system config const configs = db.select().from(systemConfig).all(); if (configs.length === 0) { return { success: true, message: 'No system configuration set', }; } const lines = ['System configuration:', '']; for (const config of configs) { lines.push(`${config.key} = ${config.value}`); if (config.description) { lines.push(` ${config.description}`); } } return { success: true, message: lines.join('\n'), data: configs, }; }