[object Object]

← back to Letsbegin

letsbegin: shared cost-tracker helper + hook 7 Gemini routes

cecf2c34e3830014e60ab14d1c71e3d59dd8cf26 · 2026-05-13 08:40:26 -0700 · Steve

New: lib/cost-track.ts — best-effort logGemini wrapper that fails soft when
~/.claude/skills/cost-tracker isn't present (e.g. Kamatera deploy).

Hooked all 7 Gemini-using API routes via the shared helper:
  /api/url-info, /api/prd/clarify, /api/prd/generate, /api/ralph/convert,
  /api/claude/suggest, /api/processes/analyze, /api/processes/fix

Every Gemini call now logs token usage to ~/.claude/cost-ledger.jsonl
with app='letsbegin' / note=<route-name>.

Files touched

Diff

commit cecf2c34e3830014e60ab14d1c71e3d59dd8cf26
Author: Steve <steve@designerwallcoverings.com>
Date:   Wed May 13 08:40:26 2026 -0700

    letsbegin: shared cost-tracker helper + hook 7 Gemini routes
    
    New: lib/cost-track.ts — best-effort logGemini wrapper that fails soft when
    ~/.claude/skills/cost-tracker isn't present (e.g. Kamatera deploy).
    
    Hooked all 7 Gemini-using API routes via the shared helper:
      /api/url-info, /api/prd/clarify, /api/prd/generate, /api/ralph/convert,
      /api/claude/suggest, /api/processes/analyze, /api/processes/fix
    
    Every Gemini call now logs token usage to ~/.claude/cost-ledger.jsonl
    with app='letsbegin' / note=<route-name>.
---
 app/api/claude/suggest/route.ts    |  2 ++
 app/api/prd/clarify/route.ts       |  2 ++
 app/api/prd/generate/route.ts      |  2 ++
 app/api/processes/analyze/route.ts |  2 ++
 app/api/processes/fix/route.ts     |  2 ++
 app/api/ralph/convert/route.ts     |  2 ++
 app/api/url-info/route.ts          |  8 ++------
 lib/cost-track.ts                  | 16 ++++++++++++++++
 8 files changed, 30 insertions(+), 6 deletions(-)

diff --git a/app/api/claude/suggest/route.ts b/app/api/claude/suggest/route.ts
index 961e9d8..a98f45a 100644
--- a/app/api/claude/suggest/route.ts
+++ b/app/api/claude/suggest/route.ts
@@ -1,4 +1,5 @@
 import { NextRequest, NextResponse } from 'next/server'
+import { logGemini } from '@/lib/cost-track'
 import * as fs from 'fs'
 import * as path from 'path'
 
@@ -25,6 +26,7 @@ async function callGemini(prompt: string, maxTokens: number = 500): Promise<stri
   }
 
   const data = await response.json()
+  logGemini(data, { note: 'claude-suggest' })
   return data.candidates?.[0]?.content?.parts?.[0]?.text || ''
 }
 
diff --git a/app/api/prd/clarify/route.ts b/app/api/prd/clarify/route.ts
index 7ad2a38..63f4c73 100644
--- a/app/api/prd/clarify/route.ts
+++ b/app/api/prd/clarify/route.ts
@@ -1,4 +1,5 @@
 import { NextRequest, NextResponse } from 'next/server'
+import { logGemini } from '@/lib/cost-track'
 
 // Gemini API configuration (FREE - no credits needed!)
 const GEMINI_API_KEY = process.env.GEMINI_API_KEY
@@ -23,6 +24,7 @@ async function callGemini(prompt: string): Promise<string> {
   }
 
   const data = await response.json()
+  logGemini(data, { note: 'prd-clarify' })
   return data.candidates?.[0]?.content?.parts?.[0]?.text || ''
 }
 
diff --git a/app/api/prd/generate/route.ts b/app/api/prd/generate/route.ts
index 0b58b7b..0ea1ea3 100644
--- a/app/api/prd/generate/route.ts
+++ b/app/api/prd/generate/route.ts
@@ -1,4 +1,5 @@
 import { NextRequest, NextResponse } from 'next/server'
+import { logGemini } from '@/lib/cost-track'
 import fs from 'fs'
 import path from 'path'
 import { trackEvent } from '@/lib/sheets'
@@ -27,6 +28,7 @@ async function callGemini(prompt: string): Promise<string> {
   }
 
   const data = await response.json()
+  logGemini(data, { note: 'prd-generate' })
   return data.candidates?.[0]?.content?.parts?.[0]?.text || ''
 }
 
