palabre 0.1.4

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/errors.js ADDED
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Erreur typée levée par les adapters.
3
+ * `kind` est stable et utilisé par l'orchestrateur pour classifier l'échec sans inspecter le message.
4
+ */
5
+ export class AdapterError extends Error {
6
+ kind;
7
+ adapterName;
8
+ details;
9
+ constructor(kind, adapterName, message, details) {
10
+ super(message);
11
+ this.kind = kind;
12
+ this.adapterName = adapterName;
13
+ this.details = details;
14
+ this.name = "AdapterError";
15
+ }
16
+ }
17
+ /** Formate le message d'erreur avec une suggestion actionnable selon `error.kind`. */
18
+ export function formatAdapterError(error) {
19
+ const hint = hintForFailure(error.kind);
20
+ return hint ? `${error.message}\nSuggestion: ${hint}` : error.message;
21
+ }
22
+ function hintForFailure(kind) {
23
+ switch (kind) {
24
+ case "command-not-found":
25
+ return "Verifie que la CLI est installee, authentifiee et disponible dans le PATH.";
26
+ case "spawn-failed":
27
+ return "Sur Windows, essaye le wrapper .cmd ou active \"shell\": true dans la config agent.";
28
+ case "timeout":
29
+ return "Augmente timeoutMs ou teste la commande directement dans le terminal.";
30
+ case "idle-timeout":
31
+ return "Desactive idleTimeoutMs pour les CLIs IA qui restent silencieuses pendant la generation.";
32
+ case "empty-output":
33
+ return "Teste la commande en dehors de Palabre et verifie que le prompt est bien lu via stdin ou argument.";
34
+ case "usage-limit":
35
+ return "Attends la fenetre indiquee par la CLI, change de modele ou relance avec un autre agent/preset disponible.";
36
+ case "non-zero-exit":
37
+ return "Teste la commande directement, puis ajuste args, permissions, modele ou authentification de la CLI.";
38
+ case "model-unavailable":
39
+ return "Installe le modele Ollama ou relance avec --pull-models pour autoriser le telechargement.";
40
+ case "unsupported-model":
41
+ return "Verifie le nom du modele, ton abonnement, ou retire --model-a/--model-b/--summary-model pour laisser la CLI utiliser son modele par defaut.";
42
+ case "model-pull-failed":
43
+ return "Verifie le nom du modele, ta connexion et l'espace disque disponible.";
44
+ case "http-error":
45
+ return "Verifie que le service local est lance et que baseUrl est correct.";
46
+ default:
47
+ return undefined;
48
+ }
49
+ }
package/dist/index.js ADDED
@@ -0,0 +1,635 @@
1
+ #!/usr/bin/env node
2
+ import { readFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { configExists, createConfigFromDiscovery, DEFAULT_CONFIG_PATH, GLOBAL_CONFIG_PATH, loadConfig, resolveDefaultConfigPath, writeExampleConfig } from "./config.js";
6
+ import { loadProjectInputs } from "./context.js";
7
+ import { discoverLocalTools } from "./discovery.js";
8
+ import { runDoctor } from "./doctor.js";
9
+ import { AdapterError, formatAdapterError } from "./errors.js";
10
+ import { runConfigWizard } from "./configWizard.js";
11
+ import { DEFAULT_TURNS, parseTurnsFlag, turnsOrDefault } from "./limits.js";
12
+ import { formatAgentPrompt } from "./prompt.js";
13
+ import { runNewWizard } from "./new.js";
14
+ import { listPresetNames, resolvePreset } from "./presets.js";
15
+ import { createConsoleRenderer } from "./renderers/console.js";
16
+ import { runDebate } from "./orchestrator.js";
17
+ import { writeDebateMarkdown } from "./output.js";
18
+ import { applySourceUpdate, formatUpdateInstructions, getUpdateInfo } from "./update.js";
19
+ import { createSessionContext } from "./session.js";
20
+ async function main() {
21
+ const parsed = parseArgs(process.argv.slice(2));
22
+ if (parsed.command === "version" || parsed.flags.version) {
23
+ console.log(await getPackageVersion());
24
+ return;
25
+ }
26
+ if (parsed.command === "help" || parsed.flags.help) {
27
+ printHelp();
28
+ return;
29
+ }
30
+ if (parsed.command === "doctor") {
31
+ const result = await runDoctor(optionalString(parsed.flags.config), Boolean(parsed.flags.plain));
32
+ console.log(result.output);
33
+ process.exitCode = result.ok ? 0 : 1;
34
+ return;
35
+ }
36
+ if (parsed.command === "config") {
37
+ await runConfigCommand(parsed.flags);
38
+ return;
39
+ }
40
+ if (parsed.command === "agents" || parsed.command === "agent") {
41
+ await runAgentsCommand(parsed.flags);
42
+ return;
43
+ }
44
+ if (parsed.command === "update") {
45
+ const info = await getUpdateInfo(await getPackageVersion());
46
+ if (parsed.flags.apply) {
47
+ await applySourceUpdate(info);
48
+ console.log("PALABRE est a jour.");
49
+ return;
50
+ }
51
+ console.log(formatUpdateInstructions(info));
52
+ return;
53
+ }
54
+ if (parsed.command === "init" || parsed.command === "setup") {
55
+ const initConfigPath = optionalString(parsed.flags.config) ?? (parsed.flags.local ? DEFAULT_CONFIG_PATH : GLOBAL_CONFIG_PATH);
56
+ if (await configExists(initConfigPath)) {
57
+ console.log(`${initConfigPath} existe déjà.`);
58
+ return;
59
+ }
60
+ const discovery = await discoverLocalTools();
61
+ const config = createConfigFromDiscovery(discovery);
62
+ await writeExampleConfig(initConfigPath, config);
63
+ console.log(`${initConfigPath} créé.`);
64
+ printInitDiscovery(discovery, config);
65
+ return;
66
+ }
67
+ const configPath = optionalString(parsed.flags.config) ?? await resolveDefaultConfigPath();
68
+ if (!(await configExists(configPath))) {
69
+ await writeExampleConfig(configPath);
70
+ console.log(`${configPath} créé. Édite la config puis relance palabre run.`);
71
+ return;
72
+ }
73
+ const config = await loadConfig(configPath);
74
+ if (parsed.command === "new") {
75
+ const selection = await runNewWizard(config);
76
+ if (!selection) {
77
+ console.log("Création de débat annulée.");
78
+ return;
79
+ }
80
+ parsed.flags["agent-a"] = selection.agentA;
81
+ parsed.flags["agent-b"] = selection.agentB;
82
+ parsed.flags.topic = selection.topic;
83
+ if (selection.modelA)
84
+ parsed.flags["model-a"] = selection.modelA;
85
+ if (selection.modelB)
86
+ parsed.flags["model-b"] = selection.modelB;
87
+ if (selection.turns)
88
+ parsed.flags.turns = String(selection.turns);
89
+ if (selection.summaryAgent)
90
+ parsed.flags["summary-agent"] = selection.summaryAgent;
91
+ if (selection.summaryModel)
92
+ parsed.flags["summary-model"] = selection.summaryModel;
93
+ if (selection.summaryEnabled === false)
94
+ parsed.flags["no-summary"] = true;
95
+ if (selection.showPrompt)
96
+ parsed.flags["show-prompt"] = true;
97
+ if (selection.plainOutput)
98
+ parsed.flags.plain = true;
99
+ if (selection.files.length > 0)
100
+ parsed.flags.files = selection.files;
101
+ if (selection.context.length > 0)
102
+ parsed.flags.context = selection.context;
103
+ }
104
+ const topic = optionalString(parsed.flags.topic) ?? "";
105
+ const context = await loadProjectInputs(getStringListFlag(parsed.flags.files), getStringListFlag(parsed.flags.context));
106
+ const presetName = optionalString(parsed.flags.preset);
107
+ const preset = presetName ? resolvePreset(presetName) : undefined;
108
+ if (!topic) {
109
+ throw new Error("Le parametre --topic/--subject est requis.");
110
+ }
111
+ const options = {
112
+ topic,
113
+ agentA: resolveAgentName("agent A", parsed.flags["agent-a"], preset?.agentA, config.defaults?.agentA),
114
+ agentB: resolveAgentName("agent B", parsed.flags["agent-b"], preset?.agentB, config.defaults?.agentB),
115
+ turns: parseTurnsFlag(parsed.flags.turns, config.defaults?.turns ?? DEFAULT_TURNS, "--turns"),
116
+ session: createSessionContext(),
117
+ files: context.files,
118
+ modelA: optionalString(parsed.flags["model-a"]),
119
+ modelB: optionalString(parsed.flags["model-b"]),
120
+ pullModels: Boolean(parsed.flags["pull-models"]),
121
+ summaryAgent: optionalString(parsed.flags["summary-agent"]) ?? config.defaults?.summaryAgent,
122
+ summaryModel: optionalString(parsed.flags["summary-model"]),
123
+ summaryEnabled: !parsed.flags["no-summary"],
124
+ earlyStopOnAgreement: !parsed.flags["no-early-stop"],
125
+ plainOutput: Boolean(parsed.flags.plain)
126
+ };
127
+ if (parsed.flags["show-prompt"]) {
128
+ printContextWarnings(context.warnings);
129
+ printPromptPreview(config, options);
130
+ return;
131
+ }
132
+ const renderer = createConsoleRenderer(options.plainOutput);
133
+ context.warnings.forEach((warning) => renderer.warning(warning));
134
+ const result = await runDebate(config, options, renderer);
135
+ const outputPath = await writeDebateMarkdown(config.outputDir ?? ".", result.options, result.messages, result.summary, result.stopReason);
136
+ renderer.done(outputPath);
137
+ }
138
+ async function runAgentsCommand(flags) {
139
+ const configPath = optionalString(flags.config) ?? await resolveDefaultConfigPath();
140
+ if (!(await configExists(configPath))) {
141
+ throw new Error("Aucune config trouvée. Lance `palabre init`, puis `palabre agents`.");
142
+ }
143
+ const config = await loadConfig(configPath);
144
+ const discovery = await discoverLocalTools();
145
+ printAgents(configPath, config, discovery);
146
+ }
147
+ async function runConfigCommand(flags) {
148
+ const configPath = optionalString(flags.config) ?? await resolveDefaultConfigPath();
149
+ if (!(await configExists(configPath))) {
150
+ await writeExampleConfig(configPath);
151
+ console.log(`${configPath} créé. Édite la config puis relance palabre config.`);
152
+ return;
153
+ }
154
+ const config = await loadConfig(configPath);
155
+ if (flags["sync-agents"]) {
156
+ const discovery = await discoverLocalTools();
157
+ const addedAgents = syncDetectedAgents(config, discovery);
158
+ if (addedAgents.length === 0) {
159
+ console.log(`Aucun agent détecté manquant dans ${configPath}.`);
160
+ return;
161
+ }
162
+ await writeExampleConfig(configPath, config);
163
+ console.log(`Agents ajoutés dans ${configPath}: ${addedAgents.join(", ")}.`);
164
+ return;
165
+ }
166
+ const defaultAgents = getStringListFlag(flags["set-defaults"]);
167
+ const hasTurnsFlag = flags.turns !== undefined;
168
+ const summaryAgentValue = optionalString(flags["summary-agent"]);
169
+ if (defaultAgents.length > 0 || hasTurnsFlag || summaryAgentValue !== undefined) {
170
+ const nextDefaults = { ...(config.defaults ?? {}) };
171
+ if (defaultAgents.length > 0) {
172
+ const [agentA, agentB] = defaultAgents;
173
+ if (!agentA || !agentB) {
174
+ throw new Error("L'option --set-defaults attend deux agents: --set-defaults <agentA> <agentB>.");
175
+ }
176
+ assertKnownAgent(config, agentA, "defaults.agentA");
177
+ assertKnownAgent(config, agentB, "defaults.agentB");
178
+ nextDefaults.agentA = agentA;
179
+ nextDefaults.agentB = agentB;
180
+ }
181
+ if (hasTurnsFlag) {
182
+ nextDefaults.turns = parseTurnsFlag(flags.turns, nextDefaults.turns ?? DEFAULT_TURNS, "--turns");
183
+ }
184
+ if (summaryAgentValue !== undefined) {
185
+ if (isNoneValue(summaryAgentValue)) {
186
+ delete nextDefaults.summaryAgent;
187
+ }
188
+ else {
189
+ assertKnownAgent(config, summaryAgentValue, "defaults.summaryAgent");
190
+ nextDefaults.summaryAgent = summaryAgentValue;
191
+ }
192
+ }
193
+ config.defaults = nextDefaults;
194
+ await writeExampleConfig(configPath, config);
195
+ console.log(`Paramètres par défaut mis à jour dans ${configPath}: ${formatDefaultsForMessage(config.defaults)}.`);
196
+ return;
197
+ }
198
+ if (flags["clear-defaults"]) {
199
+ delete config.defaults;
200
+ await writeExampleConfig(configPath, config);
201
+ console.log(`Paramètres par défaut supprimés dans ${configPath}. Utilise maintenant un preset ou --agent-a/--agent-b pour lancer un débat.`);
202
+ return;
203
+ }
204
+ await runConfigWizard(configPath, config);
205
+ }
206
+ function isNoneValue(value) {
207
+ return ["0", "none", "aucun", "disabled", "désactivé", "desactive"].includes(value.trim().toLowerCase());
208
+ }
209
+ function formatDefaultsForMessage(defaults) {
210
+ const pair = defaults.agentA && defaults.agentB
211
+ ? `agents: ${defaults.agentA} <-> ${defaults.agentB}`
212
+ : "agents: non définis";
213
+ const summary = defaults.summaryAgent ? `synthèse: ${defaults.summaryAgent}` : "synthèse: agent B";
214
+ return `${pair}, réponses: ${turnsOrDefault(defaults.turns)}, ${summary}`;
215
+ }
216
+ function assertKnownAgent(config, agentName, fieldName) {
217
+ if (!config.agents[agentName]) {
218
+ throw new Error(`Agent inconnu pour ${fieldName}: ${agentName}. Agents disponibles: ${Object.keys(config.agents).join(", ")}.`);
219
+ }
220
+ }
221
+ function resolveAgentName(label, explicitValue, presetValue, defaultValue) {
222
+ const resolved = optionalString(explicitValue) ?? presetValue ?? defaultValue;
223
+ if (!resolved) {
224
+ throw new Error(`Aucun ${label} défini. Utilise --agent-a/--agent-b, un preset, ou lance palabre init pour définir defaults.agentA/defaults.agentB.`);
225
+ }
226
+ return resolved;
227
+ }
228
+ function printPromptPreview(config, options) {
229
+ const agentConfig = config.agents[options.agentA];
230
+ if (!agentConfig) {
231
+ throw new Error(`Agent inconnu: ${options.agentA}`);
232
+ }
233
+ const prompt = formatAgentPrompt({
234
+ topic: options.topic,
235
+ turn: 1,
236
+ selfName: options.agentA,
237
+ peerName: options.agentB,
238
+ selfRole: agentConfig.role,
239
+ session: options.session,
240
+ files: options.files,
241
+ transcript: []
242
+ });
243
+ console.log(`# Prompt preview`);
244
+ console.log(`Agent: ${options.agentA} (${agentConfig.role})`);
245
+ console.log(`Peer: ${options.agentB}`);
246
+ console.log(`Pull missing Ollama models: ${options.pullModels ? "yes" : "no"}`);
247
+ console.log(`Summary: ${options.summaryEnabled ? options.summaryAgent ?? options.agentB : "disabled"}`);
248
+ console.log("");
249
+ console.log(prompt);
250
+ console.log("");
251
+ console.log("Note: seuls les prompts du premier tour sont exacts sans exécuter les agents. Les tours suivants incluent le transcript réel.");
252
+ }
253
+ function optionalString(value) {
254
+ return typeof value === "string" && value.trim() ? value : undefined;
255
+ }
256
+ function parseArgs(args) {
257
+ const flags = {};
258
+ let command = "run";
259
+ let commandExplicit = false;
260
+ const positionals = [];
261
+ const commands = new Set(["run", "new", "init", "setup", "help", "version", "update", "doctor", "config", "agent", "agents"]);
262
+ const presets = new Set(listPresetNames());
263
+ for (let index = 0; index < args.length; index += 1) {
264
+ const value = args[index];
265
+ if (!value.startsWith("-") && index === 0) {
266
+ if (commands.has(value)) {
267
+ command = value;
268
+ commandExplicit = true;
269
+ }
270
+ else if (isLikelyCommandTypo(value, commands)) {
271
+ throw new Error(`Commande inconnue: ${value}. Commandes disponibles: ${Array.from(commands).join(", ")}.`);
272
+ }
273
+ else {
274
+ positionals.push(value);
275
+ }
276
+ continue;
277
+ }
278
+ if (!value.startsWith("-")) {
279
+ positionals.push(value);
280
+ continue;
281
+ }
282
+ if (value === "-h") {
283
+ flags.help = true;
284
+ continue;
285
+ }
286
+ if (value === "-v") {
287
+ flags.version = true;
288
+ continue;
289
+ }
290
+ if (value === "-a") {
291
+ command = "agents";
292
+ commandExplicit = true;
293
+ continue;
294
+ }
295
+ if (value === "-s") {
296
+ const next = args[index + 1];
297
+ if (!next || next.startsWith("-")) {
298
+ throw new Error("L'option -s attend une valeur.");
299
+ }
300
+ flags.topic = next;
301
+ index += 1;
302
+ continue;
303
+ }
304
+ if (value === "-t") {
305
+ const next = args[index + 1];
306
+ if (!next || next.startsWith("-")) {
307
+ throw new Error("L'option -t attend une valeur.");
308
+ }
309
+ flags.turns = next;
310
+ index += 1;
311
+ continue;
312
+ }
313
+ if (value.startsWith("--")) {
314
+ const rawKey = value.slice(2);
315
+ const key = normalizeFlagName(rawKey);
316
+ if (key === "set-defaults") {
317
+ const values = [];
318
+ while (args[index + 1] && !args[index + 1].startsWith("-") && values.length < 2) {
319
+ values.push(args[index + 1]);
320
+ index += 1;
321
+ }
322
+ if (values.length !== 2) {
323
+ throw new Error("L'option --set-defaults attend deux agents: --set-defaults <agentA> <agentB>.");
324
+ }
325
+ flags[key] = values;
326
+ continue;
327
+ }
328
+ if (key === "files" || key === "context") {
329
+ const values = [];
330
+ while (args[index + 1] && !args[index + 1].startsWith("-")) {
331
+ values.push(args[index + 1]);
332
+ index += 1;
333
+ }
334
+ flags[key] = [...getStringListFlag(flags[key]), ...values];
335
+ continue;
336
+ }
337
+ const next = args[index + 1];
338
+ if (!next || next.startsWith("-")) {
339
+ if (requiresFlagValue(key)) {
340
+ throw new Error(`L'option --${rawKey} attend une valeur.`);
341
+ }
342
+ flags[key] = true;
343
+ }
344
+ else {
345
+ flags[key] = next;
346
+ index += 1;
347
+ }
348
+ }
349
+ }
350
+ if (command === "run") {
351
+ applyRunPositionals(positionals, flags, presets, commandExplicit);
352
+ }
353
+ return { command, commandExplicit, flags };
354
+ }
355
+ function isLikelyCommandTypo(value, commands) {
356
+ const normalized = value.toLowerCase();
357
+ for (const command of commands) {
358
+ if (normalized[0] === command[0] && levenshteinDistance(normalized, command) <= 2) {
359
+ return true;
360
+ }
361
+ }
362
+ return false;
363
+ }
364
+ function levenshteinDistance(left, right) {
365
+ const previous = Array.from({ length: right.length + 1 }, (_, index) => index);
366
+ for (let leftIndex = 0; leftIndex < left.length; leftIndex += 1) {
367
+ let diagonal = previous[0];
368
+ previous[0] = leftIndex + 1;
369
+ for (let rightIndex = 0; rightIndex < right.length; rightIndex += 1) {
370
+ const insertCost = previous[rightIndex + 1] + 1;
371
+ const deleteCost = previous[rightIndex] + 1;
372
+ const replaceCost = diagonal + (left[leftIndex] === right[rightIndex] ? 0 : 1);
373
+ diagonal = previous[rightIndex + 1];
374
+ previous[rightIndex + 1] = Math.min(insertCost, deleteCost, replaceCost);
375
+ }
376
+ }
377
+ return previous[right.length] ?? 0;
378
+ }
379
+ function applyRunPositionals(positionals, flags, presets, commandExplicit) {
380
+ if (positionals.length === 0) {
381
+ return;
382
+ }
383
+ const [first, ...rest] = positionals;
384
+ if (presets.has(first)) {
385
+ flags.preset ??= first;
386
+ if (rest.length > 0) {
387
+ flags.topic ??= rest.join(" ");
388
+ }
389
+ return;
390
+ }
391
+ if (!commandExplicit && positionals.length === 1 && !positionals[0]?.includes(" ")) {
392
+ throw new Error(`Commande inconnue ou sujet ambigu: ${positionals[0]}. Utilise -s "${positionals[0]}" pour un sujet en un mot, ou palabre help pour voir les commandes.`);
393
+ }
394
+ flags.topic ??= positionals.join(" ");
395
+ }
396
+ function normalizeFlagName(value) {
397
+ const aliases = {
398
+ s: "topic",
399
+ subject: "topic",
400
+ t: "turns"
401
+ };
402
+ return aliases[value] ?? value;
403
+ }
404
+ function requiresFlagValue(value) {
405
+ return new Set([
406
+ "agent-a",
407
+ "agent-b",
408
+ "config",
409
+ "model-a",
410
+ "model-b",
411
+ "preset",
412
+ "summary-agent",
413
+ "summary-model",
414
+ "set-defaults",
415
+ "topic",
416
+ "turns"
417
+ ]).has(value);
418
+ }
419
+ async function getPackageVersion() {
420
+ const packageJsonPath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "package.json");
421
+ const raw = await readFile(packageJsonPath, "utf8");
422
+ const packageJson = JSON.parse(raw);
423
+ return packageJson.version ?? "0.0.0";
424
+ }
425
+ function getStringListFlag(value) {
426
+ if (Array.isArray(value)) {
427
+ return value;
428
+ }
429
+ if (typeof value === "string") {
430
+ return [value];
431
+ }
432
+ return [];
433
+ }
434
+ function printContextWarnings(warnings) {
435
+ for (const warning of warnings) {
436
+ process.stderr.write(`Warning: ${warning}\n`);
437
+ }
438
+ }
439
+ function syncDetectedAgents(config, discovery) {
440
+ const discoveredConfig = createConfigFromDiscovery(discovery);
441
+ const missingAgents = findDetectedMissingAgents(config, discovery);
442
+ for (const agentName of missingAgents) {
443
+ config.agents[agentName] = discoveredConfig.agents[agentName];
444
+ }
445
+ return missingAgents;
446
+ }
447
+ function findDetectedMissingAgents(config, discovery) {
448
+ const detectedAgents = [
449
+ discovery.codex.available ? "codex" : undefined,
450
+ discovery.claude.available ? "claude" : undefined,
451
+ discovery.gemini.available ? "gemini" : undefined,
452
+ discovery.opencode.available ? "opencode" : undefined,
453
+ discovery.ollama.available ? "ollama-local" : undefined
454
+ ].filter((agent) => Boolean(agent));
455
+ return detectedAgents.filter((agentName) => !config.agents[agentName]);
456
+ }
457
+ function printAgents(configPath, config, discovery) {
458
+ const entries = Object.entries(config.agents).sort(([left], [right]) => left.localeCompare(right));
459
+ console.log(`Config: ${configPath}`);
460
+ console.log("");
461
+ console.log("Agents déclarés:");
462
+ for (const [name, agentConfig] of entries) {
463
+ const status = formatAgentDetection(name, agentConfig, discovery);
464
+ const defaults = formatAgentDefaults(name, config);
465
+ const details = formatAgentDetails(agentConfig);
466
+ const suffix = defaults ? ` | ${defaults}` : "";
467
+ console.log(`- ${name.padEnd(13)} ${`${agentConfig.type}/${agentConfig.role}`.padEnd(18)} ${status}${suffix}`);
468
+ if (details) {
469
+ console.log(` ${details}`);
470
+ }
471
+ }
472
+ console.log("");
473
+ console.log(`Défauts: ${config.defaults?.agentA ?? "aucun"} <-> ${config.defaults?.agentB ?? "aucun"}, réponses: ${turnsOrDefault(config.defaults?.turns)}, synthèse: ${config.defaults?.summaryAgent ?? "agent B"}`);
474
+ }
475
+ function formatAgentDefaults(name, config) {
476
+ const labels = [];
477
+ if (config.defaults?.agentA === name)
478
+ labels.push("agent A par défaut");
479
+ if (config.defaults?.agentB === name)
480
+ labels.push("agent B par défaut");
481
+ if (config.defaults?.summaryAgent === name)
482
+ labels.push("synthèse par défaut");
483
+ return labels.join(", ");
484
+ }
485
+ function formatAgentDetails(agentConfig) {
486
+ if (agentConfig.type === "ollama") {
487
+ return `modèle: ${agentConfig.model}`;
488
+ }
489
+ return `commande: ${agentConfig.command}${agentConfig.model ? ` | modèle: ${agentConfig.model}` : ""}`;
490
+ }
491
+ function formatAgentDetection(name, agentConfig, discovery) {
492
+ if (agentConfig.type === "ollama") {
493
+ if (!discovery.ollama.available) {
494
+ return discovery.ollama.commandAvailable ? "Ollama non joignable" : "Ollama non détecté";
495
+ }
496
+ return discovery.ollama.models.includes(agentConfig.model)
497
+ ? "détecté"
498
+ : `modèle absent (${agentConfig.model})`;
499
+ }
500
+ const detection = cliDetectionForAgent(name, agentConfig, discovery);
501
+ return detection.available ? `détecté (${detection.command})` : "non détecté";
502
+ }
503
+ function cliDetectionForAgent(name, agentConfig, discovery) {
504
+ const command = normalizeCommandName(agentConfig.type === "cli" ? agentConfig.command : name);
505
+ if (command === "codex")
506
+ return discovery.codex;
507
+ if (command === "claude")
508
+ return discovery.claude;
509
+ if (command === "gemini")
510
+ return discovery.gemini;
511
+ if (command === "opencode")
512
+ return discovery.opencode;
513
+ return { available: true, command: agentConfig.type === "cli" ? agentConfig.command : name };
514
+ }
515
+ function normalizeCommandName(command) {
516
+ return path.basename(command).replace(/\.(cmd|exe|ps1|bat)$/i, "").toLowerCase();
517
+ }
518
+ function printInitDiscovery(discovery, config) {
519
+ console.log("");
520
+ console.log("Détection locale:");
521
+ console.log(`- Codex CLI: ${formatCommandDetection(discovery.codex)}`);
522
+ console.log(`- Claude CLI: ${formatCommandDetection(discovery.claude)}`);
523
+ console.log(`- Gemini CLI: ${formatCommandDetection(discovery.gemini)}`);
524
+ console.log(`- OpenCode CLI: ${formatCommandDetection(discovery.opencode)}`);
525
+ console.log(`- Ollama API: ${formatOllamaDetection(discovery.ollama)}`);
526
+ console.log("");
527
+ console.log(`Défauts: ${config.defaults?.agentA ?? "codex"} <-> ${config.defaults?.agentB ?? "ollama-local"}`);
528
+ }
529
+ function formatCommandDetection(detection) {
530
+ return detection.available
531
+ ? `détecté (${detection.command})`
532
+ : "non détecté";
533
+ }
534
+ function formatOllamaDetection(detection) {
535
+ if (!detection.available) {
536
+ return detection.commandAvailable
537
+ ? `serveur non joignable (${detection.baseUrl})`
538
+ : "non détecté";
539
+ }
540
+ const modelCount = detection.models.length;
541
+ return `détectée (${modelCount} modèle${modelCount > 1 ? "s" : ""})`;
542
+ }
543
+ function printHelp() {
544
+ console.log(`
545
+ PALABRE
546
+
547
+ Usage rapide:
548
+ palabre new
549
+ Assistant interactif pour choisir les agents, le sujet et les options.
550
+ palabre run -s "Sujet"
551
+ Lance avec les agents par défaut de la config.
552
+ palabre claude-gemini "Sujet" -t 4
553
+ Lance avec un preset et un sujet positionnel.
554
+
555
+ Commandes:
556
+ palabre init [--local]
557
+ Crée une config et détecte Codex, Claude, Gemini, OpenCode et Ollama.
558
+ palabre agents [--config <path>]
559
+ Liste les agents déclarés dans la config et leur détection locale.
560
+ palabre config
561
+ Assistant pour définir ou supprimer les paramètres par défaut.
562
+ palabre config --set-defaults <agentA> <agentB> [-t <n>] [--summary-agent <name>]
563
+ Définit les agents par défaut, et optionnellement les réponses et la synthèse.
564
+ palabre config -t <n>
565
+ Définit seulement le nombre de réponses par défaut.
566
+ palabre config --summary-agent <name|none>
567
+ Définit ou retire seulement l'agent de synthèse par défaut.
568
+ palabre config --clear-defaults
569
+ Supprime les paramètres par défaut.
570
+ palabre doctor [--config <path>]
571
+ Vérifie la config et les outils locaux.
572
+ palabre update [--apply]
573
+ Affiche ou exécute les étapes de mise à jour d'un checkout git.
574
+ palabre help
575
+ Affiche cette aide. Identique à -h ou --help.
576
+ palabre version
577
+ Affiche la version. Identique à -v ou --version.
578
+
579
+ Notation:
580
+ [option] signifie facultatif. Ne tape pas les crochets.
581
+ <valeur> signifie qu'il faut remplacer ce texte par ta valeur.
582
+
583
+ Options générales:
584
+ -h, --help Affiche cette aide
585
+ -v, --version Affiche la version
586
+ -a Liste les agents. Identique à palabre agents
587
+ --config <path> Chemin vers un fichier de config explicite
588
+ --plain Utilise le rendu console simple sans habillage TUI
589
+
590
+ Sujet et lancement:
591
+ -s, --subject <text> Sujet du débat, option recommandée
592
+ --topic <text> Alias compatible de --subject
593
+ --agent-a <name> Premier agent
594
+ --agent-b <name> Second agent
595
+ --preset <name> Preset de paire d'agents. Exemples: codex-claude, claude-gemini
596
+ -t, --turns <number> Nombre total de réponses (1 à 20)
597
+ --no-early-stop Désactive l'arrêt anticipé si les agents sont clairement d'accord
598
+
599
+ Modèles:
600
+ --model-a <model> Modèle brut transmis à l'agent A
601
+ --model-b <model> Modèle brut transmis à l'agent B
602
+ --pull-models Autorise Ollama à télécharger un modèle manquant
603
+
604
+ Synthèse:
605
+ --summary-agent <name> Agent utilisé pour produire la synthèse finale
606
+ --summary-model <model> Modèle brut transmis à l'agent de synthèse
607
+ --no-summary Désactive la synthèse finale
608
+
609
+ Contexte:
610
+ --files <paths...> Fichiers texte à injecter explicitement dans le contexte
611
+ --context <paths...> Scanne fichiers/dossiers texte en respectant les limites de contexte
612
+ --show-prompt Affiche le prompt du premier tour sans appeler d'agent
613
+
614
+ Configuration:
615
+ --local Avec init/setup, crée ./palabre.config.json
616
+ --set-defaults <a b> Avec config, définit les agents par défaut
617
+ --summary-agent <name> Avec config, définit l'agent de synthèse par défaut
618
+ --summary-agent none Avec config, retire l'agent de synthèse par défaut
619
+ --clear-defaults Avec config, supprime les paramètres par défaut
620
+ --sync-agents Avec config, ajoute les agents détectés manquants
621
+
622
+ Mise à jour:
623
+ --apply Avec update, exécute les étapes de mise à jour
624
+
625
+ Presets disponibles:
626
+ ${listPresetNames().join(", ")}
627
+ `);
628
+ }
629
+ main().catch((error) => {
630
+ const message = error instanceof AdapterError
631
+ ? formatAdapterError(error)
632
+ : error instanceof Error ? error.message : String(error);
633
+ console.error(`Erreur: ${message}`);
634
+ process.exitCode = 1;
635
+ });