@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,223 @@
|
|
|
1
|
+
"""Tests for skcapstone skills CLI commands."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from unittest.mock import MagicMock, patch
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
from click.testing import CliRunner
|
|
9
|
+
|
|
10
|
+
from skcapstone.cli import main
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# ---------------------------------------------------------------------------
|
|
14
|
+
# Helpers
|
|
15
|
+
# ---------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
def _make_registry_client(skills: list[dict] | None = None) -> MagicMock:
|
|
18
|
+
"""Return a mock RegistryClient pre-populated with skills."""
|
|
19
|
+
if skills is None:
|
|
20
|
+
skills = [
|
|
21
|
+
{
|
|
22
|
+
"name": "syncthing-setup",
|
|
23
|
+
"version": "1.0.0",
|
|
24
|
+
"description": "Syncthing sovereign sync",
|
|
25
|
+
"tags": ["sync"],
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"name": "pgp-identity",
|
|
29
|
+
"version": "0.2.0",
|
|
30
|
+
"description": "PGP key management",
|
|
31
|
+
"tags": ["identity"],
|
|
32
|
+
},
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
client = MagicMock()
|
|
36
|
+
client.list_skills.return_value = skills
|
|
37
|
+
client.search.return_value = [skills[0]] if skills else []
|
|
38
|
+
return client
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# ---------------------------------------------------------------------------
|
|
42
|
+
# skcapstone skills list
|
|
43
|
+
# ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class TestSkillsList:
|
|
47
|
+
"""Tests for 'skcapstone skills list'."""
|
|
48
|
+
|
|
49
|
+
def test_list_all_skills_renders_table(self):
|
|
50
|
+
"""Happy path: list renders a table with skill rows."""
|
|
51
|
+
runner = CliRunner()
|
|
52
|
+
client = _make_registry_client()
|
|
53
|
+
|
|
54
|
+
with patch("skcapstone.cli.skills_cmd.get_registry_client", return_value=client):
|
|
55
|
+
result = runner.invoke(main, ["skills", "list"])
|
|
56
|
+
|
|
57
|
+
assert result.exit_code == 0
|
|
58
|
+
assert "syncthing-setup" in result.output
|
|
59
|
+
assert "pgp-identity" in result.output
|
|
60
|
+
|
|
61
|
+
def test_list_with_query_calls_search(self):
|
|
62
|
+
"""--query flag should invoke client.search(), not list_skills()."""
|
|
63
|
+
runner = CliRunner()
|
|
64
|
+
client = _make_registry_client()
|
|
65
|
+
|
|
66
|
+
with patch("skcapstone.cli.skills_cmd.get_registry_client", return_value=client):
|
|
67
|
+
result = runner.invoke(main, ["skills", "list", "--query", "syncthing"])
|
|
68
|
+
|
|
69
|
+
assert result.exit_code == 0
|
|
70
|
+
client.search.assert_called_once_with("syncthing")
|
|
71
|
+
client.list_skills.assert_not_called()
|
|
72
|
+
|
|
73
|
+
def test_list_json_output(self):
|
|
74
|
+
"""--json flag should output valid JSON, not a Rich table."""
|
|
75
|
+
import json
|
|
76
|
+
|
|
77
|
+
runner = CliRunner()
|
|
78
|
+
client = _make_registry_client()
|
|
79
|
+
|
|
80
|
+
with patch("skcapstone.cli.skills_cmd.get_registry_client", return_value=client):
|
|
81
|
+
result = runner.invoke(main, ["skills", "list", "--json"])
|
|
82
|
+
|
|
83
|
+
assert result.exit_code == 0
|
|
84
|
+
parsed = json.loads(result.output)
|
|
85
|
+
assert isinstance(parsed, list)
|
|
86
|
+
assert parsed[0]["name"] == "syncthing-setup"
|
|
87
|
+
|
|
88
|
+
def test_list_no_skills_empty_message(self):
|
|
89
|
+
"""Empty registry should print a helpful 'no skills' message."""
|
|
90
|
+
runner = CliRunner()
|
|
91
|
+
client = _make_registry_client(skills=[])
|
|
92
|
+
|
|
93
|
+
with patch("skcapstone.cli.skills_cmd.get_registry_client", return_value=client):
|
|
94
|
+
result = runner.invoke(main, ["skills", "list"])
|
|
95
|
+
|
|
96
|
+
assert result.exit_code == 0
|
|
97
|
+
assert "No skills found" in result.output
|
|
98
|
+
|
|
99
|
+
def test_list_skskills_not_installed(self):
|
|
100
|
+
"""When get_registry_client returns None the command exits 1."""
|
|
101
|
+
runner = CliRunner()
|
|
102
|
+
|
|
103
|
+
with patch("skcapstone.cli.skills_cmd.get_registry_client", return_value=None):
|
|
104
|
+
result = runner.invoke(main, ["skills", "list"])
|
|
105
|
+
|
|
106
|
+
assert result.exit_code == 1
|
|
107
|
+
assert "skskills not installed" in result.output
|
|
108
|
+
|
|
109
|
+
def test_list_registry_error_exits_1(self):
|
|
110
|
+
"""Registry connection error should print an error and exit 1."""
|
|
111
|
+
runner = CliRunner()
|
|
112
|
+
client = MagicMock()
|
|
113
|
+
client.list_skills.side_effect = ConnectionError("offline")
|
|
114
|
+
|
|
115
|
+
with patch("skcapstone.cli.skills_cmd.get_registry_client", return_value=client):
|
|
116
|
+
result = runner.invoke(main, ["skills", "list"])
|
|
117
|
+
|
|
118
|
+
assert result.exit_code == 1
|
|
119
|
+
assert "Registry error" in result.output
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
# ---------------------------------------------------------------------------
|
|
123
|
+
# skcapstone skills install
|
|
124
|
+
# ---------------------------------------------------------------------------
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class TestSkillsInstall:
|
|
128
|
+
"""Tests for 'skcapstone skills install'."""
|
|
129
|
+
|
|
130
|
+
def _install_result(self, name: str = "syncthing-setup") -> dict:
|
|
131
|
+
return {
|
|
132
|
+
"name": name,
|
|
133
|
+
"version": "1.0.0",
|
|
134
|
+
"agent": "global",
|
|
135
|
+
"install_path": "/home/user/.skskills/installed/syncthing-setup",
|
|
136
|
+
"status": "installed",
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
def test_install_happy_path(self):
|
|
140
|
+
"""Successful install prints the skill name, version, and path."""
|
|
141
|
+
runner = CliRunner()
|
|
142
|
+
client = MagicMock()
|
|
143
|
+
client.install.return_value = self._install_result()
|
|
144
|
+
|
|
145
|
+
with patch("skcapstone.cli.skills_cmd.get_registry_client", return_value=client):
|
|
146
|
+
result = runner.invoke(main, ["skills", "install", "syncthing-setup"])
|
|
147
|
+
|
|
148
|
+
assert result.exit_code == 0
|
|
149
|
+
assert "Installed" in result.output
|
|
150
|
+
assert "syncthing-setup" in result.output
|
|
151
|
+
client.install.assert_called_once_with(
|
|
152
|
+
"syncthing-setup", version=None, agent="global", force=False
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
def test_install_with_version_flag(self):
|
|
156
|
+
"""--version should be forwarded to client.install()."""
|
|
157
|
+
runner = CliRunner()
|
|
158
|
+
client = MagicMock()
|
|
159
|
+
client.install.return_value = self._install_result()
|
|
160
|
+
|
|
161
|
+
with patch("skcapstone.cli.skills_cmd.get_registry_client", return_value=client):
|
|
162
|
+
result = runner.invoke(
|
|
163
|
+
main, ["skills", "install", "syncthing-setup", "--version", "0.9.0"]
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
assert result.exit_code == 0
|
|
167
|
+
client.install.assert_called_once_with(
|
|
168
|
+
"syncthing-setup", version="0.9.0", agent="global", force=False
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
def test_install_with_agent_flag(self):
|
|
172
|
+
"""--agent should be forwarded to client.install()."""
|
|
173
|
+
runner = CliRunner()
|
|
174
|
+
client = MagicMock()
|
|
175
|
+
client.install.return_value = self._install_result()
|
|
176
|
+
|
|
177
|
+
with patch("skcapstone.cli.skills_cmd.get_registry_client", return_value=client):
|
|
178
|
+
result = runner.invoke(
|
|
179
|
+
main, ["skills", "install", "syncthing-setup", "--agent", "opus"]
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
assert result.exit_code == 0
|
|
183
|
+
client.install.assert_called_once_with(
|
|
184
|
+
"syncthing-setup", version=None, agent="opus", force=False
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
def test_install_not_found_exits_1(self):
|
|
188
|
+
"""FileNotFoundError from the registry should exit 1 with a helpful message."""
|
|
189
|
+
runner = CliRunner()
|
|
190
|
+
client = MagicMock()
|
|
191
|
+
client.install.side_effect = FileNotFoundError("unknown-skill not in registry")
|
|
192
|
+
|
|
193
|
+
with patch("skcapstone.cli.skills_cmd.get_registry_client", return_value=client):
|
|
194
|
+
result = runner.invoke(main, ["skills", "install", "unknown-skill"])
|
|
195
|
+
|
|
196
|
+
assert result.exit_code == 1
|
|
197
|
+
assert "Not found" in result.output
|
|
198
|
+
|
|
199
|
+
def test_install_skskills_not_installed(self):
|
|
200
|
+
"""When get_registry_client returns None the command exits 1."""
|
|
201
|
+
runner = CliRunner()
|
|
202
|
+
|
|
203
|
+
with patch("skcapstone.cli.skills_cmd.get_registry_client", return_value=None):
|
|
204
|
+
result = runner.invoke(main, ["skills", "install", "syncthing-setup"])
|
|
205
|
+
|
|
206
|
+
assert result.exit_code == 1
|
|
207
|
+
assert "skskills not installed" in result.output
|
|
208
|
+
|
|
209
|
+
def test_install_force_flag(self):
|
|
210
|
+
"""--force should be forwarded to client.install()."""
|
|
211
|
+
runner = CliRunner()
|
|
212
|
+
client = MagicMock()
|
|
213
|
+
client.install.return_value = self._install_result()
|
|
214
|
+
|
|
215
|
+
with patch("skcapstone.cli.skills_cmd.get_registry_client", return_value=client):
|
|
216
|
+
result = runner.invoke(
|
|
217
|
+
main, ["skills", "install", "syncthing-setup", "--force"]
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
assert result.exit_code == 0
|
|
221
|
+
client.install.assert_called_once_with(
|
|
222
|
+
"syncthing-setup", version=None, agent="global", force=True
|
|
223
|
+
)
|
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
"""Tests for the enhanced skcapstone status command and doctor CLI.
|
|
2
|
+
|
|
3
|
+
Covers:
|
|
4
|
+
- _probe_llm_backends() helper
|
|
5
|
+
- _get_daemon_info() helper
|
|
6
|
+
- _get_last_conversation() helper
|
|
7
|
+
- status --help
|
|
8
|
+
- status CLI output (sections: daemon, backends, disk, conversation)
|
|
9
|
+
- doctor CLI (smoke tests)
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
import os
|
|
16
|
+
import time
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from unittest.mock import MagicMock, patch
|
|
19
|
+
|
|
20
|
+
import pytest
|
|
21
|
+
import yaml
|
|
22
|
+
from click.testing import CliRunner
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
# Shared fixture
|
|
27
|
+
# ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@pytest.fixture
|
|
31
|
+
def agent_home(tmp_path: Path) -> Path:
|
|
32
|
+
"""Fully initialised agent home with minimal required files."""
|
|
33
|
+
home = tmp_path / ".skcapstone"
|
|
34
|
+
for d in [
|
|
35
|
+
"identity", "memory", "trust", "security", "sync", "config",
|
|
36
|
+
"memory/short-term", "memory/mid-term", "memory/long-term",
|
|
37
|
+
]:
|
|
38
|
+
(home / d).mkdir(parents=True, exist_ok=True)
|
|
39
|
+
|
|
40
|
+
(home / "manifest.json").write_text(json.dumps({
|
|
41
|
+
"name": "TestAgent", "version": "0.1.0",
|
|
42
|
+
}))
|
|
43
|
+
(home / "identity" / "identity.json").write_text(json.dumps({
|
|
44
|
+
"name": "TestAgent",
|
|
45
|
+
"fingerprint": "DEADBEEF12345678",
|
|
46
|
+
"capauth_managed": True,
|
|
47
|
+
}))
|
|
48
|
+
(home / "config" / "config.yaml").write_text(
|
|
49
|
+
yaml.dump({"agent_name": "TestAgent"})
|
|
50
|
+
)
|
|
51
|
+
return home
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# ---------------------------------------------------------------------------
|
|
55
|
+
# Unit tests for helper functions
|
|
56
|
+
# ---------------------------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class TestProbeLlmBackends:
|
|
60
|
+
"""Tests for _probe_llm_backends()."""
|
|
61
|
+
|
|
62
|
+
def test_ollama_available_when_reachable(self):
|
|
63
|
+
"""ollama marked True when HTTP probe succeeds."""
|
|
64
|
+
from skcapstone.cli.status import _probe_llm_backends
|
|
65
|
+
|
|
66
|
+
mock_resp = MagicMock()
|
|
67
|
+
mock_resp.__enter__ = lambda s: s
|
|
68
|
+
mock_resp.__exit__ = MagicMock(return_value=False)
|
|
69
|
+
|
|
70
|
+
with patch("urllib.request.urlopen", return_value=mock_resp):
|
|
71
|
+
result = _probe_llm_backends()
|
|
72
|
+
|
|
73
|
+
assert result["ollama"] is True
|
|
74
|
+
|
|
75
|
+
def test_ollama_unavailable_when_unreachable(self):
|
|
76
|
+
"""ollama marked False when HTTP probe raises."""
|
|
77
|
+
from skcapstone.cli.status import _probe_llm_backends
|
|
78
|
+
|
|
79
|
+
with patch("urllib.request.urlopen", side_effect=OSError("refused")):
|
|
80
|
+
result = _probe_llm_backends()
|
|
81
|
+
|
|
82
|
+
assert result["ollama"] is False
|
|
83
|
+
|
|
84
|
+
def test_cloud_backends_via_env(self, monkeypatch):
|
|
85
|
+
"""Cloud backends detected via environment variables."""
|
|
86
|
+
from skcapstone.cli.status import _probe_llm_backends
|
|
87
|
+
|
|
88
|
+
monkeypatch.setenv("ANTHROPIC_API_KEY", "test-key")
|
|
89
|
+
monkeypatch.setenv("XAI_API_KEY", "test-key")
|
|
90
|
+
monkeypatch.delenv("MOONSHOT_API_KEY", raising=False)
|
|
91
|
+
monkeypatch.delenv("NVIDIA_API_KEY", raising=False)
|
|
92
|
+
|
|
93
|
+
with patch("urllib.request.urlopen", side_effect=OSError):
|
|
94
|
+
result = _probe_llm_backends()
|
|
95
|
+
|
|
96
|
+
assert result["anthropic"] is True
|
|
97
|
+
assert result["grok"] is True
|
|
98
|
+
assert result["kimi"] is False
|
|
99
|
+
assert result["nvidia"] is False
|
|
100
|
+
|
|
101
|
+
def test_returns_all_expected_keys(self):
|
|
102
|
+
"""Result always contains the five expected backend keys."""
|
|
103
|
+
from skcapstone.cli.status import _probe_llm_backends
|
|
104
|
+
|
|
105
|
+
with patch("urllib.request.urlopen", side_effect=OSError):
|
|
106
|
+
result = _probe_llm_backends()
|
|
107
|
+
|
|
108
|
+
assert set(result.keys()) == {"ollama", "anthropic", "grok", "kimi", "nvidia"}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class TestGetDaemonInfo:
|
|
112
|
+
"""Tests for _get_daemon_info()."""
|
|
113
|
+
|
|
114
|
+
def test_stopped_when_no_pid(self, agent_home: Path):
|
|
115
|
+
"""Returns running=False when no PID file exists."""
|
|
116
|
+
from skcapstone.cli.status import _get_daemon_info
|
|
117
|
+
|
|
118
|
+
with patch("skcapstone.cli.status._get_daemon_info.__module__"):
|
|
119
|
+
pass # ensure module is imported
|
|
120
|
+
|
|
121
|
+
with patch("skcapstone.daemon.read_pid", return_value=None):
|
|
122
|
+
result = _get_daemon_info(agent_home)
|
|
123
|
+
|
|
124
|
+
assert result["running"] is False
|
|
125
|
+
|
|
126
|
+
def test_running_with_pid(self, agent_home: Path):
|
|
127
|
+
"""Returns running=True with PID when daemon is alive."""
|
|
128
|
+
from skcapstone.cli.status import _get_daemon_info
|
|
129
|
+
|
|
130
|
+
with patch("skcapstone.daemon.read_pid", return_value=12345), \
|
|
131
|
+
patch("skcapstone.daemon.get_daemon_status", return_value=None):
|
|
132
|
+
result = _get_daemon_info(agent_home)
|
|
133
|
+
|
|
134
|
+
assert result["running"] is True
|
|
135
|
+
assert result["pid"] == 12345
|
|
136
|
+
|
|
137
|
+
def test_uptime_formatting_seconds(self, agent_home: Path):
|
|
138
|
+
"""Uptime < 60s shown as Xs."""
|
|
139
|
+
from skcapstone.cli.status import _get_daemon_info
|
|
140
|
+
|
|
141
|
+
with patch("skcapstone.daemon.read_pid", return_value=1), \
|
|
142
|
+
patch("skcapstone.daemon.get_daemon_status", return_value={"uptime_seconds": 45}):
|
|
143
|
+
result = _get_daemon_info(agent_home)
|
|
144
|
+
|
|
145
|
+
assert result["uptime"] == "45s"
|
|
146
|
+
|
|
147
|
+
def test_uptime_formatting_minutes(self, agent_home: Path):
|
|
148
|
+
"""Uptime 60–3599s shown as Xm Ys."""
|
|
149
|
+
from skcapstone.cli.status import _get_daemon_info
|
|
150
|
+
|
|
151
|
+
with patch("skcapstone.daemon.read_pid", return_value=1), \
|
|
152
|
+
patch("skcapstone.daemon.get_daemon_status", return_value={"uptime_seconds": 125}):
|
|
153
|
+
result = _get_daemon_info(agent_home)
|
|
154
|
+
|
|
155
|
+
assert result["uptime"] == "2m 5s"
|
|
156
|
+
|
|
157
|
+
def test_uptime_formatting_hours(self, agent_home: Path):
|
|
158
|
+
"""Uptime >= 3600s shown as Xh Ym."""
|
|
159
|
+
from skcapstone.cli.status import _get_daemon_info
|
|
160
|
+
|
|
161
|
+
with patch("skcapstone.daemon.read_pid", return_value=1), \
|
|
162
|
+
patch("skcapstone.daemon.get_daemon_status", return_value={"uptime_seconds": 7320}):
|
|
163
|
+
result = _get_daemon_info(agent_home)
|
|
164
|
+
|
|
165
|
+
assert result["uptime"] == "2h 2m"
|
|
166
|
+
|
|
167
|
+
def test_message_count_included(self, agent_home: Path):
|
|
168
|
+
"""messages key present when daemon reports > 0 messages."""
|
|
169
|
+
from skcapstone.cli.status import _get_daemon_info
|
|
170
|
+
|
|
171
|
+
with patch("skcapstone.daemon.read_pid", return_value=1), \
|
|
172
|
+
patch("skcapstone.daemon.get_daemon_status", return_value={
|
|
173
|
+
"uptime_seconds": 60, "messages_received": 7,
|
|
174
|
+
}):
|
|
175
|
+
result = _get_daemon_info(agent_home)
|
|
176
|
+
|
|
177
|
+
assert result["messages"] == 7
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class TestGetLastConversation:
|
|
181
|
+
"""Tests for _get_last_conversation()."""
|
|
182
|
+
|
|
183
|
+
def test_none_when_no_conv_dir(self, agent_home: Path):
|
|
184
|
+
"""Returns None when conversations/ directory does not exist."""
|
|
185
|
+
from skcapstone.cli.status import _get_last_conversation
|
|
186
|
+
|
|
187
|
+
result = _get_last_conversation(agent_home)
|
|
188
|
+
assert result is None
|
|
189
|
+
|
|
190
|
+
def test_none_when_empty_conv_dir(self, agent_home: Path):
|
|
191
|
+
"""Returns None when conversations/ is empty."""
|
|
192
|
+
from skcapstone.cli.status import _get_last_conversation
|
|
193
|
+
|
|
194
|
+
(agent_home / "conversations").mkdir()
|
|
195
|
+
result = _get_last_conversation(agent_home)
|
|
196
|
+
assert result is None
|
|
197
|
+
|
|
198
|
+
def test_returns_most_recent_peer(self, agent_home: Path):
|
|
199
|
+
"""Returns the peer name of the most-recently-touched file."""
|
|
200
|
+
from skcapstone.cli.status import _get_last_conversation
|
|
201
|
+
|
|
202
|
+
conv_dir = agent_home / "conversations"
|
|
203
|
+
conv_dir.mkdir()
|
|
204
|
+
old_file = conv_dir / "alice.json"
|
|
205
|
+
new_file = conv_dir / "bob.json"
|
|
206
|
+
old_file.write_text("[]")
|
|
207
|
+
time.sleep(0.02) # ensure mtime difference
|
|
208
|
+
new_file.write_text("[]")
|
|
209
|
+
|
|
210
|
+
result = _get_last_conversation(agent_home)
|
|
211
|
+
assert result is not None
|
|
212
|
+
assert result["peer"] == "bob"
|
|
213
|
+
assert "when" in result
|
|
214
|
+
|
|
215
|
+
def test_recent_convo_shows_minutes(self, agent_home: Path):
|
|
216
|
+
"""A freshly written file shows time in minutes."""
|
|
217
|
+
from skcapstone.cli.status import _get_last_conversation
|
|
218
|
+
|
|
219
|
+
conv_dir = agent_home / "conversations"
|
|
220
|
+
conv_dir.mkdir()
|
|
221
|
+
(conv_dir / "charlie.json").write_text("[]")
|
|
222
|
+
|
|
223
|
+
result = _get_last_conversation(agent_home)
|
|
224
|
+
assert result is not None
|
|
225
|
+
assert "m ago" in result["when"] or "0m ago" in result["when"]
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
# ---------------------------------------------------------------------------
|
|
229
|
+
# CLI integration tests (via CliRunner)
|
|
230
|
+
# ---------------------------------------------------------------------------
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
class TestStatusCLI:
|
|
234
|
+
"""CLI integration tests for the status command."""
|
|
235
|
+
|
|
236
|
+
def test_help(self):
|
|
237
|
+
"""status --help exits 0 and shows description."""
|
|
238
|
+
from skcapstone.cli import main
|
|
239
|
+
|
|
240
|
+
result = CliRunner().invoke(main, ["status", "--help"])
|
|
241
|
+
assert result.exit_code == 0
|
|
242
|
+
assert "sovereign" in result.output.lower()
|
|
243
|
+
|
|
244
|
+
def test_missing_home_exits_nonzero(self, tmp_path: Path):
|
|
245
|
+
"""status on a nonexistent home exits with code 1."""
|
|
246
|
+
from skcapstone.cli import main
|
|
247
|
+
|
|
248
|
+
result = CliRunner().invoke(main, ["status", "--home", str(tmp_path / "nope")])
|
|
249
|
+
assert result.exit_code != 0
|
|
250
|
+
|
|
251
|
+
def test_daemon_stopped_shown(self, agent_home: Path):
|
|
252
|
+
"""'STOPPED' appears when daemon is not running."""
|
|
253
|
+
from skcapstone.cli import main
|
|
254
|
+
|
|
255
|
+
with patch("skcapstone.cli.status._get_daemon_info", return_value={"running": False}), \
|
|
256
|
+
patch("skcapstone.cli.status._probe_llm_backends", return_value={
|
|
257
|
+
"ollama": False, "anthropic": False, "grok": False,
|
|
258
|
+
"kimi": False, "nvidia": False,
|
|
259
|
+
}), \
|
|
260
|
+
patch("skcapstone.cli.status._print_consciousness_metrics"), \
|
|
261
|
+
patch("skcapstone.cli.status._get_last_conversation", return_value=None):
|
|
262
|
+
result = CliRunner().invoke(main, ["status", "--home", str(agent_home)])
|
|
263
|
+
|
|
264
|
+
assert result.exit_code == 0
|
|
265
|
+
assert "STOPPED" in result.output
|
|
266
|
+
|
|
267
|
+
def test_daemon_running_shown(self, agent_home: Path):
|
|
268
|
+
"""'RUNNING' appears with PID when daemon is alive."""
|
|
269
|
+
from skcapstone.cli import main
|
|
270
|
+
|
|
271
|
+
with patch("skcapstone.cli.status._get_daemon_info", return_value={
|
|
272
|
+
"running": True, "pid": 42, "uptime": "5m 3s",
|
|
273
|
+
}), \
|
|
274
|
+
patch("skcapstone.cli.status._probe_llm_backends", return_value={
|
|
275
|
+
"ollama": True, "anthropic": False, "grok": False,
|
|
276
|
+
"kimi": False, "nvidia": False,
|
|
277
|
+
}), \
|
|
278
|
+
patch("skcapstone.cli.status._print_consciousness_metrics"), \
|
|
279
|
+
patch("skcapstone.cli.status._get_last_conversation", return_value=None):
|
|
280
|
+
result = CliRunner().invoke(main, ["status", "--home", str(agent_home)])
|
|
281
|
+
|
|
282
|
+
assert result.exit_code == 0
|
|
283
|
+
assert "RUNNING" in result.output
|
|
284
|
+
assert "42" in result.output
|
|
285
|
+
|
|
286
|
+
def test_backends_section_present(self, agent_home: Path):
|
|
287
|
+
"""'Backends:' line appears in status output."""
|
|
288
|
+
from skcapstone.cli import main
|
|
289
|
+
|
|
290
|
+
with patch("skcapstone.cli.status._get_daemon_info", return_value={"running": False}), \
|
|
291
|
+
patch("skcapstone.cli.status._probe_llm_backends", return_value={
|
|
292
|
+
"ollama": True, "anthropic": False, "grok": False,
|
|
293
|
+
"kimi": False, "nvidia": False,
|
|
294
|
+
}), \
|
|
295
|
+
patch("skcapstone.cli.status._print_consciousness_metrics"), \
|
|
296
|
+
patch("skcapstone.cli.status._get_last_conversation", return_value=None):
|
|
297
|
+
result = CliRunner().invoke(main, ["status", "--home", str(agent_home)])
|
|
298
|
+
|
|
299
|
+
assert result.exit_code == 0
|
|
300
|
+
assert "Backends:" in result.output
|
|
301
|
+
|
|
302
|
+
def test_last_conversation_shown(self, agent_home: Path):
|
|
303
|
+
"""Last convo peer and time appear when conversation data exists."""
|
|
304
|
+
from skcapstone.cli import main
|
|
305
|
+
|
|
306
|
+
with patch("skcapstone.cli.status._get_daemon_info", return_value={"running": False}), \
|
|
307
|
+
patch("skcapstone.cli.status._probe_llm_backends", return_value={
|
|
308
|
+
"ollama": False, "anthropic": False, "grok": False,
|
|
309
|
+
"kimi": False, "nvidia": False,
|
|
310
|
+
}), \
|
|
311
|
+
patch("skcapstone.cli.status._print_consciousness_metrics"), \
|
|
312
|
+
patch("skcapstone.cli.status._get_last_conversation", return_value={
|
|
313
|
+
"peer": "alice", "when": "3m ago",
|
|
314
|
+
}):
|
|
315
|
+
result = CliRunner().invoke(main, ["status", "--home", str(agent_home)])
|
|
316
|
+
|
|
317
|
+
assert result.exit_code == 0
|
|
318
|
+
assert "alice" in result.output
|
|
319
|
+
assert "3m ago" in result.output
|
|
320
|
+
|
|
321
|
+
def test_disk_warning_when_low(self, agent_home: Path):
|
|
322
|
+
"""Disk warning printed when free space < 5 GB."""
|
|
323
|
+
from skcapstone.cli import main
|
|
324
|
+
|
|
325
|
+
low_usage = MagicMock()
|
|
326
|
+
low_usage.free = int(2.5 * 1024 ** 3) # 2.5 GB
|
|
327
|
+
|
|
328
|
+
with patch("skcapstone.cli.status._get_daemon_info", return_value={"running": False}), \
|
|
329
|
+
patch("skcapstone.cli.status._probe_llm_backends", return_value={
|
|
330
|
+
"ollama": False, "anthropic": False, "grok": False,
|
|
331
|
+
"kimi": False, "nvidia": False,
|
|
332
|
+
}), \
|
|
333
|
+
patch("skcapstone.cli.status._print_consciousness_metrics"), \
|
|
334
|
+
patch("skcapstone.cli.status._get_last_conversation", return_value=None), \
|
|
335
|
+
patch("shutil.disk_usage", return_value=low_usage):
|
|
336
|
+
result = CliRunner().invoke(main, ["status", "--home", str(agent_home)])
|
|
337
|
+
|
|
338
|
+
assert result.exit_code == 0
|
|
339
|
+
assert "WARNING" in result.output
|
|
340
|
+
assert "2.5" in result.output
|
|
341
|
+
|
|
342
|
+
def test_no_disk_warning_when_ample(self, agent_home: Path):
|
|
343
|
+
"""No disk warning when free space >= 5 GB."""
|
|
344
|
+
from skcapstone.cli import main
|
|
345
|
+
|
|
346
|
+
big_usage = MagicMock()
|
|
347
|
+
big_usage.free = int(50 * 1024 ** 3) # 50 GB
|
|
348
|
+
|
|
349
|
+
with patch("skcapstone.cli.status._get_daemon_info", return_value={"running": False}), \
|
|
350
|
+
patch("skcapstone.cli.status._probe_llm_backends", return_value={
|
|
351
|
+
"ollama": False, "anthropic": False, "grok": False,
|
|
352
|
+
"kimi": False, "nvidia": False,
|
|
353
|
+
}), \
|
|
354
|
+
patch("skcapstone.cli.status._print_consciousness_metrics"), \
|
|
355
|
+
patch("skcapstone.cli.status._get_last_conversation", return_value=None), \
|
|
356
|
+
patch("shutil.disk_usage", return_value=big_usage):
|
|
357
|
+
result = CliRunner().invoke(main, ["status", "--home", str(agent_home)])
|
|
358
|
+
|
|
359
|
+
assert result.exit_code == 0
|
|
360
|
+
assert "WARNING" not in result.output
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
class TestDoctorCLI:
|
|
364
|
+
"""Smoke tests for the doctor command."""
|
|
365
|
+
|
|
366
|
+
def test_doctor_help(self):
|
|
367
|
+
"""doctor --help exits 0."""
|
|
368
|
+
from skcapstone.cli import main
|
|
369
|
+
|
|
370
|
+
result = CliRunner().invoke(main, ["doctor", "--help"])
|
|
371
|
+
assert result.exit_code == 0
|
|
372
|
+
assert "Diagnose" in result.output
|
|
373
|
+
|
|
374
|
+
def test_doctor_on_agent_home(self, agent_home: Path):
|
|
375
|
+
"""doctor produces output with at least one section heading."""
|
|
376
|
+
from skcapstone.cli import main
|
|
377
|
+
|
|
378
|
+
result = CliRunner().invoke(main, ["doctor", "--home", str(agent_home)])
|
|
379
|
+
assert result.exit_code == 0
|
|
380
|
+
# At least one category must be rendered
|
|
381
|
+
assert any(
|
|
382
|
+
kw in result.output
|
|
383
|
+
for kw in ["Python Packages", "Agent Home", "Identity", "Memory"]
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
def test_doctor_json_output(self, agent_home: Path):
|
|
387
|
+
"""doctor --json-out emits valid JSON with expected keys."""
|
|
388
|
+
from skcapstone.cli import main
|
|
389
|
+
|
|
390
|
+
result = CliRunner().invoke(main, ["doctor", "--home", str(agent_home), "--json-out"])
|
|
391
|
+
assert result.exit_code == 0
|
|
392
|
+
data = json.loads(result.output)
|
|
393
|
+
assert "passed" in data
|
|
394
|
+
assert "failed" in data
|
|
395
|
+
assert isinstance(data["checks"], list)
|