pullfrog 0.1.66 → 0.1.67

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/README.md CHANGED
@@ -98,6 +98,7 @@ jobs:
98
98
  DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}
99
99
  OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
100
100
  MOONSHOT_API_KEY: ${{ secrets.MOONSHOT_API_KEY }}
101
+ KIMI_API_KEY: ${{ secrets.KIMI_API_KEY }}
101
102
 
102
103
  ```
103
104
 
package/dist/cli.mjs CHANGED
@@ -525,15 +525,15 @@ var require_file_command = __commonJS({
525
525
  }
526
526
  exports.issueFileCommand = issueFileCommand;
527
527
  function prepareKeyValueMessage(key, value2) {
528
- const delimiter2 = `ghadelimiter_${crypto5.randomUUID()}`;
528
+ const delimiter3 = `ghadelimiter_${crypto5.randomUUID()}`;
529
529
  const convertedValue = (0, utils_1.toCommandValue)(value2);
530
- if (key.includes(delimiter2)) {
531
- throw new Error(`Unexpected input: name should not contain the delimiter "${delimiter2}"`);
530
+ if (key.includes(delimiter3)) {
531
+ throw new Error(`Unexpected input: name should not contain the delimiter "${delimiter3}"`);
532
532
  }
533
- if (convertedValue.includes(delimiter2)) {
534
- throw new Error(`Unexpected input: value should not contain the delimiter "${delimiter2}"`);
533
+ if (convertedValue.includes(delimiter3)) {
534
+ throw new Error(`Unexpected input: value should not contain the delimiter "${delimiter3}"`);
535
535
  }
536
- return `${key}<<${delimiter2}${os2.EOL}${convertedValue}${os2.EOL}${delimiter2}`;
536
+ return `${key}<<${delimiter3}${os2.EOL}${convertedValue}${os2.EOL}${delimiter3}`;
537
537
  }
538
538
  exports.prepareKeyValueMessage = prepareKeyValueMessage;
539
539
  }
@@ -11973,10 +11973,10 @@ var require_headers = __commonJS({
11973
11973
  const lowercaseName = name.toLowerCase();
11974
11974
  const exists = this[kHeadersMap].get(lowercaseName);
11975
11975
  if (exists) {
11976
- const delimiter2 = lowercaseName === "cookie" ? "; " : ", ";
11976
+ const delimiter3 = lowercaseName === "cookie" ? "; " : ", ";
11977
11977
  this[kHeadersMap].set(lowercaseName, {
11978
11978
  name: exists.name,
11979
- value: `${exists.value}${delimiter2}${value2}`
11979
+ value: `${exists.value}${delimiter3}${value2}`
11980
11980
  });
11981
11981
  } else {
11982
11982
  this[kHeadersMap].set(lowercaseName, { name, value: value2 });
@@ -46981,10 +46981,10 @@ var require_headers2 = __commonJS({
46981
46981
  const lowercaseName = isLowerCase ? name : name.toLowerCase();
46982
46982
  const exists = this.headersMap.get(lowercaseName);
46983
46983
  if (exists) {
46984
- const delimiter2 = lowercaseName === "cookie" ? "; " : ", ";
46984
+ const delimiter3 = lowercaseName === "cookie" ? "; " : ", ";
46985
46985
  this.headersMap.set(lowercaseName, {
46986
46986
  name: exists.name,
46987
- value: `${exists.value}${delimiter2}${value2}`
46987
+ value: `${exists.value}${delimiter3}${value2}`
46988
46988
  });
46989
46989
  } else {
46990
46990
  this.headersMap.set(lowercaseName, { name, value: value2 });
@@ -59293,8 +59293,8 @@ var init_regexes = __esm({
59293
59293
  _emoji = `^[\\p{Extended_Pictographic}\\p{Emoji_Component}]+$`;
59294
59294
  ipv4 = /^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/;
59295
59295
  ipv6 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$/;
59296
- mac = (delimiter2) => {
59297
- const escapedDelim = escapeRegex(delimiter2 ?? ":");
59296
+ mac = (delimiter3) => {
59297
+ const escapedDelim = escapeRegex(delimiter3 ?? ":");
59298
59298
  return new RegExp(`^(?:[0-9A-F]{2}${escapedDelim}){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}${escapedDelim}){5}[0-9a-f]{2}$`);
59299
59299
  };
59300
59300
  cidrv4 = /^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/([0-9]|[1-2][0-9]|3[0-2])$/;
@@ -102436,10 +102436,10 @@ var require_turndown_cjs = __commonJS({
102436
102436
  if (!content) return "";
102437
102437
  content = content.replace(/\r?\n|\r/g, " ");
102438
102438
  var extraSpace = /^`|^ .*?[^ ].* $|`$/.test(content) ? " " : "";
102439
- var delimiter2 = "`";
102439
+ var delimiter3 = "`";
102440
102440
  var matches = content.match(/`+/gm) || [];
102441
- while (matches.indexOf(delimiter2) !== -1) delimiter2 = delimiter2 + "`";
102442
- return delimiter2 + extraSpace + content + extraSpace + delimiter2;
102441
+ while (matches.indexOf(delimiter3) !== -1) delimiter3 = delimiter3 + "`";
102442
+ return delimiter3 + extraSpace + content + extraSpace + delimiter3;
102443
102443
  }
102444
102444
  };
