@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,670 @@
1
+ """
2
+ Preflight system checks — detect and auto-install required tools.
3
+
4
+ Checks for:
5
+ - Python (already running, but verify version)
6
+ - GPG / GnuPG (required for encryption)
7
+ - Git (optional — only needed for dev/repo installs)
8
+ - Syncthing (needed for device sync, Path 2)
9
+
10
+ Each check returns a result with:
11
+ - Whether the tool is installed
12
+ - Current version (if installed)
13
+ - Platform-specific auto-install command
14
+ - Manual download URL as fallback
15
+
16
+ Auto-install uses the platform's native package manager:
17
+ - Linux: apt/dnf/pacman (via sudo)
18
+ - macOS: brew
19
+ - Windows: winget / choco / direct download
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ import platform
25
+ import shutil
26
+ import subprocess
27
+ from dataclasses import dataclass, field
28
+ from enum import Enum
29
+ from typing import Optional
30
+
31
+
32
+ class ToolStatus(str, Enum):
33
+ """Status of a system tool."""
34
+ INSTALLED = "installed"
35
+ MISSING = "missing"
36
+ OPTIONAL = "optional"
37
+
38
+
39
+ @dataclass
40
+ class ToolCheck:
41
+ """Result of checking a single system tool."""
42
+
43
+ name: str
44
+ status: ToolStatus
45
+ required: bool
46
+ version: str = ""
47
+ install_cmd: str = ""
48
+ download_url: str = ""
49
+ install_note: str = ""
50
+
51
+ @property
52
+ def installed(self) -> bool:
53
+ """Whether the tool is installed."""
54
+ return self.status == ToolStatus.INSTALLED
55
+
56
+ @property
57
+ def ok(self) -> bool:
58
+ """Whether this check passes (installed, or optional and missing)."""
59
+ return self.installed or not self.required
60
+
61
+
62
+ @dataclass
63
+ class PreflightResult:
64
+ """Combined result of all preflight checks."""
65
+
66
+ python: ToolCheck
67
+ gpg: ToolCheck
68
+ git: ToolCheck
69
+ syncthing: ToolCheck
70
+
71
+ @property
72
+ def all_ok(self) -> bool:
73
+ """True if all required tools pass."""
74
+ return all(c.ok for c in [self.python, self.gpg, self.git, self.syncthing])
75
+
76
+ @property
77
+ def required_missing(self) -> list[ToolCheck]:
78
+ """List of required tools that are missing."""
79
+ return [c for c in [self.python, self.gpg, self.git, self.syncthing]
80
+ if c.required and not c.installed]
81
+
82
+ @property
83
+ def optional_missing(self) -> list[ToolCheck]:
84
+ """List of optional tools that are missing."""
85
+ return [c for c in [self.python, self.gpg, self.git, self.syncthing]
86
+ if not c.required and not c.installed]
87
+
88
+
89
+ # ---------------------------------------------------------------------------
90
+ # Platform detection
91
+ # ---------------------------------------------------------------------------
92
+
93
+ def _system() -> str:
94
+ """Canonical platform name."""
95
+ return platform.system()
96
+
97
+
98
+ def _has_pkg_manager(name: str) -> bool:
99
+ """Check if a package manager is available."""
100
+ return shutil.which(name) is not None
101
+
102
+
103
+ def _detect_linux_pkg_manager() -> Optional[str]:
104
+ """Detect the Linux package manager."""
105
+ for mgr in ("apt", "dnf", "pacman", "zypper", "apk"):
106
+ if _has_pkg_manager(mgr):
107
+ return mgr
108
+ return None
109
+
110
+
111
+ # ---------------------------------------------------------------------------
112
+ # Individual tool checks
113
+ # ---------------------------------------------------------------------------
114
+
115
+ def check_python() -> ToolCheck:
116
+ """Check Python version (we're already running it, but verify version).
117
+
118
+ Returns:
119
+ ToolCheck for Python.
120
+ """
121
+ import sys
122
+ version = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
123
+ ok = sys.version_info >= (3, 10)
124
+ return ToolCheck(
125
+ name="Python",
126
+ status=ToolStatus.INSTALLED if ok else ToolStatus.MISSING,
127
+ required=True,
128
+ version=version,
129
+ download_url="https://python.org/downloads/",
130
+ install_note="" if ok else "Python 3.10+ is required.",
131
+ )
132
+
133
+
134
+ def check_gpg() -> ToolCheck:
135
+ """Check if GnuPG is installed.
136
+
137
+ Returns:
138
+ ToolCheck for GPG.
139
+ """
140
+ system = _system()
141
+
142
+ if shutil.which("gpg") or shutil.which("gpg2"):
143
+ binary = "gpg2" if shutil.which("gpg2") else "gpg"
144
+ version = ""
145
+ try:
146
+ result = subprocess.run(
147
+ [binary, "--version"],
148
+ capture_output=True, text=True, timeout=5,
149
+ )
150
+ if result.returncode == 0:
151
+ first_line = result.stdout.strip().split("\n")[0]
152
+ version = first_line[:60]
153
+ except (OSError, subprocess.TimeoutExpired):
154
+ pass
155
+ return ToolCheck(
156
+ name="GnuPG",
157
+ status=ToolStatus.INSTALLED,
158
+ required=True,
159
+ version=version,
160
+ )
161
+
162
+ # Not installed — provide platform-specific install commands
163
+ if system == "Linux":
164
+ mgr = _detect_linux_pkg_manager()
165
+ cmds = {
166
+ "apt": "sudo apt install -y gnupg",
167
+ "dnf": "sudo dnf install -y gnupg2",
168
+ "pacman": "sudo pacman -S --noconfirm gnupg",
169
+ "zypper": "sudo zypper install -y gpg2",
170
+ "apk": "sudo apk add gnupg",
171
+ }
172
+ install_cmd = cmds.get(mgr, "sudo apt install -y gnupg")
173
+ elif system == "Darwin":
174
+ install_cmd = "brew install gnupg" if _has_pkg_manager("brew") else ""
175
+ elif system == "Windows":
176
+ install_cmd = "winget install --id GnuPG.Gpg4win --accept-source-agreements --accept-package-agreements"
177
+ else:
178
+ install_cmd = ""
179
+
180
+ return ToolCheck(
181
+ name="GnuPG",
182
+ status=ToolStatus.MISSING,
183
+ required=True,
184
+ install_cmd=install_cmd,
185
+ download_url=_gpg_download_url(),
186
+ install_note="GPG encrypts your files and identity. Required for all operations.",
187
+ )
188
+
189
+
190
+ def check_git(required: bool = False) -> ToolCheck:
191
+ """Check if Git is installed.
192
+
193
+ Args:
194
+ required: Whether Git is required for this install path.
195
+
196
+ Returns:
197
+ ToolCheck for Git.
198
+ """
199
+ system = _system()
200
+
201
+ if shutil.which("git"):
202
+ version = ""
203
+ try:
204
+ result = subprocess.run(
205
+ ["git", "--version"],
206
+ capture_output=True, text=True, timeout=5,
207
+ )
208
+ if result.returncode == 0:
209
+ version = result.stdout.strip().split("\n")[0][:60]
210
+ except (OSError, subprocess.TimeoutExpired):
211
+ pass
212
+ return ToolCheck(
213
+ name="Git",
214
+ status=ToolStatus.INSTALLED,
215
+ required=required,
216
+ version=version,
217
+ )
218
+
219
+ if system == "Linux":
220
+ mgr = _detect_linux_pkg_manager()
221
+ cmds = {
222
+ "apt": "sudo apt install -y git",
223
+ "dnf": "sudo dnf install -y git",
224
+ "pacman": "sudo pacman -S --noconfirm git",
225
+ "zypper": "sudo zypper install -y git",
226
+ "apk": "sudo apk add git",
227
+ }
228
+ install_cmd = cmds.get(mgr, "sudo apt install -y git")
229
+ elif system == "Darwin":
230
+ install_cmd = "xcode-select --install"
231
+ elif system == "Windows":
232
+ install_cmd = "winget install --id Git.Git --accept-source-agreements --accept-package-agreements"
233
+ else:
234
+ install_cmd = ""
235
+
236
+ return ToolCheck(
237
+ name="Git",
238
+ status=ToolStatus.MISSING,
239
+ required=required,
240
+ install_cmd=install_cmd,
241
+ download_url=_git_download_url(),
242
+ install_note="Git is only needed for development. You can skip this.",
243
+ )
244
+
245
+
246
+ def check_syncthing(required: bool = False) -> ToolCheck:
247
+ """Check if Syncthing is installed.
248
+
249
+ Args:
250
+ required: Whether Syncthing is required for this install path.
251
+
252
+ Returns:
253
+ ToolCheck for Syncthing.
254
+ """
255
+ system = _system()
256
+
257
+ if shutil.which("syncthing"):
258
+ version = ""
259
+ try:
260
+ result = subprocess.run(
261
+ ["syncthing", "--version"],
262
+ capture_output=True, text=True, timeout=5,
263
+ )
264
+ if result.returncode == 0:
265
+ version = result.stdout.strip().split("\n")[0][:60]
266
+ except (OSError, subprocess.TimeoutExpired):
267
+ pass
268
+ return ToolCheck(
269
+ name="Syncthing",
270
+ status=ToolStatus.INSTALLED,
271
+ required=required,
272
+ version=version,
273
+ )
274
+
275
+ if system == "Linux":
276
+ mgr = _detect_linux_pkg_manager()
277
+ cmds = {
278
+ "apt": "sudo apt install -y syncthing",
279
+ "dnf": "sudo dnf install -y syncthing",
280
+ "pacman": "sudo pacman -S --noconfirm syncthing",
281
+ }
282
+ install_cmd = cmds.get(mgr, "sudo apt install -y syncthing")
283
+ elif system == "Darwin":
284
+ install_cmd = "brew install syncthing" if _has_pkg_manager("brew") else ""
285
+ elif system == "Windows":
286
+ install_cmd = "winget install --id Syncthing.Syncthing --accept-source-agreements --accept-package-agreements"
287
+ else:
288
+ install_cmd = ""
289
+
290
+ return ToolCheck(
291
+ name="Syncthing",
292
+ status=ToolStatus.MISSING,
293
+ required=required,
294
+ install_cmd=install_cmd,
295
+ download_url="https://syncthing.net/downloads/",
296
+ install_note="Syncthing syncs your identity between devices. Needed for multi-device setup.",
297
+ )
298
+
299
+
300
+ # ---------------------------------------------------------------------------
301
+ # Auto-install
302
+ # ---------------------------------------------------------------------------
303
+
304
+ def auto_install_tool(check: ToolCheck) -> bool:
305
+ """Attempt to auto-install a tool using its platform install command.
306
+
307
+ Args:
308
+ check: ToolCheck with install_cmd populated.
309
+
310
+ Returns:
311
+ True if install succeeded.
312
+ """
313
+ if check.installed:
314
+ return True
315
+ if not check.install_cmd:
316
+ return False
317
+
318
+ try:
319
+ result = subprocess.run(
320
+ check.install_cmd.split(),
321
+ capture_output=True, text=True, timeout=180,
322
+ )
323
+ return result.returncode == 0
324
+ except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
325
+ return False
326
+
327
+
328
+ # ---------------------------------------------------------------------------
329
+ # Full preflight
330
+ # ---------------------------------------------------------------------------
331
+
332
+ def run_preflight(
333
+ require_git: bool = False,
334
+ require_syncthing: bool = False,
335
+ ) -> PreflightResult:
336
+ """Run all preflight checks.
337
+
338
+ Args:
339
+ require_git: Whether Git is required (True for dev installs).
340
+ require_syncthing: Whether Syncthing is required (True for Path 2).
341
+
342
+ Returns:
343
+ PreflightResult with all tool checks.
344
+ """
345
+ return PreflightResult(
346
+ python=check_python(),
347
+ gpg=check_gpg(),
348
+ git=check_git(required=require_git),
349
+ syncthing=check_syncthing(required=require_syncthing),
350
+ )
351
+
352
+
353
+ # ---------------------------------------------------------------------------
354
+ # Download URLs
355
+ # ---------------------------------------------------------------------------
356
+
357
+ def _gpg_download_url() -> str:
358
+ """Platform-specific GPG download URL."""
359
+ system = _system()
360
+ urls = {
361
+ "Windows": "https://gpg4win.org/download.html",
362
+ "Darwin": "https://sourceforge.net/p/gpgosx/docu/Download/",
363
+ "Linux": "https://gnupg.org/download/",
364
+ }
365
+ return urls.get(system, "https://gnupg.org/download/")
366
+
367
+
368
+ def _git_download_url() -> str:
369
+ """Platform-specific Git download URL."""
370
+ system = _system()
371
+ urls = {
372
+ "Windows": "https://git-scm.com/download/win",
373
+ "Darwin": "https://git-scm.com/download/mac",
374
+ "Linux": "https://git-scm.com/download/linux",
375
+ }
376
+ return urls.get(system, "https://git-scm.com/downloads")
377
+
378
+
379
+ # ---------------------------------------------------------------------------
380
+ # Legacy compatibility — used by doctor.py and old code
381
+ # ---------------------------------------------------------------------------
382
+
383
+ GIT_DOWNLOAD_URLS = {
384
+ "Windows": "https://git-scm.com/download/win",
385
+ "Linux": "https://git-scm.com/download/linux",
386
+ "Darwin": "https://git-scm.com/download/mac",
387
+ }
388
+ GIT_DOWNLOAD_DEFAULT = "https://git-scm.com/downloads"
389
+
390
+
391
+ @dataclass
392
+ class GitPreflightResult:
393
+ """Legacy result object — kept for backward compatibility with doctor.py."""
394
+
395
+ installed: bool
396
+ platform_label: str
397
+ message: str
398
+ download_url: str
399
+
400
+ @classmethod
401
+ def run(cls) -> "GitPreflightResult":
402
+ """Run Git check and return a result object."""
403
+ check = check_git(required=False)
404
+ system = _system()
405
+ label = {"Windows": "Windows", "Linux": "Linux", "Darwin": "macOS"}.get(
406
+ system, system
407
+ )
408
+ return cls(
409
+ installed=check.installed,
410
+ platform_label=label,
411
+ message=check.version or check.install_note,
412
+ download_url=check.download_url,
413
+ )
414
+
415
+
416
+ def git_install_hint_for_doctor() -> str:
417
+ """Return a one-line fix hint for skcapstone doctor.
418
+
419
+ Returns:
420
+ Empty string if Git is installed, otherwise hint with URL.
421
+ """
422
+ check = check_git()
423
+ if check.installed:
424
+ return ""
425
+ return f"Install Git: {check.download_url}"
426
+
427
+
428
+ # ---------------------------------------------------------------------------
429
+ # Daemon preflight checker
430
+ # ---------------------------------------------------------------------------
431
+
432
+ import sys
433
+ from typing import Literal
434
+
435
+ CheckStatus = Literal["ok", "warn", "fail"]
436
+
437
+
438
+ @dataclass
439
+ class CheckResult:
440
+ """Result of a single daemon preflight check."""
441
+
442
+ name: str
443
+ status: CheckStatus
444
+ message: str
445
+ critical: bool = True
446
+
447
+ @property
448
+ def ok(self) -> bool:
449
+ return self.status == "ok"
450
+
451
+ @property
452
+ def failed(self) -> bool:
453
+ return self.status == "fail"
454
+
455
+ @property
456
+ def warned(self) -> bool:
457
+ return self.status == "warn"
458
+
459
+
460
+ class PreflightChecker:
461
+ """Daemon startup preflight checker.
462
+
463
+ Verifies that the environment is ready for the sovereign agent daemon
464
+ to start safely. Runs a set of named checks and aggregates results into
465
+ a summary dict.
466
+
467
+ Args:
468
+ home: Agent home directory (defaults to ``~/.skcapstone``).
469
+ """
470
+
471
+ def __init__(self, home: Optional[Path] = None):
472
+ from . import AGENT_HOME
473
+ self.home = (home or Path(AGENT_HOME)).expanduser()
474
+
475
+ # ------------------------------------------------------------------
476
+ # Individual checks
477
+ # ------------------------------------------------------------------
478
+
479
+ def check_python(self) -> CheckResult:
480
+ """Verify Python >= 3.11."""
481
+ vi = sys.version_info
482
+ version = f"{vi.major}.{vi.minor}.{vi.micro}"
483
+ if vi >= (3, 11):
484
+ return CheckResult("python", "ok", f"Python {version}")
485
+ return CheckResult(
486
+ "python", "fail",
487
+ f"Python {version} — 3.11+ required",
488
+ critical=True,
489
+ )
490
+
491
+ def check_packages(self) -> CheckResult:
492
+ """Verify skcapstone, skseed, and skcomm are importable."""
493
+ missing = []
494
+ for pkg in ("skcapstone", "skseed", "skcomm"):
495
+ try:
496
+ __import__(pkg)
497
+ except ImportError:
498
+ missing.append(pkg)
499
+ if not missing:
500
+ return CheckResult("packages", "ok", "skcapstone, skseed, skcomm all importable")
501
+ return CheckResult(
502
+ "packages", "fail",
503
+ f"Missing packages: {', '.join(missing)}",
504
+ critical=True,
505
+ )
506
+
507
+ def check_ollama(self) -> CheckResult:
508
+ """Verify Ollama is running and has at least one model."""
509
+ import urllib.request
510
+ import json as _json
511
+ try:
512
+ with urllib.request.urlopen(
513
+ "http://localhost:11434/api/tags", timeout=3
514
+ ) as resp:
515
+ data = _json.loads(resp.read())
516
+ models = data.get("models", [])
517
+ if not models:
518
+ return CheckResult(
519
+ "ollama", "warn",
520
+ "Ollama running but no models loaded — pull a model first",
521
+ critical=False,
522
+ )
523
+ names = ", ".join(m.get("name", "?") for m in models[:3])
524
+ return CheckResult("ollama", "ok", f"Ollama running — models: {names}")
525
+ except OSError:
526
+ return CheckResult(
527
+ "ollama", "warn",
528
+ "Ollama not reachable on localhost:11434 — LLM responses will be unavailable",
529
+ critical=False,
530
+ )
531
+ except Exception as exc:
532
+ return CheckResult(
533
+ "ollama", "warn",
534
+ f"Ollama check failed: {exc}",
535
+ critical=False,
536
+ )
537
+
538
+ def check_identity(self) -> CheckResult:
539
+ """Verify a PGP identity exists in the agent home."""
540
+ identity_json = self.home / "identity" / "identity.json"
541
+ if identity_json.exists():
542
+ try:
543
+ import json as _json
544
+ data = _json.loads(identity_json.read_text(encoding="utf-8"))
545
+ name = data.get("name", "unknown")
546
+ fp = data.get("fingerprint", "")
547
+ fp_display = fp[-8:] if fp else "no fingerprint"
548
+ return CheckResult(
549
+ "identity", "ok",
550
+ f"Identity: {name} (…{fp_display})",
551
+ )
552
+ except Exception as exc:
553
+ return CheckResult(
554
+ "identity", "fail",
555
+ f"identity.json unreadable: {exc}",
556
+ )
557
+ # Try legacy manifest.json
558
+ manifest = self.home / "manifest.json"
559
+ if manifest.exists():
560
+ return CheckResult(
561
+ "identity", "warn",
562
+ "manifest.json found but no identity/identity.json — run skcapstone init",
563
+ critical=False,
564
+ )
565
+ return CheckResult(
566
+ "identity", "fail",
567
+ f"No identity found in {self.home}/identity/ — run skcapstone init",
568
+ )
569
+
570
+ def check_home_dirs(self) -> CheckResult:
571
+ """Verify expected ~/.skcapstone/ subdirectory structure exists."""
572
+ required = ["memory", "trust", "identity", "config"]
573
+ missing = [d for d in required if not (self.home / d).exists()]
574
+ if not missing:
575
+ return CheckResult("home_dirs", "ok", f"Home structure OK: {self.home}")
576
+ return CheckResult(
577
+ "home_dirs", "fail",
578
+ f"Missing directories in {self.home}: {', '.join(missing)} — run skcapstone init",
579
+ )
580
+
581
+ def check_config(self) -> CheckResult:
582
+ """Verify consciousness.yaml is parseable."""
583
+ config_path = self.home / "config" / "consciousness.yaml"
584
+ if not config_path.exists():
585
+ return CheckResult(
586
+ "config", "warn",
587
+ f"consciousness.yaml not found at {config_path} — using defaults",
588
+ critical=False,
589
+ )
590
+ try:
591
+ import yaml
592
+ data = yaml.safe_load(config_path.read_text(encoding="utf-8"))
593
+ if data is None:
594
+ return CheckResult(
595
+ "config", "warn",
596
+ "consciousness.yaml is empty — using defaults",
597
+ critical=False,
598
+ )
599
+ return CheckResult("config", "ok", f"consciousness.yaml parsed OK ({config_path})")
600
+ except Exception as exc:
601
+ return CheckResult(
602
+ "config", "fail",
603
+ f"consciousness.yaml parse error: {exc}",
604
+ )
605
+
606
+ def check_disk_space(self) -> CheckResult:
607
+ """Warn if less than 5 GB free on the home directory filesystem."""
608
+ import shutil as _shutil
609
+ try:
610
+ usage = _shutil.disk_usage(self.home if self.home.exists() else Path.home())
611
+ free_gb = usage.free / (1024 ** 3)
612
+ if free_gb >= 5.0:
613
+ return CheckResult(
614
+ "disk_space", "ok",
615
+ f"{free_gb:.1f} GB free",
616
+ )
617
+ return CheckResult(
618
+ "disk_space", "warn",
619
+ f"Only {free_gb:.1f} GB free — less than 5 GB recommended",
620
+ critical=False,
621
+ )
622
+ except Exception as exc:
623
+ return CheckResult(
624
+ "disk_space", "warn",
625
+ f"Could not check disk space: {exc}",
626
+ critical=False,
627
+ )
628
+
629
+ # ------------------------------------------------------------------
630
+ # Aggregate
631
+ # ------------------------------------------------------------------
632
+
633
+ def run_all(self) -> dict:
634
+ """Run all preflight checks and return a summary dict.
635
+
636
+ Returns:
637
+ Dict with keys:
638
+ - ``checks``: list of dicts for each check result
639
+ - ``ok``: True if no critical failures
640
+ - ``warnings``: count of warn results
641
+ - ``failures``: count of fail results
642
+ """
643
+ methods = [
644
+ self.check_python,
645
+ self.check_packages,
646
+ self.check_ollama,
647
+ self.check_identity,
648
+ self.check_home_dirs,
649
+ self.check_config,
650
+ self.check_disk_space,
651
+ ]
652
+ results: list[CheckResult] = [m() for m in methods]
653
+ failures = [r for r in results if r.failed]
654
+ warnings = [r for r in results if r.warned]
655
+ critical_failures = [r for r in failures if r.critical]
656
+ return {
657
+ "ok": len(critical_failures) == 0,
658
+ "checks": [
659
+ {
660
+ "name": r.name,
661
+ "status": r.status,
662
+ "message": r.message,
663
+ "critical": r.critical,
664
+ }
665
+ for r in results
666
+ ],
667
+ "warnings": len(warnings),
668
+ "failures": len(failures),
669
+ "critical_failures": len(critical_failures),
670
+ }