@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,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 }