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
@@ -0,0 +1,333 @@
1
+ const { assertSupportedProvider } = require('./providers');
2
+
3
+ const MODEL_CATALOG_VERSION = 1;
4
+ const MODEL_CATALOG_LAST_UPDATED = '2026-05-27';
5
+
6
+ const MODEL_CATALOG = Object.freeze({
7
+ catalogVersion: MODEL_CATALOG_VERSION,
8
+ lastUpdated: MODEL_CATALOG_LAST_UPDATED,
9
+ providers: Object.freeze({
10
+ codex: Object.freeze({
11
+ id: 'codex',
12
+ displayName: 'Codex',
13
+ models: Object.freeze([
14
+ knownModel('gpt-5.5', 'GPT 5.5', {
15
+ aliases: ['GPT 5.5', 'gpt 5.5', 'Gpt-5.5'],
16
+ recommendedFor: ['planner', 'reviewer'],
17
+ costTier: 'high',
18
+ qualityTier: 'premium',
19
+ stability: 'stable',
20
+ notes: 'Recommended for planning, specs, architecture, and high-risk reviews.',
21
+ }),
22
+ knownModel('gpt-5.4', 'GPT 5.4', {
23
+ aliases: ['GPT 5.4', 'gpt 5.4'],
24
+ recommendedFor: ['planner', 'reviewer'],
25
+ costTier: 'medium',
26
+ qualityTier: 'high',
27
+ stability: 'stable',
28
+ notes: 'Balanced planner or reviewer model.',
29
+ }),
30
+ knownModel('gpt-5.4-mini', 'GPT 5.4 mini', {
31
+ aliases: ['GPT 5.4 mini', 'gpt 5.4 mini', 'gpt-5.4 mini'],
32
+ recommendedFor: ['executor', 'doctor'],
33
+ costTier: 'low',
34
+ qualityTier: 'standard',
35
+ stability: 'stable',
36
+ notes: 'Recommended for lower-cost execution, diagnostics, and sub-agent work.',
37
+ }),
38
+ knownModel('gpt-5.3-codex', 'GPT 5.3 Codex', {
39
+ aliases: ['GPT 5.3 Codex', 'gpt 5.3 codex'],
40
+ recommendedFor: ['executor'],
41
+ costTier: 'medium',
42
+ qualityTier: 'high',
43
+ stability: 'stable',
44
+ notes: 'Recommended for more complex coding slices.',
45
+ }),
46
+ knownModel('gpt-5.3-codex-spark', 'GPT 5.3 Codex Spark', {
47
+ aliases: ['GPT 5.3 Codex Spark', 'gpt 5.3 codex spark'],
48
+ recommendedFor: ['executor'],
49
+ costTier: 'medium',
50
+ qualityTier: 'high',
51
+ stability: 'preview',
52
+ notes: 'Preview or limited model for quick code iteration.',
53
+ }),
54
+ ]),
55
+ }),
56
+ claude: Object.freeze({
57
+ id: 'claude',
58
+ displayName: 'Claude',
59
+ models: Object.freeze([
60
+ knownModel('opus', 'Claude Opus', {
61
+ aliases: ['Claude Opus', 'opus'],
62
+ recommendedFor: ['planner', 'reviewer'],
63
+ costTier: 'high',
64
+ qualityTier: 'premium',
65
+ stability: 'stable',
66
+ notes: 'Recommended for complex planning and architectural decisions.',
67
+ }),
68
+ knownModel('sonnet', 'Claude Sonnet', {
69
+ aliases: ['Claude Sonnet', 'sonnet'],
70
+ recommendedFor: ['executor', 'reviewer'],
71
+ costTier: 'medium',
72
+ qualityTier: 'high',
73
+ stability: 'stable',
74
+ notes: 'Balanced execution and review model.',
75
+ }),
76
+ knownModel('haiku', 'Claude Haiku', {
77
+ aliases: ['Claude Haiku', 'haiku'],
78
+ recommendedFor: ['doctor'],
79
+ costTier: 'low',
80
+ qualityTier: 'standard',
81
+ stability: 'stable',
82
+ notes: 'Recommended for diagnostics and simple tasks.',
83
+ }),
84
+ knownModel('claude-opus-4-7', 'Claude Opus 4.7', {
85
+ aliases: ['OPUS 4.7', 'Claude Opus 4.7', 'opus 4.7'],
86
+ recommendedFor: ['planner', 'reviewer'],
87
+ costTier: 'high',
88
+ qualityTier: 'premium',
89
+ stability: 'known',
90
+ notes: 'Version-pinned known catalog choice for planning.',
91
+ }),
92
+ knownModel('claude-sonnet-4-6', 'Claude Sonnet 4.6', {
93
+ aliases: ['Claude Sonnet 4.6', 'sonnet 4.6'],
94
+ recommendedFor: ['executor', 'reviewer'],
95
+ costTier: 'medium',
96
+ qualityTier: 'high',
97
+ stability: 'known',
98
+ notes: 'Version-pinned known catalog choice for execution and review.',
99
+ }),
100
+ knownModel('claude-haiku-4-5', 'Claude Haiku 4.5', {
101
+ aliases: ['Claude Haiku 4.5', 'haiku 4.5'],
102
+ recommendedFor: ['doctor'],
103
+ costTier: 'low',
104
+ qualityTier: 'standard',
105
+ stability: 'known',
106
+ notes: 'Version-pinned known catalog choice for low-cost diagnostics.',
107
+ }),
108
+ ]),
109
+ }),
110
+ gemini: Object.freeze({
111
+ id: 'gemini',
112
+ displayName: 'Gemini',
113
+ models: Object.freeze([
114
+ knownModel('gemini-3.1-pro-preview', 'Gemini 3.1 Pro Preview', {
115
+ aliases: ['Gemini 3.1 Pro Preview', 'gemini 3.1 pro preview'],
116
+ recommendedFor: ['planner', 'reviewer'],
117
+ costTier: 'high',
118
+ qualityTier: 'premium',
119
+ stability: 'preview',
120
+ notes: 'Known catalog choice for complex planning and review.',
121
+ }),
122
+ knownModel('gemini-3.5-flash', 'Gemini 3.5 Flash', {
123
+ aliases: ['Gemini 3.5 Flash', 'gemini 3.5 flash'],
124
+ recommendedFor: ['executor'],
125
+ costTier: 'medium',
126
+ qualityTier: 'high',
127
+ stability: 'stable',
128
+ notes: 'Known catalog choice for balanced execution.',
129
+ }),
130
+ knownModel('gemini-3.1-flash-lite', 'Gemini 3.1 Flash-Lite', {
131
+ aliases: ['Gemini 3.1 Flash Lite', 'Gemini 3.1 Flash-Lite', 'gemini 3.1 flash lite'],
132
+ recommendedFor: ['doctor'],
133
+ costTier: 'low',
134
+ qualityTier: 'standard',
135
+ stability: 'stable',
136
+ notes: 'Known catalog choice for low-cost diagnostics and mechanical tasks.',
137
+ }),
138
+ knownModel('gemini-3.1-pro-preview-customtools', 'Gemini 3.1 Pro Custom Tools', {
139
+ aliases: ['Gemini 3.1 Pro Custom Tools', 'gemini 3.1 pro preview custom tools'],
140
+ recommendedFor: ['planner', 'reviewer'],
141
+ costTier: 'high',
142
+ qualityTier: 'premium',
143
+ stability: 'preview',
144
+ notes: 'Known catalog choice for advanced tool-oriented workflows.',
145
+ }),
146
+ ]),
147
+ }),
148
+ }),
149
+ });
150
+
151
+ function knownModel(id, displayName, options = {}) {
152
+ return Object.freeze({
153
+ id,
154
+ displayName,
155
+ aliases: Object.freeze(Array.from(new Set([id, displayName].concat(options.aliases || [])))),
156
+ recommendedFor: Object.freeze((options.recommendedFor || []).slice()),
157
+ costTier: options.costTier || 'unknown',
158
+ qualityTier: options.qualityTier || 'unknown',
159
+ stability: options.stability || 'known',
160
+ notes: options.notes || '',
161
+ modelSource: 'catalog',
162
+ });
163
+ }
164
+
165
+ function customModel(providerId, modelId = '') {
166
+ const id = String(modelId || '').trim();
167
+ return {
168
+ id: id || 'custom',
169
+ displayName: id || 'Custom',
170
+ aliases: id ? [id] : ['custom'],
171
+ recommendedFor: [],
172
+ costTier: 'unknown',
173
+ qualityTier: 'unknown',
174
+ stability: 'custom',
175
+ notes: 'Custom model id. Quiver cannot guarantee account availability without a live validation.',
176
+ modelSource: 'custom',
177
+ custom: true,
178
+ provider: providerId ? assertSupportedProvider(providerId) : '',
179
+ };
180
+ }
181
+
182
+ function getModelCatalog() {
183
+ return MODEL_CATALOG;
184
+ }
185
+
186
+ function listCatalogProviders() {
187
+ return Object.values(MODEL_CATALOG.providers).map((provider) => ({
188
+ id: provider.id,
189
+ displayName: provider.displayName,
190
+ modelCount: provider.models.length,
191
+ }));
192
+ }
193
+
194
+ function getProviderModelCatalog(providerId) {
195
+ const provider = assertSupportedProvider(providerId);
196
+ return MODEL_CATALOG.providers[provider];
197
+ }
198
+
199
+ function getKnownModelsForProvider(providerId, options = {}) {
200
+ const provider = getProviderModelCatalog(providerId);
201
+ const role = String(options.role || '').trim().toLowerCase();
202
+ const includeCustom = options.includeCustom !== false;
203
+ const models = provider.models.slice();
204
+ const sorted = role
205
+ ? models.sort((left, right) => roleScore(right, role) - roleScore(left, role))
206
+ : models;
207
+
208
+ if (!includeCustom) {
209
+ return sorted;
210
+ }
211
+
212
+ return sorted.concat(customModel(provider.id));
213
+ }
214
+
215
+ function roleScore(model, role) {
216
+ const roles = Array.isArray(model.recommendedFor) ? model.recommendedFor : [];
217
+ const index = roles.indexOf(role);
218
+ if (index === -1) return 0;
219
+ return 100 - index;
220
+ }
221
+
222
+ function normalizeModelAliasKey(value) {
223
+ return String(value || '')
224
+ .trim()
225
+ .toLowerCase()
226
+ .replace(/[^a-z0-9]+/g, '');
227
+ }
228
+
229
+ function resolveModelFromEntries(providerId, modelValue, entries) {
230
+ const provider = assertSupportedProvider(providerId);
231
+ const input = String(modelValue || '').trim();
232
+ const inputKey = normalizeModelAliasKey(input);
233
+ if (!inputKey) {
234
+ return {
235
+ provider,
236
+ input,
237
+ matched: false,
238
+ ambiguous: false,
239
+ model: null,
240
+ matches: [],
241
+ reason: 'empty model',
242
+ };
243
+ }
244
+
245
+ const matches = [];
246
+ for (const model of entries || []) {
247
+ const aliases = Array.isArray(model.aliases) ? model.aliases : [model.id, model.displayName];
248
+ if (aliases.some((alias) => normalizeModelAliasKey(alias) === inputKey)) {
249
+ matches.push(model);
250
+ }
251
+ }
252
+
253
+ if (matches.length === 1) {
254
+ const model = matches[0];
255
+ return {
256
+ provider,
257
+ input,
258
+ matched: true,
259
+ ambiguous: false,
260
+ model,
261
+ matches,
262
+ technicalModel: model.id,
263
+ displayName: model.displayName,
264
+ modelSource: model.modelSource || 'catalog',
265
+ reason: 'known model alias matched',
266
+ };
267
+ }
268
+
269
+ if (matches.length > 1) {
270
+ return {
271
+ provider,
272
+ input,
273
+ matched: false,
274
+ ambiguous: true,
275
+ model: null,
276
+ matches,
277
+ reason: 'ambiguous model alias',
278
+ };
279
+ }
280
+
281
+ return {
282
+ provider,
283
+ input,
284
+ matched: false,
285
+ ambiguous: false,
286
+ model: customModel(provider, input),
287
+ matches: [],
288
+ technicalModel: input,
289
+ displayName: input,
290
+ modelSource: 'custom',
291
+ reason: 'custom model',
292
+ };
293
+ }
294
+
295
+ function resolveKnownModel(providerId, modelValue) {
296
+ const provider = getProviderModelCatalog(providerId);
297
+ return resolveModelFromEntries(provider.id, modelValue, provider.models);
298
+ }
299
+
300
+ function normalizeModelSelection(providerId, modelValue, options = {}) {
301
+ const resolution = resolveKnownModel(providerId, modelValue);
302
+ const fallbackDisplayName = String(options.displayName || '').trim();
303
+
304
+ if (resolution.ambiguous) {
305
+ return {
306
+ ...resolution,
307
+ model: String(modelValue || '').trim(),
308
+ displayName: fallbackDisplayName || String(modelValue || '').trim(),
309
+ validationStatus: options.validationStatus || 'not-tested',
310
+ };
311
+ }
312
+
313
+ return {
314
+ ...resolution,
315
+ model: resolution.technicalModel || String(modelValue || '').trim(),
316
+ displayName: fallbackDisplayName || resolution.displayName || String(modelValue || '').trim(),
317
+ validationStatus: options.validationStatus || 'not-tested',
318
+ };
319
+ }
320
+
321
+ module.exports = {
322
+ MODEL_CATALOG_LAST_UPDATED,
323
+ MODEL_CATALOG_VERSION,
324
+ customModel,
325
+ getKnownModelsForProvider,
326
+ getModelCatalog,
327
+ getProviderModelCatalog,
328
+ listCatalogProviders,
329
+ normalizeModelAliasKey,
330
+ normalizeModelSelection,
331
+ resolveKnownModel,
332
+ resolveModelFromEntries,
333
+ };
@@ -72,9 +72,54 @@ function normalizeProviderModel(model) {
72
72
  return value || '';
73
73
  }
