palabre 0.7.0 → 0.8.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/dist/index.js CHANGED
@@ -7,13 +7,14 @@ 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
14
  import { createConsoleRenderer } from "./renderers/console.js";
15
15
  import { createNdjsonRenderer } from "./renderers/ndjson.js";
16
- import { runDebate } from "./orchestrator.js";
16
+ import { createTuiRenderer, promptTuiAgentsWizard, promptTuiConfigCommand, promptTuiHomeTopic, promptTuiRolesWizard, renderTuiConfig, renderTuiHelp, renderTuiHome } from "./renderers/tui.js";
17
+ import { MAX_ASK_AGENTS, runAsk, runDebate } from "./orchestrator.js";
17
18
  import { writeDebateMarkdown } from "./output.js";
18
19
  import { applySourceUpdate, formatUpdateInstructions, getUpdateInfo } from "./update.js";
19
20
  import { createSessionContext } from "./session.js";
@@ -35,7 +36,7 @@ async function main() {
35
36
  return;
36
37
  }
37
38
  if (parsed.command === "doctor") {
38
- const result = await runDoctor(optionalString(parsed.flags.config), Boolean(parsed.flags.plain), optionalString(parsed.flags.language));
39
+ const result = await runDoctor(optionalString(parsed.flags.config), Boolean(parsed.flags.plain || parsed.flags.terminal), optionalString(parsed.flags.language));
39
40
  console.log(result.output);
40
41
  process.exitCode = result.ok ? 0 : 1;
41
42
  return;
@@ -106,12 +107,106 @@ async function main() {
106
107
  return;
107
108
  }
108
109
  const config = await loadConfig(configPath);
109
- const language = resolveLanguage({
110
+ let language = resolveLanguage({
110
111
  explicitLanguage: optionalString(parsed.flags.language),
111
112
  configLanguage: config.language
112
113
  });
113
- const messages = createTranslator(language);
114
+ let messages = createTranslator(language);
114
115
  assertRunnableConfig(config, messages, configPath);
116
+ if (shouldOpenTuiHome(parsed)) {
117
+ let tuiMode = config.defaults?.mode ?? "debate";
118
+ const tuiVersion = await getPackageVersion();
119
+ let tuiNotice;
120
+ for (;;) {
121
+ renderTuiHome(config, configPath, messages, { mode: tuiMode, version: tuiVersion });
122
+ const tuiInput = await promptTuiHomeTopic(tuiMode, messages, { notice: tuiNotice });
123
+ tuiNotice = undefined;
124
+ if (!tuiInput) {
125
+ return;
126
+ }
127
+ if (tuiInput.kind === "help") {
128
+ renderTuiHelp(messages);
129
+ const nextInput = await promptTuiHomeTopic(tuiMode, messages);
130
+ if (!nextInput) {
131
+ return;
132
+ }
133
+ if (nextInput.kind === "help") {
134
+ continue;
135
+ }
136
+ if (nextInput.kind === "roles") {
137
+ const result = await runTuiRolesWizard(configPath, config, messages, tuiMode, nextInput.roles);
138
+ if (result.quit)
139
+ return;
140
+ tuiNotice = result.notice;
141
+ continue;
142
+ }
143
+ if (nextInput.kind === "agents") {
144
+ const result = await runTuiAgentsWizard(configPath, config, messages, tuiMode, nextInput.agents);
145
+ if (result.quit)
146
+ return;
147
+ tuiNotice = result.notice;
148
+ continue;
149
+ }
150
+ if (nextInput.kind === "mode") {
151
+ tuiMode = nextInput.mode;
152
+ continue;
153
+ }
154
+ if (nextInput.kind === "config") {
155
+ const result = await runTuiConfigLoop(configPath, config, messages, tuiMode);
156
+ if (result.quit)
157
+ return;
158
+ tuiMode = result.mode;
159
+ language = resolveLanguage({ explicitLanguage: optionalString(parsed.flags.language), configLanguage: config.language });
160
+ messages = createTranslator(language);
161
+ continue;
162
+ }
163
+ if (nextInput.kind === "new") {
164
+ parsed.command = "new";
165
+ parsed.commandExplicit = true;
166
+ }
167
+ else {
168
+ parsed.flags.topic = nextInput.topic;
169
+ }
170
+ }
171
+ else if (tuiInput.kind === "mode") {
172
+ tuiMode = tuiInput.mode;
173
+ continue;
174
+ }
175
+ else if (tuiInput.kind === "roles") {
176
+ const result = await runTuiRolesWizard(configPath, config, messages, tuiMode, tuiInput.roles);
177
+ if (result.quit)
178
+ return;
179
+ tuiNotice = result.notice;
180
+ continue;
181
+ }
182
+ else if (tuiInput.kind === "agents") {
183
+ const result = await runTuiAgentsWizard(configPath, config, messages, tuiMode, tuiInput.agents);
184
+ if (result.quit)
185
+ return;
186
+ tuiNotice = result.notice;
187
+ continue;
188
+ }
189
+ else if (tuiInput.kind === "config") {
190
+ const result = await runTuiConfigLoop(configPath, config, messages, tuiMode);
191
+ if (result.quit)
192
+ return;
193
+ tuiMode = result.mode;
194
+ language = resolveLanguage({ explicitLanguage: optionalString(parsed.flags.language), configLanguage: config.language });
195
+ messages = createTranslator(language);
196
+ continue;
197
+ }
198
+ else if (tuiInput.kind === "new") {
199
+ parsed.command = "new";
200
+ parsed.commandExplicit = true;
201
+ }
202
+ else {
203
+ parsed.flags.topic = tuiInput.topic;
204
+ }
205
+ parsed.flags.mode = tuiMode;
206
+ parsed.flags.renderer = "tui";
207
+ break;
208
+ }
209
+ }
115
210
  if (parsed.command === "new") {
116
211
  const selection = await runNewWizard(config, messages);
117
212
  if (!selection) {
@@ -141,6 +236,10 @@ async function main() {
141
236
  parsed.flags.files = selection.files;
142
237
  if (selection.context.length > 0)
143
238
  parsed.flags.context = selection.context;
239
+ if (selection.mode)
240
+ parsed.flags.mode = selection.mode;
241
+ if (selection.askAgents && selection.askAgents.length > 0)
242
+ parsed.flags.agents = selection.askAgents;
144
243
  }
145
244
  const topic = optionalString(parsed.flags.topic) ?? "";
146
245
  const context = await loadProjectInputs(getStringListFlag(parsed.flags.files), getStringListFlag(parsed.flags.context), process.cwd(), messages);
@@ -149,22 +248,30 @@ async function main() {
149
248
  if (!topic) {
150
249
  throw new Error(messages.common.topicRequired);
151
250
  }
251
+ const mode = parseModeFlag(optionalString(parsed.flags.mode) ?? config.defaults?.mode, messages);
252
+ const explicitAskAgents = getStringListFlag(parsed.flags.agents);
253
+ const askAgentSeeds = explicitAskAgents.length > 0 ? explicitAskAgents : config.defaults?.askAgents ?? [];
254
+ const agentA = resolveAgentName("agent A", parsed.flags["agent-a"], preset?.agentA, askAgentSeeds[0] ?? config.defaults?.agentA, messages);
255
+ const agentB = resolveAgentName("agent B", parsed.flags["agent-b"], preset?.agentB, askAgentSeeds[1] ?? askAgentSeeds[0] ?? config.defaults?.agentB, messages);
256
+ const askAgents = mode === "ask" ? resolveAskAgents(explicitAskAgents, config.defaults?.askAgents, [agentA, agentB], messages) : undefined;
152
257
  const options = {
258
+ mode,
153
259
  language,
154
260
  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),
261
+ agentA,
262
+ agentB,
263
+ askAgents,
157
264
  turns: parseTurnsFlag(parsed.flags.turns, config.defaults?.turns ?? DEFAULT_TURNS, "--turns", messages),
158
265
  session: createSessionContext(),
159
266
  files: context.files,
160
267
  modelA: optionalString(parsed.flags["model-a"]),
161
268
  modelB: optionalString(parsed.flags["model-b"]),
162
269
  pullModels: Boolean(parsed.flags["pull-models"]),
163
- summaryAgent: optionalString(parsed.flags["summary-agent"]) ?? config.defaults?.summaryAgent,
270
+ summaryAgent: resolveSummaryAgentOption(parsed.flags["summary-agent"], config.defaults, mode),
164
271
  summaryModel: optionalString(parsed.flags["summary-model"]),
165
272
  summaryEnabled: !parsed.flags["no-summary"],
166
273
  earlyStopOnAgreement: !parsed.flags["no-early-stop"],
167
- plainOutput: Boolean(parsed.flags.plain),
274
+ plainOutput: Boolean(parsed.flags.plain || parsed.flags.terminal),
168
275
  signal: debateAbortSignal()
169
276
  };
170
277
  if (parsed.flags["show-prompt"]) {
@@ -172,9 +279,11 @@ async function main() {
172
279
  printPromptPreview(config, options, language, messages);
173
280
  return;
174
281
  }
175
- const renderer = createRendererFromFlags(parsed.flags, options.plainOutput, messages);
282
+ const renderer = createRendererFromFlags(parsed.flags, options.plainOutput, config.defaults?.interface, messages);
176
283
  context.warnings.forEach((warning) => renderer.warning(warning));
177
- const result = await runDebate(config, options, renderer, messages);
284
+ const result = options.mode === "ask"
285
+ ? await runAsk(config, options, renderer, messages)
286
+ : await runDebate(config, options, renderer, messages);
178
287
  const outputPath = await writeDebateMarkdown(resolveOutputDir(config.outputDir), result.options, result.messages, result.summary, result.stopReason, messages, result.failure);
179
288
  renderer.done(outputPath);
180
289
  if (result.failure) {
@@ -257,8 +366,18 @@ async function runConfigCommand(flags) {
257
366
  const defaultAgents = getStringListFlag(flags["set-defaults"]);
258
367
  const hasTurnsFlag = flags.turns !== undefined;
259
368
  const summaryAgentValue = optionalString(flags["summary-agent"]);
369
+ const askSummaryAgentValue = optionalString(flags["ask-summary-agent"]);
370
+ const interfaceValue = optionalString(flags.interface);
371
+ const modeValue = optionalString(flags.mode);
372
+ const askAgentsValue = getStringListFlag(flags["ask-agents"]);
260
373
  const languageValue = explicitLanguage;
261
- const changesDefaults = defaultAgents.length > 0 || hasTurnsFlag || summaryAgentValue !== undefined;
374
+ const changesDefaults = defaultAgents.length > 0
375
+ || hasTurnsFlag
376
+ || summaryAgentValue !== undefined
377
+ || askSummaryAgentValue !== undefined
378
+ || interfaceValue !== undefined
379
+ || modeValue !== undefined
380
+ || askAgentsValue.length > 0;
262
381
  if (changesDefaults || languageValue !== undefined) {
263
382
  const nextDefaults = { ...(config.defaults ?? {}) };
264
383
  if (defaultAgents.length > 0) {
@@ -283,6 +402,24 @@ async function runConfigCommand(flags) {
283
402
  nextDefaults.summaryAgent = summaryAgentValue;
284
403
  }
285
404
  }
405
+ if (askSummaryAgentValue !== undefined) {
406
+ if (isNoneValue(askSummaryAgentValue)) {
407
+ delete nextDefaults.askSummaryAgent;
408
+ }
409
+ else {
410
+ assertKnownAgent(config, askSummaryAgentValue, "defaults.askSummaryAgent", messages);
411
+ nextDefaults.askSummaryAgent = askSummaryAgentValue;
412
+ }
413
+ }
414
+ if (interfaceValue !== undefined) {
415
+ nextDefaults.interface = parseInterfaceFlag(interfaceValue, messages);
416
+ }
417
+ if (modeValue !== undefined) {
418
+ nextDefaults.mode = parseModeFlag(modeValue, messages);
419
+ }
420
+ if (askAgentsValue.length > 0) {
421
+ nextDefaults.askAgents = normalizeAskAgentsForConfig(config, askAgentsValue, messages);
422
+ }
286
423
  if (languageValue !== undefined) {
287
424
  config.language = parseLanguage(languageValue, "--language");
288
425
  }
@@ -301,6 +438,258 @@ async function runConfigCommand(flags) {
301
438
  }
302
439
  await runConfigWizard(configPath, config, messages);
303
440
  }
441
+ async function runTuiConfigLoop(configPath, config, messages, initialMode) {
442
+ let mode = initialMode;
443
+ let notice;
444
+ let currentMessages = messages;
445
+ for (;;) {
446
+ renderTuiConfig(config, configPath, mode, currentMessages, { message: notice });
447
+ notice = undefined;
448
+ const input = await promptTuiConfigCommand(mode, currentMessages);
449
+ if (input.kind === "quit") {
450
+ return { mode, quit: true };
451
+ }
452
+ if (input.kind === "back") {
453
+ return { mode, quit: false };
454
+ }
455
+ if (input.kind === "unknown") {
456
+ notice = input.message;
457
+ continue;
458
+ }
459
+ if (input.kind === "mode") {
460
+ mode = mode === "ask" ? "debate" : "ask";
461
+ notice = mode === "ask" ? currentMessages.tui.askConfigMode : currentMessages.tui.debateConfigMode;
462
+ continue;
463
+ }
464
+ if (input.kind === "default-mode") {
465
+ config.defaults = { ...(config.defaults ?? {}), mode };
466
+ await writeExampleConfig(configPath, config);
467
+ notice = mode === "ask" ? currentMessages.tui.askDefaultMode : currentMessages.tui.debateDefaultMode;
468
+ continue;
469
+ }
470
+ if (input.kind === "interface") {
471
+ config.defaults = { ...(config.defaults ?? {}), interface: input.interfaceName };
472
+ await writeExampleConfig(configPath, config);
473
+ notice = currentMessages.tui.interfaceDefault(input.interfaceName);
474
+ continue;
475
+ }
476
+ if (input.kind === "language") {
477
+ config.language = parseLanguage(input.language, "--language");
478
+ await writeExampleConfig(configPath, config);
479
+ currentMessages = createTranslator(config.language ?? DEFAULT_LANGUAGE);
480
+ notice = currentMessages.tui.languageUpdated(input.language);
481
+ continue;
482
+ }
483
+ if (input.kind === "agents") {
484
+ try {
485
+ const agentsInput = input.agents.length > 0
486
+ ? { kind: "agents", agents: input.agents }
487
+ : await promptTuiAgentsWizard(config, mode, currentMessages);
488
+ if (agentsInput.kind === "quit") {
489
+ return { mode, quit: true };
490
+ }
491
+ if (agentsInput.kind === "back" || agentsInput.agents.length === 0) {
492
+ notice = currentMessages.tui.agentsUnchanged;
493
+ continue;
494
+ }
495
+ if (mode === "ask") {
496
+ const agents = normalizeTuiAskAgents(config, agentsInput.agents, currentMessages);
497
+ config.defaults = { ...(config.defaults ?? {}), askAgents: agents };
498
+ await writeExampleConfig(configPath, config);
499
+ notice = currentMessages.tui.askAgentsUpdated(agents.join(", "));
500
+ }
501
+ else {
502
+ const [agentA, agentB] = normalizeTuiDebateAgents(config, agentsInput.agents, currentMessages);
503
+ config.defaults = { ...(config.defaults ?? {}), agentA, agentB };
504
+ await writeExampleConfig(configPath, config);
505
+ notice = currentMessages.tui.debateAgentsUpdated(`${agentA} <-> ${agentB}`);
506
+ }
507
+ }
508
+ catch (error) {
509
+ notice = error instanceof Error ? error.message : String(error);
510
+ }
511
+ continue;
512
+ }
513
+ if (input.kind === "roles") {
514
+ try {
515
+ const rolesInput = input.roles.length > 0
516
+ ? { kind: "roles", roles: input.roles }
517
+ : await promptTuiRolesWizard(config, mode, currentMessages);
518
+ if (rolesInput.kind === "quit") {
519
+ return { mode, quit: true };
520
+ }
521
+ if (rolesInput.kind === "back" || rolesInput.roles.length === 0) {
522
+ notice = currentMessages.tui.rolesUnchanged;
523
+ continue;
524
+ }
525
+ notice = applyTuiRoles(config, mode, rolesInput.roles, currentMessages);
526
+ await writeExampleConfig(configPath, config);
527
+ }
528
+ catch (error) {
529
+ notice = error instanceof Error ? error.message : String(error);
530
+ }
531
+ continue;
532
+ }
533
+ if (input.kind === "turns") {
534
+ if (mode === "ask") {
535
+ notice = currentMessages.tui.askTurnsNotice;
536
+ continue;
537
+ }
538
+ try {
539
+ validateTurns(input.turns, "--turns", currentMessages);
540
+ config.defaults = { ...(config.defaults ?? {}), turns: input.turns };
541
+ await writeExampleConfig(configPath, config);
542
+ notice = currentMessages.tui.turnsUpdated(input.turns);
543
+ }
544
+ catch (error) {
545
+ notice = error instanceof Error ? error.message : String(error);
546
+ }
547
+ continue;
548
+ }
549
+ if (input.kind === "summary") {
550
+ try {
551
+ const nextDefaults = { ...(config.defaults ?? {}) };
552
+ if (input.agent !== undefined) {
553
+ assertKnownAgent(config, input.agent, mode === "ask" ? "defaults.askSummaryAgent" : "defaults.summaryAgent", currentMessages);
554
+ }
555
+ if (mode === "ask") {
556
+ if (input.agent === undefined) {
557
+ delete nextDefaults.askSummaryAgent;
558
+ notice = currentMessages.tui.askSummaryFallback;
559
+ }
560
+ else {
561
+ nextDefaults.askSummaryAgent = input.agent;
562
+ notice = currentMessages.tui.askSummaryAgent(input.agent);
563
+ }
564
+ }
565
+ else if (input.agent === undefined) {
566
+ delete nextDefaults.summaryAgent;
567
+ notice = currentMessages.tui.debateSummaryFallback;
568
+ }
569
+ else {
570
+ nextDefaults.summaryAgent = input.agent;
571
+ notice = currentMessages.tui.debateSummaryAgent(input.agent);
572
+ }
573
+ config.defaults = nextDefaults;
574
+ await writeExampleConfig(configPath, config);
575
+ }
576
+ catch (error) {
577
+ notice = error instanceof Error ? error.message : String(error);
578
+ }
579
+ }
580
+ }
581
+ }
582
+ function normalizeTuiDebateAgents(config, agents, messages) {
583
+ const unique = agents.map((agent) => agent.trim()).filter((agent, index, list) => agent && list.indexOf(agent) === index);
584
+ if (unique.length !== 2) {
585
+ throw new Error(messages.tui.debateAgentsUsage);
586
+ }
587
+ assertKnownAgent(config, unique[0], "defaults.agentA", messages);
588
+ assertKnownAgent(config, unique[1], "defaults.agentB", messages);
589
+ return [unique[0], unique[1]];
590
+ }
591
+ function normalizeTuiAskAgents(config, agents, messages) {
592
+ const unique = agents.map((agent) => agent.trim()).filter((agent, index, list) => agent && list.indexOf(agent) === index);
593
+ if (unique.length === 0) {
594
+ throw new Error(messages.tui.askAgentsUsage);
595
+ }
596
+ if (unique.length > MAX_ASK_AGENTS) {
597
+ throw new Error(messages.common.tooManyAskAgents(MAX_ASK_AGENTS));
598
+ }
599
+ unique.forEach((agent) => assertKnownAgent(config, agent, "defaults.askAgents", messages));
600
+ return unique;
601
+ }
602
+ async function runTuiAgentsWizard(configPath, config, messages, mode, inlineAgents = []) {
603
+ try {
604
+ const agentsInput = inlineAgents.length > 0
605
+ ? { kind: "agents", agents: inlineAgents }
606
+ : await promptTuiAgentsWizard(config, mode, messages);
607
+ if (agentsInput.kind === "quit") {
608
+ return { quit: true };
609
+ }
610
+ if (agentsInput.kind === "back" || agentsInput.agents.length === 0) {
611
+ return { quit: false };
612
+ }
613
+ const notice = applyTuiAgents(config, mode, agentsInput.agents, messages);
614
+ await writeExampleConfig(configPath, config);
615
+ return { notice, quit: false };
616
+ }
617
+ catch (error) {
618
+ return { notice: messages.tui.agentsError(error instanceof Error ? error.message : String(error)), quit: false };
619
+ }
620
+ }
621
+ async function runTuiRolesWizard(configPath, config, messages, mode, inlineRoles = []) {
622
+ try {
623
+ const rolesInput = inlineRoles.length > 0
624
+ ? { kind: "roles", roles: inlineRoles }
625
+ : await promptTuiRolesWizard(config, mode, messages);
626
+ if (rolesInput.kind === "quit") {
627
+ return { quit: true };
628
+ }
629
+ if (rolesInput.kind === "back" || rolesInput.roles.length === 0) {
630
+ return { quit: false };
631
+ }
632
+ const notice = applyTuiRoles(config, mode, rolesInput.roles, messages);
633
+ await writeExampleConfig(configPath, config);
634
+ return { notice, quit: false };
635
+ }
636
+ catch (error) {
637
+ return { notice: messages.tui.rolesError(error instanceof Error ? error.message : String(error)), quit: false };
638
+ }
639
+ }
640
+ function applyTuiAgents(config, mode, agentNames, messages) {
641
+ if (mode === "ask") {
642
+ const agents = normalizeTuiAskAgents(config, agentNames, messages);
643
+ config.defaults = { ...(config.defaults ?? {}), askAgents: agents };
644
+ return messages.tui.askAgentsUpdated(agents.join(", "));
645
+ }
646
+ const [agentA, agentB] = normalizeTuiDebateAgents(config, agentNames, messages);
647
+ config.defaults = { ...(config.defaults ?? {}), agentA, agentB };
648
+ return messages.tui.debateAgentsUpdated(`${agentA} <-> ${agentB}`);
649
+ }
650
+ function applyTuiRoles(config, mode, roleNames, messages) {
651
+ const agents = activeAgentsForMode(config, mode);
652
+ if (agents.length === 0) {
653
+ throw new Error(mode === "ask" ? messages.tui.noAskAgentsConfigured : messages.tui.noDebateAgentsConfigured);
654
+ }
655
+ const roles = normalizeTuiRoles(roleNames, agents, mode, messages);
656
+ agents.forEach((agent, index) => {
657
+ config.agents[agent].role = roles[index];
658
+ });
659
+ return mode === "ask"
660
+ ? messages.tui.askRolesUpdated(roles.join(", "))
661
+ : messages.tui.debateRolesUpdated(roles.join(" <-> "));
662
+ }
663
+ function activeAgentsForMode(config, mode) {
664
+ const defaults = config.defaults ?? {};
665
+ if (mode === "ask") {
666
+ if (defaults.askAgents && defaults.askAgents.length > 0) {
667
+ return defaults.askAgents.filter((agent) => Boolean(config.agents[agent]));
668
+ }
669
+ return [defaults.agentA, defaults.agentB].filter((agent) => Boolean(agent && config.agents[agent]));
670
+ }
671
+ return [defaults.agentA, defaults.agentB].filter((agent) => Boolean(agent && config.agents[agent]));
672
+ }
673
+ function normalizeTuiRoles(roleNames, agents, mode, messages) {
674
+ const roles = roleNames.map((role) => role.trim().toLowerCase()).filter(Boolean);
675
+ const expectedCount = agents.length;
676
+ if (roles.length < expectedCount) {
677
+ const agentLabel = mode === "ask"
678
+ ? agents.join(", ")
679
+ : agents.join(" <-> ");
680
+ throw new Error(messages.tui.rolesCountError(roles.length, expectedCount, agentLabel));
681
+ }
682
+ return roles.slice(0, expectedCount).map((role) => {
683
+ if (isAgentRole(role)) {
684
+ return role;
685
+ }
686
+ throw new Error(messages.tui.unknownRole(role, VALID_AGENT_ROLES.join(", ")));
687
+ });
688
+ }
689
+ function isAgentRole(value) {
690
+ return VALID_AGENT_ROLES.includes(value);
691
+ }
692
+ const VALID_AGENT_ROLES = ["implementer", "reviewer", "architect", "scout", "critic", "summarizer"];
304
693
  async function runOllamaModelsCommand(config, json) {
305
694
  const discovery = await discoverLocalTools();
306
695
  const agent = config.agents["ollama-local"];
@@ -371,7 +760,19 @@ function isNoneValue(value) {
371
760
  * @returns Chaîne résumant la paire d'agents, le nombre de réponses et l'agent de synthèse.
372
761
  */
373
762
  function formatDefaultsForMessage(defaults, messages) {
374
- return messages.config.defaultsSummary(defaults.agentA, defaults.agentB, turnsOrDefault(defaults.turns), defaults.summaryAgent);
763
+ return messages.config.defaultsSummary(defaults.agentA, defaults.agentB, turnsOrDefault(defaults.turns), defaults.summaryAgent, defaults.askSummaryAgent, defaults.mode, defaults.askAgents, defaults.interface);
764
+ }
765
+ function normalizeAskAgentsForConfig(config, agents, messages) {
766
+ const unique = agents
767
+ .map((agent) => agent.trim())
768
+ .filter((agent, index, list) => agent && list.indexOf(agent) === index);
769
+ if (unique.length > MAX_ASK_AGENTS) {
770
+ throw new Error(messages.common.tooManyAskAgents(MAX_ASK_AGENTS));
771
+ }
772
+ for (const agent of unique) {
773
+ assertKnownAgent(config, agent, "defaults.askAgents", messages);
774
+ }
775
+ return unique;
375
776
  }
376
777
  /**
377
778
  * Lève une erreur si `agentName` n'est pas déclaré dans la config.
@@ -400,38 +801,88 @@ function resolveAgentName(label, explicitValue, presetValue, defaultValue, messa
400
801
  }
401
802
  return resolved;
402
803
  }
804
+ function resolveSummaryAgentOption(explicitValue, defaults, mode) {
805
+ const explicit = optionalString(explicitValue);
806
+ if (explicit) {
807
+ return explicit;
808
+ }
809
+ if (mode === "ask") {
810
+ return defaults?.askSummaryAgent ?? defaults?.summaryAgent;
811
+ }
812
+ return defaults?.summaryAgent;
813
+ }
814
+ function parseModeFlag(value, messages) {
815
+ if (!value) {
816
+ return "debate";
817
+ }
818
+ if (value === "debate" || value === "ask") {
819
+ return value;
820
+ }
821
+ throw new Error(messages.common.unknownMode(value, "debate, ask"));
822
+ }
823
+ function parseInterfaceFlag(value, messages) {
824
+ if (!value) {
825
+ return "tui";
826
+ }
827
+ if (value === "tui" || value === "terminal") {
828
+ return value;
829
+ }
830
+ throw new Error(messages.common.unknownMode(value, "tui, terminal"));
831
+ }
832
+ function resolveAskAgents(explicitAgents, defaultAgents, fallbackAgents, messages) {
833
+ const selected = explicitAgents.length > 0
834
+ ? explicitAgents
835
+ : defaultAgents && defaultAgents.length > 0 ? defaultAgents : fallbackAgents;
836
+ const unique = selected.filter((agent, index) => agent.trim() && selected.indexOf(agent) === index);
837
+ if (unique.length > MAX_ASK_AGENTS) {
838
+ throw new Error(messages.common.tooManyAskAgents(MAX_ASK_AGENTS));
839
+ }
840
+ return unique;
841
+ }
403
842
  /**
404
843
  * Affiche un aperçu du prompt du premier tour sans appeler aucun agent (flag `--show-prompt`).
405
844
  * @param config - Config chargée.
406
845
  * @param options - Options du débat résolues.
407
846
  */
408
847
  function printPromptPreview(config, options, language, messages) {
409
- const agentConfig = config.agents[options.agentA];
848
+ const previewAgent = options.mode === "ask" ? options.askAgents?.[0] ?? options.agentA : options.agentA;
849
+ const peerName = options.mode === "ask" ? "independent-agents" : options.agentB;
850
+ const agentConfig = config.agents[previewAgent];
410
851
  if (!agentConfig) {
411
- throw new Error(messages.common.unknownAgent(options.agentA));
852
+ throw new Error(messages.common.unknownAgent(previewAgent));
412
853
  }
413
854
  const prompt = formatAgentPrompt({
414
855
  topic: options.topic,
415
856
  turn: 1,
416
- totalTurns: options.turns,
417
- selfName: options.agentA,
418
- peerName: options.agentB,
857
+ totalTurns: options.mode === "ask" ? options.askAgents?.length ?? 1 : options.turns,
858
+ selfName: previewAgent,
859
+ peerName,
419
860
  selfRole: agentConfig.role,
861
+ mode: options.mode === "ask" ? "ask" : "debate",
420
862
  language: options.language,
421
863
  session: options.session,
422
864
  files: options.files,
423
865
  transcript: []
424
866
  });
425
867
  console.log(messages.preview.title);
426
- console.log(messages.preview.agent(options.agentA, agentConfig.role));
427
- console.log(messages.preview.peer(options.agentB));
868
+ console.log(messages.preview.agent(previewAgent, agentConfig.role));
869
+ console.log(messages.preview.peer(peerName));
428
870
  console.log(messages.preview.pullModels(options.pullModels));
429
- console.log(messages.preview.summary(options.summaryEnabled ? options.summaryAgent ?? options.agentB : messages.preview.disabled));
871
+ console.log(messages.preview.summary(options.summaryEnabled ? previewSummaryAgent(options) : messages.preview.disabled));
430
872
  console.log(messages.preview.interfaceLanguage(language));
431
873
  console.log("");
432
874
  console.log(prompt);
433
875
  console.log("");
434
- console.log(messages.preview.note);
876
+ console.log(options.mode === "ask" ? messages.preview.askNote : messages.preview.note);
877
+ }
878
+ function previewSummaryAgent(options) {
879
+ if (options.summaryAgent) {
880
+ return options.summaryAgent;
881
+ }
882
+ if (options.mode === "ask" && options.askAgents && options.askAgents.length > 0) {
883
+ return options.askAgents[options.askAgents.length - 1] ?? options.agentB;
884
+ }
885
+ return options.agentB;
435
886
  }
436
887
  /**
437
888
  * Extrait une chaîne non vide depuis une valeur de flag, ou renvoie `undefined`.
@@ -455,7 +906,7 @@ function findRawLanguageFlag(args) {
455
906
  return undefined;
456
907
  }
457
908
  /** Liste des kinds de renderer acceptés par `--renderer`. */
458
- const SUPPORTED_RENDERERS = ["auto", "pretty", "plain", "ndjson"];
909
+ const SUPPORTED_RENDERERS = ["auto", "pretty", "plain", "tui", "ndjson"];
459
910
  /**
460
911
  * Instancie le renderer en fonction des flags CLI.
461
912
  *
@@ -467,7 +918,7 @@ const SUPPORTED_RENDERERS = ["auto", "pretty", "plain", "ndjson"];
467
918
  *
468
919
  * Lève si la valeur de `--renderer` n'est pas dans `SUPPORTED_RENDERERS`.
469
920
  */
470
- function createRendererFromFlags(flags, plainOutputFallback, messages) {
921
+ function createRendererFromFlags(flags, plainOutputFallback, defaultInterface, messages) {
471
922
  const explicit = optionalString(flags.renderer);
472
923
  if (explicit) {
473
924
  if (!SUPPORTED_RENDERERS.includes(explicit)) {
@@ -481,14 +932,41 @@ function createRendererFromFlags(flags, plainOutputFallback, messages) {
481
932
  return createConsoleRenderer(true, messages);
482
933
  case "pretty":
483
934
  return createConsoleRenderer(false, messages);
935
+ case "tui":
936
+ return createTuiRenderer(messages);
484
937
  case "auto":
485
- return createConsoleRenderer(plainOutputFallback, messages);
938
+ return createAutoRenderer(flags, plainOutputFallback, defaultInterface, messages);
486
939
  }
487
940
  }
488
941
  if (flags.json) {
489
942
  return createNdjsonRenderer();
490
943
  }
491
- return createConsoleRenderer(plainOutputFallback, messages);
944
+ if (flags.tui) {
945
+ return createTuiRenderer(messages);
946
+ }
947
+ if (flags.terminal || flags.plain || plainOutputFallback || defaultInterface === "terminal") {
948
+ return createConsoleRenderer(true, messages);
949
+ }
950
+ return createAutoRenderer(flags, plainOutputFallback, defaultInterface, messages);
951
+ }
952
+ function createAutoRenderer(flags, plainOutputFallback, defaultInterface, messages) {
953
+ if (flags.tui) {
954
+ return createTuiRenderer(messages);
955
+ }
956
+ if (flags.terminal || flags.plain || plainOutputFallback || defaultInterface === "terminal") {
957
+ return createConsoleRenderer(true, messages);
958
+ }
959
+ return process.stdout.isTTY ? createTuiRenderer(messages) : createConsoleRenderer(true, messages);
960
+ }
961
+ function shouldOpenTuiHome(parsed) {
962
+ return parsed.command === "run"
963
+ && !parsed.commandExplicit
964
+ && parsed.positionals.length === 0
965
+ && optionalString(parsed.flags.topic) === undefined
966
+ && optionalString(parsed.flags.renderer) === undefined
967
+ && parsed.flags.json !== true
968
+ && parsed.flags.plain !== true
969
+ && parsed.flags.terminal !== true;
492
970
  }
493
971
  /**
494
972
  * Exécute la commande `palabre presets`.
@@ -590,7 +1068,7 @@ function printAgents(configPath, config, discovery, messages) {
590
1068
  }
591
1069
  }
592
1070
  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));
1071
+ 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
1072
  }
595
1073
  /**
596
1074
  * Renvoie un libellé indiquant si l'agent est agent A, agent B ou agent de synthèse par défaut.
@@ -605,6 +1083,8 @@ function formatAgentDefaults(name, config, messages) {
605
1083
  labels.push(messages.agents.defaultAgentB);
606
1084
  if (config.defaults?.summaryAgent === name)
607
1085
  labels.push(messages.agents.defaultSummary);
1086
+ if (config.defaults?.askSummaryAgent === name)
1087
+ labels.push(messages.agents.defaultAskSummary);
608
1088
  return labels.join(", ");
609
1089
  }
610
1090
  /**
@@ -660,6 +1140,7 @@ function printInitDiscovery(discovery, config, messages) {
660
1140
  console.log(`- Gemini CLI: ${formatCommandDetection(discovery.gemini, messages)}`);
661
1141
  console.log(`- Antigravity CLI: ${formatCommandDetection(discovery.antigravity, messages)}`);
662
1142
  console.log(`- OpenCode CLI: ${formatCommandDetection(discovery.opencode, messages)}`);
1143
+ console.log(`- Mistral Vibe CLI: ${formatCommandDetection(discovery.vibe, messages)}`);
663
1144
  console.log(`- Ollama API: ${formatOllamaDetection(discovery.ollama, messages)}`);
664
1145
  console.log("");
665
1146
  console.log(config.defaults?.agentA && config.defaults.agentB