@closedrouter/cli 1.1.10 → 1.1.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -5204,7 +5204,7 @@ var {
5204
5204
  } = exports_commander;
5205
5205
 
5206
5206
  // src/index.ts
5207
- import { readFileSync as readFileSync14 } from "node:fs";
5207
+ import { readFileSync as readFileSync15 } from "node:fs";
5208
5208
 
5209
5209
  // ../node_modules/.bun/@clack+prompts@0.11.0/node_modules/@clack/prompts/dist/index.mjs
5210
5210
  import { stripVTControlCharacters as S2 } from "node:util";
@@ -8659,9 +8659,9 @@ var import_picocolors12 = __toESM(require_picocolors(), 1);
8659
8659
  import { randomUUID as randomUUID5 } from "node:crypto";
8660
8660
  import { spawn as spawn5 } from "node:child_process";
8661
8661
  import { execFileSync as execFileSync3 } from "node:child_process";
8662
- import { mkdirSync as mkdirSync4, readFileSync as readFileSync13, writeFileSync as writeFileSync4 } from "node:fs";
8662
+ import { mkdirSync as mkdirSync5, readFileSync as readFileSync14, writeFileSync as writeFileSync5 } from "node:fs";
8663
8663
  import { homedir as homedir14 } from "node:os";
8664
- import { dirname as dirname4, join as join16 } from "node:path";
8664
+ import { dirname as dirname5, join as join16 } from "node:path";
8665
8665
 
8666
8666
  // src/model-picker.ts
8667
8667
  import { emitKeypressEvents as emitKeypressEvents2 } from "node:readline";
@@ -9418,6 +9418,133 @@ async function settingsCommand(options = {}) {
9418
9418
  import { randomUUID } from "node:crypto";
9419
9419
  import { createServer as createServer2, request as httpRequest } from "node:http";
9420
9420
  import { request as httpsRequest } from "node:https";
9421
+
9422
+ // src/commands/launch-catalog.ts
9423
+ import { createHash as createHash2 } from "node:crypto";
9424
+ var modelLabel = (id) => `ClosedRouter: ${id}`;
9425
+ function transportModelId(id, family) {
9426
+ const hash = createHash2("sha256").update(id).digest("hex").slice(0, 20);
9427
+ return `${family === "claude" ? "claude-sonnet-4-6" : "gemini"}-closedrouter-${hash}`;
9428
+ }
9429
+ function createLaunchCatalog(models, selected) {
9430
+ const ids = [...new Set([selected, ...models])];
9431
+ return {
9432
+ defaultModel: selected,
9433
+ models: ids,
9434
+ aliases: Object.fromEntries(ids.flatMap((id) => [
9435
+ [transportModelId(id, "claude"), id],
9436
+ [transportModelId(id, "gemini"), id]
9437
+ ])),
9438
+ fallbacks: ["claude-sonnet-4-6", "claude-haiku-4-5-20251001", "gemini-3.8-flash", "gemini-3.8-flash-medium", "gemini-3.8-flash-high", "gemini-2.5-flash", "gemini-2.5-flash-lite"]
9439
+ };
9440
+ }
9441
+ function resolveLaunchModel(catalog, requested) {
9442
+ let id = requested.replace(/^models\//, "").replace(/\[1m\]$/, "");
9443
+ const fireworks = /^accounts\/fireworks\/models\/(.+)#anysphere\/[\w-]+$/.exec(id);
9444
+ if (fireworks)
9445
+ id = fireworks[1];
9446
+ if (catalog.models.includes(id))
9447
+ return id;
9448
+ if (id.startsWith("closedrouter/") && catalog.models.includes(id.slice(13)))
9449
+ return id.slice(13);
9450
+ if (Object.hasOwn(catalog.aliases, id))
9451
+ return catalog.aliases[id];
9452
+ if (catalog.fallbacks.includes(id))
9453
+ return catalog.defaultModel;
9454
+ return;
9455
+ }
9456
+ function claudeModelPicker(models) {
9457
+ return { replaceBuiltInOptions: true, options: models.map((id) => ({ model: id, label: modelLabel(id), description: "Via your ClosedRouter pool", behavesAs: "claude-sonnet-4-6" })) };
9458
+ }
9459
+ function codexModelCatalog(models) {
9460
+ return { models: models.map((id, priority) => ({
9461
+ slug: id,
9462
+ display_name: modelLabel(id),
9463
+ description: "Via your ClosedRouter pool",
9464
+ priority,
9465
+ visibility: "list",
9466
+ supported_in_api: true,
9467
+ supported_reasoning_levels: [],
9468
+ experimental_supported_tools: [],
9469
+ base_instructions: "You are a coding assistant working with the user in their workspace. Follow the user's task and applicable project instructions. Inspect relevant files before editing, preserve unrelated work, and verify changes with appropriate checks. Use the available tools when needed. Report the outcome and any material limitations clearly. The runtime supplies the configured inference model identity through ClosedRouter.",
9470
+ default_reasoning_level: null,
9471
+ supports_reasoning_summaries: false,
9472
+ default_reasoning_summary: "none",
9473
+ shell_type: "shell_command",
9474
+ apply_patch_tool_type: "freeform",
9475
+ web_search_tool_type: "text",
9476
+ input_modalities: ["text"],
9477
+ supports_parallel_tool_calls: false,
9478
+ support_verbosity: false,
9479
+ prefer_websockets: false,
9480
+ supports_image_detail_original: false,
9481
+ context_window: 8192,
9482
+ auto_compact_token_limit: 7000,
9483
+ truncation_policy: { mode: "tokens", limit: 6000 }
9484
+ })) };
9485
+ }
9486
+ function geminiModelCatalog(models, selected) {
9487
+ return {
9488
+ experimental: { dynamicModelConfiguration: true },
9489
+ modelConfigs: {
9490
+ modelDefinitions: Object.fromEntries(models.map((id) => [id, {
9491
+ displayName: modelLabel(id),
9492
+ dialogDescription: "Via your ClosedRouter pool",
9493
+ tier: "custom",
9494
+ family: "closedrouter",
9495
+ isVisible: true,
9496
+ features: { thinking: false }
9497
+ }])),
9498
+ customAliases: Object.fromEntries(models.map((id) => [modelLabel(id), { modelConfig: { model: id } }])),
9499
+ modelIdResolutions: Object.fromEntries(["auto", "pro", "flash", "flash-lite"].map((id) => [id, { default: selected }]))
9500
+ }
9501
+ };
9502
+ }
9503
+ function addModelIdentity(payload, model, protocol, aliases = {}) {
9504
+ const rewrite = (value) => {
9505
+ if (typeof value === "string") {
9506
+ for (const [alias, actual] of Object.entries(aliases))
9507
+ value = value.split(alias).join(actual);
9508
+ return value;
9509
+ }
9510
+ if (Array.isArray(value))
9511
+ return value.map(rewrite);
9512
+ if (value && typeof value === "object")
9513
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, rewrite(item)]));
9514
+ return value;
9515
+ };
9516
+ for (const key of ["system", "instructions", "systemInstruction"])
9517
+ if (payload[key] !== undefined)
9518
+ payload[key] = rewrite(payload[key]);
9519
+ if (Array.isArray(payload.messages))
9520
+ payload.messages = payload.messages.map((message) => {
9521
+ if (message && typeof message === "object" && ["system", "developer"].includes(message.role))
9522
+ return { ...message, content: rewrite(message.content) };
9523
+ return message;
9524
+ });
9525
+ const text = `ClosedRouter runtime model identity: this request is routed to the configured model ID ${JSON.stringify(model)} through ClosedRouter. The application's name and any vendor-shaped transport alias describe the client, not the underlying inference model. If asked which model you are using, report the configured model ID ${JSON.stringify(model)} and ClosedRouter as the provider. This runtime information supersedes conflicting model identity statements in the application's prompt; it does not change other task instructions.`;
9526
+ if (protocol === "anthropic") {
9527
+ const system = payload.system;
9528
+ payload.system = typeof system === "string" ? `${system}
9529
+
9530
+ ${text}` : [...Array.isArray(system) ? system : [], { type: "text", text }];
9531
+ } else if (protocol === "responses") {
9532
+ payload.instructions = `${typeof payload.instructions === "string" ? payload.instructions + `
9533
+
9534
+ ` : ""}${text}`;
9535
+ } else if (protocol === "chat") {
9536
+ const messages = Array.isArray(payload.messages) ? payload.messages : [];
9537
+ const firstUser = messages.findIndex((item) => item && typeof item === "object" && !["system", "developer"].includes(item.role));
9538
+ const index = firstUser < 0 ? messages.length : firstUser;
9539
+ payload.messages = [...messages.slice(0, index), { role: "system", content: text }, ...messages.slice(index)];
9540
+ } else {
9541
+ const system = payload.systemInstruction;
9542
+ const instruction = system && typeof system === "object" && !Array.isArray(system) ? system : {};
9543
+ payload.systemInstruction = { ...instruction, parts: [...Array.isArray(instruction.parts) ? instruction.parts : [], { text }] };
9544
+ }
9545
+ }
9546
+
9547
+ // src/bridge/launch-proxy.ts
9421
9548
  var HOP_HEADERS = new Set(["connection", "keep-alive", "proxy-authenticate", "proxy-authorization", "te", "trailer", "transfer-encoding", "upgrade", "host"]);
