@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
|
@@ -32,7 +32,6 @@ logger = logging.getLogger("skcapstone.trust")
|
|
|
32
32
|
FEB_SEARCH_PATHS = [
|
|
33
33
|
Path("~/.cloud9/feb-backups"),
|
|
34
34
|
Path("~/.cloud9/febs"),
|
|
35
|
-
Path("~/.openclaw/feb"),
|
|
36
35
|
Path("~/clawd/cloud9/feb-backups"),
|
|
37
36
|
Path("~/clawd/skills/cloud9/feb-backups"),
|
|
38
37
|
Path("~/Nextcloud/p/smilintux-org/cloud9/feb-backups"),
|
|
@@ -86,7 +85,7 @@ def initialize_trust(home: Path) -> TrustState:
|
|
|
86
85
|
"feb_search_paths": [str(p) for p in FEB_SEARCH_PATHS],
|
|
87
86
|
"how_to_fix": "Place .feb files in ~/.skcapstone/trust/febs/ or install cloud9",
|
|
88
87
|
}
|
|
89
|
-
(trust_dir / "trust.json").write_text(json.dumps(trust_config, indent=2))
|
|
88
|
+
(trust_dir / "trust.json").write_text(json.dumps(trust_config, indent=2), encoding="utf-8")
|
|
90
89
|
return TrustState(status=PillarStatus.MISSING)
|
|
91
90
|
|
|
92
91
|
|
|
@@ -171,19 +170,23 @@ def list_febs(home: Path) -> list[dict]:
|
|
|
171
170
|
|
|
172
171
|
summaries = []
|
|
173
172
|
for f in sorted(febs_dir.glob("*.feb")):
|
|
173
|
+
data = _read_feb_safe(f)
|
|
174
|
+
if data is None:
|
|
175
|
+
continue
|
|
174
176
|
try:
|
|
175
|
-
|
|
176
|
-
payload = data.get("emotional_payload", data.get("cooked_state", {}))
|
|
177
|
+
payload = data.get("emotional_payload", data.get("emotional", data.get("cooked_state", {})))
|
|
177
178
|
cooked = payload.get("cooked_state", payload)
|
|
179
|
+
emotion = cooked.get("primary_emotion", cooked.get("primary", "unknown"))
|
|
180
|
+
meta = data.get("metadata", {})
|
|
178
181
|
summaries.append({
|
|
179
182
|
"file": f.name,
|
|
180
|
-
"timestamp": data.get("timestamp",
|
|
181
|
-
"emotion":
|
|
183
|
+
"timestamp": data.get("timestamp", meta.get("created_at", "unknown")),
|
|
184
|
+
"emotion": emotion,
|
|
182
185
|
"intensity": cooked.get("intensity", 0),
|
|
183
|
-
"subject": payload.get("subject", "unknown"),
|
|
184
|
-
"oof_triggered":
|
|
186
|
+
"subject": payload.get("subject", data.get("relationship_state", {}).get("ai_name", "unknown")),
|
|
187
|
+
"oof_triggered": meta.get("oof_triggered", False),
|
|
185
188
|
})
|
|
186
|
-
except
|
|
189
|
+
except Exception as exc:
|
|
187
190
|
logger.warning("Could not parse FEB %s: %s", f.name, exc)
|
|
188
191
|
|
|
189
192
|
return summaries
|
|
@@ -204,12 +207,11 @@ def export_febs_for_seed(home: Path) -> list[dict]:
|
|
|
204
207
|
|
|
205
208
|
exported = []
|
|
206
209
|
for f in febs_dir.glob("*.feb"):
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
pass
|
|
210
|
+
data = _read_feb_safe(f)
|
|
211
|
+
if data is None:
|
|
212
|
+
continue
|
|
213
|
+
data["_source_file"] = f.name
|
|
214
|
+
exported.append(data)
|
|
213
215
|
|
|
214
216
|
return exported
|
|
215
217
|
|
|
@@ -239,7 +241,7 @@ def import_febs_from_seed(home: Path, seed_febs: list[dict]) -> int:
|
|
|
239
241
|
if filename in existing:
|
|
240
242
|
continue
|
|
241
243
|
|
|
242
|
-
(febs_dir / filename).write_text(json.dumps(feb_data, indent=2))
|
|
244
|
+
(febs_dir / filename).write_text(json.dumps(feb_data, indent=2), encoding="utf-8")
|
|
243
245
|
imported += 1
|
|
244
246
|
|
|
245
247
|
if imported:
|
|
@@ -250,6 +252,44 @@ def import_febs_from_seed(home: Path, seed_febs: list[dict]) -> int:
|
|
|
250
252
|
|
|
251
253
|
# --- Internal helpers ---
|
|
252
254
|
|
|
255
|
+
# OpenPGP binary packet header bytes (RFC 4880 §4.2)
|
|
256
|
+
_GPG_HEADER_BYTES = {0x8C, 0x85, 0x99, 0xC0, 0xC2, 0xC8, 0xCB}
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def _read_feb_safe(path: Path) -> Optional[dict]:
|
|
260
|
+
"""Read a FEB file safely, skipping GPG-encrypted binaries.
|
|
261
|
+
|
|
262
|
+
On Windows the default codec is cp1252, which chokes on binary .feb
|
|
263
|
+
files that are GPG-encrypted. We detect the OpenPGP packet header
|
|
264
|
+
and skip those files rather than crashing.
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
path: Path to the .feb file.
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
Parsed FEB dict, or None if the file is binary/unreadable.
|
|
271
|
+
"""
|
|
272
|
+
try:
|
|
273
|
+
raw = path.read_bytes()
|
|
274
|
+
except OSError as exc:
|
|
275
|
+
logger.warning("Cannot read FEB %s: %s", path.name, exc)
|
|
276
|
+
return None
|
|
277
|
+
|
|
278
|
+
# Detect GPG-encrypted binary by OpenPGP packet header
|
|
279
|
+
if raw and raw[0] in _GPG_HEADER_BYTES:
|
|
280
|
+
logger.debug(
|
|
281
|
+
"Skipping GPG-encrypted FEB (binary): %s — "
|
|
282
|
+
"import the peer's public key and run 'skcapstone sync pull' to decrypt",
|
|
283
|
+
path.name,
|
|
284
|
+
)
|
|
285
|
+
return None
|
|
286
|
+
|
|
287
|
+
try:
|
|
288
|
+
return json.loads(raw.decode("utf-8"))
|
|
289
|
+
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
|
|
290
|
+
logger.warning("Cannot parse FEB %s: %s", path.name, exc)
|
|
291
|
+
return None
|
|
292
|
+
|
|
253
293
|
|
|
254
294
|
def _discover_and_import_febs(home: Path) -> int:
|
|
255
295
|
"""Search known locations for FEB files and copy to agent home.
|
|
@@ -277,40 +317,62 @@ def _discover_and_import_febs(home: Path) -> int:
|
|
|
277
317
|
|
|
278
318
|
|
|
279
319
|
def _derive_trust_from_febs(home: Path, feb_files: list[Path]) -> TrustState:
|
|
280
|
-
"""Derive trust state from FEB files
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
320
|
+
"""Derive trust state from FEB files using calibration thresholds."""
|
|
321
|
+
from ..trust_calibration import load_calibration
|
|
322
|
+
|
|
323
|
+
cal = load_calibration(home)
|
|
324
|
+
depths: list[float] = []
|
|
325
|
+
trusts: list[float] = []
|
|
326
|
+
loves: list[float] = []
|
|
284
327
|
entangled = False
|
|
285
328
|
|
|
286
329
|
for f in feb_files:
|
|
287
330
|
try:
|
|
288
|
-
data =
|
|
331
|
+
data = _read_feb_safe(f)
|
|
332
|
+
if data is None:
|
|
333
|
+
continue
|
|
289
334
|
|
|
290
335
|
rel = data.get("relationship_state", {})
|
|
291
|
-
depth = float(rel.get("depth_level", 0))
|
|
336
|
+
depth = float(rel.get("depth_level", rel.get("depth", 0)))
|
|
292
337
|
trust = float(rel.get("trust_level", 0))
|
|
293
|
-
if trust >
|
|
338
|
+
if trust > cal.normalization_cap:
|
|
294
339
|
trust = trust / 10.0
|
|
295
340
|
|
|
296
|
-
payload = data.get("emotional_payload", {})
|
|
341
|
+
payload = data.get("emotional_payload", data.get("emotional", {}))
|
|
297
342
|
cooked = payload.get("cooked_state", payload)
|
|
298
343
|
love = float(cooked.get("intensity", 0))
|
|
299
|
-
if love >
|
|
344
|
+
if love > cal.normalization_cap:
|
|
300
345
|
love = love / 10.0
|
|
301
346
|
|
|
302
|
-
|
|
347
|
+
is_locked = rel.get("quantum_entanglement") == "LOCKED"
|
|
348
|
+
is_entangled_flag = rel.get("entangled", False) or data.get("quantum", {}).get("entanglement_fidelity", 0) > 0.8
|
|
349
|
+
meets_threshold = depth >= cal.entanglement_depth and trust >= cal.entanglement_trust
|
|
350
|
+
entangled = entangled or is_locked or is_entangled_flag or meets_threshold
|
|
303
351
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
352
|
+
depths.append(depth)
|
|
353
|
+
trusts.append(trust)
|
|
354
|
+
loves.append(love)
|
|
307
355
|
except (json.JSONDecodeError, ValueError, Exception) as exc:
|
|
308
356
|
logger.warning("Could not parse FEB %s: %s", f.name, exc)
|
|
309
357
|
|
|
358
|
+
if cal.peak_strategy == "average" and depths:
|
|
359
|
+
final_depth = sum(depths) / len(depths)
|
|
360
|
+
final_trust = sum(trusts) / len(trusts)
|
|
361
|
+
final_love = sum(loves) / len(loves)
|
|
362
|
+
elif cal.peak_strategy == "weighted" and depths:
|
|
363
|
+
total_weight = sum(range(1, len(depths) + 1))
|
|
364
|
+
final_depth = sum(d * (i + 1) for i, d in enumerate(depths)) / total_weight
|
|
365
|
+
final_trust = sum(t * (i + 1) for i, t in enumerate(trusts)) / total_weight
|
|
366
|
+
final_love = sum(l * (i + 1) for i, l in enumerate(loves)) / total_weight
|
|
367
|
+
else:
|
|
368
|
+
final_depth = max(depths) if depths else 0.0
|
|
369
|
+
final_trust = max(trusts) if trusts else 0.0
|
|
370
|
+
final_love = max(loves) if loves else 0.0
|
|
371
|
+
|
|
310
372
|
state = TrustState(
|
|
311
|
-
depth=
|
|
312
|
-
trust_level=
|
|
313
|
-
love_intensity=
|
|
373
|
+
depth=final_depth,
|
|
374
|
+
trust_level=final_trust,
|
|
375
|
+
love_intensity=final_love,
|
|
314
376
|
entangled=entangled,
|
|
315
377
|
last_rehydration=datetime.now(timezone.utc),
|
|
316
378
|
feb_count=len(feb_files),
|
|
@@ -332,4 +394,4 @@ def _write_trust_json(trust_dir: Path, state: TrustState) -> None:
|
|
|
332
394
|
"feb_count": state.feb_count,
|
|
333
395
|
"last_rehydration": state.last_rehydration.isoformat() if state.last_rehydration else None,
|
|
334
396
|
}
|
|
335
|
-
(trust_dir / "trust.json").write_text(json.dumps(data, indent=2))
|
|
397
|
+
(trust_dir / "trust.json").write_text(json.dumps(data, indent=2), encoding="utf-8")
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
"""SKCapstone plugin loader.
|
|
2
|
+
|
|
3
|
+
Plugins live in ``~/.skcapstone/plugins/*.py`` and are loaded at daemon and
|
|
4
|
+
MCP server startup. Each plugin module must expose a ``register`` function::
|
|
5
|
+
|
|
6
|
+
def register(mcp_server, app) -> None:
|
|
7
|
+
...
|
|
8
|
+
|
|
9
|
+
``mcp_server`` is the :class:`mcp.server.Server` instance when called from
|
|
10
|
+
the MCP server process, or ``None`` when called from the daemon process.
|
|
11
|
+
|
|
12
|
+
``app`` is the FastAPI :class:`~fastapi.FastAPI` instance when the docs
|
|
13
|
+
server is running, or ``None`` otherwise.
|
|
14
|
+
|
|
15
|
+
Plugins that add MCP tools call :func:`register_tool` inside ``register``::
|
|
16
|
+
|
|
17
|
+
from skcapstone.plugins import register_tool
|
|
18
|
+
from mcp.types import Tool, TextContent
|
|
19
|
+
|
|
20
|
+
async def _handler(arguments: dict) -> list[TextContent]:
|
|
21
|
+
return [TextContent(type="text", text="pong")]
|
|
22
|
+
|
|
23
|
+
def register(mcp_server, app):
|
|
24
|
+
register_tool(
|
|
25
|
+
Tool(
|
|
26
|
+
name="ping",
|
|
27
|
+
description="Respond with pong.",
|
|
28
|
+
inputSchema={"type": "object", "properties": {}},
|
|
29
|
+
),
|
|
30
|
+
_handler,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
The ``_registry`` singleton is process-local — the daemon and the MCP server
|
|
34
|
+
each maintain independent state. Hot-reload is triggered by sending SIGHUP to
|
|
35
|
+
the daemon PID and causes a fresh scan of ``~/.skcapstone/plugins/``.
|
|
36
|
+
|
|
37
|
+
Files whose names start with ``_`` are skipped (use them for shared helpers).
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
from __future__ import annotations
|
|
41
|
+
|
|
42
|
+
import importlib.util
|
|
43
|
+
import logging
|
|
44
|
+
import sys
|
|
45
|
+
import types
|
|
46
|
+
from pathlib import Path
|
|
47
|
+
from typing import Any, Callable, Coroutine
|
|
48
|
+
|
|
49
|
+
logger = logging.getLogger("skcapstone.plugins")
|
|
50
|
+
|
|
51
|
+
PLUGIN_DIR_NAME = "plugins"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class PluginRegistry:
|
|
55
|
+
"""Process-local registry of dynamically loaded plugin tools and handlers.
|
|
56
|
+
|
|
57
|
+
Each process (daemon, MCP server) owns an independent instance via the
|
|
58
|
+
module-level ``_registry`` singleton.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
def __init__(self) -> None:
|
|
62
|
+
# Use plain list/dict so we don't force-import mcp at module load time.
|
|
63
|
+
self._tools: list = []
|
|
64
|
+
self._handlers: dict[str, Callable[..., Coroutine[Any, Any, Any]]] = {}
|
|
65
|
+
# Canonical resolved path → loaded module, for hot-reload eviction.
|
|
66
|
+
self._loaded: dict[str, types.ModuleType] = {}
|
|
67
|
+
|
|
68
|
+
# ------------------------------------------------------------------
|
|
69
|
+
# Registration — called by plugins via register_tool()
|
|
70
|
+
# ------------------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
def register_tool(
|
|
73
|
+
self,
|
|
74
|
+
tool: Any,
|
|
75
|
+
handler: Callable[..., Coroutine[Any, Any, Any]],
|
|
76
|
+
) -> None:
|
|
77
|
+
"""Add an MCP tool + async handler to the registry.
|
|
78
|
+
|
|
79
|
+
Idempotent: re-registering the same tool name replaces the old entry.
|
|
80
|
+
"""
|
|
81
|
+
self._tools = [t for t in self._tools if t.name != tool.name]
|
|
82
|
+
self._tools.append(tool)
|
|
83
|
+
self._handlers[tool.name] = handler
|
|
84
|
+
logger.debug("Plugin registered tool: %s", tool.name)
|
|
85
|
+
|
|
86
|
+
# ------------------------------------------------------------------
|
|
87
|
+
# Query — called by mcp_server.py at list/dispatch time
|
|
88
|
+
# ------------------------------------------------------------------
|
|
89
|
+
|
|
90
|
+
def get_tools(self) -> list:
|
|
91
|
+
"""Return a snapshot of all plugin-registered Tool definitions."""
|
|
92
|
+
return list(self._tools)
|
|
93
|
+
|
|
94
|
+
def get_handlers(self) -> dict[str, Callable[..., Coroutine[Any, Any, Any]]]:
|
|
95
|
+
"""Return a snapshot of all plugin-registered handler callables."""
|
|
96
|
+
return dict(self._handlers)
|
|
97
|
+
|
|
98
|
+
# ------------------------------------------------------------------
|
|
99
|
+
# Internal loading helpers
|
|
100
|
+
# ------------------------------------------------------------------
|
|
101
|
+
|
|
102
|
+
def _load_one(self, plugin_path: Path, mcp_server: Any, app: Any) -> bool:
|
|
103
|
+
"""Load a single plugin file and invoke its ``register()`` function.
|
|
104
|
+
|
|
105
|
+
Returns ``True`` on success, ``False`` on any error.
|
|
106
|
+
"""
|
|
107
|
+
module_name = f"skcapstone._plugin_{plugin_path.stem}"
|
|
108
|
+
# Evict any cached version so hot-reload always re-executes the file.
|
|
109
|
+
sys.modules.pop(module_name, None)
|
|
110
|
+
try:
|
|
111
|
+
spec = importlib.util.spec_from_file_location(module_name, plugin_path)
|
|
112
|
+
if spec is None or spec.loader is None:
|
|
113
|
+
logger.warning("Plugin %s: cannot create module spec", plugin_path.name)
|
|
114
|
+
return False
|
|
115
|
+
|
|
116
|
+
mod = importlib.util.module_from_spec(spec)
|
|
117
|
+
sys.modules[module_name] = mod
|
|
118
|
+
spec.loader.exec_module(mod) # type: ignore[union-attr]
|
|
119
|
+
|
|
120
|
+
if not hasattr(mod, "register"):
|
|
121
|
+
logger.warning(
|
|
122
|
+
"Plugin %s: no register() function — skipping", plugin_path.name
|
|
123
|
+
)
|
|
124
|
+
sys.modules.pop(module_name, None)
|
|
125
|
+
return False
|
|
126
|
+
|
|
127
|
+
mod.register(mcp_server, app)
|
|
128
|
+
self._loaded[str(plugin_path.resolve())] = mod
|
|
129
|
+
logger.info("Plugin loaded: %s", plugin_path.name)
|
|
130
|
+
return True
|
|
131
|
+
|
|
132
|
+
except Exception as exc:
|
|
133
|
+
logger.error(
|
|
134
|
+
"Plugin %s failed to load: %s", plugin_path.name, exc, exc_info=True
|
|
135
|
+
)
|
|
136
|
+
sys.modules.pop(module_name, None)
|
|
137
|
+
return False
|
|
138
|
+
|
|
139
|
+
# ------------------------------------------------------------------
|
|
140
|
+
# Public loading API
|
|
141
|
+
# ------------------------------------------------------------------
|
|
142
|
+
|
|
143
|
+
def scan_and_load(self, plugin_dir: Path, mcp_server: Any, app: Any) -> int:
|
|
144
|
+
"""Scan *plugin_dir* for ``*.py`` files and load each as a plugin.
|
|
145
|
+
|
|
146
|
+
Files whose names start with ``_`` are skipped (private helpers).
|
|
147
|
+
Returns the number of successfully loaded plugins.
|
|
148
|
+
"""
|
|
149
|
+
if not plugin_dir.exists():
|
|
150
|
+
logger.debug("Plugin directory not found: %s", plugin_dir)
|
|
151
|
+
return 0
|
|
152
|
+
|
|
153
|
+
count = 0
|
|
154
|
+
for plugin_path in sorted(plugin_dir.glob("*.py")):
|
|
155
|
+
if plugin_path.name.startswith("_"):
|
|
156
|
+
continue
|
|
157
|
+
if self._load_one(plugin_path, mcp_server, app):
|
|
158
|
+
count += 1
|
|
159
|
+
|
|
160
|
+
if count:
|
|
161
|
+
logger.info("Loaded %d plugin(s) from %s", count, plugin_dir)
|
|
162
|
+
else:
|
|
163
|
+
logger.debug("No plugins found in %s", plugin_dir)
|
|
164
|
+
return count
|
|
165
|
+
|
|
166
|
+
def clear(self) -> None:
|
|
167
|
+
"""Unload all plugins and reset the registry."""
|
|
168
|
+
for mod in self._loaded.values():
|
|
169
|
+
sys.modules.pop(getattr(mod, "__name__", ""), None)
|
|
170
|
+
self._tools.clear()
|
|
171
|
+
self._handlers.clear()
|
|
172
|
+
self._loaded.clear()
|
|
173
|
+
logger.debug("Plugin registry cleared")
|
|
174
|
+
|
|
175
|
+
def reload(self, plugin_dir: Path, mcp_server: Any, app: Any) -> int:
|
|
176
|
+
"""Clear and reload all plugins from *plugin_dir*.
|
|
177
|
+
|
|
178
|
+
Called on SIGHUP for hot-reload. Returns the number of plugins loaded.
|
|
179
|
+
"""
|
|
180
|
+
logger.info("Plugin hot-reload — clearing %d plugin(s)", len(self._loaded))
|
|
181
|
+
self.clear()
|
|
182
|
+
return self.scan_and_load(plugin_dir, mcp_server, app)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
# Module-level singleton — each process (daemon / MCP server) has its own copy.
|
|
186
|
+
_registry = PluginRegistry()
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
# ------------------------------------------------------------------
|
|
190
|
+
# Public convenience API used by plugins and the MCP server
|
|
191
|
+
# ------------------------------------------------------------------
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def register_tool(
|
|
195
|
+
tool: Any,
|
|
196
|
+
handler: Callable[..., Coroutine[Any, Any, Any]],
|
|
197
|
+
) -> None:
|
|
198
|
+
"""Register an MCP tool from within a plugin's ``register()`` function.
|
|
199
|
+
|
|
200
|
+
Example::
|
|
201
|
+
|
|
202
|
+
from skcapstone.plugins import register_tool
|
|
203
|
+
from mcp.types import Tool, TextContent
|
|
204
|
+
|
|
205
|
+
async def _handler(arguments: dict) -> list[TextContent]:
|
|
206
|
+
return [TextContent(type="text", text="ok")]
|
|
207
|
+
|
|
208
|
+
def register(mcp_server, app):
|
|
209
|
+
register_tool(
|
|
210
|
+
Tool(
|
|
211
|
+
name="my_tool",
|
|
212
|
+
description="Does something useful.",
|
|
213
|
+
inputSchema={"type": "object", "properties": {}},
|
|
214
|
+
),
|
|
215
|
+
_handler,
|
|
216
|
+
)
|
|
217
|
+
"""
|
|
218
|
+
_registry.register_tool(tool, handler)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def load_plugins(home: Path, mcp_server: Any, app: Any) -> int:
|
|
222
|
+
"""Scan ``{home}/plugins/*.py`` and load each plugin.
|
|
223
|
+
|
|
224
|
+
Returns the count of successfully loaded plugins.
|
|
225
|
+
"""
|
|
226
|
+
return _registry.scan_and_load(home / PLUGIN_DIR_NAME, mcp_server, app)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def reload_plugins(home: Path, mcp_server: Any, app: Any) -> int:
|
|
230
|
+
"""Clear and reload all plugins from ``{home}/plugins/*.py``.
|
|
231
|
+
|
|
232
|
+
Intended to be called from the SIGHUP handler for live hot-reload.
|
|
233
|
+
"""
|
|
234
|
+
return _registry.reload(home / PLUGIN_DIR_NAME, mcp_server, app)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def get_plugin_tools() -> list:
|
|
238
|
+
"""Return all MCP Tool definitions registered by plugins."""
|
|
239
|
+
return _registry.get_tools()
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def get_plugin_handlers() -> dict[str, Callable[..., Coroutine[Any, Any, Any]]]:
|
|
243
|
+
"""Return all handler callables registered by plugins."""
|
|
244
|
+
return _registry.get_handlers()
|