@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.
Files changed (178) hide show
  1. package/LICENSE +1 -1
  2. package/NOTICE +6 -6
  3. package/README.md +10 -17
  4. package/package.json +9 -9
  5. package/src/agents/loader.mjs +1 -1
  6. package/src/agents/workflow_loader.mjs +2 -2
  7. package/src/auth/tarang-auth.mjs +3 -2
  8. package/src/config/model-catalog-default.json +4 -3
  9. package/src/config/model-catalog.mjs +2 -0
  10. package/src/context/prose-chunker.mjs +162 -5
  11. package/src/core/attachments.mjs +65 -3
  12. package/src/core/bundled-runtime.mjs +1 -1
  13. package/src/core/headless.mjs +14 -0
  14. package/src/core/jsonl-writer.mjs +3 -3
  15. package/src/core/lint-resolver.mjs +472 -0
  16. package/src/core/local-agent.mjs +15 -4
  17. package/src/core/local-store.mjs +4 -0
  18. package/src/core/output-filter.mjs +21 -25
  19. package/src/core/pricing.mjs +10 -3
  20. package/src/core/stream-client.mjs +9 -4
  21. package/src/core/tool-executor.mjs +105 -29
  22. package/src/daemon/http-dashboard.mjs +400 -0
  23. package/src/daemon/session-core.mjs +1 -0
  24. package/src/local-service/agent-relay.mjs +868 -0
  25. package/src/local-service/approval-bridge.mjs +222 -0
  26. package/src/local-service/browser.mjs +24 -0
  27. package/src/local-service/command.mjs +155 -0
  28. package/src/local-service/file-access.mjs +393 -0
  29. package/src/local-service/machine.mjs +86 -0
  30. package/src/local-service/paths.mjs +29 -0
  31. package/src/local-service/preview-converters.mjs +260 -0
  32. package/src/local-service/server.mjs +2644 -0
  33. package/src/local-service/session-store.mjs +221 -0
  34. package/src/onboarding/preflight.mjs +1 -1
  35. package/src/terminal/analytics.mjs +2 -6
  36. package/src/terminal/ansi.mjs +11 -3
  37. package/src/terminal/main.mjs +37 -11
  38. package/src/terminal/model-catalog-display.mjs +111 -0
  39. package/src/terminal/repl-format.mjs +4 -1
  40. package/src/terminal/repl-model-form.mjs +81 -14
  41. package/src/terminal/repl-render.mjs +35 -5
  42. package/src/terminal/repl-state.mjs +12 -0
  43. package/src/terminal/repl.mjs +231 -53
  44. package/src/terminal/tool-display.mjs +32 -0
  45. package/src/terminal/watch-state.mjs +118 -0
  46. package/src/tools/analyze-image.mjs +158 -0
  47. package/src/tools/bash.mjs +11 -9
  48. package/src/tools/generate-image.mjs +411 -0
  49. package/src/tools/lint.mjs +24 -43
  50. package/src/tools/registry.mjs +4 -0
  51. package/src/ui/icons.mjs +2 -0
  52. package/src/ui/palette.mjs +1 -1
  53. package/src/ui/slash-commands.mjs +4 -6
  54. package/src/ui/tool-card.mjs +7 -3
  55. package/pulse/app/activity/page.tsx +0 -190
  56. package/pulse/app/api/activity/route.ts +0 -138
  57. package/pulse/app/api/benchmark/route.ts +0 -113
  58. package/pulse/app/api/benchmarks/route.ts +0 -195
  59. package/pulse/app/api/costs/route.ts +0 -88
  60. package/pulse/app/api/export/route.ts +0 -77
  61. package/pulse/app/api/history/route.ts +0 -11
  62. package/pulse/app/api/import/route.ts +0 -31
  63. package/pulse/app/api/memory/route.ts +0 -50
  64. package/pulse/app/api/plans/route.ts +0 -9
  65. package/pulse/app/api/projects/[slug]/route.ts +0 -96
  66. package/pulse/app/api/projects/route.ts +0 -121
  67. package/pulse/app/api/sessions/[id]/replay/route.ts +0 -20
  68. package/pulse/app/api/sessions/[id]/route.ts +0 -31
  69. package/pulse/app/api/sessions/route.ts +0 -112
  70. package/pulse/app/api/settings/route.ts +0 -14
  71. package/pulse/app/api/stats/route.ts +0 -143
  72. package/pulse/app/api/todos/route.ts +0 -9
  73. package/pulse/app/api/tools/route.ts +0 -160
  74. package/pulse/app/benchmarks/page.tsx +0 -224
  75. package/pulse/app/costs/page.tsx +0 -179
  76. package/pulse/app/export/page.tsx +0 -465
  77. package/pulse/app/favicon.ico +0 -0
  78. package/pulse/app/globals.css +0 -263
  79. package/pulse/app/help/page.tsx +0 -143
  80. package/pulse/app/history/page.tsx +0 -157
  81. package/pulse/app/layout.tsx +0 -46
  82. package/pulse/app/memory/page.tsx +0 -365
  83. package/pulse/app/overview-client.tsx +0 -393
  84. package/pulse/app/page.tsx +0 -14
  85. package/pulse/app/plans/page.tsx +0 -308
  86. package/pulse/app/projects/[slug]/page.tsx +0 -390
  87. package/pulse/app/projects/page.tsx +0 -110
  88. package/pulse/app/sessions/[id]/page.tsx +0 -243
  89. package/pulse/app/sessions/page.tsx +0 -39
  90. package/pulse/app/settings/page.tsx +0 -188
  91. package/pulse/app/todos/page.tsx +0 -211
  92. package/pulse/app/tools/page.tsx +0 -249
  93. package/pulse/cli.js +0 -164
  94. package/pulse/components/activity/day-of-week-chart.tsx +0 -35
  95. package/pulse/components/activity/streak-card.tsx +0 -36
  96. package/pulse/components/costs/cache-efficiency-panel.tsx +0 -76
  97. package/pulse/components/costs/cost-by-project-chart.tsx +0 -48
  98. package/pulse/components/costs/cost-over-time-chart.tsx +0 -95
  99. package/pulse/components/costs/model-token-table.tsx +0 -60
  100. package/pulse/components/global-search.tsx +0 -193
  101. package/pulse/components/keyboard-nav-provider.tsx +0 -23
  102. package/pulse/components/layout/bottom-nav.tsx +0 -53
  103. package/pulse/components/layout/client-layout.tsx +0 -31
  104. package/pulse/components/layout/sidebar-context.tsx +0 -50
  105. package/pulse/components/layout/sidebar.tsx +0 -183
  106. package/pulse/components/layout/top-bar.tsx +0 -121
  107. package/pulse/components/overview/activity-heatmap.tsx +0 -107
  108. package/pulse/components/overview/conversation-table.tsx +0 -148
  109. package/pulse/components/overview/model-breakdown-donut.tsx +0 -95
  110. package/pulse/components/overview/peak-hours-chart.tsx +0 -87
  111. package/pulse/components/overview/project-activity-donut.tsx +0 -96
  112. package/pulse/components/overview/stat-card.tsx +0 -102
  113. package/pulse/components/overview/usage-over-time-chart.tsx +0 -166
  114. package/pulse/components/projects/project-card.tsx +0 -175
  115. package/pulse/components/sessions/replay/assistant-markdown.tsx +0 -94
  116. package/pulse/components/sessions/replay/compaction-card.tsx +0 -25
  117. package/pulse/components/sessions/replay/session-sidebar.tsx +0 -231
  118. package/pulse/components/sessions/replay/token-accumulation-chart.tsx +0 -98
  119. package/pulse/components/sessions/replay/tool-call-badge.tsx +0 -127
  120. package/pulse/components/sessions/replay/turn-cards.tsx +0 -220
  121. package/pulse/components/sessions/replay/user-tool-result.tsx +0 -158
  122. package/pulse/components/sessions/session-badges.tsx +0 -49
  123. package/pulse/components/sessions/session-table.tsx +0 -299
  124. package/pulse/components/theme-provider.tsx +0 -44
  125. package/pulse/components/tools/feature-adoption-table.tsx +0 -58
  126. package/pulse/components/tools/mcp-server-panel.tsx +0 -45
  127. package/pulse/components/tools/tool-ranking-chart.tsx +0 -57
  128. package/pulse/components/tools/version-history-table.tsx +0 -32
  129. package/pulse/components/ui/alert.tsx +0 -66
  130. package/pulse/components/ui/badge.tsx +0 -48
  131. package/pulse/components/ui/breadcrumb.tsx +0 -109
  132. package/pulse/components/ui/button.tsx +0 -64
  133. package/pulse/components/ui/calendar.tsx +0 -220
  134. package/pulse/components/ui/card.tsx +0 -92
  135. package/pulse/components/ui/command.tsx +0 -158
  136. package/pulse/components/ui/dialog.tsx +0 -158
  137. package/pulse/components/ui/input.tsx +0 -21
  138. package/pulse/components/ui/popover.tsx +0 -89
  139. package/pulse/components/ui/progress.tsx +0 -31
  140. package/pulse/components/ui/select.tsx +0 -190
  141. package/pulse/components/ui/separator.tsx +0 -28
  142. package/pulse/components/ui/sheet.tsx +0 -143
  143. package/pulse/components/ui/skeleton.tsx +0 -13
  144. package/pulse/components/ui/table.tsx +0 -116
  145. package/pulse/components/ui/tabs.tsx +0 -91
  146. package/pulse/components/ui/tooltip.tsx +0 -57
  147. package/pulse/components/use-global-keyboard-nav.ts +0 -79
  148. package/pulse/components.json +0 -23
  149. package/pulse/eslint.config.mjs +0 -18
  150. package/pulse/lib/bahulam-paths.ts +0 -23
  151. package/pulse/lib/claude-reader.ts +0 -592
  152. package/pulse/lib/decode.ts +0 -129
  153. package/pulse/lib/pricing.ts +0 -102
  154. package/pulse/lib/replay-parser.ts +0 -165
  155. package/pulse/lib/tool-categories.ts +0 -140
  156. package/pulse/lib/utils.ts +0 -6
  157. package/pulse/next-env.d.ts +0 -6
  158. package/pulse/next.config.ts +0 -16
  159. package/pulse/package.json +0 -45
  160. package/pulse/postcss.config.mjs +0 -7
  161. package/pulse/public/activity.png +0 -0
  162. package/pulse/public/cc-lens.png +0 -0
  163. package/pulse/public/command-k.png +0 -0
  164. package/pulse/public/costs.png +0 -0
  165. package/pulse/public/dashboard-dark.png +0 -0
  166. package/pulse/public/dashboard-white.png +0 -0
  167. package/pulse/public/export.png +0 -0
  168. package/pulse/public/file.svg +0 -1
  169. package/pulse/public/globe.svg +0 -1
  170. package/pulse/public/next.svg +0 -1
  171. package/pulse/public/projects.png +0 -0
  172. package/pulse/public/session-chat.png +0 -0
  173. package/pulse/public/todos.png +0 -0
  174. package/pulse/public/tools.png +0 -0
  175. package/pulse/public/vercel.svg +0 -1
  176. package/pulse/public/window.svg +0 -1
  177. package/pulse/tsconfig.json +0 -34
  178. package/pulse/types/claude.ts +0 -294
