@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,206 @@
1
+ """Tests for the skcapstone test CLI command (test_cmd.py).
2
+
3
+ Covers:
4
+ - Help output is rendered correctly
5
+ - Invalid package names are rejected with a clear error
6
+ - JSON output mode produces valid structured JSON
7
+ - Per-package table renders for available/unavailable packages
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import json
13
+ from pathlib import Path
14
+ from unittest.mock import patch
15
+
16
+ import pytest
17
+ from click.testing import CliRunner
18
+
19
+ from skcapstone.cli import main
20
+ from skcapstone.testrunner import PackageResult, TestReport
21
+
22
+
23
+ @pytest.fixture()
24
+ def runner():
25
+ return CliRunner(mix_stderr=False)
26
+
27
+
28
+ @pytest.fixture()
29
+ def mock_report_all_pass():
30
+ """A TestReport where every package passes."""
31
+ return TestReport(
32
+ results=[
33
+ PackageResult(name="skcapstone", passed=10, exit_code=0, duration_s=1.2),
34
+ PackageResult(name="skcomm", passed=5, exit_code=0, duration_s=0.8),
35
+ ],
36
+ duration_s=2.0,
37
+ )
38
+
39
+
40
+ @pytest.fixture()
41
+ def mock_report_with_failure():
42
+ """A TestReport with one failing package."""
43
+ return TestReport(
44
+ results=[
45
+ PackageResult(name="skcapstone", passed=10, exit_code=0, duration_s=1.2),
46
+ PackageResult(
47
+ name="skcomm",
48
+ passed=3,
49
+ failed=2,
50
+ exit_code=1,
51
+ duration_s=0.9,
52
+ output="FAILED tests/test_foo.py::test_bar",
53
+ ),
54
+ ],
55
+ duration_s=2.1,
56
+ )
57
+
58
+
59
+ @pytest.fixture()
60
+ def mock_report_unavailable():
61
+ """A TestReport with one unavailable package."""
62
+ return TestReport(
63
+ results=[
64
+ PackageResult(
65
+ name="capauth",
66
+ available=False,
67
+ output="Test directory not found",
68
+ ),
69
+ ],
70
+ duration_s=0.0,
71
+ )
72
+
73
+
74
+ # ── Help output ──────────────────────────────────────────────────
75
+
76
+
77
+ class TestTestCmdHelp:
78
+ """Ensure help text is registered and contains key information."""
79
+
80
+ def test_help_renders(self, runner):
81
+ """--help exits 0 and shows package names."""
82
+ result = runner.invoke(main, ["test", "--help"])
83
+ assert result.exit_code == 0
84
+ assert "skcapstone" in result.output
85
+ assert "--package" in result.output
86
+
87
+ def test_help_lists_options(self, runner):
88
+ """--help shows all major options."""
89
+ result = runner.invoke(main, ["test", "--help"])
90
+ assert "--fast" in result.output
91
+ assert "--verbose" in result.output
92
+ assert "--json-out" in result.output
93
+ assert "--timeout" in result.output
94
+
95
+
96
+ # ── Invalid package validation ───────────────────────────────────
97
+
98
+
99
+ class TestInvalidPackage:
100
+ """Unknown package names should be rejected immediately."""
101
+
102
+ def test_invalid_package_exits_nonzero(self, runner):
103
+ """Passing an unknown --package name exits with code 1."""
104
+ result = runner.invoke(main, ["test", "--package", "does-not-exist"])
105
+ assert result.exit_code == 1
106
+
107
+ def test_invalid_package_shows_valid_names(self, runner):
108
+ """Error message lists the valid package names."""
109
+ result = runner.invoke(main, ["test", "--package", "bogus"])
110
+ assert "bogus" in result.output
111
+ assert "skcapstone" in result.output
112
+
113
+ def test_valid_package_accepted(self, runner, mock_report_all_pass):
114
+ """A valid package name is accepted and run_all_tests is called."""
115
+ with patch(
116
+ "skcapstone.cli.test_cmd.run_all_tests",
117
+ return_value=mock_report_all_pass,
118
+ ):
119
+ result = runner.invoke(main, ["test", "--package", "skcomm"])
120
+ assert result.exit_code == 0
121
+
122
+
123
+ # ── JSON output mode ─────────────────────────────────────────────
124
+
125
+
126
+ class TestJsonOutput:
127
+ """--json-out should produce parseable JSON with the right structure."""
128
+
129
+ def test_json_out_is_parseable(self, runner, mock_report_all_pass):
130
+ """JSON output can be loaded by json.loads."""
131
+ with patch(
132
+ "skcapstone.cli.test_cmd.run_all_tests",
133
+ return_value=mock_report_all_pass,
134
+ ):
135
+ result = runner.invoke(main, ["test", "--json-out"])
136
+ assert result.exit_code == 0
137
+ data = json.loads(result.output)
138
+ assert "all_passed" in data
139
+ assert "packages" in data
140
+
141
+ def test_json_exit_nonzero_on_failure(self, runner, mock_report_with_failure):
142
+ """JSON mode still exits 1 when tests fail."""
143
+ with patch(
144
+ "skcapstone.cli.test_cmd.run_all_tests",
145
+ return_value=mock_report_with_failure,
146
+ ):
147
+ result = runner.invoke(main, ["test", "--json-out"])
148
+ assert result.exit_code == 1
149
+ data = json.loads(result.output)
150
+ assert data["all_passed"] is False
151
+
152
+ def test_json_has_package_details(self, runner, mock_report_all_pass):
153
+ """Each package result appears in the packages array."""
154
+ with patch(
155
+ "skcapstone.cli.test_cmd.run_all_tests",
156
+ return_value=mock_report_all_pass,
157
+ ):
158
+ result = runner.invoke(main, ["test", "--json-out"])
159
+ data = json.loads(result.output)
160
+ pkg_names = [p["name"] for p in data["packages"]]
161
+ assert "skcapstone" in pkg_names
162
+
163
+
164
+ # ── Table rendering ──────────────────────────────────────────────
165
+
166
+
167
+ class TestTableRendering:
168
+ """Rich table should contain pass/fail/skip columns."""
169
+
170
+ def test_pass_table_shows_pass(self, runner, mock_report_all_pass):
171
+ """PASS appears in the output when all tests succeed."""
172
+ with patch(
173
+ "skcapstone.cli.test_cmd.run_all_tests",
174
+ return_value=mock_report_all_pass,
175
+ ):
176
+ result = runner.invoke(main, ["test"])
177
+ assert result.exit_code == 0
178
+ assert "PASS" in result.output
179
+
180
+ def test_fail_table_shows_fail(self, runner, mock_report_with_failure):
181
+ """FAIL appears in the output when a package fails."""
182
+ with patch(
183
+ "skcapstone.cli.test_cmd.run_all_tests",
184
+ return_value=mock_report_with_failure,
185
+ ):
186
+ result = runner.invoke(main, ["test"])
187
+ assert result.exit_code == 1
188
+ assert "FAIL" in result.output
189
+
190
+ def test_unavailable_package_shows_na(self, runner, mock_report_unavailable):
191
+ """Packages without test dirs show N/A, not an error."""
192
+ with patch(
193
+ "skcapstone.cli.test_cmd.run_all_tests",
194
+ return_value=mock_report_unavailable,
195
+ ):
196
+ result = runner.invoke(main, ["test"])
197
+ assert "N/A" in result.output
198
+
199
+ def test_show_output_flag_prints_failures(self, runner, mock_report_with_failure):
200
+ """--show-output flag prints pytest output for failing packages."""
201
+ with patch(
202
+ "skcapstone.cli.test_cmd.run_all_tests",
203
+ return_value=mock_report_with_failure,
204
+ ):
205
+ result = runner.invoke(main, ["test", "--show-output"])
206
+ assert "test_bar" in result.output
@@ -0,0 +1,364 @@
1
+ """Tests for skcapstone test-connection command.
2
+
3
+ Covers:
4
+ - Happy path: peer responds with pong, latency reported
5
+ - Timeout: peer never replies, command exits with code 1
6
+ - No-transport: SKComm has no live transport, error reported
7
+ - Payload helpers: _make_ping_payload / _is_pong_for / _is_ping / _make_pong_payload
8
+ - --count > 1: multi-ping summary statistics
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import json
14
+ import time
15
+ from pathlib import Path
16
+ from unittest.mock import MagicMock, patch
17
+
18
+ import pytest
19
+ from click.testing import CliRunner
20
+
21
+ from skcapstone.cli.test_connection import (
22
+ _is_ping,
23
+ _is_pong_for,
24
+ _make_ping_payload,
25
+ _make_pong_payload,
26
+ ping_peer,
27
+ )
28
+
29
+
30
+ # ---------------------------------------------------------------------------
31
+ # Fixtures
32
+ # ---------------------------------------------------------------------------
33
+
34
+
35
+ @pytest.fixture
36
+ def agent_home(tmp_path):
37
+ """Minimal agent home directory for CLI tests."""
38
+ home = tmp_path / ".skcapstone"
39
+ (home / "identity").mkdir(parents=True)
40
+ (home / "config").mkdir(parents=True)
41
+ identity = {
42
+ "name": "TestAgent",
43
+ "fingerprint": "AABB1234",
44
+ "capauth_managed": False,
45
+ }
46
+ (home / "identity" / "identity.json").write_text(json.dumps(identity))
47
+ (home / "manifest.json").write_text(
48
+ json.dumps({"name": "TestAgent", "version": "0.1.0"})
49
+ )
50
+ import yaml
51
+
52
+ (home / "config" / "config.yaml").write_text(
53
+ yaml.dump({"agent_name": "TestAgent"})
54
+ )
55
+ return home
56
+
57
+
58
+ def _make_mock_chat(delivered: bool, pong_nonce: str | None = None, peer: str = "lumina"):
59
+ """Build a mocked AgentChat instance.
60
+
61
+ Args:
62
+ delivered: Whether AgentChat.send() reports successful delivery.
63
+ pong_nonce: When set, AgentChat.receive() will return a pong message
64
+ matching this nonce on the first call.
65
+ peer: Peer name used as sender in the pong message.
66
+
67
+ Returns:
68
+ MagicMock configured as AgentChat.
69
+ """
70
+ mock_chat = MagicMock()
71
+ mock_chat.send.return_value = {
72
+ "stored": True,
73
+ "delivered": delivered,
74
+ "transport": "tailscale" if delivered else None,
75
+ "error": None,
76
+ }
77
+
78
+ if pong_nonce is not None:
79
+ pong_payload = _make_pong_payload(pong_nonce, peer)
80
+ mock_chat.receive.return_value = [
81
+ {"sender": peer, "content": pong_payload}
82
+ ]
83
+ else:
84
+ mock_chat.receive.return_value = []
85
+
86
+ return mock_chat
87
+
88
+
89
+ # ---------------------------------------------------------------------------
90
+ # Unit tests — payload helpers
91
+ # ---------------------------------------------------------------------------
92
+
93
+
94
+ class TestPayloadHelpers:
95
+ """Verify ping/pong serialization round-trips."""
96
+
97
+ def test_make_ping_payload_is_json(self):
98
+ """Ping payload is valid JSON with required keys."""
99
+ payload = _make_ping_payload("abc123", "opus")
100
+ data = json.loads(payload)
101
+ assert data["skchat_ping"] is True
102
+ assert data["nonce"] == "abc123"
103
+ assert data["sender"] == "opus"
104
+
105
+ def test_is_pong_for_matching_nonce(self):
106
+ """_is_pong_for returns True for a matching pong."""
107
+ pong = _make_pong_payload("xyz-789", "lumina")
108
+ assert _is_pong_for(pong, "xyz-789") is True
109
+
110
+ def test_is_pong_for_wrong_nonce(self):
111
+ """_is_pong_for returns False when nonce differs."""
112
+ pong = _make_pong_payload("xyz-789", "lumina")
113
+ assert _is_pong_for(pong, "different-nonce") is False
114
+
115
+ def test_is_pong_for_plain_text(self):
116
+ """_is_pong_for is False for arbitrary text."""
117
+ assert _is_pong_for("hello world", "nonce") is False
118
+
119
+ def test_is_ping_detects_ping(self):
120
+ """_is_ping correctly identifies a ping payload."""
121
+ ping = _make_ping_payload("nonce-42", "opus")
122
+ ok, nonce, sender = _is_ping(ping)
123
+ assert ok is True
124
+ assert nonce == "nonce-42"
125
+ assert sender == "opus"
126
+
127
+ def test_is_ping_plain_text(self):
128
+ """_is_ping returns False for non-ping content."""
129
+ ok, nonce, sender = _is_ping("just a regular message")
130
+ assert ok is False
131
+ assert nonce == ""
132
+
133
+ def test_make_pong_payload_round_trip(self):
134
+ """Pong payload can be verified by _is_pong_for."""
135
+ pong = _make_pong_payload("round-trip-nonce", "jarvis")
136
+ assert _is_pong_for(pong, "round-trip-nonce") is True
137
+
138
+
139
+ # ---------------------------------------------------------------------------
140
+ # Unit tests — ping_peer()
141
+ # ---------------------------------------------------------------------------
142
+
143
+
144
+ class TestPingPeer:
145
+ """Tests for the ping_peer() core function."""
146
+
147
+ def test_happy_path_reachable(self, agent_home):
148
+ """ping_peer returns reachable=True when pong arrives."""
149
+ nonce_holder: list[str] = []
150
+
151
+ def fake_send(peer, message, **kwargs):
152
+ # Capture the nonce from the ping payload
153
+ data = json.loads(message)
154
+ nonce_holder.append(data["nonce"])
155
+ return {"stored": True, "delivered": True, "transport": "tailscale", "error": None}
156
+
157
+ def fake_receive(limit=20):
158
+ if not nonce_holder:
159
+ return []
160
+ return [
161
+ {
162
+ "sender": "lumina",
163
+ "content": _make_pong_payload(nonce_holder[0], "lumina"),
164
+ }
165
+ ]
166
+
167
+ mock_chat = MagicMock()
168
+ mock_chat.send.side_effect = fake_send
169
+ mock_chat.receive.side_effect = fake_receive
170
+
171
+ with patch(
172
+ "skcapstone.cli.test_connection.AgentChat", return_value=mock_chat
173
+ ):
174
+ result = ping_peer("lumina", agent_home, "TestAgent", timeout=2.0)
175
+
176
+ assert result["reachable"] is True
177
+ assert result["latency_ms"] is not None
178
+ assert result["latency_ms"] >= 0.0
179
+
180
+ def test_timeout_unreachable(self, agent_home):
181
+ """ping_peer returns reachable=False when pong never arrives."""
182
+ mock_chat = _make_mock_chat(delivered=True, pong_nonce=None)
183
+
184
+ with patch(
185
+ "skcapstone.cli.test_connection.AgentChat", return_value=mock_chat
186
+ ):
187
+ # Use a very short timeout so the test runs quickly
188
+ result = ping_peer("lumina", agent_home, "TestAgent", timeout=0.3)
189
+
190
+ assert result["reachable"] is False
191
+ assert result["latency_ms"] is None
192
+ assert "timeout" in (result["error"] or "").lower()
193
+
194
+ def test_no_live_transport_returns_error(self, agent_home):
195
+ """ping_peer reports error when send has no live transport."""
196
+ mock_chat = MagicMock()
197
+ mock_chat.send.return_value = {
198
+ "stored": True,
199
+ "delivered": False,
200
+ "transport": None,
201
+ "error": None,
202
+ }
203
+
204
+ with patch(
205
+ "skcapstone.cli.test_connection.AgentChat", return_value=mock_chat
206
+ ):
207
+ result = ping_peer("lumina", agent_home, "TestAgent", timeout=2.0)
208
+
209
+ assert result["reachable"] is False
210
+ assert "transport" in (result["error"] or "").lower()
211
+
212
+ def test_send_failure_returns_error(self, agent_home):
213
+ """ping_peer returns error when send itself fails entirely."""
214
+ mock_chat = MagicMock()
215
+ mock_chat.send.return_value = {
216
+ "stored": False,
217
+ "delivered": False,
218
+ "transport": None,
219
+ "error": "connection refused",
220
+ }
221
+
222
+ with patch(
223
+ "skcapstone.cli.test_connection.AgentChat", return_value=mock_chat
224
+ ):
225
+ result = ping_peer("lumina", agent_home, "TestAgent", timeout=2.0)
226
+
227
+ assert result["reachable"] is False
228
+ assert result["error"] is not None
229
+
230
+
231
+ # ---------------------------------------------------------------------------
232
+ # CLI integration tests — skcapstone test-connection
233
+ # ---------------------------------------------------------------------------
234
+
235
+
236
+ class TestTestConnectionCLI:
237
+ """CLI-level tests via CliRunner."""
238
+
239
+ def _run(self, args: list[str]):
240
+ from skcapstone.cli import main
241
+
242
+ runner = CliRunner()
243
+ return runner.invoke(main, args, catch_exceptions=False)
244
+
245
+ def test_cli_reachable_exit_zero(self, agent_home):
246
+ """CLI exits 0 and prints REACHABLE when pong arrives."""
247
+ nonce_holder: list[str] = []
248
+
249
+ def fake_send(peer, message, **kwargs):
250
+ try:
251
+ data = json.loads(message)
252
+ nonce_holder.append(data.get("nonce", ""))
253
+ except Exception:
254
+ pass
255
+ return {
256
+ "stored": True,
257
+ "delivered": True,
258
+ "transport": "tailscale",
259
+ "error": None,
260
+ }
261
+
262
+ def fake_receive(limit=20):
263
+ if not nonce_holder:
264
+ return []
265
+ return [
266
+ {
267
+ "sender": "lumina",
268
+ "content": _make_pong_payload(nonce_holder[0], "lumina"),
269
+ }
270
+ ]
271
+
272
+ mock_chat = MagicMock()
273
+ mock_chat.send.side_effect = fake_send
274
+ mock_chat.receive.side_effect = fake_receive
275
+
276
+ with patch("skcapstone.cli.test_connection.AgentChat", return_value=mock_chat):
277
+ result = self._run(
278
+ ["test-connection", "lumina", "--home", str(agent_home)]
279
+ )
280
+
281
+ assert result.exit_code == 0, result.output
282
+ assert "REACHABLE" in result.output
283
+
284
+ def test_cli_timeout_exit_one(self, agent_home):
285
+ """CLI exits 1 and prints UNREACHABLE on timeout."""
286
+ mock_chat = _make_mock_chat(delivered=True, pong_nonce=None)
287
+
288
+ with patch("skcapstone.cli.test_connection.AgentChat", return_value=mock_chat):
289
+ # Very short timeout so the test completes fast
290
+ result = self._run(
291
+ [
292
+ "test-connection", "lumina",
293
+ "--home", str(agent_home),
294
+ "--timeout", "0.3",
295
+ ]
296
+ )
297
+
298
+ assert result.exit_code == 1, result.output
299
+ assert "UNREACHABLE" in result.output
300
+
301
+ def test_cli_no_transport_exit_one(self, agent_home):
302
+ """CLI exits 1 when no live transport is available."""
303
+ mock_chat = MagicMock()
304
+ mock_chat.send.return_value = {
305
+ "stored": True,
306
+ "delivered": False,
307
+ "transport": None,
308
+ "error": None,
309
+ }
310
+
311
+ with patch("skcapstone.cli.test_connection.AgentChat", return_value=mock_chat):
312
+ result = self._run(
313
+ ["test-connection", "lumina", "--home", str(agent_home)]
314
+ )
315
+
316
+ assert result.exit_code == 1, result.output
317
+ assert "UNREACHABLE" in result.output
318
+
319
+ def test_cli_count_shows_statistics(self, agent_home):
320
+ """--count 3 produces min/avg/max latency summary."""
321
+ nonce_holder: list[str] = []
322
+
323
+ def fake_send(peer, message, **kwargs):
324
+ try:
325
+ data = json.loads(message)
326
+ nonce_holder.append(data.get("nonce", ""))
327
+ except Exception:
328
+ pass
329
+ return {
330
+ "stored": True,
331
+ "delivered": True,
332
+ "transport": "tailscale",
333
+ "error": None,
334
+ }
335
+
336
+ def fake_receive(limit=20):
337
+ if not nonce_holder:
338
+ return []
339
+ latest = nonce_holder[-1]
340
+ return [
341
+ {
342
+ "sender": "lumina",
343
+ "content": _make_pong_payload(latest, "lumina"),
344
+ }
345
+ ]
346
+
347
+ mock_chat = MagicMock()
348
+ mock_chat.send.side_effect = fake_send
349
+ mock_chat.receive.side_effect = fake_receive
350
+
351
+ with patch("skcapstone.cli.test_connection.AgentChat", return_value=mock_chat):
352
+ result = self._run(
353
+ [
354
+ "test-connection", "lumina",
355
+ "--home", str(agent_home),
356
+ "--count", "3",
357
+ ]
358
+ )
359
+
360
+ assert result.exit_code == 0, result.output
361
+ # Summary line for multiple pings contains avg=
362
+ assert "avg=" in result.output
363
+ assert "min=" in result.output
364
+ assert "max=" in result.output