@bahulam/code 0.1.6 → 0.1.7
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/LICENSE +1 -1
- package/NOTICE +6 -6
- package/README.md +10 -17
- package/package.json +9 -9
- package/src/agents/loader.mjs +1 -1
- package/src/agents/workflow_loader.mjs +2 -2
- package/src/auth/tarang-auth.mjs +3 -2
- package/src/config/model-catalog-default.json +4 -3
- package/src/config/model-catalog.mjs +2 -0
- package/src/context/prose-chunker.mjs +162 -5
- package/src/core/attachments.mjs +65 -3
- package/src/core/bundled-runtime.mjs +1 -1
- package/src/core/headless.mjs +14 -0
- package/src/core/jsonl-writer.mjs +3 -3
- package/src/core/lint-resolver.mjs +472 -0
- package/src/core/local-agent.mjs +15 -4
- package/src/core/local-store.mjs +4 -0
- package/src/core/output-filter.mjs +21 -25
- package/src/core/pricing.mjs +10 -3
- package/src/core/stream-client.mjs +9 -4
- package/src/core/tool-executor.mjs +105 -29
- package/src/daemon/http-dashboard.mjs +400 -0
- package/src/daemon/session-core.mjs +1 -0
- package/src/local-service/agent-relay.mjs +868 -0
- package/src/local-service/approval-bridge.mjs +222 -0
- package/src/local-service/browser.mjs +24 -0
- package/src/local-service/command.mjs +155 -0
- package/src/local-service/file-access.mjs +393 -0
- package/src/local-service/machine.mjs +86 -0
- package/src/local-service/paths.mjs +29 -0
- package/src/local-service/preview-converters.mjs +260 -0
- package/src/local-service/server.mjs +2644 -0
- package/src/local-service/session-store.mjs +221 -0
- package/src/onboarding/preflight.mjs +1 -1
- package/src/terminal/analytics.mjs +2 -6
- package/src/terminal/ansi.mjs +11 -3
- package/src/terminal/main.mjs +37 -11
- package/src/terminal/model-catalog-display.mjs +111 -0
- package/src/terminal/repl-format.mjs +4 -1
- package/src/terminal/repl-model-form.mjs +81 -14
- package/src/terminal/repl-render.mjs +35 -5
- package/src/terminal/repl-state.mjs +12 -0
- package/src/terminal/repl.mjs +231 -53
- package/src/terminal/tool-display.mjs +32 -0
- package/src/terminal/watch-state.mjs +118 -0
- package/src/tools/analyze-image.mjs +158 -0
- package/src/tools/bash.mjs +11 -9
- package/src/tools/generate-image.mjs +411 -0
- package/src/tools/lint.mjs +24 -43
- package/src/tools/registry.mjs +4 -0
- package/src/ui/icons.mjs +2 -0
- package/src/ui/palette.mjs +1 -1
- package/src/ui/slash-commands.mjs +4 -6
- package/src/ui/tool-card.mjs +7 -3
- package/pulse/app/activity/page.tsx +0 -190
- package/pulse/app/api/activity/route.ts +0 -138
- package/pulse/app/api/benchmark/route.ts +0 -113
- package/pulse/app/api/benchmarks/route.ts +0 -195
- package/pulse/app/api/costs/route.ts +0 -88
- package/pulse/app/api/export/route.ts +0 -77
- package/pulse/app/api/history/route.ts +0 -11
- package/pulse/app/api/import/route.ts +0 -31
- package/pulse/app/api/memory/route.ts +0 -50
- package/pulse/app/api/plans/route.ts +0 -9
- package/pulse/app/api/projects/[slug]/route.ts +0 -96
- package/pulse/app/api/projects/route.ts +0 -121
- package/pulse/app/api/sessions/[id]/replay/route.ts +0 -20
- package/pulse/app/api/sessions/[id]/route.ts +0 -31
- package/pulse/app/api/sessions/route.ts +0 -112
- package/pulse/app/api/settings/route.ts +0 -14
- package/pulse/app/api/stats/route.ts +0 -143
- package/pulse/app/api/todos/route.ts +0 -9
- package/pulse/app/api/tools/route.ts +0 -160
- package/pulse/app/benchmarks/page.tsx +0 -224
- package/pulse/app/costs/page.tsx +0 -179
- package/pulse/app/export/page.tsx +0 -465
- package/pulse/app/favicon.ico +0 -0
- package/pulse/app/globals.css +0 -263
- package/pulse/app/help/page.tsx +0 -143
- package/pulse/app/history/page.tsx +0 -157
- package/pulse/app/layout.tsx +0 -46
- package/pulse/app/memory/page.tsx +0 -365
- package/pulse/app/overview-client.tsx +0 -393
- package/pulse/app/page.tsx +0 -14
- package/pulse/app/plans/page.tsx +0 -308
- package/pulse/app/projects/[slug]/page.tsx +0 -390
- package/pulse/app/projects/page.tsx +0 -110
- package/pulse/app/sessions/[id]/page.tsx +0 -243
- package/pulse/app/sessions/page.tsx +0 -39
- package/pulse/app/settings/page.tsx +0 -188
- package/pulse/app/todos/page.tsx +0 -211
- package/pulse/app/tools/page.tsx +0 -249
- package/pulse/cli.js +0 -164
- package/pulse/components/activity/day-of-week-chart.tsx +0 -35
- package/pulse/components/activity/streak-card.tsx +0 -36
- package/pulse/components/costs/cache-efficiency-panel.tsx +0 -76
- package/pulse/components/costs/cost-by-project-chart.tsx +0 -48
- package/pulse/components/costs/cost-over-time-chart.tsx +0 -95
- package/pulse/components/costs/model-token-table.tsx +0 -60
- package/pulse/components/global-search.tsx +0 -193
- package/pulse/components/keyboard-nav-provider.tsx +0 -23
- package/pulse/components/layout/bottom-nav.tsx +0 -53
- package/pulse/components/layout/client-layout.tsx +0 -31
- package/pulse/components/layout/sidebar-context.tsx +0 -50
- package/pulse/components/layout/sidebar.tsx +0 -183
- package/pulse/components/layout/top-bar.tsx +0 -121
- package/pulse/components/overview/activity-heatmap.tsx +0 -107
- package/pulse/components/overview/conversation-table.tsx +0 -148
- package/pulse/components/overview/model-breakdown-donut.tsx +0 -95
- package/pulse/components/overview/peak-hours-chart.tsx +0 -87
- package/pulse/components/overview/project-activity-donut.tsx +0 -96
- package/pulse/components/overview/stat-card.tsx +0 -102
- package/pulse/components/overview/usage-over-time-chart.tsx +0 -166
- package/pulse/components/projects/project-card.tsx +0 -175
- package/pulse/components/sessions/replay/assistant-markdown.tsx +0 -94
- package/pulse/components/sessions/replay/compaction-card.tsx +0 -25
- package/pulse/components/sessions/replay/session-sidebar.tsx +0 -231
- package/pulse/components/sessions/replay/token-accumulation-chart.tsx +0 -98
- package/pulse/components/sessions/replay/tool-call-badge.tsx +0 -127
- package/pulse/components/sessions/replay/turn-cards.tsx +0 -220
- package/pulse/components/sessions/replay/user-tool-result.tsx +0 -158
- package/pulse/components/sessions/session-badges.tsx +0 -49
- package/pulse/components/sessions/session-table.tsx +0 -299
- package/pulse/components/theme-provider.tsx +0 -44
- package/pulse/components/tools/feature-adoption-table.tsx +0 -58
- package/pulse/components/tools/mcp-server-panel.tsx +0 -45
- package/pulse/components/tools/tool-ranking-chart.tsx +0 -57
- package/pulse/components/tools/version-history-table.tsx +0 -32
- package/pulse/components/ui/alert.tsx +0 -66
- package/pulse/components/ui/badge.tsx +0 -48
- package/pulse/components/ui/breadcrumb.tsx +0 -109
- package/pulse/components/ui/button.tsx +0 -64
- package/pulse/components/ui/calendar.tsx +0 -220
- package/pulse/components/ui/card.tsx +0 -92
- package/pulse/components/ui/command.tsx +0 -158
- package/pulse/components/ui/dialog.tsx +0 -158
- package/pulse/components/ui/input.tsx +0 -21
- package/pulse/components/ui/popover.tsx +0 -89
- package/pulse/components/ui/progress.tsx +0 -31
- package/pulse/components/ui/select.tsx +0 -190
- package/pulse/components/ui/separator.tsx +0 -28
- package/pulse/components/ui/sheet.tsx +0 -143
- package/pulse/components/ui/skeleton.tsx +0 -13
- package/pulse/components/ui/table.tsx +0 -116
- package/pulse/components/ui/tabs.tsx +0 -91
- package/pulse/components/ui/tooltip.tsx +0 -57
- package/pulse/components/use-global-keyboard-nav.ts +0 -79
- package/pulse/components.json +0 -23
- package/pulse/eslint.config.mjs +0 -18
- package/pulse/lib/bahulam-paths.ts +0 -23
- package/pulse/lib/claude-reader.ts +0 -592
- package/pulse/lib/decode.ts +0 -129
- package/pulse/lib/pricing.ts +0 -102
- package/pulse/lib/replay-parser.ts +0 -165
- package/pulse/lib/tool-categories.ts +0 -140
- package/pulse/lib/utils.ts +0 -6
- package/pulse/next-env.d.ts +0 -6
- package/pulse/next.config.ts +0 -16
- package/pulse/package.json +0 -45
- package/pulse/postcss.config.mjs +0 -7
- package/pulse/public/activity.png +0 -0
- package/pulse/public/cc-lens.png +0 -0
- package/pulse/public/command-k.png +0 -0
- package/pulse/public/costs.png +0 -0
- package/pulse/public/dashboard-dark.png +0 -0
- package/pulse/public/dashboard-white.png +0 -0
- package/pulse/public/export.png +0 -0
- package/pulse/public/file.svg +0 -1
- package/pulse/public/globe.svg +0 -1
- package/pulse/public/next.svg +0 -1
- package/pulse/public/projects.png +0 -0
- package/pulse/public/session-chat.png +0 -0
- package/pulse/public/todos.png +0 -0
- package/pulse/public/tools.png +0 -0
- package/pulse/public/vercel.svg +0 -1
- package/pulse/public/window.svg +0 -1
- package/pulse/tsconfig.json +0 -34
- package/pulse/types/claude.ts +0 -294
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import useSWR from 'swr'
|
|
4
|
-
import { TopBar } from '@/components/layout/top-bar'
|
|
5
|
-
import { ActivityHeatmap } from '@/components/overview/activity-heatmap'
|
|
6
|
-
import { PeakHoursChart } from '@/components/overview/peak-hours-chart'
|
|
7
|
-
import { DayOfWeekChart } from '@/components/activity/day-of-week-chart'
|
|
8
|
-
import { UsageOverTimeChart } from '@/components/overview/usage-over-time-chart'
|
|
9
|
-
import type { DailyActivity } from '@/types/claude'
|
|
10
|
-
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
|
11
|
-
import { Skeleton } from '@/components/ui/skeleton'
|
|
12
|
-
import { Alert, AlertDescription } from '@/components/ui/alert'
|
|
13
|
-
import { AlertTriangle, Flame, CalendarDays, BarChart3, Clock, Zap, TrendingUp, Star } from 'lucide-react'
|
|
14
|
-
|
|
15
|
-
const fetcher = (url: string) =>
|
|
16
|
-
fetch(url).then(r => { if (!r.ok) throw new Error(`API error ${r.status}`); return r.json() })
|
|
17
|
-
|
|
18
|
-
interface ActivityData {
|
|
19
|
-
daily_activity: DailyActivity[]
|
|
20
|
-
hour_counts: Array<{ hour: number; count: number }>
|
|
21
|
-
dow_counts: Array<{ day: string; count: number }>
|
|
22
|
-
streaks: { current: number; longest: number }
|
|
23
|
-
most_active_day: string
|
|
24
|
-
most_active_day_msgs: number
|
|
25
|
-
total_active_days: number
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function StatTile({
|
|
29
|
-
label,
|
|
30
|
-
value,
|
|
31
|
-
sub,
|
|
32
|
-
icon: Icon,
|
|
33
|
-
color,
|
|
34
|
-
}: {
|
|
35
|
-
label: string
|
|
36
|
-
value: string | number
|
|
37
|
-
sub: string
|
|
38
|
-
icon: React.ElementType
|
|
39
|
-
color: string
|
|
40
|
-
}) {
|
|
41
|
-
return (
|
|
42
|
-
<Card className="gap-0">
|
|
43
|
-
<CardHeader className="pb-2">
|
|
44
|
-
<CardDescription className="flex items-center gap-2 text-sm font-medium">
|
|
45
|
-
<Icon className="h-4 w-4 shrink-0 text-muted-foreground" />
|
|
46
|
-
{label}
|
|
47
|
-
</CardDescription>
|
|
48
|
-
<CardTitle className="text-3xl font-bold tabular-nums leading-none" style={{ color }}>
|
|
49
|
-
{value}
|
|
50
|
-
</CardTitle>
|
|
51
|
-
</CardHeader>
|
|
52
|
-
<CardContent className="pt-0">
|
|
53
|
-
<p className="text-xs text-muted-foreground">{sub}</p>
|
|
54
|
-
</CardContent>
|
|
55
|
-
</Card>
|
|
56
|
-
)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export default function ActivityPage() {
|
|
60
|
-
const { data, error, isLoading } = useSWR<ActivityData>('/api/activity', fetcher, { refreshInterval: 5_000 })
|
|
61
|
-
|
|
62
|
-
const hourCounts = data
|
|
63
|
-
? Object.fromEntries(data.hour_counts.map(h => [String(h.hour), h.count]))
|
|
64
|
-
: {}
|
|
65
|
-
|
|
66
|
-
return (
|
|
67
|
-
<div className="flex flex-col min-h-screen">
|
|
68
|
-
<TopBar title="Activity" subtitle="Patterns, streaks, and peak hours" />
|
|
69
|
-
<div className="w-full space-y-4 p-4 md:p-6">
|
|
70
|
-
|
|
71
|
-
{error && (
|
|
72
|
-
<Alert variant="destructive">
|
|
73
|
-
<AlertTriangle className="h-4 w-4" />
|
|
74
|
-
<AlertDescription>Error loading data: {String(error)}</AlertDescription>
|
|
75
|
-
</Alert>
|
|
76
|
-
)}
|
|
77
|
-
|
|
78
|
-
{isLoading && (
|
|
79
|
-
<>
|
|
80
|
-
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
81
|
-
{Array.from({ length: 4 }).map((_, i) => <Skeleton key={i} className="h-28 rounded-xl" />)}
|
|
82
|
-
</div>
|
|
83
|
-
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 md:items-stretch">
|
|
84
|
-
<Skeleton className="h-72 min-h-72 rounded-xl md:min-h-80" />
|
|
85
|
-
<Skeleton className="h-56 min-h-56 rounded-xl md:min-h-80" />
|
|
86
|
-
</div>
|
|
87
|
-
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 md:items-stretch">
|
|
88
|
-
<Skeleton className="h-64 rounded-xl md:min-h-72" />
|
|
89
|
-
<Skeleton className="h-56 rounded-xl md:min-h-72" />
|
|
90
|
-
</div>
|
|
91
|
-
</>
|
|
92
|
-
)}
|
|
93
|
-
|
|
94
|
-
{data && (
|
|
95
|
-
<>
|
|
96
|
-
{/* Stat tiles */}
|
|
97
|
-
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
98
|
-
<StatTile
|
|
99
|
-
label="Current Streak"
|
|
100
|
-
value={data.streaks.current}
|
|
101
|
-
sub="consecutive days"
|
|
102
|
-
icon={Flame}
|
|
103
|
-
color="#f97316"
|
|
104
|
-
/>
|
|
105
|
-
<StatTile
|
|
106
|
-
label="Longest Streak"
|
|
107
|
-
value={data.streaks.longest}
|
|
108
|
-
sub="personal best"
|
|
109
|
-
icon={Zap}
|
|
110
|
-
color="var(--viz-sky)"
|
|
111
|
-
/>
|
|
112
|
-
<StatTile
|
|
113
|
-
label="Active Days"
|
|
114
|
-
value={data.total_active_days}
|
|
115
|
-
sub="total days with activity"
|
|
116
|
-
icon={TrendingUp}
|
|
117
|
-
color="#a78bfa"
|
|
118
|
-
/>
|
|
119
|
-
<StatTile
|
|
120
|
-
label="Most Active Day"
|
|
121
|
-
value={data.most_active_day ? data.most_active_day.slice(5) : '—'}
|
|
122
|
-
sub={data.most_active_day_msgs ? `${data.most_active_day_msgs.toLocaleString()} messages` : 'no data'}
|
|
123
|
-
icon={Star}
|
|
124
|
-
color="#34d399"
|
|
125
|
-
/>
|
|
126
|
-
</div>
|
|
127
|
-
|
|
128
|
-
{/* Row 1: Activity calendar | Peak hours */}
|
|
129
|
-
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 md:items-stretch">
|
|
130
|
-
<Card className="flex h-full min-h-0 min-w-0 flex-col gap-2 py-4">
|
|
131
|
-
<CardHeader className="space-y-1 pb-0">
|
|
132
|
-
<CardTitle className="flex items-center gap-2 text-base font-semibold">
|
|
133
|
-
<CalendarDays className="h-4 w-4 shrink-0 text-muted-foreground" />
|
|
134
|
-
Activity Calendar
|
|
135
|
-
</CardTitle>
|
|
136
|
-
<CardDescription>GitHub-style contribution heatmap</CardDescription>
|
|
137
|
-
</CardHeader>
|
|
138
|
-
<CardContent className="flex flex-1 flex-col pt-3 pb-0">
|
|
139
|
-
<ActivityHeatmap data={data.daily_activity} />
|
|
140
|
-
</CardContent>
|
|
141
|
-
</Card>
|
|
142
|
-
|
|
143
|
-
<Card className="flex h-full min-h-0 min-w-0 flex-col gap-2 py-4">
|
|
144
|
-
<CardHeader className="space-y-1 pb-0">
|
|
145
|
-
<CardTitle className="flex items-center gap-2 text-base font-semibold">
|
|
146
|
-
<Clock className="h-4 w-4 shrink-0 text-muted-foreground" />
|
|
147
|
-
Peak Hours
|
|
148
|
-
</CardTitle>
|
|
149
|
-
<CardDescription>Activity by hour of day</CardDescription>
|
|
150
|
-
</CardHeader>
|
|
151
|
-
<CardContent className="flex flex-1 flex-col justify-between pt-3 pb-0">
|
|
152
|
-
<PeakHoursChart hourCounts={hourCounts} />
|
|
153
|
-
</CardContent>
|
|
154
|
-
</Card>
|
|
155
|
-
</div>
|
|
156
|
-
|
|
157
|
-
{/* Row 2: Usage over time | Day of week */}
|
|
158
|
-
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 md:items-stretch">
|
|
159
|
-
<Card className="flex h-full min-h-0 min-w-0 flex-col gap-2 py-4">
|
|
160
|
-
<CardHeader className="space-y-1 pb-0">
|
|
161
|
-
<CardTitle className="flex items-center gap-2 text-base font-semibold">
|
|
162
|
-
<BarChart3 className="h-4 w-4 shrink-0 text-muted-foreground" />
|
|
163
|
-
Usage Over Time
|
|
164
|
-
</CardTitle>
|
|
165
|
-
<CardDescription>Messages and sessions over the last 90 days</CardDescription>
|
|
166
|
-
</CardHeader>
|
|
167
|
-
<CardContent className="flex flex-1 flex-col pt-3 pb-0">
|
|
168
|
-
<UsageOverTimeChart data={data.daily_activity} days={90} />
|
|
169
|
-
</CardContent>
|
|
170
|
-
</Card>
|
|
171
|
-
|
|
172
|
-
<Card className="flex h-full min-h-0 min-w-0 flex-col gap-2 py-4">
|
|
173
|
-
<CardHeader className="space-y-1 pb-0">
|
|
174
|
-
<CardTitle className="flex items-center gap-2 text-base font-semibold">
|
|
175
|
-
<CalendarDays className="h-4 w-4 shrink-0 text-muted-foreground" />
|
|
176
|
-
Day of Week
|
|
177
|
-
</CardTitle>
|
|
178
|
-
<CardDescription>Which days you use Bahulam Code most</CardDescription>
|
|
179
|
-
</CardHeader>
|
|
180
|
-
<CardContent className="flex flex-1 flex-col justify-between pt-3 pb-0">
|
|
181
|
-
<DayOfWeekChart data={data.dow_counts} />
|
|
182
|
-
</CardContent>
|
|
183
|
-
</Card>
|
|
184
|
-
</div>
|
|
185
|
-
</>
|
|
186
|
-
)}
|
|
187
|
-
</div>
|
|
188
|
-
</div>
|
|
189
|
-
)
|
|
190
|
-
}
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import { NextResponse } from 'next/server'
|
|
2
|
-
import { getSessions, readStatsCache } from '@/lib/claude-reader'
|
|
3
|
-
import type { DailyActivity, SessionMeta } from '@/types/claude'
|
|
4
|
-
|
|
5
|
-
export const dynamic = 'force-dynamic'
|
|
6
|
-
|
|
7
|
-
function computeStreaks(dates: Set<string>): { current: number; longest: number } {
|
|
8
|
-
const sorted = [...dates].sort()
|
|
9
|
-
if (sorted.length === 0) return { current: 0, longest: 0 }
|
|
10
|
-
|
|
11
|
-
let longest = 1
|
|
12
|
-
let streak = 1
|
|
13
|
-
for (let i = 1; i < sorted.length; i++) {
|
|
14
|
-
const prev = new Date(sorted[i - 1])
|
|
15
|
-
const curr = new Date(sorted[i])
|
|
16
|
-
const diff = (curr.getTime() - prev.getTime()) / 86_400_000
|
|
17
|
-
if (diff === 1) {
|
|
18
|
-
streak++
|
|
19
|
-
if (streak > longest) longest = streak
|
|
20
|
-
} else {
|
|
21
|
-
streak = 1
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// Current streak (from today backwards)
|
|
26
|
-
const today = new Date().toISOString().slice(0, 10)
|
|
27
|
-
let current = 0
|
|
28
|
-
const d = new Date(today)
|
|
29
|
-
while (dates.has(d.toISOString().slice(0, 10))) {
|
|
30
|
-
current++
|
|
31
|
-
d.setDate(d.getDate() - 1)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return { current, longest }
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function computeDailyActivityFromSessions(sessions: SessionMeta[]): DailyActivity[] {
|
|
38
|
-
const byDate = new Map<string, { messages: number; sessions: number; tools: number }>()
|
|
39
|
-
|
|
40
|
-
for (const s of sessions) {
|
|
41
|
-
if (!s.start_time) continue
|
|
42
|
-
const date = s.start_time.slice(0, 10)
|
|
43
|
-
if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) continue
|
|
44
|
-
|
|
45
|
-
const existing = byDate.get(date) ?? { messages: 0, sessions: 0, tools: 0 }
|
|
46
|
-
existing.messages += (s.user_message_count ?? 0) + (s.assistant_message_count ?? 0)
|
|
47
|
-
existing.sessions += 1
|
|
48
|
-
existing.tools += Object.values(s.tool_counts ?? {}).reduce((sum, count) => sum + count, 0)
|
|
49
|
-
byDate.set(date, existing)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return Array.from(byDate.entries())
|
|
53
|
-
.map(([date, { messages, sessions: sessionCount, tools }]) => ({
|
|
54
|
-
date,
|
|
55
|
-
messageCount: messages,
|
|
56
|
-
sessionCount,
|
|
57
|
-
toolCallCount: tools,
|
|
58
|
-
}))
|
|
59
|
-
.sort((a, b) => a.date.localeCompare(b.date))
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function mergeDailyActivity(fromStats: DailyActivity[], fromSessions: DailyActivity[]): DailyActivity[] {
|
|
63
|
-
const byDate = new Map<string, DailyActivity>()
|
|
64
|
-
for (const d of fromStats) byDate.set(d.date, d)
|
|
65
|
-
for (const d of fromSessions) byDate.set(d.date, d)
|
|
66
|
-
return Array.from(byDate.values()).sort((a, b) => a.date.localeCompare(b.date))
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function computeHourCounts(sessions: SessionMeta[]): Array<{ hour: number; count: number }> {
|
|
70
|
-
const hourCounts = Array.from({ length: 24 }, () => 0)
|
|
71
|
-
|
|
72
|
-
for (const s of sessions) {
|
|
73
|
-
if (s.user_message_timestamps?.length) {
|
|
74
|
-
for (const ts of s.user_message_timestamps) {
|
|
75
|
-
const d = new Date(ts)
|
|
76
|
-
if (!isNaN(d.getTime())) hourCounts[d.getHours()]++
|
|
77
|
-
}
|
|
78
|
-
continue
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (s.message_hours?.length) {
|
|
82
|
-
for (const hour of s.message_hours) {
|
|
83
|
-
if (Number.isInteger(hour) && hour >= 0 && hour < 24) hourCounts[hour]++
|
|
84
|
-
}
|
|
85
|
-
continue
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const d = new Date(s.start_time)
|
|
89
|
-
if (!isNaN(d.getTime())) hourCounts[d.getHours()]++
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return hourCounts.map((count, hour) => ({ hour, count }))
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export async function GET() {
|
|
96
|
-
const [stats, sessions] = await Promise.all([readStatsCache(), getSessions()])
|
|
97
|
-
const dailyFromSessions = computeDailyActivityFromSessions(sessions)
|
|
98
|
-
const dailyActivity = stats
|
|
99
|
-
? mergeDailyActivity(stats.dailyActivity ?? [], dailyFromSessions)
|
|
100
|
-
: dailyFromSessions
|
|
101
|
-
|
|
102
|
-
// Day-of-week counts from session timestamps
|
|
103
|
-
const dowCounts: number[] = [0, 0, 0, 0, 0, 0, 0] // Sun=0..Sat=6
|
|
104
|
-
const activeDates = new Set<string>()
|
|
105
|
-
|
|
106
|
-
for (const s of sessions) {
|
|
107
|
-
if (!s.start_time) continue
|
|
108
|
-
const d = new Date(s.start_time)
|
|
109
|
-
if (isNaN(d.getTime())) continue
|
|
110
|
-
dowCounts[d.getDay()]++
|
|
111
|
-
activeDates.add(s.start_time.slice(0, 10))
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const streaks = computeStreaks(activeDates)
|
|
115
|
-
|
|
116
|
-
// Most active day
|
|
117
|
-
let mostActiveDay = ''
|
|
118
|
-
let mostActiveMsgs = 0
|
|
119
|
-
for (const da of dailyActivity) {
|
|
120
|
-
if (da.messageCount > mostActiveMsgs) {
|
|
121
|
-
mostActiveMsgs = da.messageCount
|
|
122
|
-
mostActiveDay = da.date
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return NextResponse.json({
|
|
127
|
-
daily_activity: dailyActivity,
|
|
128
|
-
hour_counts: computeHourCounts(sessions),
|
|
129
|
-
dow_counts: dowCounts.map((count, i) => ({
|
|
130
|
-
day: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'][i],
|
|
131
|
-
count,
|
|
132
|
-
})),
|
|
133
|
-
streaks,
|
|
134
|
-
most_active_day: mostActiveDay,
|
|
135
|
-
most_active_day_msgs: mostActiveMsgs,
|
|
136
|
-
total_active_days: activeDates.size,
|
|
137
|
-
})
|
|
138
|
-
}
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import { NextResponse } from 'next/server'
|
|
2
|
-
import { readFileSync } from 'fs'
|
|
3
|
-
import { join } from 'path'
|
|
4
|
-
|
|
5
|
-
export const dynamic = 'force-dynamic'
|
|
6
|
-
|
|
7
|
-
interface BenchmarkResult {
|
|
8
|
-
instance_id: string
|
|
9
|
-
repo: string
|
|
10
|
-
base_commit: string
|
|
11
|
-
test_patch: string
|
|
12
|
-
resolved: boolean
|
|
13
|
-
test_result: {
|
|
14
|
-
result: string[]
|
|
15
|
-
exit_code: number
|
|
16
|
-
}
|
|
17
|
-
metadata: {
|
|
18
|
-
agent_class: string
|
|
19
|
-
model_name: string
|
|
20
|
-
max_iterations: number
|
|
21
|
-
eval_history: Array<{
|
|
22
|
-
timestamp: string
|
|
23
|
-
action: string
|
|
24
|
-
observation: string
|
|
25
|
-
}>
|
|
26
|
-
submission: string
|
|
27
|
-
instance_id: string
|
|
28
|
-
predict_output: string
|
|
29
|
-
model_patch: string
|
|
30
|
-
test_result: {
|
|
31
|
-
result: string[]
|
|
32
|
-
exit_code: number
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
interface BenchmarkData {
|
|
38
|
-
results: BenchmarkResult[]
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export async function GET(request: Request) {
|
|
42
|
-
try {
|
|
43
|
-
const { searchParams } = new URL(request.url)
|
|
44
|
-
const run = searchParams.get('run') || 'swebench-v4-flash-300'
|
|
45
|
-
const limit = parseInt(searchParams.get('limit') || '50', 10)
|
|
46
|
-
const offset = parseInt(searchParams.get('offset') || '0', 10)
|
|
47
|
-
|
|
48
|
-
// Load benchmark results from file
|
|
49
|
-
const resultsPath = join(
|
|
50
|
-
process.cwd(),
|
|
51
|
-
'..',
|
|
52
|
-
'benchmark',
|
|
53
|
-
'results',
|
|
54
|
-
'runs',
|
|
55
|
-
run,
|
|
56
|
-
'harness-results.json'
|
|
57
|
-
)
|
|
58
|
-
|
|
59
|
-
let data: BenchmarkData
|
|
60
|
-
try {
|
|
61
|
-
const fileContent = readFileSync(resultsPath, 'utf-8')
|
|
62
|
-
data = JSON.parse(fileContent)
|
|
63
|
-
} catch (error) {
|
|
64
|
-
return NextResponse.json(
|
|
65
|
-
{ error: `Benchmark run "${run}" not found` },
|
|
66
|
-
{ status: 404 }
|
|
67
|
-
)
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Calculate statistics
|
|
71
|
-
const results = data.results || []
|
|
72
|
-
const totalTests = results.length
|
|
73
|
-
const resolvedTests = results.filter((r) => r.resolved).length
|
|
74
|
-
const passRate = totalTests > 0 ? (resolvedTests / totalTests) * 100 : 0
|
|
75
|
-
|
|
76
|
-
// Group by repo
|
|
77
|
-
const byRepo = new Map<string, number>()
|
|
78
|
-
const byRepoResolved = new Map<string, number>()
|
|
79
|
-
for (const result of results) {
|
|
80
|
-
const repo = result.repo || 'unknown'
|
|
81
|
-
byRepo.set(repo, (byRepo.get(repo) || 0) + 1)
|
|
82
|
-
if (result.resolved) {
|
|
83
|
-
byRepoResolved.set(repo, (byRepoResolved.get(repo) || 0) + 1)
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Paginate results
|
|
88
|
-
const paginatedResults = results.slice(offset, offset + limit)
|
|
89
|
-
|
|
90
|
-
return NextResponse.json({
|
|
91
|
-
run,
|
|
92
|
-
stats: {
|
|
93
|
-
totalTests,
|
|
94
|
-
resolvedTests,
|
|
95
|
-
passRate: parseFloat(passRate.toFixed(2)),
|
|
96
|
-
byRepo: Object.fromEntries(byRepo),
|
|
97
|
-
byRepoResolved: Object.fromEntries(byRepoResolved),
|
|
98
|
-
},
|
|
99
|
-
pagination: {
|
|
100
|
-
limit,
|
|
101
|
-
offset,
|
|
102
|
-
total: totalTests,
|
|
103
|
-
},
|
|
104
|
-
results: paginatedResults,
|
|
105
|
-
})
|
|
106
|
-
} catch (error) {
|
|
107
|
-
console.error('Benchmark API error:', error)
|
|
108
|
-
return NextResponse.json(
|
|
109
|
-
{ error: 'Failed to load benchmark data' },
|
|
110
|
-
{ status: 500 }
|
|
111
|
-
)
|
|
112
|
-
}
|
|
113
|
-
}
|
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
import { NextResponse } from 'next/server'
|
|
2
|
-
import fs from 'fs'
|
|
3
|
-
import path from 'path'
|
|
4
|
-
|
|
5
|
-
export const dynamic = 'force-dynamic'
|
|
6
|
-
|
|
7
|
-
interface BenchmarkResult {
|
|
8
|
-
instance_id: string
|
|
9
|
-
repo: string
|
|
10
|
-
model: string
|
|
11
|
-
timestamp: string
|
|
12
|
-
kepler: {
|
|
13
|
-
status: string
|
|
14
|
-
exit_code: number
|
|
15
|
-
duration_seconds: number
|
|
16
|
-
tokens_used: number
|
|
17
|
-
cost: number
|
|
18
|
-
tool_calls: number
|
|
19
|
-
sub_agents: string[]
|
|
20
|
-
}
|
|
21
|
-
patch_lines: number
|
|
22
|
-
model_patch: string
|
|
23
|
-
status: string
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
interface BenchmarkStats {
|
|
27
|
-
total_runs: number
|
|
28
|
-
passed: number
|
|
29
|
-
failed: number
|
|
30
|
-
error: number
|
|
31
|
-
success_rate: number
|
|
32
|
-
avg_duration: number
|
|
33
|
-
total_cost: number
|
|
34
|
-
total_tokens: number
|
|
35
|
-
avg_tokens_per_run: number
|
|
36
|
-
by_status: Record<string, number>
|
|
37
|
-
by_repo: Record<string, { count: number; passed: number; success_rate: number }>
|
|
38
|
-
by_model: Record<string, { count: number; passed: number; success_rate: number }>
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
async function loadBenchmarkResults(): Promise<BenchmarkResult[]> {
|
|
42
|
-
try {
|
|
43
|
-
const resultsPath = path.join(
|
|
44
|
-
process.cwd(),
|
|
45
|
-
'benchmark/results/runs/swebench-v4-flash-300/harness-results.json'
|
|
46
|
-
)
|
|
47
|
-
|
|
48
|
-
if (!fs.existsSync(resultsPath)) {
|
|
49
|
-
return []
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const data = JSON.parse(fs.readFileSync(resultsPath, 'utf-8'))
|
|
53
|
-
return data.results || []
|
|
54
|
-
} catch (error) {
|
|
55
|
-
console.error('Error loading benchmark results:', error)
|
|
56
|
-
return []
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function calculateStats(results: BenchmarkResult[]): BenchmarkStats {
|
|
61
|
-
if (results.length === 0) {
|
|
62
|
-
return {
|
|
63
|
-
total_runs: 0,
|
|
64
|
-
passed: 0,
|
|
65
|
-
failed: 0,
|
|
66
|
-
error: 0,
|
|
67
|
-
success_rate: 0,
|
|
68
|
-
avg_duration: 0,
|
|
69
|
-
total_cost: 0,
|
|
70
|
-
total_tokens: 0,
|
|
71
|
-
avg_tokens_per_run: 0,
|
|
72
|
-
by_status: {},
|
|
73
|
-
by_repo: {},
|
|
74
|
-
by_model: {},
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const by_status: Record<string, number> = {}
|
|
79
|
-
const by_repo: Record<string, { count: number; passed: number }> = {}
|
|
80
|
-
const by_model: Record<string, { count: number; passed: number }> = {}
|
|
81
|
-
|
|
82
|
-
let total_cost = 0
|
|
83
|
-
let total_tokens = 0
|
|
84
|
-
let total_duration = 0
|
|
85
|
-
let passed = 0
|
|
86
|
-
|
|
87
|
-
results.forEach((result) => {
|
|
88
|
-
// Count by status
|
|
89
|
-
by_status[result.status] = (by_status[result.status] || 0) + 1
|
|
90
|
-
|
|
91
|
-
// Count by repo
|
|
92
|
-
if (!by_repo[result.repo]) {
|
|
93
|
-
by_repo[result.repo] = { count: 0, passed: 0 }
|
|
94
|
-
}
|
|
95
|
-
by_repo[result.repo].count++
|
|
96
|
-
|
|
97
|
-
// Count by model
|
|
98
|
-
if (!by_model[result.model]) {
|
|
99
|
-
by_model[result.model] = { count: 0, passed: 0 }
|
|
100
|
-
}
|
|
101
|
-
by_model[result.model].count++
|
|
102
|
-
|
|
103
|
-
// Aggregate metrics
|
|
104
|
-
if (result.kepler) {
|
|
105
|
-
total_cost += result.kepler.cost || 0
|
|
106
|
-
total_tokens += result.kepler.tokens_used || 0
|
|
107
|
-
total_duration += result.kepler.duration_seconds || 0
|
|
108
|
-
|
|
109
|
-
if (result.kepler.status === 'success') {
|
|
110
|
-
passed++
|
|
111
|
-
by_repo[result.repo].passed++
|
|
112
|
-
by_model[result.model].passed++
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
// Calculate success rates
|
|
118
|
-
const by_repo_with_rates = Object.entries(by_repo).reduce(
|
|
119
|
-
(acc, [repo, data]) => {
|
|
120
|
-
acc[repo] = {
|
|
121
|
-
...data,
|
|
122
|
-
success_rate: data.count > 0 ? (data.passed / data.count) * 100 : 0,
|
|
123
|
-
}
|
|
124
|
-
return acc
|
|
125
|
-
},
|
|
126
|
-
{} as Record<string, { count: number; passed: number; success_rate: number }>
|
|
127
|
-
)
|
|
128
|
-
|
|
129
|
-
const by_model_with_rates = Object.entries(by_model).reduce(
|
|
130
|
-
(acc, [model, data]) => {
|
|
131
|
-
acc[model] = {
|
|
132
|
-
...data,
|
|
133
|
-
success_rate: data.count > 0 ? (data.passed / data.count) * 100 : 0,
|
|
134
|
-
}
|
|
135
|
-
return acc
|
|
136
|
-
},
|
|
137
|
-
{} as Record<string, { count: number; passed: number; success_rate: number }>
|
|
138
|
-
)
|
|
139
|
-
|
|
140
|
-
return {
|
|
141
|
-
total_runs: results.length,
|
|
142
|
-
passed,
|
|
143
|
-
failed: by_status['failed'] || 0,
|
|
144
|
-
error: by_status['error'] || 0,
|
|
145
|
-
success_rate: (passed / results.length) * 100,
|
|
146
|
-
avg_duration: total_duration / results.length,
|
|
147
|
-
total_cost,
|
|
148
|
-
total_tokens,
|
|
149
|
-
avg_tokens_per_run: total_tokens / results.length,
|
|
150
|
-
by_status,
|
|
151
|
-
by_repo: by_repo_with_rates,
|
|
152
|
-
by_model: by_model_with_rates,
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
export async function GET(request: Request) {
|
|
157
|
-
const { searchParams } = new URL(request.url)
|
|
158
|
-
const format = searchParams.get('format') || 'summary'
|
|
159
|
-
const repo = searchParams.get('repo')
|
|
160
|
-
const model = searchParams.get('model')
|
|
161
|
-
const status = searchParams.get('status')
|
|
162
|
-
|
|
163
|
-
const results = await loadBenchmarkResults()
|
|
164
|
-
|
|
165
|
-
// Filter results
|
|
166
|
-
let filtered = results
|
|
167
|
-
if (repo) {
|
|
168
|
-
filtered = filtered.filter((r) => r.repo === repo)
|
|
169
|
-
}
|
|
170
|
-
if (model) {
|
|
171
|
-
filtered = filtered.filter((r) => r.model === model)
|
|
172
|
-
}
|
|
173
|
-
if (status) {
|
|
174
|
-
filtered = filtered.filter((r) => r.status === status)
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
if (format === 'detailed') {
|
|
178
|
-
return NextResponse.json({
|
|
179
|
-
results: filtered,
|
|
180
|
-
count: filtered.length,
|
|
181
|
-
})
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// Default: summary format
|
|
185
|
-
const stats = calculateStats(filtered)
|
|
186
|
-
|
|
187
|
-
return NextResponse.json({
|
|
188
|
-
stats,
|
|
189
|
-
filters: {
|
|
190
|
-
repo: repo || null,
|
|
191
|
-
model: model || null,
|
|
192
|
-
status: status || null,
|
|
193
|
-
},
|
|
194
|
-
})
|
|
195
|
-
}
|