second-advisor 0.1.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/LICENSE +21 -0
- package/README.md +334 -0
- package/package.json +70 -0
- package/src/advisors.ts +205 -0
- package/src/config.ts +118 -0
- package/src/index.ts +86 -0
- package/src/models.ts +87 -0
- package/src/process.ts +47 -0
- package/src/routing.ts +19 -0
- package/src/ui.ts +554 -0
package/src/ui.ts
ADDED
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import {
|
|
5
|
+
cancel,
|
|
6
|
+
intro,
|
|
7
|
+
isCancel,
|
|
8
|
+
log,
|
|
9
|
+
outro,
|
|
10
|
+
select,
|
|
11
|
+
spinner,
|
|
12
|
+
text,
|
|
13
|
+
} from "@clack/prompts";
|
|
14
|
+
import {
|
|
15
|
+
type Advisor,
|
|
16
|
+
advisorChoices,
|
|
17
|
+
ampModes,
|
|
18
|
+
buildAdvisorCommand,
|
|
19
|
+
type Config,
|
|
20
|
+
claudeModels,
|
|
21
|
+
getModelListCommand,
|
|
22
|
+
getThinkingOptions,
|
|
23
|
+
isAdvisor,
|
|
24
|
+
summarizeConfig,
|
|
25
|
+
} from "./advisors.js";
|
|
26
|
+
import {
|
|
27
|
+
configPath,
|
|
28
|
+
parseConfig,
|
|
29
|
+
readConfigIfPresent,
|
|
30
|
+
writeConfig,
|
|
31
|
+
} from "./config.js";
|
|
32
|
+
import { loadModelChoices } from "./models.js";
|
|
33
|
+
import { getVersionArgs, resolveExecutable, runCommand } from "./process.js";
|
|
34
|
+
|
|
35
|
+
type AdvisorDoctorCheck = {
|
|
36
|
+
cli: Advisor;
|
|
37
|
+
installed: boolean;
|
|
38
|
+
path: string;
|
|
39
|
+
version: string;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
type Loader = {
|
|
43
|
+
start: (message: string) => void;
|
|
44
|
+
stop: (message: string) => void;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const secondAdvisorReviewBlock = `## Second Advisor Review
|
|
48
|
+
|
|
49
|
+
After completing substantial work, especially code changes, skill changes, CLI behavior changes, or agent workflow changes, ask for a second opinion before the final response.
|
|
50
|
+
|
|
51
|
+
Use:
|
|
52
|
+
|
|
53
|
+
second-advisor "<review prompt>"
|
|
54
|
+
|
|
55
|
+
The second opinion must be read and considered. Fix valid high-priority issues, then rerun relevant tests. If the second-advisor command hangs or fails, report that clearly instead of blocking forever.
|
|
56
|
+
|
|
57
|
+
Do not run second-advisor for:
|
|
58
|
+
- simple Q&A
|
|
59
|
+
- tiny documentation wording changes
|
|
60
|
+
- status updates
|
|
61
|
+
- tasks where the user explicitly says not to`;
|
|
62
|
+
|
|
63
|
+
const agentInstructionFiles = ["AGENTS.md", "CLAUDE.md"] as const;
|
|
64
|
+
|
|
65
|
+
export async function runWithLoader<T>(
|
|
66
|
+
loader: Loader,
|
|
67
|
+
startMessage: string,
|
|
68
|
+
stopMessage: string,
|
|
69
|
+
work: () => Promise<T>,
|
|
70
|
+
) {
|
|
71
|
+
loader.start(startMessage);
|
|
72
|
+
const result = await work();
|
|
73
|
+
loader.stop(stopMessage);
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function getInstalledAdvisorChoices(
|
|
78
|
+
resolve: (command: string) => string | undefined = resolveExecutable,
|
|
79
|
+
) {
|
|
80
|
+
return advisorChoices.filter((advisor) => resolve(advisor));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function normalizeVersionOutput(stdout: string, stderr: string) {
|
|
84
|
+
return (
|
|
85
|
+
`${stdout}\n${stderr}`
|
|
86
|
+
.split("\n")
|
|
87
|
+
.map((line) => line.trim())
|
|
88
|
+
.find((line) => line.length > 0) || "version unavailable"
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function formatAdvisorDoctorTable(rows: AdvisorDoctorCheck[]) {
|
|
93
|
+
const widths = {
|
|
94
|
+
cli: Math.max("CLI".length, ...rows.map((row) => row.cli.length)),
|
|
95
|
+
installed: "Installed".length,
|
|
96
|
+
version: Math.max(
|
|
97
|
+
"Version".length,
|
|
98
|
+
...rows.map((row) => row.version.length),
|
|
99
|
+
),
|
|
100
|
+
path: Math.max("Path".length, ...rows.map((row) => row.path.length)),
|
|
101
|
+
};
|
|
102
|
+
const headers = {
|
|
103
|
+
cli: "CLI",
|
|
104
|
+
installed: "Installed",
|
|
105
|
+
version: "Version",
|
|
106
|
+
path: "Path",
|
|
107
|
+
};
|
|
108
|
+
const formatRow = (row: {
|
|
109
|
+
cli: string;
|
|
110
|
+
installed: string;
|
|
111
|
+
version: string;
|
|
112
|
+
path: string;
|
|
113
|
+
}) =>
|
|
114
|
+
`│ ${row.cli.padEnd(widths.cli)} │ ${row.installed.padEnd(widths.installed)} │ ${row.version.padEnd(widths.version)} │ ${row.path.padEnd(widths.path)} │`;
|
|
115
|
+
const formatBorder = (left: string, middle: string, right: string) =>
|
|
116
|
+
[
|
|
117
|
+
left,
|
|
118
|
+
"─".repeat(widths.cli + 2),
|
|
119
|
+
middle,
|
|
120
|
+
"─".repeat(widths.installed + 2),
|
|
121
|
+
middle,
|
|
122
|
+
"─".repeat(widths.version + 2),
|
|
123
|
+
middle,
|
|
124
|
+
"─".repeat(widths.path + 2),
|
|
125
|
+
right,
|
|
126
|
+
].join("");
|
|
127
|
+
|
|
128
|
+
return [
|
|
129
|
+
formatBorder("┌", "┬", "┐"),
|
|
130
|
+
formatRow(headers),
|
|
131
|
+
formatBorder("├", "┼", "┤"),
|
|
132
|
+
...rows.map((row) =>
|
|
133
|
+
formatRow({
|
|
134
|
+
cli: row.cli,
|
|
135
|
+
installed: row.installed ? "yes" : "no",
|
|
136
|
+
version: row.version,
|
|
137
|
+
path: row.path,
|
|
138
|
+
}),
|
|
139
|
+
),
|
|
140
|
+
formatBorder("└", "┴", "┘"),
|
|
141
|
+
].join("\n");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function getAdvisorDoctorChecks(): Promise<AdvisorDoctorCheck[]> {
|
|
145
|
+
return Promise.all(
|
|
146
|
+
advisorChoices.map(async (advisor) => {
|
|
147
|
+
const executable = resolveExecutable(advisor);
|
|
148
|
+
if (!executable) {
|
|
149
|
+
return {
|
|
150
|
+
cli: advisor,
|
|
151
|
+
installed: false,
|
|
152
|
+
path: "-",
|
|
153
|
+
version: "not installed",
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const version = await runCommand(advisor, getVersionArgs(), {
|
|
158
|
+
pipeOutput: true,
|
|
159
|
+
});
|
|
160
|
+
return {
|
|
161
|
+
cli: advisor,
|
|
162
|
+
installed: true,
|
|
163
|
+
path: executable,
|
|
164
|
+
version:
|
|
165
|
+
version.exitCode === 0
|
|
166
|
+
? normalizeVersionOutput(version.stdout, version.stderr)
|
|
167
|
+
: "version check failed",
|
|
168
|
+
};
|
|
169
|
+
}),
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export async function runMenu() {
|
|
174
|
+
intro("second-advisor");
|
|
175
|
+
const config = await readConfigIfPresent();
|
|
176
|
+
|
|
177
|
+
if (!config) {
|
|
178
|
+
log.info(`No config found at ${configPath}`);
|
|
179
|
+
const action = await select({
|
|
180
|
+
message: "What would you like to do?",
|
|
181
|
+
options: [
|
|
182
|
+
{ value: "init", label: "Initialize second-advisor" },
|
|
183
|
+
{ value: "exit", label: "Exit" },
|
|
184
|
+
],
|
|
185
|
+
});
|
|
186
|
+
if (isCancel(action) || action === "exit") return endCancelled();
|
|
187
|
+
await runInit();
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
log.message(formatStatus(config, resolveExecutable(config.advisor)));
|
|
192
|
+
const action = await select({
|
|
193
|
+
message: "What would you like to change?",
|
|
194
|
+
options: [
|
|
195
|
+
{ value: "advisor", label: "Change coding CLI" },
|
|
196
|
+
{
|
|
197
|
+
value: "model",
|
|
198
|
+
label: config.advisor === "amp" ? "Change mode" : "Change model",
|
|
199
|
+
},
|
|
200
|
+
{ value: "thinking", label: "Change thinking/effort" },
|
|
201
|
+
{ value: "models", label: "Show available models/modes" },
|
|
202
|
+
{ value: "doctor", label: "Run doctor" },
|
|
203
|
+
{ value: "exit", label: "Exit" },
|
|
204
|
+
],
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
if (isCancel(action) || action === "exit") return endCancelled();
|
|
208
|
+
if (action === "advisor") {
|
|
209
|
+
await writeConfig(await promptForConfig());
|
|
210
|
+
outro("Updated setup.");
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
if (action === "model") {
|
|
214
|
+
await writeConfig(await promptForModel(config));
|
|
215
|
+
outro("Updated setup.");
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
if (action === "thinking") {
|
|
219
|
+
await writeConfig(await promptForThinking(config));
|
|
220
|
+
outro("Updated setup.");
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (action === "models") {
|
|
224
|
+
await runModels(config.advisor);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
await runDoctor();
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export async function runInit() {
|
|
231
|
+
intro("second-advisor init");
|
|
232
|
+
await writeConfig(await promptForConfig());
|
|
233
|
+
outro("second-advisor is configured.");
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export async function runStatus() {
|
|
237
|
+
const config = await readConfigIfPresent();
|
|
238
|
+
if (!config) {
|
|
239
|
+
log.info(`No config found at ${configPath}. Run second-advisor init.`);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
log.message(formatStatus(config, resolveExecutable(config.advisor)));
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export async function runModels(input?: string) {
|
|
246
|
+
const advisor = input
|
|
247
|
+
? parseAdvisorInput(input)
|
|
248
|
+
: (await readConfigIfPresent())?.advisor;
|
|
249
|
+
if (!advisor) {
|
|
250
|
+
log.info(
|
|
251
|
+
"No advisor configured. Run second-advisor init or pass an advisor name.",
|
|
252
|
+
);
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (advisor === "amp") {
|
|
257
|
+
log.message(
|
|
258
|
+
`Available Amp modes:\n${ampModes.map((mode) => `- ${mode}`).join("\n")}`,
|
|
259
|
+
);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
if (advisor === "claude") {
|
|
263
|
+
log.message(
|
|
264
|
+
`Available Claude models:\n${claudeModels.map((model) => `- ${model}`).join("\n")}`,
|
|
265
|
+
);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (!getModelListCommand(advisor)) {
|
|
270
|
+
log.info(
|
|
271
|
+
`${advisor} does not expose a reliable model-list command. Enter the model manually.`,
|
|
272
|
+
);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const choices = await loadModelChoices(advisor);
|
|
277
|
+
if (choices.length === 0) {
|
|
278
|
+
log.error(`Failed to list models with ${advisor}.`);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
log.message(choices.map((choice) => `- ${choice}`).join("\n"));
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export async function runSetup(cwd = process.cwd()) {
|
|
285
|
+
const result = await setupSecondAdvisorReview(cwd);
|
|
286
|
+
if (result.updated.length === 0 && result.skipped.length === 0) {
|
|
287
|
+
console.error("No AGENTS.md or CLAUDE.md found in the current directory.");
|
|
288
|
+
process.exitCode = 1;
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
result.skipped.forEach((file) => {
|
|
293
|
+
log.info(`Already configured: ${file}`);
|
|
294
|
+
});
|
|
295
|
+
result.updated.forEach((file) => {
|
|
296
|
+
log.success(`Updated: ${file}`);
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export async function setupSecondAdvisorReview(cwd = process.cwd()) {
|
|
301
|
+
const files = agentInstructionFiles
|
|
302
|
+
.map((file) => path.join(cwd, file))
|
|
303
|
+
.filter((file) => existsSync(file));
|
|
304
|
+
|
|
305
|
+
const changes = await Promise.all(
|
|
306
|
+
files.map(async (file) =>
|
|
307
|
+
(await setupSecondAdvisorReviewFile(file))
|
|
308
|
+
? { updated: path.basename(file), skipped: undefined }
|
|
309
|
+
: { updated: undefined, skipped: path.basename(file) },
|
|
310
|
+
),
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
return {
|
|
314
|
+
updated: changes
|
|
315
|
+
.map((change) => change.updated)
|
|
316
|
+
.filter((file) => file !== undefined),
|
|
317
|
+
skipped: changes
|
|
318
|
+
.map((change) => change.skipped)
|
|
319
|
+
.filter((file) => file !== undefined),
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export async function runDoctor() {
|
|
324
|
+
intro("second-advisor doctor");
|
|
325
|
+
const advisorChecks = await runWithLoader(
|
|
326
|
+
spinner(),
|
|
327
|
+
"Checking coding CLI versions",
|
|
328
|
+
"Checked coding CLI versions",
|
|
329
|
+
getAdvisorDoctorChecks,
|
|
330
|
+
);
|
|
331
|
+
log.message(
|
|
332
|
+
`Coding CLI versions:\n${formatAdvisorDoctorTable(advisorChecks)}`,
|
|
333
|
+
);
|
|
334
|
+
const config = await readConfigIfPresent();
|
|
335
|
+
|
|
336
|
+
if (!config) {
|
|
337
|
+
log.error(`Config missing: ${configPath}`);
|
|
338
|
+
outro("Run second-advisor init.");
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
log.success(`Config OK: ${summarizeConfig(config)}`);
|
|
343
|
+
const advisorCheck = advisorChecks.find(
|
|
344
|
+
(check) => check.cli === config.advisor,
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
if (!advisorCheck?.installed) {
|
|
348
|
+
log.error(`Executable not found on PATH: ${config.advisor}`);
|
|
349
|
+
outro("Doctor failed.");
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (advisorCheck.version === "version check failed") {
|
|
354
|
+
log.error(`${config.advisor} version check failed.`);
|
|
355
|
+
outro("Doctor failed.");
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (config.advisor === "amp" && !ampModes.includes(config.model)) {
|
|
360
|
+
log.error(`Invalid Amp mode: ${config.model}`);
|
|
361
|
+
outro("Doctor failed.");
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
outro("Doctor passed.");
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
export async function runPrompt(prompt: string, options = { debug: false }) {
|
|
369
|
+
const config = await readConfigIfPresent();
|
|
370
|
+
if (!config) {
|
|
371
|
+
console.error(
|
|
372
|
+
`second-advisor is not initialized. Run: second-advisor init`,
|
|
373
|
+
);
|
|
374
|
+
process.exitCode = 1;
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const advisorCommand = buildAdvisorCommand(config, prompt);
|
|
379
|
+
if (options.debug) {
|
|
380
|
+
console.error(formatCommand(advisorCommand.command, advisorCommand.args));
|
|
381
|
+
}
|
|
382
|
+
const result = await runCommand(advisorCommand.command, advisorCommand.args, {
|
|
383
|
+
pipeOutput: false,
|
|
384
|
+
});
|
|
385
|
+
process.exitCode = result.exitCode;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function formatCommand(command: string, args: string[]) {
|
|
389
|
+
return [command, ...args].map(formatCommandPart).join(" ");
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function formatCommandPart(value: string) {
|
|
393
|
+
if (/^[A-Za-z0-9_/:=.,@%+-]+$/.test(value)) return value;
|
|
394
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
async function setupSecondAdvisorReviewFile(file: string) {
|
|
398
|
+
const content = await readFile(file, "utf8");
|
|
399
|
+
if (content.includes("## Second Advisor Review")) return false;
|
|
400
|
+
await writeFile(file, appendReviewBlock(content));
|
|
401
|
+
return true;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function appendReviewBlock(content: string) {
|
|
405
|
+
const separator =
|
|
406
|
+
content.length === 0
|
|
407
|
+
? ""
|
|
408
|
+
: content.endsWith("\n\n")
|
|
409
|
+
? ""
|
|
410
|
+
: content.endsWith("\n")
|
|
411
|
+
? "\n"
|
|
412
|
+
: "\n\n";
|
|
413
|
+
return `${content}${separator}${secondAdvisorReviewBlock}\n`;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
async function promptForConfig() {
|
|
417
|
+
const installedAdvisors = getInstalledAdvisorChoices();
|
|
418
|
+
if (installedAdvisors.length === 0) {
|
|
419
|
+
log.error("No supported coding CLI found on PATH.");
|
|
420
|
+
log.info(`Supported CLIs: ${advisorChoices.join(", ")}`);
|
|
421
|
+
process.exit(1);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const advisor = await select({
|
|
425
|
+
message: "Choose a coding CLI",
|
|
426
|
+
options: installedAdvisors.map((value) => ({ value, label: value })),
|
|
427
|
+
});
|
|
428
|
+
if (isCancel(advisor)) return endCancelled();
|
|
429
|
+
return promptForAdvisor(advisor);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
async function promptForAdvisor(advisor: Advisor): Promise<Config> {
|
|
433
|
+
const model = await promptModelValue(advisor);
|
|
434
|
+
if (advisor === "kimi") return { advisor, model };
|
|
435
|
+
|
|
436
|
+
const thinking = await promptThinkingValue(advisor, undefined, model);
|
|
437
|
+
return parseConfig({ advisor, model, thinking });
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
async function promptForModel(config: Config): Promise<Config> {
|
|
441
|
+
const model = await promptModelValue(config.advisor, config.model);
|
|
442
|
+
if (config.advisor === "amp") {
|
|
443
|
+
const options = getThinkingOptions("amp", model);
|
|
444
|
+
return parseConfig({
|
|
445
|
+
...config,
|
|
446
|
+
model,
|
|
447
|
+
thinking: options.includes(config.thinking)
|
|
448
|
+
? config.thinking
|
|
449
|
+
: options[0],
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
return parseConfig({ ...config, model });
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
async function promptForThinking(config: Config): Promise<Config> {
|
|
456
|
+
if (config.advisor === "kimi") {
|
|
457
|
+
log.info("Kimi does not expose a thinking/effort setting.");
|
|
458
|
+
return config;
|
|
459
|
+
}
|
|
460
|
+
const thinking = await promptThinkingValue(
|
|
461
|
+
config.advisor,
|
|
462
|
+
config.thinking,
|
|
463
|
+
config.model,
|
|
464
|
+
);
|
|
465
|
+
return parseConfig({ ...config, thinking });
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
async function promptModelValue(advisor: Advisor, initialValue?: string) {
|
|
469
|
+
if (advisor === "amp") {
|
|
470
|
+
const mode = await select({
|
|
471
|
+
message: "Choose an Amp mode",
|
|
472
|
+
initialValue:
|
|
473
|
+
initialValue &&
|
|
474
|
+
ampModes.includes(initialValue as (typeof ampModes)[number])
|
|
475
|
+
? initialValue
|
|
476
|
+
: "smart",
|
|
477
|
+
options: ampModes.map((value) => ({ value, label: value })),
|
|
478
|
+
});
|
|
479
|
+
if (isCancel(mode)) return endCancelled();
|
|
480
|
+
return mode;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const choices = await loadChoicesWithSpinner(advisor);
|
|
484
|
+
if (choices.length > 0) {
|
|
485
|
+
const model = await select({
|
|
486
|
+
message: "Choose a model",
|
|
487
|
+
initialValue,
|
|
488
|
+
options: choices.map((value) => ({ value, label: value })),
|
|
489
|
+
});
|
|
490
|
+
if (isCancel(model)) return endCancelled();
|
|
491
|
+
return model;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
const model = await text({
|
|
495
|
+
message: `Enter ${advisor} model`,
|
|
496
|
+
placeholder: initialValue || "model name",
|
|
497
|
+
initialValue,
|
|
498
|
+
validate: (value) =>
|
|
499
|
+
!value || value.length === 0 ? "Model is required" : undefined,
|
|
500
|
+
});
|
|
501
|
+
if (isCancel(model)) return endCancelled();
|
|
502
|
+
return model;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
async function promptThinkingValue(
|
|
506
|
+
advisor: Exclude<Advisor, "kimi">,
|
|
507
|
+
initialValue?: string,
|
|
508
|
+
model?: string,
|
|
509
|
+
) {
|
|
510
|
+
const options = getThinkingOptions(advisor, model);
|
|
511
|
+
const thinking = await select({
|
|
512
|
+
message: advisor === "amp" ? "Choose effort" : "Choose thinking/effort",
|
|
513
|
+
initialValue:
|
|
514
|
+
initialValue && options.includes(initialValue)
|
|
515
|
+
? initialValue
|
|
516
|
+
: options[0],
|
|
517
|
+
options: options.map((value) => ({ value, label: value })),
|
|
518
|
+
});
|
|
519
|
+
if (isCancel(thinking)) return endCancelled();
|
|
520
|
+
return thinking;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
async function loadChoicesWithSpinner(advisor: Advisor) {
|
|
524
|
+
if (advisor === "claude") return loadModelChoices(advisor);
|
|
525
|
+
if (!getModelListCommand(advisor)) return [];
|
|
526
|
+
|
|
527
|
+
const s = spinner();
|
|
528
|
+
s.start(`Loading ${advisor} models`);
|
|
529
|
+
const choices = await loadModelChoices(advisor);
|
|
530
|
+
s.stop(
|
|
531
|
+
choices.length > 0
|
|
532
|
+
? `Loaded ${advisor} models`
|
|
533
|
+
: `Could not load ${advisor} models`,
|
|
534
|
+
);
|
|
535
|
+
return choices;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
function formatStatus(config: Config, executable?: string) {
|
|
539
|
+
return [
|
|
540
|
+
`Current setup: ${summarizeConfig(config)}`,
|
|
541
|
+
`Config: ${configPath}`,
|
|
542
|
+
`Executable: ${executable || "not found on PATH"}`,
|
|
543
|
+
].join("\n");
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
function parseAdvisorInput(input: string) {
|
|
547
|
+
if (isAdvisor(input)) return input;
|
|
548
|
+
throw new Error(`Unknown advisor: ${input}`);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function endCancelled(): never {
|
|
552
|
+
cancel("Cancelled.");
|
|
553
|
+
process.exit(0);
|
|
554
|
+
}
|