@@ -1,393 +0,0 @@
1
- 'use client'
2
-
3
- import { useState, useMemo } from 'react'
4
- import useSWR from 'swr'
5
- import { BarChart3, PieChart, Clock, CalendarDays } from 'lucide-react'
6
- import { UsageOverTimeChart } from '@/components/overview/usage-over-time-chart'
7
- import { ModelBreakdownDonut } from '@/components/overview/model-breakdown-donut'
8
- import { ProjectActivityDonut } from '@/components/overview/project-activity-donut'
9
- import { PeakHoursChart } from '@/components/overview/peak-hours-chart'
10
- import { OverviewConversationTable } from '@/components/overview/conversation-table'
11
- import { StatCard } from '@/components/overview/stat-card'
12
- import { formatTokens, formatBytes } from '@/lib/decode'
13
- import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
14
- import { Button } from '@/components/ui/button'
15
- import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'
16
- import { Skeleton } from '@/components/ui/skeleton'
17
- import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
18
- import { Calendar } from '@/components/ui/calendar'
19
- import type { StatsCache, DailyActivity, DailyTokens } from '@/types/claude'
20
- import type { SessionWithFacet, ProjectSummary } from '@/types/claude'
21
- import { format, subDays } from 'date-fns'
22
- import { useTheme } from '@/components/theme-provider'
23
-
24
- // ─── Types ────────────────────────────────────────────────────────────────────
25
-
26
- interface ApiResponse {
27
- stats: StatsCache
28
- computed: {
29
- totalCost: number
30
- totalCacheSavings: number
31
- totalTokens: number
32
- totalInputTokens: number
33
- totalOutputTokens: number
34
- totalCacheReadTokens: number
35
- totalCacheWriteTokens: number
36
- totalToolCalls: number
37
- activeDays: number
38
- avgSessionMinutes: number
39
- sessionsThisMonth: number
40
- sessionsThisWeek: number
41
- storageBytes: number
42
- sessionCount: number
43
- }
44
- }
45
-
46
- type DatePreset = '7d' | '30d' | '90d'
47
- type CustomRange = { from?: Date; to?: Date }
48
-
49
- const fetcher = (url: string) =>
50
- fetch(url).then(r => {
51
- if (!r.ok) throw new Error(`API error ${r.status}`)
52
- return r.json()
53
- })
54
-
55
- // ─── Helpers ──────────────────────────────────────────────────────────────────
56
-
57
- function computeTrend(
58
- dailyActivity: DailyActivity[],
59
- field: 'messageCount' | 'sessionCount',
60
- days = 7,
61
- ): number | undefined {
62
- const sorted = [...dailyActivity].sort((a, b) => a.date.localeCompare(b.date))
63
- const recent = sorted.slice(-days)
64
- const previous = sorted.slice(-(days * 2), -days)
65
- if (!recent.length || !previous.length) return undefined
66
- const recentSum = recent.reduce((s, d) => s + (d[field] ?? 0), 0)
67
- const prevSum = previous.reduce((s, d) => s + (d[field] ?? 0), 0)
68
- if (prevSum === 0) return undefined
69
- return ((recentSum - prevSum) / prevSum) * 100
70
- }
71
-
72
- function getActivitySpark(dailyActivity: DailyActivity[], field: 'messageCount' | 'sessionCount', days = 14): number[] {
73
- return [...dailyActivity]
74
- .sort((a, b) => a.date.localeCompare(b.date))
75
- .slice(-days)
76
- .map(d => d[field] ?? 0)
77
- }
78
-
79
- function getTokenSpark(tokensByDate: DailyTokens[], days = 14): number[] {
80
- return [...tokensByDate]
81
- .sort((a, b) => a.date.localeCompare(b.date))
82
- .slice(-days)
83
- .map(d => Object.values(d.tokensByModel ?? {}).reduce((s, v) => s + v, 0))
84
- }
85
-
86
- // ─── Component ────────────────────────────────────────────────────────────────
87
-
88
- export function OverviewClient() {
89
- const { theme } = useTheme()
90
- const [datePreset, setDatePreset] = useState<DatePreset>('30d')
91
- const [customRange, setCustomRange] = useState<CustomRange>({})
92
- const [pickerOpen, setPickerOpen] = useState(false)
93
-
94
- const { data, error, isLoading } = useSWR<ApiResponse>('/api/stats', fetcher, {
95
- refreshInterval: 5_000,
96
- })
97
- const { data: sessionsData } = useSWR<{ sessions: SessionWithFacet[] }>('/api/sessions', fetcher, {
98
- refreshInterval: 5_000,
99
- })
100
- const { data: projectsData } = useSWR<{ projects: ProjectSummary[] }>('/api/projects', fetcher, {
101
- refreshInterval: 5_000,
102
- })
103
-
104
- const sessions = sessionsData?.sessions ?? []
105
- const projects = projectsData?.projects ?? []
106
- const projectCount = projects.length
107
-
108
- const usingCustom = !!(customRange.from && customRange.to)
109
- const chartDays = usingCustom
110
- ? Math.ceil((customRange.to!.getTime() - customRange.from!.getTime()) / (24 * 60 * 60 * 1000))
111
- : datePreset === '7d' ? 7 : datePreset === '30d' ? 30 : 90
112
- const effectiveDateFrom = usingCustom
113
- ? format(customRange.from!, 'MM/dd/yyyy')
114
- : format(subDays(new Date(), chartDays), 'MM/dd/yyyy')
115
- const effectiveDateTo = usingCustom
116
- ? format(customRange.to!, 'MM/dd/yyyy')
117
- : format(new Date(), 'MM/dd/yyyy')
118
-
119
- const pickerLabel = usingCustom
120
- ? `${format(customRange.from!, 'MMM d')} – ${format(customRange.to!, 'MMM d, yyyy')}`
121
- : 'Pick a date'
122
-
123
- // ── Loading ──────────────────────────────────────────────────────────────
124
- if (isLoading || !data || !data.computed) {
125
- return (
126
- <div className="px-6 py-6 space-y-6">
127
- <div className="flex items-center justify-between">
128
- <div className="space-y-2">
129
- <Skeleton className="h-7 w-32" />
130
- <Skeleton className="h-4 w-56" />
131
- </div>
132
- <Skeleton className="h-9 w-48" />
133
- </div>
134
- <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
135
- {[...Array(4)].map((_, i) => <Skeleton key={i} className="h-32 rounded-xl" />)}
136
- </div>
137
- <div className="grid grid-cols-1 lg:grid-cols-[1fr_320px] gap-6">
138
- <Skeleton className="h-72 rounded-xl" />
139
- <Skeleton className="h-72 rounded-xl" />
140
- </div>
141
- </div>
142
- )
143
- }
144
-
145
- if (error) {
146
- return (
147
- <div className="px-6 py-6 text-destructive text-sm font-mono">
148
- ✗ error loading data: {String(error)}
149
- </div>
150
- )
151
- }
152
-
153
- const { stats, computed } = data
154
-
155
- const inputBlue = theme === 'light' ? '#1d4ed8' : '#60a5fa'
156
- const tokenSegs = [
157
- { label: 'input', value: computed.totalInputTokens, color: inputBlue },
158
- { label: 'output', value: computed.totalOutputTokens, color: '#d97706' },
159
- { label: 'cache read', value: computed.totalCacheReadTokens, color: '#34d399' },
160
- { label: 'cache write', value: computed.totalCacheWriteTokens, color: '#a78bfa' },
161
- ]
162
- const totalTokens =
163
- computed.totalInputTokens +
164
- computed.totalOutputTokens +
165
- computed.totalCacheReadTokens +
166
- computed.totalCacheWriteTokens
167
-
168
- const tokensByDate = stats.dailyModelTokens ?? stats.tokensByDate ?? []
169
-
170
- // Trends compare last N days vs previous N days (capped at 30 to avoid sparse data)
171
- const trendWindow = Math.min(Math.max(chartDays, 7), 30)
172
-
173
- return (
174
- <div className="px-6 py-6 space-y-6 bg-background">
175
-
176
- {/* ── Page header ───────────────────────────────────────────────────── */}
177
- <div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
178
- <div>
179
- <h2 className="text-2xl font-bold tracking-tight">Overview</h2>
180
- <p className="text-sm text-muted-foreground mt-0.5">
181
- {projectCount} projects · {formatBytes(computed.storageBytes)} stored
182
- </p>
183
- </div>
184
- <div className="flex items-center gap-2">
185
- <Tabs
186
- value={usingCustom ? '' : datePreset}
187
- onValueChange={v => {
188
- setDatePreset(v as DatePreset)
189
- setCustomRange({})
190
- }}
191
- >
192
- <TabsList>
193
- <TabsTrigger value="7d">7d</TabsTrigger>
194
- <TabsTrigger value="30d">30d</TabsTrigger>
195
- <TabsTrigger value="90d">90d</TabsTrigger>
196
- </TabsList>
197
- </Tabs>
198
-
199
- <Popover open={pickerOpen} onOpenChange={setPickerOpen}>
200
- <PopoverTrigger asChild>
201
- <Button
202
- variant={usingCustom ? 'default' : 'outline'}
203
- size="sm"
204
- className="gap-2"
205
- >
206
- <CalendarDays className="w-3.5 h-3.5" />
207
- {pickerLabel}
208
- </Button>
209
- </PopoverTrigger>
210
- <PopoverContent className="w-auto p-0" align="end">
211
- <Calendar
212
- mode="range"
213
- selected={{ from: customRange.from, to: customRange.to }}
214
- onSelect={range => {
215
- setCustomRange({ from: range?.from, to: range?.to })
216
- if (range?.from && range?.to) setPickerOpen(false)
217
- }}
218
- disabled={{ after: new Date() }}
219
- initialFocus
220
- />
221
- </PopoverContent>
222
- </Popover>
223
-
224
- </div>
225
- </div>
226
-
227
- {/* ── Stat cards ────────────────────────────────────────────────────── */}
228
- <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
229
- <StatCard
230
- title="Sessions"
231
- value={computed.sessionCount.toLocaleString()}
232
- description={`${computed.sessionsThisMonth} this month · ${computed.sessionsThisWeek} this week`}
233
- trend={computeTrend(stats.dailyActivity, 'sessionCount', trendWindow)}
234
- sparkData={getActivitySpark(stats.dailyActivity, 'sessionCount')}
235
- accentColor="var(--foreground)"
236
- />
237
- <StatCard
238
- title="Messages"
239
- value={stats.totalMessages.toLocaleString()}
240
- description={`${computed.activeDays} active days`}
241
- trend={computeTrend(stats.dailyActivity, 'messageCount', trendWindow)}
242
- sparkData={getActivitySpark(stats.dailyActivity, 'messageCount')}
243
- accentColor="#d97706"
244
- />
245
- <StatCard
246
- title="Tokens Used"
247
- value={formatTokens(computed.totalTokens)}
248
- description={`${formatTokens(computed.totalCacheReadTokens)} from cache`}
249
- sparkData={getTokenSpark(tokensByDate)}
250
- accentColor={inputBlue}
251
- />
252
- <StatCard
253
- title="Estimated Cost"
254
- value={`$${computed.totalCost.toFixed(2)}`}
255
- description={`$${computed.totalCacheSavings.toFixed(2)} saved via cache`}
256
- sparkData={getTokenSpark(tokensByDate)}
257
- accentColor="#34d399"
258
- />
259
- </div>
260
-
261
- {/* ── Main charts row ───────────────────────────────────────────────── */}
262
- <div className="grid grid-cols-1 lg:grid-cols-[1fr_320px] gap-6">
263
- <Card>
264
- <CardHeader>
265
- <div className="flex items-start justify-between">
266
- <div>
267
- <CardTitle>Usage Over Time</CardTitle>
268
- <CardDescription>
269
- Messages and sessions — last {chartDays} days
270
- </CardDescription>
271
- </div>
272
- <BarChart3 className="w-4 h-4 text-muted-foreground mt-0.5" />
273
- </div>
274
- </CardHeader>
275
- <CardContent>
276
- <UsageOverTimeChart
277
- data={stats.dailyActivity}
278
- days={chartDays}
279
- dateFrom={effectiveDateFrom}
280
- dateTo={effectiveDateTo}
281
- />
282
- </CardContent>
283
- </Card>
284
-
285
- <Card>
286
- <CardHeader>
287
- <div className="flex items-start justify-between">
288
- <div>
289
- <CardTitle>Model Distribution</CardTitle>
290
- <CardDescription>Token usage by model</CardDescription>
291
- </div>
292
- <PieChart className="w-4 h-4 text-muted-foreground mt-0.5" />
293
- </div>
294
- </CardHeader>
295
- <CardContent>
296
- <ModelBreakdownDonut modelUsage={stats.modelUsage} />
297
- </CardContent>
298
- </Card>
299
- </div>
300
-
301
- {/* ── Secondary charts row ──────────────────────────────────────────── */}
302
- <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
303
- <Card>
304
- <CardHeader>
305
- <div className="flex items-start justify-between">
306
- <div>
307
- <CardTitle>Peak Hours</CardTitle>
308
- <CardDescription>Activity by hour of day</CardDescription>
309
- </div>
310
- <Clock className="w-4 h-4 text-muted-foreground mt-0.5" />
311
- </div>
312
- </CardHeader>
313
- <CardContent>
314
- <PeakHoursChart hourCounts={stats.hourCounts ?? {}} />
315
- </CardContent>
316
- </Card>
317
-
318
- <Card>
319
- <CardHeader>
320
- <div className="flex items-start justify-between">
321
- <div>
322
- <CardTitle>Project Activity</CardTitle>
323
- <CardDescription>Distribution across projects</CardDescription>
324
- </div>
325
- <PieChart className="w-4 h-4 text-muted-foreground mt-0.5" />
326
- </div>
327
- </CardHeader>
328
- <CardContent>
329
- <ProjectActivityDonut projects={projects} />
330
- </CardContent>
331
- </Card>
332
- </div>
333
-
334
- {/* ── Token breakdown ───────────────────────────────────────────────── */}
335
- <Card>
336
- <CardHeader className="pb-3">
337
- <CardTitle>Token Breakdown</CardTitle>
338
- <CardDescription>Distribution across token types (all time)</CardDescription>
339
- </CardHeader>
340
- <CardContent className="space-y-3">
341
- {totalTokens > 0 ? (
342
- <>
343
- <div className="flex h-2 rounded-full overflow-hidden w-full bg-muted/40">
344
- {tokenSegs.map(({ label, value, color }) => (
345
- <div
346
- key={label}
347
- title={`${label}: ${formatTokens(value)}`}
348
- style={{
349
- width: `${(value / totalTokens) * 100}%`,
350
- minWidth: value > 0 ? 2 : 0,
351
- backgroundColor: color,
352
- }}
353
- />
354
- ))}
355
- </div>
356
- <div className="flex flex-wrap gap-x-8 gap-y-2">
357
- {tokenSegs.map(({ label, value, color }) => (
358
- <span key={label} className="inline-flex items-center gap-2">
359
- <span
360
- className="inline-block w-2 h-2 rounded-full shrink-0"
361
- style={{ backgroundColor: color }}
362
- />
363
- <span className="text-[12px] text-muted-foreground">{label}</span>
364
- <span className="text-[13px] font-bold tabular-nums font-mono" style={{ color }}>
365
- {formatTokens(value)}
366
- </span>
367
- <span className="text-[12px] text-muted-foreground/60">
368
- {Math.round((value / totalTokens) * 100)}%
369
- </span>
370
- </span>
371
- ))}
372
- </div>
373
- </>
374
- ) : (
375
- <p className="text-sm text-muted-foreground">No token usage recorded yet.</p>
376
- )}
377
- </CardContent>
378
- </Card>
379
-
380
- {/* ── Recent sessions ───────────────────────────────────────────────── */}
381
- <Card>
382
- <CardHeader>
383
- <CardTitle>Recent Sessions</CardTitle>
384
- <CardDescription>Your latest Bahulam Code sessions</CardDescription>
385
- </CardHeader>
386
- <CardContent>
387
- <OverviewConversationTable sessions={sessions} />
388
- </CardContent>
389
- </Card>
390
-
391
- </div>
392
- )
393
- }
@@ -1,14 +0,0 @@
1
- import { TopBar } from '@/components/layout/top-bar'
2
- import { OverviewClient } from './overview-client'
3
-
4
- export default function OverviewPage() {
5
- return (
6
- <div className="flex flex-col min-h-screen">
7
- <TopBar
8
- title="Bahulam Pulse"
9
- subtitle="Real-time analytics for your Bahulam Code agent sessions"
10
- />
11
- <OverviewClient />
12
- </div>
13
- )
14
- }