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