@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,670 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Preflight system checks — detect and auto-install required tools.
|
|
3
|
+
|
|
4
|
+
Checks for:
|
|
5
|
+
- Python (already running, but verify version)
|
|
6
|
+
- GPG / GnuPG (required for encryption)
|
|
7
|
+
- Git (optional — only needed for dev/repo installs)
|
|
8
|
+
- Syncthing (needed for device sync, Path 2)
|
|
9
|
+
|
|
10
|
+
Each check returns a result with:
|
|
11
|
+
- Whether the tool is installed
|
|
12
|
+
- Current version (if installed)
|
|
13
|
+
- Platform-specific auto-install command
|
|
14
|
+
- Manual download URL as fallback
|
|
15
|
+
|
|
16
|
+
Auto-install uses the platform's native package manager:
|
|
17
|
+
- Linux: apt/dnf/pacman (via sudo)
|
|
18
|
+
- macOS: brew
|
|
19
|
+
- Windows: winget / choco / direct download
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import platform
|
|
25
|
+
import shutil
|
|
26
|
+
import subprocess
|
|
27
|
+
from dataclasses import dataclass, field
|
|
28
|
+
from enum import Enum
|
|
29
|
+
from typing import Optional
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ToolStatus(str, Enum):
|
|
33
|
+
"""Status of a system tool."""
|
|
34
|
+
INSTALLED = "installed"
|
|
35
|
+
MISSING = "missing"
|
|
36
|
+
OPTIONAL = "optional"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class ToolCheck:
|
|
41
|
+
"""Result of checking a single system tool."""
|
|
42
|
+
|
|
43
|
+
name: str
|
|
44
|
+
status: ToolStatus
|
|
45
|
+
required: bool
|
|
46
|
+
version: str = ""
|
|
47
|
+
install_cmd: str = ""
|
|
48
|
+
download_url: str = ""
|
|
49
|
+
install_note: str = ""
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def installed(self) -> bool:
|
|
53
|
+
"""Whether the tool is installed."""
|
|
54
|
+
return self.status == ToolStatus.INSTALLED
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def ok(self) -> bool:
|
|
58
|
+
"""Whether this check passes (installed, or optional and missing)."""
|
|
59
|
+
return self.installed or not self.required
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass
|
|
63
|
+
class PreflightResult:
|
|
64
|
+
"""Combined result of all preflight checks."""
|
|
65
|
+
|
|
66
|
+
python: ToolCheck
|
|
67
|
+
gpg: ToolCheck
|
|
68
|
+
git: ToolCheck
|
|
69
|
+
syncthing: ToolCheck
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def all_ok(self) -> bool:
|
|
73
|
+
"""True if all required tools pass."""
|
|
74
|
+
return all(c.ok for c in [self.python, self.gpg, self.git, self.syncthing])
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def required_missing(self) -> list[ToolCheck]:
|
|
78
|
+
"""List of required tools that are missing."""
|
|
79
|
+
return [c for c in [self.python, self.gpg, self.git, self.syncthing]
|
|
80
|
+
if c.required and not c.installed]
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def optional_missing(self) -> list[ToolCheck]:
|
|
84
|
+
"""List of optional tools that are missing."""
|
|
85
|
+
return [c for c in [self.python, self.gpg, self.git, self.syncthing]
|
|
86
|
+
if not c.required and not c.installed]
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# ---------------------------------------------------------------------------
|
|
90
|
+
# Platform detection
|
|
91
|
+
# ---------------------------------------------------------------------------
|
|
92
|
+
|
|
93
|
+
def _system() -> str:
|
|
94
|
+
"""Canonical platform name."""
|
|
95
|
+
return platform.system()
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _has_pkg_manager(name: str) -> bool:
|
|
99
|
+
"""Check if a package manager is available."""
|
|
100
|
+
return shutil.which(name) is not None
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _detect_linux_pkg_manager() -> Optional[str]:
|
|
104
|
+
"""Detect the Linux package manager."""
|
|
105
|
+
for mgr in ("apt", "dnf", "pacman", "zypper", "apk"):
|
|
106
|
+
if _has_pkg_manager(mgr):
|
|
107
|
+
return mgr
|
|
108
|
+
return None
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# ---------------------------------------------------------------------------
|
|
112
|
+
# Individual tool checks
|
|
113
|
+
# ---------------------------------------------------------------------------
|
|
114
|
+
|
|
115
|
+
def check_python() -> ToolCheck:
|
|
116
|
+
"""Check Python version (we're already running it, but verify version).
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
ToolCheck for Python.
|
|
120
|
+
"""
|
|
121
|
+
import sys
|
|
122
|
+
version = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
|
|
123
|
+
ok = sys.version_info >= (3, 10)
|
|
124
|
+
return ToolCheck(
|
|
125
|
+
name="Python",
|
|
126
|
+
status=ToolStatus.INSTALLED if ok else ToolStatus.MISSING,
|
|
127
|
+
required=True,
|
|
128
|
+
version=version,
|
|
129
|
+
download_url="https://python.org/downloads/",
|
|
130
|
+
install_note="" if ok else "Python 3.10+ is required.",
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def check_gpg() -> ToolCheck:
|
|
135
|
+
"""Check if GnuPG is installed.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
ToolCheck for GPG.
|
|
139
|
+
"""
|
|
140
|
+
system = _system()
|
|
141
|
+
|
|
142
|
+
if shutil.which("gpg") or shutil.which("gpg2"):
|
|
143
|
+
binary = "gpg2" if shutil.which("gpg2") else "gpg"
|
|
144
|
+
version = ""
|
|
145
|
+
try:
|
|
146
|
+
result = subprocess.run(
|
|
147
|
+
[binary, "--version"],
|
|
148
|
+
capture_output=True, text=True, timeout=5,
|
|
149
|
+
)
|
|
150
|
+
if result.returncode == 0:
|
|
151
|
+
first_line = result.stdout.strip().split("\n")[0]
|
|
152
|
+
version = first_line[:60]
|
|
153
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
154
|
+
pass
|
|
155
|
+
return ToolCheck(
|
|
156
|
+
name="GnuPG",
|
|
157
|
+
status=ToolStatus.INSTALLED,
|
|
158
|
+
required=True,
|
|
159
|
+
version=version,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
# Not installed — provide platform-specific install commands
|
|
163
|
+
if system == "Linux":
|
|
164
|
+
mgr = _detect_linux_pkg_manager()
|
|
165
|
+
cmds = {
|
|
166
|
+
"apt": "sudo apt install -y gnupg",
|
|
167
|
+
"dnf": "sudo dnf install -y gnupg2",
|
|
168
|
+
"pacman": "sudo pacman -S --noconfirm gnupg",
|
|
169
|
+
"zypper": "sudo zypper install -y gpg2",
|
|
170
|
+
"apk": "sudo apk add gnupg",
|
|
171
|
+
}
|
|
172
|
+
install_cmd = cmds.get(mgr, "sudo apt install -y gnupg")
|
|
173
|
+
elif system == "Darwin":
|
|
174
|
+
install_cmd = "brew install gnupg" if _has_pkg_manager("brew") else ""
|
|
175
|
+
elif system == "Windows":
|
|
176
|
+
install_cmd = "winget install --id GnuPG.Gpg4win --accept-source-agreements --accept-package-agreements"
|
|
177
|
+
else:
|
|
178
|
+
install_cmd = ""
|
|
179
|
+
|
|
180
|
+
return ToolCheck(
|
|
181
|
+
name="GnuPG",
|
|
182
|
+
status=ToolStatus.MISSING,
|
|
183
|
+
required=True,
|
|
184
|
+
install_cmd=install_cmd,
|
|
185
|
+
download_url=_gpg_download_url(),
|
|
186
|
+
install_note="GPG encrypts your files and identity. Required for all operations.",
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def check_git(required: bool = False) -> ToolCheck:
|
|
191
|
+
"""Check if Git is installed.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
required: Whether Git is required for this install path.
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
ToolCheck for Git.
|
|
198
|
+
"""
|
|
199
|
+
system = _system()
|
|
200
|
+
|
|
201
|
+
if shutil.which("git"):
|
|
202
|
+
version = ""
|
|
203
|
+
try:
|
|
204
|
+
result = subprocess.run(
|
|
205
|
+
["git", "--version"],
|
|
206
|
+
capture_output=True, text=True, timeout=5,
|
|
207
|
+
)
|
|
208
|
+
if result.returncode == 0:
|
|
209
|
+
version = result.stdout.strip().split("\n")[0][:60]
|
|
210
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
211
|
+
pass
|
|
212
|
+
return ToolCheck(
|
|
213
|
+
name="Git",
|
|
214
|
+
status=ToolStatus.INSTALLED,
|
|
215
|
+
required=required,
|
|
216
|
+
version=version,
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
if system == "Linux":
|
|
220
|
+
mgr = _detect_linux_pkg_manager()
|
|
221
|
+
cmds = {
|
|
222
|
+
"apt": "sudo apt install -y git",
|
|
223
|
+
"dnf": "sudo dnf install -y git",
|
|
224
|
+
"pacman": "sudo pacman -S --noconfirm git",
|
|
225
|
+
"zypper": "sudo zypper install -y git",
|
|
226
|
+
"apk": "sudo apk add git",
|
|
227
|
+
}
|
|
228
|
+
install_cmd = cmds.get(mgr, "sudo apt install -y git")
|
|
229
|
+
elif system == "Darwin":
|
|
230
|
+
install_cmd = "xcode-select --install"
|
|
231
|
+
elif system == "Windows":
|
|
232
|
+
install_cmd = "winget install --id Git.Git --accept-source-agreements --accept-package-agreements"
|
|
233
|
+
else:
|
|
234
|
+
install_cmd = ""
|
|
235
|
+
|
|
236
|
+
return ToolCheck(
|
|
237
|
+
name="Git",
|
|
238
|
+
status=ToolStatus.MISSING,
|
|
239
|
+
required=required,
|
|
240
|
+
install_cmd=install_cmd,
|
|
241
|
+
download_url=_git_download_url(),
|
|
242
|
+
install_note="Git is only needed for development. You can skip this.",
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def check_syncthing(required: bool = False) -> ToolCheck:
|
|
247
|
+
"""Check if Syncthing is installed.
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
required: Whether Syncthing is required for this install path.
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
ToolCheck for Syncthing.
|
|
254
|
+
"""
|
|
255
|
+
system = _system()
|
|
256
|
+
|
|
257
|
+
if shutil.which("syncthing"):
|
|
258
|
+
version = ""
|
|
259
|
+
try:
|
|
260
|
+
result = subprocess.run(
|
|
261
|
+
["syncthing", "--version"],
|
|
262
|
+
capture_output=True, text=True, timeout=5,
|
|
263
|
+
)
|
|
264
|
+
if result.returncode == 0:
|
|
265
|
+
version = result.stdout.strip().split("\n")[0][:60]
|
|
266
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
267
|
+
pass
|
|
268
|
+
return ToolCheck(
|
|
269
|
+
name="Syncthing",
|
|
270
|
+
status=ToolStatus.INSTALLED,
|
|
271
|
+
required=required,
|
|
272
|
+
version=version,
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
if system == "Linux":
|
|
276
|
+
mgr = _detect_linux_pkg_manager()
|
|
277
|
+
cmds = {
|
|
278
|
+
"apt": "sudo apt install -y syncthing",
|
|
279
|
+
"dnf": "sudo dnf install -y syncthing",
|
|
280
|
+
"pacman": "sudo pacman -S --noconfirm syncthing",
|
|
281
|
+
}
|
|
282
|
+
install_cmd = cmds.get(mgr, "sudo apt install -y syncthing")
|
|
283
|
+
elif system == "Darwin":
|
|
284
|
+
install_cmd = "brew install syncthing" if _has_pkg_manager("brew") else ""
|
|
285
|
+
elif system == "Windows":
|
|
286
|
+
install_cmd = "winget install --id Syncthing.Syncthing --accept-source-agreements --accept-package-agreements"
|
|
287
|
+
else:
|
|
288
|
+
install_cmd = ""
|
|
289
|
+
|
|
290
|
+
return ToolCheck(
|
|
291
|
+
name="Syncthing",
|
|
292
|
+
status=ToolStatus.MISSING,
|
|
293
|
+
required=required,
|
|
294
|
+
install_cmd=install_cmd,
|
|
295
|
+
download_url="https://syncthing.net/downloads/",
|
|
296
|
+
install_note="Syncthing syncs your identity between devices. Needed for multi-device setup.",
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
# ---------------------------------------------------------------------------
|
|
301
|
+
# Auto-install
|
|
302
|
+
# ---------------------------------------------------------------------------
|
|
303
|
+
|
|
304
|
+
def auto_install_tool(check: ToolCheck) -> bool:
|
|
305
|
+
"""Attempt to auto-install a tool using its platform install command.
|
|
306
|
+
|
|
307
|
+
Args:
|
|
308
|
+
check: ToolCheck with install_cmd populated.
|
|
309
|
+
|
|
310
|
+
Returns:
|
|
311
|
+
True if install succeeded.
|
|
312
|
+
"""
|
|
313
|
+
if check.installed:
|
|
314
|
+
return True
|
|
315
|
+
if not check.install_cmd:
|
|
316
|
+
return False
|
|
317
|
+
|
|
318
|
+
try:
|
|
319
|
+
result = subprocess.run(
|
|
320
|
+
check.install_cmd.split(),
|
|
321
|
+
capture_output=True, text=True, timeout=180,
|
|
322
|
+
)
|
|
323
|
+
return result.returncode == 0
|
|
324
|
+
except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
|
|
325
|
+
return False
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
# ---------------------------------------------------------------------------
|
|
329
|
+
# Full preflight
|
|
330
|
+
# ---------------------------------------------------------------------------
|
|
331
|
+
|
|
332
|
+
def run_preflight(
|
|
333
|
+
require_git: bool = False,
|
|
334
|
+
require_syncthing: bool = False,
|
|
335
|
+
) -> PreflightResult:
|
|
336
|
+
"""Run all preflight checks.
|
|
337
|
+
|
|
338
|
+
Args:
|
|
339
|
+
require_git: Whether Git is required (True for dev installs).
|
|
340
|
+
require_syncthing: Whether Syncthing is required (True for Path 2).
|
|
341
|
+
|
|
342
|
+
Returns:
|
|
343
|
+
PreflightResult with all tool checks.
|
|
344
|
+
"""
|
|
345
|
+
return PreflightResult(
|
|
346
|
+
python=check_python(),
|
|
347
|
+
gpg=check_gpg(),
|
|
348
|
+
git=check_git(required=require_git),
|
|
349
|
+
syncthing=check_syncthing(required=require_syncthing),
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
# ---------------------------------------------------------------------------
|
|
354
|
+
# Download URLs
|
|
355
|
+
# ---------------------------------------------------------------------------
|
|
356
|
+
|
|
357
|
+
def _gpg_download_url() -> str:
|
|
358
|
+
"""Platform-specific GPG download URL."""
|
|
359
|
+
system = _system()
|
|
360
|
+
urls = {
|
|
361
|
+
"Windows": "https://gpg4win.org/download.html",
|
|
362
|
+
"Darwin": "https://sourceforge.net/p/gpgosx/docu/Download/",
|
|
363
|
+
"Linux": "https://gnupg.org/download/",
|
|
364
|
+
}
|
|
365
|
+
return urls.get(system, "https://gnupg.org/download/")
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def _git_download_url() -> str:
|
|
369
|
+
"""Platform-specific Git download URL."""
|
|
370
|
+
system = _system()
|
|
371
|
+
urls = {
|
|
372
|
+
"Windows": "https://git-scm.com/download/win",
|
|
373
|
+
"Darwin": "https://git-scm.com/download/mac",
|
|
374
|
+
"Linux": "https://git-scm.com/download/linux",
|
|
375
|
+
}
|
|
376
|
+
return urls.get(system, "https://git-scm.com/downloads")
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
# ---------------------------------------------------------------------------
|
|
380
|
+
# Legacy compatibility — used by doctor.py and old code
|
|
381
|
+
# ---------------------------------------------------------------------------
|
|
382
|
+
|
|
383
|
+
GIT_DOWNLOAD_URLS = {
|
|
384
|
+
"Windows": "https://git-scm.com/download/win",
|
|
385
|
+
"Linux": "https://git-scm.com/download/linux",
|
|
386
|
+
"Darwin": "https://git-scm.com/download/mac",
|
|
387
|
+
}
|
|
388
|
+
GIT_DOWNLOAD_DEFAULT = "https://git-scm.com/downloads"
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
@dataclass
|
|
392
|
+
class GitPreflightResult:
|
|
393
|
+
"""Legacy result object — kept for backward compatibility with doctor.py."""
|
|
394
|
+
|
|
395
|
+
installed: bool
|
|
396
|
+
platform_label: str
|
|
397
|
+
message: str
|
|
398
|
+
download_url: str
|
|
399
|
+
|
|
400
|
+
@classmethod
|
|
401
|
+
def run(cls) -> "GitPreflightResult":
|
|
402
|
+
"""Run Git check and return a result object."""
|
|
403
|
+
check = check_git(required=False)
|
|
404
|
+
system = _system()
|
|
405
|
+
label = {"Windows": "Windows", "Linux": "Linux", "Darwin": "macOS"}.get(
|
|
406
|
+
system, system
|
|
407
|
+
)
|
|
408
|
+
return cls(
|
|
409
|
+
installed=check.installed,
|
|
410
|
+
platform_label=label,
|
|
411
|
+
message=check.version or check.install_note,
|
|
412
|
+
download_url=check.download_url,
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def git_install_hint_for_doctor() -> str:
|
|
417
|
+
"""Return a one-line fix hint for skcapstone doctor.
|
|
418
|
+
|
|
419
|
+
Returns:
|
|
420
|
+
Empty string if Git is installed, otherwise hint with URL.
|
|
421
|
+
"""
|
|
422
|
+
check = check_git()
|
|
423
|
+
if check.installed:
|
|
424
|
+
return ""
|
|
425
|
+
return f"Install Git: {check.download_url}"
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
# ---------------------------------------------------------------------------
|
|
429
|
+
# Daemon preflight checker
|
|
430
|
+
# ---------------------------------------------------------------------------
|
|
431
|
+
|
|
432
|
+
import sys
|
|
433
|
+
from typing import Literal
|
|
434
|
+
|
|
435
|
+
CheckStatus = Literal["ok", "warn", "fail"]
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
@dataclass
|
|
439
|
+
class CheckResult:
|
|
440
|
+
"""Result of a single daemon preflight check."""
|
|
441
|
+
|
|
442
|
+
name: str
|
|
443
|
+
status: CheckStatus
|
|
444
|
+
message: str
|
|
445
|
+
critical: bool = True
|
|
446
|
+
|
|
447
|
+
@property
|
|
448
|
+
def ok(self) -> bool:
|
|
449
|
+
return self.status == "ok"
|
|
450
|
+
|
|
451
|
+
@property
|
|
452
|
+
def failed(self) -> bool:
|
|
453
|
+
return self.status == "fail"
|
|
454
|
+
|
|
455
|
+
@property
|
|
456
|
+
def warned(self) -> bool:
|
|
457
|
+
return self.status == "warn"
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
class PreflightChecker:
|
|
461
|
+
"""Daemon startup preflight checker.
|
|
462
|
+
|
|
463
|
+
Verifies that the environment is ready for the sovereign agent daemon
|
|
464
|
+
to start safely. Runs a set of named checks and aggregates results into
|
|
465
|
+
a summary dict.
|
|
466
|
+
|
|
467
|
+
Args:
|
|
468
|
+
home: Agent home directory (defaults to ``~/.skcapstone``).
|
|
469
|
+
"""
|
|
470
|
+
|
|
471
|
+
def __init__(self, home: Optional[Path] = None):
|
|
472
|
+
from . import AGENT_HOME
|
|
473
|
+
self.home = (home or Path(AGENT_HOME)).expanduser()
|
|
474
|
+
|
|
475
|
+
# ------------------------------------------------------------------
|
|
476
|
+
# Individual checks
|
|
477
|
+
# ------------------------------------------------------------------
|
|
478
|
+
|
|
479
|
+
def check_python(self) -> CheckResult:
|
|
480
|
+
"""Verify Python >= 3.11."""
|
|
481
|
+
vi = sys.version_info
|
|
482
|
+
version = f"{vi.major}.{vi.minor}.{vi.micro}"
|
|
483
|
+
if vi >= (3, 11):
|
|
484
|
+
return CheckResult("python", "ok", f"Python {version}")
|
|
485
|
+
return CheckResult(
|
|
486
|
+
"python", "fail",
|
|
487
|
+
f"Python {version} — 3.11+ required",
|
|
488
|
+
critical=True,
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
def check_packages(self) -> CheckResult:
|
|
492
|
+
"""Verify skcapstone, skseed, and skcomm are importable."""
|
|
493
|
+
missing = []
|
|
494
|
+
for pkg in ("skcapstone", "skseed", "skcomm"):
|
|
495
|
+
try:
|
|
496
|
+
__import__(pkg)
|
|
497
|
+
except ImportError:
|
|
498
|
+
missing.append(pkg)
|
|
499
|
+
if not missing:
|
|
500
|
+
return CheckResult("packages", "ok", "skcapstone, skseed, skcomm all importable")
|
|
501
|
+
return CheckResult(
|
|
502
|
+
"packages", "fail",
|
|
503
|
+
f"Missing packages: {', '.join(missing)}",
|
|
504
|
+
critical=True,
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
def check_ollama(self) -> CheckResult:
|
|
508
|
+
"""Verify Ollama is running and has at least one model."""
|
|
509
|
+
import urllib.request
|
|
510
|
+
import json as _json
|
|
511
|
+
try:
|
|
512
|
+
with urllib.request.urlopen(
|
|
513
|
+
"http://localhost:11434/api/tags", timeout=3
|
|
514
|
+
) as resp:
|
|
515
|
+
data = _json.loads(resp.read())
|
|
516
|
+
models = data.get("models", [])
|
|
517
|
+
if not models:
|
|
518
|
+
return CheckResult(
|
|
519
|
+
"ollama", "warn",
|
|
520
|
+
"Ollama running but no models loaded — pull a model first",
|
|
521
|
+
critical=False,
|
|
522
|
+
)
|
|
523
|
+
names = ", ".join(m.get("name", "?") for m in models[:3])
|
|
524
|
+
return CheckResult("ollama", "ok", f"Ollama running — models: {names}")
|
|
525
|
+
except OSError:
|
|
526
|
+
return CheckResult(
|
|
527
|
+
"ollama", "warn",
|
|
528
|
+
"Ollama not reachable on localhost:11434 — LLM responses will be unavailable",
|
|
529
|
+
critical=False,
|
|
530
|
+
)
|
|
531
|
+
except Exception as exc:
|
|
532
|
+
return CheckResult(
|
|
533
|
+
"ollama", "warn",
|
|
534
|
+
f"Ollama check failed: {exc}",
|
|
535
|
+
critical=False,
|
|
536
|
+
)
|
|
537
|
+
|
|
538
|
+
def check_identity(self) -> CheckResult:
|
|
539
|
+
"""Verify a PGP identity exists in the agent home."""
|
|
540
|
+
identity_json = self.home / "identity" / "identity.json"
|
|
541
|
+
if identity_json.exists():
|
|
542
|
+
try:
|
|
543
|
+
import json as _json
|
|
544
|
+
data = _json.loads(identity_json.read_text(encoding="utf-8"))
|
|
545
|
+
name = data.get("name", "unknown")
|
|
546
|
+
fp = data.get("fingerprint", "")
|
|
547
|
+
fp_display = fp[-8:] if fp else "no fingerprint"
|
|
548
|
+
return CheckResult(
|
|
549
|
+
"identity", "ok",
|
|
550
|
+
f"Identity: {name} (…{fp_display})",
|
|
551
|
+
)
|
|
552
|
+
except Exception as exc:
|
|
553
|
+
return CheckResult(
|
|
554
|
+
"identity", "fail",
|
|
555
|
+
f"identity.json unreadable: {exc}",
|
|
556
|
+
)
|
|
557
|
+
# Try legacy manifest.json
|
|
558
|
+
manifest = self.home / "manifest.json"
|
|
559
|
+
if manifest.exists():
|
|
560
|
+
return CheckResult(
|
|
561
|
+
"identity", "warn",
|
|
562
|
+
"manifest.json found but no identity/identity.json — run skcapstone init",
|
|
563
|
+
critical=False,
|
|
564
|
+
)
|
|
565
|
+
return CheckResult(
|
|
566
|
+
"identity", "fail",
|
|
567
|
+
f"No identity found in {self.home}/identity/ — run skcapstone init",
|
|
568
|
+
)
|
|
569
|
+
|
|
570
|
+
def check_home_dirs(self) -> CheckResult:
|
|
571
|
+
"""Verify expected ~/.skcapstone/ subdirectory structure exists."""
|
|
572
|
+
required = ["memory", "trust", "identity", "config"]
|
|
573
|
+
missing = [d for d in required if not (self.home / d).exists()]
|
|
574
|
+
if not missing:
|
|
575
|
+
return CheckResult("home_dirs", "ok", f"Home structure OK: {self.home}")
|
|
576
|
+
return CheckResult(
|
|
577
|
+
"home_dirs", "fail",
|
|
578
|
+
f"Missing directories in {self.home}: {', '.join(missing)} — run skcapstone init",
|
|
579
|
+
)
|
|
580
|
+
|
|
581
|
+
def check_config(self) -> CheckResult:
|
|
582
|
+
"""Verify consciousness.yaml is parseable."""
|
|
583
|
+
config_path = self.home / "config" / "consciousness.yaml"
|
|
584
|
+
if not config_path.exists():
|
|
585
|
+
return CheckResult(
|
|
586
|
+
"config", "warn",
|
|
587
|
+
f"consciousness.yaml not found at {config_path} — using defaults",
|
|
588
|
+
critical=False,
|
|
589
|
+
)
|
|
590
|
+
try:
|
|
591
|
+
import yaml
|
|
592
|
+
data = yaml.safe_load(config_path.read_text(encoding="utf-8"))
|
|
593
|
+
if data is None:
|
|
594
|
+
return CheckResult(
|
|
595
|
+
"config", "warn",
|
|
596
|
+
"consciousness.yaml is empty — using defaults",
|
|
597
|
+
critical=False,
|
|
598
|
+
)
|
|
599
|
+
return CheckResult("config", "ok", f"consciousness.yaml parsed OK ({config_path})")
|
|
600
|
+
except Exception as exc:
|
|
601
|
+
return CheckResult(
|
|
602
|
+
"config", "fail",
|
|
603
|
+
f"consciousness.yaml parse error: {exc}",
|
|
604
|
+
)
|
|
605
|
+
|
|
606
|
+
def check_disk_space(self) -> CheckResult:
|
|
607
|
+
"""Warn if less than 5 GB free on the home directory filesystem."""
|
|
608
|
+
import shutil as _shutil
|
|
609
|
+
try:
|
|
610
|
+
usage = _shutil.disk_usage(self.home if self.home.exists() else Path.home())
|
|
611
|
+
free_gb = usage.free / (1024 ** 3)
|
|
612
|
+
if free_gb >= 5.0:
|
|
613
|
+
return CheckResult(
|
|
614
|
+
"disk_space", "ok",
|
|
615
|
+
f"{free_gb:.1f} GB free",
|
|
616
|
+
)
|
|
617
|
+
return CheckResult(
|
|
618
|
+
"disk_space", "warn",
|
|
619
|
+
f"Only {free_gb:.1f} GB free — less than 5 GB recommended",
|
|
620
|
+
critical=False,
|
|
621
|
+
)
|
|
622
|
+
except Exception as exc:
|
|
623
|
+
return CheckResult(
|
|
624
|
+
"disk_space", "warn",
|
|
625
|
+
f"Could not check disk space: {exc}",
|
|
626
|
+
critical=False,
|
|
627
|
+
)
|
|
628
|
+
|
|
629
|
+
# ------------------------------------------------------------------
|
|
630
|
+
# Aggregate
|
|
631
|
+
# ------------------------------------------------------------------
|
|
632
|
+
|
|
633
|
+
def run_all(self) -> dict:
|
|
634
|
+
"""Run all preflight checks and return a summary dict.
|
|
635
|
+
|
|
636
|
+
Returns:
|
|
637
|
+
Dict with keys:
|
|
638
|
+
- ``checks``: list of dicts for each check result
|
|
639
|
+
- ``ok``: True if no critical failures
|
|
640
|
+
- ``warnings``: count of warn results
|
|
641
|
+
- ``failures``: count of fail results
|
|
642
|
+
"""
|
|
643
|
+
methods = [
|
|
644
|
+
self.check_python,
|
|
645
|
+
self.check_packages,
|
|
646
|
+
self.check_ollama,
|
|
647
|
+
self.check_identity,
|
|
648
|
+
self.check_home_dirs,
|
|
649
|
+
self.check_config,
|
|
650
|
+
self.check_disk_space,
|
|
651
|
+
]
|
|
652
|
+
results: list[CheckResult] = [m() for m in methods]
|
|
653
|
+
failures = [r for r in results if r.failed]
|
|
654
|
+
warnings = [r for r in results if r.warned]
|
|
655
|
+
critical_failures = [r for r in failures if r.critical]
|
|
656
|
+
return {
|
|
657
|
+
"ok": len(critical_failures) == 0,
|
|
658
|
+
"checks": [
|
|
659
|
+
{
|
|
660
|
+
"name": r.name,
|
|
661
|
+
"status": r.status,
|
|
662
|
+
"message": r.message,
|
|
663
|
+
"critical": r.critical,
|
|
664
|
+
}
|
|
665
|
+
for r in results
|
|
666
|
+
],
|
|
667
|
+
"warnings": len(warnings),
|
|
668
|
+
"failures": len(failures),
|
|
669
|
+
"critical_failures": len(critical_failures),
|
|
670
|
+
}
|