pennyrouter 0.2.34 → 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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/cli.js +31 -8
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pennyrouter",
3
- "version": "0.2.34",
3
+ "version": "0.2.35",
4
4
  "description": "Install and manage PennyRouter local coding-agent integrations.",
5
5
  "homepage": "https://pennyrouter.com",
6
6
  "bugs": {
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] || "";
@@ -598,7 +603,7 @@ export async function install(flags = {}) {
598
603
  // keeps the hosted default, while a supplied URL is used for credential storage,
599
604
  // validation, probing, and the installed harness configuration. `--local` remains the
600
605
  // explicit localhost:8400 developer shortcut and skips this prompt.
601
- if (claudeProviders.has("anthropic-proxy") && !flags.gatewayBaseUrl
606
+ if ((claudeProviders.has("anthropic-proxy") || codexProviders.has("openai-proxy")) && !flags.gatewayBaseUrl
602
607
  && !flags.yes && input.isTTY && output.isTTY) {
603
608
  const privateGateway = (await promptText(
604
609
  "PennyRouter gateway URL for this proxy (blank for hosted gateway): ",
@@ -1120,14 +1125,16 @@ async function selectCodexFundingProviders(flags, selected) {
1120
1125
 
1121
1126
  const supplied = new Set();
1122
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");
1123
1130
  if (flags.openrouterApiKey || process.env.OPENROUTER_API_KEY) supplied.add("openrouter");
1124
1131
  const hasChatgptLogin = await detectCodexChatgptAuth();
1125
1132
  if (hasChatgptLogin) supplied.add("subscription");
1126
1133
 
1127
- const explicit = flags.openaiApiKey || flags.openrouterApiKey;
1134
+ const explicit = flags.openaiApiKey || flags.openaiProxyBaseUrl || flags.openaiProxyApiKey || flags.openrouterApiKey;
1128
1135
  if (explicit || flags.yes || !input.isTTY || !output.isTTY) return supplied;
1129
1136
 
1130
- 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.");
1131
1138
  console.log("Choose one or more providers. PennyRouter will try them in order when available.");
1132
1139
  return promptFundingProviderCheckboxes(
1133
1140
  CODEX_PROVIDER_OPTIONS,
@@ -1209,6 +1216,12 @@ async function configureUserFunding(flags, selected, claudeAnthropicAuth,
1209
1216
  let openrouterApiKey = String(
1210
1217
  flags.openrouterApiKey || process.env.OPENROUTER_API_KEY || "",
1211
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();
1212
1225
  let proxyBaseUrl = String(
1213
1226
  flags.proxyBaseUrl || process.env.PENNYROUTER_PROXY_BASE_URL || "",
1214
1227
  ).trim();
@@ -1249,6 +1262,12 @@ async function configureUserFunding(flags, selected, claudeAnthropicAuth,
1249
1262
  if (!interactive) throw new Error("OpenAI API was selected, but no key was provided.");
1250
1263
  openaiApiKey = (await promptSecretish("OpenAI API key: ")).trim();
1251
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
+ }
1252
1271
  if (wantsCodex && codexProviders.has("openrouter") && !openrouterApiKey) {
1253
1272
  if (!interactive) throw new Error("OpenRouter was selected, but no key was provided.");
1254
1273
  openrouterApiKey = (await promptSecretish("OpenRouter API key: ")).trim();
@@ -1276,11 +1295,11 @@ async function configureUserFunding(flags, selected, claudeAnthropicAuth,
1276
1295
  console.log("");
1277
1296
  }
1278
1297
 
1279
- if (wantsCodex && !codexSubscriptionAuth && !openaiApiKey && !openrouterApiKey) {
1298
+ if (wantsCodex && !codexSubscriptionAuth && !openaiApiKey && !openaiProxyApiKey && !openrouterApiKey) {
1280
1299
  if (!interactive) {
1281
1300
  throw new Error(
1282
1301
  "Codex requires ChatGPT login, --openai-api-key (or OPENAI_API_KEY), "
1283
- + "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.",
1284
1303
  );
1285
1304
  }
1286
1305
  console.log("Codex is not logged into ChatGPT and needs a user-funded provider.");
@@ -1306,6 +1325,10 @@ async function configureUserFunding(flags, selected, claudeAnthropicAuth,
1306
1325
  if (wantsCodex && openaiApiKey) {
1307
1326
  credentials.push({ provider: "openai_api", token: openaiApiKey });
1308
1327
  }
1328
+ if (wantsCodex && openaiProxyBaseUrl && openaiProxyApiKey) {
1329
+ credentials.push({ provider: "openai_proxy", token: openaiProxyApiKey,
1330
+ metadata: { base_url: openaiProxyBaseUrl } });
1331
+ }
1309
1332
  if ((wantsClaude || wantsCodex) && openrouterApiKey) {
1310
1333
  credentials.push({ provider: "openrouter", token: openrouterApiKey });
1311
1334
  }
@@ -1328,7 +1351,7 @@ async function configureUserFunding(flags, selected, claudeAnthropicAuth,
1328
1351
  claudeProxyBaseUrl: claudeProxyConfigured ? proxyBaseUrl : null,
1329
1352
  codexCredentialProvider: codexSubscriptionAuth
1330
1353
  ? "chatgpt-subscription"
1331
- : openaiApiKey ? "openai-api" : openrouterApiKey ? "openrouter" : null,
1354
+ : openaiApiKey ? "openai-api" : openaiProxyBaseUrl ? "openai-proxy" : openrouterApiKey ? "openrouter" : null,
1332
1355
  };
1333
1356
  }
1334
1357
 
@@ -2044,7 +2067,7 @@ function printHelp() {
2044
2067
  console.log(`PennyRouter CLI
2045
2068
 
2046
2069
  Usage:
2047
- 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]
2048
2071
  pennyrouter update [--harness ${SUPPORTED_HARNESS_IDS}] [--all] [--dry-run] [--penny-key pr-...] [--gateway-base-url URL|--local[=URL]|--prod] [--no-mcp]
2049
2072
  pennyrouter auth anthropic [--penny-key pr-...] [--token oauth-token] [--token-command CMD] [--gateway-base-url URL] [--forget]
2050
2073
  pennyrouter disable [--harness ${SUPPORTED_HARNESS_IDS}] [--all] [--yes]
@@ -2053,7 +2076,7 @@ Usage:
2053
2076
  pennyrouter status
2054
2077
  pennyrouter mcp install|uninstall|status [--harness claude-code,codex]
2055
2078
 
2056
- When an interactive install selects an Anthropic proxy, it asks for the PennyRouter gateway
2079
+ When an interactive install selects an Anthropic or OpenAI-compatible proxy, it asks for the PennyRouter gateway
2057
2080
  that can reach that proxy. Leave it blank for the hosted gateway, or enter a private/self-hosted
2058
2081
  gateway URL. Use --gateway-base-url (or --local for localhost:8400 development) for non-interactive installs.
2059
2082