import { exec } from 'child_process';
import { promisify } from 'util';
import { getBinaryPath, buildCliArgs, escapeString } from './utils.js';
const execAsync = promisify(exec);
const EXTRACT_FLAG_MAP = {
allowTests: '--allow-tests',
contextLines: '--context',
format: '--format',
inputFile: '--input-file'
};
export async function extract(options) {
if (!options) {
throw new Error('Options object is required');
}
if ((!options.files || !Array.isArray(options.files) || options.files.length === 0) && !options.inputFile) {
throw new Error('Either files array or inputFile must be provided');
}
const binaryPath = await getBinaryPath(options.binaryOptions || {});
const cliArgs = buildCliArgs(options, EXTRACT_FLAG_MAP);
if (options.json && !options.format) {
cliArgs.push('--format', 'json');
}
if (options.files && Array.isArray(options.files) && options.files.length > 0) {
for (const file of options.files) {
cliArgs.push(escapeString(file));
}
}
let logMessage = `\nExtract:`;
if (options.files && options.files.length > 0) {
logMessage += ` files="${options.files.join(', ')}"`;
}
if (options.inputFile) logMessage += ` inputFile="${options.inputFile}"`;
if (options.allowTests) logMessage += " allowTests=true";
if (options.contextLines) logMessage += ` contextLines=${options.contextLines}`;
if (options.format) logMessage += ` format=${options.format}`;
if (options.json) logMessage += " json=true";
console.error(logMessage);
const command = `${binaryPath} extract ${cliArgs.join(' ')}`;
try {
const { stdout, stderr } = await execAsync(command);
if (stderr) {
console.error(`stderr: ${stderr}`);
}
let tokenUsage = {
requestTokens: 0,
responseTokens: 0,
totalTokens: 0
};
if (options.files && Array.isArray(options.files)) {
tokenUsage.requestTokens = options.files.join(' ').length / 4;
} else if (options.inputFile) {
tokenUsage.requestTokens = options.inputFile.length / 4;
}
if (stdout.includes('Total tokens returned:')) {
const tokenMatch = stdout.match(/Total tokens returned: (\d+)/);
if (tokenMatch && tokenMatch[1]) {
tokenUsage.responseTokens = parseInt(tokenMatch[1], 10);
tokenUsage.totalTokens = tokenUsage.requestTokens + tokenUsage.responseTokens;
}
}
let output = stdout;
if (!output.includes('Token Usage:')) {
output += `\nToken Usage:\n Request tokens: ${tokenUsage.requestTokens}\n Response tokens: ${tokenUsage.responseTokens}\n Total tokens: ${tokenUsage.totalTokens}\n`;
}
if (options.json || options.format === 'json') {
try {
const jsonOutput = JSON.parse(stdout);
if (!jsonOutput.token_usage) {
jsonOutput.token_usage = {
request_tokens: tokenUsage.requestTokens,
response_tokens: tokenUsage.responseTokens,
total_tokens: tokenUsage.totalTokens
};
}
return jsonOutput;
} catch (error) {
console.error('Error parsing JSON output:', error);
return output; }
}
return output;
} catch (error) {
const errorMessage = `Error executing extract command: ${error.message}\nCommand: ${command}`;
throw new Error(errorMessage);
}
}