@smilintux/skcapstone 0.10.0 → 0.12.5

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 (279) hide show
  1. package/.env.example +10 -4
  2. package/.github/workflows/ci.yml +2 -2
  3. package/.github/workflows/publish.yml +9 -2
  4. package/.openclaw-workspace.json +2 -2
  5. package/CLAUDE.md +37 -0
  6. package/MISSION.md +17 -2
  7. package/README.md +282 -3
  8. package/docker/Dockerfile +7 -7
  9. package/docker/compose-templates/dev-team.yml +12 -12
  10. package/docker/compose-templates/mini-team.yml +9 -9
  11. package/docker/compose-templates/ops-team.yml +10 -10
  12. package/docker/compose-templates/research-team.yml +10 -10
  13. package/docker/entrypoint.sh +4 -4
  14. package/docs/ADR-optional-integration-backbone.md +181 -0
  15. package/docs/ARCHITECTURE.md +186 -43
  16. package/docs/BOND_WITH_GROK.md +6 -6
  17. package/docs/CUSTOM_AGENT.md +123 -30
  18. package/docs/DREAMING.md +70 -0
  19. package/docs/GETTING_STARTED.md +7 -7
  20. package/docs/QUICKSTART.md +10 -6
  21. package/docs/SKJOULE_ARCHITECTURE.md +3 -3
  22. package/docs/SOUL_SWAPPER.md +5 -5
  23. package/docs/hammertime-audit.md +402 -0
  24. package/docs/sk-integration-HANDOFF.md +117 -0
  25. package/docs/skscheduler.md +155 -0
  26. package/docs/superpowers/examples/jobs.yaml +31 -0
  27. package/docs/superpowers/plans/2026-06-08-skscheduler.md +1265 -0
  28. package/docs/superpowers/specs/2026-06-08-skscheduler-design.md +186 -0
  29. package/examples/custom-bond-template.json +1 -1
  30. package/examples/grok-feb.json +1 -1
  31. package/examples/queen-ava-feb.json +1 -1
  32. package/launchd/{com.skcapstone.skcomm-heartbeat.plist → com.skcapstone.skcomms-heartbeat.plist} +4 -4
  33. package/launchd/{com.skcapstone.skcomm-queue-drain.plist → com.skcapstone.skcomms-queue-drain.plist} +4 -4
  34. package/launchd/install-launchd.sh +6 -6
  35. package/{openclaw-plugin → openclaw-plugin.archived-2026-04-23}/src/index.ts +3 -2
  36. package/package.json +1 -1
  37. package/pyproject.toml +16 -10
  38. package/scripts/archive-sessions.sh +7 -0
  39. package/scripts/check-updates.py +4 -4
  40. package/scripts/install-bundle.sh +8 -8
  41. package/scripts/install.ps1 +12 -11
  42. package/scripts/install.sh +159 -5
  43. package/scripts/model-fallback-monitor.sh +102 -0
  44. package/scripts/nvidia-proxy.mjs +78 -26
  45. package/scripts/refresh-anthropic-token.sh +172 -0
  46. package/scripts/release.sh +98 -0
  47. package/scripts/session-to-memory.py +219 -0
  48. package/scripts/skgateway.mjs +3 -3
  49. package/scripts/telegram-catchup-all.sh +12 -1
  50. package/scripts/verify_install.sh +2 -2
  51. package/scripts/wargov-ufo-capture/README.md +43 -0
  52. package/scripts/wargov-ufo-capture/cdp_capture_release2.py +273 -0
  53. package/scripts/wargov-ufo-capture/cdp_capture_splc_doj.py +246 -0
  54. package/scripts/wargov-ufo-capture/cdp_finish.py +271 -0
  55. package/scripts/wargov-ufo-capture/cdp_probe.py +188 -0
  56. package/scripts/wargov-ufo-capture/cdp_splc_pressrelease.py +101 -0
  57. package/scripts/wargov-ufo-capture/parse_csv.py +95 -0
  58. package/scripts/wargov-ufo-capture/pull_dvids.sh +107 -0
  59. package/scripts/watch-anthropic-token.sh +212 -0
  60. package/scripts/windows/install-tasks.ps1 +7 -7
  61. package/scripts/windows/skcapstone-task.xml +1 -1
  62. package/src/skcapstone/__init__.py +45 -3
  63. package/src/skcapstone/_cli_monolith.py +20 -15
  64. package/src/skcapstone/activity.py +5 -1
  65. package/src/skcapstone/agent_card.py +3 -2
  66. package/src/skcapstone/api.py +41 -40
  67. package/src/skcapstone/auction.py +14 -11
  68. package/src/skcapstone/backup.py +2 -1
  69. package/src/skcapstone/blueprint_registry.py +4 -3
  70. package/src/skcapstone/brain_first.py +238 -0
  71. package/src/skcapstone/changelog.py +1 -1
  72. package/src/skcapstone/chat.py +22 -17
  73. package/src/skcapstone/cli/__init__.py +9 -1
  74. package/src/skcapstone/cli/_common.py +1 -0
  75. package/src/skcapstone/cli/agents_spawner.py +5 -2
  76. package/src/skcapstone/cli/alerts.py +25 -4
  77. package/src/skcapstone/cli/bench.py +15 -15
  78. package/src/skcapstone/cli/chat.py +7 -4
  79. package/src/skcapstone/cli/consciousness.py +5 -2
  80. package/src/skcapstone/cli/context_cmd.py +18 -4
  81. package/src/skcapstone/cli/daemon.py +11 -7
  82. package/src/skcapstone/cli/gtd.py +26 -1
  83. package/src/skcapstone/cli/housekeeping.py +3 -3
  84. package/src/skcapstone/cli/identity_cmd.py +378 -0
  85. package/src/skcapstone/cli/joule_cmd.py +7 -3
  86. package/src/skcapstone/cli/memory.py +8 -6
  87. package/src/skcapstone/cli/peers_dir.py +1 -1
  88. package/src/skcapstone/cli/register_cmd.py +29 -3
  89. package/src/skcapstone/cli/scheduler_cmd.py +167 -0
  90. package/src/skcapstone/cli/session.py +25 -0
  91. package/src/skcapstone/cli/setup.py +96 -29
  92. package/src/skcapstone/cli/shell_cmd.py +53 -1
  93. package/src/skcapstone/cli/skills_cmd.py +2 -2
  94. package/src/skcapstone/cli/soul.py +8 -5
  95. package/src/skcapstone/cli/status.py +37 -11
  96. package/src/skcapstone/cli/telegram.py +21 -0
  97. package/src/skcapstone/cli/test_cmd.py +5 -5
  98. package/src/skcapstone/cli/test_connection.py +2 -2
  99. package/src/skcapstone/cli/upgrade_cmd.py +23 -14
  100. package/src/skcapstone/cli/version_cmd.py +1 -1
  101. package/src/skcapstone/cli/watch_cmd.py +9 -6
  102. package/src/skcapstone/cloud9_bridge.py +14 -14
  103. package/src/skcapstone/codex_setup.py +255 -0
  104. package/src/skcapstone/config_validator.py +7 -4
  105. package/src/skcapstone/consciousness_config.py +5 -1
  106. package/src/skcapstone/consciousness_loop.py +313 -273
  107. package/src/skcapstone/context_loader.py +121 -0
  108. package/src/skcapstone/coord_federation.py +2 -1
  109. package/src/skcapstone/coordination.py +23 -6
  110. package/src/skcapstone/crush_integration.py +2 -1
  111. package/src/skcapstone/daemon.py +132 -77
  112. package/src/skcapstone/dashboard.py +10 -10
  113. package/src/skcapstone/data/sk-agent-picker.sh +421 -0
  114. package/src/skcapstone/data/systemd/skcapstone-api.socket +9 -0
  115. package/src/skcapstone/data/systemd/skcapstone-memory-compress.service +18 -0
  116. package/src/skcapstone/data/systemd/skcapstone-memory-compress.timer +11 -0
  117. package/src/skcapstone/data/systemd/skcapstone.service +37 -0
  118. package/src/skcapstone/data/systemd/skcapstone@.service +50 -0
  119. package/src/skcapstone/data/systemd/skcomms-heartbeat.service +18 -0
  120. package/{systemd/skcomm-heartbeat.timer → src/skcapstone/data/systemd/skcomms-heartbeat.timer} +2 -2
  121. package/src/skcapstone/data/systemd/skcomms-queue-drain.service +17 -0
  122. package/{systemd/skcomm-queue-drain.timer → src/skcapstone/data/systemd/skcomms-queue-drain.timer} +2 -2
  123. package/src/skcapstone/defaults/claude/CLAUDE.md +67 -0
  124. package/src/skcapstone/defaults/claude/settings.json +74 -0
  125. package/src/skcapstone/defaults/lumina/config/claude-hooks.md +57 -0
  126. package/src/skcapstone/defaults/lumina/config/skgraph.yaml +55 -10
  127. package/src/skcapstone/defaults/lumina/config/skmemory.yaml +79 -13
  128. package/src/skcapstone/defaults/lumina/config/skvector.yaml +60 -9
  129. package/src/skcapstone/defaults/lumina/memory/long-term/18b9c0d1e2f3-cloud9-protocol.json +2 -2
  130. package/src/skcapstone/defaults/lumina/memory/long-term/a1b2c3d4e5f6-ecosystem-overview.json +2 -2
  131. package/src/skcapstone/defaults/lumina/memory/long-term/b2c3d4e5f6a7-five-pillars.json +9 -9
  132. package/src/skcapstone/defaults/lumina/memory/long-term/d4e5f6a7b8c9-site-directory.json +2 -2
  133. package/src/skcapstone/defaults/unhinged.json +13 -0
  134. package/src/skcapstone/discovery.py +43 -20
  135. package/src/skcapstone/doctor.py +941 -22
  136. package/src/skcapstone/dreaming.py +1183 -109
  137. package/src/skcapstone/emotion_tracker.py +2 -2
  138. package/src/skcapstone/export.py +4 -3
  139. package/src/skcapstone/fuse_mount.py +14 -12
  140. package/src/skcapstone/gui_installer.py +2 -2
  141. package/src/skcapstone/heartbeat.py +1 -1
  142. package/src/skcapstone/housekeeping.py +14 -14
  143. package/src/skcapstone/install_wizard.py +209 -7
  144. package/src/skcapstone/itil.py +13 -4
  145. package/src/skcapstone/kms_scheduler.py +10 -8
  146. package/src/skcapstone/launchd.py +19 -19
  147. package/src/skcapstone/mcp_launcher.py +15 -1
  148. package/src/skcapstone/mcp_server.py +83 -49
  149. package/src/skcapstone/mcp_tools/__init__.py +2 -0
  150. package/src/skcapstone/mcp_tools/_helpers.py +2 -2
  151. package/src/skcapstone/mcp_tools/ansible_tools.py +7 -4
  152. package/src/skcapstone/mcp_tools/brain_first_tools.py +90 -0
  153. package/src/skcapstone/mcp_tools/capauth_tools.py +7 -4
  154. package/src/skcapstone/mcp_tools/comm_tools.py +10 -10
  155. package/src/skcapstone/mcp_tools/coord_tools.py +8 -4
  156. package/src/skcapstone/mcp_tools/did_tools.py +11 -8
  157. package/src/skcapstone/mcp_tools/gtd_tools.py +4 -4
  158. package/src/skcapstone/mcp_tools/memory_tools.py +6 -2
  159. package/src/skcapstone/mcp_tools/notification_tools.py +22 -6
  160. package/src/skcapstone/mcp_tools/{skcomm_tools.py → skcomms_tools.py} +14 -14
  161. package/src/skcapstone/mcp_tools/soul_tools.py +8 -2
  162. package/src/skcapstone/mdns_discovery.py +2 -2
  163. package/src/skcapstone/memory_curator.py +1 -1
  164. package/src/skcapstone/memory_engine.py +10 -3
  165. package/src/skcapstone/metrics.py +30 -16
  166. package/src/skcapstone/migrate_memories.py +4 -3
  167. package/src/skcapstone/migrate_multi_agent.py +8 -7
  168. package/src/skcapstone/models.py +47 -5
  169. package/src/skcapstone/notifications.py +42 -18
  170. package/src/skcapstone/onboard.py +875 -121
  171. package/src/skcapstone/operator_link.py +170 -0
  172. package/src/skcapstone/peer_directory.py +4 -4
  173. package/src/skcapstone/peers.py +19 -19
  174. package/src/skcapstone/pillars/__init__.py +7 -5
  175. package/src/skcapstone/pillars/consciousness.py +191 -0
  176. package/src/skcapstone/pillars/identity.py +51 -7
  177. package/src/skcapstone/pillars/memory.py +9 -3
  178. package/src/skcapstone/pillars/sync.py +2 -2
  179. package/src/skcapstone/preflight.py +3 -3
  180. package/src/skcapstone/providers/docker.py +28 -28
  181. package/src/skcapstone/register.py +6 -6
  182. package/src/skcapstone/registry_client.py +5 -4
  183. package/src/skcapstone/runtime.py +14 -3
  184. package/src/skcapstone/scheduled_tasks.py +254 -19
  185. package/src/skcapstone/scheduler_jobs.py +456 -0
  186. package/src/skcapstone/scheduler_runner.py +239 -0
  187. package/src/skcapstone/scheduler_state.py +162 -0
  188. package/src/skcapstone/sdk.py +310 -0
  189. package/src/skcapstone/service_health.py +279 -39
  190. package/src/skcapstone/session_briefing.py +108 -0
  191. package/src/skcapstone/session_capture.py +1 -1
  192. package/src/skcapstone/shell.py +7 -1
  193. package/src/skcapstone/soul.py +3 -1
  194. package/src/skcapstone/soul_switch.py +3 -1
  195. package/src/skcapstone/summary.py +6 -6
  196. package/src/skcapstone/sync_engine.py +15 -15
  197. package/src/skcapstone/sync_watcher.py +2 -2
  198. package/src/skcapstone/systemd.py +55 -21
  199. package/src/skcapstone/team_comms.py +8 -8
  200. package/src/skcapstone/team_engine.py +1 -1
  201. package/src/skcapstone/testrunner.py +3 -3
  202. package/src/skcapstone/trust_graph.py +40 -5
  203. package/src/skcapstone/unified_search.py +15 -6
  204. package/src/skcapstone/uninstall_wizard.py +11 -3
  205. package/src/skcapstone/version_check.py +8 -4
  206. package/src/skcapstone/warmth_anchor.py +4 -2
  207. package/src/skcapstone/whoami.py +4 -4
  208. package/systemd/skcapstone.service +4 -6
  209. package/systemd/skcapstone@.service +7 -8
  210. package/systemd/skcomms-heartbeat.service +21 -0
  211. package/systemd/skcomms-heartbeat.timer +12 -0
  212. package/systemd/skcomms-queue-drain.service +17 -0
  213. package/systemd/skcomms-queue-drain.timer +12 -0
  214. package/tests/conftest.py +39 -0
  215. package/tests/integration/test_consciousness_e2e.py +39 -39
  216. package/tests/test_agent_card.py +1 -1
  217. package/tests/test_agent_home_scaffold.py +34 -0
  218. package/tests/test_alerts_consumer_topics.py +27 -0
  219. package/tests/test_backup.py +2 -1
  220. package/tests/test_chat.py +6 -6
  221. package/tests/test_claude_md.py +2 -2
  222. package/tests/test_cli_skills.py +10 -10
  223. package/tests/test_cli_test_cmd.py +4 -4
  224. package/tests/test_cli_test_connection.py +1 -1
  225. package/tests/test_cloud9_bridge.py +6 -6
  226. package/tests/test_consciousness_e2e.py +1 -1
  227. package/tests/test_consciousness_loop.py +10 -10
  228. package/tests/test_coordination.py +25 -0
  229. package/tests/test_cross_package.py +21 -21
  230. package/tests/test_daemon.py +4 -4
  231. package/tests/test_daemon_shutdown.py +1 -1
  232. package/tests/test_docker_provider.py +29 -29
  233. package/tests/test_doctor.py +400 -0
  234. package/tests/test_doctor_skscheduler.py +50 -0
  235. package/tests/test_dreaming_engine.py +147 -0
  236. package/tests/test_dreaming_gtd_capture.py +35 -0
  237. package/tests/test_e2e_automated.py +8 -5
  238. package/tests/test_fuse_mount.py +10 -10
  239. package/tests/test_gtd_brief.py +46 -0
  240. package/tests/test_gtd_malformed_tolerance.py +31 -0
  241. package/tests/test_housekeeping.py +15 -15
  242. package/tests/test_identity_migrate.py +251 -0
  243. package/tests/test_integration_backbone.py +598 -0
  244. package/tests/test_itil_gtd_lifecycle.py +37 -0
  245. package/tests/test_jobs_dropins.py +84 -0
  246. package/tests/test_mcp_server.py +82 -37
  247. package/tests/test_models.py +48 -4
  248. package/tests/test_multi_agent.py +31 -29
  249. package/tests/test_notifications.py +122 -32
  250. package/tests/test_onboard.py +63 -75
  251. package/tests/test_operator_link.py +78 -0
  252. package/tests/test_peers.py +14 -14
  253. package/tests/test_pillars.py +98 -0
  254. package/tests/test_preflight.py +3 -3
  255. package/tests/test_runtime.py +21 -0
  256. package/tests/test_scheduled_tasks.py +11 -6
  257. package/tests/test_scheduler_cli.py +47 -0
  258. package/tests/test_scheduler_features.py +133 -0
  259. package/tests/test_scheduler_integration.py +87 -0
  260. package/tests/test_scheduler_jobs.py +155 -0
  261. package/tests/test_scheduler_runner.py +64 -0
  262. package/tests/test_scheduler_state.py +57 -0
  263. package/tests/test_sdk.py +70 -0
  264. package/tests/test_service_health_incidents.py +34 -0
  265. package/tests/test_service_registry.py +52 -0
  266. package/tests/test_session_briefing.py +130 -0
  267. package/tests/test_snapshots.py +4 -4
  268. package/tests/test_sync_pipeline.py +26 -26
  269. package/tests/test_team_comms.py +2 -2
  270. package/tests/test_testrunner.py +2 -2
  271. package/tests/test_trust_graph.py +18 -0
  272. package/tests/test_unified_search.py +2 -2
  273. package/tests/test_version_check.py +10 -0
  274. package/tests/test_version_cmd.py +8 -8
  275. package/tests/test_whoami.py +1 -1
  276. package/systemd/skcomm-heartbeat.service +0 -18
  277. package/systemd/skcomm-queue-drain.service +0 -17
  278. /package/{openclaw-plugin → openclaw-plugin.archived-2026-04-23}/package.json +0 -0
  279. /package/{openclaw-plugin → openclaw-plugin.archived-2026-04-23}/src/openclaw.plugin.json +0 -0
