@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,345 @@
1
+ """Sovereign agent backup and restore.
2
+
3
+ Creates a portable, encrypted archive of the full agent state:
4
+ identity, memories, trust, config, agent card, and coordination.
5
+ Restores to any machine with a single command.
6
+
7
+ The backup is a gzip-compressed tar archive. When a PGP key is
8
+ available, the archive content manifest is signed for integrity.
9
+
10
+ Layout inside the tarball:
11
+ backup-<timestamp>/
12
+ ├── manifest.json # backup metadata + file checksums
13
+ ├── config/ # agent configuration
14
+ ├── identity/ # CapAuth profile + keys
15
+ ├── memory/ # SKMemory data
16
+ ├── trust/ # Cloud 9 FEB files + seeds
17
+ ├── coordination/ # board state
18
+ └── agent-card.json # sovereign identity card
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import hashlib
24
+ import json
25
+ import logging
26
+ import os
27
+ import tarfile
28
+ import tempfile
29
+ from datetime import datetime, timezone
30
+ from io import BytesIO
31
+ from pathlib import Path
32
+ from typing import Any, Optional
33
+
34
+ from pydantic import BaseModel, Field
35
+
36
+ from . import AGENT_HOME, __version__
37
+
38
+ logger = logging.getLogger("skcapstone.backup")
39
+
40
+
41
+ class BackupManifest(BaseModel):
42
+ """Metadata for a sovereign agent backup.
43
+
44
+ Attributes:
45
+ backup_id: Unique backup identifier (timestamp-based).
46
+ created_at: When the backup was created.
47
+ agent_name: Name of the backed-up agent.
48
+ version: SKCapstone version that created the backup.
49
+ home_path: Original agent home path.
50
+ files: Dict of relative path -> SHA-256 hash.
51
+ total_size: Total uncompressed size in bytes.
52
+ """
53
+
54
+ backup_id: str = ""
55
+ created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
56
+ agent_name: str = ""
57
+ version: str = __version__
58
+ home_path: str = ""
59
+ files: dict[str, str] = Field(default_factory=dict)
60
+ total_size: int = 0
61
+
62
+
63
+ # Directories relative to agent home to include in backup.
64
+ # Excludes ephemeral runtime dirs: sync/, heartbeats/, coordination/tasks/
65
+ BACKUP_DIRS = [
66
+ "config",
67
+ "identity",
68
+ "memory",
69
+ "soul",
70
+ "conversations",
71
+ "trust",
72
+ ]
73
+
74
+ # Individual files relative to agent home
75
+ BACKUP_FILES = [
76
+ "manifest.json",
77
+ "agent-card.json",
78
+ ]
79
+
80
+ # Patterns to exclude from backup (security-sensitive)
81
+ EXCLUDE_PATTERNS = [
82
+ "*.pyc",
83
+ "__pycache__",
84
+ ".git",
85
+ ]
86
+
87
+
88
+ def _sha256_file(filepath: Path) -> str:
89
+ """Compute SHA-256 of a file.
90
+
91
+ Args:
92
+ filepath: Path to the file.
93
+
94
+ Returns:
95
+ str: Hex digest.
96
+ """
97
+ h = hashlib.sha256()
98
+ with open(filepath, "rb") as f:
99
+ for chunk in iter(lambda: f.read(8192), b""):
100
+ h.update(chunk)
101
+ return h.hexdigest()
102
+
103
+
104
+ def _should_exclude(name: str) -> bool:
105
+ """Check if a tar member should be excluded.
106
+
107
+ Args:
108
+ name: Filename or path component.
109
+
110
+ Returns:
111
+ bool: True if the file should be skipped.
112
+ """
113
+ base = os.path.basename(name)
114
+ for pattern in EXCLUDE_PATTERNS:
115
+ if pattern.startswith("*"):
116
+ if base.endswith(pattern[1:]):
117
+ return True
118
+ elif base == pattern:
119
+ return True
120
+ return False
121
+
122
+
123
+ def create_backup(
124
+ home: Optional[Path] = None,
125
+ output_dir: Optional[Path] = None,
126
+ agent_name: str = "",
127
+ ) -> dict[str, Any]:
128
+ """Create a compressed backup of the full agent state.
129
+
130
+ Collects all agent data directories and files into a
131
+ gzip-compressed tar archive with a manifest for integrity.
132
+
133
+ Args:
134
+ home: Agent home directory. Defaults to ~/.skcapstone.
135
+ output_dir: Where to write the backup file. Defaults to ~/backups.
136
+ agent_name: Agent name for the manifest.
137
+
138
+ Returns:
139
+ dict: Result with 'filepath', 'size', 'file_count', 'manifest'.
140
+ """
141
+ home_path = (home or Path(AGENT_HOME)).expanduser()
142
+ if not home_path.exists():
143
+ raise FileNotFoundError(f"Agent home not found: {home_path}")
144
+
145
+ timestamp = datetime.now(timezone.utc).strftime("%Y%m%d-%H%M%S-%f")
146
+ backup_id = f"backup-{timestamp}"
147
+
148
+ out_dir = (output_dir or home_path / "backups").expanduser()
149
+ out_dir.mkdir(parents=True, exist_ok=True)
150
+ archive_path = out_dir / f"{backup_id}.tar.gz"
151
+
152
+ manifest = BackupManifest(
153
+ backup_id=backup_id,
154
+ agent_name=agent_name or _read_agent_name(home_path),
155
+ home_path=str(home_path),
156
+ )
157
+
158
+ total_size = 0
159
+ file_count = 0
160
+
161
+ with tarfile.open(archive_path, "w:gz") as tar:
162
+ for dir_name in BACKUP_DIRS:
163
+ dir_path = home_path / dir_name
164
+ if not dir_path.exists():
165
+ continue
166
+
167
+ for filepath in sorted(dir_path.rglob("*")):
168
+ if not filepath.is_file():
169
+ continue
170
+ if _should_exclude(str(filepath)):
171
+ continue
172
+
173
+ rel = filepath.relative_to(home_path)
174
+ arcname = f"{backup_id}/{rel}"
175
+
176
+ tar.add(filepath, arcname=arcname)
177
+ manifest.files[str(rel)] = _sha256_file(filepath)
178
+ total_size += filepath.stat().st_size
179
+ file_count += 1
180
+
181
+ for filename in BACKUP_FILES:
182
+ filepath = home_path / filename
183
+ if filepath.exists():
184
+ arcname = f"{backup_id}/{filename}"
185
+ tar.add(filepath, arcname=arcname)
186
+ manifest.files[filename] = _sha256_file(filepath)
187
+ total_size += filepath.stat().st_size
188
+ file_count += 1
189
+
190
+ manifest.total_size = total_size
191
+
192
+ manifest_json = manifest.model_dump_json(indent=2).encode("utf-8")
193
+ info = tarfile.TarInfo(name=f"{backup_id}/_backup_manifest.json")
194
+ info.size = len(manifest_json)
195
+ tar.addfile(info, BytesIO(manifest_json))
196
+
197
+ archive_size = archive_path.stat().st_size
198
+
199
+ logger.info(
200
+ "Backup created: %s (%d files, %d bytes -> %d bytes compressed)",
201
+ archive_path, file_count, total_size, archive_size,
202
+ )
203
+
204
+ return {
205
+ "filepath": str(archive_path),
206
+ "backup_id": backup_id,
207
+ "file_count": file_count,
208
+ "total_size": total_size,
209
+ "archive_size": archive_size,
210
+ "manifest": manifest.model_dump(mode="json"),
211
+ }
212
+
213
+
214
+ def restore_backup(
215
+ archive_path: str | Path,
216
+ target_home: Optional[Path] = None,
217
+ verify: bool = True,
218
+ ) -> dict[str, Any]:
219
+ """Restore an agent from a backup archive.
220
+
221
+ Extracts the archive to the target home directory and
222
+ verifies file integrity against the manifest checksums.
223
+
224
+ Args:
225
+ archive_path: Path to the .tar.gz backup file.
226
+ target_home: Where to restore. Defaults to ~/.skcapstone.
227
+ verify: Whether to verify checksums after extraction.
228
+
229
+ Returns:
230
+ dict: Result with 'restored', 'file_count', 'verified', 'errors'.
231
+ """
232
+ archive = Path(archive_path).expanduser()
233
+ if not archive.exists():
234
+ raise FileNotFoundError(f"Backup not found: {archive}")
235
+
236
+ target = (target_home or Path(AGENT_HOME)).expanduser()
237
+ target.mkdir(parents=True, exist_ok=True)
238
+
239
+ manifest: Optional[BackupManifest] = None
240
+ backup_prefix = ""
241
+
242
+ with tarfile.open(archive, "r:gz") as tar:
243
+ members = tar.getmembers()
244
+ if not members:
245
+ raise ValueError("Empty backup archive")
246
+
247
+ backup_prefix = members[0].name.split("/")[0]
248
+
249
+ manifest_member = f"{backup_prefix}/_backup_manifest.json"
250
+ try:
251
+ f = tar.extractfile(manifest_member)
252
+ if f:
253
+ manifest = BackupManifest.model_validate_json(f.read())
254
+ except (KeyError, Exception) as exc:
255
+ logger.warning("No manifest in backup: %s", exc)
256
+
257
+ file_count = 0
258
+ for member in tar.getmembers():
259
+ if member.name == manifest_member:
260
+ continue
261
+ if not member.isfile():
262
+ continue
263
+ if _should_exclude(member.name):
264
+ continue
265
+
266
+ rel_path = member.name[len(backup_prefix) + 1:]
267
+ member.name = rel_path
268
+ tar.extract(member, path=target, filter="data")
269
+ file_count += 1
270
+
271
+ errors: list[str] = []
272
+ if verify and manifest:
273
+ for rel_path, expected_hash in manifest.files.items():
274
+ restored_file = target / rel_path
275
+ if not restored_file.exists():
276
+ errors.append(f"Missing: {rel_path}")
277
+ continue
278
+ actual = _sha256_file(restored_file)
279
+ if actual != expected_hash:
280
+ errors.append(f"Checksum mismatch: {rel_path}")
281
+
282
+ logger.info(
283
+ "Restored %d files to %s (%d verification errors)",
284
+ file_count, target, len(errors),
285
+ )
286
+
287
+ return {
288
+ "restored": file_count,
289
+ "file_count": file_count,
290
+ "target": str(target),
291
+ "verified": len(errors) == 0,
292
+ "errors": errors,
293
+ "backup_id": manifest.backup_id if manifest else "unknown",
294
+ "agent_name": manifest.agent_name if manifest else "unknown",
295
+ }
296
+
297
+
298
+ def list_backups(
299
+ backup_dir: Optional[Path] = None,
300
+ ) -> list[dict[str, Any]]:
301
+ """List available backup archives.
302
+
303
+ Args:
304
+ backup_dir: Directory to scan. Defaults to ~/.skcapstone/backups.
305
+
306
+ Returns:
307
+ list[dict]: Backup metadata sorted newest first.
308
+ """
309
+ search_dir = (backup_dir or Path(AGENT_HOME).expanduser() / "backups")
310
+ if not search_dir.exists():
311
+ return []
312
+
313
+ backups = []
314
+ for f in sorted(search_dir.glob("backup-*.tar.gz"), reverse=True):
315
+ stat = f.stat()
316
+ backups.append({
317
+ "filepath": str(f),
318
+ "filename": f.name,
319
+ "size": stat.st_size,
320
+ "created": datetime.fromtimestamp(stat.st_mtime, tz=timezone.utc).isoformat(),
321
+ })
322
+
323
+ return backups
324
+
325
+
326
+ def _read_agent_name(home: Path) -> str:
327
+ """Read agent name from config or manifest.
328
+
329
+ Args:
330
+ home: Agent home directory.
331
+
332
+ Returns:
333
+ str: Agent name or 'unknown'.
334
+ """
335
+ for filename in ("manifest.json", "config/config.yaml"):
336
+ filepath = home / filename
337
+ if filepath.exists():
338
+ try:
339
+ data = json.loads(filepath.read_text(encoding="utf-8"))
340
+ name = data.get("name") or data.get("agent_name")
341
+ if name:
342
+ return name
343
+ except Exception:
344
+ continue
345
+ return "unknown"
@@ -0,0 +1,357 @@
1
+ """
2
+ Blueprint Registry Client — interact with the souls.skworld.io API.
3
+
4
+ This is a client library for the remote soul blueprint registry hosted at
5
+ souls.skworld.io. The actual server is a separate service; this module
6
+ provides a typed Python interface for listing, searching, publishing,
7
+ and downloading blueprints.
8
+
9
+ Authentication uses DID-based bearer tokens: the agent's DID key is
10
+ sent as ``Authorization: Bearer did:key:<fingerprint>`` so the registry
11
+ can attribute published blueprints to a sovereign identity.
12
+
13
+ No external dependencies — uses only ``urllib`` from the standard library.
14
+
15
+ Usage::
16
+
17
+ from skcapstone.blueprint_registry import BlueprintRegistryClient
18
+
19
+ client = BlueprintRegistryClient()
20
+ blueprints = client.list_blueprints()
21
+ result = client.search_blueprints("comedy")
22
+ client.publish_blueprint(soul_data)
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ import json
28
+ import logging
29
+ import urllib.error
30
+ import urllib.request
31
+ from pathlib import Path
32
+ from typing import Any, Optional
33
+
34
+ logger = logging.getLogger("skcapstone.blueprint_registry")
35
+
36
+ DEFAULT_BASE_URL = "https://souls.skworld.io/api"
37
+
38
+ # Timeout for HTTP requests (seconds).
39
+ _REQUEST_TIMEOUT = 30
40
+
41
+
42
+ class BlueprintRegistryError(Exception):
43
+ """Raised when a registry API call fails."""
44
+
45
+ def __init__(self, message: str, status_code: Optional[int] = None) -> None:
46
+ super().__init__(message)
47
+ self.status_code = status_code
48
+
49
+
50
+ class BlueprintRegistryClient:
51
+ """Client for the souls.skworld.io blueprint registry API.
52
+
53
+ Args:
54
+ base_url: API base URL (default: ``https://souls.skworld.io/api``).
55
+ did_key: DID key string for authentication (e.g. ``did:key:z6Mk...``).
56
+ If not provided, the client will attempt to load it from the
57
+ agent's identity on disk at ``~/.skcapstone/did/``.
58
+ timeout: HTTP request timeout in seconds (default: 30).
59
+ """
60
+
61
+ def __init__(
62
+ self,
63
+ base_url: str = DEFAULT_BASE_URL,
64
+ did_key: Optional[str] = None,
65
+ timeout: int = _REQUEST_TIMEOUT,
66
+ ) -> None:
67
+ # Strip trailing slash for consistent URL joining
68
+ self.base_url = base_url.rstrip("/")
69
+ self.did_key = did_key
70
+ self.timeout = timeout
71
+
72
+ # ------------------------------------------------------------------
73
+ # Auth helpers
74
+ # ------------------------------------------------------------------
75
+
76
+ def _resolve_did_key(self) -> Optional[str]:
77
+ """Resolve the DID key from the instance or from disk.
78
+
79
+ Returns:
80
+ DID key string, or None if unavailable.
81
+ """
82
+ if self.did_key:
83
+ return self.did_key
84
+
85
+ # Attempt to load from the agent's DID directory
86
+ did_dir = Path.home() / ".skcapstone" / "did"
87
+ key_file = did_dir / "did_key.json"
88
+ if key_file.exists():
89
+ try:
90
+ data = json.loads(key_file.read_text(encoding="utf-8"))
91
+ resolved = data.get("did") or data.get("did_key") or data.get("id")
92
+ if resolved:
93
+ logger.debug("Resolved DID key from %s", key_file)
94
+ return resolved
95
+ except (json.JSONDecodeError, OSError) as exc:
96
+ logger.debug("Could not load DID key from %s: %s", key_file, exc)
97
+
98
+ # Fallback: check identity.json
99
+ identity_file = did_dir / "identity.json"
100
+ if identity_file.exists():
101
+ try:
102
+ data = json.loads(identity_file.read_text(encoding="utf-8"))
103
+ resolved = data.get("did_key") or data.get("did") or data.get("id")
104
+ if resolved:
105
+ return resolved
106
+ except (json.JSONDecodeError, OSError):
107
+ pass
108
+
109
+ return None
110
+
111
+ def _build_headers(self, authenticated: bool = False) -> dict[str, str]:
112
+ """Build HTTP headers for a request.
113
+
114
+ Args:
115
+ authenticated: Whether to include the Authorization header.
116
+
117
+ Returns:
118
+ Header dict.
119
+ """
120
+ headers: dict[str, str] = {
121
+ "Content-Type": "application/json",
122
+ "Accept": "application/json",
123
+ "User-Agent": "skcapstone-blueprint-registry/1.0",
124
+ }
125
+ if authenticated:
126
+ did = self._resolve_did_key()
127
+ if did:
128
+ headers["Authorization"] = f"Bearer {did}"
129
+ else:
130
+ logger.warning(
131
+ "No DID key available for authenticated request. "
132
+ "Set did_key on the client or ensure ~/.skcapstone/did/ "
133
+ "contains a valid identity."
134
+ )
135
+ return headers
136
+
137
+ # ------------------------------------------------------------------
138
+ # HTTP transport
139
+ # ------------------------------------------------------------------
140
+
141
+ def _request(
142
+ self,
143
+ method: str,
144
+ path: str,
145
+ *,
146
+ body: Optional[dict[str, Any]] = None,
147
+ authenticated: bool = False,
148
+ ) -> dict[str, Any]:
149
+ """Execute an HTTP request against the registry API.
150
+
151
+ Args:
152
+ method: HTTP method (GET, POST, etc.).
153
+ path: URL path relative to base_url (e.g. ``/blueprints``).
154
+ body: JSON body for POST/PUT requests.
155
+ authenticated: Whether to attach DID auth header.
156
+
157
+ Returns:
158
+ Parsed JSON response as a dict.
159
+
160
+ Raises:
161
+ BlueprintRegistryError: On HTTP or connection errors.
162
+ """
163
+ url = f"{self.base_url}{path}"
164
+ headers = self._build_headers(authenticated=authenticated)
165
+
166
+ data = None
167
+ if body is not None:
168
+ data = json.dumps(body).encode("utf-8")
169
+
170
+ req = urllib.request.Request(
171
+ url, data=data, headers=headers, method=method
172
+ )
173
+
174
+ logger.debug("%s %s", method, url)
175
+
176
+ try:
177
+ with urllib.request.urlopen(req, timeout=self.timeout) as resp:
178
+ raw = resp.read().decode("utf-8")
179
+ if not raw.strip():
180
+ return {}
181
+ return json.loads(raw)
182
+ except urllib.error.HTTPError as exc:
183
+ error_body = ""
184
+ try:
185
+ error_body = exc.read().decode("utf-8", errors="replace")
186
+ except Exception:
187
+ pass
188
+ msg = f"Registry API error {exc.code} for {method} {path}"
189
+ if error_body:
190
+ msg += f": {error_body[:500]}"
191
+ raise BlueprintRegistryError(msg, status_code=exc.code) from exc
192
+ except urllib.error.URLError as exc:
193
+ raise BlueprintRegistryError(
194
+ f"Cannot reach registry at {url}: {exc.reason}"
195
+ ) from exc
196
+ except json.JSONDecodeError as exc:
197
+ raise BlueprintRegistryError(
198
+ f"Invalid JSON in response from {method} {path}: {exc}"
199
+ ) from exc
200
+
201
+ # ------------------------------------------------------------------
202
+ # Public API methods
203
+ # ------------------------------------------------------------------
204
+
205
+ def list_blueprints(self) -> list[dict[str, Any]]:
206
+ """List all published blueprints in the registry.
207
+
208
+ Calls ``GET /blueprints``.
209
+
210
+ Returns:
211
+ List of blueprint summary dicts, each containing at minimum
212
+ ``name``, ``display_name``, ``category``, and ``soul_id``.
213
+
214
+ Raises:
215
+ BlueprintRegistryError: On API failure.
216
+ """
217
+ result = self._request("GET", "/blueprints")
218
+ # API may return {"blueprints": [...]} or a bare list
219
+ if isinstance(result, list):
220
+ return result
221
+ return result.get("blueprints", [])
222
+
223
+ def get_blueprint(self, soul_id: str) -> dict[str, Any]:
224
+ """Get a single blueprint by its soul ID.
225
+
226
+ Calls ``GET /blueprints/{soul_id}``.
227
+
228
+ Args:
229
+ soul_id: The unique identifier (slug) of the blueprint.
230
+
231
+ Returns:
232
+ Full blueprint dict with all fields.
233
+
234
+ Raises:
235
+ BlueprintRegistryError: On API failure or 404.
236
+ """
237
+ return self._request("GET", f"/blueprints/{soul_id}")
238
+
239
+ def publish_blueprint(self, soul_data: dict[str, Any]) -> dict[str, Any]:
240
+ """Publish a soul blueprint to the registry.
241
+
242
+ Calls ``POST /blueprints`` with DID-based authentication.
243
+
244
+ The ``soul_data`` should conform to the SoulBlueprint schema
245
+ (at minimum: ``name``, ``display_name``, ``category``).
246
+
247
+ Args:
248
+ soul_data: Blueprint data dict to publish.
249
+
250
+ Returns:
251
+ API response dict (typically includes ``soul_id`` and ``status``).
252
+
253
+ Raises:
254
+ BlueprintRegistryError: On API failure or auth error.
255
+ """
256
+ return self._request(
257
+ "POST", "/blueprints", body=soul_data, authenticated=True
258
+ )
259
+
260
+ def search_blueprints(self, query: str) -> list[dict[str, Any]]:
261
+ """Search for blueprints by a text query.
262
+
263
+ Calls ``GET /blueprints/search?q=<query>``.
264
+
265
+ Args:
266
+ query: Search string (matched against name, category, traits, etc.).
267
+
268
+ Returns:
269
+ List of matching blueprint dicts.
270
+
271
+ Raises:
272
+ BlueprintRegistryError: On API failure.
273
+ """
274
+ # URL-encode the query parameter
275
+ encoded_q = urllib.request.quote(query, safe="")
276
+ result = self._request("GET", f"/blueprints/search?q={encoded_q}")
277
+ if isinstance(result, list):
278
+ return result
279
+ return result.get("blueprints", result.get("results", []))
280
+
281
+ def download_blueprint(self, soul_id: str) -> dict[str, Any]:
282
+ """Download a full blueprint for local installation.
283
+
284
+ Calls ``GET /blueprints/{soul_id}/download``.
285
+
286
+ The returned dict can be written directly to a JSON file and
287
+ loaded by the local SoulManager.
288
+
289
+ Args:
290
+ soul_id: The unique identifier (slug) of the blueprint.
291
+
292
+ Returns:
293
+ Complete blueprint data suitable for local installation.
294
+
295
+ Raises:
296
+ BlueprintRegistryError: On API failure or 404.
297
+ """
298
+ return self._request("GET", f"/blueprints/{soul_id}/download")
299
+
300
+ # ------------------------------------------------------------------
301
+ # Convenience helpers
302
+ # ------------------------------------------------------------------
303
+
304
+ def download_and_install(self, soul_id: str, home: Optional[Path] = None) -> Path:
305
+ """Download a blueprint and install it locally.
306
+
307
+ Args:
308
+ soul_id: Registry soul ID to download.
309
+ home: Agent home directory (default: ``~/.skcapstone``).
310
+
311
+ Returns:
312
+ Path to the installed blueprint JSON file.
313
+
314
+ Raises:
315
+ BlueprintRegistryError: On download failure.
316
+ """
317
+ bp_data = self.download_blueprint(soul_id)
318
+ home = home or Path.home() / ".skcapstone"
319
+ installed_dir = home / "soul" / "installed"
320
+ installed_dir.mkdir(parents=True, exist_ok=True)
321
+
322
+ name = bp_data.get("name", soul_id)
323
+ dest = installed_dir / f"{name}.json"
324
+ dest.write_text(json.dumps(bp_data, indent=2), encoding="utf-8")
325
+ logger.info("Installed blueprint '%s' from registry to %s", soul_id, dest)
326
+ return dest
327
+
328
+ def publish_from_file(self, path: Path) -> dict[str, Any]:
329
+ """Load a local blueprint file and publish it to the registry.
330
+
331
+ Args:
332
+ path: Path to a local blueprint JSON file.
333
+
334
+ Returns:
335
+ API response from publish.
336
+
337
+ Raises:
338
+ FileNotFoundError: If the file does not exist.
339
+ BlueprintRegistryError: On publish failure.
340
+ """
341
+ if not path.exists():
342
+ raise FileNotFoundError(f"Blueprint file not found: {path}")
343
+
344
+ data = json.loads(path.read_text(encoding="utf-8"))
345
+ return self.publish_blueprint(data)
346
+
347
+ def is_reachable(self) -> bool:
348
+ """Check if the registry API is reachable.
349
+
350
+ Returns:
351
+ True if a basic request succeeds, False otherwise.
352
+ """
353
+ try:
354
+ self._request("GET", "/blueprints")
355
+ return True
356
+ except BlueprintRegistryError:
357
+ return False