UNPKG

cyrus-ai

Version:

AI-powered Linear issue automation using Claude

124 lines 4.72 kB
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { dirname, resolve } from "node:path"; import { migrateEdgeConfig } from "cyrus-core"; /** * Service responsible for configuration management * Handles loading, saving, and validation of edge configuration */ export class ConfigService { logger; configPath; constructor(cyrusHome, logger) { this.logger = logger; this.configPath = resolve(cyrusHome, "config.json"); } /** * Get the configuration file path */ getConfigPath() { return this.configPath; } /** * Load edge configuration from disk */ load() { let config = { repositories: [] }; if (existsSync(this.configPath)) { try { const content = readFileSync(this.configPath, "utf-8"); const raw = JSON.parse(content); // Migrate legacy per-repo tokens to workspace-keyed format config = migrateEdgeConfig(raw); } catch (e) { this.logger.error(`Failed to load edge config: ${e.message}`); } } // Strip promptTemplatePath from all repositories to ensure built-in template is used if (config.repositories) { config.repositories = config.repositories.map((repo) => { const { promptTemplatePath, ...repoWithoutTemplate } = repo; if (promptTemplatePath) { this.logger.info(`Ignoring custom prompt template for repository: ${repo.name} (using built-in template)`); } return repoWithoutTemplate; }); } // Run migrations on loaded config config = this.migrateConfig(config); return config; } /** * Run migrations on config to ensure it's up to date * Persists changes to disk if any migrations were applied */ migrateConfig(config) { let configModified = false; // Migration: Rename legacy global model fields to Claude-specific names // Keep old values but move them to the new keys and remove deprecated fields. if (config.defaultModel !== undefined) { if (!config.claudeDefaultModel) { config.claudeDefaultModel = config.defaultModel; this.logger.info(`[Migration] Moved "defaultModel" to "claudeDefaultModel"`); } delete config.defaultModel; configModified = true; } if (config.defaultFallbackModel !== undefined) { if (!config.claudeDefaultFallbackModel) { config.claudeDefaultFallbackModel = config.defaultFallbackModel; this.logger.info(`[Migration] Moved "defaultFallbackModel" to "claudeDefaultFallbackModel"`); } delete config .defaultFallbackModel; configModified = true; } // Migration: Add "Skill" to allowedTools arrays that don't have it // This enables Claude Skills functionality for existing configurations // See: https://code.claude.com/docs/en/skills // See: https://platform.claude.com/docs/en/agent-sdk/skills if (config.repositories) { for (const repo of config.repositories) { if (repo.allowedTools && Array.isArray(repo.allowedTools)) { if (!repo.allowedTools.includes("Skill")) { repo.allowedTools.push("Skill"); configModified = true; this.logger.info(`[Migration] Added "Skill" to allowedTools for repository: ${repo.name}`); } } } } // Persist changes if any migrations were applied if (configModified) { this.save(config); this.logger.info("[Migration] Configuration updated and saved to disk"); } return config; } /** * Save edge configuration to disk */ save(config) { const configDir = dirname(this.configPath); // Ensure the ~/.cyrus directory exists if (!existsSync(configDir)) { mkdirSync(configDir, { recursive: true }); } writeFileSync(this.configPath, JSON.stringify(config, null, 2)); } /** * Update a specific field in the configuration */ update(updater) { const config = this.load(); const updated = updater(config); this.save(updated); } /** * Check if configuration exists */ exists() { return existsSync(this.configPath); } } //# sourceMappingURL=ConfigService.js.map