palabre 0.3.0 → 0.5.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.
package/dist/prompt.js CHANGED
@@ -1,101 +1,86 @@
1
+ import { createTranslator } from "./i18n.js";
1
2
  /**
2
3
  * Formate le prompt complet transmis à l'adapter.
3
4
  * Dispatche vers le format de synthèse si `input.mode === "summary"`, sinon construit le prompt de débat standard.
4
5
  */
5
6
  export function formatAgentPrompt(input) {
7
+ const messages = createTranslator(input.language ?? "fr").prompt;
6
8
  if (input.mode === "summary") {
7
- return formatSummaryPrompt(input);
9
+ return formatSummaryPrompt(input, messages);
8
10
  }
9
11
  const transcript = formatTranscript(input.transcript);
10
12
  return [
11
- `Sujet: ${input.topic}`,
13
+ messages.subject(input.topic),
12
14
  "",
13
- `Tu es ${input.selfName}. Tu reponds au tour ${input.turn}.`,
14
- `Ton interlocuteur est ${input.peerName}.`,
15
- `Role de ${input.selfName}: ${input.selfRole}.`,
16
- roleInstruction(input.selfRole),
15
+ messages.debateIntro(input.selfName, input.turn),
16
+ messages.peer(input.peerName),
17
+ messages.role(input.selfName, input.selfRole),
18
+ messages.roleInstruction(input.selfRole),
17
19
  "",
18
- "Contexte de session PALABRE:",
19
- "- Source: fourni par PALABRE et visible par tous les agents de ce debat.",
20
- `- Date locale: ${input.session.localDate}`,
21
- `- Fuseau horaire: ${input.session.timeZone}`,
22
- `- Dossier courant: ${input.session.cwd}`,
23
- `- Session demarree a: ${input.session.startedAt}`,
20
+ messages.sessionTitle,
21
+ messages.sessionSource,
22
+ messages.localDate(input.session.localDate),
23
+ messages.timeZone(input.session.timeZone),
24
+ messages.cwd(input.session.cwd),
25
+ messages.sessionStartedAt(input.session.startedAt),
26
+ messages.turnProgress(input.turn, input.totalTurns),
24
27
  "",
25
- "Objectif:",
26
- "- Apporte une reponse utile, concrete et courte.",
27
- "- Reagis aux arguments precedents au lieu de repartir de zero.",
28
- "- Signale les incertitudes ou les points a trancher.",
29
- "- Respecte ton role sans ignorer les faits du transcript.",
28
+ messages.objectiveTitle,
29
+ ...messages.debateObjectives,
30
30
  "",
31
- input.files.length > 0 ? "Contexte fichiers:" : "",
31
+ input.files.length > 0 ? messages.fileContextTitle : "",
32
32
  formatFileContext(input.files),
33
33
  input.files.length > 0 ? "" : "",
34
- transcript.length > 0 ? "Historique:" : "Historique: aucun message pour le moment.",
34
+ transcript.length > 0 ? messages.historyTitle : messages.emptyHistory,
35
35
  transcript,
36
36
  "",
37
- "Ta reponse:"
37
+ messages.answerTitle
38
38
  ]
39
39
  .filter(Boolean)
40
40
  .join("\n");
41
41
  }
42
42
  /** Formate le prompt de synthèse finale. Impose un format structuré : Consensus / Désaccords / Actions / Conclusion. */
