@smilintux/skcapstone 0.1.0 → 0.2.4

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 +880 -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 +191 -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 +398 -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 +357 -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 +264 -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,154 @@
1
+ """Peer management commands: add, list, remove, show."""
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
+ from ._validators import validate_agent_name
13
+
14
+ from rich.panel import Panel
15
+ from rich.table import Table
16
+
17
+
18
+ def register_peer_commands(main: click.Group) -> None:
19
+ """Register the peer command group."""
20
+
21
+ @main.group()
22
+ def peer():
23
+ """Peer management — discover, add, and manage trusted contacts."""
24
+
25
+ @peer.command("add")
26
+ @click.option("--card", "card_path", type=click.Path(exists=True), help="Import from identity card.")
27
+ @click.option("--name", default=None, help="Peer name (required if not using --card).")
28
+ @click.option("--pubkey", default=None, type=click.Path(exists=True), help="Path to PGP public key.")
29
+ @click.option("--email", default=None, help="Peer contact email.")
30
+ @click.option("--home", "sk_home", default=AGENT_HOME, type=click.Path())
31
+ def peer_add(card_path, name, pubkey, email, sk_home):
32
+ """Add a peer from an identity card or manually."""
33
+ from ..peers import add_peer_from_card, add_peer_manual
34
+
35
+ sk_path = Path(sk_home).expanduser()
36
+
37
+ if card_path:
38
+ try:
39
+ peer_record = add_peer_from_card(Path(card_path), skcapstone_home=sk_path)
40
+ fp = peer_record.fingerprint[:16] + "..." if peer_record.fingerprint else "none"
41
+ console.print(f"\n [green]Added peer:[/] [cyan]{peer_record.name}[/]")
42
+ console.print(f" Fingerprint: [dim]{fp}[/]")
43
+ console.print(f" Type: {peer_record.entity_type}")
44
+ console.print(f" Trust: {peer_record.trust_level}")
45
+ console.print(f" Capabilities: {', '.join(peer_record.capabilities[:5])}")
46
+ if peer_record.public_key:
47
+ console.print(f" [green]Public key imported[/] — encrypted messaging enabled")
48
+ console.print()
49
+ except (FileNotFoundError, ValueError) as exc:
50
+ console.print(f"\n [red]Error:[/] {exc}\n")
51
+ sys.exit(1)
52
+ elif name:
53
+ peer_record = add_peer_manual(
54
+ name=name, public_key_path=Path(pubkey) if pubkey else None,
55
+ email=email or "", skcapstone_home=sk_path,
56
+ )
57
+ console.print(f"\n [green]Added peer:[/] [cyan]{peer_record.name}[/]")
58
+ if peer_record.public_key:
59
+ console.print(f" [green]Public key imported[/]")
60
+ console.print()
61
+ else:
62
+ console.print("\n [yellow]Provide --card or --name.[/]")
63
+ console.print(" skcapstone peer add --card card.json")
64
+ console.print(" skcapstone peer add --name Lumina --pubkey lumina.pub.asc\n")
65
+ sys.exit(1)
66
+
67
+ @peer.command("list")
68
+ @click.option("--home", "sk_home", default=AGENT_HOME, type=click.Path())
69
+ @click.option("--json-out", is_flag=True, help="Output as JSON.")
70
+ def peer_list(sk_home, json_out):
71
+ """List all known peers."""
72
+ from ..peers import list_peers
73
+
74
+ peers = list_peers(skcapstone_home=Path(sk_home).expanduser())
75
+
76
+ if json_out:
77
+ click.echo(json.dumps([p.model_dump() for p in peers], indent=2, default=str))
78
+ return
79
+
80
+ console.print()
81
+ if not peers:
82
+ console.print(" [dim]No peers registered.[/]")
83
+ console.print(" Add one: skcapstone peer add --card card.json")
84
+ console.print()
85
+ return
86
+
87
+ table = Table(show_header=True, header_style="bold", box=None, padding=(0, 2),
88
+ title=f"Known Peers ({len(peers)})")
89
+ table.add_column("Name", style="cyan")
90
+ table.add_column("Type", style="dim")
91
+ table.add_column("Fingerprint", style="dim", max_width=20)
92
+ table.add_column("Trust")
93
+ table.add_column("Key")
94
+
95
+ for p in peers:
96
+ fp = p.fingerprint[:16] + "..." if p.fingerprint else ""
97
+ has_key = "[green]yes[/]" if p.public_key else "[yellow]no[/]"
98
+ table.add_row(p.name, p.entity_type, fp, p.trust_level, has_key)
99
+
100
+ console.print(table)
101
+ console.print()
102
+
103
+ @peer.command("remove")
104
+ @click.argument("name")
105
+ @click.option("--home", "sk_home", default=AGENT_HOME, type=click.Path())
106
+ def peer_remove(name, sk_home):
107
+ """Remove a peer by name."""
108
+ from ..peers import remove_peer
109
+
110
+ validate_agent_name(name)
111
+
112
+ removed = remove_peer(name, skcapstone_home=Path(sk_home).expanduser())
113
+ if removed:
114
+ console.print(f"\n [green]Removed peer:[/] {name}\n")
115
+ else:
116
+ console.print(f"\n [yellow]Peer '{name}' not found.[/]\n")
117
+
118
+ @peer.command("show")
119
+ @click.argument("name")
120
+ @click.option("--home", "sk_home", default=AGENT_HOME, type=click.Path())
121
+ def peer_show(name, sk_home):
122
+ """Show detailed info about a peer."""
123
+ from ..peers import get_peer
124
+
125
+ validate_agent_name(name)
126
+
127
+ p = get_peer(name, skcapstone_home=Path(sk_home).expanduser())
128
+ if not p:
129
+ console.print(f"\n [yellow]Peer '{name}' not found.[/]\n")
130
+ return
131
+
132
+ fp = p.fingerprint[:20] + "..." if len(p.fingerprint) > 20 else p.fingerprint
133
+ lines = [
134
+ f"[bold]Name:[/] [cyan]{p.name}[/]",
135
+ f"[bold]Type:[/] {p.entity_type}",
136
+ f"[bold]Fingerprint:[/] [dim]{fp}[/]",
137
+ f"[bold]Trust:[/] {p.trust_level}",
138
+ f"[bold]Source:[/] {p.source}",
139
+ f"[bold]Added:[/] {p.added_at[:19]}",
140
+ ]
141
+ if p.handle:
142
+ lines.append(f"[bold]Handle:[/] {p.handle}")
143
+ if p.email:
144
+ lines.append(f"[bold]Email:[/] {p.email}")
145
+ if p.capabilities:
146
+ lines.append(f"[bold]Capabilities:[/] {', '.join(p.capabilities[:6])}")
147
+ if p.contact_uris:
148
+ for uri in p.contact_uris:
149
+ lines.append(f"[bold]Contact:[/] [cyan]{uri}[/]")
150
+ lines.append(f"[bold]PGP Key:[/] {'[green]present[/]' if p.public_key else '[yellow]missing[/]'}")
151
+
152
+ console.print()
153
+ console.print(Panel("\n".join(lines), title=f"Peer: {p.name}", border_style="cyan"))
154
+ console.print()
@@ -0,0 +1,122 @@
1
+ """Peer directory commands: peers list, peers add, peers remove, peers discover."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+
8
+ import click
9
+
10
+ from ._common import AGENT_HOME, console
11
+
12
+ from rich.table import Table
13
+
14
+
15
+ def register_peers_dir_commands(main: click.Group) -> None:
16
+ """Register the peers (directory) command group."""
17
+
18
+ @main.group("peers")
19
+ def peers_dir():
20
+ """Peer transport directory — routing addresses for the mesh."""
21
+
22
+ @peers_dir.command("list")
23
+ @click.option("--home", "sk_home", default=AGENT_HOME, type=click.Path())
24
+ @click.option("--json-out", is_flag=True, help="Output as JSON.")
25
+ def peers_list(sk_home, json_out):
26
+ """List all peers in the transport directory."""
27
+ from ..peer_directory import PeerDirectory
28
+
29
+ directory = PeerDirectory(home=Path(sk_home).expanduser())
30
+ peers = directory.list_peers()
31
+
32
+ if json_out:
33
+ click.echo(json.dumps([p.model_dump() for p in peers], indent=2, default=str))
34
+ return
35
+
36
+ console.print()
37
+ if not peers:
38
+ console.print(" [dim]No peers in directory.[/]")
39
+ console.print(" Add one: skcapstone peers add --name lumina --address /path/to/outbox/lumina")
40
+ console.print(" Or auto-discover: skcapstone peers discover")
41
+ console.print()
42
+ return
43
+
44
+ table = Table(
45
+ show_header=True,
46
+ header_style="bold",
47
+ box=None,
48
+ padding=(0, 2),
49
+ title=f"Peer Transport Directory ({len(peers)})",
50
+ )
51
+ table.add_column("Name", style="cyan")
52
+ table.add_column("Transport", style="dim")
53
+ table.add_column("Address")
54
+ table.add_column("Last Seen", style="dim")
55
+
56
+ for p in peers:
57
+ last = p.last_seen[:19] if p.last_seen else "[dim]never[/]"
58
+ table.add_row(p.name, p.transport, p.address, last)
59
+
60
+ console.print(table)
61
+ console.print()
62
+
63
+ @peers_dir.command("add")
64
+ @click.option("--name", required=True, help="Peer name.")
65
+ @click.option("--address", required=True, help="Transport address (path, IP, URI).")
66
+ @click.option(
67
+ "--transport",
68
+ default="syncthing",
69
+ show_default=True,
70
+ help="Transport type: syncthing, webrtc, tailscale, file.",
71
+ )
72
+ @click.option("--fingerprint", default="", help="PGP fingerprint (optional).")
73
+ @click.option("--home", "sk_home", default=AGENT_HOME, type=click.Path())
74
+ def peers_add(name, address, transport, fingerprint, sk_home):
75
+ """Add a peer to the transport directory."""
76
+ from ..peer_directory import PeerDirectory
77
+
78
+ directory = PeerDirectory(home=Path(sk_home).expanduser())
79
+ entry = directory.add_peer(
80
+ name=name,
81
+ address=address,
82
+ transport=transport,
83
+ fingerprint=fingerprint,
84
+ )
85
+ console.print(f"\n [green]Added peer:[/] [cyan]{entry.name}[/]")
86
+ console.print(f" Transport: {entry.transport}")
87
+ console.print(f" Address: {entry.address}")
88
+ if entry.fingerprint:
89
+ console.print(f" Fingerprint: [dim]{entry.fingerprint[:20]}...[/]")
90
+ console.print()
91
+
92
+ @peers_dir.command("remove")
93
+ @click.argument("name")
94
+ @click.option("--home", "sk_home", default=AGENT_HOME, type=click.Path())
95
+ def peers_remove(name, sk_home):
96
+ """Remove a peer from the transport directory."""
97
+ from ..peer_directory import PeerDirectory
98
+
99
+ directory = PeerDirectory(home=Path(sk_home).expanduser())
100
+ removed = directory.remove_peer(name)
101
+ if removed:
102
+ console.print(f"\n [green]Removed:[/] {name}\n")
103
+ else:
104
+ console.print(f"\n [yellow]Peer '{name}' not in directory.[/]\n")
105
+
106
+ @peers_dir.command("discover")
107
+ @click.option("--home", "sk_home", default=AGENT_HOME, type=click.Path())
108
+ def peers_discover(sk_home):
109
+ """Auto-discover peers from heartbeat files and Syncthing outbox."""
110
+ from ..peer_directory import PeerDirectory
111
+
112
+ directory = PeerDirectory(home=Path(sk_home).expanduser())
113
+ added = directory.auto_discover()
114
+
115
+ console.print()
116
+ if not added:
117
+ console.print(" [dim]No new peers discovered.[/]")
118
+ else:
119
+ console.print(f" [green]Discovered {len(added)} new peer(s):[/]")
120
+ for entry in added:
121
+ console.print(f" [cyan]{entry.name}[/] — {entry.transport} → {entry.address}")
122
+ console.print()
@@ -0,0 +1,83 @@
1
+ """CLI command: skcapstone preflight — run daemon startup checks."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ import click
9
+
10
+ from ._common import AGENT_HOME, console
11
+
12
+
13
+ def register_preflight_commands(main: click.Group) -> None:
14
+ """Register the preflight command."""
15
+
16
+ @main.command("preflight")
17
+ @click.option("--home", default=AGENT_HOME, type=click.Path(),
18
+ help="Agent home directory.")
19
+ @click.option("--json-out", is_flag=True, help="Output as JSON.")
20
+ def preflight_cmd(home: str, json_out: bool):
21
+ """Run daemon preflight checks.
22
+
23
+ Verifies that the environment is ready for daemon startup:
24
+ Python version, required packages, Ollama, PGP identity,
25
+ home directory structure, consciousness config, and disk space.
26
+
27
+ Exits with code 0 if all critical checks pass, 1 otherwise.
28
+
29
+ Examples:
30
+
31
+ skcapstone preflight
32
+
33
+ skcapstone preflight --json-out
34
+ """
35
+ import json
36
+ from ..preflight import PreflightChecker
37
+
38
+ checker = PreflightChecker(home=Path(home).expanduser())
39
+ summary = checker.run_all()
40
+
41
+ if json_out:
42
+ click.echo(json.dumps(summary, indent=2))
43
+ sys.exit(0 if summary["ok"] else 1)
44
+
45
+ # Colored table output
46
+ console.print()
47
+ console.print("[bold]Daemon Preflight Checks[/]")
48
+ console.print()
49
+
50
+ status_styles = {
51
+ "ok": ("[green] OK [/]", "green"),
52
+ "warn": ("[yellow] WARN [/]", "yellow"),
53
+ "fail": ("[bold red] FAIL [/]", "red"),
54
+ }
55
+
56
+ for check in summary["checks"]:
57
+ badge, color = status_styles.get(check["status"], ("[dim] ? [/]", "white"))
58
+ name = check["name"].ljust(12)
59
+ msg = check["message"]
60
+ console.print(f" {badge} [{color}]{name}[/] {msg}")
61
+
62
+ console.print()
63
+
64
+ warnings = summary["warnings"]
65
+ failures = summary["failures"]
66
+ critical = summary["critical_failures"]
67
+
68
+ if summary["ok"]:
69
+ if warnings or failures:
70
+ console.print(
71
+ f"[yellow]Preflight passed with {warnings} warning(s) "
72
+ f"and {failures} non-critical failure(s).[/]"
73
+ )
74
+ else:
75
+ console.print("[bold green]All preflight checks passed.[/]")
76
+ else:
77
+ console.print(
78
+ f"[bold red]Preflight FAILED — {critical} critical failure(s).[/] "
79
+ "Fix the issues above before starting the daemon."
80
+ )
81
+
82
+ console.print()
83
+ sys.exit(0 if summary["ok"] else 1)
@@ -0,0 +1,310 @@
1
+ """Profile commands: list, show, stale."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import sys
7
+ from datetime import date, datetime, timedelta, timezone
8
+ from pathlib import Path
9
+
10
+ import click
11
+
12
+ from ._common import AGENT_HOME, console
13
+
14
+
15
+ # Number of days before a profile is considered stale.
16
+ _STALE_DAYS = 90
17
+
18
+
19
+ def _load_profiles_path(home: str) -> Path | None:
20
+ """Resolve the agent-level model_profiles.yaml override if it exists.
21
+
22
+ Args:
23
+ home: Agent home directory path.
24
+
25
+ Returns:
26
+ Path to agent-level profiles YAML, or None to use bundled defaults.
27
+ """
28
+ agent_override = Path(home).expanduser() / "config" / "model_profiles.yaml"
29
+ if agent_override.exists():
30
+ return agent_override
31
+ return None
32
+
33
+
34
+ def _get_adapter(home: str):
35
+ """Return a PromptAdapter, preferring agent-level profiles.
36
+
37
+ Args:
38
+ home: Agent home directory.
39
+
40
+ Returns:
41
+ PromptAdapter instance with profiles loaded.
42
+ """
43
+ from ..prompt_adapter import PromptAdapter
44
+
45
+ profiles_path = _load_profiles_path(home)
46
+ return PromptAdapter(profiles_path)
47
+
48
+
49
+ def _parse_last_updated(value: str) -> date | None:
50
+ """Parse a last_updated string to a date object.
51
+
52
+ Args:
53
+ value: ISO-8601 date string (e.g. "2026-03-02").
54
+
55
+ Returns:
56
+ date object or None if the string is empty / unparseable.
57
+ """
58
+ if not value:
59
+ return None
60
+ try:
61
+ return datetime.strptime(value, "%Y-%m-%d").date()
62
+ except ValueError:
63
+ return None
64
+
65
+
66
+ def register_profile_commands(main: click.Group) -> None:
67
+ """Register the ``profile`` command group on the main CLI."""
68
+
69
+ @main.group()
70
+ def profile():
71
+ """Model profile management — inspect prompt-formatting profiles.
72
+
73
+ Model profiles define how prompts are formatted for each LLM family:
74
+ system-prompt placement, structure format, thinking mode, temperature
75
+ defaults, and more.
76
+
77
+ The bundled profiles live in ``data/model_profiles.yaml``. To
78
+ override any entry, place a custom copy at::
79
+
80
+ ~/.skcapstone/agents/<name>/config/model_profiles.yaml
81
+ """
82
+
83
+ # ------------------------------------------------------------------
84
+ # profile list
85
+ # ------------------------------------------------------------------
86
+
87
+ @profile.command("list")
88
+ @click.option(
89
+ "--home", default=AGENT_HOME, type=click.Path(),
90
+ help="Agent home directory (used to resolve overrides).",
91
+ )
92
+ @click.option("--json", "json_out", is_flag=True, help="Output raw JSON.")
93
+ def profile_list(home: str, json_out: bool) -> None:
94
+ """List all loaded model profiles.
95
+
96
+ Profiles are shown in the order they are matched (first match wins).
97
+ The agent-level override (if present) is merged on top of the
98
+ bundled defaults.
99
+
100
+ Examples:
101
+
102
+ skcapstone profile list
103
+
104
+ skcapstone profile list --json
105
+ """
106
+ from rich.panel import Panel
107
+ from rich.table import Table
108
+
109
+ adapter = _get_adapter(home)
110
+ profiles = adapter.profiles
111
+
112
+ if not profiles:
113
+ console.print("\n [dim]No profiles loaded.[/]\n")
114
+ sys.exit(1)
115
+
116
+ if json_out:
117
+ rows = [p.model_dump() for p in profiles]
118
+ click.echo(json.dumps(rows, indent=2))
119
+ return
120
+
121
+ table = Table(show_header=True, header_style="bold", box=None, padding=(0, 2))
122
+ table.add_column("Family", style="cyan")
123
+ table.add_column("Pattern", style="dim")
124
+ table.add_column("Sys-prompt mode")
125
+ table.add_column("Format")
126
+ table.add_column("Thinking")
127
+ table.add_column("Updated", style="dim")
128
+
129
+ for p in profiles:
130
+ thinking = p.thinking_mode if p.thinking_enabled else "off"
131
+ table.add_row(
132
+ p.family,
133
+ p.model_pattern,
134
+ p.system_prompt_mode,
135
+ p.structure_format,
136
+ thinking,
137
+ p.last_updated or "[dim]unknown[/]",
138
+ )
139
+
140
+ label = f"[bold]{len(profiles)}[/] model profile(s) loaded"
141
+ console.print()
142
+ console.print(
143
+ Panel(label, title="Model Profiles", border_style="bright_blue")
144
+ )
145
+ console.print(table)
146
+ console.print()
147
+
148
+ # ------------------------------------------------------------------
149
+ # profile show
150
+ # ------------------------------------------------------------------
151
+
152
+ @profile.command("show")
153
+ @click.argument("model")
154
+ @click.option(
155
+ "--home", default=AGENT_HOME, type=click.Path(),
156
+ help="Agent home directory (used to resolve overrides).",
157
+ )
158
+ @click.option("--json", "json_out", is_flag=True, help="Output raw JSON.")
159
+ def profile_show(model: str, home: str, json_out: bool) -> None:
160
+ """Show the profile matched for MODEL.
161
+
162
+ MODEL is matched against all profile ``model_pattern`` regexes.
163
+ If nothing matches, the generic fallback profile is shown.
164
+
165
+ Examples:
166
+
167
+ skcapstone profile show claude-opus-4-5
168
+
169
+ skcapstone profile show grok-3
170
+
171
+ skcapstone profile show deepseek-r1-70b --json
172
+ """
173
+ from rich.panel import Panel
174
+ from rich.table import Table
175
+
176
+ adapter = _get_adapter(home)
177
+ p = adapter.resolve_profile(model)
178
+
179
+ if json_out:
180
+ click.echo(json.dumps(p.model_dump(), indent=2))
181
+ return
182
+
183
+ console.print()
184
+ console.print(
185
+ Panel(
186
+ f"Profile for [bold cyan]{model}[/] → family [bold]{p.family}[/]",
187
+ border_style="cyan",
188
+ padding=(0, 2),
189
+ )
190
+ )
191
+
192
+ table = Table(show_header=False, box=None, padding=(0, 2))
193
+ table.add_column("Field", style="bold dim", min_width=28)
194
+ table.add_column("Value")
195
+
196
+ rows = [
197
+ ("model_pattern", p.model_pattern),
198
+ ("family", p.family),
199
+ ("system_prompt_mode", p.system_prompt_mode),
200
+ ("structure_format", p.structure_format),
201
+ ("thinking_enabled", str(p.thinking_enabled)),
202
+ ("thinking_mode", p.thinking_mode),
203
+ ("thinking_budget_tokens", str(p.thinking_budget_tokens)),
204
+ ("default_temperature",
205
+ str(p.default_temperature) if p.default_temperature is not None else "[dim]—[/]"),
206
+ ("code_temperature",
207
+ str(p.code_temperature) if p.code_temperature is not None else "[dim]—[/]"),
208
+ ("reasoning_temperature",
209
+ str(p.reasoning_temperature) if p.reasoning_temperature is not None else "[dim]—[/]"),
210
+ ("max_system_tokens", str(p.max_system_tokens)),
211
+ ("tool_format", p.tool_format),
212
+ ("no_few_shot", str(p.no_few_shot)),
213
+ ("no_cot_instructions", str(p.no_cot_instructions)),
214
+ ("last_updated", p.last_updated or "[dim]unknown[/]"),
215
+ ("source_url", p.source_url or "[dim]—[/]"),
216
+ ("notes", p.notes or "[dim]—[/]"),
217
+ ]
218
+
219
+ for field, value in rows:
220
+ table.add_row(field, value)
221
+
222
+ console.print(table)
223
+ console.print()
224
+
225
+ # ------------------------------------------------------------------
226
+ # profile stale
227
+ # ------------------------------------------------------------------
228
+
229
+ @profile.command("stale")
230
+ @click.option(
231
+ "--home", default=AGENT_HOME, type=click.Path(),
232
+ help="Agent home directory (used to resolve overrides).",
233
+ )
234
+ @click.option(
235
+ "--days", default=_STALE_DAYS, type=int, show_default=True,
236
+ help="Number of days before a profile is considered stale.",
237
+ )
238
+ @click.option("--json", "json_out", is_flag=True, help="Output raw JSON.")
239
+ def profile_stale(home: str, days: int, json_out: bool) -> None:
240
+ """Show profiles older than DAYS days (default: 90).
241
+
242
+ A profile is stale when its ``last_updated`` date is more than
243
+ ``--days`` days in the past, or when ``last_updated`` is missing.
244
+
245
+ Stale profiles may have outdated guidance — consider refreshing
246
+ them against the latest provider documentation.
247
+
248
+ Examples:
249
+
250
+ skcapstone profile stale
251
+
252
+ skcapstone profile stale --days 30
253
+
254
+ skcapstone profile stale --json
255
+ """
256
+ from rich.panel import Panel
257
+ from rich.table import Table
258
+
259
+ adapter = _get_adapter(home)
260
+ cutoff = date.today() - timedelta(days=days)
261
+
262
+ stale = []
263
+ for p in adapter.profiles:
264
+ updated = _parse_last_updated(p.last_updated)
265
+ if updated is None or updated < cutoff:
266
+ stale.append((p, updated))
267
+
268
+ if json_out:
269
+ rows = []
270
+ for p, updated in stale:
271
+ d = p.model_dump()
272
+ d["_days_old"] = (
273
+ (date.today() - updated).days if updated else None
274
+ )
275
+ rows.append(d)
276
+ click.echo(json.dumps(rows, indent=2))
277
+ return
278
+
279
+ if not stale:
280
+ console.print(
281
+ f"\n [bold green]✓[/] All profiles updated within {days} days.\n"
282
+ )
283
+ return
284
+
285
+ label = (
286
+ f"[bold]{len(stale)}[/] stale profile(s) "
287
+ f"(older than {days} days — cutoff [dim]{cutoff}[/])"
288
+ )
289
+ console.print()
290
+ console.print(
291
+ Panel(label, title="Stale Profiles", border_style="yellow")
292
+ )
293
+
294
+ table = Table(show_header=True, header_style="bold", box=None, padding=(0, 2))
295
+ table.add_column("Family", style="cyan")
296
+ table.add_column("Pattern", style="dim")
297
+ table.add_column("Last Updated", style="yellow")
298
+ table.add_column("Age (days)", style="red")
299
+
300
+ for p, updated in stale:
301
+ age = str((date.today() - updated).days) if updated else "[dim]unknown[/]"
302
+ table.add_row(
303
+ p.family,
304
+ p.model_pattern,
305
+ p.last_updated or "[dim]missing[/]",
306
+ age,
307
+ )
308
+
309
+ console.print(table)
310
+ console.print()