claude-memory-layer 1.0.27 → 1.0.29

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 (331) hide show
  1. package/.env.example +7 -0
  2. package/AGENTS.md +11 -0
  3. package/README.md +374 -49
  4. package/benchmarks/replay/anonymized-real-sessions.json +48 -0
  5. package/dist/cli/index.js +10097 -6003
  6. package/dist/cli/index.js.map +4 -4
  7. package/dist/core/index.js +9745 -5587
  8. package/dist/core/index.js.map +4 -4
  9. package/dist/hooks/post-tool-use.js +6545 -5270
  10. package/dist/hooks/post-tool-use.js.map +4 -4
  11. package/dist/hooks/semantic-daemon.js +6646 -5354
  12. package/dist/hooks/semantic-daemon.js.map +4 -4
  13. package/dist/hooks/session-end.js +6618 -5347
  14. package/dist/hooks/session-end.js.map +4 -4
  15. package/dist/hooks/session-start.js +6619 -5354
  16. package/dist/hooks/session-start.js.map +4 -4
  17. package/dist/hooks/stop.js +6614 -5325
  18. package/dist/hooks/stop.js.map +4 -4
  19. package/dist/hooks/user-prompt-submit.js +6702 -5356
  20. package/dist/hooks/user-prompt-submit.js.map +4 -4
  21. package/dist/index.js +13537 -0
  22. package/dist/index.js.map +7 -0
  23. package/dist/mcp/index.js +20770 -0
  24. package/dist/mcp/index.js.map +7 -0
  25. package/dist/server/api/index.js +6632 -5319
  26. package/dist/server/api/index.js.map +4 -4
  27. package/dist/server/index.js +6667 -5340
  28. package/dist/server/index.js.map +4 -4
  29. package/dist/services/memory-service.js +6568 -5350
  30. package/dist/services/memory-service.js.map +4 -4
  31. package/dist/ui/assets/js/bootstrap.js +244 -0
  32. package/dist/ui/assets/js/chat.js +373 -0
  33. package/dist/ui/assets/js/disclosure.js +232 -0
  34. package/dist/ui/assets/js/modals.js +298 -0
  35. package/dist/ui/assets/js/overview.js +655 -0
  36. package/dist/ui/assets/js/state.js +72 -0
  37. package/dist/ui/assets/js/views.js +468 -0
  38. package/dist/ui/index.html +43 -1
  39. package/dist/ui/index.ts +3 -0
  40. package/dist/ui/style.css +222 -0
  41. package/docs/ARCHITECTURE_COMPARISON_AND_RECOMMENDATIONS.md +627 -0
  42. package/docs/HERMES_MEMORY_INGESTION_ANALYSIS.md +440 -0
  43. package/docs/MEMORY_USEFULNESS_AUDIT.md +371 -0
  44. package/docs/MEMORY_USEFULNESS_AUDIT_RAW.json +80 -0
  45. package/docs/MEMSEARCH_PROJECT_STRUCTURE_ANALYSIS.md +333 -0
  46. package/docs/PRODUCT_VALIDATION_MATRIX.md +82 -0
  47. package/docs/PROJECT_STRUCTURE_ANALYSIS.md +421 -0
  48. package/docs/REFACTORING_MILESTONES_AND_ISSUES.md +501 -0
  49. package/docs/REFACTORING_PLAN_THIN_CORE.md +414 -0
  50. package/docs/REFERENCE_PROJECT_ANALYSES.md +25 -0
  51. package/docs/SUPERLOCALMEMORY_PROJECT_STRUCTURE_ANALYSIS.md +452 -0
  52. package/docs/TARGET_ARCHITECTURE_AND_FOLDER_STRUCTURE.md +446 -0
  53. package/docs/architecture/comparison-index.md +47 -0
  54. package/docs/reports/codex-real-data-validation-20260505T040447Z.md +46 -0
  55. package/package.json +12 -5
  56. package/scripts/build.ts +25 -8
  57. package/scripts/generate-session-qrels.ts +126 -0
  58. package/scripts/postinstall-embedding-backend.cjs +142 -0
  59. package/scripts/replay-retrieval-benchmark.ts +69 -0
  60. package/specs/thin-core-refactor/context.md +275 -0
  61. package/specs/thin-core-refactor/plan.md +536 -0
  62. package/specs/thin-core-refactor/spec.md +465 -0
  63. package/src/adapters/claude/capture/index.ts +3 -0
  64. package/src/adapters/claude/context/index.ts +3 -0
  65. package/src/adapters/claude/hooks/index.ts +21 -0
  66. package/src/adapters/claude/hooks/post-tool-use.ts +239 -0
  67. package/src/adapters/claude/hooks/prompt-injection-policy.ts +104 -0
  68. package/src/adapters/claude/hooks/semantic-daemon-client.ts +209 -0
  69. package/src/adapters/claude/hooks/semantic-daemon.ts +283 -0
  70. package/src/adapters/claude/hooks/session-end.ts +59 -0
  71. package/src/adapters/claude/hooks/session-start.ts +73 -0
  72. package/src/adapters/claude/hooks/stop.ts +128 -0
  73. package/src/adapters/claude/hooks/user-prompt-submit.ts +361 -0
  74. package/src/adapters/claude/index.ts +4 -0
  75. package/src/adapters/claude/transcript/index.ts +4 -0
  76. package/src/adapters/claude/transcript/transcript-reader.ts +57 -0
  77. package/src/adapters/claude/transcript/turn-reconstructor.ts +65 -0
  78. package/src/apps/cli/claude-settings-hooks.ts +138 -0
  79. package/src/apps/cli/codex-import-runner.ts +125 -0
  80. package/src/apps/cli/codex-validation-output.ts +95 -0
  81. package/src/apps/cli/hermes-import-runner.ts +130 -0
  82. package/src/apps/cli/hermes-validation-output.ts +91 -0
  83. package/src/apps/cli/index.ts +1731 -0
  84. package/src/apps/cli/mcp-install.ts +106 -0
  85. package/src/apps/cli/retrieval-disclosure-output.ts +196 -0
  86. package/src/apps/dashboard/assets/js/bootstrap.js +244 -0
  87. package/src/apps/dashboard/assets/js/chat.js +373 -0
  88. package/src/apps/dashboard/assets/js/disclosure.js +232 -0
  89. package/src/apps/dashboard/assets/js/modals.js +298 -0
  90. package/src/apps/dashboard/assets/js/overview.js +655 -0
  91. package/src/apps/dashboard/assets/js/state.js +72 -0
  92. package/src/apps/dashboard/assets/js/views.js +468 -0
  93. package/src/{ui → apps/dashboard}/index.html +43 -1
  94. package/src/apps/dashboard/index.ts +3 -0
  95. package/src/{ui → apps/dashboard}/style.css +222 -0
  96. package/src/apps/index.ts +5 -0
  97. package/src/apps/server/api/chat.ts +244 -0
  98. package/src/apps/server/api/citations.ts +105 -0
  99. package/src/apps/server/api/events.ts +137 -0
  100. package/src/apps/server/api/health.ts +53 -0
  101. package/src/apps/server/api/index.ts +26 -0
  102. package/src/apps/server/api/projects.ts +74 -0
  103. package/src/apps/server/api/search.ts +184 -0
  104. package/src/apps/server/api/sessions.ts +115 -0
  105. package/src/apps/server/api/stats.ts +723 -0
  106. package/src/apps/server/api/turns.ts +143 -0
  107. package/src/apps/server/api/utils.ts +65 -0
  108. package/src/apps/server/index.ts +111 -0
  109. package/src/cli/index.ts +2 -1311
  110. package/src/cli/retrieval-disclosure-output.ts +2 -0
  111. package/src/compat/index.ts +5 -0
  112. package/src/core/derive/fact-deriver.ts +170 -0
  113. package/src/core/derive/index.ts +2 -0
  114. package/src/core/derive/summary-deriver.ts +76 -0
  115. package/src/core/embedder.ts +4 -152
  116. package/src/core/engine/embedding-maintenance-service.ts +187 -0
  117. package/src/core/engine/endless-memory-services.ts +4 -0
  118. package/src/core/engine/index.ts +19 -0
  119. package/src/core/engine/memory-engine-services.ts +170 -0
  120. package/src/core/engine/memory-ingest-service.ts +317 -0
  121. package/src/core/engine/memory-query-service.ts +173 -0
  122. package/src/core/engine/memory-runtime-service.ts +162 -0
  123. package/src/core/engine/memory-service-composition.ts +231 -0
  124. package/src/core/engine/retrieval-analytics-service.ts +181 -0
  125. package/src/core/engine/retrieval-disclosure-service.ts +420 -0
  126. package/src/core/engine/retrieval-orchestrator.ts +377 -0
  127. package/src/core/engine/retrieval-services.ts +176 -0
  128. package/src/core/engine/shared-memory-services.ts +4 -0
  129. package/src/core/entity-repo.ts +1 -3
  130. package/src/core/event-store.ts +3 -3
  131. package/src/core/evidence-aligner.ts +2 -2
  132. package/src/core/external-market-context.ts +582 -0
  133. package/src/core/graduation.ts +2 -3
  134. package/src/core/index.ts +21 -0
  135. package/src/core/matcher.ts +2 -4
  136. package/src/core/model/memory-fact.ts +30 -0
  137. package/src/core/model/memory-rule.ts +14 -0
  138. package/src/core/model/memory-summary.ts +21 -0
  139. package/src/core/model/raw-event.ts +28 -0
  140. package/src/core/model/retrieval-result.ts +35 -0
  141. package/src/core/privacy/filter.ts +21 -10
  142. package/src/core/product-validation-matrix.ts +314 -0
  143. package/src/core/progressive-retriever.ts +1 -2
  144. package/src/core/registry/project-path.ts +54 -0
  145. package/src/core/registry/session-registry.ts +69 -0
  146. package/src/core/replay-evaluator.ts +625 -0
  147. package/src/core/retrieval-benchmark.ts +117 -0
  148. package/src/core/retrieval-quality.ts +109 -0
  149. package/src/core/retriever.ts +53 -15
  150. package/src/core/session-qrels.ts +360 -0
  151. package/src/core/shared-event-store.ts +1 -1
  152. package/src/core/sqlite-event-store.ts +35 -11
  153. package/src/core/task/blocker-resolver.ts +2 -2
  154. package/src/core/task/task-resolver.ts +0 -1
  155. package/src/core/vector-outbox.ts +1 -10
  156. package/src/core/vector-worker.ts +1 -1
  157. package/src/extensions/endless-memory/endless-memory-services.ts +350 -0
  158. package/src/extensions/endless-memory/index.ts +1 -0
  159. package/src/extensions/index.ts +5 -0
  160. package/src/extensions/mcp/handlers.ts +960 -0
  161. package/src/extensions/mcp/index.ts +48 -0
  162. package/src/extensions/mcp/tools.ts +252 -0
  163. package/src/extensions/shared-memory/index.ts +1 -0
  164. package/src/extensions/shared-memory/shared-memory-services.ts +211 -0
  165. package/src/extensions/vector/embedder.ts +197 -0
  166. package/src/extensions/vector/index.ts +1 -0
  167. package/src/hooks/post-tool-use.ts +3 -236
  168. package/src/hooks/semantic-daemon-client.ts +1 -208
  169. package/src/hooks/semantic-daemon.ts +6 -271
  170. package/src/hooks/session-end.ts +4 -79
  171. package/src/hooks/session-start.ts +4 -73
  172. package/src/hooks/stop.ts +3 -173
  173. package/src/hooks/user-prompt-submit.ts +3 -338
  174. package/src/index.ts +13 -0
  175. package/src/mcp/handlers.ts +2 -212
  176. package/src/mcp/index.ts +3 -46
  177. package/src/mcp/tools.ts +2 -78
  178. package/src/server/api/chat.ts +2 -244
  179. package/src/server/api/citations.ts +2 -105
  180. package/src/server/api/events.ts +2 -137
  181. package/src/server/api/health.ts +2 -53
  182. package/src/server/api/index.ts +2 -26
  183. package/src/server/api/projects.ts +2 -74
  184. package/src/server/api/search.ts +2 -102
  185. package/src/server/api/sessions.ts +2 -115
  186. package/src/server/api/stats.ts +2 -724
  187. package/src/server/api/turns.ts +2 -143
  188. package/src/server/api/utils.ts +2 -46
  189. package/src/server/index.ts +2 -100
  190. package/src/services/bootstrap-organizer.ts +46 -26
  191. package/src/services/codex-session-history-importer.ts +521 -29
  192. package/src/services/hermes-session-history-importer.ts +733 -0
  193. package/src/services/memory-service-config.ts +36 -0
  194. package/src/services/memory-service-registry.ts +150 -0
  195. package/src/services/memory-service.ts +211 -1325
  196. package/src/services/session-history-importer.ts +58 -14
  197. package/tests/README.md +23 -0
  198. package/tests/adapters/claude/claude-semantic-daemon-adapter.test.ts +54 -0
  199. package/tests/adapters/claude/claude-transcript-reconstructor.test.ts +98 -0
  200. package/tests/adapters/claude-hook-prompt-injection-policy.test.ts +99 -0
  201. package/tests/apps/app-layer-boundary.test.ts +48 -0
  202. package/tests/apps/claude-settings-hooks.test.ts +107 -0
  203. package/tests/apps/cli-disclosure-output.test.ts +212 -0
  204. package/tests/apps/codex-import-runner.test.ts +99 -0
  205. package/tests/apps/codex-validation-output.test.ts +100 -0
  206. package/tests/apps/hermes-import-runner.test.ts +99 -0
  207. package/tests/apps/mcp-install-command.test.ts +59 -0
  208. package/tests/apps/package-build-entrypoints.test.ts +30 -0
  209. package/tests/apps/postinstall-embedding-backend.test.ts +167 -0
  210. package/tests/apps/search-api-disclosure.test.ts +162 -0
  211. package/tests/apps/stats-api-lightweight.test.ts +67 -0
  212. package/tests/apps/ui-disclosure-output.test.ts +140 -0
  213. package/tests/{bootstrap-organizer.test.ts → core/bootstrap-organizer.test.ts} +1 -1
  214. package/tests/{canonical-key.test.ts → core/canonical-key.test.ts} +1 -1
  215. package/tests/core/codex-session-history-importer-validation.test.ts +185 -0
  216. package/tests/{consolidation-worker.test.ts → core/consolidation-worker.test.ts} +2 -2
  217. package/tests/core/embedding-maintenance-service.test.ts +282 -0
  218. package/tests/{evidence-aligner.test.ts → core/evidence-aligner.test.ts} +1 -1
  219. package/tests/core/external-market-context.test.ts +209 -0
  220. package/tests/core/fact-deriver.test.ts +79 -0
  221. package/tests/core/hermes-session-history-importer-validation.test.ts +609 -0
  222. package/tests/{ingest-interceptor.test.ts → core/ingest-interceptor.test.ts} +1 -1
  223. package/tests/{markdown-mirror.test.ts → core/markdown-mirror.test.ts} +2 -2
  224. package/tests/{matcher.test.ts → core/matcher.test.ts} +1 -1
  225. package/tests/{md-mirror.test.ts → core/md-mirror.test.ts} +2 -2
  226. package/tests/core/memory-engine-services.test.ts +240 -0
  227. package/tests/core/memory-ingest-service.test.ts +296 -0
  228. package/tests/core/memory-query-service.test.ts +129 -0
  229. package/tests/core/memory-runtime-service.test.ts +201 -0
  230. package/tests/core/memory-service-composition.test.ts +192 -0
  231. package/tests/core/memory-service-config.test.ts +41 -0
  232. package/tests/core/memory-service-facade.test.ts +30 -0
  233. package/tests/core/memory-service-registry.test.ts +206 -0
  234. package/tests/core/product-validation-matrix.test.ts +61 -0
  235. package/tests/core/project-registry.test.ts +78 -0
  236. package/tests/core/replay-evaluator.test.ts +181 -0
  237. package/tests/core/retrieval-analytics-service.test.ts +210 -0
  238. package/tests/core/retrieval-benchmark.test.ts +93 -0
  239. package/tests/core/retrieval-disclosure-service.test.ts +264 -0
  240. package/tests/core/retrieval-orchestrator.test.ts +403 -0
  241. package/tests/core/retrieval-quality.test.ts +31 -0
  242. package/tests/core/retrieval-services.test.ts +185 -0
  243. package/tests/{retriever-fallback-chain.test.ts → core/retriever-fallback-chain.test.ts} +3 -3
  244. package/tests/{retriever-strategy-scope.test.ts → core/retriever-strategy-scope.test.ts} +70 -3
  245. package/tests/{retriever.memu-adoption.test.ts → core/retriever.memu-adoption.test.ts} +3 -3
  246. package/tests/core/session-history-importer-filter.test.ts +78 -0
  247. package/tests/core/session-qrels.test.ts +250 -0
  248. package/tests/{sqlite-event-store-replication.test.ts → core/sqlite-event-store-replication.test.ts} +36 -1
  249. package/tests/core/summary-deriver.test.ts +66 -0
  250. package/tests/extensions/embedder-warning-suppression.test.ts +53 -0
  251. package/tests/extensions/endless-memory-extension-boundary.test.ts +17 -0
  252. package/tests/extensions/endless-memory-services.test.ts +325 -0
  253. package/tests/extensions/mcp-context-tools.test.ts +905 -0
  254. package/tests/extensions/mcp-extension-boundary.test.ts +21 -0
  255. package/tests/extensions/mcp-package-build.test.ts +22 -0
  256. package/tests/extensions/mcp-project-aware-tools.test.ts +102 -0
  257. package/tests/extensions/shared-memory-extension-boundary.test.ts +24 -0
  258. package/tests/extensions/shared-memory-services.test.ts +309 -0
  259. package/tests/extensions/vector-extension-boundary.test.ts +21 -0
  260. package/.claude/settings.local.json +0 -25
  261. package/.npm-cache/_cacache/content-v2/sha512/04/76/c098f88dfe584a2b80870bff7421b05d17d3d9ee1027f77772332a22d3f93a9a57101a2855107f6ad82077a818bba912b2bc317f2361b5ddb09ad284d9ce +0 -0
  262. package/.npm-cache/_cacache/content-v2/sha512/60/25/d2ecd39cfc7cab58351162814be77f935c6d6491c10c3745d456da7ddb2117ffd90c10e53fe3c0f1ed16b403307841543634504398b16ee4e6b6dd8e0c45 +0 -0
  263. package/.npm-cache/_cacache/index-v5/2b/9a/7f8f40206ed8a2e0a84efaa953ccaed1f5d001e14b931083f2e7a0738007 +0 -2
  264. package/.npm-cache/_cacache/index-v5/2e/d9/fcfa5c6a6abdc2a3644ab84a95936047298c465a2f47ee03db8f7fe1e946 +0 -3
  265. package/.npm-cache/_cacache/index-v5/a9/42/e519633356d12d3d2f19da66a8301016d496c8f5c3e0554124aaa62dc043 +0 -2
  266. package/.npm-cache/_logs/2026-02-26T12_04_52_729Z-debug-0.log +0 -256
  267. package/.npm-cache/_logs/2026-02-26T12_05_36_835Z-debug-0.log +0 -18
  268. package/.npm-cache/_logs/2026-02-26T12_05_45_982Z-debug-0.log +0 -32
  269. package/.npm-cache/_logs/2026-02-26T12_05_48_515Z-debug-0.log +0 -260
  270. package/.npm-cache/_logs/2026-02-26T12_05_53_567Z-debug-0.log +0 -69
  271. package/.npm-cache/_update-notifier-last-checked +0 -0
  272. package/bootstrap-kb/decisions/decisions.md +0 -244
  273. package/bootstrap-kb/glossary/glossary.md +0 -46
  274. package/bootstrap-kb/modules/.claude-plugin.md +0 -22
  275. package/bootstrap-kb/modules/agents.md.md +0 -15
  276. package/bootstrap-kb/modules/claude.md.md +0 -15
  277. package/bootstrap-kb/modules/context.md.md +0 -15
  278. package/bootstrap-kb/modules/docs.md +0 -18
  279. package/bootstrap-kb/modules/handoff.md.md +0 -15
  280. package/bootstrap-kb/modules/package-lock.json.md +0 -15
  281. package/bootstrap-kb/modules/package.json.md +0 -15
  282. package/bootstrap-kb/modules/plan.md.md +0 -15
  283. package/bootstrap-kb/modules/readme.md.md +0 -15
  284. package/bootstrap-kb/modules/scripts.md +0 -26
  285. package/bootstrap-kb/modules/spec.md.md +0 -15
  286. package/bootstrap-kb/modules/specs.md +0 -20
  287. package/bootstrap-kb/modules/src.md +0 -51
  288. package/bootstrap-kb/modules/tests.md +0 -42
  289. package/bootstrap-kb/modules/tsconfig.json.md +0 -15
  290. package/bootstrap-kb/modules/vitest.config.ts.md +0 -15
  291. package/bootstrap-kb/overview/overview.md +0 -40
  292. package/bootstrap-kb/sources/manifest.json +0 -950
  293. package/bootstrap-kb/sources/manifest.md +0 -227
  294. package/bootstrap-kb/timeline/timeline.md +0 -57
  295. package/claude-memory-layer-1.0.14.tgz +0 -0
  296. package/d.sh +0 -3
  297. package/deploy.sh +0 -3
  298. package/dist/ui/app.js +0 -2101
  299. package/memory/.claude-plugin/commands/2026-02-25.md +0 -263
  300. package/memory/_index.md +0 -419
  301. package/memory/agent_response/uncategorized/2026-02-26.md +0 -176
  302. package/memory/agent_response/uncategorized/2026-03-03.md +0 -14
  303. package/memory/agent_response/uncategorized/2026-03-04.md +0 -1421
  304. package/memory/agent_response/uncategorized/2026-03-05.md +0 -157
  305. package/memory/default/uncategorized/2026-02-25.md +0 -4839
  306. package/memory/session_summary/uncategorized/2026-02-26.md +0 -13
  307. package/memory/session_summary/uncategorized/2026-03-03.md +0 -5
  308. package/memory/session_summary/uncategorized/2026-03-04.md +0 -50
  309. package/memory/specs/20260207-dashboard-upgrade/2026-02-25.md +0 -142
  310. package/memory/specs/citations-system/2026-02-25.md +0 -1121
  311. package/memory/specs/endless-mode/2026-02-25.md +0 -1392
  312. package/memory/specs/entity-edge-model/2026-02-25.md +0 -1263
  313. package/memory/specs/evidence-aligner-v2/2026-02-25.md +0 -1028
  314. package/memory/specs/mcp-desktop-integration/2026-02-25.md +0 -1334
  315. package/memory/specs/post-tool-use-hook/2026-02-25.md +0 -1164
  316. package/memory/specs/private-tags/2026-02-25.md +0 -1057
  317. package/memory/specs/progressive-disclosure/2026-02-25.md +0 -1436
  318. package/memory/specs/task-entity-system/2026-02-25.md +0 -924
  319. package/memory/specs/vector-outbox-v2/2026-02-25.md +0 -1510
  320. package/memory/specs/web-viewer-ui/2026-02-25.md +0 -1709
  321. package/memory/tool_observation/uncategorized/2026-02-26.md +0 -209
  322. package/memory/tool_observation/uncategorized/2026-03-03.md +0 -21
  323. package/memory/tool_observation/uncategorized/2026-03-04.md +0 -1033
  324. package/memory/tool_observation/uncategorized/2026-03-05.md +0 -33
  325. package/memory/user_prompt/uncategorized/2026-02-26.md +0 -25
  326. package/memory/user_prompt/uncategorized/2026-03-04.md +0 -634
  327. package/memory/user_prompt/uncategorized/2026-03-05.md +0 -6
  328. package/specs/optional-duckdb/context.md +0 -77
  329. package/specs/optional-duckdb/plan.md +0 -142
  330. package/specs/optional-duckdb/spec.md +0 -35
  331. package/src/ui/app.js +0 -2101
