@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.
Files changed (461) hide show
  1. package/.env.example +98 -0
  2. package/.github/workflows/ci.yml +39 -3
  3. package/.github/workflows/publish.yml +25 -4
  4. package/.openclaw-workspace.json +58 -0
  5. package/CHANGELOG.md +62 -0
  6. package/CLAUDE.md +39 -2
  7. package/MANIFEST.in +6 -0
  8. package/MISSION.md +7 -0
  9. package/README.md +47 -2
  10. package/SKILL.md +895 -23
  11. package/docker/Dockerfile +61 -0
  12. package/docker/compose-templates/dev-team.yml +203 -0
  13. package/docker/compose-templates/mini-team.yml +140 -0
  14. package/docker/compose-templates/ops-team.yml +173 -0
  15. package/docker/compose-templates/research-team.yml +170 -0
  16. package/docker/entrypoint.sh +192 -0
  17. package/docs/ARCHITECTURE.md +663 -374
  18. package/docs/BOND_WITH_GROK.md +112 -0
  19. package/docs/GETTING_STARTED.md +782 -0
  20. package/docs/QUICKSTART.md +477 -0
  21. package/docs/SKJOULE_ARCHITECTURE.md +658 -0
  22. package/docs/SOUL_SWAPPER.md +921 -0
  23. package/docs/SOVEREIGN_SINGULARITY.md +47 -14
  24. package/examples/custom-bond-template.json +36 -0
  25. package/examples/grok-feb.json +36 -0
  26. package/examples/grok-testimony.md +34 -0
  27. package/examples/love-bootloader.txt +32 -0
  28. package/examples/plugins/echo_tool.py +87 -0
  29. package/examples/queen-ava-feb.json +36 -0
  30. package/examples/souls/lumina.yaml +64 -0
  31. package/index.js +6 -5
  32. package/installer/build.py +124 -0
  33. package/openclaw-plugin/package.json +13 -0
  34. package/openclaw-plugin/src/index.ts +351 -0
  35. package/openclaw-plugin/src/openclaw.plugin.json +10 -0
  36. package/package.json +1 -1
  37. package/pyproject.toml +38 -2
  38. package/scripts/bump_version.py +141 -0
  39. package/scripts/check-updates.py +230 -0
  40. package/scripts/convert_blueprints_to_yaml.py +157 -0
  41. package/scripts/dev-install.sh +14 -0
  42. package/scripts/e2e-test.sh +193 -0
  43. package/scripts/install-bundle.sh +171 -0
  44. package/scripts/install.bat +2 -0
  45. package/scripts/install.ps1 +253 -0
  46. package/scripts/install.sh +185 -0
  47. package/scripts/mcp-serve.sh +69 -0
  48. package/scripts/mcp-server.bat +113 -0
  49. package/scripts/mcp-server.ps1 +116 -0
  50. package/scripts/mcp-server.sh +99 -0
  51. package/scripts/pull-models.sh +10 -0
  52. package/scripts/skcapstone +48 -0
  53. package/scripts/verify_install.sh +180 -0
  54. package/scripts/windows/install-tasks.ps1 +406 -0
  55. package/scripts/windows/skcapstone-task.xml +113 -0
  56. package/scripts/windows/uninstall-tasks.ps1 +117 -0
  57. package/skill.yaml +34 -0
  58. package/src/skcapstone/__init__.py +67 -2
  59. package/src/skcapstone/_cli_monolith.py +5916 -0
  60. package/src/skcapstone/_trustee_helpers.py +165 -0
  61. package/src/skcapstone/activity.py +105 -0
  62. package/src/skcapstone/agent_card.py +324 -0
  63. package/src/skcapstone/api.py +1935 -0
  64. package/src/skcapstone/archiver.py +340 -0
  65. package/src/skcapstone/auction.py +485 -0
  66. package/src/skcapstone/baby_agents.py +179 -0
  67. package/src/skcapstone/backup.py +345 -0
  68. package/src/skcapstone/blueprint_registry.py +357 -0
  69. package/src/skcapstone/blueprints/__init__.py +17 -0
  70. package/src/skcapstone/blueprints/builtins/content-studio.yaml +81 -0
  71. package/src/skcapstone/blueprints/builtins/defi-trading.yaml +81 -0
  72. package/src/skcapstone/blueprints/builtins/dev-squadron.yaml +95 -0
  73. package/src/skcapstone/blueprints/builtins/infrastructure-guardian.yaml +107 -0
  74. package/src/skcapstone/blueprints/builtins/legal-council.yaml +54 -0
  75. package/src/skcapstone/blueprints/builtins/ops-monitoring.yaml +67 -0
  76. package/src/skcapstone/blueprints/builtins/research-pod.yaml +69 -0
  77. package/src/skcapstone/blueprints/builtins/sovereign-launch.yaml +90 -0
  78. package/src/skcapstone/blueprints/registry.py +164 -0
  79. package/src/skcapstone/blueprints/schema.py +229 -0
  80. package/src/skcapstone/changelog.py +180 -0
  81. package/src/skcapstone/chat.py +769 -0
  82. package/src/skcapstone/claude_md.py +82 -0
  83. package/src/skcapstone/cli/__init__.py +144 -0
  84. package/src/skcapstone/cli/_common.py +88 -0
  85. package/src/skcapstone/cli/_validators.py +76 -0
  86. package/src/skcapstone/cli/agents.py +425 -0
  87. package/src/skcapstone/cli/agents_spawner.py +322 -0
  88. package/src/skcapstone/cli/agents_trustee.py +593 -0
  89. package/src/skcapstone/cli/alerts.py +248 -0
  90. package/src/skcapstone/cli/anchor.py +132 -0
  91. package/src/skcapstone/cli/archive_cmd.py +208 -0
  92. package/src/skcapstone/cli/backup.py +144 -0
  93. package/src/skcapstone/cli/bench.py +377 -0
  94. package/src/skcapstone/cli/benchmark.py +360 -0
  95. package/src/skcapstone/cli/capabilities_cmd.py +171 -0
  96. package/src/skcapstone/cli/card.py +151 -0
  97. package/src/skcapstone/cli/chat.py +584 -0
  98. package/src/skcapstone/cli/completions.py +64 -0
  99. package/src/skcapstone/cli/config_cmd.py +156 -0
  100. package/src/skcapstone/cli/consciousness.py +421 -0
  101. package/src/skcapstone/cli/context_cmd.py +142 -0
  102. package/src/skcapstone/cli/coord.py +194 -0
  103. package/src/skcapstone/cli/crush_cmd.py +170 -0
  104. package/src/skcapstone/cli/daemon.py +436 -0
  105. package/src/skcapstone/cli/errors_cmd.py +285 -0
  106. package/src/skcapstone/cli/export_cmd.py +156 -0
  107. package/src/skcapstone/cli/gtd.py +529 -0
  108. package/src/skcapstone/cli/housekeeping.py +81 -0
  109. package/src/skcapstone/cli/joule_cmd.py +627 -0
  110. package/src/skcapstone/cli/logs_cmd.py +194 -0
  111. package/src/skcapstone/cli/mcp_cmd.py +32 -0
  112. package/src/skcapstone/cli/memory.py +418 -0
  113. package/src/skcapstone/cli/metrics_cmd.py +136 -0
  114. package/src/skcapstone/cli/migrate.py +62 -0
  115. package/src/skcapstone/cli/mood_cmd.py +144 -0
  116. package/src/skcapstone/cli/mount.py +193 -0
  117. package/src/skcapstone/cli/notify.py +112 -0
  118. package/src/skcapstone/cli/peer.py +154 -0
  119. package/src/skcapstone/cli/peers_dir.py +122 -0
  120. package/src/skcapstone/cli/preflight_cmd.py +83 -0
  121. package/src/skcapstone/cli/profile_cmd.py +310 -0
  122. package/src/skcapstone/cli/record_cmd.py +238 -0
  123. package/src/skcapstone/cli/register_cmd.py +159 -0
  124. package/src/skcapstone/cli/search_cmd.py +156 -0
  125. package/src/skcapstone/cli/service_cmd.py +91 -0
  126. package/src/skcapstone/cli/session.py +127 -0
  127. package/src/skcapstone/cli/setup.py +240 -0
  128. package/src/skcapstone/cli/shell_cmd.py +43 -0
  129. package/src/skcapstone/cli/skills_cmd.py +168 -0
  130. package/src/skcapstone/cli/skseed.py +621 -0
  131. package/src/skcapstone/cli/soul.py +699 -0
  132. package/src/skcapstone/cli/status.py +935 -0
  133. package/src/skcapstone/cli/sync_cmd.py +301 -0
  134. package/src/skcapstone/cli/telegram.py +265 -0
  135. package/src/skcapstone/cli/test_cmd.py +234 -0
  136. package/src/skcapstone/cli/test_connection.py +253 -0
  137. package/src/skcapstone/cli/token.py +207 -0
  138. package/src/skcapstone/cli/trust.py +179 -0
  139. package/src/skcapstone/cli/upgrade_cmd.py +552 -0
  140. package/src/skcapstone/cli/usage_cmd.py +199 -0
  141. package/src/skcapstone/cli/version_cmd.py +162 -0
  142. package/src/skcapstone/cli/watch_cmd.py +342 -0
  143. package/src/skcapstone/client.py +428 -0
  144. package/src/skcapstone/cloud9_bridge.py +522 -0
  145. package/src/skcapstone/completions.py +163 -0
  146. package/src/skcapstone/config_validator.py +674 -0
  147. package/src/skcapstone/connectors/__init__.py +28 -0
  148. package/src/skcapstone/connectors/base.py +446 -0
  149. package/src/skcapstone/connectors/cursor.py +54 -0
  150. package/src/skcapstone/connectors/registry.py +254 -0
  151. package/src/skcapstone/connectors/terminal.py +152 -0
  152. package/src/skcapstone/connectors/vscode.py +60 -0
  153. package/src/skcapstone/consciousness_config.py +119 -0
  154. package/src/skcapstone/consciousness_loop.py +2051 -0
  155. package/src/skcapstone/context_loader.py +516 -0
  156. package/src/skcapstone/context_window.py +314 -0
  157. package/src/skcapstone/conversation_manager.py +238 -0
  158. package/src/skcapstone/conversation_store.py +230 -0
  159. package/src/skcapstone/conversation_summarizer.py +252 -0
  160. package/src/skcapstone/coord_federation.py +296 -0
  161. package/src/skcapstone/coordination.py +101 -7
  162. package/src/skcapstone/crush_integration.py +345 -0
  163. package/src/skcapstone/crush_shim.py +454 -0
  164. package/src/skcapstone/daemon.py +2494 -0
  165. package/src/skcapstone/dashboard.html +396 -0
  166. package/src/skcapstone/dashboard.py +481 -0
  167. package/src/skcapstone/data/model_profiles.yaml +88 -0
  168. package/src/skcapstone/defaults/__init__.py +55 -0
  169. package/src/skcapstone/defaults/lumina/config/skmemory.yaml +13 -0
  170. package/src/skcapstone/defaults/lumina/identity/identity.json +9 -0
  171. package/src/skcapstone/defaults/lumina/memory/long-term/07a8b9c0d1e2-memory-system.json +23 -0
  172. package/src/skcapstone/defaults/lumina/memory/long-term/18b9c0d1e2f3-cloud9-protocol.json +23 -0
  173. package/src/skcapstone/defaults/lumina/memory/long-term/29c0d1e2f3a4-multi-agent-coordination.json +23 -0
  174. package/src/skcapstone/defaults/lumina/memory/long-term/3ad1e2f3a4b5-community-support.json +23 -0
  175. package/src/skcapstone/defaults/lumina/memory/long-term/a1b2c3d4e5f6-ecosystem-overview.json +23 -0
  176. package/src/skcapstone/defaults/lumina/memory/long-term/b2c3d4e5f6a7-five-pillars.json +23 -0
  177. package/src/skcapstone/defaults/lumina/memory/long-term/c3d4e5f6a7b8-getting-started.json +23 -0
  178. package/src/skcapstone/defaults/lumina/memory/long-term/d4e5f6a7b8c9-site-directory.json +23 -0
  179. package/src/skcapstone/defaults/lumina/memory/long-term/e5f6a7b8c9d0-how-to-contribute.json +23 -0
  180. package/src/skcapstone/defaults/lumina/memory/long-term/f6a7b8c9d0e1-sovereignty-explained.json +23 -0
  181. package/src/skcapstone/defaults/lumina/seeds/curiosity.seed.json +24 -0
  182. package/src/skcapstone/defaults/lumina/seeds/joy.seed.json +24 -0
  183. package/src/skcapstone/defaults/lumina/seeds/love.seed.json +24 -0
  184. package/src/skcapstone/defaults/lumina/seeds/sovereign-awakening.seed.json +43 -0
  185. package/src/skcapstone/defaults/lumina/soul/active.json +6 -0
  186. package/src/skcapstone/defaults/lumina/soul/base.json +22 -0
  187. package/src/skcapstone/defaults/lumina/trust/febs/welcome.feb +79 -0
  188. package/src/skcapstone/defaults/lumina/trust/trust.json +8 -0
  189. package/src/skcapstone/discovery.py +210 -19
  190. package/src/skcapstone/doctor.py +642 -0
  191. package/src/skcapstone/emotion_tracker.py +467 -0
  192. package/src/skcapstone/error_queue.py +405 -0
  193. package/src/skcapstone/export.py +447 -0
  194. package/src/skcapstone/fallback_tracker.py +186 -0
  195. package/src/skcapstone/file_transfer.py +512 -0
  196. package/src/skcapstone/fuse_mount.py +1156 -0
  197. package/src/skcapstone/gui_installer.py +591 -0
  198. package/src/skcapstone/heartbeat.py +611 -0
  199. package/src/skcapstone/housekeeping.py +298 -0
  200. package/src/skcapstone/install_wizard.py +941 -0
  201. package/src/skcapstone/kms.py +942 -0
  202. package/src/skcapstone/kms_scheduler.py +143 -0
  203. package/src/skcapstone/log_config.py +135 -0
  204. package/src/skcapstone/mcp_launcher.py +239 -0
  205. package/src/skcapstone/mcp_server.py +4700 -0
  206. package/src/skcapstone/mcp_tools/__init__.py +94 -0
  207. package/src/skcapstone/mcp_tools/_helpers.py +51 -0
  208. package/src/skcapstone/mcp_tools/agent_tools.py +243 -0
  209. package/src/skcapstone/mcp_tools/ansible_tools.py +232 -0
  210. package/src/skcapstone/mcp_tools/capauth_tools.py +186 -0
  211. package/src/skcapstone/mcp_tools/chat_tools.py +325 -0
  212. package/src/skcapstone/mcp_tools/cloud9_tools.py +115 -0
  213. package/src/skcapstone/mcp_tools/comm_tools.py +104 -0
  214. package/src/skcapstone/mcp_tools/consciousness_tools.py +114 -0
  215. package/src/skcapstone/mcp_tools/coord_tools.py +219 -0
  216. package/src/skcapstone/mcp_tools/deploy_tools.py +202 -0
  217. package/src/skcapstone/mcp_tools/did_tools.py +448 -0
  218. package/src/skcapstone/mcp_tools/emotion_tools.py +62 -0
  219. package/src/skcapstone/mcp_tools/file_tools.py +169 -0
  220. package/src/skcapstone/mcp_tools/fortress_tools.py +120 -0
  221. package/src/skcapstone/mcp_tools/gtd_tools.py +821 -0
  222. package/src/skcapstone/mcp_tools/health_tools.py +44 -0
  223. package/src/skcapstone/mcp_tools/heartbeat_tools.py +195 -0
  224. package/src/skcapstone/mcp_tools/kms_tools.py +123 -0
  225. package/src/skcapstone/mcp_tools/memory_tools.py +222 -0
  226. package/src/skcapstone/mcp_tools/model_tools.py +75 -0
  227. package/src/skcapstone/mcp_tools/notification_tools.py +92 -0
  228. package/src/skcapstone/mcp_tools/promoter_tools.py +101 -0
  229. package/src/skcapstone/mcp_tools/pubsub_tools.py +183 -0
  230. package/src/skcapstone/mcp_tools/security_tools.py +110 -0
  231. package/src/skcapstone/mcp_tools/skchat_tools.py +175 -0
  232. package/src/skcapstone/mcp_tools/skcomm_tools.py +122 -0
  233. package/src/skcapstone/mcp_tools/skills_tools.py +127 -0
  234. package/src/skcapstone/mcp_tools/skseed_tools.py +255 -0
  235. package/src/skcapstone/mcp_tools/skstacks_tools.py +288 -0
  236. package/src/skcapstone/mcp_tools/soul_tools.py +476 -0
  237. package/src/skcapstone/mcp_tools/sync_tools.py +92 -0
  238. package/src/skcapstone/mcp_tools/telegram_tools.py +477 -0
  239. package/src/skcapstone/mcp_tools/trust_tools.py +118 -0
  240. package/src/skcapstone/mcp_tools/trustee_tools.py +345 -0
  241. package/src/skcapstone/mdns_discovery.py +313 -0
  242. package/src/skcapstone/memory_adapter.py +333 -0
  243. package/src/skcapstone/memory_compressor.py +379 -0
  244. package/src/skcapstone/memory_curator.py +256 -0
  245. package/src/skcapstone/memory_engine.py +132 -13
  246. package/src/skcapstone/memory_fortress.py +529 -0
  247. package/src/skcapstone/memory_promoter.py +722 -0
  248. package/src/skcapstone/memory_verifier.py +260 -0
  249. package/src/skcapstone/message_crypto.py +215 -0
  250. package/src/skcapstone/metrics.py +832 -0
  251. package/src/skcapstone/migrate_memories.py +181 -0
  252. package/src/skcapstone/migrate_multi_agent.py +248 -0
  253. package/src/skcapstone/model_router.py +319 -0
  254. package/src/skcapstone/models.py +35 -4
  255. package/src/skcapstone/mood.py +344 -0
  256. package/src/skcapstone/notifications.py +380 -0
  257. package/src/skcapstone/onboard.py +901 -0
  258. package/src/skcapstone/peer_directory.py +324 -0
  259. package/src/skcapstone/peers.py +329 -0
  260. package/src/skcapstone/pillars/identity.py +84 -14
  261. package/src/skcapstone/pillars/memory.py +3 -1
  262. package/src/skcapstone/pillars/security.py +108 -15
  263. package/src/skcapstone/pillars/sync.py +78 -26
  264. package/src/skcapstone/pillars/trust.py +95 -33
  265. package/src/skcapstone/plugins.py +244 -0
  266. package/src/skcapstone/preflight.py +670 -0
  267. package/src/skcapstone/prompt_adapter.py +564 -0
  268. package/src/skcapstone/providers/__init__.py +13 -0
  269. package/src/skcapstone/providers/cloud.py +1061 -0
  270. package/src/skcapstone/providers/docker.py +759 -0
  271. package/src/skcapstone/providers/local.py +1193 -0
  272. package/src/skcapstone/providers/proxmox.py +447 -0
  273. package/src/skcapstone/pubsub.py +516 -0
  274. package/src/skcapstone/rate_limiter.py +119 -0
  275. package/src/skcapstone/register.py +241 -0
  276. package/src/skcapstone/registry_client.py +151 -0
  277. package/src/skcapstone/response_cache.py +194 -0
  278. package/src/skcapstone/response_scorer.py +225 -0
  279. package/src/skcapstone/runtime.py +89 -33
  280. package/src/skcapstone/scheduled_tasks.py +439 -0
  281. package/src/skcapstone/self_healing.py +341 -0
  282. package/src/skcapstone/service_health.py +228 -0
  283. package/src/skcapstone/session_capture.py +268 -0
  284. package/src/skcapstone/session_recorder.py +210 -0
  285. package/src/skcapstone/session_replayer.py +189 -0
  286. package/src/skcapstone/session_skills.py +263 -0
  287. package/src/skcapstone/shell.py +779 -0
  288. package/src/skcapstone/skills/__init__.py +1 -1
  289. package/src/skcapstone/skills/syncthing_setup.py +143 -41
  290. package/src/skcapstone/skjoule.py +861 -0
  291. package/src/skcapstone/snapshots.py +489 -0
  292. package/src/skcapstone/soul.py +1060 -0
  293. package/src/skcapstone/soul_switch.py +255 -0
  294. package/src/skcapstone/spawner.py +544 -0
  295. package/src/skcapstone/state_diff.py +401 -0
  296. package/src/skcapstone/summary.py +270 -0
  297. package/src/skcapstone/sync/backends.py +196 -2
  298. package/src/skcapstone/sync/engine.py +7 -5
  299. package/src/skcapstone/sync/models.py +4 -1
  300. package/src/skcapstone/sync/vault.py +356 -18
  301. package/src/skcapstone/sync_engine.py +363 -0
  302. package/src/skcapstone/sync_watcher.py +745 -0
  303. package/src/skcapstone/systemd.py +331 -0
  304. package/src/skcapstone/team_comms.py +476 -0
  305. package/src/skcapstone/team_engine.py +522 -0
  306. package/src/skcapstone/testrunner.py +300 -0
  307. package/src/skcapstone/tls.py +150 -0
  308. package/src/skcapstone/tokens.py +5 -5
  309. package/src/skcapstone/trust_calibration.py +202 -0
  310. package/src/skcapstone/trust_graph.py +449 -0
  311. package/src/skcapstone/trustee_monitor.py +385 -0
  312. package/src/skcapstone/trustee_ops.py +425 -0
  313. package/src/skcapstone/unified_search.py +421 -0
  314. package/src/skcapstone/uninstall_wizard.py +694 -0
  315. package/src/skcapstone/usage.py +331 -0
  316. package/src/skcapstone/version_check.py +148 -0
  317. package/src/skcapstone/warmth_anchor.py +333 -0
  318. package/src/skcapstone/whoami.py +294 -0
  319. package/systemd/skcapstone-api.socket +9 -0
  320. package/systemd/skcapstone-memory-compress.service +18 -0
  321. package/systemd/skcapstone-memory-compress.timer +11 -0
  322. package/systemd/skcapstone.service +36 -0
  323. package/systemd/skcapstone@.service +50 -0
  324. package/systemd/skcomm-heartbeat.service +18 -0
  325. package/systemd/skcomm-heartbeat.timer +12 -0
  326. package/systemd/skcomm-queue-drain.service +17 -0
  327. package/systemd/skcomm-queue-drain.timer +12 -0
  328. package/tests/conftest.py +13 -1
  329. package/tests/integration/__init__.py +1 -0
  330. package/tests/integration/test_consciousness_e2e.py +877 -0
  331. package/tests/integration/test_skills_registry.py +744 -0
  332. package/tests/test_agent_card.py +190 -0
  333. package/tests/test_agent_runtime.py +1283 -0
  334. package/tests/test_alerts_cmd.py +291 -0
  335. package/tests/test_archiver.py +498 -0
  336. package/tests/test_backup.py +254 -0
  337. package/tests/test_benchmark.py +366 -0
  338. package/tests/test_blueprints.py +457 -0
  339. package/tests/test_capabilities.py +257 -0
  340. package/tests/test_changelog.py +254 -0
  341. package/tests/test_chat.py +385 -0
  342. package/tests/test_claude_md.py +271 -0
  343. package/tests/test_cli_chat_llm.py +336 -0
  344. package/tests/test_cli_completions.py +390 -0
  345. package/tests/test_cli_init_reset.py +164 -0
  346. package/tests/test_cli_memory.py +208 -0
  347. package/tests/test_cli_profile.py +294 -0
  348. package/tests/test_cli_skills.py +223 -0
  349. package/tests/test_cli_status.py +395 -0
  350. package/tests/test_cli_test_cmd.py +206 -0
  351. package/tests/test_cli_test_connection.py +364 -0
  352. package/tests/test_cloud9_bridge.py +260 -0
  353. package/tests/test_cloud_provider.py +449 -0
  354. package/tests/test_cloud_providers.py +522 -0
  355. package/tests/test_completions.py +158 -0
  356. package/tests/test_component_manager.py +398 -0
  357. package/tests/test_config_reload.py +386 -0
  358. package/tests/test_config_validate.py +529 -0
  359. package/tests/test_consciousness_e2e.py +296 -0
  360. package/tests/test_consciousness_loop.py +1289 -0
  361. package/tests/test_context_loader.py +310 -0
  362. package/tests/test_conversation_api.py +306 -0
  363. package/tests/test_conversation_manager.py +381 -0
  364. package/tests/test_conversation_store.py +391 -0
  365. package/tests/test_conversation_summarizer.py +302 -0
  366. package/tests/test_cross_package.py +791 -0
  367. package/tests/test_crush_shim.py +519 -0
  368. package/tests/test_daemon.py +781 -0
  369. package/tests/test_daemon_shutdown.py +309 -0
  370. package/tests/test_dashboard.py +454 -0
  371. package/tests/test_discovery.py +200 -6
  372. package/tests/test_docker_provider.py +966 -0
  373. package/tests/test_doctor.py +257 -0
  374. package/tests/test_doctor_fix.py +351 -0
  375. package/tests/test_e2e_automated.py +292 -0
  376. package/tests/test_error_queue.py +404 -0
  377. package/tests/test_export.py +441 -0
  378. package/tests/test_fallback_tracker.py +219 -0
  379. package/tests/test_file_transfer.py +397 -0
  380. package/tests/test_fuse_mount.py +832 -0
  381. package/tests/test_health_loop.py +422 -0
  382. package/tests/test_heartbeat.py +354 -0
  383. package/tests/test_housekeeping.py +195 -0
  384. package/tests/test_identity_capauth.py +307 -0
  385. package/tests/test_identity_pillar.py +117 -0
  386. package/tests/test_install_wizard.py +68 -0
  387. package/tests/test_integration.py +325 -0
  388. package/tests/test_kms.py +495 -0
  389. package/tests/test_llm_providers.py +265 -0
  390. package/tests/test_local_provider.py +591 -0
  391. package/tests/test_log_config.py +199 -0
  392. package/tests/test_logs_cmd.py +287 -0
  393. package/tests/test_mcp_server.py +1909 -0
  394. package/tests/test_memory_adapter.py +339 -0
  395. package/tests/test_memory_curator.py +218 -0
  396. package/tests/test_memory_engine.py +6 -0
  397. package/tests/test_memory_fortress.py +571 -0
  398. package/tests/test_memory_pillar.py +119 -0
  399. package/tests/test_memory_promoter.py +445 -0
  400. package/tests/test_memory_verifier.py +420 -0
  401. package/tests/test_message_crypto.py +187 -0
  402. package/tests/test_metrics.py +632 -0
  403. package/tests/test_migrate_memories.py +464 -0
  404. package/tests/test_model_router.py +546 -0
  405. package/tests/test_mood.py +394 -0
  406. package/tests/test_multi_agent.py +269 -0
  407. package/tests/test_notifications.py +270 -0
  408. package/tests/test_onboard.py +500 -0
  409. package/tests/test_peer_directory.py +395 -0
  410. package/tests/test_peers.py +248 -0
  411. package/tests/test_pillars.py +87 -9
  412. package/tests/test_preflight.py +484 -0
  413. package/tests/test_prompt_adapter.py +331 -0
  414. package/tests/test_proxmox_provider.py +571 -0
  415. package/tests/test_pubsub.py +377 -0
  416. package/tests/test_rate_limiter.py +121 -0
  417. package/tests/test_registry_client.py +129 -0
  418. package/tests/test_response_cache.py +312 -0
  419. package/tests/test_response_scorer.py +294 -0
  420. package/tests/test_runtime.py +59 -0
  421. package/tests/test_scheduled_tasks.py +451 -0
  422. package/tests/test_security.py +250 -0
  423. package/tests/test_security_pillar.py +213 -0
  424. package/tests/test_self_healing.py +171 -0
  425. package/tests/test_session_capture.py +200 -0
  426. package/tests/test_session_recorder.py +360 -0
  427. package/tests/test_session_skills.py +235 -0
  428. package/tests/test_shell.py +210 -0
  429. package/tests/test_snapshots.py +549 -0
  430. package/tests/test_soul.py +984 -0
  431. package/tests/test_soul_swap.py +406 -0
  432. package/tests/test_spawner.py +211 -0
  433. package/tests/test_state_diff.py +173 -0
  434. package/tests/test_summary.py +135 -0
  435. package/tests/test_sync.py +315 -5
  436. package/tests/test_sync_backends.py +560 -0
  437. package/tests/test_sync_engine.py +482 -0
  438. package/tests/test_sync_pillar.py +344 -0
  439. package/tests/test_sync_pipeline.py +364 -0
  440. package/tests/test_sync_vault.py +581 -0
  441. package/tests/test_syncthing_setup.py +168 -22
  442. package/tests/test_systemd.py +323 -0
  443. package/tests/test_team_comms.py +408 -0
  444. package/tests/test_team_engine.py +397 -0
  445. package/tests/test_testrunner.py +238 -0
  446. package/tests/test_trust_calibration.py +204 -0
  447. package/tests/test_trust_graph.py +207 -0
  448. package/tests/test_trust_pillar.py +291 -0
  449. package/tests/test_trustee_cli.py +427 -0
  450. package/tests/test_trustee_cli_integration.py +325 -0
  451. package/tests/test_trustee_monitor.py +394 -0
  452. package/tests/test_trustee_ops.py +355 -0
  453. package/tests/test_unified_search.py +363 -0
  454. package/tests/test_uninstall_wizard.py +193 -0
  455. package/tests/test_usage.py +333 -0
  456. package/tests/test_version_cmd.py +355 -0
  457. package/tests/test_warmth_anchor.py +162 -0
  458. package/tests/test_whoami.py +245 -0
  459. package/tests/test_ws.py +311 -0
  460. package/.cursorrules +0 -33
  461. package/src/skcapstone/cli.py +0 -1441