@@ -0,0 +1,378 @@
1
+ """Identity commands: migrate.
2
+
3
+ The ``skcapstone identity migrate`` command backfills every provisioned
4
+ agent's ``identity/identity.json`` with the explicit sovereign-identity
5
+ fields the unified layer expects (skcomms T2 / epic ``2b264064``):
6
+
7
+ * ``realm`` + ``operator`` — mirrored from ``cluster.json``.
8
+ * ``fqid`` — the three-tier ``<agent>@<operator>.<realm>`` label, sourced
9
+ from :func:`capauth.resolve_agent_identity` (the canonical resolver).
10
+ * ``pgp_fingerprint`` — the agent's 40-char PGP fingerprint, also from the
11
+ resolver / the agent's CapAuth profile.
12
+
13
+ This command does **not** reimplement identity logic — it delegates to
14
+ ``capauth.resolve_agent_identity`` for the per-agent identity and only mirrors
15
+ ``realm``/``operator`` from cluster.json directly (those are cluster facts, not
16
+ agent facts). It is a *walker*: it finds every provisioned agent (one with a
17
+ CapAuth home, never a ``*-template``) and merges the missing fields into its
18
+ identity.json without clobbering unrelated keys.
19
+
20
+ Safety: these are LIVE identity files, so the command defaults to a dry-run
21
+ (it prints a plan and writes nothing). Pass ``--apply`` (alias ``--write``) to
22
+ actually modify files. The operation is idempotent — a second run on an
23
+ already-complete home reports every agent as unchanged.
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ import json
29
+ from dataclasses import dataclass, field
30
+ from pathlib import Path
31
+ from typing import Optional
32
+
33
+ import click
34
+
35
+ from ._common import SHARED_ROOT, console
36
+
37
+ # Fields the walker backfills, in stable display order.
38
+ _MANAGED_FIELDS = ("realm", "operator", "fqid", "pgp_fingerprint")
39
+
40
+ # cluster.json search path (mirrors capauth.agent_identity._CLUSTER_LOOKUP so
41
+ # realm/operator come from the same source the resolver uses for the fqid).
42
+ _CLUSTER_LOOKUP = [
43
+ Path("/etc/skcapstone/cluster.json"),
44
+ ]
45
+
46
+
47
+ @dataclass
48
+ class AgentPlan:
49
+ """Planned identity.json changes for a single agent.
50
+
51
+ Attributes:
52
+ agent: Short agent name.
53
+ path: Path to the agent's identity/identity.json.
54
+ additions: Field → value mapping that would be written. Empty when
55
+ the agent is already complete (nothing to add).
56
+ applied: Whether the additions were actually written to disk.
57
+ error: Non-empty when the agent could not be processed (e.g. an
58
+ unreadable identity.json); such agents are skipped, not crashed.
59
+ """
60
+
61
+ agent: str
62
+ path: Path
63
+ additions: dict[str, str] = field(default_factory=dict)
64
+ applied: bool = False
65
+ error: str = ""
66
+
67
+ @property
68
+ def changed(self) -> bool:
69
+ """True when this agent has at least one field to add."""
70
+ return bool(self.additions)
71
+
72
+
73
+ @dataclass
74
+ class MigrationPlan:
75
+ """Aggregate plan across every walked agent.
76
+
77
+ Attributes:
78
+ home: Shared root that was walked (``~/.skcapstone``).
79
+ dry_run: True when nothing was written to disk.
80
+ cluster_found: Whether a cluster.json was located (realm/operator are
81
+ unavailable when False).
82
+ agents: Per-agent plans (one per provisioned, non-template agent).
83
+ """
84
+
85
+ home: Path
86
+ dry_run: bool
87
+ cluster_found: bool
88
+ agents: list[AgentPlan] = field(default_factory=list)
89
+
90
+ @property
91
+ def changed_count(self) -> int:
92
+ """Number of agents with at least one field to add."""
93
+ return sum(1 for a in self.agents if a.changed)
94
+
95
+ @property
96
+ def unchanged_count(self) -> int:
97
+ """Number of already-complete agents (no additions, no error)."""
98
+ return sum(1 for a in self.agents if not a.changed and not a.error)
99
+
100
+ def to_dict(self) -> dict:
101
+ """Serialise to a JSON-friendly dict."""
102
+ return {
103
+ "home": str(self.home),
104
+ "dry_run": self.dry_run,
105
+ "cluster_found": self.cluster_found,
106
+ "changed": self.changed_count,
107
+ "unchanged": self.unchanged_count,
108
+ "agents": [
109
+ {
110
+ "agent": a.agent,
111
+ "path": str(a.path),
112
+ "additions": a.additions,
113
+ "applied": a.applied,
114
+ "error": a.error,
115
+ }
116
+ for a in self.agents
117
+ ],
118
+ }
119
+
120
+
121
+ def _load_cluster(home: Path) -> Optional[dict]:
122
+ """Load cluster.json from ``/etc/skcapstone`` then the agent home.
123
+
124
+ Mirrors :data:`capauth.agent_identity._CLUSTER_LOOKUP` but resolves the
125
+ home-local copy relative to *home* so a test (or alternate root) reads the
126
+ fixture cluster.json rather than the real ``~/.skcapstone`` one.
127
+
128
+ Args:
129
+ home: Shared root directory being walked.
130
+
131
+ Returns:
132
+ The parsed cluster dict, or ``None`` when no cluster.json exists or it
133
+ cannot be parsed.
134
+ """
135
+ for path in [*_CLUSTER_LOOKUP, home / "cluster.json"]:
136
+ if path.exists():
137
+ try:
138
+ return json.loads(path.read_text(encoding="utf-8"))
139
+ except (json.JSONDecodeError, OSError):
140
+ continue
141
+ return None
142
+
143
+
144
+ def _provisioned_agents(home: Path) -> list[str]:
145
+ """List agents with a CapAuth home (and thus a real identity).
146
+
147
+ Reuses the "provisioned agent" notion from
148
+ :func:`skcapstone.doctor._provisioned_agents`: an agent counts only when
149
+ ``agents/<name>/capauth/`` exists, and ``*-template`` scaffolds are
150
+ excluded.
151
+
152
+ Args:
153
+ home: Shared root directory (``~/.skcapstone``).
154
+
155
+ Returns:
156
+ Sorted provisioned agent names.
157
+ """
158
+ from ..doctor import _provisioned_agents as _doctor_provisioned
159
+
160
+ return _doctor_provisioned(home)
161
+
162
+
163
+ def _plan_agent(home: Path, agent: str, cluster: Optional[dict]) -> AgentPlan:
164
+ """Compute the identity.json additions for one agent.
165
+
166
+ Reads the agent's current ``identity/identity.json`` and determines which
167
+ of ``realm``/``operator``/``fqid``/``pgp_fingerprint`` are missing, using
168
+ cluster.json (realm/operator) and ``capauth.resolve_agent_identity`` (fqid
169
+ + fingerprint) as the source of truth. Existing values are never
170
+ overwritten.
171
+
172
+ Args:
173
+ home: Shared root directory.
174
+ agent: Short agent name.
175
+ cluster: Parsed cluster.json dict, or ``None``.
176
+
177
+ Returns:
178
+ An :class:`AgentPlan` describing the additions (empty when complete or
179
+ when no source value is available), or carrying an ``error`` when the
180
+ identity.json is unreadable.
181
+ """
182
+ path = home / "agents" / agent / "identity" / "identity.json"
183
+ plan = AgentPlan(agent=agent, path=path)
184
+
185
+ existing: dict = {}
186
+ if path.exists():
187
+ try:
188
+ loaded = json.loads(path.read_text(encoding="utf-8"))
189
+ if isinstance(loaded, dict):
190
+ existing = loaded
191
+ except (json.JSONDecodeError, OSError) as exc:
192
+ plan.error = f"unreadable identity.json: {exc}"
193
+ return plan
194
+
195
+ # realm / operator come straight from cluster.json (cluster facts).
196
+ desired: dict[str, Optional[str]] = {}
197
+ if cluster is not None:
198
+ desired["realm"] = cluster.get("realm")
199
+ desired["operator"] = cluster.get("operator")
200
+
201
+ # fqid + pgp_fingerprint come from the canonical resolver — never
202
+ # reimplemented here (epic 2b264064; capauth is the source of truth).
203
+ try:
204
+ from capauth import resolve_agent_identity
205
+
206
+ ident = resolve_agent_identity(agent)
207
+ desired["fqid"] = getattr(ident, "fqid", None)
208
+ desired["pgp_fingerprint"] = getattr(ident, "fingerprint", None)
209
+ except Exception: # noqa: BLE001 — resolver failure must not crash the walk
210
+ pass
211
+
212
+ for key in _MANAGED_FIELDS:
213
+ value = desired.get(key)
214
+ # Only add when we have a real value AND it is not already present.
215
+ if value and not existing.get(key):
216
+ plan.additions[key] = str(value)
217
+
218
+ return plan
219
+
220
+
221
+ def migrate_identities(home: Path, *, apply: bool = False) -> MigrationPlan:
222
+ """Walk provisioned agents and backfill their identity.json.
223
+
224
+ For every provisioned agent (one with a CapAuth home, never a
225
+ ``*-template``), ensure its ``identity/identity.json`` carries ``realm``,
226
+ ``operator``, ``fqid`` and ``pgp_fingerprint``. Missing fields are merged
227
+ in without clobbering unrelated keys; files are only written when something
228
+ actually changed.
229
+
230
+ Args:
231
+ home: Shared root directory (``~/.skcapstone``).
232
+ apply: When ``True``, write the changes to disk. When ``False`` (the
233
+ default — these are live files), nothing is written and the
234
+ returned plan is a preview only.
235
+
236
+ Returns:
237
+ A :class:`MigrationPlan` describing per-agent additions and whether
238
+ each was applied.
239
+
240
+ Examples:
241
+ >>> plan = migrate_identities(Path("~/.skcapstone").expanduser())
242
+ >>> plan.dry_run
243
+ True
244
+ """
245
+ cluster = _load_cluster(home)
246
+ plan = MigrationPlan(
247
+ home=home,
248
+ dry_run=not apply,
249
+ cluster_found=cluster is not None,
250
+ )
251
+
252
+ for agent in _provisioned_agents(home):
253
+ agent_plan = _plan_agent(home, agent, cluster)
254
+ if apply and agent_plan.changed and not agent_plan.error:
255
+ try:
256
+ _apply_additions(agent_plan)
257
+ agent_plan.applied = True
258
+ except OSError as exc:
259
+ agent_plan.error = f"write failed: {exc}"
260
+ plan.agents.append(agent_plan)
261
+
262
+ return plan
263
+
264
+
265
+ def _apply_additions(plan: AgentPlan) -> None:
266
+ """Merge a plan's additions into its identity.json on disk.
267
+
268
+ Reads the current file (or starts from ``{}`` if absent), updates only the
269
+ planned keys, and writes the result back with stable indentation. Unrelated
270
+ keys are preserved.
271
+
272
+ Args:
273
+ plan: The agent plan whose ``additions`` should be persisted.
274
+
275
+ Raises:
276
+ OSError: If the file cannot be read or written.
277
+ """
278
+ data: dict = {}
279
+ if plan.path.exists():
280
+ try:
281
+ loaded = json.loads(plan.path.read_text(encoding="utf-8"))
282
+ if isinstance(loaded, dict):
283
+ data = loaded
284
+ except json.JSONDecodeError:
285
+ data = {}
286
+ data.update(plan.additions)
287
+ plan.path.parent.mkdir(parents=True, exist_ok=True)
288
+ plan.path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
289
+
290
+
291
+ def register_identity_commands(main: click.Group) -> None:
292
+ """Register the ``identity`` command group on the main CLI."""
293
+
294
+ @main.group()
295
+ def identity():
296
+ """Identity management — migrate per-agent identity.json files."""
297
+
298
+ @identity.command("migrate")
299
+ @click.option(
300
+ "--home", default=SHARED_ROOT, type=click.Path(),
301
+ help="Shared root directory (~/.skcapstone).",
302
+ )
303
+ @click.option(
304
+ "--apply", "--write", "apply_", is_flag=True,
305
+ help="Actually write changes. Default is a dry-run (writes nothing).",
306
+ )
307
+ @click.option(
308
+ "--dry-run", is_flag=True,
309
+ help="Explicitly preview only (the default). Overrides --apply if both given.",
310
+ )
311
+ @click.option("--json-out", is_flag=True, help="Output as machine-readable JSON.")
312
+ def migrate(home: str, apply_: bool, dry_run: bool, json_out: bool) -> None:
313
+ """Backfill realm/operator/fqid/pgp_fingerprint into agent identity.json.
314
+
315
+ Walks every provisioned agent (one with a CapAuth home, excluding
316
+ ``*-template`` dirs) under ``~/.skcapstone/agents/`` and ensures each
317
+ agent's ``identity/identity.json`` carries the explicit sovereign
318
+ fields. Delegates to ``capauth.resolve_agent_identity`` for the fqid
319
+ and fingerprint; realm/operator are mirrored from cluster.json.
320
+
321
+ SAFETY: defaults to a dry-run (prints a plan, writes nothing). Pass
322
+ ``--apply`` (or ``--write``) to actually modify the live identity
323
+ files. Idempotent — re-running on a complete home changes nothing.
324
+ """
325
+ home_path = Path(home).expanduser()
326
+ do_apply = apply_ and not dry_run
327
+ plan = migrate_identities(home_path, apply=do_apply)
328
+
329
+ if json_out:
330
+ click.echo(json.dumps(plan.to_dict(), indent=2))
331
+ return
332
+
333
+ _render_plan(plan)
334
+
335
+ return None
336
+
337
+
338
+ def _render_plan(plan: MigrationPlan) -> None:
339
+ """Render a migration plan as human-readable Rich output."""
340
+ mode = "[yellow]DRY-RUN[/] (no files written — pass --apply to write)" \
341
+ if plan.dry_run else "[green]APPLY[/] (files written)"
342
+ console.print()
343
+ console.print(f" [bold]identity migrate[/] {mode}")
344
+ console.print(f" [dim]{plan.home}[/]")
345
+ if not plan.cluster_found:
346
+ console.print(
347
+ " [yellow]~ cluster.json not found — realm/operator unavailable, "
348
+ "fqid may be incomplete[/]"
349
+ )
350
+ console.print()
351
+
352
+ if not plan.agents:
353
+ console.print(" [dim]No provisioned agents found (none with a CapAuth home).[/]")
354
+ console.print()
355
+ return
356
+
357
+ for a in plan.agents:
358
+ if a.error:
359
+ console.print(f" [red]✗ {a.agent}[/] {a.error}")
360
+ elif not a.changed:
361
+ console.print(f" [green]✓ {a.agent}[/] [dim]unchanged (already complete)[/]")
362
+ else:
363
+ verb = "added" if a.applied else "would add"
364
+ fields = ", ".join(f"{k}={v}" for k, v in a.additions.items())
365
+ color = "green" if a.applied else "cyan"
366
+ console.print(f" [{color}]→ {a.agent}[/] {verb}: {fields}")
367
+ console.print(f" [dim]{a.path}[/]")
368
+
369
+ console.print()
370
+ summary = (
371
+ f" {plan.changed_count} to change, {plan.unchanged_count} unchanged"
372
+ if plan.dry_run
373
+ else f" {plan.changed_count} changed, {plan.unchanged_count} unchanged"
374
+ )
375
+ console.print(f"[bold]{summary}[/]")
376
+ if plan.dry_run and plan.changed_count:
377
+ console.print(" [dim]Re-run with --apply to write these changes.[/]")
378
+ console.print()
@@ -439,7 +439,7 @@ def register_joule_commands(main: click.Group) -> None:
439
439
  @joule_group.command("dashboard")
440
440
  @click.option(
441
441
  "--agent", "-a", "agent_name", default=None,
442
- help="Agent name (default: lumina).",
442
+ help="Agent name (default: current agent).",
443
443
  )
444
444
  def dashboard_cmd(agent_name: str | None):
445
445
  """Show a financial dashboard for an agent."""
@@ -451,7 +451,9 @@ def register_joule_commands(main: click.Group) -> None:
451
451
 
452
452
  from ..skjoule import JouleEngine, TransactionKind
453
453
 
454
- agent_name = agent_name or "lumina"
454
+ from .. import active_agent_name
455
+
456
+ agent_name = agent_name or active_agent_name()
455
457
  engine = JouleEngine(home=Path(SHARED_ROOT).expanduser())
456
458
  wallet = engine.get_wallet(agent_name)
457
459
  balance = wallet.balance
@@ -624,4 +626,6 @@ def _resolve_agent(agent_name: str | None) -> str:
624
626
  if agent_name:
625
627
  return agent_name
626
628
  from .. import SKCAPSTONE_AGENT
627
- return SKCAPSTONE_AGENT or "lumina"
629
+ from .. import active_agent_name
630
+
631
+ return SKCAPSTONE_AGENT or active_agent_name() or ""
@@ -423,7 +423,7 @@ def register_memory_commands(main: click.Group) -> None:
423
423
  @memory.command("rehydrate")
424
424
  @click.option("--home", default=AGENT_HOME, type=click.Path())
425
425
  @click.option("--agent", "-a", default=None,
426
- help="Agent name (default: SKCAPSTONE_AGENT or 'lumina').")
426
+ help="Agent name (default: active agent).")
427
427
  @click.option("--febs-only", is_flag=True, help="Only ingest FEB files (trust rehydration).")
428
428
  @click.option("--memories-only", is_flag=True, help="Only ingest flat-file memories into backends.")
429
429
  @click.option("--force", is_flag=True, help="Re-ingest even if already in backend.")
@@ -439,7 +439,9 @@ def register_memory_commands(main: click.Group) -> None:
439
439
  import os
440
440
  from ..models import MemoryLayer
441
441
 
442
- agent_name = agent or os.environ.get("SKCAPSTONE_AGENT", "lumina")
442
+ from .. import active_agent_name
443
+
444
+ agent_name = agent or os.environ.get("SKCAPSTONE_AGENT") or active_agent_name()
443
445
  home_path = Path(home).expanduser()
444
446
  agent_home = home_path / "agents" / agent_name
445
447
 
@@ -463,8 +465,8 @@ def register_memory_commands(main: click.Group) -> None:
463
465
  try:
464
466
  from ..memory_adapter import get_unified, entry_to_memory
465
467
  unified = get_unified()
466
- except Exception:
467
- pass
468
+ except Exception as exc:
469
+ logger.warning("Memory adapter unavailable, falling back to file-only mode: %s", exc)
468
470
 
469
471
  for layer in MemoryLayer:
470
472
  layer_dir = mem_dir / layer.value
@@ -481,8 +483,8 @@ def register_memory_commands(main: click.Group) -> None:
481
483
  if existing:
482
484
  skipped += 1
483
485
  continue
484
- except Exception:
485
- pass
486
+ except Exception as exc:
487
+ logger.debug("Failed to check existing memory %s: %s", mem_id, exc)
486
488
 
487
489
  if unified:
488
490
  from ..models import MemoryEntry
@@ -19,7 +19,7 @@ def register_peers_dir_commands(main: click.Group) -> None:
19
19
  def peers_dir():
20
20
  """Peer transport directory — routing addresses for the mesh.
