palabre 0.7.0 → 0.8.1

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/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { assertRunnableConfig, configExists, createConfigFromDiscovery, DEFAULT_CONFIG_PATH, GLOBAL_CONFIG_PATH, loadConfig, resolveDefaultConfigPath, resolveOutputDir, setOllamaModel, syncDetectedAgents, syncOllamaModel, writeExampleConfig } from "./config.js";
2
+ import { assertRunnableConfig, configExists, createConfigFromDiscovery, DEFAULT_CONFIG_PATH, GLOBAL_CONFIG_PATH, loadConfig, resolveDefaultConfigPath, resolveOutputDir, setOllamaModel, syncDetectedAgentsDetailed, syncOllamaModel, writeExampleConfig } from "./config.js";
3
3
  import { loadProjectInputs } from "./context.js";
4
4
  import { buildContextScan } from "./contextScan.js";
5
5
  import { discoverLocalTools } from "./discovery.js";
@@ -7,17 +7,20 @@ import { runDoctor } from "./doctor.js";
7
7
  import { AdapterError, formatAdapterError } from "./errors.js";
8
8
  import { runConfigWizard } from "./configWizard.js";
9
9
  import { createTranslator, DEFAULT_LANGUAGE, parseLanguage, resolveLanguage } from "./i18n.js";
10
- import { DEFAULT_TURNS, parseTurnsFlag, turnsOrDefault } from "./limits.js";
10
+ import { DEFAULT_TURNS, parseTurnsFlag, turnsOrDefault, validateTurns } from "./limits.js";
11
11
  import { formatAgentPrompt } from "./prompt.js";
12
12
  import { runNewWizard } from "./new.js";
13
13
  import { listPresetNames, listPresetsWithAvailability, resolvePreset } from "./presets.js";
14
+ import { listHistoryEntries } from "./history.js";
14
15
  import { createConsoleRenderer } from "./renderers/console.js";
15
16
  import { createNdjsonRenderer } from "./renderers/ndjson.js";
16
- import { runDebate } from "./orchestrator.js";
17
+ import { createTuiRenderer, promptTuiAgentsWizard, promptTuiConfigCommand, promptTuiHomeTopic, promptTuiRolesWizard, renderTuiConfig, renderTuiHelp, renderTuiHistory, renderTuiHome } from "./renderers/tui.js";
18
+ import { MAX_ASK_AGENTS, runAsk, runDebate } from "./orchestrator.js";
17
19
  import { writeDebateMarkdown } from "./output.js";
18
20
  import { applySourceUpdate, formatUpdateInstructions, getUpdateInfo } from "./update.js";
19
21
  import { createSessionContext } from "./session.js";
20
22
  import { getStringListFlag, parseArgs } from "./args.js";
23
+ import { askAgentSeedsForMode, clearTuiRunOverrides } from "./tuiState.js";
21
24
  import { detectedAgentNames, detectionForCommand } from "./agentRegistry.js";
22
25
  import { getPackageVersion } from "./version.js";
23
26
  /** Point d'entrée principal du CLI Palabre. Dispatche vers la commande appropriée selon les arguments. */
@@ -35,7 +38,7 @@ async function main() {
35
38
  return;
36
39
  }
