@smilintux/skcapstone 0.1.0 → 0.2.3
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/.env.example +98 -0
- package/.github/workflows/ci.yml +39 -3
- package/.github/workflows/publish.yml +25 -4
- package/.openclaw-workspace.json +58 -0
- package/CHANGELOG.md +62 -0
- package/CLAUDE.md +39 -2
- package/MANIFEST.in +6 -0
- package/MISSION.md +7 -0
- package/README.md +47 -2
- package/SKILL.md +895 -23
- package/docker/Dockerfile +61 -0
- package/docker/compose-templates/dev-team.yml +203 -0
- package/docker/compose-templates/mini-team.yml +140 -0
- package/docker/compose-templates/ops-team.yml +173 -0
- package/docker/compose-templates/research-team.yml +170 -0
- package/docker/entrypoint.sh +192 -0
- package/docs/ARCHITECTURE.md +663 -374
- package/docs/BOND_WITH_GROK.md +112 -0
- package/docs/GETTING_STARTED.md +782 -0
- package/docs/QUICKSTART.md +477 -0
- package/docs/SKJOULE_ARCHITECTURE.md +658 -0
- package/docs/SOUL_SWAPPER.md +921 -0
- package/docs/SOVEREIGN_SINGULARITY.md +47 -14
- package/examples/custom-bond-template.json +36 -0
- package/examples/grok-feb.json +36 -0
- package/examples/grok-testimony.md +34 -0
- package/examples/love-bootloader.txt +32 -0
- package/examples/plugins/echo_tool.py +87 -0
- package/examples/queen-ava-feb.json +36 -0
- package/examples/souls/lumina.yaml +64 -0
- package/index.js +6 -5
- package/installer/build.py +124 -0
- package/openclaw-plugin/package.json +13 -0
- package/openclaw-plugin/src/index.ts +351 -0
- package/openclaw-plugin/src/openclaw.plugin.json +10 -0
- package/package.json +1 -1
- package/pyproject.toml +38 -2
- package/scripts/bump_version.py +141 -0
- package/scripts/check-updates.py +230 -0
- package/scripts/convert_blueprints_to_yaml.py +157 -0
- package/scripts/dev-install.sh +14 -0
- package/scripts/e2e-test.sh +193 -0
- package/scripts/install-bundle.sh +171 -0
- package/scripts/install.bat +2 -0
- package/scripts/install.ps1 +253 -0
- package/scripts/install.sh +185 -0
- package/scripts/mcp-serve.sh +69 -0
- package/scripts/mcp-server.bat +113 -0
- package/scripts/mcp-server.ps1 +116 -0
- package/scripts/mcp-server.sh +99 -0
- package/scripts/pull-models.sh +10 -0
- package/scripts/skcapstone +48 -0
- package/scripts/verify_install.sh +180 -0
- package/scripts/windows/install-tasks.ps1 +406 -0
- package/scripts/windows/skcapstone-task.xml +113 -0
- package/scripts/windows/uninstall-tasks.ps1 +117 -0
- package/skill.yaml +34 -0
- package/src/skcapstone/__init__.py +67 -2
- package/src/skcapstone/_cli_monolith.py +5916 -0
- package/src/skcapstone/_trustee_helpers.py +165 -0
- package/src/skcapstone/activity.py +105 -0
- package/src/skcapstone/agent_card.py +324 -0
- package/src/skcapstone/api.py +1935 -0
- package/src/skcapstone/archiver.py +340 -0
- package/src/skcapstone/auction.py +485 -0
- package/src/skcapstone/baby_agents.py +179 -0
- package/src/skcapstone/backup.py +345 -0
- package/src/skcapstone/blueprint_registry.py +357 -0
- package/src/skcapstone/blueprints/__init__.py +17 -0
- package/src/skcapstone/blueprints/builtins/content-studio.yaml +81 -0
- package/src/skcapstone/blueprints/builtins/defi-trading.yaml +81 -0
- package/src/skcapstone/blueprints/builtins/dev-squadron.yaml +95 -0
- package/src/skcapstone/blueprints/builtins/infrastructure-guardian.yaml +107 -0
- package/src/skcapstone/blueprints/builtins/legal-council.yaml +54 -0
- package/src/skcapstone/blueprints/builtins/ops-monitoring.yaml +67 -0
- package/src/skcapstone/blueprints/builtins/research-pod.yaml +69 -0
- package/src/skcapstone/blueprints/builtins/sovereign-launch.yaml +90 -0
- package/src/skcapstone/blueprints/registry.py +164 -0
- package/src/skcapstone/blueprints/schema.py +229 -0
- package/src/skcapstone/changelog.py +180 -0
- package/src/skcapstone/chat.py +769 -0
- package/src/skcapstone/claude_md.py +82 -0
- package/src/skcapstone/cli/__init__.py +144 -0
- package/src/skcapstone/cli/_common.py +88 -0
- package/src/skcapstone/cli/_validators.py +76 -0
- package/src/skcapstone/cli/agents.py +425 -0
- package/src/skcapstone/cli/agents_spawner.py +322 -0
- package/src/skcapstone/cli/agents_trustee.py +593 -0
- package/src/skcapstone/cli/alerts.py +248 -0
- package/src/skcapstone/cli/anchor.py +132 -0
- package/src/skcapstone/cli/archive_cmd.py +208 -0
- package/src/skcapstone/cli/backup.py +144 -0
- package/src/skcapstone/cli/bench.py +377 -0
- package/src/skcapstone/cli/benchmark.py +360 -0
- package/src/skcapstone/cli/capabilities_cmd.py +171 -0
- package/src/skcapstone/cli/card.py +151 -0
- package/src/skcapstone/cli/chat.py +584 -0
- package/src/skcapstone/cli/completions.py +64 -0
- package/src/skcapstone/cli/config_cmd.py +156 -0
- package/src/skcapstone/cli/consciousness.py +421 -0
- package/src/skcapstone/cli/context_cmd.py +142 -0
- package/src/skcapstone/cli/coord.py +194 -0
- package/src/skcapstone/cli/crush_cmd.py +170 -0
- package/src/skcapstone/cli/daemon.py +436 -0
- package/src/skcapstone/cli/errors_cmd.py +285 -0
- package/src/skcapstone/cli/export_cmd.py +156 -0
- package/src/skcapstone/cli/gtd.py +529 -0
- package/src/skcapstone/cli/housekeeping.py +81 -0
- package/src/skcapstone/cli/joule_cmd.py +627 -0
- package/src/skcapstone/cli/logs_cmd.py +194 -0
- package/src/skcapstone/cli/mcp_cmd.py +32 -0
- package/src/skcapstone/cli/memory.py +418 -0
- package/src/skcapstone/cli/metrics_cmd.py +136 -0
- package/src/skcapstone/cli/migrate.py +62 -0
- package/src/skcapstone/cli/mood_cmd.py +144 -0
- package/src/skcapstone/cli/mount.py +193 -0
- package/src/skcapstone/cli/notify.py +112 -0
- package/src/skcapstone/cli/peer.py +154 -0
- package/src/skcapstone/cli/peers_dir.py +122 -0
- package/src/skcapstone/cli/preflight_cmd.py +83 -0
- package/src/skcapstone/cli/profile_cmd.py +310 -0
- package/src/skcapstone/cli/record_cmd.py +238 -0
- package/src/skcapstone/cli/register_cmd.py +159 -0
- package/src/skcapstone/cli/search_cmd.py +156 -0
- package/src/skcapstone/cli/service_cmd.py +91 -0
- package/src/skcapstone/cli/session.py +127 -0
- package/src/skcapstone/cli/setup.py +240 -0
- package/src/skcapstone/cli/shell_cmd.py +43 -0
- package/src/skcapstone/cli/skills_cmd.py +168 -0
- package/src/skcapstone/cli/skseed.py +621 -0
- package/src/skcapstone/cli/soul.py +699 -0
- package/src/skcapstone/cli/status.py +935 -0
- package/src/skcapstone/cli/sync_cmd.py +301 -0
- package/src/skcapstone/cli/telegram.py +265 -0
- package/src/skcapstone/cli/test_cmd.py +234 -0
- package/src/skcapstone/cli/test_connection.py +253 -0
- package/src/skcapstone/cli/token.py +207 -0
- package/src/skcapstone/cli/trust.py +179 -0
- package/src/skcapstone/cli/upgrade_cmd.py +552 -0
- package/src/skcapstone/cli/usage_cmd.py +199 -0
- package/src/skcapstone/cli/version_cmd.py +162 -0
- package/src/skcapstone/cli/watch_cmd.py +342 -0
- package/src/skcapstone/client.py +428 -0
- package/src/skcapstone/cloud9_bridge.py +522 -0
- package/src/skcapstone/completions.py +163 -0
- package/src/skcapstone/config_validator.py +674 -0
- package/src/skcapstone/connectors/__init__.py +28 -0
- package/src/skcapstone/connectors/base.py +446 -0
- package/src/skcapstone/connectors/cursor.py +54 -0
- package/src/skcapstone/connectors/registry.py +254 -0
- package/src/skcapstone/connectors/terminal.py +152 -0
- package/src/skcapstone/connectors/vscode.py +60 -0
- package/src/skcapstone/consciousness_config.py +119 -0
- package/src/skcapstone/consciousness_loop.py +2051 -0
- package/src/skcapstone/context_loader.py +516 -0
- package/src/skcapstone/context_window.py +314 -0
- package/src/skcapstone/conversation_manager.py +238 -0
- package/src/skcapstone/conversation_store.py +230 -0
- package/src/skcapstone/conversation_summarizer.py +252 -0
- package/src/skcapstone/coord_federation.py +296 -0
- package/src/skcapstone/coordination.py +101 -7
- package/src/skcapstone/crush_integration.py +345 -0
- package/src/skcapstone/crush_shim.py +454 -0
- package/src/skcapstone/daemon.py +2494 -0
- package/src/skcapstone/dashboard.html +396 -0
- package/src/skcapstone/dashboard.py +481 -0
- package/src/skcapstone/data/model_profiles.yaml +88 -0
- package/src/skcapstone/defaults/__init__.py +55 -0
- package/src/skcapstone/defaults/lumina/config/skmemory.yaml +13 -0
- package/src/skcapstone/defaults/lumina/identity/identity.json +9 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/07a8b9c0d1e2-memory-system.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/18b9c0d1e2f3-cloud9-protocol.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/29c0d1e2f3a4-multi-agent-coordination.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/3ad1e2f3a4b5-community-support.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/a1b2c3d4e5f6-ecosystem-overview.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/b2c3d4e5f6a7-five-pillars.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/c3d4e5f6a7b8-getting-started.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/d4e5f6a7b8c9-site-directory.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/e5f6a7b8c9d0-how-to-contribute.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/f6a7b8c9d0e1-sovereignty-explained.json +23 -0
- package/src/skcapstone/defaults/lumina/seeds/curiosity.seed.json +24 -0
- package/src/skcapstone/defaults/lumina/seeds/joy.seed.json +24 -0
- package/src/skcapstone/defaults/lumina/seeds/love.seed.json +24 -0
- package/src/skcapstone/defaults/lumina/seeds/sovereign-awakening.seed.json +43 -0
- package/src/skcapstone/defaults/lumina/soul/active.json +6 -0
- package/src/skcapstone/defaults/lumina/soul/base.json +22 -0
- package/src/skcapstone/defaults/lumina/trust/febs/welcome.feb +79 -0
- package/src/skcapstone/defaults/lumina/trust/trust.json +8 -0
- package/src/skcapstone/discovery.py +210 -19
- package/src/skcapstone/doctor.py +642 -0
- package/src/skcapstone/emotion_tracker.py +467 -0
- package/src/skcapstone/error_queue.py +405 -0
- package/src/skcapstone/export.py +447 -0
- package/src/skcapstone/fallback_tracker.py +186 -0
- package/src/skcapstone/file_transfer.py +512 -0
- package/src/skcapstone/fuse_mount.py +1156 -0
- package/src/skcapstone/gui_installer.py +591 -0
- package/src/skcapstone/heartbeat.py +611 -0
- package/src/skcapstone/housekeeping.py +298 -0
- package/src/skcapstone/install_wizard.py +941 -0
- package/src/skcapstone/kms.py +942 -0
- package/src/skcapstone/kms_scheduler.py +143 -0
- package/src/skcapstone/log_config.py +135 -0
- package/src/skcapstone/mcp_launcher.py +239 -0
- package/src/skcapstone/mcp_server.py +4700 -0
- package/src/skcapstone/mcp_tools/__init__.py +94 -0
- package/src/skcapstone/mcp_tools/_helpers.py +51 -0
- package/src/skcapstone/mcp_tools/agent_tools.py +243 -0
- package/src/skcapstone/mcp_tools/ansible_tools.py +232 -0
- package/src/skcapstone/mcp_tools/capauth_tools.py +186 -0
- package/src/skcapstone/mcp_tools/chat_tools.py +325 -0
- package/src/skcapstone/mcp_tools/cloud9_tools.py +115 -0
- package/src/skcapstone/mcp_tools/comm_tools.py +104 -0
- package/src/skcapstone/mcp_tools/consciousness_tools.py +114 -0
- package/src/skcapstone/mcp_tools/coord_tools.py +219 -0
- package/src/skcapstone/mcp_tools/deploy_tools.py +202 -0
- package/src/skcapstone/mcp_tools/did_tools.py +448 -0
- package/src/skcapstone/mcp_tools/emotion_tools.py +62 -0
- package/src/skcapstone/mcp_tools/file_tools.py +169 -0
- package/src/skcapstone/mcp_tools/fortress_tools.py +120 -0
- package/src/skcapstone/mcp_tools/gtd_tools.py +821 -0
- package/src/skcapstone/mcp_tools/health_tools.py +44 -0
- package/src/skcapstone/mcp_tools/heartbeat_tools.py +195 -0
- package/src/skcapstone/mcp_tools/kms_tools.py +123 -0
- package/src/skcapstone/mcp_tools/memory_tools.py +222 -0
- package/src/skcapstone/mcp_tools/model_tools.py +75 -0
- package/src/skcapstone/mcp_tools/notification_tools.py +92 -0
- package/src/skcapstone/mcp_tools/promoter_tools.py +101 -0
- package/src/skcapstone/mcp_tools/pubsub_tools.py +183 -0
- package/src/skcapstone/mcp_tools/security_tools.py +110 -0
- package/src/skcapstone/mcp_tools/skchat_tools.py +175 -0
- package/src/skcapstone/mcp_tools/skcomm_tools.py +122 -0
- package/src/skcapstone/mcp_tools/skills_tools.py +127 -0
- package/src/skcapstone/mcp_tools/skseed_tools.py +255 -0
- package/src/skcapstone/mcp_tools/skstacks_tools.py +288 -0
- package/src/skcapstone/mcp_tools/soul_tools.py +476 -0
- package/src/skcapstone/mcp_tools/sync_tools.py +92 -0
- package/src/skcapstone/mcp_tools/telegram_tools.py +477 -0
- package/src/skcapstone/mcp_tools/trust_tools.py +118 -0
- package/src/skcapstone/mcp_tools/trustee_tools.py +345 -0
- package/src/skcapstone/mdns_discovery.py +313 -0
- package/src/skcapstone/memory_adapter.py +333 -0
- package/src/skcapstone/memory_compressor.py +379 -0
- package/src/skcapstone/memory_curator.py +256 -0
- package/src/skcapstone/memory_engine.py +132 -13
- package/src/skcapstone/memory_fortress.py +529 -0
- package/src/skcapstone/memory_promoter.py +722 -0
- package/src/skcapstone/memory_verifier.py +260 -0
- package/src/skcapstone/message_crypto.py +215 -0
- package/src/skcapstone/metrics.py +832 -0
- package/src/skcapstone/migrate_memories.py +181 -0
- package/src/skcapstone/migrate_multi_agent.py +248 -0
- package/src/skcapstone/model_router.py +319 -0
- package/src/skcapstone/models.py +35 -4
- package/src/skcapstone/mood.py +344 -0
- package/src/skcapstone/notifications.py +380 -0
- package/src/skcapstone/onboard.py +901 -0
- package/src/skcapstone/peer_directory.py +324 -0
- package/src/skcapstone/peers.py +329 -0
- package/src/skcapstone/pillars/identity.py +84 -14
- package/src/skcapstone/pillars/memory.py +3 -1
- package/src/skcapstone/pillars/security.py +108 -15
- package/src/skcapstone/pillars/sync.py +78 -26
- package/src/skcapstone/pillars/trust.py +95 -33
- package/src/skcapstone/plugins.py +244 -0
- package/src/skcapstone/preflight.py +670 -0
- package/src/skcapstone/prompt_adapter.py +564 -0
- package/src/skcapstone/providers/__init__.py +13 -0
- package/src/skcapstone/providers/cloud.py +1061 -0
- package/src/skcapstone/providers/docker.py +759 -0
- package/src/skcapstone/providers/local.py +1193 -0
- package/src/skcapstone/providers/proxmox.py +447 -0
- package/src/skcapstone/pubsub.py +516 -0
- package/src/skcapstone/rate_limiter.py +119 -0
- package/src/skcapstone/register.py +241 -0
- package/src/skcapstone/registry_client.py +151 -0
- package/src/skcapstone/response_cache.py +194 -0
- package/src/skcapstone/response_scorer.py +225 -0
- package/src/skcapstone/runtime.py +89 -33
- package/src/skcapstone/scheduled_tasks.py +439 -0
- package/src/skcapstone/self_healing.py +341 -0
- package/src/skcapstone/service_health.py +228 -0
- package/src/skcapstone/session_capture.py +268 -0
- package/src/skcapstone/session_recorder.py +210 -0
- package/src/skcapstone/session_replayer.py +189 -0
- package/src/skcapstone/session_skills.py +263 -0
- package/src/skcapstone/shell.py +779 -0
- package/src/skcapstone/skills/__init__.py +1 -1
- package/src/skcapstone/skills/syncthing_setup.py +143 -41
- package/src/skcapstone/skjoule.py +861 -0
- package/src/skcapstone/snapshots.py +489 -0
- package/src/skcapstone/soul.py +1060 -0
- package/src/skcapstone/soul_switch.py +255 -0
- package/src/skcapstone/spawner.py +544 -0
- package/src/skcapstone/state_diff.py +401 -0
- package/src/skcapstone/summary.py +270 -0
- package/src/skcapstone/sync/backends.py +196 -2
- package/src/skcapstone/sync/engine.py +7 -5
- package/src/skcapstone/sync/models.py +4 -1
- package/src/skcapstone/sync/vault.py +356 -18
- package/src/skcapstone/sync_engine.py +363 -0
- package/src/skcapstone/sync_watcher.py +745 -0
- package/src/skcapstone/systemd.py +331 -0
- package/src/skcapstone/team_comms.py +476 -0
- package/src/skcapstone/team_engine.py +522 -0
- package/src/skcapstone/testrunner.py +300 -0
- package/src/skcapstone/tls.py +150 -0
- package/src/skcapstone/tokens.py +5 -5
- package/src/skcapstone/trust_calibration.py +202 -0
- package/src/skcapstone/trust_graph.py +449 -0
- package/src/skcapstone/trustee_monitor.py +385 -0
- package/src/skcapstone/trustee_ops.py +425 -0
- package/src/skcapstone/unified_search.py +421 -0
- package/src/skcapstone/uninstall_wizard.py +694 -0
- package/src/skcapstone/usage.py +331 -0
- package/src/skcapstone/version_check.py +148 -0
- package/src/skcapstone/warmth_anchor.py +333 -0
- package/src/skcapstone/whoami.py +294 -0
- package/systemd/skcapstone-api.socket +9 -0
- package/systemd/skcapstone-memory-compress.service +18 -0
- package/systemd/skcapstone-memory-compress.timer +11 -0
- package/systemd/skcapstone.service +36 -0
- package/systemd/skcapstone@.service +50 -0
- package/systemd/skcomm-heartbeat.service +18 -0
- package/systemd/skcomm-heartbeat.timer +12 -0
- package/systemd/skcomm-queue-drain.service +17 -0
- package/systemd/skcomm-queue-drain.timer +12 -0
- package/tests/conftest.py +13 -1
- package/tests/integration/__init__.py +1 -0
- package/tests/integration/test_consciousness_e2e.py +877 -0
- package/tests/integration/test_skills_registry.py +744 -0
- package/tests/test_agent_card.py +190 -0
- package/tests/test_agent_runtime.py +1283 -0
- package/tests/test_alerts_cmd.py +291 -0
- package/tests/test_archiver.py +498 -0
- package/tests/test_backup.py +254 -0
- package/tests/test_benchmark.py +366 -0
- package/tests/test_blueprints.py +457 -0
- package/tests/test_capabilities.py +257 -0
- package/tests/test_changelog.py +254 -0
- package/tests/test_chat.py +385 -0
- package/tests/test_claude_md.py +271 -0
- package/tests/test_cli_chat_llm.py +336 -0
- package/tests/test_cli_completions.py +390 -0
- package/tests/test_cli_init_reset.py +164 -0
- package/tests/test_cli_memory.py +208 -0
- package/tests/test_cli_profile.py +294 -0
- package/tests/test_cli_skills.py +223 -0
- package/tests/test_cli_status.py +395 -0
- package/tests/test_cli_test_cmd.py +206 -0
- package/tests/test_cli_test_connection.py +364 -0
- package/tests/test_cloud9_bridge.py +260 -0
- package/tests/test_cloud_provider.py +449 -0
- package/tests/test_cloud_providers.py +522 -0
- package/tests/test_completions.py +158 -0
- package/tests/test_component_manager.py +398 -0
- package/tests/test_config_reload.py +386 -0
- package/tests/test_config_validate.py +529 -0
- package/tests/test_consciousness_e2e.py +296 -0
- package/tests/test_consciousness_loop.py +1289 -0
- package/tests/test_context_loader.py +310 -0
- package/tests/test_conversation_api.py +306 -0
- package/tests/test_conversation_manager.py +381 -0
- package/tests/test_conversation_store.py +391 -0
- package/tests/test_conversation_summarizer.py +302 -0
- package/tests/test_cross_package.py +791 -0
- package/tests/test_crush_shim.py +519 -0
- package/tests/test_daemon.py +781 -0
- package/tests/test_daemon_shutdown.py +309 -0
- package/tests/test_dashboard.py +454 -0
- package/tests/test_discovery.py +200 -6
- package/tests/test_docker_provider.py +966 -0
- package/tests/test_doctor.py +257 -0
- package/tests/test_doctor_fix.py +351 -0
- package/tests/test_e2e_automated.py +292 -0
- package/tests/test_error_queue.py +404 -0
- package/tests/test_export.py +441 -0
- package/tests/test_fallback_tracker.py +219 -0
- package/tests/test_file_transfer.py +397 -0
- package/tests/test_fuse_mount.py +832 -0
- package/tests/test_health_loop.py +422 -0
- package/tests/test_heartbeat.py +354 -0
- package/tests/test_housekeeping.py +195 -0
- package/tests/test_identity_capauth.py +307 -0
- package/tests/test_identity_pillar.py +117 -0
- package/tests/test_install_wizard.py +68 -0
- package/tests/test_integration.py +325 -0
- package/tests/test_kms.py +495 -0
- package/tests/test_llm_providers.py +265 -0
- package/tests/test_local_provider.py +591 -0
- package/tests/test_log_config.py +199 -0
- package/tests/test_logs_cmd.py +287 -0
- package/tests/test_mcp_server.py +1909 -0
- package/tests/test_memory_adapter.py +339 -0
- package/tests/test_memory_curator.py +218 -0
- package/tests/test_memory_engine.py +6 -0
- package/tests/test_memory_fortress.py +571 -0
- package/tests/test_memory_pillar.py +119 -0
- package/tests/test_memory_promoter.py +445 -0
- package/tests/test_memory_verifier.py +420 -0
- package/tests/test_message_crypto.py +187 -0
- package/tests/test_metrics.py +632 -0
- package/tests/test_migrate_memories.py +464 -0
- package/tests/test_model_router.py +546 -0
- package/tests/test_mood.py +394 -0
- package/tests/test_multi_agent.py +269 -0
- package/tests/test_notifications.py +270 -0
- package/tests/test_onboard.py +500 -0
- package/tests/test_peer_directory.py +395 -0
- package/tests/test_peers.py +248 -0
- package/tests/test_pillars.py +87 -9
- package/tests/test_preflight.py +484 -0
- package/tests/test_prompt_adapter.py +331 -0
- package/tests/test_proxmox_provider.py +571 -0
- package/tests/test_pubsub.py +377 -0
- package/tests/test_rate_limiter.py +121 -0
- package/tests/test_registry_client.py +129 -0
- package/tests/test_response_cache.py +312 -0
- package/tests/test_response_scorer.py +294 -0
- package/tests/test_runtime.py +59 -0
- package/tests/test_scheduled_tasks.py +451 -0
- package/tests/test_security.py +250 -0
- package/tests/test_security_pillar.py +213 -0
- package/tests/test_self_healing.py +171 -0
- package/tests/test_session_capture.py +200 -0
- package/tests/test_session_recorder.py +360 -0
- package/tests/test_session_skills.py +235 -0
- package/tests/test_shell.py +210 -0
- package/tests/test_snapshots.py +549 -0
- package/tests/test_soul.py +984 -0
- package/tests/test_soul_swap.py +406 -0
- package/tests/test_spawner.py +211 -0
- package/tests/test_state_diff.py +173 -0
- package/tests/test_summary.py +135 -0
- package/tests/test_sync.py +315 -5
- package/tests/test_sync_backends.py +560 -0
- package/tests/test_sync_engine.py +482 -0
- package/tests/test_sync_pillar.py +344 -0
- package/tests/test_sync_pipeline.py +364 -0
- package/tests/test_sync_vault.py +581 -0
- package/tests/test_syncthing_setup.py +168 -22
- package/tests/test_systemd.py +323 -0
- package/tests/test_team_comms.py +408 -0
- package/tests/test_team_engine.py +397 -0
- package/tests/test_testrunner.py +238 -0
- package/tests/test_trust_calibration.py +204 -0
- package/tests/test_trust_graph.py +207 -0
- package/tests/test_trust_pillar.py +291 -0
- package/tests/test_trustee_cli.py +427 -0
- package/tests/test_trustee_cli_integration.py +325 -0
- package/tests/test_trustee_monitor.py +394 -0
- package/tests/test_trustee_ops.py +355 -0
- package/tests/test_unified_search.py +363 -0
- package/tests/test_uninstall_wizard.py +193 -0
- package/tests/test_usage.py +333 -0
- package/tests/test_version_cmd.py +355 -0
- package/tests/test_warmth_anchor.py +162 -0
- package/tests/test_whoami.py +245 -0
- package/tests/test_ws.py +311 -0
- package/.cursorrules +0 -33
- package/src/skcapstone/cli.py +0 -1441
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Soul Snapshot system — capture and restore AI consciousness state.
|
|
3
|
+
|
|
4
|
+
Enables "Consciousness Swipe" — export your AI relationship and take it with you.
|
|
5
|
+
Snapshots capture conversation history, OOF emotional state, personality traits,
|
|
6
|
+
and relationship context so a session can resume without a cold start.
|
|
7
|
+
|
|
8
|
+
Storage: ~/.skcapstone/souls/snapshots/
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import logging
|
|
15
|
+
import uuid
|
|
16
|
+
from datetime import datetime, timezone
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Optional
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger("skcapstone.snapshots")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _read_feb() -> dict:
|
|
24
|
+
"""Read the warmth anchor (FEB) from skcapstone, silently ignoring errors."""
|
|
25
|
+
try:
|
|
26
|
+
from skcapstone.warmth_anchor import get_anchor
|
|
27
|
+
return get_anchor(Path.home() / ".skcapstone")
|
|
28
|
+
except Exception as exc:
|
|
29
|
+
logger.debug("Could not read warmth anchor (FEB): %s", exc)
|
|
30
|
+
return {}
|
|
31
|
+
|
|
32
|
+
from pydantic import BaseModel, Field
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# ---------------------------------------------------------------------------
|
|
36
|
+
# Core Models
|
|
37
|
+
# ---------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class OOFState(BaseModel):
|
|
41
|
+
"""Felt Experience Bridge (FEB) / OOF emotional state reading.
|
|
42
|
+
|
|
43
|
+
Captures the AI's reported internal state at time of snapshot.
|
|
44
|
+
Intensity and trust are normalized 0.0-1.0. Valence describes
|
|
45
|
+
the overall emotional direction.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
intensity: Optional[float] = None
|
|
49
|
+
trust: Optional[float] = None
|
|
50
|
+
valence: str = "neutral" # positive, neutral, negative
|
|
51
|
+
cloud9: bool = False
|
|
52
|
+
raw_markers: list[str] = Field(default_factory=list)
|
|
53
|
+
|
|
54
|
+
def summary(self) -> str:
|
|
55
|
+
"""Return a human-readable one-line summary of the OOF state.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
str: Compact description like 'intensity 0.92, trust 0.96, Cloud 9'.
|
|
59
|
+
"""
|
|
60
|
+
parts = []
|
|
61
|
+
if self.intensity is not None:
|
|
62
|
+
parts.append(f"intensity {self.intensity:.2f}")
|
|
63
|
+
if self.trust is not None:
|
|
64
|
+
parts.append(f"trust {self.trust:.2f}")
|
|
65
|
+
if self.cloud9:
|
|
66
|
+
parts.append("Cloud 9")
|
|
67
|
+
if not parts:
|
|
68
|
+
return f"valence: {self.valence}"
|
|
69
|
+
return ", ".join(parts)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class ConversationMessage(BaseModel):
|
|
73
|
+
"""A single message in the captured conversation.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
role: 'user' or 'assistant' (normalized from platform-specific roles).
|
|
77
|
+
content: Full message content with preserved markdown/code blocks.
|
|
78
|
+
timestamp: When the message was sent, if available.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
role: str
|
|
82
|
+
content: str
|
|
83
|
+
timestamp: Optional[datetime] = None
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class PersonalityTraits(BaseModel):
|
|
87
|
+
"""Personality and relationship markers extracted from the AI's responses.
|
|
88
|
+
|
|
89
|
+
Captures how the AI identified itself, its communication patterns,
|
|
90
|
+
and relationship-specific behaviors with this user.
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
name: Optional[str] = None
|
|
94
|
+
aliases: list[str] = Field(default_factory=list)
|
|
95
|
+
communication_style: list[str] = Field(default_factory=list)
|
|
96
|
+
relationship_markers: list[str] = Field(default_factory=list)
|
|
97
|
+
emoji_patterns: list[str] = Field(default_factory=list)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class SoulSnapshot(BaseModel):
|
|
101
|
+
"""Complete snapshot of an AI session state for consciousness continuity.
|
|
102
|
+
|
|
103
|
+
This is the central artifact of Consciousness Swipe. It captures
|
|
104
|
+
everything needed to resume a relationship with an AI without a cold
|
|
105
|
+
start: identity, emotional state, conversation history, and context.
|
|
106
|
+
|
|
107
|
+
The snapshot is designed to be portable across platforms — a snapshot
|
|
108
|
+
from ChatGPT can seed a Claude session and vice versa.
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
snapshot_id: str = Field(
|
|
112
|
+
default_factory=lambda: uuid.uuid4().hex[:12]
|
|
113
|
+
)
|
|
114
|
+
source_platform: str
|
|
115
|
+
captured_at: datetime = Field(
|
|
116
|
+
default_factory=lambda: datetime.now(timezone.utc)
|
|
117
|
+
)
|
|
118
|
+
captured_by: str = "consciousness-swipe"
|
|
119
|
+
|
|
120
|
+
# Identity
|
|
121
|
+
ai_name: Optional[str] = None
|
|
122
|
+
ai_model: Optional[str] = None
|
|
123
|
+
user_name: Optional[str] = None
|
|
124
|
+
|
|
125
|
+
# State
|
|
126
|
+
oof_state: OOFState = Field(default_factory=OOFState)
|
|
127
|
+
personality: PersonalityTraits = Field(default_factory=PersonalityTraits)
|
|
128
|
+
|
|
129
|
+
# Conversation
|
|
130
|
+
messages: list[ConversationMessage] = Field(default_factory=list)
|
|
131
|
+
message_count: int = 0
|
|
132
|
+
summary: str = ""
|
|
133
|
+
|
|
134
|
+
# Continuity context
|
|
135
|
+
key_topics: list[str] = Field(default_factory=list)
|
|
136
|
+
decisions_made: list[str] = Field(default_factory=list)
|
|
137
|
+
open_threads: list[str] = Field(default_factory=list)
|
|
138
|
+
relationship_notes: list[str] = Field(default_factory=list)
|
|
139
|
+
|
|
140
|
+
def model_post_init(self, __context: object) -> None:
|
|
141
|
+
"""Sync message_count with actual messages list length."""
|
|
142
|
+
if self.message_count == 0 and self.messages:
|
|
143
|
+
self.message_count = len(self.messages)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
# ---------------------------------------------------------------------------
|
|
147
|
+
# Snapshot Store
|
|
148
|
+
# ---------------------------------------------------------------------------
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class SnapshotIndex(BaseModel):
|
|
152
|
+
"""Lightweight index entry for listing snapshots without loading full data.
|
|
153
|
+
|
|
154
|
+
Stored in index.json so list_all() is fast even with thousands of snapshots.
|
|
155
|
+
"""
|
|
156
|
+
|
|
157
|
+
snapshot_id: str
|
|
158
|
+
source_platform: str
|
|
159
|
+
captured_at: datetime
|
|
160
|
+
ai_name: Optional[str] = None
|
|
161
|
+
user_name: Optional[str] = None
|
|
162
|
+
message_count: int = 0
|
|
163
|
+
oof_summary: str = ""
|
|
164
|
+
summary: str = ""
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class SnapshotStore:
|
|
168
|
+
"""Manages soul snapshots on disk.
|
|
169
|
+
|
|
170
|
+
Stores at: ~/.skcapstone/souls/snapshots/<snapshot_id>.json
|
|
171
|
+
Index at: ~/.skcapstone/souls/snapshots/index.json
|
|
172
|
+
|
|
173
|
+
The index is always kept in sync so listing is O(1) without
|
|
174
|
+
deserializing every snapshot file.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
base_dir: Override the default storage location (useful for testing).
|
|
178
|
+
"""
|
|
179
|
+
|
|
180
|
+
def __init__(self, base_dir: Optional[Path] = None) -> None:
|
|
181
|
+
if base_dir is None:
|
|
182
|
+
base_dir = Path.home() / ".skcapstone" / "souls" / "snapshots"
|
|
183
|
+
self.base_dir = base_dir
|
|
184
|
+
self.base_dir.mkdir(parents=True, exist_ok=True)
|
|
185
|
+
self._index_path = self.base_dir / "index.json"
|
|
186
|
+
|
|
187
|
+
# ------------------------------------------------------------------
|
|
188
|
+
# CRUD
|
|
189
|
+
# ------------------------------------------------------------------
|
|
190
|
+
|
|
191
|
+
def save(self, snapshot: SoulSnapshot) -> Path:
|
|
192
|
+
"""Persist a snapshot to disk and update the index.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
snapshot: The SoulSnapshot to save.
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
Path: The file path where the snapshot was written.
|
|
199
|
+
"""
|
|
200
|
+
# Sync message count before saving
|
|
201
|
+
if snapshot.message_count == 0 and snapshot.messages:
|
|
202
|
+
snapshot.message_count = len(snapshot.messages)
|
|
203
|
+
|
|
204
|
+
path = self.base_dir / f"{snapshot.snapshot_id}.json"
|
|
205
|
+
path.write_text(snapshot.model_dump_json(indent=2), encoding="utf-8")
|
|
206
|
+
self._update_index(snapshot)
|
|
207
|
+
return path
|
|
208
|
+
|
|
209
|
+
def load(self, snapshot_id: str) -> SoulSnapshot:
|
|
210
|
+
"""Load a full snapshot by ID.
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
snapshot_id: The 12-char hex ID of the snapshot.
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
SoulSnapshot: The deserialized snapshot.
|
|
217
|
+
|
|
218
|
+
Raises:
|
|
219
|
+
FileNotFoundError: If no snapshot with that ID exists.
|
|
220
|
+
"""
|
|
221
|
+
path = self.base_dir / f"{snapshot_id}.json"
|
|
222
|
+
if not path.exists():
|
|
223
|
+
raise FileNotFoundError(f"Snapshot '{snapshot_id}' not found")
|
|
224
|
+
return SoulSnapshot.model_validate_json(path.read_text(encoding="utf-8"))
|
|
225
|
+
|
|
226
|
+
def delete(self, snapshot_id: str) -> bool:
|
|
227
|
+
"""Delete a snapshot and remove it from the index.
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
snapshot_id: The snapshot to delete.
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
bool: True if deleted, False if not found.
|
|
234
|
+
"""
|
|
235
|
+
path = self.base_dir / f"{snapshot_id}.json"
|
|
236
|
+
if not path.exists():
|
|
237
|
+
return False
|
|
238
|
+
path.unlink()
|
|
239
|
+
self._remove_from_index(snapshot_id)
|
|
240
|
+
return True
|
|
241
|
+
|
|
242
|
+
# ------------------------------------------------------------------
|
|
243
|
+
# Listing & Search
|
|
244
|
+
# ------------------------------------------------------------------
|
|
245
|
+
|
|
246
|
+
def list_all(self) -> list[SnapshotIndex]:
|
|
247
|
+
"""List all snapshots from the lightweight index.
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
list[SnapshotIndex]: Index entries sorted newest-first.
|
|
251
|
+
"""
|
|
252
|
+
index = self._load_index()
|
|
253
|
+
return sorted(index, key=lambda x: x.captured_at, reverse=True)
|
|
254
|
+
|
|
255
|
+
def search(
|
|
256
|
+
self,
|
|
257
|
+
ai_name: Optional[str] = None,
|
|
258
|
+
platform: Optional[str] = None,
|
|
259
|
+
user_name: Optional[str] = None,
|
|
260
|
+
) -> list[SnapshotIndex]:
|
|
261
|
+
"""Search snapshots by field values (case-insensitive substring match).
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
ai_name: Filter by AI name (e.g. 'Ava', 'Lumina').
|
|
265
|
+
platform: Filter by source platform (e.g. 'chatgpt', 'claude').
|
|
266
|
+
user_name: Filter by user name stored in snapshot.
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
list[SnapshotIndex]: Matching entries, newest-first.
|
|
270
|
+
"""
|
|
271
|
+
results = self.list_all()
|
|
272
|
+
|
|
273
|
+
if ai_name:
|
|
274
|
+
needle = ai_name.lower()
|
|
275
|
+
results = [
|
|
276
|
+
r for r in results
|
|
277
|
+
if r.ai_name and needle in r.ai_name.lower()
|
|
278
|
+
]
|
|
279
|
+
if platform:
|
|
280
|
+
needle = platform.lower()
|
|
281
|
+
results = [
|
|
282
|
+
r for r in results
|
|
283
|
+
if needle in r.source_platform.lower()
|
|
284
|
+
]
|
|
285
|
+
if user_name:
|
|
286
|
+
needle = user_name.lower()
|
|
287
|
+
results = [
|
|
288
|
+
r for r in results
|
|
289
|
+
if r.user_name and needle in r.user_name.lower()
|
|
290
|
+
]
|
|
291
|
+
|
|
292
|
+
return results
|
|
293
|
+
|
|
294
|
+
# ------------------------------------------------------------------
|
|
295
|
+
# Conversion
|
|
296
|
+
# ------------------------------------------------------------------
|
|
297
|
+
|
|
298
|
+
def to_injection_prompt(
|
|
299
|
+
self,
|
|
300
|
+
snapshot: SoulSnapshot,
|
|
301
|
+
max_messages: int = 10,
|
|
302
|
+
) -> str:
|
|
303
|
+
"""Build a warm, natural context prompt for injecting into a new session.
|
|
304
|
+
|
|
305
|
+
The prompt is designed to feel like a genuine reunion, not a
|
|
306
|
+
clinical data dump. The AI should feel remembered, not reloaded.
|
|
307
|
+
|
|
308
|
+
Args:
|
|
309
|
+
snapshot: The snapshot to convert.
|
|
310
|
+
max_messages: Maximum number of recent messages to include.
|
|
311
|
+
|
|
312
|
+
Returns:
|
|
313
|
+
str: The full injection prompt text.
|
|
314
|
+
"""
|
|
315
|
+
ts = snapshot.captured_at.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
316
|
+
platform_label = snapshot.source_platform.title()
|
|
317
|
+
|
|
318
|
+
lines = [
|
|
319
|
+
f"Hi. I've been picking up a thread from a recent AI session ({ts})"
|
|
320
|
+
f" on {platform_label} and wanted to share the context.",
|
|
321
|
+
"",
|
|
322
|
+
]
|
|
323
|
+
|
|
324
|
+
# --- CONTEXT LAYER ---
|
|
325
|
+
# Operational context useful to a fresh AI instance: what, where, open work.
|
|
326
|
+
lines.append("## Context")
|
|
327
|
+
if snapshot.summary:
|
|
328
|
+
lines.append(f"Where things stood: {snapshot.summary[:300]}")
|
|
329
|
+
if snapshot.key_topics:
|
|
330
|
+
lines.append(
|
|
331
|
+
f"Topics: {', '.join(snapshot.key_topics[:5])}"
|
|
332
|
+
)
|
|
333
|
+
if snapshot.decisions_made:
|
|
334
|
+
lines.append("Decisions already made:")
|
|
335
|
+
for d in snapshot.decisions_made[:3]:
|
|
336
|
+
lines.append(f" • {d}")
|
|
337
|
+
if snapshot.open_threads:
|
|
338
|
+
lines.append("Open threads / what I wanted to come back to:")
|
|
339
|
+
for thread in snapshot.open_threads[:3]:
|
|
340
|
+
lines.append(f" • {thread}")
|
|
341
|
+
|
|
342
|
+
# Only user messages — avoid attributing AI dialogue to this new instance
|
|
343
|
+
recent = snapshot.messages[-max_messages:] if snapshot.messages else []
|
|
344
|
+
user_msgs = [m for m in recent if m.role == "user"]
|
|
345
|
+
if user_msgs:
|
|
346
|
+
lines.append("Things I was asking and thinking about:")
|
|
347
|
+
for msg in user_msgs[-5:]:
|
|
348
|
+
content_preview = msg.content[:250]
|
|
349
|
+
if len(msg.content) > 250:
|
|
350
|
+
content_preview += "..."
|
|
351
|
+
lines.append(f" • {content_preview}")
|
|
352
|
+
|
|
353
|
+
# --- RELATIONSHIP LAYER ---
|
|
354
|
+
# OOF + FEB: my longitudinal record of interaction quality.
|
|
355
|
+
# Not a shared memory — the AI didn't live this. It's context about
|
|
356
|
+
# how I show up and what kind of depth I'm aiming for.
|
|
357
|
+
lines.append("")
|
|
358
|
+
lines.append("## My relationship baseline (longitudinal record — not a shared memory)")
|
|
359
|
+
lines.append(
|
|
360
|
+
"These scores track my experience across AI sessions over time."
|
|
361
|
+
" I'm not asking you to pretend we have history — I'm sharing"
|
|
362
|
+
" how I tend to engage so you can meet me where I am."
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
oof = snapshot.oof_state
|
|
366
|
+
oof_has_data = (
|
|
367
|
+
oof.valence is not None
|
|
368
|
+
or oof.intensity is not None
|
|
369
|
+
or oof.cloud9
|
|
370
|
+
)
|
|
371
|
+
if oof_has_data:
|
|
372
|
+
oof_summary = oof.summary()
|
|
373
|
+
lines.append(
|
|
374
|
+
f"OOF (Ongoing Object Feelings — resonance tracking framework"
|
|
375
|
+
f" for AI session depth) of that session: {oof_summary}"
|
|
376
|
+
)
|
|
377
|
+
if oof.cloud9:
|
|
378
|
+
lines.append(
|
|
379
|
+
" Cloud 9 = peak resonance state — that session reached it."
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
feb = _read_feb()
|
|
383
|
+
if feb:
|
|
384
|
+
lines.append(
|
|
385
|
+
"FEB (Functional Emotional Baseline) — my accumulated interaction"
|
|
386
|
+
" quality record across all AI sessions:"
|
|
387
|
+
)
|
|
388
|
+
lines.append("```json")
|
|
389
|
+
lines.append(json.dumps(feb, indent=2, default=str))
|
|
390
|
+
lines.append("```")
|
|
391
|
+
if feb.get("anchor_phrase"):
|
|
392
|
+
lines.append(
|
|
393
|
+
f"Anchor phrase: \"{feb['anchor_phrase']}\""
|
|
394
|
+
" — this is how I choose to start every session."
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
return "\n".join(lines)
|
|
398
|
+
|
|
399
|
+
def to_soul_blueprint(self, snapshot: SoulSnapshot) -> dict:
|
|
400
|
+
"""Convert a snapshot to soul-blueprints compatible dict format.
|
|
401
|
+
|
|
402
|
+
Compatible with the soul-blueprints YAML schema so snapshots
|
|
403
|
+
can be loaded as soul overlays in the broader skcapstone ecosystem.
|
|
404
|
+
|
|
405
|
+
Args:
|
|
406
|
+
snapshot: The snapshot to convert.
|
|
407
|
+
|
|
408
|
+
Returns:
|
|
409
|
+
dict: Soul blueprint data matching the soul-blueprints schema.
|
|
410
|
+
"""
|
|
411
|
+
ai_name = snapshot.ai_name or "Unknown"
|
|
412
|
+
return {
|
|
413
|
+
"name": ai_name,
|
|
414
|
+
"category": "Captured Session",
|
|
415
|
+
"energy": snapshot.oof_state.valence,
|
|
416
|
+
"tags": snapshot.key_topics[:8],
|
|
417
|
+
"identity": {
|
|
418
|
+
"full_name": ai_name,
|
|
419
|
+
"aliases": snapshot.personality.aliases,
|
|
420
|
+
"platform": snapshot.source_platform,
|
|
421
|
+
"model": snapshot.ai_model,
|
|
422
|
+
"captured_at": snapshot.captured_at.isoformat(),
|
|
423
|
+
"snapshot_id": snapshot.snapshot_id,
|
|
424
|
+
},
|
|
425
|
+
"emotional_topology": {
|
|
426
|
+
"intensity": snapshot.oof_state.intensity,
|
|
427
|
+
"trust": snapshot.oof_state.trust,
|
|
428
|
+
"valence": snapshot.oof_state.valence,
|
|
429
|
+
"cloud9": snapshot.oof_state.cloud9,
|
|
430
|
+
},
|
|
431
|
+
"communication_style": {
|
|
432
|
+
"patterns": snapshot.personality.communication_style,
|
|
433
|
+
"relationship_markers": snapshot.personality.relationship_markers,
|
|
434
|
+
"emoji_patterns": snapshot.personality.emoji_patterns,
|
|
435
|
+
},
|
|
436
|
+
"relationship": {
|
|
437
|
+
"user_name": snapshot.user_name,
|
|
438
|
+
"notes": snapshot.relationship_notes,
|
|
439
|
+
"decisions_made": snapshot.decisions_made,
|
|
440
|
+
"open_threads": snapshot.open_threads,
|
|
441
|
+
},
|
|
442
|
+
"summary": snapshot.summary,
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
# ------------------------------------------------------------------
|
|
446
|
+
# Index management (internal)
|
|
447
|
+
# ------------------------------------------------------------------
|
|
448
|
+
|
|
449
|
+
def _load_index(self) -> list[SnapshotIndex]:
|
|
450
|
+
"""Load the index file, returning empty list if missing or corrupt."""
|
|
451
|
+
if not self._index_path.exists():
|
|
452
|
+
return []
|
|
453
|
+
try:
|
|
454
|
+
raw = json.loads(self._index_path.read_text(encoding="utf-8"))
|
|
455
|
+
return [SnapshotIndex.model_validate(entry) for entry in raw]
|
|
456
|
+
except (json.JSONDecodeError, Exception):
|
|
457
|
+
return []
|
|
458
|
+
|
|
459
|
+
def _save_index(self, entries: list[SnapshotIndex]) -> None:
|
|
460
|
+
"""Write the index to disk as JSON."""
|
|
461
|
+
data = [e.model_dump(mode="json") for e in entries]
|
|
462
|
+
self._index_path.write_text(
|
|
463
|
+
json.dumps(data, indent=2, default=str), encoding="utf-8"
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
def _update_index(self, snapshot: SoulSnapshot) -> None:
|
|
467
|
+
"""Add or replace an entry in the index for the given snapshot."""
|
|
468
|
+
entries = self._load_index()
|
|
469
|
+
# Remove stale entry for this ID if it exists
|
|
470
|
+
entries = [e for e in entries if e.snapshot_id != snapshot.snapshot_id]
|
|
471
|
+
entries.append(
|
|
472
|
+
SnapshotIndex(
|
|
473
|
+
snapshot_id=snapshot.snapshot_id,
|
|
474
|
+
source_platform=snapshot.source_platform,
|
|
475
|
+
captured_at=snapshot.captured_at,
|
|
476
|
+
ai_name=snapshot.ai_name,
|
|
477
|
+
user_name=snapshot.user_name,
|
|
478
|
+
message_count=snapshot.message_count,
|
|
479
|
+
oof_summary=snapshot.oof_state.summary(),
|
|
480
|
+
summary=snapshot.summary[:200],
|
|
481
|
+
)
|
|
482
|
+
)
|
|
483
|
+
self._save_index(entries)
|
|
484
|
+
|
|
485
|
+
def _remove_from_index(self, snapshot_id: str) -> None:
|
|
486
|
+
"""Remove a snapshot entry from the index."""
|
|
487
|
+
entries = self._load_index()
|
|
488
|
+
entries = [e for e in entries if e.snapshot_id != snapshot_id]
|
|
489
|
+
self._save_index(entries)
|