#!/usr/bin/env node import { readFile, writeFile } from "node:fs/promises"; import path from "node:path"; import process from "node:process"; const DEFAULT_SPACE = "build-small-hackathon/between-the-lines"; const MODEL_LABELS = { base: "Base Mellum2 (richer)", tuned: "Fine-tuned LoRA (concise)" }; function usage() { return `between-the-lines [options] Options: --model base|tuned Comment model to use. Default: base --output Write annotated code to a specific file --in-place Replace the input file after validation --summary Print summary and validation status to stderr --space Hosted Gradio Space. Default: ${DEFAULT_SPACE} -h, --help Show this help `; } function parseArgs(argv) { const args = { input: "", model: "base", output: "", inPlace: false, summary: false, space: DEFAULT_SPACE }; for (let index = 0; index < argv.length; index += 1) { const arg = argv[index]; if (arg === "-h" || arg === "--help") { args.help = true; } else if (arg === "--model") { args.model = argv[++index] ?? ""; } else if (arg === "--output" || arg === "-o") { args.output = argv[++index] ?? ""; } else if (arg === "--in-place") { args.inPlace = true; } else if (arg === "--summary") { args.summary = true; } else if (arg === "--space") { args.space = argv[++index] ?? ""; } else if (!args.input) { args.input = arg; } else { throw new Error(`unexpected argument: ${arg}`); } } if (args.help) { return args; } if (!args.input) { throw new Error("missing input file"); } if (!Object.hasOwn(MODEL_LABELS, args.model)) { throw new Error("--model must be 'base' or 'tuned'"); } if (args.output && args.inPlace) { throw new Error("use either --output or --in-place, not both"); } return args; } function defaultOutputPath(inputPath) { const parsed = path.parse(inputPath); return path.join(parsed.dir, `${parsed.name}.annotated${parsed.ext || ".py"}`); } async function predict(client, source, modelLabel) { try { return await client.predict("/annotate", [source, modelLabel]); } catch (arrayError) { try { return await client.predict("/annotate", { source, model_label: modelLabel }); } catch { throw arrayError; } } } async function main() { let args; try { args = parseArgs(process.argv.slice(2)); } catch (error) { console.error(`error: ${error.message}\n`); console.error(usage()); return 2; } if (args.help) { console.log(usage()); return 0; } const source = await readFile(args.input, "utf8"); const { Client } = await import("@gradio/client"); const client = await Client.connect(args.space); const result = await predict(client, source, MODEL_LABELS[args.model]); const [summary, annotated, status] = result.data; if (args.summary) { if (summary) { console.error(summary); } console.error(status); } if (!String(status).startsWith("Validated:") && !String(status).startsWith("Parsed successfully.")) { console.error(status); return 1; } if (args.inPlace) { await writeFile(args.input, annotated, "utf8"); } else if (args.output) { await writeFile(args.output, annotated, "utf8"); } else { const outputPath = defaultOutputPath(args.input); await writeFile(outputPath, annotated, "utf8"); console.error(`Wrote ${outputPath}`); } return 0; } main() .then((code) => { process.exitCode = code; }) .catch((error) => { console.error(`error: ${error.message}`); process.exitCode = 1; });