@smilintux/skcapstone 0.1.0 → 0.2.4
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 +880 -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 +191 -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 +398 -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 +357 -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 +264 -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,584 @@
|
|
|
1
|
+
"""Agent-to-agent chat commands: send, inbox, live, open, list, summary.
|
|
2
|
+
|
|
3
|
+
skcapstone chat <peer> Open interactive LLM session (shortcut)
|
|
4
|
+
skcapstone chat open <peer> Open interactive LLM session
|
|
5
|
+
skcapstone chat send <peer> <m> One-shot send
|
|
6
|
+
skcapstone chat inbox Browse messages
|
|
7
|
+
skcapstone chat live <peer> Alias for 'open'
|
|
8
|
+
skcapstone chat list List peers with conversation history
|
|
9
|
+
skcapstone chat --list Same as 'list'
|
|
10
|
+
skcapstone chat summary <peer> LLM-powered conversation summary
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
import sys
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Optional
|
|
19
|
+
|
|
20
|
+
import click
|
|
21
|
+
|
|
22
|
+
from ._common import AGENT_HOME, console, get_runtime
|
|
23
|
+
from ._validators import validate_agent_name
|
|
24
|
+
|
|
25
|
+
from rich.table import Table
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# Known sub-command names; anything else is treated as a peer name.
|
|
29
|
+
_KNOWN_SUBCOMMANDS = {"send", "inbox", "live", "open", "list", "history", "summary", "forward", "--help", "-h", "--version"}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class _ChatGroup(click.Group):
|
|
33
|
+
"""Click group that treats an unknown first arg as a peer for 'open'.
|
|
34
|
+
|
|
35
|
+
Allows::
|
|
36
|
+
|
|
37
|
+
skcapstone chat lumina # same as: skcapstone chat open lumina
|
|
38
|
+
skcapstone chat send lumina … # normal subcommand routing
|
|
39
|
+
skcapstone chat --list # same as: skcapstone chat list
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def parse_args(self, ctx: click.Context, args: list[str]) -> list[str]:
|
|
43
|
+
if "--list" in args:
|
|
44
|
+
remaining = [a for a in args if a != "--list"]
|
|
45
|
+
args = ["list"] + remaining
|
|
46
|
+
elif args and not args[0].startswith("-") and args[0] not in _KNOWN_SUBCOMMANDS:
|
|
47
|
+
args = ["open"] + args
|
|
48
|
+
return super().parse_args(ctx, args)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _run_llm_chat(peer: str, home_path: Path, identity: str) -> None:
|
|
52
|
+
"""Run an LLM-powered interactive terminal chat session.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
peer: Peer name used as conversation context key.
|
|
56
|
+
home_path: Agent home directory.
|
|
57
|
+
identity: Local agent name shown in the prompt.
|
|
58
|
+
"""
|
|
59
|
+
from ..consciousness_loop import (
|
|
60
|
+
ConsciousnessConfig,
|
|
61
|
+
LLMBridge,
|
|
62
|
+
SystemPromptBuilder,
|
|
63
|
+
_classify_message,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
config = ConsciousnessConfig()
|
|
67
|
+
bridge = LLMBridge(config)
|
|
68
|
+
builder = SystemPromptBuilder(home=home_path)
|
|
69
|
+
|
|
70
|
+
# Show last 5 messages from existing history
|
|
71
|
+
conv_file = home_path / "conversations" / f"{peer}.json"
|
|
72
|
+
console.print()
|
|
73
|
+
if conv_file.exists():
|
|
74
|
+
try:
|
|
75
|
+
history = json.loads(conv_file.read_text(encoding="utf-8"))
|
|
76
|
+
if history:
|
|
77
|
+
console.print(
|
|
78
|
+
f"[dim]--- {len(history)} previous message(s) with {peer} ---[/]\n"
|
|
79
|
+
)
|
|
80
|
+
for msg in history[-5:]:
|
|
81
|
+
if msg.get("role") == "user":
|
|
82
|
+
label = f"[cyan]{identity}[/]"
|
|
83
|
+
else:
|
|
84
|
+
label = f"[green]{peer}[/]"
|
|
85
|
+
content = msg.get("content", "")[:100]
|
|
86
|
+
console.print(f" {label}: {content}")
|
|
87
|
+
console.print()
|
|
88
|
+
except Exception:
|
|
89
|
+
pass
|
|
90
|
+
|
|
91
|
+
console.print(f"[bold]Chat with [cyan]{peer}[/][/] [dim]Ctrl+C or /quit to exit[/]\n")
|
|
92
|
+
|
|
93
|
+
try:
|
|
94
|
+
while True:
|
|
95
|
+
try:
|
|
96
|
+
user_msg = console.input(f"[cyan]{identity}[/] > ").strip()
|
|
97
|
+
except EOFError:
|
|
98
|
+
break
|
|
99
|
+
|
|
100
|
+
if not user_msg:
|
|
101
|
+
continue
|
|
102
|
+
if user_msg.lower() in ("/quit", "/exit", "/q"):
|
|
103
|
+
break
|
|
104
|
+
|
|
105
|
+
builder.add_to_history(peer, "user", user_msg)
|
|
106
|
+
|
|
107
|
+
system_prompt = builder.build(peer_name=peer)
|
|
108
|
+
signal = _classify_message(user_msg)
|
|
109
|
+
|
|
110
|
+
with console.status("[dim]thinking...[/]"):
|
|
111
|
+
try:
|
|
112
|
+
response = bridge.generate(system_prompt, user_msg, signal)
|
|
113
|
+
except Exception as exc:
|
|
114
|
+
response = f"[Error: {exc}]"
|
|
115
|
+
|
|
116
|
+
console.print(f"[green]{peer}[/]: {response}\n")
|
|
117
|
+
builder.add_to_history(peer, "assistant", response)
|
|
118
|
+
|
|
119
|
+
except KeyboardInterrupt:
|
|
120
|
+
console.print("\n[dim]Session ended.[/]")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def register_chat_commands(main: click.Group) -> None:
|
|
124
|
+
"""Register the chat command group."""
|
|
125
|
+
|
|
126
|
+
@main.group(cls=_ChatGroup)
|
|
127
|
+
def chat():
|
|
128
|
+
"""Agent-to-agent chat — sovereign P2P messaging.
|
|
129
|
+
|
|
130
|
+
Open an interactive session, send one-off messages, or browse
|
|
131
|
+
your inbox. Works from any terminal — no IDE required.
|
|
132
|
+
|
|
133
|
+
\b
|
|
134
|
+
Quick start:
|
|
135
|
+
skcapstone chat lumina # start chatting with 'lumina'
|
|
136
|
+
skcapstone chat send opus "hi" # send a one-off message
|
|
137
|
+
skcapstone chat inbox --poll # check for new messages
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
# ------------------------------------------------------------------
|
|
141
|
+
# open — interactive prompt_toolkit session
|
|
142
|
+
# ------------------------------------------------------------------
|
|
143
|
+
|
|
144
|
+
@chat.command("open")
|
|
145
|
+
@click.argument("peer")
|
|
146
|
+
@click.option("--home", default=AGENT_HOME, type=click.Path())
|
|
147
|
+
@click.option(
|
|
148
|
+
"--thread", "-t", default=None,
|
|
149
|
+
help="Start in a specific thread ID.",
|
|
150
|
+
)
|
|
151
|
+
@click.option(
|
|
152
|
+
"--poll-interval", default=2.0, show_default=True,
|
|
153
|
+
help="Seconds between incoming message polls.",
|
|
154
|
+
)
|
|
155
|
+
def chat_open(peer: str, home: str, thread: Optional[str], poll_interval: float):
|
|
156
|
+
"""Open an interactive LLM-powered chat session.
|
|
157
|
+
|
|
158
|
+
Starts a terminal chat loop that uses the local LLM (via
|
|
159
|
+
LLMBridge) to generate responses. Conversation history is
|
|
160
|
+
shown at startup and saved to conversations/{peer}.json.
|
|
161
|
+
|
|
162
|
+
\b
|
|
163
|
+
Slash commands:
|
|
164
|
+
/quit /exit /q — exit the session
|
|
165
|
+
|
|
166
|
+
\b
|
|
167
|
+
Examples:
|
|
168
|
+
skcapstone chat lumina
|
|
169
|
+
skcapstone chat open lumina
|
|
170
|
+
skcapstone chat open opus
|
|
171
|
+
"""
|
|
172
|
+
validate_agent_name(peer)
|
|
173
|
+
|
|
174
|
+
home_path = Path(home).expanduser()
|
|
175
|
+
if not home_path.exists():
|
|
176
|
+
console.print("[bold red]No agent found.[/] Run skcapstone init first.")
|
|
177
|
+
sys.exit(1)
|
|
178
|
+
|
|
179
|
+
runtime = get_runtime(home_path)
|
|
180
|
+
identity = runtime.manifest.name or "unknown"
|
|
181
|
+
|
|
182
|
+
_run_llm_chat(peer, home_path, identity)
|
|
183
|
+
|
|
184
|
+
# ------------------------------------------------------------------
|
|
185
|
+
# send — one-shot message
|
|
186
|
+
# ------------------------------------------------------------------
|
|
187
|
+
|
|
188
|
+
@chat.command("send")
|
|
189
|
+
@click.argument("peer")
|
|
190
|
+
@click.argument("message")
|
|
191
|
+
@click.option("--home", default=AGENT_HOME, type=click.Path())
|
|
192
|
+
@click.option("--thread", "-t", default=None, help="Thread ID for conversation grouping.")
|
|
193
|
+
@click.option("--encrypt", is_flag=True, default=False, help="Encrypt message with AES-256-GCM (key from KMS).")
|
|
194
|
+
def chat_send(peer: str, message: str, home: str, thread: Optional[str], encrypt: bool):
|
|
195
|
+
"""Send a message to a peer agent.
|
|
196
|
+
|
|
197
|
+
Stores locally and delivers via SKComm if transports
|
|
198
|
+
are configured.
|
|
199
|
+
|
|
200
|
+
\b
|
|
201
|
+
Examples:
|
|
202
|
+
skcapstone chat send lumina "Hello from the sovereign side!"
|
|
203
|
+
skcapstone chat send opus "Deploy update ready" --thread deploy-01
|
|
204
|
+
skcapstone chat send lumina "Secret plan" --encrypt
|
|
205
|
+
"""
|
|
206
|
+
from ..chat import AgentChat
|
|
207
|
+
|
|
208
|
+
validate_agent_name(peer)
|
|
209
|
+
|
|
210
|
+
home_path = Path(home).expanduser()
|
|
211
|
+
runtime = get_runtime(home_path)
|
|
212
|
+
identity = runtime.manifest.name or "unknown"
|
|
213
|
+
|
|
214
|
+
payload = message
|
|
215
|
+
if encrypt:
|
|
216
|
+
try:
|
|
217
|
+
from ..message_crypto import encrypt_content
|
|
218
|
+
payload = encrypt_content(message, home_path)
|
|
219
|
+
console.print(" [dim]Message encrypted (AES-256-GCM)[/]")
|
|
220
|
+
except Exception as exc:
|
|
221
|
+
console.print(f" [bold red]Encryption failed:[/] {exc}")
|
|
222
|
+
sys.exit(1)
|
|
223
|
+
|
|
224
|
+
agent_chat = AgentChat(home=home_path, identity=identity)
|
|
225
|
+
result = agent_chat.send(peer, payload, thread_id=thread)
|
|
226
|
+
|
|
227
|
+
console.print("")
|
|
228
|
+
if result["delivered"]:
|
|
229
|
+
console.print(f" [green]Delivered[/] to [cyan]{peer}[/] via {result['transport']}")
|
|
230
|
+
elif result["stored"]:
|
|
231
|
+
console.print(f" [yellow]Stored locally[/] for [cyan]{peer}[/]")
|
|
232
|
+
if result.get("error"):
|
|
233
|
+
console.print(f" [dim]{result['error']}[/]")
|
|
234
|
+
else:
|
|
235
|
+
console.print(f" [red]Failed[/] — {result.get('error', 'unknown error')}")
|
|
236
|
+
console.print("")
|
|
237
|
+
|
|
238
|
+
# ------------------------------------------------------------------
|
|
239
|
+
# inbox — browse messages
|
|
240
|
+
# ------------------------------------------------------------------
|
|
241
|
+
|
|
242
|
+
@chat.command("inbox")
|
|
243
|
+
@click.option("--home", default=AGENT_HOME, type=click.Path())
|
|
244
|
+
@click.option("--limit", "-n", default=20, help="Max messages to show.")
|
|
245
|
+
@click.option("--poll", is_flag=True, help="Poll transports for new messages first.")
|
|
246
|
+
@click.option("--decrypt", is_flag=True, default=False, help="Decrypt encrypted messages using KMS key.")
|
|
247
|
+
def chat_inbox(home: str, limit: int, poll: bool, decrypt: bool):
|
|
248
|
+
"""Show recent messages.
|
|
249
|
+
|
|
250
|
+
Displays messages from local history. Use --poll to check
|
|
251
|
+
SKComm transports for new messages first. Use --decrypt to
|
|
252
|
+
automatically decrypt AES-256-GCM encrypted messages.
|
|
253
|
+
|
|
254
|
+
\b
|
|
255
|
+
Examples:
|
|
256
|
+
skcapstone chat inbox
|
|
257
|
+
skcapstone chat inbox --poll --limit 5
|
|
258
|
+
skcapstone chat inbox --decrypt
|
|
259
|
+
"""
|
|
260
|
+
from ..chat import AgentChat
|
|
261
|
+
|
|
262
|
+
home_path = Path(home).expanduser()
|
|
263
|
+
runtime = get_runtime(home_path)
|
|
264
|
+
identity = runtime.manifest.name or "unknown"
|
|
265
|
+
|
|
266
|
+
agent_chat = AgentChat(home=home_path, identity=identity)
|
|
267
|
+
|
|
268
|
+
if poll:
|
|
269
|
+
incoming = agent_chat.receive(limit=limit)
|
|
270
|
+
if incoming:
|
|
271
|
+
console.print(f"\n [green]{len(incoming)} new message(s) received[/]\n")
|
|
272
|
+
|
|
273
|
+
messages = agent_chat.get_inbox(limit=limit)
|
|
274
|
+
|
|
275
|
+
console.print("")
|
|
276
|
+
if not messages:
|
|
277
|
+
console.print(" [dim]No messages.[/]")
|
|
278
|
+
console.print("")
|
|
279
|
+
return
|
|
280
|
+
|
|
281
|
+
from ..chat import _format_content
|
|
282
|
+
from ..message_crypto import decrypt_content, is_encrypted_content
|
|
283
|
+
|
|
284
|
+
table = Table(
|
|
285
|
+
show_header=True,
|
|
286
|
+
header_style="bold",
|
|
287
|
+
box=None,
|
|
288
|
+
padding=(0, 2),
|
|
289
|
+
title=f"Inbox ({len(messages)} message{'s' if len(messages) != 1 else ''})",
|
|
290
|
+
)
|
|
291
|
+
table.add_column("From", style="cyan", max_width=25)
|
|
292
|
+
table.add_column("Content", max_width=50)
|
|
293
|
+
table.add_column("Time", style="dim", max_width=20)
|
|
294
|
+
|
|
295
|
+
for msg in messages:
|
|
296
|
+
sender = msg.get("sender", "?")
|
|
297
|
+
raw_content = msg.get("content", "")
|
|
298
|
+
if decrypt and is_encrypted_content(raw_content):
|
|
299
|
+
try:
|
|
300
|
+
raw_content = decrypt_content(raw_content, home_path)
|
|
301
|
+
except Exception as exc:
|
|
302
|
+
raw_content = f"[decrypt failed: {exc}]"
|
|
303
|
+
content = _format_content(raw_content)
|
|
304
|
+
preview = content[:50] + ("…" if len(content) > 50 else "")
|
|
305
|
+
ts = str(msg.get("timestamp", ""))
|
|
306
|
+
if len(ts) > 19:
|
|
307
|
+
ts = ts[:19]
|
|
308
|
+
table.add_row(sender, preview, ts)
|
|
309
|
+
|
|
310
|
+
console.print(table)
|
|
311
|
+
console.print("")
|
|
312
|
+
|
|
313
|
+
# ------------------------------------------------------------------
|
|
314
|
+
# list — show peers with conversation history
|
|
315
|
+
# ------------------------------------------------------------------
|
|
316
|
+
|
|
317
|
+
@chat.command("list")
|
|
318
|
+
@click.option("--home", default=AGENT_HOME, type=click.Path())
|
|
319
|
+
def chat_list(home: str):
|
|
320
|
+
"""List all peers with conversation history.
|
|
321
|
+
|
|
322
|
+
Shows each peer that has a saved conversation file, along with
|
|
323
|
+
the message count and a preview of the most recent message.
|
|
324
|
+
|
|
325
|
+
\b
|
|
326
|
+
Examples:
|
|
327
|
+
skcapstone chat list
|
|
328
|
+
skcapstone chat --list
|
|
329
|
+
"""
|
|
330
|
+
home_path = Path(home).expanduser()
|
|
331
|
+
conversations_dir = home_path / "conversations"
|
|
332
|
+
|
|
333
|
+
if not conversations_dir.exists():
|
|
334
|
+
console.print("\n [dim]No conversations yet.[/]\n")
|
|
335
|
+
return
|
|
336
|
+
|
|
337
|
+
conv_files = sorted(conversations_dir.glob("*.json"))
|
|
338
|
+
if not conv_files:
|
|
339
|
+
console.print("\n [dim]No conversations yet.[/]\n")
|
|
340
|
+
return
|
|
341
|
+
|
|
342
|
+
table = Table(
|
|
343
|
+
show_header=True,
|
|
344
|
+
header_style="bold",
|
|
345
|
+
box=None,
|
|
346
|
+
padding=(0, 2),
|
|
347
|
+
title=f"Conversations ({len(conv_files)} peer{'s' if len(conv_files) != 1 else ''})",
|
|
348
|
+
)
|
|
349
|
+
table.add_column("Peer", style="cyan")
|
|
350
|
+
table.add_column("Messages", justify="right", style="dim")
|
|
351
|
+
table.add_column("Last message", max_width=60)
|
|
352
|
+
|
|
353
|
+
for conv_file in conv_files:
|
|
354
|
+
peer = conv_file.stem
|
|
355
|
+
try:
|
|
356
|
+
data = json.loads(conv_file.read_text(encoding="utf-8"))
|
|
357
|
+
count = str(len(data)) if isinstance(data, list) else "?"
|
|
358
|
+
last = ""
|
|
359
|
+
if isinstance(data, list) and data:
|
|
360
|
+
last = str(data[-1].get("content", ""))[:60]
|
|
361
|
+
table.add_row(peer, count, last)
|
|
362
|
+
except Exception:
|
|
363
|
+
table.add_row(peer, "?", "[dim][corrupted][/]")
|
|
364
|
+
|
|
365
|
+
console.print()
|
|
366
|
+
console.print(table)
|
|
367
|
+
console.print()
|
|
368
|
+
|
|
369
|
+
# ------------------------------------------------------------------
|
|
370
|
+
# history — full conversation transcript for a peer
|
|
371
|
+
# ------------------------------------------------------------------
|
|
372
|
+
|
|
373
|
+
@chat.command("history")
|
|
374
|
+
@click.argument("peer")
|
|
375
|
+
@click.option("--home", default=AGENT_HOME, type=click.Path())
|
|
376
|
+
@click.option("--limit", "-n", default=0, help="Show last N messages (0 = all).")
|
|
377
|
+
@click.option(
|
|
378
|
+
"--json", "as_json", is_flag=True, default=False,
|
|
379
|
+
help="Output raw JSON instead of a formatted table.",
|
|
380
|
+
)
|
|
381
|
+
def chat_history(peer: str, home: str, limit: int, as_json: bool):
|
|
382
|
+
"""Show the full conversation history with PEER.
|
|
383
|
+
|
|
384
|
+
Reads the persisted conversation file and displays every message
|
|
385
|
+
exchanged with the named peer, oldest first. Use --limit to
|
|
386
|
+
restrict to the most recent N messages.
|
|
387
|
+
|
|
388
|
+
\b
|
|
389
|
+
Examples:
|
|
390
|
+
skcapstone chat history lumina
|
|
391
|
+
skcapstone chat history opus --limit 10
|
|
392
|
+
skcapstone chat history jarvis --json
|
|
393
|
+
"""
|
|
394
|
+
from ..conversation_store import ConversationStore
|
|
395
|
+
|
|
396
|
+
validate_agent_name(peer)
|
|
397
|
+
|
|
398
|
+
home_path = Path(home).expanduser()
|
|
399
|
+
store = ConversationStore(home_path)
|
|
400
|
+
messages = store.load(peer)
|
|
401
|
+
|
|
402
|
+
if not messages:
|
|
403
|
+
console.print(f"\n [dim]No conversation history with {peer}.[/]\n")
|
|
404
|
+
return
|
|
405
|
+
|
|
406
|
+
if limit > 0:
|
|
407
|
+
messages = messages[-limit:]
|
|
408
|
+
|
|
409
|
+
if as_json:
|
|
410
|
+
import json as _json
|
|
411
|
+
console.print(_json.dumps(messages, ensure_ascii=False, indent=2))
|
|
412
|
+
return
|
|
413
|
+
|
|
414
|
+
title = f"Conversation with {peer} ({len(messages)} message{'s' if len(messages) != 1 else ''})"
|
|
415
|
+
table = Table(
|
|
416
|
+
show_header=True,
|
|
417
|
+
header_style="bold",
|
|
418
|
+
box=None,
|
|
419
|
+
padding=(0, 2),
|
|
420
|
+
title=title,
|
|
421
|
+
)
|
|
422
|
+
table.add_column("Role", style="cyan", width=12)
|
|
423
|
+
table.add_column("Message")
|
|
424
|
+
table.add_column("Time", style="dim", width=20)
|
|
425
|
+
|
|
426
|
+
for msg in messages:
|
|
427
|
+
role = msg.get("role", "?")
|
|
428
|
+
role_color = "green" if role == "assistant" else "cyan"
|
|
429
|
+
content = msg.get("content", "")
|
|
430
|
+
ts = str(msg.get("timestamp", ""))
|
|
431
|
+
if len(ts) > 19:
|
|
432
|
+
ts = ts[:19]
|
|
433
|
+
table.add_row(
|
|
434
|
+
f"[{role_color}]{role}[/]",
|
|
435
|
+
content,
|
|
436
|
+
ts,
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
console.print()
|
|
440
|
+
console.print(table)
|
|
441
|
+
console.print()
|
|
442
|
+
|
|
443
|
+
# ------------------------------------------------------------------
|
|
444
|
+
# forward — re-send a message to another peer
|
|
445
|
+
# ------------------------------------------------------------------
|
|
446
|
+
|
|
447
|
+
@chat.command("forward")
|
|
448
|
+
@click.argument("peer")
|
|
449
|
+
@click.argument("msg_id")
|
|
450
|
+
@click.option("--home", default=AGENT_HOME, type=click.Path())
|
|
451
|
+
@click.option("--thread", "-t", default=None, help="Thread ID for the forwarded message.")
|
|
452
|
+
def chat_forward(peer: str, msg_id: str, home: str, thread: Optional[str]):
|
|
453
|
+
"""Forward a message to another peer agent.
|
|
454
|
+
|
|
455
|
+
Looks up MSG_ID in the local inbox and forwards it to PEER,
|
|
456
|
+
preserving the original sender and timestamp in the forward
|
|
457
|
+
envelope. The forwarding agent is recorded as the new sender.
|
|
458
|
+
|
|
459
|
+
\b
|
|
460
|
+
Examples:
|
|
461
|
+
skcapstone chat forward opus msg-abc123
|
|
462
|
+
skcapstone chat forward lumina msg-xyz --thread fwd-thread-01
|
|
463
|
+
"""
|
|
464
|
+
from ..chat import AgentChat
|
|
465
|
+
|
|
466
|
+
validate_agent_name(peer)
|
|
467
|
+
|
|
468
|
+
home_path = Path(home).expanduser()
|
|
469
|
+
runtime = get_runtime(home_path)
|
|
470
|
+
identity = runtime.manifest.name or "unknown"
|
|
471
|
+
|
|
472
|
+
agent_chat = AgentChat(home=home_path, identity=identity)
|
|
473
|
+
|
|
474
|
+
messages = agent_chat.get_inbox(limit=200)
|
|
475
|
+
original = next(
|
|
476
|
+
(m for m in messages if m.get("message_id") == msg_id),
|
|
477
|
+
None,
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
if original is None:
|
|
481
|
+
console.print(f"\n [red]Message not found:[/] {msg_id}\n")
|
|
482
|
+
sys.exit(1)
|
|
483
|
+
|
|
484
|
+
result = agent_chat.forward(original, peer, thread_id=thread)
|
|
485
|
+
|
|
486
|
+
console.print("")
|
|
487
|
+
if result["delivered"]:
|
|
488
|
+
console.print(
|
|
489
|
+
f" [green]Forwarded[/] to [cyan]{peer}[/] via {result['transport']} "
|
|
490
|
+
f"[dim](id: {result['forwarded_id']})[/]"
|
|
491
|
+
)
|
|
492
|
+
elif result["stored"]:
|
|
493
|
+
console.print(
|
|
494
|
+
f" [yellow]Stored locally[/] for [cyan]{peer}[/] "
|
|
495
|
+
f"[dim](id: {result['forwarded_id']})[/]"
|
|
496
|
+
)
|
|
497
|
+
if result.get("error"):
|
|
498
|
+
console.print(f" [dim]{result['error']}[/]")
|
|
499
|
+
else:
|
|
500
|
+
console.print(f" [red]Failed[/] — {result.get('error', 'unknown error')}")
|
|
501
|
+
console.print("")
|
|
502
|
+
|
|
503
|
+
# ------------------------------------------------------------------
|
|
504
|
+
# summary — LLM-powered conversation summarizer
|
|
505
|
+
# ------------------------------------------------------------------
|
|
506
|
+
|
|
507
|
+
@chat.command("summary")
|
|
508
|
+
@click.argument("peer")
|
|
509
|
+
@click.option("--home", default=AGENT_HOME, type=click.Path())
|
|
510
|
+
@click.option(
|
|
511
|
+
"--last", "-n", default=20, show_default=True,
|
|
512
|
+
help="Number of recent messages to include in the summary.",
|
|
513
|
+
)
|
|
514
|
+
@click.option(
|
|
515
|
+
"--show-stored", is_flag=True, default=False,
|
|
516
|
+
help="Show the previously stored summary instead of generating a new one.",
|
|
517
|
+
)
|
|
518
|
+
def chat_summary(peer: str, home: str, last: int, show_stored: bool):
|
|
519
|
+
"""Summarize a conversation with PEER using the LLM.
|
|
520
|
+
|
|
521
|
+
Reads the last N messages with PEER, calls the local LLM to
|
|
522
|
+
produce a 2-3 sentence summary, and stores it under
|
|
523
|
+
~/.skcapstone/summaries/{peer}.json for future reference.
|
|
524
|
+
|
|
525
|
+
\b
|
|
526
|
+
Examples:
|
|
527
|
+
skcapstone chat summary lumina
|
|
528
|
+
skcapstone chat summary opus --last 50
|
|
529
|
+
skcapstone chat summary lumina --show-stored
|
|
530
|
+
"""
|
|
531
|
+
from ..conversation_summarizer import ConversationSummarizer
|
|
532
|
+
|
|
533
|
+
validate_agent_name(peer)
|
|
534
|
+
home_path = Path(home).expanduser()
|
|
535
|
+
|
|
536
|
+
summarizer = ConversationSummarizer(home=home_path)
|
|
537
|
+
|
|
538
|
+
if show_stored:
|
|
539
|
+
stored = summarizer.load_summary(peer)
|
|
540
|
+
if stored is None:
|
|
541
|
+
console.print(f"\n [yellow]No stored summary for[/] [cyan]{peer}[/].\n")
|
|
542
|
+
console.print(" Run without --show-stored to generate one.\n")
|
|
543
|
+
return
|
|
544
|
+
console.print(f"\n[bold]Stored summary for [cyan]{peer}[/][/]")
|
|
545
|
+
console.print(f"[dim]{stored.generated_at[:19]} ({stored.message_count} messages)[/]\n")
|
|
546
|
+
console.print(stored.text)
|
|
547
|
+
console.print()
|
|
548
|
+
return
|
|
549
|
+
|
|
550
|
+
console.print(f"\n[dim]Summarizing last {last} messages with {peer}...[/]")
|
|
551
|
+
with console.status("[dim]calling LLM...[/]"):
|
|
552
|
+
try:
|
|
553
|
+
result = summarizer.summarize(peer, n=last)
|
|
554
|
+
except ValueError as exc:
|
|
555
|
+
console.print(f"\n [red]Error:[/] {exc}\n")
|
|
556
|
+
return
|
|
557
|
+
|
|
558
|
+
console.print(f"\n[bold]Summary of conversation with [cyan]{peer}[/][/]")
|
|
559
|
+
console.print(f"[dim]{result.generated_at[:19]} ({result.message_count} messages summarized)[/]\n")
|
|
560
|
+
console.print(result.text)
|
|
561
|
+
console.print(f"\n[dim]Saved to: {home_path}/summaries/{peer}.json[/]\n")
|
|
562
|
+
|
|
563
|
+
# ------------------------------------------------------------------
|
|
564
|
+
# live — alias for open (backwards compat)
|
|
565
|
+
# ------------------------------------------------------------------
|
|
566
|
+
|
|
567
|
+
@chat.command("live")
|
|
568
|
+
@click.argument("peer")
|
|
569
|
+
@click.option("--home", default=AGENT_HOME, type=click.Path())
|
|
570
|
+
@click.option("--poll-interval", default=2.0, help="Seconds between inbox polls (default: 2).")
|
|
571
|
+
@click.option("--thread", "-t", default=None, help="Starting thread ID.")
|
|
572
|
+
@click.pass_context
|
|
573
|
+
def chat_live(ctx, peer: str, home: str, poll_interval: float, thread: Optional[str]):
|
|
574
|
+
"""Start a live interactive chat session with a peer.
|
|
575
|
+
|
|
576
|
+
Alias for 'skcapstone chat open'. Uses prompt_toolkit when
|
|
577
|
+
available, falls back to plain readline.
|
|
578
|
+
|
|
579
|
+
\b
|
|
580
|
+
Examples:
|
|
581
|
+
skcapstone chat live lumina
|
|
582
|
+
skcapstone chat live opus --poll-interval 5
|
|
583
|
+
"""
|
|
584
|
+
ctx.invoke(chat_open, peer=peer, home=home, thread=thread, poll_interval=poll_interval)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Shell completions commands: install, show, uninstall."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
|
|
9
|
+
from ._common import console
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def register_completions_commands(main: click.Group) -> None:
|
|
13
|
+
"""Register the completions command group."""
|
|
14
|
+
|
|
15
|
+
@main.group()
|
|
16
|
+
def completions():
|
|
17
|
+
"""Shell tab completion — sovereign autocomplete.
|
|
18
|
+
|
|
19
|
+
Install, show, or remove tab completion scripts for
|
|
20
|
+
bash, zsh, and fish.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
@completions.command("install")
|
|
24
|
+
@click.option("--shell", "shell_name", default=None, type=click.Choice(["bash", "zsh", "fish"]))
|
|
25
|
+
def completions_install(shell_name):
|
|
26
|
+
"""Install tab completion for your shell."""
|
|
27
|
+
from ..completions import install_completions
|
|
28
|
+
|
|
29
|
+
result = install_completions(shell=shell_name)
|
|
30
|
+
|
|
31
|
+
if not result.get("success"):
|
|
32
|
+
console.print(f"\n [red]{result.get('error', 'Install failed')}[/]\n")
|
|
33
|
+
sys.exit(1)
|
|
34
|
+
|
|
35
|
+
console.print(f"\n [green]Tab completion installed for {result['shell']}[/]")
|
|
36
|
+
console.print(f" Script: {result['script_path']}")
|
|
37
|
+
if result.get("rc_updated"):
|
|
38
|
+
console.print(f" RC updated: {result.get('rc_path')}")
|
|
39
|
+
console.print(f" [dim]Restart your shell or run: source {result['script_path']}[/]\n")
|
|
40
|
+
|
|
41
|
+
@completions.command("show")
|
|
42
|
+
@click.option("--shell", "shell_name", default=None, type=click.Choice(["bash", "zsh", "fish"]))
|
|
43
|
+
def completions_show(shell_name):
|
|
44
|
+
"""Print the completion script to stdout."""
|
|
45
|
+
from ..completions import detect_shell, generate_script
|
|
46
|
+
|
|
47
|
+
shell = shell_name or detect_shell() or "bash"
|
|
48
|
+
click.echo(generate_script(shell))
|
|
49
|
+
|
|
50
|
+
@completions.command("uninstall")
|
|
51
|
+
@click.option("--shell", "shell_name", default=None, type=click.Choice(["bash", "zsh", "fish"]))
|
|
52
|
+
def completions_uninstall(shell_name):
|
|
53
|
+
"""Remove installed completion scripts."""
|
|
54
|
+
from ..completions import uninstall_completions
|
|
55
|
+
|
|
56
|
+
result = uninstall_completions(shell=shell_name)
|
|
57
|
+
|
|
58
|
+
if result["removed"]:
|
|
59
|
+
for path in result["removed"]:
|
|
60
|
+
console.print(f" [green]Removed:[/] {path}")
|
|
61
|
+
else:
|
|
62
|
+
console.print(" [dim]No completion scripts found to remove.[/]")
|
|
63
|
+
console.print(f" [dim]{result['note']}[/]")
|
|
64
|
+
console.print()
|