llm-json-fix
Version:
Fix malformed JSON outputs from Large Language Models (LLMs)
131 lines (114 loc) • 3.83 kB
JavaScript
const fs = require('fs')
const path = require('path')
const { pipeline } = require('stream')
const { program } = require('commander')
const { createLLMJsonFixStream } = require('../lib/cjs/stream')
const packageJson = require('../package.json')
// Parse command line arguments
program
.name('llm-json-fix')
.description('Fix malformed JSON outputs from Large Language Models (LLMs)')
.version(packageJson.version, '-v, --version')
.argument('[filename]', 'Input file (if not provided, read from stdin)')
.option('-o, --output <filename>', 'Output file (if not provided, write to stdout)')
.option('--overwrite', 'Overwrite the input file')
.option('--buffer <size>', 'Buffer size in bytes, for example 64K (default) or 1M', '64K')
.option('--model <name>', 'Specify the LLM model (openai, anthropic, general)', 'general')
.option('--preserve-comments', 'Preserve comments in the output', false)
.option('--verbose', 'Show detailed repair information', false)
.helpOption('-h, --help', 'Display help for command')
.parse(process.argv)
// Get options
const options = program.opts()
const inputFile = program.args[0]
if (options.overwrite && !inputFile) {
console.error('Error: --overwrite option requires an input file')
process.exit(1)
}
if (options.output && options.overwrite) {
console.error('Error: cannot use both --output and --overwrite options')
process.exit(1)
}
// Parse buffer size
let bufferSize = 65536 // Default 64K
if (options.buffer) {
const sizeStr = options.buffer.toUpperCase()
if (sizeStr.endsWith('K')) {
bufferSize = parseInt(sizeStr.slice(0, -1), 10) * 1024
} else if (sizeStr.endsWith('M')) {
bufferSize = parseInt(sizeStr.slice(0, -1), 10) * 1024 * 1024
} else if (sizeStr.endsWith('G')) {
bufferSize = parseInt(sizeStr.slice(0, -1), 10) * 1024 * 1024 * 1024
} else {
bufferSize = parseInt(sizeStr, 10)
}
if (isNaN(bufferSize) || bufferSize <= 0) {
console.error(`Error: Invalid buffer size: ${options.buffer}`)
process.exit(1)
}
}
// Determine output file
let outputFile = options.output
if (options.overwrite) {
outputFile = inputFile
}
// Create input stream
let inputStream
if (inputFile) {
try {
inputStream = fs.createReadStream(inputFile)
} catch (err) {
console.error(`Error reading input file: ${err.message}`)
process.exit(1)
}
} else {
// Read from stdin
inputStream = process.stdin
// Check if stdin is a TTY (terminal) - if it is and no file was specified, show help
if (inputStream.isTTY) {
program.help()
}
}
// Create transform stream
const fixStream = createLLMJsonFixStream({
bufferSize,
model: options.model,
preserveComments: options.preserveComments,
verbose: options.verbose
})
// Create output stream
let outputStream
if (outputFile) {
try {
// If overwriting, write to a temporary file first
const actualOutputFile = options.overwrite ? `${outputFile}.tmp` : outputFile
outputStream = fs.createWriteStream(actualOutputFile)
} catch (err) {
console.error(`Error creating output file: ${err.message}`)
process.exit(1)
}
} else {
// Write to stdout
outputStream = process.stdout
}
// Run the pipeline
pipeline(inputStream, fixStream, outputStream, (err) => {
if (err) {
console.error(`Error: ${err.message}`)
// Clean up temporary file if we were overwriting
if (options.overwrite && fs.existsSync(`${outputFile}.tmp`)) {
fs.unlinkSync(`${outputFile}.tmp`)
}
process.exit(1)
}
// If overwriting, replace the original file with the fixed version
if (options.overwrite) {
try {
fs.renameSync(`${outputFile}.tmp`, outputFile)
} catch (err) {
console.error(`Error replacing original file: ${err.message}`)
process.exit(1)
}
}
})