pennyrouter 0.3.30 → 0.3.31

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.30",
3
+ "version": "0.3.31",
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
@@ -685,10 +685,13 @@ export async function install(flags = {}) {
685
685
  const selected = await selectHarnesses(flags);
686
686
  if (selected.length === 0) {
687
687
  console.log("No supported harnesses selected.");
688
+ await printAdditionalHarnessesHint([]);
688
689
  return;
689
690
  }
690
691
 
691
692
  console.log("PennyRouter install");
693
+ console.log("PennyRouter can be installed into multiple coding harnesses on this machine.");
694
+ await printAdditionalHarnessesHint(selected);
692
695
  console.log("");
693
696
 
694
697
  const manifest = await loadManifest();
@@ -1554,12 +1557,24 @@ async function selectClaudeFundingProviders(flags, selected) {
1554
1557
 
1555
1558
  console.log("PennyRouter can use your Claude subscription, an Anthropic API key, an OpenRouter API key, or your own Anthropic-compatible proxy to run Claude Code.");
1556
1559
  console.log("Choose one or more providers. PennyRouter will try them in order when available.");
1560
+ const subscriptionAvailable = Boolean(
1561
+ flags.token || flags.tokenCommand || process.env.ANTHROPIC_AUTH_TOKEN || commandExists("claude"),
1562
+ );
1563
+ const options = CLAUDE_PROVIDER_OPTIONS.filter(
1564
+ ({ id }) => id !== "subscription" || subscriptionAvailable,
1565
+ );
1557
1566
  return promptFundingProviderCheckboxes(
1558
- CLAUDE_PROVIDER_OPTIONS,
1559
- "Which providers should PennyRouter use for Claude Code?",
1567
+ options,
1568
+ "Which funding source should PennyRouter use for Claude Code?",
1560
1569
  // An enterprise gateway usually fronts the model provider itself, so the proxy is the
1561
1570
  // expected answer there and leads; elsewhere the subscription does.
1562
- new Set([flags.privateGatewayFlow ? "anthropic-proxy" : "subscription"]),
1571
+ new Set([
1572
+ flags.privateGatewayFlow && options.some(({ id }) => id === "anthropic-proxy")
1573
+ ? "anthropic-proxy"
1574
+ : options.some(({ id }) => id === "subscription")
1575
+ ? "subscription"
1576
+ : options[0]?.id,
1577
+ ].filter(Boolean)),
1563
1578
  );
1564
1579
  }
1565
1580
 
@@ -1580,10 +1595,17 @@ async function selectCodexFundingProviders(flags, selected) {
1580
1595
 
1581
1596
  console.log("PennyRouter can use your ChatGPT subscription, an OpenAI API key, an OpenAI-compatible proxy, or an OpenRouter API key to run Codex.");
1582
1597
  console.log("Choose one or more providers. PennyRouter will try them in order when available.");
1598
+ const options = CODEX_PROVIDER_OPTIONS.filter(
1599
+ ({ id }) => id !== "subscription" || hasChatgptLogin,
1600
+ );
1583
1601
  return promptFundingProviderCheckboxes(
1584
- CODEX_PROVIDER_OPTIONS,
1585
- "Which providers should PennyRouter use for Codex?",
1586
- new Set([flags.privateGatewayFlow ? "openai-proxy" : "subscription"]),
1602
+ options,
1603
+ "Which funding source should PennyRouter use for Codex?",
1604
+ new Set([
1605
+ flags.privateGatewayFlow && options.some(({ id }) => id === "openai-proxy")
1606
+ ? "openai-proxy"
1607
+ : options[0]?.id,
1608
+ ].filter(Boolean)),
1587
1609
  );
1588
1610
  }
1589
1611
 
@@ -1632,7 +1654,9 @@ async function maybeConfigureClaudeAnthropicAuth(flags, selected, enabled = true
1632
1654
  } else if (flags.fromConfig) {
1633
1655
  throw new Error("Claude Code was not found on PATH, so subscription sign-in could not run.");
1634
1656
  } else {
1635
- console.log("Claude Code was not found on PATH. Paste an Anthropic bearer token, or leave blank to skip.");
1657
+ // The provider picker only offers subscription auth when Claude Code can perform
1658
+ // setup-token (or the user supplied a token). Never send users into a dead manual flow.
1659
+ return null;
1636
1660
  }
1637
1661
  const token = normalizeAnthropicToken(await promptSecretish("Anthropic token (blank to skip): "));
1638
1662
  if (!token) {
@@ -2115,6 +2139,20 @@ async function selectHarnesses(flags) {
2115
2139
  return promptHarnessCheckboxes(detected);
2116
2140
  }
2117
2141
 
2142
+ async function printAdditionalHarnessesHint(selected) {
2143
+ const selectedIds = new Set(selected.map(({ id }) => id));
2144
+ const available = [];
2145
+ for (const harness of Object.values(HARNESS_BY_ID)) {
2146
+ if (selectedIds.has(harness.id)) continue;
2147
+ if (!(await safeDetect(harness))) available.push(harness.name);
2148
+ }
2149
+ if (available.length === 0) return;
2150
+ const names = available.length === 1
2151
+ ? available[0]
2152
+ : `${available.slice(0, -1).join(", ")}, or ${available.at(-1)}`;
2153
+ console.log(`To use PennyRouter with ${names}, install ${available.length === 1 ? "it" : "them"} and re-run \`npx pennyrouter install\`.`);
2154
+ }
2155
+
2118
2156
  async function promptHarnessCheckboxes(detected) {
2119
2157
  const detectedIds = new Set(detected.map((harness) => harness.id));
2120
2158
  const selectedIds = new Set(detectedIds);
@@ -86,6 +86,8 @@ export async function detectCodexChatgptAuth() {
86
86
  const status = spawnSync("codex", ["login", "status"], {
87
87
  encoding: "utf8",
88
88
  stdio: ["ignore", "pipe", "pipe"],
89
+ // Windows npm installs Codex as a .cmd shim, which requires shell resolution.
90
+ shell: process.platform === "win32",
89
91
  });
90
92
  return status.status === 0 && /logged in using chatgpt/i.test(`${status.stdout}\n${status.stderr}`);
91
93
  }