@closedrouter/cli 1.1.6 → 1.1.8

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/README.md CHANGED
@@ -193,3 +193,15 @@ print(response.choices[0].message.content)
193
193
  ## License
194
194
 
195
195
  MIT © ClosedRouter Contributors
196
+
197
+ ### Harness telemetry
198
+
199
+ `cr start` attributes each model request to the launched client and a fresh launch UUID. A streaming loopback relay adds `X-ClosedRouter-Harness` and `X-ClosedRouter-Launch-ID` for all supported clients, including clients without custom-header settings. Keep the launcher running for the duration of the client session. Re-run `cr start` to create a new session.
200
+
201
+ Activity shows per-harness requests, tokens, cached tokens, reasoning tokens, latency, errors, and launch counts for the selected period. Search request logs by harness or launch UUID; open a request to inspect its attribution and routing attempts. The relay does not retain request or response bodies, keys, arguments, project paths, or hostnames. These client-supplied labels are diagnostic metadata, not an authorization boundary.
202
+
203
+ Other integrations may set the same headers directly. Known client user agents provide fallback attribution; unrecognized clients and older logs appear as `unknown`. Playground requests are tagged `playground`.
204
+
205
+ Request inspection also reports streaming mode, tools offered, tool-result messages supplied, and total message count. These counts describe the request, not which tools the client actually executed. Tool names, arguments, outputs, and message contents are not saved.
206
+
207
+ Antigravity launches register a credential-free custom Gemini-compatible model in `~/.gemini/antigravity-cli/settings.json` under `customModelsConfig.customModels`. Its visible label is `ClosedRouter: <selected model>`, while its internal Gemini transport ID stays compatible with agy. The local adapter continues routing every request to the actual selected pool model. This fixes the previous Gemini placeholder in agy's header. Existing preferences and custom models are preserved; request routing still uses the local model adapter and telemetry relay. Re-run `cr start agy` to choose a different pool model.
@@ -0,0 +1,7 @@
1
+ /** Streaming transport relay: inject attribution for clients with no custom-header setting. */
2
+ export declare function startLaunchProxy(gatewayUrl: string, apiKey: string, harness: string): Promise<{
3
+ baseUrl: string;
4
+ launchId: `${string}-${string}-${string}-${string}-${string}`;
5
+ close: () => Promise<void>;
6
+ }>;
7
+ //# sourceMappingURL=launch-proxy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"launch-proxy.d.ts","sourceRoot":"","sources":["../../src/bridge/launch-proxy.ts"],"names":[],"mappings":"AAMA,+FAA+F;AAC/F,wBAAsB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;;;;GAqDzF"}
@@ -0,0 +1,3 @@
1
+ /** agy's custom-model key is also its visible label. Keep the upstream ID separate. */
2
+ export declare function registerAgyLaunchModel(homeDir: string, model: string, transportModel: string): string;
3
+ //# sourceMappingURL=agy-model.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agy-model.d.ts","sourceRoot":"","sources":["../../src/commands/agy-model.ts"],"names":[],"mappings":"AAQA,uFAAuF;AACvF,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,MAAM,CA4BrG"}
@@ -1 +1 @@
1
- {"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../../src/commands/start.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAaH,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,KAAK,GAAG,OAAO,GAAG,UAAU,CAAC;AACrF,KAAK,OAAO,GAAG,SAAS,CAAC;AAEzB,MAAM,WAAW,mBAAmB;IAClC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,UAAU,UAAU;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB;AAED,wBAAgB,uBAAuB,CAAC,eAAe,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAEzF;AA8BD,wBAAgB,eAAe,CAC7B,IAAI,EAAE,OAAO,EACb,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,QAAQ,GAAE,MAAM,EAAO,EACvB,OAAO,SAAY,EACnB,cAAc,CAAC,EAAE,MAAM,EACvB,UAAU,CAAC,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,CAAA;CAAE,GACxD,UAAU,CAiHZ;AAmBD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAKzF;AAiED,wBAAsB,YAAY,CAAC,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAmDnF"}
1
+ {"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../../src/commands/start.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAeH,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,KAAK,GAAG,OAAO,GAAG,UAAU,CAAC;AACrF,KAAK,OAAO,GAAG,SAAS,CAAC;AAEzB,MAAM,WAAW,mBAAmB;IAClC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,UAAU,UAAU;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB;AAED,wBAAgB,uBAAuB,CAAC,eAAe,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAEzF;AA8BD,wBAAgB,eAAe,CAC7B,IAAI,EAAE,OAAO,EACb,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,QAAQ,GAAE,MAAM,EAAO,EACvB,OAAO,SAAY,EACnB,cAAc,CAAC,EAAE,MAAM,EACvB,UAAU,CAAC,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,CAAA;CAAE,GACxD,UAAU,CAiHZ;AAmBD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAKzF;AA2ED,wBAAsB,YAAY,CAAC,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAiDnF"}
package/dist/index.js CHANGED
@@ -5197,7 +5197,7 @@ var {
5197
5197
  } = exports_commander;