37
40
  if (parsed.command === "doctor") {
38
- const result = await runDoctor(optionalString(parsed.flags.config), Boolean(parsed.flags.plain), optionalString(parsed.flags.language));
41
+ const result = await runDoctor(optionalString(parsed.flags.config), Boolean(parsed.flags.plain || parsed.flags.terminal), optionalString(parsed.flags.language));
39
42
  console.log(result.output);
40
43
  process.exitCode = result.ok ? 0 : 1;
41
44
  return;
@@ -52,6 +55,10 @@ async function main() {
52
55
  await runPresetsCommand(parsed.flags);
53
56
  return;
54
57
  }
58
+ if (parsed.command === "history" || parsed.command === "historique") {
59
+ await runHistoryCommand(parsed.flags);
60
+ return;
61
+ }
55
62
  if (parsed.command === "context") {
56
63
  await runContextCommand(parsed.flags, parsed.positionals);
57
64
  return;
@@ -94,91 +101,234 @@ async function main() {
94
101
  return;
95
102
  }
96
103
  const configPath = optionalString(parsed.flags.config) ?? await resolveDefaultConfigPath();
104
+ let config;
105
+ let tuiNotice;
97
106
  if (!(await configExists(configPath))) {
98
- const config = createConfigFromDiscovery(await discoverLocalTools());
107
+ config = createConfigFromDiscovery(await discoverLocalTools());
99
108
  config.language = resolveLanguage({
100
109
  explicitLanguage: optionalString(parsed.flags.language),
101
110
  configLanguage: config.language
102
111
  });
103
112
  const messages = createTranslator(config.language);
104
113
  await writeExampleConfig(configPath, config);
105
- console.log(messages.init.editConfigThenRerun(configPath));
106
- return;
114
+ if (!shouldOpenTuiHome(parsed)) {
115
+ console.log(messages.init.editConfigThenRerun(configPath));
116
+ return;
117
+ }
118
+ tuiNotice = messages.init.configCreated(configPath);
107
119
  }
108
- const config = await loadConfig(configPath);
109
- const language = resolveLanguage({
120
+ else {
121
+ config = await loadConfig(configPath);
122
+ }
123
+ let language = resolveLanguage({
110
124
  explicitLanguage: optionalString(parsed.flags.language),
111
125
  configLanguage: config.language
112
126
  });
113
- const messages = createTranslator(language);
127
+ let messages = createTranslator(language);
114
128
  assertRunnableConfig(config, messages, configPath);
115
- if (parsed.command === "new") {
116
- const selection = await runNewWizard(config, messages);
117
- if (!selection) {
118
- console.log(messages.new.cancelled);
119
- return;
129
+ let stayInTuiAfterSession = false;
130
+ let hasCompletedTuiSession = false;
131
+ let resetTuiRunOverridesOnNextTopic = false;
132
+ let tuiMode = config.defaults?.mode ?? "debate";
133
+ let tuiVersion = "";
134
+ const handleTuiHomeInput = async (tuiInput) => {
135
+ if (!tuiInput) {
136
+ return "quit";
137
+ }
138
+ if (tuiInput.kind === "help") {
139
+ renderTuiHelp(messages);
140
+ const nextInput = await promptTuiHomeTopic(tuiMode, messages);
141
+ return handleTuiHomeInput(nextInput);
142
+ }
143
+ if (tuiInput.kind === "history") {
144
+ renderTuiHistory(await listHistoryEntries(resolveOutputDir(config.outputDir)), messages);
145
+ const nextInput = await promptTuiHomeTopic(tuiMode, messages);
146
+ return handleTuiHomeInput(nextInput);
120
147
  }
121
- parsed.flags["agent-a"] = selection.agentA;
122
- parsed.flags["agent-b"] = selection.agentB;
123
- parsed.flags.topic = selection.topic;
124
- if (selection.modelA)
125
- parsed.flags["model-a"] = selection.modelA;
126
- if (selection.modelB)
127
- parsed.flags["model-b"] = selection.modelB;
128
- if (selection.turns)
129
- parsed.flags.turns = String(selection.turns);
130
- if (selection.summaryAgent)
131
- parsed.flags["summary-agent"] = selection.summaryAgent;
132
- if (selection.summaryModel)
133
- parsed.flags["summary-model"] = selection.summaryModel;
134
- if (selection.summaryEnabled === false)
135
- parsed.flags["no-summary"] = true;
136
- if (selection.showPrompt)
137
- parsed.flags["show-prompt"] = true;
138
- if (selection.plainOutput)
139
- parsed.flags.plain = true;
140
- if (selection.files.length > 0)
141
- parsed.flags.files = selection.files;
142
- if (selection.context.length > 0)
143
- parsed.flags.context = selection.context;
144
- }
145
- const topic = optionalString(parsed.flags.topic) ?? "";
146
- const context = await loadProjectInputs(getStringListFlag(parsed.flags.files), getStringListFlag(parsed.flags.context), process.cwd(), messages);
147
- const presetName = optionalString(parsed.flags.preset);
148
- const preset = presetName ? resolvePreset(presetName, messages) : undefined;
149
- if (!topic) {
150
- throw new Error(messages.common.topicRequired);
151
- }
152
- const options = {
153
- language,
154
- topic,
155
- agentA: resolveAgentName("agent A", parsed.flags["agent-a"], preset?.agentA, config.defaults?.agentA, messages),
156
- agentB: resolveAgentName("agent B", parsed.flags["agent-b"], preset?.agentB, config.defaults?.agentB, messages),
157
- turns: parseTurnsFlag(parsed.flags.turns, config.defaults?.turns ?? DEFAULT_TURNS, "--turns", messages),
158
- session: createSessionContext(),
159
- files: context.files,
160
- modelA: optionalString(parsed.flags["model-a"]),
161
- modelB: optionalString(parsed.flags["model-b"]),
162
- pullModels: Boolean(parsed.flags["pull-models"]),
163
- summaryAgent: optionalString(parsed.flags["summary-agent"]) ?? config.defaults?.summaryAgent,
164
- summaryModel: optionalString(parsed.flags["summary-model"]),
165
- summaryEnabled: !parsed.flags["no-summary"],
166
- earlyStopOnAgreement: !parsed.flags["no-early-stop"],
167
- plainOutput: Boolean(parsed.flags.plain),
168
- signal: debateAbortSignal()
148
+ if (tuiInput.kind === "home") {
149
+ return "continue";
150
+ }
151
+ if (tuiInput.kind === "roles") {
152
+ const result = await runTuiRolesWizard(configPath, config, messages, tuiMode, tuiInput.roles);
153
+ if (result.quit)
154
+ return "quit";
155
+ tuiNotice = result.notice;
156
+ return "continue";
157
+ }
158
+ if (tuiInput.kind === "agents") {
159
+ const result = await runTuiAgentsWizard(configPath, config, messages, tuiMode, tuiInput.agents);
160
+ if (result.quit)
161
+ return "quit";
162
+ tuiNotice = result.notice;
163
+ resetTuiRunOverridesOnNextTopic ||= Boolean(result.changedRunDefaults);
164
+ return "continue";
165
+ }
166
+ if (tuiInput.kind === "mode") {
167
+ tuiMode = tuiInput.mode;
168
+ return "continue";
169
+ }
170
+ if (tuiInput.kind === "config") {
171
+ const result = await runTuiConfigLoop(configPath, config, messages, tuiMode);
172
+ if (result.quit)
173
+ return "quit";
174
+ tuiMode = result.mode;
175
+ resetTuiRunOverridesOnNextTopic ||= result.changedRunDefaults;
176
+ language = resolveLanguage({ explicitLanguage: optionalString(parsed.flags.language), configLanguage: config.language });
177
+ messages = createTranslator(language);
178
+ return "continue";
179
+ }
180
+ if (tuiInput.kind === "new") {
181
+ parsed.command = "new";
182
+ parsed.commandExplicit = true;
183
+ delete parsed.flags.topic;
184
+ return "run";
185
+ }
186
+ if (tuiInput.kind === "retry") {
187
+ if (!optionalString(parsed.flags.topic)) {
188
+ tuiNotice = messages.tui.retryUnavailable;
189
+ return "continue";
190
+ }
191
+ return "retry";
192
+ }
193
+ parsed.command = "";
194
+ parsed.commandExplicit = false;
195
+ if (hasCompletedTuiSession || resetTuiRunOverridesOnNextTopic) {
196
+ clearTuiRunOverrides(parsed.flags);
197
+ resetTuiRunOverridesOnNextTopic = false;
198
+ }
199
+ parsed.flags.topic = tuiInput.topic;
200
+ return "run";
169
201
  };
170
- if (parsed.flags["show-prompt"]) {
171
- printContextWarnings(context.warnings, messages);
172
- printPromptPreview(config, options, language, messages);
173
- return;
202
+ if (shouldOpenTuiHome(parsed)) {
203
+ const syncResult = await syncInteractiveDetectedAgents(configPath, config);
204
+ if (!tuiNotice && syncResult.addedAgents.length > 0) {
205
+ tuiNotice = messages.config.syncAdded(configPath, syncResult.addedAgents.join(", "));
206
+ }
207
+ stayInTuiAfterSession = true;
208
+ tuiVersion = await getPackageVersion();
209
+ for (;;) {
210
+ renderTuiHome(config, configPath, messages, { mode: tuiMode, version: tuiVersion });
211
+ const tuiInput = await promptTuiHomeTopic(tuiMode, messages, { notice: tuiNotice });
212
+ tuiNotice = undefined;
213
+ const action = await handleTuiHomeInput(tuiInput);
214
+ if (action === "quit")
215
+ return;
216
+ if (action === "continue")
217
+ continue;
218
+ parsed.flags.mode = tuiMode;
219
+ parsed.flags.renderer = "tui";
220
+ break;
221
+ }
174
222
  }
175
- const renderer = createRendererFromFlags(parsed.flags, options.plainOutput, messages);
176
- context.warnings.forEach((warning) => renderer.warning(warning));
177
- const result = await runDebate(config, options, renderer, messages);
178
- const outputPath = await writeDebateMarkdown(resolveOutputDir(config.outputDir), result.options, result.messages, result.summary, result.stopReason, messages, result.failure);
179
- renderer.done(outputPath);
180
- if (result.failure) {
181
- process.exitCode = result.failure.kind === "cancelled" ? 130 : 1;
223
+ for (;;) {
224
+ if (parsed.command === "new") {
225
+ const selection = await runNewWizard(config, messages);
226
+ if (!selection) {
227
+ console.log(messages.new.cancelled);
228
+ return;
229
+ }
230
+ parsed.flags["agent-a"] = selection.agentA;
231
+ parsed.flags["agent-b"] = selection.agentB;
232
+ parsed.flags.topic = selection.topic;
233
+ if (selection.modelA)
234
+ parsed.flags["model-a"] = selection.modelA;
235
+ if (selection.modelB)
236
+ parsed.flags["model-b"] = selection.modelB;
237
+ if (selection.turns)
238
+ parsed.flags.turns = String(selection.turns);
239
+ if (selection.summaryAgent)
240
+ parsed.flags["summary-agent"] = selection.summaryAgent;
241
+ if (selection.summaryModel)
242
+ parsed.flags["summary-model"] = selection.summaryModel;
243
+ if (selection.summaryEnabled === false)
244
+ parsed.flags["no-summary"] = true;
245
+ if (selection.showPrompt)
246
+ parsed.flags["show-prompt"] = true;
247
+ if (selection.plainOutput)
248
+ parsed.flags.plain = true;
249
+ if (selection.files.length > 0)
250
+ parsed.flags.files = selection.files;
251
+ if (selection.context.length > 0)
252
+ parsed.flags.context = selection.context;
253
+ if (selection.mode)
254
+ parsed.flags.mode = selection.mode;
255
+ if (selection.askAgents && selection.askAgents.length > 0)
256
+ parsed.flags.agents = selection.askAgents;
257
+ parsed.command = "";
258
+ parsed.commandExplicit = false;
259
+ }
260
+ const topic = optionalString(parsed.flags.topic) ?? "";
261
+ const context = await loadProjectInputs(getStringListFlag(parsed.flags.files), getStringListFlag(parsed.flags.context), process.cwd(), messages);
262
+ const presetName = optionalString(parsed.flags.preset);
263
+ const preset = presetName ? resolvePreset(presetName, messages) : undefined;
264
+ if (!topic) {
265
+ throw new Error(messages.common.topicRequired);
266
+ }
267
+ const mode = parseModeFlag(optionalString(parsed.flags.mode) ?? config.defaults?.mode, messages);
268
+ const explicitAskAgents = getStringListFlag(parsed.flags.agents);
269
+ const askAgentSeeds = askAgentSeedsForMode(mode, explicitAskAgents, config.defaults?.askAgents);
270
+ const agentA = resolveAgentName("agent A", parsed.flags["agent-a"], preset?.agentA, askAgentSeeds[0] ?? config.defaults?.agentA, messages);
271
+ const agentB = resolveAgentName("agent B", parsed.flags["agent-b"], preset?.agentB, askAgentSeeds[1] ?? askAgentSeeds[0] ?? config.defaults?.agentB, messages);
272
+ const askAgents = mode === "ask" ? resolveAskAgents(explicitAskAgents, config.defaults?.askAgents, [agentA, agentB], messages) : undefined;
273
+ const options = {
274
+ mode,
275
+ language,
276
+ topic,
277
+ agentA,
278
+ agentB,
279
+ askAgents,
280
+ turns: parseTurnsFlag(parsed.flags.turns, config.defaults?.turns ?? DEFAULT_TURNS, "--turns", messages),
281
+ session: createSessionContext(),
282
+ files: context.files,
283
+ modelA: optionalString(parsed.flags["model-a"]),
284
+ modelB: optionalString(parsed.flags["model-b"]),
285
+ pullModels: Boolean(parsed.flags["pull-models"]),
286
+ summaryAgent: resolveSummaryAgentOption(parsed.flags["summary-agent"], config.defaults, mode),
287
+ summaryModel: optionalString(parsed.flags["summary-model"]),
288
+ summaryEnabled: !parsed.flags["no-summary"],
289
+ earlyStopOnAgreement: !parsed.flags["no-early-stop"],
290
+ plainOutput: Boolean(parsed.flags.plain || parsed.flags.terminal),
291
+ signal: debateAbortSignal()
292
+ };
293
+ if (parsed.flags["show-prompt"]) {
294
+ printContextWarnings(context.warnings, messages);
295
+ printPromptPreview(config, options, language, messages);
296
+ return;
297
+ }
298
+ process.exitCode = undefined;
299
+ const renderer = createRendererFromFlags(parsed.flags, options.plainOutput, config.defaults?.interface, messages);
300
+ context.warnings.forEach((warning) => renderer.warning(warning));
301
+ const result = options.mode === "ask"
302
+ ? await runAsk(config, options, renderer, messages)
303
+ : await runDebate(config, options, renderer, messages);
304
+ const outputPath = await writeDebateMarkdown(resolveOutputDir(config.outputDir), result.options, result.messages, result.summary, result.stopReason, messages, result.failure);
305
+ renderer.done(outputPath);
306
+ hasCompletedTuiSession = stayInTuiAfterSession;
307
+ if (result.failure) {
308
+ process.exitCode = result.failure.kind === "cancelled" ? 130 : 1;
309
+ }
310
+ if (!stayInTuiAfterSession) {
311
+ return;
312
+ }
313
+ tuiMode = mode;
314
+ for (;;) {
315
+ const nextInput = await promptTuiHomeTopic(tuiMode, messages, { notice: tuiNotice });
316
+ tuiNotice = undefined;
317
+ const action = await handleTuiHomeInput(nextInput);
318
+ if (action === "quit")
319
+ return;
320
+ if (action === "continue") {
321
+ renderTuiHome(config, configPath, messages, { mode: tuiMode, version: tuiVersion });
322
+ continue;
323
+ }
324
+ if (action === "retry") {
325
+ parsed.flags.renderer = "tui";
326
+ break;
327
+ }
328
+ parsed.flags.mode = tuiMode;
329
+ parsed.flags.renderer = "tui";
330
+ break;
331
+ }
182
332
  }
183
333
  }
184
334
  function debateAbortSignal() {
@@ -245,20 +395,32 @@ async function runConfigCommand(flags) {
245
395
  }
246
396
  if (flags["sync-agents"]) {
247
397
  const discovery = await discoverLocalTools();
248
- const addedAgents = syncDetectedAgents(config, discovery);
249
- if (addedAgents.length === 0) {
398
+ const result = syncDetectedAgentsDetailed(config, discovery);
399
+ if (!result.changed) {
250
400
  console.log(messages.config.syncNoMissing(configPath));
251
401
  return;
252
402
  }
253
403
  await writeExampleConfig(configPath, config);
254
- console.log(messages.config.syncAdded(configPath, addedAgents.join(", ")));
404
+ console.log(result.addedAgents.length > 0
405
+ ? messages.config.syncAdded(configPath, result.addedAgents.join(", "))
406
+ : messages.config.syncRefreshed(configPath));
255
407
  return;
256
408
  }
257
409
  const defaultAgents = getStringListFlag(flags["set-defaults"]);
258
410
  const hasTurnsFlag = flags.turns !== undefined;
259
411
  const summaryAgentValue = optionalString(flags["summary-agent"]);
412
+ const askSummaryAgentValue = optionalString(flags["ask-summary-agent"]);
413
+ const interfaceValue = optionalString(flags.interface);
414
+ const modeValue = optionalString(flags.mode);
415
+ const askAgentsValue = getStringListFlag(flags["ask-agents"]);
260
416
  const languageValue = explicitLanguage;
261
- const changesDefaults = defaultAgents.length > 0 || hasTurnsFlag || summaryAgentValue !== undefined;
417
+ const changesDefaults = defaultAgents.length > 0
418
+ || hasTurnsFlag
419
+ || summaryAgentValue !== undefined
420
+ || askSummaryAgentValue !== undefined
421
+ || interfaceValue !== undefined
422
+ || modeValue !== undefined
423
+ || askAgentsValue.length > 0;
262
424
  if (changesDefaults || languageValue !== undefined) {
263
425
  const nextDefaults = { ...(config.defaults ?? {}) };
264
426
  if (defaultAgents.length > 0) {
@@ -283,6 +445,24 @@ async function runConfigCommand(flags) {
283
445
  nextDefaults.summaryAgent = summaryAgentValue;
284
446
  }
285
447
  }
448
+ if (askSummaryAgentValue !== undefined) {
449
+ if (isNoneValue(askSummaryAgentValue)) {
450
+ delete nextDefaults.askSummaryAgent;
451
+ }
452
+ else {
453
+ assertKnownAgent(config, askSummaryAgentValue, "defaults.askSummaryAgent", messages);
454
+ nextDefaults.askSummaryAgent = askSummaryAgentValue;
455
+ }
456
+ }
457
+ if (interfaceValue !== undefined) {
458
+ nextDefaults.interface = parseInterfaceFlag(interfaceValue, messages);
459
+ }
460
+ if (modeValue !== undefined) {
461
+ nextDefaults.mode = parseModeFlag(modeValue, messages);
462
+ }
463
+ if (askAgentsValue.length > 0) {
464
+ nextDefaults.askAgents = normalizeAskAgentsForConfig(config, askAgentsValue, messages);
465
+ }
286
466
  if (languageValue !== undefined) {
287
467
  config.language = parseLanguage(languageValue, "--language");
288
468
  }
@@ -301,6 +481,357 @@ async function runConfigCommand(flags) {
301
481
  }
302
482
  await runConfigWizard(configPath, config, messages);
303
483
  }
484
+ async function runTuiConfigLoop(configPath, config, messages, initialMode) {
485
+ let mode = initialMode;
486
+ let notice;
487
+ let currentMessages = messages;
488
+ let changedRunDefaults = false;
489
+ for (;;) {
490
+ renderTuiConfig(config, configPath, mode, currentMessages, { message: notice });
491
+ notice = undefined;
492
+ const input = await promptTuiConfigCommand(mode, currentMessages);
493
+ if (input.kind === "quit") {
494
+ return { mode, quit: true, changedRunDefaults };
495
+ }
496
+ if (input.kind === "back") {
497
+ return { mode, quit: false, changedRunDefaults };
498
+ }
499
+ if (input.kind === "unknown") {
500
+ notice = input.message;
501
+ continue;
502
+ }
503
+ if (input.kind === "mode") {
504
+ mode = mode === "ask" ? "debate" : "ask";
505
+ config.defaults = { ...(config.defaults ?? {}), mode };
506
+ await writeExampleConfig(configPath, config);
507
+ changedRunDefaults = true;
508
+ notice = mode === "ask" ? currentMessages.tui.askDefaultMode : currentMessages.tui.debateDefaultMode;
509
+ continue;
510
+ }
511
+ if (input.kind === "default-mode") {
512
+ config.defaults = { ...(config.defaults ?? {}), mode };
513
+ await writeExampleConfig(configPath, config);
514
+ changedRunDefaults = true;
515
+ notice = mode === "ask" ? currentMessages.tui.askDefaultMode : currentMessages.tui.debateDefaultMode;
516
+ continue;
517
+ }
518
+ if (input.kind === "interface") {
519
+ config.defaults = { ...(config.defaults ?? {}), interface: input.interfaceName };
520
+ await writeExampleConfig(configPath, config);
521
+ notice = currentMessages.tui.interfaceDefault(input.interfaceName);
522
+ continue;
523
+ }
524
+ if (input.kind === "language") {
525
+ config.language = parseLanguage(input.language, "--language");
526
+ await writeExampleConfig(configPath, config);
527
+ currentMessages = createTranslator(config.language ?? DEFAULT_LANGUAGE);
528
+ notice = currentMessages.tui.languageUpdated(input.language);
529
+ continue;
530
+ }
531
+ if (input.kind === "agents") {
532
+ try {
533
+ const agentsInput = input.agents.length > 0
534
+ ? { kind: "agents", agents: input.agents }
535
+ : await promptTuiAgentsWizard(config, mode, currentMessages);
536
+ if (agentsInput.kind === "quit") {
537
+ return { mode, quit: true, changedRunDefaults };
538
+ }
539
+ if (agentsInput.kind === "back" || agentsInput.agents.length === 0) {
540
+ notice = currentMessages.tui.agentsUnchanged;
541
+ continue;
542
+ }
543
+ if (mode === "ask") {
544
+ const agents = normalizeTuiAskAgents(config, agentsInput.agents, currentMessages);
545
+ config.defaults = { ...(config.defaults ?? {}), askAgents: agents };
546
+ await writeExampleConfig(configPath, config);
547
+ changedRunDefaults = true;
548
+ notice = currentMessages.tui.askAgentsUpdated(agents.join(", "));
549
+ }
550
+ else {
551
+ const [agentA, agentB] = normalizeTuiDebateAgents(config, agentsInput.agents, currentMessages);
552
+ config.defaults = { ...(config.defaults ?? {}), agentA, agentB };
553
+ await writeExampleConfig(configPath, config);
554
+ changedRunDefaults = true;
555
+ notice = currentMessages.tui.debateAgentsUpdated(`${agentA} <-> ${agentB}`);
556
+ }
557
+ }
558
+ catch (error) {
559
+ notice = error instanceof Error ? error.message : String(error);
560
+ }
561
+ continue;
562
+ }
563
+ if (input.kind === "roles") {
564
+ try {
565
+ const rolesInput = input.roles.length > 0
566
+ ? { kind: "roles", roles: input.roles }
567
+ : await promptTuiRolesWizard(config, mode, currentMessages);
568
+ if (rolesInput.kind === "quit") {
569
+ return { mode, quit: true, changedRunDefaults };
570
+ }
571
+ if (rolesInput.kind === "back" || rolesInput.roles.length === 0) {
572
+ notice = currentMessages.tui.rolesUnchanged;
573
+ continue;
574
+ }
575
+ notice = applyTuiRoles(config, mode, rolesInput.roles, currentMessages);
576
+ await writeExampleConfig(configPath, config);
577
+ }
578
+ catch (error) {
579
+ notice = error instanceof Error ? error.message : String(error);
580
+ }
581
+ continue;
582
+ }
583
+ if (input.kind === "turns") {
584
+ if (mode === "ask") {
585
+ notice = currentMessages.tui.askTurnsNotice;
586
+ continue;
587
+ }
588
+ try {
589
+ validateTurns(input.turns, "--turns", currentMessages);
590
+ config.defaults = { ...(config.defaults ?? {}), turns: input.turns };
591
+ await writeExampleConfig(configPath, config);
592
+ changedRunDefaults = true;
593
+ notice = currentMessages.tui.turnsUpdated(input.turns);
594
+ }
595
+ catch (error) {
596
+ notice = error instanceof Error ? error.message : String(error);
597
+ }
598
+ continue;
599
+ }
600
+ if (input.kind === "summary") {
601
+ try {
602
+ const nextDefaults = { ...(config.defaults ?? {}) };
603
+ if (input.agent !== undefined) {
604
+ assertKnownAgent(config, input.agent, mode === "ask" ? "defaults.askSummaryAgent" : "defaults.summaryAgent", currentMessages);
605
+ }
606
+ if (mode === "ask") {
607
+ if (input.agent === undefined) {
608
+ delete nextDefaults.askSummaryAgent;
609
+ notice = currentMessages.tui.askSummaryFallback;
610
+ }
611
+ else {
612
+ nextDefaults.askSummaryAgent = input.agent;
613
+ notice = currentMessages.tui.askSummaryAgent(input.agent);
614
+ }
615
+ }
616
+ else if (input.agent === undefined) {
617
+ delete nextDefaults.summaryAgent;
618
+ notice = currentMessages.tui.debateSummaryFallback;
619
+ }
620
+ else {
621
+ nextDefaults.summaryAgent = input.agent;
622
+ notice = currentMessages.tui.debateSummaryAgent(input.agent);
623
+ }
624
+ config.defaults = nextDefaults;
625
+ await writeExampleConfig(configPath, config);
626
+ changedRunDefaults = true;
627
+ }
628
+ catch (error) {
629
+ notice = error instanceof Error ? error.message : String(error);
630
+ }
631
+ continue;
632
+ }
633
+ if (input.kind === "ollama-info") {
634
+ try {
635
+ notice = await formatTuiOllamaInfo(config, currentMessages);
636
+ }
637
+ catch (error) {
638
+ notice = error instanceof Error ? error.message : String(error);
639
+ }
640
+ continue;
641
+ }
642
+ if (input.kind === "ollama-model") {
643
+ try {
644
+ notice = await setTuiOllamaModel(configPath, config, input.model, currentMessages);
645
+ changedRunDefaults = true;
646
+ }
647
+ catch (error) {
648
+ notice = error instanceof Error ? error.message : String(error);
649
+ }
650
+ continue;
651
+ }
652
+ if (input.kind === "ollama-sync") {
653
+ try {
654
+ notice = await syncTuiOllamaModel(configPath, config, currentMessages);
655
+ changedRunDefaults = true;
656
+ }
657
+ catch (error) {
658
+ notice = error instanceof Error ? error.message : String(error);
659
+ }
660
+ continue;
661
+ }
662
+ }
663
+ }
664
+ async function formatTuiOllamaInfo(config, messages) {
665
+ const discovery = await discoverLocalTools();
666
+ const agent = config.agents["ollama-local"];
667
+ if (agent?.type !== "ollama") {
668
+ throw new Error(messages.config.ollamaModelNoAgent);
669
+ }
670
+ if (!discovery.ollama.available) {
671
+ return messages.tui.ollamaUnavailable(discovery.ollama.baseUrl);
672
+ }
673
+ const installed = discovery.ollama.models.length > 0
674
+ ? discovery.ollama.models.join(", ")
675
+ : messages.config.ollamaModelNoInstalledModels;
676
+ const api = `${discovery.ollama.baseUrl}`;
677
+ return messages.tui.ollamaInfo(agent.model, installed, api);
678
+ }
679
+ async function setTuiOllamaModel(configPath, config, model, messages) {
680
+ const trimmed = model.trim();
681
+ if (!trimmed) {
682
+ throw new Error(messages.tui.ollamaModelUsage);
683
+ }
684
+ const discovery = await discoverLocalTools();
685
+ const agent = config.agents["ollama-local"];
686
+ if (agent?.type !== "ollama") {
687
+ throw new Error(messages.config.ollamaModelNoAgent);
688
+ }
689
+ if (!discovery.ollama.models.includes(trimmed)) {
690
+ throw new Error(messages.config.ollamaModelUnavailable(trimmed));
691
+ }
692
+ const result = setOllamaModel(config, trimmed);
693
+ await writeExampleConfig(configPath, config);
694
+ return result
695
+ ? messages.config.ollamaModelUpdated(configPath, result.previousModel, result.nextModel)
696
+ : messages.config.ollamaModelNoChange(configPath, agent.model);
697
+ }
698
+ async function syncTuiOllamaModel(configPath, config, messages) {
699
+ const discovery = await discoverLocalTools();
700
+ const agent = config.agents["ollama-local"];
701
+ if (agent?.type !== "ollama") {
702
+ throw new Error(messages.config.ollamaModelNoAgent);
703
+ }
704
+ if (discovery.ollama.models.length === 0) {
705
+ throw new Error(messages.config.ollamaModelNoInstalledModels);
706
+ }
707
+ const result = syncOllamaModel(config, discovery);
708
+ if (!result) {
709
+ return messages.config.ollamaModelNoChange(configPath, agent.model);
710
+ }
711
+ await writeExampleConfig(configPath, config);
712
+ return messages.config.ollamaModelUpdated(configPath, result.previousModel, result.nextModel);
713
+ }
714
+ async function syncInteractiveDetectedAgents(configPath, config) {
715
+ const discovery = await discoverLocalTools();
716
+ const result = syncDetectedAgentsDetailed(config, discovery);
717
+ if (result.changed) {
718
+ await writeExampleConfig(configPath, config);
719
+ }
720
+ return {
721
+ addedAgents: result.addedAgents
722
+ };
723
+ }
724
+ function normalizeTuiDebateAgents(config, agents, messages) {
725
+ const unique = agents.map((agent) => agent.trim()).filter((agent, index, list) => agent && list.indexOf(agent) === index);
726
+ if (unique.length !== 2) {
727
+ throw new Error(messages.tui.debateAgentsUsage);
728
+ }
729
+ assertKnownAgent(config, unique[0], "defaults.agentA", messages);
730
+ assertKnownAgent(config, unique[1], "defaults.agentB", messages);
731
+ return [unique[0], unique[1]];
732
+ }
733
+ function normalizeTuiAskAgents(config, agents, messages) {
734
+ const unique = agents.map((agent) => agent.trim()).filter((agent, index, list) => agent && list.indexOf(agent) === index);
735
+ if (unique.length === 0) {
736
+ throw new Error(messages.tui.askAgentsUsage);
737
+ }
738
+ if (unique.length > MAX_ASK_AGENTS) {
739
+ throw new Error(messages.common.tooManyAskAgents(MAX_ASK_AGENTS));
740
+ }
741
+ unique.forEach((agent) => assertKnownAgent(config, agent, "defaults.askAgents", messages));
742
+ return unique;
743
+ }
744
+ async function runTuiAgentsWizard(configPath, config, messages, mode, inlineAgents = []) {
745
+ try {
746
+ const agentsInput = inlineAgents.length > 0
747
+ ? { kind: "agents", agents: inlineAgents }
748
+ : await promptTuiAgentsWizard(config, mode, messages);
749
+ if (agentsInput.kind === "quit") {
750
+ return { quit: true, changedRunDefaults: false };
751
+ }
752
+ if (agentsInput.kind === "back" || agentsInput.agents.length === 0) {
753
+ return { quit: false, changedRunDefaults: false };
754
+ }
755
+ const notice = applyTuiAgents(config, mode, agentsInput.agents, messages);
756
+ await writeExampleConfig(configPath, config);
757
+ return { notice, quit: false, changedRunDefaults: true };
758
+ }
759
+ catch (error) {
760
+ return { notice: messages.tui.agentsError(error instanceof Error ? error.message : String(error)), quit: false, changedRunDefaults: false };
761
+ }
762
+ }
763
+ async function runTuiRolesWizard(configPath, config, messages, mode, inlineRoles = []) {
764
+ try {
765
+ const rolesInput = inlineRoles.length > 0
766
+ ? { kind: "roles", roles: inlineRoles }
767
+ : await promptTuiRolesWizard(config, mode, messages);
768
+ if (rolesInput.kind === "quit") {
769
+ return { quit: true };
770
+ }
771
+ if (rolesInput.kind === "back" || rolesInput.roles.length === 0) {
772
+ return { quit: false };
773
+ }
774
+ const notice = applyTuiRoles(config, mode, rolesInput.roles, messages);
775
+ await writeExampleConfig(configPath, config);
776
+ return { notice, quit: false };
777
+ }
778
+ catch (error) {
779
+ return { notice: messages.tui.rolesError(error instanceof Error ? error.message : String(error)), quit: false };
780
+ }
781
+ }
782
+ function applyTuiAgents(config, mode, agentNames, messages) {
783
+ if (mode === "ask") {
784
+ const agents = normalizeTuiAskAgents(config, agentNames, messages);
785
+ config.defaults = { ...(config.defaults ?? {}), askAgents: agents };
786
+ return messages.tui.askAgentsUpdated(agents.join(", "));
787
+ }
788
+ const [agentA, agentB] = normalizeTuiDebateAgents(config, agentNames, messages);
789
+ config.defaults = { ...(config.defaults ?? {}), agentA, agentB };
790
+ return messages.tui.debateAgentsUpdated(`${agentA} <-> ${agentB}`);
791
+ }
792
+ function applyTuiRoles(config, mode, roleNames, messages) {
793
+ const agents = activeAgentsForMode(config, mode);
794
+ if (agents.length === 0) {
795
+ throw new Error(mode === "ask" ? messages.tui.noAskAgentsConfigured : messages.tui.noDebateAgentsConfigured);
796
+ }
797
+ const roles = normalizeTuiRoles(roleNames, agents, mode, messages);
798
+ agents.forEach((agent, index) => {
799
+ config.agents[agent].role = roles[index];
800
+ });
801
+ return mode === "ask"
802
+ ? messages.tui.askRolesUpdated(roles.join(", "))
803
+ : messages.tui.debateRolesUpdated(roles.join(" <-> "));
804
+ }
805
+ function activeAgentsForMode(config, mode) {
806
+ const defaults = config.defaults ?? {};
807
+ if (mode === "ask") {
808
+ if (defaults.askAgents && defaults.askAgents.length > 0) {
809
+ return defaults.askAgents.filter((agent) => Boolean(config.agents[agent]));
810
+ }
811
+ return [defaults.agentA, defaults.agentB].filter((agent) => Boolean(agent && config.agents[agent]));
812
+ }
813
+ return [defaults.agentA, defaults.agentB].filter((agent) => Boolean(agent && config.agents[agent]));
814
+ }
815
+ function normalizeTuiRoles(roleNames, agents, mode, messages) {
816
+ const roles = roleNames.map((role) => role.trim().toLowerCase()).filter(Boolean);
817
+ const expectedCount = agents.length;
818
+ if (roles.length < expectedCount) {
819
+ const agentLabel = mode === "ask"
820
+ ? agents.join(", ")
821
+ : agents.join(" <-> ");
822
+ throw new Error(messages.tui.rolesCountError(roles.length, expectedCount, agentLabel));
823
+ }
824
+ return roles.slice(0, expectedCount).map((role) => {
825
+ if (isAgentRole(role)) {
826
+ return role;
827
+ }
828
+ throw new Error(messages.tui.unknownRole(role, VALID_AGENT_ROLES.join(", ")));
829
+ });
830
+ }
831
+ function isAgentRole(value) {
832
+ return VALID_AGENT_ROLES.includes(value);
833
+ }
834
+ const VALID_AGENT_ROLES = ["implementer", "reviewer", "architect", "scout", "critic", "summarizer"];
304
835
  async function runOllamaModelsCommand(config, json) {
305
836
  const discovery = await discoverLocalTools();
306
837
  const agent = config.agents["ollama-local"];
@@ -371,7 +902,19 @@ function isNoneValue(value) {
371
902
  * @returns Chaîne résumant la paire d'agents, le nombre de réponses et l'agent de synthèse.
372
903
  */
373
904
  function formatDefaultsForMessage(defaults, messages) {
374
- return messages.config.defaultsSummary(defaults.agentA, defaults.agentB, turnsOrDefault(defaults.turns), defaults.summaryAgent);
905
+ return messages.config.defaultsSummary(defaults.agentA, defaults.agentB, turnsOrDefault(defaults.turns), defaults.summaryAgent, defaults.askSummaryAgent, defaults.mode, defaults.askAgents, defaults.interface);
906
+ }
907
+ function normalizeAskAgentsForConfig(config, agents, messages) {
908
+ const unique = agents
909
+ .map((agent) => agent.trim())
910
+ .filter((agent, index, list) => agent && list.indexOf(agent) === index);
911
+ if (unique.length > MAX_ASK_AGENTS) {
912
+ throw new Error(messages.common.tooManyAskAgents(MAX_ASK_AGENTS));
913
+ }
914
+ for (const agent of unique) {
915
+ assertKnownAgent(config, agent, "defaults.askAgents", messages);
916
+ }
917
+ return unique;
375
918
  }
376
919
  /**
377
920
  * Lève une erreur si `agentName` n'est pas déclaré dans la config.
@@ -400,38 +943,88 @@ function resolveAgentName(label, explicitValue, presetValue, defaultValue, messa
400
943
  }
401
944
  return resolved;
402
945
  }
946
+ function resolveSummaryAgentOption(explicitValue, defaults, mode) {
947
+ const explicit = optionalString(explicitValue);
948
+ if (explicit) {
949
+ return explicit;
950
+ }
951
+ if (mode === "ask") {
952
+ return defaults?.askSummaryAgent ?? defaults?.summaryAgent;
953
+ }
954
+ return defaults?.summaryAgent;
955
+ }
956
+ function parseModeFlag(value, messages) {
957
+ if (!value) {
958
+ return "debate";
959
+ }
960
+ if (value === "debate" || value === "ask") {
961
+ return value;
962
+ }
963
+ throw new Error(messages.common.unknownMode(value, "debate, ask"));
964
+ }
965
+ function parseInterfaceFlag(value, messages) {
966
+ if (!value) {
967
+ return "tui";
968
+ }
969
+ if (value === "tui" || value === "terminal") {
970
+ return value;
971
+ }
972
+ throw new Error(messages.common.unknownMode(value, "tui, terminal"));
973
+ }
974
+ function resolveAskAgents(explicitAgents, defaultAgents, fallbackAgents, messages) {
975
+ const selected = explicitAgents.length > 0
976
+ ? explicitAgents
977
+ : defaultAgents && defaultAgents.length > 0 ? defaultAgents : fallbackAgents;
978
+ const unique = selected.filter((agent, index) => agent.trim() && selected.indexOf(agent) === index);
979
+ if (unique.length > MAX_ASK_AGENTS) {
980
+ throw new Error(messages.common.tooManyAskAgents(MAX_ASK_AGENTS));
981
+ }
982
+ return unique;
983
+ }
403
984
  /**
404
985
  * Affiche un aperçu du prompt du premier tour sans appeler aucun agent (flag `--show-prompt`).
405
986
  * @param config - Config chargée.
406
987
  * @param options - Options du débat résolues.
407
988
  */
408
989
  function printPromptPreview(config, options, language, messages) {
409
- const agentConfig = config.agents[options.agentA];
990
+ const previewAgent = options.mode === "ask" ? options.askAgents?.[0] ?? options.agentA : options.agentA;
991
+ const peerName = options.mode === "ask" ? "independent-agents" : options.agentB;
992
+ const agentConfig = config.agents[previewAgent];
410
993
  if (!agentConfig) {
411
- throw new Error(messages.common.unknownAgent(options.agentA));
994
+ throw new Error(messages.common.unknownAgent(previewAgent));
412
995
  }
413
996
  const prompt = formatAgentPrompt({
414
997
  topic: options.topic,
415
998
  turn: 1,
416
- totalTurns: options.turns,
417
- selfName: options.agentA,
418
- peerName: options.agentB,
999
+ totalTurns: options.mode === "ask" ? options.askAgents?.length ?? 1 : options.turns,
1000
+ selfName: previewAgent,
1001
+ peerName,
419
1002
  selfRole: agentConfig.role,
1003
+ mode: options.mode === "ask" ? "ask" : "debate",
420
1004
  language: options.language,
421
1005
  session: options.session,
422
1006
  files: options.files,
423
1007
  transcript: []
424
1008
  });
425
1009
  console.log(messages.preview.title);
426
- console.log(messages.preview.agent(options.agentA, agentConfig.role));
427
- console.log(messages.preview.peer(options.agentB));
1010
+ console.log(messages.preview.agent(previewAgent, agentConfig.role));
1011
+ console.log(messages.preview.peer(peerName));
428
1012
  console.log(messages.preview.pullModels(options.pullModels));
429
- console.log(messages.preview.summary(options.summaryEnabled ? options.summaryAgent ?? options.agentB : messages.preview.disabled));
1013
+ console.log(messages.preview.summary(options.summaryEnabled ? previewSummaryAgent(options) : messages.preview.disabled));
430
1014
  console.log(messages.preview.interfaceLanguage(language));
431
1015
  console.log("");
432
1016
  console.log(prompt);
433
1017
  console.log("");
434
- console.log(messages.preview.note);
1018
+ console.log(options.mode === "ask" ? messages.preview.askNote : messages.preview.note);
1019
+ }
1020
+ function previewSummaryAgent(options) {
1021
+ if (options.summaryAgent) {
1022
+ return options.summaryAgent;
1023
+ }
1024
+ if (options.mode === "ask" && options.askAgents && options.askAgents.length > 0) {
1025
+ return options.askAgents[options.askAgents.length - 1] ?? options.agentB;
1026
+ }
1027
+ return options.agentB;
435
1028
  }
436
1029
  /**
437
1030
  * Extrait une chaîne non vide depuis une valeur de flag, ou renvoie `undefined`.
@@ -455,7 +1048,7 @@ function findRawLanguageFlag(args) {
455
1048
  return undefined;
456
1049
  }
457
1050
  /** Liste des kinds de renderer acceptés par `--renderer`. */
458
- const SUPPORTED_RENDERERS = ["auto", "pretty", "plain", "ndjson"];
1051
+ const SUPPORTED_RENDERERS = ["auto", "pretty", "plain", "tui", "ndjson"];
459
1052
  /**
460
1053
  * Instancie le renderer en fonction des flags CLI.
461
1054
  *
@@ -467,7 +1060,7 @@ const SUPPORTED_RENDERERS = ["auto", "pretty", "plain", "ndjson"];
467
1060
  *
468
1061
  * Lève si la valeur de `--renderer` n'est pas dans `SUPPORTED_RENDERERS`.
469
1062
  */
470
- function createRendererFromFlags(flags, plainOutputFallback, messages) {
1063
+ function createRendererFromFlags(flags, plainOutputFallback, defaultInterface, messages) {
471
1064
  const explicit = optionalString(flags.renderer);
472
1065
  if (explicit) {
473
1066
  if (!SUPPORTED_RENDERERS.includes(explicit)) {
@@ -481,14 +1074,41 @@ function createRendererFromFlags(flags, plainOutputFallback, messages) {
481
1074
  return createConsoleRenderer(true, messages);
482
1075
  case "pretty":
483
1076
  return createConsoleRenderer(false, messages);
1077
+ case "tui":
1078
+ return createTuiRenderer(messages);
484
1079
  case "auto":
485
- return createConsoleRenderer(plainOutputFallback, messages);
1080
+ return createAutoRenderer(flags, plainOutputFallback, defaultInterface, messages);
486
1081
  }
487
1082
  }
488
1083
  if (flags.json) {
489
1084
  return createNdjsonRenderer();
490
1085
  }
491
- return createConsoleRenderer(plainOutputFallback, messages);
1086
+ if (flags.tui) {
1087
+ return createTuiRenderer(messages);
1088
+ }
1089
+ if (flags.terminal || flags.plain || plainOutputFallback || defaultInterface === "terminal") {
1090
+ return createConsoleRenderer(true, messages);
1091
+ }
1092
+ return createAutoRenderer(flags, plainOutputFallback, defaultInterface, messages);
1093
+ }
1094
+ function createAutoRenderer(flags, plainOutputFallback, defaultInterface, messages) {
1095
+ if (flags.tui) {
1096
+ return createTuiRenderer(messages);
1097
+ }
1098
+ if (flags.terminal || flags.plain || plainOutputFallback || defaultInterface === "terminal") {
1099
+ return createConsoleRenderer(true, messages);
1100
+ }
1101
+ return process.stdout.isTTY ? createTuiRenderer(messages) : createConsoleRenderer(true, messages);
1102
+ }
1103
+ function shouldOpenTuiHome(parsed) {
1104
+ return parsed.command === "run"
1105
+ && !parsed.commandExplicit
1106
+ && parsed.positionals.length === 0
1107
+ && optionalString(parsed.flags.topic) === undefined
1108
+ && optionalString(parsed.flags.renderer) === undefined
1109
+ && parsed.flags.json !== true
1110
+ && parsed.flags.plain !== true
1111
+ && parsed.flags.terminal !== true;
492
1112
  }
493
1113
  /**
494
1114
  * Exécute la commande `palabre presets`.
@@ -527,6 +1147,32 @@ async function runPresetsCommand(flags) {
527
1147
  console.log("");
528
1148
  console.log(messages.presets.total(presets.length));
529
1149
  }
1150
+ async function runHistoryCommand(flags) {
1151
+ const configPath = optionalString(flags.config) ?? await resolveDefaultConfigPath();
1152
+ const config = await configExists(configPath)
1153
+ ? await loadConfig(configPath)
1154
+ : undefined;
1155
+ const language = resolveLanguage({
1156
+ explicitLanguage: optionalString(flags.language),
1157
+ configLanguage: config?.language
1158
+ });
1159
+ const messages = createTranslator(language);
1160
+ const entries = await listHistoryEntries(resolveOutputDir(config?.outputDir));
1161
+ if (flags.json) {
1162
+ process.stdout.write(JSON.stringify({ v: 1, history: entries }) + "\n");
1163
+ return;
1164
+ }
1165
+ console.log(messages.tui.historyTitle);
1166
+ console.log("");
1167
+ if (entries.length === 0) {
1168
+ console.log(messages.tui.historyEmpty);
1169
+ return;
1170
+ }
1171
+ for (const entry of entries) {
1172
+ console.log(`- ${entry.date || entry.fileName} | ${entry.mode} | ${entry.topic}`);
1173
+ console.log(` ${entry.path}`);
1174
+ }
1175
+ }
530
1176
  async function runContextCommand(flags, positionals) {
531
1177
  const language = resolveLanguage({ explicitLanguage: optionalString(flags.language) });
532
1178
  const messages = createTranslator(language);
@@ -590,7 +1236,7 @@ function printAgents(configPath, config, discovery, messages) {
590
1236
  }
591
1237
  }
592
1238
  console.log("");
593
- 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));
1239
+ 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));
594
1240
  }
595
1241
  /**
596
1242
  * Renvoie un libellé indiquant si l'agent est agent A, agent B ou agent de synthèse par défaut.
@@ -605,6 +1251,8 @@ function formatAgentDefaults(name, config, messages) {
605
1251
  labels.push(messages.agents.defaultAgentB);
606
1252
  if (config.defaults?.summaryAgent === name)
607
1253
  labels.push(messages.agents.defaultSummary);
1254
+ if (config.defaults?.askSummaryAgent === name)
1255
+ labels.push(messages.agents.defaultAskSummary);
608
1256
  return labels.join(", ");
609
1257
  }
610
1258
  /**
@@ -657,9 +1305,9 @@ function printInitDiscovery(discovery, config, messages) {
657
1305
  console.log(messages.init.localDetectionTitle);
658
1306
  console.log(`- Codex CLI: ${formatCommandDetection(discovery.codex, messages)}`);
659
1307
  console.log(`- Claude CLI: ${formatCommandDetection(discovery.claude, messages)}`);
660
- console.log(`- Gemini CLI: ${formatCommandDetection(discovery.gemini, messages)}`);
661
1308
  console.log(`- Antigravity CLI: ${formatCommandDetection(discovery.antigravity, messages)}`);
662
1309
  console.log(`- OpenCode CLI: ${formatCommandDetection(discovery.opencode, messages)}`);
1310
+ console.log(`- Mistral Vibe CLI: ${formatCommandDetection(discovery.vibe, messages)}`);
663
1311
  console.log(`- Ollama API: ${formatOllamaDetection(discovery.ollama, messages)}`);
664
1312
  console.log("");
665
1313
  console.log(config.defaults?.agentA && config.defaults.agentB