@closedrouter/cli 1.1.18 → 1.1.19

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
@@ -126,7 +126,7 @@ cr logout
126
126
 
127
127
  ### `cr start` / `cr launch`
128
128
 
129
- Run `cr start` to choose a CLI or desktop app from the tool selector. Desktop entries are labeled Codex Desktop, Antigravity Desktop, Cursor Desktop, and Claude Desktop. Use `cr start --desktop` to show only desktop apps. Explicit tool names such as `cr start codex` still default to the CLI; add `--desktop` to launch the app. The names `codex-desktop`, `claude-desktop`, `cursor-desktop`, and `antigravity-desktop` also work.
129
+ Run `cr start` to choose a CLI or desktop app from the tool selector. Grok Build is available as `cr start grok` or `cr start grok-build`. Desktop entries are labeled Codex Desktop, Antigravity Desktop, Cursor Desktop, and Claude Desktop. Use `cr start --desktop` to show only desktop apps. Explicit tool names such as `cr start codex` still default to the CLI; add `--desktop` to launch the app. The names `codex-desktop`, `claude-desktop`, `cursor-desktop`, and `antigravity-desktop` also work.
130
130
 
131
131
  ```bash
132
132
  cr start codex --desktop --model gpt-4.1
@@ -140,7 +140,7 @@ cr start claude --desktop --model claude-sonnet-4-6 --path /Applications/Claude.
140
140
 
141
141
  | Desktop app | Support |
142
142
  | --- | --- |
143
- | Codex | Configures a Responses provider and API-key authentication in an isolated `CODEX_HOME`. |
143
+ | Codex | Configures an authenticated Responses provider in an isolated `CODEX_HOME`, supplies the model catalog, disables unsupported WebSocket transport, and adds the colored provider logo. An OpenAI login is not required. |
144
144
  | Claude | Configures its native `3p` gateway profile and the supported model catalog. Local transport aliases resolve to the actual selected pool model. No Claude subscription or login is required for gateway sessions. |
145
145
  | Cursor | Enables the bundled local agent engine in the launched process and routes requests directly through the ClosedRouter relay. Existing logins are reused. Without a login, the UI accepts the configured provider key and skips the login screen. No Cursor subscription is used for inference. |
146
146
  | Antigravity | On macOS, installs a process-local request hook and translates Cloud Code generation envelopes into Gemini gateway requests for the selected pool model. Account and model-discovery traffic uses the existing `antigravity-mods` proxy. Requires its daemon running on ports 59997 and 59998. |
@@ -155,7 +155,11 @@ On macOS, Claude uses a compatible Electron runtime installed by Cockpit, or the
155
155
 
156
156
  Antigravity uses the CCPA envelope conversion documented in `~/Projects/antigravity-mods`. The launcher briefly opens a loopback Node inspector and installs its language-server hook. A browser debugger replaces the launched renderer's model catalog with native custom-model entries, then detaches. Each entry carries a distinct routing alias and a positive context and output budget. The picker lists the supported pool models, and selecting one changes inference routing. App bundles and global hook configuration are not modified. Unsupported launcher builds fail before opening; `--open-only` remains available.
157
157
 
158
- Cursor, Antigravity, and the hooked Claude view display the colored ClosedRouter logo beside model names. White-core neon-blue lightning bolts fly around the active chat model button along a 3D orbit for a brief pass every five seconds, and immediately on hover or keyboard focus. The bolts keep their orientation; perspective enlarges the front pass and the button occludes the rear pass. Reduced-motion preferences keep the mark static; hidden windows pause the effect. Codex uses its custom model catalog and provider labels. Launch requests also carry the configured model ID in system context so native client identity prompts do not misidentify the underlying model. The relay resolves the requested model on each turn and preserves tools and streamed responses. Unsupported model IDs are rejected before forwarding. Catalogs are snapshots of the models available to the platform key at launch; provider quotas can change during a session.
158
+ Supported desktop views display the colored ClosedRouter logo beside model names. White-core neon-blue lightning bolts fly around the active chat-box logo along a 3D orbit for a brief pass every five seconds, and immediately on hover or keyboard focus. The orbit measures the logo, so model-label width does not move or enlarge it. The bolts keep their orientation; perspective enlarges the front pass and the logo occludes the rear pass. Reduced-motion preferences keep the mark static; hidden windows pause the effect. Launch requests also carry the configured model ID in system context so native client identity prompts do not misidentify the underlying model. The relay resolves the requested model on each turn and preserves tools and streamed responses. Unsupported model IDs are rejected before forwarding. Catalogs are snapshots of the models available to the platform key at launch; provider quotas can change during a session.
159
+
160
+ `cr start` checks the selected model's actual tool call, tool-result continuation, and streamed completion before opening a client. Successful checks are cached for six hours by gateway and platform key. Confirmed protocol rejections are removed from that harness's selector; quota, authorization, and connection failures are reported as temporary availability failures. Use `cr verify --all-models --output compatibility.json` to audit every available tool-capable model across Responses, Chat Completions, Anthropic Messages, and Gemini. `cr verify codex --model <id>` checks one harness protocol. Reports distinguish gateway protocol checks from native client tests. `--no-verify-model` skips this check for a previously verified private gateway.
161
+
162
+ Grok Build uses its documented custom-model configuration in an isolated `GROK_HOME`, with the full pool catalog and credentials supplied through the child environment. Install Grok from its official installer, or pass `--path`. Grok Bot is a separate desktop product; its native bot/account services are not interchangeable with Grok Build's custom inference endpoints.
159
163
 
160
164
  macOS app bundles are discovered in `/Applications` and `~/Applications`. Common Windows and Linux installation paths are checked, with `--path` available for other installations. Live tests verified Cursor, Claude's Code and Cowork views, and Antigravity inference through ClosedRouter. Cursor and Antigravity switched between `gpt-5-nano` and `gpt-4.1-nano` in their native pickers. Cowork could answer through the gateway, but its workspace VM reported insufficient disk space on the test Mac; workspace actions were not verified. Gemma's full agent request previously received an upstream quota error. Desktop hooks fail closed when expected controllers change. See the [Codex provider configuration](https://developers.openai.com/codex/config-advanced) and [Electron fuse documentation](https://www.electronjs.org/docs/latest/tutorial/fuses) for configuration and inspector behavior.
161
165
 
package/dist/asar.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ /** Read only requested source members, not the app's entire asset archive. */
2
+ export declare function readAsarSources(archive: string, prefix: string): string[];
3
+ //# sourceMappingURL=asar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"asar.d.ts","sourceRoot":"","sources":["../src/asar.ts"],"names":[],"mappings":"AAIA,8EAA8E;AAC9E,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAkBzE"}
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { DesktopLaunchPlan } from "./desktop-launch";
2
+ import type { DesktopAccountIdentity } from "./desktop-account";
3
+ /** Configure the supported app-server provider first; the startup hook only
4
+ * adds the provider mark to its native model controls. */
5
+ export declare function addCodexDesktopHook(plan: DesktopLaunchPlan, app: string, models: string[], account?: DesktopAccountIdentity): void;
6
+ //# sourceMappingURL=codex-desktop.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codex-desktop.d.ts","sourceRoot":"","sources":["../../src/commands/codex-desktop.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAIhE;0DAC0D;AAC1D,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,iBAAiB,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,sBAAsB,GAAG,IAAI,CAUlI"}
@@ -1 +1 @@
1
- {"version":3,"file":"desktop-launch.d.ts","sourceRoot":"","sources":["../../src/commands/desktop-launch.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,KAAK,CAAC;AAMhE,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,KAAK,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjD,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE;QACd,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAClD,iBAAiB,CAAC,EAAE;YAAE,QAAQ,EAAE,MAAM,CAAC;YAAC,UAAU,EAAE,MAAM,CAAC;YAAC,YAAY,EAAE,MAAM,CAAC;YAAC,SAAS,EAAE,MAAM,CAAA;SAAE,CAAC;KACvG,CAAC;CACH;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,IAAI,WAAW,CAE/D;AAED,iEAAiE;AACjE,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,WAAW,EAAE,YAAY,CAAC,EAAE,MAAM,EAAE,QAAQ,kBAAmB,EAAE,IAAI,SAAY,GACtF,MAAM,CAoBR;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAIxD;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,CAI9D;AAED,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EACzD,OAAO,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,EACnD,QAAQ,GAAE,MAAM,EAAO,EAAE,QAAQ,kBAAmB,EACpD,MAAM,GAAE,MAAM,EAAyB,GACtC,iBAAiB,CAkDnB;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,iBAAiB,GAAG,IAAI,CAQpE;AAED,2EAA2E;AAC3E,wBAAgB,iCAAiC,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAO1E;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CAiB/E"}
1
+ {"version":3,"file":"desktop-launch.d.ts","sourceRoot":"","sources":["../../src/commands/desktop-launch.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,KAAK,CAAC;AAMhE,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,KAAK,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjD,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE;QACd,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAClD,iBAAiB,CAAC,EAAE;YAAE,QAAQ,EAAE,MAAM,CAAC;YAAC,UAAU,EAAE,MAAM,CAAC;YAAC,YAAY,EAAE,MAAM,CAAC;YAAC,SAAS,EAAE,MAAM,CAAA;SAAE,CAAC;KACvG,CAAC;CACH;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,IAAI,WAAW,CAE/D;AAED,iEAAiE;AACjE,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,WAAW,EAAE,YAAY,CAAC,EAAE,MAAM,EAAE,QAAQ,kBAAmB,EAAE,IAAI,SAAY,GACtF,MAAM,CAoBR;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAIxD;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,CAI9D;AAED,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EACzD,OAAO,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,EACnD,QAAQ,GAAE,MAAM,EAAO,EAAE,QAAQ,kBAAmB,EACpD,MAAM,GAAE,MAAM,EAAyB,GACtC,iBAAiB,CAmDnB;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,iBAAiB,GAAG,IAAI,CAQpE;AAED,2EAA2E;AAC3E,wBAAgB,iCAAiC,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAO1E;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CAiB/E"}
@@ -1,8 +1,8 @@
1
1
  export declare const 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>";
2
2
  import type { DesktopAccountIdentity } from "./desktop-account";
3
- export declare const CLOSEDROUTER_ELECTRIC_CSS = "\n[data-closedrouter-logo]{position:relative;isolation:isolate}\n[data-closedrouter-charged-control]{position:relative;perspective:400px;transform-style:preserve-3d;overflow:visible!important}\n[data-closedrouter-electric]{position:absolute;inset:0;pointer-events:none;transform-style:preserve-3d;opacity:0}\n[data-cr-lightning-orbit]{position:absolute;left:50%;top:50%;width:0;height:0;transform-style:preserve-3d}\n[data-cr-lightning-bolt]{position:absolute;width:20px;height:30px;left:-10px;top:-15px;overflow:visible;filter:drop-shadow(0 0 3px #58caff) drop-shadow(0 0 7px #159aff)}\n@media(prefers-reduced-motion:no-preference){[data-closedrouter-electric]{opacity:1}}\n";
4
- /** Position a bolt along a tilted orbit in world space. Perspective belongs to
5
- * the button; the bolt keeps its orientation as its x, y and depth change. */
3
+ export declare const CLOSEDROUTER_ELECTRIC_CSS = "\n[data-closedrouter-logo]{position:relative;isolation:isolate}\n[data-closedrouter-charged-control]{overflow:visible!important}\n[data-closedrouter-supercharged]{perspective:80px;transform-style:preserve-3d;isolation:auto;overflow:visible}\n[data-closedrouter-electric]{position:absolute;inset:0;pointer-events:none;transform-style:preserve-3d;opacity:0}\n[data-cr-lightning-orbit]{position:absolute;left:50%;top:50%;width:0;height:0;transform-style:preserve-3d}\n[data-cr-lightning-bolt]{position:absolute;width:14px;height:22px;left:-7px;top:-11px;overflow:visible;filter:drop-shadow(0 0 3px #58caff) drop-shadow(0 0 7px #159aff)}\n@media(prefers-reduced-motion:no-preference){[data-closedrouter-electric]{opacity:1}}\n";
4
+ /** Position a bolt along a tilted orbit around the provider logo in world
5
+ * space. The bolt keeps its orientation as its x, y and depth change. */
6
6
  export declare function lightningOrbitKeyframes(width: number, height: number, phase: number): Keyframe[];
7
7
  /** Add the provider mark to native controls without changing click handlers,
8
8
  * model IDs, keyboard behavior, or the application's typography. */
@@ -1 +1 @@
1
- {"version":3,"file":"model-branding.d.ts","sourceRoot":"","sources":["../../src/commands/model-branding.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,iBAAiB,mUAAqS,CAAC;AACpU,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAEhE,eAAO,MAAM,yBAAyB,wqBAOrC,CAAC;AAEF;8EAC8E;AAC9E,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,QAAQ,EAAE,CAiBhG;AAED;oEACoE;AACpE,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,cAAc,EAAE,OAAO,uBAAuB,GAAG,IAAI,CAsFjJ;AAED,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,sBAAsB,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAoC/F;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,sBAAsB,GAAG,MAAM,CAS5F"}
1
+ {"version":3,"file":"model-branding.d.ts","sourceRoot":"","sources":["../../src/commands/model-branding.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,iBAAiB,mUAAqS,CAAC;AACpU,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAEhE,eAAO,MAAM,yBAAyB,utBAQrC,CAAC;AAEF;yEACyE;AACzE,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,QAAQ,EAAE,CAiBhG;AAED;oEACoE;AACpE,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,cAAc,EAAE,OAAO,uBAAuB,GAAG,IAAI,CAuFjJ;AAED,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,sBAAsB,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAoC/F;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,sBAAsB,GAAG,MAAM,CAS5F"}
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import { type GatewayModel } from "../client";
7
7
  import { type LaunchCatalog } from "./launch-catalog";
8
- export type StartTool = "claude" | "codex" | "gemini" | "agy" | "aider" | "opencode" | "cursor";
8
+ export type StartTool = "claude" | "codex" | "gemini" | "agy" | "aider" | "opencode" | "cursor" | "grok";
9
9
  type CliTool = StartTool;
10
10
  export interface StartCommandOptions {
11
11
  tool?: string;
@@ -18,6 +18,7 @@ export interface StartCommandOptions {
18
18
  desktop?: boolean;
19
19
  openOnly?: boolean;
20
20
  desktopInspector?: boolean;
21
+ verifyModel?: boolean;
21
22
  }
22
23
  interface ConfigFile {
23
24
  path: string;
@@ -1 +1 @@
1
- {"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../../src/commands/start.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAUH,OAAO,EAAsB,KAAK,YAAY,EAAE,MAAM,WAAW,CAAC;AAclE,OAAO,EAA6F,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEjJ,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,KAAK,GAAG,OAAO,GAAG,UAAU,GAAG,QAAQ,CAAC;AAChG,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,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;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;IACxB,UAAU,CAAC,EAAE,UAAU,EAAE,CAAC;CAC3B;AAED,wBAAgB,uBAAuB,CAAC,eAAe,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAEzF;AAmBD,wBAAgB,mBAAmB,CAAC,WAAW,UAAQ;;;;IAEtD;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,UAAQ,GAAG;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAUxG;AAqBD,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,EACzD,OAAO,CAAC,EAAE,aAAa,GACtB,UAAU,CAyHZ;AAeD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAKlF;AAuED,wBAAsB,YAAY,CAAC,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CA6HnF"}
1
+ {"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../../src/commands/start.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAUH,OAAO,EAAsB,KAAK,YAAY,EAAE,MAAM,WAAW,CAAC;AAelE,OAAO,EAA6F,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAGjJ,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,KAAK,GAAG,OAAO,GAAG,UAAU,GAAG,QAAQ,GAAG,MAAM,CAAC;AACzG,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,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;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;IACxB,UAAU,CAAC,EAAE,UAAU,EAAE,CAAC;CAC3B;AAED,wBAAgB,uBAAuB,CAAC,eAAe,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAEzF;AAoBD,wBAAgB,mBAAmB,CAAC,WAAW,UAAQ;;;;IAEtD;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,UAAQ,GAAG;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAUxG;AAqBD,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,EACzD,OAAO,CAAC,EAAE,aAAa,GACtB,UAAU,CAiJZ;AAeD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAKlF;AAuED,wBAAsB,YAAY,CAAC,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAiJnF"}
@@ -0,0 +1,9 @@
1
+ export declare function verifyCommand(tool: string | undefined, options: {
2
+ server?: string;
3
+ key?: string;
4
+ model?: string;
5
+ allModels?: boolean;
6
+ output?: string;
7
+ concurrency?: string;
8
+ }): Promise<void>;
9
+ //# sourceMappingURL=verify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../../src/commands/verify.ts"],"names":[],"mappings":"AAMA,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE;IAAE,MAAM,CAAC,EAAC,MAAM,CAAC;IAAA,GAAG,CAAC,EAAC,MAAM,CAAC;IAAA,KAAK,CAAC,EAAC,MAAM,CAAC;IAAA,SAAS,CAAC,EAAC,OAAO,CAAC;IAAA,MAAM,CAAC,EAAC,MAAM,CAAC;IAAA,WAAW,CAAC,EAAC,MAAM,CAAA;CAAE,GAAE,OAAO,CAAC,IAAI,CAAC,CAqBvL"}
@@ -0,0 +1,32 @@
1
+ export declare const PROTOCOLS: readonly ["responses", "chat", "messages", "gemini"];
2
+ export type HarnessProtocol = typeof PROTOCOLS[number];
3
+ export type CompatibilityStatus = "passed" | "incompatible" | "unavailable" | "unverified";
4
+ export interface CompatibilityResult {
5
+ model: string;
6
+ protocol: HarnessProtocol;
7
+ status: CompatibilityStatus;
8
+ checkedAt: string;
9
+ latencyMs: number;
10
+ stage: "tool-call" | "tool-result";
11
+ httpStatus?: number;
12
+ code?: string;
13
+ reason?: string;
14
+ }
15
+ export interface CompatibilityReport {
16
+ version: 2;
17
+ results: CompatibilityResult[];
18
+ }
19
+ export declare function harnessProtocol(tool: string): HarnessProtocol;
20
+ export declare function loadCompatibility(server: string, key: string): CompatibilityReport;
21
+ export declare function saveCompatibility(server: string, key: string, results: CompatibilityResult[]): CompatibilityReport;
22
+ export declare function recentCompatibility(report: CompatibilityReport, model: string, protocol: HarnessProtocol): CompatibilityResult | undefined;
23
+ /** A quota failure is not evidence that a harness cannot use the model. */
24
+ export declare function classifyProbeHttp(status: number): CompatibilityStatus;
25
+ export declare function streamedText(raw: string, protocol: HarnessProtocol): {
26
+ text: string;
27
+ complete: boolean;
28
+ };
29
+ /** Check a real tool call, then return a new nonce only in the tool result.
30
+ * Successful HTTP status or a plain greeting alone does not pass this check. */
31
+ export declare function probeCompatibility(server: string, key: string, model: string, protocol: HarnessProtocol): Promise<CompatibilityResult>;
32
+ //# sourceMappingURL=compatibility.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compatibility.d.ts","sourceRoot":"","sources":["../src/compatibility.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,SAAS,YAAI,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,CAAU,CAAC;AAC9E,MAAM,MAAM,eAAe,GAAG,OAAO,SAAS,CAAC,MAAM,CAAC,CAAC;AACvD,MAAM,MAAM,mBAAmB,GAAG,QAAQ,GAAG,cAAc,GAAG,aAAa,GAAG,YAAY,CAAC;AAC3F,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,eAAe,CAAC;IAAC,MAAM,EAAE,mBAAmB,CAAC;IACtE,SAAS,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,WAAW,GAAG,aAAa,CAAC;IACzE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CACrD;AACD,MAAM,WAAW,mBAAmB;IAAG,OAAO,EAAE,CAAC,CAAC;IAAC,OAAO,EAAE,mBAAmB,EAAE,CAAA;CAAE;AAEnF,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,CAI7D;AAOD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,mBAAmB,CASlF;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,mBAAmB,EAAE,GAAG,mBAAmB,CAQlH;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,mBAAmB,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,GAAG,mBAAmB,GAAG,SAAS,CAG1I;AAMD,2EAA2E;AAC3E,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,mBAAmB,CAErE;AAyBD,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,CA6BxG;AAED;gFACgF;AAChF,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAqC5I"}
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 readFileSync16 } from "node:fs";
5207
+ import { readFileSync as readFileSync17 } 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";
@@ -6182,10 +6182,24 @@ class ClosedRouterClient {
6182
6182
  return data.data || data.api_keys || data.keys || [];
6183
6183
  }
6184
6184
  async listGatewayModels(options = {}) {
6185
- const res = await fetch(`${this.baseUrl}/v1/models`, {
6186
- headers: this.getHeaders({}, options.preferApiKey === true),
6187
- signal: AbortSignal.timeout(1e4)
6188
- });
6185
+ let res;
6186
+ for (let attempt = 0;attempt < 3; attempt++) {
6187
+ try {
6188
+ res = await fetch(`${this.baseUrl}/v1/models`, {
6189
+ headers: this.getHeaders({}, options.preferApiKey === true),
6190
+ signal: AbortSignal.timeout(1e4)
6191
+ });
6192
+ if (![502, 503, 504].includes(res.status) || attempt === 2)
6193
+ break;
6194
+ await res.body?.cancel();
6195
+ } catch (error) {
6196
+ if (attempt === 2)
6197
+ throw error;
6198
+ }
6199
+ await new Promise((resolve) => setTimeout(resolve, 250 * (attempt + 1)));
6200
+ }
6201
+ if (!res)
6202
+ throw new Error("Could not connect to the gateway model catalog.");
6189
6203
  if (!res.ok) {
6190
6204
  throw new Error(`Failed to list gateway models (HTTP ${res.status})`);
6191
6205
  }
@@ -8656,12 +8670,12 @@ to scan and import local subscriptions!`, "Pool Empty");
8656
8670
 
