flanders 0.8.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.
- package/README.md +22 -6
- package/lib/ai/AiRunner.d.ts +3 -1
- package/lib/ai/AiRunner.js +2 -2
- package/lib/ai/AiSession.d.ts +3 -1
- package/lib/ai/AiSession.js +2 -0
- package/lib/ai/ClaudeAdapter.d.ts +2 -1
- package/lib/ai/ClaudeAdapter.js +7 -3
- package/lib/ai/CodexAdapter.js +9 -80
- package/lib/ai/ToolAdapter.d.ts +7 -3
- package/lib/ai/toolErrorClassification.d.ts +7 -0
- package/lib/ai/toolErrorClassification.js +78 -0
- package/lib/cli.js +8 -2
- package/lib/commands/Implement.d.ts +3 -0
- package/lib/commands/Implement.js +60 -16
- package/lib/commands/Install.d.ts +9 -4
- package/lib/commands/Install.js +250 -105
- package/lib/commands/skillArtifacts.js +40 -36
- package/lib/contexts.d.ts +1 -0
- package/lib/fastMode.d.ts +2 -0
- package/lib/fastMode.js +15 -0
- package/lib/plan/PlanFile.d.ts +3 -1
- package/lib/plan/PlanFile.js +8 -1
- package/lib/prompts/prompts.js +6 -6
- package/lib/prompts/skills.js +13 -7
- package/lib/toolNames.d.ts +1 -0
- package/lib/toolNames.js +4 -0
- package/lib/ui/BottomBlock.d.ts +11 -8
- package/lib/ui/BottomBlock.js +24 -3
- package/lib/ui/PromptHelper.d.ts +7 -0
- package/lib/ui/PromptHelper.js +22 -0
- package/lib/ui/formatters.d.ts +3 -2
- package/lib/ui/formatters.js +7 -2
- package/lib/workspace/FlandersConfig.d.ts +3 -1
- package/lib/workspace/FlandersConfig.js +5 -1
- package/package.json +3 -2
package/lib/commands/Install.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
136
|
-
if (
|
|
137
|
-
return { ok: false, diagnostic:
|
|
138
|
-
answers.
|
|
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,
|
|
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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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,
|
|
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
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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,
|
|
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
|
|
216
|
-
const error =
|
|
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 <
|
|
247
|
-
return { ok: false, diagnostic: `Invalid reviewer flag: "${arg}". --reviewer-N-optional requires 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
|
|
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
|
|
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 =
|
|
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
|
|
345
|
-
if (
|
|
346
|
-
topDefault =
|
|
347
|
-
|
|
348
|
-
preselectedEntryLabel =
|
|
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
|
-
...
|
|
360
|
-
...
|
|
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
|
|
394
|
-
if (
|
|
395
|
-
return
|
|
504
|
+
const crossAlias = crossAliases.find(alias => alias.label === top.label);
|
|
505
|
+
if (crossAlias) {
|
|
506
|
+
return crossAlias.value;
|
|
396
507
|
}
|
|
397
|
-
const
|
|
508
|
+
const group = groups.find(g => g.name === top.label);
|
|
398
509
|
for (;;) {
|
|
399
|
-
const
|
|
400
|
-
|
|
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 ${
|
|
404
|
-
options:
|
|
405
|
-
defaultLabel:
|
|
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 ===
|
|
524
|
+
if (choice.label === MODEL_BACK_LABEL) {
|
|
414
525
|
continue topLevel;
|
|
415
526
|
}
|
|
416
|
-
const entry =
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
559
|
-
if (answers.
|
|
560
|
-
|
|
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
|
|
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 (!
|
|
725
|
+
if (!picked) {
|
|
576
726
|
return 1;
|
|
577
727
|
}
|
|
578
728
|
if (this._disposed) {
|
|
579
729
|
return 1;
|
|
580
730
|
}
|
|
581
|
-
|
|
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
|
-
|
|
592
|
-
|
|
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 = ((
|
|
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
|
|
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
|
};
|