create-quiver 0.15.0 → 0.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +31 -0
  3. package/README_FOR_AI.md +8 -3
  4. package/ROADMAP.md +5 -0
  5. package/docs/AI_CONTEXT.md.template +4 -0
  6. package/docs/AI_ONBOARDING_PROMPT.md.template +8 -0
  7. package/docs/CLI_UX_GUIDE.md +16 -1
  8. package/docs/COMMANDS.md.template +14 -4
  9. package/docs/TROUBLESHOOTING.md +70 -0
  10. package/docs/TROUBLESHOOTING.md.template +20 -0
  11. package/docs/getting-started/installation.md +86 -0
  12. package/docs/reference/commands.md +29 -1
  13. package/package.json +1 -1
  14. package/specs/quiver-v31-ai-model-catalog-agent-selection/EVIDENCE_REPORT.md +185 -0
  15. package/specs/quiver-v31-ai-model-catalog-agent-selection/EXECUTION_PLAN.md +85 -0
  16. package/specs/quiver-v31-ai-model-catalog-agent-selection/SPEC.md +337 -0
  17. package/specs/quiver-v31-ai-model-catalog-agent-selection/STATUS.md +30 -0
  18. package/specs/quiver-v31-ai-model-catalog-agent-selection/pr.md +98 -0
  19. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-00-spec-foundation/CLOSURE_BRIEF.md +30 -0
  20. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-00-spec-foundation/EXECUTION_BRIEF.md +51 -0
  21. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-00-spec-foundation/slice.json +62 -0
  22. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-01-model-catalog-alias-normalization/CLOSURE_BRIEF.md +32 -0
  23. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-01-model-catalog-alias-normalization/EXECUTION_BRIEF.md +53 -0
  24. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-01-model-catalog-alias-normalization/slice.json +72 -0
  25. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-02-interactive-agent-set-selectors/CLOSURE_BRIEF.md +36 -0
  26. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-02-interactive-agent-set-selectors/EXECUTION_BRIEF.md +56 -0
  27. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-02-interactive-agent-set-selectors/slice.json +78 -0
  28. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-03-agent-doctor-repair/CLOSURE_BRIEF.md +36 -0
  29. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-03-agent-doctor-repair/EXECUTION_BRIEF.md +57 -0
  30. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-03-agent-doctor-repair/slice.json +77 -0
  31. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-04-shared-preflight-provider-errors/CLOSURE_BRIEF.md +35 -0
  32. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-04-shared-preflight-provider-errors/EXECUTION_BRIEF.md +55 -0
  33. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-04-shared-preflight-provider-errors/slice.json +84 -0
  34. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-05-ai-models-list/CLOSURE_BRIEF.md +32 -0
  35. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-05-ai-models-list/EXECUTION_BRIEF.md +52 -0
  36. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-05-ai-models-list/slice.json +72 -0
  37. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-06-docs-templates-alignment/CLOSURE_BRIEF.md +33 -0
  38. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-06-docs-templates-alignment/EXECUTION_BRIEF.md +58 -0
  39. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-06-docs-templates-alignment/slice.json +84 -0
  40. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-07-tests-smokes-release-readiness/CLOSURE_BRIEF.md +32 -0
  41. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-07-tests-smokes-release-readiness/EXECUTION_BRIEF.md +59 -0
  42. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-07-tests-smokes-release-readiness/slice.json +94 -0
  43. package/specs/quiver-v32-npx-installation-guidance/EVIDENCE_REPORT.md +26 -0
  44. package/specs/quiver-v32-npx-installation-guidance/SPEC.md +55 -0
  45. package/specs/quiver-v32-npx-installation-guidance/STATUS.md +23 -0
  46. package/specs/quiver-v32-npx-installation-guidance/pr.md +72 -0
  47. package/specs/quiver-v32-npx-installation-guidance/slices/slice-00-installation-docs/CLOSURE_BRIEF.md +31 -0
  48. package/specs/quiver-v32-npx-installation-guidance/slices/slice-00-installation-docs/EXECUTION_BRIEF.md +56 -0
  49. package/specs/quiver-v32-npx-installation-guidance/slices/slice-00-installation-docs/slice.json +75 -0
  50. package/src/create-quiver/commands/ai.js +449 -34
  51. package/src/create-quiver/commands/flow.js +2 -2
  52. package/src/create-quiver/index.js +26 -10
  53. package/src/create-quiver/lib/agent-profiles.js +332 -4
  54. package/src/create-quiver/lib/ai/execution-plan.js +7 -2
  55. package/src/create-quiver/lib/ai/executor.js +9 -2
  56. package/src/create-quiver/lib/ai/model-catalog.js +333 -0
  57. package/src/create-quiver/lib/ai/providers.js +143 -4
  58. package/src/create-quiver/lib/cli/selectors.js +53 -0
