agentfit 0.1.0 → 0.1.1
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 +30 -34
- package/app/(dashboard)/daily/page.tsx +1 -1
- package/app/(dashboard)/flow/page.tsx +17 -0
- package/app/(dashboard)/layout.tsx +2 -0
- package/app/(dashboard)/page.tsx +24 -5
- package/app/(dashboard)/reports/[id]/page.tsx +72 -0
- package/app/(dashboard)/reports/page.tsx +132 -0
- package/app/(dashboard)/sessions/[id]/page.tsx +167 -0
- package/app/(dashboard)/settings/page.tsx +180 -0
- package/app/api/backup/route.ts +215 -0
- package/app/api/check/route.ts +11 -1
- package/app/api/command-insights/route.ts +13 -0
- package/app/api/images-analysis/route.ts +3 -4
- package/app/api/reports/[id]/route.ts +23 -0
- package/app/api/reports/route.ts +50 -0
- package/app/api/reset/route.ts +21 -0
- package/app/api/session/route.ts +40 -0
- package/app/api/usage/route.ts +25 -1
- package/app/layout.tsx +1 -1
- package/bin/agentfit.mjs +2 -2
- package/components/agent-coach.tsx +256 -129
- package/components/app-sidebar.tsx +258 -8
- package/components/backup-section.tsx +236 -0
- package/components/daily-chart.tsx +404 -83
- package/components/dashboard-shell.tsx +9 -24
- package/components/data-provider.tsx +66 -2
- package/components/fitness-score.tsx +95 -54
- package/components/overview-cards.tsx +148 -41
- package/components/report-view.tsx +307 -0
- package/components/screenshots-analysis.tsx +51 -46
- package/components/session-chatlog.tsx +124 -0
- package/components/session-timeline.tsx +184 -0
- package/components/session-workflow.tsx +183 -0
- package/components/sessions-table.tsx +9 -1
- package/components/tool-flow-graph.tsx +144 -0
- package/components/ui/carousel.tsx +242 -0
- package/components/ui/sidebar.tsx +1 -1
- package/components/ui/sonner.tsx +51 -0
- package/generated/prisma/browser.ts +5 -0
- package/generated/prisma/client.ts +5 -0
- package/generated/prisma/internal/class.ts +14 -4
- package/generated/prisma/internal/prismaNamespace.ts +96 -2
- package/generated/prisma/internal/prismaNamespaceBrowser.ts +20 -1
- package/generated/prisma/models/Report.ts +1219 -0
- package/generated/prisma/models/Session.ts +187 -1
- package/generated/prisma/models.ts +1 -0
- package/lib/coach.ts +530 -211
- package/lib/command-insights.ts +231 -0
- package/lib/db.ts +1 -1
- package/lib/parse-codex.ts +5 -0
- package/lib/parse-logs.ts +65 -0
- package/lib/queries-codex.ts +22 -0
- package/lib/queries.ts +42 -0
- package/lib/report.ts +156 -0
- package/lib/session-detail.ts +382 -0
- package/lib/sync.ts +77 -0
- package/lib/tool-flow.ts +71 -0
- package/next.config.mjs +6 -1
- package/package.json +16 -2
- package/plugins/cost-heatmap/component.tsx +72 -50
- package/prisma/schema.prisma +17 -0
- package/.claude/settings.local.json +0 -26
- package/CONTRIBUTING.md +0 -209
- package/prisma/migrations/20260328152517_init/migration.sql +0 -41
- package/prisma/migrations/20260328153801_add_image_model/migration.sql +0 -18
- package/prisma/migrations/migration_lock.toml +0 -3
- package/prisma.config.ts +0 -14
- package/setup.sh +0 -73
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
// ─── Command Discovery Insights ──────────────────────────────────────
|
|
2
|
+
// Analyzes ~/.claude/history.jsonl command patterns to generate
|
|
3
|
+
// actionable coaching insights about command adoption.
|
|
4
|
+
|
|
5
|
+
import fs from 'fs'
|
|
6
|
+
import path from 'path'
|
|
7
|
+
import os from 'os'
|
|
8
|
+
|
|
9
|
+
export interface CommandInsight {
|
|
10
|
+
id: string
|
|
11
|
+
title: string
|
|
12
|
+
description: string
|
|
13
|
+
severity: 'tip' | 'achievement'
|
|
14
|
+
metric?: string
|
|
15
|
+
recommendation?: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface CommandTimeline {
|
|
19
|
+
command: string
|
|
20
|
+
firstUsed: string // ISO date
|
|
21
|
+
totalUses: number
|
|
22
|
+
recentUses: number // last 7 days
|
|
23
|
+
isBuiltIn: boolean
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// High-value built-in commands that many users don't know about
|
|
27
|
+
const POWER_COMMANDS: Record<string, { description: string; benefit: string }> = {
|
|
28
|
+
'/compact': {
|
|
29
|
+
description: 'Compress conversation context with optional focus instructions',
|
|
30
|
+
benefit: 'Reduces context overflow by ~50%. Use when context exceeds 80% but you\'re still working on the same task.',
|
|
31
|
+
},
|
|
32
|
+
'/rewind': {
|
|
33
|
+
description: 'Rewind conversation and/or code to a previous point',
|
|
34
|
+
benefit: 'Undo mistakes without restarting. Saves 5-10 minutes per recovery vs starting fresh.',
|
|
35
|
+
},
|
|
36
|
+
'/simplify': {
|
|
37
|
+
description: 'Review changed code for reuse, quality, and efficiency',
|
|
38
|
+
benefit: 'Catches code quality issues automatically. Run after completing a feature to clean up.',
|
|
39
|
+
},
|
|
40
|
+
'/diff': {
|
|
41
|
+
description: 'Interactive diff viewer for uncommitted changes',
|
|
42
|
+
benefit: 'Review all changes at a glance before committing. Catches unintended modifications.',
|
|
43
|
+
},
|
|
44
|
+
'/btw': {
|
|
45
|
+
description: 'Ask a side question without polluting the main conversation',
|
|
46
|
+
benefit: 'Keeps your main context clean. Quick lookups without losing your place.',
|
|
47
|
+
},
|
|
48
|
+
'/branch': {
|
|
49
|
+
description: 'Create a branch of the current conversation',
|
|
50
|
+
benefit: 'Try risky approaches without losing your current progress. Fork, experiment, come back.',
|
|
51
|
+
},
|
|
52
|
+
'/plan': {
|
|
53
|
+
description: 'Enter plan mode for complex tasks',
|
|
54
|
+
benefit: 'Forces the agent to think before acting. Reduces wasted iterations on complex problems.',
|
|
55
|
+
},
|
|
56
|
+
'/context': {
|
|
57
|
+
description: 'Visualize current context usage as a colored grid',
|
|
58
|
+
benefit: 'See exactly what\'s consuming your context window. Know when to /compact.',
|
|
59
|
+
},
|
|
60
|
+
'/security-review': {
|
|
61
|
+
description: 'Analyze pending changes for security vulnerabilities',
|
|
62
|
+
benefit: 'Catches injection, auth, and data exposure issues before they ship.',
|
|
63
|
+
},
|
|
64
|
+
'/insights': {
|
|
65
|
+
description: 'Generate a report analyzing your Claude Code sessions',
|
|
66
|
+
benefit: 'Built-in analytics about your interaction patterns and friction points.',
|
|
67
|
+
},
|
|
68
|
+
'/frontend-design': {
|
|
69
|
+
description: 'Create distinctive, production-grade frontend interfaces',
|
|
70
|
+
benefit: 'Generates polished UI components with creative design choices, not generic AI slop.',
|
|
71
|
+
},
|
|
72
|
+
'/rename': {
|
|
73
|
+
description: 'Name your session for easy recall later',
|
|
74
|
+
benefit: 'Find important sessions quickly with /resume instead of scrolling through UUIDs.',
|
|
75
|
+
},
|
|
76
|
+
'/export': {
|
|
77
|
+
description: 'Export the current conversation as plain text',
|
|
78
|
+
benefit: 'Save important conversations for documentation, sharing, or review.',
|
|
79
|
+
},
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function parseCommandHistory(): CommandTimeline[] {
|
|
83
|
+
const historyPath = path.join(os.homedir(), '.claude', 'history.jsonl')
|
|
84
|
+
if (!fs.existsSync(historyPath)) return []
|
|
85
|
+
|
|
86
|
+
const content = fs.readFileSync(historyPath, 'utf-8')
|
|
87
|
+
const lines = content.trim().split('\n')
|
|
88
|
+
|
|
89
|
+
const commandData = new Map<string, { firstUsed: number; uses: number; recentUses: number }>()
|
|
90
|
+
const now = Date.now()
|
|
91
|
+
const sevenDaysAgo = now - 7 * 24 * 60 * 60 * 1000
|
|
92
|
+
|
|
93
|
+
for (const line of lines) {
|
|
94
|
+
if (!line.trim()) continue
|
|
95
|
+
try {
|
|
96
|
+
const entry = JSON.parse(line)
|
|
97
|
+
const text = (entry.display || '').trim()
|
|
98
|
+
const ts = entry.timestamp || 0
|
|
99
|
+
const match = text.match(/^\/([a-zA-Z_-]+)/)
|
|
100
|
+
if (!match) continue
|
|
101
|
+
const cmd = '/' + match[1]
|
|
102
|
+
|
|
103
|
+
if (!commandData.has(cmd)) {
|
|
104
|
+
commandData.set(cmd, { firstUsed: ts, uses: 0, recentUses: 0 })
|
|
105
|
+
}
|
|
106
|
+
const d = commandData.get(cmd)!
|
|
107
|
+
d.uses++
|
|
108
|
+
if (d.firstUsed > ts) d.firstUsed = ts
|
|
109
|
+
if (ts > sevenDaysAgo) d.recentUses++
|
|
110
|
+
} catch {
|
|
111
|
+
continue
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return Array.from(commandData.entries()).map(([cmd, data]) => ({
|
|
116
|
+
command: cmd,
|
|
117
|
+
firstUsed: new Date(data.firstUsed).toISOString().slice(0, 10),
|
|
118
|
+
totalUses: data.uses,
|
|
119
|
+
recentUses: data.recentUses,
|
|
120
|
+
isBuiltIn: cmd in POWER_COMMANDS,
|
|
121
|
+
}))
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function generateCommandInsights(): CommandInsight[] {
|
|
125
|
+
const timeline = parseCommandHistory()
|
|
126
|
+
const insights: CommandInsight[] = []
|
|
127
|
+
const usedCommands = new Set(timeline.map(t => t.command))
|
|
128
|
+
|
|
129
|
+
// 1. Power commands you discovered and now use frequently
|
|
130
|
+
const powerHits = timeline
|
|
131
|
+
.filter(t => POWER_COMMANDS[t.command] && t.totalUses >= 5)
|
|
132
|
+
.sort((a, b) => b.totalUses - a.totalUses)
|
|
133
|
+
|
|
134
|
+
if (powerHits.length > 0) {
|
|
135
|
+
const top = powerHits.slice(0, 3)
|
|
136
|
+
insights.push({
|
|
137
|
+
id: 'power-commands-adopted',
|
|
138
|
+
title: `Power commands mastered: ${top.map(t => t.command).join(', ')}`,
|
|
139
|
+
description: `You've adopted ${powerHits.length} power commands. ${top[0].command} is your most-used (${top[0].totalUses}x). These commands significantly improve your workflow efficiency.`,
|
|
140
|
+
severity: 'achievement',
|
|
141
|
+
metric: `${powerHits.length} mastered`,
|
|
142
|
+
})
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// 2. Recently discovered commands gaining traction
|
|
146
|
+
const recentDiscoveries = timeline
|
|
147
|
+
.filter(t => {
|
|
148
|
+
const daysSinceFirst = (Date.now() - new Date(t.firstUsed).getTime()) / (1000 * 60 * 60 * 24)
|
|
149
|
+
return daysSinceFirst < 14 && t.totalUses >= 3
|
|
150
|
+
})
|
|
151
|
+
.sort((a, b) => b.totalUses - a.totalUses)
|
|
152
|
+
|
|
153
|
+
for (const disc of recentDiscoveries.slice(0, 2)) {
|
|
154
|
+
const info = POWER_COMMANDS[disc.command]
|
|
155
|
+
insights.push({
|
|
156
|
+
id: `recent-discovery-${disc.command}`,
|
|
157
|
+
title: `New favorite: ${disc.command} (${disc.totalUses}x in ${Math.ceil((Date.now() - new Date(disc.firstUsed).getTime()) / (1000 * 60 * 60 * 24))} days)`,
|
|
158
|
+
description: info
|
|
159
|
+
? `${info.description}. ${info.benefit}`
|
|
160
|
+
: `You discovered ${disc.command} recently and it's becoming a regular part of your workflow.`,
|
|
161
|
+
severity: 'achievement',
|
|
162
|
+
metric: `${disc.totalUses}x`,
|
|
163
|
+
})
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// 3. High-value commands you haven't tried yet
|
|
167
|
+
const untried = Object.entries(POWER_COMMANDS)
|
|
168
|
+
.filter(([cmd]) => !usedCommands.has(cmd))
|
|
169
|
+
.sort(() => Math.random() - 0.5) // randomize so different tips each time
|
|
170
|
+
|
|
171
|
+
for (const [cmd, info] of untried.slice(0, 3)) {
|
|
172
|
+
insights.push({
|
|
173
|
+
id: `try-${cmd}`,
|
|
174
|
+
title: `Try ${cmd}`,
|
|
175
|
+
description: info.description,
|
|
176
|
+
severity: 'tip',
|
|
177
|
+
recommendation: info.benefit,
|
|
178
|
+
})
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// 4. Commands you used to use but stopped
|
|
182
|
+
const abandoned = timeline
|
|
183
|
+
.filter(t => t.totalUses >= 3 && t.recentUses === 0 && POWER_COMMANDS[t.command])
|
|
184
|
+
.sort((a, b) => b.totalUses - a.totalUses)
|
|
185
|
+
|
|
186
|
+
for (const cmd of abandoned.slice(0, 2)) {
|
|
187
|
+
const info = POWER_COMMANDS[cmd.command]!
|
|
188
|
+
insights.push({
|
|
189
|
+
id: `revive-${cmd.command}`,
|
|
190
|
+
title: `Revisit ${cmd.command}?`,
|
|
191
|
+
description: `You used ${cmd.command} ${cmd.totalUses} times but haven't used it recently.`,
|
|
192
|
+
severity: 'tip',
|
|
193
|
+
recommendation: info.benefit,
|
|
194
|
+
})
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// 5. Command diversity insight
|
|
198
|
+
const weeklyDiversity = timeline.filter(t => t.recentUses > 0).length
|
|
199
|
+
if (weeklyDiversity >= 8) {
|
|
200
|
+
insights.push({
|
|
201
|
+
id: 'diverse-commands',
|
|
202
|
+
title: `${weeklyDiversity} commands used this week`,
|
|
203
|
+
description: 'You have a diverse command vocabulary. This means you\'re leveraging the full power of your coding agent instead of just chatting.',
|
|
204
|
+
severity: 'achievement',
|
|
205
|
+
metric: `${weeklyDiversity} active`,
|
|
206
|
+
})
|
|
207
|
+
} else if (weeklyDiversity <= 3 && timeline.length > 5) {
|
|
208
|
+
insights.push({
|
|
209
|
+
id: 'low-diversity',
|
|
210
|
+
title: 'Low command diversity this week',
|
|
211
|
+
description: `Only ${weeklyDiversity} commands used in the last 7 days. Slash commands save time by encoding common workflows into single keystrokes.`,
|
|
212
|
+
severity: 'tip',
|
|
213
|
+
recommendation: 'Type / in Claude Code to see all available commands. Try using at least one new command today.',
|
|
214
|
+
})
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// 6. Custom skills/plugins adoption
|
|
218
|
+
const customCommands = timeline.filter(t => !Object.keys(POWER_COMMANDS).includes(t.command) && t.totalUses >= 3)
|
|
219
|
+
if (customCommands.length >= 3) {
|
|
220
|
+
const topCustom = customCommands.sort((a, b) => b.totalUses - a.totalUses).slice(0, 3)
|
|
221
|
+
insights.push({
|
|
222
|
+
id: 'custom-skills',
|
|
223
|
+
title: `${customCommands.length} custom skills in your toolkit`,
|
|
224
|
+
description: `Your most-used custom commands: ${topCustom.map(c => `${c.command} (${c.totalUses}x)`).join(', ')}. Custom skills automate your specific workflows.`,
|
|
225
|
+
severity: 'achievement',
|
|
226
|
+
metric: `${customCommands.length} skills`,
|
|
227
|
+
})
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return insights
|
|
231
|
+
}
|
package/lib/db.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { PrismaLibSql } from '@prisma/adapter-libsql'
|
|
|
3
3
|
import path from 'path'
|
|
4
4
|
|
|
5
5
|
function createPrisma() {
|
|
6
|
-
const dbPath = path.resolve(process.cwd(), '
|
|
6
|
+
const dbPath = path.resolve(process.cwd(), 'agentfit.db')
|
|
7
7
|
const adapter = new PrismaLibSql({ url: `file:${dbPath}` })
|
|
8
8
|
return new PrismaClient({ adapter })
|
|
9
9
|
}
|
package/lib/parse-codex.ts
CHANGED
|
@@ -158,6 +158,11 @@ export function parseCodexSession(filePath: string): SessionSummary | null {
|
|
|
158
158
|
model: model || 'gpt-5',
|
|
159
159
|
toolCalls,
|
|
160
160
|
toolCallsTotal: Object.values(toolCalls).reduce((a, b) => a + b, 0),
|
|
161
|
+
skillCalls: {},
|
|
162
|
+
apiErrors: 0,
|
|
163
|
+
rateLimitErrors: 0,
|
|
164
|
+
userInterruptions: 0,
|
|
165
|
+
permissionModes: {},
|
|
161
166
|
}
|
|
162
167
|
} catch {
|
|
163
168
|
return null
|
package/lib/parse-logs.ts
CHANGED
|
@@ -27,6 +27,12 @@ export interface SessionSummary {
|
|
|
27
27
|
model: string
|
|
28
28
|
toolCalls: Record<string, number>
|
|
29
29
|
toolCallsTotal: number
|
|
30
|
+
skillCalls: Record<string, number>
|
|
31
|
+
messageTimestamps?: string[] // ISO timestamps of each message
|
|
32
|
+
apiErrors: number
|
|
33
|
+
rateLimitErrors: number
|
|
34
|
+
userInterruptions: number
|
|
35
|
+
permissionModes: Record<string, number> // default, acceptEdits, bypassPermissions, plan
|
|
30
36
|
}
|
|
31
37
|
|
|
32
38
|
export interface ProjectSummary {
|
|
@@ -44,6 +50,8 @@ export interface DailyUsage {
|
|
|
44
50
|
date: string
|
|
45
51
|
sessions: number
|
|
46
52
|
messages: number
|
|
53
|
+
userMessages: number
|
|
54
|
+
assistantMessages: number
|
|
47
55
|
inputTokens: number
|
|
48
56
|
outputTokens: number
|
|
49
57
|
cacheCreationTokens: number
|
|
@@ -51,6 +59,9 @@ export interface DailyUsage {
|
|
|
51
59
|
totalTokens: number
|
|
52
60
|
costUSD: number
|
|
53
61
|
toolCalls: number
|
|
62
|
+
toolCallsDetail: Record<string, number>
|
|
63
|
+
interruptions: number
|
|
64
|
+
rateLimitErrors: number
|
|
54
65
|
}
|
|
55
66
|
|
|
56
67
|
export interface OverviewStats {
|
|
@@ -67,7 +78,12 @@ export interface OverviewStats {
|
|
|
67
78
|
totalCostUSD: number
|
|
68
79
|
totalDurationMinutes: number
|
|
69
80
|
totalToolCalls: number
|
|
81
|
+
totalApiErrors: number
|
|
82
|
+
totalRateLimitDays: number
|
|
83
|
+
totalUserInterruptions: number
|
|
70
84
|
models: Record<string, number>
|
|
85
|
+
skillUsage: Record<string, number>
|
|
86
|
+
permissionModes: Record<string, number>
|
|
71
87
|
}
|
|
72
88
|
|
|
73
89
|
export interface UsageData {
|
|
@@ -85,7 +101,23 @@ function decodeProjectPath(dirName: string): string {
|
|
|
85
101
|
}
|
|
86
102
|
|
|
87
103
|
function getProjectName(projectPath: string): string {
|
|
104
|
+
// The decoded path may be wrong if the actual folder name contains dashes,
|
|
105
|
+
// since decodeProjectPath replaces ALL dashes with slashes.
|
|
106
|
+
// Try merging trailing segments to find the real directory on disk.
|
|
107
|
+
if (fs.existsSync(projectPath)) {
|
|
108
|
+
return path.basename(projectPath)
|
|
109
|
+
}
|
|
88
110
|
const parts = projectPath.split('/')
|
|
111
|
+
for (let merge = 2; merge <= Math.min(parts.length, 6); merge++) {
|
|
112
|
+
const parentParts = parts.slice(0, -merge)
|
|
113
|
+
const nameParts = parts.slice(-merge)
|
|
114
|
+
const candidateName = nameParts.join('-')
|
|
115
|
+
const candidatePath = [...parentParts, candidateName].join('/')
|
|
116
|
+
if (fs.existsSync(candidatePath)) {
|
|
117
|
+
return candidateName
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// Fallback: last segment
|
|
89
121
|
return parts[parts.length - 1] || parts[parts.length - 2] || projectPath
|
|
90
122
|
}
|
|
91
123
|
|
|
@@ -125,6 +157,7 @@ function parseSessionFile(
|
|
|
125
157
|
let startTime = ''
|
|
126
158
|
let endTime = ''
|
|
127
159
|
const toolCalls: Record<string, number> = {}
|
|
160
|
+
const permissionModes: Record<string, number> = {}
|
|
128
161
|
|
|
129
162
|
for (const line of lines) {
|
|
130
163
|
if (!line.trim()) continue
|
|
@@ -143,6 +176,11 @@ function parseSessionFile(
|
|
|
143
176
|
|
|
144
177
|
if (entry.type === 'user') {
|
|
145
178
|
userMessages++
|
|
179
|
+
// Track permission mode
|
|
180
|
+
const pm = (entry as Record<string, unknown>).permissionMode as string | undefined
|
|
181
|
+
if (pm) {
|
|
182
|
+
permissionModes[pm] = (permissionModes[pm] || 0) + 1
|
|
183
|
+
}
|
|
146
184
|
} else if (entry.type === 'assistant' && entry.message) {
|
|
147
185
|
assistantMessages++
|
|
148
186
|
const msg = entry.message
|
|
@@ -212,6 +250,11 @@ function parseSessionFile(
|
|
|
212
250
|
model: model || 'unknown',
|
|
213
251
|
toolCalls,
|
|
214
252
|
toolCallsTotal: Object.values(toolCalls).reduce((a, b) => a + b, 0),
|
|
253
|
+
skillCalls: {},
|
|
254
|
+
apiErrors: 0,
|
|
255
|
+
rateLimitErrors: 0,
|
|
256
|
+
userInterruptions: 0,
|
|
257
|
+
permissionModes,
|
|
215
258
|
}
|
|
216
259
|
} catch {
|
|
217
260
|
return null
|
|
@@ -280,6 +323,8 @@ export async function parseAllLogs(): Promise<UsageData> {
|
|
|
280
323
|
date,
|
|
281
324
|
sessions: 0,
|
|
282
325
|
messages: 0,
|
|
326
|
+
userMessages: 0,
|
|
327
|
+
assistantMessages: 0,
|
|
283
328
|
inputTokens: 0,
|
|
284
329
|
outputTokens: 0,
|
|
285
330
|
cacheCreationTokens: 0,
|
|
@@ -287,11 +332,16 @@ export async function parseAllLogs(): Promise<UsageData> {
|
|
|
287
332
|
totalTokens: 0,
|
|
288
333
|
costUSD: 0,
|
|
289
334
|
toolCalls: 0,
|
|
335
|
+
toolCallsDetail: {},
|
|
336
|
+
interruptions: 0,
|
|
337
|
+
rateLimitErrors: 0,
|
|
290
338
|
})
|
|
291
339
|
}
|
|
292
340
|
const daily = dailyMap.get(date)!
|
|
293
341
|
daily.sessions++
|
|
294
342
|
daily.messages += session.totalMessages
|
|
343
|
+
daily.userMessages += session.userMessages
|
|
344
|
+
daily.assistantMessages += session.assistantMessages
|
|
295
345
|
daily.inputTokens += session.inputTokens
|
|
296
346
|
daily.outputTokens += session.outputTokens
|
|
297
347
|
daily.cacheCreationTokens += session.cacheCreationTokens
|
|
@@ -299,6 +349,11 @@ export async function parseAllLogs(): Promise<UsageData> {
|
|
|
299
349
|
daily.totalTokens += session.totalTokens
|
|
300
350
|
daily.costUSD += session.costUSD
|
|
301
351
|
daily.toolCalls += session.toolCallsTotal
|
|
352
|
+
daily.interruptions += session.userInterruptions
|
|
353
|
+
daily.rateLimitErrors += session.rateLimitErrors
|
|
354
|
+
for (const [tool, count] of Object.entries(session.toolCalls)) {
|
|
355
|
+
daily.toolCallsDetail[tool] = (daily.toolCallsDetail[tool] || 0) + count
|
|
356
|
+
}
|
|
302
357
|
}
|
|
303
358
|
|
|
304
359
|
// Aggregate tool usage
|
|
@@ -337,7 +392,12 @@ export async function parseAllLogs(): Promise<UsageData> {
|
|
|
337
392
|
totalCostUSD: sessions.reduce((a, s) => a + s.costUSD, 0),
|
|
338
393
|
totalDurationMinutes: sessions.reduce((a, s) => a + s.durationMinutes, 0),
|
|
339
394
|
totalToolCalls: sessions.reduce((a, s) => a + s.toolCallsTotal, 0),
|
|
395
|
+
totalApiErrors: 0,
|
|
396
|
+
totalRateLimitDays: 0,
|
|
397
|
+
totalUserInterruptions: 0,
|
|
340
398
|
models,
|
|
399
|
+
skillUsage: {},
|
|
400
|
+
permissionModes: {},
|
|
341
401
|
}
|
|
342
402
|
|
|
343
403
|
return { overview, sessions, projects, daily, toolUsage }
|
|
@@ -359,7 +419,12 @@ function emptyUsageData(): UsageData {
|
|
|
359
419
|
totalCostUSD: 0,
|
|
360
420
|
totalDurationMinutes: 0,
|
|
361
421
|
totalToolCalls: 0,
|
|
422
|
+
totalApiErrors: 0,
|
|
423
|
+
totalRateLimitDays: 0,
|
|
424
|
+
totalUserInterruptions: 0,
|
|
362
425
|
models: {},
|
|
426
|
+
skillUsage: {},
|
|
427
|
+
permissionModes: {},
|
|
363
428
|
},
|
|
364
429
|
sessions: [],
|
|
365
430
|
projects: [],
|
package/lib/queries-codex.ts
CHANGED
|
@@ -52,6 +52,8 @@ export function getCodexUsageData(): UsageData {
|
|
|
52
52
|
date,
|
|
53
53
|
sessions: 0,
|
|
54
54
|
messages: 0,
|
|
55
|
+
userMessages: 0,
|
|
56
|
+
assistantMessages: 0,
|
|
55
57
|
inputTokens: 0,
|
|
56
58
|
outputTokens: 0,
|
|
57
59
|
cacheCreationTokens: 0,
|
|
@@ -59,16 +61,26 @@ export function getCodexUsageData(): UsageData {
|
|
|
59
61
|
totalTokens: 0,
|
|
60
62
|
costUSD: 0,
|
|
61
63
|
toolCalls: 0,
|
|
64
|
+
toolCallsDetail: {},
|
|
65
|
+
interruptions: 0,
|
|
66
|
+
rateLimitErrors: 0,
|
|
62
67
|
})
|
|
63
68
|
}
|
|
64
69
|
const daily = dailyMap.get(date)!
|
|
65
70
|
daily.sessions++
|
|
66
71
|
daily.messages += s.totalMessages
|
|
72
|
+
daily.userMessages += s.userMessages
|
|
73
|
+
daily.assistantMessages += s.assistantMessages
|
|
67
74
|
daily.inputTokens += s.inputTokens
|
|
68
75
|
daily.outputTokens += s.outputTokens
|
|
69
76
|
daily.totalTokens += s.totalTokens
|
|
70
77
|
daily.costUSD += s.costUSD
|
|
71
78
|
daily.toolCalls += s.toolCallsTotal
|
|
79
|
+
daily.interruptions += s.userInterruptions
|
|
80
|
+
daily.rateLimitErrors += s.rateLimitErrors
|
|
81
|
+
for (const [tool, count] of Object.entries(s.toolCalls)) {
|
|
82
|
+
daily.toolCallsDetail[tool] = (daily.toolCallsDetail[tool] || 0) + count
|
|
83
|
+
}
|
|
72
84
|
|
|
73
85
|
// Tool usage
|
|
74
86
|
for (const [tool, count] of Object.entries(s.toolCalls)) {
|
|
@@ -98,7 +110,12 @@ export function getCodexUsageData(): UsageData {
|
|
|
98
110
|
totalCostUSD: sessions.reduce((a, s) => a + s.costUSD, 0),
|
|
99
111
|
totalDurationMinutes: sessions.reduce((a, s) => a + s.durationMinutes, 0),
|
|
100
112
|
totalToolCalls: sessions.reduce((a, s) => a + s.toolCallsTotal, 0),
|
|
113
|
+
totalApiErrors: 0,
|
|
114
|
+
totalRateLimitDays: 0,
|
|
115
|
+
totalUserInterruptions: 0,
|
|
101
116
|
models,
|
|
117
|
+
skillUsage: {},
|
|
118
|
+
permissionModes: {},
|
|
102
119
|
}
|
|
103
120
|
|
|
104
121
|
return { overview, sessions, projects, daily, toolUsage }
|
|
@@ -120,7 +137,12 @@ function emptyUsageData(): UsageData {
|
|
|
120
137
|
totalCostUSD: 0,
|
|
121
138
|
totalDurationMinutes: 0,
|
|
122
139
|
totalToolCalls: 0,
|
|
140
|
+
totalApiErrors: 0,
|
|
141
|
+
totalRateLimitDays: 0,
|
|
142
|
+
totalUserInterruptions: 0,
|
|
123
143
|
models: {},
|
|
144
|
+
skillUsage: {},
|
|
145
|
+
permissionModes: {},
|
|
124
146
|
},
|
|
125
147
|
sessions: [],
|
|
126
148
|
projects: [],
|
package/lib/queries.ts
CHANGED
|
@@ -36,6 +36,12 @@ export async function getUsageData(): Promise<UsageData> {
|
|
|
36
36
|
model: s.model,
|
|
37
37
|
toolCalls: JSON.parse(s.toolCallsJson) as Record<string, number>,
|
|
38
38
|
toolCallsTotal: s.toolCallsTotal,
|
|
39
|
+
messageTimestamps: JSON.parse(s.messageTimestamps) as string[],
|
|
40
|
+
skillCalls: JSON.parse(s.skillCallsJson) as Record<string, number>,
|
|
41
|
+
apiErrors: s.apiErrors,
|
|
42
|
+
rateLimitErrors: s.rateLimitErrors,
|
|
43
|
+
userInterruptions: s.userInterruptions,
|
|
44
|
+
permissionModes: JSON.parse(s.permissionModesJson || '{}') as Record<string, number>,
|
|
39
45
|
}))
|
|
40
46
|
|
|
41
47
|
// Aggregate projects
|
|
@@ -74,6 +80,8 @@ export async function getUsageData(): Promise<UsageData> {
|
|
|
74
80
|
date,
|
|
75
81
|
sessions: 0,
|
|
76
82
|
messages: 0,
|
|
83
|
+
userMessages: 0,
|
|
84
|
+
assistantMessages: 0,
|
|
77
85
|
inputTokens: 0,
|
|
78
86
|
outputTokens: 0,
|
|
79
87
|
cacheCreationTokens: 0,
|
|
@@ -81,11 +89,16 @@ export async function getUsageData(): Promise<UsageData> {
|
|
|
81
89
|
totalTokens: 0,
|
|
82
90
|
costUSD: 0,
|
|
83
91
|
toolCalls: 0,
|
|
92
|
+
toolCallsDetail: {},
|
|
93
|
+
interruptions: 0,
|
|
94
|
+
rateLimitErrors: 0,
|
|
84
95
|
})
|
|
85
96
|
}
|
|
86
97
|
const daily = dailyMap.get(date)!
|
|
87
98
|
daily.sessions++
|
|
88
99
|
daily.messages += s.totalMessages
|
|
100
|
+
daily.userMessages += s.userMessages
|
|
101
|
+
daily.assistantMessages += s.assistantMessages
|
|
89
102
|
daily.inputTokens += s.inputTokens
|
|
90
103
|
daily.outputTokens += s.outputTokens
|
|
91
104
|
daily.cacheCreationTokens += s.cacheCreationTokens
|
|
@@ -93,6 +106,11 @@ export async function getUsageData(): Promise<UsageData> {
|
|
|
93
106
|
daily.totalTokens += s.totalTokens
|
|
94
107
|
daily.costUSD += s.costUSD
|
|
95
108
|
daily.toolCalls += s.toolCallsTotal
|
|
109
|
+
daily.interruptions += s.userInterruptions
|
|
110
|
+
daily.rateLimitErrors += s.rateLimitErrors
|
|
111
|
+
for (const [tool, count] of Object.entries(s.toolCalls)) {
|
|
112
|
+
daily.toolCallsDetail[tool] = (daily.toolCallsDetail[tool] || 0) + count
|
|
113
|
+
}
|
|
96
114
|
|
|
97
115
|
// Tool usage
|
|
98
116
|
for (const [tool, count] of Object.entries(s.toolCalls)) {
|
|
@@ -104,8 +122,16 @@ export async function getUsageData(): Promise<UsageData> {
|
|
|
104
122
|
const projects = Array.from(projectMap.values()).sort((a, b) => b.totalCost - a.totalCost)
|
|
105
123
|
|
|
106
124
|
const models: Record<string, number> = {}
|
|
125
|
+
const skillUsage: Record<string, number> = {}
|
|
126
|
+
const permissionModes: Record<string, number> = {}
|
|
107
127
|
for (const s of sessions) {
|
|
108
128
|
models[s.model] = (models[s.model] || 0) + 1
|
|
129
|
+
for (const [skill, count] of Object.entries(s.skillCalls)) {
|
|
130
|
+
skillUsage[skill] = (skillUsage[skill] || 0) + count
|
|
131
|
+
}
|
|
132
|
+
for (const [mode, count] of Object.entries(s.permissionModes)) {
|
|
133
|
+
permissionModes[mode] = (permissionModes[mode] || 0) + count
|
|
134
|
+
}
|
|
109
135
|
}
|
|
110
136
|
|
|
111
137
|
const overview: OverviewStats = {
|
|
@@ -122,7 +148,18 @@ export async function getUsageData(): Promise<UsageData> {
|
|
|
122
148
|
totalCostUSD: sessions.reduce((a, s) => a + s.costUSD, 0),
|
|
123
149
|
totalDurationMinutes: sessions.reduce((a, s) => a + s.durationMinutes, 0),
|
|
124
150
|
totalToolCalls: sessions.reduce((a, s) => a + s.toolCallsTotal, 0),
|
|
151
|
+
totalApiErrors: sessions.reduce((a, s) => a + s.apiErrors, 0),
|
|
152
|
+
totalRateLimitDays: (() => {
|
|
153
|
+
const days = new Set<string>()
|
|
154
|
+
for (const s of sessions) {
|
|
155
|
+
if (s.rateLimitErrors > 0) days.add(s.startTime.slice(0, 10))
|
|
156
|
+
}
|
|
157
|
+
return days.size
|
|
158
|
+
})(),
|
|
159
|
+
totalUserInterruptions: sessions.reduce((a, s) => a + s.userInterruptions, 0),
|
|
125
160
|
models,
|
|
161
|
+
skillUsage,
|
|
162
|
+
permissionModes,
|
|
126
163
|
}
|
|
127
164
|
|
|
128
165
|
return { overview, sessions, projects, daily: dailyArr, toolUsage }
|
|
@@ -144,7 +181,12 @@ function emptyUsageData(): UsageData {
|
|
|
144
181
|
totalCostUSD: 0,
|
|
145
182
|
totalDurationMinutes: 0,
|
|
146
183
|
totalToolCalls: 0,
|
|
184
|
+
totalApiErrors: 0,
|
|
185
|
+
totalRateLimitDays: 0,
|
|
186
|
+
totalUserInterruptions: 0,
|
|
147
187
|
models: {},
|
|
188
|
+
skillUsage: {},
|
|
189
|
+
permissionModes: {},
|
|
148
190
|
},
|
|
149
191
|
sessions: [],
|
|
150
192
|
projects: [],
|