palabre 0.9.0 → 0.9.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,29 +1,35 @@
1
1
  #!/usr/bin/env node
2
- import { assertRunnableConfig, configExists, createConfigFromDiscovery, DEFAULT_CONFIG_PATH, GLOBAL_CONFIG_PATH, loadConfig, resolveDefaultConfigPath, resolveOutputDir, setOllamaBaseUrl, setOllamaModel, syncDetectedAgentsDetailed, syncOllamaModel, writeExampleConfig } from "./config.js";
2
+ import { assertRunnableConfig, configExists, createConfigFromDiscovery, loadConfig, resolveDefaultConfigPath, resolveOutputDir, setOllamaModel, syncDetectedAgentsDetailed, syncOllamaModel, writeExampleConfig } from "./config.js";
3
3
  import { loadProjectInputs } from "./context.js";
4
- import { buildContextScan } from "./contextScan.js";
5
- import { discoverLocalTools } from "./discovery.js";
4
+ import { discoverLocalTools, discoverLocalToolsForConfig } from "./discovery.js";
6
5
  import { runDoctor } from "./doctor.js";
7
6
  import { AdapterError, formatAdapterError } from "./errors.js";
8
7
  import { runConfigWizard } from "./configWizard.js";
9
8
  import { createTranslator, DEFAULT_LANGUAGE, parseLanguage, resolveLanguage } from "./i18n.js";
10
- import { DEFAULT_TURNS, parseTurnsFlag, turnsOrDefault, validateTurns } from "./limits.js";
9
+ import { DEFAULT_TURNS, parseTurnsFlag, turnsOrDefault } from "./limits.js";
11
10
  import { formatAgentPrompt } from "./prompt.js";
12
11
  import { runNewWizard } from "./new.js";
13
- import { listAgentsWithAvailability, listPresetNames, listPresetsWithAvailability, resolvePreset } from "./presets.js";
12
+ import { listPresetNames, resolvePreset } from "./presets.js";
14
13
  import { listHistoryEntries } from "./history.js";
15
14
  import { createConsoleRenderer } from "./renderers/console.js";
16
15
  import { createNdjsonRenderer } from "./renderers/ndjson.js";
17
- import { createTuiRenderer, promptTuiAgentsWizard, promptTuiConfigCommand, promptTuiHomeTopic, promptTuiRolesWizard, renderTuiConfig, renderTuiHelp, renderTuiHistory, renderTuiHome, renderTuiUpdate } from "./renderers/tui.js";
16
+ import { createTuiRenderer, promptTuiHomeTopic, renderTuiHelp, renderTuiHistory, renderTuiHome, renderTuiUpdate } from "./renderers/tui.js";
18
17
  import { MAX_ASK_AGENTS, runAsk, runDebate } from "./orchestrator.js";
19
18
  import { writeDebateMarkdown } from "./output.js";
20
- import { applySourceUpdate, formatUpdateInstructions, getUpdateInfo } from "./update.js";
21
- import { createSessionContext } from "./session.js";
19
+ import { formatUpdateInstructions, getUpdateInfo } from "./update.js";
22
20
  import { getStringListFlag, parseArgs } from "./args.js";
23
- import { askAgentSeedsForMode, clearTuiRunOverrides } from "./tuiState.js";
24
- import { detectedAgentNames, detectionForCommand, isRetiredAgentName } from "./agentRegistry.js";
25
- import { configuredOllamaTargets, DEFAULT_OLLAMA_BASE_URL, normalizeOllamaBaseUrl, OllamaUrlError, resolveOllamaBaseUrl } from "./ollamaUrl.js";
21
+ import { clearTuiRunOverrides } from "./tuiState.js";
22
+ import { OllamaUrlError } from "./ollamaUrl.js";
26
23
  import { compareSemver, getLatestPackageVersion, getPackageVersion } from "./version.js";
24
+ import { runAgentsCommand } from "./commands/agents.js";
25
+ import { runContextCommand } from "./commands/context.js";
26
+ import { runHistoryCommand } from "./commands/history.js";
27
+ import { runInitCommand } from "./commands/init.js";
28
+ import { runPresetsCommand } from "./commands/presets.js";
29
+ import { runUpdateCommand } from "./commands/update.js";
30
+ import { optionalString } from "./commands/shared.js";
31
+ import { runTuiAgentsWizard, runTuiConfigLoop, runTuiRolesWizard, syncInteractiveDetectedAgents } from "./tuiController.js";
32
+ import { resolveRunOptions } from "./runOptions.js";
27
33
  /** Point d'entrée principal du CLI Palabre. Dispatche vers la commande appropriée selon les arguments. */
28
34
  async function main() {
29
35
  const rawArgs = process.argv.slice(2);
@@ -65,42 +71,11 @@ async function main() {
65
71
  return;
66
72
  }
67
73
  if (parsed.command === "update") {
68
- const info = await getUpdateInfo(await getPackageVersion());
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));
74
+ await runUpdateCommand(parsed.flags);
84
75
  return;
85
76
  }
86
77
  if (parsed.command === "init" || parsed.command === "setup") {
87
- const initConfigPath = optionalString(parsed.flags.config) ?? (parsed.flags.local ? DEFAULT_CONFIG_PATH : GLOBAL_CONFIG_PATH);
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);
78
+ await runInitCommand(parsed.flags);
104
79
  return;
105
80
  }
106
81
  const configPath = optionalString(parsed.flags.config) ?? await resolveDefaultConfigPath();