@@ -13,10 +13,17 @@ const {
13
13
  const { buildContextPackMetadata, normalizeRole } = require('../lib/ai/context-packs');
14
14
  const { parseContextProposalOutput } = require('../lib/ai/context-proposal');
15
15
  const { openEditor } = require('../lib/cli/editor');
16
+ const { selectOption, promptText } = require('../lib/cli/selectors');
16
17
  const { createUx } = require('../lib/cli/ux');
17
18
  const { runExecuteSlice, runPromptSlice } = require('../lib/ai/executor');
18
19
  const { runExecutePlan } = require('../lib/ai/execution-plan');
19
20
  const { buildPrCreatePlan, formatPreflightReport, formatPrCreateReport, preflightGitHubPr, runGhPrCreate } = require('../lib/ai/github');
21
+ const {
22
+ MODEL_CATALOG_LAST_UPDATED,
23
+ MODEL_CATALOG_VERSION,
24
+ getKnownModelsForProvider,
25
+ listCatalogProviders,
26
+ } = require('../lib/ai/model-catalog');
20
27
  const { buildContextPreparationDrafts, buildPlannerOnboardingPrompt } = require('../lib/ai/onboarding-template');
21
28
  const {
22
29
  collectLifecycleExport,
@@ -43,6 +50,7 @@ const {
43
50
  validateTechnicalPlanSpecContract,
44
51
  } = require('../lib/ai/spec-generator');
45
52
  const { buildProviderInvocation, runProvider } = require('../lib/ai/providers');
53
+ const { preflightProvider } = require('../lib/ai/preflight');
46
54
  const {
47
55
  createAiRun,
48
56
  ensureAiRun,
@@ -55,9 +63,12 @@ const {
55
63
  } = require('../lib/ai/run-state');
56
64
  const {
57
65
  agentProfilesPath,
66
+ buildAgentProfileDoctorReport,
67
+ buildAgentProfileRepairPlan,
58
68
  buildAgentProfileState,
59
69
  getAgentProfile,
60
70
  getAgentProfileById,
71
+ getAgentProfilesForRole,
61
72
  listAgentProfiles,
62
73
  normalizeAgentProfileRole,
63
74
  resolveAgentProfileDisplayName,
@@ -175,6 +186,13 @@ function resolveRuntimeAgentProfile(repoRoot, role, options = {}, fallbackProvid
175
186
  };
176
187
  }
177
188
 
189
+ function runtimeModelExecutionOptions(runtimeProfile, options = {}) {
190
+ return {
191
+ model: runtimeProfile.model,
192
+ blockModelAlias: Boolean(runtimeProfile.profile && !String(options.model || '').trim()),
193
+ };
194
+ }
195
+
178
196
  function createCommandUx(options = {}) {
179
197
  if (options.ux) {
180
198
  return options.ux;
@@ -1247,7 +1265,7 @@ async function runOnboard(repoRoot, options = {}) {
1247
1265
  prompt,
1248
1266
  cwd: repoRoot,
1249
1267
  timeoutMs,
1250
- model: runtimeProfile.model,
1268
+ ...runtimeModelExecutionOptions(runtimeProfile, options),
1251
1269
  enforceModelSelection: false,
1252
1270
  });
1253
1271
  } catch (error) {
@@ -1311,7 +1329,7 @@ async function runOnboard(repoRoot, options = {}) {
1311
1329
  tempRoot: options.tempRoot,
1312
1330
  tempFileName: options.tempFileName,
1313
1331
  tempFilePrefix: options.tempFilePrefix,
1314
- model: runtimeProfile.model,
1332
+ ...runtimeModelExecutionOptions(runtimeProfile, options),
1315
1333
  enforceModelSelection: Boolean(runtimeProfile.model),
1316
1334
  }),
1317
1335
  });
@@ -1427,7 +1445,7 @@ async function runPrepareContextWithPlanner(repoRoot, options = {}) {
1427
1445
  prompt,
1428
1446
  cwd: repoRoot,
1429
1447
  timeoutMs,
1430
- model: runtimeProfile.model,
1448
+ ...runtimeModelExecutionOptions(runtimeProfile, options),
1431
1449
  enforceModelSelection: false,
1432
1450
  });
