@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,136 @@
1
+ """Metrics command: show today's consciousness loop stats."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from datetime import datetime, timezone
7
+ from pathlib import Path
8
+
9
+ import click
10
+
11
+ from ._common import AGENT_HOME, console
12
+
13
+
14
+ def register_metrics_commands(main: click.Group) -> None:
15
+ """Register the ``skcapstone metrics`` command."""
16
+
17
+ @main.command("metrics")
18
+ @click.option("--home", default=AGENT_HOME, type=click.Path(), help="Agent home directory.")
19
+ @click.option("--port", default=7777, help="Daemon API port (default: 7777).")
20
+ @click.option("--json-out", is_flag=True, help="Output raw JSON.")
21
+ @click.option("--date", "date_str", default=None, help="Show metrics for a specific date (YYYY-MM-DD).")
22
+ def metrics_cmd(home: str, port: int, json_out: bool, date_str: str | None):
23
+ """Show today's consciousness loop metrics.
24
+
25
+ Tries the running daemon first (GET /api/v1/metrics).
26
+ Falls back to reading the persisted daily JSON file.
27
+ """
28
+ data = _fetch_from_daemon(port)
29
+ if data is None:
30
+ data = _read_from_file(Path(home).expanduser(), date_str)
31
+
32
+ if data is None:
33
+ if json_out:
34
+ click.echo(json.dumps({"error": "No metrics available"}))
35
+ else:
36
+ console.print("[yellow]No metrics found.[/] Start the daemon or run a test message first.")
37
+ return
38
+
39
+ if json_out:
40
+ click.echo(json.dumps(data, indent=2))
41
+ return
42
+
43
+ _print_metrics(data)
44
+
45
+
46
+ def _fetch_from_daemon(port: int) -> dict | None:
47
+ """Try GET /api/v1/metrics from the running daemon."""
48
+ import urllib.request
49
+ import urllib.error
50
+
51
+ try:
52
+ url = f"http://127.0.0.1:{port}/api/v1/metrics"
53
+ with urllib.request.urlopen(url, timeout=2) as resp:
54
+ return json.loads(resp.read().decode("utf-8"))
55
+ except Exception:
56
+ return None
57
+
58
+
59
+ def _read_from_file(home: Path, date_str: str | None) -> dict | None:
60
+ """Read a daily metrics JSON from disk."""
61
+ if date_str is None:
62
+ date_str = datetime.now(timezone.utc).strftime("%Y-%m-%d")
63
+ path = home / "metrics" / "daily" / f"{date_str}.json"
64
+ if not path.exists():
65
+ return None
66
+ try:
67
+ return json.loads(path.read_text(encoding="utf-8"))
68
+ except Exception:
69
+ return None
70
+
71
+
72
+ def _print_metrics(data: dict) -> None:
73
+ """Render metrics in a rich panel."""
74
+ from rich.panel import Panel
75
+ from rich.table import Table
76
+
77
+ date = data.get("date", "?")
78
+ msgs = data.get("messages_processed", 0)
79
+ responses = data.get("responses_sent", 0)
80
+ errors = data.get("errors", 0)
81
+ rt = data.get("response_time_ms", {})
82
+
83
+ error_color = "red" if errors > 0 else "green"
84
+ rt_str = (
85
+ f"min={rt.get('min', 0):.0f}ms "
86
+ f"avg={rt.get('avg', 0):.0f}ms "
87
+ f"p99={rt.get('p99', 0):.0f}ms "
88
+ f"max={rt.get('max', 0):.0f}ms "
89
+ f"n={rt.get('count', 0)}"
90
+ ) if rt.get("count", 0) > 0 else "[dim]no data[/]"
91
+
92
+ summary_lines = [
93
+ f"[bold]Date:[/] {date}",
94
+ f"[bold]Messages:[/] {msgs}",
95
+ f"[bold]Responses:[/] {responses}",
96
+ f"[bold]Errors:[/] [{error_color}]{errors}[/]",
97
+ f"[bold]Response time:[/] {rt_str}",
98
+ ]
99
+ console.print()
100
+ console.print(Panel(
101
+ "\n".join(summary_lines),
102
+ title="[cyan]Consciousness Metrics[/]",
103
+ border_style="cyan",
104
+ ))
105
+
106
+ # Backend usage table
107
+ backend_usage = data.get("backend_usage", {})
108
+ if backend_usage:
109
+ table = Table(title="Backend Usage", box=None, padding=(0, 2))
110
+ table.add_column("Backend", style="bold")
111
+ table.add_column("Requests", justify="right")
112
+ for bk, count in sorted(backend_usage.items(), key=lambda x: -x[1]):
113
+ table.add_row(bk, str(count))
114
+ console.print(table)
115
+
116
+ # Tier usage table
117
+ tier_usage = data.get("tier_usage", {})
118
+ if tier_usage:
119
+ table = Table(title="Tier Usage", box=None, padding=(0, 2))
120
+ table.add_column("Tier", style="bold")
121
+ table.add_column("Requests", justify="right")
122
+ for tier, count in sorted(tier_usage.items(), key=lambda x: -x[1]):
123
+ table.add_row(tier, str(count))
124
+ console.print(table)
125
+
126
+ # Per-peer message counts (top 10)
127
+ peer_counts = data.get("messages_per_peer", {})
128
+ if peer_counts:
129
+ table = Table(title="Messages per Peer (top 10)", box=None, padding=(0, 2))
130
+ table.add_column("Peer", style="bold cyan")
131
+ table.add_column("Messages", justify="right")
132
+ for peer, count in sorted(peer_counts.items(), key=lambda x: -x[1])[:10]:
133
+ table.add_row(peer, str(count))
134
+ console.print(table)
135
+
136
+ console.print()
@@ -0,0 +1,62 @@
1
+ """CLI commands for multi-agent migration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import click
6
+
7
+ from ._common import console
8
+
9
+
10
+ def register_migrate_commands(cli: click.Group) -> None:
11
+ """Register the 'migrate' command group."""
12
+ cli.add_command(migrate_cmd)
13
+
14
+
15
+ @click.command("migrate")
16
+ @click.option("--agent", required=True, help="Agent name (e.g., opus)")
17
+ @click.option("--dry-run", is_flag=True, help="Show what would happen without moving")
18
+ @click.option("--root", default="~/.skcapstone", help="skcapstone root directory")
19
+ def migrate_cmd(agent: str, dry_run: bool, root: str) -> None:
20
+ """Migrate to multi-agent household layout.
21
+
22
+ Moves per-agent data into agents/{name}/ and creates symlinks
23
+ at old paths for backward compatibility.
24
+ """
25
+ from pathlib import Path
26
+
27
+ from ..migrate_multi_agent import migrate_to_multi_agent
28
+
29
+ results = migrate_to_multi_agent(
30
+ root=Path(root),
31
+ agent_name=agent,
32
+ dry_run=dry_run,
33
+ )
34
+
35
+ if dry_run:
36
+ console.print("[yellow]DRY RUN[/] — no files will be moved\n")
37
+
38
+ if results["moved"]:
39
+ console.print("[green]Moved:[/]")
40
+ for item in results["moved"]:
41
+ console.print(f" {item}")
42
+
43
+ if results["symlinks_created"]:
44
+ console.print("\n[cyan]Symlinks created:[/]")
45
+ for link in results["symlinks_created"]:
46
+ console.print(f" {link}")
47
+
48
+ if results["skipped"]:
49
+ console.print("\n[dim]Skipped:[/]")
50
+ for item in results["skipped"]:
51
+ console.print(f" {item}")
52
+
53
+ if results["errors"]:
54
+ console.print("\n[red]Errors:[/]")
55
+ for err in results["errors"]:
56
+ console.print(f" {err}")
57
+
58
+ if not results["moved"] and not dry_run:
59
+ console.print("[dim]Nothing to migrate.[/]")
60
+ elif not dry_run:
61
+ console.print(f"\n[green]Migration complete.[/] Agent home: {results['agent_home']}")
62
+ console.print(f"[dim]Use: SKCAPSTONE_AGENT={agent} skcapstone status[/]")
@@ -0,0 +1,144 @@
1
+ """Mood command: display the agent's current emotional state."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+
8
+ import click
9
+
10
+ from ._common import AGENT_HOME, console
11
+
12
+
13
+ def register_mood_commands(main: click.Group) -> None:
14
+ """Register the ``skcapstone mood`` command."""
15
+
16
+ @main.command("mood")
17
+ @click.option(
18
+ "--home", default=AGENT_HOME, type=click.Path(),
19
+ help="Agent home directory.",
20
+ )
21
+ @click.option("--json-out", is_flag=True, help="Output raw JSON.")
22
+ @click.option(
23
+ "--update", is_flag=True,
24
+ help="Refresh from persisted daily metrics before displaying.",
25
+ )
26
+ def mood_cmd(home: str, json_out: bool, update: bool) -> None:
27
+ """Show the agent's current emotional state.
28
+
29
+ Mood is derived from three interaction pattern factors:
30
+
31
+ \b
32
+ Success — response success rate (happy / frustrated)
33
+ Social — message frequency (social / isolated)
34
+ Stress — error rate (calm / stressed)
35
+ """
36
+ from ..mood import MoodTracker
37
+
38
+ home_path = Path(home).expanduser()
39
+ tracker = MoodTracker(home=home_path)
40
+
41
+ if update:
42
+ try:
43
+ from ..metrics import ConsciousnessMetrics
44
+ metrics = ConsciousnessMetrics(home=home_path, persist_interval=0)
45
+ tracker.update_from_metrics(metrics)
46
+ except Exception as exc:
47
+ if not json_out:
48
+ console.print(f"[yellow]Warning: could not refresh metrics: {exc}[/]")
49
+
50
+ snap = tracker.snapshot
51
+
52
+ if json_out:
53
+ click.echo(snap.model_dump_json(indent=2))
54
+ return
55
+
56
+ _print_mood(snap)
57
+
58
+
59
+ # ---------------------------------------------------------------------------
60
+ # Rich rendering
61
+ # ---------------------------------------------------------------------------
62
+
63
+ _SUMMARY_COLORS: dict[str, str] = {
64
+ "flourishing": "bright_green",
65
+ "happy": "green",
66
+ "content": "cyan",
67
+ "neutral": "white",
68
+ "isolated": "dim",
69
+ "tense": "yellow",
70
+ "frustrated": "red",
71
+ "stressed": "bold red",
72
+ }
73
+
74
+ _SUCCESS_COLORS: dict[str, str] = {
75
+ "happy": "green",
76
+ "content": "cyan",
77
+ "neutral": "white",
78
+ "frustrated": "red",
79
+ }
80
+
81
+ _SOCIAL_COLORS: dict[str, str] = {
82
+ "social": "bright_cyan",
83
+ "active": "green",
84
+ "quiet": "white",
85
+ "isolated": "dim",
86
+ }
87
+
88
+ _STRESS_COLORS: dict[str, str] = {
89
+ "calm": "green",
90
+ "relaxed": "cyan",
91
+ "tense": "yellow",
92
+ "stressed": "bold red",
93
+ }
94
+
95
+
96
+ def _print_mood(snap) -> None:
97
+ """Render mood snapshot with Rich.
98
+
99
+ Args:
100
+ snap: :class:`~skcapstone.mood.MoodSnapshot` to display.
101
+ """
102
+ from rich.panel import Panel
103
+ from rich.table import Table
104
+
105
+ border_color = _SUMMARY_COLORS.get(snap.summary, "white")
106
+ summary_color = _SUMMARY_COLORS.get(snap.summary, "white")
107
+
108
+ header = f"[{summary_color}]{snap.summary.upper()}[/]"
109
+ if snap.updated_at:
110
+ header += f" [dim]{snap.updated_at[:19]}Z[/]"
111
+
112
+ table = Table(show_header=True, header_style="bold", box=None, padding=(0, 2))
113
+ table.add_column("Dimension", style="bold", min_width=10)
114
+ table.add_column("State", min_width=12)
115
+ table.add_column("Detail", style="dim")
116
+
117
+ # Success row
118
+ sc = _SUCCESS_COLORS.get(snap.success_mood, "white")
119
+ table.add_row(
120
+ "Success",
121
+ f"[{sc}]{snap.success_mood}[/]",
122
+ f"{snap.responses_sent}/{snap.messages_processed} responses ({snap.success_rate:.0%})",
123
+ )
124
+
125
+ # Social row
126
+ soc = _SOCIAL_COLORS.get(snap.social_mood, "white")
127
+ table.add_row(
128
+ "Social",
129
+ f"[{soc}]{snap.social_mood}[/]",
130
+ f"{snap.messages_per_hour:.1f} msgs/hr (window: {snap.window_hours}h)",
131
+ )
132
+
133
+ # Stress row
134
+ stc = _STRESS_COLORS.get(snap.stress_mood, "white")
135
+ table.add_row(
136
+ "Stress",
137
+ f"[{stc}]{snap.stress_mood}[/]",
138
+ f"{snap.errors} errors ({snap.error_rate:.0%} error rate)",
139
+ )
140
+
141
+ console.print()
142
+ console.print(Panel(header, title="[bold]Agent Mood[/]", border_style=border_color))
143
+ console.print(table)
144
+ console.print()
@@ -0,0 +1,193 @@
1
+ """FUSE mount commands: start, stop, status."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ import click
10
+
11
+ from ._common import AGENT_HOME, console
12
+
13
+ from rich.panel import Panel
14
+ from rich.table import Table
15
+
16
+
17
+ def register_mount_commands(main: click.Group) -> None:
18
+ """Register the mount command group."""
19
+
20
+ @main.group()
21
+ def mount():
22
+ """Sovereign FUSE filesystem — browse agent data as files.
23
+
24
+ \b
25
+ Mount the sovereign virtual filesystem to access memories, identity,
26
+ inbox, outbox, and coordination tasks as ordinary files.
27
+
28
+ \b
29
+ Mount: skcapstone mount start
30
+ Debug: skcapstone mount start --foreground
31
+ Unmount: skcapstone mount stop
32
+ Status: skcapstone mount status
33
+ """
34
+
35
+ @mount.command("start")
36
+ @click.option(
37
+ "--mount-point",
38
+ default="~/.sovereign/mount",
39
+ type=click.Path(),
40
+ help="Directory to mount the sovereign filesystem at.",
41
+ show_default=True,
42
+ )
43
+ @click.option(
44
+ "--home",
45
+ default=AGENT_HOME,
46
+ type=click.Path(),
47
+ help="Agent home directory.",
48
+ )
49
+ @click.option(
50
+ "--foreground",
51
+ "foreground",
52
+ is_flag=True,
53
+ default=False,
54
+ help="Run in foreground (blocks; useful for debugging).",
55
+ )
56
+ def mount_start(mount_point: str, home: str, foreground: bool):
57
+ """Mount the sovereign virtual filesystem.
58
+
59
+ Exposes memories, identity, inbox, outbox, and coordination tasks
60
+ as a read-mostly POSIX filesystem via FUSE.
61
+
62
+ \b
63
+ Requires: pip install skcapstone[fuse]
64
+
65
+ \b
66
+ Examples:
67
+
68
+ skcapstone mount start
69
+
70
+ skcapstone mount start --mount-point /mnt/sovereign
71
+
72
+ skcapstone mount start --foreground
73
+ """
74
+ from ..fuse_mount import FUSEDaemon
75
+
76
+ mount_path = Path(mount_point).expanduser()
77
+ home_path = Path(home).expanduser()
78
+
79
+ daemon = FUSEDaemon(mount_point=mount_path, agent_home=home_path)
80
+
81
+ if foreground:
82
+ console.print(
83
+ f"[bold cyan]Mounting sovereign filesystem at [white]{mount_path}[/] "
84
+ f"[dim](foreground — Ctrl-C to unmount)[/]"
85
+ )
86
+ else:
87
+ console.print(
88
+ f"[bold cyan]Mounting sovereign filesystem at [white]{mount_path}[/] ..."
89
+ )
90
+
91
+ ok = daemon.start(foreground=foreground)
92
+
93
+ if ok and not foreground:
94
+ console.print(f"[green]Mounted.[/] [dim]Unmount with: skcapstone mount stop[/]")
95
+ elif not ok:
96
+ console.print("[bold red]Mount failed.[/] Check logs or try --foreground for details.")
97
+ sys.exit(1)
98
+
99
+ @mount.command("stop")
100
+ @click.option(
101
+ "--mount-point",
102
+ default="~/.sovereign/mount",
103
+ type=click.Path(),
104
+ help="Mount point to unmount.",
105
+ show_default=True,
106
+ )
107
+ @click.option(
108
+ "--home",
109
+ default=AGENT_HOME,
110
+ type=click.Path(),
111
+ help="Agent home directory.",
112
+ )
113
+ def mount_stop(mount_point: str, home: str):
114
+ """Unmount the sovereign virtual filesystem.
115
+
116
+ \b
117
+ Example:
118
+
119
+ skcapstone mount stop
120
+ """
121
+ from ..fuse_mount import FUSEDaemon
122
+
123
+ mount_path = Path(mount_point).expanduser()
124
+ home_path = Path(home).expanduser()
125
+
126
+ daemon = FUSEDaemon(mount_point=mount_path, agent_home=home_path)
127
+ console.print(f"[bold cyan]Unmounting {mount_path} ...[/]")
128
+
129
+ ok = daemon.stop()
130
+ if ok:
131
+ console.print("[green]Unmounted.[/]")
132
+ else:
133
+ console.print(
134
+ "[bold red]Unmount failed.[/] "
135
+ f"[dim]Try manually: fusermount -u {mount_path}[/]"
136
+ )
137
+ sys.exit(1)
138
+
139
+ @mount.command("status")
140
+ @click.option(
141
+ "--mount-point",
142
+ default="~/.sovereign/mount",
143
+ type=click.Path(),
144
+ help="Mount point to check.",
145
+ show_default=True,
146
+ )
147
+ @click.option(
148
+ "--home",
149
+ default=AGENT_HOME,
150
+ type=click.Path(),
151
+ help="Agent home directory.",
152
+ )
153
+ @click.option("--json", "as_json", is_flag=True, help="Output as JSON.")
154
+ def mount_status(mount_point: str, home: str, as_json: bool):
155
+ """Show the status of the sovereign FUSE filesystem.
156
+
157
+ \b
158
+ Example:
159
+
160
+ skcapstone mount status
161
+
162
+ skcapstone mount status --json
163
+ """
164
+ from ..fuse_mount import FUSEDaemon
165
+
166
+ mount_path = Path(mount_point).expanduser()
167
+ home_path = Path(home).expanduser()
168
+
169
+ daemon = FUSEDaemon(mount_point=mount_path, agent_home=home_path)
170
+ status = daemon.status()
171
+
172
+ if as_json:
173
+ click.echo(json.dumps(status, indent=2))
174
+ return
175
+
176
+ mounted = status.get("mounted", False)
177
+ icon = "[bold green]MOUNTED[/]" if mounted else "[bold red]NOT MOUNTED[/]"
178
+ pid = status.get("pid")
179
+ updated = status.get("updated_at", "—")
180
+
181
+ table = Table(show_header=False, box=None, padding=(0, 2))
182
+ table.add_column("Key", style="dim")
183
+ table.add_column("Value")
184
+
185
+ table.add_row("Status", icon)
186
+ table.add_row("Mount point", str(status.get("mount_point", "")))
187
+ table.add_row("Agent home", str(status.get("agent_home", "")))
188
+ table.add_row("PID", str(pid) if pid else "[dim]—[/]")
189
+ table.add_row("Last updated", updated or "[dim]—[/]")
190
+
191
+ console.print()
192
+ console.print(Panel(table, title="[bold]Sovereign Filesystem Status[/]", border_style="cyan"))
193
+ console.print()
@@ -0,0 +1,112 @@
1
+ """Notification commands: skcapstone notify test / skcapstone notifications."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ import click
9
+
10
+ from ._common import AGENT_HOME, console
11
+
12
+
13
+ def register_notify_commands(main: click.Group) -> None:
14
+ """Register the notify command group and the notifications alias."""
15
+
16
+ @main.group()
17
+ def notify():
18
+ """Desktop notification management."""
19
+
20
+ @notify.command("test")
21
+ @click.option("--title", default="SKCapstone", show_default=True, help="Notification title.")
22
+ @click.option("--body", default="Test notification from SKCapstone.", show_default=True, help="Notification body.")
23
+ @click.option(
24
+ "--urgency",
25
+ default="normal",
26
+ show_default=True,
27
+ type=click.Choice(["low", "normal", "critical"]),
28
+ help="Notification urgency.",
29
+ )
30
+ @click.option(
31
+ "--dashboard-url",
32
+ default="http://localhost:7778",
33
+ show_default=True,
34
+ help="Dashboard URL opened when the 'Open Dashboard' action is clicked.",
35
+ )
36
+ def notify_test(title: str, body: str, urgency: str, dashboard_url: str):
37
+ """Send a test desktop notification with click-action buttons."""
38
+ from ..notifications import NotificationManager
39
+
40
+ # Bypass debounce for the test command by using a fresh manager
41
+ mgr = NotificationManager(debounce_seconds=0, dashboard_url=dashboard_url)
42
+ dispatched = mgr.notify(title, body, urgency)
43
+ if dispatched:
44
+ console.print(f"\n [green]Notification dispatched[/]: {title!r} / {body!r}\n")
45
+ console.print(
46
+ " [dim]Click 'Open Dashboard' to open the dashboard, "
47
+ "or 'Open SKChat' to launch skchat watch.[/]\n"
48
+ )
49
+ else:
50
+ console.print(
51
+ "\n [yellow]Notification not dispatched[/] — no supported notification "
52
+ "system found (gi.repository.Notify / notify-send / osascript).\n"
53
+ )
54
+
55
+ # -----------------------------------------------------------------------
56
+ # Top-level alias: skcapstone notifications
57
+ # -----------------------------------------------------------------------
58
+
59
+ @main.command("notifications")
60
+ @click.option("--home", default=AGENT_HOME, type=click.Path(), help="Agent home directory.")
61
+ @click.option("--limit", "-n", default=20, show_default=True, help="Max results to show.")
62
+ @click.option("--json-out", is_flag=True, help="Output as JSON.")
63
+ def notifications_cmd(home: str, limit: int, json_out: bool):
64
+ """Show notification history (memories tagged 'notification')."""
65
+ import json as _json
66
+
67
+ from ..memory_engine import search as mem_search
68
+
69
+ home_path = Path(home).expanduser()
70
+ if not home_path.exists():
71
+ if json_out:
72
+ print(_json.dumps([]))
73
+ return
74
+ console.print("[bold red]No agent found.[/] Run skcapstone init first.")
75
+ sys.exit(1)
76
+
77
+ results = mem_search(home=home_path, query="notification", tags=["notification"], limit=limit)
78
+
79
+ if json_out:
80
+ output = [
81
+ {
82
+ "id": entry.memory_id,
83
+ "content": entry.content,
84
+ "tags": entry.tags,
85
+ "layer": entry.layer.value,
86
+ "created_at": entry.created_at.isoformat() if entry.created_at else None,
87
+ }
88
+ for entry in results
89
+ ]
90
+ print(_json.dumps(output))
91
+ return
92
+
93
+ if not results:
94
+ console.print("\n [dim]No notification history found.[/]\n")
95
+ return
96
+
97
+ from rich.table import Table
98
+
99
+ console.print(f"\n [bold]{len(results)}[/] notification{'s' if len(results) != 1 else ''} in history:\n")
100
+
101
+ table = Table(show_header=True, header_style="bold", box=None, padding=(0, 2))
102
+ table.add_column("ID", style="cyan", max_width=14)
103
+ table.add_column("When", style="dim", max_width=22)
104
+ table.add_column("Content", max_width=70)
105
+
106
+ for entry in results:
107
+ created = entry.created_at.strftime("%Y-%m-%d %H:%M:%S") if entry.created_at else "—"
108
+ preview = entry.content[:100] + ("..." if len(entry.content) > 100 else "")
109
+ table.add_row(entry.memory_id, created, preview)
110
+
111
+ console.print(table)
112
+ console.print()