pennyrouter 0.3.25 → 0.3.26

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pennyrouter",
3
- "version": "0.3.25",
3
+ "version": "0.3.26",
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
@@ -19,7 +19,13 @@ import {
19
19
  } from "./harnesses/claude-code.js";
20
20
  import { clineHarness } from "./harnesses/cline.js";
21
21
  import { codeGptHarness } from "./harnesses/codegpt.js";
22
- import { codexHarness, detectCodexChatgptAuth, isHostedPennyRouterGateway } from "./harnesses/codex.js";
22
+ import {
23
+ codexHarness,
24
+ detectCodexChatgptAuth,
25
+ isCodexSubscriptionAuthGateway,
26
+ isHostedPennyRouterGateway,
27
+ isLocalPennyRouterGateway,
28
+ } from "./harnesses/codex.js";
23
29
  import { fetchOpenCodeModels, opencodeHarness } from "./harnesses/opencode.js";
24
30
  import { windsurfHarness } from "./harnesses/windsurf.js";
25
31
  import { zedHarness } from "./harnesses/zed.js";
@@ -39,7 +45,10 @@ import {
39
45
  } from "./session.js";
40
46
 
41
47
  async function codexNativeAuthAvailable({ gatewayBaseUrl, apiKey, localAuth }) {
42
- if (!isHostedPennyRouterGateway(gatewayBaseUrl)) return false;
48
+ if (!isCodexSubscriptionAuthGateway(gatewayBaseUrl)) return false;
49
+ // A local gateway should use Codex's detected login as the opt-in. Do not infer
50
+ // subscription auth from some other upstream credential stored on the local gateway.
51
+ if (isLocalPennyRouterGateway(gatewayBaseUrl)) return Boolean(localAuth);
43
52
  const capabilities = await fetchGatewayCapabilities({ gatewayBaseUrl, apiKey });
44
53
  const hasOpenAiCredential = Array.isArray(capabilities?.upstream_credentials)
45
54
  && capabilities.upstream_credentials.includes("openai_api");
@@ -103,12 +103,13 @@ export function renderCodexConfig(
103
103
  text,
104
104
  { apiKey, gatewayBaseUrl, subscriptionAuth, surface = "codex-terminal" },
105
105
  ) {
106
- // Native Codex subscription/WebSocket auth is valid only through PennyRouter's hosted
107
- // cloud gateway. Local, private, proxy, and other custom gateways must always use the
108
- // ordinary PennyRouter bearer provider, even when Codex itself is logged into ChatGPT.
106
+ // Native Codex ChatGPT subscription auth is allowed through the hosted gateway and the
107
+ // explicitly supported local development gateway. Other custom gateways use Penny auth.
109
108
  // Keep this guard at the final renderer so every install/update/desktop path gets the
110
109
  // same safety rule regardless of which profile selected it.
111
- subscriptionAuth = Boolean(subscriptionAuth && isHostedPennyRouterGateway(gatewayBaseUrl));
110
+ subscriptionAuth = Boolean(
111
+ subscriptionAuth && isCodexSubscriptionAuthGateway(gatewayBaseUrl),
112
+ );
112
113
  const clean = removeManagedCodexConfig(text, { removeRootProvider: true }).trimEnd();
113
114
  if (new RegExp(`^\\s*\\[model_providers\\.${escapeRegex(PROVIDER_ID)}\\]\\s*$`, "m").test(clean)) {
114
115
  throw new Error(
@@ -156,6 +157,20 @@ export function isHostedPennyRouterGateway(gatewayBaseUrl) {
156
157
  }
157
158
  }
158
159
 
160
+ export function isLocalPennyRouterGateway(gatewayBaseUrl) {
161
+ try {
162
+ const url = new URL(String(gatewayBaseUrl));
163
+ return url.hostname.toLowerCase() === "localhost" && url.port === "8400";
164
+ } catch {
165
+ return false;
166
+ }
167
+ }
168
+
169
+ export function isCodexSubscriptionAuthGateway(gatewayBaseUrl) {
170
+ return isHostedPennyRouterGateway(gatewayBaseUrl)
171
+ || isLocalPennyRouterGateway(gatewayBaseUrl);
172
+ }
173
+
159
174
  export function removeManagedCodexConfig(text, { removeRootProvider = false } = {}) {
160
175
  let out = String(text || "").replace(
161
176
  new RegExp(`${escapeRegex(MANAGED_START)}[\\s\\S]*?${escapeRegex(MANAGED_END)}\\n?`, "g"),
package/src/launch.js CHANGED
@@ -24,7 +24,10 @@ import {
24
24
  upgradeClaudeCodeModelValue,
25
25
  writeClaudeCodeModelPresentation,
26
26
  } from "./harnesses/claude-code.js";
27
- import { renderCodexConfig } from "./harnesses/codex.js";
27
+ import {
28
+ isCodexSubscriptionAuthGateway,
29
+ renderCodexConfig,
30
+ } from "./harnesses/codex.js";
28
31
  import {
29
32
  OPENCODE_MODEL_CHOICES,
30
33
  fetchOpenCodeModels,
@@ -440,16 +443,12 @@ export async function installPennyRuntime({ updatePath = true } = {}) {
440
443
  }
441
444
 
442
445
  export function renderCodexLaunchProfile({ gatewayBaseUrl, subscriptionAuth }) {
443
- // Private/proxy/custom gateways never use Codex's native subscription/WebSocket lane.
444
- // Apply the same hosted-only rule as the Codex config renderer at this launch-profile
446
+ // Native ChatGPT subscription auth is allowed for the hosted gateway and the explicitly
447
+ // supported local development gateway. Apply the same rule as the config renderer at this
445
448
  // boundary, since this path is what bare `penny codex` selects at runtime.
446
- try {
447
- subscriptionAuth = Boolean(
448
- subscriptionAuth && new URL(String(gatewayBaseUrl)).hostname.toLowerCase() === "api.pennyrouter.com",
449
- );
450
- } catch {
451
- subscriptionAuth = false;
452
- }
449
+ subscriptionAuth = Boolean(
450
+ subscriptionAuth && isCodexSubscriptionAuthGateway(gatewayBaseUrl),
451
+ );
453
452
  if (subscriptionAuth) {
454
453
  return [
455
454
  CODEX_PROFILE_MARKER,