74
74
 
75
+ function resolveProviderModelSelection(providerId, model, options = {}) {
76
+ const rawModel = normalizeProviderModel(model);
77
+ if (!rawModel) {
78
+ return {
79
+ input: '',
80
+ model: '',
81
+ displayName: '',
82
+ modelSource: '',
83
+ aliasNormalized: false,
84
+ };
85
+ }
86
+
87
+ const { normalizeModelSelection } = require('./model-catalog');
88
+ const selection = normalizeModelSelection(providerId, rawModel, {
89
+ displayName: options.displayName,
90
+ });
91
+ const aliasNormalized = selection.modelSource === 'catalog' && selection.model !== rawModel;
92
+ if (aliasNormalized && options.blockModelAlias === true) {
93
+ throw new ProviderRunnerError(
94
+ 'DISPLAY_MODEL_ALIAS',
95
+ `Model '${rawModel}' is a display alias for provider '${providerId}', not the technical id. Use '${selection.model}' or run 'npx create-quiver ai agent repair --dry-run'.`,
96
+ {
97
+ provider: providerId,
98
+ inputModel: rawModel,
99
+ suggestedModel: selection.model,
100
+ displayName: selection.displayName,
101
+ nextSteps: [
102
+ `Use --model ${selection.model}.`,
103
+ 'Run npx create-quiver ai agent repair --dry-run to preview profile normalization.',
104
+ 'Run npx create-quiver ai agent doctor to inspect profile issues.',
105
+ ],
106
+ },
107
+ );
108
+ }
109
+
110
+ return {
111
+ input: rawModel,
112
+ model: selection.model,
113
+ displayName: selection.displayName,
114
+ modelSource: selection.modelSource,
115
+ aliasNormalized,
116
+ };
117
+ }
118
+
75
119
  function buildProviderModelArgs(providerId, model, options = {}) {
76
120
  const provider = getProviderDefinition(providerId);
77
- const normalizedModel = normalizeProviderModel(model);
121
+ const modelResolution = resolveProviderModelSelection(provider.id, model, options);
122
+ const normalizedModel = modelResolution.model;
78
123
  const enforce = options.enforce === true;
79
124
 
80
125
  if (!normalizedModel) {
@@ -114,13 +159,19 @@ function buildProviderModelArgs(providerId, model, options = {}) {
114
159
  }
115
160
 
116
161
  const args = provider.modelArgBuilder(normalizedModel);
117
- return {
162
+ const result = {
118
163
  model: normalizedModel,
119
164
  supported: true,
120
165
  enforced: enforce,
121
166
  args: Array.isArray(args) ? args.map((arg) => String(arg)) : [],
122
- reason: 'model argument supported',
167
+ reason: modelResolution.aliasNormalized ? `model alias normalized from ${modelResolution.input}` : 'model argument supported',
123
168
  };
169
+ if (modelResolution.aliasNormalized) {
170
+ result.input = modelResolution.input;
171
+ result.displayName = modelResolution.displayName;
172
+ result.modelSource = modelResolution.modelSource;
173
+ }
174
+ return result;
124
175
  }
125
176
 
126
177
  function buildProviderInvocation(providerId, options = {}) {
@@ -128,6 +179,8 @@ function buildProviderInvocation(providerId, options = {}) {
128
179
  const extraArgs = Array.isArray(options.args) ? options.args.map((arg) => String(arg)) : [];
129
180
  const modelSelection = buildProviderModelArgs(provider.id, options.model, {
130
181
  enforce: options.enforceModelSelection === true,
182
+ blockModelAlias: options.blockModelAlias === true,
183
+ displayName: options.displayName,
131
184
  });
132
185
  const prompt = String(options.prompt ?? '');
133
186
  const timeoutMs = Number.isFinite(options.timeoutMs) ? Number(options.timeoutMs) : provider.timeoutMs;
@@ -184,6 +237,85 @@ function serializeError(error, provider, invocation) {
184
237
  };
185
238
  }
186
239
 
240
+ function compactProviderText(value) {
241
+ return redactSecrets(String(value || ''))
242
+ .replace(/\bsk-[A-Za-z0-9_-]{16,}\b/g, '[REDACTED]')
243
+ .replace(/\bghp_[A-Za-z0-9_]{16,}\b/g, '[REDACTED]')
244
+ .replace(/\bgithub_pat_[A-Za-z0-9_]{16,}\b/g, '[REDACTED]')
245
+ .split(/\r?\n/)
246
+ .map((line) => line.trim())
247
+ .filter(Boolean)
248
+ .slice(0, 6)
249
+ .join(' ');
250
+ }
251
+
252
+ function extractProviderErrorCause(result = {}) {
253
+ const error = result.error || {};
254
+ const text = compactProviderText([
255
+ error.message,
256
+ result.stderr,
257
+ result.stdout,
258
+ ].filter(Boolean).join('\n'));
259
+ const lower = text.toLowerCase();
260
+
261
+ if (error.code === 'MISSING_PROVIDER_CLI') {
262
+ return {
263
+ code: 'MISSING_PROVIDER_CLI',
264
+ message: error.message || `Provider CLI '${result.command || result.provider}' is not available.`,
265
+ };
266
+ }
267
+
268
+ if (error.code === 'PROVIDER_TIMEOUT') {
269
+ return {
270
+ code: 'PROVIDER_TIMEOUT',
271
+ message: error.message || `Provider '${result.provider}' timed out.`,
272
+ };
273
+ }
274
+
275
+ const quotedUnsupportedModel = text.match(/the ['"`]([^'"`]+)['"`] model is not supported/i);
276
+ const genericInvalidModel = /invalid model|unknown model|model .*not supported|model .*not available|model .*does not exist|unsupported model/i.test(text);
277
+ if (quotedUnsupportedModel || genericInvalidModel) {
278
+ const model = quotedUnsupportedModel ? quotedUnsupportedModel[1] : result.modelSelection?.model;
279
+ return {
280
+ code: 'INVALID_PROVIDER_MODEL',
281
+ message: model
282
+ ? `Provider '${result.provider}' rejected model '${model}'. ${text}`
283
+ : `Provider '${result.provider}' rejected the selected model. ${text}`,
284
+ };
285
+ }
286
+
287
+ if (/unauthorized|forbidden|permission denied|not authenticated|authentication|api key|login required|log in/i.test(text)) {
288
+ return {
289
+ code: 'PROVIDER_AUTH_ERROR',
290
+ message: `Provider '${result.provider}' authentication failed. ${text}`,
291
+ };
292
+ }
293
+
294
+ if (text) {
295
+ return {
296
+ code: 'PROVIDER_RUN_FAILED',
297
+ message: text,
298
+ };
299
+ }
300
+
301
+ return {
302
+ code: 'PROVIDER_RUN_FAILED',
303
+ message: 'provider run failed',
304
+ };
305
+ }
306
+
307
+ function createProviderFailureError(result = {}) {
308
+ const cause = extractProviderErrorCause(result);
309
+ return new ProviderRunnerError(cause.code, cause.message, {
310
+ provider: result.provider,
311
+ command: result.command,
312
+ args: Array.isArray(result.args) ? result.args.slice() : [],
313
+ exitCode: result.exitCode,
314
+ signal: result.signal,
315
+ modelSelection: result.modelSelection,
316
+ });
317
+ }
318
+
187
319
  function runSpawn(command, args, options = {}) {
188
320
  const spawnImpl = options.spawn || spawn;
189
321
 
@@ -352,7 +484,7 @@ async function runProvider(providerId, options = {}) {
352
484
  timeoutMs: invocation.timeoutMs,
353
485
  });
354
486
 
355
- return {
487
+ const providerResult = {
356
488
  ok: execution.ok,
357
489
  dryRun: false,
358
490
  provider: invocation.provider,
@@ -369,6 +501,10 @@ async function runProvider(providerId, options = {}) {
369
501
  error: execution.error,
370
502
  preflight: preflightResult,
371
503
  };
504
+ if (!providerResult.ok && !providerResult.error) {
505
+ providerResult.error = serializeError(createProviderFailureError(providerResult), invocation.provider, invocation);
506
+ }
507
+ return providerResult;
372
508
  } finally {
373
509
  finalizePromptTransport(transport);
374
510
  }
@@ -380,7 +516,10 @@ module.exports = {
380
516
  assertSupportedProvider,
381
517
  buildProviderModelArgs,
382
518
  buildProviderInvocation,
519
+ createProviderFailureError,
520
+ extractProviderErrorCause,
383
521
  formatProviderList,
384
522
  getProviderDefinition,
523
+ resolveProviderModelSelection,
385
524
  runProvider,
386
525
  };
@@ -101,7 +101,60 @@ async function selectOption(message, rawOptions, options = {}) {
101
101
  ].join('\n')));
102
102
  }
103
103
 
104
+ async function promptText(message, options = {}) {
105
+ const explicit = String(options.value || '').trim();
106
+ if (explicit) return explicit;
107
+
108
+ const mode = resolveUxMode({
109
+ interactive: options.interactive === true,
110
+ json: options.json,
111
+ noColor: options.noColor,
112
+ stdinIsTTY: options.stdinIsTTY,
113
+ stdoutIsTTY: options.stdoutIsTTY,
114
+ stderrIsTTY: options.stderrIsTTY,
115
+ }, options.env || process.env, {
116
+ input: options.input || process.stdin,
117
+ output: options.output || process.stdout,
118
+ error: options.error || process.stderr,
119
+ });
120
+
121
+ if (mode.usePrompts) {
122
+ if (typeof options.promptText === 'function') {
123
+ const value = await options.promptText(message, options);
124
+ const normalized = String(value || '').trim();
125
+ if (!normalized && options.required !== false) {
126
+ throw new Error(formatError(`${options.name || 'input'} is required.`));
127
+ }
128
+ return normalized;
129
+ }
130
+ const clack = await loadClack(options.prompts);
131
+ const value = await clack.text({
132
+ message,
133
+ placeholder: options.placeholder,
134
+ initialValue: options.initialValue,
135
+ validate: options.required === false
136
+ ? undefined
137
+ : (input) => (String(input || '').trim() ? undefined : 'Required'),
138
+ });
139
+ if (clack.isCancel && clack.isCancel(value)) {
140
+ throw new Error(formatError(`${options.name || 'input'} was canceled.`));
141
+ }
142
+ return String(value || '').trim();
143
+ }
144
+
145
+ if (options.defaultValue !== undefined && options.defaultValue !== null) {
146
+ return String(options.defaultValue).trim();
147
+ }
148
+
149
+ const flag = options.flag || '--value';
150
+ throw new Error(formatError([
151
+ `${options.name || 'input'} requires an explicit value.`,
152
+ `Use ${flag} <value> or rerun with --interactive.`,
153
+ ].join('\n')));
154
+ }
155
+
104
156
  module.exports = {
105
157
  normalizeSelectorOptions,
158
+ promptText,
106
159
  selectOption,
107
160
  };