palabre 0.6.0 → 0.6.3
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 +33 -3
- package/dist/adapters/cli-pty.js +16 -28
- package/dist/adapters/cli-shared.js +24 -0
- package/dist/adapters/cli.js +61 -23
- package/dist/adapters/ollama.js +2 -1
- package/dist/agentRegistry.js +76 -0
- package/dist/args.js +265 -0
- package/dist/config.js +26 -20
- package/dist/contextScan.js +48 -0
- package/dist/discovery.js +1 -12
- package/dist/doctor.js +2 -27
- package/dist/exec.js +17 -0
- package/dist/index.js +38 -258
- package/dist/messages/adapter-errors.js +2 -0
- package/dist/messages/common.js +6 -0
- package/dist/messages/help.js +22 -0
- package/dist/messages/orchestrator.js +19 -0
- package/dist/messages/output.js +14 -2
- package/dist/messages/prompt.js +6 -2
- package/dist/new.js +1 -26
- package/dist/orchestrator.js +79 -30
- package/dist/output.js +20 -3
- package/dist/presets.js +2 -21
- package/dist/prompt.js +4 -0
- package/dist/renderers/console.js +15 -0
- package/dist/renderers/ndjson.js +4 -0
- package/package.json +3 -2
package/dist/config.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { applyDetectedCommands } from "./agentRegistry.js";
|
|
4
5
|
export const DEFAULT_CONFIG_PATH = "palabre.config.json";
|
|
5
6
|
export const LEGACY_CONFIG_PATH = "chicane.config.json";
|
|
6
7
|
export const CONFIG_DIR_NAME = ".palabre";
|
|
@@ -118,6 +119,30 @@ export async function loadConfig(configPath = DEFAULT_CONFIG_PATH) {
|
|
|
118
119
|
const raw = await readFile(resolved, "utf8");
|
|
119
120
|
return JSON.parse(raw);
|
|
120
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* Valide qu'une config chargée est exploitable pour lancer un débat.
|
|
124
|
+
*
|
|
125
|
+
* `loadConfig` se contente de parser le JSON ; cette garde attrape les configs
|
|
126
|
+
* structurellement cassées (racine non-objet, bloc `agents` absent ou vide)
|
|
127
|
+
* avant qu'elles ne provoquent un `TypeError` opaque dans l'orchestrateur.
|
|
128
|
+
* Volontairement minimale : la validation sémantique fine (agents par défaut
|
|
129
|
+
* inconnus, timeouts invalides, etc.) reste du ressort de `palabre doctor`.
|
|
130
|
+
*
|
|
131
|
+
* @throws {Error} message actionnable si la config ne peut pas faire tourner un débat.
|
|
132
|
+
*/
|
|
133
|
+
export function assertRunnableConfig(config, messages, configPath = DEFAULT_CONFIG_PATH) {
|
|
134
|
+
const root = config;
|
|
135
|
+
if (!root || typeof root !== "object" || Array.isArray(root)) {
|
|
136
|
+
throw new Error(messages.common.configInvalidShape(configPath));
|
|
137
|
+
}
|
|
138
|
+
const agents = root.agents;
|
|
139
|
+
if (!agents || typeof agents !== "object" || Array.isArray(agents)) {
|
|
140
|
+
throw new Error(messages.common.configMissingAgents(configPath));
|
|
141
|
+
}
|
|
142
|
+
if (Object.keys(agents).length === 0) {
|
|
143
|
+
throw new Error(messages.common.configEmptyAgents(configPath));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
121
146
|
/** Retourne `true` si le fichier de config est accessible en lecture. Silencieux sur toute erreur filesystem. */
|
|
122
147
|
export async function configExists(configPath = DEFAULT_CONFIG_PATH) {
|
|
123
148
|
try {
|
|
@@ -156,26 +181,7 @@ export async function resolveDefaultConfigPath() {
|
|
|
156
181
|
export function createConfigFromDiscovery(discovery) {
|
|
157
182
|
const config = cloneConfig(exampleConfig);
|
|
158
183
|
const pair = chooseDefaultPair(discovery);
|
|
159
|
-
config
|
|
160
|
-
...config.agents.codex,
|
|
161
|
-
...(discovery.codex.available ? { command: discovery.codex.command } : {})
|
|
162
|
-
};
|
|
163
|
-
config.agents.claude = {
|
|
164
|
-
...config.agents.claude,
|
|
165
|
-
...(discovery.claude.available ? { command: discovery.claude.command } : {})
|
|
166
|
-
};
|
|
167
|
-
config.agents.gemini = {
|
|
168
|
-
...config.agents.gemini,
|
|
169
|
-
...(discovery.gemini.available ? { command: discovery.gemini.command } : {})
|
|
170
|
-
};
|
|
171
|
-
config.agents.antigravity = {
|
|
172
|
-
...config.agents.antigravity,
|
|
173
|
-
...(discovery.antigravity.available ? { command: discovery.antigravity.command } : {})
|
|
174
|
-
};
|
|
175
|
-
config.agents.opencode = {
|
|
176
|
-
...config.agents.opencode,
|
|
177
|
-
...(discovery.opencode.available ? { command: discovery.opencode.command } : {})
|
|
178
|
-
};
|
|
184
|
+
applyDetectedCommands(config, discovery);
|
|
179
185
|
const ollamaAgent = config.agents["ollama-local"];
|
|
180
186
|
if (ollamaAgent?.type === "ollama") {
|
|
181
187
|
ollamaAgent.model = chooseDefaultOllamaModel(discovery);
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { loadProjectInputs } from "./context.js";
|
|
3
|
+
import { createTranslator } from "./i18n.js";
|
|
4
|
+
/**
|
|
5
|
+
* Builds the machine-readable context preview used by integrations.
|
|
6
|
+
*
|
|
7
|
+
* The scan intentionally reuses the same tolerant loader as `--context`, so
|
|
8
|
+
* the returned files are the files Palabre would actually inject into a debate.
|
|
9
|
+
*/
|
|
10
|
+
export async function buildContextScan(scanPaths, cwd = process.cwd(), messages = createTranslator("fr")) {
|
|
11
|
+
const effectiveScanPaths = scanPaths.length > 0 ? scanPaths : ["."];
|
|
12
|
+
const result = await loadProjectInputs([], effectiveScanPaths, cwd, messages);
|
|
13
|
+
const files = result.files.map((file) => ({
|
|
14
|
+
kind: "file",
|
|
15
|
+
path: file.path,
|
|
16
|
+
absolutePath: file.absolutePath,
|
|
17
|
+
sizeBytes: file.sizeBytes
|
|
18
|
+
}));
|
|
19
|
+
const folders = collectContextFolders(files.map((file) => file.path), cwd);
|
|
20
|
+
return {
|
|
21
|
+
v: 1,
|
|
22
|
+
root: cwd,
|
|
23
|
+
scanned: effectiveScanPaths,
|
|
24
|
+
items: [...folders, ...files],
|
|
25
|
+
warnings: result.warnings
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function collectContextFolders(filePaths, cwd) {
|
|
29
|
+
const counts = new Map();
|
|
30
|
+
if (filePaths.length > 0) {
|
|
31
|
+
counts.set(".", filePaths.length);
|
|
32
|
+
}
|
|
33
|
+
for (const filePath of filePaths) {
|
|
34
|
+
const parts = filePath.split("/").filter(Boolean);
|
|
35
|
+
for (let index = 1; index < parts.length; index += 1) {
|
|
36
|
+
const folder = parts.slice(0, index).join("/");
|
|
37
|
+
counts.set(folder, (counts.get(folder) ?? 0) + 1);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return [...counts.entries()]
|
|
41
|
+
.sort(([left], [right]) => left === "." ? -1 : right === "." ? 1 : left.localeCompare(right))
|
|
42
|
+
.map(([folder, filesCount]) => ({
|
|
43
|
+
kind: "folder",
|
|
44
|
+
path: folder,
|
|
45
|
+
absolutePath: path.resolve(cwd, folder),
|
|
46
|
+
filesCount
|
|
47
|
+
}));
|
|
48
|
+
}
|
package/dist/discovery.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { access } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { executableExtensions } from "./exec.js";
|
|
3
4
|
/**
|
|
4
5
|
* Détecte en parallèle toutes les CLIs supportées et le serveur Ollama local.
|
|
5
6
|
* Sur Windows, tente `claude.exe` avant `claude`.
|
|
@@ -100,18 +101,6 @@ async function findExecutable(command) {
|
|
|
100
101
|
}
|
|
101
102
|
return undefined;
|
|
102
103
|
}
|
|
103
|
-
function executableExtensions(command) {
|
|
104
|
-
if (path.extname(command)) {
|
|
105
|
-
return [""];
|
|
106
|
-
}
|
|
107
|
-
if (process.platform !== "win32") {
|
|
108
|
-
return [""];
|
|
109
|
-
}
|
|
110
|
-
return (process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD")
|
|
111
|
-
.split(";")
|
|
112
|
-
.map((extension) => extension.toLowerCase())
|
|
113
|
-
.concat(".ps1", "");
|
|
114
|
-
}
|
|
115
104
|
async function isAccessible(filePath) {
|
|
116
105
|
try {
|
|
117
106
|
await access(filePath);
|
package/dist/doctor.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { stat } from "node:fs/promises";
|
|
3
3
|
import { configExists, loadConfig, resolveDefaultConfigPath, resolveOutputDir } from "./config.js";
|
|
4
|
+
import { detectedAgentNames, detectionForCommand } from "./agentRegistry.js";
|
|
4
5
|
import { discoverLocalTools } from "./discovery.js";
|
|
5
6
|
import { createTranslator, resolveLanguage } from "./i18n.js";
|
|
6
7
|
import { DEFAULT_TURNS, MAX_TURNS } from "./limits.js";
|
|
@@ -194,7 +195,7 @@ function inspectAgentShape(name, agent, lines, t) {
|
|
|
194
195
|
}
|
|
195
196
|
}
|
|
196
197
|
function inspectCliAgent(name, agent, discovery, lines, t) {
|
|
197
|
-
const known =
|
|
198
|
+
const known = detectionForCommand(agent.command, discovery);
|
|
198
199
|
const prefix = `${name} [cli:${agent.role}] command=${agent.command}`;
|
|
199
200
|
if (!known) {
|
|
200
201
|
lines.push(info(t.doctor.customCommand(prefix)));
|
|
@@ -219,37 +220,11 @@ function inspectOllamaAgent(name, agent, discovery, lines, t) {
|
|
|
219
220
|
? ok(t.doctor.ollamaInstalled(prefix))
|
|
220
221
|
: warn(t.doctor.ollamaMissing(prefix, agent.model)));
|
|
221
222
|
}
|
|
222
|
-
function detectedAgentNames(discovery) {
|
|
223
|
-
return [
|
|
224
|
-
discovery.codex.available ? "codex" : undefined,
|
|
225
|
-
discovery.claude.available ? "claude" : undefined,
|
|
226
|
-
discovery.gemini.available ? "gemini" : undefined,
|
|
227
|
-
discovery.antigravity.available ? "antigravity" : undefined,
|
|
228
|
-
discovery.opencode.available ? "opencode" : undefined,
|
|
229
|
-
discovery.ollama.available ? "ollama-local" : undefined
|
|
230
|
-
].filter((name) => Boolean(name));
|
|
231
|
-
}
|
|
232
223
|
function formatCommand(label, available, command, resolvedPath, t) {
|
|
233
224
|
return available
|
|
234
225
|
? ok(t.doctor.commandDetected(label, resolvedPath ?? command))
|
|
235
226
|
: warn(t.doctor.commandMissing(label));
|
|
236
227
|
}
|
|
237
|
-
function knownCliDetection(command, discovery) {
|
|
238
|
-
const normalized = path.basename(command).toLowerCase().replace(/\.(exe|cmd|bat)$/i, "");
|
|
239
|
-
if (normalized === "codex")
|
|
240
|
-
return discovery.codex;
|
|
241
|
-
if (normalized === "claude")
|
|
242
|
-
return discovery.claude;
|
|
243
|
-
if (normalized === "gemini")
|
|
244
|
-
return discovery.gemini;
|
|
245
|
-
if (normalized === "agy")
|
|
246
|
-
return discovery.antigravity;
|
|
247
|
-
if (normalized === "antigravity")
|
|
248
|
-
return discovery.antigravity;
|
|
249
|
-
if (normalized === "opencode")
|
|
250
|
-
return discovery.opencode;
|
|
251
|
-
return undefined;
|
|
252
|
-
}
|
|
253
228
|
function render(lines, plain, t) {
|
|
254
229
|
const hasErrors = lines.some((line) => line.level === "error");
|
|
255
230
|
return {
|
package/dist/exec.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
/**
|
|
3
|
+
* Extensions exécutables candidates pour résoudre une commande dans le PATH.
|
|
4
|
+
*
|
|
5
|
+
* Retourne `[""]` quand la commande porte déjà une extension ou hors Windows.
|
|
6
|
+
* Sur Windows sans extension, dérive la liste de `PATHEXT` et ajoute `.ps1`
|
|
7
|
+
* ainsi que la candidate vide (binaire sans extension).
|
|
8
|
+
*/
|
|
9
|
+
export function executableExtensions(command) {
|
|
10
|
+
if (path.extname(command) || process.platform !== "win32") {
|
|
11
|
+
return [""];
|
|
12
|
+
}
|
|
13
|
+
return (process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD")
|
|
14
|
+
.split(";")
|
|
15
|
+
.map((extension) => extension.toLowerCase())
|
|
16
|
+
.concat(".ps1", "");
|
|
17
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { configExists, createConfigFromDiscovery, DEFAULT_CONFIG_PATH, GLOBAL_CONFIG_PATH, loadConfig, resolveDefaultConfigPath, resolveOutputDir, writeExampleConfig } from "./config.js";
|
|
5
|
+
import { assertRunnableConfig, configExists, createConfigFromDiscovery, DEFAULT_CONFIG_PATH, GLOBAL_CONFIG_PATH, loadConfig, resolveDefaultConfigPath, resolveOutputDir, writeExampleConfig } from "./config.js";
|
|
6
6
|
import { loadProjectInputs } from "./context.js";
|
|
7
|
+
import { buildContextScan } from "./contextScan.js";
|
|
7
8
|
import { discoverLocalTools } from "./discovery.js";
|
|
8
9
|
import { runDoctor } from "./doctor.js";
|
|
9
10
|
import { AdapterError, formatAdapterError } from "./errors.js";
|
|
@@ -19,6 +20,8 @@ import { runDebate } from "./orchestrator.js";
|
|
|
19
20
|
import { writeDebateMarkdown } from "./output.js";
|
|
20
21
|
import { applySourceUpdate, formatUpdateInstructions, getUpdateInfo } from "./update.js";
|
|
21
22
|
import { createSessionContext } from "./session.js";
|
|
23
|
+
import { getStringListFlag, parseArgs } from "./args.js";
|
|
24
|
+
import { detectedAgentNames, detectionForCommand } from "./agentRegistry.js";
|
|
22
25
|
/** Point d'entrée principal du CLI Palabre. Dispatche vers la commande appropriée selon les arguments. */
|
|
23
26
|
async function main() {
|
|
24
27
|
const rawArgs = process.argv.slice(2);
|
|
@@ -51,6 +54,10 @@ async function main() {
|
|
|
51
54
|
await runPresetsCommand(parsed.flags);
|
|
52
55
|
return;
|
|
53
56
|
}
|
|
57
|
+
if (parsed.command === "context") {
|
|
58
|
+
await runContextCommand(parsed.flags, parsed.positionals);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
54
61
|
if (parsed.command === "update") {
|
|
55
62
|
const info = await getUpdateInfo(await getPackageVersion());
|
|
56
63
|
const updateConfigPath = optionalString(parsed.flags.config) ?? await resolveDefaultConfigPath();
|
|
@@ -106,6 +113,7 @@ async function main() {
|
|
|
106
113
|
configLanguage: config.language
|
|
107
114
|
});
|
|
108
115
|
const messages = createTranslator(language);
|
|
116
|
+
assertRunnableConfig(config, messages, configPath);
|
|
109
117
|
if (parsed.command === "new") {
|
|
110
118
|
const selection = await runNewWizard(config, messages);
|
|
111
119
|
if (!selection) {
|
|
@@ -168,8 +176,11 @@ async function main() {
|
|
|
168
176
|
const renderer = createRendererFromFlags(parsed.flags, options.plainOutput, messages);
|
|
169
177
|
context.warnings.forEach((warning) => renderer.warning(warning));
|
|
170
178
|
const result = await runDebate(config, options, renderer, messages);
|
|
171
|
-
const outputPath = await writeDebateMarkdown(resolveOutputDir(config.outputDir), result.options, result.messages, result.summary, result.stopReason, messages);
|
|
179
|
+
const outputPath = await writeDebateMarkdown(resolveOutputDir(config.outputDir), result.options, result.messages, result.summary, result.stopReason, messages, result.failure);
|
|
172
180
|
renderer.done(outputPath);
|
|
181
|
+
if (result.failure) {
|
|
182
|
+
process.exitCode = 1;
|
|
183
|
+
}
|
|
173
184
|
}
|
|
174
185
|
/**
|
|
175
186
|
* Exécute la commande `agents` : charge la config et affiche les agents déclarés avec leur état de détection.
|
|
@@ -436,213 +447,30 @@ async function runPresetsCommand(flags) {
|
|
|
436
447
|
console.log("");
|
|
437
448
|
console.log(messages.presets.total(presets.length));
|
|
438
449
|
}
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
const
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
const presets = new Set(listPresetNames());
|
|
453
|
-
for (let index = 0; index < args.length; index += 1) {
|
|
454
|
-
const value = args[index];
|
|
455
|
-
if (!value.startsWith("-") && !commandExplicit && positionals.length === 0 && commands.has(value)) {
|
|
456
|
-
command = value;
|
|
457
|
-
commandExplicit = true;
|
|
458
|
-
continue;
|
|
459
|
-
}
|
|
460
|
-
if (!value.startsWith("-") && index === 0) {
|
|
461
|
-
if (commands.has(value)) {
|
|
462
|
-
command = value;
|
|
463
|
-
commandExplicit = true;
|
|
464
|
-
}
|
|
465
|
-
else if (isLikelyCommandTypo(value, commands)) {
|
|
466
|
-
throw new Error(messages.common.unknownCommand(value, Array.from(commands).join(", ")));
|
|
467
|
-
}
|
|
468
|
-
else {
|
|
469
|
-
positionals.push(value);
|
|
470
|
-
}
|
|
471
|
-
continue;
|
|
472
|
-
}
|
|
473
|
-
if (!value.startsWith("-")) {
|
|
474
|
-
positionals.push(value);
|
|
475
|
-
continue;
|
|
476
|
-
}
|
|
477
|
-
if (value === "-h") {
|
|
478
|
-
flags.help = true;
|
|
479
|
-
continue;
|
|
480
|
-
}
|
|
481
|
-
if (value === "-v") {
|
|
482
|
-
flags.version = true;
|
|
483
|
-
continue;
|
|
484
|
-
}
|
|
485
|
-
if (value === "-a") {
|
|
486
|
-
command = "agents";
|
|
487
|
-
commandExplicit = true;
|
|
488
|
-
continue;
|
|
489
|
-
}
|
|
490
|
-
if (value === "-s") {
|
|
491
|
-
const next = args[index + 1];
|
|
492
|
-
if (!next || next.startsWith("-")) {
|
|
493
|
-
throw new Error(messages.common.optionRequiresValue("-s"));
|
|
494
|
-
}
|
|
495
|
-
flags.topic = next;
|
|
496
|
-
index += 1;
|
|
497
|
-
continue;
|
|
498
|
-
}
|
|
499
|
-
if (value === "-t") {
|
|
500
|
-
const next = args[index + 1];
|
|
501
|
-
if (!next || next.startsWith("-")) {
|
|
502
|
-
throw new Error(messages.common.optionRequiresValue("-t"));
|
|
503
|
-
}
|
|
504
|
-
flags.turns = next;
|
|
505
|
-
index += 1;
|
|
506
|
-
continue;
|
|
507
|
-
}
|
|
508
|
-
if (value.startsWith("--")) {
|
|
509
|
-
const rawKey = value.slice(2);
|
|
510
|
-
const key = normalizeFlagName(rawKey);
|
|
511
|
-
if (key === "set-defaults") {
|
|
512
|
-
const values = [];
|
|
513
|
-
while (args[index + 1] && !args[index + 1].startsWith("-") && values.length < 2) {
|
|
514
|
-
values.push(args[index + 1]);
|
|
515
|
-
index += 1;
|
|
516
|
-
}
|
|
517
|
-
if (values.length !== 2) {
|
|
518
|
-
throw new Error(messages.common.setDefaultsRequiresTwo);
|
|
519
|
-
}
|
|
520
|
-
flags[key] = values;
|
|
521
|
-
continue;
|
|
522
|
-
}
|
|
523
|
-
if (key === "files" || key === "context") {
|
|
524
|
-
const values = [];
|
|
525
|
-
while (args[index + 1] && !args[index + 1].startsWith("-")) {
|
|
526
|
-
values.push(args[index + 1]);
|
|
527
|
-
index += 1;
|
|
528
|
-
}
|
|
529
|
-
flags[key] = [...getStringListFlag(flags[key]), ...values];
|
|
530
|
-
continue;
|
|
531
|
-
}
|
|
532
|
-
const next = args[index + 1];
|
|
533
|
-
if (!next || next.startsWith("-")) {
|
|
534
|
-
if (requiresFlagValue(key)) {
|
|
535
|
-
throw new Error(messages.common.optionRequiresValue(`--${rawKey}`));
|
|
536
|
-
}
|
|
537
|
-
flags[key] = true;
|
|
538
|
-
}
|
|
539
|
-
else {
|
|
540
|
-
flags[key] = next;
|
|
541
|
-
index += 1;
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
if (command === "run") {
|
|
546
|
-
applyRunPositionals(positionals, flags, presets, commandExplicit, commands, messages);
|
|
547
|
-
}
|
|
548
|
-
return { command, commandExplicit, flags };
|
|
549
|
-
}
|
|
550
|
-
/**
|
|
551
|
-
* Détecte si une valeur ressemble à une faute de frappe d'une commande connue
|
|
552
|
-
* (même première lettre et distance de Levenshtein ≤ 2).
|
|
553
|
-
* @param value - Token saisi par l'utilisateur.
|
|
554
|
-
* @param commands - Ensemble des commandes valides.
|
|
555
|
-
*/
|
|
556
|
-
function isLikelyCommandTypo(value, commands) {
|
|
557
|
-
const normalized = value.toLowerCase();
|
|
558
|
-
for (const command of commands) {
|
|
559
|
-
if (normalized[0] === command[0] && levenshteinDistance(normalized, command) <= 2) {
|
|
560
|
-
return true;
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
return false;
|
|
564
|
-
}
|
|
565
|
-
/**
|
|
566
|
-
* Calcule la distance de Levenshtein entre deux chaînes (insertions, suppressions, substitutions).
|
|
567
|
-
* @param left - Première chaîne.
|
|
568
|
-
* @param right - Deuxième chaîne.
|
|
569
|
-
* @returns Distance entière ≥ 0.
|
|
570
|
-
*/
|
|
571
|
-
function levenshteinDistance(left, right) {
|
|
572
|
-
const previous = Array.from({ length: right.length + 1 }, (_, index) => index);
|
|
573
|
-
for (let leftIndex = 0; leftIndex < left.length; leftIndex += 1) {
|
|
574
|
-
let diagonal = previous[0];
|
|
575
|
-
previous[0] = leftIndex + 1;
|
|
576
|
-
for (let rightIndex = 0; rightIndex < right.length; rightIndex += 1) {
|
|
577
|
-
const insertCost = previous[rightIndex + 1] + 1;
|
|
578
|
-
const deleteCost = previous[rightIndex] + 1;
|
|
579
|
-
const replaceCost = diagonal + (left[leftIndex] === right[rightIndex] ? 0 : 1);
|
|
580
|
-
diagonal = previous[rightIndex + 1];
|
|
581
|
-
previous[rightIndex + 1] = Math.min(insertCost, deleteCost, replaceCost);
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
return previous[right.length] ?? 0;
|
|
585
|
-
}
|
|
586
|
-
/**
|
|
587
|
-
* Interprète les arguments positionnels pour la commande `run` :
|
|
588
|
-
* premier positionnel = preset si connu, sinon sujet complet concaténé.
|
|
589
|
-
* @param positionals - Arguments positionnels extraits du parseur.
|
|
590
|
-
* @param flags - Map de flags à muter si un preset ou un sujet est détecté.
|
|
591
|
-
* @param presets - Ensemble des noms de presets valides.
|
|
592
|
-
* @param commandExplicit - `true` si l'utilisateur a tapé `palabre run` explicitement.
|
|
593
|
-
*/
|
|
594
|
-
function applyRunPositionals(positionals, flags, presets, commandExplicit, commands, messages) {
|
|
595
|
-
if (positionals.length === 0) {
|
|
450
|
+
async function runContextCommand(flags, positionals) {
|
|
451
|
+
const language = resolveLanguage({ explicitLanguage: optionalString(flags.language) });
|
|
452
|
+
const messages = createTranslator(language);
|
|
453
|
+
const subcommand = positionals[0] ?? "scan";
|
|
454
|
+
if (subcommand !== "scan") {
|
|
455
|
+
throw new Error(messages.common.unknownCommand(`context ${subcommand}`, "context scan"));
|
|
456
|
+
}
|
|
457
|
+
const paths = positionals.slice(1);
|
|
458
|
+
const result = await buildContextScan(paths, process.cwd(), messages);
|
|
459
|
+
const folders = result.items.filter((item) => item.kind === "folder");
|
|
460
|
+
const files = result.items.filter((item) => item.kind === "file");
|
|
461
|
+
if (flags.json) {
|
|
462
|
+
console.log(JSON.stringify(result, null, 2));
|
|
596
463
|
return;
|
|
597
464
|
}
|
|
598
|
-
const
|
|
599
|
-
|
|
600
|
-
flags.preset ??= first;
|
|
601
|
-
if (rest.length > 0) {
|
|
602
|
-
flags.topic ??= rest.join(" ");
|
|
603
|
-
}
|
|
604
|
-
return;
|
|
465
|
+
for (const folder of folders) {
|
|
466
|
+
console.log(`[folder] ${folder.path}`);
|
|
605
467
|
}
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
468
|
+
for (const file of files) {
|
|
469
|
+
console.log(`[file] ${file.path} (${file.sizeBytes} bytes)`);
|
|
470
|
+
}
|
|
471
|
+
for (const warning of result.warnings) {
|
|
472
|
+
console.error(`${messages.renderers.warningPrefix} ${warning}`);
|
|
611
473
|
}
|
|
612
|
-
flags.topic ??= positionals.join(" ");
|
|
613
|
-
}
|
|
614
|
-
/**
|
|
615
|
-
* Normalise un nom de flag long en son alias canonique (ex. `subject` → `topic`).
|
|
616
|
-
* @param value - Nom brut extrait après `--`.
|
|
617
|
-
*/
|
|
618
|
-
function normalizeFlagName(value) {
|
|
619
|
-
const aliases = {
|
|
620
|
-
lang: "language",
|
|
621
|
-
s: "topic",
|
|
622
|
-
subject: "topic",
|
|
623
|
-
t: "turns"
|
|
624
|
-
};
|
|
625
|
-
return aliases[value] ?? value;
|
|
626
|
-
}
|
|
627
|
-
/**
|
|
628
|
-
* Indique si un flag long nécessite une valeur suivante (lève une erreur si absente).
|
|
629
|
-
* @param value - Nom canonique du flag (sans `--`).
|
|
630
|
-
*/
|
|
631
|
-
function requiresFlagValue(value) {
|
|
632
|
-
return new Set([
|
|
633
|
-
"agent-a",
|
|
634
|
-
"agent-b",
|
|
635
|
-
"config",
|
|
636
|
-
"language",
|
|
637
|
-
"model-a",
|
|
638
|
-
"model-b",
|
|
639
|
-
"preset",
|
|
640
|
-
"summary-agent",
|
|
641
|
-
"summary-model",
|
|
642
|
-
"set-defaults",
|
|
643
|
-
"topic",
|
|
644
|
-
"turns"
|
|
645
|
-
]).has(value);
|
|
646
474
|
}
|
|
647
475
|
/** Lit la version depuis `package.json` adjacent au bundle compilé. */
|
|
648
476
|
async function getPackageVersion() {
|
|
@@ -651,20 +479,6 @@ async function getPackageVersion() {
|
|
|
651
479
|
const packageJson = JSON.parse(raw);
|
|
652
480
|
return packageJson.version ?? "0.0.0";
|
|
653
481
|
}
|
|
654
|
-
/**
|
|
655
|
-
* Normalise une valeur de flag multi-valeur en tableau de chaînes.
|
|
656
|
-
* @param value - Valeur brute (tableau, chaîne unique ou absent).
|
|
657
|
-
* @returns Tableau de chaînes, vide si la valeur n'est pas applicable.
|
|
658
|
-
*/
|
|
659
|
-
function getStringListFlag(value) {
|
|
660
|
-
if (Array.isArray(value)) {
|
|
661
|
-
return value;
|
|
662
|
-
}
|
|
663
|
-
if (typeof value === "string") {
|
|
664
|
-
return [value];
|
|
665
|
-
}
|
|
666
|
-
return [];
|
|
667
|
-
}
|
|
668
482
|
/**
|
|
669
483
|
* Écrit les avertissements de contexte sur `stderr`.
|
|
670
484
|
* @param warnings - Messages d'avertissement issus du chargement des fichiers de contexte.
|
|
@@ -695,15 +509,7 @@ function syncDetectedAgents(config, discovery) {
|
|
|
695
509
|
* @param discovery - Résultat de la découverte locale des outils.
|
|
696
510
|
*/
|
|
697
511
|
function findDetectedMissingAgents(config, discovery) {
|
|
698
|
-
|
|
699
|
-
discovery.codex.available ? "codex" : undefined,
|
|
700
|
-
discovery.claude.available ? "claude" : undefined,
|
|
701
|
-
discovery.gemini.available ? "gemini" : undefined,
|
|
702
|
-
discovery.antigravity.available ? "antigravity" : undefined,
|
|
703
|
-
discovery.opencode.available ? "opencode" : undefined,
|
|
704
|
-
discovery.ollama.available ? "ollama-local" : undefined
|
|
705
|
-
].filter((agent) => Boolean(agent));
|
|
706
|
-
return detectedAgents.filter((agentName) => !config.agents[agentName]);
|
|
512
|
+
return detectedAgentNames(discovery).filter((agentName) => !config.agents[agentName]);
|
|
707
513
|
}
|
|
708
514
|
/**
|
|
709
515
|
* Affiche la liste des agents déclarés avec leur type, rôle, état de détection et défauts.
|
|
@@ -774,34 +580,15 @@ function formatAgentDetection(name, agentConfig, discovery, messages) {
|
|
|
774
580
|
return detection.available ? messages.agents.detected(detection.command) : messages.agents.notDetected;
|
|
775
581
|
}
|
|
776
582
|
/**
|
|
777
|
-
* Résout l'entrée de détection correspondant à un agent CLI
|
|
583
|
+
* Résout l'entrée de détection correspondant à un agent CLI.
|
|
778
584
|
* Renvoie un objet `{ available: true }` pour les agents CLI non reconnus (considérés disponibles).
|
|
779
585
|
* @param name - Nom de l'agent dans la config.
|
|
780
586
|
* @param agentConfig - Configuration de l'agent.
|
|
781
587
|
* @param discovery - Résultat de la découverte locale des outils.
|
|
782
588
|
*/
|
|
783
589
|
function cliDetectionForAgent(name, agentConfig, discovery) {
|
|
784
|
-
const command =
|
|
785
|
-
|
|
786
|
-
return discovery.codex;
|
|
787
|
-
if (command === "claude")
|
|
788
|
-
return discovery.claude;
|
|
789
|
-
if (command === "gemini")
|
|
790
|
-
return discovery.gemini;
|
|
791
|
-
if (command === "agy")
|
|
792
|
-
return discovery.antigravity;
|
|
793
|
-
if (command === "antigravity")
|
|
794
|
-
return discovery.antigravity;
|
|
795
|
-
if (command === "opencode")
|
|
796
|
-
return discovery.opencode;
|
|
797
|
-
return { available: true, command: agentConfig.type === "cli" || agentConfig.type === "cli-pty" ? agentConfig.command : name };
|
|
798
|
-
}
|
|
799
|
-
/**
|
|
800
|
-
* Extrait le nom de base d'une commande en supprimant le chemin et l'extension Windows éventuelle.
|
|
801
|
-
* @param command - Chemin ou nom de commande brut (ex. `C:\bin\claude.cmd`).
|
|
802
|
-
*/
|
|
803
|
-
function normalizeCommandName(command) {
|
|
804
|
-
return path.basename(command).replace(/\.(cmd|exe|ps1|bat)$/i, "").toLowerCase();
|
|
590
|
+
const command = agentConfig.type === "cli" || agentConfig.type === "cli-pty" ? agentConfig.command : name;
|
|
591
|
+
return detectionForCommand(command, discovery) ?? { available: true, command };
|
|
805
592
|
}
|
|
806
593
|
/**
|
|
807
594
|
* Affiche le récapitulatif de détection locale après `palabre init`.
|
|
@@ -824,14 +611,7 @@ function printInitDiscovery(discovery, config, messages) {
|
|
|
824
611
|
console.log(messages.init.languageHint(config.language ?? DEFAULT_LANGUAGE));
|
|
825
612
|
}
|
|
826
613
|
function formatDetectedAgentSummary(discovery, language) {
|
|
827
|
-
const names =
|
|
828
|
-
discovery.codex.available ? "codex" : undefined,
|
|
829
|
-
discovery.claude.available ? "claude" : undefined,
|
|
830
|
-
discovery.gemini.available ? "gemini" : undefined,
|
|
831
|
-
discovery.antigravity.available ? "antigravity" : undefined,
|
|
832
|
-
discovery.opencode.available ? "opencode" : undefined,
|
|
833
|
-
discovery.ollama.available ? "ollama-local" : undefined
|
|
834
|
-
].filter((name) => Boolean(name));
|
|
614
|
+
const names = detectedAgentNames(discovery);
|
|
835
615
|
if (names.length === 0) {
|
|
836
616
|
return language === "en" ? "no agent detected" : "aucun agent détecté";
|
|
837
617
|
}
|
|
@@ -3,6 +3,7 @@ const frHints = {
|
|
|
3
3
|
"spawn-failed": "Sur Windows, essaye le wrapper .cmd ou active \"shell\": true dans la config agent.",
|
|
4
4
|
timeout: "Augmente timeoutMs ou teste la commande directement dans le terminal.",
|
|
5
5
|
"idle-timeout": "Desactive idleTimeoutMs pour les CLIs IA qui restent silencieuses pendant la generation.",
|
|
6
|
+
"output-too-large": "Reduis le contexte, le nombre de tours ou configure maxOutputBytes pour cet agent si ce volume est attendu.",
|
|
6
7
|
"empty-output": "Teste la commande en dehors de Palabre et verifie que le prompt est bien lu via stdin ou argument.",
|
|
7
8
|
"usage-limit": "Attends la fenetre indiquee par la CLI, change de modele ou relance avec un autre agent/preset disponible.",
|
|
8
9
|
"non-zero-exit": "Teste la commande directement, puis ajuste args, permissions, modele ou authentification de la CLI.",
|
|
@@ -16,6 +17,7 @@ const enHints = {
|
|
|
16
17
|
"spawn-failed": "On Windows, try the .cmd wrapper or enable \"shell\": true in the agent config.",
|
|
17
18
|
timeout: "Increase timeoutMs or test the command directly in the terminal.",
|
|
18
19
|
"idle-timeout": "Disable idleTimeoutMs for AI CLIs that stay silent while generating.",
|
|
20
|
+
"output-too-large": "Reduce context, turn count, or configure maxOutputBytes for this agent if this volume is expected.",
|
|
19
21
|
"empty-output": "Test the command outside Palabre and check that the prompt is read through stdin or an argument.",
|
|
20
22
|
"usage-limit": "Wait for the window indicated by the CLI, change model, or run again with another available agent/preset.",
|
|
21
23
|
"non-zero-exit": "Test the command directly, then adjust args, permissions, model, or CLI authentication.",
|
package/dist/messages/common.js
CHANGED
|
@@ -10,6 +10,9 @@ export const commonMessages = {
|
|
|
10
10
|
unknownAgentForField: (field, agent, available) => `Agent inconnu pour ${field}: ${agent}. Agents disponibles: ${available}.`,
|
|
11
11
|
unknownAgent: (agent) => `Agent inconnu: ${agent}`,
|
|
12
12
|
unknownRenderer: (value, supported) => `Renderer inconnu: ${value}. Valeurs supportées: ${supported}.`,
|
|
13
|
+
configInvalidShape: (configPath) => `Config invalide: ${configPath} ne contient pas un objet JSON. Relance palabre init ou corrige le fichier.`,
|
|
14
|
+
configMissingAgents: (configPath) => `Config invalide: ${configPath} ne déclare pas de bloc "agents". Relance palabre init ou ajoute au moins un agent.`,
|
|
15
|
+
configEmptyAgents: (configPath) => `Config invalide: ${configPath} ne déclare aucun agent. Ajoute au moins un agent ou relance palabre init.`,
|
|
13
16
|
errorPrefix: "Erreur"
|
|
14
17
|
},
|
|
15
18
|
en: {
|
|
@@ -23,6 +26,9 @@ export const commonMessages = {
|
|
|
23
26
|
unknownAgentForField: (field, agent, available) => `Unknown agent for ${field}: ${agent}. Available agents: ${available}.`,
|
|
24
27
|
unknownAgent: (agent) => `Unknown agent: ${agent}`,
|
|
25
28
|
unknownRenderer: (value, supported) => `Unknown renderer: ${value}. Supported values: ${supported}.`,
|
|
29
|
+
configInvalidShape: (configPath) => `Invalid config: ${configPath} does not contain a JSON object. Run palabre init or fix the file.`,
|
|
30
|
+
configMissingAgents: (configPath) => `Invalid config: ${configPath} has no "agents" block. Run palabre init or add at least one agent.`,
|
|
31
|
+
configEmptyAgents: (configPath) => `Invalid config: ${configPath} declares no agent. Add at least one agent or run palabre init.`,
|
|
26
32
|
errorPrefix: "Error"
|
|
27
33
|
}
|
|
28
34
|
};
|