site-mariage/_byan/mcp/byan-mcp-server/lib/cli.js
Corentin Joguet bff653acd6 first commit
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 10:30:37 +02:00

108 lines
3 KiB
JavaScript

import { spawn } from 'node:child_process';
import path from 'node:path';
const CLI_REL_PATH = 'bin/byan-v2-cli.js';
const DEFAULT_TIMEOUT_MS = 10_000;
export function parseCliOutput(stdout) {
const trimmed = stdout.trim();
if (!trimmed) return { raw: '' };
try {
return JSON.parse(trimmed);
} catch {
// fall through
}
const firstBracket = trimmed.search(/[\[{]/);
if (firstBracket >= 0) {
const candidate = trimmed.slice(firstBracket);
try {
return JSON.parse(candidate);
} catch {
// still not valid JSON
}
}
return { raw: trimmed };
}
function resolveProjectRoot(envRoot) {
return envRoot || process.env.CLAUDE_PROJECT_DIR || process.cwd();
}
export function runByanCli(args, options = {}) {
const root = resolveProjectRoot(options.projectRoot);
const script = path.join(root, CLI_REL_PATH);
const timeoutMs = options.timeoutMs || DEFAULT_TIMEOUT_MS;
return new Promise((resolve, reject) => {
const child = spawn('node', [script, ...args], {
cwd: root,
env: { ...process.env },
});
let stdout = '';
let stderr = '';
const timer = setTimeout(() => {
child.kill('SIGKILL');
reject(new Error(`byan-v2-cli timed out after ${timeoutMs}ms: ${args.join(' ')}`));
}, timeoutMs);
child.stdout.on('data', (d) => (stdout += d.toString()));
child.stderr.on('data', (d) => (stderr += d.toString()));
child.on('error', (err) => {
clearTimeout(timer);
reject(err);
});
child.on('close', (code) => {
clearTimeout(timer);
if (code !== 0) {
const err = new Error(
`byan-v2-cli exited ${code}: ${stderr.trim() || stdout.trim()}`
);
err.code = code;
err.stderr = stderr;
err.stdout = stdout;
reject(err);
return;
}
resolve(parseCliOutput(stdout));
});
});
}
export async function eloSummary(opts) {
return runByanCli(['elo', 'summary'], opts);
}
export async function eloContext({ domain, ...opts }) {
if (!domain) throw new Error('domain is required');
return runByanCli(['elo', 'context', domain], opts);
}
export async function eloDashboard({ domain, ...opts }) {
return runByanCli(domain ? ['elo', 'dashboard', domain] : ['elo', 'dashboard'], opts);
}
export async function eloRecord({ domain, result, reason, ...opts }) {
if (!domain) throw new Error('domain is required');
if (!['VALIDATED', 'BLOCKED', 'PARTIAL'].includes(result)) {
throw new Error(`result must be VALIDATED|BLOCKED|PARTIAL, got: ${result}`);
}
const args = ['elo', 'record', domain, result];
if (reason) args.push(reason);
return runByanCli(args, opts);
}
export async function fcCheck({ text, ...opts }) {
if (!text || typeof text !== 'string') throw new Error('text is required');
return runByanCli(['fc', 'check', text], opts);
}
export async function fcParse({ text, ...opts }) {
if (!text || typeof text !== 'string') throw new Error('text is required');
return runByanCli(['fc', 'parse', text], opts);
}