| #!/usr/bin/env node
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| import { spawnSync } from "node:child_process";
|
| import { readFileSync, writeFileSync } from "node:fs";
|
| import { fileURLToPath } from "node:url";
|
| import { dirname, resolve } from "node:path";
|
|
|
| const __dirname = dirname(fileURLToPath(import.meta.url));
|
| const REGISTRY_PATH = resolve(__dirname, "..", "open-sse", "config", "providerRegistry.ts");
|
|
|
| const args = new Set(process.argv.slice(2));
|
| const DRY_RUN = args.has("--dry-run");
|
| const FROM_STDIN = args.has("--from-stdin");
|
|
|
| function readSource() {
|
| if (FROM_STDIN) return readFileSync(0, "utf8");
|
|
|
| const r = spawnSync("cursor-agent", ["--model", "--help"], { encoding: "utf8" });
|
| return `${r.stdout || ""}\n${r.stderr || ""}`;
|
| }
|
|
|
|
|
|
|
|
|
|
|
| function isUnsupportedChatModel(id) {
|
| if (id === "auto") return true;
|
| if (id.startsWith("composer-")) return true;
|
| return false;
|
| }
|
|
|
| function parseModelIds(text) {
|
| const m = text.match(/Available models:\s*([^\n]+)/);
|
| if (!m) throw new Error("Could not find 'Available models:' line in cursor-agent output");
|
| const includeUnsupported = args.has("--include-unsupported");
|
| return m[1]
|
| .split(",")
|
| .map((s) => s.trim())
|
| .filter(Boolean)
|
| .filter((id) => includeUnsupported || !isUnsupportedChatModel(id));
|
| }
|
|
|
| const SEGMENT_OVERRIDES = {
|
| gpt: "GPT",
|
| claude: "Claude",
|
| gemini: "Gemini",
|
| grok: "Grok",
|
| kimi: "Kimi",
|
| composer: "Composer",
|
| opus: "Opus",
|
| sonnet: "Sonnet",
|
| haiku: "Haiku",
|
| codex: "Codex",
|
| mini: "Mini",
|
| nano: "Nano",
|
| max: "Max",
|
| high: "High",
|
| low: "Low",
|
| medium: "Medium",
|
| xhigh: "XHigh",
|
| none: "None",
|
| fast: "Fast",
|
| thinking: "Thinking",
|
| extra: "Extra",
|
| spark: "Spark",
|
| preview: "Preview",
|
| flash: "Flash",
|
| pro: "Pro",
|
| };
|
|
|
|
|
|
|
|
|
|
|
| function humanize(id) {
|
| if (id === "auto") return "Auto (Server Picks)";
|
|
|
|
|
| const collapsed = id.replace(/(\d+)-(\d+)(?=-|$)/g, "$1.$2");
|
| const parts = collapsed.split("-");
|
| const labelled = parts.map((p) => {
|
| if (SEGMENT_OVERRIDES[p]) return SEGMENT_OVERRIDES[p];
|
| if (/^\d/.test(p)) return p;
|
| return p.charAt(0).toUpperCase() + p.slice(1);
|
| });
|
| return labelled.join(" ");
|
| }
|
|
|
| function buildModelsArrayLines(ids) {
|
| const seen = new Set();
|
| const out = [];
|
| for (const id of ids) {
|
| if (seen.has(id)) continue;
|
| seen.add(id);
|
| out.push(` { id: ${JSON.stringify(id)}, name: ${JSON.stringify(humanize(id))} },`);
|
| }
|
| return out.join("\n");
|
| }
|
|
|
| function replaceCursorModels(source, modelsBlock) {
|
|
|
| const re = /(\n cursor:\s*\{[\s\S]*?\n models:\s*\[)([\s\S]*?)(\n \],)/;
|
| if (!re.test(source)) throw new Error("Could not locate cursor.models array in registry");
|
| return source.replace(re, `$1\n${modelsBlock}$3`);
|
| }
|
|
|
| const ids = parseModelIds(readSource());
|
| const block = buildModelsArrayLines(ids);
|
|
|
| if (DRY_RUN) {
|
| console.log(block);
|
| process.exit(0);
|
| }
|
|
|
| const before = readFileSync(REGISTRY_PATH, "utf8");
|
| const after = replaceCursorModels(before, block);
|
| if (before === after) {
|
| console.log("No changes — cursor models already in sync.");
|
| process.exit(0);
|
| }
|
| writeFileSync(REGISTRY_PATH, after);
|
| console.log(`Updated ${ids.length} cursor models in ${REGISTRY_PATH}`);
|
|
|