palabre 0.9.1 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/adapters/cli-pty.js +30 -10
  2. package/dist/adapters/cli-shared.js +73 -0
  3. package/dist/adapters/cli.js +40 -77
  4. package/dist/adapters/index.js +1 -0
  5. package/dist/adapters/ollama.js +32 -32
  6. package/dist/adapters/terminal.js +1 -0
  7. package/dist/args.js +1 -0
  8. package/dist/commands/agents.js +7 -1
  9. package/dist/commands/context.js +7 -1
  10. package/dist/commands/history.js +6 -1
  11. package/dist/commands/init.js +8 -3
  12. package/dist/commands/presets.js +6 -1
  13. package/dist/commands/shared.js +5 -1
  14. package/dist/commands/update.js +6 -1
  15. package/dist/config.js +17 -1
  16. package/dist/configWizard.js +5 -4
  17. package/dist/context.js +1 -0
  18. package/dist/contextScan.js +4 -3
  19. package/dist/discovery.js +1 -0
  20. package/dist/doctor.js +1 -0
  21. package/dist/errors.js +4 -0
  22. package/dist/exec.js +1 -0
  23. package/dist/history.js +10 -0
  24. package/dist/i18n.js +2 -0
  25. package/dist/index.js +151 -112
  26. package/dist/limits.js +4 -0
  27. package/dist/messages/adapter-errors.js +26 -2
  28. package/dist/messages/config.js +6 -0
  29. package/dist/messages/index.js +1 -0
  30. package/dist/messages/renderers.js +10 -2
  31. package/dist/messages/tui.js +8 -2
  32. package/dist/new.js +1 -0
  33. package/dist/ollamaUrl.js +20 -0
  34. package/dist/orchestrator.js +103 -150
  35. package/dist/output.js +1 -0
  36. package/dist/presets.js +1 -0
  37. package/dist/prompt.js +1 -0
  38. package/dist/renderers/console.js +1 -1
  39. package/dist/renderers/tui-prompts.js +339 -0
  40. package/dist/renderers/tui-renderer.js +224 -0
  41. package/dist/renderers/tui-screens.js +352 -0
  42. package/dist/renderers/tui-theme.js +356 -0
  43. package/dist/renderers/tui.js +7 -1086
  44. package/dist/runOptions.js +33 -2
  45. package/dist/session.js +1 -0
  46. package/dist/tuiController.js +61 -16
  47. package/dist/tuiState.js +4 -0
  48. package/dist/types.js +1 -0
  49. package/dist/update.js +1 -0
  50. package/dist/version.js +1 -0
  51. package/package.json +1 -1
@@ -1,4 +1,8 @@
1
- /** Extrait une chaîne non vide depuis une valeur de flag. */
1
+ /**
2
+ * Extrait une chaîne non vide depuis une valeur de flag.
3
+ * @param value - Valeur brute produite par le parseur CLI.
4
+ * @returns La chaîne non vide, sinon `undefined`.
5
+ */
2
6
  export function optionalString(value) {
3
7
  return typeof value === "string" && value.trim() ? value : undefined;
4
8
  }
@@ -1,9 +1,14 @@
1
+ /** @file Commande de diagnostic et application des mises a jour Palabre. */
1
2
  import { configExists, loadConfig, resolveDefaultConfigPath } from "../config.js";
2
3
  import { createTranslator, resolveLanguage } from "../i18n.js";
3
4
  import { applySourceUpdate, formatUpdateInstructions, getUpdateInfo } from "../update.js";
4
5
  import { getPackageVersion } from "../version.js";
5
6
  import { optionalString } from "./shared.js";
