@notenkidev/claude-token-dashboard 0.1.9 → 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 +2 -2
- package/components/ProjectTable.tsx +25 -1
- package/lib/collect.ts +30 -2
- package/package.json +3 -3
- package/screenshot.v2-1.png +0 -0
- package/screenshot.v2-2.png +0 -0
- package/screenshot-1.jpg +0 -0
- package/screenshot-2.jpg +0 -0
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))
|
|
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
|
-
|
|
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.
|
|
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"
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
"components.json",
|
|
18
18
|
"eslint.config.mjs",
|
|
19
19
|
"next-env.d.ts",
|
|
20
|
-
"screenshot-1.
|
|
21
|
-
"screenshot-2.
|
|
20
|
+
"screenshot.v2-1.png",
|
|
21
|
+
"screenshot.v2-2.png"
|
|
22
22
|
],
|
|
23
23
|
"scripts": {
|
|
24
24
|
"dev": "next dev",
|
|
Binary file
|
|
Binary file
|
package/screenshot-1.jpg
DELETED
|
Binary file
|
package/screenshot-2.jpg
DELETED
|
Binary file
|