palabre 0.9.1 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/adapters/cli-pty.js +30 -10
  2. package/dist/adapters/cli-shared.js +73 -0
  3. package/dist/adapters/cli.js +40 -77
  4. package/dist/adapters/index.js +1 -0
  5. package/dist/adapters/ollama.js +32 -32
  6. package/dist/adapters/terminal.js +1 -0
  7. package/dist/args.js +1 -0
  8. package/dist/commands/agents.js +7 -1
  9. package/dist/commands/context.js +7 -1
  10. package/dist/commands/history.js +6 -1
  11. package/dist/commands/init.js +8 -3
  12. package/dist/commands/presets.js +6 -1
  13. package/dist/commands/shared.js +5 -1
  14. package/dist/commands/update.js +6 -1
  15. package/dist/config.js +17 -1
  16. package/dist/configWizard.js +5 -4
  17. package/dist/context.js +1 -0
  18. package/dist/contextScan.js +4 -3
  19. package/dist/discovery.js +1 -0
  20. package/dist/doctor.js +1 -0
  21. package/dist/errors.js +4 -0
  22. package/dist/exec.js +1 -0
  23. package/dist/history.js +10 -0
  24. package/dist/i18n.js +2 -0
  25. package/dist/index.js +151 -112
  26. package/dist/limits.js +4 -0
  27. package/dist/messages/adapter-errors.js +26 -2
  28. package/dist/messages/config.js +6 -0
  29. package/dist/messages/index.js +1 -0
  30. package/dist/messages/renderers.js +10 -2
  31. package/dist/messages/tui.js +8 -2
  32. package/dist/new.js +1 -0
  33. package/dist/ollamaUrl.js +20 -0
  34. package/dist/orchestrator.js +103 -150
  35. package/dist/output.js +1 -0
  36. package/dist/presets.js +1 -0
  37. package/dist/prompt.js +1 -0
  38. package/dist/renderers/console.js +1 -1
  39. package/dist/renderers/tui-prompts.js +339 -0
  40. package/dist/renderers/tui-renderer.js +224 -0
  41. package/dist/renderers/tui-screens.js +352 -0
  42. package/dist/renderers/tui-theme.js +356 -0
  43. package/dist/renderers/tui.js +7 -1086
  44. package/dist/runOptions.js +33 -2
  45. package/dist/session.js +1 -0
  46. package/dist/tuiController.js +61 -16
  47. package/dist/tuiState.js +4 -0
  48. package/dist/types.js +1 -0
  49. package/dist/update.js +1 -0
  50. package/dist/version.js +1 -0
  51. package/package.json +1 -1
