Files
2026-07-13 13:39:12 +08:00

145 lines
4.6 KiB
JavaScript

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 <p>", t("files.list.purpose"))
.option("--limit <n>", 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 <fileId>")
.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 <path>")
.description(t("files.upload.description"))
.requiredOption("--purpose <p>", 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 <fileId>")
.description(t("files.content.description"))
.option("--out <path>", 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 <fileId>")
.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 };