5198
5198
 
5199
5199
  // src/index.ts
5200
- import { readFileSync as readFileSync9 } from "node:fs";
5200
+ import { readFileSync as readFileSync10 } from "node:fs";
5201
5201
 
5202
5202
  // ../node_modules/.bun/@clack+prompts@0.11.0/node_modules/@clack/prompts/dist/index.mjs
5203
5203
  import { stripVTControlCharacters as S2 } from "node:util";
@@ -8645,21 +8645,96 @@ to scan and import local subscriptions!`, "Pool Empty");
8645
8645
  var import_picocolors12 = __toESM(require_picocolors(), 1);
8646
8646
  import { spawn as spawn3 } from "node:child_process";
8647
8647
  import { execFileSync } from "node:child_process";
8648
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync8, writeFileSync as writeFileSync2 } from "node:fs";
8648
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync9, writeFileSync as writeFileSync3 } from "node:fs";
8649
8649
  import { homedir as homedir9 } from "node:os";
8650
- import { dirname, join as join9 } from "node:path";
8650
+ import { dirname, join as join10 } from "node:path";
8651
8651
 
8652
- // src/commands/agy-proxy.ts
8653
- import { createServer as createServer2 } from "node:http";
8654
- import { request as httpRequest } from "node:http";
8652
+ // src/bridge/launch-proxy.ts
8653
+ import { randomUUID } from "node:crypto";
8654
+ import { createServer as createServer2, request as httpRequest } from "node:http";
8655
8655
  import { request as httpsRequest } from "node:https";
8656
+ var HOP_HEADERS = new Set(["connection", "keep-alive", "proxy-authenticate", "proxy-authorization", "te", "trailer", "transfer-encoding", "upgrade", "host"]);
8657
+ async function startLaunchProxy(gatewayUrl, apiKey, harness) {
8658
+ const upstream = new URL(gatewayUrl.replace(/\/+$/, "") + "/");
8659
+ if (!["http:", "https:"].includes(upstream.protocol) || upstream.username || upstream.password) {
8660
+ throw new Error("ClosedRouter server URL must use HTTP or HTTPS without embedded credentials.");
8661
+ }
8662
+ const launchId = randomUUID();
8663
+ const transport = upstream.protocol === "https:" ? httpsRequest : httpRequest;
8664
+ const server = createServer2((incoming, outgoing) => {
8665
+ const authorized = incoming.headers.authorization === `Bearer ${apiKey}` || incoming.headers["x-api-key"] === apiKey || incoming.headers["x-goog-api-key"] === apiKey;
8666
+ if (!authorized) {
8667
+ outgoing.writeHead(401).end();
8668
+ return;
8669
+ }
8670
+ if (!incoming.url?.startsWith("/") || incoming.url.startsWith("//")) {
8671
+ outgoing.writeHead(400).end();
8672
+ return;
8673
+ }
8674
+ const target = new URL(upstream);
8675
+ const local = new URL(incoming.url, "http://localhost");
8676
+ target.pathname = upstream.pathname.replace(/\/$/, "") + local.pathname;
8677
+ target.search = local.search;
8678
+ const headers = { ...incoming.headers };
8679
+ const connectionHeaders = String(headers.connection || "").split(",").map((x) => x.trim().toLowerCase());
8680
+ for (const key of Object.keys(headers)) {
8681
+ if (HOP_HEADERS.has(key) || connectionHeaders.includes(key) || key.startsWith("x-closedrouter-harness") || key === "x-closedrouter-launch-id")
8682
+ delete headers[key];
8683
+ }
8684
+ headers["x-closedrouter-harness"] = harness;
8685
+ headers["x-closedrouter-launch-id"] = launchId;
8686
+ const request = transport(target, { method: incoming.method, headers }, (response) => {
8687
+ const responseHeaders = { ...response.headers };
8688
+ for (const key of HOP_HEADERS)
8689
+ delete responseHeaders[key];
8690
+ outgoing.writeHead(response.statusCode || 502, responseHeaders);
8691
+ response.on("error", () => outgoing.destroy());
8692
+ response.pipe(outgoing);
8693
+ });
8694
+ request.on("error", () => {
8695
+ if (!outgoing.headersSent)
8696
+ outgoing.writeHead(502).end(JSON.stringify({ error: { message: "ClosedRouter connection failed." } }));
8697
+ else
8698
+ outgoing.destroy();
8699
+ });
8700
+ incoming.on("aborted", () => request.destroy());
8701
+ outgoing.on("close", () => {
8702
+ if (!outgoing.writableFinished)
8703
+ request.destroy();
8704
+ });
8705
+ incoming.pipe(request);
8706
+ });
8707
+ await new Promise((resolve, reject) => {
8708
+ server.once("error", reject);
8709
+ server.listen(0, "127.0.0.1", () => {
8710
+ server.off("error", reject);
8711
+ resolve();
8712
+ });
8713
+ });
8714
+ const address = server.address();
8715
+ if (!address || typeof address === "string")
8716
+ throw new Error("Could not bind the launch relay.");
8717
+ return {
8718
+ baseUrl: `http://127.0.0.1:${address.port}`,
8719
+ launchId,
8720
+ close: () => new Promise((resolve, reject) => {
8721
+ server.close((error) => error ? reject(error) : resolve());
8722
+ server.closeAllConnections();
8723
+ })
8724
+ };
8725
+ }
8726
+
8727
+ // src/commands/agy-proxy.ts
8728
+ import { createServer as createServer3 } from "node:http";
8729
+ import { request as httpRequest2 } from "node:http";
8730
+ import { request as httpsRequest2 } from "node:https";
8656
8731
  async function startAgyModelProxy(gatewayUrl, apiKey, model) {
8657
8732
  const upstream = new URL(gatewayUrl.replace(/\/+$/, "") + "/");
8658
8733
  if (!["https:", "http:"].includes(upstream.protocol)) {
8659
8734
  throw new Error("ClosedRouter server URL must use HTTP or HTTPS.");
8660
8735
  }
8661
- const requestUpstream = upstream.protocol === "https:" ? httpsRequest : httpRequest;
8662
- const server = createServer2((incoming, outgoing) => {
8736
+ const requestUpstream = upstream.protocol === "https:" ? httpsRequest2 : httpRequest2;
8737
+ const server = createServer3((incoming, outgoing) => {
8663
8738
  if (incoming.headers["x-goog-api-key"] !== apiKey) {
8664
8739
  outgoing.writeHead(401).end();
8665
8740
  return;
@@ -8703,6 +8778,47 @@ async function startAgyModelProxy(gatewayUrl, apiKey, model) {
8703
8778
  };
8704
8779
  }
8705
8780
 
8781
+ // src/commands/agy-model.ts
8782
+ import { randomUUID as randomUUID2 } from "node:crypto";
8783
+ import { existsSync as existsSync9, readFileSync as readFileSync8, renameSync, statSync, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
8784
+ import { join as join9 } from "node:path";
8785
+ function record(value) {
8786
+ return typeof value === "object" && value !== null && !Array.isArray(value);
8787
+ }
8788
+ function registerAgyLaunchModel(homeDir, model, transportModel) {
8789
+ const path = join9(homeDir, ".gemini", "antigravity-cli", "settings.json");
8790
+ const settings = JSON.parse(readFileSync8(path, "utf8"));
8791
+ if (!record(settings) || settings.modelProvider !== "gemini") {
8792
+ throw new Error(`Set "modelProvider": "gemini" in ${path} before starting Antigravity through ClosedRouter.`);
8793
+ }
8794
+ const config = settings.customModelsConfig ?? {};
8795
+ if (!record(config))
8796
+ throw new Error(`customModelsConfig must be an object in ${path}.`);
8797
+ const models = config.customModels ?? {};
8798
+ if (!record(models))
8799
+ throw new Error(`customModelsConfig.customModels must be an object in ${path}.`);
8800
+ const label = `ClosedRouter: ${model}`;
8801
+ const entry = { modelName: transportModel, apiProvider: "gemini" };
8802
+ const existing = models[label];
8803
+ if (existing !== undefined) {
8804
+ if (!record(existing) || existing.modelName !== transportModel || existing.apiProvider !== "gemini" || Object.keys(existing).some((key) => !["modelName", "apiProvider"].includes(key))) {
8805
+ throw new Error(`The custom model '${label}' already has different settings in ${path}. Rename that entry before launching it with ClosedRouter.`);
8806
+ }
8807
+ return label;
8808
+ }
8809
+ settings.customModelsConfig = { ...config, customModels: { ...models, [label]: entry } };
8810
+ const temporary = `${path}.closedrouter-${randomUUID2()}.tmp`;
8811
+ try {
8812
+ writeFileSync2(temporary, `${JSON.stringify(settings, null, 2)}
8813
+ `, { flag: "wx", mode: statSync(path).mode & 511 });
8814
+ renameSync(temporary, path);
8815
+ } finally {
8816
+ if (existsSync9(temporary))
8817
+ unlinkSync(temporary);
8818
+ }
8819
+ return label;
8820
+ }
8821
+
8706
8822
  // src/commands/start.ts
8707
8823
  function isGatewayModelAvailable(availableModels, model) {
8708
8824
  return availableModels.includes(model);
@@ -8750,9 +8866,9 @@ function buildLaunchPlan(tool, serverUrl, apiKey, model, toolArgs = [], homeDir
8750
8866
  }
8751
8867
  if (tool === "codex") {
8752
8868
  assertNoManagedCodexArgs(toolArgs);
8753
- const codexHome = process.env.CODEX_HOME || join9(homeDir, ".codex");
8869
+ const codexHome = process.env.CODEX_HOME || join10(homeDir, ".codex");
8754
8870
  const profileName = "closedrouter-launch";
8755
- const configPath = join9(codexHome, `${profileName}.config.toml`);
8871
+ const configPath = join10(codexHome, `${profileName}.config.toml`);
8756
8872
  const configLines = [
8757
8873
  `model = ${tomlString(model)}`,
8758
8874
  `model_provider = ${tomlString(profileName)}`,
@@ -8778,7 +8894,7 @@ function buildLaunchPlan(tool, serverUrl, apiKey, model, toolArgs = [], homeDir
8778
8894
  env.GEMINI_API_KEY = apiKey;
8779
8895
  env.GEMINI_MODEL = model;
8780
8896
  env.GOOGLE_GEMINI_BASE_URL = baseUrl;
8781
- const configPath = join9(homeDir, ".closedrouter", "gemini-launch.settings.json");
8897
+ const configPath = join10(homeDir, ".closedrouter", "gemini-launch.settings.json");
8782
8898
  env.GEMINI_CLI_SYSTEM_SETTINGS_PATH = configPath;
8783
8899
  return {
8784
8900
  tool,
@@ -8843,7 +8959,7 @@ function buildLaunchPlan(tool, serverUrl, apiKey, model, toolArgs = [], homeDir
8843
8959
  function writeOwnedConfig(file) {
8844
8960
  mkdirSync2(dirname(file.path), { recursive: true, mode: 448 });
8845
8961
  try {
8846
- const existing = readFileSync8(file.path, "utf8");
8962
+ const existing = readFileSync9(file.path, "utf8");
8847
8963
  if (file.path.endsWith(".config.toml") && !existing.includes('name = "ClosedRouter"')) {
8848
8964
  throw new Error(`Refusing to replace an existing Codex profile at ${file.path}.`);
8849
8965
  }
@@ -8851,7 +8967,7 @@ function writeOwnedConfig(file) {
8851
8967
  if (error instanceof Error && error.message.startsWith("Refusing to replace"))
8852
8968
  throw error;
8853
8969
  }
8854
- writeFileSync2(file.path, file.contents, { encoding: "utf8", mode: 384 });
8970
+ writeFileSync3(file.path, file.contents, { encoding: "utf8", mode: 384 });
8855
8971
  }
8856
8972
  function modelId(model) {
8857
8973
  return String(model.id || model.name || "");
@@ -8860,18 +8976,32 @@ function modelSupportsHarness(model, tool) {
8860
8976
  const parameters = model.supported_parameters;
8861
8977
  return (model.status === undefined || model.status === "available") && (model.available_tool_account_count === undefined || model.available_tool_account_count > 0) && (!Array.isArray(parameters) || parameters.includes("tools"));
8862
8978
  }
8863
- function discoverAgyModels(command) {
8979
+ function discoverAgyTransportModel(command, apiKey) {
8980
+ const output = execFileSync(command, ["models"], {
8981
+ encoding: "utf8",
8982
+ timeout: 1e4,
8983
+ stdio: ["ignore", "pipe", "ignore"],
8984
+ env: { ...process.env, GEMINI_API_KEY: apiKey, GOOGLE_GEMINI_BASE_URL: "http://127.0.0.1:1" }
8985
+ });
8986
+ const models = output.split(/\r?\n/).map((line) => line.split("\t")[0]).filter((id) => /^gemini-[\w.-]+-(?:high|medium|low)$/.test(id)).map((id) => id.replace(/-(?:high|medium|low)$/, ""));
8987
+ if (!models.length)
8988
+ throw new Error("Antigravity did not report any Gemini API models. Update agy and select Gemini API key mode.");
8989
+ return models.includes("gemini-3.8-flash") ? "gemini-3.8-flash" : models[0];
8990
+ }
8991
+ function assertAgyCustomModel(command, label, apiKey) {
8864
8992
  try {
8865
- const output = execFileSync(command, ["models"], { encoding: "utf8", timeout: 1e4, stdio: ["ignore", "pipe", "ignore"] });
8866
- return new Set(output.split(/\r?\n/).map((line) => line.split(/\s+/)[0]).filter((id) => /^gemini-[\w.-]+-(?:high|medium|low)$/.test(id)).map((id) => id.replace(/-(?:high|medium|low)$/, "")));
8993
+ const output = execFileSync(command, ["models"], { encoding: "utf8", timeout: 1e4, stdio: ["ignore", "pipe", "ignore"], env: { ...process.env, GEMINI_API_KEY: apiKey, GOOGLE_GEMINI_BASE_URL: "http://127.0.0.1:1" } });
8994
+ if (output.split(/\r?\n/).some((line) => line.split("\t")[0] === label))
8995
+ return;
8867
8996
  } catch {
8868
8997
  throw new Error("Could not list Antigravity CLI models. Install or update `agy`, then try `cr start agy` again.");
8869
8998
  }
8999
+ throw new Error(`This version of agy did not recognize '${label}'. Update agy to use ClosedRouter's accurate model display.`);
8870
9000
  }
8871
9001
  function assertAgyGeminiMode(homeDir) {
8872
- const settingsPath = join9(homeDir, ".gemini", "antigravity-cli", "settings.json");
9002
+ const settingsPath = join10(homeDir, ".gemini", "antigravity-cli", "settings.json");
8873
9003
  try {
8874
- const settings = JSON.parse(readFileSync8(settingsPath, "utf8"));
9004
+ const settings = JSON.parse(readFileSync9(settingsPath, "utf8"));
8875
9005
  if (settings?.modelProvider === "gemini")
8876
9006
  return;
8877
9007
  } catch {}
@@ -8944,14 +9074,14 @@ async function startCommand(options = {}) {
8944
9074
  }
8945
9075
  if (tool === "agy")
8946
9076
  assertAgyGeminiMode(homedir9());
8947
- const supportedAgyModels = tool === "agy" ? discoverAgyModels(options.executablePath || "agy") : undefined;
8948
- if (supportedAgyModels && supportedAgyModels.size === 0) {
8949
- throw new Error("Antigravity did not report any Gemini API models. Update `agy` and select Gemini API key mode in its settings.");
8950
- }
8951
- const recognizedModel = supportedAgyModels ? supportedAgyModels.has("gemini-3.8-flash") ? "gemini-3.8-flash" : [...supportedAgyModels][0] : undefined;
8952
- const agyProxy = tool === "agy" ? await startAgyModelProxy(serverUrl, apiKey, model) : undefined;
9077
+ const recognizedModel = tool === "agy" ? registerAgyLaunchModel(homedir9(), model, discoverAgyTransportModel(options.executablePath || "agy", apiKey)) : undefined;
9078
+ if (recognizedModel)
9079
+ assertAgyCustomModel(options.executablePath || "agy", recognizedModel, apiKey);
9080
+ const relay = await startLaunchProxy(serverUrl, apiKey, tool);
9081
+ let agyProxy;
8953
9082
  try {
8954
- const plan = buildLaunchPlan(tool, serverUrl, apiKey, model, options.toolArgs, homedir9(), options.executablePath, agyProxy && recognizedModel ? { baseUrl: agyProxy.baseUrl, recognizedModel } : undefined);
9083
+ agyProxy = tool === "agy" ? await startAgyModelProxy(relay.baseUrl, apiKey, model) : undefined;
9084
+ const plan = buildLaunchPlan(tool, relay.baseUrl, apiKey, model, options.toolArgs, homedir9(), options.executablePath, agyProxy && recognizedModel ? { baseUrl: agyProxy.baseUrl, recognizedModel } : undefined);
8955
9085
  if (plan.configFile)
8956
9086
  writeOwnedConfig(plan.configFile);
8957
9087
  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)}`);
@@ -8959,6 +9089,7 @@ async function startCommand(options = {}) {
8959
9089
  process.exitCode = exitCode;
8960
9090
  } finally {
8961
9091
  await agyProxy?.close();
9092
+ await relay.close();
8962
9093
  }
8963
9094
  }
8964
9095
 
@@ -9007,7 +9138,7 @@ process.on("warning", (warning) => {
9007
9138
  console.warn(warning.name, warning.message);
9008
9139
  });
9009
9140
  var program2 = new Command2;
9010
- var cliVersion = JSON.parse(readFileSync9(new URL("../package.json", import.meta.url), "utf8")).version;
9141
+ var cliVersion = JSON.parse(readFileSync10(new URL("../package.json", import.meta.url), "utf8")).version;
9011
9142
  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) => {
9012
9143
  await onboardCommand({ server: options.server });
9013
9144
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@closedrouter/cli",
3
- "version": "1.1.6",
3
+ "version": "1.1.8",
4
4
  "description": "ClosedRouter CLI: Onboard and import local AI subscriptions and API keys into your shared token pool",
5
5
  "author": "ClosedRouter Contributors",
6
6
  "repository": {