@notenkidev/claude-token-dashboard 0.1.6 → 0.1.9

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/README.md CHANGED
@@ -2,14 +2,15 @@
2
2
 
3
3
  > Visualize your [Claude Code](https://claude.ai/code) token usage by project and date — built with Next.js + Tailwind.
4
4
 
5
- ![Summary cards and daily chart](screenshot-1.jpg)
5
+ ![Summary cards and daily chart](screenshot.v2-1.png)
6
6
 
7
- ![Project breakdown table](screenshot-2.jpg)
7
+ ![Project breakdown table](screenshot.v2-2.png)
8
8
 
9
9
  ## Quick Start (No Install)
10
10
 
11
11
  ```bash
12
- npx @notenkidev/claude-token-dashboard
12
+ npm install -g @notenkidev/claude-token-dashboard
13
+ claude-token-dashboard
13
14
  ```
14
15
 
15
16
  Open http://localhost:3000
@@ -18,26 +19,34 @@ Open http://localhost:3000
18
19
 
19
20
  | Section | Details |
20
21
  |---|---|
21
- | **Summary cards** | Output tokens · Input tokens · Effective input · Cache read ratio · Project count · Session count |
22
+ | **Summary cards** | Estimated total cost · Output tokens · Input tokens · Effective input · Cache read ratio · Session count |
22
23
  | **Daily chart** | Input / output tokens per day (bar chart) |
23
- | **Project table** | Per-project breakdown sorted by total usage, with inline bar |
24
+ | **Project table** | Per-project breakdown sorted by cost, with inline bar |
24
25
 
25
26
  Cache read tokens from prompt caching are tracked separately — you can see at a glance how much of your effective input Claude is serving from cache (typically 90%+).
26
27
 
28
+ ## Features
29
+
30
+ - Token usage per project (output / input / cache read / cache create)
31
+ - Daily usage chart
32
+ - Cache read ratio
33
+ - Estimated cost in USD per project (claude-sonnet-4-6 pricing)
34
+ - Cost share % with warning for projects over 20% of total spend
35
+ - Total estimated cost since first session
36
+
27
37
  ## Quick start
28
38
 
29
39
  ```bash
30
- npx @notenkidev/claude-token-dashboard
40
+ npm install -g @notenkidev/claude-token-dashboard
41
+ claude-token-dashboard
31
42
  ```
32
43
 
33
44
  Open [http://localhost:3000](http://localhost:3000).
34
45
 
35
- On first run, dependencies are installed automatically (~30s). After that it starts instantly.
36
-
37
46
  **Custom port:**
38
47
 
39
48
  ```bash
40
- npx @notenkidev/claude-token-dashboard -p 4000
49
+ claude-token-dashboard -p 4000
41
50
  ```
42
51
 
43
52
  Reads `~/.claude/projects/**/*.jsonl` directly — no config, no API key needed.
@@ -1,4 +1,5 @@
1
1
  import type { TokenStats } from "@/lib/collect"
2
+ import { calcCost, fmtCost } from "@/lib/pricing"
2
3
 
3
4
  interface Props {
4
5
  byProject: Record<string, TokenStats>
@@ -12,10 +13,17 @@ function fmt(n: number): string {
12
13
 
13
14
  export default function ProjectTable({ byProject }: Props) {
14
15
  const rows = Object.entries(byProject)
15
- .map(([project, stats]) => ({ project, ...stats, total: stats.input + stats.output }))
16
- .sort((a, b) => b.total - a.total)
16
+ .map(([project, stats]) => ({
17
+ project,
18
+ ...stats,
19
+ total: stats.input + stats.output,
20
+ cost: calcCost(stats),
21
+ }))
22
+ .sort((a, b) => b.cost - a.cost)
17
23
 
18
- const maxTotal = rows[0]?.total ?? 1
24
+ const maxTotal = Math.max(...rows.map((r) => r.total), 1)
25
+ const totalCost = rows.reduce((s, r) => s + r.cost, 0)
26
+ const WARNING_THRESHOLD = 0.20
19
27
 
20
28
  return (
21
29
  <div className="overflow-x-auto rounded-xl border border-border">
@@ -23,24 +31,47 @@ export default function ProjectTable({ byProject }: Props) {
23
31
  <thead>
24
32
  <tr className="border-b border-border bg-muted/40">
25
33
  <th className="px-4 py-3 text-left font-medium text-muted-foreground">Project</th>
34
+ <th className="px-4 py-3 text-right font-medium text-muted-foreground">Cost</th>
26
35
  <th className="px-4 py-3 text-right font-medium text-muted-foreground">Output</th>
27
36
  <th className="px-4 py-3 text-right font-medium text-muted-foreground">Input</th>
28
37
  <th className="px-4 py-3 text-right font-medium text-muted-foreground hidden md:table-cell">Cache Read</th>
29
38
  <th className="px-4 py-3 text-right font-medium text-muted-foreground hidden lg:table-cell">Cache Create</th>
30
- <th className="px-4 py-3 text-right font-medium text-muted-foreground">Total</th>
31
- <th className="px-4 py-3 w-32 hidden sm:table-cell"></th>
39
+ <th className="px-4 py-3 text-right font-medium text-muted-foreground">Tokens</th>
40
+ <th className="px-4 py-3 w-28 hidden sm:table-cell"></th>
32
41
  </tr>
33
42
  </thead>
34
43
  <tbody>
35
44
  {rows.map((row, i) => {
36
45
  const pct = (row.total / maxTotal) * 100
46
+ const costShare = totalCost > 0 ? row.cost / totalCost : 0
47
+ const isHighCost = costShare >= WARNING_THRESHOLD
48
+
37
49
  return (
38
50
  <tr
39
51
  key={row.project}
40
- className={`border-b border-border last:border-0 hover:bg-muted/20 transition-colors ${i === 0 ? "bg-muted/10" : ""}`}
52
+ className={`border-b border-border last:border-0 hover:bg-muted/20 transition-colors ${
53
+ isHighCost ? "bg-amber-950/10" : i === 0 ? "bg-muted/10" : ""
54
+ }`}
41
55
  >
42
- <td className="px-4 py-3 font-mono text-xs max-w-[200px] truncate" title={row.project}>
43
- {row.project}
56
+ <td className="px-4 py-3 font-mono text-xs max-w-[180px]">
57
+ <div className="flex items-center gap-1.5 truncate" title={row.project}>
58
+ {isHighCost && (
59
+ <span className="shrink-0 text-amber-400" title={`${(costShare * 100).toFixed(1)}% of total cost`}>
60
+
61
+ </span>
62
+ )}
63
+ <span className="truncate">{row.project}</span>
64
+ </div>
65
+ </td>
66
+ <td className="px-4 py-3 text-right font-mono text-xs">
67
+ <span className={isHighCost ? "text-amber-400 font-semibold" : "text-emerald-400"}>
68
+ {fmtCost(row.cost)}
69
+ </span>
70
+ {totalCost > 0 && (
71
+ <span className="ml-1 text-muted-foreground text-[10px]">
72
+ {(costShare * 100).toFixed(1)}%
73
+ </span>
74
+ )}
44
75
  </td>
45
76
  <td className="px-4 py-3 text-right font-mono text-xs text-violet-400">{fmt(row.output)}</td>
46
77
  <td className="px-4 py-3 text-right font-mono text-xs text-blue-400">{fmt(row.input)}</td>
@@ -50,11 +81,11 @@ export default function ProjectTable({ byProject }: Props) {
50
81
  <td className="px-4 py-3 text-right font-mono text-xs text-muted-foreground hidden lg:table-cell">
51
82
  {fmt(row.cacheCreate)}
52
83
  </td>
53
- <td className="px-4 py-3 text-right font-mono text-xs font-semibold">{fmt(row.total)}</td>
84
+ <td className="px-4 py-3 text-right font-mono text-xs text-muted-foreground">{fmt(row.total)}</td>
54
85
  <td className="px-4 py-3 hidden sm:table-cell">
55
86
  <div className="h-1.5 w-full rounded-full bg-muted overflow-hidden">
56
87
  <div
57
- className="h-full rounded-full bg-violet-500/70"
88
+ className={`h-full rounded-full ${isHighCost ? "bg-amber-500/70" : "bg-violet-500/70"}`}
58
89
  style={{ width: `${pct}%` }}
59
90
  />
60
91
  </div>
@@ -63,6 +94,15 @@ export default function ProjectTable({ byProject }: Props) {
63
94
  )
64
95
  })}
65
96
  </tbody>
97
+ <tfoot>
98
+ <tr className="border-t border-border bg-muted/20">
99
+ <td className="px-4 py-2 text-xs font-medium text-muted-foreground">TOTAL</td>
100
+ <td className="px-4 py-2 text-right font-mono text-xs font-semibold text-emerald-400">
101
+ {fmtCost(totalCost)}
102
+ </td>
103
+ <td colSpan={6} />
104
+ </tr>
105
+ </tfoot>
66
106
  </table>
67
107
  </div>
68
108
  )
@@ -1,5 +1,6 @@
1
1
  import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"
2
2
  import type { TokenStats } from "@/lib/collect"
3
+ import { calcCost, fmtCost } from "@/lib/pricing"
3
4
 
4
5
  interface Props {
5
6
  byProject: Record<string, TokenStats>
@@ -25,24 +26,33 @@ export default function SummaryCards({ byProject, totalFiles, totalEntries, skip
25
26
  const effectiveInput = totalInput + totalCacheRead + totalCacheCreate
26
27
  const cacheRatio = effectiveInput > 0 ? (totalCacheRead / effectiveInput) * 100 : 0
27
28
 
29
+ const totalCost = allStats.reduce((s, v) => s + calcCost(v), 0)
30
+
28
31
  const cards = [
32
+ {
33
+ title: "Estimated Cost",
34
+ value: fmtCost(totalCost),
35
+ sub: "claude-sonnet-4-6",
36
+ highlight: true,
37
+ },
29
38
  { title: "Output Tokens", value: fmtBig(totalOutput), sub: "generated" },
30
39
  { title: "Input Tokens", value: fmtBig(totalInput), sub: "sent" },
31
40
  { title: "Effective Input", value: fmtBig(effectiveInput), sub: "input + cache" },
32
41
  { title: "Cache Read Ratio", value: `${cacheRatio.toFixed(1)}%`, sub: "of effective input" },
33
- { title: "Projects", value: String(Object.keys(byProject).length), sub: "unique" },
34
42
  { title: "Sessions", value: fmtBig(totalFiles), sub: `${fmtBig(totalEntries)} entries · ${fmtBig(skippedDup)} deduped` },
35
43
  ]
36
44
 
37
45
  return (
38
46
  <div className="grid grid-cols-2 gap-4 sm:grid-cols-3 lg:grid-cols-6">
39
47
  {cards.map((c) => (
40
- <Card key={c.title}>
48
+ <Card key={c.title} className={c.highlight ? "border-emerald-500/40 bg-emerald-950/20" : ""}>
41
49
  <CardHeader className="pb-2">
42
50
  <CardTitle>{c.title}</CardTitle>
43
51
  </CardHeader>
44
52
  <CardContent>
45
- <p className="text-2xl font-bold font-mono tracking-tight">{c.value}</p>
53
+ <p className={`text-2xl font-bold font-mono tracking-tight ${c.highlight ? "text-emerald-400" : ""}`}>
54
+ {c.value}
55
+ </p>
46
56
  <p className="mt-1 text-xs text-muted-foreground">{c.sub}</p>
47
57
  </CardContent>
48
58
  </Card>
package/lib/pricing.ts ADDED
@@ -0,0 +1,26 @@
1
+ import type { TokenStats } from "./collect"
2
+
3
+ // claude-sonnet-4-6 pricing (USD per token)
4
+ export const PRICING = {
5
+ input: 3.00 / 1_000_000,
6
+ output: 15.00 / 1_000_000,
7
+ cacheRead: 0.30 / 1_000_000,
8
+ cacheCreate: 3.75 / 1_000_000,
9
+ } as const
10
+
11
+ export function calcCost(stats: TokenStats): number {
12
+ return (
13
+ stats.input * PRICING.input +
14
+ stats.output * PRICING.output +
15
+ stats.cacheRead * PRICING.cacheRead +
16
+ stats.cacheCreate * PRICING.cacheCreate
17
+ )
18
+ }
19
+
20
+ export function fmtCost(usd: number): string {
21
+ if (usd >= 1000) return `$${(usd / 1000).toFixed(1)}K`
22
+ if (usd >= 100) return `$${usd.toFixed(0)}`
23
+ if (usd >= 1) return `$${usd.toFixed(2)}`
24
+ if (usd >= 0.01) return `$${usd.toFixed(3)}`
25
+ return "<$0.01"
26
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@notenkidev/claude-token-dashboard",
3
- "version": "0.1.6",
3
+ "version": "0.1.9",
4
4
  "description": "Visualize your Claude Code token usage by project and date",
5
5
  "bin": {
6
6
  "claude-token-dashboard": "bin/cli.js"