8657
8671
  // src/commands/start.ts
8658
8672
  var import_picocolors12 = __toESM(require_picocolors(), 1);
8659
- import { randomUUID as randomUUID5 } from "node:crypto";
8673
+ import { randomUUID as randomUUID6 } from "node:crypto";
8660
8674
  import { spawn as spawn5 } from "node:child_process";
8661
8675
  import { execFileSync as execFileSync3 } from "node:child_process";
8662
- import { mkdirSync as mkdirSync5, readFileSync as readFileSync15, writeFileSync as writeFileSync5 } from "node:fs";
8676
+ import { existsSync as existsSync17, mkdirSync as mkdirSync6, readFileSync as readFileSync16, writeFileSync as writeFileSync6 } from "node:fs";
8663
8677
  import { homedir as homedir15 } from "node:os";
8664
- import { dirname as dirname5, join as join17 } from "node:path";
8678
+ import { dirname as dirname5, join as join19 } from "node:path";
8665
8679
 
8666
8680
  // src/model-picker.ts
8667
8681
  import { emitKeypressEvents as emitKeypressEvents2 } from "node:readline";
@@ -9435,7 +9449,7 @@ function createLaunchCatalog(models, selected) {
9435
9449
  [transportModelId(id, "claude"), id],
9436
9450
  [transportModelId(id, "gemini"), id]
9437
9451
  ])),
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"]
9452
+ 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", "grok-4.6", "grok-4.7", "grok-build"]
9439
9453
  };
