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
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- 'v*'
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: write
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
build-mac:
|
|
13
|
+
runs-on: macos-latest
|
|
14
|
+
timeout-minutes: 30
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v6
|
|
17
|
+
|
|
18
|
+
- uses: actions/setup-node@v6
|
|
19
|
+
with:
|
|
20
|
+
node-version: 22
|
|
21
|
+
cache: npm
|
|
22
|
+
|
|
23
|
+
- name: Install dependencies
|
|
24
|
+
run: npm ci
|
|
25
|
+
|
|
26
|
+
- name: Import signing certificate
|
|
27
|
+
env:
|
|
28
|
+
CERTIFICATE_P12: ${{ secrets.CERTIFICATE_P12 }}
|
|
29
|
+
CERTIFICATE_PASSWORD: ${{ secrets.CERTIFICATE_PASSWORD }}
|
|
30
|
+
run: |
|
|
31
|
+
echo "$CERTIFICATE_P12" | base64 --decode > certificate.p12
|
|
32
|
+
security create-keychain -p actions build.keychain
|
|
33
|
+
security default-keychain -s build.keychain
|
|
34
|
+
security unlock-keychain -p actions build.keychain
|
|
35
|
+
security import certificate.p12 -k build.keychain -P "$CERTIFICATE_PASSWORD" -T /usr/bin/codesign
|
|
36
|
+
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k actions build.keychain
|
|
37
|
+
rm certificate.p12
|
|
38
|
+
|
|
39
|
+
- name: Build macOS DMGs
|
|
40
|
+
run: npm run electron:build:mac
|
|
41
|
+
|
|
42
|
+
- name: Upload DMGs
|
|
43
|
+
uses: actions/upload-artifact@v7
|
|
44
|
+
with:
|
|
45
|
+
name: mac-dmgs
|
|
46
|
+
path: |
|
|
47
|
+
dist-electron/*.dmg
|
|
48
|
+
|
|
49
|
+
build-win:
|
|
50
|
+
runs-on: windows-latest
|
|
51
|
+
steps:
|
|
52
|
+
- uses: actions/checkout@v6
|
|
53
|
+
|
|
54
|
+
- uses: actions/setup-node@v6
|
|
55
|
+
with:
|
|
56
|
+
node-version: 22
|
|
57
|
+
cache: npm
|
|
58
|
+
|
|
59
|
+
- name: Install dependencies
|
|
60
|
+
run: npm ci
|
|
61
|
+
|
|
62
|
+
- name: Build Windows installer
|
|
63
|
+
run: npm run electron:build:win
|
|
64
|
+
|
|
65
|
+
- name: Upload exe
|
|
66
|
+
uses: actions/upload-artifact@v7
|
|
67
|
+
with:
|
|
68
|
+
name: win-exe
|
|
69
|
+
path: |
|
|
70
|
+
dist-electron/*.exe
|
|
71
|
+
|
|
72
|
+
publish-npm:
|
|
73
|
+
runs-on: ubuntu-latest
|
|
74
|
+
steps:
|
|
75
|
+
- uses: actions/checkout@v6
|
|
76
|
+
|
|
77
|
+
- uses: actions/setup-node@v6
|
|
78
|
+
with:
|
|
79
|
+
node-version: 22
|
|
80
|
+
registry-url: https://registry.npmjs.org
|
|
81
|
+
|
|
82
|
+
- name: Install dependencies
|
|
83
|
+
run: npm ci
|
|
84
|
+
|
|
85
|
+
- name: Build
|
|
86
|
+
run: npm run build
|
|
87
|
+
|
|
88
|
+
- name: Publish to npm
|
|
89
|
+
run: npm publish
|
|
90
|
+
env:
|
|
91
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
92
|
+
|
|
93
|
+
release:
|
|
94
|
+
needs: [build-mac, build-win]
|
|
95
|
+
runs-on: ubuntu-latest
|
|
96
|
+
steps:
|
|
97
|
+
- uses: actions/download-artifact@v8
|
|
98
|
+
with:
|
|
99
|
+
name: mac-dmgs
|
|
100
|
+
path: artifacts
|
|
101
|
+
|
|
102
|
+
- uses: actions/download-artifact@v8
|
|
103
|
+
with:
|
|
104
|
+
name: win-exe
|
|
105
|
+
path: artifacts
|
|
106
|
+
|
|
107
|
+
- name: Create GitHub Release
|
|
108
|
+
uses: softprops/action-gh-release@v2
|
|
109
|
+
with:
|
|
110
|
+
files: artifacts/*
|
|
111
|
+
generate_release_notes: true
|
package/README.md
CHANGED
|
@@ -4,64 +4,79 @@ Fitness tracker dashboard for AI coding agents. Reads your local Claude Code and
|
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
|
-
### Option 1:
|
|
7
|
+
### Option 1: Desktop App (recommended)
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
curl -fsSL https://raw.githubusercontent.com/harrywang/agentfit/main/setup.sh | bash
|
|
11
|
-
```
|
|
9
|
+
Download a pre-built installer from the [Releases](https://github.com/harrywang/agentfit/releases) page:
|
|
12
10
|
|
|
13
|
-
|
|
11
|
+
- **macOS**: `AgentFit-x.x.x.dmg` (Intel) or `AgentFit-x.x.x-arm64.dmg` (Apple Silicon)
|
|
12
|
+
- **Windows**: `AgentFit-x.x.x.exe`
|
|
13
|
+
|
|
14
|
+
> **macOS note:** If you see "AgentFit Not Opened" on first launch, go to **System Settings > Privacy & Security**, scroll down, and click **Open Anyway**. Or run `xattr -cr /Applications/AgentFit.app` in Terminal.
|
|
15
|
+
|
|
16
|
+
### Option 2: One-liner
|
|
14
17
|
|
|
15
18
|
```bash
|
|
16
|
-
|
|
19
|
+
curl -fsSL https://raw.githubusercontent.com/harrywang/agentfit/main/setup.sh | bash
|
|
17
20
|
```
|
|
18
21
|
|
|
19
|
-
### Option
|
|
22
|
+
### Option 3: npx
|
|
20
23
|
|
|
21
24
|
```bash
|
|
22
25
|
npx agentfit
|
|
23
26
|
```
|
|
24
27
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
### Option 3: Manual
|
|
28
|
+
### Option 4: Manual
|
|
28
29
|
|
|
29
30
|
```bash
|
|
30
31
|
git clone https://github.com/harrywang/agentfit.git
|
|
31
32
|
cd agentfit
|
|
32
33
|
npm install
|
|
33
|
-
echo 'DATABASE_URL="file:./
|
|
34
|
+
echo 'DATABASE_URL="file:./agentfit.db"' > .env
|
|
34
35
|
npx prisma migrate deploy
|
|
35
36
|
npm run build
|
|
36
37
|
npm start
|
|
37
38
|
```
|
|
38
39
|
|
|
39
|
-
Open [http://localhost:3000](http://localhost:3000). The app auto-syncs your `~/.claude/projects/` logs on first load.
|
|
40
|
+
Open [http://localhost:3000](http://localhost:3000). The app auto-syncs your Claude Code (`~/.claude/projects/`) and Codex (`~/.codex/sessions/`) logs on first load.
|
|
41
|
+
|
|
42
|
+
**Requirements:** Node.js 20+ (Options 2–4)
|
|
43
|
+
|
|
44
|
+
## The CRAFT Framework
|
|
45
|
+
|
|
46
|
+
AgentFit scores your AI coding proficiency using **CRAFT** — a Human-AI coding proficiency framework by [Harry Wang](https://harrywang.me). All metrics are computed from your local conversation logs.
|
|
47
|
+
|
|
48
|
+
| | Dimension | What it measures | Key metrics |
|
|
49
|
+
|---|---|---|---|
|
|
50
|
+
| **C** | **Context** | How effectively you engineer context for the AI | CLAUDE.md usage, memory writes, cache hit rate |
|
|
51
|
+
| **R** | **Reach** | How broadly you leverage available capabilities | Tool diversity, subagent usage, skill adoption |
|
|
52
|
+
| **A** | **Autonomy** | How independently the agent works for you | Message ratio, interruption rate, delegation |
|
|
53
|
+
| **F** | **Flow** | How consistently you maintain a coding rhythm | Streak length, daily consistency, active days |
|
|
54
|
+
| **T** | **Throughput** | How much output you get for your investment | Cost efficiency, output volume, error rate |
|
|
40
55
|
|
|
41
|
-
|
|
56
|
+
Inspired by [DORA Metrics](https://dora.dev) and Microsoft's [SPACE framework](https://queue.acm.org/detail.cfm?id=3454124). Behavioral signals from your logs — no surveys, no guesswork. Each dimension is scored 0–100.
|
|
42
57
|
|
|
43
58
|
## Features
|
|
44
59
|
|
|
45
60
|
- **Dashboard** — overview stats (cost, tokens, sessions, projects, messages, tool calls, duration)
|
|
46
|
-
- **
|
|
61
|
+
- **CRAFT Coach** — fitness score, achievements, and actionable improvement tips
|
|
47
62
|
- **Daily Usage** — daily cost and activity charts
|
|
48
63
|
- **Token Breakdown** — pie chart + stacked area chart of token types
|
|
49
|
-
- **Tool Usage** — top
|
|
64
|
+
- **Tool Usage** — top tools by invocation count
|
|
50
65
|
- **Projects** — per-project breakdown with top tools and sessions
|
|
51
|
-
- **
|
|
66
|
+
- **Sessions** — individual session details with chat logs and tool flow graphs
|
|
67
|
+
- **Personality Fit** — MBTI-style behavioral analysis
|
|
52
68
|
- **Command Usage** — slash command pattern tracking
|
|
53
69
|
- **Images** — screenshot analysis across sessions
|
|
54
|
-
- **Community Plugins** — extensible analysis views
|
|
70
|
+
- **Community Plugins** — extensible analysis views
|
|
55
71
|
|
|
56
|
-
##
|
|
72
|
+
## Development
|
|
57
73
|
|
|
58
|
-
|
|
59
|
-
- Images extracted from conversation logs to `data/images/`
|
|
60
|
-
- Pricing fetched from [LiteLLM](https://github.com/BerriAI/litellm) model pricing database
|
|
61
|
-
- Incremental sync — only new sessions are imported on each sync
|
|
62
|
-
- Supports Claude Code, Codex, or combined views
|
|
74
|
+
Build the desktop app locally:
|
|
63
75
|
|
|
64
|
-
|
|
76
|
+
```bash
|
|
77
|
+
npm run electron:build:mac # Mac (.dmg)
|
|
78
|
+
npm run electron:build:win # Windows (.exe)
|
|
79
|
+
```
|
|
65
80
|
|
|
66
81
|
```bash
|
|
67
82
|
npm run dev # Start dev server (Turbopack)
|
|
@@ -70,23 +85,11 @@ npm run lint # ESLint
|
|
|
70
85
|
npm run format # Prettier
|
|
71
86
|
npm run typecheck # TypeScript check
|
|
72
87
|
npm test # Run tests
|
|
73
|
-
npm run test:watch # Tests in watch mode
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
### Database
|
|
77
|
-
|
|
78
|
-
```bash
|
|
79
|
-
npx prisma migrate dev --name <name> # Create/apply migration
|
|
80
|
-
npx prisma generate # Regenerate Prisma client
|
|
81
88
|
```
|
|
82
89
|
|
|
83
90
|
## Community Plugins
|
|
84
91
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
See [CONTRIBUTING.md](CONTRIBUTING.md) for the full guide on creating your own plugin.
|
|
88
|
-
|
|
89
|
-
**Quick version:**
|
|
92
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for the full guide. Quick version:
|
|
90
93
|
|
|
91
94
|
1. Create `plugins/<your-slug>/manifest.ts` and `component.tsx`
|
|
92
95
|
2. Register in `plugins/index.ts`
|
|
@@ -98,7 +101,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for the full guide on creating your own p
|
|
|
98
101
|
| Variable | Default | Description |
|
|
99
102
|
|----------|---------|-------------|
|
|
100
103
|
| `PORT` or `AGENTFIT_PORT` | `3000` | Server port |
|
|
101
|
-
| `DATABASE_URL` | `file:./
|
|
104
|
+
| `DATABASE_URL` | `file:./agentfit.db` | SQLite database path |
|
|
102
105
|
|
|
103
106
|
## Credits
|
|
104
107
|
|
|
@@ -11,7 +11,7 @@ export default function DailyPage() {
|
|
|
11
11
|
return (
|
|
12
12
|
<>
|
|
13
13
|
<div className="grid gap-4 lg:grid-cols-3">
|
|
14
|
-
<DailyChart daily={data.daily} />
|
|
14
|
+
<DailyChart daily={data.daily} sessions={data.sessions} />
|
|
15
15
|
</div>
|
|
16
16
|
<DailyTable daily={data.daily} sessions={data.sessions} />
|
|
17
17
|
</>
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect } from 'react'
|
|
4
|
+
import { useData } from '@/components/data-provider'
|
|
5
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
|
6
|
+
import { Button } from '@/components/ui/button'
|
|
7
|
+
import {
|
|
8
|
+
Dialog,
|
|
9
|
+
DialogContent,
|
|
10
|
+
DialogFooter,
|
|
11
|
+
DialogHeader,
|
|
12
|
+
DialogTitle,
|
|
13
|
+
} from '@/components/ui/dialog'
|
|
14
|
+
import { Badge } from '@/components/ui/badge'
|
|
15
|
+
import {
|
|
16
|
+
RefreshCw,
|
|
17
|
+
RotateCcw,
|
|
18
|
+
AlertTriangle,
|
|
19
|
+
Database,
|
|
20
|
+
HardDrive,
|
|
21
|
+
FolderSync,
|
|
22
|
+
} from 'lucide-react'
|
|
23
|
+
import { BackupCard } from '@/components/backup-section'
|
|
24
|
+
|
|
25
|
+
export default function SettingsPage() {
|
|
26
|
+
const { syncing, resetting, lastSyncResult, lastSyncTime, handleSync, handleReset } = useData()
|
|
27
|
+
const [resetOpen, setResetOpen] = useState(false)
|
|
28
|
+
const [paths, setPaths] = useState<{ database: string; images: string; claudeLogs: string; codexLogs: string } | null>(null)
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
fetch('/api/check')
|
|
32
|
+
.then((r) => r.json())
|
|
33
|
+
.then((d) => { if (d.paths) setPaths(d.paths) })
|
|
34
|
+
.catch(() => {})
|
|
35
|
+
}, [])
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<div className="space-y-6 max-w-2xl">
|
|
39
|
+
{/* Local Data */}
|
|
40
|
+
<Card>
|
|
41
|
+
<CardHeader>
|
|
42
|
+
<div className="flex items-center gap-2">
|
|
43
|
+
<Database className="h-5 w-5 text-muted-foreground" />
|
|
44
|
+
<div>
|
|
45
|
+
<CardTitle>Local Data</CardTitle>
|
|
46
|
+
<CardDescription>Manage your synced session data</CardDescription>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
</CardHeader>
|
|
50
|
+
<CardContent className="space-y-4">
|
|
51
|
+
{/* Sync */}
|
|
52
|
+
<div className="flex items-center justify-between rounded-lg border p-4">
|
|
53
|
+
<div className="space-y-1">
|
|
54
|
+
<div className="flex items-center gap-2">
|
|
55
|
+
<FolderSync className="h-4 w-4 text-muted-foreground" />
|
|
56
|
+
<span className="text-sm font-medium">Sync Logs</span>
|
|
57
|
+
</div>
|
|
58
|
+
<p className="text-xs text-muted-foreground">
|
|
59
|
+
Import new sessions from ~/.claude and ~/.codex into the local database.
|
|
60
|
+
</p>
|
|
61
|
+
{lastSyncTime && (
|
|
62
|
+
<p className="text-xs text-muted-foreground">
|
|
63
|
+
Last synced: {lastSyncTime.toLocaleString()}
|
|
64
|
+
{lastSyncResult && (
|
|
65
|
+
<span> — {lastSyncResult.sessionsAdded} added, {lastSyncResult.sessionsSkipped} skipped</span>
|
|
66
|
+
)}
|
|
67
|
+
</p>
|
|
68
|
+
)}
|
|
69
|
+
</div>
|
|
70
|
+
<Button
|
|
71
|
+
variant="outline"
|
|
72
|
+
onClick={handleSync}
|
|
73
|
+
disabled={syncing || resetting}
|
|
74
|
+
className="gap-2 shrink-0"
|
|
75
|
+
>
|
|
76
|
+
<RefreshCw className={`h-4 w-4 ${syncing ? 'animate-spin' : ''}`} />
|
|
77
|
+
{syncing ? 'Syncing...' : 'Sync'}
|
|
78
|
+
</Button>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
{/* Reset */}
|
|
82
|
+
<div className="flex items-center justify-between rounded-lg border border-destructive/20 p-4">
|
|
83
|
+
<div className="space-y-1">
|
|
84
|
+
<div className="flex items-center gap-2">
|
|
85
|
+
<RotateCcw className="h-4 w-4 text-destructive" />
|
|
86
|
+
<span className="text-sm font-medium text-destructive">Reset Database</span>
|
|
87
|
+
</div>
|
|
88
|
+
<p className="text-xs text-muted-foreground">
|
|
89
|
+
Delete all synced data and re-import from log files on disk. Use if data seems corrupted.
|
|
90
|
+
</p>
|
|
91
|
+
</div>
|
|
92
|
+
<Button
|
|
93
|
+
variant="destructive"
|
|
94
|
+
onClick={() => setResetOpen(true)}
|
|
95
|
+
disabled={syncing || resetting}
|
|
96
|
+
className="gap-2 shrink-0"
|
|
97
|
+
>
|
|
98
|
+
<RotateCcw className={`h-4 w-4 ${resetting ? 'animate-spin' : ''}`} />
|
|
99
|
+
{resetting ? 'Resetting...' : 'Reset'}
|
|
100
|
+
</Button>
|
|
101
|
+
</div>
|
|
102
|
+
</CardContent>
|
|
103
|
+
</Card>
|
|
104
|
+
|
|
105
|
+
{/* Database Info */}
|
|
106
|
+
<Card>
|
|
107
|
+
<CardHeader>
|
|
108
|
+
<div className="flex items-center gap-2">
|
|
109
|
+
<HardDrive className="h-5 w-5 text-muted-foreground" />
|
|
110
|
+
<div>
|
|
111
|
+
<CardTitle>Storage</CardTitle>
|
|
112
|
+
<CardDescription>Where your data lives</CardDescription>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
</CardHeader>
|
|
116
|
+
<CardContent>
|
|
117
|
+
<div className="space-y-3 text-sm">
|
|
118
|
+
<div className="flex items-center justify-between">
|
|
119
|
+
<span className="text-muted-foreground">Database</span>
|
|
120
|
+
<code className="rounded bg-muted px-2 py-0.5 text-xs">{paths?.database ?? 'agentfit.db'}</code>
|
|
121
|
+
</div>
|
|
122
|
+
<div className="flex items-center justify-between">
|
|
123
|
+
<span className="text-muted-foreground">Claude Code logs</span>
|
|
124
|
+
<code className="rounded bg-muted px-2 py-0.5 text-xs">{paths?.claudeLogs ?? '~/.claude/projects/'}</code>
|
|
125
|
+
</div>
|
|
126
|
+
<div className="flex items-center justify-between">
|
|
127
|
+
<span className="text-muted-foreground">Codex logs</span>
|
|
128
|
+
<code className="rounded bg-muted px-2 py-0.5 text-xs">{paths?.codexLogs ?? '~/.codex/sessions/'}</code>
|
|
129
|
+
</div>
|
|
130
|
+
<div className="flex items-center justify-between">
|
|
131
|
+
<span className="text-muted-foreground">Extracted images</span>
|
|
132
|
+
<code className="rounded bg-muted px-2 py-0.5 text-xs">{paths?.images ?? 'data/images/'}</code>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
</CardContent>
|
|
136
|
+
</Card>
|
|
137
|
+
|
|
138
|
+
{/* GitHub Backup */}
|
|
139
|
+
<BackupCard />
|
|
140
|
+
|
|
141
|
+
{/* Reset confirmation dialog */}
|
|
142
|
+
<Dialog open={resetOpen} onOpenChange={setResetOpen}>
|
|
143
|
+
<DialogContent className="sm:max-w-md">
|
|
144
|
+
<DialogHeader>
|
|
145
|
+
<DialogTitle className="flex items-center gap-2 text-destructive">
|
|
146
|
+
<AlertTriangle className="h-5 w-5" />
|
|
147
|
+
Destructive Action
|
|
148
|
+
</DialogTitle>
|
|
149
|
+
</DialogHeader>
|
|
150
|
+
<div className="space-y-3 text-sm text-muted-foreground">
|
|
151
|
+
<div>
|
|
152
|
+
This will <strong className="text-foreground">permanently delete all synced data</strong> from the database and re-import from log files currently on disk.
|
|
153
|
+
</div>
|
|
154
|
+
<div className="rounded-md border border-destructive/30 bg-destructive/5 p-3 text-sm text-destructive">
|
|
155
|
+
<strong>Warning:</strong> If your coding agent (Claude Code, Codex, etc.) has purged old log files, that data will be permanently lost. The database may contain sessions that no longer exist on disk.
|
|
156
|
+
</div>
|
|
157
|
+
<div>
|
|
158
|
+
This cannot be undone. Consider backing up <code className="rounded bg-muted px-1 py-0.5 text-xs">agentfit.db</code> first.
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
<DialogFooter className="gap-3">
|
|
162
|
+
<Button variant="outline" onClick={() => setResetOpen(false)}>
|
|
163
|
+
Cancel
|
|
164
|
+
</Button>
|
|
165
|
+
<Button
|
|
166
|
+
variant="destructive"
|
|
167
|
+
disabled={resetting}
|
|
168
|
+
onClick={async () => {
|
|
169
|
+
await handleReset()
|
|
170
|
+
setResetOpen(false)
|
|
171
|
+
}}
|
|
172
|
+
>
|
|
173
|
+
I understand, reset everything
|
|
174
|
+
</Button>
|
|
175
|
+
</DialogFooter>
|
|
176
|
+
</DialogContent>
|
|
177
|
+
</Dialog>
|
|
178
|
+
</div>
|
|
179
|
+
)
|
|
180
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useData } from '@/components/data-provider'
|
|
4
|
+
import { ToolFlowGraph } from '@/components/tool-flow-graph'
|
|
5
|
+
import { SessionTimeline } from '@/components/session-timeline'
|
|
6
|
+
|
|
7
|
+
export default function FlowPage() {
|
|
8
|
+
const { data } = useData()
|
|
9
|
+
if (!data) return null
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<>
|
|
13
|
+
<SessionTimeline sessions={data.sessions} />
|
|
14
|
+
<ToolFlowGraph data={data} />
|
|
15
|
+
</>
|
|
16
|
+
)
|
|
17
|
+
}
|
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
import { DataProvider } from '@/components/data-provider'
|
|
4
4
|
import { DashboardShell } from '@/components/dashboard-shell'
|
|
5
|
+
import { Toaster } from '@/components/ui/sonner'
|
|
5
6
|
|
|
6
7
|
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
|
|
7
8
|
return (
|
|
8
9
|
<DataProvider>
|
|
9
10
|
<DashboardShell>{children}</DashboardShell>
|
|
11
|
+
<Toaster />
|
|
10
12
|
</DataProvider>
|
|
11
13
|
)
|
|
12
14
|
}
|
package/app/(dashboard)/page.tsx
CHANGED
|
@@ -3,21 +3,40 @@
|
|
|
3
3
|
import { useData } from '@/components/data-provider'
|
|
4
4
|
import { FitnessScore } from '@/components/fitness-score'
|
|
5
5
|
import { OverviewCards } from '@/components/overview-cards'
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
6
|
+
import { DailyCostChart, TopCommandsChart, TokenUsageHeatmap, UserVsAssistantChart, InterruptionRateChart, ToolMixChart } from '@/components/daily-chart'
|
|
7
|
+
import { RefreshCw } from 'lucide-react'
|
|
8
8
|
|
|
9
9
|
export default function DashboardPage() {
|
|
10
|
-
const { data } = useData()
|
|
10
|
+
const { data, newSessionsAvailable, handleSync, syncing } = useData()
|
|
11
11
|
if (!data) return null
|
|
12
12
|
|
|
13
13
|
return (
|
|
14
14
|
<>
|
|
15
|
+
{newSessionsAvailable > 0 && (
|
|
16
|
+
<div className="flex items-center justify-between rounded-lg border border-blue-200 bg-blue-50 px-4 py-3 text-sm dark:border-blue-800 dark:bg-blue-950">
|
|
17
|
+
<span>
|
|
18
|
+
<strong>{newSessionsAvailable}</strong> new session{newSessionsAvailable !== 1 ? 's' : ''} detected on disk. Sync to update your dashboard.
|
|
19
|
+
</span>
|
|
20
|
+
<button
|
|
21
|
+
onClick={handleSync}
|
|
22
|
+
disabled={syncing}
|
|
23
|
+
className="inline-flex items-center gap-1.5 rounded-md bg-blue-600 px-3 py-1.5 text-xs font-medium text-white hover:bg-blue-700 disabled:opacity-50"
|
|
24
|
+
>
|
|
25
|
+
<RefreshCw className={`h-3 w-3 ${syncing ? 'animate-spin' : ''}`} />
|
|
26
|
+
{syncing ? 'Syncing…' : 'Sync now'}
|
|
27
|
+
</button>
|
|
28
|
+
</div>
|
|
29
|
+
)}
|
|
15
30
|
<FitnessScore data={data} />
|
|
16
31
|
<OverviewCards overview={data.overview} sessions={data.sessions} />
|
|
17
32
|
<div className="grid gap-4 lg:grid-cols-3">
|
|
18
|
-
<
|
|
33
|
+
<DailyCostChart daily={data.daily} />
|
|
34
|
+
<TopCommandsChart />
|
|
35
|
+
<TokenUsageHeatmap daily={data.daily} />
|
|
36
|
+
<UserVsAssistantChart daily={data.daily} />
|
|
37
|
+
<InterruptionRateChart daily={data.daily} />
|
|
38
|
+
<ToolMixChart daily={data.daily} />
|
|
19
39
|
</div>
|
|
20
|
-
<ToolUsageChart toolUsage={data.toolUsage} />
|
|
21
40
|
</>
|
|
22
41
|
)
|
|
23
42
|
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from 'react'
|
|
4
|
+
import { useParams } from 'next/navigation'
|
|
5
|
+
import Link from 'next/link'
|
|
6
|
+
import { ArrowLeft, Loader2 } from 'lucide-react'
|
|
7
|
+
import { ReportView } from '@/components/report-view'
|
|
8
|
+
import type { ReportContent } from '@/lib/report'
|
|
9
|
+
|
|
10
|
+
interface ReportData {
|
|
11
|
+
id: string
|
|
12
|
+
title: string
|
|
13
|
+
generatedAt: string
|
|
14
|
+
contentJson: ReportContent
|
|
15
|
+
sessionCount: number
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export default function ReportDetailPage() {
|
|
19
|
+
const params = useParams()
|
|
20
|
+
const id = params.id as string
|
|
21
|
+
const [report, setReport] = useState<ReportData | null>(null)
|
|
22
|
+
const [loading, setLoading] = useState(true)
|
|
23
|
+
const [error, setError] = useState<string | null>(null)
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
async function load() {
|
|
27
|
+
try {
|
|
28
|
+
const res = await fetch(`/api/reports/${id}`)
|
|
29
|
+
if (!res.ok) throw new Error('Report not found')
|
|
30
|
+
setReport(await res.json())
|
|
31
|
+
} catch (e) {
|
|
32
|
+
setError((e as Error).message)
|
|
33
|
+
} finally {
|
|
34
|
+
setLoading(false)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
load()
|
|
38
|
+
}, [id])
|
|
39
|
+
|
|
40
|
+
if (loading) {
|
|
41
|
+
return (
|
|
42
|
+
<div className="flex items-center gap-2 py-8 text-muted-foreground">
|
|
43
|
+
<Loader2 className="h-4 w-4 animate-spin" /> Loading report...
|
|
44
|
+
</div>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (error || !report) {
|
|
49
|
+
return (
|
|
50
|
+
<div className="space-y-4">
|
|
51
|
+
<Link href="/reports" className="flex items-center gap-1 text-sm text-primary hover:underline">
|
|
52
|
+
<ArrowLeft className="h-4 w-4" /> Back to reports
|
|
53
|
+
</Link>
|
|
54
|
+
<div className="text-destructive">{error || 'Report not found'}</div>
|
|
55
|
+
</div>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<div className="space-y-4">
|
|
61
|
+
<div className="flex items-center justify-between">
|
|
62
|
+
<Link href="/reports" className="flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground">
|
|
63
|
+
<ArrowLeft className="h-4 w-4" /> Back to reports
|
|
64
|
+
</Link>
|
|
65
|
+
<span className="text-xs text-muted-foreground">
|
|
66
|
+
Generated {new Date(report.generatedAt).toLocaleString()} · {report.sessionCount} sessions
|
|
67
|
+
</span>
|
|
68
|
+
</div>
|
|
69
|
+
<ReportView content={report.contentJson} />
|
|
70
|
+
</div>
|
|
71
|
+
)
|
|
72
|
+
}
|