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.
Files changed (107) hide show
  1. package/.claude/settings.local.json +26 -0
  2. package/.prettierignore +7 -0
  3. package/.prettierrc +11 -0
  4. package/CONTRIBUTING.md +209 -0
  5. package/LICENSE +21 -0
  6. package/README.md +109 -0
  7. package/app/(dashboard)/coach/page.tsx +11 -0
  8. package/app/(dashboard)/commands/page.tsx +7 -0
  9. package/app/(dashboard)/community/[slug]/page.tsx +23 -0
  10. package/app/(dashboard)/community/page.tsx +71 -0
  11. package/app/(dashboard)/daily/page.tsx +19 -0
  12. package/app/(dashboard)/images/page.tsx +5 -0
  13. package/app/(dashboard)/layout.tsx +12 -0
  14. package/app/(dashboard)/page.tsx +23 -0
  15. package/app/(dashboard)/personality/page.tsx +11 -0
  16. package/app/(dashboard)/projects/page.tsx +11 -0
  17. package/app/(dashboard)/sessions/page.tsx +11 -0
  18. package/app/(dashboard)/tokens/page.tsx +11 -0
  19. package/app/(dashboard)/tools/page.tsx +11 -0
  20. package/app/api/check/route.ts +13 -0
  21. package/app/api/commands/route.ts +16 -0
  22. package/app/api/images/[...path]/route.ts +33 -0
  23. package/app/api/images-analysis/route.ts +177 -0
  24. package/app/api/sync/route.ts +14 -0
  25. package/app/api/usage/route.ts +117 -0
  26. package/app/favicon.ico +0 -0
  27. package/app/globals.css +144 -0
  28. package/app/icon.svg +3 -0
  29. package/app/layout.tsx +35 -0
  30. package/bin/agentfit.mjs +69 -0
  31. package/components/.gitkeep +0 -0
  32. package/components/agent-coach.tsx +248 -0
  33. package/components/app-sidebar.tsx +161 -0
  34. package/components/command-usage.tsx +294 -0
  35. package/components/daily-chart.tsx +118 -0
  36. package/components/daily-table.tsx +115 -0
  37. package/components/dashboard-shell.tsx +149 -0
  38. package/components/data-provider.tsx +213 -0
  39. package/components/fitness-score.tsx +95 -0
  40. package/components/overview-cards.tsx +198 -0
  41. package/components/pagination-controls.tsx +104 -0
  42. package/components/personality-fit.tsx +446 -0
  43. package/components/projects-table.tsx +70 -0
  44. package/components/screenshots-analysis.tsx +359 -0
  45. package/components/sessions-table.tsx +97 -0
  46. package/components/theme-provider.tsx +71 -0
  47. package/components/token-breakdown.tsx +179 -0
  48. package/components/tool-usage-chart.tsx +63 -0
  49. package/components/ui/badge.tsx +52 -0
  50. package/components/ui/button.tsx +60 -0
  51. package/components/ui/card.tsx +103 -0
  52. package/components/ui/chart.tsx +373 -0
  53. package/components/ui/dialog.tsx +160 -0
  54. package/components/ui/input.tsx +20 -0
  55. package/components/ui/scroll-area.tsx +55 -0
  56. package/components/ui/select.tsx +201 -0
  57. package/components/ui/separator.tsx +25 -0
  58. package/components/ui/sheet.tsx +138 -0
  59. package/components/ui/sidebar.tsx +723 -0
  60. package/components/ui/skeleton.tsx +13 -0
  61. package/components/ui/table.tsx +116 -0
  62. package/components/ui/tabs.tsx +82 -0
  63. package/components/ui/tooltip.tsx +66 -0
  64. package/components.json +25 -0
  65. package/generated/prisma/browser.ts +34 -0
  66. package/generated/prisma/client.ts +58 -0
  67. package/generated/prisma/commonInputTypes.ts +237 -0
  68. package/generated/prisma/enums.ts +15 -0
  69. package/generated/prisma/internal/class.ts +224 -0
  70. package/generated/prisma/internal/prismaNamespace.ts +920 -0
  71. package/generated/prisma/internal/prismaNamespaceBrowser.ts +130 -0
  72. package/generated/prisma/models/Image.ts +1310 -0
  73. package/generated/prisma/models/Session.ts +1695 -0
  74. package/generated/prisma/models/SyncLog.ts +1203 -0
  75. package/generated/prisma/models.ts +14 -0
  76. package/hooks/.gitkeep +0 -0
  77. package/hooks/use-mobile.ts +19 -0
  78. package/hooks/use-pagination.ts +60 -0
  79. package/lib/.gitkeep +0 -0
  80. package/lib/coach.ts +425 -0
  81. package/lib/commands.ts +239 -0
  82. package/lib/db.ts +15 -0
  83. package/lib/format.ts +26 -0
  84. package/lib/parse-codex.ts +201 -0
  85. package/lib/parse-logs.ts +369 -0
  86. package/lib/personality.ts +481 -0
  87. package/lib/plugins.ts +107 -0
  88. package/lib/pricing.ts +112 -0
  89. package/lib/queries-codex.ts +130 -0
  90. package/lib/queries.ts +154 -0
  91. package/lib/resolve-icon.ts +12 -0
  92. package/lib/sync.ts +335 -0
  93. package/lib/utils.ts +6 -0
  94. package/next.config.mjs +4 -0
  95. package/package.json +73 -0
  96. package/plugins/cost-heatmap/component.test.tsx +52 -0
  97. package/plugins/cost-heatmap/component.tsx +227 -0
  98. package/plugins/cost-heatmap/manifest.ts +13 -0
  99. package/plugins/index.ts +18 -0
  100. package/prisma/migrations/20260328152517_init/migration.sql +41 -0
  101. package/prisma/migrations/20260328153801_add_image_model/migration.sql +18 -0
  102. package/prisma/migrations/migration_lock.toml +3 -0
  103. package/prisma/schema.prisma +57 -0
  104. package/prisma.config.ts +14 -0
  105. package/public/.gitkeep +0 -0
  106. package/public/logo.svg +3 -0
  107. 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
+ }
@@ -0,0 +1,7 @@
1
+ dist/
2
+ node_modules/
3
+ .next/
4
+ .turbo/
5
+ coverage/
6
+ pnpm-lock.yaml
7
+ .pnpm-store/
package/.prettierrc ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "endOfLine": "lf",
3
+ "semi": false,
4
+ "singleQuote": false,
5
+ "tabWidth": 2,
6
+ "trailingComma": "es5",
7
+ "printWidth": 80,
8
+ "plugins": ["prettier-plugin-tailwindcss"],
9
+ "tailwindStylesheet": "app/globals.css",
10
+ "tailwindFunctions": ["cn", "cva"]
11
+ }
@@ -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,7 @@
1
+ 'use client'
2
+
3
+ import { CommandUsage } from '@/components/command-usage'
4
+
5
+ export default function CommandsPage() {
6
+ return <CommandUsage />
7
+ }
@@ -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,5 @@
1
+ import { ScreenshotsAnalysis } from '@/components/screenshots-analysis'
2
+
3
+ export default function ImagesPage() {
4
+ return <ScreenshotsAnalysis />
5
+ }
@@ -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
+ }