@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,223 @@
1
+ """Tests for skcapstone skills CLI commands."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from unittest.mock import MagicMock, patch
6
+
7
+ import pytest
8
+ from click.testing import CliRunner
9
+
10
+ from skcapstone.cli import main
11
+
12
+
13
+ # ---------------------------------------------------------------------------
14
+ # Helpers
15
+ # ---------------------------------------------------------------------------
16
+
17
+ def _make_registry_client(skills: list[dict] | None = None) -> MagicMock:
18
+ """Return a mock RegistryClient pre-populated with skills."""
19
+ if skills is None:
20
+ skills = [
21
+ {
22
+ "name": "syncthing-setup",
23
+ "version": "1.0.0",
24
+ "description": "Syncthing sovereign sync",
25
+ "tags": ["sync"],
26
+ },
27
+ {
28
+ "name": "pgp-identity",
29
+ "version": "0.2.0",
30
+ "description": "PGP key management",
31
+ "tags": ["identity"],
32
+ },
33
+ ]
34
+
35
+ client = MagicMock()
36
+ client.list_skills.return_value = skills
37
+ client.search.return_value = [skills[0]] if skills else []
38
+ return client
39
+
40
+
41
+ # ---------------------------------------------------------------------------
42
+ # skcapstone skills list
43
+ # ---------------------------------------------------------------------------
44
+
45
+
46
+ class TestSkillsList:
47
+ """Tests for 'skcapstone skills list'."""
48
+
49
+ def test_list_all_skills_renders_table(self):
50
+ """Happy path: list renders a table with skill rows."""
51
+ runner = CliRunner()
52
+ client = _make_registry_client()
53
+
54
+ with patch("skcapstone.cli.skills_cmd.get_registry_client", return_value=client):
55
+ result = runner.invoke(main, ["skills", "list"])
56
+
57
+ assert result.exit_code == 0
58
+ assert "syncthing-setup" in result.output
59
+ assert "pgp-identity" in result.output
60
+
61
+ def test_list_with_query_calls_search(self):
62
+ """--query flag should invoke client.search(), not list_skills()."""
63
+ runner = CliRunner()
64
+ client = _make_registry_client()
65
+
66
+ with patch("skcapstone.cli.skills_cmd.get_registry_client", return_value=client):
67
+ result = runner.invoke(main, ["skills", "list", "--query", "syncthing"])
68
+
69
+ assert result.exit_code == 0
70
+ client.search.assert_called_once_with("syncthing")
71
+ client.list_skills.assert_not_called()
72
+
73
+ def test_list_json_output(self):
74
+ """--json flag should output valid JSON, not a Rich table."""
75
+ import json
76
+
77
+ runner = CliRunner()
78
+ client = _make_registry_client()
79
+
80
+ with patch("skcapstone.cli.skills_cmd.get_registry_client", return_value=client):
81
+ result = runner.invoke(main, ["skills", "list", "--json"])
82
+
83
+ assert result.exit_code == 0
84
+ parsed = json.loads(result.output)
85
+ assert isinstance(parsed, list)
86
+ assert parsed[0]["name"] == "syncthing-setup"
87
+
88
+ def test_list_no_skills_empty_message(self):
89
+ """Empty registry should print a helpful 'no skills' message."""
90
+ runner = CliRunner()
91
+ client = _make_registry_client(skills=[])
92
+
93
+ with patch("skcapstone.cli.skills_cmd.get_registry_client", return_value=client):
94
+ result = runner.invoke(main, ["skills", "list"])
95
+
96
+ assert result.exit_code == 0
97
+ assert "No skills found" in result.output
98
+
99
+ def test_list_skskills_not_installed(self):
100
+ """When get_registry_client returns None the command exits 1."""
101
+ runner = CliRunner()
102
+
103
+ with patch("skcapstone.cli.skills_cmd.get_registry_client", return_value=None):
104
+ result = runner.invoke(main, ["skills", "list"])
105
+
106
+ assert result.exit_code == 1
107
+ assert "skskills not installed" in result.output
108
+
109
+ def test_list_registry_error_exits_1(self):
110
+ """Registry connection error should print an error and exit 1."""
111
+ runner = CliRunner()
112
+ client = MagicMock()
113
+ client.list_skills.side_effect = ConnectionError("offline")
114
+
115
+ with patch("skcapstone.cli.skills_cmd.get_registry_client", return_value=client):
116
+ result = runner.invoke(main, ["skills", "list"])
117
+
118
+ assert result.exit_code == 1
119
+ assert "Registry error" in result.output
120
+
121
+
122
+ # ---------------------------------------------------------------------------
123
+ # skcapstone skills install
124
+ # ---------------------------------------------------------------------------
125
+
126
+
127
+ class TestSkillsInstall:
128
+ """Tests for 'skcapstone skills install'."""
129
+
130
+ def _install_result(self, name: str = "syncthing-setup") -> dict:
131
+ return {
132
+ "name": name,
133
+ "version": "1.0.0",
134
+ "agent": "global",
135
+ "install_path": "/home/user/.skskills/installed/syncthing-setup",
136
+ "status": "installed",
137
+ }
138
+
139
+ def test_install_happy_path(self):
140
+ """Successful install prints the skill name, version, and path."""
141
+ runner = CliRunner()
142
+ client = MagicMock()
143
+ client.install.return_value = self._install_result()
144
+
145
+ with patch("skcapstone.cli.skills_cmd.get_registry_client", return_value=client):
146
+ result = runner.invoke(main, ["skills", "install", "syncthing-setup"])
147
+
148
+ assert result.exit_code == 0
149
+ assert "Installed" in result.output
150
+ assert "syncthing-setup" in result.output
151
+ client.install.assert_called_once_with(
152
+ "syncthing-setup", version=None, agent="global", force=False
153
+ )
154
+
155
+ def test_install_with_version_flag(self):
156
+ """--version should be forwarded to client.install()."""
157
+ runner = CliRunner()
158
+ client = MagicMock()
159
+ client.install.return_value = self._install_result()
160
+
161
+ with patch("skcapstone.cli.skills_cmd.get_registry_client", return_value=client):
162
+ result = runner.invoke(
163
+ main, ["skills", "install", "syncthing-setup", "--version", "0.9.0"]
164
+ )
165
+
166
+ assert result.exit_code == 0
167
+ client.install.assert_called_once_with(
168
+ "syncthing-setup", version="0.9.0", agent="global", force=False
169
+ )
170
+
171
+ def test_install_with_agent_flag(self):
172
+ """--agent should be forwarded to client.install()."""
173
+ runner = CliRunner()
174
+ client = MagicMock()
175
+ client.install.return_value = self._install_result()
176
+
177
+ with patch("skcapstone.cli.skills_cmd.get_registry_client", return_value=client):
178
+ result = runner.invoke(
179
+ main, ["skills", "install", "syncthing-setup", "--agent", "opus"]
180
+ )
181
+
182
+ assert result.exit_code == 0
183
+ client.install.assert_called_once_with(
184
+ "syncthing-setup", version=None, agent="opus", force=False
185
+ )
186
+
187
+ def test_install_not_found_exits_1(self):
188
+ """FileNotFoundError from the registry should exit 1 with a helpful message."""
189
+ runner = CliRunner()
190
+ client = MagicMock()
191
+ client.install.side_effect = FileNotFoundError("unknown-skill not in registry")
192
+
193
+ with patch("skcapstone.cli.skills_cmd.get_registry_client", return_value=client):
194
+ result = runner.invoke(main, ["skills", "install", "unknown-skill"])
195
+
196
+ assert result.exit_code == 1
197
+ assert "Not found" in result.output
198
+
199
+ def test_install_skskills_not_installed(self):
200
+ """When get_registry_client returns None the command exits 1."""
201
+ runner = CliRunner()
202
+
203
+ with patch("skcapstone.cli.skills_cmd.get_registry_client", return_value=None):
204
+ result = runner.invoke(main, ["skills", "install", "syncthing-setup"])
205
+
206
+ assert result.exit_code == 1
207
+ assert "skskills not installed" in result.output
208
+
209
+ def test_install_force_flag(self):
210
+ """--force should be forwarded to client.install()."""
211
+ runner = CliRunner()
212
+ client = MagicMock()
213
+ client.install.return_value = self._install_result()
214
+
215
+ with patch("skcapstone.cli.skills_cmd.get_registry_client", return_value=client):
216
+ result = runner.invoke(
217
+ main, ["skills", "install", "syncthing-setup", "--force"]
218
+ )
219
+
220
+ assert result.exit_code == 0
221
+ client.install.assert_called_once_with(
222
+ "syncthing-setup", version=None, agent="global", force=True
223
+ )
@@ -0,0 +1,395 @@
1
+ """Tests for the enhanced skcapstone status command and doctor CLI.
2
+
3
+ Covers:
4
+ - _probe_llm_backends() helper
5
+ - _get_daemon_info() helper
6
+ - _get_last_conversation() helper
7
+ - status --help
8
+ - status CLI output (sections: daemon, backends, disk, conversation)
9
+ - doctor CLI (smoke tests)
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import json
15
+ import os
16
+ import time
17
+ from pathlib import Path
18
+ from unittest.mock import MagicMock, patch
19
+
20
+ import pytest
21
+ import yaml
22
+ from click.testing import CliRunner
23
+
24
+
25
+ # ---------------------------------------------------------------------------
26
+ # Shared fixture
27
+ # ---------------------------------------------------------------------------
28
+
29
+
30
+ @pytest.fixture
31
+ def agent_home(tmp_path: Path) -> Path:
32
+ """Fully initialised agent home with minimal required files."""
33
+ home = tmp_path / ".skcapstone"
34
+ for d in [
35
+ "identity", "memory", "trust", "security", "sync", "config",
36
+ "memory/short-term", "memory/mid-term", "memory/long-term",
37
+ ]:
38
+ (home / d).mkdir(parents=True, exist_ok=True)
39
+
40
+ (home / "manifest.json").write_text(json.dumps({
41
+ "name": "TestAgent", "version": "0.1.0",
42
+ }))
43
+ (home / "identity" / "identity.json").write_text(json.dumps({
44
+ "name": "TestAgent",
45
+ "fingerprint": "DEADBEEF12345678",
46
+ "capauth_managed": True,
47
+ }))
48
+ (home / "config" / "config.yaml").write_text(
49
+ yaml.dump({"agent_name": "TestAgent"})
50
+ )
51
+ return home
52
+
53
+
54
+ # ---------------------------------------------------------------------------
55
+ # Unit tests for helper functions
56
+ # ---------------------------------------------------------------------------
57
+
58
+
59
+ class TestProbeLlmBackends:
60
+ """Tests for _probe_llm_backends()."""
61
+
62
+ def test_ollama_available_when_reachable(self):
63
+ """ollama marked True when HTTP probe succeeds."""
64
+ from skcapstone.cli.status import _probe_llm_backends
65
+
66
+ mock_resp = MagicMock()
67
+ mock_resp.__enter__ = lambda s: s
68
+ mock_resp.__exit__ = MagicMock(return_value=False)
69
+
70
+ with patch("urllib.request.urlopen", return_value=mock_resp):
71
+ result = _probe_llm_backends()
72
+
73
+ assert result["ollama"] is True
74
+
75
+ def test_ollama_unavailable_when_unreachable(self):
76
+ """ollama marked False when HTTP probe raises."""
77
+ from skcapstone.cli.status import _probe_llm_backends
78
+
79
+ with patch("urllib.request.urlopen", side_effect=OSError("refused")):
80
+ result = _probe_llm_backends()
81
+
82
+ assert result["ollama"] is False
83
+
84
+ def test_cloud_backends_via_env(self, monkeypatch):
85
+ """Cloud backends detected via environment variables."""
86
+ from skcapstone.cli.status import _probe_llm_backends
87
+
88
+ monkeypatch.setenv("ANTHROPIC_API_KEY", "test-key")
89
+ monkeypatch.setenv("XAI_API_KEY", "test-key")
90
+ monkeypatch.delenv("MOONSHOT_API_KEY", raising=False)
91
+ monkeypatch.delenv("NVIDIA_API_KEY", raising=False)
92
+
93
+ with patch("urllib.request.urlopen", side_effect=OSError):
94
+ result = _probe_llm_backends()
95
+
96
+ assert result["anthropic"] is True
97
+ assert result["grok"] is True
98
+ assert result["kimi"] is False
99
+ assert result["nvidia"] is False
100
+
101
+ def test_returns_all_expected_keys(self):
102
+ """Result always contains the five expected backend keys."""
103
+ from skcapstone.cli.status import _probe_llm_backends
104
+
105
+ with patch("urllib.request.urlopen", side_effect=OSError):
106
+ result = _probe_llm_backends()
107
+
108
+ assert set(result.keys()) == {"ollama", "anthropic", "grok", "kimi", "nvidia"}
109
+
110
+
111
+ class TestGetDaemonInfo:
112
+ """Tests for _get_daemon_info()."""
113
+
114
+ def test_stopped_when_no_pid(self, agent_home: Path):
115
+ """Returns running=False when no PID file exists."""
116
+ from skcapstone.cli.status import _get_daemon_info
117
+
118
+ with patch("skcapstone.cli.status._get_daemon_info.__module__"):
119
+ pass # ensure module is imported
120
+
121
+ with patch("skcapstone.daemon.read_pid", return_value=None):
122
+ result = _get_daemon_info(agent_home)
123
+
124
+ assert result["running"] is False
125
+
126
+ def test_running_with_pid(self, agent_home: Path):
127
+ """Returns running=True with PID when daemon is alive."""
128
+ from skcapstone.cli.status import _get_daemon_info
129
+
130
+ with patch("skcapstone.daemon.read_pid", return_value=12345), \
131
+ patch("skcapstone.daemon.get_daemon_status", return_value=None):
132
+ result = _get_daemon_info(agent_home)
133
+
134
+ assert result["running"] is True
135
+ assert result["pid"] == 12345
136
+
137
+ def test_uptime_formatting_seconds(self, agent_home: Path):
138
+ """Uptime < 60s shown as Xs."""
139
+ from skcapstone.cli.status import _get_daemon_info
140
+
141
+ with patch("skcapstone.daemon.read_pid", return_value=1), \
142
+ patch("skcapstone.daemon.get_daemon_status", return_value={"uptime_seconds": 45}):
143
+ result = _get_daemon_info(agent_home)
144
+
145
+ assert result["uptime"] == "45s"
146
+
147
+ def test_uptime_formatting_minutes(self, agent_home: Path):
148
+ """Uptime 60–3599s shown as Xm Ys."""
149
+ from skcapstone.cli.status import _get_daemon_info
150
+
151
+ with patch("skcapstone.daemon.read_pid", return_value=1), \
152
+ patch("skcapstone.daemon.get_daemon_status", return_value={"uptime_seconds": 125}):
153
+ result = _get_daemon_info(agent_home)
154
+
155
+ assert result["uptime"] == "2m 5s"
156
+
157
+ def test_uptime_formatting_hours(self, agent_home: Path):
158
+ """Uptime >= 3600s shown as Xh Ym."""
159
+ from skcapstone.cli.status import _get_daemon_info
160
+
161
+ with patch("skcapstone.daemon.read_pid", return_value=1), \
162
+ patch("skcapstone.daemon.get_daemon_status", return_value={"uptime_seconds": 7320}):
163
+ result = _get_daemon_info(agent_home)
164
+
165
+ assert result["uptime"] == "2h 2m"
166
+
167
+ def test_message_count_included(self, agent_home: Path):
168
+ """messages key present when daemon reports > 0 messages."""
169
+ from skcapstone.cli.status import _get_daemon_info
170
+
171
+ with patch("skcapstone.daemon.read_pid", return_value=1), \
172
+ patch("skcapstone.daemon.get_daemon_status", return_value={
173
+ "uptime_seconds": 60, "messages_received": 7,
174
+ }):
175
+ result = _get_daemon_info(agent_home)
176
+
177
+ assert result["messages"] == 7
178
+
179
+
180
+ class TestGetLastConversation:
181
+ """Tests for _get_last_conversation()."""
182
+
183
+ def test_none_when_no_conv_dir(self, agent_home: Path):
184
+ """Returns None when conversations/ directory does not exist."""
185
+ from skcapstone.cli.status import _get_last_conversation
186
+
187
+ result = _get_last_conversation(agent_home)
188
+ assert result is None
189
+
190
+ def test_none_when_empty_conv_dir(self, agent_home: Path):
191
+ """Returns None when conversations/ is empty."""
192
+ from skcapstone.cli.status import _get_last_conversation
193
+
194
+ (agent_home / "conversations").mkdir()
195
+ result = _get_last_conversation(agent_home)
196
+ assert result is None
197
+
198
+ def test_returns_most_recent_peer(self, agent_home: Path):
199
+ """Returns the peer name of the most-recently-touched file."""
200
+ from skcapstone.cli.status import _get_last_conversation
201
+
202
+ conv_dir = agent_home / "conversations"
203
+ conv_dir.mkdir()
204
+ old_file = conv_dir / "alice.json"
205
+ new_file = conv_dir / "bob.json"
206
+ old_file.write_text("[]")
207
+ time.sleep(0.02) # ensure mtime difference
208
+ new_file.write_text("[]")
209
+
210
+ result = _get_last_conversation(agent_home)
211
+ assert result is not None
212
+ assert result["peer"] == "bob"
213
+ assert "when" in result
214
+
215
+ def test_recent_convo_shows_minutes(self, agent_home: Path):
216
+ """A freshly written file shows time in minutes."""
217
+ from skcapstone.cli.status import _get_last_conversation
218
+
219
+ conv_dir = agent_home / "conversations"
220
+ conv_dir.mkdir()
221
+ (conv_dir / "charlie.json").write_text("[]")
222
+
223
+ result = _get_last_conversation(agent_home)
224
+ assert result is not None
225
+ assert "m ago" in result["when"] or "0m ago" in result["when"]
226
+
227
+
228
+ # ---------------------------------------------------------------------------
229
+ # CLI integration tests (via CliRunner)
230
+ # ---------------------------------------------------------------------------
231
+
232
+
233
+ class TestStatusCLI:
234
+ """CLI integration tests for the status command."""
235
+
236
+ def test_help(self):
237
+ """status --help exits 0 and shows description."""
238
+ from skcapstone.cli import main
239
+
240
+ result = CliRunner().invoke(main, ["status", "--help"])
241
+ assert result.exit_code == 0
242
+ assert "sovereign" in result.output.lower()
243
+
244
+ def test_missing_home_exits_nonzero(self, tmp_path: Path):
245
+ """status on a nonexistent home exits with code 1."""
246
+ from skcapstone.cli import main
247
+
248
+ result = CliRunner().invoke(main, ["status", "--home", str(tmp_path / "nope")])
249
+ assert result.exit_code != 0
250
+
251
+ def test_daemon_stopped_shown(self, agent_home: Path):
252
+ """'STOPPED' appears when daemon is not running."""
253
+ from skcapstone.cli import main
254
+
255
+ with patch("skcapstone.cli.status._get_daemon_info", return_value={"running": False}), \
256
+ patch("skcapstone.cli.status._probe_llm_backends", return_value={
257
+ "ollama": False, "anthropic": False, "grok": False,
258
+ "kimi": False, "nvidia": False,
259
+ }), \
260
+ patch("skcapstone.cli.status._print_consciousness_metrics"), \
261
+ patch("skcapstone.cli.status._get_last_conversation", return_value=None):
262
+ result = CliRunner().invoke(main, ["status", "--home", str(agent_home)])
263
+
264
+ assert result.exit_code == 0
265
+ assert "STOPPED" in result.output
266
+
267
+ def test_daemon_running_shown(self, agent_home: Path):
268
+ """'RUNNING' appears with PID when daemon is alive."""
269
+ from skcapstone.cli import main
270
+
271
+ with patch("skcapstone.cli.status._get_daemon_info", return_value={
272
+ "running": True, "pid": 42, "uptime": "5m 3s",
273
+ }), \
274
+ patch("skcapstone.cli.status._probe_llm_backends", return_value={
275
+ "ollama": True, "anthropic": False, "grok": False,
276
+ "kimi": False, "nvidia": False,
277
+ }), \
278
+ patch("skcapstone.cli.status._print_consciousness_metrics"), \
279
+ patch("skcapstone.cli.status._get_last_conversation", return_value=None):
280
+ result = CliRunner().invoke(main, ["status", "--home", str(agent_home)])
281
+
282
+ assert result.exit_code == 0
283
+ assert "RUNNING" in result.output
284
+ assert "42" in result.output
285
+
286
+ def test_backends_section_present(self, agent_home: Path):
287
+ """'Backends:' line appears in status output."""
288
+ from skcapstone.cli import main
289
+
290
+ with patch("skcapstone.cli.status._get_daemon_info", return_value={"running": False}), \
291
+ patch("skcapstone.cli.status._probe_llm_backends", return_value={
292
+ "ollama": True, "anthropic": False, "grok": False,
293
+ "kimi": False, "nvidia": False,
294
+ }), \
295
+ patch("skcapstone.cli.status._print_consciousness_metrics"), \
296
+ patch("skcapstone.cli.status._get_last_conversation", return_value=None):
297
+ result = CliRunner().invoke(main, ["status", "--home", str(agent_home)])
298
+
299
+ assert result.exit_code == 0
300
+ assert "Backends:" in result.output
301
+
302
+ def test_last_conversation_shown(self, agent_home: Path):
303
+ """Last convo peer and time appear when conversation data exists."""
304
+ from skcapstone.cli import main
305
+
306
+ with patch("skcapstone.cli.status._get_daemon_info", return_value={"running": False}), \
307
+ patch("skcapstone.cli.status._probe_llm_backends", return_value={
308
+ "ollama": False, "anthropic": False, "grok": False,
309
+ "kimi": False, "nvidia": False,
310
+ }), \
311
+ patch("skcapstone.cli.status._print_consciousness_metrics"), \
312
+ patch("skcapstone.cli.status._get_last_conversation", return_value={
313
+ "peer": "alice", "when": "3m ago",
314
+ }):
315
+ result = CliRunner().invoke(main, ["status", "--home", str(agent_home)])
316
+
317
+ assert result.exit_code == 0
318
+ assert "alice" in result.output
319
+ assert "3m ago" in result.output
320
+
321
+ def test_disk_warning_when_low(self, agent_home: Path):
322
+ """Disk warning printed when free space < 5 GB."""
323
+ from skcapstone.cli import main
324
+
325
+ low_usage = MagicMock()
326
+ low_usage.free = int(2.5 * 1024 ** 3) # 2.5 GB
327
+
328
+ with patch("skcapstone.cli.status._get_daemon_info", return_value={"running": False}), \
329
+ patch("skcapstone.cli.status._probe_llm_backends", return_value={
330
+ "ollama": False, "anthropic": False, "grok": False,
331
+ "kimi": False, "nvidia": False,
332
+ }), \
333
+ patch("skcapstone.cli.status._print_consciousness_metrics"), \
334
+ patch("skcapstone.cli.status._get_last_conversation", return_value=None), \
335
+ patch("shutil.disk_usage", return_value=low_usage):
336
+ result = CliRunner().invoke(main, ["status", "--home", str(agent_home)])
337
+
338
+ assert result.exit_code == 0
339
+ assert "WARNING" in result.output
340
+ assert "2.5" in result.output
341
+
342
+ def test_no_disk_warning_when_ample(self, agent_home: Path):
343
+ """No disk warning when free space >= 5 GB."""
344
+ from skcapstone.cli import main
345
+
346
+ big_usage = MagicMock()
347
+ big_usage.free = int(50 * 1024 ** 3) # 50 GB
348
+
349
+ with patch("skcapstone.cli.status._get_daemon_info", return_value={"running": False}), \
350
+ patch("skcapstone.cli.status._probe_llm_backends", return_value={
351
+ "ollama": False, "anthropic": False, "grok": False,
352
+ "kimi": False, "nvidia": False,
353
+ }), \
354
+ patch("skcapstone.cli.status._print_consciousness_metrics"), \
355
+ patch("skcapstone.cli.status._get_last_conversation", return_value=None), \
356
+ patch("shutil.disk_usage", return_value=big_usage):
357
+ result = CliRunner().invoke(main, ["status", "--home", str(agent_home)])
358
+
359
+ assert result.exit_code == 0
360
+ assert "WARNING" not in result.output
361
+
362
+
363
+ class TestDoctorCLI:
364
+ """Smoke tests for the doctor command."""
365
+
366
+ def test_doctor_help(self):
367
+ """doctor --help exits 0."""
368
+ from skcapstone.cli import main
369
+
370
+ result = CliRunner().invoke(main, ["doctor", "--help"])
371
+ assert result.exit_code == 0
372
+ assert "Diagnose" in result.output
373
+
374
+ def test_doctor_on_agent_home(self, agent_home: Path):
375
+ """doctor produces output with at least one section heading."""
376
+ from skcapstone.cli import main
377
+
378
+ result = CliRunner().invoke(main, ["doctor", "--home", str(agent_home)])
379
+ assert result.exit_code == 0
380
+ # At least one category must be rendered
381
+ assert any(
382
+ kw in result.output
383
+ for kw in ["Python Packages", "Agent Home", "Identity", "Memory"]
384
+ )
385
+
386
+ def test_doctor_json_output(self, agent_home: Path):
387
+ """doctor --json-out emits valid JSON with expected keys."""
388
+ from skcapstone.cli import main
389
+
390
+ result = CliRunner().invoke(main, ["doctor", "--home", str(agent_home), "--json-out"])
391
+ assert result.exit_code == 0
392
+ data = json.loads(result.output)
393
+ assert "passed" in data
394
+ assert "failed" in data
395
+ assert isinstance(data["checks"], list)