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.
- package/README.md +13 -15
- package/dist/adapters/cli-pty.js +1 -1
- package/dist/adapters/cli.js +33 -3
- package/dist/adapters/index.js +2 -2
- package/dist/adapters/ollama.js +9 -7
- package/dist/agentRegistry.js +7 -2
- package/dist/args.js +5 -2
- package/dist/config.js +33 -25
- package/dist/context.js +5 -1
- package/dist/discovery.js +30 -9
- package/dist/doctor.js +19 -5
- package/dist/history.js +85 -0
- package/dist/index.js +450 -205
- package/dist/messages/common.js +6 -0
- package/dist/messages/config.js +2 -0
- package/dist/messages/doctor.js +2 -2
- package/dist/messages/help.js +64 -8
- package/dist/messages/init.js +2 -2
- package/dist/messages/tui.js +78 -28
- package/dist/ollamaUrl.js +76 -0
- package/dist/orchestrator.js +34 -13
- package/dist/presets.js +25 -52
- package/dist/renderers/tui.js +242 -77
- package/dist/tuiState.js +32 -0
- package/package.json +1 -1
- package/palabre.config.example.json +0 -17
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { assertRunnableConfig, configExists, createConfigFromDiscovery, DEFAULT_CONFIG_PATH, GLOBAL_CONFIG_PATH, loadConfig, resolveDefaultConfigPath, resolveOutputDir, setOllamaModel,
|
|
2
|
+
import { assertRunnableConfig, configExists, createConfigFromDiscovery, DEFAULT_CONFIG_PATH, GLOBAL_CONFIG_PATH, loadConfig, resolveDefaultConfigPath, resolveOutputDir, setOllamaBaseUrl, setOllamaModel, syncDetectedAgentsDetailed, syncOllamaModel, writeExampleConfig } from "./config.js";
|
|
3
3
|
import { loadProjectInputs } from "./context.js";
|
|
4
4
|
import { buildContextScan } from "./contextScan.js";
|
|
5
5
|
import { discoverLocalTools } from "./discovery.js";
|
|
@@ -10,17 +10,20 @@ import { createTranslator, DEFAULT_LANGUAGE, parseLanguage, resolveLanguage } fr
|
|
|
10
10
|
import { DEFAULT_TURNS, parseTurnsFlag, turnsOrDefault, validateTurns } from "./limits.js";
|
|
11
11
|
import { formatAgentPrompt } from "./prompt.js";
|
|
12
12
|
import { runNewWizard } from "./new.js";
|
|
13
|
-
import { listPresetNames, listPresetsWithAvailability, resolvePreset } from "./presets.js";
|
|
13
|
+
import { listAgentsWithAvailability, listPresetNames, listPresetsWithAvailability, resolvePreset } from "./presets.js";
|
|
14
|
+
import { listHistoryEntries } from "./history.js";
|
|
14
15
|
import { createConsoleRenderer } from "./renderers/console.js";
|
|
15
16
|
import { createNdjsonRenderer } from "./renderers/ndjson.js";
|
|
16
|
-
import { createTuiRenderer, promptTuiAgentsWizard, promptTuiConfigCommand, promptTuiHomeTopic, promptTuiRolesWizard, renderTuiConfig, renderTuiHelp, renderTuiHome } from "./renderers/tui.js";
|
|
17
|
+
import { createTuiRenderer, promptTuiAgentsWizard, promptTuiConfigCommand, promptTuiHomeTopic, promptTuiRolesWizard, renderTuiConfig, renderTuiHelp, renderTuiHistory, renderTuiHome, renderTuiUpdate } from "./renderers/tui.js";
|
|
17
18
|
import { MAX_ASK_AGENTS, runAsk, runDebate } from "./orchestrator.js";
|
|
18
19
|
import { writeDebateMarkdown } from "./output.js";
|
|
19
20
|
import { applySourceUpdate, formatUpdateInstructions, getUpdateInfo } from "./update.js";
|
|
20
21
|
import { createSessionContext } from "./session.js";
|
|
21
22
|
import { getStringListFlag, parseArgs } from "./args.js";
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
23
|
+
import { askAgentSeedsForMode, clearTuiRunOverrides } from "./tuiState.js";
|
|
24
|
+
import { detectedAgentNames, detectionForCommand, isRetiredAgentName } from "./agentRegistry.js";
|
|
25
|
+
import { configuredOllamaTargets, DEFAULT_OLLAMA_BASE_URL, normalizeOllamaBaseUrl, OllamaUrlError, resolveOllamaBaseUrl } from "./ollamaUrl.js";
|
|
26
|
+
import { compareSemver, getLatestPackageVersion, getPackageVersion } from "./version.js";
|
|
24
27
|
/** Point d'entrée principal du CLI Palabre. Dispatche vers la commande appropriée selon les arguments. */
|
|
25
28
|
async function main() {
|
|
26
29
|
const rawArgs = process.argv.slice(2);
|
|
@@ -53,6 +56,10 @@ async function main() {
|
|
|
53
56
|
await runPresetsCommand(parsed.flags);
|
|
54
57
|
return;
|
|
55
58
|
}
|
|
59
|
+
if (parsed.command === "history" || parsed.command === "historique") {
|
|
60
|
+
await runHistoryCommand(parsed.flags);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
56
63
|
if (parsed.command === "context") {
|
|
57
64
|
await runContextCommand(parsed.flags, parsed.positionals);
|
|
58
65
|
return;
|
|
@@ -82,7 +89,9 @@ async function main() {
|
|
|
82
89
|
console.log(startupMessages.init.configExists(initConfigPath));
|
|
83
90
|
return;
|
|
84
91
|
}
|
|
85
|
-
const discovery = await discoverLocalTools(
|
|
92
|
+
const discovery = await discoverLocalTools({
|
|
93
|
+
ollamaUrl: optionalString(parsed.flags["ollama-url"])
|
|
94
|
+
});
|
|
86
95
|
const config = createConfigFromDiscovery(discovery);
|
|
87
96
|
config.language = resolveLanguage({
|
|
88
97
|
explicitLanguage: optionalString(parsed.flags.language),
|
|
@@ -95,200 +104,254 @@ async function main() {
|
|
|
95
104
|
return;
|
|
96
105
|
}
|
|
97
106
|
const configPath = optionalString(parsed.flags.config) ?? await resolveDefaultConfigPath();
|
|
107
|
+
let config;
|
|
108
|
+
let tuiNotice;
|
|
98
109
|
if (!(await configExists(configPath))) {
|
|
99
|
-
|
|
110
|
+
config = createConfigFromDiscovery(await discoverLocalTools({
|
|
111
|
+
ollamaUrl: optionalString(parsed.flags["ollama-url"])
|
|
112
|
+
}));
|
|
100
113
|
config.language = resolveLanguage({
|
|
101
114
|
explicitLanguage: optionalString(parsed.flags.language),
|
|
102
115
|
configLanguage: config.language
|
|
103
116
|
});
|
|
104
117
|
const messages = createTranslator(config.language);
|
|
105
118
|
await writeExampleConfig(configPath, config);
|
|
106
|
-
|
|
107
|
-
|
|
119
|
+
if (!shouldOpenTuiHome(parsed)) {
|
|
120
|
+
console.log(messages.init.editConfigThenRerun(configPath));
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
tuiNotice = messages.init.configCreated(configPath);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
config = await loadConfig(configPath);
|
|
108
127
|
}
|
|
109
|
-
const config = await loadConfig(configPath);
|
|
110
128
|
let language = resolveLanguage({
|
|
111
129
|
explicitLanguage: optionalString(parsed.flags.language),
|
|
112
130
|
configLanguage: config.language
|
|
113
131
|
});
|
|
114
132
|
let messages = createTranslator(language);
|
|
115
133
|
assertRunnableConfig(config, messages, configPath);
|
|
134
|
+
let stayInTuiAfterSession = false;
|
|
135
|
+
let hasCompletedTuiSession = false;
|
|
136
|
+
let resetTuiRunOverridesOnNextTopic = false;
|
|
137
|
+
let tuiMode = config.defaults?.mode ?? "debate";
|
|
138
|
+
let tuiVersion = "";
|
|
139
|
+
let tuiLatestVersion;
|
|
140
|
+
const handleTuiHomeInput = async (tuiInput) => {
|
|
141
|
+
if (!tuiInput) {
|
|
142
|
+
return "quit";
|
|
143
|
+
}
|
|
144
|
+
if (tuiInput.kind === "help") {
|
|
145
|
+
renderTuiHelp(messages);
|
|
146
|
+
const nextInput = await promptTuiHomeTopic(tuiMode, messages);
|
|
147
|
+
return handleTuiHomeInput(nextInput);
|
|
148
|
+
}
|
|
149
|
+
if (tuiInput.kind === "history") {
|
|
150
|
+
renderTuiHistory(await listHistoryEntries(resolveOutputDir(config.outputDir)), messages);
|
|
151
|
+
const nextInput = await promptTuiHomeTopic(tuiMode, messages);
|
|
152
|
+
return handleTuiHomeInput(nextInput);
|
|
153
|
+
}
|
|
154
|
+
if (tuiInput.kind === "update") {
|
|
155
|
+
const info = await getUpdateInfo(tuiVersion);
|
|
156
|
+
renderTuiUpdate(formatUpdateInstructions(info, messages), messages);
|
|
157
|
+
const nextInput = await promptTuiHomeTopic(tuiMode, messages);
|
|
158
|
+
return handleTuiHomeInput(nextInput);
|
|
159
|
+
}
|
|
160
|
+
if (tuiInput.kind === "home") {
|
|
161
|
+
return "continue";
|
|
162
|
+
}
|
|
163
|
+
if (tuiInput.kind === "roles") {
|
|
164
|
+
const result = await runTuiRolesWizard(configPath, config, messages, tuiMode, tuiInput.roles);
|
|
165
|
+
if (result.quit)
|
|
166
|
+
return "quit";
|
|
167
|
+
tuiNotice = result.notice;
|
|
168
|
+
return "continue";
|
|
169
|
+
}
|
|
170
|
+
if (tuiInput.kind === "agents") {
|
|
171
|
+
const result = await runTuiAgentsWizard(configPath, config, messages, tuiMode, tuiInput.agents);
|
|
172
|
+
if (result.quit)
|
|
173
|
+
return "quit";
|
|
174
|
+
tuiNotice = result.notice;
|
|
175
|
+
resetTuiRunOverridesOnNextTopic ||= Boolean(result.changedRunDefaults);
|
|
176
|
+
return "continue";
|
|
177
|
+
}
|
|
178
|
+
if (tuiInput.kind === "mode") {
|
|
179
|
+
tuiMode = tuiInput.mode;
|
|
180
|
+
return "continue";
|
|
181
|
+
}
|
|
182
|
+
if (tuiInput.kind === "config") {
|
|
183
|
+
const result = await runTuiConfigLoop(configPath, config, messages, tuiMode);
|
|
184
|
+
if (result.quit)
|
|
185
|
+
return "quit";
|
|
186
|
+
tuiMode = result.mode;
|
|
187
|
+
resetTuiRunOverridesOnNextTopic ||= result.changedRunDefaults;
|
|
188
|
+
language = resolveLanguage({ explicitLanguage: optionalString(parsed.flags.language), configLanguage: config.language });
|
|
189
|
+
messages = createTranslator(language);
|
|
190
|
+
return "continue";
|
|
191
|
+
}
|
|
192
|
+
if (tuiInput.kind === "new") {
|
|
193
|
+
parsed.command = "new";
|
|
194
|
+
parsed.commandExplicit = true;
|
|
195
|
+
delete parsed.flags.topic;
|
|
196
|
+
return "run";
|
|
197
|
+
}
|
|
198
|
+
if (tuiInput.kind === "retry") {
|
|
199
|
+
if (!optionalString(parsed.flags.topic)) {
|
|
200
|
+
tuiNotice = messages.tui.retryUnavailable;
|
|
201
|
+
return "continue";
|
|
202
|
+
}
|
|
203
|
+
return "retry";
|
|
204
|
+
}
|
|
205
|
+
parsed.command = "";
|
|
206
|
+
parsed.commandExplicit = false;
|
|
207
|
+
if (hasCompletedTuiSession || resetTuiRunOverridesOnNextTopic) {
|
|
208
|
+
clearTuiRunOverrides(parsed.flags);
|
|
209
|
+
resetTuiRunOverridesOnNextTopic = false;
|
|
210
|
+
}
|
|
211
|
+
parsed.flags.topic = tuiInput.topic;
|
|
212
|
+
return "run";
|
|
213
|
+
};
|
|
116
214
|
if (shouldOpenTuiHome(parsed)) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
215
|
+
const [syncResult, currentVersion, latestVersion] = await Promise.all([
|
|
216
|
+
syncInteractiveDetectedAgents(configPath, config),
|
|
217
|
+
getPackageVersion(),
|
|
218
|
+
getLatestPackageVersion()
|
|
219
|
+
]);
|
|
220
|
+
if (!tuiNotice && syncResult.addedAgents.length > 0) {
|
|
221
|
+
tuiNotice = messages.config.syncAdded(configPath, syncResult.addedAgents.join(", "));
|
|
222
|
+
}
|
|
223
|
+
stayInTuiAfterSession = true;
|
|
224
|
+
tuiVersion = currentVersion;
|
|
225
|
+
tuiLatestVersion = latestVersion && compareSemver(currentVersion, latestVersion) < 0
|
|
226
|
+
? latestVersion
|
|
227
|
+
: undefined;
|
|
120
228
|
for (;;) {
|
|
121
|
-
renderTuiHome(config, configPath, messages, { mode: tuiMode, version: tuiVersion });
|
|
229
|
+
renderTuiHome(config, configPath, messages, { mode: tuiMode, version: tuiVersion, latestVersion: tuiLatestVersion });
|
|
122
230
|
const tuiInput = await promptTuiHomeTopic(tuiMode, messages, { notice: tuiNotice });
|
|
123
231
|
tuiNotice = undefined;
|
|
124
|
-
|
|
232
|
+
const action = await handleTuiHomeInput(tuiInput);
|
|
233
|
+
if (action === "quit")
|
|
125
234
|
return;
|
|
126
|
-
|
|
127
|
-
if (tuiInput.kind === "help") {
|
|
128
|
-
renderTuiHelp(messages);
|
|
129
|
-
const nextInput = await promptTuiHomeTopic(tuiMode, messages);
|
|
130
|
-
if (!nextInput) {
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
if (nextInput.kind === "help") {
|
|
134
|
-
continue;
|
|
135
|
-
}
|
|
136
|
-
if (nextInput.kind === "roles") {
|
|
137
|
-
const result = await runTuiRolesWizard(configPath, config, messages, tuiMode, nextInput.roles);
|
|
138
|
-
if (result.quit)
|
|
139
|
-
return;
|
|
140
|
-
tuiNotice = result.notice;
|
|
141
|
-
continue;
|
|
142
|
-
}
|
|
143
|
-
if (nextInput.kind === "agents") {
|
|
144
|
-
const result = await runTuiAgentsWizard(configPath, config, messages, tuiMode, nextInput.agents);
|
|
145
|
-
if (result.quit)
|
|
146
|
-
return;
|
|
147
|
-
tuiNotice = result.notice;
|
|
148
|
-
continue;
|
|
149
|
-
}
|
|
150
|
-
if (nextInput.kind === "mode") {
|
|
151
|
-
tuiMode = nextInput.mode;
|
|
152
|
-
continue;
|
|
153
|
-
}
|
|
154
|
-
if (nextInput.kind === "config") {
|
|
155
|
-
const result = await runTuiConfigLoop(configPath, config, messages, tuiMode);
|
|
156
|
-
if (result.quit)
|
|
157
|
-
return;
|
|
158
|
-
tuiMode = result.mode;
|
|
159
|
-
language = resolveLanguage({ explicitLanguage: optionalString(parsed.flags.language), configLanguage: config.language });
|
|
160
|
-
messages = createTranslator(language);
|
|
161
|
-
continue;
|
|
162
|
-
}
|
|
163
|
-
if (nextInput.kind === "new") {
|
|
164
|
-
parsed.command = "new";
|
|
165
|
-
parsed.commandExplicit = true;
|
|
166
|
-
}
|
|
167
|
-
else {
|
|
168
|
-
parsed.flags.topic = nextInput.topic;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
else if (tuiInput.kind === "mode") {
|
|
172
|
-
tuiMode = tuiInput.mode;
|
|
173
|
-
continue;
|
|
174
|
-
}
|
|
175
|
-
else if (tuiInput.kind === "roles") {
|
|
176
|
-
const result = await runTuiRolesWizard(configPath, config, messages, tuiMode, tuiInput.roles);
|
|
177
|
-
if (result.quit)
|
|
178
|
-
return;
|
|
179
|
-
tuiNotice = result.notice;
|
|
180
|
-
continue;
|
|
181
|
-
}
|
|
182
|
-
else if (tuiInput.kind === "agents") {
|
|
183
|
-
const result = await runTuiAgentsWizard(configPath, config, messages, tuiMode, tuiInput.agents);
|
|
184
|
-
if (result.quit)
|
|
185
|
-
return;
|
|
186
|
-
tuiNotice = result.notice;
|
|
235
|
+
if (action === "continue")
|
|
187
236
|
continue;
|
|
237
|
+
parsed.flags.mode = tuiMode;
|
|
238
|
+
parsed.flags.renderer = "tui";
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
for (;;) {
|
|
243
|
+
if (parsed.command === "new") {
|
|
244
|
+
const selection = await runNewWizard(config, messages);
|
|
245
|
+
if (!selection) {
|
|
246
|
+
console.log(messages.new.cancelled);
|
|
247
|
+
return;
|
|
188
248
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
249
|
+
parsed.flags["agent-a"] = selection.agentA;
|
|
250
|
+
parsed.flags["agent-b"] = selection.agentB;
|
|
251
|
+
parsed.flags.topic = selection.topic;
|
|
252
|
+
if (selection.modelA)
|
|
253
|
+
parsed.flags["model-a"] = selection.modelA;
|
|
254
|
+
if (selection.modelB)
|
|
255
|
+
parsed.flags["model-b"] = selection.modelB;
|
|
256
|
+
if (selection.turns)
|
|
257
|
+
parsed.flags.turns = String(selection.turns);
|
|
258
|
+
if (selection.summaryAgent)
|
|
259
|
+
parsed.flags["summary-agent"] = selection.summaryAgent;
|
|
260
|
+
if (selection.summaryModel)
|
|
261
|
+
parsed.flags["summary-model"] = selection.summaryModel;
|
|
262
|
+
if (selection.summaryEnabled === false)
|
|
263
|
+
parsed.flags["no-summary"] = true;
|
|
264
|
+
if (selection.showPrompt)
|
|
265
|
+
parsed.flags["show-prompt"] = true;
|
|
266
|
+
if (selection.plainOutput)
|
|
267
|
+
parsed.flags.plain = true;
|
|
268
|
+
if (selection.files.length > 0)
|
|
269
|
+
parsed.flags.files = selection.files;
|
|
270
|
+
if (selection.context.length > 0)
|
|
271
|
+
parsed.flags.context = selection.context;
|
|
272
|
+
if (selection.mode)
|
|
273
|
+
parsed.flags.mode = selection.mode;
|
|
274
|
+
if (selection.askAgents && selection.askAgents.length > 0)
|
|
275
|
+
parsed.flags.agents = selection.askAgents;
|
|
276
|
+
parsed.command = "";
|
|
277
|
+
parsed.commandExplicit = false;
|
|
278
|
+
}
|
|
279
|
+
const topic = optionalString(parsed.flags.topic) ?? "";
|
|
280
|
+
const context = await loadProjectInputs(getStringListFlag(parsed.flags.files), getStringListFlag(parsed.flags.context), process.cwd(), messages);
|
|
281
|
+
const presetName = optionalString(parsed.flags.preset);
|
|
282
|
+
const preset = presetName ? resolvePreset(presetName, messages) : undefined;
|
|
283
|
+
if (!topic) {
|
|
284
|
+
throw new Error(messages.common.topicRequired);
|
|
285
|
+
}
|
|
286
|
+
const mode = parseModeFlag(optionalString(parsed.flags.mode) ?? config.defaults?.mode, messages);
|
|
287
|
+
const explicitAskAgents = getStringListFlag(parsed.flags.agents);
|
|
288
|
+
const askAgentSeeds = askAgentSeedsForMode(mode, explicitAskAgents, config.defaults?.askAgents);
|
|
289
|
+
const agentA = resolveAgentName("agent A", parsed.flags["agent-a"], preset?.agentA, askAgentSeeds[0] ?? config.defaults?.agentA, messages);
|
|
290
|
+
const agentB = resolveAgentName("agent B", parsed.flags["agent-b"], preset?.agentB, askAgentSeeds[1] ?? askAgentSeeds[0] ?? config.defaults?.agentB, messages);
|
|
291
|
+
const askAgents = mode === "ask" ? resolveAskAgents(explicitAskAgents, config.defaults?.askAgents, [agentA, agentB], messages) : undefined;
|
|
292
|
+
const options = {
|
|
293
|
+
mode,
|
|
294
|
+
language,
|
|
295
|
+
topic,
|
|
296
|
+
agentA,
|
|
297
|
+
agentB,
|
|
298
|
+
askAgents,
|
|
299
|
+
turns: parseTurnsFlag(parsed.flags.turns, config.defaults?.turns ?? DEFAULT_TURNS, "--turns", messages),
|
|
300
|
+
session: createSessionContext(),
|
|
301
|
+
files: context.files,
|
|
302
|
+
modelA: optionalString(parsed.flags["model-a"]),
|
|
303
|
+
modelB: optionalString(parsed.flags["model-b"]),
|
|
304
|
+
ollamaUrl: optionalString(parsed.flags["ollama-url"])
|
|
305
|
+
? normalizeOllamaBaseUrl(optionalString(parsed.flags["ollama-url"]))
|
|
306
|
+
: undefined,
|
|
307
|
+
pullModels: Boolean(parsed.flags["pull-models"]),
|
|
308
|
+
summaryAgent: resolveSummaryAgentOption(parsed.flags["summary-agent"], config.defaults, mode),
|
|
309
|
+
summaryModel: optionalString(parsed.flags["summary-model"]),
|
|
310
|
+
summaryEnabled: !parsed.flags["no-summary"],
|
|
311
|
+
earlyStopOnAgreement: !parsed.flags["no-early-stop"],
|
|
312
|
+
plainOutput: Boolean(parsed.flags.plain || parsed.flags.terminal),
|
|
313
|
+
signal: debateAbortSignal()
|
|
314
|
+
};
|
|
315
|
+
if (parsed.flags["show-prompt"]) {
|
|
316
|
+
printContextWarnings(context.warnings, messages);
|
|
317
|
+
printPromptPreview(config, options, language, messages);
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
process.exitCode = undefined;
|
|
321
|
+
const renderer = createRendererFromFlags(parsed.flags, options.plainOutput, config.defaults?.interface, messages);
|
|
322
|
+
context.warnings.forEach((warning) => renderer.warning(warning));
|
|
323
|
+
const result = options.mode === "ask"
|
|
324
|
+
? await runAsk(config, options, renderer, messages)
|
|
325
|
+
: await runDebate(config, options, renderer, messages);
|
|
326
|
+
const outputPath = await writeDebateMarkdown(resolveOutputDir(config.outputDir), result.options, result.messages, result.summary, result.stopReason, messages, result.failure);
|
|
327
|
+
renderer.done(outputPath);
|
|
328
|
+
hasCompletedTuiSession = stayInTuiAfterSession;
|
|
329
|
+
if (result.failure) {
|
|
330
|
+
process.exitCode = result.failure.kind === "cancelled" ? 130 : 1;
|
|
331
|
+
}
|
|
332
|
+
if (!stayInTuiAfterSession) {
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
tuiMode = mode;
|
|
336
|
+
for (;;) {
|
|
337
|
+
const nextInput = await promptTuiHomeTopic(tuiMode, messages, { notice: tuiNotice });
|
|
338
|
+
tuiNotice = undefined;
|
|
339
|
+
const action = await handleTuiHomeInput(nextInput);
|
|
340
|
+
if (action === "quit")
|
|
341
|
+
return;
|
|
342
|
+
if (action === "continue") {
|
|
343
|
+
renderTuiHome(config, configPath, messages, { mode: tuiMode, version: tuiVersion, latestVersion: tuiLatestVersion });
|
|
196
344
|
continue;
|
|
197
345
|
}
|
|
198
|
-
|
|
199
|
-
parsed.
|
|
200
|
-
|
|
201
|
-
}
|
|
202
|
-
else {
|
|
203
|
-
parsed.flags.topic = tuiInput.topic;
|
|
346
|
+
if (action === "retry") {
|
|
347
|
+
parsed.flags.renderer = "tui";
|
|
348
|
+
break;
|
|
204
349
|
}
|
|
205
350
|
parsed.flags.mode = tuiMode;
|
|
206
351
|
parsed.flags.renderer = "tui";
|
|
207
352
|
break;
|
|
208
353
|
}
|
|
209
354
|
}
|
|
210
|
-
if (parsed.command === "new") {
|
|
211
|
-
const selection = await runNewWizard(config, messages);
|
|
212
|
-
if (!selection) {
|
|
213
|
-
console.log(messages.new.cancelled);
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
parsed.flags["agent-a"] = selection.agentA;
|
|
217
|
-
parsed.flags["agent-b"] = selection.agentB;
|
|
218
|
-
parsed.flags.topic = selection.topic;
|
|
219
|
-
if (selection.modelA)
|
|
220
|
-
parsed.flags["model-a"] = selection.modelA;
|
|
221
|
-
if (selection.modelB)
|
|
222
|
-
parsed.flags["model-b"] = selection.modelB;
|
|
223
|
-
if (selection.turns)
|
|
224
|
-
parsed.flags.turns = String(selection.turns);
|
|
225
|
-
if (selection.summaryAgent)
|
|
226
|
-
parsed.flags["summary-agent"] = selection.summaryAgent;
|
|
227
|
-
if (selection.summaryModel)
|
|
228
|
-
parsed.flags["summary-model"] = selection.summaryModel;
|
|
229
|
-
if (selection.summaryEnabled === false)
|
|
230
|
-
parsed.flags["no-summary"] = true;
|
|
231
|
-
if (selection.showPrompt)
|
|
232
|
-
parsed.flags["show-prompt"] = true;
|
|
233
|
-
if (selection.plainOutput)
|
|
234
|
-
parsed.flags.plain = true;
|
|
235
|
-
if (selection.files.length > 0)
|
|
236
|
-
parsed.flags.files = selection.files;
|
|
237
|
-
if (selection.context.length > 0)
|
|
238
|
-
parsed.flags.context = selection.context;
|
|
239
|
-
if (selection.mode)
|
|
240
|
-
parsed.flags.mode = selection.mode;
|
|
241
|
-
if (selection.askAgents && selection.askAgents.length > 0)
|
|
242
|
-
parsed.flags.agents = selection.askAgents;
|
|
243
|
-
}
|
|
244
|
-
const topic = optionalString(parsed.flags.topic) ?? "";
|
|
245
|
-
const context = await loadProjectInputs(getStringListFlag(parsed.flags.files), getStringListFlag(parsed.flags.context), process.cwd(), messages);
|
|
246
|
-
const presetName = optionalString(parsed.flags.preset);
|
|
247
|
-
const preset = presetName ? resolvePreset(presetName, messages) : undefined;
|
|
248
|
-
if (!topic) {
|
|
249
|
-
throw new Error(messages.common.topicRequired);
|
|
250
|
-
}
|
|
251
|
-
const mode = parseModeFlag(optionalString(parsed.flags.mode) ?? config.defaults?.mode, messages);
|
|
252
|
-
const explicitAskAgents = getStringListFlag(parsed.flags.agents);
|
|
253
|
-
const askAgentSeeds = explicitAskAgents.length > 0 ? explicitAskAgents : config.defaults?.askAgents ?? [];
|
|
254
|
-
const agentA = resolveAgentName("agent A", parsed.flags["agent-a"], preset?.agentA, askAgentSeeds[0] ?? config.defaults?.agentA, messages);
|
|
255
|
-
const agentB = resolveAgentName("agent B", parsed.flags["agent-b"], preset?.agentB, askAgentSeeds[1] ?? askAgentSeeds[0] ?? config.defaults?.agentB, messages);
|
|
256
|
-
const askAgents = mode === "ask" ? resolveAskAgents(explicitAskAgents, config.defaults?.askAgents, [agentA, agentB], messages) : undefined;
|
|
257
|
-
const options = {
|
|
258
|
-
mode,
|
|
259
|
-
language,
|
|
260
|
-
topic,
|
|
261
|
-
agentA,
|
|
262
|
-
agentB,
|
|
263
|
-
askAgents,
|
|
264
|
-
turns: parseTurnsFlag(parsed.flags.turns, config.defaults?.turns ?? DEFAULT_TURNS, "--turns", messages),
|
|
265
|
-
session: createSessionContext(),
|
|
266
|
-
files: context.files,
|
|
267
|
-
modelA: optionalString(parsed.flags["model-a"]),
|
|
268
|
-
modelB: optionalString(parsed.flags["model-b"]),
|
|
269
|
-
pullModels: Boolean(parsed.flags["pull-models"]),
|
|
270
|
-
summaryAgent: resolveSummaryAgentOption(parsed.flags["summary-agent"], config.defaults, mode),
|
|
271
|
-
summaryModel: optionalString(parsed.flags["summary-model"]),
|
|
272
|
-
summaryEnabled: !parsed.flags["no-summary"],
|
|
273
|
-
earlyStopOnAgreement: !parsed.flags["no-early-stop"],
|
|
274
|
-
plainOutput: Boolean(parsed.flags.plain || parsed.flags.terminal),
|
|
275
|
-
signal: debateAbortSignal()
|
|
276
|
-
};
|
|
277
|
-
if (parsed.flags["show-prompt"]) {
|
|
278
|
-
printContextWarnings(context.warnings, messages);
|
|
279
|
-
printPromptPreview(config, options, language, messages);
|
|
280
|
-
return;
|
|
281
|
-
}
|
|
282
|
-
const renderer = createRendererFromFlags(parsed.flags, options.plainOutput, config.defaults?.interface, messages);
|
|
283
|
-
context.warnings.forEach((warning) => renderer.warning(warning));
|
|
284
|
-
const result = options.mode === "ask"
|
|
285
|
-
? await runAsk(config, options, renderer, messages)
|
|
286
|
-
: await runDebate(config, options, renderer, messages);
|
|
287
|
-
const outputPath = await writeDebateMarkdown(resolveOutputDir(config.outputDir), result.options, result.messages, result.summary, result.stopReason, messages, result.failure);
|
|
288
|
-
renderer.done(outputPath);
|
|
289
|
-
if (result.failure) {
|
|
290
|
-
process.exitCode = result.failure.kind === "cancelled" ? 130 : 1;
|
|
291
|
-
}
|
|
292
355
|
}
|
|
293
356
|
function debateAbortSignal() {
|
|
294
357
|
const controller = new AbortController();
|
|
@@ -317,7 +380,21 @@ async function runAgentsCommand(flags) {
|
|
|
317
380
|
configLanguage: config.language
|
|
318
381
|
});
|
|
319
382
|
const messages = createTranslator(language);
|
|
320
|
-
const discovery = await
|
|
383
|
+
const discovery = await discoverLocalToolsForConfig(config, optionalString(flags["ollama-url"]));
|
|
384
|
+
if (flags.json) {
|
|
385
|
+
const fallbackAskAgents = [config.defaults?.agentA, config.defaults?.agentB]
|
|
386
|
+
.filter((name) => typeof name === "string" && !isRetiredAgentName(name));
|
|
387
|
+
process.stdout.write(JSON.stringify({
|
|
388
|
+
v: 1,
|
|
389
|
+
agents: listAgentsWithAvailability(config, discovery, messages),
|
|
390
|
+
defaults: {
|
|
391
|
+
askAgents: config.defaults?.askAgents?.length
|
|
392
|
+
? config.defaults.askAgents.filter((name) => !isRetiredAgentName(name))
|
|
393
|
+
: fallbackAskAgents
|
|
394
|
+
}
|
|
395
|
+
}) + "\n");
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
321
398
|
printAgents(configPath, config, discovery, messages);
|
|
322
399
|
}
|
|
323
400
|
/**
|
|
@@ -353,14 +430,16 @@ async function runConfigCommand(flags) {
|
|
|
353
430
|
return;
|
|
354
431
|
}
|
|
355
432
|
if (flags["sync-agents"]) {
|
|
356
|
-
const discovery = await
|
|
357
|
-
const
|
|
358
|
-
if (
|
|
433
|
+
const discovery = await discoverLocalToolsForConfig(config, optionalString(flags["ollama-url"]));
|
|
434
|
+
const result = syncDetectedAgentsDetailed(config, discovery);
|
|
435
|
+
if (!result.changed) {
|
|
359
436
|
console.log(messages.config.syncNoMissing(configPath));
|
|
360
437
|
return;
|
|
361
438
|
}
|
|
362
439
|
await writeExampleConfig(configPath, config);
|
|
363
|
-
console.log(
|
|
440
|
+
console.log(result.addedAgents.length > 0
|
|
441
|
+
? messages.config.syncAdded(configPath, result.addedAgents.join(", "))
|
|
442
|
+
: messages.config.syncRefreshed(configPath));
|
|
364
443
|
return;
|
|
365
444
|
}
|
|
366
445
|
const defaultAgents = getStringListFlag(flags["set-defaults"]);
|
|
@@ -442,15 +521,16 @@ async function runTuiConfigLoop(configPath, config, messages, initialMode) {
|
|
|
442
521
|
let mode = initialMode;
|
|
443
522
|
let notice;
|
|
444
523
|
let currentMessages = messages;
|
|
524
|
+
let changedRunDefaults = false;
|
|
445
525
|
for (;;) {
|
|
446
526
|
renderTuiConfig(config, configPath, mode, currentMessages, { message: notice });
|
|
447
527
|
notice = undefined;
|
|
448
528
|
const input = await promptTuiConfigCommand(mode, currentMessages);
|
|
449
529
|
if (input.kind === "quit") {
|
|
450
|
-
return { mode, quit: true };
|
|
530
|
+
return { mode, quit: true, changedRunDefaults };
|
|
451
531
|
}
|
|
452
532
|
if (input.kind === "back") {
|
|
453
|
-
return { mode, quit: false };
|
|
533
|
+
return { mode, quit: false, changedRunDefaults };
|
|
454
534
|
}
|
|
455
535
|
if (input.kind === "unknown") {
|
|
456
536
|
notice = input.message;
|
|
@@ -458,12 +538,16 @@ async function runTuiConfigLoop(configPath, config, messages, initialMode) {
|
|
|
458
538
|
}
|
|
459
539
|
if (input.kind === "mode") {
|
|
460
540
|
mode = mode === "ask" ? "debate" : "ask";
|
|
461
|
-
|
|
541
|
+
config.defaults = { ...(config.defaults ?? {}), mode };
|
|
542
|
+
await writeExampleConfig(configPath, config);
|
|
543
|
+
changedRunDefaults = true;
|
|
544
|
+
notice = mode === "ask" ? currentMessages.tui.askDefaultMode : currentMessages.tui.debateDefaultMode;
|
|
462
545
|
continue;
|
|
463
546
|
}
|
|
464
547
|
if (input.kind === "default-mode") {
|
|
465
548
|
config.defaults = { ...(config.defaults ?? {}), mode };
|
|
466
549
|
await writeExampleConfig(configPath, config);
|
|
550
|
+
changedRunDefaults = true;
|
|
467
551
|
notice = mode === "ask" ? currentMessages.tui.askDefaultMode : currentMessages.tui.debateDefaultMode;
|
|
468
552
|
continue;
|
|
469
553
|
}
|
|
@@ -486,7 +570,7 @@ async function runTuiConfigLoop(configPath, config, messages, initialMode) {
|
|
|
486
570
|
? { kind: "agents", agents: input.agents }
|
|
487
571
|
: await promptTuiAgentsWizard(config, mode, currentMessages);
|
|
488
572
|
if (agentsInput.kind === "quit") {
|
|
489
|
-
return { mode, quit: true };
|
|
573
|
+
return { mode, quit: true, changedRunDefaults };
|
|
490
574
|
}
|
|
491
575
|
if (agentsInput.kind === "back" || agentsInput.agents.length === 0) {
|
|
492
576
|
notice = currentMessages.tui.agentsUnchanged;
|
|
@@ -496,12 +580,14 @@ async function runTuiConfigLoop(configPath, config, messages, initialMode) {
|
|
|
496
580
|
const agents = normalizeTuiAskAgents(config, agentsInput.agents, currentMessages);
|
|
497
581
|
config.defaults = { ...(config.defaults ?? {}), askAgents: agents };
|
|
498
582
|
await writeExampleConfig(configPath, config);
|
|
583
|
+
changedRunDefaults = true;
|
|
499
584
|
notice = currentMessages.tui.askAgentsUpdated(agents.join(", "));
|
|
500
585
|
}
|
|
501
586
|
else {
|
|
502
587
|
const [agentA, agentB] = normalizeTuiDebateAgents(config, agentsInput.agents, currentMessages);
|
|
503
588
|
config.defaults = { ...(config.defaults ?? {}), agentA, agentB };
|
|
504
589
|
await writeExampleConfig(configPath, config);
|
|
590
|
+
changedRunDefaults = true;
|
|
505
591
|
notice = currentMessages.tui.debateAgentsUpdated(`${agentA} <-> ${agentB}`);
|
|
506
592
|
}
|
|
507
593
|
}
|
|
@@ -516,7 +602,7 @@ async function runTuiConfigLoop(configPath, config, messages, initialMode) {
|
|
|
516
602
|
? { kind: "roles", roles: input.roles }
|
|
517
603
|
: await promptTuiRolesWizard(config, mode, currentMessages);
|
|
518
604
|
if (rolesInput.kind === "quit") {
|
|
519
|
-
return { mode, quit: true };
|
|
605
|
+
return { mode, quit: true, changedRunDefaults };
|
|
520
606
|
}
|
|
521
607
|
if (rolesInput.kind === "back" || rolesInput.roles.length === 0) {
|
|
522
608
|
notice = currentMessages.tui.rolesUnchanged;
|
|
@@ -539,6 +625,7 @@ async function runTuiConfigLoop(configPath, config, messages, initialMode) {
|
|
|
539
625
|
validateTurns(input.turns, "--turns", currentMessages);
|
|
540
626
|
config.defaults = { ...(config.defaults ?? {}), turns: input.turns };
|
|
541
627
|
await writeExampleConfig(configPath, config);
|
|
628
|
+
changedRunDefaults = true;
|
|
542
629
|
notice = currentMessages.tui.turnsUpdated(input.turns);
|
|
543
630
|
}
|
|
544
631
|
catch (error) {
|
|
@@ -572,13 +659,129 @@ async function runTuiConfigLoop(configPath, config, messages, initialMode) {
|
|
|
572
659
|
}
|
|
573
660
|
config.defaults = nextDefaults;
|
|
574
661
|
await writeExampleConfig(configPath, config);
|
|
662
|
+
changedRunDefaults = true;
|
|
663
|
+
}
|
|
664
|
+
catch (error) {
|
|
665
|
+
notice = error instanceof Error ? error.message : String(error);
|
|
666
|
+
}
|
|
667
|
+
continue;
|
|
668
|
+
}
|
|
669
|
+
if (input.kind === "ollama-info") {
|
|
670
|
+
try {
|
|
671
|
+
notice = await formatTuiOllamaInfo(config, currentMessages);
|
|
672
|
+
}
|
|
673
|
+
catch (error) {
|
|
674
|
+
notice = error instanceof Error ? error.message : String(error);
|
|
675
|
+
}
|
|
676
|
+
continue;
|
|
677
|
+
}
|
|
678
|
+
if (input.kind === "ollama-url") {
|
|
679
|
+
try {
|
|
680
|
+
notice = await setTuiOllamaUrl(configPath, config, input.url, currentMessages);
|
|
681
|
+
changedRunDefaults = true;
|
|
682
|
+
}
|
|
683
|
+
catch (error) {
|
|
684
|
+
notice = formatRuntimeError(error, currentMessages);
|
|
685
|
+
}
|
|
686
|
+
continue;
|
|
687
|
+
}
|
|
688
|
+
if (input.kind === "ollama-model") {
|
|
689
|
+
try {
|
|
690
|
+
notice = await setTuiOllamaModel(configPath, config, input.model, currentMessages);
|
|
691
|
+
changedRunDefaults = true;
|
|
575
692
|
}
|
|
576
693
|
catch (error) {
|
|
577
694
|
notice = error instanceof Error ? error.message : String(error);
|
|
578
695
|
}
|
|
696
|
+
continue;
|
|
697
|
+
}
|
|
698
|
+
if (input.kind === "ollama-sync") {
|
|
699
|
+
try {
|
|
700
|
+
notice = await syncTuiOllamaModel(configPath, config, currentMessages);
|
|
701
|
+
changedRunDefaults = true;
|
|
702
|
+
}
|
|
703
|
+
catch (error) {
|
|
704
|
+
notice = error instanceof Error ? error.message : String(error);
|
|
705
|
+
}
|
|
706
|
+
continue;
|
|
579
707
|
}
|
|
580
708
|
}
|
|
581
709
|
}
|
|
710
|
+
async function setTuiOllamaUrl(configPath, config, value, messages) {
|
|
711
|
+
if (!Object.values(config.agents).some((agent) => agent.type === "ollama")) {
|
|
712
|
+
throw new Error(messages.config.ollamaModelNoAgent);
|
|
713
|
+
}
|
|
714
|
+
const normalized = isDefaultOllamaUrl(value)
|
|
715
|
+
? DEFAULT_OLLAMA_BASE_URL
|
|
716
|
+
: normalizeOllamaBaseUrl(value);
|
|
717
|
+
const effective = resolveOllamaBaseUrl({ configUrl: normalized });
|
|
718
|
+
setOllamaBaseUrl(config, normalized);
|
|
719
|
+
await writeExampleConfig(configPath, config);
|
|
720
|
+
return messages.tui.ollamaUrlUpdated(normalized, effective);
|
|
721
|
+
}
|
|
722
|
+
function isDefaultOllamaUrl(value) {
|
|
723
|
+
return ["default", "defaut", "défaut", "local", "localhost"].includes(value.trim().toLowerCase());
|
|
724
|
+
}
|
|
725
|
+
async function formatTuiOllamaInfo(config, messages) {
|
|
726
|
+
const discovery = await discoverLocalToolsForConfig(config);
|
|
727
|
+
const agent = config.agents["ollama-local"];
|
|
728
|
+
if (agent?.type !== "ollama") {
|
|
729
|
+
throw new Error(messages.config.ollamaModelNoAgent);
|
|
730
|
+
}
|
|
731
|
+
if (!discovery.ollama.available) {
|
|
732
|
+
return messages.tui.ollamaUnavailable(discovery.ollama.baseUrl);
|
|
733
|
+
}
|
|
734
|
+
const installed = discovery.ollama.models.length > 0
|
|
735
|
+
? discovery.ollama.models.join(", ")
|
|
736
|
+
: messages.config.ollamaModelNoInstalledModels;
|
|
737
|
+
const api = `${discovery.ollama.baseUrl}`;
|
|
738
|
+
return messages.tui.ollamaInfo(agent.model, installed, api);
|
|
739
|
+
}
|
|
740
|
+
async function setTuiOllamaModel(configPath, config, model, messages) {
|
|
741
|
+
const trimmed = model.trim();
|
|
742
|
+
if (!trimmed) {
|
|
743
|
+
throw new Error(messages.tui.ollamaModelUsage);
|
|
744
|
+
}
|
|
745
|
+
const discovery = await discoverLocalToolsForConfig(config);
|
|
746
|
+
const agent = config.agents["ollama-local"];
|
|
747
|
+
if (agent?.type !== "ollama") {
|
|
748
|
+
throw new Error(messages.config.ollamaModelNoAgent);
|
|
749
|
+
}
|
|
750
|
+
if (!discovery.ollama.models.includes(trimmed)) {
|
|
751
|
+
throw new Error(messages.config.ollamaModelUnavailable(trimmed));
|
|
752
|
+
}
|
|
753
|
+
const result = setOllamaModel(config, trimmed);
|
|
754
|
+
await writeExampleConfig(configPath, config);
|
|
755
|
+
return result
|
|
756
|
+
? messages.config.ollamaModelUpdated(configPath, result.previousModel, result.nextModel)
|
|
757
|
+
: messages.config.ollamaModelNoChange(configPath, agent.model);
|
|
758
|
+
}
|
|
759
|
+
async function syncTuiOllamaModel(configPath, config, messages) {
|
|
760
|
+
const discovery = await discoverLocalToolsForConfig(config);
|
|
761
|
+
const agent = config.agents["ollama-local"];
|
|
762
|
+
if (agent?.type !== "ollama") {
|
|
763
|
+
throw new Error(messages.config.ollamaModelNoAgent);
|
|
764
|
+
}
|
|
765
|
+
if (discovery.ollama.models.length === 0) {
|
|
766
|
+
throw new Error(messages.config.ollamaModelNoInstalledModels);
|
|
767
|
+
}
|
|
768
|
+
const result = syncOllamaModel(config, discovery);
|
|
769
|
+
if (!result) {
|
|
770
|
+
return messages.config.ollamaModelNoChange(configPath, agent.model);
|
|
771
|
+
}
|
|
772
|
+
await writeExampleConfig(configPath, config);
|
|
773
|
+
return messages.config.ollamaModelUpdated(configPath, result.previousModel, result.nextModel);
|
|
774
|
+
}
|
|
775
|
+
async function syncInteractiveDetectedAgents(configPath, config) {
|
|
776
|
+
const discovery = await discoverLocalToolsForConfig(config);
|
|
777
|
+
const result = syncDetectedAgentsDetailed(config, discovery);
|
|
778
|
+
if (result.changed) {
|
|
779
|
+
await writeExampleConfig(configPath, config);
|
|
780
|
+
}
|
|
781
|
+
return {
|
|
782
|
+
addedAgents: result.addedAgents
|
|
783
|
+
};
|
|
784
|
+
}
|
|
582
785
|
function normalizeTuiDebateAgents(config, agents, messages) {
|
|
583
786
|
const unique = agents.map((agent) => agent.trim()).filter((agent, index, list) => agent && list.indexOf(agent) === index);
|
|
584
787
|
if (unique.length !== 2) {
|
|
@@ -605,17 +808,17 @@ async function runTuiAgentsWizard(configPath, config, messages, mode, inlineAgen
|
|
|
605
808
|
? { kind: "agents", agents: inlineAgents }
|
|
606
809
|
: await promptTuiAgentsWizard(config, mode, messages);
|
|
607
810
|
if (agentsInput.kind === "quit") {
|
|
608
|
-
return { quit: true };
|
|
811
|
+
return { quit: true, changedRunDefaults: false };
|
|
609
812
|
}
|
|
610
813
|
if (agentsInput.kind === "back" || agentsInput.agents.length === 0) {
|
|
611
|
-
return { quit: false };
|
|
814
|
+
return { quit: false, changedRunDefaults: false };
|
|
612
815
|
}
|
|
613
816
|
const notice = applyTuiAgents(config, mode, agentsInput.agents, messages);
|
|
614
817
|
await writeExampleConfig(configPath, config);
|
|
615
|
-
return { notice, quit: false };
|
|
818
|
+
return { notice, quit: false, changedRunDefaults: true };
|
|
616
819
|
}
|
|
617
820
|
catch (error) {
|
|
618
|
-
return { notice: messages.tui.agentsError(error instanceof Error ? error.message : String(error)), quit: false };
|
|
821
|
+
return { notice: messages.tui.agentsError(error instanceof Error ? error.message : String(error)), quit: false, changedRunDefaults: false };
|
|
619
822
|
}
|
|
620
823
|
}
|
|
621
824
|
async function runTuiRolesWizard(configPath, config, messages, mode, inlineRoles = []) {
|
|
@@ -691,7 +894,7 @@ function isAgentRole(value) {
|
|
|
691
894
|
}
|
|
692
895
|
const VALID_AGENT_ROLES = ["implementer", "reviewer", "architect", "scout", "critic", "summarizer"];
|
|
693
896
|
async function runOllamaModelsCommand(config, json) {
|
|
694
|
-
const discovery = await
|
|
897
|
+
const discovery = await discoverLocalToolsForConfig(config);
|
|
695
898
|
const agent = config.agents["ollama-local"];
|
|
696
899
|
const currentModel = agent?.type === "ollama" ? agent.model : null;
|
|
697
900
|
const payload = {
|
|
@@ -716,7 +919,7 @@ async function runSetOllamaModelCommand(configPath, config, model, messages) {
|
|
|
716
919
|
if (!trimmed) {
|
|
717
920
|
throw new Error(messages.common.optionRequiresValue("--set-ollama-model"));
|
|
718
921
|
}
|
|
719
|
-
const discovery = await
|
|
922
|
+
const discovery = await discoverLocalToolsForConfig(config);
|
|
720
923
|
const agent = config.agents["ollama-local"];
|
|
721
924
|
if (agent?.type !== "ollama") {
|
|
722
925
|
throw new Error(messages.config.ollamaModelNoAgent);
|
|
@@ -731,7 +934,7 @@ async function runSetOllamaModelCommand(configPath, config, model, messages) {
|
|
|
731
934
|
: messages.config.ollamaModelNoChange(configPath, agent.model));
|
|
732
935
|
}
|
|
733
936
|
async function runSyncOllamaModelCommand(configPath, config, messages) {
|
|
734
|
-
const discovery = await
|
|
937
|
+
const discovery = await discoverLocalToolsForConfig(config);
|
|
735
938
|
const agent = config.agents["ollama-local"];
|
|
736
939
|
if (agent?.type !== "ollama") {
|
|
737
940
|
throw new Error(messages.config.ollamaModelNoAgent);
|
|
@@ -968,28 +1171,32 @@ function shouldOpenTuiHome(parsed) {
|
|
|
968
1171
|
&& parsed.flags.plain !== true
|
|
969
1172
|
&& parsed.flags.terminal !== true;
|
|
970
1173
|
}
|
|
1174
|
+
/** Lance la discovery avec la même adresse Ollama effective que la config et les overrides globaux. */
|
|
1175
|
+
async function discoverLocalToolsForConfig(config, ollamaUrl) {
|
|
1176
|
+
return discoverLocalTools({
|
|
1177
|
+
ollamaUrl,
|
|
1178
|
+
ollamaTargets: configuredOllamaTargets(config)
|
|
1179
|
+
});
|
|
1180
|
+
}
|
|
971
1181
|
/**
|
|
972
|
-
* Exécute la commande `palabre presets
|
|
973
|
-
*
|
|
974
|
-
* Sortie humaine par défaut (liste alignée), ou JSON avec `--json` pour les
|
|
975
|
-
* intégrations (extension VS Code, scripts shell). Le schéma JSON est versionné
|
|
976
|
-
* via le champ `v` au cas où on enrichirait plus tard (ex : description par
|
|
977
|
-
* preset, tags premium/local).
|
|
978
|
-
*
|
|
979
|
-
* @param flags - Flags parsés depuis la ligne de commande.
|
|
1182
|
+
* Exécute la commande `palabre presets` en sortie humaine ou JSON versionné.
|
|
980
1183
|
*/
|
|
981
1184
|
async function runPresetsCommand(flags) {
|
|
982
|
-
const discovery = await discoverLocalTools();
|
|
983
1185
|
const configPath = optionalString(flags.config) ?? await resolveDefaultConfigPath();
|
|
1186
|
+
const ollamaUrl = optionalString(flags["ollama-url"]);
|
|
984
1187
|
const config = await configExists(configPath)
|
|
985
1188
|
? await loadConfig(configPath)
|
|
986
|
-
:
|
|
1189
|
+
: undefined;
|
|
1190
|
+
const discovery = config
|
|
1191
|
+
? await discoverLocalToolsForConfig(config, ollamaUrl)
|
|
1192
|
+
: await discoverLocalTools({ ollamaUrl });
|
|
1193
|
+
const resolvedConfig = config ?? createConfigFromDiscovery(discovery);
|
|
987
1194
|
const language = resolveLanguage({
|
|
988
1195
|
explicitLanguage: optionalString(flags.language),
|
|
989
|
-
configLanguage:
|
|
1196
|
+
configLanguage: resolvedConfig.language
|
|
990
1197
|
});
|
|
991
1198
|
const messages = createTranslator(language);
|
|
992
|
-
const presets = listPresetsWithAvailability(
|
|
1199
|
+
const presets = listPresetsWithAvailability(resolvedConfig, discovery, messages);
|
|
993
1200
|
if (flags.json) {
|
|
994
1201
|
process.stdout.write(JSON.stringify({ v: 1, presets }) + "\n");
|
|
995
1202
|
return;
|
|
@@ -1005,6 +1212,32 @@ async function runPresetsCommand(flags) {
|
|
|
1005
1212
|
console.log("");
|
|
1006
1213
|
console.log(messages.presets.total(presets.length));
|
|
1007
1214
|
}
|
|
1215
|
+
async function runHistoryCommand(flags) {
|
|
1216
|
+
const configPath = optionalString(flags.config) ?? await resolveDefaultConfigPath();
|
|
1217
|
+
const config = await configExists(configPath)
|
|
1218
|
+
? await loadConfig(configPath)
|
|
1219
|
+
: undefined;
|
|
1220
|
+
const language = resolveLanguage({
|
|
1221
|
+
explicitLanguage: optionalString(flags.language),
|
|
1222
|
+
configLanguage: config?.language
|
|
1223
|
+
});
|
|
1224
|
+
const messages = createTranslator(language);
|
|
1225
|
+
const entries = await listHistoryEntries(resolveOutputDir(config?.outputDir));
|
|
1226
|
+
if (flags.json) {
|
|
1227
|
+
process.stdout.write(JSON.stringify({ v: 1, history: entries }) + "\n");
|
|
1228
|
+
return;
|
|
1229
|
+
}
|
|
1230
|
+
console.log(messages.tui.historyTitle);
|
|
1231
|
+
console.log("");
|
|
1232
|
+
if (entries.length === 0) {
|
|
1233
|
+
console.log(messages.tui.historyEmpty);
|
|
1234
|
+
return;
|
|
1235
|
+
}
|
|
1236
|
+
for (const entry of entries) {
|
|
1237
|
+
console.log(`- ${entry.date || entry.fileName} | ${entry.mode} | ${entry.topic}`);
|
|
1238
|
+
console.log(` ${entry.path}`);
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1008
1241
|
async function runContextCommand(flags, positionals) {
|
|
1009
1242
|
const language = resolveLanguage({ explicitLanguage: optionalString(flags.language) });
|
|
1010
1243
|
const messages = createTranslator(language);
|
|
@@ -1053,7 +1286,9 @@ function printContextWarnings(warnings, messages) {
|
|
|
1053
1286
|
* @param discovery - Résultat de la découverte locale des outils.
|
|
1054
1287
|
*/
|
|
1055
1288
|
function printAgents(configPath, config, discovery, messages) {
|
|
1056
|
-
const entries = Object.entries(config.agents)
|
|
1289
|
+
const entries = Object.entries(config.agents)
|
|
1290
|
+
.filter(([name]) => !isRetiredAgentName(name))
|
|
1291
|
+
.sort(([left], [right]) => left.localeCompare(right));
|
|
1057
1292
|
console.log(messages.agents.config(configPath));
|
|
1058
1293
|
console.log("");
|
|
1059
1294
|
console.log(messages.agents.title);
|
|
@@ -1137,7 +1372,6 @@ function printInitDiscovery(discovery, config, messages) {
|
|
|
1137
1372
|
console.log(messages.init.localDetectionTitle);
|
|
1138
1373
|
console.log(`- Codex CLI: ${formatCommandDetection(discovery.codex, messages)}`);
|
|
1139
1374
|
console.log(`- Claude CLI: ${formatCommandDetection(discovery.claude, messages)}`);
|
|
1140
|
-
console.log(`- Gemini CLI: ${formatCommandDetection(discovery.gemini, messages)}`);
|
|
1141
1375
|
console.log(`- Antigravity CLI: ${formatCommandDetection(discovery.antigravity, messages)}`);
|
|
1142
1376
|
console.log(`- OpenCode CLI: ${formatCommandDetection(discovery.opencode, messages)}`);
|
|
1143
1377
|
console.log(`- Mistral Vibe CLI: ${formatCommandDetection(discovery.vibe, messages)}`);
|
|
@@ -1216,12 +1450,23 @@ async function resolveCommandMessages(flags) {
|
|
|
1216
1450
|
}
|
|
1217
1451
|
return createTranslator(resolveLanguage({ explicitLanguage, configLanguage }));
|
|
1218
1452
|
}
|
|
1453
|
+
function formatRuntimeError(error, messages) {
|
|
1454
|
+
if (error instanceof AdapterError) {
|
|
1455
|
+
return formatAdapterError(error, messages);
|
|
1456
|
+
}
|
|
1457
|
+
if (error instanceof OllamaUrlError) {
|
|
1458
|
+
if (error.kind === "empty")
|
|
1459
|
+
return messages.common.ollamaUrlEmpty;
|
|
1460
|
+
if (error.kind === "protocol")
|
|
1461
|
+
return messages.common.ollamaUrlProtocol(error.protocol ?? "");
|
|
1462
|
+
return messages.common.ollamaUrlInvalid(error.value);
|
|
1463
|
+
}
|
|
1464
|
+
return error instanceof Error ? error.message : String(error);
|
|
1465
|
+
}
|
|
1219
1466
|
main().catch((error) => {
|
|
1220
1467
|
const language = safeStartupLanguage(process.argv.slice(2));
|
|
1221
1468
|
const messages = createTranslator(language);
|
|
1222
|
-
const message = error
|
|
1223
|
-
? formatAdapterError(error, messages)
|
|
1224
|
-
: error instanceof Error ? error.message : String(error);
|
|
1469
|
+
const message = formatRuntimeError(error, messages);
|
|
1225
1470
|
console.error(`${messages.common.errorPrefix}: ${message}`);
|
|
1226
1471
|
process.exitCode = 1;
|
|
1227
1472
|
});
|