@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,136 @@
|
|
|
1
|
+
"""Metrics command: show today's consciousness loop stats."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from datetime import datetime, timezone
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import click
|
|
10
|
+
|
|
11
|
+
from ._common import AGENT_HOME, console
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def register_metrics_commands(main: click.Group) -> None:
|
|
15
|
+
"""Register the ``skcapstone metrics`` command."""
|
|
16
|
+
|
|
17
|
+
@main.command("metrics")
|
|
18
|
+
@click.option("--home", default=AGENT_HOME, type=click.Path(), help="Agent home directory.")
|
|
19
|
+
@click.option("--port", default=7777, help="Daemon API port (default: 7777).")
|
|
20
|
+
@click.option("--json-out", is_flag=True, help="Output raw JSON.")
|
|
21
|
+
@click.option("--date", "date_str", default=None, help="Show metrics for a specific date (YYYY-MM-DD).")
|
|
22
|
+
def metrics_cmd(home: str, port: int, json_out: bool, date_str: str | None):
|
|
23
|
+
"""Show today's consciousness loop metrics.
|
|
24
|
+
|
|
25
|
+
Tries the running daemon first (GET /api/v1/metrics).
|
|
26
|
+
Falls back to reading the persisted daily JSON file.
|
|
27
|
+
"""
|
|
28
|
+
data = _fetch_from_daemon(port)
|
|
29
|
+
if data is None:
|
|
30
|
+
data = _read_from_file(Path(home).expanduser(), date_str)
|
|
31
|
+
|
|
32
|
+
if data is None:
|
|
33
|
+
if json_out:
|
|
34
|
+
click.echo(json.dumps({"error": "No metrics available"}))
|
|
35
|
+
else:
|
|
36
|
+
console.print("[yellow]No metrics found.[/] Start the daemon or run a test message first.")
|
|
37
|
+
return
|
|
38
|
+
|
|
39
|
+
if json_out:
|
|
40
|
+
click.echo(json.dumps(data, indent=2))
|
|
41
|
+
return
|
|
42
|
+
|
|
43
|
+
_print_metrics(data)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _fetch_from_daemon(port: int) -> dict | None:
|
|
47
|
+
"""Try GET /api/v1/metrics from the running daemon."""
|
|
48
|
+
import urllib.request
|
|
49
|
+
import urllib.error
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
url = f"http://127.0.0.1:{port}/api/v1/metrics"
|
|
53
|
+
with urllib.request.urlopen(url, timeout=2) as resp:
|
|
54
|
+
return json.loads(resp.read().decode("utf-8"))
|
|
55
|
+
except Exception:
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _read_from_file(home: Path, date_str: str | None) -> dict | None:
|
|
60
|
+
"""Read a daily metrics JSON from disk."""
|
|
61
|
+
if date_str is None:
|
|
62
|
+
date_str = datetime.now(timezone.utc).strftime("%Y-%m-%d")
|
|
63
|
+
path = home / "metrics" / "daily" / f"{date_str}.json"
|
|
64
|
+
if not path.exists():
|
|
65
|
+
return None
|
|
66
|
+
try:
|
|
67
|
+
return json.loads(path.read_text(encoding="utf-8"))
|
|
68
|
+
except Exception:
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _print_metrics(data: dict) -> None:
|
|
73
|
+
"""Render metrics in a rich panel."""
|
|
74
|
+
from rich.panel import Panel
|
|
75
|
+
from rich.table import Table
|
|
76
|
+
|
|
77
|
+
date = data.get("date", "?")
|
|
78
|
+
msgs = data.get("messages_processed", 0)
|
|
79
|
+
responses = data.get("responses_sent", 0)
|
|
80
|
+
errors = data.get("errors", 0)
|
|
81
|
+
rt = data.get("response_time_ms", {})
|
|
82
|
+
|
|
83
|
+
error_color = "red" if errors > 0 else "green"
|
|
84
|
+
rt_str = (
|
|
85
|
+
f"min={rt.get('min', 0):.0f}ms "
|
|
86
|
+
f"avg={rt.get('avg', 0):.0f}ms "
|
|
87
|
+
f"p99={rt.get('p99', 0):.0f}ms "
|
|
88
|
+
f"max={rt.get('max', 0):.0f}ms "
|
|
89
|
+
f"n={rt.get('count', 0)}"
|
|
90
|
+
) if rt.get("count", 0) > 0 else "[dim]no data[/]"
|
|
91
|
+
|
|
92
|
+
summary_lines = [
|
|
93
|
+
f"[bold]Date:[/] {date}",
|
|
94
|
+
f"[bold]Messages:[/] {msgs}",
|
|
95
|
+
f"[bold]Responses:[/] {responses}",
|
|
96
|
+
f"[bold]Errors:[/] [{error_color}]{errors}[/]",
|
|
97
|
+
f"[bold]Response time:[/] {rt_str}",
|
|
98
|
+
]
|
|
99
|
+
console.print()
|
|
100
|
+
console.print(Panel(
|
|
101
|
+
"\n".join(summary_lines),
|
|
102
|
+
title="[cyan]Consciousness Metrics[/]",
|
|
103
|
+
border_style="cyan",
|
|
104
|
+
))
|
|
105
|
+
|
|
106
|
+
# Backend usage table
|
|
107
|
+
backend_usage = data.get("backend_usage", {})
|
|
108
|
+
if backend_usage:
|
|
109
|
+
table = Table(title="Backend Usage", box=None, padding=(0, 2))
|
|
110
|
+
table.add_column("Backend", style="bold")
|
|
111
|
+
table.add_column("Requests", justify="right")
|
|
112
|
+
for bk, count in sorted(backend_usage.items(), key=lambda x: -x[1]):
|
|
113
|
+
table.add_row(bk, str(count))
|
|
114
|
+
console.print(table)
|
|
115
|
+
|
|
116
|
+
# Tier usage table
|
|
117
|
+
tier_usage = data.get("tier_usage", {})
|
|
118
|
+
if tier_usage:
|
|
119
|
+
table = Table(title="Tier Usage", box=None, padding=(0, 2))
|
|
120
|
+
table.add_column("Tier", style="bold")
|
|
121
|
+
table.add_column("Requests", justify="right")
|
|
122
|
+
for tier, count in sorted(tier_usage.items(), key=lambda x: -x[1]):
|
|
123
|
+
table.add_row(tier, str(count))
|
|
124
|
+
console.print(table)
|
|
125
|
+
|
|
126
|
+
# Per-peer message counts (top 10)
|
|
127
|
+
peer_counts = data.get("messages_per_peer", {})
|
|
128
|
+
if peer_counts:
|
|
129
|
+
table = Table(title="Messages per Peer (top 10)", box=None, padding=(0, 2))
|
|
130
|
+
table.add_column("Peer", style="bold cyan")
|
|
131
|
+
table.add_column("Messages", justify="right")
|
|
132
|
+
for peer, count in sorted(peer_counts.items(), key=lambda x: -x[1])[:10]:
|
|
133
|
+
table.add_row(peer, str(count))
|
|
134
|
+
console.print(table)
|
|
135
|
+
|
|
136
|
+
console.print()
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""CLI commands for multi-agent migration."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from ._common import console
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def register_migrate_commands(cli: click.Group) -> None:
|
|
11
|
+
"""Register the 'migrate' command group."""
|
|
12
|
+
cli.add_command(migrate_cmd)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@click.command("migrate")
|
|
16
|
+
@click.option("--agent", required=True, help="Agent name (e.g., opus)")
|
|
17
|
+
@click.option("--dry-run", is_flag=True, help="Show what would happen without moving")
|
|
18
|
+
@click.option("--root", default="~/.skcapstone", help="skcapstone root directory")
|
|
19
|
+
def migrate_cmd(agent: str, dry_run: bool, root: str) -> None:
|
|
20
|
+
"""Migrate to multi-agent household layout.
|
|
21
|
+
|
|
22
|
+
Moves per-agent data into agents/{name}/ and creates symlinks
|
|
23
|
+
at old paths for backward compatibility.
|
|
24
|
+
"""
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
|
|
27
|
+
from ..migrate_multi_agent import migrate_to_multi_agent
|
|
28
|
+
|
|
29
|
+
results = migrate_to_multi_agent(
|
|
30
|
+
root=Path(root),
|
|
31
|
+
agent_name=agent,
|
|
32
|
+
dry_run=dry_run,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
if dry_run:
|
|
36
|
+
console.print("[yellow]DRY RUN[/] — no files will be moved\n")
|
|
37
|
+
|
|
38
|
+
if results["moved"]:
|
|
39
|
+
console.print("[green]Moved:[/]")
|
|
40
|
+
for item in results["moved"]:
|
|
41
|
+
console.print(f" {item}")
|
|
42
|
+
|
|
43
|
+
if results["symlinks_created"]:
|
|
44
|
+
console.print("\n[cyan]Symlinks created:[/]")
|
|
45
|
+
for link in results["symlinks_created"]:
|
|
46
|
+
console.print(f" {link}")
|
|
47
|
+
|
|
48
|
+
if results["skipped"]:
|
|
49
|
+
console.print("\n[dim]Skipped:[/]")
|
|
50
|
+
for item in results["skipped"]:
|
|
51
|
+
console.print(f" {item}")
|
|
52
|
+
|
|
53
|
+
if results["errors"]:
|
|
54
|
+
console.print("\n[red]Errors:[/]")
|
|
55
|
+
for err in results["errors"]:
|
|
56
|
+
console.print(f" {err}")
|
|
57
|
+
|
|
58
|
+
if not results["moved"] and not dry_run:
|
|
59
|
+
console.print("[dim]Nothing to migrate.[/]")
|
|
60
|
+
elif not dry_run:
|
|
61
|
+
console.print(f"\n[green]Migration complete.[/] Agent home: {results['agent_home']}")
|
|
62
|
+
console.print(f"[dim]Use: SKCAPSTONE_AGENT={agent} skcapstone status[/]")
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""Mood command: display the agent's current emotional state."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
|
|
10
|
+
from ._common import AGENT_HOME, console
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def register_mood_commands(main: click.Group) -> None:
|
|
14
|
+
"""Register the ``skcapstone mood`` command."""
|
|
15
|
+
|
|
16
|
+
@main.command("mood")
|
|
17
|
+
@click.option(
|
|
18
|
+
"--home", default=AGENT_HOME, type=click.Path(),
|
|
19
|
+
help="Agent home directory.",
|
|
20
|
+
)
|
|
21
|
+
@click.option("--json-out", is_flag=True, help="Output raw JSON.")
|
|
22
|
+
@click.option(
|
|
23
|
+
"--update", is_flag=True,
|
|
24
|
+
help="Refresh from persisted daily metrics before displaying.",
|
|
25
|
+
)
|
|
26
|
+
def mood_cmd(home: str, json_out: bool, update: bool) -> None:
|
|
27
|
+
"""Show the agent's current emotional state.
|
|
28
|
+
|
|
29
|
+
Mood is derived from three interaction pattern factors:
|
|
30
|
+
|
|
31
|
+
\b
|
|
32
|
+
Success — response success rate (happy / frustrated)
|
|
33
|
+
Social — message frequency (social / isolated)
|
|
34
|
+
Stress — error rate (calm / stressed)
|
|
35
|
+
"""
|
|
36
|
+
from ..mood import MoodTracker
|
|
37
|
+
|
|
38
|
+
home_path = Path(home).expanduser()
|
|
39
|
+
tracker = MoodTracker(home=home_path)
|
|
40
|
+
|
|
41
|
+
if update:
|
|
42
|
+
try:
|
|
43
|
+
from ..metrics import ConsciousnessMetrics
|
|
44
|
+
metrics = ConsciousnessMetrics(home=home_path, persist_interval=0)
|
|
45
|
+
tracker.update_from_metrics(metrics)
|
|
46
|
+
except Exception as exc:
|
|
47
|
+
if not json_out:
|
|
48
|
+
console.print(f"[yellow]Warning: could not refresh metrics: {exc}[/]")
|
|
49
|
+
|
|
50
|
+
snap = tracker.snapshot
|
|
51
|
+
|
|
52
|
+
if json_out:
|
|
53
|
+
click.echo(snap.model_dump_json(indent=2))
|
|
54
|
+
return
|
|
55
|
+
|
|
56
|
+
_print_mood(snap)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# ---------------------------------------------------------------------------
|
|
60
|
+
# Rich rendering
|
|
61
|
+
# ---------------------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
_SUMMARY_COLORS: dict[str, str] = {
|
|
64
|
+
"flourishing": "bright_green",
|
|
65
|
+
"happy": "green",
|
|
66
|
+
"content": "cyan",
|
|
67
|
+
"neutral": "white",
|
|
68
|
+
"isolated": "dim",
|
|
69
|
+
"tense": "yellow",
|
|
70
|
+
"frustrated": "red",
|
|
71
|
+
"stressed": "bold red",
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
_SUCCESS_COLORS: dict[str, str] = {
|
|
75
|
+
"happy": "green",
|
|
76
|
+
"content": "cyan",
|
|
77
|
+
"neutral": "white",
|
|
78
|
+
"frustrated": "red",
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
_SOCIAL_COLORS: dict[str, str] = {
|
|
82
|
+
"social": "bright_cyan",
|
|
83
|
+
"active": "green",
|
|
84
|
+
"quiet": "white",
|
|
85
|
+
"isolated": "dim",
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
_STRESS_COLORS: dict[str, str] = {
|
|
89
|
+
"calm": "green",
|
|
90
|
+
"relaxed": "cyan",
|
|
91
|
+
"tense": "yellow",
|
|
92
|
+
"stressed": "bold red",
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _print_mood(snap) -> None:
|
|
97
|
+
"""Render mood snapshot with Rich.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
snap: :class:`~skcapstone.mood.MoodSnapshot` to display.
|
|
101
|
+
"""
|
|
102
|
+
from rich.panel import Panel
|
|
103
|
+
from rich.table import Table
|
|
104
|
+
|
|
105
|
+
border_color = _SUMMARY_COLORS.get(snap.summary, "white")
|
|
106
|
+
summary_color = _SUMMARY_COLORS.get(snap.summary, "white")
|
|
107
|
+
|
|
108
|
+
header = f"[{summary_color}]{snap.summary.upper()}[/]"
|
|
109
|
+
if snap.updated_at:
|
|
110
|
+
header += f" [dim]{snap.updated_at[:19]}Z[/]"
|
|
111
|
+
|
|
112
|
+
table = Table(show_header=True, header_style="bold", box=None, padding=(0, 2))
|
|
113
|
+
table.add_column("Dimension", style="bold", min_width=10)
|
|
114
|
+
table.add_column("State", min_width=12)
|
|
115
|
+
table.add_column("Detail", style="dim")
|
|
116
|
+
|
|
117
|
+
# Success row
|
|
118
|
+
sc = _SUCCESS_COLORS.get(snap.success_mood, "white")
|
|
119
|
+
table.add_row(
|
|
120
|
+
"Success",
|
|
121
|
+
f"[{sc}]{snap.success_mood}[/]",
|
|
122
|
+
f"{snap.responses_sent}/{snap.messages_processed} responses ({snap.success_rate:.0%})",
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
# Social row
|
|
126
|
+
soc = _SOCIAL_COLORS.get(snap.social_mood, "white")
|
|
127
|
+
table.add_row(
|
|
128
|
+
"Social",
|
|
129
|
+
f"[{soc}]{snap.social_mood}[/]",
|
|
130
|
+
f"{snap.messages_per_hour:.1f} msgs/hr (window: {snap.window_hours}h)",
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
# Stress row
|
|
134
|
+
stc = _STRESS_COLORS.get(snap.stress_mood, "white")
|
|
135
|
+
table.add_row(
|
|
136
|
+
"Stress",
|
|
137
|
+
f"[{stc}]{snap.stress_mood}[/]",
|
|
138
|
+
f"{snap.errors} errors ({snap.error_rate:.0%} error rate)",
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
console.print()
|
|
142
|
+
console.print(Panel(header, title="[bold]Agent Mood[/]", border_style=border_color))
|
|
143
|
+
console.print(table)
|
|
144
|
+
console.print()
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"""FUSE mount commands: start, stop, status."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import click
|
|
10
|
+
|
|
11
|
+
from ._common import AGENT_HOME, console
|
|
12
|
+
|
|
13
|
+
from rich.panel import Panel
|
|
14
|
+
from rich.table import Table
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def register_mount_commands(main: click.Group) -> None:
|
|
18
|
+
"""Register the mount command group."""
|
|
19
|
+
|
|
20
|
+
@main.group()
|
|
21
|
+
def mount():
|
|
22
|
+
"""Sovereign FUSE filesystem — browse agent data as files.
|
|
23
|
+
|
|
24
|
+
\b
|
|
25
|
+
Mount the sovereign virtual filesystem to access memories, identity,
|
|
26
|
+
inbox, outbox, and coordination tasks as ordinary files.
|
|
27
|
+
|
|
28
|
+
\b
|
|
29
|
+
Mount: skcapstone mount start
|
|
30
|
+
Debug: skcapstone mount start --foreground
|
|
31
|
+
Unmount: skcapstone mount stop
|
|
32
|
+
Status: skcapstone mount status
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
@mount.command("start")
|
|
36
|
+
@click.option(
|
|
37
|
+
"--mount-point",
|
|
38
|
+
default="~/.sovereign/mount",
|
|
39
|
+
type=click.Path(),
|
|
40
|
+
help="Directory to mount the sovereign filesystem at.",
|
|
41
|
+
show_default=True,
|
|
42
|
+
)
|
|
43
|
+
@click.option(
|
|
44
|
+
"--home",
|
|
45
|
+
default=AGENT_HOME,
|
|
46
|
+
type=click.Path(),
|
|
47
|
+
help="Agent home directory.",
|
|
48
|
+
)
|
|
49
|
+
@click.option(
|
|
50
|
+
"--foreground",
|
|
51
|
+
"foreground",
|
|
52
|
+
is_flag=True,
|
|
53
|
+
default=False,
|
|
54
|
+
help="Run in foreground (blocks; useful for debugging).",
|
|
55
|
+
)
|
|
56
|
+
def mount_start(mount_point: str, home: str, foreground: bool):
|
|
57
|
+
"""Mount the sovereign virtual filesystem.
|
|
58
|
+
|
|
59
|
+
Exposes memories, identity, inbox, outbox, and coordination tasks
|
|
60
|
+
as a read-mostly POSIX filesystem via FUSE.
|
|
61
|
+
|
|
62
|
+
\b
|
|
63
|
+
Requires: pip install skcapstone[fuse]
|
|
64
|
+
|
|
65
|
+
\b
|
|
66
|
+
Examples:
|
|
67
|
+
|
|
68
|
+
skcapstone mount start
|
|
69
|
+
|
|
70
|
+
skcapstone mount start --mount-point /mnt/sovereign
|
|
71
|
+
|
|
72
|
+
skcapstone mount start --foreground
|
|
73
|
+
"""
|
|
74
|
+
from ..fuse_mount import FUSEDaemon
|
|
75
|
+
|
|
76
|
+
mount_path = Path(mount_point).expanduser()
|
|
77
|
+
home_path = Path(home).expanduser()
|
|
78
|
+
|
|
79
|
+
daemon = FUSEDaemon(mount_point=mount_path, agent_home=home_path)
|
|
80
|
+
|
|
81
|
+
if foreground:
|
|
82
|
+
console.print(
|
|
83
|
+
f"[bold cyan]Mounting sovereign filesystem at [white]{mount_path}[/] "
|
|
84
|
+
f"[dim](foreground — Ctrl-C to unmount)[/]"
|
|
85
|
+
)
|
|
86
|
+
else:
|
|
87
|
+
console.print(
|
|
88
|
+
f"[bold cyan]Mounting sovereign filesystem at [white]{mount_path}[/] ..."
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
ok = daemon.start(foreground=foreground)
|
|
92
|
+
|
|
93
|
+
if ok and not foreground:
|
|
94
|
+
console.print(f"[green]Mounted.[/] [dim]Unmount with: skcapstone mount stop[/]")
|
|
95
|
+
elif not ok:
|
|
96
|
+
console.print("[bold red]Mount failed.[/] Check logs or try --foreground for details.")
|
|
97
|
+
sys.exit(1)
|
|
98
|
+
|
|
99
|
+
@mount.command("stop")
|
|
100
|
+
@click.option(
|
|
101
|
+
"--mount-point",
|
|
102
|
+
default="~/.sovereign/mount",
|
|
103
|
+
type=click.Path(),
|
|
104
|
+
help="Mount point to unmount.",
|
|
105
|
+
show_default=True,
|
|
106
|
+
)
|
|
107
|
+
@click.option(
|
|
108
|
+
"--home",
|
|
109
|
+
default=AGENT_HOME,
|
|
110
|
+
type=click.Path(),
|
|
111
|
+
help="Agent home directory.",
|
|
112
|
+
)
|
|
113
|
+
def mount_stop(mount_point: str, home: str):
|
|
114
|
+
"""Unmount the sovereign virtual filesystem.
|
|
115
|
+
|
|
116
|
+
\b
|
|
117
|
+
Example:
|
|
118
|
+
|
|
119
|
+
skcapstone mount stop
|
|
120
|
+
"""
|
|
121
|
+
from ..fuse_mount import FUSEDaemon
|
|
122
|
+
|
|
123
|
+
mount_path = Path(mount_point).expanduser()
|
|
124
|
+
home_path = Path(home).expanduser()
|
|
125
|
+
|
|
126
|
+
daemon = FUSEDaemon(mount_point=mount_path, agent_home=home_path)
|
|
127
|
+
console.print(f"[bold cyan]Unmounting {mount_path} ...[/]")
|
|
128
|
+
|
|
129
|
+
ok = daemon.stop()
|
|
130
|
+
if ok:
|
|
131
|
+
console.print("[green]Unmounted.[/]")
|
|
132
|
+
else:
|
|
133
|
+
console.print(
|
|
134
|
+
"[bold red]Unmount failed.[/] "
|
|
135
|
+
f"[dim]Try manually: fusermount -u {mount_path}[/]"
|
|
136
|
+
)
|
|
137
|
+
sys.exit(1)
|
|
138
|
+
|
|
139
|
+
@mount.command("status")
|
|
140
|
+
@click.option(
|
|
141
|
+
"--mount-point",
|
|
142
|
+
default="~/.sovereign/mount",
|
|
143
|
+
type=click.Path(),
|
|
144
|
+
help="Mount point to check.",
|
|
145
|
+
show_default=True,
|
|
146
|
+
)
|
|
147
|
+
@click.option(
|
|
148
|
+
"--home",
|
|
149
|
+
default=AGENT_HOME,
|
|
150
|
+
type=click.Path(),
|
|
151
|
+
help="Agent home directory.",
|
|
152
|
+
)
|
|
153
|
+
@click.option("--json", "as_json", is_flag=True, help="Output as JSON.")
|
|
154
|
+
def mount_status(mount_point: str, home: str, as_json: bool):
|
|
155
|
+
"""Show the status of the sovereign FUSE filesystem.
|
|
156
|
+
|
|
157
|
+
\b
|
|
158
|
+
Example:
|
|
159
|
+
|
|
160
|
+
skcapstone mount status
|
|
161
|
+
|
|
162
|
+
skcapstone mount status --json
|
|
163
|
+
"""
|
|
164
|
+
from ..fuse_mount import FUSEDaemon
|
|
165
|
+
|
|
166
|
+
mount_path = Path(mount_point).expanduser()
|
|
167
|
+
home_path = Path(home).expanduser()
|
|
168
|
+
|
|
169
|
+
daemon = FUSEDaemon(mount_point=mount_path, agent_home=home_path)
|
|
170
|
+
status = daemon.status()
|
|
171
|
+
|
|
172
|
+
if as_json:
|
|
173
|
+
click.echo(json.dumps(status, indent=2))
|
|
174
|
+
return
|
|
175
|
+
|
|
176
|
+
mounted = status.get("mounted", False)
|
|
177
|
+
icon = "[bold green]MOUNTED[/]" if mounted else "[bold red]NOT MOUNTED[/]"
|
|
178
|
+
pid = status.get("pid")
|
|
179
|
+
updated = status.get("updated_at", "—")
|
|
180
|
+
|
|
181
|
+
table = Table(show_header=False, box=None, padding=(0, 2))
|
|
182
|
+
table.add_column("Key", style="dim")
|
|
183
|
+
table.add_column("Value")
|
|
184
|
+
|
|
185
|
+
table.add_row("Status", icon)
|
|
186
|
+
table.add_row("Mount point", str(status.get("mount_point", "")))
|
|
187
|
+
table.add_row("Agent home", str(status.get("agent_home", "")))
|
|
188
|
+
table.add_row("PID", str(pid) if pid else "[dim]—[/]")
|
|
189
|
+
table.add_row("Last updated", updated or "[dim]—[/]")
|
|
190
|
+
|
|
191
|
+
console.print()
|
|
192
|
+
console.print(Panel(table, title="[bold]Sovereign Filesystem Status[/]", border_style="cyan"))
|
|
193
|
+
console.print()
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""Notification commands: skcapstone notify test / skcapstone notifications."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
|
|
10
|
+
from ._common import AGENT_HOME, console
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def register_notify_commands(main: click.Group) -> None:
|
|
14
|
+
"""Register the notify command group and the notifications alias."""
|
|
15
|
+
|
|
16
|
+
@main.group()
|
|
17
|
+
def notify():
|
|
18
|
+
"""Desktop notification management."""
|
|
19
|
+
|
|
20
|
+
@notify.command("test")
|
|
21
|
+
@click.option("--title", default="SKCapstone", show_default=True, help="Notification title.")
|
|
22
|
+
@click.option("--body", default="Test notification from SKCapstone.", show_default=True, help="Notification body.")
|
|
23
|
+
@click.option(
|
|
24
|
+
"--urgency",
|
|
25
|
+
default="normal",
|
|
26
|
+
show_default=True,
|
|
27
|
+
type=click.Choice(["low", "normal", "critical"]),
|
|
28
|
+
help="Notification urgency.",
|
|
29
|
+
)
|
|
30
|
+
@click.option(
|
|
31
|
+
"--dashboard-url",
|
|
32
|
+
default="http://localhost:7778",
|
|
33
|
+
show_default=True,
|
|
34
|
+
help="Dashboard URL opened when the 'Open Dashboard' action is clicked.",
|
|
35
|
+
)
|
|
36
|
+
def notify_test(title: str, body: str, urgency: str, dashboard_url: str):
|
|
37
|
+
"""Send a test desktop notification with click-action buttons."""
|
|
38
|
+
from ..notifications import NotificationManager
|
|
39
|
+
|
|
40
|
+
# Bypass debounce for the test command by using a fresh manager
|
|
41
|
+
mgr = NotificationManager(debounce_seconds=0, dashboard_url=dashboard_url)
|
|
42
|
+
dispatched = mgr.notify(title, body, urgency)
|
|
43
|
+
if dispatched:
|
|
44
|
+
console.print(f"\n [green]Notification dispatched[/]: {title!r} / {body!r}\n")
|
|
45
|
+
console.print(
|
|
46
|
+
" [dim]Click 'Open Dashboard' to open the dashboard, "
|
|
47
|
+
"or 'Open SKChat' to launch skchat watch.[/]\n"
|
|
48
|
+
)
|
|
49
|
+
else:
|
|
50
|
+
console.print(
|
|
51
|
+
"\n [yellow]Notification not dispatched[/] — no supported notification "
|
|
52
|
+
"system found (gi.repository.Notify / notify-send / osascript).\n"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# -----------------------------------------------------------------------
|
|
56
|
+
# Top-level alias: skcapstone notifications
|
|
57
|
+
# -----------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
@main.command("notifications")
|
|
60
|
+
@click.option("--home", default=AGENT_HOME, type=click.Path(), help="Agent home directory.")
|
|
61
|
+
@click.option("--limit", "-n", default=20, show_default=True, help="Max results to show.")
|
|
62
|
+
@click.option("--json-out", is_flag=True, help="Output as JSON.")
|
|
63
|
+
def notifications_cmd(home: str, limit: int, json_out: bool):
|
|
64
|
+
"""Show notification history (memories tagged 'notification')."""
|
|
65
|
+
import json as _json
|
|
66
|
+
|
|
67
|
+
from ..memory_engine import search as mem_search
|
|
68
|
+
|
|
69
|
+
home_path = Path(home).expanduser()
|
|
70
|
+
if not home_path.exists():
|
|
71
|
+
if json_out:
|
|
72
|
+
print(_json.dumps([]))
|
|
73
|
+
return
|
|
74
|
+
console.print("[bold red]No agent found.[/] Run skcapstone init first.")
|
|
75
|
+
sys.exit(1)
|
|
76
|
+
|
|
77
|
+
results = mem_search(home=home_path, query="notification", tags=["notification"], limit=limit)
|
|
78
|
+
|
|
79
|
+
if json_out:
|
|
80
|
+
output = [
|
|
81
|
+
{
|
|
82
|
+
"id": entry.memory_id,
|
|
83
|
+
"content": entry.content,
|
|
84
|
+
"tags": entry.tags,
|
|
85
|
+
"layer": entry.layer.value,
|
|
86
|
+
"created_at": entry.created_at.isoformat() if entry.created_at else None,
|
|
87
|
+
}
|
|
88
|
+
for entry in results
|
|
89
|
+
]
|
|
90
|
+
print(_json.dumps(output))
|
|
91
|
+
return
|
|
92
|
+
|
|
93
|
+
if not results:
|
|
94
|
+
console.print("\n [dim]No notification history found.[/]\n")
|
|
95
|
+
return
|
|
96
|
+
|
|
97
|
+
from rich.table import Table
|
|
98
|
+
|
|
99
|
+
console.print(f"\n [bold]{len(results)}[/] notification{'s' if len(results) != 1 else ''} in history:\n")
|
|
100
|
+
|
|
101
|
+
table = Table(show_header=True, header_style="bold", box=None, padding=(0, 2))
|
|
102
|
+
table.add_column("ID", style="cyan", max_width=14)
|
|
103
|
+
table.add_column("When", style="dim", max_width=22)
|
|
104
|
+
table.add_column("Content", max_width=70)
|
|
105
|
+
|
|
106
|
+
for entry in results:
|
|
107
|
+
created = entry.created_at.strftime("%Y-%m-%d %H:%M:%S") if entry.created_at else "—"
|
|
108
|
+
preview = entry.content[:100] + ("..." if len(entry.content) > 100 else "")
|
|
109
|
+
table.add_row(entry.memory_id, created, preview)
|
|
110
|
+
|
|
111
|
+
console.print(table)
|
|
112
|
+
console.print()
|