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
@@ -1,10 +1,21 @@
1
+ /** @file Résolution centralisée des flags et defaults en options runtime immuables. */
1
2
  import { getStringListFlag } from "./args.js";
2
3
  import { DEFAULT_TURNS, MAX_ASK_AGENTS, parseTurnsFlag } from "./limits.js";
3
4
  import { normalizeOllamaBaseUrl } from "./ollamaUrl.js";
4
5
  import { createSessionContext } from "./session.js";
5
6
  import { askAgentSeedsForMode } from "./tuiState.js";
6
7
  import { optionalString } from "./commands/shared.js";
7
- /** Resolves flags and defaults into complete orchestrator options. */
8
+ /**
9
+ * Construit le contrat complet transmis à l'orchestrateur.
10
+ *
11
+ * La priorité est : flags explicites, preset, defaults de configuration, puis
12
+ * fallbacks propres au mode. La fonction ne modifie ni les flags ni la config.
13
+ *
14
+ * @param input - Configuration, flags, contexte et signal de la session.
15
+ * @param messages - Dictionnaire localisé utilisé pour les erreurs de validation.
16
+ * @returns Des options complètes, avec agent de synthèse déjà résolu.
17
+ * @throws {Error} Si le mode, les agents, le nombre de tours ou l'URL Ollama sont invalides.
18
+ */
8
19
  export function resolveRunOptions(input, messages) {
9
20
  const { flags, config, language, topic, files, preset, signal } = input;
10
21
  const mode = parseModeFlag(optionalString(flags.mode) ?? config.defaults?.mode, messages);
@@ -36,12 +47,14 @@ export function resolveRunOptions(input, messages) {
36
47
  signal
37
48
  };
38
49
  }
50
+ /** Résout un agent selon la priorité flag > preset > default configuré. */
39
51
  function resolveAgentName(label, explicitValue, presetValue, defaultValue, messages) {
40
52
  const resolved = optionalString(explicitValue) ?? presetValue ?? defaultValue;
41
53
  if (!resolved)
42
54
  throw new Error(messages.common.noAgentDefined(label));
43
55
  return resolved;
44
56
  }
