superlocalmemory 2.8.6 → 3.0.0

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 (431) hide show
  1. package/LICENSE +9 -1
  2. package/NOTICE +63 -0
  3. package/README.md +165 -480
  4. package/bin/slm +17 -449
  5. package/bin/slm-npm +1 -1
  6. package/conftest.py +5 -0
  7. package/docs/api-reference.md +284 -0
  8. package/docs/architecture.md +149 -0
  9. package/docs/auto-memory.md +150 -0
  10. package/docs/cli-reference.md +276 -0
  11. package/docs/compliance.md +191 -0
  12. package/docs/configuration.md +182 -0
  13. package/docs/getting-started.md +102 -0
  14. package/docs/ide-setup.md +261 -0
  15. package/docs/mcp-tools.md +220 -0
  16. package/docs/migration-from-v2.md +170 -0
  17. package/docs/profiles.md +173 -0
  18. package/docs/troubleshooting.md +310 -0
  19. package/{configs → ide/configs}/antigravity-mcp.json +3 -3
  20. package/ide/configs/chatgpt-desktop-mcp.json +16 -0
  21. package/{configs → ide/configs}/claude-desktop-mcp.json +3 -3
  22. package/{configs → ide/configs}/codex-mcp.toml +4 -4
  23. package/{configs → ide/configs}/continue-mcp.yaml +4 -3
  24. package/{configs → ide/configs}/continue-skills.yaml +6 -6
  25. package/ide/configs/cursor-mcp.json +15 -0
  26. package/{configs → ide/configs}/gemini-cli-mcp.json +2 -2
  27. package/{configs → ide/configs}/jetbrains-mcp.json +2 -2
  28. package/{configs → ide/configs}/opencode-mcp.json +2 -2
  29. package/{configs → ide/configs}/perplexity-mcp.json +2 -2
  30. package/{configs → ide/configs}/vscode-copilot-mcp.json +2 -2
  31. package/{configs → ide/configs}/windsurf-mcp.json +3 -3
  32. package/{configs → ide/configs}/zed-mcp.json +2 -2
  33. package/{hooks → ide/hooks}/context-hook.js +9 -20
  34. package/ide/hooks/memory-list-skill.js +70 -0
  35. package/ide/hooks/memory-profile-skill.js +101 -0
  36. package/ide/hooks/memory-recall-skill.js +62 -0
  37. package/ide/hooks/memory-remember-skill.js +68 -0
  38. package/ide/hooks/memory-reset-skill.js +160 -0
  39. package/{hooks → ide/hooks}/post-recall-hook.js +2 -2
  40. package/ide/integrations/langchain/README.md +106 -0
  41. package/ide/integrations/langchain/langchain_superlocalmemory/__init__.py +9 -0
  42. package/ide/integrations/langchain/langchain_superlocalmemory/chat_message_history.py +201 -0
  43. package/ide/integrations/langchain/pyproject.toml +38 -0
  44. package/{src/learning → ide/integrations/langchain}/tests/__init__.py +1 -0
  45. package/ide/integrations/langchain/tests/test_chat_message_history.py +215 -0
  46. package/ide/integrations/langchain/tests/test_security.py +117 -0
  47. package/ide/integrations/llamaindex/README.md +81 -0
  48. package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/__init__.py +9 -0
  49. package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/base.py +316 -0
  50. package/ide/integrations/llamaindex/pyproject.toml +43 -0
  51. package/{src/lifecycle → ide/integrations/llamaindex}/tests/__init__.py +1 -2
  52. package/ide/integrations/llamaindex/tests/test_chat_store.py +294 -0
  53. package/ide/integrations/llamaindex/tests/test_security.py +241 -0
  54. package/{skills → ide/skills}/slm-build-graph/SKILL.md +6 -6
  55. package/{skills → ide/skills}/slm-list-recent/SKILL.md +5 -5
  56. package/{skills → ide/skills}/slm-recall/SKILL.md +5 -5
  57. package/{skills → ide/skills}/slm-remember/SKILL.md +6 -6
  58. package/{skills → ide/skills}/slm-show-patterns/SKILL.md +7 -7
  59. package/{skills → ide/skills}/slm-status/SKILL.md +9 -9
  60. package/{skills → ide/skills}/slm-switch-profile/SKILL.md +9 -9
  61. package/package.json +13 -22
  62. package/pyproject.toml +85 -0
  63. package/scripts/build-dmg.sh +417 -0
  64. package/scripts/install-skills.ps1 +334 -0
  65. package/scripts/postinstall.js +2 -2
  66. package/scripts/start-dashboard.ps1 +52 -0
  67. package/scripts/start-dashboard.sh +41 -0
  68. package/scripts/sync-wiki.ps1 +127 -0
  69. package/scripts/sync-wiki.sh +82 -0
  70. package/scripts/test-dmg.sh +161 -0
  71. package/scripts/test-npm-package.ps1 +252 -0
  72. package/scripts/test-npm-package.sh +207 -0
  73. package/scripts/verify-install.ps1 +294 -0
  74. package/scripts/verify-install.sh +266 -0
  75. package/src/superlocalmemory/__init__.py +0 -0
  76. package/src/superlocalmemory/attribution/__init__.py +9 -0
  77. package/src/superlocalmemory/attribution/mathematical_dna.py +235 -0
  78. package/src/superlocalmemory/attribution/signer.py +153 -0
  79. package/src/superlocalmemory/attribution/watermark.py +189 -0
  80. package/src/superlocalmemory/cli/__init__.py +5 -0
  81. package/src/superlocalmemory/cli/commands.py +245 -0
  82. package/src/superlocalmemory/cli/main.py +89 -0
  83. package/src/superlocalmemory/cli/migrate_cmd.py +55 -0
  84. package/src/superlocalmemory/cli/post_install.py +99 -0
  85. package/src/superlocalmemory/cli/setup_wizard.py +129 -0
  86. package/src/superlocalmemory/compliance/__init__.py +0 -0
  87. package/src/superlocalmemory/compliance/abac.py +204 -0
  88. package/src/superlocalmemory/compliance/audit.py +314 -0
  89. package/src/superlocalmemory/compliance/eu_ai_act.py +131 -0
  90. package/src/superlocalmemory/compliance/gdpr.py +294 -0
  91. package/src/superlocalmemory/compliance/lifecycle.py +158 -0
  92. package/src/superlocalmemory/compliance/retention.py +232 -0
  93. package/src/superlocalmemory/compliance/scheduler.py +148 -0
  94. package/src/superlocalmemory/core/__init__.py +0 -0
  95. package/src/superlocalmemory/core/config.py +391 -0
  96. package/src/superlocalmemory/core/embeddings.py +293 -0
  97. package/src/superlocalmemory/core/engine.py +701 -0
  98. package/src/superlocalmemory/core/hooks.py +65 -0
  99. package/src/superlocalmemory/core/maintenance.py +172 -0
  100. package/src/superlocalmemory/core/modes.py +140 -0
  101. package/src/superlocalmemory/core/profiles.py +234 -0
  102. package/src/superlocalmemory/core/registry.py +117 -0
  103. package/src/superlocalmemory/dynamics/__init__.py +0 -0
  104. package/src/superlocalmemory/dynamics/fisher_langevin_coupling.py +223 -0
  105. package/src/superlocalmemory/encoding/__init__.py +0 -0
  106. package/src/superlocalmemory/encoding/consolidator.py +485 -0
  107. package/src/superlocalmemory/encoding/emotional.py +125 -0
  108. package/src/superlocalmemory/encoding/entity_resolver.py +525 -0
  109. package/src/superlocalmemory/encoding/entropy_gate.py +104 -0
  110. package/src/superlocalmemory/encoding/fact_extractor.py +775 -0
  111. package/src/superlocalmemory/encoding/foresight.py +91 -0
  112. package/src/superlocalmemory/encoding/graph_builder.py +302 -0
  113. package/src/superlocalmemory/encoding/observation_builder.py +160 -0
  114. package/src/superlocalmemory/encoding/scene_builder.py +183 -0
  115. package/src/superlocalmemory/encoding/signal_inference.py +90 -0
  116. package/src/superlocalmemory/encoding/temporal_parser.py +426 -0
  117. package/src/superlocalmemory/encoding/type_router.py +235 -0
  118. package/src/superlocalmemory/hooks/__init__.py +3 -0
  119. package/src/superlocalmemory/hooks/auto_capture.py +111 -0
  120. package/src/superlocalmemory/hooks/auto_recall.py +93 -0
  121. package/src/superlocalmemory/hooks/ide_connector.py +204 -0
  122. package/src/superlocalmemory/hooks/rules_engine.py +99 -0
  123. package/src/superlocalmemory/infra/__init__.py +3 -0
  124. package/src/superlocalmemory/infra/auth_middleware.py +82 -0
  125. package/src/superlocalmemory/infra/backup.py +317 -0
  126. package/src/superlocalmemory/infra/cache_manager.py +267 -0
  127. package/src/superlocalmemory/infra/event_bus.py +381 -0
  128. package/src/superlocalmemory/infra/rate_limiter.py +135 -0
  129. package/src/{webhook_dispatcher.py → superlocalmemory/infra/webhook_dispatcher.py} +104 -101
  130. package/src/superlocalmemory/learning/__init__.py +0 -0
  131. package/src/superlocalmemory/learning/adaptive.py +172 -0
  132. package/src/superlocalmemory/learning/behavioral.py +490 -0
  133. package/src/superlocalmemory/learning/behavioral_listener.py +94 -0
  134. package/src/superlocalmemory/learning/bootstrap.py +298 -0
  135. package/src/superlocalmemory/learning/cross_project.py +399 -0
  136. package/src/superlocalmemory/learning/database.py +376 -0
  137. package/src/superlocalmemory/learning/engagement.py +323 -0
  138. package/src/superlocalmemory/learning/features.py +138 -0
  139. package/src/superlocalmemory/learning/feedback.py +316 -0
  140. package/src/superlocalmemory/learning/outcomes.py +255 -0
  141. package/src/superlocalmemory/learning/project_context.py +366 -0
  142. package/src/superlocalmemory/learning/ranker.py +155 -0
  143. package/src/superlocalmemory/learning/source_quality.py +303 -0
  144. package/src/superlocalmemory/learning/workflows.py +309 -0
  145. package/src/superlocalmemory/llm/__init__.py +0 -0
  146. package/src/superlocalmemory/llm/backbone.py +316 -0
  147. package/src/superlocalmemory/math/__init__.py +0 -0
  148. package/src/superlocalmemory/math/fisher.py +356 -0
  149. package/src/superlocalmemory/math/langevin.py +398 -0
  150. package/src/superlocalmemory/math/sheaf.py +257 -0
  151. package/src/superlocalmemory/mcp/__init__.py +0 -0
  152. package/src/superlocalmemory/mcp/resources.py +245 -0
  153. package/src/superlocalmemory/mcp/server.py +61 -0
  154. package/src/superlocalmemory/mcp/tools.py +18 -0
  155. package/src/superlocalmemory/mcp/tools_core.py +305 -0
  156. package/src/superlocalmemory/mcp/tools_v28.py +223 -0
  157. package/src/superlocalmemory/mcp/tools_v3.py +286 -0
  158. package/src/superlocalmemory/retrieval/__init__.py +0 -0
  159. package/src/superlocalmemory/retrieval/agentic.py +295 -0
  160. package/src/superlocalmemory/retrieval/ann_index.py +223 -0
  161. package/src/superlocalmemory/retrieval/bm25_channel.py +185 -0
  162. package/src/superlocalmemory/retrieval/bridge_discovery.py +170 -0
  163. package/src/superlocalmemory/retrieval/engine.py +390 -0
  164. package/src/superlocalmemory/retrieval/entity_channel.py +179 -0
  165. package/src/superlocalmemory/retrieval/fusion.py +78 -0
  166. package/src/superlocalmemory/retrieval/profile_channel.py +105 -0
  167. package/src/superlocalmemory/retrieval/reranker.py +154 -0
  168. package/src/superlocalmemory/retrieval/semantic_channel.py +232 -0
  169. package/src/superlocalmemory/retrieval/strategy.py +96 -0
  170. package/src/superlocalmemory/retrieval/temporal_channel.py +175 -0
  171. package/src/superlocalmemory/server/__init__.py +1 -0
  172. package/src/superlocalmemory/server/api.py +248 -0
  173. package/src/superlocalmemory/server/routes/__init__.py +4 -0
  174. package/src/superlocalmemory/server/routes/agents.py +107 -0
  175. package/src/superlocalmemory/server/routes/backup.py +91 -0
  176. package/src/superlocalmemory/server/routes/behavioral.py +127 -0
  177. package/src/superlocalmemory/server/routes/compliance.py +160 -0
  178. package/src/superlocalmemory/server/routes/data_io.py +188 -0
  179. package/src/superlocalmemory/server/routes/events.py +183 -0
  180. package/src/superlocalmemory/server/routes/helpers.py +85 -0
  181. package/src/superlocalmemory/server/routes/learning.py +273 -0
  182. package/src/superlocalmemory/server/routes/lifecycle.py +116 -0
  183. package/src/superlocalmemory/server/routes/memories.py +399 -0
  184. package/src/superlocalmemory/server/routes/profiles.py +219 -0
  185. package/src/superlocalmemory/server/routes/stats.py +346 -0
  186. package/src/superlocalmemory/server/routes/v3_api.py +365 -0
  187. package/src/superlocalmemory/server/routes/ws.py +82 -0
  188. package/src/superlocalmemory/server/security_middleware.py +57 -0
  189. package/src/superlocalmemory/server/ui.py +245 -0
  190. package/src/superlocalmemory/storage/__init__.py +0 -0
  191. package/src/superlocalmemory/storage/access_control.py +182 -0
  192. package/src/superlocalmemory/storage/database.py +594 -0
  193. package/src/superlocalmemory/storage/migrations.py +303 -0
  194. package/src/superlocalmemory/storage/models.py +406 -0
  195. package/src/superlocalmemory/storage/schema.py +726 -0
  196. package/src/superlocalmemory/storage/v2_migrator.py +317 -0
  197. package/src/superlocalmemory/trust/__init__.py +0 -0
  198. package/src/superlocalmemory/trust/gate.py +130 -0
  199. package/src/superlocalmemory/trust/provenance.py +124 -0
  200. package/src/superlocalmemory/trust/scorer.py +347 -0
  201. package/src/superlocalmemory/trust/signals.py +153 -0
  202. package/ui/index.html +278 -5
  203. package/ui/js/auto-settings.js +70 -0
  204. package/ui/js/dashboard.js +90 -0
  205. package/ui/js/fact-detail.js +92 -0
  206. package/ui/js/feedback.js +2 -2
  207. package/ui/js/ide-status.js +102 -0
  208. package/ui/js/math-health.js +98 -0
  209. package/ui/js/recall-lab.js +127 -0
  210. package/ui/js/settings.js +2 -2
  211. package/ui/js/trust-dashboard.js +73 -0
  212. package/api_server.py +0 -724
  213. package/bin/aider-smart +0 -72
  214. package/bin/superlocalmemoryv2-learning +0 -4
  215. package/bin/superlocalmemoryv2-list +0 -3
  216. package/bin/superlocalmemoryv2-patterns +0 -4
  217. package/bin/superlocalmemoryv2-profile +0 -3
  218. package/bin/superlocalmemoryv2-recall +0 -3
  219. package/bin/superlocalmemoryv2-remember +0 -3
  220. package/bin/superlocalmemoryv2-reset +0 -3
  221. package/bin/superlocalmemoryv2-status +0 -3
  222. package/configs/chatgpt-desktop-mcp.json +0 -16
  223. package/configs/cursor-mcp.json +0 -15
  224. package/hooks/memory-list-skill.js +0 -139
  225. package/hooks/memory-profile-skill.js +0 -273
  226. package/hooks/memory-recall-skill.js +0 -114
  227. package/hooks/memory-remember-skill.js +0 -127
  228. package/hooks/memory-reset-skill.js +0 -274
  229. package/mcp_server.py +0 -1808
  230. package/requirements-core.txt +0 -22
  231. package/requirements-learning.txt +0 -12
  232. package/requirements.txt +0 -12
  233. package/src/agent_registry.py +0 -411
  234. package/src/auth_middleware.py +0 -61
  235. package/src/auto_backup.py +0 -459
  236. package/src/behavioral/__init__.py +0 -49
  237. package/src/behavioral/behavioral_listener.py +0 -203
  238. package/src/behavioral/behavioral_patterns.py +0 -275
  239. package/src/behavioral/cross_project_transfer.py +0 -206
  240. package/src/behavioral/outcome_inference.py +0 -194
  241. package/src/behavioral/outcome_tracker.py +0 -193
  242. package/src/behavioral/tests/__init__.py +0 -4
  243. package/src/behavioral/tests/test_behavioral_integration.py +0 -108
  244. package/src/behavioral/tests/test_behavioral_patterns.py +0 -150
  245. package/src/behavioral/tests/test_cross_project_transfer.py +0 -142
  246. package/src/behavioral/tests/test_mcp_behavioral.py +0 -139
  247. package/src/behavioral/tests/test_mcp_report_outcome.py +0 -117
  248. package/src/behavioral/tests/test_outcome_inference.py +0 -107
  249. package/src/behavioral/tests/test_outcome_tracker.py +0 -96
  250. package/src/cache_manager.py +0 -518
  251. package/src/compliance/__init__.py +0 -48
  252. package/src/compliance/abac_engine.py +0 -149
  253. package/src/compliance/abac_middleware.py +0 -116
  254. package/src/compliance/audit_db.py +0 -215
  255. package/src/compliance/audit_logger.py +0 -148
  256. package/src/compliance/retention_manager.py +0 -289
  257. package/src/compliance/retention_scheduler.py +0 -186
  258. package/src/compliance/tests/__init__.py +0 -4
  259. package/src/compliance/tests/test_abac_enforcement.py +0 -95
  260. package/src/compliance/tests/test_abac_engine.py +0 -124
  261. package/src/compliance/tests/test_abac_mcp_integration.py +0 -118
  262. package/src/compliance/tests/test_audit_db.py +0 -123
  263. package/src/compliance/tests/test_audit_logger.py +0 -98
  264. package/src/compliance/tests/test_mcp_audit.py +0 -128
  265. package/src/compliance/tests/test_mcp_retention_policy.py +0 -125
  266. package/src/compliance/tests/test_retention_manager.py +0 -131
  267. package/src/compliance/tests/test_retention_scheduler.py +0 -99
  268. package/src/compression/__init__.py +0 -25
  269. package/src/compression/cli.py +0 -150
  270. package/src/compression/cold_storage.py +0 -217
  271. package/src/compression/config.py +0 -72
  272. package/src/compression/orchestrator.py +0 -133
  273. package/src/compression/tier2_compressor.py +0 -228
  274. package/src/compression/tier3_compressor.py +0 -153
  275. package/src/compression/tier_classifier.py +0 -148
  276. package/src/db_connection_manager.py +0 -536
  277. package/src/embedding_engine.py +0 -63
  278. package/src/embeddings/__init__.py +0 -47
  279. package/src/embeddings/cache.py +0 -70
  280. package/src/embeddings/cli.py +0 -113
  281. package/src/embeddings/constants.py +0 -47
  282. package/src/embeddings/database.py +0 -91
  283. package/src/embeddings/engine.py +0 -247
  284. package/src/embeddings/model_loader.py +0 -145
  285. package/src/event_bus.py +0 -562
  286. package/src/graph/__init__.py +0 -36
  287. package/src/graph/build_helpers.py +0 -74
  288. package/src/graph/cli.py +0 -87
  289. package/src/graph/cluster_builder.py +0 -188
  290. package/src/graph/cluster_summary.py +0 -148
  291. package/src/graph/constants.py +0 -47
  292. package/src/graph/edge_builder.py +0 -162
  293. package/src/graph/entity_extractor.py +0 -95
  294. package/src/graph/graph_core.py +0 -226
  295. package/src/graph/graph_search.py +0 -231
  296. package/src/graph/hierarchical.py +0 -207
  297. package/src/graph/schema.py +0 -99
  298. package/src/graph_engine.py +0 -52
  299. package/src/hnsw_index.py +0 -628
  300. package/src/hybrid_search.py +0 -46
  301. package/src/learning/__init__.py +0 -217
  302. package/src/learning/adaptive_ranker.py +0 -682
  303. package/src/learning/bootstrap/__init__.py +0 -69
  304. package/src/learning/bootstrap/constants.py +0 -93
  305. package/src/learning/bootstrap/db_queries.py +0 -316
  306. package/src/learning/bootstrap/sampling.py +0 -82
  307. package/src/learning/bootstrap/text_utils.py +0 -71
  308. package/src/learning/cross_project_aggregator.py +0 -857
  309. package/src/learning/db/__init__.py +0 -40
  310. package/src/learning/db/constants.py +0 -44
  311. package/src/learning/db/schema.py +0 -279
  312. package/src/learning/engagement_tracker.py +0 -628
  313. package/src/learning/feature_extractor.py +0 -708
  314. package/src/learning/feedback_collector.py +0 -806
  315. package/src/learning/learning_db.py +0 -915
  316. package/src/learning/project_context_manager.py +0 -572
  317. package/src/learning/ranking/__init__.py +0 -33
  318. package/src/learning/ranking/constants.py +0 -84
  319. package/src/learning/ranking/helpers.py +0 -278
  320. package/src/learning/source_quality_scorer.py +0 -676
  321. package/src/learning/synthetic_bootstrap.py +0 -755
  322. package/src/learning/tests/test_adaptive_ranker.py +0 -325
  323. package/src/learning/tests/test_adaptive_ranker_v28.py +0 -60
  324. package/src/learning/tests/test_aggregator.py +0 -306
  325. package/src/learning/tests/test_auto_retrain_v28.py +0 -35
  326. package/src/learning/tests/test_e2e_ranking_v28.py +0 -82
  327. package/src/learning/tests/test_feature_extractor_v28.py +0 -93
  328. package/src/learning/tests/test_feedback_collector.py +0 -294
  329. package/src/learning/tests/test_learning_db.py +0 -602
  330. package/src/learning/tests/test_learning_db_v28.py +0 -110
  331. package/src/learning/tests/test_learning_init_v28.py +0 -48
  332. package/src/learning/tests/test_outcome_signals.py +0 -48
  333. package/src/learning/tests/test_project_context.py +0 -292
  334. package/src/learning/tests/test_schema_migration.py +0 -319
  335. package/src/learning/tests/test_signal_inference.py +0 -397
  336. package/src/learning/tests/test_source_quality.py +0 -351
  337. package/src/learning/tests/test_synthetic_bootstrap.py +0 -429
  338. package/src/learning/tests/test_workflow_miner.py +0 -318
  339. package/src/learning/workflow_pattern_miner.py +0 -655
  340. package/src/lifecycle/__init__.py +0 -54
  341. package/src/lifecycle/bounded_growth.py +0 -239
  342. package/src/lifecycle/compaction_engine.py +0 -226
  343. package/src/lifecycle/lifecycle_engine.py +0 -355
  344. package/src/lifecycle/lifecycle_evaluator.py +0 -257
  345. package/src/lifecycle/lifecycle_scheduler.py +0 -130
  346. package/src/lifecycle/retention_policy.py +0 -285
  347. package/src/lifecycle/tests/test_bounded_growth.py +0 -193
  348. package/src/lifecycle/tests/test_compaction.py +0 -179
  349. package/src/lifecycle/tests/test_lifecycle_engine.py +0 -137
  350. package/src/lifecycle/tests/test_lifecycle_evaluation.py +0 -177
  351. package/src/lifecycle/tests/test_lifecycle_scheduler.py +0 -127
  352. package/src/lifecycle/tests/test_lifecycle_search.py +0 -109
  353. package/src/lifecycle/tests/test_mcp_compact.py +0 -149
  354. package/src/lifecycle/tests/test_mcp_lifecycle_status.py +0 -114
  355. package/src/lifecycle/tests/test_retention_policy.py +0 -162
  356. package/src/mcp_tools_v28.py +0 -281
  357. package/src/memory/__init__.py +0 -36
  358. package/src/memory/cli.py +0 -205
  359. package/src/memory/constants.py +0 -39
  360. package/src/memory/helpers.py +0 -28
  361. package/src/memory/schema.py +0 -166
  362. package/src/memory-profiles.py +0 -595
  363. package/src/memory-reset.py +0 -491
  364. package/src/memory_compression.py +0 -989
  365. package/src/memory_store_v2.py +0 -1155
  366. package/src/migrate_v1_to_v2.py +0 -629
  367. package/src/pattern_learner.py +0 -34
  368. package/src/patterns/__init__.py +0 -24
  369. package/src/patterns/analyzers.py +0 -251
  370. package/src/patterns/learner.py +0 -271
  371. package/src/patterns/scoring.py +0 -171
  372. package/src/patterns/store.py +0 -225
  373. package/src/patterns/terminology.py +0 -140
  374. package/src/provenance_tracker.py +0 -312
  375. package/src/qualixar_attribution.py +0 -139
  376. package/src/qualixar_watermark.py +0 -78
  377. package/src/query_optimizer.py +0 -511
  378. package/src/rate_limiter.py +0 -83
  379. package/src/search/__init__.py +0 -20
  380. package/src/search/cli.py +0 -77
  381. package/src/search/constants.py +0 -26
  382. package/src/search/engine.py +0 -241
  383. package/src/search/fusion.py +0 -122
  384. package/src/search/index_loader.py +0 -114
  385. package/src/search/methods.py +0 -162
  386. package/src/search_engine_v2.py +0 -401
  387. package/src/setup_validator.py +0 -482
  388. package/src/subscription_manager.py +0 -391
  389. package/src/tree/__init__.py +0 -59
  390. package/src/tree/builder.py +0 -185
  391. package/src/tree/nodes.py +0 -202
  392. package/src/tree/queries.py +0 -257
  393. package/src/tree/schema.py +0 -80
  394. package/src/tree_manager.py +0 -19
  395. package/src/trust/__init__.py +0 -45
  396. package/src/trust/constants.py +0 -66
  397. package/src/trust/queries.py +0 -157
  398. package/src/trust/schema.py +0 -95
  399. package/src/trust/scorer.py +0 -299
  400. package/src/trust/signals.py +0 -95
  401. package/src/trust_scorer.py +0 -44
  402. package/ui/app.js +0 -1588
  403. package/ui/js/graph-cytoscape-monolithic-backup.js +0 -1168
  404. package/ui/js/graph-cytoscape.js +0 -1168
  405. package/ui/js/graph-d3-backup.js +0 -32
  406. package/ui/js/graph.js +0 -32
  407. package/ui_server.py +0 -286
  408. /package/docs/{ACCESSIBILITY.md → v2-archive/ACCESSIBILITY.md} +0 -0
  409. /package/docs/{ARCHITECTURE.md → v2-archive/ARCHITECTURE.md} +0 -0
  410. /package/docs/{CLI-COMMANDS-REFERENCE.md → v2-archive/CLI-COMMANDS-REFERENCE.md} +0 -0
  411. /package/docs/{COMPRESSION-README.md → v2-archive/COMPRESSION-README.md} +0 -0
  412. /package/docs/{FRAMEWORK-INTEGRATIONS.md → v2-archive/FRAMEWORK-INTEGRATIONS.md} +0 -0
  413. /package/docs/{MCP-MANUAL-SETUP.md → v2-archive/MCP-MANUAL-SETUP.md} +0 -0
  414. /package/docs/{MCP-TROUBLESHOOTING.md → v2-archive/MCP-TROUBLESHOOTING.md} +0 -0
  415. /package/docs/{PATTERN-LEARNING.md → v2-archive/PATTERN-LEARNING.md} +0 -0
  416. /package/docs/{PROFILES-GUIDE.md → v2-archive/PROFILES-GUIDE.md} +0 -0
  417. /package/docs/{RESET-GUIDE.md → v2-archive/RESET-GUIDE.md} +0 -0
  418. /package/docs/{SEARCH-ENGINE-V2.2.0.md → v2-archive/SEARCH-ENGINE-V2.2.0.md} +0 -0
  419. /package/docs/{SEARCH-INTEGRATION-GUIDE.md → v2-archive/SEARCH-INTEGRATION-GUIDE.md} +0 -0
  420. /package/docs/{UI-SERVER.md → v2-archive/UI-SERVER.md} +0 -0
  421. /package/docs/{UNIVERSAL-INTEGRATION.md → v2-archive/UNIVERSAL-INTEGRATION.md} +0 -0
  422. /package/docs/{V2.2.0-OPTIONAL-SEARCH.md → v2-archive/V2.2.0-OPTIONAL-SEARCH.md} +0 -0
  423. /package/docs/{WINDOWS-INSTALL-README.txt → v2-archive/WINDOWS-INSTALL-README.txt} +0 -0
  424. /package/docs/{WINDOWS-POST-INSTALL.txt → v2-archive/WINDOWS-POST-INSTALL.txt} +0 -0
  425. /package/docs/{example_graph_usage.py → v2-archive/example_graph_usage.py} +0 -0
  426. /package/{completions → ide/completions}/slm.bash +0 -0
  427. /package/{completions → ide/completions}/slm.zsh +0 -0
  428. /package/{configs → ide/configs}/cody-commands.json +0 -0
  429. /package/{install-skills.sh → scripts/install-skills.sh} +0 -0
  430. /package/{install.ps1 → scripts/install.ps1} +0 -0
  431. /package/{install.sh → scripts/install.sh} +0 -0