21
21
 
22
- SKComm transport endpoints. For identity/trust peers, see 'peer'."""
22
+ SKComms transport endpoints. For identity/trust peers, see 'peer'."""
23
23
 
24
24
  @peers_dir.command("list")
25
25
  @click.option("--home", "sk_home", default=AGENT_HOME, type=click.Path())
@@ -1,7 +1,7 @@
1
1
  """Register command — auto-register SK* skills and MCP servers.
2
2
 
3
3
  Detects the user's environments (OpenClaw, Claude Code, Cursor, VS Code,
4
- OpenCode CLI, mcporter) and registers SKILL.md symlinks + MCP server entries.
4
+ OpenCode CLI, Codex, mcporter) and registers SKILL.md symlinks + MCP server entries.
5
5
 
6
6
  Commands:
7
7
  skcapstone register — register all SK* packages
@@ -49,7 +49,7 @@ def register_register_commands(main: click.Group) -> None:
49
49
  """Register all SK* skills and MCP servers in detected environments.
50
50
 
51
51
  Auto-detects your development environments (Claude Code, Cursor,
52
- VS Code, OpenClaw, OpenCode, mcporter) and ensures all SK* skill
52
+ VS Code, OpenClaw, OpenCode, Codex, mcporter) and ensures all SK* skill
53
53
  manifests and MCP server entries are properly configured.