@@ -283,35 +258,15 @@ async function main() {
283
258
  if (!topic) {
284
259
  throw new Error(messages.common.topicRequired);
285
260
  }
286
- const mode = parseModeFlag(optionalString(parsed.flags.mode) ?? config.defaults?.mode, messages);
287
- const explicitAskAgents = getStringListFlag(parsed.flags.agents);
288
- const askAgentSeeds = askAgentSeedsForMode(mode, explicitAskAgents, config.defaults?.askAgents);
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,
261
+ const options = resolveRunOptions({
262
+ flags: parsed.flags,
263
+ config,
294
264
  language,
295
265
  topic,
296
- agentA,
297
- agentB,
298
- askAgents,
299
- turns: parseTurnsFlag(parsed.flags.turns, config.defaults?.turns ?? DEFAULT_TURNS, "--turns", messages),
300
- session: createSessionContext(),
301
266
  files: context.files,
302
- modelA: optionalString(parsed.flags["model-a"]),
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),
267
+ preset,
313
268
  signal: debateAbortSignal()
314
- };
269
+ }, messages);
315
270
  if (parsed.flags["show-prompt"]) {
316
271
  printContextWarnings(context.warnings, messages);
317
272
  printPromptPreview(config, options, language, messages);
@@ -332,7 +287,7 @@ async function main() {
332
287
  if (!stayInTuiAfterSession) {
333
288
  return;
334
289
  }
335
- tuiMode = mode;
290
+ tuiMode = options.mode;
336
291
  for (;;) {
337
292
  const nextInput = await promptTuiHomeTopic(tuiMode, messages, { notice: tuiNotice });
338
293
  tuiNotice = undefined;
@@ -364,39 +319,6 @@ function debateAbortSignal() {
364
319
  process.once("SIGTERM", abort);
365
320
  return controller.signal;
366
321
  }
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
322
  /**
401
323
  * Exécute la commande `config` : wizard interactif ou mise à jour directe des paramètres par défaut.
402
324
  * @param flags - Flags parsés depuis la ligne de commande.
@@ -517,382 +439,6 @@ async function runConfigCommand(flags) {
517
439
  }
518
440
  await runConfigWizard(configPath, config, messages);
519
441
  }
520
- async function runTuiConfigLoop(configPath, config, messages, initialMode) {
521
- let mode = initialMode;
522
- let notice;
523
- let currentMessages = messages;
524
- let changedRunDefaults = false;
525
- for (;;) {
526
- renderTuiConfig(config, configPath, mode, currentMessages, { message: notice });
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
442
  async function runOllamaModelsCommand(config, json) {
897
443
  const discovery = await discoverLocalToolsForConfig(config);
898
444
  const agent = config.agents["ollama-local"];
@@ -988,32 +534,6 @@ function assertKnownAgent(config, agentName, fieldName, messages) {
988
534
  throw new Error(messages.common.unknownAgentForField(fieldName, agentName, Object.keys(config.agents).join(", ")));
989
535
  }
990
536
  }
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
537
  function parseModeFlag(value, messages) {
1018
538
  if (!value) {
1019
539
  return "debate";
@@ -1032,16 +552,6 @@ function parseInterfaceFlag(value, messages) {
1032
552
  }
1033
553
  throw new Error(messages.common.unknownMode(value, "tui, terminal"));
1034
554
  }
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
555
  /**
1046
556
  * Affiche un aperçu du prompt du premier tour sans appeler aucun agent (flag `--show-prompt`).
1047
557
  * @param config - Config chargée.
@@ -1071,29 +581,13 @@ function printPromptPreview(config, options, language, messages) {
1071
581
  console.log(messages.preview.agent(previewAgent, agentConfig.role));
1072
582
  console.log(messages.preview.peer(peerName));
1073
583
  console.log(messages.preview.pullModels(options.pullModels));
1074
- console.log(messages.preview.summary(options.summaryEnabled ? previewSummaryAgent(options) : messages.preview.disabled));
584
+ console.log(messages.preview.summary(options.summaryEnabled ? options.summaryAgent : messages.preview.disabled));
1075
585
  console.log(messages.preview.interfaceLanguage(language));
1076
586
  console.log("");
1077
587
  console.log(prompt);
1078
588
  console.log("");
1079
589
  console.log(options.mode === "ask" ? messages.preview.askNote : messages.preview.note);
1080
590
  }
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
591
  /**
1098
592
  * Pré-lit seulement `--language`/`--lang` dans les arguments bruts pour localiser
1099
593
  * les erreurs qui peuvent survenir avant le parsing complet ou le chargement de config.
@@ -1171,98 +665,6 @@ function shouldOpenTuiHome(parsed) {
1171
665
  && parsed.flags.plain !== true
1172
666
  && parsed.flags.terminal !== true;
1173
667
  }
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
668
  /**
1267
669
  * Écrit les avertissements de contexte sur `stderr`.
1268
670
  * @param warnings - Messages d'avertissement issus du chargement des fichiers de contexte.
@@ -1272,152 +674,6 @@ function printContextWarnings(warnings, messages) {
1272
674
  process.stderr.write(`${messages.renderers.warningPrefix} ${warning}\n`);
1273
675
  }
1274
676
  }
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
677
  /** Affiche le texte d'aide complet sur `stdout`. */
1422
678
  function printHelp(messages, command) {
1423
679
  const commandHelp = command ? messages.help.renderCommand(command) : undefined;