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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { useState } from 'react'
|
|
3
|
+
import { useState, type ReactNode } from 'react'
|
|
4
4
|
import Link from 'next/link'
|
|
5
5
|
import { usePathname } from 'next/navigation'
|
|
6
6
|
import {
|
|
@@ -9,11 +9,14 @@ import {
|
|
|
9
9
|
Camera,
|
|
10
10
|
ChevronRight,
|
|
11
11
|
Coins,
|
|
12
|
+
FileText,
|
|
12
13
|
FolderOpen,
|
|
14
|
+
GitBranch,
|
|
13
15
|
HeartPulse,
|
|
14
16
|
LayoutDashboard,
|
|
15
17
|
ListTree,
|
|
16
18
|
Puzzle,
|
|
19
|
+
Settings,
|
|
17
20
|
Terminal,
|
|
18
21
|
Wrench,
|
|
19
22
|
} from 'lucide-react'
|
|
@@ -24,6 +27,7 @@ import {
|
|
|
24
27
|
SidebarGroup,
|
|
25
28
|
SidebarGroupContent,
|
|
26
29
|
SidebarGroupLabel,
|
|
30
|
+
SidebarFooter,
|
|
27
31
|
SidebarHeader,
|
|
28
32
|
SidebarMenu,
|
|
29
33
|
SidebarMenuButton,
|
|
@@ -37,7 +41,7 @@ import { getPlugins } from '@/lib/plugins'
|
|
|
37
41
|
import '@/plugins'
|
|
38
42
|
|
|
39
43
|
interface NavItem {
|
|
40
|
-
title:
|
|
44
|
+
title: ReactNode
|
|
41
45
|
icon: ComponentType<{ className?: string }>
|
|
42
46
|
href: string
|
|
43
47
|
}
|
|
@@ -50,11 +54,18 @@ interface NavGroup {
|
|
|
50
54
|
|
|
51
55
|
const topItems: NavItem[] = [
|
|
52
56
|
{ title: 'Dashboard', icon: LayoutDashboard, href: '/' },
|
|
53
|
-
{ title: '
|
|
54
|
-
{ title: 'Projects', icon: FolderOpen, href: '/projects' },
|
|
57
|
+
{ title: 'CRAFT Coach', icon: HeartPulse, href: '/coach' },
|
|
55
58
|
]
|
|
56
59
|
|
|
57
60
|
const navGroups: NavGroup[] = [
|
|
61
|
+
{
|
|
62
|
+
title: 'Projects',
|
|
63
|
+
icon: FolderOpen,
|
|
64
|
+
items: [
|
|
65
|
+
{ title: 'Overview', icon: FolderOpen, href: '/projects' },
|
|
66
|
+
{ title: 'Sessions', icon: ListTree, href: '/sessions' },
|
|
67
|
+
],
|
|
68
|
+
},
|
|
58
69
|
{
|
|
59
70
|
title: 'Usage',
|
|
60
71
|
icon: BarChart3,
|
|
@@ -62,6 +73,7 @@ const navGroups: NavGroup[] = [
|
|
|
62
73
|
{ title: 'Daily Usage', icon: BarChart3, href: '/daily' },
|
|
63
74
|
{ title: 'Token Breakdown', icon: Coins, href: '/tokens' },
|
|
64
75
|
{ title: 'Tool Usage', icon: Wrench, href: '/tools' },
|
|
76
|
+
{ title: 'Command Usage', icon: Terminal, href: '/commands' },
|
|
65
77
|
],
|
|
66
78
|
},
|
|
67
79
|
{
|
|
@@ -69,8 +81,9 @@ const navGroups: NavGroup[] = [
|
|
|
69
81
|
icon: Brain,
|
|
70
82
|
items: [
|
|
71
83
|
{ title: 'Personality Fit', icon: Brain, href: '/personality' },
|
|
72
|
-
{ title: '
|
|
73
|
-
{ title: '
|
|
84
|
+
{ title: 'Session Flow', icon: GitBranch, href: '/flow' },
|
|
85
|
+
{ title: 'Image Analysis', icon: Camera, href: '/images' },
|
|
86
|
+
{ title: 'Reports', icon: FileText, href: '/reports' },
|
|
74
87
|
],
|
|
75
88
|
},
|
|
76
89
|
]
|
|
@@ -115,10 +128,7 @@ export function AppSidebar() {
|
|
|
115
128
|
<SidebarHeader>
|
|
116
129
|
<div className="flex items-center gap-2 px-2 py-2">
|
|
117
130
|
<img src="/logo.svg" alt="AgentFit" className="h-8 w-8" />
|
|
118
|
-
<div>
|
|
119
|
-
<div className="text-sm font-semibold">AgentFit</div>
|
|
120
|
-
<div className="text-xs text-muted-foreground">Coding Agent Fitness Tracker</div>
|
|
121
|
-
</div>
|
|
131
|
+
<div className="text-sm font-semibold">AgentFit</div>
|
|
122
132
|
</div>
|
|
123
133
|
</SidebarHeader>
|
|
124
134
|
<SidebarContent>
|
|
@@ -151,10 +161,35 @@ export function AppSidebar() {
|
|
|
151
161
|
</SidebarMenuButton>
|
|
152
162
|
</SidebarMenuItem>
|
|
153
163
|
)}
|
|
164
|
+
<SidebarMenuItem>
|
|
165
|
+
<SidebarMenuButton
|
|
166
|
+
render={<Link href="/data-management" />}
|
|
167
|
+
isActive={pathname === '/data-management'}
|
|
168
|
+
>
|
|
169
|
+
<Settings className="h-4 w-4" />
|
|
170
|
+
<span>Data Management</span>
|
|
171
|
+
</SidebarMenuButton>
|
|
172
|
+
</SidebarMenuItem>
|
|
154
173
|
</SidebarMenu>
|
|
155
174
|
</SidebarGroupContent>
|
|
156
175
|
</SidebarGroup>
|
|
157
176
|
</SidebarContent>
|
|
177
|
+
|
|
178
|
+
<SidebarFooter>
|
|
179
|
+
<div className="flex items-center justify-center px-3 py-2 text-xs text-muted-foreground">
|
|
180
|
+
<a
|
|
181
|
+
href="https://github.com/harrywang/agentfit"
|
|
182
|
+
target="_blank"
|
|
183
|
+
rel="noopener noreferrer"
|
|
184
|
+
className="flex items-center gap-1.5 hover:text-foreground transition-colors"
|
|
185
|
+
>
|
|
186
|
+
<svg className="h-4 w-4" viewBox="0 0 24 24" fill="currentColor">
|
|
187
|
+
<path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z" />
|
|
188
|
+
</svg>
|
|
189
|
+
GitHub
|
|
190
|
+
</a>
|
|
191
|
+
</div>
|
|
192
|
+
</SidebarFooter>
|
|
158
193
|
<SidebarRail />
|
|
159
194
|
</Sidebar>
|
|
160
195
|
)
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect, useCallback } from 'react'
|
|
4
|
+
import { toast } from 'sonner'
|
|
5
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
|
6
|
+
import { Button } from '@/components/ui/button'
|
|
7
|
+
import { Badge } from '@/components/ui/badge'
|
|
8
|
+
import {
|
|
9
|
+
Cloud,
|
|
10
|
+
CloudOff,
|
|
11
|
+
Check,
|
|
12
|
+
Loader2,
|
|
13
|
+
Upload,
|
|
14
|
+
} from 'lucide-react'
|
|
15
|
+
|
|
16
|
+
interface FolderStatus {
|
|
17
|
+
exists: boolean
|
|
18
|
+
hasGit: boolean
|
|
19
|
+
hasRemote: boolean
|
|
20
|
+
isDirty: boolean
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface BackupStatus {
|
|
24
|
+
ghInstalled: boolean
|
|
25
|
+
ghAuthenticated: boolean
|
|
26
|
+
ghUser: string
|
|
27
|
+
claude: FolderStatus
|
|
28
|
+
codex: FolderStatus
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function BackupCard() {
|
|
32
|
+
const [status, setStatus] = useState<BackupStatus | null>(null)
|
|
33
|
+
const [loading, setLoading] = useState(false)
|
|
34
|
+
const [syncing, setSyncing] = useState<Record<string, boolean>>({})
|
|
35
|
+
|
|
36
|
+
const fetchStatus = useCallback(async () => {
|
|
37
|
+
setLoading(true)
|
|
38
|
+
try {
|
|
39
|
+
const res = await fetch('/api/backup')
|
|
40
|
+
setStatus(await res.json())
|
|
41
|
+
} catch {
|
|
42
|
+
// ignore
|
|
43
|
+
} finally {
|
|
44
|
+
setLoading(false)
|
|
45
|
+
}
|
|
46
|
+
}, [])
|
|
47
|
+
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
fetchStatus()
|
|
50
|
+
}, [fetchStatus])
|
|
51
|
+
|
|
52
|
+
const handleInit = async (folder: 'claude' | 'codex') => {
|
|
53
|
+
setSyncing((s) => ({ ...s, [folder]: true }))
|
|
54
|
+
try {
|
|
55
|
+
const res = await fetch('/api/backup', {
|
|
56
|
+
method: 'POST',
|
|
57
|
+
headers: { 'Content-Type': 'application/json' },
|
|
58
|
+
body: JSON.stringify({ action: 'init', folder }),
|
|
59
|
+
})
|
|
60
|
+
const data = await res.json()
|
|
61
|
+
if (!res.ok) {
|
|
62
|
+
toast.error('Backup failed', { description: data.error })
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
toast.success(`~/.${folder} backed up`, {
|
|
66
|
+
description: (
|
|
67
|
+
<a href={data.repoUrl} target="_blank" rel="noopener noreferrer" className="underline">
|
|
68
|
+
{data.repoUrl}
|
|
69
|
+
</a>
|
|
70
|
+
),
|
|
71
|
+
})
|
|
72
|
+
await fetchStatus()
|
|
73
|
+
} catch (e) {
|
|
74
|
+
toast.error('Backup failed', { description: (e as Error).message })
|
|
75
|
+
} finally {
|
|
76
|
+
setSyncing((s) => ({ ...s, [folder]: false }))
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const handleSync = async (folder: 'claude' | 'codex') => {
|
|
81
|
+
setSyncing((s) => ({ ...s, [folder]: true }))
|
|
82
|
+
try {
|
|
83
|
+
const res = await fetch('/api/backup', {
|
|
84
|
+
method: 'POST',
|
|
85
|
+
headers: { 'Content-Type': 'application/json' },
|
|
86
|
+
body: JSON.stringify({ action: 'sync', folder }),
|
|
87
|
+
})
|
|
88
|
+
const data = await res.json()
|
|
89
|
+
if (!res.ok) {
|
|
90
|
+
toast.error('Sync failed', { description: data.error })
|
|
91
|
+
return
|
|
92
|
+
}
|
|
93
|
+
toast.success(`~/.${folder} synced`, { description: data.message })
|
|
94
|
+
await fetchStatus()
|
|
95
|
+
} catch (e) {
|
|
96
|
+
toast.error('Sync failed', { description: (e as Error).message })
|
|
97
|
+
} finally {
|
|
98
|
+
setSyncing((s) => ({ ...s, [folder]: false }))
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (loading) {
|
|
103
|
+
return (
|
|
104
|
+
<Card>
|
|
105
|
+
<CardHeader>
|
|
106
|
+
<div className="flex items-center gap-2">
|
|
107
|
+
<Cloud className="h-5 w-5 text-muted-foreground" />
|
|
108
|
+
<div>
|
|
109
|
+
<CardTitle>GitHub Backup</CardTitle>
|
|
110
|
+
<CardDescription>Checking GitHub status...</CardDescription>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
</CardHeader>
|
|
114
|
+
<CardContent>
|
|
115
|
+
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
|
116
|
+
<Loader2 className="h-4 w-4 animate-spin" />
|
|
117
|
+
Loading...
|
|
118
|
+
</div>
|
|
119
|
+
</CardContent>
|
|
120
|
+
</Card>
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (!status || !status.ghInstalled) {
|
|
125
|
+
return (
|
|
126
|
+
<Card>
|
|
127
|
+
<CardHeader>
|
|
128
|
+
<div className="flex items-center gap-2">
|
|
129
|
+
<CloudOff className="h-5 w-5 text-muted-foreground" />
|
|
130
|
+
<div>
|
|
131
|
+
<CardTitle>GitHub Backup</CardTitle>
|
|
132
|
+
<CardDescription>Back up your agent logs to a private GitHub repo</CardDescription>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
</CardHeader>
|
|
136
|
+
<CardContent>
|
|
137
|
+
<div className="space-y-2 text-sm text-muted-foreground">
|
|
138
|
+
<p>GitHub CLI is not installed.</p>
|
|
139
|
+
<a href="https://cli.github.com" target="_blank" rel="noopener noreferrer" className="text-primary underline">
|
|
140
|
+
Install GitHub CLI
|
|
141
|
+
</a>
|
|
142
|
+
</div>
|
|
143
|
+
</CardContent>
|
|
144
|
+
</Card>
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (!status.ghAuthenticated) {
|
|
149
|
+
return (
|
|
150
|
+
<Card>
|
|
151
|
+
<CardHeader>
|
|
152
|
+
<div className="flex items-center gap-2">
|
|
153
|
+
<CloudOff className="h-5 w-5 text-muted-foreground" />
|
|
154
|
+
<div>
|
|
155
|
+
<CardTitle>GitHub Backup</CardTitle>
|
|
156
|
+
<CardDescription>Back up your agent logs to a private GitHub repo</CardDescription>
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
</CardHeader>
|
|
160
|
+
<CardContent>
|
|
161
|
+
<div className="space-y-2 text-sm text-muted-foreground">
|
|
162
|
+
<p>Not logged in to GitHub.</p>
|
|
163
|
+
<code className="block rounded bg-muted px-2 py-1.5 text-xs font-mono">gh auth login</code>
|
|
164
|
+
</div>
|
|
165
|
+
</CardContent>
|
|
166
|
+
</Card>
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const folders = [
|
|
171
|
+
{ key: 'claude' as const, label: '~/.claude', status: status.claude },
|
|
172
|
+
{ key: 'codex' as const, label: '~/.codex', status: status.codex },
|
|
173
|
+
].filter((f) => f.status.exists)
|
|
174
|
+
|
|
175
|
+
return (
|
|
176
|
+
<Card>
|
|
177
|
+
<CardHeader>
|
|
178
|
+
<div className="flex items-center gap-2">
|
|
179
|
+
<Cloud className="h-5 w-5 text-muted-foreground" />
|
|
180
|
+
<div>
|
|
181
|
+
<CardTitle>GitHub Backup</CardTitle>
|
|
182
|
+
<CardDescription>
|
|
183
|
+
Back up agent logs to private GitHub repos — logged in as <strong>{status.ghUser}</strong>
|
|
184
|
+
</CardDescription>
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
</CardHeader>
|
|
188
|
+
<CardContent className="space-y-3">
|
|
189
|
+
{folders.map(({ key, label, status: fs }) => {
|
|
190
|
+
const isSetUp = fs.hasGit && fs.hasRemote
|
|
191
|
+
const isSyncing = syncing[key]
|
|
192
|
+
|
|
193
|
+
return (
|
|
194
|
+
<div key={key} className="flex items-center justify-between rounded-lg border p-4">
|
|
195
|
+
<div className="flex items-center gap-3">
|
|
196
|
+
<code className="text-sm font-medium">{label}</code>
|
|
197
|
+
{isSetUp && !fs.isDirty && (
|
|
198
|
+
<Check className="h-4 w-4 text-green-500" />
|
|
199
|
+
)}
|
|
200
|
+
{isSetUp && fs.isDirty && (
|
|
201
|
+
<Badge variant="secondary" className="text-xs">new changes</Badge>
|
|
202
|
+
)}
|
|
203
|
+
</div>
|
|
204
|
+
{!isSetUp ? (
|
|
205
|
+
<Button
|
|
206
|
+
variant="outline"
|
|
207
|
+
size="sm"
|
|
208
|
+
onClick={() => handleInit(key)}
|
|
209
|
+
disabled={isSyncing}
|
|
210
|
+
className="gap-2"
|
|
211
|
+
>
|
|
212
|
+
{isSyncing ? <Loader2 className="h-4 w-4 animate-spin" /> : <Upload className="h-4 w-4" />}
|
|
213
|
+
Backup
|
|
214
|
+
</Button>
|
|
215
|
+
) : (
|
|
216
|
+
<Button
|
|
217
|
+
variant="outline"
|
|
218
|
+
size="sm"
|
|
219
|
+
onClick={() => handleSync(key)}
|
|
220
|
+
disabled={isSyncing}
|
|
221
|
+
className="gap-2"
|
|
222
|
+
>
|
|
223
|
+
{isSyncing ? <Loader2 className="h-4 w-4 animate-spin" /> : <Cloud className="h-4 w-4" />}
|
|
224
|
+
Sync
|
|
225
|
+
</Button>
|
|
226
|
+
)}
|
|
227
|
+
</div>
|
|
228
|
+
)
|
|
229
|
+
})}
|
|
230
|
+
{folders.length === 0 && (
|
|
231
|
+
<p className="text-sm text-muted-foreground">No agent log directories found.</p>
|
|
232
|
+
)}
|
|
233
|
+
</CardContent>
|
|
234
|
+
</Card>
|
|
235
|
+
)
|
|
236
|
+
}
|