43
- function formatSummaryPrompt(input) {
43
+ function formatSummaryPrompt(input, messages) {
44
44
  const transcript = formatTranscript(input.transcript);
45
45
  return [
46
- `Sujet: ${input.topic}`,
46
+ messages.subject(input.topic),
47
47
  "",
48
- `Tu es ${input.selfName}. Tu produis la synthese finale du debat.`,
49
- `Role de ${input.selfName}: ${input.selfRole}.`,
50
- roleInstruction("summarizer"),
48
+ messages.summaryIntro(input.selfName),
49
+ messages.role(input.selfName, input.selfRole),
50
+ messages.roleInstruction("summarizer"),
51
51
  "",
52
- "Contexte de session PALABRE:",
53
- "- Source: fourni par PALABRE et visible par tous les agents de ce debat.",
54
- `- Date locale: ${input.session.localDate}`,
55
- `- Fuseau horaire: ${input.session.timeZone}`,
56
- `- Dossier courant: ${input.session.cwd}`,
57
- `- Session demarree a: ${input.session.startedAt}`,
52
+ messages.sessionTitle,
53
+ messages.sessionSource,
54
+ messages.localDate(input.session.localDate),
55
+ messages.timeZone(input.session.timeZone),
56
+ messages.cwd(input.session.cwd),
57
+ messages.sessionStartedAt(input.session.startedAt),
58
58
  "",
59
- "Objectif:",
60
- "- Resume le consensus en points concrets.",
61
- "- Liste les desaccords ou incertitudes qui restent.",
62
- "- Propose les prochaines actions techniques.",
63
- "- Termine par une conclusion courte en prose, bien ecrite, qui explique rapidement ce qu'il faut retenir.",
64
- "- Reste concis et exploitable.",
59
+ messages.objectiveTitle,
60
+ ...messages.summaryObjectives,
65
61
  "",
66
- input.files.length > 0 ? "Contexte fichiers:" : "",
62
+ input.files.length > 0 ? messages.fileContextTitle : "",
67
63
  formatFileContext(input.files),
68
64
  input.files.length > 0 ? "" : "",
69
- "Transcript du debat:",
70
- transcript || "Aucun message.",
65
+ messages.transcriptTitle,
66
+ transcript || messages.noMessage,
71
67
  "",
72
- "Format attendu:",
73
- "### Consensus",
68
+ messages.expectedFormatTitle,
69
+ messages.consensusHeading,
74
70
  "",
75
- "### Desaccords / incertitudes",
71
+ messages.disagreementsHeading,
76
72
  "",
77
- "### Actions proposees",
73
+ messages.actionsHeading,
78
74
  "",
79
- "### Conclusion",
75
+ messages.conclusionHeading,
80
76
  "",
81
- "Un court paragraphe de synthese en prose, sans liste, qui resume le sens general du debat et la decision ou direction la plus raisonnable.",
77
+ messages.finalProseInstruction,
82
78
  "",
83
- "Synthese:"
79
+ messages.summaryAnswerTitle
84
80
  ]
85
81
  .filter(Boolean)
86
82
  .join("\n");
87
83
  }
88
- function roleInstruction(role) {
89
- const instructions = {
90
- implementer: "Consigne de role: propose une solution concrete, executable et sobrement justifiee.",
91
- reviewer: "Consigne de role: cherche les risques, regressions, angles morts et tests manquants.",
92
- architect: "Consigne de role: structure les options techniques, compromis et frontieres du systeme.",
93
- scout: "Consigne de role: explore rapidement le terrain, releve les pistes utiles et les inconnues.",
94
- critic: "Consigne de role: challenge les hypotheses, pointe les faiblesses et demande les preuves utiles.",
95
- summarizer: "Consigne de role: synthetise fidelement le transcript sans ajouter de nouvelles hypotheses non signalees."
96
- };
97
- return instructions[role];
98
- }
99
84
  /** Formate les fichiers projet en blocs de code annotés pour l'injection dans le prompt. */
100
85
  function formatFileContext(files) {
101
86
  return files
@@ -1,8 +1,8 @@
1
1
  const supportsColor = Boolean(process.stdout.isTTY) && !process.env.NO_COLOR;
2
2
  const supportsInteractiveOutput = Boolean(process.stdout.isTTY);
3
3
  /** Instancie le renderer adapté : pretty (spinner, couleurs ANSI, sections) ou plain (logs bruts). */
4
- export function createConsoleRenderer(plain) {
5
- return plain ? new PlainConsoleRenderer() : new PrettyConsoleRenderer(supportsColor, supportsInteractiveOutput);
4
+ export function createConsoleRenderer(plain, messages) {
5
+ return plain ? new PlainConsoleRenderer(messages) : new PrettyConsoleRenderer(supportsColor, supportsInteractiveOutput, messages);
6
6
  }
7
7
  /**
8
8
  * Renderer interactif avec spinner, couleurs ANSI et encadrés de section.
@@ -11,6 +11,7 @@ export function createConsoleRenderer(plain) {
11
11
  class PrettyConsoleRenderer {
12
12
  color;
13
13
  interactive;
14
+ messages;
14
15
  spinner;
15
16
  spinnerFrame = 0;
16
17
  renderingSummary = false;
@@ -19,9 +20,10 @@ class PrettyConsoleRenderer {
19
20
  * @param color - Active les codes couleur ANSI.
20
21
  * @param interactive - Active le spinner en place (mode TTY interactif).
21
22
  */
22
- constructor(color, interactive) {
23
+ constructor(color, interactive, messages) {
23
24
  this.color = color;
24
25
  this.interactive = interactive;
26
+ this.messages = messages;
25
27
  }
26
28
  /** Affiche l'en-tête du débat (sujet, agents, options). */
27
29
  start(options, agents = []) {
@@ -29,29 +31,29 @@ class PrettyConsoleRenderer {
29
31
  process.stdout.write([
30
32
  "",
31
33
  this.c("cyan", `┌─ ${title} ${"─".repeat(Math.max(1, 54 - title.length))}`),
32
- this.c("cyan", `│`) + ` Sujet: ${options.topic}`,
33
- this.c("cyan", `│`) + ` Agents: ${formatAgentPair(options, agents)}`,
34
- this.c("cyan", `│`) + ` Réponses: ${options.turns} | Synthèse: ${formatSummary(options)}`,
35
- this.c("cyan", `│`) + ` Contexte: ${formatContext(options)}`,
36
- this.c("cyan", `│`) + ` Options: arrêt anticipé ${options.earlyStopOnAgreement ? "activé" : "désactivé"}, auto-pull Ollama ${options.pullModels ? "activé" : "désactivé"}`,
34
+ this.c("cyan", `│`) + ` ${this.messages.renderers.subject(options.topic)}`,
35
+ this.c("cyan", `│`) + ` ${this.messages.renderers.agents(formatAgentPair(options, agents))}`,
36
+ this.c("cyan", `│`) + ` ${this.messages.renderers.responsesSummary(options.turns, formatSummary(options, this.messages))}`,
37
+ this.c("cyan", `│`) + ` ${this.messages.renderers.context(formatContext(options, this.messages))}`,
38
+ this.c("cyan", `│`) + ` ${this.messages.renderers.options(options.earlyStopOnAgreement, options.pullModels)}`,
37
39
  this.c("cyan", `└${"─".repeat(57)}`),
38
40
  ""
39
41
  ].join("\n"));
40
42
  }
41
43
  /** Écrit un avertissement sur `stderr` en jaune. */
42
44
  warning(message) {
43
- process.stderr.write(`${this.c("yellow", "Warning:")} ${message}\n`);
45
+ process.stderr.write(`${this.c("yellow", this.messages.renderers.warningPrefix)} ${message}\n`);
44
46
  }
45
47
  /** Écrit une notice informative sur `stdout` en vert. */
46
48
  notice(message) {
47
- process.stdout.write(`${this.c("green", "Info:")} ${message}\n`);
49
+ process.stdout.write(`${this.c("green", this.messages.renderers.infoPrefix)} ${message}\n`);
48
50
  }
49
51
  /** Affiche l'en-tête d'un nouveau tour (agent, rôle, progression). */
50
52
  turnStart(turn, totalTurns, agent, role) {
51
53
  this.renderingSummary = false;
52
54
  process.stdout.write([
53
55
  "",
54
- this.c("orange", `◆ ${agent}`) + this.dim(` · ${role} · tour ${turn}/${totalTurns}`),
56
+ this.c("orange", `◆ ${agent}`) + this.dim(` · ${role} · ${this.messages.renderers.turn(turn, totalTurns)}`),
55
57
  this.dim("─".repeat(60)),
56
58
  ""
57
59
  ].join("\n"));
@@ -59,7 +61,7 @@ class PrettyConsoleRenderer {
59
61
  /** Démarre le spinner de réflexion (ou affiche une ligne fixe si non interactif). */
60
62
  thinkingStart(agent, role) {
61
63
  this.thinkingEnd();
62
- const text = `${agent} (${role}) reflechit`;
64
+ const text = this.messages.renderers.thinking(agent, role);
63
65
  if (!this.interactive) {
64
66
  process.stdout.write(`${this.dim(`${text}...`)}\n`);
65
67
  return;
@@ -92,14 +94,14 @@ class PrettyConsoleRenderer {
92
94
  this.renderingSummary = true;
93
95
  process.stdout.write([
94
96
  "",
95
- this.c("pink", `◆ Synthese`) + this.dim(` · ${agent} · ${role}`),
97
+ this.c("pink", `◆ ${this.messages.renderers.summaryTitle}`) + this.dim(` · ${agent} · ${role}`),
96
98
  this.dim("─".repeat(60)),
97
99
  ""
98
100
  ].join("\n"));
99
101
  }
100
102
  /** Affiche le chemin du fichier de sortie en vert à la fin du débat. */
101
103
  done(outputPath) {
102
- process.stdout.write(`\n\n${this.c("green", "Debat exporte:")} ${outputPath}\n\n`);
104
+ process.stdout.write(`\n\n${this.c("green", this.messages.renderers.exported(outputPath))}\n\n`);
103
105
  }
104
106
  /**
105
107
  * Convertit les titres Markdown `### Heading` en titres colorés avec séparateur.
@@ -139,19 +141,23 @@ class PrettyConsoleRenderer {
139
141
  * Utilisé avec `--plain` ou quand `stdout` n'est pas un TTY.
140
142
  */
141
143
  class PlainConsoleRenderer {
144
+ messages;
145
+ constructor(messages) {
146
+ this.messages = messages;
147
+ }
142
148
  /** Affiche les informations de démarrage du débat en texte brut. */
143
149
  start(options, agents = []) {
144
- process.stdout.write(`Sujet: ${options.topic}` + "\n");
145
- process.stdout.write(`Agents: ${formatAgentPair(options, agents)}` + "\n");
146
- process.stdout.write(`Réponses: ${options.turns} | Synthèse: ${formatSummary(options)} | Contexte: ${formatContext(options)}` + "\n");
150
+ process.stdout.write(this.messages.renderers.subject(options.topic) + "\n");
151
+ process.stdout.write(this.messages.renderers.agents(formatAgentPair(options, agents)) + "\n");
152
+ process.stdout.write(this.messages.renderers.responsesSummaryContext(options.turns, formatSummary(options, this.messages), formatContext(options, this.messages)) + "\n");
147
153
  }
148
154
  /** Écrit un avertissement sur `stderr`. */
149
155
  warning(message) {
150
- process.stderr.write(`Warning: ${message}\n`);
156
+ process.stderr.write(`${this.messages.renderers.warningPrefix} ${message}\n`);
151
157
  }
152
158
  /** Écrit une notice informative sur `stdout`. */
153
159
  notice(message) {
154
- process.stdout.write(`Info: ${message}\n`);
160
+ process.stdout.write(`${this.messages.renderers.infoPrefix} ${message}\n`);
155
161
  }
156
162
  /** Affiche la progression du tour en texte brut. */
157
163
  turnStart(turn, totalTurns, agent, role) {
@@ -167,11 +173,11 @@ class PlainConsoleRenderer {
167
173
  }
168
174
  /** Affiche l'en-tête de la section synthèse en texte brut. */
169
175
  summaryStart(agent, role) {
170
- process.stdout.write(`\n[Synthese] ${agent} (${role})...\n`);
176
+ process.stdout.write(`\n[${this.messages.renderers.summaryTitle}] ${agent} (${role})...\n`);
171
177
  }
172
178
  /** Affiche le chemin du fichier de sortie à la fin du débat. */
173
179
  done(outputPath) {
174
- process.stdout.write(`\nDebat exporte: ${outputPath}\n`);
180
+ process.stdout.write(`\n${this.messages.renderers.exported(outputPath)}\n`);
175
181
  }
176
182
  }
177
183
  /**
@@ -200,19 +206,19 @@ function formatAgentLabel(agent) {
200
206
  * Renvoie le nom de l'agent de synthèse ou `"désactivée"` si la synthèse est désactivée.
201
207
  * @param options - Options du débat.
202
208
  */
203
- function formatSummary(options) {
204
- return options.summaryEnabled ? options.summaryAgent ?? options.agentB : "désactivée";
209
+ function formatSummary(options, messages) {
210
+ return options.summaryEnabled ? options.summaryAgent ?? options.agentB : messages.renderers.disabled;
205
211
  }
206
212
  /**
207
213
  * Renvoie un résumé du contexte injecté (nombre de fichiers ou mention d'absence).
208
214
  * @param options - Options du débat.
209
215
  */
210
- function formatContext(options) {
216
+ function formatContext(options, messages) {
211
217
  const count = options.files.length;
212
218
  if (count === 0) {
213
- return "aucun fichier injecté";
219
+ return messages.renderers.noInjectedFiles;
214
220
  }
215
- return `${count} fichier${count > 1 ? "s" : ""} injecté${count > 1 ? "s" : ""}`;
221
+ return messages.renderers.injectedFiles(count);
216
222
  }
217
223
  /** Codes d'échappement ANSI utilisés par `PrettyConsoleRenderer`. */
218
224
  const codes = {
package/dist/update.js CHANGED
@@ -12,32 +12,21 @@ export async function getUpdateInfo(version) {
12
12
  };
13
13
  }
14
14
  /** Génère les instructions de mise à jour adaptées au mode d'installation détecté dans `info`. */
15
- export function formatUpdateInstructions(info) {
16
- const lines = [
17
- `PALABRE ${info.version}`,
18
- "",
19
- "Mise a jour recommandee:"
20
- ];
21
- if (info.sourceCheckout) {
22
- lines.push("", "Installation depuis le repo source detectee.", "", ` cd "${info.projectRoot}"`, " git pull --ff-only", " pnpm install", " pnpm build", " pnpm link --global", "", "Pour executer ces etapes automatiquement:", "", " palabre update --apply");
23
- }
24
- else {
25
- lines.push("", "Installation package detectee.", "", " pnpm add --global palabre@latest", "", "Si tu utilises npm:", "", " npm install --global palabre@latest");
26
- }
27
- return lines.join("\n");
15
+ export function formatUpdateInstructions(info, messages) {
16
+ return messages.update.instructions(info);
28
17
  }
29
18
  /**
30
19
  * Exécute `git pull`, `pnpm install`, `pnpm build`, `pnpm link --global` dans le répertoire du projet.
31
20
  * @throws {Error} si `info.sourceCheckout` est faux — la mise à jour automatique ne s'applique qu'aux checkouts git.
32
21
  */
33
- export async function applySourceUpdate(info) {
22
+ export async function applySourceUpdate(info, messages) {
34
23
  if (!info.sourceCheckout) {
35
- throw new Error("Mise a jour automatique disponible seulement depuis un checkout git. Utilise pnpm add --global palabre@latest.");
24
+ throw new Error(messages.update.automaticSourceOnly);
36
25
  }
37
- await runStep("git", ["pull", "--ff-only"], info.projectRoot);
38
- await runStep("pnpm", ["install"], info.projectRoot);
39
- await runStep("pnpm", ["build"], info.projectRoot);
40
- await runStep("pnpm", ["link", "--global"], info.projectRoot);
26
+ await runStep("git", ["pull", "--ff-only"], info.projectRoot, messages);
27
+ await runStep("pnpm", ["install"], info.projectRoot, messages);
28
+ await runStep("pnpm", ["build"], info.projectRoot, messages);
29
+ await runStep("pnpm", ["link", "--global"], info.projectRoot, messages);
41
30
  }
42
31
  async function exists(targetPath) {
43
32
  try {
@@ -48,7 +37,7 @@ async function exists(targetPath) {
48
37
  return false;
49
38
  }
50
39
  }
51
- function runStep(command, args, cwd) {
40
+ function runStep(command, args, cwd, messages) {
52
41
  return new Promise((resolve, reject) => {
53
42
  const child = spawn(command, args, {
54
43
  cwd,
@@ -62,7 +51,7 @@ function runStep(command, args, cwd) {
62
51
  resolve();
63
52
  return;
64
53
  }
65
- reject(new Error(`${command} ${args.join(" ")} a echoue avec le code ${exitCode ?? "inconnu"}.`));
54
+ reject(new Error(messages.update.stepFailed(command, args.join(" "), String(exitCode ?? "inconnu"))));
66
55
  });
67
56
  });
68
57
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "palabre",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Orchestrateur de debat entre agents IA locaux, CLIs et Ollama.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1,4 +1,5 @@
1
1
  {
2
+ "language": "fr",
2
3
  "outputDir": ".",
3
4
  "defaults": {
4
5
  "agentA": "codex",