@closedrouter/cli 1.0.0 → 1.1.0
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 +7 -5
- package/dist/browser-login.d.ts +7 -0
- package/dist/browser-login.d.ts.map +1 -0
- package/dist/commands/keys.d.ts.map +1 -1
- 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/commands/status.d.ts.map +1 -1
- package/dist/index.js +141 -10
- package/package.json +3 -3
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`
|
|
@@ -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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../../src/commands/keys.ts"],"names":[],"mappings":"AAAA;;GAEG;AASH,wBAAsB,WAAW,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../../src/commands/keys.ts"],"names":[],"mappings":"AAAA;;GAEG;AASH,wBAAsB,WAAW,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA8ElF"}
|
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,CA4FZ;AA6FD,wBAAsB,YAAY,CAAC,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsCnF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAQH,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAQH,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAsDnD"}
|
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
|
}
|
|
@@ -7968,6 +7968,7 @@ ${import_picocolors6.default.yellow("Save this key securely. It has been set as
|
|
|
7968
7968
|
} catch (err) {
|
|
7969
7969
|
s2.stop(import_picocolors6.default.red("Failed to create key"));
|
|
7970
7970
|
Me(err instanceof Error ? err.message : String(err), "Error");
|
|
7971
|
+
process.exitCode = 1;
|
|
7971
7972
|
}
|
|
7972
7973
|
Se("Done.");
|
|
7973
7974
|
return;
|
|
@@ -7984,9 +7985,11 @@ Create one via:
|
|
|
7984
7985
|
} else {
|
|
7985
7986
|
const rows = keys.map((k3, i) => {
|
|
7986
7987
|
const active = k3.is_active ? import_picocolors6.default.green("● active") : import_picocolors6.default.red("○ revoked");
|
|
7987
|
-
const
|
|
7988
|
+
const budgetLimit = k3.budget_limit_tokens ?? k3.budget_tokens;
|
|
7989
|
+
const budget = budgetLimit == null ? "unlimited" : `${Number(budgetLimit).toLocaleString()} tokens`;
|
|
7990
|
+
const used = `${Number(k3.used_tokens ?? 0).toLocaleString()} tokens`;
|
|
7988
7991
|
return ` ${import_picocolors6.default.dim((i + 1).toString().padStart(2, " "))}. ${import_picocolors6.default.bold(k3.name || "Unnamed Key")} [${k3.key_prefix || "cr-sk-..."}] ${active}
|
|
7989
|
-
${import_picocolors6.default.dim("Created:")} ${new Date(k3.created_at).toLocaleDateString()} | ${import_picocolors6.default.dim("Budget:")} ${budget}`;
|
|
7992
|
+
${import_picocolors6.default.dim("Created:")} ${new Date(k3.created_at).toLocaleDateString()} | ${import_picocolors6.default.dim("Budget:")} ${budget} | ${import_picocolors6.default.dim("Used:")} ${used}`;
|
|
7990
7993
|
});
|
|
7991
7994
|
console.log(rows.join(`
|
|
7992
7995
|
|
|
@@ -7996,12 +7999,107 @@ Create one via:
|
|
|
7996
7999
|
} catch (err) {
|
|
7997
8000
|
s.stop(import_picocolors6.default.red("Failed to list keys"));
|
|
7998
8001
|
Me(err instanceof Error ? err.message : String(err), "Error");
|
|
8002
|
+
process.exitCode = 1;
|
|
7999
8003
|
}
|
|
8000
8004
|
Se("Done.");
|
|
8001
8005
|
}
|
|
8002
8006
|
|
|
8003
8007
|
// src/commands/login.ts
|
|
8004
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
|
|
8005
8103
|
async function loginCommand(options) {
|
|
8006
8104
|
Ie(import_picocolors7.default.bgCyan(import_picocolors7.default.black(" ClosedRouter Login ")));
|
|
8007
8105
|
const config = loadConfig();
|
|
@@ -8015,6 +8113,22 @@ async function loginCommand(options) {
|
|
|
8015
8113
|
process.exit(1);
|
|
8016
8114
|
}
|
|
8017
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
|
+
}
|
|
8018
8132
|
const method = await ve({
|
|
8019
8133
|
message: "Select login method:",
|
|
8020
8134
|
options: [
|
|
@@ -8151,6 +8265,7 @@ Or start the backend on port 8000.`, "Server Unreachable");
|
|
|
8151
8265
|
const authMethod = await ve({
|
|
8152
8266
|
message: "How would you like to authenticate with ClosedRouter?",
|
|
8153
8267
|
options: [
|
|
8268
|
+
{ value: "browser", label: "Sign in with browser" },
|
|
8154
8269
|
{ value: "login", label: "Sign in with email and password" },
|
|
8155
8270
|
{ value: "signup", label: "Create a new account" },
|
|
8156
8271
|
{ value: "token", label: "Paste existing session token or API key" }
|
|
@@ -8160,7 +8275,21 @@ Or start the backend on port 8000.`, "Server Unreachable");
|
|
|
8160
8275
|
xe("Onboarding cancelled.");
|
|
8161
8276
|
process.exit(0);
|
|
8162
8277
|
}
|
|
8163
|
-
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") {
|
|
8164
8293
|
const name = await he({
|
|
8165
8294
|
message: "Your name:",
|
|
8166
8295
|
placeholder: "Alex Smith"
|
|
@@ -8470,13 +8599,14 @@ to scan and import local subscriptions!`, "Pool Empty");
|
|
|
8470
8599
|
} catch (err) {
|
|
8471
8600
|
s.stop(import_picocolors11.default.red("Could not fetch account status"));
|
|
8472
8601
|
Me(err instanceof Error ? err.message : String(err), "Error");
|
|
8602
|
+
process.exitCode = 1;
|
|
8473
8603
|
}
|
|
8474
8604
|
Se(import_picocolors11.default.cyan(`Dashboard available at: ${config.serverUrl.replace(/:\d+$/, ":5173")}`));
|
|
8475
8605
|
}
|
|
8476
8606
|
|
|
8477
8607
|
// src/commands/start.ts
|
|
8478
8608
|
var import_picocolors12 = __toESM(require_picocolors(), 1);
|
|
8479
|
-
import { spawn as
|
|
8609
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
8480
8610
|
import { mkdirSync as mkdirSync2, readFileSync as readFileSync8, writeFileSync as writeFileSync2 } from "node:fs";
|
|
8481
8611
|
import { homedir as homedir9 } from "node:os";
|
|
8482
8612
|
import { dirname, join as join9, resolve } from "node:path";
|
|
@@ -8535,6 +8665,7 @@ function buildLaunchPlan(tool, serverUrl, apiKey, model, toolArgs = [], homeDir
|
|
|
8535
8665
|
`[model_providers.${profileName}]`,
|
|
8536
8666
|
`name = ${tomlString("ClosedRouter")}`,
|
|
8537
8667
|
`base_url = ${tomlString(`${baseUrl}/v1/`)}`,
|
|
8668
|
+
'env_key = "OPENAI_API_KEY"',
|
|
8538
8669
|
'wire_api = "responses"',
|
|
8539
8670
|
""
|
|
8540
8671
|
];
|
|
@@ -8648,7 +8779,7 @@ async function ensureClaudeBridge(client, serverUrl, key) {
|
|
|
8648
8779
|
const script = process.argv[1];
|
|
8649
8780
|
if (!script)
|
|
8650
8781
|
throw new Error("No Claude subscription gateway is online. Start one with `cr gateway`.");
|
|
8651
|
-
const child =
|
|
8782
|
+
const child = spawn3(process.execPath, [resolve(script), "gateway", "--server", serverUrl, "--key-stdin"], {
|
|
8652
8783
|
detached: true,
|
|
8653
8784
|
env: process.env,
|
|
8654
8785
|
stdio: ["pipe", "ignore", "ignore"]
|
|
@@ -8669,7 +8800,7 @@ async function ensureClaudeBridge(client, serverUrl, key) {
|
|
|
8669
8800
|
}
|
|
8670
8801
|
async function runTool(plan) {
|
|
8671
8802
|
return await new Promise((resolveExit, rejectExit) => {
|
|
8672
|
-
const child =
|
|
8803
|
+
const child = spawn3(plan.command, plan.args, { env: plan.env, stdio: "inherit" });
|
|
8673
8804
|
child.once("error", rejectExit);
|
|
8674
8805
|
child.once("exit", (code) => resolveExit(code ?? 1));
|
|
8675
8806
|
});
|
|
@@ -8757,7 +8888,7 @@ process.on("warning", (warning) => {
|
|
|
8757
8888
|
console.warn(warning.name, warning.message);
|
|
8758
8889
|
});
|
|
8759
8890
|
var program2 = new Command;
|
|
8760
|
-
program2.name("closedrouter").description("ClosedRouter CLI: Import local AI subscriptions & API keys into your shared token pool").version("1.
|
|
8891
|
+
program2.name("closedrouter").description("ClosedRouter CLI: Import local AI subscriptions & API keys into your shared token pool").version("1.1.0").option("-s, --server <url>", "ClosedRouter server base URL (default: http://localhost:8000)").action(async (options) => {
|
|
8761
8892
|
await onboardCommand({ server: options.server });
|
|
8762
8893
|
});
|
|
8763
8894
|
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) => {
|
|
@@ -8780,8 +8911,8 @@ program2.command("import").description("Scan and import local accounts into Clos
|
|
|
8780
8911
|
server: options.server || program2.opts().server
|
|
8781
8912
|
});
|
|
8782
8913
|
});
|
|
8783
|
-
program2.command("login").description("
|
|
8784
|
-
await loginCommand({ server: options.server || program2.opts().server });
|
|
8914
|
+
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) => {
|
|
8915
|
+
await loginCommand({ server: options.server || program2.opts().server, browser: options.browser });
|
|
8785
8916
|
});
|
|
8786
8917
|
program2.command("logout").description("Clear stored session credentials").action(() => {
|
|
8787
8918
|
logoutCommand();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@closedrouter/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "ClosedRouter CLI: Onboard and import local AI subscriptions and API keys into your shared token pool",
|
|
5
5
|
"author": "ClosedRouter Contributors",
|
|
6
6
|
"repository": {
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
"main": "./dist/index.js",
|
|
17
17
|
"types": "./dist/index.d.ts",
|
|
18
18
|
"bin": {
|
|
19
|
-
"closedrouter": "
|
|
20
|
-
"cr": "
|
|
19
|
+
"closedrouter": "bin/closedrouter.js",
|
|
20
|
+
"cr": "bin/closedrouter.js"
|
|
21
21
|
},
|
|
22
22
|
"files": [
|
|
23
23
|
"dist",
|