claudish 9.0.0 → 9.0.2

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 +520 -395
  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 = "9.0.0";
734
+ var VERSION = "9.0.2";
735
735
 
736
736
  // src/logger.ts
737
737
  var exports_logger = {};
@@ -835,7 +835,7 @@ function redactDeep(val, key) {
835
835
  return val;
836
836
  }
837
837
  function isStructuralLogWorthy(msg) {
838
- return msg.startsWith("[SSE:") || msg.startsWith("[Suppressed]") || msg.startsWith("[Proxy]") || msg.startsWith("[Fallback]") || msg.startsWith("[Streaming] ===") || msg.startsWith("[Streaming] Chunk:") || msg.startsWith("[Streaming] Received") || msg.startsWith("[Streaming] Text-based tool calls") || msg.startsWith("[Streaming] Final usage") || msg.startsWith("[Streaming] Sending") || msg.startsWith("[AnthropicSSE] Stream complete") || msg.startsWith("[AnthropicSSE] Tool use:") || msg.includes("Response status:") || msg.includes("Error") || msg.includes("error") || msg.includes("[Auto-route]");
838
+ return msg.startsWith("[SSE:") || msg.startsWith("[Suppressed]") || msg.startsWith("[Proxy]") || msg.startsWith("[Claude Code]") || msg.startsWith("[Fallback]") || msg.startsWith("[Streaming] ===") || msg.startsWith("[Streaming] Chunk:") || msg.startsWith("[Streaming] Received") || msg.startsWith("[Streaming] Text-based tool calls") || msg.startsWith("[Streaming] Final usage") || msg.startsWith("[Streaming] Sending") || msg.startsWith("[AnthropicSSE] Stream complete") || msg.startsWith("[AnthropicSSE] Tool use:") || msg.includes("Response status:") || msg.includes("Error") || msg.includes("error") || msg.includes("[Auto-route]");
839
839
  }
