import { createReadStream, readFileSync, statSync, writeFileSync } from "node:fs"; import { basename } from "node:path"; import { createInterface } from "node:readline"; import { apiFetch, getBaseUrl } from "../api.mjs"; import { emit } from "../output.mjs"; import { t } from "../i18n.mjs"; function fmtTs(v) { if (!v) return "-"; return new Date(typeof v === "number" ? v * 1000 : v).toLocaleString(); } function fmtBytes(n) { if (n == null) return "-"; if (n < 1024) return `${n} B`; if (n < 1024 ** 2) return `${(n / 1024).toFixed(1)} KB`; if (n < 1024 ** 3) return `${(n / 1024 ** 2).toFixed(1)} MB`; return `${(n / 1024 ** 3).toFixed(2)} GB`; } async function confirm(q) { const rl = createInterface({ input: process.stdin, output: process.stdout }); return new Promise((resolve) => { rl.question(`${q} [y/N] `, (a) => { rl.close(); resolve(a.trim().toLowerCase() === "y"); }); }); } function authHeaders(opts) { const h = { accept: "application/json" }; if (opts.apiKey) h["Authorization"] = `Bearer ${opts.apiKey}`; return h; } const fileSchema = [ { key: "id", header: "File ID", width: 30 }, { key: "filename", header: "Filename", width: 35 }, { key: "purpose", header: "Purpose", width: 14 }, { key: "bytes", header: "Bytes", formatter: fmtBytes }, { key: "created_at", header: "Created", formatter: fmtTs }, { key: "status", header: "Status" }, ]; export function registerFiles(program) { const files = program.command("files").description(t("files.description")); files .command("list") .option("--purpose

", t("files.list.purpose")) .option("--limit ", t("files.list.limit"), parseInt, 100) .action(async (opts, cmd) => { const params = new URLSearchParams({ limit: String(opts.limit) }); if (opts.purpose) params.set("purpose", opts.purpose); const res = await apiFetch(`/v1/files?${params}`); if (!res.ok) { process.stderr.write(`Error: ${res.status}\n`); process.exit(1); } const data = await res.json(); emit(data.data ?? data.items ?? data, cmd.optsWithGlobals(), fileSchema); }); files .command("get ") .description(t("files.get.description")) .action(async (id, opts, cmd) => { const res = await apiFetch(`/v1/files/${id}`); if (!res.ok) { process.stderr.write(`Error: ${res.status}\n`); process.exit(1); } emit(await res.json(), cmd.optsWithGlobals()); }); files .command("upload ") .description(t("files.upload.description")) .requiredOption("--purpose

", t("files.upload.purpose")) .action(async (filePath, opts, cmd) => { const stat = statSync(filePath); if (stat.size > 100 * 1024 * 1024) { process.stderr.write( `Warning: file is ${fmtBytes(stat.size)} (${stat.size > 500e6 ? "very " : ""}large)\n` ); } const globalOpts = cmd.optsWithGlobals(); const form = new FormData(); form.append("purpose", opts.purpose); form.append("file", new Blob([readFileSync(filePath)]), basename(filePath)); const res = await fetch(`${getBaseUrl(globalOpts)}/v1/files`, { method: "POST", headers: authHeaders(globalOpts), body: form, }); if (!res.ok) { process.stderr.write(`Upload failed: ${res.status}\n`); process.exit(1); } emit(await res.json(), globalOpts); }); files .command("content ") .description(t("files.content.description")) .option("--out ", t("files.content.out")) .action(async (id, opts, cmd) => { const globalOpts = cmd.optsWithGlobals(); const res = await fetch(`${getBaseUrl(globalOpts)}/v1/files/${id}/content`, { headers: authHeaders(globalOpts), }); if (!res.ok) { process.stderr.write(`HTTP ${res.status}\n`); process.exit(1); } if (opts.out) { const buf = Buffer.from(await res.arrayBuffer()); writeFileSync(opts.out, buf); process.stdout.write(`Saved ${buf.length} bytes to ${opts.out}\n`); } else { process.stdout.write(await res.text()); } }); files .command("delete ") .option("--yes", t("files.delete.yes")) .action(async (id, opts, cmd) => { if (!opts.yes) { const ok = await confirm(`Delete file ${id}?`); if (!ok) return; } const res = await apiFetch(`/v1/files/${id}`, { method: "DELETE" }); if (!res.ok) { process.stderr.write(`Error: ${res.status}\n`); process.exit(1); } process.stdout.write("Deleted\n"); }); } export { fmtBytes };