pullfrog 0.1.78 → 0.1.80
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 +236 -47
- package/dist/index.js +184 -38
- package/dist/internal/index.d.ts +2 -1
- package/dist/internal.js +168 -28
- 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.80",
|
|
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?`,
|
|
@@ -125293,7 +125336,10 @@ var providers = {
|
|
|
125293
125336
|
"kimi-k2": {
|
|
125294
125337
|
displayName: "Kimi K2",
|
|
125295
125338
|
description: "Included in every Kimi membership tier",
|
|
125296
|
-
resolve: "kimi-for-coding/kimi-for-coding"
|
|
125339
|
+
resolve: "kimi-for-coding/kimi-for-coding",
|
|
125340
|
+
// models.dev published this ladder on 2026-09-14; the catalog gate
|
|
125341
|
+
// mirrors it, so main was red until the alias carried it too.
|
|
125342
|
+
effort: ["low", "high", "max"]
|
|
125297
125343
|
},
|
|
125298
125344
|
"kimi-k2-highspeed": {
|
|
125299
125345
|
displayName: "Kimi K2 HighSpeed",
|
|
@@ -125536,17 +125582,27 @@ var providers = {
|
|
|
125536
125582
|
effort: ["minimal", "low", "medium", "high"],
|
|
125537
125583
|
openRouterResolve: "openrouter/openai/gpt-5-nano"
|
|
125538
125584
|
},
|
|
125539
|
-
// Zen's PAID Muse Spark, priced like Meta's own Standard tier.
|
|
125540
|
-
//
|
|
125541
|
-
// Go refuses with DataPolicyError. unverified through the harness on
|
|
125542
|
-
// 2026-09-11: the workspace was out of balance and a claude-sonnet-5
|
|
125543
|
-
// control failed identically. see wiki/muse-spark.md.
|
|
125585
|
+
// Zen's PAID Muse Spark, priced like Meta's own Standard tier. answered
|
|
125586
|
+
// `OK` through the harness on 2026-09-12 once the workspace was funded.
|
|
125544
125587
|
"muse-spark": {
|
|
125545
125588
|
displayName: "Muse Spark",
|
|
125546
125589
|
resolve: "opencode/muse-spark-1.3",
|
|
125547
125590
|
effort: ["minimal", "low", "medium", "high", "xhigh", "max"],
|
|
125548
125591
|
openRouterResolve: "openrouter/meta/muse-spark-1.3"
|
|
125549
125592
|
},
|
|
125593
|
+
// Zen's FREE contributor tier: the same model at $0 because Meta trains
|
|
125594
|
+
// on the prompts and completions. Zen served it to our key on 2026-09-12
|
|
125595
|
+
// without the DataPolicyError Go raises for its contributor id. see
|
|
125596
|
+
// wiki/muse-spark.md.
|
|
125597
|
+
"muse-spark-contributor": {
|
|
125598
|
+
displayName: "Muse Spark Contributor",
|
|
125599
|
+
description: "Meta trains on prompts and completions",
|
|
125600
|
+
resolve: "opencode/muse-spark-1.3-contributor-free",
|
|
125601
|
+
effort: ["minimal", "low", "medium", "high", "xhigh"],
|
|
125602
|
+
// free to run, still gated on the provider's own OPENCODE_API_KEY —
|
|
125603
|
+
// see the big-pickle note above (#1077).
|
|
125604
|
+
isFree: true
|
|
125605
|
+
},
|
|
125550
125606
|
// Zen's live free MiMo, and the second free row in a menu that big-pickle
|
|
125551
125607
|
// was alone in since `mimo-v2-pro-free` lost its model.
|
|
125552
125608
|
mimo: {
|
|
@@ -215173,6 +215229,115 @@ function subagentDeniedToolNames(ctx, outputSchema) {
|
|
|
215173
215229
|
return names;
|
|
215174
215230
|
}
|
|
215175
215231
|
|
|
215232
|
+
// utils/providerDashboards.ts
|
|
215233
|
+
var PROVIDER_DASHBOARDS = {
|
|
215234
|
+
anthropic: {
|
|
215235
|
+
name: "Anthropic",
|
|
215236
|
+
billing: "https://platform.claude.com/settings/billing",
|
|
215237
|
+
usage: "https://platform.claude.com/usage",
|
|
215238
|
+
keys: "https://platform.claude.com/settings/keys"
|
|
215239
|
+
},
|
|
215240
|
+
"claude-plan": {
|
|
215241
|
+
name: "Claude Pro/Max",
|
|
215242
|
+
credential: "CLAUDE_CODE_OAUTH_TOKEN",
|
|
215243
|
+
billing: "https://claude.ai/settings/billing",
|
|
215244
|
+
usage: "https://claude.ai/settings/usage"
|
|
215245
|
+
},
|
|
215246
|
+
openai: {
|
|
215247
|
+
name: "OpenAI",
|
|
215248
|
+
billing: "https://platform.openai.com/settings/organization/billing/overview",
|
|
215249
|
+
usage: "https://platform.openai.com/settings/organization/usage",
|
|
215250
|
+
keys: "https://platform.openai.com/api-keys"
|
|
215251
|
+
},
|
|
215252
|
+
"codex-plan": {
|
|
215253
|
+
name: "ChatGPT/Codex",
|
|
215254
|
+
credential: "CODEX_AUTH_JSON",
|
|
215255
|
+
billing: "https://chatgpt.com/#settings/Subscription",
|
|
215256
|
+
usage: "https://chatgpt.com/codex/settings/usage"
|
|
215257
|
+
},
|
|
215258
|
+
google: {
|
|
215259
|
+
name: "Google AI Studio",
|
|
215260
|
+
// AI Studio's "Usage & billing" page is where the Gemini API plan lives;
|
|
215261
|
+
// the Cloud billing console is a level up and needs a project picked.
|
|
215262
|
+
billing: "https://aistudio.google.com/usage",
|
|
215263
|
+
keys: "https://aistudio.google.com/apikey"
|
|
215264
|
+
},
|
|
215265
|
+
vertex: {
|
|
215266
|
+
name: "Google Vertex AI",
|
|
215267
|
+
billing: "https://console.cloud.google.com/billing",
|
|
215268
|
+
usage: "https://console.cloud.google.com/vertex-ai"
|
|
215269
|
+
},
|
|
215270
|
+
xai: {
|
|
215271
|
+
name: "xAI",
|
|
215272
|
+
billing: "https://console.x.ai/team/default/billing",
|
|
215273
|
+
usage: "https://console.x.ai/team/default/usage",
|
|
215274
|
+
keys: "https://console.x.ai/team/default/api-keys"
|
|
215275
|
+
},
|
|
215276
|
+
"grok-plan": {
|
|
215277
|
+
name: "SuperGrok",
|
|
215278
|
+
credential: "GROK_AUTH_JSON",
|
|
215279
|
+
billing: "https://grok.com/?_s=billing"
|
|
215280
|
+
},
|
|
215281
|
+
deepseek: {
|
|
215282
|
+
name: "DeepSeek",
|
|
215283
|
+
billing: "https://platform.deepseek.com/top_up",
|
|
215284
|
+
usage: "https://platform.deepseek.com/usage",
|
|
215285
|
+
keys: "https://platform.deepseek.com/api_keys"
|
|
215286
|
+
},
|
|
215287
|
+
moonshotai: {
|
|
215288
|
+
name: "Moonshot (Kimi API)",
|
|
215289
|
+
billing: "https://platform.kimi.ai/console/pay",
|
|
215290
|
+
usage: "https://platform.kimi.ai/console/account",
|
|
215291
|
+
keys: "https://platform.kimi.ai/console/api-keys"
|
|
215292
|
+
},
|
|
215293
|
+
"kimi-for-coding": {
|
|
215294
|
+
name: "Kimi Code",
|
|
215295
|
+
billing: "https://www.kimi.com/membership/pricing",
|
|
215296
|
+
keys: "https://www.kimi.com/code/console"
|
|
215297
|
+
},
|
|
215298
|
+
meta: {
|
|
215299
|
+
name: "Meta Model API",
|
|
215300
|
+
billing: "https://dev.meta.ai/billing",
|
|
215301
|
+
usage: "https://dev.meta.ai/usage",
|
|
215302
|
+
keys: "https://dev.meta.ai/api-keys"
|
|
215303
|
+
},
|
|
215304
|
+
opencode: {
|
|
215305
|
+
name: "OpenCode Zen",
|
|
215306
|
+
billing: "https://opencode.ai/zen",
|
|
215307
|
+
keys: "https://opencode.ai/zen"
|
|
215308
|
+
},
|
|
215309
|
+
// Go is a separate plan on the same key, with its own usage cap that resets
|
|
215310
|
+
// on its own (wiki/opencode-silent-stall.md). Its page is the plan page; no
|
|
215311
|
+
// per-window usage page is documented.
|
|
215312
|
+
"opencode-go": {
|
|
215313
|
+
name: "OpenCode Go",
|
|
215314
|
+
billing: "https://opencode.ai/go",
|
|
215315
|
+
keys: "https://opencode.ai/zen"
|
|
215316
|
+
},
|
|
215317
|
+
openrouter: {
|
|
215318
|
+
name: "OpenRouter",
|
|
215319
|
+
billing: "https://openrouter.ai/settings/credits",
|
|
215320
|
+
usage: "https://openrouter.ai/activity",
|
|
215321
|
+
keys: "https://openrouter.ai/workspaces/default/keys"
|
|
215322
|
+
},
|
|
215323
|
+
vercel: {
|
|
215324
|
+
name: "Vercel AI Gateway",
|
|
215325
|
+
// `/d?to=` resolves `[team]` to whichever team the visitor is signed into.
|
|
215326
|
+
billing: "https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai-gateway",
|
|
215327
|
+
keys: "https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai-gateway%2Fapi-keys"
|
|
215328
|
+
},
|
|
215329
|
+
bedrock: {
|
|
215330
|
+
name: "Amazon Bedrock",
|
|
215331
|
+
billing: "https://console.aws.amazon.com/billing/home",
|
|
215332
|
+
keys: "https://us-east-1.console.aws.amazon.com/bedrock/home#/api-keys"
|
|
215333
|
+
},
|
|
215334
|
+
// Azure OpenAI keys are per-resource, so there is no stable keys page to name.
|
|
215335
|
+
azure: {
|
|
215336
|
+
name: "Azure OpenAI",
|
|
215337
|
+
billing: "https://portal.azure.com/#view/Microsoft_Azure_CostManagement/Menu/~/overview"
|
|
215338
|
+
}
|
|
215339
|
+
};
|
|
215340
|
+
|
|
215176
215341
|
// utils/apiKeys.ts
|
|
215177
215342
|
var MISSING_KEY_MARKER = "no API key found";
|
|
215178
215343
|
var BYOK_SLUG_MARKER = "env var is required when the model is set to";
|
|
@@ -215497,14 +215662,22 @@ function buildRejectedCredentialError(params) {
|
|
|
215497
215662
|
].join("\n");
|
|
215498
215663
|
}
|
|
215499
215664
|
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)`;
|
|
215665
|
+
const keysUrl = providerKeysUrl(params.credential);
|
|
215666
|
+
const issue4 = keysUrl ? `[Issue a new key \u2192](${keysUrl}) \xB7 ` : "";
|
|
215500
215667
|
return [
|
|
215501
215668
|
`**Your \`${params.credential}\` ${CREDENTIAL_REJECTED_MARKER}**${detail}, so the agent never ran.`,
|
|
215502
215669
|
"",
|
|
215503
215670
|
`**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"}.`,
|
|
215504
215671
|
"",
|
|
215505
|
-
`${where} \xB7 [Model settings \u2192](${settingsUrl}) \xB7 [Setup docs \u2192](https://docs.pullfrog.com/keys) \xB7 [Ask in Discord \u2192](https://discord.gg/8y96raFg8e)`
|
|
215672
|
+
`${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)`
|
|
215506
215673
|
].join("\n");
|
|
215507
215674
|
}
|
|
215675
|
+
function providerKeysUrl(credential) {
|
|
215676
|
+
for (const [id, provider2] of Object.entries(providers)) {
|
|
215677
|
+
if (provider2.envVars.includes(credential)) return PROVIDER_DASHBOARDS[id]?.keys;
|
|
215678
|
+
}
|
|
215679
|
+
return void 0;
|
|
215680
|
+
}
|
|
215508
215681
|
function formatApiKeyErrorSummary(params) {
|
|
215509
215682
|
if (params.raw.includes(MISSING_KEY_MARKER)) {
|
|
215510
215683
|
if (params.raw.startsWith(`**${MISSING_KEY_MARKER}**`)) return params.raw;
|
|
@@ -215696,23 +215869,35 @@ function billingConsoleUrl(owner) {
|
|
|
215696
215869
|
return `https://pullfrog.com/console/${encodeURIComponent(owner)}#billing`;
|
|
215697
215870
|
}
|
|
215698
215871
|
function commercialPaywallBody(params) {
|
|
215699
|
-
|
|
215700
|
-
|
|
215701
|
-
|
|
215702
|
-
|
|
215703
|
-
|
|
215704
|
-
|
|
215705
|
-
|
|
215706
|
-
|
|
215872
|
+
const pitch = "Pro is required on private organization repos like this one: a flat $30/month per organization for unlimited runs, members, and repos.";
|
|
215873
|
+
switch (params.reason) {
|
|
215874
|
+
case "commercial":
|
|
215875
|
+
return [
|
|
215876
|
+
`**${params.ownerLogin}'s Pullfrog trial has expired.**`,
|
|
215877
|
+
"",
|
|
215878
|
+
pitch,
|
|
215879
|
+
"",
|
|
215880
|
+
`[Upgrade now \u2192](${params.url})`
|
|
215881
|
+
].join("\n");
|
|
215882
|
+
case "subscription_ended":
|
|
215883
|
+
return [
|
|
215884
|
+
`**${params.ownerLogin}'s Pullfrog Pro subscription has ended.**`,
|
|
215885
|
+
"",
|
|
215886
|
+
pitch,
|
|
215887
|
+
"",
|
|
215888
|
+
`[Resubscribe \u2192](${params.url})`
|
|
215889
|
+
].join("\n");
|
|
215890
|
+
case "subscription_unpaid":
|
|
215891
|
+
return [
|
|
215892
|
+
`**Pullfrog paused runs on ${params.ownerLogin}: the Pro renewal failed.**`,
|
|
215893
|
+
"",
|
|
215894
|
+
"Update the card on file to resume runs.",
|
|
215895
|
+
"",
|
|
215896
|
+
`[Update billing \u2192](${params.url})`
|
|
215897
|
+
].join("\n");
|
|
215898
|
+
default:
|
|
215899
|
+
return params.reason;
|
|
215707
215900
|
}
|
|
215708
|
-
params.reason;
|
|
215709
|
-
return [
|
|
215710
|
-
`**Pullfrog paused runs on ${params.ownerLogin}: the Pro renewal failed.**`,
|
|
215711
|
-
"",
|
|
215712
|
-
"Update the card on file to resume runs.",
|
|
215713
|
-
"",
|
|
215714
|
-
`[Update billing \u2192](${params.url})`
|
|
215715
|
-
].join("\n");
|
|
215716
215901
|
}
|
|
215717
215902
|
function formatCommercialGateSummary(params) {
|
|
215718
215903
|
return commercialPaywallBody({
|
|
@@ -215764,6 +215949,12 @@ function formatBillingErrorSummary(error109, owner) {
|
|
|
215764
215949
|
ownerLogin: owner
|
|
215765
215950
|
});
|
|
215766
215951
|
}
|
|
215952
|
+
if (error109.code === "subscription_ended") {
|
|
215953
|
+
return formatCommercialGateSummary({
|
|
215954
|
+
reason: "subscription_ended",
|
|
215955
|
+
ownerLogin: owner
|
|
215956
|
+
});
|
|
215957
|
+
}
|
|
215767
215958
|
if (error109.code === "subscription_unpaid") {
|
|
215768
215959
|
return formatCommercialGateSummary({
|
|
215769
215960
|
reason: "subscription_unpaid",
|
|
@@ -217676,8 +217867,9 @@ async function fetchRunContext(params) {
|
|
|
217676
217867
|
clearTimeout(timeoutId);
|
|
217677
217868
|
if (response.status === 402) {
|
|
217678
217869
|
const body = await response.json().catch(() => null);
|
|
217679
|
-
const reason = typeof body === "object" && body !== null && "reason" in body
|
|
217680
|
-
|
|
217870
|
+
const reason = typeof body === "object" && body !== null && "reason" in body ? body.reason : null;
|
|
217871
|
+
const commercialRefused = reason === "subscription_unpaid" || reason === "subscription_ended" ? reason : "commercial";
|
|
217872
|
+
return { ...defaultRunContext, commercialRefused };
|
|
217681
217873
|
}
|
|
217682
217874
|
if (!response.ok) {
|
|
217683
217875
|
return response.status >= 500 ? unknownSecretsRunContext : defaultRunContext;
|
|
@@ -217917,15 +218109,9 @@ function formatMinimalFailureComment(repo) {
|
|
|
217917
218109
|
const url5 = `${server}/${repo.owner}/${repo.name}/actions/runs/${runId}`;
|
|
217918
218110
|
return `**Run failed.** [View the logs \u2192](${url5})`;
|
|
217919
218111
|
}
|
|
217920
|
-
var PROVIDER_BILLING_URLS =
|
|
217921
|
-
|
|
217922
|
-
|
|
217923
|
-
openai: "https://platform.openai.com/account/billing",
|
|
217924
|
-
google: "https://aistudio.google.com/usage",
|
|
217925
|
-
opencode: "https://opencode.ai/zen",
|
|
217926
|
-
xai: "https://console.x.ai/team/default/billing",
|
|
217927
|
-
openrouter: "https://openrouter.ai/settings/credits"
|
|
217928
|
-
};
|
|
218112
|
+
var PROVIDER_BILLING_URLS = Object.fromEntries(
|
|
218113
|
+
Object.entries(PROVIDER_DASHBOARDS).map(([id, dashboard]) => [id, dashboard.billing])
|
|
218114
|
+
);
|
|
217929
218115
|
function detectProviderId(message) {
|
|
217930
218116
|
const harnessId = extractProviderId(message);
|
|
217931
218117
|
if (harnessId) return harnessId;
|
|
@@ -217936,7 +218122,6 @@ function detectProviderId(message) {
|
|
|
217936
218122
|
}
|
|
217937
218123
|
return null;
|
|
217938
218124
|
}
|
|
217939
|
-
var OPENROUTER_KEYS_URL = "https://openrouter.ai/workspaces/default/keys";
|
|
217940
218125
|
var OPENROUTER_PRIVACY_URL = "https://openrouter.ai/settings/privacy";
|
|
217941
218126
|
function formatProviderBillingExhausted(input2) {
|
|
217942
218127
|
const providerId = detectProviderId(input2.errorMessage);
|
|
@@ -217947,7 +218132,7 @@ function formatProviderBillingExhausted(input2) {
|
|
|
217947
218132
|
"",
|
|
217948
218133
|
"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.",
|
|
217949
218134
|
"",
|
|
217950
|
-
`[Raise the key's limit \u2192](${
|
|
218135
|
+
`[Raise the key's limit \u2192](${PROVIDER_DASHBOARDS.openrouter.keys}) \xB7 [Add credits \u2192](${PROVIDER_BILLING_URLS.openrouter})`,
|
|
217951
218136
|
"",
|
|
217952
218137
|
`\`\`\`
|
|
217953
218138
|
${input2.errorMessage}
|
|
@@ -218881,7 +219066,11 @@ async function main() {
|
|
|
218881
219066
|
authorPermission: void 0,
|
|
218882
219067
|
oidc: oidcCredentials
|
|
218883
219068
|
}), true);
|
|
218884
|
-
const errorMessage =
|
|
219069
|
+
const errorMessage = {
|
|
219070
|
+
commercial: "Pro trial expired for this organization",
|
|
219071
|
+
subscription_ended: "Pro subscription ended for this organization",
|
|
219072
|
+
subscription_unpaid: "Pro renewal failed for this organization"
|
|
219073
|
+
}[runContext.commercialRefused];
|
|
218885
219074
|
log2.error(errorMessage);
|
|
218886
219075
|
const body = formatCommercialGateSummary({
|
|
218887
219076
|
reason: runContext.commercialRefused,
|
|
@@ -219972,7 +220161,7 @@ function resolveTarget2(repo) {
|
|
|
219972
220161
|
return { owner: match3[1], repo: match3[2] };
|
|
219973
220162
|
}
|
|
219974
220163
|
function cliVersion() {
|
|
219975
|
-
const parts = /^(\d+)\.(\d+)\.(\d+)$/.exec("0.1.
|
|
220164
|
+
const parts = /^(\d+)\.(\d+)\.(\d+)$/.exec("0.1.80");
|
|
219976
220165
|
if (!parts) return "0.0.0";
|
|
219977
220166
|
return `${Number(parts[1])}.${Number(parts[2])}.${Number(parts[3])}`;
|
|
219978
220167
|
}
|
|
@@ -220139,7 +220328,7 @@ async function runCli8(input2) {
|
|
|
220139
220328
|
}
|
|
220140
220329
|
|
|
220141
220330
|
// cli.ts
|
|
220142
|
-
var VERSION10 = "0.1.
|
|
220331
|
+
var VERSION10 = "0.1.80";
|
|
220143
220332
|
var bin = basename2(process.argv[1] || "");
|
|
220144
220333
|
var PROG = bin === "pf" || bin === "pullfrog" ? bin : "pullfrog";
|
|
220145
220334
|
var rawArgs = process.argv.slice(2);
|
package/dist/index.js
CHANGED
|
@@ -108112,7 +108112,10 @@ var providers = {
|
|
|
108112
108112
|
"kimi-k2": {
|
|
108113
108113
|
displayName: "Kimi K2",
|
|
108114
108114
|
description: "Included in every Kimi membership tier",
|
|
108115
|
-
resolve: "kimi-for-coding/kimi-for-coding"
|
|
108115
|
+
resolve: "kimi-for-coding/kimi-for-coding",
|
|
108116
|
+
// models.dev published this ladder on 2026-09-14; the catalog gate
|
|
108117
|
+
// mirrors it, so main was red until the alias carried it too.
|
|
108118
|
+
effort: ["low", "high", "max"]
|
|
108116
108119
|
},
|
|
108117
108120
|
"kimi-k2-highspeed": {
|
|
108118
108121
|
displayName: "Kimi K2 HighSpeed",
|
|
@@ -108355,17 +108358,27 @@ var providers = {
|
|
|
108355
108358
|
effort: ["minimal", "low", "medium", "high"],
|
|
108356
108359
|
openRouterResolve: "openrouter/openai/gpt-5-nano"
|
|
108357
108360
|
},
|
|
108358
|
-
// Zen's PAID Muse Spark, priced like Meta's own Standard tier.
|
|
108359
|
-
//
|
|
108360
|
-
// Go refuses with DataPolicyError. unverified through the harness on
|
|
108361
|
-
// 2026-09-11: the workspace was out of balance and a claude-sonnet-5
|
|
108362
|
-
// control failed identically. see wiki/muse-spark.md.
|
|
108361
|
+
// Zen's PAID Muse Spark, priced like Meta's own Standard tier. answered
|
|
108362
|
+
// `OK` through the harness on 2026-09-12 once the workspace was funded.
|
|
108363
108363
|
"muse-spark": {
|
|
108364
108364
|
displayName: "Muse Spark",
|
|
108365
108365
|
resolve: "opencode/muse-spark-1.3",
|
|
108366
108366
|
effort: ["minimal", "low", "medium", "high", "xhigh", "max"],
|
|
108367
108367
|
openRouterResolve: "openrouter/meta/muse-spark-1.3"
|
|
108368
108368
|
},
|
|
108369
|
+
// Zen's FREE contributor tier: the same model at $0 because Meta trains
|
|
108370
|
+
// on the prompts and completions. Zen served it to our key on 2026-09-12
|
|
108371
|
+
// without the DataPolicyError Go raises for its contributor id. see
|
|
108372
|
+
// wiki/muse-spark.md.
|
|
108373
|
+
"muse-spark-contributor": {
|
|
108374
|
+
displayName: "Muse Spark Contributor",
|
|
108375
|
+
description: "Meta trains on prompts and completions",
|
|
108376
|
+
resolve: "opencode/muse-spark-1.3-contributor-free",
|
|
108377
|
+
effort: ["minimal", "low", "medium", "high", "xhigh"],
|
|
108378
|
+
// free to run, still gated on the provider's own OPENCODE_API_KEY —
|
|
108379
|
+
// see the big-pickle note above (#1077).
|
|
108380
|
+
isFree: true
|
|
108381
|
+
},
|
|
108369
108382
|
// Zen's live free MiMo, and the second free row in a menu that big-pickle
|
|
108370
108383
|
// was alone in since `mimo-v2-pro-free` lost its model.
|
|
108371
108384
|
mimo: {
|
|
@@ -109962,7 +109975,7 @@ var import_semver = __toESM(require_semver2(), 1);
|
|
|
109962
109975
|
// package.json
|
|
109963
109976
|
var package_default = {
|
|
109964
109977
|
name: "pullfrog",
|
|
109965
|
-
version: "0.1.
|
|
109978
|
+
version: "0.1.80",
|
|
109966
109979
|
cliContractVersion: "1",
|
|
109967
109980
|
type: "module",
|
|
109968
109981
|
bin: {
|
|
@@ -198033,6 +198046,115 @@ function subagentDeniedToolNames(ctx, outputSchema) {
|
|
|
198033
198046
|
return names;
|
|
198034
198047
|
}
|
|
198035
198048
|
|
|
198049
|
+
// utils/providerDashboards.ts
|
|
198050
|
+
var PROVIDER_DASHBOARDS = {
|
|
198051
|
+
anthropic: {
|
|
198052
|
+
name: "Anthropic",
|
|
198053
|
+
billing: "https://platform.claude.com/settings/billing",
|
|
198054
|
+
usage: "https://platform.claude.com/usage",
|
|
198055
|
+
keys: "https://platform.claude.com/settings/keys"
|
|
198056
|
+
},
|
|
198057
|
+
"claude-plan": {
|
|
198058
|
+
name: "Claude Pro/Max",
|
|
198059
|
+
credential: "CLAUDE_CODE_OAUTH_TOKEN",
|
|
198060
|
+
billing: "https://claude.ai/settings/billing",
|
|
198061
|
+
usage: "https://claude.ai/settings/usage"
|
|
198062
|
+
},
|
|
198063
|
+
openai: {
|
|
198064
|
+
name: "OpenAI",
|
|
198065
|
+
billing: "https://platform.openai.com/settings/organization/billing/overview",
|
|
198066
|
+
usage: "https://platform.openai.com/settings/organization/usage",
|
|
198067
|
+
keys: "https://platform.openai.com/api-keys"
|
|
198068
|
+
},
|
|
198069
|
+
"codex-plan": {
|
|
198070
|
+
name: "ChatGPT/Codex",
|
|
198071
|
+
credential: "CODEX_AUTH_JSON",
|
|
198072
|
+
billing: "https://chatgpt.com/#settings/Subscription",
|
|
198073
|
+
usage: "https://chatgpt.com/codex/settings/usage"
|
|
198074
|
+
},
|
|
198075
|
+
google: {
|
|
198076
|
+
name: "Google AI Studio",
|
|
198077
|
+
// AI Studio's "Usage & billing" page is where the Gemini API plan lives;
|
|
198078
|
+
// the Cloud billing console is a level up and needs a project picked.
|
|
198079
|
+
billing: "https://aistudio.google.com/usage",
|
|
198080
|
+
keys: "https://aistudio.google.com/apikey"
|
|
198081
|
+
},
|
|
198082
|
+
vertex: {
|
|
198083
|
+
name: "Google Vertex AI",
|
|
198084
|
+
billing: "https://console.cloud.google.com/billing",
|
|
198085
|
+
usage: "https://console.cloud.google.com/vertex-ai"
|
|
198086
|
+
},
|
|
198087
|
+
xai: {
|
|
198088
|
+
name: "xAI",
|
|
198089
|
+
billing: "https://console.x.ai/team/default/billing",
|
|
198090
|
+
usage: "https://console.x.ai/team/default/usage",
|
|
198091
|
+
keys: "https://console.x.ai/team/default/api-keys"
|
|
198092
|
+
},
|
|
198093
|
+
"grok-plan": {
|
|
198094
|
+
name: "SuperGrok",
|
|
198095
|
+
credential: "GROK_AUTH_JSON",
|
|
198096
|
+
billing: "https://grok.com/?_s=billing"
|
|
198097
|
+
},
|
|
198098
|
+
deepseek: {
|
|
198099
|
+
name: "DeepSeek",
|
|
198100
|
+
billing: "https://platform.deepseek.com/top_up",
|
|
198101
|
+
usage: "https://platform.deepseek.com/usage",
|
|
198102
|
+
keys: "https://platform.deepseek.com/api_keys"
|
|
198103
|
+
},
|
|
198104
|
+
moonshotai: {
|
|
198105
|
+
name: "Moonshot (Kimi API)",
|
|
198106
|
+
billing: "https://platform.kimi.ai/console/pay",
|
|
198107
|
+
usage: "https://platform.kimi.ai/console/account",
|
|
198108
|
+
keys: "https://platform.kimi.ai/console/api-keys"
|
|
198109
|
+
},
|
|
198110
|
+
"kimi-for-coding": {
|
|
198111
|
+
name: "Kimi Code",
|
|
198112
|
+
billing: "https://www.kimi.com/membership/pricing",
|
|
198113
|
+
keys: "https://www.kimi.com/code/console"
|
|
198114
|
+
},
|
|
198115
|
+
meta: {
|
|
198116
|
+
name: "Meta Model API",
|
|
198117
|
+
billing: "https://dev.meta.ai/billing",
|
|
198118
|
+
usage: "https://dev.meta.ai/usage",
|
|
198119
|
+
keys: "https://dev.meta.ai/api-keys"
|
|
198120
|
+
},
|
|
198121
|
+
opencode: {
|
|
198122
|
+
name: "OpenCode Zen",
|
|
198123
|
+
billing: "https://opencode.ai/zen",
|
|
198124
|
+
keys: "https://opencode.ai/zen"
|
|
198125
|
+
},
|
|
198126
|
+
// Go is a separate plan on the same key, with its own usage cap that resets
|
|
198127
|
+
// on its own (wiki/opencode-silent-stall.md). Its page is the plan page; no
|
|
198128
|
+
// per-window usage page is documented.
|
|
198129
|
+
"opencode-go": {
|
|
198130
|
+
name: "OpenCode Go",
|
|
198131
|
+
billing: "https://opencode.ai/go",
|
|
198132
|
+
keys: "https://opencode.ai/zen"
|
|
198133
|
+
},
|
|
198134
|
+
openrouter: {
|
|
198135
|
+
name: "OpenRouter",
|
|
198136
|
+
billing: "https://openrouter.ai/settings/credits",
|
|
198137
|
+
usage: "https://openrouter.ai/activity",
|
|
198138
|
+
keys: "https://openrouter.ai/workspaces/default/keys"
|
|
198139
|
+
},
|
|
198140
|
+
vercel: {
|
|
198141
|
+
name: "Vercel AI Gateway",
|
|
198142
|
+
// `/d?to=` resolves `[team]` to whichever team the visitor is signed into.
|
|
198143
|
+
billing: "https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai-gateway",
|
|
198144
|
+
keys: "https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai-gateway%2Fapi-keys"
|
|
198145
|
+
},
|
|
198146
|
+
bedrock: {
|
|
198147
|
+
name: "Amazon Bedrock",
|
|
198148
|
+
billing: "https://console.aws.amazon.com/billing/home",
|
|
198149
|
+
keys: "https://us-east-1.console.aws.amazon.com/bedrock/home#/api-keys"
|
|
198150
|
+
},
|
|
198151
|
+
// Azure OpenAI keys are per-resource, so there is no stable keys page to name.
|
|
198152
|
+
azure: {
|
|
198153
|
+
name: "Azure OpenAI",
|
|
198154
|
+
billing: "https://portal.azure.com/#view/Microsoft_Azure_CostManagement/Menu/~/overview"
|
|
198155
|
+
}
|
|
198156
|
+
};
|
|
198157
|
+
|
|
198036
198158
|
// utils/apiKeys.ts
|
|
198037
198159
|
var MISSING_KEY_MARKER = "no API key found";
|
|
198038
198160
|
var BYOK_SLUG_MARKER = "env var is required when the model is set to";
|
|
@@ -198357,14 +198479,22 @@ function buildRejectedCredentialError(params) {
|
|
|
198357
198479
|
].join("\n");
|
|
198358
198480
|
}
|
|
198359
198481
|
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)`;
|
|
198482
|
+
const keysUrl = providerKeysUrl(params.credential);
|
|
198483
|
+
const issue3 = keysUrl ? `[Issue a new key \u2192](${keysUrl}) \xB7 ` : "";
|
|
198360
198484
|
return [
|
|
198361
198485
|
`**Your \`${params.credential}\` ${CREDENTIAL_REJECTED_MARKER}**${detail}, so the agent never ran.`,
|
|
198362
198486
|
"",
|
|
198363
198487
|
`**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"}.`,
|
|
198364
198488
|
"",
|
|
198365
|
-
`${where} \xB7 [Model settings \u2192](${settingsUrl}) \xB7 [Setup docs \u2192](https://docs.pullfrog.com/keys) \xB7 [Ask in Discord \u2192](https://discord.gg/8y96raFg8e)`
|
|
198489
|
+
`${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)`
|
|
198366
198490
|
].join("\n");
|
|
198367
198491
|
}
|
|
198492
|
+
function providerKeysUrl(credential) {
|
|
198493
|
+
for (const [id, provider2] of Object.entries(providers)) {
|
|
198494
|
+
if (provider2.envVars.includes(credential)) return PROVIDER_DASHBOARDS[id]?.keys;
|
|
198495
|
+
}
|
|
198496
|
+
return void 0;
|
|
198497
|
+
}
|
|
198368
198498
|
function formatApiKeyErrorSummary(params) {
|
|
198369
198499
|
if (params.raw.includes(MISSING_KEY_MARKER)) {
|
|
198370
198500
|
if (params.raw.startsWith(`**${MISSING_KEY_MARKER}**`)) return params.raw;
|
|
@@ -198556,23 +198686,35 @@ function billingConsoleUrl(owner) {
|
|
|
198556
198686
|
return `https://pullfrog.com/console/${encodeURIComponent(owner)}#billing`;
|
|
198557
198687
|
}
|
|
198558
198688
|
function commercialPaywallBody(params) {
|
|
198559
|
-
|
|
198560
|
-
|
|
198561
|
-
|
|
198562
|
-
|
|
198563
|
-
|
|
198564
|
-
|
|
198565
|
-
|
|
198566
|
-
|
|
198689
|
+
const pitch = "Pro is required on private organization repos like this one: a flat $30/month per organization for unlimited runs, members, and repos.";
|
|
198690
|
+
switch (params.reason) {
|
|
198691
|
+
case "commercial":
|
|
198692
|
+
return [
|
|
198693
|
+
`**${params.ownerLogin}'s Pullfrog trial has expired.**`,
|
|
198694
|
+
"",
|
|
198695
|
+
pitch,
|
|
198696
|
+
"",
|
|
198697
|
+
`[Upgrade now \u2192](${params.url})`
|
|
198698
|
+
].join("\n");
|
|
198699
|
+
case "subscription_ended":
|
|
198700
|
+
return [
|
|
198701
|
+
`**${params.ownerLogin}'s Pullfrog Pro subscription has ended.**`,
|
|
198702
|
+
"",
|
|
198703
|
+
pitch,
|
|
198704
|
+
"",
|
|
198705
|
+
`[Resubscribe \u2192](${params.url})`
|
|
198706
|
+
].join("\n");
|
|
198707
|
+
case "subscription_unpaid":
|
|
198708
|
+
return [
|
|
198709
|
+
`**Pullfrog paused runs on ${params.ownerLogin}: the Pro renewal failed.**`,
|
|
198710
|
+
"",
|
|
198711
|
+
"Update the card on file to resume runs.",
|
|
198712
|
+
"",
|
|
198713
|
+
`[Update billing \u2192](${params.url})`
|
|
198714
|
+
].join("\n");
|
|
198715
|
+
default:
|
|
198716
|
+
return params.reason;
|
|
198567
198717
|
}
|
|
198568
|
-
params.reason;
|
|
198569
|
-
return [
|
|
198570
|
-
`**Pullfrog paused runs on ${params.ownerLogin}: the Pro renewal failed.**`,
|
|
198571
|
-
"",
|
|
198572
|
-
"Update the card on file to resume runs.",
|
|
198573
|
-
"",
|
|
198574
|
-
`[Update billing \u2192](${params.url})`
|
|
198575
|
-
].join("\n");
|
|
198576
198718
|
}
|
|
198577
198719
|
function formatCommercialGateSummary(params) {
|
|
198578
198720
|
return commercialPaywallBody({
|
|
@@ -198624,6 +198766,12 @@ function formatBillingErrorSummary(error62, owner) {
|
|
|
198624
198766
|
ownerLogin: owner
|
|
198625
198767
|
});
|
|
198626
198768
|
}
|
|
198769
|
+
if (error62.code === "subscription_ended") {
|
|
198770
|
+
return formatCommercialGateSummary({
|
|
198771
|
+
reason: "subscription_ended",
|
|
198772
|
+
ownerLogin: owner
|
|
198773
|
+
});
|
|
198774
|
+
}
|
|
198627
198775
|
if (error62.code === "subscription_unpaid") {
|
|
198628
198776
|
return formatCommercialGateSummary({
|
|
198629
198777
|
reason: "subscription_unpaid",
|
|
@@ -200536,8 +200684,9 @@ async function fetchRunContext(params) {
|
|
|
200536
200684
|
clearTimeout(timeoutId);
|
|
200537
200685
|
if (response.status === 402) {
|
|
200538
200686
|
const body = await response.json().catch(() => null);
|
|
200539
|
-
const reason = typeof body === "object" && body !== null && "reason" in body
|
|
200540
|
-
|
|
200687
|
+
const reason = typeof body === "object" && body !== null && "reason" in body ? body.reason : null;
|
|
200688
|
+
const commercialRefused = reason === "subscription_unpaid" || reason === "subscription_ended" ? reason : "commercial";
|
|
200689
|
+
return { ...defaultRunContext, commercialRefused };
|
|
200541
200690
|
}
|
|
200542
200691
|
if (!response.ok) {
|
|
200543
200692
|
return response.status >= 500 ? unknownSecretsRunContext : defaultRunContext;
|
|
@@ -200777,15 +200926,9 @@ function formatMinimalFailureComment(repo) {
|
|
|
200777
200926
|
const url4 = `${server}/${repo.owner}/${repo.name}/actions/runs/${runId}`;
|
|
200778
200927
|
return `**Run failed.** [View the logs \u2192](${url4})`;
|
|
200779
200928
|
}
|
|
200780
|
-
var PROVIDER_BILLING_URLS =
|
|
200781
|
-
|
|
200782
|
-
|
|
200783
|
-
openai: "https://platform.openai.com/account/billing",
|
|
200784
|
-
google: "https://aistudio.google.com/usage",
|
|
200785
|
-
opencode: "https://opencode.ai/zen",
|
|
200786
|
-
xai: "https://console.x.ai/team/default/billing",
|
|
200787
|
-
openrouter: "https://openrouter.ai/settings/credits"
|
|
200788
|
-
};
|
|
200929
|
+
var PROVIDER_BILLING_URLS = Object.fromEntries(
|
|
200930
|
+
Object.entries(PROVIDER_DASHBOARDS).map(([id, dashboard]) => [id, dashboard.billing])
|
|
200931
|
+
);
|
|
200789
200932
|
function detectProviderId(message) {
|
|
200790
200933
|
const harnessId = extractProviderId(message);
|
|
200791
200934
|
if (harnessId) return harnessId;
|
|
@@ -200796,7 +200939,6 @@ function detectProviderId(message) {
|
|
|
200796
200939
|
}
|
|
200797
200940
|
return null;
|
|
200798
200941
|
}
|
|
200799
|
-
var OPENROUTER_KEYS_URL = "https://openrouter.ai/workspaces/default/keys";
|
|
200800
200942
|
var OPENROUTER_PRIVACY_URL = "https://openrouter.ai/settings/privacy";
|
|
200801
200943
|
function formatProviderBillingExhausted(input2) {
|
|
200802
200944
|
const providerId = detectProviderId(input2.errorMessage);
|
|
@@ -200807,7 +200949,7 @@ function formatProviderBillingExhausted(input2) {
|
|
|
200807
200949
|
"",
|
|
200808
200950
|
"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.",
|
|
200809
200951
|
"",
|
|
200810
|
-
`[Raise the key's limit \u2192](${
|
|
200952
|
+
`[Raise the key's limit \u2192](${PROVIDER_DASHBOARDS.openrouter.keys}) \xB7 [Add credits \u2192](${PROVIDER_BILLING_URLS.openrouter})`,
|
|
200811
200953
|
"",
|
|
200812
200954
|
`\`\`\`
|
|
200813
200955
|
${input2.errorMessage}
|
|
@@ -201741,7 +201883,11 @@ async function main() {
|
|
|
201741
201883
|
authorPermission: void 0,
|
|
201742
201884
|
oidc: oidcCredentials
|
|
201743
201885
|
}), true);
|
|
201744
|
-
const errorMessage =
|
|
201886
|
+
const errorMessage = {
|
|
201887
|
+
commercial: "Pro trial expired for this organization",
|
|
201888
|
+
subscription_ended: "Pro subscription ended for this organization",
|
|
201889
|
+
subscription_unpaid: "Pro renewal failed for this organization"
|
|
201890
|
+
}[runContext.commercialRefused];
|
|
201745
201891
|
log.error(errorMessage);
|
|
201746
201892
|
const body = formatCommercialGateSummary({
|
|
201747
201893
|
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.80",
|
|
12
12
|
cliContractVersion: "1",
|
|
13
13
|
type: "module",
|
|
14
14
|
bin: {
|
|
@@ -14680,7 +14680,10 @@ var providers = {
|
|
|
14680
14680
|
"kimi-k2": {
|
|
14681
14681
|
displayName: "Kimi K2",
|
|
14682
14682
|
description: "Included in every Kimi membership tier",
|
|
14683
|
-
resolve: "kimi-for-coding/kimi-for-coding"
|
|
14683
|
+
resolve: "kimi-for-coding/kimi-for-coding",
|
|
14684
|
+
// models.dev published this ladder on 2026-09-14; the catalog gate
|
|
14685
|
+
// mirrors it, so main was red until the alias carried it too.
|
|
14686
|
+
effort: ["low", "high", "max"]
|
|
14684
14687
|
},
|
|
14685
14688
|
"kimi-k2-highspeed": {
|
|
14686
14689
|
displayName: "Kimi K2 HighSpeed",
|
|
@@ -14923,17 +14926,27 @@ var providers = {
|
|
|
14923
14926
|
effort: ["minimal", "low", "medium", "high"],
|
|
14924
14927
|
openRouterResolve: "openrouter/openai/gpt-5-nano"
|
|
14925
14928
|
},
|
|
14926
|
-
// Zen's PAID Muse Spark, priced like Meta's own Standard tier.
|
|
14927
|
-
//
|
|
14928
|
-
// Go refuses with DataPolicyError. unverified through the harness on
|
|
14929
|
-
// 2026-09-11: the workspace was out of balance and a claude-sonnet-5
|
|
14930
|
-
// control failed identically. see wiki/muse-spark.md.
|
|
14929
|
+
// Zen's PAID Muse Spark, priced like Meta's own Standard tier. answered
|
|
14930
|
+
// `OK` through the harness on 2026-09-12 once the workspace was funded.
|
|
14931
14931
|
"muse-spark": {
|
|
14932
14932
|
displayName: "Muse Spark",
|
|
14933
14933
|
resolve: "opencode/muse-spark-1.3",
|
|
14934
14934
|
effort: ["minimal", "low", "medium", "high", "xhigh", "max"],
|
|
14935
14935
|
openRouterResolve: "openrouter/meta/muse-spark-1.3"
|
|
14936
14936
|
},
|
|
14937
|
+
// Zen's FREE contributor tier: the same model at $0 because Meta trains
|
|
14938
|
+
// on the prompts and completions. Zen served it to our key on 2026-09-12
|
|
14939
|
+
// without the DataPolicyError Go raises for its contributor id. see
|
|
14940
|
+
// wiki/muse-spark.md.
|
|
14941
|
+
"muse-spark-contributor": {
|
|
14942
|
+
displayName: "Muse Spark Contributor",
|
|
14943
|
+
description: "Meta trains on prompts and completions",
|
|
14944
|
+
resolve: "opencode/muse-spark-1.3-contributor-free",
|
|
14945
|
+
effort: ["minimal", "low", "medium", "high", "xhigh"],
|
|
14946
|
+
// free to run, still gated on the provider's own OPENCODE_API_KEY —
|
|
14947
|
+
// see the big-pickle note above (#1077).
|
|
14948
|
+
isFree: true
|
|
14949
|
+
},
|
|
14937
14950
|
// Zen's live free MiMo, and the second free row in a menu that big-pickle
|
|
14938
14951
|
// was alone in since `mimo-v2-pro-free` lost its model.
|
|
14939
14952
|
mimo: {
|
|
@@ -16138,23 +16151,35 @@ var modes = computeModes("opencode");
|
|
|
16138
16151
|
|
|
16139
16152
|
// utils/billingErrors.ts
|
|
16140
16153
|
function commercialPaywallBody(params) {
|
|
16141
|
-
|
|
16142
|
-
|
|
16143
|
-
|
|
16144
|
-
|
|
16145
|
-
|
|
16146
|
-
|
|
16147
|
-
|
|
16148
|
-
|
|
16149
|
-
|
|
16150
|
-
|
|
16151
|
-
|
|
16152
|
-
|
|
16153
|
-
|
|
16154
|
-
|
|
16155
|
-
|
|
16156
|
-
|
|
16157
|
-
|
|
16154
|
+
const pitch = "Pro is required on private organization repos like this one: a flat $30/month per organization for unlimited runs, members, and repos.";
|
|
16155
|
+
switch (params.reason) {
|
|
16156
|
+
case "commercial":
|
|
16157
|
+
return [
|
|
16158
|
+
`**${params.ownerLogin}'s Pullfrog trial has expired.**`,
|
|
16159
|
+
"",
|
|
16160
|
+
pitch,
|
|
16161
|
+
"",
|
|
16162
|
+
`[Upgrade now \u2192](${params.url})`
|
|
16163
|
+
].join("\n");
|
|
16164
|
+
case "subscription_ended":
|
|
16165
|
+
return [
|
|
16166
|
+
`**${params.ownerLogin}'s Pullfrog Pro subscription has ended.**`,
|
|
16167
|
+
"",
|
|
16168
|
+
pitch,
|
|
16169
|
+
"",
|
|
16170
|
+
`[Resubscribe \u2192](${params.url})`
|
|
16171
|
+
].join("\n");
|
|
16172
|
+
case "subscription_unpaid":
|
|
16173
|
+
return [
|
|
16174
|
+
`**Pullfrog paused runs on ${params.ownerLogin}: the Pro renewal failed.**`,
|
|
16175
|
+
"",
|
|
16176
|
+
"Update the card on file to resume runs.",
|
|
16177
|
+
"",
|
|
16178
|
+
`[Update billing \u2192](${params.url})`
|
|
16179
|
+
].join("\n");
|
|
16180
|
+
default:
|
|
16181
|
+
return params.reason;
|
|
16182
|
+
}
|
|
16158
16183
|
}
|
|
16159
16184
|
|
|
16160
16185
|
// utils/buildPullfrogFooter.ts
|
|
@@ -16287,10 +16312,10 @@ function decodeJwtExpMs(token) {
|
|
|
16287
16312
|
// utils/codexOAuth.ts
|
|
16288
16313
|
var CODEX_OAUTH_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
16289
16314
|
var CODEX_OAUTH_TOKEN_URL = "https://auth.openai.com/oauth/token";
|
|
16290
|
-
function codexChainIsDead(body) {
|
|
16315
|
+
function codexChainIsDead(status, body) {
|
|
16316
|
+
if (status !== 401) return false;
|
|
16291
16317
|
const err = parseOAuthErrorBody(body)?.error;
|
|
16292
|
-
|
|
16293
|
-
return "code" in err && err.code === "token_expired";
|
|
16318
|
+
return !(err && typeof err === "object" && "code" in err && err.code === "invalid_client");
|
|
16294
16319
|
}
|
|
16295
16320
|
async function refreshCodexAuthBody(body) {
|
|
16296
16321
|
const response = await fetch(CODEX_OAUTH_TOKEN_URL, {
|
|
@@ -16306,7 +16331,12 @@ async function refreshCodexAuthBody(body) {
|
|
|
16306
16331
|
if (!response.ok) {
|
|
16307
16332
|
const text = await response.text().catch(() => "");
|
|
16308
16333
|
if (response.status >= 400 && response.status < 500) {
|
|
16309
|
-
throw new OAuthInvalidGrantError(
|
|
16334
|
+
throw new OAuthInvalidGrantError(
|
|
16335
|
+
"Codex",
|
|
16336
|
+
response.status,
|
|
16337
|
+
text,
|
|
16338
|
+
codexChainIsDead(response.status, text)
|
|
16339
|
+
);
|
|
16310
16340
|
}
|
|
16311
16341
|
throw new Error(`Codex token refresh failed: ${response.status} ${text}`);
|
|
16312
16342
|
}
|
|
@@ -16585,6 +16615,115 @@ async function createLeapingProgressComment(ctx, target, body) {
|
|
|
16585
16615
|
};
|
|
16586
16616
|
}
|
|
16587
16617
|
|
|
16618
|
+
// utils/providerDashboards.ts
|
|
16619
|
+
var PROVIDER_DASHBOARDS = {
|
|
16620
|
+
anthropic: {
|
|
16621
|
+
name: "Anthropic",
|
|
16622
|
+
billing: "https://platform.claude.com/settings/billing",
|
|
16623
|
+
usage: "https://platform.claude.com/usage",
|
|
16624
|
+
keys: "https://platform.claude.com/settings/keys"
|
|
16625
|
+
},
|
|
16626
|
+
"claude-plan": {
|
|
16627
|
+
name: "Claude Pro/Max",
|
|
16628
|
+
credential: "CLAUDE_CODE_OAUTH_TOKEN",
|
|
16629
|
+
billing: "https://claude.ai/settings/billing",
|
|
16630
|
+
usage: "https://claude.ai/settings/usage"
|
|
16631
|
+
},
|
|
16632
|
+
openai: {
|
|
16633
|
+
name: "OpenAI",
|
|
16634
|
+
billing: "https://platform.openai.com/settings/organization/billing/overview",
|
|
16635
|
+
usage: "https://platform.openai.com/settings/organization/usage",
|
|
16636
|
+
keys: "https://platform.openai.com/api-keys"
|
|
16637
|
+
},
|
|
16638
|
+
"codex-plan": {
|
|
16639
|
+
name: "ChatGPT/Codex",
|
|
16640
|
+
credential: "CODEX_AUTH_JSON",
|
|
16641
|
+
billing: "https://chatgpt.com/#settings/Subscription",
|
|
16642
|
+
usage: "https://chatgpt.com/codex/settings/usage"
|
|
16643
|
+
},
|
|
16644
|
+
google: {
|
|
16645
|
+
name: "Google AI Studio",
|
|
16646
|
+
// AI Studio's "Usage & billing" page is where the Gemini API plan lives;
|
|
16647
|
+
// the Cloud billing console is a level up and needs a project picked.
|
|
16648
|
+
billing: "https://aistudio.google.com/usage",
|
|
16649
|
+
keys: "https://aistudio.google.com/apikey"
|
|
16650
|
+
},
|
|
16651
|
+
vertex: {
|
|
16652
|
+
name: "Google Vertex AI",
|
|
16653
|
+
billing: "https://console.cloud.google.com/billing",
|
|
16654
|
+
usage: "https://console.cloud.google.com/vertex-ai"
|
|
16655
|
+
},
|
|
16656
|
+
xai: {
|
|
16657
|
+
name: "xAI",
|
|
16658
|
+
billing: "https://console.x.ai/team/default/billing",
|
|
16659
|
+
usage: "https://console.x.ai/team/default/usage",
|
|
16660
|
+
keys: "https://console.x.ai/team/default/api-keys"
|
|
16661
|
+
},
|
|
16662
|
+
"grok-plan": {
|
|
16663
|
+
name: "SuperGrok",
|
|
16664
|
+
credential: "GROK_AUTH_JSON",
|
|
16665
|
+
billing: "https://grok.com/?_s=billing"
|
|
16666
|
+
},
|
|
16667
|
+
deepseek: {
|
|
16668
|
+
name: "DeepSeek",
|
|
16669
|
+
billing: "https://platform.deepseek.com/top_up",
|
|
16670
|
+
usage: "https://platform.deepseek.com/usage",
|
|
16671
|
+
keys: "https://platform.deepseek.com/api_keys"
|
|
16672
|
+
},
|
|
16673
|
+
moonshotai: {
|
|
16674
|
+
name: "Moonshot (Kimi API)",
|
|
16675
|
+
billing: "https://platform.kimi.ai/console/pay",
|
|
16676
|
+
usage: "https://platform.kimi.ai/console/account",
|
|
16677
|
+
keys: "https://platform.kimi.ai/console/api-keys"
|
|
16678
|
+
},
|
|
16679
|
+
"kimi-for-coding": {
|
|
16680
|
+
name: "Kimi Code",
|
|
16681
|
+
billing: "https://www.kimi.com/membership/pricing",
|
|
16682
|
+
keys: "https://www.kimi.com/code/console"
|
|
16683
|
+
},
|
|
16684
|
+
meta: {
|
|
16685
|
+
name: "Meta Model API",
|
|
16686
|
+
billing: "https://dev.meta.ai/billing",
|
|
16687
|
+
usage: "https://dev.meta.ai/usage",
|
|
16688
|
+
keys: "https://dev.meta.ai/api-keys"
|
|
16689
|
+
},
|
|
16690
|
+
opencode: {
|
|
16691
|
+
name: "OpenCode Zen",
|
|
16692
|
+
billing: "https://opencode.ai/zen",
|
|
16693
|
+
keys: "https://opencode.ai/zen"
|
|
16694
|
+
},
|
|
16695
|
+
// Go is a separate plan on the same key, with its own usage cap that resets
|
|
16696
|
+
// on its own (wiki/opencode-silent-stall.md). Its page is the plan page; no
|
|
16697
|
+
// per-window usage page is documented.
|
|
16698
|
+
"opencode-go": {
|
|
16699
|
+
name: "OpenCode Go",
|
|
16700
|
+
billing: "https://opencode.ai/go",
|
|
16701
|
+
keys: "https://opencode.ai/zen"
|
|
16702
|
+
},
|
|
16703
|
+
openrouter: {
|
|
16704
|
+
name: "OpenRouter",
|
|
16705
|
+
billing: "https://openrouter.ai/settings/credits",
|
|
16706
|
+
usage: "https://openrouter.ai/activity",
|
|
16707
|
+
keys: "https://openrouter.ai/workspaces/default/keys"
|
|
16708
|
+
},
|
|
16709
|
+
vercel: {
|
|
16710
|
+
name: "Vercel AI Gateway",
|
|
16711
|
+
// `/d?to=` resolves `[team]` to whichever team the visitor is signed into.
|
|
16712
|
+
billing: "https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai-gateway",
|
|
16713
|
+
keys: "https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai-gateway%2Fapi-keys"
|
|
16714
|
+
},
|
|
16715
|
+
bedrock: {
|
|
16716
|
+
name: "Amazon Bedrock",
|
|
16717
|
+
billing: "https://console.aws.amazon.com/billing/home",
|
|
16718
|
+
keys: "https://us-east-1.console.aws.amazon.com/bedrock/home#/api-keys"
|
|
16719
|
+
},
|
|
16720
|
+
// Azure OpenAI keys are per-resource, so there is no stable keys page to name.
|
|
16721
|
+
azure: {
|
|
16722
|
+
name: "Azure OpenAI",
|
|
16723
|
+
billing: "https://portal.azure.com/#view/Microsoft_Azure_CostManagement/Menu/~/overview"
|
|
16724
|
+
}
|
|
16725
|
+
};
|
|
16726
|
+
|
|
16588
16727
|
// utils/runStatusCheck.ts
|
|
16589
16728
|
var RUN_STATUS_CHECK_NAME = "pullfrog";
|
|
16590
16729
|
var GITHUB_ACTIONS_APP_SLUG = "github-actions";
|
|
@@ -16793,6 +16932,7 @@ export {
|
|
|
16793
16932
|
OAuthInvalidGrantError,
|
|
16794
16933
|
OSS_MODEL_ALLOWLIST,
|
|
16795
16934
|
OSS_RECOMMENDED_MODEL,
|
|
16935
|
+
PROVIDER_DASHBOARDS,
|
|
16796
16936
|
PROVIDER_GATEWAY_URL_ENV,
|
|
16797
16937
|
PULLFROG_DIVIDER,
|
|
16798
16938
|
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;
|