agentfit 0.1.0
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/.claude/settings.local.json +26 -0
- package/.prettierignore +7 -0
- package/.prettierrc +11 -0
- package/CONTRIBUTING.md +209 -0
- package/LICENSE +21 -0
- package/README.md +109 -0
- package/app/(dashboard)/coach/page.tsx +11 -0
- package/app/(dashboard)/commands/page.tsx +7 -0
- package/app/(dashboard)/community/[slug]/page.tsx +23 -0
- package/app/(dashboard)/community/page.tsx +71 -0
- package/app/(dashboard)/daily/page.tsx +19 -0
- package/app/(dashboard)/images/page.tsx +5 -0
- package/app/(dashboard)/layout.tsx +12 -0
- package/app/(dashboard)/page.tsx +23 -0
- package/app/(dashboard)/personality/page.tsx +11 -0
- package/app/(dashboard)/projects/page.tsx +11 -0
- package/app/(dashboard)/sessions/page.tsx +11 -0
- package/app/(dashboard)/tokens/page.tsx +11 -0
- package/app/(dashboard)/tools/page.tsx +11 -0
- package/app/api/check/route.ts +13 -0
- package/app/api/commands/route.ts +16 -0
- package/app/api/images/[...path]/route.ts +33 -0
- package/app/api/images-analysis/route.ts +177 -0
- package/app/api/sync/route.ts +14 -0
- package/app/api/usage/route.ts +117 -0
- package/app/favicon.ico +0 -0
- package/app/globals.css +144 -0
- package/app/icon.svg +3 -0
- package/app/layout.tsx +35 -0
- package/bin/agentfit.mjs +69 -0
- package/components/.gitkeep +0 -0
- package/components/agent-coach.tsx +248 -0
- package/components/app-sidebar.tsx +161 -0
- package/components/command-usage.tsx +294 -0
- package/components/daily-chart.tsx +118 -0
- package/components/daily-table.tsx +115 -0
- package/components/dashboard-shell.tsx +149 -0
- package/components/data-provider.tsx +213 -0
- package/components/fitness-score.tsx +95 -0
- package/components/overview-cards.tsx +198 -0
- package/components/pagination-controls.tsx +104 -0
- package/components/personality-fit.tsx +446 -0
- package/components/projects-table.tsx +70 -0
- package/components/screenshots-analysis.tsx +359 -0
- package/components/sessions-table.tsx +97 -0
- package/components/theme-provider.tsx +71 -0
- package/components/token-breakdown.tsx +179 -0
- package/components/tool-usage-chart.tsx +63 -0
- package/components/ui/badge.tsx +52 -0
- package/components/ui/button.tsx +60 -0
- package/components/ui/card.tsx +103 -0
- package/components/ui/chart.tsx +373 -0
- package/components/ui/dialog.tsx +160 -0
- package/components/ui/input.tsx +20 -0
- package/components/ui/scroll-area.tsx +55 -0
- package/components/ui/select.tsx +201 -0
- package/components/ui/separator.tsx +25 -0
- package/components/ui/sheet.tsx +138 -0
- package/components/ui/sidebar.tsx +723 -0
- package/components/ui/skeleton.tsx +13 -0
- package/components/ui/table.tsx +116 -0
- package/components/ui/tabs.tsx +82 -0
- package/components/ui/tooltip.tsx +66 -0
- package/components.json +25 -0
- package/generated/prisma/browser.ts +34 -0
- package/generated/prisma/client.ts +58 -0
- package/generated/prisma/commonInputTypes.ts +237 -0
- package/generated/prisma/enums.ts +15 -0
- package/generated/prisma/internal/class.ts +224 -0
- package/generated/prisma/internal/prismaNamespace.ts +920 -0
- package/generated/prisma/internal/prismaNamespaceBrowser.ts +130 -0
- package/generated/prisma/models/Image.ts +1310 -0
- package/generated/prisma/models/Session.ts +1695 -0
- package/generated/prisma/models/SyncLog.ts +1203 -0
- package/generated/prisma/models.ts +14 -0
- package/hooks/.gitkeep +0 -0
- package/hooks/use-mobile.ts +19 -0
- package/hooks/use-pagination.ts +60 -0
- package/lib/.gitkeep +0 -0
- package/lib/coach.ts +425 -0
- package/lib/commands.ts +239 -0
- package/lib/db.ts +15 -0
- package/lib/format.ts +26 -0
- package/lib/parse-codex.ts +201 -0
- package/lib/parse-logs.ts +369 -0
- package/lib/personality.ts +481 -0
- package/lib/plugins.ts +107 -0
- package/lib/pricing.ts +112 -0
- package/lib/queries-codex.ts +130 -0
- package/lib/queries.ts +154 -0
- package/lib/resolve-icon.ts +12 -0
- package/lib/sync.ts +335 -0
- package/lib/utils.ts +6 -0
- package/next.config.mjs +4 -0
- package/package.json +73 -0
- package/plugins/cost-heatmap/component.test.tsx +52 -0
- package/plugins/cost-heatmap/component.tsx +227 -0
- package/plugins/cost-heatmap/manifest.ts +13 -0
- package/plugins/index.ts +18 -0
- package/prisma/migrations/20260328152517_init/migration.sql +41 -0
- package/prisma/migrations/20260328153801_add_image_model/migration.sql +18 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +57 -0
- package/prisma.config.ts +14 -0
- package/public/.gitkeep +0 -0
- package/public/logo.svg +3 -0
- package/setup.sh +73 -0
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import os from 'os'
|
|
4
|
+
import { loadPricing, calculateCost, type ModelPricing } from './pricing'
|
|
5
|
+
|
|
6
|
+
const CLAUDE_DIR = path.join(os.homedir(), '.claude')
|
|
7
|
+
const PROJECTS_DIR = path.join(CLAUDE_DIR, 'projects')
|
|
8
|
+
|
|
9
|
+
// ─── Types ───────────────────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
export interface SessionSummary {
|
|
12
|
+
sessionId: string
|
|
13
|
+
project: string
|
|
14
|
+
projectPath: string // original path decoded from dir name
|
|
15
|
+
startTime: string
|
|
16
|
+
endTime: string
|
|
17
|
+
durationMinutes: number
|
|
18
|
+
userMessages: number
|
|
19
|
+
assistantMessages: number
|
|
20
|
+
totalMessages: number
|
|
21
|
+
inputTokens: number
|
|
22
|
+
outputTokens: number
|
|
23
|
+
cacheCreationTokens: number
|
|
24
|
+
cacheReadTokens: number
|
|
25
|
+
totalTokens: number
|
|
26
|
+
costUSD: number
|
|
27
|
+
model: string
|
|
28
|
+
toolCalls: Record<string, number>
|
|
29
|
+
toolCallsTotal: number
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface ProjectSummary {
|
|
33
|
+
name: string
|
|
34
|
+
path: string
|
|
35
|
+
sessions: number
|
|
36
|
+
totalMessages: number
|
|
37
|
+
totalTokens: number
|
|
38
|
+
totalCost: number
|
|
39
|
+
totalDurationMinutes: number
|
|
40
|
+
toolCalls: Record<string, number>
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface DailyUsage {
|
|
44
|
+
date: string
|
|
45
|
+
sessions: number
|
|
46
|
+
messages: number
|
|
47
|
+
inputTokens: number
|
|
48
|
+
outputTokens: number
|
|
49
|
+
cacheCreationTokens: number
|
|
50
|
+
cacheReadTokens: number
|
|
51
|
+
totalTokens: number
|
|
52
|
+
costUSD: number
|
|
53
|
+
toolCalls: number
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface OverviewStats {
|
|
57
|
+
totalSessions: number
|
|
58
|
+
totalProjects: number
|
|
59
|
+
totalMessages: number
|
|
60
|
+
totalUserMessages: number
|
|
61
|
+
totalAssistantMessages: number
|
|
62
|
+
totalInputTokens: number
|
|
63
|
+
totalOutputTokens: number
|
|
64
|
+
totalCacheCreationTokens: number
|
|
65
|
+
totalCacheReadTokens: number
|
|
66
|
+
totalTokens: number
|
|
67
|
+
totalCostUSD: number
|
|
68
|
+
totalDurationMinutes: number
|
|
69
|
+
totalToolCalls: number
|
|
70
|
+
models: Record<string, number>
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface UsageData {
|
|
74
|
+
overview: OverviewStats
|
|
75
|
+
sessions: SessionSummary[]
|
|
76
|
+
projects: ProjectSummary[]
|
|
77
|
+
daily: DailyUsage[]
|
|
78
|
+
toolUsage: Record<string, number>
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ─── Parsing ─────────────────────────────────────────────────────────
|
|
82
|
+
|
|
83
|
+
function decodeProjectPath(dirName: string): string {
|
|
84
|
+
return dirName.replace(/-/g, '/')
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function getProjectName(projectPath: string): string {
|
|
88
|
+
const parts = projectPath.split('/')
|
|
89
|
+
return parts[parts.length - 1] || parts[parts.length - 2] || projectPath
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
interface LogEntry {
|
|
93
|
+
type?: string
|
|
94
|
+
message?: {
|
|
95
|
+
role?: string
|
|
96
|
+
content?: unknown[]
|
|
97
|
+
model?: string
|
|
98
|
+
usage?: {
|
|
99
|
+
input_tokens?: number
|
|
100
|
+
output_tokens?: number
|
|
101
|
+
cache_creation_input_tokens?: number
|
|
102
|
+
cache_read_input_tokens?: number
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
timestamp?: string
|
|
106
|
+
uuid?: string
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function parseSessionFile(
|
|
110
|
+
filePath: string,
|
|
111
|
+
allPricing: Record<string, ModelPricing>
|
|
112
|
+
): SessionSummary | null {
|
|
113
|
+
try {
|
|
114
|
+
const content = fs.readFileSync(filePath, 'utf-8')
|
|
115
|
+
const lines = content.trim().split('\n')
|
|
116
|
+
|
|
117
|
+
let userMessages = 0
|
|
118
|
+
let assistantMessages = 0
|
|
119
|
+
let inputTokens = 0
|
|
120
|
+
let outputTokens = 0
|
|
121
|
+
let cacheCreationTokens = 0
|
|
122
|
+
let cacheReadTokens = 0
|
|
123
|
+
let costUSD = 0
|
|
124
|
+
let model = ''
|
|
125
|
+
let startTime = ''
|
|
126
|
+
let endTime = ''
|
|
127
|
+
const toolCalls: Record<string, number> = {}
|
|
128
|
+
|
|
129
|
+
for (const line of lines) {
|
|
130
|
+
if (!line.trim()) continue
|
|
131
|
+
let entry: LogEntry
|
|
132
|
+
try {
|
|
133
|
+
entry = JSON.parse(line)
|
|
134
|
+
} catch {
|
|
135
|
+
continue
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Track timestamps
|
|
139
|
+
if (entry.timestamp) {
|
|
140
|
+
if (!startTime) startTime = entry.timestamp
|
|
141
|
+
endTime = entry.timestamp
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (entry.type === 'user') {
|
|
145
|
+
userMessages++
|
|
146
|
+
} else if (entry.type === 'assistant' && entry.message) {
|
|
147
|
+
assistantMessages++
|
|
148
|
+
const msg = entry.message
|
|
149
|
+
|
|
150
|
+
// Model
|
|
151
|
+
if (msg.model && msg.model !== '<synthetic>') {
|
|
152
|
+
model = msg.model
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Usage
|
|
156
|
+
if (msg.usage) {
|
|
157
|
+
const u = msg.usage
|
|
158
|
+
inputTokens += u.input_tokens || 0
|
|
159
|
+
outputTokens += u.output_tokens || 0
|
|
160
|
+
cacheCreationTokens += u.cache_creation_input_tokens || 0
|
|
161
|
+
cacheReadTokens += u.cache_read_input_tokens || 0
|
|
162
|
+
|
|
163
|
+
if (model) {
|
|
164
|
+
costUSD += calculateCost(model, u, allPricing)
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Tool calls
|
|
169
|
+
if (Array.isArray(msg.content)) {
|
|
170
|
+
for (const block of msg.content) {
|
|
171
|
+
if (
|
|
172
|
+
block &&
|
|
173
|
+
typeof block === 'object' &&
|
|
174
|
+
'type' in block &&
|
|
175
|
+
(block as Record<string, unknown>).type === 'tool_use'
|
|
176
|
+
) {
|
|
177
|
+
const toolName = (block as Record<string, unknown>).name as string
|
|
178
|
+
if (toolName) {
|
|
179
|
+
toolCalls[toolName] = (toolCalls[toolName] || 0) + 1
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (userMessages === 0 && assistantMessages === 0) return null
|
|
188
|
+
|
|
189
|
+
const durationMinutes =
|
|
190
|
+
startTime && endTime
|
|
191
|
+
? (new Date(endTime).getTime() - new Date(startTime).getTime()) / 60000
|
|
192
|
+
: 0
|
|
193
|
+
|
|
194
|
+
const sessionId = path.basename(filePath, '.jsonl')
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
sessionId,
|
|
198
|
+
project: '',
|
|
199
|
+
projectPath: '',
|
|
200
|
+
startTime,
|
|
201
|
+
endTime,
|
|
202
|
+
durationMinutes: Math.max(0, durationMinutes),
|
|
203
|
+
userMessages,
|
|
204
|
+
assistantMessages,
|
|
205
|
+
totalMessages: userMessages + assistantMessages,
|
|
206
|
+
inputTokens,
|
|
207
|
+
outputTokens,
|
|
208
|
+
cacheCreationTokens,
|
|
209
|
+
cacheReadTokens,
|
|
210
|
+
totalTokens: inputTokens + outputTokens + cacheCreationTokens + cacheReadTokens,
|
|
211
|
+
costUSD,
|
|
212
|
+
model: model || 'unknown',
|
|
213
|
+
toolCalls,
|
|
214
|
+
toolCallsTotal: Object.values(toolCalls).reduce((a, b) => a + b, 0),
|
|
215
|
+
}
|
|
216
|
+
} catch {
|
|
217
|
+
return null
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export async function parseAllLogs(): Promise<UsageData> {
|
|
222
|
+
const allPricing = await loadPricing()
|
|
223
|
+
|
|
224
|
+
if (!fs.existsSync(PROJECTS_DIR)) {
|
|
225
|
+
return emptyUsageData()
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const projectDirs = fs.readdirSync(PROJECTS_DIR).filter((d) => {
|
|
229
|
+
return fs.statSync(path.join(PROJECTS_DIR, d)).isDirectory()
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
const sessions: SessionSummary[] = []
|
|
233
|
+
const projectMap = new Map<string, ProjectSummary>()
|
|
234
|
+
const dailyMap = new Map<string, DailyUsage>()
|
|
235
|
+
const toolUsage: Record<string, number> = {}
|
|
236
|
+
|
|
237
|
+
for (const dir of projectDirs) {
|
|
238
|
+
const projectPath = decodeProjectPath(dir)
|
|
239
|
+
const projectName = getProjectName(projectPath)
|
|
240
|
+
const dirPath = path.join(PROJECTS_DIR, dir)
|
|
241
|
+
|
|
242
|
+
const jsonlFiles = fs.readdirSync(dirPath).filter((f) => f.endsWith('.jsonl'))
|
|
243
|
+
|
|
244
|
+
for (const file of jsonlFiles) {
|
|
245
|
+
const session = parseSessionFile(path.join(dirPath, file), allPricing)
|
|
246
|
+
if (!session) continue
|
|
247
|
+
|
|
248
|
+
session.project = projectName
|
|
249
|
+
session.projectPath = projectPath
|
|
250
|
+
sessions.push(session)
|
|
251
|
+
|
|
252
|
+
// Aggregate project stats
|
|
253
|
+
if (!projectMap.has(projectName)) {
|
|
254
|
+
projectMap.set(projectName, {
|
|
255
|
+
name: projectName,
|
|
256
|
+
path: projectPath,
|
|
257
|
+
sessions: 0,
|
|
258
|
+
totalMessages: 0,
|
|
259
|
+
totalTokens: 0,
|
|
260
|
+
totalCost: 0,
|
|
261
|
+
totalDurationMinutes: 0,
|
|
262
|
+
toolCalls: {},
|
|
263
|
+
})
|
|
264
|
+
}
|
|
265
|
+
const proj = projectMap.get(projectName)!
|
|
266
|
+
proj.sessions++
|
|
267
|
+
proj.totalMessages += session.totalMessages
|
|
268
|
+
proj.totalTokens += session.totalTokens
|
|
269
|
+
proj.totalCost += session.costUSD
|
|
270
|
+
proj.totalDurationMinutes += session.durationMinutes
|
|
271
|
+
for (const [tool, count] of Object.entries(session.toolCalls)) {
|
|
272
|
+
proj.toolCalls[tool] = (proj.toolCalls[tool] || 0) + count
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Aggregate daily stats
|
|
276
|
+
if (session.startTime) {
|
|
277
|
+
const date = session.startTime.slice(0, 10)
|
|
278
|
+
if (!dailyMap.has(date)) {
|
|
279
|
+
dailyMap.set(date, {
|
|
280
|
+
date,
|
|
281
|
+
sessions: 0,
|
|
282
|
+
messages: 0,
|
|
283
|
+
inputTokens: 0,
|
|
284
|
+
outputTokens: 0,
|
|
285
|
+
cacheCreationTokens: 0,
|
|
286
|
+
cacheReadTokens: 0,
|
|
287
|
+
totalTokens: 0,
|
|
288
|
+
costUSD: 0,
|
|
289
|
+
toolCalls: 0,
|
|
290
|
+
})
|
|
291
|
+
}
|
|
292
|
+
const daily = dailyMap.get(date)!
|
|
293
|
+
daily.sessions++
|
|
294
|
+
daily.messages += session.totalMessages
|
|
295
|
+
daily.inputTokens += session.inputTokens
|
|
296
|
+
daily.outputTokens += session.outputTokens
|
|
297
|
+
daily.cacheCreationTokens += session.cacheCreationTokens
|
|
298
|
+
daily.cacheReadTokens += session.cacheReadTokens
|
|
299
|
+
daily.totalTokens += session.totalTokens
|
|
300
|
+
daily.costUSD += session.costUSD
|
|
301
|
+
daily.toolCalls += session.toolCallsTotal
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Aggregate tool usage
|
|
305
|
+
for (const [tool, count] of Object.entries(session.toolCalls)) {
|
|
306
|
+
toolUsage[tool] = (toolUsage[tool] || 0) + count
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Sort sessions by start time desc
|
|
312
|
+
sessions.sort((a, b) => (b.startTime || '').localeCompare(a.startTime || ''))
|
|
313
|
+
|
|
314
|
+
// Build daily array sorted by date
|
|
315
|
+
const daily = Array.from(dailyMap.values()).sort((a, b) => a.date.localeCompare(b.date))
|
|
316
|
+
|
|
317
|
+
// Build projects array sorted by cost desc
|
|
318
|
+
const projects = Array.from(projectMap.values()).sort((a, b) => b.totalCost - a.totalCost)
|
|
319
|
+
|
|
320
|
+
// Build overview
|
|
321
|
+
const models: Record<string, number> = {}
|
|
322
|
+
for (const s of sessions) {
|
|
323
|
+
models[s.model] = (models[s.model] || 0) + 1
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const overview: OverviewStats = {
|
|
327
|
+
totalSessions: sessions.length,
|
|
328
|
+
totalProjects: projects.length,
|
|
329
|
+
totalMessages: sessions.reduce((a, s) => a + s.totalMessages, 0),
|
|
330
|
+
totalUserMessages: sessions.reduce((a, s) => a + s.userMessages, 0),
|
|
331
|
+
totalAssistantMessages: sessions.reduce((a, s) => a + s.assistantMessages, 0),
|
|
332
|
+
totalInputTokens: sessions.reduce((a, s) => a + s.inputTokens, 0),
|
|
333
|
+
totalOutputTokens: sessions.reduce((a, s) => a + s.outputTokens, 0),
|
|
334
|
+
totalCacheCreationTokens: sessions.reduce((a, s) => a + s.cacheCreationTokens, 0),
|
|
335
|
+
totalCacheReadTokens: sessions.reduce((a, s) => a + s.cacheReadTokens, 0),
|
|
336
|
+
totalTokens: sessions.reduce((a, s) => a + s.totalTokens, 0),
|
|
337
|
+
totalCostUSD: sessions.reduce((a, s) => a + s.costUSD, 0),
|
|
338
|
+
totalDurationMinutes: sessions.reduce((a, s) => a + s.durationMinutes, 0),
|
|
339
|
+
totalToolCalls: sessions.reduce((a, s) => a + s.toolCallsTotal, 0),
|
|
340
|
+
models,
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return { overview, sessions, projects, daily, toolUsage }
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function emptyUsageData(): UsageData {
|
|
347
|
+
return {
|
|
348
|
+
overview: {
|
|
349
|
+
totalSessions: 0,
|
|
350
|
+
totalProjects: 0,
|
|
351
|
+
totalMessages: 0,
|
|
352
|
+
totalUserMessages: 0,
|
|
353
|
+
totalAssistantMessages: 0,
|
|
354
|
+
totalInputTokens: 0,
|
|
355
|
+
totalOutputTokens: 0,
|
|
356
|
+
totalCacheCreationTokens: 0,
|
|
357
|
+
totalCacheReadTokens: 0,
|
|
358
|
+
totalTokens: 0,
|
|
359
|
+
totalCostUSD: 0,
|
|
360
|
+
totalDurationMinutes: 0,
|
|
361
|
+
totalToolCalls: 0,
|
|
362
|
+
models: {},
|
|
363
|
+
},
|
|
364
|
+
sessions: [],
|
|
365
|
+
projects: [],
|
|
366
|
+
daily: [],
|
|
367
|
+
toolUsage: {},
|
|
368
|
+
}
|
|
369
|
+
}
|