pullfrog 0.1.89 → 0.1.90

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 CHANGED
@@ -107103,7 +107103,7 @@ var import_picocolors = __toESM(require_picocolors(), 1);
107103
107103
  // package.json
107104
107104
  var package_default = {
107105
107105
  name: "pullfrog",
107106
- version: "0.1.89",
107106
+ version: "0.1.90",
107107
107107
  cliContractVersion: "1",
107108
107108
  type: "module",
107109
107109
  bin: {
@@ -196672,8 +196672,9 @@ function ResolveReviewThreadTool(ctx) {
196672
196672
  }
196673
196673
 
196674
196674
  // mcp/searchIssues.ts
196675
+ var TERM = /^[\p{L}\p{N}_./-]+$/u;
196675
196676
  var SearchIssues = type({
196676
- terms: type.string.matching(/^[\p{L}\p{N}_./-]+$/u).atMostLength(40).array().atLeastLength(1).atMostLength(5).describe("1-5 keywords from the issue; no GitHub qualifiers or query operators")
196677
+ terms: type.string.atMostLength(40).array().atLeastLength(1).atMostLength(5).describe("1-5 keywords from the issue; no GitHub qualifiers or query operators")
196677
196678
  });
196678
196679
  function SearchIssuesTool(ctx) {
196679
196680
  return tool({
@@ -196681,6 +196682,10 @@ function SearchIssuesTool(ctx) {
196681
196682
  description: "Find open or closed issues in this repository by keyword. Inspect promising matches with get_issue before deciding whether they are duplicates or related. Results are bounded, not an exhaustive duplicate check.",
196682
196683
  parameters: SearchIssues,
196683
196684
  execute: execute(async (params) => {
196685
+ const invalid = params.terms.find((term) => !TERM.test(term));
196686
+ if (invalid) {
196687
+ throw new Error(`search term "${invalid}" may contain only letters, digits and _ . / -`);
196688
+ }
196684
196689
  const repoPath = `/repos/${ctx.repo.owner}/${ctx.repo.name}`.toLowerCase();
196685
196690
  const terms = params.terms.map((term) => `"${term}"`).join(" ");
196686
196691
  const response = await ctx.octokit.rest.search.issuesAndPullRequests({
@@ -202225,7 +202230,7 @@ function resolveTarget2(repo) {
202225
202230
  return { owner: match3[1], repo: match3[2] };
202226
202231
  }
202227
202232
  function cliVersion() {
202228
- const parts = /^(\d+)\.(\d+)\.(\d+)$/.exec("0.1.89");
202233
+ const parts = /^(\d+)\.(\d+)\.(\d+)$/.exec("0.1.90");
202229
202234
  if (!parts) return "0.0.0";
202230
202235
  return `${Number(parts[1])}.${Number(parts[2])}.${Number(parts[3])}`;
202231
202236
  }
@@ -202392,7 +202397,7 @@ async function runCli8(input) {
202392
202397
  }
202393
202398
 
202394
202399
  // cli.ts
202395
- var VERSION10 = "0.1.89";
202400
+ var VERSION10 = "0.1.90";
202396
202401
  var bin = basename2(process.argv[1] || "");
202397
202402
  var PROG = bin === "pf" || bin === "pullfrog" ? bin : "pullfrog";
202398
202403
  var rawArgs = process.argv.slice(2);
package/dist/index.js CHANGED
@@ -105729,7 +105729,7 @@ var import_semver = __toESM(require_semver2(), 1);
105729
105729
  // package.json
105730
105730
  var package_default = {
105731
105731
  name: "pullfrog",
105732
- version: "0.1.89",
105732
+ version: "0.1.90",
105733
105733
  cliContractVersion: "1",
105734
105734
  type: "module",
105735
105735
  bin: {
@@ -193165,8 +193165,9 @@ function ResolveReviewThreadTool(ctx) {
193165
193165
  }
193166
193166
 
193167
193167
  // mcp/searchIssues.ts
193168
+ var TERM = /^[\p{L}\p{N}_./-]+$/u;
193168
193169
  var SearchIssues = type({
193169
- terms: type.string.matching(/^[\p{L}\p{N}_./-]+$/u).atMostLength(40).array().atLeastLength(1).atMostLength(5).describe("1-5 keywords from the issue; no GitHub qualifiers or query operators")
193170
+ terms: type.string.atMostLength(40).array().atLeastLength(1).atMostLength(5).describe("1-5 keywords from the issue; no GitHub qualifiers or query operators")
193170
193171
  });
193171
193172
  function SearchIssuesTool(ctx) {
193172
193173
  return tool({
@@ -193174,6 +193175,10 @@ function SearchIssuesTool(ctx) {
193174
193175
  description: "Find open or closed issues in this repository by keyword. Inspect promising matches with get_issue before deciding whether they are duplicates or related. Results are bounded, not an exhaustive duplicate check.",
193175
193176
  parameters: SearchIssues,
193176
193177
  execute: execute(async (params) => {
193178
+ const invalid = params.terms.find((term) => !TERM.test(term));
193179
+ if (invalid) {
193180
+ throw new Error(`search term "${invalid}" may contain only letters, digits and _ . / -`);
193181
+ }
193177
193182
  const repoPath = `/repos/${ctx.repo.owner}/${ctx.repo.name}`.toLowerCase();
193178
193183
  const terms = params.terms.map((term) => `"${term}"`).join(" ");
193179
193184
  const response = await ctx.octokit.rest.search.issuesAndPullRequests({
@@ -24,6 +24,7 @@ export { countOutstandingPullfrogThreads } from "../utils/outstandingThreads.ts"
24
24
  export type { CreateProgressCommentTarget, ProgressComment, ProgressCommentType, } from "../utils/progressComment.ts";
25
25
  export { createLeapingProgressComment, deleteProgressCommentApi, getProgressComment, updateProgressComment, } from "../utils/progressComment.ts";
26
26
  export { PROVIDER_DASHBOARDS, type ProviderDashboard } from "../utils/providerDashboards.ts";
27
+ export { detectProviderError } from "../utils/providerErrors.ts";
27
28
  export type { RunStatusCheckConclusion, RunStatusCheckOctokit, } from "../utils/runStatusCheck.ts";
28
29
  export { APPROVAL_CHECK_NAME, createRunStatusCheck, finalizeRunStatusCheck, RUN_STATUS_CHECK_NAME, runStatusCheckNeedsFinalizing, } from "../utils/runStatusCheck.ts";
29
30
  export * from "../utils/subscriptionCredentials.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.89",
11
+ version: "0.1.90",
12
12
  cliContractVersion: "1",
13
13
  type: "module",
14
14
  bin: {
@@ -16821,6 +16821,146 @@ var PROVIDER_DASHBOARDS = {
16821
16821
  }
16822
16822
  };
16823
16823
 
16824
+ // utils/providerErrors.ts
16825
+ var PROVIDER_BILLING_EXHAUSTED_LABEL = "provider billing exhausted";
16826
+ var PROVIDER_NO_ENDPOINTS_LABEL = "provider no routable endpoints";
16827
+ var PROVIDER_USAGE_LIMIT_LABEL = "provider usage limit reached";
16828
+ var statusKey = `\\b(?:status[_ ]?code|http[_ ]?status|status)["']?\\s*[:=]\\s*["']?`;
16829
+ var ANTHROPIC_SPEND_CAP_PATTERN = /reached your specified API usage limits\.?(?:\s*You will regain access on ([^"\n]+?)\.)?/i;
16830
+ var PROVIDER_ERROR_PATTERNS = [
16831
+ // billing-payload patterns come BEFORE bare status-code patterns. providers
16832
+ // commonly return 401 / 429 for billing/quota exhaustion (OpenCode Zen
16833
+ // `CreditsError` / `FreeUsageLimitError`, Gemini `RESOURCE_EXHAUSTED` +
16834
+ // "spending cap", Anthropic "Insufficient balance" / "credit balance is
16835
+ // too low"). these are non-retryable and require user-billing action —
16836
+ // distinct from a transient auth error or rate-limit. status-code patterns
16837
+ // would otherwise win and surface "auth error (401)" / "rate limited (429)"
16838
+ // with no billing hint. see #778, #835.
16839
+ { regex: /\bCreditsError\b/, label: PROVIDER_BILLING_EXHAUSTED_LABEL },
16840
+ { regex: /\bFreeUsageLimitError\b/, label: PROVIDER_BILLING_EXHAUSTED_LABEL },
16841
+ // `balance` is DeepSeek's noun, `credits` is OpenRouter's for the SAME
16842
+ // terminal state — #1164's run 7 (`Insufficient credits. Add more using
16843
+ // …/settings/credits`) matched neither this nor `requires more credits`, so
16844
+ // the one run in that episode that was a genuinely empty wallet rendered with
16845
+ // no CTA at all while runs 1-6 rendered correctly.
16846
+ { regex: /Insufficient (?:balance|credits)/i, label: PROVIDER_BILLING_EXHAUSTED_LABEL },
16847
+ { regex: /credit balance is too low/i, label: PROVIDER_BILLING_EXHAUSTED_LABEL },
16848
+ // "spending cap" (Gemini) and "spending limit" (xAI, #1076) are the same
16849
+ // condition in two house styles — a configured ceiling was reached.
16850
+ { regex: /spending (?:cap|limit)/i, label: PROVIDER_BILLING_EXHAUSTED_LABEL },
16851
+ // xAI exhausts credits and the monthly ceiling in one 403 whose status code
16852
+ // sits nowhere near a `status:` key, so no status pattern below fires (#1076).
16853
+ { regex: /used all available credits/i, label: PROVIDER_BILLING_EXHAUSTED_LABEL },
16854
+ // Gemini's daily / free-tier cap. it tells the user to "check your plan and
16855
+ // billing details" in its own words but matches none of the wallet wordings
16856
+ // above, so it fell to the catch-all `quota` label — which is not the label
16857
+ // the actionable copy is keyed on, so the run rendered a blank comment for a
16858
+ // condition the provider had spelled out (#1114).
16859
+ {
16860
+ regex: /exceeded your current quota, please check your plan and billing/i,
16861
+ label: PROVIDER_BILLING_EXHAUSTED_LABEL
16862
+ },
16863
+ { regex: /generate_content_free_tier_requests/i, label: PROVIDER_BILLING_EXHAUSTED_LABEL },
16864
+ // Anthropic's Console spend cap. its noun is "API usage limits", which none of
16865
+ // the wallet-shaped patterns above reach, and its 400 sits nowhere near a
16866
+ // `status:` key either — so the whole message was dropped on the floor (#1208).
16867
+ { regex: ANTHROPIC_SPEND_CAP_PATTERN, label: PROVIDER_BILLING_EXHAUSTED_LABEL },
16868
+ // OpenCode Zen / Codex-subscription cap: `AI_APICallError: The usage limit has
16869
+ // been reached`. Its noun is "usage limit" with no "rate", no status code and
16870
+ // no wallet wording, so every pattern here missed it and `lastProviderError`
16871
+ // stayed unset — which suppresses nothing, so the run was reported as "The
16872
+ // model produced no output at all … usually transient; re-running often
16873
+ // succeeds" while the same log held nine of these. Measured on a live account
16874
+ // whose ChatGPT subscription was capped.
16875
+ { regex: /\bthe usage limit has been reached\b/i, label: PROVIDER_USAGE_LIMIT_LABEL },
16876
+ // the OpenRouter shapes `isRouterKeylimitExhaustedError` matches. on a Router
16877
+ // run those mean the PULLFROG wallet is empty and `renderRunError` returns a
16878
+ // `BillingError` before ever reaching this list; on a BYOK run the very same
16879
+ // wire text means the user's OWN OpenRouter wallet is empty, and this entry is
16880
+ // what gives them the right dashboard instead of ours. see #1135.
16881
+ {
16882
+ regex: /requires more credits|Key limit exceeded \(total limit\)/i,
16883
+ label: PROVIDER_BILLING_EXHAUSTED_LABEL
16884
+ },
16885
+ // OpenRouter has no endpoint it is allowed to route to: the customer's
16886
+ // data-policy / guardrail settings exclude every provider serving the model.
16887
+ // dies ~2s into the session with nothing billed, so it is neither a billing
16888
+ // nor an auth condition — and both of those patterns would otherwise be
16889
+ // reached first for a message that mentions neither. see #1164.
16890
+ {
16891
+ regex: /No endpoints available matching your (?:guardrail restrictions|data policy)/i,
16892
+ label: PROVIDER_NO_ENDPOINTS_LABEL
16893
+ },
16894
+ // auth patterns must come BEFORE rate-limit patterns. OpenRouter 401 error
16895
+ // payloads carry `x-ratelimit-*` response headers in the dump, and the
16896
+ // free-form rate-limit regex below would otherwise win on word-boundary
16897
+ // matches inside header names. canonical 401 messages: OpenRouter returns
16898
+ // `{"error":{"message":"User not found","code":401}}` for disabled or
16899
+ // invalid keys (https://openai.luzhipeng.com/docs/api/reference/errors-and-debugging).
16900
+ { regex: new RegExp(`${statusKey}401\\b`, "i"), label: "auth error (401)" },
16901
+ { regex: new RegExp(`${statusKey}403\\b`, "i"), label: "auth error (403)" },
16902
+ { regex: /\bUser not found\b/i, label: "auth error (invalid/disabled key)" },
16903
+ { regex: /\bInvalid authentication\b/i, label: "auth error (invalid credentials)" },
16904
+ { regex: /\bNo auth credentials found\b/i, label: "auth error (missing credentials)" },
16905
+ { regex: new RegExp(`${statusKey}429\\b`, "i"), label: "rate limited (429)" },
16906
+ { regex: new RegExp(`${statusKey}500\\b`, "i"), label: "provider 500 error" },
16907
+ { regex: new RegExp(`${statusKey}503\\b`, "i"), label: "provider unavailable (503)" },
16908
+ // matches `rate limit`, `rate limited`, `rate limits exceeded`,
16909
+ // `rate_limit_error`, `rate_limit_exceeded`. the leading `\b` + `[_ ]`
16910
+ // separator rejects `x-ratelimit-*` / `anthropic-ratelimit-*` response
16911
+ // headers (no separator between "rate" and "limit") which routinely
16912
+ // appear in dumped 401 / 4xx error JSON.
16913
+ { regex: /\brate[_ ]limit/i, label: "rate limited" },
16914
+ { regex: /\bRESOURCE_EXHAUSTED\b/, label: "quota exhausted" },
16915
+ // Google gRPC `INTERNAL` status. word-boundary anchors reject
16916
+ // `INTERNAL_SERVER_ERROR` (HTTP 500 message that may appear in unrelated
16917
+ // log lines) and identifiers like `INTERNALS`.
16918
+ { regex: /\bINTERNAL\b/, label: "provider internal error" },
16919
+ { regex: /\bUNAVAILABLE\b/, label: "provider unavailable" },
16920
+ // matches `quota`, `insufficient_quota`, `quota_exceeded`, `quotaExceeded`.
16921
+ // word-character lookarounds would reject `_quota` / `quotaX`; `quota` is
16922
+ // specific enough that a plain substring match is safe.
16923
+ { regex: /quota/i, label: "quota error" },
16924
+ // explicit zero-quota response, e.g. `{"limit": 0}`. the `\b` anchor
16925
+ // around `limit` rejects keys like `time_limit` or `field_limit`.
16926
+ { regex: /["']?\blimit\b["']?\s*:\s*0\b/, label: "zero quota" }
16927
+ ];
16928
+ var EXCERPT_MAX_BYTES = 600;
16929
+ var LINES_BEFORE = 1;
16930
+ var LINES_AFTER = 2;
16931
+ function findProviderErrorMatch(text) {
16932
+ for (const entry of PROVIDER_ERROR_PATTERNS) {
16933
+ const m = entry.regex.exec(text);
16934
+ if (!m) continue;
16935
+ return { label: entry.label, excerpt: extractExcerpt(text, m.index) };
16936
+ }
16937
+ return null;
16938
+ }
16939
+ function detectProviderError(text) {
16940
+ return findProviderErrorMatch(text)?.label ?? null;
16941
+ }
16942
+ function extractExcerpt(text, matchIndex) {
16943
+ const lineStart = text.lastIndexOf("\n", matchIndex - 1) + 1;
16944
+ const lineEndRaw = text.indexOf("\n", matchIndex);
16945
+ const lineEnd = lineEndRaw === -1 ? text.length : lineEndRaw;
16946
+ let start = lineStart;
16947
+ for (let i = 0; i < LINES_BEFORE && start > 0; i++) {
16948
+ const prev = text.lastIndexOf("\n", start - 2);
16949
+ start = prev < 0 ? 0 : prev + 1;
16950
+ }
16951
+ let end = lineEnd;
16952
+ for (let i = 0; i < LINES_AFTER && end < text.length; i++) {
16953
+ const next = text.indexOf("\n", end + 1);
16954
+ end = next < 0 ? text.length : next;
16955
+ }
16956
+ let excerpt = text.slice(start, end);
16957
+ if (excerpt.length > EXCERPT_MAX_BYTES) {
16958
+ excerpt = text.slice(lineStart, lineEnd);
16959
+ if (excerpt.length > EXCERPT_MAX_BYTES) excerpt = excerpt.slice(0, EXCERPT_MAX_BYTES);
16960
+ }
16961
+ return excerpt.trim();
16962
+ }
16963
+
16824
16964
  // utils/runStatusCheck.ts
16825
16965
  var RUN_STATUS_CHECK_NAME = "pullfrog";
16826
16966
  var GITHUB_ACTIONS_APP_SLUG = "github-actions";
@@ -17214,6 +17354,7 @@ export {
17214
17354
  decodeJwtExpMs,
17215
17355
  defaultAutoTier,
17216
17356
  deleteProgressCommentApi,
17357
+ detectProviderError,
17217
17358
  finalizeRunStatusCheck,
17218
17359
  getAutoSelectHintModel,
17219
17360
  getModelEffortLevels,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pullfrog",
3
- "version": "0.1.89",
3
+ "version": "0.1.90",
4
4
  "cliContractVersion": "1",
5
5
  "type": "module",
6
6
  "bin": {