Write the assembled config to disk, merging with existing values.
(picks: {
primaryModel: string;
provider: string;
primaryDetected?: DetectedModel;
subagentMode: 'off' | 'sequential' | 'parallel';
subagentRole?: { provider: string; model: string };
subagentDetected?: DetectedModel;
visionRole?: { provider: string; model: string };
visionDetected?: DetectedModel;
anthropicCaching: boolean;
autoSnapshot: boolean;
hardware: HardwareProfile;
})
| 577 | |
| 578 | /** Write the assembled config to disk, merging with existing values. */ |
| 579 | async function writeConfig(picks: { |
| 580 | primaryModel: string; |
| 581 | provider: string; |
| 582 | primaryDetected?: DetectedModel; |
| 583 | subagentMode: 'off' | 'sequential' | 'parallel'; |
| 584 | subagentRole?: { provider: string; model: string }; |
| 585 | subagentDetected?: DetectedModel; |
| 586 | visionRole?: { provider: string; model: string }; |
| 587 | visionDetected?: DetectedModel; |
| 588 | anthropicCaching: boolean; |
| 589 | autoSnapshot: boolean; |
| 590 | hardware: HardwareProfile; |
| 591 | }): Promise<void> { |
| 592 | await fs.mkdir(QODEX_HOME, { recursive: true }); |
| 593 | |
| 594 | await withLock(QODEX_CONFIG_FILE + '.lock', async () => { |
| 595 | // Try to load existing config and merge; if missing, start from DEFAULT_CONFIG. |
| 596 | let existing: QodexConfig; |
| 597 | try { |
| 598 | existing = await loadConfig(process.cwd()); |
| 599 | } catch (err: any) { |
| 600 | if (err?.code === 'ENOENT') { |
| 601 | // No config yet — fresh start. |
| 602 | existing = JSON.parse(JSON.stringify(DEFAULT_CONFIG)); |
| 603 | } else { |
| 604 | // A real parse/IO error (corrupt YAML, permission denied, etc.). Do NOT |
| 605 | // silently reset over the user's config. Back the existing file up to a |
| 606 | // timestamped copy first, warn loudly, then continue from defaults so the |
| 607 | // original data is preserved on disk and recoverable. |
| 608 | const backup = `${QODEX_CONFIG_FILE}.bak-${new Date().toISOString().replace(/[:.]/g, '-')}`; |
| 609 | let backedUp = false; |
| 610 | try { |
| 611 | await fs.copyFile(QODEX_CONFIG_FILE, backup); |
| 612 | backedUp = true; |
| 613 | } catch { |
| 614 | // If even the backup fails, fall through to the abort below. |
| 615 | } |
| 616 | if (!backedUp) { |
| 617 | throw new Error( |
| 618 | `Could not read existing config at ${QODEX_CONFIG_FILE} (${err?.message ?? err}), ` + |
| 619 | `and could not back it up before overwriting. Aborting to avoid losing your settings. ` + |
| 620 | `Fix or move ${QODEX_CONFIG_FILE} aside, then re-run \`qx setup\`.`, |
| 621 | ); |
| 622 | } |
| 623 | console.error( |
| 624 | `⚠ Could not read existing config at ${QODEX_CONFIG_FILE} (${err?.message ?? err}).\n` + |
| 625 | ` Your previous config was backed up to ${backup} before continuing from defaults.`, |
| 626 | ); |
| 627 | existing = JSON.parse(JSON.stringify(DEFAULT_CONFIG)); |
| 628 | } |
| 629 | } |
| 630 | |
| 631 | const updated: QodexConfig = { |
| 632 | ...existing, |
| 633 | defaults: { |
| 634 | ...existing.defaults, |
| 635 | provider: picks.provider, |
| 636 | model: picks.primaryModel, |
no test coverage detected