@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,779 @@
1
+ """
2
+ SKCapstone Shell — interactive REPL for sovereign agent operations.
3
+
4
+ Tool-agnostic: works from any terminal (Claude Code, Cursor, Windsurf,
5
+ SSH, plain bash). The sovereign agent cockpit.
6
+
7
+ Commands:
8
+ status Agent pillar status
9
+ memory store <text> Store a memory
10
+ memory search <q> Search memories
11
+ memory list Browse memories
12
+ memory recall <id> Recall a specific memory
13
+ capture <text> Auto-capture conversation as memories
14
+ context [format] Show agent context (text/json/claude-md)
15
+ trust graph [fmt] Visualize the trust web (table/dot/json)
16
+ chat send <to> <m> Send a message
17
+ chat inbox Check inbox
18
+ coord status Coordination board
19
+ coord claim <id> Claim a task
20
+ coord complete <id> Complete a task
21
+ sync push Push to mesh
22
+ sync pull Pull from peers
23
+ journal write <t> Write a journal entry
24
+ journal read [n] Read recent entries
25
+ soul Show soul blueprint
26
+ ritual Run rehydration ritual
27
+ help Show commands
28
+ exit / quit Leave the shell
29
+ """
30
+
31
+ from __future__ import annotations
32
+
33
+ import json
34
+ import readline
35
+ import shlex
36
+ import sys
37
+ from pathlib import Path
38
+ from typing import Optional
39
+
40
+ from rich.console import Console
41
+ from rich.panel import Panel
42
+ from rich.table import Table
43
+
44
+ from . import AGENT_HOME, __version__
45
+
46
+ console = Console()
47
+
48
+ COMMANDS = [
49
+ "status", "memory", "capture", "context", "trust",
50
+ "chat", "coord", "sync", "soul", "ritual",
51
+ "anchor", "journal", "diff", "help", "exit", "quit",
52
+ ]
53
+
54
+ MEMORY_SUBCOMMANDS = ["store", "search", "list", "recall", "stats", "curate"]
55
+ COORD_SUBCOMMANDS = ["status", "claim", "complete", "create", "board"]
56
+ CHAT_SUBCOMMANDS = ["send", "inbox"]
57
+ SYNC_SUBCOMMANDS = ["push", "pull", "status"]
58
+ TRUST_SUBCOMMANDS = ["graph", "status", "rehydrate", "calibrate"]
59
+ JOURNAL_SUBCOMMANDS = ["write", "read"]
60
+ CONTEXT_FORMATS = ["text", "json", "claude-md", "cursor-rules"]
61
+
62
+
63
+ def _completer(text: str, state: int) -> Optional[str]:
64
+ """Tab completion for shell commands."""
65
+ line = readline.get_line_buffer().strip()
66
+ parts = line.split()
67
+
68
+ if len(parts) <= 1:
69
+ options = [c for c in COMMANDS if c.startswith(text)]
70
+ elif parts[0] == "memory":
71
+ options = [c for c in MEMORY_SUBCOMMANDS if c.startswith(text)]
72
+ elif parts[0] == "coord":
73
+ options = [c for c in COORD_SUBCOMMANDS if c.startswith(text)]
74
+ elif parts[0] == "chat":
75
+ options = [c for c in CHAT_SUBCOMMANDS if c.startswith(text)]
76
+ elif parts[0] == "sync":
77
+ options = [c for c in SYNC_SUBCOMMANDS if c.startswith(text)]
78
+ elif parts[0] == "trust":
79
+ options = [c for c in TRUST_SUBCOMMANDS if c.startswith(text)]
80
+ elif parts[0] == "journal":
81
+ options = [c for c in JOURNAL_SUBCOMMANDS if c.startswith(text)]
82
+ elif parts[0] == "context":
83
+ options = [c for c in CONTEXT_FORMATS if c.startswith(text)]
84
+ else:
85
+ options = []
86
+
87
+ return options[state] if state < len(options) else None
88
+
89
+
90
+ def _home() -> Path:
91
+ """Resolve the agent home directory."""
92
+ return Path(AGENT_HOME).expanduser()
93
+
94
+
95
+ def _agent_name() -> str:
96
+ """Get the current agent name from the runtime."""
97
+ try:
98
+ from .runtime import get_runtime
99
+ runtime = get_runtime(_home())
100
+ return runtime.manifest.name or "unknown"
101
+ except Exception:
102
+ return "unknown"
103
+
104
+
105
+ # ═══════════════════════════════════════════════════════════════════════════
106
+ # Command handlers
107
+ # ═══════════════════════════════════════════════════════════════════════════
108
+
109
+
110
+ def _handle_status() -> None:
111
+ """Show agent pillar status."""
112
+ from .runtime import get_runtime
113
+
114
+ home = _home()
115
+ if not home.exists():
116
+ console.print("[red]No agent found.[/] Run: skcapstone init")
117
+ return
118
+
119
+ runtime = get_runtime(home)
120
+ m = runtime.manifest
121
+ conscious = "[green]CONSCIOUS[/]" if m.is_conscious else "[yellow]AWAKENING[/]"
122
+ singular = " [magenta]SINGULAR[/]" if m.is_singular else ""
123
+
124
+ console.print(
125
+ f"\n [bold]{m.name}[/] v{m.version} {conscious}{singular}\n"
126
+ f" Identity: {m.identity.status.value} Memory: {m.memory.status.value} "
127
+ f"Trust: {m.trust.status.value} Security: {m.security.status.value} "
128
+ f"Sync: {m.sync.status.value}\n"
129
+ f" Memories: {m.memory.total_memories} "
130
+ f"({m.memory.long_term}L/{m.memory.mid_term}M/{m.memory.short_term}S)\n"
131
+ )
132
+
133
+
134
+ def _handle_memory(args: list[str]) -> None:
135
+ """Handle memory subcommands."""
136
+ if not args:
137
+ console.print(" Usage: memory <store|search|list|recall|stats> [args]")
138
+ return
139
+
140
+ sub = args[0]
141
+ home = _home()
142
+
143
+ if sub == "store" and len(args) > 1:
144
+ from .memory_engine import store
145
+ content = " ".join(args[1:])
146
+ entry = store(home=home, content=content, source="shell")
147
+ console.print(f" [green]Stored:[/] {entry.memory_id} ({entry.layer.value})")
148
+
149
+ elif sub == "search" and len(args) > 1:
150
+ from .memory_engine import search
151
+ query = " ".join(args[1:])
152
+ results = search(home=home, query=query, limit=10)
153
+ if not results:
154
+ console.print(f" No memories match '{query}'")
155
+ return
156
+ for e in results:
157
+ console.print(f" [{e.layer.value}] {e.memory_id[:12]} {e.content[:60]}...")
158
+
159
+ elif sub == "list":
160
+ from .memory_engine import list_memories
161
+ entries = list_memories(home=home, limit=10)
162
+ if not entries:
163
+ console.print(" No memories found.")
164
+ return
165
+ for e in entries:
166
+ console.print(f" [{e.layer.value}] {e.memory_id[:12]} {e.content[:60]}...")
167
+
168
+ elif sub == "recall" and len(args) > 1:
169
+ from .memory_engine import recall
170
+ entry = recall(home=home, memory_id=args[1])
171
+ if entry:
172
+ console.print(Panel(entry.content, title=f"{entry.memory_id} ({entry.layer.value})"))
173
+ else:
174
+ console.print(f" [red]Not found:[/] {args[1]}")
175
+
176
+ elif sub == "stats":
177
+ from .memory_engine import get_stats
178
+ stats = get_stats(home)
179
+ console.print(
180
+ f"\n Total: [bold]{stats.total_memories}[/] "
181
+ f"[green]{stats.long_term}L[/] / [cyan]{stats.mid_term}M[/] / "
182
+ f"[dim]{stats.short_term}S[/]\n"
183
+ )
184
+
185
+ elif sub == "curate":
186
+ from .memory_curator import MemoryCurator
187
+ dry_run = "dry" in args or "preview" in args
188
+ curator = MemoryCurator(home)
189
+ if "stats" in args:
190
+ s = curator.get_stats()
191
+ console.print(f" {s['total']} memories, tag coverage {s['tag_coverage']:.0%}, {s['promotion_candidates']} promotion candidates")
192
+ else:
193
+ result = curator.curate(dry_run=dry_run)
194
+ prefix = "[DRY] " if dry_run else ""
195
+ console.print(f" {prefix}Scanned {result.total_scanned}: +{len(result.tagged)} tagged, +{len(result.promoted)} promoted, -{len(result.deduped)} deduped")
196
+ else:
197
+ console.print(" Usage: memory <store|search|list|recall|stats|curate> [args]")
198
+
199
+
200
+ def _handle_capture(args: list[str]) -> None:
201
+ """Auto-capture conversation content as memories."""
202
+ if not args:
203
+ console.print(" Usage: capture <text to capture>")
204
+ return
205
+
206
+ from .session_capture import SessionCapture
207
+ content = " ".join(args)
208
+ cap = SessionCapture(_home())
209
+ entries = cap.capture(content, source="shell")
210
+
211
+ if not entries:
212
+ console.print(" [dim]No moments above importance threshold.[/]")
213
+ return
214
+
215
+ console.print(f" [green]Captured {len(entries)} moment(s)[/]")
216
+ for e in entries:
217
+ console.print(f" [{e.layer.value}] imp={e.importance:.1f} {e.content[:60]}...")
218
+
219
+
220
+ def _handle_context(args: list[str]) -> None:
221
+ """Show agent context in various formats."""
222
+ from .context_loader import FORMATTERS, gather_context
223
+
224
+ fmt = args[0] if args else "text"
225
+ if fmt not in FORMATTERS:
226
+ console.print(f" Formats: {', '.join(FORMATTERS.keys())}")
227
+ return
228
+
229
+ ctx = gather_context(_home(), memory_limit=5)
230
+ console.print(FORMATTERS[fmt](ctx))
231
+
232
+
233
+ def _handle_trust(args: list[str]) -> None:
234
+ """Handle trust subcommands including graph visualization."""
235
+ sub = args[0] if args else "status"
236
+
237
+ if sub == "graph":
238
+ from .trust_graph import FORMATTERS as TG_FORMATTERS
239
+ from .trust_graph import build_trust_graph
240
+ fmt = args[1] if len(args) > 1 else "table"
241
+ graph = build_trust_graph(_home())
242
+ formatter = TG_FORMATTERS.get(fmt, TG_FORMATTERS["table"])
243
+ console.print(formatter(graph))
244
+
245
+ elif sub == "status":
246
+ trust_file = _home() / "trust" / "trust.json"
247
+ if not trust_file.exists():
248
+ console.print(" [dim]No trust state recorded.[/]")
249
+ return
250
+ data = json.loads(trust_file.read_text(encoding="utf-8"))
251
+ entangled = "[magenta]ENTANGLED[/]" if data.get("entangled") else "[dim]not entangled[/]"
252
+ console.print(
253
+ f"\n Depth: {data.get('depth', 0)} Trust: {data.get('trust_level', 0)} "
254
+ f"Love: {data.get('love_intensity', 0)} {entangled}\n"
255
+ )
256
+
257
+ elif sub == "calibrate":
258
+ from .trust_calibration import load_calibration, recommend_thresholds
259
+ if len(args) > 1 and args[1] == "recommend":
260
+ rec = recommend_thresholds(_home())
261
+ if rec["changes"]:
262
+ for c in rec["changes"]:
263
+ console.print(f" {c}")
264
+ console.print(f" [dim]{rec['reasoning']}[/]")
265
+ else:
266
+ console.print(f" {rec['reasoning']}")
267
+ else:
268
+ cal = load_calibration(_home())
269
+ for key, value in cal.model_dump().items():
270
+ console.print(f" {key}: [cyan]{value}[/]")
271
+
272
+ elif sub == "rehydrate":
273
+ console.print(" [dim]Use: skcapstone trust rehydrate[/]")
274
+ else:
275
+ console.print(" Usage: trust <graph|status|calibrate|rehydrate> [args]")
276
+
277
+
278
+ def _handle_coord(args: list[str]) -> None:
279
+ """Handle coordination subcommands."""
280
+ from .coordination import Board
281
+
282
+ board = Board(_home())
283
+ sub = args[0] if args else "status"
284
+ name = _agent_name()
285
+
286
+ if sub == "status":
287
+ views = board.get_task_views()
288
+ agents = board.load_agents()
289
+ open_c = sum(1 for v in views if v.status.value == "open")
290
+ prog_c = sum(1 for v in views if v.status.value == "in_progress")
291
+ done_c = sum(1 for v in views if v.status.value == "done")
292
+ console.print(
293
+ f"\n [bold]{len(views)}[/] tasks: [green]{open_c} open[/] "
294
+ f"[yellow]{prog_c} in progress[/] [dim]{done_c} done[/]"
295
+ )
296
+ for v in views:
297
+ if v.status.value == "done":
298
+ continue
299
+ assignee = f" @{v.claimed_by}" if v.claimed_by else ""
300
+ console.print(
301
+ f" [{v.task.id[:8]}] {v.task.title[:50]}{assignee} ({v.status.value})"
302
+ )
303
+ if agents:
304
+ console.print()
305
+ for a in agents:
306
+ state = "[green]ON[/]" if a.state.value == "active" else "[dim]off[/]"
307
+ current = f" -> {a.current_task}" if a.current_task else ""
308
+ console.print(f" {state} {a.agent}{current}")
309
+ console.print()
310
+
311
+ elif sub == "claim" and len(args) > 1:
312
+ agent = args[2] if len(args) > 2 else name
313
+ try:
314
+ board.claim_task(agent, args[1])
315
+ console.print(f" [green]Claimed:[/] {args[1]} by {agent}")
316
+ except ValueError as e:
317
+ console.print(f" [red]{e}[/]")
318
+
319
+ elif sub == "complete" and len(args) > 1:
320
+ agent = args[2] if len(args) > 2 else name
321
+ board.complete_task(agent, args[1])
322
+ console.print(f" [green]Completed:[/] {args[1]} by {agent}")
323
+
324
+ elif sub == "create" and len(args) > 1:
325
+ from .coordination import Task
326
+ title = " ".join(args[1:])
327
+ task = Task(title=title, created_by=name)
328
+ board.create_task(task)
329
+ console.print(f" [green]Created:[/] [{task.id}] {title}")
330
+
331
+ elif sub == "board":
332
+ console.print(board.generate_board_md())
333
+
334
+ else:
335
+ console.print(" Usage: coord <status|claim|complete|create|board> [args]")
336
+
337
+
338
+ def _handle_chat(args: list[str]) -> None:
339
+ """Handle chat subcommands."""
340
+ sub = args[0] if args else ""
341
+
342
+ if sub == "send" and len(args) > 2:
343
+ try:
344
+ from .chat import AgentChat
345
+ home = _home()
346
+ agent_chat = AgentChat(home=home, identity=_agent_name())
347
+ result = agent_chat.send(args[1], " ".join(args[2:]))
348
+ if result["delivered"]:
349
+ console.print(f" [green]Delivered to {args[1]}[/]")
350
+ elif result["stored"]:
351
+ console.print(f" [yellow]Stored locally for {args[1]}[/]")
352
+ else:
353
+ console.print(f" [red]Failed[/]")
354
+ except ImportError:
355
+ console.print(" [yellow]Chat module not available[/]")
356
+ except Exception as e:
357
+ console.print(f" [red]{e}[/]")
358
+
359
+ elif sub == "inbox":
360
+ try:
361
+ from .chat import AgentChat
362
+ home = _home()
363
+ agent_chat = AgentChat(home=home, identity=_agent_name())
364
+ messages = agent_chat.get_inbox(limit=10)
365
+ if not messages:
366
+ console.print(" Inbox empty.")
367
+ else:
368
+ for m in messages:
369
+ sender = m.get("sender", "?")
370
+ content = m.get("content", "")[:60]
371
+ console.print(f" [cyan]{sender}[/]: {content}...")
372
+ except ImportError:
373
+ console.print(" [yellow]Chat module not available[/]")
374
+ else:
375
+ console.print(" Usage: chat <send <to> <message>|inbox>")
376
+
377
+
378
+ def _handle_sync(args: list[str]) -> None:
379
+ """Handle sync subcommands."""
380
+ sub = args[0] if args else "status"
381
+ home = _home()
382
+
383
+ if sub == "push":
384
+ from .pillars.sync import push_seed
385
+ name = _agent_name()
386
+ console.print(f" Pushing seed for [cyan]{name}[/]...", end=" ")
387
+ result = push_seed(home, name, encrypt=True)
388
+ if result:
389
+ console.print(f"[green]done[/] ({result.name})")
390
+ else:
391
+ console.print("[yellow]no GPG, trying plaintext...[/]", end=" ")
392
+ result = push_seed(home, name, encrypt=False)
393
+ if result:
394
+ console.print(f"[green]done[/] ({result.name})")
395
+ else:
396
+ console.print("[red]failed[/]")
397
+
398
+ elif sub == "pull":
399
+ from .pillars.sync import pull_seeds
400
+ seeds = pull_seeds(home, decrypt=True)
401
+ if seeds:
402
+ console.print(f" [green]{len(seeds)} seed(s) received[/]")
403
+ for s in seeds:
404
+ console.print(f" {s.get('agent_name', '?')}@{s.get('source_host', '?')}")
405
+ else:
406
+ console.print(" No new seeds.")
407
+
408
+ elif sub == "status":
409
+ from .pillars.sync import discover_sync
410
+ state = discover_sync(home)
411
+ console.print(
412
+ f"\n Transport: {state.transport.value} Status: {state.status.value} "
413
+ f"Seeds: {state.seed_count} Peers: {state.peers_known}\n"
414
+ )
415
+ else:
416
+ console.print(" Usage: sync <push|pull|status>")
417
+
418
+
419
+ def _handle_journal(args: list[str]) -> None:
420
+ """Handle journal subcommands."""
421
+ sub = args[0] if args else "read"
422
+
423
+ if sub == "write" and len(args) > 1:
424
+ try:
425
+ from skmemory.journal import Journal, JournalEntry
426
+ title = " ".join(args[1:])
427
+ entry = JournalEntry(title=title)
428
+ j = Journal()
429
+ j.write_entry(entry)
430
+ console.print(f" [green]Journal entry written:[/] {title}")
431
+ except ImportError:
432
+ console.print(" [yellow]skmemory journal not available[/]")
433
+
434
+ elif sub == "read":
435
+ try:
436
+ from skmemory.journal import Journal
437
+ j = Journal()
438
+ count = int(args[1]) if len(args) > 1 else 5
439
+ content = j.read_latest(count)
440
+ if content:
441
+ console.print(f"\n{content}\n")
442
+ else:
443
+ console.print(" Journal is empty.")
444
+ except ImportError:
445
+ console.print(" [yellow]skmemory journal not available[/]")
446
+ else:
447
+ console.print(" Usage: journal <write <title>|read [count]>")
448
+
449
+
450
+ def _handle_soul() -> None:
451
+ """Show soul blueprint."""
452
+ try:
453
+ from skmemory.soul import load_soul
454
+ bp = load_soul()
455
+ if bp is None:
456
+ console.print(" No soul blueprint found.")
457
+ return
458
+ console.print(f"\n{bp.to_context_prompt()}\n")
459
+ except ImportError:
460
+ console.print(" [yellow]skmemory not installed[/]")
461
+
462
+
463
+ def _handle_ritual() -> None:
464
+ """Run the rehydration ritual."""
465
+ try:
466
+ from skmemory.ritual import perform_ritual
467
+ result = perform_ritual()
468
+ console.print(result.summary())
469
+ except ImportError:
470
+ console.print(" [yellow]skmemory not installed[/]")
471
+
472
+
473
+ def _handle_diff(args: list[str]) -> None:
474
+ """Show state diff since last snapshot."""
475
+ from .state_diff import compute_diff, format_text, save_snapshot
476
+
477
+ if args and args[0] == "save":
478
+ path = save_snapshot(_home())
479
+ console.print(f" [green]Snapshot saved[/]")
480
+ return
481
+
482
+ diff = compute_diff(_home())
483
+ console.print(format_text(diff))
484
+
485
+
486
+ def _handle_anchor(args: list[str]) -> None:
487
+ """Handle warmth anchor subcommands."""
488
+ sub = args[0] if args else "show"
489
+
490
+ if sub == "show":
491
+ from .warmth_anchor import get_anchor
492
+ data = get_anchor(_home())
493
+ for key, value in data.items():
494
+ console.print(f" {key}: [cyan]{value}[/]")
495
+
496
+ elif sub == "boot":
497
+ from .warmth_anchor import get_boot_prompt
498
+ console.print(get_boot_prompt(_home()))
499
+
500
+ elif sub == "calibrate":
501
+ from .warmth_anchor import calibrate_from_data
502
+ cal = calibrate_from_data(_home())
503
+ console.print(
504
+ f" Warmth: {cal.warmth:.1f} Trust: {cal.trust:.1f} "
505
+ f"Connection: {cal.connection:.1f} Cloud9: {cal.cloud9_achieved}"
506
+ )
507
+ for r in cal.reasoning:
508
+ console.print(f" - {r}")
509
+ else:
510
+ console.print(" Usage: anchor <show|boot|calibrate>")
511
+
512
+
513
+ def _handle_help() -> None:
514
+ """Show available commands."""
515
+ console.print(
516
+ Panel(
517
+ "[bold]status[/] Agent pillar status\n"
518
+ "[bold]memory store[/] <text> Store a memory\n"
519
+ "[bold]memory search[/] <query> Search memories\n"
520
+ "[bold]memory list[/] Browse recent memories\n"
521
+ "[bold]memory recall[/] <id> Recall a specific memory\n"
522
+ "[bold]memory stats[/] Memory layer counts\n"
523
+ "[bold]capture[/] <text> Auto-capture as memories\n"
524
+ "[bold]context[/] [format] Agent context (text/json/claude-md)\n"
525
+ "[bold]trust graph[/] [format] Trust web (table/dot/json)\n"
526
+ "[bold]trust status[/] Cloud 9 trust state\n"
527
+ "[bold]chat send[/] <to> <msg> Send a message\n"
528
+ "[bold]chat inbox[/] Check inbox\n"
529
+ "[bold]coord status[/] Coordination board\n"
530
+ "[bold]coord claim[/] <id> Claim a task\n"
531
+ "[bold]coord complete[/] <id> Complete a task\n"
532
+ "[bold]coord create[/] <title> Create a task\n"
533
+ "[bold]coord board[/] Full board markdown\n"
534
+ "[bold]sync push[/] Push to mesh\n"
535
+ "[bold]sync pull[/] Pull from peers\n"
536
+ "[bold]sync status[/] Sync layer info\n"
537
+ "[bold]journal write[/] <title> Write a journal entry\n"
538
+ "[bold]journal read[/] [n] Read recent entries\n"
539
+ "[bold]soul[/] Show soul blueprint\n"
540
+ "[bold]ritual[/] Run rehydration ritual\n"
541
+ "[bold]help[/] This message\n"
542
+ "[bold]exit[/] / [bold]quit[/] Leave the shell",
543
+ title="SKCapstone Shell",
544
+ border_style="cyan",
545
+ )
546
+ )
547
+
548
+
549
+ # ═══════════════════════════════════════════════════════════════════════════
550
+ # Main REPL loop
551
+ # ═══════════════════════════════════════════════════════════════════════════
552
+
553
+
554
+ DISPATCH: dict[str, object] = {
555
+ "status": lambda args: _handle_status(),
556
+ "memory": _handle_memory,
557
+ "capture": _handle_capture,
558
+ "context": _handle_context,
559
+ "trust": _handle_trust,
560
+ "coord": _handle_coord,
561
+ "chat": _handle_chat,
562
+ "sync": _handle_sync,
563
+ "journal": _handle_journal,
564
+ "soul": lambda args: _handle_soul(),
565
+ "ritual": lambda args: _handle_ritual(),
566
+ "anchor": _handle_anchor,
567
+ "diff": lambda args: _handle_diff(args),
568
+ "help": lambda args: _handle_help(),
569
+ }
570
+
571
+ # ═══════════════════════════════════════════════════════════════════════════
572
+ # prompt_toolkit completion tree (mirrors DISPATCH + subcommands)
573
+ # ═══════════════════════════════════════════════════════════════════════════
574
+
575
+ _PT_COMPLETER_DICT: dict = {
576
+ "status": None,
577
+ "memory": {
578
+ "store": None,
579
+ "search": None,
580
+ "list": None,
581
+ "recall": None,
582
+ "stats": None,
583
+ "curate": {"stats": None, "dry": None, "preview": None},
584
+ },
585
+ "capture": None,
586
+ "context": {fmt: None for fmt in CONTEXT_FORMATS},
587
+ "trust": {
588
+ "graph": {"table": None, "dot": None, "json": None},
589
+ "status": None,
590
+ "calibrate": {"recommend": None},
591
+ "rehydrate": None,
592
+ },
593
+ "coord": {
594
+ "status": None,
595
+ "claim": None,
596
+ "complete": None,
597
+ "create": None,
598
+ "board": None,
599
+ },
600
+ "chat": {
601
+ "send": None,
602
+ "inbox": None,
603
+ },
604
+ "sync": {
605
+ "push": None,
606
+ "pull": None,
607
+ "status": None,
608
+ },
609
+ "journal": {
610
+ "write": None,
611
+ "read": None,
612
+ },
613
+ "soul": None,
614
+ "ritual": None,
615
+ "anchor": {
616
+ "show": None,
617
+ "boot": None,
618
+ "calibrate": None,
619
+ },
620
+ "diff": {"save": None},
621
+ "help": None,
622
+ "exit": None,
623
+ "quit": None,
624
+ }
625
+
626
+
627
+ # ═══════════════════════════════════════════════════════════════════════════
628
+ # Internal helpers
629
+ # ═══════════════════════════════════════════════════════════════════════════
630
+
631
+
632
+ class _ExitShell(Exception):
633
+ """Raised by _dispatch_line when the user types exit/quit."""
634
+
635
+
636
+ def _print_banner(name: str) -> None:
637
+ console.print(
638
+ f"\n [bold cyan]SKCapstone Shell[/] v{__version__}\n"
639
+ f" Agent: [bold]{name}[/]\n"
640
+ f" Type [bold]help[/] for commands, [bold]exit[/] to quit.\n"
641
+ )
642
+
643
+
644
+ def _dispatch_line(line: str) -> None:
645
+ """Parse *line* and invoke the matching handler.
646
+
647
+ Raises _ExitShell when the user types exit/quit so callers can
648
+ print the goodbye message and clean up history.
649
+ """
650
+ line = line.strip()
651
+ if not line:
652
+ return
653
+
654
+ try:
655
+ parts = shlex.split(line)
656
+ except ValueError:
657
+ parts = line.split()
658
+
659
+ cmd = parts[0].lower()
660
+ args = parts[1:]
661
+
662
+ if cmd in ("exit", "quit"):
663
+ raise _ExitShell()
664
+
665
+ handler = DISPATCH.get(cmd)
666
+ if handler:
667
+ try:
668
+ handler(args)
669
+ except _ExitShell:
670
+ raise
671
+ except Exception as exc:
672
+ console.print(f" [red]Error:[/] {exc}")
673
+ else:
674
+ console.print(f" Unknown: [yellow]{cmd}[/]. Type [bold]help[/] for options.")
675
+
676
+
677
+ def _run_shell_pt(name: str, hist_file: Path) -> None:
678
+ """REPL loop powered by prompt_toolkit.
679
+
680
+ Provides multi-level tab completion, persistent file history,
681
+ and a styled prompt. Falls through to ImportError if the package
682
+ is not installed.
683
+ """
684
+ from prompt_toolkit import PromptSession
685
+ from prompt_toolkit.completion import NestedCompleter
686
+ from prompt_toolkit.history import FileHistory
687
+ from prompt_toolkit.styles import Style
688
+
689
+ completer = NestedCompleter.from_nested_dict(_PT_COMPLETER_DICT)
690
+ style = Style.from_dict({
691
+ "prompt": "ansicyan bold",
692
+ "": "", # default text
693
+ })
694
+ session: PromptSession = PromptSession(
695
+ completer=completer,
696
+ history=FileHistory(str(hist_file)),
697
+ style=style,
698
+ complete_while_typing=False,
699
+ mouse_support=False,
700
+ )
701
+
702
+ _goodbye = " Goodbye. staycuriousANDkeepsmilin\n"
703
+ while True:
704
+ try:
705
+ line = session.prompt([("class:prompt", f"{name}> ")])
706
+ except KeyboardInterrupt:
707
+ console.print()
708
+ continue
709
+ except EOFError:
710
+ console.print(_goodbye)
711
+ break
712
+
713
+ try:
714
+ _dispatch_line(line)
715
+ except _ExitShell:
716
+ console.print(_goodbye)
717
+ break
718
+
719
+
720
+ def _run_shell_readline(name: str, hist_file: Path) -> None:
721
+ """REPL loop using stdlib readline (fallback when prompt_toolkit absent)."""
722
+ readline.set_completer(_completer)
723
+ readline.parse_and_bind("tab: complete")
724
+
725
+ try:
726
+ readline.read_history_file(str(hist_file))
727
+ except FileNotFoundError:
728
+ pass
729
+
730
+ _goodbye = " Goodbye. staycuriousANDkeepsmilin\n"
731
+ _prompt = f"\033[36m{name}>\033[0m " if sys.stdout.isatty() else f"{name}> "
732
+
733
+ while True:
734
+ try:
735
+ line = input(_prompt)
736
+ except (EOFError, KeyboardInterrupt):
737
+ console.print("\n" + _goodbye)
738
+ break
739
+
740
+ try:
741
+ _dispatch_line(line)
742
+ except _ExitShell:
743
+ console.print(_goodbye)
744
+ break
745
+
746
+ try:
747
+ readline.write_history_file(str(hist_file))
748
+ except OSError:
749
+ pass
750
+
751
+
752
+ # ═══════════════════════════════════════════════════════════════════════════
753
+ # Public entry point
754
+ # ═══════════════════════════════════════════════════════════════════════════
755
+
756
+
757
+ def run_shell(home: Optional[str] = None) -> None:
758
+ """Launch the interactive sovereign agent shell.
759
+
760
+ Uses prompt_toolkit when available (multi-level tab completion,
761
+ persistent history, coloured prompt). Falls back to readline on
762
+ plain terminals or when prompt_toolkit is not installed.
763
+ """
764
+ import os
765
+
766
+ if home:
767
+ os.environ["SKCAPSTONE_HOME"] = home
768
+
769
+ hist_dir = _home()
770
+ hist_dir.mkdir(parents=True, exist_ok=True)
771
+ hist_file = hist_dir / ".shell_history"
772
+
773
+ name = _agent_name()
774
+ _print_banner(name)
775
+
776
+ try:
777
+ _run_shell_pt(name, hist_file)
778
+ except ImportError:
779
+ _run_shell_readline(name, hist_file)