9440
9454
  }
9441
9455
  function resolveLaunchModel(catalog, requested) {
@@ -9480,8 +9494,8 @@ function codexModelCatalog(models) {
9480
9494
  support_verbosity: false,
9481
9495
  prefer_websockets: false,
9482
9496
  supports_image_detail_original: false,
9483
- context_window: 8192,
9484
- auto_compact_token_limit: 7000,
9497
+ context_window: 32768,
9498
+ auto_compact_token_limit: 28000,
9485
9499
  truncation_policy: { mode: "tokens", limit: 6000 }
9486
9500
  })) };
9487
9501
  }
@@ -9990,7 +10004,9 @@ function buildDesktopLaunchPlan(tool, executable, profileDir, baseUrl, localKey,
9990
10004
  'name = "ClosedRouter"',
9991
10005
  `base_url = ${JSON.stringify(`${baseUrl.replace(/\/+$/, "")}/v1`)}`,
9992
10006
  'wire_api = "responses"',
9993
- "requires_openai_auth = true",
10007
+ "requires_openai_auth = false",
10008
+ "supports_websockets = false",
10009
+ `experimental_bearer_token = ${JSON.stringify(localKey)}`,
9994
10010
  ""
9995
10011
  ].join(`
9996
10012
  `) });
@@ -10270,10 +10286,11 @@ app.on('web-contents-created', (_event, contents) => {
10270
10286
  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>';
10271
10287
  var CLOSEDROUTER_ELECTRIC_CSS = `
10272
10288
  [data-closedrouter-logo]{position:relative;isolation:isolate}
10273
- [data-closedrouter-charged-control]{position:relative;perspective:400px;transform-style:preserve-3d;overflow:visible!important}
10289
+ [data-closedrouter-charged-control]{overflow:visible!important}
10290
+ [data-closedrouter-supercharged]{perspective:80px;transform-style:preserve-3d;isolation:auto;overflow:visible}
10274
10291
  [data-closedrouter-electric]{position:absolute;inset:0;pointer-events:none;transform-style:preserve-3d;opacity:0}
10275
10292
  [data-cr-lightning-orbit]{position:absolute;left:50%;top:50%;width:0;height:0;transform-style:preserve-3d}
10276
- [data-cr-lightning-bolt]{position:absolute;width:20px;height:30px;left:-10px;top:-15px;overflow:visible;filter:drop-shadow(0 0 3px #58caff) drop-shadow(0 0 7px #159aff)}
10293
+ [data-cr-lightning-bolt]{position:absolute;width:14px;height:22px;left:-7px;top:-11px;overflow:visible;filter:drop-shadow(0 0 3px #58caff) drop-shadow(0 0 7px #159aff)}
10277
10294
  @media(prefers-reduced-motion:no-preference){[data-closedrouter-electric]{opacity:1}}
10278
10295
  `;
10279
10296
  function lightningOrbitKeyframes(width, height, phase) {
@@ -10281,10 +10298,11 @@ function lightningOrbitKeyframes(width, height, phase) {
10281
10298
  for (let step = 0;step <= 40; step++) {
10282
10299
  const progress = step / 40;
10283
10300
  const angle = (progress + phase) * Math.PI * 2;
10284
- const x = (width / 2 + 12) * Math.cos(angle);
10285
- const y = (height / 2 + 6) * Math.sin(angle) * Math.sin(Math.PI / 15);
10286
- const z = 46 * Math.sin(angle) * Math.cos(Math.PI / 15);
10287
- const scale = 400 / (400 - z);
10301
+ const radius = Math.max(width, height) / 2 + 8;
10302
+ const x = (width / 2 + 8) * Math.cos(angle);
10303
+ const y = (height / 2 + 4) * Math.sin(angle) * Math.sin(Math.PI / 15);
10304
+ const z = Math.min(radius, 32) * Math.sin(angle) * Math.cos(Math.PI / 15);
10305
+ const scale = 80 / (80 - z);
10288
10306
  const behindButton = z < -1 && Math.abs(x * scale) < width / 2 && Math.abs(y * scale) < height / 2;
10289
10307
  const fade = Math.min(1, progress * 20, (1 - progress) * 20);
10290
10308
  frames.push({ offset: progress / 5, transform: `translate3d(${x}px,${y}px,${z}px)`, opacity: behindButton ? 0 : fade * (z < 0 ? 0.55 : 1) });
@@ -10307,7 +10325,7 @@ function installModelBranding(models, artwork, electricCss, orbitKeyframes) {
10307
10325
  const previous = charged.get(button) || [];
10308
10326
  const time = previous[0]?.currentTime;
10309
10327
  previous.forEach((animation) => animation.cancel());
10310
- const { width, height } = button.getBoundingClientRect();
10328
+ const { width = 16, height = 16 } = button.querySelector("[data-closedrouter-supercharged]")?.getBoundingClientRect() || {};
10311
10329
  const animations = motion.matches ? [] : [...button.querySelectorAll("[data-cr-lightning-bolt]")].map((bolt, index) => {
10312
10330
  const animation = bolt.animate(orbitKeyframes(width, height, index / 2), { duration: 5000, iterations: Infinity, easing: "linear" });
10313
10331
  if (typeof time === "number")
@@ -10380,7 +10398,7 @@ function installModelBranding(models, artwork, electricCss, orbitKeyframes) {
10380
10398
  orbit.append(bolt);
10381
10399
  }
10382
10400
  layer.append(orbit);
10383
- element.append(layer);
10401
+ logo.append(layer);
10384
10402
  const restart = () => charged.get(element)?.forEach((animation) => {
10385
10403
  animation.currentTime = 0;
10386
10404
  });
@@ -10389,6 +10407,8 @@ function installModelBranding(models, artwork, electricCss, orbitKeyframes) {
10389
10407
  resize.observe(element);
10390
10408
  }
10391
10409
  element.prepend(logo);
10410
+ if (logo.dataset.closedrouterSupercharged)
10411
+ animate(element);
10392
10412
  }
10393
10413
  };
10394
10414
  let scheduled = false;
@@ -11103,10 +11123,65 @@ require('node:fs').writeFileSync(${JSON.stringify(join15(plan.profileDir, "hook-
11103
11123
  plan.args.splice(plan.args.indexOf("--args"), 0, "--stderr", logPath);
11104
11124
  }
11105
11125
 
11126
+ // src/commands/codex-desktop.ts
11127
+ import { join as join16 } from "node:path";
11128
+
11129
+ // src/asar.ts
11130
+ import { closeSync as closeSync2, openSync as openSync2, readSync, statSync as statSync3 } from "node:fs";
11131
+ function readAsarSources(archive, prefix) {
11132
+ const fd = openSync2(archive, "r");
11133
+ try {
11134
+ const size = Buffer.alloc(8);
11135
+ readSync(fd, size, 0, 8, 0);
11136
+ const length = size.readUInt32LE(4);
11137
+ if (length < 8 || length > 32 * 1024 * 1024)
11138
+ throw new Error("Unsupported Electron archive header.");
11139
+ const header = Buffer.alloc(length);
11140
+ readSync(fd, header, 0, length, 8);
11141
+ const tree = JSON.parse(header.subarray(8, 8 + header.readUInt32LE(4)).toString("utf8"));
11142
+ const sources = [];
11143
+ const archiveSize = statSync3(archive).size;
11144
+ const visit = (entry, path) => {
11145
+ if (entry.files) {
11146
+ for (const [name, child] of Object.entries(entry.files))
11147
+ visit(child, path ? `${path}/${name}` : name);
11148
+ return;
11149
+ }
11150
+ if (!path.startsWith(prefix) || !path.endsWith(".js") || entry.offset === undefined || entry.size === undefined)
11151
+ return;
11152
+ const offset = 8 + length + Number(entry.offset);
11153
+ if (!Number.isSafeInteger(offset) || offset < 8 + length || entry.size > 32 * 1024 * 1024 || offset + entry.size > archiveSize)
11154
+ throw new Error("Invalid Electron source member.");
11155
+ const bytes = Buffer.alloc(entry.size);
11156
+ readSync(fd, bytes, 0, entry.size, offset);
11157
+ sources.push(bytes.toString("utf8"));
11158
+ };
11159
+ visit(tree, "");
11160
+ return sources;
11161
+ } finally {
11162
+ closeSync2(fd);
11163
+ }
11164
+ }
11165
+
11166
+ // src/commands/codex-desktop.ts
11167
+ function addCodexDesktopHook(plan, app, models, account) {
11168
+ const archive = app.endsWith(".app") ? join16(app, "Contents/Resources/app.asar") : join16(app, "../resources/app.asar");
11169
+ if (!readAsarSources(archive, ".vite/build/bootstrap-").some((source) => source.includes("CODEX_ELECTRON_USER_DATA_PATH")))
11170
+ throw new Error("This Codex app build does not support isolated provider profiles. Update the app before launching through ClosedRouter.");
11171
+ const hook = join16(plan.profileDir, "codex-hook.cjs");
11172
+ const marker = join16(plan.profileDir, "codex-hook-loaded");
11173
+ const log = join16(plan.profileDir, "inspector.log");
11174
+ plan.files.push({ path: hook, contents: modelBrandingHook(models, account) + `require('node:fs').writeFileSync(${JSON.stringify(marker)},'ready',{mode:0o600});` });
11175
+ plan.inspectorHook = { path: hook, logPath: log, readyPath: marker };
11176
+ plan.args.push("--inspect-brk=127.0.0.1:0");
11177
+ if (plan.command === "/usr/bin/open")
11178
+ plan.args.splice(plan.args.indexOf("--args"), 0, "--stderr", log);
11179
+ }
11180
+
11106
11181
  // src/commands/desktop-account.ts
11107
11182
  import { existsSync as existsSync16, readFileSync as readFileSync14 } from "node:fs";
11108
11183
  import { homedir as homedir14 } from "node:os";
11109
- import { join as join16 } from "node:path";
11184
+ import { join as join17 } from "node:path";
11110
11185
  function desktopAccountIdentity(config, server, key) {
11111
11186
  const sameAccount = config.apiKey === key && config.serverUrl.replace(/\/+$/, "") === server.replace(/\/+$/, "");
11112
11187
  const user = sameAccount ? config.user : undefined;
@@ -11118,8 +11193,8 @@ function desktopAccountIdentity(config, server, key) {
11118
11193
  serverOrigin: new URL(server).origin
11119
11194
  };
11120
11195
  }
11121
- function antigravityNativeAccountExists(geminiDir = process.env.CLOSEDROUTER_ANTIGRAVITY_NATIVE_DIR || join16(homedir14(), ".gemini")) {
11122
- const path = join16(geminiDir, "jetski-standalone-oauth-token");
11196
+ function antigravityNativeAccountExists(geminiDir = process.env.CLOSEDROUTER_ANTIGRAVITY_NATIVE_DIR || join17(homedir14(), ".gemini")) {
11197
+ const path = join17(geminiDir, "jetski-standalone-oauth-token");
11123
11198
  if (!existsSync16(path))
11124
11199
  return false;
11125
11200
  try {
@@ -11130,6 +11205,194 @@ function antigravityNativeAccountExists(geminiDir = process.env.CLOSEDROUTER_ANT
11130
11205
  }
11131
11206
  }
11132
11207
 
11208
+ // src/compatibility.ts
11209
+ import { createHash as createHash4, randomUUID as randomUUID5 } from "node:crypto";
11210
+ import { mkdirSync as mkdirSync5, readFileSync as readFileSync15, writeFileSync as writeFileSync5 } from "node:fs";
11211
+ import { join as join18 } from "node:path";
11212
+ var PROTOCOLS = ["responses", "chat", "messages", "gemini"];
11213
+ function harnessProtocol(tool) {
11214
+ const mapping = { codex: "responses", grok: "responses", "grok-build": "responses", claude: "messages", agy: "gemini", antigravity: "gemini", gemini: "gemini", cursor: "chat", aider: "chat", opencode: "chat" };
11215
+ if (!Object.hasOwn(mapping, tool))
11216
+ throw new Error(`Unsupported harness ${JSON.stringify(tool)}.`);
11217
+ return mapping[tool];
11218
+ }
11219
+ function reportPath(server, key) {
11220
+ const scope = createHash4("sha256").update(`${server.replace(/\/+$/, "")}\x00${key}`).digest("hex");
11221
+ return join18(getConfigDir(), "compatibility", `${scope}.json`);
11222
+ }
11223
+ function loadCompatibility(server, key) {
11224
+ try {
11225
+ const report = JSON.parse(readFileSync15(reportPath(server, key), "utf8"));
11226
+ if (report.version === 2 && Array.isArray(report.results) && report.results.every((item) => item && typeof item.model === "string" && PROTOCOLS.includes(item.protocol) && ["passed", "incompatible", "unavailable", "unverified"].includes(item.status) && Number.isFinite(Date.parse(item.checkedAt))))
11227
+ return report;
11228
+ } catch {}
11229
+ return { version: 2, results: [] };
11230
+ }
11231
+ function saveCompatibility(server, key, results) {
11232
+ const report = loadCompatibility(server, key);
11233
+ for (const result of results) {
11234
+ report.results = report.results.filter((item) => item.model !== result.model || item.protocol !== result.protocol);
11235
+ report.results.push(result);
11236
+ }
11237
+ const path = reportPath(server, key);
11238
+ mkdirSync5(join18(getConfigDir(), "compatibility"), { recursive: true, mode: 448 });
11239
+ writeFileSync5(path, JSON.stringify(report, null, 2) + `
11240
+ `, { mode: 384 });
11241
+ return report;
11242
+ }
11243
+ function recentCompatibility(report, model, protocol) {
11244
+ return report.results.find((result) => result.model === model && result.protocol === protocol && Date.now() - Date.parse(result.checkedAt) < (result.status === "unavailable" ? 60000 : 6 * 60 * 60 * 1000));
11245
+ }
11246
+
11247
+ class ProbeFailure extends Error {
11248
+ status;
11249
+ code;
11250
+ httpStatus;
11251
+ constructor(status, code, message, httpStatus) {
11252
+ super(message);
11253
+ this.status = status;
11254
+ this.code = code;
11255
+ this.httpStatus = httpStatus;
11256
+ }
11257
+ }
11258
+ function classifyProbeHttp(status) {
11259
+ return status === 400 || status === 422 ? "incompatible" : "unavailable";
11260
+ }
11261
+ var TOOL = "closedrouter_probe";
11262
+ var PARAMETERS = { type: "object", properties: { ticket: { type: "string" } }, required: ["ticket"], additionalProperties: false };
11263
+ function toolCall(data, protocol) {
11264
+ const call = protocol === "responses" ? data.output?.find((item) => item.type === "function_call" && item.name === TOOL) : protocol === "messages" ? data.content?.find((item) => item.type === "tool_use" && item.name === TOOL) : protocol === "gemini" ? data.candidates?.[0]?.content?.parts?.map((part) => part.functionCall).find((call) => call?.name === TOOL) : data.choices?.[0]?.message?.tool_calls?.find((item) => item.function?.name === TOOL);
11265
+ if (!call)
11266
+ throw new ProbeFailure("unverified", "missing_tool_call", "The model did not produce the required tool call.");
11267
+ let args;
11268
+ try {
11269
+ args = protocol === "chat" ? JSON.parse(call.function.arguments) : protocol === "responses" ? JSON.parse(call.arguments) : protocol === "messages" ? call.input : call.args;
11270
+ } catch {
11271
+ throw new ProbeFailure("incompatible", "invalid_tool_arguments", "The tool call arguments were not valid JSON.");
11272
+ }
11273
+ return { id: call.call_id || call.id || TOOL, ticket: args?.ticket };
11274
+ }
11275
+ function textFrom(data, protocol) {
11276
+ if (protocol === "responses")
11277
+ return data.output_text || (data.output || []).flatMap((item) => item.content || []).map((part) => part.text || "").join("");
11278
+ if (protocol === "messages")
11279
+ return (data.content || []).map((part) => part.text || "").join("");
11280
+ if (protocol === "gemini")
11281
+ return (data.candidates?.[0]?.content?.parts || []).map((part) => part.text || "").join("");
11282
+ return data.choices?.[0]?.message?.content || "";
11283
+ }
11284
+ function streamedText(raw, protocol) {
11285
+ if (!raw.includes("data:")) {
11286
+ try {
11287
+ return { text: textFrom(JSON.parse(raw), protocol), complete: true };
11288
+ } catch {
11289
+ return { text: "", complete: false };
11290
+ }
11291
+ }
11292
+ let text = "", complete = false, lastSequence = -1;
11293
+ for (const record of raw.replace(/\r\n/g, `
11294
+ `).split(`
11295
+
11296
+ `)) {
11297
+ const payload = record.split(`
11298
+ `).filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trim()).join(`
11299
+ `);
11300
+ if (!payload)
11301
+ continue;
11302
+ if (payload === "[DONE]") {
11303
+ complete = true;
11304
+ continue;
11305
+ }
11306
+ let event;
11307
+ try {
11308
+ event = JSON.parse(payload);
11309
+ } catch {
11310
+ continue;
11311
+ }
11312
+ if (event.error || event.type === "error" || event.type === "response.failed")
11313
+ throw new ProbeFailure("unavailable", "stream_error", "The gateway returned an error during streaming.");
11314
+ if (protocol === "responses") {
11315
+ if (typeof event.type === "string" && event.type.startsWith("response.")) {
11316
+ if (!Number.isInteger(event.sequence_number) || event.sequence_number <= lastSequence)
11317
+ throw new ProbeFailure("incompatible", "invalid_stream_sequence", "Responses events lack an increasing sequence number required by strict clients.");
11318
+ lastSequence = event.sequence_number;
11319
+ }
11320
+ if (event.type === "response.output_text.delta")
11321
+ text += event.delta || "";
11322
+ if (event.type === "response.completed") {
11323
+ complete = true;
11324
+ if (!text)
11325
+ text = textFrom(event.response || {}, protocol);
11326
+ }
11327
+ } else if (protocol === "messages") {
11328
+ if (event.type === "content_block_delta" && event.delta?.type === "text_delta")
11329
+ text += event.delta.text || "";
11330
+ if (event.type === "message_stop")
11331
+ complete = true;
11332
+ } else if (protocol === "gemini") {
11333
+ text += textFrom(event, protocol);
11334
+ if (event.candidates?.[0]?.finishReason)
11335
+ complete = true;
11336
+ } else {
11337
+ text += event.choices?.[0]?.delta?.content || "";
11338
+ if (event.choices?.[0]?.finish_reason)
11339
+ complete = true;
11340
+ }
11341
+ }
11342
+ return { text, complete };
11343
+ }
11344
+ async function probeCompatibility(server, key, model, protocol) {
11345
+ const started = Date.now(), ticket = randomUUID5(), proof = `CR_TOOL_OK_${randomUUID5().replace(/-/g, "")}`;
11346
+ let stage = "tool-call";
11347
+ const result = (status, extra = {}) => ({ model, protocol, status, stage, checkedAt: new Date().toISOString(), latencyMs: Date.now() - started, ...extra });
11348
+ const prompt = `Call the ${TOOL} tool exactly once with ticket ${ticket}. After its result, reply with only the exact text returned by the tool. Do not invent a tool result.`;
11349
+ const base = server.replace(/\/+$/, "");
11350
+ const url = protocol === "gemini" ? `${base}/v1beta/models/${encodeURIComponent(model)}:generateContent` : `${base}/v1/${protocol === "chat" ? "chat/completions" : protocol}`;
11351
+ const send = async (payload, stream = false) => {
11352
+ const endpoint = stream && protocol === "gemini" ? url.replace(":generateContent", ":streamGenerateContent") + "?alt=sse" : url;
11353
+ const response = await fetch(endpoint, { method: "POST", headers: { authorization: `Bearer ${key}`, "content-type": "application/json", "x-closedrouter-harness": `compatibility-${protocol}` }, body: JSON.stringify(payload), signal: AbortSignal.timeout(90000) });
11354
+ const body = await response.text();
11355
+ if (!response.ok)
11356
+ throw new ProbeFailure(classifyProbeHttp(response.status), `http_${response.status}`, `The gateway rejected the ${stage} check (HTTP ${response.status}).`, response.status);
11357
+ return body;
11358
+ };
11359
+ try {
11360
+ let first;
11361
+ if (protocol === "responses")
11362
+ first = { model, input: [{ role: "user", content: prompt }], tools: [{ type: "function", name: TOOL, parameters: PARAMETERS, strict: true }], tool_choice: { type: "function", name: TOOL }, max_output_tokens: 2048, store: false };
11363
+ else if (protocol === "messages")
11364
+ first = { model, messages: [{ role: "user", content: prompt }], tools: [{ name: TOOL, input_schema: PARAMETERS }], tool_choice: { type: "tool", name: TOOL }, max_tokens: 2048 };
11365
+ else if (protocol === "gemini")
11366
+ first = { contents: [{ role: "user", parts: [{ text: prompt }] }], tools: [{ functionDeclarations: [{ name: TOOL, parameters: PARAMETERS }] }], toolConfig: { functionCallingConfig: { mode: "ANY", allowedFunctionNames: [TOOL] } }, generationConfig: { maxOutputTokens: 2048 } };
11367
+ else
11368
+ first = { model, messages: [{ role: "user", content: prompt }], tools: [{ type: "function", function: { name: TOOL, parameters: PARAMETERS } }], tool_choice: { type: "function", function: { name: TOOL } }, max_tokens: 2048 };
11369
+ const data = JSON.parse(await send(first));
11370
+ const call = toolCall(data, protocol);
11371
+ if (call.ticket !== ticket)
11372
+ throw new ProbeFailure("unverified", "wrong_tool_arguments", "The model did not preserve the requested tool argument.");
11373
+ stage = "tool-result";
11374
+ let next;
11375
+ if (protocol === "responses")
11376
+ next = { ...first, input: [...first.input, ...data.output || [], { type: "function_call_output", call_id: call.id, output: proof }], tool_choice: "none", stream: true };
11377
+ else if (protocol === "messages")
11378
+ next = { ...first, messages: [...first.messages, { role: "assistant", content: data.content }, { role: "user", content: [{ type: "tool_result", tool_use_id: call.id, content: proof }] }], tool_choice: { type: "auto" }, stream: true };
11379
+ else if (protocol === "gemini")
11380
+ next = { ...first, contents: [...first.contents, data.candidates[0].content, { role: "user", parts: [{ functionResponse: { name: TOOL, response: { result: proof } } }] }], toolConfig: { functionCallingConfig: { mode: "NONE" } } };
11381
+ else
11382
+ next = { ...first, messages: [...first.messages, data.choices[0].message, { role: "tool", tool_call_id: call.id, content: proof }], tool_choice: "none", stream: true };
11383
+ const final = streamedText(await send(next, true), protocol);
11384
+ if (!final.complete)
11385
+ throw new ProbeFailure("incompatible", "incomplete_stream", "The response stream did not reach its completion event.");
11386
+ if (!final.text.includes(proof))
11387
+ throw new ProbeFailure("unverified", "missing_tool_result", "The model did not return the supplied tool result.");
11388
+ return result("passed");
11389
+ } catch (error) {
11390
+ if (error instanceof ProbeFailure)
11391
+ return result(error.status, { code: error.code, reason: error.message, httpStatus: error.httpStatus });
11392
+ return result("unavailable", { code: "connection_or_response_error", reason: "The model check could not complete. Check the gateway connection and provider availability." });
11393
+ }
11394
+ }
11395
+
11133
11396
  // src/commands/start.ts
11134
11397
  function isGatewayModelAvailable(availableModels, model) {
11135
11398
  return availableModels.includes(model);
@@ -11141,7 +11404,8 @@ var TOOL_OPTIONS = [
11141
11404
  { value: "agy", label: "Antigravity CLI", hint: "Google Gemini API via your ClosedRouter pool" },
11142
11405
  { value: "aider", label: "Aider", hint: "OpenAI-compatible API" },
11143
11406
  { value: "opencode", label: "OpenCode", hint: "OpenAI-compatible API" },
11144
- { value: "cursor", label: "Cursor Agent Local", hint: "OpenAI-compatible API; separate preview package required" }
11407
+ { value: "cursor", label: "Cursor Agent Local", hint: "OpenAI-compatible API; separate preview package required" },
11408
+ { value: "grok", label: "Grok Build", hint: "Custom provider using your ClosedRouter pool" }
11145
11409
  ];
11146
11410
  var DESKTOP_TOOL_OPTIONS = [
11147
11411
  { value: "codex-desktop", label: "Codex Desktop", hint: "Responses API with an isolated app profile" },
@@ -11156,7 +11420,7 @@ function resolveStartTarget(value, desktop = false) {
11156
11420
  const normalized = value.toLowerCase();
11157
11421
  desktop ||= normalized.endsWith("-desktop");
11158
11422
  const name = normalized.replace(/-desktop$/, "");
11159
- const tool = name === "antigravity" ? "agy" : name;
11423
+ const tool = name === "antigravity" ? "agy" : name === "grok-build" ? "grok" : name;
11160
11424
  if (!TOOL_OPTIONS.some((option) => option.value === tool)) {
11161
11425
  throw new Error(`Unsupported tool '${value}'. Choose: ${getStartToolOptions().map((option) => option.value).join(", ")}.`);
11162
11426
  }
@@ -11202,10 +11466,11 @@ function buildLaunchPlan(tool, serverUrl, apiKey, model, toolArgs = [], homeDir
11202
11466
  }
11203
11467
  if (tool === "codex") {
11204
11468
  assertNoManagedCodexArgs(toolArgs);
11205
- const codexHome = process.env.CODEX_HOME || join17(homeDir, ".codex");
11469
+ const codexHome = join19(homeDir, ".closedrouter", "cli", "codex", `session-${randomUUID6()}`);
11470
+ env.CODEX_HOME = codexHome;
11206
11471
  const profileName = "closedrouter-launch";
11207
- const configPath = join17(codexHome, `${profileName}.config.toml`);
11208
- const catalogPath = join17(codexHome, "closedrouter-models.json");
11472
+ const configPath = join19(codexHome, "config.toml");
11473
+ const catalogPath = join19(codexHome, "closedrouter-models.json");
11209
11474
  const configLines = [
11210
11475
  `model = ${tomlString(model)}`,
11211
11476
  `model_provider = ${tomlString(profileName)}`,
@@ -11216,25 +11481,59 @@ function buildLaunchPlan(tool, serverUrl, apiKey, model, toolArgs = [], homeDir
11216
11481
  `base_url = ${tomlString(`${baseUrl}/v1/`)}`,
11217
11482
  'env_key = "OPENAI_API_KEY"',
11218
11483
  'wire_api = "responses"',
11484
+ "requires_openai_auth = false",
11485
+ "supports_websockets = false",
11219
11486
  ""
11220
11487
  ];
11221
11488
  env.OPENAI_API_KEY = apiKey;
11222
11489
  return {
11223
11490
  tool,
11224
11491
  command,
11225
- args: ["--profile", profileName, ...toolArgs],
11492
+ args: [...toolArgs],
11226
11493
  env,
11227
11494
  configFile: { path: configPath, contents: configLines.join(`
11228
11495
  `) },
11229
11496
  ...catalog ? { extraFiles: [{ path: catalogPath, contents: JSON.stringify(codexModelCatalog(catalog.models)) }] } : {}
11230
11497
  };
11231
11498
  }
11499
+ if (tool === "grok") {
11500
+ const home = join19(homeDir, ".closedrouter", "cli", "grok", `session-${randomUUID6()}`);
11501
+ env.GROK_HOME = home;
11502
+ env.CLOSEDROUTER_API_KEY = apiKey;
11503
+ delete env.XAI_API_KEY;
11504
+ const ids = catalog?.models || [model];
11505
+ const config = ["[models]", `default = ${tomlString(model)}`, `web_search = ${tomlString(model)}`, ...ids.flatMap((id) => [
11506
+ "",
11507
+ `[model.${tomlString(id)}]`,
11508
+ `model = ${tomlString(id)}`,
11509
+ `name = ${tomlString(modelLabel(id))}`,
11510
+ 'description = "Via your ClosedRouter pool"',
11511
+ `base_url = ${tomlString(`${baseUrl}/v1`)}`,
11512
+ 'env_key = "CLOSEDROUTER_API_KEY"',
11513
+ 'api_backend = "responses"',
11514
+ "supports_backend_search = false",
11515
+ "context_window = 32768"
11516
+ ])].join(`
11517
+ `);
11518
+ const candidates = [join19(homeDir, ".grok", "bin", "grok"), join19(homeDir, ".local", "bin", "grok")];
11519
+ return {
11520
+ tool,
11521
+ command: executablePath || candidates.find((path) => existsSync17(path)) || "grok",
11522
+ args: [...toolArgs],
11523
+ env,
11524
+ configFile: { path: join19(home, "config.toml"), contents: config }
11525
+ };
11526
+ }
11232
11527
  if (tool === "gemini") {
11528
+ delete env.GOOGLE_API_KEY;
11529
+ delete env.GOOGLE_GENAI_USE_VERTEXAI;
11233
11530
  env.GEMINI_API_KEY = apiKey;
11234
11531
  env.GEMINI_MODEL = model;
11235
11532
  env.GOOGLE_GEMINI_BASE_URL = baseUrl;
11236
- const configPath = join17(homeDir, ".closedrouter", "gemini-launch.settings.json");
11237
- env.GEMINI_CLI_SYSTEM_SETTINGS_PATH = configPath;
11533
+ const geminiHome = join19(homeDir, ".closedrouter", "cli", "gemini", `session-${randomUUID6()}`);
11534
+ env.GEMINI_CLI_HOME = geminiHome;
11535
+ const configPath = join19(geminiHome, ".gemini", "settings.json");
11536
+ delete env.GEMINI_CLI_SYSTEM_SETTINGS_PATH;
11238
11537
  return {
11239
11538
  tool,
11240
11539
  command,
@@ -11268,9 +11567,9 @@ function buildLaunchPlan(tool, serverUrl, apiKey, model, toolArgs = [], homeDir
11268
11567
  return {
11269
11568
  tool,
11270
11569
  command,
11271
- args: ["--model", `openai/${catalog ? "closedrouter/" : ""}${model}`, ...catalog ? ["--model-metadata-file", join17(homeDir, ".closedrouter", "aider-models.json")] : [], ...toolArgs],
11570
+ args: ["--model", `openai/${catalog ? "closedrouter/" : ""}${model}`, ...catalog ? ["--model-metadata-file", join19(homeDir, ".closedrouter", "aider-models.json")] : [], ...toolArgs],
11272
11571
  env,
11273
- ...catalog ? { configFile: { path: join17(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 }]))) } } : {}
11572
+ ...catalog ? { configFile: { path: join19(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 }]))) } } : {}
11274
11573
  };
11275
11574
  }
11276
11575
  const config = {
@@ -11297,9 +11596,9 @@ function buildLaunchPlan(tool, serverUrl, apiKey, model, toolArgs = [], homeDir
11297
11596
  };
11298
11597
  }
11299
11598
  function writeOwnedConfig(file) {
11300
- mkdirSync5(dirname5(file.path), { recursive: true, mode: 448 });
11599
+ mkdirSync6(dirname5(file.path), { recursive: true, mode: 448 });
11301
11600
  try {
11302
- const existing = readFileSync15(file.path, "utf8");
11601
+ const existing = readFileSync16(file.path, "utf8");
11303
11602
  if (file.path.endsWith(".config.toml") && !existing.includes('name = "ClosedRouter"')) {
11304
11603
  throw new Error(`Refusing to replace an existing Codex profile at ${file.path}.`);
11305
11604
  }
@@ -11307,7 +11606,7 @@ function writeOwnedConfig(file) {
11307
11606
  if (error instanceof Error && error.message.startsWith("Refusing to replace"))
11308
11607
  throw error;
11309
11608
  }
11310
- writeFileSync5(file.path, file.contents, { encoding: "utf8", mode: 384 });
11609
+ writeFileSync6(file.path, file.contents, { encoding: "utf8", mode: 384 });
11311
11610
  }
11312
11611
  function modelSupportsHarness(model, tool) {
11313
11612
  const parameters = model.supported_parameters;
@@ -11336,9 +11635,9 @@ function assertAgyCustomModel(command, label, apiKey) {
11336
11635
  throw new Error(`This version of agy did not recognize '${label}'. Update agy to use ClosedRouter's accurate model display.`);
11337
11636
  }
11338
11637
  function assertAgyGeminiMode(homeDir) {
11339
- const settingsPath = join17(homeDir, ".gemini", "antigravity-cli", "settings.json");
11638
+ const settingsPath = join19(homeDir, ".gemini", "antigravity-cli", "settings.json");
11340
11639
  try {
11341
- const settings = JSON.parse(readFileSync15(settingsPath, "utf8"));
11640
+ const settings = JSON.parse(readFileSync16(settingsPath, "utf8"));
11342
11641
  if (settings?.modelProvider === "gemini")
11343
11642
  return;
11344
11643
  } catch {}
@@ -11389,6 +11688,19 @@ async function startCommand(options = {}) {
11389
11688
  if (desktop)
11390
11689
  validateDesktopArgs(options.toolArgs || []);
11391
11690
  let executablePath = desktop && isDesktopTool(tool) ? resolveDesktopExecutable(tool, options.executablePath) : tool === "cursor" ? resolveCursorLocalExecutable(options.executablePath) : options.executablePath;
11691
+ if (!desktop && tool === "grok") {
11692
+ const candidates = executablePath ? [executablePath] : ["grok", join19(homedir15(), ".grok/bin/grok"), join19(homedir15(), ".local/bin/grok")];
11693
+ executablePath = candidates.find((command) => {
11694
+ try {
11695
+ execFileSync3(command, ["--version"], { timeout: 5000, stdio: "ignore" });
11696
+ return true;
11697
+ } catch {
11698
+ return false;
11699
+ }
11700
+ });
11701
+ if (!executablePath)
11702
+ throw new Error("Grok Build was not found. Install it from https://grok.com/build or pass --path <grok-executable>.");
11703
+ }
11392
11704
  const sourceApp = executablePath;
11393
11705
  let sharedClaudeElectron;
11394
11706
  if (desktop && isDesktopTool(tool) && options.openOnly) {
@@ -11422,7 +11734,9 @@ async function startCommand(options = {}) {
11422
11734
  const apiKey = await ensurePlatformKey(tool, client, options);
11423
11735
  client.setApiKey(apiKey);
11424
11736
  const models = await client.listGatewayModels({ preferApiKey: true });
11425
- const eligibleModels = models.filter((item) => modelSupportsHarness(item, tool));
11737
+ const protocol = harnessProtocol(tool);
11738
+ const compatibility = loadCompatibility(serverUrl, apiKey);
11739
+ const eligibleModels = models.filter((item) => modelSupportsHarness(item, tool) && recentCompatibility(compatibility, gatewayModelId(item), protocol)?.status !== "incompatible");
11426
11740
  const availableModels = [...new Set(eligibleModels.map(gatewayModelId).filter(Boolean))].sort();
11427
11741
  let model = options.model?.trim();
11428
11742
  if (!model) {
@@ -11447,10 +11761,20 @@ async function startCommand(options = {}) {
11447
11761
  if (!isGatewayModelAvailable(availableModels, model)) {
11448
11762
  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.`);
11449
11763
  }
11764
+ if (options.verifyModel !== false) {
11765
+ let checked = recentCompatibility(compatibility, model, protocol);
11766
+ if (checked?.status !== "passed") {
11767
+ M2.info(`Checking tool use and streaming for ${model}.`);
11768
+ checked = await probeCompatibility(serverUrl, apiKey, model, protocol);
11769
+ saveCompatibility(serverUrl, apiKey, [checked]);
11770
+ }
11771
+ if (checked.status !== "passed")
11772
+ throw new Error(`${model} did not pass the ${protocol} compatibility check: ${checked.reason || checked.code}. Run cr verify ${tool} --model ${model} to check again. Provider quota and connection failures are temporary and do not mark the model incompatible.`);
11773
+ }
11450
11774
  const catalog = createLaunchCatalog(availableModels, model);
11451
11775
  const account = desktopAccountIdentity(config, serverUrl, apiKey);
11452
11776
  if (desktop && tool === "cursor") {
11453
- const localKey = randomUUID5();
11777
+ const localKey = randomUUID6();
11454
11778
  const relay = await startLaunchProxy(serverUrl, apiKey, "cursor-desktop", localKey, catalog);
11455
11779
  try {
11456
11780
  const plan = buildDesktopLaunchPlan("cursor", executablePath, createDesktopProfile("cursor"), undefined, undefined, undefined, options.toolArgs);
@@ -11465,11 +11789,13 @@ async function startCommand(options = {}) {
11465
11789
  return;
11466
11790
  }
11467
11791
  if (desktop && (tool === "codex" || tool === "claude" || tool === "agy")) {
11468
- const localKey = randomUUID5();
11792
+ const localKey = randomUUID6();
11469
11793
  const relay = await startLaunchProxy(serverUrl, apiKey, `${tool}-desktop`, localKey, catalog);
11470
11794
  let agyDesktopProxy;
11471
11795
  try {
11472
11796
  const plan = buildDesktopLaunchPlan(tool, executablePath, createDesktopProfile(tool), relay.baseUrl, localKey, model, options.toolArgs, process.platform, catalog.models);
11797
+ if (tool === "codex" && executablePath.endsWith(".app"))
11798
+ addCodexDesktopHook(plan, executablePath, catalog.models, account);
11473
11799
  if (sharedClaudeElectron && tool === "claude") {
11474
11800
  if (sharedClaudeElectron)
11475
11801
  useClaudeSharedElectron(plan, sharedClaudeElectron, sourceApp, options.toolArgs, true);
@@ -11512,6 +11838,45 @@ async function startCommand(options = {}) {
11512
11838
  }
11513
11839
  }
11514
11840
 
11841
+ // src/commands/verify.ts
11842
+ import { writeFileSync as writeFileSync7 } from "node:fs";
11843
+ async function verifyCommand(tool, options) {
11844
+ const config = loadConfig(), server = options.server || config.serverUrl, key = options.key || config.apiKey;
11845
+ if (!key)
11846
+ throw new Error("Sign in with cr login or supply a platform API key before checking models.");
11847
+ const client = new ClosedRouterClient({ baseUrl: server, apiKey: key });
11848
+ const catalog = await client.listGatewayModels({ preferApiKey: true });
11849
+ const models = catalog.filter((model) => (model.status === undefined || model.status === "available") && (!model.supported_parameters || model.supported_parameters.includes("tools"))).map(gatewayModelId).filter(Boolean);
11850
+ const ids = options.model ? [options.model] : options.allModels ? models : models.slice(0, 1);
11851
+ if (ids.some((id) => !models.includes(id)))
11852
+ throw new Error("Choose an available tool-capable model from the gateway catalog.");
11853
+ const protocols = tool ? [harnessProtocol(tool.replace(/-desktop$/, ""))] : PROTOCOLS;
11854
+ const concurrency = Number(options.concurrency || 2);
11855
+ if (!Number.isInteger(concurrency) || concurrency < 1 || concurrency > 4)
11856
+ throw new Error("Use a concurrency between 1 and 4.");
11857
+ const queue = ids.flatMap((model) => protocols.map((protocol) => ({ model, protocol }))), results = [];
11858
+ let cursor = 0;
11859
+ console.log(`Checking ${queue.length} model/protocol combinations with tool calls, tool results, and streaming.`);
11860
+ await Promise.all(Array.from({ length: Math.min(concurrency, queue.length) }, async () => {
11861
+ while (cursor < queue.length) {
11862
+ const item = queue[cursor++];
11863
+ if (!item)
11864
+ break;
11865
+ const result = await probeCompatibility(server, key, item.model, item.protocol);
11866
+ results.push(result);
11867
+ saveCompatibility(server, key, [result]);
11868
+ console.log(`${results.length}/${queue.length} ${result.model} ${result.protocol}: ${result.status}${result.code ? ` (${result.code}, ${result.stage})` : ""}`);
11869
+ }
11870
+ }));
11871
+ const report = { checkedAt: new Date().toISOString(), scope: "gateway protocol checks; native harness checks are separate", results };
11872
+ if (options.output)
11873
+ writeFileSync7(options.output, JSON.stringify(report, null, 2) + `
11874
+ `, { mode: 384 });
11875
+ console.log(JSON.stringify({ passed: results.filter((r) => r.status === "passed").length, incompatible: results.filter((r) => r.status === "incompatible").length, unavailable: results.filter((r) => r.status === "unavailable").length, unverified: results.filter((r) => r.status === "unverified").length }));
11876
+ if (results.some((r) => r.status !== "passed"))
11877
+ process.exitCode = 1;
11878
+ }
11879
+
11515
11880
  // src/commands/whoami.ts
11516
11881
  var import_picocolors13 = __toESM(require_picocolors(), 1);
11517
11882
  async function whoamiCommand() {
@@ -11557,7 +11922,7 @@ process.on("warning", (warning) => {
11557
11922
  console.warn(warning.name, warning.message);
11558
11923
  });
11559
11924
  var program2 = new Command2;
11560
- var cliVersion = JSON.parse(readFileSync16(new URL("../package.json", import.meta.url), "utf8")).version;
11925
+ var cliVersion = JSON.parse(readFileSync17(new URL("../package.json", import.meta.url), "utf8")).version;
11561
11926
  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) => {
11562
11927
  await onboardCommand({ server: options.server });
11563
11928
  });
@@ -11608,7 +11973,8 @@ program2.command("gateway").alias("daemon").alias("bridge").description("Start t
11608
11973
  program2.command("settings").description("Configure the CLI model selector").option("--model-grouping <grouping>", "Group models by model-provider, account-provider, or none").action(async (options) => {
11609
11974
  await settingsCommand({ modelGrouping: options.modelGrouping });
11610
11975
  });
11611
- program2.command("start [tool] [tool-args...]").alias("launch").description("Configure and start a coding CLI or desktop app through ClosedRouter").option("--desktop", "Launch the desktop app for codex, claude, cursor, or antigravity").option("--open-only", "Open an isolated desktop app without gateway configuration").option("--desktop-inspector", "Enable Claude's startup hook using an installed shared Electron runtime").option("-s, --server <url>", "ClosedRouter server base URL").option("-k, --key <key>", "Platform API key; otherwise use the saved CLI key").option("-m, --model <model>", "Gateway model to use").option("--group-by <grouping>", "Override model grouping for this launch: model-provider, account-provider, or none").option("--path <path>", "Path to the installed CLI, desktop executable, or macOS app bundle").allowUnknownOption().action(async (tool, toolArgs, options) => {
11976
+ program2.command("verify [tool]").description("Check model tool calls, tool-result continuation, and streaming through the gateway").option("-s, --server <url>", "ClosedRouter server base URL").option("-k, --key <key>", "Platform API key; otherwise use the saved CLI key").option("-m, --model <model>", "Check one model").option("--all-models", "Check every available tool-capable model").option("--concurrency <count>", "Concurrent checks, between 1 and 4", "2").option("--output <path>", "Write a JSON compatibility report").action(async (tool, options) => verifyCommand(tool, { ...options, server: options.server || program2.opts().server }));
11977
+ program2.command("start [tool] [tool-args...]").alias("launch").description("Configure and start a coding CLI or desktop app through ClosedRouter").option("--desktop", "Launch the desktop app for codex, claude, cursor, or antigravity").option("--open-only", "Open an isolated desktop app without gateway configuration").option("--desktop-inspector", "Enable Claude's startup hook using an installed shared Electron runtime").option("--no-verify-model", "Skip the selected-model protocol check for a previously verified private gateway").option("-s, --server <url>", "ClosedRouter server base URL").option("-k, --key <key>", "Platform API key; otherwise use the saved CLI key").option("-m, --model <model>", "Gateway model to use").option("--group-by <grouping>", "Override model grouping for this launch: model-provider, account-provider, or none").option("--path <path>", "Path to the installed CLI, desktop executable, or macOS app bundle").allowUnknownOption().action(async (tool, toolArgs, options) => {
11612
11978
  await startCommand({
11613
11979
  tool,
11614
11980
  toolArgs: toolArgs || [],
@@ -11619,7 +11985,8 @@ program2.command("start [tool] [tool-args...]").alias("launch").description("Con
11619
11985
  executablePath: options.path,
11620
11986
  desktop: options.desktop,
11621
11987
  openOnly: options.openOnly,
11622
- desktopInspector: options.desktopInspector
11988
+ desktopInspector: options.desktopInspector,
11989
+ verifyModel: options.verifyModel
11623
11990
  });
11624
11991
  });
11625
11992
  program2.parseAsync(process.argv).catch((error) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@closedrouter/cli",
3
- "version": "1.1.18",
3
+ "version": "1.1.19",
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": {