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