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