131 lines
4.3 KiB
JavaScript
131 lines
4.3 KiB
JavaScript
#!/usr/bin/env node
|
|
// Sync the cursor models list in open-sse/config/providerRegistry.ts from
|
|
// cursor-agent's runtime model list. Triggers an intentional invalid --model
|
|
// invocation so cursor-agent prints "Available models: ..." on stderr.
|
|
//
|
|
// Usage:
|
|
// node scripts/ad-hoc/sync-cursor-models.mjs # spawn cursor-agent and apply
|
|
// node scripts/ad-hoc/sync-cursor-models.mjs --dry-run # print proposed block, don't write
|
|
// node scripts/ad-hoc/sync-cursor-models.mjs --from-stdin # read the error message from stdin
|
|
|
|
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");
|
|
// cursor-agent prints "Available models: ..." to stderr and exits non-zero.
|
|
const r = spawnSync("cursor-agent", ["--model", "--help"], { encoding: "utf8" });
|
|
return `${r.stdout || ""}\n${r.stderr || ""}`;
|
|
}
|
|
|
|
// `auto` is a CLI-side abstraction (cursor-agent resolves it locally before
|
|
// sending) and `composer-*` targets cursor's edit/composer endpoint, neither
|
|
// of which work via the chat RPC. Filter them so the dashboard never offers
|
|
// them to the chat path. Pass --include-unsupported to keep them.
|
|
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",
|
|
};
|
|
|
|
// Pretty-print an id by:
|
|
// 1) collapsing claude-NAME-X-Y dotted version (e.g. claude-opus-4-7 → claude-opus-4.7)
|
|
// 2) splitting on '-'
|
|
// 3) applying SEGMENT_OVERRIDES; falling back to capitalize-first
|
|
function humanize(id) {
|
|
if (id === "auto") return "Auto (Server Picks)";
|
|
|
|
// Collapse "X-Y" numeric suffix in claude-foo-X-Y- patterns into "X.Y"
|
|
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; // leave version numbers / "k2.5" alone
|
|
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) {
|
|
// Match the `cursor:` provider entry and replace just its `models: [ ... ],` array.
|
|
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}`);
|