@notenkidev/claude-token-dashboard 0.1.6 → 0.1.8

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.
@@ -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.8",
4
4
  "description": "Visualize your Claude Code token usage by project and date",
5
5
  "bin": {
6
6
  "claude-token-dashboard": "bin/cli.js"