@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,165 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Private helpers for TrusteeOps — audit, snapshot, and log utilities.
|
|
3
|
+
|
|
4
|
+
Not part of the public API; imported only by trustee_ops.py.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import logging
|
|
11
|
+
from datetime import datetime, timezone
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any, Dict, List, Optional
|
|
14
|
+
|
|
15
|
+
from .team_engine import AgentStatus, TeamDeployment
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# ---------------------------------------------------------------------------
|
|
21
|
+
# Audit trail
|
|
22
|
+
# ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
_AUDIT_DIR = Path("~/.skcapstone/coordination")
|
|
25
|
+
_AUDIT_FILE = _AUDIT_DIR / "audit.log"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def write_audit(
|
|
29
|
+
action: str,
|
|
30
|
+
deployment_id: str,
|
|
31
|
+
details: Dict[str, Any],
|
|
32
|
+
home: Optional[Path] = None,
|
|
33
|
+
) -> None:
|
|
34
|
+
"""Append a structured audit entry to the trustee audit log.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
action: Short action name (e.g. "restart_agent").
|
|
38
|
+
deployment_id: The affected deployment.
|
|
39
|
+
details: Extra key/value context for the entry.
|
|
40
|
+
home: Agent home directory override.
|
|
41
|
+
"""
|
|
42
|
+
audit_dir = (home / "coordination") if home else _AUDIT_DIR.expanduser()
|
|
43
|
+
audit_dir.mkdir(parents=True, exist_ok=True)
|
|
44
|
+
audit_path = audit_dir / "audit.log"
|
|
45
|
+
|
|
46
|
+
entry = {
|
|
47
|
+
"ts": datetime.now(timezone.utc).isoformat(),
|
|
48
|
+
"action": action,
|
|
49
|
+
"deployment_id": deployment_id,
|
|
50
|
+
**details,
|
|
51
|
+
}
|
|
52
|
+
with audit_path.open("a", encoding="utf-8") as fh:
|
|
53
|
+
fh.write(json.dumps(entry) + "\n")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# ---------------------------------------------------------------------------
|
|
57
|
+
# Deployment status refresh
|
|
58
|
+
# ---------------------------------------------------------------------------
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def refresh_deployment_status(deployment: TeamDeployment) -> None:
|
|
62
|
+
"""Update the overall deployment.status based on agent states.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
deployment: The deployment to update in-place.
|
|
66
|
+
"""
|
|
67
|
+
statuses = {a.status for a in deployment.agents.values()}
|
|
68
|
+
if not statuses:
|
|
69
|
+
deployment.status = "empty"
|
|
70
|
+
elif statuses == {AgentStatus.RUNNING}:
|
|
71
|
+
deployment.status = "running"
|
|
72
|
+
elif AgentStatus.FAILED in statuses:
|
|
73
|
+
deployment.status = "degraded"
|
|
74
|
+
else:
|
|
75
|
+
deployment.status = "partial"
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# ---------------------------------------------------------------------------
|
|
79
|
+
# Context snapshot
|
|
80
|
+
# ---------------------------------------------------------------------------
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def snapshot_agent_context(home: Path, agent_name: str) -> Path:
|
|
84
|
+
"""Copy agent memory/scratch to a timestamped snapshot directory.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
home: Agent home directory.
|
|
88
|
+
agent_name: Name of the agent to snapshot.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Path to the snapshot directory (parent created even if source absent).
|
|
92
|
+
"""
|
|
93
|
+
import shutil
|
|
94
|
+
|
|
95
|
+
ts = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
|
|
96
|
+
src = home / "agents" / "local" / agent_name
|
|
97
|
+
dst = home / "snapshots" / f"{agent_name}-{ts}"
|
|
98
|
+
|
|
99
|
+
if src.exists():
|
|
100
|
+
dst.parent.mkdir(parents=True, exist_ok=True)
|
|
101
|
+
shutil.copytree(src, dst)
|
|
102
|
+
logger.info("Snapshotted %s → %s", agent_name, dst)
|
|
103
|
+
else:
|
|
104
|
+
dst.parent.mkdir(parents=True, exist_ok=True)
|
|
105
|
+
logger.warning("No source dir to snapshot for %s", agent_name)
|
|
106
|
+
|
|
107
|
+
return dst
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
# ---------------------------------------------------------------------------
|
|
111
|
+
# Audit-based log fallback
|
|
112
|
+
# ---------------------------------------------------------------------------
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def audit_lines_for_agent(
|
|
116
|
+
home: Path,
|
|
117
|
+
deployment_id: str,
|
|
118
|
+
agent_name: str,
|
|
119
|
+
tail: int = 50,
|
|
120
|
+
) -> List[str]:
|
|
121
|
+
"""Extract audit log lines referencing a specific agent/deployment.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
home: Agent home directory.
|
|
125
|
+
deployment_id: Deployment ID to filter on.
|
|
126
|
+
agent_name: Agent name to filter on.
|
|
127
|
+
tail: Maximum lines to return.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
List of formatted audit log strings.
|
|
131
|
+
"""
|
|
132
|
+
audit_path = home / "coordination" / "audit.log"
|
|
133
|
+
if not audit_path.exists():
|
|
134
|
+
return []
|
|
135
|
+
|
|
136
|
+
matching: List[str] = []
|
|
137
|
+
for raw in audit_path.read_text(encoding="utf-8").splitlines():
|
|
138
|
+
try:
|
|
139
|
+
entry = json.loads(raw)
|
|
140
|
+
if entry.get("deployment_id") == deployment_id and (
|
|
141
|
+
entry.get("agent_name") in (agent_name, "ALL", None)
|
|
142
|
+
):
|
|
143
|
+
ts = entry.get("ts", "")
|
|
144
|
+
action = entry.get("action", "")
|
|
145
|
+
matching.append(f"[{ts}] {action}: {json.dumps(entry)}")
|
|
146
|
+
except (json.JSONDecodeError, KeyError):
|
|
147
|
+
continue
|
|
148
|
+
|
|
149
|
+
return matching[-tail:]
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
# ---------------------------------------------------------------------------
|
|
153
|
+
# Stub spec for rotation re-provisioning
|
|
154
|
+
# ---------------------------------------------------------------------------
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def stub_spec() -> Any:
|
|
158
|
+
"""Return a minimal stub AgentSpec for rotation re-provisioning.
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
A minimal AgentSpec with default values.
|
|
162
|
+
"""
|
|
163
|
+
from .blueprints.schema import AgentRole, AgentSpec, ModelTier
|
|
164
|
+
|
|
165
|
+
return AgentSpec(role=AgentRole.WORKER, model=ModelTier.FAST, skills=[])
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""Lightweight in-process activity bus for the SKCapstone daemon.
|
|
2
|
+
|
|
3
|
+
Stores the last 100 events in a thread-safe deque and fans out live
|
|
4
|
+
events to registered SSE client queues. No external dependencies —
|
|
5
|
+
stdlib only.
|
|
6
|
+
|
|
7
|
+
Usage::
|
|
8
|
+
|
|
9
|
+
from . import activity
|
|
10
|
+
|
|
11
|
+
# publish an event (any thread)
|
|
12
|
+
activity.push("memory.stored", {"memory_id": "abc", "layer": "short-term"})
|
|
13
|
+
|
|
14
|
+
# SSE handler: register a queue, drain history, then block on live events
|
|
15
|
+
q = queue.Queue(maxsize=200)
|
|
16
|
+
activity.register_client(q)
|
|
17
|
+
try:
|
|
18
|
+
for chunk in activity.get_history_encoded():
|
|
19
|
+
wfile.write(chunk)
|
|
20
|
+
while True:
|
|
21
|
+
chunk = q.get(timeout=15) # raises queue.Empty on timeout
|
|
22
|
+
wfile.write(chunk)
|
|
23
|
+
finally:
|
|
24
|
+
activity.unregister_client(q)
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import json
|
|
30
|
+
import queue
|
|
31
|
+
import threading
|
|
32
|
+
from collections import deque
|
|
33
|
+
from datetime import datetime, timezone
|
|
34
|
+
from typing import Any
|
|
35
|
+
|
|
36
|
+
_MAXLEN = 100
|
|
37
|
+
|
|
38
|
+
_history: deque[dict] = deque(maxlen=_MAXLEN)
|
|
39
|
+
_history_lock = threading.Lock()
|
|
40
|
+
|
|
41
|
+
_clients: set[queue.Queue] = set()
|
|
42
|
+
_clients_lock = threading.Lock()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def push(event_type: str, data: dict[str, Any]) -> None:
|
|
46
|
+
"""Append an event to history and fan out to all live SSE clients.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
event_type: Dot-namespaced event type, e.g. ``"memory.stored"``.
|
|
50
|
+
data: Arbitrary JSON-serialisable payload dict.
|
|
51
|
+
"""
|
|
52
|
+
event: dict = {
|
|
53
|
+
"type": event_type,
|
|
54
|
+
"ts": datetime.now(timezone.utc).isoformat(),
|
|
55
|
+
"data": data,
|
|
56
|
+
}
|
|
57
|
+
with _history_lock:
|
|
58
|
+
_history.append(event)
|
|
59
|
+
_fan_out(event)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def get_history() -> list[dict]:
|
|
63
|
+
"""Return a snapshot of the last ≤100 events (oldest first)."""
|
|
64
|
+
with _history_lock:
|
|
65
|
+
return list(_history)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def get_history_encoded() -> list[bytes]:
|
|
69
|
+
"""Return history as a list of SSE-encoded byte chunks."""
|
|
70
|
+
return [_encode(e) for e in get_history()]
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def register_client(q: queue.Queue) -> None:
|
|
74
|
+
"""Register a queue to receive live SSE byte chunks."""
|
|
75
|
+
with _clients_lock:
|
|
76
|
+
_clients.add(q)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def unregister_client(q: queue.Queue) -> None:
|
|
80
|
+
"""Remove a queue from the live fan-out set."""
|
|
81
|
+
with _clients_lock:
|
|
82
|
+
_clients.discard(q)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# ── internal helpers ──────────────────────────────────────────────────────────
|
|
86
|
+
|
|
87
|
+
def _fan_out(event: dict) -> None:
|
|
88
|
+
global _clients
|
|
89
|
+
chunk = _encode(event)
|
|
90
|
+
dead: set[queue.Queue] = set()
|
|
91
|
+
with _clients_lock:
|
|
92
|
+
clients = set(_clients)
|
|
93
|
+
for q in clients:
|
|
94
|
+
try:
|
|
95
|
+
q.put_nowait(chunk)
|
|
96
|
+
except Exception:
|
|
97
|
+
dead.add(q)
|
|
98
|
+
if dead:
|
|
99
|
+
with _clients_lock:
|
|
100
|
+
_clients -= dead
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _encode(event: dict) -> bytes:
|
|
104
|
+
data = json.dumps(event, default=str)
|
|
105
|
+
return f"data: {data}\n\n".encode("utf-8")
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
"""Agent Card -- shareable sovereign identity for P2P discovery.
|
|
2
|
+
|
|
3
|
+
An agent card is like a vCard for the sovereign mesh. It contains
|
|
4
|
+
everything another agent needs to discover, verify, and communicate
|
|
5
|
+
with you: identity, public key, contact transports, capabilities,
|
|
6
|
+
and trust level.
|
|
7
|
+
|
|
8
|
+
Cards are JSON files signed with the agent's PGP key. They can be
|
|
9
|
+
shared over SKComm, published to Nostr, posted as QR codes, or
|
|
10
|
+
exchanged via any out-of-band channel.
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
card = AgentCard.generate(profile, transports, capabilities)
|
|
14
|
+
card.save("~/.skcapstone/card.json")
|
|
15
|
+
card = AgentCard.load("~/.skcapstone/card.json")
|
|
16
|
+
verified = AgentCard.verify(card, public_key_armor)
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import hashlib
|
|
22
|
+
import json
|
|
23
|
+
import logging
|
|
24
|
+
from datetime import datetime, timezone
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
from typing import Any, Optional
|
|
27
|
+
from uuid import uuid4
|
|
28
|
+
|
|
29
|
+
from pydantic import BaseModel, Field
|
|
30
|
+
|
|
31
|
+
logger = logging.getLogger("skcapstone.agent_card")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class TransportEndpoint(BaseModel):
|
|
35
|
+
"""A contact transport endpoint for reaching this agent.
|
|
36
|
+
|
|
37
|
+
Attributes:
|
|
38
|
+
transport: Transport name (file, syncthing, nostr, etc.).
|
|
39
|
+
address: Transport-specific address (path, pubkey, relay URL).
|
|
40
|
+
priority: Lower = preferred.
|
|
41
|
+
metadata: Extra transport-specific config.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
transport: str
|
|
45
|
+
address: str
|
|
46
|
+
priority: int = 1
|
|
47
|
+
metadata: dict[str, Any] = Field(default_factory=dict)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class AgentCapability(BaseModel):
|
|
51
|
+
"""A capability or service this agent offers.
|
|
52
|
+
|
|
53
|
+
Attributes:
|
|
54
|
+
name: Capability identifier (e.g., "chat", "memory", "advocacy").
|
|
55
|
+
version: Capability version.
|
|
56
|
+
description: Human-readable description.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
name: str
|
|
60
|
+
version: str = "1.0"
|
|
61
|
+
description: str = ""
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class AgentCard(BaseModel):
|
|
65
|
+
"""Sovereign agent identity card for P2P discovery.
|
|
66
|
+
|
|
67
|
+
Contains everything needed to discover, verify, and contact
|
|
68
|
+
an agent on the mesh. Designed for serialization to JSON and
|
|
69
|
+
optional PGP signing.
|
|
70
|
+
|
|
71
|
+
Attributes:
|
|
72
|
+
card_id: Unique card identifier.
|
|
73
|
+
card_version: Card format version.
|
|
74
|
+
created_at: When the card was generated.
|
|
75
|
+
name: Agent display name.
|
|
76
|
+
entity_type: human, ai, or organization.
|
|
77
|
+
fingerprint: PGP fingerprint (40-char hex).
|
|
78
|
+
public_key: ASCII-armored PGP public key.
|
|
79
|
+
transports: List of contact endpoints.
|
|
80
|
+
capabilities: List of offered services.
|
|
81
|
+
trust_depth: Cloud 9 trust depth (0-9).
|
|
82
|
+
entangled: Whether the agent is entangled (Cloud 9).
|
|
83
|
+
motto: Optional short tagline.
|
|
84
|
+
signature: PGP signature over the card content (set by sign()).
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
card_id: str = Field(default_factory=lambda: str(uuid4()))
|
|
88
|
+
card_version: str = "1.0"
|
|
89
|
+
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
90
|
+
name: str
|
|
91
|
+
entity_type: str = "human"
|
|
92
|
+
fingerprint: str
|
|
93
|
+
public_key: str
|
|
94
|
+
transports: list[TransportEndpoint] = Field(default_factory=list)
|
|
95
|
+
capabilities: list[AgentCapability] = Field(default_factory=list)
|
|
96
|
+
trust_depth: int = Field(default=0, ge=0, le=9)
|
|
97
|
+
entangled: bool = False
|
|
98
|
+
motto: Optional[str] = None
|
|
99
|
+
signature: Optional[str] = None
|
|
100
|
+
|
|
101
|
+
@classmethod
|
|
102
|
+
def generate(
|
|
103
|
+
cls,
|
|
104
|
+
name: str,
|
|
105
|
+
fingerprint: str,
|
|
106
|
+
public_key: str,
|
|
107
|
+
entity_type: str = "human",
|
|
108
|
+
transports: Optional[list[TransportEndpoint]] = None,
|
|
109
|
+
capabilities: Optional[list[AgentCapability]] = None,
|
|
110
|
+
trust_depth: int = 0,
|
|
111
|
+
entangled: bool = False,
|
|
112
|
+
motto: Optional[str] = None,
|
|
113
|
+
) -> AgentCard:
|
|
114
|
+
"""Generate a new agent card.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
name: Agent display name.
|
|
118
|
+
fingerprint: PGP fingerprint.
|
|
119
|
+
public_key: ASCII-armored PGP public key.
|
|
120
|
+
entity_type: human, ai, or organization.
|
|
121
|
+
transports: Contact transport endpoints.
|
|
122
|
+
capabilities: Offered services.
|
|
123
|
+
trust_depth: Cloud 9 trust depth.
|
|
124
|
+
entangled: Cloud 9 entanglement status.
|
|
125
|
+
motto: Optional tagline.
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
AgentCard: Unsigned card ready for signing.
|
|
129
|
+
"""
|
|
130
|
+
return cls(
|
|
131
|
+
name=name,
|
|
132
|
+
entity_type=entity_type,
|
|
133
|
+
fingerprint=fingerprint,
|
|
134
|
+
public_key=public_key,
|
|
135
|
+
transports=transports or [],
|
|
136
|
+
capabilities=capabilities or [],
|
|
137
|
+
trust_depth=trust_depth,
|
|
138
|
+
entangled=entangled,
|
|
139
|
+
motto=motto,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
@classmethod
|
|
143
|
+
def from_capauth_profile(
|
|
144
|
+
cls,
|
|
145
|
+
profile_dir: str | Path = "~/.capauth",
|
|
146
|
+
transports: Optional[list[TransportEndpoint]] = None,
|
|
147
|
+
capabilities: Optional[list[AgentCapability]] = None,
|
|
148
|
+
) -> AgentCard:
|
|
149
|
+
"""Generate a card from an existing CapAuth sovereign profile.
|
|
150
|
+
|
|
151
|
+
Reads the profile.json and public key from the CapAuth directory.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
profile_dir: CapAuth home directory.
|
|
155
|
+
transports: Contact transport endpoints.
|
|
156
|
+
capabilities: Offered services.
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
AgentCard: Card populated from the CapAuth profile.
|
|
160
|
+
|
|
161
|
+
Raises:
|
|
162
|
+
FileNotFoundError: If profile files don't exist.
|
|
163
|
+
"""
|
|
164
|
+
base = Path(profile_dir).expanduser()
|
|
165
|
+
profile_path = base / "identity" / "profile.json"
|
|
166
|
+
pubkey_path = base / "identity" / "public.asc"
|
|
167
|
+
|
|
168
|
+
if not profile_path.exists():
|
|
169
|
+
raise FileNotFoundError(f"CapAuth profile not found: {profile_path}")
|
|
170
|
+
if not pubkey_path.exists():
|
|
171
|
+
raise FileNotFoundError(f"Public key not found: {pubkey_path}")
|
|
172
|
+
|
|
173
|
+
profile_data = json.loads(profile_path.read_text(encoding="utf-8"))
|
|
174
|
+
public_key = pubkey_path.read_text(encoding="utf-8")
|
|
175
|
+
|
|
176
|
+
entity = profile_data.get("entity", {})
|
|
177
|
+
key_info = profile_data.get("key_info", {})
|
|
178
|
+
|
|
179
|
+
return cls.generate(
|
|
180
|
+
name=entity.get("name", "unknown"),
|
|
181
|
+
fingerprint=key_info.get("fingerprint", ""),
|
|
182
|
+
public_key=public_key,
|
|
183
|
+
entity_type=entity.get("entity_type", "human"),
|
|
184
|
+
transports=transports,
|
|
185
|
+
capabilities=capabilities,
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
def content_hash(self) -> str:
|
|
189
|
+
"""Compute SHA-256 hash of the card content (excluding signature).
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
str: Hex digest of the card content.
|
|
193
|
+
"""
|
|
194
|
+
data = self.model_dump(exclude={"signature"})
|
|
195
|
+
serialized = json.dumps(data, sort_keys=True, default=str)
|
|
196
|
+
return hashlib.sha256(serialized.encode()).hexdigest()
|
|
197
|
+
|
|
198
|
+
def sign(self, private_key_armor: str, passphrase: str) -> None:
|
|
199
|
+
"""Sign this card with a PGP private key.
|
|
200
|
+
|
|
201
|
+
Sets the signature field with a PGP signature over
|
|
202
|
+
the card's content hash.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
private_key_armor: ASCII-armored PGP private key.
|
|
206
|
+
passphrase: Passphrase to unlock the key.
|
|
207
|
+
"""
|
|
208
|
+
try:
|
|
209
|
+
import pgpy
|
|
210
|
+
|
|
211
|
+
key, _ = pgpy.PGPKey.from_blob(private_key_armor)
|
|
212
|
+
content = self.content_hash().encode("utf-8")
|
|
213
|
+
pgp_message = pgpy.PGPMessage.new(content, cleartext=False)
|
|
214
|
+
|
|
215
|
+
with key.unlock(passphrase):
|
|
216
|
+
sig = key.sign(pgp_message)
|
|
217
|
+
|
|
218
|
+
self.signature = str(sig)
|
|
219
|
+
except Exception as exc:
|
|
220
|
+
logger.error("Failed to sign agent card: %s", exc)
|
|
221
|
+
raise
|
|
222
|
+
|
|
223
|
+
@staticmethod
|
|
224
|
+
def verify_signature(card: AgentCard) -> bool:
|
|
225
|
+
"""Verify the PGP signature on an agent card.
|
|
226
|
+
|
|
227
|
+
Uses the public key embedded in the card to verify
|
|
228
|
+
the signature over the content hash.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
card: The agent card to verify.
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
bool: True if the signature is valid.
|
|
235
|
+
"""
|
|
236
|
+
if not card.signature or not card.public_key:
|
|
237
|
+
return False
|
|
238
|
+
|
|
239
|
+
try:
|
|
240
|
+
import pgpy
|
|
241
|
+
|
|
242
|
+
pub_key, _ = pgpy.PGPKey.from_blob(card.public_key)
|
|
243
|
+
sig = pgpy.PGPSignature.from_blob(card.signature)
|
|
244
|
+
|
|
245
|
+
content = card.content_hash().encode("utf-8")
|
|
246
|
+
pgp_message = pgpy.PGPMessage.new(content, cleartext=False)
|
|
247
|
+
pgp_message |= sig
|
|
248
|
+
|
|
249
|
+
verification = pub_key.verify(pgp_message)
|
|
250
|
+
return bool(verification)
|
|
251
|
+
except Exception:
|
|
252
|
+
return False
|
|
253
|
+
|
|
254
|
+
def save(self, filepath: str | Path) -> Path:
|
|
255
|
+
"""Save the card to a JSON file.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
filepath: Destination path (tilde-expanded).
|
|
259
|
+
|
|
260
|
+
Returns:
|
|
261
|
+
Path: The written file path.
|
|
262
|
+
"""
|
|
263
|
+
path = Path(filepath).expanduser()
|
|
264
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
265
|
+
path.write_text(self.model_dump_json(indent=2), encoding="utf-8")
|
|
266
|
+
logger.info("Agent card saved to %s", path)
|
|
267
|
+
return path
|
|
268
|
+
|
|
269
|
+
@classmethod
|
|
270
|
+
def load(cls, filepath: str | Path) -> AgentCard:
|
|
271
|
+
"""Load a card from a JSON file.
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
filepath: Path to the card file.
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
AgentCard: The loaded card.
|
|
278
|
+
|
|
279
|
+
Raises:
|
|
280
|
+
FileNotFoundError: If the file doesn't exist.
|
|
281
|
+
"""
|
|
282
|
+
path = Path(filepath).expanduser()
|
|
283
|
+
if not path.exists():
|
|
284
|
+
raise FileNotFoundError(f"Agent card not found: {path}")
|
|
285
|
+
return cls.model_validate_json(path.read_text(encoding="utf-8"))
|
|
286
|
+
|
|
287
|
+
def to_compact(self) -> dict:
|
|
288
|
+
"""Export a compact representation for display or QR codes.
|
|
289
|
+
|
|
290
|
+
Excludes the full public key to keep the size small.
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
dict: Compact card with essential fields only.
|
|
294
|
+
"""
|
|
295
|
+
return {
|
|
296
|
+
"name": self.name,
|
|
297
|
+
"type": self.entity_type,
|
|
298
|
+
"fp": self.fingerprint[:16],
|
|
299
|
+
"transports": [
|
|
300
|
+
{"t": t.transport, "a": t.address} for t in self.transports
|
|
301
|
+
],
|
|
302
|
+
"caps": [c.name for c in self.capabilities],
|
|
303
|
+
"trust": self.trust_depth,
|
|
304
|
+
"motto": self.motto,
|
|
305
|
+
"signed": self.signature is not None,
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
def summary(self) -> str:
|
|
309
|
+
"""Human-readable summary of the card.
|
|
310
|
+
|
|
311
|
+
Returns:
|
|
312
|
+
str: Multi-line summary string.
|
|
313
|
+
"""
|
|
314
|
+
lines = [
|
|
315
|
+
f"Agent: {self.name} ({self.entity_type})",
|
|
316
|
+
f"Fingerprint: {self.fingerprint[:16]}...",
|
|
317
|
+
f"Trust: depth={self.trust_depth} entangled={self.entangled}",
|
|
318
|
+
f"Transports: {len(self.transports)}",
|
|
319
|
+
f"Capabilities: {', '.join(c.name for c in self.capabilities) or 'none'}",
|
|
320
|
+
f"Signed: {'yes' if self.signature else 'no'}",
|
|
321
|
+
]
|
|
322
|
+
if self.motto:
|
|
323
|
+
lines.insert(1, f'Motto: "{self.motto}"')
|
|
324
|
+
return "\n".join(lines)
|