6
- /** Exécute `palabre update`, avec application explicite pour un checkout source. */
7
+ /**
8
+ * Affiche les instructions de mise à jour ou applique le workflow d'un checkout source.
9
+ * @param flags - Flags de config, langue et application explicite.
10
+ * @returns Une promesse résolue après affichage ou mise à jour.
11
+ */
7
12
  export async function runUpdateCommand(flags) {
8
13
  const info = await getUpdateInfo(await getPackageVersion());
9
14
  const configPath = optionalString(flags.config) ?? await resolveDefaultConfigPath();
package/dist/config.js CHANGED
@@ -1,3 +1,4 @@
1
+ /** @file Chargement, génération et validation de la config Palabre, ainsi que la synchronisation des agents/modèles Ollama détectés. */
1
2
  import { access, mkdir, readFile, writeFile } from "node:fs/promises";
2
3
  import os from "node:os";
3
4
  import path from "node:path";
@@ -234,6 +235,10 @@ function migrateVibePlanAgent(config) {
234
235
  function isLegacyVibePlanArgs(args) {
235
236
  return JSON.stringify(args) === JSON.stringify(["--output", "text", "--agent", "plan", "--trust", "--prompt"]);
236
237
  }
238
+ /**
239
+ * Variante de `syncDetectedAgents` qui indique aussi si la config a changé,
240
+ * même quand aucun agent n'a été ajouté (ex. migration d'arguments legacy).
241
+ */
237
242
  export function syncDetectedAgentsDetailed(config, discovery) {
238
243
  const before = JSON.stringify(config.agents);
239
244
  const addedAgents = syncDetectedAgents(config, discovery);
@@ -243,6 +248,12 @@ export function syncDetectedAgentsDetailed(config, discovery) {
243
248
  changed
244
249
  };
245
250
  }
251
+ /**
252
+ * Bascule le modèle de l'agent `ollama-local` vers un modèle installé si celui configuré
253
+ * ne l'est pas. Mute `config` directement.
254
+ * @returns `undefined` si l'agent n'est pas de type `ollama`, si aucun modèle n'est installé,
255
+ * ou si le modèle configuré est déjà installé.
256
+ */
246
257
  export function syncOllamaModel(config, discovery) {
247
258
  const agent = config.agents["ollama-local"];
248
259
  if (agent?.type !== "ollama" || discovery.ollama.models.length === 0) {
@@ -258,6 +269,11 @@ export function syncOllamaModel(config, discovery) {
258
269
  nextModel: agent.model
259
270
  };
260
271
  }
272
+ /**
273
+ * Force le modèle de l'agent `ollama-local` à `model`, sans vérifier qu'il est installé.
274
+ * Mute `config` directement.
275
+ * @returns `undefined` si l'agent n'est pas de type `ollama`.
276
+ */
261
277
  export function setOllamaModel(config, model) {
262
278
  const agent = config.agents["ollama-local"];
263
279
  if (agent?.type !== "ollama") {
@@ -279,7 +295,7 @@ export function setOllamaBaseUrl(config, baseUrl) {
279
295
  return agents.length;
280
296
  }
281
297
  /** Écrit `config` sérialisé en JSON dans `configPath`. Crée le répertoire parent si nécessaire. */
282
- export async function writeExampleConfig(configPath = DEFAULT_CONFIG_PATH, config = exampleConfig) {
298
+ export async function writeConfig(configPath = DEFAULT_CONFIG_PATH, config = exampleConfig) {
283
299
  const resolved = path.resolve(configPath);
284
300
  await mkdir(path.dirname(resolved), { recursive: true });
285
301
  await writeFile(resolved, `${JSON.stringify(config, null, 2)}\n`, "utf8");
@@ -1,6 +1,7 @@
1
+ /** @file Wizard interactif `palabre config` (sans sous-commande) pour éditer les defaults au clavier. */
1
2
  import { createInterface } from "node:readline/promises";
2
3
  import { stdin as input, stdout as output } from "node:process";
3
- import { syncDetectedAgents, writeExampleConfig } from "./config.js";
4
+ import { syncDetectedAgents, writeConfig } from "./config.js";
4
5
  import { discoverLocalTools } from "./discovery.js";
5
6
  import { DEFAULT_TURNS, MAX_TURNS, turnsOrDefault, validateTurns } from "./limits.js";
6
7
  /**
@@ -36,7 +37,7 @@ export async function runConfigWizard(configPath, config, messages) {
36
37
  }
37
38
  if (action === "2") {
38
39
  delete config.defaults;
39
- await writeExampleConfig(configPath, config);
40
+ await writeConfig(configPath, config);
40
41
  console.log(messages.config.wizardCleared(configPath));
41
42
  return;
42
43
  }
@@ -47,7 +48,7 @@ export async function runConfigWizard(configPath, config, messages) {
47
48
  console.log(messages.config.syncNoMissing(configPath));
48
49
  return;
49
50
  }
50
- await writeExampleConfig(configPath, config);
51
+ await writeConfig(configPath, config);
51
52
  console.log(messages.config.syncAdded(configPath, addedAgents.join(", ")));
52
53
  return;
53
54
  }
@@ -75,7 +76,7 @@ export async function runConfigWizard(configPath, config, messages) {
75
76
  else {
76
77
  delete config.defaults.summaryAgent;
77
78
  }
78
- await writeExampleConfig(configPath, config);
79
+ await writeConfig(configPath, config);
79
80
  console.log(messages.config.wizardDefaultsSet(configPath, formatDefaults(config.defaults, messages)));
80
81
  }
81
82
  finally {
package/dist/context.js CHANGED
@@ -1,3 +1,4 @@
1
+ /** @file Chargement du contexte projet : `--files` strict et `--context` tolérant avec règles `.gitignore` simplifiées. */
1
2
  import { readdir, readFile, stat } from "node:fs/promises";
2
3
  import path from "node:path";
3
4
  import { createTranslator } from "./i18n.js";
@@ -1,11 +1,12 @@
1
+ /** @file Contrat JSON v1 de `palabre context scan --json`, consommé par les intégrations. */
1
2
  import path from "node:path";
2
3
  import { loadProjectInputs } from "./context.js";
3
4
  import { createTranslator } from "./i18n.js";
4
5
  /**
5
- * Builds the machine-readable context preview used by integrations.
6
+ * Construit l'aperçu de contexte lisible par une machine, utilisé par les intégrations.
6
7
  *
7
- * The scan intentionally reuses the same tolerant loader as `--context`, so
8
- * the returned files are the files Palabre would actually inject into a debate.
8
+ * Le scan réutilise volontairement le même chargeur tolérant que `--context`, afin que
9
+ * les fichiers renvoyés soient exactement ceux que Palabre injecterait dans un débat.
9
10
  */
10
11
  export async function buildContextScan(scanPaths, cwd = process.cwd(), messages = createTranslator("fr")) {
11
12
  const effectiveScanPaths = scanPaths.length > 0 ? scanPaths : ["."];
package/dist/discovery.js CHANGED
@@ -1,3 +1,4 @@
1
+ /** @file Détection locale des CLIs agents et d'Ollama, utilisée par `init`, `doctor`, `presets` et l'accueil TUI. */
1
2
  import { access } from "node:fs/promises";
2
3
  import path from "node:path";
3
4
  import { executableExtensions } from "./exec.js";
package/dist/doctor.js CHANGED
@@ -1,3 +1,4 @@
1
+ /** @file Diagnostic `palabre doctor` : vérifie config, agents détectés et connectivité Ollama. */
1
2
  import path from "node:path";
2
3
  import { stat } from "node:fs/promises";
3
4
  import { configExists, loadConfig, resolveDefaultConfigPath, resolveOutputDir } from "./config.js";
package/dist/errors.js CHANGED
@@ -20,3 +20,7 @@ export function formatAdapterError(error, messages = createTranslator("fr")) {
20
20
  const hint = messages.adapterErrors.hint(error.kind);
21
21
  return hint ? `${error.message}\n${messages.adapterErrors.suggestionPrefix}: ${hint}` : error.message;
22
22
  }
23
+ /** Construit l'`AdapterError` standard d'annulation utilisateur, partagée par tous les adapters. */
24
+ export function cancelledError(adapterName) {
25
+ return new AdapterError("cancelled", adapterName, `${adapterName} cancelled by user.`);
26
+ }
package/dist/exec.js CHANGED
@@ -1,3 +1,4 @@
1
+ /** @file Résolution d'extensions exécutables partagée entre discovery et l'adapter PTY. */
1
2
  import path from "node:path";
2
3
  /**
3
4
  * Extensions exécutables candidates pour résoudre une commande dans le PATH.
package/dist/history.js CHANGED
@@ -1,6 +1,12 @@
1
+ /** @file Liste les exports Markdown récents en reparsant leur table de métadonnées, pour `palabre history` et la TUI. */
1
2
  import { readdir, readFile, stat } from "node:fs/promises";
2
3
  import path from "node:path";
4
+ /** Limite de lecture par fichier : la table de métadonnées est toujours en tête de l'export. */
3
5
  const maxHeaderBytes = 12_000;
6
+ /**
7
+ * Liste les exports les plus récents d'un dossier de sortie, triés par date de modification.
8
+ * Silencieux si `outputDir` est absent ou inaccessible : retourne `[]` sans lever.
9
+ */
4
10
  export async function listHistoryEntries(outputDir, limit = 10) {
5
11
  const resolved = path.resolve(outputDir);
6
12
  let entries;
@@ -19,6 +25,7 @@ export async function listHistoryEntries(outputDir, limit = 10) {
19
25
  .sort((left, right) => right.mtimeMs - left.mtimeMs)
20
26
  .slice(0, limit);
21
27
  }
28
+ /** Lit et parse un fichier d'export. Retourne `undefined` sur toute erreur filesystem/parsing plutôt que de lever. */
22
29
  async function readHistoryFile(filePath) {
23
30
  try {
24
31
  const [metadata, raw] = await Promise.all([
@@ -44,6 +51,7 @@ async function readHistoryFile(filePath) {
44
51
  return undefined;
45
52
  }
46
53
  }
54
+ /** Formate le compteur `x/y` adapté au mode, en tolérant les clés FR/EN de la table de métadonnées. */
47
55
  function countFromTable(mode, table) {
48
56
  if (mode === "ask") {
49
57
  const received = table["Reponses recues"] ?? table["Received responses"];
@@ -54,6 +62,7 @@ function countFromTable(mode, table) {
54
62
  const requested = table["Tours demandes"] ?? table["Requested turns"];
55
63
  return played && requested ? `${played}/${requested}` : played ?? requested ?? "";
56
64
  }
65
+ /** Extrait les paires clé/valeur de la table Markdown `| Champ | Valeur |` générée par l'export. */
57
66
  function parseMetadataTable(markdown) {
58
67
  const fields = {};
59
68
  const lines = markdown.split(/\r?\n/);
@@ -75,6 +84,7 @@ function stripMarkdown(value) {
75
84
  .replace(/`/g, "")
76
85
  .trim();
77
86
  }
87
+ /** Reconstruit un sujet lisible depuis le nom de fichier quand la table de métadonnées ne l'indique pas. */
78
88
  function topicFromFileName(fileName) {
79
89
  return fileName
80
90
  .replace(/^palabre-/, "")
package/dist/i18n.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import { createTranslator } from "./messages/index.js";
2
+ /** Langue utilisée quand aucune source explicite (flag, env, config) n'en fournit une valide. */
2
3
  export const DEFAULT_LANGUAGE = "fr";
4
+ /** Langues acceptées par `parseLanguage`/`resolveLanguage`. */
3
5
  export const SUPPORTED_LANGUAGES = ["fr", "en"];
4
6
  /**
5
7
  * Valide une langue Palabre.
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([
@@ -308,6 +330,7 @@ async function main() {
308
330
  }
309
331
  }
310
332
  }
333
+ /** Construit un signal d'annulation déclenché sur `SIGINT`/`SIGTERM` pour interrompre un débat en cours. */
311
334
  function debateAbortSignal() {
312
335
  const controller = new AbortController();
313
336
  const abort = () => {
@@ -328,7 +351,7 @@ async function runConfigCommand(flags) {
328
351
  const explicitLanguage = optionalString(flags.language);
329
352
  if (!(await configExists(configPath))) {
330
353
  const messages = createTranslator(resolveLanguage({ explicitLanguage }));
331
- await writeExampleConfig(configPath);
354
+ await writeConfig(configPath);
332
355
  console.log(messages.config.createdForConfig(configPath));
333
356
  return;
334
357
  }
@@ -339,7 +362,7 @@ async function runConfigCommand(flags) {
339
362
  });
340
363
  const messages = createTranslator(language);
341
364
  if (flags["ollama-models"]) {
342
- await runOllamaModelsCommand(config, Boolean(flags.json));
365
+ await runOllamaModelsCommand(config, Boolean(flags.json), messages);
343
366
  return;
344
367
  }
345
368
  const setOllamaModelValue = optionalString(flags["set-ollama-model"]);
@@ -358,7 +381,7 @@ async function runConfigCommand(flags) {
358
381
  console.log(messages.config.syncNoMissing(configPath));
359
382
  return;
360
383
  }
361
- await writeExampleConfig(configPath, config);
384
+ await writeConfig(configPath, config);
362
385
  console.log(result.addedAgents.length > 0
363
386
  ? messages.config.syncAdded(configPath, result.addedAgents.join(", "))
364
387
  : messages.config.syncRefreshed(configPath));
@@ -427,19 +450,25 @@ async function runConfigCommand(flags) {
427
450
  if (changesDefaults) {
428
451
  config.defaults = nextDefaults;
429
452
  }
430
- await writeExampleConfig(configPath, config);
453
+ await writeConfig(configPath, config);
431
454
  console.log(messages.config.updated(configPath, formatDefaultsForMessage(config.defaults ?? {}, messages), config.language ?? DEFAULT_LANGUAGE));
432
455
  return;
433
456
  }
434
457
  if (flags["clear-defaults"]) {
435
458
  delete config.defaults;
436
- await writeExampleConfig(configPath, config);
459
+ await writeConfig(configPath, config);
437
460
  console.log(messages.config.cleared(configPath));
438
461
  return;
439
462
  }
440
463
  await runConfigWizard(configPath, config, messages);
441
464
  }
442
- async function runOllamaModelsCommand(config, json) {
465
+ /**
466
+ * Affiche l'état de l'agent Ollama local (modèle courant, disponibilité de l'API, modèles installés).
467
+ * @param config - Config chargée.
468
+ * @param json - Si `true`, affiche le résultat en JSON plutôt qu'en texte lisible.
469
+ * @param messages - Dictionnaire localisé pour la sortie texte.
470
+ */
471
+ async function runOllamaModelsCommand(config, json, messages) {
443
472
  const discovery = await discoverLocalToolsForConfig(config);
444
473
  const agent = config.agents["ollama-local"];
445
474
  const currentModel = agent?.type === "ollama" ? agent.model : null;
@@ -456,10 +485,16 @@ async function runOllamaModelsCommand(config, json) {
456
485
  console.log(JSON.stringify(payload, null, 2));
457
486
  return;
458
487
  }
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)"}`);
488
+ console.log(messages.config.ollamaModelsCurrent(currentModel));
489
+ console.log(messages.config.ollamaModelsApi(discovery.ollama.available, discovery.ollama.baseUrl));
490
+ console.log(messages.config.ollamaModelsInstalled(discovery.ollama.models));
462
491
  }
492
+ /**
493
+ * Change le modèle configuré pour l'agent `ollama-local` après vérification qu'il est bien installé.
494
+ * @param configPath - Chemin du fichier de config à mettre à jour.
495
+ * @param config - Config chargée.
496
+ * @param model - Nom du modèle Ollama à définir.
497
+ */
463
498
  async function runSetOllamaModelCommand(configPath, config, model, messages) {
464
499
  const trimmed = model.trim();
465
500
  if (!trimmed) {
@@ -474,11 +509,16 @@ async function runSetOllamaModelCommand(configPath, config, model, messages) {
474
509
  throw new Error(messages.config.ollamaModelUnavailable(trimmed));
475
510
  }
476
511
  const result = setOllamaModel(config, trimmed);
477
- await writeExampleConfig(configPath, config);
512
+ await writeConfig(configPath, config);
478
513
  console.log(result
479
514
  ? messages.config.ollamaModelUpdated(configPath, result.previousModel, result.nextModel)
480
515
  : messages.config.ollamaModelNoChange(configPath, agent.model));
481
516
  }
517
+ /**
518
+ * Aligne automatiquement le modèle de l'agent `ollama-local` sur un modèle réellement installé.
519
+ * @param configPath - Chemin du fichier de config à mettre à jour.
520
+ * @param config - Config chargée.
521
+ */
482
522
  async function runSyncOllamaModelCommand(configPath, config, messages) {
483
523
  const discovery = await discoverLocalToolsForConfig(config);
484
524
  const agent = config.agents["ollama-local"];
@@ -493,7 +533,7 @@ async function runSyncOllamaModelCommand(configPath, config, messages) {
493
533
  console.log(messages.config.ollamaModelNoChange(configPath, agent.model));
494
534
  return;
495
535
  }
496
- await writeExampleConfig(configPath, config);
536
+ await writeConfig(configPath, config);
497
537
  console.log(messages.config.ollamaModelUpdated(configPath, result.previousModel, result.nextModel));
498
538
  }
499
539
  /**
@@ -511,6 +551,11 @@ function isNoneValue(value) {
511
551
  function formatDefaultsForMessage(defaults, messages) {
512
552
  return messages.config.defaultsSummary(defaults.agentA, defaults.agentB, turnsOrDefault(defaults.turns), defaults.summaryAgent, defaults.askSummaryAgent, defaults.mode, defaults.askAgents, defaults.interface);
513
553
  }
554
+ /**
555
+ * Déduplique et valide une liste d'agents `ask` fournie via `--ask-agents`.
556
+ * @param agents - Noms d'agents bruts, éventuellement en doublon.
557
+ * @throws Si la liste dépasse `MAX_ASK_AGENTS` ou référence un agent inconnu de la config.
558
+ */
514
559
  function normalizeAskAgentsForConfig(config, agents, messages) {
515
560
  const unique = agents
516
561
  .map((agent) => agent.trim())
@@ -534,24 +579,6 @@ function assertKnownAgent(config, agentName, fieldName, messages) {
534
579
  throw new Error(messages.common.unknownAgentForField(fieldName, agentName, Object.keys(config.agents).join(", ")));
535
580
  }
536
581
  }
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
582
  /**
556
583
  * Affiche un aperçu du prompt du premier tour sans appeler aucun agent (flag `--show-prompt`).
557
584
  * @param config - Config chargée.
@@ -638,14 +665,12 @@ function createRendererFromFlags(flags, plainOutputFallback, defaultInterface, m
638
665
  if (flags.json) {
639
666
  return createNdjsonRenderer();
640
667
  }
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
668
  return createAutoRenderer(flags, plainOutputFallback, defaultInterface, messages);
648
669
  }
670
+ /**
671
+ * Choix du renderer par défaut (`--renderer auto`) : TUI si les flags/config le demandent
672
+ * ou si stdout est un TTY, rendu console plain sinon.
673
+ */
649
674
  function createAutoRenderer(flags, plainOutputFallback, defaultInterface, messages) {
650
675
  if (flags.tui) {
651
676
  return createTuiRenderer(messages);
@@ -655,6 +680,11 @@ function createAutoRenderer(flags, plainOutputFallback, defaultInterface, messag
655
680
  }
656
681
  return process.stdout.isTTY ? createTuiRenderer(messages) : createConsoleRenderer(true, messages);
657
682
  }
683
+ /**
684
+ * Détermine si l'accueil TUI doit s'ouvrir : commande `run` implicite, sans sujet, preset
685
+ * ni flag de rendu déjà fourni. Toute intention explicite de lancer directement un débat
686
+ * (topic, `--renderer`, `--json`, `--plain`, `--terminal`) désactive l'accueil.
687
+ */
658
688
  function shouldOpenTuiHome(parsed) {
659
689
  return parsed.command === "run"
660
690
  && !parsed.commandExplicit
@@ -679,6 +709,11 @@ function printHelp(messages, command) {
679
709
  const commandHelp = command ? messages.help.renderCommand(command) : undefined;
680
710
  console.log(commandHelp ?? messages.help.render(listPresetNames().join(", ")));
681
711
  }
712
+ /**
713
+ * Résout la commande cible pour l'aide contextuelle (`palabre <cmd> --help`), en normalisant
714
+ * les alias (`agent` -> `agents`, `preset` -> `presets`, `setup` -> `init`).
715
+ * @returns `undefined` pour `help`/`run`, qui utilisent l'aide générale.
716
+ */
682
717
  function commandHelpTarget(parsed) {
683
718
  if (parsed.command === "help" || parsed.command === "run") {
684
719
  return undefined;
@@ -706,16 +741,16 @@ async function resolveCommandMessages(flags) {
706
741
  }
707
742
  return createTranslator(resolveLanguage({ explicitLanguage, configLanguage }));
708
743
  }
744
+ /**
745
+ * Formate une erreur non gérée remontée jusqu'au point d'entrée en message lisible,
746
+ * en spécialisant `AdapterError` et `OllamaUrlError` pour rester actionnable.
747
+ */
709
748
  function formatRuntimeError(error, messages) {
710
749
  if (error instanceof AdapterError) {
711
750
  return formatAdapterError(error, messages);
712
751
  }
713
752
  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);
753
+ return formatOllamaUrlError(error, messages);
719
754
  }
720
755
  return error instanceof Error ? error.message : String(error);
721
756
  }
@@ -726,6 +761,10 @@ main().catch((error) => {
726
761
  console.error(`${messages.common.errorPrefix}: ${message}`);
727
762
  process.exitCode = 1;
728
763
  });
764
+ /**
765
+ * Variante de `findRawLanguageFlag` + `resolveLanguage` qui ne peut pas lever, utilisée dans
766
+ * le gestionnaire d'erreur global où la config n'est pas forcément chargée.
767
+ */
729
768
  function safeStartupLanguage(args) {
730
769
  try {
731
770
  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")) {