palabre 0.8.0 → 0.8.1
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/ollama.js +1 -1
- package/dist/agentRegistry.js +1 -2
- package/dist/args.js +4 -2
- package/dist/config.js +25 -25
- package/dist/context.js +5 -1
- package/dist/discovery.js +1 -3
- package/dist/doctor.js +0 -1
- package/dist/history.js +85 -0
- package/dist/index.js +346 -179
- package/dist/messages/config.js +2 -0
- package/dist/messages/help.js +58 -8
- package/dist/messages/init.js +2 -2
- package/dist/messages/tui.js +64 -28
- package/dist/orchestrator.js +12 -4
- package/dist/presets.js +0 -48
- package/dist/renderers/tui.js +196 -73
- package/dist/tuiState.js +31 -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, 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";
|
|
@@ -11,14 +11,16 @@ import { DEFAULT_TURNS, parseTurnsFlag, turnsOrDefault, validateTurns } from "./
|
|
|
11
11
|
import { formatAgentPrompt } from "./prompt.js";
|
|
12
12
|
import { runNewWizard } from "./new.js";
|
|
13
13
|
import { 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 } 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";
|
|
23
|
+
import { askAgentSeedsForMode, clearTuiRunOverrides } from "./tuiState.js";
|
|
22
24
|
import { detectedAgentNames, detectionForCommand } from "./agentRegistry.js";
|
|
23
25
|
import { getPackageVersion } from "./version.js";
|
|
24
26
|
/** Point d'entrée principal du CLI Palabre. Dispatche vers la commande appropriée selon les arguments. */
|
|
@@ -53,6 +55,10 @@ async function main() {
|
|
|
53
55
|
await runPresetsCommand(parsed.flags);
|
|
54
56
|
return;
|
|
55
57
|
}
|
|
58
|
+
if (parsed.command === "history" || parsed.command === "historique") {
|
|
59
|
+
await runHistoryCommand(parsed.flags);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
56
62
|
if (parsed.command === "context") {
|
|
57
63
|
await runContextCommand(parsed.flags, parsed.positionals);
|
|
58
64
|
return;
|
|
@@ -95,200 +101,235 @@ async function main() {
|
|
|
95
101
|
return;
|
|
96
102
|
}
|
|
97
103
|
const configPath = optionalString(parsed.flags.config) ?? await resolveDefaultConfigPath();
|
|
104
|
+
let config;
|
|
105
|
+
let tuiNotice;
|
|
98
106
|
if (!(await configExists(configPath))) {
|
|
99
|
-
|
|
107
|
+
config = createConfigFromDiscovery(await discoverLocalTools());
|
|
100
108
|
config.language = resolveLanguage({
|
|
101
109
|
explicitLanguage: optionalString(parsed.flags.language),
|
|
102
110
|
configLanguage: config.language
|
|
103
111
|
});
|
|
104
112
|
const messages = createTranslator(config.language);
|
|
105
113
|
await writeExampleConfig(configPath, config);
|
|
106
|
-
|
|
107
|
-
|
|
114
|
+
if (!shouldOpenTuiHome(parsed)) {
|
|
115
|
+
console.log(messages.init.editConfigThenRerun(configPath));
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
tuiNotice = messages.init.configCreated(configPath);
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
config = await loadConfig(configPath);
|
|
108
122
|
}
|
|
109
|
-
const config = await loadConfig(configPath);
|
|
110
123
|
let language = resolveLanguage({
|
|
111
124
|
explicitLanguage: optionalString(parsed.flags.language),
|
|
112
125
|
configLanguage: config.language
|
|
113
126
|
});
|
|
114
127
|
let messages = createTranslator(language);
|
|
115
128
|
assertRunnableConfig(config, messages, configPath);
|
|
129
|
+
let stayInTuiAfterSession = false;
|
|
130
|
+
let hasCompletedTuiSession = false;
|
|
131
|
+
let resetTuiRunOverridesOnNextTopic = false;
|
|
132
|
+
let tuiMode = config.defaults?.mode ?? "debate";
|
|
133
|
+
let tuiVersion = "";
|
|
134
|
+
const handleTuiHomeInput = async (tuiInput) => {
|
|
135
|
+
if (!tuiInput) {
|
|
136
|
+
return "quit";
|
|
137
|
+
}
|
|
138
|
+
if (tuiInput.kind === "help") {
|
|
139
|
+
renderTuiHelp(messages);
|
|
140
|
+
const nextInput = await promptTuiHomeTopic(tuiMode, messages);
|
|
141
|
+
return handleTuiHomeInput(nextInput);
|
|
142
|
+
}
|
|
143
|
+
if (tuiInput.kind === "history") {
|
|
144
|
+
renderTuiHistory(await listHistoryEntries(resolveOutputDir(config.outputDir)), messages);
|
|
145
|
+
const nextInput = await promptTuiHomeTopic(tuiMode, messages);
|
|
146
|
+
return handleTuiHomeInput(nextInput);
|
|
147
|
+
}
|
|
148
|
+
if (tuiInput.kind === "home") {
|
|
149
|
+
return "continue";
|
|
150
|
+
}
|
|
151
|
+
if (tuiInput.kind === "roles") {
|
|
152
|
+
const result = await runTuiRolesWizard(configPath, config, messages, tuiMode, tuiInput.roles);
|
|
153
|
+
if (result.quit)
|
|
154
|
+
return "quit";
|
|
155
|
+
tuiNotice = result.notice;
|
|
156
|
+
return "continue";
|
|
157
|
+
}
|
|
158
|
+
if (tuiInput.kind === "agents") {
|
|
159
|
+
const result = await runTuiAgentsWizard(configPath, config, messages, tuiMode, tuiInput.agents);
|
|
160
|
+
if (result.quit)
|
|
161
|
+
return "quit";
|
|
162
|
+
tuiNotice = result.notice;
|
|
163
|
+
resetTuiRunOverridesOnNextTopic ||= Boolean(result.changedRunDefaults);
|
|
164
|
+
return "continue";
|
|
165
|
+
}
|
|
166
|
+
if (tuiInput.kind === "mode") {
|
|
167
|
+
tuiMode = tuiInput.mode;
|
|
168
|
+
return "continue";
|
|
169
|
+
}
|
|
170
|
+
if (tuiInput.kind === "config") {
|
|
171
|
+
const result = await runTuiConfigLoop(configPath, config, messages, tuiMode);
|
|
172
|
+
if (result.quit)
|
|
173
|
+
return "quit";
|
|
174
|
+
tuiMode = result.mode;
|
|
175
|
+
resetTuiRunOverridesOnNextTopic ||= result.changedRunDefaults;
|
|
176
|
+
language = resolveLanguage({ explicitLanguage: optionalString(parsed.flags.language), configLanguage: config.language });
|
|
177
|
+
messages = createTranslator(language);
|
|
178
|
+
return "continue";
|
|
179
|
+
}
|
|
180
|
+
if (tuiInput.kind === "new") {
|
|
181
|
+
parsed.command = "new";
|
|
182
|
+
parsed.commandExplicit = true;
|
|
183
|
+
delete parsed.flags.topic;
|
|
184
|
+
return "run";
|
|
185
|
+
}
|
|
186
|
+
if (tuiInput.kind === "retry") {
|
|
187
|
+
if (!optionalString(parsed.flags.topic)) {
|
|
188
|
+
tuiNotice = messages.tui.retryUnavailable;
|
|
189
|
+
return "continue";
|
|
190
|
+
}
|
|
191
|
+
return "retry";
|
|
192
|
+
}
|
|
193
|
+
parsed.command = "";
|
|
194
|
+
parsed.commandExplicit = false;
|
|
195
|
+
if (hasCompletedTuiSession || resetTuiRunOverridesOnNextTopic) {
|
|
196
|
+
clearTuiRunOverrides(parsed.flags);
|
|
197
|
+
resetTuiRunOverridesOnNextTopic = false;
|
|
198
|
+
}
|
|
199
|
+
parsed.flags.topic = tuiInput.topic;
|
|
200
|
+
return "run";
|
|
201
|
+
};
|
|
116
202
|
if (shouldOpenTuiHome(parsed)) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
203
|
+
const syncResult = await syncInteractiveDetectedAgents(configPath, config);
|
|
204
|
+
if (!tuiNotice && syncResult.addedAgents.length > 0) {
|
|
205
|
+
tuiNotice = messages.config.syncAdded(configPath, syncResult.addedAgents.join(", "));
|
|
206
|
+
}
|
|
207
|
+
stayInTuiAfterSession = true;
|
|
208
|
+
tuiVersion = await getPackageVersion();
|
|
120
209
|
for (;;) {
|
|
121
210
|
renderTuiHome(config, configPath, messages, { mode: tuiMode, version: tuiVersion });
|
|
122
211
|
const tuiInput = await promptTuiHomeTopic(tuiMode, messages, { notice: tuiNotice });
|
|
123
212
|
tuiNotice = undefined;
|
|
124
|
-
|
|
213
|
+
const action = await handleTuiHomeInput(tuiInput);
|
|
214
|
+
if (action === "quit")
|
|
125
215
|
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;
|
|
216
|
+
if (action === "continue")
|
|
187
217
|
continue;
|
|
218
|
+
parsed.flags.mode = tuiMode;
|
|
219
|
+
parsed.flags.renderer = "tui";
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
for (;;) {
|
|
224
|
+
if (parsed.command === "new") {
|
|
225
|
+
const selection = await runNewWizard(config, messages);
|
|
226
|
+
if (!selection) {
|
|
227
|
+
console.log(messages.new.cancelled);
|
|
228
|
+
return;
|
|
188
229
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
230
|
+
parsed.flags["agent-a"] = selection.agentA;
|
|
231
|
+
parsed.flags["agent-b"] = selection.agentB;
|
|
232
|
+
parsed.flags.topic = selection.topic;
|
|
233
|
+
if (selection.modelA)
|
|
234
|
+
parsed.flags["model-a"] = selection.modelA;
|
|
235
|
+
if (selection.modelB)
|
|
236
|
+
parsed.flags["model-b"] = selection.modelB;
|
|
237
|
+
if (selection.turns)
|
|
238
|
+
parsed.flags.turns = String(selection.turns);
|
|
239
|
+
if (selection.summaryAgent)
|
|
240
|
+
parsed.flags["summary-agent"] = selection.summaryAgent;
|
|
241
|
+
if (selection.summaryModel)
|
|
242
|
+
parsed.flags["summary-model"] = selection.summaryModel;
|
|
243
|
+
if (selection.summaryEnabled === false)
|
|
244
|
+
parsed.flags["no-summary"] = true;
|
|
245
|
+
if (selection.showPrompt)
|
|
246
|
+
parsed.flags["show-prompt"] = true;
|
|
247
|
+
if (selection.plainOutput)
|
|
248
|
+
parsed.flags.plain = true;
|
|
249
|
+
if (selection.files.length > 0)
|
|
250
|
+
parsed.flags.files = selection.files;
|
|
251
|
+
if (selection.context.length > 0)
|
|
252
|
+
parsed.flags.context = selection.context;
|
|
253
|
+
if (selection.mode)
|
|
254
|
+
parsed.flags.mode = selection.mode;
|
|
255
|
+
if (selection.askAgents && selection.askAgents.length > 0)
|
|
256
|
+
parsed.flags.agents = selection.askAgents;
|
|
257
|
+
parsed.command = "";
|
|
258
|
+
parsed.commandExplicit = false;
|
|
259
|
+
}
|
|
260
|
+
const topic = optionalString(parsed.flags.topic) ?? "";
|
|
261
|
+
const context = await loadProjectInputs(getStringListFlag(parsed.flags.files), getStringListFlag(parsed.flags.context), process.cwd(), messages);
|
|
262
|
+
const presetName = optionalString(parsed.flags.preset);
|
|
263
|
+
const preset = presetName ? resolvePreset(presetName, messages) : undefined;
|
|
264
|
+
if (!topic) {
|
|
265
|
+
throw new Error(messages.common.topicRequired);
|
|
266
|
+
}
|
|
267
|
+
const mode = parseModeFlag(optionalString(parsed.flags.mode) ?? config.defaults?.mode, messages);
|
|
268
|
+
const explicitAskAgents = getStringListFlag(parsed.flags.agents);
|
|
269
|
+
const askAgentSeeds = askAgentSeedsForMode(mode, explicitAskAgents, config.defaults?.askAgents);
|
|
270
|
+
const agentA = resolveAgentName("agent A", parsed.flags["agent-a"], preset?.agentA, askAgentSeeds[0] ?? config.defaults?.agentA, messages);
|
|
271
|
+
const agentB = resolveAgentName("agent B", parsed.flags["agent-b"], preset?.agentB, askAgentSeeds[1] ?? askAgentSeeds[0] ?? config.defaults?.agentB, messages);
|
|
272
|
+
const askAgents = mode === "ask" ? resolveAskAgents(explicitAskAgents, config.defaults?.askAgents, [agentA, agentB], messages) : undefined;
|
|
273
|
+
const options = {
|
|
274
|
+
mode,
|
|
275
|
+
language,
|
|
276
|
+
topic,
|
|
277
|
+
agentA,
|
|
278
|
+
agentB,
|
|
279
|
+
askAgents,
|
|
280
|
+
turns: parseTurnsFlag(parsed.flags.turns, config.defaults?.turns ?? DEFAULT_TURNS, "--turns", messages),
|
|
281
|
+
session: createSessionContext(),
|
|
282
|
+
files: context.files,
|
|
283
|
+
modelA: optionalString(parsed.flags["model-a"]),
|
|
284
|
+
modelB: optionalString(parsed.flags["model-b"]),
|
|
285
|
+
pullModels: Boolean(parsed.flags["pull-models"]),
|
|
286
|
+
summaryAgent: resolveSummaryAgentOption(parsed.flags["summary-agent"], config.defaults, mode),
|
|
287
|
+
summaryModel: optionalString(parsed.flags["summary-model"]),
|
|
288
|
+
summaryEnabled: !parsed.flags["no-summary"],
|
|
289
|
+
earlyStopOnAgreement: !parsed.flags["no-early-stop"],
|
|
290
|
+
plainOutput: Boolean(parsed.flags.plain || parsed.flags.terminal),
|
|
291
|
+
signal: debateAbortSignal()
|
|
292
|
+
};
|
|
293
|
+
if (parsed.flags["show-prompt"]) {
|
|
294
|
+
printContextWarnings(context.warnings, messages);
|
|
295
|
+
printPromptPreview(config, options, language, messages);
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
process.exitCode = undefined;
|
|
299
|
+
const renderer = createRendererFromFlags(parsed.flags, options.plainOutput, config.defaults?.interface, messages);
|
|
300
|
+
context.warnings.forEach((warning) => renderer.warning(warning));
|
|
301
|
+
const result = options.mode === "ask"
|
|
302
|
+
? await runAsk(config, options, renderer, messages)
|
|
303
|
+
: await runDebate(config, options, renderer, messages);
|
|
304
|
+
const outputPath = await writeDebateMarkdown(resolveOutputDir(config.outputDir), result.options, result.messages, result.summary, result.stopReason, messages, result.failure);
|
|
305
|
+
renderer.done(outputPath);
|
|
306
|
+
hasCompletedTuiSession = stayInTuiAfterSession;
|
|
307
|
+
if (result.failure) {
|
|
308
|
+
process.exitCode = result.failure.kind === "cancelled" ? 130 : 1;
|
|
309
|
+
}
|
|
310
|
+
if (!stayInTuiAfterSession) {
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
tuiMode = mode;
|
|
314
|
+
for (;;) {
|
|
315
|
+
const nextInput = await promptTuiHomeTopic(tuiMode, messages, { notice: tuiNotice });
|
|
316
|
+
tuiNotice = undefined;
|
|
317
|
+
const action = await handleTuiHomeInput(nextInput);
|
|
318
|
+
if (action === "quit")
|
|
319
|
+
return;
|
|
320
|
+
if (action === "continue") {
|
|
321
|
+
renderTuiHome(config, configPath, messages, { mode: tuiMode, version: tuiVersion });
|
|
196
322
|
continue;
|
|
197
323
|
}
|
|
198
|
-
|
|
199
|
-
parsed.
|
|
200
|
-
|
|
201
|
-
}
|
|
202
|
-
else {
|
|
203
|
-
parsed.flags.topic = tuiInput.topic;
|
|
324
|
+
if (action === "retry") {
|
|
325
|
+
parsed.flags.renderer = "tui";
|
|
326
|
+
break;
|
|
204
327
|
}
|
|
205
328
|
parsed.flags.mode = tuiMode;
|
|
206
329
|
parsed.flags.renderer = "tui";
|
|
207
330
|
break;
|
|
208
331
|
}
|
|
209
332
|
}
|
|
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
333
|
}
|
|
293
334
|
function debateAbortSignal() {
|
|
294
335
|
const controller = new AbortController();
|
|
@@ -354,13 +395,15 @@ async function runConfigCommand(flags) {
|
|
|
354
395
|
}
|
|
355
396
|
if (flags["sync-agents"]) {
|
|
356
397
|
const discovery = await discoverLocalTools();
|
|
357
|
-
const
|
|
358
|
-
if (
|
|
398
|
+
const result = syncDetectedAgentsDetailed(config, discovery);
|
|
399
|
+
if (!result.changed) {
|
|
359
400
|
console.log(messages.config.syncNoMissing(configPath));
|
|
360
401
|
return;
|
|
361
402
|
}
|
|
362
403
|
await writeExampleConfig(configPath, config);
|
|
363
|
-
console.log(
|
|
404
|
+
console.log(result.addedAgents.length > 0
|
|
405
|
+
? messages.config.syncAdded(configPath, result.addedAgents.join(", "))
|
|
406
|
+
: messages.config.syncRefreshed(configPath));
|
|
364
407
|
return;
|
|
365
408
|
}
|
|
366
409
|
const defaultAgents = getStringListFlag(flags["set-defaults"]);
|
|
@@ -442,15 +485,16 @@ async function runTuiConfigLoop(configPath, config, messages, initialMode) {
|
|
|
442
485
|
let mode = initialMode;
|
|
443
486
|
let notice;
|
|
444
487
|
let currentMessages = messages;
|
|
488
|
+
let changedRunDefaults = false;
|
|
445
489
|
for (;;) {
|
|
446
490
|
renderTuiConfig(config, configPath, mode, currentMessages, { message: notice });
|
|
447
491
|
notice = undefined;
|
|
448
492
|
const input = await promptTuiConfigCommand(mode, currentMessages);
|
|
449
493
|
if (input.kind === "quit") {
|
|
450
|
-
return { mode, quit: true };
|
|
494
|
+
return { mode, quit: true, changedRunDefaults };
|
|
451
495
|
}
|
|
452
496
|
if (input.kind === "back") {
|
|
453
|
-
return { mode, quit: false };
|
|
497
|
+
return { mode, quit: false, changedRunDefaults };
|
|
454
498
|
}
|
|
455
499
|
if (input.kind === "unknown") {
|
|
456
500
|
notice = input.message;
|
|
@@ -458,12 +502,16 @@ async function runTuiConfigLoop(configPath, config, messages, initialMode) {
|
|
|
458
502
|
}
|
|
459
503
|
if (input.kind === "mode") {
|
|
460
504
|
mode = mode === "ask" ? "debate" : "ask";
|
|
461
|
-
|
|
505
|
+
config.defaults = { ...(config.defaults ?? {}), mode };
|
|
506
|
+
await writeExampleConfig(configPath, config);
|
|
507
|
+
changedRunDefaults = true;
|
|
508
|
+
notice = mode === "ask" ? currentMessages.tui.askDefaultMode : currentMessages.tui.debateDefaultMode;
|
|
462
509
|
continue;
|
|
463
510
|
}
|
|
464
511
|
if (input.kind === "default-mode") {
|
|
465
512
|
config.defaults = { ...(config.defaults ?? {}), mode };
|
|
466
513
|
await writeExampleConfig(configPath, config);
|
|
514
|
+
changedRunDefaults = true;
|
|
467
515
|
notice = mode === "ask" ? currentMessages.tui.askDefaultMode : currentMessages.tui.debateDefaultMode;
|
|
468
516
|
continue;
|
|
469
517
|
}
|
|
@@ -486,7 +534,7 @@ async function runTuiConfigLoop(configPath, config, messages, initialMode) {
|
|
|
486
534
|
? { kind: "agents", agents: input.agents }
|
|
487
535
|
: await promptTuiAgentsWizard(config, mode, currentMessages);
|
|
488
536
|
if (agentsInput.kind === "quit") {
|
|
489
|
-
return { mode, quit: true };
|
|
537
|
+
return { mode, quit: true, changedRunDefaults };
|
|
490
538
|
}
|
|
491
539
|
if (agentsInput.kind === "back" || agentsInput.agents.length === 0) {
|
|
492
540
|
notice = currentMessages.tui.agentsUnchanged;
|
|
@@ -496,12 +544,14 @@ async function runTuiConfigLoop(configPath, config, messages, initialMode) {
|
|
|
496
544
|
const agents = normalizeTuiAskAgents(config, agentsInput.agents, currentMessages);
|
|
497
545
|
config.defaults = { ...(config.defaults ?? {}), askAgents: agents };
|
|
498
546
|
await writeExampleConfig(configPath, config);
|
|
547
|
+
changedRunDefaults = true;
|
|
499
548
|
notice = currentMessages.tui.askAgentsUpdated(agents.join(", "));
|
|
500
549
|
}
|
|
501
550
|
else {
|
|
502
551
|
const [agentA, agentB] = normalizeTuiDebateAgents(config, agentsInput.agents, currentMessages);
|
|
503
552
|
config.defaults = { ...(config.defaults ?? {}), agentA, agentB };
|
|
504
553
|
await writeExampleConfig(configPath, config);
|
|
554
|
+
changedRunDefaults = true;
|
|
505
555
|
notice = currentMessages.tui.debateAgentsUpdated(`${agentA} <-> ${agentB}`);
|
|
506
556
|
}
|
|
507
557
|
}
|
|
@@ -516,7 +566,7 @@ async function runTuiConfigLoop(configPath, config, messages, initialMode) {
|
|
|
516
566
|
? { kind: "roles", roles: input.roles }
|
|
517
567
|
: await promptTuiRolesWizard(config, mode, currentMessages);
|
|
518
568
|
if (rolesInput.kind === "quit") {
|
|
519
|
-
return { mode, quit: true };
|
|
569
|
+
return { mode, quit: true, changedRunDefaults };
|
|
520
570
|
}
|
|
521
571
|
if (rolesInput.kind === "back" || rolesInput.roles.length === 0) {
|
|
522
572
|
notice = currentMessages.tui.rolesUnchanged;
|
|
@@ -539,6 +589,7 @@ async function runTuiConfigLoop(configPath, config, messages, initialMode) {
|
|
|
539
589
|
validateTurns(input.turns, "--turns", currentMessages);
|
|
540
590
|
config.defaults = { ...(config.defaults ?? {}), turns: input.turns };
|
|
541
591
|
await writeExampleConfig(configPath, config);
|
|
592
|
+
changedRunDefaults = true;
|
|
542
593
|
notice = currentMessages.tui.turnsUpdated(input.turns);
|
|
543
594
|
}
|
|
544
595
|
catch (error) {
|
|
@@ -572,12 +623,103 @@ async function runTuiConfigLoop(configPath, config, messages, initialMode) {
|
|
|
572
623
|
}
|
|
573
624
|
config.defaults = nextDefaults;
|
|
574
625
|
await writeExampleConfig(configPath, config);
|
|
626
|
+
changedRunDefaults = true;
|
|
575
627
|
}
|
|
576
628
|
catch (error) {
|
|
577
629
|
notice = error instanceof Error ? error.message : String(error);
|
|
578
630
|
}
|
|
631
|
+
continue;
|
|
579
632
|
}
|
|
633
|
+
if (input.kind === "ollama-info") {
|
|
634
|
+
try {
|
|
635
|
+
notice = await formatTuiOllamaInfo(config, currentMessages);
|
|
636
|
+
}
|
|
637
|
+
catch (error) {
|
|
638
|
+
notice = error instanceof Error ? error.message : String(error);
|
|
639
|
+
}
|
|
640
|
+
continue;
|
|
641
|
+
}
|
|
642
|
+
if (input.kind === "ollama-model") {
|
|
643
|
+
try {
|
|
644
|
+
notice = await setTuiOllamaModel(configPath, config, input.model, currentMessages);
|
|
645
|
+
changedRunDefaults = true;
|
|
646
|
+
}
|
|
647
|
+
catch (error) {
|
|
648
|
+
notice = error instanceof Error ? error.message : String(error);
|
|
649
|
+
}
|
|
650
|
+
continue;
|
|
651
|
+
}
|
|
652
|
+
if (input.kind === "ollama-sync") {
|
|
653
|
+
try {
|
|
654
|
+
notice = await syncTuiOllamaModel(configPath, config, currentMessages);
|
|
655
|
+
changedRunDefaults = true;
|
|
656
|
+
}
|
|
657
|
+
catch (error) {
|
|
658
|
+
notice = error instanceof Error ? error.message : String(error);
|
|
659
|
+
}
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
async function formatTuiOllamaInfo(config, messages) {
|
|
665
|
+
const discovery = await discoverLocalTools();
|
|
666
|
+
const agent = config.agents["ollama-local"];
|
|
667
|
+
if (agent?.type !== "ollama") {
|
|
668
|
+
throw new Error(messages.config.ollamaModelNoAgent);
|
|
669
|
+
}
|
|
670
|
+
if (!discovery.ollama.available) {
|
|
671
|
+
return messages.tui.ollamaUnavailable(discovery.ollama.baseUrl);
|
|
672
|
+
}
|
|
673
|
+
const installed = discovery.ollama.models.length > 0
|
|
674
|
+
? discovery.ollama.models.join(", ")
|
|
675
|
+
: messages.config.ollamaModelNoInstalledModels;
|
|
676
|
+
const api = `${discovery.ollama.baseUrl}`;
|
|
677
|
+
return messages.tui.ollamaInfo(agent.model, installed, api);
|
|
678
|
+
}
|
|
679
|
+
async function setTuiOllamaModel(configPath, config, model, messages) {
|
|
680
|
+
const trimmed = model.trim();
|
|
681
|
+
if (!trimmed) {
|
|
682
|
+
throw new Error(messages.tui.ollamaModelUsage);
|
|
683
|
+
}
|
|
684
|
+
const discovery = await discoverLocalTools();
|
|
685
|
+
const agent = config.agents["ollama-local"];
|
|
686
|
+
if (agent?.type !== "ollama") {
|
|
687
|
+
throw new Error(messages.config.ollamaModelNoAgent);
|
|
688
|
+
}
|
|
689
|
+
if (!discovery.ollama.models.includes(trimmed)) {
|
|
690
|
+
throw new Error(messages.config.ollamaModelUnavailable(trimmed));
|
|
691
|
+
}
|
|
692
|
+
const result = setOllamaModel(config, trimmed);
|
|
693
|
+
await writeExampleConfig(configPath, config);
|
|
694
|
+
return result
|
|
695
|
+
? messages.config.ollamaModelUpdated(configPath, result.previousModel, result.nextModel)
|
|
696
|
+
: messages.config.ollamaModelNoChange(configPath, agent.model);
|
|
697
|
+
}
|
|
698
|
+
async function syncTuiOllamaModel(configPath, config, messages) {
|
|
699
|
+
const discovery = await discoverLocalTools();
|
|
700
|
+
const agent = config.agents["ollama-local"];
|
|
701
|
+
if (agent?.type !== "ollama") {
|
|
702
|
+
throw new Error(messages.config.ollamaModelNoAgent);
|
|
703
|
+
}
|
|
704
|
+
if (discovery.ollama.models.length === 0) {
|
|
705
|
+
throw new Error(messages.config.ollamaModelNoInstalledModels);
|
|
580
706
|
}
|
|
707
|
+
const result = syncOllamaModel(config, discovery);
|
|
708
|
+
if (!result) {
|
|
709
|
+
return messages.config.ollamaModelNoChange(configPath, agent.model);
|
|
710
|
+
}
|
|
711
|
+
await writeExampleConfig(configPath, config);
|
|
712
|
+
return messages.config.ollamaModelUpdated(configPath, result.previousModel, result.nextModel);
|
|
713
|
+
}
|
|
714
|
+
async function syncInteractiveDetectedAgents(configPath, config) {
|
|
715
|
+
const discovery = await discoverLocalTools();
|
|
716
|
+
const result = syncDetectedAgentsDetailed(config, discovery);
|
|
717
|
+
if (result.changed) {
|
|
718
|
+
await writeExampleConfig(configPath, config);
|
|
719
|
+
}
|
|
720
|
+
return {
|
|
721
|
+
addedAgents: result.addedAgents
|
|
722
|
+
};
|
|
581
723
|
}
|
|
582
724
|
function normalizeTuiDebateAgents(config, agents, messages) {
|
|
583
725
|
const unique = agents.map((agent) => agent.trim()).filter((agent, index, list) => agent && list.indexOf(agent) === index);
|
|
@@ -605,17 +747,17 @@ async function runTuiAgentsWizard(configPath, config, messages, mode, inlineAgen
|
|
|
605
747
|
? { kind: "agents", agents: inlineAgents }
|
|
606
748
|
: await promptTuiAgentsWizard(config, mode, messages);
|
|
607
749
|
if (agentsInput.kind === "quit") {
|
|
608
|
-
return { quit: true };
|
|
750
|
+
return { quit: true, changedRunDefaults: false };
|
|
609
751
|
}
|
|
610
752
|
if (agentsInput.kind === "back" || agentsInput.agents.length === 0) {
|
|
611
|
-
return { quit: false };
|
|
753
|
+
return { quit: false, changedRunDefaults: false };
|
|
612
754
|
}
|
|
613
755
|
const notice = applyTuiAgents(config, mode, agentsInput.agents, messages);
|
|
614
756
|
await writeExampleConfig(configPath, config);
|
|
615
|
-
return { notice, quit: false };
|
|
757
|
+
return { notice, quit: false, changedRunDefaults: true };
|
|
616
758
|
}
|
|
617
759
|
catch (error) {
|
|
618
|
-
return { notice: messages.tui.agentsError(error instanceof Error ? error.message : String(error)), quit: false };
|
|
760
|
+
return { notice: messages.tui.agentsError(error instanceof Error ? error.message : String(error)), quit: false, changedRunDefaults: false };
|
|
619
761
|
}
|
|
620
762
|
}
|
|
621
763
|
async function runTuiRolesWizard(configPath, config, messages, mode, inlineRoles = []) {
|
|
@@ -1005,6 +1147,32 @@ async function runPresetsCommand(flags) {
|
|
|
1005
1147
|
console.log("");
|
|
1006
1148
|
console.log(messages.presets.total(presets.length));
|
|
1007
1149
|
}
|
|
1150
|
+
async function runHistoryCommand(flags) {
|
|
1151
|
+
const configPath = optionalString(flags.config) ?? await resolveDefaultConfigPath();
|
|
1152
|
+
const config = await configExists(configPath)
|
|
1153
|
+
? await loadConfig(configPath)
|
|
1154
|
+
: undefined;
|
|
1155
|
+
const language = resolveLanguage({
|
|
1156
|
+
explicitLanguage: optionalString(flags.language),
|
|
1157
|
+
configLanguage: config?.language
|
|
1158
|
+
});
|
|
1159
|
+
const messages = createTranslator(language);
|
|
1160
|
+
const entries = await listHistoryEntries(resolveOutputDir(config?.outputDir));
|
|
1161
|
+
if (flags.json) {
|
|
1162
|
+
process.stdout.write(JSON.stringify({ v: 1, history: entries }) + "\n");
|
|
1163
|
+
return;
|
|
1164
|
+
}
|
|
1165
|
+
console.log(messages.tui.historyTitle);
|
|
1166
|
+
console.log("");
|
|
1167
|
+
if (entries.length === 0) {
|
|
1168
|
+
console.log(messages.tui.historyEmpty);
|
|
1169
|
+
return;
|
|
1170
|
+
}
|
|
1171
|
+
for (const entry of entries) {
|
|
1172
|
+
console.log(`- ${entry.date || entry.fileName} | ${entry.mode} | ${entry.topic}`);
|
|
1173
|
+
console.log(` ${entry.path}`);
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1008
1176
|
async function runContextCommand(flags, positionals) {
|
|
1009
1177
|
const language = resolveLanguage({ explicitLanguage: optionalString(flags.language) });
|
|
1010
1178
|
const messages = createTranslator(language);
|
|
@@ -1137,7 +1305,6 @@ function printInitDiscovery(discovery, config, messages) {
|
|
|
1137
1305
|
console.log(messages.init.localDetectionTitle);
|
|
1138
1306
|
console.log(`- Codex CLI: ${formatCommandDetection(discovery.codex, messages)}`);
|
|
1139
1307
|
console.log(`- Claude CLI: ${formatCommandDetection(discovery.claude, messages)}`);
|
|
1140
|
-
console.log(`- Gemini CLI: ${formatCommandDetection(discovery.gemini, messages)}`);
|
|
1141
1308
|
console.log(`- Antigravity CLI: ${formatCommandDetection(discovery.antigravity, messages)}`);
|
|
1142
1309
|
console.log(`- OpenCode CLI: ${formatCommandDetection(discovery.opencode, messages)}`);
|
|
1143
1310
|
console.log(`- Mistral Vibe CLI: ${formatCommandDetection(discovery.vibe, messages)}`);
|