SPB Git

Repository Public Research preview

claude-api.mjs 5.1 KB 82 lines javascript

1#!/usr/bin/env node2/**3 * Offline companion for the claude-api-toolkit skill. No network, no API key, no dependencies.4 *5 *   node claude-api.mjs models [model-id]            model table, or everything known about one model6 *   node claude-api.mjs lint <request.json|->        findings for a Messages request (exit 2 if it would be rejected)7 *   node claude-api.mjs fix <request.json|->         corrected request + list of changes8 *   node claude-api.mjs migrate <request.json|-> <target-model>9 *   node claude-api.mjs cost <model> <input_tokens> <output_tokens> [--cache-read N] [--batch] [--fast] [--requests N]10 *   node claude-api.mjs compare <input_tokens> <output_tokens> [--requests N]11 *   node claude-api.mjs scaffold <approach> [--language typescript|python] [--model ID] [--out FILE]12 *   node claude-api.mjs guide [topic]13 * Add --api-version 2023-06-01 to lint/fix for code that pins the legacy version. Output is JSON unless noted.14 */15import { readFileSync, writeFileSync, existsSync } from "node:fs";16import { compareCost, estimateCost } from "./lib/cost.mjs";17import { GUIDES } from "./lib/guides.mjs";18import { CURRENT, KB, MODELS, modelSummary, resolveModel } from "./lib/knowledge.mjs";19import { fixRequest, lintRequest } from "./lib/lint.mjs";20import { APPROACHES, scaffold } from "./lib/scaffold.mjs";2122const [cmd, ...rest] = process.argv.slice(2);23const flags = {}, args = [];24for (let i = 0; i < rest.length; i++) {25  if (rest[i].startsWith("--")) { const k = rest[i].slice(2); const v = rest[i + 1]; if (v === undefined || v.startsWith("--")) flags[k] = true; else { flags[k] = v; i++; } }26  else args.push(rest[i]);27}28const print = (x) => process.stdout.write(JSON.stringify(x, null, 2) + "\n");29const die = (msg, code = 1) => { process.stderr.write(`error: ${msg}\n`); process.exit(code); };30function readRequest(src) {31  if (!src) die("give a request file, or - for stdin");32  let text;33  try { text = src === "-" ? readFileSync(0, "utf8") : readFileSync(src, "utf8"); } catch { die(`cannot read ${src}`); }34  try { return JSON.parse(text); } catch (e) { die(`${src} is not valid JSON: ${e.message}`); }35}36const opts = { apiVersion: typeof flags["api-version"] === "string" ? flags["api-version"] : undefined };37const int = (v, name) => { const n = Number(v); if (!Number.isInteger(n) || n < 0) die(`${name} must be a non-negative integer`); return n; };3839switch (cmd) {40  case "models": {41    if (args[0]) { const m = resolveModel(args[0]); if (!m) die(`unknown model "${args[0]}". Known: ${Object.keys(MODELS).join(", ")}`); print({ observed_at: KB.observed_at, ...m }); }42    else print({ observed_at: KB.observed_at, current_generation: CURRENT, models: Object.values(MODELS).sort((a, b) => b.released.localeCompare(a.released)).map(modelSummary) });43    break;44  }45  case "lint": { const r = lintRequest(readRequest(args[0]), opts); print(r); process.exit(r.valid ? 0 : 2); }46  case "fix": { print(fixRequest(readRequest(args[0]), opts)); break; }47  case "migrate": {48    const target = resolveModel(args[1]);49    if (!target) die(`unknown target model "${args[1]}"`);50    print({ to: target.id, ...fixRequest({ ...readRequest(args[0]), model: target.id }, opts) });51    break;52  }53  case "cost": {54    if (args.length < 3) die("usage: cost <model> <input_tokens> <output_tokens>");55    const r = estimateCost({ model: args[0], input_tokens: int(args[1], "input_tokens"), output_tokens: int(args[2], "output_tokens"), cache_read_input_tokens: flags["cache-read"] ? int(flags["cache-read"], "--cache-read") : 0, cache_write_5m_tokens: flags["cache-write"] ? int(flags["cache-write"], "--cache-write") : 0, batch: !!flags.batch, fast: !!flags.fast, requests: flags.requests ? int(flags.requests, "--requests") : 1 });56    if (r.error) die(r.error);57    print(r);58    break;59  }60  case "compare": {61    if (args.length < 2) die("usage: compare <input_tokens> <output_tokens>");62    print(compareCost({ input_tokens: int(args[0], "input_tokens"), output_tokens: int(args[1], "output_tokens"), requests: flags.requests ? int(flags.requests, "--requests") : 1, batch: !!flags.batch }, Object.keys(MODELS)));63    break;64  }65  case "scaffold": {66    const r = scaffold({ approach: args[0], language: flags.language ?? "typescript", model: flags.model, effort: flags.effort });67    if (r.error) die(`${r.error}\napproaches: ${Object.entries(APPROACHES).map(([k, v]) => `\n  ${k} — ${v}`).join("")}`);68    if (flags.out) { if (existsSync(flags.out)) die(`${flags.out} already exists; refusing to overwrite`); writeFileSync(flags.out, r.code); process.stderr.write(`wrote ${flags.out}\ninstall: ${r.install}\nrun:     ${r.run}\n`); }69    else process.stdout.write(r.code);70    break;71  }72  case "guide": {73    if (!args[0]) print({ topics: Object.keys(GUIDES) });74    else if (!GUIDES[args[0]]) die(`unknown topic "${args[0]}". Topics: ${Object.keys(GUIDES).join(", ")}`);75    else process.stdout.write(GUIDES[args[0]] + "\n");76    break;77  }78  default:79    process.stderr.write(readFileSync(new URL(import.meta.url), "utf8").split("*/")[0].split("\n").slice(2).map((l) => l.replace(/^ \* ?/, "")).join("\n") + "\n");80    process.exit(cmd ? 1 : 0);81}82
Files main
Menu

Theme