@bahulam/code 0.1.5 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) 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 +112 -16
  21. package/src/core/tool-executor.mjs +123 -31
  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 +255 -55
  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/project-overview.mjs +5 -2
  51. package/src/tools/registry.mjs +4 -0
  52. package/src/ui/icons.mjs +2 -0
  53. package/src/ui/palette.mjs +1 -1
  54. package/src/ui/slash-commands.mjs +4 -6
  55. package/src/ui/tool-card.mjs +7 -3
  56. package/pulse/app/activity/page.tsx +0 -190
  57. package/pulse/app/api/activity/route.ts +0 -138
  58. package/pulse/app/api/benchmark/route.ts +0 -113
  59. package/pulse/app/api/benchmarks/route.ts +0 -195
  60. package/pulse/app/api/costs/route.ts +0 -88
  61. package/pulse/app/api/export/route.ts +0 -77
  62. package/pulse/app/api/history/route.ts +0 -11
  63. package/pulse/app/api/import/route.ts +0 -31
  64. package/pulse/app/api/memory/route.ts +0 -50
  65. package/pulse/app/api/plans/route.ts +0 -9
  66. package/pulse/app/api/projects/[slug]/route.ts +0 -96
  67. package/pulse/app/api/projects/route.ts +0 -121
  68. package/pulse/app/api/sessions/[id]/replay/route.ts +0 -20
  69. package/pulse/app/api/sessions/[id]/route.ts +0 -31
  70. package/pulse/app/api/sessions/route.ts +0 -112
  71. package/pulse/app/api/settings/route.ts +0 -14
  72. package/pulse/app/api/stats/route.ts +0 -143
  73. package/pulse/app/api/todos/route.ts +0 -9
  74. package/pulse/app/api/tools/route.ts +0 -160
  75. package/pulse/app/benchmarks/page.tsx +0 -224
  76. package/pulse/app/costs/page.tsx +0 -179
  77. package/pulse/app/export/page.tsx +0 -465
  78. package/pulse/app/favicon.ico +0 -0
  79. package/pulse/app/globals.css +0 -263
  80. package/pulse/app/help/page.tsx +0 -143
  81. package/pulse/app/history/page.tsx +0 -157
  82. package/pulse/app/layout.tsx +0 -46
  83. package/pulse/app/memory/page.tsx +0 -365
  84. package/pulse/app/overview-client.tsx +0 -393
  85. package/pulse/app/page.tsx +0 -14
  86. package/pulse/app/plans/page.tsx +0 -308
  87. package/pulse/app/projects/[slug]/page.tsx +0 -390
  88. package/pulse/app/projects/page.tsx +0 -110
  89. package/pulse/app/sessions/[id]/page.tsx +0 -243
  90. package/pulse/app/sessions/page.tsx +0 -39
  91. package/pulse/app/settings/page.tsx +0 -188
  92. package/pulse/app/todos/page.tsx +0 -211
  93. package/pulse/app/tools/page.tsx +0 -249
  94. package/pulse/cli.js +0 -164
  95. package/pulse/components/activity/day-of-week-chart.tsx +0 -35
  96. package/pulse/components/activity/streak-card.tsx +0 -36
  97. package/pulse/components/costs/cache-efficiency-panel.tsx +0 -76
  98. package/pulse/components/costs/cost-by-project-chart.tsx +0 -48
  99. package/pulse/components/costs/cost-over-time-chart.tsx +0 -95
  100. package/pulse/components/costs/model-token-table.tsx +0 -60
  101. package/pulse/components/global-search.tsx +0 -193
  102. package/pulse/components/keyboard-nav-provider.tsx +0 -23
  103. package/pulse/components/layout/bottom-nav.tsx +0 -53
  104. package/pulse/components/layout/client-layout.tsx +0 -31
  105. package/pulse/components/layout/sidebar-context.tsx +0 -50
  106. package/pulse/components/layout/sidebar.tsx +0 -183
  107. package/pulse/components/layout/top-bar.tsx +0 -121
  108. package/pulse/components/overview/activity-heatmap.tsx +0 -107
  109. package/pulse/components/overview/conversation-table.tsx +0 -148
  110. package/pulse/components/overview/model-breakdown-donut.tsx +0 -95
  111. package/pulse/components/overview/peak-hours-chart.tsx +0 -87
  112. package/pulse/components/overview/project-activity-donut.tsx +0 -96
  113. package/pulse/components/overview/stat-card.tsx +0 -102
  114. package/pulse/components/overview/usage-over-time-chart.tsx +0 -166
  115. package/pulse/components/projects/project-card.tsx +0 -175
  116. package/pulse/components/sessions/replay/assistant-markdown.tsx +0 -94
  117. package/pulse/components/sessions/replay/compaction-card.tsx +0 -25
  118. package/pulse/components/sessions/replay/session-sidebar.tsx +0 -231
  119. package/pulse/components/sessions/replay/token-accumulation-chart.tsx +0 -98
  120. package/pulse/components/sessions/replay/tool-call-badge.tsx +0 -127
  121. package/pulse/components/sessions/replay/turn-cards.tsx +0 -220
  122. package/pulse/components/sessions/replay/user-tool-result.tsx +0 -158
  123. package/pulse/components/sessions/session-badges.tsx +0 -49
  124. package/pulse/components/sessions/session-table.tsx +0 -299
  125. package/pulse/components/theme-provider.tsx +0 -44
  126. package/pulse/components/tools/feature-adoption-table.tsx +0 -58
  127. package/pulse/components/tools/mcp-server-panel.tsx +0 -45
  128. package/pulse/components/tools/tool-ranking-chart.tsx +0 -57
  129. package/pulse/components/tools/version-history-table.tsx +0 -32
  130. package/pulse/components/ui/alert.tsx +0 -66
  131. package/pulse/components/ui/badge.tsx +0 -48
  132. package/pulse/components/ui/breadcrumb.tsx +0 -109
  133. package/pulse/components/ui/button.tsx +0 -64
  134. package/pulse/components/ui/calendar.tsx +0 -220
  135. package/pulse/components/ui/card.tsx +0 -92
  136. package/pulse/components/ui/command.tsx +0 -158
  137. package/pulse/components/ui/dialog.tsx +0 -158
  138. package/pulse/components/ui/input.tsx +0 -21
  139. package/pulse/components/ui/popover.tsx +0 -89
  140. package/pulse/components/ui/progress.tsx +0 -31
  141. package/pulse/components/ui/select.tsx +0 -190
  142. package/pulse/components/ui/separator.tsx +0 -28
  143. package/pulse/components/ui/sheet.tsx +0 -143
  144. package/pulse/components/ui/skeleton.tsx +0 -13
  145. package/pulse/components/ui/table.tsx +0 -116
  146. package/pulse/components/ui/tabs.tsx +0 -91
  147. package/pulse/components/ui/tooltip.tsx +0 -57
  148. package/pulse/components/use-global-keyboard-nav.ts +0 -79
  149. package/pulse/components.json +0 -23
  150. package/pulse/eslint.config.mjs +0 -18
  151. package/pulse/lib/bahulam-paths.ts +0 -23
  152. package/pulse/lib/claude-reader.ts +0 -592
  153. package/pulse/lib/decode.ts +0 -129
  154. package/pulse/lib/pricing.ts +0 -102
  155. package/pulse/lib/replay-parser.ts +0 -165
  156. package/pulse/lib/tool-categories.ts +0 -140
  157. package/pulse/lib/utils.ts +0 -6
  158. package/pulse/next-env.d.ts +0 -6
  159. package/pulse/next.config.ts +0 -16
  160. package/pulse/package.json +0 -45
  161. package/pulse/postcss.config.mjs +0 -7
  162. package/pulse/public/activity.png +0 -0
  163. package/pulse/public/cc-lens.png +0 -0
  164. package/pulse/public/command-k.png +0 -0
  165. package/pulse/public/costs.png +0 -0
  166. package/pulse/public/dashboard-dark.png +0 -0
  167. package/pulse/public/dashboard-white.png +0 -0
  168. package/pulse/public/export.png +0 -0
  169. package/pulse/public/file.svg +0 -1
  170. package/pulse/public/globe.svg +0 -1
  171. package/pulse/public/next.svg +0 -1
  172. package/pulse/public/projects.png +0 -0
  173. package/pulse/public/session-chat.png +0 -0
  174. package/pulse/public/todos.png +0 -0
  175. package/pulse/public/tools.png +0 -0
  176. package/pulse/public/vercel.svg +0 -1
  177. package/pulse/public/window.svg +0 -1
  178. package/pulse/tsconfig.json +0 -34
  179. package/pulse/types/claude.ts +0 -294