1433
1451
  } catch (error) {
@@ -1502,7 +1520,7 @@ async function runPrepareContextWithPlanner(repoRoot, options = {}) {
1502
1520
  tempRoot: options.tempRoot,
1503
1521
  tempFileName: options.tempFileName,
1504
1522
  tempFilePrefix: options.tempFilePrefix,
1505
- model: runtimeProfile.model,
1523
+ ...runtimeModelExecutionOptions(runtimeProfile, options),
1506
1524
  enforceModelSelection: Boolean(runtimeProfile.model),
1507
1525
  }),
1508
1526
  });
@@ -1681,7 +1699,7 @@ async function runPlan(repoRoot, options = {}) {
1681
1699
  prompt,
1682
1700
  cwd: repoRoot,
1683
1701
  timeoutMs,
1684
- model: runtimeProfile.model,
1702
+ ...runtimeModelExecutionOptions(runtimeProfile, options),
1685
1703
  enforceModelSelection: false,
1686
1704
  });
1687
1705
  } catch (error) {
@@ -1754,7 +1772,7 @@ async function runPlan(repoRoot, options = {}) {
1754
1772
  tempRoot: options.tempRoot,
1755
1773
  tempFileName: options.tempFileName,
1756
1774
  tempFilePrefix: options.tempFilePrefix,
1757
- model: runtimeProfile.model,
1775
+ ...runtimeModelExecutionOptions(runtimeProfile, options),
1758
1776
  enforceModelSelection: Boolean(runtimeProfile.model),
1759
1777
  }),
1760
1778
  });
@@ -1848,7 +1866,7 @@ async function runReviewPlan(repoRoot, options = {}) {
1848
1866
  prompt: built.prompt,
1849
1867
  cwd: repoRoot,
1850
1868
  timeoutMs,
1851
- model: runtimeProfile.model,
1869
+ ...runtimeModelExecutionOptions(runtimeProfile, options),
1852
1870
  enforceModelSelection: false,
1853
1871
  });
1854
1872
  } catch (error) {
@@ -1932,7 +1950,7 @@ async function runReviewPlan(repoRoot, options = {}) {
1932
1950
  tempRoot: options.tempRoot,
1933
1951
  tempFileName: options.tempFileName,
1934
1952
  tempFilePrefix: options.tempFilePrefix,
1935
- model: runtimeProfile.model,
1953
+ ...runtimeModelExecutionOptions(runtimeProfile, options),
1936
1954
  enforceModelSelection: Boolean(runtimeProfile.model),
1937
1955
  }),
1938
1956
  });
@@ -2013,7 +2031,7 @@ async function runRepairPlan(repoRoot, options = {}) {
2013
2031
  prompt: built.prompt,
2014
2032
  cwd: repoRoot,
2015
2033
  timeoutMs,
2016
- model: runtimeProfile.model,
2034
+ ...runtimeModelExecutionOptions(runtimeProfile, options),
2017
2035
  enforceModelSelection: false,
2018
2036
  });
2019
2037
  } catch (error) {
@@ -2082,7 +2100,7 @@ async function runRepairPlan(repoRoot, options = {}) {
2082
2100
  tempRoot: options.tempRoot,
2083
2101
  tempFileName: options.tempFileName,
2084
2102
  tempFilePrefix: options.tempFilePrefix,
2085
- model: runtimeProfile.model,
2103
+ ...runtimeModelExecutionOptions(runtimeProfile, options),
2086
2104
  enforceModelSelection: Boolean(runtimeProfile.model),
2087
2105
  }),
2088
2106
  });
@@ -2418,6 +2436,212 @@ function runLifecycleRun(repoRoot, options = {}) {
2418
2436
  };
2419
2437
  }
2420
2438
 
