agentfit 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +26 -0
- package/.prettierignore +7 -0
- package/.prettierrc +11 -0
- package/CONTRIBUTING.md +209 -0
- package/LICENSE +21 -0
- package/README.md +109 -0
- package/app/(dashboard)/coach/page.tsx +11 -0
- package/app/(dashboard)/commands/page.tsx +7 -0
- package/app/(dashboard)/community/[slug]/page.tsx +23 -0
- package/app/(dashboard)/community/page.tsx +71 -0
- package/app/(dashboard)/daily/page.tsx +19 -0
- package/app/(dashboard)/images/page.tsx +5 -0
- package/app/(dashboard)/layout.tsx +12 -0
- package/app/(dashboard)/page.tsx +23 -0
- package/app/(dashboard)/personality/page.tsx +11 -0
- package/app/(dashboard)/projects/page.tsx +11 -0
- package/app/(dashboard)/sessions/page.tsx +11 -0
- package/app/(dashboard)/tokens/page.tsx +11 -0
- package/app/(dashboard)/tools/page.tsx +11 -0
- package/app/api/check/route.ts +13 -0
- package/app/api/commands/route.ts +16 -0
- package/app/api/images/[...path]/route.ts +33 -0
- package/app/api/images-analysis/route.ts +177 -0
- package/app/api/sync/route.ts +14 -0
- package/app/api/usage/route.ts +117 -0
- package/app/favicon.ico +0 -0
- package/app/globals.css +144 -0
- package/app/icon.svg +3 -0
- package/app/layout.tsx +35 -0
- package/bin/agentfit.mjs +69 -0
- package/components/.gitkeep +0 -0
- package/components/agent-coach.tsx +248 -0
- package/components/app-sidebar.tsx +161 -0
- package/components/command-usage.tsx +294 -0
- package/components/daily-chart.tsx +118 -0
- package/components/daily-table.tsx +115 -0
- package/components/dashboard-shell.tsx +149 -0
- package/components/data-provider.tsx +213 -0
- package/components/fitness-score.tsx +95 -0
- package/components/overview-cards.tsx +198 -0
- package/components/pagination-controls.tsx +104 -0
- package/components/personality-fit.tsx +446 -0
- package/components/projects-table.tsx +70 -0
- package/components/screenshots-analysis.tsx +359 -0
- package/components/sessions-table.tsx +97 -0
- package/components/theme-provider.tsx +71 -0
- package/components/token-breakdown.tsx +179 -0
- package/components/tool-usage-chart.tsx +63 -0
- package/components/ui/badge.tsx +52 -0
- package/components/ui/button.tsx +60 -0
- package/components/ui/card.tsx +103 -0
- package/components/ui/chart.tsx +373 -0
- package/components/ui/dialog.tsx +160 -0
- package/components/ui/input.tsx +20 -0
- package/components/ui/scroll-area.tsx +55 -0
- package/components/ui/select.tsx +201 -0
- package/components/ui/separator.tsx +25 -0
- package/components/ui/sheet.tsx +138 -0
- package/components/ui/sidebar.tsx +723 -0
- package/components/ui/skeleton.tsx +13 -0
- package/components/ui/table.tsx +116 -0
- package/components/ui/tabs.tsx +82 -0
- package/components/ui/tooltip.tsx +66 -0
- package/components.json +25 -0
- package/generated/prisma/browser.ts +34 -0
- package/generated/prisma/client.ts +58 -0
- package/generated/prisma/commonInputTypes.ts +237 -0
- package/generated/prisma/enums.ts +15 -0
- package/generated/prisma/internal/class.ts +224 -0
- package/generated/prisma/internal/prismaNamespace.ts +920 -0
- package/generated/prisma/internal/prismaNamespaceBrowser.ts +130 -0
- package/generated/prisma/models/Image.ts +1310 -0
- package/generated/prisma/models/Session.ts +1695 -0
- package/generated/prisma/models/SyncLog.ts +1203 -0
- package/generated/prisma/models.ts +14 -0
- package/hooks/.gitkeep +0 -0
- package/hooks/use-mobile.ts +19 -0
- package/hooks/use-pagination.ts +60 -0
- package/lib/.gitkeep +0 -0
- package/lib/coach.ts +425 -0
- package/lib/commands.ts +239 -0
- package/lib/db.ts +15 -0
- package/lib/format.ts +26 -0
- package/lib/parse-codex.ts +201 -0
- package/lib/parse-logs.ts +369 -0
- package/lib/personality.ts +481 -0
- package/lib/plugins.ts +107 -0
- package/lib/pricing.ts +112 -0
- package/lib/queries-codex.ts +130 -0
- package/lib/queries.ts +154 -0
- package/lib/resolve-icon.ts +12 -0
- package/lib/sync.ts +335 -0
- package/lib/utils.ts +6 -0
- package/next.config.mjs +4 -0
- package/package.json +73 -0
- package/plugins/cost-heatmap/component.test.tsx +52 -0
- package/plugins/cost-heatmap/component.tsx +227 -0
- package/plugins/cost-heatmap/manifest.ts +13 -0
- package/plugins/index.ts +18 -0
- package/prisma/migrations/20260328152517_init/migration.sql +41 -0
- package/prisma/migrations/20260328153801_add_image_model/migration.sql +18 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +57 -0
- package/prisma.config.ts +14 -0
- package/public/.gitkeep +0 -0
- package/public/logo.svg +3 -0
- package/setup.sh +73 -0
|
@@ -0,0 +1,26 @@
|
|
|
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
|
+
}
|
package/.prettierignore
ADDED
package/.prettierrc
ADDED
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# Contributing a Community Plugin
|
|
2
|
+
|
|
3
|
+
AgentFit has a plugin system that lets anyone contribute new analysis views. Your plugin appears in the **Community** sidebar section and receives the same usage data as built-in pages.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# 1. Fork & clone
|
|
9
|
+
git clone https://github.com/<you>/agentfit && cd agentfit
|
|
10
|
+
npm install
|
|
11
|
+
|
|
12
|
+
# 2. Scaffold your plugin
|
|
13
|
+
mkdir -p plugins/my-analysis
|
|
14
|
+
|
|
15
|
+
# 3. Create the two required files (see below)
|
|
16
|
+
# 4. Register it in plugins/index.ts
|
|
17
|
+
# 5. Run dev server & tests
|
|
18
|
+
npm run dev
|
|
19
|
+
npm test
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Plugin Structure
|
|
23
|
+
|
|
24
|
+
Every plugin lives in its own folder under `plugins/` and has exactly **two files** plus a test:
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
plugins/
|
|
28
|
+
my-analysis/
|
|
29
|
+
manifest.ts # metadata (name, slug, icon, etc.)
|
|
30
|
+
component.tsx # React component that renders the analysis
|
|
31
|
+
component.test.tsx # tests (required for PR acceptance)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### manifest.ts
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import type { PluginManifest } from '@/lib/plugins'
|
|
38
|
+
|
|
39
|
+
const manifest: PluginManifest = {
|
|
40
|
+
slug: 'my-analysis', // URL-safe, lowercase, hyphens only
|
|
41
|
+
name: 'My Analysis', // shown in sidebar
|
|
42
|
+
description: 'One-line summary of what this shows',
|
|
43
|
+
author: 'your-github-handle',
|
|
44
|
+
icon: 'ChartArea', // any lucide-react icon name
|
|
45
|
+
version: '1.0.0',
|
|
46
|
+
tags: ['cost', 'productivity'], // optional, for discoverability
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export default manifest
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Slug rules:** lowercase letters, numbers, hyphens only (e.g. `my-analysis`). Must be unique across all plugins.
|
|
53
|
+
|
|
54
|
+
### component.tsx
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
'use client'
|
|
58
|
+
|
|
59
|
+
import { useMemo } from 'react'
|
|
60
|
+
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
|
61
|
+
import type { PluginProps } from '@/lib/plugins'
|
|
62
|
+
|
|
63
|
+
export default function MyAnalysis({ data }: PluginProps) {
|
|
64
|
+
const stats = useMemo(() => {
|
|
65
|
+
// Compute your analysis from data.sessions, data.daily, etc.
|
|
66
|
+
return { total: data.sessions.length }
|
|
67
|
+
}, [data])
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<Card>
|
|
71
|
+
<CardHeader>
|
|
72
|
+
<CardTitle>My Analysis</CardTitle>
|
|
73
|
+
</CardHeader>
|
|
74
|
+
<CardContent>
|
|
75
|
+
<p>Found {stats.total} sessions</p>
|
|
76
|
+
</CardContent>
|
|
77
|
+
</Card>
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Registration
|
|
83
|
+
|
|
84
|
+
Open `plugins/index.ts` and add your import + registration call:
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
// ─── Community plugins ──────────────────────────────────────────────
|
|
88
|
+
import myAnalysisManifest from './my-analysis/manifest'
|
|
89
|
+
import MyAnalysis from './my-analysis/component'
|
|
90
|
+
registerPlugin(myAnalysisManifest, MyAnalysis)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
That's it. Your plugin now appears in the sidebar at `/community/my-analysis`.
|
|
94
|
+
|
|
95
|
+
## Data Available to Plugins
|
|
96
|
+
|
|
97
|
+
Your component receives `PluginProps`:
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
interface PluginProps {
|
|
101
|
+
data: UsageData
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`UsageData` contains:
|
|
106
|
+
|
|
107
|
+
| Field | Type | Description |
|
|
108
|
+
|-------|------|-------------|
|
|
109
|
+
| `overview` | `OverviewStats` | Aggregated metrics (totals for sessions, tokens, cost, etc.) |
|
|
110
|
+
| `sessions` | `SessionSummary[]` | Every session with tokens, cost, duration, tool calls |
|
|
111
|
+
| `projects` | `ProjectSummary[]` | Per-project aggregations |
|
|
112
|
+
| `daily` | `DailyUsage[]` | Per-day aggregations |
|
|
113
|
+
| `toolUsage` | `Record<string, number>` | Tool invocation counts |
|
|
114
|
+
|
|
115
|
+
All data is already filtered by the user's selected time range and agent type. You don't need to filter again.
|
|
116
|
+
|
|
117
|
+
See `lib/parse-logs.ts` for the full type definitions.
|
|
118
|
+
|
|
119
|
+
## UI Guidelines
|
|
120
|
+
|
|
121
|
+
- Use **shadcn UI** components from `components/ui/` (Card, Badge, Table, etc.)
|
|
122
|
+
- Use **Recharts** (already installed) for charts, wrapped in `ChartContainer`
|
|
123
|
+
- Use `lib/format.ts` helpers: `formatCost()`, `formatTokens()`, `formatDuration()`
|
|
124
|
+
- Use the existing CSS chart color variables: `--chart-1` through `--chart-10`
|
|
125
|
+
- Mark your component as `'use client'` at the top
|
|
126
|
+
- Wrap computed values in `useMemo` for performance
|
|
127
|
+
|
|
128
|
+
## Writing Tests
|
|
129
|
+
|
|
130
|
+
Every plugin must include tests. Use the provided test helpers:
|
|
131
|
+
|
|
132
|
+
```tsx
|
|
133
|
+
// plugins/my-analysis/component.test.tsx
|
|
134
|
+
import { describe, it, expect } from 'vitest'
|
|
135
|
+
import { screen } from '@testing-library/react'
|
|
136
|
+
import { renderPlugin } from '@/tests/plugin-helpers'
|
|
137
|
+
import { validateManifest } from '@/lib/plugins'
|
|
138
|
+
import MyAnalysis from './component'
|
|
139
|
+
import manifest from './manifest'
|
|
140
|
+
|
|
141
|
+
describe('my-analysis plugin', () => {
|
|
142
|
+
it('manifest passes validation', () => {
|
|
143
|
+
expect(validateManifest(manifest)).toEqual([])
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it('renders without crashing', () => {
|
|
147
|
+
const { container } = renderPlugin(MyAnalysis)
|
|
148
|
+
expect(container).toBeTruthy()
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
it('shows expected content', () => {
|
|
152
|
+
renderPlugin(MyAnalysis)
|
|
153
|
+
expect(screen.getByText('My Analysis')).toBeInTheDocument()
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
it('handles empty data', () => {
|
|
157
|
+
renderPlugin(MyAnalysis, { sessions: [], daily: [], projects: [] })
|
|
158
|
+
// Should not crash — show an empty state instead
|
|
159
|
+
})
|
|
160
|
+
})
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Test utilities
|
|
164
|
+
|
|
165
|
+
| Helper | Import | Description |
|
|
166
|
+
|--------|--------|-------------|
|
|
167
|
+
| `renderPlugin(Component, dataOverrides?)` | `@/tests/plugin-helpers` | Renders your plugin with mock `UsageData` |
|
|
168
|
+
| `createMockData(overrides?)` | `@/tests/fixtures` | Creates a realistic `UsageData` object |
|
|
169
|
+
| `createMockSession(overrides?)` | `@/tests/fixtures` | Creates a single `SessionSummary` |
|
|
170
|
+
| `createMockDaily(overrides?)` | `@/tests/fixtures` | Creates a single `DailyUsage` |
|
|
171
|
+
| `validateManifest(manifest)` | `@/lib/plugins` | Returns array of validation errors (empty = valid) |
|
|
172
|
+
|
|
173
|
+
### Running tests
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
npm test # run all tests once
|
|
177
|
+
npm run test:watch # watch mode during development
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## PR Checklist
|
|
181
|
+
|
|
182
|
+
Before submitting your pull request:
|
|
183
|
+
|
|
184
|
+
- [ ] Plugin folder is in `plugins/<your-slug>/`
|
|
185
|
+
- [ ] `manifest.ts` passes `validateManifest()` with zero errors
|
|
186
|
+
- [ ] `component.tsx` exports a default React component accepting `PluginProps`
|
|
187
|
+
- [ ] Plugin is registered in `plugins/index.ts`
|
|
188
|
+
- [ ] Tests exist in `component.test.tsx` and all pass (`npm test`)
|
|
189
|
+
- [ ] Component handles empty data gracefully (no crashes)
|
|
190
|
+
- [ ] No new dependencies added (use existing recharts, shadcn, lucide-react)
|
|
191
|
+
- [ ] `npm run typecheck` passes
|
|
192
|
+
- [ ] `npm run lint` passes
|
|
193
|
+
|
|
194
|
+
## Advanced: Custom Data Sources
|
|
195
|
+
|
|
196
|
+
If your plugin fetches its own data (e.g., from a custom API route), set `customDataSource: true` in your manifest. This hides the time-range filter when your plugin is active.
|
|
197
|
+
|
|
198
|
+
```ts
|
|
199
|
+
const manifest: PluginManifest = {
|
|
200
|
+
// ...
|
|
201
|
+
customDataSource: true,
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
You can still use the `data` prop as a fallback, but you're free to `fetch()` additional data in a `useEffect`.
|
|
206
|
+
|
|
207
|
+
## Example Plugin
|
|
208
|
+
|
|
209
|
+
See `plugins/cost-heatmap/` for a complete working example with manifest, component, and tests.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Harry Wang
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# AgentFit
|
|
2
|
+
|
|
3
|
+
Fitness tracker dashboard for AI coding agents. Reads your local Claude Code and Codex conversation logs, syncs them into a SQLite database, and presents rich usage analytics — cost, tokens, tool usage, productivity patterns, and more.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
### Option 1: One-liner (recommended)
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
curl -fsSL https://raw.githubusercontent.com/harrywang/agentfit/main/setup.sh | bash
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Then start the dashboard:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
cd agentfit && npm start
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Option 2: npx
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npx agentfit
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
This handles first-run setup (database creation, build) automatically.
|
|
26
|
+
|
|
27
|
+
### Option 3: Manual
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
git clone https://github.com/harrywang/agentfit.git
|
|
31
|
+
cd agentfit
|
|
32
|
+
npm install
|
|
33
|
+
echo 'DATABASE_URL="file:./dev.db"' > .env
|
|
34
|
+
npx prisma migrate deploy
|
|
35
|
+
npm run build
|
|
36
|
+
npm start
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Open [http://localhost:3000](http://localhost:3000). The app auto-syncs your `~/.claude/projects/` logs on first load.
|
|
40
|
+
|
|
41
|
+
**Requirements:** Node.js 20+
|
|
42
|
+
|
|
43
|
+
## Features
|
|
44
|
+
|
|
45
|
+
- **Dashboard** — overview stats (cost, tokens, sessions, projects, messages, tool calls, duration)
|
|
46
|
+
- **Agent Coach** — fitness score, achievements, improvement areas, and tips
|
|
47
|
+
- **Daily Usage** — daily cost and activity charts
|
|
48
|
+
- **Token Breakdown** — pie chart + stacked area chart of token types
|
|
49
|
+
- **Tool Usage** — top 20 tools by invocation count
|
|
50
|
+
- **Projects** — per-project breakdown with top tools and sessions
|
|
51
|
+
- **Personality Fit** — MBTI-style behavioral analysis with task fit scores
|
|
52
|
+
- **Command Usage** — slash command pattern tracking
|
|
53
|
+
- **Images** — screenshot analysis across sessions
|
|
54
|
+
- **Community Plugins** — extensible analysis views contributed by the community
|
|
55
|
+
|
|
56
|
+
## Data
|
|
57
|
+
|
|
58
|
+
- Session metrics stored in local SQLite (`dev.db`) via Prisma
|
|
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
|
|
63
|
+
|
|
64
|
+
## Development
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
npm run dev # Start dev server (Turbopack)
|
|
68
|
+
npm run build # Production build
|
|
69
|
+
npm run lint # ESLint
|
|
70
|
+
npm run format # Prettier
|
|
71
|
+
npm run typecheck # TypeScript check
|
|
72
|
+
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
|
+
```
|
|
82
|
+
|
|
83
|
+
## Community Plugins
|
|
84
|
+
|
|
85
|
+
AgentFit has a plugin system for community-contributed analysis views. Plugins appear in the **Community** section of the sidebar.
|
|
86
|
+
|
|
87
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for the full guide on creating your own plugin.
|
|
88
|
+
|
|
89
|
+
**Quick version:**
|
|
90
|
+
|
|
91
|
+
1. Create `plugins/<your-slug>/manifest.ts` and `component.tsx`
|
|
92
|
+
2. Register in `plugins/index.ts`
|
|
93
|
+
3. Add tests in `component.test.tsx`
|
|
94
|
+
4. Submit a PR
|
|
95
|
+
|
|
96
|
+
## Configuration
|
|
97
|
+
|
|
98
|
+
| Variable | Default | Description |
|
|
99
|
+
|----------|---------|-------------|
|
|
100
|
+
| `PORT` or `AGENTFIT_PORT` | `3000` | Server port |
|
|
101
|
+
| `DATABASE_URL` | `file:./dev.db` | SQLite database path |
|
|
102
|
+
|
|
103
|
+
## Credits
|
|
104
|
+
|
|
105
|
+
- Logo: [Robot SVG](https://www.svgrepo.com/svg/486361/robot) from SVG Repo (CC0 License)
|
|
106
|
+
|
|
107
|
+
## License
|
|
108
|
+
|
|
109
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useData } from '@/components/data-provider'
|
|
4
|
+
import { AgentCoach } from '@/components/agent-coach'
|
|
5
|
+
|
|
6
|
+
export default function CoachPage() {
|
|
7
|
+
const { data } = useData()
|
|
8
|
+
if (!data) return null
|
|
9
|
+
|
|
10
|
+
return <AgentCoach data={data} />
|
|
11
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { use } from 'react'
|
|
4
|
+
import { notFound } from 'next/navigation'
|
|
5
|
+
import { useData } from '@/components/data-provider'
|
|
6
|
+
import { getPlugin } from '@/lib/plugins'
|
|
7
|
+
import '@/plugins' // ensure plugins are registered
|
|
8
|
+
|
|
9
|
+
export default function CommunityPluginPage({
|
|
10
|
+
params,
|
|
11
|
+
}: {
|
|
12
|
+
params: Promise<{ slug: string }>
|
|
13
|
+
}) {
|
|
14
|
+
const { slug } = use(params)
|
|
15
|
+
const { data } = useData()
|
|
16
|
+
const plugin = getPlugin(slug)
|
|
17
|
+
|
|
18
|
+
if (!plugin) notFound()
|
|
19
|
+
if (!data) return null
|
|
20
|
+
|
|
21
|
+
const Component = plugin.component
|
|
22
|
+
return <Component data={data} />
|
|
23
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import Link from 'next/link'
|
|
4
|
+
import { Card, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
|
5
|
+
import { Badge } from '@/components/ui/badge'
|
|
6
|
+
import { getPlugins } from '@/lib/plugins'
|
|
7
|
+
import { resolveLucideIcon } from '@/lib/resolve-icon'
|
|
8
|
+
import '@/plugins'
|
|
9
|
+
|
|
10
|
+
export default function CommunityPage() {
|
|
11
|
+
const plugins = getPlugins()
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<div className="space-y-6">
|
|
15
|
+
<div>
|
|
16
|
+
<p className="text-sm text-muted-foreground">
|
|
17
|
+
Community-contributed analysis views. Click a card to explore.
|
|
18
|
+
</p>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
{plugins.length === 0 ? (
|
|
22
|
+
<Card>
|
|
23
|
+
<CardHeader>
|
|
24
|
+
<CardTitle className="text-base">No community plugins installed</CardTitle>
|
|
25
|
+
<CardDescription>
|
|
26
|
+
See CONTRIBUTING.md to learn how to create and share your own analysis plugin.
|
|
27
|
+
</CardDescription>
|
|
28
|
+
</CardHeader>
|
|
29
|
+
</Card>
|
|
30
|
+
) : (
|
|
31
|
+
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
|
32
|
+
{plugins.map((plugin) => {
|
|
33
|
+
const Icon = resolveLucideIcon(plugin.manifest.icon)
|
|
34
|
+
return (
|
|
35
|
+
<Link
|
|
36
|
+
key={plugin.manifest.slug}
|
|
37
|
+
href={`/community/${plugin.manifest.slug}`}
|
|
38
|
+
className="group"
|
|
39
|
+
>
|
|
40
|
+
<Card className="h-full transition-colors group-hover:border-primary/50 group-hover:bg-muted/50">
|
|
41
|
+
<CardHeader>
|
|
42
|
+
<div className="flex items-start justify-between">
|
|
43
|
+
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-primary/10 text-primary">
|
|
44
|
+
<Icon className="h-5 w-5" />
|
|
45
|
+
</div>
|
|
46
|
+
<Badge variant="secondary" className="text-[10px]">
|
|
47
|
+
v{plugin.manifest.version}
|
|
48
|
+
</Badge>
|
|
49
|
+
</div>
|
|
50
|
+
<CardTitle className="text-base">{plugin.manifest.name}</CardTitle>
|
|
51
|
+
<CardDescription>{plugin.manifest.description}</CardDescription>
|
|
52
|
+
<div className="flex items-center gap-2 pt-1">
|
|
53
|
+
<span className="text-xs text-muted-foreground">
|
|
54
|
+
by {plugin.manifest.author}
|
|
55
|
+
</span>
|
|
56
|
+
{plugin.manifest.tags?.map((tag) => (
|
|
57
|
+
<Badge key={tag} variant="outline" className="text-[10px]">
|
|
58
|
+
{tag}
|
|
59
|
+
</Badge>
|
|
60
|
+
))}
|
|
61
|
+
</div>
|
|
62
|
+
</CardHeader>
|
|
63
|
+
</Card>
|
|
64
|
+
</Link>
|
|
65
|
+
)
|
|
66
|
+
})}
|
|
67
|
+
</div>
|
|
68
|
+
)}
|
|
69
|
+
</div>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useData } from '@/components/data-provider'
|
|
4
|
+
import { DailyChart } from '@/components/daily-chart'
|
|
5
|
+
import { DailyTable } from '@/components/daily-table'
|
|
6
|
+
|
|
7
|
+
export default function DailyPage() {
|
|
8
|
+
const { data } = useData()
|
|
9
|
+
if (!data) return null
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<>
|
|
13
|
+
<div className="grid gap-4 lg:grid-cols-3">
|
|
14
|
+
<DailyChart daily={data.daily} />
|
|
15
|
+
</div>
|
|
16
|
+
<DailyTable daily={data.daily} sessions={data.sessions} />
|
|
17
|
+
</>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { DataProvider } from '@/components/data-provider'
|
|
4
|
+
import { DashboardShell } from '@/components/dashboard-shell'
|
|
5
|
+
|
|
6
|
+
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
|
|
7
|
+
return (
|
|
8
|
+
<DataProvider>
|
|
9
|
+
<DashboardShell>{children}</DashboardShell>
|
|
10
|
+
</DataProvider>
|
|
11
|
+
)
|
|
12
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useData } from '@/components/data-provider'
|
|
4
|
+
import { FitnessScore } from '@/components/fitness-score'
|
|
5
|
+
import { OverviewCards } from '@/components/overview-cards'
|
|
6
|
+
import { DailyChart } from '@/components/daily-chart'
|
|
7
|
+
import { ToolUsageChart } from '@/components/tool-usage-chart'
|
|
8
|
+
|
|
9
|
+
export default function DashboardPage() {
|
|
10
|
+
const { data } = useData()
|
|
11
|
+
if (!data) return null
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<>
|
|
15
|
+
<FitnessScore data={data} />
|
|
16
|
+
<OverviewCards overview={data.overview} sessions={data.sessions} />
|
|
17
|
+
<div className="grid gap-4 lg:grid-cols-3">
|
|
18
|
+
<DailyChart daily={data.daily} />
|
|
19
|
+
</div>
|
|
20
|
+
<ToolUsageChart toolUsage={data.toolUsage} />
|
|
21
|
+
</>
|
|
22
|
+
)
|
|
23
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useData } from '@/components/data-provider'
|
|
4
|
+
import { PersonalityFit } from '@/components/personality-fit'
|
|
5
|
+
|
|
6
|
+
export default function PersonalityPage() {
|
|
7
|
+
const { data } = useData()
|
|
8
|
+
if (!data) return null
|
|
9
|
+
|
|
10
|
+
return <PersonalityFit data={data} />
|
|
11
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useData } from '@/components/data-provider'
|
|
4
|
+
import { ProjectsTable } from '@/components/projects-table'
|
|
5
|
+
|
|
6
|
+
export default function ProjectsPage() {
|
|
7
|
+
const { data } = useData()
|
|
8
|
+
if (!data) return null
|
|
9
|
+
|
|
10
|
+
return <ProjectsTable projects={data.projects} />
|
|
11
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useData } from '@/components/data-provider'
|
|
4
|
+
import { SessionsTable } from '@/components/sessions-table'
|
|
5
|
+
|
|
6
|
+
export default function SessionsPage() {
|
|
7
|
+
const { data } = useData()
|
|
8
|
+
if (!data) return null
|
|
9
|
+
|
|
10
|
+
return <SessionsTable sessions={data.sessions} />
|
|
11
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useData } from '@/components/data-provider'
|
|
4
|
+
import { TokenBreakdown } from '@/components/token-breakdown'
|
|
5
|
+
|
|
6
|
+
export default function TokensPage() {
|
|
7
|
+
const { data } = useData()
|
|
8
|
+
if (!data) return null
|
|
9
|
+
|
|
10
|
+
return <TokenBreakdown daily={data.daily} overview={data.overview} />
|
|
11
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useData } from '@/components/data-provider'
|
|
4
|
+
import { ToolUsageChart } from '@/components/tool-usage-chart'
|
|
5
|
+
|
|
6
|
+
export default function ToolsPage() {
|
|
7
|
+
const { data } = useData()
|
|
8
|
+
if (!data) return null
|
|
9
|
+
|
|
10
|
+
return <ToolUsageChart toolUsage={data.toolUsage} />
|
|
11
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { checkForNewSessions } from '@/lib/sync'
|
|
3
|
+
|
|
4
|
+
export const dynamic = 'force-dynamic'
|
|
5
|
+
|
|
6
|
+
export async function GET() {
|
|
7
|
+
try {
|
|
8
|
+
const newSessions = await checkForNewSessions()
|
|
9
|
+
return NextResponse.json({ newSessions })
|
|
10
|
+
} catch (error) {
|
|
11
|
+
return NextResponse.json({ newSessions: 0, error: (error as Error).message }, { status: 500 })
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { analyzeCommands } from '@/lib/commands'
|
|
3
|
+
|
|
4
|
+
export const dynamic = 'force-dynamic'
|
|
5
|
+
|
|
6
|
+
export async function GET() {
|
|
7
|
+
try {
|
|
8
|
+
const analysis = analyzeCommands()
|
|
9
|
+
return NextResponse.json(analysis)
|
|
10
|
+
} catch (error) {
|
|
11
|
+
return NextResponse.json(
|
|
12
|
+
{ error: (error as Error).message },
|
|
13
|
+
{ status: 500 }
|
|
14
|
+
)
|
|
15
|
+
}
|
|
16
|
+
}
|