pullfrog 0.1.79 → 0.1.81
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/dist/cli.mjs +231 -44
- package/dist/index.js +179 -35
- package/dist/internal/index.d.ts +2 -1
- package/dist/internal.js +163 -25
- package/dist/utils/billingErrors.d.ts +11 -2
- package/dist/utils/codexOAuth.d.ts +2 -3
- package/dist/utils/oauthShared.d.ts +7 -6
- package/dist/utils/providerDashboards.d.ts +38 -0
- package/dist/utils/runContext.d.ts +9 -9
- package/dist/utils/runContextData.d.ts +2 -1
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -108850,10 +108850,10 @@ function decodeJwtExpMs(token) {
|
|
|
108850
108850
|
// utils/codexOAuth.ts
|
|
108851
108851
|
var CODEX_OAUTH_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
108852
108852
|
var CODEX_OAUTH_TOKEN_URL = "https://auth.openai.com/oauth/token";
|
|
108853
|
-
function codexChainIsDead(body) {
|
|
108853
|
+
function codexChainIsDead(status, body) {
|
|
108854
|
+
if (status !== 401) return false;
|
|
108854
108855
|
const err = parseOAuthErrorBody(body)?.error;
|
|
108855
|
-
|
|
108856
|
-
return "code" in err && err.code === "token_expired";
|
|
108856
|
+
return !(err && typeof err === "object" && "code" in err && err.code === "invalid_client");
|
|
108857
108857
|
}
|
|
108858
108858
|
async function refreshCodexAuthBody(body) {
|
|
108859
108859
|
const response = await fetch(CODEX_OAUTH_TOKEN_URL, {
|
|
@@ -108869,7 +108869,12 @@ async function refreshCodexAuthBody(body) {
|
|
|
108869
108869
|
if (!response.ok) {
|
|
108870
108870
|
const text = await response.text().catch(() => "");
|
|
108871
108871
|
if (response.status >= 400 && response.status < 500) {
|
|
108872
|
-
throw new OAuthInvalidGrantError(
|
|
108872
|
+
throw new OAuthInvalidGrantError(
|
|
108873
|
+
"Codex",
|
|
108874
|
+
response.status,
|
|
108875
|
+
text,
|
|
108876
|
+
codexChainIsDead(response.status, text)
|
|
108877
|
+
);
|
|
108873
108878
|
}
|
|
108874
108879
|
throw new Error(`Codex token refresh failed: ${response.status} ${text}`);
|
|
108875
108880
|
}
|
|
@@ -123000,7 +123005,7 @@ var import_picocolors = __toESM(require_picocolors(), 1);
|
|
|
123000
123005
|
// package.json
|
|
123001
123006
|
var package_default = {
|
|
123002
123007
|
name: "pullfrog",
|
|
123003
|
-
version: "0.1.
|
|
123008
|
+
version: "0.1.81",
|
|
123004
123009
|
cliContractVersion: "1",
|
|
123005
123010
|
type: "module",
|
|
123006
123011
|
bin: {
|
|
@@ -123124,6 +123129,27 @@ var PULLFROG_API_URL = (process.env.PULLFROG_API_URL || "https://pullfrog.com").
|
|
|
123124
123129
|
/\/+$/,
|
|
123125
123130
|
""
|
|
123126
123131
|
);
|
|
123132
|
+
var CODEX_AUTH_SECRET = "CODEX_AUTH_JSON";
|
|
123133
|
+
var CLAUDE_OAUTH_SECRET = "CLAUDE_CODE_OAUTH_TOKEN";
|
|
123134
|
+
var GROK_AUTH_SECRET = "GROK_AUTH_JSON";
|
|
123135
|
+
var SUBSCRIPTION_SECRET_NAMES = [CODEX_AUTH_SECRET, CLAUDE_OAUTH_SECRET, GROK_AUTH_SECRET];
|
|
123136
|
+
function shadowRefusal(params) {
|
|
123137
|
+
const repos = params.overrides.filter((override) => override.name === params.name).map((override) => `${params.owner}/${override.repo}`);
|
|
123138
|
+
if (!repos.length) return null;
|
|
123139
|
+
const bin2 = process.env.PULLFROG_BIN_NAME || "pullfrog";
|
|
123140
|
+
return [
|
|
123141
|
+
`${import_picocolors.default.cyan(params.name)} is already set on ${repos.map((repo) => import_picocolors.default.cyan(repo)).join(", ")}.`,
|
|
123142
|
+
`a repo's own copy takes precedence, so saving to ${import_picocolors.default.cyan(params.owner)} would not change`,
|
|
123143
|
+
`what runs there read.`,
|
|
123144
|
+
``,
|
|
123145
|
+
`delete the repo copies, then run this command again:`,
|
|
123146
|
+
...repos.map(
|
|
123147
|
+
(repo) => ` ${import_picocolors.default.cyan(`npx ${bin2} secret delete ${params.name} --repo ${repo}`)}`
|
|
123148
|
+
),
|
|
123149
|
+
``,
|
|
123150
|
+
`${import_picocolors.default.dim("or:")} pass ${import_picocolors.default.cyan("--repo OWNER/REPO")} to replace one repo's own copy instead.`
|
|
123151
|
+
].join("\n");
|
|
123152
|
+
}
|
|
123127
123153
|
var activeSpin = null;
|
|
123128
123154
|
function setActiveSpin(spin) {
|
|
123129
123155
|
activeSpin = spin;
|
|
@@ -123360,6 +123386,9 @@ var secretNamesSchema = external_exports.object({
|
|
|
123360
123386
|
target: external_exports.string(),
|
|
123361
123387
|
secrets: external_exports.array(external_exports.string()),
|
|
123362
123388
|
inherited: external_exports.array(external_exports.string()),
|
|
123389
|
+
// `.default([])` so a CLI newer than its server — a preview deployment, a self-hosted
|
|
123390
|
+
// install — keeps working: every command parses this shape, not just `auth`.
|
|
123391
|
+
overrides: external_exports.array(external_exports.object({ name: external_exports.string(), repo: external_exports.string() })).default([]),
|
|
123363
123392
|
writable: external_exports.boolean()
|
|
123364
123393
|
});
|
|
123365
123394
|
async function runCli(input2) {
|
|
@@ -123429,6 +123458,10 @@ use auth codex, auth claude or auth grok for subscription credentials.`);
|
|
|
123429
123458
|
throw new Error("--file requires exactly one secret name");
|
|
123430
123459
|
if (file3 === void 0 && !process.stdin.isTTY)
|
|
123431
123460
|
throw new Error("use --file PATH or --file - without a terminal");
|
|
123461
|
+
for (const name of names.filter((secret) => SUBSCRIPTION_SECRET_NAMES.includes(secret))) {
|
|
123462
|
+
const refusal = shadowRefusal({ overrides: data.overrides, owner: target.owner, name });
|
|
123463
|
+
if (refusal) throw new Error(refusal);
|
|
123464
|
+
}
|
|
123432
123465
|
const secrets = [];
|
|
123433
123466
|
for (const name of names) {
|
|
123434
123467
|
const value2 = file3 === void 0 ? await password({
|
|
@@ -123445,9 +123478,6 @@ use auth codex, auth claude or auth grok for subscription credentials.`);
|
|
|
123445
123478
|
}
|
|
123446
123479
|
|
|
123447
123480
|
// commands/auth.ts
|
|
123448
|
-
var CODEX_AUTH_SECRET = "CODEX_AUTH_JSON";
|
|
123449
|
-
var CLAUDE_OAUTH_SECRET = "CLAUDE_CODE_OAUTH_TOKEN";
|
|
123450
|
-
var GROK_AUTH_SECRET = "GROK_AUTH_JSON";
|
|
123451
123481
|
var CLAUDE_OAUTH_TOKEN_PREFIX = "sk-ant-oat";
|
|
123452
123482
|
function stripAnsi(s) {
|
|
123453
123483
|
return s.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "");
|
|
@@ -123561,6 +123591,16 @@ async function runCodex(params) {
|
|
|
123561
123591
|
if (parsed2._.length) throw new Error("unexpected auth argument");
|
|
123562
123592
|
await runCodexAuth(parsed2);
|
|
123563
123593
|
}
|
|
123594
|
+
function refuseWhenRepoCopiesShadow(params) {
|
|
123595
|
+
const refusal = shadowRefusal({
|
|
123596
|
+
overrides: params.access.overrides,
|
|
123597
|
+
owner: params.owner,
|
|
123598
|
+
name: params.name
|
|
123599
|
+
});
|
|
123600
|
+
if (!refusal) return;
|
|
123601
|
+
log.warn(refusal);
|
|
123602
|
+
bail("nothing saved.");
|
|
123603
|
+
}
|
|
123564
123604
|
async function runCodexAuth(parsed2) {
|
|
123565
123605
|
intro(import_picocolors2.default.bgGreen(import_picocolors2.default.black(" pullfrog auth codex ")));
|
|
123566
123606
|
const spin = spinner();
|
|
@@ -123601,6 +123641,7 @@ async function runCodexAuth(parsed2) {
|
|
|
123601
123641
|
bail(
|
|
123602
123642
|
scope2 === "repo" ? "repo admin required to change secrets" : "org owner required to change secrets"
|
|
123603
123643
|
);
|
|
123644
|
+
refuseWhenRepoCopiesShadow({ access, owner: remote.owner, name: CODEX_AUTH_SECRET });
|
|
123604
123645
|
if (access.secrets.includes(CODEX_AUTH_SECRET)) {
|
|
123605
123646
|
const overwrite = await select({
|
|
123606
123647
|
message: `${import_picocolors2.default.cyan(CODEX_AUTH_SECRET)} is already configured \u2014 overwrite?`,
|
|
@@ -123777,6 +123818,7 @@ async function runClaudeAuth(parsed2) {
|
|
|
123777
123818
|
bail(
|
|
123778
123819
|
scope2 === "repo" ? "repo admin required to change secrets" : "org owner required to change secrets"
|
|
123779
123820
|
);
|
|
123821
|
+
refuseWhenRepoCopiesShadow({ access, owner: remote.owner, name: CLAUDE_OAUTH_SECRET });
|
|
123780
123822
|
if (access.secrets.includes(CLAUDE_OAUTH_SECRET)) {
|
|
123781
123823
|
const overwrite = await select({
|
|
123782
123824
|
message: `${import_picocolors2.default.cyan(CLAUDE_OAUTH_SECRET)} is already configured \u2014 overwrite?`,
|
|
@@ -123911,6 +123953,7 @@ async function runGrokAuth(parsed2) {
|
|
|
123911
123953
|
bail(
|
|
123912
123954
|
scope2 === "repo" ? "repo admin required to change secrets" : "org owner required to change secrets"
|
|
123913
123955
|
);
|
|
123956
|
+
refuseWhenRepoCopiesShadow({ access, owner: remote.owner, name: GROK_AUTH_SECRET });
|
|
123914
123957
|
if (access.secrets.includes(GROK_AUTH_SECRET)) {
|
|
123915
123958
|
const overwrite = await select({
|
|
123916
123959
|
message: `${import_picocolors2.default.cyan(GROK_AUTH_SECRET)} is already configured \u2014 overwrite?`,
|
|
@@ -125535,9 +125578,9 @@ var providers = {
|
|
|
125535
125578
|
},
|
|
125536
125579
|
"gpt-5-nano": {
|
|
125537
125580
|
displayName: "GPT Nano",
|
|
125538
|
-
resolve: "opencode/gpt-5-nano",
|
|
125539
|
-
effort: ["
|
|
125540
|
-
openRouterResolve: "openrouter/openai/gpt-5-nano"
|
|
125581
|
+
resolve: "opencode/gpt-5.4-nano",
|
|
125582
|
+
effort: ["none", "low", "medium", "high", "xhigh"],
|
|
125583
|
+
openRouterResolve: "openrouter/openai/gpt-5.4-nano"
|
|
125541
125584
|
},
|
|
125542
125585
|
// Zen's PAID Muse Spark, priced like Meta's own Standard tier. answered
|
|
125543
125586
|
// `OK` through the harness on 2026-09-12 once the workspace was funded.
|
|
@@ -125560,6 +125603,17 @@ var providers = {
|
|
|
125560
125603
|
// see the big-pickle note above (#1077).
|
|
125561
125604
|
isFree: true
|
|
125562
125605
|
},
|
|
125606
|
+
// Zen's FREE stealth preview (listed 2026-09-16), the big-pickle shape: an
|
|
125607
|
+
// anonymous vendor's model at $0 for a limited time. OpenRouter's listing:
|
|
125608
|
+
// prompts and completions may be retained, not trained on.
|
|
125609
|
+
"union-alpha": {
|
|
125610
|
+
displayName: "Union Alpha",
|
|
125611
|
+
description: "Stealth preview; prompts may be retained",
|
|
125612
|
+
resolve: "opencode/union-alpha",
|
|
125613
|
+
// free to run, still gated on the provider's own OPENCODE_API_KEY —
|
|
125614
|
+
// see the big-pickle note above (#1077).
|
|
125615
|
+
isFree: true
|
|
125616
|
+
},
|
|
125563
125617
|
// Zen's live free MiMo, and the second free row in a menu that big-pickle
|
|
125564
125618
|
// was alone in since `mimo-v2-pro-free` lost its model.
|
|
125565
125619
|
mimo: {
|
|
@@ -215186,6 +215240,115 @@ function subagentDeniedToolNames(ctx, outputSchema) {
|
|
|
215186
215240
|
return names;
|
|
215187
215241
|
}
|
|
215188
215242
|
|
|
215243
|
+
// utils/providerDashboards.ts
|
|
215244
|
+
var PROVIDER_DASHBOARDS = {
|
|
215245
|
+
anthropic: {
|
|
215246
|
+
name: "Anthropic",
|
|
215247
|
+
billing: "https://platform.claude.com/settings/billing",
|
|
215248
|
+
usage: "https://platform.claude.com/usage",
|
|
215249
|
+
keys: "https://platform.claude.com/settings/keys"
|
|
215250
|
+
},
|
|
215251
|
+
"claude-plan": {
|
|
215252
|
+
name: "Claude Pro/Max",
|
|
215253
|
+
credential: "CLAUDE_CODE_OAUTH_TOKEN",
|
|
215254
|
+
billing: "https://claude.ai/settings/billing",
|
|
215255
|
+
usage: "https://claude.ai/settings/usage"
|
|
215256
|
+
},
|
|
215257
|
+
openai: {
|
|
215258
|
+
name: "OpenAI",
|
|
215259
|
+
billing: "https://platform.openai.com/settings/organization/billing/overview",
|
|
215260
|
+
usage: "https://platform.openai.com/settings/organization/usage",
|
|
215261
|
+
keys: "https://platform.openai.com/api-keys"
|
|
215262
|
+
},
|
|
215263
|
+
"codex-plan": {
|
|
215264
|
+
name: "ChatGPT/Codex",
|
|
215265
|
+
credential: "CODEX_AUTH_JSON",
|
|
215266
|
+
billing: "https://chatgpt.com/#settings/Subscription",
|
|
215267
|
+
usage: "https://chatgpt.com/codex/settings/usage"
|
|
215268
|
+
},
|
|
215269
|
+
google: {
|
|
215270
|
+
name: "Google AI Studio",
|
|
215271
|
+
// AI Studio's "Usage & billing" page is where the Gemini API plan lives;
|
|
215272
|
+
// the Cloud billing console is a level up and needs a project picked.
|
|
215273
|
+
billing: "https://aistudio.google.com/usage",
|
|
215274
|
+
keys: "https://aistudio.google.com/apikey"
|
|
215275
|
+
},
|
|
215276
|
+
vertex: {
|
|
215277
|
+
name: "Google Vertex AI",
|
|
215278
|
+
billing: "https://console.cloud.google.com/billing",
|
|
215279
|
+
usage: "https://console.cloud.google.com/vertex-ai"
|
|
215280
|
+
},
|
|
215281
|
+
xai: {
|
|
215282
|
+
name: "xAI",
|
|
215283
|
+
billing: "https://console.x.ai/team/default/billing",
|
|
215284
|
+
usage: "https://console.x.ai/team/default/usage",
|
|
215285
|
+
keys: "https://console.x.ai/team/default/api-keys"
|
|
215286
|
+
},
|
|
215287
|
+
"grok-plan": {
|
|
215288
|
+
name: "SuperGrok",
|
|
215289
|
+
credential: "GROK_AUTH_JSON",
|
|
215290
|
+
billing: "https://grok.com/?_s=billing"
|
|
215291
|
+
},
|
|
215292
|
+
deepseek: {
|
|
215293
|
+
name: "DeepSeek",
|
|
215294
|
+
billing: "https://platform.deepseek.com/top_up",
|
|
215295
|
+
usage: "https://platform.deepseek.com/usage",
|
|
215296
|
+
keys: "https://platform.deepseek.com/api_keys"
|
|
215297
|
+
},
|
|
215298
|
+
moonshotai: {
|
|
215299
|
+
name: "Moonshot (Kimi API)",
|
|
215300
|
+
billing: "https://platform.kimi.ai/console/pay",
|
|
215301
|
+
usage: "https://platform.kimi.ai/console/account",
|
|
215302
|
+
keys: "https://platform.kimi.ai/console/api-keys"
|
|
215303
|
+
},
|
|
215304
|
+
"kimi-for-coding": {
|
|
215305
|
+
name: "Kimi Code",
|
|
215306
|
+
billing: "https://www.kimi.com/membership/pricing",
|
|
215307
|
+
keys: "https://www.kimi.com/code/console"
|
|
215308
|
+
},
|
|
215309
|
+
meta: {
|
|
215310
|
+
name: "Meta Model API",
|
|
215311
|
+
billing: "https://dev.meta.ai/billing",
|
|
215312
|
+
usage: "https://dev.meta.ai/usage",
|
|
215313
|
+
keys: "https://dev.meta.ai/api-keys"
|
|
215314
|
+
},
|
|
215315
|
+
opencode: {
|
|
215316
|
+
name: "OpenCode Zen",
|
|
215317
|
+
billing: "https://opencode.ai/zen",
|
|
215318
|
+
keys: "https://opencode.ai/zen"
|
|
215319
|
+
},
|
|
215320
|
+
// Go is a separate plan on the same key, with its own usage cap that resets
|
|
215321
|
+
// on its own (wiki/opencode-silent-stall.md). Its page is the plan page; no
|
|
215322
|
+
// per-window usage page is documented.
|
|
215323
|
+
"opencode-go": {
|
|
215324
|
+
name: "OpenCode Go",
|
|
215325
|
+
billing: "https://opencode.ai/go",
|
|
215326
|
+
keys: "https://opencode.ai/zen"
|
|
215327
|
+
},
|
|
215328
|
+
openrouter: {
|
|
215329
|
+
name: "OpenRouter",
|
|
215330
|
+
billing: "https://openrouter.ai/settings/credits",
|
|
215331
|
+
usage: "https://openrouter.ai/activity",
|
|
215332
|
+
keys: "https://openrouter.ai/workspaces/default/keys"
|
|
215333
|
+
},
|
|
215334
|
+
vercel: {
|
|
215335
|
+
name: "Vercel AI Gateway",
|
|
215336
|
+
// `/d?to=` resolves `[team]` to whichever team the visitor is signed into.
|
|
215337
|
+
billing: "https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai-gateway",
|
|
215338
|
+
keys: "https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai-gateway%2Fapi-keys"
|
|
215339
|
+
},
|
|
215340
|
+
bedrock: {
|
|
215341
|
+
name: "Amazon Bedrock",
|
|
215342
|
+
billing: "https://console.aws.amazon.com/billing/home",
|
|
215343
|
+
keys: "https://us-east-1.console.aws.amazon.com/bedrock/home#/api-keys"
|
|
215344
|
+
},
|
|
215345
|
+
// Azure OpenAI keys are per-resource, so there is no stable keys page to name.
|
|
215346
|
+
azure: {
|
|
215347
|
+
name: "Azure OpenAI",
|
|
215348
|
+
billing: "https://portal.azure.com/#view/Microsoft_Azure_CostManagement/Menu/~/overview"
|
|
215349
|
+
}
|
|
215350
|
+
};
|
|
215351
|
+
|
|
215189
215352
|
// utils/apiKeys.ts
|
|
215190
215353
|
var MISSING_KEY_MARKER = "no API key found";
|
|
215191
215354
|
var BYOK_SLUG_MARKER = "env var is required when the model is set to";
|
|
@@ -215510,14 +215673,22 @@ function buildRejectedCredentialError(params) {
|
|
|
215510
215673
|
].join("\n");
|
|
215511
215674
|
}
|
|
215512
215675
|
const where = params.inPullfrogStore ? `[Update it in Pullfrog \u2192](${settingsUrl})` : `[Update the GitHub Actions secret \u2192](https://github.com/${params.owner}/${params.name}/settings/secrets/actions)`;
|
|
215676
|
+
const keysUrl = providerKeysUrl(params.credential);
|
|
215677
|
+
const issue4 = keysUrl ? `[Issue a new key \u2192](${keysUrl}) \xB7 ` : "";
|
|
215513
215678
|
return [
|
|
215514
215679
|
`**Your \`${params.credential}\` ${CREDENTIAL_REJECTED_MARKER}**${detail}, so the agent never ran.`,
|
|
215515
215680
|
"",
|
|
215516
215681
|
`**To fix:** issue a new key in your provider dashboard and update the copy Pullfrog uses${params.inPullfrogStore ? " in the console" : " in your repo's GitHub Actions secrets"}.`,
|
|
215517
215682
|
"",
|
|
215518
|
-
`${where} \xB7 [Model settings \u2192](${settingsUrl}) \xB7 [Setup docs \u2192](https://docs.pullfrog.com/keys) \xB7 [Ask in Discord \u2192](https://discord.gg/8y96raFg8e)`
|
|
215683
|
+
`${issue4}${where} \xB7 [Model settings \u2192](${settingsUrl}) \xB7 [Setup docs \u2192](https://docs.pullfrog.com/keys) \xB7 [Ask in Discord \u2192](https://discord.gg/8y96raFg8e)`
|
|
215519
215684
|
].join("\n");
|
|
215520
215685
|
}
|
|
215686
|
+
function providerKeysUrl(credential) {
|
|
215687
|
+
for (const [id, provider2] of Object.entries(providers)) {
|
|
215688
|
+
if (provider2.envVars.includes(credential)) return PROVIDER_DASHBOARDS[id]?.keys;
|
|
215689
|
+
}
|
|
215690
|
+
return void 0;
|
|
215691
|
+
}
|
|
215521
215692
|
function formatApiKeyErrorSummary(params) {
|
|
215522
215693
|
if (params.raw.includes(MISSING_KEY_MARKER)) {
|
|
215523
215694
|
if (params.raw.startsWith(`**${MISSING_KEY_MARKER}**`)) return params.raw;
|
|
@@ -215709,23 +215880,35 @@ function billingConsoleUrl(owner) {
|
|
|
215709
215880
|
return `https://pullfrog.com/console/${encodeURIComponent(owner)}#billing`;
|
|
215710
215881
|
}
|
|
215711
215882
|
function commercialPaywallBody(params) {
|
|
215712
|
-
|
|
215713
|
-
|
|
215714
|
-
|
|
215715
|
-
|
|
215716
|
-
|
|
215717
|
-
|
|
215718
|
-
|
|
215719
|
-
|
|
215883
|
+
const pitch = "Pro is required on private organization repos like this one: a flat $30/month per organization for unlimited runs, members, and repos.";
|
|
215884
|
+
switch (params.reason) {
|
|
215885
|
+
case "commercial":
|
|
215886
|
+
return [
|
|
215887
|
+
`**${params.ownerLogin}'s Pullfrog trial has expired.**`,
|
|
215888
|
+
"",
|
|
215889
|
+
pitch,
|
|
215890
|
+
"",
|
|
215891
|
+
`[Upgrade now \u2192](${params.url})`
|
|
215892
|
+
].join("\n");
|
|
215893
|
+
case "subscription_ended":
|
|
215894
|
+
return [
|
|
215895
|
+
`**${params.ownerLogin}'s Pullfrog Pro subscription has ended.**`,
|
|
215896
|
+
"",
|
|
215897
|
+
pitch,
|
|
215898
|
+
"",
|
|
215899
|
+
`[Resubscribe \u2192](${params.url})`
|
|
215900
|
+
].join("\n");
|
|
215901
|
+
case "subscription_unpaid":
|
|
215902
|
+
return [
|
|
215903
|
+
`**Pullfrog paused runs on ${params.ownerLogin}: the Pro renewal failed.**`,
|
|
215904
|
+
"",
|
|
215905
|
+
"Update the card on file to resume runs.",
|
|
215906
|
+
"",
|
|
215907
|
+
`[Update billing \u2192](${params.url})`
|
|
215908
|
+
].join("\n");
|
|
215909
|
+
default:
|
|
215910
|
+
return params.reason;
|
|
215720
215911
|
}
|
|
215721
|
-
params.reason;
|
|
215722
|
-
return [
|
|
215723
|
-
`**Pullfrog paused runs on ${params.ownerLogin}: the Pro renewal failed.**`,
|
|
215724
|
-
"",
|
|
215725
|
-
"Update the card on file to resume runs.",
|
|
215726
|
-
"",
|
|
215727
|
-
`[Update billing \u2192](${params.url})`
|
|
215728
|
-
].join("\n");
|
|
215729
215912
|
}
|
|
215730
215913
|
function formatCommercialGateSummary(params) {
|
|
215731
215914
|
return commercialPaywallBody({
|
|
@@ -215777,6 +215960,12 @@ function formatBillingErrorSummary(error109, owner) {
|
|
|
215777
215960
|
ownerLogin: owner
|
|
215778
215961
|
});
|
|
215779
215962
|
}
|
|
215963
|
+
if (error109.code === "subscription_ended") {
|
|
215964
|
+
return formatCommercialGateSummary({
|
|
215965
|
+
reason: "subscription_ended",
|
|
215966
|
+
ownerLogin: owner
|
|
215967
|
+
});
|
|
215968
|
+
}
|
|
215780
215969
|
if (error109.code === "subscription_unpaid") {
|
|
215781
215970
|
return formatCommercialGateSummary({
|
|
215782
215971
|
reason: "subscription_unpaid",
|
|
@@ -217689,8 +217878,9 @@ async function fetchRunContext(params) {
|
|
|
217689
217878
|
clearTimeout(timeoutId);
|
|
217690
217879
|
if (response.status === 402) {
|
|
217691
217880
|
const body = await response.json().catch(() => null);
|
|
217692
|
-
const reason = typeof body === "object" && body !== null && "reason" in body
|
|
217693
|
-
|
|
217881
|
+
const reason = typeof body === "object" && body !== null && "reason" in body ? body.reason : null;
|
|
217882
|
+
const commercialRefused = reason === "subscription_unpaid" || reason === "subscription_ended" ? reason : "commercial";
|
|
217883
|
+
return { ...defaultRunContext, commercialRefused };
|
|
217694
217884
|
}
|
|
217695
217885
|
if (!response.ok) {
|
|
217696
217886
|
return response.status >= 500 ? unknownSecretsRunContext : defaultRunContext;
|
|
@@ -217930,15 +218120,9 @@ function formatMinimalFailureComment(repo) {
|
|
|
217930
218120
|
const url5 = `${server}/${repo.owner}/${repo.name}/actions/runs/${runId}`;
|
|
217931
218121
|
return `**Run failed.** [View the logs \u2192](${url5})`;
|
|
217932
218122
|
}
|
|
217933
|
-
var PROVIDER_BILLING_URLS =
|
|
217934
|
-
|
|
217935
|
-
|
|
217936
|
-
openai: "https://platform.openai.com/account/billing",
|
|
217937
|
-
google: "https://aistudio.google.com/usage",
|
|
217938
|
-
opencode: "https://opencode.ai/zen",
|
|
217939
|
-
xai: "https://console.x.ai/team/default/billing",
|
|
217940
|
-
openrouter: "https://openrouter.ai/settings/credits"
|
|
217941
|
-
};
|
|
218123
|
+
var PROVIDER_BILLING_URLS = Object.fromEntries(
|
|
218124
|
+
Object.entries(PROVIDER_DASHBOARDS).map(([id, dashboard]) => [id, dashboard.billing])
|
|
218125
|
+
);
|
|
217942
218126
|
function detectProviderId(message) {
|
|
217943
218127
|
const harnessId = extractProviderId(message);
|
|
217944
218128
|
if (harnessId) return harnessId;
|
|
@@ -217949,7 +218133,6 @@ function detectProviderId(message) {
|
|
|
217949
218133
|
}
|
|
217950
218134
|
return null;
|
|
217951
218135
|
}
|
|
217952
|
-
var OPENROUTER_KEYS_URL = "https://openrouter.ai/workspaces/default/keys";
|
|
217953
218136
|
var OPENROUTER_PRIVACY_URL = "https://openrouter.ai/settings/privacy";
|
|
217954
218137
|
function formatProviderBillingExhausted(input2) {
|
|
217955
218138
|
const providerId = detectProviderId(input2.errorMessage);
|
|
@@ -217960,7 +218143,7 @@ function formatProviderBillingExhausted(input2) {
|
|
|
217960
218143
|
"",
|
|
217961
218144
|
"OpenRouter refused the request against the key's own spending ceiling, not your account balance \u2014 raising or removing that limit is the fix, and topping up credits alone may not be.",
|
|
217962
218145
|
"",
|
|
217963
|
-
`[Raise the key's limit \u2192](${
|
|
218146
|
+
`[Raise the key's limit \u2192](${PROVIDER_DASHBOARDS.openrouter.keys}) \xB7 [Add credits \u2192](${PROVIDER_BILLING_URLS.openrouter})`,
|
|
217964
218147
|
"",
|
|
217965
218148
|
`\`\`\`
|
|
217966
218149
|
${input2.errorMessage}
|
|
@@ -218894,7 +219077,11 @@ async function main() {
|
|
|
218894
219077
|
authorPermission: void 0,
|
|
218895
219078
|
oidc: oidcCredentials
|
|
218896
219079
|
}), true);
|
|
218897
|
-
const errorMessage =
|
|
219080
|
+
const errorMessage = {
|
|
219081
|
+
commercial: "Pro trial expired for this organization",
|
|
219082
|
+
subscription_ended: "Pro subscription ended for this organization",
|
|
219083
|
+
subscription_unpaid: "Pro renewal failed for this organization"
|
|
219084
|
+
}[runContext.commercialRefused];
|
|
218898
219085
|
log2.error(errorMessage);
|
|
218899
219086
|
const body = formatCommercialGateSummary({
|
|
218900
219087
|
reason: runContext.commercialRefused,
|
|
@@ -219985,7 +220172,7 @@ function resolveTarget2(repo) {
|
|
|
219985
220172
|
return { owner: match3[1], repo: match3[2] };
|
|
219986
220173
|
}
|
|
219987
220174
|
function cliVersion() {
|
|
219988
|
-
const parts = /^(\d+)\.(\d+)\.(\d+)$/.exec("0.1.
|
|
220175
|
+
const parts = /^(\d+)\.(\d+)\.(\d+)$/.exec("0.1.81");
|
|
219989
220176
|
if (!parts) return "0.0.0";
|
|
219990
220177
|
return `${Number(parts[1])}.${Number(parts[2])}.${Number(parts[3])}`;
|
|
219991
220178
|
}
|
|
@@ -220152,7 +220339,7 @@ async function runCli8(input2) {
|
|
|
220152
220339
|
}
|
|
220153
220340
|
|
|
220154
220341
|
// cli.ts
|
|
220155
|
-
var VERSION10 = "0.1.
|
|
220342
|
+
var VERSION10 = "0.1.81";
|
|
220156
220343
|
var bin = basename2(process.argv[1] || "");
|
|
220157
220344
|
var PROG = bin === "pf" || bin === "pullfrog" ? bin : "pullfrog";
|
|
220158
220345
|
var rawArgs = process.argv.slice(2);
|
package/dist/index.js
CHANGED
|
@@ -108354,9 +108354,9 @@ var providers = {
|
|
|
108354
108354
|
},
|
|
108355
108355
|
"gpt-5-nano": {
|
|
108356
108356
|
displayName: "GPT Nano",
|
|
108357
|
-
resolve: "opencode/gpt-5-nano",
|
|
108358
|
-
effort: ["
|
|
108359
|
-
openRouterResolve: "openrouter/openai/gpt-5-nano"
|
|
108357
|
+
resolve: "opencode/gpt-5.4-nano",
|
|
108358
|
+
effort: ["none", "low", "medium", "high", "xhigh"],
|
|
108359
|
+
openRouterResolve: "openrouter/openai/gpt-5.4-nano"
|
|
108360
108360
|
},
|
|
108361
108361
|
// Zen's PAID Muse Spark, priced like Meta's own Standard tier. answered
|
|
108362
108362
|
// `OK` through the harness on 2026-09-12 once the workspace was funded.
|
|
@@ -108379,6 +108379,17 @@ var providers = {
|
|
|
108379
108379
|
// see the big-pickle note above (#1077).
|
|
108380
108380
|
isFree: true
|
|
108381
108381
|
},
|
|
108382
|
+
// Zen's FREE stealth preview (listed 2026-09-16), the big-pickle shape: an
|
|
108383
|
+
// anonymous vendor's model at $0 for a limited time. OpenRouter's listing:
|
|
108384
|
+
// prompts and completions may be retained, not trained on.
|
|
108385
|
+
"union-alpha": {
|
|
108386
|
+
displayName: "Union Alpha",
|
|
108387
|
+
description: "Stealth preview; prompts may be retained",
|
|
108388
|
+
resolve: "opencode/union-alpha",
|
|
108389
|
+
// free to run, still gated on the provider's own OPENCODE_API_KEY —
|
|
108390
|
+
// see the big-pickle note above (#1077).
|
|
108391
|
+
isFree: true
|
|
108392
|
+
},
|
|
108382
108393
|
// Zen's live free MiMo, and the second free row in a menu that big-pickle
|
|
108383
108394
|
// was alone in since `mimo-v2-pro-free` lost its model.
|
|
108384
108395
|
mimo: {
|
|
@@ -109975,7 +109986,7 @@ var import_semver = __toESM(require_semver2(), 1);
|
|
|
109975
109986
|
// package.json
|
|
109976
109987
|
var package_default = {
|
|
109977
109988
|
name: "pullfrog",
|
|
109978
|
-
version: "0.1.
|
|
109989
|
+
version: "0.1.81",
|
|
109979
109990
|
cliContractVersion: "1",
|
|
109980
109991
|
type: "module",
|
|
109981
109992
|
bin: {
|
|
@@ -198046,6 +198057,115 @@ function subagentDeniedToolNames(ctx, outputSchema) {
|
|
|
198046
198057
|
return names;
|
|
198047
198058
|
}
|
|
198048
198059
|
|
|
198060
|
+
// utils/providerDashboards.ts
|
|
198061
|
+
var PROVIDER_DASHBOARDS = {
|
|
198062
|
+
anthropic: {
|
|
198063
|
+
name: "Anthropic",
|
|
198064
|
+
billing: "https://platform.claude.com/settings/billing",
|
|
198065
|
+
usage: "https://platform.claude.com/usage",
|
|
198066
|
+
keys: "https://platform.claude.com/settings/keys"
|
|
198067
|
+
},
|
|
198068
|
+
"claude-plan": {
|
|
198069
|
+
name: "Claude Pro/Max",
|
|
198070
|
+
credential: "CLAUDE_CODE_OAUTH_TOKEN",
|
|
198071
|
+
billing: "https://claude.ai/settings/billing",
|
|
198072
|
+
usage: "https://claude.ai/settings/usage"
|
|
198073
|
+
},
|
|
198074
|
+
openai: {
|
|
198075
|
+
name: "OpenAI",
|
|
198076
|
+
billing: "https://platform.openai.com/settings/organization/billing/overview",
|
|
198077
|
+
usage: "https://platform.openai.com/settings/organization/usage",
|
|
198078
|
+
keys: "https://platform.openai.com/api-keys"
|
|
198079
|
+
},
|
|
198080
|
+
"codex-plan": {
|
|
198081
|
+
name: "ChatGPT/Codex",
|
|
198082
|
+
credential: "CODEX_AUTH_JSON",
|
|
198083
|
+
billing: "https://chatgpt.com/#settings/Subscription",
|
|
198084
|
+
usage: "https://chatgpt.com/codex/settings/usage"
|
|
198085
|
+
},
|
|
198086
|
+
google: {
|
|
198087
|
+
name: "Google AI Studio",
|
|
198088
|
+
// AI Studio's "Usage & billing" page is where the Gemini API plan lives;
|
|
198089
|
+
// the Cloud billing console is a level up and needs a project picked.
|
|
198090
|
+
billing: "https://aistudio.google.com/usage",
|
|
198091
|
+
keys: "https://aistudio.google.com/apikey"
|
|
198092
|
+
},
|
|
198093
|
+
vertex: {
|
|
198094
|
+
name: "Google Vertex AI",
|
|
198095
|
+
billing: "https://console.cloud.google.com/billing",
|
|
198096
|
+
usage: "https://console.cloud.google.com/vertex-ai"
|
|
198097
|
+
},
|
|
198098
|
+
xai: {
|
|
198099
|
+
name: "xAI",
|
|
198100
|
+
billing: "https://console.x.ai/team/default/billing",
|
|
198101
|
+
usage: "https://console.x.ai/team/default/usage",
|
|
198102
|
+
keys: "https://console.x.ai/team/default/api-keys"
|
|
198103
|
+
},
|
|
198104
|
+
"grok-plan": {
|
|
198105
|
+
name: "SuperGrok",
|
|
198106
|
+
credential: "GROK_AUTH_JSON",
|
|
198107
|
+
billing: "https://grok.com/?_s=billing"
|
|
198108
|
+
},
|
|
198109
|
+
deepseek: {
|
|
198110
|
+
name: "DeepSeek",
|
|
198111
|
+
billing: "https://platform.deepseek.com/top_up",
|
|
198112
|
+
usage: "https://platform.deepseek.com/usage",
|
|
198113
|
+
keys: "https://platform.deepseek.com/api_keys"
|
|
198114
|
+
},
|
|
198115
|
+
moonshotai: {
|
|
198116
|
+
name: "Moonshot (Kimi API)",
|
|
198117
|
+
billing: "https://platform.kimi.ai/console/pay",
|
|
198118
|
+
usage: "https://platform.kimi.ai/console/account",
|
|
198119
|
+
keys: "https://platform.kimi.ai/console/api-keys"
|
|
198120
|
+
},
|
|
198121
|
+
"kimi-for-coding": {
|
|
198122
|
+
name: "Kimi Code",
|
|
198123
|
+
billing: "https://www.kimi.com/membership/pricing",
|
|
198124
|
+
keys: "https://www.kimi.com/code/console"
|
|
198125
|
+
},
|
|
198126
|
+
meta: {
|
|
198127
|
+
name: "Meta Model API",
|
|
198128
|
+
billing: "https://dev.meta.ai/billing",
|
|
198129
|
+
usage: "https://dev.meta.ai/usage",
|
|
198130
|
+
keys: "https://dev.meta.ai/api-keys"
|
|
198131
|
+
},
|
|
198132
|
+
opencode: {
|
|
198133
|
+
name: "OpenCode Zen",
|
|
198134
|
+
billing: "https://opencode.ai/zen",
|
|
198135
|
+
keys: "https://opencode.ai/zen"
|
|
198136
|
+
},
|
|
198137
|
+
// Go is a separate plan on the same key, with its own usage cap that resets
|
|
198138
|
+
// on its own (wiki/opencode-silent-stall.md). Its page is the plan page; no
|
|
198139
|
+
// per-window usage page is documented.
|
|
198140
|
+
"opencode-go": {
|
|
198141
|
+
name: "OpenCode Go",
|
|
198142
|
+
billing: "https://opencode.ai/go",
|
|
198143
|
+
keys: "https://opencode.ai/zen"
|
|
198144
|
+
},
|
|
198145
|
+
openrouter: {
|
|
198146
|
+
name: "OpenRouter",
|
|
198147
|
+
billing: "https://openrouter.ai/settings/credits",
|
|
198148
|
+
usage: "https://openrouter.ai/activity",
|
|
198149
|
+
keys: "https://openrouter.ai/workspaces/default/keys"
|
|
198150
|
+
},
|
|
198151
|
+
vercel: {
|
|
198152
|
+
name: "Vercel AI Gateway",
|
|
198153
|
+
// `/d?to=` resolves `[team]` to whichever team the visitor is signed into.
|
|
198154
|
+
billing: "https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai-gateway",
|
|
198155
|
+
keys: "https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai-gateway%2Fapi-keys"
|
|
198156
|
+
},
|
|
198157
|
+
bedrock: {
|
|
198158
|
+
name: "Amazon Bedrock",
|
|
198159
|
+
billing: "https://console.aws.amazon.com/billing/home",
|
|
198160
|
+
keys: "https://us-east-1.console.aws.amazon.com/bedrock/home#/api-keys"
|
|
198161
|
+
},
|
|
198162
|
+
// Azure OpenAI keys are per-resource, so there is no stable keys page to name.
|
|
198163
|
+
azure: {
|
|
198164
|
+
name: "Azure OpenAI",
|
|
198165
|
+
billing: "https://portal.azure.com/#view/Microsoft_Azure_CostManagement/Menu/~/overview"
|
|
198166
|
+
}
|
|
198167
|
+
};
|
|
198168
|
+
|
|
198049
198169
|
// utils/apiKeys.ts
|
|
198050
198170
|
var MISSING_KEY_MARKER = "no API key found";
|
|
198051
198171
|
var BYOK_SLUG_MARKER = "env var is required when the model is set to";
|
|
@@ -198370,14 +198490,22 @@ function buildRejectedCredentialError(params) {
|
|
|
198370
198490
|
].join("\n");
|
|
198371
198491
|
}
|
|
198372
198492
|
const where = params.inPullfrogStore ? `[Update it in Pullfrog \u2192](${settingsUrl})` : `[Update the GitHub Actions secret \u2192](https://github.com/${params.owner}/${params.name}/settings/secrets/actions)`;
|
|
198493
|
+
const keysUrl = providerKeysUrl(params.credential);
|
|
198494
|
+
const issue3 = keysUrl ? `[Issue a new key \u2192](${keysUrl}) \xB7 ` : "";
|
|
198373
198495
|
return [
|
|
198374
198496
|
`**Your \`${params.credential}\` ${CREDENTIAL_REJECTED_MARKER}**${detail}, so the agent never ran.`,
|
|
198375
198497
|
"",
|
|
198376
198498
|
`**To fix:** issue a new key in your provider dashboard and update the copy Pullfrog uses${params.inPullfrogStore ? " in the console" : " in your repo's GitHub Actions secrets"}.`,
|
|
198377
198499
|
"",
|
|
198378
|
-
`${where} \xB7 [Model settings \u2192](${settingsUrl}) \xB7 [Setup docs \u2192](https://docs.pullfrog.com/keys) \xB7 [Ask in Discord \u2192](https://discord.gg/8y96raFg8e)`
|
|
198500
|
+
`${issue3}${where} \xB7 [Model settings \u2192](${settingsUrl}) \xB7 [Setup docs \u2192](https://docs.pullfrog.com/keys) \xB7 [Ask in Discord \u2192](https://discord.gg/8y96raFg8e)`
|
|
198379
198501
|
].join("\n");
|
|
198380
198502
|
}
|
|
198503
|
+
function providerKeysUrl(credential) {
|
|
198504
|
+
for (const [id, provider2] of Object.entries(providers)) {
|
|
198505
|
+
if (provider2.envVars.includes(credential)) return PROVIDER_DASHBOARDS[id]?.keys;
|
|
198506
|
+
}
|
|
198507
|
+
return void 0;
|
|
198508
|
+
}
|
|
198381
198509
|
function formatApiKeyErrorSummary(params) {
|
|
198382
198510
|
if (params.raw.includes(MISSING_KEY_MARKER)) {
|
|
198383
198511
|
if (params.raw.startsWith(`**${MISSING_KEY_MARKER}**`)) return params.raw;
|
|
@@ -198569,23 +198697,35 @@ function billingConsoleUrl(owner) {
|
|
|
198569
198697
|
return `https://pullfrog.com/console/${encodeURIComponent(owner)}#billing`;
|
|
198570
198698
|
}
|
|
198571
198699
|
function commercialPaywallBody(params) {
|
|
198572
|
-
|
|
198573
|
-
|
|
198574
|
-
|
|
198575
|
-
|
|
198576
|
-
|
|
198577
|
-
|
|
198578
|
-
|
|
198579
|
-
|
|
198700
|
+
const pitch = "Pro is required on private organization repos like this one: a flat $30/month per organization for unlimited runs, members, and repos.";
|
|
198701
|
+
switch (params.reason) {
|
|
198702
|
+
case "commercial":
|
|
198703
|
+
return [
|
|
198704
|
+
`**${params.ownerLogin}'s Pullfrog trial has expired.**`,
|
|
198705
|
+
"",
|
|
198706
|
+
pitch,
|
|
198707
|
+
"",
|
|
198708
|
+
`[Upgrade now \u2192](${params.url})`
|
|
198709
|
+
].join("\n");
|
|
198710
|
+
case "subscription_ended":
|
|
198711
|
+
return [
|
|
198712
|
+
`**${params.ownerLogin}'s Pullfrog Pro subscription has ended.**`,
|
|
198713
|
+
"",
|
|
198714
|
+
pitch,
|
|
198715
|
+
"",
|
|
198716
|
+
`[Resubscribe \u2192](${params.url})`
|
|
198717
|
+
].join("\n");
|
|
198718
|
+
case "subscription_unpaid":
|
|
198719
|
+
return [
|
|
198720
|
+
`**Pullfrog paused runs on ${params.ownerLogin}: the Pro renewal failed.**`,
|
|
198721
|
+
"",
|
|
198722
|
+
"Update the card on file to resume runs.",
|
|
198723
|
+
"",
|
|
198724
|
+
`[Update billing \u2192](${params.url})`
|
|
198725
|
+
].join("\n");
|
|
198726
|
+
default:
|
|
198727
|
+
return params.reason;
|
|
198580
198728
|
}
|
|
198581
|
-
params.reason;
|
|
198582
|
-
return [
|
|
198583
|
-
`**Pullfrog paused runs on ${params.ownerLogin}: the Pro renewal failed.**`,
|
|
198584
|
-
"",
|
|
198585
|
-
"Update the card on file to resume runs.",
|
|
198586
|
-
"",
|
|
198587
|
-
`[Update billing \u2192](${params.url})`
|
|
198588
|
-
].join("\n");
|
|
198589
198729
|
}
|
|
198590
198730
|
function formatCommercialGateSummary(params) {
|
|
198591
198731
|
return commercialPaywallBody({
|
|
@@ -198637,6 +198777,12 @@ function formatBillingErrorSummary(error62, owner) {
|
|
|
198637
198777
|
ownerLogin: owner
|
|
198638
198778
|
});
|
|
198639
198779
|
}
|
|
198780
|
+
if (error62.code === "subscription_ended") {
|
|
198781
|
+
return formatCommercialGateSummary({
|
|
198782
|
+
reason: "subscription_ended",
|
|
198783
|
+
ownerLogin: owner
|
|
198784
|
+
});
|
|
198785
|
+
}
|
|
198640
198786
|
if (error62.code === "subscription_unpaid") {
|
|
198641
198787
|
return formatCommercialGateSummary({
|
|
198642
198788
|
reason: "subscription_unpaid",
|
|
@@ -200549,8 +200695,9 @@ async function fetchRunContext(params) {
|
|
|
200549
200695
|
clearTimeout(timeoutId);
|
|
200550
200696
|
if (response.status === 402) {
|
|
200551
200697
|
const body = await response.json().catch(() => null);
|
|
200552
|
-
const reason = typeof body === "object" && body !== null && "reason" in body
|
|
200553
|
-
|
|
200698
|
+
const reason = typeof body === "object" && body !== null && "reason" in body ? body.reason : null;
|
|
200699
|
+
const commercialRefused = reason === "subscription_unpaid" || reason === "subscription_ended" ? reason : "commercial";
|
|
200700
|
+
return { ...defaultRunContext, commercialRefused };
|
|
200554
200701
|
}
|
|
200555
200702
|
if (!response.ok) {
|
|
200556
200703
|
return response.status >= 500 ? unknownSecretsRunContext : defaultRunContext;
|
|
@@ -200790,15 +200937,9 @@ function formatMinimalFailureComment(repo) {
|
|
|
200790
200937
|
const url4 = `${server}/${repo.owner}/${repo.name}/actions/runs/${runId}`;
|
|
200791
200938
|
return `**Run failed.** [View the logs \u2192](${url4})`;
|
|
200792
200939
|
}
|
|
200793
|
-
var PROVIDER_BILLING_URLS =
|
|
200794
|
-
|
|
200795
|
-
|
|
200796
|
-
openai: "https://platform.openai.com/account/billing",
|
|
200797
|
-
google: "https://aistudio.google.com/usage",
|
|
200798
|
-
opencode: "https://opencode.ai/zen",
|
|
200799
|
-
xai: "https://console.x.ai/team/default/billing",
|
|
200800
|
-
openrouter: "https://openrouter.ai/settings/credits"
|
|
200801
|
-
};
|
|
200940
|
+
var PROVIDER_BILLING_URLS = Object.fromEntries(
|
|
200941
|
+
Object.entries(PROVIDER_DASHBOARDS).map(([id, dashboard]) => [id, dashboard.billing])
|
|
200942
|
+
);
|
|
200802
200943
|
function detectProviderId(message) {
|
|
200803
200944
|
const harnessId = extractProviderId(message);
|
|
200804
200945
|
if (harnessId) return harnessId;
|
|
@@ -200809,7 +200950,6 @@ function detectProviderId(message) {
|
|
|
200809
200950
|
}
|
|
200810
200951
|
return null;
|
|
200811
200952
|
}
|
|
200812
|
-
var OPENROUTER_KEYS_URL = "https://openrouter.ai/workspaces/default/keys";
|
|
200813
200953
|
var OPENROUTER_PRIVACY_URL = "https://openrouter.ai/settings/privacy";
|
|
200814
200954
|
function formatProviderBillingExhausted(input2) {
|
|
200815
200955
|
const providerId = detectProviderId(input2.errorMessage);
|
|
@@ -200820,7 +200960,7 @@ function formatProviderBillingExhausted(input2) {
|
|
|
200820
200960
|
"",
|
|
200821
200961
|
"OpenRouter refused the request against the key's own spending ceiling, not your account balance \u2014 raising or removing that limit is the fix, and topping up credits alone may not be.",
|
|
200822
200962
|
"",
|
|
200823
|
-
`[Raise the key's limit \u2192](${
|
|
200963
|
+
`[Raise the key's limit \u2192](${PROVIDER_DASHBOARDS.openrouter.keys}) \xB7 [Add credits \u2192](${PROVIDER_BILLING_URLS.openrouter})`,
|
|
200824
200964
|
"",
|
|
200825
200965
|
`\`\`\`
|
|
200826
200966
|
${input2.errorMessage}
|
|
@@ -201754,7 +201894,11 @@ async function main() {
|
|
|
201754
201894
|
authorPermission: void 0,
|
|
201755
201895
|
oidc: oidcCredentials
|
|
201756
201896
|
}), true);
|
|
201757
|
-
const errorMessage =
|
|
201897
|
+
const errorMessage = {
|
|
201898
|
+
commercial: "Pro trial expired for this organization",
|
|
201899
|
+
subscription_ended: "Pro subscription ended for this organization",
|
|
201900
|
+
subscription_unpaid: "Pro renewal failed for this organization"
|
|
201901
|
+
}[runContext.commercialRefused];
|
|
201758
201902
|
log.error(errorMessage);
|
|
201759
201903
|
const body = formatCommercialGateSummary({
|
|
201760
201904
|
reason: runContext.commercialRefused,
|
package/dist/internal/index.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ export type { AuthorPermission, AutoTier, EffortPosition, ModelAlias, ModelProvi
|
|
|
9
9
|
export { AUTO_EFFICIENT, AUTO_INTELLIGENT, AUTO_ROUTER, autoLadder, autoRoutedSlug, DEFAULT_EFFORT_POSITION, DEFAULT_PROXY_MODEL, defaultAutoTier, EFFORT_ALIASES, getAutoSelectHintModel, getModelEffortLevels, getModelEnvVars, getModelManagedCredentials, getModelProvider, getProviderDisplayName, isAutoRouted, isAutoSlug, isAutoTier, isCardGatedModel, isEffortPosition, isOssAllowedModel, isRouterTier, ladderRungs, modelAliases, modelHasStoredAuth, OSS_MODEL_ALLOWLIST, OSS_RECOMMENDED_MODEL, offeredRungs, ossFundedModelNames, PROVIDER_GATEWAY_URL_ENV, parseEffortPosition, parseModel, providers, pullfrogMcpName, ROUTED_PROVIDERS, ROUTER_LADDER, ROUTER_TIERS, resolveAutoTier, resolveCliModel, resolveDisplayAlias, resolveModelRung, resolveModelSlug, resolveOpenRouterModel, resolveRoutedModel, resolveRung, rungLabel, rungPosition, SUBSIDY_RUNG, } from "../external.ts";
|
|
10
10
|
export type { Mode } from "../modes.ts";
|
|
11
11
|
export { modes } from "../modes.ts";
|
|
12
|
-
export { commercialPaywallBody } from "../utils/billingErrors.ts";
|
|
12
|
+
export { type CommercialRefusal, commercialPaywallBody } from "../utils/billingErrors.ts";
|
|
13
13
|
export type { BuildPullfrogFooterParams, WorkflowRunFooterInfo, } from "../utils/buildPullfrogFooter.ts";
|
|
14
14
|
export { buildPullfrogFooter, PULLFROG_DIVIDER, stripExistingFooter, } from "../utils/buildPullfrogFooter.ts";
|
|
15
15
|
export type { CodexAuthBody } from "../utils/codexOAuth.ts";
|
|
@@ -22,6 +22,7 @@ export { isLeapingIntoActionCommentBody, LEAPING_INTO_ACTION_PREFIX, } from "../
|
|
|
22
22
|
export { MAX_LEARNINGS_LENGTH, truncateAtLineBoundary } from "../utils/learningsTruncate.ts";
|
|
23
23
|
export type { CreateProgressCommentTarget, ProgressComment, ProgressCommentType, } from "../utils/progressComment.ts";
|
|
24
24
|
export { createLeapingProgressComment, deleteProgressCommentApi, getProgressComment, updateProgressComment, } from "../utils/progressComment.ts";
|
|
25
|
+
export { PROVIDER_DASHBOARDS, type ProviderDashboard } from "../utils/providerDashboards.ts";
|
|
25
26
|
export type { RunStatusCheckConclusion, RunStatusCheckOctokit, } from "../utils/runStatusCheck.ts";
|
|
26
27
|
export { APPROVAL_CHECK_NAME, createRunStatusCheck, finalizeRunStatusCheck, RUN_STATUS_CHECK_NAME, runStatusCheckNeedsFinalizing, } from "../utils/runStatusCheck.ts";
|
|
27
28
|
export { isValidTimeString, parseTimeString, TIMEOUT_DISABLED, } from "../utils/time.ts";
|
package/dist/internal.js
CHANGED
|
@@ -8,7 +8,7 @@ var __export = (target, all) => {
|
|
|
8
8
|
// package.json
|
|
9
9
|
var package_default = {
|
|
10
10
|
name: "pullfrog",
|
|
11
|
-
version: "0.1.
|
|
11
|
+
version: "0.1.81",
|
|
12
12
|
cliContractVersion: "1",
|
|
13
13
|
type: "module",
|
|
14
14
|
bin: {
|
|
@@ -14922,9 +14922,9 @@ var providers = {
|
|
|
14922
14922
|
},
|
|
14923
14923
|
"gpt-5-nano": {
|
|
14924
14924
|
displayName: "GPT Nano",
|
|
14925
|
-
resolve: "opencode/gpt-5-nano",
|
|
14926
|
-
effort: ["
|
|
14927
|
-
openRouterResolve: "openrouter/openai/gpt-5-nano"
|
|
14925
|
+
resolve: "opencode/gpt-5.4-nano",
|
|
14926
|
+
effort: ["none", "low", "medium", "high", "xhigh"],
|
|
14927
|
+
openRouterResolve: "openrouter/openai/gpt-5.4-nano"
|
|
14928
14928
|
},
|
|
14929
14929
|
// Zen's PAID Muse Spark, priced like Meta's own Standard tier. answered
|
|
14930
14930
|
// `OK` through the harness on 2026-09-12 once the workspace was funded.
|
|
@@ -14947,6 +14947,17 @@ var providers = {
|
|
|
14947
14947
|
// see the big-pickle note above (#1077).
|
|
14948
14948
|
isFree: true
|
|
14949
14949
|
},
|
|
14950
|
+
// Zen's FREE stealth preview (listed 2026-09-16), the big-pickle shape: an
|
|
14951
|
+
// anonymous vendor's model at $0 for a limited time. OpenRouter's listing:
|
|
14952
|
+
// prompts and completions may be retained, not trained on.
|
|
14953
|
+
"union-alpha": {
|
|
14954
|
+
displayName: "Union Alpha",
|
|
14955
|
+
description: "Stealth preview; prompts may be retained",
|
|
14956
|
+
resolve: "opencode/union-alpha",
|
|
14957
|
+
// free to run, still gated on the provider's own OPENCODE_API_KEY —
|
|
14958
|
+
// see the big-pickle note above (#1077).
|
|
14959
|
+
isFree: true
|
|
14960
|
+
},
|
|
14950
14961
|
// Zen's live free MiMo, and the second free row in a menu that big-pickle
|
|
14951
14962
|
// was alone in since `mimo-v2-pro-free` lost its model.
|
|
14952
14963
|
mimo: {
|
|
@@ -16151,23 +16162,35 @@ var modes = computeModes("opencode");
|
|
|
16151
16162
|
|
|
16152
16163
|
// utils/billingErrors.ts
|
|
16153
16164
|
function commercialPaywallBody(params) {
|
|
16154
|
-
|
|
16155
|
-
|
|
16156
|
-
|
|
16157
|
-
|
|
16158
|
-
|
|
16159
|
-
|
|
16160
|
-
|
|
16161
|
-
|
|
16162
|
-
|
|
16163
|
-
|
|
16164
|
-
|
|
16165
|
-
|
|
16166
|
-
|
|
16167
|
-
|
|
16168
|
-
|
|
16169
|
-
|
|
16170
|
-
|
|
16165
|
+
const pitch = "Pro is required on private organization repos like this one: a flat $30/month per organization for unlimited runs, members, and repos.";
|
|
16166
|
+
switch (params.reason) {
|
|
16167
|
+
case "commercial":
|
|
16168
|
+
return [
|
|
16169
|
+
`**${params.ownerLogin}'s Pullfrog trial has expired.**`,
|
|
16170
|
+
"",
|
|
16171
|
+
pitch,
|
|
16172
|
+
"",
|
|
16173
|
+
`[Upgrade now \u2192](${params.url})`
|
|
16174
|
+
].join("\n");
|
|
16175
|
+
case "subscription_ended":
|
|
16176
|
+
return [
|
|
16177
|
+
`**${params.ownerLogin}'s Pullfrog Pro subscription has ended.**`,
|
|
16178
|
+
"",
|
|
16179
|
+
pitch,
|
|
16180
|
+
"",
|
|
16181
|
+
`[Resubscribe \u2192](${params.url})`
|
|
16182
|
+
].join("\n");
|
|
16183
|
+
case "subscription_unpaid":
|
|
16184
|
+
return [
|
|
16185
|
+
`**Pullfrog paused runs on ${params.ownerLogin}: the Pro renewal failed.**`,
|
|
16186
|
+
"",
|
|
16187
|
+
"Update the card on file to resume runs.",
|
|
16188
|
+
"",
|
|
16189
|
+
`[Update billing \u2192](${params.url})`
|
|
16190
|
+
].join("\n");
|
|
16191
|
+
default:
|
|
16192
|
+
return params.reason;
|
|
16193
|
+
}
|
|
16171
16194
|
}
|
|
16172
16195
|
|
|
16173
16196
|
// utils/buildPullfrogFooter.ts
|
|
@@ -16300,10 +16323,10 @@ function decodeJwtExpMs(token) {
|
|
|
16300
16323
|
// utils/codexOAuth.ts
|
|
16301
16324
|
var CODEX_OAUTH_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
16302
16325
|
var CODEX_OAUTH_TOKEN_URL = "https://auth.openai.com/oauth/token";
|
|
16303
|
-
function codexChainIsDead(body) {
|
|
16326
|
+
function codexChainIsDead(status, body) {
|
|
16327
|
+
if (status !== 401) return false;
|
|
16304
16328
|
const err = parseOAuthErrorBody(body)?.error;
|
|
16305
|
-
|
|
16306
|
-
return "code" in err && err.code === "token_expired";
|
|
16329
|
+
return !(err && typeof err === "object" && "code" in err && err.code === "invalid_client");
|
|
16307
16330
|
}
|
|
16308
16331
|
async function refreshCodexAuthBody(body) {
|
|
16309
16332
|
const response = await fetch(CODEX_OAUTH_TOKEN_URL, {
|
|
@@ -16319,7 +16342,12 @@ async function refreshCodexAuthBody(body) {
|
|
|
16319
16342
|
if (!response.ok) {
|
|
16320
16343
|
const text = await response.text().catch(() => "");
|
|
16321
16344
|
if (response.status >= 400 && response.status < 500) {
|
|
16322
|
-
throw new OAuthInvalidGrantError(
|
|
16345
|
+
throw new OAuthInvalidGrantError(
|
|
16346
|
+
"Codex",
|
|
16347
|
+
response.status,
|
|
16348
|
+
text,
|
|
16349
|
+
codexChainIsDead(response.status, text)
|
|
16350
|
+
);
|
|
16323
16351
|
}
|
|
16324
16352
|
throw new Error(`Codex token refresh failed: ${response.status} ${text}`);
|
|
16325
16353
|
}
|
|
@@ -16598,6 +16626,115 @@ async function createLeapingProgressComment(ctx, target, body) {
|
|
|
16598
16626
|
};
|
|
16599
16627
|
}
|
|
16600
16628
|
|
|
16629
|
+
// utils/providerDashboards.ts
|
|
16630
|
+
var PROVIDER_DASHBOARDS = {
|
|
16631
|
+
anthropic: {
|
|
16632
|
+
name: "Anthropic",
|
|
16633
|
+
billing: "https://platform.claude.com/settings/billing",
|
|
16634
|
+
usage: "https://platform.claude.com/usage",
|
|
16635
|
+
keys: "https://platform.claude.com/settings/keys"
|
|
16636
|
+
},
|
|
16637
|
+
"claude-plan": {
|
|
16638
|
+
name: "Claude Pro/Max",
|
|
16639
|
+
credential: "CLAUDE_CODE_OAUTH_TOKEN",
|
|
16640
|
+
billing: "https://claude.ai/settings/billing",
|
|
16641
|
+
usage: "https://claude.ai/settings/usage"
|
|
16642
|
+
},
|
|
16643
|
+
openai: {
|
|
16644
|
+
name: "OpenAI",
|
|
16645
|
+
billing: "https://platform.openai.com/settings/organization/billing/overview",
|
|
16646
|
+
usage: "https://platform.openai.com/settings/organization/usage",
|
|
16647
|
+
keys: "https://platform.openai.com/api-keys"
|
|
16648
|
+
},
|
|
16649
|
+
"codex-plan": {
|
|
16650
|
+
name: "ChatGPT/Codex",
|
|
16651
|
+
credential: "CODEX_AUTH_JSON",
|
|
16652
|
+
billing: "https://chatgpt.com/#settings/Subscription",
|
|
16653
|
+
usage: "https://chatgpt.com/codex/settings/usage"
|
|
16654
|
+
},
|
|
16655
|
+
google: {
|
|
16656
|
+
name: "Google AI Studio",
|
|
16657
|
+
// AI Studio's "Usage & billing" page is where the Gemini API plan lives;
|
|
16658
|
+
// the Cloud billing console is a level up and needs a project picked.
|
|
16659
|
+
billing: "https://aistudio.google.com/usage",
|
|
16660
|
+
keys: "https://aistudio.google.com/apikey"
|
|
16661
|
+
},
|
|
16662
|
+
vertex: {
|
|
16663
|
+
name: "Google Vertex AI",
|
|
16664
|
+
billing: "https://console.cloud.google.com/billing",
|
|
16665
|
+
usage: "https://console.cloud.google.com/vertex-ai"
|
|
16666
|
+
},
|
|
16667
|
+
xai: {
|
|
16668
|
+
name: "xAI",
|
|
16669
|
+
billing: "https://console.x.ai/team/default/billing",
|
|
16670
|
+
usage: "https://console.x.ai/team/default/usage",
|
|
16671
|
+
keys: "https://console.x.ai/team/default/api-keys"
|
|
16672
|
+
},
|
|
16673
|
+
"grok-plan": {
|
|
16674
|
+
name: "SuperGrok",
|
|
16675
|
+
credential: "GROK_AUTH_JSON",
|
|
16676
|
+
billing: "https://grok.com/?_s=billing"
|
|
16677
|
+
},
|
|
16678
|
+
deepseek: {
|
|
16679
|
+
name: "DeepSeek",
|
|
16680
|
+
billing: "https://platform.deepseek.com/top_up",
|
|
16681
|
+
usage: "https://platform.deepseek.com/usage",
|
|
16682
|
+
keys: "https://platform.deepseek.com/api_keys"
|
|
16683
|
+
},
|
|
16684
|
+
moonshotai: {
|
|
16685
|
+
name: "Moonshot (Kimi API)",
|
|
16686
|
+
billing: "https://platform.kimi.ai/console/pay",
|
|
16687
|
+
usage: "https://platform.kimi.ai/console/account",
|
|
16688
|
+
keys: "https://platform.kimi.ai/console/api-keys"
|
|
16689
|
+
},
|
|
16690
|
+
"kimi-for-coding": {
|
|
16691
|
+
name: "Kimi Code",
|
|
16692
|
+
billing: "https://www.kimi.com/membership/pricing",
|
|
16693
|
+
keys: "https://www.kimi.com/code/console"
|
|
16694
|
+
},
|
|
16695
|
+
meta: {
|
|
16696
|
+
name: "Meta Model API",
|
|
16697
|
+
billing: "https://dev.meta.ai/billing",
|
|
16698
|
+
usage: "https://dev.meta.ai/usage",
|
|
16699
|
+
keys: "https://dev.meta.ai/api-keys"
|
|
16700
|
+
},
|
|
16701
|
+
opencode: {
|
|
16702
|
+
name: "OpenCode Zen",
|
|
16703
|
+
billing: "https://opencode.ai/zen",
|
|
16704
|
+
keys: "https://opencode.ai/zen"
|
|
16705
|
+
},
|
|
16706
|
+
// Go is a separate plan on the same key, with its own usage cap that resets
|
|
16707
|
+
// on its own (wiki/opencode-silent-stall.md). Its page is the plan page; no
|
|
16708
|
+
// per-window usage page is documented.
|
|
16709
|
+
"opencode-go": {
|
|
16710
|
+
name: "OpenCode Go",
|
|
16711
|
+
billing: "https://opencode.ai/go",
|
|
16712
|
+
keys: "https://opencode.ai/zen"
|
|
16713
|
+
},
|
|
16714
|
+
openrouter: {
|
|
16715
|
+
name: "OpenRouter",
|
|
16716
|
+
billing: "https://openrouter.ai/settings/credits",
|
|
16717
|
+
usage: "https://openrouter.ai/activity",
|
|
16718
|
+
keys: "https://openrouter.ai/workspaces/default/keys"
|
|
16719
|
+
},
|
|
16720
|
+
vercel: {
|
|
16721
|
+
name: "Vercel AI Gateway",
|
|
16722
|
+
// `/d?to=` resolves `[team]` to whichever team the visitor is signed into.
|
|
16723
|
+
billing: "https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai-gateway",
|
|
16724
|
+
keys: "https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai-gateway%2Fapi-keys"
|
|
16725
|
+
},
|
|
16726
|
+
bedrock: {
|
|
16727
|
+
name: "Amazon Bedrock",
|
|
16728
|
+
billing: "https://console.aws.amazon.com/billing/home",
|
|
16729
|
+
keys: "https://us-east-1.console.aws.amazon.com/bedrock/home#/api-keys"
|
|
16730
|
+
},
|
|
16731
|
+
// Azure OpenAI keys are per-resource, so there is no stable keys page to name.
|
|
16732
|
+
azure: {
|
|
16733
|
+
name: "Azure OpenAI",
|
|
16734
|
+
billing: "https://portal.azure.com/#view/Microsoft_Azure_CostManagement/Menu/~/overview"
|
|
16735
|
+
}
|
|
16736
|
+
};
|
|
16737
|
+
|
|
16601
16738
|
// utils/runStatusCheck.ts
|
|
16602
16739
|
var RUN_STATUS_CHECK_NAME = "pullfrog";
|
|
16603
16740
|
var GITHUB_ACTIONS_APP_SLUG = "github-actions";
|
|
@@ -16806,6 +16943,7 @@ export {
|
|
|
16806
16943
|
OAuthInvalidGrantError,
|
|
16807
16944
|
OSS_MODEL_ALLOWLIST,
|
|
16808
16945
|
OSS_RECOMMENDED_MODEL,
|
|
16946
|
+
PROVIDER_DASHBOARDS,
|
|
16809
16947
|
PROVIDER_GATEWAY_URL_ENV,
|
|
16810
16948
|
PULLFROG_DIVIDER,
|
|
16811
16949
|
ROUTED_PROVIDERS,
|
|
@@ -43,6 +43,15 @@ export declare class BillingError extends Error {
|
|
|
43
43
|
export declare class TransientError extends Error {
|
|
44
44
|
constructor(message: string);
|
|
45
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Why the org commercial gate refused a run (billing model v2), resolved by
|
|
48
|
+
* `resolveTeamRunAccess` and carried unchanged through the paywall comment, the
|
|
49
|
+
* run-context 402 and the proxy-token 402. `commercial`: the no-card trial
|
|
50
|
+
* wound down to `paused`. `subscription_ended`: a paid subscription was
|
|
51
|
+
* canceled and lapsed to `paused`. `subscription_unpaid`: the renewal failed
|
|
52
|
+
* dunning.
|
|
53
|
+
*/
|
|
54
|
+
export type CommercialRefusal = "commercial" | "subscription_ended" | "subscription_unpaid";
|
|
46
55
|
/**
|
|
47
56
|
* Canonical paused-run paywall body (billing model v2). Single source of truth so
|
|
48
57
|
* the two surfaces that show it can never drift: the trigger-time comment (a run
|
|
@@ -51,7 +60,7 @@ export declare class TransientError extends Error {
|
|
|
51
60
|
* caller's billing surface (each passes its own, internal plan page vs console).
|
|
52
61
|
*/
|
|
53
62
|
export declare function commercialPaywallBody(params: {
|
|
54
|
-
reason:
|
|
63
|
+
reason: CommercialRefusal;
|
|
55
64
|
ownerLogin: string;
|
|
56
65
|
url: string;
|
|
57
66
|
}): string;
|
|
@@ -65,7 +74,7 @@ export declare function commercialPaywallBody(params: {
|
|
|
65
74
|
* production row; see `isSubscriptionHostTrusted` and wiki/billing-model-v2.md.
|
|
66
75
|
*/
|
|
67
76
|
export declare function formatCommercialGateSummary(params: {
|
|
68
|
-
reason:
|
|
77
|
+
reason: CommercialRefusal;
|
|
69
78
|
ownerLogin: string;
|
|
70
79
|
}): string;
|
|
71
80
|
/**
|
|
@@ -21,9 +21,8 @@ export interface CodexAuthBody {
|
|
|
21
21
|
};
|
|
22
22
|
last_refresh?: string;
|
|
23
23
|
/**
|
|
24
|
-
* ISO timestamp of a rejection OpenAI attributed to the token itself
|
|
25
|
-
*
|
|
26
|
-
* `invalid_grant` here). OpenAI rotates the refresh
|
|
24
|
+
* ISO timestamp of a rejection OpenAI attributed to the token itself (a `401`
|
|
25
|
+
* other than `invalid_client` — see `codexChainIsDead`). OpenAI rotates the refresh
|
|
27
26
|
* token on every use, so such a rejection is PERMANENT — without a latch the
|
|
28
27
|
* server re-issued the identical doomed refresh on every run (455 futile
|
|
29
28
|
* round trips in 7 days, one per run, each holding a Postgres row lock across
|
|
@@ -11,12 +11,13 @@
|
|
|
11
11
|
* Thrown when an OAuth provider rejects a refresh token (4xx).
|
|
12
12
|
*
|
|
13
13
|
* `chainIsDead` is decided by the CALLER, because the two providers we talk to
|
|
14
|
-
* disagree about how to say it and only the caller knows its own dialect
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
14
|
+
* disagree about how to say it and only the caller knows its own dialect, and
|
|
15
|
+
* so does the list of rejections that are NOT dead. xAI answers RFC 6749
|
|
16
|
+
* (`{"error":"invalid_grant"}`), so anything else — an unknown client id, an
|
|
17
|
+
* unsupported grant, a CDN error page — is spared. OpenAI answers a dead chain
|
|
18
|
+
* with a `401` whose body varies and is often empty, so there the `401` itself
|
|
19
|
+
* is the signal and only `invalid_client` is spared. Latching wrongly retires a
|
|
20
|
+
* customer's working credential until they re-mint it.
|
|
20
21
|
*/
|
|
21
22
|
export declare class OAuthInvalidGrantError extends Error {
|
|
22
23
|
readonly status: number;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where each provider's own account lives, keyed by the provider id the rest of
|
|
3
|
+
* the runtime already uses (`providers` in `models.ts`, `providerID=` in the
|
|
4
|
+
* opencode log). Both failure surfaces read this one table — the per-run PR
|
|
5
|
+
* comment for its top-up CTA, and the diagnosis agent's link registry — so a
|
|
6
|
+
* host move is fixed in one place. Every catalog provider is here except
|
|
7
|
+
* `openai-compatible`, whose endpoint is the customer's own and has no page.
|
|
8
|
+
*
|
|
9
|
+
* How each path is known (2026-09-14), because "checked" means three different
|
|
10
|
+
* things here. Probed with a discriminating control — the path answers 200 and
|
|
11
|
+
* a junk path on the same host 404s: `platform.claude.com`, `platform.kimi.ai`,
|
|
12
|
+
* `www.kimi.com`, `dev.meta.ai`, `opencode.ai`, the AWS billing host.
|
|
13
|
+
* Taken from the provider's own docs, because the host answers a probe with a
|
|
14
|
+
* Cloudflare challenge or a catch-all login redirect that no junk path can
|
|
15
|
+
* distinguish: OpenAI, xAI, DeepSeek, `claude.ai`, `chatgpt.com`, Google,
|
|
16
|
+
* OpenRouter, Vercel, `grok.com`. Fragment routes (`#…`) never reach a server,
|
|
17
|
+
* so those rest on the docs alone. `console.anthropic.com` and
|
|
18
|
+
* `platform.moonshot.ai` both 301 now, so the entries name the hosts they land
|
|
19
|
+
* on.
|
|
20
|
+
*
|
|
21
|
+
* The subscription rows are not providers in the catalog sense — a Claude,
|
|
22
|
+
* ChatGPT or SuperGrok plan has a usage window and a plan page but no API key —
|
|
23
|
+
* so they carry the `-plan` suffix, no `keys`, and the credential that proves
|
|
24
|
+
* the plan is in play.
|
|
25
|
+
*/
|
|
26
|
+
export type ProviderDashboard = {
|
|
27
|
+
/** the name a customer knows the account by */
|
|
28
|
+
name: string;
|
|
29
|
+
/** a plan row only: the stored credential whose presence means the plan is in use */
|
|
30
|
+
credential?: string;
|
|
31
|
+
/** the wallet, the card on file, a spend cap, or the plan itself */
|
|
32
|
+
billing: string;
|
|
33
|
+
/** spend to date, or a plan's limit window and its reset time */
|
|
34
|
+
usage?: string;
|
|
35
|
+
/** where an API key is created or rotated */
|
|
36
|
+
keys?: string;
|
|
37
|
+
};
|
|
38
|
+
export declare const PROVIDER_DASHBOARDS: Record<string, ProviderDashboard>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { PushPermission, ShellPermission } from "../external.ts";
|
|
2
2
|
import type { RouterTier } from "../models.ts";
|
|
3
|
+
import type { CommercialRefusal } from "./billingErrors.ts";
|
|
3
4
|
import type { RepoContext } from "./github.ts";
|
|
4
5
|
export interface Mode {
|
|
5
6
|
id: string;
|
|
@@ -54,15 +55,6 @@ export interface RepoSettings {
|
|
|
54
55
|
* `"none"` = no card; `"payg"` = card on file.
|
|
55
56
|
*/
|
|
56
57
|
export type AccountPlan = "none" | "payg";
|
|
57
|
-
/**
|
|
58
|
-
* The org commercial gate's refusal (billing model v2). Set when run-context
|
|
59
|
-
* returns 402 for a `paused`/`unpaid` org — the backstop for manual re-runs and
|
|
60
|
-
* self-configured triggers that never hit reserveRun. main.ts stops before
|
|
61
|
-
* installing the agent or loading account secrets and writes actionable copy.
|
|
62
|
-
* A forked action can bypass this response, so proxy-token enforces the same
|
|
63
|
-
* verdict before issuing a Pullfrog Router key.
|
|
64
|
-
*/
|
|
65
|
-
export type CommercialRefusal = "commercial" | "subscription_unpaid";
|
|
66
58
|
export interface RunContext {
|
|
67
59
|
settings: RepoSettings;
|
|
68
60
|
apiToken: string;
|
|
@@ -70,6 +62,14 @@ export interface RunContext {
|
|
|
70
62
|
plan: AccountPlan;
|
|
71
63
|
proxyModel?: string | undefined;
|
|
72
64
|
dbSecrets?: Record<string, string> | undefined;
|
|
65
|
+
/**
|
|
66
|
+
* The org commercial gate's refusal (billing model v2). Set when run-context
|
|
67
|
+
* returns 402 for a `paused`/`unpaid` org — the backstop for manual re-runs and
|
|
68
|
+
* self-configured triggers that never hit reserveRun. main.ts stops before
|
|
69
|
+
* installing the agent or loading account secrets and writes actionable copy.
|
|
70
|
+
* A forked action can bypass this response, so proxy-token enforces the same
|
|
71
|
+
* verdict before issuing a Pullfrog Router key.
|
|
72
|
+
*/
|
|
73
73
|
commercialRefused?: CommercialRefusal | undefined;
|
|
74
74
|
/**
|
|
75
75
|
* the server tried and failed to materialize Pullfrog-stored secrets (or we
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { Octokit } from "@octokit/rest";
|
|
2
2
|
import type { RouterTier } from "../models.ts";
|
|
3
|
+
import type { CommercialRefusal } from "./billingErrors.ts";
|
|
3
4
|
import { type OctokitWithPlugins } from "./github.ts";
|
|
4
|
-
import { type AccountPlan, type
|
|
5
|
+
import { type AccountPlan, type RepoSettings } from "./runContext.ts";
|
|
5
6
|
export interface RunContextData {
|
|
6
7
|
repo: {
|
|
7
8
|
owner: string;
|