840
840
  function redactLogLine(message, timestamp) {
841
841
  if (message.startsWith("[SSE:")) {
@@ -27477,11 +27477,13 @@ function readAllModelsCache(path = ALL_MODELS_CACHE_PATH) {
27477
27477
  const lastUpdated = typeof data.lastUpdated === "string" ? data.lastUpdated : new Date(0).toISOString();
27478
27478
  const models = Array.isArray(data.models) ? data.models : [];
27479
27479
  const entries = Array.isArray(data.entries) ? data.entries : [];
27480
+ const plans = Array.isArray(data.plans) ? data.plans : undefined;
27480
27481
  return {
27481
27482
  version: 2,
27482
27483
  lastUpdated,
27483
27484
  entries,
27484
- models
27485
+ models,
27486
+ ...plans !== undefined ? { plans } : {}
27485
27487
  };
27486
27488
  }
27487
27489
  function writeAllModelsCache(data, path = ALL_MODELS_CACHE_PATH) {
@@ -27490,7 +27492,8 @@ function writeAllModelsCache(data, path = ALL_MODELS_CACHE_PATH) {
27490
27492
  version: 2,
27491
27493
  lastUpdated: data.lastUpdated ?? new Date().toISOString(),
27492
27494
  entries: data.entries ?? existing?.entries ?? [],
27493
- models: data.models ?? existing?.models ?? []
27495
+ models: data.models ?? existing?.models ?? [],
27496
+ ...data.plans !== undefined || existing?.plans !== undefined ? { plans: data.plans ?? existing?.plans ?? [] } : {}
27494
27497
  };
27495
27498
  mkdirSync5(dirname4(path), { recursive: true });
27496
27499
  writeFileSync5(path, JSON.stringify(merged), "utf-8");
@@ -27632,13 +27635,32 @@ function resolveSubscriptionRouting(modelId, provider, cachePath) {
27632
27635
  const entry = findCacheEntry(modelId, cachePath);
27633
27636
  if (!entry)
27634
27637
  return { kind: "unknown" };
27635
- if (entry.subscriptionPlans?.includes(provider)) {
27638
+ const cache = readAllModelsCache(cachePath);
27639
+ const providerPlans = cache?.plans?.filter((plan) => plan.routing?.providerUid === provider) ?? [];
27640
+ if (cache?.plans === undefined) {
27641
+ if (entry.subscriptionPlans?.includes(provider)) {
27642
+ const agg = entry.aggregators?.find((a) => a.provider === provider);
27643
+ return agg?.externalId ? { kind: "serves", externalId: agg.externalId } : { kind: "unknown" };
27644
+ }
27645
+ return isLegacySubscriptionPlan(provider, cachePath) ? { kind: "not-served" } : { kind: "unknown" };
27646
+ }
27647
+ if (providerPlans.length === 0)
27648
+ return { kind: "unknown" };
27649
+ const providerPlanIds = new Set(providerPlans.map((plan) => plan.id));
27650
+ const hasMembership = entry.subscriptionPlans?.some((planId) => providerPlanIds.has(planId));
27651
+ if (hasMembership) {
27636
27652
  const agg = entry.aggregators?.find((a) => a.provider === provider);
27637
27653
  return agg?.externalId ? { kind: "serves", externalId: agg.externalId } : { kind: "unknown" };
27638
27654
  }
27639
- return isSubscriptionPlan(provider, cachePath) ? { kind: "not-served" } : { kind: "unknown" };
27655
+ const hasPublishedProviderRoster = cache.entries.some((candidate) => candidate.subscriptionPlans?.some((planId) => providerPlanIds.has(planId)));
27656
+ if (!hasPublishedProviderRoster)
27657
+ return { kind: "unknown" };
27658
+ return providerPlans.every(isCatalogDiscoveredPlan) ? { kind: "not-served" } : { kind: "unknown" };
27640
27659
  }
27641
- function isSubscriptionPlan(provider, cachePath) {
27660
+ function isCatalogDiscoveredPlan(plan) {
27661
+ return plan.modelDiscovery === "catalog";
27662
+ }
27663
+ function isLegacySubscriptionPlan(provider, cachePath) {
27642
27664
  const cache = readAllModelsCache(cachePath);
27643
27665
  if (!cache)
27644
27666
  return false;
@@ -46348,6 +46370,16 @@ var init_auto_route = __esm(() => {
46348
46370
  });
46349
46371
 
46350
46372
  // src/providers/catalog-client.ts
46373
+ function derivePlansUrl(catalogUrl) {
46374
+ try {
46375
+ const url2 = new URL(catalogUrl);
46376
+ url2.pathname = url2.pathname.replace(/\/queryModels$/, "/queryPlans");
46377
+ url2.search = "";
46378
+ return url2.toString();
46379
+ } catch {
46380
+ return "https://us-central1-claudish-6da10.cloudfunctions.net/queryPlans";
46381
+ }
46382
+ }
46351
46383
  function getCatalogEntries() {
46352
46384
  if (_catalogEntriesForTest !== undefined)
46353
46385
  return _catalogEntriesForTest;
@@ -46481,6 +46513,7 @@ function logResolution(userInput, result, quiet = false) {
46481
46513
  }
46482
46514
  }
46483
46515
  async function refreshCatalog(timeoutMs) {
46516
+ const plansPromise = fetchSubscriptionPlans(timeoutMs);
46484
46517
  let response;
46485
46518
  try {
46486
46519
  response = await fetch(FIREBASE_CATALOG_URL, { signal: AbortSignal.timeout(timeoutMs) });
@@ -46507,10 +46540,28 @@ async function refreshCatalog(timeoutMs) {
46507
46540
  backwardCompatModels.push({ id });
46508
46541
  }
46509
46542
  _memCache = data.models;
46510
- writeAllModelsCache({ entries: data.models, models: backwardCompatModels });
46543
+ const plans = await plansPromise;
46544
+ writeAllModelsCache({
46545
+ entries: data.models,
46546
+ models: backwardCompatModels,
46547
+ ...plans !== undefined ? { plans } : {}
46548
+ });
46511
46549
  _warmPromise = Promise.resolve();
46512
46550
  return { kind: "refreshed", modelCount: data.models.length };
46513
46551
  }
46552
+ async function fetchSubscriptionPlans(timeoutMs) {
46553
+ try {
46554
+ const response = await fetch(FIREBASE_PLANS_URL, {
46555
+ signal: AbortSignal.timeout(timeoutMs)
46556
+ });
46557
+ if (!response.ok)
46558
+ return;
46559
+ const data = await response.json();
46560
+ return Array.isArray(data.plans) ? data.plans : undefined;
46561
+ } catch {
46562
+ return;
46563
+ }
46564
+ }
46514
46565
  async function warmCatalog() {
46515
46566
  if (!_warmPromise) {
46516
46567
  _warmPromise = refreshCatalog(8000).then(() => {
@@ -46532,10 +46583,11 @@ async function ensureCatalogReady(timeoutMs = 5000) {
46532
46583
  new Promise((resolve3) => setTimeout(resolve3, timeoutMs))
46533
46584
  ]);
46534
46585
  }
46535
- var FIREBASE_CATALOG_URL, _memCache = null, _catalogEntriesForTest, _warmPromise = null;
46586
+ var FIREBASE_CATALOG_URL, FIREBASE_PLANS_URL, _memCache = null, _catalogEntriesForTest, _warmPromise = null;
46536
46587
  var init_catalog_client = __esm(() => {
46537
46588
  init_all_models_cache();
46538
46589
  FIREBASE_CATALOG_URL = process.env.CLAUDISH_CATALOG_URL ?? process.env.FIREBASE_CATALOG_URL ?? "https://us-central1-claudish-6da10.cloudfunctions.net/queryModels?status=active&catalog=slim&limit=1000";
46590
+ FIREBASE_PLANS_URL = process.env.CLAUDISH_PLANS_URL ?? derivePlansUrl(FIREBASE_CATALOG_URL);
46539
46591
  });
46540
46592
 
46541
46593
  // src/config-schema.ts
@@ -47633,6 +47685,349 @@ var init_routing_hints = __esm(() => {
47633
47685
  };
47634
47686
  });
47635
47687
 
47688
+ // src/providers/cache-ttl.ts
47689
+ var FIREBASE_CACHE_TTL_HOURS = 24, FIREBASE_CACHE_TTL_MS;
47690
+ var init_cache_ttl = __esm(() => {
47691
+ FIREBASE_CACHE_TTL_MS = FIREBASE_CACHE_TTL_HOURS * 60 * 60 * 1000;
47692
+ });
47693
+
47694
+ // src/model-loader.ts
47695
+ import { existsSync as existsSync19, mkdirSync as mkdirSync11, readFileSync as readFileSync18, writeFileSync as writeFileSync10 } from "fs";
47696
+ import { homedir as homedir27 } from "os";
47697
+ import { join as join27 } from "path";
47698
+ function groupRecommendedModels(entries) {
47699
+ const byId = new Map;
47700
+ const categoryOrder = new Map;
47701
+ for (const entry of entries) {
47702
+ const list = byId.get(entry.id);
47703
+ if (list)
47704
+ list.push(entry);
47705
+ else
47706
+ byId.set(entry.id, [entry]);
47707
+ if (!categoryOrder.has(entry.category))
47708
+ categoryOrder.set(entry.category, categoryOrder.size);
47709
+ }
47710
+ const flagship = [];
47711
+ const fast = [];
47712
+ for (const [id, members] of byId.entries()) {
47713
+ const primary = members.find((m) => m.category !== "subscription") ?? members[0];
47714
+ const subscriptions = members.filter((m) => m.category === "subscription");
47715
+ const bucket = primary.category === "programming" || primary.category === "vision" || primary.category === "reasoning" ? "flagship" : "fast";
47716
+ const group = { id, primary, subscriptions, bucket };
47717
+ if (bucket === "flagship")
47718
+ flagship.push(group);
47719
+ else
47720
+ fast.push(group);
47721
+ }
47722
+ const byCuratedPriorityThenFreshness = (a, b) => {
47723
+ const aCat = categoryOrder.get(a.primary.category) ?? Number.MAX_SAFE_INTEGER;
47724
+ const bCat = categoryOrder.get(b.primary.category) ?? Number.MAX_SAFE_INTEGER;
47725
+ if (aCat !== bCat)
47726
+ return aCat - bCat;
47727
+ if (a.primary.priority !== b.primary.priority)
47728
+ return a.primary.priority - b.primary.priority;
47729
+ return compareByReleaseDateDesc(a.primary, b.primary);
47730
+ };
47731
+ flagship.sort(byCuratedPriorityThenFreshness);
47732
+ fast.sort(byCuratedPriorityThenFreshness);
47733
+ return { flagship, fast };
47734
+ }
47735
+ function collectRoutingPrefixes(group, getNativePrefix) {
47736
+ const slug = (group.primary.provider || "").toLowerCase();
47737
+ const native = getNativePrefix(slug);
47738
+ const seen = new Set;
47739
+ const out = [];
47740
+ if (native) {
47741
+ out.push(native);
47742
+ seen.add(native);
47743
+ }
47744
+ for (const subscriptionRow of group.subscriptions) {
47745
+ const routes = subscriptionRow.subscriptions && subscriptionRow.subscriptions.length > 0 ? subscriptionRow.subscriptions : subscriptionRow.subscription ? [subscriptionRow.subscription] : [];
47746
+ const orderedRoutes = [...routes].sort(compareRecommendedRoutes);
47747
+ for (const route of orderedRoutes) {
47748
+ const p = route?.prefix;
47749
+ if (!p || seen.has(p))
47750
+ continue;
47751
+ seen.add(p);
47752
+ out.push(p);
47753
+ }
47754
+ }
47755
+ return out;
47756
+ }
47757
+ function compareRecommendedRoutes(left, right) {
47758
+ const leftRank = left?.tier && Object.hasOwn(RECOMMENDED_ROUTE_TIER_ORDER, left.tier) ? RECOMMENDED_ROUTE_TIER_ORDER[left.tier] : Number.MAX_SAFE_INTEGER;
47759
+ const rightRank = right?.tier && Object.hasOwn(RECOMMENDED_ROUTE_TIER_ORDER, right.tier) ? RECOMMENDED_ROUTE_TIER_ORDER[right.tier] : Number.MAX_SAFE_INTEGER;
47760
+ return leftRank - rightRank;
47761
+ }
47762
+ function buildCatalogRoutingRules(doc2) {
47763
+ const routesByModel = new Map;
47764
+ let sourceIndex = 0;
47765
+ for (const entry of doc2.models) {
47766
+ const routes = entry.subscriptions && entry.subscriptions.length > 0 ? entry.subscriptions : entry.subscription ? [entry.subscription] : [];
47767
+ for (const route of routes) {
47768
+ routesByModel.set(entry.id, [...routesByModel.get(entry.id) ?? [], { route, sourceIndex }]);
47769
+ sourceIndex += 1;
47770
+ }
47771
+ }
47772
+ const rules = {};
47773
+ for (const [modelId, candidates] of routesByModel) {
47774
+ const seen = new Set;
47775
+ const entries = candidates.filter(({ route }) => typeof route?.routingProvider === "string" && route.routingProvider.length > 0 && typeof route.command === "string" && route.command.length > 0).sort((left, right) => compareRecommendedRoutes(left.route, right.route) || left.sourceIndex - right.sourceIndex).map(({ route }) => {
47776
+ const at = route.command.indexOf("@");
47777
+ const wireId = at >= 0 ? route.command.slice(at + 1) : route.command;
47778
+ return `${route.routingProvider}@${wireId}`;
47779
+ }).filter((entry) => {
47780
+ if (seen.has(entry))
47781
+ return false;
47782
+ seen.add(entry);
47783
+ return true;
47784
+ });
47785
+ if (entries.length > 0)
47786
+ rules[modelId] = entries;
47787
+ }
47788
+ return rules;
47789
+ }
47790
+ function parsePriceAvg(s) {
47791
+ if (!s || s === "N/A")
47792
+ return Number.POSITIVE_INFINITY;
47793
+ if (s === "FREE")
47794
+ return 0;
47795
+ const m = s.match(/\$([\d.]+)/);
47796
+ return m ? Number.parseFloat(m[1]) : Number.POSITIVE_INFINITY;
47797
+ }
47798
+ function parseCtx(s) {
47799
+ if (!s || s === "N/A")
47800
+ return 0;
47801
+ const upper = s.toUpperCase();
47802
+ if (upper.includes("M"))
47803
+ return Number.parseFloat(upper) * 1e6;
47804
+ if (upper.includes("K"))
47805
+ return Number.parseFloat(upper) * 1000;
47806
+ return Number.parseInt(s, 10) || 0;
47807
+ }
47808
+ function normalizePricingDisplay(raw) {
47809
+ const pricing = raw || "N/A";
47810
+ if (pricing.includes("-1000000"))
47811
+ return "varies";
47812
+ if (pricing === "$0.00/1M" || pricing === "FREE")
47813
+ return "FREE";
47814
+ return pricing;
47815
+ }
47816
+ function formatListingPrice(entry, opts) {
47817
+ const rate = normalizePricingDisplay(entry.pricing?.average);
47818
+ if (rate !== "N/A")
47819
+ return rate;
47820
+ const plan = entry.subscription?.plan;
47821
+ if (!plan)
47822
+ return "N/A";
47823
+ return opts?.compact ? "SUB" : `SUB (${plan})`;
47824
+ }
47825
+ function computeQuickPicks(primaries) {
47826
+ if (primaries.length === 0) {
47827
+ return {
47828
+ budget: null,
47829
+ largeContext: null,
47830
+ mostCapable: null,
47831
+ visionCoding: null,
47832
+ agentic: null
47833
+ };
47834
+ }
47835
+ const priced = primaries.filter((m) => {
47836
+ const p = parsePriceAvg(m.pricing?.average);
47837
+ return p > 0 && p !== Number.POSITIVE_INFINITY;
47838
+ }).sort((a, b) => parsePriceAvg(a.pricing?.average) - parsePriceAvg(b.pricing?.average));
47839
+ const budget = priced[0] ?? null;
47840
+ const byCtx = [...primaries].sort((a, b) => parseCtx(b.context) - parseCtx(a.context));
47841
+ const largeContext = byCtx[0] ?? null;
47842
+ const byPrice = [...primaries].sort((a, b) => parsePriceAvg(b.pricing?.average) - parsePriceAvg(a.pricing?.average));
47843
+ const mostCapable = byPrice.find((m) => parsePriceAvg(m.pricing?.average) !== Number.POSITIVE_INFINITY) ?? null;
47844
+ const visionCoding = primaries.find((m) => m.supportsVision === true && m.id !== budget?.id && m.id !== mostCapable?.id) ?? null;
47845
+ const agentic = primaries.find((m) => m.supportsReasoning === true && m.id !== mostCapable?.id) ?? null;
47846
+ return { budget, largeContext, mostCapable, visionCoding, agentic };
47847
+ }
47848
+ async function getRecommendedModels(opts = {}) {
47849
+ const { forceRefresh = false } = opts;
47850
+ if (!forceRefresh && _cachedRecommendedModels) {
47851
+ return _cachedRecommendedModels;
47852
+ }
47853
+ if (!forceRefresh && existsSync19(RECOMMENDED_MODELS_CACHE_PATH)) {
47854
+ try {
47855
+ const cacheData = JSON.parse(readFileSync18(RECOMMENDED_MODELS_CACHE_PATH, "utf-8"));
47856
+ if (cacheData.models && cacheData.models.length > 0 && isFreshEnough(cacheData)) {
47857
+ _cachedRecommendedModels = cacheData;
47858
+ return cacheData;
47859
+ }
47860
+ } catch {}
47861
+ }
47862
+ try {
47863
+ const response = await fetch(FIREBASE_RECOMMENDED_URL, {
47864
+ signal: AbortSignal.timeout(RECOMMENDED_FETCH_TIMEOUT_MS)
47865
+ });
47866
+ if (response.ok) {
47867
+ const data = await response.json();
47868
+ if (data.models && data.models.length > 0) {
47869
+ _cachedRecommendedModels = data;
47870
+ try {
47871
+ const cacheDir = join27(homedir27(), ".claudish");
47872
+ mkdirSync11(cacheDir, { recursive: true });
47873
+ writeFileSync10(RECOMMENDED_MODELS_CACHE_PATH, JSON.stringify(data), "utf-8");
47874
+ } catch {}
47875
+ return data;
47876
+ }
47877
+ }
47878
+ } catch {}
47879
+ throw new Error("Unable to load recommended models: Firebase unreachable and no local cache. " + "Check connectivity.");
47880
+ }
47881
+ function getRecommendedModelsSync() {
47882
+ if (_cachedRecommendedModels)
47883
+ return _cachedRecommendedModels;
47884
+ if (existsSync19(RECOMMENDED_MODELS_CACHE_PATH)) {
47885
+ try {
47886
+ const cacheData = JSON.parse(readFileSync18(RECOMMENDED_MODELS_CACHE_PATH, "utf-8"));
47887
+ if (cacheData.models && cacheData.models.length > 0 && isFreshEnough(cacheData)) {
47888
+ _cachedRecommendedModels = cacheData;
47889
+ return cacheData;
47890
+ }
47891
+ } catch {}
47892
+ }
47893
+ return { version: "0", lastUpdated: "", models: [] };
47894
+ }
47895
+ async function warmRecommendedModels() {
47896
+ try {
47897
+ return await getRecommendedModels({ forceRefresh: true });
47898
+ } catch {
47899
+ return null;
47900
+ }
47901
+ }
47902
+ function isFreshEnough(doc2) {
47903
+ const generatedAt = doc2.generatedAt;
47904
+ if (!generatedAt)
47905
+ return true;
47906
+ const ageHours = (Date.now() - new Date(generatedAt).getTime()) / (1000 * 60 * 60);
47907
+ return ageHours <= FIREBASE_CACHE_TTL_HOURS;
47908
+ }
47909
+ async function searchModels(query, limit = 50) {
47910
+ const url2 = `${FIREBASE_BASE_URL}?search=${encodeURIComponent(query)}&limit=${limit}&status=active`;
47911
+ const response = await fetch(url2, {
47912
+ signal: AbortSignal.timeout(SEARCH_FETCH_TIMEOUT_MS)
47913
+ });
47914
+ if (!response.ok) {
47915
+ throw new Error(`Firebase search returned ${response.status} ${response.statusText}`);
47916
+ }
47917
+ const data = await response.json();
47918
+ return data.models ?? [];
47919
+ }
47920
+ async function getModelByIdFromFirebase(modelId) {
47921
+ const url2 = `${FIREBASE_BASE_URL}?search=${encodeURIComponent(modelId)}&limit=5`;
47922
+ const response = await fetch(url2, {
47923
+ signal: AbortSignal.timeout(SEARCH_FETCH_TIMEOUT_MS)
47924
+ });
47925
+ if (!response.ok) {
47926
+ throw new Error(`Firebase lookup returned ${response.status} ${response.statusText}`);
47927
+ }
47928
+ const data = await response.json();
47929
+ const models = data.models ?? [];
47930
+ for (const m of models) {
47931
+ if (m.modelId === modelId)
47932
+ return m;
47933
+ if (m.aliases?.includes(modelId))
47934
+ return m;
47935
+ }
47936
+ return null;
47937
+ }
47938
+ async function getTop100Models() {
47939
+ const url2 = `${FIREBASE_BASE_URL}?catalog=top100`;
47940
+ const response = await fetch(url2, {
47941
+ signal: AbortSignal.timeout(SEARCH_FETCH_TIMEOUT_MS)
47942
+ });
47943
+ if (!response.ok) {
47944
+ throw new Error(`Firebase top100 fetch failed: ${response.status} ${response.statusText}`);
47945
+ }
47946
+ const data = await response.json();
47947
+ return data;
47948
+ }
47949
+ async function getProviderList() {
47950
+ const url2 = `${FIREBASE_BASE_URL}?catalog=providers`;
47951
+ const response = await fetch(url2, {
47952
+ signal: AbortSignal.timeout(SEARCH_FETCH_TIMEOUT_MS)
47953
+ });
47954
+ if (!response.ok) {
47955
+ throw new Error(`Firebase providers fetch failed: ${response.status} ${response.statusText}`);
47956
+ }
47957
+ const data = await response.json();
47958
+ return data.providers ?? [];
47959
+ }
47960
+ async function getModelsByProvider(provider, limit = 200) {
47961
+ const url2 = `${FIREBASE_BASE_URL}?provider=${encodeURIComponent(provider)}&status=active&limit=${limit}`;
47962
+ const response = await fetch(url2, {
47963
+ signal: AbortSignal.timeout(SEARCH_FETCH_TIMEOUT_MS)
47964
+ });
47965
+ if (!response.ok) {
47966
+ throw new Error(`Firebase provider query returned ${response.status} ${response.statusText}`);
47967
+ }
47968
+ const data = await response.json();
47969
+ if (Array.isArray(data))
47970
+ return data;
47971
+ return data.models ?? [];
47972
+ }
47973
+ function loadModelInfo() {
47974
+ if (_cachedModelInfo) {
47975
+ return _cachedModelInfo;
47976
+ }
47977
+ const data = getRecommendedModelsSync();
47978
+ const modelInfo = {};
47979
+ for (const model of data.models) {
47980
+ modelInfo[model.id] = {
47981
+ name: model.name,
47982
+ description: model.description,
47983
+ priority: model.priority,
47984
+ provider: model.provider
47985
+ };
47986
+ }
47987
+ modelInfo.custom = {
47988
+ name: "Custom Model",
47989
+ description: "Enter any model ID manually",
47990
+ priority: 999,
47991
+ provider: "Custom"
47992
+ };
47993
+ _cachedModelInfo = modelInfo;
47994
+ return modelInfo;
47995
+ }
47996
+ function getAvailableModels() {
47997
+ if (_cachedModelIds) {
47998
+ return _cachedModelIds;
47999
+ }
48000
+ const data = getRecommendedModelsSync();
48001
+ const modelIds = data.models.sort((a, b) => a.priority - b.priority).map((m) => m.id);
48002
+ const result = [...modelIds, "custom"];
48003
+ _cachedModelIds = result;
48004
+ return result;
48005
+ }
48006
+ var _cachedModelInfo = null, _cachedModelIds = null, _cachedRecommendedModels = null, FIREBASE_BASE_URL = "https://us-central1-claudish-6da10.cloudfunctions.net/queryModels", FIREBASE_RECOMMENDED_URL, RECOMMENDED_MODELS_CACHE_PATH, RECOMMENDED_FETCH_TIMEOUT_MS = 5000, SEARCH_FETCH_TIMEOUT_MS = 1e4, FIREBASE_SLUG_TO_PROVIDER_NAME, RECOMMENDED_ROUTE_TIER_ORDER;
48007
+ var init_model_loader = __esm(() => {
48008
+ init_cache_ttl();
48009
+ FIREBASE_RECOMMENDED_URL = `${FIREBASE_BASE_URL}?catalog=recommended`;
48010
+ RECOMMENDED_MODELS_CACHE_PATH = join27(homedir27(), ".claudish", "recommended-models-cache.json");
48011
+ FIREBASE_SLUG_TO_PROVIDER_NAME = {
48012
+ openai: "openai",
48013
+ google: "google",
48014
+ "x-ai": "x-ai",
48015
+ "z-ai": "z-ai",
48016
+ moonshotai: "kimi",
48017
+ minimax: "minimax",
48018
+ qwen: "qwen",
48019
+ deepseek: "deepseek",
48020
+ mistralai: "mistralai",
48021
+ sakana: "sakana"
48022
+ };
48023
+ RECOMMENDED_ROUTE_TIER_ORDER = {
48024
+ native: 0,
48025
+ general: 1,
48026
+ metered: 2,
48027
+ aggregator: 3
48028
+ };
48029
+ });
48030
+
47636
48031
  // src/providers/default-routing-rules.ts
47637
48032
  function validateRoutingRulesAgainstProviders(rules) {
47638
48033
  const unknown3 = [];
@@ -47729,7 +48124,21 @@ function loadRoutingRules() {
47729
48124
  const global_ = loadConfig().routing ?? {};
47730
48125
  validateRoutingRules(local);
47731
48126
  validateRoutingRules(global_);
47732
- return mergeRoutingRules(DEFAULT_ROUTING_RULES, global_, local);
48127
+ const catalogRules = retainKnownCatalogRoutingRules(buildCatalogRoutingRules(getRecommendedModelsSync()));
48128
+ return mergeRoutingRules({ ...DEFAULT_ROUTING_RULES, ...catalogRules }, global_, local);
48129
+ }
48130
+ function retainKnownCatalogRoutingRules(rules) {
48131
+ const retained = {};
48132
+ for (const [modelId, entries] of Object.entries(rules)) {
48133
+ const knownEntries = entries.filter((entry) => {
48134
+ const providerRaw = entry.split("@", 1)[0]?.toLowerCase() ?? "";
48135
+ const provider = PROVIDER_SHORTCUTS[providerRaw] ?? providerRaw;
48136
+ return getProviderByName(provider) !== undefined;
48137
+ });
48138
+ if (knownEntries.length > 0)
48139
+ retained[modelId] = knownEntries;
48140
+ }
48141
+ return retained;
47733
48142
  }
47734
48143
  function validateRoutingRules(rules) {
47735
48144
  const seenLower = new Map;
@@ -47924,6 +48333,7 @@ var init_routing_rules = __esm(() => {
47924
48333
  init_authority();
47925
48334
  init_remote_provider_types();
47926
48335
  init_logger();
48336
+ init_model_loader();
47927
48337
  init_profile_config();
47928
48338
  init_auto_route();
47929
48339
  init_catalog_client();
@@ -47931,6 +48341,7 @@ var init_routing_rules = __esm(() => {
47931
48341
  init_model_availability();
47932
48342
  init_model_parser();
47933
48343
  init_model_parser();
48344
+ init_provider_definitions();
47934
48345
  init_routing_hints();
47935
48346
  });
47936
48347
 
@@ -49066,8 +49477,8 @@ __export(exports_session_discovery, {
49066
49477
  });
49067
49478
  import { execFile, execFileSync as execFileSync2 } from "child_process";
49068
49479
  import { closeSync as closeSync6, openSync as openSync6, readSync as readSync2, readdirSync as readdirSync4, realpathSync, statSync as statSync5 } from "fs";
49069
- import { homedir as homedir27 } from "os";
49070
- import { basename, join as join27 } from "path";
49480
+ import { homedir as homedir28 } from "os";
49481
+ import { basename, join as join28 } from "path";
49071
49482
  function slugForPath(absPath) {
49072
49483
  return absPath.replace(/[/.]/g, "-");
49073
49484
  }
@@ -49076,7 +49487,7 @@ function transcriptPathFor(cwd, sessionUuid) {
49076
49487
  try {
49077
49488
  real = realpathSync(cwd);
49078
49489
  } catch {}
49079
- return join27(PROJECTS_DIR, slugForPath(real), `${sessionUuid}.jsonl`);
49490
+ return join28(PROJECTS_DIR, slugForPath(real), `${sessionUuid}.jsonl`);
49080
49491
  }
49081
49492
  function isAgentSession(row) {
49082
49493
  return row.entrypoint !== undefined && row.entrypoint !== "cli";
@@ -49123,7 +49534,7 @@ function projectDirs() {
49123
49534
  }
49124
49535
  }
49125
49536
  function sessionsIn(dirName) {
49126
- const dir = join27(PROJECTS_DIR, dirName);
49537
+ const dir = join28(PROJECTS_DIR, dirName);
49127
49538
  let names;
49128
49539
  try {
49129
49540
  names = readdirSync4(dir).filter((n) => n.endsWith(".jsonl"));
@@ -49132,7 +49543,7 @@ function sessionsIn(dirName) {
49132
49543
  }
49133
49544
  const rows = [];
49134
49545
  for (const n of names) {
49135
- const file2 = join27(dir, n);
49546
+ const file2 = join28(dir, n);
49136
49547
  try {
49137
49548
  const st = statSync5(file2);
49138
49549
  if (st.size === 0)
@@ -49491,7 +49902,7 @@ function findLatestSessionId(cwd = process.cwd(), sinceMs = 0) {
49491
49902
  }
49492
49903
  var ENTRYPOINT_BYTES = 8192, PROJECTS_DIR, ACTIVE_WINDOW_MS = 120000, HEAD_BYTES, TAIL_BYTES, HARNESS_ENVELOPES, DEEP_TAIL_BYTES, RECENT_AI_TURNS = 5, RECENT_USER_TURNS = 1;
49493
49904
  var init_session_discovery = __esm(() => {
49494
- PROJECTS_DIR = join27(homedir27(), ".claude", "projects");
49905
+ PROJECTS_DIR = join28(homedir28(), ".claude", "projects");
49495
49906
  HEAD_BYTES = 64 * 1024;
49496
49907
  TAIL_BYTES = 128 * 1024;
49497
49908
  HARNESS_ENVELOPES = [
@@ -49526,19 +49937,19 @@ function newStdioDecoder() {
49526
49937
  var init_stdio_decode = () => {};
49527
49938
 
49528
49939
  // src/team-stats.ts
49529
- import { existsSync as existsSync19, readFileSync as readFileSync18, writeFileSync as writeFileSync10 } from "fs";
49530
- import { join as join28 } from "path";
49940
+ import { existsSync as existsSync20, readFileSync as readFileSync19, writeFileSync as writeFileSync11 } from "fs";
49941
+ import { join as join29 } from "path";
49531
49942
  function statsDir(sessionPath) {
49532
- return join28(sessionPath, "stats");
49943
+ return join29(sessionPath, "stats");
49533
49944
  }
49534
49945
  function tokenFileFor(sessionPath, anonId) {
49535
- return join28(statsDir(sessionPath), `${anonId}.json`);
49946
+ return join29(statsDir(sessionPath), `${anonId}.json`);
49536
49947
  }
49537
49948
  function readTokenStatsAt(path) {
49538
- if (!existsSync19(path))
49949
+ if (!existsSync20(path))
49539
49950
  return null;
49540
49951
  try {
49541
- return JSON.parse(readFileSync18(path, "utf-8"));
49952
+ return JSON.parse(readFileSync19(path, "utf-8"));
49542
49953
  } catch {
49543
49954
  return null;
49544
49955
  }
@@ -49689,7 +50100,7 @@ ${segs.join(" \xB7 ")}`;
49689
50100
  }
49690
50101
  function writeStatusFile(sessionPath, manifest, status, opts) {
49691
50102
  try {
49692
- writeFileSync10(join28(sessionPath, "status.txt"), `${renderTeamStats(sessionPath, manifest, status, opts)}
50103
+ writeFileSync11(join29(sessionPath, "status.txt"), `${renderTeamStats(sessionPath, manifest, status, opts)}
49693
50104
  `, "utf-8");
49694
50105
  } catch {}
49695
50106
  }
@@ -49724,13 +50135,13 @@ __export(exports_team_orchestrator, {
49724
50135
  import { spawn as spawn2 } from "child_process";
49725
50136
  import {
49726
50137
  createWriteStream,
49727
- existsSync as existsSync20,
49728
- mkdirSync as mkdirSync11,
49729
- readFileSync as readFileSync19,
50138
+ existsSync as existsSync21,
50139
+ mkdirSync as mkdirSync12,
50140
+ readFileSync as readFileSync20,
49730
50141
  readdirSync as readdirSync5,
49731
- writeFileSync as writeFileSync11
50142
+ writeFileSync as writeFileSync12
49732
50143
  } from "fs";
49733
- import { basename as basename2, join as join29, resolve as resolve3 } from "path";
50144
+ import { basename as basename2, join as join30, resolve as resolve3 } from "path";
49734
50145
  function resolveCaptureMode(explicit, env = process.env) {
49735
50146
  if (explicit)
49736
50147
  return explicit;
@@ -49848,7 +50259,7 @@ function persistErrorLog(errorLogPath, header, stderr, stdoutTail) {
49848
50259
  parts.push("--- stderr ---", stderr.trim() ? redactSecrets(stderr) : "(empty)", "");
49849
50260
  parts.push("--- stdout (tail) ---", stdoutTail.trim() ? redactSecrets(stdoutTail) : "(empty)", "");
49850
50261
  try {
49851
- writeFileSync11(errorLogPath, parts.join(`
50262
+ writeFileSync12(errorLogPath, parts.join(`
49852
50263
  `), "utf-8");
49853
50264
  } catch {}
49854
50265
  }
@@ -49866,10 +50277,10 @@ function readTeamInputFile(inputPath) {
49866
50277
  if (!resolved.startsWith(`${cwd}/`) && resolved !== cwd) {
49867
50278
  throw new Error(`Input file must be within current directory: ${inputPath}`);
49868
50279
  }
49869
- if (!existsSync20(resolved)) {
50280
+ if (!existsSync21(resolved)) {
49870
50281
  throw new Error(`Input file not found: ${resolved}`);
49871
50282
  }
49872
- const text = readFileSync19(resolved, "utf-8");
50283
+ const text = readFileSync20(resolved, "utf-8");
49873
50284
  if (text.trim().length === 0) {
49874
50285
  throw new Error(`Input file is empty: ${resolved}`);
49875
50286
  }
@@ -49879,14 +50290,14 @@ function setupSession(sessionPath, models, input) {
49879
50290
  if (models.length === 0) {
49880
50291
  throw new Error("At least one model is required");
49881
50292
  }
49882
- if (existsSync20(join29(sessionPath, "manifest.json"))) {
50293
+ if (existsSync21(join30(sessionPath, "manifest.json"))) {
49883
50294
  throw new Error(`Session already exists at ${sessionPath}. Use a new directory path or delete the existing session first.`);
49884
50295
  }
49885
- mkdirSync11(join29(sessionPath, "work"), { recursive: true });
49886
- mkdirSync11(join29(sessionPath, "errors"), { recursive: true });
50296
+ mkdirSync12(join30(sessionPath, "work"), { recursive: true });
50297
+ mkdirSync12(join30(sessionPath, "errors"), { recursive: true });
49887
50298
  if (input !== undefined) {
49888
- writeFileSync11(join29(sessionPath, "input.md"), input, "utf-8");
49889
- } else if (!existsSync20(join29(sessionPath, "input.md"))) {
50299
+ writeFileSync12(join30(sessionPath, "input.md"), input, "utf-8");
50300
+ } else if (!existsSync21(join30(sessionPath, "input.md"))) {
49890
50301
  throw new Error(`No input.md found at ${sessionPath} and no input provided`);
49891
50302
  }
49892
50303
  const ids = models.map((_, i) => String(i + 1).padStart(2, "0"));
@@ -49903,9 +50314,9 @@ function setupSession(sessionPath, models, input) {
49903
50314
  model: models[i],
49904
50315
  assignedAt: now2
49905
50316
  };
49906
- mkdirSync11(join29(sessionPath, "work", anonId), { recursive: true });
50317
+ mkdirSync12(join30(sessionPath, "work", anonId), { recursive: true });
49907
50318
  }
49908
- writeFileSync11(join29(sessionPath, "manifest.json"), JSON.stringify(manifest, null, 2), "utf-8");
50319
+ writeFileSync12(join30(sessionPath, "manifest.json"), JSON.stringify(manifest, null, 2), "utf-8");
49909
50320
  const status = {
49910
50321
  startedAt: now2,
49911
50322
  models: Object.fromEntries(Object.keys(manifest.models).map((id) => [
@@ -49919,7 +50330,7 @@ function setupSession(sessionPath, models, input) {
49919
50330
  }
49920
50331
  ]))
49921
50332
  };
49922
- writeFileSync11(join29(sessionPath, "status.json"), JSON.stringify(status, null, 2), "utf-8");
50333
+ writeFileSync12(join30(sessionPath, "status.json"), JSON.stringify(status, null, 2), "utf-8");
49923
50334
  return manifest;
49924
50335
  }
49925
50336
  function assertValidRequirePattern(pattern) {
@@ -49936,22 +50347,22 @@ function readFullOutputIfNeeded(opts) {
49936
50347
  if (crashed || !requirePattern || outputSize <= STDOUT_TAIL_LIMIT)
49937
50348
  return;
49938
50349
  try {
49939
- return readFileSync19(outputPath, "utf-8");
50350
+ return readFileSync20(outputPath, "utf-8");
49940
50351
  } catch {
49941
50352
  return;
49942
50353
  }
49943
50354
  }
49944
50355
  async function startModels(sessionPath, opts = {}) {
49945
50356
  assertValidRequirePattern(opts.requirePattern);
49946
- const manifest = JSON.parse(readFileSync19(join29(sessionPath, "manifest.json"), "utf-8"));
49947
- const statusPath = join29(sessionPath, "status.json");
49948
- const inputPath = join29(sessionPath, "input.md");
49949
- const inputContent = readFileSync19(inputPath, "utf-8");
50357
+ const manifest = JSON.parse(readFileSync20(join30(sessionPath, "manifest.json"), "utf-8"));
50358
+ const statusPath = join30(sessionPath, "status.json");
50359
+ const inputPath = join30(sessionPath, "input.md");
50360
+ const inputContent = readFileSync20(inputPath, "utf-8");
49950
50361
  const spawnPlan = await (opts.spawnPlanner ?? prehydrateCredentialsForSpawn)(Object.values(manifest.models).map((m) => m.model));
49951
- const statusCache = JSON.parse(readFileSync19(statusPath, "utf-8"));
50362
+ const statusCache = JSON.parse(readFileSync20(statusPath, "utf-8"));
49952
50363
  function updateModelStatus(id, update) {
49953
50364
  statusCache.models[id] = { ...statusCache.models[id], ...update };
49954
- writeFileSync11(statusPath, JSON.stringify(statusCache, null, 2), "utf-8");
50365
+ writeFileSync12(statusPath, JSON.stringify(statusCache, null, 2), "utf-8");
49955
50366
  }
49956
50367
  const minOutputBytes = opts.minOutputBytes ?? DEFAULT_MIN_OUTPUT_BYTES;
49957
50368
  const requirePattern = opts.requirePattern;
@@ -49984,7 +50395,7 @@ async function startModels(sessionPath, opts = {}) {
49984
50395
  persistErrorLog(rt.errorLogPath, `RECOVERED: ${note}`, stderr, stdoutTail);
49985
50396
  opts.onStatusChange?.(id, statusCache.models[id]);
49986
50397
  }
49987
- mkdirSync11(statsDir(sessionPath), { recursive: true });
50398
+ mkdirSync12(statsDir(sessionPath), { recursive: true });
49988
50399
  const processes = new Map;
49989
50400
  const runtimes = new Map;
49990
50401
  const cancelledSlots = new Set;
@@ -49997,9 +50408,9 @@ async function startModels(sessionPath, opts = {}) {
49997
50408
  process.on("SIGINT", sigintHandler);
49998
50409
  const completionPromises = [];
49999
50410
  for (const [anonId, entry] of Object.entries(manifest.models)) {
50000
- const outputPath = join29(sessionPath, `response-${anonId}.md`);
50001
- const errorLogPath = join29(sessionPath, "errors", `${anonId}.log`);
50002
- const upstreamErrorLogPath = join29(sessionPath, "errors", `${anonId}-upstream.jsonl`);
50411
+ const outputPath = join30(sessionPath, `response-${anonId}.md`);
50412
+ const errorLogPath = join30(sessionPath, "errors", `${anonId}.log`);
50413
+ const upstreamErrorLogPath = join30(sessionPath, "errors", `${anonId}-upstream.jsonl`);
50003
50414
  const spawnModel = spawnPlan.pinned.get(entry.model) ?? entry.model;
50004
50415
  const args = [
50005
50416
  "--model",
@@ -50139,7 +50550,7 @@ async function startModels(sessionPath, opts = {}) {
50139
50550
  stderrSnippet: stderr ? redactSecrets(stderr).slice(-2000) : undefined,
50140
50551
  stdoutSnippet: stdoutTail ? redactSecrets(stdoutTail).slice(-2000) : undefined,
50141
50552
  errorLogPath,
50142
- upstreamErrorLogPath: existsSync20(upstreamErrorLogPath) ? upstreamErrorLogPath : undefined,
50553
+ upstreamErrorLogPath: existsSync21(upstreamErrorLogPath) ? upstreamErrorLogPath : undefined,
50143
50554
  workDir: sessionPath
50144
50555
  }
50145
50556
  });
@@ -50159,7 +50570,7 @@ async function startModels(sessionPath, opts = {}) {
50159
50570
  proc.on("exit", (code) => {
50160
50571
  const timedOut = statusCache.models[anonId]?.state === "TIMEOUT";
50161
50572
  if (!timedOut && meaningfulStderr(stderr)) {
50162
- writeFileSync11(errorLogPath, redactSecrets(stderr), "utf-8");
50573
+ writeFileSync12(errorLogPath, redactSecrets(stderr), "utf-8");
50163
50574
  }
50164
50575
  exitCode = code;
50165
50576
  if (outputStream.destroyed) {
@@ -50239,23 +50650,23 @@ async function judgeResponses(sessionPath, opts = {}) {
50239
50650
  const responses = {};
50240
50651
  for (const file2 of responseFiles) {
50241
50652
  const id = file2.replace(/^response-/, "").replace(/\.md$/, "");
50242
- responses[id] = readFileSync19(join29(sessionPath, file2), "utf-8");
50653
+ responses[id] = readFileSync20(join30(sessionPath, file2), "utf-8");
50243
50654
  }
50244
- const input = readFileSync19(join29(sessionPath, "input.md"), "utf-8");
50655
+ const input = readFileSync20(join30(sessionPath, "input.md"), "utf-8");
50245
50656
  const judgePrompt = buildJudgePrompt(input, responses);
50246
- writeFileSync11(join29(sessionPath, "judge-prompt.md"), judgePrompt, "utf-8");
50657
+ writeFileSync12(join30(sessionPath, "judge-prompt.md"), judgePrompt, "utf-8");
50247
50658
  const judgeModels = opts.judges ?? getDefaultJudgeModels(sessionPath);
50248
- const judgePath = join29(sessionPath, "judging");
50249
- mkdirSync11(judgePath, { recursive: true });
50659
+ const judgePath = join30(sessionPath, "judging");
50660
+ mkdirSync12(judgePath, { recursive: true });
50250
50661
  setupSession(judgePath, judgeModels, judgePrompt);
50251
50662
  await runModels(judgePath, { claudeFlags: opts.claudeFlags });
50252
50663
  const votes = parseJudgeVotes(judgePath, Object.keys(responses));
50253
50664
  const verdict = aggregateVerdict(votes, Object.keys(responses));
50254
- writeFileSync11(join29(sessionPath, "verdict.md"), formatVerdict(verdict, sessionPath), "utf-8");
50665
+ writeFileSync12(join30(sessionPath, "verdict.md"), formatVerdict(verdict, sessionPath), "utf-8");
50255
50666
  return verdict;
50256
50667
  }
50257
50668
  function getStatus(sessionPath) {
50258
- return JSON.parse(readFileSync19(join29(sessionPath, "status.json"), "utf-8"));
50669
+ return JSON.parse(readFileSync20(join30(sessionPath, "status.json"), "utf-8"));
50259
50670
  }
50260
50671
  function fisherYatesShuffle(arr) {
50261
50672
  for (let i = arr.length - 1;i > 0; i--) {
@@ -50265,7 +50676,7 @@ function fisherYatesShuffle(arr) {
50265
50676
  return arr;
50266
50677
  }
50267
50678
  function getDefaultJudgeModels(sessionPath) {
50268
- const manifest = JSON.parse(readFileSync19(join29(sessionPath, "manifest.json"), "utf-8"));
50679
+ const manifest = JSON.parse(readFileSync20(join30(sessionPath, "manifest.json"), "utf-8"));
50269
50680
  return Object.values(manifest.models).map((e) => e.model);
50270
50681
  }
50271
50682
  function buildJudgePrompt(input, responses) {
@@ -50328,7 +50739,7 @@ function parseJudgeVotes(judgePath, responseIds) {
50328
50739
  const judgeId = file2.replace(/^response-/, "").replace(/\.md$/, "");
50329
50740
  let content;
50330
50741
  try {
50331
- content = readFileSync19(join29(judgePath, file2), "utf-8");
50742
+ content = readFileSync20(join30(judgePath, file2), "utf-8");
50332
50743
  } catch {
50333
50744
  continue;
50334
50745
  }
@@ -50380,7 +50791,7 @@ function aggregateVerdict(votes, responseIds) {
50380
50791
  function formatVerdict(verdict, sessionPath) {
50381
50792
  let manifest = null;
50382
50793
  try {
50383
- manifest = JSON.parse(readFileSync19(join29(sessionPath, "manifest.json"), "utf-8"));
50794
+ manifest = JSON.parse(readFileSync20(join30(sessionPath, "manifest.json"), "utf-8"));
50384
50795
  } catch {}
50385
50796
  let output = `# Team Verdict
50386
50797
 
@@ -50434,15 +50845,15 @@ import {
50434
50845
  appendFileSync as appendFileSync6,
50435
50846
  closeSync as closeSync7,
50436
50847
  createWriteStream as createWriteStream2,
50437
- mkdirSync as mkdirSync12,
50848
+ mkdirSync as mkdirSync13,
50438
50849
  openSync as openSync7,
50439
- readFileSync as readFileSync20,
50850
+ readFileSync as readFileSync21,
50440
50851
  readSync as readSync3,
50441
50852
  statSync as statSync6,
50442
- writeFileSync as writeFileSync12
50853
+ writeFileSync as writeFileSync13
50443
50854
  } from "fs";
50444
- import { homedir as homedir28 } from "os";
50445
- import { join as join30, resolve as resolve4, sep } from "path";
50855
+ import { homedir as homedir29 } from "os";
50856
+ import { join as join31, resolve as resolve4, sep } from "path";
50446
50857
  import { StringDecoder as StringDecoder2 } from "string_decoder";
50447
50858
  function buildChannelSpawnArgs(opts) {
50448
50859
  return [
@@ -50517,7 +50928,7 @@ function readJsonObject(path, maxBytes) {
50517
50928
  try {
50518
50929
  if (fileSize(path) > maxBytes)
50519
50930
  return null;
50520
- const parsed = JSON.parse(readFileSync20(path, "utf-8"));
50931
+ const parsed = JSON.parse(readFileSync21(path, "utf-8"));
50521
50932
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
50522
50933
  return null;
50523
50934
  return parsed;
@@ -50533,7 +50944,7 @@ function dropLeadingFragment(tail) {
50533
50944
  return firstBreak === -1 ? tail.text : tail.text.slice(firstBreak + 1);
50534
50945
  }
50535
50946
  function diskAccounting(sessionDir) {
50536
- const stats = readTokenStatsAt(join30(sessionDir, "tokens.json"));
50947
+ const stats = readTokenStatsAt(join31(sessionDir, "tokens.json"));
50537
50948
  return {
50538
50949
  tokensUsed: (stats?.total_tokens ?? 0) || (stats?.input_tokens ?? 0) + (stats?.output_tokens ?? 0),
50539
50950
  costUsd: stats?.total_cost ?? 0,
@@ -50573,7 +50984,7 @@ class SessionManager {
50573
50984
  this.maxSessions = options?.maxSessions ?? DEFAULT_MAX_SESSIONS;
50574
50985
  this.scrollbackCapacity = options?.scrollbackCapacity ?? DEFAULT_SCROLLBACK;
50575
50986
  this.terminalRetentionMs = options?.terminalRetentionMs ?? TERMINAL_RETENTION_MS;
50576
- this.sessionsDir = options?.sessionsDir ?? process.env.CLAUDISH_SESSIONS_DIR ?? join30(homedir28(), ".claudish", "sessions");
50987
+ this.sessionsDir = options?.sessionsDir ?? process.env.CLAUDISH_SESSIONS_DIR ?? join31(homedir29(), ".claudish", "sessions");
50577
50988
  this.stallSeconds = options?.stallSeconds;
50578
50989
  this.onStateChange = options?.onStateChange;
50579
50990
  }
@@ -50589,19 +51000,19 @@ class SessionManager {
50589
51000
  const claudeSessionId = randomUUID4();
50590
51001
  const timeout = Math.min(opts.timeoutSeconds ?? DEFAULT_TIMEOUT, MAX_TIMEOUT);
50591
51002
  const startedAt = new Date().toISOString();
50592
- const sessionDir = opts.sessionDir ?? join30(this.sessionsDir, sessionId2);
50593
- mkdirSync12(sessionDir, { recursive: true });
51003
+ const sessionDir = opts.sessionDir ?? join31(this.sessionsDir, sessionId2);
51004
+ mkdirSync13(sessionDir, { recursive: true });
50594
51005
  if (opts.prompt) {
50595
- writeFileSync12(join30(sessionDir, "prompt.md"), opts.prompt, "utf-8");
51006
+ writeFileSync13(join31(sessionDir, "prompt.md"), opts.prompt, "utf-8");
50596
51007
  }
50597
51008
  const args = buildChannelSpawnArgs({
50598
51009
  model: opts.spawnModel ?? opts.model,
50599
51010
  claudeSessionId,
50600
51011
  claudishFlags: opts.claudishFlags
50601
51012
  });
50602
- const tokenFile = opts.tokenFile ?? join30(sessionDir, "tokens.json");
50603
- const eventLogPath = join30(sessionDir, "events.jsonl");
50604
- const upstreamErrorLogPath = join30(sessionDir, "upstream-errors.jsonl");
51013
+ const tokenFile = opts.tokenFile ?? join31(sessionDir, "tokens.json");
51014
+ const eventLogPath = join31(sessionDir, "events.jsonl");
51015
+ const upstreamErrorLogPath = join31(sessionDir, "upstream-errors.jsonl");
50605
51016
  const cwd = opts.cwd ?? process.cwd();
50606
51017
  const spawnTarget = resolveClaudishSpawn();
50607
51018
  const proc = spawn3(spawnTarget.command, [...spawnTarget.prefixArgs, ...args], {
@@ -50616,7 +51027,7 @@ class SessionManager {
50616
51027
  }
50617
51028
  });
50618
51029
  const scrollback = new ScrollbackBuffer(this.scrollbackCapacity);
50619
- const outputLogStream = createWriteStream2(join30(sessionDir, "output.log"));
51030
+ const outputLogStream = createWriteStream2(join31(sessionDir, "output.log"));
50620
51031
  const entry = {
50621
51032
  info: {
50622
51033
  sessionId: sessionId2,
@@ -50908,7 +51319,7 @@ class SessionManager {
50908
51319
  return null;
50909
51320
  const root = resolve4(this.sessionsDir);
50910
51321
  const dir = resolve4(root, sessionId2);
50911
- if (dir !== join30(root, sessionId2))
51322
+ if (dir !== join31(root, sessionId2))
50912
51323
  return null;
50913
51324
  if (!dir.startsWith(root + sep))
50914
51325
  return null;
@@ -50927,7 +51338,7 @@ class SessionManager {
50927
51338
  } catch {
50928
51339
  return null;
50929
51340
  }
50930
- const meta3 = readJsonObject(join30(sessionDir, "meta.json"), META_READ_LIMIT);
51341
+ const meta3 = readJsonObject(join31(sessionDir, "meta.json"), META_READ_LIMIT);
50931
51342
  const partial2 = meta3 === null;
50932
51343
  const measured = diskAccounting(sessionDir);
50933
51344
  const startedAt = metaString(meta3?.startedAt) ?? new Date(dirMtimeMs).toISOString();
@@ -50957,7 +51368,7 @@ class SessionManager {
50957
51368
  };
50958
51369
  }
50959
51370
  diskOutput(record4, tailLines) {
50960
- const tail = readTailText(join30(record4.sessionDir, "output.log"), OUTPUT_TAIL_BYTES);
51371
+ const tail = readTailText(join31(record4.sessionDir, "output.log"), OUTPUT_TAIL_BYTES);
50961
51372
  const buffer = new ScrollbackBuffer(this.scrollbackCapacity);
50962
51373
  if (tail?.text)
50963
51374
  buffer.append(dropLeadingFragment(tail));
@@ -50976,9 +51387,9 @@ class SessionManager {
50976
51387
  }
50977
51388
  diskDiagnostics(record4, limit) {
50978
51389
  const { sessionDir, info } = record4;
50979
- const eventLogPath = join30(sessionDir, "events.jsonl");
50980
- const upstreamErrorLogPath = join30(sessionDir, "upstream-errors.jsonl");
50981
- const outputLogPath = join30(sessionDir, "output.log");
51390
+ const eventLogPath = join31(sessionDir, "events.jsonl");
51391
+ const upstreamErrorLogPath = join31(sessionDir, "upstream-errors.jsonl");
51392
+ const outputLogPath = join31(sessionDir, "output.log");
50982
51393
  const events = readTailLines(eventLogPath, EVENT_TAIL_BYTES);
50983
51394
  const outputTail = readTailText(outputLogPath, OUTPUT_TAIL_BYTES);
50984
51395
  return {
@@ -51020,7 +51431,7 @@ class SessionManager {
51020
51431
  };
51021
51432
  }
51022
51433
  diskStderrForDiagnostics(record4) {
51023
- const tail = readTailText(join30(record4.sessionDir, "stderr.log"), STDERR_READ_BYTES);
51434
+ const tail = readTailText(join31(record4.sessionDir, "stderr.log"), STDERR_READ_BYTES);
51024
51435
  const raw = tail?.text ?? "";
51025
51436
  const filtered = record4.info.status === "completed";
51026
51437
  const source = filtered ? meaningfulStderr(raw) : raw;
@@ -51196,11 +51607,11 @@ ${STDERR_TRUNCATION_MARKER} ${STDERR_SIDE_LIMIT} bytes per end \u2026
51196
51607
  entry.outputLogStream?.end();
51197
51608
  entry.outputLogStream = null;
51198
51609
  if (entry.stderr) {
51199
- writeFileSync12(join30(entry.sessionDir, "stderr.log"), redactSecrets(entry.stderr), "utf-8");
51610
+ writeFileSync13(join31(entry.sessionDir, "stderr.log"), redactSecrets(entry.stderr), "utf-8");
51200
51611
  }
51201
51612
  this.refreshAccounting(entry);
51202
51613
  entry.info.claudeSessionId = entry.reducer.claudeSessionId ?? entry.info.claudeSessionId;
51203
- writeFileSync12(join30(entry.sessionDir, "meta.json"), JSON.stringify(entry.info, null, 2), "utf-8");
51614
+ writeFileSync13(join31(entry.sessionDir, "meta.json"), JSON.stringify(entry.info, null, 2), "utf-8");
51204
51615
  }
51205
51616
  scheduleEviction(entry) {
51206
51617
  if (entry.evictHandle)
@@ -51260,7 +51671,7 @@ ${STDERR_TRUNCATION_MARKER} ${STDERR_SIDE_LIMIT} bytes per end \u2026
51260
51671
  return { state: "completed", content: "" };
51261
51672
  }
51262
51673
  refreshAccounting(entry) {
51263
- const stats = readTokenStatsAt(join30(entry.sessionDir, "tokens.json"));
51674
+ const stats = readTokenStatsAt(join31(entry.sessionDir, "tokens.json"));
51264
51675
  const fileTokens = (stats?.total_tokens ?? 0) || (stats?.input_tokens ?? 0) + (stats?.output_tokens ?? 0);
51265
51676
  entry.info.tokensUsed = fileTokens || entry.reducer.tokens;
51266
51677
  entry.info.costUsd = stats?.total_cost ?? 0;
@@ -51451,309 +51862,6 @@ var init_progress_heartbeat = __esm(() => {
51451
51862
  });
51452
51863
  });
51453
51864
 
51454
- // src/providers/cache-ttl.ts
51455
- var FIREBASE_CACHE_TTL_HOURS = 24, FIREBASE_CACHE_TTL_MS;
51456
- var init_cache_ttl = __esm(() => {
51457
- FIREBASE_CACHE_TTL_MS = FIREBASE_CACHE_TTL_HOURS * 60 * 60 * 1000;
51458
- });
51459
-
51460
- // src/model-loader.ts
51461
- import { existsSync as existsSync21, mkdirSync as mkdirSync13, readFileSync as readFileSync21, writeFileSync as writeFileSync13 } from "fs";
51462
- import { homedir as homedir29 } from "os";
51463
- import { join as join31 } from "path";
51464
- function groupRecommendedModels(entries) {
51465
- const byId = new Map;
51466
- const categoryOrder = new Map;
51467
- for (const entry of entries) {
51468
- const list = byId.get(entry.id);
51469
- if (list)
51470
- list.push(entry);
51471
- else
51472
- byId.set(entry.id, [entry]);
51473
- if (!categoryOrder.has(entry.category))
51474
- categoryOrder.set(entry.category, categoryOrder.size);
51475
- }
51476
- const flagship = [];
51477
- const fast = [];
51478
- for (const [id, members] of byId.entries()) {
51479
- const primary = members.find((m) => m.category !== "subscription") ?? members[0];
51480
- const subscriptions = members.filter((m) => m.category === "subscription");
51481
- const bucket = primary.category === "programming" || primary.category === "vision" || primary.category === "reasoning" ? "flagship" : "fast";
51482
- const group = { id, primary, subscriptions, bucket };
51483
- if (bucket === "flagship")
51484
- flagship.push(group);
51485
- else
51486
- fast.push(group);
51487
- }
51488
- const byCuratedPriorityThenFreshness = (a, b) => {
51489
- const aCat = categoryOrder.get(a.primary.category) ?? Number.MAX_SAFE_INTEGER;
51490
- const bCat = categoryOrder.get(b.primary.category) ?? Number.MAX_SAFE_INTEGER;
51491
- if (aCat !== bCat)
51492
- return aCat - bCat;
51493
- if (a.primary.priority !== b.primary.priority)
51494
- return a.primary.priority - b.primary.priority;
51495
- return compareByReleaseDateDesc(a.primary, b.primary);
51496
- };
51497
- flagship.sort(byCuratedPriorityThenFreshness);
51498
- fast.sort(byCuratedPriorityThenFreshness);
51499
- return { flagship, fast };
51500
- }
51501
- function collectRoutingPrefixes(group, getNativePrefix) {
51502
- const slug = (group.primary.provider || "").toLowerCase();
51503
- const native = getNativePrefix(slug);
51504
- const seen = new Set;
51505
- const out = [];
51506
- if (native) {
51507
- out.push(native);
51508
- seen.add(native);
51509
- }
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
- }
51519
- }
51520
- return out;
51521
- }
51522
- function parsePriceAvg(s) {
51523
- if (!s || s === "N/A")
51524
- return Number.POSITIVE_INFINITY;
51525
- if (s === "FREE")
51526
- return 0;
51527
- const m = s.match(/\$([\d.]+)/);
51528
- return m ? Number.parseFloat(m[1]) : Number.POSITIVE_INFINITY;
51529
- }
51530
- function parseCtx(s) {
51531
- if (!s || s === "N/A")
51532
- return 0;
51533
- const upper = s.toUpperCase();
51534
- if (upper.includes("M"))
51535
- return Number.parseFloat(upper) * 1e6;
51536
- if (upper.includes("K"))
51537
- return Number.parseFloat(upper) * 1000;
51538
- return Number.parseInt(s, 10) || 0;
51539
- }
51540
- function normalizePricingDisplay(raw) {
51541
- const pricing = raw || "N/A";
51542
- if (pricing.includes("-1000000"))
51543
- return "varies";
51544
- if (pricing === "$0.00/1M" || pricing === "FREE")
51545
- return "FREE";
51546
- return pricing;
51547
- }
51548
- function formatListingPrice(entry, opts) {
51549
- const rate = normalizePricingDisplay(entry.pricing?.average);
51550
- if (rate !== "N/A")
51551
- return rate;
51552
- const plan = entry.subscription?.plan;
51553
- if (!plan)
51554
- return "N/A";
51555
- return opts?.compact ? "SUB" : `SUB (${plan})`;
51556
- }
51557
- function computeQuickPicks(primaries) {
51558
- if (primaries.length === 0) {
51559
- return {
51560
- budget: null,
51561
- largeContext: null,
51562
- mostCapable: null,
51563
- visionCoding: null,
51564
- agentic: null
51565
- };
51566
- }
51567
- const priced = primaries.filter((m) => {
51568
- const p = parsePriceAvg(m.pricing?.average);
51569
- return p > 0 && p !== Number.POSITIVE_INFINITY;
51570
- }).sort((a, b) => parsePriceAvg(a.pricing?.average) - parsePriceAvg(b.pricing?.average));
51571
- const budget = priced[0] ?? null;
51572
- const byCtx = [...primaries].sort((a, b) => parseCtx(b.context) - parseCtx(a.context));
51573
- const largeContext = byCtx[0] ?? null;
51574
- const byPrice = [...primaries].sort((a, b) => parsePriceAvg(b.pricing?.average) - parsePriceAvg(a.pricing?.average));
51575
- const mostCapable = byPrice.find((m) => parsePriceAvg(m.pricing?.average) !== Number.POSITIVE_INFINITY) ?? null;
51576
- const visionCoding = primaries.find((m) => m.supportsVision === true && m.id !== budget?.id && m.id !== mostCapable?.id) ?? null;
51577
- const agentic = primaries.find((m) => m.supportsReasoning === true && m.id !== mostCapable?.id) ?? null;
51578
- return { budget, largeContext, mostCapable, visionCoding, agentic };
51579
- }
51580
- async function getRecommendedModels(opts = {}) {
51581
- const { forceRefresh = false } = opts;
51582
- if (!forceRefresh && _cachedRecommendedModels) {
51583
- return _cachedRecommendedModels;
51584
- }
51585
- if (!forceRefresh && existsSync21(RECOMMENDED_MODELS_CACHE_PATH)) {
51586
- try {
51587
- const cacheData = JSON.parse(readFileSync21(RECOMMENDED_MODELS_CACHE_PATH, "utf-8"));
51588
- if (cacheData.models && cacheData.models.length > 0 && isFreshEnough(cacheData)) {
51589
- _cachedRecommendedModels = cacheData;
51590
- return cacheData;
51591
- }
51592
- } catch {}
51593
- }
51594
- try {
51595
- const response = await fetch(FIREBASE_RECOMMENDED_URL, {
51596
- signal: AbortSignal.timeout(RECOMMENDED_FETCH_TIMEOUT_MS)
51597
- });
51598
- if (response.ok) {
51599
- const data = await response.json();
51600
- if (data.models && data.models.length > 0) {
51601
- _cachedRecommendedModels = data;
51602
- try {
51603
- const cacheDir = join31(homedir29(), ".claudish");
51604
- mkdirSync13(cacheDir, { recursive: true });
51605
- writeFileSync13(RECOMMENDED_MODELS_CACHE_PATH, JSON.stringify(data), "utf-8");
51606
- } catch {}
51607
- return data;
51608
- }
51609
- }
51610
- } catch {}
51611
- throw new Error("Unable to load recommended models: Firebase unreachable and no local cache. " + "Check connectivity.");
51612
- }
51613
- function getRecommendedModelsSync() {
51614
- if (_cachedRecommendedModels)
51615
- return _cachedRecommendedModels;
51616
- if (existsSync21(RECOMMENDED_MODELS_CACHE_PATH)) {
51617
- try {
51618
- const cacheData = JSON.parse(readFileSync21(RECOMMENDED_MODELS_CACHE_PATH, "utf-8"));
51619
- if (cacheData.models && cacheData.models.length > 0 && isFreshEnough(cacheData)) {
51620
- _cachedRecommendedModels = cacheData;
51621
- return cacheData;
51622
- }
51623
- } catch {}
51624
- }
51625
- return { version: "0", lastUpdated: "", models: [] };
51626
- }
51627
- async function warmRecommendedModels() {
51628
- try {
51629
- return await getRecommendedModels({ forceRefresh: true });
51630
- } catch {
51631
- return null;
51632
- }
51633
- }
51634
- function isFreshEnough(doc2) {
51635
- const generatedAt = doc2.generatedAt;
51636
- if (!generatedAt)
51637
- return true;
51638
- const ageHours = (Date.now() - new Date(generatedAt).getTime()) / (1000 * 60 * 60);
51639
- return ageHours <= FIREBASE_CACHE_TTL_HOURS;
51640
- }
51641
- async function searchModels(query, limit = 50) {
51642
- const url2 = `${FIREBASE_BASE_URL}?search=${encodeURIComponent(query)}&limit=${limit}&status=active`;
51643
- const response = await fetch(url2, {
51644
- signal: AbortSignal.timeout(SEARCH_FETCH_TIMEOUT_MS)
51645
- });
51646
- if (!response.ok) {
51647
- throw new Error(`Firebase search returned ${response.status} ${response.statusText}`);
51648
- }
51649
- const data = await response.json();
51650
- return data.models ?? [];
51651
- }
51652
- async function getModelByIdFromFirebase(modelId) {
51653
- const url2 = `${FIREBASE_BASE_URL}?search=${encodeURIComponent(modelId)}&limit=5`;
51654
- const response = await fetch(url2, {
51655
- signal: AbortSignal.timeout(SEARCH_FETCH_TIMEOUT_MS)
51656
- });
51657
- if (!response.ok) {
51658
- throw new Error(`Firebase lookup returned ${response.status} ${response.statusText}`);
51659
- }
51660
- const data = await response.json();
51661
- const models = data.models ?? [];
51662
- for (const m of models) {
51663
- if (m.modelId === modelId)
51664
- return m;
51665
- if (m.aliases?.includes(modelId))
51666
- return m;
51667
- }
51668
- return null;
51669
- }
51670
- async function getTop100Models() {
51671
- const url2 = `${FIREBASE_BASE_URL}?catalog=top100`;
51672
- const response = await fetch(url2, {
51673
- signal: AbortSignal.timeout(SEARCH_FETCH_TIMEOUT_MS)
51674
- });
51675
- if (!response.ok) {
51676
- throw new Error(`Firebase top100 fetch failed: ${response.status} ${response.statusText}`);
51677
- }
51678
- const data = await response.json();
51679
- return data;
51680
- }
51681
- async function getProviderList() {
51682
- const url2 = `${FIREBASE_BASE_URL}?catalog=providers`;
51683
- const response = await fetch(url2, {
51684
- signal: AbortSignal.timeout(SEARCH_FETCH_TIMEOUT_MS)
51685
- });
51686
- if (!response.ok) {
51687
- throw new Error(`Firebase providers fetch failed: ${response.status} ${response.statusText}`);
51688
- }
51689
- const data = await response.json();
51690
- return data.providers ?? [];
51691
- }
51692
- async function getModelsByProvider(provider, limit = 200) {
51693
- const url2 = `${FIREBASE_BASE_URL}?provider=${encodeURIComponent(provider)}&status=active&limit=${limit}`;
51694
- const response = await fetch(url2, {
51695
- signal: AbortSignal.timeout(SEARCH_FETCH_TIMEOUT_MS)
51696
- });
51697
- if (!response.ok) {
51698
- throw new Error(`Firebase provider query returned ${response.status} ${response.statusText}`);
51699
- }
51700
- const data = await response.json();
51701
- if (Array.isArray(data))
51702
- return data;
51703
- return data.models ?? [];
51704
- }
51705
- function loadModelInfo() {
51706
- if (_cachedModelInfo) {
51707
- return _cachedModelInfo;
51708
- }
51709
- const data = getRecommendedModelsSync();
51710
- const modelInfo = {};
51711
- for (const model of data.models) {
51712
- modelInfo[model.id] = {
51713
- name: model.name,
51714
- description: model.description,
51715
- priority: model.priority,
51716
- provider: model.provider
51717
- };
51718
- }
51719
- modelInfo.custom = {
51720
- name: "Custom Model",
51721
- description: "Enter any model ID manually",
51722
- priority: 999,
51723
- provider: "Custom"
51724
- };
51725
- _cachedModelInfo = modelInfo;
51726
- return modelInfo;
51727
- }
51728
- function getAvailableModels() {
51729
- if (_cachedModelIds) {
51730
- return _cachedModelIds;
51731
- }
51732
- const data = getRecommendedModelsSync();
51733
- const modelIds = data.models.sort((a, b) => a.priority - b.priority).map((m) => m.id);
51734
- const result = [...modelIds, "custom"];
51735
- _cachedModelIds = result;
51736
- return result;
51737
- }
51738
- var _cachedModelInfo = null, _cachedModelIds = null, _cachedRecommendedModels = null, FIREBASE_BASE_URL = "https://us-central1-claudish-6da10.cloudfunctions.net/queryModels", FIREBASE_RECOMMENDED_URL, RECOMMENDED_MODELS_CACHE_PATH, RECOMMENDED_FETCH_TIMEOUT_MS = 5000, SEARCH_FETCH_TIMEOUT_MS = 1e4, FIREBASE_SLUG_TO_PROVIDER_NAME;
51739
- var init_model_loader = __esm(() => {
51740
- init_cache_ttl();
51741
- FIREBASE_RECOMMENDED_URL = `${FIREBASE_BASE_URL}?catalog=recommended`;
51742
- RECOMMENDED_MODELS_CACHE_PATH = join31(homedir29(), ".claudish", "recommended-models-cache.json");
51743
- FIREBASE_SLUG_TO_PROVIDER_NAME = {
51744
- openai: "openai",
51745
- google: "google",
51746
- "x-ai": "x-ai",
51747
- "z-ai": "z-ai",
51748
- moonshotai: "kimi",
51749
- minimax: "minimax",
51750
- qwen: "qwen",
51751
- deepseek: "deepseek",
51752
- mistralai: "mistralai",
51753
- sakana: "sakana"
51754
- };
51755
- });
51756
-
51757
51865
  // src/port-manager.ts
51758
51866
  var exports_port_manager = {};
51759
51867
  __export(exports_port_manager, {
@@ -56062,6 +56170,12 @@ ${plan.hint}` : `[Route] ${plan.reason}`;
56062
56170
  };
56063
56171
  const app = new Hono2;
56064
56172
  app.use("*", cors());
56173
+ let modelRequestCount = 0;
56174
+ app.use("*", async (c, next) => {
56175
+ if (c.req.path === "/v1/messages")
56176
+ modelRequestCount++;
56177
+ await next();
56178
+ });
56065
56179
  app.onError((err, c) => {
56066
56180
  logStderr(`[Proxy] Unhandled error on ${c.req.method} ${c.req.path}: ${err?.message ?? err}`);
56067
56181
  log(`[Proxy] Unhandled error stack: ${err?.stack ?? "(no stack)"}`);
@@ -56194,6 +56308,7 @@ ${plan.hint}` : `[Route] ${plan.reason}`;
56194
56308
  return {
56195
56309
  port: resolvedPort,
56196
56310
  url: `http://127.0.0.1:${resolvedPort}`,
56311
+ modelRequestCount: () => modelRequestCount,
56197
56312
  shutdown: async () => {
56198
56313
  await server.stop(true);
56199
56314
  },
@@ -85631,12 +85746,16 @@ Or set CLAUDE_PATH to your custom installation:`);
85631
85746
  });
85632
85747
  }
85633
85748
  setupSignalHandlers(proc, tempSettingsPath, config3.quiet, onCleanup);
85634
- const exitCode = await new Promise((resolve6) => {
85635
- proc.on("exit", (code) => {
85749
+ const { exitCode, exitSignal } = await new Promise((resolve6) => {
85750
+ proc.on("exit", (code, signal) => {
85636
85751
  setClaudeCodeRunning(false);
85637
- resolve6(code ?? 1);
85752
+ resolve6({
85753
+ exitCode: signal ? 128 + (SIGNAL_EXIT_NUMBERS[signal] ?? 0) : code ?? 1,
85754
+ exitSignal: signal
85755
+ });
85638
85756
  });
85639
85757
  });
85758
+ log(exitSignal ? `[Claude Code] Exited from ${exitSignal} (exit code ${exitCode})` : `[Claude Code] Exited with code ${exitCode}`);
85640
85759
  releaseTerminalIsolation();
85641
85760
  try {
85642
85761
  unlinkSync10(tempSettingsPath);
@@ -89011,9 +89130,15 @@ Team Status`);
89011
89130
  }
89012
89131
  }
89013
89132
  const sessionLogPath = getAlwaysOnLogPath2();
89014
- if (exitCode !== 0 && sessionLogPath && !cliConfig.quiet) {
89133
+ const quitBySignal = exitCode === 130 || exitCode === 143;
89134
+ if (exitCode !== 0 && !quitBySignal && sessionLogPath && !cliConfig.quiet) {
89015
89135
  console.error(`
89016
- [claudish] Session ended with errors. Log: ${sessionLogPath}`);
89136
+ [claudish] Session ended with errors (exit code ${exitCode}).`);
89137
+ if (proxy.modelRequestCount() === 0) {
89138
+ console.error("[claudish] Claude Code exited before sending any model request, so the fault is on its side, not the model's.");
89139
+ console.error(`[claudish] Run \`claude\` in ${process.cwd()} to see its own error.`);
89140
+ }
89141
+ console.error(`[claudish] Log: ${sessionLogPath}`);
89017
89142
  console.error(`[claudish] To review: /debug-logs ${sessionLogPath}`);
89018
89143
  }
89019
89144
  process.exit(exitCode);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudish",
3
- "version": "9.0.0",
3
+ "version": "9.0.2",
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": "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"
63
+ "@claudish/magmux-darwin-arm64": "9.0.2",
64
+ "@claudish/magmux-darwin-x64": "9.0.2",
65
+ "@claudish/magmux-linux-arm64": "9.0.2",
66
+ "@claudish/magmux-linux-x64": "9.0.2"
67
67
  },
68
68
  "author": "Jack Rudenko <i@madappgang.com>",
69
69
  "license": "MIT",