palabre 0.8.0 → 0.9.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.
@@ -1,5 +1,9 @@
1
1
  import { createInterface } from "node:readline/promises";
2
2
  import { stdin as input, stdout as output } from "node:process";
3
+ import path from "node:path";
4
+ import { pathToFileURL } from "node:url";
5
+ import { isRetiredAgentName } from "../agentRegistry.js";
6
+ import { DEFAULT_OLLAMA_BASE_URL, resolveOllamaBaseUrl } from "../ollamaUrl.js";
3
7
  const supportsColor = Boolean(process.stdout.isTTY) && !process.env.NO_COLOR;
4
8
  const supportsInteractiveOutput = Boolean(process.stdout.isTTY);
5
9
  /** Cree le premier renderer TUI leger, sans dependance UI externe. */
@@ -33,10 +37,14 @@ export function renderTuiHome(config, _configPath, messages, state = {}) {
33
37
  const summary = mode === "ask"
34
38
  ? defaults.askSummaryAgent ?? defaults.summaryAgent ?? messages.tui.lastAskAgent
35
39
  : defaults.summaryAgent ?? defaults.agentB ?? "agent B";
40
+ const version = state.version ?? "0.0.0";
41
+ const versionLines = state.latestVersion
42
+ ? [dim(`v${version}`), accent(messages.tui.updateAvailable(version, state.latestVersion))]
43
+ : [dim(`v${version}`)];
36
44
  const lines = [
37
45
  "",
38
46
  ...centerLogo(viewport, messages),
39
- ...centerBlock([dim(`v${state.version ?? "0.0.0"}`)], viewport),
47
+ ...centerBlock(versionLines, viewport),
40
48
  "",
41
49
  ...centerBlock(composerCard([
42
50
  `${accent(messages.tui.modeValue(mode))} ${dim("·")} ${mode === "ask" ? askAgents : debateAgents}`,
@@ -54,36 +62,68 @@ export function renderTuiHome(config, _configPath, messages, state = {}) {
54
62
  ];
55
63
  process.stdout.write(lines.join("\n") + "\n");
56
64
  }
65
+ /** Affiche les instructions de mise a jour sans quitter le TUI. */
66
+ export function renderTuiUpdate(instructions, messages) {
67
+ if (supportsInteractiveOutput) {
68
+ clearScreen();
69
+ }
70
+ const viewport = viewportWidth();
71
+ const width = surfaceWidth();
72
+ process.stdout.write([
73
+ "",
74
+ ...centerLogo(viewport, messages),
75
+ "",
76
+ ...centerBlock(card(instructions.split(/\r?\n/), width), viewport),
77
+ ""
78
+ ].join("\n"));
79
+ }
57
80
  /** Affiche l'aide interne du composer TUI. */
58
81
  export function renderTuiHelp(messages) {
82
+ if (supportsInteractiveOutput) {
83
+ clearScreen();
84
+ }
59
85
  const viewport = viewportWidth();
60
86
  const width = surfaceWidth();
61
87
  process.stdout.write([
88
+ "",
89
+ ...centerLogo(viewport, messages),
62
90
  "",
63
91
  ...centerBlock(card([
64
92
  bold(messages.tui.helpTitle),
65
- `${accent("/ask")} ${messages.tui.helpAsk}`,
66
- `${accent("/debat")} ${messages.tui.helpDebate}`,
67
- `${accent("/agents")} ${messages.tui.helpAgents}`,
68
- `${accent("/roles")} ${messages.tui.helpRoles}`,
69
- `${accent("/config")} ${messages.tui.helpConfig}`,
70
- `${accent("/new")} ${messages.tui.helpNew}`,
71
- `${accent("/help")} ${messages.tui.helpHelp}`,
72
- `${accent("/quit")} ${messages.tui.helpQuit}`,
93
+ "",
94
+ row("/ask", messages.tui.helpAsk),
95
+ row("/debat", messages.tui.helpDebate),
96
+ "",
97
+ row("/agents", messages.tui.helpAgents),
98
+ row("/roles", messages.tui.helpRoles),
99
+ row("/config", messages.tui.helpConfig),
100
+ "",
101
+ row("/new", messages.tui.helpNew),
102
+ row("/retry", messages.tui.helpRetry),
103
+ row("/history", messages.tui.helpHistory),
104
+ row("/update", messages.tui.helpUpdate),
105
+ row("/home", messages.tui.backCommand),
106
+ row("/help", messages.tui.helpHelp),
107
+ row("/quit", messages.tui.helpQuit),
73
108
  "",
74
109
  dim(messages.tui.helpFallback)
75
- ], Math.min(width, 78)), viewport),
110
+ ], width), viewport),
76
111
  ""
77
112
  ].join("\n"));
78
113
  }
79
114
  /** Affiche l'aide rapide des agents configures. */
80
115
  export function renderTuiAgentsHelp(config, mode, messages) {
116
+ if (supportsInteractiveOutput) {
117
+ clearScreen();
118
+ }
81
119
  const viewport = viewportWidth();
82
120
  const width = surfaceWidth();
83
121
  const activeAgents = activeAgentNamesForMode(config, mode);
84
122
  const separator = mode === "ask" ? ", " : " <-> ";
85
123
  const exampleAgents = exampleAgentsForMode(config, mode);
86
124
  process.stdout.write([
125
+ "",
126
+ ...centerLogo(viewport, messages),
87
127
  "",
88
128
  ...centerBlock(card([
89
129
  bold(messages.tui.agentsTitle),
@@ -96,12 +136,15 @@ export function renderTuiAgentsHelp(config, mode, messages) {
96
136
  ...agentInventoryRows(config, messages),
97
137
  "",
98
138
  dim(`${messages.tui.example}: ${messages.tui.modeLabel(mode)} > ${messages.tui.agentsPrompt} > ${exampleAgents.join(" ")}`)
99
- ], Math.min(width, 82)), viewport),
139
+ ], width), viewport),
100
140
  ""
101
141
  ].join("\n"));
