import path from "node:path"; import os from "node:os"; import { getHermesConfigPath } from "./hermesHome.ts"; import { generateClaudeConfig } from "./claude"; import { generateClineConfig } from "./cline"; import { generateCodexConfig } from "./codex"; import { generateContinueConfig } from "./continue"; import { generateHermesConfig } from "./hermes"; import { generateHermesAgentConfig, type HermesAgentConfigPayload } from "./hermes-agent"; import { generateKilocodeConfig } from "./kilocode"; import { generateOpencodeConfig } from "./opencode"; export interface GenerateOptions { baseUrl: string; apiKey: string; model?: string; } export interface GenerateResult { success: boolean; configPath: string; content?: string; error?: string; } export function validateBaseUrl(url: string): boolean { try { const u = new URL(url); return u.protocol === "http:" || u.protocol === "https:"; } catch { return false; } } function expandHome(p: string): string { const home = os.homedir(); return p.replace(/^~\//, home + "/"); } // Static paths that do not depend on runtime env vars can stay eagerly computed. const STATIC_TOOL_CONFIG_PATHS: Record = { claude: path.join(os.homedir(), ".claude", "settings.json"), codex: path.join(os.homedir(), ".codex", "config.yaml"), opencode: path.join(os.homedir(), ".config", "opencode", "opencode.json"), cline: path.join(os.homedir(), ".cline", "data", "globalState.json"), kilocode: path.join(os.homedir(), ".config", "kilocode", "settings.json"), continue: path.join(os.homedir(), ".continue", "config.yaml"), }; /** * Returns the config path for a given tool. * * Hermes entries are resolved lazily at call-time so `HERMES_HOME` is always * honoured (#3628). All other tools use the eagerly-computed static map. */ function getToolConfigPath(toolId: string): string { if (toolId === "hermes" || toolId === "hermes-agent") { return getHermesConfigPath(); } return STATIC_TOOL_CONFIG_PATHS[toolId] ?? ""; } type ConfigGenerator = (options: GenerateOptions) => string | Promise; const GENERATORS: Record = { claude: generateClaudeConfig, codex: generateCodexConfig, opencode: generateOpencodeConfig, cline: generateClineConfig, kilocode: generateKilocodeConfig, continue: generateContinueConfig, hermes: generateHermesConfig, "hermes-agent": generateHermesAgentConfig as any, // rich multi-role version }; export async function generateConfig( toolId: string, options: GenerateOptions ): Promise { if (!validateBaseUrl(options.baseUrl)) { return { success: false, configPath: "", error: "Invalid baseUrl: must be an absolute HTTP(S) URL", }; } if (!options.apiKey || options.apiKey.trim().length === 0) { return { success: false, configPath: "", error: "API key is required" }; } try { const generate = GENERATORS[toolId]; if (!generate) { return { success: false, configPath: "", error: `Unknown tool: ${toolId}` }; } const content = await generate(options); const configPath = getToolConfigPath(toolId); return { success: true, configPath, content }; } catch (err) { const msg = err instanceof Error ? err.message : String(err); return { success: false, configPath: "", error: `Generation failed: ${msg}` }; } } export async function generateAllConfigs(options: GenerateOptions): Promise { const toolIds = [ "claude", "codex", "opencode", "cline", "kilocode", "continue", "hermes", ] as const; const results = await Promise.allSettled(toolIds.map((id) => generateConfig(id, options))); return results.map((r) => r.status === "fulfilled" ? r.value : { success: false, configPath: "", error: r.reason?.message || "Unknown error" } ); }