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,245 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
3
|
+
# Licensed under the MIT License - see LICENSE file
|
|
4
|
+
# Part of SuperLocalMemory V3 | https://qualixar.com | https://varunpratap.com
|
|
5
|
+
"""
|
|
6
|
+
SuperLocalMemory V3 - FastAPI UI Server
|
|
7
|
+
App initialization, middleware, static mount, and router registration.
|
|
8
|
+
|
|
9
|
+
All route handlers live in routes/ directory:
|
|
10
|
+
routes/memories.py -- /api/memories, /api/graph, /api/search, /api/clusters
|
|
11
|
+
routes/stats.py -- /api/stats, /api/timeline, /api/patterns
|
|
12
|
+
routes/profiles.py -- /api/profiles (CRUD + switch)
|
|
13
|
+
routes/backup.py -- /api/backup (status, create, configure, list)
|
|
14
|
+
routes/data_io.py -- /api/export, /api/import
|
|
15
|
+
routes/events.py -- /events/stream (SSE), /api/events [v2.5]
|
|
16
|
+
routes/agents.py -- /api/agents, /api/trust [v2.5]
|
|
17
|
+
routes/ws.py -- /ws/updates (WebSocket)
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import sys
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from datetime import datetime
|
|
23
|
+
|
|
24
|
+
_script_dir = str(Path(__file__).parent.resolve())
|
|
25
|
+
sys.path = [p for p in sys.path if p not in ("", _script_dir)]
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
from fastapi import FastAPI
|
|
29
|
+
from fastapi.staticfiles import StaticFiles
|
|
30
|
+
from fastapi.responses import HTMLResponse
|
|
31
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
32
|
+
from fastapi.middleware.gzip import GZipMiddleware
|
|
33
|
+
import uvicorn
|
|
34
|
+
except ImportError:
|
|
35
|
+
raise ImportError(
|
|
36
|
+
"FastAPI dependencies not installed. "
|
|
37
|
+
"Install with: pip install 'fastapi[all]' uvicorn websockets"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
from superlocalmemory.server.security_middleware import SecurityHeadersMiddleware
|
|
41
|
+
|
|
42
|
+
# V3 Paths (migrated from ~/.claude-memory to ~/.superlocalmemory)
|
|
43
|
+
MEMORY_DIR = Path.home() / ".superlocalmemory"
|
|
44
|
+
DB_PATH = MEMORY_DIR / "memory.db"
|
|
45
|
+
# ui/ is at repo root, 4 levels up from src/superlocalmemory/server/ui.py
|
|
46
|
+
UI_DIR = Path(__file__).resolve().parent.parent.parent.parent / "ui"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def create_app() -> FastAPI:
|
|
50
|
+
"""Create and configure the FastAPI application."""
|
|
51
|
+
application = FastAPI(
|
|
52
|
+
title="SuperLocalMemory V3 UI Server",
|
|
53
|
+
description="Memory Dashboard with V3 Engine, Trust, Learning, and Compliance",
|
|
54
|
+
version="3.0.0",
|
|
55
|
+
docs_url="/api/docs",
|
|
56
|
+
redoc_url="/api/redoc",
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# Middleware (order matters: security headers should be outermost)
|
|
60
|
+
application.add_middleware(SecurityHeadersMiddleware)
|
|
61
|
+
application.add_middleware(GZipMiddleware, minimum_size=1000)
|
|
62
|
+
application.add_middleware(
|
|
63
|
+
CORSMiddleware,
|
|
64
|
+
allow_origins=[
|
|
65
|
+
"http://localhost:8765", "http://127.0.0.1:8765",
|
|
66
|
+
"http://localhost:8417", "http://127.0.0.1:8417",
|
|
67
|
+
],
|
|
68
|
+
allow_credentials=True,
|
|
69
|
+
allow_methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"],
|
|
70
|
+
allow_headers=["Content-Type", "Authorization", "X-SLM-API-Key"],
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
# Rate limiting (graceful)
|
|
74
|
+
try:
|
|
75
|
+
from superlocalmemory.infra.rate_limiter import RateLimiter
|
|
76
|
+
_write_limiter = RateLimiter(max_requests=30, window_seconds=60)
|
|
77
|
+
_read_limiter = RateLimiter(max_requests=120, window_seconds=60)
|
|
78
|
+
|
|
79
|
+
@application.middleware("http")
|
|
80
|
+
async def rate_limit_middleware(request, call_next):
|
|
81
|
+
client_ip = request.client.host if request.client else "unknown"
|
|
82
|
+
is_write = request.method in ("POST", "PUT", "DELETE", "PATCH")
|
|
83
|
+
limiter = _write_limiter if is_write else _read_limiter
|
|
84
|
+
allowed, remaining = limiter.is_allowed(client_ip)
|
|
85
|
+
if not allowed:
|
|
86
|
+
from fastapi.responses import JSONResponse
|
|
87
|
+
return JSONResponse(
|
|
88
|
+
status_code=429,
|
|
89
|
+
content={"error": "Too many requests. Please slow down."},
|
|
90
|
+
headers={"Retry-After": str(limiter.window_seconds)},
|
|
91
|
+
)
|
|
92
|
+
response = await call_next(request)
|
|
93
|
+
response.headers["X-RateLimit-Remaining"] = str(remaining)
|
|
94
|
+
return response
|
|
95
|
+
except (ImportError, Exception):
|
|
96
|
+
pass
|
|
97
|
+
|
|
98
|
+
# Auth middleware (graceful)
|
|
99
|
+
try:
|
|
100
|
+
from superlocalmemory.infra.auth_middleware import check_api_key
|
|
101
|
+
|
|
102
|
+
@application.middleware("http")
|
|
103
|
+
async def auth_middleware(request, call_next):
|
|
104
|
+
is_write = request.method in ("POST", "PUT", "DELETE", "PATCH")
|
|
105
|
+
headers = dict(request.headers)
|
|
106
|
+
if not check_api_key(headers, is_write=is_write):
|
|
107
|
+
from fastapi.responses import JSONResponse
|
|
108
|
+
return JSONResponse(
|
|
109
|
+
status_code=401,
|
|
110
|
+
content={"error": "Invalid or missing API key."},
|
|
111
|
+
)
|
|
112
|
+
response = await call_next(request)
|
|
113
|
+
return response
|
|
114
|
+
except (ImportError, Exception):
|
|
115
|
+
pass
|
|
116
|
+
|
|
117
|
+
# Mount static files (UI directory)
|
|
118
|
+
UI_DIR.mkdir(exist_ok=True)
|
|
119
|
+
application.mount("/static", StaticFiles(directory=str(UI_DIR)), name="static")
|
|
120
|
+
|
|
121
|
+
# ========================================================================
|
|
122
|
+
# Register Route Modules
|
|
123
|
+
# ========================================================================
|
|
124
|
+
from superlocalmemory.server.routes.memories import router as memories_router
|
|
125
|
+
from superlocalmemory.server.routes.stats import router as stats_router
|
|
126
|
+
from superlocalmemory.server.routes.profiles import router as profiles_router
|
|
127
|
+
from superlocalmemory.server.routes.backup import router as backup_router
|
|
128
|
+
from superlocalmemory.server.routes.data_io import router as data_io_router
|
|
129
|
+
from superlocalmemory.server.routes.events import router as events_router, register_event_listener
|
|
130
|
+
from superlocalmemory.server.routes.agents import router as agents_router
|
|
131
|
+
from superlocalmemory.server.routes.ws import router as ws_router, manager as ws_manager
|
|
132
|
+
|
|
133
|
+
application.include_router(memories_router)
|
|
134
|
+
application.include_router(stats_router)
|
|
135
|
+
application.include_router(profiles_router)
|
|
136
|
+
application.include_router(backup_router)
|
|
137
|
+
application.include_router(data_io_router)
|
|
138
|
+
application.include_router(events_router)
|
|
139
|
+
application.include_router(agents_router)
|
|
140
|
+
application.include_router(ws_router)
|
|
141
|
+
|
|
142
|
+
# V3 API endpoints (dashboard, mode, trust, math, etc.)
|
|
143
|
+
from superlocalmemory.server.routes.v3_api import router as v3_router
|
|
144
|
+
application.include_router(v3_router)
|
|
145
|
+
|
|
146
|
+
# Graceful optional routers
|
|
147
|
+
for _module_name in ("learning", "lifecycle", "behavioral", "compliance"):
|
|
148
|
+
try:
|
|
149
|
+
_mod = __import__(f"superlocalmemory.server.routes.{_module_name}", fromlist=["router"])
|
|
150
|
+
application.include_router(_mod.router)
|
|
151
|
+
except (ImportError, Exception):
|
|
152
|
+
pass
|
|
153
|
+
|
|
154
|
+
# Wire WebSocket manager into routes that need broadcast capability
|
|
155
|
+
import superlocalmemory.server.routes.profiles as _profiles_mod
|
|
156
|
+
import superlocalmemory.server.routes.data_io as _data_io_mod
|
|
157
|
+
_profiles_mod.ws_manager = ws_manager
|
|
158
|
+
_data_io_mod.ws_manager = ws_manager
|
|
159
|
+
|
|
160
|
+
# ========================================================================
|
|
161
|
+
# Basic Routes (root page + health check)
|
|
162
|
+
# ========================================================================
|
|
163
|
+
|
|
164
|
+
@application.get("/", response_class=HTMLResponse)
|
|
165
|
+
async def root():
|
|
166
|
+
"""Serve main UI page."""
|
|
167
|
+
index_path = UI_DIR / "index.html"
|
|
168
|
+
if not index_path.exists():
|
|
169
|
+
return (
|
|
170
|
+
"<!DOCTYPE html><html><head>"
|
|
171
|
+
"<title>SuperLocalMemory V3</title></head>"
|
|
172
|
+
"<body style='font-family:Arial;padding:40px'>"
|
|
173
|
+
"<h1>SuperLocalMemory V3 UI Server Running</h1>"
|
|
174
|
+
"<p>UI not found. Check ui/index.html</p>"
|
|
175
|
+
"<p><a href='/api/docs'>API Documentation</a></p>"
|
|
176
|
+
"</body></html>"
|
|
177
|
+
)
|
|
178
|
+
return index_path.read_text()
|
|
179
|
+
|
|
180
|
+
@application.get("/health")
|
|
181
|
+
async def health_check():
|
|
182
|
+
"""Health check endpoint."""
|
|
183
|
+
return {
|
|
184
|
+
"status": "healthy",
|
|
185
|
+
"version": "3.0.0",
|
|
186
|
+
"database": "connected" if DB_PATH.exists() else "missing",
|
|
187
|
+
"timestamp": datetime.now().isoformat(),
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
# ========================================================================
|
|
191
|
+
# Startup Events
|
|
192
|
+
# ========================================================================
|
|
193
|
+
|
|
194
|
+
@application.on_event("startup")
|
|
195
|
+
async def startup_event():
|
|
196
|
+
"""Register Event Bus listener for SSE bridge on startup."""
|
|
197
|
+
register_event_listener()
|
|
198
|
+
|
|
199
|
+
return application
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
app = create_app()
|
|
203
|
+
|
|
204
|
+
# ============================================================================
|
|
205
|
+
# Server Startup
|
|
206
|
+
# ============================================================================
|
|
207
|
+
|
|
208
|
+
if __name__ == "__main__":
|
|
209
|
+
import argparse
|
|
210
|
+
import socket
|
|
211
|
+
|
|
212
|
+
parser = argparse.ArgumentParser(description="SuperLocalMemory V3 - Web Dashboard")
|
|
213
|
+
parser.add_argument("--port", type=int, default=8765, help="Port (default 8765)")
|
|
214
|
+
parser.add_argument("--profile", type=str, default=None, help="Memory profile")
|
|
215
|
+
args = parser.parse_args()
|
|
216
|
+
|
|
217
|
+
def find_available_port(preferred: int) -> int:
|
|
218
|
+
for port in [preferred] + list(range(preferred + 1, preferred + 20)):
|
|
219
|
+
try:
|
|
220
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
221
|
+
s.bind(("127.0.0.1", port))
|
|
222
|
+
return port
|
|
223
|
+
except OSError:
|
|
224
|
+
continue
|
|
225
|
+
return preferred
|
|
226
|
+
|
|
227
|
+
ui_port = find_available_port(args.port)
|
|
228
|
+
if ui_port != args.port:
|
|
229
|
+
print(f"\n Port {args.port} in use -- using {ui_port} instead\n")
|
|
230
|
+
|
|
231
|
+
print("=" * 70)
|
|
232
|
+
print(" SuperLocalMemory V3 - Web Dashboard")
|
|
233
|
+
print(" Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar")
|
|
234
|
+
print("=" * 70)
|
|
235
|
+
print(f" Database: {DB_PATH}")
|
|
236
|
+
print(f" UI: {UI_DIR}")
|
|
237
|
+
print("=" * 70)
|
|
238
|
+
print(f"\n Dashboard: http://localhost:{ui_port}")
|
|
239
|
+
print(f" API Docs: http://localhost:{ui_port}/api/docs")
|
|
240
|
+
print(f" Health: http://localhost:{ui_port}/health")
|
|
241
|
+
print(f" SSE Stream: http://localhost:{ui_port}/events/stream")
|
|
242
|
+
print(f" WebSocket: ws://localhost:{ui_port}/ws/updates")
|
|
243
|
+
print("\n Press Ctrl+C to stop\n")
|
|
244
|
+
|
|
245
|
+
uvicorn.run(app, host="127.0.0.1", port=ui_port, log_level="info", access_log=True)
|
|
File without changes
|
|
@@ -0,0 +1,182 @@
|
|
|
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
|
+
"""SuperLocalMemory V3 — Attribute-Based Access Control (ABAC).
|
|
6
|
+
|
|
7
|
+
Profile-scoped access control. Ensures memory operations
|
|
8
|
+
respect profile boundaries and permission rules.
|
|
9
|
+
Ported from V2.8 with enhancements.
|
|
10
|
+
|
|
11
|
+
Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import logging
|
|
17
|
+
from dataclasses import dataclass, field
|
|
18
|
+
from enum import Enum
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Permission(str, Enum):
|
|
24
|
+
"""Memory operation permissions."""
|
|
25
|
+
|
|
26
|
+
READ = "read"
|
|
27
|
+
WRITE = "write"
|
|
28
|
+
DELETE = "delete"
|
|
29
|
+
EXPORT = "export"
|
|
30
|
+
ADMIN = "admin" # Can manage profiles, access control
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class AccessLevel(str, Enum):
|
|
34
|
+
"""Access level tiers."""
|
|
35
|
+
|
|
36
|
+
OWNER = "owner" # Full access to profile
|
|
37
|
+
AGENT = "agent" # Read + write, no delete/export
|
|
38
|
+
READONLY = "readonly" # Read only
|
|
39
|
+
NONE = "none" # No access
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# Permission matrix per access level
|
|
43
|
+
_PERMISSIONS: dict[AccessLevel, frozenset[Permission]] = {
|
|
44
|
+
AccessLevel.OWNER: frozenset(Permission),
|
|
45
|
+
AccessLevel.AGENT: frozenset({Permission.READ, Permission.WRITE}),
|
|
46
|
+
AccessLevel.READONLY: frozenset({Permission.READ}),
|
|
47
|
+
AccessLevel.NONE: frozenset(),
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass(frozen=True)
|
|
52
|
+
class AccessGrant:
|
|
53
|
+
"""Grant of access to a profile for an agent/user."""
|
|
54
|
+
|
|
55
|
+
subject_id: str # Agent ID or user ID
|
|
56
|
+
profile_id: str # Which profile
|
|
57
|
+
access_level: AccessLevel
|
|
58
|
+
granted_by: str = ""
|
|
59
|
+
granted_at: str = ""
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass
|
|
63
|
+
class AccessController:
|
|
64
|
+
"""Attribute-based access control for memory operations.
|
|
65
|
+
|
|
66
|
+
Enforces profile isolation:
|
|
67
|
+
- Each agent/user has an access level per profile
|
|
68
|
+
- Operations check permissions before execution
|
|
69
|
+
- Default: profile creator = OWNER, others = NONE
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
# In-memory grant store (persisted via profiles.json or DB)
|
|
73
|
+
_grants: dict[tuple[str, str], AccessGrant] = field(default_factory=dict)
|
|
74
|
+
|
|
75
|
+
def grant_access(
|
|
76
|
+
self,
|
|
77
|
+
subject_id: str,
|
|
78
|
+
profile_id: str,
|
|
79
|
+
access_level: AccessLevel,
|
|
80
|
+
granted_by: str = "system",
|
|
81
|
+
) -> AccessGrant:
|
|
82
|
+
"""Grant access to a profile for a subject."""
|
|
83
|
+
from datetime import UTC, datetime
|
|
84
|
+
|
|
85
|
+
grant = AccessGrant(
|
|
86
|
+
subject_id=subject_id,
|
|
87
|
+
profile_id=profile_id,
|
|
88
|
+
access_level=access_level,
|
|
89
|
+
granted_by=granted_by,
|
|
90
|
+
granted_at=datetime.now(UTC).isoformat(),
|
|
91
|
+
)
|
|
92
|
+
self._grants[(subject_id, profile_id)] = grant
|
|
93
|
+
logger.info(
|
|
94
|
+
"Granted %s access to profile '%s' for '%s'",
|
|
95
|
+
access_level.value,
|
|
96
|
+
profile_id,
|
|
97
|
+
subject_id,
|
|
98
|
+
)
|
|
99
|
+
return grant
|
|
100
|
+
|
|
101
|
+
def revoke_access(self, subject_id: str, profile_id: str) -> None:
|
|
102
|
+
"""Revoke access to a profile."""
|
|
103
|
+
key = (subject_id, profile_id)
|
|
104
|
+
if key in self._grants:
|
|
105
|
+
del self._grants[key]
|
|
106
|
+
logger.info(
|
|
107
|
+
"Revoked access to profile '%s' for '%s'",
|
|
108
|
+
profile_id,
|
|
109
|
+
subject_id,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
def check_permission(
|
|
113
|
+
self,
|
|
114
|
+
subject_id: str,
|
|
115
|
+
profile_id: str,
|
|
116
|
+
permission: Permission,
|
|
117
|
+
) -> bool:
|
|
118
|
+
"""Check if subject has permission on profile.
|
|
119
|
+
|
|
120
|
+
Default behavior: if no grant exists, deny access.
|
|
121
|
+
Exception: "default" profile allows READ for all subjects.
|
|
122
|
+
"""
|
|
123
|
+
grant = self._grants.get((subject_id, profile_id))
|
|
124
|
+
|
|
125
|
+
if grant is None:
|
|
126
|
+
# Default profile allows read for everyone
|
|
127
|
+
if profile_id == "default" and permission == Permission.READ:
|
|
128
|
+
return True
|
|
129
|
+
return False
|
|
130
|
+
|
|
131
|
+
allowed = _PERMISSIONS.get(grant.access_level, frozenset())
|
|
132
|
+
return permission in allowed
|
|
133
|
+
|
|
134
|
+
def require_permission(
|
|
135
|
+
self,
|
|
136
|
+
subject_id: str,
|
|
137
|
+
profile_id: str,
|
|
138
|
+
permission: Permission,
|
|
139
|
+
) -> None:
|
|
140
|
+
"""Raise if subject lacks permission. Use as a guard."""
|
|
141
|
+
if not self.check_permission(subject_id, profile_id, permission):
|
|
142
|
+
raise PermissionError(
|
|
143
|
+
f"Subject '{subject_id}' lacks {permission.value} "
|
|
144
|
+
f"permission on profile '{profile_id}'"
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
def get_access_level(
|
|
148
|
+
self, subject_id: str, profile_id: str
|
|
149
|
+
) -> AccessLevel:
|
|
150
|
+
"""Get the access level for a subject on a profile."""
|
|
151
|
+
grant = self._grants.get((subject_id, profile_id))
|
|
152
|
+
return grant.access_level if grant else AccessLevel.NONE
|
|
153
|
+
|
|
154
|
+
def list_grants(self, profile_id: str | None = None) -> list[AccessGrant]:
|
|
155
|
+
"""List all grants, optionally filtered by profile."""
|
|
156
|
+
grants = list(self._grants.values())
|
|
157
|
+
if profile_id is not None:
|
|
158
|
+
grants = [g for g in grants if g.profile_id == profile_id]
|
|
159
|
+
return grants
|
|
160
|
+
|
|
161
|
+
def export_grants(self) -> list[dict]:
|
|
162
|
+
"""Export all grants as dicts for persistence."""
|
|
163
|
+
return [
|
|
164
|
+
{
|
|
165
|
+
"subject_id": g.subject_id,
|
|
166
|
+
"profile_id": g.profile_id,
|
|
167
|
+
"access_level": g.access_level.value,
|
|
168
|
+
"granted_by": g.granted_by,
|
|
169
|
+
"granted_at": g.granted_at,
|
|
170
|
+
}
|
|
171
|
+
for g in self._grants.values()
|
|
172
|
+
]
|
|
173
|
+
|
|
174
|
+
def import_grants(self, data: list[dict]) -> None:
|
|
175
|
+
"""Import grants from persisted data."""
|
|
176
|
+
for item in data:
|
|
177
|
+
self.grant_access(
|
|
178
|
+
subject_id=item["subject_id"],
|
|
179
|
+
profile_id=item["profile_id"],
|
|
180
|
+
access_level=AccessLevel(item["access_level"]),
|
|
181
|
+
granted_by=item.get("granted_by", "import"),
|
|
182
|
+
)
|