claudish 2.9.0 → 2.10.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 +133 -30
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -34353,6 +34353,7 @@ process.stdin.on('end', () => {
34353
34353
 
34354
34354
  let ctx = 100, cost = 0;
34355
34355
  const model = process.env.CLAUDISH_ACTIVE_MODEL_NAME || 'unknown';
34356
+ const isLocal = process.env.CLAUDISH_IS_LOCAL === 'true';
34356
34357
 
34357
34358
  try {
34358
34359
  const tokens = JSON.parse(fs.readFileSync('${escapedTokenPath}', 'utf-8'));
@@ -34365,8 +34366,8 @@ process.stdin.on('end', () => {
34365
34366
  } catch {}
34366
34367
  }
34367
34368
 
34368
- const costStr = cost.toFixed(3);
34369
- console.log(\`\${CYAN}\${BOLD}\${dir}\${RESET} \${DIM}•\${RESET} \${YELLOW}\${model}\${RESET} \${DIM}•\${RESET} \${GREEN}$\${costStr}\${RESET} \${DIM}•\${RESET} \${MAGENTA}\${ctx}%\${RESET}\`);
34369
+ const costDisplay = isLocal ? 'LOCAL' : ('$' + cost.toFixed(3));
34370
+ console.log(\`\${CYAN}\${BOLD}\${dir}\${RESET} \${DIM}•\${RESET} \${YELLOW}\${model}\${RESET} \${DIM}•\${RESET} \${GREEN}\${costDisplay}\${RESET} \${DIM}•\${RESET} \${MAGENTA}\${ctx}%\${RESET}\`);
34370
34371
  } catch (e) {
34371
34372
  console.log('claudish');
34372
34373
  }
@@ -34392,7 +34393,7 @@ function createTempSettingsFile(modelDisplay, port) {
34392
34393
  const DIM2 = "\\033[2m";
34393
34394
  const RESET2 = "\\033[0m";
34394
34395
  const BOLD2 = "\\033[1m";
34395
- statusCommand = `JSON=$(cat) && DIR=$(basename "$(pwd)") && [ \${#DIR} -gt 15 ] && DIR="\${DIR:0:12}..." || true && CTX=100 && COST="0" && if [ -f "${tokenFilePath}" ]; then TOKENS=$(cat "${tokenFilePath}" 2>/dev/null) && REAL_COST=$(echo "$TOKENS" | grep -o '"total_cost":[0-9.]*' | cut -d: -f2) && REAL_CTX=$(echo "$TOKENS" | grep -o '"context_left_percent":[0-9]*' | grep -o '[0-9]*') && if [ ! -z "$REAL_COST" ]; then COST="$REAL_COST"; else COST=$(echo "$JSON" | grep -o '"total_cost_usd":[0-9.]*' | cut -d: -f2); fi && if [ ! -z "$REAL_CTX" ]; then CTX="$REAL_CTX"; fi; else COST=$(echo "$JSON" | grep -o '"total_cost_usd":[0-9.]*' | cut -d: -f2); fi && [ -z "$COST" ] && COST="0" || true && printf "${CYAN2}${BOLD2}%s${RESET2} ${DIM2}•${RESET2} ${YELLOW2}%s${RESET2} ${DIM2}•${RESET2} ${GREEN2}\\$%.3f${RESET2} ${DIM2}•${RESET2} ${MAGENTA}%s%%${RESET2}\\n" "$DIR" "$CLAUDISH_ACTIVE_MODEL_NAME" "$COST" "$CTX"`;
34396
+ statusCommand = `JSON=$(cat) && DIR=$(basename "$(pwd)") && [ \${#DIR} -gt 15 ] && DIR="\${DIR:0:12}..." || true && CTX=100 && COST="0" && if [ -f "${tokenFilePath}" ]; then TOKENS=$(cat "${tokenFilePath}" 2>/dev/null) && REAL_COST=$(echo "$TOKENS" | grep -o '"total_cost":[0-9.]*' | cut -d: -f2) && REAL_CTX=$(echo "$TOKENS" | grep -o '"context_left_percent":[0-9]*' | grep -o '[0-9]*') && if [ ! -z "$REAL_COST" ]; then COST="$REAL_COST"; else COST=$(echo "$JSON" | grep -o '"total_cost_usd":[0-9.]*' | cut -d: -f2); fi && if [ ! -z "$REAL_CTX" ]; then CTX="$REAL_CTX"; fi; else COST=$(echo "$JSON" | grep -o '"total_cost_usd":[0-9.]*' | cut -d: -f2); fi && [ -z "$COST" ] && COST="0" || true && if [ "$CLAUDISH_IS_LOCAL" = "true" ]; then COST_DISPLAY="LOCAL"; else COST_DISPLAY=$(printf "\\$%.3f" "$COST"); fi && printf "${CYAN2}${BOLD2}%s${RESET2} ${DIM2}•${RESET2} ${YELLOW2}%s${RESET2} ${DIM2}•${RESET2} ${GREEN2}%s${RESET2} ${DIM2}•${RESET2} ${MAGENTA}%s%%${RESET2}\\n" "$DIR" "$CLAUDISH_ACTIVE_MODEL_NAME" "$COST_DISPLAY" "$CTX"`;
34396
34397
  }
34397
34398
  const settings = {
34398
34399
  statusLine: {
@@ -34438,10 +34439,12 @@ async function runClaudeWithProxy(config3, proxyUrl) {
34438
34439
  claudeArgs.push(...config3.claudeArgs);
34439
34440
  }
34440
34441
  }
34442
+ const isLocalModel = modelId.startsWith("ollama/") || modelId.startsWith("ollama:") || modelId.startsWith("lmstudio/") || modelId.startsWith("lmstudio:") || modelId.startsWith("vllm/") || modelId.startsWith("vllm:") || modelId.startsWith("http://") || modelId.startsWith("https://");
34441
34443
  const env = {
34442
34444
  ...process.env,
34443
34445
  ANTHROPIC_BASE_URL: proxyUrl,
34444
34446
  [ENV.CLAUDISH_ACTIVE_MODEL_NAME]: modelId,
34447
+ CLAUDISH_IS_LOCAL: isLocalModel ? "true" : "false",
34445
34448
  [ENV.ANTHROPIC_MODEL]: modelId,
34446
34449
  [ENV.ANTHROPIC_SMALL_FAST_MODEL]: modelId
34447
34450
  };
@@ -34926,17 +34929,40 @@ async function fetchOllamaModels() {
34926
34929
  return [];
34927
34930
  const data = await response.json();
34928
34931
  const models = data.models || [];
34929
- return models.map((m) => ({
34930
- id: `ollama/${m.name}`,
34931
- name: m.name,
34932
- description: `Local Ollama model (${m.details?.parameter_size || "unknown size"})`,
34933
- provider: "ollama",
34934
- context_length: null,
34935
- pricing: { prompt: "0", completion: "0" },
34936
- isLocal: true,
34937
- details: m.details,
34938
- size: m.size
34932
+ const modelsWithCapabilities = await Promise.all(models.map(async (m) => {
34933
+ let capabilities = [];
34934
+ try {
34935
+ const showResponse = await fetch(`${ollamaHost}/api/show`, {
34936
+ method: "POST",
34937
+ headers: { "Content-Type": "application/json" },
34938
+ body: JSON.stringify({ name: m.name }),
34939
+ signal: AbortSignal.timeout(2000)
34940
+ });
34941
+ if (showResponse.ok) {
34942
+ const showData = await showResponse.json();
34943
+ capabilities = showData.capabilities || [];
34944
+ }
34945
+ } catch {}
34946
+ const supportsTools = capabilities.includes("tools");
34947
+ const isEmbeddingModel = capabilities.includes("embedding") || m.name.toLowerCase().includes("embed");
34948
+ const sizeInfo = m.details?.parameter_size || "unknown size";
34949
+ const toolsIndicator = supportsTools ? "✓ tools" : "✗ no tools";
34950
+ return {
34951
+ id: `ollama/${m.name}`,
34952
+ name: m.name,
34953
+ description: `Local Ollama model (${sizeInfo}, ${toolsIndicator})`,
34954
+ provider: "ollama",
34955
+ context_length: null,
34956
+ pricing: { prompt: "0", completion: "0" },
34957
+ isLocal: true,
34958
+ supportsTools,
34959
+ isEmbeddingModel,
34960
+ capabilities,
34961
+ details: m.details,
34962
+ size: m.size
34963
+ };
34939
34964
  }));
34965
+ return modelsWithCapabilities.filter((m) => !m.isEmbeddingModel);
34940
34966
  } catch (e) {
34941
34967
  return [];
34942
34968
  }
@@ -34990,6 +35016,10 @@ async function searchAndPrintModels(query, forceUpdate) {
34990
35016
  console.log(`No models found matching "${query}"`);
34991
35017
  return;
34992
35018
  }
35019
+ const RED = "\x1B[31m";
35020
+ const GREEN2 = "\x1B[32m";
35021
+ const RESET2 = "\x1B[0m";
35022
+ const DIM2 = "\x1B[2m";
34993
35023
  console.log(`
34994
35024
  Found ${results.length} matching models:
34995
35025
  `);
@@ -35020,9 +35050,17 @@ Found ${results.length} matching models:
35020
35050
  const contextLen = model.context_length || model.top_provider?.context_length || 0;
35021
35051
  const context = contextLen > 0 ? `${Math.round(contextLen / 1000)}K` : "N/A";
35022
35052
  const contextPadded = context.padEnd(7);
35023
- console.log(` ${modelIdPadded} ${providerPadded} ${pricingPadded} ${contextPadded} ${(score * 100).toFixed(0)}%`);
35053
+ if (model.isLocal && model.supportsTools === false) {
35054
+ console.log(` ${RED}${modelIdPadded} ${providerPadded} ${pricingPadded} ${contextPadded} ${(score * 100).toFixed(0)}% ✗ no tools${RESET2}`);
35055
+ } else if (model.isLocal && model.supportsTools === true) {
35056
+ console.log(` ${GREEN2}${modelIdPadded}${RESET2} ${providerPadded} ${pricingPadded} ${contextPadded} ${(score * 100).toFixed(0)}%`);
35057
+ } else {
35058
+ console.log(` ${modelIdPadded} ${providerPadded} ${pricingPadded} ${contextPadded} ${(score * 100).toFixed(0)}%`);
35059
+ }
35024
35060
  }
35025
35061
  console.log("");
35062
+ console.log(`${DIM2}Local models: ${RED}red${RESET2}${DIM2} = no tool support (incompatible), ${GREEN2}green${RESET2}${DIM2} = compatible${RESET2}`);
35063
+ console.log("");
35026
35064
  console.log("Use a model: claudish --model <model-id>");
35027
35065
  console.log("Local models: claudish --model ollama/<model-name>");
35028
35066
  }
@@ -35077,23 +35115,37 @@ async function printAllModels(jsonOutput, forceUpdate) {
35077
35115
  }, null, 2));
35078
35116
  return;
35079
35117
  }
35118
+ const RED = "\x1B[31m";
35119
+ const GREEN2 = "\x1B[32m";
35120
+ const RESET2 = "\x1B[0m";
35121
+ const DIM2 = "\x1B[2m";
35080
35122
  if (ollamaModels.length > 0) {
35123
+ const toolCapableCount = ollamaModels.filter((m) => m.supportsTools).length;
35081
35124
  console.log(`
35082
- \uD83C\uDFE0 LOCAL OLLAMA MODELS (${ollamaModels.length} installed):
35125
+ \uD83C\uDFE0 LOCAL OLLAMA MODELS (${ollamaModels.length} installed, ${toolCapableCount} with tool support):
35083
35126
  `);
35084
- console.log(" " + "─".repeat(70));
35127
+ console.log(" Model Size Params Tools");
35128
+ console.log(" " + "─".repeat(76));
35085
35129
  for (const model of ollamaModels) {
35086
- const shortId = model.name;
35087
- const modelId = shortId.length > 40 ? shortId.substring(0, 37) + "..." : shortId;
35088
- const modelIdPadded = modelId.padEnd(42);
35130
+ const fullId = model.id;
35131
+ const modelId = fullId.length > 35 ? fullId.substring(0, 32) + "..." : fullId;
35132
+ const modelIdPadded = modelId.padEnd(38);
35089
35133
  const size = model.size ? `${(model.size / 1e9).toFixed(1)}GB` : "N/A";
35090
35134
  const sizePadded = size.padEnd(12);
35091
35135
  const params = model.details?.parameter_size || "N/A";
35092
35136
  const paramsPadded = params.padEnd(8);
35093
- console.log(` ${modelIdPadded} ${sizePadded} ${paramsPadded}`);
35137
+ if (model.supportsTools) {
35138
+ console.log(` ${modelIdPadded} ${sizePadded} ${paramsPadded} ${GREEN2}✓${RESET2}`);
35139
+ } else {
35140
+ console.log(` ${RED}${modelIdPadded} ${sizePadded} ${paramsPadded} ✗ no tools${RESET2}`);
35141
+ }
35094
35142
  }
35095
35143
  console.log("");
35144
+ console.log(` ${GREEN2}✓${RESET2} = Compatible with Claude Code (supports tool calling)`);
35145
+ console.log(` ${RED}✗${RESET2} = Not compatible ${DIM2}(Claude Code requires tool support)${RESET2}`);
35146
+ console.log("");
35096
35147
  console.log(" Use: claudish --model ollama/<model-name>");
35148
+ console.log(" Pull a compatible model: ollama pull llama3.2");
35097
35149
  } else {
35098
35150
  console.log(`
35099
35151
  \uD83C\uDFE0 LOCAL OLLAMA: Not running or no models installed`);
@@ -39535,6 +39587,10 @@ var init_openai_compat = __esm(() => {
39535
39587
  });
39536
39588
 
39537
39589
  // src/handlers/local-provider-handler.ts
39590
+ import { writeFileSync as writeFileSync9 } from "node:fs";
39591
+ import { tmpdir as tmpdir3 } from "node:os";
39592
+ import { join as join9 } from "node:path";
39593
+
39538
39594
  class LocalProviderHandler {
39539
39595
  provider;
39540
39596
  modelName;
@@ -39543,6 +39599,9 @@ class LocalProviderHandler {
39543
39599
  port;
39544
39600
  healthChecked = false;
39545
39601
  isHealthy = false;
39602
+ contextWindow = 8192;
39603
+ sessionInputTokens = 0;
39604
+ sessionOutputTokens = 0;
39546
39605
  constructor(provider, modelName, port) {
39547
39606
  this.provider = provider;
39548
39607
  this.modelName = modelName;
@@ -39587,6 +39646,49 @@ class LocalProviderHandler {
39587
39646
  this.isHealthy = false;
39588
39647
  return false;
39589
39648
  }
39649
+ async fetchContextWindow() {
39650
+ if (this.provider.name !== "ollama")
39651
+ return;
39652
+ try {
39653
+ const response = await fetch(`${this.provider.baseUrl}/api/show`, {
39654
+ method: "POST",
39655
+ headers: { "Content-Type": "application/json" },
39656
+ body: JSON.stringify({ name: this.modelName }),
39657
+ signal: AbortSignal.timeout(3000)
39658
+ });
39659
+ if (response.ok) {
39660
+ const data = await response.json();
39661
+ const ctxFromInfo = data.model_info?.["general.context_length"];
39662
+ const ctxFromParams = data.parameters?.match(/num_ctx\s+(\d+)/)?.[1];
39663
+ if (ctxFromInfo) {
39664
+ this.contextWindow = parseInt(ctxFromInfo, 10);
39665
+ } else if (ctxFromParams) {
39666
+ this.contextWindow = parseInt(ctxFromParams, 10);
39667
+ } else {
39668
+ this.contextWindow = 8192;
39669
+ }
39670
+ log(`[LocalProvider:${this.provider.name}] Context window: ${this.contextWindow}`);
39671
+ }
39672
+ } catch (e) {}
39673
+ }
39674
+ writeTokenFile(input, output) {
39675
+ try {
39676
+ this.sessionInputTokens += input;
39677
+ this.sessionOutputTokens += output;
39678
+ const total = this.sessionInputTokens + this.sessionOutputTokens;
39679
+ const leftPct = this.contextWindow > 0 ? Math.max(0, Math.min(100, Math.round((this.contextWindow - total) / this.contextWindow * 100))) : 100;
39680
+ const data = {
39681
+ input_tokens: this.sessionInputTokens,
39682
+ output_tokens: this.sessionOutputTokens,
39683
+ total_tokens: total,
39684
+ total_cost: 0,
39685
+ context_window: this.contextWindow,
39686
+ context_left_percent: leftPct,
39687
+ updated_at: Date.now()
39688
+ };
39689
+ writeFileSync9(join9(tmpdir3(), `claudish-tokens-${this.port}.json`), JSON.stringify(data), "utf-8");
39690
+ } catch (e) {}
39691
+ }
39590
39692
  async handle(c, payload) {
39591
39693
  const target = this.modelName;
39592
39694
  logStructured(`LocalProvider Request`, {
@@ -39600,6 +39702,7 @@ class LocalProviderHandler {
39600
39702
  if (!healthy) {
39601
39703
  return this.errorResponse(c, "connection_error", this.getConnectionErrorMessage());
39602
39704
  }
39705
+ await this.fetchContextWindow();
39603
39706
  }
39604
39707
  const { claudeRequest, droppedParams } = transformOpenAIToClaude(payload);
39605
39708
  const messages = convertMessagesToOpenAI(claudeRequest, target, filterIdentity);
@@ -39652,7 +39755,7 @@ class LocalProviderHandler {
39652
39755
  c.header("X-Dropped-Params", droppedParams.join(", "));
39653
39756
  }
39654
39757
  if (openAIPayload.stream) {
39655
- return createStreamingResponseHandler(c, response, adapter, target, this.middlewareManager);
39758
+ return createStreamingResponseHandler(c, response, adapter, target, this.middlewareManager, (input, output) => this.writeTokenFile(input, output));
39656
39759
  }
39657
39760
  const data = await response.json();
39658
39761
  return c.json(data);
@@ -39948,24 +40051,24 @@ __export(exports_update_checker, {
39948
40051
  });
39949
40052
  import { execSync } from "node:child_process";
39950
40053
  import { createInterface as createInterface2 } from "node:readline";
39951
- import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync9, mkdirSync as mkdirSync4, unlinkSync as unlinkSync2 } from "node:fs";
39952
- import { join as join9 } from "node:path";
39953
- import { tmpdir as tmpdir3, homedir as homedir2, platform as platform2 } from "node:os";
40054
+ import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync10, mkdirSync as mkdirSync4, unlinkSync as unlinkSync2 } from "node:fs";
40055
+ import { join as join10 } from "node:path";
40056
+ import { tmpdir as tmpdir4, homedir as homedir2, platform as platform2 } from "node:os";
39954
40057
  function getCacheFilePath() {
39955
40058
  let cacheDir;
39956
40059
  if (isWindows2) {
39957
- const localAppData = process.env.LOCALAPPDATA || join9(homedir2(), "AppData", "Local");
39958
- cacheDir = join9(localAppData, "claudish");
40060
+ const localAppData = process.env.LOCALAPPDATA || join10(homedir2(), "AppData", "Local");
40061
+ cacheDir = join10(localAppData, "claudish");
39959
40062
  } else {
39960
- cacheDir = join9(homedir2(), ".cache", "claudish");
40063
+ cacheDir = join10(homedir2(), ".cache", "claudish");
39961
40064
  }
39962
40065
  try {
39963
40066
  if (!existsSync7(cacheDir)) {
39964
40067
  mkdirSync4(cacheDir, { recursive: true });
39965
40068
  }
39966
- return join9(cacheDir, "update-check.json");
40069
+ return join10(cacheDir, "update-check.json");
39967
40070
  } catch {
39968
- return join9(tmpdir3(), "claudish-update-check.json");
40071
+ return join10(tmpdir4(), "claudish-update-check.json");
39969
40072
  }
39970
40073
  }
39971
40074
  function readCache() {
@@ -39987,7 +40090,7 @@ function writeCache(latestVersion) {
39987
40090
  lastCheck: Date.now(),
39988
40091
  latestVersion
39989
40092
  };
39990
- writeFileSync9(cachePath, JSON.stringify(data), "utf-8");
40093
+ writeFileSync10(cachePath, JSON.stringify(data), "utf-8");
39991
40094
  } catch {}
39992
40095
  }
39993
40096
  function isCacheValid(cache) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudish",
3
- "version": "2.9.0",
3
+ "version": "2.10.0",
4
4
  "description": "Run Claude Code with any OpenRouter model - CLI tool and MCP server",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",