claudish 8.0.0 → 9.0.0

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.
Files changed (2) hide show
  1. package/dist/index.js +438 -272
  2. package/package.json +5 -5
package/dist/index.js CHANGED
@@ -731,7 +731,7 @@ var init_onepassword_config = __esm(() => {
731
731
  });
732
732
 
733
733
  // src/version.ts
734
- var VERSION = "8.0.0";
734
+ var VERSION = "9.0.0";
735
735
 
736
736
  // src/logger.ts
737
737
  var exports_logger = {};
@@ -27458,6 +27458,271 @@ var init_stdio2 = __esm(() => {
27458
27458
  init_stdio();
27459
27459
  });
27460
27460
 
27461
+ // src/providers/all-models-cache.ts
27462
+ import { existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "fs";
27463
+ import { homedir as homedir7 } from "os";
27464
+ import { dirname as dirname4, join as join7 } from "path";
27465
+ function readAllModelsCache(path = ALL_MODELS_CACHE_PATH) {
27466
+ if (!existsSync5(path))
27467
+ return null;
27468
+ let raw;
27469
+ try {
27470
+ raw = JSON.parse(readFileSync5(path, "utf-8"));
27471
+ } catch {
27472
+ return null;
27473
+ }
27474
+ if (!raw || typeof raw !== "object")
27475
+ return null;
27476
+ const data = raw;
27477
+ const lastUpdated = typeof data.lastUpdated === "string" ? data.lastUpdated : new Date(0).toISOString();
27478
+ const models = Array.isArray(data.models) ? data.models : [];
27479
+ const entries = Array.isArray(data.entries) ? data.entries : [];
27480
+ return {
27481
+ version: 2,
27482
+ lastUpdated,
27483
+ entries,
27484
+ models
27485
+ };
27486
+ }
27487
+ function writeAllModelsCache(data, path = ALL_MODELS_CACHE_PATH) {
27488
+ const existing = readAllModelsCache(path);
27489
+ const merged = {
27490
+ version: 2,
27491
+ lastUpdated: data.lastUpdated ?? new Date().toISOString(),
27492
+ entries: data.entries ?? existing?.entries ?? [],
27493
+ models: data.models ?? existing?.models ?? []
27494
+ };
27495
+ mkdirSync5(dirname4(path), { recursive: true });
27496
+ writeFileSync5(path, JSON.stringify(merged), "utf-8");
27497
+ }
27498
+ var ALL_MODELS_CACHE_PATH;
27499
+ var init_all_models_cache = __esm(() => {
27500
+ ALL_MODELS_CACHE_PATH = join7(homedir7(), ".claudish", "all-models.json");
27501
+ });
27502
+
27503
+ // src/providers/model-ordering.ts
27504
+ function extractVersionParts(modelId) {
27505
+ const tokens = modelId.toLowerCase().split(/[\/_-]+/);
27506
+ let started = false;
27507
+ const parts = [];
27508
+ for (const token of tokens) {
27509
+ const match = token.match(/\d+(?:\.\d+)*/);
27510
+ if (!match) {
27511
+ if (started)
27512
+ break;
27513
+ continue;
27514
+ }
27515
+ if (!started && /^\d+b$/.test(token) && Number.parseInt(match[0], 10) > 10) {
27516
+ continue;
27517
+ }
27518
+ if (!started) {
27519
+ started = true;
27520
+ for (const part of match[0].split(".")) {
27521
+ parts.push(Number.parseInt(part, 10));
27522
+ }
27523
+ if (!/^\d+(?:\.\d+)*$/.test(token)) {
27524
+ break;
27525
+ }
27526
+ continue;
27527
+ }
27528
+ if (!/^\d{1,2}(?:\.\d+)?$/.test(token)) {
27529
+ break;
27530
+ }
27531
+ for (const part of token.split(".")) {
27532
+ parts.push(Number.parseInt(part, 10));
27533
+ }
27534
+ }
27535
+ return parts;
27536
+ }
27537
+ function compareVersionPartsDesc(a, b) {
27538
+ const maxLength = Math.max(a.length, b.length);
27539
+ for (let i = 0;i < maxLength; i++) {
27540
+ const aPart = a[i] ?? -1;
27541
+ const bPart = b[i] ?? -1;
27542
+ if (aPart !== bPart) {
27543
+ return bPart - aPart;
27544
+ }
27545
+ }
27546
+ return 0;
27547
+ }
27548
+ function compareByReleaseDateDesc(a, b) {
27549
+ const aReleaseRaw = a.releaseDate ? Date.parse(a.releaseDate) : 0;
27550
+ const bReleaseRaw = b.releaseDate ? Date.parse(b.releaseDate) : 0;
27551
+ const aRelease = Number.isNaN(aReleaseRaw) ? 0 : aReleaseRaw;
27552
+ const bRelease = Number.isNaN(bReleaseRaw) ? 0 : bReleaseRaw;
27553
+ if (aRelease !== bRelease) {
27554
+ return bRelease - aRelease;
27555
+ }
27556
+ const aId = a.id ?? a.modelId ?? "";
27557
+ const bId = b.id ?? b.modelId ?? "";
27558
+ const versionCompare = compareVersionPartsDesc(extractVersionParts(aId), extractVersionParts(bId));
27559
+ if (versionCompare !== 0) {
27560
+ return versionCompare;
27561
+ }
27562
+ return aId.localeCompare(bId);
27563
+ }
27564
+
27565
+ // src/adapters/model-catalog.ts
27566
+ function lookupModel(modelId, cachePath) {
27567
+ const entry = findCacheEntry(modelId, cachePath);
27568
+ if (!entry || entry.contextWindow === undefined)
27569
+ return;
27570
+ return {
27571
+ modelId: entry.modelId,
27572
+ contextWindow: entry.contextWindow,
27573
+ supportsVision: entry.supportsVision,
27574
+ releaseDate: entry.releaseDate
27575
+ };
27576
+ }
27577
+ function lookupModelReasoning(modelId, cachePath) {
27578
+ return findCacheEntry(modelId, cachePath)?.reasoning;
27579
+ }
27580
+ function lookupModelTokenParam(modelId, cachePath) {
27581
+ return findCacheEntry(modelId, cachePath)?.tokenParam;
27582
+ }
27583
+ function lookupRouteReasoningMode(modelId, provider, cachePath) {
27584
+ return findCacheEntry(modelId, cachePath)?.aggregators?.find((aggregator) => aggregator.provider === provider)?.reasoning?.mode;
27585
+ }
27586
+ function lookupFamilyDefaultVariant(familyId, provider, cachePath) {
27587
+ const cache = readAllModelsCache(cachePath);
27588
+ if (!cache)
27589
+ return;
27590
+ for (const entry of cache.entries) {
27591
+ const rv = entry.routeVariant;
27592
+ if (!rv?.isDefault)
27593
+ continue;
27594
+ if (rv.provider !== provider)
27595
+ continue;
27596
+ if (rv.familyId === familyId || rv.baseModelId === familyId)
27597
+ return entry.modelId;
27598
+ }
27599
+ return;
27600
+ }
27601
+ function lookupVariantPresets(baseModelId, provider, cachePath) {
27602
+ const cache = readAllModelsCache(cachePath);
27603
+ if (!cache)
27604
+ return [];
27605
+ const wanted = stripVendorPrefix(baseModelId.toLowerCase());
27606
+ const found = [];
27607
+ for (const entry of cache.entries) {
27608
+ const rv = entry.routeVariant;
27609
+ if (!rv?.preset || !rv.baseModelId)
27610
+ continue;
27611
+ if (stripVendorPrefix(rv.baseModelId.toLowerCase()) !== wanted)
27612
+ continue;
27613
+ if (provider !== undefined && rv.provider !== provider)
27614
+ continue;
27615
+ found.push({ modelId: entry.modelId, preset: rv.preset, provider: rv.provider });
27616
+ }
27617
+ return found;
27618
+ }
27619
+ function lookupModelCapabilities(modelId, cachePath) {
27620
+ const entry = findCacheEntry(modelId, cachePath);
27621
+ if (!entry)
27622
+ return;
27623
+ return { supportsTools: entry.supportsTools, supportsThinking: entry.supportsThinking };
27624
+ }
27625
+ function lookupModelForProvider(modelId, provider, cachePath) {
27626
+ const entry = findCacheEntry(modelId, cachePath);
27627
+ if (!entry)
27628
+ return;
27629
+ return entry.aggregators?.find((a) => a.provider === provider)?.contextWindow ?? entry.contextWindow;
27630
+ }
27631
+ function resolveSubscriptionRouting(modelId, provider, cachePath) {
27632
+ const entry = findCacheEntry(modelId, cachePath);
27633
+ if (!entry)
27634
+ return { kind: "unknown" };
27635
+ if (entry.subscriptionPlans?.includes(provider)) {
27636
+ const agg = entry.aggregators?.find((a) => a.provider === provider);
27637
+ return agg?.externalId ? { kind: "serves", externalId: agg.externalId } : { kind: "unknown" };
27638
+ }
27639
+ return isSubscriptionPlan(provider, cachePath) ? { kind: "not-served" } : { kind: "unknown" };
27640
+ }
27641
+ function isSubscriptionPlan(provider, cachePath) {
27642
+ const cache = readAllModelsCache(cachePath);
27643
+ if (!cache)
27644
+ return false;
27645
+ return cache.entries.some((e) => e.subscriptionPlans?.includes(provider));
27646
+ }
27647
+ function stripVendorPrefix(lowerId) {
27648
+ return lowerId.includes("/") ? lowerId.substring(lowerId.lastIndexOf("/") + 1) : lowerId;
27649
+ }
27650
+ function findCacheEntry(modelId, cachePath) {
27651
+ if (modelId.includes("@")) {
27652
+ throw new Error(`model-catalog lookup received provider-routed ID "${modelId}" \u2014 callers must strip the "@" prefix before calling`);
27653
+ }
27654
+ const cache = readAllModelsCache(cachePath);
27655
+ if (!cache || cache.entries.length === 0)
27656
+ return;
27657
+ const lower = modelId.toLowerCase();
27658
+ const unprefixed = stripVendorPrefix(lower);
27659
+ for (const entry of cache.entries) {
27660
+ const entryId = entry.modelId.toLowerCase();
27661
+ const exactMatch = entryId === unprefixed || entryId === lower;
27662
+ const aliasMatch = entry.aliases?.some((a) => a.toLowerCase() === unprefixed || a.toLowerCase() === lower);
27663
+ if (exactMatch || aliasMatch) {
27664
+ return entry;
27665
+ }
27666
+ }
27667
+ return;
27668
+ }
27669
+ function classifyCatalogHit(entry, q) {
27670
+ const id = entry.modelId.toLowerCase();
27671
+ if (id === q || stripVendorPrefix(id) === q)
27672
+ return { bucket: "exact" };
27673
+ const aliases = entry.aliases ?? [];
27674
+ const exactAlias = aliases.find((a) => a.toLowerCase() === q || stripVendorPrefix(a.toLowerCase()) === q);
27675
+ if (exactAlias)
27676
+ return { bucket: "exact", matchedAlias: exactAlias };
27677
+ if (id.includes(q))
27678
+ return { bucket: "id" };
27679
+ const partialAlias = aliases.find((a) => a.toLowerCase().includes(q));
27680
+ return partialAlias ? { bucket: "alias", matchedAlias: partialAlias } : undefined;
27681
+ }
27682
+ function searchCatalogModels(query, limit = 10, cachePath) {
27683
+ const q = query.trim().toLowerCase();
27684
+ if (!q)
27685
+ return [];
27686
+ const cache = readAllModelsCache(cachePath);
27687
+ if (!cache || cache.entries.length === 0)
27688
+ return [];
27689
+ const exact = [];
27690
+ const idPartial = [];
27691
+ const aliasPartial = [];
27692
+ for (const entry of cache.entries) {
27693
+ const hit = classifyCatalogHit(entry, q);
27694
+ if (!hit)
27695
+ continue;
27696
+ const ranked = {
27697
+ entry,
27698
+ match: {
27699
+ modelId: entry.modelId,
27700
+ aliases: entry.aliases ?? [],
27701
+ subscriptionPlans: entry.subscriptionPlans ?? [],
27702
+ ...hit.matchedAlias ? { matchedAlias: hit.matchedAlias } : {}
27703
+ }
27704
+ };
27705
+ if (hit.bucket === "exact")
27706
+ exact.push(ranked);
27707
+ else if (hit.bucket === "id")
27708
+ idPartial.push(ranked);
27709
+ else
27710
+ aliasPartial.push(ranked);
27711
+ }
27712
+ const byRelevance = (a, b) => {
27713
+ const lengthDelta = a.entry.modelId.length - b.entry.modelId.length;
27714
+ if (lengthDelta !== 0)
27715
+ return lengthDelta;
27716
+ return compareByReleaseDateDesc(a.entry, b.entry);
27717
+ };
27718
+ idPartial.sort(byRelevance);
27719
+ aliasPartial.sort(byRelevance);
27720
+ return [...exact, ...idPartial, ...aliasPartial].slice(0, limit).map((r) => r.match);
27721
+ }
27722
+ var init_model_catalog = __esm(() => {
27723
+ init_all_models_cache();
27724
+ });
27725
+
27461
27726
  // src/agent-availability.ts
27462
27727
  import { spawn } from "child_process";
27463
27728
  function parseAvailableAgents(output) {
@@ -27574,26 +27839,26 @@ __export(exports_profile_config, {
27574
27839
  setKeychainEnabled: () => setKeychainEnabled,
27575
27840
  setProfile: () => setProfile
27576
27841
  });
27577
- import { existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "fs";
27578
- import { homedir as homedir7 } from "os";
27579
- import { dirname as dirname4, join as join7, parse as parse6 } from "path";
27842
+ import { existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "fs";
27843
+ import { homedir as homedir8 } from "os";
27844
+ import { dirname as dirname5, join as join8, parse as parse6 } from "path";
27580
27845
  function activeConfigFile() {
27581
27846
  return activeGlobalConfigFile(CONFIG_FILE);
27582
27847
  }
27583
27848
  function ensureConfigDir() {
27584
- if (!existsSync5(CONFIG_DIR)) {
27585
- mkdirSync5(CONFIG_DIR, { recursive: true });
27849
+ if (!existsSync6(CONFIG_DIR)) {
27850
+ mkdirSync6(CONFIG_DIR, { recursive: true });
27586
27851
  }
27587
27852
  }
27588
27853
  function loadConfig() {
27589
27854
  const activeFile = activeConfigFile();
27590
27855
  if (!getConfigFileOverride())
27591
27856
  ensureConfigDir();
27592
- if (!existsSync5(activeFile)) {
27857
+ if (!existsSync6(activeFile)) {
27593
27858
  return { ...DEFAULT_CONFIG };
27594
27859
  }
27595
27860
  try {
27596
- const content = readFileSync5(activeFile, "utf-8");
27861
+ const content = readFileSync6(activeFile, "utf-8");
27597
27862
  const config2 = JSON.parse(content);
27598
27863
  const merged = {
27599
27864
  version: config2.version || DEFAULT_CONFIG.version,
@@ -27663,39 +27928,39 @@ function loadConfig() {
27663
27928
  function saveConfig(config2) {
27664
27929
  if (!getConfigFileOverride())
27665
27930
  ensureConfigDir();
27666
- writeFileSync5(activeConfigFile(), JSON.stringify(config2, null, 2), "utf-8");
27931
+ writeFileSync6(activeConfigFile(), JSON.stringify(config2, null, 2), "utf-8");
27667
27932
  }
27668
27933
  function configExists() {
27669
- return existsSync5(CONFIG_FILE);
27934
+ return existsSync6(CONFIG_FILE);
27670
27935
  }
27671
27936
  function getConfigPath() {
27672
27937
  return CONFIG_FILE;
27673
27938
  }
27674
27939
  function getLocalConfigPath() {
27675
- const home = homedir7();
27940
+ const home = homedir8();
27676
27941
  let dir = process.cwd();
27677
27942
  const root = parse6(dir).root;
27678
27943
  while (dir !== root && dir !== home) {
27679
- const candidate = join7(dir, LOCAL_CONFIG_FILENAME);
27680
- if (existsSync5(candidate))
27944
+ const candidate = join8(dir, LOCAL_CONFIG_FILENAME);
27945
+ if (existsSync6(candidate))
27681
27946
  return candidate;
27682
- if (existsSync5(join7(dir, ".git"))) {
27947
+ if (existsSync6(join8(dir, ".git"))) {
27683
27948
  return candidate;
27684
27949
  }
27685
- dir = dirname4(dir);
27950
+ dir = dirname5(dir);
27686
27951
  }
27687
- return join7(process.cwd(), LOCAL_CONFIG_FILENAME);
27952
+ return join8(process.cwd(), LOCAL_CONFIG_FILENAME);
27688
27953
  }
27689
27954
  function localConfigExists() {
27690
- return existsSync5(getLocalConfigPath());
27955
+ return existsSync6(getLocalConfigPath());
27691
27956
  }
27692
27957
  function readProOnUltracode(paths = defaultScopedConfigPaths) {
27693
27958
  for (const pathFn of [paths.project, paths.global]) {
27694
27959
  try {
27695
27960
  const path = pathFn();
27696
- if (!existsSync5(path))
27961
+ if (!existsSync6(path))
27697
27962
  continue;
27698
- const parsed = JSON.parse(readFileSync5(path, "utf-8"));
27963
+ const parsed = JSON.parse(readFileSync6(path, "utf-8"));
27699
27964
  if (typeof parsed?.proOnUltracode === "boolean")
27700
27965
  return parsed.proOnUltracode;
27701
27966
  } catch {}
@@ -27704,17 +27969,17 @@ function readProOnUltracode(paths = defaultScopedConfigPaths) {
27704
27969
  }
27705
27970
  function isProjectDirectory() {
27706
27971
  const cwd = process.cwd();
27707
- return [".git", "package.json", "Cargo.toml", "go.mod", "pyproject.toml", ".claudish.json"].some((f) => existsSync5(join7(cwd, f)));
27972
+ return [".git", "package.json", "Cargo.toml", "go.mod", "pyproject.toml", ".claudish.json"].some((f) => existsSync6(join8(cwd, f)));
27708
27973
  }
27709
27974
  function loadLocalConfig() {
27710
27975
  if (getConfigFileOverride())
27711
27976
  return null;
27712
27977
  const localPath = getLocalConfigPath();
27713
- if (!existsSync5(localPath)) {
27978
+ if (!existsSync6(localPath)) {
27714
27979
  return null;
27715
27980
  }
27716
27981
  try {
27717
- const content = readFileSync5(localPath, "utf-8");
27982
+ const content = readFileSync6(localPath, "utf-8");
27718
27983
  const config2 = JSON.parse(content);
27719
27984
  return {
27720
27985
  ...config2,
@@ -27732,7 +27997,7 @@ function saveLocalConfig(config2) {
27732
27997
  if (toWrite.routing !== undefined && Object.keys(toWrite.routing).length === 0) {
27733
27998
  delete toWrite.routing;
27734
27999
  }
27735
- writeFileSync5(getLocalConfigPath(), JSON.stringify(toWrite, null, 2), "utf-8");
28000
+ writeFileSync6(getLocalConfigPath(), JSON.stringify(toWrite, null, 2), "utf-8");
27736
28001
  }
27737
28002
  function loadConfigForScope(scope) {
27738
28003
  if (scope === "local") {
@@ -27987,8 +28252,8 @@ function disableLocalProvider(providerName) {
27987
28252
  }
27988
28253
  var CONFIG_DIR, CONFIG_FILE, LOCAL_CONFIG_FILENAME = ".claudish.json", DEFAULT_CONFIG, defaultScopedConfigPaths;
27989
28254
  var init_profile_config = __esm(() => {
27990
- CONFIG_DIR = join7(homedir7(), ".claudish");
27991
- CONFIG_FILE = join7(CONFIG_DIR, "config.json");
28255
+ CONFIG_DIR = join8(homedir8(), ".claudish");
28256
+ CONFIG_FILE = join8(CONFIG_DIR, "config.json");
27992
28257
  DEFAULT_CONFIG = {
27993
28258
  version: "1.0.0",
27994
28259
  defaultProfile: "default",
@@ -28040,17 +28305,24 @@ var init_runtime_providers = __esm(() => {
28040
28305
  });
28041
28306
 
28042
28307
  // src/handlers/shared/remote-provider-types.ts
28308
+ function registerSubscriptionCredentialProbe(fn) {
28309
+ const previous = _subscriptionCredentialProbe;
28310
+ _subscriptionCredentialProbe = fn;
28311
+ return previous;
28312
+ }
28043
28313
  function isSubscriptionProvider(provider) {
28044
- return SUBSCRIPTION_PROVIDERS.has(provider.toLowerCase());
28314
+ const p = provider.toLowerCase();
28315
+ if (SUBSCRIPTION_PROVIDERS.has(p))
28316
+ return true;
28317
+ if (!CREDENTIAL_DECIDED_PROVIDERS.has(p))
28318
+ return false;
28319
+ return _subscriptionCredentialProbe?.(p) === true;
28045
28320
  }
28046
28321
  function registerDynamicPricingLookup(fn) {
28047
28322
  _dynamicLookup = fn;
28048
28323
  }
28049
28324
  function getModelPricing(provider, modelName) {
28050
28325
  const p = provider.toLowerCase();
28051
- if (FREE_PROVIDERS.has(p)) {
28052
- return { inputCostPer1M: 0, outputCostPer1M: 0, isFree: true };
28053
- }
28054
28326
  if (isSubscriptionProvider(p)) {
28055
28327
  return { inputCostPer1M: 0, outputCostPer1M: 0, isSubscription: true };
28056
28328
  }
@@ -28062,7 +28334,7 @@ function getModelPricing(provider, modelName) {
28062
28334
  const canonical = PROVIDER_ALIAS[p] || p;
28063
28335
  return PROVIDER_DEFAULTS[canonical] || { inputCostPer1M: 1, outputCostPer1M: 4, isEstimate: true };
28064
28336
  }
28065
- var PROVIDER_DEFAULTS, FREE_PROVIDERS, SUBSCRIPTION_PROVIDERS, PROVIDER_ALIAS, _dynamicLookup = null;
28337
+ var PROVIDER_DEFAULTS, SUBSCRIPTION_PROVIDERS, CREDENTIAL_DECIDED_PROVIDERS, _subscriptionCredentialProbe = null, PROVIDER_ALIAS, _dynamicLookup = null;
28066
28338
  var init_remote_provider_types = __esm(() => {
28067
28339
  PROVIDER_DEFAULTS = {
28068
28340
  gemini: { inputCostPer1M: 0.5, outputCostPer1M: 2, isEstimate: true },
@@ -28072,7 +28344,6 @@ var init_remote_provider_types = __esm(() => {
28072
28344
  glm: { inputCostPer1M: 0.16, outputCostPer1M: 0.8, isEstimate: true },
28073
28345
  ollamacloud: { inputCostPer1M: 1, outputCostPer1M: 4, isEstimate: true }
28074
28346
  };
28075
- FREE_PROVIDERS = new Set(["opencode-zen", "zen"]);
28076
28347
  SUBSCRIPTION_PROVIDERS = new Set([
28077
28348
  "minimax-coding",
28078
28349
  "kimi-coding",
@@ -28081,8 +28352,10 @@ var init_remote_provider_types = __esm(() => {
28081
28352
  "devin",
28082
28353
  "antigravity",
28083
28354
  "sakana-subscription",
28084
- "grok-subscription"
28355
+ "grok-subscription",
28356
+ "opencode-zen-go"
28085
28357
  ]);
28358
+ CREDENTIAL_DECIDED_PROVIDERS = new Set(["openai-codex"]);
28086
28359
  PROVIDER_ALIAS = {
28087
28360
  google: "gemini",
28088
28361
  oai: "openai",
@@ -28095,153 +28368,6 @@ var init_remote_provider_types = __esm(() => {
28095
28368
  };
28096
28369
  });
28097
28370
 
28098
- // src/providers/all-models-cache.ts
28099
- import { existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "fs";
28100
- import { homedir as homedir8 } from "os";
28101
- import { dirname as dirname5, join as join8 } from "path";
28102
- function readAllModelsCache(path = ALL_MODELS_CACHE_PATH) {
28103
- if (!existsSync6(path))
28104
- return null;
28105
- let raw;
28106
- try {
28107
- raw = JSON.parse(readFileSync6(path, "utf-8"));
28108
- } catch {
28109
- return null;
28110
- }
28111
- if (!raw || typeof raw !== "object")
28112
- return null;
28113
- const data = raw;
28114
- const lastUpdated = typeof data.lastUpdated === "string" ? data.lastUpdated : new Date(0).toISOString();
28115
- const models = Array.isArray(data.models) ? data.models : [];
28116
- const entries = Array.isArray(data.entries) ? data.entries : [];
28117
- return {
28118
- version: 2,
28119
- lastUpdated,
28120
- entries,
28121
- models
28122
- };
28123
- }
28124
- function writeAllModelsCache(data, path = ALL_MODELS_CACHE_PATH) {
28125
- const existing = readAllModelsCache(path);
28126
- const merged = {
28127
- version: 2,
28128
- lastUpdated: data.lastUpdated ?? new Date().toISOString(),
28129
- entries: data.entries ?? existing?.entries ?? [],
28130
- models: data.models ?? existing?.models ?? []
28131
- };
28132
- mkdirSync6(dirname5(path), { recursive: true });
28133
- writeFileSync6(path, JSON.stringify(merged), "utf-8");
28134
- }
28135
- var ALL_MODELS_CACHE_PATH;
28136
- var init_all_models_cache = __esm(() => {
28137
- ALL_MODELS_CACHE_PATH = join8(homedir8(), ".claudish", "all-models.json");
28138
- });
28139
-
28140
- // src/adapters/model-catalog.ts
28141
- function lookupModel(modelId, cachePath) {
28142
- const entry = findCacheEntry(modelId, cachePath);
28143
- if (!entry || entry.contextWindow === undefined)
28144
- return;
28145
- return {
28146
- modelId: entry.modelId,
28147
- contextWindow: entry.contextWindow,
28148
- supportsVision: entry.supportsVision,
28149
- releaseDate: entry.releaseDate
28150
- };
28151
- }
28152
- function lookupModelReasoning(modelId, cachePath) {
28153
- return findCacheEntry(modelId, cachePath)?.reasoning;
28154
- }
28155
- function lookupModelTokenParam(modelId, cachePath) {
28156
- return findCacheEntry(modelId, cachePath)?.tokenParam;
28157
- }
28158
- function lookupFamilyDefaultVariant(familyId, provider, cachePath) {
28159
- const cache2 = readAllModelsCache(cachePath);
28160
- if (!cache2)
28161
- return;
28162
- for (const entry of cache2.entries) {
28163
- const rv = entry.routeVariant;
28164
- if (!rv?.isDefault)
28165
- continue;
28166
- if (rv.provider !== provider)
28167
- continue;
28168
- if (rv.familyId === familyId || rv.baseModelId === familyId)
28169
- return entry.modelId;
28170
- }
28171
- return;
28172
- }
28173
- function lookupVariantPresets(baseModelId, provider, cachePath) {
28174
- const cache2 = readAllModelsCache(cachePath);
28175
- if (!cache2)
28176
- return [];
28177
- const wanted = stripVendorPrefix(baseModelId.toLowerCase());
28178
- const found = [];
28179
- for (const entry of cache2.entries) {
28180
- const rv = entry.routeVariant;
28181
- if (!rv?.preset || !rv.baseModelId)
28182
- continue;
28183
- if (stripVendorPrefix(rv.baseModelId.toLowerCase()) !== wanted)
28184
- continue;
28185
- if (provider !== undefined && rv.provider !== provider)
28186
- continue;
28187
- found.push({ modelId: entry.modelId, preset: rv.preset, provider: rv.provider });
28188
- }
28189
- return found;
28190
- }
28191
- function lookupModelCapabilities(modelId, cachePath) {
28192
- const entry = findCacheEntry(modelId, cachePath);
28193
- if (!entry)
28194
- return;
28195
- return { supportsTools: entry.supportsTools, supportsThinking: entry.supportsThinking };
28196
- }
28197
- function lookupModelForProvider(modelId, provider, cachePath) {
28198
- const entry = findCacheEntry(modelId, cachePath);
28199
- if (!entry)
28200
- return;
28201
- return entry.aggregators?.find((a) => a.provider === provider)?.contextWindow ?? entry.contextWindow;
28202
- }
28203
- function resolveSubscriptionRouting(modelId, provider, cachePath) {
28204
- const entry = findCacheEntry(modelId, cachePath);
28205
- if (!entry)
28206
- return { kind: "unknown" };
28207
- if (entry.subscriptionPlans?.includes(provider)) {
28208
- const agg = entry.aggregators?.find((a) => a.provider === provider);
28209
- return agg?.externalId ? { kind: "serves", externalId: agg.externalId } : { kind: "unknown" };
28210
- }
28211
- return isSubscriptionPlan(provider, cachePath) ? { kind: "not-served" } : { kind: "unknown" };
28212
- }
28213
- function isSubscriptionPlan(provider, cachePath) {
28214
- const cache2 = readAllModelsCache(cachePath);
28215
- if (!cache2)
28216
- return false;
28217
- return cache2.entries.some((e) => e.subscriptionPlans?.includes(provider));
28218
- }
28219
- function stripVendorPrefix(lowerId) {
28220
- return lowerId.includes("/") ? lowerId.substring(lowerId.lastIndexOf("/") + 1) : lowerId;
28221
- }
28222
- function findCacheEntry(modelId, cachePath) {
28223
- if (modelId.includes("@")) {
28224
- throw new Error(`model-catalog lookup received provider-routed ID "${modelId}" \u2014 callers must strip the "@" prefix before calling`);
28225
- }
28226
- const cache2 = readAllModelsCache(cachePath);
28227
- if (!cache2 || cache2.entries.length === 0)
28228
- return;
28229
- const lower = modelId.toLowerCase();
28230
- const unprefixed = stripVendorPrefix(lower);
28231
- for (const entry of cache2.entries) {
28232
- const entryId = entry.modelId.toLowerCase();
28233
- const exactMatch = entryId === unprefixed || entryId === lower;
28234
- const aliasMatch = entry.aliases?.some((a) => a.toLowerCase() === unprefixed || a.toLowerCase() === lower);
28235
- if (exactMatch || aliasMatch) {
28236
- return entry;
28237
- }
28238
- }
28239
- return;
28240
- }
28241
- var init_model_catalog = __esm(() => {
28242
- init_all_models_cache();
28243
- });
28244
-
28245
28371
  // src/adapters/tool-name-utils.ts
28246
28372
  function hashToolName(name) {
28247
28373
  let h1 = 3735928559;
@@ -36430,7 +36556,7 @@ class ApiKeyCredentialProvider {
36430
36556
  } else {
36431
36557
  headers = { ...this.staticHeaders };
36432
36558
  }
36433
- return { headers };
36559
+ return { arm: "api-key", headers };
36434
36560
  }
36435
36561
  }
36436
36562
  var init_api_key_credential = __esm(() => {
@@ -36809,6 +36935,34 @@ var init_codex_oauth = __esm(() => {
36809
36935
  };
36810
36936
  });
36811
36937
 
36938
+ // src/auth/credentials/billing-probe.ts
36939
+ function recordSignedArm(provider, arm) {
36940
+ signedArm.set(provider.toLowerCase(), arm);
36941
+ }
36942
+ function clearSignedArm(provider) {
36943
+ if (provider)
36944
+ signedArm.delete(provider.toLowerCase());
36945
+ else
36946
+ signedArm.clear();
36947
+ }
36948
+ function installBillingProbes() {
36949
+ return registerSubscriptionCredentialProbe((p) => {
36950
+ const recorded = signedArm.get(p);
36951
+ if (recorded)
36952
+ return recorded === "subscription";
36953
+ return PROBES[p]?.() === true;
36954
+ });
36955
+ }
36956
+ var signedArm, PROBES;
36957
+ var init_billing_probe = __esm(() => {
36958
+ init_remote_provider_types();
36959
+ init_codex_oauth();
36960
+ signedArm = new Map;
36961
+ PROBES = {
36962
+ "openai-codex": () => CodexOAuth.getInstance().hasCredentials()
36963
+ };
36964
+ });
36965
+
36812
36966
  // src/auth/credentials/composite-credential.ts
36813
36967
  class CompositeCredentialProvider {
36814
36968
  catalogName;
@@ -36876,6 +37030,7 @@ class CodexOAuthHalf {
36876
37030
  const token = await this.oauth.getAccessToken();
36877
37031
  const accountId = this.oauth.getAccountId();
36878
37032
  return {
37033
+ arm: "oauth",
36879
37034
  headers: buildOAuthHeaders(token, accountId),
36880
37035
  endpoint: CODEX_RESPONSES_ENDPOINT,
36881
37036
  transformPayload: (p) => ({
@@ -37365,6 +37520,17 @@ var init_kimi_oauth = __esm(() => {
37365
37520
  import { existsSync as existsSync15, readFileSync as readFileSync14 } from "fs";
37366
37521
  import { homedir as homedir22 } from "os";
37367
37522
  import { join as join22 } from "path";
37523
+ function credentialSatisfies(descriptor, data) {
37524
+ if (!data?.access_token)
37525
+ return false;
37526
+ if (data.refresh_token)
37527
+ return true;
37528
+ if (descriptor.expiresAtField && data[descriptor.expiresAtField]) {
37529
+ const buffer = descriptor.expiryBufferMs ?? 0;
37530
+ return data[descriptor.expiresAtField] > Date.now() + buffer;
37531
+ }
37532
+ return true;
37533
+ }
37368
37534
  function hasValidOAuthCredentials(descriptor) {
37369
37535
  const credPath = join22(homedir22(), ".claudish", descriptor.credentialFile);
37370
37536
  if (!existsSync15(credPath))
@@ -37373,16 +37539,7 @@ function hasValidOAuthCredentials(descriptor) {
37373
37539
  return true;
37374
37540
  }
37375
37541
  try {
37376
- const data = JSON.parse(readFileSync14(credPath, "utf-8"));
37377
- if (!data.access_token)
37378
- return false;
37379
- if (data.refresh_token)
37380
- return true;
37381
- if (descriptor.expiresAtField && data[descriptor.expiresAtField]) {
37382
- const buffer = descriptor.expiryBufferMs ?? 0;
37383
- return data[descriptor.expiresAtField] > Date.now() + buffer;
37384
- }
37385
- return true;
37542
+ return credentialSatisfies(descriptor, JSON.parse(readFileSync14(credPath, "utf-8")));
37386
37543
  } catch {
37387
37544
  return false;
37388
37545
  }
@@ -37433,6 +37590,7 @@ class KimiOAuthHalf {
37433
37590
  async getRequestAuth(_ctx) {
37434
37591
  const token = await this.oauth.getAccessToken();
37435
37592
  return {
37593
+ arm: "oauth",
37436
37594
  headers: {
37437
37595
  "anthropic-version": "2023-06-01",
37438
37596
  Authorization: `Bearer ${token}`,
@@ -37632,6 +37790,7 @@ class CredentialAuthority {
37632
37790
  return p.getRequestAuth(ctx);
37633
37791
  }
37634
37792
  invalidate(name) {
37793
+ clearSignedArm(name);
37635
37794
  if (name) {
37636
37795
  this.registry.get(name)?.invalidate?.();
37637
37796
  return;
@@ -37646,9 +37805,11 @@ class CredentialAuthority {
37646
37805
  }
37647
37806
  async login(name) {
37648
37807
  await this.registry.get(name)?.login?.();
37808
+ clearSignedArm(name);
37649
37809
  }
37650
37810
  async logout(name) {
37651
37811
  await this.registry.get(name)?.logout?.();
37812
+ clearSignedArm(name);
37652
37813
  }
37653
37814
  get(name) {
37654
37815
  return this.registry.get(name);
@@ -37699,6 +37860,7 @@ var init_authority = __esm(() => {
37699
37860
  init_provider_definitions();
37700
37861
  init_antigravity_credential();
37701
37862
  init_api_key_credential();
37863
+ init_billing_probe();
37702
37864
  init_codex_credential();
37703
37865
  init_devin_credential();
37704
37866
  init_grok_credential();
@@ -37711,6 +37873,7 @@ var init_authority = __esm(() => {
37711
37873
  google: ["gemini"]
37712
37874
  };
37713
37875
  credentials = CredentialAuthority.buildDefault();
37876
+ installBillingProbes();
37714
37877
  });
37715
37878
 
37716
37879
  // src/providers/devin/proto-codec.ts
@@ -39642,6 +39805,18 @@ var init_session_events = __esm(() => {
39642
39805
 
39643
39806
  // src/session-events/pro-injection.ts
39644
39807
  function resolveVariantPreset(bareModelName, provider, cachePath) {
39808
+ const routeMode = lookupRouteReasoningMode(bareModelName, provider, cachePath);
39809
+ if (routeMode) {
39810
+ if (routeMode.status !== "supported" || !routeMode.values.includes("pro")) {
39811
+ return;
39812
+ }
39813
+ return {
39814
+ params: { reasoning: { mode: "pro" } },
39815
+ provider,
39816
+ preset: "reasoning.mode=pro",
39817
+ sourceLabel: `route capability @ ${provider}`
39818
+ };
39819
+ }
39645
39820
  for (const variant of lookupVariantPresets(bareModelName, provider, cachePath)) {
39646
39821
  try {
39647
39822
  const params = parseModelParams(variant.preset);
@@ -39649,9 +39824,9 @@ function resolveVariantPreset(bareModelName, provider, cachePath) {
39649
39824
  continue;
39650
39825
  return {
39651
39826
  params,
39652
- variantModelId: variant.modelId,
39653
39827
  provider: variant.provider,
39654
- preset: variant.preset
39828
+ preset: variant.preset,
39829
+ sourceLabel: `variant ${variant.modelId} @ ${variant.provider}`
39655
39830
  };
39656
39831
  } catch {}
39657
39832
  }
@@ -39675,7 +39850,7 @@ function applyProInjection(requestPayload, opts) {
39675
39850
  if (!resolved)
39676
39851
  return false;
39677
39852
  deepMergeParams(requestPayload, resolved.params);
39678
- log(`[SessionEvents] ultracode active \u2192 preset ${resolved.preset} for ${opts.targetModel} ` + `(catalog variant ${resolved.variantModelId} @ ${resolved.provider}, session ${opts.sessionId})`);
39853
+ log(`[SessionEvents] ultracode active \u2192 preset ${resolved.preset} for ${opts.targetModel} ` + `(catalog ${resolved.sourceLabel}, session ${opts.sessionId})`);
39679
39854
  return true;
39680
39855
  } catch {
39681
39856
  return false;
@@ -44846,6 +45021,7 @@ var init_openai_codex = __esm(() => {
44846
45021
  init_codex_api_format();
44847
45022
  init_model_catalog();
44848
45023
  init_authority();
45024
+ init_billing_probe();
44849
45025
  init_harness();
44850
45026
  init_openai();
44851
45027
  FALLBACK_CONVERSATION_KEY = randomBytes6(16).toString("hex");
@@ -44859,6 +45035,8 @@ var init_openai_codex = __esm(() => {
44859
45035
  } catch {
44860
45036
  this.cachedAuth = null;
44861
45037
  }
45038
+ const signedWithOAuth = this.cachedAuth?.arm === "oauth";
45039
+ recordSignedArm("openai-codex", signedWithOAuth ? "subscription" : "metered");
44862
45040
  }
44863
45041
  getEndpoint(_targetModel) {
44864
45042
  return this.cachedAuth?.endpoint ?? super.getEndpoint();
@@ -45347,16 +45525,28 @@ function describeMissingCredential(providerName) {
45347
45525
  const keyNames = info?.envVar ? [info.envVar, ...info.aliases ?? []].join(" or ") : undefined;
45348
45526
  const signup = info?.url ? ` Get one at ${info.url}.` : "";
45349
45527
  const def = getProviderByName(providerName);
45528
+ const sibling = describeSiblingKeys(def);
45350
45529
  if (isLocalTransport(providerName)) {
45351
45530
  const where = def ? ` Claudish will use ${getEffectiveBaseUrl(def)}.` : "";
45352
45531
  const keyClause = keyNames ? ` (Only set ${keyNames} if your local server requires a bearer token.)` : "";
45353
- return `Provider "${providerName}" is a LOCAL server and is not enabled. Enable it in \`claudish config\` (Providers tab), or add "localProviders": ["${providerName}"] to ~/.claudish/config.json.${where}${keyClause}`;
45532
+ return `Provider "${providerName}" is a LOCAL server and is not enabled. Enable it in \`claudish config\` (Providers tab), or add "localProviders": ["${providerName}"] to ~/.claudish/config.json.${where}${keyClause}${sibling}`;
45354
45533
  }
45355
45534
  if (def?.oauthFallback) {
45356
45535
  const keyClause = keyNames ? ` Or set ${keyNames} (env, config, or 1Password import) to use a metered API key instead.${signup}` : "";
45357
- return `No credential for provider "${providerName}". Sign in with \`claudish login ${providerName}\` to use your existing subscription.${keyClause}`;
45536
+ return `No credential for provider "${providerName}". Sign in with \`claudish login ${providerName}\` to use your existing subscription.${keyClause}${sibling}`;
45358
45537
  }
45359
- return keyNames ? `No API key for provider "${providerName}". Set ${keyNames} (env, config, or 1Password import).${signup}` : `No API key for provider "${providerName}".`;
45538
+ return keyNames ? `No API key for provider "${providerName}". Set ${keyNames} (env, config, or 1Password import).${signup}${sibling}` : `No API key for provider "${providerName}".${sibling}`;
45539
+ }
45540
+ function describeSiblingKeys(def) {
45541
+ const vars = def?.siblingKeyEnvVars ?? [];
45542
+ if (vars.length === 0)
45543
+ return "";
45544
+ const all = getAllProviders();
45545
+ const named = vars.map((v) => {
45546
+ const owner = all.find((p) => p.name !== def?.name && p.apiKeyEnvVar === v);
45547
+ return owner ? `${v} (${owner.name})` : v;
45548
+ });
45549
+ return ` Note: ${named.join(" or ")} is a DIFFERENT plan's key and is not accepted here.`;
45360
45550
  }
45361
45551
  function getDisplayName(providerName) {
45362
45552
  const def = getProviderByName(providerName);
@@ -45822,7 +46012,7 @@ var init_provider_definitions = __esm(() => {
45822
46012
  apiPath: "/v1/chat/completions",
45823
46013
  modelDiscovery: { path: "/v1/models", format: "openai-models-list" },
45824
46014
  apiKeyEnvVar: "OPENCODE_GO_API_KEY",
45825
- apiKeyAliases: ["OPENCODE_API_KEY"],
46015
+ siblingKeyEnvVars: ["OPENCODE_API_KEY"],
45826
46016
  apiKeyDescription: "OpenCode Zen Go (Lite Plan) API Key",
45827
46017
  apiKeyUrl: "https://opencode.ai/",
45828
46018
  shortcuts: ["zengo", "zgo"],
@@ -47438,7 +47628,8 @@ var init_routing_hints = __esm(() => {
47438
47628
  openrouter: { apiKeyEnvVar: "OPENROUTER_API_KEY" },
47439
47629
  "x-ai": { apiKeyEnvVar: "XAI_API_KEY" },
47440
47630
  "z-ai": { apiKeyEnvVar: "ZAI_API_KEY" },
47441
- "opencode-zen": { apiKeyEnvVar: "OPENCODE_API_KEY" }
47631
+ "opencode-zen": { apiKeyEnvVar: "OPENCODE_API_KEY" },
47632
+ "opencode-zen-go": { apiKeyEnvVar: "OPENCODE_GO_API_KEY" }
47442
47633
  };
47443
47634
  });
47444
47635
 
@@ -51266,68 +51457,6 @@ var init_cache_ttl = __esm(() => {
51266
51457
  FIREBASE_CACHE_TTL_MS = FIREBASE_CACHE_TTL_HOURS * 60 * 60 * 1000;
51267
51458
  });
51268
51459
 
51269
- // src/providers/model-ordering.ts
51270
- function extractVersionParts(modelId) {
51271
- const tokens = modelId.toLowerCase().split(/[\/_-]+/);
51272
- let started = false;
51273
- const parts = [];
51274
- for (const token of tokens) {
51275
- const match = token.match(/\d+(?:\.\d+)*/);
51276
- if (!match) {
51277
- if (started)
51278
- break;
51279
- continue;
51280
- }
51281
- if (!started && /^\d+b$/.test(token) && Number.parseInt(match[0], 10) > 10) {
51282
- continue;
51283
- }
51284
- if (!started) {
51285
- started = true;
51286
- for (const part of match[0].split(".")) {
51287
- parts.push(Number.parseInt(part, 10));
51288
- }
51289
- if (!/^\d+(?:\.\d+)*$/.test(token)) {
51290
- break;
51291
- }
51292
- continue;
51293
- }
51294
- if (!/^\d{1,2}(?:\.\d+)?$/.test(token)) {
51295
- break;
51296
- }
51297
- for (const part of token.split(".")) {
51298
- parts.push(Number.parseInt(part, 10));
51299
- }
51300
- }
51301
- return parts;
51302
- }
51303
- function compareVersionPartsDesc(a, b) {
51304
- const maxLength = Math.max(a.length, b.length);
51305
- for (let i = 0;i < maxLength; i++) {
51306
- const aPart = a[i] ?? -1;
51307
- const bPart = b[i] ?? -1;
51308
- if (aPart !== bPart) {
51309
- return bPart - aPart;
51310
- }
51311
- }
51312
- return 0;
51313
- }
51314
- function compareByReleaseDateDesc(a, b) {
51315
- const aReleaseRaw = a.releaseDate ? Date.parse(a.releaseDate) : 0;
51316
- const bReleaseRaw = b.releaseDate ? Date.parse(b.releaseDate) : 0;
51317
- const aRelease = Number.isNaN(aReleaseRaw) ? 0 : aReleaseRaw;
51318
- const bRelease = Number.isNaN(bReleaseRaw) ? 0 : bReleaseRaw;
51319
- if (aRelease !== bRelease) {
51320
- return bRelease - aRelease;
51321
- }
51322
- const aId = a.id ?? a.modelId ?? "";
51323
- const bId = b.id ?? b.modelId ?? "";
51324
- const versionCompare = compareVersionPartsDesc(extractVersionParts(aId), extractVersionParts(bId));
51325
- if (versionCompare !== 0) {
51326
- return versionCompare;
51327
- }
51328
- return aId.localeCompare(bId);
51329
- }
51330
-
51331
51460
  // src/model-loader.ts
51332
51461
  import { existsSync as existsSync21, mkdirSync as mkdirSync13, readFileSync as readFileSync21, writeFileSync as writeFileSync13 } from "fs";
51333
51462
  import { homedir as homedir29 } from "os";
@@ -51378,12 +51507,15 @@ function collectRoutingPrefixes(group, getNativePrefix) {
51378
51507
  out.push(native);
51379
51508
  seen.add(native);
51380
51509
  }
51381
- for (const sub of group.subscriptions) {
51382
- const p = sub.subscription?.prefix;
51383
- if (!p || seen.has(p))
51384
- continue;
51385
- seen.add(p);
51386
- out.push(p);
51510
+ for (const subscriptionRow of group.subscriptions) {
51511
+ const routes = subscriptionRow.subscriptions && subscriptionRow.subscriptions.length > 0 ? subscriptionRow.subscriptions : subscriptionRow.subscription ? [subscriptionRow.subscription] : [];
51512
+ for (const route2 of routes) {
51513
+ const p = route2?.prefix;
51514
+ if (!p || seen.has(p))
51515
+ continue;
51516
+ seen.add(p);
51517
+ out.push(p);
51518
+ }
51387
51519
  }
51388
51520
  return out;
51389
51521
  }
@@ -56488,7 +56620,7 @@ Tokens: ${result.usage.input} input, ${result.usage.output} output`;
56488
56620
  });
56489
56621
  tools.push({
56490
56622
  name: "search_models",
56491
- description: "Search all OpenRouter models by name, provider, or capability",
56623
+ description: "Search OpenRouter's listing by name, provider, or capability, and cross-reference " + "claudish's own catalog. SCOPE: the listing covers OpenRouter only, so a name's " + "absence from it is NOT evidence the name is unroutable \u2014 subscription wire ids " + "(`k3`) and catalog aliases live outside that namespace and are reported separately " + "here. This tool cannot tell you which provider will serve a model or whether the " + "hop is subscription or metered; call `preflight` for that.",
56492
56624
  inputSchema: {
56493
56625
  type: "object",
56494
56626
  properties: {
@@ -56523,13 +56655,40 @@ Tokens: ${result.usage.input} input, ${result.usage.output} output`;
56523
56655
  return b.score - a.score;
56524
56656
  return compareByReleaseDateDesc(orderingKey(a.model), orderingKey(b.model));
56525
56657
  }).slice(0, maxResults);
56658
+ const catalogMatches = searchCatalogModels(query, Math.max(maxResults, 5));
56659
+ const renderCatalogSection = () => {
56660
+ if (catalogMatches.length === 0)
56661
+ return "";
56662
+ let s = `
56663
+ ## Catalog names (what claudish routes)
56664
+
56665
+ `;
56666
+ s += `| Bare name | Matched alias | Subscription plan |
56667
+ `;
56668
+ s += `|-----------|---------------|-------------------|
56669
+ `;
56670
+ for (const m of catalogMatches) {
56671
+ const plans = m.subscriptionPlans.length > 0 ? m.subscriptionPlans.join(", ") : "-";
56672
+ s += `| ${m.modelId} | ${m.matchedAlias ?? "-"} | ${plans} |
56673
+ `;
56674
+ }
56675
+ s += `
56676
+ Pass the **bare name**. Routing puts a subscription ahead of the metered API and ` + "rewrites the model to that plan's wire id for you. An aggregator-qualified id " + "(`moonshotai/...`, `accounts/fireworks/...`) pins that aggregator and bills per token.\n";
56677
+ return s;
56678
+ };
56526
56679
  if (results.length === 0) {
56527
- return {
56528
- content: [{ type: "text", text: `No models found matching "${query}"` }]
56529
- };
56680
+ const catalog = renderCatalogSection();
56681
+ const text = catalog ? `No OpenRouter listing matches "${query}", but claudish's catalog knows these:
56682
+ ${catalog}` : `No models found matching "${query}".
56683
+
56684
+ ` + "This searched OpenRouter's listing only. Subscription wire ids and catalog " + "aliases are not in it, so this is not proof the name is unroutable. Call " + "`list_models` for the recommended set, or `preflight` to test a specific name " + "against real routing.";
56685
+ return { content: [{ type: "text", text }] };
56530
56686
  }
56531
56687
  let output = `# Search Results for "${query}"
56532
56688
 
56689
+ `;
56690
+ output += `## OpenRouter listing
56691
+
56533
56692
  `;
56534
56693
  output += `| Model | Provider | Pricing | Context |
56535
56694
  `;
@@ -56545,8 +56704,13 @@ Tokens: ${result.usage.input} input, ${result.usage.output} output`;
56545
56704
  output += `| ${model.id} | ${provider} | ${pricing} | ${context} |
56546
56705
  `;
56547
56706
  }
56707
+ output += renderCatalogSection();
56708
+ const suggested = catalogMatches[0]?.modelId ?? results[0].model.id;
56548
56709
  output += `
56549
- Use with: run_prompt(model="${results[0].model.id}", prompt="your prompt")`;
56710
+ Use with: run_prompt(model="${suggested}", prompt="your prompt")`;
56711
+ output += `
56712
+
56713
+ To learn which provider would actually serve \`${suggested}\`, and whether that ` + "hop is covered by a subscription or billed per token, call " + `\`preflight({models: ["${suggested}"]})\`. This listing cannot answer that.`;
56550
56714
  return { content: [{ type: "text", text: output }] };
56551
56715
  }
56552
56716
  });
@@ -57450,6 +57614,7 @@ var init_mcp_server = __esm(() => {
57450
57614
  init_server2();
57451
57615
  init_stdio2();
57452
57616
  init_types();
57617
+ init_model_catalog();
57453
57618
  init_agent_availability();
57454
57619
  init_prehydrate();
57455
57620
  init_diagnostics();
@@ -75349,7 +75514,7 @@ var init_api_key_map = __esm(() => {
75349
75514
  "qwen-payg": { envVar: "DASHSCOPE_API_KEY", aliases: ["QWEN_API_KEY"] },
75350
75515
  ollamacloud: { envVar: "OLLAMA_API_KEY" },
75351
75516
  "opencode-zen": { envVar: "OPENCODE_API_KEY" },
75352
- "opencode-zen-go": { envVar: "OPENCODE_GO_API_KEY", aliases: ["OPENCODE_API_KEY"] },
75517
+ "opencode-zen-go": { envVar: "OPENCODE_GO_API_KEY" },
75353
75518
  vertex: { envVar: "VERTEX_API_KEY", aliases: ["VERTEX_PROJECT"] },
75354
75519
  poe: { envVar: "POE_API_KEY" }
75355
75520
  };
@@ -76922,6 +77087,7 @@ ${h("ENVIRONMENT VARIABLES")}
76922
77087
  ${blue("SAKANA_SUBSCRIPTION_API_KEY")} Sakana Fugu Subscription ${dim4("(sc@; separate subscription key)")}
76923
77088
  ${blue("OLLAMA_API_KEY")} OllamaCloud ${dim4("(oc@, llama@)")}
76924
77089
  ${blue("OPENCODE_API_KEY")} OpenCode Zen ${dim4("(zen@)")}
77090
+ ${blue("OPENCODE_GO_API_KEY")} OpenCode Zen Go plan ${dim4("(zgo@, zengo@; separate plan key)")}
76925
77091
  ${blue("POE_API_KEY")} Poe ${dim4("(poe@)")}
76926
77092
  ${blue("LITELLM_API_KEY")} LiteLLM ${dim4("(litellm@, ll@; needs LITELLM_BASE_URL)")}
76927
77093
  ${blue("VERTEX_API_KEY")} Vertex AI Express ${dim4("(v@)")}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudish",
3
- "version": "8.0.0",
3
+ "version": "9.0.0",
4
4
  "description": "Run Claude Code with any model - OpenRouter, Ollama, LM Studio & local models",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -60,10 +60,10 @@
60
60
  "ai"
61
61
  ],
62
62
  "optionalDependencies": {
63
- "@claudish/magmux-darwin-arm64": "8.0.0",
64
- "@claudish/magmux-darwin-x64": "8.0.0",
65
- "@claudish/magmux-linux-arm64": "8.0.0",
66
- "@claudish/magmux-linux-x64": "8.0.0"
63
+ "@claudish/magmux-darwin-arm64": "9.0.0",
64
+ "@claudish/magmux-darwin-x64": "9.0.0",
65
+ "@claudish/magmux-linux-arm64": "9.0.0",
66
+ "@claudish/magmux-linux-x64": "9.0.0"
67
67
  },
68
68
  "author": "Jack Rudenko <i@madappgang.com>",
69
69
  "license": "MIT",