102
142
  }
103
143
  /** Affiche l'aide rapide des roles disponibles. */
104
144
  export function renderTuiRolesHelp(mode, messages, config) {
145
+ if (supportsInteractiveOutput) {
146
+ clearScreen();
147
+ }
105
148
  const viewport = viewportWidth();
106
149
  const width = surfaceWidth();
107
150
  const currentRoles = config ? roleLineForMode(config, mode, messages) : undefined;
@@ -109,6 +152,8 @@ export function renderTuiRolesHelp(mode, messages, config) {
109
152
  const expectedCount = activeAgents.length || (mode === "ask" ? 3 : 2);
110
153
  const exampleRoles = exampleRolesForMode(mode, expectedCount);
111
154
  process.stdout.write([
155
+ "",
156
+ ...centerLogo(viewport, messages),
112
157
  "",
113
158
  ...centerBlock(card([
114
159
  bold(messages.tui.rolesTitle),
@@ -129,6 +174,42 @@ export function renderTuiRolesHelp(mode, messages, config) {
129
174
  ""
130
175
  ].join("\n"));
131
176
  }
177
+ /** Affiche les derniers exports Palabre disponibles. */
178
+ export function renderTuiHistory(entries, messages) {
179
+ if (supportsInteractiveOutput) {
180
+ clearScreen();
181
+ }
182
+ const viewport = viewportWidth();
183
+ const width = surfaceWidth();
184
+ const rows = entries.length === 0
185
+ ? [dim(messages.tui.historyEmpty)]
186
+ : entries.flatMap((entry) => {
187
+ const folderPath = path.dirname(entry.path);
188
+ const folderLabel = folderPath === "." ? dirnamePortable(entry.path) : folderPath;
189
+ return [
190
+ row(messages.tui.historyMode(entry.mode), entry.topic),
191
+ row(messages.tui.activeAgents, entry.agents || messages.tui.noValue),
192
+ ...(entry.count ? [row(messages.tui.historyCount(entry.mode), entry.count)] : []),
193
+ row(messages.tui.historyFile, terminalLink(entry.path, compactFileName(entry.fileName, width - 24))),
194
+ row(messages.tui.folder, terminalLink(folderPath, compactPath(folderLabel, width - 24))),
195
+ ...(entry.date ? [row("Date", entry.date)] : []),
196
+ ""
197
+ ];
198
+ }).slice(0, -1);
199
+ process.stdout.write([
200
+ "",
201
+ ...centerLogo(viewport, messages),
202
+ "",
203
+ ...centerBlock(panel([
204
+ bold(messages.tui.historyTitle),
205
+ "",
206
+ ...rows,
207
+ "",
208
+ dim(messages.tui.historyOpenHint)
209
+ ], width), viewport),
210
+ ""
211
+ ].join("\n"));
212
+ }
132
213
  /** Assistant minimal pour modifier les agents du mode courant. */
133
214
  export async function promptTuiAgentsWizard(config, mode, messages) {
134
215
  if (!input.isTTY) {
@@ -145,7 +226,7 @@ export async function promptTuiAgentsWizard(config, mode, messages) {
145
226
  return { kind: "back" };
146
227
  }
147
228
  const value = result.value.trim();
148
- if (!value || value === "/back") {
229
+ if (!value || value === "/home" || value === "/back") {
149
230
  return { kind: "back" };
150
231
  }
151
232
  if (value === "/quit" || value === "/q") {
@@ -174,7 +255,7 @@ export async function promptTuiRolesWizard(config, mode, messages) {
174
255
  }
175
256
  const answer = result.value;
176
257
  const value = answer.trim();
177
- if (!value || value === "/back") {
258
+ if (!value || value === "/home" || value === "/back") {
178
259
  return { kind: "back" };
179
260
  }
180
261
  if (value === "/quit" || value === "/q") {
@@ -216,30 +297,34 @@ export function renderTuiConfig(config, configPath, mode, messages, state = {})
216
297
  const summary = mode === "ask"
217
298
  ? defaults.askSummaryAgent ?? defaults.summaryAgent ?? messages.tui.lastAskAgent
218
299
  : defaults.summaryAgent ?? defaults.agentB ?? messages.tui.noValue;
300
+ const ollamaAgent = config.agents["ollama-local"];
301
+ const ollamaModel = ollamaAgent?.type === "ollama" ? ollamaAgent.model : undefined;
302
+ const ollamaUrl = ollamaAgent?.type === "ollama" ? ollamaAgent.baseUrl ?? DEFAULT_OLLAMA_BASE_URL : undefined;
303
+ const ollamaEffectiveUrl = ollamaUrl ? safeEffectiveOllamaUrl(ollamaUrl) : undefined;
219
304
  const currentLines = mode === "ask"
220
305
  ? [
221
306
  row(messages.tui.activeAgents, askAgents),
222
- row(messages.tui.currentConfig, askRoles),
307
+ row(messages.tui.roles, askRoles),
223
308
  row(messages.tui.summary, summary),
224
309
  "",
225
310
  bold(messages.tui.availableCommands),
226
311
  "",
227
- row("/agents", "/agents codex claude opencode"),
228
- row("/roles", "/roles architect critic scout"),
229
- row("/summary", `/summary opencode ${dim(messages.tui.or)} /summary none`)
312
+ row("/agents", messages.tui.askAgentsUsage),
313
+ row("/roles", messages.tui.rolesUsage),
314
+ row("/summary", messages.tui.summaryUsage)
230
315
  ]
231
316
  : [
232
317
  row(messages.tui.activeAgents, debateAgents),
233
- row(messages.tui.currentConfig, debateRoles),
318
+ row(messages.tui.roles, debateRoles),
234
319
  row(messages.tui.summary, summary),
235
320
  row(messages.tui.responses, String(defaults.turns ?? "?")),
236
321
  "",
237
322
  bold(messages.tui.availableCommands),
238
323
  "",
239
- row("/agents", "/agents codex gemini"),
240
- row("/roles", "/roles implementer critic"),
241
- row("/turns", "/turns 4"),
242
- row("/summary", `/summary ollama-local ${dim(messages.tui.or)} /summary none`)
324
+ row("/agents", messages.tui.debateAgentsUsage),
325
+ row("/roles", messages.tui.rolesUsage),
326
+ row("/turns", messages.tui.turnsUsage),
327
+ row("/summary", messages.tui.summaryUsage)
243
328
  ];
244
329
  const lines = [
245
330
  "",
@@ -249,24 +334,47 @@ export function renderTuiConfig(config, configPath, mode, messages, state = {})
249
334
  bold(messages.tui.configTitle),
250
335
  "",
251
336
  row(messages.tui.activeMode, messages.tui.modeValue(mode)),
337
+ ...(ollamaUrl ? [row(messages.tui.ollamaUrl, ollamaUrl)] : []),
338
+ ...(ollamaEffectiveUrl && ollamaEffectiveUrl !== ollamaUrl ? [row(messages.tui.ollamaUrlEffective, ollamaEffectiveUrl)] : []),
252
339
  row(messages.tui.configFile, configPath),
253
340
  row(messages.tui.interface, defaults.interface ?? "tui"),
254
341
  row(messages.tui.language, config.language ?? "fr"),
255
342
  row(messages.tui.availableAgentsShort, agentInventoryLine(config, messages)),
343
+ ...(ollamaModel ? [row(messages.tui.ollamaModel, ollamaModel)] : []),
256
344
  "",
257
345
  ...currentLines,
258
346
  "",
259
- row("/default", messages.tui.defaultModeCommand(mode)),
260
- row("/interface", `/interface tui ${dim(messages.tui.or)} /interface terminal`),
261
- row("/language", `/language fr ${dim(messages.tui.or)} /language en`),
262
347
  row("/mode", messages.tui.modeConfigCommand),
263
- row("/back", messages.tui.backCommand),
348
+ "",
349
+ ...(ollamaModel ? [
350
+ row("/ollama", messages.tui.ollamaInfoCommand),
351
+ row("/ollama-model", messages.tui.ollamaModelUsage),
352
+ row("/ollama-url", messages.tui.ollamaUrlCommand),
353
+ row("/ollama-sync", messages.tui.ollamaSyncCommand),
354
+ ""
355
+ ] : []),
356
+ row("/interface", messages.tui.interfaceUsage),
357
+ row("/language", messages.tui.languageUsage),
358
+ "",
359
+ row("/home", messages.tui.backCommand),
264
360
  row("/quit", messages.tui.quitCommand)
265
361
  ], width), viewport),
266
362
  ...(state.message ? ["", ...centerBlock([state.message], viewport)] : [])
267
363
  ];
268
364
  process.stdout.write(lines.join("\n") + "\n");
269
365
  }
366
+ export function parseTuiOllamaUrlCommand(parts, messages) {
367
+ const value = parts[1];
368
+ return value ? { kind: "ollama-url", url: value } : { kind: "unknown", message: messages.tui.ollamaUrlUsage };
369
+ }
370
+ function safeEffectiveOllamaUrl(configUrl) {
371
+ try {
372
+ return resolveOllamaBaseUrl({ configUrl });
373
+ }
374
+ catch {
375
+ return process.env.OLLAMA_HOST?.trim() || configUrl;
376
+ }
377
+ }
270
378
  let lastTuiInterruptAt = 0;
271
379
  const doubleInterruptMs = 1200;
272
380
  function nextInterruptKind() {
@@ -322,6 +430,18 @@ export async function promptTuiHomeTopic(mode = "debate", messages, options = {}
322
430
  if (command === "/new") {
323
431
  return { kind: "new" };
324
432
  }
433
+ if (command === "/retry") {
434
+ return { kind: "retry" };
435
+ }
436
+ if (command === "/update") {
437
+ return { kind: "update" };
438
+ }
439
+ if (command === "/historique" || command === "/history") {
440
+ return { kind: "history" };
441
+ }
442
+ if (command === "/home" || command === "/back" || command === "/b") {
443
+ return { kind: "home" };
444
+ }
325
445
  if (command === "/config") {
326
446
  return { kind: "config" };
327
447
  }
@@ -356,7 +476,6 @@ export async function promptTuiConfigCommand(mode, messages) {
356
476
  }
357
477
  const rl = createInterface({ input, output });
358
478
  try {
359
- renderTuiComposer(mode, messages, messages.tui.configPrompt);
360
479
  const result = await questionWithInterrupt(rl, tuiPrompt(mode, messages.tui.configPrompt, messages));
361
480
  if (result.kind === "quit") {
362
481
  return { kind: "quit" };
@@ -367,7 +486,7 @@ export async function promptTuiConfigCommand(mode, messages) {
367
486
  const answer = result.value;
368
487
  const parts = answer.trim().split(/\s+/).filter(Boolean);
369
488
  const command = parts[0]?.toLowerCase();
370
- if (!command || command === "/back" || command === "/b") {
489
+ if (!command || command === "/home" || command === "/back" || command === "/b") {
371
490
  return { kind: "back" };
372
491
  }
373
492
  if (command === "/quit" || command === "/q" || command === "/exit") {
@@ -414,6 +533,25 @@ export async function promptTuiConfigCommand(mode, messages) {
414
533
  }
415
534
  return { kind: "summary", agent: isNoneValue(value) ? undefined : value };
416
535
  }
536
+ if (command === "/ollama") {
537
+ const value = parts[1];
538
+ return value ? { kind: "ollama-model", model: value } : { kind: "ollama-info" };
539
+ }
540
+ if (command === "/ollama-url" || command === "/ollama-host") {
541
+ return parseTuiOllamaUrlCommand(parts, messages);
542
+ }
543
+ if (command === "/ollama-model") {
544
+ const value = parts[1];
545
+ return value ? { kind: "ollama-model", model: value } : { kind: "unknown", message: messages.tui.ollamaModelUsage };
546
+ }
547
+ if (command === "/model") {
548
+ const [first, second] = parts.slice(1);
549
+ const value = first === "ollama-local" ? second : first;
550
+ return value ? { kind: "ollama-model", model: value } : { kind: "unknown", message: messages.tui.ollamaModelUsage };
551
+ }
552
+ if (command === "/ollama-sync") {
553
+ return { kind: "ollama-sync" };
554
+ }
417
555
  return { kind: "unknown", message: messages.tui.unknownCommand };
418
556
  }
419
557
  finally {
@@ -448,7 +586,11 @@ class TuiRenderer {
448
586
  process.stdout.write(this.renderSessionHeader(options, agents).join("\n") + "\n");
449
587
  }
450
588
  notice(message) {
451
- process.stdout.write(`${this.c("green", this.messages.renderers.infoPrefix)} ${message}\n`);
589
+ const viewport = viewportWidth();
590
+ const width = this.width();
591
+ process.stdout.write(`\n${centerBlock(card([
592
+ `${this.c("green", this.messages.renderers.infoPrefix)} ${message}`
593
+ ], width), viewport).join("\n")}\n`);
452
594
  }
453
595
  warning(message) {
454
596
  process.stderr.write(`${this.c("yellow", this.messages.renderers.warningPrefix)} ${message}\n`);
@@ -502,48 +644,46 @@ class TuiRenderer {
502
644
  }
503
645
  error(failure) {
504
646
  this.thinkingEnd();
505
- process.stderr.write(`\n${this.c("red", this.messages.common.errorPrefix)} ${formatFailureLocation(failure, this.messages)}: ${failure.message}\n`);
647
+ const viewport = viewportWidth();
648
+ const width = this.width();
649
+ process.stderr.write(`\n${centerBlock(card([
650
+ this.c("red", this.messages.common.errorPrefix),
651
+ `${formatFailureLocation(failure, this.messages)}: ${failure.message}`
652
+ ], width), viewport).join("\n")}\n`);
506
653
  }
507
654
  done(outputPath) {
508
655
  this.thinkingEnd();
509
656
  const viewport = viewportWidth();
510
657
  const width = this.width();
511
- process.stdout.write(`\n${centerBlock(card([
658
+ const folderPath = path.dirname(outputPath);
659
+ const fileName = path.basename(outputPath);
660
+ process.stdout.write(`\n${centerBlock(panel([
512
661
  bold(this.messages.tui.sessionDone),
513
- this.messages.renderers.exported(outputPath)
662
+ "",
663
+ row(this.messages.tui.historyFile, terminalLink(outputPath, compactFileName(fileName, width - 24))),
664
+ row(this.messages.tui.folder, terminalLink(folderPath, compactPath(folderPath, width - 24))),
665
+ "",
666
+ dim(this.messages.tui.sessionHistoryHint)
514
667
  ], width), viewport).join("\n")}\n\n`);
515
668
  }
516
669
  renderSessionHeader(options, agents) {
517
670
  const viewport = viewportWidth();
518
671
  const width = this.width();
519
672
  const mode = messagesModeLabel(this.messages, options.mode).toUpperCase();
520
- const main = [
521
- ...card([
522
- `${bold("PALABRE")} ${dim("-")} ${accent(mode)}`,
523
- this.messages.renderers.subject(options.topic),
524
- this.messages.renderers.agents(formatAgents(options, agents)),
525
- this.messages.renderers.responsesSummary(formatResponseCount(options), formatSummary(options, this.messages)),
526
- this.messages.renderers.context(formatContext(options, this.messages)),
527
- ...formatPtyNotice(agents, this.messages)
528
- ], width)
529
- ];
673
+ const main = panel([
674
+ accent(mode),
675
+ this.messages.renderers.subject(options.topic),
676
+ this.messages.renderers.agents(formatAgents(options, agents)),
677
+ formatSessionProgress(options, this.messages),
678
+ this.messages.renderers.context(formatContext(options, this.messages))
679
+ ], width);
530
680
  return [
531
- ...centerBlock(main, viewport),
681
+ ...centerLogo(viewport, this.messages),
532
682
  "",
533
- ...centerBlock(this.planPanel(options, width), viewport),
683
+ ...centerBlock(main, viewport),
534
684
  ""
535
685
  ];
536
686
  }
537
- planPanel(options, width) {
538
- return panel([
539
- bold(this.messages.tui.planTitle),
540
- dim(options.session.localDate),
541
- "",
542
- `${accent("1")} ${options.mode === "ask" ? this.messages.tui.planCollectAsk : this.messages.tui.planLaunchDebate}`,
543
- `${accent("2")} ${options.summaryEnabled ? this.messages.tui.planSummaryComparative : this.messages.tui.planSummaryDisabled}`,
544
- `${accent("3")} ${this.messages.tui.planExport}`
545
- ], width);
546
- }
547
687
  promptBlock(title, agent) {
548
688
  const viewport = viewportWidth();
549
689
  const width = this.width();
@@ -601,13 +741,7 @@ function formatAgents(options, agents) {
601
741
  return `${options.agentA} <-> ${options.agentB}`;
602
742
  }
603
743
  function formatAgent(agent) {
604
- return agent ? `${agentLabel(agent.name)} (${agent.role}, ${agent.type})` : "?";
605
- }
606
- function formatPtyNotice(agents, messages) {
607
- const ptyAgents = agents.filter((agent) => agent.type === "cli-pty").map((agent) => agent.name);
608
- return ptyAgents.length > 0
609
- ? ["", orange(messages.tui.ptyNotice(ptyAgents.join(", ")))]
610
- : [];
744
+ return agent ? `${agentLabel(agent.name)} (${agent.role})` : "?";
611
745
  }
612
746
  function formatSummary(options, messages) {
613
747
  if (!options.summaryEnabled) {
@@ -624,6 +758,9 @@ function formatSummary(options, messages) {
624
758
  function formatResponseCount(options) {
625
759
  return options.mode === "ask" ? options.askAgents?.length ?? 2 : options.turns;
626
760
  }
761
+ function formatSessionProgress(options, messages) {
762
+ return `${messages.tui.historyCount(options.mode)}: ${formatResponseCount(options)} | ${messages.tui.summary}: ${formatSummary(options, messages)}`;
763
+ }
627
764
  function formatContext(options, messages) {
628
765
  return options.files.length === 0
629
766
  ? messages.renderers.noInjectedFiles
@@ -637,7 +774,9 @@ function formatFailureLocation(failure, messages) {
637
774
  return `${failure.agent ?? "?"} (${failure.role ?? "?"}${turn})`;
638
775
  }
639
776
  function stripAnsi(value) {
640
- return value.replace(/\u001b\[[0-9;?]*[A-Za-z]/g, "");
777
+ return value
778
+ .replace(/\u001b\][^\u001b]*(?:\u0007|\u001b\\)/g, "")
779
+ .replace(/\u001b\[[0-9;?]*[A-Za-z]/g, "");
641
780
  }
642
781
  function clearScreen() {
643
782
  process.stdout.write("\u001bc\u001b[?25h");
@@ -649,18 +788,18 @@ function viewportWidth() {
649
788
  return Math.max(72, Math.min(process.stdout.columns || 100, 180));
650
789
  }
651
790
  function tuiPrompt(mode, labelPrefix, messages, notice) {
652
- const label = promptModeLabel(mode, messages);
791
+ const label = promptTrail(mode, labelPrefix, messages);
653
792
  const padding = surfacePadding();
654
- const promptLine = `${padding}${accent(label)} ${dim(">")} ${bold(labelPrefix)} ${dim(">")} `;
793
+ const promptLine = `${padding}${label} ${dim(">")} `;
655
794
  return [
656
795
  "",
657
796
  promptRuleLine(),
658
797
  ...(notice ? [
659
- `${padding}${accent(label)} ${dim(">")} ${bold(labelPrefix)} ${dim(">")}`,
798
+ `${padding}${label} ${dim(">")}`,
660
799
  ...promptNoticeLines(notice),
661
800
  ""
662
801
  ] : []),
663
- promptLine
802
+ promptLine,
664
803
  ].join("\n");
665
804
  }
666
805
  function promptRuleLine() {
@@ -680,6 +819,13 @@ function surfacePadding() {
680
819
  function promptModeLabel(mode, messages) {
681
820
  return `Mode ${messages.tui.modeValue(mode).toLowerCase()}`;
682
821
  }
822
+ function promptTrail(mode, labelPrefix, messages) {
823
+ const parts = [bold("Palabre"), accent(promptModeLabel(mode, messages))];
824
+ if (labelPrefix !== messages.tui.subject) {
825
+ parts.push(bold(labelPrefix));
826
+ }
827
+ return parts.join(` ${dim(">")} `);
828
+ }
683
829
  function messagesModeLabel(messages, mode) {
684
830
  return messages.tui.modeValue(mode);
685
831
  }
@@ -691,6 +837,25 @@ function compactPath(value, maxLength) {
691
837
  const tailLength = Math.max(12, maxLength - marker.length);
692
838
  return `${marker}${value.slice(-tailLength)}`;
693
839
  }
840
+ function dirnamePortable(value) {
841
+ const separatorIndex = Math.max(value.lastIndexOf("/"), value.lastIndexOf("\\"));
842
+ return separatorIndex > 0 ? value.slice(0, separatorIndex) : path.dirname(value);
843
+ }
844
+ function compactFileName(value, maxLength) {
845
+ if (value.length <= maxLength) {
846
+ return value;
847
+ }
848
+ const extension = value.match(/\.(debate|ask)\.md$/i)?.[0] ?? "";
849
+ const marker = "...";
850
+ const headLength = Math.max(12, maxLength - marker.length - extension.length);
851
+ return `${value.slice(0, headLength)}${marker}${extension}`;
852
+ }
853
+ function terminalLink(filePath, label) {
854
+ if (!supportsInteractiveOutput) {
855
+ return label;
856
+ }
857
+ return `\u001b]8;;${pathToFileURL(filePath).href}\u001b\\${label}\u001b]8;;\u001b\\`;
858
+ }
694
859
  function roleFor(config, agent, messages) {
695
860
  return config.agents[agent]?.role ?? messages.tui.noValue;
696
861
  }
@@ -709,16 +874,21 @@ function activeAgentNamesForMode(config, mode) {
709
874
  const agents = defaults.askAgents && defaults.askAgents.length > 0
710
875
  ? defaults.askAgents
711
876
  : [defaults.agentA, defaults.agentB].filter((agent) => Boolean(agent));
712
- return agents.filter((agent) => Boolean(config.agents[agent]));
877
+ return agents.filter((agent) => Boolean(config.agents[agent]) && !isRetiredAgentName(agent));
713
878
  }
714
- return [defaults.agentA, defaults.agentB].filter((agent) => Boolean(agent && config.agents[agent]));
879
+ return [defaults.agentA, defaults.agentB].filter((agent) => typeof agent === "string" && Boolean(config.agents[agent]) && !isRetiredAgentName(agent));
715
880
  }
716
881
  function agentInventoryLine(config, messages) {
717
- const agents = Object.keys(config.agents).sort();
882
+ const agents = Object.entries(config.agents)
883
+ .filter(([name]) => !isRetiredAgentName(name))
884
+ .map(([name]) => name)
885
+ .sort();
718
886
  return agents.length > 0 ? agents.map(agentLabel).join(", ") : messages.tui.noValue;
719
887
  }
720
888
  function agentInventoryRows(config, messages) {
721
- const entries = Object.entries(config.agents).sort(([agentA], [agentB]) => agentA.localeCompare(agentB));
889
+ const entries = Object.entries(config.agents)
890
+ .filter(([name]) => !isRetiredAgentName(name))
891
+ .sort(([agentA], [agentB]) => agentA.localeCompare(agentB));
722
892
  if (entries.length === 0) {
723
893
  return [dim(messages.tui.noConfiguredAgents)];
724
894
  }
@@ -729,7 +899,7 @@ function exampleAgentsForMode(config, mode) {
729
899
  if (activeAgents.length > 0) {
730
900
  return activeAgents;
731
901
  }
732
- const available = Object.keys(config.agents).sort();
902
+ const available = Object.keys(config.agents).filter((agent) => !isRetiredAgentName(agent)).sort();
733
903
  return mode === "ask" ? available.slice(0, 3) : available.slice(0, 2);
734
904
  }
735
905
  function documentationUrl(config) {
@@ -786,9 +956,8 @@ function centerLine(line, width) {
786
956
  return `${" ".repeat(left)}${line}`;
787
957
  }
788
958
  function composerInputBox(mode, labelPrefix, width, messages) {
789
- const label = promptModeLabel(mode, messages);
790
959
  return composerCard([
791
- `${accent(label)} ${dim(">")} ${bold(labelPrefix)} ${dim(">")}`
960
+ `${promptTrail(mode, labelPrefix, messages)} ${dim(">")}`
792
961
  ], width, "center");
793
962
  }
794
963
  function panel(lines, width) {
@@ -863,9 +1032,6 @@ function accent(value) {
863
1032
  function violet(value) {
864
1033
  return supportsColor ? `${codes.violet}${value}${codes.reset}` : value;
865
1034
  }
866
- function orange(value) {
867
- return supportsColor ? `${codes.orange}${value}${codes.reset}` : value;
868
- }
869
1035
  function muted(value) {
870
1036
  return supportsColor ? `${codes.gray}${value}${codes.reset}` : value;
871
1037
  }
@@ -909,7 +1075,6 @@ function hslToRgb(hue, saturation, lightness) {
909
1075
  const agentColors = {
910
1076
  antigravity: [76, 141, 255],
911
1077
  claude: [222, 115, 86],
912
- gemini: [71, 150, 227],
913
1078
  codex: [136, 82, 197],
914
1079
  opencode: [80, 168, 103],
915
1080
  vibe: [234, 92, 126],
@@ -0,0 +1,32 @@
1
+ const TUI_RUN_OVERRIDE_FLAGS = [
2
+ "preset",
3
+ "agent-a",
4
+ "agent-b",
5
+ "agents",
6
+ "model-a",
7
+ "ollama-url",
8
+ "model-b",
9
+ "summary-agent",
10
+ "summary-model",
11
+ "turns",
12
+ "files",
13
+ "context",
14
+ "no-summary",
15
+ "no-early-stop",
16
+ "pull-models",
17
+ "show-prompt",
18
+ "plain",
19
+ "terminal",
20
+ "json"
21
+ ];
22
+ export function clearTuiRunOverrides(flags) {
23
+ for (const flag of TUI_RUN_OVERRIDE_FLAGS) {
24
+ delete flags[flag];
25
+ }
26
+ }
27
+ export function askAgentSeedsForMode(mode, explicitAskAgents, defaultAskAgents) {
28
+ if (mode !== "ask") {
29
+ return [];
30
+ }
31
+ return explicitAskAgents.length > 0 ? explicitAskAgents : defaultAskAgents ?? [];
32
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "palabre",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Orchestrateur de debat entre agents IA locaux, CLIs et Ollama.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -39,23 +39,6 @@
39
39
  "role": "reviewer",
40
40
  "tier": "primary"
41
41
  },
42
- "gemini": {
43
- "type": "cli",
44
- "command": "gemini",
45
- "args": [
46
- "--output-format",
47
- "text",
48
- "--approval-mode",
49
- "plan",
50
- "--skip-trust",
51
- "--prompt",
52
- "-"
53
- ],
54
- "promptMode": "stdin",
55
- "shell": true,
56
- "role": "reviewer",
57
- "tier": "primary"
58
- },
59
42
  "opencode": {
60
43
  "type": "cli",
61
44
  "command": "opencode",