@closedrouter/cli 1.1.9 → 1.1.10

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
@@ -141,17 +141,21 @@ cr start claude --desktop --model claude-sonnet-4-6 --path /Applications/Claude.
141
141
  | Desktop app | Support |
142
142
  | --- | --- |
143
143
  | Codex | Configures a Responses provider and API-key authentication in an isolated `CODEX_HOME`. |
144
- | Claude | Configures the desktop inference gateway using its `3p` deployment profile and `configLibrary`. Requires a Claude build with gateway support. |
145
- | Cursor | Automatically configures the OpenAI API key, base URL, enabled custom model, and model selectors in an isolated SQLite profile. Reuses an existing Cursor login. The picker excludes `claude-` and `gemini-` names because the standard editor assigns those to separate providers. BYOK requests use Cursor's servers, so the gateway must be publicly reachable. Cursor account and feature restrictions still apply. |
144
+ | Claude | Configures its native `3p` gateway profile, displays the selected pool model, and maps the compatible Anthropic transport name to that model. No Claude subscription or login is required for gateway sessions. |
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. |
147
147
 
148
148
  Desktop launches follow the isolation and gateway configuration in `reference_repos/cockpit-tools`. Each launch creates a new Electron profile under `~/.closedrouter/desktop/<tool>/`, or your `CLOSEDROUTER_CONFIG_DIR`. Those profiles retain the app's session files. Cursor copies only its login state, and Antigravity retains its native language-server account and conversation store for compatibility with `antigravity-mods`. Use `--open-only` to open an app without creating a gateway configuration or requiring ClosedRouter login. An open-only Antigravity instance continues using your existing account-rotation hook when that daemon is running.
149
149
 
150
- Codex, Claude, and Antigravity use a streaming loopback relay with a per-launch local credential. Their platform key stays in the launcher process. Cursor needs the platform key in its isolated profile so its server-side BYOK transport can authenticate to the public gateway; its installed editor migrates that key into secure storage on startup. Generated files and profile directories start with private permissions. Keep `cr` running and close the launched app instance to end the session. On macOS, `open -W -n -a` starts a separate instance and waits for it to exit. Interrupting `cr` stops local routing; macOS may leave the app open. Start a new session with `cr start` to reconnect.
150
+ Desktop clients use a streaming loopback relay with a per-launch local credential. The platform key stays in the launcher process. Cursor's local engine uses the relay directly, so the gateway does not need to be publicly reachable from Cursor's servers. Generated files and profile directories start with private permissions. Keep `cr` running and close the launched app instance to end the session. Interrupting `cr` stops local routing; macOS may leave the app open. Start a new session with `cr start` to reconnect.
151
+
152
+ Cursor's startup hook enables local provider mode in memory and serves an app-owned workbench script. The installed app files and signature are preserved. All tool-capable pool models are offered, including Claude and Gemini names. The relay pins requests to the selected model, avoiding Cursor's built-in Fireworks aliases. Native model and login controllers are checked before injection; unsupported app builds fail with an update instruction. A valid existing login retains its normal authentication policy checks. Set `CLOSEDROUTER_CURSOR_AUTH_DB` to a different SQLite source, or a nonexistent path to test the no-login case.
153
+
154
+ Claude normally uses its signed native app and gateway configuration, without a debugger. For a startup hook, use `cr start claude --desktop --desktop-inspector`. The launcher reuses a compatible Electron runtime already installed by Cockpit, or the executable supplied through `CLOSEDROUTER_CLAUDE_ELECTRON`. It runs the installed Claude archive and resources under that runtime, avoiding an app copy or signing changes. The inspector binds to loopback and closes after injection. If no compatible runtime is available, native gateway mode remains available without the option. The displayed model label identifies the actual pool model; the Anthropic transport ID is an internal compatibility name.
151
155
 
152
156
  Antigravity uses the CCPA envelope conversion documented in `~/Projects/antigravity-mods`. The launcher briefly opens a loopback Node inspector, installs its language-server spawn hook before the app starts, then closes the inspector. It overrides generation routing only in the launched process and prevents the global mods daemon from overwriting that session's endpoint. App bundles and global hook configuration are not modified. The app still displays its native model names; the actual pool model is the one selected by `cr start`. Unsupported launcher builds fail before opening; `--open-only` remains available.
153
157
 
