palabre 0.9.0 → 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 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.
@@ -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,4 @@
1
+ /** Extrait une chaîne non vide depuis une valeur de flag. */
2
+ export function optionalString(value) {
3
+ return typeof value === "string" && value.trim() ? value : undefined;
4
+ }
@@ -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/discovery.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { access } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { executableExtensions } from "./exec.js";
4
- import { resolveOllamaBaseUrl } from "./ollamaUrl.js";
4
+ import { configuredOllamaTargets, resolveOllamaBaseUrl } from "./ollamaUrl.js";
5
5
  /**
6
6
  * Détecte en parallèle toutes les CLIs supportées et le serveur Ollama local.
7
7
  * Sur Windows, tente `claude.exe` avant `claude`.
@@ -134,3 +134,10 @@ async function isAccessible(filePath) {
134
134
  return false;
135
135
  }
136
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
+ }