@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,238 @@
1
+ """Session record/replay commands.
2
+
3
+ skcapstone record [--output FILE.jsonl]
4
+ Start the MCP server in recording mode. All tool calls +
5
+ responses are captured as JSONL. Sessions are also auto-saved
6
+ to ~/.skcapstone/sessions/ (last 5 kept).
7
+
8
+ skcapstone replay FILE.jsonl [--dry-run]
9
+ Play back a recorded session. --dry-run prints what would
10
+ be called without executing any handlers.
11
+
12
+ skcapstone sessions list
13
+ List all auto-saved sessions.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import json
19
+ import os
20
+ import sys
21
+ from pathlib import Path
22
+
23
+ import click
24
+
25
+ from ._common import AGENT_HOME, console
26
+
27
+
28
+ def register_record_commands(main: click.Group) -> None:
29
+ """Register record, replay, and sessions commands."""
30
+
31
+ # ------------------------------------------------------------------
32
+ # skcapstone record
33
+ # ------------------------------------------------------------------
34
+
35
+ @main.command("record")
36
+ @click.option(
37
+ "--output", "-o",
38
+ default=None,
39
+ type=click.Path(),
40
+ help="Write tool calls to this JSONL file (in addition to auto-session).",
41
+ )
42
+ @click.option("--home", default=AGENT_HOME, type=click.Path())
43
+ def record(output: str | None, home: str) -> None:
44
+ """Start MCP server in recording mode.
45
+
46
+ Every tool call and its response is captured as a JSONL line.
47
+ Sessions are also auto-saved to ~/.skcapstone/sessions/ with
48
+ the last 5 retained.
49
+
50
+ \b
51
+ Examples:
52
+ skcapstone record
53
+ skcapstone record --output /tmp/debug.jsonl
54
+ SKCAPSTONE_RECORD_FILE=/tmp/debug.jsonl skcapstone mcp serve
55
+ """
56
+ if output:
57
+ os.environ["SKCAPSTONE_RECORD_FILE"] = str(Path(output).expanduser())
58
+ console.print(f" [green]Recording to:[/] {output}")
59
+
60
+ home_path = Path(home).expanduser()
61
+ if not home_path.exists():
62
+ console.print(
63
+ "[bold red]No agent found.[/] Run [cyan]skcapstone init[/] first."
64
+ )
65
+ sys.exit(1)
66
+
67
+ console.print(
68
+ " [dim]Auto-session saved to[/] "
69
+ f"[cyan]{home_path / 'sessions'}[/] (last 5 kept)"
70
+ )
71
+ console.print(" [dim]Starting MCP server … (Ctrl-C to stop)[/]\n")
72
+
73
+ import asyncio
74
+ from ..mcp_server import _run_server
75
+ try:
76
+ asyncio.run(_run_server())
77
+ except KeyboardInterrupt:
78
+ console.print("\n [yellow]Recording stopped.[/]")
79
+
80
+ # ------------------------------------------------------------------
81
+ # skcapstone replay
82
+ # ------------------------------------------------------------------
83
+
84
+ @main.command("replay")
85
+ @click.argument("session_file", type=click.Path(exists=True))
86
+ @click.option(
87
+ "--dry-run", "dry_run",
88
+ is_flag=True,
89
+ default=False,
90
+ help="Print what would be called without executing handlers.",
91
+ )
92
+ @click.option(
93
+ "--format", "fmt",
94
+ type=click.Choice(["text", "json"]),
95
+ default="text",
96
+ help="Output format.",
97
+ )
98
+ @click.option("--home", default=AGENT_HOME, type=click.Path())
99
+ def replay(
100
+ session_file: str,
101
+ dry_run: bool,
102
+ fmt: str,
103
+ home: str,
104
+ ) -> None:
105
+ """Replay a recorded JSONL session.
106
+
107
+ \b
108
+ Modes:
109
+ --dry-run Print tool names + arguments only (no execution).
110
+ (default) Execute each tool call against the live handlers
111
+ and compare results to the recording.
112
+
113
+ \b
114
+ Examples:
115
+ skcapstone replay session-20260302T120000.jsonl --dry-run
116
+ skcapstone replay /tmp/debug.jsonl
117
+ skcapstone replay /tmp/debug.jsonl --format json
118
+ """
119
+ from ..session_replayer import SessionReplayer
120
+
121
+ path = Path(session_file).expanduser()
122
+ replayer = SessionReplayer(path, dry_run=dry_run)
123
+ results = list(replayer.replay())
124
+
125
+ if fmt == "json":
126
+ # Pure JSON output — no decorative header so callers can parse stdout.
127
+ rows = []
128
+ for r in results:
129
+ rows.append({
130
+ "index": r.index,
131
+ "tool": r.tool,
132
+ "arguments": r.arguments,
133
+ "recorded_result": r.recorded_result,
134
+ "replayed_result": r.replayed_result,
135
+ "duration_ms": r.duration_ms,
136
+ "match": r.match,
137
+ "error": r.error,
138
+ })
139
+ click.echo(json.dumps(rows, indent=2, default=str))
140
+ return
141
+
142
+ # Text output
143
+ mode_label = "[yellow]DRY-RUN[/]" if dry_run else "[green]LIVE[/]"
144
+ console.print(f"\n Replaying [bold]{path.name}[/] {mode_label}\n")
145
+ if not results:
146
+ console.print(" [dim]Session file is empty.[/]\n")
147
+ return
148
+
149
+ mismatches = 0
150
+ errors = 0
151
+ for r in results:
152
+ args_preview = json.dumps(r.arguments, ensure_ascii=False)
153
+ if len(args_preview) > 80:
154
+ args_preview = args_preview[:77] + "..."
155
+
156
+ if dry_run:
157
+ status = "[dim]SKIP[/]"
158
+ elif r.error:
159
+ status = "[red]ERROR[/]"
160
+ errors += 1
161
+ elif r.match is True:
162
+ status = "[green]MATCH[/]"
163
+ elif r.match is False:
164
+ status = "[yellow]DIFF[/]"
165
+ mismatches += 1
166
+ else:
167
+ status = "[dim]?[/]"
168
+
169
+ console.print(
170
+ f" [{r.index:>3}] {status} [cyan]{r.tool}[/]"
171
+ f" [dim]{args_preview}[/]"
172
+ f" [dim]{r.duration_ms}ms[/]"
173
+ )
174
+ if r.error:
175
+ console.print(f" [red]{r.error}[/]")
176
+
177
+ total = len(results)
178
+ console.print(f"\n {total} call(s) replayed", end="")
179
+ if not dry_run:
180
+ if mismatches:
181
+ console.print(f" [yellow]{mismatches} diff(s)[/]", end="")
182
+ if errors:
183
+ console.print(f" [red]{errors} error(s)[/]", end="")
184
+ console.print("\n")
185
+
186
+ if not dry_run and (mismatches or errors):
187
+ sys.exit(1)
188
+
189
+ # ------------------------------------------------------------------
190
+ # skcapstone sessions
191
+ # ------------------------------------------------------------------
192
+
193
+ @main.group("sessions")
194
+ def sessions_group() -> None:
195
+ """Manage auto-saved MCP sessions."""
196
+
197
+ @sessions_group.command("list")
198
+ @click.option("--home", default=AGENT_HOME, type=click.Path())
199
+ @click.option("--limit", default=10, help="Max sessions to show.")
200
+ def sessions_list(home: str, limit: int) -> None:
201
+ """List auto-saved session files newest-first.
202
+
203
+ \b
204
+ Example:
205
+ skcapstone sessions list
206
+ skcapstone sessions list --limit 3
207
+ """
208
+ from ..session_recorder import list_sessions, load_session
209
+
210
+ home_path = Path(home).expanduser()
211
+ files = list_sessions(home_path)
212
+
213
+ if not files:
214
+ console.print("\n [dim]No sessions found in "
215
+ f"{home_path / 'sessions'}[/]\n")
216
+ return
217
+
218
+ console.print(f"\n [bold]{len(files)}[/] session(s) found "
219
+ f"(showing up to {limit}):\n")
220
+ for f in files[:limit]:
221
+ try:
222
+ entries = load_session(f)
223
+ count = len(entries)
224
+ tools = list({e.get("tool", "?") for e in entries})
225
+ tools_str = ", ".join(sorted(tools)[:5])
226
+ if len(tools) > 5:
227
+ tools_str += f" +{len(tools) - 5}"
228
+ except Exception:
229
+ count = 0
230
+ tools_str = "[dim]unreadable[/]"
231
+
232
+ size_kb = f.stat().st_size / 1024
233
+ console.print(
234
+ f" [cyan]{f.name}[/] "
235
+ f"[dim]{count} calls {size_kb:.1f}KB {tools_str}[/]"
236
+ )
237
+ console.print(f" [dim]{f}[/]")
238
+ console.print()
@@ -0,0 +1,159 @@
1
+ """Register command — auto-register SK* skills and MCP servers.
2
+
3
+ Detects the user's environments (OpenClaw, Claude Code, Cursor, VS Code,
4
+ OpenCode CLI, mcporter) and registers SKILL.md symlinks + MCP server entries.
5
+
6
+ Commands:
7
+ skcapstone register — register all SK* packages
8
+ skcapstone register --dry-run — show what would be done
9
+ skcapstone register --env claude-code — target specific environment
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from pathlib import Path
15
+ from typing import Optional
16
+
17
+ import click
18
+
19
+ from ._common import AGENT_HOME, console
20
+
21
+
22
+ def register_register_commands(main: click.Group) -> None:
23
+ """Register the 'register' command on the main CLI group."""
24
+
25
+ @main.command()
26
+ @click.option(
27
+ "--workspace",
28
+ default=None,
29
+ help="Workspace root directory (default: ~/clawd/).",
30
+ type=click.Path(),
31
+ )
32
+ @click.option(
33
+ "--env",
34
+ "target_env",
35
+ default=None,
36
+ help="Target a specific environment (e.g. claude-code, cursor, mcporter).",
37
+ )
38
+ @click.option(
39
+ "--dry-run",
40
+ is_flag=True,
41
+ default=False,
42
+ help="Show what would be done without making changes.",
43
+ )
44
+ def register(
45
+ workspace: Optional[str],
46
+ target_env: Optional[str],
47
+ dry_run: bool,
48
+ ) -> None:
49
+ """Register all SK* skills and MCP servers in detected environments.
50
+
51
+ Auto-detects your development environments (Claude Code, Cursor,
52
+ VS Code, OpenClaw, OpenCode, mcporter) and ensures all SK* skill
53
+ manifests and MCP server entries are properly configured.
54
+
55
+ Examples:
56
+
57
+ skcapstone register # auto-detect and register
58
+ skcapstone register --dry-run # preview what would happen
59
+ skcapstone register --env claude-code # target Claude Code only
60
+ """
61
+ from skmemory.register import detect_environments
62
+ from skcapstone.register import register_all
63
+
64
+ workspace_path = Path(workspace).expanduser() if workspace else None
65
+ environments = [target_env] if target_env else None
66
+
67
+ console.print()
68
+ console.print("[bold cyan]SK* Registration[/]")
69
+ console.print()
70
+
71
+ # Show detected environments
72
+ detected = detect_environments()
73
+ console.print(" [bold]Detected environments:[/]")
74
+ for env in detected:
75
+ marker = "[green]●[/]" if (environments is None or env in environments) else "[dim]○[/]"
76
+ console.print(f" {marker} {env}")
77
+ if not detected:
78
+ console.print(" [dim]None detected[/]")
79
+ console.print()
80
+ console.print(" [yellow]Tip:[/] Install OpenClaw, Claude Code, or Cursor to enable registration.")
81
+ console.print()
82
+
83
+ if dry_run:
84
+ console.print(" [yellow]Dry run — no changes will be made.[/]")
85
+ console.print()
86
+
87
+ # Run registration
88
+ results = register_all(
89
+ workspace=workspace_path,
90
+ environments=environments,
91
+ dry_run=dry_run,
92
+ )
93
+
94
+ # Display results
95
+ from rich.table import Table
96
+
97
+ table = Table(show_header=True, header_style="bold", box=None, padding=(0, 2))
98
+ table.add_column("Package", style="cyan")
99
+ table.add_column("Skill", style="dim")
100
+ table.add_column("MCP")
101
+ table.add_column("OpenClaw Plugin")
102
+
103
+ for name, pkg_result in results.get("packages", {}).items():
104
+ skill_info = pkg_result.get("skill", {})
105
+ skill_action = skill_info.get("action", "—")
106
+
107
+ if skill_action == "created":
108
+ skill_str = "[green]created[/]"
109
+ elif skill_action == "exists":
110
+ skill_str = "[dim]exists[/]"
111
+ elif skill_action == "error":
112
+ skill_str = f"[red]{skill_info.get('error', 'error')}[/]"
113
+ elif skill_action == "dry-run":
114
+ skill_str = "[yellow]would create[/]"
115
+ else:
116
+ skill_str = f"[dim]{skill_action}[/]"
117
+
118
+ mcp_info = pkg_result.get("mcp", {})
119
+ if not mcp_info:
120
+ mcp_str = "[dim]—[/]"
121
+ elif isinstance(mcp_info, dict):
122
+ parts = []
123
+ for env_name, action in mcp_info.items():
124
+ if action == "created":
125
+ parts.append(f"[green]{env_name}[/]")
126
+ elif action == "exists":
127
+ parts.append(f"[dim]{env_name}[/]")
128
+ elif action == "dry-run":
129
+ parts.append(f"[yellow]{env_name}[/]")
130
+ else:
131
+ parts.append(f"{env_name}:{action}")
132
+ mcp_str = ", ".join(parts) if parts else "[dim]—[/]"
133
+ else:
134
+ mcp_str = str(mcp_info)
135
+
136
+ plugin_action = pkg_result.get("openclaw_plugin", "")
137
+ if plugin_action == "created":
138
+ plugin_str = "[green]created[/]"
139
+ elif plugin_action == "exists":
140
+ plugin_str = "[dim]exists[/]"
141
+ elif plugin_action == "dry-run":
142
+ plugin_str = "[yellow]would create[/]"
143
+ elif plugin_action and plugin_action.startswith("error"):
144
+ plugin_str = f"[red]{plugin_action}[/]"
145
+ elif not plugin_action:
146
+ plugin_str = "[dim]—[/]"
147
+ else:
148
+ plugin_str = f"[dim]{plugin_action}[/]"
149
+
150
+ table.add_row(name, skill_str, mcp_str, plugin_str)
151
+
152
+ console.print(table)
153
+ console.print()
154
+
155
+ if dry_run:
156
+ console.print(" [yellow]No changes made (dry run).[/]")
157
+ else:
158
+ console.print(" [green]Registration complete.[/]")
159
+ console.print()
@@ -0,0 +1,156 @@
1
+ """Search command: full-text search across memories, conversations, and messages."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ import click
10
+ from rich.table import Table
11
+ from rich.text import Text
12
+
13
+ from ._common import AGENT_HOME, console
14
+
15
+
16
+ # Source icons shown in the table
17
+ _SOURCE_ICON = {
18
+ "memory": "🧠",
19
+ "conversation": "💬",
20
+ "message": "📨",
21
+ "journal": "📓",
22
+ }
23
+
24
+ _SOURCE_COLOR = {
25
+ "memory": "cyan",
26
+ "conversation": "green",
27
+ "message": "yellow",
28
+ "journal": "magenta",
29
+ }
30
+
31
+ _VALID_SOURCES = ("memory", "conversation", "message", "journal")
32
+
33
+
34
+ def _fmt_ts(ts) -> str:
35
+ """Format a datetime for display, returning '--' for None."""
36
+ if ts is None:
37
+ return "--"
38
+ return ts.strftime("%Y-%m-%d %H:%M")
39
+
40
+
41
+ def register_search_commands(main: click.Group) -> None:
42
+ """Register the top-level ``search`` command."""
43
+
44
+ @main.command("search")
45
+ @click.argument("query")
46
+ @click.option(
47
+ "--home",
48
+ default=AGENT_HOME,
49
+ type=click.Path(),
50
+ help="Agent home directory.",
51
+ )
52
+ @click.option(
53
+ "--type", "-t",
54
+ "source_types",
55
+ multiple=True,
56
+ type=click.Choice(_VALID_SOURCES),
57
+ help=(
58
+ "Restrict search to a specific source type. "
59
+ "May be repeated: -t memory -t conversation"
60
+ ),
61
+ )
62
+ @click.option(
63
+ "--limit", "-n",
64
+ default=20,
65
+ show_default=True,
66
+ help="Maximum number of results.",
67
+ )
68
+ @click.option(
69
+ "--json-out",
70
+ is_flag=True,
71
+ help="Output results as JSON.",
72
+ )
73
+ def search_cmd(query, home, source_types, limit, json_out):
74
+ """Search across memories, conversations, and messages.
75
+
76
+ QUERY is matched case-insensitively against all data stores.
77
+ Results are ranked by relevance and recency.
78
+
79
+ Examples:\n
80
+ skcapstone search "consciousness"\n
81
+ skcapstone search "Opus" --type conversation\n
82
+ skcapstone search "trust" -t memory -t journal -n 10\n
83
+ skcapstone search "sprint" --json-out | jq .[].preview
84
+ """
85
+ from ..unified_search import search as unified_search, SOURCE_ALL
86
+
87
+ home_path = Path(home).expanduser()
88
+ if not home_path.exists():
89
+ if json_out:
90
+ print(json.dumps([]))
91
+ return
92
+ console.print(
93
+ "[bold red]No agent found.[/] Run [cyan]skcapstone init[/] first."
94
+ )
95
+ sys.exit(1)
96
+
97
+ sources = frozenset(source_types) if source_types else SOURCE_ALL
98
+
99
+ results = unified_search(home=home_path, query=query, sources=sources, limit=limit)
100
+
101
+ if json_out:
102
+ output = [
103
+ {
104
+ "source": r.source,
105
+ "id": r.result_id,
106
+ "title": r.title,
107
+ "preview": r.preview,
108
+ "score": round(r.score, 4),
109
+ "timestamp": r.timestamp.isoformat() if r.timestamp else None,
110
+ "metadata": r.metadata,
111
+ }
112
+ for r in results
113
+ ]
114
+ print(json.dumps(output, indent=2))
115
+ return
116
+
117
+ if not results:
118
+ console.print(
119
+ f"\n [dim]No results for '[/]{query}[dim]'[/] "
120
+ f"across {', '.join(sorted(sources))}.\n"
121
+ )
122
+ return
123
+
124
+ source_label = (
125
+ ", ".join(sorted(sources))
126
+ if len(sources) < len(SOURCE_ALL)
127
+ else "all sources"
128
+ )
129
+ console.print(
130
+ f"\n [bold]{len(results)}[/] result"
131
+ f"{'s' if len(results) != 1 else ''} for "
132
+ f"[bold cyan]'{query}'[/] in {source_label}:\n"
133
+ )
134
+
135
+ table = Table(show_header=True, header_style="bold", box=None, padding=(0, 2))
136
+ table.add_column("Type", max_width=14)
137
+ table.add_column("Title", max_width=30)
138
+ table.add_column("Preview", max_width=55)
139
+ table.add_column("Score", justify="right", max_width=7)
140
+ table.add_column("Date", justify="right", style="dim", max_width=16)
141
+
142
+ for r in results:
143
+ icon = _SOURCE_ICON.get(r.source, "?")
144
+ color = _SOURCE_COLOR.get(r.source, "white")
145
+ source_text = Text(f"{icon} {r.source}", style=color)
146
+ preview_clipped = r.preview[:110] + ("…" if len(r.preview) > 110 else "")
147
+ table.add_row(
148
+ source_text,
149
+ r.title,
150
+ preview_clipped,
151
+ f"{r.score:.2f}",
152
+ _fmt_ts(r.timestamp),
153
+ )
154
+
155
+ console.print(table)
156
+ console.print()
@@ -0,0 +1,91 @@
1
+ """CLI command: skcapstone service check — service health checks."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+
7
+ import click
8
+ from rich.table import Table
9
+
10
+ from ._common import console
11
+
12
+
13
+ def register_service_commands(main: click.Group) -> None:
14
+ """Register the ``service`` command group on the main CLI."""
15
+
16
+ @main.group()
17
+ def service():
18
+ """Service infrastructure commands."""
19
+
20
+ @service.command("check")
21
+ @click.option("--json-out", is_flag=True, help="Output as machine-readable JSON.")
22
+ def service_check(json_out: bool):
23
+ """Check health of all services in the sovereign stack.
24
+
25
+ Pings SKVector (Qdrant), SKGraph (FalkorDB), Syncthing,
26
+ skcapstone daemon, and skchat daemon. Reports status,
27
+ latency, and any errors.
28
+ """
29
+ from ..service_health import check_all_services
30
+
31
+ results = check_all_services()
32
+
33
+ if json_out:
34
+ up = sum(1 for r in results if r["status"] == "up")
35
+ down = sum(1 for r in results if r["status"] == "down")
36
+ data = {
37
+ "summary": {
38
+ "total": len(results),
39
+ "up": up,
40
+ "down": down,
41
+ "unknown": len(results) - up - down,
42
+ },
43
+ "services": results,
44
+ }
45
+ click.echo(json.dumps(data, indent=2))
46
+ return
47
+
48
+ console.print()
49
+
50
+ table = Table(
51
+ show_header=True, header_style="bold", box=None, padding=(0, 2),
52
+ )
53
+ table.add_column("Service", style="cyan", no_wrap=True)
54
+ table.add_column("URL", style="dim", no_wrap=True)
55
+ table.add_column("Status")
56
+ table.add_column("Latency", justify="right", style="dim")
57
+ table.add_column("Version", style="dim")
58
+ table.add_column("Error", style="dim")
59
+
60
+ status_styles = {
61
+ "up": "[green]UP[/]",
62
+ "down": "[red]DOWN[/]",
63
+ "unknown": "[yellow]UNKNOWN[/]",
64
+ }
65
+
66
+ for r in results:
67
+ status_str = status_styles.get(r["status"], r["status"])
68
+ latency_str = f"{r['latency_ms']:.0f}ms" if r["latency_ms"] else "-"
69
+ version_str = str(r["version"]) if r["version"] else "-"
70
+ error_str = r["error"][:50] if r["error"] else "-"
71
+ table.add_row(
72
+ r["name"], r["url"], status_str,
73
+ latency_str, version_str, error_str,
74
+ )
75
+
76
+ console.print(table)
77
+
78
+ up_count = sum(1 for r in results if r["status"] == "up")
79
+ down_count = sum(1 for r in results if r["status"] == "down")
80
+ total = len(results)
81
+
82
+ console.print()
83
+ if down_count == 0:
84
+ console.print(f" [green]All {up_count}/{total} reachable services are healthy.[/]")
85
+ else:
86
+ console.print(
87
+ f" [green]{up_count}[/] up, "
88
+ f"[red]{down_count}[/] down "
89
+ f"out of {total} services."
90
+ )
91
+ console.print()