102445
102445
  rules.image = {
@@ -104189,8 +104189,8 @@ var require_common = __commonJS({
104189
104189
  }
104190
104190
  return debug4;
104191
104191
  }
104192
- function extend3(namespace, delimiter2) {
104193
- const newDebug = createDebug(this.namespace + (typeof delimiter2 === "undefined" ? ":" : delimiter2) + namespace);
104192
+ function extend3(namespace, delimiter3) {
104193
+ const newDebug = createDebug(this.namespace + (typeof delimiter3 === "undefined" ? ":" : delimiter3) + namespace);
104194
104194
  newDebug.log = this.log;
104195
104195
  return newDebug;
104196
104196
  }
@@ -108809,9 +108809,9 @@ var import_picocolors2 = __toESM(require_picocolors(), 1);
108809
108809
 
108810
108810
  // utils/codexAuth.ts
108811
108811
  import { spawn } from "node:child_process";
108812
- import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
108812
+ import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
108813
108813
  import { tmpdir } from "node:os";
108814
- import { join } from "node:path";
108814
+ import { delimiter, join } from "node:path";
108815
108815
 
108816
108816
  // utils/oauthShared.ts
108817
108817
  var OAuthInvalidGrantError = class extends Error {
@@ -108960,11 +108960,60 @@ async function mintCodexAuth(options) {
108960
108960
  }
108961
108961
  }
108962
108962
  var SIGTERM_GRACE_MS = 5e3;
108963
+ var DEVICE_AUTH_ARGS = ["login", "--device-auth"];
108964
+ var DEFAULT_PATHEXT = ".COM;.EXE;.BAT;.CMD";
108965
+ function envValue(env2, name) {
108966
+ if (env2[name] !== void 0) return env2[name];
108967
+ const key = Object.keys(env2).find((candidate) => candidate.toUpperCase() === name.toUpperCase());
108968
+ return key === void 0 ? void 0 : env2[key];
108969
+ }
108970
+ function commandProcessor(env2) {
108971
+ return envValue(env2, "ComSpec") ?? join(envValue(env2, "SystemRoot") ?? "C:\\Windows", "system32", "cmd.exe");
108972
+ }
108973
+ function resolveSpawnTarget(params) {
108974
+ if (process.platform !== "win32") {
108975
+ return { file: params.command, args: params.args, wrapped: false };
108976
+ }
108977
+ const extensions2 = (envValue(params.env, "PATHEXT") ?? DEFAULT_PATHEXT).split(";").filter(Boolean);
108978
+ for (const directory of (envValue(params.env, "PATH") ?? "").split(delimiter).filter(Boolean)) {
108979
+ for (const extension of extensions2) {
108980
+ const candidate = join(directory, `${params.command}${extension.toLowerCase()}`);
108981
+ if (!existsSync(candidate)) continue;
108982
+ if (!/\.(cmd|bat)$/i.test(candidate)) {
108983
+ return { file: candidate, args: params.args, wrapped: false };
108984
+ }
108985
+ const command = [`"${candidate}"`, ...params.args].join(" ");
108986
+ return {
108987
+ file: commandProcessor(params.env),
108988
+ args: ["/d", "/s", "/c", `"${command}"`],
108989
+ wrapped: true
108990
+ };
108991
+ }
108992
+ }
108993
+ return null;
108994
+ }
108995
+ function codexNotFoundMessage(env2) {
108996
+ const install = "install it with `npm i -g @openai/codex` or see https://developers.openai.com/codex/cli for other install options.";
108997
+ if (process.platform !== "win32") return `codex CLI not found on PATH. ${install}`;
108998
+ const extensions2 = (envValue(env2, "PATHEXT") ?? DEFAULT_PATHEXT).split(";").filter(Boolean).join(" ");
108999
+ return `codex CLI not found on PATH (searched every PATH entry for codex with ${extensions2}). a working \`codex --version\` in a shell does not prove Node can spawn it. ${install}`;
109000
+ }
108963
109001
  function runDeviceAuth(input2) {
108964
109002
  return new Promise((resolve3, reject) => {
108965
- const child = spawn("codex", ["login", "--device-auth"], {
109003
+ const target = resolveSpawnTarget({
109004
+ command: "codex",
109005
+ args: DEVICE_AUTH_ARGS,
109006
+ env: process.env
109007
+ });
109008
+ if (!target) {
109009
+ reject(new Error(codexNotFoundMessage(process.env)));
109010
+ return;
109011
+ }
109012
+ const child = spawn(target.file, target.args, {
108966
109013
  env: { ...process.env, CODEX_HOME: input2.codexHome },
108967
- stdio: ["ignore", input2.childStdio, input2.childStdio]
109014
+ stdio: ["ignore", input2.childStdio, input2.childStdio],
109015
+ windowsVerbatimArguments: target.wrapped,
109016
+ windowsHide: true
108968
109017
  });
108969
109018
  if (input2.childStdio === "pipe") {
108970
109019
  const onLine = input2.onChildLine ?? (() => {
@@ -108976,6 +109025,10 @@ function runDeviceAuth(input2) {
108976
109025
  let timedOut = false;
108977
109026
  const timeoutTimer = setTimeout(() => {
108978
109027
  timedOut = true;
109028
+ if (target.wrapped && child.pid) {
109029
+ spawn("taskkill", ["/pid", String(child.pid), "/t", "/f"], { stdio: "ignore" });
109030
+ return;
109031
+ }
108979
109032
  child.kill("SIGTERM");
108980
109033
  killTimer = setTimeout(() => child.kill("SIGKILL"), SIGTERM_GRACE_MS);
108981
109034
  }, input2.timeoutMs);
@@ -108983,7 +109036,7 @@ function runDeviceAuth(input2) {
108983
109036
  clearTimeout(timeoutTimer);
108984
109037
  if (killTimer) clearTimeout(killTimer);
108985
109038
  const errno = err;
108986
- const message = errno.code === "ENOENT" ? "codex CLI not found on PATH. install it with `npm i -g @openai/codex` or see https://developers.openai.com/codex/cli for other install options." : `failed to spawn codex: ${errno.message}`;
109039
+ const message = errno.code === "ENOENT" ? codexNotFoundMessage(process.env) : `failed to spawn codex: ${errno.message}`;
108987
109040
  reject(new Error(message));
108988
109041
  });
108989
109042
  child.on("close", (code, signal) => {
@@ -109797,7 +109850,7 @@ var import_arg2 = __toESM(require_arg(), 1);
109797
109850
  import { dirname as dirname6 } from "node:path";
109798
109851
 
109799
109852
  // main.ts
109800
- import { existsSync as existsSync10, readdirSync as readdirSync2 } from "node:fs";
109853
+ import { existsSync as existsSync11, readdirSync as readdirSync2 } from "node:fs";
109801
109854
  import { readFile as readFile5 } from "node:fs/promises";
109802
109855
  import { join as join28 } from "node:path";
109803
109856
 
@@ -110479,6 +110532,69 @@ var providers = {
110479
110532
  }
110480
110533
  }
110481
110534
  }),
110535
+ // the same models as `moonshotai` above, billed against a Kimi membership
110536
+ // instead of a Moonshot API balance — a separate endpoint, separate model
110537
+ // ids and a separate key, which is why it is a provider rather than a second
110538
+ // `envVars` entry. no provider injection is needed: models.dev carries
110539
+ // `kimi-for-coding` (api `https://api.kimi.com/coding/v1`, npm
110540
+ // `@ai-sdk/anthropic`) and opencode's env loop enables it from a bare
110541
+ // `KIMI_API_KEY`, verified against our pinned opencode-ai@1.18.5 — all four
110542
+ // ids list in `opencode models` and a run reaches Kimi's own auth check.
110543
+ //
110544
+ // NO `openRouterResolve` ON PURPOSE. OpenRouter cannot serve a Kimi
110545
+ // membership, so a Router path here would silently bill the wallet for
110546
+ // Moonshot pay-as-you-go under a slug picked for the opposite reason. The
110547
+ // absence is load-bearing: `isCardGatedModel` reads it as "not Router-
110548
+ // resolvable", `decideModelAccess` answers an explicit pick with the
110549
+ // `router` copy ("add your own provider key"), and run-context clamps a
110550
+ // keyless Router pick to the default subsidy model with `noRouterPath`.
110551
+ "kimi-for-coding": provider({
110552
+ // "Kimi Code" is the product name on kimi.com/code and in the console the
110553
+ // key is minted from. The provider KEY has to stay `kimi-for-coding` — it
110554
+ // is models.dev's id, so it is what `enabled_providers` and every
110555
+ // `resolve` prefix must spell.
110556
+ displayName: "Kimi Code",
110557
+ envVars: ["KIMI_API_KEY"],
110558
+ models: {
110559
+ // the tier each model needs is in `description` because Kimi answers an
110560
+ // out-of-tier request with 401 — indistinguishable from a bad key by
110561
+ // status alone, and only `isKimiPlanTierError` tells them apart after the
110562
+ // fact. saying it in the picker is the half that stops the run happening.
110563
+ "kimi-k3": {
110564
+ displayName: "Kimi K3",
110565
+ description: "1M context, but only on Allegretto and above \xB7 Moderato caps it at 256K",
110566
+ resolve: "kimi-for-coding/k3",
110567
+ effort: ["low", "high", "max"],
110568
+ subagentModel: "kimi-k2"
110569
+ },
110570
+ // `preferred` sits on the 256K variant rather than the 1M flagship, which
110571
+ // inverts the usual "top tier wins" rule for a reason specific to Kimi:
110572
+ // below Allegretto the two are the SAME 256K ceiling and `k3` charges
110573
+ // roughly double the quota for it, so on the median tier the flagship is
110574
+ // strictly dominated rather than merely pricier. This flag seeds the
110575
+ // onboarding pick (`getRecommendedSlug`) and wins key-only auto-select
110576
+ // (`autoSelectModel`), which are exactly the two moments nobody has
110577
+ // chosen yet.
110578
+ "kimi-k3-256k": {
110579
+ displayName: "Kimi K3 256K",
110580
+ description: "Same model at 256K for ~half the quota \xB7 Moderato or above",
110581
+ resolve: "kimi-for-coding/k3-256k",
110582
+ effort: ["low", "high", "max"],
110583
+ preferred: true,
110584
+ subagentModel: "kimi-k2"
110585
+ },
110586
+ "kimi-k2": {
110587
+ displayName: "Kimi K2",
110588
+ description: "Included in every Kimi membership tier",
110589
+ resolve: "kimi-for-coding/kimi-for-coding"
110590
+ },
110591
+ "kimi-k2-highspeed": {
110592
+ displayName: "Kimi K2 HighSpeed",
110593
+ description: "~6x output speed for ~3x quota \xB7 needs Allegretto or above",
110594
+ resolve: "kimi-for-coding/kimi-for-coding-highspeed"
110595
+ }
110596
+ }
110597
+ }),
110482
110598
  opencode: provider({
110483
110599
  displayName: "OpenCode Zen",
110484
110600
  envVars: ["OPENCODE_API_KEY"],
@@ -111031,6 +111147,80 @@ var providers = {
111031
111147
  openRouterResolve: "openrouter/minimax/minimax-m3"
111032
111148
  }
111033
111149
  }
111150
+ }),
111151
+ vercel: provider({
111152
+ // Vercel AI Gateway — one key serves every model it fronts. model IDs use
111153
+ // the models.dev `vercel` catalog's own naming (dotted versions, e.g.
111154
+ // `anthropic/claude-opus-5`, `zai/glm-5.3`), and effort ladders mirror that
111155
+ // catalog's entries, not the upstream vendor's. deliberately no
111156
+ // `openRouterResolve`: a gateway pick is BYOK-only — silently rerouting it
111157
+ // over the Router proxy would bill the wallet for traffic the user pointed
111158
+ // at their own gateway.
111159
+ displayName: "Vercel AI Gateway",
111160
+ envVars: ["AI_GATEWAY_API_KEY"],
111161
+ models: {
111162
+ "claude-opus": {
111163
+ displayName: "Claude Opus",
111164
+ resolve: "vercel/anthropic/claude-opus-5",
111165
+ effort: ["low", "medium", "high", "xhigh", "max"],
111166
+ preferred: true,
111167
+ subagentModel: "claude-sonnet"
111168
+ },
111169
+ "claude-sonnet": {
111170
+ displayName: "Claude Sonnet",
111171
+ resolve: "vercel/anthropic/claude-sonnet-5",
111172
+ effort: ["low", "medium", "high", "xhigh"]
111173
+ },
111174
+ "claude-haiku": {
111175
+ displayName: "Claude Haiku",
111176
+ resolve: "vercel/anthropic/claude-haiku-4.5"
111177
+ },
111178
+ "gpt-sol": {
111179
+ displayName: "GPT Sol",
111180
+ resolve: "vercel/openai/gpt-5.6-sol",
111181
+ effort: ["none", "low", "medium", "high", "xhigh", "max"],
111182
+ subagentModel: "gpt-terra"
111183
+ },
111184
+ "gpt-terra": {
111185
+ displayName: "GPT Terra",
111186
+ resolve: "vercel/openai/gpt-5.6-terra",
111187
+ effort: ["none", "low", "medium", "high", "xhigh", "max"]
111188
+ },
111189
+ "gpt-luna": {
111190
+ displayName: "GPT Luna",
111191
+ resolve: "vercel/openai/gpt-5.6-luna",
111192
+ effort: ["none", "low", "medium", "high", "xhigh", "max"]
111193
+ },
111194
+ "gemini-pro": {
111195
+ displayName: "Gemini Pro",
111196
+ resolve: "vercel/google/gemini-3.1-pro-preview",
111197
+ effort: ["low", "medium", "high"]
111198
+ },
111199
+ "gemini-flash": {
111200
+ displayName: "Gemini Flash",
111201
+ resolve: "vercel/google/gemini-3.6-flash",
111202
+ effort: ["minimal", "low", "medium", "high"]
111203
+ },
111204
+ "deepseek-pro": {
111205
+ displayName: "DeepSeek Pro",
111206
+ resolve: "vercel/deepseek/deepseek-v4-pro-0813",
111207
+ effort: ["high", "xhigh"]
111208
+ },
111209
+ "deepseek-flash": {
111210
+ displayName: "DeepSeek Flash",
111211
+ resolve: "vercel/deepseek/deepseek-v4-flash",
111212
+ effort: ["high", "xhigh"]
111213
+ },
111214
+ glm: {
111215
+ displayName: "GLM",
111216
+ resolve: "vercel/zai/glm-5.3",
111217
+ effort: ["low", "high", "max"]
111218
+ },
111219
+ "kimi-k3": {
111220
+ displayName: "Kimi K3",
111221
+ resolve: "vercel/moonshotai/kimi-k3"
111222
+ }
111223
+ }
111034
111224
  })
111035
111225
  };
111036
111226
  function parseModel(slug2) {
@@ -111458,9 +111648,9 @@ function logTokenTable(t2) {
111458
111648
  }
111459
111649
 
111460
111650
  // utils/globals.ts
111461
- import { existsSync } from "node:fs";
111651
+ import { existsSync as existsSync2 } from "node:fs";
111462
111652
  var isGitHubActions = !!process.env.GITHUB_ACTIONS;
111463
- var isInsideDocker = existsSync("/.dockerenv");
111653
+ var isInsideDocker = existsSync2("/.dockerenv");
111464
111654
 
111465
111655
  // utils/log.ts
111466
111656
  var logContext = new AsyncLocalStorage();
@@ -111688,7 +111878,7 @@ function formatUsageSummary(entries) {
111688
111878
 
111689
111879
  // utils/install.ts
111690
111880
  import { spawnSync } from "node:child_process";
111691
- import { chmodSync, createWriteStream, existsSync as existsSync2, mkdirSync } from "node:fs";
111881
+ import { chmodSync, createWriteStream, existsSync as existsSync3, mkdirSync } from "node:fs";
111692
111882
  import { join as join2 } from "node:path";
111693
111883
  import { pipeline } from "node:stream/promises";
111694
111884
  async function installFromNpmTarball(params) {
@@ -111697,7 +111887,7 @@ async function installFromNpmTarball(params) {
111697
111887
  const packageRoot = join2(tempDir, `npm-${params.packageName.replace(/[^a-z0-9]+/gi, "-")}`);
111698
111888
  const extractedDir = join2(packageRoot, "package");
111699
111889
  const cliPath = join2(extractedDir, params.executablePath);
111700
- if (existsSync2(cliPath)) {
111890
+ if (existsSync3(cliPath)) {
111701
111891
  log2.debug(`\xBB using cached binary at ${cliPath}`);
111702
111892
  return cliPath;
111703
111893
  }
@@ -111771,7 +111961,7 @@ async function installFromNpmTarball(params) {
111771
111961
  }
111772
111962
  log2.debug(`\xBB dependencies installed`);
111773
111963
  }
111774
- if (!existsSync2(cliPath)) {
111964
+ if (!existsSync3(cliPath)) {
111775
111965
  throw new Error(`Executable not found in extracted package at ${cliPath}`);
111776
111966
  }
111777
111967
  chmodSync(cliPath, 493);
@@ -111978,7 +112168,7 @@ function resolveRunEffort(ctx) {
111978
112168
 
111979
112169
  // utils/skills.ts
111980
112170
  import { spawnSync as spawnSync2 } from "node:child_process";
111981
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
112171
+ import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
111982
112172
  import { tmpdir as tmpdir2 } from "node:os";
111983
112173
  import { dirname, join as join3 } from "node:path";
111984
112174
  import { fileURLToPath } from "node:url";
@@ -111989,7 +112179,7 @@ var import_semver = __toESM(require_semver2(), 1);
111989
112179
  // package.json
111990
112180
  var package_default = {
111991
112181
  name: "pullfrog",
111992
- version: "0.1.66",
112182
+ version: "0.1.67",
111993
112183
  type: "module",
111994
112184
  bin: {
111995
112185
  pullfrog: "dist/cli.mjs",
@@ -112118,7 +112308,7 @@ function resolveSkillPath(name) {
112118
112308
  join3(here, "skills", name, "SKILL.md")
112119
112309
  ];
112120
112310
  for (const candidate of candidates) {
112121
- if (existsSync3(candidate)) return candidate;
112311
+ if (existsSync4(candidate)) return candidate;
112122
112312
  }
112123
112313
  throw new Error(`bundled skill not found: ${name} (looked in ${candidates.join(", ")})`);
112124
112314
  }
@@ -114159,13 +114349,13 @@ var claude = agent({
114159
114349
 
114160
114350
  // agents/codex.ts
114161
114351
  var core2 = __toESM(require_core(), 1);
114162
- import { existsSync as existsSync5, mkdirSync as mkdirSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync6 } from "node:fs";
114352
+ import { existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync6 } from "node:fs";
114163
114353
  import { join as join7 } from "node:path";
114164
114354
  import { performance as performance6 } from "node:perf_hooks";
114165
114355
 
114166
114356
  // utils/codexHome.ts
114167
114357
  import { execFileSync as execFileSync4 } from "node:child_process";
114168
- import { existsSync as existsSync4, mkdirSync as mkdirSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync5 } from "node:fs";
114358
+ import { existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync5 } from "node:fs";
114169
114359
  import { homedir as homedir2, userInfo } from "node:os";
114170
114360
  import { join as join6 } from "node:path";
114171
114361
  var CODEX_AUTH_ENV = "CODEX_AUTH_JSON";
@@ -114214,7 +114404,7 @@ function installCodexAuth() {
114214
114404
  }
114215
114405
  function writeOpenCodeAuthEntry(params) {
114216
114406
  let existing = {};
114217
- if (existsSync4(params.authPath)) {
114407
+ if (existsSync5(params.authPath)) {
114218
114408
  try {
114219
114409
  const parsed2 = JSON.parse(readFileSync3(params.authPath, "utf8"));
114220
114410
  if (parsed2 && typeof parsed2 === "object") existing = parsed2;
@@ -114531,7 +114721,7 @@ var APPROVAL_MODE_KEY = "default_tools_approval_mode";
114531
114721
  var APPROVE_TOOLS = `${APPROVAL_MODE_KEY} = "approve"`;
114532
114722
  function repoMcpApprovals(repoDir) {
114533
114723
  const path4 = join7(repoDir, ".codex", "config.toml");
114534
- if (!existsSync5(path4)) return [];
114724
+ if (!existsSync6(path4)) return [];
114535
114725
  const out = [];
114536
114726
  let section = [];
114537
114727
  let isServerTable = false;
@@ -178940,7 +179130,7 @@ var pbkdf2Async = promisify(pbkdf2);
178940
179130
 
178941
179131
  // utils/browser.ts
178942
179132
  import { execFileSync as execFileSync5, spawnSync as spawnSync4 } from "node:child_process";
178943
- import { existsSync as existsSync6 } from "node:fs";
179133
+ import { existsSync as existsSync7 } from "node:fs";
178944
179134
  import { dirname as dirname2 } from "node:path";
178945
179135
  var CHROME_PATHS = ["/usr/bin/google-chrome-stable"];
178946
179136
  var systemChromePath;
@@ -178949,7 +179139,7 @@ function findSystemChromePath() {
178949
179139
  return systemChromePath || void 0;
178950
179140
  }
178951
179141
  for (const p of CHROME_PATHS) {
178952
- if (existsSync6(p)) {
179142
+ if (existsSync7(p)) {
178953
179143
  systemChromePath = p;
178954
179144
  log2.info(`found system chrome: ${p}`);
178955
179145
  return p;
@@ -182620,7 +182810,7 @@ var describeExpressionOptions = {
182620
182810
  };
182621
182811
  var expressBranches = (expressions) => describeBranches(expressions, describeExpressionOptions);
182622
182812
  var describeBranches = (descriptions, opts) => {
182623
- const delimiter2 = opts?.delimiter ?? ", ";
182813
+ const delimiter3 = opts?.delimiter ?? ", ";
182624
182814
  const finalDelimiter = opts?.finalDelimiter ?? " or ";
182625
182815
  if (descriptions.length === 0)
182626
182816
  return "never";
@@ -182631,7 +182821,7 @@ var describeBranches = (descriptions, opts) => {
182631
182821
  const seen = {};
182632
182822
  const unique = descriptions.filter((s) => seen[s] ? false : seen[s] = true);
182633
182823
  const last = unique.pop();
182634
- return `${unique.join(delimiter2)}${unique.length ? finalDelimiter : ""}${last}`;
182824
+ return `${unique.join(delimiter3)}${unique.length ? finalDelimiter : ""}${last}`;
182635
182825
  };
182636
182826
  var intersectBranches = (l2, r2, ctx) => {
182637
182827
  const batchesByR = r2.map(() => []);
@@ -193349,7 +193539,7 @@ function isValidUnquotedKey(key) {
193349
193539
  function isIdentifierSegment(key) {
193350
193540
  return /^[A-Z_]\w*$/i.test(key);
193351
193541
  }
193352
- function isSafeUnquoted(value2, delimiter2 = DEFAULT_DELIMITER) {
193542
+ function isSafeUnquoted(value2, delimiter3 = DEFAULT_DELIMITER) {
193353
193543
  if (!value2) return false;
193354
193544
  if (value2 !== value2.trim()) return false;
193355
193545
  if (isBooleanOrNullLiteral(value2) || isNumericLike(value2)) return false;
@@ -193357,7 +193547,7 @@ function isSafeUnquoted(value2, delimiter2 = DEFAULT_DELIMITER) {
193357
193547
  if (value2.includes('"') || value2.includes("\\")) return false;
193358
193548
  if (/[[\]{}]/.test(value2)) return false;
193359
193549
  if (/[\n\r\t]/.test(value2)) return false;
193360
- if (value2.includes(delimiter2)) return false;
193550
+ if (value2.includes(delimiter3)) return false;
193361
193551
  if (value2.startsWith(LIST_ITEM_MARKER)) return false;
193362
193552
  return true;
193363
193553
  }
@@ -193408,33 +193598,33 @@ function collectSingleKeyChain(startKey, startValue, maxDepth) {
193408
193598
  function buildFoldedKey(segments) {
193409
193599
  return segments.join(DOT);
193410
193600
  }
193411
- function encodePrimitive(value2, delimiter2) {
193601
+ function encodePrimitive(value2, delimiter3) {
193412
193602
  if (value2 === null) return NULL_LITERAL;
193413
193603
  if (typeof value2 === "boolean") return String(value2);
193414
193604
  if (typeof value2 === "number") return String(value2);
193415
- return encodeStringLiteral(value2, delimiter2);
193605
+ return encodeStringLiteral(value2, delimiter3);
193416
193606
  }
193417
- function encodeStringLiteral(value2, delimiter2 = DEFAULT_DELIMITER) {
193418
- if (isSafeUnquoted(value2, delimiter2)) return value2;
193607
+ function encodeStringLiteral(value2, delimiter3 = DEFAULT_DELIMITER) {
193608
+ if (isSafeUnquoted(value2, delimiter3)) return value2;
193419
193609
  return `${DOUBLE_QUOTE}${escapeString(value2)}${DOUBLE_QUOTE}`;
193420
193610
  }
193421
193611
  function encodeKey(key) {
193422
193612
  if (isValidUnquotedKey(key)) return key;
193423
193613
  return `${DOUBLE_QUOTE}${escapeString(key)}${DOUBLE_QUOTE}`;
193424
193614
  }
193425
- function encodeAndJoinPrimitives(values, delimiter2 = DEFAULT_DELIMITER) {
193426
- return values.map((v) => encodePrimitive(v, delimiter2)).join(delimiter2);
193615
+ function encodeAndJoinPrimitives(values, delimiter3 = DEFAULT_DELIMITER) {
193616
+ return values.map((v) => encodePrimitive(v, delimiter3)).join(delimiter3);
193427
193617
  }
193428
193618
  function formatHeader(length, options) {
193429
193619
  const key = options?.key;
193430
193620
  const fields = options?.fields;
193431
- const delimiter2 = options?.delimiter ?? COMMA2;
193621
+ const delimiter3 = options?.delimiter ?? COMMA2;
193432
193622
  let header = "";
193433
193623
  if (key) header += encodeKey(key);
193434
- header += `[${length}${delimiter2 !== DEFAULT_DELIMITER ? delimiter2 : ""}]`;
193624
+ header += `[${length}${delimiter3 !== DEFAULT_DELIMITER ? delimiter3 : ""}]`;
193435
193625
  if (fields) {
193436
193626
  const quotedFields = fields.map((f) => encodeKey(f));
193437
- header += `{${quotedFields.join(delimiter2)}}`;
193627
+ header += `{${quotedFields.join(delimiter3)}}`;
193438
193628
  }
193439
193629
  header += ":";
193440
193630
  return header;
@@ -193527,12 +193717,12 @@ function* encodeArrayOfArraysAsListItemsLines(prefix, values, depth, options) {
193527
193717
  yield indentedListItem(depth + 1, arrayLine, options.indent);
193528
193718
  }
193529
193719
  }
193530
- function encodeInlineArrayLine(values, delimiter2, prefix) {
193720
+ function encodeInlineArrayLine(values, delimiter3, prefix) {
193531
193721
  const header = formatHeader(values.length, {
193532
193722
  key: prefix,
193533
- delimiter: delimiter2
193723
+ delimiter: delimiter3
193534
193724
  });
193535
- const joinedValue = encodeAndJoinPrimitives(values, delimiter2);
193725
+ const joinedValue = encodeAndJoinPrimitives(values, delimiter3);
193536
193726
  if (values.length === 0) return header;
193537
193727
  return `${header} ${joinedValue}`;
193538
193728
  }
@@ -196951,14 +197141,14 @@ function ReopenCurrentTool(ctx) {
196951
197141
  import { performance as performance9 } from "node:perf_hooks";
196952
197142
 
196953
197143
  // prep/installNodeDependencies.ts
196954
- import { existsSync as existsSync8 } from "node:fs";
197144
+ import { existsSync as existsSync9 } from "node:fs";
196955
197145
  import { join as join19 } from "node:path";
196956
197146
 
196957
197147
  // utils/packageManager.ts
196958
197148
  var import_semver2 = __toESM(require_semver2(), 1);
196959
- import { existsSync as existsSync7 } from "node:fs";
197149
+ import { existsSync as existsSync8 } from "node:fs";
196960
197150
  import { mkdir, readFile as readFile2 } from "node:fs/promises";
196961
- import { delimiter, join as join18 } from "node:path";
197151
+ import { delimiter as delimiter2, join as join18 } from "node:path";
196962
197152
  var SUPPORTED_NAMES = ["npm", "pnpm", "yarn", "bun"];
196963
197153
  var COREPACK_MANAGED = ["pnpm", "yarn"];
196964
197154
  function isSupported(name) {
@@ -196997,7 +197187,7 @@ function parseDevEnginesField(field) {
196997
197187
  }
196998
197188
  async function resolvePackageManagerSpec(cwd) {
196999
197189
  const pkgPath = join18(cwd, "package.json");
197000
- if (!existsSync7(pkgPath)) return null;
197190
+ if (!existsSync8(pkgPath)) return null;
197001
197191
  let pkg;
197002
197192
  try {
197003
197193
  pkg = JSON.parse(await readFile2(pkgPath, "utf-8"));
@@ -197085,7 +197275,7 @@ async function ensurePackageManager(params) {
197085
197275
  );
197086
197276
  return false;
197087
197277
  }
197088
- process.env.PATH = `${params.binDir}${delimiter}${process.env.PATH ?? ""}`;
197278
+ process.env.PATH = `${params.binDir}${delimiter2}${process.env.PATH ?? ""}`;
197089
197279
  const prepare = await runCorepack(["prepare", `${spec.name}@${spec.version}`, "--activate"]);
197090
197280
  if (prepare.exitCode !== 0) {
197091
197281
  log2.warning(
@@ -197152,7 +197342,7 @@ var installNodeDependencies = {
197152
197342
  name: "installNodeDependencies",
197153
197343
  shouldRun: () => {
197154
197344
  const packageJsonPath = join19(process.cwd(), "package.json");
197155
- return existsSync8(packageJsonPath);
197345
+ return existsSync9(packageJsonPath);
197156
197346
  },
197157
197347
  run: async (options) => {
197158
197348
  const declared = await resolvePackageManagerSpec(process.cwd());
@@ -197246,14 +197436,14 @@ ${errorMessage}`]
197246
197436
  };
197247
197437
 
197248
197438
  // prep/installPythonDependencies.ts
197249
- import { existsSync as existsSync9, readFileSync as readFileSync6 } from "node:fs";
197439
+ import { existsSync as existsSync10, readFileSync as readFileSync6 } from "node:fs";
197250
197440
  import { join as join20 } from "node:path";
197251
197441
  function declaresBuildSystem(path4) {
197252
197442
  return /^\s*\[\s*build-system\s*\]/m.test(readFileSync6(path4, "utf8"));
197253
197443
  }
197254
197444
  function configApplies(config3, cwd) {
197255
197445
  const path4 = join20(cwd, config3.file);
197256
- return existsSync9(path4) && (config3.applies?.(path4) ?? true);
197446
+ return existsSync10(path4) && (config3.applies?.(path4) ?? true);
197257
197447
  }
197258
197448
  var PYTHON_CONFIGS = [
197259
197449
  {
@@ -200121,7 +200311,7 @@ function validateAgentApiKey(params) {
200121
200311
  }
200122
200312
  function isApiKeyAuthError(text) {
200123
200313
  if (!text) return false;
200124
- return text.includes(MISSING_KEY_MARKER) || /Invalid API key/i.test(text) || /Incorrect API key provided/i.test(text) || /\bUser not found\b/i.test(text) || /\bInvalid authentication\b/i.test(text) || /authentication_error/i.test(text) || /Invalid bearer token/i.test(text) || /api_error_status\s*=\s*401/i.test(text) || /API Error:\s*401/i.test(text) || /Failed to authenticate\. API Error:/i.test(text) || /Your api key:.*is invalid/i.test(text) || isMalformedKeyError(text) || isClaudeSubscriptionDisabledError(text) || isClaudeSessionLimitError(text) || isOAuthCredentialExpiredError(text);
200314
+ return text.includes(MISSING_KEY_MARKER) || /Invalid API key/i.test(text) || /Incorrect API key provided/i.test(text) || /\bUser not found\b/i.test(text) || /\bInvalid authentication\b/i.test(text) || /authentication_error/i.test(text) || /Invalid bearer token/i.test(text) || /api_error_status\s*=\s*401/i.test(text) || /API Error:\s*401/i.test(text) || /Failed to authenticate\. API Error:/i.test(text) || /Your api key:.*is invalid/i.test(text) || isMalformedKeyError(text) || isClaudeSubscriptionDisabledError(text) || isKimiPlanTierError(text) || isClaudeSessionLimitError(text) || isOAuthCredentialExpiredError(text);
200125
200315
  }
200126
200316
  function isOAuthCredentialExpiredError(text) {
200127
200317
  return (
@@ -200142,6 +200332,9 @@ function isMalformedKeyError(text) {
200142
200332
  function isClaudeSubscriptionDisabledError(text) {
200143
200333
  return /disabled Claude subscription access/i.test(text);
200144
200334
  }
200335
+ function isKimiPlanTierError(text) {
200336
+ return /Your current (?:subscription does not have access to|plan supports only)\b/i.test(text);
200337
+ }
200145
200338
  var CLAUDE_SESSION_LIMIT_PATTERN = /hit your (?:session|weekly|five-hour|Opus|Sonnet|Haiku)\s+limit(?:\s*·\s*resets\s+([^"\n]+))?/i;
200146
200339
  function isClaudeSessionLimitError(text) {
200147
200340
  return CLAUDE_SESSION_LIMIT_PATTERN.test(text);
@@ -200215,6 +200408,13 @@ function formatApiKeyErrorSummary(params) {
200215
200408
  `[Add repo secret \u2192](${githubSecretsUrl}) \xB7 [Model settings \u2192](${settingsUrl}) \xB7 [Setup docs \u2192](https://docs.pullfrog.com/keys) \xB7 [Ask in Discord \u2192](https://discord.gg/8y96raFg8e)`
200216
200409
  ].join("\n");
200217
200410
  }
200411
+ if (isKimiPlanTierError(params.raw)) {
200412
+ return [
200413
+ "**Your Kimi membership doesn't include the model this run asked for.** Kimi K3 needs a Moderato plan or above, and K3's 1M context and Kimi K2 HighSpeed need Allegretto or above. Upgrade the membership, or switch this repo to **Kimi K2**, which every tier includes.",
200414
+ "",
200415
+ `[Kimi plans \u2192](https://www.kimi.com/membership/pricing) \xB7 [Model settings \u2192](${settingsUrl}) \xB7 [Setup docs \u2192](https://docs.pullfrog.com/kimi-code) \xB7 [Ask in Discord \u2192](https://discord.gg/8y96raFg8e)`
200416
+ ].join("\n");
200417
+ }
200218
200418
  if (isOAuthCredentialExpiredError(params.raw)) {
200219
200419
  return [
200220
200420
  `**Your provider OAuth credential has expired or been revoked.** Re-authenticate the provider connection (e.g. \`pullfrog auth claude\` / \`pullfrog auth codex\` / \`pullfrog auth grok\`), then re-trigger the run.`,
@@ -203860,7 +204060,7 @@ ${instructions.user}` : null,
203860
204060
  });
203861
204061
  if (agentId === "opencode") {
203862
204062
  const pluginDir = join28(process.cwd(), ".opencode", "plugin");
203863
- const hasPlugins = existsSync10(pluginDir) && readdirSync2(pluginDir).some((f) => /\.[jt]sx?$/.test(f));
204063
+ const hasPlugins = existsSync11(pluginDir) && readdirSync2(pluginDir).some((f) => /\.[jt]sx?$/.test(f));
203864
204064
  if (hasPlugins && toolState.dependencyInstallation?.promise) {
203865
204065
  log2.info(
203866
204066
  "\xBB .opencode/plugin/ detected \u2014 awaiting dependency installation before agent start"
@@ -204597,7 +204797,7 @@ function resolveTarget(repo) {
204597
204797
  return { owner: match3[1], repo: match3[2] };
204598
204798
  }
204599
204799
  function cliVersion() {
204600
- const parts = /^(\d+)\.(\d+)\.(\d+)$/.exec("0.1.66");
204800
+ const parts = /^(\d+)\.(\d+)\.(\d+)$/.exec("0.1.67");
204601
204801
  if (!parts) return "0.0.0";
204602
204802
  return `${Number(parts[1])}.${Number(parts[2])}.${Number(parts[3])}`;
204603
204803
  }
@@ -204764,7 +204964,7 @@ async function runCli5(input2) {
204764
204964
  }
204765
204965
 
204766
204966
  // cli.ts
204767
- var VERSION10 = "0.1.66";
204967
+ var VERSION10 = "0.1.67";
204768
204968
  var bin = basename2(process.argv[1] || "");
204769
204969
  var PROG = bin === "pf" || bin === "pullfrog" ? bin : "pullfrog";
204770
204970
  var rawArgs = process.argv.slice(2);
package/dist/index.js CHANGED
@@ -108042,6 +108042,69 @@ var providers = {
108042
108042
  }
108043
108043
  }
108044
108044
  }),
108045
+ // the same models as `moonshotai` above, billed against a Kimi membership
108046
+ // instead of a Moonshot API balance — a separate endpoint, separate model
108047
+ // ids and a separate key, which is why it is a provider rather than a second
108048
+ // `envVars` entry. no provider injection is needed: models.dev carries
108049
+ // `kimi-for-coding` (api `https://api.kimi.com/coding/v1`, npm
108050
+ // `@ai-sdk/anthropic`) and opencode's env loop enables it from a bare
108051
+ // `KIMI_API_KEY`, verified against our pinned opencode-ai@1.18.5 — all four
108052
+ // ids list in `opencode models` and a run reaches Kimi's own auth check.
108053
+ //
108054
+ // NO `openRouterResolve` ON PURPOSE. OpenRouter cannot serve a Kimi
108055
+ // membership, so a Router path here would silently bill the wallet for
108056
+ // Moonshot pay-as-you-go under a slug picked for the opposite reason. The
108057
+ // absence is load-bearing: `isCardGatedModel` reads it as "not Router-
108058
+ // resolvable", `decideModelAccess` answers an explicit pick with the
108059
+ // `router` copy ("add your own provider key"), and run-context clamps a
108060
+ // keyless Router pick to the default subsidy model with `noRouterPath`.
108061
+ "kimi-for-coding": provider({
108062
+ // "Kimi Code" is the product name on kimi.com/code and in the console the
108063
+ // key is minted from. The provider KEY has to stay `kimi-for-coding` — it
108064
+ // is models.dev's id, so it is what `enabled_providers` and every
108065
+ // `resolve` prefix must spell.
108066
+ displayName: "Kimi Code",
108067
+ envVars: ["KIMI_API_KEY"],
108068
+ models: {
108069
+ // the tier each model needs is in `description` because Kimi answers an
108070
+ // out-of-tier request with 401 — indistinguishable from a bad key by
108071
+ // status alone, and only `isKimiPlanTierError` tells them apart after the
108072
+ // fact. saying it in the picker is the half that stops the run happening.
108073
+ "kimi-k3": {
108074
+ displayName: "Kimi K3",
108075
+ description: "1M context, but only on Allegretto and above \xB7 Moderato caps it at 256K",
108076
+ resolve: "kimi-for-coding/k3",
108077
+ effort: ["low", "high", "max"],
108078
+ subagentModel: "kimi-k2"
108079
+ },
108080
+ // `preferred` sits on the 256K variant rather than the 1M flagship, which
108081
+ // inverts the usual "top tier wins" rule for a reason specific to Kimi:
108082
+ // below Allegretto the two are the SAME 256K ceiling and `k3` charges
108083
+ // roughly double the quota for it, so on the median tier the flagship is
108084
+ // strictly dominated rather than merely pricier. This flag seeds the
108085
+ // onboarding pick (`getRecommendedSlug`) and wins key-only auto-select
108086
+ // (`autoSelectModel`), which are exactly the two moments nobody has
108087
+ // chosen yet.
108088
+ "kimi-k3-256k": {
108089
+ displayName: "Kimi K3 256K",
108090
+ description: "Same model at 256K for ~half the quota \xB7 Moderato or above",
108091
+ resolve: "kimi-for-coding/k3-256k",
108092
+ effort: ["low", "high", "max"],
108093
+ preferred: true,
108094
+ subagentModel: "kimi-k2"
108095
+ },
108096
+ "kimi-k2": {
108097
+ displayName: "Kimi K2",
108098
+ description: "Included in every Kimi membership tier",
108099
+ resolve: "kimi-for-coding/kimi-for-coding"
108100
+ },
108101
+ "kimi-k2-highspeed": {
108102
+ displayName: "Kimi K2 HighSpeed",
108103
+ description: "~6x output speed for ~3x quota \xB7 needs Allegretto or above",
108104
+ resolve: "kimi-for-coding/kimi-for-coding-highspeed"
108105
+ }
108106
+ }
108107
+ }),
108045
108108
  opencode: provider({
108046
108109
  displayName: "OpenCode Zen",
108047
108110
  envVars: ["OPENCODE_API_KEY"],
@@ -108594,6 +108657,80 @@ var providers = {
108594
108657
  openRouterResolve: "openrouter/minimax/minimax-m3"
108595
108658
  }
108596
108659
  }
108660
+ }),
108661
+ vercel: provider({
108662
+ // Vercel AI Gateway — one key serves every model it fronts. model IDs use
108663
+ // the models.dev `vercel` catalog's own naming (dotted versions, e.g.
108664
+ // `anthropic/claude-opus-5`, `zai/glm-5.3`), and effort ladders mirror that
108665
+ // catalog's entries, not the upstream vendor's. deliberately no
108666
+ // `openRouterResolve`: a gateway pick is BYOK-only — silently rerouting it
108667
+ // over the Router proxy would bill the wallet for traffic the user pointed
108668
+ // at their own gateway.
108669
+ displayName: "Vercel AI Gateway",
108670
+ envVars: ["AI_GATEWAY_API_KEY"],
108671
+ models: {
108672
+ "claude-opus": {
108673
+ displayName: "Claude Opus",
108674
+ resolve: "vercel/anthropic/claude-opus-5",
108675
+ effort: ["low", "medium", "high", "xhigh", "max"],
108676
+ preferred: true,
108677
+ subagentModel: "claude-sonnet"
108678
+ },
108679
+ "claude-sonnet": {
108680
+ displayName: "Claude Sonnet",
108681
+ resolve: "vercel/anthropic/claude-sonnet-5",
108682
+ effort: ["low", "medium", "high", "xhigh"]
108683
+ },
108684
+ "claude-haiku": {
108685
+ displayName: "Claude Haiku",
108686
+ resolve: "vercel/anthropic/claude-haiku-4.5"
108687
+ },
108688
+ "gpt-sol": {
108689
+ displayName: "GPT Sol",
108690
+ resolve: "vercel/openai/gpt-5.6-sol",
108691
+ effort: ["none", "low", "medium", "high", "xhigh", "max"],
108692
+ subagentModel: "gpt-terra"
108693
+ },
108694
+ "gpt-terra": {
108695
+ displayName: "GPT Terra",
108696
+ resolve: "vercel/openai/gpt-5.6-terra",
108697
+ effort: ["none", "low", "medium", "high", "xhigh", "max"]
108698
+ },
108699
+ "gpt-luna": {
108700
+ displayName: "GPT Luna",
108701
+ resolve: "vercel/openai/gpt-5.6-luna",
108702
+ effort: ["none", "low", "medium", "high", "xhigh", "max"]
108703
+ },
108704
+ "gemini-pro": {
108705
+ displayName: "Gemini Pro",
108706
+ resolve: "vercel/google/gemini-3.1-pro-preview",
108707
+ effort: ["low", "medium", "high"]
108708
+ },
108709
+ "gemini-flash": {
108710
+ displayName: "Gemini Flash",
108711
+ resolve: "vercel/google/gemini-3.6-flash",
108712
+ effort: ["minimal", "low", "medium", "high"]
108713
+ },
108714
+ "deepseek-pro": {
108715
+ displayName: "DeepSeek Pro",
108716
+ resolve: "vercel/deepseek/deepseek-v4-pro-0813",
108717
+ effort: ["high", "xhigh"]
108718
+ },
108719
+ "deepseek-flash": {
108720
+ displayName: "DeepSeek Flash",
108721
+ resolve: "vercel/deepseek/deepseek-v4-flash",
108722
+ effort: ["high", "xhigh"]
108723
+ },
108724
+ glm: {
108725
+ displayName: "GLM",
108726
+ resolve: "vercel/zai/glm-5.3",
108727
+ effort: ["low", "high", "max"]
108728
+ },
108729
+ "kimi-k3": {
108730
+ displayName: "Kimi K3",
108731
+ resolve: "vercel/moonshotai/kimi-k3"
108732
+ }
108733
+ }
108597
108734
  })
108598
108735
  };
108599
108736
  function parseModel(slug2) {
@@ -109552,7 +109689,7 @@ var import_semver = __toESM(require_semver2(), 1);
109552
109689
  // package.json
109553
109690
  var package_default = {
109554
109691
  name: "pullfrog",
109555
- version: "0.1.66",
109692
+ version: "0.1.67",
109556
109693
  type: "module",
109557
109694
  bin: {
109558
109695
  pullfrog: "dist/cli.mjs",
@@ -197757,7 +197894,7 @@ function validateAgentApiKey(params) {
197757
197894
  }
197758
197895
  function isApiKeyAuthError(text) {
197759
197896
  if (!text) return false;
197760
- return text.includes(MISSING_KEY_MARKER) || /Invalid API key/i.test(text) || /Incorrect API key provided/i.test(text) || /\bUser not found\b/i.test(text) || /\bInvalid authentication\b/i.test(text) || /authentication_error/i.test(text) || /Invalid bearer token/i.test(text) || /api_error_status\s*=\s*401/i.test(text) || /API Error:\s*401/i.test(text) || /Failed to authenticate\. API Error:/i.test(text) || /Your api key:.*is invalid/i.test(text) || isMalformedKeyError(text) || isClaudeSubscriptionDisabledError(text) || isClaudeSessionLimitError(text) || isOAuthCredentialExpiredError(text);
197897
+ return text.includes(MISSING_KEY_MARKER) || /Invalid API key/i.test(text) || /Incorrect API key provided/i.test(text) || /\bUser not found\b/i.test(text) || /\bInvalid authentication\b/i.test(text) || /authentication_error/i.test(text) || /Invalid bearer token/i.test(text) || /api_error_status\s*=\s*401/i.test(text) || /API Error:\s*401/i.test(text) || /Failed to authenticate\. API Error:/i.test(text) || /Your api key:.*is invalid/i.test(text) || isMalformedKeyError(text) || isClaudeSubscriptionDisabledError(text) || isKimiPlanTierError(text) || isClaudeSessionLimitError(text) || isOAuthCredentialExpiredError(text);
197761
197898
  }
197762
197899
  function isOAuthCredentialExpiredError(text) {
197763
197900
  return (
@@ -197778,6 +197915,9 @@ function isMalformedKeyError(text) {
197778
197915
  function isClaudeSubscriptionDisabledError(text) {
197779
197916
  return /disabled Claude subscription access/i.test(text);
197780
197917
  }
197918
+ function isKimiPlanTierError(text) {
197919
+ return /Your current (?:subscription does not have access to|plan supports only)\b/i.test(text);
197920
+ }
197781
197921
  var CLAUDE_SESSION_LIMIT_PATTERN = /hit your (?:session|weekly|five-hour|Opus|Sonnet|Haiku)\s+limit(?:\s*·\s*resets\s+([^"\n]+))?/i;
197782
197922
  function isClaudeSessionLimitError(text) {
197783
197923
  return CLAUDE_SESSION_LIMIT_PATTERN.test(text);
@@ -197851,6 +197991,13 @@ function formatApiKeyErrorSummary(params) {
197851
197991
  `[Add repo secret \u2192](${githubSecretsUrl}) \xB7 [Model settings \u2192](${settingsUrl}) \xB7 [Setup docs \u2192](https://docs.pullfrog.com/keys) \xB7 [Ask in Discord \u2192](https://discord.gg/8y96raFg8e)`
197852
197992
  ].join("\n");
197853
197993
  }
197994
+ if (isKimiPlanTierError(params.raw)) {
197995
+ return [
197996
+ "**Your Kimi membership doesn't include the model this run asked for.** Kimi K3 needs a Moderato plan or above, and K3's 1M context and Kimi K2 HighSpeed need Allegretto or above. Upgrade the membership, or switch this repo to **Kimi K2**, which every tier includes.",
197997
+ "",
197998
+ `[Kimi plans \u2192](https://www.kimi.com/membership/pricing) \xB7 [Model settings \u2192](${settingsUrl}) \xB7 [Setup docs \u2192](https://docs.pullfrog.com/kimi-code) \xB7 [Ask in Discord \u2192](https://discord.gg/8y96raFg8e)`
197999
+ ].join("\n");
198000
+ }
197854
198001
  if (isOAuthCredentialExpiredError(params.raw)) {
197855
198002
  return [
197856
198003
  `**Your provider OAuth credential has expired or been revoked.** Re-authenticate the provider connection (e.g. \`pullfrog auth claude\` / \`pullfrog auth codex\` / \`pullfrog auth grok\`), then re-trigger the run.`,
package/dist/internal.js CHANGED
@@ -338,6 +338,69 @@ var providers = {
338
338
  }
339
339
  }
340
340
  }),
341
+ // the same models as `moonshotai` above, billed against a Kimi membership
342
+ // instead of a Moonshot API balance — a separate endpoint, separate model
343
+ // ids and a separate key, which is why it is a provider rather than a second
344
+ // `envVars` entry. no provider injection is needed: models.dev carries
345
+ // `kimi-for-coding` (api `https://api.kimi.com/coding/v1`, npm
346
+ // `@ai-sdk/anthropic`) and opencode's env loop enables it from a bare
347
+ // `KIMI_API_KEY`, verified against our pinned opencode-ai@1.18.5 — all four
348
+ // ids list in `opencode models` and a run reaches Kimi's own auth check.
349
+ //
350
+ // NO `openRouterResolve` ON PURPOSE. OpenRouter cannot serve a Kimi
351
+ // membership, so a Router path here would silently bill the wallet for
352
+ // Moonshot pay-as-you-go under a slug picked for the opposite reason. The
353
+ // absence is load-bearing: `isCardGatedModel` reads it as "not Router-
354
+ // resolvable", `decideModelAccess` answers an explicit pick with the
355
+ // `router` copy ("add your own provider key"), and run-context clamps a
356
+ // keyless Router pick to the default subsidy model with `noRouterPath`.
357
+ "kimi-for-coding": provider({
358
+ // "Kimi Code" is the product name on kimi.com/code and in the console the
359
+ // key is minted from. The provider KEY has to stay `kimi-for-coding` — it
360
+ // is models.dev's id, so it is what `enabled_providers` and every
361
+ // `resolve` prefix must spell.
362
+ displayName: "Kimi Code",
363
+ envVars: ["KIMI_API_KEY"],
364
+ models: {
365
+ // the tier each model needs is in `description` because Kimi answers an
366
+ // out-of-tier request with 401 — indistinguishable from a bad key by
367
+ // status alone, and only `isKimiPlanTierError` tells them apart after the
368
+ // fact. saying it in the picker is the half that stops the run happening.
369
+ "kimi-k3": {
370
+ displayName: "Kimi K3",
371
+ description: "1M context, but only on Allegretto and above \xB7 Moderato caps it at 256K",
372
+ resolve: "kimi-for-coding/k3",
373
+ effort: ["low", "high", "max"],
374
+ subagentModel: "kimi-k2"
375
+ },
376
+ // `preferred` sits on the 256K variant rather than the 1M flagship, which
377
+ // inverts the usual "top tier wins" rule for a reason specific to Kimi:
378
+ // below Allegretto the two are the SAME 256K ceiling and `k3` charges
379
+ // roughly double the quota for it, so on the median tier the flagship is
380
+ // strictly dominated rather than merely pricier. This flag seeds the
381
+ // onboarding pick (`getRecommendedSlug`) and wins key-only auto-select
382
+ // (`autoSelectModel`), which are exactly the two moments nobody has
383
+ // chosen yet.
384
+ "kimi-k3-256k": {
385
+ displayName: "Kimi K3 256K",
386
+ description: "Same model at 256K for ~half the quota \xB7 Moderato or above",
387
+ resolve: "kimi-for-coding/k3-256k",
388
+ effort: ["low", "high", "max"],
389
+ preferred: true,
390
+ subagentModel: "kimi-k2"
391
+ },
392
+ "kimi-k2": {
393
+ displayName: "Kimi K2",
394
+ description: "Included in every Kimi membership tier",
395
+ resolve: "kimi-for-coding/kimi-for-coding"
396
+ },
397
+ "kimi-k2-highspeed": {
398
+ displayName: "Kimi K2 HighSpeed",
399
+ description: "~6x output speed for ~3x quota \xB7 needs Allegretto or above",
400
+ resolve: "kimi-for-coding/kimi-for-coding-highspeed"
401
+ }
402
+ }
403
+ }),
341
404
  opencode: provider({
342
405
  displayName: "OpenCode Zen",
343
406
  envVars: ["OPENCODE_API_KEY"],
@@ -890,6 +953,80 @@ var providers = {
890
953
  openRouterResolve: "openrouter/minimax/minimax-m3"
891
954
  }
892
955
  }
956
+ }),
957
+ vercel: provider({
958
+ // Vercel AI Gateway — one key serves every model it fronts. model IDs use
959
+ // the models.dev `vercel` catalog's own naming (dotted versions, e.g.
960
+ // `anthropic/claude-opus-5`, `zai/glm-5.3`), and effort ladders mirror that
961
+ // catalog's entries, not the upstream vendor's. deliberately no
962
+ // `openRouterResolve`: a gateway pick is BYOK-only — silently rerouting it
963
+ // over the Router proxy would bill the wallet for traffic the user pointed
964
+ // at their own gateway.
965
+ displayName: "Vercel AI Gateway",
966
+ envVars: ["AI_GATEWAY_API_KEY"],
967
+ models: {
968
+ "claude-opus": {
969
+ displayName: "Claude Opus",
970
+ resolve: "vercel/anthropic/claude-opus-5",
971
+ effort: ["low", "medium", "high", "xhigh", "max"],
972
+ preferred: true,
973
+ subagentModel: "claude-sonnet"
974
+ },
975
+ "claude-sonnet": {
976
+ displayName: "Claude Sonnet",
977
+ resolve: "vercel/anthropic/claude-sonnet-5",
978
+ effort: ["low", "medium", "high", "xhigh"]
979
+ },
980
+ "claude-haiku": {
981
+ displayName: "Claude Haiku",
982
+ resolve: "vercel/anthropic/claude-haiku-4.5"
983
+ },
984
+ "gpt-sol": {
985
+ displayName: "GPT Sol",
986
+ resolve: "vercel/openai/gpt-5.6-sol",
987
+ effort: ["none", "low", "medium", "high", "xhigh", "max"],
988
+ subagentModel: "gpt-terra"
989
+ },
990
+ "gpt-terra": {
991
+ displayName: "GPT Terra",
992
+ resolve: "vercel/openai/gpt-5.6-terra",
993
+ effort: ["none", "low", "medium", "high", "xhigh", "max"]
994
+ },
995
+ "gpt-luna": {
996
+ displayName: "GPT Luna",
997
+ resolve: "vercel/openai/gpt-5.6-luna",
998
+ effort: ["none", "low", "medium", "high", "xhigh", "max"]
999
+ },
1000
+ "gemini-pro": {
1001
+ displayName: "Gemini Pro",
1002
+ resolve: "vercel/google/gemini-3.1-pro-preview",
1003
+ effort: ["low", "medium", "high"]
1004
+ },
1005
+ "gemini-flash": {
1006
+ displayName: "Gemini Flash",
1007
+ resolve: "vercel/google/gemini-3.6-flash",
1008
+ effort: ["minimal", "low", "medium", "high"]
1009
+ },
1010
+ "deepseek-pro": {
1011
+ displayName: "DeepSeek Pro",
1012
+ resolve: "vercel/deepseek/deepseek-v4-pro-0813",
1013
+ effort: ["high", "xhigh"]
1014
+ },
1015
+ "deepseek-flash": {
1016
+ displayName: "DeepSeek Flash",
1017
+ resolve: "vercel/deepseek/deepseek-v4-flash",
1018
+ effort: ["high", "xhigh"]
1019
+ },
1020
+ glm: {
1021
+ displayName: "GLM",
1022
+ resolve: "vercel/zai/glm-5.3",
1023
+ effort: ["low", "high", "max"]
1024
+ },
1025
+ "kimi-k3": {
1026
+ displayName: "Kimi K3",
1027
+ resolve: "vercel/moonshotai/kimi-k3"
1028
+ }
1029
+ }
893
1030
  })
894
1031
  };
895
1032
  function parseModel(slug) {
package/dist/models.d.ts CHANGED
@@ -120,6 +120,7 @@ export declare const providers: {
120
120
  xai: ProviderConfig;
121
121
  deepseek: ProviderConfig;
122
122
  moonshotai: ProviderConfig;
123
+ "kimi-for-coding": ProviderConfig;
123
124
  opencode: ProviderConfig;
124
125
  "opencode-go": ProviderConfig;
125
126
  bedrock: ProviderConfig;
@@ -127,6 +128,7 @@ export declare const providers: {
127
128
  azure: ProviderConfig;
128
129
  "openai-compatible": ProviderConfig;
129
130
  openrouter: ProviderConfig;
131
+ vercel: ProviderConfig;
130
132
  };
131
133
  export type ModelProvider = keyof typeof providers;
132
134
  export declare function parseModel(slug: string): {
@@ -135,6 +135,21 @@ export declare function isMalformedKeyError(text: string): boolean;
135
135
  * user controls fixes it, so it gets its own copy instead of the re-auth CTA.
136
136
  */
137
137
  export declare function isClaudeSubscriptionDisabledError(text: string): boolean;
138
+ /**
139
+ * Kimi Code entitlement denial — the key is valid and the membership is live,
140
+ * but the tier doesn't include the model or the context window the run asked
141
+ * for (`k3` and `k3-256k` need Moderato, `kimi-for-coding-highspeed` and K3's
142
+ * 1M window need Allegretto). Kimi answers all of them with **401**, the same
143
+ * status as a revoked key, so nothing but the sentence tells them apart — and
144
+ * without that the run renders "rotate the key in your provider dashboard",
145
+ * which names three things that will not fix it.
146
+ *
147
+ * Both published shapes, parameterised over the clause that varies rather than
148
+ * the model id inside it, per `isOAuthCredentialExpiredError`. Listed in
149
+ * `isApiKeyAuthError` below so the branch is reachable whatever `type` Kimi
150
+ * puts in the payload. See wiki/kimi-code.md.
151
+ */
152
+ export declare function isKimiPlanTierError(text: string): boolean;
138
153
  export declare function isClaudeSessionLimitError(text: string): boolean;
139
154
  /**
140
155
  * The provider rejected a credential we checked before the agent started, and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pullfrog",
3
- "version": "0.1.66",
3
+ "version": "0.1.67",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "pullfrog": "dist/cli.mjs",