palabre 0.9.1 → 0.10.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.
Files changed (52) hide show
  1. package/README.md +6 -0
  2. package/dist/adapters/cli-pty.js +30 -10
  3. package/dist/adapters/cli-shared.js +73 -0
  4. package/dist/adapters/cli.js +40 -77
  5. package/dist/adapters/index.js +1 -0
  6. package/dist/adapters/ollama.js +32 -32
  7. package/dist/adapters/terminal.js +1 -0
  8. package/dist/args.js +1 -0
  9. package/dist/commands/agents.js +7 -1
  10. package/dist/commands/context.js +7 -1
  11. package/dist/commands/history.js +6 -1
  12. package/dist/commands/init.js +8 -3
  13. package/dist/commands/presets.js +6 -1
  14. package/dist/commands/shared.js +5 -1
  15. package/dist/commands/update.js +6 -1
  16. package/dist/config.js +17 -1
  17. package/dist/configWizard.js +5 -4
  18. package/dist/context.js +1 -0
  19. package/dist/contextScan.js +4 -3
  20. package/dist/discovery.js +1 -0
  21. package/dist/doctor.js +1 -0
  22. package/dist/errors.js +4 -0
  23. package/dist/exec.js +1 -0
  24. package/dist/history.js +10 -0
  25. package/dist/i18n.js +2 -0
  26. package/dist/index.js +170 -112
  27. package/dist/limits.js +4 -0
  28. package/dist/messages/adapter-errors.js +26 -2
  29. package/dist/messages/config.js +6 -0
  30. package/dist/messages/index.js +1 -0
  31. package/dist/messages/renderers.js +10 -2
  32. package/dist/messages/tui.js +8 -2
  33. package/dist/new.js +65 -11
  34. package/dist/ollamaUrl.js +20 -0
  35. package/dist/orchestrator.js +103 -150
  36. package/dist/output.js +1 -0
  37. package/dist/presets.js +1 -0
  38. package/dist/prompt.js +1 -0
  39. package/dist/renderers/console.js +1 -1
  40. package/dist/renderers/tui-prompts.js +343 -0
  41. package/dist/renderers/tui-renderer.js +228 -0
  42. package/dist/renderers/tui-screens.js +352 -0
  43. package/dist/renderers/tui-theme.js +356 -0
  44. package/dist/renderers/tui.js +7 -1086
  45. package/dist/runOptions.js +33 -2
  46. package/dist/session.js +1 -0
  47. package/dist/tuiController.js +61 -16
  48. package/dist/tuiState.js +4 -0
  49. package/dist/types.js +1 -0
  50. package/dist/update.js +1 -0
  51. package/dist/version.js +1 -0
  52. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,5 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import { assertRunnableConfig, configExists, createConfigFromDiscovery, loadConfig, resolveDefaultConfigPath, resolveOutputDir, setOllamaModel, syncDetectedAgentsDetailed, syncOllamaModel, writeExampleConfig } from "./config.js";
2
+ /**
3
+ * @file Point d'entrée CLI de Palabre : parsing des arguments, dispatch vers les
4
+ * commandes leaf (`agents`, `presets`, `history`, `context`, `update`, `init`, `config`),
5
+ * résolution de la config/langue, boucle TUI d'accueil et lancement d'un débat ou d'une
6
+ * requête `ask` via l'orchestrateur.
7
+ */
8
+ import { assertRunnableConfig, configExists, createConfigFromDiscovery, loadConfig, resolveDefaultConfigPath, resolveOutputDir, setOllamaModel, syncDetectedAgentsDetailed, syncOllamaModel, writeConfig } from "./config.js";
3
9
  import { loadProjectInputs } from "./context.js";
4
10
  import { discoverLocalTools, discoverLocalToolsForConfig } from "./discovery.js";
5
11
  import { runDoctor } from "./doctor.js";
@@ -19,7 +25,7 @@ import { writeDebateMarkdown } from "./output.js";
19
25
  import { formatUpdateInstructions, getUpdateInfo } from "./update.js";
20
26
  import { getStringListFlag, parseArgs } from "./args.js";
21
27
  import { clearTuiRunOverrides } from "./tuiState.js";
22
- import { OllamaUrlError } from "./ollamaUrl.js";
28
+ import { formatOllamaUrlError, OllamaUrlError } from "./ollamaUrl.js";
23
29
  import { compareSemver, getLatestPackageVersion, getPackageVersion } from "./version.js";
24
30
  import { runAgentsCommand } from "./commands/agents.js";
25
31
  import { runContextCommand } from "./commands/context.js";
@@ -29,7 +35,7 @@ import { runPresetsCommand } from "./commands/presets.js";
29
35
  import { runUpdateCommand } from "./commands/update.js";
30
36
  import { optionalString } from "./commands/shared.js";
