palabre 0.8.1 → 0.9.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 +2 -0
- package/dist/adapters/index.js +2 -2
- package/dist/adapters/ollama.js +8 -6
- package/dist/agentRegistry.js +6 -0
- package/dist/args.js +1 -0
- package/dist/commands/agents.js +85 -0
- package/dist/commands/context.js +25 -0
- package/dist/commands/history.js +28 -0
- package/dist/commands/init.js +59 -0
- package/dist/commands/presets.js +34 -0
- package/dist/commands/shared.js +4 -0
- package/dist/commands/update.js +21 -0
- package/dist/config.js +8 -0
- package/dist/discovery.js +36 -6
- package/dist/doctor.js +19 -4
- package/dist/index.js +66 -732
- package/dist/limits.js +1 -0
- package/dist/messages/common.js +6 -0
- package/dist/messages/doctor.js +2 -2
- package/dist/messages/help.js +6 -0
- package/dist/messages/tui.js +14 -0
- package/dist/ollamaUrl.js +76 -0
- package/dist/orchestrator.js +29 -24
- package/dist/output.js +1 -10
- package/dist/presets.js +25 -4
- package/dist/renderers/console.js +1 -7
- package/dist/renderers/ndjson.js +1 -10
- package/dist/renderers/tui.js +50 -17
- package/dist/runOptions.js +66 -0
- package/dist/tuiController.js +400 -0
- package/dist/tuiState.js +1 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -70,6 +70,7 @@ PALABRE does not list models: they change often and depend on each CLI or user a
|
|
|
70
70
|
|
|
71
71
|
PALABRE exposes versioned JSON outputs for external clients:
|
|
72
72
|
|
|
73
|
+
- `palabre agents --json` to read configured agents and CLI-owned availability;
|
|
73
74
|
- `palabre presets --json` to read available agent pairs;
|
|
74
75
|
- `palabre context scan --json` to preview the context `--context` would retain;
|
|
75
76
|
- `--renderer ndjson` or `--json` to follow a debate event by event.
|
|
@@ -178,6 +179,7 @@ PALABRE ne liste pas les modèles : ils changent souvent et dépendent de chaque
|
|
|
178
179
|
|
|
179
180
|
PALABRE expose des sorties JSON versionnées pour les clients externes :
|
|
180
181
|
|
|
182
|
+
- `palabre agents --json` pour lire les agents configurés et leur disponibilité calculée par le CLI ;
|
|
181
183
|
- `palabre presets --json` pour lire les paires d'agents disponibles ;
|
|
182
184
|
- `palabre context scan --json` pour prévisualiser le contexte que `--context` retiendrait ;
|
|
183
185
|
- `--renderer ndjson` ou `--json` pour suivre un débat événement par événement.
|
package/dist/adapters/index.js
CHANGED
|
@@ -2,13 +2,13 @@ import { CliAdapter } from "./cli.js";
|
|
|
2
2
|
import { CliPtyAdapter } from "./cli-pty.js";
|
|
3
3
|
import { OllamaAdapter } from "./ollama.js";
|
|
4
4
|
/** Factory qui instancie l'adapter approprié selon `config.type`. Exhaustive : tout `AgentConfig` valide produit un adapter. */
|
|
5
|
-
export function createAgent(name, config) {
|
|
5
|
+
export function createAgent(name, config, runtime = {}) {
|
|
6
6
|
switch (config.type) {
|
|
7
7
|
case "cli":
|
|
8
8
|
return new CliAdapter(name, config);
|
|
9
9
|
case "cli-pty":
|
|
10
10
|
return new CliPtyAdapter(name, config);
|
|
11
11
|
case "ollama":
|
|
12
|
-
return new OllamaAdapter(name, config);
|
|
12
|
+
return new OllamaAdapter(name, config, runtime);
|
|
13
13
|
}
|
|
14
14
|
}
|
package/dist/adapters/ollama.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AdapterError } from "../errors.js";
|
|
2
2
|
import { createTranslator } from "../i18n.js";
|
|
3
3
|
import { formatAgentPrompt } from "../prompt.js";
|
|
4
|
+
import { resolveOllamaBaseUrl } from "../ollamaUrl.js";
|
|
4
5
|
/**
|
|
5
6
|
* Adapter pour Ollama via l'API HTTP locale (`POST /api/chat`).
|
|
6
7
|
* N'accède jamais au filesystem : ne voit que le prompt et le transcript fournis par l'orchestrateur.
|
|
@@ -9,11 +10,13 @@ import { formatAgentPrompt } from "../prompt.js";
|
|
|
9
10
|
export class OllamaAdapter {
|
|
10
11
|
name;
|
|
11
12
|
config;
|
|
13
|
+
runtime;
|
|
12
14
|
role;
|
|
13
15
|
contract;
|
|
14
|
-
constructor(name, config) {
|
|
16
|
+
constructor(name, config, runtime = {}) {
|
|
15
17
|
this.name = name;
|
|
16
18
|
this.config = config;
|
|
19
|
+
this.runtime = runtime;
|
|
17
20
|
this.role = config.role;
|
|
18
21
|
this.contract = {
|
|
19
22
|
name,
|
|
@@ -38,7 +41,10 @@ export class OllamaAdapter {
|
|
|
38
41
|
if (prompt.signal?.aborted) {
|
|
39
42
|
throw cancelledError(this.name);
|
|
40
43
|
}
|
|
41
|
-
const baseUrl =
|
|
44
|
+
const baseUrl = resolveOllamaBaseUrl({
|
|
45
|
+
cliUrl: this.runtime.ollamaUrl,
|
|
46
|
+
configUrl: this.config.baseUrl
|
|
47
|
+
});
|
|
42
48
|
if (this.config.validateModel !== false) {
|
|
43
49
|
await this.ensureModelAvailable(baseUrl);
|
|
44
50
|
}
|
|
@@ -225,10 +231,6 @@ async function unloadModel(baseUrl, model, signal) {
|
|
|
225
231
|
});
|
|
226
232
|
}
|
|
227
233
|
}
|
|
228
|
-
/** Supprime le slash final de `baseUrl` pour éviter les doubles slashs dans les URLs construites. */
|
|
229
|
-
function normalizeBaseUrl(baseUrl) {
|
|
230
|
-
return baseUrl.replace(/\/$/, "");
|
|
231
|
-
}
|
|
232
234
|
function cancelledError(adapterName) {
|
|
233
235
|
return new AdapterError("cancelled", adapterName, `${adapterName} cancelled by user.`);
|
|
234
236
|
}
|
package/dist/agentRegistry.js
CHANGED
|
@@ -10,6 +10,12 @@ const KNOWN_CLI_AGENTS = [
|
|
|
10
10
|
{ configKey: "opencode", commandAliases: ["opencode"], discoveryKey: "opencode" },
|
|
11
11
|
{ configKey: "vibe", commandAliases: ["vibe"], discoveryKey: "vibe" }
|
|
12
12
|
];
|
|
13
|
+
/** Agents retirés conservés uniquement pour lire les anciennes configurations. */
|
|
14
|
+
const RETIRED_AGENT_NAMES = new Set(["gemini"]);
|
|
15
|
+
/** Indique qu'un nom d'agent ne doit plus être proposé ni exposé aux intégrations. */
|
|
16
|
+
export function isRetiredAgentName(name) {
|
|
17
|
+
return RETIRED_AGENT_NAMES.has(name.toLowerCase());
|
|
18
|
+
}
|
|
13
19
|
/** Clé de config de l'agent Ollama local par défaut. */
|
|
14
20
|
export const OLLAMA_AGENT_KEY = "ollama-local";
|
|
15
21
|
/**
|
package/dist/args.js
CHANGED
|
@@ -35,6 +35,7 @@ const FLAG_SPECS = {
|
|
|
35
35
|
language: { arity: "single" },
|
|
36
36
|
"model-a": { arity: "single" },
|
|
37
37
|
"model-b": { arity: "single" },
|
|
38
|
+
"ollama-url": { arity: "single" },
|
|
38
39
|
mode: { arity: "single" },
|
|
39
40
|
"set-ollama-model": { arity: "single" },
|
|
40
41
|
preset: { arity: "single" },
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { configExists, loadConfig, resolveDefaultConfigPath } from "../config.js";
|
|
2
|
+
import { discoverLocalToolsForConfig } from "../discovery.js";
|
|
3
|
+
import { createTranslator, resolveLanguage } from "../i18n.js";
|
|
4
|
+
import { turnsOrDefault } from "../limits.js";
|
|
5
|
+
import { listAgentsWithAvailability } from "../presets.js";
|
|
6
|
+
import { detectionForCommand, isRetiredAgentName } from "../agentRegistry.js";
|
|
7
|
+
import { optionalString } from "./shared.js";
|
|
8
|
+
/** Exécute `palabre agents` en sortie humaine ou JSON versionné. */
|
|
9
|
+
export async function runAgentsCommand(flags) {
|
|
10
|
+
const configPath = optionalString(flags.config) ?? await resolveDefaultConfigPath();
|
|
11
|
+
if (!(await configExists(configPath))) {
|
|
12
|
+
const messages = createTranslator(resolveLanguage({ explicitLanguage: optionalString(flags.language) }));
|
|
13
|
+
throw new Error(messages.agents.noConfig);
|
|
14
|
+
}
|
|
15
|
+
const config = await loadConfig(configPath);
|
|
16
|
+
const messages = createTranslator(resolveLanguage({
|
|
17
|
+
explicitLanguage: optionalString(flags.language),
|
|
18
|
+
configLanguage: config.language
|
|
19
|
+
}));
|
|
20
|
+
const discovery = await discoverLocalToolsForConfig(config, optionalString(flags["ollama-url"]));
|
|
21
|
+
if (flags.json) {
|
|
22
|
+
const fallbackAskAgents = [config.defaults?.agentA, config.defaults?.agentB]
|
|
23
|
+
.filter((name) => typeof name === "string" && !isRetiredAgentName(name));
|
|
24
|
+
process.stdout.write(JSON.stringify({
|
|
25
|
+
v: 1,
|
|
26
|
+
agents: listAgentsWithAvailability(config, discovery, messages),
|
|
27
|
+
defaults: {
|
|
28
|
+
askAgents: config.defaults?.askAgents?.length
|
|
29
|
+
? config.defaults.askAgents.filter((name) => !isRetiredAgentName(name))
|
|
30
|
+
: fallbackAskAgents
|
|
31
|
+
}
|
|
32
|
+
}) + "\n");
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
printAgents(configPath, config, discovery, messages);
|
|
36
|
+
}
|
|
37
|
+
function printAgents(configPath, config, discovery, messages) {
|
|
38
|
+
const entries = Object.entries(config.agents)
|
|
39
|
+
.filter(([name]) => !isRetiredAgentName(name))
|
|
40
|
+
.sort(([left], [right]) => left.localeCompare(right));
|
|
41
|
+
console.log(messages.agents.config(configPath));
|
|
42
|
+
console.log("");
|
|
43
|
+
console.log(messages.agents.title);
|
|
44
|
+
for (const [name, agentConfig] of entries) {
|
|
45
|
+
const status = formatAgentDetection(name, agentConfig, discovery, messages);
|
|
46
|
+
const defaults = formatAgentDefaults(name, config, messages);
|
|
47
|
+
const details = formatAgentDetails(agentConfig, messages);
|
|
48
|
+
console.log(`- ${name.padEnd(13)} ${`${agentConfig.type}/${agentConfig.role}`.padEnd(18)} ${status}${defaults ? ` | ${defaults}` : ""}`);
|
|
49
|
+
if (details)
|
|
50
|
+
console.log(` ${details}`);
|
|
51
|
+
}
|
|
52
|
+
console.log("");
|
|
53
|
+
console.log(messages.agents.defaults(config.defaults?.agentA ?? messages.agents.none, config.defaults?.agentB ?? messages.agents.none, turnsOrDefault(config.defaults?.turns), config.defaults?.summaryAgent ?? messages.agents.summaryAgentB, config.defaults?.askSummaryAgent));
|
|
54
|
+
}
|
|
55
|
+
function formatAgentDefaults(name, config, messages) {
|
|
56
|
+
const labels = [];
|
|
57
|
+
if (config.defaults?.agentA === name)
|
|
58
|
+
labels.push(messages.agents.defaultAgentA);
|
|
59
|
+
if (config.defaults?.agentB === name)
|
|
60
|
+
labels.push(messages.agents.defaultAgentB);
|
|
61
|
+
if (config.defaults?.summaryAgent === name)
|
|
62
|
+
labels.push(messages.agents.defaultSummary);
|
|
63
|
+
if (config.defaults?.askSummaryAgent === name)
|
|
64
|
+
labels.push(messages.agents.defaultAskSummary);
|
|
65
|
+
return labels.join(", ");
|
|
66
|
+
}
|
|
67
|
+
function formatAgentDetails(agentConfig, messages) {
|
|
68
|
+
return agentConfig.type === "ollama"
|
|
69
|
+
? messages.agents.model(agentConfig.model)
|
|
70
|
+
: messages.agents.command(agentConfig.command, agentConfig.model);
|
|
71
|
+
}
|
|
72
|
+
function formatAgentDetection(name, agentConfig, discovery, messages) {
|
|
73
|
+
if (agentConfig.type === "ollama") {
|
|
74
|
+
const ollama = discovery.ollama;
|
|
75
|
+
if (!ollama.available)
|
|
76
|
+
return ollama.commandAvailable ? messages.agents.ollamaUnreachable : messages.agents.ollamaNotDetected;
|
|
77
|
+
return ollama.models.includes(agentConfig.model) ? messages.agents.detected() : messages.agents.missingModel(agentConfig.model);
|
|
78
|
+
}
|
|
79
|
+
const detection = cliDetectionForAgent(name, agentConfig, discovery);
|
|
80
|
+
return detection.available ? messages.agents.detected(detection.command) : messages.agents.notDetected;
|
|
81
|
+
}
|
|
82
|
+
function cliDetectionForAgent(name, agentConfig, discovery) {
|
|
83
|
+
const command = agentConfig.type === "cli" || agentConfig.type === "cli-pty" ? agentConfig.command : name;
|
|
84
|
+
return detectionForCommand(command, discovery) ?? { available: true, command };
|
|
85
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { buildContextScan } from "../contextScan.js";
|
|
2
|
+
import { createTranslator, resolveLanguage } from "../i18n.js";
|
|
3
|
+
import { optionalString } from "./shared.js";
|
|
4
|
+
/** Exécute `palabre context scan` en sortie humaine ou JSON versionné. */
|
|
5
|
+
export async function runContextCommand(flags, positionals) {
|
|
6
|
+
const messages = createTranslator(resolveLanguage({ explicitLanguage: optionalString(flags.language) }));
|
|
7
|
+
const subcommand = positionals[0] ?? "scan";
|
|
8
|
+
if (subcommand !== "scan") {
|
|
9
|
+
throw new Error(messages.common.unknownCommand(`context ${subcommand}`, "context scan"));
|
|
10
|
+
}
|
|
11
|
+
const result = await buildContextScan(positionals.slice(1), process.cwd(), messages);
|
|
12
|
+
if (flags.json) {
|
|
13
|
+
console.log(JSON.stringify(result, null, 2));
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
for (const folder of result.items.filter((item) => item.kind === "folder")) {
|
|
17
|
+
console.log(`[folder] ${folder.path}`);
|
|
18
|
+
}
|
|
19
|
+
for (const file of result.items.filter((item) => item.kind === "file")) {
|
|
20
|
+
console.log(`[file] ${file.path} (${file.sizeBytes} bytes)`);
|
|
21
|
+
}
|
|
22
|
+
for (const warning of result.warnings) {
|
|
23
|
+
console.error(`${messages.renderers.warningPrefix} ${warning}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { configExists, loadConfig, resolveDefaultConfigPath, resolveOutputDir } from "../config.js";
|
|
2
|
+
import { listHistoryEntries } from "../history.js";
|
|
3
|
+
import { createTranslator, resolveLanguage } from "../i18n.js";
|
|
4
|
+
import { optionalString } from "./shared.js";
|
|
5
|
+
/** Exécute `palabre history` en sortie humaine ou JSON versionné. */
|
|
6
|
+
export async function runHistoryCommand(flags) {
|
|
7
|
+
const configPath = optionalString(flags.config) ?? await resolveDefaultConfigPath();
|
|
8
|
+
const config = await configExists(configPath) ? await loadConfig(configPath) : undefined;
|
|
9
|
+
const messages = createTranslator(resolveLanguage({
|
|
10
|
+
explicitLanguage: optionalString(flags.language),
|
|
11
|
+
configLanguage: config?.language
|
|
12
|
+
}));
|
|
13
|
+
const entries = await listHistoryEntries(resolveOutputDir(config?.outputDir));
|
|
14
|
+
if (flags.json) {
|
|
15
|
+
process.stdout.write(JSON.stringify({ v: 1, history: entries }) + "\n");
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
console.log(messages.tui.historyTitle);
|
|
19
|
+
console.log("");
|
|
20
|
+
if (entries.length === 0) {
|
|
21
|
+
console.log(messages.tui.historyEmpty);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
for (const entry of entries) {
|
|
25
|
+
console.log(`- ${entry.date || entry.fileName} | ${entry.mode} | ${entry.topic}`);
|
|
26
|
+
console.log(` ${entry.path}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { configExists, createConfigFromDiscovery, DEFAULT_CONFIG_PATH, GLOBAL_CONFIG_PATH, writeExampleConfig } from "../config.js";
|
|
2
|
+
import { discoverLocalTools } from "../discovery.js";
|
|
3
|
+
import { detectedAgentNames } from "../agentRegistry.js";
|
|
4
|
+
import { createTranslator, DEFAULT_LANGUAGE, resolveLanguage } from "../i18n.js";
|
|
5
|
+
import { optionalString } from "./shared.js";
|
|
6
|
+
/** Exécute `palabre init` ou son alias `setup`. */
|
|
7
|
+
export async function runInitCommand(flags) {
|
|
8
|
+
const configPath = optionalString(flags.config) ?? (flags.local ? DEFAULT_CONFIG_PATH : GLOBAL_CONFIG_PATH);
|
|
9
|
+
const startupMessages = createTranslator(resolveLanguage({ explicitLanguage: optionalString(flags.language) }));
|
|
10
|
+
if (await configExists(configPath)) {
|
|
11
|
+
console.log(startupMessages.init.configExists(configPath));
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const discovery = await discoverLocalTools({ ollamaUrl: optionalString(flags["ollama-url"]) });
|
|
15
|
+
const config = createConfigFromDiscovery(discovery);
|
|
16
|
+
config.language = resolveLanguage({
|
|
17
|
+
explicitLanguage: optionalString(flags.language),
|
|
18
|
+
configLanguage: config.language
|
|
19
|
+
});
|
|
20
|
+
const messages = createTranslator(config.language);
|
|
21
|
+
await writeExampleConfig(configPath, config);
|
|
22
|
+
console.log(messages.init.configCreated(configPath));
|
|
23
|
+
printInitDiscovery(discovery, config, messages);
|
|
24
|
+
}
|
|
25
|
+
function printInitDiscovery(discovery, config, messages) {
|
|
26
|
+
console.log("");
|
|
27
|
+
console.log(messages.init.localDetectionTitle);
|
|
28
|
+
console.log(`- Codex CLI: ${formatCommandDetection(discovery.codex, messages)}`);
|
|
29
|
+
console.log(`- Claude CLI: ${formatCommandDetection(discovery.claude, messages)}`);
|
|
30
|
+
console.log(`- Antigravity CLI: ${formatCommandDetection(discovery.antigravity, messages)}`);
|
|
31
|
+
console.log(`- OpenCode CLI: ${formatCommandDetection(discovery.opencode, messages)}`);
|
|
32
|
+
console.log(`- Mistral Vibe CLI: ${formatCommandDetection(discovery.vibe, messages)}`);
|
|
33
|
+
console.log(`- Ollama API: ${formatOllamaDetection(discovery.ollama, messages)}`);
|
|
34
|
+
console.log("");
|
|
35
|
+
console.log(config.defaults?.agentA && config.defaults.agentB
|
|
36
|
+
? messages.init.defaults(config.defaults.agentA, config.defaults.agentB)
|
|
37
|
+
: messages.init.noDefaultPair(formatDetectedAgentSummary(discovery, config.language ?? DEFAULT_LANGUAGE)));
|
|
38
|
+
console.log(messages.init.languageHint(config.language ?? DEFAULT_LANGUAGE));
|
|
39
|
+
}
|
|
40
|
+
function formatDetectedAgentSummary(discovery, language) {
|
|
41
|
+
const names = detectedAgentNames(discovery);
|
|
42
|
+
if (names.length === 0)
|
|
43
|
+
return language === "en" ? "no agent detected" : "aucun agent détecté";
|
|
44
|
+
if (names.length === 1) {
|
|
45
|
+
return language === "en" ? `only one agent detected (${names[0]})` : `un seul agent détecté (${names[0]})`;
|
|
46
|
+
}
|
|
47
|
+
return language === "en"
|
|
48
|
+
? `no usable pair detected among ${names.join(", ")}`
|
|
49
|
+
: `aucune paire utilisable détectée parmi ${names.join(", ")}`;
|
|
50
|
+
}
|
|
51
|
+
function formatCommandDetection(detection, messages) {
|
|
52
|
+
return detection.available ? messages.init.commandDetected(detection.command) : messages.init.commandMissing;
|
|
53
|
+
}
|
|
54
|
+
function formatOllamaDetection(detection, messages) {
|
|
55
|
+
if (!detection.available) {
|
|
56
|
+
return detection.commandAvailable ? messages.init.ollamaServerUnreachable(detection.baseUrl) : messages.init.ollamaMissing;
|
|
57
|
+
}
|
|
58
|
+
return messages.init.ollamaDetected(detection.models.length);
|
|
59
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { configExists, createConfigFromDiscovery, loadConfig, resolveDefaultConfigPath } from "../config.js";
|
|
2
|
+
import { discoverLocalTools, discoverLocalToolsForConfig } from "../discovery.js";
|
|
3
|
+
import { createTranslator, resolveLanguage } from "../i18n.js";
|
|
4
|
+
import { listPresetsWithAvailability } from "../presets.js";
|
|
5
|
+
import { optionalString } from "./shared.js";
|
|
6
|
+
/** Exécute `palabre presets` en sortie humaine ou JSON versionné. */
|
|
7
|
+
export async function runPresetsCommand(flags) {
|
|
8
|
+
const configPath = optionalString(flags.config) ?? await resolveDefaultConfigPath();
|
|
9
|
+
const ollamaUrl = optionalString(flags["ollama-url"]);
|
|
10
|
+
const config = await configExists(configPath) ? await loadConfig(configPath) : undefined;
|
|
11
|
+
const discovery = config
|
|
12
|
+
? await discoverLocalToolsForConfig(config, ollamaUrl)
|
|
13
|
+
: await discoverLocalTools({ ollamaUrl });
|
|
14
|
+
const resolvedConfig = config ?? createConfigFromDiscovery(discovery);
|
|
15
|
+
const messages = createTranslator(resolveLanguage({
|
|
16
|
+
explicitLanguage: optionalString(flags.language),
|
|
17
|
+
configLanguage: resolvedConfig.language
|
|
18
|
+
}));
|
|
19
|
+
const presets = listPresetsWithAvailability(resolvedConfig, discovery, messages);
|
|
20
|
+
if (flags.json) {
|
|
21
|
+
process.stdout.write(JSON.stringify({ v: 1, presets }) + "\n");
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
console.log(messages.presets.title);
|
|
25
|
+
console.log("");
|
|
26
|
+
for (const preset of presets) {
|
|
27
|
+
const status = preset.available
|
|
28
|
+
? messages.presets.available
|
|
29
|
+
: messages.presets.unavailable(preset.unavailableReasons.join("; "));
|
|
30
|
+
console.log(` ${preset.name.padEnd(20)} ${preset.agentA} <-> ${preset.agentB} ${status}`);
|
|
31
|
+
}
|
|
32
|
+
console.log("");
|
|
33
|
+
console.log(messages.presets.total(presets.length));
|
|
34
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { configExists, loadConfig, resolveDefaultConfigPath } from "../config.js";
|
|
2
|
+
import { createTranslator, resolveLanguage } from "../i18n.js";
|
|
3
|
+
import { applySourceUpdate, formatUpdateInstructions, getUpdateInfo } from "../update.js";
|
|
4
|
+
import { getPackageVersion } from "../version.js";
|
|
5
|
+
import { optionalString } from "./shared.js";
|
|
6
|
+
/** Exécute `palabre update`, avec application explicite pour un checkout source. */
|
|
7
|
+
export async function runUpdateCommand(flags) {
|
|
8
|
+
const info = await getUpdateInfo(await getPackageVersion());
|
|
9
|
+
const configPath = optionalString(flags.config) ?? await resolveDefaultConfigPath();
|
|
10
|
+
const config = await configExists(configPath) ? await loadConfig(configPath) : undefined;
|
|
11
|
+
const messages = createTranslator(resolveLanguage({
|
|
12
|
+
explicitLanguage: optionalString(flags.language),
|
|
13
|
+
configLanguage: config?.language
|
|
14
|
+
}));
|
|
15
|
+
if (flags.apply) {
|
|
16
|
+
await applySourceUpdate(info, messages);
|
|
17
|
+
console.log(messages.update.upToDate);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
console.log(formatUpdateInstructions(info, messages));
|
|
21
|
+
}
|
package/dist/config.js
CHANGED
|
@@ -270,6 +270,14 @@ export function setOllamaModel(config, model) {
|
|
|
270
270
|
nextModel: agent.model
|
|
271
271
|
};
|
|
272
272
|
}
|
|
273
|
+
/** Met à jour l'adresse persistante de tous les agents Ollama configurés. */
|
|
274
|
+
export function setOllamaBaseUrl(config, baseUrl) {
|
|
275
|
+
const agents = Object.values(config.agents).filter((agent) => agent.type === "ollama");
|
|
276
|
+
for (const agent of agents) {
|
|
277
|
+
agent.baseUrl = baseUrl;
|
|
278
|
+
}
|
|
279
|
+
return agents.length;
|
|
280
|
+
}
|
|
273
281
|
/** Écrit `config` sérialisé en JSON dans `configPath`. Crée le répertoire parent si nécessaire. */
|
|
274
282
|
export async function writeExampleConfig(configPath = DEFAULT_CONFIG_PATH, config = exampleConfig) {
|
|
275
283
|
const resolved = path.resolve(configPath);
|
package/dist/discovery.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { access } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { executableExtensions } from "./exec.js";
|
|
4
|
+
import { configuredOllamaTargets, resolveOllamaBaseUrl } from "./ollamaUrl.js";
|
|
4
5
|
/**
|
|
5
6
|
* Détecte en parallèle toutes les CLIs supportées et le serveur Ollama local.
|
|
6
7
|
* Sur Windows, tente `claude.exe` avant `claude`.
|
|
7
8
|
* Antigravity est exposé selon les installations sous `agy` ou `antigravity`.
|
|
8
9
|
*/
|
|
9
|
-
export async function discoverLocalTools() {
|
|
10
|
+
export async function discoverLocalTools(options = {}) {
|
|
10
11
|
const [codex, claude, antigravity, opencode, vibe, ollamaCommand] = await Promise.all([
|
|
11
12
|
detectCommand("codex"),
|
|
12
13
|
detectFirstCommand(process.platform === "win32" ? ["claude.exe", "claude"] : ["claude"]),
|
|
@@ -15,17 +16,39 @@ export async function discoverLocalTools() {
|
|
|
15
16
|
detectCommand("vibe"),
|
|
16
17
|
detectCommand("ollama")
|
|
17
18
|
]);
|
|
18
|
-
const
|
|
19
|
+
const configuredTargets = Object.entries(options.ollamaTargets ?? {});
|
|
20
|
+
const targets = configuredTargets.length > 0
|
|
21
|
+
? configuredTargets
|
|
22
|
+
: [["ollama-local", options.ollamaConfigUrl]];
|
|
23
|
+
const resolvedTargets = targets.map(([name, configUrl]) => ({
|
|
24
|
+
name,
|
|
25
|
+
baseUrl: resolveOllamaBaseUrl({
|
|
26
|
+
cliUrl: options.ollamaUrl,
|
|
27
|
+
configUrl
|
|
28
|
+
})
|
|
29
|
+
}));
|
|
30
|
+
const uniqueUrls = resolvedTargets
|
|
31
|
+
.map((target) => target.baseUrl)
|
|
32
|
+
.filter((baseUrl, index, urls) => urls.indexOf(baseUrl) === index);
|
|
33
|
+
const servers = await Promise.all(uniqueUrls.map(async (baseUrl) => [
|
|
34
|
+
baseUrl,
|
|
35
|
+
await detectOllamaServer(baseUrl)
|
|
36
|
+
]));
|
|
37
|
+
const serversByUrl = new Map(servers);
|
|
38
|
+
const ollamaAgents = Object.fromEntries(resolvedTargets.map(({ name, baseUrl }) => [name, {
|
|
39
|
+
...serversByUrl.get(baseUrl),
|
|
40
|
+
commandAvailable: ollamaCommand.available
|
|
41
|
+
}]));
|
|
42
|
+
const primaryName = ollamaAgents["ollama-local"] ? "ollama-local" : resolvedTargets[0].name;
|
|
43
|
+
const ollamaServer = ollamaAgents[primaryName];
|
|
19
44
|
return {
|
|
20
45
|
codex,
|
|
21
46
|
claude,
|
|
22
47
|
antigravity,
|
|
23
48
|
opencode,
|
|
24
49
|
vibe,
|
|
25
|
-
ollama:
|
|
26
|
-
|
|
27
|
-
commandAvailable: ollamaCommand.available
|
|
28
|
-
}
|
|
50
|
+
ollama: ollamaServer,
|
|
51
|
+
ollamaAgents
|
|
29
52
|
};
|
|
30
53
|
}
|
|
31
54
|
async function detectFirstCommand(commands) {
|
|
@@ -111,3 +134,10 @@ async function isAccessible(filePath) {
|
|
|
111
134
|
return false;
|
|
112
135
|
}
|
|
113
136
|
}
|
|
137
|
+
/** Détecte les outils en tenant compte de tous les serveurs Ollama configurés. */
|
|
138
|
+
export function discoverLocalToolsForConfig(config, ollamaUrl) {
|
|
139
|
+
return discoverLocalTools({
|
|
140
|
+
ollamaUrl,
|
|
141
|
+
ollamaTargets: configuredOllamaTargets(config)
|
|
142
|
+
});
|
|
143
|
+
}
|
package/dist/doctor.js
CHANGED
|
@@ -5,6 +5,7 @@ import { detectedAgentNames, detectionForCommand } from "./agentRegistry.js";
|
|
|
5
5
|
import { discoverLocalTools } from "./discovery.js";
|
|
6
6
|
import { createTranslator, resolveLanguage } from "./i18n.js";
|
|
7
7
|
import { DEFAULT_TURNS, MAX_TURNS } from "./limits.js";
|
|
8
|
+
import { configuredOllamaTargets, normalizeOllamaBaseUrl } from "./ollamaUrl.js";
|
|
8
9
|
import { compareSemver, getLatestPackageVersion, getPackageVersion } from "./version.js";
|
|
9
10
|
/**
|
|
10
11
|
* Exécute le diagnostic complet : config, outils locaux et agents.
|
|
@@ -39,7 +40,11 @@ export async function runDoctor(explicitConfigPath, plain = false, explicitLangu
|
|
|
39
40
|
lines.push(ok(t.doctor.configReadable));
|
|
40
41
|
lines.push(ok(t.doctor.interfaceLanguage(language)));
|
|
41
42
|
await inspectConfig(config, lines, t);
|
|
42
|
-
const
|
|
43
|
+
const ollamaTargets = Object.fromEntries(Object.entries(configuredOllamaTargets(config))
|
|
44
|
+
.map(([name, value]) => [name, value && isValidOllamaBaseUrl(value) ? value : undefined]));
|
|
45
|
+
const discovery = await discoverLocalTools({
|
|
46
|
+
ollamaTargets
|
|
47
|
+
});
|
|
43
48
|
lines.push(info(t.doctor.localTools, "tools"));
|
|
44
49
|
lines.push(formatCommand("Codex CLI", discovery.codex.available, discovery.codex.command, discovery.codex.path, t));
|
|
45
50
|
lines.push(formatCommand("Claude CLI", discovery.claude.available, discovery.claude.command, discovery.claude.path, t));
|
|
@@ -204,13 +209,22 @@ function inspectAgentShape(name, agent, lines, t) {
|
|
|
204
209
|
if (!agent.model || !agent.model.trim()) {
|
|
205
210
|
lines.push(error(t.doctor.ollamaModelMissing(name)));
|
|
206
211
|
}
|
|
207
|
-
if (agent.baseUrl &&
|
|
212
|
+
if (agent.baseUrl && !isValidOllamaBaseUrl(agent.baseUrl)) {
|
|
208
213
|
lines.push(error(t.doctor.ollamaBaseUrlInvalid(name, agent.baseUrl)));
|
|
209
214
|
}
|
|
210
215
|
if (agent.timeoutMs !== undefined && (!Number.isFinite(agent.timeoutMs) || agent.timeoutMs <= 0)) {
|
|
211
216
|
lines.push(error(t.doctor.positiveTimeout(name, "timeoutMs")));
|
|
212
217
|
}
|
|
213
218
|
}
|
|
219
|
+
function isValidOllamaBaseUrl(value) {
|
|
220
|
+
try {
|
|
221
|
+
normalizeOllamaBaseUrl(value);
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
214
228
|
function inspectCliAgent(name, agent, discovery, lines, t) {
|
|
215
229
|
const known = detectionForCommand(agent.command, discovery);
|
|
216
230
|
const prefix = `${name} [cli:${agent.role}] command=${agent.command}`;
|
|
@@ -224,7 +238,8 @@ function inspectCliAgent(name, agent, discovery, lines, t) {
|
|
|
224
238
|
}
|
|
225
239
|
function inspectOllamaAgent(name, agent, discovery, lines, t) {
|
|
226
240
|
const prefix = `${name} [ollama:${agent.role}] model=${agent.model}`;
|
|
227
|
-
|
|
241
|
+
const ollama = discovery.ollamaAgents?.[name] ?? discovery.ollama;
|
|
242
|
+
if (!ollama.available) {
|
|
228
243
|
lines.push(warn(t.doctor.ollamaNotVerifiable(prefix)));
|
|
229
244
|
return;
|
|
230
245
|
}
|
|
@@ -232,7 +247,7 @@ function inspectOllamaAgent(name, agent, discovery, lines, t) {
|
|
|
232
247
|
lines.push(info(t.doctor.ollamaValidateFalse(prefix)));
|
|
233
248
|
return;
|
|
234
249
|
}
|
|
235
|
-
const installed =
|
|
250
|
+
const installed = ollama.models.includes(agent.model);
|
|
236
251
|
lines.push(installed
|
|
237
252
|
? ok(t.doctor.ollamaInstalled(prefix))
|
|
238
253
|
: warn(t.doctor.ollamaMissing(prefix, agent.model)));
|