superlocalmemory 4.1.13 → 4.1.15
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/.claude-plugin/marketplace.json +2 -2
- package/CHANGELOG.md +103 -0
- package/README.md +3 -3
- package/package.json +1 -8
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/CLAUDE.md +3 -3
- package/plugin/agents/slm-governance-advisor.md +1 -1
- package/plugin/agents/slm-loop-runner.md +1 -1
- package/plugin/agents/slm-memory-advisor.md +1 -1
- package/plugin/agents/slm-optimize-advisor.md +1 -1
- package/plugin/requirements.txt +1 -1
- package/plugin/skills/slm-cache/SKILL.md +1 -1
- package/plugin/skills/slm-compress/SKILL.md +1 -1
- package/plugin/skills/slm-governance/SKILL.md +1 -1
- package/plugin/skills/slm-graph/SKILL.md +1 -1
- package/plugin/skills/slm-loop/SKILL.md +1 -1
- package/plugin/skills/slm-mesh/SKILL.md +1 -1
- package/plugin/skills/slm-profile/SKILL.md +1 -1
- package/plugin/skills/slm-recall/SKILL.md +1 -1
- package/plugin/skills/slm-remember/SKILL.md +1 -1
- package/plugin/skills/slm-scope/SKILL.md +1 -1
- package/plugin/skills/slm-session/SKILL.md +1 -1
- package/plugin/skills/slm-status/SKILL.md +1 -1
- package/plugin-src/agents/slm-memory-advisor.md +1 -1
- package/plugin-src/agents/slm-optimize-advisor.md +1 -1
- package/plugin-src/rules/AGENTS.md +1 -1
- package/plugin-src/skills/slm-cache/SKILL.md +1 -1
- package/plugin-src/skills/slm-compress/SKILL.md +1 -1
- package/plugin-src/skills/slm-governance/SKILL.md +1 -1
- package/plugin-src/skills/slm-graph/SKILL.md +1 -1
- package/plugin-src/skills/slm-loop/SKILL.md +1 -1
- package/plugin-src/skills/slm-mesh/SKILL.md +1 -1
- package/plugin-src/skills/slm-profile/SKILL.md +1 -1
- package/plugin-src/skills/slm-recall/SKILL.md +1 -1
- package/plugin-src/skills/slm-remember/SKILL.md +1 -1
- package/plugin-src/skills/slm-scope/SKILL.md +1 -1
- package/plugin-src/skills/slm-session/SKILL.md +1 -1
- package/plugin-src/skills/slm-status/SKILL.md +1 -1
- package/scripts/postinstall.js +71 -2
- package/pyproject.toml +0 -250
- package/src/superlocalmemory/__init__.py +0 -82
- package/src/superlocalmemory/access/__init__.py +0 -3
- package/src/superlocalmemory/access/rbac.py +0 -575
- package/src/superlocalmemory/attribution/__init__.py +0 -9
- package/src/superlocalmemory/attribution/signer.py +0 -173
- package/src/superlocalmemory/attribution/watermark.py +0 -189
- package/src/superlocalmemory/brain/__init__.py +0 -5
- package/src/superlocalmemory/brain/truth.py +0 -418
- package/src/superlocalmemory/cli/__init__.py +0 -5
- package/src/superlocalmemory/cli/__main__.py +0 -17
- package/src/superlocalmemory/cli/_lazy_init.py +0 -115
- package/src/superlocalmemory/cli/cache_cmd.py +0 -198
- package/src/superlocalmemory/cli/commands.py +0 -4710
- package/src/superlocalmemory/cli/compress_cmd.py +0 -151
- package/src/superlocalmemory/cli/context_commands.py +0 -193
- package/src/superlocalmemory/cli/daemon.py +0 -909
- package/src/superlocalmemory/cli/db_migrate.py +0 -150
- package/src/superlocalmemory/cli/diagnostics_cmd.py +0 -101
- package/src/superlocalmemory/cli/escape_hatch.py +0 -220
- package/src/superlocalmemory/cli/evidence_cmd.py +0 -103
- package/src/superlocalmemory/cli/gdpr_cmd.py +0 -792
- package/src/superlocalmemory/cli/gdpr_io.py +0 -109
- package/src/superlocalmemory/cli/help_cmd.py +0 -197
- package/src/superlocalmemory/cli/host_upgrades.py +0 -189
- package/src/superlocalmemory/cli/ingest_cmd.py +0 -327
- package/src/superlocalmemory/cli/json_output.py +0 -81
- package/src/superlocalmemory/cli/loop_cmd.py +0 -187
- package/src/superlocalmemory/cli/main.py +0 -1145
- package/src/superlocalmemory/cli/mesh_cmd.py +0 -38
- package/src/superlocalmemory/cli/migrate_cmd.py +0 -55
- package/src/superlocalmemory/cli/ops_cmd.py +0 -281
- package/src/superlocalmemory/cli/optimize_cmd.py +0 -179
- package/src/superlocalmemory/cli/optimize_constants.py +0 -31
- package/src/superlocalmemory/cli/pending_store.py +0 -296
- package/src/superlocalmemory/cli/proxy_cmd.py +0 -108
- package/src/superlocalmemory/cli/scale_engine_cmd.py +0 -56
- package/src/superlocalmemory/cli/service_installer.py +0 -373
- package/src/superlocalmemory/cli/setup_wizard.py +0 -1162
- package/src/superlocalmemory/cli/summary_cmd.py +0 -215
- package/src/superlocalmemory/cli/version_banner.py +0 -202
- package/src/superlocalmemory/cli/wizard_v3426_options.py +0 -129
- package/src/superlocalmemory/code_graph/__init__.py +0 -46
- package/src/superlocalmemory/code_graph/blast_radius.py +0 -177
- package/src/superlocalmemory/code_graph/bridge/__init__.py +0 -36
- package/src/superlocalmemory/code_graph/bridge/entity_resolver.py +0 -490
- package/src/superlocalmemory/code_graph/bridge/event_listeners.py +0 -206
- package/src/superlocalmemory/code_graph/bridge/fact_enricher.py +0 -159
- package/src/superlocalmemory/code_graph/bridge/hebbian_linker.py +0 -170
- package/src/superlocalmemory/code_graph/bridge/maintenance.py +0 -220
- package/src/superlocalmemory/code_graph/bridge/temporal_checker.py +0 -152
- package/src/superlocalmemory/code_graph/changes.py +0 -363
- package/src/superlocalmemory/code_graph/communities.py +0 -299
- package/src/superlocalmemory/code_graph/config.py +0 -154
- package/src/superlocalmemory/code_graph/database.py +0 -526
- package/src/superlocalmemory/code_graph/extractors/__init__.py +0 -95
- package/src/superlocalmemory/code_graph/extractors/python.py +0 -413
- package/src/superlocalmemory/code_graph/extractors/typescript.py +0 -556
- package/src/superlocalmemory/code_graph/flows.py +0 -350
- package/src/superlocalmemory/code_graph/git_hooks.py +0 -226
- package/src/superlocalmemory/code_graph/graph_engine.py +0 -295
- package/src/superlocalmemory/code_graph/graph_store.py +0 -335
- package/src/superlocalmemory/code_graph/incremental.py +0 -200
- package/src/superlocalmemory/code_graph/models.py +0 -130
- package/src/superlocalmemory/code_graph/parser.py +0 -687
- package/src/superlocalmemory/code_graph/resolver.py +0 -321
- package/src/superlocalmemory/code_graph/search.py +0 -460
- package/src/superlocalmemory/code_graph/service.py +0 -95
- package/src/superlocalmemory/code_graph/watcher.py +0 -207
- package/src/superlocalmemory/compliance/__init__.py +0 -0
- package/src/superlocalmemory/compliance/abac.py +0 -204
- package/src/superlocalmemory/compliance/audit.py +0 -385
- package/src/superlocalmemory/compliance/eu_ai_act.py +0 -101
- package/src/superlocalmemory/compliance/gdpr.py +0 -1479
- package/src/superlocalmemory/compliance/lifecycle.py +0 -158
- package/src/superlocalmemory/compliance/retention.py +0 -415
- package/src/superlocalmemory/compliance/scheduler.py +0 -217
- package/src/superlocalmemory/contracts/__init__.py +0 -1
- package/src/superlocalmemory/contracts/schemas/agent-experience-v1.schema.json +0 -92
- package/src/superlocalmemory/contracts/schemas/agent-integration-contract-v2.schema.json +0 -46
- package/src/superlocalmemory/contracts/schemas/cognitive-turn-receipt-v1.schema.json +0 -59
- package/src/superlocalmemory/contracts/v402.py +0 -62
- package/src/superlocalmemory/core/__init__.py +0 -0
- package/src/superlocalmemory/core/actor_context.py +0 -166
- package/src/superlocalmemory/core/admission.py +0 -769
- package/src/superlocalmemory/core/backend_orchestrator.py +0 -637
- package/src/superlocalmemory/core/block_hygiene.py +0 -147
- package/src/superlocalmemory/core/community_summary.py +0 -267
- package/src/superlocalmemory/core/component_healer.py +0 -144
- package/src/superlocalmemory/core/component_registry.py +0 -514
- package/src/superlocalmemory/core/config.py +0 -2204
- package/src/superlocalmemory/core/consolidation_engine.py +0 -983
- package/src/superlocalmemory/core/context_cache.py +0 -574
- package/src/superlocalmemory/core/derivation_lineage.py +0 -246
- package/src/superlocalmemory/core/embedding_worker.py +0 -208
- package/src/superlocalmemory/core/embeddings.py +0 -1052
- package/src/superlocalmemory/core/engine.py +0 -1395
- package/src/superlocalmemory/core/engine_capabilities.py +0 -24
- package/src/superlocalmemory/core/engine_ingestion.py +0 -983
- package/src/superlocalmemory/core/engine_lock.py +0 -75
- package/src/superlocalmemory/core/engine_wiring.py +0 -776
- package/src/superlocalmemory/core/entity_community.py +0 -178
- package/src/superlocalmemory/core/error_envelope.py +0 -60
- package/src/superlocalmemory/core/evidence_bundle.py +0 -528
- package/src/superlocalmemory/core/fact_consolidator.py +0 -812
- package/src/superlocalmemory/core/file_lock.py +0 -92
- package/src/superlocalmemory/core/graph_analyzer.py +0 -456
- package/src/superlocalmemory/core/graph_metrics.py +0 -597
- package/src/superlocalmemory/core/graph_pruner.py +0 -939
- package/src/superlocalmemory/core/health_monitor.py +0 -338
- package/src/superlocalmemory/core/hooks.py +0 -65
- package/src/superlocalmemory/core/ingest_gate.py +0 -133
- package/src/superlocalmemory/core/ingest_policy.py +0 -38
- package/src/superlocalmemory/core/ingestion_command.py +0 -1042
- package/src/superlocalmemory/core/injection.py +0 -434
- package/src/superlocalmemory/core/install_detector.py +0 -131
- package/src/superlocalmemory/core/key_expander.py +0 -138
- package/src/superlocalmemory/core/lifecycle_state.py +0 -153
- package/src/superlocalmemory/core/maintenance.py +0 -777
- package/src/superlocalmemory/core/maintenance_scheduler.py +0 -507
- package/src/superlocalmemory/core/materialization_control.py +0 -20
- package/src/superlocalmemory/core/mcp_embedder_proxy.py +0 -89
- package/src/superlocalmemory/core/memory_health.py +0 -266
- package/src/superlocalmemory/core/mode_capability.py +0 -111
- package/src/superlocalmemory/core/modes.py +0 -168
- package/src/superlocalmemory/core/mutations.py +0 -688
- package/src/superlocalmemory/core/ollama_embedder.py +0 -266
- package/src/superlocalmemory/core/ollama_validator.py +0 -315
- package/src/superlocalmemory/core/operation_policy.py +0 -92
- package/src/superlocalmemory/core/operation_policy_registry.py +0 -542
- package/src/superlocalmemory/core/operation_request.py +0 -127
- package/src/superlocalmemory/core/ops_remediation.py +0 -542
- package/src/superlocalmemory/core/pii.py +0 -105
- package/src/superlocalmemory/core/platform_utils.py +0 -138
- package/src/superlocalmemory/core/profiles.py +0 -234
- package/src/superlocalmemory/core/progressive_abstraction.py +0 -208
- package/src/superlocalmemory/core/projection_drain.py +0 -380
- package/src/superlocalmemory/core/queue_consumer.py +0 -168
- package/src/superlocalmemory/core/ram_lock.py +0 -160
- package/src/superlocalmemory/core/rate_limit.py +0 -151
- package/src/superlocalmemory/core/recall_gate.py +0 -95
- package/src/superlocalmemory/core/recall_pipeline.py +0 -1337
- package/src/superlocalmemory/core/recall_queue.py +0 -377
- package/src/superlocalmemory/core/recall_worker.py +0 -414
- package/src/superlocalmemory/core/registry.py +0 -121
- package/src/superlocalmemory/core/remember_admission.py +0 -161
- package/src/superlocalmemory/core/remember_runtime.py +0 -1190
- package/src/superlocalmemory/core/remote_mode.py +0 -214
- package/src/superlocalmemory/core/reranker_worker.py +0 -338
- package/src/superlocalmemory/core/safe_fs.py +0 -108
- package/src/superlocalmemory/core/scale_autopromote.py +0 -196
- package/src/superlocalmemory/core/scale_engine.py +0 -915
- package/src/superlocalmemory/core/score_contract.py +0 -82
- package/src/superlocalmemory/core/security_primitives.py +0 -672
- package/src/superlocalmemory/core/session_identity.py +0 -98
- package/src/superlocalmemory/core/shadow_router.py +0 -319
- package/src/superlocalmemory/core/slm_disabled.py +0 -85
- package/src/superlocalmemory/core/status_contract.py +0 -108
- package/src/superlocalmemory/core/store_pipeline.py +0 -1404
- package/src/superlocalmemory/core/summarizer.py +0 -200
- package/src/superlocalmemory/core/tier_manager.py +0 -461
- package/src/superlocalmemory/core/topic_signature.py +0 -156
- package/src/superlocalmemory/core/transactions/__init__.py +0 -78
- package/src/superlocalmemory/core/transactions/concrete_owners.py +0 -604
- package/src/superlocalmemory/core/transactions/erasure.py +0 -825
- package/src/superlocalmemory/core/transactions/manifest.py +0 -255
- package/src/superlocalmemory/core/transactions/manifest_key.py +0 -155
- package/src/superlocalmemory/core/transactions/obligations.py +0 -272
- package/src/superlocalmemory/core/transactions/owners.py +0 -114
- package/src/superlocalmemory/core/transactions/reconciler.py +0 -285
- package/src/superlocalmemory/core/transactions/service.py +0 -330
- package/src/superlocalmemory/core/worker_pool.py +0 -377
- package/src/superlocalmemory/core/working_memory.py +0 -288
- package/src/superlocalmemory/dynamics/__init__.py +0 -0
- package/src/superlocalmemory/dynamics/activation_guided_quantization.py +0 -374
- package/src/superlocalmemory/dynamics/eap_scheduler.py +0 -294
- package/src/superlocalmemory/dynamics/ebbinghaus_langevin_coupling.py +0 -171
- package/src/superlocalmemory/dynamics/fisher_langevin_coupling.py +0 -227
- package/src/superlocalmemory/encoding/__init__.py +0 -0
- package/src/superlocalmemory/encoding/auto_linker.py +0 -308
- package/src/superlocalmemory/encoding/cognitive_consolidator.py +0 -899
- package/src/superlocalmemory/encoding/consolidator.py +0 -472
- package/src/superlocalmemory/encoding/context_generator.py +0 -175
- package/src/superlocalmemory/encoding/emotional.py +0 -189
- package/src/superlocalmemory/encoding/entity_reflexion.py +0 -200
- package/src/superlocalmemory/encoding/entity_resolver.py +0 -687
- package/src/superlocalmemory/encoding/entropy_gate.py +0 -101
- package/src/superlocalmemory/encoding/fact_extractor.py +0 -877
- package/src/superlocalmemory/encoding/foresight.py +0 -93
- package/src/superlocalmemory/encoding/graph_builder.py +0 -346
- package/src/superlocalmemory/encoding/observation_builder.py +0 -177
- package/src/superlocalmemory/encoding/prospective_markers.py +0 -262
- package/src/superlocalmemory/encoding/scene_builder.py +0 -410
- package/src/superlocalmemory/encoding/signal_inference.py +0 -90
- package/src/superlocalmemory/encoding/temporal_parser.py +0 -432
- package/src/superlocalmemory/encoding/temporal_validator.py +0 -572
- package/src/superlocalmemory/encoding/type_router.py +0 -237
- package/src/superlocalmemory/evaluation/__init__.py +0 -13
- package/src/superlocalmemory/evaluation/calibration.py +0 -308
- package/src/superlocalmemory/evolution/__init__.py +0 -29
- package/src/superlocalmemory/evolution/blind_verifier.py +0 -122
- package/src/superlocalmemory/evolution/budget.py +0 -356
- package/src/superlocalmemory/evolution/evolution_store.py +0 -619
- package/src/superlocalmemory/evolution/llm_dispatch.py +0 -559
- package/src/superlocalmemory/evolution/model_selection.py +0 -175
- package/src/superlocalmemory/evolution/mutation_generator.py +0 -226
- package/src/superlocalmemory/evolution/skill_activator.py +0 -270
- package/src/superlocalmemory/evolution/skill_evolver.py +0 -928
- package/src/superlocalmemory/evolution/triggers.py +0 -376
- package/src/superlocalmemory/evolution/types.py +0 -114
- package/src/superlocalmemory/graph/__init__.py +0 -9
- package/src/superlocalmemory/graph/cozo_adjacency.py +0 -122
- package/src/superlocalmemory/graph/cozo_backend.py +0 -751
- package/src/superlocalmemory/hooks/__init__.py +0 -3
- package/src/superlocalmemory/hooks/_outcome_common.py +0 -523
- package/src/superlocalmemory/hooks/adapter_base.py +0 -347
- package/src/superlocalmemory/hooks/antigravity_adapter.py +0 -171
- package/src/superlocalmemory/hooks/auto_capture.py +0 -133
- package/src/superlocalmemory/hooks/auto_invoker.py +0 -521
- package/src/superlocalmemory/hooks/auto_parameterize.py +0 -147
- package/src/superlocalmemory/hooks/auto_recall.py +0 -191
- package/src/superlocalmemory/hooks/auto_recall_hook.py +0 -251
- package/src/superlocalmemory/hooks/before_web_hook.py +0 -131
- package/src/superlocalmemory/hooks/claude_code_hooks.py +0 -637
- package/src/superlocalmemory/hooks/codex_assets.py +0 -251
- package/src/superlocalmemory/hooks/codex_hooks.py +0 -186
- package/src/superlocalmemory/hooks/context_payload.py +0 -311
- package/src/superlocalmemory/hooks/copilot_adapter.py +0 -208
- package/src/superlocalmemory/hooks/cross_platform_connector.py +0 -90
- package/src/superlocalmemory/hooks/cursor_adapter.py +0 -173
- package/src/superlocalmemory/hooks/hook_daemon.py +0 -295
- package/src/superlocalmemory/hooks/hook_handlers.py +0 -822
- package/src/superlocalmemory/hooks/ide_connector.py +0 -246
- package/src/superlocalmemory/hooks/memory_protocol.py +0 -158
- package/src/superlocalmemory/hooks/portable_kit.py +0 -755
- package/src/superlocalmemory/hooks/post_tool_async_hook.py +0 -183
- package/src/superlocalmemory/hooks/post_tool_outcome_hook.py +0 -351
- package/src/superlocalmemory/hooks/prewarm_auth.py +0 -187
- package/src/superlocalmemory/hooks/rules_engine.py +0 -99
- package/src/superlocalmemory/hooks/session_registry.py +0 -330
- package/src/superlocalmemory/hooks/stop_outcome_hook.py +0 -138
- package/src/superlocalmemory/hooks/sync_loop.py +0 -114
- package/src/superlocalmemory/hooks/topic_shift_hook.py +0 -302
- package/src/superlocalmemory/hooks/user_prompt_hook.py +0 -131
- package/src/superlocalmemory/hooks/user_prompt_rehash_hook.py +0 -210
- package/src/superlocalmemory/infra/__init__.py +0 -3
- package/src/superlocalmemory/infra/auth_middleware.py +0 -145
- package/src/superlocalmemory/infra/backup.py +0 -974
- package/src/superlocalmemory/infra/backup_obligations.py +0 -423
- package/src/superlocalmemory/infra/cache_manager.py +0 -267
- package/src/superlocalmemory/infra/cloud_backup.py +0 -788
- package/src/superlocalmemory/infra/daemon_identity.py +0 -300
- package/src/superlocalmemory/infra/data_root.py +0 -238
- package/src/superlocalmemory/infra/event_bus.py +0 -637
- package/src/superlocalmemory/infra/local_diagnostics.py +0 -327
- package/src/superlocalmemory/infra/pid_manager.py +0 -193
- package/src/superlocalmemory/infra/process_identity.py +0 -180
- package/src/superlocalmemory/infra/process_reaper.py +0 -624
- package/src/superlocalmemory/infra/rate_limiter.py +0 -228
- package/src/superlocalmemory/infra/self_heal.py +0 -401
- package/src/superlocalmemory/infra/version_integrity.py +0 -229
- package/src/superlocalmemory/ingestion/__init__.py +0 -13
- package/src/superlocalmemory/ingestion/adapter_manager.py +0 -255
- package/src/superlocalmemory/ingestion/base_adapter.py +0 -171
- package/src/superlocalmemory/ingestion/calendar_adapter.py +0 -349
- package/src/superlocalmemory/ingestion/credentials.py +0 -125
- package/src/superlocalmemory/ingestion/gmail_adapter.py +0 -378
- package/src/superlocalmemory/ingestion/parsers.py +0 -100
- package/src/superlocalmemory/ingestion/transcript_adapter.py +0 -161
- package/src/superlocalmemory/integrations/__init__.py +0 -1
- package/src/superlocalmemory/integrations/bounded_loops_mcp.py +0 -431
- package/src/superlocalmemory/integrations/bounded_loops_v051.py +0 -236
- package/src/superlocalmemory/learning/__init__.py +0 -0
- package/src/superlocalmemory/learning/adaptive.py +0 -172
- package/src/superlocalmemory/learning/arm_catalog.py +0 -97
- package/src/superlocalmemory/learning/assertion_miner.py +0 -403
- package/src/superlocalmemory/learning/bandit.py +0 -654
- package/src/superlocalmemory/learning/bandit_cache.py +0 -131
- package/src/superlocalmemory/learning/behavioral.py +0 -542
- package/src/superlocalmemory/learning/bootstrap.py +0 -298
- package/src/superlocalmemory/learning/consolidation_cycle.py +0 -398
- package/src/superlocalmemory/learning/consolidation_quantization_worker.py +0 -115
- package/src/superlocalmemory/learning/consolidation_worker.py +0 -261
- package/src/superlocalmemory/learning/cross_project.py +0 -408
- package/src/superlocalmemory/learning/database.py +0 -698
- package/src/superlocalmemory/learning/dedup_hnsw.py +0 -413
- package/src/superlocalmemory/learning/engagement.py +0 -487
- package/src/superlocalmemory/learning/engagement_features.py +0 -279
- package/src/superlocalmemory/learning/ensemble.py +0 -309
- package/src/superlocalmemory/learning/entity_compiler.py +0 -356
- package/src/superlocalmemory/learning/fact_outcome_joins.py +0 -207
- package/src/superlocalmemory/learning/features.py +0 -138
- package/src/superlocalmemory/learning/feedback.py +0 -724
- package/src/superlocalmemory/learning/forgetting_scheduler.py +0 -375
- package/src/superlocalmemory/learning/hnsw_dedup.py +0 -69
- package/src/superlocalmemory/learning/labeler.py +0 -85
- package/src/superlocalmemory/learning/legacy_migration.py +0 -316
- package/src/superlocalmemory/learning/lightgbm_subprocess.py +0 -236
- package/src/superlocalmemory/learning/memory_merge.py +0 -175
- package/src/superlocalmemory/learning/model_cache.py +0 -267
- package/src/superlocalmemory/learning/model_rollback.py +0 -281
- package/src/superlocalmemory/learning/outcome_queue.py +0 -306
- package/src/superlocalmemory/learning/outcomes.py +0 -286
- package/src/superlocalmemory/learning/pattern_miner.py +0 -465
- package/src/superlocalmemory/learning/pattern_miner_constants.py +0 -90
- package/src/superlocalmemory/learning/pcos.py +0 -291
- package/src/superlocalmemory/learning/project_context.py +0 -366
- package/src/superlocalmemory/learning/propensity.py +0 -131
- package/src/superlocalmemory/learning/ranker.py +0 -300
- package/src/superlocalmemory/learning/ranker_common.py +0 -163
- package/src/superlocalmemory/learning/ranker_retrain_legacy.py +0 -210
- package/src/superlocalmemory/learning/ranker_retrain_online.py +0 -423
- package/src/superlocalmemory/learning/reward.py +0 -888
- package/src/superlocalmemory/learning/reward_archive.py +0 -223
- package/src/superlocalmemory/learning/reward_boost.py +0 -211
- package/src/superlocalmemory/learning/reward_from_outcomes.py +0 -365
- package/src/superlocalmemory/learning/reward_model.py +0 -144
- package/src/superlocalmemory/learning/reward_proxy.py +0 -578
- package/src/superlocalmemory/learning/shadow_test.py +0 -524
- package/src/superlocalmemory/learning/signal_kinds.py +0 -79
- package/src/superlocalmemory/learning/signal_worker.py +0 -268
- package/src/superlocalmemory/learning/signals.py +0 -646
- package/src/superlocalmemory/learning/skill_performance_miner.py +0 -422
- package/src/superlocalmemory/learning/source_quality.py +0 -828
- package/src/superlocalmemory/learning/trigram_index.py +0 -548
- package/src/superlocalmemory/learning/workflows.py +0 -309
- package/src/superlocalmemory/llm/__init__.py +0 -0
- package/src/superlocalmemory/llm/backbone.py +0 -364
- package/src/superlocalmemory/loops/__init__.py +0 -58
- package/src/superlocalmemory/loops/budget.py +0 -58
- package/src/superlocalmemory/loops/engine.py +0 -174
- package/src/superlocalmemory/loops/ledger.py +0 -298
- package/src/superlocalmemory/loops/models.py +0 -152
- package/src/superlocalmemory/loops/rules.py +0 -52
- package/src/superlocalmemory/math/__init__.py +0 -0
- package/src/superlocalmemory/math/ebbinghaus.py +0 -352
- package/src/superlocalmemory/math/fisher.py +0 -356
- package/src/superlocalmemory/math/fisher_quantized.py +0 -255
- package/src/superlocalmemory/math/hopfield.py +0 -282
- package/src/superlocalmemory/math/langevin.py +0 -411
- package/src/superlocalmemory/math/polar_quant.py +0 -414
- package/src/superlocalmemory/math/qjl.py +0 -115
- package/src/superlocalmemory/math/sheaf.py +0 -261
- package/src/superlocalmemory/math/turbo_quant.py +0 -318
- package/src/superlocalmemory/mcp/__init__.py +0 -0
- package/src/superlocalmemory/mcp/_daemon_proxy.py +0 -203
- package/src/superlocalmemory/mcp/_pool_adapter.py +0 -181
- package/src/superlocalmemory/mcp/_stdin_guard.py +0 -60
- package/src/superlocalmemory/mcp/agent_context.py +0 -115
- package/src/superlocalmemory/mcp/cli_fallback.py +0 -602
- package/src/superlocalmemory/mcp/http_transport.py +0 -85
- package/src/superlocalmemory/mcp/profiles.py +0 -154
- package/src/superlocalmemory/mcp/resources.py +0 -281
- package/src/superlocalmemory/mcp/server.py +0 -480
- package/src/superlocalmemory/mcp/session_binding.py +0 -98
- package/src/superlocalmemory/mcp/shared.py +0 -112
- package/src/superlocalmemory/mcp/tools.py +0 -18
- package/src/superlocalmemory/mcp/tools_active.py +0 -958
- package/src/superlocalmemory/mcp/tools_brain.py +0 -298
- package/src/superlocalmemory/mcp/tools_code_graph.py +0 -1717
- package/src/superlocalmemory/mcp/tools_context.py +0 -239
- package/src/superlocalmemory/mcp/tools_core.py +0 -1112
- package/src/superlocalmemory/mcp/tools_evolution.py +0 -343
- package/src/superlocalmemory/mcp/tools_learning.py +0 -393
- package/src/superlocalmemory/mcp/tools_loops.py +0 -345
- package/src/superlocalmemory/mcp/tools_mesh.py +0 -429
- package/src/superlocalmemory/mcp/tools_ops.py +0 -115
- package/src/superlocalmemory/mcp/tools_optimize.py +0 -322
- package/src/superlocalmemory/mcp/tools_summaries.py +0 -147
- package/src/superlocalmemory/mcp/tools_v28.py +0 -292
- package/src/superlocalmemory/mcp/tools_v3.py +0 -398
- package/src/superlocalmemory/mcp/tools_v33.py +0 -507
- package/src/superlocalmemory/mesh/__init__.py +0 -12
- package/src/superlocalmemory/mesh/broker.py +0 -812
- package/src/superlocalmemory/mesh/broker_security.py +0 -470
- package/src/superlocalmemory/mesh/discovery.py +0 -365
- package/src/superlocalmemory/mesh/lock_protocol.py +0 -313
- package/src/superlocalmemory/mesh/node_identity.py +0 -97
- package/src/superlocalmemory/mesh/outbox_remote.py +0 -429
- package/src/superlocalmemory/mesh/remote_sync.py +0 -829
- package/src/superlocalmemory/mesh/state_sync.py +0 -286
- package/src/superlocalmemory/migrations/__init__.py +0 -5
- package/src/superlocalmemory/migrations/v3_4_25_to_v3_4_26.py +0 -144
- package/src/superlocalmemory/optimize/NOTICE +0 -6
- package/src/superlocalmemory/optimize/__init__.py +0 -0
- package/src/superlocalmemory/optimize/adapters/__init__.py +0 -68
- package/src/superlocalmemory/optimize/adapters/_agent_registry.py +0 -120
- package/src/superlocalmemory/optimize/adapters/anthropic_adapter.py +0 -112
- package/src/superlocalmemory/optimize/adapters/openai_adapter.py +0 -122
- package/src/superlocalmemory/optimize/adapters/wrap.py +0 -228
- package/src/superlocalmemory/optimize/cache/__init__.py +0 -31
- package/src/superlocalmemory/optimize/cache/boundary_store.py +0 -488
- package/src/superlocalmemory/optimize/cache/centroid_store.py +0 -199
- package/src/superlocalmemory/optimize/cache/context_key.py +0 -67
- package/src/superlocalmemory/optimize/cache/exact.py +0 -88
- package/src/superlocalmemory/optimize/cache/invalidation.py +0 -36
- package/src/superlocalmemory/optimize/cache/key_builder.py +0 -111
- package/src/superlocalmemory/optimize/cache/manager.py +0 -737
- package/src/superlocalmemory/optimize/cache/semantic.py +0 -637
- package/src/superlocalmemory/optimize/cache/stampede.py +0 -50
- package/src/superlocalmemory/optimize/compress/__init__.py +0 -17
- package/src/superlocalmemory/optimize/compress/align.py +0 -159
- package/src/superlocalmemory/optimize/compress/ccr.py +0 -116
- package/src/superlocalmemory/optimize/compress/prose_llmlingua.py +0 -71
- package/src/superlocalmemory/optimize/compress/router.py +0 -667
- package/src/superlocalmemory/optimize/config/__init__.py +0 -56
- package/src/superlocalmemory/optimize/config/defaults.py +0 -43
- package/src/superlocalmemory/optimize/config/schema.py +0 -327
- package/src/superlocalmemory/optimize/config/store.py +0 -270
- package/src/superlocalmemory/optimize/metrics/__init__.py +0 -8
- package/src/superlocalmemory/optimize/metrics/counters.py +0 -155
- package/src/superlocalmemory/optimize/metrics/estimator.py +0 -87
- package/src/superlocalmemory/optimize/metrics/exporters.py +0 -77
- package/src/superlocalmemory/optimize/metrics/persistence.py +0 -115
- package/src/superlocalmemory/optimize/proxy/__init__.py +0 -28
- package/src/superlocalmemory/optimize/proxy/_helpers.py +0 -730
- package/src/superlocalmemory/optimize/proxy/anthropic_surface.py +0 -375
- package/src/superlocalmemory/optimize/proxy/capture.py +0 -550
- package/src/superlocalmemory/optimize/proxy/gemini_surface.py +0 -528
- package/src/superlocalmemory/optimize/proxy/lifecycle.py +0 -126
- package/src/superlocalmemory/optimize/proxy/openai_surface.py +0 -465
- package/src/superlocalmemory/optimize/proxy/server.py +0 -199
- package/src/superlocalmemory/optimize/proxy/vertex_surface.py +0 -246
- package/src/superlocalmemory/optimize/storage/__init__.py +0 -0
- package/src/superlocalmemory/optimize/storage/db.py +0 -1185
- package/src/superlocalmemory/optimize/storage/schema.py +0 -205
- package/src/superlocalmemory/parameterization/__init__.py +0 -47
- package/src/superlocalmemory/parameterization/cross_project.py +0 -12
- package/src/superlocalmemory/parameterization/pattern_extractor.py +0 -584
- package/src/superlocalmemory/parameterization/pii_filter.py +0 -106
- package/src/superlocalmemory/parameterization/prompt_injector.py +0 -219
- package/src/superlocalmemory/parameterization/prompt_lifecycle.py +0 -281
- package/src/superlocalmemory/parameterization/soft_prompt_generator.py +0 -542
- package/src/superlocalmemory/parameterization/workflow_miner.py +0 -17
- package/src/superlocalmemory/reliability/__init__.py +0 -45
- package/src/superlocalmemory/reliability/join_liveness.py +0 -301
- package/src/superlocalmemory/reliability/prior_distance.py +0 -243
- package/src/superlocalmemory/retrieval/__init__.py +0 -0
- package/src/superlocalmemory/retrieval/agentic.py +0 -367
- package/src/superlocalmemory/retrieval/ann_index.py +0 -235
- package/src/superlocalmemory/retrieval/bm25_channel.py +0 -451
- package/src/superlocalmemory/retrieval/bridge_discovery.py +0 -253
- package/src/superlocalmemory/retrieval/channel_registry.py +0 -154
- package/src/superlocalmemory/retrieval/channel_status.py +0 -117
- package/src/superlocalmemory/retrieval/engine.py +0 -1615
- package/src/superlocalmemory/retrieval/entity_channel.py +0 -994
- package/src/superlocalmemory/retrieval/forgetting_filter.py +0 -160
- package/src/superlocalmemory/retrieval/fusion.py +0 -81
- package/src/superlocalmemory/retrieval/graph_adjacency.py +0 -219
- package/src/superlocalmemory/retrieval/hopfield_channel.py +0 -465
- package/src/superlocalmemory/retrieval/profile_channel.py +0 -105
- package/src/superlocalmemory/retrieval/quantization_aware_search.py +0 -147
- package/src/superlocalmemory/retrieval/remote_reranker.py +0 -758
- package/src/superlocalmemory/retrieval/reranker.py +0 -674
- package/src/superlocalmemory/retrieval/scope_policy.py +0 -126
- package/src/superlocalmemory/retrieval/semantic_channel.py +0 -638
- package/src/superlocalmemory/retrieval/spreading.py +0 -288
- package/src/superlocalmemory/retrieval/spreading_activation.py +0 -616
- package/src/superlocalmemory/retrieval/strategy.py +0 -248
- package/src/superlocalmemory/retrieval/temporal_channel.py +0 -433
- package/src/superlocalmemory/retrieval/temporal_frame.py +0 -102
- package/src/superlocalmemory/retrieval/temporal_utils.py +0 -122
- package/src/superlocalmemory/retrieval/temporal_validity_filter.py +0 -499
- package/src/superlocalmemory/retrieval/time_window.py +0 -181
- package/src/superlocalmemory/retrieval/vector_store.py +0 -863
- package/src/superlocalmemory/server/__init__.py +0 -1
- package/src/superlocalmemory/server/api.py +0 -310
- package/src/superlocalmemory/server/asset_versions.py +0 -171
- package/src/superlocalmemory/server/bandit_loops.py +0 -158
- package/src/superlocalmemory/server/config_file.py +0 -90
- package/src/superlocalmemory/server/consolidation_runner.py +0 -140
- package/src/superlocalmemory/server/egress_policy.py +0 -258
- package/src/superlocalmemory/server/loopback.py +0 -85
- package/src/superlocalmemory/server/middleware/__init__.py +0 -11
- package/src/superlocalmemory/server/middleware/security_headers.py +0 -144
- package/src/superlocalmemory/server/origin.py +0 -55
- package/src/superlocalmemory/server/profile_runtime.py +0 -515
- package/src/superlocalmemory/server/rbac_enforce.py +0 -194
- package/src/superlocalmemory/server/recall_health.py +0 -343
- package/src/superlocalmemory/server/recall_serializer.py +0 -320
- package/src/superlocalmemory/server/route_mutations.py +0 -104
- package/src/superlocalmemory/server/routes/__init__.py +0 -4
- package/src/superlocalmemory/server/routes/abstraction.py +0 -314
- package/src/superlocalmemory/server/routes/adapters.py +0 -63
- package/src/superlocalmemory/server/routes/agents.py +0 -303
- package/src/superlocalmemory/server/routes/backup.py +0 -869
- package/src/superlocalmemory/server/routes/behavioral.py +0 -659
- package/src/superlocalmemory/server/routes/brain.py +0 -1892
- package/src/superlocalmemory/server/routes/chat.py +0 -393
- package/src/superlocalmemory/server/routes/compliance.py +0 -533
- package/src/superlocalmemory/server/routes/config_api.py +0 -703
- package/src/superlocalmemory/server/routes/data_io.py +0 -329
- package/src/superlocalmemory/server/routes/entity.py +0 -237
- package/src/superlocalmemory/server/routes/events.py +0 -214
- package/src/superlocalmemory/server/routes/evolution.py +0 -510
- package/src/superlocalmemory/server/routes/helpers.py +0 -499
- package/src/superlocalmemory/server/routes/ingest.py +0 -137
- package/src/superlocalmemory/server/routes/insights.py +0 -366
- package/src/superlocalmemory/server/routes/learning.py +0 -834
- package/src/superlocalmemory/server/routes/learning_telemetry.py +0 -154
- package/src/superlocalmemory/server/routes/lifecycle.py +0 -184
- package/src/superlocalmemory/server/routes/memories.py +0 -1661
- package/src/superlocalmemory/server/routes/mesh.py +0 -517
- package/src/superlocalmemory/server/routes/mesh_lock.py +0 -54
- package/src/superlocalmemory/server/routes/mesh_state.py +0 -63
- package/src/superlocalmemory/server/routes/optimize.py +0 -197
- package/src/superlocalmemory/server/routes/prewarm.py +0 -173
- package/src/superlocalmemory/server/routes/profiles.py +0 -292
- package/src/superlocalmemory/server/routes/ratelimit.py +0 -132
- package/src/superlocalmemory/server/routes/rbac.py +0 -366
- package/src/superlocalmemory/server/routes/stats.py +0 -385
- package/src/superlocalmemory/server/routes/tiers.py +0 -222
- package/src/superlocalmemory/server/routes/timeline.py +0 -258
- package/src/superlocalmemory/server/routes/token.py +0 -90
- package/src/superlocalmemory/server/routes/v3_api.py +0 -3023
- package/src/superlocalmemory/server/routes/ws.py +0 -171
- package/src/superlocalmemory/server/security_middleware.py +0 -89
- package/src/superlocalmemory/server/ui.py +0 -354
- package/src/superlocalmemory/server/unified_daemon.py +0 -6326
- package/src/superlocalmemory/server/write_identity.py +0 -195
- package/src/superlocalmemory/storage/__init__.py +0 -0
- package/src/superlocalmemory/storage/_migration_internals.py +0 -638
- package/src/superlocalmemory/storage/_schema_version.py +0 -174
- package/src/superlocalmemory/storage/access_log.py +0 -170
- package/src/superlocalmemory/storage/admission_codec.py +0 -129
- package/src/superlocalmemory/storage/admission_journal.py +0 -843
- package/src/superlocalmemory/storage/agent_experience.py +0 -546
- package/src/superlocalmemory/storage/backup.py +0 -531
- package/src/superlocalmemory/storage/correction_cases.py +0 -670
- package/src/superlocalmemory/storage/database.py +0 -3180
- package/src/superlocalmemory/storage/deferred_writes.py +0 -209
- package/src/superlocalmemory/storage/embedding_codec.py +0 -200
- package/src/superlocalmemory/storage/embedding_migrator.py +0 -672
- package/src/superlocalmemory/storage/erasure_fence.py +0 -45
- package/src/superlocalmemory/storage/execution_learning.py +0 -285
- package/src/superlocalmemory/storage/external_evidence.py +0 -359
- package/src/superlocalmemory/storage/generation_fence.py +0 -63
- package/src/superlocalmemory/storage/lineage_retention.py +0 -236
- package/src/superlocalmemory/storage/logical_edges.py +0 -86
- package/src/superlocalmemory/storage/memory_write.py +0 -115
- package/src/superlocalmemory/storage/migration_runner.py +0 -895
- package/src/superlocalmemory/storage/migration_v33.py +0 -140
- package/src/superlocalmemory/storage/migrations/M001_add_signal_features_columns.py +0 -67
- package/src/superlocalmemory/storage/migrations/M002_model_state_history.py +0 -107
- package/src/superlocalmemory/storage/migrations/M003_migration_log.py +0 -38
- package/src/superlocalmemory/storage/migrations/M004_cross_platform_sync_log.py +0 -46
- package/src/superlocalmemory/storage/migrations/M005_bandit_tables.py +0 -75
- package/src/superlocalmemory/storage/migrations/M006_action_outcomes_reward.py +0 -75
- package/src/superlocalmemory/storage/migrations/M007_pending_outcomes.py +0 -63
- package/src/superlocalmemory/storage/migrations/M009_model_lineage.py +0 -94
- package/src/superlocalmemory/storage/migrations/M010_evolution_config.py +0 -80
- package/src/superlocalmemory/storage/migrations/M011_archive_and_merge.py +0 -87
- package/src/superlocalmemory/storage/migrations/M012_shadow_observations.py +0 -72
- package/src/superlocalmemory/storage/migrations/M013_bi_temporal_columns.py +0 -55
- package/src/superlocalmemory/storage/migrations/M014_v345_scale_ready.py +0 -45
- package/src/superlocalmemory/storage/migrations/M015_add_pinned_column.py +0 -58
- package/src/superlocalmemory/storage/migrations/M016_add_scope_support.py +0 -120
- package/src/superlocalmemory/storage/migrations/M017_ccq_scope_column.py +0 -79
- package/src/superlocalmemory/storage/migrations/M018_ingestion_operations.py +0 -120
- package/src/superlocalmemory/storage/migrations/M019_derivation_lineage.py +0 -54
- package/src/superlocalmemory/storage/migrations/M020_model_state_integrity.py +0 -52
- package/src/superlocalmemory/storage/migrations/M021_ingestion_log_profile.py +0 -108
- package/src/superlocalmemory/storage/migrations/M022_entity_aliases_profile.py +0 -86
- package/src/superlocalmemory/storage/migrations/M023_mesh_profile_isolation.py +0 -194
- package/src/superlocalmemory/storage/migrations/M024_rbac_users_roles.py +0 -87
- package/src/superlocalmemory/storage/migrations/M025_perf_indexes.py +0 -90
- package/src/superlocalmemory/storage/migrations/M026_rbac_memberships_fk.py +0 -136
- package/src/superlocalmemory/storage/migrations/M027_transferable_patterns_profile.py +0 -163
- package/src/superlocalmemory/storage/migrations/M028_fact_entity_associations.py +0 -305
- package/src/superlocalmemory/storage/migrations/M029_behavioral_history_indexes.py +0 -137
- package/src/superlocalmemory/storage/migrations/M030_entity_explorer_indexes.py +0 -93
- package/src/superlocalmemory/storage/migrations/M031_dead_letter_operations.py +0 -80
- package/src/superlocalmemory/storage/migrations/M032_write_coordinator_admission.py +0 -188
- package/src/superlocalmemory/storage/migrations/M033_projection_transactions.py +0 -148
- package/src/superlocalmemory/storage/migrations/M034_obligation_integrity.py +0 -58
- package/src/superlocalmemory/storage/migrations/M035_erasure_receipts.py +0 -113
- package/src/superlocalmemory/storage/migrations/M036_vector_row_map.py +0 -107
- package/src/superlocalmemory/storage/migrations/M037_manifest_hmac_version.py +0 -162
- package/src/superlocalmemory/storage/migrations/M038_learning_feedback_channel.py +0 -77
- package/src/superlocalmemory/storage/migrations/M039_scene_fact_members.py +0 -137
- package/src/superlocalmemory/storage/migrations/M040_agent_experience_receipts.py +0 -254
- package/src/superlocalmemory/storage/migrations/M041_external_evidence_receipts.py +0 -189
- package/src/superlocalmemory/storage/migrations/M042_correction_case_ledger.py +0 -245
- package/src/superlocalmemory/storage/migrations/M043_quarantine_display_summaries.py +0 -512
- package/src/superlocalmemory/storage/migrations/M044_play_carries_its_own_evidence.py +0 -127
- package/src/superlocalmemory/storage/migrations/M045_fact_outcome_score.py +0 -158
- package/src/superlocalmemory/storage/migrations/M046_prospective_memory_has_its_own_name.py +0 -620
- package/src/superlocalmemory/storage/migrations/M047_fisher_vectors_are_stored_like_every_other_vector.py +0 -306
- package/src/superlocalmemory/storage/migrations/M048_upcoming_holds_only_what_is_upcoming.py +0 -229
- package/src/superlocalmemory/storage/migrations/M049_a_schema_version_marker_is_one_row.py +0 -201
- package/src/superlocalmemory/storage/migrations/M050_execution_learning_v2.py +0 -70
- package/src/superlocalmemory/storage/migrations/__init__.py +0 -103
- package/src/superlocalmemory/storage/migrations.py +0 -333
- package/src/superlocalmemory/storage/models.py +0 -500
- package/src/superlocalmemory/storage/projection_outbox.py +0 -346
- package/src/superlocalmemory/storage/quantized_store.py +0 -280
- package/src/superlocalmemory/storage/read_connection.py +0 -115
- package/src/superlocalmemory/storage/retention_policy.py +0 -860
- package/src/superlocalmemory/storage/schema.py +0 -1108
- package/src/superlocalmemory/storage/schema_code_graph.py +0 -282
- package/src/superlocalmemory/storage/schema_v32.py +0 -382
- package/src/superlocalmemory/storage/schema_v3410.py +0 -159
- package/src/superlocalmemory/storage/schema_v3411.py +0 -149
- package/src/superlocalmemory/storage/schema_v343.py +0 -315
- package/src/superlocalmemory/storage/schema_v345.py +0 -109
- package/src/superlocalmemory/storage/schema_v347.py +0 -140
- package/src/superlocalmemory/storage/sqlite_vectors.py +0 -169
- package/src/superlocalmemory/storage/v2_migrator.py +0 -466
- package/src/superlocalmemory/storage/write_coordinator.py +0 -949
- package/src/superlocalmemory/storage/write_lock.py +0 -88
- package/src/superlocalmemory/summaries/__init__.py +0 -37
- package/src/superlocalmemory/summaries/base.py +0 -267
- package/src/superlocalmemory/summaries/daily_reflection.py +0 -340
- package/src/superlocalmemory/summaries/non_answer.py +0 -223
- package/src/superlocalmemory/summaries/project_work_log.py +0 -440
- package/src/superlocalmemory/summaries/session_summary.py +0 -311
- package/src/superlocalmemory/trust/__init__.py +0 -0
- package/src/superlocalmemory/trust/gate.py +0 -171
- package/src/superlocalmemory/trust/provenance.py +0 -124
- package/src/superlocalmemory/trust/scorer.py +0 -413
- package/src/superlocalmemory/trust/signals.py +0 -153
- package/src/superlocalmemory/ui/assets/slm-icon-white.svg +0 -64
- package/src/superlocalmemory/ui/assets/slm-icon.svg +0 -36
- package/src/superlocalmemory/ui/css/brain.css +0 -409
- package/src/superlocalmemory/ui/css/design-system.css +0 -696
- package/src/superlocalmemory/ui/css/legacy-dashboard.css +0 -663
- package/src/superlocalmemory/ui/css/neural-glass.css +0 -1599
- package/src/superlocalmemory/ui/css/od-bridge.css +0 -158
- package/src/superlocalmemory/ui/favicon.svg +0 -36
- package/src/superlocalmemory/ui/index.html +0 -1638
- package/src/superlocalmemory/ui/js/agents.js +0 -192
- package/src/superlocalmemory/ui/js/auto-settings.js +0 -624
- package/src/superlocalmemory/ui/js/brain.js +0 -1400
- package/src/superlocalmemory/ui/js/clusters.js +0 -326
- package/src/superlocalmemory/ui/js/compliance.js +0 -307
- package/src/superlocalmemory/ui/js/core.js +0 -566
- package/src/superlocalmemory/ui/js/dashboard.js +0 -503
- package/src/superlocalmemory/ui/js/event-delegation.js +0 -113
- package/src/superlocalmemory/ui/js/events.js +0 -178
- package/src/superlocalmemory/ui/js/fact-detail.js +0 -142
- package/src/superlocalmemory/ui/js/feedback.js +0 -339
- package/src/superlocalmemory/ui/js/graph-event-bus.js +0 -83
- package/src/superlocalmemory/ui/js/graph-filters.js +0 -220
- package/src/superlocalmemory/ui/js/graph-ui.js +0 -214
- package/src/superlocalmemory/ui/js/ide-status.js +0 -115
- package/src/superlocalmemory/ui/js/init.js +0 -54
- package/src/superlocalmemory/ui/js/knowledge-graph.js +0 -945
- package/src/superlocalmemory/ui/js/lifecycle.js +0 -387
- package/src/superlocalmemory/ui/js/math-health.js +0 -114
- package/src/superlocalmemory/ui/js/memories.js +0 -394
- package/src/superlocalmemory/ui/js/memory-chat.js +0 -371
- package/src/superlocalmemory/ui/js/memory-timeline.js +0 -265
- package/src/superlocalmemory/ui/js/modal.js +0 -733
- package/src/superlocalmemory/ui/js/ng-entities.js +0 -298
- package/src/superlocalmemory/ui/js/ng-health.js +0 -208
- package/src/superlocalmemory/ui/js/ng-ingestion.js +0 -203
- package/src/superlocalmemory/ui/js/ng-mesh.js +0 -374
- package/src/superlocalmemory/ui/js/ng-shell.js +0 -524
- package/src/superlocalmemory/ui/js/ng-skills.js +0 -663
- package/src/superlocalmemory/ui/js/od-agents.js +0 -588
- package/src/superlocalmemory/ui/js/od-auth-gate.js +0 -257
- package/src/superlocalmemory/ui/js/od-backup.js +0 -878
- package/src/superlocalmemory/ui/js/od-boundedloops.js +0 -324
- package/src/superlocalmemory/ui/js/od-brain.js +0 -1095
- package/src/superlocalmemory/ui/js/od-compliance-ext.js +0 -301
- package/src/superlocalmemory/ui/js/od-components.js +0 -147
- package/src/superlocalmemory/ui/js/od-entities.js +0 -622
- package/src/superlocalmemory/ui/js/od-graph.js +0 -776
- package/src/superlocalmemory/ui/js/od-health.js +0 -579
- package/src/superlocalmemory/ui/js/od-mcp.js +0 -508
- package/src/superlocalmemory/ui/js/od-memories.js +0 -1499
- package/src/superlocalmemory/ui/js/od-mesh.js +0 -645
- package/src/superlocalmemory/ui/js/od-operations.js +0 -1268
- package/src/superlocalmemory/ui/js/od-ops-health.js +0 -417
- package/src/superlocalmemory/ui/js/od-optimize.js +0 -828
- package/src/superlocalmemory/ui/js/od-settings.js +0 -1275
- package/src/superlocalmemory/ui/js/od-shell.js +0 -819
- package/src/superlocalmemory/ui/js/od-skills.js +0 -600
- package/src/superlocalmemory/ui/js/od-team.js +0 -265
- package/src/superlocalmemory/ui/js/optimize.js +0 -191
- package/src/superlocalmemory/ui/js/profiles.js +0 -362
- package/src/superlocalmemory/ui/js/quick-actions.js +0 -334
- package/src/superlocalmemory/ui/js/recall-lab.js +0 -373
- package/src/superlocalmemory/ui/js/search.js +0 -86
- package/src/superlocalmemory/ui/js/settings.js +0 -556
- package/src/superlocalmemory/ui/js/timeline.js +0 -62
- package/src/superlocalmemory/ui/js/trust-dashboard.js +0 -225
- package/src/superlocalmemory/ui/vendor/bootstrap-icons/bootstrap-icons.css +0 -2018
- package/src/superlocalmemory/ui/vendor/bootstrap-icons/fonts/bootstrap-icons.woff +0 -0
- package/src/superlocalmemory/ui/vendor/bootstrap-icons/fonts/bootstrap-icons.woff2 +0 -0
- package/src/superlocalmemory/ui/vendor/bootstrap.bundle.min.js +0 -7
- package/src/superlocalmemory/ui/vendor/bootstrap.min.css +0 -6
- package/src/superlocalmemory/ui/vendor/d3.v7.min.js +0 -2
- package/src/superlocalmemory/ui/vendor/graphology-library.min.js +0 -2
- package/src/superlocalmemory/ui/vendor/graphology.umd.min.js +0 -2
- package/src/superlocalmemory/ui/vendor/inter-ui/inter-variable.min.css +0 -8
- package/src/superlocalmemory/ui/vendor/inter-ui/variable/InterVariable-Italic.woff2 +0 -0
- package/src/superlocalmemory/ui/vendor/inter-ui/variable/InterVariable.woff2 +0 -0
- package/src/superlocalmemory/ui/vendor/sigma.min.js +0 -1
- package/src/superlocalmemory/vector/__init__.py +0 -9
- package/src/superlocalmemory/vector/lancedb_backend.py +0 -366
|
@@ -1,869 +0,0 @@
|
|
|
1
|
-
# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
2
|
-
# Licensed under AGPL-3.0-or-later - see LICENSE file
|
|
3
|
-
# Part of SuperLocalMemory V3 | https://qualixar.com | https://varunpratap.com
|
|
4
|
-
"""SuperLocalMemory V3.4.10 "Fortress" - Backup Routes
|
|
5
|
-
|
|
6
|
-
Routes:
|
|
7
|
-
Local: /api/backup/status, /api/backup/create, /api/backup/configure, /api/backup/list
|
|
8
|
-
Cloud: /api/backup/destinations, /api/backup/connect/github, /api/backup/connect/gdrive,
|
|
9
|
-
/api/backup/disconnect/{id}, /api/backup/sync, /api/backup/export
|
|
10
|
-
"""
|
|
11
|
-
import gzip
|
|
12
|
-
import hashlib
|
|
13
|
-
import hmac
|
|
14
|
-
import html as _html
|
|
15
|
-
import logging
|
|
16
|
-
import secrets
|
|
17
|
-
import shutil
|
|
18
|
-
import tempfile
|
|
19
|
-
import threading
|
|
20
|
-
import time
|
|
21
|
-
import urllib.parse
|
|
22
|
-
|
|
23
|
-
from fastapi import APIRouter, HTTPException, Request
|
|
24
|
-
from fastapi.responses import FileResponse, HTMLResponse, RedirectResponse
|
|
25
|
-
from pydantic import BaseModel, Field
|
|
26
|
-
from starlette.background import BackgroundTask
|
|
27
|
-
|
|
28
|
-
from .helpers import DB_PATH, MEMORY_DIR, BackupConfigRequest
|
|
29
|
-
|
|
30
|
-
logger = logging.getLogger("superlocalmemory.routes.backup")
|
|
31
|
-
router = APIRouter()
|
|
32
|
-
|
|
33
|
-
_OAUTH_STATE_TTL_SECONDS = 10 * 60
|
|
34
|
-
_OAUTH_STATE_LOCK = threading.Lock()
|
|
35
|
-
_OAUTH_STATES: dict[str, dict[str, object]] = {}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def _require_read(request: Request) -> None:
|
|
39
|
-
"""Require READ on the active profile for backup metadata."""
|
|
40
|
-
from superlocalmemory.access.rbac import Permission
|
|
41
|
-
from superlocalmemory.server.rbac_enforce import require_permission
|
|
42
|
-
|
|
43
|
-
require_permission(request, Permission.READ)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
def _require_manage(request: Request) -> dict:
|
|
47
|
-
"""Require MANAGE for backup configuration, creation, and cloud access."""
|
|
48
|
-
from superlocalmemory.server.rbac_enforce import require_manage
|
|
49
|
-
|
|
50
|
-
return require_manage(request)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def _require_oauth_start(request: Request) -> None:
|
|
54
|
-
"""Allow OAuth initiation from the exact dashboard or an authenticated admin."""
|
|
55
|
-
from superlocalmemory.server.origin import origin_is_daemon
|
|
56
|
-
|
|
57
|
-
fetch_site = request.headers.get("sec-fetch-site", "").lower()
|
|
58
|
-
if fetch_site == "cross-site":
|
|
59
|
-
raise HTTPException(
|
|
60
|
-
status_code=403,
|
|
61
|
-
detail="Cross-site OAuth initiation is not allowed.",
|
|
62
|
-
)
|
|
63
|
-
principal = _require_manage(request)
|
|
64
|
-
origin = request.headers.get("origin", "")
|
|
65
|
-
if origin:
|
|
66
|
-
descriptor = getattr(
|
|
67
|
-
getattr(request.app, "state", None),
|
|
68
|
-
"daemon_descriptor",
|
|
69
|
-
None,
|
|
70
|
-
)
|
|
71
|
-
daemon_port = (
|
|
72
|
-
getattr(descriptor, "port", None)
|
|
73
|
-
or request.url.port
|
|
74
|
-
or 8765
|
|
75
|
-
)
|
|
76
|
-
if not origin_is_daemon(origin, port=int(daemon_port)):
|
|
77
|
-
raise HTTPException(
|
|
78
|
-
status_code=403,
|
|
79
|
-
detail="OAuth initiation requires the local dashboard origin.",
|
|
80
|
-
)
|
|
81
|
-
elif fetch_site and fetch_site != "same-origin":
|
|
82
|
-
# Browser top-level navigations commonly omit Origin. Sec-Fetch-Site
|
|
83
|
-
# still distinguishes this daemon's own page (same-origin) from a
|
|
84
|
-
# different localhost port (same-site).
|
|
85
|
-
raise HTTPException(
|
|
86
|
-
status_code=403,
|
|
87
|
-
detail="OAuth initiation requires the local dashboard origin.",
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
from superlocalmemory.server.loopback import is_loopback as _is_loopback_host
|
|
91
|
-
|
|
92
|
-
host = request.client.host if request.client else ""
|
|
93
|
-
# "testclient" is included for in-process test compatibility (preserved
|
|
94
|
-
# from original behaviour; was in the original frozenset).
|
|
95
|
-
_from_loopback = _is_loopback_host(host) or host == "testclient"
|
|
96
|
-
if not _from_loopback and principal.get("kind") != "user":
|
|
97
|
-
raise HTTPException(
|
|
98
|
-
status_code=403,
|
|
99
|
-
detail=(
|
|
100
|
-
"OAuth connections must start from the local dashboard or an "
|
|
101
|
-
"authenticated admin session."
|
|
102
|
-
),
|
|
103
|
-
)
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
def _oauth_context(request: Request) -> str:
|
|
107
|
-
"""Fingerprint the local browser/session that initiated an OAuth flow.
|
|
108
|
-
|
|
109
|
-
OAuth callbacks cannot carry the install-token header, so the random state
|
|
110
|
-
is bound to stable browser context available on both the start and callback:
|
|
111
|
-
loopback client address, dashboard user session (when present), and user
|
|
112
|
-
agent. Only the digest is retained in memory.
|
|
113
|
-
"""
|
|
114
|
-
client_host = request.client.host if request.client else ""
|
|
115
|
-
session = request.headers.get("X-SLM-User-Session", "")
|
|
116
|
-
if not session:
|
|
117
|
-
session = request.cookies.get("slm_session", "")
|
|
118
|
-
user_agent = request.headers.get("user-agent", "")
|
|
119
|
-
material = "\x00".join((client_host, session, user_agent))
|
|
120
|
-
return hashlib.sha256(material.encode("utf-8")).hexdigest()
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
def _issue_oauth_state(provider: str, request: Request) -> str:
|
|
124
|
-
"""Create a cryptographically random, expiring, provider-bound state."""
|
|
125
|
-
now = time.monotonic()
|
|
126
|
-
state = secrets.token_urlsafe(32)
|
|
127
|
-
record = {
|
|
128
|
-
"provider": provider,
|
|
129
|
-
"context": _oauth_context(request),
|
|
130
|
-
"expires_at": now + _OAUTH_STATE_TTL_SECONDS,
|
|
131
|
-
}
|
|
132
|
-
with _OAUTH_STATE_LOCK:
|
|
133
|
-
expired = [
|
|
134
|
-
key for key, value in _OAUTH_STATES.items()
|
|
135
|
-
if float(value.get("expires_at", 0)) <= now
|
|
136
|
-
]
|
|
137
|
-
for key in expired:
|
|
138
|
-
_OAUTH_STATES.pop(key, None)
|
|
139
|
-
_OAUTH_STATES[state] = record
|
|
140
|
-
return state
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
def _consume_oauth_state(state: str, provider: str, request: Request) -> bool:
|
|
144
|
-
"""Atomically consume and validate OAuth state.
|
|
145
|
-
|
|
146
|
-
The pop happens under the lock, so parallel/replayed callbacks cannot both
|
|
147
|
-
exchange the same authorization code. A mismatched provider or browser
|
|
148
|
-
context fails closed and the suspicious state cannot be retried.
|
|
149
|
-
"""
|
|
150
|
-
if not state:
|
|
151
|
-
return False
|
|
152
|
-
with _OAUTH_STATE_LOCK:
|
|
153
|
-
record = _OAUTH_STATES.pop(state, None)
|
|
154
|
-
if not record or float(record.get("expires_at", 0)) <= time.monotonic():
|
|
155
|
-
return False
|
|
156
|
-
return (
|
|
157
|
-
hmac.compare_digest(str(record.get("provider", "")), provider)
|
|
158
|
-
and hmac.compare_digest(
|
|
159
|
-
str(record.get("context", "")),
|
|
160
|
-
_oauth_context(request),
|
|
161
|
-
)
|
|
162
|
-
)
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
def _internal_error(detail: str = "Internal server error") -> HTTPException:
|
|
166
|
-
"""SEC-H-02: log full traceback server-side; return a generic message to the client."""
|
|
167
|
-
logger.exception("backup route error")
|
|
168
|
-
return HTTPException(status_code=500, detail=detail)
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
# Feature flags
|
|
172
|
-
BACKUP_AVAILABLE = False
|
|
173
|
-
CLOUD_AVAILABLE = False
|
|
174
|
-
try:
|
|
175
|
-
from superlocalmemory.infra.backup import BackupManager
|
|
176
|
-
BACKUP_AVAILABLE = True
|
|
177
|
-
except ImportError:
|
|
178
|
-
pass
|
|
179
|
-
|
|
180
|
-
try:
|
|
181
|
-
from superlocalmemory.infra.cloud_backup import (
|
|
182
|
-
connect_github,
|
|
183
|
-
connect_google_drive,
|
|
184
|
-
get_destinations,
|
|
185
|
-
remove_destination,
|
|
186
|
-
sync_all_destinations,
|
|
187
|
-
)
|
|
188
|
-
CLOUD_AVAILABLE = True
|
|
189
|
-
except ImportError:
|
|
190
|
-
pass
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
def _get_backup_manager() -> "BackupManager":
|
|
194
|
-
"""Get V3 backup manager instance."""
|
|
195
|
-
return BackupManager(db_path=DB_PATH, base_dir=MEMORY_DIR)
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
# ---- Request models -------------------------------------------------------
|
|
199
|
-
|
|
200
|
-
class GitHubConnectRequest(BaseModel):
|
|
201
|
-
pat: str = Field(..., min_length=1)
|
|
202
|
-
repo_name: str = Field(default="slm-backup")
|
|
203
|
-
|
|
204
|
-
class GDriveConnectRequest(BaseModel):
|
|
205
|
-
auth_code: str = Field(..., min_length=1)
|
|
206
|
-
redirect_uri: str = Field(default="http://localhost:8765/api/backup/oauth/callback")
|
|
207
|
-
|
|
208
|
-
class GDriveClientConfig(BaseModel):
|
|
209
|
-
client_id: str = Field(..., min_length=1)
|
|
210
|
-
client_secret: str = Field(..., min_length=1)
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
# ---- Local backup routes (existing) ---------------------------------------
|
|
214
|
-
|
|
215
|
-
@router.get("/api/backup/status")
|
|
216
|
-
def backup_status(request: Request):
|
|
217
|
-
"""Get auto-backup system status + cloud destinations."""
|
|
218
|
-
_require_read(request)
|
|
219
|
-
if not BACKUP_AVAILABLE:
|
|
220
|
-
return {"status": "not_implemented", "message": "Backup module not available"}
|
|
221
|
-
try:
|
|
222
|
-
manager = _get_backup_manager()
|
|
223
|
-
status = manager.get_status()
|
|
224
|
-
if CLOUD_AVAILABLE:
|
|
225
|
-
status["cloud_destinations"] = get_destinations(DB_PATH)
|
|
226
|
-
else:
|
|
227
|
-
status["cloud_destinations"] = []
|
|
228
|
-
return status
|
|
229
|
-
except Exception:
|
|
230
|
-
raise _internal_error("Backup status error")
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
@router.post("/api/backup/create")
|
|
234
|
-
def backup_create(request: Request):
|
|
235
|
-
"""Create a manual backup immediately."""
|
|
236
|
-
_require_manage(request)
|
|
237
|
-
if not BACKUP_AVAILABLE:
|
|
238
|
-
return {"success": False, "message": "Backup module not available"}
|
|
239
|
-
try:
|
|
240
|
-
manager = _get_backup_manager()
|
|
241
|
-
filename = manager.create_backup(label='manual')
|
|
242
|
-
if filename:
|
|
243
|
-
return {
|
|
244
|
-
"success": True, "filename": str(filename),
|
|
245
|
-
"message": f"Backup created: {filename}",
|
|
246
|
-
"status": manager.get_status(),
|
|
247
|
-
}
|
|
248
|
-
return {"success": False, "message": "Backup failed"}
|
|
249
|
-
except Exception:
|
|
250
|
-
raise _internal_error("Backup create error")
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
@router.post("/api/backup/configure")
|
|
254
|
-
def backup_configure(request: Request, payload: BackupConfigRequest):
|
|
255
|
-
"""Update auto-backup configuration."""
|
|
256
|
-
_require_manage(request)
|
|
257
|
-
if not BACKUP_AVAILABLE:
|
|
258
|
-
return {"success": False, "message": "Backup module not available"}
|
|
259
|
-
try:
|
|
260
|
-
manager = _get_backup_manager()
|
|
261
|
-
result = manager.configure(
|
|
262
|
-
interval_hours=payload.interval_hours,
|
|
263
|
-
max_backups=payload.max_backups,
|
|
264
|
-
enabled=payload.enabled,
|
|
265
|
-
)
|
|
266
|
-
return {"success": True, "message": "Backup configuration updated", "status": result}
|
|
267
|
-
except Exception:
|
|
268
|
-
raise _internal_error("Backup configure error")
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
@router.get("/api/backup/list")
|
|
272
|
-
def backup_list(request: Request):
|
|
273
|
-
"""List all available backups."""
|
|
274
|
-
_require_read(request)
|
|
275
|
-
if not BACKUP_AVAILABLE:
|
|
276
|
-
return {"backups": [], "count": 0, "message": "Backup module not available"}
|
|
277
|
-
# This GET is not covered by the mutation middleware. The local machine
|
|
278
|
-
# owner (loopback) is trusted; a remote caller must present a credential;
|
|
279
|
-
# non-loopback uncredentialed callers fail closed. Using the mutation-actor
|
|
280
|
-
# boundary (not require_write_actor) so the same-origin dashboard — whose
|
|
281
|
-
# fetch wrapper only attaches the install token to mutating requests — can
|
|
282
|
-
# still read its own backup list.
|
|
283
|
-
from superlocalmemory.server.write_identity import require_http_mutation_actor
|
|
284
|
-
require_http_mutation_actor(request, getattr(request.app.state, "daemon_descriptor", None),
|
|
285
|
-
actor_kind="backup-list")
|
|
286
|
-
try:
|
|
287
|
-
manager = _get_backup_manager()
|
|
288
|
-
backups = manager.list_backups()
|
|
289
|
-
return {"backups": backups, "count": len(backups)}
|
|
290
|
-
except Exception:
|
|
291
|
-
raise _internal_error("Backup list error")
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
# ---- Cloud destination routes (v3.4.10) -----------------------------------
|
|
295
|
-
|
|
296
|
-
@router.get("/api/backup/destinations")
|
|
297
|
-
def list_destinations(request: Request):
|
|
298
|
-
"""List all configured cloud backup destinations."""
|
|
299
|
-
_require_read(request)
|
|
300
|
-
if not CLOUD_AVAILABLE:
|
|
301
|
-
return {"destinations": [], "cloud_available": False}
|
|
302
|
-
return {"destinations": get_destinations(DB_PATH), "cloud_available": True}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
@router.post("/api/backup/connect/github")
|
|
306
|
-
def connect_github_route(request: Request, payload: GitHubConnectRequest):
|
|
307
|
-
"""Connect GitHub as a backup destination using PAT."""
|
|
308
|
-
_require_manage(request)
|
|
309
|
-
if not CLOUD_AVAILABLE:
|
|
310
|
-
raise HTTPException(status_code=501, detail="Cloud backup module not available")
|
|
311
|
-
result = connect_github(payload.pat, payload.repo_name)
|
|
312
|
-
if "error" in result:
|
|
313
|
-
raise HTTPException(status_code=400, detail=result["error"])
|
|
314
|
-
return result
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
@router.post("/api/backup/connect/gdrive/config")
|
|
318
|
-
def configure_gdrive_client(request: Request, payload: GDriveClientConfig):
|
|
319
|
-
"""Store Google OAuth client credentials (one-time setup)."""
|
|
320
|
-
_require_manage(request)
|
|
321
|
-
if not CLOUD_AVAILABLE:
|
|
322
|
-
raise HTTPException(status_code=501, detail="Cloud backup module not available")
|
|
323
|
-
from superlocalmemory.infra.cloud_backup import _store_credential
|
|
324
|
-
_store_credential("gdrive_client_id", payload.client_id)
|
|
325
|
-
_store_credential("gdrive_client_secret", payload.client_secret)
|
|
326
|
-
return {"success": True, "message": "Google OAuth client configured"}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
@router.post("/api/backup/connect/gdrive")
|
|
330
|
-
def connect_gdrive_route(request: Request, payload: GDriveConnectRequest):
|
|
331
|
-
"""Complete Google Drive OAuth2 flow with authorization code."""
|
|
332
|
-
_require_manage(request)
|
|
333
|
-
if not CLOUD_AVAILABLE:
|
|
334
|
-
raise HTTPException(status_code=501, detail="Cloud backup module not available")
|
|
335
|
-
result = connect_google_drive(payload.auth_code, payload.redirect_uri)
|
|
336
|
-
if "error" in result:
|
|
337
|
-
raise HTTPException(status_code=400, detail=result["error"])
|
|
338
|
-
return result
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
@router.delete("/api/backup/disconnect/{dest_id}")
|
|
342
|
-
def disconnect_destination(request: Request, dest_id: str):
|
|
343
|
-
"""Remove a cloud backup destination."""
|
|
344
|
-
_require_manage(request)
|
|
345
|
-
if not CLOUD_AVAILABLE:
|
|
346
|
-
raise HTTPException(status_code=501, detail="Cloud backup module not available")
|
|
347
|
-
ok = remove_destination(dest_id, DB_PATH)
|
|
348
|
-
if not ok:
|
|
349
|
-
raise HTTPException(status_code=404, detail="Destination not found")
|
|
350
|
-
return {"success": True, "message": "Destination disconnected"}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
@router.post("/api/backup/sync")
|
|
354
|
-
def sync_cloud(request: Request):
|
|
355
|
-
"""Manually trigger sync to all cloud destinations.
|
|
356
|
-
|
|
357
|
-
Runs the upload in a background thread so it doesn't block the
|
|
358
|
-
dashboard. Returns immediately with status 'syncing'. The actual
|
|
359
|
-
upload status is reflected in the destination's last_sync_status.
|
|
360
|
-
"""
|
|
361
|
-
import threading
|
|
362
|
-
|
|
363
|
-
_require_manage(request)
|
|
364
|
-
if not CLOUD_AVAILABLE:
|
|
365
|
-
raise HTTPException(status_code=501, detail="Cloud backup module not available")
|
|
366
|
-
if not BACKUP_AVAILABLE:
|
|
367
|
-
raise HTTPException(status_code=501, detail="Backup module not available")
|
|
368
|
-
|
|
369
|
-
# Create backup synchronously (fast — SQLite .backup is ~2s)
|
|
370
|
-
manager = _get_backup_manager()
|
|
371
|
-
filename = manager.create_backup(label="cloud-sync")
|
|
372
|
-
if not filename:
|
|
373
|
-
raise HTTPException(status_code=500, detail="Failed to create backup")
|
|
374
|
-
|
|
375
|
-
# Run the cloud upload in a background thread (non-blocking)
|
|
376
|
-
def _sync_background():
|
|
377
|
-
try:
|
|
378
|
-
sync_all_destinations(DB_PATH)
|
|
379
|
-
except Exception as exc:
|
|
380
|
-
logger.error("Background cloud sync failed: %s", exc)
|
|
381
|
-
|
|
382
|
-
thread = threading.Thread(target=_sync_background, daemon=True)
|
|
383
|
-
thread.start()
|
|
384
|
-
|
|
385
|
-
return {
|
|
386
|
-
"success": True,
|
|
387
|
-
"backup": filename,
|
|
388
|
-
"sync": {
|
|
389
|
-
"status": "syncing",
|
|
390
|
-
"message": (
|
|
391
|
-
"Upload started in background. Check destination status for progress."
|
|
392
|
-
),
|
|
393
|
-
},
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
# ---- Export / Download route (v3.4.10) ------------------------------------
|
|
398
|
-
|
|
399
|
-
@router.post("/api/backup/export")
|
|
400
|
-
def export_backup(request: Request):
|
|
401
|
-
"""Create and download a compressed backup archive.
|
|
402
|
-
|
|
403
|
-
Export is a credentialed mutation because it creates a local snapshot.
|
|
404
|
-
The compressed transport file is temporary and is removed after the
|
|
405
|
-
response completes; the normal retention policy owns the snapshot itself.
|
|
406
|
-
"""
|
|
407
|
-
_require_manage(request)
|
|
408
|
-
if not BACKUP_AVAILABLE:
|
|
409
|
-
raise HTTPException(status_code=501, detail="Backup module not available")
|
|
410
|
-
|
|
411
|
-
manager = _get_backup_manager()
|
|
412
|
-
filename = manager.create_backup(label="export")
|
|
413
|
-
if not filename:
|
|
414
|
-
raise HTTPException(status_code=500, detail="Failed to create backup")
|
|
415
|
-
|
|
416
|
-
backup_path = MEMORY_DIR / "backups" / filename
|
|
417
|
-
if not backup_path.exists():
|
|
418
|
-
raise HTTPException(status_code=500, detail="Backup file not found")
|
|
419
|
-
|
|
420
|
-
# Compress into a one-response transport file. Never retain another full
|
|
421
|
-
# copy beside the bounded snapshot set.
|
|
422
|
-
with tempfile.NamedTemporaryFile(
|
|
423
|
-
prefix="slm-export-",
|
|
424
|
-
suffix=".db.gz",
|
|
425
|
-
dir=backup_path.parent,
|
|
426
|
-
delete=False,
|
|
427
|
-
) as temp_file:
|
|
428
|
-
gz_path = backup_path.parent / temp_file.name
|
|
429
|
-
try:
|
|
430
|
-
with open(backup_path, "rb") as f_in:
|
|
431
|
-
with gzip.open(gz_path, "wb") as f_out:
|
|
432
|
-
shutil.copyfileobj(f_in, f_out)
|
|
433
|
-
except Exception:
|
|
434
|
-
gz_path.unlink(missing_ok=True)
|
|
435
|
-
raise
|
|
436
|
-
|
|
437
|
-
return FileResponse(
|
|
438
|
-
path=str(gz_path),
|
|
439
|
-
media_type="application/gzip",
|
|
440
|
-
filename=f"{backup_path.name}.gz",
|
|
441
|
-
background=BackgroundTask(gz_path.unlink, missing_ok=True),
|
|
442
|
-
)
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
# ---- OAuth SSO Flows (v3.4.10) -------------------------------------------
|
|
446
|
-
# These routes handle the browser popup flow:
|
|
447
|
-
# 1. /start → redirect to provider's login page
|
|
448
|
-
# 2. Provider redirects back to /callback with auth code
|
|
449
|
-
# 3. /callback exchanges code for tokens, stores in keychain, shows success page
|
|
450
|
-
|
|
451
|
-
_OAUTH_SUCCESS_HTML = """<!DOCTYPE html>
|
|
452
|
-
<html><head><title>Connected!</title>
|
|
453
|
-
<style>
|
|
454
|
-
body {{ font-family: -apple-system, system-ui, sans-serif; background: #0a0a0f; color: #e0e0e0;
|
|
455
|
-
display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; }}
|
|
456
|
-
.card {{ background: rgba(255,255,255,0.05); border: 1px solid rgba(0,212,170,0.3);
|
|
457
|
-
border-radius: 16px; padding: 40px; text-align: center; max-width: 400px; }}
|
|
458
|
-
.icon {{ font-size: 48px; margin-bottom: 16px; }}
|
|
459
|
-
h2 {{ color: #00D4AA; margin: 0 0 8px; }}
|
|
460
|
-
p {{ color: #999; margin: 0 0 20px; }}
|
|
461
|
-
.btn {{ background: #00D4AA; color: #0a0a0f; border: none; padding: 10px 24px;
|
|
462
|
-
border-radius: 8px; cursor: pointer; font-size: 14px; font-weight: 600; }}
|
|
463
|
-
.btn:hover {{ background: #00b894; }}
|
|
464
|
-
</style></head>
|
|
465
|
-
<body><div class="card">
|
|
466
|
-
<div class="icon">{icon}</div>
|
|
467
|
-
<h2>{title}</h2>
|
|
468
|
-
<p>{message}</p>
|
|
469
|
-
<button class="btn" onclick="window.close()">Close Window</button>
|
|
470
|
-
</div></body></html>"""
|
|
471
|
-
|
|
472
|
-
_OAUTH_ERROR_HTML = """<!DOCTYPE html>
|
|
473
|
-
<html><head><title>Connection Failed</title>
|
|
474
|
-
<style>
|
|
475
|
-
body {{ font-family: -apple-system, system-ui, sans-serif; background: #0a0a0f; color: #e0e0e0;
|
|
476
|
-
display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; }}
|
|
477
|
-
.card {{ background: rgba(255,255,255,0.05); border: 1px solid rgba(255,71,87,0.3);
|
|
478
|
-
border-radius: 16px; padding: 40px; text-align: center; max-width: 400px; }}
|
|
479
|
-
.icon {{ font-size: 48px; margin-bottom: 16px; }}
|
|
480
|
-
h2 {{ color: #ff4757; margin: 0 0 8px; }}
|
|
481
|
-
p {{ color: #999; margin: 0 0 20px; font-size: 13px; }}
|
|
482
|
-
.btn {{ background: #333; color: #e0e0e0; border: none; padding: 10px 24px;
|
|
483
|
-
border-radius: 8px; cursor: pointer; font-size: 14px; }}
|
|
484
|
-
</style></head>
|
|
485
|
-
<body><div class="card">
|
|
486
|
-
<div class="icon">{icon}</div>
|
|
487
|
-
<h2>Connection Failed</h2>
|
|
488
|
-
<p>{error}</p>
|
|
489
|
-
<button class="btn" onclick="window.close()">Close</button>
|
|
490
|
-
</div></body></html>"""
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
# S8-SEC-04: OAuth success / error pages render user-influenced strings
|
|
494
|
-
# (``error``, ``error_description``, provider ``message`` / ``title``).
|
|
495
|
-
# ``str.format`` doesn't escape HTML, so a hostile OAuth callback can
|
|
496
|
-
# inject ``<script>`` into these pages. These helpers HTML-escape every
|
|
497
|
-
# interpolated value before emitting the template.
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
def _oauth_error_page(icon: str, error: str) -> str:
|
|
501
|
-
return _OAUTH_ERROR_HTML.format(
|
|
502
|
-
icon=_html.escape(str(icon), quote=True),
|
|
503
|
-
error=_html.escape(str(error), quote=True),
|
|
504
|
-
)
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
def _oauth_success_page(icon: str, title: str, message: str) -> str:
|
|
508
|
-
return _OAUTH_SUCCESS_HTML.format(
|
|
509
|
-
icon=_html.escape(str(icon), quote=True),
|
|
510
|
-
title=_html.escape(str(title), quote=True),
|
|
511
|
-
message=_html.escape(str(message), quote=True),
|
|
512
|
-
)
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
# ---- Google OAuth SSO Flow ------------------------------------------------
|
|
516
|
-
|
|
517
|
-
@router.get("/api/backup/oauth/google/start")
|
|
518
|
-
def google_oauth_start(request: Request):
|
|
519
|
-
"""Start Google OAuth2 flow — redirects to Google's login page."""
|
|
520
|
-
_require_oauth_start(request)
|
|
521
|
-
if not CLOUD_AVAILABLE:
|
|
522
|
-
return HTMLResponse(_oauth_error_page(icon="⚠", error="Cloud backup module not available"))
|
|
523
|
-
|
|
524
|
-
from superlocalmemory.infra.cloud_backup import _get_credential
|
|
525
|
-
|
|
526
|
-
client_id = _get_credential("gdrive_client_id")
|
|
527
|
-
if not client_id:
|
|
528
|
-
return HTMLResponse("""<!DOCTYPE html>
|
|
529
|
-
<html><head><title>Set Up Google Drive</title>
|
|
530
|
-
<style>
|
|
531
|
-
body { font-family: -apple-system, system-ui, sans-serif; background: #0a0a0f; color: #e0e0e0;
|
|
532
|
-
display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0; padding: 20px; }
|
|
533
|
-
.card { background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.1);
|
|
534
|
-
border-radius: 16px; padding: 32px; max-width: 520px; width: 100%; }
|
|
535
|
-
h2 { color: #e0e0e0; margin: 0 0 4px; font-size: 20px; }
|
|
536
|
-
.sub { color: #999; font-size: 13px; margin-bottom: 20px; }
|
|
537
|
-
.step { padding: 10px 0; border-bottom: 1px solid rgba(255,255,255,0.05); }
|
|
538
|
-
.step-num { display: inline-block; width: 24px; height: 24px; border-radius: 50%;
|
|
539
|
-
background: rgba(0,212,170,0.15); color: #00D4AA; text-align: center; line-height: 24px;
|
|
540
|
-
font-size: 12px; font-weight: 600; margin-right: 8px; }
|
|
541
|
-
.step-text { color: #ccc; font-size: 13px; }
|
|
542
|
-
code { background: rgba(255,255,255,0.08); padding: 2px 6px; border-radius: 4px; font-size: 12px; }
|
|
543
|
-
.fields { margin-top: 20px; }
|
|
544
|
-
label { display: block; color: #bbb; font-size: 12px; margin-bottom: 4px; }
|
|
545
|
-
input { width: 100%; box-sizing: border-box; padding: 8px 10px; border-radius: 8px;
|
|
546
|
-
border: 1px solid rgba(255,255,255,0.12); background: rgba(255,255,255,0.04);
|
|
547
|
-
color: #e0e0e0; font-size: 13px; margin-bottom: 12px; }
|
|
548
|
-
input:focus { outline: none; border-color: #00D4AA; }
|
|
549
|
-
.btn { background: #00D4AA; color: #0a0a0f; border: none; padding: 10px 24px;
|
|
550
|
-
border-radius: 8px; cursor: pointer; font-size: 14px; font-weight: 600; width: 100%; }
|
|
551
|
-
.btn:hover { background: #00b894; }
|
|
552
|
-
.btn:disabled { opacity: 0.5; }
|
|
553
|
-
#status { margin-top: 8px; font-size: 12px; }
|
|
554
|
-
a { color: #00D4AA; }
|
|
555
|
-
</style></head>
|
|
556
|
-
<body><div class="card">
|
|
557
|
-
<h2>Connect Google Drive</h2>
|
|
558
|
-
<p class="sub">Google Drive backup requires a one-time OAuth setup. Follow these steps:</p>
|
|
559
|
-
|
|
560
|
-
<div class="step"><span class="step-num">1</span>
|
|
561
|
-
<span class="step-text">Go to <a href="https://console.cloud.google.com/apis/credentials" target="_blank">Google Cloud Console</a> and create a project</span></div>
|
|
562
|
-
|
|
563
|
-
<div class="step"><span class="step-num">2</span>
|
|
564
|
-
<span class="step-text">Enable <strong>Google Drive API</strong> and <strong>People API</strong></span></div>
|
|
565
|
-
|
|
566
|
-
<div class="step"><span class="step-num">3</span>
|
|
567
|
-
<span class="step-text">Go to <strong>OAuth consent screen</strong> → External → add your email as test user</span></div>
|
|
568
|
-
|
|
569
|
-
<div class="step"><span class="step-num">4</span>
|
|
570
|
-
<span class="step-text">Go to <strong>Credentials</strong> → Create OAuth Client → <strong>Web application</strong></span></div>
|
|
571
|
-
|
|
572
|
-
<div class="step"><span class="step-num">5</span>
|
|
573
|
-
<span class="step-text">Add redirect URI: <code>http://localhost:8765/api/backup/oauth/google/callback</code></span></div>
|
|
574
|
-
|
|
575
|
-
<div class="step" style="border:none"><span class="step-num">6</span>
|
|
576
|
-
<span class="step-text">Paste Client ID and Secret below:</span></div>
|
|
577
|
-
|
|
578
|
-
<div class="fields">
|
|
579
|
-
<label>Client ID</label>
|
|
580
|
-
<input type="text" id="cid" placeholder="xxxx.apps.googleusercontent.com">
|
|
581
|
-
<label>Client Secret</label>
|
|
582
|
-
<input type="password" id="csec" placeholder="GOCSPX-xxxx">
|
|
583
|
-
<button class="btn" id="saveBtn" onclick="saveAndConnect()">Save & Connect Google Drive</button>
|
|
584
|
-
<div id="status"></div>
|
|
585
|
-
</div>
|
|
586
|
-
|
|
587
|
-
<p style="color:#555;font-size:11px;margin-top:16px;">
|
|
588
|
-
Credentials use your OS credential store when available, with a permission-restricted local fallback.
|
|
589
|
-
Full guide: <a href="https://github.com/qualixar/superlocalmemory/wiki/Cloud-Backup#google-drive-backup" target="_blank">Cloud Backup Wiki</a>
|
|
590
|
-
</p>
|
|
591
|
-
</div>
|
|
592
|
-
<script>
|
|
593
|
-
async function saveAndConnect() {
|
|
594
|
-
var cid = document.getElementById('cid').value.trim();
|
|
595
|
-
var csec = document.getElementById('csec').value.trim();
|
|
596
|
-
if (!cid || !csec) { document.getElementById('status').innerHTML = '<span style="color:#ff4757">Both fields required</span>'; return; }
|
|
597
|
-
document.getElementById('saveBtn').disabled = true;
|
|
598
|
-
var status = document.getElementById('status');
|
|
599
|
-
status.style.color = '#999';
|
|
600
|
-
status.textContent = 'Saving...';
|
|
601
|
-
try {
|
|
602
|
-
var tokenResp = await fetch('/internal/token', {credentials: 'same-origin'});
|
|
603
|
-
if (!tokenResp.ok) throw new Error('Could not authorize this dashboard');
|
|
604
|
-
var tokenData = await tokenResp.json();
|
|
605
|
-
if (!tokenData.token) throw new Error('Could not authorize this dashboard');
|
|
606
|
-
var resp = await fetch('/api/backup/connect/gdrive/config', {
|
|
607
|
-
method: 'POST', credentials: 'same-origin',
|
|
608
|
-
headers: {'Content-Type': 'application/json', 'X-Install-Token': tokenData.token},
|
|
609
|
-
body: JSON.stringify({client_id: cid, client_secret: csec})
|
|
610
|
-
});
|
|
611
|
-
if (resp.ok) {
|
|
612
|
-
window.location.href = '/api/backup/oauth/google/start';
|
|
613
|
-
} else {
|
|
614
|
-
status.style.color = '#ff4757';
|
|
615
|
-
status.textContent = 'Failed to save';
|
|
616
|
-
document.getElementById('saveBtn').disabled = false;
|
|
617
|
-
}
|
|
618
|
-
} catch(e) {
|
|
619
|
-
status.style.color = '#ff4757';
|
|
620
|
-
status.textContent = 'Error: ' + e.message;
|
|
621
|
-
document.getElementById('saveBtn').disabled = false;
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
</script></body></html>""")
|
|
625
|
-
|
|
626
|
-
# Build the Google OAuth URL
|
|
627
|
-
base_url = str(request.base_url).rstrip("/")
|
|
628
|
-
redirect_uri = f"{base_url}/api/backup/oauth/google/callback"
|
|
629
|
-
state = _issue_oauth_state("google", request)
|
|
630
|
-
|
|
631
|
-
params = urllib.parse.urlencode({
|
|
632
|
-
"client_id": client_id,
|
|
633
|
-
"redirect_uri": redirect_uri,
|
|
634
|
-
"response_type": "code",
|
|
635
|
-
"scope": "openid https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/userinfo.email",
|
|
636
|
-
"access_type": "offline",
|
|
637
|
-
"prompt": "consent",
|
|
638
|
-
"state": state,
|
|
639
|
-
})
|
|
640
|
-
|
|
641
|
-
return RedirectResponse(f"https://accounts.google.com/o/oauth2/v2/auth?{params}")
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
@router.get("/api/backup/oauth/google/callback")
|
|
645
|
-
def google_oauth_callback(
|
|
646
|
-
request: Request,
|
|
647
|
-
code: str = "",
|
|
648
|
-
error: str = "",
|
|
649
|
-
state: str = "",
|
|
650
|
-
):
|
|
651
|
-
"""Google OAuth2 callback — exchanges code for tokens."""
|
|
652
|
-
if not _consume_oauth_state(state, "google", request):
|
|
653
|
-
return HTMLResponse(
|
|
654
|
-
_oauth_error_page(
|
|
655
|
-
icon="❌",
|
|
656
|
-
error="Invalid, expired, or already-used OAuth state. Start the connection again.",
|
|
657
|
-
),
|
|
658
|
-
status_code=400,
|
|
659
|
-
)
|
|
660
|
-
if error:
|
|
661
|
-
return HTMLResponse(_oauth_error_page(icon="❌", error=f"Google denied access: {error}"))
|
|
662
|
-
|
|
663
|
-
if not code:
|
|
664
|
-
return HTMLResponse(_oauth_error_page(icon="❌", error="No authorization code received"))
|
|
665
|
-
|
|
666
|
-
base_url = str(request.base_url).rstrip("/")
|
|
667
|
-
redirect_uri = f"{base_url}/api/backup/oauth/google/callback"
|
|
668
|
-
|
|
669
|
-
result = connect_google_drive(code, redirect_uri)
|
|
670
|
-
|
|
671
|
-
if "error" in result:
|
|
672
|
-
return HTMLResponse(_oauth_error_page(icon="❌", error=result["error"]))
|
|
673
|
-
|
|
674
|
-
return HTMLResponse(_oauth_success_page(
|
|
675
|
-
icon="☁️",
|
|
676
|
-
title="Google Drive Connected!",
|
|
677
|
-
message=f"Signed in as {result.get('email', 'unknown')}. The destination is configured; sync status will appear after an upload."
|
|
678
|
-
))
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
# ---- GitHub OAuth SSO Flow ------------------------------------------------
|
|
682
|
-
# Uses GitHub Device Flow (no OAuth App needed — works with PATs too)
|
|
683
|
-
# But for best UX, we use GitHub OAuth Web Flow if a GitHub App is configured,
|
|
684
|
-
# otherwise fall back to Device Flow with a nice UI.
|
|
685
|
-
|
|
686
|
-
@router.get("/api/backup/oauth/github/start")
|
|
687
|
-
def github_oauth_start(request: Request):
|
|
688
|
-
"""Start GitHub OAuth flow."""
|
|
689
|
-
_require_oauth_start(request)
|
|
690
|
-
if not CLOUD_AVAILABLE:
|
|
691
|
-
return HTMLResponse(_oauth_error_page(icon="⚠", error="Cloud backup module not available"))
|
|
692
|
-
|
|
693
|
-
from superlocalmemory.infra.cloud_backup import _get_credential
|
|
694
|
-
|
|
695
|
-
# Check if GitHub OAuth App is configured (client_id)
|
|
696
|
-
gh_client_id = _get_credential("github_client_id")
|
|
697
|
-
|
|
698
|
-
if gh_client_id:
|
|
699
|
-
# Full OAuth Web Flow — browser redirects to GitHub login
|
|
700
|
-
base_url = str(request.base_url).rstrip("/")
|
|
701
|
-
redirect_uri = f"{base_url}/api/backup/oauth/github/callback"
|
|
702
|
-
state = _issue_oauth_state("github", request)
|
|
703
|
-
|
|
704
|
-
params = urllib.parse.urlencode({
|
|
705
|
-
"client_id": gh_client_id,
|
|
706
|
-
"redirect_uri": redirect_uri,
|
|
707
|
-
"scope": "repo",
|
|
708
|
-
"state": state,
|
|
709
|
-
})
|
|
710
|
-
return RedirectResponse(f"https://github.com/login/oauth/authorize?{params}")
|
|
711
|
-
|
|
712
|
-
# No OAuth App — show a friendly PAT entry form (still in the popup)
|
|
713
|
-
return HTMLResponse("""<!DOCTYPE html>
|
|
714
|
-
<html><head><title>Connect GitHub</title>
|
|
715
|
-
<style>
|
|
716
|
-
body { font-family: -apple-system, system-ui, sans-serif; background: #0a0a0f; color: #e0e0e0;
|
|
717
|
-
display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; }
|
|
718
|
-
.card { background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.1);
|
|
719
|
-
border-radius: 16px; padding: 32px; max-width: 440px; width: 100%; }
|
|
720
|
-
h2 { color: #e0e0e0; margin: 0 0 4px; font-size: 20px; }
|
|
721
|
-
.sub { color: #999; font-size: 13px; margin-bottom: 20px; }
|
|
722
|
-
label { display: block; color: #bbb; font-size: 13px; margin-bottom: 4px; font-weight: 500; }
|
|
723
|
-
input { width: 100%; box-sizing: border-box; padding: 10px 12px; border-radius: 8px;
|
|
724
|
-
border: 1px solid rgba(255,255,255,0.12); background: rgba(255,255,255,0.04);
|
|
725
|
-
color: #e0e0e0; font-size: 14px; margin-bottom: 16px; }
|
|
726
|
-
input:focus { outline: none; border-color: #00D4AA; }
|
|
727
|
-
.btn { background: #00D4AA; color: #0a0a0f; border: none; padding: 10px 24px;
|
|
728
|
-
border-radius: 8px; cursor: pointer; font-size: 14px; font-weight: 600; width: 100%; }
|
|
729
|
-
.btn:hover { background: #00b894; }
|
|
730
|
-
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
731
|
-
a { color: #00D4AA; }
|
|
732
|
-
.hint { color: #666; font-size: 12px; margin-top: 12px; }
|
|
733
|
-
#status { margin-top: 12px; font-size: 13px; }
|
|
734
|
-
</style></head>
|
|
735
|
-
<body><div class="card">
|
|
736
|
-
<h2>Connect GitHub</h2>
|
|
737
|
-
<p class="sub">Back up your memories to a private GitHub repository.</p>
|
|
738
|
-
|
|
739
|
-
<label>Personal Access Token</label>
|
|
740
|
-
<input type="password" id="pat" placeholder="GitHub personal access token">
|
|
741
|
-
|
|
742
|
-
<label>Repository Name</label>
|
|
743
|
-
<input type="text" id="repo" value="slm-backup" placeholder="slm-backup">
|
|
744
|
-
|
|
745
|
-
<button class="btn" id="connectBtn" onclick="doConnect()">Connect</button>
|
|
746
|
-
<div id="status"></div>
|
|
747
|
-
|
|
748
|
-
<p class="hint">
|
|
749
|
-
Need a token? <a href="https://github.com/settings/tokens/new?scopes=repo&description=SLM+Backup" target="_blank">Create one here</a> (select <code>repo</code> scope).
|
|
750
|
-
Your token uses the OS credential store when available, with a permission-restricted local fallback.
|
|
751
|
-
</p>
|
|
752
|
-
</div>
|
|
753
|
-
<script>
|
|
754
|
-
async function doConnect() {
|
|
755
|
-
var pat = document.getElementById('pat').value.trim();
|
|
756
|
-
var repo = document.getElementById('repo').value.trim();
|
|
757
|
-
var status = document.getElementById('status');
|
|
758
|
-
if (!pat) { status.style.color = '#ff4757'; status.textContent = 'Token required'; return; }
|
|
759
|
-
|
|
760
|
-
var btn = document.getElementById('connectBtn');
|
|
761
|
-
btn.disabled = true; btn.textContent = 'Connecting...';
|
|
762
|
-
status.style.color = '#999';
|
|
763
|
-
status.textContent = 'Verifying token and creating repo...';
|
|
764
|
-
|
|
765
|
-
try {
|
|
766
|
-
var tokenResp = await fetch('/internal/token', {credentials: 'same-origin'});
|
|
767
|
-
if (!tokenResp.ok) throw new Error('Could not authorize this dashboard');
|
|
768
|
-
var tokenData = await tokenResp.json();
|
|
769
|
-
if (!tokenData.token) throw new Error('Could not authorize this dashboard');
|
|
770
|
-
var resp = await fetch('/api/backup/connect/github', {
|
|
771
|
-
method: 'POST',
|
|
772
|
-
credentials: 'same-origin',
|
|
773
|
-
headers: {'Content-Type': 'application/json', 'X-Install-Token': tokenData.token},
|
|
774
|
-
body: JSON.stringify({pat: pat, repo_name: repo || 'slm-backup'})
|
|
775
|
-
});
|
|
776
|
-
var data = await resp.json();
|
|
777
|
-
if (resp.ok) {
|
|
778
|
-
var card = document.createElement('div');
|
|
779
|
-
card.className = 'card';
|
|
780
|
-
card.style.cssText = 'text-align:center;background:rgba(255,255,255,0.05);border:1px solid rgba(0,212,170,0.3);border-radius:16px;padding:40px;max-width:400px;';
|
|
781
|
-
var icon = document.createElement('div');
|
|
782
|
-
icon.style.cssText = 'font-size:48px;margin-bottom:16px;';
|
|
783
|
-
icon.textContent = '\u2705';
|
|
784
|
-
var title = document.createElement('h2');
|
|
785
|
-
title.style.cssText = 'color:#00D4AA;margin:0 0 8px;';
|
|
786
|
-
title.textContent = 'GitHub Connected!';
|
|
787
|
-
var repoText = document.createElement('p');
|
|
788
|
-
repoText.style.cssText = 'color:#999;margin:0 0 20px;';
|
|
789
|
-
repoText.textContent = 'Repository: ' + (data.repo || repo);
|
|
790
|
-
var closeBtn = document.createElement('button');
|
|
791
|
-
closeBtn.className = 'btn';
|
|
792
|
-
closeBtn.style.cssText = 'background:#00D4AA;color:#0a0a0f;border:none;padding:10px 24px;border-radius:8px;cursor:pointer;font-weight:600;';
|
|
793
|
-
closeBtn.textContent = 'Close Window';
|
|
794
|
-
closeBtn.addEventListener('click', function () { window.close(); });
|
|
795
|
-
card.append(icon, title, repoText, closeBtn);
|
|
796
|
-
document.body.replaceChildren(card);
|
|
797
|
-
} else {
|
|
798
|
-
status.style.color = '#ff4757';
|
|
799
|
-
status.textContent = data.detail || 'Connection failed';
|
|
800
|
-
btn.disabled = false; btn.textContent = 'Connect';
|
|
801
|
-
}
|
|
802
|
-
} catch(e) {
|
|
803
|
-
status.style.color = '#ff4757';
|
|
804
|
-
status.textContent = 'Connection failed';
|
|
805
|
-
btn.disabled = false; btn.textContent = 'Connect';
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
</script></body></html>""")
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
@router.get("/api/backup/oauth/github/callback")
|
|
812
|
-
def github_oauth_callback(
|
|
813
|
-
request: Request,
|
|
814
|
-
code: str = "",
|
|
815
|
-
error: str = "",
|
|
816
|
-
state: str = "",
|
|
817
|
-
):
|
|
818
|
-
"""GitHub OAuth callback — exchanges code for access token."""
|
|
819
|
-
if not _consume_oauth_state(state, "github", request):
|
|
820
|
-
return HTMLResponse(
|
|
821
|
-
_oauth_error_page(
|
|
822
|
-
icon="❌",
|
|
823
|
-
error="Invalid, expired, or already-used OAuth state. Start the connection again.",
|
|
824
|
-
),
|
|
825
|
-
status_code=400,
|
|
826
|
-
)
|
|
827
|
-
if error:
|
|
828
|
-
return HTMLResponse(_oauth_error_page(icon="❌", error=f"GitHub denied access: {error}"))
|
|
829
|
-
|
|
830
|
-
if not code:
|
|
831
|
-
return HTMLResponse(_oauth_error_page(icon="❌", error="No authorization code received"))
|
|
832
|
-
|
|
833
|
-
import httpx
|
|
834
|
-
|
|
835
|
-
from superlocalmemory.infra.cloud_backup import _get_credential
|
|
836
|
-
|
|
837
|
-
gh_client_id = _get_credential("github_client_id")
|
|
838
|
-
gh_client_secret = _get_credential("github_client_secret")
|
|
839
|
-
|
|
840
|
-
if not gh_client_id or not gh_client_secret:
|
|
841
|
-
return HTMLResponse(_oauth_error_page(icon="❌", error="GitHub OAuth App not configured"))
|
|
842
|
-
|
|
843
|
-
try:
|
|
844
|
-
# Exchange code for access token
|
|
845
|
-
resp = httpx.post(
|
|
846
|
-
"https://github.com/login/oauth/access_token",
|
|
847
|
-
json={"client_id": gh_client_id, "client_secret": gh_client_secret, "code": code},
|
|
848
|
-
headers={"Accept": "application/json"},
|
|
849
|
-
timeout=15,
|
|
850
|
-
)
|
|
851
|
-
data = resp.json()
|
|
852
|
-
access_token = data.get("access_token")
|
|
853
|
-
if not access_token:
|
|
854
|
-
return HTMLResponse(_oauth_error_page(icon="❌", error=data.get("error_description", "Failed to get access token")))
|
|
855
|
-
|
|
856
|
-
# Use the token to connect
|
|
857
|
-
result = connect_github(access_token, "slm-backup")
|
|
858
|
-
if "error" in result:
|
|
859
|
-
return HTMLResponse(_oauth_error_page(icon="❌", error=result["error"]))
|
|
860
|
-
|
|
861
|
-
return HTMLResponse(_oauth_success_page(
|
|
862
|
-
icon="✅",
|
|
863
|
-
title="GitHub Connected!",
|
|
864
|
-
message=f"Repository: {result.get('repo', 'slm-backup')}. The destination is configured; sync status will appear after an upload."
|
|
865
|
-
))
|
|
866
|
-
|
|
867
|
-
except Exception:
|
|
868
|
-
logger.exception("GitHub OAuth callback failed")
|
|
869
|
-
return HTMLResponse(_oauth_error_page(icon="❌", error="GitHub connection failed"))
|