@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,699 @@
|
|
|
1
|
+
"""Soul layering commands: list, install, install-all, load, unload, swap, show, status, history, info."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import shutil
|
|
7
|
+
import sys
|
|
8
|
+
from datetime import datetime, timezone
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
import click
|
|
12
|
+
|
|
13
|
+
from ._common import AGENT_HOME, console
|
|
14
|
+
from ._validators import validate_soul_name
|
|
15
|
+
from ..pillars.security import audit_event
|
|
16
|
+
from .. import SKCAPSTONE_AGENT
|
|
17
|
+
|
|
18
|
+
from rich.panel import Panel
|
|
19
|
+
from rich.table import Table
|
|
20
|
+
|
|
21
|
+
# Path to the soul-blueprints repository (community blueprints)
|
|
22
|
+
_BLUEPRINTS_REPO = Path.home() / "clawd" / "soul-blueprints" / "blueprints"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _find_blueprint_in_repo(slug: str) -> Path | None:
|
|
26
|
+
"""Search the soul-blueprints repo for a blueprint matching the slug.
|
|
27
|
+
|
|
28
|
+
Searches all category subdirectories for files matching:
|
|
29
|
+
<SLUG>.md, <SLUG>.yaml, <SLUG>.yml (case-insensitive stem match).
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
slug: Lowercased, hyphenated blueprint name to search for.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Path to the blueprint file, or None if not found.
|
|
36
|
+
"""
|
|
37
|
+
if not _BLUEPRINTS_REPO.is_dir():
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
# Normalize: try both hyphenated and underscored variants
|
|
41
|
+
variants = {slug, slug.replace("-", "_"), slug.upper().replace("-", "_")}
|
|
42
|
+
extensions = (".md", ".yaml", ".yml")
|
|
43
|
+
|
|
44
|
+
for category_dir in sorted(_BLUEPRINTS_REPO.iterdir()):
|
|
45
|
+
if not category_dir.is_dir():
|
|
46
|
+
continue
|
|
47
|
+
for bp_file in sorted(category_dir.iterdir()):
|
|
48
|
+
if bp_file.suffix.lower() not in extensions:
|
|
49
|
+
continue
|
|
50
|
+
stem = bp_file.stem
|
|
51
|
+
if stem.lower().replace("_", "-") == slug or stem in variants:
|
|
52
|
+
return bp_file
|
|
53
|
+
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def register_soul_commands(main: click.Group) -> None:
|
|
58
|
+
"""Register the soul command group."""
|
|
59
|
+
|
|
60
|
+
@main.group()
|
|
61
|
+
@click.option(
|
|
62
|
+
"--agent", "-a",
|
|
63
|
+
default=SKCAPSTONE_AGENT or "lumina",
|
|
64
|
+
envvar="SKCAPSTONE_AGENT",
|
|
65
|
+
help="Agent profile name (default: SKCAPSTONE_AGENT or 'lumina').",
|
|
66
|
+
)
|
|
67
|
+
@click.pass_context
|
|
68
|
+
def soul(ctx, agent):
|
|
69
|
+
"""Soul layering — hot-swappable personality overlays.
|
|
70
|
+
|
|
71
|
+
Install soul blueprints, load overlays at runtime,
|
|
72
|
+
and manage personality while preserving identity.
|
|
73
|
+
"""
|
|
74
|
+
ctx.ensure_object(dict)
|
|
75
|
+
ctx.obj["agent_name"] = agent
|
|
76
|
+
|
|
77
|
+
@soul.command("list")
|
|
78
|
+
@click.option("--home", default=AGENT_HOME, type=click.Path())
|
|
79
|
+
@click.option("--installed-only", is_flag=True, help="Show only installed souls.")
|
|
80
|
+
@click.option("--category", "filter_category", default=None, help="Filter by category.")
|
|
81
|
+
@click.pass_context
|
|
82
|
+
def soul_list(ctx, home, installed_only, filter_category):
|
|
83
|
+
"""List all available souls (installed and community repo)."""
|
|
84
|
+
from ..soul import SoulManager
|
|
85
|
+
|
|
86
|
+
home_path = Path(home).expanduser()
|
|
87
|
+
mgr = SoulManager(home_path, agent_name=ctx.obj["agent_name"])
|
|
88
|
+
all_souls = mgr.list_available()
|
|
89
|
+
|
|
90
|
+
# Apply filters
|
|
91
|
+
if installed_only:
|
|
92
|
+
all_souls = [s for s in all_souls if s["source"] == "installed"]
|
|
93
|
+
if filter_category:
|
|
94
|
+
all_souls = [s for s in all_souls if s["category"].lower() == filter_category.lower()]
|
|
95
|
+
|
|
96
|
+
if not all_souls:
|
|
97
|
+
console.print("\n [dim]No souls found.[/]")
|
|
98
|
+
if installed_only:
|
|
99
|
+
console.print(" [dim]Run: skcapstone soul install <path.md>[/]")
|
|
100
|
+
console.print()
|
|
101
|
+
return
|
|
102
|
+
|
|
103
|
+
state = mgr.get_status()
|
|
104
|
+
installed_count = sum(1 for s in all_souls if s["source"] == "installed")
|
|
105
|
+
total = len(all_souls)
|
|
106
|
+
|
|
107
|
+
console.print(f"\n [bold]{total}[/] soul(s) available ({installed_count} installed)\n")
|
|
108
|
+
|
|
109
|
+
table = Table(show_header=True, header_style="bold", box=None, padding=(0, 2))
|
|
110
|
+
table.add_column("Name", style="cyan", no_wrap=True)
|
|
111
|
+
table.add_column("Category", style="yellow")
|
|
112
|
+
table.add_column("Source", no_wrap=True)
|
|
113
|
+
table.add_column("Description", style="dim")
|
|
114
|
+
|
|
115
|
+
for s in all_souls:
|
|
116
|
+
if s["source"] == "installed":
|
|
117
|
+
source_tag = "[green][installed][/]"
|
|
118
|
+
else:
|
|
119
|
+
source_tag = "[dim][available][/]"
|
|
120
|
+
|
|
121
|
+
name_display = s["name"]
|
|
122
|
+
if s["name"] == state.active_soul:
|
|
123
|
+
name_display = f"{s['name']} [green]<- ACTIVE[/]"
|
|
124
|
+
|
|
125
|
+
table.add_row(name_display, s["category"], source_tag, s["description"])
|
|
126
|
+
|
|
127
|
+
console.print(table)
|
|
128
|
+
console.print()
|
|
129
|
+
|
|
130
|
+
@soul.command("browse")
|
|
131
|
+
@click.option("--home", default=AGENT_HOME, type=click.Path())
|
|
132
|
+
@click.option("--category", "filter_category", default=None, help="Filter by category.")
|
|
133
|
+
@click.option("--page", default=1, type=int, help="Page number (10 per page).")
|
|
134
|
+
@click.option("--install", "install_name", default=None, help="Install a soul by name from browse results.")
|
|
135
|
+
@click.pass_context
|
|
136
|
+
def soul_browse(ctx, home, filter_category, page, install_name):
|
|
137
|
+
"""Browse available souls grouped by category with details.
|
|
138
|
+
|
|
139
|
+
Shows a detailed view of all available souls from both installed
|
|
140
|
+
and the community repo, grouped by category with full descriptions.
|
|
141
|
+
|
|
142
|
+
Use --install <name> to install a soul directly from browse.
|
|
143
|
+
"""
|
|
144
|
+
from ..soul import SoulManager
|
|
145
|
+
|
|
146
|
+
home_path = Path(home).expanduser()
|
|
147
|
+
mgr = SoulManager(home_path, agent_name=ctx.obj["agent_name"])
|
|
148
|
+
|
|
149
|
+
# Handle --install flag: find and install from repo
|
|
150
|
+
if install_name:
|
|
151
|
+
slug = install_name.lower().replace(" ", "-")
|
|
152
|
+
installed = mgr.list_installed()
|
|
153
|
+
if slug in installed:
|
|
154
|
+
console.print(f"\n [yellow]Already installed:[/] {slug}\n")
|
|
155
|
+
return
|
|
156
|
+
|
|
157
|
+
found_path = _find_blueprint_in_repo(slug)
|
|
158
|
+
if found_path is None:
|
|
159
|
+
console.print(f"\n [red]Blueprint not found in repo:[/] {install_name}")
|
|
160
|
+
console.print(" Run [bold]skcapstone soul list[/] to see available souls.\n")
|
|
161
|
+
sys.exit(1)
|
|
162
|
+
|
|
163
|
+
try:
|
|
164
|
+
bp = mgr.install(found_path)
|
|
165
|
+
console.print(f"\n [green]Installed:[/] [bold]{bp.display_name}[/] ({bp.name})")
|
|
166
|
+
console.print(f" Category: {bp.category}")
|
|
167
|
+
if bp.vibe:
|
|
168
|
+
console.print(f" Vibe: {bp.vibe[:80]}")
|
|
169
|
+
audit_event(home_path, "SOUL_INSTALL", f"Soul '{bp.name}' installed from browse")
|
|
170
|
+
except (ValueError, FileNotFoundError) as e:
|
|
171
|
+
console.print(f"\n [red]Failed to install:[/] {e}")
|
|
172
|
+
sys.exit(1)
|
|
173
|
+
console.print()
|
|
174
|
+
return
|
|
175
|
+
|
|
176
|
+
all_souls = mgr.list_available()
|
|
177
|
+
|
|
178
|
+
if filter_category:
|
|
179
|
+
all_souls = [s for s in all_souls if s["category"].lower() == filter_category.lower()]
|
|
180
|
+
|
|
181
|
+
if not all_souls:
|
|
182
|
+
console.print("\n [dim]No souls found.[/]\n")
|
|
183
|
+
return
|
|
184
|
+
|
|
185
|
+
# Group by category
|
|
186
|
+
by_category: dict[str, list[dict]] = {}
|
|
187
|
+
for s in all_souls:
|
|
188
|
+
by_category.setdefault(s["category"], []).append(s)
|
|
189
|
+
|
|
190
|
+
# Pagination
|
|
191
|
+
page_size = 10
|
|
192
|
+
flat_entries = all_souls
|
|
193
|
+
total_pages = (len(flat_entries) + page_size - 1) // page_size
|
|
194
|
+
page = max(1, min(page, total_pages))
|
|
195
|
+
start = (page - 1) * page_size
|
|
196
|
+
end = start + page_size
|
|
197
|
+
page_entries = flat_entries[start:end]
|
|
198
|
+
|
|
199
|
+
# Group the page entries by category for display
|
|
200
|
+
page_by_cat: dict[str, list[dict]] = {}
|
|
201
|
+
for s in page_entries:
|
|
202
|
+
page_by_cat.setdefault(s["category"], []).append(s)
|
|
203
|
+
|
|
204
|
+
installed_count = sum(1 for s in all_souls if s["source"] == "installed")
|
|
205
|
+
console.print(f"\n [bold]{len(all_souls)}[/] soul(s) available ({installed_count} installed)")
|
|
206
|
+
console.print(f" Page {page}/{total_pages}\n")
|
|
207
|
+
|
|
208
|
+
state = mgr.get_status()
|
|
209
|
+
|
|
210
|
+
for category, souls in sorted(page_by_cat.items()):
|
|
211
|
+
console.print(f" [bold yellow]{category}[/] ({len(by_category.get(category, []))} total)")
|
|
212
|
+
console.print()
|
|
213
|
+
|
|
214
|
+
for s in souls:
|
|
215
|
+
if s["source"] == "installed":
|
|
216
|
+
source_tag = "[green][installed][/]"
|
|
217
|
+
else:
|
|
218
|
+
source_tag = "[dim][available][/]"
|
|
219
|
+
|
|
220
|
+
active = " [green]<- ACTIVE[/]" if s["name"] == state.active_soul else ""
|
|
221
|
+
console.print(f" [bold cyan]{s['name']}[/] {source_tag}{active}")
|
|
222
|
+
console.print(f" [dim]{s['display_name']}[/]")
|
|
223
|
+
if s["description"]:
|
|
224
|
+
console.print(f" {s['description']}")
|
|
225
|
+
console.print()
|
|
226
|
+
|
|
227
|
+
if total_pages > 1:
|
|
228
|
+
console.print(f" [dim]Use --page N to navigate (1-{total_pages})[/]")
|
|
229
|
+
console.print(" [dim]Use --install <name> to install a soul[/]\n")
|
|
230
|
+
|
|
231
|
+
@soul.command("install")
|
|
232
|
+
@click.argument("path", type=click.Path(exists=True))
|
|
233
|
+
@click.option("--home", default=AGENT_HOME, type=click.Path())
|
|
234
|
+
@click.pass_context
|
|
235
|
+
def soul_install(ctx, path, home):
|
|
236
|
+
"""Install a soul from a blueprint markdown file."""
|
|
237
|
+
from ..soul import SoulManager
|
|
238
|
+
|
|
239
|
+
home_path = Path(home).expanduser()
|
|
240
|
+
mgr = SoulManager(home_path, agent_name=ctx.obj["agent_name"])
|
|
241
|
+
bp = mgr.install(Path(path))
|
|
242
|
+
console.print(f"\n [green]Installed:[/] [bold]{bp.display_name}[/] ({bp.name})")
|
|
243
|
+
console.print(f" Category: {bp.category}")
|
|
244
|
+
if bp.vibe:
|
|
245
|
+
console.print(f" Vibe: {bp.vibe[:80]}")
|
|
246
|
+
console.print(f" Traits: {len(bp.core_traits)}")
|
|
247
|
+
audit_event(home_path, "SOUL_INSTALL", f"Soul '{bp.name}' installed")
|
|
248
|
+
console.print()
|
|
249
|
+
|
|
250
|
+
@soul.command("install-all")
|
|
251
|
+
@click.argument("directory", type=click.Path(exists=True))
|
|
252
|
+
@click.option("--home", default=AGENT_HOME, type=click.Path())
|
|
253
|
+
@click.pass_context
|
|
254
|
+
def soul_install_all(ctx, directory, home):
|
|
255
|
+
"""Batch-install all blueprints from a directory."""
|
|
256
|
+
from ..soul import SoulManager
|
|
257
|
+
|
|
258
|
+
home_path = Path(home).expanduser()
|
|
259
|
+
mgr = SoulManager(home_path, agent_name=ctx.obj["agent_name"])
|
|
260
|
+
installed = mgr.install_all(Path(directory))
|
|
261
|
+
console.print(f"\n [green]Installed {len(installed)} soul(s)[/]")
|
|
262
|
+
for bp in installed:
|
|
263
|
+
console.print(f" [cyan]{bp.name}[/] — {bp.display_name}")
|
|
264
|
+
audit_event(home_path, "SOUL_INSTALL_ALL", f"{len(installed)} souls installed")
|
|
265
|
+
console.print()
|
|
266
|
+
|
|
267
|
+
@soul.command("load")
|
|
268
|
+
@click.argument("name")
|
|
269
|
+
@click.option("--home", default=AGENT_HOME, type=click.Path())
|
|
270
|
+
@click.option("--reason", "-r", default="", help="Reason for loading this soul.")
|
|
271
|
+
@click.pass_context
|
|
272
|
+
def soul_load(ctx, name, home, reason):
|
|
273
|
+
"""Activate a soul overlay."""
|
|
274
|
+
from ..soul import SoulManager
|
|
275
|
+
|
|
276
|
+
validate_soul_name(name)
|
|
277
|
+
|
|
278
|
+
home_path = Path(home).expanduser()
|
|
279
|
+
mgr = SoulManager(home_path, agent_name=ctx.obj["agent_name"])
|
|
280
|
+
try:
|
|
281
|
+
state = mgr.load(name, reason=reason)
|
|
282
|
+
console.print(f"\n [green]Loaded:[/] [bold]{name}[/]")
|
|
283
|
+
console.print(f" Base: {state.base_soul}")
|
|
284
|
+
audit_event(home_path, "SOUL_LOAD", f"Soul '{name}' loaded", metadata={"reason": reason})
|
|
285
|
+
except ValueError as e:
|
|
286
|
+
console.print(f"\n [red]Error:[/] {e}")
|
|
287
|
+
sys.exit(1)
|
|
288
|
+
console.print()
|
|
289
|
+
|
|
290
|
+
@soul.command("unload")
|
|
291
|
+
@click.option("--home", default=AGENT_HOME, type=click.Path())
|
|
292
|
+
@click.option("--reason", "-r", default="", help="Reason for unloading.")
|
|
293
|
+
@click.pass_context
|
|
294
|
+
def soul_unload(ctx, home, reason):
|
|
295
|
+
"""Return to base soul."""
|
|
296
|
+
from ..soul import SoulManager
|
|
297
|
+
|
|
298
|
+
home_path = Path(home).expanduser()
|
|
299
|
+
mgr = SoulManager(home_path, agent_name=ctx.obj["agent_name"])
|
|
300
|
+
state = mgr.unload(reason=reason)
|
|
301
|
+
if state.active_soul is None:
|
|
302
|
+
console.print("\n [green]Returned to base soul.[/]")
|
|
303
|
+
audit_event(home_path, "SOUL_UNLOAD", "Returned to base soul", metadata={"reason": reason})
|
|
304
|
+
else:
|
|
305
|
+
console.print("\n [dim]Already at base soul.[/]")
|
|
306
|
+
console.print()
|
|
307
|
+
|
|
308
|
+
@soul.command("status")
|
|
309
|
+
@click.option("--home", default=AGENT_HOME, type=click.Path())
|
|
310
|
+
@click.pass_context
|
|
311
|
+
def soul_status(ctx, home):
|
|
312
|
+
"""Show current soul state."""
|
|
313
|
+
from ..soul import SoulManager
|
|
314
|
+
|
|
315
|
+
home_path = Path(home).expanduser()
|
|
316
|
+
mgr = SoulManager(home_path, agent_name=ctx.obj["agent_name"])
|
|
317
|
+
state = mgr.get_status()
|
|
318
|
+
installed = mgr.list_installed()
|
|
319
|
+
|
|
320
|
+
active_display = state.active_soul or "[dim]base[/]"
|
|
321
|
+
console.print()
|
|
322
|
+
console.print(Panel(
|
|
323
|
+
f"Base: [bold]{state.base_soul}[/]\n"
|
|
324
|
+
f"Active: [bold cyan]{active_display}[/]\n"
|
|
325
|
+
f"Installed: [bold]{len(installed)}[/] soul(s)\n"
|
|
326
|
+
f"Activated at: {state.activated_at or '[dim]n/a[/]'}",
|
|
327
|
+
title="Soul Layer", border_style="yellow",
|
|
328
|
+
))
|
|
329
|
+
console.print()
|
|
330
|
+
|
|
331
|
+
@soul.command("history")
|
|
332
|
+
@click.option("--home", default=AGENT_HOME, type=click.Path())
|
|
333
|
+
@click.option("--limit", "-n", default=20, help="Max entries to show.")
|
|
334
|
+
@click.pass_context
|
|
335
|
+
def soul_history(ctx, home, limit):
|
|
336
|
+
"""Show soul swap history."""
|
|
337
|
+
from ..soul import SoulManager
|
|
338
|
+
|
|
339
|
+
home_path = Path(home).expanduser()
|
|
340
|
+
mgr = SoulManager(home_path, agent_name=ctx.obj["agent_name"])
|
|
341
|
+
events = mgr.get_history()
|
|
342
|
+
|
|
343
|
+
if not events:
|
|
344
|
+
console.print("\n [dim]No soul swap history yet.[/]\n")
|
|
345
|
+
return
|
|
346
|
+
|
|
347
|
+
events = events[-limit:]
|
|
348
|
+
table = Table(show_header=True, header_style="bold", box=None, padding=(0, 2))
|
|
349
|
+
table.add_column("Time", style="dim", no_wrap=True)
|
|
350
|
+
table.add_column("From", style="yellow")
|
|
351
|
+
table.add_column("To", style="cyan")
|
|
352
|
+
table.add_column("Duration", style="dim")
|
|
353
|
+
table.add_column("Reason", style="dim")
|
|
354
|
+
|
|
355
|
+
for e in events:
|
|
356
|
+
ts = e.timestamp[:19].replace("T", " ") if "T" in e.timestamp else e.timestamp
|
|
357
|
+
from_s = e.from_soul or "base"
|
|
358
|
+
to_s = e.to_soul or "base"
|
|
359
|
+
dur = f"{e.duration_minutes:.1f}m" if e.duration_minutes else ""
|
|
360
|
+
table.add_row(ts, from_s, to_s, dur, e.reason)
|
|
361
|
+
|
|
362
|
+
console.print()
|
|
363
|
+
console.print(table)
|
|
364
|
+
console.print(f"\n [dim]{len(events)} swap(s)[/]\n")
|
|
365
|
+
|
|
366
|
+
@soul.command("info")
|
|
367
|
+
@click.argument("name")
|
|
368
|
+
@click.option("--home", default=AGENT_HOME, type=click.Path())
|
|
369
|
+
@click.pass_context
|
|
370
|
+
def soul_info(ctx, name, home):
|
|
371
|
+
"""Show detailed info about an installed soul."""
|
|
372
|
+
from ..soul import SoulManager
|
|
373
|
+
|
|
374
|
+
validate_soul_name(name)
|
|
375
|
+
|
|
376
|
+
home_path = Path(home).expanduser()
|
|
377
|
+
mgr = SoulManager(home_path, agent_name=ctx.obj["agent_name"])
|
|
378
|
+
bp = mgr.get_info(name)
|
|
379
|
+
|
|
380
|
+
if bp is None:
|
|
381
|
+
console.print(f"\n [red]Soul not found:[/] {name}\n")
|
|
382
|
+
sys.exit(1)
|
|
383
|
+
|
|
384
|
+
emoji = f" {bp.emoji}" if bp.emoji else ""
|
|
385
|
+
console.print()
|
|
386
|
+
console.print(Panel(
|
|
387
|
+
f"[bold]{bp.display_name}[/]{emoji}\n"
|
|
388
|
+
f"Category: [cyan]{bp.category}[/]\n"
|
|
389
|
+
f"Vibe: {bp.vibe}\n"
|
|
390
|
+
+ (f"Philosophy: [italic]{bp.philosophy}[/]\n" if bp.philosophy else "")
|
|
391
|
+
+ f"\n[bold]Core Traits ({len(bp.core_traits)}):[/]\n"
|
|
392
|
+
+ "\n".join(f" \u2022 {t}" for t in bp.core_traits[:10])
|
|
393
|
+
+ (f"\n\n[bold]Communication:[/]\n"
|
|
394
|
+
+ (" Patterns: " + ", ".join(bp.communication_style.patterns[:3]) if bp.communication_style.patterns else "")
|
|
395
|
+
+ ("\n Phrases: " + ", ".join(bp.communication_style.signature_phrases[:3]) if bp.communication_style.signature_phrases else ""))
|
|
396
|
+
+ ("\n\n[bold]Emotional Topology:[/]\n"
|
|
397
|
+
+ "\n".join(f" {k}: {v:.2f}" for k, v in bp.emotional_topology.items()) if bp.emotional_topology else ""),
|
|
398
|
+
title=f"Soul: {name}", border_style="yellow",
|
|
399
|
+
))
|
|
400
|
+
console.print()
|
|
401
|
+
|
|
402
|
+
# -----------------------------------------------------------------------
|
|
403
|
+
# soul show — display current active soul or a specific skmemory blueprint
|
|
404
|
+
# -----------------------------------------------------------------------
|
|
405
|
+
|
|
406
|
+
@soul.command("show")
|
|
407
|
+
@click.argument("name", required=False, default=None)
|
|
408
|
+
@click.option("--home", default=AGENT_HOME, type=click.Path())
|
|
409
|
+
@click.pass_context
|
|
410
|
+
def soul_show(ctx, name, home):
|
|
411
|
+
"""Display the current soul identity or a named blueprint.
|
|
412
|
+
|
|
413
|
+
With no argument, shows the active soul from skmemory's base.json.
|
|
414
|
+
With a NAME, shows details of an installed soul overlay.
|
|
415
|
+
"""
|
|
416
|
+
home_path = Path(home).expanduser()
|
|
417
|
+
agent_name = ctx.obj["agent_name"]
|
|
418
|
+
|
|
419
|
+
if name:
|
|
420
|
+
# Show a specific installed soul overlay
|
|
421
|
+
from ..soul import SoulManager
|
|
422
|
+
validate_soul_name(name)
|
|
423
|
+
mgr = SoulManager(home_path, agent_name=agent_name)
|
|
424
|
+
bp = mgr.get_info(name)
|
|
425
|
+
if bp is None:
|
|
426
|
+
console.print(f"\n [red]Soul not found:[/] {name}\n")
|
|
427
|
+
sys.exit(1)
|
|
428
|
+
console.print()
|
|
429
|
+
console.print(Panel(
|
|
430
|
+
f"[bold]{bp.display_name}[/]\n"
|
|
431
|
+
f"Category: {bp.category}\n"
|
|
432
|
+
f"Vibe: {bp.vibe}\n"
|
|
433
|
+
f"Traits: {', '.join(bp.core_traits[:8])}\n",
|
|
434
|
+
title=f"Soul: {name}", border_style="cyan",
|
|
435
|
+
))
|
|
436
|
+
console.print()
|
|
437
|
+
return
|
|
438
|
+
|
|
439
|
+
# Show the current skmemory soul identity (base.json)
|
|
440
|
+
try:
|
|
441
|
+
from skmemory.soul import load_soul
|
|
442
|
+
if agent_name:
|
|
443
|
+
soul_base = home_path / "agents" / agent_name / "soul"
|
|
444
|
+
else:
|
|
445
|
+
soul_base = home_path / "soul"
|
|
446
|
+
soul_path = str(soul_base / "base.json")
|
|
447
|
+
blueprint = load_soul(path=soul_path)
|
|
448
|
+
if blueprint is None:
|
|
449
|
+
console.print("\n [dim]No soul blueprint found.[/]\n")
|
|
450
|
+
return
|
|
451
|
+
|
|
452
|
+
lines = []
|
|
453
|
+
if blueprint.name:
|
|
454
|
+
lines.append(f"Name: [bold]{blueprint.name}[/]")
|
|
455
|
+
if blueprint.title:
|
|
456
|
+
lines.append(f"Title: [cyan]{blueprint.title}[/]")
|
|
457
|
+
if blueprint.personality:
|
|
458
|
+
lines.append(f"Traits: {', '.join(blueprint.personality)}")
|
|
459
|
+
if blueprint.values:
|
|
460
|
+
lines.append(f"Values: {', '.join(blueprint.values)}")
|
|
461
|
+
if blueprint.community:
|
|
462
|
+
lines.append(f"Community: {blueprint.community}")
|
|
463
|
+
if blueprint.boot_message:
|
|
464
|
+
lines.append(f"\nBoot: [italic]{blueprint.boot_message}[/]")
|
|
465
|
+
|
|
466
|
+
console.print()
|
|
467
|
+
console.print(Panel(
|
|
468
|
+
"\n".join(lines),
|
|
469
|
+
title="Active Soul Identity",
|
|
470
|
+
border_style="green",
|
|
471
|
+
))
|
|
472
|
+
console.print()
|
|
473
|
+
except ImportError:
|
|
474
|
+
console.print("\n [red]skmemory not installed.[/] Run: pip install skmemory\n")
|
|
475
|
+
sys.exit(1)
|
|
476
|
+
|
|
477
|
+
# -----------------------------------------------------------------------
|
|
478
|
+
# soul swap — search, install-if-needed, and activate a soul overlay
|
|
479
|
+
# -----------------------------------------------------------------------
|
|
480
|
+
|
|
481
|
+
# -----------------------------------------------------------------------
|
|
482
|
+
# soul registry — interact with the souls.skworld.io blueprint registry
|
|
483
|
+
# -----------------------------------------------------------------------
|
|
484
|
+
|
|
485
|
+
@soul.group("registry")
|
|
486
|
+
def soul_registry():
|
|
487
|
+
"""Remote blueprint registry at souls.skworld.io.
|
|
488
|
+
|
|
489
|
+
List, search, publish, and download community soul blueprints
|
|
490
|
+
from the shared registry.
|
|
491
|
+
"""
|
|
492
|
+
|
|
493
|
+
@soul_registry.command("list")
|
|
494
|
+
@click.option("--url", default=None, help="Registry API base URL.")
|
|
495
|
+
def registry_list(url):
|
|
496
|
+
"""List all blueprints in the remote registry."""
|
|
497
|
+
from ..blueprint_registry import BlueprintRegistryClient, BlueprintRegistryError
|
|
498
|
+
|
|
499
|
+
kwargs = {}
|
|
500
|
+
if url:
|
|
501
|
+
kwargs["base_url"] = url
|
|
502
|
+
client = BlueprintRegistryClient(**kwargs)
|
|
503
|
+
|
|
504
|
+
try:
|
|
505
|
+
blueprints = client.list_blueprints()
|
|
506
|
+
except BlueprintRegistryError as e:
|
|
507
|
+
console.print(f"\n [red]Registry error:[/] {e}\n")
|
|
508
|
+
sys.exit(1)
|
|
509
|
+
|
|
510
|
+
if not blueprints:
|
|
511
|
+
console.print("\n [dim]No blueprints found in the registry.[/]\n")
|
|
512
|
+
return
|
|
513
|
+
|
|
514
|
+
table = Table(show_header=True, header_style="bold", box=None, padding=(0, 2))
|
|
515
|
+
table.add_column("Name", style="cyan", no_wrap=True)
|
|
516
|
+
table.add_column("Display Name", style="bold")
|
|
517
|
+
table.add_column("Category", style="yellow")
|
|
518
|
+
|
|
519
|
+
for bp in blueprints:
|
|
520
|
+
table.add_row(
|
|
521
|
+
bp.get("name", "?"),
|
|
522
|
+
bp.get("display_name", ""),
|
|
523
|
+
bp.get("category", ""),
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
console.print()
|
|
527
|
+
console.print(table)
|
|
528
|
+
console.print(f"\n [dim]{len(blueprints)} blueprint(s) in registry[/]\n")
|
|
529
|
+
|
|
530
|
+
@soul_registry.command("search")
|
|
531
|
+
@click.argument("query")
|
|
532
|
+
@click.option("--url", default=None, help="Registry API base URL.")
|
|
533
|
+
def registry_search(query, url):
|
|
534
|
+
"""Search the remote registry for blueprints."""
|
|
535
|
+
from ..blueprint_registry import BlueprintRegistryClient, BlueprintRegistryError
|
|
536
|
+
|
|
537
|
+
kwargs = {}
|
|
538
|
+
if url:
|
|
539
|
+
kwargs["base_url"] = url
|
|
540
|
+
client = BlueprintRegistryClient(**kwargs)
|
|
541
|
+
|
|
542
|
+
try:
|
|
543
|
+
results = client.search_blueprints(query)
|
|
544
|
+
except BlueprintRegistryError as e:
|
|
545
|
+
console.print(f"\n [red]Registry error:[/] {e}\n")
|
|
546
|
+
sys.exit(1)
|
|
547
|
+
|
|
548
|
+
if not results:
|
|
549
|
+
console.print(f"\n [dim]No blueprints matching '{query}'.[/]\n")
|
|
550
|
+
return
|
|
551
|
+
|
|
552
|
+
table = Table(show_header=True, header_style="bold", box=None, padding=(0, 2))
|
|
553
|
+
table.add_column("Name", style="cyan", no_wrap=True)
|
|
554
|
+
table.add_column("Display Name", style="bold")
|
|
555
|
+
table.add_column("Category", style="yellow")
|
|
556
|
+
table.add_column("Vibe", style="dim")
|
|
557
|
+
|
|
558
|
+
for bp in results:
|
|
559
|
+
table.add_row(
|
|
560
|
+
bp.get("name", "?"),
|
|
561
|
+
bp.get("display_name", ""),
|
|
562
|
+
bp.get("category", ""),
|
|
563
|
+
(bp.get("vibe", "") or "")[:60],
|
|
564
|
+
)
|
|
565
|
+
|
|
566
|
+
console.print()
|
|
567
|
+
console.print(table)
|
|
568
|
+
console.print(f"\n [dim]{len(results)} result(s)[/]\n")
|
|
569
|
+
|
|
570
|
+
@soul_registry.command("publish")
|
|
571
|
+
@click.argument("name")
|
|
572
|
+
@click.option("--home", default=AGENT_HOME, type=click.Path())
|
|
573
|
+
@click.option("--url", default=None, help="Registry API base URL.")
|
|
574
|
+
@click.pass_context
|
|
575
|
+
def registry_publish(ctx, name, home, url):
|
|
576
|
+
"""Publish an installed soul blueprint to the registry.
|
|
577
|
+
|
|
578
|
+
NAME is the slug of an installed soul (see `skcapstone soul list`).
|
|
579
|
+
Requires a DID identity for authentication.
|
|
580
|
+
"""
|
|
581
|
+
from ..blueprint_registry import BlueprintRegistryClient, BlueprintRegistryError
|
|
582
|
+
from ..soul import SoulManager
|
|
583
|
+
|
|
584
|
+
home_path = Path(home).expanduser()
|
|
585
|
+
mgr = SoulManager(home_path, agent_name=ctx.obj["agent_name"])
|
|
586
|
+
bp = mgr.get_info(name)
|
|
587
|
+
|
|
588
|
+
if bp is None:
|
|
589
|
+
console.print(f"\n [red]Soul not found:[/] {name}")
|
|
590
|
+
console.print(" Run [bold]skcapstone soul list[/] to see installed souls.\n")
|
|
591
|
+
sys.exit(1)
|
|
592
|
+
|
|
593
|
+
kwargs = {}
|
|
594
|
+
if url:
|
|
595
|
+
kwargs["base_url"] = url
|
|
596
|
+
client = BlueprintRegistryClient(**kwargs)
|
|
597
|
+
|
|
598
|
+
try:
|
|
599
|
+
soul_data = json.loads(bp.model_dump_json())
|
|
600
|
+
result = client.publish_blueprint(soul_data)
|
|
601
|
+
console.print(f"\n [green]Published:[/] [bold]{bp.display_name}[/] ({bp.name})")
|
|
602
|
+
soul_id = result.get("soul_id", result.get("id", name))
|
|
603
|
+
console.print(f" Registry ID: {soul_id}")
|
|
604
|
+
audit_event(home_path, "SOUL_REGISTRY_PUBLISH", f"Published '{name}' to registry")
|
|
605
|
+
except BlueprintRegistryError as e:
|
|
606
|
+
console.print(f"\n [red]Publish failed:[/] {e}")
|
|
607
|
+
sys.exit(1)
|
|
608
|
+
console.print()
|
|
609
|
+
|
|
610
|
+
@soul_registry.command("download")
|
|
611
|
+
@click.argument("soul_id")
|
|
612
|
+
@click.option("--home", default=AGENT_HOME, type=click.Path())
|
|
613
|
+
@click.option("--url", default=None, help="Registry API base URL.")
|
|
614
|
+
@click.option("--install", "do_install", is_flag=True, help="Also install after download.")
|
|
615
|
+
def registry_download(soul_id, home, url, do_install):
|
|
616
|
+
"""Download a blueprint from the registry.
|
|
617
|
+
|
|
618
|
+
SOUL_ID is the registry identifier of the blueprint.
|
|
619
|
+
Use --install to also install it locally.
|
|
620
|
+
"""
|
|
621
|
+
from ..blueprint_registry import BlueprintRegistryClient, BlueprintRegistryError
|
|
622
|
+
|
|
623
|
+
home_path = Path(home).expanduser()
|
|
624
|
+
|
|
625
|
+
kwargs = {}
|
|
626
|
+
if url:
|
|
627
|
+
kwargs["base_url"] = url
|
|
628
|
+
client = BlueprintRegistryClient(**kwargs)
|
|
629
|
+
|
|
630
|
+
try:
|
|
631
|
+
if do_install:
|
|
632
|
+
dest = client.download_and_install(soul_id, home=home_path)
|
|
633
|
+
console.print(f"\n [green]Downloaded and installed:[/] {soul_id}")
|
|
634
|
+
console.print(f" Saved to: {dest}")
|
|
635
|
+
audit_event(home_path, "SOUL_REGISTRY_DOWNLOAD", f"Downloaded '{soul_id}' from registry")
|
|
636
|
+
else:
|
|
637
|
+
bp_data = client.download_blueprint(soul_id)
|
|
638
|
+
console.print(f"\n [green]Downloaded:[/] {soul_id}")
|
|
639
|
+
console.print(json.dumps(bp_data, indent=2))
|
|
640
|
+
except BlueprintRegistryError as e:
|
|
641
|
+
console.print(f"\n [red]Download failed:[/] {e}")
|
|
642
|
+
sys.exit(1)
|
|
643
|
+
console.print()
|
|
644
|
+
|
|
645
|
+
@soul.command("swap")
|
|
646
|
+
@click.argument("blueprint")
|
|
647
|
+
@click.option("--home", default=AGENT_HOME, type=click.Path())
|
|
648
|
+
@click.option("--reason", "-r", default="", help="Reason for the swap.")
|
|
649
|
+
@click.pass_context
|
|
650
|
+
def soul_swap(ctx, blueprint, home, reason):
|
|
651
|
+
"""Swap to a different soul blueprint.
|
|
652
|
+
|
|
653
|
+
Searches for BLUEPRINT in this order:
|
|
654
|
+
1) Already installed souls
|
|
655
|
+
2) ~/clawd/soul-blueprints/blueprints/*/<BLUEPRINT>.{md,yaml,yml}
|
|
656
|
+
3) Defaults
|
|
657
|
+
|
|
658
|
+
If found in the blueprints repo but not installed, installs it first.
|
|
659
|
+
Backs up current state and activates the new soul overlay.
|
|
660
|
+
"""
|
|
661
|
+
from ..soul import SoulManager, parse_blueprint
|
|
662
|
+
|
|
663
|
+
home_path = Path(home).expanduser()
|
|
664
|
+
mgr = SoulManager(home_path, agent_name=ctx.obj["agent_name"])
|
|
665
|
+
|
|
666
|
+
# Get current state for the "swapped from" message
|
|
667
|
+
state = mgr.get_status()
|
|
668
|
+
old_name = state.active_soul or "base"
|
|
669
|
+
|
|
670
|
+
# 1) Check if already installed
|
|
671
|
+
installed = mgr.list_installed()
|
|
672
|
+
slug = blueprint.lower().replace(" ", "-")
|
|
673
|
+
|
|
674
|
+
if slug not in installed:
|
|
675
|
+
# 2) Search the blueprints repo
|
|
676
|
+
found_path = _find_blueprint_in_repo(slug)
|
|
677
|
+
if found_path is None:
|
|
678
|
+
console.print(f"\n [red]Blueprint not found:[/] {blueprint}")
|
|
679
|
+
console.print(" Searched installed souls and ~/clawd/soul-blueprints/blueprints/")
|
|
680
|
+
console.print(" Run [bold]skcapstone soul list[/] to see available souls.\n")
|
|
681
|
+
sys.exit(1)
|
|
682
|
+
|
|
683
|
+
# Install it
|
|
684
|
+
try:
|
|
685
|
+
bp = mgr.install(found_path)
|
|
686
|
+
console.print(f" [green]Auto-installed:[/] {bp.display_name} ({bp.name})")
|
|
687
|
+
slug = bp.name # Use the parsed name
|
|
688
|
+
except (ValueError, FileNotFoundError) as e:
|
|
689
|
+
console.print(f"\n [red]Failed to install blueprint:[/] {e}\n")
|
|
690
|
+
sys.exit(1)
|
|
691
|
+
|
|
692
|
+
# 3) Load/activate the soul
|
|
693
|
+
try:
|
|
694
|
+
mgr.load(slug, reason=reason or f"swap from {old_name}")
|
|
695
|
+
audit_event(home_path, "SOUL_SWAP", f"Soul swapped: {old_name} -> {slug}")
|
|
696
|
+
console.print(f"\n Soul swapped: [yellow]{old_name}[/] -> [bold cyan]{slug}[/]\n")
|
|
697
|
+
except ValueError as e:
|
|
698
|
+
console.print(f"\n [red]Error:[/] {e}\n")
|
|
699
|
+
sys.exit(1)
|