@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,156 @@
1
+ """Config commands: validate."""
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
12
+
13
+
14
+ def register_config_commands(main: click.Group) -> None:
15
+ """Register the ``config`` command group on the main CLI."""
16
+
17
+ @main.group()
18
+ def config():
19
+ """Config management — validate and inspect agent configuration."""
20
+
21
+ @config.command("validate")
22
+ @click.option(
23
+ "--home", default=AGENT_HOME, type=click.Path(),
24
+ help="Agent home directory.",
25
+ )
26
+ @click.option("--json-out", is_flag=True, help="Output as machine-readable JSON.")
27
+ @click.option(
28
+ "--strict", is_flag=True,
29
+ help="Treat warnings as errors (non-zero exit when warnings present).",
30
+ )
31
+ def validate(home: str, json_out: bool, strict: bool) -> None:
32
+ """Validate all agent config files.
33
+
34
+ Checks consciousness.yaml, model_profiles.yaml, identity.json,
35
+ and soul blueprints. Reports schema errors with line numbers.
36
+
37
+ Exits 0 when all configs are valid (warnings do not cause failure
38
+ unless --strict is set). Exits 1 when any errors are found or
39
+ --strict is set and warnings are present.
40
+ """
41
+ from ..config_validator import validate_all
42
+
43
+ home_path = Path(home).expanduser()
44
+ report = validate_all(home_path)
45
+
46
+ if json_out:
47
+ _json_output(report, strict)
48
+ return
49
+
50
+ _rich_output(report, strict)
51
+
52
+ has_errors = report.total_errors > 0 or (strict and report.total_warnings > 0)
53
+ if has_errors:
54
+ sys.exit(1)
55
+
56
+
57
+ # ---------------------------------------------------------------------------
58
+ # Output helpers
59
+ # ---------------------------------------------------------------------------
60
+
61
+
62
+ def _json_output(report: object, strict: bool) -> None: # type: ignore[type-arg]
63
+ """Emit machine-readable JSON to stdout."""
64
+ from ..config_validator import ConfigValidationReport
65
+
66
+ r: ConfigValidationReport = report # type: ignore[assignment]
67
+ data = {
68
+ "valid": r.is_valid and not (strict and r.total_warnings > 0),
69
+ "total_errors": r.total_errors,
70
+ "total_warnings": r.total_warnings,
71
+ "files": [
72
+ {
73
+ "name": fr.config_name,
74
+ "path": str(fr.file_path),
75
+ "found": fr.found,
76
+ "valid": fr.is_valid,
77
+ "issues": [
78
+ {
79
+ "severity": i.severity,
80
+ "message": i.message,
81
+ "field": i.field,
82
+ "line": i.line,
83
+ }
84
+ for i in fr.issues
85
+ ],
86
+ }
87
+ for fr in r.results
88
+ ],
89
+ }
90
+ click.echo(json.dumps(data, indent=2))
91
+
92
+
93
+ def _rich_output(report: object, strict: bool) -> None: # type: ignore[type-arg]
94
+ """Emit formatted Rich output to the terminal."""
95
+ from ..config_validator import ConfigValidationReport
96
+ from rich.panel import Panel
97
+
98
+ r: ConfigValidationReport = report # type: ignore[assignment]
99
+
100
+ console.print()
101
+ console.print(Panel(
102
+ "[bold]Config Validation[/]",
103
+ border_style="cyan",
104
+ padding=(0, 2),
105
+ ))
106
+ console.print()
107
+
108
+ for fr in r.results:
109
+ if not fr.found:
110
+ icon, status = "[yellow]~[/]", "[yellow]NOT FOUND[/]"
111
+ elif fr.errors:
112
+ n = len(fr.errors)
113
+ icon = "[red]✗[/]"
114
+ status = f"[red]FAIL {n} error{'s' if n != 1 else ''}[/]"
115
+ elif fr.warnings:
116
+ n = len(fr.warnings)
117
+ icon = "[yellow]~[/]"
118
+ status = f"[yellow]OK {n} warning{'s' if n != 1 else ''}[/]"
119
+ else:
120
+ icon, status = "[green]✓[/]", "[green]OK[/]"
121
+
122
+ console.print(f" {icon} [bold]{fr.config_name}[/] {status}")
123
+ console.print(f" [dim]{fr.file_path}[/]")
124
+
125
+ for issue in fr.issues:
126
+ color = "red" if issue.severity == "error" else "yellow"
127
+ loc = f" line {issue.line}" if issue.line else ""
128
+ fld = f" [{issue.field}]" if issue.field else ""
129
+ console.print(
130
+ f" [{color}]{issue.severity.upper()}[/]"
131
+ f"[dim]{fld}{loc}[/] {issue.message}"
132
+ )
133
+
134
+ console.print()
135
+
136
+ # Summary line
137
+ errors = r.total_errors
138
+ warnings = r.total_warnings
139
+ if errors == 0 and warnings == 0:
140
+ console.print(" [bold green]✓ All configs valid.[/]")
141
+ elif errors == 0:
142
+ detail = f"{warnings} warning{'s' if warnings != 1 else ''}"
143
+ console.print(
144
+ f" [bold yellow]~ {detail}.[/] Configs are functional."
145
+ )
146
+ else:
147
+ err_detail = f"{errors} error{'s' if errors != 1 else ''}"
148
+ warn_detail = (
149
+ f", {warnings} warning{'s' if warnings != 1 else ''}"
150
+ if warnings else ""
151
+ )
152
+ console.print(
153
+ f" [bold red]✗ {err_detail}{warn_detail}[/] — fix before running the agent."
154
+ )
155
+
156
+ console.print()
@@ -0,0 +1,421 @@
1
+ """Consciousness commands: status, config, test, backends, profiles."""
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
12
+
13
+
14
+ def register_consciousness_commands(main: click.Group) -> None:
15
+ """Register the consciousness command group."""
16
+
17
+ @main.group()
18
+ def consciousness():
19
+ """Consciousness loop — autonomous message processing.
20
+
21
+ Manages the LLM-powered consciousness loop that lets agents
22
+ respond to messages autonomously, route to the right model,
23
+ and self-heal when things break.
24
+ """
25
+
26
+ @consciousness.command("status")
27
+ @click.option("--port", default=7777, help="Daemon API port.")
28
+ @click.option("--json-out", is_flag=True, help="Output as JSON.")
29
+ def consciousness_status(port: int, json_out: bool):
30
+ """Show consciousness loop status from the running daemon."""
31
+ import urllib.request
32
+ import urllib.error
33
+
34
+ try:
35
+ url = f"http://127.0.0.1:{port}/consciousness"
36
+ with urllib.request.urlopen(url, timeout=3) as resp:
37
+ data = json.loads(resp.read())
38
+ except (urllib.error.URLError, OSError):
39
+ if json_out:
40
+ click.echo(json.dumps({"error": "Daemon not reachable"}))
41
+ else:
42
+ console.print("[yellow]Daemon is not running or unreachable.[/]")
43
+ return
44
+
45
+ if json_out:
46
+ click.echo(json.dumps(data, indent=2))
47
+ return
48
+
49
+ from rich.panel import Panel
50
+ from rich.table import Table
51
+
52
+ enabled = data.get("enabled", False)
53
+ color = "green" if enabled else "yellow"
54
+
55
+ inbox_watcher = data.get("inbox_watcher", "unknown")
56
+
57
+ console.print()
58
+ console.print(
59
+ Panel(
60
+ f"Enabled: [bold {color}]{enabled}[/]\n"
61
+ f"Messages processed: [bold]{data.get('messages_processed', 0)}[/]\n"
62
+ f"Responses sent: [bold]{data.get('responses_sent', 0)}[/]\n"
63
+ f"Errors: [bold]{data.get('errors', 0)}[/]\n"
64
+ f"Inotify active: {data.get('inotify_active', False)}\n"
65
+ f"Inbox watcher mode: [dim]{inbox_watcher}[/]\n"
66
+ f"Last activity: {data.get('last_activity') or '[dim]never[/]'}",
67
+ title=f"[{color}]Consciousness Loop[/]",
68
+ border_style=color,
69
+ )
70
+ )
71
+
72
+ backends = data.get("backends", {})
73
+ if backends:
74
+ table = Table(title="LLM Backends")
75
+ table.add_column("Backend", style="bold")
76
+ table.add_column("Status")
77
+ for name, available in sorted(backends.items()):
78
+ status_str = "[green]available[/]" if available else "[red]unavailable[/]"
79
+ table.add_row(name, status_str)
80
+ console.print(table)
81
+
82
+ console.print()
83
+
84
+ @consciousness.command("config")
85
+ @click.option("--init", "do_init", is_flag=True, help="Write default config YAML.")
86
+ @click.option("--show", "do_show", is_flag=True, help="Show current config.")
87
+ @click.option("--home", default=AGENT_HOME, type=click.Path())
88
+ def consciousness_config(do_init: bool, do_show: bool, home: str):
89
+ """Manage consciousness configuration."""
90
+ home_path = Path(home).expanduser()
91
+
92
+ if do_init:
93
+ from ..consciousness_config import write_default_config
94
+ path = write_default_config(home_path)
95
+ console.print(f"\n [green]Config written to[/] {path}\n")
96
+ return
97
+
98
+ if do_show:
99
+ from ..consciousness_config import load_consciousness_config
100
+ config = load_consciousness_config(home_path)
101
+ console.print()
102
+ for key, value in config.model_dump().items():
103
+ console.print(f" [bold]{key}[/]: {value}")
104
+ console.print()
105
+ return
106
+
107
+ # Default: show help
108
+ click.echo(click.get_current_context().get_help())
109
+
110
+ @consciousness.command("test")
111
+ @click.argument("message")
112
+ @click.option("--home", default=AGENT_HOME, type=click.Path())
113
+ def consciousness_test(message: str, home: str):
114
+ """Test the LLM pipeline end-to-end with a message.
115
+
116
+ Builds the full agent system prompt, routes via the model
117
+ router, and returns the LLM response.
118
+ """
119
+ home_path = Path(home).expanduser()
120
+
121
+ from ..consciousness_config import load_consciousness_config
122
+ from ..consciousness_loop import (
123
+ ConsciousnessConfig,
124
+ LLMBridge,
125
+ SystemPromptBuilder,
126
+ _classify_message,
127
+ )
128
+
129
+ console.print("\n [cyan]Testing consciousness pipeline...[/]")
130
+
131
+ config = load_consciousness_config(home_path)
132
+ bridge = LLMBridge(config)
133
+ builder = SystemPromptBuilder(home_path, config.max_context_tokens)
134
+
135
+ # Classify
136
+ signal = _classify_message(message)
137
+ console.print(f" Signal: tags={signal.tags}, tokens~{signal.estimated_tokens}")
138
+
139
+ # Build system prompt
140
+ system_prompt = builder.build()
141
+ console.print(f" System prompt: {len(system_prompt)} chars")
142
+
143
+ # Generate
144
+ console.print(" [dim]Generating response...[/]")
145
+ try:
146
+ response = bridge.generate(system_prompt, message, signal)
147
+ console.print(f"\n [green]Response ({len(response)} chars):[/]\n")
148
+ console.print(f" {response}\n")
149
+ except Exception as exc:
150
+ console.print(f"\n [red]Error:[/] {exc}\n")
151
+ sys.exit(1)
152
+
153
+ @consciousness.command("backends")
154
+ @click.option("--json-out", is_flag=True, help="Output as JSON.")
155
+ def consciousness_backends(json_out: bool):
156
+ """Show which LLM backends are reachable."""
157
+ from ..consciousness_loop import ConsciousnessConfig, LLMBridge
158
+
159
+ config = ConsciousnessConfig()
160
+ bridge = LLMBridge(config)
161
+ health = bridge.health_check()
162
+
163
+ if json_out:
164
+ click.echo(json.dumps(health, indent=2))
165
+ return
166
+
167
+ from rich.table import Table
168
+
169
+ console.print()
170
+ table = Table(title="LLM Backend Availability")
171
+ table.add_column("Backend", style="bold")
172
+ table.add_column("Status")
173
+ table.add_column("Source")
174
+
175
+ backend_sources = {
176
+ "ollama": "localhost:11434 or OLLAMA_HOST",
177
+ "anthropic": "ANTHROPIC_API_KEY",
178
+ "openai": "OPENAI_API_KEY",
179
+ "grok": "XAI_API_KEY",
180
+ "kimi": "MOONSHOT_API_KEY",
181
+ "nvidia": "NVIDIA_API_KEY",
182
+ "passthrough": "always available (echo mode)",
183
+ }
184
+
185
+ for name, available in sorted(health.items()):
186
+ status_str = "[green]AVAILABLE[/]" if available else "[red]UNAVAILABLE[/]"
187
+ source = backend_sources.get(name, "")
188
+ table.add_row(name, status_str, source)
189
+
190
+ console.print(table)
191
+
192
+ try:
193
+ import watchdog # noqa: F401
194
+ inotify_line = "[green]inotify: active (watchdog installed)[/]"
195
+ except ImportError:
196
+ inotify_line = "[yellow]inotify: degraded (polling only, install watchdog)[/]"
197
+ console.print(f" {inotify_line}")
198
+ console.print()
199
+
200
+ @consciousness.command("quality")
201
+ @click.option("--home", default=AGENT_HOME, type=click.Path(), help="Agent home directory.")
202
+ @click.option("--json-out", is_flag=True, help="Output as JSON.")
203
+ @click.option("--port", default=7777, help="Daemon API port (tries live daemon first).")
204
+ def consciousness_quality(home: str, json_out: bool, port: int):
205
+ """Show average response quality scores for today.
206
+
207
+ Reads quality metrics from today's daily metrics file.
208
+ Dimensions: length appropriateness, coherence, latency, overall.
209
+ """
210
+ import urllib.request
211
+ import urllib.error
212
+ from datetime import datetime, timezone
213
+ from pathlib import Path
214
+
215
+ quality: dict = {}
216
+
217
+ # Try live daemon first — it may have unsaved in-memory data
218
+ try:
219
+ url = f"http://127.0.0.1:{port}/consciousness"
220
+ with urllib.request.urlopen(url, timeout=2) as resp:
221
+ data = json.loads(resp.read())
222
+ if "quality_avg" in data:
223
+ quality = data["quality_avg"]
224
+ except Exception:
225
+ pass # Daemon unreachable — fall through to file
226
+
227
+ # Fall back to daily metrics file
228
+ if not quality:
229
+ home_path = Path(home).expanduser()
230
+ date_str = datetime.now(timezone.utc).strftime("%Y-%m-%d")
231
+ daily = home_path / "metrics" / "daily" / f"{date_str}.json"
232
+ if daily.exists():
233
+ try:
234
+ file_data = json.loads(daily.read_text(encoding="utf-8"))
235
+ quality = file_data.get("quality_avg", {})
236
+ except Exception:
237
+ pass
238
+
239
+ if not quality or quality.get("count", 0) == 0:
240
+ if json_out:
241
+ click.echo(json.dumps({"count": 0, "message": "No quality data for today"}))
242
+ else:
243
+ console.print("\n [yellow]No quality metrics recorded today.[/]\n")
244
+ return
245
+
246
+ if json_out:
247
+ click.echo(json.dumps(quality, indent=2))
248
+ return
249
+
250
+ from rich.panel import Panel
251
+ from rich.table import Table
252
+
253
+ count = quality.get("count", 0)
254
+ overall = quality.get("overall", 0.0)
255
+ color = "green" if overall >= 0.7 else ("yellow" if overall >= 0.4 else "red")
256
+
257
+ def _bar(score: float) -> str:
258
+ filled = int(round(score * 10))
259
+ return "█" * filled + "░" * (10 - filled)
260
+
261
+ table = Table(title=f"Response Quality — {count} response(s) today", show_header=True)
262
+ table.add_column("Dimension", style="bold")
263
+ table.add_column("Score", justify="right")
264
+ table.add_column("Bar")
265
+ table.add_column("Description", style="dim")
266
+
267
+ dims = [
268
+ ("Length", quality.get("length", 0.0), "Response is appropriate length for question"),
269
+ ("Coherence", quality.get("coherence", 0.0), "Keywords from question appear in response"),
270
+ ("Latency", quality.get("latency", 0.0), "Response generated quickly"),
271
+ ("Overall", overall, "Weighted average (coherence 40%, length 30%, latency 30%)"),
272
+ ]
273
+
274
+ for name, score, desc in dims:
275
+ s_color = "green" if score >= 0.7 else ("yellow" if score >= 0.4 else "red")
276
+ table.add_row(
277
+ name,
278
+ f"[{s_color}]{score:.3f}[/]",
279
+ f"[{s_color}]{_bar(score)}[/]",
280
+ desc,
281
+ )
282
+
283
+ console.print()
284
+ console.print(table)
285
+ console.print(
286
+ Panel(
287
+ f"Overall quality: [{color}]{overall:.3f}[/] ({count} responses today)",
288
+ border_style=color,
289
+ )
290
+ )
291
+ console.print()
292
+
293
+ @consciousness.command("profiles")
294
+ @click.option("--show", "do_show", is_flag=True, help="List all profiles.")
295
+ @click.option("--stale", "do_stale", is_flag=True, help="Show profiles older than 90 days.")
296
+ @click.option("--update", "do_update", is_flag=True, help="Re-apply bundled defaults.")
297
+ @click.option("--export", "do_export", is_flag=True, help="Export profiles as YAML.")
298
+ def consciousness_profiles(do_show: bool, do_stale: bool, do_update: bool, do_export: bool):
299
+ """Manage model prompt profiles."""
300
+ from ..prompt_adapter import PromptAdapter, _BUNDLED_PROFILES
301
+
302
+ adapter = PromptAdapter()
303
+
304
+ if do_export:
305
+ import yaml
306
+ profiles_data = [p.model_dump() for p in adapter.profiles]
307
+ click.echo(yaml.dump({"profiles": profiles_data}, default_flow_style=False))
308
+ return
309
+
310
+ if do_update:
311
+ adapter.reload_profiles()
312
+ console.print(f"\n [green]Reloaded {len(adapter.profiles)} profiles from bundled defaults.[/]\n")
313
+ return
314
+
315
+ if do_stale:
316
+ from datetime import datetime, timezone
317
+ now = datetime.now(timezone.utc)
318
+ stale = []
319
+ for p in adapter.profiles:
320
+ if not p.last_updated:
321
+ stale.append((p.family, "never"))
322
+ continue
323
+ try:
324
+ updated = datetime.fromisoformat(p.last_updated)
325
+ if hasattr(updated, "tzinfo") and updated.tzinfo is None:
326
+ updated = updated.replace(tzinfo=timezone.utc)
327
+ age = (now - updated).days
328
+ if age > 90:
329
+ stale.append((p.family, f"{age}d ago"))
330
+ except (ValueError, TypeError):
331
+ stale.append((p.family, "invalid date"))
332
+
333
+ if stale:
334
+ console.print("\n [yellow]Stale profiles (>90 days):[/]")
335
+ for family, age in stale:
336
+ console.print(f" {family}: {age}")
337
+ else:
338
+ console.print("\n [green]All profiles are fresh.[/]")
339
+ console.print()
340
+ return
341
+
342
+ # Default: --show
343
+ from rich.table import Table
344
+ table = Table(title="Model Profiles")
345
+ table.add_column("Family", style="bold")
346
+ table.add_column("Pattern")
347
+ table.add_column("System Mode")
348
+ table.add_column("Format")
349
+ table.add_column("Thinking")
350
+ table.add_column("Updated")
351
+
352
+ for p in adapter.profiles:
353
+ thinking = p.thinking_mode if p.thinking_enabled else "-"
354
+ table.add_row(
355
+ p.family,
356
+ p.model_pattern,
357
+ p.system_prompt_mode,
358
+ p.structure_format,
359
+ thinking,
360
+ p.last_updated or "[dim]never[/]",
361
+ )
362
+
363
+ console.print()
364
+ console.print(table)
365
+ console.print()
366
+
367
+ @consciousness.command("fallbacks")
368
+ @click.option("--limit", default=20, show_default=True, help="Number of recent events to show.")
369
+ @click.option("--json-out", is_flag=True, help="Output as JSON.")
370
+ @click.option("--clear", "do_clear", is_flag=True, help="Clear all stored fallback events.")
371
+ def consciousness_fallbacks(limit: int, json_out: bool, do_clear: bool):
372
+ """Show LLM fallback history — when and why the agent degraded.
373
+
374
+ Each entry records the primary model that failed, the backend that
375
+ was tried next, whether the fallback succeeded, and the reason.
376
+ Events are stored in ~/.skcapstone/fallbacks.json.
377
+ """
378
+ from ..fallback_tracker import FallbackTracker
379
+
380
+ tracker = FallbackTracker()
381
+
382
+ if do_clear:
383
+ count = tracker.clear()
384
+ console.print(f"\n [green]Cleared {count} fallback event(s).[/]\n")
385
+ return
386
+
387
+ events = tracker.load_events(limit=limit)
388
+
389
+ if json_out:
390
+ click.echo(
391
+ json.dumps([e.model_dump() for e in events], indent=2)
392
+ )
393
+ return
394
+
395
+ if not events:
396
+ console.print(
397
+ f"\n [dim]No fallback events recorded yet.[/]\n"
398
+ f" Events are written to: {tracker.path}\n"
399
+ )
400
+ return
401
+
402
+ from rich.table import Table
403
+
404
+ table = Table(title=f"LLM Fallback History (last {len(events)})", show_lines=True)
405
+ table.add_column("Time", style="dim", no_wrap=True)
406
+ table.add_column("Primary", style="bold")
407
+ table.add_column("→ Fallback")
408
+ table.add_column("OK?", justify="center")
409
+ table.add_column("Reason")
410
+
411
+ for evt in events:
412
+ ts = evt.timestamp[:19].replace("T", " ") # YYYY-MM-DD HH:MM:SS
413
+ ok_str = "[green]✓[/]" if evt.success else "[red]✗[/]"
414
+ primary = f"{evt.primary_model}\n[dim]{evt.primary_backend}[/]"
415
+ fallback = f"{evt.fallback_model}\n[dim]{evt.fallback_backend}[/]"
416
+ table.add_row(ts, primary, fallback, ok_str, evt.reason)
417
+
418
+ console.print()
419
+ console.print(table)
420
+ console.print(f" [dim]Source: {tracker.path}[/]")
421
+ console.print()