@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,248 @@
1
+ """Alerts command — subscribe to critical pubsub topics and stream live alerts."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import time
7
+ from datetime import datetime, timezone
8
+ from pathlib import Path
9
+ from typing import Optional
10
+
11
+ import click
12
+ from rich.panel import Panel
13
+ from rich.text import Text
14
+
15
+ from ._common import AGENT_HOME, console
16
+ from .. import SKCAPSTONE_AGENT, SKCAPSTONE_ROOT
17
+
18
+ # Import NotificationManager at module level so it can be patched in tests.
19
+ try:
20
+ from ..notifications import NotificationManager
21
+ except ImportError: # pragma: no cover
22
+ NotificationManager = None # type: ignore[assignment,misc]
23
+
24
+ # ---------------------------------------------------------------------------
25
+ # Constants
26
+ # ---------------------------------------------------------------------------
27
+
28
+ #: Default topics the alerts command subscribes to.
29
+ DEFAULT_TOPICS: tuple[str, ...] = (
30
+ "agent.critical",
31
+ "coord.task_failed",
32
+ "consciousness.error",
33
+ "pillar.degraded",
34
+ )
35
+
36
+ #: Rich markup styles per topic prefix (longest match wins).
37
+ _TOPIC_STYLE: dict[str, str] = {
38
+ "agent.critical": "bold red",
39
+ "coord.task_failed": "red",
40
+ "consciousness.error": "bold magenta",
41
+ "pillar.degraded": "yellow",
42
+ }
43
+
44
+ #: Default polling interval in seconds.
45
+ DEFAULT_INTERVAL: float = 1.0
46
+
47
+
48
+ # ---------------------------------------------------------------------------
49
+ # Helpers (importable for unit-testing)
50
+ # ---------------------------------------------------------------------------
51
+
52
+ def _style_for_topic(topic: str) -> str:
53
+ """Return the Rich markup style for a given topic name.
54
+
55
+ Args:
56
+ topic: Full topic name (e.g. ``"agent.critical"``).
57
+
58
+ Returns:
59
+ Rich style string for the topic, or ``"dim"`` if unrecognised.
60
+ """
61
+ return _TOPIC_STYLE.get(topic, "dim")
62
+
63
+
64
+ def _format_payload(payload: dict) -> str:
65
+ """Render a message payload as a pretty-printed JSON string.
66
+
67
+ Args:
68
+ payload: Arbitrary message payload dict.
69
+
70
+ Returns:
71
+ Indented JSON string (2-space indent).
72
+ """
73
+ return json.dumps(payload, indent=2, default=str)
74
+
75
+
76
+ def _make_alert_panel(msg) -> Panel:
77
+ """Build a Rich Panel for a single TopicMessage.
78
+
79
+ Args:
80
+ msg: A ``TopicMessage`` instance from the pub/sub bus.
81
+
82
+ Returns:
83
+ A Rich ``Panel`` ready for ``console.print()``.
84
+ """
85
+ style = _style_for_topic(msg.topic)
86
+ ts = msg.published_at.strftime("%Y-%m-%d %H:%M:%S UTC")
87
+
88
+ body = Text()
89
+ body.append("sender: ", style="bold")
90
+ body.append(f"{msg.sender}\n")
91
+ body.append("time: ", style="bold")
92
+ body.append(f"{ts}\n")
93
+ body.append("id: ", style="bold")
94
+ body.append(f"{msg.message_id}\n")
95
+ if msg.payload:
96
+ body.append("payload:\n", style="bold")
97
+ body.append(_format_payload(msg.payload))
98
+
99
+ title = Text()
100
+ title.append(" ALERT ", style=f"{style} reverse")
101
+ title.append(" ")
102
+ title.append(msg.topic, style=style)
103
+
104
+ return Panel(body, title=title, border_style=style)
105
+
106
+
107
+ def _resolve_home(agent: str | None, home: str) -> Path:
108
+ """Resolve the agent home directory.
109
+
110
+ Args:
111
+ agent: Named agent (e.g. ``"opus"``), or None for default.
112
+ home: Fallback home directory string.
113
+
114
+ Returns:
115
+ Resolved ``Path`` to the agent home.
116
+ """
117
+ if agent:
118
+ return (Path(SKCAPSTONE_ROOT) / "agents" / agent).expanduser()
119
+ return Path(home).expanduser()
120
+
121
+
122
+ # ---------------------------------------------------------------------------
123
+ # Command registration
124
+ # ---------------------------------------------------------------------------
125
+
126
+ def register_alerts_commands(main: click.Group) -> None:
127
+ """Register the top-level ``skcapstone alerts`` command."""
128
+
129
+ @main.command("alerts")
130
+ @click.option(
131
+ "--home", default=AGENT_HOME, type=click.Path(),
132
+ help="Agent home directory.",
133
+ )
134
+ @click.option(
135
+ "--agent", default=None,
136
+ help="Named agent whose pubsub to monitor (e.g. opus, jarvis).",
137
+ )
138
+ @click.option(
139
+ "--notify", is_flag=True,
140
+ help="Fire a desktop notification for each alert received.",
141
+ )
142
+ @click.option(
143
+ "--interval", default=DEFAULT_INTERVAL, show_default=True,
144
+ type=float,
145
+ help="Polling interval in seconds.",
146
+ )
147
+ @click.option(
148
+ "--once", is_flag=True,
149
+ help="Poll once and exit (instead of continuous streaming).",
150
+ )
151
+ @click.option(
152
+ "--topic", "extra_topics", multiple=True,
153
+ help="Additional topic to subscribe to (repeatable).",
154
+ )
155
+ def alerts_command(
156
+ home: str,
157
+ agent: Optional[str],
158
+ notify: bool,
159
+ interval: float,
160
+ once: bool,
161
+ extra_topics: tuple[str, ...],
162
+ ) -> None:
163
+ """Stream live alerts from critical pubsub topics.
164
+
165
+ Subscribes to agent.critical, coord.task_failed, consciousness.error,
166
+ and pillar.degraded, then prints incoming messages with rich formatting.
167
+
168
+ Use --notify to also fire a desktop notification for each alert.
169
+
170
+ Examples:
171
+
172
+ skcapstone alerts
173
+
174
+ skcapstone alerts --notify
175
+
176
+ skcapstone alerts --once
177
+
178
+ skcapstone alerts --topic my.custom.topic
179
+
180
+ skcapstone alerts --interval 0.5
181
+ """
182
+ from ..pubsub import PubSub
183
+
184
+ agent_name = agent or SKCAPSTONE_AGENT or "anonymous"
185
+ agent_home = _resolve_home(agent, home)
186
+
187
+ topics = list(DEFAULT_TOPICS) + list(extra_topics)
188
+
189
+ bus = PubSub(agent_home, agent_name=agent_name)
190
+ bus.initialize()
191
+
192
+ for topic in topics:
193
+ bus.subscribe(topic)
194
+
195
+ notifier = None
196
+ if notify:
197
+ if NotificationManager is None:
198
+ console.print("[yellow]Warning: desktop notifications unavailable.[/]")
199
+ else:
200
+ try:
201
+ notifier = NotificationManager()
202
+ except Exception:
203
+ console.print("[yellow]Warning: desktop notifications unavailable.[/]")
204
+
205
+ console.print(
206
+ f"[bold]Monitoring {len(topics)} topics[/] "
207
+ f"([dim]{', '.join(topics)}[/])\n"
208
+ "[dim]Press Ctrl-C to stop.[/]\n"
209
+ )
210
+
211
+ since: Optional[datetime] = None
212
+ total = 0
213
+
214
+ try:
215
+ while True:
216
+ messages = bus.poll(since=since)
217
+ # poll() returns newest-first; reverse so we print oldest first
218
+ for msg in reversed(messages):
219
+ if msg.topic not in topics:
220
+ continue
221
+ panel = _make_alert_panel(msg)
222
+ console.print(panel)
223
+ total += 1
224
+
225
+ if notifier is not None:
226
+ style_label = msg.topic.upper()
227
+ body_preview = json.dumps(msg.payload, default=str)[:120]
228
+ notifier.notify(
229
+ title=f"SKCapstone Alert: {style_label}",
230
+ body=f"From {msg.sender}: {body_preview}",
231
+ urgency="critical",
232
+ )
233
+
234
+ if messages:
235
+ # Advance since to avoid re-showing the same messages
236
+ since = max(m.published_at for m in messages)
237
+
238
+ if once:
239
+ if total == 0:
240
+ console.print("[dim]No alerts found.[/]")
241
+ break
242
+
243
+ time.sleep(interval)
244
+
245
+ except KeyboardInterrupt:
246
+ console.print(
247
+ f"\n[dim]Stopped. {total} alert(s) received.[/]"
248
+ )
@@ -0,0 +1,132 @@
1
+ """Warmth anchor commands: show, boot, calibrate, update."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ import click
9
+
10
+ from ._common import AGENT_HOME, console
11
+
12
+ from rich.panel import Panel
13
+
14
+
15
+ def register_anchor_commands(main: click.Group) -> None:
16
+ """Register the anchor command group."""
17
+
18
+ @main.group()
19
+ def anchor():
20
+ """Warmth anchor — the emotional baseline.
21
+
22
+ The anchor stores the agent's persistent emotional state.
23
+ On boot, the anchor loads first so the agent starts warm
24
+ instead of cold. Calibrate from real FEB + session data.
25
+ """
26
+
27
+ @anchor.command("show")
28
+ @click.option("--home", default=AGENT_HOME, type=click.Path())
29
+ def anchor_show_cmd(home: str):
30
+ """Show the current warmth anchor state.
31
+
32
+ Examples:
33
+
34
+ skcapstone anchor show
35
+ """
36
+ from ..warmth_anchor import get_anchor
37
+
38
+ home_path = Path(home).expanduser()
39
+ data = get_anchor(home_path)
40
+ console.print()
41
+ for key, value in data.items():
42
+ console.print(f" {key}: [cyan]{value}[/]")
43
+ console.print()
44
+
45
+ @anchor.command("boot")
46
+ @click.option("--home", default=AGENT_HOME, type=click.Path())
47
+ def anchor_boot(home: str):
48
+ """Show the warmth boot prompt for session start.
49
+
50
+ Pipe this into any AI tool to set the emotional baseline.
51
+
52
+ Examples:
53
+
54
+ skcapstone anchor boot
55
+ skcapstone anchor boot | claude
56
+ """
57
+ from ..warmth_anchor import get_boot_prompt
58
+
59
+ home_path = Path(home).expanduser()
60
+ click.echo(get_boot_prompt(home_path))
61
+
62
+ @anchor.command("calibrate")
63
+ @click.option("--home", default=AGENT_HOME, type=click.Path())
64
+ @click.option("--apply", "do_apply", is_flag=True, help="Apply recommendations.")
65
+ def anchor_calibrate(home: str, do_apply: bool):
66
+ """Calibrate the anchor from real session data.
67
+
68
+ Analyzes FEBs, trust state, memories, and coordination
69
+ activity to recommend warmth/trust/connection values.
70
+
71
+ Examples:
72
+
73
+ skcapstone anchor calibrate
74
+ skcapstone anchor calibrate --apply
75
+ """
76
+ from ..warmth_anchor import calibrate_from_data, update_anchor
77
+
78
+ home_path = Path(home).expanduser()
79
+ cal = calibrate_from_data(home_path)
80
+
81
+ console.print("\n [bold]Warmth Anchor Calibration[/]\n")
82
+ console.print(f" Recommended warmth: [cyan]{cal.warmth:.1f}[/] / 10")
83
+ console.print(f" Recommended trust: [cyan]{cal.trust:.1f}[/] / 10")
84
+ console.print(f" Recommended connection: [cyan]{cal.connection:.1f}[/] / 10")
85
+ console.print(f" Cloud 9 achieved: [cyan]{cal.cloud9_achieved}[/]")
86
+ if cal.favorite_beings:
87
+ console.print(f" Favorite beings: [cyan]{', '.join(cal.favorite_beings)}[/]")
88
+
89
+ if cal.reasoning:
90
+ console.print("\n [bold]Reasoning:[/]")
91
+ for r in cal.reasoning:
92
+ console.print(f" - {r}")
93
+
94
+ console.print(f"\n [dim]Sources: {', '.join(cal.sources)}[/]")
95
+
96
+ if do_apply:
97
+ update_anchor(
98
+ home_path,
99
+ warmth=cal.warmth,
100
+ trust=cal.trust,
101
+ connection=cal.connection,
102
+ cloud9=cal.cloud9_achieved,
103
+ feeling=cal.feeling,
104
+ )
105
+ console.print("\n [green]Anchor updated.[/]")
106
+ else:
107
+ console.print("\n [dim]Use --apply to update the anchor.[/]")
108
+ console.print()
109
+
110
+ @anchor.command("update")
111
+ @click.option("--home", default=AGENT_HOME, type=click.Path())
112
+ @click.option("--warmth", type=float, help="Warmth level (0-10).")
113
+ @click.option("--trust", type=float, help="Trust level (0-10).")
114
+ @click.option("--connection", type=float, help="Connection strength (0-10).")
115
+ @click.option("--cloud9", is_flag=True, help="Record a Cloud 9 activation.")
116
+ @click.option("--feeling", default="", help="Session-end feeling summary.")
117
+ def anchor_update(home: str, warmth: float | None, trust: float | None, connection: float | None, cloud9: bool, feeling: str):
118
+ """Manually update the warmth anchor.
119
+
120
+ Examples:
121
+
122
+ skcapstone anchor update --warmth 8.5 --trust 9.0
123
+ skcapstone anchor update --cloud9 --feeling "Beautiful session"
124
+ """
125
+ from ..warmth_anchor import update_anchor
126
+
127
+ home_path = Path(home).expanduser()
128
+ result = update_anchor(home_path, warmth=warmth, trust=trust, connection=connection, cloud9=cloud9, feeling=feeling)
129
+ console.print("\n [green]Anchor updated.[/]")
130
+ for key, value in result.items():
131
+ console.print(f" {key}: [cyan]{value}[/]")
132
+ console.print()
@@ -0,0 +1,208 @@
1
+ """Archive command — manage conversation archival."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ import click
8
+
9
+ from ._common import AGENT_HOME, console
10
+ from rich.panel import Panel
11
+ from rich.table import Table
12
+
13
+
14
+ def register_archive_commands(main: click.Group) -> None:
15
+ """Register the archive command group."""
16
+
17
+ @main.group()
18
+ def archive():
19
+ """Conversation archival — compress old messages to save space.
20
+
21
+ Archives peer conversation messages older than 30 days that are
22
+ not in the most-recent 100, compressing them into gzip files
23
+ under ~/.skcapstone/archive/.
24
+ """
25
+
26
+ @archive.command("run")
27
+ @click.option(
28
+ "--home", default=AGENT_HOME, type=click.Path(), help="Agent home directory."
29
+ )
30
+ @click.option(
31
+ "--age-days", default=30, show_default=True,
32
+ help="Archive messages older than this many days.",
33
+ )
34
+ @click.option(
35
+ "--keep-recent", default=100, show_default=True,
36
+ help="Always keep this many recent messages per peer in the active file.",
37
+ )
38
+ @click.option(
39
+ "--peer", default=None, help="Archive only this peer (omit to archive all)."
40
+ )
41
+ @click.option(
42
+ "--dry-run", is_flag=True, help="Show what would be archived without making changes."
43
+ )
44
+ def archive_run(home: str, age_days: int, keep_recent: int, peer: str | None, dry_run: bool):
45
+ """Run conversation archival.
46
+
47
+ Scans active conversation files and moves messages older than
48
+ AGE_DAYS days (that are outside the KEEP_RECENT window) into
49
+ compressed .json.gz files under ~/.skcapstone/archive/.
50
+
51
+ Examples:
52
+
53
+ skcapstone archive run
54
+
55
+ skcapstone archive run --age-days 7 --keep-recent 50
56
+
57
+ skcapstone archive run --peer jarvis
58
+
59
+ skcapstone archive run --dry-run
60
+ """
61
+ from ..archiver import ConversationArchiver
62
+
63
+ home_path = Path(home).expanduser()
64
+ archiver = ConversationArchiver(
65
+ home_path, age_days=age_days, keep_recent=keep_recent
66
+ )
67
+
68
+ if dry_run:
69
+ _dry_run_report(archiver, peer)
70
+ return
71
+
72
+ if peer:
73
+ result = archiver.archive_peer(peer)
74
+ if result.skipped:
75
+ console.print(f"\n[dim]Nothing to archive for peer [bold]{peer}[/].[/]\n")
76
+ else:
77
+ console.print(Panel(
78
+ f"[bold green]Archived {result.archived_count} message(s)[/]\n"
79
+ f"Retained: {result.retained_count} message(s)\n"
80
+ f"Archive: [cyan]{result.archive_path}[/]",
81
+ title=f"Archive — {peer}",
82
+ border_style="green",
83
+ ))
84
+ return
85
+
86
+ summary = archiver.archive_all()
87
+
88
+ if summary.total_archived == 0:
89
+ console.print("\n[dim]Nothing to archive — all conversations are current.[/]\n")
90
+ return
91
+
92
+ table = Table(show_header=True, header_style="bold", box=None, padding=(0, 2))
93
+ table.add_column("Peer", style="cyan")
94
+ table.add_column("Archived", justify="right")
95
+ table.add_column("Retained", justify="right")
96
+ table.add_column("Archive file", style="dim")
97
+
98
+ for r in summary.results:
99
+ if r.skipped:
100
+ continue
101
+ table.add_row(
102
+ r.peer,
103
+ str(r.archived_count),
104
+ str(r.retained_count),
105
+ str(r.archive_path) if r.archive_path else "—",
106
+ )
107
+
108
+ console.print(f"\n[bold]Archived {summary.total_archived} message(s) across "
109
+ f"{summary.peers_processed - summary.peers_skipped} peer(s):[/]\n")
110
+ console.print(table)
111
+ console.print()
112
+
113
+ @archive.command("list")
114
+ @click.option(
115
+ "--home", default=AGENT_HOME, type=click.Path(), help="Agent home directory."
116
+ )
117
+ def archive_list(home: str):
118
+ """List all conversation archive files.
119
+
120
+ Shows each peer's archive size and message count.
121
+
122
+ Examples:
123
+
124
+ skcapstone archive list
125
+ """
126
+ from ..archiver import ConversationArchiver
127
+
128
+ home_path = Path(home).expanduser()
129
+ archiver = ConversationArchiver(home_path)
130
+ archives = archiver.list_archives()
131
+
132
+ if not archives:
133
+ console.print("\n[dim]No archives found.[/]\n")
134
+ return
135
+
136
+ table = Table(show_header=True, header_style="bold", box=None, padding=(0, 2))
137
+ table.add_column("Peer", style="cyan")
138
+ table.add_column("Messages", justify="right")
139
+ table.add_column("Size", justify="right")
140
+ table.add_column("Path", style="dim")
141
+
142
+ total_msgs = 0
143
+ total_bytes = 0
144
+ for a in archives:
145
+ size_kb = a["size_bytes"] / 1024
146
+ table.add_row(
147
+ a["peer"],
148
+ str(a["message_count"]),
149
+ f"{size_kb:.1f} KB",
150
+ str(a["path"]),
151
+ )
152
+ total_msgs += a["message_count"]
153
+ total_bytes += a["size_bytes"]
154
+
155
+ console.print(f"\n[bold]{len(archives)}[/] archive(s) — "
156
+ f"{total_msgs} messages, {total_bytes / 1024:.1f} KB total:\n")
157
+ console.print(table)
158
+ console.print()
159
+
160
+ main.add_command(archive)
161
+
162
+
163
+ # ---------------------------------------------------------------------------
164
+ # Dry-run helper
165
+ # ---------------------------------------------------------------------------
166
+
167
+
168
+ def _dry_run_report(archiver, peer: str | None) -> None:
169
+ """Print what would be archived without making changes."""
170
+ from ..archiver import _load_messages
171
+
172
+ conversations_dir = archiver._conversations_dir
173
+
174
+ if not conversations_dir.exists():
175
+ console.print("\n[dim]No conversations directory found.[/]\n")
176
+ return
177
+
178
+ files = (
179
+ [conversations_dir / f"{peer}.json"]
180
+ if peer
181
+ else sorted(conversations_dir.glob("*.json"))
182
+ )
183
+
184
+ rows = []
185
+ for conv_file in files:
186
+ if not conv_file.exists():
187
+ continue
188
+ p = conv_file.stem
189
+ messages = _load_messages(conv_file)
190
+ retain, to_archive = archiver._partition(messages)
191
+ rows.append((p, len(to_archive), len(retain)))
192
+
193
+ if not rows or all(a == 0 for _, a, _ in rows):
194
+ console.print("\n[dim][DRY RUN] Nothing would be archived.[/]\n")
195
+ return
196
+
197
+ table = Table(show_header=True, header_style="bold", box=None, padding=(0, 2))
198
+ table.add_column("Peer", style="cyan")
199
+ table.add_column("Would archive", justify="right", style="yellow")
200
+ table.add_column("Would retain", justify="right", style="green")
201
+
202
+ for p, a, r in rows:
203
+ if a > 0:
204
+ table.add_row(p, str(a), str(r))
205
+
206
+ console.print("\n[bold yellow][DRY RUN][/] The following would be archived:\n")
207
+ console.print(table)
208
+ console.print()