create-quiver 0.14.1 → 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 (94) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +35 -0
  3. package/README_FOR_AI.md +15 -4
  4. package/ROADMAP.md +13 -2
  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 +79 -4
  8. package/docs/COMMANDS.md.template +21 -5
  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 +33 -1
  13. package/package.json +1 -1
  14. package/specs/quiver-v30-interactive-cli-ux-agent-selection/EVIDENCE_REPORT.md +213 -0
  15. package/specs/quiver-v30-interactive-cli-ux-agent-selection/EXECUTION_PLAN.md +85 -0
  16. package/specs/quiver-v30-interactive-cli-ux-agent-selection/SPEC.md +213 -0
  17. package/specs/quiver-v30-interactive-cli-ux-agent-selection/STATUS.md +31 -0
  18. package/specs/quiver-v30-interactive-cli-ux-agent-selection/pr.md +103 -0
  19. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-00-spec-foundation/CLOSURE_BRIEF.md +33 -0
  20. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-00-spec-foundation/EXECUTION_BRIEF.md +56 -0
  21. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-00-spec-foundation/slice.json +71 -0
  22. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-01-cli-ux-runtime-progress-engine/CLOSURE_BRIEF.md +31 -0
  23. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-01-cli-ux-runtime-progress-engine/EXECUTION_BRIEF.md +54 -0
  24. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-01-cli-ux-runtime-progress-engine/slice.json +69 -0
  25. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-02-agent-profile-selection-selectors/CLOSURE_BRIEF.md +33 -0
  26. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-02-agent-profile-selection-selectors/EXECUTION_BRIEF.md +56 -0
  27. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-02-agent-profile-selection-selectors/slice.json +81 -0
  28. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-03-provider-model-selection-contract/CLOSURE_BRIEF.md +32 -0
  29. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-03-provider-model-selection-contract/EXECUTION_BRIEF.md +54 -0
  30. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-03-provider-model-selection-contract/slice.json +75 -0
  31. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-04-planner-ia-progress-flows/CLOSURE_BRIEF.md +32 -0
  32. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-04-planner-ia-progress-flows/EXECUTION_BRIEF.md +57 -0
  33. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-04-planner-ia-progress-flows/slice.json +85 -0
  34. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-05-executor-pr-progress-flows/CLOSURE_BRIEF.md +33 -0
  35. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-05-executor-pr-progress-flows/EXECUTION_BRIEF.md +57 -0
  36. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-05-executor-pr-progress-flows/slice.json +85 -0
  37. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-06-doctor-visual-json-contract/CLOSURE_BRIEF.md +35 -0
  38. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-06-doctor-visual-json-contract/EXECUTION_BRIEF.md +55 -0
  39. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-06-doctor-visual-json-contract/slice.json +81 -0
  40. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-07-interactive-init-spec-create/CLOSURE_BRIEF.md +34 -0
  41. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-07-interactive-init-spec-create/EXECUTION_BRIEF.md +55 -0
  42. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-07-interactive-init-spec-create/slice.json +85 -0
  43. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-08-tests-docs-cross-platform-release/CLOSURE_BRIEF.md +34 -0
  44. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-08-tests-docs-cross-platform-release/EXECUTION_BRIEF.md +59 -0
  45. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-08-tests-docs-cross-platform-release/slice.json +95 -0
  46. package/specs/quiver-v31-ai-model-catalog-agent-selection/EVIDENCE_REPORT.md +185 -0
  47. package/specs/quiver-v31-ai-model-catalog-agent-selection/EXECUTION_PLAN.md +85 -0
  48. package/specs/quiver-v31-ai-model-catalog-agent-selection/SPEC.md +337 -0
  49. package/specs/quiver-v31-ai-model-catalog-agent-selection/STATUS.md +30 -0
  50. package/specs/quiver-v31-ai-model-catalog-agent-selection/pr.md +98 -0
  51. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-00-spec-foundation/CLOSURE_BRIEF.md +30 -0
  52. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-00-spec-foundation/EXECUTION_BRIEF.md +51 -0
  53. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-00-spec-foundation/slice.json +62 -0
  54. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-01-model-catalog-alias-normalization/CLOSURE_BRIEF.md +32 -0
  55. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-01-model-catalog-alias-normalization/EXECUTION_BRIEF.md +53 -0
  56. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-01-model-catalog-alias-normalization/slice.json +72 -0
  57. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-02-interactive-agent-set-selectors/CLOSURE_BRIEF.md +36 -0
  58. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-02-interactive-agent-set-selectors/EXECUTION_BRIEF.md +56 -0
  59. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-02-interactive-agent-set-selectors/slice.json +78 -0
  60. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-03-agent-doctor-repair/CLOSURE_BRIEF.md +36 -0
  61. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-03-agent-doctor-repair/EXECUTION_BRIEF.md +57 -0
  62. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-03-agent-doctor-repair/slice.json +77 -0
  63. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-04-shared-preflight-provider-errors/CLOSURE_BRIEF.md +35 -0
  64. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-04-shared-preflight-provider-errors/EXECUTION_BRIEF.md +55 -0
  65. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-04-shared-preflight-provider-errors/slice.json +84 -0
  66. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-05-ai-models-list/CLOSURE_BRIEF.md +32 -0
  67. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-05-ai-models-list/EXECUTION_BRIEF.md +52 -0
  68. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-05-ai-models-list/slice.json +72 -0
  69. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-06-docs-templates-alignment/CLOSURE_BRIEF.md +33 -0
  70. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-06-docs-templates-alignment/EXECUTION_BRIEF.md +58 -0
  71. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-06-docs-templates-alignment/slice.json +84 -0
  72. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-07-tests-smokes-release-readiness/CLOSURE_BRIEF.md +32 -0
  73. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-07-tests-smokes-release-readiness/EXECUTION_BRIEF.md +59 -0
  74. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-07-tests-smokes-release-readiness/slice.json +94 -0
  75. package/specs/quiver-v32-npx-installation-guidance/EVIDENCE_REPORT.md +26 -0
  76. package/specs/quiver-v32-npx-installation-guidance/SPEC.md +55 -0
  77. package/specs/quiver-v32-npx-installation-guidance/STATUS.md +23 -0
  78. package/specs/quiver-v32-npx-installation-guidance/pr.md +72 -0
  79. package/specs/quiver-v32-npx-installation-guidance/slices/slice-00-installation-docs/CLOSURE_BRIEF.md +31 -0
  80. package/specs/quiver-v32-npx-installation-guidance/slices/slice-00-installation-docs/EXECUTION_BRIEF.md +56 -0
  81. package/specs/quiver-v32-npx-installation-guidance/slices/slice-00-installation-docs/slice.json +75 -0
  82. package/src/create-quiver/commands/ai.js +797 -99
  83. package/src/create-quiver/commands/flow.js +2 -2
  84. package/src/create-quiver/commands/spec.js +106 -6
  85. package/src/create-quiver/index.js +557 -78
  86. package/src/create-quiver/lib/agent-profiles.js +440 -11
  87. package/src/create-quiver/lib/ai/execution-plan.js +111 -8
  88. package/src/create-quiver/lib/ai/executor.js +291 -28
  89. package/src/create-quiver/lib/ai/model-catalog.js +333 -0
  90. package/src/create-quiver/lib/ai/providers.js +211 -2
  91. package/src/create-quiver/lib/cli/selectors.js +160 -0
  92. package/src/create-quiver/lib/cli/theme.js +5 -2
  93. package/src/create-quiver/lib/cli/ux-flags.js +18 -0
  94. package/src/create-quiver/lib/cli/ux.js +100 -5
