@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,401 @@
1
+ """
2
+ State Diff — show what changed since the last sync/snapshot.
3
+
4
+ Compares the current agent state to the most recent sync seed
5
+ or saved snapshot, producing a clear diff of what's new, changed,
6
+ or removed across memories, trust, coordination, and pillars.
7
+
8
+ Tool-agnostic: works from any terminal, MCP, or the REPL shell.
9
+
10
+ Usage:
11
+ skcapstone diff # text diff to terminal
12
+ skcapstone diff --format json # machine-readable
13
+ skcapstone diff --save # save current state as baseline
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import json
19
+ import logging
20
+ from dataclasses import dataclass, field
21
+ from datetime import datetime, timezone
22
+ from pathlib import Path
23
+ from typing import Any
24
+
25
+ logger = logging.getLogger("skcapstone.state_diff")
26
+
27
+
28
+ @dataclass
29
+ class StateDiff:
30
+ """Diff between two agent state snapshots.
31
+
32
+ Attributes:
33
+ new_memories: Memory IDs added since last snapshot.
34
+ removed_memories: Memory IDs no longer present.
35
+ trust_changes: Dict of changed trust fields (old -> new).
36
+ new_tasks: Task IDs created since last snapshot.
37
+ completed_tasks: Task IDs completed since last snapshot.
38
+ pillar_changes: Dict of pillar status changes.
39
+ memory_count_before: Total memories at last snapshot.
40
+ memory_count_now: Total memories now.
41
+ snapshot_time: When the baseline snapshot was taken.
42
+ has_changes: Whether any changes were detected.
43
+ """
44
+
45
+ new_memories: list[dict[str, Any]] = field(default_factory=list)
46
+ removed_memories: list[str] = field(default_factory=list)
47
+ trust_changes: dict[str, dict[str, Any]] = field(default_factory=dict)
48
+ new_tasks: list[dict[str, Any]] = field(default_factory=list)
49
+ completed_tasks: list[dict[str, Any]] = field(default_factory=list)
50
+ pillar_changes: dict[str, dict[str, str]] = field(default_factory=dict)
51
+ memory_count_before: int = 0
52
+ memory_count_now: int = 0
53
+ snapshot_time: str = ""
54
+ has_changes: bool = False
55
+
56
+
57
+ SNAPSHOT_FILENAME = "state_snapshot.json"
58
+
59
+
60
+ def take_snapshot(home: Path) -> dict[str, Any]:
61
+ """Capture the current agent state as a snapshot.
62
+
63
+ Args:
64
+ home: Agent home directory (~/.skcapstone).
65
+
66
+ Returns:
67
+ Dict representing the full state at this moment.
68
+ """
69
+ snapshot: dict[str, Any] = {
70
+ "timestamp": datetime.now(timezone.utc).isoformat(),
71
+ "version": "1.0",
72
+ }
73
+
74
+ snapshot["memories"] = _snapshot_memories(home)
75
+ snapshot["trust"] = _snapshot_trust(home)
76
+ snapshot["tasks"] = _snapshot_tasks(home)
77
+ snapshot["pillars"] = _snapshot_pillars(home)
78
+
79
+ return snapshot
80
+
81
+
82
+ def save_snapshot(home: Path) -> Path:
83
+ """Save the current state as the diff baseline.
84
+
85
+ Args:
86
+ home: Agent home directory.
87
+
88
+ Returns:
89
+ Path to the saved snapshot file.
90
+ """
91
+ snapshot = take_snapshot(home)
92
+ snap_path = home / SNAPSHOT_FILENAME
93
+ snap_path.write_text(json.dumps(snapshot, indent=2, default=str), encoding="utf-8")
94
+ return snap_path
95
+
96
+
97
+ def load_snapshot(home: Path) -> dict[str, Any] | None:
98
+ """Load the most recent saved snapshot.
99
+
100
+ Falls back to the most recent sync seed if no snapshot exists.
101
+
102
+ Args:
103
+ home: Agent home directory.
104
+
105
+ Returns:
106
+ Snapshot dict, or None if no baseline exists.
107
+ """
108
+ snap_path = home / SNAPSHOT_FILENAME
109
+ if snap_path.exists():
110
+ try:
111
+ return json.loads(snap_path.read_text(encoding="utf-8"))
112
+ except (json.JSONDecodeError, OSError):
113
+ pass
114
+
115
+ return _find_latest_seed(home)
116
+
117
+
118
+ def compute_diff(home: Path) -> StateDiff:
119
+ """Compute the diff between current state and last snapshot.
120
+
121
+ Args:
122
+ home: Agent home directory.
123
+
124
+ Returns:
125
+ StateDiff with all detected changes.
126
+ """
127
+ baseline = load_snapshot(home)
128
+ current = take_snapshot(home)
129
+ diff = StateDiff()
130
+
131
+ if baseline is None:
132
+ diff.memory_count_now = len(current.get("memories", {}).get("ids", []))
133
+ diff.has_changes = True
134
+ diff.new_memories = [
135
+ {"id": m["id"], "content": m["content"][:80]}
136
+ for m in current.get("memories", {}).get("entries", [])
137
+ ]
138
+ return diff
139
+
140
+ diff.snapshot_time = baseline.get("timestamp", "unknown")
141
+
142
+ _diff_memories(baseline, current, diff)
143
+ _diff_trust(baseline, current, diff)
144
+ _diff_tasks(baseline, current, diff)
145
+ _diff_pillars(baseline, current, diff)
146
+
147
+ diff.has_changes = bool(
148
+ diff.new_memories or diff.removed_memories or diff.trust_changes
149
+ or diff.new_tasks or diff.completed_tasks or diff.pillar_changes
150
+ )
151
+
152
+ return diff
153
+
154
+
155
+ def format_text(diff: StateDiff) -> str:
156
+ """Format the diff as plain text.
157
+
158
+ Args:
159
+ diff: The computed state diff.
160
+
161
+ Returns:
162
+ Human-readable diff text.
163
+ """
164
+ lines = ["# Agent State Diff", ""]
165
+
166
+ if diff.snapshot_time:
167
+ lines.append(f"Since: {diff.snapshot_time}")
168
+ lines.append(
169
+ f"Memories: {diff.memory_count_before} -> {diff.memory_count_now}"
170
+ )
171
+ lines.append("")
172
+
173
+ if not diff.has_changes:
174
+ lines.append("No changes detected.")
175
+ return "\n".join(lines)
176
+
177
+ if diff.new_memories:
178
+ lines.append(f"+ {len(diff.new_memories)} new memor{'y' if len(diff.new_memories) == 1 else 'ies'}:")
179
+ for m in diff.new_memories[:10]:
180
+ lines.append(f" + {m['content'][:70]}")
181
+
182
+ if diff.removed_memories:
183
+ lines.append(f"- {len(diff.removed_memories)} removed memor{'y' if len(diff.removed_memories) == 1 else 'ies'}")
184
+
185
+ if diff.trust_changes:
186
+ lines.append("")
187
+ lines.append("Trust changes:")
188
+ for key, change in diff.trust_changes.items():
189
+ lines.append(f" {key}: {change.get('old')} -> {change.get('new')}")
190
+
191
+ if diff.completed_tasks:
192
+ lines.append("")
193
+ lines.append(f"Completed {len(diff.completed_tasks)} task(s):")
194
+ for t in diff.completed_tasks:
195
+ lines.append(f" [done] {t.get('title', t.get('id', '?'))}")
196
+
197
+ if diff.new_tasks:
198
+ lines.append("")
199
+ lines.append(f"Created {len(diff.new_tasks)} task(s):")
200
+ for t in diff.new_tasks:
201
+ lines.append(f" [new] {t.get('title', t.get('id', '?'))}")
202
+
203
+ if diff.pillar_changes:
204
+ lines.append("")
205
+ lines.append("Pillar changes:")
206
+ for name, change in diff.pillar_changes.items():
207
+ lines.append(f" {name}: {change.get('old')} -> {change.get('new')}")
208
+
209
+ lines.append("")
210
+ return "\n".join(lines)
211
+
212
+
213
+ def format_json(diff: StateDiff) -> str:
214
+ """Format the diff as JSON.
215
+
216
+ Args:
217
+ diff: The computed state diff.
218
+
219
+ Returns:
220
+ JSON string.
221
+ """
222
+ return json.dumps({
223
+ "has_changes": diff.has_changes,
224
+ "snapshot_time": diff.snapshot_time,
225
+ "memories": {
226
+ "before": diff.memory_count_before,
227
+ "now": diff.memory_count_now,
228
+ "new": len(diff.new_memories),
229
+ "removed": len(diff.removed_memories),
230
+ "new_entries": diff.new_memories[:20],
231
+ },
232
+ "trust_changes": diff.trust_changes,
233
+ "tasks": {
234
+ "new": diff.new_tasks,
235
+ "completed": diff.completed_tasks,
236
+ },
237
+ "pillar_changes": diff.pillar_changes,
238
+ }, indent=2, default=str)
239
+
240
+
241
+ FORMATTERS = {"text": format_text, "json": format_json}
242
+
243
+
244
+ # ═══════════════════════════════════════════════════════════════════════════
245
+ # Snapshot helpers
246
+ # ═══════════════════════════════════════════════════════════════════════════
247
+
248
+
249
+ def _snapshot_memories(home: Path) -> dict[str, Any]:
250
+ """Capture memory state."""
251
+ from .memory_engine import list_memories
252
+ try:
253
+ entries = list_memories(home, limit=10000)
254
+ return {
255
+ "count": len(entries),
256
+ "ids": [e.memory_id for e in entries],
257
+ "entries": [
258
+ {"id": e.memory_id, "content": e.content[:100], "layer": e.layer.value}
259
+ for e in entries
260
+ ],
261
+ }
262
+ except Exception as exc:
263
+ logger.warning("Failed to snapshot memories: %s", exc)
264
+ return {"count": 0, "ids": [], "entries": []}
265
+
266
+
267
+ def _snapshot_trust(home: Path) -> dict[str, Any]:
268
+ """Capture trust state."""
269
+ trust_file = home / "trust" / "trust.json"
270
+ if not trust_file.exists():
271
+ return {}
272
+ try:
273
+ return json.loads(trust_file.read_text(encoding="utf-8"))
274
+ except (json.JSONDecodeError, OSError):
275
+ return {}
276
+
277
+
278
+ def _snapshot_tasks(home: Path) -> dict[str, Any]:
279
+ """Capture coordination board state."""
280
+ from .coordination import Board
281
+ try:
282
+ board = Board(home)
283
+ views = board.get_task_views()
284
+ return {
285
+ "total": len(views),
286
+ "done_ids": [v.task.id for v in views if v.status.value == "done"],
287
+ "all_ids": [v.task.id for v in views],
288
+ "tasks": [
289
+ {"id": v.task.id, "title": v.task.title, "status": v.status.value}
290
+ for v in views
291
+ ],
292
+ }
293
+ except Exception as exc:
294
+ logger.warning("Failed to snapshot tasks: %s", exc)
295
+ return {"total": 0, "done_ids": [], "all_ids": [], "tasks": []}
296
+
297
+
298
+ def _snapshot_pillars(home: Path) -> dict[str, str]:
299
+ """Capture pillar statuses."""
300
+ from .discovery import discover_all
301
+ try:
302
+ states = discover_all(home)
303
+ return {name: state.status.value for name, state in states.items()}
304
+ except Exception as exc:
305
+ logger.warning("Failed to snapshot pillars: %s", exc)
306
+ return {}
307
+
308
+
309
+ def _find_latest_seed(home: Path) -> dict[str, Any] | None:
310
+ """Find the most recent sync seed as a fallback baseline."""
311
+ for subdir in ("outbox", "archive"):
312
+ seed_dir = home / "sync" / subdir
313
+ if not seed_dir.exists():
314
+ continue
315
+ seeds = sorted(seed_dir.glob("*.seed.json*"), reverse=True)
316
+ if seeds:
317
+ try:
318
+ data = json.loads(seeds[0].read_text(encoding="utf-8"))
319
+ memory_data = data.get("memory", {})
320
+ return {
321
+ "timestamp": data.get("created_at", "unknown"),
322
+ "memories": {
323
+ "count": memory_data.get("total", 0),
324
+ "ids": [],
325
+ "entries": [],
326
+ },
327
+ "trust": data.get("trust", {}),
328
+ "tasks": {"total": 0, "done_ids": [], "all_ids": [], "tasks": []},
329
+ "pillars": {},
330
+ }
331
+ except (json.JSONDecodeError, OSError):
332
+ continue
333
+ return None
334
+
335
+
336
+ # ═══════════════════════════════════════════════════════════════════════════
337
+ # Diff computation
338
+ # ═══════════════════════════════════════════════════════════════════════════
339
+
340
+
341
+ def _diff_memories(baseline: dict, current: dict, diff: StateDiff) -> None:
342
+ """Compute memory differences."""
343
+ old_mem = baseline.get("memories", {})
344
+ new_mem = current.get("memories", {})
345
+
346
+ old_ids = set(old_mem.get("ids", []))
347
+ new_ids = set(new_mem.get("ids", []))
348
+
349
+ diff.memory_count_before = old_mem.get("count", len(old_ids))
350
+ diff.memory_count_now = new_mem.get("count", len(new_ids))
351
+
352
+ added_ids = new_ids - old_ids
353
+ diff.removed_memories = list(old_ids - new_ids)
354
+
355
+ entries_by_id = {e["id"]: e for e in new_mem.get("entries", [])}
356
+ diff.new_memories = [
357
+ entries_by_id.get(mid, {"id": mid, "content": ""})
358
+ for mid in added_ids
359
+ ]
360
+
361
+
362
+ def _diff_trust(baseline: dict, current: dict, diff: StateDiff) -> None:
363
+ """Compute trust state differences."""
364
+ old_trust = baseline.get("trust", {})
365
+ new_trust = current.get("trust", {})
366
+
367
+ for key in ("depth", "trust_level", "love_intensity", "entangled"):
368
+ old_val = old_trust.get(key)
369
+ new_val = new_trust.get(key)
370
+ if old_val != new_val and new_val is not None:
371
+ diff.trust_changes[key] = {"old": old_val, "new": new_val}
372
+
373
+
374
+ def _diff_tasks(baseline: dict, current: dict, diff: StateDiff) -> None:
375
+ """Compute coordination board differences."""
376
+ old_tasks = baseline.get("tasks", {})
377
+ new_tasks = current.get("tasks", {})
378
+
379
+ old_ids = set(old_tasks.get("all_ids", []))
380
+ new_ids = set(new_tasks.get("all_ids", []))
381
+ old_done = set(old_tasks.get("done_ids", []))
382
+ new_done = set(new_tasks.get("done_ids", []))
383
+
384
+ created_ids = new_ids - old_ids
385
+ newly_done = new_done - old_done
386
+
387
+ tasks_by_id = {t["id"]: t for t in new_tasks.get("tasks", [])}
388
+ diff.new_tasks = [tasks_by_id.get(tid, {"id": tid}) for tid in created_ids]
389
+ diff.completed_tasks = [tasks_by_id.get(tid, {"id": tid}) for tid in newly_done]
390
+
391
+
392
+ def _diff_pillars(baseline: dict, current: dict, diff: StateDiff) -> None:
393
+ """Compute pillar status changes."""
394
+ old_p = baseline.get("pillars", {})
395
+ new_p = current.get("pillars", {})
396
+
397
+ for name in set(list(old_p.keys()) + list(new_p.keys())):
398
+ old_val = old_p.get(name, "unknown")
399
+ new_val = new_p.get(name, "unknown")
400
+ if old_val != new_val:
401
+ diff.pillar_changes[name] = {"old": old_val, "new": new_val}
@@ -0,0 +1,270 @@
1
+ """
2
+ Sovereign agent morning briefing.
3
+
4
+ One screen. Everything you need to know. The first command of
5
+ every session. Gathers data from every pillar and presents a
6
+ compact, information-dense overview.
7
+
8
+ Usage:
9
+ skcapstone summary
10
+ skcapstone summary --json-out
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import json
16
+ import logging
17
+ from datetime import datetime, timezone
18
+ from pathlib import Path
19
+ from typing import Optional
20
+
21
+ logger = logging.getLogger("skcapstone.summary")
22
+
23
+
24
+ def gather_briefing(home: Path) -> dict:
25
+ """Gather all data for the morning briefing.
26
+
27
+ Pulls from runtime, memory, coordination board, peers,
28
+ backups, and doctor diagnostics to build a single dict.
29
+
30
+ Args:
31
+ home: Agent home directory (~/.skcapstone).
32
+
33
+ Returns:
34
+ dict: Complete briefing data.
35
+ """
36
+ briefing = {
37
+ "timestamp": datetime.now(timezone.utc).isoformat(),
38
+ "agent": _agent_info(home),
39
+ "pillars": _pillar_summary(home),
40
+ "memory": _memory_summary(home),
41
+ "board": _board_summary(home),
42
+ "peers": _peer_summary(home),
43
+ "inbox": _inbox_summary(home),
44
+ "sync": _sync_summary(home),
45
+ "backups": _backup_summary(home),
46
+ "health": _health_summary(home),
47
+ "journal": _journal_summary(),
48
+ }
49
+ return briefing
50
+
51
+
52
+ def _agent_info(home: Path) -> dict:
53
+ """Load basic agent identity and consciousness."""
54
+ try:
55
+ from .runtime import get_runtime
56
+
57
+ runtime = get_runtime(home)
58
+ m = runtime.manifest
59
+
60
+ if m.is_singular:
61
+ consciousness = "SINGULAR"
62
+ elif m.is_conscious:
63
+ consciousness = "CONSCIOUS"
64
+ else:
65
+ consciousness = "AWAKENING"
66
+
67
+ return {
68
+ "name": m.name,
69
+ "version": m.version,
70
+ "consciousness": consciousness,
71
+ "home": str(m.home),
72
+ }
73
+ except Exception as exc:
74
+ logger.warning("Failed to load agent info: %s", exc)
75
+ return {"name": "unknown", "consciousness": "UNKNOWN", "home": str(home)}
76
+
77
+
78
+ def _pillar_summary(home: Path) -> dict:
79
+ """Get one-line status for each pillar."""
80
+ try:
81
+ from .runtime import get_runtime
82
+
83
+ runtime = get_runtime(home)
84
+ m = runtime.manifest
85
+ return {k: v.value for k, v in m.pillar_summary.items()}
86
+ except Exception as exc:
87
+ logger.warning("Failed to load pillar summary: %s", exc)
88
+ return {}
89
+
90
+
91
+ def _memory_summary(home: Path) -> dict:
92
+ """Get memory counts and recent entries."""
93
+ try:
94
+ from .memory_engine import get_stats, list_memories
95
+ from .models import MemoryLayer
96
+
97
+ stats = get_stats(home)
98
+ recent = list_memories(home, limit=3)
99
+ recent_titles = []
100
+ for entry in recent:
101
+ preview = entry.content[:60] + "..." if len(entry.content) > 60 else entry.content
102
+ recent_titles.append(preview)
103
+
104
+ return {
105
+ "total": stats.total_memories,
106
+ "short_term": stats.short_term,
107
+ "mid_term": stats.mid_term,
108
+ "long_term": stats.long_term,
109
+ "recent": recent_titles,
110
+ }
111
+ except Exception as exc:
112
+ logger.warning("Failed to load memory summary: %s", exc)
113
+ return {"total": 0, "recent": []}
114
+
115
+
116
+ def _board_summary(home: Path) -> dict:
117
+ """Get coordination board stats and active tasks."""
118
+ try:
119
+ from .coordination import Board
120
+
121
+ board = Board(home)
122
+ views = board.get_task_views()
123
+
124
+ done = sum(1 for v in views if v.status.value == "done")
125
+ open_count = sum(1 for v in views if v.status.value == "open")
126
+ in_progress = sum(1 for v in views if v.status.value in ("in_progress", "claimed"))
127
+
128
+ active_tasks = [
129
+ {"title": v.task.title[:50], "assignee": v.claimed_by or "unassigned"}
130
+ for v in views
131
+ if v.status.value in ("in_progress", "claimed", "open")
132
+ ][:5]
133
+
134
+ return {
135
+ "total": len(views),
136
+ "done": done,
137
+ "open": open_count,
138
+ "in_progress": in_progress,
139
+ "active_tasks": active_tasks,
140
+ }
141
+ except Exception as exc:
142
+ logger.warning("Failed to load board summary: %s", exc)
143
+ return {"total": 0, "done": 0, "open": 0, "in_progress": 0, "active_tasks": []}
144
+
145
+
146
+ def _peer_summary(home: Path) -> dict:
147
+ """Count known peers."""
148
+ try:
149
+ from .peers import list_peers
150
+
151
+ peers = list_peers(skcapstone_home=home)
152
+ names = [p.name for p in peers[:5]]
153
+ return {"count": len(peers), "names": names}
154
+ except Exception as exc:
155
+ logger.debug("Peer discovery unavailable: %s", exc)
156
+ peers_dir = home / "peers"
157
+ if peers_dir.exists():
158
+ count = sum(1 for f in peers_dir.glob("*.json"))
159
+ return {"count": count, "names": []}
160
+ return {"count": 0, "names": []}
161
+
162
+
163
+ def _backup_summary(home: Path) -> dict:
164
+ """Get last backup info."""
165
+ try:
166
+ from .backup import list_backups
167
+
168
+ backups = list_backups(home)
169
+ if backups:
170
+ latest = backups[0]
171
+ return {
172
+ "count": len(backups),
173
+ "latest": latest.get("created_at", "")[:19],
174
+ "encrypted": latest.get("encrypted", False),
175
+ }
176
+ return {"count": 0, "latest": None}
177
+ except Exception as exc:
178
+ logger.debug("Backup info unavailable: %s", exc)
179
+ return {"count": 0, "latest": None}
180
+
181
+
182
+ def _health_summary(home: Path) -> dict:
183
+ """Get doctor pass/fail counts."""
184
+ try:
185
+ from .doctor import run_diagnostics
186
+
187
+ report = run_diagnostics(home)
188
+ return {
189
+ "passed": report.passed_count,
190
+ "failed": report.failed_count,
191
+ "total": report.total_count,
192
+ "all_passed": report.all_passed,
193
+ }
194
+ except Exception as exc:
195
+ logger.debug("Doctor diagnostics unavailable: %s", exc)
196
+ return {"passed": 0, "failed": 0, "total": 0, "all_passed": False}
197
+
198
+
199
+ def _inbox_summary(home: Path) -> dict:
200
+ """Count unread messages in the SKComm and SKChat inboxes."""
201
+ total = 0
202
+ sources: list[str] = []
203
+
204
+ # SKComm file transport inbox
205
+ skcomm_inbox = home / "comms" / "inbox"
206
+ if skcomm_inbox.exists():
207
+ count = sum(1 for f in skcomm_inbox.iterdir() if f.is_file())
208
+ if count:
209
+ total += count
210
+ sources.append(f"skcomm:{count}")
211
+
212
+ # SKChat local inbox (skchat daemon stores messages here)
213
+ skchat_inbox = home / "skchat" / "inbox"
214
+ if skchat_inbox.exists():
215
+ count = sum(1 for f in skchat_inbox.iterdir() if f.is_file())
216
+ if count:
217
+ total += count
218
+ sources.append(f"skchat:{count}")
219
+
220
+ return {"count": total, "sources": sources}
221
+
222
+
223
+ def _sync_summary(home: Path) -> dict:
224
+ """Get sync pillar status: transport, seed count, last push."""
225
+ try:
226
+ from .runtime import get_runtime
227
+
228
+ runtime = get_runtime(home)
229
+ sy = runtime.manifest.sync
230
+ result: dict = {
231
+ "status": sy.status.value,
232
+ "seed_count": sy.seed_count,
233
+ "transport": sy.transport.value if sy.transport else None,
234
+ "gpg": bool(sy.gpg_fingerprint),
235
+ "last_push": sy.last_push.isoformat() if sy.last_push else None,
236
+ }
237
+ return result
238
+ except Exception as exc:
239
+ logger.debug("Sync summary from runtime unavailable: %s", exc)
240
+ # Fallback: inspect sync directory directly
241
+ sync_dir = home / "sync"
242
+ seeds = list(sync_dir.glob("*.tar.gz*")) if sync_dir.exists() else []
243
+ return {
244
+ "status": "active" if sync_dir.exists() else "missing",
245
+ "seed_count": len(seeds),
246
+ "transport": None,
247
+ "gpg": False,
248
+ "last_push": None,
249
+ }
250
+
251
+
252
+ def _journal_summary() -> dict:
253
+ """Get journal entry count and latest title."""
254
+ try:
255
+ from skmemory.journal import Journal
256
+
257
+ journal = Journal()
258
+ count = journal.count_entries()
259
+ latest = ""
260
+ if count > 0:
261
+ text = journal.read_latest(1)
262
+ for line in text.split("\n"):
263
+ if line.startswith("## "):
264
+ latest = line[3:].strip()
265
+ break
266
+
267
+ return {"entries": count, "latest_title": latest}
268
+ except Exception as exc:
269
+ logger.debug("Journal unavailable: %s", exc)
270
+ return {"entries": 0, "latest_title": ""}