@bahulam/code 0.1.6 → 0.1.8

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 (204) hide show
  1. package/LICENSE +1 -1
  2. package/NOTICE +6 -6
  3. package/README.md +11 -18
  4. package/package.json +11 -11
  5. package/src/agents/loader.mjs +1 -1
  6. package/src/agents/workflow_loader.mjs +2 -2
  7. package/src/auth/{tarang-auth.mjs → bahulam-auth.mjs} +4 -3
  8. package/src/commands/agent.mjs +3 -3
  9. package/src/commands/device.mjs +2 -2
  10. package/src/commands/pair.mjs +2 -2
  11. package/src/commands/remote.mjs +3 -3
  12. package/src/commands/workflow.mjs +5 -5
  13. package/src/config/env.mjs +9 -2
  14. package/src/config/memory-loader.mjs +1 -1
  15. package/src/config/model-catalog-default.json +4 -3
  16. package/src/config/model-catalog.mjs +2 -0
  17. package/src/config/settings.mjs +2 -2
  18. package/src/context/prose-chunker.mjs +162 -5
  19. package/src/core/agent-loop.mjs +17 -0
  20. package/src/core/attachments.mjs +66 -4
  21. package/src/core/backend-url.mjs +11 -12
  22. package/src/core/bundled-runtime.mjs +1 -1
  23. package/src/core/callback-client.mjs +1 -1
  24. package/src/core/headless.mjs +18 -4
  25. package/src/core/jsonl-writer.mjs +18 -18
  26. package/src/core/lint-resolver.mjs +472 -0
  27. package/src/core/local-agent.mjs +40 -8
  28. package/src/core/local-store.mjs +5 -1
  29. package/src/core/mode-selector.mjs +1 -1
  30. package/src/core/output-filter.mjs +22 -26
  31. package/src/core/pricing.mjs +10 -3
  32. package/src/core/project-artifacts.mjs +3 -3
  33. package/src/core/project-context-loader.mjs +9 -9
  34. package/src/core/settings-sync.mjs +1 -1
  35. package/src/core/stagnation.mjs +61 -1
  36. package/src/core/stream-client.mjs +13 -8
  37. package/src/core/system-prompt.mjs +2 -2
  38. package/src/core/tool-executor.mjs +178 -84
  39. package/src/daemon/http-dashboard.mjs +400 -0
  40. package/src/daemon/session-core.mjs +1 -0
  41. package/src/local-service/agent-relay.mjs +868 -0
  42. package/src/local-service/approval-bridge.mjs +222 -0
  43. package/src/local-service/browser.mjs +24 -0
  44. package/src/local-service/command.mjs +155 -0
  45. package/src/local-service/file-access.mjs +393 -0
  46. package/src/local-service/machine.mjs +86 -0
  47. package/src/local-service/paths.mjs +29 -0
  48. package/src/local-service/preview-converters.mjs +260 -0
  49. package/src/local-service/server.mjs +2644 -0
  50. package/src/local-service/session-store.mjs +221 -0
  51. package/src/mcp/client.mjs +4 -4
  52. package/src/onboarding/preflight.mjs +2 -2
  53. package/src/terminal/agents.mjs +2 -2
  54. package/src/terminal/analytics.mjs +2 -6
  55. package/src/terminal/ansi.mjs +11 -3
  56. package/src/terminal/main.mjs +43 -17
  57. package/src/terminal/model-catalog-display.mjs +111 -0
  58. package/src/terminal/repl-format.mjs +4 -1
  59. package/src/terminal/repl-model-form.mjs +81 -14
  60. package/src/terminal/repl-render.mjs +37 -7
  61. package/src/terminal/repl-resume.mjs +2 -2
  62. package/src/terminal/repl-state.mjs +12 -0
  63. package/src/terminal/repl.mjs +248 -70
  64. package/src/terminal/tool-display.mjs +32 -0
  65. package/src/terminal/watch-state.mjs +118 -0
  66. package/src/tools/agent.mjs +2 -2
  67. package/src/tools/analyze-image.mjs +158 -0
  68. package/src/tools/bash.mjs +11 -9
  69. package/src/tools/generate-image.mjs +411 -0
  70. package/src/tools/lint.mjs +24 -43
  71. package/src/tools/project-overview.mjs +12 -12
  72. package/src/tools/registry.mjs +4 -0
  73. package/src/ui/formatter.mjs +2 -2
  74. package/src/ui/icons.mjs +2 -0
  75. package/src/ui/input-dock.mjs +7 -7
  76. package/src/ui/palette.mjs +1 -1
  77. package/src/ui/slash-commands.mjs +4 -6
  78. package/src/ui/spinner.mjs +1 -1
  79. package/src/ui/term.mjs +2 -6
  80. package/src/ui/tool-card.mjs +81 -6
  81. package/pulse/app/activity/page.tsx +0 -190
  82. package/pulse/app/api/activity/route.ts +0 -138
  83. package/pulse/app/api/benchmark/route.ts +0 -113
  84. package/pulse/app/api/benchmarks/route.ts +0 -195
  85. package/pulse/app/api/costs/route.ts +0 -88
  86. package/pulse/app/api/export/route.ts +0 -77
  87. package/pulse/app/api/history/route.ts +0 -11
  88. package/pulse/app/api/import/route.ts +0 -31
  89. package/pulse/app/api/memory/route.ts +0 -50
  90. package/pulse/app/api/plans/route.ts +0 -9
  91. package/pulse/app/api/projects/[slug]/route.ts +0 -96
  92. package/pulse/app/api/projects/route.ts +0 -121
  93. package/pulse/app/api/sessions/[id]/replay/route.ts +0 -20
  94. package/pulse/app/api/sessions/[id]/route.ts +0 -31
  95. package/pulse/app/api/sessions/route.ts +0 -112
  96. package/pulse/app/api/settings/route.ts +0 -14
  97. package/pulse/app/api/stats/route.ts +0 -143
  98. package/pulse/app/api/todos/route.ts +0 -9
  99. package/pulse/app/api/tools/route.ts +0 -160
  100. package/pulse/app/benchmarks/page.tsx +0 -224
  101. package/pulse/app/costs/page.tsx +0 -179
  102. package/pulse/app/export/page.tsx +0 -465
  103. package/pulse/app/favicon.ico +0 -0
  104. package/pulse/app/globals.css +0 -263
  105. package/pulse/app/help/page.tsx +0 -143
  106. package/pulse/app/history/page.tsx +0 -157
  107. package/pulse/app/layout.tsx +0 -46
  108. package/pulse/app/memory/page.tsx +0 -365
  109. package/pulse/app/overview-client.tsx +0 -393
  110. package/pulse/app/page.tsx +0 -14
  111. package/pulse/app/plans/page.tsx +0 -308
  112. package/pulse/app/projects/[slug]/page.tsx +0 -390
  113. package/pulse/app/projects/page.tsx +0 -110
  114. package/pulse/app/sessions/[id]/page.tsx +0 -243
  115. package/pulse/app/sessions/page.tsx +0 -39
  116. package/pulse/app/settings/page.tsx +0 -188
  117. package/pulse/app/todos/page.tsx +0 -211
  118. package/pulse/app/tools/page.tsx +0 -249
  119. package/pulse/cli.js +0 -164
  120. package/pulse/components/activity/day-of-week-chart.tsx +0 -35
  121. package/pulse/components/activity/streak-card.tsx +0 -36
  122. package/pulse/components/costs/cache-efficiency-panel.tsx +0 -76
  123. package/pulse/components/costs/cost-by-project-chart.tsx +0 -48
  124. package/pulse/components/costs/cost-over-time-chart.tsx +0 -95
  125. package/pulse/components/costs/model-token-table.tsx +0 -60
  126. package/pulse/components/global-search.tsx +0 -193
  127. package/pulse/components/keyboard-nav-provider.tsx +0 -23
  128. package/pulse/components/layout/bottom-nav.tsx +0 -53
  129. package/pulse/components/layout/client-layout.tsx +0 -31
  130. package/pulse/components/layout/sidebar-context.tsx +0 -50
  131. package/pulse/components/layout/sidebar.tsx +0 -183
  132. package/pulse/components/layout/top-bar.tsx +0 -121
  133. package/pulse/components/overview/activity-heatmap.tsx +0 -107
  134. package/pulse/components/overview/conversation-table.tsx +0 -148
  135. package/pulse/components/overview/model-breakdown-donut.tsx +0 -95
  136. package/pulse/components/overview/peak-hours-chart.tsx +0 -87
  137. package/pulse/components/overview/project-activity-donut.tsx +0 -96
  138. package/pulse/components/overview/stat-card.tsx +0 -102
  139. package/pulse/components/overview/usage-over-time-chart.tsx +0 -166
  140. package/pulse/components/projects/project-card.tsx +0 -175
  141. package/pulse/components/sessions/replay/assistant-markdown.tsx +0 -94
  142. package/pulse/components/sessions/replay/compaction-card.tsx +0 -25
  143. package/pulse/components/sessions/replay/session-sidebar.tsx +0 -231
  144. package/pulse/components/sessions/replay/token-accumulation-chart.tsx +0 -98
  145. package/pulse/components/sessions/replay/tool-call-badge.tsx +0 -127
  146. package/pulse/components/sessions/replay/turn-cards.tsx +0 -220
  147. package/pulse/components/sessions/replay/user-tool-result.tsx +0 -158
  148. package/pulse/components/sessions/session-badges.tsx +0 -49
  149. package/pulse/components/sessions/session-table.tsx +0 -299
  150. package/pulse/components/theme-provider.tsx +0 -44
  151. package/pulse/components/tools/feature-adoption-table.tsx +0 -58
  152. package/pulse/components/tools/mcp-server-panel.tsx +0 -45
  153. package/pulse/components/tools/tool-ranking-chart.tsx +0 -57
  154. package/pulse/components/tools/version-history-table.tsx +0 -32
  155. package/pulse/components/ui/alert.tsx +0 -66
  156. package/pulse/components/ui/badge.tsx +0 -48
  157. package/pulse/components/ui/breadcrumb.tsx +0 -109
  158. package/pulse/components/ui/button.tsx +0 -64
  159. package/pulse/components/ui/calendar.tsx +0 -220
  160. package/pulse/components/ui/card.tsx +0 -92
  161. package/pulse/components/ui/command.tsx +0 -158
  162. package/pulse/components/ui/dialog.tsx +0 -158
  163. package/pulse/components/ui/input.tsx +0 -21
  164. package/pulse/components/ui/popover.tsx +0 -89
  165. package/pulse/components/ui/progress.tsx +0 -31
  166. package/pulse/components/ui/select.tsx +0 -190
  167. package/pulse/components/ui/separator.tsx +0 -28
  168. package/pulse/components/ui/sheet.tsx +0 -143
  169. package/pulse/components/ui/skeleton.tsx +0 -13
  170. package/pulse/components/ui/table.tsx +0 -116
  171. package/pulse/components/ui/tabs.tsx +0 -91
  172. package/pulse/components/ui/tooltip.tsx +0 -57
  173. package/pulse/components/use-global-keyboard-nav.ts +0 -79
  174. package/pulse/components.json +0 -23
  175. package/pulse/eslint.config.mjs +0 -18
  176. package/pulse/lib/bahulam-paths.ts +0 -23
  177. package/pulse/lib/claude-reader.ts +0 -592
  178. package/pulse/lib/decode.ts +0 -129
  179. package/pulse/lib/pricing.ts +0 -102
  180. package/pulse/lib/replay-parser.ts +0 -165
  181. package/pulse/lib/tool-categories.ts +0 -140
  182. package/pulse/lib/utils.ts +0 -6
  183. package/pulse/next-env.d.ts +0 -6
  184. package/pulse/next.config.ts +0 -16
  185. package/pulse/package.json +0 -45
  186. package/pulse/postcss.config.mjs +0 -7
  187. package/pulse/public/activity.png +0 -0
  188. package/pulse/public/cc-lens.png +0 -0
  189. package/pulse/public/command-k.png +0 -0
  190. package/pulse/public/costs.png +0 -0
  191. package/pulse/public/dashboard-dark.png +0 -0
  192. package/pulse/public/dashboard-white.png +0 -0
  193. package/pulse/public/export.png +0 -0
  194. package/pulse/public/file.svg +0 -1
  195. package/pulse/public/globe.svg +0 -1
  196. package/pulse/public/next.svg +0 -1
  197. package/pulse/public/projects.png +0 -0
  198. package/pulse/public/session-chat.png +0 -0
  199. package/pulse/public/todos.png +0 -0
  200. package/pulse/public/tools.png +0 -0
  201. package/pulse/public/vercel.svg +0 -1
  202. package/pulse/public/window.svg +0 -1
  203. package/pulse/tsconfig.json +0 -34
  204. 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
- }