Retire Planner v1: engine (live 2026 CPP/OAS/GIS/RRIF/tax), 17-step UI, 4 SVG charts, vision gallery, tests

This commit is contained in:
krisf committed 2026-10-08 23:00:18 -04:00
commit e25dd4e0a1
29 files changed
+3143

No files matched your search

+53
View File
@@ -0,0 +1,53 @@
// Generate one vision image via nano-gpt Image API. Usage: KEY env var set, node gen1.js <slug> "<prompt>"
// Prints one JSON line: {slug, ok, cost, bal, bytes, type, err}
const [slug, prompt] = process.argv.slice(2);
const KEY = process.env.RETIRE_NANO_KEY;
async function main() {
const out = { slug, ok: false };
try {
const res = await fetch("https://nano-gpt.com/api/v1/images/generations", {
method: "POST",
headers: { "Authorization": "Bearer " + KEY, "Content-Type": "application/json" },
body: JSON.stringify({ model: "z-image-turbo", prompt, resolution: "1024*1024", n: 1 }),
signal: AbortSignal.timeout(180000),
});
const j = await res.json().catch(() => ({}));
out.cost = j.cost ?? (j.data && j.data[0] && j.data[0].cost) ?? null;
out.bal = j.remainingBalance ?? null;
if (!res.ok) { out.err = "http " + res.status + " " + JSON.stringify(j).slice(0, 300); return out; }
const d = j.data && j.data[0];
if (!d) { out.err = "no data: " + JSON.stringify(j).slice(0, 300); return out; }
// dump raw response shape (strip image payloads) for evidence
try {
const fs0 = require("fs");
const copy = JSON.parse(JSON.stringify(j));
if (copy.data) copy.data = copy.data.map(x => ({ ...x, b64_json: x.b64_json ? "<" + x.b64_json.length + "b>" : undefined, url: x.url ? x.url.slice(0, 80) : undefined }));
fs0.writeFileSync(".fetch/resp_" + slug + ".json", JSON.stringify(copy).slice(0, 4000));
} catch (e) {}
let buf;
if (d.b64_json) {
buf = Buffer.from(d.b64_json, "base64");
} else if (d.url) {
const img = await fetch(d.url, { signal: AbortSignal.timeout(120000) });
if (!img.ok) { out.err = "img fetch http " + img.status; return out; }
buf = Buffer.from(await img.arrayBuffer());
} else { out.err = "no image in response"; return out; }
// magic bytes
let type = "bin";
if (buf.length > 12 && buf[0] === 0x89 && buf[1] === 0x50) type = "png";
else if (buf.slice(0, 4).toString("latin1") === "RIFF" && buf.slice(8, 12).toString("latin1") === "WEBP") type = "webp";
else if (buf[0] === 0xff && buf[1] === 0xd8) type = "jpeg";
out.type = type;
out.bytes = buf.length;
const fs = require("fs");
fs.mkdirSync("site/vision", { recursive: true });
// browsers sniff magic bytes; keep .png extension for the UI contract, save true extension alongside
fs.writeFileSync("site/vision/" + slug + "." + (type === "png" ? "png" : type), buf);
if (type !== "png") fs.copyFileSync("site/vision/" + slug + "." + type, "site/vision/" + slug + ".png");
out.ok = true;
} catch (e) {
out.err = String(e).slice(0, 300);
}
return out;
}
main().then(o => console.log(JSON.stringify(o)));