claudish 6.13.2 → 6.14.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 +985 -1609
  2. package/package.json +5 -5
package/dist/index.js CHANGED
@@ -17321,188 +17321,6 @@ var init_openrouter_queue = __esm(() => {
17321
17321
  init_logger();
17322
17322
  });
17323
17323
 
17324
- // src/model-loader.ts
17325
- import { readFileSync as readFileSync2, existsSync as existsSync3, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4 } from "fs";
17326
- import { join as join4, dirname } from "path";
17327
- import { fileURLToPath } from "url";
17328
- import { homedir as homedir3 } from "os";
17329
- import { createHash } from "crypto";
17330
- function getRecommendedModelsPath() {
17331
- return join4(__dirname2, "../recommended-models.json");
17332
- }
17333
- function loadRecommendedModelsJSON() {
17334
- if (_cachedRecommendedModels) {
17335
- return _cachedRecommendedModels;
17336
- }
17337
- if (existsSync3(RECOMMENDED_CACHE_PATH)) {
17338
- try {
17339
- const cacheData = JSON.parse(readFileSync2(RECOMMENDED_CACHE_PATH, "utf-8"));
17340
- if (cacheData.models && cacheData.models.length > 0) {
17341
- const generatedAt = cacheData.generatedAt;
17342
- if (generatedAt) {
17343
- const ageHours = (Date.now() - new Date(generatedAt).getTime()) / (1000 * 60 * 60);
17344
- if (ageHours <= RECOMMENDED_CACHE_MAX_AGE_HOURS) {
17345
- _cachedRecommendedModels = cacheData;
17346
- return cacheData;
17347
- }
17348
- } else {
17349
- _cachedRecommendedModels = cacheData;
17350
- return cacheData;
17351
- }
17352
- }
17353
- } catch {}
17354
- }
17355
- const jsonPath = getRecommendedModelsPath();
17356
- if (!existsSync3(jsonPath)) {
17357
- throw new Error(`recommended-models.json not found at ${jsonPath}. ` + `Run 'claudish --update-models' to fetch the latest model list.`);
17358
- }
17359
- try {
17360
- const jsonContent = readFileSync2(jsonPath, "utf-8");
17361
- _cachedRecommendedModels = JSON.parse(jsonContent);
17362
- return _cachedRecommendedModels;
17363
- } catch (error2) {
17364
- throw new Error(`Failed to parse recommended-models.json: ${error2}`);
17365
- }
17366
- }
17367
- function loadModelInfo() {
17368
- if (_cachedModelInfo) {
17369
- return _cachedModelInfo;
17370
- }
17371
- const data = loadRecommendedModelsJSON();
17372
- const modelInfo = {};
17373
- for (const model of data.models) {
17374
- modelInfo[model.id] = {
17375
- name: model.name,
17376
- description: model.description,
17377
- priority: model.priority,
17378
- provider: model.provider
17379
- };
17380
- }
17381
- modelInfo.custom = {
17382
- name: "Custom Model",
17383
- description: "Enter any OpenRouter model ID manually",
17384
- priority: 999,
17385
- provider: "Custom"
17386
- };
17387
- _cachedModelInfo = modelInfo;
17388
- return modelInfo;
17389
- }
17390
- function getAvailableModels() {
17391
- if (_cachedModelIds) {
17392
- return _cachedModelIds;
17393
- }
17394
- const data = loadRecommendedModelsJSON();
17395
- const modelIds = data.models.sort((a, b) => a.priority - b.priority).map((m) => m.id);
17396
- const result = [...modelIds, "custom"];
17397
- _cachedModelIds = result;
17398
- return result;
17399
- }
17400
- function getCachedOpenRouterModels() {
17401
- return _cachedOpenRouterModels;
17402
- }
17403
- async function ensureOpenRouterModelsLoaded() {
17404
- if (_cachedOpenRouterModels)
17405
- return _cachedOpenRouterModels;
17406
- try {
17407
- const response = await fetch("https://openrouter.ai/api/v1/models");
17408
- if (response.ok) {
17409
- const data = await response.json();
17410
- _cachedOpenRouterModels = data.data || [];
17411
- return _cachedOpenRouterModels;
17412
- }
17413
- } catch {}
17414
- return [];
17415
- }
17416
- async function fetchLiteLLMModels(baseUrl, apiKey, forceUpdate = false) {
17417
- const hash = createHash("sha256").update(baseUrl).digest("hex").substring(0, 16);
17418
- const cacheDir = join4(homedir3(), ".claudish");
17419
- const cachePath = join4(cacheDir, `litellm-models-${hash}.json`);
17420
- if (!forceUpdate && existsSync3(cachePath)) {
17421
- try {
17422
- const cacheData = JSON.parse(readFileSync2(cachePath, "utf-8"));
17423
- const timestamp = new Date(cacheData.timestamp);
17424
- const now = new Date;
17425
- const ageInHours = (now.getTime() - timestamp.getTime()) / (1000 * 60 * 60);
17426
- if (ageInHours < LITELLM_CACHE_MAX_AGE_HOURS) {
17427
- return cacheData.models;
17428
- }
17429
- } catch {}
17430
- }
17431
- try {
17432
- const url = `${baseUrl.replace(/\/$/, "")}/model_group/info`;
17433
- const response = await fetch(url, {
17434
- headers: {
17435
- Authorization: `Bearer ${apiKey}`
17436
- },
17437
- signal: AbortSignal.timeout(1e4)
17438
- });
17439
- if (!response.ok) {
17440
- console.error(`Failed to fetch LiteLLM models: ${response.status} ${response.statusText}`);
17441
- if (existsSync3(cachePath)) {
17442
- try {
17443
- const cacheData2 = JSON.parse(readFileSync2(cachePath, "utf-8"));
17444
- return cacheData2.models;
17445
- } catch {
17446
- return [];
17447
- }
17448
- }
17449
- return [];
17450
- }
17451
- const responseData = await response.json();
17452
- const rawModels = responseData.data || responseData;
17453
- const transformedModels = rawModels.filter((m) => m.mode === "chat" && m.supports_function_calling).map((m) => {
17454
- const inputCostPerM = (m.input_cost_per_token || 0) * 1e6;
17455
- const outputCostPerM = (m.output_cost_per_token || 0) * 1e6;
17456
- const avgCost = (inputCostPerM + outputCostPerM) / 2;
17457
- const isFree = inputCostPerM === 0 && outputCostPerM === 0;
17458
- const contextLength = m.max_input_tokens || 128000;
17459
- const contextStr = contextLength >= 1e6 ? `${Math.round(contextLength / 1e6)}M` : `${Math.round(contextLength / 1000)}K`;
17460
- return {
17461
- id: `litellm@${m.model_group}`,
17462
- name: m.model_group,
17463
- description: `LiteLLM model (providers: ${m.providers.join(", ")})`,
17464
- provider: "LiteLLM",
17465
- pricing: {
17466
- input: isFree ? "FREE" : `$${inputCostPerM.toFixed(2)}`,
17467
- output: isFree ? "FREE" : `$${outputCostPerM.toFixed(2)}`,
17468
- average: isFree ? "FREE" : `$${avgCost.toFixed(2)}/1M`
17469
- },
17470
- context: contextStr,
17471
- contextLength,
17472
- supportsTools: m.supports_function_calling || false,
17473
- supportsReasoning: m.supports_reasoning || false,
17474
- supportsVision: m.supports_vision || false,
17475
- isFree,
17476
- source: "LiteLLM"
17477
- };
17478
- });
17479
- mkdirSync4(cacheDir, { recursive: true });
17480
- const cacheData = {
17481
- timestamp: new Date().toISOString(),
17482
- models: transformedModels
17483
- };
17484
- writeFileSync4(cachePath, JSON.stringify(cacheData, null, 2), "utf-8");
17485
- return transformedModels;
17486
- } catch (error2) {
17487
- console.error(`Failed to fetch LiteLLM models: ${error2}`);
17488
- if (existsSync3(cachePath)) {
17489
- try {
17490
- const cacheData = JSON.parse(readFileSync2(cachePath, "utf-8"));
17491
- return cacheData.models;
17492
- } catch {
17493
- return [];
17494
- }
17495
- }
17496
- return [];
17497
- }
17498
- }
17499
- var __filename2, __dirname2, _cachedModelInfo = null, _cachedModelIds = null, _cachedRecommendedModels = null, RECOMMENDED_CACHE_PATH, RECOMMENDED_CACHE_MAX_AGE_HOURS = 12, _cachedOpenRouterModels = null, LITELLM_CACHE_MAX_AGE_HOURS = 24;
17500
- var init_model_loader = __esm(() => {
17501
- __filename2 = fileURLToPath(import.meta.url);
17502
- __dirname2 = dirname(__filename2);
17503
- RECOMMENDED_CACHE_PATH = join4(homedir3(), ".claudish", "recommended-models-cache.json");
17504
- });
17505
-
17506
17324
  // src/providers/transport/openrouter.ts
