scripts/lib.js
// Shared utilities for xray scripts. Zero dependencies by design.
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import crypto from "node:crypto";
export const REPO_ROOT = path.resolve(path.dirname(new URL(import.meta.url).pathname), "..");
export const PROTOCOL_VERSION = "1.0";
export const AUTH_FILE = path.join(os.homedir(), ".local/share/opencode/auth.json");
export function getCloudflareAuth() {
const token = process.env.CLOUDFLARE_API_TOKEN || null;
const account = process.env.CLOUDFLARE_ACCOUNT_ID || null;
if (token) return { token, account, source: "env" };
try {
const raw = JSON.parse(fs.readFileSync(AUTH_FILE, "utf8"));
const key = raw?.["cloudflare-workers-ai"]?.key || null;
if (key) return { token: key, account, source: "auth.json" };
} catch {}
return { token: null, account, source: "none" };
}
// Parses the controlled YAML subset used by ontology/*.yaml:
// list of mappings at indent 0 ("- id: x"), string fields at indent 2,
// and string-list fields ("seeds:" + indent-4 " - item") entries.
export function parseOntologyYaml(text) {
const items = [];
let cur = null;
let listKey = null;
for (const raw of String(text).split("\n")) {
const line = raw.replace(/\r$/, "");
if (!line.trim() || /^\s*#/.test(line)) continue;
const indent = line.length - line.trimStart().length;
const t = line.trim();
if (t.startsWith("- ")) {
const rest = t.slice(2).trim();
if (indent === 0) {
cur = {};
items.push(cur);
listKey = null;
} else {
if (listKey) cur[listKey].push(rest);
continue;
}
const ci = rest.indexOf(":");
if (ci > -1) cur[rest.slice(0, ci).trim()] = rest.slice(ci + 1).trim();
continue;
}
const ci = t.indexOf(":");
if (ci === -1) continue;
const key = t.slice(0, ci).trim();
const val = t.slice(ci + 1).trim();
if (val === "") {
cur[key] = [];
listKey = key;
} else {
cur[key] = val;
listKey = null;
}
}
return items;
}
export function loadDomains() {
return parseOntologyYaml(fs.readFileSync(path.join(REPO_ROOT, "ontology/domains.yaml"), "utf8"));
}
export function loadLenses() {
return parseOntologyYaml(fs.readFileSync(path.join(REPO_ROOT, "ontology/lenses.yaml"), "utf8"));
}
export function modelSlug(modelId) {
return modelId.replace(/^@cf\//, "").replace(/[/._]/g, "-");
}
export function nowIso() {
return new Date().toISOString();
}
export function hashId(prefix, content) {
const h = crypto.createHash("sha256").update(content).digest("hex").slice(0, 12);
return `${prefix}_${h}`;
}
export function appendJsonl(filePath, obj) {
fs.mkdirSync(path.dirname(filePath), { recursive: true });
fs.appendFileSync(filePath, JSON.stringify(obj) + "\n", "utf8");
}
export function readJsonl(filePath) {
if (!fs.existsSync(filePath)) return [];
return fs.readFileSync(filePath, "utf8").split("\n").filter(Boolean).map((l) => JSON.parse(l));
}
export function fmtUsd(x) {
return "$" + x.toFixed(x < 1 ? 3 : 2);
}
export function parseArgs(argv, spec) {
// spec: { flag: {takes: "value"|"none"|"optional", default: any}}
const out = {};
const positional = [];
for (const k of Object.keys(spec)) out[k] = spec[k].default;
for (let i = 0; i < argv.length; i++) {
const a = argv[i];
if (a.startsWith("--")) {
const key = a.slice(2).replace(/-/g, "_");
const s = spec[key];
if (!s) { console.error(`Unknown flag --${key}`); process.exit(2); }
if (s.takes === "none") out[key] = true;
else {
let val = argv[i + 1];
if (val === undefined || val.startsWith("--")) {
if (s.takes === "optional") val = true;
else { console.error(`--${key} requires a value`); process.exit(2); }
out[key] = val;
} else {
out[key] = val;
i++;
}
}
} else positional.push(a);
}
return { args: out, positional };
}