pennyrouter 0.2.33 → 0.2.35
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/package.json +1 -1
- package/src/cli.js +46 -6
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -78,6 +78,7 @@ const CLAUDE_PROVIDER_OPTIONS = [
|
|
|
78
78
|
const CODEX_PROVIDER_OPTIONS = [
|
|
79
79
|
{ id: "subscription", name: "ChatGPT subscription" },
|
|
80
80
|
{ id: "openai-api", name: "OpenAI API key" },
|
|
81
|
+
{ id: "openai-proxy", name: "Other OpenAI-compatible proxy" },
|
|
81
82
|
{ id: "openrouter", name: "OpenRouter API key" },
|
|
82
83
|
];
|
|
83
84
|
|
|
@@ -171,6 +172,10 @@ function parseArgs(argv) {
|
|
|
171
172
|
else if (arg === "--openai-api-key") flags.openaiApiKey = rest[++i] || "";
|
|
172
173
|
else if (arg.startsWith("--openai-api-key=")) flags.openaiApiKey = arg.slice("--openai-api-key=".length);
|
|
173
174
|
else if (arg === "--openrouter-api-key") flags.openrouterApiKey = rest[++i] || "";
|
|
175
|
+
else if (arg === "--openai-proxy-base-url") flags.openaiProxyBaseUrl = rest[++i] || "";
|
|
176
|
+
else if (arg.startsWith("--openai-proxy-base-url=")) flags.openaiProxyBaseUrl = arg.slice("--openai-proxy-base-url=".length);
|
|
177
|
+
else if (arg === "--openai-proxy-api-key") flags.openaiProxyApiKey = rest[++i] || "";
|
|
178
|
+
else if (arg.startsWith("--openai-proxy-api-key=")) flags.openaiProxyApiKey = arg.slice("--openai-proxy-api-key=".length);
|
|
174
179
|
else if (arg === "--proxy-base-url") flags.proxyBaseUrl = rest[++i] || "";
|
|
175
180
|
else if (arg.startsWith("--proxy-base-url=")) flags.proxyBaseUrl = arg.slice("--proxy-base-url=".length);
|
|
176
181
|
else if (arg === "--proxy-api-key") flags.proxyApiKey = rest[++i] || "";
|
|
@@ -593,6 +598,19 @@ export async function install(flags = {}) {
|
|
|
593
598
|
flags, needsAuth, claudeAnthropicAuth, claudeProviders, codexProviders);
|
|
594
599
|
gatewayBaseUrl = flags.gatewayBaseUrl || claim.gateway_base_url;
|
|
595
600
|
|
|
601
|
+
// A user-supplied proxy may live on a private network that the hosted gateway cannot
|
|
602
|
+
// reach. Let interactive installs choose the gateway after they choose the proxy: blank
|
|
603
|
+
// keeps the hosted default, while a supplied URL is used for credential storage,
|
|
604
|
+
// validation, probing, and the installed harness configuration. `--local` remains the
|
|
605
|
+
// explicit localhost:8400 developer shortcut and skips this prompt.
|
|
606
|
+
if ((claudeProviders.has("anthropic-proxy") || codexProviders.has("openai-proxy")) && !flags.gatewayBaseUrl
|
|
607
|
+
&& !flags.yes && input.isTTY && output.isTTY) {
|
|
608
|
+
const privateGateway = (await promptText(
|
|
609
|
+
"PennyRouter gateway URL for this proxy (blank for hosted gateway): ",
|
|
610
|
+
)).trim();
|
|
611
|
+
if (privateGateway) gatewayBaseUrl = privateGateway;
|
|
612
|
+
}
|
|
613
|
+
|
|
596
614
|
for (const credential of funding.credentials) {
|
|
597
615
|
await storeUpstreamCredential({
|
|
598
616
|
gatewayBaseUrl,
|
|
@@ -1107,14 +1125,16 @@ async function selectCodexFundingProviders(flags, selected) {
|
|
|
1107
1125
|
|
|
1108
1126
|
const supplied = new Set();
|
|
1109
1127
|
if (flags.openaiApiKey || process.env.OPENAI_API_KEY) supplied.add("openai-api");
|
|
1128
|
+
if ((flags.openaiProxyBaseUrl || process.env.PENNYROUTER_OPENAI_PROXY_BASE_URL)
|
|
1129
|
+
&& (flags.openaiProxyApiKey || process.env.PENNYROUTER_OPENAI_PROXY_API_KEY)) supplied.add("openai-proxy");
|
|
1110
1130
|
if (flags.openrouterApiKey || process.env.OPENROUTER_API_KEY) supplied.add("openrouter");
|
|
1111
1131
|
const hasChatgptLogin = await detectCodexChatgptAuth();
|
|
1112
1132
|
if (hasChatgptLogin) supplied.add("subscription");
|
|
1113
1133
|
|
|
1114
|
-
const explicit = flags.openaiApiKey || flags.openrouterApiKey;
|
|
1134
|
+
const explicit = flags.openaiApiKey || flags.openaiProxyBaseUrl || flags.openaiProxyApiKey || flags.openrouterApiKey;
|
|
1115
1135
|
if (explicit || flags.yes || !input.isTTY || !output.isTTY) return supplied;
|
|
1116
1136
|
|
|
1117
|
-
console.log("PennyRouter can use your ChatGPT subscription, an OpenAI API key, or an OpenRouter API key to run Codex.");
|
|
1137
|
+
console.log("PennyRouter can use your ChatGPT subscription, an OpenAI API key, an OpenAI-compatible proxy, or an OpenRouter API key to run Codex.");
|
|
1118
1138
|
console.log("Choose one or more providers. PennyRouter will try them in order when available.");
|
|
1119
1139
|
return promptFundingProviderCheckboxes(
|
|
1120
1140
|
CODEX_PROVIDER_OPTIONS,
|
|
@@ -1196,6 +1216,12 @@ async function configureUserFunding(flags, selected, claudeAnthropicAuth,
|
|
|
1196
1216
|
let openrouterApiKey = String(
|
|
1197
1217
|
flags.openrouterApiKey || process.env.OPENROUTER_API_KEY || "",
|
|
1198
1218
|
).trim();
|
|
1219
|
+
let openaiProxyBaseUrl = String(
|
|
1220
|
+
flags.openaiProxyBaseUrl || process.env.PENNYROUTER_OPENAI_PROXY_BASE_URL || "",
|
|
1221
|
+
).trim();
|
|
1222
|
+
let openaiProxyApiKey = String(
|
|
1223
|
+
flags.openaiProxyApiKey || process.env.PENNYROUTER_OPENAI_PROXY_API_KEY || "",
|
|
1224
|
+
).trim();
|
|
1199
1225
|
let proxyBaseUrl = String(
|
|
1200
1226
|
flags.proxyBaseUrl || process.env.PENNYROUTER_PROXY_BASE_URL || "",
|
|
1201
1227
|
).trim();
|
|
@@ -1236,6 +1262,12 @@ async function configureUserFunding(flags, selected, claudeAnthropicAuth,
|
|
|
1236
1262
|
if (!interactive) throw new Error("OpenAI API was selected, but no key was provided.");
|
|
1237
1263
|
openaiApiKey = (await promptSecretish("OpenAI API key: ")).trim();
|
|
1238
1264
|
}
|
|
1265
|
+
if (wantsCodex && codexProviders.has("openai-proxy") && !(openaiProxyBaseUrl && openaiProxyApiKey)) {
|
|
1266
|
+
if (!interactive) throw new Error("OpenAI-compatible proxy was selected, but --openai-proxy-base-url and --openai-proxy-api-key were not both provided.");
|
|
1267
|
+
if (!openaiProxyBaseUrl) openaiProxyBaseUrl = await promptText("OpenAI proxy base URL (e.g. https://proxy.example.com/v1): ");
|
|
1268
|
+
if (!openaiProxyApiKey) openaiProxyApiKey = (await promptSecretish("OpenAI proxy API key: ")).trim();
|
|
1269
|
+
if (!openaiProxyBaseUrl || !openaiProxyApiKey) throw new Error("OpenAI-compatible proxy requires both a base URL and an API key.");
|
|
1270
|
+
}
|
|
1239
1271
|
if (wantsCodex && codexProviders.has("openrouter") && !openrouterApiKey) {
|
|
1240
1272
|
if (!interactive) throw new Error("OpenRouter was selected, but no key was provided.");
|
|
1241
1273
|
openrouterApiKey = (await promptSecretish("OpenRouter API key: ")).trim();
|
|
@@ -1263,11 +1295,11 @@ async function configureUserFunding(flags, selected, claudeAnthropicAuth,
|
|
|
1263
1295
|
console.log("");
|
|
1264
1296
|
}
|
|
1265
1297
|
|
|
1266
|
-
if (wantsCodex && !codexSubscriptionAuth && !openaiApiKey && !openrouterApiKey) {
|
|
1298
|
+
if (wantsCodex && !codexSubscriptionAuth && !openaiApiKey && !openaiProxyApiKey && !openrouterApiKey) {
|
|
1267
1299
|
if (!interactive) {
|
|
1268
1300
|
throw new Error(
|
|
1269
1301
|
"Codex requires ChatGPT login, --openai-api-key (or OPENAI_API_KEY), "
|
|
1270
|
-
+ "or --openrouter-api-key (or OPENROUTER_API_KEY). Nothing was installed.",
|
|
1302
|
+
+ "--openai-proxy-api-key (with --openai-proxy-base-url), or --openrouter-api-key (or OPENROUTER_API_KEY). Nothing was installed.",
|
|
1271
1303
|
);
|
|
1272
1304
|
}
|
|
1273
1305
|
console.log("Codex is not logged into ChatGPT and needs a user-funded provider.");
|
|
@@ -1293,6 +1325,10 @@ async function configureUserFunding(flags, selected, claudeAnthropicAuth,
|
|
|
1293
1325
|
if (wantsCodex && openaiApiKey) {
|
|
1294
1326
|
credentials.push({ provider: "openai_api", token: openaiApiKey });
|
|
1295
1327
|
}
|
|
1328
|
+
if (wantsCodex && openaiProxyBaseUrl && openaiProxyApiKey) {
|
|
1329
|
+
credentials.push({ provider: "openai_proxy", token: openaiProxyApiKey,
|
|
1330
|
+
metadata: { base_url: openaiProxyBaseUrl } });
|
|
1331
|
+
}
|
|
1296
1332
|
if ((wantsClaude || wantsCodex) && openrouterApiKey) {
|
|
1297
1333
|
credentials.push({ provider: "openrouter", token: openrouterApiKey });
|
|
1298
1334
|
}
|
|
@@ -1315,7 +1351,7 @@ async function configureUserFunding(flags, selected, claudeAnthropicAuth,
|
|
|
1315
1351
|
claudeProxyBaseUrl: claudeProxyConfigured ? proxyBaseUrl : null,
|
|
1316
1352
|
codexCredentialProvider: codexSubscriptionAuth
|
|
1317
1353
|
? "chatgpt-subscription"
|
|
1318
|
-
: openaiApiKey ? "openai-api" : openrouterApiKey ? "openrouter" : null,
|
|
1354
|
+
: openaiApiKey ? "openai-api" : openaiProxyBaseUrl ? "openai-proxy" : openrouterApiKey ? "openrouter" : null,
|
|
1319
1355
|
};
|
|
1320
1356
|
}
|
|
1321
1357
|
|
|
@@ -2031,7 +2067,7 @@ function printHelp() {
|
|
|
2031
2067
|
console.log(`PennyRouter CLI
|
|
2032
2068
|
|
|
2033
2069
|
Usage:
|
|
2034
|
-
pennyrouter install [--harness ${SUPPORTED_HARNESS_IDS}] [--all] [--yes] [--dry-run] [--make-default|--penny-only] [--no-path-update] [--no-mcp] [--anthropic-auth|--no-anthropic-auth] [--anthropic-api-key KEY] [--openai-api-key KEY] [--openrouter-api-key KEY] [--proxy-base-url URL --proxy-api-key KEY] [--local[=URL]|--prod] [--config PATH]
|
|
2070
|
+
pennyrouter install [--harness ${SUPPORTED_HARNESS_IDS}] [--all] [--yes] [--dry-run] [--make-default|--penny-only] [--no-path-update] [--no-mcp] [--anthropic-auth|--no-anthropic-auth] [--anthropic-api-key KEY] [--openai-api-key KEY] [--openai-proxy-base-url URL --openai-proxy-api-key KEY] [--openrouter-api-key KEY] [--proxy-base-url URL --proxy-api-key KEY] [--local[=URL]|--prod] [--config PATH]
|
|
2035
2071
|
pennyrouter update [--harness ${SUPPORTED_HARNESS_IDS}] [--all] [--dry-run] [--penny-key pr-...] [--gateway-base-url URL|--local[=URL]|--prod] [--no-mcp]
|
|
2036
2072
|
pennyrouter auth anthropic [--penny-key pr-...] [--token oauth-token] [--token-command CMD] [--gateway-base-url URL] [--forget]
|
|
2037
2073
|
pennyrouter disable [--harness ${SUPPORTED_HARNESS_IDS}] [--all] [--yes]
|
|
@@ -2040,6 +2076,10 @@ Usage:
|
|
|
2040
2076
|
pennyrouter status
|
|
2041
2077
|
pennyrouter mcp install|uninstall|status [--harness claude-code,codex]
|
|
2042
2078
|
|
|
2079
|
+
When an interactive install selects an Anthropic or OpenAI-compatible proxy, it asks for the PennyRouter gateway
|
|
2080
|
+
that can reach that proxy. Leave it blank for the hosted gateway, or enter a private/self-hosted
|
|
2081
|
+
gateway URL. Use --gateway-base-url (or --local for localhost:8400 development) for non-interactive installs.
|
|
2082
|
+
|
|
2043
2083
|
Install and update both register the Threads MCP server with Claude Code and Codex (--no-mcp to
|
|
2044
2084
|
skip a run). Run "pennyrouter mcp install" if you skipped it or had to rename a conflicting
|
|
2045
2085
|
server, "pennyrouter mcp status" to see whether it is wired up, and "pennyrouter mcp uninstall"
|