@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,94 @@
|
|
|
1
|
+
"""MCP tool group modules — split from mcp_server.py for maintainability.
|
|
2
|
+
|
|
3
|
+
Each module exposes:
|
|
4
|
+
TOOLS: list[Tool] — MCP tool definitions
|
|
5
|
+
HANDLERS: dict — {tool_name: async_handler_fn}
|
|
6
|
+
|
|
7
|
+
The ``collect_all_tools`` and ``collect_all_handlers`` functions aggregate
|
|
8
|
+
across every module so mcp_server.py can register them in one shot.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from typing import Any, Callable, Coroutine
|
|
14
|
+
|
|
15
|
+
from mcp.types import TextContent, Tool
|
|
16
|
+
|
|
17
|
+
from . import (
|
|
18
|
+
agent_tools,
|
|
19
|
+
ansible_tools,
|
|
20
|
+
chat_tools,
|
|
21
|
+
comm_tools,
|
|
22
|
+
consciousness_tools,
|
|
23
|
+
coord_tools,
|
|
24
|
+
deploy_tools,
|
|
25
|
+
did_tools,
|
|
26
|
+
emotion_tools,
|
|
27
|
+
file_tools,
|
|
28
|
+
fortress_tools,
|
|
29
|
+
gtd_tools,
|
|
30
|
+
health_tools,
|
|
31
|
+
heartbeat_tools,
|
|
32
|
+
kms_tools,
|
|
33
|
+
memory_tools,
|
|
34
|
+
model_tools,
|
|
35
|
+
notification_tools,
|
|
36
|
+
promoter_tools,
|
|
37
|
+
pubsub_tools,
|
|
38
|
+
skills_tools,
|
|
39
|
+
skseed_tools,
|
|
40
|
+
skstacks_tools,
|
|
41
|
+
soul_tools,
|
|
42
|
+
sync_tools,
|
|
43
|
+
telegram_tools,
|
|
44
|
+
trust_tools,
|
|
45
|
+
trustee_tools,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
# Ordered list of all tool-group modules.
|
|
49
|
+
_MODULES = [
|
|
50
|
+
agent_tools,
|
|
51
|
+
memory_tools,
|
|
52
|
+
comm_tools,
|
|
53
|
+
sync_tools,
|
|
54
|
+
coord_tools,
|
|
55
|
+
ansible_tools,
|
|
56
|
+
soul_tools,
|
|
57
|
+
did_tools,
|
|
58
|
+
trust_tools,
|
|
59
|
+
skills_tools,
|
|
60
|
+
chat_tools,
|
|
61
|
+
trustee_tools,
|
|
62
|
+
health_tools,
|
|
63
|
+
heartbeat_tools,
|
|
64
|
+
file_tools,
|
|
65
|
+
gtd_tools,
|
|
66
|
+
pubsub_tools,
|
|
67
|
+
fortress_tools,
|
|
68
|
+
promoter_tools,
|
|
69
|
+
kms_tools,
|
|
70
|
+
skseed_tools,
|
|
71
|
+
skstacks_tools,
|
|
72
|
+
deploy_tools,
|
|
73
|
+
model_tools,
|
|
74
|
+
consciousness_tools,
|
|
75
|
+
emotion_tools,
|
|
76
|
+
notification_tools,
|
|
77
|
+
telegram_tools,
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def collect_all_tools() -> list[Tool]:
|
|
82
|
+
"""Return every Tool definition from all group modules."""
|
|
83
|
+
tools: list[Tool] = []
|
|
84
|
+
for mod in _MODULES:
|
|
85
|
+
tools.extend(mod.TOOLS)
|
|
86
|
+
return tools
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def collect_all_handlers() -> dict[str, Callable[..., Coroutine[Any, Any, list[TextContent]]]]:
|
|
90
|
+
"""Return a merged {name: handler} dict from all group modules."""
|
|
91
|
+
handlers: dict[str, Callable[..., Coroutine[Any, Any, list[TextContent]]]] = {}
|
|
92
|
+
for mod in _MODULES:
|
|
93
|
+
handlers.update(mod.HANDLERS)
|
|
94
|
+
return handlers
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Shared helpers for MCP tool modules."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import logging
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from mcp.types import TextContent
|
|
11
|
+
|
|
12
|
+
from .. import AGENT_HOME, SHARED_ROOT
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger("skcapstone.mcp")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _home() -> Path:
|
|
18
|
+
"""Resolve the per-agent home directory."""
|
|
19
|
+
return Path(AGENT_HOME).expanduser()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _shared_root() -> Path:
|
|
23
|
+
"""Resolve the shared agent root (coordination, heartbeats, peers)."""
|
|
24
|
+
return Path(SHARED_ROOT).expanduser()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _json_response(data: Any) -> list[TextContent]:
|
|
28
|
+
"""Wrap data as a JSON text content response."""
|
|
29
|
+
return [TextContent(type="text", text=json.dumps(data, indent=2, default=str))]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _text_response(text: str) -> list[TextContent]:
|
|
33
|
+
"""Wrap a plain string as a text content response."""
|
|
34
|
+
return [TextContent(type="text", text=text)]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _error_response(message: str) -> list[TextContent]:
|
|
38
|
+
"""Return an error message as text content."""
|
|
39
|
+
return [TextContent(type="text", text=json.dumps({"error": message}))]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _get_agent_name(home: Path) -> str:
|
|
43
|
+
"""Read the agent name from identity file."""
|
|
44
|
+
identity_path = home / "identity" / "identity.json"
|
|
45
|
+
if identity_path.exists():
|
|
46
|
+
try:
|
|
47
|
+
data = json.loads(identity_path.read_text(encoding="utf-8"))
|
|
48
|
+
return data.get("name", "anonymous")
|
|
49
|
+
except Exception:
|
|
50
|
+
pass
|
|
51
|
+
return "anonymous"
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
"""Agent status, context, state diff, and session capture tools."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
|
|
7
|
+
from mcp.types import TextContent, Tool
|
|
8
|
+
|
|
9
|
+
from ._helpers import _error_response, _home, _json_response, _text_response
|
|
10
|
+
|
|
11
|
+
TOOLS: list[Tool] = [
|
|
12
|
+
Tool(
|
|
13
|
+
name="agent_status",
|
|
14
|
+
description=(
|
|
15
|
+
"Get the sovereign agent's current state: pillar statuses "
|
|
16
|
+
"(identity, memory, trust, security, sync), consciousness "
|
|
17
|
+
"level, connected platforms, and overall health."
|
|
18
|
+
),
|
|
19
|
+
inputSchema={"type": "object", "properties": {}, "required": []},
|
|
20
|
+
),
|
|
21
|
+
Tool(
|
|
22
|
+
name="session_capture",
|
|
23
|
+
description=(
|
|
24
|
+
"Capture AI conversation content as sovereign memories. "
|
|
25
|
+
"Extracts key moments, auto-scores importance by topic "
|
|
26
|
+
"novelty and information density, deduplicates against "
|
|
27
|
+
"existing memories, and stores as tagged, searchable "
|
|
28
|
+
"memories. The agent never forgets a conversation."
|
|
29
|
+
),
|
|
30
|
+
inputSchema={
|
|
31
|
+
"type": "object",
|
|
32
|
+
"properties": {
|
|
33
|
+
"content": {
|
|
34
|
+
"type": "string",
|
|
35
|
+
"description": "Conversation text to capture (any length)",
|
|
36
|
+
},
|
|
37
|
+
"tags": {
|
|
38
|
+
"type": "array",
|
|
39
|
+
"items": {"type": "string"},
|
|
40
|
+
"description": "Extra tags to apply to all captured memories",
|
|
41
|
+
},
|
|
42
|
+
"source": {
|
|
43
|
+
"type": "string",
|
|
44
|
+
"description": "Source identifier (default: 'mcp-session')",
|
|
45
|
+
},
|
|
46
|
+
"min_importance": {
|
|
47
|
+
"type": "number",
|
|
48
|
+
"description": "Minimum importance threshold (default: 0.3)",
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
"required": ["content"],
|
|
52
|
+
},
|
|
53
|
+
),
|
|
54
|
+
Tool(
|
|
55
|
+
name="state_diff",
|
|
56
|
+
description=(
|
|
57
|
+
"Show what changed since the last sync/snapshot. "
|
|
58
|
+
"Compares current agent state to the baseline: new "
|
|
59
|
+
"memories, trust changes, completed tasks, pillar "
|
|
60
|
+
"status changes. Use action='save' to set a new baseline."
|
|
61
|
+
),
|
|
62
|
+
inputSchema={
|
|
63
|
+
"type": "object",
|
|
64
|
+
"properties": {
|
|
65
|
+
"action": {
|
|
66
|
+
"type": "string",
|
|
67
|
+
"enum": ["diff", "save"],
|
|
68
|
+
"description": "Action: diff (compare) or save (new baseline). Default: diff.",
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
"required": [],
|
|
72
|
+
},
|
|
73
|
+
),
|
|
74
|
+
Tool(
|
|
75
|
+
name="agent_context",
|
|
76
|
+
description=(
|
|
77
|
+
"Get the full agent context: identity, pillar status, "
|
|
78
|
+
"coordination board, recent memories, soul overlay, and "
|
|
79
|
+
"MCP status. Returns everything an AI needs to understand "
|
|
80
|
+
"the sovereign agent's current state. Supports text, JSON, "
|
|
81
|
+
"and claude-md output formats."
|
|
82
|
+
),
|
|
83
|
+
inputSchema={
|
|
84
|
+
"type": "object",
|
|
85
|
+
"properties": {
|
|
86
|
+
"format": {
|
|
87
|
+
"type": "string",
|
|
88
|
+
"enum": ["text", "json", "claude-md", "cursor-rules"],
|
|
89
|
+
"description": "Output format (default: json)",
|
|
90
|
+
},
|
|
91
|
+
"memories": {
|
|
92
|
+
"type": "integer",
|
|
93
|
+
"description": "Max recent memories to include (default: 10)",
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
"required": [],
|
|
97
|
+
},
|
|
98
|
+
),
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _get_memory_backend_health() -> dict:
|
|
103
|
+
"""Get health status of all memory backends (sqlite, skvector, skgraph)."""
|
|
104
|
+
try:
|
|
105
|
+
from ..memory_adapter import get_unified
|
|
106
|
+
|
|
107
|
+
store = get_unified()
|
|
108
|
+
if store is None:
|
|
109
|
+
return {"json": "ok"}
|
|
110
|
+
|
|
111
|
+
health = store.health()
|
|
112
|
+
backends = {}
|
|
113
|
+
if "primary" in health:
|
|
114
|
+
backends["sqlite"] = "ok" if health["primary"].get("ok") else "error"
|
|
115
|
+
if "vector" in health:
|
|
116
|
+
backends["skvector"] = "ok" if health["vector"].get("ok") else "error"
|
|
117
|
+
if "graph" in health:
|
|
118
|
+
backends["skgraph"] = "ok" if health["graph"].get("ok") else "error"
|
|
119
|
+
return backends or {"json": "ok"}
|
|
120
|
+
except Exception:
|
|
121
|
+
return {"json": "ok"}
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
async def _handle_agent_status(_args: dict) -> list[TextContent]:
|
|
125
|
+
"""Return agent pillar states and consciousness level."""
|
|
126
|
+
from ..runtime import get_runtime
|
|
127
|
+
|
|
128
|
+
home = _home()
|
|
129
|
+
if not home.exists():
|
|
130
|
+
return _error_response("Agent not initialized. Run: skcapstone init")
|
|
131
|
+
|
|
132
|
+
runtime = get_runtime(home)
|
|
133
|
+
m = runtime.manifest
|
|
134
|
+
return _json_response({
|
|
135
|
+
"name": m.name,
|
|
136
|
+
"version": m.version,
|
|
137
|
+
"is_conscious": m.is_conscious,
|
|
138
|
+
"is_singular": m.is_singular,
|
|
139
|
+
"pillars": {
|
|
140
|
+
"identity": {
|
|
141
|
+
"status": m.identity.status.value,
|
|
142
|
+
"fingerprint": m.identity.fingerprint,
|
|
143
|
+
},
|
|
144
|
+
"memory": {
|
|
145
|
+
"status": m.memory.status.value,
|
|
146
|
+
"total": m.memory.total_memories,
|
|
147
|
+
"long_term": m.memory.long_term,
|
|
148
|
+
"mid_term": m.memory.mid_term,
|
|
149
|
+
"short_term": m.memory.short_term,
|
|
150
|
+
"backends": _get_memory_backend_health(),
|
|
151
|
+
},
|
|
152
|
+
"trust": {
|
|
153
|
+
"status": m.trust.status.value,
|
|
154
|
+
"depth": m.trust.depth,
|
|
155
|
+
"trust_level": m.trust.trust_level,
|
|
156
|
+
"love_intensity": m.trust.love_intensity,
|
|
157
|
+
"entangled": m.trust.entangled,
|
|
158
|
+
},
|
|
159
|
+
"security": {
|
|
160
|
+
"status": m.security.status.value,
|
|
161
|
+
"audit_entries": m.security.audit_entries,
|
|
162
|
+
"threats_detected": m.security.threats_detected,
|
|
163
|
+
},
|
|
164
|
+
"sync": {
|
|
165
|
+
"status": m.sync.status.value,
|
|
166
|
+
"seed_count": m.sync.seed_count,
|
|
167
|
+
"transport": m.sync.transport.value if m.sync.transport else None,
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
"connectors": [c.platform for c in m.connectors if c.active],
|
|
171
|
+
"last_awakened": m.last_awakened.isoformat() if m.last_awakened else None,
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
async def _handle_session_capture(args: dict) -> list[TextContent]:
|
|
176
|
+
"""Capture conversation content as sovereign memories."""
|
|
177
|
+
from ..session_capture import SessionCapture
|
|
178
|
+
|
|
179
|
+
content = args.get("content", "")
|
|
180
|
+
if not content:
|
|
181
|
+
return _error_response("content is required")
|
|
182
|
+
|
|
183
|
+
home = _home()
|
|
184
|
+
cap = SessionCapture(home)
|
|
185
|
+
entries = cap.capture(
|
|
186
|
+
content=content,
|
|
187
|
+
tags=args.get("tags", []),
|
|
188
|
+
source=args.get("source", "mcp-session"),
|
|
189
|
+
min_importance=args.get("min_importance", 0.3),
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
return _json_response({
|
|
193
|
+
"captured": len(entries),
|
|
194
|
+
"moments": [
|
|
195
|
+
{
|
|
196
|
+
"memory_id": e.memory_id,
|
|
197
|
+
"content": e.content[:200],
|
|
198
|
+
"layer": e.layer.value,
|
|
199
|
+
"importance": e.importance,
|
|
200
|
+
"tags": e.tags,
|
|
201
|
+
}
|
|
202
|
+
for e in entries
|
|
203
|
+
],
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
async def _handle_state_diff(args: dict) -> list[TextContent]:
|
|
208
|
+
"""Show agent state diff or save a baseline snapshot."""
|
|
209
|
+
from ..state_diff import compute_diff, format_json, save_snapshot
|
|
210
|
+
|
|
211
|
+
home = _home()
|
|
212
|
+
action = args.get("action", "diff")
|
|
213
|
+
|
|
214
|
+
if action == "save":
|
|
215
|
+
path = save_snapshot(home)
|
|
216
|
+
return _json_response({"saved": True, "path": str(path)})
|
|
217
|
+
|
|
218
|
+
diff = compute_diff(home)
|
|
219
|
+
return _json_response(json.loads(format_json(diff)))
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
async def _handle_agent_context(args: dict) -> list[TextContent]:
|
|
223
|
+
"""Return the full agent context in the requested format."""
|
|
224
|
+
from ..context_loader import FORMATTERS, gather_context
|
|
225
|
+
|
|
226
|
+
home = _home()
|
|
227
|
+
fmt = args.get("format", "json")
|
|
228
|
+
limit = args.get("memories", 10)
|
|
229
|
+
|
|
230
|
+
ctx = gather_context(home, memory_limit=limit)
|
|
231
|
+
formatter = FORMATTERS.get(fmt, FORMATTERS["json"])
|
|
232
|
+
|
|
233
|
+
if fmt == "json":
|
|
234
|
+
return _json_response(ctx)
|
|
235
|
+
return _text_response(formatter(ctx))
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
HANDLERS: dict = {
|
|
239
|
+
"agent_status": _handle_agent_status,
|
|
240
|
+
"session_capture": _handle_session_capture,
|
|
241
|
+
"state_diff": _handle_state_diff,
|
|
242
|
+
"agent_context": _handle_agent_context,
|
|
243
|
+
}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"""Ansible playbook runner tool — run_ansible_playbook.
|
|
2
|
+
|
|
3
|
+
Streams stdout/stderr lines to the activity feed SSE queue as
|
|
4
|
+
``ansible.playbook.line`` / ``ansible.playbook.stderr`` events.
|
|
5
|
+
Stores the exit code and play-recap summary in agent memory with
|
|
6
|
+
``tag=ansible-run``.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import asyncio
|
|
12
|
+
import json
|
|
13
|
+
import shutil
|
|
14
|
+
import uuid
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
from mcp.types import TextContent, Tool
|
|
18
|
+
|
|
19
|
+
from ._helpers import _error_response, _home, _json_response
|
|
20
|
+
|
|
21
|
+
TOOLS: list[Tool] = [
|
|
22
|
+
Tool(
|
|
23
|
+
name="run_ansible_playbook",
|
|
24
|
+
description=(
|
|
25
|
+
"Run an Ansible playbook via ansible-playbook subprocess. "
|
|
26
|
+
"Streams stdout lines to the activity feed SSE queue as "
|
|
27
|
+
"ansible.playbook.line events (stderr lines as "
|
|
28
|
+
"ansible.playbook.stderr). Stores exit code and play-recap "
|
|
29
|
+
"summary in agent memory with tag=ansible-run. "
|
|
30
|
+
"dry_run=true adds --check (no changes applied). "
|
|
31
|
+
"Requires ansible-playbook binary in PATH."
|
|
32
|
+
),
|
|
33
|
+
inputSchema={
|
|
34
|
+
"type": "object",
|
|
35
|
+
"properties": {
|
|
36
|
+
"playbook_path": {
|
|
37
|
+
"type": "string",
|
|
38
|
+
"description": (
|
|
39
|
+
"Absolute or relative path to the Ansible playbook YAML file"
|
|
40
|
+
),
|
|
41
|
+
},
|
|
42
|
+
"inventory": {
|
|
43
|
+
"type": "string",
|
|
44
|
+
"description": (
|
|
45
|
+
"Inventory file path, directory, or comma-separated host pattern"
|
|
46
|
+
),
|
|
47
|
+
},
|
|
48
|
+
"extra_vars": {
|
|
49
|
+
"type": "object",
|
|
50
|
+
"description": (
|
|
51
|
+
"Extra variables passed to ansible-playbook via --extra-vars "
|
|
52
|
+
"(serialised as a JSON string)"
|
|
53
|
+
),
|
|
54
|
+
"additionalProperties": True,
|
|
55
|
+
},
|
|
56
|
+
"dry_run": {
|
|
57
|
+
"type": "boolean",
|
|
58
|
+
"description": (
|
|
59
|
+
"If true, pass --check so ansible-playbook simulates changes "
|
|
60
|
+
"without applying them (default: false)"
|
|
61
|
+
),
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
"required": ["playbook_path", "inventory"],
|
|
65
|
+
},
|
|
66
|
+
),
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# ── handler ───────────────────────────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
async def _handle_run_ansible_playbook(args: dict) -> list[TextContent]:
|
|
74
|
+
"""Run an Ansible playbook and stream output to the activity bus."""
|
|
75
|
+
playbook_path = args.get("playbook_path", "").strip()
|
|
76
|
+
inventory = args.get("inventory", "").strip()
|
|
77
|
+
extra_vars: dict = args.get("extra_vars") or {}
|
|
78
|
+
dry_run: bool = bool(args.get("dry_run", False))
|
|
79
|
+
|
|
80
|
+
# --- input validation ---
|
|
81
|
+
if not playbook_path:
|
|
82
|
+
return _error_response("playbook_path is required")
|
|
83
|
+
if not inventory:
|
|
84
|
+
return _error_response("inventory is required")
|
|
85
|
+
|
|
86
|
+
# Require ansible-playbook binary in PATH
|
|
87
|
+
if not shutil.which("ansible-playbook"):
|
|
88
|
+
return _error_response(
|
|
89
|
+
"ansible-playbook binary not found in PATH; "
|
|
90
|
+
"install Ansible first (e.g. pip install ansible or dnf install ansible)"
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
playbook = Path(playbook_path).expanduser().resolve()
|
|
94
|
+
if not playbook.exists():
|
|
95
|
+
return _error_response(f"playbook not found: {playbook_path!r}")
|
|
96
|
+
|
|
97
|
+
# --- build command ---
|
|
98
|
+
cmd: list[str] = [
|
|
99
|
+
"ansible-playbook",
|
|
100
|
+
str(playbook),
|
|
101
|
+
"-i", inventory,
|
|
102
|
+
]
|
|
103
|
+
if dry_run:
|
|
104
|
+
cmd.append("--check")
|
|
105
|
+
if extra_vars:
|
|
106
|
+
cmd.extend(["--extra-vars", json.dumps(extra_vars)])
|
|
107
|
+
|
|
108
|
+
# --- emit start event ---
|
|
109
|
+
run_id = uuid.uuid4().hex[:8]
|
|
110
|
+
|
|
111
|
+
try:
|
|
112
|
+
from .. import activity as _activity
|
|
113
|
+
|
|
114
|
+
_activity.push("ansible.playbook.start", {
|
|
115
|
+
"run_id": run_id,
|
|
116
|
+
"playbook": str(playbook),
|
|
117
|
+
"inventory": inventory,
|
|
118
|
+
"dry_run": dry_run,
|
|
119
|
+
"cmd": cmd,
|
|
120
|
+
})
|
|
121
|
+
except Exception:
|
|
122
|
+
_activity = None # type: ignore[assignment]
|
|
123
|
+
|
|
124
|
+
# --- stream subprocess output ---
|
|
125
|
+
stdout_lines: list[str] = []
|
|
126
|
+
stderr_lines: list[str] = []
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
proc = await asyncio.create_subprocess_exec(
|
|
130
|
+
*cmd,
|
|
131
|
+
stdout=asyncio.subprocess.PIPE,
|
|
132
|
+
stderr=asyncio.subprocess.PIPE,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
async def _drain(
|
|
136
|
+
stream: asyncio.StreamReader,
|
|
137
|
+
store: list[str],
|
|
138
|
+
event_type: str,
|
|
139
|
+
) -> None:
|
|
140
|
+
while True:
|
|
141
|
+
raw = await stream.readline()
|
|
142
|
+
if not raw:
|
|
143
|
+
break
|
|
144
|
+
line = raw.decode(errors="replace").rstrip("\n")
|
|
145
|
+
store.append(line)
|
|
146
|
+
try:
|
|
147
|
+
if _activity is not None:
|
|
148
|
+
_activity.push(event_type, {"run_id": run_id, "line": line})
|
|
149
|
+
except Exception:
|
|
150
|
+
pass
|
|
151
|
+
|
|
152
|
+
await asyncio.gather(
|
|
153
|
+
_drain(proc.stdout, stdout_lines, "ansible.playbook.line"),
|
|
154
|
+
_drain(proc.stderr, stderr_lines, "ansible.playbook.stderr"),
|
|
155
|
+
)
|
|
156
|
+
await proc.wait()
|
|
157
|
+
exit_code: int = proc.returncode # type: ignore[assignment]
|
|
158
|
+
|
|
159
|
+
except Exception as exc:
|
|
160
|
+
return _error_response(f"Failed to launch ansible-playbook: {exc}")
|
|
161
|
+
|
|
162
|
+
# --- build summary ---
|
|
163
|
+
success = exit_code == 0
|
|
164
|
+
|
|
165
|
+
# Extract PLAY RECAP block lines (host rows contain "ok=" or "failed=")
|
|
166
|
+
recap_lines = [
|
|
167
|
+
line for line in stdout_lines
|
|
168
|
+
if "PLAY RECAP" in line or ("ok=" in line and "changed=" in line)
|
|
169
|
+
]
|
|
170
|
+
|
|
171
|
+
summary = {
|
|
172
|
+
"run_id": run_id,
|
|
173
|
+
"playbook": str(playbook),
|
|
174
|
+
"inventory": inventory,
|
|
175
|
+
"dry_run": dry_run,
|
|
176
|
+
"exit_code": exit_code,
|
|
177
|
+
"success": success,
|
|
178
|
+
"stdout_lines": len(stdout_lines),
|
|
179
|
+
"stderr_lines": len(stderr_lines),
|
|
180
|
+
"recap": recap_lines,
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
# --- emit completion event ---
|
|
184
|
+
try:
|
|
185
|
+
if _activity is not None:
|
|
186
|
+
_activity.push("ansible.playbook.done", summary)
|
|
187
|
+
except Exception:
|
|
188
|
+
pass
|
|
189
|
+
|
|
190
|
+
# --- store in memory with tag=ansible-run ---
|
|
191
|
+
try:
|
|
192
|
+
from ..memory_engine import store as _mem_store
|
|
193
|
+
|
|
194
|
+
recap_str = " | ".join(recap_lines) if recap_lines else "none"
|
|
195
|
+
_mem_store(
|
|
196
|
+
home=_home(),
|
|
197
|
+
content=(
|
|
198
|
+
f"Ansible run {'(dry-run) ' if dry_run else ''}— "
|
|
199
|
+
f"playbook: {str(playbook)!r}, inventory: {inventory!r}, "
|
|
200
|
+
f"exit_code: {exit_code}, success: {success}. "
|
|
201
|
+
f"Recap: {recap_str}"
|
|
202
|
+
),
|
|
203
|
+
tags=["ansible-run"],
|
|
204
|
+
source="mcp:run_ansible_playbook",
|
|
205
|
+
importance=0.6 if success else 0.8,
|
|
206
|
+
metadata={
|
|
207
|
+
"run_id": run_id,
|
|
208
|
+
"playbook": str(playbook),
|
|
209
|
+
"inventory": inventory,
|
|
210
|
+
"dry_run": dry_run,
|
|
211
|
+
"exit_code": exit_code,
|
|
212
|
+
"recap": recap_lines,
|
|
213
|
+
},
|
|
214
|
+
)
|
|
215
|
+
except Exception:
|
|
216
|
+
pass # memory failure must not block the tool response
|
|
217
|
+
|
|
218
|
+
if not success:
|
|
219
|
+
# Surface last 20 stderr lines for quick diagnosis
|
|
220
|
+
stderr_tail = stderr_lines[-20:] if len(stderr_lines) > 20 else stderr_lines
|
|
221
|
+
return _json_response({
|
|
222
|
+
**summary,
|
|
223
|
+
"stderr_tail": stderr_tail,
|
|
224
|
+
"error": f"ansible-playbook exited with code {exit_code}",
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
return _json_response(summary)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
HANDLERS: dict = {
|
|
231
|
+
"run_ansible_playbook": _handle_run_ansible_playbook,
|
|
232
|
+
}
|