@@ -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,15 @@ const {
55
63
  } = require('../lib/ai/run-state');
56
64
  const {
57
65
  agentProfilesPath,
66
+ buildAgentProfileDoctorReport,
67
+ buildAgentProfileRepairPlan,
58
68
  buildAgentProfileState,
59
69
  getAgentProfile,
70
+ getAgentProfileById,
71
+ getAgentProfilesForRole,
60
72
  listAgentProfiles,
73
+ normalizeAgentProfileRole,
74
+ resolveAgentProfileDisplayName,
61
75
  resolveProfileProvider,
62
76
  setAgentProfile,
63
77
  } = require('../lib/agent-profiles');
@@ -122,11 +136,126 @@ function normalizeTimeout(timeoutMs) {
122
136
  return parsed;
123
137
  }
124
138
 
125
- function resolveProviderForProfile(repoRoot, role, provider, providerExplicit, fallbackProvider) {
126
- if (providerExplicit === true || (provider && providerExplicit !== false)) {
127
- return String(provider || fallbackProvider).trim().toLowerCase();
139
+ function profileOptionForRole(options, role) {
140
+ const normalized = normalizeAgentProfileRole(role);
141
+ if (normalized === 'planner') {
142
+ return options.plannerProfile || options.profileId || '';
143
+ }
144
+ if (normalized === 'executor') {
145
+ return options.executorProfile || options.profileId || '';
146
+ }
147
+ if (normalized === 'reviewer') {
148
+ return options.reviewerProfile || options.profileId || '';
149
+ }
150
+ if (normalized === 'doctor') {
151
+ return options.doctorProfile || options.profileId || '';
152
+ }
153
+ return options.profileId || '';
154
+ }
155
+
156
+ function resolveRuntimeAgentProfile(repoRoot, role, options = {}, fallbackProvider = DEFAULT_PLAN_PROVIDER) {
157
+ const normalizedRole = normalizeAgentProfileRole(role);
158
+ const explicitProvider = options.providerExplicit === true || (options.provider && options.providerExplicit !== false);
159
+ const explicitModel = String(options.model || '').trim();
160
+
161
+ if (explicitProvider) {
162
+ const provider = String(options.provider || fallbackProvider).trim().toLowerCase();
163
+ return {
164
+ role: normalizedRole,
165
+ profile: null,
166
+ profileId: '',
167
+ displayName: provider,
168
+ provider,
169
+ model: explicitModel,
170
+ };
171
+ }
172
+
173
+ const profileId = profileOptionForRole(options, normalizedRole);
174
+ const profile = profileId
175
+ ? getAgentProfileById(repoRoot, normalizedRole, profileId)
176
+ : getAgentProfile(repoRoot, normalizedRole);
177
+ const provider = profile?.provider || resolveProfileProvider(repoRoot, normalizedRole, fallbackProvider);
178
+
179
+ return {
180
+ role: normalizedRole,
181
+ profile,
182
+ profileId: profile?.id || profileId || '',
183
+ displayName: profile ? resolveAgentProfileDisplayName(profile) : provider,
184
+ provider,
185
+ model: explicitModel || profile?.model || '',
186
+ };
187
+ }
188
+
189
+ function runtimeModelExecutionOptions(runtimeProfile, options = {}) {
190
+ return {
191
+ model: runtimeProfile.model,
192
+ blockModelAlias: Boolean(runtimeProfile.profile && !String(options.model || '').trim()),
193
+ };
194
+ }
195
+
196
+ function createCommandUx(options = {}) {
197
+ if (options.ux) {
198
+ return options.ux;
199
+ }
200
+
201
+ return createUx({
202
+ env: options.env || process.env,
203
+ input: options.inputStream,
204
+ output: options.outputStream,
205
+ error: options.errorStream,
206
+ interactive: options.interactive,
207
+ json: options.json,
208
+ noColor: options.noColor,
209
+ prompts: options.prompts,
210
+ spinner: options.spinner,
211
+ stdinIsTTY: options.stdinIsTTY,
212
+ stdoutIsTTY: options.stdoutIsTTY,
213
+ stderrIsTTY: options.stderrIsTTY,
214
+ write: options.write,
215
+ });
216
+ }
217
+
218
+ function shouldShowHumanProgress(ux, options = {}) {
219
+ return options.progress !== false
220
+ && options.dryRun !== true
221
+ && options.printPrompt !== true
222
+ && ux?.mode?.decoration === true;
223
+ }
224
+
225
+ function plannerProgressTitle(action, runtimeProfile) {
226
+ return `${action} con ${runtimeProfile.displayName || runtimeProfile.model || runtimeProfile.provider}`;
227
+ }
228
+
229
+ function writeProgressChecks(ux, enabled, title, checks = []) {
230
+ if (!enabled) {
231
+ return;
232
+ }
233
+ ux.heading(title);
234
+ for (const check of checks) {
235
+ ux.check(check);
236
+ }
237
+ }
238
+
239
+ async function runProviderWithProgress({ ux, enabled, message = 'Ejecutando agente...', successMessage = 'Agente finalizado', failureMessage = 'Fallo ejecutando agente', run }) {
240
+ async function runAndFailOnProviderResult() {
241
+ const result = await run();
242
+ if (result && result.ok === false) {
243
+ const error = new Error(result.error?.message || 'provider run failed');
244
+ error.code = result.error?.code || 'AI_PROVIDER_RUN_FAILED';
245
+ error.providerResult = result;
246
+ throw error;
247
+ }
248
+ return result;
249
+ }
250
+
251
+ if (!enabled) {
252
+ return run();
128
253
  }
129
- return resolveProfileProvider(repoRoot, role, fallbackProvider);
254
+
255
+ return ux.withSpinner(message, runAndFailOnProviderResult, {
256
+ successMessage,
257
+ failureMessage,
258
+ });
130
259
  }
131
260
 
132
261
  function buildPlanContext({ role, context, phase, inputText, inputPath, repoRoot, revise = false }) {
@@ -210,6 +339,10 @@ function formatDryRunReport({ task, provider, role, contextPack, phase, invocati
210
339
  lines.push(`Timeout: ${invocation.timeoutMs}ms`);
211
340
  lines.push(`Prompt transport: ${invocation.promptTransport.mode}`);
212
341
  lines.push(`Prompt length: ${invocation.promptLength} bytes`);
342
+ if (invocation.modelSelection && invocation.modelSelection.model) {
343
+ lines.push(`Model: ${invocation.modelSelection.model}`);
344
+ lines.push(`Model support: ${invocation.modelSelection.supported ? 'supported' : 'unsupported'} (${invocation.modelSelection.reason})`);
345
+ }
213
346
 
214
347
  if (onboardingPlan) {
215
348
  lines.push(`Prompt source: ${onboardingPlan.promptSource}`);
@@ -236,6 +369,10 @@ function formatPromptOnlyReport({ task, provider, role, contextPack, phase, invo
236
369
  lines.push(`Timeout: ${invocation.timeoutMs}ms`);
237
370
  lines.push(`Prompt transport: ${invocation.promptTransport.mode}`);
238
371
  lines.push(`Prompt length: ${invocation.promptLength} bytes`);
372
+ if (invocation.modelSelection && invocation.modelSelection.model) {
373
+ lines.push(`Model: ${invocation.modelSelection.model}`);
374
+ lines.push(`Model support: ${invocation.modelSelection.supported ? 'supported' : 'unsupported'} (${invocation.modelSelection.reason})`);
375
+ }
239
376
 
240
377
  if (onboardingPlan) {
241
378
  lines.push(`Prompt source: ${onboardingPlan.promptSource}`);
@@ -407,6 +544,12 @@ function formatPrepareContextPlannerDryRunReport({ provider, role, context, invo
407
544
  `Context pack: ${context}`,
408
545
  `Command: ${invocation.command} ${invocation.args.join(' ')}`.trim(),
409
546
  `Prompt bytes: ${invocation.promptLength}`,
547
+ invocation.modelSelection && invocation.modelSelection.model
548
+ ? `Model: ${invocation.modelSelection.model}`
549
+ : '',
550
+ invocation.modelSelection && invocation.modelSelection.model
551
+ ? `Model support: ${invocation.modelSelection.supported ? 'supported' : 'unsupported'} (${invocation.modelSelection.reason})`
552
+ : '',
410
553
  `Prompt source: ${promptInfo.promptSource}`,
411
554
  `Review requested: ${review ? 'yes' : 'no'}`,
412
555
  `Interactive requested: ${interactive ? 'yes' : 'no'}`,
@@ -424,7 +567,7 @@ function formatPrepareContextPlannerDryRunReport({ provider, role, context, invo
424
567
  '- npx create-quiver ai prepare-context --with-planner',
425
568
  ];
426
569
 
427
- return `${lines.join('\n')}\n`;
570
+ return `${lines.filter(Boolean).join('\n')}\n`;
428
571
  }
429
572
 
430
573
  function serializeProposalForReview(proposal) {
@@ -1108,7 +1251,8 @@ function annotateGitHubError(error, scope) {
1108
1251
 
1109
1252
  async function runOnboard(repoRoot, options = {}) {
1110
1253
  const role = normalizeRole(options.role || DEFAULT_ONBOARD_ROLE);
1111
- const provider = resolveProviderForProfile(repoRoot, role, options.provider, options.providerExplicit, DEFAULT_ONBOARD_PROVIDER);
1254
+ const runtimeProfile = resolveRuntimeAgentProfile(repoRoot, role, options, DEFAULT_ONBOARD_PROVIDER);
1255
+ const provider = runtimeProfile.provider;
1112
1256
  const context = options.context || DEFAULT_ONBOARD_CONTEXT;
1113
1257
  const timeoutMs = normalizeTimeout(options.timeout);
1114
1258
  const inputText = readTextFile(options.input, repoRoot);
@@ -1121,9 +1265,15 @@ async function runOnboard(repoRoot, options = {}) {
1121
1265
  prompt,
1122
1266
  cwd: repoRoot,
1123
1267
  timeoutMs,
1268
+ ...runtimeModelExecutionOptions(runtimeProfile, options),
1269
+ enforceModelSelection: false,
1124
1270
  });
1125
1271
  } catch (error) {
1126
- throw annotateProviderError(error, 'onboard');
1272
+ if (error.providerResult) {
1273
+ result = error.providerResult;
1274
+ } else {
1275
+ throw annotateProviderError(error, 'onboard');
1276
+ }
1127
1277
  }
1128
1278
 
1129
1279
  if (options.dryRun) {
@@ -1134,6 +1284,7 @@ async function runOnboard(repoRoot, options = {}) {
1134
1284
  contextPack: context,
1135
1285
  invocation,
1136
1286
  onboardingPlan: contextInfo.plan,
1287
+ profile: runtimeProfile,
1137
1288
  };
1138
1289
  process.stdout.write(formatDryRunReport(report));
1139
1290
  return report;
@@ -1148,23 +1299,39 @@ async function runOnboard(repoRoot, options = {}) {
1148
1299
  invocation,
1149
1300
  onboardingPlan: contextInfo.plan,
1150
1301
  prompt,
1302
+ profile: runtimeProfile,
1151
1303
  };
1152
1304
  process.stdout.write(formatPromptOnlyReport(report));
1153
1305
  return report;
1154
1306
  }
1155
1307
 
1308
+ const ux = createCommandUx(options);
1309
+ const showProgress = shouldShowHumanProgress(ux, options);
1310
+ writeProgressChecks(
1311
+ ux,
1312
+ showProgress,
1313
+ plannerProgressTitle('Ejecutando onboarding', runtimeProfile),
1314
+ ['Leyendo docs base', 'Detectando estructura', 'Preparando prompt'],
1315
+ );
1316
+
1156
1317
  let result;
1157
1318
  try {
1158
- result = await (options.runProviderFn || runProvider)(provider, {
1159
- prompt,
1160
- cwd: repoRoot,
1161
- timeoutMs,
1162
- dryRun: false,
1163
- probe: options.probe,
1164
- spawn: options.spawn,
1165
- tempRoot: options.tempRoot,
1166
- tempFileName: options.tempFileName,
1167
- tempFilePrefix: options.tempFilePrefix,
1319
+ result = await runProviderWithProgress({
1320
+ ux,
1321
+ enabled: showProgress,
1322
+ run: () => (options.runProviderFn || runProvider)(provider, {
1323
+ prompt,
1324
+ cwd: repoRoot,
1325
+ timeoutMs,
1326
+ dryRun: false,
1327
+ probe: options.probe,
1328
+ spawn: options.spawn,
1329
+ tempRoot: options.tempRoot,
1330
+ tempFileName: options.tempFileName,
1331
+ tempFilePrefix: options.tempFilePrefix,
1332
+ ...runtimeModelExecutionOptions(runtimeProfile, options),
1333
+ enforceModelSelection: Boolean(runtimeProfile.model),
1334
+ }),
1168
1335
  });
1169
1336
  } catch (error) {
1170
1337
  throw annotateProviderError(error, 'onboard');
@@ -1185,6 +1352,7 @@ async function runOnboard(repoRoot, options = {}) {
1185
1352
  contextPack: context,
1186
1353
  invocation,
1187
1354
  onboardingPlan: contextInfo.plan,
1355
+ profile: runtimeProfile,
1188
1356
  result,
1189
1357
  };
1190
1358
  }
@@ -1257,7 +1425,8 @@ async function runPrepareContext(repoRoot, options = {}) {
1257
1425
 
1258
1426
  async function runPrepareContextWithPlanner(repoRoot, options = {}) {
1259
1427
  const role = normalizeRole(options.role || DEFAULT_PLAN_ROLE);
1260
- const provider = resolveProviderForProfile(repoRoot, role, options.provider, options.providerExplicit, DEFAULT_PLAN_PROVIDER);
1428
+ const runtimeProfile = resolveRuntimeAgentProfile(repoRoot, role, options, DEFAULT_PLAN_PROVIDER);
1429
+ const provider = runtimeProfile.provider;
1261
1430
  const context = options.context || DEFAULT_PLAN_CONTEXT;
1262
1431
  const timeoutMs = normalizeTimeout(options.timeout);
1263
1432
  const draftPack = buildContextPreparationDrafts(repoRoot);
@@ -1276,9 +1445,15 @@ async function runPrepareContextWithPlanner(repoRoot, options = {}) {
1276
1445
  prompt,
1277
1446
  cwd: repoRoot,
1278
1447
  timeoutMs,
1448
+ ...runtimeModelExecutionOptions(runtimeProfile, options),
1449
+ enforceModelSelection: false,
1279
1450
  });
1280
1451
  } catch (error) {
1281
- throw annotateProviderError(error, 'prepare-context');
1452
+ if (error.providerResult) {
1453
+ result = error.providerResult;
1454
+ } else {
1455
+ throw annotateProviderError(error, 'prepare-context');
1456
+ }
1282
1457
  }
1283
1458
 
1284
1459
  if (options.dryRun) {
@@ -1292,6 +1467,7 @@ async function runPrepareContextWithPlanner(repoRoot, options = {}) {
1292
1467
  invocation,
1293
1468
  candidateDocs: promptInfo.allowedPaths,
1294
1469
  plan: draftPack.plan,
1470
+ profile: runtimeProfile,
1295
1471
  };
1296
1472
  process.stdout.write(formatPrepareContextPlannerDryRunReport({
1297
1473
  provider,
@@ -1314,23 +1490,39 @@ async function runPrepareContextWithPlanner(repoRoot, options = {}) {
1314
1490
  invocation,
1315
1491
  prompt,
1316
1492
  promptSource: promptInfo.promptSource,
1493
+ profile: runtimeProfile,
1317
1494
  };
1318
1495
  process.stdout.write(formatPromptOnlyReport(report));
1319
1496
  return report;
1320
1497
  }
1321
1498
 
1499
+ const ux = createCommandUx(options);
1500
+ const showProgress = shouldShowHumanProgress(ux, options);
1501
+ writeProgressChecks(
1502
+ ux,
1503
+ showProgress,
1504
+ plannerProgressTitle('Ejecutando onboarding', runtimeProfile),
1505
+ ['Leyendo docs base', 'Detectando estructura', 'Preparando prompt'],
1506
+ );
1507
+
1322
1508
  let result;
1323
1509
  try {
1324
- result = await (options.runProviderFn || runProvider)(provider, {
1325
- prompt,
1326
- cwd: repoRoot,
1327
- timeoutMs,
1328
- dryRun: false,
1329
- probe: options.probe,
1330
- spawn: options.spawn,
1331
- tempRoot: options.tempRoot,
1332
- tempFileName: options.tempFileName,
1333
- tempFilePrefix: options.tempFilePrefix,
1510
+ result = await runProviderWithProgress({
1511
+ ux,
1512
+ enabled: showProgress,
1513
+ run: () => (options.runProviderFn || runProvider)(provider, {
1514
+ prompt,
1515
+ cwd: repoRoot,
1516
+ timeoutMs,
1517
+ dryRun: false,
1518
+ probe: options.probe,
1519
+ spawn: options.spawn,
1520
+ tempRoot: options.tempRoot,
1521
+ tempFileName: options.tempFileName,
1522
+ tempFilePrefix: options.tempFilePrefix,
1523
+ ...runtimeModelExecutionOptions(runtimeProfile, options),
1524
+ enforceModelSelection: Boolean(runtimeProfile.model),
1525
+ }),
1334
1526
  });
1335
1527
  } catch (error) {
1336
1528
  throw annotateProviderError(error, 'prepare-context');
@@ -1404,7 +1596,8 @@ async function runPrepareContextWithPlanner(repoRoot, options = {}) {
1404
1596
  async function runPlan(repoRoot, options = {}) {
1405
1597
  const phase = normalizePlannerPhase(options.phase || DEFAULT_PLAN_PHASE);
1406
1598
  const role = normalizeRole(options.role || DEFAULT_PLAN_ROLE);
1407
- const provider = resolveProviderForProfile(repoRoot, role, options.provider, options.providerExplicit, DEFAULT_PLAN_PROVIDER);
1599
+ const runtimeProfile = resolveRuntimeAgentProfile(repoRoot, role, options, DEFAULT_PLAN_PROVIDER);
1600
+ const provider = runtimeProfile.provider;
1408
1601
  const context = options.context || DEFAULT_PLAN_CONTEXT;
1409
1602
  const timeoutMs = normalizeTimeout(options.timeout);
1410
1603
  let inputPath = options.input || '';
@@ -1506,9 +1699,15 @@ async function runPlan(repoRoot, options = {}) {
1506
1699
  prompt,
1507
1700
  cwd: repoRoot,
1508
1701
  timeoutMs,
1702
+ ...runtimeModelExecutionOptions(runtimeProfile, options),
1703
+ enforceModelSelection: false,
1509
1704
  });
1510
1705
  } catch (error) {
1511
- throw annotateProviderError(error, 'plan', phase);
1706
+ if (error.providerResult) {
1707
+ result = error.providerResult;
1708
+ } else {
1709
+ throw annotateProviderError(error, 'plan', phase);
1710
+ }
1512
1711
  }
1513
1712
 
1514
1713
  if (options.dryRun) {
@@ -1519,6 +1718,7 @@ async function runPlan(repoRoot, options = {}) {
1519
1718
  contextPack: contextInfo.pack.packName,
1520
1719
  phase,
1521
1720
  invocation,
1721
+ profile: runtimeProfile,
1522
1722
  };
1523
1723
  process.stdout.write(formatDryRunReport(report));
1524
1724
  if (options.withPlanner === true) {
@@ -1542,23 +1742,39 @@ async function runPlan(repoRoot, options = {}) {
1542
1742
  phase,
1543
1743
  invocation,
1544
1744
  prompt,
1745
+ profile: runtimeProfile,
1545
1746
  };
1546
1747
  process.stdout.write(formatPromptOnlyReport(report));
1547
1748
  return report;
1548
1749
  }
1549
1750
 
1751
+ const ux = createCommandUx(options);
1752
+ const showProgress = shouldShowHumanProgress(ux, options);
1753
+ writeProgressChecks(
1754
+ ux,
1755
+ showProgress,
1756
+ plannerProgressTitle(`Ejecutando plan ${phase}`, runtimeProfile),
1757
+ ['Leyendo entrada', 'Preparando contexto', 'Preparando prompt'],
1758
+ );
1759
+
1550
1760
  let result;
1551
1761
  try {
1552
- result = await (options.runProviderFn || runProvider)(provider, {
1553
- prompt,
1554
- cwd: repoRoot,
1555
- timeoutMs,
1556
- dryRun: false,
1557
- probe: options.probe,
1558
- spawn: options.spawn,
1559
- tempRoot: options.tempRoot,
1560
- tempFileName: options.tempFileName,
1561
- tempFilePrefix: options.tempFilePrefix,
1762
+ result = await runProviderWithProgress({
1763
+ ux,
1764
+ enabled: showProgress,
1765
+ run: () => (options.runProviderFn || runProvider)(provider, {
1766
+ prompt,
1767
+ cwd: repoRoot,
1768
+ timeoutMs,
1769
+ dryRun: false,
1770
+ probe: options.probe,
1771
+ spawn: options.spawn,
1772
+ tempRoot: options.tempRoot,
1773
+ tempFileName: options.tempFileName,
1774
+ tempFilePrefix: options.tempFilePrefix,
1775
+ ...runtimeModelExecutionOptions(runtimeProfile, options),
1776
+ enforceModelSelection: Boolean(runtimeProfile.model),
1777
+ }),
1562
1778
  });
1563
1779
  } catch (error) {
1564
1780
  throw annotateProviderError(error, 'plan', phase);
@@ -1625,7 +1841,8 @@ async function runPlan(repoRoot, options = {}) {
1625
1841
 
1626
1842
  async function runReviewPlan(repoRoot, options = {}) {
1627
1843
  const role = 'planner';
1628
- const provider = resolveProviderForProfile(repoRoot, 'reviewer', options.provider, options.providerExplicit, DEFAULT_PLAN_PROVIDER);
1844
+ const runtimeProfile = resolveRuntimeAgentProfile(repoRoot, 'reviewer', options, DEFAULT_PLAN_PROVIDER);
1845
+ const provider = runtimeProfile.provider;
1629
1846
  const context = options.context || DEFAULT_PLAN_CONTEXT;
1630
1847
  const timeoutMs = normalizeTimeout(options.timeout);
1631
1848
  const resolved = resolveTechnicalPlanReviewInput(repoRoot, options.input || undefined);
@@ -1649,9 +1866,15 @@ async function runReviewPlan(repoRoot, options = {}) {
1649
1866
  prompt: built.prompt,
1650
1867
  cwd: repoRoot,
1651
1868
  timeoutMs,
1869
+ ...runtimeModelExecutionOptions(runtimeProfile, options),
1870
+ enforceModelSelection: false,
1652
1871
  });
1653
1872
  } catch (error) {
1654
- throw annotateProviderError(error, 'review-plan');
1873
+ if (error.providerResult) {
1874
+ result = error.providerResult;
1875
+ } else {
1876
+ throw annotateProviderError(error, 'review-plan');
1877
+ }
1655
1878
  }
1656
1879
 
1657
1880
  if (options.dryRun) {
@@ -1665,6 +1888,7 @@ async function runReviewPlan(repoRoot, options = {}) {
1665
1888
  inputPath,
1666
1889
  inputKind: resolved.kind,
1667
1890
  inputVersion: resolved.version,
1891
+ profile: runtimeProfile,
1668
1892
  };
1669
1893
  process.stdout.write(formatDryRunReport({
1670
1894
  task: 'review-plan',
@@ -1696,23 +1920,39 @@ async function runReviewPlan(repoRoot, options = {}) {
1696
1920
  inputPath,
1697
1921
  inputKind: resolved.kind,
1698
1922
  inputVersion: resolved.version,
1923
+ profile: runtimeProfile,
1699
1924
  };
1700
1925
  process.stdout.write(formatPromptOnlyReport(report));
1701
1926
  return report;
1702
1927
  }
1703
1928
 
1929
+ const ux = createCommandUx(options);
1930
+ const showProgress = shouldShowHumanProgress(ux, options);
1931
+ writeProgressChecks(
1932
+ ux,
1933
+ showProgress,
1934
+ plannerProgressTitle('Ejecutando revisión del plan', runtimeProfile),
1935
+ ['Leyendo plan técnico', 'Preparando contexto', 'Preparando prompt'],
1936
+ );
1937
+
1704
1938
  let result;
1705
1939
  try {
1706
- result = await (options.runProviderFn || runProvider)(provider, {
1707
- prompt: built.prompt,
1708
- cwd: repoRoot,
1709
- timeoutMs,
1710
- dryRun: false,
1711
- probe: options.probe,
1712
- spawn: options.spawn,
1713
- tempRoot: options.tempRoot,
1714
- tempFileName: options.tempFileName,
1715
- tempFilePrefix: options.tempFilePrefix,
1940
+ result = await runProviderWithProgress({
1941
+ ux,
1942
+ enabled: showProgress,
1943
+ run: () => (options.runProviderFn || runProvider)(provider, {
1944
+ prompt: built.prompt,
1945
+ cwd: repoRoot,
1946
+ timeoutMs,
1947
+ dryRun: false,
1948
+ probe: options.probe,
1949
+ spawn: options.spawn,
1950
+ tempRoot: options.tempRoot,
1951
+ tempFileName: options.tempFileName,
1952
+ tempFilePrefix: options.tempFilePrefix,
1953
+ ...runtimeModelExecutionOptions(runtimeProfile, options),
1954
+ enforceModelSelection: Boolean(runtimeProfile.model),
1955
+ }),
1716
1956
  });
1717
1957
  } catch (error) {
1718
1958
  throw annotateProviderError(error, 'review-plan');
@@ -1770,7 +2010,8 @@ async function runReviewPlan(repoRoot, options = {}) {
1770
2010
 
1771
2011
  async function runRepairPlan(repoRoot, options = {}) {
1772
2012
  const role = normalizeRole(options.role || DEFAULT_PLAN_ROLE);
1773
- const provider = resolveProviderForProfile(repoRoot, role, options.provider, options.providerExplicit, DEFAULT_PLAN_PROVIDER);
2013
+ const runtimeProfile = resolveRuntimeAgentProfile(repoRoot, role, options, DEFAULT_PLAN_PROVIDER);
2014
+ const provider = runtimeProfile.provider;
1774
2015
  const context = options.context || DEFAULT_PLAN_CONTEXT;
1775
2016
  const timeoutMs = normalizeTimeout(options.timeout);
1776
2017
  const source = resolveApprovedTechnicalPlanForRepair(repoRoot, options.input || '');
@@ -1790,9 +2031,15 @@ async function runRepairPlan(repoRoot, options = {}) {
1790
2031
  prompt: built.prompt,
1791
2032
  cwd: repoRoot,
1792
2033
  timeoutMs,
2034
+ ...runtimeModelExecutionOptions(runtimeProfile, options),
2035
+ enforceModelSelection: false,
1793
2036
  });
1794
2037
  } catch (error) {
1795
- throw annotateProviderError(error, 'repair-plan');
2038
+ if (error.providerResult) {
2039
+ providerResult = error.providerResult;
2040
+ } else {
2041
+ throw annotateProviderError(error, 'repair-plan');
2042
+ }
1796
2043
  }
1797
2044
 
1798
2045
  if (options.dryRun) {
@@ -1803,6 +2050,7 @@ async function runRepairPlan(repoRoot, options = {}) {
1803
2050
  contextPack: built.pack.packName,
1804
2051
  phase: 'technical-plan',
1805
2052
  invocation,
2053
+ profile: runtimeProfile,
1806
2054
  };
1807
2055
  process.stdout.write(formatDryRunReport(report));
1808
2056
  process.stdout.write(`Source approved artifact: ${source.path}\n`);
@@ -1822,23 +2070,39 @@ async function runRepairPlan(repoRoot, options = {}) {
1822
2070
  inputPath: source.path,
1823
2071
  inputKind: 'approved',
1824
2072
  inputVersion: source.approval.meta?.approved?.version || null,
2073
+ profile: runtimeProfile,
1825
2074
  };
1826
2075
  process.stdout.write(formatPromptOnlyReport(report));
1827
2076
  return report;
1828
2077
  }
1829
2078
 
2079
+ const ux = createCommandUx(options);
2080
+ const showProgress = shouldShowHumanProgress(ux, options);
2081
+ writeProgressChecks(
2082
+ ux,
2083
+ showProgress,
2084
+ plannerProgressTitle('Ejecutando reparación del plan', runtimeProfile),
2085
+ ['Leyendo plan aprobado', 'Preparando contexto', 'Preparando prompt'],
2086
+ );
2087
+
1830
2088
  let providerResult;
1831
2089
  try {
1832
- providerResult = await (options.runProviderFn || runProvider)(provider, {
1833
- prompt: built.prompt,
1834
- cwd: repoRoot,
1835
- timeoutMs,
1836
- dryRun: false,
1837
- probe: options.probe,
1838
- spawn: options.spawn,
1839
- tempRoot: options.tempRoot,
1840
- tempFileName: options.tempFileName,
1841
- tempFilePrefix: options.tempFilePrefix,
2090
+ providerResult = await runProviderWithProgress({
2091
+ ux,
2092
+ enabled: showProgress,
2093
+ run: () => (options.runProviderFn || runProvider)(provider, {
2094
+ prompt: built.prompt,
2095
+ cwd: repoRoot,
2096
+ timeoutMs,
2097
+ dryRun: false,
2098
+ probe: options.probe,
2099
+ spawn: options.spawn,
2100
+ tempRoot: options.tempRoot,
2101
+ tempFileName: options.tempFileName,
2102
+ tempFilePrefix: options.tempFilePrefix,
2103
+ ...runtimeModelExecutionOptions(runtimeProfile, options),
2104
+ enforceModelSelection: Boolean(runtimeProfile.model),
2105
+ }),
1842
2106
  });
1843
2107
  } catch (error) {
1844
2108
  throw annotateProviderError(error, 'repair-plan');
@@ -2172,12 +2436,221 @@ function runLifecycleRun(repoRoot, options = {}) {
2172
2436
  };
2173
2437
  }
2174
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
+
2175
2645
  function formatAgentProfile(profile) {
2176
2646
  const lines = [
2647
+ `ID: ${profile.id || 'default'}`,
2177
2648
  `Role: ${profile.role}`,
2178
2649
  `Provider: ${profile.provider}`,
2179
2650
  `Model: ${profile.model || '(not set)'}`,
2180
2651
  `Label: ${profile.label || '(not set)'}`,
2652
+ `Display name: ${resolveAgentProfileDisplayName(profile) || '(not set)'}`,
2653
+ `Default: ${profile.default === true ? 'yes' : 'no'}`,
2181
2654
  `Context: ${profile.context || '(not set)'}`,
2182
2655
  `Updated: ${profile.updated_at}`,
2183
2656
  ];
@@ -2193,7 +2666,9 @@ function formatAgentProfileList(profiles) {
2193
2666
  }
2194
2667
  const model = item.profile.model ? ` model=${item.profile.model}` : '';
2195
2668
  const label = item.profile.label ? ` label=${item.profile.label}` : '';
2196
- lines.push(`- ${item.role}: provider=${item.profile.provider}${model}${label}`);
2669
+ const displayName = resolveAgentProfileDisplayName(item.profile);
2670
+ const alternatives = item.profiles.length > 1 ? ` options=${item.profiles.length}` : '';
2671
+ lines.push(`- ${item.role}: provider=${item.profile.provider}${model}${label} displayName=${displayName}${alternatives}`);
2197
2672
  }
2198
2673
  return `${lines.join('\n')}\n`;
2199
2674
  }
@@ -2213,22 +2688,156 @@ function formatAgentProfileDryRun(repoRoot, result) {
2213
2688
  ].join('\n');
2214
2689
  }
2215
2690
 
2216
- 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 = {}) {
2217
2825
  const command = String(options.command || '').trim().toLowerCase();
2218
2826
 
2219
2827
  if (command === 'set') {
2220
2828
  if (!options.role) {
2221
- throw new Error(formatError('missing agent role. Use: npx create-quiver ai agent set <planner|executor|reviewer|doctor> --provider <provider>'));
2222
- }
2223
- if (!options.provider) {
2224
- 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>'));
2225
2830
  }
2831
+ const profileOptions = await resolveInteractiveAgentSetOptions(repoRoot, options);
2226
2832
  if (options.dryRun) {
2227
- const preview = buildAgentProfileState(repoRoot, options.role, {
2228
- context: options.context,
2229
- label: options.label,
2230
- model: options.model,
2231
- 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,
2232
2841
  });
2233
2842
  process.stdout.write(formatAgentProfileDryRun(repoRoot, preview));
2234
2843
  return {
@@ -2239,11 +2848,35 @@ function runAgent(repoRoot, options = {}) {
2239
2848
  filePath: path.relative(repoRoot, preview.filePath).split(path.sep).join('/'),
2240
2849
  };
2241
2850
  }
2242
- const result = setAgentProfile(repoRoot, options.role, {
2243
- context: options.context,
2244
- label: options.label,
2245
- model: options.model,
2246
- 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,
2247
2880
  });
2248
2881
  process.stdout.write('AI agent profile saved\n');
2249
2882
  process.stdout.write(formatAgentProfile(result.profile));
@@ -2260,13 +2893,17 @@ function runAgent(repoRoot, options = {}) {
2260
2893
  if (!options.role) {
2261
2894
  throw new Error(formatError('missing agent role. Use: npx create-quiver ai agent show <planner|executor|reviewer|doctor>'));
2262
2895
  }
2263
- const profile = getAgentProfile(repoRoot, options.role);
2896
+ const profile = options.id
2897
+ ? getAgentProfileById(repoRoot, options.role, options.id)
2898
+ : getAgentProfile(repoRoot, options.role);
2264
2899
  if (!profile) {
2265
2900
  throw new Error(formatActionableError({
2266
- failure: `agent profile '${options.role}' is not configured.`,
2901
+ failure: options.id
2902
+ ? `agent profile '${options.role}/${options.id}' is not configured.`
2903
+ : `agent profile '${options.role}' is not configured.`,
2267
2904
  impact: 'Quiver will fall back to default provider behavior and may use the wrong model/cost profile.',
2268
- fix: `Configure the ${options.role} profile with a supported provider and optional model label.`,
2269
- 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>`,
2270
2907
  }));
2271
2908
  }
2272
2909
  process.stdout.write(formatAgentProfile(profile));
@@ -2288,7 +2925,44 @@ function runAgent(repoRoot, options = {}) {
2288
2925
  };
2289
2926
  }
2290
2927
 
2291
- 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`));
2292
2966
  }
2293
2967
 
2294
2968
  async function runGitHubTask(repoRoot, options = {}, mode = 'pr') {
@@ -2324,20 +2998,32 @@ async function runGitHubTask(repoRoot, options = {}, mode = 'pr') {
2324
2998
  async function runPr(repoRoot, options = {}) {
2325
2999
  const dryRun = options.dryRun === true;
2326
3000
  const create = options.create === true;
3001
+ const ux = createCommandUx(options);
3002
+ const showProgress = create && !dryRun && shouldShowHumanProgress(ux, options);
3003
+ if (showProgress) {
3004
+ ux.heading('Creando PR con gh');
3005
+ }
2327
3006
  let preflight;
2328
3007
 
2329
3008
  try {
2330
- preflight = await (options.preflightFn || preflightGitHubPr)(repoRoot, {
2331
- remote: options.remote,
2332
- sshHostAlias: options.sshHostAlias,
2333
- identityFile: options.identityFile,
2334
- gitFlowGuidePath: options.gitFlowGuidePath,
2335
- ghCommand: options.ghCommand,
2336
- ghProbe: options.ghProbe,
2337
- ghAuthProbe: options.ghAuthProbe,
2338
- ghProbeArgs: options.ghProbeArgs,
2339
- ghAuthArgs: options.ghAuthArgs,
2340
- blockedBranches: options.blockedBranches,
3009
+ preflight = await runProviderWithProgress({
3010
+ ux,
3011
+ enabled: showProgress,
3012
+ message: 'Ejecutando preflight de GitHub...',
3013
+ successMessage: 'Preflight de GitHub listo',
3014
+ failureMessage: 'Falló preflight de GitHub',
3015
+ run: () => (options.preflightFn || preflightGitHubPr)(repoRoot, {
3016
+ remote: options.remote,
3017
+ sshHostAlias: options.sshHostAlias,
3018
+ identityFile: options.identityFile,
3019
+ gitFlowGuidePath: options.gitFlowGuidePath,
3020
+ ghCommand: options.ghCommand,
3021
+ ghProbe: options.ghProbe,
3022
+ ghAuthProbe: options.ghAuthProbe,
3023
+ ghProbeArgs: options.ghProbeArgs,
3024
+ ghAuthArgs: options.ghAuthArgs,
3025
+ blockedBranches: options.blockedBranches,
3026
+ }),
2341
3027
  });
2342
3028
  } catch (error) {
2343
3029
  throw annotateGitHubError(error, 'pr');
@@ -2352,6 +3038,9 @@ async function runPr(repoRoot, options = {}) {
2352
3038
  prBodyPath: options.prBodyPath,
2353
3039
  title: options.title,
2354
3040
  });
3041
+ if (showProgress) {
3042
+ ux.check('Cuerpo del PR preparado');
3043
+ }
2355
3044
  } catch (error) {
2356
3045
  throw annotateGitHubError(error, 'pr');
2357
3046
  }
@@ -2396,8 +3085,15 @@ async function runPr(repoRoot, options = {}) {
2396
3085
 
2397
3086
  let result;
2398
3087
  try {
2399
- result = runGhPrCreate(plan, {
2400
- ghCreateRunner: options.ghCreateRunner,
3088
+ result = await runProviderWithProgress({
3089
+ ux,
3090
+ enabled: showProgress,
3091
+ message: 'Creando PR en GitHub...',
3092
+ successMessage: 'PR creado',
3093
+ failureMessage: 'Falló creación del PR',
3094
+ run: () => runGhPrCreate(plan, {
3095
+ ghCreateRunner: options.ghCreateRunner,
3096
+ }),
2401
3097
  });
2402
3098
  } catch (error) {
2403
3099
  throw annotateGitHubError(error, 'pr');
@@ -2435,6 +3131,7 @@ module.exports = {
2435
3131
  formatSpecDryRunReport,
2436
3132
  normalizeTimeout,
2437
3133
  readTextFile,
3134
+ resolveInteractiveAgentSetOptions,
2438
3135
  runAgent,
2439
3136
  runActiveSlice,
2440
3137
  runDoctor,
@@ -2443,6 +3140,7 @@ module.exports = {
2443
3140
  runLifecycleResume,
2444
3141
  runLifecycleRun,
2445
3142
  runLifecycleStatus,
3143
+ runModelsList,
2446
3144
  runExport,
2447
3145
  runInspect,
2448
3146
  runPromptSlice,