palabre 0.9.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 +2 -0
- package/dist/adapters/cli-pty.js +30 -10
- package/dist/adapters/cli-shared.js +73 -0
- package/dist/adapters/cli.js +40 -77
- package/dist/adapters/index.js +1 -0
- package/dist/adapters/ollama.js +32 -32
- package/dist/adapters/terminal.js +1 -0
- package/dist/args.js +1 -0
- package/dist/commands/agents.js +91 -0
- package/dist/commands/context.js +31 -0
- package/dist/commands/history.js +33 -0
- package/dist/commands/init.js +64 -0
- package/dist/commands/presets.js +39 -0
- package/dist/commands/shared.js +8 -0
- package/dist/commands/update.js +26 -0
- package/dist/config.js +17 -1
- package/dist/configWizard.js +5 -4
- package/dist/context.js +1 -0
- package/dist/contextScan.js +4 -3
- package/dist/discovery.js +9 -1
- package/dist/doctor.js +1 -0
- package/dist/errors.js +4 -0
- package/dist/exec.js +1 -0
- package/dist/history.js +10 -0
- package/dist/i18n.js +2 -0
- package/dist/index.js +174 -879
- package/dist/limits.js +5 -0
- package/dist/messages/adapter-errors.js +26 -2
- package/dist/messages/config.js +6 -0
- package/dist/messages/index.js +1 -0
- package/dist/messages/renderers.js +10 -2
- package/dist/messages/tui.js +8 -2
- package/dist/new.js +1 -0
- package/dist/ollamaUrl.js +20 -0
- package/dist/orchestrator.js +106 -161
- package/dist/output.js +2 -10
- package/dist/presets.js +1 -0
- package/dist/prompt.js +1 -0
- package/dist/renderers/console.js +2 -8
- package/dist/renderers/ndjson.js +1 -10
- package/dist/renderers/tui-prompts.js +339 -0
- package/dist/renderers/tui-renderer.js +224 -0
- package/dist/renderers/tui-screens.js +352 -0
- package/dist/renderers/tui-theme.js +356 -0
- package/dist/renderers/tui.js +7 -1095
- package/dist/runOptions.js +97 -0
- package/dist/session.js +1 -0
- package/dist/tuiController.js +445 -0
- package/dist/tuiState.js +4 -0
- package/dist/types.js +1 -0
- package/dist/update.js +1 -0
- package/dist/version.js +1 -0
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -1,29 +1,41 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* @file Point d'entrée CLI de Palabre : parsing des arguments, dispatch vers les
|
|
4
|
+
* commandes leaf (`agents`, `presets`, `history`, `context`, `update`, `init`, `config`),
|
|
5
|
+
* résolution de la config/langue, boucle TUI d'accueil et lancement d'un débat ou d'une
|
|
6
|
+
* requête `ask` via l'orchestrateur.
|
|
7
|
+
*/
|
|
8
|
+
import { assertRunnableConfig, configExists, createConfigFromDiscovery, loadConfig, resolveDefaultConfigPath, resolveOutputDir, setOllamaModel, syncDetectedAgentsDetailed, syncOllamaModel, writeConfig } from "./config.js";
|
|
3
9
|
import { loadProjectInputs } from "./context.js";
|
|
4
|
-
import {
|
|
5
|
-
import { discoverLocalTools } from "./discovery.js";
|
|
10
|
+
import { discoverLocalTools, discoverLocalToolsForConfig } from "./discovery.js";
|
|
6
11
|
import { runDoctor } from "./doctor.js";
|
|
7
12
|
import { AdapterError, formatAdapterError } from "./errors.js";
|
|
8
13
|
import { runConfigWizard } from "./configWizard.js";
|
|
9
14
|
import { createTranslator, DEFAULT_LANGUAGE, parseLanguage, resolveLanguage } from "./i18n.js";
|
|
10
|
-
import { DEFAULT_TURNS, parseTurnsFlag, turnsOrDefault
|
|
15
|
+
import { DEFAULT_TURNS, parseTurnsFlag, turnsOrDefault } from "./limits.js";
|
|
11
16
|
import { formatAgentPrompt } from "./prompt.js";
|
|
12
17
|
import { runNewWizard } from "./new.js";
|
|
13
|
-
import {
|
|
18
|
+
import { listPresetNames, resolvePreset } from "./presets.js";
|
|
14
19
|
import { listHistoryEntries } from "./history.js";
|
|
15
20
|
import { createConsoleRenderer } from "./renderers/console.js";
|
|
16
21
|
import { createNdjsonRenderer } from "./renderers/ndjson.js";
|
|
17
|
-
import { createTuiRenderer,
|
|
22
|
+
import { createTuiRenderer, promptTuiHomeTopic, renderTuiHelp, renderTuiHistory, renderTuiHome, renderTuiUpdate } from "./renderers/tui.js";
|
|
18
23
|
import { MAX_ASK_AGENTS, runAsk, runDebate } from "./orchestrator.js";
|
|
19
24
|
import { writeDebateMarkdown } from "./output.js";
|
|
20
|
-
import {
|
|
21
|
-
import { createSessionContext } from "./session.js";
|
|
25
|
+
import { formatUpdateInstructions, getUpdateInfo } from "./update.js";
|
|
22
26
|
import { getStringListFlag, parseArgs } from "./args.js";
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import { configuredOllamaTargets, DEFAULT_OLLAMA_BASE_URL, normalizeOllamaBaseUrl, OllamaUrlError, resolveOllamaBaseUrl } from "./ollamaUrl.js";
|
|
27
|
+
import { clearTuiRunOverrides } from "./tuiState.js";
|
|
28
|
+
import { formatOllamaUrlError, OllamaUrlError } from "./ollamaUrl.js";
|
|
26
29
|
import { compareSemver, getLatestPackageVersion, getPackageVersion } from "./version.js";
|
|
30
|
+
import { runAgentsCommand } from "./commands/agents.js";
|
|
31
|
+
import { runContextCommand } from "./commands/context.js";
|
|
32
|
+
import { runHistoryCommand } from "./commands/history.js";
|
|
33
|
+
import { runInitCommand } from "./commands/init.js";
|
|
34
|
+
import { runPresetsCommand } from "./commands/presets.js";
|
|
35
|
+
import { runUpdateCommand } from "./commands/update.js";
|
|
36
|
+
import { optionalString } from "./commands/shared.js";
|
|
37
|
+
import { runTuiAgentsWizard, runTuiConfigLoop, runTuiRolesWizard, syncInteractiveDetectedAgents } from "./tuiController.js";
|
|
38
|
+
import { parseInterfaceFlag, parseModeFlag, resolveRunOptions } from "./runOptions.js";
|
|
27
39
|
/** Point d'entrée principal du CLI Palabre. Dispatche vers la commande appropriée selon les arguments. */
|
|
28
40
|
async function main() {
|
|
29
41
|
const rawArgs = process.argv.slice(2);
|
|
@@ -65,42 +77,11 @@ async function main() {
|
|
|
65
77
|
return;
|
|
66
78
|
}
|
|
67
79
|
if (parsed.command === "update") {
|
|
68
|
-
|
|
69
|
-
const updateConfigPath = optionalString(parsed.flags.config) ?? await resolveDefaultConfigPath();
|
|
70
|
-
const updateConfig = await configExists(updateConfigPath)
|
|
71
|
-
? await loadConfig(updateConfigPath)
|
|
72
|
-
: undefined;
|
|
73
|
-
const updateLanguage = resolveLanguage({
|
|
74
|
-
explicitLanguage: optionalString(parsed.flags.language),
|
|
75
|
-
configLanguage: updateConfig?.language
|
|
76
|
-
});
|
|
77
|
-
const updateMessages = createTranslator(updateLanguage);
|
|
78
|
-
if (parsed.flags.apply) {
|
|
79
|
-
await applySourceUpdate(info, updateMessages);
|
|
80
|
-
console.log(updateMessages.update.upToDate);
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
console.log(formatUpdateInstructions(info, updateMessages));
|
|
80
|
+
await runUpdateCommand(parsed.flags);
|
|
84
81
|
return;
|
|
85
82
|
}
|
|
86
83
|
if (parsed.command === "init" || parsed.command === "setup") {
|
|
87
|
-
|
|
88
|
-
if (await configExists(initConfigPath)) {
|
|
89
|
-
console.log(startupMessages.init.configExists(initConfigPath));
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
const discovery = await discoverLocalTools({
|
|
93
|
-
ollamaUrl: optionalString(parsed.flags["ollama-url"])
|
|
94
|
-
});
|
|
95
|
-
const config = createConfigFromDiscovery(discovery);
|
|
96
|
-
config.language = resolveLanguage({
|
|
97
|
-
explicitLanguage: optionalString(parsed.flags.language),
|
|
98
|
-
configLanguage: config.language
|
|
99
|
-
});
|
|
100
|
-
const initMessages = createTranslator(config.language);
|
|
101
|
-
await writeExampleConfig(initConfigPath, config);
|
|
102
|
-
console.log(initMessages.init.configCreated(initConfigPath));
|
|
103
|
-
printInitDiscovery(discovery, config, initMessages);
|
|
84
|
+
await runInitCommand(parsed.flags);
|
|
104
85
|
return;
|
|
105
86
|
}
|
|
106
87
|
const configPath = optionalString(parsed.flags.config) ?? await resolveDefaultConfigPath();
|
|
@@ -115,7 +96,7 @@ async function main() {
|
|
|
115
96
|
configLanguage: config.language
|
|
116
97
|
});
|
|
117
98
|
const messages = createTranslator(config.language);
|
|
118
|
-
await
|
|
99
|
+
await writeConfig(configPath, config);
|
|
119
100
|
if (!shouldOpenTuiHome(parsed)) {
|
|
120
101
|
console.log(messages.init.editConfigThenRerun(configPath));
|
|
121
102
|
return;
|
|
@@ -138,78 +119,94 @@ async function main() {
|
|
|
138
119
|
let tuiVersion = "";
|
|
139
120
|
let tuiLatestVersion;
|
|
140
121
|
const handleTuiHomeInput = async (tuiInput) => {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
const nextInput = await promptTuiHomeTopic(tuiMode, messages);
|
|
147
|
-
return handleTuiHomeInput(nextInput);
|
|
148
|
-
}
|
|
149
|
-
if (tuiInput.kind === "history") {
|
|
150
|
-
renderTuiHistory(await listHistoryEntries(resolveOutputDir(config.outputDir)), messages);
|
|
151
|
-
const nextInput = await promptTuiHomeTopic(tuiMode, messages);
|
|
152
|
-
return handleTuiHomeInput(nextInput);
|
|
153
|
-
}
|
|
154
|
-
if (tuiInput.kind === "update") {
|
|
155
|
-
const info = await getUpdateInfo(tuiVersion);
|
|
156
|
-
renderTuiUpdate(formatUpdateInstructions(info, messages), messages);
|
|
157
|
-
const nextInput = await promptTuiHomeTopic(tuiMode, messages);
|
|
158
|
-
return handleTuiHomeInput(nextInput);
|
|
159
|
-
}
|
|
160
|
-
if (tuiInput.kind === "home") {
|
|
161
|
-
return "continue";
|
|
162
|
-
}
|
|
163
|
-
if (tuiInput.kind === "roles") {
|
|
164
|
-
const result = await runTuiRolesWizard(configPath, config, messages, tuiMode, tuiInput.roles);
|
|
165
|
-
if (result.quit)
|
|
166
|
-
return "quit";
|
|
167
|
-
tuiNotice = result.notice;
|
|
168
|
-
return "continue";
|
|
169
|
-
}
|
|
170
|
-
if (tuiInput.kind === "agents") {
|
|
171
|
-
const result = await runTuiAgentsWizard(configPath, config, messages, tuiMode, tuiInput.agents);
|
|
172
|
-
if (result.quit)
|
|
173
|
-
return "quit";
|
|
174
|
-
tuiNotice = result.notice;
|
|
175
|
-
resetTuiRunOverridesOnNextTopic ||= Boolean(result.changedRunDefaults);
|
|
176
|
-
return "continue";
|
|
177
|
-
}
|
|
178
|
-
if (tuiInput.kind === "mode") {
|
|
179
|
-
tuiMode = tuiInput.mode;
|
|
180
|
-
return "continue";
|
|
181
|
-
}
|
|
182
|
-
if (tuiInput.kind === "config") {
|
|
183
|
-
const result = await runTuiConfigLoop(configPath, config, messages, tuiMode);
|
|
184
|
-
if (result.quit)
|
|
122
|
+
let input = tuiInput;
|
|
123
|
+
// Les vues informatives (/help, /history, /update) réaffichent le prompt d'accueil
|
|
124
|
+
// puis reprennent le traitement avec la nouvelle saisie, d'où la boucle.
|
|
125
|
+
for (;;) {
|
|
126
|
+
if (!input) {
|
|
185
127
|
return "quit";
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
128
|
+
}
|
|
129
|
+
if (input.kind === "help") {
|
|
130
|
+
renderTuiHelp(messages);
|
|
131
|
+
input = await promptTuiHomeTopic(tuiMode, messages);
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if (input.kind === "history") {
|
|
135
|
+
renderTuiHistory(await listHistoryEntries(resolveOutputDir(config.outputDir)), messages);
|
|
136
|
+
input = await promptTuiHomeTopic(tuiMode, messages);
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if (input.kind === "update") {
|
|
140
|
+
const info = await getUpdateInfo(tuiVersion);
|
|
141
|
+
renderTuiUpdate(formatUpdateInstructions(info, messages), messages);
|
|
142
|
+
input = await promptTuiHomeTopic(tuiMode, messages);
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if (input.kind === "home") {
|
|
201
146
|
return "continue";
|
|
202
147
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
148
|
+
if (input.kind === "roles") {
|
|
149
|
+
const result = await runTuiRolesWizard(configPath, config, messages, tuiMode, input.roles);
|
|
150
|
+
if (result.quit)
|
|
151
|
+
return "quit";
|
|
152
|
+
tuiNotice = result.notice;
|
|
153
|
+
return "continue";
|
|
154
|
+
}
|
|
155
|
+
if (input.kind === "agents") {
|
|
156
|
+
const result = await runTuiAgentsWizard(configPath, config, messages, tuiMode, input.agents);
|
|
157
|
+
if (result.quit)
|
|
158
|
+
return "quit";
|
|
159
|
+
tuiNotice = result.notice;
|
|
160
|
+
resetTuiRunOverridesOnNextTopic ||= Boolean(result.changedRunDefaults);
|
|
161
|
+
return "continue";
|
|
162
|
+
}
|
|
163
|
+
if (input.kind === "mode") {
|
|
164
|
+
tuiMode = input.mode;
|
|
165
|
+
return "continue";
|
|
166
|
+
}
|
|
167
|
+
if (input.kind === "config") {
|
|
168
|
+
const result = await runTuiConfigLoop(configPath, config, messages, tuiMode);
|
|
169
|
+
if (result.quit)
|
|
170
|
+
return "quit";
|
|
171
|
+
tuiMode = result.mode;
|
|
172
|
+
resetTuiRunOverridesOnNextTopic ||= result.changedRunDefaults;
|
|
173
|
+
language = resolveLanguage({ explicitLanguage: optionalString(parsed.flags.language), configLanguage: config.language });
|
|
174
|
+
messages = createTranslator(language);
|
|
175
|
+
return "continue";
|
|
176
|
+
}
|
|
177
|
+
if (input.kind === "new") {
|
|
178
|
+
parsed.command = "new";
|
|
179
|
+
parsed.commandExplicit = true;
|
|
180
|
+
delete parsed.flags.topic;
|
|
181
|
+
return "run";
|
|
182
|
+
}
|
|
183
|
+
if (input.kind === "retry") {
|
|
184
|
+
if (!optionalString(parsed.flags.topic)) {
|
|
185
|
+
tuiNotice = messages.tui.retryUnavailable;
|
|
186
|
+
return "continue";
|
|
187
|
+
}
|
|
188
|
+
return "retry";
|
|
189
|
+
}
|
|
190
|
+
if (!input.topic) {
|
|
191
|
+
// Saisie réduite à des flags inline (ex. "--context src") : re-prompter au lieu de sortir en erreur.
|
|
192
|
+
tuiNotice = messages.common.topicRequired;
|
|
193
|
+
return "continue";
|
|
194
|
+
}
|
|
195
|
+
parsed.command = "";
|
|
196
|
+
parsed.commandExplicit = false;
|
|
197
|
+
if (hasCompletedTuiSession || resetTuiRunOverridesOnNextTopic) {
|
|
198
|
+
clearTuiRunOverrides(parsed.flags);
|
|
199
|
+
resetTuiRunOverridesOnNextTopic = false;
|
|
200
|
+
}
|
|
201
|
+
parsed.flags.topic = input.topic;
|
|
202
|
+
if (input.files && input.files.length > 0) {
|
|
203
|
+
parsed.flags.files = input.files;
|
|
204
|
+
}
|
|
205
|
+
if (input.context && input.context.length > 0) {
|
|
206
|
+
parsed.flags.context = input.context;
|
|
207
|
+
}
|
|
208
|
+
return "run";
|
|
210
209
|
}
|
|
211
|
-
parsed.flags.topic = tuiInput.topic;
|
|
212
|
-
return "run";
|
|
213
210
|
};
|
|
214
211
|
if (shouldOpenTuiHome(parsed)) {
|
|
215
212
|
const [syncResult, currentVersion, latestVersion] = await Promise.all([
|
|
@@ -283,35 +280,15 @@ async function main() {
|
|
|
283
280
|
if (!topic) {
|
|
284
281
|
throw new Error(messages.common.topicRequired);
|
|
285
282
|
}
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
const agentA = resolveAgentName("agent A", parsed.flags["agent-a"], preset?.agentA, askAgentSeeds[0] ?? config.defaults?.agentA, messages);
|
|
290
|
-
const agentB = resolveAgentName("agent B", parsed.flags["agent-b"], preset?.agentB, askAgentSeeds[1] ?? askAgentSeeds[0] ?? config.defaults?.agentB, messages);
|
|
291
|
-
const askAgents = mode === "ask" ? resolveAskAgents(explicitAskAgents, config.defaults?.askAgents, [agentA, agentB], messages) : undefined;
|
|
292
|
-
const options = {
|
|
293
|
-
mode,
|
|
283
|
+
const options = resolveRunOptions({
|
|
284
|
+
flags: parsed.flags,
|
|
285
|
+
config,
|
|
294
286
|
language,
|
|
295
287
|
topic,
|
|
296
|
-
agentA,
|
|
297
|
-
agentB,
|
|
298
|
-
askAgents,
|
|
299
|
-
turns: parseTurnsFlag(parsed.flags.turns, config.defaults?.turns ?? DEFAULT_TURNS, "--turns", messages),
|
|
300
|
-
session: createSessionContext(),
|
|
301
288
|
files: context.files,
|
|
302
|
-
|
|
303
|
-
modelB: optionalString(parsed.flags["model-b"]),
|
|
304
|
-
ollamaUrl: optionalString(parsed.flags["ollama-url"])
|
|
305
|
-
? normalizeOllamaBaseUrl(optionalString(parsed.flags["ollama-url"]))
|
|
306
|
-
: undefined,
|
|
307
|
-
pullModels: Boolean(parsed.flags["pull-models"]),
|
|
308
|
-
summaryAgent: resolveSummaryAgentOption(parsed.flags["summary-agent"], config.defaults, mode),
|
|
309
|
-
summaryModel: optionalString(parsed.flags["summary-model"]),
|
|
310
|
-
summaryEnabled: !parsed.flags["no-summary"],
|
|
311
|
-
earlyStopOnAgreement: !parsed.flags["no-early-stop"],
|
|
312
|
-
plainOutput: Boolean(parsed.flags.plain || parsed.flags.terminal),
|
|
289
|
+
preset,
|
|
313
290
|
signal: debateAbortSignal()
|
|
314
|
-
};
|
|
291
|
+
}, messages);
|
|
315
292
|
if (parsed.flags["show-prompt"]) {
|
|
316
293
|
printContextWarnings(context.warnings, messages);
|
|
317
294
|
printPromptPreview(config, options, language, messages);
|
|
@@ -332,7 +309,7 @@ async function main() {
|
|
|
332
309
|
if (!stayInTuiAfterSession) {
|
|
333
310
|
return;
|
|
334
311
|
}
|
|
335
|
-
tuiMode = mode;
|
|
312
|
+
tuiMode = options.mode;
|
|
336
313
|
for (;;) {
|
|
337
314
|
const nextInput = await promptTuiHomeTopic(tuiMode, messages, { notice: tuiNotice });
|
|
338
315
|
tuiNotice = undefined;
|
|
@@ -353,6 +330,7 @@ async function main() {
|
|
|
353
330
|
}
|
|
354
331
|
}
|
|
355
332
|
}
|
|
333
|
+
/** Construit un signal d'annulation déclenché sur `SIGINT`/`SIGTERM` pour interrompre un débat en cours. */
|
|
356
334
|
function debateAbortSignal() {
|
|
357
335
|
const controller = new AbortController();
|
|
358
336
|
const abort = () => {
|
|
@@ -364,39 +342,6 @@ function debateAbortSignal() {
|
|
|
364
342
|
process.once("SIGTERM", abort);
|
|
365
343
|
return controller.signal;
|
|
366
344
|
}
|
|
367
|
-
/**
|
|
368
|
-
* Exécute la commande `agents` : charge la config et affiche les agents déclarés avec leur état de détection.
|
|
369
|
-
* @param flags - Flags parsés depuis la ligne de commande.
|
|
370
|
-
*/
|
|
371
|
-
async function runAgentsCommand(flags) {
|
|
372
|
-
const configPath = optionalString(flags.config) ?? await resolveDefaultConfigPath();
|
|
373
|
-
if (!(await configExists(configPath))) {
|
|
374
|
-
const messages = createTranslator(resolveLanguage({ explicitLanguage: optionalString(flags.language) }));
|
|
375
|
-
throw new Error(messages.agents.noConfig);
|
|
376
|
-
}
|
|
377
|
-
const config = await loadConfig(configPath);
|
|
378
|
-
const language = resolveLanguage({
|
|
379
|
-
explicitLanguage: optionalString(flags.language),
|
|
380
|
-
configLanguage: config.language
|
|
381
|
-
});
|
|
382
|
-
const messages = createTranslator(language);
|
|
383
|
-
const discovery = await discoverLocalToolsForConfig(config, optionalString(flags["ollama-url"]));
|
|
384
|
-
if (flags.json) {
|
|
385
|
-
const fallbackAskAgents = [config.defaults?.agentA, config.defaults?.agentB]
|
|
386
|
-
.filter((name) => typeof name === "string" && !isRetiredAgentName(name));
|
|
387
|
-
process.stdout.write(JSON.stringify({
|
|
388
|
-
v: 1,
|
|
389
|
-
agents: listAgentsWithAvailability(config, discovery, messages),
|
|
390
|
-
defaults: {
|
|
391
|
-
askAgents: config.defaults?.askAgents?.length
|
|
392
|
-
? config.defaults.askAgents.filter((name) => !isRetiredAgentName(name))
|
|
393
|
-
: fallbackAskAgents
|
|
394
|
-
}
|
|
395
|
-
}) + "\n");
|
|
396
|
-
return;
|
|
397
|
-
}
|
|
398
|
-
printAgents(configPath, config, discovery, messages);
|
|
399
|
-
}
|
|
400
345
|
/**
|
|
401
346
|
* Exécute la commande `config` : wizard interactif ou mise à jour directe des paramètres par défaut.
|
|
402
347
|
* @param flags - Flags parsés depuis la ligne de commande.
|
|
@@ -406,7 +351,7 @@ async function runConfigCommand(flags) {
|
|
|
406
351
|
const explicitLanguage = optionalString(flags.language);
|
|
407
352
|
if (!(await configExists(configPath))) {
|
|
408
353
|
const messages = createTranslator(resolveLanguage({ explicitLanguage }));
|
|
409
|
-
await
|
|
354
|
+
await writeConfig(configPath);
|
|
410
355
|
console.log(messages.config.createdForConfig(configPath));
|
|
411
356
|
return;
|
|
412
357
|
}
|
|
@@ -417,7 +362,7 @@ async function runConfigCommand(flags) {
|
|
|
417
362
|
});
|
|
418
363
|
const messages = createTranslator(language);
|
|
419
364
|
if (flags["ollama-models"]) {
|
|
420
|
-
await runOllamaModelsCommand(config, Boolean(flags.json));
|
|
365
|
+
await runOllamaModelsCommand(config, Boolean(flags.json), messages);
|
|
421
366
|
return;
|
|
422
367
|
}
|
|
423
368
|
const setOllamaModelValue = optionalString(flags["set-ollama-model"]);
|
|
@@ -436,7 +381,7 @@ async function runConfigCommand(flags) {
|
|
|
436
381
|
console.log(messages.config.syncNoMissing(configPath));
|
|
437
382
|
return;
|
|
438
383
|
}
|
|
439
|
-
await
|
|
384
|
+
await writeConfig(configPath, config);
|
|
440
385
|
console.log(result.addedAgents.length > 0
|
|
441
386
|
? messages.config.syncAdded(configPath, result.addedAgents.join(", "))
|
|
442
387
|
: messages.config.syncRefreshed(configPath));
|
|
@@ -505,395 +450,25 @@ async function runConfigCommand(flags) {
|
|
|
505
450
|
if (changesDefaults) {
|
|
506
451
|
config.defaults = nextDefaults;
|
|
507
452
|
}
|
|
508
|
-
await
|
|
453
|
+
await writeConfig(configPath, config);
|
|
509
454
|
console.log(messages.config.updated(configPath, formatDefaultsForMessage(config.defaults ?? {}, messages), config.language ?? DEFAULT_LANGUAGE));
|
|
510
455
|
return;
|
|
511
456
|
}
|
|
512
457
|
if (flags["clear-defaults"]) {
|
|
513
458
|
delete config.defaults;
|
|
514
|
-
await
|
|
459
|
+
await writeConfig(configPath, config);
|
|
515
460
|
console.log(messages.config.cleared(configPath));
|
|
516
461
|
return;
|
|
517
462
|
}
|
|
518
463
|
await runConfigWizard(configPath, config, messages);
|
|
519
464
|
}
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
notice = undefined;
|
|
528
|
-
const input = await promptTuiConfigCommand(mode, currentMessages);
|
|
529
|
-
if (input.kind === "quit") {
|
|
530
|
-
return { mode, quit: true, changedRunDefaults };
|
|
531
|
-
}
|
|
532
|
-
if (input.kind === "back") {
|
|
533
|
-
return { mode, quit: false, changedRunDefaults };
|
|
534
|
-
}
|
|
535
|
-
if (input.kind === "unknown") {
|
|
536
|
-
notice = input.message;
|
|
537
|
-
continue;
|
|
538
|
-
}
|
|
539
|
-
if (input.kind === "mode") {
|
|
540
|
-
mode = mode === "ask" ? "debate" : "ask";
|
|
541
|
-
config.defaults = { ...(config.defaults ?? {}), mode };
|
|
542
|
-
await writeExampleConfig(configPath, config);
|
|
543
|
-
changedRunDefaults = true;
|
|
544
|
-
notice = mode === "ask" ? currentMessages.tui.askDefaultMode : currentMessages.tui.debateDefaultMode;
|
|
545
|
-
continue;
|
|
546
|
-
}
|
|
547
|
-
if (input.kind === "default-mode") {
|
|
548
|
-
config.defaults = { ...(config.defaults ?? {}), mode };
|
|
549
|
-
await writeExampleConfig(configPath, config);
|
|
550
|
-
changedRunDefaults = true;
|
|
551
|
-
notice = mode === "ask" ? currentMessages.tui.askDefaultMode : currentMessages.tui.debateDefaultMode;
|
|
552
|
-
continue;
|
|
553
|
-
}
|
|
554
|
-
if (input.kind === "interface") {
|
|
555
|
-
config.defaults = { ...(config.defaults ?? {}), interface: input.interfaceName };
|
|
556
|
-
await writeExampleConfig(configPath, config);
|
|
557
|
-
notice = currentMessages.tui.interfaceDefault(input.interfaceName);
|
|
558
|
-
continue;
|
|
559
|
-
}
|
|
560
|
-
if (input.kind === "language") {
|
|
561
|
-
config.language = parseLanguage(input.language, "--language");
|
|
562
|
-
await writeExampleConfig(configPath, config);
|
|
563
|
-
currentMessages = createTranslator(config.language ?? DEFAULT_LANGUAGE);
|
|
564
|
-
notice = currentMessages.tui.languageUpdated(input.language);
|
|
565
|
-
continue;
|
|
566
|
-
}
|
|
567
|
-
if (input.kind === "agents") {
|
|
568
|
-
try {
|
|
569
|
-
const agentsInput = input.agents.length > 0
|
|
570
|
-
? { kind: "agents", agents: input.agents }
|
|
571
|
-
: await promptTuiAgentsWizard(config, mode, currentMessages);
|
|
572
|
-
if (agentsInput.kind === "quit") {
|
|
573
|
-
return { mode, quit: true, changedRunDefaults };
|
|
574
|
-
}
|
|
575
|
-
if (agentsInput.kind === "back" || agentsInput.agents.length === 0) {
|
|
576
|
-
notice = currentMessages.tui.agentsUnchanged;
|
|
577
|
-
continue;
|
|
578
|
-
}
|
|
579
|
-
if (mode === "ask") {
|
|
580
|
-
const agents = normalizeTuiAskAgents(config, agentsInput.agents, currentMessages);
|
|
581
|
-
config.defaults = { ...(config.defaults ?? {}), askAgents: agents };
|
|
582
|
-
await writeExampleConfig(configPath, config);
|
|
583
|
-
changedRunDefaults = true;
|
|
584
|
-
notice = currentMessages.tui.askAgentsUpdated(agents.join(", "));
|
|
585
|
-
}
|
|
586
|
-
else {
|
|
587
|
-
const [agentA, agentB] = normalizeTuiDebateAgents(config, agentsInput.agents, currentMessages);
|
|
588
|
-
config.defaults = { ...(config.defaults ?? {}), agentA, agentB };
|
|
589
|
-
await writeExampleConfig(configPath, config);
|
|
590
|
-
changedRunDefaults = true;
|
|
591
|
-
notice = currentMessages.tui.debateAgentsUpdated(`${agentA} <-> ${agentB}`);
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
catch (error) {
|
|
595
|
-
notice = error instanceof Error ? error.message : String(error);
|
|
596
|
-
}
|
|
597
|
-
continue;
|
|
598
|
-
}
|
|
599
|
-
if (input.kind === "roles") {
|
|
600
|
-
try {
|
|
601
|
-
const rolesInput = input.roles.length > 0
|
|
602
|
-
? { kind: "roles", roles: input.roles }
|
|
603
|
-
: await promptTuiRolesWizard(config, mode, currentMessages);
|
|
604
|
-
if (rolesInput.kind === "quit") {
|
|
605
|
-
return { mode, quit: true, changedRunDefaults };
|
|
606
|
-
}
|
|
607
|
-
if (rolesInput.kind === "back" || rolesInput.roles.length === 0) {
|
|
608
|
-
notice = currentMessages.tui.rolesUnchanged;
|
|
609
|
-
continue;
|
|
610
|
-
}
|
|
611
|
-
notice = applyTuiRoles(config, mode, rolesInput.roles, currentMessages);
|
|
612
|
-
await writeExampleConfig(configPath, config);
|
|
613
|
-
}
|
|
614
|
-
catch (error) {
|
|
615
|
-
notice = error instanceof Error ? error.message : String(error);
|
|
616
|
-
}
|
|
617
|
-
continue;
|
|
618
|
-
}
|
|
619
|
-
if (input.kind === "turns") {
|
|
620
|
-
if (mode === "ask") {
|
|
621
|
-
notice = currentMessages.tui.askTurnsNotice;
|
|
622
|
-
continue;
|
|
623
|
-
}
|
|
624
|
-
try {
|
|
625
|
-
validateTurns(input.turns, "--turns", currentMessages);
|
|
626
|
-
config.defaults = { ...(config.defaults ?? {}), turns: input.turns };
|
|
627
|
-
await writeExampleConfig(configPath, config);
|
|
628
|
-
changedRunDefaults = true;
|
|
629
|
-
notice = currentMessages.tui.turnsUpdated(input.turns);
|
|
630
|
-
}
|
|
631
|
-
catch (error) {
|
|
632
|
-
notice = error instanceof Error ? error.message : String(error);
|
|
633
|
-
}
|
|
634
|
-
continue;
|
|
635
|
-
}
|
|
636
|
-
if (input.kind === "summary") {
|
|
637
|
-
try {
|
|
638
|
-
const nextDefaults = { ...(config.defaults ?? {}) };
|
|
639
|
-
if (input.agent !== undefined) {
|
|
640
|
-
assertKnownAgent(config, input.agent, mode === "ask" ? "defaults.askSummaryAgent" : "defaults.summaryAgent", currentMessages);
|
|
641
|
-
}
|
|
642
|
-
if (mode === "ask") {
|
|
643
|
-
if (input.agent === undefined) {
|
|
644
|
-
delete nextDefaults.askSummaryAgent;
|
|
645
|
-
notice = currentMessages.tui.askSummaryFallback;
|
|
646
|
-
}
|
|
647
|
-
else {
|
|
648
|
-
nextDefaults.askSummaryAgent = input.agent;
|
|
649
|
-
notice = currentMessages.tui.askSummaryAgent(input.agent);
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
else if (input.agent === undefined) {
|
|
653
|
-
delete nextDefaults.summaryAgent;
|
|
654
|
-
notice = currentMessages.tui.debateSummaryFallback;
|
|
655
|
-
}
|
|
656
|
-
else {
|
|
657
|
-
nextDefaults.summaryAgent = input.agent;
|
|
658
|
-
notice = currentMessages.tui.debateSummaryAgent(input.agent);
|
|
659
|
-
}
|
|
660
|
-
config.defaults = nextDefaults;
|
|
661
|
-
await writeExampleConfig(configPath, config);
|
|
662
|
-
changedRunDefaults = true;
|
|
663
|
-
}
|
|
664
|
-
catch (error) {
|
|
665
|
-
notice = error instanceof Error ? error.message : String(error);
|
|
666
|
-
}
|
|
667
|
-
continue;
|
|
668
|
-
}
|
|
669
|
-
if (input.kind === "ollama-info") {
|
|
670
|
-
try {
|
|
671
|
-
notice = await formatTuiOllamaInfo(config, currentMessages);
|
|
672
|
-
}
|
|
673
|
-
catch (error) {
|
|
674
|
-
notice = error instanceof Error ? error.message : String(error);
|
|
675
|
-
}
|
|
676
|
-
continue;
|
|
677
|
-
}
|
|
678
|
-
if (input.kind === "ollama-url") {
|
|
679
|
-
try {
|
|
680
|
-
notice = await setTuiOllamaUrl(configPath, config, input.url, currentMessages);
|
|
681
|
-
changedRunDefaults = true;
|
|
682
|
-
}
|
|
683
|
-
catch (error) {
|
|
684
|
-
notice = formatRuntimeError(error, currentMessages);
|
|
685
|
-
}
|
|
686
|
-
continue;
|
|
687
|
-
}
|
|
688
|
-
if (input.kind === "ollama-model") {
|
|
689
|
-
try {
|
|
690
|
-
notice = await setTuiOllamaModel(configPath, config, input.model, currentMessages);
|
|
691
|
-
changedRunDefaults = true;
|
|
692
|
-
}
|
|
693
|
-
catch (error) {
|
|
694
|
-
notice = error instanceof Error ? error.message : String(error);
|
|
695
|
-
}
|
|
696
|
-
continue;
|
|
697
|
-
}
|
|
698
|
-
if (input.kind === "ollama-sync") {
|
|
699
|
-
try {
|
|
700
|
-
notice = await syncTuiOllamaModel(configPath, config, currentMessages);
|
|
701
|
-
changedRunDefaults = true;
|
|
702
|
-
}
|
|
703
|
-
catch (error) {
|
|
704
|
-
notice = error instanceof Error ? error.message : String(error);
|
|
705
|
-
}
|
|
706
|
-
continue;
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
async function setTuiOllamaUrl(configPath, config, value, messages) {
|
|
711
|
-
if (!Object.values(config.agents).some((agent) => agent.type === "ollama")) {
|
|
712
|
-
throw new Error(messages.config.ollamaModelNoAgent);
|
|
713
|
-
}
|
|
714
|
-
const normalized = isDefaultOllamaUrl(value)
|
|
715
|
-
? DEFAULT_OLLAMA_BASE_URL
|
|
716
|
-
: normalizeOllamaBaseUrl(value);
|
|
717
|
-
const effective = resolveOllamaBaseUrl({ configUrl: normalized });
|
|
718
|
-
setOllamaBaseUrl(config, normalized);
|
|
719
|
-
await writeExampleConfig(configPath, config);
|
|
720
|
-
return messages.tui.ollamaUrlUpdated(normalized, effective);
|
|
721
|
-
}
|
|
722
|
-
function isDefaultOllamaUrl(value) {
|
|
723
|
-
return ["default", "defaut", "défaut", "local", "localhost"].includes(value.trim().toLowerCase());
|
|
724
|
-
}
|
|
725
|
-
async function formatTuiOllamaInfo(config, messages) {
|
|
726
|
-
const discovery = await discoverLocalToolsForConfig(config);
|
|
727
|
-
const agent = config.agents["ollama-local"];
|
|
728
|
-
if (agent?.type !== "ollama") {
|
|
729
|
-
throw new Error(messages.config.ollamaModelNoAgent);
|
|
730
|
-
}
|
|
731
|
-
if (!discovery.ollama.available) {
|
|
732
|
-
return messages.tui.ollamaUnavailable(discovery.ollama.baseUrl);
|
|
733
|
-
}
|
|
734
|
-
const installed = discovery.ollama.models.length > 0
|
|
735
|
-
? discovery.ollama.models.join(", ")
|
|
736
|
-
: messages.config.ollamaModelNoInstalledModels;
|
|
737
|
-
const api = `${discovery.ollama.baseUrl}`;
|
|
738
|
-
return messages.tui.ollamaInfo(agent.model, installed, api);
|
|
739
|
-
}
|
|
740
|
-
async function setTuiOllamaModel(configPath, config, model, messages) {
|
|
741
|
-
const trimmed = model.trim();
|
|
742
|
-
if (!trimmed) {
|
|
743
|
-
throw new Error(messages.tui.ollamaModelUsage);
|
|
744
|
-
}
|
|
745
|
-
const discovery = await discoverLocalToolsForConfig(config);
|
|
746
|
-
const agent = config.agents["ollama-local"];
|
|
747
|
-
if (agent?.type !== "ollama") {
|
|
748
|
-
throw new Error(messages.config.ollamaModelNoAgent);
|
|
749
|
-
}
|
|
750
|
-
if (!discovery.ollama.models.includes(trimmed)) {
|
|
751
|
-
throw new Error(messages.config.ollamaModelUnavailable(trimmed));
|
|
752
|
-
}
|
|
753
|
-
const result = setOllamaModel(config, trimmed);
|
|
754
|
-
await writeExampleConfig(configPath, config);
|
|
755
|
-
return result
|
|
756
|
-
? messages.config.ollamaModelUpdated(configPath, result.previousModel, result.nextModel)
|
|
757
|
-
: messages.config.ollamaModelNoChange(configPath, agent.model);
|
|
758
|
-
}
|
|
759
|
-
async function syncTuiOllamaModel(configPath, config, messages) {
|
|
760
|
-
const discovery = await discoverLocalToolsForConfig(config);
|
|
761
|
-
const agent = config.agents["ollama-local"];
|
|
762
|
-
if (agent?.type !== "ollama") {
|
|
763
|
-
throw new Error(messages.config.ollamaModelNoAgent);
|
|
764
|
-
}
|
|
765
|
-
if (discovery.ollama.models.length === 0) {
|
|
766
|
-
throw new Error(messages.config.ollamaModelNoInstalledModels);
|
|
767
|
-
}
|
|
768
|
-
const result = syncOllamaModel(config, discovery);
|
|
769
|
-
if (!result) {
|
|
770
|
-
return messages.config.ollamaModelNoChange(configPath, agent.model);
|
|
771
|
-
}
|
|
772
|
-
await writeExampleConfig(configPath, config);
|
|
773
|
-
return messages.config.ollamaModelUpdated(configPath, result.previousModel, result.nextModel);
|
|
774
|
-
}
|
|
775
|
-
async function syncInteractiveDetectedAgents(configPath, config) {
|
|
776
|
-
const discovery = await discoverLocalToolsForConfig(config);
|
|
777
|
-
const result = syncDetectedAgentsDetailed(config, discovery);
|
|
778
|
-
if (result.changed) {
|
|
779
|
-
await writeExampleConfig(configPath, config);
|
|
780
|
-
}
|
|
781
|
-
return {
|
|
782
|
-
addedAgents: result.addedAgents
|
|
783
|
-
};
|
|
784
|
-
}
|
|
785
|
-
function normalizeTuiDebateAgents(config, agents, messages) {
|
|
786
|
-
const unique = agents.map((agent) => agent.trim()).filter((agent, index, list) => agent && list.indexOf(agent) === index);
|
|
787
|
-
if (unique.length !== 2) {
|
|
788
|
-
throw new Error(messages.tui.debateAgentsUsage);
|
|
789
|
-
}
|
|
790
|
-
assertKnownAgent(config, unique[0], "defaults.agentA", messages);
|
|
791
|
-
assertKnownAgent(config, unique[1], "defaults.agentB", messages);
|
|
792
|
-
return [unique[0], unique[1]];
|
|
793
|
-
}
|
|
794
|
-
function normalizeTuiAskAgents(config, agents, messages) {
|
|
795
|
-
const unique = agents.map((agent) => agent.trim()).filter((agent, index, list) => agent && list.indexOf(agent) === index);
|
|
796
|
-
if (unique.length === 0) {
|
|
797
|
-
throw new Error(messages.tui.askAgentsUsage);
|
|
798
|
-
}
|
|
799
|
-
if (unique.length > MAX_ASK_AGENTS) {
|
|
800
|
-
throw new Error(messages.common.tooManyAskAgents(MAX_ASK_AGENTS));
|
|
801
|
-
}
|
|
802
|
-
unique.forEach((agent) => assertKnownAgent(config, agent, "defaults.askAgents", messages));
|
|
803
|
-
return unique;
|
|
804
|
-
}
|
|
805
|
-
async function runTuiAgentsWizard(configPath, config, messages, mode, inlineAgents = []) {
|
|
806
|
-
try {
|
|
807
|
-
const agentsInput = inlineAgents.length > 0
|
|
808
|
-
? { kind: "agents", agents: inlineAgents }
|
|
809
|
-
: await promptTuiAgentsWizard(config, mode, messages);
|
|
810
|
-
if (agentsInput.kind === "quit") {
|
|
811
|
-
return { quit: true, changedRunDefaults: false };
|
|
812
|
-
}
|
|
813
|
-
if (agentsInput.kind === "back" || agentsInput.agents.length === 0) {
|
|
814
|
-
return { quit: false, changedRunDefaults: false };
|
|
815
|
-
}
|
|
816
|
-
const notice = applyTuiAgents(config, mode, agentsInput.agents, messages);
|
|
817
|
-
await writeExampleConfig(configPath, config);
|
|
818
|
-
return { notice, quit: false, changedRunDefaults: true };
|
|
819
|
-
}
|
|
820
|
-
catch (error) {
|
|
821
|
-
return { notice: messages.tui.agentsError(error instanceof Error ? error.message : String(error)), quit: false, changedRunDefaults: false };
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
async function runTuiRolesWizard(configPath, config, messages, mode, inlineRoles = []) {
|
|
825
|
-
try {
|
|
826
|
-
const rolesInput = inlineRoles.length > 0
|
|
827
|
-
? { kind: "roles", roles: inlineRoles }
|
|
828
|
-
: await promptTuiRolesWizard(config, mode, messages);
|
|
829
|
-
if (rolesInput.kind === "quit") {
|
|
830
|
-
return { quit: true };
|
|
831
|
-
}
|
|
832
|
-
if (rolesInput.kind === "back" || rolesInput.roles.length === 0) {
|
|
833
|
-
return { quit: false };
|
|
834
|
-
}
|
|
835
|
-
const notice = applyTuiRoles(config, mode, rolesInput.roles, messages);
|
|
836
|
-
await writeExampleConfig(configPath, config);
|
|
837
|
-
return { notice, quit: false };
|
|
838
|
-
}
|
|
839
|
-
catch (error) {
|
|
840
|
-
return { notice: messages.tui.rolesError(error instanceof Error ? error.message : String(error)), quit: false };
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
|
-
function applyTuiAgents(config, mode, agentNames, messages) {
|
|
844
|
-
if (mode === "ask") {
|
|
845
|
-
const agents = normalizeTuiAskAgents(config, agentNames, messages);
|
|
846
|
-
config.defaults = { ...(config.defaults ?? {}), askAgents: agents };
|
|
847
|
-
return messages.tui.askAgentsUpdated(agents.join(", "));
|
|
848
|
-
}
|
|
849
|
-
const [agentA, agentB] = normalizeTuiDebateAgents(config, agentNames, messages);
|
|
850
|
-
config.defaults = { ...(config.defaults ?? {}), agentA, agentB };
|
|
851
|
-
return messages.tui.debateAgentsUpdated(`${agentA} <-> ${agentB}`);
|
|
852
|
-
}
|
|
853
|
-
function applyTuiRoles(config, mode, roleNames, messages) {
|
|
854
|
-
const agents = activeAgentsForMode(config, mode);
|
|
855
|
-
if (agents.length === 0) {
|
|
856
|
-
throw new Error(mode === "ask" ? messages.tui.noAskAgentsConfigured : messages.tui.noDebateAgentsConfigured);
|
|
857
|
-
}
|
|
858
|
-
const roles = normalizeTuiRoles(roleNames, agents, mode, messages);
|
|
859
|
-
agents.forEach((agent, index) => {
|
|
860
|
-
config.agents[agent].role = roles[index];
|
|
861
|
-
});
|
|
862
|
-
return mode === "ask"
|
|
863
|
-
? messages.tui.askRolesUpdated(roles.join(", "))
|
|
864
|
-
: messages.tui.debateRolesUpdated(roles.join(" <-> "));
|
|
865
|
-
}
|
|
866
|
-
function activeAgentsForMode(config, mode) {
|
|
867
|
-
const defaults = config.defaults ?? {};
|
|
868
|
-
if (mode === "ask") {
|
|
869
|
-
if (defaults.askAgents && defaults.askAgents.length > 0) {
|
|
870
|
-
return defaults.askAgents.filter((agent) => Boolean(config.agents[agent]));
|
|
871
|
-
}
|
|
872
|
-
return [defaults.agentA, defaults.agentB].filter((agent) => Boolean(agent && config.agents[agent]));
|
|
873
|
-
}
|
|
874
|
-
return [defaults.agentA, defaults.agentB].filter((agent) => Boolean(agent && config.agents[agent]));
|
|
875
|
-
}
|
|
876
|
-
function normalizeTuiRoles(roleNames, agents, mode, messages) {
|
|
877
|
-
const roles = roleNames.map((role) => role.trim().toLowerCase()).filter(Boolean);
|
|
878
|
-
const expectedCount = agents.length;
|
|
879
|
-
if (roles.length < expectedCount) {
|
|
880
|
-
const agentLabel = mode === "ask"
|
|
881
|
-
? agents.join(", ")
|
|
882
|
-
: agents.join(" <-> ");
|
|
883
|
-
throw new Error(messages.tui.rolesCountError(roles.length, expectedCount, agentLabel));
|
|
884
|
-
}
|
|
885
|
-
return roles.slice(0, expectedCount).map((role) => {
|
|
886
|
-
if (isAgentRole(role)) {
|
|
887
|
-
return role;
|
|
888
|
-
}
|
|
889
|
-
throw new Error(messages.tui.unknownRole(role, VALID_AGENT_ROLES.join(", ")));
|
|
890
|
-
});
|
|
891
|
-
}
|
|
892
|
-
function isAgentRole(value) {
|
|
893
|
-
return VALID_AGENT_ROLES.includes(value);
|
|
894
|
-
}
|
|
895
|
-
const VALID_AGENT_ROLES = ["implementer", "reviewer", "architect", "scout", "critic", "summarizer"];
|
|
896
|
-
async function runOllamaModelsCommand(config, json) {
|
|
465
|
+
/**
|
|
466
|
+
* Affiche l'état de l'agent Ollama local (modèle courant, disponibilité de l'API, modèles installés).
|
|
467
|
+
* @param config - Config chargée.
|
|
468
|
+
* @param json - Si `true`, affiche le résultat en JSON plutôt qu'en texte lisible.
|
|
469
|
+
* @param messages - Dictionnaire localisé pour la sortie texte.
|
|
470
|
+
*/
|
|
471
|
+
async function runOllamaModelsCommand(config, json, messages) {
|
|
897
472
|
const discovery = await discoverLocalToolsForConfig(config);
|
|
898
473
|
const agent = config.agents["ollama-local"];
|
|
899
474
|
const currentModel = agent?.type === "ollama" ? agent.model : null;
|
|
@@ -910,10 +485,16 @@ async function runOllamaModelsCommand(config, json) {
|
|
|
910
485
|
console.log(JSON.stringify(payload, null, 2));
|
|
911
486
|
return;
|
|
912
487
|
}
|
|
913
|
-
console.log(
|
|
914
|
-
console.log(
|
|
915
|
-
console.log(
|
|
488
|
+
console.log(messages.config.ollamaModelsCurrent(currentModel));
|
|
489
|
+
console.log(messages.config.ollamaModelsApi(discovery.ollama.available, discovery.ollama.baseUrl));
|
|
490
|
+
console.log(messages.config.ollamaModelsInstalled(discovery.ollama.models));
|
|
916
491
|
}
|
|
492
|
+
/**
|
|
493
|
+
* Change le modèle configuré pour l'agent `ollama-local` après vérification qu'il est bien installé.
|
|
494
|
+
* @param configPath - Chemin du fichier de config à mettre à jour.
|
|
495
|
+
* @param config - Config chargée.
|
|
496
|
+
* @param model - Nom du modèle Ollama à définir.
|
|
497
|
+
*/
|
|
917
498
|
async function runSetOllamaModelCommand(configPath, config, model, messages) {
|
|
918
499
|
const trimmed = model.trim();
|
|
919
500
|
if (!trimmed) {
|
|
@@ -928,11 +509,16 @@ async function runSetOllamaModelCommand(configPath, config, model, messages) {
|
|
|
928
509
|
throw new Error(messages.config.ollamaModelUnavailable(trimmed));
|
|
929
510
|
}
|
|
930
511
|
const result = setOllamaModel(config, trimmed);
|
|
931
|
-
await
|
|
512
|
+
await writeConfig(configPath, config);
|
|
932
513
|
console.log(result
|
|
933
514
|
? messages.config.ollamaModelUpdated(configPath, result.previousModel, result.nextModel)
|
|
934
515
|
: messages.config.ollamaModelNoChange(configPath, agent.model));
|
|
935
516
|
}
|
|
517
|
+
/**
|
|
518
|
+
* Aligne automatiquement le modèle de l'agent `ollama-local` sur un modèle réellement installé.
|
|
519
|
+
* @param configPath - Chemin du fichier de config à mettre à jour.
|
|
520
|
+
* @param config - Config chargée.
|
|
521
|
+
*/
|
|
936
522
|
async function runSyncOllamaModelCommand(configPath, config, messages) {
|
|
937
523
|
const discovery = await discoverLocalToolsForConfig(config);
|
|
938
524
|
const agent = config.agents["ollama-local"];
|
|
@@ -947,7 +533,7 @@ async function runSyncOllamaModelCommand(configPath, config, messages) {
|
|
|
947
533
|
console.log(messages.config.ollamaModelNoChange(configPath, agent.model));
|
|
948
534
|
return;
|
|
949
535
|
}
|
|
950
|
-
await
|
|
536
|
+
await writeConfig(configPath, config);
|
|
951
537
|
console.log(messages.config.ollamaModelUpdated(configPath, result.previousModel, result.nextModel));
|
|
952
538
|
}
|
|
953
539
|
/**
|
|
@@ -965,6 +551,11 @@ function isNoneValue(value) {
|
|
|
965
551
|
function formatDefaultsForMessage(defaults, messages) {
|
|
966
552
|
return messages.config.defaultsSummary(defaults.agentA, defaults.agentB, turnsOrDefault(defaults.turns), defaults.summaryAgent, defaults.askSummaryAgent, defaults.mode, defaults.askAgents, defaults.interface);
|
|
967
553
|
}
|
|
554
|
+
/**
|
|
555
|
+
* Déduplique et valide une liste d'agents `ask` fournie via `--ask-agents`.
|
|
556
|
+
* @param agents - Noms d'agents bruts, éventuellement en doublon.
|
|
557
|
+
* @throws Si la liste dépasse `MAX_ASK_AGENTS` ou référence un agent inconnu de la config.
|
|
558
|
+
*/
|
|
968
559
|
function normalizeAskAgentsForConfig(config, agents, messages) {
|
|
969
560
|
const unique = agents
|
|
970
561
|
.map((agent) => agent.trim())
|
|
@@ -988,60 +579,6 @@ function assertKnownAgent(config, agentName, fieldName, messages) {
|
|
|
988
579
|
throw new Error(messages.common.unknownAgentForField(fieldName, agentName, Object.keys(config.agents).join(", ")));
|
|
989
580
|
}
|
|
990
581
|
}
|
|
991
|
-
/**
|
|
992
|
-
* Résout le nom d'un agent selon la priorité : flag CLI > preset > défaut config.
|
|
993
|
-
* Lève une erreur si aucune source ne fournit de valeur.
|
|
994
|
-
* @param label - Libellé humain utilisé dans le message d'erreur (ex. "agent A").
|
|
995
|
-
* @param explicitValue - Valeur passée via flag CLI.
|
|
996
|
-
* @param presetValue - Valeur issue du preset sélectionné.
|
|
997
|
-
* @param defaultValue - Valeur issue des défauts de la config.
|
|
998
|
-
* @returns Nom de l'agent résolu.
|
|
999
|
-
*/
|
|
1000
|
-
function resolveAgentName(label, explicitValue, presetValue, defaultValue, messages) {
|
|
1001
|
-
const resolved = optionalString(explicitValue) ?? presetValue ?? defaultValue;
|
|
1002
|
-
if (!resolved) {
|
|
1003
|
-
throw new Error(messages.common.noAgentDefined(label));
|
|
1004
|
-
}
|
|
1005
|
-
return resolved;
|
|
1006
|
-
}
|
|
1007
|
-
function resolveSummaryAgentOption(explicitValue, defaults, mode) {
|
|
1008
|
-
const explicit = optionalString(explicitValue);
|
|
1009
|
-
if (explicit) {
|
|
1010
|
-
return explicit;
|
|
1011
|
-
}
|
|
1012
|
-
if (mode === "ask") {
|
|
1013
|
-
return defaults?.askSummaryAgent ?? defaults?.summaryAgent;
|
|
1014
|
-
}
|
|
1015
|
-
return defaults?.summaryAgent;
|
|
1016
|
-
}
|
|
1017
|
-
function parseModeFlag(value, messages) {
|
|
1018
|
-
if (!value) {
|
|
1019
|
-
return "debate";
|
|
1020
|
-
}
|
|
1021
|
-
if (value === "debate" || value === "ask") {
|
|
1022
|
-
return value;
|
|
1023
|
-
}
|
|
1024
|
-
throw new Error(messages.common.unknownMode(value, "debate, ask"));
|
|
1025
|
-
}
|
|
1026
|
-
function parseInterfaceFlag(value, messages) {
|
|
1027
|
-
if (!value) {
|
|
1028
|
-
return "tui";
|
|
1029
|
-
}
|
|
1030
|
-
if (value === "tui" || value === "terminal") {
|
|
1031
|
-
return value;
|
|
1032
|
-
}
|
|
1033
|
-
throw new Error(messages.common.unknownMode(value, "tui, terminal"));
|
|
1034
|
-
}
|
|
1035
|
-
function resolveAskAgents(explicitAgents, defaultAgents, fallbackAgents, messages) {
|
|
1036
|
-
const selected = explicitAgents.length > 0
|
|
1037
|
-
? explicitAgents
|
|
1038
|
-
: defaultAgents && defaultAgents.length > 0 ? defaultAgents : fallbackAgents;
|
|
1039
|
-
const unique = selected.filter((agent, index) => agent.trim() && selected.indexOf(agent) === index);
|
|
1040
|
-
if (unique.length > MAX_ASK_AGENTS) {
|
|
1041
|
-
throw new Error(messages.common.tooManyAskAgents(MAX_ASK_AGENTS));
|
|
1042
|
-
}
|
|
1043
|
-
return unique;
|
|
1044
|
-
}
|
|
1045
582
|
/**
|
|
1046
583
|
* Affiche un aperçu du prompt du premier tour sans appeler aucun agent (flag `--show-prompt`).
|
|
1047
584
|
* @param config - Config chargée.
|
|
@@ -1071,29 +608,13 @@ function printPromptPreview(config, options, language, messages) {
|
|
|
1071
608
|
console.log(messages.preview.agent(previewAgent, agentConfig.role));
|
|
1072
609
|
console.log(messages.preview.peer(peerName));
|
|
1073
610
|
console.log(messages.preview.pullModels(options.pullModels));
|
|
1074
|
-
console.log(messages.preview.summary(options.summaryEnabled ?
|
|
611
|
+
console.log(messages.preview.summary(options.summaryEnabled ? options.summaryAgent : messages.preview.disabled));
|
|
1075
612
|
console.log(messages.preview.interfaceLanguage(language));
|
|
1076
613
|
console.log("");
|
|
1077
614
|
console.log(prompt);
|
|
1078
615
|
console.log("");
|
|
1079
616
|
console.log(options.mode === "ask" ? messages.preview.askNote : messages.preview.note);
|
|
1080
617
|
}
|
|
1081
|
-
function previewSummaryAgent(options) {
|
|
1082
|
-
if (options.summaryAgent) {
|
|
1083
|
-
return options.summaryAgent;
|
|
1084
|
-
}
|
|
1085
|
-
if (options.mode === "ask" && options.askAgents && options.askAgents.length > 0) {
|
|
1086
|
-
return options.askAgents[options.askAgents.length - 1] ?? options.agentB;
|
|
1087
|
-
}
|
|
1088
|
-
return options.agentB;
|
|
1089
|
-
}
|
|
1090
|
-
/**
|
|
1091
|
-
* Extrait une chaîne non vide depuis une valeur de flag, ou renvoie `undefined`.
|
|
1092
|
-
* @param value - Valeur brute issue du parseur de flags.
|
|
1093
|
-
*/
|
|
1094
|
-
function optionalString(value) {
|
|
1095
|
-
return typeof value === "string" && value.trim() ? value : undefined;
|
|
1096
|
-
}
|
|
1097
618
|
/**
|
|
1098
619
|
* Pré-lit seulement `--language`/`--lang` dans les arguments bruts pour localiser
|
|
1099
620
|
* les erreurs qui peuvent survenir avant le parsing complet ou le chargement de config.
|
|
@@ -1144,14 +665,12 @@ function createRendererFromFlags(flags, plainOutputFallback, defaultInterface, m
|
|
|
1144
665
|
if (flags.json) {
|
|
1145
666
|
return createNdjsonRenderer();
|
|
1146
667
|
}
|
|
1147
|
-
if (flags.tui) {
|
|
1148
|
-
return createTuiRenderer(messages);
|
|
1149
|
-
}
|
|
1150
|
-
if (flags.terminal || flags.plain || plainOutputFallback || defaultInterface === "terminal") {
|
|
1151
|
-
return createConsoleRenderer(true, messages);
|
|
1152
|
-
}
|
|
1153
668
|
return createAutoRenderer(flags, plainOutputFallback, defaultInterface, messages);
|
|
1154
669
|
}
|
|
670
|
+
/**
|
|
671
|
+
* Choix du renderer par défaut (`--renderer auto`) : TUI si les flags/config le demandent
|
|
672
|
+
* ou si stdout est un TTY, rendu console plain sinon.
|
|
673
|
+
*/
|
|
1155
674
|
function createAutoRenderer(flags, plainOutputFallback, defaultInterface, messages) {
|
|
1156
675
|
if (flags.tui) {
|
|
1157
676
|
return createTuiRenderer(messages);
|
|
@@ -1161,6 +680,11 @@ function createAutoRenderer(flags, plainOutputFallback, defaultInterface, messag
|
|
|
1161
680
|
}
|
|
1162
681
|
return process.stdout.isTTY ? createTuiRenderer(messages) : createConsoleRenderer(true, messages);
|
|
1163
682
|
}
|
|
683
|
+
/**
|
|
684
|
+
* Détermine si l'accueil TUI doit s'ouvrir : commande `run` implicite, sans sujet, preset
|
|
685
|
+
* ni flag de rendu déjà fourni. Toute intention explicite de lancer directement un débat
|
|
686
|
+
* (topic, `--renderer`, `--json`, `--plain`, `--terminal`) désactive l'accueil.
|
|
687
|
+
*/
|
|
1164
688
|
function shouldOpenTuiHome(parsed) {
|
|
1165
689
|
return parsed.command === "run"
|
|
1166
690
|
&& !parsed.commandExplicit
|
|
@@ -1171,98 +695,6 @@ function shouldOpenTuiHome(parsed) {
|
|
|
1171
695
|
&& parsed.flags.plain !== true
|
|
1172
696
|
&& parsed.flags.terminal !== true;
|
|
1173
697
|
}
|
|
1174
|
-
/** Lance la discovery avec la même adresse Ollama effective que la config et les overrides globaux. */
|
|
1175
|
-
async function discoverLocalToolsForConfig(config, ollamaUrl) {
|
|
1176
|
-
return discoverLocalTools({
|
|
1177
|
-
ollamaUrl,
|
|
1178
|
-
ollamaTargets: configuredOllamaTargets(config)
|
|
1179
|
-
});
|
|
1180
|
-
}
|
|
1181
|
-
/**
|
|
1182
|
-
* Exécute la commande `palabre presets` en sortie humaine ou JSON versionné.
|
|
1183
|
-
*/
|
|
1184
|
-
async function runPresetsCommand(flags) {
|
|
1185
|
-
const configPath = optionalString(flags.config) ?? await resolveDefaultConfigPath();
|
|
1186
|
-
const ollamaUrl = optionalString(flags["ollama-url"]);
|
|
1187
|
-
const config = await configExists(configPath)
|
|
1188
|
-
? await loadConfig(configPath)
|
|
1189
|
-
: undefined;
|
|
1190
|
-
const discovery = config
|
|
1191
|
-
? await discoverLocalToolsForConfig(config, ollamaUrl)
|
|
1192
|
-
: await discoverLocalTools({ ollamaUrl });
|
|
1193
|
-
const resolvedConfig = config ?? createConfigFromDiscovery(discovery);
|
|
1194
|
-
const language = resolveLanguage({
|
|
1195
|
-
explicitLanguage: optionalString(flags.language),
|
|
1196
|
-
configLanguage: resolvedConfig.language
|
|
1197
|
-
});
|
|
1198
|
-
const messages = createTranslator(language);
|
|
1199
|
-
const presets = listPresetsWithAvailability(resolvedConfig, discovery, messages);
|
|
1200
|
-
if (flags.json) {
|
|
1201
|
-
process.stdout.write(JSON.stringify({ v: 1, presets }) + "\n");
|
|
1202
|
-
return;
|
|
1203
|
-
}
|
|
1204
|
-
console.log(messages.presets.title);
|
|
1205
|
-
console.log("");
|
|
1206
|
-
for (const preset of presets) {
|
|
1207
|
-
const status = preset.available
|
|
1208
|
-
? messages.presets.available
|
|
1209
|
-
: messages.presets.unavailable(preset.unavailableReasons.join("; "));
|
|
1210
|
-
console.log(` ${preset.name.padEnd(20)} ${preset.agentA} <-> ${preset.agentB} ${status}`);
|
|
1211
|
-
}
|
|
1212
|
-
console.log("");
|
|
1213
|
-
console.log(messages.presets.total(presets.length));
|
|
1214
|
-
}
|
|
1215
|
-
async function runHistoryCommand(flags) {
|
|
1216
|
-
const configPath = optionalString(flags.config) ?? await resolveDefaultConfigPath();
|
|
1217
|
-
const config = await configExists(configPath)
|
|
1218
|
-
? await loadConfig(configPath)
|
|
1219
|
-
: undefined;
|
|
1220
|
-
const language = resolveLanguage({
|
|
1221
|
-
explicitLanguage: optionalString(flags.language),
|
|
1222
|
-
configLanguage: config?.language
|
|
1223
|
-
});
|
|
1224
|
-
const messages = createTranslator(language);
|
|
1225
|
-
const entries = await listHistoryEntries(resolveOutputDir(config?.outputDir));
|
|
1226
|
-
if (flags.json) {
|
|
1227
|
-
process.stdout.write(JSON.stringify({ v: 1, history: entries }) + "\n");
|
|
1228
|
-
return;
|
|
1229
|
-
}
|
|
1230
|
-
console.log(messages.tui.historyTitle);
|
|
1231
|
-
console.log("");
|
|
1232
|
-
if (entries.length === 0) {
|
|
1233
|
-
console.log(messages.tui.historyEmpty);
|
|
1234
|
-
return;
|
|
1235
|
-
}
|
|
1236
|
-
for (const entry of entries) {
|
|
1237
|
-
console.log(`- ${entry.date || entry.fileName} | ${entry.mode} | ${entry.topic}`);
|
|
1238
|
-
console.log(` ${entry.path}`);
|
|
1239
|
-
}
|
|
1240
|
-
}
|
|
1241
|
-
async function runContextCommand(flags, positionals) {
|
|
1242
|
-
const language = resolveLanguage({ explicitLanguage: optionalString(flags.language) });
|
|
1243
|
-
const messages = createTranslator(language);
|
|
1244
|
-
const subcommand = positionals[0] ?? "scan";
|
|
1245
|
-
if (subcommand !== "scan") {
|
|
1246
|
-
throw new Error(messages.common.unknownCommand(`context ${subcommand}`, "context scan"));
|
|
1247
|
-
}
|
|
1248
|
-
const paths = positionals.slice(1);
|
|
1249
|
-
const result = await buildContextScan(paths, process.cwd(), messages);
|
|
1250
|
-
const folders = result.items.filter((item) => item.kind === "folder");
|
|
1251
|
-
const files = result.items.filter((item) => item.kind === "file");
|
|
1252
|
-
if (flags.json) {
|
|
1253
|
-
console.log(JSON.stringify(result, null, 2));
|
|
1254
|
-
return;
|
|
1255
|
-
}
|
|
1256
|
-
for (const folder of folders) {
|
|
1257
|
-
console.log(`[folder] ${folder.path}`);
|
|
1258
|
-
}
|
|
1259
|
-
for (const file of files) {
|
|
1260
|
-
console.log(`[file] ${file.path} (${file.sizeBytes} bytes)`);
|
|
1261
|
-
}
|
|
1262
|
-
for (const warning of result.warnings) {
|
|
1263
|
-
console.error(`${messages.renderers.warningPrefix} ${warning}`);
|
|
1264
|
-
}
|
|
1265
|
-
}
|
|
1266
698
|
/**
|
|
1267
699
|
* Écrit les avertissements de contexte sur `stderr`.
|
|
1268
700
|
* @param warnings - Messages d'avertissement issus du chargement des fichiers de contexte.
|
|
@@ -1272,157 +704,16 @@ function printContextWarnings(warnings, messages) {
|
|
|
1272
704
|
process.stderr.write(`${messages.renderers.warningPrefix} ${warning}\n`);
|
|
1273
705
|
}
|
|
1274
706
|
}
|
|
1275
|
-
/**
|
|
1276
|
-
* Ajoute dans `config.agents` les agents détectés localement mais absents de la config.
|
|
1277
|
-
* Mute `config` directement ; l'appelant est responsable de persister la config.
|
|
1278
|
-
* @param config - Config Palabre à compléter.
|
|
1279
|
-
* @param discovery - Résultat de la découverte locale des outils.
|
|
1280
|
-
* @returns Noms des agents nouvellement ajoutés.
|
|
1281
|
-
*/
|
|
1282
|
-
/**
|
|
1283
|
-
* Affiche la liste des agents déclarés avec leur type, rôle, état de détection et défauts.
|
|
1284
|
-
* @param configPath - Chemin du fichier de config (affiché en en-tête).
|
|
1285
|
-
* @param config - Config Palabre chargée.
|
|
1286
|
-
* @param discovery - Résultat de la découverte locale des outils.
|
|
1287
|
-
*/
|
|
1288
|
-
function printAgents(configPath, config, discovery, messages) {
|
|
1289
|
-
const entries = Object.entries(config.agents)
|
|
1290
|
-
.filter(([name]) => !isRetiredAgentName(name))
|
|
1291
|
-
.sort(([left], [right]) => left.localeCompare(right));
|
|
1292
|
-
console.log(messages.agents.config(configPath));
|
|
1293
|
-
console.log("");
|
|
1294
|
-
console.log(messages.agents.title);
|
|
1295
|
-
for (const [name, agentConfig] of entries) {
|
|
1296
|
-
const status = formatAgentDetection(name, agentConfig, discovery, messages);
|
|
1297
|
-
const defaults = formatAgentDefaults(name, config, messages);
|
|
1298
|
-
const details = formatAgentDetails(agentConfig, messages);
|
|
1299
|
-
const suffix = defaults ? ` | ${defaults}` : "";
|
|
1300
|
-
console.log(`- ${name.padEnd(13)} ${`${agentConfig.type}/${agentConfig.role}`.padEnd(18)} ${status}${suffix}`);
|
|
1301
|
-
if (details) {
|
|
1302
|
-
console.log(` ${details}`);
|
|
1303
|
-
}
|
|
1304
|
-
}
|
|
1305
|
-
console.log("");
|
|
1306
|
-
console.log(messages.agents.defaults(config.defaults?.agentA ?? messages.agents.none, config.defaults?.agentB ?? messages.agents.none, turnsOrDefault(config.defaults?.turns), config.defaults?.summaryAgent ?? messages.agents.summaryAgentB, config.defaults?.askSummaryAgent));
|
|
1307
|
-
}
|
|
1308
|
-
/**
|
|
1309
|
-
* Renvoie un libellé indiquant si l'agent est agent A, agent B ou agent de synthèse par défaut.
|
|
1310
|
-
* @param name - Nom de l'agent.
|
|
1311
|
-
* @param config - Config Palabre contenant les défauts.
|
|
1312
|
-
*/
|
|
1313
|
-
function formatAgentDefaults(name, config, messages) {
|
|
1314
|
-
const labels = [];
|
|
1315
|
-
if (config.defaults?.agentA === name)
|
|
1316
|
-
labels.push(messages.agents.defaultAgentA);
|
|
1317
|
-
if (config.defaults?.agentB === name)
|
|
1318
|
-
labels.push(messages.agents.defaultAgentB);
|
|
1319
|
-
if (config.defaults?.summaryAgent === name)
|
|
1320
|
-
labels.push(messages.agents.defaultSummary);
|
|
1321
|
-
if (config.defaults?.askSummaryAgent === name)
|
|
1322
|
-
labels.push(messages.agents.defaultAskSummary);
|
|
1323
|
-
return labels.join(", ");
|
|
1324
|
-
}
|
|
1325
|
-
/**
|
|
1326
|
-
* Renvoie une ligne de détails pour un agent : commande CLI ou modèle Ollama.
|
|
1327
|
-
* @param agentConfig - Configuration de l'agent.
|
|
1328
|
-
*/
|
|
1329
|
-
function formatAgentDetails(agentConfig, messages) {
|
|
1330
|
-
if (agentConfig.type === "ollama") {
|
|
1331
|
-
return messages.agents.model(agentConfig.model);
|
|
1332
|
-
}
|
|
1333
|
-
return messages.agents.command(agentConfig.command, agentConfig.model);
|
|
1334
|
-
}
|
|
1335
|
-
/**
|
|
1336
|
-
* Renvoie le statut de détection d'un agent sous forme de chaîne lisible.
|
|
1337
|
-
* Pour Ollama, vérifie la disponibilité du serveur et la présence du modèle.
|
|
1338
|
-
* @param name - Nom de l'agent dans la config.
|
|
1339
|
-
* @param agentConfig - Configuration de l'agent.
|
|
1340
|
-
* @param discovery - Résultat de la découverte locale des outils.
|
|
1341
|
-
*/
|
|
1342
|
-
function formatAgentDetection(name, agentConfig, discovery, messages) {
|
|
1343
|
-
if (agentConfig.type === "ollama") {
|
|
1344
|
-
if (!discovery.ollama.available) {
|
|
1345
|
-
return discovery.ollama.commandAvailable ? messages.agents.ollamaUnreachable : messages.agents.ollamaNotDetected;
|
|
1346
|
-
}
|
|
1347
|
-
return discovery.ollama.models.includes(agentConfig.model)
|
|
1348
|
-
? messages.agents.detected()
|
|
1349
|
-
: messages.agents.missingModel(agentConfig.model);
|
|
1350
|
-
}
|
|
1351
|
-
const detection = cliDetectionForAgent(name, agentConfig, discovery);
|
|
1352
|
-
return detection.available ? messages.agents.detected(detection.command) : messages.agents.notDetected;
|
|
1353
|
-
}
|
|
1354
|
-
/**
|
|
1355
|
-
* Résout l'entrée de détection correspondant à un agent CLI.
|
|
1356
|
-
* Renvoie un objet `{ available: true }` pour les agents CLI non reconnus (considérés disponibles).
|
|
1357
|
-
* @param name - Nom de l'agent dans la config.
|
|
1358
|
-
* @param agentConfig - Configuration de l'agent.
|
|
1359
|
-
* @param discovery - Résultat de la découverte locale des outils.
|
|
1360
|
-
*/
|
|
1361
|
-
function cliDetectionForAgent(name, agentConfig, discovery) {
|
|
1362
|
-
const command = agentConfig.type === "cli" || agentConfig.type === "cli-pty" ? agentConfig.command : name;
|
|
1363
|
-
return detectionForCommand(command, discovery) ?? { available: true, command };
|
|
1364
|
-
}
|
|
1365
|
-
/**
|
|
1366
|
-
* Affiche le récapitulatif de détection locale après `palabre init`.
|
|
1367
|
-
* @param discovery - Résultat de la découverte locale des outils.
|
|
1368
|
-
* @param config - Config générée à partir de la découverte.
|
|
1369
|
-
*/
|
|
1370
|
-
function printInitDiscovery(discovery, config, messages) {
|
|
1371
|
-
console.log("");
|
|
1372
|
-
console.log(messages.init.localDetectionTitle);
|
|
1373
|
-
console.log(`- Codex CLI: ${formatCommandDetection(discovery.codex, messages)}`);
|
|
1374
|
-
console.log(`- Claude CLI: ${formatCommandDetection(discovery.claude, messages)}`);
|
|
1375
|
-
console.log(`- Antigravity CLI: ${formatCommandDetection(discovery.antigravity, messages)}`);
|
|
1376
|
-
console.log(`- OpenCode CLI: ${formatCommandDetection(discovery.opencode, messages)}`);
|
|
1377
|
-
console.log(`- Mistral Vibe CLI: ${formatCommandDetection(discovery.vibe, messages)}`);
|
|
1378
|
-
console.log(`- Ollama API: ${formatOllamaDetection(discovery.ollama, messages)}`);
|
|
1379
|
-
console.log("");
|
|
1380
|
-
console.log(config.defaults?.agentA && config.defaults.agentB
|
|
1381
|
-
? messages.init.defaults(config.defaults.agentA, config.defaults.agentB)
|
|
1382
|
-
: messages.init.noDefaultPair(formatDetectedAgentSummary(discovery, config.language ?? DEFAULT_LANGUAGE)));
|
|
1383
|
-
console.log(messages.init.languageHint(config.language ?? DEFAULT_LANGUAGE));
|
|
1384
|
-
}
|
|
1385
|
-
function formatDetectedAgentSummary(discovery, language) {
|
|
1386
|
-
const names = detectedAgentNames(discovery);
|
|
1387
|
-
if (names.length === 0) {
|
|
1388
|
-
return language === "en" ? "no agent detected" : "aucun agent détecté";
|
|
1389
|
-
}
|
|
1390
|
-
if (names.length === 1) {
|
|
1391
|
-
return language === "en"
|
|
1392
|
-
? `only one agent detected (${names[0]})`
|
|
1393
|
-
: `un seul agent détecté (${names[0]})`;
|
|
1394
|
-
}
|
|
1395
|
-
return language === "en"
|
|
1396
|
-
? `no usable pair detected among ${names.join(", ")}`
|
|
1397
|
-
: `aucune paire utilisable détectée parmi ${names.join(", ")}`;
|
|
1398
|
-
}
|
|
1399
|
-
/**
|
|
1400
|
-
* Formate le statut de détection d'un outil CLI (disponible ou non).
|
|
1401
|
-
* @param detection - Résultat de détection d'un outil CLI.
|
|
1402
|
-
*/
|
|
1403
|
-
function formatCommandDetection(detection, messages) {
|
|
1404
|
-
return detection.available
|
|
1405
|
-
? messages.init.commandDetected(detection.command)
|
|
1406
|
-
: messages.init.commandMissing;
|
|
1407
|
-
}
|
|
1408
|
-
/**
|
|
1409
|
-
* Formate le statut de détection d'Ollama : commande absente, serveur injoignable ou modèles disponibles.
|
|
1410
|
-
* @param detection - Résultat de détection d'Ollama.
|
|
1411
|
-
*/
|
|
1412
|
-
function formatOllamaDetection(detection, messages) {
|
|
1413
|
-
if (!detection.available) {
|
|
1414
|
-
return detection.commandAvailable
|
|
1415
|
-
? messages.init.ollamaServerUnreachable(detection.baseUrl)
|
|
1416
|
-
: messages.init.ollamaMissing;
|
|
1417
|
-
}
|
|
1418
|
-
const modelCount = detection.models.length;
|
|
1419
|
-
return messages.init.ollamaDetected(modelCount);
|
|
1420
|
-
}
|
|
1421
707
|
/** Affiche le texte d'aide complet sur `stdout`. */
|
|
1422
708
|
function printHelp(messages, command) {
|
|
1423
709
|
const commandHelp = command ? messages.help.renderCommand(command) : undefined;
|
|
1424
710
|
console.log(commandHelp ?? messages.help.render(listPresetNames().join(", ")));
|
|
1425
711
|
}
|
|
712
|
+
/**
|
|
713
|
+
* Résout la commande cible pour l'aide contextuelle (`palabre <cmd> --help`), en normalisant
|
|
714
|
+
* les alias (`agent` -> `agents`, `preset` -> `presets`, `setup` -> `init`).
|
|
715
|
+
* @returns `undefined` pour `help`/`run`, qui utilisent l'aide générale.
|
|
716
|
+
*/
|
|
1426
717
|
function commandHelpTarget(parsed) {
|
|
1427
718
|
if (parsed.command === "help" || parsed.command === "run") {
|
|
1428
719
|
return undefined;
|
|
@@ -1450,16 +741,16 @@ async function resolveCommandMessages(flags) {
|
|
|
1450
741
|
}
|
|
1451
742
|
return createTranslator(resolveLanguage({ explicitLanguage, configLanguage }));
|
|
1452
743
|
}
|
|
744
|
+
/**
|
|
745
|
+
* Formate une erreur non gérée remontée jusqu'au point d'entrée en message lisible,
|
|
746
|
+
* en spécialisant `AdapterError` et `OllamaUrlError` pour rester actionnable.
|
|
747
|
+
*/
|
|
1453
748
|
function formatRuntimeError(error, messages) {
|
|
1454
749
|
if (error instanceof AdapterError) {
|
|
1455
750
|
return formatAdapterError(error, messages);
|
|
1456
751
|
}
|
|
1457
752
|
if (error instanceof OllamaUrlError) {
|
|
1458
|
-
|
|
1459
|
-
return messages.common.ollamaUrlEmpty;
|
|
1460
|
-
if (error.kind === "protocol")
|
|
1461
|
-
return messages.common.ollamaUrlProtocol(error.protocol ?? "");
|
|
1462
|
-
return messages.common.ollamaUrlInvalid(error.value);
|
|
753
|
+
return formatOllamaUrlError(error, messages);
|
|
1463
754
|
}
|
|
1464
755
|
return error instanceof Error ? error.message : String(error);
|
|
1465
756
|
}
|
|
@@ -1470,6 +761,10 @@ main().catch((error) => {
|
|
|
1470
761
|
console.error(`${messages.common.errorPrefix}: ${message}`);
|
|
1471
762
|
process.exitCode = 1;
|
|
1472
763
|
});
|
|
764
|
+
/**
|
|
765
|
+
* Variante de `findRawLanguageFlag` + `resolveLanguage` qui ne peut pas lever, utilisée dans
|
|
766
|
+
* le gestionnaire d'erreur global où la config n'est pas forcément chargée.
|
|
767
|
+
*/
|
|
1473
768
|
function safeStartupLanguage(args) {
|
|
1474
769
|
try {
|
|
1475
770
|
return resolveLanguage({ explicitLanguage: findRawLanguageFlag(args) });
|