superlocalmemory 2.8.5 → 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.
- package/CHANGELOG.md +11 -0
- package/LICENSE +9 -1
- package/NOTICE +63 -0
- package/README.md +165 -480
- package/bin/slm +17 -449
- package/bin/slm-npm +2 -2
- package/bin/slm.bat +4 -2
- package/conftest.py +5 -0
- package/docs/api-reference.md +284 -0
- package/docs/architecture.md +149 -0
- package/docs/auto-memory.md +150 -0
- package/docs/cli-reference.md +276 -0
- package/docs/compliance.md +191 -0
- package/docs/configuration.md +182 -0
- package/docs/getting-started.md +102 -0
- package/docs/ide-setup.md +261 -0
- package/docs/mcp-tools.md +220 -0
- package/docs/migration-from-v2.md +170 -0
- package/docs/profiles.md +173 -0
- package/docs/troubleshooting.md +310 -0
- package/{configs → ide/configs}/antigravity-mcp.json +3 -3
- package/ide/configs/chatgpt-desktop-mcp.json +16 -0
- package/{configs → ide/configs}/claude-desktop-mcp.json +3 -3
- package/{configs → ide/configs}/codex-mcp.toml +4 -4
- package/{configs → ide/configs}/continue-mcp.yaml +4 -3
- package/{configs → ide/configs}/continue-skills.yaml +6 -6
- package/ide/configs/cursor-mcp.json +15 -0
- package/{configs → ide/configs}/gemini-cli-mcp.json +2 -2
- package/{configs → ide/configs}/jetbrains-mcp.json +2 -2
- package/{configs → ide/configs}/opencode-mcp.json +2 -2
- package/{configs → ide/configs}/perplexity-mcp.json +2 -2
- package/{configs → ide/configs}/vscode-copilot-mcp.json +2 -2
- package/{configs → ide/configs}/windsurf-mcp.json +3 -3
- package/{configs → ide/configs}/zed-mcp.json +2 -2
- package/{hooks → ide/hooks}/context-hook.js +9 -20
- package/ide/hooks/memory-list-skill.js +70 -0
- package/ide/hooks/memory-profile-skill.js +101 -0
- package/ide/hooks/memory-recall-skill.js +62 -0
- package/ide/hooks/memory-remember-skill.js +68 -0
- package/ide/hooks/memory-reset-skill.js +160 -0
- package/{hooks → ide/hooks}/post-recall-hook.js +2 -2
- package/ide/integrations/langchain/README.md +106 -0
- package/ide/integrations/langchain/langchain_superlocalmemory/__init__.py +9 -0
- package/ide/integrations/langchain/langchain_superlocalmemory/chat_message_history.py +201 -0
- package/ide/integrations/langchain/pyproject.toml +38 -0
- package/{src/learning → ide/integrations/langchain}/tests/__init__.py +1 -0
- package/ide/integrations/langchain/tests/test_chat_message_history.py +215 -0
- package/ide/integrations/langchain/tests/test_security.py +117 -0
- package/ide/integrations/llamaindex/README.md +81 -0
- package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/__init__.py +9 -0
- package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/base.py +316 -0
- package/ide/integrations/llamaindex/pyproject.toml +43 -0
- package/{src/lifecycle → ide/integrations/llamaindex}/tests/__init__.py +1 -2
- package/ide/integrations/llamaindex/tests/test_chat_store.py +294 -0
- package/ide/integrations/llamaindex/tests/test_security.py +241 -0
- package/{skills → ide/skills}/slm-build-graph/SKILL.md +6 -6
- package/{skills → ide/skills}/slm-list-recent/SKILL.md +5 -5
- package/{skills → ide/skills}/slm-recall/SKILL.md +5 -5
- package/{skills → ide/skills}/slm-remember/SKILL.md +6 -6
- package/{skills → ide/skills}/slm-show-patterns/SKILL.md +7 -7
- package/{skills → ide/skills}/slm-status/SKILL.md +9 -9
- package/{skills → ide/skills}/slm-switch-profile/SKILL.md +9 -9
- package/package.json +13 -22
- package/pyproject.toml +85 -0
- package/scripts/build-dmg.sh +417 -0
- package/scripts/install-skills.ps1 +334 -0
- package/{install.ps1 → scripts/install.ps1} +36 -4
- package/{install.sh → scripts/install.sh} +14 -13
- package/scripts/postinstall.js +2 -2
- package/scripts/start-dashboard.ps1 +52 -0
- package/scripts/start-dashboard.sh +41 -0
- package/scripts/sync-wiki.ps1 +127 -0
- package/scripts/sync-wiki.sh +82 -0
- package/scripts/test-dmg.sh +161 -0
- package/scripts/test-npm-package.ps1 +252 -0
- package/scripts/test-npm-package.sh +207 -0
- package/scripts/verify-install.ps1 +294 -0
- package/scripts/verify-install.sh +266 -0
- package/src/superlocalmemory/__init__.py +0 -0
- package/src/superlocalmemory/attribution/__init__.py +9 -0
- package/src/superlocalmemory/attribution/mathematical_dna.py +235 -0
- package/src/superlocalmemory/attribution/signer.py +153 -0
- package/src/superlocalmemory/attribution/watermark.py +189 -0
- package/src/superlocalmemory/cli/__init__.py +5 -0
- package/src/superlocalmemory/cli/commands.py +245 -0
- package/src/superlocalmemory/cli/main.py +89 -0
- package/src/superlocalmemory/cli/migrate_cmd.py +55 -0
- package/src/superlocalmemory/cli/post_install.py +99 -0
- package/src/superlocalmemory/cli/setup_wizard.py +129 -0
- package/src/superlocalmemory/compliance/__init__.py +0 -0
- package/src/superlocalmemory/compliance/abac.py +204 -0
- package/src/superlocalmemory/compliance/audit.py +314 -0
- package/src/superlocalmemory/compliance/eu_ai_act.py +131 -0
- package/src/superlocalmemory/compliance/gdpr.py +294 -0
- package/src/superlocalmemory/compliance/lifecycle.py +158 -0
- package/src/superlocalmemory/compliance/retention.py +232 -0
- package/src/superlocalmemory/compliance/scheduler.py +148 -0
- package/src/superlocalmemory/core/__init__.py +0 -0
- package/src/superlocalmemory/core/config.py +391 -0
- package/src/superlocalmemory/core/embeddings.py +293 -0
- package/src/superlocalmemory/core/engine.py +701 -0
- package/src/superlocalmemory/core/hooks.py +65 -0
- package/src/superlocalmemory/core/maintenance.py +172 -0
- package/src/superlocalmemory/core/modes.py +140 -0
- package/src/superlocalmemory/core/profiles.py +234 -0
- package/src/superlocalmemory/core/registry.py +117 -0
- package/src/superlocalmemory/dynamics/__init__.py +0 -0
- package/src/superlocalmemory/dynamics/fisher_langevin_coupling.py +223 -0
- package/src/superlocalmemory/encoding/__init__.py +0 -0
- package/src/superlocalmemory/encoding/consolidator.py +485 -0
- package/src/superlocalmemory/encoding/emotional.py +125 -0
- package/src/superlocalmemory/encoding/entity_resolver.py +525 -0
- package/src/superlocalmemory/encoding/entropy_gate.py +104 -0
- package/src/superlocalmemory/encoding/fact_extractor.py +775 -0
- package/src/superlocalmemory/encoding/foresight.py +91 -0
- package/src/superlocalmemory/encoding/graph_builder.py +302 -0
- package/src/superlocalmemory/encoding/observation_builder.py +160 -0
- package/src/superlocalmemory/encoding/scene_builder.py +183 -0
- package/src/superlocalmemory/encoding/signal_inference.py +90 -0
- package/src/superlocalmemory/encoding/temporal_parser.py +426 -0
- package/src/superlocalmemory/encoding/type_router.py +235 -0
- package/src/superlocalmemory/hooks/__init__.py +3 -0
- package/src/superlocalmemory/hooks/auto_capture.py +111 -0
- package/src/superlocalmemory/hooks/auto_recall.py +93 -0
- package/src/superlocalmemory/hooks/ide_connector.py +204 -0
- package/src/superlocalmemory/hooks/rules_engine.py +99 -0
- package/src/superlocalmemory/infra/__init__.py +3 -0
- package/src/superlocalmemory/infra/auth_middleware.py +82 -0
- package/src/superlocalmemory/infra/backup.py +317 -0
- package/src/superlocalmemory/infra/cache_manager.py +267 -0
- package/src/superlocalmemory/infra/event_bus.py +381 -0
- package/src/superlocalmemory/infra/rate_limiter.py +135 -0
- package/src/{webhook_dispatcher.py → superlocalmemory/infra/webhook_dispatcher.py} +104 -101
- package/src/superlocalmemory/learning/__init__.py +0 -0
- package/src/superlocalmemory/learning/adaptive.py +172 -0
- package/src/superlocalmemory/learning/behavioral.py +490 -0
- package/src/superlocalmemory/learning/behavioral_listener.py +94 -0
- package/src/superlocalmemory/learning/bootstrap.py +298 -0
- package/src/superlocalmemory/learning/cross_project.py +399 -0
- package/src/superlocalmemory/learning/database.py +376 -0
- package/src/superlocalmemory/learning/engagement.py +323 -0
- package/src/superlocalmemory/learning/features.py +138 -0
- package/src/superlocalmemory/learning/feedback.py +316 -0
- package/src/superlocalmemory/learning/outcomes.py +255 -0
- package/src/superlocalmemory/learning/project_context.py +366 -0
- package/src/superlocalmemory/learning/ranker.py +155 -0
- package/src/superlocalmemory/learning/source_quality.py +303 -0
- package/src/superlocalmemory/learning/workflows.py +309 -0
- package/src/superlocalmemory/llm/__init__.py +0 -0
- package/src/superlocalmemory/llm/backbone.py +316 -0
- package/src/superlocalmemory/math/__init__.py +0 -0
- package/src/superlocalmemory/math/fisher.py +356 -0
- package/src/superlocalmemory/math/langevin.py +398 -0
- package/src/superlocalmemory/math/sheaf.py +257 -0
- package/src/superlocalmemory/mcp/__init__.py +0 -0
- package/src/superlocalmemory/mcp/resources.py +245 -0
- package/src/superlocalmemory/mcp/server.py +61 -0
- package/src/superlocalmemory/mcp/tools.py +18 -0
- package/src/superlocalmemory/mcp/tools_core.py +305 -0
- package/src/superlocalmemory/mcp/tools_v28.py +223 -0
- package/src/superlocalmemory/mcp/tools_v3.py +286 -0
- package/src/superlocalmemory/retrieval/__init__.py +0 -0
- package/src/superlocalmemory/retrieval/agentic.py +295 -0
- package/src/superlocalmemory/retrieval/ann_index.py +223 -0
- package/src/superlocalmemory/retrieval/bm25_channel.py +185 -0
- package/src/superlocalmemory/retrieval/bridge_discovery.py +170 -0
- package/src/superlocalmemory/retrieval/engine.py +390 -0
- package/src/superlocalmemory/retrieval/entity_channel.py +179 -0
- package/src/superlocalmemory/retrieval/fusion.py +78 -0
- package/src/superlocalmemory/retrieval/profile_channel.py +105 -0
- package/src/superlocalmemory/retrieval/reranker.py +154 -0
- package/src/superlocalmemory/retrieval/semantic_channel.py +232 -0
- package/src/superlocalmemory/retrieval/strategy.py +96 -0
- package/src/superlocalmemory/retrieval/temporal_channel.py +175 -0
- package/src/superlocalmemory/server/__init__.py +1 -0
- package/src/superlocalmemory/server/api.py +248 -0
- package/src/superlocalmemory/server/routes/__init__.py +4 -0
- package/src/superlocalmemory/server/routes/agents.py +107 -0
- package/src/superlocalmemory/server/routes/backup.py +91 -0
- package/src/superlocalmemory/server/routes/behavioral.py +127 -0
- package/src/superlocalmemory/server/routes/compliance.py +160 -0
- package/src/superlocalmemory/server/routes/data_io.py +188 -0
- package/src/superlocalmemory/server/routes/events.py +183 -0
- package/src/superlocalmemory/server/routes/helpers.py +85 -0
- package/src/superlocalmemory/server/routes/learning.py +273 -0
- package/src/superlocalmemory/server/routes/lifecycle.py +116 -0
- package/src/superlocalmemory/server/routes/memories.py +399 -0
- package/src/superlocalmemory/server/routes/profiles.py +219 -0
- package/src/superlocalmemory/server/routes/stats.py +346 -0
- package/src/superlocalmemory/server/routes/v3_api.py +365 -0
- package/src/superlocalmemory/server/routes/ws.py +82 -0
- package/src/superlocalmemory/server/security_middleware.py +57 -0
- package/src/superlocalmemory/server/ui.py +245 -0
- package/src/superlocalmemory/storage/__init__.py +0 -0
- package/src/superlocalmemory/storage/access_control.py +182 -0
- package/src/superlocalmemory/storage/database.py +594 -0
- package/src/superlocalmemory/storage/migrations.py +303 -0
- package/src/superlocalmemory/storage/models.py +406 -0
- package/src/superlocalmemory/storage/schema.py +726 -0
- package/src/superlocalmemory/storage/v2_migrator.py +317 -0
- package/src/superlocalmemory/trust/__init__.py +0 -0
- package/src/superlocalmemory/trust/gate.py +130 -0
- package/src/superlocalmemory/trust/provenance.py +124 -0
- package/src/superlocalmemory/trust/scorer.py +347 -0
- package/src/superlocalmemory/trust/signals.py +153 -0
- package/ui/index.html +278 -5
- package/ui/js/auto-settings.js +70 -0
- package/ui/js/dashboard.js +90 -0
- package/ui/js/fact-detail.js +92 -0
- package/ui/js/feedback.js +2 -2
- package/ui/js/ide-status.js +102 -0
- package/ui/js/math-health.js +98 -0
- package/ui/js/recall-lab.js +127 -0
- package/ui/js/settings.js +2 -2
- package/ui/js/trust-dashboard.js +73 -0
- package/api_server.py +0 -724
- package/bin/aider-smart +0 -72
- package/bin/superlocalmemoryv2-learning +0 -4
- package/bin/superlocalmemoryv2-list +0 -3
- package/bin/superlocalmemoryv2-patterns +0 -4
- package/bin/superlocalmemoryv2-profile +0 -3
- package/bin/superlocalmemoryv2-recall +0 -3
- package/bin/superlocalmemoryv2-remember +0 -3
- package/bin/superlocalmemoryv2-reset +0 -3
- package/bin/superlocalmemoryv2-status +0 -3
- package/configs/chatgpt-desktop-mcp.json +0 -16
- package/configs/cursor-mcp.json +0 -15
- package/docs/SECURITY-QUICK-REFERENCE.md +0 -214
- package/hooks/memory-list-skill.js +0 -139
- package/hooks/memory-profile-skill.js +0 -273
- package/hooks/memory-recall-skill.js +0 -114
- package/hooks/memory-remember-skill.js +0 -127
- package/hooks/memory-reset-skill.js +0 -274
- package/mcp_server.py +0 -1800
- package/requirements-core.txt +0 -22
- package/requirements-learning.txt +0 -12
- package/requirements.txt +0 -12
- package/src/agent_registry.py +0 -411
- package/src/auth_middleware.py +0 -61
- package/src/auto_backup.py +0 -459
- package/src/behavioral/__init__.py +0 -49
- package/src/behavioral/behavioral_listener.py +0 -203
- package/src/behavioral/behavioral_patterns.py +0 -275
- package/src/behavioral/cross_project_transfer.py +0 -206
- package/src/behavioral/outcome_inference.py +0 -194
- package/src/behavioral/outcome_tracker.py +0 -193
- package/src/behavioral/tests/__init__.py +0 -4
- package/src/behavioral/tests/test_behavioral_integration.py +0 -108
- package/src/behavioral/tests/test_behavioral_patterns.py +0 -150
- package/src/behavioral/tests/test_cross_project_transfer.py +0 -142
- package/src/behavioral/tests/test_mcp_behavioral.py +0 -139
- package/src/behavioral/tests/test_mcp_report_outcome.py +0 -117
- package/src/behavioral/tests/test_outcome_inference.py +0 -107
- package/src/behavioral/tests/test_outcome_tracker.py +0 -96
- package/src/cache_manager.py +0 -518
- package/src/compliance/__init__.py +0 -48
- package/src/compliance/abac_engine.py +0 -149
- package/src/compliance/abac_middleware.py +0 -116
- package/src/compliance/audit_db.py +0 -215
- package/src/compliance/audit_logger.py +0 -148
- package/src/compliance/retention_manager.py +0 -289
- package/src/compliance/retention_scheduler.py +0 -186
- package/src/compliance/tests/__init__.py +0 -4
- package/src/compliance/tests/test_abac_enforcement.py +0 -95
- package/src/compliance/tests/test_abac_engine.py +0 -124
- package/src/compliance/tests/test_abac_mcp_integration.py +0 -118
- package/src/compliance/tests/test_audit_db.py +0 -123
- package/src/compliance/tests/test_audit_logger.py +0 -98
- package/src/compliance/tests/test_mcp_audit.py +0 -128
- package/src/compliance/tests/test_mcp_retention_policy.py +0 -125
- package/src/compliance/tests/test_retention_manager.py +0 -131
- package/src/compliance/tests/test_retention_scheduler.py +0 -99
- package/src/compression/__init__.py +0 -25
- package/src/compression/cli.py +0 -150
- package/src/compression/cold_storage.py +0 -217
- package/src/compression/config.py +0 -72
- package/src/compression/orchestrator.py +0 -133
- package/src/compression/tier2_compressor.py +0 -228
- package/src/compression/tier3_compressor.py +0 -153
- package/src/compression/tier_classifier.py +0 -148
- package/src/db_connection_manager.py +0 -536
- package/src/embedding_engine.py +0 -63
- package/src/embeddings/__init__.py +0 -47
- package/src/embeddings/cache.py +0 -70
- package/src/embeddings/cli.py +0 -113
- package/src/embeddings/constants.py +0 -47
- package/src/embeddings/database.py +0 -91
- package/src/embeddings/engine.py +0 -247
- package/src/embeddings/model_loader.py +0 -145
- package/src/event_bus.py +0 -562
- package/src/graph/__init__.py +0 -36
- package/src/graph/build_helpers.py +0 -74
- package/src/graph/cli.py +0 -87
- package/src/graph/cluster_builder.py +0 -188
- package/src/graph/cluster_summary.py +0 -148
- package/src/graph/constants.py +0 -47
- package/src/graph/edge_builder.py +0 -162
- package/src/graph/entity_extractor.py +0 -95
- package/src/graph/graph_core.py +0 -226
- package/src/graph/graph_search.py +0 -231
- package/src/graph/hierarchical.py +0 -207
- package/src/graph/schema.py +0 -99
- package/src/graph_engine.py +0 -52
- package/src/hnsw_index.py +0 -628
- package/src/hybrid_search.py +0 -46
- package/src/learning/__init__.py +0 -217
- package/src/learning/adaptive_ranker.py +0 -682
- package/src/learning/bootstrap/__init__.py +0 -69
- package/src/learning/bootstrap/constants.py +0 -93
- package/src/learning/bootstrap/db_queries.py +0 -316
- package/src/learning/bootstrap/sampling.py +0 -82
- package/src/learning/bootstrap/text_utils.py +0 -71
- package/src/learning/cross_project_aggregator.py +0 -857
- package/src/learning/db/__init__.py +0 -40
- package/src/learning/db/constants.py +0 -44
- package/src/learning/db/schema.py +0 -279
- package/src/learning/engagement_tracker.py +0 -628
- package/src/learning/feature_extractor.py +0 -708
- package/src/learning/feedback_collector.py +0 -806
- package/src/learning/learning_db.py +0 -915
- package/src/learning/project_context_manager.py +0 -572
- package/src/learning/ranking/__init__.py +0 -33
- package/src/learning/ranking/constants.py +0 -84
- package/src/learning/ranking/helpers.py +0 -278
- package/src/learning/source_quality_scorer.py +0 -676
- package/src/learning/synthetic_bootstrap.py +0 -755
- package/src/learning/tests/test_adaptive_ranker.py +0 -325
- package/src/learning/tests/test_adaptive_ranker_v28.py +0 -60
- package/src/learning/tests/test_aggregator.py +0 -306
- package/src/learning/tests/test_auto_retrain_v28.py +0 -35
- package/src/learning/tests/test_e2e_ranking_v28.py +0 -82
- package/src/learning/tests/test_feature_extractor_v28.py +0 -93
- package/src/learning/tests/test_feedback_collector.py +0 -294
- package/src/learning/tests/test_learning_db.py +0 -602
- package/src/learning/tests/test_learning_db_v28.py +0 -110
- package/src/learning/tests/test_learning_init_v28.py +0 -48
- package/src/learning/tests/test_outcome_signals.py +0 -48
- package/src/learning/tests/test_project_context.py +0 -292
- package/src/learning/tests/test_schema_migration.py +0 -319
- package/src/learning/tests/test_signal_inference.py +0 -397
- package/src/learning/tests/test_source_quality.py +0 -351
- package/src/learning/tests/test_synthetic_bootstrap.py +0 -429
- package/src/learning/tests/test_workflow_miner.py +0 -318
- package/src/learning/workflow_pattern_miner.py +0 -655
- package/src/lifecycle/__init__.py +0 -54
- package/src/lifecycle/bounded_growth.py +0 -239
- package/src/lifecycle/compaction_engine.py +0 -226
- package/src/lifecycle/lifecycle_engine.py +0 -355
- package/src/lifecycle/lifecycle_evaluator.py +0 -257
- package/src/lifecycle/lifecycle_scheduler.py +0 -130
- package/src/lifecycle/retention_policy.py +0 -285
- package/src/lifecycle/tests/test_bounded_growth.py +0 -193
- package/src/lifecycle/tests/test_compaction.py +0 -179
- package/src/lifecycle/tests/test_lifecycle_engine.py +0 -137
- package/src/lifecycle/tests/test_lifecycle_evaluation.py +0 -177
- package/src/lifecycle/tests/test_lifecycle_scheduler.py +0 -127
- package/src/lifecycle/tests/test_lifecycle_search.py +0 -109
- package/src/lifecycle/tests/test_mcp_compact.py +0 -149
- package/src/lifecycle/tests/test_mcp_lifecycle_status.py +0 -114
- package/src/lifecycle/tests/test_retention_policy.py +0 -162
- package/src/mcp_tools_v28.py +0 -281
- package/src/memory/__init__.py +0 -36
- package/src/memory/cli.py +0 -205
- package/src/memory/constants.py +0 -39
- package/src/memory/helpers.py +0 -28
- package/src/memory/schema.py +0 -166
- package/src/memory-profiles.py +0 -595
- package/src/memory-reset.py +0 -491
- package/src/memory_compression.py +0 -989
- package/src/memory_store_v2.py +0 -1155
- package/src/migrate_v1_to_v2.py +0 -629
- package/src/pattern_learner.py +0 -34
- package/src/patterns/__init__.py +0 -24
- package/src/patterns/analyzers.py +0 -251
- package/src/patterns/learner.py +0 -271
- package/src/patterns/scoring.py +0 -171
- package/src/patterns/store.py +0 -225
- package/src/patterns/terminology.py +0 -140
- package/src/provenance_tracker.py +0 -312
- package/src/qualixar_attribution.py +0 -139
- package/src/qualixar_watermark.py +0 -78
- package/src/query_optimizer.py +0 -511
- package/src/rate_limiter.py +0 -83
- package/src/search/__init__.py +0 -20
- package/src/search/cli.py +0 -77
- package/src/search/constants.py +0 -26
- package/src/search/engine.py +0 -241
- package/src/search/fusion.py +0 -122
- package/src/search/index_loader.py +0 -114
- package/src/search/methods.py +0 -162
- package/src/search_engine_v2.py +0 -401
- package/src/setup_validator.py +0 -482
- package/src/subscription_manager.py +0 -391
- package/src/tree/__init__.py +0 -59
- package/src/tree/builder.py +0 -185
- package/src/tree/nodes.py +0 -202
- package/src/tree/queries.py +0 -257
- package/src/tree/schema.py +0 -80
- package/src/tree_manager.py +0 -19
- package/src/trust/__init__.py +0 -45
- package/src/trust/constants.py +0 -66
- package/src/trust/queries.py +0 -157
- package/src/trust/schema.py +0 -95
- package/src/trust/scorer.py +0 -299
- package/src/trust/signals.py +0 -95
- package/src/trust_scorer.py +0 -44
- package/ui/app.js +0 -1588
- package/ui/js/graph-cytoscape-monolithic-backup.js +0 -1168
- package/ui/js/graph-cytoscape.js +0 -1168
- package/ui/js/graph-d3-backup.js +0 -32
- package/ui/js/graph.js +0 -32
- package/ui_server.py +0 -266
- /package/docs/{ACCESSIBILITY.md → v2-archive/ACCESSIBILITY.md} +0 -0
- /package/docs/{ARCHITECTURE.md → v2-archive/ARCHITECTURE.md} +0 -0
- /package/docs/{CLI-COMMANDS-REFERENCE.md → v2-archive/CLI-COMMANDS-REFERENCE.md} +0 -0
- /package/docs/{COMPRESSION-README.md → v2-archive/COMPRESSION-README.md} +0 -0
- /package/docs/{FRAMEWORK-INTEGRATIONS.md → v2-archive/FRAMEWORK-INTEGRATIONS.md} +0 -0
- /package/docs/{MCP-MANUAL-SETUP.md → v2-archive/MCP-MANUAL-SETUP.md} +0 -0
- /package/docs/{MCP-TROUBLESHOOTING.md → v2-archive/MCP-TROUBLESHOOTING.md} +0 -0
- /package/docs/{PATTERN-LEARNING.md → v2-archive/PATTERN-LEARNING.md} +0 -0
- /package/docs/{PROFILES-GUIDE.md → v2-archive/PROFILES-GUIDE.md} +0 -0
- /package/docs/{RESET-GUIDE.md → v2-archive/RESET-GUIDE.md} +0 -0
- /package/docs/{SEARCH-ENGINE-V2.2.0.md → v2-archive/SEARCH-ENGINE-V2.2.0.md} +0 -0
- /package/docs/{SEARCH-INTEGRATION-GUIDE.md → v2-archive/SEARCH-INTEGRATION-GUIDE.md} +0 -0
- /package/docs/{UI-SERVER.md → v2-archive/UI-SERVER.md} +0 -0
- /package/docs/{UNIVERSAL-INTEGRATION.md → v2-archive/UNIVERSAL-INTEGRATION.md} +0 -0
- /package/docs/{V2.2.0-OPTIONAL-SEARCH.md → v2-archive/V2.2.0-OPTIONAL-SEARCH.md} +0 -0
- /package/docs/{WINDOWS-INSTALL-README.txt → v2-archive/WINDOWS-INSTALL-README.txt} +0 -0
- /package/docs/{WINDOWS-POST-INSTALL.txt → v2-archive/WINDOWS-POST-INSTALL.txt} +0 -0
- /package/docs/{example_graph_usage.py → v2-archive/example_graph_usage.py} +0 -0
- /package/{completions → ide/completions}/slm.bash +0 -0
- /package/{completions → ide/completions}/slm.zsh +0 -0
- /package/{configs → ide/configs}/cody-commands.json +0 -0
- /package/{install-skills.sh → scripts/install-skills.sh} +0 -0
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
2
|
+
# Licensed under the MIT License - see LICENSE file
|
|
3
|
+
# Part of SuperLocalMemory V3 | https://qualixar.com | https://varunpratap.com
|
|
4
|
+
|
|
5
|
+
"""V3 API endpoints for the SuperLocalMemory dashboard."""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import logging
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from fastapi import APIRouter, Request
|
|
13
|
+
from fastapi.responses import JSONResponse
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
router = APIRouter(prefix="/api/v3", tags=["v3"])
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# ── Dashboard ────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
@router.get("/dashboard")
|
|
23
|
+
async def dashboard(request: Request):
|
|
24
|
+
"""Dashboard summary: mode, memory count, health score, recent activity."""
|
|
25
|
+
try:
|
|
26
|
+
from superlocalmemory.core.config import SLMConfig
|
|
27
|
+
config = SLMConfig.load()
|
|
28
|
+
|
|
29
|
+
# Get basic stats from engine if available
|
|
30
|
+
engine = getattr(request.app.state, "engine", None)
|
|
31
|
+
memory_count = 0
|
|
32
|
+
fact_count = 0
|
|
33
|
+
if engine and engine._db:
|
|
34
|
+
try:
|
|
35
|
+
rows = engine._db.execute("SELECT COUNT(*) FROM atomic_facts")
|
|
36
|
+
if rows:
|
|
37
|
+
fact_count = rows[0][0] if isinstance(rows[0], (list, tuple)) else dict(rows[0]).get("COUNT(*)", 0)
|
|
38
|
+
except Exception:
|
|
39
|
+
pass
|
|
40
|
+
try:
|
|
41
|
+
rows = engine._db.execute("SELECT COUNT(*) FROM memories")
|
|
42
|
+
if rows:
|
|
43
|
+
memory_count = rows[0][0] if isinstance(rows[0], (list, tuple)) else dict(rows[0]).get("COUNT(*)", 0)
|
|
44
|
+
except Exception:
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
"mode": config.mode.value,
|
|
49
|
+
"mode_name": {"a": "Local Guardian", "b": "Smart Local", "c": "Full Power"}.get(config.mode.value, "Unknown"),
|
|
50
|
+
"provider": config.llm.provider or "none",
|
|
51
|
+
"model": config.llm.model or "",
|
|
52
|
+
"memory_count": memory_count,
|
|
53
|
+
"fact_count": fact_count,
|
|
54
|
+
"profile": config.active_profile,
|
|
55
|
+
"base_dir": str(config.base_dir),
|
|
56
|
+
"version": "3.0.0",
|
|
57
|
+
}
|
|
58
|
+
except Exception as e:
|
|
59
|
+
return JSONResponse({"error": str(e)}, status_code=500)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# ── Mode ─────────────────────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
@router.get("/mode")
|
|
65
|
+
async def get_mode():
|
|
66
|
+
"""Get current operating mode."""
|
|
67
|
+
try:
|
|
68
|
+
from superlocalmemory.core.config import SLMConfig
|
|
69
|
+
config = SLMConfig.load()
|
|
70
|
+
modes = {
|
|
71
|
+
"a": {"name": "Local Guardian", "description": "Zero cloud. Your data never leaves your machine.", "llm": False, "eu_compliant": True},
|
|
72
|
+
"b": {"name": "Smart Local", "description": "Local LLM via Ollama. Still fully private.", "llm": "local", "eu_compliant": True},
|
|
73
|
+
"c": {"name": "Full Power", "description": "Cloud LLM for maximum accuracy.", "llm": "cloud", "eu_compliant": False},
|
|
74
|
+
}
|
|
75
|
+
current = config.mode.value
|
|
76
|
+
return {"current": current, "details": modes.get(current, {}), "all_modes": modes}
|
|
77
|
+
except Exception as e:
|
|
78
|
+
return JSONResponse({"error": str(e)}, status_code=500)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@router.put("/mode")
|
|
82
|
+
async def set_mode(request: Request):
|
|
83
|
+
"""Switch operating mode. Body: {"mode": "a"|"b"|"c"}"""
|
|
84
|
+
try:
|
|
85
|
+
body = await request.json()
|
|
86
|
+
new_mode = body.get("mode", "").lower()
|
|
87
|
+
if new_mode not in ("a", "b", "c"):
|
|
88
|
+
return JSONResponse({"error": "Invalid mode. Use a, b, or c."}, status_code=400)
|
|
89
|
+
|
|
90
|
+
from superlocalmemory.core.config import SLMConfig
|
|
91
|
+
from superlocalmemory.storage.models import Mode
|
|
92
|
+
old_config = SLMConfig.load()
|
|
93
|
+
new_config = SLMConfig.for_mode(
|
|
94
|
+
Mode(new_mode),
|
|
95
|
+
llm_provider=old_config.llm.provider,
|
|
96
|
+
llm_model=old_config.llm.model,
|
|
97
|
+
llm_api_key=old_config.llm.api_key,
|
|
98
|
+
llm_api_base=old_config.llm.api_base,
|
|
99
|
+
)
|
|
100
|
+
new_config.active_profile = old_config.active_profile
|
|
101
|
+
new_config.save()
|
|
102
|
+
|
|
103
|
+
# Reset engine to pick up new config
|
|
104
|
+
if hasattr(request.app.state, "engine"):
|
|
105
|
+
request.app.state.engine = None
|
|
106
|
+
|
|
107
|
+
return {"success": True, "mode": new_mode}
|
|
108
|
+
except Exception as e:
|
|
109
|
+
return JSONResponse({"error": str(e)}, status_code=500)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
# ── Provider ─────────────────────────────────────────────────
|
|
113
|
+
|
|
114
|
+
@router.get("/providers")
|
|
115
|
+
async def list_providers():
|
|
116
|
+
"""List available LLM providers with presets."""
|
|
117
|
+
try:
|
|
118
|
+
from superlocalmemory.core.config import SLMConfig
|
|
119
|
+
return {"providers": SLMConfig.provider_presets()}
|
|
120
|
+
except Exception as exc:
|
|
121
|
+
return {"error": str(exc), "providers": []}
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@router.get("/provider")
|
|
125
|
+
async def get_provider():
|
|
126
|
+
"""Get current provider configuration (API key masked)."""
|
|
127
|
+
try:
|
|
128
|
+
from superlocalmemory.core.config import SLMConfig
|
|
129
|
+
config = SLMConfig.load()
|
|
130
|
+
key = config.llm.api_key
|
|
131
|
+
masked = f"****{key[-4:]}" if len(key) > 8 else "****" if key else ""
|
|
132
|
+
return {
|
|
133
|
+
"provider": config.llm.provider or "none",
|
|
134
|
+
"model": config.llm.model,
|
|
135
|
+
"base_url": config.llm.api_base,
|
|
136
|
+
"api_key_masked": masked,
|
|
137
|
+
"has_key": bool(key),
|
|
138
|
+
}
|
|
139
|
+
except Exception as exc:
|
|
140
|
+
return {"error": str(exc), "provider": "unknown"}
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@router.put("/provider")
|
|
144
|
+
async def set_provider(request: Request):
|
|
145
|
+
"""Set LLM provider. Body: {"provider": "openai", "api_key": "...", "model": "..."}"""
|
|
146
|
+
try:
|
|
147
|
+
body = await request.json()
|
|
148
|
+
provider = body.get("provider", "")
|
|
149
|
+
api_key = body.get("api_key", "")
|
|
150
|
+
model = body.get("model", "")
|
|
151
|
+
base_url = body.get("base_url", "")
|
|
152
|
+
|
|
153
|
+
from superlocalmemory.core.config import SLMConfig
|
|
154
|
+
from superlocalmemory.storage.models import Mode
|
|
155
|
+
config = SLMConfig.load()
|
|
156
|
+
|
|
157
|
+
# Use preset base_url if not provided
|
|
158
|
+
if not base_url:
|
|
159
|
+
presets = SLMConfig.provider_presets()
|
|
160
|
+
preset = presets.get(provider, {})
|
|
161
|
+
base_url = preset.get("base_url", "")
|
|
162
|
+
if not model:
|
|
163
|
+
model = preset.get("model", "")
|
|
164
|
+
|
|
165
|
+
new_config = SLMConfig.for_mode(
|
|
166
|
+
config.mode,
|
|
167
|
+
llm_provider=provider,
|
|
168
|
+
llm_model=model,
|
|
169
|
+
llm_api_key=api_key,
|
|
170
|
+
llm_api_base=base_url,
|
|
171
|
+
)
|
|
172
|
+
new_config.active_profile = config.active_profile
|
|
173
|
+
new_config.save()
|
|
174
|
+
|
|
175
|
+
return {"success": True, "provider": provider, "model": model}
|
|
176
|
+
except Exception as e:
|
|
177
|
+
return JSONResponse({"error": str(e)}, status_code=500)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
# ── Recall Trace ─────────────────────────────────────────────
|
|
181
|
+
|
|
182
|
+
@router.post("/recall/trace")
|
|
183
|
+
async def recall_trace(request: Request):
|
|
184
|
+
"""Recall with per-channel score breakdown."""
|
|
185
|
+
try:
|
|
186
|
+
body = await request.json()
|
|
187
|
+
query = body.get("query", "")
|
|
188
|
+
limit = body.get("limit", 10)
|
|
189
|
+
|
|
190
|
+
engine = getattr(request.app.state, "engine", None)
|
|
191
|
+
if not engine:
|
|
192
|
+
return JSONResponse({"error": "Engine not initialized"}, status_code=503)
|
|
193
|
+
|
|
194
|
+
response = engine.recall(query, limit=limit)
|
|
195
|
+
results = []
|
|
196
|
+
for r in response.results[:limit]:
|
|
197
|
+
results.append({
|
|
198
|
+
"fact_id": r.fact.fact_id,
|
|
199
|
+
"content": r.fact.content[:300],
|
|
200
|
+
"score": round(r.score, 4),
|
|
201
|
+
"confidence": round(r.confidence, 4),
|
|
202
|
+
"trust_score": round(r.trust_score, 4),
|
|
203
|
+
"channel_scores": {k: round(v, 4) for k, v in (r.channel_scores or {}).items()},
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
"query": query,
|
|
208
|
+
"query_type": response.query_type,
|
|
209
|
+
"result_count": len(results),
|
|
210
|
+
"retrieval_time_ms": round(response.retrieval_time_ms, 1),
|
|
211
|
+
"results": results,
|
|
212
|
+
}
|
|
213
|
+
except Exception as e:
|
|
214
|
+
return JSONResponse({"error": str(e)}, status_code=500)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
# ── Trust Dashboard ──────────────────────────────────────────
|
|
218
|
+
|
|
219
|
+
@router.get("/trust/dashboard")
|
|
220
|
+
async def trust_dashboard(request: Request):
|
|
221
|
+
"""Trust overview: per-agent scores, alerts."""
|
|
222
|
+
try:
|
|
223
|
+
engine = getattr(request.app.state, "engine", None)
|
|
224
|
+
if not engine or not engine._trust_scorer:
|
|
225
|
+
return {"agents": [], "alerts": [], "message": "Trust scorer not available"}
|
|
226
|
+
|
|
227
|
+
from superlocalmemory.core.config import SLMConfig
|
|
228
|
+
config = SLMConfig.load()
|
|
229
|
+
scores = engine._trust_scorer.get_all_scores(config.active_profile)
|
|
230
|
+
|
|
231
|
+
agents = []
|
|
232
|
+
for s in scores:
|
|
233
|
+
if isinstance(s, dict):
|
|
234
|
+
agents.append(s)
|
|
235
|
+
else:
|
|
236
|
+
agents.append({
|
|
237
|
+
"target_id": s.target_id,
|
|
238
|
+
"target_type": s.target_type,
|
|
239
|
+
"trust_score": round(s.trust_score, 3),
|
|
240
|
+
"evidence_count": s.evidence_count,
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
return {"agents": agents, "alerts": [], "profile": config.active_profile}
|
|
244
|
+
except Exception as e:
|
|
245
|
+
return JSONResponse({"error": str(e)}, status_code=500)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
# ── Math Health ──────────────────────────────────────────────
|
|
249
|
+
|
|
250
|
+
@router.get("/math/health")
|
|
251
|
+
async def math_health(request: Request):
|
|
252
|
+
"""Mathematical layer health: Fisher, sheaf, Langevin status."""
|
|
253
|
+
try:
|
|
254
|
+
engine = getattr(request.app.state, "engine", None)
|
|
255
|
+
|
|
256
|
+
health = {
|
|
257
|
+
"fisher": {"status": "active", "description": "Fisher-Rao information geometry for similarity"},
|
|
258
|
+
"sheaf": {"status": "active", "description": "Sheaf cohomology for consistency detection"},
|
|
259
|
+
"langevin": {"status": "active", "description": "Riemannian Langevin dynamics for lifecycle"},
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
# Check if math layers are configured
|
|
263
|
+
if engine:
|
|
264
|
+
from superlocalmemory.core.config import SLMConfig
|
|
265
|
+
config = SLMConfig.load()
|
|
266
|
+
health["fisher"]["mode"] = config.math.fisher_mode
|
|
267
|
+
health["sheaf"]["threshold"] = config.math.sheaf_contradiction_threshold
|
|
268
|
+
health["langevin"]["temperature"] = config.math.langevin_temperature
|
|
269
|
+
|
|
270
|
+
return {"health": health, "overall": "healthy"}
|
|
271
|
+
except Exception as e:
|
|
272
|
+
return JSONResponse({"error": str(e)}, status_code=500)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
# ── Auto-Capture / Auto-Recall Config ────────────────────────
|
|
276
|
+
|
|
277
|
+
@router.get("/auto-capture/config")
|
|
278
|
+
async def get_auto_capture_config():
|
|
279
|
+
"""Get auto-capture configuration."""
|
|
280
|
+
try:
|
|
281
|
+
from superlocalmemory.hooks.rules_engine import RulesEngine
|
|
282
|
+
from superlocalmemory.core.config import DEFAULT_BASE_DIR
|
|
283
|
+
rules = RulesEngine(config_path=DEFAULT_BASE_DIR / "config.json")
|
|
284
|
+
return {"config": rules.get_capture_config()}
|
|
285
|
+
except Exception as exc:
|
|
286
|
+
return {"error": str(exc), "config": {}}
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
@router.put("/auto-capture/config")
|
|
290
|
+
async def set_auto_capture_config(request: Request):
|
|
291
|
+
"""Update auto-capture config. Body: {"enabled": true, "capture_decisions": true, ...}"""
|
|
292
|
+
try:
|
|
293
|
+
body = await request.json()
|
|
294
|
+
from superlocalmemory.hooks.rules_engine import RulesEngine
|
|
295
|
+
from superlocalmemory.core.config import DEFAULT_BASE_DIR
|
|
296
|
+
config_path = DEFAULT_BASE_DIR / "config.json"
|
|
297
|
+
rules = RulesEngine(config_path=config_path)
|
|
298
|
+
for key, value in body.items():
|
|
299
|
+
rules.update_rule("auto_capture", key, value)
|
|
300
|
+
rules.save(config_path)
|
|
301
|
+
return {"success": True, "config": rules.get_capture_config()}
|
|
302
|
+
except Exception as e:
|
|
303
|
+
return JSONResponse({"error": str(e)}, status_code=500)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
@router.get("/auto-recall/config")
|
|
307
|
+
async def get_auto_recall_config():
|
|
308
|
+
"""Get auto-recall configuration."""
|
|
309
|
+
try:
|
|
310
|
+
from superlocalmemory.hooks.rules_engine import RulesEngine
|
|
311
|
+
from superlocalmemory.core.config import DEFAULT_BASE_DIR
|
|
312
|
+
rules = RulesEngine(config_path=DEFAULT_BASE_DIR / "config.json")
|
|
313
|
+
return {"config": rules.get_recall_config()}
|
|
314
|
+
except Exception as exc:
|
|
315
|
+
return {"error": str(exc), "config": {}}
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
@router.put("/auto-recall/config")
|
|
319
|
+
async def set_auto_recall_config(request: Request):
|
|
320
|
+
"""Update auto-recall config."""
|
|
321
|
+
try:
|
|
322
|
+
body = await request.json()
|
|
323
|
+
from superlocalmemory.hooks.rules_engine import RulesEngine
|
|
324
|
+
from superlocalmemory.core.config import DEFAULT_BASE_DIR
|
|
325
|
+
config_path = DEFAULT_BASE_DIR / "config.json"
|
|
326
|
+
rules = RulesEngine(config_path=config_path)
|
|
327
|
+
for key, value in body.items():
|
|
328
|
+
rules.update_rule("auto_recall", key, value)
|
|
329
|
+
rules.save(config_path)
|
|
330
|
+
return {"success": True, "config": rules.get_recall_config()}
|
|
331
|
+
except Exception as e:
|
|
332
|
+
return JSONResponse({"error": str(e)}, status_code=500)
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
# ── IDE Status ───────────────────────────────────────────────
|
|
336
|
+
|
|
337
|
+
@router.get("/ide/status")
|
|
338
|
+
async def ide_status():
|
|
339
|
+
"""Get IDE connection status."""
|
|
340
|
+
try:
|
|
341
|
+
from superlocalmemory.hooks.ide_connector import IDEConnector
|
|
342
|
+
connector = IDEConnector()
|
|
343
|
+
return {"ides": connector.get_status()}
|
|
344
|
+
except Exception as exc:
|
|
345
|
+
return {"error": str(exc), "ides": []}
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
@router.post("/ide/connect")
|
|
349
|
+
async def ide_connect(request: Request):
|
|
350
|
+
"""Connect an IDE. Body: {"ide": "cursor"} or {} for all."""
|
|
351
|
+
try:
|
|
352
|
+
body = await request.json()
|
|
353
|
+
ide = body.get("ide", "")
|
|
354
|
+
|
|
355
|
+
from superlocalmemory.hooks.ide_connector import IDEConnector
|
|
356
|
+
connector = IDEConnector()
|
|
357
|
+
|
|
358
|
+
if ide:
|
|
359
|
+
success = connector.connect(ide)
|
|
360
|
+
return {"success": success, "ide": ide}
|
|
361
|
+
else:
|
|
362
|
+
results = connector.connect_all()
|
|
363
|
+
return {"results": results}
|
|
364
|
+
except Exception as e:
|
|
365
|
+
return JSONResponse({"error": str(e)}, status_code=500)
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
2
|
+
# Licensed under the MIT License - see LICENSE file
|
|
3
|
+
# Part of SuperLocalMemory V3 | https://qualixar.com | https://varunpratap.com
|
|
4
|
+
"""SuperLocalMemory V3 - WebSocket Routes
|
|
5
|
+
- MIT License
|
|
6
|
+
|
|
7
|
+
Routes: /ws/updates
|
|
8
|
+
"""
|
|
9
|
+
from typing import Set
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
|
|
12
|
+
from fastapi import APIRouter, WebSocket, WebSocketDisconnect
|
|
13
|
+
|
|
14
|
+
router = APIRouter()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ConnectionManager:
|
|
18
|
+
"""Manages WebSocket connections for real-time updates."""
|
|
19
|
+
|
|
20
|
+
def __init__(self):
|
|
21
|
+
self.active_connections: Set[WebSocket] = set()
|
|
22
|
+
|
|
23
|
+
async def connect(self, websocket: WebSocket):
|
|
24
|
+
await websocket.accept()
|
|
25
|
+
self.active_connections.add(websocket)
|
|
26
|
+
|
|
27
|
+
def disconnect(self, websocket: WebSocket):
|
|
28
|
+
self.active_connections.discard(websocket)
|
|
29
|
+
|
|
30
|
+
async def broadcast(self, message: dict):
|
|
31
|
+
disconnected = set()
|
|
32
|
+
for connection in self.active_connections:
|
|
33
|
+
try:
|
|
34
|
+
await connection.send_json(message)
|
|
35
|
+
except Exception:
|
|
36
|
+
disconnected.add(connection)
|
|
37
|
+
self.active_connections -= disconnected
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
manager = ConnectionManager()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@router.websocket("/ws/updates")
|
|
44
|
+
async def websocket_updates(websocket: WebSocket):
|
|
45
|
+
"""WebSocket endpoint for real-time memory updates."""
|
|
46
|
+
await manager.connect(websocket)
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
await websocket.send_json({
|
|
50
|
+
"type": "connected",
|
|
51
|
+
"message": "WebSocket connection established",
|
|
52
|
+
"timestamp": datetime.now().isoformat(),
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
while True:
|
|
56
|
+
try:
|
|
57
|
+
data = await websocket.receive_json()
|
|
58
|
+
|
|
59
|
+
if data.get('type') == 'ping':
|
|
60
|
+
await websocket.send_json({
|
|
61
|
+
"type": "pong",
|
|
62
|
+
"timestamp": datetime.now().isoformat(),
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
elif data.get('type') == 'get_stats':
|
|
66
|
+
await websocket.send_json({
|
|
67
|
+
"type": "stats_update",
|
|
68
|
+
"message": "Use /api/stats endpoint for stats",
|
|
69
|
+
"timestamp": datetime.now().isoformat(),
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
except WebSocketDisconnect:
|
|
73
|
+
break
|
|
74
|
+
except Exception as e:
|
|
75
|
+
await websocket.send_json({
|
|
76
|
+
"type": "error",
|
|
77
|
+
"message": str(e),
|
|
78
|
+
"timestamp": datetime.now().isoformat(),
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
finally:
|
|
82
|
+
manager.disconnect(websocket)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
# Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
|
|
4
|
+
"""Security headers middleware for FastAPI servers.
|
|
5
|
+
|
|
6
|
+
Adds comprehensive security headers to all HTTP responses:
|
|
7
|
+
- X-Content-Type-Options: Prevents MIME type sniffing
|
|
8
|
+
- X-Frame-Options: Prevents clickjacking attacks
|
|
9
|
+
- X-XSS-Protection: Enables browser XSS filters
|
|
10
|
+
- Content-Security-Policy: Restricts resource loading
|
|
11
|
+
- Referrer-Policy: Controls referrer information leakage
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
15
|
+
from starlette.requests import Request
|
|
16
|
+
from starlette.responses import Response
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SecurityHeadersMiddleware(BaseHTTPMiddleware):
|
|
20
|
+
"""Add security headers to all HTTP responses."""
|
|
21
|
+
|
|
22
|
+
async def dispatch(self, request: Request, call_next) -> Response:
|
|
23
|
+
"""Process request and add security headers to response."""
|
|
24
|
+
response = await call_next(request)
|
|
25
|
+
|
|
26
|
+
# Prevent MIME type sniffing
|
|
27
|
+
response.headers["X-Content-Type-Options"] = "nosniff"
|
|
28
|
+
|
|
29
|
+
# Prevent clickjacking attacks
|
|
30
|
+
response.headers["X-Frame-Options"] = "DENY"
|
|
31
|
+
|
|
32
|
+
# Enable browser XSS filter (legacy, but doesn't hurt)
|
|
33
|
+
response.headers["X-XSS-Protection"] = "1; mode=block"
|
|
34
|
+
|
|
35
|
+
# Content Security Policy
|
|
36
|
+
# Note: 'unsafe-inline' is needed for Bootstrap and inline scripts
|
|
37
|
+
# For production, consider moving inline scripts to separate files
|
|
38
|
+
csp_directives = [
|
|
39
|
+
"default-src 'self'",
|
|
40
|
+
"script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://unpkg.com https://d3js.org",
|
|
41
|
+
"style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://unpkg.com",
|
|
42
|
+
"font-src 'self' https://cdn.jsdelivr.net",
|
|
43
|
+
"img-src 'self' data: https:",
|
|
44
|
+
"connect-src 'self' ws://localhost:* ws://127.0.0.1:*",
|
|
45
|
+
"frame-ancestors 'none'",
|
|
46
|
+
]
|
|
47
|
+
response.headers["Content-Security-Policy"] = "; ".join(csp_directives)
|
|
48
|
+
|
|
49
|
+
# Control referrer information leakage
|
|
50
|
+
response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
|
|
51
|
+
|
|
52
|
+
# Prevent caching of sensitive data (for API endpoints)
|
|
53
|
+
if request.url.path.startswith("/api/"):
|
|
54
|
+
response.headers["Cache-Control"] = "no-store, no-cache, must-revalidate"
|
|
55
|
+
response.headers["Pragma"] = "no-cache"
|
|
56
|
+
|
|
57
|
+
return response
|