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.
- package/dist/adapters/cli-pty.js +30 -10
- package/dist/adapters/cli-shared.js +73 -0
- package/dist/adapters/cli.js +40 -77
- package/dist/adapters/index.js +1 -0
- package/dist/adapters/ollama.js +32 -32
- package/dist/adapters/terminal.js +1 -0
- package/dist/args.js +1 -0
- package/dist/commands/agents.js +7 -1
- package/dist/commands/context.js +7 -1
- package/dist/commands/history.js +6 -1
- package/dist/commands/init.js +8 -3
- package/dist/commands/presets.js +6 -1
- package/dist/commands/shared.js +5 -1
- package/dist/commands/update.js +6 -1
- package/dist/config.js +17 -1
- package/dist/configWizard.js +5 -4
- package/dist/context.js +1 -0
- package/dist/contextScan.js +4 -3
- package/dist/discovery.js +1 -0
- package/dist/doctor.js +1 -0
- package/dist/errors.js +4 -0
- package/dist/exec.js +1 -0
- package/dist/history.js +10 -0
- package/dist/i18n.js +2 -0
- package/dist/index.js +151 -112
- package/dist/limits.js +4 -0
- package/dist/messages/adapter-errors.js +26 -2
- package/dist/messages/config.js +6 -0
- package/dist/messages/index.js +1 -0
- package/dist/messages/renderers.js +10 -2
- package/dist/messages/tui.js +8 -2
- package/dist/new.js +1 -0
- package/dist/ollamaUrl.js +20 -0
- package/dist/orchestrator.js +103 -150
- package/dist/output.js +1 -0
- package/dist/presets.js +1 -0
- package/dist/prompt.js +1 -0
- package/dist/renderers/console.js +1 -1
- package/dist/renderers/tui-prompts.js +339 -0
- package/dist/renderers/tui-renderer.js +224 -0
- package/dist/renderers/tui-screens.js +352 -0
- package/dist/renderers/tui-theme.js +356 -0
- package/dist/renderers/tui.js +7 -1086
- package/dist/runOptions.js +33 -2
- package/dist/session.js +1 -0
- package/dist/tuiController.js +61 -16
- package/dist/tuiState.js +4 -0
- package/dist/types.js +1 -0
- package/dist/update.js +1 -0
- package/dist/version.js +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Écrans plein terminal du TUI : accueil, aide, agents, rôles, historique,
|
|
3
|
+
* configuration et instructions de mise à jour. Chaque écran efface l'écran en TTY
|
|
4
|
+
* puis compose des blocs du thème (`tui-theme`) ; aucune lecture d'entrée ici.
|
|
5
|
+
*/
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { isRetiredAgentName } from "../agentRegistry.js";
|
|
8
|
+
import { DEFAULT_OLLAMA_BASE_URL, resolveOllamaBaseUrl } from "../ollamaUrl.js";
|
|
9
|
+
import { accent, agentLabel, bold, brandHeader, card, clearScreen, compactFileName, compactPath, composerCard, dim, dirnamePortable, logoBlock, padBlock, panel, row, rows, supportsInteractiveOutput, surfaceWidth, terminalLink, viewportWidth } from "./tui-theme.js";
|
|
10
|
+
/** Affiche l'ecran d'accueil TUI lance par `palabre` sans sujet. */
|
|
11
|
+
export function renderTuiHome(config, _configPath, messages, state = {}) {
|
|
12
|
+
if (supportsInteractiveOutput) {
|
|
13
|
+
clearScreen();
|
|
14
|
+
}
|
|
15
|
+
const viewport = viewportWidth();
|
|
16
|
+
const width = surfaceWidth();
|
|
17
|
+
const defaults = config.defaults ?? {};
|
|
18
|
+
const mode = state.mode ?? defaults.mode ?? "debate";
|
|
19
|
+
const debateAgents = defaults.agentA && defaults.agentB
|
|
20
|
+
? `${defaults.agentA} <-> ${defaults.agentB}`
|
|
21
|
+
: messages.tui.noValue;
|
|
22
|
+
const askAgents = defaults.askAgents && defaults.askAgents.length > 0
|
|
23
|
+
? defaults.askAgents.join(", ")
|
|
24
|
+
: debateAgents.replace(" <-> ", ", ");
|
|
25
|
+
const debateRoles = defaults.agentA && defaults.agentB
|
|
26
|
+
? `${roleFor(config, defaults.agentA, messages)} <-> ${roleFor(config, defaults.agentB, messages)}`
|
|
27
|
+
: messages.tui.noValue;
|
|
28
|
+
const askAgentNames = defaults.askAgents && defaults.askAgents.length > 0
|
|
29
|
+
? defaults.askAgents
|
|
30
|
+
: [defaults.agentA, defaults.agentB].filter((agent) => Boolean(agent));
|
|
31
|
+
const askRoles = askAgentNames.length > 0
|
|
32
|
+
? askAgentNames.map((agent) => roleFor(config, agent, messages)).join(", ")
|
|
33
|
+
: debateRoles.replace(" <-> ", ", ");
|
|
34
|
+
const summary = mode === "ask"
|
|
35
|
+
? defaults.askSummaryAgent ?? defaults.summaryAgent ?? messages.tui.lastAskAgent
|
|
36
|
+
: defaults.summaryAgent ?? defaults.agentB ?? "agent B";
|
|
37
|
+
const version = state.version ?? "0.0.0";
|
|
38
|
+
const versionLines = state.latestVersion
|
|
39
|
+
? [dim(`v${version}`), accent(messages.tui.updateAvailable(version, state.latestVersion))]
|
|
40
|
+
: [dim(`v${version}`)];
|
|
41
|
+
const lines = [
|
|
42
|
+
"",
|
|
43
|
+
...padBlock(logoBlock(messages)),
|
|
44
|
+
...padBlock(versionLines),
|
|
45
|
+
"",
|
|
46
|
+
...padBlock(composerCard([
|
|
47
|
+
`${accent(messages.tui.modeValue(mode))} ${dim("·")} ${mode === "ask" ? askAgents : debateAgents}`,
|
|
48
|
+
`${accent(messages.tui.roles)} ${dim("·")} ${mode === "ask" ? askRoles : debateRoles}`,
|
|
49
|
+
`${accent(messages.tui.summary)} ${dim("·")} ${summary}${mode === "debate" ? ` ${dim("·")} ${accent(messages.tui.responses)} ${String(defaults.turns ?? "?")}` : ""}`,
|
|
50
|
+
`${accent(messages.tui.folder)} ${dim("·")} ${compactPath(process.cwd(), Math.min(width - 4, viewport - 12))}`,
|
|
51
|
+
`${accent(messages.tui.docs)} ${dim("·")} ${documentationUrl(config)}`,
|
|
52
|
+
"",
|
|
53
|
+
`${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)}`
|
|
54
|
+
], width)),
|
|
55
|
+
"",
|
|
56
|
+
...padBlock([
|
|
57
|
+
dim(messages.tui.tipContext)
|
|
58
|
+
])
|
|
59
|
+
];
|
|
60
|
+
process.stdout.write(lines.join("\n") + "\n");
|
|
61
|
+
}
|
|
62
|
+
/** Affiche les instructions de mise a jour sans quitter le TUI. */
|
|
63
|
+
export function renderTuiUpdate(instructions, _messages) {
|
|
64
|
+
if (supportsInteractiveOutput) {
|
|
65
|
+
clearScreen();
|
|
66
|
+
}
|
|
67
|
+
const width = surfaceWidth();
|
|
68
|
+
process.stdout.write([
|
|
69
|
+
"",
|
|
70
|
+
...padBlock([brandHeader()]),
|
|
71
|
+
"",
|
|
72
|
+
...padBlock(card(instructions.split(/\r?\n/), width)),
|
|
73
|
+
""
|
|
74
|
+
].join("\n"));
|
|
75
|
+
}
|
|
76
|
+
/** Affiche l'aide interne du composer TUI. */
|
|
77
|
+
export function renderTuiHelp(messages) {
|
|
78
|
+
if (supportsInteractiveOutput) {
|
|
79
|
+
clearScreen();
|
|
80
|
+
}
|
|
81
|
+
const width = surfaceWidth();
|
|
82
|
+
process.stdout.write([
|
|
83
|
+
"",
|
|
84
|
+
...padBlock([brandHeader(messages.tui.helpTitle)]),
|
|
85
|
+
"",
|
|
86
|
+
...padBlock(card([
|
|
87
|
+
row("/ask", messages.tui.helpAsk),
|
|
88
|
+
row("/debat", messages.tui.helpDebate),
|
|
89
|
+
"",
|
|
90
|
+
row("/agents", messages.tui.helpAgents),
|
|
91
|
+
row("/roles", messages.tui.helpRoles),
|
|
92
|
+
row("/config", messages.tui.helpConfig),
|
|
93
|
+
"",
|
|
94
|
+
row("/new", messages.tui.helpNew),
|
|
95
|
+
row("/retry", messages.tui.helpRetry),
|
|
96
|
+
row("/history", messages.tui.helpHistory),
|
|
97
|
+
row("/update", messages.tui.helpUpdate),
|
|
98
|
+
row("/home", messages.tui.backCommand),
|
|
99
|
+
row("/help", messages.tui.helpHelp),
|
|
100
|
+
row("/quit", messages.tui.helpQuit),
|
|
101
|
+
"",
|
|
102
|
+
dim(messages.tui.helpFallback)
|
|
103
|
+
], width)),
|
|
104
|
+
""
|
|
105
|
+
].join("\n"));
|
|
106
|
+
}
|
|
107
|
+
/** Affiche l'aide rapide des agents configures. */
|
|
108
|
+
export function renderTuiAgentsHelp(config, mode, messages) {
|
|
109
|
+
if (supportsInteractiveOutput) {
|
|
110
|
+
clearScreen();
|
|
111
|
+
}
|
|
112
|
+
const width = surfaceWidth();
|
|
113
|
+
const activeAgents = activeAgentNamesForMode(config, mode);
|
|
114
|
+
const separator = mode === "ask" ? ", " : " <-> ";
|
|
115
|
+
const exampleAgents = exampleAgentsForMode(config, mode);
|
|
116
|
+
process.stdout.write([
|
|
117
|
+
"",
|
|
118
|
+
...padBlock([brandHeader(messages.tui.agentsTitle)]),
|
|
119
|
+
"",
|
|
120
|
+
...padBlock(card([
|
|
121
|
+
row(messages.tui.activeMode, messages.tui.modeValue(mode)),
|
|
122
|
+
row(messages.tui.activeAgents, activeAgents.length > 0 ? activeAgents.join(separator) : messages.tui.noValue),
|
|
123
|
+
"",
|
|
124
|
+
bold(messages.tui.availableAgents),
|
|
125
|
+
"",
|
|
126
|
+
...agentInventoryRows(config, messages),
|
|
127
|
+
"",
|
|
128
|
+
dim(`${messages.tui.example}: ${messages.tui.modeLabel(mode)} > ${messages.tui.agentsPrompt} > ${exampleAgents.join(" ")}`)
|
|
129
|
+
], width)),
|
|
130
|
+
""
|
|
131
|
+
].join("\n"));
|
|
132
|
+
}
|
|
133
|
+
/** Affiche l'aide rapide des roles disponibles. */
|
|
134
|
+
export function renderTuiRolesHelp(mode, messages, config) {
|
|
135
|
+
if (supportsInteractiveOutput) {
|
|
136
|
+
clearScreen();
|
|
137
|
+
}
|
|
138
|
+
const width = surfaceWidth();
|
|
139
|
+
const currentRoles = config ? roleLineForMode(config, mode, messages) : undefined;
|
|
140
|
+
const activeAgents = config ? activeAgentNamesForMode(config, mode) : [];
|
|
141
|
+
const expectedCount = activeAgents.length || (mode === "ask" ? 3 : 2);
|
|
142
|
+
const exampleRoles = exampleRolesForMode(mode, expectedCount);
|
|
143
|
+
process.stdout.write([
|
|
144
|
+
"",
|
|
145
|
+
...padBlock([brandHeader(messages.tui.rolesTitle)]),
|
|
146
|
+
"",
|
|
147
|
+
...padBlock(card([
|
|
148
|
+
...(activeAgents.length > 0 ? [row(messages.tui.activeAgents, activeAgents.join(mode === "ask" ? ", " : " <-> "))] : []),
|
|
149
|
+
...(currentRoles ? [row(messages.tui.currentConfig, currentRoles), ""] : []),
|
|
150
|
+
bold(messages.tui.availableRoles),
|
|
151
|
+
"",
|
|
152
|
+
row("implementer", messages.tui.roleImplementer),
|
|
153
|
+
row("critic", messages.tui.roleCritic),
|
|
154
|
+
row("architect", messages.tui.roleArchitect),
|
|
155
|
+
row("scout", messages.tui.roleScout),
|
|
156
|
+
row("reviewer", messages.tui.roleReviewer),
|
|
157
|
+
row("summarizer", messages.tui.roleSummarizer),
|
|
158
|
+
"",
|
|
159
|
+
dim(`${messages.tui.example}: ${messages.tui.modeLabel(mode)} > ${messages.tui.rolesPrompt} > ${exampleRoles.join(" ")}`)
|
|
160
|
+
], Math.min(width, 82))),
|
|
161
|
+
""
|
|
162
|
+
].join("\n"));
|
|
163
|
+
}
|
|
164
|
+
/** Affiche les derniers exports Palabre disponibles. */
|
|
165
|
+
export function renderTuiHistory(entries, messages) {
|
|
166
|
+
if (supportsInteractiveOutput) {
|
|
167
|
+
clearScreen();
|
|
168
|
+
}
|
|
169
|
+
const width = surfaceWidth();
|
|
170
|
+
const entryRows = entries.length === 0
|
|
171
|
+
? [dim(messages.tui.historyEmpty)]
|
|
172
|
+
: entries.flatMap((entry) => {
|
|
173
|
+
const folderPath = path.dirname(entry.path);
|
|
174
|
+
const folderLabel = folderPath === "." ? dirnamePortable(entry.path) : folderPath;
|
|
175
|
+
return [
|
|
176
|
+
row(messages.tui.historyMode(entry.mode), entry.topic),
|
|
177
|
+
row(messages.tui.activeAgents, entry.agents || messages.tui.noValue),
|
|
178
|
+
...(entry.count ? [row(messages.tui.historyCount(entry.mode), entry.count)] : []),
|
|
179
|
+
row(messages.tui.historyFile, terminalLink(entry.path, compactFileName(entry.fileName, width - 24))),
|
|
180
|
+
row(messages.tui.folder, terminalLink(folderPath, compactPath(folderLabel, width - 24))),
|
|
181
|
+
...(entry.date ? [row("Date", entry.date)] : []),
|
|
182
|
+
""
|
|
183
|
+
];
|
|
184
|
+
}).slice(0, -1);
|
|
185
|
+
process.stdout.write([
|
|
186
|
+
"",
|
|
187
|
+
...padBlock([brandHeader(messages.tui.historyTitle)]),
|
|
188
|
+
"",
|
|
189
|
+
...padBlock(panel([
|
|
190
|
+
...entryRows,
|
|
191
|
+
"",
|
|
192
|
+
dim(messages.tui.historyOpenHint)
|
|
193
|
+
], width)),
|
|
194
|
+
""
|
|
195
|
+
].join("\n"));
|
|
196
|
+
}
|
|
197
|
+
/** Affiche l'ecran de config natif TUI, adapte au mode courant. */
|
|
198
|
+
export function renderTuiConfig(config, configPath, mode, messages, state = {}) {
|
|
199
|
+
if (supportsInteractiveOutput) {
|
|
200
|
+
clearScreen();
|
|
201
|
+
}
|
|
202
|
+
const width = surfaceWidth();
|
|
203
|
+
const defaults = config.defaults ?? {};
|
|
204
|
+
const debateAgents = defaults.agentA && defaults.agentB ? `${defaults.agentA} <-> ${defaults.agentB}` : messages.tui.noValue;
|
|
205
|
+
const askAgents = defaults.askAgents && defaults.askAgents.length > 0
|
|
206
|
+
? defaults.askAgents.join(", ")
|
|
207
|
+
: debateAgents.replace(" <-> ", ", ");
|
|
208
|
+
const debateRoles = defaults.agentA && defaults.agentB ? `${roleFor(config, defaults.agentA, messages)} <-> ${roleFor(config, defaults.agentB, messages)}` : messages.tui.noValue;
|
|
209
|
+
const askRoles = roleLineForMode(config, "ask", messages);
|
|
210
|
+
const summary = mode === "ask"
|
|
211
|
+
? defaults.askSummaryAgent ?? defaults.summaryAgent ?? messages.tui.lastAskAgent
|
|
212
|
+
: defaults.summaryAgent ?? defaults.agentB ?? messages.tui.noValue;
|
|
213
|
+
const ollamaAgent = config.agents["ollama-local"];
|
|
214
|
+
const ollamaModel = ollamaAgent?.type === "ollama" ? ollamaAgent.model : undefined;
|
|
215
|
+
const ollamaUrl = ollamaAgent?.type === "ollama" ? ollamaAgent.baseUrl ?? DEFAULT_OLLAMA_BASE_URL : undefined;
|
|
216
|
+
const ollamaEffectiveUrl = ollamaUrl ? safeEffectiveOllamaUrl(ollamaUrl) : undefined;
|
|
217
|
+
const generalBox = card(rows([
|
|
218
|
+
[messages.tui.activeMode, messages.tui.modeValue(mode)],
|
|
219
|
+
[messages.tui.configFile, configPath],
|
|
220
|
+
[messages.tui.interface, defaults.interface ?? "tui"],
|
|
221
|
+
[messages.tui.language, config.language ?? "fr"],
|
|
222
|
+
[messages.tui.availableAgentsShort, agentInventoryLine(config, messages)]
|
|
223
|
+
]), width, messages.tui.configSectionGeneral);
|
|
224
|
+
const ollamaBox = ollamaModel
|
|
225
|
+
? card(rows([
|
|
226
|
+
[messages.tui.ollamaModel, ollamaModel],
|
|
227
|
+
...(ollamaUrl ? [[messages.tui.ollamaUrl, ollamaUrl]] : []),
|
|
228
|
+
...(ollamaEffectiveUrl && ollamaEffectiveUrl !== ollamaUrl ? [[messages.tui.ollamaUrlEffective, ollamaEffectiveUrl]] : [])
|
|
229
|
+
]), width, "Ollama")
|
|
230
|
+
: [];
|
|
231
|
+
const sessionEntries = mode === "ask"
|
|
232
|
+
? [
|
|
233
|
+
[messages.tui.activeAgents, askAgents],
|
|
234
|
+
[messages.tui.roles, askRoles],
|
|
235
|
+
[messages.tui.summary, summary]
|
|
236
|
+
]
|
|
237
|
+
: [
|
|
238
|
+
[messages.tui.activeAgents, debateAgents],
|
|
239
|
+
[messages.tui.roles, debateRoles],
|
|
240
|
+
[messages.tui.summary, summary],
|
|
241
|
+
[messages.tui.responses, String(defaults.turns ?? "?")]
|
|
242
|
+
];
|
|
243
|
+
const sessionBox = card(rows(sessionEntries), width, messages.tui.modeValue(mode));
|
|
244
|
+
const commandRows = [
|
|
245
|
+
bold(messages.tui.availableCommands),
|
|
246
|
+
"",
|
|
247
|
+
...(mode === "ask"
|
|
248
|
+
? [
|
|
249
|
+
row("/agents", messages.tui.askAgentsUsage),
|
|
250
|
+
row("/roles", messages.tui.rolesUsage),
|
|
251
|
+
row("/summary", messages.tui.summaryUsage)
|
|
252
|
+
]
|
|
253
|
+
: [
|
|
254
|
+
row("/agents", messages.tui.debateAgentsUsage),
|
|
255
|
+
row("/roles", messages.tui.rolesUsage),
|
|
256
|
+
row("/turns", messages.tui.turnsUsage),
|
|
257
|
+
row("/summary", messages.tui.summaryUsage)
|
|
258
|
+
]),
|
|
259
|
+
row("/mode", messages.tui.modeConfigCommand),
|
|
260
|
+
...(ollamaModel ? [
|
|
261
|
+
row("/ollama", messages.tui.ollamaInfoCommand),
|
|
262
|
+
row("/ollama-model", messages.tui.ollamaModelUsage),
|
|
263
|
+
row("/ollama-url", messages.tui.ollamaUrlCommand),
|
|
264
|
+
row("/ollama-sync", messages.tui.ollamaSyncCommand)
|
|
265
|
+
] : []),
|
|
266
|
+
row("/interface", messages.tui.interfaceUsage),
|
|
267
|
+
row("/language", messages.tui.languageUsage),
|
|
268
|
+
"",
|
|
269
|
+
row("/home", messages.tui.backCommand),
|
|
270
|
+
row("/quit", messages.tui.quitCommand)
|
|
271
|
+
];
|
|
272
|
+
const lines = [
|
|
273
|
+
"",
|
|
274
|
+
...padBlock([brandHeader(messages.tui.configTitle)]),
|
|
275
|
+
"",
|
|
276
|
+
...padBlock(generalBox),
|
|
277
|
+
"",
|
|
278
|
+
...padBlock(sessionBox),
|
|
279
|
+
...(ollamaBox.length > 0 ? ["", ...padBlock(ollamaBox)] : []),
|
|
280
|
+
"",
|
|
281
|
+
...padBlock(commandRows),
|
|
282
|
+
...(state.message ? ["", ...padBlock([state.message])] : [])
|
|
283
|
+
];
|
|
284
|
+
process.stdout.write(lines.join("\n") + "\n");
|
|
285
|
+
}
|
|
286
|
+
/** Résout l'URL Ollama effective sans lever : retombe sur `OLLAMA_HOST` ou l'URL config brute. */
|
|
287
|
+
function safeEffectiveOllamaUrl(configUrl) {
|
|
288
|
+
try {
|
|
289
|
+
return resolveOllamaBaseUrl({ configUrl });
|
|
290
|
+
}
|
|
291
|
+
catch {
|
|
292
|
+
return process.env.OLLAMA_HOST?.trim() || configUrl;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
function roleFor(config, agent, messages) {
|
|
296
|
+
return config.agents[agent]?.role ?? messages.tui.noValue;
|
|
297
|
+
}
|
|
298
|
+
function roleLineForMode(config, mode, messages) {
|
|
299
|
+
const agents = activeAgentNamesForMode(config, mode);
|
|
300
|
+
if (mode === "ask") {
|
|
301
|
+
return agents.length > 0 ? agents.map((agent) => roleFor(config, agent, messages)).join(", ") : messages.tui.noValue;
|
|
302
|
+
}
|
|
303
|
+
return agents.length === 2
|
|
304
|
+
? `${roleFor(config, agents[0], messages)} <-> ${roleFor(config, agents[1], messages)}`
|
|
305
|
+
: messages.tui.noValue;
|
|
306
|
+
}
|
|
307
|
+
function activeAgentNamesForMode(config, mode) {
|
|
308
|
+
const defaults = config.defaults ?? {};
|
|
309
|
+
if (mode === "ask") {
|
|
310
|
+
const agents = defaults.askAgents && defaults.askAgents.length > 0
|
|
311
|
+
? defaults.askAgents
|
|
312
|
+
: [defaults.agentA, defaults.agentB].filter((agent) => Boolean(agent));
|
|
313
|
+
return agents.filter((agent) => Boolean(config.agents[agent]) && !isRetiredAgentName(agent));
|
|
314
|
+
}
|
|
315
|
+
return [defaults.agentA, defaults.agentB].filter((agent) => typeof agent === "string" && Boolean(config.agents[agent]) && !isRetiredAgentName(agent));
|
|
316
|
+
}
|
|
317
|
+
function agentInventoryLine(config, messages) {
|
|
318
|
+
const agents = Object.entries(config.agents)
|
|
319
|
+
.filter(([name]) => !isRetiredAgentName(name))
|
|
320
|
+
.map(([name]) => name)
|
|
321
|
+
.sort();
|
|
322
|
+
return agents.length > 0 ? agents.map(agentLabel).join(", ") : messages.tui.noValue;
|
|
323
|
+
}
|
|
324
|
+
function agentInventoryRows(config, messages) {
|
|
325
|
+
const entries = Object.entries(config.agents)
|
|
326
|
+
.filter(([name]) => !isRetiredAgentName(name))
|
|
327
|
+
.sort(([agentA], [agentB]) => agentA.localeCompare(agentB));
|
|
328
|
+
if (entries.length === 0) {
|
|
329
|
+
return [dim(messages.tui.noConfiguredAgents)];
|
|
330
|
+
}
|
|
331
|
+
return entries.map(([name, agent]) => row(name, `${agent.type} ${dim("·")} ${agent.role}`));
|
|
332
|
+
}
|
|
333
|
+
function exampleAgentsForMode(config, mode) {
|
|
334
|
+
const activeAgents = activeAgentNamesForMode(config, mode);
|
|
335
|
+
if (activeAgents.length > 0) {
|
|
336
|
+
return activeAgents;
|
|
337
|
+
}
|
|
338
|
+
const available = Object.keys(config.agents).filter((agent) => !isRetiredAgentName(agent)).sort();
|
|
339
|
+
return mode === "ask" ? available.slice(0, 3) : available.slice(0, 2);
|
|
340
|
+
}
|
|
341
|
+
function documentationUrl(config) {
|
|
342
|
+
return `https://palab.re/${config.language === "en" ? "en" : "fr"}`;
|
|
343
|
+
}
|
|
344
|
+
function exampleRolesForMode(mode, count) {
|
|
345
|
+
const roles = mode === "ask"
|
|
346
|
+
? ["critic", "implementer", "scout", "architect"]
|
|
347
|
+
: ["implementer", "critic"];
|
|
348
|
+
while (roles.length < count) {
|
|
349
|
+
roles.push("reviewer");
|
|
350
|
+
}
|
|
351
|
+
return roles.slice(0, count);
|
|
352
|
+
}
|