17507
17325
  class OpenRouterProviderTransport {
17508
17326
  name = "openrouter";
@@ -17510,10 +17328,8 @@ class OpenRouterProviderTransport {
17510
17328
  streamFormat = "openai-sse";
17511
17329
  apiKey;
17512
17330
  queue;
17513
- modelId;
17514
- constructor(apiKey, modelId) {
17331
+ constructor(apiKey, _modelId) {
17515
17332
  this.apiKey = apiKey;
17516
- this.modelId = modelId || "";
17517
17333
  this.queue = OpenRouterRequestQueue.getInstance();
17518
17334
  }
17519
17335
  overrideStreamFormat() {
@@ -17533,15 +17349,12 @@ class OpenRouterProviderTransport {
17533
17349
  return this.queue.enqueue(fetchFn);
17534
17350
  }
17535
17351
  getContextWindow() {
17536
- const models = this.modelId ? getCachedOpenRouterModels() : null;
17537
- const model = models?.find((m) => m.id === this.modelId);
17538
- return model?.context_length || model?.top_provider?.context_length || 0;
17352
+ return 0;
17539
17353
  }
17540
17354
  }
17541
17355
  var OPENROUTER_API_URL = "https://openrouter.ai/api/v1/chat/completions";
17542
17356
  var init_openrouter = __esm(() => {
17543
17357
  init_openrouter_queue();
17544
- init_model_loader();
17545
17358
  });
17546
17359
 
17547
17360
  // src/adapters/tool-name-utils.ts
@@ -20945,9 +20758,9 @@ var init_middleware = __esm(() => {
20945
20758
  });
20946
20759
 
20947
20760
  // src/handlers/shared/token-tracker.ts
20948
- import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "fs";
20949
- import { homedir as homedir4 } from "os";
20950
- import { join as join5 } from "path";
20761
+ import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
20762
+ import { homedir as homedir3 } from "os";
20763
+ import { join as join4 } from "path";
20951
20764
 
20952
20765
  class TokenTracker {
20953
20766
  port;
@@ -21081,9 +20894,9 @@ class TokenTracker {
21081
20894
  if (this.quotaRemaining !== undefined) {
21082
20895
  data.quota_remaining = this.quotaRemaining;
21083
20896
  }
21084
- const claudishDir = join5(homedir4(), ".claudish");
21085
- mkdirSync5(claudishDir, { recursive: true });
21086
- writeFileSync5(join5(claudishDir, `tokens-${this.port}.json`), JSON.stringify(data), "utf-8");
20897
+ const claudishDir = join4(homedir3(), ".claudish");
20898
+ mkdirSync4(claudishDir, { recursive: true });
20899
+ writeFileSync4(join4(claudishDir, `tokens-${this.port}.json`), JSON.stringify(data), "utf-8");
21087
20900
  } catch (e) {
21088
20901
  log(`[TokenTracker] Error writing token file: ${e}`);
21089
20902
  }
@@ -22037,21 +21850,21 @@ __export(exports_profile_config, {
22037
21850
  configExistsForScope: () => configExistsForScope,
22038
21851
  configExists: () => configExists
22039
21852
  });
22040
- import { existsSync as existsSync4, mkdirSync as mkdirSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync6 } from "fs";
22041
- import { homedir as homedir5 } from "os";
22042
- import { join as join6 } from "path";
21853
+ import { existsSync as existsSync3, mkdirSync as mkdirSync5, readFileSync as readFileSync2, writeFileSync as writeFileSync5 } from "fs";
21854
+ import { homedir as homedir4 } from "os";
21855
+ import { join as join5 } from "path";
22043
21856
  function ensureConfigDir() {
22044
- if (!existsSync4(CONFIG_DIR)) {
22045
- mkdirSync6(CONFIG_DIR, { recursive: true });
21857
+ if (!existsSync3(CONFIG_DIR)) {
21858
+ mkdirSync5(CONFIG_DIR, { recursive: true });
22046
21859
  }
22047
21860
  }
22048
21861
  function loadConfig() {
22049
21862
  ensureConfigDir();
22050
- if (!existsSync4(CONFIG_FILE)) {
21863
+ if (!existsSync3(CONFIG_FILE)) {
22051
21864
  return { ...DEFAULT_CONFIG };
22052
21865
  }
22053
21866
  try {
22054
- const content = readFileSync3(CONFIG_FILE, "utf-8");
21867
+ const content = readFileSync2(CONFIG_FILE, "utf-8");
22055
21868
  const config2 = JSON.parse(content);
22056
21869
  const merged = {
22057
21870
  version: config2.version || DEFAULT_CONFIG.version,
@@ -22084,31 +21897,31 @@ function loadConfig() {
22084
21897
  }
22085
21898
  function saveConfig(config2) {
22086
21899
  ensureConfigDir();
22087
- writeFileSync6(CONFIG_FILE, JSON.stringify(config2, null, 2), "utf-8");
21900
+ writeFileSync5(CONFIG_FILE, JSON.stringify(config2, null, 2), "utf-8");
22088
21901
  }
22089
21902
  function configExists() {
22090
- return existsSync4(CONFIG_FILE);
21903
+ return existsSync3(CONFIG_FILE);
22091
21904
  }
22092
21905
  function getConfigPath() {
22093
21906
  return CONFIG_FILE;
22094
21907
  }
22095
21908
  function getLocalConfigPath() {
22096
- return join6(process.cwd(), LOCAL_CONFIG_FILENAME);
21909
+ return join5(process.cwd(), LOCAL_CONFIG_FILENAME);
22097
21910
  }
22098
21911
  function localConfigExists() {
22099
- return existsSync4(getLocalConfigPath());
21912
+ return existsSync3(getLocalConfigPath());
22100
21913
  }
22101
21914
  function isProjectDirectory() {
22102
21915
  const cwd = process.cwd();
22103
- return [".git", "package.json", "Cargo.toml", "go.mod", "pyproject.toml", ".claudish.json"].some((f) => existsSync4(join6(cwd, f)));
21916
+ return [".git", "package.json", "Cargo.toml", "go.mod", "pyproject.toml", ".claudish.json"].some((f) => existsSync3(join5(cwd, f)));
22104
21917
  }
22105
21918
  function loadLocalConfig() {
22106
21919
  const localPath = getLocalConfigPath();
22107
- if (!existsSync4(localPath)) {
21920
+ if (!existsSync3(localPath)) {
22108
21921
  return null;
22109
21922
  }
22110
21923
  try {
22111
- const content = readFileSync3(localPath, "utf-8");
21924
+ const content = readFileSync2(localPath, "utf-8");
22112
21925
  const config2 = JSON.parse(content);
22113
21926
  const local = {
22114
21927
  version: config2.version || DEFAULT_CONFIG.version,
@@ -22125,7 +21938,7 @@ function loadLocalConfig() {
22125
21938
  }
22126
21939
  }
22127
21940
  function saveLocalConfig(config2) {
22128
- writeFileSync6(getLocalConfigPath(), JSON.stringify(config2, null, 2), "utf-8");
21941
+ writeFileSync5(getLocalConfigPath(), JSON.stringify(config2, null, 2), "utf-8");
22129
21942
  }
22130
21943
  function loadConfigForScope(scope) {
22131
21944
  if (scope === "local") {
@@ -22345,8 +22158,8 @@ function removeEndpoint(name) {
22345
22158
  }
22346
22159
  var CONFIG_DIR, CONFIG_FILE, LOCAL_CONFIG_FILENAME = ".claudish.json", DEFAULT_CONFIG;
22347
22160
  var init_profile_config = __esm(() => {
22348
- CONFIG_DIR = join6(homedir5(), ".claudish");
22349
- CONFIG_FILE = join6(CONFIG_DIR, "config.json");
22161
+ CONFIG_DIR = join5(homedir4(), ".claudish");
22162
+ CONFIG_FILE = join5(CONFIG_DIR, "config.json");
22350
22163
  DEFAULT_CONFIG = {
22351
22164
  version: "1.0.0",
22352
22165
  defaultProfile: "default",
@@ -22363,11 +22176,12 @@ var init_profile_config = __esm(() => {
22363
22176
  });
22364
22177
 
22365
22178
  // src/version.ts
22366
- var VERSION = "6.13.2";
22179
+ var VERSION = "6.14.0";
22367
22180
 
22368
22181
  // src/telemetry.ts
22369
22182
  var exports_telemetry = {};
22370
22183
  __export(exports_telemetry, {
22184
+ setClaudeCodeRunning: () => setClaudeCodeRunning,
22371
22185
  sanitizeModelId: () => sanitizeModelId,
22372
22186
  sanitizeMessage: () => sanitizeMessage,
22373
22187
  runConsentPrompt: () => runConsentPrompt,
@@ -22577,6 +22391,8 @@ async function sendReport(report) {
22577
22391
  function showConsentPromptAsync(ctx) {
22578
22392
  if (consentPromptActive)
22579
22393
  return;
22394
+ if (claudeCodeRunning)
22395
+ return;
22580
22396
  try {
22581
22397
  const profileConfig = loadConfig();
22582
22398
  if (profileConfig.telemetry?.askedAt !== undefined)
@@ -22650,9 +22466,12 @@ function initTelemetry(config2) {
22650
22466
  claudishVersion = getVersion();
22651
22467
  installMethod = detectInstallMethod();
22652
22468
  }
22469
+ function setClaudeCodeRunning(running) {
22470
+ claudeCodeRunning = running;
22471
+ }
22653
22472
  function reportError2(ctx) {
22654
22473
  if (!initialized || !consentEnabled) {
22655
- if (initialized && !consentEnabled && ctx.isInteractive && process.stderr.isTTY) {
22474
+ if (initialized && !consentEnabled && ctx.isInteractive && process.stderr.isTTY && !claudeCodeRunning) {
22656
22475
  showConsentPromptAsync(ctx);
22657
22476
  }
22658
22477
  return;
@@ -22755,7 +22574,7 @@ Usage: claudish telemetry on|off|status|reset
22755
22574
  process.exit(1);
22756
22575
  }
22757
22576
  }
22758
- var TELEMETRY_ENDPOINT = "https://claudish.com/v1/report", MAX_REPORT_BYTES = 4096, KNOWN_PUBLIC_HOSTS, PUBLIC_PROVIDERS, consentEnabled = false, sessionId = "", initialized = false, claudishVersion = "", installMethod = "unknown", consentPromptActive = false;
22577
+ var TELEMETRY_ENDPOINT = "https://claudish.com/v1/report", MAX_REPORT_BYTES = 4096, KNOWN_PUBLIC_HOSTS, PUBLIC_PROVIDERS, consentEnabled = false, sessionId = "", initialized = false, claudishVersion = "", installMethod = "unknown", consentPromptActive = false, claudeCodeRunning = false;
22759
22578
  var init_telemetry = __esm(() => {
22760
22579
  init_profile_config();
22761
22580
  init_logger();
@@ -22787,9 +22606,9 @@ var init_telemetry = __esm(() => {
22787
22606
  });
22788
22607
 
22789
22608
  // src/providers/provider-definitions.ts
22790
- import { existsSync as existsSync5 } from "fs";
22791
- import { join as join7 } from "path";
22792
- import { homedir as homedir6 } from "os";
22609
+ import { existsSync as existsSync4 } from "fs";
22610
+ import { join as join6 } from "path";
22611
+ import { homedir as homedir5 } from "os";
22793
22612
  function ensureProviderByNameCache() {
22794
22613
  if (!_providerByNameCache) {
22795
22614
  _providerByNameCache = new Map;
@@ -22929,7 +22748,7 @@ function isProviderAvailable(def) {
22929
22748
  }
22930
22749
  if (def.oauthFallback) {
22931
22750
  try {
22932
- if (existsSync5(join7(homedir6(), ".claudish", def.oauthFallback)))
22751
+ if (existsSync4(join6(homedir5(), ".claudish", def.oauthFallback)))
22933
22752
  return true;
22934
22753
  } catch {}
22935
22754
  }
@@ -23548,25 +23367,25 @@ var init_model_parser = __esm(() => {
23548
23367
 
23549
23368
  // src/stats-buffer.ts
23550
23369
  import {
23551
- existsSync as existsSync6,
23552
- mkdirSync as mkdirSync7,
23553
- readFileSync as readFileSync4,
23370
+ existsSync as existsSync5,
23371
+ mkdirSync as mkdirSync6,
23372
+ readFileSync as readFileSync3,
23554
23373
  renameSync,
23555
23374
  unlinkSync as unlinkSync2,
23556
- writeFileSync as writeFileSync7
23375
+ writeFileSync as writeFileSync6
23557
23376
  } from "fs";
23558
- import { homedir as homedir7 } from "os";
23559
- import { join as join8 } from "path";
23377
+ import { homedir as homedir6 } from "os";
23378
+ import { join as join7 } from "path";
23560
23379
  function ensureDir() {
23561
- if (!existsSync6(CLAUDISH_DIR)) {
23562
- mkdirSync7(CLAUDISH_DIR, { recursive: true });
23380
+ if (!existsSync5(CLAUDISH_DIR)) {
23381
+ mkdirSync6(CLAUDISH_DIR, { recursive: true });
23563
23382
  }
23564
23383
  }
23565
23384
  function readFromDisk() {
23566
23385
  try {
23567
- if (!existsSync6(BUFFER_FILE))
23386
+ if (!existsSync5(BUFFER_FILE))
23568
23387
  return [];
23569
- const raw2 = readFileSync4(BUFFER_FILE, "utf-8");
23388
+ const raw2 = readFileSync3(BUFFER_FILE, "utf-8");
23570
23389
  const parsed = JSON.parse(raw2);
23571
23390
  if (!Array.isArray(parsed.events))
23572
23391
  return [];
@@ -23590,8 +23409,8 @@ function writeToDisk(events) {
23590
23409
  ensureDir();
23591
23410
  const trimmed = enforceSizeCap([...events]);
23592
23411
  const payload = { version: 1, events: trimmed };
23593
- const tmpFile = join8(CLAUDISH_DIR, `stats-buffer.tmp.${process.pid}.json`);
23594
- writeFileSync7(tmpFile, JSON.stringify(payload, null, 2), "utf-8");
23412
+ const tmpFile = join7(CLAUDISH_DIR, `stats-buffer.tmp.${process.pid}.json`);
23413
+ writeFileSync6(tmpFile, JSON.stringify(payload, null, 2), "utf-8");
23595
23414
  renameSync(tmpFile, BUFFER_FILE);
23596
23415
  memoryCache = trimmed;
23597
23416
  } catch {}
@@ -23635,7 +23454,7 @@ function clearBuffer() {
23635
23454
  try {
23636
23455
  memoryCache = [];
23637
23456
  eventsSinceLastFlush = 0;
23638
- if (existsSync6(BUFFER_FILE)) {
23457
+ if (existsSync5(BUFFER_FILE)) {
23639
23458
  unlinkSync2(BUFFER_FILE);
23640
23459
  }
23641
23460
  } catch {}
@@ -23664,8 +23483,8 @@ function syncFlushOnExit() {
23664
23483
  var BUFFER_MAX_BYTES, CLAUDISH_DIR, BUFFER_FILE, memoryCache = null, eventsSinceLastFlush = 0, lastFlushTime, flushScheduled = false;
23665
23484
  var init_stats_buffer = __esm(() => {
23666
23485
  BUFFER_MAX_BYTES = 64 * 1024;
23667
- CLAUDISH_DIR = join8(homedir7(), ".claudish");
23668
- BUFFER_FILE = join8(CLAUDISH_DIR, "stats-buffer.json");
23486
+ CLAUDISH_DIR = join7(homedir6(), ".claudish");
23487
+ BUFFER_FILE = join7(CLAUDISH_DIR, "stats-buffer.json");
23669
23488
  lastFlushTime = Date.now();
23670
23489
  process.on("exit", syncFlushOnExit);
23671
23490
  process.on("SIGTERM", () => {
@@ -24863,18 +24682,18 @@ var init_remote_provider_registry = __esm(() => {
24863
24682
  });
24864
24683
 
24865
24684
  // src/auth/oauth-registry.ts
24866
- import { existsSync as existsSync7, readFileSync as readFileSync5 } from "fs";
24867
- import { join as join9 } from "path";
24868
- import { homedir as homedir8 } from "os";
24685
+ import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
24686
+ import { join as join8 } from "path";
24687
+ import { homedir as homedir7 } from "os";
24869
24688
  function hasValidOAuthCredentials(descriptor) {
24870
- const credPath = join9(homedir8(), ".claudish", descriptor.credentialFile);
24871
- if (!existsSync7(credPath))
24689
+ const credPath = join8(homedir7(), ".claudish", descriptor.credentialFile);
24690
+ if (!existsSync6(credPath))
24872
24691
  return false;
24873
24692
  if (descriptor.validationMode === "file-exists") {
24874
24693
  return true;
24875
24694
  }
24876
24695
  try {
24877
- const data = JSON.parse(readFileSync5(credPath, "utf-8"));
24696
+ const data = JSON.parse(readFileSync4(credPath, "utf-8"));
24878
24697
  if (!data.access_token)
24879
24698
  return false;
24880
24699
  if (data.refresh_token)
@@ -24962,15 +24781,15 @@ var init_static_fallback = __esm(() => {
24962
24781
  });
24963
24782
 
24964
24783
  // src/providers/all-models-cache.ts
24965
- import { readFileSync as readFileSync6, existsSync as existsSync8, writeFileSync as writeFileSync8, mkdirSync as mkdirSync8 } from "fs";
24966
- import { join as join10, dirname as dirname2 } from "path";
24967
- import { homedir as homedir9 } from "os";
24784
+ import { readFileSync as readFileSync5, existsSync as existsSync7, writeFileSync as writeFileSync7, mkdirSync as mkdirSync7 } from "fs";
24785
+ import { join as join9, dirname } from "path";
24786
+ import { homedir as homedir8 } from "os";
24968
24787
  function readAllModelsCache(path = ALL_MODELS_CACHE_PATH) {
24969
- if (!existsSync8(path))
24788
+ if (!existsSync7(path))
24970
24789
  return null;
24971
24790
  let raw2;
24972
24791
  try {
24973
- raw2 = JSON.parse(readFileSync6(path, "utf-8"));
24792
+ raw2 = JSON.parse(readFileSync5(path, "utf-8"));
24974
24793
  } catch {
24975
24794
  return null;
24976
24795
  }
@@ -24995,12 +24814,12 @@ function writeAllModelsCache(data, path = ALL_MODELS_CACHE_PATH) {
24995
24814
  entries: data.entries ?? existing?.entries ?? [],
24996
24815
  models: data.models ?? existing?.models ?? []
24997
24816
  };
24998
- mkdirSync8(dirname2(path), { recursive: true });
24999
- writeFileSync8(path, JSON.stringify(merged), "utf-8");
24817
+ mkdirSync7(dirname(path), { recursive: true });
24818
+ writeFileSync7(path, JSON.stringify(merged), "utf-8");
25000
24819
  }
25001
24820
  var ALL_MODELS_CACHE_PATH;
25002
24821
  var init_all_models_cache = __esm(() => {
25003
- ALL_MODELS_CACHE_PATH = join10(homedir9(), ".claudish", "all-models.json");
24822
+ ALL_MODELS_CACHE_PATH = join9(homedir8(), ".claudish", "all-models.json");
25004
24823
  });
25005
24824
 
25006
24825
  // src/providers/catalog-resolvers/openrouter.ts
@@ -25139,16 +24958,16 @@ var init_openrouter2 = __esm(() => {
25139
24958
  });
25140
24959
 
25141
24960
  // src/providers/catalog-resolvers/litellm.ts
25142
- import { readFileSync as readFileSync7, existsSync as existsSync9 } from "fs";
25143
- import { join as join11 } from "path";
25144
- import { homedir as homedir10 } from "os";
25145
- import { createHash as createHash2 } from "crypto";
24961
+ import { readFileSync as readFileSync6, existsSync as existsSync8 } from "fs";
24962
+ import { join as join10 } from "path";
24963
+ import { homedir as homedir9 } from "os";
24964
+ import { createHash } from "crypto";
25146
24965
  function getCachePath() {
25147
24966
  const baseUrl = process.env.LITELLM_BASE_URL;
25148
24967
  if (!baseUrl)
25149
24968
  return null;
25150
- const hash = createHash2("sha256").update(baseUrl).digest("hex").substring(0, 16);
25151
- return join11(homedir10(), ".claudish", `litellm-models-${hash}.json`);
24969
+ const hash = createHash("sha256").update(baseUrl).digest("hex").substring(0, 16);
24970
+ return join10(homedir9(), ".claudish", `litellm-models-${hash}.json`);
25152
24971
  }
25153
24972
 
25154
24973
  class LiteLLMCatalogResolver {
@@ -25176,10 +24995,10 @@ class LiteLLMCatalogResolver {
25176
24995
  }
25177
24996
  async warmCache() {
25178
24997
  const path = getCachePath();
25179
- if (!path || !existsSync9(path))
24998
+ if (!path || !existsSync8(path))
25180
24999
  return;
25181
25000
  try {
25182
- const data = JSON.parse(readFileSync7(path, "utf-8"));
25001
+ const data = JSON.parse(readFileSync6(path, "utf-8"));
25183
25002
  if (Array.isArray(data.models)) {
25184
25003
  _memCache2 = data.models.map((m) => m.name ?? m.id?.replace("litellm@", "") ?? "");
25185
25004
  }
@@ -25196,10 +25015,10 @@ class LiteLLMCatalogResolver {
25196
25015
  if (_memCache2)
25197
25016
  return _memCache2;
25198
25017
  const path = getCachePath();
25199
- if (!path || !existsSync9(path))
25018
+ if (!path || !existsSync8(path))
25200
25019
  return null;
25201
25020
  try {
25202
- const data = JSON.parse(readFileSync7(path, "utf-8"));
25021
+ const data = JSON.parse(readFileSync6(path, "utf-8"));
25203
25022
  if (Array.isArray(data.models)) {
25204
25023
  _memCache2 = data.models.map((m) => m.name ?? m.id?.replace("litellm@", "") ?? "");
25205
25024
  return _memCache2;
@@ -25264,17 +25083,17 @@ var init_model_catalog_resolver = __esm(() => {
25264
25083
  });
25265
25084
 
25266
25085
  // src/providers/auto-route.ts
25267
- import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
25268
- import { join as join12 } from "path";
25269
- import { homedir as homedir11 } from "os";
25270
- import { createHash as createHash3 } from "crypto";
25086
+ import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
25087
+ import { join as join11 } from "path";
25088
+ import { homedir as homedir10 } from "os";
25089
+ import { createHash as createHash2 } from "crypto";
25271
25090
  function readLiteLLMCacheSync(baseUrl) {
25272
- const hash = createHash3("sha256").update(baseUrl).digest("hex").substring(0, 16);
25273
- const cachePath = join12(homedir11(), ".claudish", `litellm-models-${hash}.json`);
25274
- if (!existsSync10(cachePath))
25091
+ const hash = createHash2("sha256").update(baseUrl).digest("hex").substring(0, 16);
25092
+ const cachePath = join11(homedir10(), ".claudish", `litellm-models-${hash}.json`);
25093
+ if (!existsSync9(cachePath))
25275
25094
  return null;
25276
25095
  try {
25277
- const data = JSON.parse(readFileSync8(cachePath, "utf-8"));
25096
+ const data = JSON.parse(readFileSync7(cachePath, "utf-8"));
25278
25097
  if (!Array.isArray(data.models))
25279
25098
  return null;
25280
25099
  return data.models;
@@ -25395,17 +25214,17 @@ async function warmZenModelCache() {
25395
25214
  const models = (data.data ?? []).map((m) => ({ id: m.id }));
25396
25215
  if (models.length === 0)
25397
25216
  return;
25398
- const cacheDir = join12(homedir11(), ".claudish");
25399
- const { mkdirSync: mkdirSync9, writeFileSync: writeSync } = await import("fs");
25400
- mkdirSync9(cacheDir, { recursive: true });
25401
- writeSync(join12(cacheDir, "zen-models.json"), JSON.stringify({ models, fetchedAt: new Date().toISOString() }));
25217
+ const cacheDir = join11(homedir10(), ".claudish");
25218
+ const { mkdirSync: mkdirSync8, writeFileSync: writeSync } = await import("fs");
25219
+ mkdirSync8(cacheDir, { recursive: true });
25220
+ writeSync(join11(cacheDir, "zen-models.json"), JSON.stringify({ models, fetchedAt: new Date().toISOString() }));
25402
25221
  }
25403
25222
  function readZenGoModelCacheSync() {
25404
- const cachePath = join12(homedir11(), ".claudish", "zen-go-models.json");
25405
- if (!existsSync10(cachePath))
25223
+ const cachePath = join11(homedir10(), ".claudish", "zen-go-models.json");
25224
+ if (!existsSync9(cachePath))
25406
25225
  return null;
25407
25226
  try {
25408
- const data = JSON.parse(readFileSync8(cachePath, "utf-8"));
25227
+ const data = JSON.parse(readFileSync7(cachePath, "utf-8"));
25409
25228
  if (!Array.isArray(data.models))
25410
25229
  return null;
25411
25230
  return new Set(data.models.map((m) => m.id));
@@ -25432,10 +25251,10 @@ async function warmZenGoModelCache() {
25432
25251
  const models = (data.data ?? []).map((m) => ({ id: m.id }));
25433
25252
  if (models.length === 0)
25434
25253
  return;
25435
- const cacheDir = join12(homedir11(), ".claudish");
25436
- const { mkdirSync: mkdirSync9, writeFileSync: writeSync } = await import("fs");
25437
- mkdirSync9(cacheDir, { recursive: true });
25438
- writeSync(join12(cacheDir, "zen-go-models.json"), JSON.stringify({ models, fetchedAt: new Date().toISOString() }));
25254
+ const cacheDir = join11(homedir10(), ".claudish");
25255
+ const { mkdirSync: mkdirSync8, writeFileSync: writeSync } = await import("fs");
25256
+ mkdirSync8(cacheDir, { recursive: true });
25257
+ writeSync(join11(cacheDir, "zen-go-models.json"), JSON.stringify({ models, fetchedAt: new Date().toISOString() }));
25439
25258
  }
25440
25259
  function hasProviderCredentials(provider) {
25441
25260
  const keyInfo = getApiKeyEnvVars(provider);
@@ -25572,9 +25391,9 @@ __export(exports_provider_resolver, {
25572
25391
  getMissingKeyResolutions: () => getMissingKeyResolutions,
25573
25392
  getMissingKeyError: () => getMissingKeyError
25574
25393
  });
25575
- import { existsSync as existsSync11 } from "fs";
25576
- import { join as join13 } from "path";
25577
- import { homedir as homedir12 } from "os";
25394
+ import { existsSync as existsSync10 } from "fs";
25395
+ import { join as join12 } from "path";
25396
+ import { homedir as homedir11 } from "os";
25578
25397
  function getApiKeyInfoForProvider(providerName) {
25579
25398
  const lookupName = providerName === "gemini" ? "google" : providerName;
25580
25399
  const info = getApiKeyInfo(lookupName);
@@ -25609,8 +25428,8 @@ function isApiKeyAvailable(info) {
25609
25428
  }
25610
25429
  if (info.oauthFallback) {
25611
25430
  try {
25612
- const credPath = join13(homedir12(), ".claudish", info.oauthFallback);
25613
- if (existsSync11(credPath)) {
25431
+ const credPath = join12(homedir11(), ".claudish", info.oauthFallback);
25432
+ if (existsSync10(credPath)) {
25614
25433
  return true;
25615
25434
  }
25616
25435
  } catch {}
@@ -25911,9 +25730,9 @@ var init_provider_resolver = __esm(() => {
25911
25730
  });
25912
25731
 
25913
25732
  // src/services/pricing-cache.ts
25914
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync9, existsSync as existsSync12, mkdirSync as mkdirSync9, statSync } from "fs";
25915
- import { homedir as homedir13 } from "os";
25916
- import { join as join14 } from "path";
25733
+ import { readFileSync as readFileSync8, existsSync as existsSync11, statSync } from "fs";
25734
+ import { homedir as homedir12 } from "os";
25735
+ import { join as join13 } from "path";
25917
25736
  function getDynamicPricingSync(provider, modelName) {
25918
25737
  if (provider === "openrouter") {
25919
25738
  const direct = pricingMap.get(modelName);
@@ -25954,36 +25773,21 @@ async function warmPricingCache() {
25954
25773
  const diskFresh = loadDiskCache();
25955
25774
  if (diskFresh) {
25956
25775
  log("[PricingCache] Loaded pricing from disk cache");
25957
- return;
25958
- }
25959
- log("[PricingCache] Disk cache stale or missing, fetching from OpenRouter API...");
25960
- const models = await ensureOpenRouterModelsLoaded();
25961
- if (models.length === 0) {
25962
- const cached2 = getCachedOpenRouterModels();
25963
- if (cached2 && cached2.length > 0) {
25964
- populateFromOpenRouterModels(cached2);
25965
- saveDiskCache();
25966
- log(`[PricingCache] Populated from existing model-loader cache (${pricingMap.size} models)`);
25967
- return;
25968
- }
25969
- log("[PricingCache] No models available, will use provider defaults");
25970
- return;
25776
+ } else {
25777
+ log("[PricingCache] Disk cache stale or missing, using provider defaults");
25971
25778
  }
25972
- populateFromOpenRouterModels(models);
25973
- saveDiskCache();
25974
- log(`[PricingCache] Fetched and cached pricing for ${pricingMap.size} models`);
25975
25779
  } catch (error2) {
25976
25780
  log(`[PricingCache] Error warming cache: ${error2}`);
25977
25781
  }
25978
25782
  }
25979
25783
  function loadDiskCache() {
25980
25784
  try {
25981
- if (!existsSync12(CACHE_FILE))
25785
+ if (!existsSync11(CACHE_FILE))
25982
25786
  return false;
25983
25787
  const stat = statSync(CACHE_FILE);
25984
25788
  const age = Date.now() - stat.mtimeMs;
25985
25789
  const isFresh = age < CACHE_TTL_MS;
25986
- const raw2 = readFileSync9(CACHE_FILE, "utf-8");
25790
+ const raw2 = readFileSync8(CACHE_FILE, "utf-8");
25987
25791
  const data = JSON.parse(raw2);
25988
25792
  for (const [key, pricing] of Object.entries(data)) {
25989
25793
  pricingMap.set(key, pricing);
@@ -25993,45 +25797,13 @@ function loadDiskCache() {
25993
25797
  return false;
25994
25798
  }
25995
25799
  }
25996
- function saveDiskCache() {
25997
- try {
25998
- mkdirSync9(CACHE_DIR, { recursive: true });
25999
- const data = {};
26000
- for (const [key, pricing] of pricingMap) {
26001
- data[key] = pricing;
26002
- }
26003
- writeFileSync9(CACHE_FILE, JSON.stringify(data), "utf-8");
26004
- } catch (error2) {
26005
- log(`[PricingCache] Error saving disk cache: ${error2}`);
26006
- }
26007
- }
26008
- function populateFromOpenRouterModels(models) {
26009
- for (const model of models) {
26010
- if (!model.id || !model.pricing)
26011
- continue;
26012
- const promptPrice = parseFloat(model.pricing.prompt || "0");
26013
- const completionPrice = parseFloat(model.pricing.completion || "0");
26014
- if (isNaN(promptPrice) || isNaN(completionPrice))
26015
- continue;
26016
- const inputCostPer1M = promptPrice * 1e6;
26017
- const outputCostPer1M = completionPrice * 1e6;
26018
- const isFree = inputCostPer1M === 0 && outputCostPer1M === 0;
26019
- pricingMap.set(model.id, {
26020
- inputCostPer1M,
26021
- outputCostPer1M,
26022
- isEstimate: true,
26023
- ...isFree ? { isFree: true } : {}
26024
- });
26025
- }
26026
- }
26027
25800
  var pricingMap, CACHE_DIR, CACHE_FILE, CACHE_TTL_MS, cacheWarmed = false, PROVIDER_TO_OR_PREFIX;
26028
25801
  var init_pricing_cache = __esm(() => {
26029
25802
  init_logger();
26030
- init_model_loader();
26031
25803
  init_remote_provider_types();
26032
25804
  pricingMap = new Map;
26033
- CACHE_DIR = join14(homedir13(), ".claudish");
26034
- CACHE_FILE = join14(CACHE_DIR, "pricing-cache.json");
25805
+ CACHE_DIR = join13(homedir12(), ".claudish");
25806
+ CACHE_FILE = join13(CACHE_DIR, "pricing-cache.json");
26035
25807
  CACHE_TTL_MS = 24 * 60 * 60 * 1000;
26036
25808
  PROVIDER_TO_OR_PREFIX = {
26037
25809
  openai: ["openai/"],
@@ -26049,6 +25821,358 @@ var init_pricing_cache = __esm(() => {
26049
25821
  };
26050
25822
  });
26051
25823
 
25824
+ // src/model-loader.ts
25825
+ import { readFileSync as readFileSync9, existsSync as existsSync12, writeFileSync as writeFileSync8, mkdirSync as mkdirSync8 } from "fs";
25826
+ import { join as join14, dirname as dirname2 } from "path";
25827
+ import { fileURLToPath } from "url";
25828
+ import { homedir as homedir13 } from "os";
25829
+ import { createHash as createHash3 } from "crypto";
25830
+ function getBundledRecommendedModelsPath() {
25831
+ return join14(__dirname2, "../recommended-models.json");
25832
+ }
25833
+ function groupRecommendedModels(entries) {
25834
+ const byId = new Map;
25835
+ for (const entry of entries) {
25836
+ const list = byId.get(entry.id);
25837
+ if (list)
25838
+ list.push(entry);
25839
+ else
25840
+ byId.set(entry.id, [entry]);
25841
+ }
25842
+ const flagship = [];
25843
+ const fast = [];
25844
+ for (const [id, members] of byId.entries()) {
25845
+ const primary = members.find((m) => m.category !== "subscription") ?? members[0];
25846
+ const subscriptions = members.filter((m) => m.category === "subscription");
25847
+ const bucket = primary.category === "programming" || primary.category === "vision" || primary.category === "reasoning" ? "flagship" : "fast";
25848
+ const group = { id, primary, subscriptions, bucket };
25849
+ if (bucket === "flagship")
25850
+ flagship.push(group);
25851
+ else
25852
+ fast.push(group);
25853
+ }
25854
+ return { flagship, fast };
25855
+ }
25856
+ function collectRoutingPrefixes(group, getNativePrefix) {
25857
+ const slug = (group.primary.provider || "").toLowerCase();
25858
+ const native = getNativePrefix(slug);
25859
+ const seen = new Set;
25860
+ const out = [];
25861
+ if (native) {
25862
+ out.push(native);
25863
+ seen.add(native);
25864
+ }
25865
+ for (const sub of group.subscriptions) {
25866
+ const p = sub.subscription?.prefix;
25867
+ if (!p || seen.has(p))
25868
+ continue;
25869
+ seen.add(p);
25870
+ out.push(p);
25871
+ }
25872
+ return out;
25873
+ }
25874
+ function parsePriceAvg(s) {
25875
+ if (!s || s === "N/A")
25876
+ return Infinity;
25877
+ if (s === "FREE")
25878
+ return 0;
25879
+ const m = s.match(/\$([\d.]+)/);
25880
+ return m ? parseFloat(m[1]) : Infinity;
25881
+ }
25882
+ function parseCtx(s) {
25883
+ if (!s || s === "N/A")
25884
+ return 0;
25885
+ const upper = s.toUpperCase();
25886
+ if (upper.includes("M"))
25887
+ return parseFloat(upper) * 1e6;
25888
+ if (upper.includes("K"))
25889
+ return parseFloat(upper) * 1000;
25890
+ return parseInt(s, 10) || 0;
25891
+ }
25892
+ function normalizePricingDisplay(raw2) {
25893
+ const pricing = raw2 || "N/A";
25894
+ if (pricing.includes("-1000000"))
25895
+ return "varies";
25896
+ if (pricing === "$0.00/1M" || pricing === "FREE")
25897
+ return "FREE";
25898
+ return pricing;
25899
+ }
25900
+ function computeQuickPicks(primaries) {
25901
+ if (primaries.length === 0) {
25902
+ return {
25903
+ budget: null,
25904
+ largeContext: null,
25905
+ mostCapable: null,
25906
+ visionCoding: null,
25907
+ agentic: null
25908
+ };
25909
+ }
25910
+ const priced = primaries.filter((m) => {
25911
+ const p = parsePriceAvg(m.pricing?.average);
25912
+ return p > 0 && p !== Infinity;
25913
+ }).sort((a, b) => parsePriceAvg(a.pricing?.average) - parsePriceAvg(b.pricing?.average));
25914
+ const budget = priced[0] ?? null;
25915
+ const byCtx = [...primaries].sort((a, b) => parseCtx(b.context) - parseCtx(a.context));
25916
+ const largeContext = byCtx[0] ?? null;
25917
+ const byPrice = [...primaries].sort((a, b) => parsePriceAvg(b.pricing?.average) - parsePriceAvg(a.pricing?.average));
25918
+ const mostCapable = byPrice.find((m) => parsePriceAvg(m.pricing?.average) !== Infinity) ?? null;
25919
+ const visionCoding = primaries.find((m) => m.supportsVision === true && m.id !== budget?.id && m.id !== mostCapable?.id) ?? null;
25920
+ const agentic = primaries.find((m) => m.supportsReasoning === true && m.id !== mostCapable?.id) ?? null;
25921
+ return { budget, largeContext, mostCapable, visionCoding, agentic };
25922
+ }
25923
+ async function getRecommendedModels(opts = {}) {
25924
+ const { forceRefresh = false } = opts;
25925
+ if (!forceRefresh && _cachedRecommendedModels) {
25926
+ return _cachedRecommendedModels;
25927
+ }
25928
+ if (!forceRefresh && existsSync12(RECOMMENDED_MODELS_CACHE_PATH)) {
25929
+ try {
25930
+ const cacheData = JSON.parse(readFileSync9(RECOMMENDED_MODELS_CACHE_PATH, "utf-8"));
25931
+ if (cacheData.models && cacheData.models.length > 0 && isFreshEnough(cacheData)) {
25932
+ _cachedRecommendedModels = cacheData;
25933
+ return cacheData;
25934
+ }
25935
+ } catch {}
25936
+ }
25937
+ try {
25938
+ const response = await fetch(FIREBASE_RECOMMENDED_URL, {
25939
+ signal: AbortSignal.timeout(RECOMMENDED_FETCH_TIMEOUT_MS)
25940
+ });
25941
+ if (response.ok) {
25942
+ const data = await response.json();
25943
+ if (data.models && data.models.length > 0) {
25944
+ _cachedRecommendedModels = data;
25945
+ try {
25946
+ const cacheDir = join14(homedir13(), ".claudish");
25947
+ mkdirSync8(cacheDir, { recursive: true });
25948
+ writeFileSync8(RECOMMENDED_MODELS_CACHE_PATH, JSON.stringify(data), "utf-8");
25949
+ } catch {}
25950
+ return data;
25951
+ }
25952
+ }
25953
+ } catch {}
25954
+ return loadBundledRecommendedModels();
25955
+ }
25956
+ function getRecommendedModelsSync() {
25957
+ if (_cachedRecommendedModels)
25958
+ return _cachedRecommendedModels;
25959
+ if (existsSync12(RECOMMENDED_MODELS_CACHE_PATH)) {
25960
+ try {
25961
+ const cacheData = JSON.parse(readFileSync9(RECOMMENDED_MODELS_CACHE_PATH, "utf-8"));
25962
+ if (cacheData.models && cacheData.models.length > 0) {
25963
+ _cachedRecommendedModels = cacheData;
25964
+ return cacheData;
25965
+ }
25966
+ } catch {}
25967
+ }
25968
+ return loadBundledRecommendedModels();
25969
+ }
25970
+ async function warmRecommendedModels() {
25971
+ try {
25972
+ return await getRecommendedModels({ forceRefresh: true });
25973
+ } catch {
25974
+ return null;
25975
+ }
25976
+ }
25977
+ function isFreshEnough(doc2) {
25978
+ const generatedAt = doc2.generatedAt;
25979
+ if (!generatedAt)
25980
+ return true;
25981
+ const ageHours = (Date.now() - new Date(generatedAt).getTime()) / (1000 * 60 * 60);
25982
+ return ageHours <= RECOMMENDED_CACHE_MAX_AGE_HOURS;
25983
+ }
25984
+ function loadBundledRecommendedModels() {
25985
+ const jsonPath = getBundledRecommendedModelsPath();
25986
+ if (!existsSync12(jsonPath)) {
25987
+ throw new Error(`recommended-models.json not found at ${jsonPath}. ` + `Run 'claudish --top-models --force-update' to refresh from Firebase.`);
25988
+ }
25989
+ try {
25990
+ const doc2 = JSON.parse(readFileSync9(jsonPath, "utf-8"));
25991
+ _cachedRecommendedModels = doc2;
25992
+ return doc2;
25993
+ } catch (error2) {
25994
+ throw new Error(`Failed to parse bundled recommended-models.json: ${error2}`);
25995
+ }
25996
+ }
25997
+ async function searchModels(query, limit = 50) {
25998
+ const url = `${FIREBASE_BASE_URL}?search=${encodeURIComponent(query)}&limit=${limit}&status=active`;
25999
+ const response = await fetch(url, {
26000
+ signal: AbortSignal.timeout(SEARCH_FETCH_TIMEOUT_MS)
26001
+ });
26002
+ if (!response.ok) {
26003
+ throw new Error(`Firebase search returned ${response.status} ${response.statusText}`);
26004
+ }
26005
+ const data = await response.json();
26006
+ return data.models ?? [];
26007
+ }
26008
+ async function getTop100Models() {
26009
+ const url = `${FIREBASE_BASE_URL}?catalog=top100`;
26010
+ const response = await fetch(url, {
26011
+ signal: AbortSignal.timeout(SEARCH_FETCH_TIMEOUT_MS)
26012
+ });
26013
+ if (!response.ok) {
26014
+ throw new Error(`Firebase top100 fetch failed: ${response.status} ${response.statusText}`);
26015
+ }
26016
+ const data = await response.json();
26017
+ return data;
26018
+ }
26019
+ async function getProviderList() {
26020
+ const url = `${FIREBASE_BASE_URL}?catalog=providers`;
26021
+ const response = await fetch(url, {
26022
+ signal: AbortSignal.timeout(SEARCH_FETCH_TIMEOUT_MS)
26023
+ });
26024
+ if (!response.ok) {
26025
+ throw new Error(`Firebase providers fetch failed: ${response.status} ${response.statusText}`);
26026
+ }
26027
+ const data = await response.json();
26028
+ return data.providers ?? [];
26029
+ }
26030
+ async function getModelsByProvider(provider, limit = 200) {
26031
+ const url = `${FIREBASE_BASE_URL}?provider=${encodeURIComponent(provider)}&status=active&limit=${limit}`;
26032
+ const response = await fetch(url, {
26033
+ signal: AbortSignal.timeout(SEARCH_FETCH_TIMEOUT_MS)
26034
+ });
26035
+ if (!response.ok) {
26036
+ throw new Error(`Firebase provider query returned ${response.status} ${response.statusText}`);
26037
+ }
26038
+ const data = await response.json();
26039
+ if (Array.isArray(data))
26040
+ return data;
26041
+ return data.models ?? [];
26042
+ }
26043
+ function loadModelInfo() {
26044
+ if (_cachedModelInfo) {
26045
+ return _cachedModelInfo;
26046
+ }
26047
+ const data = getRecommendedModelsSync();
26048
+ const modelInfo = {};
26049
+ for (const model of data.models) {
26050
+ modelInfo[model.id] = {
26051
+ name: model.name,
26052
+ description: model.description,
26053
+ priority: model.priority,
26054
+ provider: model.provider
26055
+ };
26056
+ }
26057
+ modelInfo.custom = {
26058
+ name: "Custom Model",
26059
+ description: "Enter any model ID manually",
26060
+ priority: 999,
26061
+ provider: "Custom"
26062
+ };
26063
+ _cachedModelInfo = modelInfo;
26064
+ return modelInfo;
26065
+ }
26066
+ function getAvailableModels() {
26067
+ if (_cachedModelIds) {
26068
+ return _cachedModelIds;
26069
+ }
26070
+ const data = getRecommendedModelsSync();
26071
+ const modelIds = data.models.sort((a, b) => a.priority - b.priority).map((m) => m.id);
26072
+ const result = [...modelIds, "custom"];
26073
+ _cachedModelIds = result;
26074
+ return result;
26075
+ }
26076
+ async function fetchLiteLLMModels(baseUrl, apiKey, forceUpdate = false) {
26077
+ const hash = createHash3("sha256").update(baseUrl).digest("hex").substring(0, 16);
26078
+ const cacheDir = join14(homedir13(), ".claudish");
26079
+ const cachePath = join14(cacheDir, `litellm-models-${hash}.json`);
26080
+ if (!forceUpdate && existsSync12(cachePath)) {
26081
+ try {
26082
+ const cacheData = JSON.parse(readFileSync9(cachePath, "utf-8"));
26083
+ const timestamp = new Date(cacheData.timestamp);
26084
+ const now = new Date;
26085
+ const ageInHours = (now.getTime() - timestamp.getTime()) / (1000 * 60 * 60);
26086
+ if (ageInHours < LITELLM_CACHE_MAX_AGE_HOURS) {
26087
+ return cacheData.models;
26088
+ }
26089
+ } catch {}
26090
+ }
26091
+ try {
26092
+ const url = `${baseUrl.replace(/\/$/, "")}/model_group/info`;
26093
+ const response = await fetch(url, {
26094
+ headers: {
26095
+ Authorization: `Bearer ${apiKey}`
26096
+ },
26097
+ signal: AbortSignal.timeout(1e4)
26098
+ });
26099
+ if (!response.ok) {
26100
+ console.error(`Failed to fetch LiteLLM models: ${response.status} ${response.statusText}`);
26101
+ if (existsSync12(cachePath)) {
26102
+ try {
26103
+ const cacheData2 = JSON.parse(readFileSync9(cachePath, "utf-8"));
26104
+ return cacheData2.models;
26105
+ } catch {
26106
+ return [];
26107
+ }
26108
+ }
26109
+ return [];
26110
+ }
26111
+ const responseData = await response.json();
26112
+ const rawModels = Array.isArray(responseData) ? responseData : responseData.data || [];
26113
+ const transformedModels = rawModels.filter((m) => m.mode === "chat" && m.supports_function_calling).map((m) => {
26114
+ const inputCostPerM = (m.input_cost_per_token || 0) * 1e6;
26115
+ const outputCostPerM = (m.output_cost_per_token || 0) * 1e6;
26116
+ const avgCost = (inputCostPerM + outputCostPerM) / 2;
26117
+ const isFree = inputCostPerM === 0 && outputCostPerM === 0;
26118
+ const contextLength = m.max_input_tokens || 128000;
26119
+ const contextStr = contextLength >= 1e6 ? `${Math.round(contextLength / 1e6)}M` : `${Math.round(contextLength / 1000)}K`;
26120
+ return {
26121
+ id: `litellm@${m.model_group}`,
26122
+ name: m.model_group,
26123
+ description: `LiteLLM model (providers: ${m.providers.join(", ")})`,
26124
+ provider: "LiteLLM",
26125
+ pricing: {
26126
+ input: isFree ? "FREE" : `$${inputCostPerM.toFixed(2)}`,
26127
+ output: isFree ? "FREE" : `$${outputCostPerM.toFixed(2)}`,
26128
+ average: isFree ? "FREE" : `$${avgCost.toFixed(2)}/1M`
26129
+ },
26130
+ context: contextStr,
26131
+ contextLength,
26132
+ supportsTools: m.supports_function_calling || false,
26133
+ supportsReasoning: m.supports_reasoning || false,
26134
+ supportsVision: m.supports_vision || false,
26135
+ isFree,
26136
+ source: "LiteLLM"
26137
+ };
26138
+ });
26139
+ mkdirSync8(cacheDir, { recursive: true });
26140
+ const cacheData = {
26141
+ timestamp: new Date().toISOString(),
26142
+ models: transformedModels
26143
+ };
26144
+ writeFileSync8(cachePath, JSON.stringify(cacheData, null, 2), "utf-8");
26145
+ return transformedModels;
26146
+ } catch (error2) {
26147
+ console.error(`Failed to fetch LiteLLM models: ${error2}`);
26148
+ if (existsSync12(cachePath)) {
26149
+ try {
26150
+ const cacheData = JSON.parse(readFileSync9(cachePath, "utf-8"));
26151
+ return cacheData.models;
26152
+ } catch {
26153
+ return [];
26154
+ }
26155
+ }
26156
+ return [];
26157
+ }
26158
+ }
26159
+ var __filename2, __dirname2, _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_CACHE_MAX_AGE_HOURS = 12, RECOMMENDED_FETCH_TIMEOUT_MS = 5000, SEARCH_FETCH_TIMEOUT_MS = 1e4, FIREBASE_SLUG_TO_PROVIDER_NAME, LITELLM_CACHE_MAX_AGE_HOURS = 24;
26160
+ var init_model_loader = __esm(() => {
26161
+ __filename2 = fileURLToPath(import.meta.url);
26162
+ __dirname2 = dirname2(__filename2);
26163
+ FIREBASE_RECOMMENDED_URL = `${FIREBASE_BASE_URL}?catalog=recommended`;
26164
+ RECOMMENDED_MODELS_CACHE_PATH = join14(homedir13(), ".claudish", "recommended-models-cache.json");
26165
+ FIREBASE_SLUG_TO_PROVIDER_NAME = {
26166
+ openai: "openai",
26167
+ google: "google",
26168
+ "x-ai": "xai",
26169
+ "z-ai": "zai",
26170
+ moonshotai: "kimi",
26171
+ minimax: "minimax",
26172
+ qwen: "qwen"
26173
+ };
26174
+ });
26175
+
26052
26176
  // src/handlers/fallback-handler.ts
26053
26177
  class FallbackHandler {
26054
26178
  candidates;
@@ -26567,8 +26691,8 @@ Details: ${e.message}`);
26567
26691
  const credPath = this.getCredentialsPath();
26568
26692
  const claudishDir = join15(homedir14(), ".claudish");
26569
26693
  if (!existsSync13(claudishDir)) {
26570
- const { mkdirSync: mkdirSync10 } = __require("fs");
26571
- mkdirSync10(claudishDir, { recursive: true });
26694
+ const { mkdirSync: mkdirSync9 } = __require("fs");
26695
+ mkdirSync9(claudishDir, { recursive: true });
26572
26696
  }
26573
26697
  const fd = openSync(credPath, "w", 384);
26574
26698
  try {
@@ -27414,8 +27538,8 @@ Details: ${e.message}`);
27414
27538
  const credPath = this.getCredentialsPath();
27415
27539
  const claudishDir = join16(homedir15(), ".claudish");
27416
27540
  if (!existsSync14(claudishDir)) {
27417
- const { mkdirSync: mkdirSync10 } = __require("fs");
27418
- mkdirSync10(claudishDir, { recursive: true });
27541
+ const { mkdirSync: mkdirSync9 } = __require("fs");
27542
+ mkdirSync9(claudishDir, { recursive: true });
27419
27543
  }
27420
27544
  const fd = openSync2(credPath, "w", 384);
27421
27545
  try {
@@ -27717,8 +27841,8 @@ class KimiOAuth {
27717
27841
  const deviceIdPath = this.getDeviceIdPath();
27718
27842
  const claudishDir = join18(homedir17(), ".claudish");
27719
27843
  if (!existsSync16(claudishDir)) {
27720
- const { mkdirSync: mkdirSync10 } = __require("fs");
27721
- mkdirSync10(claudishDir, { recursive: true });
27844
+ const { mkdirSync: mkdirSync9 } = __require("fs");
27845
+ mkdirSync9(claudishDir, { recursive: true });
27722
27846
  }
27723
27847
  if (existsSync16(deviceIdPath)) {
27724
27848
  try {
@@ -27996,8 +28120,8 @@ Waiting for authorization...`);
27996
28120
  const credPath = this.getCredentialsPath();
27997
28121
  const claudishDir = join18(homedir17(), ".claudish");
27998
28122
  if (!existsSync16(claudishDir)) {
27999
- const { mkdirSync: mkdirSync10 } = __require("fs");
28000
- mkdirSync10(claudishDir, { recursive: true });
28123
+ const { mkdirSync: mkdirSync9 } = __require("fs");
28124
+ mkdirSync9(claudishDir, { recursive: true });
28001
28125
  }
28002
28126
  const fd = openSync3(credPath, "w", 384);
28003
28127
  try {
@@ -29338,6 +29462,7 @@ async function createProxyServer(port, openrouterApiKey, model, monitorMode = fa
29338
29462
  port = actualPort;
29339
29463
  log(`[Proxy] Server started on port ${port}`);
29340
29464
  warmPricingCache().catch(() => {});
29465
+ warmRecommendedModels().catch(() => {});
29341
29466
  const catalogProvidersToWarm = ["openrouter"];
29342
29467
  if (process.env.LITELLM_BASE_URL)
29343
29468
  catalogProvidersToWarm.push("litellm");
@@ -29346,7 +29471,7 @@ async function createProxyServer(port, openrouterApiKey, model, monitorMode = fa
29346
29471
  port,
29347
29472
  url: `http://127.0.0.1:${port}`,
29348
29473
  shutdown: async () => {
29349
- return new Promise((resolve3) => server.close((e) => resolve3()));
29474
+ return new Promise((resolve3) => server.close(() => resolve3()));
29350
29475
  }
29351
29476
  };
29352
29477
  }
@@ -29415,47 +29540,10 @@ __export(exports_mcp_server, {
29415
29540
  runPromptViaProxy: () => runPromptViaProxy,
29416
29541
  parseAnthropicSse: () => parseAnthropicSse
29417
29542
  });
29418
- import { readFileSync as readFileSync18, existsSync as existsSync21, writeFileSync as writeFileSync10, mkdirSync as mkdirSync10, readdirSync as readdirSync3 } from "fs";
29543
+ import { readFileSync as readFileSync18, existsSync as existsSync21, writeFileSync as writeFileSync9, mkdirSync as mkdirSync9, readdirSync as readdirSync3 } from "fs";
29419
29544
  import { join as join23, dirname as dirname3 } from "path";
29420
29545
  import { homedir as homedir22 } from "os";
29421
29546
  import { fileURLToPath as fileURLToPath2 } from "url";
29422
- function loadRecommendedModels() {
29423
- const cachedPath = join23(CLAUDISH_CACHE_DIR, "recommended-models-cache.json");
29424
- if (existsSync21(cachedPath)) {
29425
- try {
29426
- const data = JSON.parse(readFileSync18(cachedPath, "utf-8"));
29427
- if (data.models && data.models.length > 0)
29428
- return data.models;
29429
- } catch {}
29430
- }
29431
- if (existsSync21(RECOMMENDED_MODELS_PATH)) {
29432
- try {
29433
- const data = JSON.parse(readFileSync18(RECOMMENDED_MODELS_PATH, "utf-8"));
29434
- return data.models || [];
29435
- } catch {
29436
- return [];
29437
- }
29438
- }
29439
- return [];
29440
- }
29441
- function parsePriceAvg(s) {
29442
- if (!s || s === "N/A")
29443
- return Infinity;
29444
- if (s === "FREE")
29445
- return 0;
29446
- const m = s.match(/\$([\d.]+)/);
29447
- return m ? parseFloat(m[1]) : Infinity;
29448
- }
29449
- function parseCtx(s) {
29450
- if (!s || s === "N/A")
29451
- return 0;
29452
- const upper = s.toUpperCase();
29453
- if (upper.includes("M"))
29454
- return parseFloat(upper) * 1e6;
29455
- if (upper.includes("K"))
29456
- return parseFloat(upper) * 1000;
29457
- return parseInt(s) || 0;
29458
- }
29459
29547
  async function loadAllModels(forceRefresh = false) {
29460
29548
  if (!forceRefresh && existsSync21(ALL_MODELS_CACHE_PATH2)) {
29461
29549
  try {
@@ -29473,8 +29561,8 @@ async function loadAllModels(forceRefresh = false) {
29473
29561
  throw new Error(`API returned ${response.status}`);
29474
29562
  const data = await response.json();
29475
29563
  const models = data.data || [];
29476
- mkdirSync10(CLAUDISH_CACHE_DIR, { recursive: true });
29477
- writeFileSync10(ALL_MODELS_CACHE_PATH2, JSON.stringify({ lastUpdated: new Date().toISOString(), models }), "utf-8");
29564
+ mkdirSync9(CLAUDISH_CACHE_DIR, { recursive: true });
29565
+ writeFileSync9(ALL_MODELS_CACHE_PATH2, JSON.stringify({ lastUpdated: new Date().toISOString(), models }), "utf-8");
29478
29566
  return models;
29479
29567
  } catch {
29480
29568
  if (existsSync21(ALL_MODELS_CACHE_PATH2)) {
@@ -29683,8 +29771,20 @@ Tokens: ${result.usage.input} input, ${result.usage.output} output`;
29683
29771
  inputSchema: { type: "object" },
29684
29772
  group: "low-level",
29685
29773
  handler: async () => {
29686
- const models = loadRecommendedModels();
29687
- if (models.length === 0) {
29774
+ let doc2;
29775
+ try {
29776
+ doc2 = getRecommendedModelsSync();
29777
+ } catch {
29778
+ return {
29779
+ content: [
29780
+ {
29781
+ type: "text",
29782
+ text: "No recommended models found. Try search_models instead."
29783
+ }
29784
+ ]
29785
+ };
29786
+ }
29787
+ if (!doc2.models || doc2.models.length === 0) {
29688
29788
  return {
29689
29789
  content: [
29690
29790
  {
@@ -29694,43 +29794,81 @@ Tokens: ${result.usage.input} input, ${result.usage.output} output`;
29694
29794
  ]
29695
29795
  };
29696
29796
  }
29797
+ const { flagship, fast } = groupRecommendedModels(doc2.models);
29798
+ const providerByName = new Map(BUILTIN_PROVIDERS.map((p) => [p.name, p]));
29799
+ const getNativePrefix = (firebaseSlug) => {
29800
+ const canonical = FIREBASE_SLUG_TO_PROVIDER_NAME[firebaseSlug];
29801
+ if (!canonical)
29802
+ return null;
29803
+ const def = providerByName.get(canonical);
29804
+ if (!def || !def.shortcuts || def.shortcuts.length === 0)
29805
+ return null;
29806
+ return def.shortcuts[0];
29807
+ };
29808
+ const renderGroup = (group) => {
29809
+ const m = group.primary;
29810
+ const pricing = normalizePricingDisplay(m.pricing?.average);
29811
+ const ctx = m.context || "N/A";
29812
+ const caps = [];
29813
+ if (m.supportsTools)
29814
+ caps.push("tools");
29815
+ if (m.supportsReasoning)
29816
+ caps.push("reasoning");
29817
+ if (m.supportsVision)
29818
+ caps.push("vision");
29819
+ const capsLine = caps.length > 0 ? caps.join(", ") : "none";
29820
+ const prefixes = collectRoutingPrefixes(group, getNativePrefix);
29821
+ const accessLine = prefixes.length > 0 ? prefixes.map((p) => `\`${p}@${m.id}\``).join(" \xB7 ") : `\`${m.id}\``;
29822
+ return [
29823
+ `### ${m.id}`,
29824
+ `- **Pricing**: ${pricing} avg \xB7 ${ctx} context`,
29825
+ `- **Capabilities**: ${capsLine}`,
29826
+ `- **Access**: ${accessLine}`,
29827
+ ""
29828
+ ].join(`
29829
+ `);
29830
+ };
29697
29831
  let output = `# Recommended Models
29698
29832
 
29699
29833
  `;
29700
- output += `| Model | Provider | Pricing | Context | Tools | Reasoning | Vision |
29701
- `;
29702
- output += `|-------|----------|---------|---------|-------|-----------|--------|
29834
+ output += `_Last updated: ${doc2.lastUpdated || "unknown"}_
29835
+
29703
29836
  `;
29704
- for (const model of models) {
29705
- const t = model.supportsTools ? "\u2713" : "\xB7";
29706
- const r = model.supportsReasoning ? "\u2713" : "\xB7";
29707
- const v = model.supportsVision ? "\u2713" : "\xB7";
29708
- output += `| ${model.id} | ${model.provider} | ${model.pricing?.average || "N/A"} | ${model.context || "N/A"} | ${t} | ${r} | ${v} |
29837
+ if (flagship.length > 0) {
29838
+ output += `## Flagship models
29839
+
29709
29840
  `;
29841
+ for (const group of flagship)
29842
+ output += renderGroup(group);
29710
29843
  }
29711
- output += `
29712
- ## Quick Picks
29713
- `;
29714
- const cheapest = [...models].sort((a, b) => parsePriceAvg(a.pricing?.average) - parsePriceAvg(b.pricing?.average))[0];
29715
- const bigCtx = [...models].sort((a, b) => parseCtx(b.context) - parseCtx(a.context))[0];
29716
- const priciest = [...models].sort((a, b) => parsePriceAvg(b.pricing?.average) - parsePriceAvg(a.pricing?.average))[0];
29717
- const vision = models.find((m) => m.supportsVision);
29718
- const reasoning = models.find((m) => m.supportsReasoning && m.id !== priciest?.id);
29719
- if (cheapest)
29720
- output += `- **Budget**: \`${cheapest.id}\` (${cheapest.pricing?.average || "N/A"})
29721
- `;
29722
- if (bigCtx && bigCtx.id !== cheapest?.id)
29723
- output += `- **Large context**: \`${bigCtx.id}\` (${bigCtx.context || "N/A"} tokens)
29724
- `;
29725
- if (priciest && priciest.id !== cheapest?.id)
29726
- output += `- **Most advanced**: \`${priciest.id}\` (${priciest.pricing?.average || "N/A"})
29844
+ if (fast.length > 0) {
29845
+ output += `## Fast variants
29846
+
29727
29847
  `;
29728
- if (vision && vision.id !== cheapest?.id && vision.id !== priciest?.id)
29729
- output += `- **Vision + coding**: \`${vision.id}\` (${vision.pricing?.average || "N/A"})
29848
+ for (const group of fast)
29849
+ output += renderGroup(group);
29850
+ }
29851
+ const primaries = [...flagship, ...fast].map((g) => g.primary);
29852
+ const picks = computeQuickPicks(primaries);
29853
+ const pickLines = [];
29854
+ if (picks.budget)
29855
+ pickLines.push(`- **Budget**: \`${picks.budget.id}\` (${normalizePricingDisplay(picks.budget.pricing?.average)})`);
29856
+ if (picks.largeContext)
29857
+ pickLines.push(`- **Large context**: \`${picks.largeContext.id}\` (${picks.largeContext.context || "N/A"})`);
29858
+ if (picks.mostCapable)
29859
+ pickLines.push(`- **Most capable**: \`${picks.mostCapable.id}\``);
29860
+ if (picks.visionCoding)
29861
+ pickLines.push(`- **Vision + coding**: \`${picks.visionCoding.id}\``);
29862
+ if (picks.agentic)
29863
+ pickLines.push(`- **Agentic**: \`${picks.agentic.id}\``);
29864
+ if (pickLines.length > 0) {
29865
+ output += `## Quick picks
29866
+
29730
29867
  `;
29731
- if (reasoning && reasoning.id !== cheapest?.id)
29732
- output += `- **Agentic**: \`${reasoning.id}\` (${reasoning.pricing?.average || "N/A"})
29868
+ output += pickLines.join(`
29869
+ `) + `
29733
29870
  `;
29871
+ }
29734
29872
  return { content: [{ type: "text", text: output }] };
29735
29873
  }
29736
29874
  });
@@ -30354,7 +30492,7 @@ function startMcpServer() {
30354
30492
  process.exit(1);
30355
30493
  });
30356
30494
  }
30357
- var import_dotenv2, __filename3, __dirname3, RECOMMENDED_MODELS_PATH, CLAUDISH_CACHE_DIR, ALL_MODELS_CACHE_PATH2, CACHE_MAX_AGE_DAYS = 2, INSTRUCTIONS = `Claudish MCP server provides access to external AI models (OpenRouter, Ollama, LM Studio, etc.) for coding tasks.
30495
+ var import_dotenv2, __filename3, __dirname3, CLAUDISH_CACHE_DIR, ALL_MODELS_CACHE_PATH2, CACHE_MAX_AGE_DAYS = 2, INSTRUCTIONS = `Claudish MCP server provides access to external AI models (OpenRouter, Ollama, LM Studio, etc.) for coding tasks.
30358
30496
 
30359
30497
  ## Channel Mode \u2014 External Model Sessions
30360
30498
 
@@ -30387,11 +30525,12 @@ var init_mcp_server = __esm(() => {
30387
30525
  init_channel();
30388
30526
  init_proxy_server();
30389
30527
  init_port_manager();
30528
+ init_model_loader();
30529
+ init_provider_definitions();
30390
30530
  import_dotenv2 = __toESM(require_main(), 1);
30391
30531
  import_dotenv2.config();
30392
30532
  __filename3 = fileURLToPath2(import.meta.url);
30393
30533
  __dirname3 = dirname3(__filename3);
30394
- RECOMMENDED_MODELS_PATH = join23(__dirname3, "../recommended-models.json");
30395
30534
  CLAUDISH_CACHE_DIR = join23(homedir22(), ".claudish");
30396
30535
  ALL_MODELS_CACHE_PATH2 = join23(CLAUDISH_CACHE_DIR, "all-models.json");
30397
30536
  });
@@ -41667,7 +41806,7 @@ var init_RemoveFileError = __esm(() => {
41667
41806
 
41668
41807
  // ../../node_modules/.bun/@inquirer+external-editor@2.0.1+04f2146be16c61ef/node_modules/@inquirer/external-editor/dist/index.js
41669
41808
  import { spawn as spawn3, spawnSync } from "child_process";
41670
- import { readFileSync as readFileSync19, unlinkSync as unlinkSync6, writeFileSync as writeFileSync11 } from "fs";
41809
+ import { readFileSync as readFileSync19, unlinkSync as unlinkSync6, writeFileSync as writeFileSync10 } from "fs";
41671
41810
  import path from "path";
41672
41811
  import os from "os";
41673
41812
  import { randomUUID as randomUUID3 } from "crypto";
@@ -41776,7 +41915,7 @@ class ExternalEditor {
41776
41915
  if (Object.prototype.hasOwnProperty.call(this.fileOptions, "mode")) {
41777
41916
  opt.mode = this.fileOptions.mode;
41778
41917
  }
41779
- writeFileSync11(this.tempFile, this.text, opt);
41918
+ writeFileSync10(this.tempFile, this.text, opt);
41780
41919
  } catch (createFileError) {
41781
41920
  throw new CreateFileError(createFileError);
41782
41921
  }
@@ -43190,50 +43329,6 @@ var init_config = __esm(() => {
43190
43329
  };
43191
43330
  });
43192
43331
 
43193
- // src/utils.ts
43194
- function fuzzyScore2(text, query) {
43195
- if (!text || !query)
43196
- return 0;
43197
- const t = text.toLowerCase();
43198
- const q = query.toLowerCase();
43199
- if (t === q)
43200
- return 1;
43201
- if (t.startsWith(q))
43202
- return 0.9;
43203
- if (t.includes(` ${q}`) || t.includes(`-${q}`) || t.includes(`/${q}`))
43204
- return 0.8;
43205
- if (t.includes(q))
43206
- return 0.6;
43207
- const normSep = (s) => s.replace(/[\s\-_.]/g, "");
43208
- const tn = normSep(t);
43209
- const qn = normSep(q);
43210
- if (tn === qn)
43211
- return 0.95;
43212
- if (tn.startsWith(qn))
43213
- return 0.85;
43214
- if (tn.includes(qn))
43215
- return 0.65;
43216
- let score = 0;
43217
- let tIdx = 0;
43218
- let qIdx = 0;
43219
- let consecutive = 0;
43220
- while (tIdx < t.length && qIdx < q.length) {
43221
- if (t[tIdx] === q[qIdx]) {
43222
- score += 1 + consecutive * 0.5;
43223
- consecutive++;
43224
- qIdx++;
43225
- } else {
43226
- consecutive = 0;
43227
- }
43228
- tIdx++;
43229
- }
43230
- if (qIdx === q.length) {
43231
- const compactness = q.length / (tIdx + 1);
43232
- return 0.1 + 0.4 * compactness * (score / (q.length * 2));
43233
- }
43234
- return 0;
43235
- }
43236
-
43237
43332
  // src/providers/api-key-map.ts
43238
43333
  var API_KEY_MAP;
43239
43334
  var init_api_key_map = __esm(() => {
@@ -43582,7 +43677,7 @@ import os2 from "os";
43582
43677
  import path2 from "path";
43583
43678
  import { EventEmitter as EventEmitter3 } from "events";
43584
43679
  import { dlopen, toArrayBuffer as toArrayBuffer4, JSCallback, ptr as ptr4 } from "bun:ffi";
43585
- import { existsSync as existsSync23, writeFileSync as writeFileSync12 } from "fs";
43680
+ import { existsSync as existsSync23, writeFileSync as writeFileSync11 } from "fs";
43586
43681
  import { EventEmitter as EventEmitter4 } from "events";
43587
43682
  import { toArrayBuffer, ptr } from "bun:ffi";
43588
43683
  import { ptr as ptr2, toArrayBuffer as toArrayBuffer2 } from "bun:ffi";
@@ -48928,7 +49023,7 @@ function convertToDebugSymbols(symbols) {
48928
49023
  if (env.OTUI_DEBUG_FFI && globalFFILogPath) {
48929
49024
  const logPath = globalFFILogPath;
48930
49025
  const writeSync4 = (msg) => {
48931
- writeFileSync12(logPath, msg + `
49026
+ writeFileSync11(logPath, msg + `
48932
49027
  `, { flag: "a" });
48933
49028
  };
48934
49029
  Object.entries(symbols).forEach(([key, value]) => {
@@ -100262,9 +100357,8 @@ __export(exports_cli, {
100262
100357
  });
100263
100358
  import {
100264
100359
  readFileSync as readFileSync20,
100265
- writeFileSync as writeFileSync13,
100266
100360
  existsSync as existsSync24,
100267
- mkdirSync as mkdirSync11,
100361
+ mkdirSync as mkdirSync10,
100268
100362
  copyFileSync,
100269
100363
  readdirSync as readdirSync4,
100270
100364
  unlinkSync as unlinkSync7
@@ -100279,7 +100373,7 @@ function clearAllModelCaches() {
100279
100373
  const cacheDir = join25(homedir23(), ".claudish");
100280
100374
  if (!existsSync24(cacheDir))
100281
100375
  return;
100282
- const cachePatterns = ["all-models.json", "pricing-cache.json"];
100376
+ const cachePatterns = ["pricing-cache.json", "recommended-models-cache.json"];
100283
100377
  let cleared = 0;
100284
100378
  try {
100285
100379
  const files = readdirSync4(cacheDir);
@@ -100477,25 +100571,54 @@ async function parseArgs(args) {
100477
100571
  const forceUpdate = args.includes("--force-update");
100478
100572
  if (forceUpdate)
100479
100573
  clearAllModelCaches();
100480
- await checkAndUpdateModelsCache(forceUpdate);
100481
- if (hasJsonFlag) {
100482
- printAvailableModelsJSON();
100483
- } else {
100484
- printAvailableModels();
100485
- }
100574
+ await printRecommendedModels(hasJsonFlag, forceUpdate);
100486
100575
  process.exit(0);
100576
+ } else if (arg === "--list-providers") {
100577
+ const hasJsonFlag = args.includes("--json");
100578
+ try {
100579
+ const providers = await getProviderList();
100580
+ if (hasJsonFlag) {
100581
+ console.log(JSON.stringify({ providers, total: providers.length }, null, 2));
100582
+ } else {
100583
+ console.log(`
100584
+ Providers in Firebase catalog:
100585
+ `);
100586
+ console.log(" Slug Active models");
100587
+ console.log(" " + "\u2500".repeat(40));
100588
+ for (const { slug, count } of providers) {
100589
+ console.log(` ${slug.padEnd(20)} ${String(count).padStart(5)}`);
100590
+ }
100591
+ console.log(`
100592
+ Usage: claudish --list-models --provider <slug>`);
100593
+ console.log(` claudish -s <query> (fuzzy search)
100594
+ `);
100595
+ }
100596
+ process.exit(0);
100597
+ } catch (err) {
100598
+ console.error(`Failed to fetch providers: ${err instanceof Error ? err.message : String(err)}`);
100599
+ process.exit(1);
100600
+ }
100487
100601
  } else if (arg === "--models" || arg === "--list-models" || arg === "-s" || arg === "--search") {
100488
100602
  const nextArg = args[i + 1];
100489
100603
  const hasQuery = nextArg && !nextArg.startsWith("--");
100490
100604
  const query = hasQuery ? args[++i] : null;
100491
100605
  const hasJsonFlag = args.includes("--json");
100492
100606
  const forceUpdate = args.includes("--force-update");
100607
+ const providerIdx = args.indexOf("--provider");
100608
+ const providerSlug = providerIdx !== -1 && providerIdx + 1 < args.length ? args[providerIdx + 1] : null;
100493
100609
  if (forceUpdate)
100494
100610
  clearAllModelCaches();
100611
+ if (query && providerSlug) {
100612
+ console.error("Use --provider together with --list-models (without a query) to filter the catalog.");
100613
+ console.error("For keyword search, drop --provider: claudish -s <query>");
100614
+ process.exit(1);
100615
+ }
100495
100616
  if (query) {
100496
- await searchAndPrintModels(query, forceUpdate);
100617
+ await searchAndPrintModels(query, hasJsonFlag);
100618
+ } else if (providerSlug) {
100619
+ await printByProvider(providerSlug, hasJsonFlag);
100497
100620
  } else {
100498
- await printAllModels(hasJsonFlag, forceUpdate);
100621
+ await printTop100(hasJsonFlag);
100499
100622
  }
100500
100623
  process.exit(0);
100501
100624
  } else if (arg === "--summarize-tools") {
@@ -100618,502 +100741,298 @@ async function fetchOllamaModels() {
100618
100741
  return [];
100619
100742
  }
100620
100743
  }
100621
- async function searchAndPrintModels(query, forceUpdate) {
100622
- let models = [];
100623
- if (!forceUpdate && existsSync24(ALL_MODELS_JSON_PATH)) {
100624
- try {
100625
- const cacheData = JSON.parse(readFileSync20(ALL_MODELS_JSON_PATH, "utf-8"));
100626
- const lastUpdated = new Date(cacheData.lastUpdated);
100627
- const now = new Date;
100628
- const ageInDays = (now.getTime() - lastUpdated.getTime()) / (1000 * 60 * 60 * 24);
100629
- if (ageInDays <= CACHE_MAX_AGE_DAYS2) {
100630
- models = cacheData.models;
100631
- }
100632
- } catch (e) {}
100744
+ function formatModelDocPricing(pricing) {
100745
+ if (!pricing)
100746
+ return "N/A";
100747
+ const input = typeof pricing.input === "number" ? pricing.input : undefined;
100748
+ const output = typeof pricing.output === "number" ? pricing.output : undefined;
100749
+ if (input === undefined && output === undefined)
100750
+ return "N/A";
100751
+ if ((input ?? 0) === 0 && (output ?? 0) === 0)
100752
+ return "FREE";
100753
+ const avg = ((input ?? 0) + (output ?? 0)) / 2;
100754
+ return `$${avg.toFixed(2)}/1M`;
100755
+ }
100756
+ function formatModelDocContext(ctx) {
100757
+ if (!ctx || ctx <= 0)
100758
+ return "N/A";
100759
+ if (ctx >= 1e6)
100760
+ return `${Math.round(ctx / 1e6)}M`;
100761
+ return `${Math.round(ctx / 1000)}K`;
100762
+ }
100763
+ function formatModelDocCaps(caps) {
100764
+ if (!caps)
100765
+ return "\xB7";
100766
+ const parts = [];
100767
+ if (caps.tools)
100768
+ parts.push("T");
100769
+ if (caps.thinking)
100770
+ parts.push("R");
100771
+ if (caps.vision)
100772
+ parts.push("V");
100773
+ return parts.length > 0 ? parts.join("") : "\xB7";
100774
+ }
100775
+ async function searchAndPrintModels(query, jsonOutput) {
100776
+ let results;
100777
+ try {
100778
+ console.error(`\uD83D\uDD04 Searching Firebase catalog for "${query}"...`);
100779
+ results = await searchModels(query, 50);
100780
+ } catch (error2) {
100781
+ console.error(`\u274C Failed to reach Firebase model catalog: ${error2 instanceof Error ? error2.message : String(error2)}`);
100782
+ console.error(" Check your network connection.");
100783
+ process.exit(1);
100633
100784
  }
100634
- if (models.length === 0) {
100635
- console.error("\uD83D\uDD04 Fetching all models from OpenRouter (this may take a moment)...");
100636
- try {
100637
- const response = await fetch("https://openrouter.ai/api/v1/models");
100638
- if (!response.ok)
100639
- throw new Error(`API returned ${response.status}`);
100640
- const data = await response.json();
100641
- models = data.data;
100642
- mkdirSync11(CLAUDISH_CACHE_DIR2, { recursive: true });
100643
- writeFileSync13(ALL_MODELS_JSON_PATH, JSON.stringify({
100644
- lastUpdated: new Date().toISOString(),
100645
- models
100646
- }), "utf-8");
100647
- console.error(`\u2705 Cached ${models.length} models`);
100648
- } catch (error2) {
100649
- console.error(`\u274C Failed to fetch models: ${error2}`);
100650
- process.exit(1);
100785
+ if (results.length === 0) {
100786
+ if (jsonOutput) {
100787
+ console.log(JSON.stringify({ query, count: 0, models: [] }, null, 2));
100788
+ } else {
100789
+ console.log(`No models found matching "${query}"`);
100651
100790
  }
100791
+ return;
100652
100792
  }
100653
- const ollamaModels = await fetchOllamaModels();
100654
- if (ollamaModels.length > 0) {
100655
- console.error(`\uD83C\uDFE0 Found ${ollamaModels.length} local Ollama models`);
100656
- models = [...ollamaModels, ...models];
100657
- }
100658
- if (process.env.OPENAI_API_KEY) {
100659
- try {
100660
- const modelsDevResponse = await fetch("https://models.dev/api.json", {
100661
- signal: AbortSignal.timeout(5000)
100662
- });
100663
- if (modelsDevResponse.ok) {
100664
- const modelsDevData = await modelsDevResponse.json();
100665
- const openaiData = modelsDevData.openai;
100666
- if (openaiData?.models) {
100667
- const openaiModels = Object.entries(openaiData.models).filter(([id, _2]) => {
100668
- const lowerId = id.toLowerCase();
100669
- return lowerId.startsWith("gpt-") || lowerId.startsWith("o1-") || lowerId.startsWith("o3-") || lowerId.startsWith("o4-") || lowerId.startsWith("chatgpt-");
100670
- }).map(([id, m2]) => {
100671
- const inputCost = m2.cost?.input || 2;
100672
- const outputCost = m2.cost?.output || 8;
100673
- const contextLen = m2.limit?.context || 128000;
100674
- const inputModalities = m2.modalities?.input || [];
100675
- return {
100676
- id: `oai/${id}`,
100677
- name: m2.name || id,
100678
- description: `OpenAI direct model`,
100679
- context_length: contextLen,
100680
- pricing: {
100681
- prompt: String(inputCost / 1e6),
100682
- completion: String(outputCost / 1e6)
100683
- },
100684
- isOAIDirect: true,
100685
- supportsTools: m2.tool_call === true,
100686
- supportsReasoning: m2.reasoning === true,
100687
- supportsVision: inputModalities.includes("image") || inputModalities.includes("video")
100688
- };
100689
- });
100690
- console.error(`\uD83D\uDD11 Found ${openaiModels.length} OpenAI direct models`);
100691
- models = [...openaiModels, ...models];
100692
- }
100693
- }
100694
- } catch {}
100695
- }
100696
- if (process.env.GLM_CODING_API_KEY) {
100697
- try {
100698
- const glmCodingModels = await fetchGLMCodingModels();
100699
- if (glmCodingModels.length > 0) {
100700
- console.error(`\uD83D\uDD11 Found ${glmCodingModels.length} GLM Coding Plan models`);
100701
- models = [...glmCodingModels, ...models];
100702
- }
100703
- } catch {}
100704
- }
100705
- if (process.env.LITELLM_BASE_URL && process.env.LITELLM_API_KEY) {
100706
- try {
100707
- const litellmModels = await fetchLiteLLMModels(process.env.LITELLM_BASE_URL, process.env.LITELLM_API_KEY, forceUpdate);
100708
- if (litellmModels.length > 0) {
100709
- console.error(`\uD83D\uDD17 Found ${litellmModels.length} LiteLLM models`);
100710
- models = [...litellmModels, ...models];
100711
- }
100712
- } catch {}
100713
- }
100714
- const results = models.map((model) => {
100715
- const nameScore = fuzzyScore2(model.name || "", query);
100716
- const idScore = fuzzyScore2(model.id || "", query);
100717
- const descScore = fuzzyScore2(model.description || "", query) * 0.5;
100718
- return {
100719
- model,
100720
- score: Math.max(nameScore, idScore, descScore)
100721
- };
100722
- }).filter((item) => item.score > 0.2).sort((a, b2) => b2.score - a.score).slice(0, 20);
100723
- if (results.length === 0) {
100724
- console.log(`No models found matching "${query}"`);
100793
+ if (jsonOutput) {
100794
+ console.log(JSON.stringify({
100795
+ query,
100796
+ count: results.length,
100797
+ models: results.map((m2) => ({
100798
+ id: m2.modelId,
100799
+ provider: m2.provider,
100800
+ contextWindow: m2.contextWindow,
100801
+ pricing: m2.pricing,
100802
+ capabilities: m2.capabilities,
100803
+ aliases: m2.aliases,
100804
+ status: m2.status
100805
+ }))
100806
+ }, null, 2));
100725
100807
  return;
100726
100808
  }
100727
- const RED2 = "\x1B[31m";
100728
- const GREEN = "\x1B[32m";
100729
- const RESET = "\x1B[0m";
100730
- const DIM = "\x1B[2m";
100731
100809
  console.log(`
100732
100810
  Found ${results.length} matching models:
100733
100811
  `);
100734
- console.log(" Model Provider Pricing Context Score");
100812
+ console.log(" Model Provider Pricing Context Caps");
100735
100813
  console.log(" " + "\u2500".repeat(80));
100736
- for (const { model, score } of results) {
100737
- let fullModelId;
100738
- if (model.isLocal) {
100739
- fullModelId = model.id.replace("ollama/", "ollama@");
100740
- } else if (model.id.startsWith("zen/")) {
100741
- fullModelId = model.id.replace("zen/", "zen@");
100742
- } else if (model.id.startsWith("oai/") || model.isOAIDirect) {
100743
- fullModelId = model.id.replace("oai/", "oai@");
100744
- } else if (model.source === "LiteLLM" || model.id.startsWith("litellm@")) {
100745
- fullModelId = model.id;
100746
- } else {
100747
- fullModelId = `openrouter@${model.id}`;
100748
- }
100749
- const modelId = fullModelId.length > 30 ? fullModelId.substring(0, 27) + "..." : fullModelId;
100750
- const modelIdPadded = modelId.padEnd(30);
100751
- const providerName = model.id.split("/")[0];
100752
- const provider = providerName.length > 10 ? providerName.substring(0, 7) + "..." : providerName;
100753
- const providerPadded = provider.padEnd(10);
100754
- let pricing;
100755
- if (model.isLocal) {
100756
- pricing = "LOCAL";
100757
- } else {
100758
- const promptPrice = parseFloat(model.pricing?.prompt || "0") * 1e6;
100759
- const completionPrice = parseFloat(model.pricing?.completion || "0") * 1e6;
100760
- const avg = (promptPrice + completionPrice) / 2;
100761
- if (avg < 0) {
100762
- pricing = "varies";
100763
- } else if (avg === 0) {
100764
- pricing = "FREE";
100765
- } else {
100766
- pricing = `$${avg.toFixed(2)}/1M`;
100767
- }
100768
- }
100769
- const pricingPadded = pricing.padEnd(10);
100770
- const contextLen = model.context_length || model.top_provider?.context_length || 0;
100771
- const context = contextLen > 0 ? `${Math.round(contextLen / 1000)}K` : "N/A";
100772
- const contextPadded = context.padEnd(7);
100773
- if (model.isLocal && model.supportsTools === false) {
100774
- console.log(` ${RED2}${modelIdPadded} ${providerPadded} ${pricingPadded} ${contextPadded} ${(score * 100).toFixed(0)}% \u2717 no tools${RESET}`);
100775
- } else if (model.isLocal && model.supportsTools === true) {
100776
- console.log(` ${GREEN}${modelIdPadded}${RESET} ${providerPadded} ${pricingPadded} ${contextPadded} ${(score * 100).toFixed(0)}%`);
100777
- } else {
100778
- console.log(` ${modelIdPadded} ${providerPadded} ${pricingPadded} ${contextPadded} ${(score * 100).toFixed(0)}%`);
100779
- }
100814
+ for (const m2 of results) {
100815
+ const id = m2.modelId.length > 30 ? m2.modelId.substring(0, 27) + "..." : m2.modelId;
100816
+ const idPadded = id.padEnd(30);
100817
+ const prov = (m2.provider || "").padEnd(10);
100818
+ const price = formatModelDocPricing(m2.pricing).padEnd(10);
100819
+ const ctx = formatModelDocContext(m2.contextWindow).padEnd(7);
100820
+ const caps = formatModelDocCaps(m2.capabilities);
100821
+ console.log(` ${idPadded} ${prov} ${price} ${ctx} ${caps}`);
100780
100822
  }
100781
100823
  console.log("");
100782
- console.log(`${DIM}Local models: ${RED2}red${RESET}${DIM} = no tool support (incompatible), ${GREEN}green${RESET}${DIM} = compatible${RESET}`);
100824
+ console.log("Caps: T = tools R = reasoning V = vision");
100783
100825
  console.log("");
100784
- console.log("Use OpenRouter model: claudish --model openrouter@<provider/model-id>");
100785
- console.log("OpenAI direct model: claudish --model oai@<model-name>");
100786
- console.log("Local Ollama model: claudish --model ollama@<model-name>");
100787
- console.log("OpenCode Zen model: claudish --model zen@<model-id>");
100788
- console.log("LiteLLM proxy model: claudish --model litellm@<model-group>");
100789
- }
100790
- async function printAllModels(jsonOutput, forceUpdate) {
100791
- let models = [];
100792
- const [ollamaModels, zenModels] = await Promise.all([fetchOllamaModels(), fetchZenModels()]);
100793
- if (!forceUpdate && existsSync24(ALL_MODELS_JSON_PATH)) {
100826
+ console.log("Use any model by its ID: claudish --model <model-id>");
100827
+ console.log("Provider shortcuts: claudish --model or@<id> | google@<id> | oai@<id>");
100828
+ }
100829
+ function renderModelDocTable(models, showRank) {
100830
+ const header = showRank ? " # Model Provider Pricing Context Caps" : " Model Provider Pricing Context Caps";
100831
+ console.log(header);
100832
+ console.log(" " + "\u2500".repeat(80));
100833
+ for (const m2 of models) {
100834
+ const rankCell = showRank ? String(m2.rank ?? "").padStart(3) + " " : " ";
100835
+ const rawId = m2.modelId;
100836
+ const id = rawId.length > 30 ? rawId.substring(0, 27) + "..." : rawId;
100837
+ const idPadded = id.padEnd(30);
100838
+ const prov = (m2.provider || "").padEnd(10);
100839
+ const price = formatModelDocPricing(m2.pricing).padEnd(10);
100840
+ const ctx = formatModelDocContext(m2.contextWindow).padEnd(7);
100841
+ const caps = formatModelDocCaps(m2.capabilities);
100842
+ console.log(` ${rankCell}${idPadded} ${prov} ${price} ${ctx} ${caps}`);
100843
+ }
100844
+ }
100845
+ async function printLocalProvidersFooter() {
100846
+ console.log(`
100847
+ Local providers`);
100848
+ console.log(" " + "\u2500".repeat(70));
100849
+ let ollamaLine = " Ollama: not running";
100850
+ try {
100851
+ const ollamaModels = await fetchOllamaModels();
100852
+ if (ollamaModels.length > 0) {
100853
+ const toolCount = ollamaModels.filter((m2) => m2.supportsTools).length;
100854
+ ollamaLine = ` Ollama: ${ollamaModels.length} models installed (${toolCount} with tools) \u2014 use: claudish --model ollama@<name>`;
100855
+ }
100856
+ } catch {}
100857
+ console.log(ollamaLine);
100858
+ let litellmLine = " LiteLLM: not configured (set LITELLM_BASE_URL + LITELLM_API_KEY)";
100859
+ if (process.env.LITELLM_BASE_URL && process.env.LITELLM_API_KEY) {
100794
100860
  try {
100795
- const cacheData = JSON.parse(readFileSync20(ALL_MODELS_JSON_PATH, "utf-8"));
100796
- const lastUpdated = new Date(cacheData.lastUpdated);
100797
- const now = new Date;
100798
- const ageInDays = (now.getTime() - lastUpdated.getTime()) / (1000 * 60 * 60 * 24);
100799
- if (ageInDays <= CACHE_MAX_AGE_DAYS2) {
100800
- models = cacheData.models;
100801
- if (!jsonOutput) {
100802
- console.error(`\u2713 Using cached models (last updated: ${cacheData.lastUpdated.split("T")[0]})`);
100803
- }
100861
+ const litellmModels = await fetchLiteLLMModels(process.env.LITELLM_BASE_URL, process.env.LITELLM_API_KEY, false);
100862
+ if (litellmModels.length > 0) {
100863
+ litellmLine = ` LiteLLM: ${litellmModels.length} model groups configured \u2014 use: claudish --model litellm@<group>`;
100864
+ } else {
100865
+ litellmLine = " LiteLLM: reachable but no model groups returned";
100804
100866
  }
100805
- } catch (e) {}
100806
- }
100807
- if (models.length === 0) {
100808
- console.error("\uD83D\uDD04 Fetching all models from OpenRouter...");
100809
- try {
100810
- const response = await fetch("https://openrouter.ai/api/v1/models");
100811
- if (!response.ok)
100812
- throw new Error(`API returned ${response.status}`);
100813
- const data = await response.json();
100814
- models = data.data;
100815
- mkdirSync11(CLAUDISH_CACHE_DIR2, { recursive: true });
100816
- writeFileSync13(ALL_MODELS_JSON_PATH, JSON.stringify({
100817
- lastUpdated: new Date().toISOString(),
100818
- models
100819
- }), "utf-8");
100820
- console.error(`\u2705 Cached ${models.length} models`);
100821
- } catch (error2) {
100822
- console.error(`\u274C Failed to fetch models: ${error2}`);
100823
- process.exit(1);
100867
+ } catch {
100868
+ litellmLine = " LiteLLM: configured but unreachable";
100824
100869
  }
100825
100870
  }
100871
+ console.log(litellmLine);
100872
+ }
100873
+ async function printTop100(jsonOutput) {
100874
+ let response;
100875
+ try {
100876
+ response = await getTop100Models();
100877
+ } catch (error2) {
100878
+ console.error(`\u274C Failed to load top-100 models from Firebase: ${error2 instanceof Error ? error2.message : String(error2)}`);
100879
+ console.error(" Check your network connection.");
100880
+ process.exit(1);
100881
+ }
100826
100882
  if (jsonOutput) {
100827
- const allModels = [...ollamaModels, ...zenModels, ...models];
100828
- console.log(JSON.stringify({
100829
- count: allModels.length,
100830
- localCount: ollamaModels.length,
100831
- zenCount: zenModels.length,
100832
- lastUpdated: new Date().toISOString().split("T")[0],
100833
- models: allModels.map((m2) => {
100834
- let id;
100835
- if (m2.isLocal) {
100836
- id = m2.id.replace("ollama/", "ollama@");
100837
- } else if (m2.isZen || m2.id.startsWith("zen/")) {
100838
- id = m2.id.replace("zen/", "zen@");
100839
- } else if (m2.source === "LiteLLM" || m2.id.startsWith("litellm@")) {
100840
- id = m2.id;
100841
- } else {
100842
- id = `openrouter@${m2.id}`;
100843
- }
100844
- return {
100845
- id,
100846
- name: m2.name,
100847
- context: m2.context_length || m2.top_provider?.context_length,
100848
- pricing: m2.pricing,
100849
- isLocal: m2.isLocal || false,
100850
- isZen: m2.isZen || false
100851
- };
100852
- })
100853
- }, null, 2));
100883
+ console.log(JSON.stringify(response, null, 2));
100854
100884
  return;
100855
100885
  }
100856
- const RED2 = "\x1B[31m";
100857
- const GREEN = "\x1B[32m";
100858
- const RESET = "\x1B[0m";
100859
- const DIM = "\x1B[2m";
100860
- if (ollamaModels.length > 0) {
100861
- const toolCapableCount = ollamaModels.filter((m2) => m2.supportsTools).length;
100862
- console.log(`
100863
- \uD83C\uDFE0 LOCAL OLLAMA MODELS (${ollamaModels.length} installed, ${toolCapableCount} with tool support):
100886
+ console.log(`
100887
+ Top ${response.total} models from Firebase (pool: ${response.poolSize} eligible)
100864
100888
  `);
100865
- console.log(" Model Size Params Tools");
100866
- console.log(" " + "\u2500".repeat(76));
100867
- for (const model of ollamaModels) {
100868
- const fullId = model.id.replace("ollama/", "ollama@");
100869
- const modelId = fullId.length > 35 ? fullId.substring(0, 32) + "..." : fullId;
100870
- const modelIdPadded = modelId.padEnd(38);
100871
- const size = model.size ? `${(model.size / 1e9).toFixed(1)}GB` : "N/A";
100872
- const sizePadded = size.padEnd(12);
100873
- const params = model.details?.parameter_size || "N/A";
100874
- const paramsPadded = params.padEnd(8);
100875
- if (model.supportsTools) {
100876
- console.log(` ${modelIdPadded} ${sizePadded} ${paramsPadded} ${GREEN}\u2713${RESET}`);
100877
- } else {
100878
- console.log(` ${RED2}${modelIdPadded} ${sizePadded} ${paramsPadded} \u2717 no tools${RESET}`);
100879
- }
100880
- }
100881
- console.log("");
100882
- console.log(` ${GREEN}\u2713${RESET} = Compatible with Claude Code (supports tool calling)`);
100883
- console.log(` ${RED2}\u2717${RESET} = Not compatible ${DIM}(Claude Code requires tool support)${RESET}`);
100884
- console.log("");
100885
- console.log(" Use: claudish --model ollama@<model-name>");
100886
- console.log(" Pull a compatible model: ollama pull llama3.2");
100889
+ if (response.models.length === 0) {
100890
+ console.log(" No eligible models in the catalog.");
100887
100891
  } else {
100888
- console.log(`
100889
- \uD83C\uDFE0 LOCAL OLLAMA: Not running or no models installed`);
100890
- console.log(" Start Ollama: ollama serve");
100891
- console.log(" Pull a model: ollama pull llama3.2");
100892
- }
100893
- if (zenModels.length > 0) {
100894
- const freeCount = zenModels.filter((m2) => m2.isFree).length;
100895
- console.log(`
100896
- \uD83D\uDD2E OPENCODE ZEN (${zenModels.length} models, ${freeCount} FREE - no API key needed):
100897
- `);
100898
- console.log(" Model Context Pricing Tools");
100899
- console.log(" " + "\u2500".repeat(68));
100900
- const sortedModels = [...zenModels].sort((a, b2) => {
100901
- if (a.isFree && !b2.isFree)
100902
- return -1;
100903
- if (!a.isFree && b2.isFree)
100904
- return 1;
100905
- return (b2.context_length || 0) - (a.context_length || 0);
100906
- });
100907
- for (const model of sortedModels) {
100908
- const fullId = model.id.replace("zen/", "zen@");
100909
- const modelId = fullId.length > 30 ? fullId.substring(0, 27) + "..." : fullId;
100910
- const modelIdPadded = modelId.padEnd(32);
100911
- const contextLen = model.context_length || 0;
100912
- const context = contextLen > 0 ? `${Math.round(contextLen / 1000)}K` : "N/A";
100913
- const contextPadded = context.padEnd(10);
100914
- const pricing = model.isFree ? `${GREEN}FREE${RESET}` : `$${(parseFloat(model.pricing?.prompt || "0") + parseFloat(model.pricing?.completion || "0")).toFixed(1)}/M`;
100915
- const pricingPadded = model.isFree ? "FREE " : pricing.padEnd(12);
100916
- const tools = model.supportsTools ? `${GREEN}\u2713${RESET}` : `${RED2}\u2717${RESET}`;
100917
- console.log(` ${modelIdPadded} ${contextPadded} ${pricingPadded} ${tools}`);
100918
- }
100892
+ renderModelDocTable(response.models, true);
100919
100893
  console.log("");
100920
- console.log(` ${DIM}FREE models work without API key!${RESET}`);
100921
- console.log(" Use: claudish --model zen@<model-id>");
100894
+ console.log(" Caps: T = tools R = reasoning V = vision");
100922
100895
  }
100923
- if (process.env.LITELLM_BASE_URL && process.env.LITELLM_API_KEY) {
100924
- try {
100925
- const litellmModels = await fetchLiteLLMModels(process.env.LITELLM_BASE_URL, process.env.LITELLM_API_KEY, forceUpdate);
100926
- if (litellmModels.length > 0) {
100927
- console.log(`
100928
- \uD83D\uDD17 LITELLM PROXY (${litellmModels.length} model groups):
100929
- `);
100930
- console.log(" Model Context Pricing Tools");
100931
- console.log(" " + "\u2500".repeat(68));
100932
- for (const model of litellmModels) {
100933
- const modelId = model.id.length > 30 ? model.id.substring(0, 27) + "..." : model.id;
100934
- const modelIdPadded = modelId.padEnd(32);
100935
- const contextPadded = (model.context || "N/A").padEnd(10);
100936
- const pricingStr = model.isFree ? `${GREEN}FREE${RESET}` : model.pricing?.average || "N/A";
100937
- const pricingPadded = model.isFree ? "FREE " : pricingStr.padEnd(12);
100938
- const tools = model.supportsTools ? `${GREEN}\u2713${RESET}` : `${RED2}\u2717${RESET}`;
100939
- console.log(` ${modelIdPadded} ${contextPadded} ${pricingPadded} ${tools}`);
100940
- }
100941
- console.log("");
100942
- console.log(" Use: claudish --model litellm@<model-group>");
100943
- }
100944
- } catch {}
100896
+ await printLocalProvidersFooter();
100897
+ console.log("");
100898
+ console.log("Filter by provider: claudish --list-models --provider <slug>");
100899
+ console.log(" (e.g. opencode-zen, anthropic, openai, google, x-ai)");
100900
+ console.log("All providers: claudish --list-providers");
100901
+ console.log("Search by keyword: claudish -s <query>");
100902
+ console.log("Top recommended: claudish --top-models");
100903
+ console.log("");
100904
+ }
100905
+ async function printByProvider(providerSlug, jsonOutput) {
100906
+ let models;
100907
+ try {
100908
+ models = await getModelsByProvider(providerSlug, 200);
100909
+ } catch (error2) {
100910
+ console.error(`\u274C Failed to load provider catalog from Firebase: ${error2 instanceof Error ? error2.message : String(error2)}`);
100911
+ console.error(" Check your network connection.");
100912
+ process.exit(1);
100945
100913
  }
100946
- const byProvider = new Map;
100947
- for (const model of models) {
100948
- const provider = model.id.split("/")[0];
100949
- if (!byProvider.has(provider)) {
100950
- byProvider.set(provider, []);
100951
- }
100952
- byProvider.get(provider).push(model);
100914
+ if (jsonOutput) {
100915
+ console.log(JSON.stringify({ provider: providerSlug, count: models.length, models }, null, 2));
100916
+ return;
100953
100917
  }
100954
- const sortedProviders = [...byProvider.keys()].sort();
100955
- console.log(`
100956
- \u2601\uFE0F OPENROUTER MODELS (${models.length} total):
100957
- `);
100958
- for (const provider of sortedProviders) {
100959
- const providerModels = byProvider.get(provider);
100918
+ if (models.length === 0) {
100960
100919
  console.log(`
100961
- ${provider.toUpperCase()} (${providerModels.length} models)`);
100962
- console.log(" " + "\u2500".repeat(70));
100963
- for (const model of providerModels) {
100964
- const fullId = `openrouter@${model.id}`;
100965
- const modelId = fullId.length > 40 ? fullId.substring(0, 37) + "..." : fullId;
100966
- const modelIdPadded = modelId.padEnd(42);
100967
- const promptPrice = parseFloat(model.pricing?.prompt || "0") * 1e6;
100968
- const completionPrice = parseFloat(model.pricing?.completion || "0") * 1e6;
100969
- const avg = (promptPrice + completionPrice) / 2;
100970
- let pricing;
100971
- if (avg < 0) {
100972
- pricing = "varies";
100973
- } else if (avg === 0) {
100974
- pricing = "FREE";
100975
- } else {
100976
- pricing = `$${avg.toFixed(2)}/1M`;
100977
- }
100978
- const pricingPadded = pricing.padEnd(12);
100979
- const contextLen = model.context_length || model.top_provider?.context_length || 0;
100980
- const context = contextLen > 0 ? `${Math.round(contextLen / 1000)}K` : "N/A";
100981
- const contextPadded = context.padEnd(8);
100982
- console.log(` ${modelIdPadded} ${pricingPadded} ${contextPadded}`);
100983
- }
100920
+ No active models found for provider "${providerSlug}". Try \`claudish -s <query>\` to search the full catalog.
100921
+ `);
100922
+ return;
100984
100923
  }
100985
100924
  console.log(`
100925
+ Provider: ${providerSlug} (${models.length} active models)
100986
100926
  `);
100987
- console.log("Use OpenRouter model: claudish --model openrouter@<provider/model-id>");
100988
- console.log(" Example: claudish --model openrouter@google/gemini-2.0-flash-exp:free");
100989
- console.log("Local Ollama model: claudish --model ollama@<model-name>");
100990
- console.log("OpenCode Zen model: claudish --model zen@<model-id>");
100991
- console.log("LiteLLM proxy model: claudish --model litellm@<model-group>");
100992
- console.log("Search: claudish --search <query>");
100993
- console.log("Top models: claudish --top-models");
100994
- }
100995
- function isCacheStale() {
100996
- const cachePath = existsSync24(CACHED_MODELS_PATH) ? CACHED_MODELS_PATH : BUNDLED_MODELS_PATH;
100997
- if (!existsSync24(cachePath)) {
100998
- return true;
100999
- }
100927
+ renderModelDocTable(models, false);
100928
+ console.log("");
100929
+ console.log(" Caps: T = tools R = reasoning V = vision");
100930
+ console.log("");
100931
+ console.log("Use any model: claudish --model <model-id>");
100932
+ console.log("Provider shortcuts: claudish --model or@<id> | google@<id> | oai@<id>");
100933
+ console.log("");
100934
+ }
100935
+ async function printRecommendedModels(jsonOutput, forceUpdate) {
100936
+ let doc2;
101000
100937
  try {
101001
- const jsonContent = readFileSync20(cachePath, "utf-8");
101002
- const data = JSON.parse(jsonContent);
101003
- if (!data.lastUpdated) {
101004
- return true;
101005
- }
101006
- const lastUpdated = new Date(data.lastUpdated);
101007
- const now = new Date;
101008
- const ageInDays = (now.getTime() - lastUpdated.getTime()) / (1000 * 60 * 60 * 24);
101009
- return ageInDays > CACHE_MAX_AGE_DAYS2;
100938
+ doc2 = await getRecommendedModels({ forceRefresh: forceUpdate });
101010
100939
  } catch (error2) {
101011
- return true;
100940
+ console.error(`\u274C Failed to load recommended models: ${error2 instanceof Error ? error2.message : String(error2)}`);
100941
+ process.exit(1);
101012
100942
  }
101013
- }
101014
- async function updateModelsFromOpenRouter() {
101015
- console.error("\uD83D\uDD04 Updating model recommendations from OpenRouter...");
101016
- try {
101017
- const apiResponse = await fetch("https://openrouter.ai/api/v1/models?category=programming&order=top-weekly");
101018
- if (!apiResponse.ok) {
101019
- throw new Error(`OpenRouter API returned ${apiResponse.status}`);
101020
- }
101021
- const openrouterData = await apiResponse.json();
101022
- const topModels = openrouterData.data;
101023
- const recommendations = [];
101024
- const providers = new Set;
101025
- for (const model of topModels) {
101026
- const modelId = model.id;
101027
- const provider = modelId.split("/")[0];
101028
- if (provider === "anthropic") {
101029
- continue;
101030
- }
101031
- if (provider === "openrouter") {
101032
- continue;
101033
- }
101034
- if (providers.has(provider)) {
101035
- continue;
101036
- }
101037
- const name = model.name || modelId;
101038
- const description = model.description || `${name} model`;
101039
- const architecture = model.architecture || {};
101040
- const topProvider = model.top_provider || {};
101041
- const supportedParams = model.supported_parameters || [];
101042
- const promptPrice = parseFloat(model.pricing?.prompt || "0");
101043
- const completionPrice = parseFloat(model.pricing?.completion || "0");
101044
- const inputPrice = promptPrice > 0 ? `$${(promptPrice * 1e6).toFixed(2)}/1M` : "FREE";
101045
- const outputPrice = completionPrice > 0 ? `$${(completionPrice * 1e6).toFixed(2)}/1M` : "FREE";
101046
- const avgPrice = promptPrice > 0 || completionPrice > 0 ? `$${((promptPrice + completionPrice) / 2 * 1e6).toFixed(2)}/1M` : "FREE";
101047
- let category = "programming";
101048
- const lowerDesc = description.toLowerCase() + " " + name.toLowerCase();
101049
- if (lowerDesc.includes("vision") || lowerDesc.includes("vl-") || lowerDesc.includes("multimodal")) {
101050
- category = "vision";
101051
- } else if (lowerDesc.includes("reason")) {
101052
- category = "reasoning";
101053
- }
101054
- const bareId = modelId.split("/").pop().replace(/:free$/, "");
101055
- recommendations.push({
101056
- id: bareId,
101057
- openrouterId: modelId,
101058
- name,
101059
- description,
101060
- provider: provider.charAt(0).toUpperCase() + provider.slice(1),
101061
- category,
101062
- priority: recommendations.length + 1,
101063
- pricing: {
101064
- input: inputPrice,
101065
- output: outputPrice,
101066
- average: avgPrice
101067
- },
101068
- context: topProvider.context_length ? `${Math.floor(topProvider.context_length / 1000)}K` : "N/A",
101069
- maxOutputTokens: topProvider.max_completion_tokens || null,
101070
- modality: architecture.modality || "text->text",
101071
- supportsTools: supportedParams.includes("tools") || supportedParams.includes("tool_choice"),
101072
- supportsReasoning: supportedParams.includes("reasoning") || supportedParams.includes("include_reasoning"),
101073
- supportsVision: (architecture.input_modalities || []).includes("image") || (architecture.input_modalities || []).includes("video"),
101074
- isModerated: topProvider.is_moderated || false,
101075
- recommended: true
101076
- });
101077
- providers.add(provider);
100943
+ if (jsonOutput) {
100944
+ console.log(JSON.stringify(doc2, null, 2));
100945
+ return;
100946
+ }
100947
+ const lastUpdated = doc2.lastUpdated || "unknown";
100948
+ const { flagship, fast } = groupRecommendedModels(doc2.models);
100949
+ const providerByName = new Map(BUILTIN_PROVIDERS.map((p) => [p.name, p]));
100950
+ const getNativePrefix = (firebaseSlug) => {
100951
+ const canonical = FIREBASE_SLUG_TO_PROVIDER_NAME[firebaseSlug];
100952
+ if (!canonical)
100953
+ return null;
100954
+ const def = providerByName.get(canonical);
100955
+ if (!def || !def.shortcuts || def.shortcuts.length === 0)
100956
+ return null;
100957
+ return def.shortcuts[0];
100958
+ };
100959
+ const renderGroup = (group) => {
100960
+ const m2 = group.primary;
100961
+ const rawId = m2.id;
100962
+ const modelId = rawId.length > 28 ? rawId.substring(0, 25) + "..." : rawId;
100963
+ const modelIdPadded = modelId.padEnd(28);
100964
+ const pricing = normalizePricingDisplay(m2.pricing?.average);
100965
+ const pricingPadded = pricing.padEnd(10);
100966
+ const context = m2.context || "N/A";
100967
+ const contextPadded = context.padEnd(6);
100968
+ const caps = [];
100969
+ if (m2.supportsTools)
100970
+ caps.push("\uD83D\uDD27");
100971
+ if (m2.supportsReasoning)
100972
+ caps.push("\uD83E\uDDE0");
100973
+ if (m2.supportsVision)
100974
+ caps.push("\uD83D\uDC41\uFE0F");
100975
+ const capabilities = caps.join(" ");
100976
+ console.log(` ${modelIdPadded} ${pricingPadded} ${contextPadded} ${capabilities}`);
100977
+ const prefixes = collectRoutingPrefixes(group, getNativePrefix);
100978
+ if (prefixes.length > 0) {
100979
+ const viaLine = prefixes.map((p) => `${p}@`).join(" \xB7 ");
100980
+ console.log(` via: ${viaLine}`);
101078
100981
  }
101079
- let version2 = "1.2.0";
101080
- const existingPath = existsSync24(CACHED_MODELS_PATH) ? CACHED_MODELS_PATH : BUNDLED_MODELS_PATH;
101081
- if (existsSync24(existingPath)) {
101082
- try {
101083
- const existing = JSON.parse(readFileSync20(existingPath, "utf-8"));
101084
- version2 = existing.version || version2;
101085
- } catch {}
100982
+ };
100983
+ console.log(`
100984
+ Recommended Models (last updated: ${lastUpdated}):
100985
+ `);
100986
+ if (flagship.length > 0) {
100987
+ console.log("Flagship models");
100988
+ console.log(" " + "\u2500".repeat(70));
100989
+ for (let i = 0;i < flagship.length; i++) {
100990
+ renderGroup(flagship[i]);
100991
+ if (i < flagship.length - 1)
100992
+ console.log("");
101086
100993
  }
101087
- const updatedData = {
101088
- version: version2,
101089
- lastUpdated: new Date().toISOString().split("T")[0],
101090
- source: "https://openrouter.ai/models?categories=programming&fmt=cards&order=top-weekly",
101091
- models: recommendations
101092
- };
101093
- mkdirSync11(CLAUDISH_CACHE_DIR2, { recursive: true });
101094
- writeFileSync13(CACHED_MODELS_PATH, JSON.stringify(updatedData, null, 2), "utf-8");
101095
- console.error(`\u2705 Updated ${recommendations.length} models (last updated: ${updatedData.lastUpdated})`);
101096
- } catch (error2) {
101097
- console.error(`\u274C Failed to update models: ${error2 instanceof Error ? error2.message : String(error2)}`);
101098
- console.error(" Using cached models (if available)");
101099
100994
  }
101100
- }
101101
- async function checkAndUpdateModelsCache(forceUpdate = false) {
101102
- if (forceUpdate) {
101103
- console.error("\uD83D\uDD04 Force update requested...");
101104
- await updateModelsFromOpenRouter();
101105
- return;
100995
+ if (fast.length > 0) {
100996
+ if (flagship.length > 0)
100997
+ console.log("");
100998
+ console.log("Fast variants");
100999
+ console.log(" " + "\u2500".repeat(70));
101000
+ for (let i = 0;i < fast.length; i++) {
101001
+ renderGroup(fast[i]);
101002
+ if (i < fast.length - 1)
101003
+ console.log("");
101004
+ }
101106
101005
  }
101107
- if (isCacheStale()) {
101108
- console.error("\u26A0\uFE0F Model cache is stale (>2 days old), updating...");
101109
- await updateModelsFromOpenRouter();
101110
- } else {
101111
- try {
101112
- const cachePath = existsSync24(CACHED_MODELS_PATH) ? CACHED_MODELS_PATH : BUNDLED_MODELS_PATH;
101113
- const data = JSON.parse(readFileSync20(cachePath, "utf-8"));
101114
- console.error(`\u2713 Using cached models (last updated: ${data.lastUpdated})`);
101115
- } catch {}
101006
+ console.log("");
101007
+ console.log(" Capabilities: \uD83D\uDD27 Tools \uD83E\uDDE0 Reasoning \uD83D\uDC41\uFE0F Vision");
101008
+ const primaries = [...flagship, ...fast].map((g2) => g2.primary);
101009
+ const picks = computeQuickPicks(primaries);
101010
+ const pickLines = [];
101011
+ if (picks.budget)
101012
+ pickLines.push(` Budget \u2192 ${picks.budget.id} (${normalizePricingDisplay(picks.budget.pricing?.average)})`);
101013
+ if (picks.largeContext)
101014
+ pickLines.push(` Large ctx \u2192 ${picks.largeContext.id} (${picks.largeContext.context || "N/A"})`);
101015
+ if (picks.mostCapable)
101016
+ pickLines.push(` Most capable \u2192 ${picks.mostCapable.id}`);
101017
+ if (picks.visionCoding)
101018
+ pickLines.push(` Vision+code \u2192 ${picks.visionCoding.id}`);
101019
+ if (picks.agentic)
101020
+ pickLines.push(` Agentic \u2192 ${picks.agentic.id}`);
101021
+ if (pickLines.length > 0) {
101022
+ console.log("");
101023
+ console.log(" Quick picks:");
101024
+ for (const line of pickLines)
101025
+ console.log(line);
101116
101026
  }
101027
+ console.log("");
101028
+ console.log(" Set default: export CLAUDISH_MODEL=<model>");
101029
+ console.log(" or: claudish --model <model> ...");
101030
+ console.log("");
101031
+ console.log(" For more: claudish --list-models (browse full catalog)");
101032
+ console.log(" claudish --list-providers (list all providers + counts)");
101033
+ console.log(" claudish -s <query> (search by keyword)");
101034
+ console.log(" claudish --top-models --force-update (refresh from Firebase)");
101035
+ console.log("");
101117
101036
  }
101118
101037
  function printVersion() {
101119
101038
  console.log(`claudish version ${VERSION}`);
@@ -101563,9 +101482,15 @@ OPTIONS:
101563
101482
  --cost-tracker Enable cost tracking for API usage (NB!)
101564
101483
  --audit-costs Show cost analysis report
101565
101484
  --reset-costs Reset accumulated cost statistics
101566
- --models List ALL models (OpenRouter + OpenCode Zen + Ollama)
101567
- --models <query> Fuzzy search all models by name, ID, or description
101568
- --top-models List recommended/top programming models (curated)
101485
+ --list-models Top 100 ranked models from Firebase + local providers
101486
+ --list-models --provider <slug>
101487
+ Filter Firebase catalog to one provider
101488
+ (e.g. --provider opencode-zen, --provider anthropic)
101489
+ --list-providers List every provider + active-model count
101490
+ -s, --search <query> Search Firebase catalog by keyword \u2014 matches model ID,
101491
+ brand synonyms (chatgpt, claude, grok), gateway names
101492
+ (zen, oc, codex), or capabilities (reasoning, vision, free)
101493
+ --top-models List the curated recommended models (flagship + fast)
101569
101494
  --team <models> Run multiple models in parallel (comma-separated)
101570
101495
  Example: --team minimax-m2.5,kimi-k2.5 "prompt"
101571
101496
  --mode <mode> Team mode: default (grid), interactive, json
@@ -101574,7 +101499,7 @@ OPTIONS:
101574
101499
  1-token request (diagnostic, may incur tiny cost)
101575
101500
  --no-probe Skip live requests, show static chain only
101576
101501
  --probe-timeout <secs> Per-link timeout for live probes (default: 40)
101577
- --json Output in JSON format (use with --models, --top-models, --probe)
101502
+ --json Output in JSON format (use with --list-models, --top-models, --probe)
101578
101503
  --force-update Force refresh model cache from OpenRouter API
101579
101504
  --version Show version information
101580
101505
  -h, --help Show this help message
@@ -101623,7 +101548,7 @@ MODEL MAPPING (per-role override):
101623
101548
  --model-subagent <model> Model for sub-agents (Task tool)
101624
101549
 
101625
101550
  CUSTOM MODELS:
101626
- Claudish accepts ANY valid OpenRouter model ID, even if not in --list-models
101551
+ Claudish accepts ANY valid model ID from the Firebase catalog, even if not in --list-models
101627
101552
  Example: claudish --model openrouter@your_provider/custom-model-123 "task"
101628
101553
 
101629
101554
  MODES:
@@ -101791,14 +101716,14 @@ LOCAL MODELS (Ollama, LM Studio, vLLM):
101791
101716
  OLLAMA_HOST=http://192.168.1.50:11434 claudish --model ollama/llama3.2 "task"
101792
101717
 
101793
101718
  AVAILABLE MODELS:
101794
- List all models: claudish --models (includes OpenRouter, OpenCode Zen, Ollama)
101795
- Search models: claudish --models <query>
101796
- Top recommended: claudish --top-models
101719
+ Top 100 ranked: claudish --list-models (Firebase-ranked list + local providers)
101720
+ By provider: claudish --list-models --provider <slug> (e.g. opencode-zen, anthropic, openai, google, x-ai)
101721
+ All providers: claudish --list-providers (every provider + active-model count)
101722
+ Search models: claudish -s <query> (fuzzy: id, brand synonyms, gateways, capabilities)
101723
+ Top recommended: claudish --top-models (curated flagship + fast)
101797
101724
  Probe routing: claudish --probe minimax-m2.5 kimi-k2.5 gemini-3.1-pro-preview
101798
- Free models only: claudish --free (interactive selector with free models)
101799
- JSON output: claudish --models --json
101800
- Force cache update: claudish --models --force-update
101801
- (Cache auto-updates every 2 days)
101725
+ Free models only: claudish --free (interactive selector with free models)
101726
+ JSON output: claudish --list-models --json | claudish --top-models --json
101802
101727
 
101803
101728
  MORE INFO:
101804
101729
  GitHub: https://github.com/MadAppGang/claude-code
@@ -101846,15 +101771,15 @@ async function initializeClaudishSkill() {
101846
101771
  }
101847
101772
  try {
101848
101773
  if (!existsSync24(claudeDir)) {
101849
- mkdirSync11(claudeDir, { recursive: true });
101774
+ mkdirSync10(claudeDir, { recursive: true });
101850
101775
  console.log("\uD83D\uDCC1 Created .claude/ directory");
101851
101776
  }
101852
101777
  if (!existsSync24(skillsDir)) {
101853
- mkdirSync11(skillsDir, { recursive: true });
101778
+ mkdirSync10(skillsDir, { recursive: true });
101854
101779
  console.log("\uD83D\uDCC1 Created .claude/skills/ directory");
101855
101780
  }
101856
101781
  if (!existsSync24(claudishSkillDir)) {
101857
- mkdirSync11(claudishSkillDir, { recursive: true });
101782
+ mkdirSync10(claudishSkillDir, { recursive: true });
101858
101783
  console.log("\uD83D\uDCC1 Created .claude/skills/claudish-usage/ directory");
101859
101784
  }
101860
101785
  copyFileSync(sourceSkillPath, skillFile);
@@ -101893,151 +101818,27 @@ async function initializeClaudishSkill() {
101893
101818
  }
101894
101819
  }
101895
101820
  function printAvailableModels() {
101896
- let lastUpdated = "unknown";
101897
- let models = [];
101898
101821
  try {
101899
- const cachePath = existsSync24(CACHED_MODELS_PATH) ? CACHED_MODELS_PATH : BUNDLED_MODELS_PATH;
101900
- if (existsSync24(cachePath)) {
101901
- const data = JSON.parse(readFileSync20(cachePath, "utf-8"));
101902
- lastUpdated = data.lastUpdated || "unknown";
101903
- models = data.models || [];
101904
- }
101905
- } catch {
101906
101822
  const basicModels = getAvailableModels();
101907
101823
  const modelInfo = loadModelInfo();
101824
+ console.log("\nAvailable models (type `claudish --top-models` for full table):\n");
101908
101825
  for (const model of basicModels) {
101909
101826
  const info = modelInfo[model];
101827
+ if (!info)
101828
+ continue;
101910
101829
  console.log(` ${model}`);
101911
101830
  console.log(` ${info.name} - ${info.description}`);
101912
- console.log("");
101913
101831
  }
101914
- return;
101915
- }
101916
- console.log(`
101917
- Recommended Models (last updated: ${lastUpdated}):
101918
- `);
101919
- console.log(" Model Pricing Context Capabilities");
101920
- console.log(" " + "\u2500".repeat(66));
101921
- for (const model of models) {
101922
- const modelId = model.id.length > 28 ? model.id.substring(0, 25) + "..." : model.id;
101923
- const modelIdPadded = modelId.padEnd(28);
101924
- let pricing = model.pricing?.average || "N/A";
101925
- if (pricing.includes("-1000000")) {
101926
- pricing = "varies";
101927
- } else if (pricing === "$0.00/1M" || pricing === "FREE") {
101928
- pricing = "FREE";
101929
- }
101930
- const pricingPadded = pricing.padEnd(10);
101931
- const context = model.context || "N/A";
101932
- const contextPadded = context.padEnd(7);
101933
- const tools = model.supportsTools ? "\uD83D\uDD27" : " ";
101934
- const reasoning = model.supportsReasoning ? "\uD83E\uDDE0" : " ";
101935
- const vision = model.supportsVision ? "\uD83D\uDC41\uFE0F " : " ";
101936
- const capabilities = `${tools} ${reasoning} ${vision}`;
101937
- console.log(` ${modelIdPadded} ${pricingPadded} ${contextPadded} ${capabilities}`);
101938
- }
101939
- console.log("");
101940
- console.log(" Capabilities: \uD83D\uDD27 Tools \uD83E\uDDE0 Reasoning \uD83D\uDC41\uFE0F Vision");
101941
- console.log("");
101942
- console.log("Set default with: export CLAUDISH_MODEL=<model>");
101943
- console.log(" or: export ANTHROPIC_MODEL=<model>");
101944
- console.log("Or use: claudish --model <model> ...");
101945
- console.log(`
101946
- Force update: claudish --list-models --force-update
101947
- `);
101948
- }
101949
- function printAvailableModelsJSON() {
101950
- const jsonPath = existsSync24(CACHED_MODELS_PATH) ? CACHED_MODELS_PATH : BUNDLED_MODELS_PATH;
101951
- try {
101952
- const jsonContent = readFileSync20(jsonPath, "utf-8");
101953
- const data = JSON.parse(jsonContent);
101954
- console.log(JSON.stringify(data, null, 2));
101832
+ console.log("");
101955
101833
  } catch (error2) {
101956
- const models = getAvailableModels();
101957
- const modelInfo = loadModelInfo();
101958
- const output = {
101959
- version: VERSION,
101960
- lastUpdated: new Date().toISOString().split("T")[0],
101961
- source: "runtime",
101962
- models: models.filter((m2) => m2 !== "custom").map((modelId) => {
101963
- const info = modelInfo[modelId];
101964
- return {
101965
- id: modelId,
101966
- name: info.name,
101967
- description: info.description,
101968
- provider: info.provider,
101969
- priority: info.priority
101970
- };
101971
- })
101972
- };
101973
- console.log(JSON.stringify(output, null, 2));
101834
+ console.error(`Failed to load available models: ${error2 instanceof Error ? error2.message : String(error2)}`);
101974
101835
  }
101975
101836
  }
101976
- async function fetchZenModels() {
101977
- try {
101978
- const response = await fetch("https://models.dev/api.json", {
101979
- signal: AbortSignal.timeout(1e4)
101980
- });
101981
- if (!response.ok) {
101982
- return [];
101983
- }
101984
- const data = await response.json();
101985
- const opencode = data.opencode;
101986
- if (!opencode?.models)
101987
- return [];
101988
- return Object.entries(opencode.models).map(([id, m2]) => {
101989
- const isFree = m2.cost?.input === 0 && m2.cost?.output === 0;
101990
- return {
101991
- id: `zen/${id}`,
101992
- name: m2.name || id,
101993
- context_length: m2.limit?.context || 128000,
101994
- max_output: m2.limit?.output || 32000,
101995
- pricing: isFree ? { prompt: "0", completion: "0" } : { prompt: String(m2.cost?.input || 0), completion: String(m2.cost?.output || 0) },
101996
- isZen: true,
101997
- isFree,
101998
- supportsTools: m2.tool_call || false,
101999
- supportsReasoning: m2.reasoning || false
102000
- };
102001
- });
102002
- } catch {
102003
- return [];
102004
- }
102005
- }
102006
- async function fetchGLMCodingModels() {
102007
- try {
102008
- const response = await fetch("https://models.dev/api.json", {
102009
- signal: AbortSignal.timeout(5000)
102010
- });
102011
- if (!response.ok) {
102012
- return [];
102013
- }
102014
- const data = await response.json();
102015
- const codingPlan = data["zai-coding-plan"];
102016
- if (!codingPlan?.models)
102017
- return [];
102018
- return Object.entries(codingPlan.models).map(([id, m2]) => {
102019
- const inputModalities = m2.modalities?.input || [];
102020
- return {
102021
- id: `gc/${id}`,
102022
- name: m2.name || id,
102023
- description: `GLM Coding Plan model (subscription)`,
102024
- context_length: m2.limit?.context || 131072,
102025
- pricing: { prompt: "0", completion: "0" },
102026
- isGLMCoding: true,
102027
- isSubscription: true,
102028
- supportsTools: m2.tool_call || false,
102029
- supportsReasoning: m2.reasoning || false,
102030
- supportsVision: inputModalities.includes("image") || inputModalities.includes("video")
102031
- };
102032
- });
102033
- } catch {
102034
- return [];
102035
- }
102036
- }
102037
- var __filename4, __dirname4, CACHE_MAX_AGE_DAYS2 = 2, CLAUDISH_CACHE_DIR2, BUNDLED_MODELS_PATH, CACHED_MODELS_PATH, ALL_MODELS_JSON_PATH;
101837
+ var __filename4, __dirname4;
102038
101838
  var init_cli = __esm(async () => {
102039
101839
  init_config();
102040
101840
  init_model_loader();
101841
+ init_provider_definitions();
102041
101842
  init_profile_config();
102042
101843
  init_model_parser();
102043
101844
  init_auto_route();
@@ -102050,10 +101851,6 @@ var init_cli = __esm(async () => {
102050
101851
  await init_probe_tui_runtime();
102051
101852
  __filename4 = fileURLToPath4(import.meta.url);
102052
101853
  __dirname4 = dirname5(__filename4);
102053
- CLAUDISH_CACHE_DIR2 = join25(homedir23(), ".claudish");
102054
- BUNDLED_MODELS_PATH = join25(__dirname4, "../recommended-models.json");
102055
- CACHED_MODELS_PATH = join25(CLAUDISH_CACHE_DIR2, "recommended-models.json");
102056
- ALL_MODELS_JSON_PATH = join25(CLAUDISH_CACHE_DIR2, "all-models.json");
102057
101854
  });
102058
101855
 
102059
101856
  // src/update-checker.ts
@@ -102064,7 +101861,7 @@ __export(exports_update_checker, {
102064
101861
  clearCache: () => clearCache,
102065
101862
  checkForUpdates: () => checkForUpdates
102066
101863
  });
102067
- import { existsSync as existsSync26, mkdirSync as mkdirSync12, readFileSync as readFileSync21, unlinkSync as unlinkSync8, writeFileSync as writeFileSync14 } from "fs";
101864
+ import { existsSync as existsSync26, mkdirSync as mkdirSync11, readFileSync as readFileSync21, unlinkSync as unlinkSync8, writeFileSync as writeFileSync12 } from "fs";
102068
101865
  import { homedir as homedir24, platform as platform2, tmpdir } from "os";
102069
101866
  import { join as join26 } from "path";
102070
101867
  function getCacheFilePath() {
@@ -102077,7 +101874,7 @@ function getCacheFilePath() {
102077
101874
  }
102078
101875
  try {
102079
101876
  if (!existsSync26(cacheDir)) {
102080
- mkdirSync12(cacheDir, { recursive: true });
101877
+ mkdirSync11(cacheDir, { recursive: true });
102081
101878
  }
102082
101879
  return join26(cacheDir, "update-check.json");
102083
101880
  } catch {
@@ -102103,7 +101900,7 @@ function writeCache(latestVersion) {
102103
101900
  lastCheck: Date.now(),
102104
101901
  latestVersion
102105
101902
  };
102106
- writeFileSync14(cachePath, JSON.stringify(data), "utf-8");
101903
+ writeFileSync12(cachePath, JSON.stringify(data), "utf-8");
102107
101904
  } catch {}
102108
101905
  }
102109
101906
  function isCacheValid(cache) {
@@ -102414,218 +102211,36 @@ __export(exports_model_selector, {
102414
102211
  promptForApiKey: () => promptForApiKey,
102415
102212
  confirmAction: () => confirmAction
102416
102213
  });
102417
- import { readFileSync as readFileSync22, writeFileSync as writeFileSync15, existsSync as existsSync27, mkdirSync as mkdirSync13 } from "fs";
102418
- import { join as join27, dirname as dirname6 } from "path";
102419
- import { homedir as homedir25 } from "os";
102420
- import { fileURLToPath as fileURLToPath5 } from "url";
102421
- function loadRecommendedModels2() {
102422
- const cachedPath = join27(CLAUDISH_CACHE_DIR3, "recommended-models-cache.json");
102423
- if (existsSync27(cachedPath)) {
102424
- try {
102425
- const data = JSON.parse(readFileSync22(cachedPath, "utf-8"));
102426
- if (data.models && data.models.length > 0) {
102427
- return data.models.map((model) => ({
102428
- ...model,
102429
- source: "Recommended"
102430
- }));
102431
- }
102432
- } catch {}
102433
- }
102434
- if (existsSync27(RECOMMENDED_MODELS_JSON_PATH)) {
102435
- try {
102436
- const content = readFileSync22(RECOMMENDED_MODELS_JSON_PATH, "utf-8");
102437
- const data = JSON.parse(content);
102438
- return (data.models || []).map((model) => ({
102439
- ...model,
102440
- source: "Recommended"
102441
- }));
102442
- } catch {
102443
- return [];
102444
- }
102445
- }
102446
- return [];
102447
- }
102448
- async function fetchAllModels(forceUpdate = false) {
102449
- if (!forceUpdate && existsSync27(ALL_MODELS_JSON_PATH2)) {
102450
- try {
102451
- const cacheData = JSON.parse(readFileSync22(ALL_MODELS_JSON_PATH2, "utf-8"));
102452
- const lastUpdated = new Date(cacheData.lastUpdated);
102453
- const now = new Date;
102454
- const ageInDays = (now.getTime() - lastUpdated.getTime()) / (1000 * 60 * 60 * 24);
102455
- if (ageInDays <= CACHE_MAX_AGE_DAYS3) {
102456
- return cacheData.models;
102457
- }
102458
- } catch {}
102459
- }
102460
- console.log("Fetching models from OpenRouter...");
102461
- try {
102462
- const response = await fetch("https://openrouter.ai/api/v1/models");
102463
- if (!response.ok)
102464
- throw new Error(`API returned ${response.status}`);
102465
- const data = await response.json();
102466
- const models = data.data;
102467
- mkdirSync13(CLAUDISH_CACHE_DIR3, { recursive: true });
102468
- writeFileSync15(ALL_MODELS_JSON_PATH2, JSON.stringify({
102469
- lastUpdated: new Date().toISOString(),
102470
- models
102471
- }), "utf-8");
102472
- console.log(`Cached ${models.length} models`);
102473
- return models;
102474
- } catch (error2) {
102475
- console.error(`Failed to fetch models: ${error2}`);
102476
- return [];
102477
- }
102478
- }
102479
- function toModelInfo(model) {
102480
- const provider = model.id.split("/")[0];
102481
- const contextLen = model.context_length || model.top_provider?.context_length || 0;
102482
- const promptPrice = parseFloat(model.pricing?.prompt || "0");
102483
- const completionPrice = parseFloat(model.pricing?.completion || "0");
102484
- const isFree = promptPrice === 0 && completionPrice === 0;
102485
- let pricingStr = "N/A";
102486
- if (isFree) {
102487
- pricingStr = "FREE";
102488
- } else if (model.pricing) {
102489
- const avgPrice = (promptPrice + completionPrice) / 2;
102490
- if (avgPrice < 0.001) {
102491
- pricingStr = `$${(avgPrice * 1e6).toFixed(2)}/1M`;
102492
- } else {
102493
- pricingStr = `$${avgPrice.toFixed(4)}/1K`;
102494
- }
102495
- }
102496
- return {
102497
- id: `openrouter@${model.id}`,
102498
- name: model.name || model.id,
102499
- description: model.description || "",
102500
- provider: provider.charAt(0).toUpperCase() + provider.slice(1),
102501
- pricing: {
102502
- input: model.pricing?.prompt || "N/A",
102503
- output: model.pricing?.completion || "N/A",
102504
- average: pricingStr
102505
- },
102506
- context: contextLen > 0 ? `${Math.round(contextLen / 1000)}K` : "N/A",
102507
- contextLength: contextLen,
102508
- supportsTools: (model.supported_parameters || []).includes("tools"),
102509
- supportsReasoning: (model.supported_parameters || []).includes("reasoning"),
102510
- supportsVision: (model.architecture?.input_modalities || []).includes("image"),
102511
- isFree,
102512
- source: "OpenRouter"
102513
- };
102514
- }
102515
- async function fetchZenGoModels() {
102516
- const apiKey = process.env.OPENCODE_API_KEY;
102517
- if (!apiKey)
102518
- return [];
102519
- const ZEN_GO_BASE = process.env.OPENCODE_BASE_URL ? process.env.OPENCODE_BASE_URL.replace("/zen", "/zen/go") : "https://opencode.ai/zen/go";
102214
+ function loadRecommendedModels() {
102520
102215
  try {
102521
- const mdevResp = await fetch("https://models.dev/api.json", {
102522
- signal: AbortSignal.timeout(5000)
102523
- });
102524
- if (!mdevResp.ok)
102525
- return [];
102526
- const mdevData = await mdevResp.json();
102527
- const ocModels = mdevData?.opencode?.models ?? {};
102528
- const candidateIds = Object.keys(ocModels);
102529
- const fallbackIds = ["glm-5"];
102530
- const probeResults = await Promise.all(candidateIds.map(async (modelId) => {
102531
- try {
102532
- const r = await fetch(`${ZEN_GO_BASE}/v1/chat/completions`, {
102533
- method: "POST",
102534
- headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` },
102535
- body: JSON.stringify({
102536
- model: modelId,
102537
- messages: [{ role: "user", content: "hi" }],
102538
- max_tokens: 1
102539
- }),
102540
- signal: AbortSignal.timeout(8000)
102541
- });
102542
- if (!r.ok)
102543
- return null;
102544
- const body = await r.json().catch(() => ({}));
102545
- return Array.isArray(body?.choices) && body.choices.length > 0 ? modelId : null;
102546
- } catch {
102547
- return null;
102548
- }
102216
+ const doc2 = getRecommendedModelsSync();
102217
+ return doc2.models.map((model) => ({
102218
+ id: model.id,
102219
+ name: model.name,
102220
+ description: model.description,
102221
+ provider: model.provider,
102222
+ pricing: model.pricing,
102223
+ context: model.context,
102224
+ contextLength: parseContextString(model.context),
102225
+ supportsTools: model.supportsTools,
102226
+ supportsReasoning: model.supportsReasoning,
102227
+ supportsVision: model.supportsVision,
102228
+ source: "Recommended"
102549
102229
  }));
102550
- const discoveredIds = probeResults.filter(Boolean);
102551
- const goModelIds = discoveredIds.length > 0 ? discoveredIds : fallbackIds;
102552
- return goModelIds.map((id) => {
102553
- const m2 = ocModels[id];
102554
- if (!m2)
102555
- return null;
102556
- const inputModalities = m2.modalities?.input ?? [];
102557
- return {
102558
- id: `zgo@${id}`,
102559
- name: m2.name || id,
102560
- description: `OpenCode Zen Go plan model`,
102561
- provider: "Zen Go",
102562
- pricing: { input: "PLAN", output: "PLAN", average: "PLAN" },
102563
- context: m2.limit?.context ? `${Math.round(m2.limit.context / 1000)}K` : "128K",
102564
- contextLength: m2.limit?.context || 128000,
102565
- supportsTools: m2.tool_call === true,
102566
- supportsReasoning: m2.reasoning || false,
102567
- supportsVision: inputModalities.includes("image") || inputModalities.includes("video"),
102568
- isFree: false,
102569
- source: "Zen"
102570
- };
102571
- }).filter(Boolean);
102572
102230
  } catch {
102573
102231
  return [];
102574
102232
  }
102575
102233
  }
102576
- async function fetchZenFreeModels() {
102577
- try {
102578
- const ZEN_BASE = process.env.OPENCODE_BASE_URL || "https://opencode.ai/zen";
102579
- const ZEN_API_KEY = process.env.OPENCODE_API_KEY || "public";
102580
- const [mdevResp, liveResp] = await Promise.all([
102581
- fetch("https://models.dev/api.json", { signal: AbortSignal.timeout(5000) }),
102582
- fetch(`${ZEN_BASE}/v1/models`, {
102583
- headers: { Authorization: `Bearer ${ZEN_API_KEY}` },
102584
- signal: AbortSignal.timeout(5000)
102585
- })
102586
- ]);
102587
- if (!mdevResp.ok)
102588
- return [];
102589
- const mdevData = await mdevResp.json();
102590
- const opencode = mdevData.opencode;
102591
- if (!opencode?.models)
102592
- return [];
102593
- const liveIds = new Set;
102594
- if (liveResp.ok) {
102595
- const liveData = await liveResp.json();
102596
- for (const m2 of liveData.data ?? [])
102597
- liveIds.add(m2.id);
102598
- }
102599
- return Object.entries(opencode.models).filter(([id, m2]) => {
102600
- const isFree = m2.cost?.input === 0 && m2.cost?.output === 0;
102601
- const supportsTools = m2.tool_call === true;
102602
- const isLive = liveIds.size === 0 || liveIds.has(id);
102603
- return isFree && supportsTools && isLive;
102604
- }).map(([id, m2]) => {
102605
- const inputModalities = m2.modalities?.input || [];
102606
- const supportsVision = inputModalities.includes("image") || inputModalities.includes("video");
102607
- return {
102608
- id: `zen@${id}`,
102609
- name: m2.name || id,
102610
- description: `OpenCode Zen free model`,
102611
- provider: "Zen",
102612
- pricing: {
102613
- input: "FREE",
102614
- output: "FREE",
102615
- average: "FREE"
102616
- },
102617
- context: m2.limit?.context ? `${Math.round(m2.limit.context / 1000)}K` : "128K",
102618
- contextLength: m2.limit?.context || 128000,
102619
- supportsTools: true,
102620
- supportsReasoning: m2.reasoning || false,
102621
- supportsVision,
102622
- isFree: true,
102623
- source: "Zen"
102624
- };
102625
- });
102626
- } catch {
102627
- return [];
102628
- }
102234
+ function parseContextString(ctx) {
102235
+ if (!ctx || ctx === "N/A")
102236
+ return 0;
102237
+ const upper = ctx.toUpperCase();
102238
+ if (upper.endsWith("M"))
102239
+ return parseFloat(upper) * 1e6;
102240
+ if (upper.endsWith("K"))
102241
+ return parseFloat(upper) * 1000;
102242
+ const n = parseInt(upper, 10);
102243
+ return isNaN(n) ? 0 : n;
102629
102244
  }
102630
102245
  function getXAIContextWindow(modelId) {
102631
102246
  const id = modelId.toLowerCase();
@@ -102776,239 +102391,15 @@ async function fetchGeminiModels() {
102776
102391
  return [];
102777
102392
  }
102778
102393
  }
102779
- async function fetchOpenAIModels() {
102780
- const apiKey = process.env.OPENAI_API_KEY;
102781
- if (!apiKey) {
102782
- return [];
102783
- }
102784
- try {
102785
- const response = await fetch("https://models.dev/api.json", {
102786
- signal: AbortSignal.timeout(5000)
102787
- });
102788
- if (!response.ok) {
102789
- return [];
102790
- }
102791
- const data = await response.json();
102792
- const openaiData = data.openai;
102793
- if (!openaiData?.models)
102794
- return [];
102795
- return Object.entries(openaiData.models).filter(([id, _2]) => {
102796
- const lowerId = id.toLowerCase();
102797
- return lowerId.startsWith("gpt-") || lowerId.startsWith("o1-") || lowerId.startsWith("o3-") || lowerId.startsWith("o4-") || lowerId.startsWith("chatgpt-");
102798
- }).map(([id, m2]) => {
102799
- const inputCost = m2.cost?.input || 2;
102800
- const outputCost = m2.cost?.output || 8;
102801
- const avgCost = (inputCost + outputCost) / 2;
102802
- const contextLength = m2.limit?.context || 128000;
102803
- const contextStr = contextLength >= 1e6 ? `${Math.round(contextLength / 1e6)}M` : `${Math.round(contextLength / 1000)}K`;
102804
- const inputModalities = m2.modalities?.input || [];
102805
- const supportsVision = inputModalities.includes("image") || inputModalities.includes("video");
102806
- return {
102807
- id: `oai@${id}`,
102808
- name: m2.name || id,
102809
- description: `OpenAI model`,
102810
- provider: "OpenAI",
102811
- pricing: {
102812
- input: `$${inputCost.toFixed(2)}`,
102813
- output: `$${outputCost.toFixed(2)}`,
102814
- average: `$${avgCost.toFixed(2)}/1M`
102815
- },
102816
- context: contextStr,
102817
- contextLength,
102818
- supportsTools: m2.tool_call === true,
102819
- supportsReasoning: m2.reasoning === true,
102820
- supportsVision,
102821
- isFree: false,
102822
- source: "OpenAI"
102823
- };
102824
- });
102825
- } catch {
102826
- return [];
102827
- }
102828
- }
102829
- async function fetchGLMCodingModels2() {
102830
- const apiKey = process.env.GLM_CODING_API_KEY;
102831
- if (!apiKey) {
102832
- return [];
102833
- }
102834
- try {
102835
- const response = await fetch("https://models.dev/api.json", {
102836
- signal: AbortSignal.timeout(5000)
102837
- });
102838
- if (!response.ok) {
102839
- return [];
102840
- }
102841
- const data = await response.json();
102842
- const codingPlan = data["zai-coding-plan"];
102843
- if (!codingPlan?.models)
102844
- return [];
102845
- return Object.entries(codingPlan.models).filter(([_2, m2]) => m2.tool_call === true).map(([id, m2]) => {
102846
- const inputModalities = m2.modalities?.input || [];
102847
- const supportsVision = inputModalities.includes("image") || inputModalities.includes("video");
102848
- const contextLength = m2.limit?.context || 131072;
102849
- return {
102850
- id: `gc@${id}`,
102851
- name: m2.name || id,
102852
- description: `GLM Coding Plan (subscription)`,
102853
- provider: "GLM Coding",
102854
- pricing: {
102855
- input: "SUB",
102856
- output: "SUB",
102857
- average: "SUB"
102858
- },
102859
- context: contextLength >= 1e6 ? `${Math.round(contextLength / 1e6)}M` : `${Math.round(contextLength / 1000)}K`,
102860
- contextLength,
102861
- supportsTools: true,
102862
- supportsReasoning: m2.reasoning || false,
102863
- supportsVision,
102864
- isFree: false,
102865
- source: "GLM Coding"
102866
- };
102867
- });
102868
- } catch {
102869
- return [];
102870
- }
102871
- }
102872
- async function fetchGLMDirectModels() {
102873
- try {
102874
- const response = await fetch("https://models.dev/api.json", {
102875
- signal: AbortSignal.timeout(5000)
102876
- });
102877
- if (!response.ok) {
102878
- return [];
102879
- }
102880
- const data = await response.json();
102881
- const codingPlan = data["zai-coding-plan"];
102882
- if (!codingPlan?.models)
102883
- return [];
102884
- return Object.entries(codingPlan.models).filter(([_2, m2]) => m2.tool_call === true).map(([id, m2]) => {
102885
- const inputModalities = m2.modalities?.input || [];
102886
- const supportsVision = inputModalities.includes("image") || inputModalities.includes("video");
102887
- const contextLength = m2.limit?.context || 131072;
102888
- const inputCost = m2.cost?.input || 0;
102889
- const outputCost = m2.cost?.output || 0;
102890
- const isFree = inputCost === 0 && outputCost === 0;
102891
- return {
102892
- id: `glm@${id}`,
102893
- name: m2.name || id,
102894
- description: `GLM/Zhipu direct API`,
102895
- provider: "GLM",
102896
- pricing: {
102897
- input: isFree ? "FREE" : `$${inputCost.toFixed(2)}`,
102898
- output: isFree ? "FREE" : `$${outputCost.toFixed(2)}`,
102899
- average: isFree ? "FREE" : `$${((inputCost + outputCost) / 2).toFixed(2)}/1M`
102900
- },
102901
- context: contextLength >= 1e6 ? `${Math.round(contextLength / 1e6)}M` : `${Math.round(contextLength / 1000)}K`,
102902
- contextLength,
102903
- supportsTools: true,
102904
- supportsReasoning: m2.reasoning || false,
102905
- supportsVision,
102906
- isFree,
102907
- source: "GLM"
102908
- };
102909
- });
102910
- } catch {
102911
- return [];
102912
- }
102913
- }
102914
- async function fetchOllamaCloudModels() {
102915
- try {
102916
- const response = await fetch("https://models.dev/api.json", {
102917
- signal: AbortSignal.timeout(5000)
102918
- });
102919
- if (!response.ok) {
102920
- return [];
102921
- }
102922
- const data = await response.json();
102923
- const ollamaCloud = data["ollama-cloud"];
102924
- if (!ollamaCloud?.models)
102925
- return [];
102926
- return Object.entries(ollamaCloud.models).filter(([_2, m2]) => m2.tool_call === true).map(([id, m2]) => {
102927
- const inputModalities = m2.modalities?.input || [];
102928
- const supportsVision = inputModalities.includes("image") || inputModalities.includes("video");
102929
- const contextLength = m2.limit?.context || 131072;
102930
- return {
102931
- id: `oc@${id}`,
102932
- name: m2.name || id,
102933
- description: `OllamaCloud`,
102934
- provider: "OllamaCloud",
102935
- pricing: {
102936
- input: "N/A",
102937
- output: "N/A",
102938
- average: "N/A"
102939
- },
102940
- context: contextLength >= 1e6 ? `${Math.round(contextLength / 1e6)}M` : `${Math.round(contextLength / 1000)}K`,
102941
- contextLength,
102942
- supportsTools: true,
102943
- supportsReasoning: m2.reasoning || false,
102944
- supportsVision,
102945
- source: "OllamaCloud"
102946
- };
102947
- });
102948
- } catch {
102949
- return [];
102950
- }
102951
- }
102952
- function shouldRefreshForFreeModels() {
102953
- if (!existsSync27(ALL_MODELS_JSON_PATH2)) {
102954
- return true;
102955
- }
102956
- try {
102957
- const cacheData = JSON.parse(readFileSync22(ALL_MODELS_JSON_PATH2, "utf-8"));
102958
- const lastUpdated = new Date(cacheData.lastUpdated);
102959
- const now = new Date;
102960
- const ageInHours = (now.getTime() - lastUpdated.getTime()) / (1000 * 60 * 60);
102961
- return ageInHours > FREE_MODELS_CACHE_MAX_AGE_HOURS;
102962
- } catch {
102963
- return true;
102964
- }
102965
- }
102966
102394
  async function getFreeModels() {
102967
- const forceUpdate = shouldRefreshForFreeModels();
102968
- const [allModels, zenModels] = await Promise.all([
102969
- fetchAllModels(forceUpdate),
102970
- fetchZenFreeModels()
102971
- ]);
102972
- const openRouterFreeModels = allModels.filter((model) => {
102973
- if (!model.id?.endsWith(":free"))
102974
- return false;
102975
- const supportsTools = (model.supported_parameters || []).includes("tools");
102976
- if (!supportsTools)
102977
- return false;
102978
- return true;
102979
- });
102980
- openRouterFreeModels.sort((a, b2) => {
102981
- const contextA = a.context_length || a.top_provider?.context_length || 0;
102982
- const contextB = b2.context_length || b2.top_provider?.context_length || 0;
102983
- return contextB - contextA;
102984
- });
102985
- const openRouterModels = openRouterFreeModels.slice(0, 20).map(toModelInfo);
102986
- const combined = [...zenModels, ...openRouterModels];
102987
- combined.sort((a, b2) => {
102988
- if (a.source === "Zen" && b2.source !== "Zen")
102989
- return -1;
102990
- if (a.source !== "Zen" && b2.source === "Zen")
102991
- return 1;
102992
- return (b2.contextLength || 0) - (a.contextLength || 0);
102993
- });
102994
- return combined;
102395
+ return [];
102995
102396
  }
102996
102397
  async function getAllModelsForSearch(forceUpdate = false) {
102997
102398
  const litellmBaseUrl = process.env.LITELLM_BASE_URL;
102998
102399
  const litellmApiKey = process.env.LITELLM_API_KEY;
102999
102400
  const allEntries = [
103000
- {
103001
- name: "OpenRouter",
103002
- promise: () => fetchAllModels(forceUpdate).then((models) => models.map(toModelInfo))
103003
- },
103004
102401
  { name: "xAI", provider: "xai", promise: () => fetchXAIModels() },
103005
102402
  { name: "Gemini", provider: "google", promise: () => fetchGeminiModels() },
103006
- { name: "OpenAI", provider: "openai", promise: () => fetchOpenAIModels() },
103007
- { name: "GLM", provider: "glm", promise: () => fetchGLMDirectModels() },
103008
- { name: "GLM Coding", provider: "glm-coding", promise: () => fetchGLMCodingModels2() },
103009
- { name: "OllamaCloud", provider: "ollamacloud", promise: () => fetchOllamaCloudModels() },
103010
- { name: "Zen", provider: "opencode-zen", promise: () => fetchZenFreeModels() },
103011
- { name: "Zen Go", provider: "opencode-zen-go", promise: () => fetchZenGoModels() },
103012
102403
  {
103013
102404
  name: "MiniMax",
103014
102405
  provider: "minimax",
@@ -103053,22 +102444,15 @@ async function getAllModelsForSearch(forceUpdate = false) {
103053
102444
  }
103054
102445
  const r = (name) => fetchResults[name] || [];
103055
102446
  const allModels = [
103056
- ...r("Zen"),
103057
- ...r("Zen Go"),
103058
- ...r("OllamaCloud"),
103059
102447
  ...r("xAI"),
103060
102448
  ...r("Gemini"),
103061
- ...r("OpenAI"),
103062
102449
  ...r("OpenAI Codex"),
103063
- ...r("GLM"),
103064
- ...r("GLM Coding"),
103065
102450
  ...r("MiniMax"),
103066
102451
  ...r("MiniMax Coding"),
103067
102452
  ...r("Kimi"),
103068
102453
  ...r("Kimi Coding"),
103069
102454
  ...r("Z.AI"),
103070
- ...r("LiteLLM"),
103071
- ...r("OpenRouter")
102455
+ ...r("LiteLLM")
103072
102456
  ];
103073
102457
  return allModels;
103074
102458
  }
@@ -103164,23 +102548,17 @@ async function selectModel(options = {}) {
103164
102548
  } else {
103165
102549
  const [allModels, recommendedModels] = await Promise.all([
103166
102550
  getAllModelsForSearch(forceUpdate),
103167
- Promise.resolve(recommended ? loadRecommendedModels2() : [])
102551
+ Promise.resolve(recommended ? loadRecommendedModels() : [])
103168
102552
  ]);
103169
102553
  const seenIds = new Set;
103170
102554
  models = [];
103171
- for (const m2 of allModels.filter((m3) => m3.source === "Zen")) {
103172
- if (!seenIds.has(m2.id)) {
103173
- seenIds.add(m2.id);
103174
- models.push(m2);
103175
- }
103176
- }
103177
102555
  for (const m2 of recommendedModels) {
103178
102556
  if (!seenIds.has(m2.id)) {
103179
102557
  seenIds.add(m2.id);
103180
102558
  models.push(m2);
103181
102559
  }
103182
102560
  }
103183
- for (const m2 of allModels.filter((m3) => m3.source && m3.source !== "Zen" && m3.source !== "OpenRouter")) {
102561
+ for (const m2 of allModels.filter((m3) => m3.source && m3.source !== "OpenRouter")) {
103184
102562
  if (!seenIds.has(m2.id)) {
103185
102563
  seenIds.add(m2.id);
103186
102564
  models.push(m2);
@@ -103550,7 +102928,7 @@ async function selectModelsForProfile() {
103550
102928
  Loading available models...`);
103551
102929
  const [fetchedModels, recommendedModels] = await Promise.all([
103552
102930
  getAllModelsForSearch(),
103553
- Promise.resolve(loadRecommendedModels2())
102931
+ Promise.resolve(loadRecommendedModels())
103554
102932
  ]);
103555
102933
  const tiers = [
103556
102934
  { key: "opus", name: "Opus", description: "Most capable, used for complex reasoning" },
@@ -103644,16 +103022,11 @@ async function selectProfile(profiles) {
103644
103022
  async function confirmAction(message) {
103645
103023
  return dist_default4({ message, default: false });
103646
103024
  }
103647
- var __filename5, __dirname5, CLAUDISH_CACHE_DIR3, ALL_MODELS_JSON_PATH2, RECOMMENDED_MODELS_JSON_PATH, CACHE_MAX_AGE_DAYS3 = 2, FREE_MODELS_CACHE_MAX_AGE_HOURS = 3, PROVIDER_FILTER_ALIASES, ALL_PROVIDER_CHOICES, PROVIDER_MODEL_PREFIX, PROVIDER_SOURCE_FILTER;
103025
+ var PROVIDER_FILTER_ALIASES, ALL_PROVIDER_CHOICES, PROVIDER_MODEL_PREFIX, PROVIDER_SOURCE_FILTER;
103648
103026
  var init_model_selector = __esm(() => {
103649
103027
  init_dist17();
103650
103028
  init_model_loader();
103651
103029
  init_provider_definitions();
103652
- __filename5 = fileURLToPath5(import.meta.url);
103653
- __dirname5 = dirname6(__filename5);
103654
- CLAUDISH_CACHE_DIR3 = join27(homedir25(), ".claudish");
103655
- ALL_MODELS_JSON_PATH2 = join27(CLAUDISH_CACHE_DIR3, "all-models.json");
103656
- RECOMMENDED_MODELS_JSON_PATH = join27(__dirname5, "../recommended-models.json");
103657
103030
  PROVIDER_FILTER_ALIASES = {
103658
103031
  zen: "Zen",
103659
103032
  openrouter: "OpenRouter",
@@ -107310,9 +106683,9 @@ __export(exports_claude_runner, {
107310
106683
  checkClaudeInstalled: () => checkClaudeInstalled
107311
106684
  });
107312
106685
  import { spawn as spawn4 } from "child_process";
107313
- import { writeFileSync as writeFileSync16, unlinkSync as unlinkSync9, mkdirSync as mkdirSync14, existsSync as existsSync28, readFileSync as readFileSync23 } from "fs";
107314
- import { tmpdir as tmpdir2, homedir as homedir26 } from "os";
107315
- import { join as join28 } from "path";
106686
+ import { writeFileSync as writeFileSync13, unlinkSync as unlinkSync9, mkdirSync as mkdirSync12, existsSync as existsSync27, readFileSync as readFileSync22 } from "fs";
106687
+ import { tmpdir as tmpdir2, homedir as homedir25 } from "os";
106688
+ import { join as join27 } from "path";
107316
106689
  function hasNativeAnthropicMapping(config3) {
107317
106690
  const models = [
107318
106691
  config3.model,
@@ -107328,9 +106701,9 @@ function isWindows2() {
107328
106701
  }
107329
106702
  function createStatusLineScript(tokenFilePath) {
107330
106703
  const homeDir = process.env.HOME || process.env.USERPROFILE || tmpdir2();
107331
- const claudishDir = join28(homeDir, ".claudish");
106704
+ const claudishDir = join27(homeDir, ".claudish");
107332
106705
  const timestamp = Date.now();
107333
- const scriptPath = join28(claudishDir, `status-${timestamp}.js`);
106706
+ const scriptPath = join27(claudishDir, `status-${timestamp}.js`);
107334
106707
  const escapedTokenPath = tokenFilePath.replace(/\\/g, "\\\\");
107335
106708
  const script = `
107336
106709
  const fs = require('fs');
@@ -107422,18 +106795,18 @@ process.stdin.on('end', () => {
107422
106795
  }
107423
106796
  });
107424
106797
  `;
107425
- writeFileSync16(scriptPath, script, "utf-8");
106798
+ writeFileSync13(scriptPath, script, "utf-8");
107426
106799
  return scriptPath;
107427
106800
  }
107428
106801
  function createTempSettingsFile(modelDisplay, port) {
107429
106802
  const homeDir = process.env.HOME || process.env.USERPROFILE || tmpdir2();
107430
- const claudishDir = join28(homeDir, ".claudish");
106803
+ const claudishDir = join27(homeDir, ".claudish");
107431
106804
  try {
107432
- mkdirSync14(claudishDir, { recursive: true });
106805
+ mkdirSync12(claudishDir, { recursive: true });
107433
106806
  } catch {}
107434
106807
  const timestamp = Date.now();
107435
- const tempPath = join28(claudishDir, `settings-${timestamp}.json`);
107436
- const tokenFilePath = join28(claudishDir, `tokens-${port}.json`);
106808
+ const tempPath = join27(claudishDir, `settings-${timestamp}.json`);
106809
+ const tokenFilePath = join27(claudishDir, `tokens-${port}.json`);
107437
106810
  let statusCommand;
107438
106811
  if (isWindows2()) {
107439
106812
  const scriptPath = createStatusLineScript(tokenFilePath);
@@ -107455,7 +106828,7 @@ function createTempSettingsFile(modelDisplay, port) {
107455
106828
  padding: 0
107456
106829
  };
107457
106830
  const settings = { statusLine };
107458
- writeFileSync16(tempPath, JSON.stringify(settings, null, 2), "utf-8");
106831
+ writeFileSync13(tempPath, JSON.stringify(settings, null, 2), "utf-8");
107459
106832
  return { path: tempPath, statusLine };
107460
106833
  }
107461
106834
  function mergeUserSettingsIfPresent(config3, tempSettingsPath, statusLine) {
@@ -107469,11 +106842,11 @@ function mergeUserSettingsIfPresent(config3, tempSettingsPath, statusLine) {
107469
106842
  if (userSettingsValue.trimStart().startsWith("{")) {
107470
106843
  userSettings = JSON.parse(userSettingsValue);
107471
106844
  } else {
107472
- const rawUserSettings = readFileSync23(userSettingsValue, "utf-8");
106845
+ const rawUserSettings = readFileSync22(userSettingsValue, "utf-8");
107473
106846
  userSettings = JSON.parse(rawUserSettings);
107474
106847
  }
107475
106848
  userSettings.statusLine = statusLine;
107476
- writeFileSync16(tempSettingsPath, JSON.stringify(userSettings, null, 2), "utf-8");
106849
+ writeFileSync13(tempSettingsPath, JSON.stringify(userSettings, null, 2), "utf-8");
107477
106850
  } catch {
107478
106851
  if (!config3.quiet) {
107479
106852
  console.warn(`[claudish] Warning: could not merge user settings: ${userSettingsValue}`);
@@ -107561,13 +106934,14 @@ async function runClaudeWithProxy(config3, proxyUrl, onCleanup) {
107561
106934
  console.error("Install it from: https://claude.com/claude-code");
107562
106935
  console.error(`
107563
106936
  Or set CLAUDE_PATH to your custom installation:`);
107564
- const home = homedir26();
107565
- const localPath = isWindows2() ? join28(home, ".claude", "local", "claude.exe") : join28(home, ".claude", "local", "claude");
106937
+ const home = homedir25();
106938
+ const localPath = isWindows2() ? join27(home, ".claude", "local", "claude.exe") : join27(home, ".claude", "local", "claude");
107566
106939
  console.error(` export CLAUDE_PATH=${localPath}`);
107567
106940
  process.exit(1);
107568
106941
  }
107569
106942
  const needsShell = isWindows2() && claudeBinary.endsWith(".cmd");
107570
106943
  const spawnCommand = needsShell ? `"${claudeBinary}"` : claudeBinary;
106944
+ setClaudeCodeRunning(true);
107571
106945
  const proc = spawn4(spawnCommand, claudeArgs, {
107572
106946
  env: env2,
107573
106947
  stdio: "inherit",
@@ -107576,6 +106950,7 @@ Or set CLAUDE_PATH to your custom installation:`);
107576
106950
  setupSignalHandlers(proc, tempSettingsPath, config3.quiet, onCleanup);
107577
106951
  const exitCode = await new Promise((resolve4) => {
107578
106952
  proc.on("exit", (code) => {
106953
+ setClaudeCodeRunning(false);
107579
106954
  resolve4(code ?? 1);
107580
106955
  });
107581
106956
  });
@@ -107608,23 +106983,23 @@ function setupSignalHandlers(proc, tempSettingsPath, quiet, onCleanup) {
107608
106983
  async function findClaudeBinary() {
107609
106984
  const isWindows3 = process.platform === "win32";
107610
106985
  if (process.env.CLAUDE_PATH) {
107611
- if (existsSync28(process.env.CLAUDE_PATH)) {
106986
+ if (existsSync27(process.env.CLAUDE_PATH)) {
107612
106987
  return process.env.CLAUDE_PATH;
107613
106988
  }
107614
106989
  }
107615
- const home = homedir26();
107616
- const localPath = isWindows3 ? join28(home, ".claude", "local", "claude.exe") : join28(home, ".claude", "local", "claude");
107617
- if (existsSync28(localPath)) {
106990
+ const home = homedir25();
106991
+ const localPath = isWindows3 ? join27(home, ".claude", "local", "claude.exe") : join27(home, ".claude", "local", "claude");
106992
+ if (existsSync27(localPath)) {
107618
106993
  return localPath;
107619
106994
  }
107620
106995
  if (isWindows3) {
107621
106996
  const windowsPaths = [
107622
- join28(home, "AppData", "Roaming", "npm", "claude.cmd"),
107623
- join28(home, ".npm-global", "claude.cmd"),
107624
- join28(home, "node_modules", ".bin", "claude.cmd")
106997
+ join27(home, "AppData", "Roaming", "npm", "claude.cmd"),
106998
+ join27(home, ".npm-global", "claude.cmd"),
106999
+ join27(home, "node_modules", ".bin", "claude.cmd")
107625
107000
  ];
107626
107001
  for (const path3 of windowsPaths) {
107627
- if (existsSync28(path3)) {
107002
+ if (existsSync27(path3)) {
107628
107003
  return path3;
107629
107004
  }
107630
107005
  }
@@ -107632,14 +107007,14 @@ async function findClaudeBinary() {
107632
107007
  const commonPaths = [
107633
107008
  "/usr/local/bin/claude",
107634
107009
  "/opt/homebrew/bin/claude",
107635
- join28(home, ".npm-global/bin/claude"),
107636
- join28(home, ".local/bin/claude"),
107637
- join28(home, "node_modules/.bin/claude"),
107010
+ join27(home, ".npm-global/bin/claude"),
107011
+ join27(home, ".local/bin/claude"),
107012
+ join27(home, "node_modules/.bin/claude"),
107638
107013
  "/data/data/com.termux/files/usr/bin/claude",
107639
- join28(home, "../usr/bin/claude")
107014
+ join27(home, "../usr/bin/claude")
107640
107015
  ];
107641
107016
  for (const path3 of commonPaths) {
107642
- if (existsSync28(path3)) {
107017
+ if (existsSync27(path3)) {
107643
107018
  return path3;
107644
107019
  }
107645
107020
  }
@@ -107679,6 +107054,7 @@ async function checkClaudeInstalled() {
107679
107054
  var init_claude_runner = __esm(() => {
107680
107055
  init_config();
107681
107056
  init_model_parser();
107057
+ init_telemetry();
107682
107058
  });
107683
107059
 
107684
107060
  // src/diag-output.ts
@@ -107688,18 +107064,18 @@ __export(exports_diag_output, {
107688
107064
  NullDiagOutput: () => NullDiagOutput,
107689
107065
  LogFileDiagOutput: () => LogFileDiagOutput
107690
107066
  });
107691
- import { createWriteStream as createWriteStream3, mkdirSync as mkdirSync15, writeFileSync as writeFileSync17, unlinkSync as unlinkSync10 } from "fs";
107692
- import { homedir as homedir27 } from "os";
107693
- import { join as join29 } from "path";
107067
+ import { createWriteStream as createWriteStream3, mkdirSync as mkdirSync13, writeFileSync as writeFileSync14, unlinkSync as unlinkSync10 } from "fs";
107068
+ import { homedir as homedir26 } from "os";
107069
+ import { join as join28 } from "path";
107694
107070
  function getClaudishDir() {
107695
- const dir = join29(homedir27(), ".claudish");
107071
+ const dir = join28(homedir26(), ".claudish");
107696
107072
  try {
107697
- mkdirSync15(dir, { recursive: true });
107073
+ mkdirSync13(dir, { recursive: true });
107698
107074
  } catch {}
107699
107075
  return dir;
107700
107076
  }
107701
107077
  function getDiagLogPath() {
107702
- return join29(getClaudishDir(), `diag-${process.pid}.log`);
107078
+ return join28(getClaudishDir(), `diag-${process.pid}.log`);
107703
107079
  }
107704
107080
 
107705
107081
  class LogFileDiagOutput {
@@ -107708,7 +107084,7 @@ class LogFileDiagOutput {
107708
107084
  constructor() {
107709
107085
  this.logPath = getDiagLogPath();
107710
107086
  try {
107711
- writeFileSync17(this.logPath, `--- claudish diag session ${new Date().toISOString()} ---
107087
+ writeFileSync14(this.logPath, `--- claudish diag session ${new Date().toISOString()} ---
107712
107088
  `);
107713
107089
  } catch {}
107714
107090
  this.stream = createWriteStream3(this.logPath, { flags: "a" });
@@ -107758,12 +107134,12 @@ __export(exports_team_grid, {
107758
107134
  });
107759
107135
  import { spawn as spawn5 } from "child_process";
107760
107136
  import {
107761
- existsSync as existsSync29,
107762
- readFileSync as readFileSync24,
107763
- writeFileSync as writeFileSync18
107137
+ existsSync as existsSync28,
107138
+ readFileSync as readFileSync23,
107139
+ writeFileSync as writeFileSync15
107764
107140
  } from "fs";
107765
- import { dirname as dirname7, join as join30 } from "path";
107766
- import { fileURLToPath as fileURLToPath6 } from "url";
107141
+ import { dirname as dirname6, join as join29 } from "path";
107142
+ import { fileURLToPath as fileURLToPath5 } from "url";
107767
107143
  import { execSync as execSync2 } from "child_process";
107768
107144
  import { connect as netConnect } from "net";
107769
107145
  import { setTimeout as wait } from "timers/promises";
@@ -107848,22 +107224,22 @@ function buildPaneHeader(model, prompt, bg2) {
107848
107224
  return lines.join(" ");
107849
107225
  }
107850
107226
  function findMagmuxBinary() {
107851
- const thisFile = fileURLToPath6(import.meta.url);
107852
- const thisDir = dirname7(thisFile);
107853
- const pkgRoot = join30(thisDir, "..");
107227
+ const thisFile = fileURLToPath5(import.meta.url);
107228
+ const thisDir = dirname6(thisFile);
107229
+ const pkgRoot = join29(thisDir, "..");
107854
107230
  const platform3 = process.platform;
107855
107231
  const arch = process.arch;
107856
- const bundledMagmux = join30(pkgRoot, "native", `magmux-${platform3}-${arch}`);
107857
- if (existsSync29(bundledMagmux))
107232
+ const bundledMagmux = join29(pkgRoot, "native", `magmux-${platform3}-${arch}`);
107233
+ if (existsSync28(bundledMagmux))
107858
107234
  return bundledMagmux;
107859
107235
  try {
107860
107236
  const pkgName = `@claudish/magmux-${platform3}-${arch}`;
107861
107237
  let searchDir = pkgRoot;
107862
107238
  for (let i = 0;i < 5; i++) {
107863
- const candidate = join30(searchDir, "node_modules", pkgName, "bin", "magmux");
107864
- if (existsSync29(candidate))
107239
+ const candidate = join29(searchDir, "node_modules", pkgName, "bin", "magmux");
107240
+ if (existsSync28(candidate))
107865
107241
  return candidate;
107866
- const parent = dirname7(searchDir);
107242
+ const parent = dirname6(searchDir);
107867
107243
  if (parent === searchDir)
107868
107244
  break;
107869
107245
  searchDir = parent;
@@ -107880,7 +107256,7 @@ function findMagmuxBinary() {
107880
107256
  async function subscribeToMagmux(sockPath, onEvent) {
107881
107257
  let client = null;
107882
107258
  for (let attempt = 0;attempt < 40; attempt++) {
107883
- if (existsSync29(sockPath)) {
107259
+ if (existsSync28(sockPath)) {
107884
107260
  try {
107885
107261
  client = await new Promise((resolve4, reject) => {
107886
107262
  const s = netConnect(sockPath);
@@ -107967,9 +107343,9 @@ async function runWithGrid(sessionPath, models, input, opts) {
107967
107343
  const keep = opts?.keep ?? false;
107968
107344
  const manifest = setupSession(sessionPath, models, input);
107969
107345
  const startedAt = new Date().toISOString();
107970
- const gridfilePath = join30(sessionPath, "gridfile.txt");
107971
- const prompt = readFileSync24(join30(sessionPath, "input.md"), "utf-8").replace(/'/g, "'\\''").replace(/\n/g, " ");
107972
- const rawPrompt = readFileSync24(join30(sessionPath, "input.md"), "utf-8");
107346
+ const gridfilePath = join29(sessionPath, "gridfile.txt");
107347
+ const prompt = readFileSync23(join29(sessionPath, "input.md"), "utf-8").replace(/'/g, "'\\''").replace(/\n/g, " ");
107348
+ const rawPrompt = readFileSync23(join29(sessionPath, "input.md"), "utf-8");
107973
107349
  const usedBannerColors = new Set;
107974
107350
  const gridLines = Object.entries(manifest.models).map(([anonId]) => {
107975
107351
  const model = manifest.models[anonId].model;
@@ -107980,7 +107356,7 @@ async function runWithGrid(sessionPath, models, input, opts) {
107980
107356
  const header = buildPaneHeader(model, rawPrompt, bg2);
107981
107357
  return `${header} claudish --model ${model} -y --quiet '${prompt}'`;
107982
107358
  });
107983
- writeFileSync18(gridfilePath, gridLines.join(`
107359
+ writeFileSync15(gridfilePath, gridLines.join(`
107984
107360
  `) + `
107985
107361
  `, "utf-8");
107986
107362
  const magmuxPath = findMagmuxBinary();
@@ -108000,8 +107376,8 @@ async function runWithGrid(sessionPath, models, input, opts) {
108000
107376
  });
108001
107377
  const [{ results }] = await Promise.all([subscription, procExit]);
108002
107378
  const status = buildTeamStatus(manifest, startedAt, results?.panes ?? null);
108003
- const statusPath = join30(sessionPath, "status.json");
108004
- writeFileSync18(statusPath, JSON.stringify(status, null, 2), "utf-8");
107379
+ const statusPath = join29(sessionPath, "status.json");
107380
+ writeFileSync15(statusPath, JSON.stringify(status, null, 2), "utf-8");
108005
107381
  return status;
108006
107382
  }
108007
107383
  var BANNER_BG_COLORS;
@@ -108023,16 +107399,16 @@ var init_team_grid = __esm(() => {
108023
107399
 
108024
107400
  // src/index.ts
108025
107401
  var import_dotenv3 = __toESM(require_main(), 1);
108026
- import { existsSync as existsSync30, readFileSync as readFileSync25 } from "fs";
108027
- import { homedir as homedir28 } from "os";
108028
- import { join as join31 } from "path";
107402
+ import { existsSync as existsSync29, readFileSync as readFileSync24 } from "fs";
107403
+ import { homedir as homedir27 } from "os";
107404
+ import { join as join30 } from "path";
108029
107405
  import_dotenv3.config({ quiet: true });
108030
107406
  function loadStoredApiKeys() {
108031
107407
  try {
108032
- const configPath = join31(homedir28(), ".claudish", "config.json");
108033
- if (!existsSync30(configPath))
107408
+ const configPath = join30(homedir27(), ".claudish", "config.json");
107409
+ if (!existsSync29(configPath))
108034
107410
  return;
108035
- const raw2 = readFileSync25(configPath, "utf-8");
107411
+ const raw2 = readFileSync24(configPath, "utf-8");
108036
107412
  const cfg = JSON.parse(raw2);
108037
107413
  if (cfg.apiKeys) {
108038
107414
  for (const [envVar, value] of Object.entries(cfg.apiKeys)) {
@@ -108146,14 +107522,14 @@ async function runCli() {
108146
107522
  if (cliConfig.team && cliConfig.team.length > 0) {
108147
107523
  let prompt = cliConfig.claudeArgs.join(" ");
108148
107524
  if (cliConfig.inputFile) {
108149
- prompt = readFileSync25(cliConfig.inputFile, "utf-8");
107525
+ prompt = readFileSync24(cliConfig.inputFile, "utf-8");
108150
107526
  }
108151
107527
  if (!prompt.trim()) {
108152
107528
  console.error("Error: --team requires a prompt (positional args or -f <file>)");
108153
107529
  process.exit(1);
108154
107530
  }
108155
107531
  const mode = cliConfig.teamMode ?? "default";
108156
- const sessionPath = join31(process.cwd(), `.claudish-team-${Date.now()}`);
107532
+ const sessionPath = join30(process.cwd(), `.claudish-team-${Date.now()}`);
108157
107533
  if (mode === "json") {
108158
107534
  const { setupSession: setupSession2, runModels: runModels2 } = await Promise.resolve().then(() => (init_team_orchestrator(), exports_team_orchestrator));
108159
107535
  setupSession2(sessionPath, cliConfig.team, prompt);
@@ -108163,9 +107539,9 @@ async function runCli() {
108163
107539
  });
108164
107540
  const result = { ...status2, responses: {} };
108165
107541
  for (const anonId of Object.keys(status2.models)) {
108166
- const responsePath = join31(sessionPath, `response-${anonId}.md`);
107542
+ const responsePath = join30(sessionPath, `response-${anonId}.md`);
108167
107543
  try {
108168
- const raw2 = readFileSync25(responsePath, "utf-8").trim();
107544
+ const raw2 = readFileSync24(responsePath, "utf-8").trim();
108169
107545
  try {
108170
107546
  result.responses[anonId] = JSON.parse(raw2);
108171
107547
  } catch {