57
+ /** Résout une seule fois l'agent de synthèse avant l'entrée dans l'orchestrateur. */
45
58
  function resolveSummaryAgent(explicitValue, defaults, mode, askAgents, agentB) {
46
59
  const explicit = optionalString(explicitValue);
47
60
  if (explicit)
@@ -50,13 +63,31 @@ function resolveSummaryAgent(explicitValue, defaults, mode, askAgents, agentB) {
50
63
  return defaults?.askSummaryAgent ?? defaults?.summaryAgent ?? askAgents?.at(-1) ?? agentB;
51
64
  return defaults?.summaryAgent ?? agentB;
52
65
  }
53
- function parseModeFlag(value, messages) {
66
+ /**
67
+ * Valide le mode demandé (`--mode`, `config --mode`) et applique `debate` quand
68
+ * aucune valeur n'est fournie. Partagé entre `run` et la commande `config`.
69
+ * @throws {Error} Si `value` n'est ni `debate` ni `ask`.
70
+ */
71
+ export function parseModeFlag(value, messages) {
54
72
  if (!value)
55
73
  return "debate";
56
74
  if (value === "debate" || value === "ask")
57
75
  return value;
58
76
  throw new Error(messages.common.unknownMode(value, "debate, ask"));
59
77
  }
78
+ /**
79
+ * Valide l'interface demandée (`--interface`, `config --interface`) et applique `tui`
80
+ * quand aucune valeur n'est fournie.
81
+ * @throws {Error} Si `value` n'est ni `tui` ni `terminal`.
82
+ */
83
+ export function parseInterfaceFlag(value, messages) {
84
+ if (!value)
85
+ return "tui";
86
+ if (value === "tui" || value === "terminal")
87
+ return value;
88
+ throw new Error(messages.common.unknownMode(value, "tui, terminal"));
89
+ }
90
+ /** Déduplique les agents Ask et applique la limite produit sans modifier les listes sources. */
60
91
  function resolveAskAgents(explicitAgents, defaultAgents, fallbackAgents, messages) {
61
92
  const selected = explicitAgents.length > 0 ? explicitAgents : defaultAgents && defaultAgents.length > 0 ? defaultAgents : fallbackAgents;
62
93
  const unique = selected.filter((agent, index) => agent.trim() && selected.indexOf(agent) === index);
package/dist/session.js CHANGED
@@ -1,3 +1,4 @@
1
+ /** @file Construit le contexte de session factuel (date, fuseau, dossier) injecté dans chaque prompt agent. */
1
2
  import path from "node:path";
2
3
  /**
3
4
  * Construit le contexte de session partagé par tous les agents pour la durée du débat.
@@ -1,10 +1,20 @@
1
- import { setOllamaBaseUrl, setOllamaModel, syncDetectedAgentsDetailed, syncOllamaModel, writeExampleConfig } from "./config.js";
1
+ /** @file Contrôleur des interactions TUI qui lisent et persistent la configuration Palabre. */
2
+ import { setOllamaBaseUrl, setOllamaModel, syncDetectedAgentsDetailed, syncOllamaModel, writeConfig } from "./config.js";
2
3
  import { discoverLocalToolsForConfig } from "./discovery.js";
3
4
  import { AdapterError, formatAdapterError } from "./errors.js";
4
5
  import { createTranslator, DEFAULT_LANGUAGE, parseLanguage } from "./i18n.js";
5
6
  import { MAX_ASK_AGENTS, validateTurns } from "./limits.js";
6
7
  import { DEFAULT_OLLAMA_BASE_URL, normalizeOllamaBaseUrl, OllamaUrlError, resolveOllamaBaseUrl } from "./ollamaUrl.js";
7
8
  import { promptTuiAgentsWizard, promptTuiConfigCommand, promptTuiRolesWizard, renderTuiConfig } from "./renderers/tui.js";
9
+ /**
10
+ * Exécute la boucle `/config` jusqu'au retour à l'accueil ou à la fermeture.
11
+ *
12
+ * @param configPath - Fichier de configuration à persister après chaque changement.
13
+ * @param config - Configuration chargée et mutée en mémoire.
14
+ * @param messages - Dictionnaire actif, remplacé si la langue change.
15
+ * @param initialMode - Mode affiché à l'ouverture de la vue.
16
+ * @returns Le mode final et les indicateurs de sortie ou de defaults modifiés.
17
+ */
8
18
  export async function runTuiConfigLoop(configPath, config, messages, initialMode) {
9
19
  let mode = initialMode;
10
20
  let notice;
@@ -27,27 +37,27 @@ export async function runTuiConfigLoop(configPath, config, messages, initialMode
27
37
  if (input.kind === "mode") {
28
38
  mode = mode === "ask" ? "debate" : "ask";
29
39
  config.defaults = { ...(config.defaults ?? {}), mode };
30
- await writeExampleConfig(configPath, config);
40
+ await writeConfig(configPath, config);
31
41
  changedRunDefaults = true;
32
42
  notice = mode === "ask" ? currentMessages.tui.askDefaultMode : currentMessages.tui.debateDefaultMode;
33
43
  continue;
34
44
  }
35
45
  if (input.kind === "default-mode") {
36
46
  config.defaults = { ...(config.defaults ?? {}), mode };
37
- await writeExampleConfig(configPath, config);
47
+ await writeConfig(configPath, config);
38
48
  changedRunDefaults = true;
39
49
  notice = mode === "ask" ? currentMessages.tui.askDefaultMode : currentMessages.tui.debateDefaultMode;
40
50
  continue;
41
51
  }
42
52
  if (input.kind === "interface") {
43
53
  config.defaults = { ...(config.defaults ?? {}), interface: input.interfaceName };
44
- await writeExampleConfig(configPath, config);
54
+ await writeConfig(configPath, config);
45
55
  notice = currentMessages.tui.interfaceDefault(input.interfaceName);
46
56
  continue;
47
57
  }
48
58
  if (input.kind === "language") {
49
59
  config.language = parseLanguage(input.language, "--language");
50
- await writeExampleConfig(configPath, config);
60
+ await writeConfig(configPath, config);
51
61
  currentMessages = createTranslator(config.language ?? DEFAULT_LANGUAGE);
52
62
  notice = currentMessages.tui.languageUpdated(input.language);
53
63
  continue;
@@ -67,14 +77,14 @@ export async function runTuiConfigLoop(configPath, config, messages, initialMode
67
77
  if (mode === "ask") {
68
78
  const agents = normalizeTuiAskAgents(config, agentsInput.agents, currentMessages);
69
79
  config.defaults = { ...(config.defaults ?? {}), askAgents: agents };
70
- await writeExampleConfig(configPath, config);
80
+ await writeConfig(configPath, config);
71
81
  changedRunDefaults = true;
72
82
  notice = currentMessages.tui.askAgentsUpdated(agents.join(", "));
73
83
  }
74
84
  else {
75
85
  const [agentA, agentB] = normalizeTuiDebateAgents(config, agentsInput.agents, currentMessages);
76
86
  config.defaults = { ...(config.defaults ?? {}), agentA, agentB };
77
- await writeExampleConfig(configPath, config);
87
+ await writeConfig(configPath, config);
78
88
  changedRunDefaults = true;
79
89
  notice = currentMessages.tui.debateAgentsUpdated(`${agentA} <-> ${agentB}`);
80
90
  }
@@ -97,7 +107,7 @@ export async function runTuiConfigLoop(configPath, config, messages, initialMode
97
107
  continue;
98
108
  }
99
109
  notice = applyTuiRoles(config, mode, rolesInput.roles, currentMessages);
100
- await writeExampleConfig(configPath, config);
110
+ await writeConfig(configPath, config);
101
111
  }
102
112
  catch (error) {
103
113
  notice = error instanceof Error ? error.message : String(error);
@@ -112,7 +122,7 @@ export async function runTuiConfigLoop(configPath, config, messages, initialMode
112
122
  try {
113
123
  validateTurns(input.turns, "--turns", currentMessages);
114
124
  config.defaults = { ...(config.defaults ?? {}), turns: input.turns };
115
- await writeExampleConfig(configPath, config);
125
+ await writeConfig(configPath, config);
116
126
  changedRunDefaults = true;
117
127
  notice = currentMessages.tui.turnsUpdated(input.turns);
118
128
  }
@@ -146,7 +156,7 @@ export async function runTuiConfigLoop(configPath, config, messages, initialMode
146
156
  notice = currentMessages.tui.debateSummaryAgent(input.agent);
147
157
  }
148
158
  config.defaults = nextDefaults;
149
- await writeExampleConfig(configPath, config);
159
+ await writeConfig(configPath, config);
150
160
  changedRunDefaults = true;
151
161
  }
152
162
  catch (error) {
@@ -195,6 +205,7 @@ export async function runTuiConfigLoop(configPath, config, messages, initialMode
195
205
  }
196
206
  }
197
207
  }
208
+ /** Normalise et persiste l'URL de tous les agents Ollama configurés. */
198
209
  async function setTuiOllamaUrl(configPath, config, value, messages) {
199
210
  if (!Object.values(config.agents).some((agent) => agent.type === "ollama")) {
200
211
  throw new Error(messages.config.ollamaModelNoAgent);
@@ -204,12 +215,13 @@ async function setTuiOllamaUrl(configPath, config, value, messages) {
204
215
  : normalizeOllamaBaseUrl(value);
205
216
  const effective = resolveOllamaBaseUrl({ configUrl: normalized });
206
217
  setOllamaBaseUrl(config, normalized);
207
- await writeExampleConfig(configPath, config);
218
+ await writeConfig(configPath, config);
208
219
  return messages.tui.ollamaUrlUpdated(normalized, effective);
209
220
  }
210
221
  function isDefaultOllamaUrl(value) {
211
222
  return ["default", "defaut", "défaut", "local", "localhost"].includes(value.trim().toLowerCase());
212
223
  }
224
+ /** Interroge le serveur Ollama effectif et formate son état pour une notice TUI. */
213
225
  async function formatTuiOllamaInfo(config, messages) {
214
226
  const discovery = await discoverLocalToolsForConfig(config);
215
227
  const agent = config.agents["ollama-local"];
@@ -225,6 +237,7 @@ async function formatTuiOllamaInfo(config, messages) {
225
237
  const api = `${discovery.ollama.baseUrl}`;
226
238
  return messages.tui.ollamaInfo(agent.model, installed, api);
227
239
  }
240
+ /** Valide un modèle Ollama installé avant de le persister. */
228
241
  async function setTuiOllamaModel(configPath, config, model, messages) {
229
242
  const trimmed = model.trim();
230
243
  if (!trimmed) {
@@ -239,11 +252,12 @@ async function setTuiOllamaModel(configPath, config, model, messages) {
239
252
  throw new Error(messages.config.ollamaModelUnavailable(trimmed));
240
253
  }
241
254
  const result = setOllamaModel(config, trimmed);
242
- await writeExampleConfig(configPath, config);
255
+ await writeConfig(configPath, config);
243
256
  return result
244
257
  ? messages.config.ollamaModelUpdated(configPath, result.previousModel, result.nextModel)
245
258
  : messages.config.ollamaModelNoChange(configPath, agent.model);
246
259
  }
260
+ /** Remplace le modèle Ollama absent par le fallback installé choisi par la config. */
247
261
  async function syncTuiOllamaModel(configPath, config, messages) {
248
262
  const discovery = await discoverLocalToolsForConfig(config);
249
263
  const agent = config.agents["ollama-local"];
@@ -257,14 +271,21 @@ async function syncTuiOllamaModel(configPath, config, messages) {
257
271
  if (!result) {
258
272
  return messages.config.ollamaModelNoChange(configPath, agent.model);
259
273
  }
260
- await writeExampleConfig(configPath, config);
274
+ await writeConfig(configPath, config);
261
275
  return messages.config.ollamaModelUpdated(configPath, result.previousModel, result.nextModel);
262
276
  }
277
+ /**
278
+ * Synchronise les agents connus détectés avant l'affichage de l'accueil TUI.
279
+ *
280
+ * @param configPath - Fichier à réécrire uniquement si la synchronisation change la config.
281
+ * @param config - Configuration chargée et mise à jour en mémoire.
282
+ * @returns Les noms des agents nouvellement ajoutés, utilisés pour la notice d'accueil.
283
+ */
263
284
  export async function syncInteractiveDetectedAgents(configPath, config) {
264
285
  const discovery = await discoverLocalToolsForConfig(config);
265
286
  const result = syncDetectedAgentsDetailed(config, discovery);
266
287
  if (result.changed) {
267
- await writeExampleConfig(configPath, config);
288
+ await writeConfig(configPath, config);
268
289
  }
269
290
  return {
270
291
  addedAgents: result.addedAgents
@@ -290,6 +311,16 @@ function normalizeTuiAskAgents(config, agents, messages) {
290
311
  unique.forEach((agent) => assertKnownAgent(config, agent, "defaults.askAgents", messages));
291
312
  return unique;
292
313
  }
314
+ /**
315
+ * Exécute le wizard d'agents ou applique directement les noms fournis.
316
+ *
317
+ * @param configPath - Fichier de configuration à persister.
318
+ * @param config - Configuration chargée et modifiée en mémoire.
319
+ * @param messages - Dictionnaire localisé de la vue.
320
+ * @param mode - Mode dont les agents actifs doivent être modifiés.
321
+ * @param inlineAgents - Noms fournis directement après la commande `/agents`.
322
+ * @returns Une notice utilisateur et les indicateurs de sortie/changement.
323
+ */
293
324
  export async function runTuiAgentsWizard(configPath, config, messages, mode, inlineAgents = []) {
294
325
  try {
295
326
  const agentsInput = inlineAgents.length > 0
@@ -302,13 +333,23 @@ export async function runTuiAgentsWizard(configPath, config, messages, mode, inl
302
333
  return { quit: false, changedRunDefaults: false };
303
334
  }
304
335
  const notice = applyTuiAgents(config, mode, agentsInput.agents, messages);
305
- await writeExampleConfig(configPath, config);
336
+ await writeConfig(configPath, config);
306
337
  return { notice, quit: false, changedRunDefaults: true };
307
338
  }
308
339
  catch (error) {
309
340
  return { notice: messages.tui.agentsError(error instanceof Error ? error.message : String(error)), quit: false, changedRunDefaults: false };
310
341
  }
311
342
  }
343
+ /**
344
+ * Exécute le wizard de rôles ou applique directement les rôles fournis.
345
+ *
346
+ * @param configPath - Fichier de configuration à persister.
347
+ * @param config - Configuration chargée et modifiée en mémoire.
348
+ * @param messages - Dictionnaire localisé de la vue.
349
+ * @param mode - Mode déterminant la liste d'agents concernés.
350
+ * @param inlineRoles - Rôles fournis directement après la commande `/roles`.
351
+ * @returns Une notice utilisateur et l'indicateur de fermeture de la TUI.
352
+ */
312
353
  export async function runTuiRolesWizard(configPath, config, messages, mode, inlineRoles = []) {
313
354
  try {
314
355
  const rolesInput = inlineRoles.length > 0
@@ -321,13 +362,14 @@ export async function runTuiRolesWizard(configPath, config, messages, mode, inli
321
362
  return { quit: false };
322
363
  }
323
364
  const notice = applyTuiRoles(config, mode, rolesInput.roles, messages);
324
- await writeExampleConfig(configPath, config);
365
+ await writeConfig(configPath, config);
325
366
  return { notice, quit: false };
326
367
  }
327
368
  catch (error) {
328
369
  return { notice: messages.tui.rolesError(error instanceof Error ? error.message : String(error)), quit: false };
329
370
  }
330
371
  }
372
+ /** Valide puis applique les agents actifs du mode courant. */
331
373
  function applyTuiAgents(config, mode, agentNames, messages) {
332
374
  if (mode === "ask") {
333
375
  const agents = normalizeTuiAskAgents(config, agentNames, messages);
@@ -338,6 +380,7 @@ function applyTuiAgents(config, mode, agentNames, messages) {
338
380
  config.defaults = { ...(config.defaults ?? {}), agentA, agentB };
339
381
  return messages.tui.debateAgentsUpdated(`${agentA} <-> ${agentB}`);
340
382
  }
383
+ /** Associe les rôles saisis aux agents actifs du mode courant. */
341
384
  function applyTuiRoles(config, mode, roleNames, messages) {
342
385
  const agents = activeAgentsForMode(config, mode);
343
386
  if (agents.length === 0) {
@@ -361,6 +404,7 @@ function activeAgentsForMode(config, mode) {
361
404
  }
362
405
  return [defaults.agentA, defaults.agentB].filter((agent) => Boolean(agent && config.agents[agent]));
363
406
  }
407
+ /** Valide les rôles connus et conserve exactement le nombre attendu. */
364
408
  function normalizeTuiRoles(roleNames, agents, mode, messages) {
365
409
  const roles = roleNames.map((role) => role.trim().toLowerCase()).filter(Boolean);
366
410
  const expectedCount = agents.length;
@@ -386,6 +430,7 @@ function assertKnownAgent(config, agentName, fieldName, messages) {
386
430
  throw new Error(messages.common.unknownAgentForField(fieldName, agentName, Object.keys(config.agents).join(", ")));
387
431
  }
388
432
  }
433
+ /** Convertit les erreurs adapter et URL stables en message localisé pour la TUI. */
389
434
  function formatTuiRuntimeError(error, messages) {
390
435
  if (error instanceof AdapterError)
391
436
  return formatAdapterError(error, messages);
package/dist/tuiState.js CHANGED
@@ -1,3 +1,5 @@
1
+ /** @file État partagé entre le point d'entrée et la TUI pour réinitialiser les overrides de lancement entre deux sessions. */
2
+ /** Flags de session ponctuels (par sujet) qui ne doivent pas persister entre deux lancements TUI successifs. */
1
3
  const TUI_RUN_OVERRIDE_FLAGS = [
2
4
  "preset",
3
5
  "agent-a",
@@ -19,11 +21,13 @@ const TUI_RUN_OVERRIDE_FLAGS = [
19
21
  "terminal",
20
22
  "json"
21
23
  ];
24
+ /** Supprime les overrides de lancement d'une session TUI terminée, pour repartir des defaults au prochain sujet. */
22
25
  export function clearTuiRunOverrides(flags) {
23
26
  for (const flag of TUI_RUN_OVERRIDE_FLAGS) {
24
27
  delete flags[flag];
25
28
  }
26
29
  }
30
+ /** Pré-remplit les agents `ask` proposés par le wizard : explicites, sinon defaults de config, sinon aucun en mode `debate`. */
27
31
  export function askAgentSeedsForMode(mode, explicitAskAgents, defaultAskAgents) {
28
32
  if (mode !== "ask") {
29
33
  return [];
package/dist/types.js CHANGED
@@ -1 +1,2 @@
1
+ /** @file Contrats partagés entre modules : agents, config, options de débat et résultats de session. */
1
2
  export {};
package/dist/update.js CHANGED
@@ -1,3 +1,4 @@
1
+ /** @file Détection du mode d'installation et application du workflow de mise à jour pour un checkout source. */
1
2
  import { spawn } from "node:child_process";
2
3
  import { access } from "node:fs/promises";
3
4
  import path from "node:path";
package/dist/version.js CHANGED
@@ -1,3 +1,4 @@
1
+ /** @file Version locale courante et dernière version publiée npm, pour `--version` et la notice de mise à jour TUI. */
1
2
  import { readFile } from "node:fs/promises";
2
3
  import path from "node:path";
3
4
  import { fileURLToPath } from "node:url";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "palabre",
3
- "version": "0.9.1",
3
+ "version": "0.10.1",
4
4
  "description": "Orchestrateur de debat entre agents IA locaux, CLIs et Ollama.",
5
5
  "license": "MIT",
6
6
  "type": "module",