@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,307 @@
1
+ """Tests for CapAuth identity integration.
2
+
3
+ Covers both paths: real CapAuth profile present vs placeholder fallback.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import json
9
+ from datetime import datetime, timezone
10
+ from pathlib import Path
11
+ from types import SimpleNamespace
12
+ from unittest.mock import MagicMock, patch
13
+
14
+ import pytest
15
+
16
+ from skcapstone.discovery import (
17
+ _sync_identity_json,
18
+ _try_load_capauth_profile,
19
+ discover_identity,
20
+ )
21
+ from skcapstone.models import IdentityState, PillarStatus
22
+ from skcapstone.pillars.identity import (
23
+ _generate_placeholder_fingerprint,
24
+ _try_init_capauth,
25
+ generate_identity,
26
+ )
27
+
28
+
29
+ def _fake_profile(
30
+ fingerprint: str = "A" * 40,
31
+ name: str = "Opus",
32
+ email: str = "opus@capauth.local",
33
+ ) -> SimpleNamespace:
34
+ """Build a fake SovereignProfile-like object for testing."""
35
+ return SimpleNamespace(
36
+ key_info=SimpleNamespace(
37
+ fingerprint=fingerprint,
38
+ public_key_path="/tmp/public.asc",
39
+ created=datetime(2026, 1, 1, tzinfo=timezone.utc),
40
+ ),
41
+ entity=SimpleNamespace(name=name, email=email),
42
+ )
43
+
44
+
45
+ # ---------------------------------------------------------------------------
46
+ # discover_identity: real CapAuth path
47
+ # ---------------------------------------------------------------------------
48
+
49
+
50
+ class TestDiscoverWithCapAuth:
51
+ """Tests for discover_identity when a real CapAuth profile exists."""
52
+
53
+ def test_uses_capauth_fingerprint(self, tmp_agent_home: Path):
54
+ """discover_identity returns the real CapAuth fingerprint."""
55
+ fake = _fake_profile(fingerprint="B" * 40, name="Jarvis")
56
+
57
+ with patch(
58
+ "skcapstone.discovery._try_load_capauth_profile",
59
+ return_value=IdentityState(
60
+ fingerprint=fake.key_info.fingerprint,
61
+ name=fake.entity.name,
62
+ email=fake.entity.email,
63
+ key_path=Path(fake.key_info.public_key_path),
64
+ created_at=fake.key_info.created,
65
+ status=PillarStatus.ACTIVE,
66
+ ),
67
+ ):
68
+ state = discover_identity(tmp_agent_home)
69
+
70
+ assert state.fingerprint == "B" * 40
71
+ assert state.name == "Jarvis"
72
+ assert state.status == PillarStatus.ACTIVE
73
+
74
+ def test_syncs_identity_json(self, tmp_agent_home: Path):
75
+ """discover_identity writes identity.json with capauth_managed=True."""
76
+ identity_dir = tmp_agent_home / "identity"
77
+ identity_dir.mkdir(parents=True, exist_ok=True)
78
+
79
+ fake = _fake_profile(fingerprint="C" * 40)
80
+ with patch(
81
+ "skcapstone.discovery._try_load_capauth_profile",
82
+ return_value=IdentityState(
83
+ fingerprint="C" * 40,
84
+ name="Opus",
85
+ email="opus@capauth.local",
86
+ key_path=Path("/tmp/public.asc"),
87
+ status=PillarStatus.ACTIVE,
88
+ ),
89
+ ):
90
+ discover_identity(tmp_agent_home)
91
+
92
+ manifest = json.loads((identity_dir / "identity.json").read_text())
93
+ assert manifest["fingerprint"] == "C" * 40
94
+ assert manifest["capauth_managed"] is True
95
+
96
+ def test_replaces_placeholder_fingerprint(self, tmp_agent_home: Path):
97
+ """When upgrading from placeholder to real keys, identity.json updates."""
98
+ identity_dir = tmp_agent_home / "identity"
99
+ identity_dir.mkdir(parents=True, exist_ok=True)
100
+
101
+ old_manifest = {
102
+ "name": "test",
103
+ "email": "test@skcapstone.local",
104
+ "fingerprint": _generate_placeholder_fingerprint("test"),
105
+ "capauth_managed": False,
106
+ }
107
+ (identity_dir / "identity.json").write_text(json.dumps(old_manifest))
108
+
109
+ with patch(
110
+ "skcapstone.discovery._try_load_capauth_profile",
111
+ return_value=IdentityState(
112
+ fingerprint="D" * 40,
113
+ name="test",
114
+ email="test@capauth.local",
115
+ status=PillarStatus.ACTIVE,
116
+ ),
117
+ ):
118
+ state = discover_identity(tmp_agent_home)
119
+
120
+ assert state.fingerprint == "D" * 40
121
+ updated = json.loads((identity_dir / "identity.json").read_text())
122
+ assert updated["fingerprint"] == "D" * 40
123
+ assert updated["capauth_managed"] is True
124
+
125
+
126
+ # ---------------------------------------------------------------------------
127
+ # discover_identity: placeholder fallback
128
+ # ---------------------------------------------------------------------------
129
+
130
+
131
+ class TestDiscoverWithoutCapAuth:
132
+ """Tests for discover_identity when CapAuth is not available."""
133
+
134
+ def test_reads_existing_identity_json(self, tmp_agent_home: Path):
135
+ """Falls back to reading identity.json when no CapAuth profile."""
136
+ identity_dir = tmp_agent_home / "identity"
137
+ identity_dir.mkdir(parents=True, exist_ok=True)
138
+
139
+ manifest = {
140
+ "name": "fallback-agent",
141
+ "email": "fallback@skcapstone.local",
142
+ "fingerprint": "F" * 40,
143
+ "capauth_managed": False,
144
+ "created_at": "2026-01-01T00:00:00+00:00",
145
+ }
146
+ (identity_dir / "identity.json").write_text(json.dumps(manifest))
147
+
148
+ with patch("skcapstone.discovery._try_load_capauth_profile", return_value=None):
149
+ state = discover_identity(tmp_agent_home)
150
+
151
+ assert state.fingerprint == "F" * 40
152
+ assert state.name == "fallback-agent"
153
+ assert state.status == PillarStatus.DEGRADED
154
+
155
+ def test_no_identity_at_all(self, tmp_agent_home: Path):
156
+ """No identity.json and no CapAuth returns MISSING."""
157
+ with patch("skcapstone.discovery._try_load_capauth_profile", return_value=None):
158
+ state = discover_identity(tmp_agent_home)
159
+
160
+ assert state.status == PillarStatus.MISSING
161
+
162
+ def test_capauth_managed_true_is_active(self, tmp_agent_home: Path):
163
+ """identity.json with capauth_managed=True is ACTIVE even without profile load."""
164
+ identity_dir = tmp_agent_home / "identity"
165
+ identity_dir.mkdir(parents=True, exist_ok=True)
166
+
167
+ manifest = {
168
+ "name": "real-agent",
169
+ "email": "real@capauth.local",
170
+ "fingerprint": "E" * 40,
171
+ "capauth_managed": True,
172
+ }
173
+ (identity_dir / "identity.json").write_text(json.dumps(manifest))
174
+
175
+ with patch("skcapstone.discovery._try_load_capauth_profile", return_value=None):
176
+ state = discover_identity(tmp_agent_home)
177
+
178
+ assert state.status == PillarStatus.ACTIVE
179
+
180
+
181
+ # ---------------------------------------------------------------------------
182
+ # generate_identity: init path
183
+ # ---------------------------------------------------------------------------
184
+
185
+
186
+ class TestGenerateIdentity:
187
+ """Tests for generate_identity (the skcapstone init path)."""
188
+
189
+ def test_placeholder_when_no_capauth(self, tmp_agent_home: Path):
190
+ """Without CapAuth, generates a placeholder fingerprint."""
191
+ with patch(
192
+ "skcapstone.pillars.identity._try_init_capauth", return_value=None
193
+ ):
194
+ state = generate_identity(tmp_agent_home, "test-agent")
195
+
196
+ assert state.status == PillarStatus.DEGRADED
197
+ assert state.fingerprint == _generate_placeholder_fingerprint("test-agent")
198
+ assert len(state.fingerprint) == 40
199
+
200
+ def test_active_when_capauth_works(self, tmp_agent_home: Path):
201
+ """With CapAuth, uses real keys and sets ACTIVE."""
202
+ fake_state = IdentityState(
203
+ fingerprint="A" * 40,
204
+ key_path=Path("/tmp/pub.asc"),
205
+ name="test",
206
+ email="test@capauth.local",
207
+ status=PillarStatus.ACTIVE,
208
+ )
209
+ with patch(
210
+ "skcapstone.pillars.identity._try_init_capauth", return_value=fake_state
211
+ ):
212
+ state = generate_identity(tmp_agent_home, "test")
213
+
214
+ assert state.fingerprint == "A" * 40
215
+ assert state.status == PillarStatus.ACTIVE
216
+
217
+ def test_identity_json_written(self, tmp_agent_home: Path):
218
+ """identity.json is always written, even with placeholders."""
219
+ with patch(
220
+ "skcapstone.pillars.identity._try_init_capauth", return_value=None
221
+ ):
222
+ generate_identity(tmp_agent_home, "writer-test")
223
+
224
+ assert (tmp_agent_home / "identity" / "identity.json").exists()
225
+ data = json.loads(
226
+ (tmp_agent_home / "identity" / "identity.json").read_text()
227
+ )
228
+ assert data["name"] == "writer-test"
229
+ assert data["capauth_managed"] is False
230
+
231
+ def test_placeholder_deterministic(self):
232
+ """Placeholder fingerprints are deterministic for the same name."""
233
+ fp1 = _generate_placeholder_fingerprint("agent-x")
234
+ fp2 = _generate_placeholder_fingerprint("agent-x")
235
+ fp3 = _generate_placeholder_fingerprint("agent-y")
236
+ assert fp1 == fp2
237
+ assert fp1 != fp3
238
+
239
+
240
+ # ---------------------------------------------------------------------------
241
+ # _sync_identity_json
242
+ # ---------------------------------------------------------------------------
243
+
244
+
245
+ class TestSyncIdentityJson:
246
+ """Tests for the identity.json sync helper."""
247
+
248
+ def test_creates_file_if_missing(self, tmp_path: Path):
249
+ """Creates identity.json from scratch when it doesn't exist."""
250
+ identity_dir = tmp_path / "identity"
251
+ state = IdentityState(
252
+ fingerprint="X" * 40,
253
+ name="new-agent",
254
+ email="new@capauth.local",
255
+ status=PillarStatus.ACTIVE,
256
+ )
257
+ _sync_identity_json(identity_dir, state)
258
+
259
+ assert (identity_dir / "identity.json").exists()
260
+ data = json.loads((identity_dir / "identity.json").read_text())
261
+ assert data["fingerprint"] == "X" * 40
262
+ assert data["capauth_managed"] is True
263
+
264
+ def test_skips_write_when_unchanged(self, tmp_path: Path):
265
+ """Does not rewrite identity.json when fingerprint matches."""
266
+ identity_dir = tmp_path / "identity"
267
+ identity_dir.mkdir(parents=True)
268
+
269
+ existing = {
270
+ "name": "same",
271
+ "email": "same@capauth.local",
272
+ "fingerprint": "Y" * 40,
273
+ "capauth_managed": True,
274
+ "created_at": None,
275
+ }
276
+ path = identity_dir / "identity.json"
277
+ path.write_text(json.dumps(existing))
278
+ mtime_before = path.stat().st_mtime
279
+
280
+ state = IdentityState(
281
+ fingerprint="Y" * 40,
282
+ name="same",
283
+ email="same@capauth.local",
284
+ status=PillarStatus.ACTIVE,
285
+ )
286
+ _sync_identity_json(identity_dir, state)
287
+
288
+ assert path.stat().st_mtime == mtime_before
289
+
290
+ def test_updates_when_fingerprint_changes(self, tmp_path: Path):
291
+ """Rewrites identity.json when the fingerprint is different."""
292
+ identity_dir = tmp_path / "identity"
293
+ identity_dir.mkdir(parents=True)
294
+
295
+ old = {"fingerprint": "OLD" + "0" * 37, "capauth_managed": False}
296
+ (identity_dir / "identity.json").write_text(json.dumps(old))
297
+
298
+ state = IdentityState(
299
+ fingerprint="NEW" + "1" * 37,
300
+ name="upgraded",
301
+ status=PillarStatus.ACTIVE,
302
+ )
303
+ _sync_identity_json(identity_dir, state)
304
+
305
+ data = json.loads((identity_dir / "identity.json").read_text())
306
+ assert data["fingerprint"] == "NEW" + "1" * 37
307
+ assert data["capauth_managed"] is True
@@ -0,0 +1,117 @@
1
+ """Unit tests for the identity pillar module."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+ from unittest.mock import MagicMock, patch
8
+
9
+ import pytest
10
+
11
+ from skcapstone.models import PillarStatus
12
+ from skcapstone.pillars.identity import (
13
+ _generate_placeholder_fingerprint,
14
+ generate_identity,
15
+ )
16
+
17
+
18
+ class TestGenerateIdentity:
19
+ """Tests for generate_identity()."""
20
+
21
+ def test_creates_identity_directory(self, tmp_agent_home: Path):
22
+ generate_identity(tmp_agent_home, "test-agent")
23
+ assert (tmp_agent_home / "identity").is_dir()
24
+
25
+ def test_writes_identity_json(self, tmp_agent_home: Path):
26
+ generate_identity(tmp_agent_home, "test-agent")
27
+ assert (tmp_agent_home / "identity" / "identity.json").exists()
28
+
29
+ def test_state_name_matches(self, tmp_agent_home: Path):
30
+ state = generate_identity(tmp_agent_home, "my-agent")
31
+ assert state.name == "my-agent"
32
+
33
+ def test_explicit_email_used(self, tmp_agent_home: Path):
34
+ state = generate_identity(tmp_agent_home, "opus", email="opus@skworld.io")
35
+ assert state.email == "opus@skworld.io"
36
+
37
+ def test_email_auto_generated_from_name(self, tmp_agent_home: Path):
38
+ state = generate_identity(tmp_agent_home, "My Agent")
39
+ assert state.email == "my-agent@skcapstone.local"
40
+
41
+ def test_fingerprint_always_set(self, tmp_agent_home: Path):
42
+ state = generate_identity(tmp_agent_home, "test")
43
+ assert state.fingerprint is not None
44
+ assert len(state.fingerprint) == 40
45
+
46
+ def test_identity_json_has_required_fields(self, tmp_agent_home: Path):
47
+ generate_identity(tmp_agent_home, "agent-x")
48
+ data = json.loads(
49
+ (tmp_agent_home / "identity" / "identity.json").read_text()
50
+ )
51
+ for field in ("name", "email", "fingerprint", "created_at"):
52
+ assert field in data, f"missing field: {field}"
53
+
54
+ def test_identity_json_name_matches(self, tmp_agent_home: Path):
55
+ generate_identity(tmp_agent_home, "lumina")
56
+ data = json.loads(
57
+ (tmp_agent_home / "identity" / "identity.json").read_text()
58
+ )
59
+ assert data["name"] == "lumina"
60
+
61
+ def test_idempotent_second_call_succeeds(self, tmp_agent_home: Path):
62
+ generate_identity(tmp_agent_home, "agent-a")
63
+ state2 = generate_identity(tmp_agent_home, "agent-a")
64
+ assert state2.name == "agent-a"
65
+
66
+ def test_degraded_status_without_capauth(self, tmp_agent_home: Path):
67
+ """Without capauth installed the status should be DEGRADED."""
68
+ with patch.dict("sys.modules", {"capauth": None, "capauth.profile": None, "capauth.keys": None}):
69
+ state = generate_identity(tmp_agent_home, "test-agent")
70
+ assert state.status == PillarStatus.DEGRADED
71
+
72
+ def test_capauth_managed_false_without_capauth(self, tmp_agent_home: Path):
73
+ with patch.dict("sys.modules", {"capauth": None, "capauth.profile": None, "capauth.keys": None}):
74
+ generate_identity(tmp_agent_home, "no-capauth")
75
+ data = json.loads(
76
+ (tmp_agent_home / "identity" / "identity.json").read_text()
77
+ )
78
+ assert data["capauth_managed"] is False
79
+
80
+ def test_active_status_with_capauth_profile(self, tmp_agent_home: Path):
81
+ """When capauth.profile.load_profile succeeds, status is ACTIVE."""
82
+ mock_profile = MagicMock()
83
+ mock_profile.key_info.fingerprint = "A" * 40
84
+ mock_profile.key_info.public_key_path = str(tmp_agent_home / "agent.pub")
85
+ mock_profile.entity.name = "opus"
86
+ mock_profile.entity.email = "opus@test"
87
+
88
+ mock_capauth_profile = MagicMock()
89
+ mock_capauth_profile.load_profile.return_value = mock_profile
90
+
91
+ with patch.dict("sys.modules", {"capauth": MagicMock(), "capauth.profile": mock_capauth_profile}):
92
+ state = generate_identity(tmp_agent_home, "opus")
93
+
94
+ assert state.status == PillarStatus.ACTIVE
95
+ assert state.fingerprint == "A" * 40
96
+
97
+
98
+ class TestPlaceholderFingerprint:
99
+ """Tests for _generate_placeholder_fingerprint()."""
100
+
101
+ def test_length_is_40(self):
102
+ fp = _generate_placeholder_fingerprint("test-agent")
103
+ assert len(fp) == 40
104
+
105
+ def test_is_uppercase_hex(self):
106
+ fp = _generate_placeholder_fingerprint("agent")
107
+ assert all(c in "0123456789ABCDEF" for c in fp)
108
+
109
+ def test_deterministic(self):
110
+ fp1 = _generate_placeholder_fingerprint("opus")
111
+ fp2 = _generate_placeholder_fingerprint("opus")
112
+ assert fp1 == fp2
113
+
114
+ def test_different_names_different_fingerprints(self):
115
+ fp1 = _generate_placeholder_fingerprint("opus")
116
+ fp2 = _generate_placeholder_fingerprint("lumina")
117
+ assert fp1 != fp2
@@ -0,0 +1,68 @@
1
+ """Tests for the install wizard — path selection, confirmation, routing."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from unittest.mock import MagicMock, patch
7
+
8
+ import pytest
9
+
10
+ from skcapstone.install_wizard import (
11
+ PATH_LABELS,
12
+ _wait_for_sync,
13
+ )
14
+
15
+
16
+ class TestPathLabels:
17
+ """Tests for path label definitions."""
18
+
19
+ def test_all_three_paths_exist(self) -> None:
20
+ """All three install paths have labels."""
21
+ assert 1 in PATH_LABELS
22
+ assert 2 in PATH_LABELS
23
+ assert 3 in PATH_LABELS
24
+
25
+ def test_labels_are_human_readable(self) -> None:
26
+ """Labels contain no jargon."""
27
+ for label in PATH_LABELS.values():
28
+ assert len(label) > 10
29
+ assert "sovereign singularity" not in label.lower()
30
+
31
+
32
+ class TestWaitForSync:
33
+ """Tests for _wait_for_sync helper."""
34
+
35
+ def test_returns_true_when_identity_exists(self, tmp_path: Path) -> None:
36
+ """Returns True immediately if identity.json exists."""
37
+ (tmp_path / "identity.json").write_text("{}")
38
+ assert _wait_for_sync(tmp_path, timeout_seconds=1) is True
39
+
40
+ def test_returns_true_when_registry_exists(self, tmp_path: Path) -> None:
41
+ """Returns True if vault-registry.json exists."""
42
+ (tmp_path / "vault-registry.json").write_text("{}")
43
+ assert _wait_for_sync(tmp_path, timeout_seconds=1) is True
44
+
45
+ def test_returns_true_when_auth_key_exists(self, tmp_path: Path) -> None:
46
+ """Returns True if tailscale.key.gpg exists."""
47
+ (tmp_path / "tailscale.key.gpg").write_bytes(b"encrypted")
48
+ assert _wait_for_sync(tmp_path, timeout_seconds=1) is True
49
+
50
+ def test_returns_false_when_empty(self, tmp_path: Path) -> None:
51
+ """Returns False after timeout when no files exist."""
52
+ assert _wait_for_sync(tmp_path, timeout_seconds=1) is False
53
+
54
+
55
+ class TestPathUpdateExisting:
56
+ """Tests for path 3 — update existing node."""
57
+
58
+ def test_exits_if_no_home(self, tmp_path: Path) -> None:
59
+ """Path 3 exits if agent home doesn't exist."""
60
+ from skcapstone.install_wizard import _path_update_existing
61
+
62
+ fake_home = str(tmp_path / "nonexistent")
63
+ with pytest.raises(SystemExit):
64
+ _path_update_existing(
65
+ home=fake_home,
66
+ skip_deps=True,
67
+ skip_ritual=True,
68
+ )