clawculator 2.1.2 → 2.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawculator",
3
- "version": "2.1.2",
3
+ "version": "2.1.3",
4
4
  "description": "AI cost forensics for OpenClaw and multi-model setups. Your friendly penny pincher. 100% offline. Zero AI. Pure deterministic logic.",
5
5
  "main": "src/analyzer.js",
6
6
  "bin": {
package/src/analyzer.js CHANGED
@@ -141,6 +141,11 @@ function isLocalModel(modelStr) {
141
141
  ['qwen', 'llama', 'mistral', 'phi', 'gemma', 'deepseek', 'kimi'].some(m => lower.includes(m));
142
142
  }
143
143
 
144
+ function isHaikuTier(modelStr) {
145
+ if (!modelStr) return false;
146
+ return modelStr.toLowerCase().includes('haiku');
147
+ }
148
+
144
149
  function isOpenRouter(modelStr) {
145
150
  return modelStr?.toLowerCase().startsWith('openrouter/');
146
151
  }
@@ -337,25 +342,37 @@ function analyzeConfig(configPath) {
337
342
  const hooks = config.hooks?.internal?.entries || config.hooks || {};
338
343
  const hookNames = Object.keys(hooks).filter(k => k !== 'enabled' && k !== 'token' && k !== 'path');
339
344
  let hookIssues = 0;
345
+ let haikuHooks = 0;
340
346
 
341
347
  for (const name of hookNames) {
342
348
  const hook = typeof hooks[name] === 'object' ? hooks[name] : {};
343
349
  if (hook.enabled === false) continue;
344
350
  const hookModel = hook.model || primaryModel;
345
- if (!isLocalModel(hookModel) && resolveModel(hookModel)) {
351
+ if (isLocalModel(hookModel)) {
352
+ // local = free, no finding needed
353
+ } else if (isHaikuTier(hookModel) && resolveModel(hookModel)) {
354
+ const monthly = costPerCall(resolveModel(hookModel), 1000, 200) * 50 * 30;
355
+ haikuHooks++;
356
+ findings.push({
357
+ severity: 'low', source: 'hooks',
358
+ message: `Hook "${name}" on Haiku — minimal cost, good choice`,
359
+ detail: `~50 fires/day estimated · $${monthly.toFixed(2)}/month`,
360
+ monthlyCost: monthly,
361
+ });
362
+ } else if (resolveModel(hookModel)) {
346
363
  const monthly = costPerCall(resolveModel(hookModel), 1000, 200) * 50 * 30;
347
364
  hookIssues++;
348
365
  findings.push({
349
366
  severity: 'high', source: 'hooks',
350
- message: `Hook "${name}" running on paid model: ${hookModel || 'primary'}`,
367
+ message: `Hook "${name}" running on ${hookModel} switch to Haiku or local`,
351
368
  detail: `~50 fires/day estimated · $${monthly.toFixed(2)}/month`,
352
369
  monthlyCost: monthly,
353
370
  ...FIXES.HOOK_PAID_MODEL(name),
354
371
  });
355
372
  }
356
373
  }
357
- if (hookNames.length > 0 && hookIssues === 0) {
358
- findings.push({ severity: 'info', source: 'hooks', message: `All ${hookNames.length} hooks on cheap/local models ✓`, monthlyCost: 0 });
374
+ if (hookNames.length > 0 && hookIssues === 0 && haikuHooks === 0) {
375
+ findings.push({ severity: 'info', source: 'hooks', message: `All ${hookNames.length} hooks on local models ✓`, monthlyCost: 0 });
359
376
  }
360
377
 
361
378
  // ── WhatsApp ───────────────────────────────────────────────────
package/src/reporter.js CHANGED
@@ -4,6 +4,7 @@ const SEVERITY_CONFIG = {
4
4
  critical: { color: '\x1b[31m', icon: '🔴', label: 'CRITICAL' },
5
5
  high: { color: '\x1b[33m', icon: '🟠', label: 'HIGH' },
6
6
  medium: { color: '\x1b[33m', icon: '🟡', label: 'MEDIUM' },
7
+ low: { color: '\x1b[36m', icon: '🔵', label: 'LOW' },
7
8
  info: { color: '\x1b[32m', icon: '✅', label: 'OK' },
8
9
  };
9
10
 
@@ -54,7 +55,7 @@ function generateTerminalReport(analysis) {
54
55
  console.log(`${B}${RED}⚠️ Estimated monthly cost exposure: $${bleed.toFixed(2)}/month${R}\n`);
55
56
  }
56
57
 
57
- for (const severity of ['critical', 'high', 'medium', 'info']) {
58
+ for (const severity of ['critical', 'high', 'medium', 'low', 'info']) {
58
59
  const group = findings.filter(f => f.severity === severity);
59
60
  if (!group.length) continue;
60
61
 
@@ -89,7 +90,7 @@ function generateTerminalReport(analysis) {
89
90
 
90
91
  // Summary
91
92
  console.log(`${C}━━━ Summary ━━━${R}`);
92
- console.log(` 🔴 ${RED}${summary.critical}${R} critical 🟠 ${summary.high} high 🟡 ${summary.medium} medium ✅ ${summary.info} ok`);
93
+ console.log(` 🔴 ${RED}${summary.critical}${R} critical 🟠 ${summary.high} high 🟡 ${summary.medium} medium 🔵 ${summary.low||0} low ✅ ${summary.info} ok`);
93
94
  console.log(` Sessions analyzed: ${summary.sessionsAnalyzed} · Tokens found: ${(summary.totalTokensFound||0).toLocaleString()}`);
94
95
  if (bleed > 0) {
95
96
  console.log(` ${RED}${B}Monthly bleed: $${bleed.toFixed(2)}/month${R}`);