( env: Record<string, string | undefined> = process.env, configDir = KILO_CONFIG_DIR, deps: KiloCliConfigDeps = defaultDeps )
| 42 | }; |
| 43 | |
| 44 | export function writeKiloCliConfig( |
| 45 | env: Record<string, string | undefined> = process.env, |
| 46 | configDir = KILO_CONFIG_DIR, |
| 47 | deps: KiloCliConfigDeps = defaultDeps |
| 48 | ): boolean { |
| 49 | // Gate on feature flag |
| 50 | if (env.KILOCLAW_KILO_CLI !== 'true') return false; |
| 51 | |
| 52 | const configPath = path.join(configDir, CONFIG_FILE); |
| 53 | const legacyConfigPath = path.join(configDir, LEGACY_CONFIG_FILE); |
| 54 | if (!env.KILOCODE_API_KEY) return false; |
| 55 | |
| 56 | const isFreshInstall = env.KILOCLAW_FRESH_INSTALL === 'true'; |
| 57 | |
| 58 | if (!deps.existsSync(configPath) && deps.existsSync(legacyConfigPath)) { |
| 59 | try { |
| 60 | deps.mkdirSync(configDir, { recursive: true }); |
| 61 | deps.writeFileSync(configPath, deps.readFileSync(legacyConfigPath, 'utf8'), { mode: 0o600 }); |
| 62 | console.log( |
| 63 | '[kilo-cli] Migrated legacy config from ' + legacyConfigPath + ' to ' + configPath |
| 64 | ); |
| 65 | } catch (err) { |
| 66 | console.error('[kilo-cli] Failed to migrate legacy config, skipping:', err); |
| 67 | } |
| 68 | } |
| 69 | |
| 70 | // Seed config on fresh install only. |
| 71 | // No provider block needed — the KiloAuthPlugin auto-registers the "kilo" |
| 72 | // provider when KILO_API_KEY is in the environment (set by bootstrap). |
| 73 | if (isFreshInstall && !deps.existsSync(configPath)) { |
| 74 | const config: Record<string, unknown> = { |
| 75 | $schema: 'https://app.kilo.ai/config.json', |
| 76 | permission: { edit: 'allow', bash: 'allow' }, |
| 77 | }; |
| 78 | if (env.KILOCODE_DEFAULT_MODEL) { |
| 79 | config.model = toKiloModelId(env.KILOCODE_DEFAULT_MODEL); |
| 80 | } |
| 81 | deps.mkdirSync(configDir, { recursive: true }); |
| 82 | deps.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 0o600 }); |
| 83 | console.log('[kilo-cli] Seeded config at ' + configPath); |
| 84 | } |
| 85 | |
| 86 | // Patch config on every boot (if it exists). |
| 87 | if (deps.existsSync(configPath)) { |
| 88 | try { |
| 89 | // JSON structure is open-ended (user may add arbitrary keys), so we use `any` |
| 90 | // rather than a strict schema. |
| 91 | // eslint-disable-next-line @typescript-eslint/no-explicit-any |
| 92 | const config: any = JSON.parse(deps.readFileSync(configPath, 'utf8')); |
| 93 | let dirty = false; |
| 94 | |
| 95 | // Remove any stale provider.kilo.options.baseURL from the config file. |
| 96 | // Setting baseURL in config JSON is broken (the Kilo CLI ignores it |
| 97 | // in certain code paths). The correct mechanism is the KILO_API_URL env |
| 98 | // var, which bootstrap sets from KILOCODE_API_BASE_URL. Early deployments |
| 99 | // may still have the broken field, so we scrub it on every boot. |
| 100 | if (config.provider?.kilo?.options?.baseURL) { |
| 101 | delete config.provider.kilo.options.baseURL; |
no test coverage detected