@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,194 @@
1
+ """Logs command — tail daemon logs with optional filtering."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ import time
7
+ from collections import deque
8
+ from pathlib import Path
9
+
10
+ import click
11
+
12
+ from ._common import AGENT_HOME, console
13
+ from .. import SKCAPSTONE_ROOT
14
+
15
+ # Log level ordering (lowest → highest)
16
+ _LEVELS = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
17
+
18
+ # Rich markup style per level
19
+ _LEVEL_STYLE: dict[str, str] = {
20
+ "DEBUG": "dim",
21
+ "INFO": "",
22
+ "WARNING": "yellow",
23
+ "ERROR": "red",
24
+ "CRITICAL": "bold red",
25
+ }
26
+
27
+ # Regex to extract level from daemon log format:
28
+ # "2026-03-02 12:34:56,789 [skcapstone.daemon] INFO: message"
29
+ _LEVEL_RE = re.compile(r"\]\s+(DEBUG|INFO|WARNING|ERROR|CRITICAL):")
30
+
31
+
32
+ # ---------------------------------------------------------------------------
33
+ # Helpers (importable for unit-testing)
34
+ # ---------------------------------------------------------------------------
35
+
36
+ def _resolve_log_file(agent: str | None, home: str) -> Path:
37
+ """Return the path to daemon.log for the given agent/home."""
38
+ if agent:
39
+ base = (Path(SKCAPSTONE_ROOT) / "agents" / agent).expanduser()
40
+ else:
41
+ base = Path(home).expanduser()
42
+ return base / "logs" / "daemon.log"
43
+
44
+
45
+ def _parse_level(line: str) -> str | None:
46
+ """Extract the log level keyword from a formatted log line, or None."""
47
+ m = _LEVEL_RE.search(line)
48
+ return m.group(1) if m else None
49
+
50
+
51
+ def _matches_filters(line: str, min_level: str | None, peer: str | None) -> bool:
52
+ """Return True if *line* passes the active filters.
53
+
54
+ Args:
55
+ line: A single log line (no trailing newline).
56
+ min_level: Minimum log level (e.g. ``"WARNING"``). Lines whose level
57
+ is below this threshold are excluded. ``None`` disables the filter.
58
+ peer: Substring to match against the line (case-insensitive).
59
+ ``None`` disables the filter.
60
+ """
61
+ if min_level:
62
+ lvl = _parse_level(line)
63
+ if lvl is None:
64
+ return False
65
+ if _LEVELS.index(lvl) < _LEVELS.index(min_level):
66
+ return False
67
+ if peer and peer.lower() not in line.lower():
68
+ return False
69
+ return True
70
+
71
+
72
+ def _format_line(line: str) -> str:
73
+ """Wrap *line* in Rich markup that reflects its log level."""
74
+ lvl = _parse_level(line)
75
+ style = _LEVEL_STYLE.get(lvl or "", "")
76
+ if not style:
77
+ return line
78
+ return f"[{style}]{line}[/]"
79
+
80
+
81
+ def _tail(path: Path, n: int) -> list[str]:
82
+ """Return the last *n* lines from *path* (with newlines preserved)."""
83
+ with open(path) as fh:
84
+ return list(deque(fh, maxlen=n))
85
+
86
+
87
+ # ---------------------------------------------------------------------------
88
+ # Command registration
89
+ # ---------------------------------------------------------------------------
90
+
91
+ def register_logs_commands(main: click.Group) -> None:
92
+ """Register the top-level ``skcapstone logs`` command."""
93
+
94
+ @main.command("logs")
95
+ @click.option(
96
+ "--home", default=AGENT_HOME, type=click.Path(),
97
+ help="Agent home directory.",
98
+ )
99
+ @click.option(
100
+ "--agent", default=None,
101
+ help="Named agent whose logs to read (e.g. opus, jarvis).",
102
+ )
103
+ @click.option(
104
+ "--follow", "-f", is_flag=True,
105
+ help="Stream new log entries as they arrive (like tail -f).",
106
+ )
107
+ @click.option(
108
+ "--lines", "-n", default=50, show_default=True,
109
+ help="Number of recent lines to show.",
110
+ )
111
+ @click.option(
112
+ "--level",
113
+ default=None,
114
+ type=click.Choice(
115
+ ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
116
+ case_sensitive=False,
117
+ ),
118
+ help="Minimum log level to display.",
119
+ )
120
+ @click.option(
121
+ "--peer", default=None,
122
+ help="Only show lines that mention this peer name.",
123
+ )
124
+ def logs_command(
125
+ home: str,
126
+ agent: str | None,
127
+ follow: bool,
128
+ lines: int,
129
+ level: str | None,
130
+ peer: str | None,
131
+ ) -> None:
132
+ """Tail daemon logs in real-time.
133
+
134
+ Reads from ~/.skcapstone/logs/daemon.log by default.
135
+ Use --agent or --home to target a specific named agent.
136
+
137
+ Examples:
138
+
139
+ skcapstone logs
140
+
141
+ skcapstone logs -n 100
142
+
143
+ skcapstone logs -f
144
+
145
+ skcapstone logs --level WARNING
146
+
147
+ skcapstone logs --peer opus --follow
148
+ """
149
+ log_file = _resolve_log_file(agent, home)
150
+ min_level = level.upper() if level else None
151
+
152
+ if not log_file.exists():
153
+ console.print(
154
+ f"[yellow]Log file not found:[/] {log_file}\n"
155
+ "[dim]Start the daemon to create logs.[/]"
156
+ )
157
+ return
158
+
159
+ if not follow:
160
+ # Static mode: read last N lines, apply filters, print, exit.
161
+ raw = _tail(log_file, lines)
162
+ filtered = [
163
+ ln.rstrip("\n")
164
+ for ln in raw
165
+ if _matches_filters(ln.rstrip("\n"), min_level, peer)
166
+ ]
167
+ if not filtered:
168
+ console.print("[dim]No matching log lines.[/]")
169
+ return
170
+ for ln in filtered:
171
+ console.print(_format_line(ln))
172
+ return
173
+
174
+ # Follow mode: show initial N lines, then stream new content.
175
+ try:
176
+ with open(log_file) as fh:
177
+ # Emit the last N historical lines first.
178
+ initial = list(deque(fh, maxlen=lines))
179
+ for ln in initial:
180
+ ln = ln.rstrip("\n")
181
+ if _matches_filters(ln, min_level, peer):
182
+ console.print(_format_line(ln))
183
+
184
+ # Poll for new content until Ctrl-C.
185
+ while True:
186
+ chunk = fh.read()
187
+ if chunk:
188
+ for ln in chunk.splitlines():
189
+ if _matches_filters(ln, min_level, peer):
190
+ console.print(_format_line(ln))
191
+ time.sleep(0.2)
192
+
193
+ except KeyboardInterrupt:
194
+ pass
@@ -0,0 +1,32 @@
1
+ """MCP (Model Context Protocol) server commands: serve."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import click
6
+
7
+
8
+ def register_mcp_commands(main: click.Group) -> None:
9
+ """Register the mcp command group."""
10
+
11
+ @main.group()
12
+ def mcp():
13
+ """MCP (Model Context Protocol) server.
14
+
15
+ Expose sovereign agent capabilities as MCP tools for
16
+ AI platforms like Cursor and Claude Desktop.
17
+ """
18
+
19
+ @mcp.command("serve")
20
+ def mcp_serve():
21
+ """Start the MCP server on stdio transport.
22
+
23
+ Exposes agent_status, memory_recall, memory_store, send_message,
24
+ check_inbox, sync_push, sync_pull, coord_status, coord_claim,
25
+ and coord_complete as MCP tools.
26
+
27
+ For Cursor: configure in .cursor/mcp.json.
28
+ For Claude Desktop: add to claude_desktop_config.json.
29
+ """
30
+ from ..mcp_server import main as mcp_main
31
+
32
+ mcp_main()
@@ -0,0 +1,418 @@
1
+ """Memory commands: store, search, list, recall, delete, stats, gc, curate, migrate, verify, reindex."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ import click
10
+
11
+ from ._common import AGENT_HOME, console, status_icon
12
+ from ._validators import validate_task_id
13
+ from ..pillars.security import audit_event
14
+
15
+ from rich.panel import Panel
16
+ from rich.table import Table
17
+ from rich.text import Text
18
+
19
+
20
+ def register_memory_commands(main: click.Group) -> None:
21
+ """Register the memory command group."""
22
+
23
+ @main.group()
24
+ def memory():
25
+ """Sovereign memory — your agent never forgets.
26
+
27
+ Store, search, recall, and manage memories across
28
+ sessions and platforms.
29
+ """
30
+
31
+ @memory.command("store")
32
+ @click.option("--home", default=AGENT_HOME, type=click.Path())
33
+ @click.argument("content")
34
+ @click.option("--tag", "-t", multiple=True, help="Tags for categorization.")
35
+ @click.option("--source", "-s", default="cli", help="Memory source.")
36
+ @click.option("--importance", "-i", default=0.5, type=float, help="Importance 0.0-1.0.")
37
+ @click.option("--layer", "-l", type=click.Choice(["short-term", "mid-term", "long-term"]), default=None)
38
+ def memory_store(home, content, tag, source, importance, layer):
39
+ """Store a new memory."""
40
+ from ..memory_engine import store as mem_store
41
+ from ..models import MemoryLayer
42
+
43
+ home_path = Path(home).expanduser()
44
+ if not home_path.exists():
45
+ console.print("[bold red]No agent found.[/] Run skcapstone init first.")
46
+ sys.exit(1)
47
+
48
+ lyr = MemoryLayer(layer) if layer else None
49
+ entry = mem_store(home=home_path, content=content, tags=list(tag),
50
+ source=source, importance=importance, layer=lyr)
51
+
52
+ console.print(f"\n [green]Stored:[/] {entry.memory_id}")
53
+ console.print(f" Layer: [cyan]{entry.layer.value}[/]")
54
+ console.print(f" Tags: {', '.join(entry.tags) if entry.tags else '[dim]none[/]'}")
55
+ console.print(f" Importance: {entry.importance}")
56
+ audit_event(home_path, "MEMORY_STORE", f"Memory {entry.memory_id} stored in {entry.layer.value}")
57
+ console.print()
58
+
59
+ @memory.command("search")
60
+ @click.option("--home", default=AGENT_HOME, type=click.Path())
61
+ @click.argument("query")
62
+ @click.option("--tag", "-t", multiple=True, help="Filter by tag.")
63
+ @click.option("--layer", "-l", type=click.Choice(["short-term", "mid-term", "long-term"]), default=None)
64
+ @click.option("--limit", "-n", default=20, help="Max results.")
65
+ @click.option("--json-out", is_flag=True, help="Output results as JSON.")
66
+ def memory_search(home, query, tag, layer, limit, json_out):
67
+ """Search memories by content and tags."""
68
+ from ..memory_engine import search as mem_search
69
+ from ..models import MemoryLayer
70
+
71
+ home_path = Path(home).expanduser()
72
+ if not home_path.exists():
73
+ if json_out:
74
+ print(json.dumps([]))
75
+ return
76
+ console.print("[bold red]No agent found.[/] Run skcapstone init first.")
77
+ sys.exit(1)
78
+
79
+ lyr = MemoryLayer(layer) if layer else None
80
+ tags = list(tag) if tag else None
81
+ results = mem_search(home=home_path, query=query, layer=lyr, tags=tags, limit=limit)
82
+
83
+ if json_out:
84
+ output = [
85
+ {
86
+ "id": entry.memory_id,
87
+ "content": entry.content,
88
+ "tags": entry.tags,
89
+ "importance": entry.importance,
90
+ "layer": entry.layer.value,
91
+ "created_at": entry.created_at.isoformat() if entry.created_at else None,
92
+ }
93
+ for entry in results
94
+ ]
95
+ print(json.dumps(output))
96
+ return
97
+
98
+ if not results:
99
+ console.print(f"\n [dim]No memories match '[/]{query}[dim]'[/]\n")
100
+ return
101
+
102
+ console.print(f"\n [bold]{len(results)}[/] memor{'y' if len(results) == 1 else 'ies'} found:\n")
103
+
104
+ table = Table(show_header=True, header_style="bold", box=None, padding=(0, 2))
105
+ table.add_column("ID", style="cyan", max_width=14)
106
+ table.add_column("Layer", style="dim")
107
+ table.add_column("Content", max_width=50)
108
+ table.add_column("Tags", style="dim")
109
+ table.add_column("Imp", justify="right")
110
+
111
+ for entry in results:
112
+ preview = entry.content[:80] + ("..." if len(entry.content) > 80 else "")
113
+ table.add_row(entry.memory_id, entry.layer.value, preview,
114
+ ", ".join(entry.tags) if entry.tags else "", f"{entry.importance:.1f}")
115
+
116
+ console.print(table)
117
+ console.print()
118
+
119
+ @memory.command("list")
120
+ @click.option("--home", default=AGENT_HOME, type=click.Path())
121
+ @click.option("--layer", "-l", type=click.Choice(["short-term", "mid-term", "long-term"]), default=None)
122
+ @click.option("--tag", "-t", multiple=True, help="Filter by tag.")
123
+ @click.option("--limit", "-n", default=50, help="Max results.")
124
+ def memory_list(home, layer, tag, limit):
125
+ """Browse memories, newest first."""
126
+ from ..memory_engine import list_memories as mem_list
127
+ from ..models import MemoryLayer
128
+
129
+ home_path = Path(home).expanduser()
130
+ if not home_path.exists():
131
+ console.print("[bold red]No agent found.[/] Run skcapstone init first.")
132
+ sys.exit(1)
133
+
134
+ lyr = MemoryLayer(layer) if layer else None
135
+ tags = list(tag) if tag else None
136
+ entries = mem_list(home=home_path, layer=lyr, tags=tags, limit=limit)
137
+
138
+ if not entries:
139
+ console.print("\n [dim]No memories found.[/]\n")
140
+ return
141
+
142
+ console.print(f"\n [bold]{len(entries)}[/] memor{'y' if len(entries) == 1 else 'ies'}:\n")
143
+
144
+ table = Table(show_header=True, header_style="bold", box=None, padding=(0, 2))
145
+ table.add_column("ID", style="cyan", max_width=14)
146
+ table.add_column("Layer")
147
+ table.add_column("Content", max_width=50)
148
+ table.add_column("Tags", style="dim")
149
+ table.add_column("Imp", justify="right")
150
+ table.add_column("Accessed", justify="right", style="dim")
151
+
152
+ for entry in entries:
153
+ preview = entry.content[:80] + ("..." if len(entry.content) > 80 else "")
154
+ layer_color = {"long-term": "green", "mid-term": "cyan", "short-term": "dim"}.get(entry.layer.value, "dim")
155
+ table.add_row(entry.memory_id, Text(entry.layer.value, style=layer_color), preview,
156
+ ", ".join(entry.tags) if entry.tags else "", f"{entry.importance:.1f}",
157
+ str(entry.access_count))
158
+
159
+ console.print(table)
160
+ console.print()
161
+
162
+ @memory.command("recall")
163
+ @click.option("--home", default=AGENT_HOME, type=click.Path())
164
+ @click.argument("memory_id")
165
+ def memory_recall(home, memory_id):
166
+ """Recall a specific memory by ID."""
167
+ from ..memory_engine import recall as mem_recall
168
+
169
+ validate_task_id(memory_id) # memory IDs are hex UUIDs
170
+
171
+ home_path = Path(home).expanduser()
172
+ if not home_path.exists():
173
+ console.print("[bold red]No agent found.[/] Run skcapstone init first.")
174
+ sys.exit(1)
175
+
176
+ entry = mem_recall(home=home_path, memory_id=memory_id)
177
+ if entry is None:
178
+ console.print(f"[red]Memory not found:[/] {memory_id}")
179
+ sys.exit(1)
180
+
181
+ console.print()
182
+ console.print(Panel(
183
+ entry.content,
184
+ title=f"[cyan]{entry.memory_id}[/] — {entry.layer.value}",
185
+ subtitle=f"importance={entry.importance} accessed={entry.access_count} source={entry.source}",
186
+ border_style="bright_blue",
187
+ ))
188
+ if entry.tags:
189
+ console.print(f" Tags: {', '.join(entry.tags)}")
190
+ if entry.metadata:
191
+ console.print(f" Metadata: {json.dumps(entry.metadata)}")
192
+ console.print(f" Created: {entry.created_at.isoformat() if entry.created_at else 'unknown'}")
193
+ if entry.accessed_at:
194
+ console.print(f" Last accessed: {entry.accessed_at.isoformat()}")
195
+ console.print()
196
+
197
+ @memory.command("delete")
198
+ @click.option("--home", default=AGENT_HOME, type=click.Path())
199
+ @click.argument("memory_id")
200
+ @click.option("--force", is_flag=True, help="Skip confirmation.")
201
+ def memory_delete(home, memory_id, force):
202
+ """Delete a memory by ID."""
203
+ from ..memory_engine import delete as mem_delete
204
+
205
+ validate_task_id(memory_id) # memory IDs are hex UUIDs
206
+
207
+ home_path = Path(home).expanduser()
208
+ if not force and not click.confirm(f"Delete memory {memory_id}?"):
209
+ console.print("[yellow]Aborted.[/]")
210
+ return
211
+
212
+ if mem_delete(home_path, memory_id):
213
+ console.print(f"\n [red]Deleted:[/] {memory_id}\n")
214
+ audit_event(home_path, "MEMORY_DELETE", f"Memory {memory_id} deleted")
215
+ else:
216
+ console.print(f"[red]Memory not found:[/] {memory_id}")
217
+ sys.exit(1)
218
+
219
+ @memory.command("stats")
220
+ @click.option("--home", default=AGENT_HOME, type=click.Path())
221
+ def memory_stats(home):
222
+ """Show memory statistics across all layers."""
223
+ from ..memory_engine import get_stats
224
+
225
+ home_path = Path(home).expanduser()
226
+ if not home_path.exists():
227
+ console.print("[bold red]No agent found.[/] Run skcapstone init first.")
228
+ sys.exit(1)
229
+
230
+ stats = get_stats(home_path)
231
+ console.print()
232
+ console.print(Panel(
233
+ f"Total: [bold]{stats.total_memories}[/] memories\n"
234
+ f" [green]Long-term:[/] {stats.long_term}\n"
235
+ f" [cyan]Mid-term:[/] {stats.mid_term}\n"
236
+ f" [dim]Short-term:[/] {stats.short_term}\n\n"
237
+ f"Store: {stats.store_path}\n"
238
+ f"Status: {status_icon(stats.status)}",
239
+ title="SKMemory", border_style="bright_blue",
240
+ ))
241
+ console.print()
242
+
243
+ @memory.command("gc")
244
+ @click.option("--home", default=AGENT_HOME, type=click.Path())
245
+ def memory_gc(home):
246
+ """Garbage-collect expired short-term memories."""
247
+ from ..memory_engine import gc_expired
248
+
249
+ home_path = Path(home).expanduser()
250
+ removed = gc_expired(home_path)
251
+ if removed:
252
+ console.print(f"\n [yellow]Cleaned up {removed} expired memor{'y' if removed == 1 else 'ies'}.[/]\n")
253
+ else:
254
+ console.print("\n [green]Nothing to clean up.[/]\n")
255
+
256
+ @memory.command("curate")
257
+ @click.option("--home", default=AGENT_HOME, type=click.Path())
258
+ @click.option("--dry-run", is_flag=True, help="Preview changes without applying.")
259
+ @click.option("--promote", is_flag=True, help="Only run promotion pass.")
260
+ @click.option("--dedupe", is_flag=True, help="Only run deduplication pass.")
261
+ @click.option("--stats", is_flag=True, help="Show curation statistics only.")
262
+ def memory_curate(home, dry_run, promote, dedupe, stats):
263
+ """Curate memories: auto-tag, promote, deduplicate."""
264
+ from ..memory_curator import MemoryCurator
265
+
266
+ home_path = Path(home).expanduser()
267
+ curator = MemoryCurator(home_path)
268
+
269
+ if stats:
270
+ s = curator.get_stats()
271
+ console.print(f"\n [bold]{s['total']}[/] memories")
272
+ for lyr, count in s.get("layers", {}).items():
273
+ console.print(f" {lyr}: {count}")
274
+ console.print(f" Tag coverage: [bold]{s['tag_coverage']:.0%}[/]")
275
+ console.print(f" Avg importance: [bold]{s['avg_importance']:.2f}[/]")
276
+ console.print(f" Promotion candidates: [bold]{s['promotion_candidates']}[/]")
277
+ if s.get("top_tags"):
278
+ console.print(" Top tags:")
279
+ for tg, count in s["top_tags"][:10]:
280
+ console.print(f" {tg}: {count}")
281
+ console.print()
282
+ return
283
+
284
+ run_promote = promote or (not promote and not dedupe)
285
+ run_dedupe = dedupe or (not promote and not dedupe)
286
+
287
+ prefix = "[DRY RUN] " if dry_run else ""
288
+ console.print(f"\n {prefix}Running curation pass...\n")
289
+
290
+ result = curator.curate(dry_run=dry_run, promote=run_promote, dedupe=run_dedupe)
291
+
292
+ console.print(f" Scanned: {result.total_scanned} memories")
293
+ if result.tagged:
294
+ console.print(f" [cyan]Tagged:[/] {len(result.tagged)} memories received new tags")
295
+ if result.promoted:
296
+ console.print(f" [green]Promoted:[/] {len(result.promoted)} memories moved to higher tier")
297
+ if result.deduped:
298
+ console.print(f" [yellow]Deduped:[/] {len(result.deduped)} duplicate(s) removed")
299
+ if not result.tagged and not result.promoted and not result.deduped:
300
+ console.print(" [dim]Nothing to curate — memories are clean.[/]")
301
+ console.print()
302
+
303
+ @memory.command("migrate")
304
+ @click.option("--home", default=AGENT_HOME, type=click.Path())
305
+ @click.option("--dry-run", is_flag=True, help="Preview without writing.")
306
+ @click.option("--verify", is_flag=True, help="Verify migration integrity.")
307
+ def memory_migrate(home, dry_run, verify):
308
+ """Migrate JSON memories to the unified three-tier backend."""
309
+ from ..migrate_memories import migrate
310
+
311
+ home_path = Path(home).expanduser()
312
+ if not home_path.exists():
313
+ console.print("[bold red]No agent found.[/] Run skcapstone init first.")
314
+ sys.exit(1)
315
+
316
+ result = migrate(home_path, dry_run=dry_run, verify=verify)
317
+
318
+ if dry_run:
319
+ console.print(f"\n [bold]DRY RUN:[/] Found {result['total_json']} JSON memories to migrate.\n")
320
+ return
321
+
322
+ if verify:
323
+ verified = result.get("verified", 0)
324
+ missing = result.get("missing", [])
325
+ if not missing:
326
+ console.print(f"\n [green]Verified:[/] All {verified} memories present in unified backend.\n")
327
+ else:
328
+ console.print(f"\n [yellow]Verification:[/] {verified} present, {len(missing)} missing.")
329
+ for mid in missing[:10]:
330
+ console.print(f" [red]Missing:[/] {mid}")
331
+ if len(missing) > 10:
332
+ console.print(f" ... and {len(missing) - 10} more")
333
+ console.print()
334
+ return
335
+
336
+ console.print(f"\n [bold]Migration results:[/]")
337
+ console.print(f" Total JSON memories: {result['total_json']}")
338
+ console.print(f" [green]Migrated:[/] {result['migrated']}")
339
+ console.print(f" [dim]Skipped (existing):[/] {result['skipped_existing']}")
340
+ if result.get("errors"):
341
+ console.print(f" [red]Errors:[/] {len(result['errors'])}")
342
+ for err in result["errors"][:5]:
343
+ console.print(f" {err}")
344
+ console.print()
345
+
346
+ @memory.command("verify")
347
+ @click.option("--home", default=AGENT_HOME, type=click.Path())
348
+ def memory_verify(home):
349
+ """Check consistency across memory backends."""
350
+ from ..memory_adapter import verify_sync
351
+
352
+ home_path = Path(home).expanduser()
353
+ if not home_path.exists():
354
+ console.print("[bold red]No agent found.[/] Run skcapstone init first.")
355
+ sys.exit(1)
356
+
357
+ result = verify_sync()
358
+
359
+ console.print("\n [bold]Backend sync status:[/]")
360
+ for name, info in result.get("backends", {}).items():
361
+ ok = info.get("ok", False)
362
+ count = info.get("count", "?")
363
+ icon = "[green]ok[/]" if ok else "[red]error[/]"
364
+ console.print(f" {name}: {icon} ({count} memories)")
365
+
366
+ if result.get("synced"):
367
+ console.print("\n [green]All backends in sync.[/]\n")
368
+ else:
369
+ console.print(f"\n [yellow]Out of sync:[/] {result.get('reason', 'unknown')}\n")
370
+
371
+ @memory.command("reindex")
372
+ @click.option("--home", default=AGENT_HOME, type=click.Path())
373
+ def memory_reindex(home):
374
+ """Rebuild vector and graph indexes from SQLite primary."""
375
+ from ..memory_adapter import reindex_all
376
+
377
+ home_path = Path(home).expanduser()
378
+ if not home_path.exists():
379
+ console.print("[bold red]No agent found.[/] Run skcapstone init first.")
380
+ sys.exit(1)
381
+
382
+ console.print("\n Reindexing secondary backends...\n")
383
+ result = reindex_all()
384
+
385
+ if result.get("ok"):
386
+ console.print(f" [green]Done:[/] {result['total']} memories reindexed.")
387
+ console.print(f" Vector: {result['vector_indexed']}")
388
+ console.print(f" Graph: {result['graph_indexed']}")
389
+ else:
390
+ console.print(f" [red]Errors during reindex.[/]")
391
+ for err in result.get("errors", [])[:5]:
392
+ console.print(f" {err}")
393
+ console.print()
394
+
395
+ @memory.command("dedup")
396
+ @click.option("--home", default=AGENT_HOME, type=click.Path())
397
+ def memory_dedup(home):
398
+ """Deduplicate memories across all tiers.
399
+
400
+ Scans for exact and near-duplicate titles. Keeps the newest
401
+ copy and archives the rest to memory/archive/deduped/.
402
+ """
403
+ from ..memory_promoter import PromotionEngine
404
+
405
+ home_path = Path(home).expanduser()
406
+ if not home_path.exists():
407
+ console.print("[bold red]No agent found.[/] Run skcapstone init first.")
408
+ sys.exit(1)
409
+
410
+ console.print("\n Scanning for duplicate memories...\n")
411
+ engine = PromotionEngine(home_path)
412
+ removed = engine.dedup_memories()
413
+
414
+ if removed:
415
+ console.print(f" [yellow]Deduped:[/] {removed} duplicate{'s' if removed != 1 else ''} archived.")
416
+ else:
417
+ console.print(" [green]No duplicates found.[/]")
418
+ console.print()