pennyrouter 0.3.29 → 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 +1 -1
- package/src/cli.js +45 -7
- package/src/harnesses/codex.js +2 -0
- package/src/launch.js +8 -1
package/package.json
CHANGED
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
|
-
|
|
1559
|
-
"Which
|
|
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([
|
|
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
|
-
|
|
1585
|
-
"Which
|
|
1586
|
-
new Set([
|
|
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
|
-
|
|
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);
|
package/src/harnesses/codex.js
CHANGED
|
@@ -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
|
}
|
package/src/launch.js
CHANGED
|
@@ -505,7 +505,14 @@ export function renderCodexLaunchProfile({
|
|
|
505
505
|
}
|
|
506
506
|
|
|
507
507
|
export function codexDesktopExecutable() {
|
|
508
|
-
if (process.platform === "darwin")
|
|
508
|
+
if (process.platform === "darwin") {
|
|
509
|
+
const appPath = "/Applications/Codex.app";
|
|
510
|
+
const result = spawnSync("/usr/libexec/PlistBuddy", [
|
|
511
|
+
"-c", "Print :CFBundleExecutable", join(appPath, "Contents", "Info.plist"),
|
|
512
|
+
], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
513
|
+
const executable = result.status === 0 ? result.stdout.trim() : "";
|
|
514
|
+
return join(appPath, "Contents", "MacOS", executable || "Codex");
|
|
515
|
+
}
|
|
509
516
|
if (process.platform === "win32") return "Codex.exe";
|
|
510
517
|
return "codex";
|
|
511
518
|
}
|