@closedrouter/cli 1.1.16 → 1.1.17
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 +1 -1
- package/dist/commands/cursor-desktop-account.d.ts +6 -0
- package/dist/commands/cursor-desktop-account.d.ts.map +1 -0
- package/dist/commands/cursor-desktop-hook.d.ts +2 -1
- package/dist/commands/cursor-desktop-hook.d.ts.map +1 -1
- package/dist/commands/cursor-desktop.d.ts +1 -0
- package/dist/commands/cursor-desktop.d.ts.map +1 -1
- package/dist/index.js +156 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -149,7 +149,7 @@ Desktop launches follow the isolation and gateway configuration in `reference_re
|
|
|
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
151
|
|
|
152
|
-
Cursor's startup hook enables local provider mode in memory and serves app-owned scripts for both desktop views. The installed app files and signature are preserved. Its picker lists all tool-capable pool models, and changing the selection changes the routed model. 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.
|
|
152
|
+
Cursor's startup hook enables local provider mode in memory and serves app-owned scripts for both desktop views. The installed app files and signature are preserved. Its picker lists all tool-capable pool models, and changing the selection changes the routed model. 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 and account UI. Without a native login, the account area shows the ClosedRouter name and colored logo; its menu shows the email and a link to the ClosedRouter account. Set `CLOSEDROUTER_CURSOR_AUTH_DB` to a different SQLite source, or a nonexistent path to test the no-login case.
|
|
153
153
|
|
|
154
154
|
On macOS, Claude uses a compatible Electron runtime installed by Cockpit, or the executable supplied through `CLOSEDROUTER_CLAUDE_ELECTRON`, when one is available. The launcher shares its immutable framework files by hard link and creates private copies of the small executable stubs before signing them with the virtualization entitlement Cowork requires. This added about 860 KB in the tested runtime. The installed Claude app and original Electron runtime are not modified. LaunchServices starts the owned runtime with Claude's original archive and resources. The startup inspector binds to loopback and closes after injection. The runtime and ClosedRouter config directory must be on the same filesystem. Without a compatible runtime, Claude uses its native gateway configuration and text model labels; `--desktop-inspector` requires a compatible runtime.
|
|
155
155
|
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { DesktopAccountIdentity } from "./desktop-account";
|
|
2
|
+
/** Provider identity belongs to this isolated launch, not to Cursor's OAuth
|
|
3
|
+
* store. Use the native account area without manufacturing a vendor login. */
|
|
4
|
+
export declare function installCursorProviderAccountUi(account: DesktopAccountIdentity, artwork: string): void;
|
|
5
|
+
export declare function cursorProviderAccountHook(account: DesktopAccountIdentity): string;
|
|
6
|
+
//# sourceMappingURL=cursor-desktop-account.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cursor-desktop-account.d.ts","sourceRoot":"","sources":["../../src/commands/cursor-desktop-account.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAGhE;8EAC8E;AAC9E,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,sBAAsB,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAuDrG;AAED,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,sBAAsB,GAAG,MAAM,CAKjF"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { DesktopLaunchPlan } from "./desktop-launch";
|
|
2
2
|
import type { LaunchCatalog } from "./launch-catalog";
|
|
3
|
-
|
|
3
|
+
import type { DesktopAccountIdentity } from "./desktop-account";
|
|
4
|
+
export declare function addCursorDesktopLocalHook(plan: DesktopLaunchPlan, app: string, catalog?: LaunchCatalog, account?: DesktopAccountIdentity): void;
|
|
4
5
|
//# sourceMappingURL=cursor-desktop-hook.d.ts.map
|
|
@@ -1 +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;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;
|
|
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;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEtD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAKhE,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,iBAAiB,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,EAAE,OAAO,CAAC,EAAE,sBAAsB,GAAG,IAAI,CA2E/I"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export declare const CURSOR_APPLICATION_STORAGE = "src.vs.platform.reactivestorage.browser.reactiveStorageServiceImpl.persistentStorage.applicationUser";
|
|
2
|
+
export declare function cursorNativeAccountExists(sourceDb?: string): Promise<boolean>;
|
|
2
3
|
export declare function configureCursorDesktop(profileDir: string, baseUrl: string, apiKey: string, model: string, sourceDb?: string, models?: string[]): Promise<string>;
|
|
3
4
|
//# 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;AAyCjJ,wBAAsB,sBAAsB,CAC1C,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAClE,QAAQ,SAAqE,EAC7E,MAAM,GAAE,MAAM,EAAY,GACzB,OAAO,CAAC,MAAM,CAAC,CA2CjB"}
|
|
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,yBAAyB,CAAC,QAAQ,SAAqE,GAAG,OAAO,CAAC,OAAO,CAAC,CAI/I;AAED,wBAAsB,sBAAsB,CAC1C,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAClE,QAAQ,SAAqE,EAC7E,MAAM,GAAE,MAAM,EAAY,GACzB,OAAO,CAAC,MAAM,CAAC,CA2CjB"}
|
package/dist/index.js
CHANGED
|
@@ -3920,7 +3920,7 @@ var require_websocket = __commonJS(function(exports, module) {
|
|
|
3920
3920
|
NOOP
|
|
3921
3921
|
} = require_constants();
|
|
3922
3922
|
var {
|
|
3923
|
-
EventTarget: { addEventListener, removeEventListener }
|
|
3923
|
+
EventTarget: { addEventListener: addEventListener2, removeEventListener }
|
|
3924
3924
|
} = require_event_target();
|
|
3925
3925
|
var { format, parse } = require_extension();
|
|
3926
3926
|
var { toBuffer } = require_buffer_util();
|
|
@@ -4262,7 +4262,7 @@ var require_websocket = __commonJS(function(exports, module) {
|
|
|
4262
4262
|
}
|
|
4263
4263
|
});
|
|
4264
4264
|
});
|
|
4265
|
-
WebSocket.prototype.addEventListener =
|
|
4265
|
+
WebSocket.prototype.addEventListener = addEventListener2;
|
|
4266
4266
|
WebSocket.prototype.removeEventListener = removeEventListener;
|
|
4267
4267
|
module.exports = WebSocket;
|
|
4268
4268
|
function initAsClient(websocket, address, protocols, options) {
|
|
@@ -10121,6 +10121,12 @@ function defaultCursorDatabase() {
|
|
|
10121
10121
|
const root = process.platform === "darwin" ? join12(homedir11(), "Library", "Application Support") : process.platform === "win32" ? process.env.APPDATA || join12(homedir11(), "AppData", "Roaming") : join12(homedir11(), ".config");
|
|
10122
10122
|
return join12(root, "Cursor", "User", "globalStorage", "state.vscdb");
|
|
10123
10123
|
}
|
|
10124
|
+
async function cursorNativeAccountExists(sourceDb = process.env.CLOSEDROUTER_CURSOR_AUTH_DB ?? defaultCursorDatabase()) {
|
|
10125
|
+
if (!existsSync12(sourceDb))
|
|
10126
|
+
return false;
|
|
10127
|
+
const auth = await querySqliteKeyValue(sourceDb, "cursorAuth/");
|
|
10128
|
+
return Boolean(auth.get("cursorAuth/accessToken") && auth.get("cursorAuth/refreshToken"));
|
|
10129
|
+
}
|
|
10124
10130
|
async function configureCursorDesktop(profileDir, baseUrl, apiKey, model, sourceDb = process.env.CLOSEDROUTER_CURSOR_AUTH_DB ?? defaultCursorDatabase(), models = [model]) {
|
|
10125
10131
|
const cursorModel = model;
|
|
10126
10132
|
const catalog = [...new Set([model, ...models])];
|
|
@@ -10850,8 +10856,152 @@ if (typeof moduleHooks.registerHooks === 'function') moduleHooks.registerHooks({
|
|
|
10850
10856
|
// src/commands/cursor-desktop-hook.ts
|
|
10851
10857
|
import { existsSync as existsSync15, readFileSync as readFileSync13 } from "node:fs";
|
|
10852
10858
|
import { dirname as dirname4, join as join15 } from "node:path";
|
|
10859
|
+
|
|
10860
|
+
// src/commands/cursor-desktop-account.ts
|
|
10861
|
+
function installCursorProviderAccountUi(account, artwork) {
|
|
10862
|
+
if (document.querySelector("[data-closedrouter-cursor-account-hook]"))
|
|
10863
|
+
return;
|
|
10864
|
+
const marker = document.createElement("meta");
|
|
10865
|
+
marker.dataset.closedrouterCursorAccountHook = "true";
|
|
10866
|
+
document.head.append(marker);
|
|
10867
|
+
document.documentElement.dataset.closedrouterAccount = "true";
|
|
10868
|
+
const style = document.createElement("style");
|
|
10869
|
+
style.textContent = "[data-closedrouter-replaced-account]{display:none!important}";
|
|
10870
|
+
document.head.append(style);
|
|
10871
|
+
const image = (size) => {
|
|
10872
|
+
const logo = document.createElement("img");
|
|
10873
|
+
logo.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(artwork)}`;
|
|
10874
|
+
logo.alt = "ClosedRouter";
|
|
10875
|
+
logo.width = size;
|
|
10876
|
+
logo.height = size;
|
|
10877
|
+
logo.style.flex = `0 0 ${size}px`;
|
|
10878
|
+
return logo;
|
|
10879
|
+
};
|
|
10880
|
+
const button = document.createElement("button");
|
|
10881
|
+
button.type = "button";
|
|
10882
|
+
button.dataset.closedrouterCursorAccount = "true";
|
|
10883
|
+
button.setAttribute("aria-label", `ClosedRouter account: ${account.name}`);
|
|
10884
|
+
button.setAttribute("aria-haspopup", "menu");
|
|
10885
|
+
button.setAttribute("aria-expanded", "false");
|
|
10886
|
+
button.style.cssText = "display:flex;align-items:center;gap:8px;width:100%;padding:8px;border:0;border-radius:6px;background:transparent;color:inherit;font:inherit;text-align:left;cursor:pointer";
|
|
10887
|
+
const mark = image(24);
|
|
10888
|
+
mark.dataset.closedrouterAccountLogo = "true";
|
|
10889
|
+
const label = document.createElement("span");
|
|
10890
|
+
label.style.cssText = "flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap";
|
|
10891
|
+
label.textContent = account.name;
|
|
10892
|
+
const provider = document.createElement("span");
|
|
10893
|
+
provider.textContent = "ClosedRouter";
|
|
10894
|
+
provider.style.cssText = "font-size:11px;opacity:.7";
|
|
10895
|
+
button.append(mark, label, provider);
|
|
10896
|
+
let menu;
|
|
10897
|
+
const close = (focus = false) => {
|
|
10898
|
+
menu?.remove();
|
|
10899
|
+
menu = undefined;
|
|
10900
|
+
button.setAttribute("aria-expanded", "false");
|
|
10901
|
+
button.removeAttribute("aria-controls");
|
|
10902
|
+
if (focus)
|
|
10903
|
+
button.focus();
|
|
10904
|
+
};
|
|
10905
|
+
const open = () => {
|
|
10906
|
+
if (menu) {
|
|
10907
|
+
close();
|
|
10908
|
+
return;
|
|
10909
|
+
}
|
|
10910
|
+
const bounds = button.getBoundingClientRect();
|
|
10911
|
+
menu = document.createElement("div");
|
|
10912
|
+
menu.id = "closedrouter-cursor-account-menu";
|
|
10913
|
+
menu.setAttribute("role", "menu");
|
|
10914
|
+
menu.setAttribute("aria-label", "ClosedRouter account");
|
|
10915
|
+
menu.style.cssText = `position:fixed;z-index:10000;left:${Math.max(8, Math.min(bounds.left, innerWidth - 300))}px;bottom:${Math.max(8, innerHeight - bounds.top + 6)}px;width:280px;max-width:calc(100vw - 16px);padding:8px;border:1px solid var(--vscode-menu-border,rgba(128,128,128,.25));border-radius:8px;background:var(--vscode-menu-background,var(--vscode-sideBar-background,#202020));color:var(--vscode-menu-foreground,var(--vscode-foreground,inherit));box-shadow:0 6px 20px #0004;font:inherit`;
|
|
10916
|
+
const card = document.createElement("section");
|
|
10917
|
+
card.id = "closedrouter-provider-account";
|
|
10918
|
+
card.setAttribute("aria-label", "ClosedRouter account");
|
|
10919
|
+
card.style.cssText = "display:flex;gap:12px;align-items:center;padding:12px 8px;margin:4px 0;border-bottom:1px solid color-mix(in srgb,currentColor 15%,transparent)";
|
|
10920
|
+
const details = document.createElement("div");
|
|
10921
|
+
details.style.cssText = "min-width:0;overflow-wrap:anywhere";
|
|
10922
|
+
for (const text of [account.name, account.email, "Connected through ClosedRouter"])
|
|
10923
|
+
if (text) {
|
|
10924
|
+
const line = document.createElement("div");
|
|
10925
|
+
line.textContent = text;
|
|
10926
|
+
details.append(line);
|
|
10927
|
+
}
|
|
10928
|
+
card.append(image(28), details);
|
|
10929
|
+
const link = document.createElement("a");
|
|
10930
|
+
link.href = `${account.serverOrigin}/settings`;
|
|
10931
|
+
link.target = "_blank";
|
|
10932
|
+
link.rel = "noopener noreferrer";
|
|
10933
|
+
link.setAttribute("role", "menuitem");
|
|
10934
|
+
link.textContent = "Open ClosedRouter account";
|
|
10935
|
+
link.style.cssText = "display:block;padding:8px;color:inherit;text-decoration:none;border-radius:4px";
|
|
10936
|
+
menu.append(card, link);
|
|
10937
|
+
document.body.append(menu);
|
|
10938
|
+
button.setAttribute("aria-controls", menu.id);
|
|
10939
|
+
button.setAttribute("aria-expanded", "true");
|
|
10940
|
+
link.focus();
|
|
10941
|
+
};
|
|
10942
|
+
button.addEventListener("click", (event) => {
|
|
10943
|
+
event.preventDefault();
|
|
10944
|
+
event.stopPropagation();
|
|
10945
|
+
open();
|
|
10946
|
+
});
|
|
10947
|
+
button.addEventListener("keydown", (event) => {
|
|
10948
|
+
if (event.key === "ArrowDown") {
|
|
10949
|
+
event.preventDefault();
|
|
10950
|
+
event.stopPropagation();
|
|
10951
|
+
if (!menu)
|
|
10952
|
+
open();
|
|
10953
|
+
}
|
|
10954
|
+
});
|
|
10955
|
+
document.addEventListener("pointerdown", (event) => {
|
|
10956
|
+
if (menu && !menu.contains(event.target) && !button.contains(event.target))
|
|
10957
|
+
close();
|
|
10958
|
+
});
|
|
10959
|
+
document.addEventListener("keydown", (event) => {
|
|
10960
|
+
if (menu && event.key === "Escape") {
|
|
10961
|
+
event.preventDefault();
|
|
10962
|
+
event.stopPropagation();
|
|
10963
|
+
close(true);
|
|
10964
|
+
}
|
|
10965
|
+
});
|
|
10966
|
+
addEventListener("resize", () => close());
|
|
10967
|
+
const paint = () => {
|
|
10968
|
+
const upgrade = document.querySelector('[aria-label="Upgrade to a Pro account"]');
|
|
10969
|
+
if (upgrade)
|
|
10970
|
+
upgrade.dataset.closedrouterReplacedAccount = "true";
|
|
10971
|
+
const target = document.querySelector('[aria-label="Loading account information"],[aria-label="Accounts"]') || upgrade || [...document.querySelectorAll("button,[role=button]")].find((node) => /^(Sign in|Sign In|Accounts)$/.test(node.innerText.trim()));
|
|
10972
|
+
if (!target?.parentElement)
|
|
10973
|
+
return;
|
|
10974
|
+
if (button.isConnected && button.nextElementSibling === target)
|
|
10975
|
+
return;
|
|
10976
|
+
close();
|
|
10977
|
+
const compact = target.parentElement.getBoundingClientRect().width < 70;
|
|
10978
|
+
label.style.display = provider.style.display = compact ? "none" : "";
|
|
10979
|
+
button.style.padding = compact ? "4px" : "8px";
|
|
10980
|
+
target.dataset.closedrouterReplacedAccount = "true";
|
|
10981
|
+
target.parentElement.insertBefore(button, target);
|
|
10982
|
+
};
|
|
10983
|
+
let queued = false;
|
|
10984
|
+
new MutationObserver(() => {
|
|
10985
|
+
if (!queued) {
|
|
10986
|
+
queued = true;
|
|
10987
|
+
queueMicrotask(() => {
|
|
10988
|
+
queued = false;
|
|
10989
|
+
paint();
|
|
10990
|
+
});
|
|
10991
|
+
}
|
|
10992
|
+
}).observe(document.documentElement, { subtree: true, childList: true });
|
|
10993
|
+
paint();
|
|
10994
|
+
}
|
|
10995
|
+
function cursorProviderAccountHook(account) {
|
|
10996
|
+
const script = `(${installCursorProviderAccountUi.toString()})(${JSON.stringify(account)},${JSON.stringify(CLOSEDROUTER_LOGO)});`;
|
|
10997
|
+
return `require('electron').app.on('web-contents-created',(_event,contents)=>{
|
|
10998
|
+
contents.on('did-finish-load',()=>contents.executeJavaScript(${JSON.stringify(script)}).catch(()=>{}));
|
|
10999
|
+
});`;
|
|
11000
|
+
}
|
|
11001
|
+
|
|
11002
|
+
// src/commands/cursor-desktop-hook.ts
|
|
10853
11003
|
var FLAGS = /([\w$]+)=\{extensionIsDev:[^{}]{1,1000}localMode:(?:!1|false)\}/;
|
|
10854
|
-
function addCursorDesktopLocalHook(plan, app, catalog) {
|
|
11004
|
+
function addCursorDesktopLocalHook(plan, app, catalog, account) {
|
|
10855
11005
|
const resources = app.endsWith(".app") ? join15(app, "Contents/Resources/app") : join15(dirname4(app), "resources/app");
|
|
10856
11006
|
const source = join15(resources, "out/main.js");
|
|
10857
11007
|
const mainSource = readFileSync13(source, "utf8");
|
|
@@ -10930,7 +11080,7 @@ function installProtocolHook() {
|
|
|
10930
11080
|
}
|
|
10931
11081
|
if (app.isReady()) installProtocolHook(); else app.once('ready', installProtocolHook);
|
|
10932
11082
|
require('node:fs').writeFileSync(${JSON.stringify(join15(plan.profileDir, "hook-loaded"))}, 'ready', {mode: 0o600});
|
|
10933
|
-
` + (catalog ? modelBrandingHook(catalog.models) : "") });
|
|
11083
|
+
` + (catalog ? modelBrandingHook(catalog.models) : "") + (account ? cursorProviderAccountHook(account) : "") });
|
|
10934
11084
|
const point = mainSource.slice(0, mainMatch.index + mainMatch[0].length).split(`
|
|
10935
11085
|
`);
|
|
10936
11086
|
const logPath = join15(plan.profileDir, "inspector.log");
|
|
@@ -11301,9 +11451,9 @@ async function startCommand(options = {}) {
|
|
|
11301
11451
|
const relay = await startLaunchProxy(serverUrl, apiKey, "cursor-desktop", localKey, catalog);
|
|
11302
11452
|
try {
|
|
11303
11453
|
const plan = buildDesktopLaunchPlan("cursor", executablePath, createDesktopProfile("cursor"), undefined, undefined, undefined, options.toolArgs);
|
|
11304
|
-
|
|
11454
|
+
const cursorDb = await configureCursorDesktop(plan.profileDir, relay.baseUrl, localKey, model, undefined, catalog.models);
|
|
11455
|
+
addCursorDesktopLocalHook(plan, executablePath, catalog, await cursorNativeAccountExists(cursorDb) ? undefined : account);
|
|
11305
11456
|
writeDesktopLaunchPlan(plan);
|
|
11306
|
-
await configureCursorDesktop(plan.profileDir, relay.baseUrl, localKey, model, undefined, catalog.models);
|
|
11307
11457
|
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.`);
|
|
11308
11458
|
process.exitCode = await runDesktopLaunch(plan);
|
|
11309
11459
|
} finally {
|
package/package.json
CHANGED