agentfit 0.1.0 → 0.1.2
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/.github/workflows/release.yml +111 -0
- package/README.md +41 -38
- package/app/(dashboard)/daily/page.tsx +1 -1
- package/app/(dashboard)/data-management/page.tsx +180 -0
- 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/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/commands/route.ts +55 -1
- 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 +26 -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 +45 -10
- package/components/backup-section.tsx +236 -0
- package/components/daily-chart.tsx +447 -83
- package/components/dashboard-shell.tsx +29 -31
- package/components/data-provider.tsx +88 -8
- 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/electron/entitlements.mac.plist +16 -0
- package/electron/init-db.mjs +37 -0
- package/electron/main.mjs +203 -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 +97 -2
- package/generated/prisma/internal/prismaNamespaceBrowser.ts +21 -1
- package/generated/prisma/models/Report.ts +1219 -0
- package/generated/prisma/models/Session.ts +221 -1
- package/generated/prisma/models.ts +1 -0
- package/lib/coach.ts +571 -211
- package/lib/command-insights.ts +231 -0
- package/lib/db.ts +2 -2
- package/lib/parse-codex.ts +6 -0
- package/lib/parse-logs.ts +80 -1
- package/lib/queries-codex.ts +24 -0
- package/lib/queries.ts +45 -0
- package/lib/report.ts +156 -0
- package/lib/session-detail.ts +382 -0
- package/lib/sync.ts +87 -0
- package/lib/tool-flow.ts +71 -0
- package/next.config.mjs +6 -1
- package/package.json +17 -2
- package/plugins/cost-heatmap/component.tsx +72 -50
- package/prisma/migrations/20260401144555_add_system_prompt_edits/migration.sql +80 -0
- package/prisma/schema.prisma +18 -0
- package/prisma/schema.sql +81 -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.config.ts +0 -14
- package/setup.sh +0 -73
package/lib/tool-flow.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// ─── Tool Flow Analysis ──────────────────────────────────────────────
|
|
2
|
+
// Extracts tool call transition data for flow visualization.
|
|
3
|
+
|
|
4
|
+
import type { UsageData } from './parse-logs'
|
|
5
|
+
|
|
6
|
+
export interface ToolTransition {
|
|
7
|
+
source: string
|
|
8
|
+
target: string
|
|
9
|
+
count: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ToolFlowData {
|
|
13
|
+
nodes: { id: string; count: number }[]
|
|
14
|
+
edges: ToolTransition[]
|
|
15
|
+
totalTransitions: number
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function computeToolFlow(data: UsageData): ToolFlowData {
|
|
19
|
+
const toolUsage = data.toolUsage
|
|
20
|
+
const transitions = new Map<string, number>()
|
|
21
|
+
|
|
22
|
+
// We need per-session tool sequences. Since we store toolCallsJson as
|
|
23
|
+
// aggregate counts per session, we compute transitions from the global
|
|
24
|
+
// tool usage data and common patterns. For accurate transitions we'd
|
|
25
|
+
// need the raw sequence — but we can derive a meaningful flow from the
|
|
26
|
+
// session-level tool call co-occurrence.
|
|
27
|
+
|
|
28
|
+
// Build transitions from sessions that have tool call data
|
|
29
|
+
for (const session of data.sessions) {
|
|
30
|
+
const tools = Object.entries(session.toolCalls)
|
|
31
|
+
.sort((a, b) => b[1] - a[1])
|
|
32
|
+
|
|
33
|
+
// Create edges between co-occurring tools (weighted by min count)
|
|
34
|
+
for (let i = 0; i < tools.length; i++) {
|
|
35
|
+
for (let j = i + 1; j < tools.length; j++) {
|
|
36
|
+
const [toolA] = tools[i]
|
|
37
|
+
const [toolB] = tools[j]
|
|
38
|
+
// Direction: higher count tool → lower count tool
|
|
39
|
+
const key = `${toolA}→${toolB}`
|
|
40
|
+
const weight = Math.min(tools[i][1], tools[j][1])
|
|
41
|
+
transitions.set(key, (transitions.get(key) || 0) + weight)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Build nodes from tool usage
|
|
47
|
+
const nodes = Object.entries(toolUsage)
|
|
48
|
+
.filter(([, count]) => count > 5)
|
|
49
|
+
.sort((a, b) => b[1] - a[1])
|
|
50
|
+
.slice(0, 15)
|
|
51
|
+
.map(([id, count]) => ({ id, count }))
|
|
52
|
+
|
|
53
|
+
const nodeSet = new Set(nodes.map(n => n.id))
|
|
54
|
+
|
|
55
|
+
// Build edges (filter to only include nodes we're showing)
|
|
56
|
+
const edges: ToolTransition[] = []
|
|
57
|
+
for (const [key, count] of transitions) {
|
|
58
|
+
const [source, target] = key.split('→')
|
|
59
|
+
if (nodeSet.has(source) && nodeSet.has(target) && count > 2) {
|
|
60
|
+
edges.push({ source, target, count })
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
edges.sort((a, b) => b.count - a.count)
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
nodes,
|
|
68
|
+
edges: edges.slice(0, 30),
|
|
69
|
+
totalTransitions: edges.reduce((sum, e) => sum + e.count, 0),
|
|
70
|
+
}
|
|
71
|
+
}
|
package/next.config.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentfit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Fitness tracker dashboard for AI coding agents (Claude Code, Codex). Visualize usage, cost, tokens, and productivity from local conversation logs.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"type": "git",
|
|
21
21
|
"url": "https://github.com/harrywang/agentfit.git"
|
|
22
22
|
},
|
|
23
|
+
"main": "electron/main.mjs",
|
|
23
24
|
"scripts": {
|
|
24
25
|
"postinstall": "prisma generate",
|
|
25
26
|
"dev": "next dev --turbopack",
|
|
@@ -29,7 +30,14 @@
|
|
|
29
30
|
"format": "prettier --write \"**/*.{ts,tsx}\"",
|
|
30
31
|
"typecheck": "tsc --noEmit",
|
|
31
32
|
"test": "vitest run",
|
|
32
|
-
"test:watch": "vitest"
|
|
33
|
+
"test:watch": "vitest",
|
|
34
|
+
"prisma:schema-sql": "npx prisma migrate diff --from-empty --to-schema prisma/schema.prisma --script | sed 's/CREATE TABLE/CREATE TABLE IF NOT EXISTS/g; s/CREATE UNIQUE INDEX/CREATE UNIQUE INDEX IF NOT EXISTS/g; s/CREATE INDEX/CREATE INDEX IF NOT EXISTS/g' > prisma/schema.sql",
|
|
35
|
+
"electron:prepare": "node scripts/prepare-electron.mjs",
|
|
36
|
+
"electron:dev": "npm run prisma:schema-sql && npm run build && npm run electron:prepare && electron .",
|
|
37
|
+
"electron:build": "rm -rf dist-electron electron/server && npm run build && npm run electron:prepare && electron-builder --config electron-builder.yml",
|
|
38
|
+
"electron:build:mac": "rm -rf dist-electron electron/server && npm run build && npm run electron:prepare && electron-builder --mac --publish never --config electron-builder.yml",
|
|
39
|
+
"electron:build:win": "rm -rf dist-electron electron/server && npm run build && npm run electron:prepare && electron-builder --win --publish never --config electron-builder.yml",
|
|
40
|
+
"electron:build:all": "rm -rf dist-electron electron/server && npm run build && npm run electron:prepare && electron-builder --mac --win --config electron-builder.yml"
|
|
33
41
|
},
|
|
34
42
|
"dependencies": {
|
|
35
43
|
"@base-ui/react": "^1.3.0",
|
|
@@ -37,9 +45,13 @@
|
|
|
37
45
|
"@prisma/adapter-libsql": "^7.6.0",
|
|
38
46
|
"@prisma/client": "^7.6.0",
|
|
39
47
|
"@tabler/icons-react": "^3.41.0",
|
|
48
|
+
"@types/dagre": "^0.7.54",
|
|
49
|
+
"@xyflow/react": "^12.10.2",
|
|
40
50
|
"class-variance-authority": "^0.7.1",
|
|
41
51
|
"clsx": "^2.1.1",
|
|
52
|
+
"dagre": "^0.8.5",
|
|
42
53
|
"date-fns": "^4.1.0",
|
|
54
|
+
"embla-carousel-react": "^8.6.0",
|
|
43
55
|
"lucide-react": "^1.7.0",
|
|
44
56
|
"next": "16.1.7",
|
|
45
57
|
"next-themes": "^0.4.6",
|
|
@@ -48,6 +60,7 @@
|
|
|
48
60
|
"react-dom": "^19.2.4",
|
|
49
61
|
"recharts": "^3.8.0",
|
|
50
62
|
"shadcn": "^4.1.1",
|
|
63
|
+
"sonner": "^2.0.7",
|
|
51
64
|
"tailwind-merge": "^3.5.0",
|
|
52
65
|
"tw-animate-css": "^1.4.0"
|
|
53
66
|
},
|
|
@@ -60,6 +73,8 @@
|
|
|
60
73
|
"@types/react": "^19.2.14",
|
|
61
74
|
"@types/react-dom": "^19.2.3",
|
|
62
75
|
"@vitejs/plugin-react": "^6.0.1",
|
|
76
|
+
"electron": "^35.1.2",
|
|
77
|
+
"electron-builder": "^26.0.12",
|
|
63
78
|
"eslint": "^9.39.4",
|
|
64
79
|
"eslint-config-next": "16.1.7",
|
|
65
80
|
"jsdom": "^29.0.1",
|
|
@@ -16,12 +16,12 @@ function getMonthLabel(dateStr: string): string {
|
|
|
16
16
|
return new Date(dateStr + 'T00:00:00').toLocaleString('en-US', { month: 'short' })
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
function
|
|
20
|
-
if (ratio === 0)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
return
|
|
19
|
+
function intensityStyle(ratio: number): React.CSSProperties {
|
|
20
|
+
if (ratio === 0)
|
|
21
|
+
return { backgroundColor: 'var(--muted)' }
|
|
22
|
+
// Use chart-2 (teal) with increasing opacity for a clean gradient
|
|
23
|
+
const alpha = 0.15 + ratio * 0.85
|
|
24
|
+
return { backgroundColor: `oklch(0.55 0.15 170 / ${alpha})` }
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
const WEEKDAYS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
|
|
@@ -29,7 +29,7 @@ const WEEKDAYS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
|
|
|
29
29
|
// ─── Component ──────────────────────────────────────────────────────
|
|
30
30
|
|
|
31
31
|
export default function CostHeatmap({ data }: PluginProps) {
|
|
32
|
-
const { grid,
|
|
32
|
+
const { grid, weeks, monthLabels, stats } = useMemo(() => {
|
|
33
33
|
const dailyMap = new Map(data.daily.map((d) => [d.date, d.costUSD]))
|
|
34
34
|
const dates = data.daily.map((d) => d.date).sort()
|
|
35
35
|
|
|
@@ -92,7 +92,8 @@ export default function CostHeatmap({ data }: PluginProps) {
|
|
|
92
92
|
const costs = allDates.map((d) => dailyMap.get(d) || 0)
|
|
93
93
|
const total = costs.reduce((a, b) => a + b, 0)
|
|
94
94
|
const activeDays = costs.filter((c) => c > 0).length
|
|
95
|
-
const
|
|
95
|
+
const peakIdx = costs.indexOf(Math.max(...costs))
|
|
96
|
+
const peakDay = allDates[peakIdx]
|
|
96
97
|
|
|
97
98
|
return {
|
|
98
99
|
grid: g,
|
|
@@ -116,6 +117,9 @@ export default function CostHeatmap({ data }: PluginProps) {
|
|
|
116
117
|
)
|
|
117
118
|
}
|
|
118
119
|
|
|
120
|
+
const cellSize = 18
|
|
121
|
+
const cellGap = 3
|
|
122
|
+
|
|
119
123
|
return (
|
|
120
124
|
<div className="space-y-6">
|
|
121
125
|
{/* Stats row */}
|
|
@@ -159,64 +163,82 @@ export default function CostHeatmap({ data }: PluginProps) {
|
|
|
159
163
|
<CardContent>
|
|
160
164
|
<div className="overflow-x-auto">
|
|
161
165
|
{/* Month labels */}
|
|
162
|
-
<div className="
|
|
166
|
+
<div className="flex items-end" style={{ paddingLeft: 40, marginBottom: 6 }}>
|
|
163
167
|
{monthLabels.map((m, i) => (
|
|
164
168
|
<span
|
|
165
169
|
key={i}
|
|
166
|
-
className="text-xs text-muted-foreground"
|
|
170
|
+
className="text-xs font-medium text-muted-foreground"
|
|
167
171
|
style={{
|
|
168
|
-
position: '
|
|
169
|
-
left:
|
|
170
|
-
marginRight: i < monthLabels.length - 1 ? 0 : undefined,
|
|
172
|
+
position: 'absolute',
|
|
173
|
+
left: 40 + m.week * (cellSize + cellGap),
|
|
171
174
|
}}
|
|
172
175
|
>
|
|
173
176
|
{m.label}
|
|
174
177
|
</span>
|
|
175
178
|
))}
|
|
176
179
|
</div>
|
|
180
|
+
|
|
177
181
|
{/* Grid */}
|
|
178
|
-
<div className="
|
|
179
|
-
{
|
|
180
|
-
|
|
181
|
-
{
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
182
|
+
<div className="relative" style={{ paddingTop: 20 }}>
|
|
183
|
+
<div className="flex" style={{ gap: cellGap }}>
|
|
184
|
+
{/* Weekday labels */}
|
|
185
|
+
<div className="flex flex-col" style={{ gap: cellGap, width: 36 }}>
|
|
186
|
+
{WEEKDAYS.map((d, i) => (
|
|
187
|
+
<span
|
|
188
|
+
key={d}
|
|
189
|
+
className="flex items-center text-xs text-muted-foreground"
|
|
190
|
+
style={{ height: cellSize }}
|
|
191
|
+
>
|
|
192
|
+
{i % 2 === 1 ? d : ''}
|
|
193
|
+
</span>
|
|
194
|
+
))}
|
|
195
|
+
</div>
|
|
196
|
+
{/* Weeks */}
|
|
197
|
+
{Array.from({ length: weeks }, (_, weekIdx) => (
|
|
198
|
+
<div key={weekIdx} className="flex flex-col" style={{ gap: cellGap }}>
|
|
199
|
+
{Array.from({ length: 7 }, (_, dayIdx) => {
|
|
200
|
+
const cell = grid[dayIdx]?.[weekIdx]
|
|
201
|
+
if (!cell) {
|
|
202
|
+
return (
|
|
203
|
+
<div
|
|
204
|
+
key={dayIdx}
|
|
205
|
+
style={{ width: cellSize, height: cellSize }}
|
|
206
|
+
className="rounded-sm"
|
|
207
|
+
/>
|
|
208
|
+
)
|
|
209
|
+
}
|
|
210
|
+
return (
|
|
211
|
+
<Tooltip key={dayIdx}>
|
|
212
|
+
<TooltipTrigger
|
|
213
|
+
render={
|
|
214
|
+
<div
|
|
215
|
+
className="rounded-sm transition-all hover:ring-2 hover:ring-foreground/30 hover:scale-110 cursor-default"
|
|
216
|
+
style={{ width: cellSize, height: cellSize, ...intensityStyle(cell.ratio) }}
|
|
217
|
+
/>
|
|
218
|
+
}
|
|
219
|
+
/>
|
|
220
|
+
<TooltipContent side="top" className="text-center">
|
|
221
|
+
<p className="text-xs font-medium">{cell.date}</p>
|
|
222
|
+
<p className="text-sm font-bold">{formatCost(cell.cost)}</p>
|
|
223
|
+
</TooltipContent>
|
|
224
|
+
</Tooltip>
|
|
225
|
+
)
|
|
226
|
+
})}
|
|
227
|
+
</div>
|
|
188
228
|
))}
|
|
189
229
|
</div>
|
|
190
|
-
{/* Weeks */}
|
|
191
|
-
{Array.from({ length: weeks }, (_, weekIdx) => (
|
|
192
|
-
<div key={weekIdx} className="flex flex-col gap-0.5">
|
|
193
|
-
{Array.from({ length: 7 }, (_, dayIdx) => {
|
|
194
|
-
const cell = grid[dayIdx]?.[weekIdx]
|
|
195
|
-
if (!cell) {
|
|
196
|
-
return <div key={dayIdx} className="h-3 w-3 rounded-sm" />
|
|
197
|
-
}
|
|
198
|
-
return (
|
|
199
|
-
<Tooltip key={dayIdx}>
|
|
200
|
-
<TooltipTrigger render={<div className={`h-3 w-3 rounded-sm ${intensityClass(cell.ratio)} transition-colors hover:ring-1 hover:ring-foreground`} />}>
|
|
201
|
-
</TooltipTrigger>
|
|
202
|
-
<TooltipContent>
|
|
203
|
-
<p className="text-xs font-medium">{cell.date}</p>
|
|
204
|
-
<p className="text-xs text-muted-foreground">{formatCost(cell.cost)}</p>
|
|
205
|
-
</TooltipContent>
|
|
206
|
-
</Tooltip>
|
|
207
|
-
)
|
|
208
|
-
})}
|
|
209
|
-
</div>
|
|
210
|
-
))}
|
|
211
230
|
</div>
|
|
231
|
+
|
|
212
232
|
{/* Legend */}
|
|
213
|
-
<div className="mt-
|
|
233
|
+
<div className="mt-4 flex items-center gap-1.5 text-xs text-muted-foreground">
|
|
214
234
|
<span>Less</span>
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
235
|
+
{[0, 0.2, 0.4, 0.6, 0.8, 1].map((ratio) => (
|
|
236
|
+
<div
|
|
237
|
+
key={ratio}
|
|
238
|
+
className="rounded-sm"
|
|
239
|
+
style={{ width: cellSize - 4, height: cellSize - 4, ...intensityStyle(ratio) }}
|
|
240
|
+
/>
|
|
241
|
+
))}
|
|
220
242
|
<span>More</span>
|
|
221
243
|
</div>
|
|
222
244
|
</div>
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
-- CreateTable
|
|
2
|
+
CREATE TABLE "Session" (
|
|
3
|
+
"id" TEXT NOT NULL PRIMARY KEY,
|
|
4
|
+
"sessionId" TEXT NOT NULL,
|
|
5
|
+
"project" TEXT NOT NULL,
|
|
6
|
+
"projectPath" TEXT NOT NULL,
|
|
7
|
+
"startTime" DATETIME NOT NULL,
|
|
8
|
+
"endTime" DATETIME NOT NULL,
|
|
9
|
+
"durationMinutes" REAL NOT NULL,
|
|
10
|
+
"userMessages" INTEGER NOT NULL,
|
|
11
|
+
"assistantMessages" INTEGER NOT NULL,
|
|
12
|
+
"totalMessages" INTEGER NOT NULL,
|
|
13
|
+
"inputTokens" INTEGER NOT NULL,
|
|
14
|
+
"outputTokens" INTEGER NOT NULL,
|
|
15
|
+
"cacheCreationTokens" INTEGER NOT NULL,
|
|
16
|
+
"cacheReadTokens" INTEGER NOT NULL,
|
|
17
|
+
"totalTokens" INTEGER NOT NULL,
|
|
18
|
+
"costUSD" REAL NOT NULL,
|
|
19
|
+
"model" TEXT NOT NULL,
|
|
20
|
+
"toolCallsTotal" INTEGER NOT NULL,
|
|
21
|
+
"toolCallsJson" TEXT NOT NULL,
|
|
22
|
+
"skillCallsJson" TEXT NOT NULL DEFAULT '{}',
|
|
23
|
+
"messageTimestamps" TEXT NOT NULL DEFAULT '[]',
|
|
24
|
+
"apiErrors" INTEGER NOT NULL DEFAULT 0,
|
|
25
|
+
"rateLimitErrors" INTEGER NOT NULL DEFAULT 0,
|
|
26
|
+
"userInterruptions" INTEGER NOT NULL DEFAULT 0,
|
|
27
|
+
"permissionModesJson" TEXT NOT NULL DEFAULT '{}',
|
|
28
|
+
"systemPromptEdits" INTEGER NOT NULL DEFAULT 0,
|
|
29
|
+
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
-- CreateTable
|
|
33
|
+
CREATE TABLE "Image" (
|
|
34
|
+
"id" TEXT NOT NULL PRIMARY KEY,
|
|
35
|
+
"sessionId" TEXT NOT NULL,
|
|
36
|
+
"messageId" TEXT NOT NULL,
|
|
37
|
+
"filename" TEXT NOT NULL,
|
|
38
|
+
"mediaType" TEXT NOT NULL,
|
|
39
|
+
"sizeBytes" INTEGER NOT NULL,
|
|
40
|
+
"timestamp" DATETIME NOT NULL,
|
|
41
|
+
"role" TEXT NOT NULL,
|
|
42
|
+
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
-- CreateTable
|
|
46
|
+
CREATE TABLE "SyncLog" (
|
|
47
|
+
"id" TEXT NOT NULL PRIMARY KEY,
|
|
48
|
+
"syncedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
49
|
+
"filesProcessed" INTEGER NOT NULL,
|
|
50
|
+
"sessionsAdded" INTEGER NOT NULL,
|
|
51
|
+
"sessionsSkipped" INTEGER NOT NULL
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
-- CreateTable
|
|
55
|
+
CREATE TABLE "Report" (
|
|
56
|
+
"id" TEXT NOT NULL PRIMARY KEY,
|
|
57
|
+
"title" TEXT NOT NULL,
|
|
58
|
+
"generatedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
59
|
+
"contentJson" TEXT NOT NULL,
|
|
60
|
+
"sessionCount" INTEGER NOT NULL,
|
|
61
|
+
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
-- CreateIndex
|
|
65
|
+
CREATE UNIQUE INDEX "Session_sessionId_key" ON "Session"("sessionId");
|
|
66
|
+
|
|
67
|
+
-- CreateIndex
|
|
68
|
+
CREATE INDEX "Session_project_idx" ON "Session"("project");
|
|
69
|
+
|
|
70
|
+
-- CreateIndex
|
|
71
|
+
CREATE INDEX "Session_startTime_idx" ON "Session"("startTime");
|
|
72
|
+
|
|
73
|
+
-- CreateIndex
|
|
74
|
+
CREATE INDEX "Image_sessionId_idx" ON "Image"("sessionId");
|
|
75
|
+
|
|
76
|
+
-- CreateIndex
|
|
77
|
+
CREATE UNIQUE INDEX "Image_sessionId_messageId_filename_key" ON "Image"("sessionId", "messageId", "filename");
|
|
78
|
+
|
|
79
|
+
-- CreateIndex
|
|
80
|
+
CREATE INDEX "Report_generatedAt_idx" ON "Report"("generatedAt");
|
package/prisma/schema.prisma
CHANGED
|
@@ -27,6 +27,13 @@ model Session {
|
|
|
27
27
|
model String
|
|
28
28
|
toolCallsTotal Int
|
|
29
29
|
toolCallsJson String // JSON string of Record<string, number>
|
|
30
|
+
skillCallsJson String @default("{}") // JSON string of Record<string, number>
|
|
31
|
+
messageTimestamps String @default("[]") // JSON array of ISO timestamp strings
|
|
32
|
+
apiErrors Int @default(0)
|
|
33
|
+
rateLimitErrors Int @default(0)
|
|
34
|
+
userInterruptions Int @default(0)
|
|
35
|
+
permissionModesJson String @default("{}") // JSON: {default:N, acceptEdits:N, bypassPermissions:N, plan:N}
|
|
36
|
+
systemPromptEdits Int @default(0) // Edits/writes to CLAUDE.md, AGENTS.md, agent.md
|
|
30
37
|
createdAt DateTime @default(now())
|
|
31
38
|
|
|
32
39
|
@@index([project])
|
|
@@ -55,3 +62,14 @@ model SyncLog {
|
|
|
55
62
|
sessionsAdded Int
|
|
56
63
|
sessionsSkipped Int
|
|
57
64
|
}
|
|
65
|
+
|
|
66
|
+
model Report {
|
|
67
|
+
id String @id @default(cuid())
|
|
68
|
+
title String
|
|
69
|
+
generatedAt DateTime @default(now())
|
|
70
|
+
contentJson String
|
|
71
|
+
sessionCount Int
|
|
72
|
+
createdAt DateTime @default(now())
|
|
73
|
+
|
|
74
|
+
@@index([generatedAt])
|
|
75
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
-- CreateTable
|
|
2
|
+
CREATE TABLE IF NOT EXISTS "Session" (
|
|
3
|
+
"id" TEXT NOT NULL PRIMARY KEY,
|
|
4
|
+
"sessionId" TEXT NOT NULL,
|
|
5
|
+
"project" TEXT NOT NULL,
|
|
6
|
+
"projectPath" TEXT NOT NULL,
|
|
7
|
+
"startTime" DATETIME NOT NULL,
|
|
8
|
+
"endTime" DATETIME NOT NULL,
|
|
9
|
+
"durationMinutes" REAL NOT NULL,
|
|
10
|
+
"userMessages" INTEGER NOT NULL,
|
|
11
|
+
"assistantMessages" INTEGER NOT NULL,
|
|
12
|
+
"totalMessages" INTEGER NOT NULL,
|
|
13
|
+
"inputTokens" INTEGER NOT NULL,
|
|
14
|
+
"outputTokens" INTEGER NOT NULL,
|
|
15
|
+
"cacheCreationTokens" INTEGER NOT NULL,
|
|
16
|
+
"cacheReadTokens" INTEGER NOT NULL,
|
|
17
|
+
"totalTokens" INTEGER NOT NULL,
|
|
18
|
+
"costUSD" REAL NOT NULL,
|
|
19
|
+
"model" TEXT NOT NULL,
|
|
20
|
+
"toolCallsTotal" INTEGER NOT NULL,
|
|
21
|
+
"toolCallsJson" TEXT NOT NULL,
|
|
22
|
+
"skillCallsJson" TEXT NOT NULL DEFAULT '{}',
|
|
23
|
+
"messageTimestamps" TEXT NOT NULL DEFAULT '[]',
|
|
24
|
+
"apiErrors" INTEGER NOT NULL DEFAULT 0,
|
|
25
|
+
"rateLimitErrors" INTEGER NOT NULL DEFAULT 0,
|
|
26
|
+
"userInterruptions" INTEGER NOT NULL DEFAULT 0,
|
|
27
|
+
"permissionModesJson" TEXT NOT NULL DEFAULT '{}',
|
|
28
|
+
"systemPromptEdits" INTEGER NOT NULL DEFAULT 0,
|
|
29
|
+
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
-- CreateTable
|
|
33
|
+
CREATE TABLE IF NOT EXISTS "Image" (
|
|
34
|
+
"id" TEXT NOT NULL PRIMARY KEY,
|
|
35
|
+
"sessionId" TEXT NOT NULL,
|
|
36
|
+
"messageId" TEXT NOT NULL,
|
|
37
|
+
"filename" TEXT NOT NULL,
|
|
38
|
+
"mediaType" TEXT NOT NULL,
|
|
39
|
+
"sizeBytes" INTEGER NOT NULL,
|
|
40
|
+
"timestamp" DATETIME NOT NULL,
|
|
41
|
+
"role" TEXT NOT NULL,
|
|
42
|
+
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
-- CreateTable
|
|
46
|
+
CREATE TABLE IF NOT EXISTS "SyncLog" (
|
|
47
|
+
"id" TEXT NOT NULL PRIMARY KEY,
|
|
48
|
+
"syncedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
49
|
+
"filesProcessed" INTEGER NOT NULL,
|
|
50
|
+
"sessionsAdded" INTEGER NOT NULL,
|
|
51
|
+
"sessionsSkipped" INTEGER NOT NULL
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
-- CreateTable
|
|
55
|
+
CREATE TABLE IF NOT EXISTS "Report" (
|
|
56
|
+
"id" TEXT NOT NULL PRIMARY KEY,
|
|
57
|
+
"title" TEXT NOT NULL,
|
|
58
|
+
"generatedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
59
|
+
"contentJson" TEXT NOT NULL,
|
|
60
|
+
"sessionCount" INTEGER NOT NULL,
|
|
61
|
+
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
-- CreateIndex
|
|
65
|
+
CREATE UNIQUE INDEX IF NOT EXISTS "Session_sessionId_key" ON "Session"("sessionId");
|
|
66
|
+
|
|
67
|
+
-- CreateIndex
|
|
68
|
+
CREATE INDEX IF NOT EXISTS "Session_project_idx" ON "Session"("project");
|
|
69
|
+
|
|
70
|
+
-- CreateIndex
|
|
71
|
+
CREATE INDEX IF NOT EXISTS "Session_startTime_idx" ON "Session"("startTime");
|
|
72
|
+
|
|
73
|
+
-- CreateIndex
|
|
74
|
+
CREATE INDEX IF NOT EXISTS "Image_sessionId_idx" ON "Image"("sessionId");
|
|
75
|
+
|
|
76
|
+
-- CreateIndex
|
|
77
|
+
CREATE UNIQUE INDEX IF NOT EXISTS "Image_sessionId_messageId_filename_key" ON "Image"("sessionId", "messageId", "filename");
|
|
78
|
+
|
|
79
|
+
-- CreateIndex
|
|
80
|
+
CREATE INDEX IF NOT EXISTS "Report_generatedAt_idx" ON "Report"("generatedAt");
|
|
81
|
+
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"permissions": {
|
|
3
|
-
"allow": [
|
|
4
|
-
"Bash(python3 -m json.tool)",
|
|
5
|
-
"Bash(python3:*)",
|
|
6
|
-
"Bash(head -5 /Users/harrywang/sandbox/bizpub-cc/logs/*.jsonl)",
|
|
7
|
-
"Bash(wc -l /Users/harrywang/sandbox/bizpub-cc/logs/*.jsonl)",
|
|
8
|
-
"Read(//private/tmp/**)",
|
|
9
|
-
"Bash(rm -rf agentfit-scaffold)",
|
|
10
|
-
"Bash(npx create-next-app@latest agentfit-scaffold --typescript --tailwind --eslint --app --src-dir --no-import-alias --use-npm --no-turbopack)",
|
|
11
|
-
"Bash(curl -s https://api.github.com/repos/ryoppippi/ccusage/contents)",
|
|
12
|
-
"Bash(curl -s \"https://api.github.com/search/code?q=repo:ryoppippi/ccusage+filename:model\")",
|
|
13
|
-
"Bash(curl:*)",
|
|
14
|
-
"WebFetch(domain:raw.githubusercontent.com)",
|
|
15
|
-
"Bash(lsof -ti:3000,3001)",
|
|
16
|
-
"Bash(npm install:*)",
|
|
17
|
-
"Bash(npx prisma:*)",
|
|
18
|
-
"Bash(ls -la /Users/harrywang/sandbox/agentfit/*.md)",
|
|
19
|
-
"WebSearch",
|
|
20
|
-
"WebFetch(domain:betelgeuse.work)",
|
|
21
|
-
"WebFetch(domain:github.com)",
|
|
22
|
-
"WebFetch(domain:ui.shadcn.com)",
|
|
23
|
-
"Bash(npx shadcn@latest init -p blue --force)"
|
|
24
|
-
]
|
|
25
|
-
}
|
|
26
|
-
}
|