2439
+ function isInteractiveAgentPromptAvailable(options = {}) {
2440
+ const stdinIsTTY = options.stdinIsTTY ?? Boolean((options.input || process.stdin).isTTY);
2441
+ const stdoutIsTTY = options.stdoutIsTTY ?? Boolean((options.output || process.stdout).isTTY);
2442
+ const ci = String((options.env || process.env).CI || '').trim().toLowerCase();
2443
+ return stdinIsTTY && stdoutIsTTY && ci !== '1' && ci !== 'true';
2444
+ }
2445
+
2446
+ function providerInstallHint(repoRoot, providerId, options = {}) {
2447
+ const probe = options.preflightProvider || preflightProvider;
2448
+ try {
2449
+ probe(providerId, {
2450
+ cwd: repoRoot,
2451
+ probe: options.providerProbe,
2452
+ probeArgs: options.providerProbeArgs,
2453
+ });
2454
+ return 'installed';
2455
+ } catch (error) {
2456
+ if (error && error.code === 'MISSING_PROVIDER_CLI') {
2457
+ return 'not installed';
2458
+ }
2459
+ return 'not verified';
2460
+ }
2461
+ }
2462
+
2463
+ function buildAgentProviderChoices(repoRoot, options = {}) {
2464
+ return listCatalogProviders().map((provider) => {
2465
+ const status = providerInstallHint(repoRoot, provider.id, options);
2466
+ return {
2467
+ label: provider.displayName,
2468
+ value: provider.id,
2469
+ hint: `${provider.modelCount} models, ${status}`,
2470
+ raw: {
2471
+ ...provider,
2472
+ installStatus: status,
2473
+ },
2474
+ };
2475
+ });
2476
+ }
2477
+
2478
+ function buildAgentModelChoices(provider, role) {
2479
+ return getKnownModelsForProvider(provider, { role }).map((model) => {
2480
+ const recommended = model.recommendedFor.includes(role) ? 'recommended' : 'available';
2481
+ const tier = [model.costTier, model.qualityTier, model.stability].filter(Boolean).join('/');
2482
+ return {
2483
+ label: model.displayName,
2484
+ value: model.id,
2485
+ hint: [recommended, tier].filter(Boolean).join(', '),
2486
+ default: model.recommendedFor.includes(role),
2487
+ raw: model,
2488
+ };
2489
+ });
2490
+ }
2491
+
2492
+ async function resolveInteractiveAgentSetOptions(repoRoot, options = {}) {
2493
+ const role = normalizeAgentProfileRole(options.role);
2494
+ const hasProvider = Boolean(String(options.provider || '').trim());
2495
+ const hasModel = Boolean(String(options.model || '').trim());
2496
+ if (hasProvider && hasModel) {
2497
+ return {
2498
+ ...options,
2499
+ role,
2500
+ };
2501
+ }
2502
+
2503
+ const canPrompt = isInteractiveAgentPromptAvailable(options);
2504
+ const shouldPrompt = options.interactive === true || canPrompt;
2505
+ if (!shouldPrompt || !canPrompt) {
2506
+ throw new Error(formatActionableError({
2507
+ failure: `ai agent set ${role} requires --provider and --model when prompts are not available.`,
2508
+ impact: 'Quiver cannot safely guess the provider or technical model id for an agent profile.',
2509
+ fix: 'Pass both flags explicitly, or rerun from an interactive terminal with --interactive.',
2510
+ nextCommand: `npx create-quiver ai agent set ${role} --provider codex --model gpt-5.5`,
2511
+ }));
2512
+ }
2513
+
2514
+ const promptOptions = {
2515
+ env: options.env,
2516
+ error: options.error,
2517
+ input: options.input,
2518
+ interactive: true,
2519
+ noColor: options.noColor,
2520
+ output: options.output,
2521
+ prompts: options.prompts,
2522
+ promptSelect: options.promptSelect,
2523
+ promptText: options.promptText,
2524
+ stdinIsTTY: options.stdinIsTTY,
2525
+ stdoutIsTTY: options.stdoutIsTTY,
2526
+ stderrIsTTY: options.stderrIsTTY,
2527
+ write: options.write,
2528
+ };
2529
+
2530
+ const existingProfiles = getAgentProfilesForRole(repoRoot, role);
2531
+ let next = { ...options, role };
2532
+ if (existingProfiles.length > 0 && !options.id && !options.defaultProfile) {
2533
+ const ux = createCommandUx(promptOptions);
2534
+ ux.summary(existingProfiles.map((profile) => ({
2535
+ label: profile.id,
2536
+ value: `${profile.provider} ${profile.model || '(no model)'}${profile.default ? ' default' : ''}`,
2537
+ })), {
2538
+ title: `Existing ${role} profiles`,
2539
+ });
2540
+
2541
+ const action = await selectOption(`Ya existe al menos un perfil ${role}. ¿Qué querés hacer?`, [
2542
+ { label: 'Actualizar perfil actual', value: 'update-current', hint: 'Reutiliza el perfil default', default: true },
2543
+ { label: 'Crear perfil nuevo', value: 'create-new', hint: 'Permite guardar otro modelo o provider' },
2544
+ { label: 'Cambiar default', value: 'change-default', hint: 'Marca un perfil existente como default' },
2545
+ { label: 'Cancelar', value: 'cancel', hint: 'No escribe archivos' },
2546
+ ], {
2547
+ ...promptOptions,
2548
+ name: 'agent profile action',
2549
+ flag: '--id',
2550
+ });
2551
+
2552
+ if (action.value === 'cancel') {
2553
+ throw new Error(formatError('ai agent set canceled. No files were written.'));
2554
+ }
2555
+
2556
+ if (action.value === 'change-default') {
2557
+ const profile = await selectOption(`Elegí el perfil ${role} default`, existingProfiles.map((item) => ({
2558
+ label: resolveAgentProfileDisplayName(item),
2559
+ value: item.id,
2560
+ hint: `${item.provider} ${item.model || '(no model)'}`,
2561
+ default: item.default === true,
2562
+ raw: item,
2563
+ })), {
2564
+ ...promptOptions,
2565
+ name: 'agent profile default',
2566
+ flag: '--id',
2567
+ });
2568
+ return {
2569
+ ...next,
2570
+ id: profile.raw.id,
2571
+ provider: profile.raw.provider,
2572
+ model: profile.raw.model,
2573
+ displayName: profile.raw.displayName,
2574
+ label: profile.raw.label,
2575
+ context: profile.raw.context,
2576
+ defaultProfile: true,
2577
+ };
2578
+ }
2579
+
2580
+ if (action.value === 'update-current') {
2581
+ const current = existingProfiles.find((profile) => profile.default) || existingProfiles[0];
2582
+ next = {
2583
+ ...next,
2584
+ id: current.id,
2585
+ context: next.context || current.context,
2586
+ label: next.label || current.label,
2587
+ };
2588
+ }
2589
+
2590
+ if (action.value === 'create-new') {
2591
+ const id = await promptText(`ID para el nuevo perfil ${role}`, {
2592
+ ...promptOptions,
2593
+ name: 'agent profile id',
2594
+ flag: '--id',
2595
+ placeholder: `${role}-gpt-55`,
2596
+ });
2597
+ next.id = id;
2598
+ next.defaultProfile = options.defaultProfile === true;
2599
+ }
2600
+ }
2601
+
2602
+ if (!next.provider) {
2603
+ const selectedProvider = await selectOption(`Elegí un provider para ${role}`, buildAgentProviderChoices(repoRoot, options), {
2604
+ ...promptOptions,
2605
+ name: 'agent provider',
2606
+ flag: '--provider',
2607
+ });
2608
+ next.provider = selectedProvider.value;
2609
+ }
2610
+
2611
+ if (!next.model) {
2612
+ const selectedModel = await selectOption(`Elegí un modelo para ${role}`, buildAgentModelChoices(next.provider, role), {
2613
+ ...promptOptions,
2614
+ name: 'agent model',
2615
+ flag: '--model',
2616
+ });
2617
+ if (selectedModel.value === 'custom') {
2618
+ const model = await promptText(`Modelo custom para ${next.provider}`, {
2619
+ ...promptOptions,
2620
+ name: 'agent model',
2621
+ flag: '--model',
2622
+ placeholder: `${next.provider}-model-id`,
2623
+ });
2624
+ const displayName = await promptText('Nombre visible del modelo custom', {
2625
+ ...promptOptions,
2626
+ name: 'agent model display name',
2627
+ flag: '--display-name',
2628
+ initialValue: model,
2629
+ required: false,
2630
+ });
2631
+ next.model = model;
2632
+ next.displayName = next.displayName || displayName || model;
2633
+ } else {
2634
+ next.model = selectedModel.value;
2635
+ next.displayName = next.displayName || selectedModel.raw.displayName;
2636
+ }
2637
+ }
2638
+
2639
+ return {
2640
+ ...next,
2641
+ interactiveResolved: true,
2642
+ };
2643
+ }
2644
+
2421
2645
  function formatAgentProfile(profile) {
2422
2646
  const lines = [
2423
2647
  `ID: ${profile.id || 'default'}`,
@@ -2464,25 +2688,156 @@ function formatAgentProfileDryRun(repoRoot, result) {
2464
2688
  ].join('\n');
2465
2689
  }
2466
2690
 
2467
- function runAgent(repoRoot, options = {}) {
2691
+ function agentDoctorSymbol(status) {
2692
+ if (status === 'error') return 'x';
2693
+ if (status === 'warning') return '!';
2694
+ return 'OK';
2695
+ }
2696
+
2697
+ function formatAgentDoctorReport(report) {
2698
+ const lines = [
2699
+ 'Quiver Agent Doctor',
2700
+ '',
2701
+ 'Checks',
2702
+ ];
2703
+
2704
+ if (report.checks.length === 0) {
2705
+ lines.push(' ! No agent profiles configured');
2706
+ }
2707
+
2708
+ for (const check of report.checks) {
2709
+ const target = `${check.role}/${check.id}`;
2710
+ const model = check.model || '(no model)';
2711
+ const provider = check.provider || '(no provider)';
2712
+ const defaultText = check.default ? ' default' : '';
2713
+ lines.push(` ${agentDoctorSymbol(check.status)} ${target}: provider=${provider} model=${model}${defaultText}`);
2714
+ for (const finding of check.findings.filter((item) => item.severity !== 'info')) {
2715
+ lines.push(` - ${finding.severity}: ${finding.message}`);
2716
+ }
2717
+ }
2718
+
2719
+ lines.push('', 'Suggested fixes');
2720
+ if (report.suggestedFixes.length === 0) {
2721
+ lines.push(' OK No fixes suggested.');
2722
+ } else {
2723
+ for (const fix of report.suggestedFixes) {
2724
+ lines.push(` - ${fix}`);
2725
+ }
2726
+ }
2727
+ lines.push('');
2728
+ lines.push(`Summary: profiles=${report.summary.profiles} errors=${report.summary.errors} warnings=${report.summary.warnings} info=${report.summary.info}`);
2729
+ lines.push('');
2730
+ return `${lines.join('\n')}\n`;
2731
+ }
2732
+
2733
+ function formatAgentRepairPlan(plan) {
2734
+ const lines = [
2735
+ 'AI agent profile repair dry-run',
2736
+ '- Writes: none',
2737
+ '',
2738
+ 'Proposed changes',
2739
+ ];
2740
+
2741
+ if (plan.changes.length === 0) {
2742
+ lines.push(' OK No safe repairs found.');
2743
+ }
2744
+
2745
+ for (const change of plan.changes) {
2746
+ lines.push(` - ${change.role}/${change.profileId}: ${change.reason}`);
2747
+ lines.push(` Before: model=${change.before.model || '(not set)'} displayName=${change.before.displayName || '(not set)'}`);
2748
+ lines.push(` After: model=${change.after.model || '(not set)'} displayName=${change.after.displayName || '(not set)'}`);
2749
+ }
2750
+
2751
+ lines.push('');
2752
+ return `${lines.join('\n')}\n`;
2753
+ }
2754
+
2755
+ function buildModelsListReport(options = {}) {
2756
+ const providerFilter = String(options.provider || '').trim().toLowerCase();
2757
+ const providers = providerFilter
2758
+ ? [listCatalogProviders().find((provider) => provider.id === providerFilter)].filter(Boolean)
2759
+ : listCatalogProviders();
2760
+
2761
+ if (providerFilter && providers.length === 0) {
2762
+ throw new Error(formatError(`unsupported provider filter '${options.provider}'. Supported providers: ${listCatalogProviders().map((provider) => provider.id).join(', ')}.`));
2763
+ }
2764
+
2765
+ return {
2766
+ catalogVersion: MODEL_CATALOG_VERSION,
2767
+ lastUpdated: MODEL_CATALOG_LAST_UPDATED,
2768
+ note: 'Models are known by Quiver. This does not guarantee provider account access.',
2769
+ providers: providers.map((provider) => ({
2770
+ id: provider.id,
2771
+ displayName: provider.displayName,
2772
+ models: getKnownModelsForProvider(provider.id).map((model) => ({
2773
+ id: model.id,
2774
+ displayName: model.displayName,
2775
+ recommendedFor: model.recommendedFor,
2776
+ costTier: model.costTier,
2777
+ qualityTier: model.qualityTier,
2778
+ stability: model.stability,
2779
+ custom: model.custom === true,
2780
+ })),
2781
+ })),
2782
+ };
2783
+ }
2784
+
2785
+ function formatModelsListReport(report) {
2786
+ const lines = [
2787
+ 'AI models known by Quiver',
2788
+ `Catalog version: ${report.catalogVersion}`,
2789
+ `Last updated: ${report.lastUpdated}`,
2790
+ 'Note: Models are known by Quiver; provider account access is not guaranteed.',
2791
+ '',
2792
+ ];
2793
+
2794
+ for (const provider of report.providers) {
2795
+ lines.push(`${provider.displayName} (${provider.id})`);
2796
+ for (const model of provider.models) {
2797
+ const roles = model.recommendedFor.length > 0 ? model.recommendedFor.join(', ') : 'custom/manual';
2798
+ lines.push(` - ${model.id} (${model.displayName})`);
2799
+ lines.push(` roles: ${roles}`);
2800
+ lines.push(` cost=${model.costTier} quality=${model.qualityTier} stability=${model.stability}`);
2801
+ }
2802
+ lines.push('');
2803
+ }
2804
+
2805
+ return `${lines.join('\n')}\n`;
2806
+ }
2807
+
2808
+ function runModelsList(options = {}) {
2809
+ const report = buildModelsListReport({
2810
+ provider: options.provider,
2811
+ });
2812
+ if (options.json) {
2813
+ process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
2814
+ } else {
2815
+ process.stdout.write(formatModelsListReport(report));
2816
+ }
2817
+ return {
2818
+ task: 'models',
2819
+ command: 'list',
2820
+ report,
2821
+ };
2822
+ }
2823
+
2824
+ async function runAgent(repoRoot, options = {}) {
2468
2825
  const command = String(options.command || '').trim().toLowerCase();
2469
2826
 
2470
2827
  if (command === 'set') {
2471
2828
  if (!options.role) {
2472
- throw new Error(formatError('missing agent role. Use: npx create-quiver ai agent set <planner|executor|reviewer|doctor> --provider <provider>'));
2473
- }
2474
- if (!options.provider) {
2475
- throw new Error(formatError('ai agent set requires --provider. Supported providers: codex, claude, gemini.'));
2829
+ throw new Error(formatError('missing agent role. Use: npx create-quiver ai agent set <planner|executor|reviewer|doctor> --provider <provider> --model <model>'));
2476
2830
  }
2831
+ const profileOptions = await resolveInteractiveAgentSetOptions(repoRoot, options);
2477
2832
  if (options.dryRun) {
2478
- const preview = buildAgentProfileState(repoRoot, options.role, {
2479
- context: options.context,
2480
- default: options.defaultProfile,
2481
- displayName: options.displayName,
2482
- id: options.id,
2483
- label: options.label,
2484
- model: options.model,
2485
- provider: options.provider,
2833
+ const preview = buildAgentProfileState(repoRoot, profileOptions.role, {
2834
+ context: profileOptions.context,
2835
+ default: profileOptions.defaultProfile,
2836
+ displayName: profileOptions.displayName,
2837
+ id: profileOptions.id,
2838
+ label: profileOptions.label,
2839
+ model: profileOptions.model,
2840
+ provider: profileOptions.provider,
2486
2841
  });
2487
2842
  process.stdout.write(formatAgentProfileDryRun(repoRoot, preview));
2488
2843
  return {
@@ -2493,14 +2848,35 @@ function runAgent(repoRoot, options = {}) {
2493
2848
  filePath: path.relative(repoRoot, preview.filePath).split(path.sep).join('/'),
2494
2849
  };
2495
2850
  }
2496
- const result = setAgentProfile(repoRoot, options.role, {
2497
- context: options.context,
2498
- default: options.defaultProfile,
2499
- displayName: options.displayName,
2500
- id: options.id,
2501
- label: options.label,
2502
- model: options.model,
2503
- provider: options.provider,
2851
+ if (profileOptions.interactiveResolved === true) {
2852
+ const preview = buildAgentProfileState(repoRoot, profileOptions.role, {
2853
+ context: profileOptions.context,
2854
+ default: profileOptions.defaultProfile,
2855
+ displayName: profileOptions.displayName,
2856
+ id: profileOptions.id,
2857
+ label: profileOptions.label,
2858
+ model: profileOptions.model,
2859
+ provider: profileOptions.provider,
2860
+ });
2861
+ const ux = createCommandUx(profileOptions);
2862
+ ux.summary([
2863
+ { label: 'Role', value: preview.profile.role },
2864
+ { label: 'Provider', value: preview.profile.provider },
2865
+ { label: 'Model', value: preview.profile.model || '(not set)' },
2866
+ { label: 'Display name', value: resolveAgentProfileDisplayName(preview.profile) || '(not set)' },
2867
+ { label: 'Default', value: preview.profile.default === true ? 'yes' : 'no' },
2868
+ ], {
2869
+ title: 'Profile to save',
2870
+ });
2871
+ }
2872
+ const result = setAgentProfile(repoRoot, profileOptions.role, {
2873
+ context: profileOptions.context,
2874
+ default: profileOptions.defaultProfile,
2875
+ displayName: profileOptions.displayName,
2876
+ id: profileOptions.id,
2877
+ label: profileOptions.label,
2878
+ model: profileOptions.model,
2879
+ provider: profileOptions.provider,
2504
2880
  });
2505
2881
  process.stdout.write('AI agent profile saved\n');
2506
2882
  process.stdout.write(formatAgentProfile(result.profile));
@@ -2526,8 +2902,8 @@ function runAgent(repoRoot, options = {}) {
2526
2902
  ? `agent profile '${options.role}/${options.id}' is not configured.`
2527
2903
  : `agent profile '${options.role}' is not configured.`,
2528
2904
  impact: 'Quiver will fall back to default provider behavior and may use the wrong model/cost profile.',
2529
- fix: `Configure the ${options.role} profile with a supported provider and optional model label.`,
2530
- nextCommand: `npx create-quiver ai agent set ${options.role} --provider <provider> --model <label>`,
2905
+ fix: `Configure the ${options.role} profile with a supported provider and technical model id.`,
2906
+ nextCommand: `npx create-quiver ai agent set ${options.role} --provider <provider> --model <model-id>`,
2531
2907
  }));
2532
2908
  }
2533
2909
  process.stdout.write(formatAgentProfile(profile));
@@ -2549,7 +2925,44 @@ function runAgent(repoRoot, options = {}) {
2549
2925
  };
2550
2926
  }
2551
2927
 
2552
- throw new Error(formatError(`unsupported ai agent subcommand: ${command}. Supported tasks: set, list, show`));
2928
+ if (command === 'doctor') {
2929
+ const report = buildAgentProfileDoctorReport(repoRoot);
2930
+ if (options.json) {
2931
+ process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
2932
+ } else {
2933
+ process.stdout.write(formatAgentDoctorReport(report));
2934
+ }
2935
+ if (report.summary.errors > 0) {
2936
+ process.exitCode = 1;
2937
+ }
2938
+ return {
2939
+ task: 'agent',
2940
+ command,
2941
+ report,
2942
+ };
2943
+ }
2944
+
2945
+ if (command === 'repair') {
2946
+ if (options.dryRun !== true) {
2947
+ throw new Error(formatError('ai agent repair only supports --dry-run for now. No files were written.'));
2948
+ }
2949
+ const plan = buildAgentProfileRepairPlan(repoRoot, {
2950
+ includeState: options.json === true,
2951
+ });
2952
+ if (options.json) {
2953
+ process.stdout.write(`${JSON.stringify(plan, null, 2)}\n`);
2954
+ } else {
2955
+ process.stdout.write(formatAgentRepairPlan(plan));
2956
+ }
2957
+ return {
2958
+ task: 'agent',
2959
+ command,
2960
+ dryRun: true,
2961
+ plan,
2962
+ };
2963
+ }
2964
+
2965
+ throw new Error(formatError(`unsupported ai agent subcommand: ${command}. Supported tasks: set, list, show, doctor, repair`));
2553
2966
  }
2554
2967
 
2555
2968
  async function runGitHubTask(repoRoot, options = {}, mode = 'pr') {
@@ -2718,6 +3131,7 @@ module.exports = {
2718
3131
  formatSpecDryRunReport,
2719
3132
  normalizeTimeout,
2720
3133
  readTextFile,
3134
+ resolveInteractiveAgentSetOptions,
2721
3135
  runAgent,
2722
3136
  runActiveSlice,
2723
3137
  runDoctor,
@@ -2726,6 +3140,7 @@ module.exports = {
2726
3140
  runLifecycleResume,
2727
3141
  runLifecycleRun,
2728
3142
  runLifecycleStatus,
3143
+ runModelsList,
2729
3144
  runExport,
2730
3145
  runInspect,
2731
3146
  runPromptSlice,
@@ -321,10 +321,10 @@ function detectFlowState(projectRoot) {
321
321
  stage: 'agent-profiles-needed',
322
322
  label: 'agent profiles need setup',
323
323
  blockers: [`Missing ${agents.missingCore.join(' and ')} agent profile${agents.missingCore.length > 1 ? 's' : ''}.`],
324
- nextCommand: `npx create-quiver ai agent set ${role} --provider codex --model "<model-label>"`,
324
+ nextCommand: `npx create-quiver ai agent set ${role} --provider codex --model gpt-5.5`,
325
325
  suggestedCommands: [
326
326
  'npx create-quiver ai agent list',
327
- `npx create-quiver ai agent set ${role} --provider codex --model "<model-label>"`,
327
+ `npx create-quiver ai agent set ${role} --provider codex --model gpt-5.5`,
328
328
  ],
329
329
  facts,
330
330
  });