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.
- package/LICENSE +9 -1
- package/NOTICE +63 -0
- package/README.md +165 -480
- package/bin/slm +17 -449
- package/bin/slm-npm +1 -1
- 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/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/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 -1808
- 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 -286
- /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
- /package/{install.ps1 → scripts/install.ps1} +0 -0
- /package/{install.sh → scripts/install.sh} +0 -0
package/src/memory-profiles.py
DELETED
|
@@ -1,595 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# SPDX-License-Identifier: MIT
|
|
3
|
-
# Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
|
|
4
|
-
"""
|
|
5
|
-
SuperLocalMemory V2 - Profile Management System (Column-Based)
|
|
6
|
-
|
|
7
|
-
v2.4.0: Rewritten to use column-based profiles in a SINGLE database.
|
|
8
|
-
All memories live in one memory.db with a 'profile' column.
|
|
9
|
-
Switching profiles = updating config. No file copying. No data loss risk.
|
|
10
|
-
|
|
11
|
-
Previous versions used separate database files per profile, which caused
|
|
12
|
-
data loss when switching. This version is backward compatible and will
|
|
13
|
-
auto-migrate old profile directories on first run.
|
|
14
|
-
|
|
15
|
-
Allows users to maintain separate memory contexts:
|
|
16
|
-
- Work profile: Professional coding memories
|
|
17
|
-
- Personal profile: Personal projects and learning
|
|
18
|
-
- Client-specific profiles: Different clients get isolated memories
|
|
19
|
-
- Experimentation profile: Testing and experiments
|
|
20
|
-
"""
|
|
21
|
-
|
|
22
|
-
import os
|
|
23
|
-
import sys
|
|
24
|
-
import json
|
|
25
|
-
import sqlite3
|
|
26
|
-
import hashlib
|
|
27
|
-
from pathlib import Path
|
|
28
|
-
from datetime import datetime
|
|
29
|
-
import argparse
|
|
30
|
-
import re
|
|
31
|
-
|
|
32
|
-
MEMORY_DIR = Path.home() / ".claude-memory"
|
|
33
|
-
DB_PATH = MEMORY_DIR / "memory.db"
|
|
34
|
-
PROFILES_DIR = MEMORY_DIR / "profiles"
|
|
35
|
-
CONFIG_FILE = MEMORY_DIR / "profiles.json"
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
class ProfileManager:
|
|
39
|
-
"""
|
|
40
|
-
Column-based profile manager. All memories in ONE database.
|
|
41
|
-
Profile = a value in the 'profile' column of the memories table.
|
|
42
|
-
Switching = updating which profile name is active in config.
|
|
43
|
-
"""
|
|
44
|
-
|
|
45
|
-
def __init__(self):
|
|
46
|
-
self.memory_dir = MEMORY_DIR
|
|
47
|
-
self.db_path = DB_PATH
|
|
48
|
-
self.config_file = CONFIG_FILE
|
|
49
|
-
|
|
50
|
-
# Ensure memory directory exists
|
|
51
|
-
self.memory_dir.mkdir(exist_ok=True)
|
|
52
|
-
|
|
53
|
-
# Ensure profile column exists in DB
|
|
54
|
-
self._ensure_profile_column()
|
|
55
|
-
|
|
56
|
-
# Load or create config
|
|
57
|
-
self.config = self._load_config()
|
|
58
|
-
|
|
59
|
-
# Auto-migrate old profile directories if they exist
|
|
60
|
-
self._migrate_old_profiles()
|
|
61
|
-
|
|
62
|
-
def _ensure_profile_column(self):
|
|
63
|
-
"""Add 'profile' column to memories table if it doesn't exist."""
|
|
64
|
-
if not self.db_path.exists():
|
|
65
|
-
return
|
|
66
|
-
|
|
67
|
-
conn = sqlite3.connect(self.db_path)
|
|
68
|
-
cursor = conn.cursor()
|
|
69
|
-
|
|
70
|
-
cursor.execute("PRAGMA table_info(memories)")
|
|
71
|
-
columns = {row[1] for row in cursor.fetchall()}
|
|
72
|
-
|
|
73
|
-
if 'profile' not in columns:
|
|
74
|
-
cursor.execute("ALTER TABLE memories ADD COLUMN profile TEXT DEFAULT 'default'")
|
|
75
|
-
cursor.execute("UPDATE memories SET profile = 'default' WHERE profile IS NULL")
|
|
76
|
-
try:
|
|
77
|
-
cursor.execute("CREATE INDEX IF NOT EXISTS idx_profile ON memories(profile)")
|
|
78
|
-
except sqlite3.OperationalError:
|
|
79
|
-
pass
|
|
80
|
-
conn.commit()
|
|
81
|
-
|
|
82
|
-
conn.close()
|
|
83
|
-
|
|
84
|
-
def _migrate_old_profiles(self):
|
|
85
|
-
"""
|
|
86
|
-
Backward compatibility: migrate old separate-DB profiles into the main DB.
|
|
87
|
-
Old profiles stored in ~/.claude-memory/profiles/<name>/memory.db
|
|
88
|
-
New profiles use a 'profile' column in the main memory.db
|
|
89
|
-
"""
|
|
90
|
-
profiles_dir = MEMORY_DIR / "profiles"
|
|
91
|
-
if not profiles_dir.exists():
|
|
92
|
-
return
|
|
93
|
-
|
|
94
|
-
migrated_any = False
|
|
95
|
-
for profile_dir in profiles_dir.iterdir():
|
|
96
|
-
if not profile_dir.is_dir():
|
|
97
|
-
continue
|
|
98
|
-
|
|
99
|
-
profile_db = profile_dir / "memory.db"
|
|
100
|
-
if not profile_db.exists():
|
|
101
|
-
continue
|
|
102
|
-
|
|
103
|
-
profile_name = profile_dir.name
|
|
104
|
-
marker_file = profile_dir / ".migrated_to_column"
|
|
105
|
-
|
|
106
|
-
# Skip if already migrated
|
|
107
|
-
if marker_file.exists():
|
|
108
|
-
continue
|
|
109
|
-
|
|
110
|
-
# Import memories from old profile DB
|
|
111
|
-
try:
|
|
112
|
-
self._import_from_old_profile(profile_name, profile_db)
|
|
113
|
-
# Mark as migrated
|
|
114
|
-
marker_file.write_text(datetime.now().isoformat())
|
|
115
|
-
migrated_any = True
|
|
116
|
-
except Exception as e:
|
|
117
|
-
print(f" Warning: Could not migrate profile '{profile_name}': {e}", file=sys.stderr)
|
|
118
|
-
|
|
119
|
-
if migrated_any:
|
|
120
|
-
print(" Profile migration complete (old separate DBs -> column-based)", file=sys.stderr)
|
|
121
|
-
|
|
122
|
-
def _import_from_old_profile(self, profile_name, old_db_path):
|
|
123
|
-
"""Import memories from an old separate-DB profile into main DB."""
|
|
124
|
-
if not self.db_path.exists():
|
|
125
|
-
return
|
|
126
|
-
|
|
127
|
-
main_conn = sqlite3.connect(self.db_path)
|
|
128
|
-
main_cursor = main_conn.cursor()
|
|
129
|
-
|
|
130
|
-
old_conn = sqlite3.connect(old_db_path)
|
|
131
|
-
try:
|
|
132
|
-
old_cursor = old_conn.cursor()
|
|
133
|
-
|
|
134
|
-
# Get existing hashes
|
|
135
|
-
main_cursor.execute("SELECT content_hash FROM memories WHERE content_hash IS NOT NULL")
|
|
136
|
-
existing_hashes = {row[0] for row in main_cursor.fetchall()}
|
|
137
|
-
|
|
138
|
-
# Get columns from old DB
|
|
139
|
-
old_cursor.execute("PRAGMA table_info(memories)")
|
|
140
|
-
old_columns = {row[1] for row in old_cursor.fetchall()}
|
|
141
|
-
|
|
142
|
-
# Build SELECT based on available columns
|
|
143
|
-
select_cols = ['content', 'summary', 'project_path', 'project_name', 'tags',
|
|
144
|
-
'category', 'memory_type', 'importance', 'created_at', 'updated_at',
|
|
145
|
-
'content_hash']
|
|
146
|
-
available_cols = [c for c in select_cols if c in old_columns]
|
|
147
|
-
|
|
148
|
-
if 'content' not in available_cols:
|
|
149
|
-
return
|
|
150
|
-
|
|
151
|
-
old_cursor.execute(f"SELECT {', '.join(available_cols)} FROM memories")
|
|
152
|
-
rows = old_cursor.fetchall()
|
|
153
|
-
|
|
154
|
-
imported = 0
|
|
155
|
-
for row in rows:
|
|
156
|
-
row_dict = dict(zip(available_cols, row))
|
|
157
|
-
content = row_dict.get('content', '')
|
|
158
|
-
content_hash = row_dict.get('content_hash')
|
|
159
|
-
|
|
160
|
-
if not content:
|
|
161
|
-
continue
|
|
162
|
-
|
|
163
|
-
# Generate hash if missing
|
|
164
|
-
if not content_hash:
|
|
165
|
-
content_hash = hashlib.sha256(content.encode()).hexdigest()[:32]
|
|
166
|
-
|
|
167
|
-
if content_hash in existing_hashes:
|
|
168
|
-
continue
|
|
169
|
-
|
|
170
|
-
try:
|
|
171
|
-
main_cursor.execute('''
|
|
172
|
-
INSERT INTO memories (content, summary, project_path, project_name, tags,
|
|
173
|
-
category, memory_type, importance, created_at, updated_at,
|
|
174
|
-
content_hash, profile)
|
|
175
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
176
|
-
''', (
|
|
177
|
-
content,
|
|
178
|
-
row_dict.get('summary'),
|
|
179
|
-
row_dict.get('project_path'),
|
|
180
|
-
row_dict.get('project_name'),
|
|
181
|
-
row_dict.get('tags'),
|
|
182
|
-
row_dict.get('category'),
|
|
183
|
-
row_dict.get('memory_type', 'session'),
|
|
184
|
-
row_dict.get('importance', 5),
|
|
185
|
-
row_dict.get('created_at'),
|
|
186
|
-
row_dict.get('updated_at'),
|
|
187
|
-
content_hash,
|
|
188
|
-
profile_name
|
|
189
|
-
))
|
|
190
|
-
imported += 1
|
|
191
|
-
existing_hashes.add(content_hash)
|
|
192
|
-
except sqlite3.IntegrityError:
|
|
193
|
-
pass
|
|
194
|
-
|
|
195
|
-
main_conn.commit()
|
|
196
|
-
finally:
|
|
197
|
-
old_conn.close()
|
|
198
|
-
main_conn.close()
|
|
199
|
-
|
|
200
|
-
if imported > 0:
|
|
201
|
-
# Add profile to config if not present
|
|
202
|
-
config = self._load_config()
|
|
203
|
-
if profile_name not in config.get('profiles', {}):
|
|
204
|
-
config['profiles'][profile_name] = {
|
|
205
|
-
'name': profile_name,
|
|
206
|
-
'description': f'Memory profile: {profile_name} (migrated)',
|
|
207
|
-
'created_at': datetime.now().isoformat(),
|
|
208
|
-
'last_used': None
|
|
209
|
-
}
|
|
210
|
-
self._save_config(config)
|
|
211
|
-
|
|
212
|
-
def _load_config(self):
|
|
213
|
-
"""Load profiles configuration."""
|
|
214
|
-
if self.config_file.exists():
|
|
215
|
-
with open(self.config_file, 'r') as f:
|
|
216
|
-
return json.load(f)
|
|
217
|
-
else:
|
|
218
|
-
config = {
|
|
219
|
-
'profiles': {
|
|
220
|
-
'default': {
|
|
221
|
-
'name': 'default',
|
|
222
|
-
'description': 'Default memory profile',
|
|
223
|
-
'created_at': datetime.now().isoformat(),
|
|
224
|
-
'last_used': datetime.now().isoformat()
|
|
225
|
-
}
|
|
226
|
-
},
|
|
227
|
-
'active_profile': 'default'
|
|
228
|
-
}
|
|
229
|
-
self._save_config(config)
|
|
230
|
-
return config
|
|
231
|
-
|
|
232
|
-
def _save_config(self, config=None):
|
|
233
|
-
"""Save profiles configuration."""
|
|
234
|
-
if config is None:
|
|
235
|
-
config = self.config
|
|
236
|
-
|
|
237
|
-
with open(self.config_file, 'w') as f:
|
|
238
|
-
json.dump(config, f, indent=2)
|
|
239
|
-
|
|
240
|
-
def _validate_profile_name(self, profile_name):
|
|
241
|
-
"""Validate profile name for security."""
|
|
242
|
-
if not profile_name:
|
|
243
|
-
raise ValueError("Profile name cannot be empty")
|
|
244
|
-
|
|
245
|
-
if not re.match(r'^[a-zA-Z0-9_-]+$', profile_name):
|
|
246
|
-
raise ValueError("Invalid profile name. Use only letters, numbers, dash, underscore.")
|
|
247
|
-
|
|
248
|
-
if len(profile_name) > 50:
|
|
249
|
-
raise ValueError("Profile name too long (max 50 characters)")
|
|
250
|
-
|
|
251
|
-
if profile_name in ['.', '..']:
|
|
252
|
-
raise ValueError(f"Reserved profile name: {profile_name}")
|
|
253
|
-
|
|
254
|
-
def _get_memory_count(self, profile_name):
|
|
255
|
-
"""Get memory count for a specific profile."""
|
|
256
|
-
if not self.db_path.exists():
|
|
257
|
-
return 0
|
|
258
|
-
|
|
259
|
-
conn = sqlite3.connect(self.db_path)
|
|
260
|
-
try:
|
|
261
|
-
cursor = conn.cursor()
|
|
262
|
-
cursor.execute("SELECT COUNT(*) FROM memories WHERE profile = ?", (profile_name,))
|
|
263
|
-
count = cursor.fetchone()[0]
|
|
264
|
-
finally:
|
|
265
|
-
conn.close()
|
|
266
|
-
return count
|
|
267
|
-
|
|
268
|
-
def get_active_profile(self) -> str:
|
|
269
|
-
"""Get the currently active profile name."""
|
|
270
|
-
return self.config.get('active_profile', 'default')
|
|
271
|
-
|
|
272
|
-
def list_profiles(self) -> list:
|
|
273
|
-
"""List all available profiles with memory counts."""
|
|
274
|
-
print("\n" + "=" * 60)
|
|
275
|
-
print("AVAILABLE MEMORY PROFILES")
|
|
276
|
-
print("=" * 60)
|
|
277
|
-
|
|
278
|
-
active = self.config.get('active_profile', 'default')
|
|
279
|
-
|
|
280
|
-
if not self.config.get('profiles'):
|
|
281
|
-
print("\n No profiles found. Create one with: create <name>")
|
|
282
|
-
return
|
|
283
|
-
|
|
284
|
-
print(f"\n{'Profile':20s} {'Description':30s} {'Memories':10s} {'Status':10s}")
|
|
285
|
-
print("-" * 75)
|
|
286
|
-
|
|
287
|
-
for name, info in self.config['profiles'].items():
|
|
288
|
-
status = "ACTIVE" if name == active else ""
|
|
289
|
-
desc = info.get('description', 'No description')[:30]
|
|
290
|
-
marker = "-> " if name == active else " "
|
|
291
|
-
count = self._get_memory_count(name)
|
|
292
|
-
print(f"{marker}{name:17s} {desc:30s} {count:<10d} {status:10s}")
|
|
293
|
-
|
|
294
|
-
print(f"\nTotal profiles: {len(self.config['profiles'])}")
|
|
295
|
-
print(f"Active profile: {active}")
|
|
296
|
-
|
|
297
|
-
def create_profile(self, name, description=None, from_current=False):
|
|
298
|
-
"""
|
|
299
|
-
Create a new profile.
|
|
300
|
-
Column-based: just adds to config. If from_current, copies memories.
|
|
301
|
-
"""
|
|
302
|
-
print(f"\nCreating profile: {name}")
|
|
303
|
-
|
|
304
|
-
self._validate_profile_name(name)
|
|
305
|
-
|
|
306
|
-
if name in self.config['profiles']:
|
|
307
|
-
print(f"Error: Profile '{name}' already exists")
|
|
308
|
-
return False
|
|
309
|
-
|
|
310
|
-
if from_current:
|
|
311
|
-
# Copy current profile's memories to new profile
|
|
312
|
-
active = self.config.get('active_profile', 'default')
|
|
313
|
-
if self.db_path.exists():
|
|
314
|
-
conn = sqlite3.connect(self.db_path)
|
|
315
|
-
cursor = conn.cursor()
|
|
316
|
-
cursor.execute("""
|
|
317
|
-
INSERT INTO memories (content, summary, project_path, project_name, tags,
|
|
318
|
-
category, parent_id, tree_path, depth, memory_type,
|
|
319
|
-
importance, created_at, updated_at, last_accessed,
|
|
320
|
-
access_count, content_hash, cluster_id, profile)
|
|
321
|
-
SELECT content, summary, project_path, project_name, tags,
|
|
322
|
-
category, parent_id, tree_path, depth, memory_type,
|
|
323
|
-
importance, created_at, updated_at, last_accessed,
|
|
324
|
-
access_count, NULL, cluster_id, ?
|
|
325
|
-
FROM memories WHERE profile = ?
|
|
326
|
-
""", (name, active))
|
|
327
|
-
copied = cursor.rowcount
|
|
328
|
-
conn.commit()
|
|
329
|
-
|
|
330
|
-
# Generate new content hashes for copied memories
|
|
331
|
-
cursor.execute("SELECT id, content FROM memories WHERE profile = ? AND content_hash IS NULL", (name,))
|
|
332
|
-
for row in cursor.fetchall():
|
|
333
|
-
new_hash = hashlib.sha256(row[1].encode()).hexdigest()[:32]
|
|
334
|
-
try:
|
|
335
|
-
cursor.execute("UPDATE memories SET content_hash = ? WHERE id = ?", (new_hash + f"_{name}", row[0]))
|
|
336
|
-
except sqlite3.IntegrityError:
|
|
337
|
-
pass
|
|
338
|
-
conn.commit()
|
|
339
|
-
conn.close()
|
|
340
|
-
|
|
341
|
-
print(f" Copied {copied} memories from '{active}' profile")
|
|
342
|
-
else:
|
|
343
|
-
print(" No database found, creating empty profile")
|
|
344
|
-
else:
|
|
345
|
-
print(f" Empty profile created (memories will be saved here when active)")
|
|
346
|
-
|
|
347
|
-
# Add to config
|
|
348
|
-
self.config['profiles'][name] = {
|
|
349
|
-
'name': name,
|
|
350
|
-
'description': description or f'Memory profile: {name}',
|
|
351
|
-
'created_at': datetime.now().isoformat(),
|
|
352
|
-
'last_used': None,
|
|
353
|
-
'created_from': 'current' if from_current else 'empty'
|
|
354
|
-
}
|
|
355
|
-
self._save_config()
|
|
356
|
-
|
|
357
|
-
print(f"Profile '{name}' created successfully")
|
|
358
|
-
return True
|
|
359
|
-
|
|
360
|
-
def switch_profile(self, name):
|
|
361
|
-
"""
|
|
362
|
-
Switch to a different profile.
|
|
363
|
-
Column-based: just updates the active_profile in config. Instant. No data risk.
|
|
364
|
-
"""
|
|
365
|
-
if name not in self.config['profiles']:
|
|
366
|
-
print(f"Error: Profile '{name}' not found")
|
|
367
|
-
print(f" Available: {', '.join(self.config['profiles'].keys())}")
|
|
368
|
-
return False
|
|
369
|
-
|
|
370
|
-
current = self.config.get('active_profile', 'default')
|
|
371
|
-
|
|
372
|
-
if current == name:
|
|
373
|
-
print(f"Already using profile: {name}")
|
|
374
|
-
return True
|
|
375
|
-
|
|
376
|
-
# Column-based switch: just update config
|
|
377
|
-
self.config['active_profile'] = name
|
|
378
|
-
self.config['profiles'][name]['last_used'] = datetime.now().isoformat()
|
|
379
|
-
self._save_config()
|
|
380
|
-
|
|
381
|
-
count = self._get_memory_count(name)
|
|
382
|
-
print(f"\nSwitched to profile: {name} ({count} memories)")
|
|
383
|
-
print(f"Previous profile: {current}")
|
|
384
|
-
return True
|
|
385
|
-
|
|
386
|
-
def delete_profile(self, name, force=False):
|
|
387
|
-
"""Delete a profile. Moves memories to 'default' or deletes them."""
|
|
388
|
-
if name not in self.config['profiles']:
|
|
389
|
-
print(f"Error: Profile '{name}' not found")
|
|
390
|
-
return False
|
|
391
|
-
|
|
392
|
-
if name == 'default':
|
|
393
|
-
print(f"Error: Cannot delete 'default' profile")
|
|
394
|
-
return False
|
|
395
|
-
|
|
396
|
-
if self.config.get('active_profile') == name:
|
|
397
|
-
print(f"Error: Cannot delete active profile")
|
|
398
|
-
print(f" Switch to another profile first: slm profile switch default")
|
|
399
|
-
return False
|
|
400
|
-
|
|
401
|
-
count = self._get_memory_count(name)
|
|
402
|
-
|
|
403
|
-
if not force:
|
|
404
|
-
print(f"\nWARNING: This will delete profile '{name}' ({count} memories)")
|
|
405
|
-
print(f"Memories will be moved to 'default' profile before deletion.")
|
|
406
|
-
response = input(f"Type profile name '{name}' to confirm: ")
|
|
407
|
-
|
|
408
|
-
if response != name:
|
|
409
|
-
print("Cancelled.")
|
|
410
|
-
return False
|
|
411
|
-
|
|
412
|
-
# Move memories to default profile
|
|
413
|
-
if self.db_path.exists() and count > 0:
|
|
414
|
-
conn = sqlite3.connect(self.db_path)
|
|
415
|
-
try:
|
|
416
|
-
cursor = conn.cursor()
|
|
417
|
-
cursor.execute("UPDATE memories SET profile = 'default' WHERE profile = ?", (name,))
|
|
418
|
-
moved = cursor.rowcount
|
|
419
|
-
conn.commit()
|
|
420
|
-
finally:
|
|
421
|
-
conn.close()
|
|
422
|
-
print(f" Moved {moved} memories to 'default' profile")
|
|
423
|
-
|
|
424
|
-
# Remove from config
|
|
425
|
-
del self.config['profiles'][name]
|
|
426
|
-
self._save_config()
|
|
427
|
-
|
|
428
|
-
print(f"Profile '{name}' deleted")
|
|
429
|
-
return True
|
|
430
|
-
|
|
431
|
-
def show_current(self):
|
|
432
|
-
"""Show current active profile with stats."""
|
|
433
|
-
active = self.config.get('active_profile', 'default')
|
|
434
|
-
|
|
435
|
-
if active in self.config['profiles']:
|
|
436
|
-
info = self.config['profiles'][active]
|
|
437
|
-
|
|
438
|
-
print("\n" + "=" * 60)
|
|
439
|
-
print("CURRENT ACTIVE PROFILE")
|
|
440
|
-
print("=" * 60)
|
|
441
|
-
print(f"\nProfile: {active}")
|
|
442
|
-
print(f"Description: {info.get('description', 'N/A')}")
|
|
443
|
-
print(f"Created: {info.get('created_at', 'N/A')}")
|
|
444
|
-
print(f"Last used: {info.get('last_used', 'N/A')}")
|
|
445
|
-
|
|
446
|
-
count = self._get_memory_count(active)
|
|
447
|
-
print(f"\nMemories in this profile: {count}")
|
|
448
|
-
|
|
449
|
-
# Show total memories across all profiles
|
|
450
|
-
if self.db_path.exists():
|
|
451
|
-
conn = sqlite3.connect(self.db_path)
|
|
452
|
-
try:
|
|
453
|
-
cursor = conn.cursor()
|
|
454
|
-
cursor.execute("SELECT COUNT(*) FROM memories")
|
|
455
|
-
total = cursor.fetchone()[0]
|
|
456
|
-
finally:
|
|
457
|
-
conn.close()
|
|
458
|
-
print(f"Total memories (all profiles): {total}")
|
|
459
|
-
else:
|
|
460
|
-
print(f"Warning: Current profile '{active}' not found in config")
|
|
461
|
-
print("Resetting to 'default' profile...")
|
|
462
|
-
self.config['active_profile'] = 'default'
|
|
463
|
-
self._save_config()
|
|
464
|
-
|
|
465
|
-
def rename_profile(self, old_name, new_name):
|
|
466
|
-
"""Rename a profile. Updates the column value in all memories."""
|
|
467
|
-
if old_name not in self.config['profiles']:
|
|
468
|
-
print(f"Error: Profile '{old_name}' not found")
|
|
469
|
-
return False
|
|
470
|
-
|
|
471
|
-
if new_name in self.config['profiles']:
|
|
472
|
-
print(f"Error: Profile '{new_name}' already exists")
|
|
473
|
-
return False
|
|
474
|
-
|
|
475
|
-
if old_name == 'default':
|
|
476
|
-
print(f"Error: Cannot rename 'default' profile")
|
|
477
|
-
return False
|
|
478
|
-
|
|
479
|
-
self._validate_profile_name(new_name)
|
|
480
|
-
|
|
481
|
-
# Update profile column in all memories
|
|
482
|
-
if self.db_path.exists():
|
|
483
|
-
conn = sqlite3.connect(self.db_path)
|
|
484
|
-
try:
|
|
485
|
-
cursor = conn.cursor()
|
|
486
|
-
cursor.execute("UPDATE memories SET profile = ? WHERE profile = ?", (new_name, old_name))
|
|
487
|
-
updated = cursor.rowcount
|
|
488
|
-
conn.commit()
|
|
489
|
-
finally:
|
|
490
|
-
conn.close()
|
|
491
|
-
print(f" Updated {updated} memories")
|
|
492
|
-
|
|
493
|
-
# Update config
|
|
494
|
-
self.config['profiles'][new_name] = self.config['profiles'][old_name]
|
|
495
|
-
self.config['profiles'][new_name]['name'] = new_name
|
|
496
|
-
del self.config['profiles'][old_name]
|
|
497
|
-
|
|
498
|
-
if self.config.get('active_profile') == old_name:
|
|
499
|
-
self.config['active_profile'] = new_name
|
|
500
|
-
|
|
501
|
-
self._save_config()
|
|
502
|
-
|
|
503
|
-
print(f"Profile renamed: '{old_name}' -> '{new_name}'")
|
|
504
|
-
return True
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
def main():
|
|
509
|
-
parser = argparse.ArgumentParser(
|
|
510
|
-
description='SuperLocalMemory V2 - Profile Management (Column-Based)',
|
|
511
|
-
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
512
|
-
epilog='''
|
|
513
|
-
Examples:
|
|
514
|
-
# List all profiles
|
|
515
|
-
python memory-profiles.py list
|
|
516
|
-
|
|
517
|
-
# Show current profile
|
|
518
|
-
python memory-profiles.py current
|
|
519
|
-
|
|
520
|
-
# Create new empty profile
|
|
521
|
-
python memory-profiles.py create work --description "Work projects"
|
|
522
|
-
|
|
523
|
-
# Create profile from current memories
|
|
524
|
-
python memory-profiles.py create personal --from-current
|
|
525
|
-
|
|
526
|
-
# Switch to different profile (instant, no restart needed)
|
|
527
|
-
python memory-profiles.py switch work
|
|
528
|
-
|
|
529
|
-
# Delete a profile (memories moved to default)
|
|
530
|
-
python memory-profiles.py delete old-profile
|
|
531
|
-
|
|
532
|
-
# Rename profile
|
|
533
|
-
python memory-profiles.py rename old-name new-name
|
|
534
|
-
|
|
535
|
-
Architecture (v2.4.0):
|
|
536
|
-
All profiles share ONE database (memory.db).
|
|
537
|
-
Each memory has a 'profile' column.
|
|
538
|
-
Switching profiles = changing config. Instant. Safe.
|
|
539
|
-
'''
|
|
540
|
-
)
|
|
541
|
-
|
|
542
|
-
parser.add_argument('command',
|
|
543
|
-
choices=['list', 'current', 'create', 'switch', 'delete', 'rename'],
|
|
544
|
-
help='Profile command')
|
|
545
|
-
parser.add_argument('name', nargs='?', help='Profile name')
|
|
546
|
-
parser.add_argument('name2', nargs='?', help='Second name (for rename)')
|
|
547
|
-
parser.add_argument('--description', help='Profile description')
|
|
548
|
-
parser.add_argument('--from-current', action='store_true',
|
|
549
|
-
help='Create from current profile memories')
|
|
550
|
-
parser.add_argument('--force', action='store_true',
|
|
551
|
-
help='Force operation without confirmation')
|
|
552
|
-
|
|
553
|
-
args = parser.parse_args()
|
|
554
|
-
|
|
555
|
-
manager = ProfileManager()
|
|
556
|
-
|
|
557
|
-
if args.command == 'list':
|
|
558
|
-
manager.list_profiles()
|
|
559
|
-
|
|
560
|
-
elif args.command == 'current':
|
|
561
|
-
manager.show_current()
|
|
562
|
-
|
|
563
|
-
elif args.command == 'create':
|
|
564
|
-
if not args.name:
|
|
565
|
-
print("Error: Profile name required")
|
|
566
|
-
print(" Usage: python memory-profiles.py create <name>")
|
|
567
|
-
sys.exit(1)
|
|
568
|
-
|
|
569
|
-
manager.create_profile(args.name, args.description, args.from_current)
|
|
570
|
-
|
|
571
|
-
elif args.command == 'switch':
|
|
572
|
-
if not args.name:
|
|
573
|
-
print("Error: Profile name required")
|
|
574
|
-
sys.exit(1)
|
|
575
|
-
|
|
576
|
-
manager.switch_profile(args.name)
|
|
577
|
-
|
|
578
|
-
elif args.command == 'delete':
|
|
579
|
-
if not args.name:
|
|
580
|
-
print("Error: Profile name required")
|
|
581
|
-
sys.exit(1)
|
|
582
|
-
|
|
583
|
-
manager.delete_profile(args.name, args.force)
|
|
584
|
-
|
|
585
|
-
elif args.command == 'rename':
|
|
586
|
-
if not args.name or not args.name2:
|
|
587
|
-
print("Error: Both old and new names required")
|
|
588
|
-
print(" Usage: python memory-profiles.py rename <old> <new>")
|
|
589
|
-
sys.exit(1)
|
|
590
|
-
|
|
591
|
-
manager.rename_profile(args.name, args.name2)
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
if __name__ == '__main__':
|
|
595
|
-
main()
|