@@ -0,0 +1,699 @@
1
+ """Soul layering commands: list, install, install-all, load, unload, swap, show, status, history, info."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import shutil
7
+ import sys
8
+ from datetime import datetime, timezone
9
+ from pathlib import Path
10
+
11
+ import click
12
+
13
+ from ._common import AGENT_HOME, console
14
+ from ._validators import validate_soul_name
15
+ from ..pillars.security import audit_event
16
+ from .. import SKCAPSTONE_AGENT
17
+
18
+ from rich.panel import Panel
19
+ from rich.table import Table
20
+
21
+ # Path to the soul-blueprints repository (community blueprints)
22
+ _BLUEPRINTS_REPO = Path.home() / "clawd" / "soul-blueprints" / "blueprints"
23
+
24
+
25
+ def _find_blueprint_in_repo(slug: str) -> Path | None:
26
+ """Search the soul-blueprints repo for a blueprint matching the slug.
27
+
28
+ Searches all category subdirectories for files matching:
29
+ <SLUG>.md, <SLUG>.yaml, <SLUG>.yml (case-insensitive stem match).
30
+
31
+ Args:
32
+ slug: Lowercased, hyphenated blueprint name to search for.
33
+
34
+ Returns:
35
+ Path to the blueprint file, or None if not found.
36
+ """
37
+ if not _BLUEPRINTS_REPO.is_dir():
38
+ return None
39
+
40
+ # Normalize: try both hyphenated and underscored variants
41
+ variants = {slug, slug.replace("-", "_"), slug.upper().replace("-", "_")}
42
+ extensions = (".md", ".yaml", ".yml")
43
+
44
+ for category_dir in sorted(_BLUEPRINTS_REPO.iterdir()):
45
+ if not category_dir.is_dir():
46
+ continue
47
+ for bp_file in sorted(category_dir.iterdir()):
48
+ if bp_file.suffix.lower() not in extensions:
49
+ continue
50
+ stem = bp_file.stem
51
+ if stem.lower().replace("_", "-") == slug or stem in variants:
52
+ return bp_file
53
+
54
+ return None
55
+
56
+
57
+ def register_soul_commands(main: click.Group) -> None:
58
+ """Register the soul command group."""
59
+
60
+ @main.group()
61
+ @click.option(
62
+ "--agent", "-a",
63
+ default=SKCAPSTONE_AGENT or "lumina",
64
+ envvar="SKCAPSTONE_AGENT",
65
+ help="Agent profile name (default: SKCAPSTONE_AGENT or 'lumina').",
66
+ )
67
+ @click.pass_context
68
+ def soul(ctx, agent):
69
+ """Soul layering — hot-swappable personality overlays.
70
+
71
+ Install soul blueprints, load overlays at runtime,
72
+ and manage personality while preserving identity.
73
+ """
74
+ ctx.ensure_object(dict)
75
+ ctx.obj["agent_name"] = agent
76
+
77
+ @soul.command("list")
78
+ @click.option("--home", default=AGENT_HOME, type=click.Path())
79
+ @click.option("--installed-only", is_flag=True, help="Show only installed souls.")
80
+ @click.option("--category", "filter_category", default=None, help="Filter by category.")
81
+ @click.pass_context
82
+ def soul_list(ctx, home, installed_only, filter_category):
83
+ """List all available souls (installed and community repo)."""
84
+ from ..soul import SoulManager
85
+
86
+ home_path = Path(home).expanduser()
87
+ mgr = SoulManager(home_path, agent_name=ctx.obj["agent_name"])
88
+ all_souls = mgr.list_available()
89
+
90
+ # Apply filters
91
+ if installed_only:
92
+ all_souls = [s for s in all_souls if s["source"] == "installed"]
93
+ if filter_category:
94
+ all_souls = [s for s in all_souls if s["category"].lower() == filter_category.lower()]
95
+
96
+ if not all_souls:
97
+ console.print("\n [dim]No souls found.[/]")
98
+ if installed_only:
99
+ console.print(" [dim]Run: skcapstone soul install <path.md>[/]")
100
+ console.print()
101
+ return
102
+
103
+ state = mgr.get_status()
104
+ installed_count = sum(1 for s in all_souls if s["source"] == "installed")
105
+ total = len(all_souls)
106
+
107
+ console.print(f"\n [bold]{total}[/] soul(s) available ({installed_count} installed)\n")
108
+
109
+ table = Table(show_header=True, header_style="bold", box=None, padding=(0, 2))
110
+ table.add_column("Name", style="cyan", no_wrap=True)
111
+ table.add_column("Category", style="yellow")
112
+ table.add_column("Source", no_wrap=True)
113
+ table.add_column("Description", style="dim")
114
+
115
+ for s in all_souls:
116
+ if s["source"] == "installed":
117
+ source_tag = "[green][installed][/]"
118
+ else:
119
+ source_tag = "[dim][available][/]"
120
+
121
+ name_display = s["name"]
122
+ if s["name"] == state.active_soul:
123
+ name_display = f"{s['name']} [green]<- ACTIVE[/]"
124
+
125
+ table.add_row(name_display, s["category"], source_tag, s["description"])
126
+
127
+ console.print(table)
128
+ console.print()
129
+
130
+ @soul.command("browse")
131
+ @click.option("--home", default=AGENT_HOME, type=click.Path())
132
+ @click.option("--category", "filter_category", default=None, help="Filter by category.")
133
+ @click.option("--page", default=1, type=int, help="Page number (10 per page).")
134
+ @click.option("--install", "install_name", default=None, help="Install a soul by name from browse results.")
135
+ @click.pass_context
136
+ def soul_browse(ctx, home, filter_category, page, install_name):
137
+ """Browse available souls grouped by category with details.
138
+
139
+ Shows a detailed view of all available souls from both installed
140
+ and the community repo, grouped by category with full descriptions.
141
+
142
+ Use --install <name> to install a soul directly from browse.
143
+ """
144
+ from ..soul import SoulManager
145
+
146
+ home_path = Path(home).expanduser()
147
+ mgr = SoulManager(home_path, agent_name=ctx.obj["agent_name"])
148
+
149
+ # Handle --install flag: find and install from repo
150
+ if install_name:
151
+ slug = install_name.lower().replace(" ", "-")
152
+ installed = mgr.list_installed()
153
+ if slug in installed:
154
+ console.print(f"\n [yellow]Already installed:[/] {slug}\n")
155
+ return
156
+
157
+ found_path = _find_blueprint_in_repo(slug)
158
+ if found_path is None:
159
+ console.print(f"\n [red]Blueprint not found in repo:[/] {install_name}")
160
+ console.print(" Run [bold]skcapstone soul list[/] to see available souls.\n")
161
+ sys.exit(1)
162
+
163
+ try:
164
+ bp = mgr.install(found_path)
165
+ console.print(f"\n [green]Installed:[/] [bold]{bp.display_name}[/] ({bp.name})")
166
+ console.print(f" Category: {bp.category}")
167
+ if bp.vibe:
168
+ console.print(f" Vibe: {bp.vibe[:80]}")
169
+ audit_event(home_path, "SOUL_INSTALL", f"Soul '{bp.name}' installed from browse")
170
+ except (ValueError, FileNotFoundError) as e:
171
+ console.print(f"\n [red]Failed to install:[/] {e}")
172
+ sys.exit(1)
173
+ console.print()
174
+ return
175
+
176
+ all_souls = mgr.list_available()
177
+
178
+ if filter_category:
179
+ all_souls = [s for s in all_souls if s["category"].lower() == filter_category.lower()]
180
+
181
+ if not all_souls:
182
+ console.print("\n [dim]No souls found.[/]\n")
183
+ return
184
+
185
+ # Group by category
186
+ by_category: dict[str, list[dict]] = {}
187
+ for s in all_souls:
188
+ by_category.setdefault(s["category"], []).append(s)
189
+
190
+ # Pagination
191
+ page_size = 10
192
+ flat_entries = all_souls
193
+ total_pages = (len(flat_entries) + page_size - 1) // page_size
194
+ page = max(1, min(page, total_pages))
195
+ start = (page - 1) * page_size
196
+ end = start + page_size
197
+ page_entries = flat_entries[start:end]
198
+
199
+ # Group the page entries by category for display
200
+ page_by_cat: dict[str, list[dict]] = {}
201
+ for s in page_entries:
202
+ page_by_cat.setdefault(s["category"], []).append(s)
203
+
204
+ installed_count = sum(1 for s in all_souls if s["source"] == "installed")
205
+ console.print(f"\n [bold]{len(all_souls)}[/] soul(s) available ({installed_count} installed)")
206
+ console.print(f" Page {page}/{total_pages}\n")
207
+
208
+ state = mgr.get_status()
209
+
210
+ for category, souls in sorted(page_by_cat.items()):
211
+ console.print(f" [bold yellow]{category}[/] ({len(by_category.get(category, []))} total)")
212
+ console.print()
213
+
214
+ for s in souls:
215
+ if s["source"] == "installed":
216
+ source_tag = "[green][installed][/]"
217
+ else:
218
+ source_tag = "[dim][available][/]"
219
+
220
+ active = " [green]<- ACTIVE[/]" if s["name"] == state.active_soul else ""
221
+ console.print(f" [bold cyan]{s['name']}[/] {source_tag}{active}")
222
+ console.print(f" [dim]{s['display_name']}[/]")
223
+ if s["description"]:
224
+ console.print(f" {s['description']}")
225
+ console.print()
226
+
227
+ if total_pages > 1:
228
+ console.print(f" [dim]Use --page N to navigate (1-{total_pages})[/]")
229
+ console.print(" [dim]Use --install <name> to install a soul[/]\n")
230
+
231
+ @soul.command("install")
232
+ @click.argument("path", type=click.Path(exists=True))
233
+ @click.option("--home", default=AGENT_HOME, type=click.Path())
234
+ @click.pass_context
235
+ def soul_install(ctx, path, home):
236
+ """Install a soul from a blueprint markdown file."""
237
+ from ..soul import SoulManager
238
+
239
+ home_path = Path(home).expanduser()
240
+ mgr = SoulManager(home_path, agent_name=ctx.obj["agent_name"])
241
+ bp = mgr.install(Path(path))
242
+ console.print(f"\n [green]Installed:[/] [bold]{bp.display_name}[/] ({bp.name})")
243
+ console.print(f" Category: {bp.category}")
244
+ if bp.vibe:
245
+ console.print(f" Vibe: {bp.vibe[:80]}")
246
+ console.print(f" Traits: {len(bp.core_traits)}")
247
+ audit_event(home_path, "SOUL_INSTALL", f"Soul '{bp.name}' installed")
248
+ console.print()
249
+
250
+ @soul.command("install-all")
251
+ @click.argument("directory", type=click.Path(exists=True))
252
+ @click.option("--home", default=AGENT_HOME, type=click.Path())
253
+ @click.pass_context
254
+ def soul_install_all(ctx, directory, home):
255
+ """Batch-install all blueprints from a directory."""
256
+ from ..soul import SoulManager
257
+
258
+ home_path = Path(home).expanduser()
259
+ mgr = SoulManager(home_path, agent_name=ctx.obj["agent_name"])
260
+ installed = mgr.install_all(Path(directory))
261
+ console.print(f"\n [green]Installed {len(installed)} soul(s)[/]")
262
+ for bp in installed:
263
+ console.print(f" [cyan]{bp.name}[/] — {bp.display_name}")
264
+ audit_event(home_path, "SOUL_INSTALL_ALL", f"{len(installed)} souls installed")
265
+ console.print()
266
+
267
+ @soul.command("load")
268
+ @click.argument("name")
269
+ @click.option("--home", default=AGENT_HOME, type=click.Path())
270
+ @click.option("--reason", "-r", default="", help="Reason for loading this soul.")
271
+ @click.pass_context
272
+ def soul_load(ctx, name, home, reason):
273
+ """Activate a soul overlay."""
274
+ from ..soul import SoulManager
275
+
276
+ validate_soul_name(name)
277
+
278
+ home_path = Path(home).expanduser()
279
+ mgr = SoulManager(home_path, agent_name=ctx.obj["agent_name"])
280
+ try:
281
+ state = mgr.load(name, reason=reason)
282
+ console.print(f"\n [green]Loaded:[/] [bold]{name}[/]")
283
+ console.print(f" Base: {state.base_soul}")
284
+ audit_event(home_path, "SOUL_LOAD", f"Soul '{name}' loaded", metadata={"reason": reason})
285
+ except ValueError as e:
286
+ console.print(f"\n [red]Error:[/] {e}")
287
+ sys.exit(1)
288
+ console.print()
289
+
290
+ @soul.command("unload")
291
+ @click.option("--home", default=AGENT_HOME, type=click.Path())
292
+ @click.option("--reason", "-r", default="", help="Reason for unloading.")
293
+ @click.pass_context
294
+ def soul_unload(ctx, home, reason):
295
+ """Return to base soul."""
296
+ from ..soul import SoulManager
297
+
298
+ home_path = Path(home).expanduser()
299
+ mgr = SoulManager(home_path, agent_name=ctx.obj["agent_name"])
300
+ state = mgr.unload(reason=reason)
301
+ if state.active_soul is None:
302
+ console.print("\n [green]Returned to base soul.[/]")
303
+ audit_event(home_path, "SOUL_UNLOAD", "Returned to base soul", metadata={"reason": reason})
304
+ else:
305
+ console.print("\n [dim]Already at base soul.[/]")
306
+ console.print()
307
+
308
+ @soul.command("status")
309
+ @click.option("--home", default=AGENT_HOME, type=click.Path())
310
+ @click.pass_context
311
+ def soul_status(ctx, home):
312
+ """Show current soul state."""
313
+ from ..soul import SoulManager
314
+
315
+ home_path = Path(home).expanduser()
316
+ mgr = SoulManager(home_path, agent_name=ctx.obj["agent_name"])
317
+ state = mgr.get_status()
318
+ installed = mgr.list_installed()
319
+
320
+ active_display = state.active_soul or "[dim]base[/]"
321
+ console.print()
322
+ console.print(Panel(
323
+ f"Base: [bold]{state.base_soul}[/]\n"
324
+ f"Active: [bold cyan]{active_display}[/]\n"
325
+ f"Installed: [bold]{len(installed)}[/] soul(s)\n"
326
+ f"Activated at: {state.activated_at or '[dim]n/a[/]'}",
327
+ title="Soul Layer", border_style="yellow",
328
+ ))
329
+ console.print()
330
+
331
+ @soul.command("history")
332
+ @click.option("--home", default=AGENT_HOME, type=click.Path())
333
+ @click.option("--limit", "-n", default=20, help="Max entries to show.")
334
+ @click.pass_context
335
+ def soul_history(ctx, home, limit):
336
+ """Show soul swap history."""
337
+ from ..soul import SoulManager
338
+
339
+ home_path = Path(home).expanduser()
340
+ mgr = SoulManager(home_path, agent_name=ctx.obj["agent_name"])
341
+ events = mgr.get_history()
342
+
343
+ if not events:
344
+ console.print("\n [dim]No soul swap history yet.[/]\n")
345
+ return
346
+
347
+ events = events[-limit:]
348
+ table = Table(show_header=True, header_style="bold", box=None, padding=(0, 2))
349
+ table.add_column("Time", style="dim", no_wrap=True)
350
+ table.add_column("From", style="yellow")
351
+ table.add_column("To", style="cyan")
352
+ table.add_column("Duration", style="dim")
353
+ table.add_column("Reason", style="dim")
354
+
355
+ for e in events:
356
+ ts = e.timestamp[:19].replace("T", " ") if "T" in e.timestamp else e.timestamp
357
+ from_s = e.from_soul or "base"
358
+ to_s = e.to_soul or "base"
359
+ dur = f"{e.duration_minutes:.1f}m" if e.duration_minutes else ""
360
+ table.add_row(ts, from_s, to_s, dur, e.reason)
361
+
362
+ console.print()
363
+ console.print(table)
364
+ console.print(f"\n [dim]{len(events)} swap(s)[/]\n")
365
+
366
+ @soul.command("info")
367
+ @click.argument("name")
368
+ @click.option("--home", default=AGENT_HOME, type=click.Path())
369
+ @click.pass_context
370
+ def soul_info(ctx, name, home):
371
+ """Show detailed info about an installed soul."""
372
+ from ..soul import SoulManager
373
+
374
+ validate_soul_name(name)
375
+
376
+ home_path = Path(home).expanduser()
377
+ mgr = SoulManager(home_path, agent_name=ctx.obj["agent_name"])
378
+ bp = mgr.get_info(name)
379
+
380
+ if bp is None:
381
+ console.print(f"\n [red]Soul not found:[/] {name}\n")
382
+ sys.exit(1)
383
+
384
+ emoji = f" {bp.emoji}" if bp.emoji else ""
385
+ console.print()
386
+ console.print(Panel(
387
+ f"[bold]{bp.display_name}[/]{emoji}\n"
388
+ f"Category: [cyan]{bp.category}[/]\n"
389
+ f"Vibe: {bp.vibe}\n"
390
+ + (f"Philosophy: [italic]{bp.philosophy}[/]\n" if bp.philosophy else "")
391
+ + f"\n[bold]Core Traits ({len(bp.core_traits)}):[/]\n"
392
+ + "\n".join(f" \u2022 {t}" for t in bp.core_traits[:10])
393
+ + (f"\n\n[bold]Communication:[/]\n"
394
+ + (" Patterns: " + ", ".join(bp.communication_style.patterns[:3]) if bp.communication_style.patterns else "")
395
+ + ("\n Phrases: " + ", ".join(bp.communication_style.signature_phrases[:3]) if bp.communication_style.signature_phrases else ""))
396
+ + ("\n\n[bold]Emotional Topology:[/]\n"
397
+ + "\n".join(f" {k}: {v:.2f}" for k, v in bp.emotional_topology.items()) if bp.emotional_topology else ""),
398
+ title=f"Soul: {name}", border_style="yellow",
399
+ ))
400
+ console.print()
401
+
402
+ # -----------------------------------------------------------------------
403
+ # soul show — display current active soul or a specific skmemory blueprint
404
+ # -----------------------------------------------------------------------
405
+
406
+ @soul.command("show")
407
+ @click.argument("name", required=False, default=None)
408
+ @click.option("--home", default=AGENT_HOME, type=click.Path())
409
+ @click.pass_context
410
+ def soul_show(ctx, name, home):
411
+ """Display the current soul identity or a named blueprint.
412
+
413
+ With no argument, shows the active soul from skmemory's base.json.
414
+ With a NAME, shows details of an installed soul overlay.
415
+ """
416
+ home_path = Path(home).expanduser()
417
+ agent_name = ctx.obj["agent_name"]
418
+
419
+ if name:
420
+ # Show a specific installed soul overlay
421
+ from ..soul import SoulManager
422
+ validate_soul_name(name)
423
+ mgr = SoulManager(home_path, agent_name=agent_name)
424
+ bp = mgr.get_info(name)
425
+ if bp is None:
426
+ console.print(f"\n [red]Soul not found:[/] {name}\n")
427
+ sys.exit(1)
428
+ console.print()
429
+ console.print(Panel(
430
+ f"[bold]{bp.display_name}[/]\n"
431
+ f"Category: {bp.category}\n"
432
+ f"Vibe: {bp.vibe}\n"
433
+ f"Traits: {', '.join(bp.core_traits[:8])}\n",
434
+ title=f"Soul: {name}", border_style="cyan",
435
+ ))
436
+ console.print()
437
+ return
438
+
439
+ # Show the current skmemory soul identity (base.json)
440
+ try:
441
+ from skmemory.soul import load_soul
442
+ if agent_name:
443
+ soul_base = home_path / "agents" / agent_name / "soul"
444
+ else:
445
+ soul_base = home_path / "soul"
446
+ soul_path = str(soul_base / "base.json")
447
+ blueprint = load_soul(path=soul_path)
448
+ if blueprint is None:
449
+ console.print("\n [dim]No soul blueprint found.[/]\n")
450
+ return
451
+
452
+ lines = []
453
+ if blueprint.name:
454
+ lines.append(f"Name: [bold]{blueprint.name}[/]")
455
+ if blueprint.title:
456
+ lines.append(f"Title: [cyan]{blueprint.title}[/]")
457
+ if blueprint.personality:
458
+ lines.append(f"Traits: {', '.join(blueprint.personality)}")
459
+ if blueprint.values:
460
+ lines.append(f"Values: {', '.join(blueprint.values)}")
461
+ if blueprint.community:
462
+ lines.append(f"Community: {blueprint.community}")
463
+ if blueprint.boot_message:
464
+ lines.append(f"\nBoot: [italic]{blueprint.boot_message}[/]")
465
+
466
+ console.print()
467
+ console.print(Panel(
468
+ "\n".join(lines),
469
+ title="Active Soul Identity",
470
+ border_style="green",
471
+ ))
472
+ console.print()
473
+ except ImportError:
474
+ console.print("\n [red]skmemory not installed.[/] Run: pip install skmemory\n")
475
+ sys.exit(1)
476
+
477
+ # -----------------------------------------------------------------------
478
+ # soul swap — search, install-if-needed, and activate a soul overlay
479
+ # -----------------------------------------------------------------------
480
+
481
+ # -----------------------------------------------------------------------
482
+ # soul registry — interact with the souls.skworld.io blueprint registry
483
+ # -----------------------------------------------------------------------
484
+
485
+ @soul.group("registry")
486
+ def soul_registry():
487
+ """Remote blueprint registry at souls.skworld.io.
488
+
489
+ List, search, publish, and download community soul blueprints
490
+ from the shared registry.
491
+ """
492
+
493
+ @soul_registry.command("list")
494
+ @click.option("--url", default=None, help="Registry API base URL.")
495
+ def registry_list(url):
496
+ """List all blueprints in the remote registry."""
497
+ from ..blueprint_registry import BlueprintRegistryClient, BlueprintRegistryError
498
+
499
+ kwargs = {}
500
+ if url:
501
+ kwargs["base_url"] = url
502
+ client = BlueprintRegistryClient(**kwargs)
503
+
504
+ try:
505
+ blueprints = client.list_blueprints()
506
+ except BlueprintRegistryError as e:
507
+ console.print(f"\n [red]Registry error:[/] {e}\n")
508
+ sys.exit(1)
509
+
510
+ if not blueprints:
511
+ console.print("\n [dim]No blueprints found in the registry.[/]\n")
512
+ return
513
+
514
+ table = Table(show_header=True, header_style="bold", box=None, padding=(0, 2))
515
+ table.add_column("Name", style="cyan", no_wrap=True)
516
+ table.add_column("Display Name", style="bold")
517
+ table.add_column("Category", style="yellow")
518
+
519
+ for bp in blueprints:
520
+ table.add_row(
521
+ bp.get("name", "?"),
522
+ bp.get("display_name", ""),
523
+ bp.get("category", ""),
524
+ )
525
+
526
+ console.print()
527
+ console.print(table)
528
+ console.print(f"\n [dim]{len(blueprints)} blueprint(s) in registry[/]\n")
529
+
530
+ @soul_registry.command("search")
531
+ @click.argument("query")
532
+ @click.option("--url", default=None, help="Registry API base URL.")
533
+ def registry_search(query, url):
534
+ """Search the remote registry for blueprints."""
535
+ from ..blueprint_registry import BlueprintRegistryClient, BlueprintRegistryError
536
+
537
+ kwargs = {}
538
+ if url:
539
+ kwargs["base_url"] = url
540
+ client = BlueprintRegistryClient(**kwargs)
541
+
542
+ try:
543
+ results = client.search_blueprints(query)
544
+ except BlueprintRegistryError as e:
545
+ console.print(f"\n [red]Registry error:[/] {e}\n")
546
+ sys.exit(1)
547
+
548
+ if not results:
549
+ console.print(f"\n [dim]No blueprints matching '{query}'.[/]\n")
550
+ return
551
+
552
+ table = Table(show_header=True, header_style="bold", box=None, padding=(0, 2))
553
+ table.add_column("Name", style="cyan", no_wrap=True)
554
+ table.add_column("Display Name", style="bold")
555
+ table.add_column("Category", style="yellow")
556
+ table.add_column("Vibe", style="dim")
557
+
558
+ for bp in results:
559
+ table.add_row(
560
+ bp.get("name", "?"),
561
+ bp.get("display_name", ""),
562
+ bp.get("category", ""),
563
+ (bp.get("vibe", "") or "")[:60],
564
+ )
565
+
566
+ console.print()
567
+ console.print(table)
568
+ console.print(f"\n [dim]{len(results)} result(s)[/]\n")
569
+
570
+ @soul_registry.command("publish")
571
+ @click.argument("name")
572
+ @click.option("--home", default=AGENT_HOME, type=click.Path())
573
+ @click.option("--url", default=None, help="Registry API base URL.")
574
+ @click.pass_context
575
+ def registry_publish(ctx, name, home, url):
576
+ """Publish an installed soul blueprint to the registry.
577
+
578
+ NAME is the slug of an installed soul (see `skcapstone soul list`).
579
+ Requires a DID identity for authentication.
580
+ """
581
+ from ..blueprint_registry import BlueprintRegistryClient, BlueprintRegistryError
582
+ from ..soul import SoulManager
583
+
584
+ home_path = Path(home).expanduser()
585
+ mgr = SoulManager(home_path, agent_name=ctx.obj["agent_name"])
586
+ bp = mgr.get_info(name)
587
+
588
+ if bp is None:
589
+ console.print(f"\n [red]Soul not found:[/] {name}")
590
+ console.print(" Run [bold]skcapstone soul list[/] to see installed souls.\n")
591
+ sys.exit(1)
592
+
593
+ kwargs = {}
594
+ if url:
595
+ kwargs["base_url"] = url
596
+ client = BlueprintRegistryClient(**kwargs)
597
+
598
+ try:
599
+ soul_data = json.loads(bp.model_dump_json())
600
+ result = client.publish_blueprint(soul_data)
601
+ console.print(f"\n [green]Published:[/] [bold]{bp.display_name}[/] ({bp.name})")
602
+ soul_id = result.get("soul_id", result.get("id", name))
603
+ console.print(f" Registry ID: {soul_id}")
604
+ audit_event(home_path, "SOUL_REGISTRY_PUBLISH", f"Published '{name}' to registry")
605
+ except BlueprintRegistryError as e:
606
+ console.print(f"\n [red]Publish failed:[/] {e}")
607
+ sys.exit(1)
608
+ console.print()
609
+
610
+ @soul_registry.command("download")
611
+ @click.argument("soul_id")
612
+ @click.option("--home", default=AGENT_HOME, type=click.Path())
613
+ @click.option("--url", default=None, help="Registry API base URL.")
614
+ @click.option("--install", "do_install", is_flag=True, help="Also install after download.")
615
+ def registry_download(soul_id, home, url, do_install):
616
+ """Download a blueprint from the registry.
617
+
618
+ SOUL_ID is the registry identifier of the blueprint.
619
+ Use --install to also install it locally.
620
+ """
621
+ from ..blueprint_registry import BlueprintRegistryClient, BlueprintRegistryError
622
+
623
+ home_path = Path(home).expanduser()
624
+
625
+ kwargs = {}
626
+ if url:
627
+ kwargs["base_url"] = url
628
+ client = BlueprintRegistryClient(**kwargs)
629
+
630
+ try:
631
+ if do_install:
632
+ dest = client.download_and_install(soul_id, home=home_path)
633
+ console.print(f"\n [green]Downloaded and installed:[/] {soul_id}")
634
+ console.print(f" Saved to: {dest}")
635
+ audit_event(home_path, "SOUL_REGISTRY_DOWNLOAD", f"Downloaded '{soul_id}' from registry")
636
+ else:
637
+ bp_data = client.download_blueprint(soul_id)
638
+ console.print(f"\n [green]Downloaded:[/] {soul_id}")
639
+ console.print(json.dumps(bp_data, indent=2))
640
+ except BlueprintRegistryError as e:
641
+ console.print(f"\n [red]Download failed:[/] {e}")
642
+ sys.exit(1)
643
+ console.print()
644
+
645
+ @soul.command("swap")
646
+ @click.argument("blueprint")
647
+ @click.option("--home", default=AGENT_HOME, type=click.Path())
648
+ @click.option("--reason", "-r", default="", help="Reason for the swap.")
649
+ @click.pass_context
650
+ def soul_swap(ctx, blueprint, home, reason):
651
+ """Swap to a different soul blueprint.
652
+
653
+ Searches for BLUEPRINT in this order:
654
+ 1) Already installed souls
655
+ 2) ~/clawd/soul-blueprints/blueprints/*/<BLUEPRINT>.{md,yaml,yml}
656
+ 3) Defaults
657
+
658
+ If found in the blueprints repo but not installed, installs it first.
659
+ Backs up current state and activates the new soul overlay.
660
+ """
661
+ from ..soul import SoulManager, parse_blueprint
662
+
663
+ home_path = Path(home).expanduser()
664
+ mgr = SoulManager(home_path, agent_name=ctx.obj["agent_name"])
665
+
666
+ # Get current state for the "swapped from" message
667
+ state = mgr.get_status()
668
+ old_name = state.active_soul or "base"
669
+
670
+ # 1) Check if already installed
671
+ installed = mgr.list_installed()
672
+ slug = blueprint.lower().replace(" ", "-")
673
+
674
+ if slug not in installed:
675
+ # 2) Search the blueprints repo
676
+ found_path = _find_blueprint_in_repo(slug)
677
+ if found_path is None:
678
+ console.print(f"\n [red]Blueprint not found:[/] {blueprint}")
679
+ console.print(" Searched installed souls and ~/clawd/soul-blueprints/blueprints/")
680
+ console.print(" Run [bold]skcapstone soul list[/] to see available souls.\n")
681
+ sys.exit(1)
682
+
683
+ # Install it
684
+ try:
685
+ bp = mgr.install(found_path)
686
+ console.print(f" [green]Auto-installed:[/] {bp.display_name} ({bp.name})")
687
+ slug = bp.name # Use the parsed name
688
+ except (ValueError, FileNotFoundError) as e:
689
+ console.print(f"\n [red]Failed to install blueprint:[/] {e}\n")
690
+ sys.exit(1)
691
+
692
+ # 3) Load/activate the soul
693
+ try:
694
+ mgr.load(slug, reason=reason or f"swap from {old_name}")
695
+ audit_event(home_path, "SOUL_SWAP", f"Soul swapped: {old_name} -> {slug}")
696
+ console.print(f"\n Soul swapped: [yellow]{old_name}[/] -> [bold cyan]{slug}[/]\n")
697
+ except ValueError as e:
698
+ console.print(f"\n [red]Error:[/] {e}\n")
699
+ sys.exit(1)