@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,199 @@
|
|
|
1
|
+
"""Tests for skcapstone.log_config — structured JSON logging."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import logging
|
|
7
|
+
import logging.handlers
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
import pytest
|
|
12
|
+
|
|
13
|
+
import skcapstone.log_config as log_config_module
|
|
14
|
+
from skcapstone.log_config import JsonFormatter, configure_logging
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# ---------------------------------------------------------------------------
|
|
18
|
+
# Fixtures
|
|
19
|
+
# ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@pytest.fixture(autouse=True)
|
|
23
|
+
def _reset_log_config(monkeypatch):
|
|
24
|
+
"""Reset the _CONFIGURED flag and remove rotating file handlers before/after each test."""
|
|
25
|
+
monkeypatch.setattr(log_config_module, "_CONFIGURED", False)
|
|
26
|
+
root = logging.getLogger()
|
|
27
|
+
# Remove any RotatingFileHandlers left by previous tests before this test runs.
|
|
28
|
+
for h in list(root.handlers):
|
|
29
|
+
if isinstance(h, logging.handlers.RotatingFileHandler):
|
|
30
|
+
root.removeHandler(h)
|
|
31
|
+
try:
|
|
32
|
+
h.close()
|
|
33
|
+
except Exception:
|
|
34
|
+
pass
|
|
35
|
+
handlers_before = list(root.handlers)
|
|
36
|
+
yield
|
|
37
|
+
# Tear down any handlers added during the test.
|
|
38
|
+
for h in list(root.handlers):
|
|
39
|
+
if h not in handlers_before:
|
|
40
|
+
root.removeHandler(h)
|
|
41
|
+
try:
|
|
42
|
+
h.close()
|
|
43
|
+
except Exception:
|
|
44
|
+
pass
|
|
45
|
+
monkeypatch.setattr(log_config_module, "_CONFIGURED", False)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# ---------------------------------------------------------------------------
|
|
49
|
+
# JsonFormatter tests
|
|
50
|
+
# ---------------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class TestJsonFormatter:
|
|
54
|
+
def test_outputs_valid_json(self):
|
|
55
|
+
formatter = JsonFormatter()
|
|
56
|
+
record = logging.LogRecord(
|
|
57
|
+
name="test.logger",
|
|
58
|
+
level=logging.INFO,
|
|
59
|
+
pathname="",
|
|
60
|
+
lineno=0,
|
|
61
|
+
msg="hello world",
|
|
62
|
+
args=(),
|
|
63
|
+
exc_info=None,
|
|
64
|
+
)
|
|
65
|
+
output = formatter.format(record)
|
|
66
|
+
data = json.loads(output) # must not raise
|
|
67
|
+
assert isinstance(data, dict)
|
|
68
|
+
|
|
69
|
+
def test_required_fields_present(self):
|
|
70
|
+
formatter = JsonFormatter()
|
|
71
|
+
record = logging.LogRecord(
|
|
72
|
+
name="skcapstone.daemon",
|
|
73
|
+
level=logging.WARNING,
|
|
74
|
+
pathname="",
|
|
75
|
+
lineno=0,
|
|
76
|
+
msg="something %s",
|
|
77
|
+
args=("bad",),
|
|
78
|
+
exc_info=None,
|
|
79
|
+
)
|
|
80
|
+
data = json.loads(formatter.format(record))
|
|
81
|
+
assert data["level"] == "WARNING"
|
|
82
|
+
assert data["logger"] == "skcapstone.daemon"
|
|
83
|
+
assert data["msg"] == "something bad"
|
|
84
|
+
assert "ts" in data
|
|
85
|
+
# ts should be a parseable ISO-8601 string
|
|
86
|
+
from datetime import datetime
|
|
87
|
+
datetime.fromisoformat(data["ts"]) # raises if malformed
|
|
88
|
+
|
|
89
|
+
def test_exception_info_included(self):
|
|
90
|
+
formatter = JsonFormatter()
|
|
91
|
+
try:
|
|
92
|
+
raise RuntimeError("boom")
|
|
93
|
+
except RuntimeError:
|
|
94
|
+
exc_info = sys.exc_info()
|
|
95
|
+
|
|
96
|
+
record = logging.LogRecord(
|
|
97
|
+
name="test",
|
|
98
|
+
level=logging.ERROR,
|
|
99
|
+
pathname="",
|
|
100
|
+
lineno=0,
|
|
101
|
+
msg="an error",
|
|
102
|
+
args=(),
|
|
103
|
+
exc_info=exc_info,
|
|
104
|
+
)
|
|
105
|
+
data = json.loads(formatter.format(record))
|
|
106
|
+
assert "exc" in data
|
|
107
|
+
assert "RuntimeError" in data["exc"]
|
|
108
|
+
assert "boom" in data["exc"]
|
|
109
|
+
|
|
110
|
+
def test_extra_fields_forwarded(self):
|
|
111
|
+
formatter = JsonFormatter()
|
|
112
|
+
record = logging.LogRecord(
|
|
113
|
+
name="test",
|
|
114
|
+
level=logging.DEBUG,
|
|
115
|
+
pathname="",
|
|
116
|
+
lineno=0,
|
|
117
|
+
msg="extra test",
|
|
118
|
+
args=(),
|
|
119
|
+
exc_info=None,
|
|
120
|
+
)
|
|
121
|
+
record.request_id = "abc-123"
|
|
122
|
+
data = json.loads(formatter.format(record))
|
|
123
|
+
assert data.get("request_id") == "abc-123"
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
# ---------------------------------------------------------------------------
|
|
127
|
+
# configure_logging tests
|
|
128
|
+
# ---------------------------------------------------------------------------
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class TestConfigureLogging:
|
|
132
|
+
def test_creates_rotating_file_handler(self, tmp_path):
|
|
133
|
+
log_file = tmp_path / "logs" / "daemon.log"
|
|
134
|
+
configure_logging(log_file)
|
|
135
|
+
root = logging.getLogger()
|
|
136
|
+
rotating = [
|
|
137
|
+
h for h in root.handlers if isinstance(h, logging.handlers.RotatingFileHandler)
|
|
138
|
+
]
|
|
139
|
+
assert len(rotating) == 1
|
|
140
|
+
h = rotating[0]
|
|
141
|
+
assert h.maxBytes == 10 * 1024 * 1024
|
|
142
|
+
assert h.backupCount == 5
|
|
143
|
+
|
|
144
|
+
def test_file_handler_uses_json_formatter(self, tmp_path):
|
|
145
|
+
log_file = tmp_path / "daemon.log"
|
|
146
|
+
configure_logging(log_file)
|
|
147
|
+
root = logging.getLogger()
|
|
148
|
+
rotating = [
|
|
149
|
+
h for h in root.handlers if isinstance(h, logging.handlers.RotatingFileHandler)
|
|
150
|
+
]
|
|
151
|
+
assert len(rotating) == 1
|
|
152
|
+
assert isinstance(rotating[0].formatter, JsonFormatter)
|
|
153
|
+
|
|
154
|
+
def test_creates_console_handler_at_info(self, tmp_path):
|
|
155
|
+
log_file = tmp_path / "daemon.log"
|
|
156
|
+
configure_logging(log_file)
|
|
157
|
+
root = logging.getLogger()
|
|
158
|
+
stream_handlers = [
|
|
159
|
+
h
|
|
160
|
+
for h in root.handlers
|
|
161
|
+
if isinstance(h, logging.StreamHandler)
|
|
162
|
+
and not isinstance(h, logging.handlers.RotatingFileHandler)
|
|
163
|
+
]
|
|
164
|
+
assert len(stream_handlers) >= 1
|
|
165
|
+
assert any(h.level == logging.INFO for h in stream_handlers)
|
|
166
|
+
|
|
167
|
+
def test_creates_log_parent_directory(self, tmp_path):
|
|
168
|
+
log_file = tmp_path / "nested" / "deep" / "daemon.log"
|
|
169
|
+
assert not log_file.parent.exists()
|
|
170
|
+
configure_logging(log_file)
|
|
171
|
+
assert log_file.parent.exists()
|
|
172
|
+
|
|
173
|
+
def test_idempotent_no_duplicate_handlers(self, tmp_path):
|
|
174
|
+
log_file = tmp_path / "daemon.log"
|
|
175
|
+
configure_logging(log_file)
|
|
176
|
+
root = logging.getLogger()
|
|
177
|
+
count_after_first = len(root.handlers)
|
|
178
|
+
# Second call must be a no-op (_CONFIGURED is now True).
|
|
179
|
+
configure_logging(log_file)
|
|
180
|
+
assert len(root.handlers) == count_after_first
|
|
181
|
+
|
|
182
|
+
def test_file_handler_log_level_is_debug_by_default(self, tmp_path):
|
|
183
|
+
log_file = tmp_path / "daemon.log"
|
|
184
|
+
configure_logging(log_file)
|
|
185
|
+
root = logging.getLogger()
|
|
186
|
+
rotating = [
|
|
187
|
+
h for h in root.handlers if isinstance(h, logging.handlers.RotatingFileHandler)
|
|
188
|
+
]
|
|
189
|
+
assert rotating[0].level == logging.DEBUG
|
|
190
|
+
|
|
191
|
+
def test_custom_max_bytes_and_backup_count(self, tmp_path):
|
|
192
|
+
log_file = tmp_path / "daemon.log"
|
|
193
|
+
configure_logging(log_file, max_bytes=5 * 1024 * 1024, backup_count=3)
|
|
194
|
+
root = logging.getLogger()
|
|
195
|
+
rotating = [
|
|
196
|
+
h for h in root.handlers if isinstance(h, logging.handlers.RotatingFileHandler)
|
|
197
|
+
]
|
|
198
|
+
assert rotating[0].maxBytes == 5 * 1024 * 1024
|
|
199
|
+
assert rotating[0].backupCount == 3
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
"""Tests for ``skcapstone logs`` command.
|
|
2
|
+
|
|
3
|
+
Covers:
|
|
4
|
+
- help text exposes all options
|
|
5
|
+
- graceful handling of missing log file
|
|
6
|
+
- --lines N limits output to N recent lines
|
|
7
|
+
- --level filters by minimum log level
|
|
8
|
+
- --peer filters lines containing a peer name substring
|
|
9
|
+
- --follow outputs existing lines before entering the tail loop
|
|
10
|
+
- --follow combined with --level filter
|
|
11
|
+
- helper: _parse_level
|
|
12
|
+
- helper: _matches_filters
|
|
13
|
+
- helper: _tail
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from unittest.mock import patch
|
|
20
|
+
|
|
21
|
+
import pytest
|
|
22
|
+
from click.testing import CliRunner
|
|
23
|
+
|
|
24
|
+
from skcapstone.cli import main
|
|
25
|
+
from skcapstone.cli.logs_cmd import _matches_filters, _parse_level, _tail
|
|
26
|
+
|
|
27
|
+
runner = CliRunner()
|
|
28
|
+
|
|
29
|
+
# ---------------------------------------------------------------------------
|
|
30
|
+
# Shared fixture
|
|
31
|
+
# ---------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
SAMPLE_LOG = (
|
|
34
|
+
"2026-01-01 00:00:01,000 [skcapstone.daemon] DEBUG: debug message\n"
|
|
35
|
+
"2026-01-01 00:00:02,000 [skcapstone.daemon] INFO: started up\n"
|
|
36
|
+
"2026-01-01 00:00:03,000 [skcapstone.sync] WARNING: peer opus slow\n"
|
|
37
|
+
"2026-01-01 00:00:04,000 [skcapstone.daemon] ERROR: transport failed\n"
|
|
38
|
+
"2026-01-01 00:00:05,000 [skcapstone.daemon] INFO: heartbeat ok\n"
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@pytest.fixture
|
|
43
|
+
def log_home(tmp_path: Path) -> Path:
|
|
44
|
+
"""Agent home with a pre-populated daemon.log."""
|
|
45
|
+
log_dir = tmp_path / "logs"
|
|
46
|
+
log_dir.mkdir(parents=True)
|
|
47
|
+
(log_dir / "daemon.log").write_text(SAMPLE_LOG)
|
|
48
|
+
return tmp_path
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# ---------------------------------------------------------------------------
|
|
52
|
+
# Unit tests — _parse_level
|
|
53
|
+
# ---------------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
class TestParseLevel:
|
|
56
|
+
def test_info(self):
|
|
57
|
+
assert _parse_level("2026-01-01 [skcapstone.daemon] INFO: hello") == "INFO"
|
|
58
|
+
|
|
59
|
+
def test_error(self):
|
|
60
|
+
assert _parse_level("2026-01-01 [transport] ERROR: connection refused") == "ERROR"
|
|
61
|
+
|
|
62
|
+
def test_warning(self):
|
|
63
|
+
assert _parse_level("... [x] WARNING: slow") == "WARNING"
|
|
64
|
+
|
|
65
|
+
def test_debug(self):
|
|
66
|
+
assert _parse_level("2026-01-01 [y] DEBUG: verbose") == "DEBUG"
|
|
67
|
+
|
|
68
|
+
def test_critical(self):
|
|
69
|
+
assert _parse_level("2026-01-01 [z] CRITICAL: crash") == "CRITICAL"
|
|
70
|
+
|
|
71
|
+
def test_unparseable_returns_none(self):
|
|
72
|
+
assert _parse_level("not a structured log line") is None
|
|
73
|
+
|
|
74
|
+
def test_empty_string(self):
|
|
75
|
+
assert _parse_level("") is None
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# ---------------------------------------------------------------------------
|
|
79
|
+
# Unit tests — _matches_filters
|
|
80
|
+
# ---------------------------------------------------------------------------
|
|
81
|
+
|
|
82
|
+
class TestMatchesFilters:
|
|
83
|
+
def test_no_filters_accepts_all(self):
|
|
84
|
+
assert _matches_filters("any line whatsoever", None, None) is True
|
|
85
|
+
|
|
86
|
+
def test_level_min_warning_accepts_error(self):
|
|
87
|
+
line = "2026-01-01 [x] ERROR: something bad"
|
|
88
|
+
assert _matches_filters(line, "WARNING", None) is True
|
|
89
|
+
|
|
90
|
+
def test_level_min_error_rejects_warning(self):
|
|
91
|
+
line = "2026-01-01 [x] WARNING: minor issue"
|
|
92
|
+
assert _matches_filters(line, "ERROR", None) is False
|
|
93
|
+
|
|
94
|
+
def test_level_min_error_rejects_info(self):
|
|
95
|
+
line = "2026-01-01 [x] INFO: routine"
|
|
96
|
+
assert _matches_filters(line, "ERROR", None) is False
|
|
97
|
+
|
|
98
|
+
def test_level_exact_match(self):
|
|
99
|
+
line = "2026-01-01 [x] INFO: hello"
|
|
100
|
+
assert _matches_filters(line, "INFO", None) is True
|
|
101
|
+
|
|
102
|
+
def test_level_unparseable_excluded(self):
|
|
103
|
+
assert _matches_filters("plain text", "INFO", None) is False
|
|
104
|
+
|
|
105
|
+
def test_peer_match(self):
|
|
106
|
+
line = "2026-01-01 [x] INFO: message from opus"
|
|
107
|
+
assert _matches_filters(line, None, "opus") is True
|
|
108
|
+
|
|
109
|
+
def test_peer_case_insensitive(self):
|
|
110
|
+
line = "2026-01-01 [x] INFO: message from OPUS"
|
|
111
|
+
assert _matches_filters(line, None, "opus") is True
|
|
112
|
+
|
|
113
|
+
def test_peer_no_match(self):
|
|
114
|
+
line = "2026-01-01 [x] INFO: message from jarvis"
|
|
115
|
+
assert _matches_filters(line, None, "opus") is False
|
|
116
|
+
|
|
117
|
+
def test_combined_both_pass(self):
|
|
118
|
+
line = "2026-01-01 [x] ERROR: opus transport failed"
|
|
119
|
+
assert _matches_filters(line, "WARNING", "opus") is True
|
|
120
|
+
|
|
121
|
+
def test_combined_level_fails(self):
|
|
122
|
+
line = "2026-01-01 [x] DEBUG: opus verbose"
|
|
123
|
+
assert _matches_filters(line, "WARNING", "opus") is False
|
|
124
|
+
|
|
125
|
+
def test_combined_peer_fails(self):
|
|
126
|
+
line = "2026-01-01 [x] ERROR: jarvis transport failed"
|
|
127
|
+
assert _matches_filters(line, "WARNING", "opus") is False
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
# ---------------------------------------------------------------------------
|
|
131
|
+
# Unit tests — _tail
|
|
132
|
+
# ---------------------------------------------------------------------------
|
|
133
|
+
|
|
134
|
+
class TestTail:
|
|
135
|
+
def test_returns_last_n_lines(self, tmp_path):
|
|
136
|
+
f = tmp_path / "test.log"
|
|
137
|
+
f.write_text("\n".join(f"line{i}" for i in range(20)) + "\n")
|
|
138
|
+
lines = _tail(f, 5)
|
|
139
|
+
assert len(lines) == 5
|
|
140
|
+
assert "line19" in lines[-1]
|
|
141
|
+
|
|
142
|
+
def test_fewer_lines_than_n(self, tmp_path):
|
|
143
|
+
f = tmp_path / "test.log"
|
|
144
|
+
f.write_text("a\nb\nc\n")
|
|
145
|
+
lines = _tail(f, 100)
|
|
146
|
+
assert len(lines) == 3
|
|
147
|
+
|
|
148
|
+
def test_single_line(self, tmp_path):
|
|
149
|
+
f = tmp_path / "test.log"
|
|
150
|
+
f.write_text("only one line\n")
|
|
151
|
+
lines = _tail(f, 10)
|
|
152
|
+
assert len(lines) == 1
|
|
153
|
+
assert "only one line" in lines[0]
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
# ---------------------------------------------------------------------------
|
|
157
|
+
# CLI integration tests
|
|
158
|
+
# ---------------------------------------------------------------------------
|
|
159
|
+
|
|
160
|
+
class TestLogsCLI:
|
|
161
|
+
def test_help_shows_all_options(self):
|
|
162
|
+
"""``logs --help`` exposes every documented option."""
|
|
163
|
+
result = runner.invoke(main, ["logs", "--help"])
|
|
164
|
+
assert result.exit_code == 0
|
|
165
|
+
assert "--follow" in result.output
|
|
166
|
+
assert "--lines" in result.output
|
|
167
|
+
assert "--level" in result.output
|
|
168
|
+
assert "--peer" in result.output
|
|
169
|
+
|
|
170
|
+
def test_missing_log_file_friendly_message(self, tmp_path):
|
|
171
|
+
"""Missing daemon.log prints a helpful message and exits 0."""
|
|
172
|
+
result = runner.invoke(main, ["logs", "--home", str(tmp_path)])
|
|
173
|
+
assert result.exit_code == 0
|
|
174
|
+
assert "not found" in result.output.lower() or "log file" in result.output.lower()
|
|
175
|
+
|
|
176
|
+
def test_lines_limits_output(self, log_home):
|
|
177
|
+
"""``-n 2`` returns only the 2 most recent lines."""
|
|
178
|
+
result = runner.invoke(main, ["logs", "--home", str(log_home), "-n", "2"])
|
|
179
|
+
assert result.exit_code == 0
|
|
180
|
+
# Last 2 lines of fixture
|
|
181
|
+
assert "heartbeat ok" in result.output
|
|
182
|
+
assert "transport failed" in result.output
|
|
183
|
+
# 3rd-to-last must not appear
|
|
184
|
+
assert "peer opus slow" not in result.output
|
|
185
|
+
|
|
186
|
+
def test_level_filter_hides_lower_levels(self, log_home):
|
|
187
|
+
"""``--level WARNING`` hides DEBUG and INFO lines."""
|
|
188
|
+
result = runner.invoke(
|
|
189
|
+
main, ["logs", "--home", str(log_home), "--level", "WARNING"]
|
|
190
|
+
)
|
|
191
|
+
assert result.exit_code == 0
|
|
192
|
+
assert "debug message" not in result.output
|
|
193
|
+
assert "started up" not in result.output
|
|
194
|
+
assert "heartbeat ok" not in result.output
|
|
195
|
+
assert "peer opus slow" in result.output # WARNING
|
|
196
|
+
assert "transport failed" in result.output # ERROR
|
|
197
|
+
|
|
198
|
+
def test_level_filter_case_insensitive(self, log_home):
|
|
199
|
+
"""``--level warning`` (lowercase) works identically."""
|
|
200
|
+
result = runner.invoke(
|
|
201
|
+
main, ["logs", "--home", str(log_home), "--level", "warning"]
|
|
202
|
+
)
|
|
203
|
+
assert result.exit_code == 0
|
|
204
|
+
assert "peer opus slow" in result.output
|
|
205
|
+
assert "debug message" not in result.output
|
|
206
|
+
|
|
207
|
+
def test_peer_filter_only_matching_lines(self, log_home):
|
|
208
|
+
"""``--peer opus`` shows only lines containing 'opus'."""
|
|
209
|
+
result = runner.invoke(
|
|
210
|
+
main, ["logs", "--home", str(log_home), "--peer", "opus"]
|
|
211
|
+
)
|
|
212
|
+
assert result.exit_code == 0
|
|
213
|
+
assert "peer opus slow" in result.output
|
|
214
|
+
# Lines without 'opus' must be absent
|
|
215
|
+
assert "heartbeat ok" not in result.output
|
|
216
|
+
assert "transport failed" not in result.output
|
|
217
|
+
assert "debug message" not in result.output
|
|
218
|
+
|
|
219
|
+
def test_no_matching_lines_message(self, log_home):
|
|
220
|
+
"""Filtering that matches nothing prints a 'no matching' notice."""
|
|
221
|
+
result = runner.invoke(
|
|
222
|
+
main, ["logs", "--home", str(log_home), "--peer", "zz_no_such_peer_zz"]
|
|
223
|
+
)
|
|
224
|
+
assert result.exit_code == 0
|
|
225
|
+
assert "no matching" in result.output.lower()
|
|
226
|
+
|
|
227
|
+
def test_follow_shows_initial_lines(self, log_home):
|
|
228
|
+
"""``--follow`` outputs the last N historical lines before entering the loop."""
|
|
229
|
+
|
|
230
|
+
def fake_sleep(_n: float) -> None:
|
|
231
|
+
raise KeyboardInterrupt()
|
|
232
|
+
|
|
233
|
+
with patch("skcapstone.cli.logs_cmd.time.sleep", fake_sleep):
|
|
234
|
+
result = runner.invoke(
|
|
235
|
+
main, ["logs", "--home", str(log_home), "--follow", "-n", "3"]
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
assert result.exit_code == 0
|
|
239
|
+
# Last 3 lines of fixture
|
|
240
|
+
assert "heartbeat ok" in result.output
|
|
241
|
+
assert "transport failed" in result.output
|
|
242
|
+
assert "peer opus slow" in result.output
|
|
243
|
+
# First 2 lines must NOT appear (only last 3 requested)
|
|
244
|
+
assert "debug message" not in result.output
|
|
245
|
+
assert "started up" not in result.output
|
|
246
|
+
|
|
247
|
+
def test_follow_with_level_filter(self, log_home):
|
|
248
|
+
"""``--follow --level ERROR`` only streams lines at ERROR or above."""
|
|
249
|
+
|
|
250
|
+
def fake_sleep(_n: float) -> None:
|
|
251
|
+
raise KeyboardInterrupt()
|
|
252
|
+
|
|
253
|
+
with patch("skcapstone.cli.logs_cmd.time.sleep", fake_sleep):
|
|
254
|
+
result = runner.invoke(
|
|
255
|
+
main,
|
|
256
|
+
["logs", "--home", str(log_home), "--follow", "--level", "ERROR"],
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
assert result.exit_code == 0
|
|
260
|
+
assert "transport failed" in result.output
|
|
261
|
+
assert "debug message" not in result.output
|
|
262
|
+
assert "started up" not in result.output
|
|
263
|
+
assert "peer opus slow" not in result.output # WARNING < ERROR
|
|
264
|
+
|
|
265
|
+
def test_follow_streams_new_content(self, log_home):
|
|
266
|
+
"""``--follow`` reads content appended after the initial seek."""
|
|
267
|
+
log_file = log_home / "logs" / "daemon.log"
|
|
268
|
+
|
|
269
|
+
call_count = 0
|
|
270
|
+
|
|
271
|
+
def fake_sleep(_n: float) -> None:
|
|
272
|
+
nonlocal call_count
|
|
273
|
+
call_count += 1
|
|
274
|
+
if call_count == 1:
|
|
275
|
+
# Append a new line on first poll
|
|
276
|
+
with open(log_file, "a") as fh:
|
|
277
|
+
fh.write("2026-01-01 00:01:00,000 [skcapstone.daemon] INFO: new entry\n")
|
|
278
|
+
else:
|
|
279
|
+
raise KeyboardInterrupt()
|
|
280
|
+
|
|
281
|
+
with patch("skcapstone.cli.logs_cmd.time.sleep", fake_sleep):
|
|
282
|
+
result = runner.invoke(
|
|
283
|
+
main, ["logs", "--home", str(log_home), "--follow"]
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
assert result.exit_code == 0
|
|
287
|
+
assert "new entry" in result.output
|