@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,498 @@
1
+ """Tests for ConversationArchiver — conversation archival and compression."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import gzip
6
+ import json
7
+ from datetime import datetime, timezone, timedelta
8
+ from pathlib import Path
9
+
10
+ import pytest
11
+
12
+ from skcapstone.archiver import (
13
+ ConversationArchiver,
14
+ ArchiveResult,
15
+ ArchiveSummary,
16
+ _parse_ts,
17
+ _load_messages,
18
+ _save_messages,
19
+ _load_archive,
20
+ _save_archive,
21
+ )
22
+
23
+
24
+ # ---------------------------------------------------------------------------
25
+ # Helpers
26
+ # ---------------------------------------------------------------------------
27
+
28
+
29
+ def _ts(days_ago: float) -> str:
30
+ """Return an ISO-8601 UTC timestamp for N days ago."""
31
+ dt = datetime.now(timezone.utc) - timedelta(days=days_ago)
32
+ return dt.isoformat()
33
+
34
+
35
+ def _make_msg(role: str, content: str, days_ago: float) -> dict:
36
+ return {"role": role, "content": content, "timestamp": _ts(days_ago)}
37
+
38
+
39
+ def _write_conv(conv_dir: Path, peer: str, messages: list[dict]) -> Path:
40
+ conv_dir.mkdir(parents=True, exist_ok=True)
41
+ path = conv_dir / f"{peer}.json"
42
+ path.write_text(json.dumps(messages), encoding="utf-8")
43
+ return path
44
+
45
+
46
+ # ---------------------------------------------------------------------------
47
+ # Fixtures
48
+ # ---------------------------------------------------------------------------
49
+
50
+
51
+ @pytest.fixture
52
+ def home(tmp_path) -> Path:
53
+ h = tmp_path / ".skcapstone"
54
+ h.mkdir()
55
+ return h
56
+
57
+
58
+ @pytest.fixture
59
+ def archiver(home) -> ConversationArchiver:
60
+ return ConversationArchiver(home, age_days=30, keep_recent=100)
61
+
62
+
63
+ # ---------------------------------------------------------------------------
64
+ # _parse_ts
65
+ # ---------------------------------------------------------------------------
66
+
67
+
68
+ class TestParseTs:
69
+ def test_valid_utc_string(self):
70
+ ts = "2026-01-01T10:00:00+00:00"
71
+ dt = _parse_ts(ts)
72
+ assert dt is not None
73
+ assert dt.tzinfo is not None
74
+
75
+ def test_naive_datetime_gets_utc(self):
76
+ dt = _parse_ts("2026-01-01T10:00:00")
77
+ assert dt.tzinfo == timezone.utc
78
+
79
+ def test_none_returns_none(self):
80
+ assert _parse_ts(None) is None
81
+
82
+ def test_invalid_string_returns_none(self):
83
+ assert _parse_ts("not-a-date") is None
84
+
85
+ def test_empty_string_returns_none(self):
86
+ assert _parse_ts("") is None
87
+
88
+
89
+ # ---------------------------------------------------------------------------
90
+ # _load_messages / _save_messages
91
+ # ---------------------------------------------------------------------------
92
+
93
+
94
+ class TestLoadSaveMessages:
95
+ def test_round_trip(self, tmp_path):
96
+ msgs = [{"role": "user", "content": "hello", "timestamp": _ts(1)}]
97
+ path = tmp_path / "peer.json"
98
+ _save_messages(path, msgs)
99
+ loaded = _load_messages(path)
100
+ assert loaded == msgs
101
+
102
+ def test_load_missing_file_returns_empty(self, tmp_path):
103
+ assert _load_messages(tmp_path / "nonexistent.json") == []
104
+
105
+ def test_load_corrupt_file_returns_empty(self, tmp_path):
106
+ path = tmp_path / "bad.json"
107
+ path.write_text("not json")
108
+ assert _load_messages(path) == []
109
+
110
+ def test_load_non_list_json_returns_empty(self, tmp_path):
111
+ path = tmp_path / "dict.json"
112
+ path.write_text(json.dumps({"not": "a list"}))
113
+ assert _load_messages(path) == []
114
+
115
+ def test_save_atomic_no_tmp_file(self, tmp_path):
116
+ path = tmp_path / "peer.json"
117
+ _save_messages(path, [])
118
+ assert not (tmp_path / "peer.json.tmp").exists()
119
+
120
+
121
+ # ---------------------------------------------------------------------------
122
+ # _load_archive / _save_archive
123
+ # ---------------------------------------------------------------------------
124
+
125
+
126
+ class TestLoadSaveArchive:
127
+ def test_round_trip_gzip(self, tmp_path):
128
+ msgs = [{"role": "user", "content": "archived", "timestamp": _ts(60)}]
129
+ path = tmp_path / "peer.json.gz"
130
+ _save_archive(path, msgs)
131
+ loaded = _load_archive(path)
132
+ assert len(loaded) == 1
133
+ assert loaded[0]["content"] == "archived"
134
+
135
+ def test_is_actually_compressed(self, tmp_path):
136
+ path = tmp_path / "peer.json.gz"
137
+ _save_archive(path, [{"role": "user", "content": "x", "timestamp": _ts(60)}])
138
+ # Verify the file is gzip magic bytes (1f 8b)
139
+ raw = path.read_bytes()
140
+ assert raw[:2] == b"\x1f\x8b"
141
+
142
+ def test_missing_file_returns_empty(self, tmp_path):
143
+ assert _load_archive(tmp_path / "missing.json.gz") == []
144
+
145
+ def test_corrupt_archive_returns_empty(self, tmp_path):
146
+ path = tmp_path / "bad.json.gz"
147
+ path.write_bytes(b"not gzip data")
148
+ assert _load_archive(path) == []
149
+
150
+ def test_sorted_by_timestamp(self, tmp_path):
151
+ msgs = [
152
+ {"role": "user", "content": "newer", "timestamp": _ts(30)},
153
+ {"role": "user", "content": "older", "timestamp": _ts(90)},
154
+ ]
155
+ path = tmp_path / "peer.json.gz"
156
+ _save_archive(path, msgs)
157
+ loaded = _load_archive(path)
158
+ assert loaded[0]["content"] == "older"
159
+ assert loaded[1]["content"] == "newer"
160
+
161
+ def test_creates_parent_dirs(self, tmp_path):
162
+ deep = tmp_path / "a" / "b" / "peer.json.gz"
163
+ _save_archive(deep, [])
164
+ assert deep.exists()
165
+
166
+
167
+ # ---------------------------------------------------------------------------
168
+ # ConversationArchiver._partition
169
+ # ---------------------------------------------------------------------------
170
+
171
+
172
+ class TestPartition:
173
+ def test_all_recent_kept(self, archiver):
174
+ msgs = [_make_msg("user", f"msg {i}", i) for i in range(5)]
175
+ retain, to_archive = archiver._partition(msgs)
176
+ assert len(retain) == 5
177
+ assert len(to_archive) == 0
178
+
179
+ def test_old_messages_beyond_keep_recent_archived(self, home):
180
+ arch = ConversationArchiver(home, age_days=30, keep_recent=2)
181
+ msgs = [
182
+ _make_msg("user", "ancient", 90),
183
+ _make_msg("user", "old", 60),
184
+ _make_msg("user", "recent1", 5),
185
+ _make_msg("user", "recent2", 1),
186
+ ]
187
+ retain, to_archive = arch._partition(msgs)
188
+ # keep_recent=2 → last 2 always retained; first 2 are old → archived
189
+ assert len(retain) == 2
190
+ assert len(to_archive) == 2
191
+ contents = {m["content"] for m in to_archive}
192
+ assert "ancient" in contents
193
+ assert "old" in contents
194
+
195
+ def test_old_messages_within_keep_recent_not_archived(self, home):
196
+ # keep_recent=100 means all 5 messages are protected even if old
197
+ arch = ConversationArchiver(home, age_days=30, keep_recent=100)
198
+ msgs = [_make_msg("user", f"msg {i}", 60) for i in range(5)]
199
+ retain, to_archive = arch._partition(msgs)
200
+ assert len(to_archive) == 0
201
+ assert len(retain) == 5
202
+
203
+ def test_message_without_timestamp_always_retained(self, home):
204
+ arch = ConversationArchiver(home, age_days=30, keep_recent=1)
205
+ msgs = [
206
+ {"role": "user", "content": "no-ts", "timestamp": None},
207
+ _make_msg("user", "protected", 1),
208
+ ]
209
+ retain, to_archive = arch._partition(msgs)
210
+ # The no-ts message is not in the last 1, but has no parseable ts → retained
211
+ assert all(m["content"] != "no-ts" or m in retain for m in msgs)
212
+ assert len(to_archive) == 0
213
+
214
+ def test_empty_messages_returns_empty_lists(self, archiver):
215
+ assert archiver._partition([]) == ([], [])
216
+
217
+
218
+ # ---------------------------------------------------------------------------
219
+ # ConversationArchiver.archive_peer
220
+ # ---------------------------------------------------------------------------
221
+
222
+
223
+ class TestArchivePeer:
224
+ def test_missing_peer_file_returns_skipped(self, archiver):
225
+ result = archiver.archive_peer("nobody")
226
+ assert result.skipped is True
227
+ assert result.peer == "nobody"
228
+
229
+ def test_no_archivable_messages_returns_skipped(self, home, archiver):
230
+ conv_dir = home / "conversations"
231
+ msgs = [_make_msg("user", "fresh", 1)]
232
+ _write_conv(conv_dir, "jarvis", msgs)
233
+
234
+ result = archiver.archive_peer("jarvis")
235
+ assert result.skipped is True
236
+
237
+ def test_archives_old_messages(self, home):
238
+ arch = ConversationArchiver(home, age_days=30, keep_recent=1)
239
+ conv_dir = home / "conversations"
240
+ msgs = [
241
+ _make_msg("user", "ancient", 90),
242
+ _make_msg("user", "recent", 1),
243
+ ]
244
+ _write_conv(conv_dir, "alice", msgs)
245
+
246
+ result = arch.archive_peer("alice")
247
+ assert result.archived_count == 1
248
+ assert result.retained_count == 1
249
+ assert result.skipped is False
250
+
251
+ def test_archive_file_is_gzip_compressed(self, home):
252
+ arch = ConversationArchiver(home, age_days=30, keep_recent=1)
253
+ conv_dir = home / "conversations"
254
+ msgs = [
255
+ _make_msg("user", "old", 60),
256
+ _make_msg("user", "new", 1),
257
+ ]
258
+ _write_conv(conv_dir, "bob", msgs)
259
+ result = arch.archive_peer("bob")
260
+
261
+ assert result.archive_path is not None
262
+ raw = result.archive_path.read_bytes()
263
+ assert raw[:2] == b"\x1f\x8b", "Archive file must be gzip-compressed"
264
+
265
+ def test_active_file_rewritten_with_retained_only(self, home):
266
+ arch = ConversationArchiver(home, age_days=30, keep_recent=1)
267
+ conv_dir = home / "conversations"
268
+ msgs = [
269
+ _make_msg("user", "ancient", 90),
270
+ _make_msg("user", "recent", 2),
271
+ ]
272
+ _write_conv(conv_dir, "carol", msgs)
273
+ arch.archive_peer("carol")
274
+
275
+ active = json.loads((conv_dir / "carol.json").read_text())
276
+ assert len(active) == 1
277
+ assert active[0]["content"] == "recent"
278
+
279
+ def test_archive_accumulates_across_runs(self, home):
280
+ arch = ConversationArchiver(home, age_days=30, keep_recent=1)
281
+ conv_dir = home / "conversations"
282
+
283
+ # First run
284
+ _write_conv(conv_dir, "dave", [
285
+ _make_msg("user", "very old", 120),
286
+ _make_msg("user", "latest", 2),
287
+ ])
288
+ arch.archive_peer("dave")
289
+
290
+ # Second run: add another old message
291
+ _write_conv(conv_dir, "dave", [
292
+ _make_msg("user", "also old", 90),
293
+ _make_msg("user", "newest", 1),
294
+ ])
295
+ arch.archive_peer("dave")
296
+
297
+ archive_path = home / "archive" / "dave.json.gz"
298
+ archived = _load_archive(archive_path)
299
+ contents = {m["content"] for m in archived}
300
+ assert "very old" in contents
301
+ assert "also old" in contents
302
+
303
+ def test_archive_placed_in_archive_dir(self, home):
304
+ arch = ConversationArchiver(home, age_days=30, keep_recent=1)
305
+ conv_dir = home / "conversations"
306
+ _write_conv(conv_dir, "eve", [
307
+ _make_msg("user", "old", 60),
308
+ _make_msg("user", "new", 1),
309
+ ])
310
+ result = arch.archive_peer("eve")
311
+ assert result.archive_path == home / "archive" / "eve.json.gz"
312
+
313
+
314
+ # ---------------------------------------------------------------------------
315
+ # ConversationArchiver.archive_all
316
+ # ---------------------------------------------------------------------------
317
+
318
+
319
+ class TestArchiveAll:
320
+ def test_empty_conversations_dir_returns_empty_summary(self, archiver):
321
+ summary = archiver.archive_all()
322
+ assert summary.peers_processed == 0
323
+ assert summary.total_archived == 0
324
+
325
+ def test_archives_all_peers(self, home):
326
+ arch = ConversationArchiver(home, age_days=30, keep_recent=1)
327
+ conv_dir = home / "conversations"
328
+ for peer in ("alice", "bob", "carol"):
329
+ _write_conv(conv_dir, peer, [
330
+ _make_msg("user", "old", 60),
331
+ _make_msg("user", "new", 1),
332
+ ])
333
+
334
+ summary = arch.archive_all()
335
+ assert summary.peers_processed == 3
336
+ assert summary.total_archived == 3
337
+ assert summary.total_retained == 3
338
+
339
+ def test_skips_peers_with_nothing_to_archive(self, home):
340
+ arch = ConversationArchiver(home, age_days=30, keep_recent=100)
341
+ conv_dir = home / "conversations"
342
+ _write_conv(conv_dir, "fresh-peer", [_make_msg("user", "recent", 1)])
343
+
344
+ summary = arch.archive_all()
345
+ assert summary.peers_skipped == 1
346
+ assert summary.total_archived == 0
347
+
348
+ def test_summary_results_list_populated(self, home):
349
+ arch = ConversationArchiver(home, age_days=30, keep_recent=1)
350
+ conv_dir = home / "conversations"
351
+ _write_conv(conv_dir, "zara", [
352
+ _make_msg("user", "old", 60),
353
+ _make_msg("user", "new", 1),
354
+ ])
355
+ summary = arch.archive_all()
356
+ assert len(summary.results) == 1
357
+ assert summary.results[0].peer == "zara"
358
+
359
+
360
+ # ---------------------------------------------------------------------------
361
+ # ConversationArchiver.list_archives
362
+ # ---------------------------------------------------------------------------
363
+
364
+
365
+ class TestListArchives:
366
+ def test_empty_archive_dir_returns_empty(self, archiver):
367
+ assert archiver.list_archives() == []
368
+
369
+ def test_lists_existing_archives(self, home):
370
+ arch = ConversationArchiver(home, age_days=30, keep_recent=1)
371
+ conv_dir = home / "conversations"
372
+ _write_conv(conv_dir, "listme", [
373
+ _make_msg("user", "old", 60),
374
+ _make_msg("user", "new", 1),
375
+ ])
376
+ arch.archive_peer("listme")
377
+
378
+ archives = arch.list_archives()
379
+ assert len(archives) == 1
380
+ assert archives[0]["peer"] == "listme"
381
+ assert archives[0]["message_count"] == 1
382
+ assert archives[0]["size_bytes"] > 0
383
+
384
+ def test_list_includes_path_key(self, home):
385
+ arch = ConversationArchiver(home, age_days=30, keep_recent=1)
386
+ conv_dir = home / "conversations"
387
+ _write_conv(conv_dir, "pathtest", [
388
+ _make_msg("user", "old", 60),
389
+ _make_msg("user", "new", 1),
390
+ ])
391
+ arch.archive_peer("pathtest")
392
+
393
+ archives = arch.list_archives()
394
+ assert "path" in archives[0]
395
+ assert archives[0]["path"].exists()
396
+
397
+
398
+ # ---------------------------------------------------------------------------
399
+ # Custom archive directory
400
+ # ---------------------------------------------------------------------------
401
+
402
+
403
+ class TestCustomArchiveDir:
404
+ def test_custom_archive_dir_used(self, tmp_path):
405
+ home = tmp_path / ".skcapstone"
406
+ home.mkdir()
407
+ custom_dir = tmp_path / "my_archives"
408
+ arch = ConversationArchiver(home, age_days=30, keep_recent=1, archive_dir=custom_dir)
409
+
410
+ conv_dir = home / "conversations"
411
+ _write_conv(conv_dir, "custom", [
412
+ _make_msg("user", "old", 60),
413
+ _make_msg("user", "new", 1),
414
+ ])
415
+ result = arch.archive_peer("custom")
416
+
417
+ assert result.archive_path.parent == custom_dir
418
+
419
+
420
+ # ---------------------------------------------------------------------------
421
+ # CLI integration (smoke tests via Click test runner)
422
+ # ---------------------------------------------------------------------------
423
+
424
+
425
+ class TestArchiveCLI:
426
+ def test_archive_run_nothing_to_archive(self, home):
427
+ from click.testing import CliRunner
428
+ from skcapstone.cli import main
429
+
430
+ conv_dir = home / "conversations"
431
+ _write_conv(conv_dir, "peer", [_make_msg("user", "fresh", 1)])
432
+
433
+ runner = CliRunner()
434
+ result = runner.invoke(main, ["archive", "run", "--home", str(home)])
435
+ assert result.exit_code == 0
436
+ assert "Nothing to archive" in result.output
437
+
438
+ def test_archive_run_with_archivable_messages(self, home):
439
+ from click.testing import CliRunner
440
+ from skcapstone.cli import main
441
+
442
+ conv_dir = home / "conversations"
443
+ _write_conv(conv_dir, "oldpeer", [
444
+ _make_msg("user", "old message", 60),
445
+ _make_msg("user", "new message", 1),
446
+ ])
447
+
448
+ runner = CliRunner()
449
+ result = runner.invoke(
450
+ main,
451
+ ["archive", "run", "--home", str(home), "--keep-recent", "1"],
452
+ )
453
+ assert result.exit_code == 0
454
+
455
+ def test_archive_list_no_archives(self, home):
456
+ from click.testing import CliRunner
457
+ from skcapstone.cli import main
458
+
459
+ runner = CliRunner()
460
+ result = runner.invoke(main, ["archive", "list", "--home", str(home)])
461
+ assert result.exit_code == 0
462
+ assert "No archives" in result.output
463
+
464
+ def test_archive_dry_run_shows_preview(self, home):
465
+ from click.testing import CliRunner
466
+ from skcapstone.cli import main
467
+
468
+ conv_dir = home / "conversations"
469
+ _write_conv(conv_dir, "drypeer", [
470
+ _make_msg("user", "old", 60),
471
+ _make_msg("user", "new", 1),
472
+ ])
473
+
474
+ runner = CliRunner()
475
+ result = runner.invoke(
476
+ main,
477
+ ["archive", "run", "--home", str(home), "--keep-recent", "1", "--dry-run"],
478
+ )
479
+ assert result.exit_code == 0
480
+ assert "DRY RUN" in result.output
481
+ # Dry run must not actually create any archive file
482
+ assert not (home / "archive" / "drypeer.json.gz").exists()
483
+
484
+ def test_archive_list_after_run(self, home):
485
+ from click.testing import CliRunner
486
+ from skcapstone.cli import main
487
+
488
+ conv_dir = home / "conversations"
489
+ _write_conv(conv_dir, "listpeer", [
490
+ _make_msg("user", "old", 60),
491
+ _make_msg("user", "new", 1),
492
+ ])
493
+
494
+ runner = CliRunner()
495
+ runner.invoke(main, ["archive", "run", "--home", str(home), "--keep-recent", "1"])
496
+ result = runner.invoke(main, ["archive", "list", "--home", str(home)])
497
+ assert result.exit_code == 0
498
+ assert "listpeer" in result.output