@notenkidev/claude-token-dashboard 0.1.10 → 0.1.11

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/app/page.tsx CHANGED
@@ -8,7 +8,7 @@ import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"
8
8
  export const dynamic = "force-dynamic"
9
9
 
10
10
  export default function Page() {
11
- const { byProject, byDay, totalFiles, totalEntries, skippedDup } = collect()
11
+ const { byProject, byDay, byProjectClaudeMd, totalFiles, totalEntries, skippedDup } = collect()
12
12
 
13
13
  const dayData = Object.entries(byDay)
14
14
  .filter(([date]) => date !== "unknown")
@@ -66,7 +66,7 @@ export default function Page() {
66
66
  </CardTitle>
67
67
  </CardHeader>
68
68
  <CardContent className="p-0">
69
- <ProjectTable byProject={byProject} />
69
+ <ProjectTable byProject={byProject} byProjectClaudeMd={byProjectClaudeMd} />
70
70
  </CardContent>
71
71
  </Card>
72
72
  </main>
@@ -1,8 +1,11 @@
1
1
  import type { TokenStats } from "@/lib/collect"
2
2
  import { calcCost, fmtCost } from "@/lib/pricing"
3
3
 
4
+ const CLAUDE_MD_WARN_BYTES = 5 * 1024 // 5 KB
5
+
4
6
  interface Props {
5
7
  byProject: Record<string, TokenStats>
8
+ byProjectClaudeMd: Record<string, number | null>
6
9
  }
7
10
 
8
11
  function fmt(n: number): string {
@@ -11,13 +14,14 @@ function fmt(n: number): string {
11
14
  return n.toLocaleString()
12
15
  }
13
16
 
14
- export default function ProjectTable({ byProject }: Props) {
17
+ export default function ProjectTable({ byProject, byProjectClaudeMd }: Props) {
15
18
  const rows = Object.entries(byProject)
16
19
  .map(([project, stats]) => ({
17
20
  project,
18
21
  ...stats,
19
22
  total: stats.input + stats.output,
20
23
  cost: calcCost(stats),
24
+ claudeMdBytes: byProjectClaudeMd[project] ?? null,
21
25
  }))
22
26
  .sort((a, b) => b.cost - a.cost)
23
27
 
@@ -36,6 +40,7 @@ export default function ProjectTable({ byProject }: Props) {
36
40
  <th className="px-4 py-3 text-right font-medium text-muted-foreground">Input</th>
37
41
  <th className="px-4 py-3 text-right font-medium text-muted-foreground hidden md:table-cell">Cache Read</th>
38
42
  <th className="px-4 py-3 text-right font-medium text-muted-foreground hidden lg:table-cell">Cache Create</th>
43
+ <th className="px-4 py-3 text-right font-medium text-muted-foreground hidden xl:table-cell">CLAUDE.md</th>
39
44
  <th className="px-4 py-3 text-right font-medium text-muted-foreground">Tokens</th>
40
45
  <th className="px-4 py-3 w-28 hidden sm:table-cell"></th>
41
46
  </tr>
@@ -45,6 +50,11 @@ export default function ProjectTable({ byProject }: Props) {
45
50
  const pct = (row.total / maxTotal) * 100
46
51
  const costShare = totalCost > 0 ? row.cost / totalCost : 0
47
52
  const isHighCost = costShare >= WARNING_THRESHOLD
53
+ const isHeavyClaudeMd = row.claudeMdBytes !== null && row.claudeMdBytes >= CLAUDE_MD_WARN_BYTES
54
+ const claudeMdLabel =
55
+ row.claudeMdBytes === null
56
+ ? "-"
57
+ : `${(row.claudeMdBytes / 1024).toFixed(1)} KB`
48
58
 
49
59
  return (
50
60
  <tr
@@ -81,6 +91,20 @@ export default function ProjectTable({ byProject }: Props) {
81
91
  <td className="px-4 py-3 text-right font-mono text-xs text-muted-foreground hidden lg:table-cell">
82
92
  {fmt(row.cacheCreate)}
83
93
  </td>
94
+ <td className="px-4 py-3 text-right font-mono text-xs hidden xl:table-cell">
95
+ {isHeavyClaudeMd ? (
96
+ <span
97
+ className="text-orange-400 font-semibold"
98
+ title="CLAUDE.mdが重い可能性があります(5KB以上)"
99
+ >
100
+ ⚠ {claudeMdLabel}
101
+ </span>
102
+ ) : (
103
+ <span className={row.claudeMdBytes !== null ? "text-muted-foreground" : "text-muted-foreground/40"}>
104
+ {claudeMdLabel}
105
+ </span>
106
+ )}
107
+ </td>
84
108
  <td className="px-4 py-3 text-right font-mono text-xs text-muted-foreground">{fmt(row.total)}</td>
85
109
  <td className="px-4 py-3 hidden sm:table-cell">
86
110
  <div className="h-1.5 w-full rounded-full bg-muted overflow-hidden">
package/lib/collect.ts CHANGED
@@ -12,6 +12,7 @@ export interface TokenStats {
12
12
  export interface DashboardData {
13
13
  byProject: Record<string, TokenStats>
14
14
  byDay: Record<string, TokenStats>
15
+ byProjectClaudeMd: Record<string, number | null> // bytes, null = not found
15
16
  totalFiles: number
16
17
  totalEntries: number
17
18
  skippedDup: number
@@ -50,15 +51,34 @@ function addStats(target: TokenStats, input: number, output: number, cacheCreate
50
51
  target.cacheRead += cacheRead
51
52
  }
52
53
 
54
+ function claudeMdBytes(cwd: string): number | null {
55
+ // Walk up from cwd to find CLAUDE.md, stopping at home directory
56
+ const home = os.homedir()
57
+ let dir = cwd
58
+ while (dir.startsWith(home) && dir !== home) {
59
+ try {
60
+ const stat = fs.statSync(path.join(dir, "CLAUDE.md"))
61
+ if (stat.isFile()) return stat.size
62
+ } catch { /* not found at this level */ }
63
+ const parent = path.dirname(dir)
64
+ if (parent === dir) break
65
+ dir = parent
66
+ }
67
+ return null
68
+ }
69
+
53
70
  export function collect(): DashboardData {
54
71
  const byProject: Record<string, TokenStats> = {}
55
72
  const byDay: Record<string, TokenStats> = {}
73
+ const cwdByLabel: Record<string, string> = {} // label → first seen cwd
56
74
  const seenMessageIds = new Set<string>()
57
75
  let totalFiles = 0
58
76
  let totalEntries = 0
59
77
  let skippedDup = 0
60
78
 
61
- if (!fs.existsSync(BASE)) return { byProject, byDay, totalFiles, totalEntries, skippedDup }
79
+ if (!fs.existsSync(BASE)) {
80
+ return { byProject, byDay, byProjectClaudeMd: {}, totalFiles, totalEntries, skippedDup }
81
+ }
62
82
 
63
83
  const projectDirs = fs.readdirSync(BASE).sort()
64
84
 
@@ -101,6 +121,7 @@ export function collect(): DashboardData {
101
121
 
102
122
  const cwd = (d.cwd as string) || ""
103
123
  const label = cwd ? projectLabelFromCwd(cwd) : fallbackLabel
124
+ if (cwd && !cwdByLabel[label]) cwdByLabel[label] = cwd
104
125
 
105
126
  const ts = (d.timestamp as string) || ""
106
127
  let date = "unknown"
@@ -121,5 +142,12 @@ export function collect(): DashboardData {
121
142
  }
122
143
  }
123
144
 
124
- return { byProject, byDay, totalFiles, totalEntries, skippedDup }
145
+ // Resolve CLAUDE.md sizes after all entries are processed
146
+ const byProjectClaudeMd: Record<string, number | null> = {}
147
+ for (const label of Object.keys(byProject)) {
148
+ const cwd = cwdByLabel[label]
149
+ byProjectClaudeMd[label] = cwd ? claudeMdBytes(cwd) : null
150
+ }
151
+
152
+ return { byProject, byDay, byProjectClaudeMd, totalFiles, totalEntries, skippedDup }
125
153
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@notenkidev/claude-token-dashboard",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
4
4
  "description": "Visualize your Claude Code token usage by project and date",
5
5
  "bin": {
6
6
  "claude-token-dashboard": "bin/cli.js"