@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 +8 -4
- package/dist/bridge/launch-proxy.d.ts +1 -1
- package/dist/bridge/launch-proxy.d.ts.map +1 -1
- package/dist/commands/claude-desktop.d.ts +5 -0
- package/dist/commands/claude-desktop.d.ts.map +1 -0
- package/dist/commands/cursor-desktop-hook.d.ts +3 -0
- package/dist/commands/cursor-desktop-hook.d.ts.map +1 -0
- package/dist/commands/cursor-desktop.d.ts +0 -1
- package/dist/commands/cursor-desktop.d.ts.map +1 -1
- package/dist/commands/desktop-inspector.d.ts +10 -2
- package/dist/commands/desktop-inspector.d.ts.map +1 -1
- package/dist/commands/desktop-launch.d.ts +7 -0
- package/dist/commands/desktop-launch.d.ts.map +1 -1
- package/dist/commands/start.d.ts +1 -0
- package/dist/commands/start.d.ts.map +1 -1
- package/dist/index.js +247 -61
- package/package.json +1 -1
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
|
|
145
|
-
| Cursor |
|
|
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
|
-
|
|
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.
|
|
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;;;;
|
|
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 @@
|
|
|
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;
|
|
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
|
|
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
|
|
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,
|
|
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;
|
|
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"}
|
package/dist/commands/start.d.ts
CHANGED
|
@@ -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;
|
|
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
|
|
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
|
|
8662
|
-
import { mkdirSync as mkdirSync4, readFileSync as
|
|
8663
|
-
import { homedir as
|
|
8664
|
-
import { dirname as
|
|
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
|
-
|
|
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("
|
|
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("
|
|
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
|
|
9716
|
-
socket.addEventListener("close", () => reject(new Error("
|
|
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("
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
9942
|
-
const
|
|
9943
|
-
|
|
9944
|
-
|
|
9945
|
-
|
|
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: [
|
|
9995
|
+
availableAPIKeyModels: [cursorModel],
|
|
9953
9996
|
havePerformedSettingsServiceMigration: true,
|
|
9997
|
+
localProviderModelIds: [cursorModel],
|
|
9998
|
+
localProviderAgentModelIds: [cursorModel],
|
|
9999
|
+
...membershipType ? { membershipType } : {},
|
|
9954
10000
|
aiSettings: {
|
|
9955
|
-
userAddedModels: [
|
|
9956
|
-
modelOverrideEnabled: [
|
|
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:
|
|
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:
|
|
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 =
|
|
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 ||
|
|
10407
|
+
const codexHome = process.env.CODEX_HOME || join16(homeDir, ".codex");
|
|
10246
10408
|
const profileName = "closedrouter-launch";
|
|
10247
|
-
const configPath =
|
|
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 =
|
|
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(
|
|
10498
|
+
mkdirSync4(dirname4(file.path), { recursive: true, mode: 448 });
|
|
10337
10499
|
try {
|
|
10338
|
-
const existing =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
10537
|
+
const settingsPath = join16(homeDir, ".gemini", "antigravity-cli", "settings.json");
|
|
10376
10538
|
try {
|
|
10377
|
-
const settings = JSON.parse(
|
|
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
|
-
|
|
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)
|
|
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
|
|
10474
|
-
|
|
10475
|
-
|
|
10476
|
-
|
|
10477
|
-
|
|
10478
|
-
|
|
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(
|
|
10502
|
-
const recognizedModel = tool === "agy" ? registerAgyLaunchModel(
|
|
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,
|
|
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(
|
|
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