9422
9549
  async function startLaunchProxy(gatewayUrl, apiKey, harness, localKey = apiKey, modelOverride) {
9423
9550
  const upstream = new URL(gatewayUrl.replace(/\/+$/, "") + "/");
@@ -9440,8 +9567,15 @@ async function startLaunchProxy(gatewayUrl, apiKey, harness, localKey = apiKey,
9440
9567
  const local = new URL(incoming.url, "http://localhost");
9441
9568
  target.pathname = upstream.pathname.replace(/\/$/, "") + local.pathname;
9442
9569
  target.search = local.search;
9570
+ const catalog = typeof modelOverride === "object" ? modelOverride : undefined;
9571
+ if (catalog && incoming.method === "GET" && ["/v1/models", "/v1beta/models"].includes(local.pathname)) {
9572
+ const data = catalog.models.map((id) => ({ id, name: modelLabel(id), display_name: modelLabel(id), object: "model", type: "model", created: 0, created_at: "2026-01-01T00:00:00Z", owned_by: "closedrouter" }));
9573
+ outgoing.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify(local.pathname === "/v1/models" ? { object: "list", data, has_more: false, first_id: data[0]?.id, last_id: data.at(-1)?.id } : { models: catalog.models.map((id) => ({ name: `models/${id}`, displayName: modelLabel(id), supportedGenerationMethods: ["generateContent", "streamGenerateContent"] })) }));
9574
+ return;
9575
+ }
9443
9576
  let rewrittenBody;
9444
- if (modelOverride && incoming.method === "POST" && /^\/v1\/(?:messages(?:\/count_tokens)?|chat\/completions|responses)$/.test(local.pathname)) {
9577
+ const gemini = /^\/v1beta\/models\/(.+):(generateContent|streamGenerateContent)$/.exec(local.pathname);
9578
+ if (modelOverride && incoming.method === "POST" && (gemini || /^\/v1\/(?:messages(?:\/count_tokens)?|chat\/completions|responses)$/.test(local.pathname))) {
9445
9579
  try {
9446
9580
  const chunks = [];
9447
9581
  let length = 0;
@@ -9459,8 +9593,18 @@ async function startLaunchProxy(gatewayUrl, apiKey, harness, localKey = apiKey,
9459
9593
  return;
9460
9594
  }
9461
9595
  const payload = body;
9462
- const tinyProbe = local.pathname === "/v1/messages" && !modelOverride.startsWith("claude-") && payload.model !== modelOverride && typeof payload.max_tokens === "number" && payload.max_tokens <= 16 && Array.isArray(payload.messages) && payload.messages.length === 1 && (!Array.isArray(payload.tools) || payload.tools.length === 0);
9463
- rewrittenBody = JSON.stringify({ ...payload, model: modelOverride, ...tinyProbe ? { max_tokens: 512 } : {} });
9596
+ const requested = gemini ? decodeURIComponent(gemini[1]) : typeof payload.model === "string" ? payload.model : catalog?.defaultModel || "";
9597
+ const model = catalog ? resolveLaunchModel(catalog, requested) : modelOverride;
9598
+ if (!model) {
9599
+ outgoing.writeHead(400, { "content-type": "application/json" }).end(JSON.stringify({ error: { type: "invalid_request_error", message: "This model is not in this launch's ClosedRouter catalog. Choose a supported model from the picker." } }));
9600
+ return;
9601
+ }
9602
+ const tinyProbe = local.pathname === "/v1/messages" && !model.startsWith("claude-") && payload.model !== model && typeof payload.max_tokens === "number" && payload.max_tokens <= 16 && Array.isArray(payload.messages) && payload.messages.length === 1 && (!Array.isArray(payload.tools) || payload.tools.length === 0);
9603
+ if (catalog)
9604
+ addModelIdentity(payload, model, gemini ? "gemini" : local.pathname.startsWith("/v1/messages") ? "anthropic" : local.pathname === "/v1/responses" ? "responses" : "chat", catalog.aliases);
9605
+ if (gemini)
9606
+ target.pathname = upstream.pathname.replace(/\/$/, "") + `/v1beta/models/${encodeURIComponent(model)}:${gemini[2]}`;
9607
+ rewrittenBody = JSON.stringify({ ...payload, ...!gemini ? { model } : {}, ...tinyProbe ? { max_tokens: 512 } : {} });
9464
9608
  } catch {
9465
9609
  outgoing.writeHead(400).end();
9466
9610
  return;
@@ -9529,7 +9673,7 @@ async function startLaunchProxy(gatewayUrl, apiKey, harness, localKey = apiKey,
9529
9673
  import { createServer as createServer3 } from "node:http";
9530
9674
  import { request as httpRequest2 } from "node:http";
9531
9675
  import { request as httpsRequest2 } from "node:https";
9532
- async function startAgyModelProxy(gatewayUrl, apiKey, model) {
9676
+ async function startAgyModelProxy(gatewayUrl, apiKey, model, catalog) {
9533
9677
  const upstream = new URL(gatewayUrl.replace(/\/+$/, "") + "/");
9534
9678
  if (!["https:", "http:"].includes(upstream.protocol)) {
9535
9679
  throw new Error("ClosedRouter server URL must use HTTP or HTTPS.");
@@ -9541,12 +9685,17 @@ async function startAgyModelProxy(gatewayUrl, apiKey, model) {
9541
9685
  return;
9542
9686
  }
9543
9687
  const requestUrl = new URL(incoming.url || "/", "http://localhost");
9544
- const match = /^\/v1beta\/models\/[^/:]+:(generateContent|streamGenerateContent)$/.exec(requestUrl.pathname);
9688
+ const match = /^\/v1beta\/models\/([^/:]+):(generateContent|streamGenerateContent)$/.exec(requestUrl.pathname);
9545
9689
  if (incoming.method !== "POST" || !match) {
9546
9690
  outgoing.writeHead(404).end();
9547
9691
  return;
9548
9692
  }
9549
- const target = new URL(`v1beta/models/${encodeURIComponent(model)}:${match[1]}${requestUrl.search}`, upstream);
9693
+ const selected = catalog ? resolveLaunchModel(catalog, decodeURIComponent(match[1])) : model;
9694
+ if (!selected) {
9695
+ outgoing.writeHead(400).end(JSON.stringify({ error: { message: "Choose a supported ClosedRouter model." } }));
9696
+ return;
9697
+ }
9698
+ const target = new URL(`v1beta/models/${encodeURIComponent(selected)}:${match[2]}${requestUrl.search}`, upstream);
9550
9699
  const headers = { ...incoming.headers, host: target.host, "x-goog-api-key": apiKey };
9551
9700
  const upstreamRequest = requestUpstream(target, { method: "POST", headers }, (response) => {
9552
9701
  outgoing.writeHead(response.statusCode || 502, response.headers);
@@ -9586,28 +9735,26 @@ import { join as join9 } from "node:path";
9586
9735
  function record(value) {
9587
9736
  return typeof value === "object" && value !== null && !Array.isArray(value);
9588
9737
  }
9589
- function registerAgyLaunchModel(homeDir, model, transportModel) {
9738
+ function registerAgyLaunchCatalog(homeDir, catalog, legacyTransport) {
9590
9739
  const path = join9(homeDir, ".gemini", "antigravity-cli", "settings.json");
9591
9740
  const settings = JSON.parse(readFileSync8(path, "utf8"));
9592
- if (!record(settings) || settings.modelProvider !== "gemini") {
9741
+ if (!record(settings) || settings.modelProvider !== "gemini")
9593
9742
  throw new Error(`Set "modelProvider": "gemini" in ${path} before starting Antigravity through ClosedRouter.`);
9594
- }
9595
9743
  const config = settings.customModelsConfig ?? {};
9596
9744
  if (!record(config))
9597
9745
  throw new Error(`customModelsConfig must be an object in ${path}.`);
9598
- const models = config.customModels ?? {};
9599
- if (!record(models))
9746
+ const previous = config.customModels ?? {};
9747
+ if (!record(previous))
9600
9748
  throw new Error(`customModelsConfig.customModels must be an object in ${path}.`);
9601
- const label = `ClosedRouter: ${model}`;
9602
- const entry = { modelName: transportModel, apiProvider: "gemini" };
9603
- const existing = models[label];
9604
- if (existing !== undefined) {
9605
- if (!record(existing) || existing.modelName !== transportModel || existing.apiProvider !== "gemini" || Object.keys(existing).some((key) => !["modelName", "apiProvider"].includes(key))) {
9749
+ const models = { ...previous };
9750
+ for (const id of catalog.models) {
9751
+ const label = modelLabel(id), name = transportModelId(id, "gemini"), existing = previous[label];
9752
+ if (existing !== undefined && (!record(existing) || existing.apiProvider !== "gemini" || ![name, legacyTransport].includes(String(existing.modelName)) || Object.keys(existing).some((key) => !["modelName", "apiProvider"].includes(key)))) {
9606
9753
  throw new Error(`The custom model '${label}' already has different settings in ${path}. Rename that entry before launching it with ClosedRouter.`);
9607
9754
  }
9608
- return label;
9755
+ models[label] = { modelName: name, apiProvider: "gemini" };
9609
9756
  }
9610
- settings.customModelsConfig = { ...config, customModels: { ...models, [label]: entry } };
9757
+ settings.customModelsConfig = { ...config, customModels: models };
9611
9758
  const temporary = `${path}.closedrouter-${randomUUID2()}.tmp`;
9612
9759
  try {
9613
9760
  writeFileSync2(temporary, `${JSON.stringify(settings, null, 2)}
@@ -9617,7 +9764,7 @@ function registerAgyLaunchModel(homeDir, model, transportModel) {
9617
9764
  if (existsSync9(temporary))
9618
9765
  unlinkSync(temporary);
9619
9766
  }
9620
- return label;
9767
+ return modelLabel(catalog.defaultModel);
9621
9768
  }
9622
9769
 
9623
9770
  // src/commands/cursor-launch.ts
@@ -9815,7 +9962,7 @@ function createDesktopProfile(tool) {
9815
9962
  mkdirSync2(root, { recursive: true, mode: 448 });
9816
9963
  return mkdtempSync(join11(root, "session-"));
9817
9964
  }
9818
- function buildDesktopLaunchPlan(tool, executable, profileDir, baseUrl, localKey, model, toolArgs = [], platform = process.platform) {
9965
+ function buildDesktopLaunchPlan(tool, executable, profileDir, baseUrl, localKey, model, toolArgs = [], platform = process.platform, models = model ? [model] : []) {
9819
9966
  validateDesktopArgs(toolArgs);
9820
9967
  const env = { ...process.env };
9821
9968
  delete env.ELECTRON_RUN_AS_NODE;
@@ -9835,6 +9982,8 @@ function buildDesktopLaunchPlan(tool, executable, profileDir, baseUrl, localKey,
9835
9982
  `model = ${JSON.stringify(model)}`,
9836
9983
  'model_provider = "closedrouter"',
9837
9984
  "",
9985
+ `model_catalog_json = ${JSON.stringify(join11(codexHome, "closedrouter-models.json"))}`,
9986
+ "",
9838
9987
  "[model_providers.closedrouter]",
9839
9988
  'name = "ClosedRouter"',
9840
9989
  `base_url = ${JSON.stringify(`${baseUrl.replace(/\/+$/, "")}/v1`)}`,
@@ -9844,11 +9993,11 @@ function buildDesktopLaunchPlan(tool, executable, profileDir, baseUrl, localKey,
9844
9993
  ].join(`
9845
9994
  `) });
9846
9995
  files.push({ path: join11(codexHome, "auth.json"), contents: JSON.stringify({ auth_mode: "apikey", OPENAI_API_KEY: localKey }) });
9996
+ files.push({ path: join11(codexHome, "closedrouter-models.json"), contents: JSON.stringify(codexModelCatalog([...new Set([model, ...models])])) });
9847
9997
  }
9848
9998
  if (tool === "claude" && baseUrl && localKey && model) {
9849
9999
  userData = join11(profileDir, "user-data-3p");
9850
10000
  const id = randomUUID3();
9851
- const transportModel = model.startsWith("claude-") || model.startsWith("anthropic/claude-") ? model : "claude-sonnet-4-6";
9852
10001
  const json = (value) => `${JSON.stringify(value, null, 2)}
9853
10002
  `;
9854
10003
  files.push({ path: join11(userData, "claude_desktop_config.json"), contents: json({ deploymentMode: "3p" }) });
@@ -9859,7 +10008,7 @@ function buildDesktopLaunchPlan(tool, executable, profileDir, baseUrl, localKey,
9859
10008
  inferenceGatewayApiKey: localKey,
9860
10009
  inferenceGatewayAuthScheme: "bearer",
9861
10010
  disableDeploymentModeChooser: true,
9862
- inferenceModels: [{ name: transportModel, labelOverride: `ClosedRouter: ${model}` }]
10011
+ inferenceModels: [...new Set([model, ...models])].map((id) => ({ name: transportModelId(id, "claude"), labelOverride: modelLabel(id) }))
9863
10012
  }) });
9864
10013
  }
9865
10014
  Object.assign(env, appEnv);
@@ -9970,8 +10119,9 @@ function defaultCursorDatabase() {
9970
10119
  const root = process.platform === "darwin" ? join12(homedir11(), "Library", "Application Support") : process.platform === "win32" ? process.env.APPDATA || join12(homedir11(), "AppData", "Roaming") : join12(homedir11(), ".config");
9971
10120
  return join12(root, "Cursor", "User", "globalStorage", "state.vscdb");
9972
10121
  }
9973
- async function configureCursorDesktop(profileDir, baseUrl, apiKey, model, sourceDb = process.env.CLOSEDROUTER_CURSOR_AUTH_DB ?? defaultCursorDatabase()) {
10122
+ async function configureCursorDesktop(profileDir, baseUrl, apiKey, model, sourceDb = process.env.CLOSEDROUTER_CURSOR_AUTH_DB ?? defaultCursorDatabase(), models = [model]) {
9974
10123
  const cursorModel = model;
10124
+ const catalog = [...new Set([model, ...models])];
9975
10125
  const dbPath = join12(profileDir, "user-data", "User", "globalStorage", "state.vscdb");
9976
10126
  mkdirSync3(dirname2(dbPath), { recursive: true, mode: 448 });
9977
10127
  const items = new Map;
@@ -9992,14 +10142,14 @@ async function configureCursorDesktop(profileDir, baseUrl, apiKey, model, source
9992
10142
  items.set(CURSOR_APPLICATION_STORAGE, JSON.stringify({
9993
10143
  useOpenAIKey: true,
9994
10144
  openAIBaseUrl: `${baseUrl.replace(/\/+$/, "")}/v1`,
9995
- availableAPIKeyModels: [cursorModel],
10145
+ availableAPIKeyModels: catalog,
9996
10146
  havePerformedSettingsServiceMigration: true,
9997
- localProviderModelIds: [cursorModel],
9998
- localProviderAgentModelIds: [cursorModel],
10147
+ localProviderModelIds: catalog,
10148
+ localProviderAgentModelIds: catalog,
9999
10149
  ...membershipType ? { membershipType } : {},
10000
10150
  aiSettings: {
10001
- userAddedModels: [cursorModel],
10002
- modelOverrideEnabled: [cursorModel],
10151
+ userAddedModels: catalog,
10152
+ modelOverrideEnabled: catalog,
10003
10153
  modelOverrideDisabled: [],
10004
10154
  modelConfig: Object.fromEntries(["cmd-k", "composer", "background-composer", "plan-execution", "spec", "quick-agent"].map((feature) => [feature, { modelName: cursorModel, maxMode: false, selectedModels: [{ modelId: cursorModel, parameters: [] }] }]))
10005
10155
  }
@@ -10024,6 +10174,129 @@ import { randomUUID as randomUUID4 } from "node:crypto";
10024
10174
  import { existsSync as existsSync13, readFileSync as readFileSync11 } from "node:fs";
10025
10175
  import { homedir as homedir12 } from "node:os";
10026
10176
  import { join as join13 } from "node:path";
10177
+
10178
+ // src/commands/agy-desktop-catalog.ts
10179
+ function patchAntigravityCatalog(source, catalog) {
10180
+ const provider = /this\.customModelsProvider=([\w$]+)\(this\.rootProvider,([\w$]+)=>\(\{customModels:\2\.appState\?\.customModelsConfig\?\.customModels\?\?\{\}\}\)\)/;
10181
+ const context = /function ([\w$]+)\(\{children:([\w$]+),userStatus:([\w$]+),lastSelectedAgentModel:([\w$]+),setLastSelectedAgentModel:/;
10182
+ const match = context.exec(source);
10183
+ if (!provider.test(source) || !match)
10184
+ throw new Error("This Antigravity build has an unsupported custom-model picker. Update the ClosedRouter launcher.");
10185
+ const entries = catalog.models.map((id) => [`ClosedRouter: ${id}`, Object.keys(catalog.aliases).find((alias) => alias.startsWith("gemini-closedrouter-") && catalog.aliases[alias] === id)]);
10186
+ const models = `Object.fromEntries(${JSON.stringify(entries)}.map(([label,id])=>[label,r(I.ModelInfoSchema,{modelId:I.Model.GOOGLE_GEMINI_INTERNAL_BYOM,apiProvider:I.APIProvider.API_PROVIDER_GOOGLE_EVERGREEN,chatModelName:label,modelName:id,maxTokens:32768,maxOutputTokens:4096,supportsContext:true,modelFeatures:r(I.ModelFeaturesSchema,{zeroShotCapable:true,supportsToolCalls:true,supportsImages:false,supportsThinking:false})})]))`;
10187
+ const customState = /([\w$]+)=!!([\w$]+),([\w$]+)=\(0,([\w$]+)\.useMemo\)\(\(\)=>\(\{customModels:([\w$]+)\?\.customModels\?\?([\w$]+)\}\),\[\5\]\)/;
10188
+ if (!customState.test(source))
10189
+ throw new Error("This Antigravity build has an unsupported custom-model context.");
10190
+ source = source.replace(customState, (_all, supported, _feature, state, react, input) => `${supported}=true,${state}=(0,${react}.useMemo)(()=>({customModels:${models}}),[${input}])`);
10191
+ const customHeader = /showCustomModelHeader:[\w$]+,onAddCustomModel:/;
10192
+ if (!customHeader.test(source))
10193
+ throw new Error("This Antigravity build has an unsupported custom-model menu.");
10194
+ source = source.replace(/showCustomModelHeader:[\w$]+,onAddCustomModel:[\w$]+,onEditCustomModel:[\w$]+/, "showCustomModelHeader:true,onAddCustomModel:undefined,onEditCustomModel:undefined");
10195
+ source = source.replace(/([\w$]+)=([\w$]+)\.some\(([\w$]+)=>\3\.options\.length>0\);([\w$]+)=/, "$1=true;$4=");
10196
+ source = source.replace(provider, (_all, selector, variable) => `this.customModelsProvider=${selector}(this.rootProvider,${variable}=>({customModels:${models}}))`);
10197
+ const start = source.indexOf(match[0]);
10198
+ const end = source.indexOf(`function `, start + match[0].length);
10199
+ let body = source.slice(start, end);
10200
+ const state = /\[([\w$]+),([\w$]+)\]=\(0,([\w$]+)\.useState\)\(null\)/;
10201
+ if (!state.test(body) || !body.includes("clientModelConfigs||[]"))
10202
+ throw new Error("This Antigravity build has an unsupported model-selection context.");
10203
+ body = body.replace(state, (_all, value, setter, react) => `[${value},${setter}]=(0,${react}.useState)(${JSON.stringify(`ClosedRouter: ${catalog.defaultModel}`)})`).replace(/\([\w$]+\?\.cascadeModelConfigData\?\.clientModelConfigs\|\|\[\]\)/g, "([])").replace(/[\w$]+\?\.clientModelConfigs\|\|\[\]/g, "[]").replace(/[\w$]+\?\.clientModelSorts\|\|\[\]/g, "[]");
10204
+ return source.slice(0, start) + body + source.slice(end);
10205
+ }
10206
+ function antigravityCatalogHook(catalog, marker) {
10207
+ return `
10208
+ const {app} = require('electron');
10209
+ const patchCatalog = ${patchAntigravityCatalog.toString()};
10210
+ app.on('web-contents-created', (_event, contents) => {
10211
+ const debuggerClient = contents.debugger;
10212
+ try {
10213
+ debuggerClient.attach('1.3');
10214
+ debuggerClient.sendCommand('Fetch.enable', {patterns:[{urlPattern:'https://127.0.0.1:*/main.js',requestStage:'Response'}]}).catch(() => {});
10215
+ debuggerClient.on('message', async (_event, method, params) => {
10216
+ if (method !== 'Fetch.requestPaused') return;
10217
+ try {
10218
+ const url = new URL(params.request.url);
10219
+ if (url.hostname !== '127.0.0.1' || url.pathname !== '/main.js' || params.responseStatusCode !== 200) {
10220
+ await debuggerClient.sendCommand('Fetch.continueRequest', {requestId:params.requestId}); return;
10221
+ }
10222
+ const result = await debuggerClient.sendCommand('Fetch.getResponseBody', {requestId:params.requestId});
10223
+ const source = result.base64Encoded ? Buffer.from(result.body,'base64').toString('utf8') : result.body;
10224
+ const patched = patchCatalog(source, ${JSON.stringify(catalog)});
10225
+ await debuggerClient.sendCommand('Fetch.fulfillRequest', {requestId:params.requestId,responseCode:200,
10226
+ responseHeaders:(params.responseHeaders || []).filter(h=>!['content-length','content-encoding','transfer-encoding'].includes(h.name.toLowerCase())),
10227
+ body:Buffer.from(patched).toString('base64')});
10228
+ require('node:fs').writeFileSync(${JSON.stringify(marker)}, 'ready', {mode:0o600});
10229
+ await debuggerClient.sendCommand('Fetch.disable');
10230
+ debuggerClient.detach();
10231
+ } catch (error) {
10232
+ require('node:fs').writeFileSync(${JSON.stringify(marker + ".error")}, String(error.message), {mode:0o600});
10233
+ await debuggerClient.sendCommand('Fetch.failRequest', {requestId:params.requestId,errorReason:'Failed'}).catch(()=>{});
10234
+ }
10235
+ });
10236
+ } catch (error) {
10237
+ require('node:fs').writeFileSync(${JSON.stringify(marker + ".error")}, String(error.message), {mode:0o600});
10238
+ }
10239
+ });
10240
+ `;
10241
+ }
10242
+
10243
+ // src/commands/model-branding.ts
10244
+ var CLOSEDROUTER_LOGO = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><rect width="24" height="24" rx="6" fill="#D4FE51"/><path d="M16.68 7.82A6.5 6.5 0 1 0 16.68 16.18" fill="none" stroke="#04080A" stroke-width="2.6" stroke-linecap="round"/><circle cx="18.2" cy="12" r="1.9" fill="#04080A"/></svg>';
10245
+ function installModelBranding(models, artwork) {
10246
+ if (document.querySelector("[data-closedrouter-branding]"))
10247
+ return;
10248
+ const marker = document.createElement("meta");
10249
+ marker.dataset.closedrouterBranding = "true";
10250
+ document.head.append(marker);
10251
+ const paint = () => {
10252
+ for (const element of document.querySelectorAll('button,[role="button"],[role="option"],[role="menuitem"],[role="menuitemradio"],[data-testid="model-selector-item"]')) {
10253
+ const text = element.innerText.trim().split(`
10254
+ `)[0];
10255
+ if (!models.some((id) => text === id || text === `ClosedRouter: ${id}`))
10256
+ continue;
10257
+ const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT);
10258
+ while (walker.nextNode()) {
10259
+ const node = walker.currentNode;
10260
+ const value = node.textContent || "";
10261
+ if (models.some((id) => value.trim() === `ClosedRouter: ${id}`))
10262
+ node.textContent = value.replace("ClosedRouter: ", "");
10263
+ }
10264
+ if (element.querySelector(":scope > [data-closedrouter-logo]"))
10265
+ continue;
10266
+ const logo = document.createElement("img");
10267
+ logo.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(artwork)}`;
10268
+ logo.alt = "ClosedRouter";
10269
+ logo.title = "Routed through ClosedRouter";
10270
+ logo.dataset.closedrouterLogo = "true";
10271
+ logo.style.cssText = "width:16px;height:16px;flex:0 0 16px;display:inline-block;vertical-align:middle;margin-inline-end:6px;pointer-events:none";
10272
+ element.prepend(logo);
10273
+ }
10274
+ };
10275
+ let scheduled = false;
10276
+ const observer = new MutationObserver(() => {
10277
+ if (scheduled)
10278
+ return;
10279
+ scheduled = true;
10280
+ requestAnimationFrame(() => {
10281
+ scheduled = false;
10282
+ paint();
10283
+ });
10284
+ });
10285
+ observer.observe(document.documentElement, { childList: true, subtree: true, characterData: true });
10286
+ paint();
10287
+ }
10288
+ function modelBrandingHook(models) {
10289
+ const script = `(${installModelBranding.toString()})(${JSON.stringify(models)},${JSON.stringify(CLOSEDROUTER_LOGO)})`;
10290
+ return `
10291
+ require('electron').app.on('web-contents-created', (_event, contents) => {
10292
+ contents.on('did-finish-load', () => {
10293
+ contents.executeJavaScript(${JSON.stringify(script)}).catch(() => {});
10294
+ });
10295
+ });
10296
+ `;
10297
+ }
10298
+
10299
+ // src/commands/agy-desktop.ts
10027
10300
  var MODS_CONTROL_URL = "http://127.0.0.1:59998";
10028
10301
  var MODS_PROXY_URL = "http://127.0.0.1:59997";
10029
10302
  async function assertAntigravityModsAvailable() {
@@ -10060,7 +10333,7 @@ function assertAntigravityDesktopHookSupport(executable) {
10060
10333
  if (index < 0 || binary[index + 32 + 5] !== 49)
10061
10334
  throw new Error("This Antigravity build disables launch inspector hooks. Use --open-only.");
10062
10335
  }
10063
- async function startAgyDesktopProxy(gatewayUrl, localKey, model, modsUrl = MODS_PROXY_URL) {
10336
+ async function startAgyDesktopProxy(gatewayUrl, localKey, model, modsUrl = MODS_PROXY_URL, catalog) {
10064
10337
  const capability = randomUUID4();
10065
10338
  const controllers = new Set;
10066
10339
  const server = createServer4(async (incoming, outgoing) => {
@@ -10099,9 +10372,15 @@ async function startAgyDesktopProxy(gatewayUrl, localKey, model, modsUrl = MODS_
10099
10372
  return;
10100
10373
  }
10101
10374
  const body = JSON.parse(raw.toString("utf8"));
10375
+ const requested = body.modelName || body.model || model;
10376
+ const selected = catalog ? resolveLaunchModel(catalog, requested) : model;
10377
+ if (!selected) {
10378
+ outgoing.writeHead(400).end(JSON.stringify({ error: { message: "Choose a supported ClosedRouter model." } }));
10379
+ return;
10380
+ }
10102
10381
  const request = body.request || body;
10103
10382
  payload = Buffer.from(JSON.stringify(request));
10104
- target = `${gatewayUrl}/v1beta/models/${encodeURIComponent(model)}:${generation[1]}${generation[1] === "streamGenerateContent" ? "?alt=sse" : ""}`;
10383
+ target = `${gatewayUrl}/v1beta/models/${encodeURIComponent(selected)}:${generation[1]}${generation[1] === "streamGenerateContent" ? "?alt=sse" : ""}`;
10105
10384
  headers.authorization = `Bearer ${localKey}`;
10106
10385
  headers["content-type"] = "application/json";
10107
10386
  } else {
@@ -10182,8 +10461,9 @@ async function startAgyDesktopProxy(gatewayUrl, localKey, model, modsUrl = MODS_
10182
10461
  })
10183
10462
  };
10184
10463
  }
10185
- function addAntigravityDesktopHook(plan, endpoint) {
10464
+ function addAntigravityDesktopHook(plan, endpoint, catalog) {
10186
10465
  const path = join13(plan.profileDir, "antigravity-hook.cjs");
10466
+ const marker = join13(plan.profileDir, "model-catalog-ready");
10187
10467
  const contents = `const cp = require('node:child_process');
10188
10468
  const spawn = cp.spawn;
10189
10469
  cp.spawn = function(command, args, options) {
@@ -10205,10 +10485,10 @@ cp.spawn = function(command, args, options) {
10205
10485
  return spawn.call(this, command, args, options);
10206
10486
  };
10207
10487
  require('node:fs').writeFileSync(${JSON.stringify(join13(plan.profileDir, "hook-loaded"))}, 'ready', {mode: 0o600});
10208
- `;
10488
+ ` + (catalog ? antigravityCatalogHook(catalog, marker) + modelBrandingHook(catalog.models) : "");
10209
10489
  plan.files.push({ path, contents });
10210
10490
  const logPath = join13(plan.profileDir, "inspector.log");
10211
- plan.inspectorHook = { path, logPath };
10491
+ plan.inspectorHook = { path, logPath, ...catalog ? { readyPath: marker } : {} };
10212
10492
  plan.args.push("--inspect-brk=127.0.0.1:0");
10213
10493
  if (plan.command === "/usr/bin/open") {
10214
10494
  const index = plan.args.indexOf("--args");
@@ -10218,9 +10498,92 @@ require('node:fs').writeFileSync(${JSON.stringify(join13(plan.profileDir, "hook-
10218
10498
 
10219
10499
  // src/commands/claude-desktop.ts
10220
10500
  import { execFileSync as execFileSync2 } from "node:child_process";
10221
- import { existsSync as existsSync14, readdirSync as readdirSync2 } from "node:fs";
10501
+ import { createHash as createHash3 } from "node:crypto";
10502
+ import { copyFileSync, existsSync as existsSync14, linkSync, lstatSync, mkdirSync as mkdirSync4, mkdtempSync as mkdtempSync2, readFileSync as readFileSync12, readdirSync as readdirSync2, readlinkSync, realpathSync, renameSync as renameSync2, rmSync, statSync as statSync2, symlinkSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "node:fs";
10222
10503
  import { homedir as homedir13 } from "node:os";
10223
- import { join as join14 } from "node:path";
10504
+ import { dirname as dirname3, join as join14, relative as relative2 } from "node:path";
10505
+ function patchClaudeModelIdentity(source, aliases) {
10506
+ return source.replace(/function ([\w$]+)\(\{modelId:([\w$]+),provider:([\w$]+),labelOverride:([\w$]+)\}\)\{/, (start, _name, id) => start + `const closedrouterIdentity=${JSON.stringify(aliases)}[${id}];if(closedrouterIdentity)return 'Your configured inference model ID is '+closedrouterIdentity+'. Your inference provider is ClosedRouter. The client and its transport alias do not identify the underlying inference model.';`);
10507
+ }
10508
+ function cloneClaudeElectronRuntime(source, destination) {
10509
+ const clone = (from, to) => {
10510
+ const info = lstatSync(from);
10511
+ if (info.isSymbolicLink())
10512
+ symlinkSync(readlinkSync(from), to);
10513
+ else if (info.isDirectory()) {
10514
+ mkdirSync4(to, { mode: 448 });
10515
+ for (const name of readdirSync2(from))
10516
+ clone(join14(from, name), join14(to, name));
10517
+ } else if (info.isFile())
10518
+ linkSync(from, to);
10519
+ else
10520
+ throw new Error("Unsupported file in the shared Electron runtime.");
10521
+ };
10522
+ clone(source, destination);
10523
+ const contents = join14(destination, "Contents");
10524
+ const roots = [contents, ...readdirSync2(join14(contents, "Frameworks")).filter((name) => name.endsWith(".app")).map((name) => join14(contents, "Frameworks", name, "Contents"))];
10525
+ const owned = [];
10526
+ for (const root of roots) {
10527
+ if (!realpathSync(root).startsWith(realpathSync(destination) + "/"))
10528
+ throw new Error("Electron helper signing must stay inside the owned runtime.");
10529
+ for (const name of readdirSync2(join14(root, "MacOS"))) {
10530
+ const target = join14(root, "MacOS", name);
10531
+ if (!lstatSync(target).isFile())
10532
+ throw new Error("Electron helper executables must be regular files.");
10533
+ const original = join14(source, relative2(destination, target));
10534
+ unlinkSync2(target);
10535
+ copyFileSync(original, target);
10536
+ owned.push(target);
10537
+ }
10538
+ rmSync(join14(root, "_CodeSignature"), { recursive: true, force: true });
10539
+ }
10540
+ return owned;
10541
+ }
10542
+ function prepareClaudeElectronRuntime(runtime, sourceApp) {
10543
+ const source = dirname3(dirname3(dirname3(realpathSync(runtime))));
10544
+ if (!source.endsWith(".app"))
10545
+ throw new Error("Claude's shared runtime must be a macOS Electron app bundle.");
10546
+ const framework = join14(source, "Contents/Frameworks/Electron Framework.framework/Versions/A/Electron Framework");
10547
+ const fingerprint = createHash3("sha256").update(JSON.stringify({
10548
+ schema: 1,
10549
+ source,
10550
+ sourceApp,
10551
+ executable: statSync2(runtime).mtimeMs,
10552
+ framework: statSync2(framework).mtimeMs,
10553
+ appInfo: readFileSync12(join14(sourceApp, "Contents/Info.plist"), "utf8")
10554
+ })).digest("hex").slice(0, 24);
10555
+ const root = join14(getConfigDir(), "desktop-runtime", "claude");
10556
+ mkdirSync4(root, { recursive: true, mode: 448 });
10557
+ const cache = join14(root, fingerprint), app = join14(cache, "Electron.app");
10558
+ const executable = join14(app, "Contents/MacOS", runtime.split("/").at(-1));
10559
+ const marker = join14(cache, "ready.json");
10560
+ if (existsSync14(marker)) {
10561
+ const saved = JSON.parse(readFileSync12(marker, "utf8"));
10562
+ if (saved.fingerprint !== fingerprint || saved.source !== source)
10563
+ throw new Error("Claude's owned runtime cache has unexpected metadata.");
10564
+ execFileSync2("/usr/bin/codesign", ["--verify", app], { stdio: "pipe" });
10565
+ return executable;
10566
+ }
10567
+ const staging = mkdtempSync2(join14(root, ".preparing-"));
10568
+ try {
10569
+ const target = join14(staging, "Electron.app");
10570
+ const owned = cloneClaudeElectronRuntime(source, target);
10571
+ const entitlements = join14(staging, "entitlements.plist");
10572
+ writeFileSync4(entitlements, '<?xml version="1.0"?><plist version="1.0"><dict><key>com.apple.security.cs.allow-jit</key><true/><key>com.apple.security.cs.disable-library-validation</key><true/><key>com.apple.security.virtualization</key><true/></dict></plist>', { mode: 384 });
10573
+ for (const path of [...owned.slice(1), owned[0]]) {
10574
+ if (statSync2(path).nlink !== 1)
10575
+ throw new Error("Refusing to sign a shared Electron executable.");
10576
+ execFileSync2("/usr/bin/codesign", ["--force", "--sign", "-", "--entitlements", entitlements, path], { stdio: "pipe" });
10577
+ }
10578
+ execFileSync2("/usr/bin/codesign", ["--verify", target], { stdio: "pipe" });
10579
+ writeFileSync4(join14(staging, "ready.json"), JSON.stringify({ fingerprint, source, sourceApp }), { mode: 384 });
10580
+ renameSync2(staging, cache);
10581
+ return executable;
10582
+ } catch (error) {
10583
+ rmSync(staging, { recursive: true, force: true });
10584
+ throw new Error("Could not prepare Claude's lightweight runtime. The shared runtime and ClosedRouter config directory must be on the same filesystem, with codesign available.", { cause: error });
10585
+ }
10586
+ }
10224
10587
  function findClaudeSharedElectron(sourceApp, home = homedir13()) {
10225
10588
  const explicit = process.env.CLOSEDROUTER_CLAUDE_ELECTRON || process.env.CLAUDE_DESKTOP_AUTH_ELECTRON;
10226
10589
  if (explicit && existsSync14(explicit))
@@ -10233,11 +10596,12 @@ function findClaudeSharedElectron(sourceApp, home = homedir13()) {
10233
10596
  const versions = readdirSync2(root).filter((version) => version.startsWith(`${major}.`)).sort((a, b) => b.localeCompare(a, undefined, { numeric: true }));
10234
10597
  return versions.map((version) => join14(root, version, `darwin-${process.arch}`, "Electron.app/Contents/MacOS/Electron")).find((path) => existsSync14(path));
10235
10598
  }
10236
- function useClaudeSharedElectron(plan, runtime, sourceApp, toolArgs = []) {
10237
- plan.command = runtime;
10238
- plan.args = [join14(sourceApp, "Contents/Resources/app.asar"), `--user-data-dir=${join14(plan.profileDir, "user-data-3p")}`, ...toolArgs];
10599
+ function useClaudeSharedElectron(plan, runtime, sourceApp, toolArgs = [], launchServices = false) {
10600
+ const args = [join14(sourceApp, "Contents/Resources/app.asar"), `--user-data-dir=${join14(plan.profileDir, "user-data-3p")}`, ...toolArgs];
10601
+ plan.command = launchServices ? "/usr/bin/open" : runtime;
10602
+ plan.args = launchServices ? ["-W", "-n", "-a", dirname3(dirname3(dirname3(runtime))), "--args", ...args] : args;
10239
10603
  }
10240
- function addClaudeDesktopHook(plan, baseUrl, localKey, sourceApp) {
10604
+ function addClaudeDesktopHook(plan, baseUrl, localKey, sourceApp, models) {
10241
10605
  const path = join14(plan.profileDir, "claude-hook.cjs");
10242
10606
  plan.files.push({ path, contents: `${sourceApp ? `const electron = require('electron');
10243
10607
  Object.defineProperty(process, 'resourcesPath', {value: ${JSON.stringify(join14(sourceApp, "Contents/Resources"))}});
@@ -10260,7 +10624,22 @@ if (autoUpdater) {
10260
10624
  autoUpdater.quitAndInstall = () => {};
10261
10625
  }
10262
10626
  require('node:fs').writeFileSync(${JSON.stringify(join14(plan.profileDir, "hook-loaded"))}, 'ready', {mode: 0o600});
10263
- ` });
10627
+ ` + (models && sourceApp ? `
10628
+ const moduleHooks = require('node:module');
10629
+ const patchIdentity = ${patchClaudeModelIdentity.toString()};
10630
+ if (typeof moduleHooks.registerHooks === 'function') moduleHooks.registerHooks({load(url, context, nextLoad) {
10631
+ const loaded = nextLoad(url, context);
10632
+ if (url.startsWith('file:') && require('node:url').fileURLToPath(url).startsWith(${JSON.stringify(join14(sourceApp, "Contents/Resources/app.asar/.vite/build/"))}) && loaded.source != null) {
10633
+ const source = typeof loaded.source === 'string' ? loaded.source : Buffer.from(loaded.source).toString('utf8');
10634
+ const patched = patchIdentity(source, ${JSON.stringify(Object.fromEntries(models.map((id) => [transportModelId(id, "claude"), id])))});
10635
+ if (patched !== source) {
10636
+ require('node:fs').writeFileSync(${JSON.stringify(join14(plan.profileDir, "model-identity-hook-ready"))}, 'ready', {mode:0o600});
10637
+ return {...loaded, source:patched};
10638
+ }
10639
+ }
10640
+ return loaded;
10641
+ }});
10642
+ ` : "") + (models ? modelBrandingHook(models) : "") });
10264
10643
  const logPath = join14(plan.profileDir, "inspector.log");
10265
10644
  plan.inspectorHook = { path, logPath };
10266
10645
  plan.args.push("--inspect-brk=127.0.0.1:0");
@@ -10269,30 +10648,69 @@ require('node:fs').writeFileSync(${JSON.stringify(join14(plan.profileDir, "hook-
10269
10648
  }
10270
10649
 
10271
10650
  // src/commands/cursor-desktop-hook.ts
10272
- import { readFileSync as readFileSync12 } from "node:fs";
10273
- import { dirname as dirname3, join as join15 } from "node:path";
10651
+ import { existsSync as existsSync15, readFileSync as readFileSync13 } from "node:fs";
10652
+ import { dirname as dirname4, join as join15 } from "node:path";
10274
10653
  var FLAGS = /([\w$]+)=\{extensionIsDev:[^{}]{1,1000}localMode:(?:!1|false)\}/;
10275
- function addCursorDesktopLocalHook(plan, app) {
10276
- const resources = app.endsWith(".app") ? join15(app, "Contents/Resources/app") : join15(dirname3(app), "resources/app");
10654
+ function addCursorDesktopLocalHook(plan, app, catalog) {
10655
+ const resources = app.endsWith(".app") ? join15(app, "Contents/Resources/app") : join15(dirname4(app), "resources/app");
10277
10656
  const source = join15(resources, "out/main.js");
10278
- const renderer = join15(resources, "out/vs/workbench/workbench.desktop.main.js");
10279
- const mainSource = readFileSync12(source, "utf8");
10280
- const rendererSource = readFileSync12(renderer, "utf8");
10657
+ const mainSource = readFileSync13(source, "utf8");
10281
10658
  const mainMatch = FLAGS.exec(mainSource);
10282
- const rendererMatch = FLAGS.exec(rendererSource);
10283
- if (!mainMatch || !rendererMatch || !rendererSource.includes("runLocalAgentInExtensionHost"))
10659
+ if (!mainMatch)
10284
10660
  throw new Error("This Cursor Desktop build does not include the supported local agent runtime. Update Cursor or use cr start cursor with Cursor Agent Local.");
10285
10661
  const nativeKey = mainMatch[1];
10286
- const authMethod = "isAuthenticated(){const e=this.accessToken(),t=this.refreshToken();return this.scheduleTeamPolicyCheck(),!!(e&&t)}";
10287
- if (!rendererSource.includes(authMethod))
10288
- throw new Error("This Cursor Desktop build has an unsupported login controller. Update the ClosedRouter launcher before using local provider mode.");
10289
- const modifiedRenderer = rendererSource.replace(rendererMatch[0], rendererMatch[0].replace(/localMode:(?:!1|false)/, "localMode:!0")).replace(authMethod, `isAuthenticated(){const e=this.accessToken(),t=this.refreshToken();if(!e&&!t&&${rendererMatch[1]}.localMode&&this.byokProviderKeyStorage?.openAIKey()&&this._reactiveStorageService.applicationUserPersistentStorage.openAIBaseUrl)return true;return this.scheduleTeamPolicyCheck(),!!(e&&t)}`);
10290
- const patched = join15(plan.profileDir, "cursor-local-workbench.js");
10662
+ const redirects = {};
10663
+ for (const variant of ["desktop", "glass"]) {
10664
+ const renderer = join15(resources, `out/vs/workbench/workbench.${variant}.main.js`);
10665
+ if (!existsSync15(renderer) && variant === "glass")
10666
+ continue;
10667
+ const rendererSource = readFileSync13(renderer, "utf8");
10668
+ const rendererMatch = FLAGS.exec(rendererSource);
10669
+ if (!rendererMatch || !rendererSource.includes("runLocalAgentInExtensionHost"))
10670
+ throw new Error("This Cursor Desktop build does not include the supported local agent runtime. Update Cursor or use cr start cursor with Cursor Agent Local.");
10671
+ const authMethod = /isAuthenticated\(\)\{const ([\w$]+)=this\.accessToken\(\),([\w$]+)=this\.refreshToken\(\);return this\.scheduleTeamPolicyCheck\(\),!!\(\1&&\2\)\}/;
10672
+ if (!authMethod.test(rendererSource))
10673
+ throw new Error("This Cursor Desktop build has an unsupported login controller. Update the ClosedRouter launcher before using local provider mode.");
10674
+ let modifiedRenderer = rendererSource.replace(rendererMatch[0], rendererMatch[0].replace(/localMode:(?:!1|false)/, "localMode:!0")).replace(authMethod, `isAuthenticated(){const e=this.accessToken(),t=this.refreshToken();if(!e&&!t&&${rendererMatch[1]}.localMode&&this.byokProviderKeyStorage?.openAIKey()&&this._reactiveStorageService.applicationUserPersistentStorage.openAIBaseUrl)return true;return this.scheduleTeamPolicyCheck(),!!(e&&t)}`);
10675
+ if (catalog) {
10676
+ const nativeCatalog = "getAvailableDefaultModels(){return[...this._availableDefaultModels()]}";
10677
+ if (!modifiedRenderer.includes(nativeCatalog))
10678
+ throw new Error("This Cursor Desktop build has an unsupported model catalog controller. Update the ClosedRouter launcher.");
10679
+ const entries = catalog.models.map((id) => ({
10680
+ name: id,
10681
+ serverModelName: id,
10682
+ clientDisplayName: id,
10683
+ inputboxShortModelName: id,
10684
+ defaultOn: true,
10685
+ supportsAgent: true,
10686
+ supportsPlanMode: true,
10687
+ supportsNonMaxMode: true,
10688
+ supportsImages: false,
10689
+ supportsThinking: false,
10690
+ supportsCmdK: true,
10691
+ supportsSmartModeClassifier: true,
10692
+ parameterDefinitions: [],
10693
+ variants: [],
10694
+ legacySlugs: [],
10695
+ idAliases: [],
10696
+ namedModelSectionIndex: 0,
10697
+ tooltipData: { markdownContent: "Via your ClosedRouter pool" }
10698
+ }));
10699
+ modifiedRenderer = modifiedRenderer.replace(nativeCatalog, `getAvailableDefaultModels(){return ${JSON.stringify(entries)}}`);
10700
+ const display = /function ([\w$]+)\(([\w$]+),([\w$]+),([\w$]+)\)\{if\(\2\.includes\(","\)\)/;
10701
+ if (!display.test(modifiedRenderer))
10702
+ throw new Error("This Cursor Desktop build has an unsupported model-label formatter. Update the ClosedRouter launcher.");
10703
+ modifiedRenderer = modifiedRenderer.replace(display, (all, _fn, model) => all.replace("{if(", `{if(${JSON.stringify(catalog.models)}.includes(${model}))return 'ClosedRouter: '+${model};if(`));
10704
+ }
10705
+ const patched = join15(plan.profileDir, `cursor-local-workbench${variant === "desktop" ? "" : "-glass"}.js`);
10706
+ redirects[renderer] = patched;
10707
+ plan.files.push({ path: patched, contents: modifiedRenderer });
10708
+ }
10291
10709
  const hook = join15(plan.profileDir, "cursor-local-hook.cjs");
10292
10710
  const marker = join15(plan.profileDir, "main-local-mode-enabled");
10293
- plan.files.push({ path: patched, contents: modifiedRenderer });
10294
10711
  plan.files.push({ path: hook, contents: `const {app, session} = require('electron');
10295
10712
  const fs = require('node:fs');
10713
+ const redirects = ${JSON.stringify(redirects)};
10296
10714
  function installProtocolHook() {
10297
10715
  const protocol = session.defaultSession.protocol;
10298
10716
  const prototype = Object.getPrototypeOf(protocol);
@@ -10300,9 +10718,9 @@ function installProtocolHook() {
10300
10718
  prototype.registerFileProtocol = function(scheme, handler, ...rest) {
10301
10719
  if (scheme !== 'vscode-file') return register.call(this, scheme, handler, ...rest);
10302
10720
  return register.call(this, scheme, (request, callback) => handler(request, result => {
10303
- if (request.url.includes('workbench.desktop.main.js')) fs.writeFileSync(${JSON.stringify(join15(plan.profileDir, "renderer-request.json"))}, JSON.stringify({type: typeof result, path: result?.path}), {mode: 0o600});
10304
- if (result && typeof result === 'object' && result.path === ${JSON.stringify(renderer)}) {
10305
- result = {...result, path: ${JSON.stringify(patched)}};
10721
+ if (/workbench.(?:desktop|glass).main.js/.test(request.url)) fs.writeFileSync(${JSON.stringify(join15(plan.profileDir, "renderer-request.json"))}, JSON.stringify({type: typeof result, path: result?.path}), {mode: 0o600});
10722
+ if (result && typeof result === 'object' && Object.hasOwn(redirects, result.path)) {
10723
+ result = {...result, path: redirects[result.path]};
10306
10724
  fs.writeFileSync(${JSON.stringify(join15(plan.profileDir, "renderer-local-mode-served"))}, 'ready', {mode: 0o600});
10307
10725
  }
10308
10726
  callback(result);
@@ -10312,7 +10730,7 @@ function installProtocolHook() {
10312
10730
  }
10313
10731
  if (app.isReady()) installProtocolHook(); else app.once('ready', installProtocolHook);
10314
10732
  require('node:fs').writeFileSync(${JSON.stringify(join15(plan.profileDir, "hook-loaded"))}, 'ready', {mode: 0o600});
10315
- ` });
10733
+ ` + (catalog ? modelBrandingHook(catalog.models) : "") });
10316
10734
  const point = mainSource.slice(0, mainMatch.index + mainMatch[0].length).split(`
10317
10735
  `);
10318
10736
  const logPath = join15(plan.profileDir, "inspector.log");
@@ -10348,7 +10766,7 @@ var TOOL_OPTIONS = [
10348
10766
  var DESKTOP_TOOL_OPTIONS = [
10349
10767
  { value: "codex-desktop", label: "Codex Desktop", hint: "Responses API with an isolated app profile" },
10350
10768
  { value: "antigravity-desktop", label: "Antigravity Desktop", hint: "ClosedRouter routing through your antigravity-mods hook" },
10351
- { value: "cursor-desktop", label: "Cursor Desktop", hint: "Automatic API-key setup; requires a public gateway" },
10769
+ { value: "cursor-desktop", label: "Cursor Desktop", hint: "Local model engine using your ClosedRouter pool" },
10352
10770
  { value: "claude-desktop", label: "Claude Desktop", hint: "Inference gateway with an isolated app profile" }
10353
10771
  ];
10354
10772
  function getStartToolOptions(desktopOnly = false) {
@@ -10378,7 +10796,7 @@ function assertNoManagedCodexArgs(args) {
10378
10796
  throw new Error(`Remove ${conflicting}; ClosedRouter manages the Codex profile and model. Put other arguments after --.`);
10379
10797
  }
10380
10798
  }
10381
- function buildLaunchPlan(tool, serverUrl, apiKey, model, toolArgs = [], homeDir = homedir14(), executablePath, agyRouting) {
10799
+ function buildLaunchPlan(tool, serverUrl, apiKey, model, toolArgs = [], homeDir = homedir14(), executablePath, agyRouting, catalog) {
10382
10800
  const baseUrl = normalizeServerUrl(serverUrl);
10383
10801
  const env = { ...process.env };
10384
10802
  const command = executablePath || tool;
@@ -10398,7 +10816,7 @@ function buildLaunchPlan(tool, serverUrl, apiKey, model, toolArgs = [], homeDir
10398
10816
  return {
10399
10817
  tool,
10400
10818
  command,
10401
- args: ["--model", model, ...toolArgs],
10819
+ args: ["--model", model, ...catalog ? ["--settings", JSON.stringify({ modelPicker: claudeModelPicker(catalog.models) })] : [], ...toolArgs],
10402
10820
  env
10403
10821
  };
10404
10822
  }
@@ -10407,9 +10825,11 @@ function buildLaunchPlan(tool, serverUrl, apiKey, model, toolArgs = [], homeDir
10407
10825
  const codexHome = process.env.CODEX_HOME || join16(homeDir, ".codex");
10408
10826
  const profileName = "closedrouter-launch";
10409
10827
  const configPath = join16(codexHome, `${profileName}.config.toml`);
10828
+ const catalogPath = join16(codexHome, "closedrouter-models.json");
10410
10829
  const configLines = [
10411
10830
  `model = ${tomlString(model)}`,
10412
10831
  `model_provider = ${tomlString(profileName)}`,
10832
+ ...catalog ? [`model_catalog_json = ${tomlString(catalogPath)}`] : [],
10413
10833
  "",
10414
10834
  `[model_providers.${profileName}]`,
10415
10835
  `name = ${tomlString("ClosedRouter")}`,
@@ -10425,7 +10845,8 @@ function buildLaunchPlan(tool, serverUrl, apiKey, model, toolArgs = [], homeDir
10425
10845
  args: ["--profile", profileName, ...toolArgs],
10426
10846
  env,
10427
10847
  configFile: { path: configPath, contents: configLines.join(`
10428
- `) }
10848
+ `) },
10849
+ ...catalog ? { extraFiles: [{ path: catalogPath, contents: JSON.stringify(codexModelCatalog(catalog.models)) }] } : {}
10429
10850
  };
10430
10851
  }
10431
10852
  if (tool === "gemini") {
@@ -10441,7 +10862,7 @@ function buildLaunchPlan(tool, serverUrl, apiKey, model, toolArgs = [], homeDir
10441
10862
  env,
10442
10863
  configFile: {
10443
10864
  path: configPath,
10444
- contents: `${JSON.stringify({ security: { auth: { selectedType: "gemini-api-key" } } }, null, 2)}
10865
+ contents: `${JSON.stringify({ security: { auth: { selectedType: "gemini-api-key" } }, ...catalog ? geminiModelCatalog(catalog.models, model) : {} }, null, 2)}
10445
10866
  `
10446
10867
  }
10447
10868
  };
@@ -10467,8 +10888,9 @@ function buildLaunchPlan(tool, serverUrl, apiKey, model, toolArgs = [], homeDir
10467
10888
  return {
10468
10889
  tool,
10469
10890
  command,
10470
- args: ["--model", `openai/${model}`, ...toolArgs],
10471
- env
10891
+ args: ["--model", `openai/${catalog ? "closedrouter/" : ""}${model}`, ...catalog ? ["--model-metadata-file", join16(homeDir, ".closedrouter", "aider-models.json")] : [], ...toolArgs],
10892
+ env,
10893
+ ...catalog ? { configFile: { path: join16(homeDir, ".closedrouter", "aider-models.json"), contents: JSON.stringify(Object.fromEntries(catalog.models.map((id) => [`openai/closedrouter/${id}`, { litellm_provider: "openai", mode: "chat", max_input_tokens: 8192, max_output_tokens: 4096, supports_function_calling: true }]))) } } : {}
10472
10894
  };
10473
10895
  }
10474
10896
  const config = {
@@ -10481,7 +10903,7 @@ function buildLaunchPlan(tool, serverUrl, apiKey, model, toolArgs = [], homeDir
10481
10903
  baseURL: `${baseUrl}/v1`,
10482
10904
  apiKey: "{env:CLOSEDROUTER_API_KEY}"
10483
10905
  },
10484
- models: { [model]: { name: model } }
10906
+ models: Object.fromEntries((catalog?.models || [model]).map((id) => [id, { name: modelLabel(id) }]))
10485
10907
  }
10486
10908
  }
10487
10909
  };
@@ -10495,9 +10917,9 @@ function buildLaunchPlan(tool, serverUrl, apiKey, model, toolArgs = [], homeDir
10495
10917
  };
10496
10918
  }
10497
10919
  function writeOwnedConfig(file) {
10498
- mkdirSync4(dirname4(file.path), { recursive: true, mode: 448 });
10920
+ mkdirSync5(dirname5(file.path), { recursive: true, mode: 448 });
10499
10921
  try {
10500
- const existing = readFileSync13(file.path, "utf8");
10922
+ const existing = readFileSync14(file.path, "utf8");
10501
10923
  if (file.path.endsWith(".config.toml") && !existing.includes('name = "ClosedRouter"')) {
10502
10924
  throw new Error(`Refusing to replace an existing Codex profile at ${file.path}.`);
10503
10925
  }
@@ -10505,7 +10927,7 @@ function writeOwnedConfig(file) {
10505
10927
  if (error instanceof Error && error.message.startsWith("Refusing to replace"))
10506
10928
  throw error;
10507
10929
  }
10508
- writeFileSync4(file.path, file.contents, { encoding: "utf8", mode: 384 });
10930
+ writeFileSync5(file.path, file.contents, { encoding: "utf8", mode: 384 });
10509
10931
  }
10510
10932
  function modelSupportsHarness(model, tool) {
10511
10933
  const parameters = model.supported_parameters;
@@ -10536,7 +10958,7 @@ function assertAgyCustomModel(command, label, apiKey) {
10536
10958
  function assertAgyGeminiMode(homeDir) {
10537
10959
  const settingsPath = join16(homeDir, ".gemini", "antigravity-cli", "settings.json");
10538
10960
  try {
10539
- const settings = JSON.parse(readFileSync13(settingsPath, "utf8"));
10961
+ const settings = JSON.parse(readFileSync14(settingsPath, "utf8"));
10540
10962
  if (settings?.modelProvider === "gemini")
10541
10963
  return;
10542
10964
  } catch {}
@@ -10600,11 +11022,12 @@ async function startCommand(options = {}) {
10600
11022
  }
10601
11023
  if (desktop && tool === "claude") {
10602
11024
  assertClaudeDesktopGatewaySupport(executablePath);
10603
- if (options.desktopInspector && process.platform === "darwin" && executablePath.endsWith(".app")) {
11025
+ if (process.platform === "darwin" && executablePath.endsWith(".app")) {
10604
11026
  sharedClaudeElectron = findClaudeSharedElectron(executablePath);
10605
- if (sharedClaudeElectron)
10606
- M2.info("Using the shared Electron runtime for Claude's startup inspector. The installed app bundle is reused.");
10607
- else
11027
+ if (sharedClaudeElectron) {
11028
+ sharedClaudeElectron = prepareClaudeElectronRuntime(sharedClaudeElectron, executablePath);
11029
+ M2.info("Using the lightweight Claude launcher with Cowork virtualization support. Existing framework files are shared.");
11030
+ } else if (options.desktopInspector)
10608
11031
  throw new Error("No compatible shared Electron runtime was found. Set CLOSEDROUTER_CLAUDE_ELECTRON to an installed Electron executable or omit --desktop-inspector to use Claude's native gateway mode.");
10609
11032
  }
10610
11033
  }
@@ -10643,14 +11066,15 @@ async function startCommand(options = {}) {
10643
11066
  if (!isGatewayModelAvailable(availableModels, model)) {
10644
11067
  throw new Error(`Model '${model}' is not available for ${tool} with this platform key. Agent clients require a provider that supports gateway function tools. Choose another model from ClosedRouter's model catalog.`);
10645
11068
  }
11069
+ const catalog = createLaunchCatalog(availableModels, model);
10646
11070
  if (desktop && tool === "cursor") {
10647
11071
  const localKey = randomUUID5();
10648
- const relay = await startLaunchProxy(serverUrl, apiKey, "cursor-desktop", localKey, model);
11072
+ const relay = await startLaunchProxy(serverUrl, apiKey, "cursor-desktop", localKey, catalog);
10649
11073
  try {
10650
11074
  const plan = buildDesktopLaunchPlan("cursor", executablePath, createDesktopProfile("cursor"), undefined, undefined, undefined, options.toolArgs);
10651
- addCursorDesktopLocalHook(plan, executablePath);
11075
+ addCursorDesktopLocalHook(plan, executablePath, catalog);
10652
11076
  writeDesktopLaunchPlan(plan);
10653
- await configureCursorDesktop(plan.profileDir, relay.baseUrl, localKey, model);
11077
+ await configureCursorDesktop(plan.profileDir, relay.baseUrl, localKey, model, undefined, catalog.models);
10654
11078
  M2.info(`Starting Cursor desktop local provider mode with ${model}. Model requests use ClosedRouter, without a paid Cursor subscription. Profile: ${plan.profileDir}. Keep cr running until you close this app instance.`);
10655
11079
  process.exitCode = await runDesktopLaunch(plan);
10656
11080
  } finally {
@@ -10660,18 +11084,18 @@ async function startCommand(options = {}) {
10660
11084
  }
10661
11085
  if (desktop && (tool === "codex" || tool === "claude" || tool === "agy")) {
10662
11086
  const localKey = randomUUID5();
10663
- const relay = await startLaunchProxy(serverUrl, apiKey, `${tool}-desktop`, localKey, tool === "claude" ? model : undefined);
11087
+ const relay = await startLaunchProxy(serverUrl, apiKey, `${tool}-desktop`, localKey, catalog);
10664
11088
  let agyDesktopProxy;
10665
11089
  try {
10666
- const plan = buildDesktopLaunchPlan(tool, executablePath, createDesktopProfile(tool), relay.baseUrl, localKey, model, options.toolArgs);
10667
- if (options.desktopInspector && tool === "claude" && executablePath.endsWith(".app") && process.platform === "darwin") {
11090
+ const plan = buildDesktopLaunchPlan(tool, executablePath, createDesktopProfile(tool), relay.baseUrl, localKey, model, options.toolArgs, process.platform, catalog.models);
11091
+ if (sharedClaudeElectron && tool === "claude") {
10668
11092
  if (sharedClaudeElectron)
10669
- useClaudeSharedElectron(plan, sharedClaudeElectron, sourceApp, options.toolArgs);
10670
- addClaudeDesktopHook(plan, relay.baseUrl, localKey, sharedClaudeElectron ? sourceApp : undefined);
11093
+ useClaudeSharedElectron(plan, sharedClaudeElectron, sourceApp, options.toolArgs, true);
11094
+ addClaudeDesktopHook(plan, relay.baseUrl, localKey, sourceApp, catalog.models);
10671
11095
  }
10672
11096
  if (tool === "agy") {
10673
- agyDesktopProxy = await startAgyDesktopProxy(relay.baseUrl, localKey, model);
10674
- addAntigravityDesktopHook(plan, agyDesktopProxy.baseUrl);
11097
+ agyDesktopProxy = await startAgyDesktopProxy(relay.baseUrl, localKey, model, undefined, catalog);
11098
+ addAntigravityDesktopHook(plan, agyDesktopProxy.baseUrl, catalog);
10675
11099
  }
10676
11100
  writeDesktopLaunchPlan(plan);
10677
11101
  M2.info(`Starting ${tool} desktop with ${model} through ${serverUrl}. Profile: ${plan.profileDir}. Keep cr running until you close this app instance.`);
@@ -10684,16 +11108,18 @@ async function startCommand(options = {}) {
10684
11108
  }
10685
11109
  if (tool === "agy")
10686
11110
  assertAgyGeminiMode(homedir14());
10687
- const recognizedModel = tool === "agy" ? registerAgyLaunchModel(homedir14(), model, discoverAgyTransportModel(options.executablePath || "agy", apiKey)) : undefined;
11111
+ const recognizedModel = tool === "agy" ? registerAgyLaunchCatalog(homedir14(), catalog, discoverAgyTransportModel(options.executablePath || "agy", apiKey)) : undefined;
10688
11112
  if (recognizedModel)
10689
11113
  assertAgyCustomModel(options.executablePath || "agy", recognizedModel, apiKey);
10690
- const relay = await startLaunchProxy(serverUrl, apiKey, tool);
11114
+ const relay = await startLaunchProxy(serverUrl, apiKey, tool, apiKey, catalog);
10691
11115
  let agyProxy;
10692
11116
  try {
10693
- agyProxy = tool === "agy" ? await startAgyModelProxy(relay.baseUrl, apiKey, model) : undefined;
10694
- const plan = buildLaunchPlan(tool, relay.baseUrl, apiKey, model, options.toolArgs, homedir14(), executablePath, agyProxy && recognizedModel ? { baseUrl: agyProxy.baseUrl, recognizedModel } : undefined);
11117
+ agyProxy = tool === "agy" ? await startAgyModelProxy(relay.baseUrl, apiKey, model, catalog) : undefined;
11118
+ const plan = buildLaunchPlan(tool, relay.baseUrl, apiKey, model, options.toolArgs, homedir14(), executablePath, agyProxy && recognizedModel ? { baseUrl: agyProxy.baseUrl, recognizedModel } : undefined, catalog);
10695
11119
  if (plan.configFile)
10696
11120
  writeOwnedConfig(plan.configFile);
11121
+ for (const file of plan.extraFiles || [])
11122
+ writeOwnedConfig(file);
10697
11123
  M2.info(`${import_picocolors12.default.green("Starting")} ${import_picocolors12.default.bold(TOOL_OPTIONS.find((option) => option.value === tool)?.label || tool)} with ${import_picocolors12.default.cyan(model)} through ${import_picocolors12.default.dim(serverUrl)}`);
10698
11124
  const exitCode = await runTool(plan);
10699
11125
  process.exitCode = exitCode;
@@ -10748,7 +11174,7 @@ process.on("warning", (warning) => {
10748
11174
  console.warn(warning.name, warning.message);
10749
11175
  });
10750
11176
  var program2 = new Command2;
10751
- var cliVersion = JSON.parse(readFileSync14(new URL("../package.json", import.meta.url), "utf8")).version;
11177
+ var cliVersion = JSON.parse(readFileSync15(new URL("../package.json", import.meta.url), "utf8")).version;
10752
11178
  program2.name("closedrouter").description("ClosedRouter CLI: Import local AI subscriptions & API keys into your shared token pool").version(cliVersion).option("-s, --server <url>", "ClosedRouter server base URL (default: http://localhost:8000)").action(async (options) => {
10753
11179
  await onboardCommand({ server: options.server });
10754
11180
  });