@closedrouter/cli 1.1.1 → 1.1.3
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 +3 -2
- package/dist/commands/start.d.ts +2 -1
- package/dist/commands/start.d.ts.map +1 -1
- package/dist/index.js +47 -46
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -125,7 +125,7 @@ cr logout
|
|
|
125
125
|
```
|
|
126
126
|
|
|
127
127
|
### `cr start` / `cr launch`
|
|
128
|
-
Configure and start an installed coding CLI using models exposed to your platform key. The command discovers available models
|
|
128
|
+
Configure and start an installed coding CLI using models exposed to your platform key. The command discovers available tool-capable models and keeps keys out of arguments and generated client profiles. Starting Claude Code as a client uses the Anthropic-compatible platform endpoint; `cr gateway` separately contributes a local Claude Code subscription to the shared pool.
|
|
129
129
|
|
|
130
130
|
```bash
|
|
131
131
|
# Interactive model and tool selection
|
|
@@ -135,6 +135,7 @@ cr start
|
|
|
135
135
|
cr start claude --model claude-sonnet-4-6
|
|
136
136
|
cr start codex --model gpt-4.1
|
|
137
137
|
cr start gemini --model gemini-2.5-flash
|
|
138
|
+
cr start agy --model gemini-3.8-flash
|
|
138
139
|
cr start aider --model gpt-4o-mini
|
|
139
140
|
cr start opencode --model gpt-4o-mini
|
|
140
141
|
|
|
@@ -142,7 +143,7 @@ cr start opencode --model gpt-4o-mini
|
|
|
142
143
|
cr start claude --model claude-sonnet-4-6 -- --resume
|
|
143
144
|
```
|
|
144
145
|
|
|
145
|
-
Claude Code
|
|
146
|
+
Claude Code uses the Anthropic-compatible endpoint and can route through any eligible provider account. 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. Antigravity CLI requires `modelProvider: "gemini"` in `~/.gemini/antigravity-cli/settings.json`; its launcher uses `GEMINI_API_KEY` and `GOOGLE_GEMINI_BASE_URL`. The launchers do not change the tools' main configuration files.
|
|
146
147
|
|
|
147
148
|
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.
|
|
148
149
|
|
package/dist/commands/start.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* The per-tool adapters follow the launcher pattern used by Ollama: discover a
|
|
4
4
|
* model, write only app-owned config, then start the installed client process.
|
|
5
5
|
*/
|
|
6
|
-
export type StartTool = "claude" | "codex" | "gemini" | "aider" | "opencode";
|
|
6
|
+
export type StartTool = "claude" | "codex" | "gemini" | "agy" | "aider" | "opencode";
|
|
7
7
|
type CliTool = StartTool;
|
|
8
8
|
export interface StartCommandOptions {
|
|
9
9
|
tool?: string;
|
|
@@ -26,6 +26,7 @@ export interface LaunchPlan {
|
|
|
26
26
|
}
|
|
27
27
|
export declare function isGatewayModelAvailable(availableModels: string[], model: string): boolean;
|
|
28
28
|
export declare function buildLaunchPlan(tool: CliTool, serverUrl: string, apiKey: string, model: string, toolArgs?: string[], homeDir?: string, executablePath?: string): LaunchPlan;
|
|
29
|
+
export declare function modelSupportsHarness(model: Record<string, any>, tool: StartTool): boolean;
|
|
29
30
|
export declare function startCommand(options?: StartCommandOptions): Promise<void>;
|
|
30
31
|
export {};
|
|
31
32
|
//# sourceMappingURL=start.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../../src/commands/start.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../../src/commands/start.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAYH,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,KAAK,GAAG,OAAO,GAAG,UAAU,CAAC;AACrF,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;AA8BD,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,CAgHZ;AAmBD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAMzF;AAsDD,wBAAsB,YAAY,CAAC,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAwCnF"}
|
package/dist/index.js
CHANGED
|
@@ -8607,16 +8607,18 @@ to scan and import local subscriptions!`, "Pool Empty");
|
|
|
8607
8607
|
// src/commands/start.ts
|
|
8608
8608
|
var import_picocolors12 = __toESM(require_picocolors(), 1);
|
|
8609
8609
|
import { spawn as spawn3 } from "node:child_process";
|
|
8610
|
+
import { execFileSync } from "node:child_process";
|
|
8610
8611
|
import { mkdirSync as mkdirSync2, readFileSync as readFileSync8, writeFileSync as writeFileSync2 } from "node:fs";
|
|
8611
8612
|
import { homedir as homedir9 } from "node:os";
|
|
8612
|
-
import { dirname, join as join9
|
|
8613
|
+
import { dirname, join as join9 } from "node:path";
|
|
8613
8614
|
function isGatewayModelAvailable(availableModels, model) {
|
|
8614
8615
|
return availableModels.includes(model);
|
|
8615
8616
|
}
|
|
8616
8617
|
var TOOL_OPTIONS = [
|
|
8617
|
-
{ value: "claude", label: "Claude Code", hint: "Anthropic API
|
|
8618
|
+
{ value: "claude", label: "Claude Code", hint: "Anthropic-compatible API using your ClosedRouter pool" },
|
|
8618
8619
|
{ value: "codex", label: "Codex CLI", hint: "OpenAI Responses API" },
|
|
8619
8620
|
{ value: "gemini", label: "Gemini CLI", hint: "Google Gemini API" },
|
|
8621
|
+
{ value: "agy", label: "Antigravity CLI", hint: "Google Gemini API via your ClosedRouter pool" },
|
|
8620
8622
|
{ value: "aider", label: "Aider", hint: "OpenAI-compatible API" },
|
|
8621
8623
|
{ value: "opencode", label: "OpenCode", hint: "OpenAI-compatible API" }
|
|
8622
8624
|
];
|
|
@@ -8641,11 +8643,11 @@ function buildLaunchPlan(tool, serverUrl, apiKey, model, toolArgs = [], homeDir
|
|
|
8641
8643
|
env.ANTHROPIC_BASE_URL = baseUrl;
|
|
8642
8644
|
env.ANTHROPIC_API_KEY = apiKey;
|
|
8643
8645
|
const existingHeaders = (env.ANTHROPIC_CUSTOM_HEADERS || "").split(/\r?\n/).filter((header) => header.trim() && !/^\s*x-closedrouter-provider-only\s*:/i.test(header));
|
|
8644
|
-
|
|
8645
|
-
|
|
8646
|
-
"X-ClosedRouter-Provider-Only: claude_oauth"
|
|
8647
|
-
].join(`
|
|
8646
|
+
if (existingHeaders.length)
|
|
8647
|
+
env.ANTHROPIC_CUSTOM_HEADERS = existingHeaders.join(`
|
|
8648
8648
|
`);
|
|
8649
|
+
else
|
|
8650
|
+
delete env.ANTHROPIC_CUSTOM_HEADERS;
|
|
8649
8651
|
return {
|
|
8650
8652
|
tool,
|
|
8651
8653
|
command,
|
|
@@ -8697,6 +8699,19 @@ function buildLaunchPlan(tool, serverUrl, apiKey, model, toolArgs = [], homeDir
|
|
|
8697
8699
|
}
|
|
8698
8700
|
};
|
|
8699
8701
|
}
|
|
8702
|
+
if (tool === "agy") {
|
|
8703
|
+
env.GEMINI_API_KEY = apiKey;
|
|
8704
|
+
env.GOOGLE_GEMINI_BASE_URL = baseUrl;
|
|
8705
|
+
const conflicting = toolArgs.find((arg) => arg === "--model" || arg.startsWith("--model=") || arg === "--effort" || arg.startsWith("--effort="));
|
|
8706
|
+
if (conflicting)
|
|
8707
|
+
throw new Error(`Remove ${conflicting}; ClosedRouter manages the Antigravity model and effort.`);
|
|
8708
|
+
return {
|
|
8709
|
+
tool,
|
|
8710
|
+
command,
|
|
8711
|
+
args: ["--model", model, "--effort", "high", ...toolArgs],
|
|
8712
|
+
env
|
|
8713
|
+
};
|
|
8714
|
+
}
|
|
8700
8715
|
if (tool === "aider") {
|
|
8701
8716
|
env.OPENAI_API_BASE = `${baseUrl}/v1`;
|
|
8702
8717
|
env.OPENAI_API_KEY = apiKey;
|
|
@@ -8746,6 +8761,18 @@ function writeOwnedConfig(file) {
|
|
|
8746
8761
|
function modelId(model) {
|
|
8747
8762
|
return String(model.id || model.name || "");
|
|
8748
8763
|
}
|
|
8764
|
+
function modelSupportsHarness(model, tool) {
|
|
8765
|
+
const parameters = model.supported_parameters;
|
|
8766
|
+
return (tool !== "agy" || modelId(model).startsWith("gemini-")) && (model.status === undefined || model.status === "available") && (model.available_tool_account_count === undefined || model.available_tool_account_count > 0) && (!Array.isArray(parameters) || parameters.includes("tools"));
|
|
8767
|
+
}
|
|
8768
|
+
function discoverAgyModels(command) {
|
|
8769
|
+
try {
|
|
8770
|
+
const output = execFileSync(command, ["models"], { encoding: "utf8", timeout: 1e4, stdio: ["ignore", "pipe", "ignore"] });
|
|
8771
|
+
return new Set(output.split(/\r?\n/).map((line) => line.split(/\s+/)[0]).filter((id) => /^gemini-[\w.-]+-(?:high|medium|low)$/.test(id)).map((id) => id.replace(/-(?:high|medium|low)$/, "")));
|
|
8772
|
+
} catch {
|
|
8773
|
+
throw new Error("Could not list Antigravity CLI models. Install or update `agy`, then try `cr start agy` again.");
|
|
8774
|
+
}
|
|
8775
|
+
}
|
|
8749
8776
|
async function selectTool(tool) {
|
|
8750
8777
|
const cleaned = tool?.toLowerCase();
|
|
8751
8778
|
if (cleaned) {
|
|
@@ -8763,50 +8790,19 @@ async function selectTool(tool) {
|
|
|
8763
8790
|
}
|
|
8764
8791
|
async function ensurePlatformKey(tool, client, options) {
|
|
8765
8792
|
const config = loadConfig();
|
|
8766
|
-
const key = options.key?.trim() ||
|
|
8793
|
+
const key = options.key?.trim() || config.apiKey;
|
|
8767
8794
|
if (key)
|
|
8768
8795
|
return key;
|
|
8769
8796
|
if (!config.sessionToken) {
|
|
8770
8797
|
throw new Error("No platform API key is configured. Run `cr login` or pass `--key <cr-sk-...>`.");
|
|
8771
8798
|
}
|
|
8772
|
-
const
|
|
8773
|
-
const allowedModels = tool === "claude" ? await client.listClaudeBridgeModels() : undefined;
|
|
8774
|
-
if (tool === "claude" && !allowedModels?.length) {
|
|
8775
|
-
throw new Error("No Claude OAuth models are enabled for this workspace.");
|
|
8776
|
-
}
|
|
8777
|
-
const created = await client.createApiKey(label, 1e6, allowedModels, 60);
|
|
8799
|
+
const created = await client.createApiKey(`ClosedRouter ${tool} launch`, 1e6, undefined, 60);
|
|
8778
8800
|
const rawKey = String(created.key || created.api_key || "");
|
|
8779
8801
|
if (!rawKey.startsWith("cr-sk-"))
|
|
8780
8802
|
throw new Error("The server did not return a platform API key.");
|
|
8781
|
-
saveConfig(
|
|
8803
|
+
saveConfig({ apiKey: rawKey });
|
|
8782
8804
|
return rawKey;
|
|
8783
8805
|
}
|
|
8784
|
-
async function ensureClaudeBridge(client, serverUrl, key) {
|
|
8785
|
-
const hasClaudeNode = (nodes) => nodes.some((node) => node.status === "online" && Array.isArray(node.providers) && node.providers.includes("claude_oauth"));
|
|
8786
|
-
if (hasClaudeNode(await client.listBridgeNodes()))
|
|
8787
|
-
return;
|
|
8788
|
-
const script = process.argv[1];
|
|
8789
|
-
if (!script)
|
|
8790
|
-
throw new Error("No Claude subscription gateway is online. Start one with `cr gateway`.");
|
|
8791
|
-
const child = spawn3(process.execPath, [resolve(script), "gateway", "--server", serverUrl, "--key-stdin"], {
|
|
8792
|
-
detached: true,
|
|
8793
|
-
env: process.env,
|
|
8794
|
-
stdio: ["pipe", "ignore", "ignore"]
|
|
8795
|
-
});
|
|
8796
|
-
child.stdin?.end(`${key}
|
|
8797
|
-
`);
|
|
8798
|
-
child.unref();
|
|
8799
|
-
for (let attempt = 0;attempt < 30; attempt++) {
|
|
8800
|
-
if (child.exitCode !== null)
|
|
8801
|
-
break;
|
|
8802
|
-
await new Promise((resolveDelay) => setTimeout(resolveDelay, 400));
|
|
8803
|
-
if (hasClaudeNode(await client.listBridgeNodes()))
|
|
8804
|
-
return;
|
|
8805
|
-
}
|
|
8806
|
-
if (child.exitCode === null)
|
|
8807
|
-
child.kill("SIGTERM");
|
|
8808
|
-
throw new Error("Could not start the local Claude gateway. Run `cr gateway` in a terminal to see its connection status.");
|
|
8809
|
-
}
|
|
8810
8806
|
async function runTool(plan) {
|
|
8811
8807
|
return await new Promise((resolveExit, rejectExit) => {
|
|
8812
8808
|
const child = spawn3(plan.command, plan.args, { env: plan.env, stdio: "inherit" });
|
|
@@ -8822,7 +8818,11 @@ async function startCommand(options = {}) {
|
|
|
8822
8818
|
const apiKey = await ensurePlatformKey(tool, client, options);
|
|
8823
8819
|
client.setApiKey(apiKey);
|
|
8824
8820
|
const models = await client.listGatewayModels({ preferApiKey: true });
|
|
8825
|
-
|
|
8821
|
+
let availableModels = models.filter((item) => modelSupportsHarness(item, tool)).map(modelId).filter(Boolean).sort();
|
|
8822
|
+
if (tool === "agy") {
|
|
8823
|
+
const supportedModels = discoverAgyModels(options.executablePath || "agy");
|
|
8824
|
+
availableModels = availableModels.filter((id) => supportedModels.has(id));
|
|
8825
|
+
}
|
|
8826
8826
|
let model = options.model?.trim();
|
|
8827
8827
|
if (!model) {
|
|
8828
8828
|
if (availableModels.length === 0) {
|
|
@@ -8840,10 +8840,8 @@ async function startCommand(options = {}) {
|
|
|
8840
8840
|
model = String(choice);
|
|
8841
8841
|
}
|
|
8842
8842
|
if (!isGatewayModelAvailable(availableModels, model)) {
|
|
8843
|
-
throw new Error(`Model '${model}' is not available
|
|
8843
|
+
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.`);
|
|
8844
8844
|
}
|
|
8845
|
-
if (tool === "claude")
|
|
8846
|
-
await ensureClaudeBridge(client, serverUrl, apiKey);
|
|
8847
8845
|
const plan = buildLaunchPlan(tool, serverUrl, apiKey, model, options.toolArgs, homedir9(), options.executablePath);
|
|
8848
8846
|
if (plan.configFile)
|
|
8849
8847
|
writeOwnedConfig(plan.configFile);
|
|
@@ -8897,7 +8895,7 @@ process.on("warning", (warning) => {
|
|
|
8897
8895
|
console.warn(warning.name, warning.message);
|
|
8898
8896
|
});
|
|
8899
8897
|
var program2 = new Command;
|
|
8900
|
-
program2.name("closedrouter").description("ClosedRouter CLI: Import local AI subscriptions & API keys into your shared token pool").version("1.1.
|
|
8898
|
+
program2.name("closedrouter").description("ClosedRouter CLI: Import local AI subscriptions & API keys into your shared token pool").version("1.1.3").option("-s, --server <url>", "ClosedRouter server base URL (default: http://localhost:8000)").action(async (options) => {
|
|
8901
8899
|
await onboardCommand({ server: options.server });
|
|
8902
8900
|
});
|
|
8903
8901
|
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) => {
|
|
@@ -8954,4 +8952,7 @@ program2.command("start [tool] [tool-args...]").alias("launch").description("Con
|
|
|
8954
8952
|
executablePath: options.path
|
|
8955
8953
|
});
|
|
8956
8954
|
});
|
|
8957
|
-
program2.
|
|
8955
|
+
program2.parseAsync(process.argv).catch((error) => {
|
|
8956
|
+
console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
8957
|
+
process.exitCode = 1;
|
|
8958
|
+
});
|
package/package.json
CHANGED