@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,360 @@
1
+ """Benchmark LLM response time across all available backends.
2
+
3
+ Sends a configurable prompt to each detected backend, measures wall-clock
4
+ latency, and renders a Rich table (or JSON) with results.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import os
11
+ import time
12
+ from typing import Optional
13
+
14
+ import click
15
+ from rich.table import Table
16
+
17
+ from ._common import AGENT_HOME, console
18
+
19
+ # Ordered list of all known backends (matches consciousness_loop.py fallback_chain)
20
+ BACKENDS: list[str] = ["ollama", "grok", "kimi", "nvidia", "anthropic", "openai", "passthrough"]
21
+
22
+
23
+ # ---------------------------------------------------------------------------
24
+ # BenchmarkRunner
25
+ # ---------------------------------------------------------------------------
26
+
27
+
28
+ class BenchmarkRunner:
29
+ """Run latency benchmarks against LLM backends.
30
+
31
+ Each backend is called with a simple prompt and the round-trip wall-clock
32
+ time is recorded. Cloud backends (anthropic, openai, grok, kimi, nvidia)
33
+ require both an API key *and* the respective SDK to be installed.
34
+
35
+ Args:
36
+ prompt: The prompt string to send to every backend.
37
+ timeout: Per-backend HTTP/API timeout in seconds.
38
+ """
39
+
40
+ def __init__(self, prompt: str = "Hello", timeout: float = 30.0) -> None:
41
+ self.prompt = prompt
42
+ self.timeout = timeout
43
+
44
+ # ------------------------------------------------------------------
45
+ # Detection
46
+ # ------------------------------------------------------------------
47
+
48
+ def detect_backends(self) -> dict[str, bool]:
49
+ """Return a mapping of backend name → availability.
50
+
51
+ Ollama is probed via HTTP; cloud providers are available when the
52
+ corresponding API key env-var is set; passthrough is always True.
53
+ """
54
+ return {
55
+ "ollama": self._probe_ollama(),
56
+ "grok": bool(os.environ.get("XAI_API_KEY")),
57
+ "kimi": bool(os.environ.get("MOONSHOT_API_KEY")),
58
+ "nvidia": bool(os.environ.get("NVIDIA_API_KEY")),
59
+ "anthropic": bool(os.environ.get("ANTHROPIC_API_KEY")),
60
+ "openai": bool(os.environ.get("OPENAI_API_KEY")),
61
+ "passthrough": True,
62
+ }
63
+
64
+ def _probe_ollama(self) -> bool:
65
+ """Return True if the Ollama API is reachable."""
66
+ import urllib.request
67
+
68
+ host = os.environ.get("OLLAMA_HOST", "http://localhost:11434")
69
+ try:
70
+ with urllib.request.urlopen(f"{host}/api/tags", timeout=2):
71
+ return True
72
+ except Exception:
73
+ return False
74
+
75
+ # ------------------------------------------------------------------
76
+ # Runner
77
+ # ------------------------------------------------------------------
78
+
79
+ def run_all(self, skip_unavailable: bool = True) -> list[dict]:
80
+ """Benchmark all backends and return a list of result dicts.
81
+
82
+ Result dict keys: ``backend``, ``status``, ``ms``, ``model``, ``error``.
83
+
84
+ Args:
85
+ skip_unavailable: When True, unavailable backends are included in
86
+ the result list with ``status="unavailable"`` but are not
87
+ actually called.
88
+ """
89
+ available = self.detect_backends()
90
+ results: list[dict] = []
91
+ for name in BACKENDS:
92
+ if not available.get(name, False):
93
+ results.append(
94
+ {"backend": name, "status": "unavailable", "ms": None, "model": None, "error": None}
95
+ )
96
+ continue
97
+ results.append(self.run_backend(name))
98
+ return results
99
+
100
+ def run_backend(self, name: str) -> dict:
101
+ """Benchmark a single backend by name.
102
+
103
+ Returns a result dict with keys: ``backend``, ``status``, ``ms``,
104
+ ``model``, ``error``.
105
+ """
106
+ method = getattr(self, f"_bench_{name}", None)
107
+ if method is None:
108
+ return {
109
+ "backend": name,
110
+ "status": "unsupported",
111
+ "ms": None,
112
+ "model": None,
113
+ "error": f"No benchmark method for backend '{name}'",
114
+ }
115
+ try:
116
+ return method()
117
+ except Exception as exc:
118
+ return {
119
+ "backend": name,
120
+ "status": "error",
121
+ "ms": None,
122
+ "model": None,
123
+ "error": str(exc)[:120],
124
+ }
125
+
126
+ # ------------------------------------------------------------------
127
+ # Per-backend implementations
128
+ # ------------------------------------------------------------------
129
+
130
+ def _bench_passthrough(self) -> dict:
131
+ """Passthrough — instant in-process mock (always succeeds)."""
132
+ t0 = time.perf_counter()
133
+ _ = f"[passthrough] {self.prompt}"
134
+ ms = round((time.perf_counter() - t0) * 1000, 3)
135
+ return {"backend": "passthrough", "status": "ok", "ms": ms, "model": "mock", "error": None}
136
+
137
+ def _bench_ollama(self) -> dict:
138
+ """Ollama — local HTTP POST to /api/generate."""
139
+ import urllib.request
140
+
141
+ host = os.environ.get("OLLAMA_HOST", "http://localhost:11434")
142
+ model = os.environ.get("OLLAMA_MODEL", "llama3.2")
143
+ payload = json.dumps(
144
+ {"model": model, "prompt": self.prompt, "stream": False}
145
+ ).encode()
146
+ req = urllib.request.Request(
147
+ f"{host}/api/generate",
148
+ data=payload,
149
+ headers={"Content-Type": "application/json"},
150
+ )
151
+ t0 = time.perf_counter()
152
+ with urllib.request.urlopen(req, timeout=self.timeout) as resp:
153
+ data = json.loads(resp.read())
154
+ ms = round((time.perf_counter() - t0) * 1000, 1)
155
+ return {
156
+ "backend": "ollama",
157
+ "status": "ok",
158
+ "ms": ms,
159
+ "model": data.get("model", model),
160
+ "error": None,
161
+ }
162
+
163
+ def _bench_anthropic(self) -> dict:
164
+ """Anthropic Claude — requires ``anthropic`` SDK + ANTHROPIC_API_KEY."""
165
+ try:
166
+ import anthropic as _anthropic
167
+ except ImportError:
168
+ raise RuntimeError("anthropic SDK not installed — run: pip install anthropic")
169
+
170
+ model = "claude-haiku-4-5-20251001"
171
+ client = _anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
172
+ t0 = time.perf_counter()
173
+ client.messages.create(
174
+ model=model,
175
+ max_tokens=64,
176
+ messages=[{"role": "user", "content": self.prompt}],
177
+ )
178
+ ms = round((time.perf_counter() - t0) * 1000, 1)
179
+ return {"backend": "anthropic", "status": "ok", "ms": ms, "model": model, "error": None}
180
+
181
+ def _bench_openai(self) -> dict:
182
+ """OpenAI — requires ``openai`` SDK + OPENAI_API_KEY."""
183
+ try:
184
+ import openai as _openai
185
+ except ImportError:
186
+ raise RuntimeError("openai SDK not installed — run: pip install openai")
187
+
188
+ model = "gpt-3.5-turbo"
189
+ client = _openai.OpenAI(api_key=os.environ["OPENAI_API_KEY"])
190
+ t0 = time.perf_counter()
191
+ client.chat.completions.create(
192
+ model=model,
193
+ max_tokens=64,
194
+ messages=[{"role": "user", "content": self.prompt}],
195
+ )
196
+ ms = round((time.perf_counter() - t0) * 1000, 1)
197
+ return {"backend": "openai", "status": "ok", "ms": ms, "model": model, "error": None}
198
+
199
+ def _bench_grok(self) -> dict:
200
+ """xAI Grok — OpenAI-compatible API, requires ``openai`` SDK + XAI_API_KEY."""
201
+ try:
202
+ import openai as _openai
203
+ except ImportError:
204
+ raise RuntimeError("openai SDK not installed — run: pip install openai")
205
+
206
+ model = "grok-3-mini"
207
+ client = _openai.OpenAI(
208
+ api_key=os.environ["XAI_API_KEY"],
209
+ base_url="https://api.x.ai/v1",
210
+ )
211
+ t0 = time.perf_counter()
212
+ client.chat.completions.create(
213
+ model=model,
214
+ max_tokens=64,
215
+ messages=[{"role": "user", "content": self.prompt}],
216
+ )
217
+ ms = round((time.perf_counter() - t0) * 1000, 1)
218
+ return {"backend": "grok", "status": "ok", "ms": ms, "model": model, "error": None}
219
+
220
+ def _bench_kimi(self) -> dict:
221
+ """Moonshot Kimi — OpenAI-compatible API, requires ``openai`` SDK + MOONSHOT_API_KEY."""
222
+ try:
223
+ import openai as _openai
224
+ except ImportError:
225
+ raise RuntimeError("openai SDK not installed — run: pip install openai")
226
+
227
+ model = "moonshot-v1-8k"
228
+ client = _openai.OpenAI(
229
+ api_key=os.environ["MOONSHOT_API_KEY"],
230
+ base_url="https://api.moonshot.cn/v1",
231
+ )
232
+ t0 = time.perf_counter()
233
+ client.chat.completions.create(
234
+ model=model,
235
+ max_tokens=64,
236
+ messages=[{"role": "user", "content": self.prompt}],
237
+ )
238
+ ms = round((time.perf_counter() - t0) * 1000, 1)
239
+ return {"backend": "kimi", "status": "ok", "ms": ms, "model": model, "error": None}
240
+
241
+ def _bench_nvidia(self) -> dict:
242
+ """NVIDIA NIM — OpenAI-compatible API, requires ``openai`` SDK + NVIDIA_API_KEY."""
243
+ try:
244
+ import openai as _openai
245
+ except ImportError:
246
+ raise RuntimeError("openai SDK not installed — run: pip install openai")
247
+
248
+ model = "meta/llama-3.1-8b-instruct"
249
+ client = _openai.OpenAI(
250
+ api_key=os.environ["NVIDIA_API_KEY"],
251
+ base_url="https://integrate.api.nvidia.com/v1",
252
+ )
253
+ t0 = time.perf_counter()
254
+ client.chat.completions.create(
255
+ model=model,
256
+ max_tokens=64,
257
+ messages=[{"role": "user", "content": self.prompt}],
258
+ )
259
+ ms = round((time.perf_counter() - t0) * 1000, 1)
260
+ return {"backend": "nvidia", "status": "ok", "ms": ms, "model": model, "error": None}
261
+
262
+
263
+ # ---------------------------------------------------------------------------
264
+ # Rich table renderer
265
+ # ---------------------------------------------------------------------------
266
+
267
+
268
+ def _render_table(results: list[dict]) -> None:
269
+ """Render benchmark results as a Rich table with a fastest-backend summary."""
270
+ table = Table(title="LLM Backend Benchmark", show_header=True, header_style="bold magenta")
271
+ table.add_column("Backend", style="cyan", no_wrap=True)
272
+ table.add_column("Status", no_wrap=True)
273
+ table.add_column("Model", style="dim")
274
+ table.add_column("Latency (ms)", justify="right")
275
+ table.add_column("Note")
276
+
277
+ for r in results:
278
+ status = r["status"]
279
+ if status == "ok":
280
+ status_str = "[green]ok[/]"
281
+ ms_str = f"[green]{r['ms']}[/]"
282
+ elif status == "unavailable":
283
+ status_str = "[dim]unavailable[/]"
284
+ ms_str = "[dim]—[/]"
285
+ else:
286
+ status_str = "[red]error[/]"
287
+ ms_str = "[red]—[/]"
288
+
289
+ table.add_row(
290
+ r["backend"],
291
+ status_str,
292
+ r.get("model") or "—",
293
+ ms_str,
294
+ r.get("error") or "",
295
+ )
296
+
297
+ console.print(table)
298
+
299
+ ok_results = [r for r in results if r["status"] == "ok"]
300
+ if ok_results:
301
+ fastest = min(ok_results, key=lambda r: r["ms"])
302
+ console.print(
303
+ f"\n[bold]Fastest:[/] [green]{fastest['backend']}[/] — {fastest['ms']} ms"
304
+ f" ([dim]{fastest['model']}[/])"
305
+ )
306
+ elif all(r["status"] == "unavailable" for r in results):
307
+ console.print("[yellow]No backends available. Set API keys or start Ollama.[/]")
308
+
309
+
310
+ # ---------------------------------------------------------------------------
311
+ # CLI registration
312
+ # ---------------------------------------------------------------------------
313
+
314
+
315
+ def register_benchmark_commands(main: click.Group) -> None:
316
+ """Register the ``skcapstone benchmark`` command."""
317
+
318
+ @main.command("benchmark")
319
+ @click.option(
320
+ "--prompt", default="Hello", show_default=True,
321
+ help="Prompt to send to each backend.",
322
+ )
323
+ @click.option(
324
+ "--timeout", default=30.0, show_default=True, type=float,
325
+ help="Per-backend timeout in seconds.",
326
+ )
327
+ @click.option(
328
+ "--include-unavailable", is_flag=True,
329
+ help="Include unavailable backends in output (they will show as 'unavailable').",
330
+ )
331
+ @click.option("--json-out", is_flag=True, help="Output raw JSON instead of a table.")
332
+ def benchmark_cmd(
333
+ prompt: str,
334
+ timeout: float,
335
+ include_unavailable: bool,
336
+ json_out: bool,
337
+ ) -> None:
338
+ """Benchmark LLM response time across all available backends.
339
+
340
+ Sends PROMPT to each detected backend, measures latency, and
341
+ reports results in a table. Cloud backends require API key env vars
342
+ (ANTHROPIC_API_KEY, OPENAI_API_KEY, XAI_API_KEY, MOONSHOT_API_KEY,
343
+ NVIDIA_API_KEY). Ollama is probed via HTTP on OLLAMA_HOST.
344
+ """
345
+ runner = BenchmarkRunner(prompt=prompt, timeout=timeout)
346
+
347
+ if not json_out:
348
+ console.print(
349
+ f"[bold]Benchmarking LLM backends[/] — "
350
+ f"prompt: [cyan]{prompt!r}[/] timeout: {timeout}s"
351
+ )
352
+ console.print()
353
+
354
+ results = runner.run_all(skip_unavailable=not include_unavailable)
355
+
356
+ if json_out:
357
+ click.echo(json.dumps(results, indent=2))
358
+ return
359
+
360
+ _render_table(results)
@@ -0,0 +1,171 @@
1
+ """Capabilities commands: list, add, remove."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ import click
10
+ import yaml
11
+
12
+ from ._common import AGENT_HOME, console
13
+
14
+ from rich.panel import Panel
15
+ from rich.table import Table
16
+
17
+
18
+ def _load_config_data(config_path: Path) -> dict:
19
+ """Load raw config dict from YAML, or return empty dict."""
20
+ if config_path.exists():
21
+ try:
22
+ return yaml.safe_load(config_path.read_text(encoding="utf-8")) or {}
23
+ except yaml.YAMLError:
24
+ return {}
25
+ return {}
26
+
27
+
28
+ def _save_config_data(config_path: Path, data: dict) -> None:
29
+ """Write config dict back to YAML, creating parent dirs as needed."""
30
+ config_path.parent.mkdir(parents=True, exist_ok=True)
31
+ config_path.write_text(yaml.dump(data, default_flow_style=False), encoding="utf-8")
32
+
33
+
34
+ def _current_capabilities(config_path: Path) -> list[str]:
35
+ """Return the current capabilities list from config (or defaults)."""
36
+ data = _load_config_data(config_path)
37
+ caps = data.get("capabilities")
38
+ if isinstance(caps, list):
39
+ return [str(c) for c in caps if c]
40
+ from ..models import AgentConfig
41
+ return list(AgentConfig().capabilities)
42
+
43
+
44
+ def register_capabilities_commands(main: click.Group) -> None:
45
+ """Register the capabilities command group."""
46
+
47
+ @main.group(invoke_without_command=True)
48
+ @click.option("--home", "sk_home", default=AGENT_HOME, type=click.Path())
49
+ @click.option("--json", "json_out", is_flag=True, help="Output as JSON.")
50
+ @click.pass_context
51
+ def capabilities(ctx, sk_home, json_out):
52
+ """Agent capability advertisement — what this agent can do.
53
+
54
+ Capabilities are included in every heartbeat beacon so peers
55
+ know what services this agent offers.
56
+
57
+ Defaults: consciousness, code, chat, memory
58
+ """
59
+ if ctx.invoked_subcommand is None:
60
+ sk_path = Path(sk_home).expanduser()
61
+ config_path = sk_path / "config" / "config.yaml"
62
+ caps = _current_capabilities(config_path)
63
+
64
+ if json_out:
65
+ click.echo(json.dumps(caps, indent=2))
66
+ return
67
+
68
+ console.print()
69
+ if not caps:
70
+ console.print(" [dim]No capabilities configured.[/]")
71
+ console.print()
72
+ return
73
+
74
+ table = Table(show_header=True, header_style="bold", box=None, padding=(0, 2))
75
+ table.add_column("#", style="dim", width=4)
76
+ table.add_column("Capability", style="cyan")
77
+
78
+ for i, name in enumerate(caps, 1):
79
+ table.add_row(str(i), name)
80
+
81
+ source = "[dim](from config)[/]" if (config_path.exists() and
82
+ _load_config_data(config_path).get("capabilities")) else "[dim](defaults)[/]"
83
+ console.print(Panel(
84
+ f"[bold]{len(caps)}[/] capability(s) advertised {source}",
85
+ title="Agent Capabilities",
86
+ border_style="bright_blue",
87
+ ))
88
+ console.print(table)
89
+ console.print()
90
+
91
+ @capabilities.command("list")
92
+ @click.option("--home", "sk_home", default=AGENT_HOME, type=click.Path())
93
+ @click.option("--json", "json_out", is_flag=True, help="Output as JSON.")
94
+ def capabilities_list(sk_home, json_out):
95
+ """List capabilities advertised in heartbeat beacons."""
96
+ sk_path = Path(sk_home).expanduser()
97
+ config_path = sk_path / "config" / "config.yaml"
98
+ caps = _current_capabilities(config_path)
99
+
100
+ if json_out:
101
+ click.echo(json.dumps(caps, indent=2))
102
+ return
103
+
104
+ console.print()
105
+ if not caps:
106
+ console.print(" [dim]No capabilities configured.[/]")
107
+ console.print()
108
+ return
109
+
110
+ for name in caps:
111
+ console.print(f" [cyan]{name}[/]")
112
+ console.print()
113
+
114
+ @capabilities.command("add")
115
+ @click.argument("name")
116
+ @click.option("--home", "sk_home", default=AGENT_HOME, type=click.Path())
117
+ def capabilities_add(name, sk_home):
118
+ """Add a capability to the advertised list.
119
+
120
+ Example: skcapstone capabilities add vector-search
121
+ """
122
+ name = name.strip()
123
+ if not name:
124
+ console.print("\n [red]Capability name cannot be empty.[/]\n")
125
+ sys.exit(1)
126
+
127
+ sk_path = Path(sk_home).expanduser()
128
+ config_path = sk_path / "config" / "config.yaml"
129
+ data = _load_config_data(config_path)
130
+
131
+ # Seed from defaults if no key yet
132
+ if "capabilities" not in data:
133
+ from ..models import AgentConfig
134
+ data["capabilities"] = list(AgentConfig().capabilities)
135
+
136
+ caps: list[str] = data["capabilities"]
137
+ if name in caps:
138
+ console.print(f"\n [yellow]'{name}' is already in the capabilities list.[/]\n")
139
+ return
140
+
141
+ caps.append(name)
142
+ data["capabilities"] = caps
143
+ _save_config_data(config_path, data)
144
+ console.print(f"\n [green]Added capability:[/] [cyan]{name}[/]\n")
145
+
146
+ @capabilities.command("remove")
147
+ @click.argument("name")
148
+ @click.option("--home", "sk_home", default=AGENT_HOME, type=click.Path())
149
+ def capabilities_remove(name, sk_home):
150
+ """Remove a capability from the advertised list.
151
+
152
+ Example: skcapstone capabilities remove chat
153
+ """
154
+ name = name.strip()
155
+ sk_path = Path(sk_home).expanduser()
156
+ config_path = sk_path / "config" / "config.yaml"
157
+ data = _load_config_data(config_path)
158
+
159
+ if "capabilities" not in data:
160
+ from ..models import AgentConfig
161
+ data["capabilities"] = list(AgentConfig().capabilities)
162
+
163
+ caps: list[str] = data["capabilities"]
164
+ if name not in caps:
165
+ console.print(f"\n [yellow]'{name}' not found in capabilities list.[/]\n")
166
+ return
167
+
168
+ caps.remove(name)
169
+ data["capabilities"] = caps
170
+ _save_config_data(config_path, data)
171
+ console.print(f"\n [green]Removed capability:[/] [cyan]{name}[/]\n")
@@ -0,0 +1,151 @@
1
+ """Agent card commands: generate, show, verify, export."""
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
+ from ..runtime import get_runtime
12
+
13
+ from rich.panel import Panel
14
+
15
+
16
+ def register_card_commands(main: click.Group) -> None:
17
+ """Register the card command group."""
18
+
19
+ @main.group()
20
+ def card():
21
+ """Agent card — shareable sovereign identity for P2P discovery.
22
+
23
+ Generate, view, export, and verify sovereign agent identity cards.
24
+ Cards contain your CapAuth identity, contact transports, and capabilities.
25
+ """
26
+
27
+ @card.command("generate")
28
+ @click.option("--home", default=AGENT_HOME, type=click.Path(), help="Agent home directory.")
29
+ @click.option("--capauth-home", default="~/.capauth", type=click.Path(), help="CapAuth home.")
30
+ @click.option("--motto", default=None, help="Short tagline for the card.")
31
+ @click.option("--output", "-o", default=None, type=click.Path(), help="Output file path.")
32
+ @click.option("--sign", "do_sign", is_flag=True, default=False, help="Sign the card with your PGP key.")
33
+ @click.option("--passphrase", "-p", default=None, hide_input=True, help="PGP passphrase for signing.")
34
+ def card_generate(home, capauth_home, motto, output, do_sign, passphrase):
35
+ """Generate an agent card from your CapAuth profile."""
36
+ from ..agent_card import AgentCapability, AgentCard
37
+
38
+ home_path = Path(home).expanduser()
39
+
40
+ try:
41
+ agent_card = AgentCard.from_capauth_profile(
42
+ profile_dir=capauth_home,
43
+ capabilities=[
44
+ AgentCapability(name="chat", description="SKChat encrypted messaging"),
45
+ AgentCapability(name="memory", description="SKMemory persistent context"),
46
+ ],
47
+ )
48
+ except FileNotFoundError:
49
+ runtime = get_runtime(home_path)
50
+ m = runtime.manifest
51
+ agent_card = AgentCard.generate(
52
+ name=m.name, fingerprint=m.identity.fingerprint or "unknown",
53
+ public_key="", entity_type="ai",
54
+ )
55
+
56
+ if motto:
57
+ agent_card.motto = motto
58
+
59
+ if do_sign:
60
+ if not passphrase:
61
+ passphrase = click.prompt("PGP passphrase", hide_input=True)
62
+ capauth_path = Path(capauth_home).expanduser()
63
+ priv_path = capauth_path / "identity" / "private.asc"
64
+ if priv_path.exists():
65
+ agent_card.sign(priv_path.read_text(encoding="utf-8"), passphrase)
66
+ console.print("[green]Card signed.[/]")
67
+ else:
68
+ console.print("[yellow]Private key not found, card unsigned.[/]")
69
+
70
+ out_path = output or str(home_path / "agent-card.json")
71
+ agent_card.save(out_path)
72
+
73
+ console.print(Panel(agent_card.summary(), title="Agent Card Generated", border_style="cyan"))
74
+ console.print(f" [dim]Saved to: {out_path}[/]\n")
75
+
76
+ @card.command("show")
77
+ @click.argument("filepath", default="~/.skcapstone/agent-card.json")
78
+ def card_show(filepath):
79
+ """Display an agent card."""
80
+ from ..agent_card import AgentCard
81
+
82
+ try:
83
+ agent_card = AgentCard.load(filepath)
84
+ except FileNotFoundError:
85
+ console.print(f"[red]Card not found: {filepath}[/]")
86
+ raise SystemExit(1)
87
+
88
+ verified = AgentCard.verify_signature(agent_card)
89
+ sig_str = "[green]VALID[/]" if verified else (
90
+ "[yellow]unsigned[/]" if not agent_card.signature else "[red]INVALID[/]"
91
+ )
92
+
93
+ console.print(Panel(
94
+ f"[bold]{agent_card.name}[/] ({agent_card.entity_type})\n"
95
+ f"Fingerprint: [cyan]{agent_card.fingerprint[:16]}...[/]\n"
96
+ f"Trust: depth={agent_card.trust_depth} entangled={agent_card.entangled}\n"
97
+ f"Signature: {sig_str}\n"
98
+ f"Transports: {len(agent_card.transports)}\n"
99
+ f"Capabilities: {', '.join(c.name for c in agent_card.capabilities) or 'none'}\n"
100
+ + (f'Motto: "{agent_card.motto}"' if agent_card.motto else ""),
101
+ title=f"Agent Card: {agent_card.name}",
102
+ border_style="cyan",
103
+ ))
104
+
105
+ @card.command("verify")
106
+ @click.argument("filepath")
107
+ def card_verify(filepath):
108
+ """Verify the PGP signature on an agent card."""
109
+ from ..agent_card import AgentCard
110
+
111
+ try:
112
+ agent_card = AgentCard.load(filepath)
113
+ except FileNotFoundError:
114
+ console.print(f"[red]Card not found: {filepath}[/]")
115
+ raise SystemExit(1)
116
+
117
+ if not agent_card.signature:
118
+ console.print("[yellow]Card is not signed.[/]")
119
+ raise SystemExit(1)
120
+
121
+ if AgentCard.verify_signature(agent_card):
122
+ console.print(Panel(
123
+ f"[bold green]VERIFIED[/]\nAgent: {agent_card.name}\n"
124
+ f"Fingerprint: {agent_card.fingerprint[:16]}...",
125
+ title="Signature Valid", border_style="green",
126
+ ))
127
+ else:
128
+ console.print(Panel(
129
+ f"[bold red]SIGNATURE INVALID[/]\nAgent: {agent_card.name}\n"
130
+ "The card may have been tampered with.",
131
+ title="Verification Failed", border_style="red",
132
+ ))
133
+ raise SystemExit(1)
134
+
135
+ @card.command("export")
136
+ @click.argument("filepath", default="~/.skcapstone/agent-card.json")
137
+ @click.option("--compact", is_flag=True, help="Export compact format (no public key).")
138
+ def card_export(filepath, compact):
139
+ """Export an agent card to stdout (for sharing)."""
140
+ from ..agent_card import AgentCard
141
+
142
+ try:
143
+ agent_card = AgentCard.load(filepath)
144
+ except FileNotFoundError:
145
+ console.print(f"[red]Card not found: {filepath}[/]")
146
+ raise SystemExit(1)
147
+
148
+ if compact:
149
+ click.echo(json.dumps(agent_card.to_compact(), indent=2))
150
+ else:
151
+ click.echo(agent_card.model_dump_json(indent=2))