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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/cli.js +46 -6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pennyrouter",
3
- "version": "0.2.33",
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] || "";
@@ -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"