31
37
  import { runTuiAgentsWizard, runTuiConfigLoop, runTuiRolesWizard, syncInteractiveDetectedAgents } from "./tuiController.js";
32
- import { resolveRunOptions } from "./runOptions.js";
38
+ import { parseInterfaceFlag, parseModeFlag, resolveRunOptions } from "./runOptions.js";
33
39
  /** Point d'entrée principal du CLI Palabre. Dispatche vers la commande appropriée selon les arguments. */
34
40
  async function main() {
35
41
  const rawArgs = process.argv.slice(2);
@@ -90,7 +96,7 @@ async function main() {
90
96
  configLanguage: config.language
91
97
  });
92
98
  const messages = createTranslator(config.language);
93
- await writeExampleConfig(configPath, config);
99
+ await writeConfig(configPath, config);
94
100
  if (!shouldOpenTuiHome(parsed)) {
95
101
  console.log(messages.init.editConfigThenRerun(configPath));
96
102
  return;
@@ -113,78 +119,94 @@ async function main() {
113
119
  let tuiVersion = "";
114
120
  let tuiLatestVersion;
115
121
  const handleTuiHomeInput = async (tuiInput) => {
116
- if (!tuiInput) {
117
- return "quit";
118
- }
119
- if (tuiInput.kind === "help") {
120
- renderTuiHelp(messages);
121
- const nextInput = await promptTuiHomeTopic(tuiMode, messages);
122
- return handleTuiHomeInput(nextInput);
123
- }
124
- if (tuiInput.kind === "history") {
125
- renderTuiHistory(await listHistoryEntries(resolveOutputDir(config.outputDir)), messages);
126
- const nextInput = await promptTuiHomeTopic(tuiMode, messages);
127
- return handleTuiHomeInput(nextInput);
128
- }
129
- if (tuiInput.kind === "update") {
130
- const info = await getUpdateInfo(tuiVersion);
131
- renderTuiUpdate(formatUpdateInstructions(info, messages), messages);
132
- const nextInput = await promptTuiHomeTopic(tuiMode, messages);
133
- return handleTuiHomeInput(nextInput);
134
- }
135
- if (tuiInput.kind === "home") {
136
- return "continue";
137
- }
138
- if (tuiInput.kind === "roles") {
139
- const result = await runTuiRolesWizard(configPath, config, messages, tuiMode, tuiInput.roles);
140
- if (result.quit)
141
- return "quit";
142
- tuiNotice = result.notice;
143
- return "continue";
144
- }
145
- if (tuiInput.kind === "agents") {
146
- const result = await runTuiAgentsWizard(configPath, config, messages, tuiMode, tuiInput.agents);
147
- if (result.quit)
148
- return "quit";
149
- tuiNotice = result.notice;
150
- resetTuiRunOverridesOnNextTopic ||= Boolean(result.changedRunDefaults);
151
- return "continue";
152
- }
153
- if (tuiInput.kind === "mode") {
154
- tuiMode = tuiInput.mode;
155
- return "continue";
156
- }
157
- if (tuiInput.kind === "config") {
158
- const result = await runTuiConfigLoop(configPath, config, messages, tuiMode);
159
- if (result.quit)
122
+ let input = tuiInput;
123
+ // Les vues informatives (/help, /history, /update) réaffichent le prompt d'accueil
124
+ // puis reprennent le traitement avec la nouvelle saisie, d'où la boucle.
125
+ for (;;) {
126
+ if (!input) {
160
127
  return "quit";
161
- tuiMode = result.mode;
162
- resetTuiRunOverridesOnNextTopic ||= result.changedRunDefaults;
163
- language = resolveLanguage({ explicitLanguage: optionalString(parsed.flags.language), configLanguage: config.language });
164
- messages = createTranslator(language);
165
- return "continue";
166
- }
167
- if (tuiInput.kind === "new") {
168
- parsed.command = "new";
169
- parsed.commandExplicit = true;
170
- delete parsed.flags.topic;
171
- return "run";
172
- }
173
- if (tuiInput.kind === "retry") {
174
- if (!optionalString(parsed.flags.topic)) {
175
- tuiNotice = messages.tui.retryUnavailable;
128
+ }
129
+ if (input.kind === "help") {
130
+ renderTuiHelp(messages);
131
+ input = await promptTuiHomeTopic(tuiMode, messages);
132
+ continue;
133
+ }
134
+ if (input.kind === "history") {
135
+ renderTuiHistory(await listHistoryEntries(resolveOutputDir(config.outputDir)), messages);
136
+ input = await promptTuiHomeTopic(tuiMode, messages);
137
+ continue;
138
+ }
139
+ if (input.kind === "update") {
140
+ const info = await getUpdateInfo(tuiVersion);
141
+ renderTuiUpdate(formatUpdateInstructions(info, messages), messages);
142
+ input = await promptTuiHomeTopic(tuiMode, messages);
143
+ continue;
144
+ }
145
+ if (input.kind === "home") {
176
146
  return "continue";
177
147
  }
178
- return "retry";
179
- }
180
- parsed.command = "";
181
- parsed.commandExplicit = false;
182
- if (hasCompletedTuiSession || resetTuiRunOverridesOnNextTopic) {
183
- clearTuiRunOverrides(parsed.flags);
184
- resetTuiRunOverridesOnNextTopic = false;
148
+ if (input.kind === "roles") {
149
+ const result = await runTuiRolesWizard(configPath, config, messages, tuiMode, input.roles);
150
+ if (result.quit)
151
+ return "quit";
152
+ tuiNotice = result.notice;
153
+ return "continue";
154
+ }
155
+ if (input.kind === "agents") {
156
+ const result = await runTuiAgentsWizard(configPath, config, messages, tuiMode, input.agents);
157
+ if (result.quit)
158
+ return "quit";
159
+ tuiNotice = result.notice;
160
+ resetTuiRunOverridesOnNextTopic ||= Boolean(result.changedRunDefaults);
161
+ return "continue";
162
+ }
163
+ if (input.kind === "mode") {
164
+ tuiMode = input.mode;
165
+ return "continue";
166
+ }
167
+ if (input.kind === "config") {
168
+ const result = await runTuiConfigLoop(configPath, config, messages, tuiMode);
169
+ if (result.quit)
170
+ return "quit";
171
+ tuiMode = result.mode;
172
+ resetTuiRunOverridesOnNextTopic ||= result.changedRunDefaults;
173
+ language = resolveLanguage({ explicitLanguage: optionalString(parsed.flags.language), configLanguage: config.language });
174
+ messages = createTranslator(language);
175
+ return "continue";
176
+ }
177
+ if (input.kind === "new") {
178
+ parsed.command = "new";
179
+ parsed.commandExplicit = true;
180
+ delete parsed.flags.topic;
181
+ return "run";
182
+ }
183
+ if (input.kind === "retry") {
184
+ if (!optionalString(parsed.flags.topic)) {
185
+ tuiNotice = messages.tui.retryUnavailable;
186
+ return "continue";
187
+ }
188
+ return "retry";
189
+ }
190
+ if (!input.topic) {
191
+ // Saisie réduite à des flags inline (ex. "--context src") : re-prompter au lieu de sortir en erreur.
192
+ tuiNotice = messages.common.topicRequired;
193
+ return "continue";
194
+ }
195
+ parsed.command = "";
196
+ parsed.commandExplicit = false;
197
+ if (hasCompletedTuiSession || resetTuiRunOverridesOnNextTopic) {
198
+ clearTuiRunOverrides(parsed.flags);
199
+ resetTuiRunOverridesOnNextTopic = false;
200
+ }
201
+ parsed.flags.topic = input.topic;
202
+ if (input.files && input.files.length > 0) {
203
+ parsed.flags.files = input.files;
204
+ }
205
+ if (input.context && input.context.length > 0) {
206
+ parsed.flags.context = input.context;
207
+ }
208
+ return "run";
185
209
  }
186
- parsed.flags.topic = tuiInput.topic;
187
- return "run";
188
210
  };
189
211
  if (shouldOpenTuiHome(parsed)) {
190
212
  const [syncResult, currentVersion, latestVersion] = await Promise.all([
@@ -218,6 +240,25 @@ async function main() {
218
240
  if (parsed.command === "new") {
219
241
  const selection = await runNewWizard(config, messages);
220
242
  if (!selection) {
243
+ if (stayInTuiAfterSession) {
244
+ tuiNotice = messages.new.cancelled;
245
+ parsed.command = "";
246
+ parsed.commandExplicit = false;
247
+ for (;;) {
248
+ renderTuiHome(config, configPath, messages, { mode: tuiMode, version: tuiVersion, latestVersion: tuiLatestVersion });
249
+ const nextInput = await promptTuiHomeTopic(tuiMode, messages, { notice: tuiNotice });
250
+ tuiNotice = undefined;
251
+ const action = await handleTuiHomeInput(nextInput);
252
+ if (action === "quit")
253
+ return;
254
+ if (action === "continue" || action === "retry")
255
+ continue;
256
+ parsed.flags.mode = tuiMode;
257
+ parsed.flags.renderer = "tui";
258
+ break;
259
+ }
260
+ continue;
261
+ }
221
262
  console.log(messages.new.cancelled);
222
263
  return;
223
264
  }
@@ -308,6 +349,7 @@ async function main() {
308
349
  }
309
350
  }
310
351
  }
352
+ /** Construit un signal d'annulation déclenché sur `SIGINT`/`SIGTERM` pour interrompre un débat en cours. */
311
353
  function debateAbortSignal() {
312
354
  const controller = new AbortController();
313
355
  const abort = () => {
@@ -328,7 +370,7 @@ async function runConfigCommand(flags) {
328
370
  const explicitLanguage = optionalString(flags.language);
329
371
  if (!(await configExists(configPath))) {
330
372
  const messages = createTranslator(resolveLanguage({ explicitLanguage }));
331
- await writeExampleConfig(configPath);
373
+ await writeConfig(configPath);
332
374
  console.log(messages.config.createdForConfig(configPath));
333
375
  return;
334
376
  }
@@ -339,7 +381,7 @@ async function runConfigCommand(flags) {
339
381
  });
340
382
  const messages = createTranslator(language);
341
383
  if (flags["ollama-models"]) {
342
- await runOllamaModelsCommand(config, Boolean(flags.json));
384
+ await runOllamaModelsCommand(config, Boolean(flags.json), messages);
343
385
  return;
344
386
  }
345
387
  const setOllamaModelValue = optionalString(flags["set-ollama-model"]);
@@ -358,7 +400,7 @@ async function runConfigCommand(flags) {
358
400
  console.log(messages.config.syncNoMissing(configPath));
359
401
  return;
360
402
  }
361
- await writeExampleConfig(configPath, config);
403
+ await writeConfig(configPath, config);
362
404
  console.log(result.addedAgents.length > 0
363
405
  ? messages.config.syncAdded(configPath, result.addedAgents.join(", "))
364
406
  : messages.config.syncRefreshed(configPath));
@@ -427,19 +469,25 @@ async function runConfigCommand(flags) {
427
469
  if (changesDefaults) {
428
470
  config.defaults = nextDefaults;
429
471
  }
430
- await writeExampleConfig(configPath, config);
472
+ await writeConfig(configPath, config);
431
473
  console.log(messages.config.updated(configPath, formatDefaultsForMessage(config.defaults ?? {}, messages), config.language ?? DEFAULT_LANGUAGE));
432
474
  return;
433
475
  }
434
476
  if (flags["clear-defaults"]) {
435
477
  delete config.defaults;
436
- await writeExampleConfig(configPath, config);
478
+ await writeConfig(configPath, config);
437
479
  console.log(messages.config.cleared(configPath));
438
480
  return;
439
481
  }
440
482
  await runConfigWizard(configPath, config, messages);
441
483
  }
442
- async function runOllamaModelsCommand(config, json) {
484
+ /**
485
+ * Affiche l'état de l'agent Ollama local (modèle courant, disponibilité de l'API, modèles installés).
486
+ * @param config - Config chargée.
487
+ * @param json - Si `true`, affiche le résultat en JSON plutôt qu'en texte lisible.
488
+ * @param messages - Dictionnaire localisé pour la sortie texte.
489
+ */
490
+ async function runOllamaModelsCommand(config, json, messages) {
443
491
  const discovery = await discoverLocalToolsForConfig(config);
444
492
  const agent = config.agents["ollama-local"];
445
493
  const currentModel = agent?.type === "ollama" ? agent.model : null;
@@ -456,10 +504,16 @@ async function runOllamaModelsCommand(config, json) {
456
504
  console.log(JSON.stringify(payload, null, 2));
457
505
  return;
458
506
  }
459
- console.log(`ollama-local: ${currentModel ?? "(non configuré)"}`);
460
- console.log(`Ollama API: ${discovery.ollama.available ? "joignable" : "indisponible"} (${discovery.ollama.baseUrl})`);
461
- console.log(`Modèles installés: ${discovery.ollama.models.length > 0 ? discovery.ollama.models.join(", ") : "(aucun)"}`);
507
+ console.log(messages.config.ollamaModelsCurrent(currentModel));
508
+ console.log(messages.config.ollamaModelsApi(discovery.ollama.available, discovery.ollama.baseUrl));
509
+ console.log(messages.config.ollamaModelsInstalled(discovery.ollama.models));
462
510
  }
511
+ /**
512
+ * Change le modèle configuré pour l'agent `ollama-local` après vérification qu'il est bien installé.
513
+ * @param configPath - Chemin du fichier de config à mettre à jour.
514
+ * @param config - Config chargée.
515
+ * @param model - Nom du modèle Ollama à définir.
516
+ */
463
517
  async function runSetOllamaModelCommand(configPath, config, model, messages) {
464
518
  const trimmed = model.trim();
465
519
  if (!trimmed) {
@@ -474,11 +528,16 @@ async function runSetOllamaModelCommand(configPath, config, model, messages) {
474
528
  throw new Error(messages.config.ollamaModelUnavailable(trimmed));
475
529
  }
476
530
  const result = setOllamaModel(config, trimmed);
477
- await writeExampleConfig(configPath, config);
531
+ await writeConfig(configPath, config);
478
532
  console.log(result
479
533
  ? messages.config.ollamaModelUpdated(configPath, result.previousModel, result.nextModel)
480
534
  : messages.config.ollamaModelNoChange(configPath, agent.model));
481
535
  }
536
+ /**
537
+ * Aligne automatiquement le modèle de l'agent `ollama-local` sur un modèle réellement installé.
538
+ * @param configPath - Chemin du fichier de config à mettre à jour.
539
+ * @param config - Config chargée.
540
+ */
482
541
  async function runSyncOllamaModelCommand(configPath, config, messages) {
483
542
  const discovery = await discoverLocalToolsForConfig(config);
484
543
  const agent = config.agents["ollama-local"];
@@ -493,7 +552,7 @@ async function runSyncOllamaModelCommand(configPath, config, messages) {
493
552
  console.log(messages.config.ollamaModelNoChange(configPath, agent.model));
494
553
  return;
495
554
  }
496
- await writeExampleConfig(configPath, config);
555
+ await writeConfig(configPath, config);
497
556
  console.log(messages.config.ollamaModelUpdated(configPath, result.previousModel, result.nextModel));
498
557
  }
499
558
  /**
@@ -511,6 +570,11 @@ function isNoneValue(value) {
511
570
  function formatDefaultsForMessage(defaults, messages) {
512
571
  return messages.config.defaultsSummary(defaults.agentA, defaults.agentB, turnsOrDefault(defaults.turns), defaults.summaryAgent, defaults.askSummaryAgent, defaults.mode, defaults.askAgents, defaults.interface);
513
572
  }
573
+ /**
574
+ * Déduplique et valide une liste d'agents `ask` fournie via `--ask-agents`.
575
+ * @param agents - Noms d'agents bruts, éventuellement en doublon.
576
+ * @throws Si la liste dépasse `MAX_ASK_AGENTS` ou référence un agent inconnu de la config.
577
+ */
514
578
  function normalizeAskAgentsForConfig(config, agents, messages) {
515
579
  const unique = agents
516
580
  .map((agent) => agent.trim())
@@ -534,24 +598,6 @@ function assertKnownAgent(config, agentName, fieldName, messages) {
534
598
  throw new Error(messages.common.unknownAgentForField(fieldName, agentName, Object.keys(config.agents).join(", ")));
535
599
  }
536
600
  }
537
- function parseModeFlag(value, messages) {
538
- if (!value) {
539
- return "debate";
540
- }
541
- if (value === "debate" || value === "ask") {
542
- return value;
543
- }
544
- throw new Error(messages.common.unknownMode(value, "debate, ask"));
545
- }
546
- function parseInterfaceFlag(value, messages) {
547
- if (!value) {
548
- return "tui";
549
- }
550
- if (value === "tui" || value === "terminal") {
551
- return value;
552
- }
553
- throw new Error(messages.common.unknownMode(value, "tui, terminal"));
554
- }
555
601
  /**
556
602
  * Affiche un aperçu du prompt du premier tour sans appeler aucun agent (flag `--show-prompt`).
557
603
  * @param config - Config chargée.
@@ -638,14 +684,12 @@ function createRendererFromFlags(flags, plainOutputFallback, defaultInterface, m
638
684
  if (flags.json) {
639
685
  return createNdjsonRenderer();
640
686
  }
641
- if (flags.tui) {
642
- return createTuiRenderer(messages);
643
- }
644
- if (flags.terminal || flags.plain || plainOutputFallback || defaultInterface === "terminal") {
645
- return createConsoleRenderer(true, messages);
646
- }
647
687
  return createAutoRenderer(flags, plainOutputFallback, defaultInterface, messages);
648
688
  }
689
+ /**
690
+ * Choix du renderer par défaut (`--renderer auto`) : TUI si les flags/config le demandent
691
+ * ou si stdout est un TTY, rendu console plain sinon.
692
+ */
649
693
  function createAutoRenderer(flags, plainOutputFallback, defaultInterface, messages) {
650
694
  if (flags.tui) {
651
695
  return createTuiRenderer(messages);
@@ -655,6 +699,11 @@ function createAutoRenderer(flags, plainOutputFallback, defaultInterface, messag
655
699
  }
656
700
  return process.stdout.isTTY ? createTuiRenderer(messages) : createConsoleRenderer(true, messages);
657
701
  }
702
+ /**
703
+ * Détermine si l'accueil TUI doit s'ouvrir : commande `run` implicite, sans sujet, preset
704
+ * ni flag de rendu déjà fourni. Toute intention explicite de lancer directement un débat
705
+ * (topic, `--renderer`, `--json`, `--plain`, `--terminal`) désactive l'accueil.
706
+ */
658
707
  function shouldOpenTuiHome(parsed) {
659
708
  return parsed.command === "run"
660
709
  && !parsed.commandExplicit
@@ -679,6 +728,11 @@ function printHelp(messages, command) {
679
728
  const commandHelp = command ? messages.help.renderCommand(command) : undefined;
680
729
  console.log(commandHelp ?? messages.help.render(listPresetNames().join(", ")));
681
730
  }
731
+ /**
732
+ * Résout la commande cible pour l'aide contextuelle (`palabre <cmd> --help`), en normalisant
733
+ * les alias (`agent` -> `agents`, `preset` -> `presets`, `setup` -> `init`).
734
+ * @returns `undefined` pour `help`/`run`, qui utilisent l'aide générale.
735
+ */
682
736
  function commandHelpTarget(parsed) {
683
737
  if (parsed.command === "help" || parsed.command === "run") {
684
738
  return undefined;
@@ -706,16 +760,16 @@ async function resolveCommandMessages(flags) {
706
760
  }
707
761
  return createTranslator(resolveLanguage({ explicitLanguage, configLanguage }));
708
762
  }
763
+ /**
764
+ * Formate une erreur non gérée remontée jusqu'au point d'entrée en message lisible,
765
+ * en spécialisant `AdapterError` et `OllamaUrlError` pour rester actionnable.
766
+ */
709
767
  function formatRuntimeError(error, messages) {
710
768
  if (error instanceof AdapterError) {
711
769
  return formatAdapterError(error, messages);
712
770
  }
713
771
  if (error instanceof OllamaUrlError) {
714
- if (error.kind === "empty")
715
- return messages.common.ollamaUrlEmpty;
716
- if (error.kind === "protocol")
717
- return messages.common.ollamaUrlProtocol(error.protocol ?? "");
718
- return messages.common.ollamaUrlInvalid(error.value);
772
+ return formatOllamaUrlError(error, messages);
719
773
  }
720
774
  return error instanceof Error ? error.message : String(error);
721
775
  }
@@ -726,6 +780,10 @@ main().catch((error) => {
726
780
  console.error(`${messages.common.errorPrefix}: ${message}`);
727
781
  process.exitCode = 1;
728
782
  });
783
+ /**
784
+ * Variante de `findRawLanguageFlag` + `resolveLanguage` qui ne peut pas lever, utilisée dans
785
+ * le gestionnaire d'erreur global où la config n'est pas forcément chargée.
786
+ */
729
787
  function safeStartupLanguage(args) {
730
788
  try {
731
789
  return resolveLanguage({ explicitLanguage: findRawLanguageFlag(args) });
package/dist/limits.js CHANGED
@@ -1,6 +1,10 @@
1
+ /** @file Limites produit sur le nombre de tours et d'agents `ask`, et parsing/validation de `--turns`. */
1
2
  import { createTranslator } from "./i18n.js";
3
+ /** Nombre de tours par défaut quand `--turns` n'est pas fourni. */
2
4
  export const DEFAULT_TURNS = 4;
5
+ /** Nombre maximal d'agents acceptés par `--agents` en mode `ask`. */
3
6
  export const MAX_ASK_AGENTS = 4;
7
+ /** Borne haute produit pour `--turns`, au-delà considérée comme une erreur d'utilisation. */
4
8
  export const MAX_TURNS = 20;
5
9
  /** Convertit `value` en nombre et valide la plage [1, `MAX_TURNS`]. Lève une erreur si invalide. */
6
10
  export function parseTurns(value, label = "--turns", messages = createTranslator("fr")) {
@@ -29,10 +29,34 @@ const enHints = {
29
29
  export const adapterErrorMessages = {
30
30
  fr: {
31
31
  suggestionPrefix: "Suggestion",
32
- hint: (kind) => frHints[kind]
32
+ hint: (kind) => frHints[kind],
33
+ usageLimit: (adapterName, detail) => `${adapterName} a atteint une limite d'utilisation: ${detail}`,
34
+ unsupportedModel: (adapterName, detail) => `${adapterName} ne peut pas utiliser ce modèle: ${detail}`,
35
+ noStderrCaptured: "aucun stderr capturé.",
36
+ noPtyOutputCaptured: "aucune sortie PTY capturée.",
37
+ ollamaModelUnavailable: (model, installedModels) => `Modèle Ollama indisponible: ${model}. Modèles détectés: ${installedModels.join(", ") || "aucun"}. ` +
38
+ "Utilise --pull-models ou autoPullModel: true pour autoriser le téléchargement.",
39
+ ollamaModelStillUnavailable: (model) => `Le modèle Ollama ${model} reste indisponible après téléchargement.`,
40
+ ollamaPullProgress: (model) => `[ollama] Modèle absent, téléchargement: ${model}`,
41
+ ollamaPullFailed: (model, detail) => `Échec du téléchargement Ollama ${model}: ${detail}`,
42
+ ollamaTagsHttpError: (status) => `Ollama HTTP ${status} pendant la détection des modèles`,
43
+ ollamaPsHttpError: (status) => `Ollama HTTP ${status} pendant la détection des modèles chargés`,
44
+ ollamaUnloadFailed: (model, status) => `Impossible de décharger le modèle Ollama ${model}: HTTP ${status}`
33
45
  },
34
46
  en: {
35
47
  suggestionPrefix: "Suggestion",
36
- hint: (kind) => enHints[kind]
48
+ hint: (kind) => enHints[kind],
49
+ usageLimit: (adapterName, detail) => `${adapterName} hit a usage limit: ${detail}`,
50
+ unsupportedModel: (adapterName, detail) => `${adapterName} cannot use this model: ${detail}`,
51
+ noStderrCaptured: "no stderr captured.",
52
+ noPtyOutputCaptured: "no PTY output captured.",
53
+ ollamaModelUnavailable: (model, installedModels) => `Ollama model unavailable: ${model}. Detected models: ${installedModels.join(", ") || "none"}. ` +
54
+ "Use --pull-models or autoPullModel: true to allow downloading.",
55
+ ollamaModelStillUnavailable: (model) => `Ollama model ${model} is still unavailable after downloading.`,
56
+ ollamaPullProgress: (model) => `[ollama] Model missing, downloading: ${model}`,
57
+ ollamaPullFailed: (model, detail) => `Ollama download failed for ${model}: ${detail}`,
58
+ ollamaTagsHttpError: (status) => `Ollama HTTP ${status} while detecting models`,
59
+ ollamaPsHttpError: (status) => `Ollama HTTP ${status} while detecting loaded models`,
60
+ ollamaUnloadFailed: (model, status) => `Failed to unload Ollama model ${model}: HTTP ${status}`
37
61
  }
38
62
  };
@@ -9,6 +9,9 @@ export const configMessages = {
9
9
  ollamaModelUnavailable: (model) => `Modèle Ollama non installé: ${model}. Action: choisis un modèle installé ou lance \`ollama pull ${model}\`.`,
10
10
  ollamaModelNoAgent: "Agent ollama-local absent ou invalide dans la config.",
11
11
  ollamaModelNoInstalledModels: "Aucun modèle Ollama installé détecté. Action: lance `ollama pull <modèle>`.",
12
+ ollamaModelsCurrent: (model) => `ollama-local: ${model ?? "(non configuré)"}`,
13
+ ollamaModelsApi: (available, baseUrl) => `Ollama API: ${available ? "joignable" : "indisponible"} (${baseUrl})`,
14
+ ollamaModelsInstalled: (models) => `Modèles installés: ${models.length > 0 ? models.join(", ") : "(aucun)"}`,
12
15
  updated: (path, defaults, language) => `Configuration mise à jour dans ${path}: ${defaults}, langue: ${language}.`,
13
16
  cleared: (path) => `Paramètres par défaut supprimés dans ${path}. Utilise maintenant un preset ou --agent-a/--agent-b pour lancer un débat.`,
14
17
  defaultsSummary: (agentA, agentB, turns, summaryAgent, askSummaryAgent, mode, askAgents, interfaceName) => {
@@ -63,6 +66,9 @@ export const configMessages = {
63
66
  ollamaModelUnavailable: (model) => `Ollama model is not installed: ${model}. Action: choose an installed model or run \`ollama pull ${model}\`.`,
64
67
  ollamaModelNoAgent: "ollama-local agent is missing or invalid in the config.",
65
68
  ollamaModelNoInstalledModels: "No installed Ollama model detected. Action: run `ollama pull <model>`.",
69
+ ollamaModelsCurrent: (model) => `ollama-local: ${model ?? "(not configured)"}`,
70
+ ollamaModelsApi: (available, baseUrl) => `Ollama API: ${available ? "reachable" : "unavailable"} (${baseUrl})`,
71
+ ollamaModelsInstalled: (models) => `Installed models: ${models.length > 0 ? models.join(", ") : "(none)"}`,
66
72
  updated: (path, defaults, language) => `Configuration updated in ${path}: ${defaults}, language: ${language}.`,
67
73
  cleared: (path) => `Default settings cleared in ${path}. Use a preset or --agent-a/--agent-b to start a debate now.`,
68
74
  defaultsSummary: (agentA, agentB, turns, summaryAgent, askSummaryAgent, mode, askAgents, interfaceName) => {
@@ -16,6 +16,7 @@ import { previewMessages } from "./preview.js";
16
16
  import { rendererMessages } from "./renderers.js";
17
17
  import { tuiMessages } from "./tui.js";
18
18
  import { updateMessages } from "./update.js";
19
+ /** Construit le dictionnaire `Messages` pour `language`, en assemblant chaque domaine indépendamment. */
19
20
  export function createTranslator(language) {
20
21
  return {
21
22
  adapterErrors: adapterErrorMessages[language],
@@ -1,3 +1,9 @@
1
+ /** Tronque une liste de chemins pour l'affichage : 3 premiers + compteur du reste. */
2
+ function summarizePaths(paths) {
3
+ const shown = paths.slice(0, 3);
4
+ const extra = paths.length - shown.length;
5
+ return `${shown.join(", ")}${extra > 0 ? ` (+${extra})` : ""}`;
6
+ }
1
7
  export const rendererMessages = {
2
8
  fr: {
3
9
  subject: (topic) => `Sujet: ${topic}`,
@@ -5,6 +11,7 @@ export const rendererMessages = {
5
11
  responsesSummaryContext: (turns, summary, context) => `Réponses: ${turns} | Synthèse: ${summary} | Contexte: ${context}`,
6
12
  responsesSummary: (turns, summary) => `Réponses: ${turns} | Synthèse: ${summary}`,
7
13
  context: (context) => `Contexte: ${context}`,
14
+ workingFolder: (path) => `Dossier: ${path}`,
8
15
  options: (earlyStop, pullModels) => `Options: arrêt anticipé ${earlyStop ? "activé" : "désactivé"}, auto-pull Ollama ${pullModels ? "activé" : "désactivé"}`,
9
16
  enabled: "activé",
10
17
  disabled: "désactivée",
@@ -15,7 +22,7 @@ export const rendererMessages = {
15
22
  summaryTitle: "Synthese",
16
23
  exported: (path) => `Palabre exporte: ${path}`,
17
24
  noInjectedFiles: "aucun fichier injecté",
18
- injectedFiles: (count) => `${count} fichier${count > 1 ? "s" : ""} injecté${count > 1 ? "s" : ""}`
25
+ injectedFiles: (count, paths) => `${count} fichier${count > 1 ? "s" : ""} injecté${count > 1 ? "s" : ""} : ${summarizePaths(paths)}`
19
26
  },
20
27
  en: {
21
28
  subject: (topic) => `Subject: ${topic}`,
@@ -23,6 +30,7 @@ export const rendererMessages = {
23
30
  responsesSummaryContext: (turns, summary, context) => `Responses: ${turns} | Summary: ${summary} | Context: ${context}`,
24
31
  responsesSummary: (turns, summary) => `Responses: ${turns} | Summary: ${summary}`,
25
32
  context: (context) => `Context: ${context}`,
33
+ workingFolder: (path) => `Folder: ${path}`,
26
34
  options: (earlyStop, pullModels) => `Options: early stop ${earlyStop ? "enabled" : "disabled"}, Ollama auto-pull ${pullModels ? "enabled" : "disabled"}`,
27
35
  enabled: "enabled",
28
36
  disabled: "disabled",
@@ -33,6 +41,6 @@ export const rendererMessages = {
33
41
  summaryTitle: "Summary",
34
42
  exported: (path) => `Palabre exported: ${path}`,
35
43
  noInjectedFiles: "no injected files",
36
- injectedFiles: (count) => `${count} injected file${count > 1 ? "s" : ""}`
44
+ injectedFiles: (count, paths) => `${count} injected file${count > 1 ? "s" : ""}: ${summarizePaths(paths)}`
37
45
  }
38
46
  };
@@ -9,7 +9,7 @@ export const tuiMessages = {
9
9
  roles: "Roles",
10
10
  summary: "Synthese",
11
11
  ollamaModel: "Modele Ollama",
12
- ollamaUrl: "Adresse Ollama configuree",
12
+ ollamaUrl: "Adresse Ollama",
13
13
  ollamaUrlEffective: "Adresse Ollama effective",
14
14
  responses: "Tours",
15
15
  folder: "Dossier",
@@ -53,6 +53,9 @@ export const tuiMessages = {
53
53
  roleReviewer: "cherche risques et tests manquants",
54
54
  roleSummarizer: "synthetise fidelement",
55
55
  configTitle: "Configuration Palabre",
56
+ configSectionGeneral: "Général",
57
+ exportedFile: "Fichier exporté",
58
+ exportedFolder: "Dossier d'export",
56
59
  configFile: "Config",
57
60
  interface: "Interface",
58
61
  language: "Langue",
@@ -129,7 +132,7 @@ export const tuiMessages = {
129
132
  roles: "Roles",
130
133
  summary: "Summary",
131
134
  ollamaModel: "Ollama model",
132
- ollamaUrl: "Configured Ollama address",
135
+ ollamaUrl: "Ollama address",
133
136
  ollamaUrlEffective: "Effective Ollama address",
134
137
  responses: "Turns",
135
138
  folder: "Folder",
@@ -173,6 +176,9 @@ export const tuiMessages = {
173
176
  roleReviewer: "looks for risks and missing tests",
174
177
  roleSummarizer: "summarizes faithfully",
175
178
  configTitle: "Palabre Configuration",
179
+ configSectionGeneral: "General",
180
+ exportedFile: "Exported file",
181
+ exportedFolder: "Export folder",
176
182
  configFile: "Config",
177
183
  interface: "Interface",
178
184
  language: "Language",