@closedrouter/cli 1.0.1 → 1.1.1
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 -6
- package/dist/browser-login.d.ts +7 -0
- package/dist/browser-login.d.ts.map +1 -0
- package/dist/commands/login.d.ts +1 -0
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/onboard.d.ts.map +1 -1
- package/dist/commands/start.d.ts.map +1 -1
- package/dist/index.js +151 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -45,8 +45,8 @@ cr
|
|
|
45
45
|
|
|
46
46
|
### `closedrouter` / `closedrouter onboard`
|
|
47
47
|
Launches the full interactive onboarding wizard:
|
|
48
|
-
1. Connects to
|
|
49
|
-
2.
|
|
48
|
+
1. Connects to the hosted ClosedRouter server (use `--server` for a self-hosted install)
|
|
49
|
+
2. Opens your browser to sign in or create an account and approve CLI access
|
|
50
50
|
3. Scans your computer for local AI subscriptions and API keys
|
|
51
51
|
4. Presents an interactive multi-select checklist of discovered accounts
|
|
52
52
|
5. Configures sharing policies (Shared Pool vs Private)
|
|
@@ -116,10 +116,12 @@ closedrouter keys create "MacBook Pro M3"
|
|
|
116
116
|
```
|
|
117
117
|
|
|
118
118
|
### `closedrouter login` / `closedrouter logout`
|
|
119
|
-
|
|
119
|
+
`cr login` opens the web app. Sign in or create an account, then approve CLI access. The CLI receives a separate 14-day session through a one-time local callback. Credentials are stored in `~/.closedrouter/config.json` with owner-only permissions. Use `--no-browser` for terminal email, signup, or token login.
|
|
120
120
|
```bash
|
|
121
|
-
|
|
122
|
-
|
|
121
|
+
cr login
|
|
122
|
+
cr login --server http://localhost:8000 # local self-hosted install
|
|
123
|
+
cr login --no-browser # terminal fallback
|
|
124
|
+
cr logout
|
|
123
125
|
```
|
|
124
126
|
|
|
125
127
|
### `cr start` / `cr launch`
|
|
@@ -140,7 +142,7 @@ cr start opencode --model gpt-4o-mini
|
|
|
140
142
|
cr start claude --model claude-sonnet-4-6 -- --resume
|
|
141
143
|
```
|
|
142
144
|
|
|
143
|
-
Claude Code sends an explicit `claude_oauth` provider choice to ClosedRouter so its requests use the local Claude subscription bridge. Codex gets a separate `~/.codex/closedrouter-launch.config.toml` profile using the Responses API.
|
|
145
|
+
Claude Code sends an explicit `claude_oauth` provider choice to ClosedRouter so its requests use the local Claude subscription bridge. Codex gets a separate `~/.codex/closedrouter-launch.config.toml` profile using the Responses API. Gemini gets an app-owned authentication settings file under `~/.closedrouter/`, and OpenCode receives inline launch configuration. The launchers do not change the tools' main configuration files.
|
|
144
146
|
|
|
145
147
|
Cursor SDK keys can also be connected in the web account dialog or imported from `CURSOR_API_KEY`. ClosedRouter discovers models using Cursor's SDK and sends text-only, tools-disabled SDK runs through its normal gateway and telemetry path. This credential type is separate from Cursor OAuth tokens imported from the editor.
|
|
146
148
|
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Browser sign-in using a loopback callback and PKCE. */
|
|
2
|
+
import type { AuthResponse } from "./client";
|
|
3
|
+
export declare function browserLogin(serverUrl: string, options?: {
|
|
4
|
+
open?: boolean;
|
|
5
|
+
timeoutMs?: number;
|
|
6
|
+
}): Promise<AuthResponse>;
|
|
7
|
+
//# sourceMappingURL=browser-login.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-login.d.ts","sourceRoot":"","sources":["../src/browser-login.ts"],"names":[],"mappings":"AAAA,0DAA0D;AAM1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAc7C,wBAAsB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,GAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,OAAO,CAAC,YAAY,CAAC,CAuEjI"}
|
package/dist/commands/login.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAAA;;GAEG;
|
|
1
|
+
{"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAAA;;GAEG;AAQH,wBAAsB,YAAY,CAAC,OAAO,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAuGjG"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"onboard.d.ts","sourceRoot":"","sources":["../../src/commands/onboard.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"onboard.d.ts","sourceRoot":"","sources":["../../src/commands/onboard.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAYH,wBAAsB,cAAc,CAAC,OAAO,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAuWjH"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../../src/commands/start.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAWH,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,GAAG,UAAU,CAAC;AAC7E,KAAK,OAAO,GAAG,SAAS,CAAC;AAEzB,MAAM,WAAW,mBAAmB;IAClC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,UAAU,UAAU;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB;AAED,wBAAgB,uBAAuB,CAAC,eAAe,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAEzF;AA6BD,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,GACtB,UAAU,
|
|
1
|
+
{"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../../src/commands/start.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAWH,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,GAAG,UAAU,CAAC;AAC7E,KAAK,OAAO,GAAG,SAAS,CAAC;AAEzB,MAAM,WAAW,mBAAmB;IAClC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,UAAU,UAAU;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB;AAED,wBAAgB,uBAAuB,CAAC,eAAe,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAEzF;AA6BD,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,GACtB,UAAU,CAqGZ;AA6FD,wBAAsB,YAAY,CAAC,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsCnF"}
|
package/dist/index.js
CHANGED
|
@@ -6224,7 +6224,7 @@ class ClosedRouterClient {
|
|
|
6224
6224
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
6225
6225
|
import { homedir } from "node:os";
|
|
6226
6226
|
import { join } from "node:path";
|
|
6227
|
-
var DEFAULT_SERVER_URL = "
|
|
6227
|
+
var DEFAULT_SERVER_URL = "https://closedrouter.213.183.59.186.sslip.io";
|
|
6228
6228
|
function getConfigDir() {
|
|
6229
6229
|
return process.env.CLOSEDROUTER_CONFIG_DIR || join(homedir(), ".closedrouter");
|
|
6230
6230
|
}
|
|
@@ -8006,6 +8006,100 @@ Create one via:
|
|
|
8006
8006
|
|
|
8007
8007
|
// src/commands/login.ts
|
|
8008
8008
|
var import_picocolors7 = __toESM(require_picocolors(), 1);
|
|
8009
|
+
|
|
8010
|
+
// src/browser-login.ts
|
|
8011
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
8012
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
8013
|
+
import { createServer } from "node:http";
|
|
8014
|
+
var encode = (bytes) => bytes.toString("base64url");
|
|
8015
|
+
function openBrowser(url) {
|
|
8016
|
+
const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
|
|
8017
|
+
const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
8018
|
+
const child = spawn2(command, args, { stdio: "ignore", detached: true });
|
|
8019
|
+
child.on("error", () => {
|
|
8020
|
+
console.log("Could not open your browser automatically. Open the URL above manually.");
|
|
8021
|
+
});
|
|
8022
|
+
child.unref();
|
|
8023
|
+
}
|
|
8024
|
+
async function browserLogin(serverUrl, options = {}) {
|
|
8025
|
+
const baseUrl = serverUrl.replace(/\/+$/, "");
|
|
8026
|
+
const configResponse = await fetch(`${baseUrl}/api/v1/cli-auth/config`, { signal: AbortSignal.timeout(1e4) });
|
|
8027
|
+
if (!configResponse.ok)
|
|
8028
|
+
throw new Error(`Browser login is unavailable (HTTP ${configResponse.status}).`);
|
|
8029
|
+
const { browser_url: browserUrl } = await configResponse.json();
|
|
8030
|
+
if (!browserUrl || !/^https?:\/\//.test(browserUrl))
|
|
8031
|
+
throw new Error("Server returned an invalid browser URL.");
|
|
8032
|
+
const verifier = encode(randomBytes(32));
|
|
8033
|
+
const challenge = encode(createHash("sha256").update(verifier).digest());
|
|
8034
|
+
const state = encode(randomBytes(32));
|
|
8035
|
+
let timer;
|
|
8036
|
+
let settled = false;
|
|
8037
|
+
let resolveResult;
|
|
8038
|
+
let rejectResult;
|
|
8039
|
+
const result = new Promise((resolve, reject) => {
|
|
8040
|
+
resolveResult = resolve;
|
|
8041
|
+
rejectResult = reject;
|
|
8042
|
+
});
|
|
8043
|
+
const server = createServer(async (request, response) => {
|
|
8044
|
+
const url = new URL(request.url ?? "/", "http://127.0.0.1");
|
|
8045
|
+
response.setHeader("Cache-Control", "no-store");
|
|
8046
|
+
response.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
8047
|
+
response.setHeader("Referrer-Policy", "no-referrer");
|
|
8048
|
+
if (request.method !== "GET" || url.pathname !== "/callback" || url.searchParams.get("state") !== state || !url.searchParams.get("code") || settled) {
|
|
8049
|
+
response.writeHead(400).end("Invalid CLI login callback.");
|
|
8050
|
+
return;
|
|
8051
|
+
}
|
|
8052
|
+
settled = true;
|
|
8053
|
+
try {
|
|
8054
|
+
const exchange = await fetch(`${baseUrl}/api/v1/cli-auth/exchange`, {
|
|
8055
|
+
method: "POST",
|
|
8056
|
+
headers: { "Content-Type": "application/json" },
|
|
8057
|
+
body: JSON.stringify({ code: url.searchParams.get("code"), code_verifier: verifier }),
|
|
8058
|
+
signal: AbortSignal.timeout(1e4)
|
|
8059
|
+
});
|
|
8060
|
+
if (!exchange.ok) {
|
|
8061
|
+
const body = await exchange.json().catch(() => ({}));
|
|
8062
|
+
throw new Error(body.error?.message || `Login exchange failed (HTTP ${exchange.status}).`);
|
|
8063
|
+
}
|
|
8064
|
+
const credentials = await exchange.json();
|
|
8065
|
+
if (!credentials.token || !credentials.user?.email)
|
|
8066
|
+
throw new Error("Server returned incomplete credentials.");
|
|
8067
|
+
response.writeHead(200).end("<script>history.replaceState(null, '', '/');</script><h1>ClosedRouter CLI connected</h1><p>You can return to your terminal.</p>");
|
|
8068
|
+
resolveResult(credentials);
|
|
8069
|
+
} catch (error) {
|
|
8070
|
+
response.writeHead(400).end("CLI login failed. Return to your terminal and try again.");
|
|
8071
|
+
rejectResult(error instanceof Error ? error : new Error(String(error)));
|
|
8072
|
+
}
|
|
8073
|
+
});
|
|
8074
|
+
server.on("error", rejectResult);
|
|
8075
|
+
try {
|
|
8076
|
+
await new Promise((resolve, reject) => {
|
|
8077
|
+
server.listen(0, "127.0.0.1", resolve);
|
|
8078
|
+
server.once("error", reject);
|
|
8079
|
+
});
|
|
8080
|
+
const port = server.address().port;
|
|
8081
|
+
const url = new URL("/cli-login", browserUrl);
|
|
8082
|
+
url.searchParams.set("callback", `http://127.0.0.1:${port}/callback`);
|
|
8083
|
+
url.searchParams.set("state", state);
|
|
8084
|
+
url.searchParams.set("challenge", challenge);
|
|
8085
|
+
console.log(`Open this URL to sign in:
|
|
8086
|
+
${url.toString()}
|
|
8087
|
+
`);
|
|
8088
|
+
if (options.open !== false)
|
|
8089
|
+
openBrowser(url.toString());
|
|
8090
|
+
timer = setTimeout(() => {
|
|
8091
|
+
if (!settled)
|
|
8092
|
+
rejectResult(new Error("Browser login timed out. Run cr login again."));
|
|
8093
|
+
}, options.timeoutMs ?? 300000);
|
|
8094
|
+
return await result;
|
|
8095
|
+
} finally {
|
|
8096
|
+
if (timer)
|
|
8097
|
+
clearTimeout(timer);
|
|
8098
|
+
server.close();
|
|
8099
|
+
}
|
|
8100
|
+
}
|
|
8101
|
+
|
|
8102
|
+
// src/commands/login.ts
|
|
8009
8103
|
async function loginCommand(options) {
|
|
8010
8104
|
Ie(import_picocolors7.default.bgCyan(import_picocolors7.default.black(" ClosedRouter Login ")));
|
|
8011
8105
|
const config = loadConfig();
|
|
@@ -8019,6 +8113,22 @@ async function loginCommand(options) {
|
|
|
8019
8113
|
process.exit(1);
|
|
8020
8114
|
}
|
|
8021
8115
|
s.stop(import_picocolors7.default.green(`Connected to ${serverUrl}`));
|
|
8116
|
+
if (options.browser !== false) {
|
|
8117
|
+
try {
|
|
8118
|
+
const res = await browserLogin(serverUrl);
|
|
8119
|
+
client.setSessionToken(res.token);
|
|
8120
|
+
const verified = await client.getSession();
|
|
8121
|
+
if (!verified)
|
|
8122
|
+
throw new Error("The browser session could not be verified.");
|
|
8123
|
+
saveConfig({ serverUrl, sessionToken: res.token, user: verified });
|
|
8124
|
+
Se(import_picocolors7.default.green(`Logged in as ${verified.email}.`));
|
|
8125
|
+
return;
|
|
8126
|
+
} catch (error) {
|
|
8127
|
+
Me(error instanceof Error ? error.message : String(error), "Browser login failed");
|
|
8128
|
+
process.exitCode = 1;
|
|
8129
|
+
return;
|
|
8130
|
+
}
|
|
8131
|
+
}
|
|
8022
8132
|
const method = await ve({
|
|
8023
8133
|
message: "Select login method:",
|
|
8024
8134
|
options: [
|
|
@@ -8155,6 +8265,7 @@ Or start the backend on port 8000.`, "Server Unreachable");
|
|
|
8155
8265
|
const authMethod = await ve({
|
|
8156
8266
|
message: "How would you like to authenticate with ClosedRouter?",
|
|
8157
8267
|
options: [
|
|
8268
|
+
{ value: "browser", label: "Sign in with browser" },
|
|
8158
8269
|
{ value: "login", label: "Sign in with email and password" },
|
|
8159
8270
|
{ value: "signup", label: "Create a new account" },
|
|
8160
8271
|
{ value: "token", label: "Paste existing session token or API key" }
|
|
@@ -8164,7 +8275,21 @@ Or start the backend on port 8000.`, "Server Unreachable");
|
|
|
8164
8275
|
xe("Onboarding cancelled.");
|
|
8165
8276
|
process.exit(0);
|
|
8166
8277
|
}
|
|
8167
|
-
if (authMethod === "
|
|
8278
|
+
if (authMethod === "browser") {
|
|
8279
|
+
try {
|
|
8280
|
+
const authRes = await browserLogin(serverUrl);
|
|
8281
|
+
client.setSessionToken(authRes.token);
|
|
8282
|
+
const verified = await client.getSession();
|
|
8283
|
+
if (!verified)
|
|
8284
|
+
throw new Error("The browser session could not be verified.");
|
|
8285
|
+
saveConfig({ serverUrl, sessionToken: authRes.token, user: verified });
|
|
8286
|
+
Me(`Logged in as ${verified.email}.`, "Browser Login");
|
|
8287
|
+
} catch (error) {
|
|
8288
|
+
Me(error instanceof Error ? error.message : String(error), "Browser Login Failed");
|
|
8289
|
+
process.exitCode = 1;
|
|
8290
|
+
return;
|
|
8291
|
+
}
|
|
8292
|
+
} else if (authMethod === "signup") {
|
|
8168
8293
|
const name = await he({
|
|
8169
8294
|
message: "Your name:",
|
|
8170
8295
|
placeholder: "Alex Smith"
|
|
@@ -8481,7 +8606,7 @@ to scan and import local subscriptions!`, "Pool Empty");
|
|
|
8481
8606
|
|
|
8482
8607
|
// src/commands/start.ts
|
|
8483
8608
|
var import_picocolors12 = __toESM(require_picocolors(), 1);
|
|
8484
|
-
import { spawn as
|
|
8609
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
8485
8610
|
import { mkdirSync as mkdirSync2, readFileSync as readFileSync8, writeFileSync as writeFileSync2 } from "node:fs";
|
|
8486
8611
|
import { homedir as homedir9 } from "node:os";
|
|
8487
8612
|
import { dirname, join as join9, resolve } from "node:path";
|
|
@@ -8532,7 +8657,7 @@ function buildLaunchPlan(tool, serverUrl, apiKey, model, toolArgs = [], homeDir
|
|
|
8532
8657
|
assertNoManagedCodexArgs(toolArgs);
|
|
8533
8658
|
const codexHome = process.env.CODEX_HOME || join9(homeDir, ".codex");
|
|
8534
8659
|
const profileName = "closedrouter-launch";
|
|
8535
|
-
const
|
|
8660
|
+
const configPath = join9(codexHome, `${profileName}.config.toml`);
|
|
8536
8661
|
const configLines = [
|
|
8537
8662
|
`model = ${tomlString(model)}`,
|
|
8538
8663
|
`model_provider = ${tomlString(profileName)}`,
|
|
@@ -8540,6 +8665,7 @@ function buildLaunchPlan(tool, serverUrl, apiKey, model, toolArgs = [], homeDir
|
|
|
8540
8665
|
`[model_providers.${profileName}]`,
|
|
8541
8666
|
`name = ${tomlString("ClosedRouter")}`,
|
|
8542
8667
|
`base_url = ${tomlString(`${baseUrl}/v1/`)}`,
|
|
8668
|
+
'env_key = "OPENAI_API_KEY"',
|
|
8543
8669
|
'wire_api = "responses"',
|
|
8544
8670
|
""
|
|
8545
8671
|
];
|
|
@@ -8549,7 +8675,7 @@ function buildLaunchPlan(tool, serverUrl, apiKey, model, toolArgs = [], homeDir
|
|
|
8549
8675
|
command,
|
|
8550
8676
|
args: ["--profile", profileName, ...toolArgs],
|
|
8551
8677
|
env,
|
|
8552
|
-
configFile: { path:
|
|
8678
|
+
configFile: { path: configPath, contents: configLines.join(`
|
|
8553
8679
|
`) }
|
|
8554
8680
|
};
|
|
8555
8681
|
}
|
|
@@ -8557,7 +8683,19 @@ function buildLaunchPlan(tool, serverUrl, apiKey, model, toolArgs = [], homeDir
|
|
|
8557
8683
|
env.GEMINI_API_KEY = apiKey;
|
|
8558
8684
|
env.GEMINI_MODEL = model;
|
|
8559
8685
|
env.GOOGLE_GEMINI_BASE_URL = baseUrl;
|
|
8560
|
-
|
|
8686
|
+
const configPath = join9(homeDir, ".closedrouter", "gemini-launch.settings.json");
|
|
8687
|
+
env.GEMINI_CLI_SYSTEM_SETTINGS_PATH = configPath;
|
|
8688
|
+
return {
|
|
8689
|
+
tool,
|
|
8690
|
+
command,
|
|
8691
|
+
args: [...toolArgs],
|
|
8692
|
+
env,
|
|
8693
|
+
configFile: {
|
|
8694
|
+
path: configPath,
|
|
8695
|
+
contents: `${JSON.stringify({ security: { auth: { selectedType: "gemini-api-key" } } }, null, 2)}
|
|
8696
|
+
`
|
|
8697
|
+
}
|
|
8698
|
+
};
|
|
8561
8699
|
}
|
|
8562
8700
|
if (tool === "aider") {
|
|
8563
8701
|
env.OPENAI_API_BASE = `${baseUrl}/v1`;
|
|
@@ -8569,7 +8707,6 @@ function buildLaunchPlan(tool, serverUrl, apiKey, model, toolArgs = [], homeDir
|
|
|
8569
8707
|
env
|
|
8570
8708
|
};
|
|
8571
8709
|
}
|
|
8572
|
-
const configPath = join9(homeDir, ".closedrouter", "opencode-launch.json");
|
|
8573
8710
|
const config = {
|
|
8574
8711
|
$schema: "https://opencode.ai/config.json",
|
|
8575
8712
|
provider: {
|
|
@@ -8584,15 +8721,13 @@ function buildLaunchPlan(tool, serverUrl, apiKey, model, toolArgs = [], homeDir
|
|
|
8584
8721
|
}
|
|
8585
8722
|
}
|
|
8586
8723
|
};
|
|
8587
|
-
env.
|
|
8724
|
+
env.OPENCODE_CONFIG_CONTENT = JSON.stringify(config);
|
|
8588
8725
|
env.CLOSEDROUTER_API_KEY = apiKey;
|
|
8589
8726
|
return {
|
|
8590
8727
|
tool,
|
|
8591
8728
|
command,
|
|
8592
8729
|
args: ["--model", `closedrouter/${model}`, ...toolArgs],
|
|
8593
|
-
env
|
|
8594
|
-
configFile: { path: configPath, contents: `${JSON.stringify(config, null, 2)}
|
|
8595
|
-
` }
|
|
8730
|
+
env
|
|
8596
8731
|
};
|
|
8597
8732
|
}
|
|
8598
8733
|
function writeOwnedConfig(file) {
|
|
@@ -8653,7 +8788,7 @@ async function ensureClaudeBridge(client, serverUrl, key) {
|
|
|
8653
8788
|
const script = process.argv[1];
|
|
8654
8789
|
if (!script)
|
|
8655
8790
|
throw new Error("No Claude subscription gateway is online. Start one with `cr gateway`.");
|
|
8656
|
-
const child =
|
|
8791
|
+
const child = spawn3(process.execPath, [resolve(script), "gateway", "--server", serverUrl, "--key-stdin"], {
|
|
8657
8792
|
detached: true,
|
|
8658
8793
|
env: process.env,
|
|
8659
8794
|
stdio: ["pipe", "ignore", "ignore"]
|
|
@@ -8674,7 +8809,7 @@ async function ensureClaudeBridge(client, serverUrl, key) {
|
|
|
8674
8809
|
}
|
|
8675
8810
|
async function runTool(plan) {
|
|
8676
8811
|
return await new Promise((resolveExit, rejectExit) => {
|
|
8677
|
-
const child =
|
|
8812
|
+
const child = spawn3(plan.command, plan.args, { env: plan.env, stdio: "inherit" });
|
|
8678
8813
|
child.once("error", rejectExit);
|
|
8679
8814
|
child.once("exit", (code) => resolveExit(code ?? 1));
|
|
8680
8815
|
});
|
|
@@ -8762,7 +8897,7 @@ process.on("warning", (warning) => {
|
|
|
8762
8897
|
console.warn(warning.name, warning.message);
|
|
8763
8898
|
});
|
|
8764
8899
|
var program2 = new Command;
|
|
8765
|
-
program2.name("closedrouter").description("ClosedRouter CLI: Import local AI subscriptions & API keys into your shared token pool").version("1.
|
|
8900
|
+
program2.name("closedrouter").description("ClosedRouter CLI: Import local AI subscriptions & API keys into your shared token pool").version("1.1.1").option("-s, --server <url>", "ClosedRouter server base URL (default: http://localhost:8000)").action(async (options) => {
|
|
8766
8901
|
await onboardCommand({ server: options.server });
|
|
8767
8902
|
});
|
|
8768
8903
|
program2.command("onboard").description("Run the full interactive onboarding wizard").option("-s, --server <url>", "ClosedRouter server base URL").option("-a, --all", "Automatically import all discovered accounts").option("--shared", "Contribute accounts to the shared team pool").action(async (options) => {
|
|
@@ -8785,8 +8920,8 @@ program2.command("import").description("Scan and import local accounts into Clos
|
|
|
8785
8920
|
server: options.server || program2.opts().server
|
|
8786
8921
|
});
|
|
8787
8922
|
});
|
|
8788
|
-
program2.command("login").description("
|
|
8789
|
-
await loginCommand({ server: options.server || program2.opts().server });
|
|
8923
|
+
program2.command("login").description("Sign in through your browser and store CLI credentials").option("-s, --server <url>", "ClosedRouter server base URL").option("--no-browser", "Use email, signup, or token login in the terminal").action(async (options) => {
|
|
8924
|
+
await loginCommand({ server: options.server || program2.opts().server, browser: options.browser });
|
|
8790
8925
|
});
|
|
8791
8926
|
program2.command("logout").description("Clear stored session credentials").action(() => {
|
|
8792
8927
|
logoutCommand();
|
package/package.json
CHANGED