@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,299 +0,0 @@
1
- 'use client'
2
-
3
- import { useState, useMemo, useEffect, useRef } from 'react'
4
- import { useRouter } from 'next/navigation'
5
- import Link from 'next/link'
6
- import { SessionBadges } from './session-badges'
7
- import { formatCost, formatTokens, formatDuration, formatDate, projectDisplayName } from '@/lib/decode'
8
- import type { SessionWithFacet } from '@/types/claude'
9
-
10
- const PAGE_SIZE = 25
11
-
12
- type SortKey = 'start_time' | 'duration_minutes' | 'total_messages' | 'estimated_cost' | 'tool_calls'
13
- type SortDir = 'asc' | 'desc'
14
-
15
- interface Props {
16
- sessions: SessionWithFacet[]
17
- }
18
-
19
- function SortHeader({
20
- label, k, sortKey, sortDir, onSort,
21
- }: {
22
- label: string
23
- k: SortKey
24
- sortKey: SortKey
25
- sortDir: SortDir
26
- onSort: (k: SortKey) => void
27
- }) {
28
- const active = sortKey === k
29
- return (
30
- <button
31
- onClick={() => onSort(k)}
32
- className={`text-left text-[12px] font-bold uppercase tracking-wider whitespace-nowrap hover:text-foreground transition-colors ${active ? 'text-primary' : 'text-muted-foreground'}`}
33
- >
34
- {label} {active ? (sortDir === 'desc' ? '↓' : '↑') : ''}
35
- </button>
36
- )
37
- }
38
-
39
- export function SessionTable({ sessions }: Props) {
40
- const [sortKey, setSortKey] = useState<SortKey>('start_time')
41
- const [sortDir, setSortDir] = useState<SortDir>('desc')
42
- const [page, setPage] = useState(1)
43
- const [filterCompacted, setFilterCompacted] = useState(false)
44
- const [filterAgent, setFilterAgent] = useState(false)
45
- const [filterMcp, setFilterMcp] = useState(false)
46
- const [search, setSearch] = useState('')
47
- const [focusedIdx, setFocusedIdx] = useState<number | null>(null)
48
- const rowRefs = useRef<(HTMLTableRowElement | null)[]>([])
49
- const router = useRouter()
50
-
51
- const filtered = useMemo(() => {
52
- let s = sessions
53
- if (filterCompacted) s = s.filter(x => x.has_compaction)
54
- if (filterAgent) s = s.filter(x => x.uses_task_agent)
55
- if (filterMcp) s = s.filter(x => x.uses_mcp)
56
- if (search) {
57
- const q = search.toLowerCase()
58
- s = s.filter(x =>
59
- x.project_path?.toLowerCase().includes(q) ||
60
- x.first_prompt?.toLowerCase().includes(q) ||
61
- x.slug?.toLowerCase().includes(q)
62
- )
63
- }
64
- return s
65
- }, [sessions, filterCompacted, filterAgent, filterMcp, search])
66
-
67
- const sorted = useMemo(() => {
68
- return [...filtered].sort((a, b) => {
69
- let av: number, bv: number
70
- if (sortKey === 'start_time') {
71
- av = new Date(a.start_time).getTime()
72
- bv = new Date(b.start_time).getTime()
73
- } else if (sortKey === 'total_messages') {
74
- av = (a.user_message_count ?? 0) + (a.assistant_message_count ?? 0)
75
- bv = (b.user_message_count ?? 0) + (b.assistant_message_count ?? 0)
76
- } else if (sortKey === 'tool_calls') {
77
- av = Object.values(a.tool_counts ?? {}).reduce((s, c) => s + c, 0)
78
- bv = Object.values(b.tool_counts ?? {}).reduce((s, c) => s + c, 0)
79
- } else {
80
- av = (a[sortKey] as number) ?? 0
81
- bv = (b[sortKey] as number) ?? 0
82
- }
83
- return sortDir === 'desc' ? bv - av : av - bv
84
- })
85
- }, [filtered, sortKey, sortDir])
86
-
87
- const totalPages = Math.ceil(sorted.length / PAGE_SIZE)
88
- const paginated = sorted.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE)
89
-
90
- // j/k keyboard navigation for rows
91
- useEffect(() => {
92
- function handler(e: KeyboardEvent) {
93
- const el = document.activeElement
94
- const tag = el?.tagName.toLowerCase()
95
- if (tag === 'input' || tag === 'textarea' || (el as HTMLElement)?.isContentEditable) return
96
-
97
- if (e.key === 'j') {
98
- e.preventDefault()
99
- setFocusedIdx(i => {
100
- const next = i === null ? 0 : Math.min(i + 1, paginated.length - 1)
101
- rowRefs.current[next]?.scrollIntoView({ block: 'nearest' })
102
- return next
103
- })
104
- } else if (e.key === 'k') {
105
- e.preventDefault()
106
- setFocusedIdx(i => {
107
- const next = i === null ? 0 : Math.max(i - 1, 0)
108
- rowRefs.current[next]?.scrollIntoView({ block: 'nearest' })
109
- return next
110
- })
111
- } else if (e.key === 'Enter' && focusedIdx !== null) {
112
- const s = paginated[focusedIdx]
113
- if (s) router.push(`/sessions/${s.session_id}`)
114
- } else if (e.key === 'Escape' && focusedIdx !== null) {
115
- setFocusedIdx(null)
116
- }
117
- }
118
- window.addEventListener('keydown', handler)
119
- return () => window.removeEventListener('keydown', handler)
120
- }, [focusedIdx, paginated, router])
121
-
122
- function toggleSort(key: SortKey) {
123
- if (sortKey === key) setSortDir(d => d === 'desc' ? 'asc' : 'desc')
124
- else { setSortKey(key); setSortDir('desc') }
125
- setPage(1)
126
- setFocusedIdx(null)
127
- }
128
-
129
- return (
130
- <div className="space-y-3">
131
- {/* Filters */}
132
- <div className="flex flex-wrap gap-2 items-center">
133
- <input
134
- type="text"
135
- placeholder="Search project or prompt..."
136
- value={search}
137
- onChange={e => { setSearch(e.target.value); setPage(1); setFocusedIdx(null) }}
138
- className="bg-muted border border-border rounded px-2 py-1 text-[13px] text-foreground placeholder:text-muted-foreground/50 outline-none focus:border-primary/50 w-52"
139
- />
140
- <label className="flex items-center gap-1.5 text-[13px] text-muted-foreground cursor-pointer hover:text-foreground transition-colors">
141
- <input
142
- type="checkbox"
143
- checked={filterCompacted}
144
- onChange={e => { setFilterCompacted(e.target.checked); setPage(1); setFocusedIdx(null) }}
145
- className="accent-amber-500"
146
- />
147
- ⚡ compacted
148
- </label>
149
- <label className="flex items-center gap-1.5 text-[13px] text-muted-foreground cursor-pointer hover:text-foreground transition-colors">
150
- <input
151
- type="checkbox"
152
- checked={filterAgent}
153
- onChange={e => { setFilterAgent(e.target.checked); setPage(1); setFocusedIdx(null) }}
154
- className="accent-purple-500"
155
- />
156
- 🤖 agent
157
- </label>
158
- <label className="flex items-center gap-1.5 text-[13px] text-muted-foreground cursor-pointer hover:text-foreground transition-colors">
159
- <input
160
- type="checkbox"
161
- checked={filterMcp}
162
- onChange={e => { setFilterMcp(e.target.checked); setPage(1); setFocusedIdx(null) }}
163
- className="accent-blue-500"
164
- />
165
- 🔌 mcp
166
- </label>
167
- <span className="ml-auto text-[13px] text-muted-foreground">
168
- {filtered.length} sessions
169
- </span>
170
- </div>
171
-
172
- {/* Table */}
173
- <div className="border border-border rounded overflow-hidden">
174
- <div className="overflow-x-auto">
175
- <table className="w-full text-[13px]">
176
- <thead>
177
- <tr className="border-b border-border bg-muted">
178
- <th className="px-3 py-2 text-left"><SortHeader label="Date" k="start_time" sortKey={sortKey} sortDir={sortDir} onSort={toggleSort} /></th>
179
- <th className="px-3 py-2 text-left"><span className="text-[12px] font-bold uppercase tracking-wider text-muted-foreground">Project</span></th>
180
- <th className="px-3 py-2 text-right"><SortHeader label="Dur" k="duration_minutes" sortKey={sortKey} sortDir={sortDir} onSort={toggleSort} /></th>
181
- <th className="px-3 py-2 text-right"><SortHeader label="Msgs" k="total_messages" sortKey={sortKey} sortDir={sortDir} onSort={toggleSort} /></th>
182
- <th className="px-3 py-2 text-right"><SortHeader label="Tools" k="tool_calls" sortKey={sortKey} sortDir={sortDir} onSort={toggleSort} /></th>
183
- <th className="px-3 py-2 text-right"><SortHeader label="Cost" k="estimated_cost" sortKey={sortKey} sortDir={sortDir} onSort={toggleSort} /></th>
184
- <th className="px-3 py-2 text-left"><span className="text-[12px] font-bold uppercase tracking-wider text-muted-foreground">Flags</span></th>
185
- </tr>
186
- </thead>
187
- <tbody>
188
- {paginated.map((s, i) => {
189
- const totalMsgs = (s.user_message_count ?? 0) + (s.assistant_message_count ?? 0)
190
- const totalTools = Object.values(s.tool_counts ?? {}).reduce((sum, c) => sum + c, 0)
191
- const totalTokens = (s.input_tokens ?? 0) + (s.output_tokens ?? 0)
192
- const projectName = projectDisplayName(s.project_path ?? '')
193
-
194
- return (
195
- <tr
196
- key={s.session_id}
197
- ref={el => { rowRefs.current[i] = el }}
198
- className={[
199
- 'border-b border-border/50 hover:bg-muted transition-colors',
200
- i % 2 === 0 ? '' : 'bg-muted/30',
201
- i === focusedIdx ? 'ring-2 ring-primary ring-inset bg-muted' : '',
202
- ].join(' ')}
203
- >
204
- <td className="px-3 py-2 font-mono text-muted-foreground whitespace-nowrap">
205
- {formatDate(s.start_time)}
206
- </td>
207
- <td className="px-3 py-2 max-w-[200px]">
208
- <Link
209
- href={`/sessions/${s.session_id}`}
210
- className="text-foreground hover:text-primary transition-colors font-medium truncate block"
211
- title={s.project_path ?? ''}
212
- >
213
- {projectName}
214
- </Link>
215
- {s.first_prompt && (
216
- <p className="text-muted-foreground/60 truncate text-[12px]">
217
- {s.first_prompt.slice(0, 60)}
218
- </p>
219
- )}
220
- </td>
221
- <td className="px-3 py-2 text-right text-muted-foreground whitespace-nowrap">
222
- {formatDuration(s.duration_minutes ?? 0)}
223
- </td>
224
- <td className="px-3 py-2 text-right text-muted-foreground">
225
- {totalMsgs.toLocaleString()}
226
- </td>
227
- <td className="px-3 py-2 text-right text-muted-foreground">
228
- {totalTools.toLocaleString()}
229
- </td>
230
- <td className="px-3 py-2 text-right font-mono text-primary">
231
- {formatCost(s.estimated_cost)}
232
- </td>
233
- <td className="px-3 py-2">
234
- <SessionBadges
235
- has_compaction={s.has_compaction}
236
- uses_task_agent={s.uses_task_agent}
237
- uses_mcp={s.uses_mcp}
238
- uses_web_search={s.uses_web_search}
239
- uses_web_fetch={s.uses_web_fetch}
240
- has_thinking={s.has_thinking}
241
- />
242
- </td>
243
- </tr>
244
- )
245
- })}
246
- {paginated.length === 0 && (
247
- <tr>
248
- <td colSpan={7} className="px-3 py-8 text-center text-muted-foreground/50 text-[13px]">
249
- No sessions match filters
250
- </td>
251
- </tr>
252
- )}
253
- </tbody>
254
- </table>
255
- </div>
256
- </div>
257
-
258
- {/* Pagination */}
259
- {totalPages > 1 && (
260
- <div className="flex items-center justify-between text-[13px]">
261
- <span className="text-muted-foreground">
262
- Page {page} of {totalPages} · {sorted.length} sessions
263
- </span>
264
- <div className="flex gap-1">
265
- <button
266
- onClick={() => { setPage(p => Math.max(1, p - 1)); setFocusedIdx(null) }}
267
- disabled={page === 1}
268
- className="px-2 py-1 rounded border border-border text-muted-foreground hover:text-foreground hover:border-primary/40 disabled:opacity-30 disabled:cursor-not-allowed transition-colors"
269
- >
270
-
271
- </button>
272
- {(() => {
273
- const maxVisible = Math.min(5, totalPages)
274
- const startPage = Math.max(1, Math.min(page - 2, totalPages - maxVisible + 1))
275
- const numPages = Math.min(maxVisible, totalPages - startPage + 1)
276
- const pages = Array.from({ length: numPages }, (_, i) => startPage + i)
277
- return pages.map((p) => (
278
- <button
279
- key={p}
280
- onClick={() => { setPage(p); setFocusedIdx(null) }}
281
- className={`px-2 py-1 rounded border transition-colors ${p === page ? 'border-primary text-primary' : 'border-border text-muted-foreground hover:text-foreground hover:border-primary/40'}`}
282
- >
283
- {p}
284
- </button>
285
- ))
286
- })()}
287
- <button
288
- onClick={() => { setPage(p => Math.min(totalPages, p + 1)); setFocusedIdx(null) }}
289
- disabled={page === totalPages}
290
- className="px-2 py-1 rounded border border-border text-muted-foreground hover:text-foreground hover:border-primary/40 disabled:opacity-30 disabled:cursor-not-allowed transition-colors"
291
- >
292
-
293
- </button>
294
- </div>
295
- </div>
296
- )}
297
- </div>
298
- )
299
- }
@@ -1,44 +0,0 @@
1
- 'use client'
2
-
3
- import { createContext, useContext, useEffect, useState } from 'react'
4
-
5
- type Theme = 'dark' | 'light'
6
-
7
- const ThemeContext = createContext<{ theme: Theme; toggle: () => void }>({
8
- theme: 'dark',
9
- toggle: () => {},
10
- })
11
-
12
- export function useTheme() {
13
- return useContext(ThemeContext)
14
- }
15
-
16
- export function ThemeProvider({ children }: { children: React.ReactNode }) {
17
- const [theme, setTheme] = useState<Theme>('dark')
18
-
19
- useEffect(() => {
20
- const id = window.setTimeout(() => {
21
- const stored = localStorage.getItem('theme')
22
- const next: Theme = stored === 'light' ? 'light' : 'dark'
23
- setTheme(next)
24
- document.documentElement.classList.toggle('dark', next === 'dark')
25
- }, 0)
26
-
27
- return () => window.clearTimeout(id)
28
- }, [])
29
-
30
- function toggle() {
31
- setTheme(prev => {
32
- const next: Theme = prev === 'dark' ? 'light' : 'dark'
33
- localStorage.setItem('theme', next)
34
- document.documentElement.classList.toggle('dark', next === 'dark')
35
- return next
36
- })
37
- }
38
-
39
- return (
40
- <ThemeContext.Provider value={{ theme, toggle }}>
41
- {children}
42
- </ThemeContext.Provider>
43
- )
44
- }
@@ -1,58 +0,0 @@
1
- interface Props {
2
- adoption: Record<string, { sessions: number; pct: number }>
3
- totalSessions: number
4
- }
5
-
6
- const FEATURE_LABELS: Record<string, { label: string; icon: string }> = {
7
- task_agents: { label: 'Task Agents', icon: '🤖' },
8
- mcp: { label: 'MCP Servers', icon: '🔌' },
9
- web_search: { label: 'Web Search', icon: '🔍' },
10
- web_fetch: { label: 'Web Fetch', icon: '🌐' },
11
- plan_mode: { label: 'Plan Mode', icon: '📋' },
12
- git_commits: { label: 'Git Commits', icon: '📦' },
13
- extended_thinking: { label: 'Extended Thinking', icon: '🧠' },
14
- }
15
-
16
- export function FeatureAdoptionTable({ adoption, totalSessions }: Props) {
17
- const rows = Object.entries(adoption)
18
- .map(([key, data]) => ({ key, ...data, ...FEATURE_LABELS[key] }))
19
- .filter(r => r.label)
20
- .sort((a, b) => b.sessions - a.sessions)
21
-
22
- return (
23
- <div className="overflow-x-auto">
24
- <table className="w-full text-[13px]">
25
- <thead>
26
- <tr className="border-b border-border">
27
- <th className="py-2 text-left text-[12px] font-bold text-muted-foreground uppercase tracking-wider">Feature</th>
28
- <th className="py-2 text-right text-[12px] font-bold text-muted-foreground uppercase tracking-wider">Sessions</th>
29
- <th className="py-2 text-right text-[12px] font-bold text-muted-foreground uppercase tracking-wider">% of Total</th>
30
- <th className="py-2 pl-4 text-left text-[12px] font-bold text-muted-foreground uppercase tracking-wider w-32">Adoption</th>
31
- </tr>
32
- </thead>
33
- <tbody>
34
- {rows.map(r => {
35
- const pct = (r.pct * 100).toFixed(1)
36
- const width = Math.round(r.pct * 100)
37
- return (
38
- <tr key={r.key} className="border-b border-border/30 hover:bg-muted/30 transition-colors">
39
- <td className="py-2">
40
- <span className="mr-1.5">{r.icon}</span>
41
- <span className="text-foreground/80">{r.label}</span>
42
- </td>
43
- <td className="py-2 text-right text-foreground font-bold">{r.sessions}</td>
44
- <td className="py-2 text-right text-[#d97706]">{pct}%</td>
45
- <td className="py-2 pl-4">
46
- <div className="h-2 bg-muted rounded-full overflow-hidden w-24">
47
- <div className="h-full rounded-full bg-[#d97706]/60" style={{ width: `${width}%` }} />
48
- </div>
49
- </td>
50
- </tr>
51
- )
52
- })}
53
- </tbody>
54
- </table>
55
- <p className="text-[12px] text-muted-foreground/40 mt-2">{totalSessions} total sessions analyzed</p>
56
- </div>
57
- )
58
- }
@@ -1,45 +0,0 @@
1
- import type { McpServerSummary } from '@/types/claude'
2
-
3
- interface Props {
4
- servers: McpServerSummary[]
5
- }
6
-
7
- export function McpServerPanel({ servers }: Props) {
8
- if (servers.length === 0) {
9
- return <p className="text-muted-foreground/50 text-[13px]">No MCP server usage detected</p>
10
- }
11
-
12
- return (
13
- <div className="space-y-4">
14
- {servers.map(srv => {
15
- const maxCalls = srv.tools[0]?.calls ?? 1
16
- return (
17
- <div key={srv.server_name} className="border border-border/50 rounded-lg p-3">
18
- <div className="flex items-center justify-between mb-2">
19
- <span className="text-[13px] font-bold text-[#34d399]">
20
- 🔌 {srv.server_name}
21
- </span>
22
- <span className="text-[12px] text-muted-foreground/60">
23
- {srv.tools.length} tools · {srv.total_calls.toLocaleString()} calls · {srv.session_count} sessions
24
- </span>
25
- </div>
26
- <div className="space-y-1">
27
- {srv.tools.map(t => {
28
- const width = Math.max(4, Math.round((t.calls / maxCalls) * 100))
29
- return (
30
- <div key={t.name} className="flex items-center gap-2 text-[12px]">
31
- <span className="text-muted-foreground/70 w-32 truncate font-mono">{t.name}</span>
32
- <div className="flex-1 h-1.5 bg-muted rounded-full overflow-hidden">
33
- <div className="h-full rounded-full bg-[#34d399]/50" style={{ width: `${width}%` }} />
34
- </div>
35
- <span className="text-muted-foreground/50 w-12 text-right">{t.calls}</span>
36
- </div>
37
- )
38
- })}
39
- </div>
40
- </div>
41
- )
42
- })}
43
- </div>
44
- )
45
- }
@@ -1,57 +0,0 @@
1
- 'use client'
2
-
3
- import { BarChart, Bar, XAxis, YAxis, Tooltip, CartesianGrid, Cell, ResponsiveContainer } from 'recharts'
4
- import { toolBarColor } from '@/lib/tool-categories'
5
- import type { ToolSummary } from '@/types/claude'
6
-
7
- interface Props {
8
- tools: ToolSummary[]
9
- }
10
-
11
- export function ToolRankingChart({ tools }: Props) {
12
- const top = tools.slice(0, 20)
13
-
14
- return (
15
- <div>
16
- <h3 className="text-[13px] font-bold text-muted-foreground uppercase tracking-widest mb-3">
17
- Tool Usage — Ranked by Total Calls
18
- </h3>
19
- <ResponsiveContainer width="100%" height={Math.max(200, top.length * 26)}>
20
- <BarChart data={top} layout="vertical" margin={{ top: 0, right: 60, bottom: 0, left: 8 }}>
21
- <CartesianGrid strokeDasharray="3 3" stroke="var(--border)" horizontal={false} />
22
- <XAxis
23
- type="number"
24
- tick={{ fontSize: 11, fill: 'var(--muted-foreground)' }}
25
- tickLine={false}
26
- axisLine={false}
27
- tickFormatter={v => v.toLocaleString()}
28
- />
29
- <YAxis
30
- type="category"
31
- dataKey="name"
32
- tick={{ fontSize: 12, fill: 'var(--muted-foreground)' }}
33
- tickLine={false}
34
- axisLine={false}
35
- width={110}
36
- />
37
- <Tooltip
38
- contentStyle={{ background: 'var(--card)', border: '1px solid var(--border)', borderRadius: 4, fontSize: 12 }}
39
- formatter={(val: number | undefined, _name?: string, props?: { payload?: { name?: string } }) => [
40
- (val ?? 0).toLocaleString() + ' calls',
41
- props?.payload?.name ?? '',
42
- ]}
43
- />
44
- <Bar dataKey="total_calls" radius={[0, 3, 3, 0]}>
45
- {top.map((tool, i) => (
46
- <Cell
47
- key={i}
48
- fill={toolBarColor(tool.name)}
49
- fillOpacity={0.92}
50
- />
51
- ))}
52
- </Bar>
53
- </BarChart>
54
- </ResponsiveContainer>
55
- </div>
56
- )
57
- }
@@ -1,32 +0,0 @@
1
- import { formatDate } from '@/lib/decode'
2
- import type { VersionRecord } from '@/types/claude'
3
-
4
- interface Props {
5
- versions: VersionRecord[]
6
- }
7
-
8
- export function VersionHistoryTable({ versions }: Props) {
9
- return (
10
- <div className="overflow-x-auto">
11
- <table className="w-full text-[13px] font-mono">
12
- <thead>
13
- <tr className="border-b border-border">
14
- {['Version', 'Sessions', 'First Seen', 'Last Seen'].map(h => (
15
- <th key={h} className={`py-2 text-[12px] font-bold text-muted-foreground uppercase tracking-wider ${h === 'Version' ? 'text-left' : 'text-right'}`}>{h}</th>
16
- ))}
17
- </tr>
18
- </thead>
19
- <tbody>
20
- {versions.map((v, i) => (
21
- <tr key={v.version} className={`border-b border-border/30 hover:bg-muted/30 transition-colors ${i === 0 ? 'text-[#34d399]' : 'text-foreground/70'}`}>
22
- <td className="py-2 font-bold">{v.version}</td>
23
- <td className="py-2 text-right">{v.session_count}</td>
24
- <td className="py-2 text-right text-muted-foreground">{v.first_seen ? formatDate(v.first_seen) : '—'}</td>
25
- <td className="py-2 text-right text-muted-foreground">{v.last_seen ? formatDate(v.last_seen) : '—'}</td>
26
- </tr>
27
- ))}
28
- </tbody>
29
- </table>
30
- </div>
31
- )
32
- }
@@ -1,66 +0,0 @@
1
- import * as React from "react"
2
- import { cva, type VariantProps } from "class-variance-authority"
3
-
4
- import { cn } from "@/lib/utils"
5
-
6
- const alertVariants = cva(
7
- "relative grid w-full grid-cols-[0_1fr] items-start gap-y-0.5 rounded-lg border px-4 py-3 text-sm has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] has-[>svg]:gap-x-3 [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
8
- {
9
- variants: {
10
- variant: {
11
- default: "bg-card text-card-foreground",
12
- destructive:
13
- "bg-card text-destructive *:data-[slot=alert-description]:text-destructive/90 [&>svg]:text-current",
14
- },
15
- },
16
- defaultVariants: {
17
- variant: "default",
18
- },
19
- }
20
- )
21
-
22
- function Alert({
23
- className,
24
- variant,
25
- ...props
26
- }: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) {
27
- return (
28
- <div
29
- data-slot="alert"
30
- role="alert"
31
- className={cn(alertVariants({ variant }), className)}
32
- {...props}
33
- />
34
- )
35
- }
36
-
37
- function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
38
- return (
39
- <div
40
- data-slot="alert-title"
41
- className={cn(
42
- "col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight",
43
- className
44
- )}
45
- {...props}
46
- />
47
- )
48
- }
49
-
50
- function AlertDescription({
51
- className,
52
- ...props
53
- }: React.ComponentProps<"div">) {
54
- return (
55
- <div
56
- data-slot="alert-description"
57
- className={cn(
58
- "col-start-2 grid justify-items-start gap-1 text-sm text-muted-foreground [&_p]:leading-relaxed",
59
- className
60
- )}
61
- {...props}
62
- />
63
- )
64
- }
65
-
66
- export { Alert, AlertTitle, AlertDescription }
@@ -1,48 +0,0 @@
1
- import * as React from "react"
2
- import { cva, type VariantProps } from "class-variance-authority"
3
- import { Slot } from "radix-ui"
4
-
5
- import { cn } from "@/lib/utils"
6
-
7
- const badgeVariants = cva(
8
- "inline-flex items-center justify-center rounded-full border border-transparent px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
9
- {
10
- variants: {
11
- variant: {
12
- default: "bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
13
- secondary:
14
- "bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
15
- destructive:
16
- "bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
17
- outline:
18
- "border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
19
- ghost: "[a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
20
- link: "text-primary underline-offset-4 [a&]:hover:underline",
21
- },
22
- },
23
- defaultVariants: {
24
- variant: "default",
25
- },
26
- }
27
- )
28
-
29
- function Badge({
30
- className,
31
- variant = "default",
32
- asChild = false,
33
- ...props
34
- }: React.ComponentProps<"span"> &
35
- VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
36
- const Comp = asChild ? Slot.Root : "span"
37
-
38
- return (
39
- <Comp
40
- data-slot="badge"
41
- data-variant={variant}
42
- className={cn(badgeVariants({ variant }), className)}
43
- {...props}
44
- />
45
- )
46
- }
47
-
48
- export { Badge, badgeVariants }