flanders 0.7.0 → 0.9.0

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.
@@ -6,6 +6,8 @@ const FlandersConfig_1 = require("../workspace/FlandersConfig");
6
6
  const PromptHelper_1 = require("../ui/PromptHelper");
7
7
  const skillArtifacts_1 = require("./skillArtifacts");
8
8
  const InstallModelProbe_1 = require("./InstallModelProbe");
9
+ const toolNames_1 = require("../toolNames");
10
+ const fastMode_1 = require("../fastMode");
9
11
  async function promptChoice(ask, args) {
10
12
  try {
11
13
  return await (0, PromptHelper_1.askChoice)(ask, args);
@@ -28,6 +30,17 @@ async function promptText(ask, args) {
28
30
  throw e;
29
31
  }
30
32
  }
33
+ async function promptMultiChoice(ask, args) {
34
+ try {
35
+ return await (0, PromptHelper_1.askMultiChoice)(ask, args);
36
+ }
37
+ catch (e) {
38
+ if (e instanceof Error && e.name === "AbortError") {
39
+ return null;
40
+ }
41
+ throw e;
42
+ }
43
+ }
31
44
  function extractFlagValue(rawArgs, flag) {
32
45
  const prefix = flag + "=";
33
46
  for (const arg of rawArgs) {
@@ -44,10 +57,43 @@ function validateClosedSet(value, allowed, flagName) {
44
57
  return `Invalid value for ${flagName}: "${value}". Allowed values: ${allowed.join(", ")}.\n`;
45
58
  }
46
59
  const CODEX_EFFORT_LEVELS = ["minimal", "low", "medium", "high", "xhigh"];
60
+ function validateEffortForTool(value, tool, flagName) {
61
+ if (value === "") {
62
+ return null;
63
+ }
64
+ if (tool === "codex") {
65
+ return validateClosedSet(value, CODEX_EFFORT_LEVELS, flagName);
66
+ }
67
+ return null;
68
+ }
69
+ function parseSkillsToolList(value) {
70
+ const invalid = {
71
+ ok: false,
72
+ diagnostic: `Invalid value for --skills-tool: "${value}". Expected a comma-separated list of distinct names from: ${toolNames_1.TOOL_NAMES.join(", ")}.\n`
73
+ };
74
+ const seen = new Set();
75
+ const tools = [];
76
+ for (const part of value.split(",")) {
77
+ if (!toolNames_1.TOOL_NAMES.includes(part) || seen.has(part)) {
78
+ return invalid;
79
+ }
80
+ seen.add(part);
81
+ tools.push(part);
82
+ }
83
+ return { ok: true, tools };
84
+ }
85
+ const TOOL_CHOICE_OPTIONS = [
86
+ { label: "claude", description: "Use Claude Code" },
87
+ { label: "codex", description: "Use Codex CLI" }
88
+ ];
89
+ const SKILLS_TOOL_DESTINATIONS = {
90
+ claude: { project: ".claude/skills/", global: "~/.claude/skills/" },
91
+ codex: { project: ".codex/prompts/", global: "~/.codex/prompts/" }
92
+ };
47
93
  const CLAUDE_EFFORT_LEVELS = ["low", "medium", "high", "xhigh", "max"];
48
94
  const CLAUDE_MODEL_FAMILIES = [
49
95
  {
50
- family: "Opus",
96
+ name: "Opus",
51
97
  entries: [
52
98
  { label: "Latest Opus", value: "opus" },
53
99
  { label: "Latest Opus [1m context]", value: "opus[1m]" },
@@ -60,7 +106,7 @@ const CLAUDE_MODEL_FAMILIES = [
60
106
  ]
61
107
  },
62
108
  {
63
- family: "Sonnet",
109
+ name: "Sonnet",
64
110
  entries: [
65
111
  { label: "Latest Sonnet", value: "sonnet" },
66
112
  { label: "Latest Sonnet [1m context]", value: "sonnet[1m]" },
@@ -71,14 +117,14 @@ const CLAUDE_MODEL_FAMILIES = [
71
117
  ]
72
118
  },
73
119
  {
74
- family: "Haiku",
120
+ name: "Haiku",
75
121
  entries: [
76
122
  { label: "Latest Haiku", value: "haiku" },
77
123
  { label: "Haiku 4.5", value: "claude-haiku-4-5-20251001" }
78
124
  ]
79
125
  },
80
126
  {
81
- family: "Fable",
127
+ name: "Fable",
82
128
  entries: [
83
129
  { label: "Latest Fable", value: "fable" },
84
130
  { label: "Fable 5", value: "claude-fable-5" }
@@ -88,9 +134,38 @@ const CLAUDE_MODEL_FAMILIES = [
88
134
  const CLAUDE_CROSS_FAMILY_ALIASES = [
89
135
  { label: "Best (auto-pick)", value: "best" }
90
136
  ];
91
- const CLAUDE_BACK_LABEL = "← back";
137
+ const MODEL_BACK_LABEL = "← back";
92
138
  const REVIEWER_INDEXED_RE = /^--reviewer-(\d+)-(tool|model|effort)=/;
93
139
  const REVIEWER_OPTIONAL_INDEXED_RE = /^--reviewer-(\d+)-optional$/;
140
+ const REVIEWER_FAST_INDEXED_RE = /^--reviewer-(\d+)-fast$/;
141
+ function validateFastFlagsForReviewerCount(fastReviewerIndices, reviewerCount) {
142
+ if (fastReviewerIndices !== undefined) {
143
+ for (const idx of fastReviewerIndices) {
144
+ if (idx > reviewerCount) {
145
+ return `Invalid reviewer flag: --reviewer-${idx}-fast references reviewer ${idx}, beyond the configured reviewer list of ${reviewerCount}.\n`;
146
+ }
147
+ }
148
+ }
149
+ return null;
150
+ }
151
+ function fastFlagEligibilityError(tool, model, flagName) {
152
+ const reason = tool === "claude"
153
+ ? `the model "${model}" does not support Claude Code fast mode`
154
+ : `the ${tool} tool has no fast mode`;
155
+ return `Invalid flag ${flagName}: ${reason}.\n`;
156
+ }
157
+ function knownFastFlagError(tool, model, flagName) {
158
+ if (tool === undefined) {
159
+ return null;
160
+ }
161
+ if (tool !== "claude") {
162
+ return fastFlagEligibilityError(tool, "", flagName);
163
+ }
164
+ if (model === undefined) {
165
+ return null;
166
+ }
167
+ return (0, fastMode_1.modelSupportsFastMode)(model) ? null : fastFlagEligibilityError(tool, model, flagName);
168
+ }
94
169
  function validateWeightedFlagsForReviewerCount(reviewerMinimum, optionalReviewerIndices, reviewerCount) {
95
170
  if (reviewerCount === 1) {
96
171
  if (reviewerMinimum !== undefined) {
@@ -132,14 +207,14 @@ function parseInstallFlags(rawArgs) {
132
207
  answers.scope = "global";
133
208
  const skillsTool = extractFlagValue(rawArgs, "--skills-tool");
134
209
  if (skillsTool !== undefined) {
135
- const error = validateClosedSet(skillsTool, ["claude", "codex", "both"], "--skills-tool");
136
- if (error)
137
- return { ok: false, diagnostic: error };
138
- answers.skillsTool = skillsTool;
210
+ const result = parseSkillsToolList(skillsTool);
211
+ if (!result.ok)
212
+ return { ok: false, diagnostic: result.diagnostic };
213
+ answers.skillsTools = result.tools;
139
214
  }
140
215
  const workerTool = extractFlagValue(rawArgs, "--worker-tool");
141
216
  if (workerTool !== undefined) {
142
- const error = validateClosedSet(workerTool, ["claude", "codex"], "--worker-tool");
217
+ const error = validateClosedSet(workerTool, toolNames_1.TOOL_NAMES, "--worker-tool");
143
218
  if (error)
144
219
  return { ok: false, diagnostic: error };
145
220
  answers.workerTool = workerTool;
@@ -150,11 +225,9 @@ function parseInstallFlags(rawArgs) {
150
225
  }
151
226
  const workerEffort = extractFlagValue(rawArgs, "--worker-effort");
152
227
  if (workerEffort !== undefined) {
153
- if (workerEffort !== "" && answers.workerTool === "codex") {
154
- const error = validateClosedSet(workerEffort, CODEX_EFFORT_LEVELS, "--worker-effort");
155
- if (error)
156
- return { ok: false, diagnostic: error };
157
- }
228
+ const error = validateEffortForTool(workerEffort, answers.workerTool, "--worker-effort");
229
+ if (error)
230
+ return { ok: false, diagnostic: error };
158
231
  answers.workerEffort = workerEffort;
159
232
  }
160
233
  const reviewerIndices = new Map();
@@ -162,7 +235,7 @@ function parseInstallFlags(rawArgs) {
162
235
  const reviewer1Model = extractFlagValue(rawArgs, "--reviewer-model");
163
236
  const reviewer1Effort = extractFlagValue(rawArgs, "--reviewer-effort");
164
237
  if (reviewer1Tool !== undefined) {
165
- const error = validateClosedSet(reviewer1Tool, ["claude", "codex"], "--reviewer-tool");
238
+ const error = validateClosedSet(reviewer1Tool, toolNames_1.TOOL_NAMES, "--reviewer-tool");
166
239
  if (error)
167
240
  return { ok: false, diagnostic: error };
168
241
  if (!reviewerIndices.has(1))
@@ -176,11 +249,9 @@ function parseInstallFlags(rawArgs) {
176
249
  }
177
250
  if (reviewer1Effort !== undefined) {
178
251
  const tool1 = (_a = reviewerIndices.get(1)) === null || _a === void 0 ? void 0 : _a.tool;
179
- if (reviewer1Effort !== "" && tool1 === "codex") {
180
- const error = validateClosedSet(reviewer1Effort, CODEX_EFFORT_LEVELS, "--reviewer-effort");
181
- if (error)
182
- return { ok: false, diagnostic: error };
183
- }
252
+ const error = validateEffortForTool(reviewer1Effort, tool1, "--reviewer-effort");
253
+ if (error)
254
+ return { ok: false, diagnostic: error };
184
255
  if (!reviewerIndices.has(1))
185
256
  reviewerIndices.set(1, {});
186
257
  reviewerIndices.get(1).effort = reviewer1Effort;
@@ -199,7 +270,7 @@ function parseInstallFlags(rawArgs) {
199
270
  reviewerIndices.set(idx, {});
200
271
  const entry = reviewerIndices.get(idx);
201
272
  if (field === "tool") {
202
- const error = validateClosedSet(value, ["claude", "codex"], `--reviewer-${idx}-tool`);
273
+ const error = validateClosedSet(value, toolNames_1.TOOL_NAMES, `--reviewer-${idx}-tool`);
203
274
  if (error)
204
275
  return { ok: false, diagnostic: error };
205
276
  entry.tool = value;
@@ -212,8 +283,8 @@ function parseInstallFlags(rawArgs) {
212
283
  }
213
284
  }
214
285
  for (const [idx, entry] of reviewerIndices) {
215
- if (entry.effort !== undefined && entry.effort !== "" && entry.tool === "codex") {
216
- const error = validateClosedSet(entry.effort, CODEX_EFFORT_LEVELS, `--reviewer-${idx}-effort`);
286
+ if (entry.effort !== undefined) {
287
+ const error = validateEffortForTool(entry.effort, entry.tool, `--reviewer-${idx}-effort`);
217
288
  if (error)
218
289
  return { ok: false, diagnostic: error };
219
290
  }
@@ -243,14 +314,40 @@ function parseInstallFlags(rawArgs) {
243
314
  continue;
244
315
  }
245
316
  const idx = Number(match[1]);
246
- if (idx < 1) {
247
- return { ok: false, diagnostic: `Invalid reviewer flag: "${arg}". --reviewer-N-optional requires N >= 1.\n` };
317
+ if (idx < 2) {
318
+ return { ok: false, diagnostic: `Invalid reviewer flag: "${arg}". Reviewer 1 uses --reviewer-optional; --reviewer-N-optional requires N >= 2.\n` };
248
319
  }
249
320
  optionalIndices.add(idx);
250
321
  }
251
322
  if (optionalIndices.size > 0) {
252
323
  answers.optionalReviewerIndices = [...optionalIndices].sort((a, b) => a - b);
253
324
  }
325
+ if (rawArgs.includes("--worker-fast")) {
326
+ answers.workerFast = true;
327
+ const workerFastError = knownFastFlagError(answers.workerTool, answers.workerModel, "--worker-fast");
328
+ if (workerFastError !== null) {
329
+ return { ok: false, diagnostic: workerFastError };
330
+ }
331
+ }
332
+ const fastIndices = new Set();
333
+ for (const arg of rawArgs) {
334
+ if (arg === "--reviewer-fast") {
335
+ fastIndices.add(1);
336
+ continue;
337
+ }
338
+ const match = arg.match(REVIEWER_FAST_INDEXED_RE);
339
+ if (match === null) {
340
+ continue;
341
+ }
342
+ const idx = Number(match[1]);
343
+ if (idx < 2) {
344
+ return { ok: false, diagnostic: `Invalid reviewer flag: "${arg}". Reviewer 1 uses --reviewer-fast; --reviewer-N-fast requires N >= 2.\n` };
345
+ }
346
+ fastIndices.add(idx);
347
+ }
348
+ if (fastIndices.size > 0) {
349
+ answers.fastReviewerIndices = [...fastIndices].sort((a, b) => a - b);
350
+ }
254
351
  const minimumRaw = extractFlagValue(rawArgs, "--reviewer-minimum");
255
352
  if (minimumRaw !== undefined) {
256
353
  if (!/^\d+$/.test(minimumRaw)) {
@@ -263,6 +360,20 @@ function parseInstallFlags(rawArgs) {
263
360
  if (diagnostic !== null) {
264
361
  return { ok: false, diagnostic };
265
362
  }
363
+ const fastDiagnostic = validateFastFlagsForReviewerCount(answers.fastReviewerIndices, answers.reviewers.length);
364
+ if (fastDiagnostic !== null) {
365
+ return { ok: false, diagnostic: fastDiagnostic };
366
+ }
367
+ if (answers.fastReviewerIndices !== undefined) {
368
+ for (const idx of answers.fastReviewerIndices) {
369
+ const supplied = answers.reviewers[idx - 1];
370
+ const flagName = idx === 1 ? "--reviewer-fast" : `--reviewer-${idx}-fast`;
371
+ const reviewerFastError = knownFastFlagError(supplied.tool, supplied.model, flagName);
372
+ if (reviewerFastError !== null) {
373
+ return { ok: false, diagnostic: reviewerFastError };
374
+ }
375
+ }
376
+ }
266
377
  }
267
378
  return { ok: true, answers };
268
379
  }
@@ -325,27 +436,27 @@ class Install {
325
436
  }
326
437
  return option.label;
327
438
  }
328
- async _resolveClaudeModel(roleLabel, headerLabel, contexts, preselect) {
439
+ async _resolveGroupedModel(roleLabel, headerLabel, groups, crossAliases, contexts, preselect) {
329
440
  const question = `Which model should ${roleLabel} use?`;
330
441
  let topDefault;
331
442
  let customDefault;
332
- let preselectedFamily;
443
+ let preselectedGroup;
333
444
  let preselectedEntryLabel;
334
445
  if (preselect !== undefined) {
335
446
  if (preselect === "") {
336
447
  topDefault = "default configured model";
337
448
  }
338
449
  else {
339
- const alias = CLAUDE_CROSS_FAMILY_ALIASES.find(a => a.value === preselect);
450
+ const alias = crossAliases.find(a => a.value === preselect);
340
451
  if (alias) {
341
452
  topDefault = alias.label;
342
453
  }
343
454
  else {
344
- const family = CLAUDE_MODEL_FAMILIES.find(f => f.entries.some(e => e.value === preselect));
345
- if (family) {
346
- topDefault = family.family;
347
- preselectedFamily = family;
348
- preselectedEntryLabel = family.entries.find(e => e.value === preselect).label;
455
+ const group = groups.find(g => g.entries.some(e => e.value === preselect));
456
+ if (group) {
457
+ topDefault = group.name;
458
+ preselectedGroup = group;
459
+ preselectedEntryLabel = group.entries.find(e => e.value === preselect).label;
349
460
  }
350
461
  else {
351
462
  topDefault = "enter a custom value…";
@@ -356,8 +467,8 @@ class Install {
356
467
  }
357
468
  topLevel: for (;;) {
358
469
  const topOptions = [
359
- ...CLAUDE_MODEL_FAMILIES.map(family => ({ label: family.family })),
360
- ...CLAUDE_CROSS_FAMILY_ALIASES.map(alias => ({ label: alias.label })),
470
+ ...groups.map(group => ({ label: group.name })),
471
+ ...crossAliases.map(alias => ({ label: alias.label })),
361
472
  { label: "default configured model" },
362
473
  { label: "enter a custom value…" }
363
474
  ];
@@ -390,19 +501,19 @@ class Install {
390
501
  }
391
502
  return text;
392
503
  }
393
- const crossFamily = CLAUDE_CROSS_FAMILY_ALIASES.find(alias => alias.label === top.label);
394
- if (crossFamily) {
395
- return crossFamily.value;
504
+ const crossAlias = crossAliases.find(alias => alias.label === top.label);
505
+ if (crossAlias) {
506
+ return crossAlias.value;
396
507
  }
397
- const family = CLAUDE_MODEL_FAMILIES.find(f => f.family === top.label);
508
+ const group = groups.find(g => g.name === top.label);
398
509
  for (;;) {
399
- const familyOptions = family.entries.map(entry => ({ label: entry.label }));
400
- familyOptions.push({ label: CLAUDE_BACK_LABEL });
510
+ const groupOptions = group.entries.map(entry => ({ label: entry.label }));
511
+ groupOptions.push({ label: MODEL_BACK_LABEL });
401
512
  const choice = await promptChoice(contexts.ask, {
402
513
  header: headerLabel,
403
- question: `Which ${family.family} model should ${roleLabel} use?`,
404
- options: familyOptions,
405
- defaultLabel: family === preselectedFamily ? preselectedEntryLabel : undefined
514
+ question: `Which ${group.name} model should ${roleLabel} use?`,
515
+ options: groupOptions,
516
+ defaultLabel: group === preselectedGroup ? preselectedEntryLabel : undefined
406
517
  });
407
518
  if (!choice) {
408
519
  return null;
@@ -410,14 +521,28 @@ class Install {
410
521
  if (this._disposed) {
411
522
  return null;
412
523
  }
413
- if (choice.label === CLAUDE_BACK_LABEL) {
524
+ if (choice.label === MODEL_BACK_LABEL) {
414
525
  continue topLevel;
415
526
  }
416
- const entry = family.entries.find(e => e.label === choice.label);
527
+ const entry = group.entries.find(e => e.label === choice.label);
417
528
  return entry.value;
418
529
  }
419
530
  }
420
531
  }
532
+ async _resolveFreeTextModel(roleLabel, contexts, preselect) {
533
+ const text = await promptText(contexts.ask, {
534
+ question: `Which model should ${roleLabel} use?`,
535
+ placeholder: "leave empty for the default configured model",
536
+ default: preselect
537
+ });
538
+ if (text === null) {
539
+ return null;
540
+ }
541
+ if (this._disposed) {
542
+ return null;
543
+ }
544
+ return text;
545
+ }
421
546
  async _resolveRoleModel(roleLabel, headerLabel, tool, suppliedModel, contexts, preselect) {
422
547
  if (suppliedModel !== undefined) {
423
548
  return suppliedModel;
@@ -426,7 +551,7 @@ class Install {
426
551
  return null;
427
552
  }
428
553
  if (tool === "claude") {
429
- return await this._resolveClaudeModel(roleLabel, headerLabel, contexts, preselect);
554
+ return await this._resolveGroupedModel(roleLabel, headerLabel, CLAUDE_MODEL_FAMILIES, CLAUDE_CROSS_FAMILY_ALIASES, contexts, preselect);
430
555
  }
431
556
  if (!this._modelProbeCache.has(tool)) {
432
557
  const result = await (0, InstallModelProbe_1.probeModelList)(contexts.script);
@@ -465,18 +590,7 @@ class Install {
465
590
  }
466
591
  return option.label === "default configured model" ? "" : option.label;
467
592
  }
468
- const text = await promptText(contexts.ask, {
469
- question: `Which model should ${roleLabel} use?`,
470
- placeholder: "leave empty for the default configured model",
471
- default: preselect
472
- });
473
- if (text === null) {
474
- return null;
475
- }
476
- if (this._disposed) {
477
- return null;
478
- }
479
- return text;
593
+ return await this._resolveFreeTextModel(roleLabel, contexts, preselect);
480
594
  }
481
595
  async _resolveRoleEffort(roleLabel, headerLabel, tool, suppliedEffort, contexts, preselect) {
482
596
  if (suppliedEffort !== undefined) {
@@ -508,7 +622,36 @@ class Install {
508
622
  }
509
623
  return await this._resolveCuratedChoice(headerLabel, `What effort level should ${roleLabel} use?`, CLAUDE_EFFORT_LEVELS, "default configured effort", "enter a custom value…", "leave empty for the default configured effort", contexts, preselect);
510
624
  }
511
- async _resolveReviewer(idx, supplied, contexts, storedReviewer) {
625
+ async _resolveRoleFast(roleLabel, headerLabel, tool, model, fastFlag, flagName, contexts, storedFast) {
626
+ const eligible = tool === "claude" && (0, fastMode_1.modelSupportsFastMode)(model);
627
+ if (fastFlag) {
628
+ if (!eligible) {
629
+ contexts.output.writeError(fastFlagEligibilityError(tool, model, flagName));
630
+ return null;
631
+ }
632
+ return true;
633
+ }
634
+ if (!eligible) {
635
+ return false;
636
+ }
637
+ const option = await promptChoice(contexts.ask, {
638
+ header: headerLabel,
639
+ question: `Should ${roleLabel} run with Claude Code's fast mode enabled, neighbor?`,
640
+ options: [
641
+ { label: "no", description: "Standard mode — fast mode off" },
642
+ { label: "yes", description: "Fast mode — Claude Code's higher-speed, higher-cost configuration" }
643
+ ],
644
+ defaultLabel: storedFast === true ? "yes" : "no"
645
+ });
646
+ if (!option) {
647
+ return null;
648
+ }
649
+ if (this._disposed) {
650
+ return null;
651
+ }
652
+ return option.label === "yes";
653
+ }
654
+ async _resolveReviewer(idx, supplied, fastFlag, contexts, storedReviewer) {
512
655
  const ordinal = idx === 1 ? "" : ` ${idx}`;
513
656
  const roleLabel = `reviewer${ordinal}`;
514
657
  let tool;
@@ -522,10 +665,7 @@ class Install {
522
665
  const option = await promptChoice(contexts.ask, {
523
666
  header: `Reviewer${ordinal} tool`,
524
667
  question: `Which AI tool should ${roleLabel} use?`,
525
- options: [
526
- { label: "claude", description: "Use Claude Code" },
527
- { label: "codex", description: "Use Codex CLI" }
528
- ],
668
+ options: TOOL_CHOICE_OPTIONS,
529
669
  defaultLabel: storedReviewer === null || storedReviewer === void 0 ? void 0 : storedReviewer.tool
530
670
  });
531
671
  if (!option) {
@@ -536,6 +676,13 @@ class Install {
536
676
  }
537
677
  tool = option.label;
538
678
  }
679
+ if ((supplied === null || supplied === void 0 ? void 0 : supplied.effort) !== undefined) {
680
+ const effortError = validateEffortForTool(supplied.effort, tool, idx === 1 ? "--reviewer-effort" : `--reviewer-${idx}-effort`);
681
+ if (effortError !== null) {
682
+ contexts.output.writeError(effortError);
683
+ return null;
684
+ }
685
+ }
539
686
  const model = await this._resolveRoleModel(roleLabel, `Reviewer${ordinal} model`, tool, supplied === null || supplied === void 0 ? void 0 : supplied.model, contexts, storedReviewer === null || storedReviewer === void 0 ? void 0 : storedReviewer.model);
540
687
  if (model === null) {
541
688
  return null;
@@ -544,10 +691,14 @@ class Install {
544
691
  if (effort === null) {
545
692
  return null;
546
693
  }
547
- return { tool, model, effort };
694
+ const fast = await this._resolveRoleFast(roleLabel, `Reviewer${ordinal} fast`, tool, model, fastFlag, idx === 1 ? "--reviewer-fast" : `--reviewer-${idx}-fast`, contexts, storedReviewer === null || storedReviewer === void 0 ? void 0 : storedReviewer.fast);
695
+ if (fast === null) {
696
+ return null;
697
+ }
698
+ return { tool, model, effort, fast };
548
699
  }
549
700
  async _run(rawArgs, options, contexts) {
550
- var _a;
701
+ var _a, _b;
551
702
  try {
552
703
  const parsed = parseInstallFlags(rawArgs);
553
704
  if (!parsed.ok) {
@@ -555,30 +706,29 @@ class Install {
555
706
  return 1;
556
707
  }
557
708
  const answers = parsed.answers;
558
- let skillsTool;
559
- if (answers.skillsTool !== undefined) {
560
- skillsTool = answers.skillsTool;
709
+ let skillsTools;
710
+ if (answers.skillsTools !== undefined) {
711
+ skillsTools = answers.skillsTools;
561
712
  }
562
713
  else {
563
714
  if (this._disposed) {
564
715
  return 1;
565
716
  }
566
- const option = await promptChoice(contexts.ask, {
717
+ const picked = await promptMultiChoice(contexts.ask, {
567
718
  header: "Skills tool",
568
719
  question: "Which AI tool(s) should the skills be installed for, neighbor?",
569
720
  options: [
570
721
  { label: "claude", description: "Install skills for Claude Code" },
571
- { label: "codex", description: "Install skills for Codex CLI" },
572
- { label: "both", description: "Install skills for both Claude Code and Codex CLI" }
722
+ { label: "codex", description: "Install skills for Codex CLI" }
573
723
  ]
574
724
  });
575
- if (!option) {
725
+ if (!picked) {
576
726
  return 1;
577
727
  }
578
728
  if (this._disposed) {
579
729
  return 1;
580
730
  }
581
- skillsTool = option.label;
731
+ skillsTools = picked.map(o => o.label);
582
732
  }
583
733
  let mode;
584
734
  if (answers.scope) {
@@ -588,20 +738,8 @@ class Install {
588
738
  if (this._disposed) {
589
739
  return 1;
590
740
  }
591
- let projectDescription;
592
- let globalDescription;
593
- if (skillsTool === "claude") {
594
- projectDescription = "Install in .claude/skills/ relative to CWD";
595
- globalDescription = "Install in ~/.claude/skills/";
596
- }
597
- else if (skillsTool === "codex") {
598
- projectDescription = "Install in .codex/prompts/ relative to CWD";
599
- globalDescription = "Install in ~/.codex/prompts/";
600
- }
601
- else {
602
- projectDescription = "Install in .claude/skills/ and .codex/prompts/ relative to CWD";
603
- globalDescription = "Install in ~/.claude/skills/ and ~/.codex/prompts/";
604
- }
741
+ const projectDescription = `Install in ${skillsTools.map(t => SKILLS_TOOL_DESTINATIONS[t].project).join(" and ")} relative to CWD`;
742
+ const globalDescription = `Install in ${skillsTools.map(t => SKILLS_TOOL_DESTINATIONS[t].global).join(" and ")}`;
605
743
  const option = await promptChoice(contexts.ask, {
606
744
  header: "Install destination",
607
745
  question: "Where should Flanders skills be installed?",
@@ -637,10 +775,7 @@ class Install {
637
775
  const option = await promptChoice(contexts.ask, {
638
776
  header: "Worker tool, neighborino",
639
777
  question: "Which AI tool should the worker use?",
640
- options: [
641
- { label: "claude", description: "Use Claude Code" },
642
- { label: "codex", description: "Use Codex CLI" }
643
- ],
778
+ options: TOOL_CHOICE_OPTIONS,
644
779
  defaultLabel: storedConfig === null || storedConfig === void 0 ? void 0 : storedConfig.worker.tool
645
780
  });
646
781
  if (!option) {
@@ -651,6 +786,13 @@ class Install {
651
786
  }
652
787
  workerTool = option.label;
653
788
  }
789
+ if (answers.workerEffort !== undefined) {
790
+ const effortError = validateEffortForTool(answers.workerEffort, workerTool, "--worker-effort");
791
+ if (effortError !== null) {
792
+ contexts.output.writeError(effortError);
793
+ return 1;
794
+ }
795
+ }
654
796
  const workerModel = await this._resolveRoleModel("the worker", "Worker model", workerTool, answers.workerModel, contexts, storedConfig === null || storedConfig === void 0 ? void 0 : storedConfig.worker.model);
655
797
  if (workerModel === null) {
656
798
  return 1;
@@ -659,12 +801,17 @@ class Install {
659
801
  if (workerEffort === null) {
660
802
  return 1;
661
803
  }
804
+ const workerFast = await this._resolveRoleFast("the worker", "Worker fast", workerTool, workerModel, answers.workerFast === true, "--worker-fast", contexts, storedConfig === null || storedConfig === void 0 ? void 0 : storedConfig.worker.fast);
805
+ if (workerFast === null) {
806
+ return 1;
807
+ }
662
808
  const suppliedReviewers = answers.reviewers;
809
+ const fastReviewerSet = new Set((_a = answers.fastReviewerIndices) !== null && _a !== void 0 ? _a : []);
663
810
  const storedReviewers = storedConfig === null || storedConfig === void 0 ? void 0 : storedConfig.reviewers;
664
811
  const reviewers = [];
665
812
  if (suppliedReviewers && suppliedReviewers.length > 0) {
666
813
  for (let i = 0; i < suppliedReviewers.length; i++) {
667
- const reviewer = await this._resolveReviewer(i + 1, suppliedReviewers[i], contexts, storedReviewers === null || storedReviewers === void 0 ? void 0 : storedReviewers[i]);
814
+ const reviewer = await this._resolveReviewer(i + 1, suppliedReviewers[i], fastReviewerSet.has(i + 1), contexts, storedReviewers === null || storedReviewers === void 0 ? void 0 : storedReviewers[i]);
668
815
  if (reviewer === null) {
669
816
  return 1;
670
817
  }
@@ -674,7 +821,7 @@ class Install {
674
821
  else {
675
822
  let idx = 1;
676
823
  for (;;) {
677
- const reviewer = await this._resolveReviewer(idx, undefined, contexts, storedReviewers === null || storedReviewers === void 0 ? void 0 : storedReviewers[idx - 1]);
824
+ const reviewer = await this._resolveReviewer(idx, undefined, fastReviewerSet.has(idx), contexts, storedReviewers === null || storedReviewers === void 0 ? void 0 : storedReviewers[idx - 1]);
678
825
  if (reviewer === null) {
679
826
  return 1;
680
827
  }
@@ -712,6 +859,11 @@ class Install {
712
859
  contexts.output.writeError(diagnostic);
713
860
  return 1;
714
861
  }
862
+ const fastDiagnostic = validateFastFlagsForReviewerCount(answers.fastReviewerIndices, reviewers.length);
863
+ if (fastDiagnostic !== null) {
864
+ contexts.output.writeError(fastDiagnostic);
865
+ return 1;
866
+ }
715
867
  }
716
868
  let minimumReviews;
717
869
  const reviewerConfigs = [];
@@ -770,7 +922,7 @@ class Install {
770
922
  const reviewer = reviewers[i];
771
923
  const modelLabel = reviewer.model === "" ? "default configured model" : reviewer.model;
772
924
  const effortLabel = reviewer.effort === "" ? "default configured effort" : reviewer.effort;
773
- const optionalDefault = ((_a = storedReviewers === null || storedReviewers === void 0 ? void 0 : storedReviewers[i]) === null || _a === void 0 ? void 0 : _a.optional) === true ? "yes" : "no";
925
+ const optionalDefault = ((_b = storedReviewers === null || storedReviewers === void 0 ? void 0 : storedReviewers[i]) === null || _b === void 0 ? void 0 : _b.optional) === true ? "yes" : "no";
774
926
  const optionalOption = await promptChoice(contexts.ask, {
775
927
  header: `Reviewer ${i + 1} optional`,
776
928
  question: `Is reviewer ${i + 1} (${reviewer.tool} · ${modelLabel} · ${effortLabel}) optional?`,
@@ -800,14 +952,7 @@ class Install {
800
952
  ? contexts.platform.homedir()
801
953
  : options.projectRoot;
802
954
  const writtenPaths = [];
803
- const tools = [];
804
- if (skillsTool === "claude" || skillsTool === "both") {
805
- tools.push("claude");
806
- }
807
- if (skillsTool === "codex" || skillsTool === "both") {
808
- tools.push("codex");
809
- }
810
- for (const tool of tools) {
955
+ for (const tool of skillsTools) {
811
956
  const result = await (0, skillArtifacts_1.writeSkillArtifacts)(contexts.fs, scopeRoot, tool, () => this._disposed);
812
957
  if (!result.ok) {
813
958
  if (result.diagnostic !== null) {
@@ -821,7 +966,7 @@ class Install {
821
966
  return 1;
822
967
  }
823
968
  const config = {
824
- worker: { tool: workerTool, model: workerModel, effort: workerEffort },
969
+ worker: { tool: workerTool, model: workerModel, effort: workerEffort, fast: workerFast },
825
970
  reviewers: reviewerConfigs,
826
971
  minimumReviews
827
972
  };