@bahulam/code 0.1.5 → 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 +112 -16
- package/src/core/tool-executor.mjs +123 -31
- 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 +255 -55
- 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/project-overview.mjs +5 -2
- 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,143 +0,0 @@
|
|
|
1
|
-
import { NextResponse } from 'next/server'
|
|
2
|
-
import { readStatsCache, getSessions, getClaudeStorageBytes } from '@/lib/claude-reader'
|
|
3
|
-
import { estimateTotalCostFromModel, getPricing } from '@/lib/pricing'
|
|
4
|
-
import type { DailyActivity, SessionMeta } from '@/types/claude'
|
|
5
|
-
|
|
6
|
-
export const dynamic = 'force-dynamic'
|
|
7
|
-
|
|
8
|
-
/** Compute daily activity from session JSONL — fresher than stats-cache */
|
|
9
|
-
function computeDailyActivityFromSessions(sessions: SessionMeta[]): DailyActivity[] {
|
|
10
|
-
const byDate = new Map<string, { messages: number; sessions: number; tools: number }>()
|
|
11
|
-
for (const s of sessions) {
|
|
12
|
-
const date = s.start_time.slice(0, 10)
|
|
13
|
-
if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) continue
|
|
14
|
-
const existing = byDate.get(date) ?? { messages: 0, sessions: 0, tools: 0 }
|
|
15
|
-
existing.messages += (s.user_message_count ?? 0) + (s.assistant_message_count ?? 0)
|
|
16
|
-
existing.sessions += 1
|
|
17
|
-
existing.tools += Object.values(s.tool_counts ?? {}).reduce((a, b) => a + b, 0)
|
|
18
|
-
byDate.set(date, existing)
|
|
19
|
-
}
|
|
20
|
-
return Array.from(byDate.entries())
|
|
21
|
-
.map(([date, { messages, sessions: count, tools }]) => ({
|
|
22
|
-
date,
|
|
23
|
-
messageCount: messages,
|
|
24
|
-
sessionCount: count,
|
|
25
|
-
toolCallCount: tools,
|
|
26
|
-
}))
|
|
27
|
-
.sort((a, b) => a.date.localeCompare(b.date))
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/** Merge stats dailyActivity with session-derived data; session data overrides for same dates */
|
|
31
|
-
function mergeDailyActivity(
|
|
32
|
-
fromStats: DailyActivity[],
|
|
33
|
-
fromSessions: DailyActivity[]
|
|
34
|
-
): DailyActivity[] {
|
|
35
|
-
const map = new Map<string, DailyActivity>()
|
|
36
|
-
for (const d of fromStats) map.set(d.date, d)
|
|
37
|
-
for (const d of fromSessions) map.set(d.date, d)
|
|
38
|
-
return Array.from(map.values()).sort((a, b) => a.date.localeCompare(b.date))
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export async function GET() {
|
|
42
|
-
const [stats, sessions, storageBytes] = await Promise.all([
|
|
43
|
-
readStatsCache(),
|
|
44
|
-
getSessions(),
|
|
45
|
-
getClaudeStorageBytes(),
|
|
46
|
-
])
|
|
47
|
-
|
|
48
|
-
const dailyFromSessions = computeDailyActivityFromSessions(sessions)
|
|
49
|
-
const dailyActivity = stats
|
|
50
|
-
? mergeDailyActivity(stats.dailyActivity ?? [], dailyFromSessions)
|
|
51
|
-
: dailyFromSessions
|
|
52
|
-
|
|
53
|
-
const modelUsage = stats?.modelUsage ?? {}
|
|
54
|
-
|
|
55
|
-
// Compute estimated total cost from modelUsage
|
|
56
|
-
let totalCost = 0
|
|
57
|
-
let totalCacheSavings = 0
|
|
58
|
-
for (const [model, usage] of Object.entries(modelUsage)) {
|
|
59
|
-
const cost = estimateTotalCostFromModel(model, usage)
|
|
60
|
-
totalCost += cost
|
|
61
|
-
const p = getPricing(model)
|
|
62
|
-
totalCacheSavings += (usage.cacheReadInputTokens ?? 0) * (p.input - p.cacheRead)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Compute total tokens
|
|
66
|
-
let totalInputTokens = 0
|
|
67
|
-
let totalOutputTokens = 0
|
|
68
|
-
let totalCacheReadTokens = 0
|
|
69
|
-
let totalCacheWriteTokens = 0
|
|
70
|
-
for (const usage of Object.values(modelUsage)) {
|
|
71
|
-
totalInputTokens += usage.inputTokens ?? 0
|
|
72
|
-
totalOutputTokens += usage.outputTokens ?? 0
|
|
73
|
-
totalCacheReadTokens += usage.cacheReadInputTokens ?? 0
|
|
74
|
-
totalCacheWriteTokens += usage.cacheCreationInputTokens ?? 0
|
|
75
|
-
}
|
|
76
|
-
const totalTokens = totalInputTokens + totalOutputTokens + totalCacheReadTokens + totalCacheWriteTokens
|
|
77
|
-
|
|
78
|
-
// Aggregate tool calls total
|
|
79
|
-
let totalToolCalls = 0
|
|
80
|
-
for (const s of sessions) {
|
|
81
|
-
for (const count of Object.values(s.tool_counts ?? {})) {
|
|
82
|
-
totalToolCalls += count
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Active days (days with at least 1 session)
|
|
87
|
-
const activeDays = dailyActivity.filter(d => d.sessionCount > 0).length
|
|
88
|
-
|
|
89
|
-
// Average session length
|
|
90
|
-
const avgSessionMinutes =
|
|
91
|
-
sessions.length > 0
|
|
92
|
-
? sessions.reduce((sum, s) => sum + (s.duration_minutes ?? 0), 0) / sessions.length
|
|
93
|
-
: 0
|
|
94
|
-
|
|
95
|
-
// Sessions this month & week
|
|
96
|
-
const now = new Date()
|
|
97
|
-
const monthStart = new Date(now.getFullYear(), now.getMonth(), 1)
|
|
98
|
-
const weekStart = new Date(now)
|
|
99
|
-
weekStart.setDate(now.getDate() - 7)
|
|
100
|
-
|
|
101
|
-
const sessionsThisMonth = sessions.filter(
|
|
102
|
-
s => new Date(s.start_time) >= monthStart
|
|
103
|
-
).length
|
|
104
|
-
const sessionsThisWeek = sessions.filter(
|
|
105
|
-
s => new Date(s.start_time) >= weekStart
|
|
106
|
-
).length
|
|
107
|
-
|
|
108
|
-
const statsOut = stats
|
|
109
|
-
? { ...stats, dailyActivity }
|
|
110
|
-
: {
|
|
111
|
-
version: 0,
|
|
112
|
-
lastComputedDate: '',
|
|
113
|
-
dailyActivity,
|
|
114
|
-
tokensByDate: [],
|
|
115
|
-
modelUsage: {},
|
|
116
|
-
totalSessions: sessions.length,
|
|
117
|
-
totalMessages: sessions.reduce((s, m) => s + (m.user_message_count ?? 0) + (m.assistant_message_count ?? 0), 0),
|
|
118
|
-
longestSession: { sessionId: '', duration: 0, messageCount: 0, timestamp: '' },
|
|
119
|
-
firstSessionDate: sessions[sessions.length - 1]?.start_time ?? '',
|
|
120
|
-
hourCounts: {},
|
|
121
|
-
totalSpeculationTimeSavedMs: 0,
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return NextResponse.json({
|
|
125
|
-
stats: statsOut,
|
|
126
|
-
computed: {
|
|
127
|
-
totalCost,
|
|
128
|
-
totalCacheSavings,
|
|
129
|
-
totalTokens,
|
|
130
|
-
totalInputTokens,
|
|
131
|
-
totalOutputTokens,
|
|
132
|
-
totalCacheReadTokens,
|
|
133
|
-
totalCacheWriteTokens,
|
|
134
|
-
totalToolCalls,
|
|
135
|
-
activeDays,
|
|
136
|
-
avgSessionMinutes,
|
|
137
|
-
sessionsThisMonth,
|
|
138
|
-
sessionsThisWeek,
|
|
139
|
-
storageBytes,
|
|
140
|
-
sessionCount: sessions.length,
|
|
141
|
-
},
|
|
142
|
-
})
|
|
143
|
-
}
|
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
import path from 'path'
|
|
2
|
-
import { NextResponse } from 'next/server'
|
|
3
|
-
import { getSessions, listProjectSlugs, listProjectJSONLFiles, readJSONLLines } from '@/lib/claude-reader'
|
|
4
|
-
import { categorizeTool, isMcpTool, parseMcpTool } from '@/lib/tool-categories'
|
|
5
|
-
import type { ToolsAnalytics, ToolSummary, McpServerSummary, VersionRecord } from '@/types/claude'
|
|
6
|
-
|
|
7
|
-
export const dynamic = 'force-dynamic'
|
|
8
|
-
|
|
9
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
10
|
-
type AnyLine = Record<string, any>
|
|
11
|
-
|
|
12
|
-
export async function GET() {
|
|
13
|
-
const sessions = await getSessions()
|
|
14
|
-
const totalSessions = sessions.length
|
|
15
|
-
|
|
16
|
-
// ── Aggregate tool counts across all sessions ──────────────────────────────
|
|
17
|
-
const toolTotals = new Map<string, number>()
|
|
18
|
-
const toolSessionCount = new Map<string, Set<string>>()
|
|
19
|
-
const mcpServerCalls = new Map<string, Map<string, number>>()
|
|
20
|
-
const mcpServerSessions = new Map<string, Set<string>>()
|
|
21
|
-
const errorCategories: Record<string, number> = {}
|
|
22
|
-
let totalErrors = 0
|
|
23
|
-
|
|
24
|
-
for (const s of sessions) {
|
|
25
|
-
const sid = s.session_id
|
|
26
|
-
for (const [tool, count] of Object.entries(s.tool_counts ?? {})) {
|
|
27
|
-
toolTotals.set(tool, (toolTotals.get(tool) ?? 0) + count)
|
|
28
|
-
if (!toolSessionCount.has(tool)) toolSessionCount.set(tool, new Set())
|
|
29
|
-
toolSessionCount.get(tool)!.add(sid)
|
|
30
|
-
|
|
31
|
-
if (isMcpTool(tool)) {
|
|
32
|
-
const parsed = parseMcpTool(tool)
|
|
33
|
-
if (parsed) {
|
|
34
|
-
if (!mcpServerCalls.has(parsed.server)) mcpServerCalls.set(parsed.server, new Map())
|
|
35
|
-
if (!mcpServerSessions.has(parsed.server)) mcpServerSessions.set(parsed.server, new Set())
|
|
36
|
-
const srv = mcpServerCalls.get(parsed.server)!
|
|
37
|
-
srv.set(parsed.tool, (srv.get(parsed.tool) ?? 0) + count)
|
|
38
|
-
mcpServerSessions.get(parsed.server)!.add(sid)
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Error categories
|
|
44
|
-
for (const [cat, count] of Object.entries(s.tool_error_categories ?? {})) {
|
|
45
|
-
errorCategories[cat] = (errorCategories[cat] ?? 0) + count
|
|
46
|
-
totalErrors += count
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// ── Build ToolSummary list ─────────────────────────────────────────────────
|
|
51
|
-
const tools: ToolSummary[] = [...toolTotals.entries()]
|
|
52
|
-
.map(([name, total_calls]) => ({
|
|
53
|
-
name,
|
|
54
|
-
category: categorizeTool(name),
|
|
55
|
-
total_calls,
|
|
56
|
-
session_count: toolSessionCount.get(name)?.size ?? 0,
|
|
57
|
-
error_count: 0,
|
|
58
|
-
}))
|
|
59
|
-
.sort((a, b) => b.total_calls - a.total_calls)
|
|
60
|
-
|
|
61
|
-
const totalToolCalls = tools.reduce((s, t) => s + t.total_calls, 0)
|
|
62
|
-
|
|
63
|
-
// ── MCP server summaries ───────────────────────────────────────────────────
|
|
64
|
-
const mcp_servers: McpServerSummary[] = [...mcpServerCalls.entries()]
|
|
65
|
-
.map(([server_name, toolMap]) => {
|
|
66
|
-
const toolArr = [...toolMap.entries()]
|
|
67
|
-
.map(([name, calls]) => ({ name, calls }))
|
|
68
|
-
.sort((a, b) => b.calls - a.calls)
|
|
69
|
-
const total_calls = toolArr.reduce((s, t) => s + t.calls, 0)
|
|
70
|
-
return {
|
|
71
|
-
server_name,
|
|
72
|
-
tools: toolArr,
|
|
73
|
-
total_calls,
|
|
74
|
-
session_count: mcpServerSessions.get(server_name)?.size ?? 0,
|
|
75
|
-
}
|
|
76
|
-
})
|
|
77
|
-
.sort((a, b) => b.total_calls - a.total_calls)
|
|
78
|
-
|
|
79
|
-
// ── Feature adoption ──────────────────────────────────────────────────────
|
|
80
|
-
const featureSessions = {
|
|
81
|
-
task_agents: sessions.filter(s => s.uses_task_agent || (s.tool_counts?.Task ?? 0) > 0).length,
|
|
82
|
-
mcp: sessions.filter(s => s.uses_mcp || Object.keys(s.tool_counts ?? {}).some(isMcpTool)).length,
|
|
83
|
-
web_search: sessions.filter(s => s.uses_web_search || (s.tool_counts?.WebSearch ?? 0) > 0).length,
|
|
84
|
-
web_fetch: sessions.filter(s => s.uses_web_fetch || (s.tool_counts?.WebFetch ?? 0) > 0).length,
|
|
85
|
-
plan_mode: sessions.filter(s => (s.tool_counts?.EnterPlanMode ?? 0) > 0).length,
|
|
86
|
-
git_commits: sessions.filter(s => (s.git_commits ?? 0) > 0).length,
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const feature_adoption: Record<string, { sessions: number; pct: number }> = {}
|
|
90
|
-
for (const [key, count] of Object.entries(featureSessions)) {
|
|
91
|
-
feature_adoption[key] = { sessions: count, pct: totalSessions > 0 ? count / totalSessions : 0 }
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// ── Version + branch info from JSONL ─────────────────────────────────────
|
|
95
|
-
const versionData = new Map<string, { sessions: Set<string>; dates: string[] }>()
|
|
96
|
-
const branchTurns = new Map<string, number>()
|
|
97
|
-
|
|
98
|
-
const slugs = await listProjectSlugs()
|
|
99
|
-
await Promise.all(
|
|
100
|
-
slugs.map(async (slug) => {
|
|
101
|
-
const files = await listProjectJSONLFiles(slug)
|
|
102
|
-
await Promise.all(
|
|
103
|
-
files.map(async (f) => {
|
|
104
|
-
const sessionId = path.basename(f, '.jsonl')
|
|
105
|
-
let fileVersion: string | undefined
|
|
106
|
-
let fileDate: string | undefined
|
|
107
|
-
|
|
108
|
-
await readJSONLLines(f, (line: AnyLine) => {
|
|
109
|
-
if (!fileVersion && line.version) {
|
|
110
|
-
fileVersion = line.version
|
|
111
|
-
fileDate = line.timestamp
|
|
112
|
-
}
|
|
113
|
-
if (line.gitBranch && line.gitBranch !== 'HEAD') {
|
|
114
|
-
branchTurns.set(line.gitBranch, (branchTurns.get(line.gitBranch) ?? 0) + 1)
|
|
115
|
-
}
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
if (fileVersion) {
|
|
119
|
-
if (!versionData.has(fileVersion)) {
|
|
120
|
-
versionData.set(fileVersion, { sessions: new Set(), dates: [] })
|
|
121
|
-
}
|
|
122
|
-
const vd = versionData.get(fileVersion)!
|
|
123
|
-
vd.sessions.add(sessionId)
|
|
124
|
-
if (fileDate) vd.dates.push(fileDate)
|
|
125
|
-
}
|
|
126
|
-
})
|
|
127
|
-
)
|
|
128
|
-
})
|
|
129
|
-
)
|
|
130
|
-
|
|
131
|
-
const versions: VersionRecord[] = [...versionData.entries()]
|
|
132
|
-
.map(([version, data]) => {
|
|
133
|
-
const sortedDates = data.dates.sort()
|
|
134
|
-
return {
|
|
135
|
-
version,
|
|
136
|
-
session_count: data.sessions.size,
|
|
137
|
-
first_seen: sortedDates[0] ?? '',
|
|
138
|
-
last_seen: sortedDates[sortedDates.length - 1] ?? '',
|
|
139
|
-
}
|
|
140
|
-
})
|
|
141
|
-
.sort((a, b) => b.last_seen.localeCompare(a.last_seen))
|
|
142
|
-
|
|
143
|
-
const branches = [...branchTurns.entries()]
|
|
144
|
-
.map(([branch, turns]) => ({ branch, turns }))
|
|
145
|
-
.sort((a, b) => b.turns - a.turns)
|
|
146
|
-
.slice(0, 15)
|
|
147
|
-
|
|
148
|
-
const result: ToolsAnalytics = {
|
|
149
|
-
tools,
|
|
150
|
-
mcp_servers,
|
|
151
|
-
feature_adoption,
|
|
152
|
-
versions,
|
|
153
|
-
branches,
|
|
154
|
-
error_categories: errorCategories,
|
|
155
|
-
total_tool_calls: totalToolCalls,
|
|
156
|
-
total_errors: totalErrors,
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
return NextResponse.json(result)
|
|
160
|
-
}
|
|
@@ -1,224 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { useEffect, useState } from 'react'
|
|
4
|
-
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
|
5
|
-
import { Badge } from '@/components/ui/badge'
|
|
6
|
-
|
|
7
|
-
interface BenchmarkStats {
|
|
8
|
-
total_runs: number
|
|
9
|
-
passed: number
|
|
10
|
-
failed: number
|
|
11
|
-
error: number
|
|
12
|
-
success_rate: number
|
|
13
|
-
avg_duration: number
|
|
14
|
-
total_cost: number
|
|
15
|
-
total_tokens: number
|
|
16
|
-
avg_tokens_per_run: number
|
|
17
|
-
by_status: Record<string, number>
|
|
18
|
-
by_repo: Record<string, { count: number; passed: number; success_rate: number }>
|
|
19
|
-
by_model: Record<string, { count: number; passed: number; success_rate: number }>
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
interface BenchmarkResponse {
|
|
23
|
-
stats: BenchmarkStats
|
|
24
|
-
filters: {
|
|
25
|
-
repo: string | null
|
|
26
|
-
model: string | null
|
|
27
|
-
status: string | null
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export default function BenchmarksPage() {
|
|
32
|
-
const [data, setData] = useState<BenchmarkResponse | null>(null)
|
|
33
|
-
const [loading, setLoading] = useState(true)
|
|
34
|
-
const [error, setError] = useState<string | null>(null)
|
|
35
|
-
|
|
36
|
-
useEffect(() => {
|
|
37
|
-
const fetchBenchmarks = async () => {
|
|
38
|
-
try {
|
|
39
|
-
const response = await fetch('/api/benchmarks')
|
|
40
|
-
if (!response.ok) {
|
|
41
|
-
throw new Error('Failed to fetch benchmarks')
|
|
42
|
-
}
|
|
43
|
-
const json = await response.json()
|
|
44
|
-
setData(json)
|
|
45
|
-
} catch (err) {
|
|
46
|
-
setError(err instanceof Error ? err.message : 'Unknown error')
|
|
47
|
-
} finally {
|
|
48
|
-
setLoading(false)
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
fetchBenchmarks()
|
|
53
|
-
}, [])
|
|
54
|
-
|
|
55
|
-
if (loading) {
|
|
56
|
-
return (
|
|
57
|
-
<div className="flex items-center justify-center min-h-screen">
|
|
58
|
-
<p className="text-muted-foreground">Loading benchmarks...</p>
|
|
59
|
-
</div>
|
|
60
|
-
)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (error) {
|
|
64
|
-
return (
|
|
65
|
-
<div className="flex items-center justify-center min-h-screen">
|
|
66
|
-
<p className="text-destructive">Error: {error}</p>
|
|
67
|
-
</div>
|
|
68
|
-
)
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (!data) {
|
|
72
|
-
return (
|
|
73
|
-
<div className="flex items-center justify-center min-h-screen">
|
|
74
|
-
<p className="text-muted-foreground">No benchmark data available</p>
|
|
75
|
-
</div>
|
|
76
|
-
)
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const stats = data.stats
|
|
80
|
-
|
|
81
|
-
return (
|
|
82
|
-
<div className="space-y-6 p-6">
|
|
83
|
-
<div>
|
|
84
|
-
<h1 className="text-3xl font-bold tracking-tight">Benchmarks</h1>
|
|
85
|
-
<p className="text-muted-foreground mt-2">SWE-Bench v4 Flash 300 Results</p>
|
|
86
|
-
</div>
|
|
87
|
-
|
|
88
|
-
{/* Key Metrics */}
|
|
89
|
-
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
|
|
90
|
-
<Card>
|
|
91
|
-
<CardHeader className="pb-2">
|
|
92
|
-
<CardTitle className="text-sm font-medium">Total Runs</CardTitle>
|
|
93
|
-
</CardHeader>
|
|
94
|
-
<CardContent>
|
|
95
|
-
<div className="text-2xl font-bold">{stats.total_runs}</div>
|
|
96
|
-
</CardContent>
|
|
97
|
-
</Card>
|
|
98
|
-
|
|
99
|
-
<Card>
|
|
100
|
-
<CardHeader className="pb-2">
|
|
101
|
-
<CardTitle className="text-sm font-medium">Success Rate</CardTitle>
|
|
102
|
-
</CardHeader>
|
|
103
|
-
<CardContent>
|
|
104
|
-
<div className="text-2xl font-bold">{stats.success_rate.toFixed(1)}%</div>
|
|
105
|
-
<p className="text-xs text-muted-foreground mt-1">
|
|
106
|
-
{stats.passed} passed, {stats.failed} failed
|
|
107
|
-
</p>
|
|
108
|
-
</CardContent>
|
|
109
|
-
</Card>
|
|
110
|
-
|
|
111
|
-
<Card>
|
|
112
|
-
<CardHeader className="pb-2">
|
|
113
|
-
<CardTitle className="text-sm font-medium">Total Cost</CardTitle>
|
|
114
|
-
</CardHeader>
|
|
115
|
-
<CardContent>
|
|
116
|
-
<div className="text-2xl font-bold">${stats.total_cost.toFixed(2)}</div>
|
|
117
|
-
<p className="text-xs text-muted-foreground mt-1">
|
|
118
|
-
{stats.avg_tokens_per_run.toFixed(0)} tokens/run
|
|
119
|
-
</p>
|
|
120
|
-
</CardContent>
|
|
121
|
-
</Card>
|
|
122
|
-
|
|
123
|
-
<Card>
|
|
124
|
-
<CardHeader className="pb-2">
|
|
125
|
-
<CardTitle className="text-sm font-medium">Avg Duration</CardTitle>
|
|
126
|
-
</CardHeader>
|
|
127
|
-
<CardContent>
|
|
128
|
-
<div className="text-2xl font-bold">{stats.avg_duration.toFixed(1)}s</div>
|
|
129
|
-
<p className="text-xs text-muted-foreground mt-1">
|
|
130
|
-
{(stats.total_tokens / 1000).toFixed(1)}K tokens total
|
|
131
|
-
</p>
|
|
132
|
-
</CardContent>
|
|
133
|
-
</Card>
|
|
134
|
-
</div>
|
|
135
|
-
|
|
136
|
-
{/* Status Breakdown */}
|
|
137
|
-
<Card>
|
|
138
|
-
<CardHeader>
|
|
139
|
-
<CardTitle>Status Breakdown</CardTitle>
|
|
140
|
-
<CardDescription>Distribution of run statuses</CardDescription>
|
|
141
|
-
</CardHeader>
|
|
142
|
-
<CardContent>
|
|
143
|
-
<div className="space-y-3">
|
|
144
|
-
{Object.entries(stats.by_status).map(([status, count]) => (
|
|
145
|
-
<div key={status} className="flex items-center justify-between">
|
|
146
|
-
<div className="flex items-center gap-2">
|
|
147
|
-
<Badge
|
|
148
|
-
variant={
|
|
149
|
-
status === 'success'
|
|
150
|
-
? 'default'
|
|
151
|
-
: status === 'failed'
|
|
152
|
-
? 'destructive'
|
|
153
|
-
: 'secondary'
|
|
154
|
-
}
|
|
155
|
-
>
|
|
156
|
-
{status}
|
|
157
|
-
</Badge>
|
|
158
|
-
<span className="text-sm text-muted-foreground">{count} runs</span>
|
|
159
|
-
</div>
|
|
160
|
-
<span className="text-sm font-medium">
|
|
161
|
-
{((count / stats.total_runs) * 100).toFixed(1)}%
|
|
162
|
-
</span>
|
|
163
|
-
</div>
|
|
164
|
-
))}
|
|
165
|
-
</div>
|
|
166
|
-
</CardContent>
|
|
167
|
-
</Card>
|
|
168
|
-
|
|
169
|
-
{/* By Repository */}
|
|
170
|
-
<Card>
|
|
171
|
-
<CardHeader>
|
|
172
|
-
<CardTitle>Performance by Repository</CardTitle>
|
|
173
|
-
<CardDescription>Success rate and run count per repository</CardDescription>
|
|
174
|
-
</CardHeader>
|
|
175
|
-
<CardContent>
|
|
176
|
-
<div className="space-y-4">
|
|
177
|
-
{Object.entries(stats.by_repo)
|
|
178
|
-
.sort((a, b) => b[1].count - a[1].count)
|
|
179
|
-
.map(([repo, data]) => (
|
|
180
|
-
<div key={repo} className="flex items-center justify-between border-b pb-3 last:border-0">
|
|
181
|
-
<div>
|
|
182
|
-
<p className="font-medium text-sm">{repo}</p>
|
|
183
|
-
<p className="text-xs text-muted-foreground">
|
|
184
|
-
{data.count} runs, {data.passed} passed
|
|
185
|
-
</p>
|
|
186
|
-
</div>
|
|
187
|
-
<div className="text-right">
|
|
188
|
-
<p className="font-bold text-sm">{data.success_rate.toFixed(1)}%</p>
|
|
189
|
-
</div>
|
|
190
|
-
</div>
|
|
191
|
-
))}
|
|
192
|
-
</div>
|
|
193
|
-
</CardContent>
|
|
194
|
-
</Card>
|
|
195
|
-
|
|
196
|
-
{/* By Model */}
|
|
197
|
-
<Card>
|
|
198
|
-
<CardHeader>
|
|
199
|
-
<CardTitle>Performance by Model</CardTitle>
|
|
200
|
-
<CardDescription>Success rate and run count per model</CardDescription>
|
|
201
|
-
</CardHeader>
|
|
202
|
-
<CardContent>
|
|
203
|
-
<div className="space-y-4">
|
|
204
|
-
{Object.entries(stats.by_model)
|
|
205
|
-
.sort((a, b) => b[1].count - a[1].count)
|
|
206
|
-
.map(([model, data]) => (
|
|
207
|
-
<div key={model} className="flex items-center justify-between border-b pb-3 last:border-0">
|
|
208
|
-
<div>
|
|
209
|
-
<p className="font-medium text-sm">{model}</p>
|
|
210
|
-
<p className="text-xs text-muted-foreground">
|
|
211
|
-
{data.count} runs, {data.passed} passed
|
|
212
|
-
</p>
|
|
213
|
-
</div>
|
|
214
|
-
<div className="text-right">
|
|
215
|
-
<p className="font-bold text-sm">{data.success_rate.toFixed(1)}%</p>
|
|
216
|
-
</div>
|
|
217
|
-
</div>
|
|
218
|
-
))}
|
|
219
|
-
</div>
|
|
220
|
-
</CardContent>
|
|
221
|
-
</Card>
|
|
222
|
-
</div>
|
|
223
|
-
)
|
|
224
|
-
}
|