154
- macOS app bundles are discovered in `/Applications` and `~/Applications`. Common Windows and Linux installation paths are checked, with `--path` available for other installations. Antigravity's routed desktop hook currently supports the inspected macOS build; other platforms can open the app with `--open-only`. Cursor configuration supports Bun, Node 22+, or the system `sqlite3` executable. Tests cover generated profiles, authenticated routing, and streamed function-call envelopes. Installed-app smoke checks verified Cursor settings hydration and Antigravity's live language-server routing. Full model turns in each desktop UI still require verification. See the [Codex provider configuration](https://developers.openai.com/codex/config-advanced) and [Cursor documentation](https://cursor.com/docs) for their configuration interfaces.
158
+ macOS app bundles are discovered in `/Applications` and `~/Applications`. Common Windows and Linux installation paths are checked, with `--path` available for other installations. Live conversation tests verified the installed Cursor, Claude Code desktop view, and Antigravity apps using a ClosedRouter OpenAI route. Cursor was tested with no OAuth tokens and no paid login. Gemma's route reached Google's API, but the full agent request received an upstream quota error; provider quotas still apply. Desktop hooks are verified against inspected app builds and 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.
155
159
 
156
160
  The model picker uses ClosedRouter's lime selection accent, colored provider names, gold favorites, and a framed live-search field. Wide terminals show details for the highlighted model beside the list. Smaller terminals use a compact layout. Closing the picker restores your previous terminal screen. Color adapts to truecolor, 256-color, or standard ANSI terminals; set `NO_COLOR=1` for plain text.
157
161
 
@@ -1,5 +1,5 @@
1
1
  /** Streaming transport relay: inject attribution for clients with no custom-header setting. */
2
- export declare function startLaunchProxy(gatewayUrl: string, apiKey: string, harness: string, localKey?: string): Promise<{
2
+ export declare function startLaunchProxy(gatewayUrl: string, apiKey: string, harness: string, localKey?: string, modelOverride?: string): Promise<{
3
3
  baseUrl: string;
4
4
  launchId: `${string}-${string}-${string}-${string}-${string}`;
5
5
  close: () => Promise<void>;
@@ -1 +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,EAAE,QAAQ,SAAS;;;;GA0D5G"}
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,EAAE,QAAQ,SAAS,EAAE,aAAa,CAAC,EAAE,MAAM;;;;GAmFpI"}
@@ -0,0 +1,5 @@
1
+ import type { DesktopLaunchPlan } from "./desktop-launch";
2
+ export declare function findClaudeSharedElectron(sourceApp: string, home?: string): string | undefined;
3
+ export declare function useClaudeSharedElectron(plan: DesktopLaunchPlan, runtime: string, sourceApp: string, toolArgs?: string[]): void;
4
+ export declare function addClaudeDesktopHook(plan: DesktopLaunchPlan, baseUrl: string, localKey: string, sourceApp?: string): void;
5
+ //# sourceMappingURL=claude-desktop.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-desktop.d.ts","sourceRoot":"","sources":["../../src/commands/claude-desktop.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAE1D,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,SAAY,GAAG,MAAM,GAAG,SAAS,CAShG;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAM,EAAO,GAAG,IAAI,CAGlI;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CA4BzH"}
@@ -0,0 +1,3 @@
1
+ import type { DesktopLaunchPlan } from "./desktop-launch";
2
+ export declare function addCursorDesktopLocalHook(plan: DesktopLaunchPlan, app: string): void;
3
+ //# sourceMappingURL=cursor-desktop-hook.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cursor-desktop-hook.d.ts","sourceRoot":"","sources":["../../src/commands/cursor-desktop-hook.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAI1D,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,iBAAiB,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAsDpF"}
@@ -1,4 +1,3 @@
1
1
  export declare const CURSOR_APPLICATION_STORAGE = "src.vs.platform.reactivestorage.browser.reactiveStorageServiceImpl.persistentStorage.applicationUser";
2
- export declare function cursorDesktopSupportsModel(model: string): boolean;
3
2
  export declare function configureCursorDesktop(profileDir: string, baseUrl: string, apiKey: string, model: string, sourceDb?: string): Promise<string>;
4
3
  //# sourceMappingURL=cursor-desktop.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cursor-desktop.d.ts","sourceRoot":"","sources":["../../src/commands/cursor-desktop.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,0BAA0B,yGAAyG,CAAC;AAEjJ,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAIjE;AAyCD,wBAAsB,sBAAsB,CAC1C,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAClE,QAAQ,SAA0B,GACjC,OAAO,CAAC,MAAM,CAAC,CAkCjB"}
1
+ {"version":3,"file":"cursor-desktop.d.ts","sourceRoot":"","sources":["../../src/commands/cursor-desktop.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,0BAA0B,yGAAyG,CAAC;AAyCjJ,wBAAsB,sBAAsB,CAC1C,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAClE,QAAQ,SAAqE,GAC5E,OAAO,CAAC,MAAM,CAAC,CA0CjB"}
@@ -1,4 +1,12 @@
1
- /** Inject before Antigravity's first application module, then detach. Packaged
1
+ /** Inject before Desktop app's first application module, then detach. Packaged
2
2
  * Electron rejects --require in NODE_OPTIONS but supports this loopback inspector. */
3
- export declare function installDesktopInspectorHook(hookPath: string, logPath: string, signal: AbortSignal): Promise<void>;
3
+ export declare function installDesktopInspectorHook(hookPath: string, logPath: string, signal: AbortSignal, options?: {
4
+ readyPath?: string;
5
+ startupBreakpoint?: {
6
+ urlRegex: string;
7
+ lineNumber: number;
8
+ columnNumber: number;
9
+ condition: string;
10
+ };
11
+ }): Promise<void>;
4
12
  //# sourceMappingURL=desktop-inspector.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"desktop-inspector.d.ts","sourceRoot":"","sources":["../../src/commands/desktop-inspector.ts"],"names":[],"mappings":"AAIA;sFACsF;AACtF,wBAAsB,2BAA2B,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CA6DvH"}
1
+ {"version":3,"file":"desktop-inspector.d.ts","sourceRoot":"","sources":["../../src/commands/desktop-inspector.ts"],"names":[],"mappings":"AAIA;sFACsF;AACtF,wBAAsB,2BAA2B,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,GAAE;IACjH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iBAAiB,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;CAClG,GAAG,OAAO,CAAC,IAAI,CAAC,CAkErB"}
@@ -11,6 +11,13 @@ export interface DesktopLaunchPlan {
11
11
  inspectorHook?: {
12
12
  path: string;
13
13
  logPath: string;
14
+ readyPath?: string;
15
+ startupBreakpoint?: {
16
+ urlRegex: string;
17
+ lineNumber: number;
18
+ columnNumber: number;
19
+ condition: string;
20
+ };
14
21
  };
15
22
  }
16
23
  export declare function isDesktopTool(tool: string): tool is DesktopTool;
@@ -1 +1 @@
1
- {"version":3,"file":"desktop-launch.d.ts","sourceRoot":"","sources":["../../src/commands/desktop-launch.ts"],"names":[],"mappings":"AAQA,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;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CACnD;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,GACnD,iBAAiB,CA+CnB;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,CAe/E"}
1
+ {"version":3,"file":"desktop-launch.d.ts","sourceRoot":"","sources":["../../src/commands/desktop-launch.ts"],"names":[],"mappings":"AAQA,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,GACnD,iBAAiB,CAiDnB;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"}
@@ -16,6 +16,7 @@ export interface StartCommandOptions {
16
16
  executablePath?: string;
17
17
  desktop?: boolean;
18
18
  openOnly?: boolean;
19
+ desktopInspector?: boolean;
19
20
  }
20
21
  interface ConfigFile {
21
22
  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;AAYlE,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;CACpB;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;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,GACxD,UAAU,CAqHZ;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,CAmGnF"}
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,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;CACzB;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,GACxD,UAAU,CAqHZ;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,CAsHnF"}
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 readFileSync13 } from "node:fs";
5207
+ import { readFileSync as readFileSync14 } 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";
@@ -8658,10 +8658,10 @@ to scan and import local subscriptions!`, "Pool Empty");
8658
8658
  var import_picocolors12 = __toESM(require_picocolors(), 1);
8659
8659
  import { randomUUID as randomUUID5 } from "node:crypto";
8660
8660
  import { spawn as spawn5 } from "node:child_process";
8661
- import { execFileSync as execFileSync2 } from "node:child_process";
8662
- import { mkdirSync as mkdirSync4, readFileSync as readFileSync12, writeFileSync as writeFileSync4 } from "node:fs";
8663
- import { homedir as homedir13 } from "node:os";
8664
- import { dirname as dirname3, join as join14 } from "node:path";
8661
+ import { execFileSync as execFileSync3 } from "node:child_process";
8662
+ import { mkdirSync as mkdirSync4, readFileSync as readFileSync13, writeFileSync as writeFileSync4 } from "node:fs";
8663
+ import { homedir as homedir14 } from "node:os";
8664
+ import { dirname as dirname4, join as join16 } from "node:path";
8665
8665
 
8666
8666
  // src/model-picker.ts
8667
8667
  import { emitKeypressEvents as emitKeypressEvents2 } from "node:readline";
@@ -9419,14 +9419,14 @@ import { randomUUID } from "node:crypto";
9419
9419
  import { createServer as createServer2, request as httpRequest } from "node:http";
9420
9420
  import { request as httpsRequest } from "node:https";
9421
9421
  var HOP_HEADERS = new Set(["connection", "keep-alive", "proxy-authenticate", "proxy-authorization", "te", "trailer", "transfer-encoding", "upgrade", "host"]);
9422
- async function startLaunchProxy(gatewayUrl, apiKey, harness, localKey = apiKey) {
9422
+ async function startLaunchProxy(gatewayUrl, apiKey, harness, localKey = apiKey, modelOverride) {
9423
9423
  const upstream = new URL(gatewayUrl.replace(/\/+$/, "") + "/");
9424
9424
  if (!["http:", "https:"].includes(upstream.protocol) || upstream.username || upstream.password) {
9425
9425
  throw new Error("ClosedRouter server URL must use HTTP or HTTPS without embedded credentials.");
9426
9426
  }
9427
9427
  const launchId = randomUUID();
9428
9428
  const transport = upstream.protocol === "https:" ? httpsRequest : httpRequest;
9429
- const server = createServer2((incoming, outgoing) => {
9429
+ const server = createServer2(async (incoming, outgoing) => {
9430
9430
  const authorized = incoming.headers.authorization === `Bearer ${localKey}` || incoming.headers["x-api-key"] === localKey || incoming.headers["x-goog-api-key"] === localKey;
9431
9431
  if (!authorized) {
9432
9432
  outgoing.writeHead(401).end();
@@ -9440,6 +9440,32 @@ async function startLaunchProxy(gatewayUrl, apiKey, harness, localKey = apiKey)
9440
9440
  const local = new URL(incoming.url, "http://localhost");
9441
9441
  target.pathname = upstream.pathname.replace(/\/$/, "") + local.pathname;
9442
9442
  target.search = local.search;
9443
+ let rewrittenBody;
9444
+ if (modelOverride && incoming.method === "POST" && /^\/v1\/(?:messages(?:\/count_tokens)?|chat\/completions|responses)$/.test(local.pathname)) {
9445
+ try {
9446
+ const chunks = [];
9447
+ let length = 0;
9448
+ for await (const chunk of incoming) {
9449
+ length += chunk.length;
9450
+ if (length > 32 * 1024 * 1024) {
9451
+ outgoing.writeHead(413).end();
9452
+ return;
9453
+ }
9454
+ chunks.push(Buffer.from(chunk));
9455
+ }
9456
+ const body = JSON.parse(Buffer.concat(chunks).toString("utf8"));
9457
+ if (!body || typeof body !== "object" || Array.isArray(body)) {
9458
+ outgoing.writeHead(400).end();
9459
+ return;
9460
+ }
9461
+ const payload = body;
9462
+ const tinyProbe = local.pathname === "/v1/messages" && !modelOverride.startsWith("claude-") && payload.model !== modelOverride && typeof payload.max_tokens === "number" && payload.max_tokens <= 16 && Array.isArray(payload.messages) && payload.messages.length === 1 && (!Array.isArray(payload.tools) || payload.tools.length === 0);
9463
+ rewrittenBody = JSON.stringify({ ...payload, model: modelOverride, ...tinyProbe ? { max_tokens: 512 } : {} });
9464
+ } catch {
9465
+ outgoing.writeHead(400).end();
9466
+ return;
9467
+ }
9468
+ }
9443
9469
  const headers = { ...incoming.headers };
9444
9470
  const connectionHeaders = String(headers.connection || "").split(",").map((x) => x.trim().toLowerCase());
9445
9471
  for (const key of Object.keys(headers)) {
@@ -9453,6 +9479,8 @@ async function startLaunchProxy(gatewayUrl, apiKey, harness, localKey = apiKey)
9453
9479
  delete headers["x-goog-api-key"];
9454
9480
  headers.authorization = `Bearer ${apiKey}`;
9455
9481
  }
9482
+ if (rewrittenBody !== undefined)
9483
+ delete headers["content-length"];
9456
9484
  const request = transport(target, { method: incoming.method, headers }, (response) => {
9457
9485
  const responseHeaders = { ...response.headers };
9458
9486
  for (const key of HOP_HEADERS)
@@ -9472,7 +9500,10 @@ async function startLaunchProxy(gatewayUrl, apiKey, harness, localKey = apiKey)
9472
9500
  if (!outgoing.writableFinished)
9473
9501
  request.destroy();
9474
9502
  });
9475
- incoming.pipe(request);
9503
+ if (rewrittenBody !== undefined)
9504
+ request.end(rewrittenBody);
9505
+ else
9506
+ incoming.pipe(request);
9476
9507
  });
9477
9508
  await new Promise((resolve, reject) => {
9478
9509
  server.once("error", reject);
@@ -9653,14 +9684,14 @@ function resolveCursorLocalExecutable(executablePath, homeDir = homedir9()) {
9653
9684
  // src/commands/desktop-launch.ts
9654
9685
  import { spawn as spawn3 } from "node:child_process";
9655
9686
  import { randomUUID as randomUUID3 } from "node:crypto";
9656
- import { accessSync, constants, existsSync as existsSync11, mkdirSync as mkdirSync2, mkdtempSync, readFileSync as readFileSync10, writeFileSync as writeFileSync3 } from "node:fs";
9687
+ import { accessSync, closeSync, constants, existsSync as existsSync11, mkdirSync as mkdirSync2, mkdtempSync, openSync, readFileSync as readFileSync10, writeFileSync as writeFileSync3 } from "node:fs";
9657
9688
  import { homedir as homedir10 } from "node:os";
9658
9689
  import { dirname, isAbsolute, join as join11, relative, resolve } from "node:path";
9659
9690
 
9660
9691
  // src/commands/desktop-inspector.ts
9661
9692
  import { existsSync as existsSync10, readFileSync as readFileSync9 } from "node:fs";
9662
9693
  import { setTimeout as delay } from "node:timers/promises";
9663
- async function installDesktopInspectorHook(hookPath, logPath, signal) {
9694
+ async function installDesktopInspectorHook(hookPath, logPath, signal, options = {}) {
9664
9695
  const deadline = Date.now() + 15000;
9665
9696
  let target;
9666
9697
  while (Date.now() < deadline && !signal.aborted) {
@@ -9671,7 +9702,7 @@ async function installDesktopInspectorHook(hookPath, logPath, signal) {
9671
9702
  await delay(100, undefined, { signal });
9672
9703
  }
9673
9704
  if (!target)
9674
- throw new Error("Antigravity did not expose its launch inspector. Close the paused app and retry with --open-only.");
9705
+ throw new Error("Desktop app did not expose its launch inspector. Close the paused app and retry with --open-only.");
9675
9706
  const Socket = "Bun" in globalThis ? globalThis.WebSocket : wrapper_default;
9676
9707
  const socket = new Socket(target);
9677
9708
  let sequence = 0;
@@ -9685,7 +9716,7 @@ async function installDesktopInspectorHook(hookPath, logPath, signal) {
9685
9716
  signal.addEventListener("abort", abort, { once: true });
9686
9717
  const closed = () => {
9687
9718
  for (const item of pending.values())
9688
- item.reject(new Error("Antigravity launch inspector disconnected before the hook was installed."));
9719
+ item.reject(new Error("Desktop app launch inspector disconnected before the hook was installed."));
9689
9720
  pending.clear();
9690
9721
  };
9691
9722
  socket.addEventListener("close", closed);
@@ -9712,21 +9743,29 @@ async function installDesktopInspectorHook(hookPath, logPath, signal) {
9712
9743
  try {
9713
9744
  await new Promise((resolve, reject) => {
9714
9745
  socket.addEventListener("open", () => resolve(), { once: true });
9715
- socket.addEventListener("error", () => reject(new Error("Could not connect to Antigravity's launch inspector.")), { once: true });
9716
- socket.addEventListener("close", () => reject(new Error("Antigravity's launch inspector closed.")), { once: true });
9746
+ socket.addEventListener("error", () => reject(new Error("Could not connect to Desktop app's launch inspector.")), { once: true });
9747
+ socket.addEventListener("close", () => reject(new Error("Desktop app's launch inspector closed.")), { once: true });
9717
9748
  });
9718
9749
  await rpc("Debugger.enable");
9750
+ if (options.startupBreakpoint)
9751
+ await rpc("Debugger.setBreakpointByUrl", options.startupBreakpoint);
9719
9752
  await rpc("Runtime.runIfWaitingForDebugger");
9720
9753
  await Promise.race([paused, delay(Math.max(1, deadline - Date.now()), undefined, { signal }).then(() => {
9721
- throw new Error("Antigravity did not pause before loading its application.");
9754
+ throw new Error("Desktop app did not pause before loading its application.");
9722
9755
  })]);
9723
9756
  const result = await rpc("Runtime.evaluate", {
9724
9757
  expression: `process.getBuiltinModule('module').createRequire(process.cwd() + '/closedrouter-hook.cjs')(${JSON.stringify(hookPath)}); 'closedrouter-hook-loaded'`,
9725
9758
  returnByValue: true
9726
9759
  });
9727
9760
  if (result.exceptionDetails || result.result?.value !== "closedrouter-hook-loaded")
9728
- throw new Error("Could not install Antigravity's request hook. Close the paused app and use --open-only.");
9761
+ throw new Error("Could not install Desktop app's request hook. Close the paused app and use --open-only.");
9729
9762
  await rpc("Debugger.resume");
9763
+ if (options.readyPath) {
9764
+ while (!existsSync10(options.readyPath) && Date.now() < deadline)
9765
+ await delay(25, undefined, { signal });
9766
+ if (!existsSync10(options.readyPath))
9767
+ throw new Error("The desktop app did not enable its local provider runtime.");
9768
+ }
9730
9769
  await rpc("Runtime.evaluate", { expression: "setTimeout(() => process.getBuiltinModule('inspector').close(), 100).unref(); true", returnByValue: true });
9731
9770
  } finally {
9732
9771
  clearTimeout(timer);
@@ -9809,6 +9848,7 @@ function buildDesktopLaunchPlan(tool, executable, profileDir, baseUrl, localKey,
9809
9848
  if (tool === "claude" && baseUrl && localKey && model) {
9810
9849
  userData = join11(profileDir, "user-data-3p");
9811
9850
  const id = randomUUID3();
9851
+ const transportModel = model.startsWith("claude-") || model.startsWith("anthropic/claude-") ? model : "claude-sonnet-4-6";
9812
9852
  const json = (value) => `${JSON.stringify(value, null, 2)}
9813
9853
  `;
9814
9854
  files.push({ path: join11(userData, "claude_desktop_config.json"), contents: json({ deploymentMode: "3p" }) });
@@ -9819,7 +9859,7 @@ function buildDesktopLaunchPlan(tool, executable, profileDir, baseUrl, localKey,
9819
9859
  inferenceGatewayApiKey: localKey,
9820
9860
  inferenceGatewayAuthScheme: "bearer",
9821
9861
  disableDeploymentModeChooser: true,
9822
- inferenceModels: [{ name: model }]
9862
+ inferenceModels: [{ name: transportModel, labelOverride: `ClosedRouter: ${model}` }]
9823
9863
  }) });
9824
9864
  }
9825
9865
  Object.assign(env, appEnv);
@@ -9859,7 +9899,10 @@ function assertClaudeDesktopGatewaySupport(executable) {
9859
9899
  async function runDesktopLaunch(plan) {
9860
9900
  return await new Promise((resolveExit, reject) => {
9861
9901
  const controller = new AbortController;
9862
- const child = spawn3(plan.command, plan.args, { env: plan.env, stdio: "inherit" });
9902
+ const stderrFile = plan.inspectorHook && plan.command !== "/usr/bin/open" ? openSync(plan.inspectorHook.logPath, "a", 384) : undefined;
9903
+ const child = spawn3(plan.command, plan.args, { env: plan.env, stdio: stderrFile === undefined ? "inherit" : ["inherit", "inherit", stderrFile] });
9904
+ if (stderrFile !== undefined)
9905
+ closeSync(stderrFile);
9863
9906
  const interrupt = () => {
9864
9907
  controller.abort();
9865
9908
  child.kill("SIGTERM");
@@ -9880,7 +9923,7 @@ async function runDesktopLaunch(plan) {
9880
9923
  resolveExit(code ?? 1);
9881
9924
  });
9882
9925
  if (plan.inspectorHook) {
9883
- installDesktopInspectorHook(plan.inspectorHook.path, plan.inspectorHook.logPath, controller.signal).catch((error) => {
9926
+ installDesktopInspectorHook(plan.inspectorHook.path, plan.inspectorHook.logPath, controller.signal, plan.inspectorHook).catch((error) => {
9884
9927
  child.kill("SIGTERM");
9885
9928
  cleanup();
9886
9929
  reject(error);
@@ -9895,9 +9938,6 @@ import { chmodSync, existsSync as existsSync12, mkdirSync as mkdirSync3 } from "
9895
9938
  import { homedir as homedir11 } from "node:os";
9896
9939
  import { dirname as dirname2, join as join12 } from "node:path";
9897
9940
  var CURSOR_APPLICATION_STORAGE = "src.vs.platform.reactivestorage.browser.reactiveStorageServiceImpl.persistentStorage.applicationUser";
9898
- function cursorDesktopSupportsModel(model) {
9899
- return !model.startsWith("claude-") && !model.startsWith("gemini-");
9900
- }
9901
9941
  async function writeSqlite(dbPath, sql) {
9902
9942
  let database;
9903
9943
  if ("Bun" in globalThis) {
@@ -9930,35 +9970,41 @@ function defaultCursorDatabase() {
9930
9970
  const root = process.platform === "darwin" ? join12(homedir11(), "Library", "Application Support") : process.platform === "win32" ? process.env.APPDATA || join12(homedir11(), "AppData", "Roaming") : join12(homedir11(), ".config");
9931
9971
  return join12(root, "Cursor", "User", "globalStorage", "state.vscdb");
9932
9972
  }
9933
- async function configureCursorDesktop(profileDir, baseUrl, apiKey, model, sourceDb = defaultCursorDatabase()) {
9934
- if (!cursorDesktopSupportsModel(model))
9935
- throw new Error("Cursor desktop selects a separate provider for Claude/Gemini model names. Choose an OpenAI-compatible custom model or use cr start cursor with the local CLI runtime.");
9973
+ async function configureCursorDesktop(profileDir, baseUrl, apiKey, model, sourceDb = process.env.CLOSEDROUTER_CURSOR_AUTH_DB ?? defaultCursorDatabase()) {
9974
+ const cursorModel = model;
9936
9975
  const dbPath = join12(profileDir, "user-data", "User", "globalStorage", "state.vscdb");
9937
9976
  mkdirSync3(dirname2(dbPath), { recursive: true, mode: 448 });
9938
9977
  const items = new Map;
9978
+ let membershipType;
9939
9979
  if (existsSync12(sourceDb)) {
9940
9980
  const auth = await querySqliteKeyValue(sourceDb, "cursorAuth/");
9941
- for (const name of ["accessToken", "refreshToken", "cachedEmail", "stripeMembershipAuthId", "membershipType", "cachedSignUpType"]) {
9942
- const key = `cursorAuth/${name}`;
9943
- const value = auth.get(key);
9944
- if (value !== undefined)
9945
- items.set(key, value);
9981
+ if (auth.get("cursorAuth/accessToken") && auth.get("cursorAuth/refreshToken")) {
9982
+ for (const name of ["accessToken", "refreshToken", "cachedEmail", "stripeMembershipAuthId", "stripeMembershipType", "stripeSubscriptionStatus", "membershipType", "cachedSignUpType"]) {
9983
+ const key = `cursorAuth/${name}`;
9984
+ const value = auth.get(key);
9985
+ if (value !== undefined)
9986
+ items.set(key, value);
9987
+ }
9988
+ membershipType = auth.get("cursorAuth/stripeMembershipType") || auth.get("cursorAuth/membershipType");
9946
9989
  }
9947
9990
  }
9948
9991
  items.set("cursorAuth/openAIKey", apiKey);
9949
9992
  items.set(CURSOR_APPLICATION_STORAGE, JSON.stringify({
9950
9993
  useOpenAIKey: true,
9951
9994
  openAIBaseUrl: `${baseUrl.replace(/\/+$/, "")}/v1`,
9952
- availableAPIKeyModels: [model],
9995
+ availableAPIKeyModels: [cursorModel],
9953
9996
  havePerformedSettingsServiceMigration: true,
9997
+ localProviderModelIds: [cursorModel],
9998
+ localProviderAgentModelIds: [cursorModel],
9999
+ ...membershipType ? { membershipType } : {},
9954
10000
  aiSettings: {
9955
- userAddedModels: [model],
9956
- modelOverrideEnabled: [model],
10001
+ userAddedModels: [cursorModel],
10002
+ modelOverrideEnabled: [cursorModel],
9957
10003
  modelOverrideDisabled: [],
9958
- modelConfig: Object.fromEntries(["cmd-k", "composer", "background-composer", "plan-execution", "spec", "quick-agent"].map((feature) => [feature, { modelName: model, maxMode: false, selectedModels: [{ modelId: model, parameters: [] }] }]))
10004
+ modelConfig: Object.fromEntries(["cmd-k", "composer", "background-composer", "plan-execution", "spec", "quick-agent"].map((feature) => [feature, { modelName: cursorModel, maxMode: false, selectedModels: [{ modelId: cursorModel, parameters: [] }] }]))
9959
10005
  }
9960
10006
  }));
9961
- items.set("lastSingleModelPreference", JSON.stringify({ composer: model, "cmd-k": model }));
10007
+ items.set("lastSingleModelPreference", JSON.stringify({ composer: cursorModel, "cmd-k": cursorModel }));
9962
10008
  const quote = (value) => `'${value.replace(/'/g, "''")}'`;
9963
10009
  await writeSqlite(dbPath, [
9964
10010
  "BEGIN IMMEDIATE;",
@@ -10170,6 +10216,122 @@ require('node:fs').writeFileSync(${JSON.stringify(join13(plan.profileDir, "hook-
10170
10216
  }
10171
10217
  }
10172
10218
 
10219
+ // src/commands/claude-desktop.ts
10220
+ import { execFileSync as execFileSync2 } from "node:child_process";
10221
+ import { existsSync as existsSync14, readdirSync as readdirSync2 } from "node:fs";
10222
+ import { homedir as homedir13 } from "node:os";
10223
+ import { join as join14 } from "node:path";
10224
+ function findClaudeSharedElectron(sourceApp, home = homedir13()) {
10225
+ const explicit = process.env.CLOSEDROUTER_CLAUDE_ELECTRON || process.env.CLAUDE_DESKTOP_AUTH_ELECTRON;
10226
+ if (explicit && existsSync14(explicit))
10227
+ return explicit;
10228
+ const info = JSON.parse(execFileSync2("/usr/bin/plutil", ["-convert", "json", "-o", "-", join14(sourceApp, "Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/Info.plist")], { encoding: "utf8" }));
10229
+ const major = info.CFBundleVersion.split(".")[0];
10230
+ const root = join14(home, ".antigravity_cockpit", "electron_runtime");
10231
+ if (!existsSync14(root))
10232
+ return;
10233
+ const versions = readdirSync2(root).filter((version) => version.startsWith(`${major}.`)).sort((a, b) => b.localeCompare(a, undefined, { numeric: true }));
10234
+ return versions.map((version) => join14(root, version, `darwin-${process.arch}`, "Electron.app/Contents/MacOS/Electron")).find((path) => existsSync14(path));
10235
+ }
10236
+ function useClaudeSharedElectron(plan, runtime, sourceApp, toolArgs = []) {
10237
+ plan.command = runtime;
10238
+ plan.args = [join14(sourceApp, "Contents/Resources/app.asar"), `--user-data-dir=${join14(plan.profileDir, "user-data-3p")}`, ...toolArgs];
10239
+ }
10240
+ function addClaudeDesktopHook(plan, baseUrl, localKey, sourceApp) {
10241
+ const path = join14(plan.profileDir, "claude-hook.cjs");
10242
+ plan.files.push({ path, contents: `${sourceApp ? `const electron = require('electron');
10243
+ Object.defineProperty(process, 'resourcesPath', {value: ${JSON.stringify(join14(sourceApp, "Contents/Resources"))}});
10244
+ Object.defineProperty(electron.app, 'isPackaged', {get: () => true});
10245
+ electron.app.setPath('userData', ${JSON.stringify(join14(plan.profileDir, "user-data-3p"))});
10246
+ process.env.CLAUDE_USER_DATA_DIR = ${JSON.stringify(join14(plan.profileDir, "user-data-3p"))};
10247
+ ` : ""}const cp = require('node:child_process');
10248
+ const spawn = cp.spawn;
10249
+ cp.spawn = function(command, args, options) {
10250
+ if (typeof command === 'string' && /(?:^|[\\\\/])claude(?:\\.exe)?$/.test(command)) {
10251
+ options = { ...options, env: { ...process.env, ...options?.env,
10252
+ ANTHROPIC_BASE_URL: ${JSON.stringify(baseUrl)}, ANTHROPIC_API_KEY: ${JSON.stringify(localKey)} } };
10253
+ delete options.env.ANTHROPIC_AUTH_TOKEN;
10254
+ }
10255
+ return spawn.call(this, command, args, options);
10256
+ };
10257
+ const { autoUpdater } = require('electron');
10258
+ if (autoUpdater) {
10259
+ autoUpdater.checkForUpdates = () => {};
10260
+ autoUpdater.quitAndInstall = () => {};
10261
+ }
10262
+ require('node:fs').writeFileSync(${JSON.stringify(join14(plan.profileDir, "hook-loaded"))}, 'ready', {mode: 0o600});
10263
+ ` });
10264
+ const logPath = join14(plan.profileDir, "inspector.log");
10265
+ plan.inspectorHook = { path, logPath };
10266
+ plan.args.push("--inspect-brk=127.0.0.1:0");
10267
+ if (plan.command === "/usr/bin/open")
10268
+ plan.args.splice(plan.args.indexOf("--args"), 0, "--stderr", logPath);
10269
+ }
10270
+
10271
+ // src/commands/cursor-desktop-hook.ts
10272
+ import { readFileSync as readFileSync12 } from "node:fs";
10273
+ import { dirname as dirname3, join as join15 } from "node:path";
10274
+ var FLAGS = /([\w$]+)=\{extensionIsDev:[^{}]{1,1000}localMode:(?:!1|false)\}/;
10275
+ function addCursorDesktopLocalHook(plan, app) {
10276
+ const resources = app.endsWith(".app") ? join15(app, "Contents/Resources/app") : join15(dirname3(app), "resources/app");
10277
+ const source = join15(resources, "out/main.js");
10278
+ const renderer = join15(resources, "out/vs/workbench/workbench.desktop.main.js");
10279
+ const mainSource = readFileSync12(source, "utf8");
10280
+ const rendererSource = readFileSync12(renderer, "utf8");
10281
+ const mainMatch = FLAGS.exec(mainSource);
10282
+ const rendererMatch = FLAGS.exec(rendererSource);
10283
+ if (!mainMatch || !rendererMatch || !rendererSource.includes("runLocalAgentInExtensionHost"))
10284
+ throw new Error("This Cursor Desktop build does not include the supported local agent runtime. Update Cursor or use cr start cursor with Cursor Agent Local.");
10285
+ const nativeKey = mainMatch[1];
10286
+ const authMethod = "isAuthenticated(){const e=this.accessToken(),t=this.refreshToken();return this.scheduleTeamPolicyCheck(),!!(e&&t)}";
10287
+ if (!rendererSource.includes(authMethod))
10288
+ throw new Error("This Cursor Desktop build has an unsupported login controller. Update the ClosedRouter launcher before using local provider mode.");
10289
+ const modifiedRenderer = rendererSource.replace(rendererMatch[0], rendererMatch[0].replace(/localMode:(?:!1|false)/, "localMode:!0")).replace(authMethod, `isAuthenticated(){const e=this.accessToken(),t=this.refreshToken();if(!e&&!t&&${rendererMatch[1]}.localMode&&this.byokProviderKeyStorage?.openAIKey()&&this._reactiveStorageService.applicationUserPersistentStorage.openAIBaseUrl)return true;return this.scheduleTeamPolicyCheck(),!!(e&&t)}`);
10290
+ const patched = join15(plan.profileDir, "cursor-local-workbench.js");
10291
+ const hook = join15(plan.profileDir, "cursor-local-hook.cjs");
10292
+ const marker = join15(plan.profileDir, "main-local-mode-enabled");
10293
+ plan.files.push({ path: patched, contents: modifiedRenderer });
10294
+ plan.files.push({ path: hook, contents: `const {app, session} = require('electron');
10295
+ const fs = require('node:fs');
10296
+ function installProtocolHook() {
10297
+ const protocol = session.defaultSession.protocol;
10298
+ const prototype = Object.getPrototypeOf(protocol);
10299
+ const register = prototype.registerFileProtocol;
10300
+ prototype.registerFileProtocol = function(scheme, handler, ...rest) {
10301
+ if (scheme !== 'vscode-file') return register.call(this, scheme, handler, ...rest);
10302
+ return register.call(this, scheme, (request, callback) => handler(request, result => {
10303
+ if (request.url.includes('workbench.desktop.main.js')) fs.writeFileSync(${JSON.stringify(join15(plan.profileDir, "renderer-request.json"))}, JSON.stringify({type: typeof result, path: result?.path}), {mode: 0o600});
10304
+ if (result && typeof result === 'object' && result.path === ${JSON.stringify(renderer)}) {
10305
+ result = {...result, path: ${JSON.stringify(patched)}};
10306
+ fs.writeFileSync(${JSON.stringify(join15(plan.profileDir, "renderer-local-mode-served"))}, 'ready', {mode: 0o600});
10307
+ }
10308
+ callback(result);
10309
+ }), ...rest);
10310
+ };
10311
+ fs.writeFileSync(${JSON.stringify(join15(plan.profileDir, "protocol-hook-loaded"))}, 'ready', {mode: 0o600});
10312
+ }
10313
+ if (app.isReady()) installProtocolHook(); else app.once('ready', installProtocolHook);
10314
+ require('node:fs').writeFileSync(${JSON.stringify(join15(plan.profileDir, "hook-loaded"))}, 'ready', {mode: 0o600});
10315
+ ` });
10316
+ const point = mainSource.slice(0, mainMatch.index + mainMatch[0].length).split(`
10317
+ `);
10318
+ const logPath = join15(plan.profileDir, "inspector.log");
10319
+ plan.inspectorHook = {
10320
+ path: hook,
10321
+ logPath,
10322
+ startupBreakpoint: {
10323
+ urlRegex: source.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "$",
10324
+ lineNumber: point.length - 1,
10325
+ columnNumber: point.at(-1).length,
10326
+ condition: `typeof ${nativeKey} === 'object' && (${nativeKey}.localMode = true, process.getBuiltinModule('fs').writeFileSync(${JSON.stringify(marker)}, 'ready', {mode: 0o600}), false)`
10327
+ },
10328
+ readyPath: marker
10329
+ };
10330
+ plan.args.push("--inspect-brk=127.0.0.1:0");
10331
+ if (plan.command === "/usr/bin/open")
10332
+ plan.args.splice(plan.args.indexOf("--args"), 0, "--stderr", logPath);
10333
+ }
10334
+
10173
10335
  // src/commands/start.ts
10174
10336
  function isGatewayModelAvailable(availableModels, model) {
10175
10337
  return availableModels.includes(model);
@@ -10216,7 +10378,7 @@ function assertNoManagedCodexArgs(args) {
10216
10378
  throw new Error(`Remove ${conflicting}; ClosedRouter manages the Codex profile and model. Put other arguments after --.`);
10217
10379
  }
10218
10380
  }
10219
- function buildLaunchPlan(tool, serverUrl, apiKey, model, toolArgs = [], homeDir = homedir13(), executablePath, agyRouting) {
10381
+ function buildLaunchPlan(tool, serverUrl, apiKey, model, toolArgs = [], homeDir = homedir14(), executablePath, agyRouting) {
10220
10382
  const baseUrl = normalizeServerUrl(serverUrl);
10221
10383
  const env = { ...process.env };
10222
10384
  const command = executablePath || tool;
@@ -10242,9 +10404,9 @@ function buildLaunchPlan(tool, serverUrl, apiKey, model, toolArgs = [], homeDir
10242
10404
  }
10243
10405
  if (tool === "codex") {
10244
10406
  assertNoManagedCodexArgs(toolArgs);
10245
- const codexHome = process.env.CODEX_HOME || join14(homeDir, ".codex");
10407
+ const codexHome = process.env.CODEX_HOME || join16(homeDir, ".codex");
10246
10408
  const profileName = "closedrouter-launch";
10247
- const configPath = join14(codexHome, `${profileName}.config.toml`);
10409
+ const configPath = join16(codexHome, `${profileName}.config.toml`);
10248
10410
  const configLines = [
10249
10411
  `model = ${tomlString(model)}`,
10250
10412
  `model_provider = ${tomlString(profileName)}`,
@@ -10270,7 +10432,7 @@ function buildLaunchPlan(tool, serverUrl, apiKey, model, toolArgs = [], homeDir
10270
10432
  env.GEMINI_API_KEY = apiKey;
10271
10433
  env.GEMINI_MODEL = model;
10272
10434
  env.GOOGLE_GEMINI_BASE_URL = baseUrl;
10273
- const configPath = join14(homeDir, ".closedrouter", "gemini-launch.settings.json");
10435
+ const configPath = join16(homeDir, ".closedrouter", "gemini-launch.settings.json");
10274
10436
  env.GEMINI_CLI_SYSTEM_SETTINGS_PATH = configPath;
10275
10437
  return {
10276
10438
  tool,
@@ -10333,9 +10495,9 @@ function buildLaunchPlan(tool, serverUrl, apiKey, model, toolArgs = [], homeDir
10333
10495
  };
10334
10496
  }
10335
10497
  function writeOwnedConfig(file) {
10336
- mkdirSync4(dirname3(file.path), { recursive: true, mode: 448 });
10498
+ mkdirSync4(dirname4(file.path), { recursive: true, mode: 448 });
10337
10499
  try {
10338
- const existing = readFileSync12(file.path, "utf8");
10500
+ const existing = readFileSync13(file.path, "utf8");
10339
10501
  if (file.path.endsWith(".config.toml") && !existing.includes('name = "ClosedRouter"')) {
10340
10502
  throw new Error(`Refusing to replace an existing Codex profile at ${file.path}.`);
10341
10503
  }
@@ -10350,7 +10512,7 @@ function modelSupportsHarness(model, tool) {
10350
10512
  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"));
10351
10513
  }
10352
10514
  function discoverAgyTransportModel(command, apiKey) {
10353
- const output = execFileSync2(command, ["models"], {
10515
+ const output = execFileSync3(command, ["models"], {
10354
10516
  encoding: "utf8",
10355
10517
  timeout: 1e4,
10356
10518
  stdio: ["ignore", "pipe", "ignore"],
@@ -10363,7 +10525,7 @@ function discoverAgyTransportModel(command, apiKey) {
10363
10525
  }
10364
10526
  function assertAgyCustomModel(command, label, apiKey) {
10365
10527
  try {
10366
- const output = execFileSync2(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" } });
10528
+ const output = execFileSync3(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" } });
10367
10529
  if (output.split(/\r?\n/).some((line) => line.split("\t")[0] === label))
10368
10530
  return;
10369
10531
  } catch {
@@ -10372,9 +10534,9 @@ function assertAgyCustomModel(command, label, apiKey) {
10372
10534
  throw new Error(`This version of agy did not recognize '${label}'. Update agy to use ClosedRouter's accurate model display.`);
10373
10535
  }
10374
10536
  function assertAgyGeminiMode(homeDir) {
10375
- const settingsPath = join14(homeDir, ".gemini", "antigravity-cli", "settings.json");
10537
+ const settingsPath = join16(homeDir, ".gemini", "antigravity-cli", "settings.json");
10376
10538
  try {
10377
- const settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
10539
+ const settings = JSON.parse(readFileSync13(settingsPath, "utf8"));
10378
10540
  if (settings?.modelProvider === "gemini")
10379
10541
  return;
10380
10542
  } catch {}
@@ -10416,13 +10578,17 @@ async function runTool(plan) {
10416
10578
  async function startCommand(options = {}) {
10417
10579
  const groupingOverride = options.groupBy === undefined ? undefined : parseModelGrouping(options.groupBy);
10418
10580
  const { tool, desktop } = await selectTool(options.tool, options.desktop);
10581
+ if (options.desktopInspector && (!desktop || tool !== "claude"))
10582
+ throw new Error("--desktop-inspector is supported for Claude Desktop only.");
10419
10583
  if (options.openOnly && !desktop)
10420
10584
  throw new Error("--open-only requires --desktop.");
10421
10585
  if (desktop && !isDesktopTool(tool))
10422
10586
  throw new Error(`Desktop launch is not supported for ${tool}. Choose codex, claude, cursor, or antigravity.`);
10423
10587
  if (desktop)
10424
10588
  validateDesktopArgs(options.toolArgs || []);
10425
- const executablePath = desktop && isDesktopTool(tool) ? resolveDesktopExecutable(tool, options.executablePath) : tool === "cursor" ? resolveCursorLocalExecutable(options.executablePath) : options.executablePath;
10589
+ let executablePath = desktop && isDesktopTool(tool) ? resolveDesktopExecutable(tool, options.executablePath) : tool === "cursor" ? resolveCursorLocalExecutable(options.executablePath) : options.executablePath;
10590
+ const sourceApp = executablePath;
10591
+ let sharedClaudeElectron;
10426
10592
  if (desktop && isDesktopTool(tool) && options.openOnly) {
10427
10593
  if (options.model || options.key || options.server)
10428
10594
  throw new Error("Open-only desktop launches do not use --model, --key, or --server.");
@@ -10432,8 +10598,16 @@ async function startCommand(options = {}) {
10432
10598
  process.exitCode = await runDesktopLaunch(plan);
10433
10599
  return;
10434
10600
  }
10435
- if (desktop && tool === "claude")
10601
+ if (desktop && tool === "claude") {
10436
10602
  assertClaudeDesktopGatewaySupport(executablePath);
10603
+ if (options.desktopInspector && process.platform === "darwin" && executablePath.endsWith(".app")) {
10604
+ sharedClaudeElectron = findClaudeSharedElectron(executablePath);
10605
+ if (sharedClaudeElectron)
10606
+ M2.info("Using the shared Electron runtime for Claude's startup inspector. The installed app bundle is reused.");
10607
+ else
10608
+ throw new Error("No compatible shared Electron runtime was found. Set CLOSEDROUTER_CLAUDE_ELECTRON to an installed Electron executable or omit --desktop-inspector to use Claude's native gateway mode.");
10609
+ }
10610
+ }
10437
10611
  if (desktop && tool === "agy") {
10438
10612
  assertAntigravityDesktopHookSupport(executablePath);
10439
10613
  await assertAntigravityModsAvailable();
@@ -10444,7 +10618,7 @@ async function startCommand(options = {}) {
10444
10618
  const apiKey = await ensurePlatformKey(tool, client, options);
10445
10619
  client.setApiKey(apiKey);
10446
10620
  const models = await client.listGatewayModels({ preferApiKey: true });
10447
- const eligibleModels = models.filter((item) => modelSupportsHarness(item, tool) && (!desktop || tool !== "cursor" || cursorDesktopSupportsModel(gatewayModelId(item))));
10621
+ const eligibleModels = models.filter((item) => modelSupportsHarness(item, tool));
10448
10622
  const availableModels = [...new Set(eligibleModels.map(gatewayModelId).filter(Boolean))].sort();
10449
10623
  let model = options.model?.trim();
10450
10624
  if (!model) {
@@ -10470,20 +10644,31 @@ async function startCommand(options = {}) {
10470
10644
  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.`);
10471
10645
  }
10472
10646
  if (desktop && tool === "cursor") {
10473
- const plan = buildDesktopLaunchPlan("cursor", executablePath, createDesktopProfile("cursor"), undefined, undefined, undefined, options.toolArgs);
10474
- writeDesktopLaunchPlan(plan);
10475
- await configureCursorDesktop(plan.profileDir, serverUrl, apiKey, model);
10476
- M2.info(`Starting Cursor desktop with ${model}. API key, gateway URL, and model selection are configured. Profile: ${plan.profileDir}.`);
10477
- M2.warn("Cursor's standard editor sends BYOK requests through Cursor's servers, so the gateway URL must be publicly reachable. Cursor account and feature restrictions still apply.");
10478
- process.exitCode = await runDesktopLaunch(plan);
10647
+ const localKey = randomUUID5();
10648
+ const relay = await startLaunchProxy(serverUrl, apiKey, "cursor-desktop", localKey, model);
10649
+ try {
10650
+ const plan = buildDesktopLaunchPlan("cursor", executablePath, createDesktopProfile("cursor"), undefined, undefined, undefined, options.toolArgs);
10651
+ addCursorDesktopLocalHook(plan, executablePath);
10652
+ writeDesktopLaunchPlan(plan);
10653
+ await configureCursorDesktop(plan.profileDir, relay.baseUrl, localKey, model);
10654
+ M2.info(`Starting Cursor desktop local provider mode with ${model}. Model requests use ClosedRouter, without a paid Cursor subscription. Profile: ${plan.profileDir}. Keep cr running until you close this app instance.`);
10655
+ process.exitCode = await runDesktopLaunch(plan);
10656
+ } finally {
10657
+ await relay.close();
10658
+ }
10479
10659
  return;
10480
10660
  }
10481
10661
  if (desktop && (tool === "codex" || tool === "claude" || tool === "agy")) {
10482
10662
  const localKey = randomUUID5();
10483
- const relay = await startLaunchProxy(serverUrl, apiKey, `${tool}-desktop`, localKey);
10663
+ const relay = await startLaunchProxy(serverUrl, apiKey, `${tool}-desktop`, localKey, tool === "claude" ? model : undefined);
10484
10664
  let agyDesktopProxy;
10485
10665
  try {
10486
10666
  const plan = buildDesktopLaunchPlan(tool, executablePath, createDesktopProfile(tool), relay.baseUrl, localKey, model, options.toolArgs);
10667
+ if (options.desktopInspector && tool === "claude" && executablePath.endsWith(".app") && process.platform === "darwin") {
10668
+ if (sharedClaudeElectron)
10669
+ useClaudeSharedElectron(plan, sharedClaudeElectron, sourceApp, options.toolArgs);
10670
+ addClaudeDesktopHook(plan, relay.baseUrl, localKey, sharedClaudeElectron ? sourceApp : undefined);
10671
+ }
10487
10672
  if (tool === "agy") {
10488
10673
  agyDesktopProxy = await startAgyDesktopProxy(relay.baseUrl, localKey, model);
10489
10674
  addAntigravityDesktopHook(plan, agyDesktopProxy.baseUrl);
@@ -10498,15 +10683,15 @@ async function startCommand(options = {}) {
10498
10683
  return;
10499
10684
  }
10500
10685
  if (tool === "agy")
10501
- assertAgyGeminiMode(homedir13());
10502
- const recognizedModel = tool === "agy" ? registerAgyLaunchModel(homedir13(), model, discoverAgyTransportModel(options.executablePath || "agy", apiKey)) : undefined;
10686
+ assertAgyGeminiMode(homedir14());
10687
+ const recognizedModel = tool === "agy" ? registerAgyLaunchModel(homedir14(), model, discoverAgyTransportModel(options.executablePath || "agy", apiKey)) : undefined;
10503
10688
  if (recognizedModel)
10504
10689
  assertAgyCustomModel(options.executablePath || "agy", recognizedModel, apiKey);
10505
10690
  const relay = await startLaunchProxy(serverUrl, apiKey, tool);
10506
10691
  let agyProxy;
10507
10692
  try {
10508
10693
  agyProxy = tool === "agy" ? await startAgyModelProxy(relay.baseUrl, apiKey, model) : undefined;
10509
- const plan = buildLaunchPlan(tool, relay.baseUrl, apiKey, model, options.toolArgs, homedir13(), executablePath, agyProxy && recognizedModel ? { baseUrl: agyProxy.baseUrl, recognizedModel } : undefined);
10694
+ const plan = buildLaunchPlan(tool, relay.baseUrl, apiKey, model, options.toolArgs, homedir14(), executablePath, agyProxy && recognizedModel ? { baseUrl: agyProxy.baseUrl, recognizedModel } : undefined);
10510
10695
  if (plan.configFile)
10511
10696
  writeOwnedConfig(plan.configFile);
10512
10697
  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)}`);
@@ -10563,7 +10748,7 @@ process.on("warning", (warning) => {
10563
10748
  console.warn(warning.name, warning.message);
10564
10749
  });
10565
10750
  var program2 = new Command2;
10566
- var cliVersion = JSON.parse(readFileSync13(new URL("../package.json", import.meta.url), "utf8")).version;
10751
+ var cliVersion = JSON.parse(readFileSync14(new URL("../package.json", import.meta.url), "utf8")).version;
10567
10752
  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) => {
10568
10753
  await onboardCommand({ server: options.server });
10569
10754
  });
@@ -10614,7 +10799,7 @@ program2.command("gateway").alias("daemon").alias("bridge").description("Start t
10614
10799
  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) => {
10615
10800
  await settingsCommand({ modelGrouping: options.modelGrouping });
10616
10801
  });
10617
- 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("-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) => {
10802
+ 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) => {
10618
10803
  await startCommand({
10619
10804
  tool,
10620
10805
  toolArgs: toolArgs || [],
@@ -10624,7 +10809,8 @@ program2.command("start [tool] [tool-args...]").alias("launch").description("Con
10624
10809
  groupBy: options.groupBy,
10625
10810
  executablePath: options.path,
10626
10811
  desktop: options.desktop,
10627
- openOnly: options.openOnly
10812
+ openOnly: options.openOnly,
10813
+ desktopInspector: options.desktopInspector
10628
10814
  });
10629
10815
  });
10630
10816
  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.9",
3
+ "version": "1.1.10",
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": {