@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,360 @@
|
|
|
1
|
+
"""Benchmark LLM response time across all available backends.
|
|
2
|
+
|
|
3
|
+
Sends a configurable prompt to each detected backend, measures wall-clock
|
|
4
|
+
latency, and renders a Rich table (or JSON) with results.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
import time
|
|
12
|
+
from typing import Optional
|
|
13
|
+
|
|
14
|
+
import click
|
|
15
|
+
from rich.table import Table
|
|
16
|
+
|
|
17
|
+
from ._common import AGENT_HOME, console
|
|
18
|
+
|
|
19
|
+
# Ordered list of all known backends (matches consciousness_loop.py fallback_chain)
|
|
20
|
+
BACKENDS: list[str] = ["ollama", "grok", "kimi", "nvidia", "anthropic", "openai", "passthrough"]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# ---------------------------------------------------------------------------
|
|
24
|
+
# BenchmarkRunner
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class BenchmarkRunner:
|
|
29
|
+
"""Run latency benchmarks against LLM backends.
|
|
30
|
+
|
|
31
|
+
Each backend is called with a simple prompt and the round-trip wall-clock
|
|
32
|
+
time is recorded. Cloud backends (anthropic, openai, grok, kimi, nvidia)
|
|
33
|
+
require both an API key *and* the respective SDK to be installed.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
prompt: The prompt string to send to every backend.
|
|
37
|
+
timeout: Per-backend HTTP/API timeout in seconds.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def __init__(self, prompt: str = "Hello", timeout: float = 30.0) -> None:
|
|
41
|
+
self.prompt = prompt
|
|
42
|
+
self.timeout = timeout
|
|
43
|
+
|
|
44
|
+
# ------------------------------------------------------------------
|
|
45
|
+
# Detection
|
|
46
|
+
# ------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
def detect_backends(self) -> dict[str, bool]:
|
|
49
|
+
"""Return a mapping of backend name → availability.
|
|
50
|
+
|
|
51
|
+
Ollama is probed via HTTP; cloud providers are available when the
|
|
52
|
+
corresponding API key env-var is set; passthrough is always True.
|
|
53
|
+
"""
|
|
54
|
+
return {
|
|
55
|
+
"ollama": self._probe_ollama(),
|
|
56
|
+
"grok": bool(os.environ.get("XAI_API_KEY")),
|
|
57
|
+
"kimi": bool(os.environ.get("MOONSHOT_API_KEY")),
|
|
58
|
+
"nvidia": bool(os.environ.get("NVIDIA_API_KEY")),
|
|
59
|
+
"anthropic": bool(os.environ.get("ANTHROPIC_API_KEY")),
|
|
60
|
+
"openai": bool(os.environ.get("OPENAI_API_KEY")),
|
|
61
|
+
"passthrough": True,
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
def _probe_ollama(self) -> bool:
|
|
65
|
+
"""Return True if the Ollama API is reachable."""
|
|
66
|
+
import urllib.request
|
|
67
|
+
|
|
68
|
+
host = os.environ.get("OLLAMA_HOST", "http://localhost:11434")
|
|
69
|
+
try:
|
|
70
|
+
with urllib.request.urlopen(f"{host}/api/tags", timeout=2):
|
|
71
|
+
return True
|
|
72
|
+
except Exception:
|
|
73
|
+
return False
|
|
74
|
+
|
|
75
|
+
# ------------------------------------------------------------------
|
|
76
|
+
# Runner
|
|
77
|
+
# ------------------------------------------------------------------
|
|
78
|
+
|
|
79
|
+
def run_all(self, skip_unavailable: bool = True) -> list[dict]:
|
|
80
|
+
"""Benchmark all backends and return a list of result dicts.
|
|
81
|
+
|
|
82
|
+
Result dict keys: ``backend``, ``status``, ``ms``, ``model``, ``error``.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
skip_unavailable: When True, unavailable backends are included in
|
|
86
|
+
the result list with ``status="unavailable"`` but are not
|
|
87
|
+
actually called.
|
|
88
|
+
"""
|
|
89
|
+
available = self.detect_backends()
|
|
90
|
+
results: list[dict] = []
|
|
91
|
+
for name in BACKENDS:
|
|
92
|
+
if not available.get(name, False):
|
|
93
|
+
results.append(
|
|
94
|
+
{"backend": name, "status": "unavailable", "ms": None, "model": None, "error": None}
|
|
95
|
+
)
|
|
96
|
+
continue
|
|
97
|
+
results.append(self.run_backend(name))
|
|
98
|
+
return results
|
|
99
|
+
|
|
100
|
+
def run_backend(self, name: str) -> dict:
|
|
101
|
+
"""Benchmark a single backend by name.
|
|
102
|
+
|
|
103
|
+
Returns a result dict with keys: ``backend``, ``status``, ``ms``,
|
|
104
|
+
``model``, ``error``.
|
|
105
|
+
"""
|
|
106
|
+
method = getattr(self, f"_bench_{name}", None)
|
|
107
|
+
if method is None:
|
|
108
|
+
return {
|
|
109
|
+
"backend": name,
|
|
110
|
+
"status": "unsupported",
|
|
111
|
+
"ms": None,
|
|
112
|
+
"model": None,
|
|
113
|
+
"error": f"No benchmark method for backend '{name}'",
|
|
114
|
+
}
|
|
115
|
+
try:
|
|
116
|
+
return method()
|
|
117
|
+
except Exception as exc:
|
|
118
|
+
return {
|
|
119
|
+
"backend": name,
|
|
120
|
+
"status": "error",
|
|
121
|
+
"ms": None,
|
|
122
|
+
"model": None,
|
|
123
|
+
"error": str(exc)[:120],
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
# ------------------------------------------------------------------
|
|
127
|
+
# Per-backend implementations
|
|
128
|
+
# ------------------------------------------------------------------
|
|
129
|
+
|
|
130
|
+
def _bench_passthrough(self) -> dict:
|
|
131
|
+
"""Passthrough — instant in-process mock (always succeeds)."""
|
|
132
|
+
t0 = time.perf_counter()
|
|
133
|
+
_ = f"[passthrough] {self.prompt}"
|
|
134
|
+
ms = round((time.perf_counter() - t0) * 1000, 3)
|
|
135
|
+
return {"backend": "passthrough", "status": "ok", "ms": ms, "model": "mock", "error": None}
|
|
136
|
+
|
|
137
|
+
def _bench_ollama(self) -> dict:
|
|
138
|
+
"""Ollama — local HTTP POST to /api/generate."""
|
|
139
|
+
import urllib.request
|
|
140
|
+
|
|
141
|
+
host = os.environ.get("OLLAMA_HOST", "http://localhost:11434")
|
|
142
|
+
model = os.environ.get("OLLAMA_MODEL", "llama3.2")
|
|
143
|
+
payload = json.dumps(
|
|
144
|
+
{"model": model, "prompt": self.prompt, "stream": False}
|
|
145
|
+
).encode()
|
|
146
|
+
req = urllib.request.Request(
|
|
147
|
+
f"{host}/api/generate",
|
|
148
|
+
data=payload,
|
|
149
|
+
headers={"Content-Type": "application/json"},
|
|
150
|
+
)
|
|
151
|
+
t0 = time.perf_counter()
|
|
152
|
+
with urllib.request.urlopen(req, timeout=self.timeout) as resp:
|
|
153
|
+
data = json.loads(resp.read())
|
|
154
|
+
ms = round((time.perf_counter() - t0) * 1000, 1)
|
|
155
|
+
return {
|
|
156
|
+
"backend": "ollama",
|
|
157
|
+
"status": "ok",
|
|
158
|
+
"ms": ms,
|
|
159
|
+
"model": data.get("model", model),
|
|
160
|
+
"error": None,
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
def _bench_anthropic(self) -> dict:
|
|
164
|
+
"""Anthropic Claude — requires ``anthropic`` SDK + ANTHROPIC_API_KEY."""
|
|
165
|
+
try:
|
|
166
|
+
import anthropic as _anthropic
|
|
167
|
+
except ImportError:
|
|
168
|
+
raise RuntimeError("anthropic SDK not installed — run: pip install anthropic")
|
|
169
|
+
|
|
170
|
+
model = "claude-haiku-4-5-20251001"
|
|
171
|
+
client = _anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
|
|
172
|
+
t0 = time.perf_counter()
|
|
173
|
+
client.messages.create(
|
|
174
|
+
model=model,
|
|
175
|
+
max_tokens=64,
|
|
176
|
+
messages=[{"role": "user", "content": self.prompt}],
|
|
177
|
+
)
|
|
178
|
+
ms = round((time.perf_counter() - t0) * 1000, 1)
|
|
179
|
+
return {"backend": "anthropic", "status": "ok", "ms": ms, "model": model, "error": None}
|
|
180
|
+
|
|
181
|
+
def _bench_openai(self) -> dict:
|
|
182
|
+
"""OpenAI — requires ``openai`` SDK + OPENAI_API_KEY."""
|
|
183
|
+
try:
|
|
184
|
+
import openai as _openai
|
|
185
|
+
except ImportError:
|
|
186
|
+
raise RuntimeError("openai SDK not installed — run: pip install openai")
|
|
187
|
+
|
|
188
|
+
model = "gpt-3.5-turbo"
|
|
189
|
+
client = _openai.OpenAI(api_key=os.environ["OPENAI_API_KEY"])
|
|
190
|
+
t0 = time.perf_counter()
|
|
191
|
+
client.chat.completions.create(
|
|
192
|
+
model=model,
|
|
193
|
+
max_tokens=64,
|
|
194
|
+
messages=[{"role": "user", "content": self.prompt}],
|
|
195
|
+
)
|
|
196
|
+
ms = round((time.perf_counter() - t0) * 1000, 1)
|
|
197
|
+
return {"backend": "openai", "status": "ok", "ms": ms, "model": model, "error": None}
|
|
198
|
+
|
|
199
|
+
def _bench_grok(self) -> dict:
|
|
200
|
+
"""xAI Grok — OpenAI-compatible API, requires ``openai`` SDK + XAI_API_KEY."""
|
|
201
|
+
try:
|
|
202
|
+
import openai as _openai
|
|
203
|
+
except ImportError:
|
|
204
|
+
raise RuntimeError("openai SDK not installed — run: pip install openai")
|
|
205
|
+
|
|
206
|
+
model = "grok-3-mini"
|
|
207
|
+
client = _openai.OpenAI(
|
|
208
|
+
api_key=os.environ["XAI_API_KEY"],
|
|
209
|
+
base_url="https://api.x.ai/v1",
|
|
210
|
+
)
|
|
211
|
+
t0 = time.perf_counter()
|
|
212
|
+
client.chat.completions.create(
|
|
213
|
+
model=model,
|
|
214
|
+
max_tokens=64,
|
|
215
|
+
messages=[{"role": "user", "content": self.prompt}],
|
|
216
|
+
)
|
|
217
|
+
ms = round((time.perf_counter() - t0) * 1000, 1)
|
|
218
|
+
return {"backend": "grok", "status": "ok", "ms": ms, "model": model, "error": None}
|
|
219
|
+
|
|
220
|
+
def _bench_kimi(self) -> dict:
|
|
221
|
+
"""Moonshot Kimi — OpenAI-compatible API, requires ``openai`` SDK + MOONSHOT_API_KEY."""
|
|
222
|
+
try:
|
|
223
|
+
import openai as _openai
|
|
224
|
+
except ImportError:
|
|
225
|
+
raise RuntimeError("openai SDK not installed — run: pip install openai")
|
|
226
|
+
|
|
227
|
+
model = "moonshot-v1-8k"
|
|
228
|
+
client = _openai.OpenAI(
|
|
229
|
+
api_key=os.environ["MOONSHOT_API_KEY"],
|
|
230
|
+
base_url="https://api.moonshot.cn/v1",
|
|
231
|
+
)
|
|
232
|
+
t0 = time.perf_counter()
|
|
233
|
+
client.chat.completions.create(
|
|
234
|
+
model=model,
|
|
235
|
+
max_tokens=64,
|
|
236
|
+
messages=[{"role": "user", "content": self.prompt}],
|
|
237
|
+
)
|
|
238
|
+
ms = round((time.perf_counter() - t0) * 1000, 1)
|
|
239
|
+
return {"backend": "kimi", "status": "ok", "ms": ms, "model": model, "error": None}
|
|
240
|
+
|
|
241
|
+
def _bench_nvidia(self) -> dict:
|
|
242
|
+
"""NVIDIA NIM — OpenAI-compatible API, requires ``openai`` SDK + NVIDIA_API_KEY."""
|
|
243
|
+
try:
|
|
244
|
+
import openai as _openai
|
|
245
|
+
except ImportError:
|
|
246
|
+
raise RuntimeError("openai SDK not installed — run: pip install openai")
|
|
247
|
+
|
|
248
|
+
model = "meta/llama-3.1-8b-instruct"
|
|
249
|
+
client = _openai.OpenAI(
|
|
250
|
+
api_key=os.environ["NVIDIA_API_KEY"],
|
|
251
|
+
base_url="https://integrate.api.nvidia.com/v1",
|
|
252
|
+
)
|
|
253
|
+
t0 = time.perf_counter()
|
|
254
|
+
client.chat.completions.create(
|
|
255
|
+
model=model,
|
|
256
|
+
max_tokens=64,
|
|
257
|
+
messages=[{"role": "user", "content": self.prompt}],
|
|
258
|
+
)
|
|
259
|
+
ms = round((time.perf_counter() - t0) * 1000, 1)
|
|
260
|
+
return {"backend": "nvidia", "status": "ok", "ms": ms, "model": model, "error": None}
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
# ---------------------------------------------------------------------------
|
|
264
|
+
# Rich table renderer
|
|
265
|
+
# ---------------------------------------------------------------------------
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def _render_table(results: list[dict]) -> None:
|
|
269
|
+
"""Render benchmark results as a Rich table with a fastest-backend summary."""
|
|
270
|
+
table = Table(title="LLM Backend Benchmark", show_header=True, header_style="bold magenta")
|
|
271
|
+
table.add_column("Backend", style="cyan", no_wrap=True)
|
|
272
|
+
table.add_column("Status", no_wrap=True)
|
|
273
|
+
table.add_column("Model", style="dim")
|
|
274
|
+
table.add_column("Latency (ms)", justify="right")
|
|
275
|
+
table.add_column("Note")
|
|
276
|
+
|
|
277
|
+
for r in results:
|
|
278
|
+
status = r["status"]
|
|
279
|
+
if status == "ok":
|
|
280
|
+
status_str = "[green]ok[/]"
|
|
281
|
+
ms_str = f"[green]{r['ms']}[/]"
|
|
282
|
+
elif status == "unavailable":
|
|
283
|
+
status_str = "[dim]unavailable[/]"
|
|
284
|
+
ms_str = "[dim]—[/]"
|
|
285
|
+
else:
|
|
286
|
+
status_str = "[red]error[/]"
|
|
287
|
+
ms_str = "[red]—[/]"
|
|
288
|
+
|
|
289
|
+
table.add_row(
|
|
290
|
+
r["backend"],
|
|
291
|
+
status_str,
|
|
292
|
+
r.get("model") or "—",
|
|
293
|
+
ms_str,
|
|
294
|
+
r.get("error") or "",
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
console.print(table)
|
|
298
|
+
|
|
299
|
+
ok_results = [r for r in results if r["status"] == "ok"]
|
|
300
|
+
if ok_results:
|
|
301
|
+
fastest = min(ok_results, key=lambda r: r["ms"])
|
|
302
|
+
console.print(
|
|
303
|
+
f"\n[bold]Fastest:[/] [green]{fastest['backend']}[/] — {fastest['ms']} ms"
|
|
304
|
+
f" ([dim]{fastest['model']}[/])"
|
|
305
|
+
)
|
|
306
|
+
elif all(r["status"] == "unavailable" for r in results):
|
|
307
|
+
console.print("[yellow]No backends available. Set API keys or start Ollama.[/]")
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
# ---------------------------------------------------------------------------
|
|
311
|
+
# CLI registration
|
|
312
|
+
# ---------------------------------------------------------------------------
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def register_benchmark_commands(main: click.Group) -> None:
|
|
316
|
+
"""Register the ``skcapstone benchmark`` command."""
|
|
317
|
+
|
|
318
|
+
@main.command("benchmark")
|
|
319
|
+
@click.option(
|
|
320
|
+
"--prompt", default="Hello", show_default=True,
|
|
321
|
+
help="Prompt to send to each backend.",
|
|
322
|
+
)
|
|
323
|
+
@click.option(
|
|
324
|
+
"--timeout", default=30.0, show_default=True, type=float,
|
|
325
|
+
help="Per-backend timeout in seconds.",
|
|
326
|
+
)
|
|
327
|
+
@click.option(
|
|
328
|
+
"--include-unavailable", is_flag=True,
|
|
329
|
+
help="Include unavailable backends in output (they will show as 'unavailable').",
|
|
330
|
+
)
|
|
331
|
+
@click.option("--json-out", is_flag=True, help="Output raw JSON instead of a table.")
|
|
332
|
+
def benchmark_cmd(
|
|
333
|
+
prompt: str,
|
|
334
|
+
timeout: float,
|
|
335
|
+
include_unavailable: bool,
|
|
336
|
+
json_out: bool,
|
|
337
|
+
) -> None:
|
|
338
|
+
"""Benchmark LLM response time across all available backends.
|
|
339
|
+
|
|
340
|
+
Sends PROMPT to each detected backend, measures latency, and
|
|
341
|
+
reports results in a table. Cloud backends require API key env vars
|
|
342
|
+
(ANTHROPIC_API_KEY, OPENAI_API_KEY, XAI_API_KEY, MOONSHOT_API_KEY,
|
|
343
|
+
NVIDIA_API_KEY). Ollama is probed via HTTP on OLLAMA_HOST.
|
|
344
|
+
"""
|
|
345
|
+
runner = BenchmarkRunner(prompt=prompt, timeout=timeout)
|
|
346
|
+
|
|
347
|
+
if not json_out:
|
|
348
|
+
console.print(
|
|
349
|
+
f"[bold]Benchmarking LLM backends[/] — "
|
|
350
|
+
f"prompt: [cyan]{prompt!r}[/] timeout: {timeout}s"
|
|
351
|
+
)
|
|
352
|
+
console.print()
|
|
353
|
+
|
|
354
|
+
results = runner.run_all(skip_unavailable=not include_unavailable)
|
|
355
|
+
|
|
356
|
+
if json_out:
|
|
357
|
+
click.echo(json.dumps(results, indent=2))
|
|
358
|
+
return
|
|
359
|
+
|
|
360
|
+
_render_table(results)
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"""Capabilities commands: list, add, remove."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import click
|
|
10
|
+
import yaml
|
|
11
|
+
|
|
12
|
+
from ._common import AGENT_HOME, console
|
|
13
|
+
|
|
14
|
+
from rich.panel import Panel
|
|
15
|
+
from rich.table import Table
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _load_config_data(config_path: Path) -> dict:
|
|
19
|
+
"""Load raw config dict from YAML, or return empty dict."""
|
|
20
|
+
if config_path.exists():
|
|
21
|
+
try:
|
|
22
|
+
return yaml.safe_load(config_path.read_text(encoding="utf-8")) or {}
|
|
23
|
+
except yaml.YAMLError:
|
|
24
|
+
return {}
|
|
25
|
+
return {}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _save_config_data(config_path: Path, data: dict) -> None:
|
|
29
|
+
"""Write config dict back to YAML, creating parent dirs as needed."""
|
|
30
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
31
|
+
config_path.write_text(yaml.dump(data, default_flow_style=False), encoding="utf-8")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _current_capabilities(config_path: Path) -> list[str]:
|
|
35
|
+
"""Return the current capabilities list from config (or defaults)."""
|
|
36
|
+
data = _load_config_data(config_path)
|
|
37
|
+
caps = data.get("capabilities")
|
|
38
|
+
if isinstance(caps, list):
|
|
39
|
+
return [str(c) for c in caps if c]
|
|
40
|
+
from ..models import AgentConfig
|
|
41
|
+
return list(AgentConfig().capabilities)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def register_capabilities_commands(main: click.Group) -> None:
|
|
45
|
+
"""Register the capabilities command group."""
|
|
46
|
+
|
|
47
|
+
@main.group(invoke_without_command=True)
|
|
48
|
+
@click.option("--home", "sk_home", default=AGENT_HOME, type=click.Path())
|
|
49
|
+
@click.option("--json", "json_out", is_flag=True, help="Output as JSON.")
|
|
50
|
+
@click.pass_context
|
|
51
|
+
def capabilities(ctx, sk_home, json_out):
|
|
52
|
+
"""Agent capability advertisement — what this agent can do.
|
|
53
|
+
|
|
54
|
+
Capabilities are included in every heartbeat beacon so peers
|
|
55
|
+
know what services this agent offers.
|
|
56
|
+
|
|
57
|
+
Defaults: consciousness, code, chat, memory
|
|
58
|
+
"""
|
|
59
|
+
if ctx.invoked_subcommand is None:
|
|
60
|
+
sk_path = Path(sk_home).expanduser()
|
|
61
|
+
config_path = sk_path / "config" / "config.yaml"
|
|
62
|
+
caps = _current_capabilities(config_path)
|
|
63
|
+
|
|
64
|
+
if json_out:
|
|
65
|
+
click.echo(json.dumps(caps, indent=2))
|
|
66
|
+
return
|
|
67
|
+
|
|
68
|
+
console.print()
|
|
69
|
+
if not caps:
|
|
70
|
+
console.print(" [dim]No capabilities configured.[/]")
|
|
71
|
+
console.print()
|
|
72
|
+
return
|
|
73
|
+
|
|
74
|
+
table = Table(show_header=True, header_style="bold", box=None, padding=(0, 2))
|
|
75
|
+
table.add_column("#", style="dim", width=4)
|
|
76
|
+
table.add_column("Capability", style="cyan")
|
|
77
|
+
|
|
78
|
+
for i, name in enumerate(caps, 1):
|
|
79
|
+
table.add_row(str(i), name)
|
|
80
|
+
|
|
81
|
+
source = "[dim](from config)[/]" if (config_path.exists() and
|
|
82
|
+
_load_config_data(config_path).get("capabilities")) else "[dim](defaults)[/]"
|
|
83
|
+
console.print(Panel(
|
|
84
|
+
f"[bold]{len(caps)}[/] capability(s) advertised {source}",
|
|
85
|
+
title="Agent Capabilities",
|
|
86
|
+
border_style="bright_blue",
|
|
87
|
+
))
|
|
88
|
+
console.print(table)
|
|
89
|
+
console.print()
|
|
90
|
+
|
|
91
|
+
@capabilities.command("list")
|
|
92
|
+
@click.option("--home", "sk_home", default=AGENT_HOME, type=click.Path())
|
|
93
|
+
@click.option("--json", "json_out", is_flag=True, help="Output as JSON.")
|
|
94
|
+
def capabilities_list(sk_home, json_out):
|
|
95
|
+
"""List capabilities advertised in heartbeat beacons."""
|
|
96
|
+
sk_path = Path(sk_home).expanduser()
|
|
97
|
+
config_path = sk_path / "config" / "config.yaml"
|
|
98
|
+
caps = _current_capabilities(config_path)
|
|
99
|
+
|
|
100
|
+
if json_out:
|
|
101
|
+
click.echo(json.dumps(caps, indent=2))
|
|
102
|
+
return
|
|
103
|
+
|
|
104
|
+
console.print()
|
|
105
|
+
if not caps:
|
|
106
|
+
console.print(" [dim]No capabilities configured.[/]")
|
|
107
|
+
console.print()
|
|
108
|
+
return
|
|
109
|
+
|
|
110
|
+
for name in caps:
|
|
111
|
+
console.print(f" [cyan]{name}[/]")
|
|
112
|
+
console.print()
|
|
113
|
+
|
|
114
|
+
@capabilities.command("add")
|
|
115
|
+
@click.argument("name")
|
|
116
|
+
@click.option("--home", "sk_home", default=AGENT_HOME, type=click.Path())
|
|
117
|
+
def capabilities_add(name, sk_home):
|
|
118
|
+
"""Add a capability to the advertised list.
|
|
119
|
+
|
|
120
|
+
Example: skcapstone capabilities add vector-search
|
|
121
|
+
"""
|
|
122
|
+
name = name.strip()
|
|
123
|
+
if not name:
|
|
124
|
+
console.print("\n [red]Capability name cannot be empty.[/]\n")
|
|
125
|
+
sys.exit(1)
|
|
126
|
+
|
|
127
|
+
sk_path = Path(sk_home).expanduser()
|
|
128
|
+
config_path = sk_path / "config" / "config.yaml"
|
|
129
|
+
data = _load_config_data(config_path)
|
|
130
|
+
|
|
131
|
+
# Seed from defaults if no key yet
|
|
132
|
+
if "capabilities" not in data:
|
|
133
|
+
from ..models import AgentConfig
|
|
134
|
+
data["capabilities"] = list(AgentConfig().capabilities)
|
|
135
|
+
|
|
136
|
+
caps: list[str] = data["capabilities"]
|
|
137
|
+
if name in caps:
|
|
138
|
+
console.print(f"\n [yellow]'{name}' is already in the capabilities list.[/]\n")
|
|
139
|
+
return
|
|
140
|
+
|
|
141
|
+
caps.append(name)
|
|
142
|
+
data["capabilities"] = caps
|
|
143
|
+
_save_config_data(config_path, data)
|
|
144
|
+
console.print(f"\n [green]Added capability:[/] [cyan]{name}[/]\n")
|
|
145
|
+
|
|
146
|
+
@capabilities.command("remove")
|
|
147
|
+
@click.argument("name")
|
|
148
|
+
@click.option("--home", "sk_home", default=AGENT_HOME, type=click.Path())
|
|
149
|
+
def capabilities_remove(name, sk_home):
|
|
150
|
+
"""Remove a capability from the advertised list.
|
|
151
|
+
|
|
152
|
+
Example: skcapstone capabilities remove chat
|
|
153
|
+
"""
|
|
154
|
+
name = name.strip()
|
|
155
|
+
sk_path = Path(sk_home).expanduser()
|
|
156
|
+
config_path = sk_path / "config" / "config.yaml"
|
|
157
|
+
data = _load_config_data(config_path)
|
|
158
|
+
|
|
159
|
+
if "capabilities" not in data:
|
|
160
|
+
from ..models import AgentConfig
|
|
161
|
+
data["capabilities"] = list(AgentConfig().capabilities)
|
|
162
|
+
|
|
163
|
+
caps: list[str] = data["capabilities"]
|
|
164
|
+
if name not in caps:
|
|
165
|
+
console.print(f"\n [yellow]'{name}' not found in capabilities list.[/]\n")
|
|
166
|
+
return
|
|
167
|
+
|
|
168
|
+
caps.remove(name)
|
|
169
|
+
data["capabilities"] = caps
|
|
170
|
+
_save_config_data(config_path, data)
|
|
171
|
+
console.print(f"\n [green]Removed capability:[/] [cyan]{name}[/]\n")
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""Agent card commands: generate, show, verify, export."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
|
|
10
|
+
from ._common import AGENT_HOME, console
|
|
11
|
+
from ..runtime import get_runtime
|
|
12
|
+
|
|
13
|
+
from rich.panel import Panel
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def register_card_commands(main: click.Group) -> None:
|
|
17
|
+
"""Register the card command group."""
|
|
18
|
+
|
|
19
|
+
@main.group()
|
|
20
|
+
def card():
|
|
21
|
+
"""Agent card — shareable sovereign identity for P2P discovery.
|
|
22
|
+
|
|
23
|
+
Generate, view, export, and verify sovereign agent identity cards.
|
|
24
|
+
Cards contain your CapAuth identity, contact transports, and capabilities.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
@card.command("generate")
|
|
28
|
+
@click.option("--home", default=AGENT_HOME, type=click.Path(), help="Agent home directory.")
|
|
29
|
+
@click.option("--capauth-home", default="~/.capauth", type=click.Path(), help="CapAuth home.")
|
|
30
|
+
@click.option("--motto", default=None, help="Short tagline for the card.")
|
|
31
|
+
@click.option("--output", "-o", default=None, type=click.Path(), help="Output file path.")
|
|
32
|
+
@click.option("--sign", "do_sign", is_flag=True, default=False, help="Sign the card with your PGP key.")
|
|
33
|
+
@click.option("--passphrase", "-p", default=None, hide_input=True, help="PGP passphrase for signing.")
|
|
34
|
+
def card_generate(home, capauth_home, motto, output, do_sign, passphrase):
|
|
35
|
+
"""Generate an agent card from your CapAuth profile."""
|
|
36
|
+
from ..agent_card import AgentCapability, AgentCard
|
|
37
|
+
|
|
38
|
+
home_path = Path(home).expanduser()
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
agent_card = AgentCard.from_capauth_profile(
|
|
42
|
+
profile_dir=capauth_home,
|
|
43
|
+
capabilities=[
|
|
44
|
+
AgentCapability(name="chat", description="SKChat encrypted messaging"),
|
|
45
|
+
AgentCapability(name="memory", description="SKMemory persistent context"),
|
|
46
|
+
],
|
|
47
|
+
)
|
|
48
|
+
except FileNotFoundError:
|
|
49
|
+
runtime = get_runtime(home_path)
|
|
50
|
+
m = runtime.manifest
|
|
51
|
+
agent_card = AgentCard.generate(
|
|
52
|
+
name=m.name, fingerprint=m.identity.fingerprint or "unknown",
|
|
53
|
+
public_key="", entity_type="ai",
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
if motto:
|
|
57
|
+
agent_card.motto = motto
|
|
58
|
+
|
|
59
|
+
if do_sign:
|
|
60
|
+
if not passphrase:
|
|
61
|
+
passphrase = click.prompt("PGP passphrase", hide_input=True)
|
|
62
|
+
capauth_path = Path(capauth_home).expanduser()
|
|
63
|
+
priv_path = capauth_path / "identity" / "private.asc"
|
|
64
|
+
if priv_path.exists():
|
|
65
|
+
agent_card.sign(priv_path.read_text(encoding="utf-8"), passphrase)
|
|
66
|
+
console.print("[green]Card signed.[/]")
|
|
67
|
+
else:
|
|
68
|
+
console.print("[yellow]Private key not found, card unsigned.[/]")
|
|
69
|
+
|
|
70
|
+
out_path = output or str(home_path / "agent-card.json")
|
|
71
|
+
agent_card.save(out_path)
|
|
72
|
+
|
|
73
|
+
console.print(Panel(agent_card.summary(), title="Agent Card Generated", border_style="cyan"))
|
|
74
|
+
console.print(f" [dim]Saved to: {out_path}[/]\n")
|
|
75
|
+
|
|
76
|
+
@card.command("show")
|
|
77
|
+
@click.argument("filepath", default="~/.skcapstone/agent-card.json")
|
|
78
|
+
def card_show(filepath):
|
|
79
|
+
"""Display an agent card."""
|
|
80
|
+
from ..agent_card import AgentCard
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
agent_card = AgentCard.load(filepath)
|
|
84
|
+
except FileNotFoundError:
|
|
85
|
+
console.print(f"[red]Card not found: {filepath}[/]")
|
|
86
|
+
raise SystemExit(1)
|
|
87
|
+
|
|
88
|
+
verified = AgentCard.verify_signature(agent_card)
|
|
89
|
+
sig_str = "[green]VALID[/]" if verified else (
|
|
90
|
+
"[yellow]unsigned[/]" if not agent_card.signature else "[red]INVALID[/]"
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
console.print(Panel(
|
|
94
|
+
f"[bold]{agent_card.name}[/] ({agent_card.entity_type})\n"
|
|
95
|
+
f"Fingerprint: [cyan]{agent_card.fingerprint[:16]}...[/]\n"
|
|
96
|
+
f"Trust: depth={agent_card.trust_depth} entangled={agent_card.entangled}\n"
|
|
97
|
+
f"Signature: {sig_str}\n"
|
|
98
|
+
f"Transports: {len(agent_card.transports)}\n"
|
|
99
|
+
f"Capabilities: {', '.join(c.name for c in agent_card.capabilities) or 'none'}\n"
|
|
100
|
+
+ (f'Motto: "{agent_card.motto}"' if agent_card.motto else ""),
|
|
101
|
+
title=f"Agent Card: {agent_card.name}",
|
|
102
|
+
border_style="cyan",
|
|
103
|
+
))
|
|
104
|
+
|
|
105
|
+
@card.command("verify")
|
|
106
|
+
@click.argument("filepath")
|
|
107
|
+
def card_verify(filepath):
|
|
108
|
+
"""Verify the PGP signature on an agent card."""
|
|
109
|
+
from ..agent_card import AgentCard
|
|
110
|
+
|
|
111
|
+
try:
|
|
112
|
+
agent_card = AgentCard.load(filepath)
|
|
113
|
+
except FileNotFoundError:
|
|
114
|
+
console.print(f"[red]Card not found: {filepath}[/]")
|
|
115
|
+
raise SystemExit(1)
|
|
116
|
+
|
|
117
|
+
if not agent_card.signature:
|
|
118
|
+
console.print("[yellow]Card is not signed.[/]")
|
|
119
|
+
raise SystemExit(1)
|
|
120
|
+
|
|
121
|
+
if AgentCard.verify_signature(agent_card):
|
|
122
|
+
console.print(Panel(
|
|
123
|
+
f"[bold green]VERIFIED[/]\nAgent: {agent_card.name}\n"
|
|
124
|
+
f"Fingerprint: {agent_card.fingerprint[:16]}...",
|
|
125
|
+
title="Signature Valid", border_style="green",
|
|
126
|
+
))
|
|
127
|
+
else:
|
|
128
|
+
console.print(Panel(
|
|
129
|
+
f"[bold red]SIGNATURE INVALID[/]\nAgent: {agent_card.name}\n"
|
|
130
|
+
"The card may have been tampered with.",
|
|
131
|
+
title="Verification Failed", border_style="red",
|
|
132
|
+
))
|
|
133
|
+
raise SystemExit(1)
|
|
134
|
+
|
|
135
|
+
@card.command("export")
|
|
136
|
+
@click.argument("filepath", default="~/.skcapstone/agent-card.json")
|
|
137
|
+
@click.option("--compact", is_flag=True, help="Export compact format (no public key).")
|
|
138
|
+
def card_export(filepath, compact):
|
|
139
|
+
"""Export an agent card to stdout (for sharing)."""
|
|
140
|
+
from ..agent_card import AgentCard
|
|
141
|
+
|
|
142
|
+
try:
|
|
143
|
+
agent_card = AgentCard.load(filepath)
|
|
144
|
+
except FileNotFoundError:
|
|
145
|
+
console.print(f"[red]Card not found: {filepath}[/]")
|
|
146
|
+
raise SystemExit(1)
|
|
147
|
+
|
|
148
|
+
if compact:
|
|
149
|
+
click.echo(json.dumps(agent_card.to_compact(), indent=2))
|
|
150
|
+
else:
|
|
151
|
+
click.echo(agent_card.model_dump_json(indent=2))
|