54
54
 
55
55
  Examples:
@@ -91,12 +91,23 @@ def register_register_commands(main: click.Group) -> None:
91
91
  dry_run=dry_run,
92
92
  )
93
93
 
94
+ # Register Claude Code hooks
95
+ if not dry_run:
96
+ try:
97
+ from skmemory.register import register_hooks
98
+ register_hooks(install_hooks=True)
99
+ except ImportError:
100
+ pass
101
+ except Exception:
102
+ pass
103
+
94
104
  # Display results
95
105
  from rich.table import Table
96
106
 
97
107
  table = Table(show_header=True, header_style="bold", box=None, padding=(0, 2))
98
108
  table.add_column("Package", style="cyan")
99
109
  table.add_column("Skill", style="dim")
110
+ table.add_column("Codex")
100
111
  table.add_column("MCP")
101
112
  table.add_column("OpenClaw Plugin")
102
113
 
@@ -133,6 +144,21 @@ def register_register_commands(main: click.Group) -> None:
133
144
  else:
134
145
  mcp_str = str(mcp_info)
135
146
 
147
+ codex_info = pkg_result.get("codex_skill", {})
148
+ codex_action = codex_info.get("action", "")
149
+ if codex_action == "created":
150
+ codex_str = "[green]created[/]"
151
+ elif codex_action == "exists":
152
+ codex_str = "[dim]exists[/]"
153
+ elif codex_action == "dry-run":
154
+ codex_str = "[yellow]would create[/]"
155
+ elif codex_action == "error":
156
+ codex_str = f"[red]{codex_info.get('error', 'error')}[/]"
157
+ elif not codex_action:
158
+ codex_str = "[dim]—[/]"
159
+ else:
160
+ codex_str = f"[dim]{codex_action}[/]"
161
+
136
162
  plugin_action = pkg_result.get("openclaw_plugin", "")
137
163
  if plugin_action == "created":
138
164
  plugin_str = "[green]created[/]"
@@ -147,7 +173,7 @@ def register_register_commands(main: click.Group) -> None:
147
173
  else:
148
174
  plugin_str = f"[dim]{plugin_action}[/]"
149
175
 
150
- table.add_row(name, skill_str, mcp_str, plugin_str)
176
+ table.add_row(name, skill_str, codex_str, mcp_str, plugin_str)
151
177
 
152
178
  console.print(table)
153
179
  console.print()