@@ -1,1709 +0,0 @@
1
-
2
- ## 2026-02-25T12:31:26.464Z | 259d5c09-2d91-48f7-87a1-e94a0851ba2b
3
- - type: session_summary
4
- - session: import:organized
5
- # Web Viewer UI Context
6
-
7
- > **Version**: 1.0.0
8
- > **Created**: 2026-02-01
9
-
10
- ## 1. 배경
11
-
12
- ### 1.1 claude-mem의 접근 방식
13
-
14
- claude-mem은 Web Viewer를 핵심 기능으로 제공:
15
-
16
- ```
17
- http://localhost:37777
18
- ├── 실시간 메모리 스트림
19
- ├── 세션별 탐색
20
- ├── 검색 인터페이스
21
- ├── 설정 관리
22
- └── Beta 기능 토글
23
- ```
24
-
25
- **주요 특징**:
26
- - Bun 기반 HTTP 서버
27
- - 10개의 검색 엔드포인트
28
- - 실시간 observation 스트림
29
- - Settings 페이지에서 버전 전환
30
-
31
- **장점**:
32
- - CLI 한계 극복 (대량 데이터 시각화)
33
- - 디버깅 용이
34
- - 비개발자도 접근 가능
35
-
36
- ### 1.2 현재 code-memory의 상황
37
-
38
- 현재 CLI만 제공:
39
-
40
- ```bash
41
- # 현재 지원 명령어
42
- code-memory search "query"
43
- code-memory history
44
- code-memory stats
45
- code-memory forget
46
- ```
47
-
48
- **한계점**:
49
- 1. 대량 결과 탐색 불편
50
- 2. 실시간 모니터링 불가
51
- 3. 시각적 통계 없음
52
- 4. 복잡한 필터링 어려움
53
-
54
- ### 1.3 Web UI 도입 필요성
55
-
56
- | CLI | Web UI |
57
- |-----|--------|
58
- | 텍스트만 | 시각화 가능 |
59
- | 동기 실행 | 실시간 업데이트 |
60
- | 단일 작업 | 여러 뷰 동시 |
61
- | 복잡한 필터링 | 직관적 UI |
62
-
63
- ## 2. 기술 선택 이유
64
-
65
- ### 2.1 Bun 서버
66
-
67
- **선택 이유**:
68
- - Node.js 대비 3-4배 빠른 성능
69
- - 내장 WebSocket 지원
70
- - TypeScript 직접 실행
71
- - 번들러 내장
72
-
73
- **대안 비교**:
74
-
75
- | 옵션 | 장점 | 단점 |
76
- |------|------|------|
77
- | Express | 생태계 | 느림, 설정 복잡 |
78
- | Fastify | 빠름 | 설정 복잡 |
79
- | **Bun.serve** | 최고 성능, 간단 | 생태계 작음 |
80
-
81
- ### 2.2 Hono 프레임워크
82
-
83
- **선택 이유**:
84
- - 초경량 (12KB)
85
- - Bun 최적화
86
- - Express 유사 API
87
- - 미들웨어 생태계
88
-
89
- ```typescript
90
- // Hono 사용 예시
91
- import { Hono } from 'hono';
92
-
93
- const app = new Hono();
94
- app.get('/api/sessions', (c) => c.json(sessions));
95
- ```
96
-
97
- ### 2.3 Preact + HTM
98
-
99
- **선택 이유**:
100
- - React 호환 API
101
- - 3KB (React 45KB)
102
- - JSX 없이 사용 가능
103
- - 빌드 선택적
104
-
105
- ```typescript
106
- // HTM 사용 (빌드 불필요)
107
- import { html } from 'htm/preact';
108
-
109
- function App() {
110
- return html`<div class="container">Hello</div>`;
111
- }
112
- ```
113
-
114
- **대안 비교**:
115
-
116
- | 옵션 | 번들 크기 | 빌드 필요 |
117
- |------|----------|----------|
118
- | React | 45KB | 필수 |
119
- | Vue 3 | 33KB | 권장 |
120
- | Svelte | 2KB | 필수 |
121
- | **Preact** | 3KB | 선택 |
122
-
123
- ### 2.4 Tailwind CSS
124
-
125
- **선택 이유**:
126
- - 빠른 개발
127
- - 번들 크기 최적화 (JIT)
128
- - 다크 테마 기본 지원
129
-
130
- ```html
131
- <!-- CDN으로 즉시 사용 가능 -->
132
- <script src="https://cdn.tailwindcss.com"></script>
133
- ```
134
-
135
- ## 3. 기존 코드와의 관계
136
-
137
- ### 3.1 MemoryService
138
-
139
- Web 서버가 사용할 서비스 메서드:
140
-
141
- ```typescript
142
- // 현재 MemoryService
143
- export class MemoryService {
144
- // 이미 있는 것
145
- async search(query: string): Promise<SearchResult[]>;
146
- async getStats(): Promise<Stats>;
147
-
148
- // 추가 필요
149
- async getSessions(options: PageOptions): Promise<PaginatedResult<Session>>;
150
- async getSessionById(id: string): Promise<Session | null>;
151
- async getEventsBySession(sessionId: string): Promise<Event[]>;
152
- async getEventById(id: string): Promise<Event | null>;
153
- async getActivityTimeline(days: number): Promise<DailyStats[]>;
154
- }
155
- ```
156
-
157
- ### 3.2 EventStore
158
-
159
- WebSocket 브로드캐스트를 위한 이벤트 훅:
160
-
161
- ```typescript
162
- // 현재 EventStore.append()
163
- async append(event: EventInput): Promise<string> {
164
- const eventId = await this.db.insert(event);
165
- // WebSocket 브로드캐스트 추가 필요
166
- return eventId;
167
- }
168
- ```
169
-
170
- ### 3.3 VectorWorker
171
-
172
- Outbox 상태 모니터링:
173
-
174
- ```typescript
175
- // VectorWorker에 상태 노출
176
- export class VectorWorker {
177
- getStatus(): OutboxStatus {
178
- return {
179
- pending: this.pendingCount,
180
- processing: this.processingIds,
181
- failed: this.failedIds,
182
- avgTime: this.avgProcessTime
183
- };
184
- }
185
- }
186
- ```
187
-
188
- ## 4. 설계 결정 사항
189
-
190
- ### 4.1 포트 선택 (37777)
191
-
192
- **claude-mem과 동일**: 충돌 가능성 낮은 포트
193
- **설정 가능**: 환경 변수로 변경 가능
194
-
195
- ```typescript
196
- const PORT = process.env.MEMORY_VIEWER_PORT || 37777;
197
- ```
198
-
199
- ### 4.2 localhost 전용
200
-
201
- **보안 고려**:
202
- - 외부 접근 차단
203
- - 인증 불필요
204
- - CORS 제한적 허용
205
-
206
- ```typescript
207
- Bun.serve({
208
- hostname: '127.0.0.1', // localhost만
209
- port: 37777
210
- });
211
- ```
212
-
213
- ### 4.3 자동 시작 vs 수동 시작
214
-
215
- **자동 시작 선택**:
216
- - session-start 훅에서 서버 시작
217
- - 이미 실행 중이면 스킵
218
- - 사용자 개입 불필요
219
-
220
- **대안 (수동)**:
221
- ```bash
222
- code-memory serve # 별도 명령어
223
- ```
224
-
225
- ### 4.4 SSR vs CSR
226
-
227
- **CSR (Client-Side Rendering) 선택**:
228
- - 서버 복잡도 낮음
229
- - 정적 파일만 서빙
230
- - 실시간 업데이트 용이
231
-
232
- **대안 (SSR)**:
233
- - 초기 로딩 빠름
234
- - SEO (불필요)
235
- - 서버 복잡도 증가
236
-
237
- ## 5. API 설계 원칙
238
-
239
- ### 5.1 RESTful 패턴
240
-
241
- ```
242
- GET /api/sessions # 목록 조회
243
- GET /api/sessions/:id # 단일 조회
244
- GET /api/events # 목록 조회 (필터링)
245
- GET /api/events/:id # 단일 조회
246
- POST /api/search # 검색 (body에 쿼리)
247
- GET /api/stats # 통계
248
- GET /api/config # 설정 조회
249
- PATCH /api/config # 설정 수정
250
- ```
251
-
252
- ### 5.2 응답 형식
253
-
254
- ```typescript
255
- // 성공 응답
256
- {
257
- data: T,
258
- meta?: {
259
- total: number,
260
- page: number,
261
- pageSize: number
262
- }
263
- }
264
-
265
- // 에러 응답
266
- {
267
- error: {
268
- code: string,
269
- message: string
270
- }
271
- }
272
- ```
273
-
274
- ### 5.3 페이지네이션
275
-
276
- ```typescript
277
- // 쿼리 파라미터
278
- GET /api/sessions?page=1&pageSize=20
279
-
280
- // 응답
281
- {
282
- sessions: [...],
283
- meta: {
284
- total: 100,
285
- page: 1,
286
- pageSize: 20,
287
- hasMore: true
288
- }
289
- }
290
- ```
291
-
292
- ## 6. WebSocket 설계
293
-
294
- ### 6.1 연결 패턴
295
-
296
- ```
297
- 클라이언트 서버
298
- │ │
299
- │ Connect ws://... │
300
- │─────────────────────────▶│
301
- │ │
302
- │ { type: 'subscribe', │
303
- │ channels: ['events'] }│
304
- │─────────────────────────▶│
305
- │ │
306
- │ { channel: 'events', │
307
- │ data: {...} } │
308
- │◀─────────────────────────│
309
- │ │
310
- ```
311
-
312
- ### 6.2 채널 설계
313
-
314
- | 채널 | 용도 | 메시지 빈도 |
315
- |------|------|------------|
316
- | events | 새 이벤트 알림 | 높음 |
317
- | outbox | 임베딩 상태 | 중간 |
318
- | stats | 통계 업데이트 | 낮음 |
319
-
320
- ### 6.3 필터링
321
-
322
- ```typescript
323
- // 특정 세션만 구독
324
- {
325
- type: 'subscribe',
326
- channels: ['events'],
327
- filters: {
328
- sessionId: 'session_123'
329
- }
330
- }
331
- ```
332
-
333
- ## 7. 성능 고려사항
334
-
335
- ### 7.1 정적 파일 캐싱
336
-
337
- ```typescript
338
- app.use('/*', serveStatic({
339
- root: './dist/ui',
340
- maxAge: 86400 // 24시간
341
- }));
342
- ```
343
-
344
- ### 7.2 API 응답 압축
345
-
346
- ```typescript
347
- import { compress } from 'hono/compress';
348
- app.use('/*', compress());
349
- ```
350
-
351
- ### 7.3 WebSocket 메시지 배치
352
-
353
- ```typescript
354
- // 100ms 내 이벤트 모아서 전송
355
- const eventBuffer: Event[] = [];
356
- let flushTimeout: Timer | null = null;
357
-
358
- function bufferEvent(event: Event) {
359
- eventBuffer.push(event);
360
-
361
- if (!flushTimeout) {
362
- flushTimeout = setTimeout(() => {
363
- broadcastEvents(eventBuffer);
364
- eventBuffer.length = 0;
365
- flushTimeout = null;
366
- }, 100);
367
- }
368
- }
369
- ```
370
-
371
- ### 7.4 메모리 관리
372
-
373
- ```typescript
374
- // WebSocket 클라이언트 제한
375
- const MAX_CLIENTS = 10;
376
-
377
- if (clients.size >= MAX_CLIENTS) {
378
- ws.close(1013, 'Too many connections');
379
- return;
380
- }
381
- ```
382
-
383
- ## 8. 참고 자료
384
-
385
- - **claude-mem README**: Web viewer at localhost:37777
386
- - **Hono Documentation**: https://hono.dev/
387
- - **Preact Documentation**: https://preactjs.com/
388
- - **Bun Documentation**: https://bun.sh/
389
-
390
- ## 2026-02-25T12:31:26.472Z | fd5a58b1-8adc-4b89-a282-93544fc23a52
391
- - type: session_summary
392
- - session: import:organized
393
- # Web Viewer UI Implementation Plan
394
-
395
- > **Version**: 1.0.0
396
- > **Status**: Draft
397
- > **Created**: 2026-02-01
398
-
399
- ## Phase 1: 서버 인프라 (P0)
400
-
401
- ### 1.1 HTTP 서버 설정
402
-
403
- **파일**: `src/server/index.ts` (신규)
404
-
405
- ```typescript
406
- import { Hono } from 'hono';
407
- import { cors } from 'hono/cors';
408
- import { serveStatic } from 'hono/bun';
409
-
410
- const app = new Hono();
411
-
412
- // CORS (개발용)
413
- app.use('/*', cors());
414
-
415
- // Static files
416
- app.use('/*', serveStatic({ root: './dist/ui' }));
417
-
418
- // API routes
419
- app.route('/api', apiRouter);
420
-
421
- export function startServer(port: number = 37777) {
422
- return Bun.serve({
423
- hostname: '127.0.0.1',
424
- port,
425
- fetch: app.fetch
426
- });
427
- }
428
- ```
429
-
430
- **작업 항목**:
431
- - [ ] Hono 라우터 설정
432
- - [ ] Static 파일 서빙
433
- - [ ] CORS 설정
434
- - [ ] 에러 핸들링 미들웨어
435
-
436
- ### 1.2 API 라우터
437
-
438
- **파일**: `src/server/api/index.ts` (신규)
439
-
440
- ```typescript
441
- import { Hono } from 'hono';
442
- import { sessionsRouter } from './sessions';
443
- import { eventsRouter } from './events';
444
- import { searchRouter } from './search';
445
- import { statsRouter } from './stats';
446
- import { configRouter } from './config';
447
-
448
- export const apiRouter = new Hono()
449
- .route('/sessions', sessionsRouter)
450
- .route('/events', eventsRouter)
451
- .route('/search', searchRouter)
452
- .route('/stats', statsRouter)
453
- .route('/config', configRouter);
454
- ```
455
-
456
- **작업 항목**:
457
- - [ ] API 라우터 분리 구조
458
- - [ ] 공통 미들웨어 (로깅, 인증)
459
-
460
- ## Phase 2: REST API 구현 (P0)
461
-
462
- ### 2.1 Sessions API
463
-
464
- **파일**: `src/server/api/sessions.ts` (신규)
465
-
466
- ```typescript
467
- import { Hono } from 'hono';
468
- import { MemoryService } from '../../services/memory-service';
469
-
470
- export const sessionsRouter = new Hono();
471
-
472
- // GET /api/sessions
473
- sessionsRouter.get('/', async (c) => {
474
- const { page = 1, pageSize = 20 } = c.req.query();
475
- const memoryService = await MemoryService.getInstance();
476
-
477
- const sessions = await memoryService.getSessions({
478
- page: Number(page),
479
- pageSize: Number(pageSize)
480
- });
481
-
482
- return c.json({
483
- sessions: sessions.items,
484
- total: sessions.total,
485
- page: Number(page),
486
- pageSize: Number(pageSize)
487
- });
488
- });
489
-
490
- // GET /api/sessions/:id
491
- sessionsRouter.get('/:id', async (c) => {
492
- const { id } = c.req.param();
493
- const memoryService = await MemoryService.getInstance();
494
-
495
- const session = await memoryService.getSessionById(id);
496
- if (!session) {
497
- return c.json({ error: 'Session not found' }, 404);
498
- }
499
-
500
- const events = await memoryService.getEventsBySession(id);
501
- const stats = await memoryService.getSessionStats(id);
502
-
503
- return c.json({ session, events, stats });
504
- });
505
- ```
506
-
507
- **작업 항목**:
508
- - [ ] 세션 목록 조회
509
- - [ ] 세션 상세 조회
510
- - [ ] 페이지네이션 구현
511
- - [ ] 정렬 옵션
512
-
513
- ### 2.2 Events API
514
-
515
- **파일**: `src/server/api/events.ts` (신규)
516
-
517
- ```typescript
518
- export const eventsRouter = new Hono();
519
-
520
- // GET /api/events
521
- eventsRouter.get('/', async (c) => {
522
- const { sessionId, type, limit = 100, offset = 0 } = c.req.query();
523
- const memoryService = await MemoryService.getInstance();
524
-
525
- const events = await memoryService.getEvents({
526
- sessionId,
527
- eventType: type,
528
- limit: Number(limit),
529
- offset: Number(offset)
530
- });
531
-
532
- return c.json({
533
- events: events.map(e => ({
534
- eventId: e.eventId,
535
- eventType: e.eventType,
536
- timestamp: e.timestamp,
537
- sessionId: e.sessionId,
538
- preview: generatePreview(e.payload, 100)
539
- })),
540
- total: events.total
541
- });
542
- });
543
-
544
- // GET /api/events/:id
545
- eventsRouter.get('/:id', async (c) => {
546
- const { id } = c.req.param();
547
- const memoryService = await MemoryService.getInstance();
548
-
549
- const event = await memoryService.getEventById(id);
550
- if (!event) {
551
- return c.json({ error: 'Event not found' }, 404);
552
- }
553
-
554
- const related = await memoryService.getRelatedEvents(id);
555
-
556
- return c.json({ event, related });
557
- });
558
- ```
559
-
560
- **작업 항목**:
561
- - [ ] 이벤트 목록 조회 (필터링)
562
- - [ ] 이벤트 상세 조회
563
- - [ ] 미리보기 생성
564
- - [ ] 관련 이벤트 조회
565
-
566
- ### 2.3 Search API
567
-
568
- **파일**: `src/server/api/search.ts` (신규)
569
-
570
- ```typescript
571
- export const searchRouter = new Hono();
572
-
573
- // POST /api/search
574
- searchRouter.post('/', async (c) => {
575
- const body = await c.req.json<SearchRequest>();
576
- const memoryService = await MemoryService.getInstance();
577
-
578
- const startTime = Date.now();
579
-
580
- const results = await memoryService.search(body.query, {
581
- filters: body.filters,
582
- topK: body.options?.topK ?? 10,
583
- minScore: body.options?.minScore ?? 0.7,
584
- progressive: body.options?.progressive ?? true
585
- });
586
-
587
- return c.json({
588
- results: results.map(r => ({
589
- id: r.id,
590
- score: r.score,
591
- type: r.type,
592
- timestamp: r.timestamp,
593
- sessionId: r.sessionId,
594
- preview: r.preview,
595
- highlight: highlightMatches(r.content, body.query)
596
- })),
597
- meta: {
598
- totalMatches: results.length,
599
- searchTime: Date.now() - startTime,
600
- mode: 'hybrid'
601
- }
602
- });
603
- });
604
- ```
605
-
606
- **작업 항목**:
607
- - [ ] 검색 API 구현
608
- - [ ] 필터링 옵션
609
- - [ ] 하이라이트 기능
610
- - [ ] Progressive 모드 지원
611
-
612
- ### 2.4 Stats API
613
-
614
- **파일**: `src/server/api/stats.ts` (신규)
615
-
616
- ```typescript
617
- export const statsRouter = new Hono();
618
-
619
- // GET /api/stats
620
- statsRouter.get('/', async (c) => {
621
- const memoryService = await MemoryService.getInstance();
622
- const stats = await memoryService.getStats();
623
-
624
- return c.json({
625
- storage: {
626
- eventCount: stats.events.count,
627
- vectorCount: stats.vectors.count,
628
- dbSizeMB: stats.storage.duckdb / (1024 * 1024),
629
- vectorSizeMB: stats.storage.lancedb / (1024 * 1024)
630
- },
631
- sessions: {
632
- total: stats.sessions.total,
633
- active: stats.sessions.active,
634
- thisWeek: stats.sessions.thisWeek
635
- },
636
- embeddings: {
637
- pending: stats.outbox.pending,
638
- processed: stats.outbox.processed,
639
- failed: stats.outbox.failed,
640
- avgProcessTime: stats.outbox.avgTime
641
- },
642
- memory: {
643
- heapUsed: process.memoryUsage().heapUsed,
644
- heapTotal: process.memoryUsage().heapTotal
645
- }
646
- });
647
- });
648
-
649
- // GET /api/stats/timeline
650
- statsRouter.get('/timeline', async (c) => {
651
- const { days = 7 } = c.req.query();
652
- const memoryService = await MemoryService.getInstance();
653
-
654
- const timeline = await memoryService.getActivityTimeline(Number(days));
655
-
656
- return c.json({ daily: timeline });
657
- });
658
- ```
659
-
660
- **작업 항목**:
661
- - [ ] 전체 통계 조회
662
- - [ ] 타임라인 통계
663
- - [ ] 메모리 사용량
664
-
665
- ## Phase 3: WebSocket 구현 (P1)
666
-
667
- ### 3.1 WebSocket 서버
668
-
669
- **파일**: `src/server/websocket.ts` (신규)
670
-
671
- ```typescript
672
- import { EventEmitter } from 'events';
673
-
674
- const eventBus = new EventEmitter();
675
-
676
- interface WSClient {
677
- ws: WebSocket;
678
- subscriptions: Set<string>;
679
- filters: {
680
- sessionId?: string;
681
- eventType?: string[];
682
- };
683
- }
684
-
685
- const clients = new Map<string, WSClient>();
686
-
687
- export function handleWebSocket(ws: WebSocket) {
688
- const clientId = crypto.randomUUID();
689
-
690
- clients.set(clientId, {
691
- ws,
692
- subscriptions: new Set(),
693
- filters: {}
694
- });
695
-
696
- ws.onmessage = (event) => {
697
- const msg = JSON.parse(event.data);
698
-
699
- if (msg.type === 'subscribe') {
700
- const client = clients.get(clientId);
701
- msg.channels.forEach((ch: string) => client?.subscriptions.add(ch));
702
- if (msg.filters) {
703
- client!.filters = msg.filters;
704
- }
705
- }
706
-
707
- if (msg.type === 'unsubscribe') {
708
- const client = clients.get(clientId);
709
- msg.channels.forEach((ch: string) => client?.subscriptions.delete(ch));
710
- }
711
- };
712
-
713
- ws.onclose = () => {
714
- clients.delete(clientId);
715
- };
716
- }
717
-
718
- // 이벤트 브로드캐스트
719
- export function broadcastEvent(channel: string, data: unknown) {
720
- for (const client of clients.values()) {
721
- if (client.subscriptions.has(channel)) {
722
- // 필터 적용
723
- if (channel === 'events' && client.filters.sessionId) {
724
- if ((data as any).sessionId !== client.filters.sessionId) {
725
- continue;
726
- }
727
- }
728
-
729
- client.ws.send(JSON.stringify({ channel, data }));
730
- }
731
- }
732
- }
733
- ```
734
-
735
- **작업 항목**:
736
- - [ ] WebSocket 연결 관리
737
- - [ ] 구독/구독취소 처리
738
- - [ ] 필터링 적용
739
- - [ ] 브로드캐스트 함수
740
-
741
- ### 3.2 이벤트 연동
742
-
743
- **파일**: `src/services/memory-service.ts` 수정
744
-
745
- ```typescript
746
- import { broadcastEvent } from '../server/websocket';
747
-
748
- export class MemoryService {
749
- async storeEvent(event: Event): Promise<string> {
750
- const eventId = await this.eventStore.append(event);
751
-
752
- // WebSocket 브로드캐스트
753
- broadcastEvent('events', {
754
- type: 'new_event',
755
- event: {
756
- eventId,
757
- eventType: event.eventType,
758
- timestamp: event.timestamp,
759
- sessionId: event.sessionId,
760
- preview: generatePreview(event.payload, 100)
761
- }
762
- });
763
-
764
- return eventId;
765
- }
766
- }
767
- ```
768
-
769
- **작업 항목**:
770
- - [ ] 이벤트 저장 시 브로드캐스트
771
- - [ ] Outbox 상태 브로드캐스트
772
- - [ ] 통계 업데이트 브로드캐스트
773
-
774
- ## Phase 4: UI 구현 (P1)
775
-
776
- ### 4.1 HTML 템플릿
777
-
778
- **파일**: `src/ui/index.html` (신규)
779
-
780
- ```html
781
- <!DOCTYPE html>
782
- <html lang="en">
783
- <head>
784
- <meta charset="UTF-8">
785
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
786
- <title>Code Memory Dashboard</title>
787
- <script src="https://cdn.tailwindcss.com"></script>
788
- <script type="module" src="/app.js"></script>
789
- </head>
790
- <body class="bg-gray-900 text-gray-100">
791
- <div id="app"></div>
792
- </body>
793
- </html>
794
- ```
795
-
796
- **작업 항목**:
797
- - [ ] HTML 기본 템플릿
798
- - [ ] Tailwind 설정
799
- - [ ] 다크 테마
800
-
801
- ### 4.2 메인 앱
802
-
803
- **파일**: `src/ui/app.ts` (신규)
804
-
805
- ```typescript
806
- import { h, render } from 'preact';
807
- import { signal } from '@preact/signals';
808
- import { Router, Route } from 'preact-router';
809
-
810
- import { Dashboard } from './pages/Dashboard';
811
- import { Sessions } from './pages/Sessions';
812
- import { Timeline } from './pages/Timeline';
813
- import { Search } from './pages/Search';
814
- import { Stats } from './pages/Stats';
815
-
816
- const currentPath = signal(window.location.pathname);
817
-
818
- function App() {
819
- return h('div', { class: 'min-h-screen' },
820
- h('nav', { class: 'bg-gray-800 p-4' },
821
- h('div', { class: 'flex items-center gap-4' },
822
- h('span', { class: 'text-xl font-bold' }, '🧠 Code Memory'),
823
- h('a', { href: '/', class: 'hover:text-blue-400' }, 'Dashboard'),
824
- h('a', { href: '/sessions', class: 'hover:text-blue-400' }, 'Sessions'),
825
- h('a', { href: '/timeline', class: 'hover:text-blue-400' }, 'Timeline'),
826
- h('a', { href: '/search', class: 'hover:text-blue-400' }, 'Search'),
827
- h('a', { href: '/stats', class: 'hover:text-blue-400' }, 'Stats')
828
- )
829
- ),
830
- h('main', { class: 'p-4' },
831
- h(Router, {},
832
- h(Route, { path: '/', component: Dashboard }),
833
- h(Route, { path: '/sessions', component: Sessions }),
834
- h(Route, { path: '/sessions/:id', component: SessionDetail }),
835
- h(Route, { path: '/timeline', component: Timeline }),
836
- h(Route, { path: '/search', component: Search }),
837
- h(Route, { path: '/stats', component: Stats })
838
- )
839
- )
840
- );
841
- }
842
-
843
- render(h(App), document.getElementById('app')!);
844
- ```
845
-
846
- **작업 항목**:
847
- - [ ] Preact 앱 설정
848
- - [ ] 라우터 구성
849
- - [ ] 네비게이션 바
850
-
851
- ### 4.3 API 클라이언트
852
-
853
- **파일**: `src/ui/api.ts` (신규)
854
-
855
- ```typescript
856
- const BASE_URL = '/api';
857
-
858
- export async function fetchSessions(options?: { page?: number; pageSize?: number }) {
859
- const params = new URLSearchParams();
860
- if (options?.page) params.set('page', String(options.page));
861
- if (options?.pageSize) params.set('pageSize', String(options.pageSize));
862
-
863
- const res = await fetch(`${BASE_URL}/sessions?${params}`);
864
- return res.json();
865
- }
866
-
867
- export async function fetchEvents(options?: { sessionId?: string; type?: string; limit?: number }) {
868
- const params = new URLSearchParams();
869
- if (options?.sessionId) params.set('sessionId', options.sessionId);
870
- if (options?.type) params.set('type', options.type);
871
- if (options?.limit) params.set('limit', String(options.limit));
872
-
873
- const res = await fetch(`${BASE_URL}/events?${params}`);
874
- return res.json();
875
- }
876
-
877
- export async function search(query: string, options?: SearchOptions) {
878
- const res = await fetch(`${BASE_URL}/search`, {
879
- method: 'POST',
880
- headers: { 'Content-Type': 'application/json' },
881
- body: JSON.stringify({ query, options })
882
- });
883
- return res.json();
884
- }
885
-
886
- export async function fetchStats() {
887
- const res = await fetch(`${BASE_URL}/stats`);
888
- return res.json();
889
- }
890
- ```
891
-
892
- **작업 항목**:
893
- - [ ] Sessions API 클라이언트
894
- - [ ] Events API 클라이언트
895
- - [ ] Search API 클라이언트
896
- - [ ] Stats API 클라이언트
897
-
898
- ### 4.4 WebSocket 클라이언트
899
-
900
- **파일**: `src/ui/websocket.ts` (신규)
901
-
902
- ```typescript
903
- import { signal } from '@preact/signals';
904
-
905
- export const wsConnected = signal(false);
906
- export const liveEvents = signal<Event[]>([]);
907
- export const outboxStatus = signal({ pending: 0, processing: [], failed: [] });
908
-
909
- let ws: WebSocket | null = null;
910
-
911
- export function connectWebSocket() {
912
- ws = new WebSocket(`ws://${window.location.host}/ws`);
913
-
914
- ws.onopen = () => {
915
- wsConnected.value = true;
916
- ws?.send(JSON.stringify({
917
- type: 'subscribe',
918
- channels: ['events', 'outbox']
919
- }));
920
- };
921
-
922
- ws.onmessage = (event) => {
923
- const msg = JSON.parse(event.data);
924
-
925
- if (msg.channel === 'events') {
926
- liveEvents.value = [msg.data.event, ...liveEvents.value.slice(0, 99)];
927
- }
928
-
929
- if (msg.channel === 'outbox') {
930
- outboxStatus.value = msg.data;
931
- }
932
- };
933
-
934
- ws.onclose = () => {
935
- wsConnected.value = false;
936
- setTimeout(connectWebSocket, 3000); // 재연결
937
- };
938
- }
939
-
940
- export function subscribeToSession(sessionId: string) {
941
- ws?.send(JSON.stringify({
942
- type: 'subscribe',
943
- channels: ['events'],
944
- filters: { sessionId }
945
- }));
946
- }
947
- ```
948
-
949
- **작업 항목**:
950
- - [ ] WebSocket 연결 관리
951
- - [ ] 자동 재연결
952
- - [ ] 구독 관리
953
- - [ ] 실시간 상태 시그널
954
-
955
- ## Phase 5: 페이지 컴포넌트 (P1)
956
-
957
- ### 5.1 Dashboard 페이지
958
-
959
- **파일**: `src/ui/pages/Dashboard.ts` (신규)
960
-
961
- ```typescript
962
- import { h } from 'preact';
963
- import { useEffect, useState } from 'preact/hooks';
964
- import { fetchStats, fetchSessions } from '../api';
965
-
966
- export function Dashboard() {
967
- const [stats, setStats] = useState(null);
968
- const [recentSessions, setRecentSessions] = useState([]);
969
-
970
- useEffect(() => {
971
- fetchStats().then(setStats);
972
- fetchSessions({ pageSize: 5 }).then(data => setRecentSessions(data.sessions));
973
- }, []);
974
-
975
- return h('div', { class: 'space-y-6' },
976
- // Stats cards
977
- h('div', { class: 'grid grid-cols-3 gap-4' },
978
- h(StatCard, { title: 'Events', value: stats?.storage.eventCount }),
979
- h(StatCard, { title: 'Vectors', value: stats?.storage.vectorCount }),
980
- h(StatCard, { title: 'Sessions', value: stats?.sessions.total })
981
- ),
982
- // Recent sessions
983
- h('div', { class: 'bg-gray-800 rounded p-4' },
984
- h('h2', { class: 'text-lg font-semibold mb-4' }, 'Recent Sessions'),
985
- recentSessions.map(s => h(SessionItem, { session: s }))
986
- )
987
- );
988
- }
989
- ```
990
-
991
- **작업 항목**:
992
- - [ ] 통계 카드 컴포넌트
993
- - [ ] 최근 세션 목록
994
- - [ ] 실시간 업데이트
995
-
996
- ### 5.2 Timeline 페이지
997
-
998
- **파일**: `src/ui/pages/Timeline.ts` (신규)
999
-
1000
- ```typescript
1001
- import { h } from 'preact';
1002
- import { useEffect } from 'preact/hooks';
1003
- import { liveEvents, connectWebSocket } from '../websocket';
1004
-
1005
- export function Timeline() {
1006
- useEffect(() => {
1007
- connectWebSocket();
1008
- }, []);
1009
-
1010
- return h('div', { class: 'space-y-4' },
1011
- h('div', { class: 'flex items-center justify-between' },
1012
- h('h1', { class: 'text-xl font-bold' }, '📅 Timeline'),
1013
- h('span', { class: 'text-green-400' }, '● Live')
1014
- ),
1015
- h('div', { class: 'space-y-2' },
1016
- liveEvents.value.map(event =>
1017
- h(TimelineItem, { event })
1018
- )
1019
- )
1020
- );
1021
- }
1022
-
1023
- function TimelineItem({ event }) {
1024
- const icons = {
1025
- user_prompt: '💬',
1026
- assistant_response: '🤖',
1027
- tool_observation: '🛠️'
1028
- };
1029
-
1030
- return h('div', { class: 'flex gap-4 p-4 bg-gray-800 rounded' },
1031
- h('div', { class: 'text-2xl' }, icons[event.eventType] || '📝'),
1032
- h('div', { class: 'flex-1' },
1033
- h('div', { class: 'text-sm text-gray-400' },
1034
- new Date(event.timestamp).toLocaleTimeString()
1035
- ),
1036
- h('div', {}, event.preview)
1037
- )
1038
- );
1039
- }
1040
- ```
1041
-
1042
- **작업 항목**:
1043
- - [ ] 실시간 타임라인
1044
- - [ ] 이벤트 타입별 아이콘
1045
- - [ ] 필터링 옵션
1046
- - [ ] 무한 스크롤
1047
-
1048
- ### 5.3 Search 페이지
1049
-
1050
- **파일**: `src/ui/pages/Search.ts` (신규)
1051
-
1052
- ```typescript
1053
- import { h } from 'preact';
1054
- import { useState } from 'preact/hooks';
1055
- import { search } from '../api';
1056
-
1057
- export function Search() {
1058
- const [query, setQuery] = useState('');
1059
- const [results, setResults] = useState([]);
1060
- const [loading, setLoading] = useState(false);
1061
-
1062
- async function handleSearch() {
1063
- if (!query.trim()) return;
1064
- setLoading(true);
1065
- const data = await search(query);
1066
- setResults(data.results);
1067
- setLoading(false);
1068
- }
1069
-
1070
- return h('div', { class: 'space-y-4' },
1071
- h('div', { class: 'flex gap-2' },
1072
- h('input', {
1073
- type: 'text',
1074
- value: query,
1075
- onInput: (e) => setQuery(e.target.value),
1076
- onKeyDown: (e) => e.key === 'Enter' && handleSearch(),
1077
- placeholder: 'Search memories...',
1078
- class: 'flex-1 bg-gray-800 rounded px-4 py-2'
1079
- }),
1080
- h('button', {
1081
- onClick: handleSearch,
1082
- class: 'bg-blue-600 px-4 py-2 rounded'
1083
- }, 'Search')
1084
- ),
1085
- loading && h('div', {}, 'Searching...'),
1086
- h('div', { class: 'space-y-2' },
1087
- results.map(r => h(SearchResult, { result: r }))
1088
- )
1089
- );
1090
- }
1091
- ```
1092
-
1093
- **작업 항목**:
1094
- - [ ] 검색 입력
1095
- - [ ] 필터 옵션
1096
- - [ ] 결과 표시
1097
- - [ ] 하이라이트
1098
-
1099
- ## Phase 6: 빌드 및 통합 (P0)
1100
-
1101
- ### 6.1 빌드 스크립트
1102
-
1103
- **파일**: `package.json` 수정
1104
-
1105
- ```json
1106
- {
1107
- "scripts": {
1108
- "build:ui": "esbuild src/ui/app.ts --bundle --outfile=dist/ui/app.js --minify",
1109
- "build:server": "esbuild src/server/index.ts --bundle --platform=node --outfile=dist/server.js",
1110
- "dev:ui": "esbuild src/ui/app.ts --bundle --outfile=dist/ui/app.js --watch",
1111
- "start:server": "bun dist/server.js"
1112
- }
1113
- }
1114
- ```
1115
-
1116
- **작업 항목**:
1117
- - [ ] UI 빌드 스크립트
1118
- - [ ] 서버 빌드 스크립트
1119
- - [ ] 개발 모드 설정
1120
-
1121
- ### 6.2 서버 자동 시작
1122
-
1123
- **파일**: `src/hooks/session-start.ts` 수정
1124
-
1125
- ```typescript
1126
- import { startServer, isServerRunning } from '../server';
1127
-
1128
- export async function handleSessionStart(): Promise<void> {
1129
- // 서버 실행 확인 및 시작
1130
- if (!await isServerRunning(37777)) {
1131
- startServer(37777);
1132
- console.log('Memory viewer started at http://localhost:37777');
1133
- }
1134
-
1135
- // 기존 로직...
1136
- }
1137
- ```
1138
-
1139
- **작업 항목**:
1140
- - [ ] 세션 시작 시 서버 자동 시작
1141
- - [ ] 포트 충돌 처리
1142
- - [ ] 로그 출력
1143
-
1144
- ## 파일 목록
1145
-
1146
- ### 신규 파일
1147
- ```
1148
- # Server
1149
- src/server/index.ts # HTTP 서버 메인
1150
- src/server/api/index.ts # API 라우터
1151
- src/server/api/sessions.ts # Sessions API
1152
- src/server/api/events.ts # Events API
1153
- src/server/api/search.ts # Search API
1154
- src/server/api/stats.ts # Stats API
1155
- src/server/api/config.ts # Config API
1156
- src/server/websocket.ts # WebSocket 핸들러
1157
-
1158
- # UI
1159
- src/ui/index.html # HTML 템플릿
1160
- src/ui/app.ts # Preact 앱
1161
- src/ui/api.ts # API 클라이언트
1162
- src/ui/websocket.ts # WebSocket 클라이언트
1163
- src/ui/pages/Dashboard.ts # 대시보드 페이지
1164
- src/ui/pages/Sessions.ts # 세션 페이지
1165
- src/ui/pages/Timeline.ts # 타임라인 페이지
1166
- src/ui/pages/Search.ts # 검색 페이지
1167
- src/ui/pages/Stats.ts # 통계 페이지
1168
- src/ui/components/*.ts # 공통 컴포넌트
1169
- ```
1170
-
1171
- ### 수정 파일
1172
- ```
1173
- src/services/memory-service.ts # WebSocket 브로드캐스트 추가
1174
- src/hooks/session-start.ts # 서버 자동 시작
1175
- package.json # 빌드 스크립트
1176
- ```
1177
-
1178
- ## 마일스톤
1179
-
1180
- | 단계 | 완료 기준 |
1181
- |------|----------|
1182
- | M1 | HTTP 서버 + 정적 파일 서빙 |
1183
- | M2 | REST API (Sessions, Events) |
1184
- | M3 | REST API (Search, Stats, Config) |
1185
- | M4 | WebSocket 기본 구현 |
1186
- | M5 | UI 기본 레이아웃 |
1187
- | M6 | Dashboard + Timeline 페이지 |
1188
- | M7 | Search + Stats 페이지 |
1189
- | M8 | 빌드 및 통합 테스트 |
1190
-
1191
- ## 2026-02-25T12:31:26.480Z | 5489e5b4-f3f1-4933-9208-c13ca5e1bcff
1192
- - type: session_summary
1193
- - session: import:organized
1194
- # Web Viewer UI Specification
1195
-
1196
- > **Version**: 1.0.0
1197
- > **Status**: Draft
1198
- > **Created**: 2026-02-01
1199
- > **Reference**: claude-mem (thedotmack/claude-mem)
1200
-
1201
- ## 1. 개요
1202
-
1203
- ### 1.1 문제 정의
1204
-
1205
- 현재 시스템에서 메모리 상태 시각화가 어려움:
1206
-
1207
- 1. **CLI 한계**: 대량 데이터 탐색 불편
1208
- 2. **실시간 모니터링 없음**: 세션 진행 중 메모리 변화 확인 불가
1209
- 3. **디버깅 어려움**: 메모리 저장/검색 과정 추적 어려움
1210
-
1211
- ### 1.2 해결 방향
1212
-
1213
- **Web Viewer UI**:
1214
- - HTTP API 서버 (localhost:37777)
1215
- - 실시간 메모리 스트림 대시보드
1216
- - 세션/프로젝트별 탐색 인터페이스
1217
-
1218
- ## 2. 핵심 개념
1219
-
1220
- ### 2.1 시스템 아키텍처
1221
-
1222
- ```
1223
- ┌─────────────────────────────────────────────────────────────┐
1224
- │ Claude Code │
1225
- │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
1226
- │ │ Hooks │ │ CLI │ │ Memory │ │ Web │ │
1227
- │ │ │ │ │ │ Service │ │ Server │ │
1228
- │ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │
1229
- │ │ │ │ │ │
1230
- │ └────────────┴────────────┴────────────┘ │
1231
- │ │ │
1232
- └──────────────────────────┼───────────────────────────────────┘
1233
-
1234
-
1235
- ┌─────────────────────────────────────────────────────────────┐
1236
- │ Web Server (Bun) │
1237
- │ localhost:37777 │
1238
- │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
1239
- │ │ REST API │ │ WebSocket │ │ Static │ │
1240
- │ │ /api/* │ │ /ws │ │ / │ │
1241
- │ └─────────────┘ └─────────────┘ └─────────────┘ │
1242
- └─────────────────────────────────────────────────────────────┘
1243
-
1244
-
1245
- ┌─────────────────────────────────────────────────────────────┐
1246
- │ Web Browser │
1247
- │ ┌─────────────────────────────────────────────────────┐ │
1248
- │ │ Memory Dashboard │ │
1249
- │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
1250
- │ │ │ Sessions │ │ Timeline │ │ Search │ │ │
1251
- │ │ └──────────┘ └──────────┘ └──────────┘ │ │
1252
- │ └─────────────────────────────────────────────────────┘ │
1253
- └─────────────────────────────────────────────────────────────┘
1254
- ```
1255
-
1256
- ### 2.2 주요 기능
1257
-
1258
- | 기능 | 설명 |
1259
- |------|------|
1260
- | **Session Browser** | 세션 목록, 세션별 이벤트 탐색 |
1261
- | **Memory Timeline** | 시간순 메모리 스트림 (실시간) |
1262
- | **Search Interface** | 벡터 검색 + 필터링 |
1263
- | **Stats Dashboard** | 저장소 통계, 사용량 |
1264
- | **Debug View** | Outbox 상태, 임베딩 진행률 |
1265
- | **Settings** | 설정 조회/수정 |
1266
-
1267
- ### 2.3 포트 및 경로
1268
-
1269
- ```
1270
- http://localhost:37777
1271
- ├── / # 대시보드 메인
1272
- ├── /sessions # 세션 목록
1273
- ├── /sessions/:id # 세션 상세
1274
- ├── /timeline # 실시간 타임라인
1275
- ├── /search # 검색 인터페이스
1276
- ├── /stats # 통계
1277
- ├── /settings # 설정
1278
- └── /api # REST API
1279
- ├── /api/sessions
1280
- ├── /api/events
1281
- ├── /api/search
1282
- ├── /api/stats
1283
- └── /api/config
1284
- ```
1285
-
1286
- ## 3. REST API 스키마
1287
-
1288
- ### 3.1 Sessions API
1289
-
1290
- ```typescript
1291
- // GET /api/sessions
1292
- interface SessionsResponse {
1293
- sessions: {
1294
- sessionId: string;
1295
- projectPath: string;
1296
- startedAt: Date;
1297
- endedAt?: Date;
1298
- eventCount: number;
1299
- status: 'active' | 'ended';
1300
- }[];
1301
- total: number;
1302
- page: number;
1303
- pageSize: number;
1304
- }
1305
-
1306
- // GET /api/sessions/:id
1307
- interface SessionDetailResponse {
1308
- session: {
1309
- sessionId: string;
1310
- projectPath: string;
1311
- startedAt: Date;
1312
- endedAt?: Date;
1313
- summary?: string;
1314
- };
1315
- events: Event[];
1316
- stats: {
1317
- promptCount: number;
1318
- responseCount: number;
1319
- toolCount: number;
1320
- totalTokens: number;
1321
- };
1322
- }
1323
- ```
1324
-
1325
- ### 3.2 Events API
1326
-
1327
- ```typescript
1328
- // GET /api/events?sessionId=xxx&type=xxx&limit=100
1329
- interface EventsResponse {
1330
- events: {
1331
- eventId: string;
1332
- eventType: string;
1333
- timestamp: Date;
1334
- sessionId: string;
1335
- preview: string; // 100자 미리보기
1336
- metadata: {
1337
- tokenCount?: number;
1338
- hasCode?: boolean;
1339
- };
1340
- }[];
1341
- total: number;
1342
- }
1343
-
1344
- // GET /api/events/:id
1345
- interface EventDetailResponse {
1346
- event: {
1347
- eventId: string;
1348
- eventType: string;
1349
- timestamp: Date;
1350
- sessionId: string;
1351
- payload: unknown; // 전체 페이로드
1352
- };
1353
- related: {
1354
- previous?: string;
1355
- next?: string;
1356
- };
1357
- }
1358
- ```
1359
-
1360
- ### 3.3 Search API
1361
-
1362
- ```typescript
1363
- // POST /api/search
1364
- interface SearchRequest {
1365
- query: string;
1366
- filters?: {
1367
- sessionId?: string;
1368
- eventType?: string[];
1369
- dateFrom?: Date;
1370
- dateTo?: Date;
1371
- };
1372
- options?: {
1373
- topK?: number;
1374
- minScore?: number;
1375
- progressive?: boolean;
1376
- };
1377
- }
1378
-
1379
- interface SearchResponse {
1380
- results: {
1381
- id: string;
1382
- score: number;
1383
- type: string;
1384
- timestamp: Date;
1385
- sessionId: string;
1386
- preview: string;
1387
- highlight?: string; // 매칭된 부분 강조
1388
- }[];
1389
- meta: {
1390
- totalMatches: number;
1391
- searchTime: number;
1392
- mode: 'vector' | 'fts' | 'hybrid';
1393
- };
1394
- }
1395
- ```
1396
-
1397
- ### 3.4 Stats API
1398
-
1399
- ```typescript
1400
- // GET /api/stats
1401
- interface StatsResponse {
1402
- storage: {
1403
- eventCount: number;
1404
- vectorCount: number;
1405
- dbSizeMB: number;
1406
- vectorSizeMB: number;
1407
- };
1408
- sessions: {
1409
- total: number;
1410
- active: number;
1411
- thisWeek: number;
1412
- };
1413
- embeddings: {
1414
- pending: number;
1415
- processed: number;
1416
- failed: number;
1417
- avgProcessTime: number;
1418
- };
1419
- memory: {
1420
- heapUsed: number;
1421
- heapTotal: number;
1422
- };
1423
- }
1424
-
1425
- // GET /api/stats/timeline
1426
- interface TimelineStatsResponse {
1427
- daily: {
1428
- date: string;
1429
- eventCount: number;
1430
- sessionCount: number;
1431
- }[];
1432
- }
1433
- ```
1434
-
1435
- ### 3.5 Config API
1436
-
1437
- ```typescript
1438
- // GET /api/config
1439
- interface ConfigResponse {
1440
- config: MemoryConfig;
1441
- editable: string[]; // 수정 가능한 필드 목록
1442
- }
1443
-
1444
- // PATCH /api/config
1445
- interface ConfigUpdateRequest {
1446
- updates: Partial<MemoryConfig>;
1447
- }
1448
-
1449
- interface ConfigUpdateResponse {
1450
- success: boolean;
1451
- config: MemoryConfig;
1452
- restartRequired?: boolean;
1453
- }
1454
- ```
1455
-
1456
- ## 4. WebSocket 인터페이스
1457
-
1458
- ### 4.1 실시간 이벤트 스트림
1459
-
1460
- ```typescript
1461
- // 연결: ws://localhost:37777/ws
1462
-
1463
- // 클라이언트 → 서버 메시지
1464
- interface WSClientMessage {
1465
- type: 'subscribe' | 'unsubscribe';
1466
- channels: ('events' | 'stats' | 'outbox')[];
1467
- filters?: {
1468
- sessionId?: string;
1469
- eventType?: string[];
1470
- };
1471
- }
1472
-
1473
- // 서버 → 클라이언트 메시지
1474
- interface WSServerMessage {
1475
- channel: 'events' | 'stats' | 'outbox';
1476
- data: EventMessage | StatsMessage | OutboxMessage;
1477
- }
1478
-
1479
- interface EventMessage {
1480
- type: 'new_event';
1481
- event: {
1482
- eventId: string;
1483
- eventType: string;
1484
- timestamp: Date;
1485
- sessionId: string;
1486
- preview: string;
1487
- };
1488
- }
1489
-
1490
- interface OutboxMessage {
1491
- type: 'outbox_update';
1492
- pending: number;
1493
- processing: string[];
1494
- completed: string[];
1495
- failed: string[];
1496
- }
1497
- ```
1498
-
1499
- ### 4.2 연결 예시
1500
-
1501
- ```typescript
1502
- // 클라이언트 코드
1503
- const ws = new WebSocket('ws://localhost:37777/ws');
1504
-
1505
- ws.onopen = () => {
1506
- ws.send(JSON.stringify({
1507
- type: 'subscribe',
1508
- channels: ['events', 'outbox']
1509
- }));
1510
- };
1511
-
1512
- ws.onmessage = (event) => {
1513
- const msg = JSON.parse(event.data);
1514
- if (msg.channel === 'events') {
1515
- addToTimeline(msg.data.event);
1516
- } else if (msg.channel === 'outbox') {
1517
- updateOutboxStatus(msg.data);
1518
- }
1519
- };
1520
- ```
1521
-
1522
- ## 5. UI 컴포넌트
1523
-
1524
- ### 5.1 대시보드 레이아웃
1525
-
1526
- ```
1527
- ┌─────────────────────────────────────────────────────────────┐
1528
- │ 🧠 Code Memory [Search] [Settings]│
1529
- ├─────────────────────────────────────────────────────────────┤
1530
- │ ┌─────────────┐ ┌───────────────────────────────────────┐ │
1531
- │ │ │ │ │ │
1532
- │ │ Sessions │ │ Main Content Area │ │
1533
- │ │ ───────── │ │ │ │
1534
- │ │ > session1 │ │ - Timeline View │ │
1535
- │ │ session2 │ │ - Search Results │ │
1536
- │ │ session3 │ │ - Session Details │ │
1537
- │ │ ... │ │ - Stats Dashboard │ │
1538
- │ │ │ │ │ │
1539
- │ │ ───────── │ │ │ │
1540
- │ │ Projects │ │ │ │
1541
- │ │ > project1 │ │ │ │
1542
- │ │ project2 │ │ │ │
1543
- │ │ │ │ │ │
1544
- │ └─────────────┘ └───────────────────────────────────────┘ │
1545
- ├─────────────────────────────────────────────────────────────┤
1546
- │ Events: 1,234 │ Vectors: 987 │ Outbox: 5 pending │
1547
- └─────────────────────────────────────────────────────────────┘
1548
- ```
1549
-
1550
- ### 5.2 Timeline View
1551
-
1552
- ```
1553
- ┌─────────────────────────────────────────────────────────────┐
1554
- │ 📅 Timeline [Filter ▼] [Live ●] │
1555
- ├─────────────────────────────────────────────────────────────┤
1556
- │ │
1557
- │ ○─── 14:35 ───────────────────────────────────────────────│
1558
- │ │ │
1559
- │ │ 💬 User Prompt │
1560
- │ │ "DuckDB 스키마를 어떻게 설계할까요?" │
1561
- │ │ │
1562
- │ ○─── 14:36 ───────────────────────────────────────────────│
1563
- │ │ │
1564
- │ │ 🛠️ Tool: Read │
1565
- │ │ /src/core/event-store.ts │
1566
- │ │ │
1567
- │ ○─── 14:37 ───────────────────────────────────────────────│
1568
- │ │ │
1569
- │ │ 🤖 Assistant Response │
1570
- │ │ "DuckDB를 사용하여 이벤트 소싱 패턴을..." │
1571
- │ │ [Show Full] [Copy] │
1572
- │ │ │
1573
- │ ○─── 14:40 ───────────────────────────────────────────────│
1574
- │ │
1575
- └─────────────────────────────────────────────────────────────┘
1576
- ```
1577
-
1578
- ### 5.3 Search View
1579
-
1580
- ```
1581
- ┌─────────────────────────────────────────────────────────────┐
1582
- │ 🔍 Search │
1583
- ├─────────────────────────────────────────────────────────────┤
1584
- │ ┌─────────────────────────────────────────────────────┐ │
1585
- │ │ Type to search memories... │ │
1586
- │ └─────────────────────────────────────────────────────┘ │
1587
- │ │
1588
- │ Filters: [All Types ▼] [All Sessions ▼] [Date Range ▼] │
1589
- │ │
1590
- │ ───────────────────────────────────────────────────────── │
1591
- │ │
1592
- │ 📄 Result 1 (score: 0.94) │
1593
- │ Session: abc123 │ 2026-01-30 14:05 │
1594
- │ "DuckDB를 사용하여 이벤트 소싱 패턴을 구현하는 방법을..." │
1595
- │ [View] [Timeline] [Copy ID] │
1596
- │ │
1597
- │ 📄 Result 2 (score: 0.87) │
1598
- │ Session: def456 │ 2026-01-29 10:20 │
1599
- │ "타입 시스템 리팩토링 시 고려할 점..." │
1600
- │ [View] [Timeline] [Copy ID] │
1601
- │ │
1602
- └─────────────────────────────────────────────────────────────┘
1603
- ```
1604
-
1605
- ### 5.4 Stats Dashboard
1606
-
1607
- ```
1608
- ┌─────────────────────────────────────────────────────────────┐
1609
- │ 📊 Statistics [Refresh] [Export] │
1610
- ├─────────────────────────────────────────────────────────────┤
1611
- │ │
1612
- │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
1613
- │ │ Events │ │ Vectors │ │ Sessions │ │
1614
- │ │ 1,234 │ │ 987 │ │ 45 │ │
1615
- │ │ ↑12 today │ │ ↑8 today │ │ 3 active │ │
1616
- │ └──────────────┘ └──────────────┘ └──────────────┘ │
1617
- │ │
1618
- │ Storage │
1619
- │ ┌─────────────────────────────────────────────────────┐ │
1620
- │ │ DuckDB [████████░░] 156 MB / 500 MB │ │
1621
- │ │ LanceDB [███░░░░░░░] 45 MB / 500 MB │ │
1622
- │ └─────────────────────────────────────────────────────┘ │
1623
- │ │
1624
- │ Embedding Pipeline │
1625
- │ ┌─────────────────────────────────────────────────────┐ │
1626
- │ │ Pending: 5 │ Processing: 2 │ Failed: 0 │ │
1627
- │ │ Avg Time: 125ms │ │
1628
- │ └─────────────────────────────────────────────────────┘ │
1629
- │ │
1630
- │ Activity (Last 7 Days) │
1631
- │ ┌─────────────────────────────────────────────────────┐ │
1632
- │ │ ▂▄█▆▃▂▄ │ │
1633
- │ │ Mon Tue Wed Thu Fri Sat Sun │ │
1634
- │ └─────────────────────────────────────────────────────┘ │
1635
- │ │
1636
- └─────────────────────────────────────────────────────────────┘
1637
- ```
1638
-
1639
- ## 6. 기술 스택
1640
-
1641
- ### 6.1 서버
1642
-
1643
- | 컴포넌트 | 기술 | 이유 |
1644
- |----------|------|------|
1645
- | HTTP Server | Bun.serve | 빠른 성능, 번들 불필요 |
1646
- | WebSocket | Bun WebSocket | 내장 지원 |
1647
- | Router | Hono | 경량, Bun 최적화 |
1648
-
1649
- ### 6.2 클라이언트
1650
-
1651
- | 컴포넌트 | 기술 | 이유 |
1652
- |----------|------|------|
1653
- | UI Framework | Preact + HTM | 번들 크기 최소 |
1654
- | Styling | Tailwind CSS | 빠른 개발 |
1655
- | State | Signals | 경량 반응성 |
1656
- | Charts | Chart.js | 간단한 통계 시각화 |
1657
-
1658
- ### 6.3 대안
1659
-
1660
- | 옵션 | 장점 | 단점 |
1661
- |------|------|------|
1662
- | React + Vite | 생태계 | 번들 크기 |
1663
- | Vue 3 | 간결함 | 추가 학습 |
1664
- | Svelte | 번들 최소 | 생태계 작음 |
1665
- | **Preact + HTM** | 초경량, JSX 없이 | 생태계 제한 |
1666
-
1667
- ## 7. 보안
1668
-
1669
- ### 7.1 접근 제어
1670
-
1671
- ```typescript
1672
- // localhost만 허용
1673
- const server = Bun.serve({
1674
- hostname: '127.0.0.1', // localhost만
1675
- port: 37777,
1676
- // ...
1677
- });
1678
-
1679
- // 또는 토큰 기반
1680
- const AUTH_TOKEN = process.env.MEMORY_VIEWER_TOKEN;
1681
-
1682
- function authMiddleware(req: Request): boolean {
1683
- const token = req.headers.get('Authorization');
1684
- return token === `Bearer ${AUTH_TOKEN}`;
1685
- }
1686
- ```
1687
-
1688
- ### 7.2 민감 정보 필터링
1689
-
1690
- ```typescript
1691
- // API 응답에서 민감 정보 제거
1692
- function sanitizeEvent(event: Event): SanitizedEvent {
1693
- return {
1694
- ...event,
1695
- payload: maskSensitiveFields(event.payload)
1696
- };
1697
- }
1698
- ```
1699
-
1700
- ## 8. 성공 기준
1701
-
1702
- - [ ] localhost:37777에서 대시보드 접근 가능
1703
- - [ ] 세션 목록 및 상세 조회 동작
1704
- - [ ] 실시간 이벤트 스트림 표시
1705
- - [ ] 검색 기능 동작 (벡터 + FTS)
1706
- - [ ] 통계 대시보드 표시
1707
- - [ ] WebSocket 연결 안정적
1708
- - [ ] 첫 로드 < 1초
1709
- - [ ] API 응답 < 200ms