@@ -1,365 +0,0 @@
1
- 'use client'
2
-
3
- import { useState, useMemo } from 'react'
4
- import useSWR, { mutate } from 'swr'
5
- import { TopBar } from '@/components/layout/top-bar'
6
- import type { MemoryEntry, MemoryType } from '@/lib/claude-reader'
7
- import { projectDisplayName, projectShortPath, formatRelativeDate } from '@/lib/decode'
8
-
9
- const fetcher = (url: string) =>
10
- fetch(url).then(r => { if (!r.ok) throw new Error(`API error ${r.status}`); return r.json() })
11
-
12
- // ── Type config ───────────────────────────────────────────────────────────────
13
-
14
- const TYPE_META: Record<MemoryType, { label: string; color: string; bg: string; border: string; dot: string }> = {
15
- user: { label: 'user', color: 'text-blue-700 dark:text-blue-400', bg: 'bg-blue-700/10 dark:bg-blue-400/10', border: 'border-blue-700/30 dark:border-blue-400/30', dot: 'var(--viz-sky)' },
16
- feedback: { label: 'feedback', color: 'text-[#f87171]', bg: 'bg-[#f87171]/10', border: 'border-[#f87171]/30', dot: '#f87171' },
17
- project: { label: 'project', color: 'text-[#a78bfa]', bg: 'bg-[#a78bfa]/10', border: 'border-[#a78bfa]/30', dot: '#a78bfa' },
18
- reference: { label: 'reference', color: 'text-[#34d399]', bg: 'bg-[#34d399]/10', border: 'border-[#34d399]/30', dot: '#34d399' },
19
- index: { label: 'index', color: 'text-[#fbbf24]', bg: 'bg-[#fbbf24]/10', border: 'border-[#fbbf24]/30', dot: '#fbbf24' },
20
- unknown: { label: '?', color: 'text-muted-foreground', bg: 'bg-muted', border: 'border-border', dot: '#94a3b8' },
21
- }
22
-
23
- const FILTER_TYPES = ['all', 'user', 'feedback', 'project', 'reference', 'index'] as const
24
- type FilterType = typeof FILTER_TYPES[number]
25
-
26
- function TypeBadge({ type }: { type: MemoryType }) {
27
- const m = TYPE_META[type] ?? TYPE_META.unknown
28
- return (
29
- <span className={`text-[10px] font-mono font-bold uppercase tracking-wider px-1.5 py-0.5 rounded border ${m.color} ${m.bg} ${m.border}`}>
30
- {m.label}
31
- </span>
32
- )
33
- }
34
-
35
- function StaleBadge({ mtime }: { mtime: string }) {
36
- // eslint-disable-next-line react-hooks/purity
37
- const daysOld = Math.floor((Date.now() - new Date(mtime).getTime()) / 86_400_000)
38
- if (daysOld < 30) return null
39
- return (
40
- <span className="text-[10px] font-mono px-1.5 py-0.5 rounded border border-[#f87171]/30 bg-[#f87171]/10 text-[#f87171]">
41
- stale
42
- </span>
43
- )
44
- }
45
-
46
- // ── Memory card ───────────────────────────────────────────────────────────────
47
-
48
- function MemoryCard({ entry, onClick, expanded }: { entry: MemoryEntry; onClick: () => void; expanded: boolean }) {
49
- const projectName = projectDisplayName(entry.projectPath)
50
- const shortPath = projectShortPath(entry.projectPath)
51
- const m = TYPE_META[entry.type] ?? TYPE_META.unknown
52
- const preview = entry.body.slice(0, 200).replace(/\n+/g, ' ').trim()
53
-
54
- const [editing, setEditing] = useState(false)
55
- const [draft, setDraft] = useState(entry.body)
56
- const [saving, setSaving] = useState(false)
57
- const [saveError, setSaveError] = useState<string | null>(null)
58
-
59
- async function handleSave(e: React.MouseEvent) {
60
- e.stopPropagation()
61
- setSaving(true)
62
- setSaveError(null)
63
- try {
64
- const res = await fetch('/api/memory', {
65
- method: 'PATCH',
66
- headers: { 'Content-Type': 'application/json' },
67
- body: JSON.stringify({
68
- projectSlug: entry.projectSlug,
69
- file: entry.file,
70
- content: draft,
71
- }),
72
- })
73
- if (!res.ok) {
74
- const { error } = await res.json()
75
- setSaveError(error ?? 'Save failed')
76
- } else {
77
- setEditing(false)
78
- mutate('/api/memory')
79
- }
80
- } catch (err) {
81
- setSaveError(String(err))
82
- } finally {
83
- setSaving(false)
84
- }
85
- }
86
-
87
- function handleEdit(e: React.MouseEvent) {
88
- e.stopPropagation()
89
- setDraft(entry.body)
90
- setSaveError(null)
91
- setEditing(true)
92
- }
93
-
94
- function handleCancel(e: React.MouseEvent) {
95
- e.stopPropagation()
96
- setEditing(false)
97
- setSaveError(null)
98
- setDraft(entry.body)
99
- }
100
-
101
- return (
102
- <div
103
- className={[
104
- 'border rounded-lg bg-card transition-all',
105
- editing ? 'cursor-default' : 'cursor-pointer',
106
- expanded ? '' : 'hover:border-primary/30',
107
- 'border-border',
108
- ].join(' ')}
109
- onClick={editing ? undefined : onClick}
110
- style={expanded ? { borderColor: m.dot + '66' } : undefined}
111
- >
112
- <div className="px-4 py-3.5 flex items-start gap-3">
113
- {/* Type dot */}
114
- <div
115
- className="flex-shrink-0 w-2 h-2 rounded-full mt-2"
116
- style={{ backgroundColor: m.dot }}
117
- />
118
-
119
- <div className="flex-1 min-w-0 space-y-1.5">
120
- {/* Header row */}
121
- <div className="flex flex-wrap items-center gap-2">
122
- <span className="text-sm font-mono font-semibold text-foreground truncate">{entry.name}</span>
123
- <TypeBadge type={entry.type} />
124
- <StaleBadge mtime={entry.mtime} />
125
- {expanded && !editing && (
126
- <button
127
- onClick={handleEdit}
128
- className="ml-auto text-[10px] font-mono px-2 py-0.5 rounded border border-border text-muted-foreground hover:border-primary/40 hover:text-foreground transition-colors"
129
- >
130
- edit
131
- </button>
132
- )}
133
- </div>
134
-
135
- {/* Description */}
136
- {entry.description && (
137
- <p className="text-xs font-mono text-muted-foreground">{entry.description}</p>
138
- )}
139
-
140
- {/* Body preview (collapsed) */}
141
- {!expanded && preview && (
142
- <p className="text-xs font-mono text-muted-foreground/60 line-clamp-2">{preview}</p>
143
- )}
144
-
145
- {/* Full body (expanded, read mode) */}
146
- {expanded && !editing && (
147
- <pre className="mt-2 text-xs font-mono text-foreground/80 whitespace-pre-wrap bg-muted/40 rounded p-3 overflow-x-auto max-h-96 overflow-y-auto">
148
- {entry.body}
149
- </pre>
150
- )}
151
-
152
- {/* Edit mode */}
153
- {expanded && editing && (
154
- <div className="mt-2 space-y-2" onClick={e => e.stopPropagation()}>
155
- <textarea
156
- className="w-full min-h-64 bg-muted/40 border border-primary/40 rounded p-3 text-xs font-mono text-foreground resize-y outline-none focus:border-primary/70 transition-colors"
157
- value={draft}
158
- onChange={e => setDraft(e.target.value)}
159
- spellCheck={false}
160
- />
161
- {saveError && (
162
- <p className="text-[11px] font-mono text-[#f87171]">{saveError}</p>
163
- )}
164
- <div className="flex gap-2">
165
- <button
166
- onClick={handleSave}
167
- disabled={saving}
168
- className="px-3 py-1.5 text-xs font-mono rounded border border-[#34d399]/50 text-[#34d399] bg-[#34d399]/10 hover:bg-[#34d399]/20 disabled:opacity-50 transition-colors"
169
- >
170
- {saving ? 'saving…' : 'save'}
171
- </button>
172
- <button
173
- onClick={handleCancel}
174
- disabled={saving}
175
- className="px-3 py-1.5 text-xs font-mono rounded border border-border text-muted-foreground hover:text-foreground hover:border-primary/30 disabled:opacity-50 transition-colors"
176
- >
177
- cancel
178
- </button>
179
- </div>
180
- </div>
181
- )}
182
-
183
- {/* Footer */}
184
- <div className="flex flex-wrap items-center gap-2 pt-0.5">
185
- <span className="text-[10px] font-mono text-muted-foreground/60 bg-muted px-1.5 py-0.5 rounded">
186
- {projectName}
187
- </span>
188
- <span className="text-[10px] font-mono text-muted-foreground/40">{shortPath}</span>
189
- <span className="text-[10px] font-mono text-muted-foreground/50 ml-auto">
190
- {formatRelativeDate(entry.mtime)}
191
- </span>
192
- </div>
193
- </div>
194
- </div>
195
- </div>
196
- )
197
- }
198
-
199
- // ── Stat card ─────────────────────────────────────────────────────────────────
200
-
201
- function StatCard({ value, label, color }: { value: number; label: string; color: string }) {
202
- return (
203
- <div className="border border-border bg-card rounded-lg px-4 py-3 flex flex-col gap-1">
204
- <span className="text-2xl font-mono font-bold" style={{ color }}>{value}</span>
205
- <span className="text-[10px] font-mono uppercase tracking-widest text-muted-foreground">{label}</span>
206
- </div>
207
- )
208
- }
209
-
210
- // ── Page ──────────────────────────────────────────────────────────────────────
211
-
212
- export default function MemoryPage() {
213
- const { data, error, isLoading } = useSWR<{ memories: MemoryEntry[] }>(
214
- '/api/memory', fetcher, { refreshInterval: 15_000 }
215
- )
216
- const [filter, setFilter] = useState<FilterType>('all')
217
- const [search, setSearch] = useState('')
218
- const [expandedId, setExpandedId] = useState<string | null>(null)
219
-
220
- const memories = data?.memories ?? []
221
-
222
- const counts = useMemo(() => {
223
- const c: Record<string, number> = { all: memories.length }
224
- for (const type of ['user', 'feedback', 'project', 'reference', 'index']) {
225
- c[type] = memories.filter(m => m.type === type).length
226
- }
227
- return c
228
- }, [memories])
229
-
230
- const staleCount = useMemo(
231
- // eslint-disable-next-line react-hooks/purity
232
- () => memories.filter(m => (Date.now() - new Date(m.mtime).getTime()) / 86_400_000 >= 30).length,
233
- [memories]
234
- )
235
-
236
- const projectCount = useMemo(
237
- () => new Set(memories.map(m => m.projectSlug)).size,
238
- [memories]
239
- )
240
-
241
- const filtered = useMemo(() => {
242
- return memories.filter(m => {
243
- if (filter !== 'all' && m.type !== filter) return false
244
- if (search) {
245
- const q = search.toLowerCase()
246
- return (
247
- m.name.toLowerCase().includes(q) ||
248
- m.description.toLowerCase().includes(q) ||
249
- m.body.toLowerCase().includes(q) ||
250
- m.projectPath.toLowerCase().includes(q)
251
- )
252
- }
253
- return true
254
- })
255
- }, [memories, filter, search])
256
-
257
- function toggleExpand(id: string) {
258
- setExpandedId(prev => (prev === id ? null : id))
259
- }
260
-
261
- return (
262
- <div className="flex flex-col min-h-screen">
263
- <TopBar title="Bahulam Pulse · memory" subtitle="~/.bahulam/projects/*/memory/" />
264
- <div className="p-4 md:p-6 space-y-5">
265
-
266
- {error && <p className="text-[#f87171] text-sm font-mono">Error loading memories.</p>}
267
-
268
- {isLoading && (
269
- <div className="space-y-3">
270
- <div className="grid grid-cols-2 md:grid-cols-4 gap-3">
271
- {Array.from({ length: 4 }).map((_, i) => (
272
- <div key={i} className="h-20 bg-muted rounded-lg animate-pulse" />
273
- ))}
274
- </div>
275
- {Array.from({ length: 6 }).map((_, i) => (
276
- <div key={i} className="h-24 bg-muted rounded-lg animate-pulse" />
277
- ))}
278
- </div>
279
- )}
280
-
281
- {data && (
282
- <>
283
- {/* Stat cards */}
284
- <div className="grid grid-cols-2 md:grid-cols-4 gap-3">
285
- <StatCard value={memories.length} label="total memories" color="#fbbf24" />
286
- <StatCard value={projectCount} label="projects" color="var(--viz-sky)" />
287
- <StatCard value={counts.feedback ?? 0} label="feedback" color="#f87171" />
288
- <StatCard value={staleCount} label="stale (>30d)" color="#94a3b8" />
289
- </div>
290
-
291
- {/* Type filter tabs */}
292
- <div className="flex flex-wrap gap-2">
293
- {FILTER_TYPES.map(type => {
294
- const m = type === 'all' ? null : TYPE_META[type as MemoryType]
295
- const count = counts[type] ?? 0
296
- const active = filter === type
297
- return (
298
- <button
299
- key={type}
300
- onClick={() => setFilter(type)}
301
- className={[
302
- 'flex items-center gap-1.5 px-3 py-1.5 rounded border text-xs font-mono transition-all',
303
- active
304
- ? 'border-primary/50 bg-primary/10 text-primary'
305
- : 'border-border bg-card text-muted-foreground hover:border-primary/30 hover:text-foreground',
306
- ].join(' ')}
307
- >
308
- {m && (
309
- <span className="w-1.5 h-1.5 rounded-full flex-shrink-0" style={{ backgroundColor: m.dot }} />
310
- )}
311
- {type}
312
- <span className="opacity-60">({count})</span>
313
- </button>
314
- )
315
- })}
316
- </div>
317
-
318
- {/* Search */}
319
- <div className="border border-border rounded-lg bg-card focus-within:border-primary/40 transition-colors">
320
- <input
321
- className="w-full bg-transparent px-4 py-2.5 text-sm font-mono text-foreground placeholder-muted-foreground/50 outline-none"
322
- placeholder="search memories..."
323
- value={search}
324
- onChange={e => setSearch(e.target.value)}
325
- />
326
- </div>
327
-
328
- {/* Result count */}
329
- {(search || filter !== 'all') && (
330
- <p className="text-xs font-mono text-muted-foreground/60">
331
- showing <span className="text-[#fbbf24]">{filtered.length}</span> of {memories.length} memories
332
- </p>
333
- )}
334
-
335
- {/* Memory list */}
336
- {filtered.length === 0 ? (
337
- <div className="text-center py-16">
338
- <p className="text-3xl mb-3">🧠</p>
339
- <p className="text-muted-foreground/60 text-sm font-mono">
340
- {memories.length === 0
341
- ? 'No memory files found in ~/.bahulam/projects/*/memory/'
342
- : 'No memories match your filter.'}
343
- </p>
344
- </div>
345
- ) : (
346
- <div className="space-y-2">
347
- {filtered.map(entry => {
348
- const id = `${entry.projectSlug}/${entry.file}`
349
- return (
350
- <MemoryCard
351
- key={id}
352
- entry={entry}
353
- expanded={expandedId === id}
354
- onClick={() => toggleExpand(id)}
355
- />
356
- )
357
- })}
358
- </div>
359
- )}
360
- </>
361
- )}
362
- </div>
363
- </div>
364
- )
365
- }