package/ui/app.js DELETED
@@ -1,1588 +0,0 @@
1
- // SuperLocalMemory V2 - UI Application
2
- // Note: All data from API is from our own trusted local database.
3
- // All user-facing strings are escaped via escapeHtml() before DOM insertion.
4
- // innerHTML usage is safe here: all dynamic values are sanitized, and no
5
- // external/untrusted input reaches the DOM.
6
-
7
- let graphData = { nodes: [], links: [] };
8
- let currentMemoryDetail = null; // Memory currently shown in modal
9
- let lastSearchResults = null; // Cached search results for export
10
-
11
- // ============================================================================
12
- // Dark Mode
13
- // ============================================================================
14
-
15
- function initDarkMode() {
16
- var saved = localStorage.getItem('slm-theme');
17
- var theme;
18
- if (saved) {
19
- theme = saved;
20
- } else {
21
- // Respect system preference on first load
22
- theme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
23
- }
24
- applyTheme(theme);
25
- }
26
-
27
- function applyTheme(theme) {
28
- document.documentElement.setAttribute('data-bs-theme', theme);
29
- var icon = document.getElementById('theme-icon');
30
- if (icon) {
31
- icon.className = theme === 'dark' ? 'bi bi-moon-stars-fill' : 'bi bi-sun-fill';
32
- }
33
- }
34
-
35
- function toggleDarkMode() {
36
- var current = document.documentElement.getAttribute('data-bs-theme');
37
- var next = current === 'dark' ? 'light' : 'dark';
38
- localStorage.setItem('slm-theme', next);
39
- applyTheme(next);
40
- }
41
-
42
- // ============================================================================
43
- // Animated Counter
44
- // ============================================================================
45
-
46
- function animateCounter(elementId, target) {
47
- var el = document.getElementById(elementId);
48
- if (!el) return;
49
- var duration = 600;
50
- var startTime = null;
51
-
52
- function step(timestamp) {
53
- if (!startTime) startTime = timestamp;
54
- var progress = Math.min((timestamp - startTime) / duration, 1);
55
- var eased = 1 - Math.pow(1 - progress, 3); // ease-out cubic
56
- el.textContent = Math.floor(eased * target).toLocaleString();
57
- if (progress < 1) {
58
- requestAnimationFrame(step);
59
- } else {
60
- el.textContent = target.toLocaleString();
61
- }
62
- }
63
-
64
- if (target === 0) {
65
- el.textContent = '0';
66
- } else {
67
- requestAnimationFrame(step);
68
- }
69
- }
70
-
71
- // ============================================================================
72
- // HTML Escaping — all dynamic text MUST pass through this before DOM insertion
73
- // ============================================================================
74
-
75
- function escapeHtml(text) {
76
- if (!text) return '';
77
- var div = document.createElement('div');
78
- div.appendChild(document.createTextNode(String(text)));
79
- return div.innerHTML;
80
- }
81
-
82
- // ============================================================================
83
- // Loading / Empty State helpers
84
- // ============================================================================
85
-
86
- function showLoading(containerId, message) {
87
- var el = document.getElementById(containerId);
88
- if (!el) return;
89
- // Build DOM nodes instead of innerHTML for loading state
90
- el.textContent = '';
91
- var wrapper = document.createElement('div');
92
- wrapper.className = 'loading';
93
- var spinner = document.createElement('div');
94
- spinner.className = 'spinner-border text-primary';
95
- spinner.setAttribute('role', 'status');
96
- var msg = document.createElement('div');
97
- msg.textContent = message || 'Loading...';
98
- wrapper.appendChild(spinner);
99
- wrapper.appendChild(msg);
100
- el.appendChild(wrapper);
101
- }
102
-
103
- function showEmpty(containerId, icon, message) {
104
- var el = document.getElementById(containerId);
105
- if (!el) return;
106
- el.textContent = '';
107
- var wrapper = document.createElement('div');
108
- wrapper.className = 'empty-state';
109
- var iconEl = document.createElement('i');
110
- iconEl.className = 'bi bi-' + icon + ' d-block';
111
- var p = document.createElement('p');
112
- p.textContent = message;
113
- wrapper.appendChild(iconEl);
114
- wrapper.appendChild(p);
115
- el.appendChild(wrapper);
116
- }
117
-
118
- // ============================================================================
119
- // Safe HTML builder — constructs sanitized HTML strings from trusted templates
120
- // and escaped dynamic values. Used for table/card rendering where DOM-node-by-
121
- // node construction would be impractical for 50+ row tables.
122
- // ============================================================================
123
-
124
- function safeHtml(templateParts) {
125
- // Tagged template literal helper: safeHtml`<b>${userValue}</b>`
126
- // All interpolated values are auto-escaped.
127
- var args = Array.prototype.slice.call(arguments, 1);
128
- var result = '';
129
- for (var i = 0; i < templateParts.length; i++) {
130
- result += templateParts[i];
131
- if (i < args.length) {
132
- result += escapeHtml(String(args[i]));
133
- }
134
- }
135
- return result;
136
- }
137
-
138
- // ============================================================================
139
- // Stats
140
- // ============================================================================
141
-
142
- async function loadStats() {
143
- try {
144
- var response = await fetch('/api/stats');
145
- var data = await response.json();
146
- animateCounter('stat-memories', data.overview.total_memories);
147
- animateCounter('stat-clusters', data.overview.total_clusters);
148
- animateCounter('stat-nodes', data.overview.graph_nodes);
149
- animateCounter('stat-edges', data.overview.graph_edges);
150
- populateFilters(data.categories, data.projects);
151
- } catch (error) {
152
- console.error('Error loading stats:', error);
153
- }
154
- }
155
-
156
- function populateFilters(categories, projects) {
157
- var categorySelect = document.getElementById('filter-category');
158
- var projectSelect = document.getElementById('filter-project');
159
- categories.forEach(function(cat) {
160
- if (cat.category) {
161
- var option = document.createElement('option');
162
- option.value = cat.category;
163
- option.textContent = cat.category + ' (' + cat.count + ')';
164
- categorySelect.appendChild(option);
165
- }
166
- });
167
- projects.forEach(function(proj) {
168
- if (proj.project_name) {
169
- var option = document.createElement('option');
170
- option.value = proj.project_name;
171
- option.textContent = proj.project_name + ' (' + proj.count + ')';
172
- projectSelect.appendChild(option);
173
- }
174
- });
175
- }
176
-
177
- // ============================================================================
178
- // Graph
179
- // ============================================================================
180
-
181
- async function loadGraph() {
182
- var maxNodes = document.getElementById('graph-max-nodes').value;
183
- try {
184
- var response = await fetch('/api/graph?max_nodes=' + maxNodes);
185
- graphData = await response.json();
186
- renderGraph(graphData);
187
- } catch (error) {
188
- console.error('Error loading graph:', error);
189
- }
190
- }
191
-
192
- function renderGraph(data) {
193
- var container = document.getElementById('graph-container');
194
- container.textContent = '';
195
- var width = container.clientWidth || 1200;
196
- var height = 600;
197
- var svg = d3.select('#graph-container').append('svg').attr('width', width).attr('height', height);
198
- var tooltip = d3.select('body').append('div').attr('class', 'tooltip-custom').style('opacity', 0);
199
- var colorScale = d3.scaleOrdinal(d3.schemeCategory10);
200
- var simulation = d3.forceSimulation(data.nodes).force('link', d3.forceLink(data.links).id(function(d) { return d.id; }).distance(100)).force('charge', d3.forceManyBody().strength(-200)).force('center', d3.forceCenter(width / 2, height / 2)).force('collision', d3.forceCollide().radius(20));
201
- var link = svg.append('g').selectAll('line').data(data.links).enter().append('line').attr('class', 'link').attr('stroke-width', function(d) { return Math.sqrt(d.weight * 2); });
202
- var node = svg.append('g').selectAll('circle').data(data.nodes).enter().append('circle').attr('class', 'node').attr('r', function(d) { return 5 + (d.importance || 5); }).attr('fill', function(d) { return colorScale(d.cluster_id || 0); }).call(d3.drag().on('start', dragStarted).on('drag', dragged).on('end', dragEnded)).on('mouseover', function(event, d) { tooltip.transition().duration(200).style('opacity', .9); var label = d.category || d.project_name || 'Memory #' + d.id; tooltip.text(label + ': ' + (d.content_preview || d.summary || 'No content')).style('left', (event.pageX + 10) + 'px').style('top', (event.pageY - 28) + 'px'); }).on('mouseout', function() { tooltip.transition().duration(500).style('opacity', 0); }).on('click', function(event, d) { openMemoryDetail(d); });
203
- simulation.on('tick', function() { link.attr('x1', function(d) { return d.source.x; }).attr('y1', function(d) { return d.source.y; }).attr('x2', function(d) { return d.target.x; }).attr('y2', function(d) { return d.target.y; }); node.attr('cx', function(d) { return d.x; }).attr('cy', function(d) { return d.y; }); });
204
- function dragStarted(event, d) { if (!event.active) simulation.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y; }
205
- function dragged(event, d) { d.fx = event.x; d.fy = event.y; }
206
- function dragEnded(event, d) { if (!event.active) simulation.alphaTarget(0); d.fx = null; d.fy = null; }
207
- }
208
-
209
- // ============================================================================
210
- // Memories
211
- // ============================================================================
212
-
213
- async function loadMemories() {
214
- var category = document.getElementById('filter-category').value;
215
- var project = document.getElementById('filter-project').value;
216
- var url = '/api/memories?limit=50';
217
- if (category) url += '&category=' + encodeURIComponent(category);
218
- if (project) url += '&project_name=' + encodeURIComponent(project);
219
-
220
- showLoading('memories-list', 'Loading memories...');
221
- try {
222
- var response = await fetch(url);
223
- var data = await response.json();
224
- lastSearchResults = null; // Clear search cache when browsing
225
- var exportBtn = document.getElementById('export-search-btn');
226
- if (exportBtn) exportBtn.style.display = 'none';
227
- renderMemoriesTable(data.memories, false);
228
- } catch (error) {
229
- console.error('Error loading memories:', error);
230
- showEmpty('memories-list', 'exclamation-triangle', 'Failed to load memories');
231
- }
232
- }
233
-
234
- function renderMemoriesTable(memories, showScores) {
235
- var container = document.getElementById('memories-list');
236
- if (!memories || memories.length === 0) {
237
- showEmpty('memories-list', 'journal-x', 'No memories found. Try a different search or filter.');
238
- return;
239
- }
240
-
241
- // Store memories for row-click access
242
- window._slmMemories = memories;
243
-
244
- var scoreHeader = showScores ? '<th>Score</th>' : '';
245
-
246
- // All dynamic values below are escaped via escapeHtml() — safe for innerHTML.
247
- var rows = '';
248
- memories.forEach(function(mem, idx) {
249
- var content = mem.summary || mem.content || '';
250
- var contentPreview = content.length > 100 ? content.substring(0, 100) + '...' : content;
251
- var importance = mem.importance || 5;
252
- var importanceClass = importance >= 8 ? 'success' : importance >= 5 ? 'warning' : 'secondary';
253
-
254
- var scoreCell = '';
255
- if (showScores) {
256
- var score = mem.score || 0;
257
- var pct = Math.round(score * 100);
258
- var barColor = pct >= 70 ? '#43e97b' : pct >= 40 ? '#f9c74f' : '#f94144';
259
- scoreCell = '<td><span class="score-label">' + escapeHtml(String(pct)) + '%</span>'
260
- + '<div class="score-bar-container"><div class="score-bar">'
261
- + '<div class="score-bar-fill" style="width:' + pct + '%;background:' + barColor + '"></div>'
262
- + '</div></div></td>';
263
- }
264
-
265
- rows += '<tr data-mem-idx="' + idx + '">'
266
- + '<td>' + escapeHtml(String(mem.id)) + '</td>'
267
- + '<td><span class="badge bg-primary">' + escapeHtml(mem.category || 'None') + '</span></td>'
268
- + '<td><small>' + escapeHtml(mem.project_name || '-') + '</small></td>'
269
- + '<td class="memory-content" title="' + escapeHtml(content) + '">' + escapeHtml(contentPreview) + '</td>'
270
- + scoreCell
271
- + '<td><span class="badge bg-' + importanceClass + ' badge-importance">' + escapeHtml(String(importance)) + '</span></td>'
272
- + '<td>' + escapeHtml(String(mem.cluster_id || '-')) + '</td>'
273
- + '<td><small>' + escapeHtml(formatDate(mem.created_at)) + '</small></td>'
274
- + '</tr>';
275
- });
276
-
277
- var html = '<table class="table table-hover memory-table"><thead><tr>'
278
- + '<th class="sortable" data-sort="id">ID</th>'
279
- + '<th class="sortable" data-sort="category">Category</th>'
280
- + '<th class="sortable" data-sort="project">Project</th>'
281
- + '<th>Content</th>'
282
- + scoreHeader
283
- + '<th class="sortable" data-sort="importance">Importance</th>'
284
- + '<th>Cluster</th>'
285
- + '<th class="sortable" data-sort="created">Created</th>'
286
- + '</tr></thead><tbody>' + rows + '</tbody></table>';
287
-
288
- // Safe: all interpolated values above are escaped via escapeHtml()
289
- container.innerHTML = html; // nosemgrep: innerHTML-xss — all values escaped
290
-
291
- // Attach click handlers via delegation
292
- var table = container.querySelector('table');
293
- if (table) {
294
- table.addEventListener('click', function(e) {
295
- // Check if clicking a sortable header
296
- var th = e.target.closest('th.sortable');
297
- if (th) {
298
- handleSort(th);
299
- return;
300
- }
301
- var row = e.target.closest('tr[data-mem-idx]');
302
- if (row) {
303
- var idx = parseInt(row.getAttribute('data-mem-idx'), 10);
304
- if (window._slmMemories && window._slmMemories[idx]) {
305
- openMemoryDetail(window._slmMemories[idx]);
306
- }
307
- }
308
- });
309
- }
310
- }
311
-
312
- // ============================================================================
313
- // Search
314
- // ============================================================================
315
-
316
- async function searchMemories() {
317
- var query = document.getElementById('search-query').value;
318
- if (!query.trim()) { loadMemories(); return; }
319
-
320
- showLoading('memories-list', 'Searching...');
321
- try {
322
- var response = await fetch('/api/search', {
323
- method: 'POST',
324
- headers: { 'Content-Type': 'application/json' },
325
- body: JSON.stringify({ query: query, limit: 20, min_score: 0.3 })
326
- });
327
- var data = await response.json();
328
-
329
- // Sort by relevance score descending
330
- var results = data.results || [];
331
- results.sort(function(a, b) { return (b.score || 0) - (a.score || 0); });
332
-
333
- // Cache for export
334
- lastSearchResults = results;
335
-
336
- // Show export search results button
337
- var exportBtn = document.getElementById('export-search-btn');
338
- if (exportBtn) exportBtn.style.display = results.length > 0 ? '' : 'none';
339
-
340
- renderMemoriesTable(results, true);
341
- } catch (error) {
342
- console.error('Error searching:', error);
343
- showEmpty('memories-list', 'exclamation-triangle', 'Search failed. Please try again.');
344
- }
345
- }
346
-
347
- // ============================================================================
348
- // Memory Detail Modal
349
- // ============================================================================
350
-
351
- function openMemoryDetail(mem) {
352
- currentMemoryDetail = mem;
353
- var body = document.getElementById('memory-detail-body');
354
- if (!mem) {
355
- body.textContent = 'No memory data';
356
- return;
357
- }
358
-
359
- var content = mem.content || mem.summary || '(no content)';
360
- var tags = mem.tags || '';
361
- var importance = mem.importance || 5;
362
- var importanceClass = importance >= 8 ? 'success' : importance >= 5 ? 'warning' : 'secondary';
363
-
364
- var scoreBlock = '';
365
- if (typeof mem.score === 'number') {
366
- var pct = Math.round(mem.score * 100);
367
- var barColor = pct >= 70 ? '#43e97b' : pct >= 40 ? '#f9c74f' : '#f94144';
368
- scoreBlock = '<dt>Relevance Score</dt><dd><span class="score-label">'
369
- + escapeHtml(String(pct)) + '%</span>'
370
- + '<div class="score-bar-container"><div class="score-bar">'
371
- + '<div class="score-bar-fill" style="width:' + pct + '%;background:' + barColor + '"></div>'
372
- + '</div></div></dd>';
373
- }
374
-
375
- // All values escaped — safe for innerHTML
376
- var html = '<div class="memory-detail-content">' + escapeHtml(content) + '</div>'
377
- + '<hr>'
378
- + '<dl class="memory-detail-meta row">'
379
- + '<div class="col-md-6">'
380
- + '<dt>ID</dt><dd>' + escapeHtml(String(mem.id || '-')) + '</dd>'
381
- + '<dt>Category</dt><dd><span class="badge bg-primary">' + escapeHtml(mem.category || 'None') + '</span></dd>'
382
- + '<dt>Project</dt><dd>' + escapeHtml(mem.project_name || '-') + '</dd>'
383
- + '<dt>Tags</dt><dd>' + (tags ? formatTags(tags) : '<span class="text-muted">None</span>') + '</dd>'
384
- + '</div>'
385
- + '<div class="col-md-6">'
386
- + '<dt>Importance</dt><dd><span class="badge bg-' + importanceClass + '">' + escapeHtml(String(importance)) + '/10</span></dd>'
387
- + '<dt>Cluster</dt><dd>' + escapeHtml(String(mem.cluster_id || '-')) + '</dd>'
388
- + '<dt>Created</dt><dd>' + escapeHtml(formatDateFull(mem.created_at)) + '</dd>'
389
- + (mem.updated_at ? '<dt>Updated</dt><dd>' + escapeHtml(formatDateFull(mem.updated_at)) + '</dd>' : '')
390
- + scoreBlock
391
- + '</div>'
392
- + '</dl>';
393
-
394
- body.innerHTML = html; // nosemgrep: innerHTML-xss — all values escaped
395
-
396
- var modal = new bootstrap.Modal(document.getElementById('memoryDetailModal'));
397
- modal.show();
398
- }
399
-
400
- function formatTags(tags) {
401
- if (!tags) return '';
402
- var tagList = typeof tags === 'string' ? tags.split(',') : tags;
403
- return tagList.map(function(t) {
404
- var tag = t.trim();
405
- return tag ? '<span class="badge bg-secondary me-1">' + escapeHtml(tag) + '</span>' : '';
406
- }).join('');
407
- }
408
-
409
- // ============================================================================
410
- // Copy / Export from Modal
411
- // ============================================================================
412
-
413
- function copyMemoryToClipboard() {
414
- if (!currentMemoryDetail) return;
415
- var text = currentMemoryDetail.content || currentMemoryDetail.summary || '';
416
- navigator.clipboard.writeText(text).then(function() {
417
- showToast('Copied to clipboard');
418
- }).catch(function() {
419
- // Fallback for older browsers
420
- var ta = document.createElement('textarea');
421
- ta.value = text;
422
- document.body.appendChild(ta);
423
- ta.select();
424
- document.execCommand('copy');
425
- document.body.removeChild(ta);
426
- showToast('Copied to clipboard');
427
- });
428
- }
429
-
430
- function exportMemoryAsMarkdown() {
431
- if (!currentMemoryDetail) return;
432
- var mem = currentMemoryDetail;
433
- var md = '# Memory #' + (mem.id || 'unknown') + '\n\n';
434
- md += '**Category:** ' + (mem.category || 'None') + ' \n';
435
- md += '**Project:** ' + (mem.project_name || '-') + ' \n';
436
- md += '**Importance:** ' + (mem.importance || 5) + '/10 \n';
437
- md += '**Tags:** ' + (mem.tags || 'None') + ' \n';
438
- md += '**Created:** ' + (mem.created_at || '-') + ' \n';
439
- if (mem.cluster_id) md += '**Cluster:** ' + mem.cluster_id + ' \n';
440
- md += '\n---\n\n';
441
- md += mem.content || mem.summary || '(no content)';
442
- md += '\n\n---\n*Exported from SuperLocalMemory V2*\n';
443
-
444
- downloadFile('memory-' + (mem.id || 'export') + '.md', md, 'text/markdown');
445
- }
446
-
447
- // ============================================================================
448
- // Export All / Search Results
449
- // ============================================================================
450
-
451
- function exportAll(format) {
452
- // Trigger browser download from the API endpoint
453
- var url = '/api/export?format=' + encodeURIComponent(format);
454
- var category = document.getElementById('filter-category').value;
455
- var project = document.getElementById('filter-project').value;
456
- if (category) url += '&category=' + encodeURIComponent(category);
457
- if (project) url += '&project_name=' + encodeURIComponent(project);
458
- window.location.href = url;
459
- }
460
-
461
- function exportSearchResults() {
462
- if (!lastSearchResults || lastSearchResults.length === 0) {
463
- showToast('No search results to export');
464
- return;
465
- }
466
- var content = JSON.stringify({
467
- exported_at: new Date().toISOString(),
468
- query: document.getElementById('search-query').value,
469
- total: lastSearchResults.length,
470
- results: lastSearchResults
471
- }, null, 2);
472
- downloadFile('search-results-' + Date.now() + '.json', content, 'application/json');
473
- }
474
-
475
- // ============================================================================
476
- // File Download helper
477
- // ============================================================================
478
-
479
- function downloadFile(filename, content, mimeType) {
480
- var blob = new Blob([content], { type: mimeType });
481
- var url = URL.createObjectURL(blob);
482
- var a = document.createElement('a');
483
- a.href = url;
484
- a.download = filename;
485
- document.body.appendChild(a);
486
- a.click();
487
- document.body.removeChild(a);
488
- URL.revokeObjectURL(url);
489
- }
490
-
491
- // ============================================================================
492
- // Toast notification
493
- // ============================================================================
494
-
495
- function showToast(message) {
496
- var toast = document.createElement('div');
497
- toast.style.cssText = 'position:fixed;bottom:24px;right:24px;background:#333;color:#fff;padding:10px 20px;border-radius:8px;font-size:0.9rem;z-index:9999;opacity:0;transition:opacity 0.3s;';
498
- toast.textContent = message;
499
- document.body.appendChild(toast);
500
- requestAnimationFrame(function() { toast.style.opacity = '1'; });
501
- setTimeout(function() {
502
- toast.style.opacity = '0';
503
- setTimeout(function() {
504
- if (toast.parentNode) document.body.removeChild(toast);
505
- }, 300);
506
- }, 2000);
507
- }
508
-
509
- // ============================================================================
510
- // Clusters
511
- // ============================================================================
512
-
513
- async function loadClusters() {
514
- showLoading('clusters-list', 'Loading clusters...');
515
- try {
516
- var response = await fetch('/api/clusters');
517
- var data = await response.json();
518
- renderClusters(data.clusters);
519
- } catch (error) {
520
- console.error('Error loading clusters:', error);
521
- showEmpty('clusters-list', 'collection', 'Failed to load clusters');
522
- }
523
- }
524
-
525
- function renderClusters(clusters) {
526
- var container = document.getElementById('clusters-list');
527
- if (!clusters || clusters.length === 0) {
528
- showEmpty('clusters-list', 'collection', 'No clusters found. Run "slm build-graph" to generate clusters.');
529
- return;
530
- }
531
- var colors = ['#667eea', '#f093fb', '#4facfe', '#43e97b', '#fa709a'];
532
-
533
- // All dynamic values escaped — safe for innerHTML
534
- var html = '';
535
- clusters.forEach(function(cluster, idx) {
536
- var color = colors[idx % colors.length];
537
- html += '<div class="card cluster-card" style="border-color: ' + color + '">'
538
- + '<div class="card-body">'
539
- + '<h6 class="card-title">Cluster ' + escapeHtml(String(cluster.cluster_id))
540
- + ' <span class="badge bg-secondary float-end">' + escapeHtml(String(cluster.member_count)) + ' memories</span></h6>'
541
- + '<p class="mb-2"><strong>Avg Importance:</strong> ' + escapeHtml(parseFloat(cluster.avg_importance).toFixed(1)) + '</p>'
542
- + '<p class="mb-2"><strong>Categories:</strong> ' + escapeHtml(cluster.categories || 'None') + '</p>'
543
- + '<div><strong>Top Entities:</strong><br/>';
544
- if (cluster.top_entities && cluster.top_entities.length > 0) {
545
- cluster.top_entities.forEach(function(e) {
546
- html += '<span class="badge bg-info entity-badge">' + escapeHtml(e.entity) + ' (' + escapeHtml(String(e.count)) + ')</span> ';
547
- });
548
- } else {
549
- html += '<span class="text-muted">No entities</span>';
550
- }
551
- html += '</div></div></div>';
552
- });
553
- container.innerHTML = html; // nosemgrep: innerHTML-xss — all values escaped
554
- }
555
-
556
- // ============================================================================
557
- // Patterns
558
- // ============================================================================
559
-
560
- async function loadPatterns() {
561
- showLoading('patterns-list', 'Loading patterns...');
562
- try {
563
- var response = await fetch('/api/patterns');
564
- var data = await response.json();
565
- renderPatterns(data.patterns);
566
- } catch (error) {
567
- console.error('Error loading patterns:', error);
568
- showEmpty('patterns-list', 'puzzle', 'Failed to load patterns');
569
- }
570
- }
571
-
572
- function renderPatterns(patterns) {
573
- var container = document.getElementById('patterns-list');
574
- if (!patterns || Object.keys(patterns).length === 0) {
575
- showEmpty('patterns-list', 'puzzle', 'No patterns learned yet. Use SuperLocalMemory for a while to build patterns.');
576
- return;
577
- }
578
-
579
- var typeIcons = { preference: 'heart', style: 'palette', terminology: 'code-slash' };
580
- var typeLabels = { preference: 'Preferences', style: 'Coding Style', terminology: 'Terminology' };
581
-
582
- // Build using DOM for safety
583
- container.textContent = '';
584
-
585
- for (var type in patterns) {
586
- if (!patterns.hasOwnProperty(type)) continue;
587
- var items = patterns[type];
588
-
589
- var header = document.createElement('h6');
590
- header.className = 'mt-3 mb-2';
591
- var icon = document.createElement('i');
592
- icon.className = 'bi bi-' + (typeIcons[type] || 'puzzle') + ' me-1';
593
- header.appendChild(icon);
594
- header.appendChild(document.createTextNode(typeLabels[type] || type));
595
- var countBadge = document.createElement('span');
596
- countBadge.className = 'badge bg-secondary ms-2';
597
- countBadge.textContent = items.length;
598
- header.appendChild(countBadge);
599
- container.appendChild(header);
600
-
601
- var group = document.createElement('div');
602
- group.className = 'list-group mb-3';
603
-
604
- items.forEach(function(pattern) {
605
- var pct = Math.round(pattern.confidence * 100);
606
- var barColor = pct >= 60 ? '#43e97b' : pct >= 40 ? '#f9c74f' : '#6c757d';
607
- var badgeClass = pct >= 60 ? 'bg-success' : pct >= 40 ? 'bg-warning text-dark' : 'bg-secondary';
608
-
609
- var item = document.createElement('div');
610
- item.className = 'list-group-item';
611
-
612
- var topRow = document.createElement('div');
613
- topRow.className = 'd-flex justify-content-between align-items-center';
614
- var keyEl = document.createElement('strong');
615
- keyEl.textContent = pattern.key;
616
- var badge = document.createElement('span');
617
- badge.className = 'badge ' + badgeClass;
618
- badge.textContent = pct + '%';
619
- topRow.appendChild(keyEl);
620
- topRow.appendChild(badge);
621
- item.appendChild(topRow);
622
-
623
- // Confidence bar
624
- var barContainer = document.createElement('div');
625
- barContainer.className = 'confidence-bar';
626
- var barFill = document.createElement('div');
627
- barFill.className = 'confidence-fill';
628
- barFill.style.width = pct + '%';
629
- barFill.style.background = barColor;
630
- barContainer.appendChild(barFill);
631
- item.appendChild(barContainer);
632
-
633
- var valueEl = document.createElement('div');
634
- valueEl.className = 'mt-1';
635
- var valueSmall = document.createElement('small');
636
- valueSmall.className = 'text-muted';
637
- valueSmall.textContent = typeof pattern.value === 'string' ? pattern.value : JSON.stringify(pattern.value);
638
- valueEl.appendChild(valueSmall);
639
- item.appendChild(valueEl);
640
-
641
- var evidenceEl = document.createElement('small');
642
- evidenceEl.className = 'text-muted';
643
- evidenceEl.textContent = 'Evidence: ' + (pattern.evidence_count || '?') + ' memories';
644
- item.appendChild(evidenceEl);
645
-
646
- group.appendChild(item);
647
- });
648
-
649
- container.appendChild(group);
650
- }
651
- }
652
-
653
- // ============================================================================
654
- // Timeline
655
- // ============================================================================
656
-
657
- async function loadTimeline() {
658
- showLoading('timeline-chart', 'Loading timeline...');
659
- try {
660
- var response = await fetch('/api/timeline?days=30');
661
- var data = await response.json();
662
- renderTimeline(data.timeline);
663
- } catch (error) {
664
- console.error('Error loading timeline:', error);
665
- showEmpty('timeline-chart', 'clock-history', 'Failed to load timeline');
666
- }
667
- }
668
-
669
- function renderTimeline(timeline) {
670
- var container = document.getElementById('timeline-chart');
671
- if (!timeline || timeline.length === 0) {
672
- showEmpty('timeline-chart', 'clock-history', 'No timeline data for the last 30 days.');
673
- return;
674
- }
675
- var margin = { top: 20, right: 20, bottom: 50, left: 50 };
676
- var width = container.clientWidth - margin.left - margin.right;
677
- var height = 300 - margin.top - margin.bottom;
678
- container.textContent = '';
679
- var svg = d3.select('#timeline-chart').append('svg').attr('width', width + margin.left + margin.right).attr('height', height + margin.top + margin.bottom).append('g').attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
680
- var x = d3.scaleBand().range([0, width]).domain(timeline.map(function(d) { return d.date || d.period; })).padding(0.1);
681
- var y = d3.scaleLinear().range([height, 0]).domain([0, d3.max(timeline, function(d) { return d.count; })]);
682
- svg.append('g').attr('transform', 'translate(0,' + height + ')').call(d3.axisBottom(x)).selectAll('text').attr('transform', 'rotate(-45)').style('text-anchor', 'end');
683
- svg.append('g').call(d3.axisLeft(y));
684
- svg.selectAll('.bar').data(timeline).enter().append('rect').attr('class', 'bar').attr('x', function(d) { return x(d.date || d.period); }).attr('y', function(d) { return y(d.count); }).attr('width', x.bandwidth()).attr('height', function(d) { return height - y(d.count); }).attr('fill', '#667eea').attr('rx', 3);
685
- }
686
-
687
- // ============================================================================
688
- // Date Formatters
689
- // ============================================================================
690
-
691
- function formatDate(dateString) {
692
- if (!dateString) return '-';
693
- var date = new Date(dateString);
694
- return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
695
- }
696
-
697
- function formatDateFull(dateString) {
698
- if (!dateString) return '-';
699
- var date = new Date(dateString);
700
- return date.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' });
701
- }
702
-
703
- // ============================================================================
704
- // Event Listeners
705
- // ============================================================================
706
-
707
- document.getElementById('memories-tab').addEventListener('shown.bs.tab', loadMemories);
708
- document.getElementById('clusters-tab').addEventListener('shown.bs.tab', loadClusters);
709
- document.getElementById('patterns-tab').addEventListener('shown.bs.tab', loadPatterns);
710
- document.getElementById('timeline-tab').addEventListener('shown.bs.tab', loadTimeline);
711
- document.getElementById('settings-tab').addEventListener('shown.bs.tab', loadSettings);
712
- document.getElementById('search-query').addEventListener('keypress', function(e) { if (e.key === 'Enter') searchMemories(); });
713
-
714
- document.getElementById('profile-select').addEventListener('change', function() {
715
- switchProfile(this.value);
716
- });
717
-
718
- document.getElementById('add-profile-btn').addEventListener('click', function() {
719
- createProfile();
720
- });
721
-
722
- var newProfileInput = document.getElementById('new-profile-name');
723
- if (newProfileInput) {
724
- newProfileInput.addEventListener('keypress', function(e) {
725
- if (e.key === 'Enter') createProfile();
726
- });
727
- }
728
-
729
- window.addEventListener('DOMContentLoaded', function() {
730
- initDarkMode();
731
- loadProfiles();
732
- loadStats();
733
- loadGraph();
734
-
735
- // v2.5 — Event Bus + Agent Registry
736
- initEventStream();
737
- loadEventStats();
738
- loadAgents();
739
- });
740
-
741
- // ============================================================================
742
- // Profile Management
743
- // ============================================================================
744
-
745
- async function loadProfiles() {
746
- try {
747
- var response = await fetch('/api/profiles');
748
- var data = await response.json();
749
- var select = document.getElementById('profile-select');
750
- select.textContent = '';
751
- var profiles = data.profiles || [];
752
- var active = data.active_profile || 'default';
753
-
754
- profiles.forEach(function(p) {
755
- var opt = document.createElement('option');
756
- opt.value = p.name;
757
- opt.textContent = p.name + (p.memory_count ? ' (' + p.memory_count + ')' : '');
758
- if (p.name === active) opt.selected = true;
759
- select.appendChild(opt);
760
- });
761
- } catch (error) {
762
- console.error('Error loading profiles:', error);
763
- }
764
- }
765
-
766
- async function createProfile(nameOverride) {
767
- var name = nameOverride || document.getElementById('new-profile-name').value.trim();
768
- if (!name) {
769
- // Prompt with a simple browser dialog if called from the "+" button
770
- name = prompt('Enter new profile name:');
771
- if (!name || !name.trim()) return;
772
- name = name.trim();
773
- }
774
-
775
- // Validate: alphanumeric, dashes, underscores only
776
- if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
777
- showToast('Invalid name. Use letters, numbers, dashes, underscores.');
778
- return;
779
- }
780
-
781
- try {
782
- var response = await fetch('/api/profiles/create', {
783
- method: 'POST',
784
- headers: { 'Content-Type': 'application/json' },
785
- body: JSON.stringify({ profile_name: name })
786
- });
787
- var data = await response.json();
788
- if (response.status === 409) {
789
- showToast('Profile "' + name + '" already exists');
790
- return;
791
- }
792
- if (!response.ok) {
793
- showToast(data.detail || 'Failed to create profile');
794
- return;
795
- }
796
- showToast('Profile "' + name + '" created');
797
- var input = document.getElementById('new-profile-name');
798
- if (input) input.value = '';
799
- loadProfiles();
800
- loadProfilesTable();
801
- } catch (error) {
802
- console.error('Error creating profile:', error);
803
- showToast('Error creating profile');
804
- }
805
- }
806
-
807
- async function deleteProfile(name) {
808
- if (name === 'default') {
809
- showToast('Cannot delete the default profile');
810
- return;
811
- }
812
- if (!confirm('Delete profile "' + name + '"?\nIts memories will be moved to the default profile.')) {
813
- return;
814
- }
815
- try {
816
- var response = await fetch('/api/profiles/' + encodeURIComponent(name), {
817
- method: 'DELETE'
818
- });
819
- var data = await response.json();
820
- if (!response.ok) {
821
- showToast(data.detail || 'Failed to delete profile');
822
- return;
823
- }
824
- showToast(data.message || 'Profile deleted');
825
- loadProfiles();
826
- loadProfilesTable();
827
- loadStats();
828
- } catch (error) {
829
- console.error('Error deleting profile:', error);
830
- showToast('Error deleting profile');
831
- }
832
- }
833
-
834
- async function loadProfilesTable() {
835
- var container = document.getElementById('profiles-table');
836
- if (!container) return;
837
- try {
838
- var response = await fetch('/api/profiles');
839
- var data = await response.json();
840
- var profiles = data.profiles || [];
841
- var active = data.active_profile || 'default';
842
-
843
- if (profiles.length === 0) {
844
- showEmpty('profiles-table', 'people', 'No profiles found.');
845
- return;
846
- }
847
-
848
- var table = document.createElement('table');
849
- table.className = 'table table-sm mb-0';
850
- var thead = document.createElement('thead');
851
- var headRow = document.createElement('tr');
852
- ['Name', 'Memories', 'Status', 'Actions'].forEach(function(h) {
853
- var th = document.createElement('th');
854
- th.textContent = h;
855
- headRow.appendChild(th);
856
- });
857
- thead.appendChild(headRow);
858
- table.appendChild(thead);
859
-
860
- var tbody = document.createElement('tbody');
861
- profiles.forEach(function(p) {
862
- var row = document.createElement('tr');
863
-
864
- var nameCell = document.createElement('td');
865
- var nameIcon = document.createElement('i');
866
- nameIcon.className = 'bi bi-person me-1';
867
- nameCell.appendChild(nameIcon);
868
- nameCell.appendChild(document.createTextNode(p.name));
869
- row.appendChild(nameCell);
870
-
871
- var countCell = document.createElement('td');
872
- countCell.textContent = (p.memory_count || 0) + ' memories';
873
- row.appendChild(countCell);
874
-
875
- var statusCell = document.createElement('td');
876
- if (p.name === active) {
877
- var badge = document.createElement('span');
878
- badge.className = 'badge bg-success';
879
- badge.textContent = 'Active';
880
- statusCell.appendChild(badge);
881
- } else {
882
- var switchBtn = document.createElement('button');
883
- switchBtn.className = 'btn btn-sm btn-outline-primary';
884
- switchBtn.textContent = 'Switch';
885
- switchBtn.addEventListener('click', (function(n) {
886
- return function() { switchProfile(n); };
887
- })(p.name));
888
- statusCell.appendChild(switchBtn);
889
- }
890
- row.appendChild(statusCell);
891
-
892
- var actionsCell = document.createElement('td');
893
- if (p.name !== 'default') {
894
- var delBtn = document.createElement('button');
895
- delBtn.className = 'btn btn-sm btn-outline-danger btn-delete-profile';
896
- delBtn.title = 'Delete profile';
897
- var delIcon = document.createElement('i');
898
- delIcon.className = 'bi bi-trash';
899
- delBtn.appendChild(delIcon);
900
- delBtn.addEventListener('click', (function(n) {
901
- return function() { deleteProfile(n); };
902
- })(p.name));
903
- actionsCell.appendChild(delBtn);
904
- } else {
905
- var protectedBadge = document.createElement('span');
906
- protectedBadge.className = 'badge bg-secondary';
907
- protectedBadge.textContent = 'Protected';
908
- actionsCell.appendChild(protectedBadge);
909
- }
910
- row.appendChild(actionsCell);
911
-
912
- tbody.appendChild(row);
913
- });
914
- table.appendChild(tbody);
915
-
916
- container.textContent = '';
917
- container.appendChild(table);
918
- } catch (error) {
919
- console.error('Error loading profiles table:', error);
920
- showEmpty('profiles-table', 'exclamation-triangle', 'Failed to load profiles');
921
- }
922
- }
923
-
924
- async function switchProfile(profileName) {
925
- try {
926
- var response = await fetch('/api/profiles/' + encodeURIComponent(profileName) + '/switch', {
927
- method: 'POST'
928
- });
929
- var data = await response.json();
930
- if (data.success || data.active_profile) {
931
- showToast('Switched to profile: ' + profileName);
932
- loadProfiles();
933
- loadStats();
934
- loadGraph();
935
- loadProfilesTable();
936
- var activeTab = document.querySelector('#mainTabs .nav-link.active');
937
- if (activeTab) activeTab.click();
938
- } else {
939
- showToast('Failed to switch profile');
940
- }
941
- } catch (error) {
942
- console.error('Error switching profile:', error);
943
- showToast('Error switching profile');
944
- }
945
- }
946
-
947
- // ============================================================================
948
- // Settings & Backup
949
- // ============================================================================
950
-
951
- async function loadSettings() {
952
- loadProfilesTable();
953
- loadBackupStatus();
954
- loadBackupList();
955
- }
956
-
957
- async function loadBackupStatus() {
958
- try {
959
- var response = await fetch('/api/backup/status');
960
- var data = await response.json();
961
- renderBackupStatus(data);
962
- document.getElementById('backup-interval').value = data.interval_hours <= 24 ? '24' : '168';
963
- document.getElementById('backup-max').value = data.max_backups || 10;
964
- document.getElementById('backup-enabled').checked = data.enabled !== false;
965
- } catch (error) {
966
- var container = document.getElementById('backup-status');
967
- var alert = document.createElement('div');
968
- alert.className = 'alert alert-warning mb-0';
969
- alert.textContent = 'Auto-backup not available. Update to v2.4.0+.';
970
- container.textContent = '';
971
- container.appendChild(alert);
972
- }
973
- }
974
-
975
- function renderBackupStatus(data) {
976
- var container = document.getElementById('backup-status');
977
- container.textContent = '';
978
-
979
- var lastBackup = data.last_backup ? formatDateFull(data.last_backup) : 'Never';
980
- var nextBackup = data.next_backup || 'N/A';
981
- if (nextBackup === 'overdue') nextBackup = 'Overdue';
982
- else if (nextBackup !== 'N/A' && nextBackup !== 'unknown') nextBackup = formatDateFull(nextBackup);
983
-
984
- var statusColor = data.enabled ? 'text-success' : 'text-secondary';
985
- var statusText = data.enabled ? 'Active' : 'Disabled';
986
-
987
- // Build DOM nodes for safety
988
- var row = document.createElement('div');
989
- row.className = 'row g-2 mb-2';
990
-
991
- var stats = [
992
- { value: statusText, label: 'Status', cls: statusColor },
993
- { value: String(data.backup_count || 0), label: 'Backups', cls: '' },
994
- { value: (data.total_size_mb || 0) + ' MB', label: 'Storage', cls: '' }
995
- ];
996
-
997
- stats.forEach(function(s) {
998
- var col = document.createElement('div');
999
- col.className = 'col-4';
1000
- var stat = document.createElement('div');
1001
- stat.className = 'backup-stat';
1002
- var val = document.createElement('div');
1003
- val.className = 'value ' + s.cls;
1004
- val.textContent = s.value;
1005
- var lbl = document.createElement('div');
1006
- lbl.className = 'label';
1007
- lbl.textContent = s.label;
1008
- stat.appendChild(val);
1009
- stat.appendChild(lbl);
1010
- col.appendChild(stat);
1011
- row.appendChild(col);
1012
- });
1013
- container.appendChild(row);
1014
-
1015
- var details = [
1016
- { label: 'Last backup:', value: lastBackup },
1017
- { label: 'Next backup:', value: nextBackup },
1018
- { label: 'Interval:', value: data.interval_display || '-' }
1019
- ];
1020
- details.forEach(function(d) {
1021
- var div = document.createElement('div');
1022
- div.className = 'small text-muted';
1023
- var strong = document.createElement('strong');
1024
- strong.textContent = d.label + ' ';
1025
- div.appendChild(strong);
1026
- div.appendChild(document.createTextNode(d.value));
1027
- container.appendChild(div);
1028
- });
1029
- }
1030
-
1031
- async function saveBackupConfig() {
1032
- try {
1033
- var response = await fetch('/api/backup/configure', {
1034
- method: 'POST',
1035
- headers: { 'Content-Type': 'application/json' },
1036
- body: JSON.stringify({
1037
- interval_hours: parseInt(document.getElementById('backup-interval').value),
1038
- max_backups: parseInt(document.getElementById('backup-max').value),
1039
- enabled: document.getElementById('backup-enabled').checked
1040
- })
1041
- });
1042
- var data = await response.json();
1043
- renderBackupStatus(data);
1044
- showToast('Backup settings saved');
1045
- } catch (error) {
1046
- console.error('Error saving backup config:', error);
1047
- showToast('Failed to save backup settings');
1048
- }
1049
- }
1050
-
1051
- async function createBackupNow() {
1052
- showToast('Creating backup...');
1053
- try {
1054
- var response = await fetch('/api/backup/create', { method: 'POST' });
1055
- var data = await response.json();
1056
- if (data.success) {
1057
- showToast('Backup created: ' + data.filename);
1058
- loadBackupStatus();
1059
- loadBackupList();
1060
- } else {
1061
- showToast('Backup failed');
1062
- }
1063
- } catch (error) {
1064
- console.error('Error creating backup:', error);
1065
- showToast('Backup failed');
1066
- }
1067
- }
1068
-
1069
- async function loadBackupList() {
1070
- try {
1071
- var response = await fetch('/api/backup/list');
1072
- var data = await response.json();
1073
- renderBackupList(data.backups || []);
1074
- } catch (error) {
1075
- var container = document.getElementById('backup-list');
1076
- container.textContent = 'Backup list unavailable';
1077
- }
1078
- }
1079
-
1080
- function renderBackupList(backups) {
1081
- var container = document.getElementById('backup-list');
1082
- if (!backups || backups.length === 0) {
1083
- showEmpty('backup-list', 'archive', 'No backups yet. Create your first backup above.');
1084
- return;
1085
- }
1086
-
1087
- // Build table using DOM nodes
1088
- var table = document.createElement('table');
1089
- table.className = 'table table-sm';
1090
- var thead = document.createElement('thead');
1091
- var headRow = document.createElement('tr');
1092
- ['Filename', 'Size', 'Age', 'Created'].forEach(function(h) {
1093
- var th = document.createElement('th');
1094
- th.textContent = h;
1095
- headRow.appendChild(th);
1096
- });
1097
- thead.appendChild(headRow);
1098
- table.appendChild(thead);
1099
-
1100
- var tbody = document.createElement('tbody');
1101
- backups.forEach(function(b) {
1102
- var row = document.createElement('tr');
1103
- var age = b.age_hours < 48 ? Math.round(b.age_hours) + 'h ago' : Math.round(b.age_hours / 24) + 'd ago';
1104
- var cells = [b.filename, b.size_mb + ' MB', age, formatDateFull(b.created)];
1105
- cells.forEach(function(text) {
1106
- var td = document.createElement('td');
1107
- td.textContent = text;
1108
- row.appendChild(td);
1109
- });
1110
- tbody.appendChild(row);
1111
- });
1112
- table.appendChild(tbody);
1113
-
1114
- container.textContent = '';
1115
- container.appendChild(table);
1116
- }
1117
-
1118
- // ============================================================================
1119
- // Column Sorting
1120
- // ============================================================================
1121
-
1122
- var currentSort = { column: null, direction: 'asc' };
1123
-
1124
- function handleSort(th) {
1125
- var col = th.getAttribute('data-sort');
1126
- if (!col) return;
1127
-
1128
- if (currentSort.column === col) {
1129
- currentSort.direction = currentSort.direction === 'asc' ? 'desc' : 'asc';
1130
- } else {
1131
- currentSort.column = col;
1132
- currentSort.direction = 'asc';
1133
- }
1134
-
1135
- // Update header classes
1136
- document.querySelectorAll('#memories-list th.sortable').forEach(function(h) {
1137
- h.classList.remove('sort-asc', 'sort-desc');
1138
- });
1139
- th.classList.add('sort-' + currentSort.direction);
1140
-
1141
- // Sort the data
1142
- if (!window._slmMemories) return;
1143
- var memories = window._slmMemories.slice();
1144
- var dir = currentSort.direction === 'asc' ? 1 : -1;
1145
-
1146
- memories.sort(function(a, b) {
1147
- var av, bv;
1148
- switch (col) {
1149
- case 'id': return ((a.id || 0) - (b.id || 0)) * dir;
1150
- case 'importance': return ((a.importance || 0) - (b.importance || 0)) * dir;
1151
- case 'category':
1152
- av = (a.category || '').toLowerCase(); bv = (b.category || '').toLowerCase();
1153
- return av < bv ? -dir : av > bv ? dir : 0;
1154
- case 'project':
1155
- av = (a.project_name || '').toLowerCase(); bv = (b.project_name || '').toLowerCase();
1156
- return av < bv ? -dir : av > bv ? dir : 0;
1157
- case 'created':
1158
- av = a.created_at || ''; bv = b.created_at || '';
1159
- return av < bv ? -dir : av > bv ? dir : 0;
1160
- case 'score': return ((a.score || 0) - (b.score || 0)) * dir;
1161
- default: return 0;
1162
- }
1163
- });
1164
-
1165
- window._slmMemories = memories;
1166
- var showScores = memories.length > 0 && typeof memories[0].score === 'number';
1167
- renderMemoriesTable(memories, showScores);
1168
- }
1169
-
1170
- // ============================================================================
1171
- // v2.5 — Live Event Stream (SSE)
1172
- // ============================================================================
1173
- // Security note: All dynamic values are escaped via escapeHtml() before DOM insertion.
1174
- // Data originates from our own trusted local SQLite database (localhost only).
1175
- // No external/untrusted user input reaches the DOM — same pattern as existing code above.
1176
-
1177
- var _eventSource = null;
1178
- var _eventStreamItems = [];
1179
- var _maxEventStreamItems = 200;
1180
-
1181
- function initEventStream() {
1182
- try {
1183
- _eventSource = new EventSource('/events/stream');
1184
-
1185
- _eventSource.onopen = function() {
1186
- var badge = document.getElementById('event-connection-status');
1187
- if (badge) {
1188
- badge.textContent = 'Connected';
1189
- badge.className = 'badge bg-success me-2';
1190
- }
1191
- };
1192
-
1193
- _eventSource.onmessage = function(e) {
1194
- try {
1195
- var event = JSON.parse(e.data);
1196
- appendEventToStream(event);
1197
- } catch (err) {
1198
- // Ignore parse errors (keepalive comments)
1199
- }
1200
- };
1201
-
1202
- _eventSource.onerror = function() {
1203
- var badge = document.getElementById('event-connection-status');
1204
- if (badge) {
1205
- badge.textContent = 'Reconnecting...';
1206
- badge.className = 'badge bg-warning me-2';
1207
- }
1208
- };
1209
-
1210
- ['memory.created', 'memory.updated', 'memory.deleted', 'memory.recalled',
1211
- 'agent.connected', 'agent.disconnected', 'graph.updated', 'pattern.learned'
1212
- ].forEach(function(type) {
1213
- _eventSource.addEventListener(type, function(e) {
1214
- try {
1215
- appendEventToStream(JSON.parse(e.data));
1216
- } catch (err) { /* ignore */ }
1217
- });
1218
- });
1219
- } catch (err) {
1220
- console.log('SSE not available:', err);
1221
- var badge = document.getElementById('event-connection-status');
1222
- if (badge) {
1223
- badge.textContent = 'Unavailable';
1224
- badge.className = 'badge bg-secondary me-2';
1225
- }
1226
- }
1227
- }
1228
-
1229
- function appendEventToStream(event) {
1230
- var container = document.getElementById('event-stream');
1231
- if (!container) return;
1232
-
1233
- if (_eventStreamItems.length === 0) {
1234
- container.textContent = '';
1235
- }
1236
-
1237
- _eventStreamItems.push(event);
1238
- if (_eventStreamItems.length > _maxEventStreamItems) {
1239
- _eventStreamItems.shift();
1240
- }
1241
-
1242
- var filter = document.getElementById('event-type-filter');
1243
- var filterValue = filter ? filter.value : '';
1244
- if (filterValue && event.event_type !== filterValue) return;
1245
-
1246
- var typeColors = {
1247
- 'memory.created': 'text-success', 'memory.updated': 'text-info',
1248
- 'memory.deleted': 'text-danger', 'memory.recalled': 'text-primary',
1249
- 'agent.connected': 'text-warning', 'agent.disconnected': 'text-secondary',
1250
- 'graph.updated': 'text-info', 'pattern.learned': 'text-success'
1251
- };
1252
- var typeIcons = {
1253
- 'memory.created': 'bi-plus-circle', 'memory.updated': 'bi-pencil',
1254
- 'memory.deleted': 'bi-trash', 'memory.recalled': 'bi-search',
1255
- 'agent.connected': 'bi-plug', 'agent.disconnected': 'bi-plug',
1256
- 'graph.updated': 'bi-diagram-3', 'pattern.learned': 'bi-lightbulb'
1257
- };
1258
-
1259
- var colorClass = typeColors[event.event_type] || 'text-muted';
1260
- var iconClass = typeIcons[event.event_type] || 'bi-circle';
1261
- var ts = event.timestamp ? new Date(event.timestamp).toLocaleTimeString() : '';
1262
- var payload = event.payload || {};
1263
- var preview = payload.content_preview || payload.agent_id || payload.agent_name || '';
1264
- if (preview.length > 80) preview = preview.substring(0, 80) + '...';
1265
-
1266
- // Build event line using safe DOM methods + escapeHtml for all dynamic content
1267
- var div = document.createElement('div');
1268
- div.className = 'event-line mb-1 pb-1 border-bottom border-opacity-25';
1269
-
1270
- var timeSpan = document.createElement('small');
1271
- timeSpan.className = 'text-muted';
1272
- timeSpan.textContent = ts;
1273
-
1274
- var icon = document.createElement('i');
1275
- icon.className = 'bi ' + iconClass + ' ' + colorClass;
1276
- icon.style.marginLeft = '6px';
1277
-
1278
- var typeSpan = document.createElement('span');
1279
- typeSpan.className = colorClass + ' fw-bold';
1280
- typeSpan.style.marginLeft = '4px';
1281
- typeSpan.textContent = event.event_type;
1282
-
1283
- div.appendChild(timeSpan);
1284
- div.appendChild(document.createTextNode(' '));
1285
- div.appendChild(icon);
1286
- div.appendChild(document.createTextNode(' '));
1287
- div.appendChild(typeSpan);
1288
- div.appendChild(document.createTextNode(' '));
1289
-
1290
- if (event.memory_id) {
1291
- var badge = document.createElement('span');
1292
- badge.className = 'badge bg-secondary';
1293
- badge.textContent = '#' + event.memory_id;
1294
- div.appendChild(badge);
1295
- div.appendChild(document.createTextNode(' '));
1296
- }
1297
-
1298
- var previewSpan = document.createElement('span');
1299
- previewSpan.className = 'text-muted';
1300
- previewSpan.textContent = preview;
1301
- div.appendChild(previewSpan);
1302
-
1303
- container.insertBefore(div, container.firstChild);
1304
-
1305
- while (container.children.length > _maxEventStreamItems) {
1306
- container.removeChild(container.lastChild);
1307
- }
1308
- }
1309
-
1310
- function filterEvents() {
1311
- var container = document.getElementById('event-stream');
1312
- if (!container) return;
1313
- container.textContent = '';
1314
-
1315
- var filter = document.getElementById('event-type-filter');
1316
- var filterValue = filter ? filter.value : '';
1317
-
1318
- var filtered = filterValue
1319
- ? _eventStreamItems.filter(function(e) { return e.event_type === filterValue; })
1320
- : _eventStreamItems;
1321
-
1322
- filtered.forEach(function(event) {
1323
- appendEventToStream(event);
1324
- });
1325
- }
1326
-
1327
- function clearEventStream() {
1328
- _eventStreamItems = [];
1329
- var container = document.getElementById('event-stream');
1330
- if (container) {
1331
- container.textContent = '';
1332
- var placeholder = document.createElement('div');
1333
- placeholder.className = 'text-muted text-center py-4';
1334
- var pIcon = document.createElement('i');
1335
- pIcon.className = 'bi bi-broadcast';
1336
- pIcon.style.fontSize = '2rem';
1337
- placeholder.appendChild(pIcon);
1338
- var pText = document.createElement('p');
1339
- pText.className = 'mt-2';
1340
- pText.textContent = 'Event stream cleared. Waiting for new events...';
1341
- placeholder.appendChild(pText);
1342
- container.appendChild(placeholder);
1343
- }
1344
- }
1345
-
1346
- async function loadEventStats() {
1347
- try {
1348
- var response = await fetch('/api/events/stats');
1349
- var stats = await response.json();
1350
- var el;
1351
- el = document.getElementById('event-stat-total');
1352
- if (el) el.textContent = (stats.total_events || 0).toLocaleString();
1353
- el = document.getElementById('event-stat-24h');
1354
- if (el) el.textContent = (stats.events_last_24h || 0).toLocaleString();
1355
- el = document.getElementById('event-stat-listeners');
1356
- if (el) el.textContent = (stats.listener_count || 0).toLocaleString();
1357
- el = document.getElementById('event-stat-buffer');
1358
- if (el) el.textContent = (stats.buffer_size || 0).toLocaleString();
1359
- } catch (err) {
1360
- console.log('Event stats not available:', err);
1361
- }
1362
- }
1363
-
1364
- // ============================================================================
1365
- // v2.5 — Connected Agents
1366
- // ============================================================================
1367
-
1368
- async function loadAgents() {
1369
- try {
1370
- var response = await fetch('/api/agents');
1371
- var data = await response.json();
1372
- var agents = data.agents || [];
1373
- var stats = data.stats || {};
1374
-
1375
- var el;
1376
- el = document.getElementById('agent-stat-total');
1377
- if (el) el.textContent = (stats.total_agents || 0).toLocaleString();
1378
- el = document.getElementById('agent-stat-active');
1379
- if (el) el.textContent = (stats.active_last_24h || 0).toLocaleString();
1380
- el = document.getElementById('agent-stat-writes');
1381
- if (el) el.textContent = (stats.total_writes || 0).toLocaleString();
1382
- el = document.getElementById('agent-stat-recalls');
1383
- if (el) el.textContent = (stats.total_recalls || 0).toLocaleString();
1384
-
1385
- var container = document.getElementById('agents-list');
1386
- if (!container) return;
1387
-
1388
- if (agents.length === 0) {
1389
- container.textContent = '';
1390
- var empty = document.createElement('div');
1391
- empty.className = 'text-muted text-center py-4';
1392
- var emptyIcon = document.createElement('i');
1393
- emptyIcon.className = 'bi bi-robot';
1394
- emptyIcon.style.fontSize = '2rem';
1395
- empty.appendChild(emptyIcon);
1396
- var emptyText = document.createElement('p');
1397
- emptyText.className = 'mt-2';
1398
- emptyText.textContent = 'No agents registered yet. Agents appear automatically when they connect via MCP, CLI, or REST.';
1399
- empty.appendChild(emptyText);
1400
- container.appendChild(empty);
1401
- loadTrustOverview();
1402
- return;
1403
- }
1404
-
1405
- // Build agent table using safe DOM methods
1406
- var table = document.createElement('table');
1407
- table.className = 'table table-hover table-sm';
1408
- var thead = document.createElement('thead');
1409
- var headerRow = document.createElement('tr');
1410
- ['Agent', 'Protocol', 'Trust', 'Writes', 'Recalls', 'Last Seen'].forEach(function(h) {
1411
- var th = document.createElement('th');
1412
- th.textContent = h;
1413
- headerRow.appendChild(th);
1414
- });
1415
- thead.appendChild(headerRow);
1416
- table.appendChild(thead);
1417
-
1418
- var tbody = document.createElement('tbody');
1419
- agents.forEach(function(agent) {
1420
- var tr = document.createElement('tr');
1421
-
1422
- // Agent name cell
1423
- var tdName = document.createElement('td');
1424
- var strong = document.createElement('strong');
1425
- strong.textContent = agent.agent_name || agent.agent_id;
1426
- tdName.appendChild(strong);
1427
- tdName.appendChild(document.createElement('br'));
1428
- var smallId = document.createElement('small');
1429
- smallId.className = 'text-muted';
1430
- smallId.textContent = agent.agent_id;
1431
- tdName.appendChild(smallId);
1432
- tr.appendChild(tdName);
1433
-
1434
- // Protocol badge
1435
- var tdProto = document.createElement('td');
1436
- var protoBadge = document.createElement('span');
1437
- var protocolColors = {
1438
- 'mcp': 'bg-primary', 'cli': 'bg-success', 'rest': 'bg-info',
1439
- 'python': 'bg-secondary'
1440
- };
1441
- protoBadge.className = 'badge ' + (protocolColors[agent.protocol] || 'bg-secondary');
1442
- protoBadge.textContent = agent.protocol;
1443
- tdProto.appendChild(protoBadge);
1444
- tr.appendChild(tdProto);
1445
-
1446
- // Trust score
1447
- var tdTrust = document.createElement('td');
1448
- var trustScore = agent.trust_score != null ? agent.trust_score : 0.667;
1449
- tdTrust.className = trustScore < 0.3 ? 'text-danger fw-bold'
1450
- : trustScore < 0.5 ? 'text-warning fw-bold' : 'text-success fw-bold';
1451
- tdTrust.textContent = trustScore.toFixed(2);
1452
- tr.appendChild(tdTrust);
1453
-
1454
- // Writes
1455
- var tdW = document.createElement('td');
1456
- tdW.textContent = agent.memories_written || 0;
1457
- tr.appendChild(tdW);
1458
-
1459
- // Recalls
1460
- var tdR = document.createElement('td');
1461
- tdR.textContent = agent.memories_recalled || 0;
1462
- tr.appendChild(tdR);
1463
-
1464
- // Last seen
1465
- var tdLast = document.createElement('td');
1466
- var lastSmall = document.createElement('small');
1467
- lastSmall.textContent = agent.last_seen ? new Date(agent.last_seen).toLocaleString() : 'Never';
1468
- tdLast.appendChild(lastSmall);
1469
- tr.appendChild(tdLast);
1470
-
1471
- tbody.appendChild(tr);
1472
- });
1473
- table.appendChild(tbody);
1474
-
1475
- container.textContent = '';
1476
- container.appendChild(table);
1477
-
1478
- loadTrustOverview();
1479
-
1480
- } catch (err) {
1481
- console.log('Agents not available:', err);
1482
- var container = document.getElementById('agents-list');
1483
- if (container) {
1484
- container.textContent = '';
1485
- var msg = document.createElement('small');
1486
- msg.className = 'text-muted';
1487
- msg.textContent = 'Agent registry not available. This feature requires v2.5+.';
1488
- container.appendChild(msg);
1489
- }
1490
- }
1491
- }
1492
-
1493
- async function loadTrustOverview() {
1494
- try {
1495
- var response = await fetch('/api/trust/stats');
1496
- var stats = await response.json();
1497
- var container = document.getElementById('trust-overview');
1498
- if (!container) return;
1499
-
1500
- container.textContent = '';
1501
- var row = document.createElement('div');
1502
- row.className = 'row g-3';
1503
-
1504
- // Total signals card
1505
- var col1 = document.createElement('div');
1506
- col1.className = 'col-md-4';
1507
- var card1 = document.createElement('div');
1508
- card1.className = 'border rounded p-3 text-center';
1509
- var val1 = document.createElement('div');
1510
- val1.className = 'fs-4 fw-bold';
1511
- val1.textContent = (stats.total_signals || 0).toLocaleString();
1512
- card1.appendChild(val1);
1513
- var lbl1 = document.createElement('small');
1514
- lbl1.className = 'text-muted';
1515
- lbl1.textContent = 'Total Signals Collected';
1516
- card1.appendChild(lbl1);
1517
- col1.appendChild(card1);
1518
- row.appendChild(col1);
1519
-
1520
- // Avg trust card
1521
- var col2 = document.createElement('div');
1522
- col2.className = 'col-md-4';
1523
- var card2 = document.createElement('div');
1524
- card2.className = 'border rounded p-3 text-center';
1525
- var val2 = document.createElement('div');
1526
- val2.className = 'fs-4 fw-bold';
1527
- val2.textContent = (stats.avg_trust_score || 0.667).toFixed(3);
1528
- card2.appendChild(val2);
1529
- var lbl2 = document.createElement('small');
1530
- lbl2.className = 'text-muted';
1531
- lbl2.textContent = 'Average Trust Score';
1532
- card2.appendChild(lbl2);
1533
- col2.appendChild(card2);
1534
- row.appendChild(col2);
1535
-
1536
- // Enforcement card
1537
- var col3 = document.createElement('div');
1538
- col3.className = 'col-md-4';
1539
- var card3 = document.createElement('div');
1540
- card3.className = 'border rounded p-3 text-center';
1541
- var val3 = document.createElement('div');
1542
- val3.className = 'fs-4 fw-bold text-info';
1543
- val3.textContent = stats.enforcement || 'disabled';
1544
- card3.appendChild(val3);
1545
- var lbl3 = document.createElement('small');
1546
- lbl3.className = 'text-muted';
1547
- lbl3.textContent = 'Enforcement Status';
1548
- card3.appendChild(lbl3);
1549
- col3.appendChild(card3);
1550
- row.appendChild(col3);
1551
-
1552
- container.appendChild(row);
1553
-
1554
- // Signal breakdown
1555
- if (stats.by_signal_type && Object.keys(stats.by_signal_type).length > 0) {
1556
- var breakdownDiv = document.createElement('div');
1557
- breakdownDiv.className = 'col-12 mt-3';
1558
- var h6 = document.createElement('h6');
1559
- h6.textContent = 'Signal Breakdown';
1560
- breakdownDiv.appendChild(h6);
1561
- var badgeWrap = document.createElement('div');
1562
- badgeWrap.className = 'd-flex flex-wrap gap-2';
1563
- Object.keys(stats.by_signal_type).forEach(function(type) {
1564
- var count = stats.by_signal_type[type];
1565
- var signalClass = (type.indexOf('high_volume') >= 0 || type.indexOf('quick_delete') >= 0)
1566
- ? 'bg-danger' : (type.indexOf('recalled') >= 0 || type.indexOf('high_importance') >= 0)
1567
- ? 'bg-success' : 'bg-secondary';
1568
- var b = document.createElement('span');
1569
- b.className = 'badge ' + signalClass;
1570
- b.textContent = type + ': ' + count;
1571
- badgeWrap.appendChild(b);
1572
- });
1573
- breakdownDiv.appendChild(badgeWrap);
1574
- container.appendChild(breakdownDiv);
1575
- }
1576
-
1577
- } catch (err) {
1578
- console.log('Trust stats not available:', err);
1579
- var container = document.getElementById('trust-overview');
1580
- if (container) {
1581
- container.textContent = '';
1582
- var msg = document.createElement('small');
1583
- msg.className = 'text-muted';
1584
- msg.textContent = 'Trust scoring data will appear here once agents interact with memory.';
1585
- container.appendChild(msg);
1586
- }
1587
- }
1588
- }