@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,234 @@
1
+ """Test runner command — unified pytest across all ecosystem packages.
2
+
3
+ Usage:
4
+ skcapstone test # run all packages
5
+ skcapstone test --package skcomm # run one package
6
+ skcapstone test --fast # stop on first failure
7
+ skcapstone test --json-out # machine-readable JSON output
8
+ skcapstone test --verbose # pass -v to pytest
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import json
14
+ import sys
15
+ from pathlib import Path
16
+
17
+ import click
18
+
19
+ from ._common import console
20
+ from ..testrunner import ECOSYSTEM_PACKAGES, run_all_tests, TestReport, PackageResult
21
+
22
+
23
+ def _monorepo_root() -> Path:
24
+ """Locate the monorepo root (parent of the skcapstone package dir).
25
+
26
+ Walks up from this file until we find a directory that contains
27
+ the skcapstone sub-package, then returns its parent.
28
+
29
+ Returns:
30
+ Path to the monorepo root.
31
+ """
32
+ # src/skcapstone/cli/test_cmd.py → go up 4 levels
33
+ candidate = Path(__file__).resolve().parent.parent.parent.parent.parent
34
+ # candidate is now smilintux-org/skcapstone/src → go one more up to skcapstone pkg root,
35
+ # then one more up to monorepo root
36
+ # Path layout: monorepo/skcapstone/src/skcapstone/cli/test_cmd.py
37
+ # parents[0] = cli/, [1] = skcapstone (pkg), [2] = src/, [3] = skcapstone (repo), [4] = monorepo
38
+ monorepo = Path(__file__).resolve().parents[4]
39
+ if (monorepo / "skcapstone").is_dir():
40
+ return monorepo
41
+ # Fallback: cwd
42
+ return Path.cwd()
43
+
44
+
45
+ def _render_table(report: TestReport) -> None:
46
+ """Print a Rich table summarising per-package results.
47
+
48
+ Args:
49
+ report: Completed test report.
50
+ """
51
+ from rich.table import Table
52
+ from rich.panel import Panel
53
+
54
+ table = Table(title="Ecosystem Test Results", show_lines=False, expand=True)
55
+ table.add_column("Package", style="bold", min_width=16)
56
+ table.add_column("Status", min_width=10)
57
+ table.add_column("Passed", justify="right", min_width=7)
58
+ table.add_column("Failed", justify="right", min_width=7)
59
+ table.add_column("Errors", justify="right", min_width=7)
60
+ table.add_column("Skipped", justify="right", min_width=7)
61
+ table.add_column("Duration", justify="right", min_width=9)
62
+
63
+ for r in report.results:
64
+ if not r.available:
65
+ table.add_row(
66
+ r.name,
67
+ "[dim]N/A[/]",
68
+ "-", "-", "-", "-",
69
+ "[dim]—[/]",
70
+ )
71
+ continue
72
+
73
+ if r.success:
74
+ status = "[bold green]PASS[/]"
75
+ else:
76
+ status = "[bold red]FAIL[/]"
77
+
78
+ failed_str = f"[red]{r.failed}[/]" if r.failed else "0"
79
+ errors_str = f"[red]{r.errors}[/]" if r.errors else "0"
80
+ skipped_str = f"[yellow]{r.skipped}[/]" if r.skipped else "0"
81
+
82
+ table.add_row(
83
+ r.name,
84
+ status,
85
+ str(r.passed),
86
+ failed_str,
87
+ errors_str,
88
+ skipped_str,
89
+ f"{r.duration_s:.1f}s",
90
+ )
91
+
92
+ console.print()
93
+ console.print(table)
94
+
95
+ # Footer summary
96
+ total_pass = report.total_passed
97
+ total_fail = report.total_failed
98
+ total_err = report.total_errors
99
+ pkgs_tested = report.packages_tested
100
+ overall_duration = f"{report.duration_s:.1f}s"
101
+
102
+ if report.all_passed:
103
+ summary_color = "green"
104
+ verdict = "ALL PASSED"
105
+ else:
106
+ summary_color = "red"
107
+ verdict = "FAILURES DETECTED"
108
+
109
+ console.print(
110
+ Panel(
111
+ f"[bold {summary_color}]{verdict}[/] "
112
+ f"[green]{total_pass} passed[/] "
113
+ + (f"[red]{total_fail} failed[/] " if total_fail else "")
114
+ + (f"[red]{total_err} errors[/] " if total_err else "")
115
+ + f"across {pkgs_tested} package(s) "
116
+ f"in {overall_duration}",
117
+ border_style=summary_color,
118
+ expand=False,
119
+ )
120
+ )
121
+ console.print()
122
+
123
+
124
+ def _render_failures(report: TestReport) -> None:
125
+ """Print abbreviated pytest output for any failed package.
126
+
127
+ Args:
128
+ report: Completed test report.
129
+ """
130
+ for r in report.results:
131
+ if r.available and not r.success and r.output.strip():
132
+ console.print(f"\n[bold red]── {r.name} output ──[/]\n")
133
+ console.print(r.output)
134
+
135
+
136
+ def register_test_commands(main: click.Group) -> None:
137
+ """Register the `skcapstone test` command.
138
+
139
+ Args:
140
+ main: Root Click group.
141
+ """
142
+
143
+ @main.command("test")
144
+ @click.option(
145
+ "--package", "-p",
146
+ "packages",
147
+ multiple=True,
148
+ metavar="NAME",
149
+ help="Restrict to one or more packages (repeat for multiple).",
150
+ )
151
+ @click.option(
152
+ "--fast", is_flag=True,
153
+ help="Stop after the first failing package.",
154
+ )
155
+ @click.option(
156
+ "--verbose", "-v", is_flag=True,
157
+ help="Pass -v to pytest for detailed output.",
158
+ )
159
+ @click.option(
160
+ "--json-out", is_flag=True,
161
+ help="Emit machine-readable JSON instead of a Rich table.",
162
+ )
163
+ @click.option(
164
+ "--timeout", default=120, show_default=True, type=int,
165
+ help="Per-package timeout in seconds.",
166
+ )
167
+ @click.option(
168
+ "--root", default=None, type=click.Path(),
169
+ help="Override monorepo root path (auto-detected by default).",
170
+ )
171
+ @click.option(
172
+ "--show-output", is_flag=True,
173
+ help="Print pytest output for failing packages.",
174
+ )
175
+ def test_cmd(
176
+ packages: tuple[str, ...],
177
+ fast: bool,
178
+ verbose: bool,
179
+ json_out: bool,
180
+ timeout: int,
181
+ root: str | None,
182
+ show_output: bool,
183
+ ) -> None:
184
+ """Run pytest across all ecosystem packages and show a summary table.
185
+
186
+ Discovers packages in the monorepo (skcapstone, capauth, skcomm,
187
+ skchat, skmemory, cloud9-python) and runs their test suites in
188
+ sequence, then renders a consolidated Rich table.
189
+
190
+ \b
191
+ Examples:
192
+ skcapstone test
193
+ skcapstone test --package skcomm
194
+ skcapstone test -p skcomm -p skchat
195
+ skcapstone test --fast --verbose
196
+ skcapstone test --json-out | jq .
197
+ """
198
+ monorepo = Path(root).expanduser() if root else _monorepo_root()
199
+
200
+ pkg_filter = list(packages) if packages else None
201
+
202
+ valid_names = {p["name"] for p in ECOSYSTEM_PACKAGES}
203
+ if pkg_filter:
204
+ invalid = [n for n in pkg_filter if n not in valid_names]
205
+ if invalid:
206
+ console.print(
207
+ f"[red]Unknown package(s): {', '.join(invalid)}[/]\n"
208
+ f"Valid: {', '.join(sorted(valid_names))}"
209
+ )
210
+ sys.exit(1)
211
+
212
+ if not json_out:
213
+ names_str = ", ".join(pkg_filter) if pkg_filter else "all packages"
214
+ console.print(f"\n [cyan]Running tests for:[/] {names_str}")
215
+ console.print(f" [dim]Monorepo root: {monorepo}[/]\n")
216
+
217
+ report = run_all_tests(
218
+ monorepo_root=monorepo,
219
+ packages=pkg_filter,
220
+ fail_fast=fast,
221
+ verbose=verbose,
222
+ timeout=timeout,
223
+ )
224
+
225
+ if json_out:
226
+ click.echo(json.dumps(report.to_dict(), indent=2))
227
+ sys.exit(0 if report.all_passed else 1)
228
+
229
+ _render_table(report)
230
+
231
+ if show_output:
232
+ _render_failures(report)
233
+
234
+ sys.exit(0 if report.all_passed else 1)
@@ -0,0 +1,253 @@
1
+ """test-connection command — ping a peer via SKComm and measure latency.
2
+
3
+ Usage:
4
+ skcapstone test-connection <peer>
5
+ skcapstone test-connection <peer> --timeout 5
6
+ skcapstone test-connection <peer> --count 3
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ import time
13
+ import uuid
14
+ from pathlib import Path
15
+
16
+ import click
17
+
18
+ from ._common import AGENT_HOME, console
19
+ from ..chat import AgentChat
20
+
21
+
22
+ # Payload type markers
23
+ _PING_KEY = "skchat_ping"
24
+ _PONG_KEY = "skchat_pong"
25
+
26
+
27
+ def _make_ping_payload(nonce: str, sender: str) -> str:
28
+ """Serialize a ping message.
29
+
30
+ Args:
31
+ nonce: Unique identifier for this ping/pong round-trip.
32
+ sender: Sending agent name.
33
+
34
+ Returns:
35
+ str: JSON payload string.
36
+ """
37
+ return json.dumps({_PING_KEY: True, "nonce": nonce, "sender": sender})
38
+
39
+
40
+ def _is_pong_for(payload: str, nonce: str) -> bool:
41
+ """Return True if *payload* is a pong matching *nonce*.
42
+
43
+ Args:
44
+ payload: Raw message content string.
45
+ nonce: The nonce sent in the corresponding ping.
46
+
47
+ Returns:
48
+ bool: True when the payload is a valid matching pong.
49
+ """
50
+ try:
51
+ data = json.loads(payload)
52
+ return bool(data.get(_PONG_KEY)) and data.get("nonce") == nonce
53
+ except (json.JSONDecodeError, TypeError, AttributeError):
54
+ return False
55
+
56
+
57
+ def _is_ping(payload: str) -> tuple[bool, str, str]:
58
+ """Parse an incoming ping payload.
59
+
60
+ Args:
61
+ payload: Raw message content string.
62
+
63
+ Returns:
64
+ tuple: (is_ping, nonce, sender) — nonce/sender are empty strings
65
+ when the payload is not a ping.
66
+ """
67
+ try:
68
+ data = json.loads(payload)
69
+ if data.get(_PING_KEY):
70
+ return True, data.get("nonce", ""), data.get("sender", "")
71
+ except (json.JSONDecodeError, TypeError, AttributeError):
72
+ pass
73
+ return False, "", ""
74
+
75
+
76
+ def _make_pong_payload(nonce: str, sender: str) -> str:
77
+ """Serialize a pong reply.
78
+
79
+ Args:
80
+ nonce: The nonce received from the ping.
81
+ sender: Replying agent name.
82
+
83
+ Returns:
84
+ str: JSON payload string.
85
+ """
86
+ return json.dumps({_PONG_KEY: True, "nonce": nonce, "sender": sender})
87
+
88
+
89
+ def ping_peer(
90
+ peer: str,
91
+ home: Path,
92
+ identity: str,
93
+ timeout: float = 10.0,
94
+ ) -> dict:
95
+ """Send a ping to *peer* and wait for a matching pong.
96
+
97
+ Sends a structured ping message via AgentChat and polls the inbox
98
+ for a pong response with a matching nonce. Times out after *timeout*
99
+ seconds.
100
+
101
+ Args:
102
+ peer: Peer agent name or identity.
103
+ home: Agent home directory.
104
+ identity: Local agent name used as sender.
105
+ timeout: Maximum seconds to wait for a pong.
106
+
107
+ Returns:
108
+ dict with keys:
109
+ - ``reachable`` (bool): True if pong was received in time.
110
+ - ``latency_ms`` (float | None): Round-trip time in milliseconds,
111
+ or None on timeout.
112
+ - ``transport`` (str | None): Transport that delivered the ping.
113
+ - ``error`` (str | None): Error message, if any.
114
+ """
115
+ result: dict = {
116
+ "reachable": False,
117
+ "latency_ms": None,
118
+ "transport": None,
119
+ "error": None,
120
+ }
121
+
122
+ nonce = str(uuid.uuid4())
123
+ agent_chat = AgentChat(home=home, identity=identity)
124
+
125
+ # Send the ping
126
+ t0 = time.monotonic()
127
+ send_result = agent_chat.send(peer, _make_ping_payload(nonce, identity))
128
+
129
+ if not send_result.get("delivered") and not send_result.get("stored"):
130
+ result["error"] = send_result.get("error") or "send failed (no transport)"
131
+ return result
132
+
133
+ result["transport"] = send_result.get("transport")
134
+
135
+ # If we only stored locally (no live transport), report accordingly
136
+ if not send_result.get("delivered"):
137
+ result["error"] = "ping stored locally — no live transport available"
138
+ return result
139
+
140
+ # Poll for pong
141
+ deadline = t0 + timeout
142
+ poll_interval = 0.25 # seconds
143
+
144
+ while time.monotonic() < deadline:
145
+ messages = agent_chat.receive(limit=20)
146
+ for msg in messages:
147
+ content = msg.get("content", "")
148
+ sender_name = msg.get("sender", "")
149
+ if sender_name == peer and _is_pong_for(content, nonce):
150
+ elapsed_ms = (time.monotonic() - t0) * 1000.0
151
+ result["reachable"] = True
152
+ result["latency_ms"] = round(elapsed_ms, 2)
153
+ return result
154
+ time.sleep(poll_interval)
155
+
156
+ result["error"] = f"timeout after {timeout:.0f}s — no pong received"
157
+ return result
158
+
159
+
160
+ def register_test_connection_commands(main: click.Group) -> None:
161
+ """Register the test-connection command on *main*."""
162
+
163
+ @main.command("test-connection")
164
+ @click.argument("peer")
165
+ @click.option(
166
+ "--timeout", "-t",
167
+ default=10.0,
168
+ show_default=True,
169
+ help="Seconds to wait for a pong response.",
170
+ )
171
+ @click.option(
172
+ "--count", "-c",
173
+ default=1,
174
+ show_default=True,
175
+ help="Number of pings to send (reports min/avg/max when > 1).",
176
+ )
177
+ @click.option("--home", default=AGENT_HOME, type=click.Path())
178
+ def test_connection(peer: str, timeout: float, count: int, home: str) -> None:
179
+ """Test connectivity to a peer by sending a ping via SKComm.
180
+
181
+ Sends a ping message, waits for the peer to reply with a pong,
182
+ and reports the round-trip latency. Exits with code 1 when the
183
+ peer is unreachable.
184
+
185
+ \b
186
+ Examples:
187
+ skcapstone test-connection lumina
188
+ skcapstone test-connection opus --timeout 5
189
+ skcapstone test-connection jarvis --count 3
190
+ """
191
+ from ..runtime import get_runtime
192
+
193
+ home_path = Path(home).expanduser()
194
+ try:
195
+ runtime = get_runtime(home_path)
196
+ identity = runtime.manifest.name or "unknown"
197
+ except Exception:
198
+ identity = "unknown"
199
+
200
+ console.print()
201
+ console.print(
202
+ f" PING [cyan]{peer}[/] (timeout={timeout:.0f}s, count={count})"
203
+ )
204
+ console.print()
205
+
206
+ latencies: list[float] = []
207
+ last_error: str | None = None
208
+ last_transport: str | None = None
209
+
210
+ for i in range(count):
211
+ result = ping_peer(peer, home_path, identity, timeout=timeout)
212
+ last_transport = result.get("transport") or last_transport
213
+
214
+ if result["reachable"]:
215
+ ms = result["latency_ms"]
216
+ latencies.append(ms)
217
+ console.print(
218
+ f" [{i + 1}/{count}] [green]pong from {peer}[/] "
219
+ f"latency=[bold]{ms:.1f} ms[/]"
220
+ )
221
+ else:
222
+ last_error = result.get("error") or "unreachable"
223
+ console.print(
224
+ f" [{i + 1}/{count}] [red]no pong[/] {last_error}"
225
+ )
226
+
227
+ console.print()
228
+
229
+ if latencies:
230
+ if count > 1:
231
+ lo = min(latencies)
232
+ hi = max(latencies)
233
+ avg = sum(latencies) / len(latencies)
234
+ console.print(
235
+ f" [bold green]REACHABLE[/] "
236
+ f"min={lo:.1f}ms avg={avg:.1f}ms max={hi:.1f}ms "
237
+ f"({len(latencies)}/{count} received)"
238
+ )
239
+ else:
240
+ console.print(
241
+ f" [bold green]REACHABLE[/] "
242
+ f"latency={latencies[0]:.1f} ms"
243
+ )
244
+ if last_transport:
245
+ console.print(f" transport: [dim]{last_transport}[/]")
246
+ else:
247
+ console.print(
248
+ f" [bold red]UNREACHABLE[/] {last_error or 'no pong received'}"
249
+ )
250
+ console.print()
251
+ raise SystemExit(1)
252
+
253
+ console.print()
@@ -0,0 +1,207 @@
1
+ """Capability token commands: issue, list, verify, revoke, export."""
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
+ from ._validators import validate_agent_name, validate_task_id
12
+ from ..pillars.security import audit_event
13
+
14
+ from rich.table import Table
15
+
16
+
17
+ def register_token_commands(main: click.Group) -> None:
18
+ """Register the token command group."""
19
+
20
+ @main.group()
21
+ def token():
22
+ """Manage capability tokens.
23
+
24
+ Issue, verify, list, and revoke PGP-signed capability
25
+ tokens for fine-grained agent authorization.
26
+ """
27
+
28
+ @token.command("issue")
29
+ @click.option("--home", default=AGENT_HOME, type=click.Path())
30
+ @click.option("--subject", required=True, help="Who the token is for.")
31
+ @click.option("--cap", multiple=True, required=True, help="Capabilities to grant.")
32
+ @click.option("--ttl", default=24, help="Hours until expiry (0 = no expiry).")
33
+ @click.option("--type", "token_type", default="capability", help="Token type.")
34
+ @click.option("--no-sign", is_flag=True, help="Skip PGP signing.")
35
+ def token_issue(home, subject, cap, ttl, token_type, no_sign):
36
+ """Issue a new capability token."""
37
+ from ..tokens import TokenType, issue_token
38
+
39
+ validate_agent_name(subject)
40
+
41
+ home_path = Path(home).expanduser()
42
+ if not home_path.exists():
43
+ console.print("[bold red]No agent found.[/] Run skcapstone init first.")
44
+ sys.exit(1)
45
+
46
+ try:
47
+ tt = TokenType(token_type)
48
+ except ValueError:
49
+ console.print(f"[red]Invalid token type:[/] {token_type}")
50
+ sys.exit(1)
51
+
52
+ ttl_hours = ttl if ttl > 0 else None
53
+ capabilities = list(cap)
54
+
55
+ console.print(f"\n Issuing [cyan]{tt.value}[/] token for [bold]{subject}[/]...")
56
+ signed = issue_token(
57
+ home=home_path, subject=subject, capabilities=capabilities,
58
+ token_type=tt, ttl_hours=ttl_hours, sign=not no_sign,
59
+ )
60
+
61
+ console.print(f" [green]Token issued:[/] {signed.payload.token_id[:16]}...")
62
+ console.print(f" Capabilities: {', '.join(capabilities)}")
63
+ if signed.payload.expires_at:
64
+ console.print(f" Expires: {signed.payload.expires_at.isoformat()}")
65
+ else:
66
+ console.print(" Expires: [yellow]never[/]")
67
+ if signed.signature:
68
+ console.print(" [green]PGP signed[/]")
69
+ else:
70
+ console.print(" [yellow]Unsigned[/]")
71
+
72
+ audit_event(home_path, "TOKEN_ISSUE", f"Token {signed.payload.token_id[:16]} for {subject}")
73
+ console.print()
74
+
75
+ @token.command("list")
76
+ @click.option("--home", default=AGENT_HOME, type=click.Path())
77
+ def token_list(home):
78
+ """List all issued tokens."""
79
+ from ..tokens import is_revoked, list_tokens
80
+
81
+ home_path = Path(home).expanduser()
82
+ if not home_path.exists():
83
+ console.print("[bold red]No agent found.[/]")
84
+ sys.exit(1)
85
+
86
+ tokens = list_tokens(home_path)
87
+ if not tokens:
88
+ console.print("\n [dim]No tokens issued yet.[/]\n")
89
+ return
90
+
91
+ table = Table(title="Capability Tokens", show_lines=True)
92
+ table.add_column("ID", style="cyan", max_width=16)
93
+ table.add_column("Type", style="bold")
94
+ table.add_column("Subject")
95
+ table.add_column("Capabilities")
96
+ table.add_column("Status")
97
+ table.add_column("Expires")
98
+
99
+ for t in tokens:
100
+ p = t.payload
101
+ revoked = is_revoked(home_path, p.token_id)
102
+ if revoked:
103
+ st = "[red]REVOKED[/]"
104
+ elif p.is_expired:
105
+ st = "[yellow]EXPIRED[/]"
106
+ elif t.signature:
107
+ st = "[green]SIGNED[/]"
108
+ else:
109
+ st = "[dim]UNSIGNED[/]"
110
+
111
+ exp_str = p.expires_at.strftime("%m/%d %H:%M") if p.expires_at else "never"
112
+ table.add_row(p.token_id[:16], p.token_type.value, p.subject,
113
+ ", ".join(p.capabilities), st, exp_str)
114
+
115
+ console.print()
116
+ console.print(table)
117
+ console.print()
118
+
119
+ @token.command("verify")
120
+ @click.argument("token_id")
121
+ @click.option("--home", default=AGENT_HOME, type=click.Path())
122
+ def token_verify(token_id, home):
123
+ """Verify a token's signature and validity."""
124
+ from ..tokens import is_revoked, list_tokens, verify_token
125
+
126
+ validate_task_id(token_id) # token IDs are hex UUIDs
127
+
128
+ home_path = Path(home).expanduser()
129
+ tokens = list_tokens(home_path)
130
+
131
+ target = None
132
+ for t in tokens:
133
+ if t.payload.token_id.startswith(token_id):
134
+ target = t
135
+ break
136
+
137
+ if not target:
138
+ console.print(f"[red]Token not found:[/] {token_id}")
139
+ sys.exit(1)
140
+
141
+ if is_revoked(home_path, target.payload.token_id):
142
+ console.print(f"\n [red]REVOKED[/] Token {token_id[:16]} has been revoked.\n")
143
+ sys.exit(1)
144
+
145
+ valid = verify_token(target, home_path)
146
+ if valid:
147
+ console.print(f"\n [green]VALID[/] Token {token_id[:16]}")
148
+ console.print(f" Subject: {target.payload.subject}")
149
+ console.print(f" Capabilities: {', '.join(target.payload.capabilities)}")
150
+ else:
151
+ console.print(f"\n [red]INVALID[/] Token {token_id[:16]}")
152
+ if target.payload.is_expired:
153
+ console.print(" Reason: expired")
154
+ else:
155
+ console.print(" Reason: signature verification failed")
156
+ console.print()
157
+
158
+ @token.command("revoke")
159
+ @click.argument("token_id")
160
+ @click.option("--home", default=AGENT_HOME, type=click.Path())
161
+ def token_revoke(token_id, home):
162
+ """Revoke a previously issued token."""
163
+ from ..tokens import list_tokens, revoke_token
164
+
165
+ validate_task_id(token_id) # token IDs are hex UUIDs
166
+
167
+ home_path = Path(home).expanduser()
168
+ tokens = list_tokens(home_path)
169
+
170
+ full_id = None
171
+ for t in tokens:
172
+ if t.payload.token_id.startswith(token_id):
173
+ full_id = t.payload.token_id
174
+ break
175
+
176
+ if not full_id:
177
+ console.print(f"[red]Token not found:[/] {token_id}")
178
+ sys.exit(1)
179
+
180
+ revoke_token(home_path, full_id)
181
+ console.print(f"\n [red]REVOKED[/] Token {token_id[:16]}...")
182
+ audit_event(home_path, "TOKEN_REVOKE", f"Token {token_id[:16]} revoked")
183
+ console.print()
184
+
185
+ @token.command("export")
186
+ @click.argument("token_id")
187
+ @click.option("--home", default=AGENT_HOME, type=click.Path())
188
+ def token_export(token_id, home):
189
+ """Export a token as portable JSON."""
190
+ from ..tokens import export_token, list_tokens
191
+
192
+ validate_task_id(token_id) # token IDs are hex UUIDs
193
+
194
+ home_path = Path(home).expanduser()
195
+ tokens = list_tokens(home_path)
196
+
197
+ target = None
198
+ for t in tokens:
199
+ if t.payload.token_id.startswith(token_id):
200
+ target = t
201
+ break
202
+
203
+ if not target:
204
+ console.print(f"[red]Token not found:[/] {token_id}")
205
+ sys.exit(1)
206
+
207
+ console.print(export_token(target))