@@ -1,1088 +1,9 @@
1
- import { createInterface } from "node:readline/promises";
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";
7
- const supportsColor = Boolean(process.stdout.isTTY) && !process.env.NO_COLOR;
8
- const supportsInteractiveOutput = Boolean(process.stdout.isTTY);
9
- /** Cree le premier renderer TUI leger, sans dependance UI externe. */
10
- export function createTuiRenderer(messages) {
11
- return new TuiRenderer(messages, supportsColor, supportsInteractiveOutput);
12
- }
13
- /** Affiche l'ecran d'accueil TUI lance par `palabre` sans sujet. */
14
- export function renderTuiHome(config, _configPath, messages, state = {}) {
15
- if (supportsInteractiveOutput) {
16
- clearScreen();
17
- }
18
- const viewport = viewportWidth();
19
- const width = surfaceWidth();
20
- const defaults = config.defaults ?? {};
21
- const mode = state.mode ?? defaults.mode ?? "debate";
22
- const debateAgents = defaults.agentA && defaults.agentB
23
- ? `${defaults.agentA} <-> ${defaults.agentB}`
24
- : messages.tui.noValue;
25
- const askAgents = defaults.askAgents && defaults.askAgents.length > 0
26
- ? defaults.askAgents.join(", ")
27
- : debateAgents.replace(" <-> ", ", ");
28
- const debateRoles = defaults.agentA && defaults.agentB
29
- ? `${roleFor(config, defaults.agentA, messages)} <-> ${roleFor(config, defaults.agentB, messages)}`
30
- : messages.tui.noValue;
31
- const askAgentNames = defaults.askAgents && defaults.askAgents.length > 0
32
- ? defaults.askAgents
33
- : [defaults.agentA, defaults.agentB].filter((agent) => Boolean(agent));
34
- const askRoles = askAgentNames.length > 0
35
- ? askAgentNames.map((agent) => roleFor(config, agent, messages)).join(", ")
36
- : debateRoles.replace(" <-> ", ", ");
37
- const summary = mode === "ask"
38
- ? defaults.askSummaryAgent ?? defaults.summaryAgent ?? messages.tui.lastAskAgent
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}`)];
44
- const lines = [
45
- "",
46
- ...centerLogo(viewport, messages),
47
- ...centerBlock(versionLines, viewport),
48
- "",
49
- ...centerBlock(composerCard([
50
- `${accent(messages.tui.modeValue(mode))} ${dim("·")} ${mode === "ask" ? askAgents : debateAgents}`,
51
- `${accent(messages.tui.roles)} ${dim("·")} ${mode === "ask" ? askRoles : debateRoles}`,
52
- `${accent(messages.tui.summary)} ${dim("·")} ${summary}${mode === "debate" ? ` ${dim("·")} ${accent(messages.tui.responses)} ${String(defaults.turns ?? "?")}` : ""}`,
53
- `${accent(messages.tui.folder)} ${dim("·")} ${compactPath(process.cwd(), Math.min(width - 4, viewport - 12))}`,
54
- `${accent(messages.tui.docs)} ${dim("·")} ${documentationUrl(config)}`,
55
- "",
56
- `${accent("/help")} ${dim(messages.tui.commands)} ${accent("/roles")} ${dim(messages.tui.roles.toLowerCase())} ${accent("/config")} ${dim(messages.tui.settings)} ${accent(mode === "ask" ? "/debat" : "/ask")} ${dim(messages.tui.changeMode)}`
57
- ], width, "center"), viewport),
58
- "",
59
- ...centerBlock([
60
- dim(messages.tui.tipContext)
61
- ], viewport)
62
- ];
63
- process.stdout.write(lines.join("\n") + "\n");
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
- }
80
- /** Affiche l'aide interne du composer TUI. */
81
- export function renderTuiHelp(messages) {
82
- if (supportsInteractiveOutput) {
83
- clearScreen();
84
- }
85
- const viewport = viewportWidth();
86
- const width = surfaceWidth();
87
- process.stdout.write([
88
- "",
89
- ...centerLogo(viewport, messages),
90
- "",
91
- ...centerBlock(card([
92
- bold(messages.tui.helpTitle),
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),
108
- "",
109
- dim(messages.tui.helpFallback)
110
- ], width), viewport),
111
- ""
112
- ].join("\n"));
113
- }
114
- /** Affiche l'aide rapide des agents configures. */
115
- export function renderTuiAgentsHelp(config, mode, messages) {
116
- if (supportsInteractiveOutput) {
117
- clearScreen();
118
- }
119
- const viewport = viewportWidth();
120
- const width = surfaceWidth();
121
- const activeAgents = activeAgentNamesForMode(config, mode);
122
- const separator = mode === "ask" ? ", " : " <-> ";
123
- const exampleAgents = exampleAgentsForMode(config, mode);
124
- process.stdout.write([
125
- "",
126
- ...centerLogo(viewport, messages),
127
- "",
128
- ...centerBlock(card([
129
- bold(messages.tui.agentsTitle),
130
- "",
131
- row(messages.tui.activeMode, messages.tui.modeValue(mode)),
132
- row(messages.tui.activeAgents, activeAgents.length > 0 ? activeAgents.join(separator) : messages.tui.noValue),
133
- "",
134
- bold(messages.tui.availableAgents),
135
- "",
136
- ...agentInventoryRows(config, messages),
137
- "",
138
- dim(`${messages.tui.example}: ${messages.tui.modeLabel(mode)} > ${messages.tui.agentsPrompt} > ${exampleAgents.join(" ")}`)
139
- ], width), viewport),
140
- ""
141
- ].join("\n"));
142
- }
143
- /** Affiche l'aide rapide des roles disponibles. */
144
- export function renderTuiRolesHelp(mode, messages, config) {
145
- if (supportsInteractiveOutput) {
146
- clearScreen();
147
- }
148
- const viewport = viewportWidth();
149
- const width = surfaceWidth();
150
- const currentRoles = config ? roleLineForMode(config, mode, messages) : undefined;
151
- const activeAgents = config ? activeAgentNamesForMode(config, mode) : [];
152
- const expectedCount = activeAgents.length || (mode === "ask" ? 3 : 2);
153
- const exampleRoles = exampleRolesForMode(mode, expectedCount);
154
- process.stdout.write([
155
- "",
156
- ...centerLogo(viewport, messages),
157
- "",
158
- ...centerBlock(card([
159
- bold(messages.tui.rolesTitle),
160
- "",
161
- ...(activeAgents.length > 0 ? [row(messages.tui.activeAgents, activeAgents.join(mode === "ask" ? ", " : " <-> "))] : []),
162
- ...(currentRoles ? [row(messages.tui.currentConfig, currentRoles), ""] : []),
163
- bold(messages.tui.availableRoles),
164
- "",
165
- row("implementer", messages.tui.roleImplementer),
166
- row("critic", messages.tui.roleCritic),
167
- row("architect", messages.tui.roleArchitect),
168
- row("scout", messages.tui.roleScout),
169
- row("reviewer", messages.tui.roleReviewer),
170
- row("summarizer", messages.tui.roleSummarizer),
171
- "",
172
- dim(`${messages.tui.example}: ${messages.tui.modeLabel(mode)} > ${messages.tui.rolesPrompt} > ${exampleRoles.join(" ")}`)
173
- ], Math.min(width, 82)), viewport),
174
- ""
175
- ].join("\n"));
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
- }
213
- /** Assistant minimal pour modifier les agents du mode courant. */
214
- export async function promptTuiAgentsWizard(config, mode, messages) {
215
- if (!input.isTTY) {
216
- return { kind: "back" };
217
- }
218
- renderTuiAgentsHelp(config, mode, messages);
219
- const rl = createInterface({ input, output });
220
- try {
221
- const result = await questionWithInterrupt(rl, tuiPrompt(mode, messages.tui.agentsPrompt, messages));
222
- if (result.kind === "quit") {
223
- return { kind: "quit" };
224
- }
225
- if (result.kind === "back") {
226
- return { kind: "back" };
227
- }
228
- const value = result.value.trim();
229
- if (!value || value === "/home" || value === "/back") {
230
- return { kind: "back" };
231
- }
232
- if (value === "/quit" || value === "/q") {
233
- return { kind: "quit" };
234
- }
235
- return { kind: "agents", agents: value.split(/\s+/).filter(Boolean) };
236
- }
237
- finally {
238
- rl.close();
239
- }
240
- }
241
- /** Assistant minimal pour modifier les roles du mode courant. */
242
- export async function promptTuiRolesWizard(config, mode, messages) {
243
- if (!input.isTTY) {
244
- return { kind: "back" };
245
- }
246
- renderTuiRolesHelp(mode, messages, config);
247
- const rl = createInterface({ input, output });
248
- try {
249
- const result = await questionWithInterrupt(rl, tuiPrompt(mode, messages.tui.rolesPrompt, messages));
250
- if (result.kind === "quit") {
251
- return { kind: "quit" };
252
- }
253
- if (result.kind === "back") {
254
- return { kind: "back" };
255
- }
256
- const answer = result.value;
257
- const value = answer.trim();
258
- if (!value || value === "/home" || value === "/back") {
259
- return { kind: "back" };
260
- }
261
- if (value === "/quit" || value === "/q") {
262
- return { kind: "quit" };
263
- }
264
- return { kind: "roles", roles: value.split(/\s+/).filter(Boolean) };
265
- }
266
- finally {
267
- rl.close();
268
- }
269
- }
270
- /** Affiche un composer visuel juste avant la vraie ligne readline. */
271
- export function renderTuiComposer(mode, messages, labelPrefix = messages.tui.subject, options = {}) {
272
- if (!options.force && !input.isTTY) {
273
- return;
274
- }
275
- const viewport = viewportWidth();
276
- const width = surfaceWidth();
277
- process.stdout.write([
278
- "",
279
- ...centerBlock(composerInputBox(mode, labelPrefix, width, messages), viewport),
280
- ""
281
- ].join("\n"));
282
- }
283
- /** Affiche l'ecran de config natif TUI, adapte au mode courant. */
284
- export function renderTuiConfig(config, configPath, mode, messages, state = {}) {
285
- if (supportsInteractiveOutput) {
286
- clearScreen();
287
- }
288
- const viewport = viewportWidth();
289
- const width = surfaceWidth();
290
- const defaults = config.defaults ?? {};
291
- const debateAgents = defaults.agentA && defaults.agentB ? `${defaults.agentA} <-> ${defaults.agentB}` : messages.tui.noValue;
292
- const askAgents = defaults.askAgents && defaults.askAgents.length > 0
293
- ? defaults.askAgents.join(", ")
294
- : debateAgents.replace(" <-> ", ", ");
295
- const debateRoles = defaults.agentA && defaults.agentB ? `${roleFor(config, defaults.agentA, messages)} <-> ${roleFor(config, defaults.agentB, messages)}` : messages.tui.noValue;
296
- const askRoles = roleLineForMode(config, "ask", messages);
297
- const summary = mode === "ask"
298
- ? defaults.askSummaryAgent ?? defaults.summaryAgent ?? messages.tui.lastAskAgent
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;
304
- const currentLines = mode === "ask"
305
- ? [
306
- row(messages.tui.activeAgents, askAgents),
307
- row(messages.tui.roles, askRoles),
308
- row(messages.tui.summary, summary),
309
- "",
310
- bold(messages.tui.availableCommands),
311
- "",
312
- row("/agents", messages.tui.askAgentsUsage),
313
- row("/roles", messages.tui.rolesUsage),
314
- row("/summary", messages.tui.summaryUsage)
315
- ]
316
- : [
317
- row(messages.tui.activeAgents, debateAgents),
318
- row(messages.tui.roles, debateRoles),
319
- row(messages.tui.summary, summary),
320
- row(messages.tui.responses, String(defaults.turns ?? "?")),
321
- "",
322
- bold(messages.tui.availableCommands),
323
- "",
324
- row("/agents", messages.tui.debateAgentsUsage),
325
- row("/roles", messages.tui.rolesUsage),
326
- row("/turns", messages.tui.turnsUsage),
327
- row("/summary", messages.tui.summaryUsage)
328
- ];
329
- const lines = [
330
- "",
331
- ...centerLogo(viewport, messages),
332
- "",
333
- ...centerBlock(card([
334
- bold(messages.tui.configTitle),
335
- "",
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)] : []),
339
- row(messages.tui.configFile, configPath),
340
- row(messages.tui.interface, defaults.interface ?? "tui"),
341
- row(messages.tui.language, config.language ?? "fr"),
342
- row(messages.tui.availableAgentsShort, agentInventoryLine(config, messages)),
343
- ...(ollamaModel ? [row(messages.tui.ollamaModel, ollamaModel)] : []),
344
- "",
345
- ...currentLines,
346
- "",
347
- row("/mode", messages.tui.modeConfigCommand),
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),
360
- row("/quit", messages.tui.quitCommand)
361
- ], width), viewport),
362
- ...(state.message ? ["", ...centerBlock([state.message], viewport)] : [])
363
- ];
364
- process.stdout.write(lines.join("\n") + "\n");
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
- }
378
- let lastTuiInterruptAt = 0;
379
- const doubleInterruptMs = 1200;
380
- function nextInterruptKind() {
381
- const now = Date.now();
382
- const kind = now - lastTuiInterruptAt <= doubleInterruptMs ? "quit" : "back";
383
- lastTuiInterruptAt = now;
384
- return kind;
385
- }
386
- function questionWithInterrupt(rl, prompt) {
387
- return new Promise((resolve, reject) => {
388
- let settled = false;
389
- const cleanup = () => rl.off("SIGINT", onSigint);
390
- const settle = (result) => {
391
- if (settled)
392
- return;
393
- settled = true;
394
- cleanup();
395
- resolve(result);
396
- };
397
- const onSigint = () => {
398
- const kind = nextInterruptKind();
399
- rl.close();
400
- settle({ kind });
401
- };
402
- rl.once("SIGINT", onSigint);
403
- rl.question(prompt).then((value) => settle({ kind: "answer", value }), (error) => {
404
- if (settled)
405
- return;
406
- settled = true;
407
- cleanup();
408
- reject(error);
409
- });
410
- });
411
- }
412
- /** Lit une demande depuis l'accueil TUI. Retourne undefined si l'utilisateur quitte. */
413
- export async function promptTuiHomeTopic(mode = "debate", messages, options = {}) {
414
- if (!input.isTTY) {
415
- return undefined;
416
- }
417
- const rl = createInterface({ input, output });
418
- try {
419
- const result = await questionWithInterrupt(rl, tuiPrompt(mode, messages.tui.subject, messages, options.notice));
420
- if (result.kind !== "answer") {
421
- return undefined;
422
- }
423
- const answer = result.value;
424
- const value = answer.trim();
425
- const parts = value.split(/\s+/).filter(Boolean);
426
- const command = parts[0]?.toLowerCase() ?? "";
427
- if (!value || command === "/quit" || command === "/q" || command === "/exit") {
428
- return undefined;
429
- }
430
- if (command === "/new") {
431
- return { kind: "new" };
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
- }
445
- if (command === "/config") {
446
- return { kind: "config" };
447
- }
448
- if (command === "/agents") {
449
- return { kind: "agents", agents: parts.slice(1) };
450
- }
451
- if (command === "/ask") {
452
- return { kind: "mode", mode: "ask" };
453
- }
454
- if (command === "/debat" || command === "/débat" || command === "/debate") {
455
- return { kind: "mode", mode: "debate" };
456
- }
457
- if (command === "/help" || command === "/h" || command === "/?") {
458
- return { kind: "help" };
459
- }
460
- if (command === "/roles" || command === "/role") {
461
- return { kind: "roles", roles: parts.slice(1) };
462
- }
463
- if (value.startsWith("/")) {
464
- return { kind: "help" };
465
- }
466
- return { kind: "topic", topic: value };
467
- }
468
- finally {
469
- rl.close();
470
- }
471
- }
472
- /** Lit une commande depuis l'ecran de config TUI. */
473
- export async function promptTuiConfigCommand(mode, messages) {
474
- if (!input.isTTY) {
475
- return { kind: "back" };
476
- }
477
- const rl = createInterface({ input, output });
478
- try {
479
- const result = await questionWithInterrupt(rl, tuiPrompt(mode, messages.tui.configPrompt, messages));
480
- if (result.kind === "quit") {
481
- return { kind: "quit" };
482
- }
483
- if (result.kind === "back") {
484
- return { kind: "back" };
485
- }
486
- const answer = result.value;
487
- const parts = answer.trim().split(/\s+/).filter(Boolean);
488
- const command = parts[0]?.toLowerCase();
489
- if (!command || command === "/home" || command === "/back" || command === "/b") {
490
- return { kind: "back" };
491
- }
492
- if (command === "/quit" || command === "/q" || command === "/exit") {
493
- return { kind: "quit" };
494
- }
495
- if (command === "/mode") {
496
- return { kind: "mode" };
497
- }
498
- if (command === "/default") {
499
- return { kind: "default-mode" };
500
- }
501
- if (command === "/interface") {
502
- const value = parts[1];
503
- if (value === "tui" || value === "terminal") {
504
- return { kind: "interface", interfaceName: value };
505
- }
506
- return { kind: "unknown", message: "Usage: /interface <tui|terminal>" };
507
- }
508
- if (command === "/language" || command === "/langue" || command === "/lang") {
509
- const value = parts[1];
510
- if (value === "fr" || value === "en") {
511
- return { kind: "language", language: value };
512
- }
513
- return { kind: "unknown", message: "Usage: /language <fr|en>" };
514
- }
515
- if (command === "/agents") {
516
- return parts.length > 1
517
- ? { kind: "agents", agents: parts.slice(1) }
518
- : { kind: "agents", agents: [] };
519
- }
520
- if (command === "/roles" || command === "/role") {
521
- return { kind: "roles", roles: parts.slice(1) };
522
- }
523
- if (command === "/turns") {
524
- const turns = Number(parts[1]);
525
- return Number.isInteger(turns)
526
- ? { kind: "turns", turns }
527
- : { kind: "unknown", message: messages.tui.turnsUsage };
528
- }
529
- if (command === "/summary") {
530
- const value = parts[1];
531
- if (!value) {
532
- return { kind: "unknown", message: messages.tui.summaryUsage };
533
- }
534
- return { kind: "summary", agent: isNoneValue(value) ? undefined : value };
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
- }
555
- return { kind: "unknown", message: messages.tui.unknownCommand };
556
- }
557
- finally {
558
- rl.close();
559
- }
560
- }
561
1
  /**
562
- * Renderer TUI V0.
563
- *
564
- * Il ne tente pas encore de faire du scrolling controle ou des panes interactives :
565
- * il fournit un tableau de bord plein terminal, un statut d'agent en cours et des
566
- * sections lisibles tout en conservant la sortie complete dans le flux terminal.
2
+ * @file Point d'entrée public du rendu TUI. Ré-exporte l'API des modules spécialisés :
3
+ * `tui-renderer` (événements de débat/ask), `tui-screens` (écrans plein terminal),
4
+ * `tui-prompts` (entrées readline et commandes slash). Les primitives visuelles de
5
+ * `tui-theme` restent internes au rendu TUI et ne sont pas ré-exportées.
567
6
  */
568
- class TuiRenderer {
569
- messages;
570
- color;
571
- interactive;
572
- spinner;
573
- spinnerFrame = 0;
574
- currentSection = "debate";
575
- currentAgent;
576
- frames = ["-", "\\", "|", "/"];
577
- constructor(messages, color, interactive) {
578
- this.messages = messages;
579
- this.color = color;
580
- this.interactive = interactive;
581
- }
582
- start(options, agents = []) {
583
- if (this.interactive) {
584
- clearScreen();
585
- }
586
- process.stdout.write(this.renderSessionHeader(options, agents).join("\n") + "\n");
587
- }
588
- notice(message) {
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`);
594
- }
595
- warning(message) {
596
- process.stderr.write(`${this.c("yellow", this.messages.renderers.warningPrefix)} ${message}\n`);
597
- }
598
- turnStart(turn, totalTurns, agent, role) {
599
- this.currentSection = "debate";
600
- this.currentAgent = agent;
601
- this.promptBlock(`${agentLabel(agent)} (${role}) - ${this.messages.renderers.turn(turn, totalTurns)}`, agent);
602
- }
603
- askResponseStart(response, totalResponses, agent, role) {
604
- this.currentSection = "ask";
605
- this.currentAgent = agent;
606
- this.promptBlock(`${agentLabel(agent)} (${role}) - ${this.messages.tui.askResponse(response, totalResponses)}`, agent);
607
- }
608
- thinkingStart(agent, role) {
609
- this.thinkingEnd();
610
- const text = this.messages.renderers.thinking(agent, role);
611
- if (!this.interactive) {
612
- process.stdout.write(`${text}...\n`);
613
- return;
614
- }
615
- process.stdout.write("\u001b[?25l");
616
- const render = () => {
617
- const frame = this.frames[this.spinnerFrame % this.frames.length];
618
- this.spinnerFrame += 1;
619
- process.stdout.write(`\r\u001b[2K${surfacePadding()}${agentColor(agent, frame)} ${text}...`);
620
- };
621
- render();
622
- this.spinner = setInterval(render, 120);
623
- }
624
- thinkingEnd() {
625
- if (this.spinner) {
626
- clearInterval(this.spinner);
627
- this.spinner = undefined;
628
- }
629
- if (this.interactive) {
630
- process.stdout.write("\r\u001b[2K\u001b[?25h");
631
- }
632
- }
633
- message(content) {
634
- const trimmed = content.trim();
635
- process.stdout.write(`${this.formatMessage(this.currentSection === "summary" ? this.formatSummary(trimmed) : trimmed)}\n`);
636
- }
637
- askResponseMessage(content) {
638
- this.message(content);
639
- }
640
- summaryStart(agent, role) {
641
- this.currentSection = "summary";
642
- this.currentAgent = agent;
643
- this.promptBlock(`${this.messages.renderers.summaryTitle} - ${agent} (${role})`, agent);
644
- }
645
- error(failure) {
646
- this.thinkingEnd();
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`);
653
- }
654
- done(outputPath) {
655
- this.thinkingEnd();
656
- const viewport = viewportWidth();
657
- const width = this.width();
658
- const folderPath = path.dirname(outputPath);
659
- const fileName = path.basename(outputPath);
660
- process.stdout.write(`\n${centerBlock(panel([
661
- bold(this.messages.tui.sessionDone),
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)
667
- ], width), viewport).join("\n")}\n\n`);
668
- }
669
- renderSessionHeader(options, agents) {
670
- const viewport = viewportWidth();
671
- const width = this.width();
672
- const mode = messagesModeLabel(this.messages, options.mode).toUpperCase();
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);
680
- return [
681
- ...centerLogo(viewport, this.messages),
682
- "",
683
- ...centerBlock(main, viewport),
684
- ""
685
- ];
686
- }
687
- promptBlock(title, agent) {
688
- const viewport = viewportWidth();
689
- const width = this.width();
690
- const underline = underlineFor(title, width, agent);
691
- process.stdout.write(`\n${centerBlock(card([bold(title), underline], width), viewport).join("\n")}\n`);
692
- }
693
- formatMessage(content) {
694
- const width = this.width();
695
- const contentWidth = Math.max(24, width - 4);
696
- const body = content
697
- .split(/\r?\n/)
698
- .flatMap((line) => line ? wrapLine(line, contentWidth) : [""]);
699
- return `\n${centerBlock(textSurface(body, width, this.currentAgent), viewportWidth()).join("\n")}\n`;
700
- }
701
- formatSummary(content) {
702
- return content
703
- .split(/\r?\n/)
704
- .map((line) => {
705
- const heading = line.match(/^###\s+(.+)$/);
706
- if (!heading) {
707
- return line;
708
- }
709
- return [
710
- "",
711
- this.c("magenta", heading[1] ?? line),
712
- this.dim("-".repeat(Math.min(48, this.width())))
713
- ].join("\n");
714
- })
715
- .join("\n")
716
- .trimStart();
717
- }
718
- width() {
719
- return surfaceWidth();
720
- }
721
- c(color, value) {
722
- if (!this.color)
723
- return value;
724
- return `${codes[color]}${value}${codes.reset}`;
725
- }
726
- dim(value) {
727
- if (!this.color)
728
- return value;
729
- return `${codes.dim}${value}${codes.reset}`;
730
- }
731
- }
732
- function formatAgents(options, agents) {
733
- if (options.mode === "ask") {
734
- return agents.length > 0
735
- ? agents.map(formatAgent).join(", ")
736
- : (options.askAgents ?? [options.agentA, options.agentB]).join(", ");
737
- }
738
- if (agents.length >= 2) {
739
- return `${formatAgent(agents[0])} <-> ${formatAgent(agents[1])}`;
740
- }
741
- return `${options.agentA} <-> ${options.agentB}`;
742
- }
743
- function formatAgent(agent) {
744
- return agent ? `${agentLabel(agent.name)} (${agent.role})` : "?";
745
- }
746
- function formatSummary(options, messages) {
747
- if (!options.summaryEnabled) {
748
- return messages.renderers.disabled;
749
- }
750
- return options.summaryAgent;
751
- }
752
- function formatResponseCount(options) {
753
- return options.mode === "ask" ? options.askAgents?.length ?? 2 : options.turns;
754
- }
755
- function formatSessionProgress(options, messages) {
756
- return `${messages.tui.historyCount(options.mode)}: ${formatResponseCount(options)} | ${messages.tui.summary}: ${formatSummary(options, messages)}`;
757
- }
758
- function formatContext(options, messages) {
759
- return options.files.length === 0
760
- ? messages.renderers.noInjectedFiles
761
- : messages.renderers.injectedFiles(options.files.length);
762
- }
763
- function formatFailureLocation(failure, messages) {
764
- if (failure.phase === "summary") {
765
- return messages.renderers.summaryTitle;
766
- }
767
- const turn = failure.turn === undefined ? "" : `, ${messages.tui.turnLabel(failure.turn)}`;
768
- return `${failure.agent ?? "?"} (${failure.role ?? "?"}${turn})`;
769
- }
770
- function stripAnsi(value) {
771
- return value
772
- .replace(/\u001b\][^\u001b]*(?:\u0007|\u001b\\)/g, "")
773
- .replace(/\u001b\[[0-9;?]*[A-Za-z]/g, "");
774
- }
775
- function clearScreen() {
776
- process.stdout.write("\u001bc\u001b[?25h");
777
- }
778
- function surfaceWidth() {
779
- return Math.max(72, Math.min(viewportWidth() - 8, 96));
780
- }
781
- function viewportWidth() {
782
- return Math.max(72, Math.min(process.stdout.columns || 100, 180));
783
- }
784
- function tuiPrompt(mode, labelPrefix, messages, notice) {
785
- const label = promptTrail(mode, labelPrefix, messages);
786
- const padding = surfacePadding();
787
- const promptLine = `${padding}${label} ${dim(">")} `;
788
- return [
789
- "",
790
- promptRuleLine(),
791
- ...(notice ? [
792
- `${padding}${label} ${dim(">")}`,
793
- ...promptNoticeLines(notice),
794
- ""
795
- ] : []),
796
- promptLine,
797
- ].join("\n");
798
- }
799
- function promptRuleLine() {
800
- return `${surfacePadding()}${violet("-".repeat(surfaceWidth()))}`;
801
- }
802
- function promptNoticeLines(notice) {
803
- const padding = surfacePadding();
804
- const contentWidth = surfaceWidth();
805
- return [
806
- `${padding}${dim("-".repeat(contentWidth))}`,
807
- ...wrapLine(notice, contentWidth).map((line) => `${padding}${line}`)
808
- ];
809
- }
810
- function surfacePadding() {
811
- return " ".repeat(Math.max(0, Math.floor((viewportWidth() - surfaceWidth()) / 2)));
812
- }
813
- function promptModeLabel(mode, messages) {
814
- return `Mode ${messages.tui.modeValue(mode).toLowerCase()}`;
815
- }
816
- function promptTrail(mode, labelPrefix, messages) {
817
- const parts = [bold("Palabre"), accent(promptModeLabel(mode, messages))];
818
- if (labelPrefix !== messages.tui.subject) {
819
- parts.push(bold(labelPrefix));
820
- }
821
- return parts.join(` ${dim(">")} `);
822
- }
823
- function messagesModeLabel(messages, mode) {
824
- return messages.tui.modeValue(mode);
825
- }
826
- function compactPath(value, maxLength) {
827
- if (value.length <= maxLength) {
828
- return value;
829
- }
830
- const marker = "...";
831
- const tailLength = Math.max(12, maxLength - marker.length);
832
- return `${marker}${value.slice(-tailLength)}`;
833
- }
834
- function dirnamePortable(value) {
835
- const separatorIndex = Math.max(value.lastIndexOf("/"), value.lastIndexOf("\\"));
836
- return separatorIndex > 0 ? value.slice(0, separatorIndex) : path.dirname(value);
837
- }
838
- function compactFileName(value, maxLength) {
839
- if (value.length <= maxLength) {
840
- return value;
841
- }
842
- const extension = value.match(/\.(debate|ask)\.md$/i)?.[0] ?? "";
843
- const marker = "...";
844
- const headLength = Math.max(12, maxLength - marker.length - extension.length);
845
- return `${value.slice(0, headLength)}${marker}${extension}`;
846
- }
847
- function terminalLink(filePath, label) {
848
- if (!supportsInteractiveOutput) {
849
- return label;
850
- }
851
- return `\u001b]8;;${pathToFileURL(filePath).href}\u001b\\${label}\u001b]8;;\u001b\\`;
852
- }
853
- function roleFor(config, agent, messages) {
854
- return config.agents[agent]?.role ?? messages.tui.noValue;
855
- }
856
- function roleLineForMode(config, mode, messages) {
857
- const agents = activeAgentNamesForMode(config, mode);
858
- if (mode === "ask") {
859
- return agents.length > 0 ? agents.map((agent) => roleFor(config, agent, messages)).join(", ") : messages.tui.noValue;
860
- }
861
- return agents.length === 2
862
- ? `${roleFor(config, agents[0], messages)} <-> ${roleFor(config, agents[1], messages)}`
863
- : messages.tui.noValue;
864
- }
865
- function activeAgentNamesForMode(config, mode) {
866
- const defaults = config.defaults ?? {};
867
- if (mode === "ask") {
868
- const agents = defaults.askAgents && defaults.askAgents.length > 0
869
- ? defaults.askAgents
870
- : [defaults.agentA, defaults.agentB].filter((agent) => Boolean(agent));
871
- return agents.filter((agent) => Boolean(config.agents[agent]) && !isRetiredAgentName(agent));
872
- }
873
- return [defaults.agentA, defaults.agentB].filter((agent) => typeof agent === "string" && Boolean(config.agents[agent]) && !isRetiredAgentName(agent));
874
- }
875
- function agentInventoryLine(config, messages) {
876
- const agents = Object.entries(config.agents)
877
- .filter(([name]) => !isRetiredAgentName(name))
878
- .map(([name]) => name)
879
- .sort();
880
- return agents.length > 0 ? agents.map(agentLabel).join(", ") : messages.tui.noValue;
881
- }
882
- function agentInventoryRows(config, messages) {
883
- const entries = Object.entries(config.agents)
884
- .filter(([name]) => !isRetiredAgentName(name))
885
- .sort(([agentA], [agentB]) => agentA.localeCompare(agentB));
886
- if (entries.length === 0) {
887
- return [dim(messages.tui.noConfiguredAgents)];
888
- }
889
- return entries.map(([name, agent]) => row(name, `${agent.type} ${dim("·")} ${agent.role}`));
890
- }
891
- function exampleAgentsForMode(config, mode) {
892
- const activeAgents = activeAgentNamesForMode(config, mode);
893
- if (activeAgents.length > 0) {
894
- return activeAgents;
895
- }
896
- const available = Object.keys(config.agents).filter((agent) => !isRetiredAgentName(agent)).sort();
897
- return mode === "ask" ? available.slice(0, 3) : available.slice(0, 2);
898
- }
899
- function documentationUrl(config) {
900
- return `https://palab.re/${config.language === "en" ? "en" : "fr"}`;
901
- }
902
- function exampleRolesForMode(mode, count) {
903
- const roles = mode === "ask"
904
- ? ["critic", "implementer", "scout", "architect"]
905
- : ["implementer", "critic"];
906
- while (roles.length < count) {
907
- roles.push("reviewer");
908
- }
909
- return roles.slice(0, count);
910
- }
911
- function isNoneValue(value) {
912
- return ["none", "aucun", "off", "non", "0", "disabled"].includes(value.toLowerCase());
913
- }
914
- function centerLogo(width, messages) {
915
- return [
916
- ...logo(),
917
- "",
918
- bold(messages.tui.tagline)
919
- ].map((line) => padLeft(line, Math.max(0, Math.floor((width - visibleLength(line)) / 2))));
920
- }
921
- function logo() {
922
- return [
923
- " ___ ___ _ ___ ___ ___ ___ ",
924
- "| _ \\| _ || | | _ || _ )| _ \\| __|",
925
- "| _/| || |_ | || _ \\| /| _| ",
926
- "|_| |_|_||___||_|_||___/|_|_\\|___|"
927
- ].map((line) => supportsColor ? `${codes.logoViolet}${line}${codes.reset}` : line);
928
- }
929
- function centerBlock(lines, width) {
930
- const blockWidth = Math.max(...lines.map(visibleLength), 0);
931
- const left = Math.max(0, Math.floor((width - blockWidth) / 2));
932
- return lines.map((line) => padLeft(line, left));
933
- }
934
- function card(lines, width) {
935
- const contentWidth = Math.max(24, width - 4);
936
- const body = lines.flatMap((line) => wrapLine(line, contentWidth));
937
- return body.map((line) => `${dim("|")} ${padRight(line, contentWidth)} ${dim("|")}`);
938
- }
939
- function composerCard(lines, width, align = "left") {
940
- const contentWidth = Math.max(24, width - 4);
941
- const body = lines.flatMap((line) => wrapLine(line, contentWidth));
942
- return [
943
- `${dim("|")} ${" ".repeat(contentWidth)} ${dim("|")}`,
944
- ...body.map((line) => `${dim("|")} ${padRight(align === "center" ? centerLine(line, contentWidth) : line, contentWidth)} ${dim("|")}`),
945
- `${dim("|")} ${" ".repeat(contentWidth)} ${dim("|")}`
946
- ];
947
- }
948
- function centerLine(line, width) {
949
- const left = Math.max(0, Math.floor((width - visibleLength(line)) / 2));
950
- return `${" ".repeat(left)}${line}`;
951
- }
952
- function composerInputBox(mode, labelPrefix, width, messages) {
953
- return composerCard([
954
- `${promptTrail(mode, labelPrefix, messages)} ${dim(">")}`
955
- ], width, "center");
956
- }
957
- function panel(lines, width) {
958
- const contentWidth = Math.max(18, width - 4);
959
- const body = lines.flatMap((line) => wrapLine(line, contentWidth));
960
- return [
961
- `${dim("+")}${dim("-".repeat(contentWidth + 2))}${dim("+")}`,
962
- ...body.map((line) => `${dim("|")} ${padRight(line, contentWidth)} ${dim("|")}`),
963
- `${dim("+")}${dim("-".repeat(contentWidth + 2))}${dim("+")}`
964
- ];
965
- }
966
- function textSurface(lines, width, agent) {
967
- const contentWidth = Math.max(24, width - 4);
968
- const body = lines.length > 0 ? lines : [""];
969
- const border = agent ? (value) => agentColor(agent, value) : violet;
970
- return [
971
- `${border("|")} ${" ".repeat(contentWidth)} ${border("|")}`,
972
- ...body.map((line) => `${border("|")} ${padRight(line, contentWidth)} ${border("|")}`),
973
- `${border("|")} ${" ".repeat(contentWidth)} ${border("|")}`
974
- ];
975
- }
976
- function underlineFor(title, width, agent) {
977
- const length = Math.max(8, Math.min(48, visibleLength(title), width - 4));
978
- const line = "-".repeat(length);
979
- return agent ? agentColor(agent, line) : accent(line);
980
- }
981
- function row(label, value) {
982
- return `${bold(label.padEnd(16))} ${value}`;
983
- }
984
- function wrapLine(value, width) {
985
- const clean = value.replace(/\r?\n/g, " ");
986
- if (visibleLength(clean) <= width) {
987
- return [clean];
988
- }
989
- const words = clean.split(/\s+/);
990
- const lines = [];
991
- let current = "";
992
- for (const word of words) {
993
- const next = current ? `${current} ${word}` : word;
994
- if (visibleLength(next) <= width) {
995
- current = next;
996
- }
997
- else {
998
- if (current)
999
- lines.push(current);
1000
- current = word;
1001
- }
1002
- }
1003
- if (current)
1004
- lines.push(current);
1005
- return lines;
1006
- }
1007
- function padLeft(value, width) {
1008
- return `${" ".repeat(width)}${value}`;
1009
- }
1010
- function padRight(value, width) {
1011
- const missing = Math.max(0, width - visibleLength(value));
1012
- return `${value}${" ".repeat(missing)}`;
1013
- }
1014
- function visibleLength(value) {
1015
- return stripAnsi(value).length;
1016
- }
1017
- function bold(value) {
1018
- return supportsColor ? `${codes.bright}${value}${codes.reset}` : value;
1019
- }
1020
- function dim(value) {
1021
- return supportsColor ? `${codes.dim}${value}${codes.reset}` : value;
1022
- }
1023
- function accent(value) {
1024
- return supportsColor ? `${codes.violet}${value}${codes.reset}` : value;
1025
- }
1026
- function violet(value) {
1027
- return supportsColor ? `${codes.violet}${value}${codes.reset}` : value;
1028
- }
1029
- function agentLabel(agent) {
1030
- return supportsColor ? `${agentAnsi(agent)}${agent}${codes.reset}` : agent;
1031
- }
1032
- function agentColor(agent, value) {
1033
- return supportsColor ? `${agentAnsi(agent)}${value}${codes.reset}` : value;
1034
- }
1035
- function agentAnsi(agent) {
1036
- const normalized = agent.toLowerCase();
1037
- const color = agentColors[normalized] ?? hashedAgentColor(normalized);
1038
- return `\u001b[38;2;${color[0]};${color[1]};${color[2]}m`;
1039
- }
1040
- function hashedAgentColor(agent) {
1041
- let hash = 0;
1042
- for (let index = 0; index < agent.length; index += 1) {
1043
- hash = (hash * 31 + agent.charCodeAt(index)) | 0;
1044
- }
1045
- const hue = Math.abs(hash) % 360;
1046
- return hslToRgb(hue, 55, 55);
1047
- }
1048
- function hslToRgb(hue, saturation, lightness) {
1049
- const s = saturation / 100;
1050
- const l = lightness / 100;
1051
- const c = (1 - Math.abs(2 * l - 1)) * s;
1052
- const x = c * (1 - Math.abs((hue / 60) % 2 - 1));
1053
- const m = l - c / 2;
1054
- const [r, g, b] = hue < 60 ? [c, x, 0]
1055
- : hue < 120 ? [x, c, 0]
1056
- : hue < 180 ? [0, c, x]
1057
- : hue < 240 ? [0, x, c]
1058
- : hue < 300 ? [x, 0, c]
1059
- : [c, 0, x];
1060
- return [
1061
- Math.round((r + m) * 255),
1062
- Math.round((g + m) * 255),
1063
- Math.round((b + m) * 255)
1064
- ];
1065
- }
1066
- const agentColors = {
1067
- antigravity: [76, 141, 255],
1068
- claude: [222, 115, 86],
1069
- codex: [136, 82, 197],
1070
- opencode: [80, 168, 103],
1071
- vibe: [234, 92, 126],
1072
- "ollama-local": [179, 176, 31],
1073
- ollama: [179, 176, 31]
1074
- };
1075
- const codes = {
1076
- reset: "\u001b[0m",
1077
- bright: "\u001b[1m",
1078
- dim: "\u001b[2m",
1079
- logoViolet: "\u001b[38;2;167;139;250m",
1080
- violet: "\u001b[38;5;141m",
1081
- cyan: "\u001b[36m",
1082
- gray: "\u001b[38;5;244m",
1083
- green: "\u001b[32m",
1084
- magenta: "\u001b[35m",
1085
- orange: "\u001b[38;5;214m",
1086
- red: "\u001b[31m",
1087
- yellow: "\u001b[33m"
1088
- };
7
+ export { createTuiRenderer } from "./tui-renderer.js";
8
+ export { renderTuiAgentsHelp, renderTuiConfig, renderTuiHelp, renderTuiHistory, renderTuiHome, renderTuiRolesHelp, renderTuiUpdate } from "./tui-screens.js";
9
+ export { parseComposerTopic, parseTuiOllamaUrlCommand, promptTuiAgentsWizard, promptTuiConfigCommand, promptTuiHomeTopic, promptTuiRolesWizard, renderTuiComposer } from "./tui-prompts.js";