diff --git a/app/api/processes/analyze/route.ts b/app/api/processes/analyze/route.ts
index 84ef32f..4c1a976 100644
--- a/app/api/processes/analyze/route.ts
+++ b/app/api/processes/analyze/route.ts
@@ -1,4 +1,5 @@
 import { NextRequest, NextResponse } from 'next/server'
+import { logGemini } from '@/lib/cost-track'
 import * as fs from 'fs'
 import * as path from 'path'
 
@@ -25,6 +26,7 @@ async function callGemini(prompt: string): Promise<string> {
   }
 
   const data = await response.json()
+  logGemini(data, { note: 'processes-analyze' })
   return data.candidates?.[0]?.content?.parts?.[0]?.text || ''
 }
 
diff --git a/app/api/processes/fix/route.ts b/app/api/processes/fix/route.ts
index 3a42fbc..2e46cad 100644
--- a/app/api/processes/fix/route.ts
+++ b/app/api/processes/fix/route.ts
@@ -1,4 +1,5 @@
 import { NextRequest, NextResponse } from 'next/server'
+import { logGemini } from '@/lib/cost-track'
 import * as fs from 'fs'
 import * as path from 'path'
 import { execSync, spawn } from 'child_process'
@@ -26,6 +27,7 @@ async function callGemini(prompt: string): Promise<string> {
   }
 
   const data = await response.json()
+  logGemini(data, { note: 'processes-fix' })
   return data.candidates?.[0]?.content?.parts?.[0]?.text || ''
 }
 
diff --git a/app/api/ralph/convert/route.ts b/app/api/ralph/convert/route.ts
index 9a2ff2f..0a2c42e 100644
--- a/app/api/ralph/convert/route.ts
+++ b/app/api/ralph/convert/route.ts
@@ -1,4 +1,5 @@
 import { NextRequest, NextResponse } from 'next/server'
+import { logGemini } from '@/lib/cost-track'
 import * as fs from 'fs'
 import * as path from 'path'
 
@@ -25,6 +26,7 @@ async function callGemini(prompt: string): Promise<string> {
   }
 
   const data = await response.json()
+  logGemini(data, { note: 'ralph-convert' })
   return data.candidates?.[0]?.content?.parts?.[0]?.text || ''
 }
 
diff --git a/app/api/url-info/route.ts b/app/api/url-info/route.ts
index 4c81f33..32373b9 100644
--- a/app/api/url-info/route.ts
+++ b/app/api/url-info/route.ts
@@ -1,4 +1,5 @@
 import { NextRequest, NextResponse } from 'next/server'
+import { logGemini } from '@/lib/cost-track'
 
 // Gemini API configuration (FREE - no credits needed!)
 const GEMINI_API_KEY = process.env.GEMINI_API_KEY
@@ -23,12 +24,7 @@ async function callGemini(prompt: string): Promise<string> {
   }
 
   const data = await response.json()
-  // Cost-tracker hook (best-effort; fail-soft in Next.js runtime).
-  try {
-    // eslint-disable-next-line @typescript-eslint/no-require-imports
-    const { logGemini } = require(require('os').homedir() + '/.claude/skills/cost-tracker/scripts/log-gemini.js')
-    logGemini(data, { app: 'letsbegin', note: 'url-info', model: 'gemini-2.0-flash' })
-  } catch {}
+  logGemini(data, { note: 'url-info' })
   return data.candidates?.[0]?.content?.parts?.[0]?.text || ''
 }
 
diff --git a/lib/cost-track.ts b/lib/cost-track.ts
new file mode 100644
index 0000000..28378f0
--- /dev/null
+++ b/lib/cost-track.ts
@@ -0,0 +1,16 @@
+// Best-effort cost-tracker wrapper for Letsbegin Gemini calls.
+// Fails silently in Edge runtime / when the cost-tracker module isn't present
+// (e.g. on Kamatera deploy where ~/.claude/ doesn't exist).
+import os from 'os'
+
+type GeminiResponseLike = { usageMetadata?: { promptTokenCount?: number; candidatesTokenCount?: number }, modelVersion?: string }
+
+export function logGemini(response: GeminiResponseLike, opts: { note?: string; model?: string } = {}) {
+  try {
+    // eslint-disable-next-line @typescript-eslint/no-require-imports
+    const { logGemini: hook } = require(os.homedir() + '/.claude/skills/cost-tracker/scripts/log-gemini.js')
+    hook(response, { app: 'letsbegin', note: opts.note || '', model: opts.model || 'gemini-2.0-flash' })
+  } catch {
+    // fail-soft — cost-tracker is optional
+  }
+}

← 7a38d32 url-info: hook Gemini call into cost-tracker (best-effort)  ·  back to Letsbegin  ·  security: strip hardcoded dw_admin DSN password -> env-first 55eb229 →