@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
@@ -21,6 +21,7 @@ Steps:
21
21
  from __future__ import annotations
22
22
 
23
23
  import json
24
+ import logging
24
25
  import sys
25
26
  import time
26
27
  from datetime import datetime, timezone
@@ -28,6 +29,8 @@ from pathlib import Path
28
29
  from typing import Optional
29
30
 
30
31
  import click
32
+
33
+ logger = logging.getLogger(__name__)
31
34
  from rich.console import Console
32
35
  from rich.panel import Panel
33
36
  from rich.prompt import Confirm, Prompt
@@ -39,7 +42,7 @@ from . import AGENT_HOME, __version__
39
42
 
40
43
  console = Console()
41
44
 
42
- TOTAL_STEPS = 13 # excludes welcome + celebrate; includes 4 new system-setup steps
45
+ TOTAL_STEPS = 16 # excludes welcome + celebrate; includes pillar install + import step
43
46
 
44
47
 
45
48
  def _step_header(n: int, title: str) -> None:
@@ -90,6 +93,7 @@ def _step_identity(home_path: Path, name: str, email: str | None) -> tuple[str,
90
93
  from .pillars.memory import initialize_memory
91
94
  from .pillars.sync import initialize_sync
92
95
  from .models import AgentConfig, SyncConfig
96
+ from .operator_link import build_agent_manifest, discover_human_operator
93
97
  from .soul import SoulManager
94
98
 
95
99
  with Status(" Generating PGP identity…", console=console, spinner="dots") as s:
@@ -155,12 +159,11 @@ def _step_identity(home_path: Path, name: str, email: str | None) -> tuple[str,
155
159
  for d in skeleton_dirs:
156
160
  d.mkdir(parents=True, exist_ok=True)
157
161
 
158
- manifest = {
159
- "name": name,
160
- "version": __version__,
161
- "created_at": datetime.now(timezone.utc).isoformat(),
162
- "connectors": [],
163
- }
162
+ manifest = build_agent_manifest(
163
+ name,
164
+ __version__,
165
+ operator=discover_human_operator(),
166
+ )
164
167
  (home_path / "manifest.json").write_text(
165
168
  json.dumps(manifest, indent=2), encoding="utf-8"
166
169
  )
@@ -476,67 +479,460 @@ def _step_prereqs() -> dict:
476
479
  return results
477
480
 
478
481
 
479
- def _step_ollama_models(prereqs: dict) -> bool:
480
- """Pull the default Ollama model (llama3.2).
482
+ # Pillar packages: (import_name, pip_name, description)
483
+ _PILLAR_PACKAGES = [
484
+ ("capauth", "capauth", "PGP-based sovereign identity"),
485
+ ("skcomms", "skcomms", "Redundant agent communication"),
486
+ ("skchat", "skchat-sovereign", "Encrypted P2P chat"),
487
+ ("skseed", "skseed", "Cloud 9 seeds & LLM callbacks"),
488
+ ("sksecurity", "sksecurity", "Audit logging & threat detection"),
489
+ ("pgpy", "pgpy", "PGP cryptography (PGPy backend)"),
490
+ ("skwhisper", "skwhisper", "Subconscious memory layer (session digester)"),
491
+ ]
492
+
493
+
494
+ def _step_install_pillars() -> dict:
495
+ """Detect missing pillar packages and offer to install them.
496
+
497
+ Returns:
498
+ dict mapping pip_name -> bool (installed successfully).
499
+ """
500
+ import subprocess
501
+
502
+ results = {}
503
+ missing = []
504
+
505
+ click.echo(click.style(" Checking pillar packages…", fg="bright_black"))
506
+ for import_name, pip_name, description in _PILLAR_PACKAGES:
507
+ try:
508
+ __import__(import_name)
509
+ click.echo(click.style(" ✓ ", fg="green") + f"{pip_name} — {description}")
510
+ results[pip_name] = True
511
+ except ImportError:
512
+ click.echo(click.style(" ✗ ", fg="red") + f"{pip_name} — {description} [bold red](missing)[/]")
513
+ missing.append((import_name, pip_name, description))
514
+ results[pip_name] = False
515
+
516
+ if not missing:
517
+ click.echo()
518
+ click.echo(click.style(" ✓ ", fg="green") + "All pillar packages installed")
519
+ return results
520
+
521
+ click.echo()
522
+ click.echo(
523
+ click.style(" ℹ ", fg="cyan")
524
+ + f"{len(missing)} pillar(s) missing. These are needed for full sovereign functionality."
525
+ )
526
+
527
+ choices = {
528
+ "a": "Install all missing pillars",
529
+ "s": "Select which to install",
530
+ "n": "Skip (install later manually)",
531
+ }
532
+ for key, desc in choices.items():
533
+ click.echo(f" [{key}] {desc}")
534
+ choice = click.prompt(" Choice", default="a", show_choices=False).strip().lower()
535
+
536
+ to_install: list[tuple[str, str, str]] = []
537
+ if choice == "a":
538
+ to_install = missing
539
+ elif choice == "s":
540
+ for import_name, pip_name, description in missing:
541
+ if click.confirm(f" Install {pip_name} ({description})?", default=True):
542
+ to_install.append((import_name, pip_name, description))
543
+ else:
544
+ click.echo(click.style(" ↷ ", fg="bright_black") + "Skipped — install later:")
545
+ for _, pip_name, _ in missing:
546
+ click.echo(click.style(" ", fg="bright_black") + f"pip install {pip_name}")
547
+ return results
548
+
549
+ if not to_install:
550
+ return results
551
+
552
+ # Determine pip command — prefer ~/.skenv if it exists, else use current Python
553
+ import os as _os
554
+ skenv_pip = Path(_os.path.expanduser("~/.skenv/bin/pip"))
555
+ if skenv_pip.exists():
556
+ pip_cmd = [str(skenv_pip), "install"]
557
+ else:
558
+ pip_cmd = [sys.executable, "-m", "pip", "install", "--break-system-packages"]
559
+
560
+ for import_name, pip_name, description in to_install:
561
+ click.echo(click.style(" ↓ ", fg="cyan") + f"Installing {pip_name}…")
562
+ try:
563
+ r = subprocess.run(
564
+ [*pip_cmd, pip_name, "-q"],
565
+ capture_output=True, text=True, timeout=120,
566
+ )
567
+ if r.returncode == 0:
568
+ click.echo(click.style(" ✓ ", fg="green") + f"{pip_name} installed")
569
+ results[pip_name] = True
570
+ else:
571
+ click.echo(click.style(" ✗ ", fg="red") + f"{pip_name} failed: {r.stderr.strip()[:100]}")
572
+ click.echo(click.style(" ", fg="bright_black") + f"Try manually: pip install {pip_name}")
573
+ except subprocess.TimeoutExpired:
574
+ click.echo(click.style(" ⚠ ", fg="yellow") + f"{pip_name} timed out")
575
+ except Exception as exc:
576
+ click.echo(click.style(" ⚠ ", fg="yellow") + f"{pip_name}: {exc}")
577
+
578
+ return results
579
+
580
+
581
+ # ---------------------------------------------------------------------------
582
+ # Import sources — detect and import from existing agent platforms
583
+ # ---------------------------------------------------------------------------
584
+
585
+ # (source_id, display_name, detect_func, import_func_key)
586
+ _IMPORT_SOURCES: list[tuple[str, str, str]] = [
587
+ ("openclaw", "OpenClaw (Jarvis)", "~/.openclaw/workspace"),
588
+ ("claude", "Claude Code", "~/.claude"),
589
+ ("cloud9", "Cloud 9 FEB Templates", ""), # always available if cloud9 installed
590
+ ]
591
+
592
+
593
+ def _detect_import_sources(home_path: Path) -> list[dict]:
594
+ """Detect available sources for importing memories, soul, and trust data.
595
+
596
+ Returns:
597
+ List of dicts with 'id', 'name', 'available', 'detail', 'items'.
598
+ """
599
+ sources = []
600
+
601
+ # --- OpenClaw ---
602
+ oc_workspace = Path.home() / ".openclaw" / "workspace"
603
+ oc_memory = oc_workspace / "memory"
604
+ oc_soul = oc_workspace / "SOUL.md"
605
+ oc_identity = oc_workspace / "IDENTITY.md"
606
+ oc_agents = oc_workspace / "agents"
607
+ if oc_workspace.exists():
608
+ items = []
609
+ if oc_memory.exists():
610
+ mem_files = list(oc_memory.glob("*.md"))
611
+ items.append(f"{len(mem_files)} memory files")
612
+ if oc_soul.exists():
613
+ items.append("SOUL.md")
614
+ if oc_identity.exists():
615
+ items.append("IDENTITY.md")
616
+ if oc_agents.exists():
617
+ agent_souls = list(oc_agents.rglob("SOUL.md"))
618
+ if agent_souls:
619
+ items.append(f"{len(agent_souls)} agent soul(s)")
620
+ sources.append({
621
+ "id": "openclaw",
622
+ "name": "OpenClaw (Jarvis)",
623
+ "available": True,
624
+ "detail": ", ".join(items) if items else "workspace found",
625
+ "paths": {
626
+ "memory": oc_memory,
627
+ "soul": oc_soul,
628
+ "identity": oc_identity,
629
+ "agents": oc_agents,
630
+ "workspace": oc_workspace,
631
+ },
632
+ })
633
+
634
+ # --- Claude Code ---
635
+ claude_dir = Path.home() / ".claude"
636
+ claude_memory = None
637
+ if claude_dir.exists():
638
+ # Find project memory dirs
639
+ projects = claude_dir / "projects"
640
+ items = []
641
+ if projects.exists():
642
+ for proj_dir in projects.iterdir():
643
+ mem_dir = proj_dir / "memory"
644
+ if mem_dir.exists() and list(mem_dir.glob("*.md")):
645
+ mem_files = list(mem_dir.glob("*.md"))
646
+ items.append(f"{len(mem_files)} memory file(s) in {proj_dir.name}")
647
+ claude_memory = mem_dir
648
+ memory_md = proj_dir / "MEMORY.md"
649
+ if memory_md.exists():
650
+ items.append(f"MEMORY.md in {proj_dir.name}")
651
+ if items:
652
+ sources.append({
653
+ "id": "claude",
654
+ "name": "Claude Code",
655
+ "available": True,
656
+ "detail": ", ".join(items),
657
+ "paths": {"memory": claude_memory, "projects": projects},
658
+ })
659
+
660
+ # --- Cloud 9 FEB Templates ---
661
+ try:
662
+ import cloud9
663
+ c9_pkg = Path(cloud9.__file__).parent
664
+ feb_files = list(c9_pkg.rglob("*.feb"))
665
+ # Also check skcapstone defaults
666
+ defaults_dir = Path(__file__).parent / "defaults"
667
+ if defaults_dir.exists():
668
+ feb_files.extend(defaults_dir.rglob("*.feb"))
669
+ # Check user cloud9 dirs
670
+ for cloud9_dir in [Path.home() / ".cloud9" / "febs", Path.home() / ".cloud9" / "feb-backups"]:
671
+ if cloud9_dir.exists():
672
+ feb_files.extend(cloud9_dir.glob("*.feb"))
673
+ if feb_files:
674
+ sources.append({
675
+ "id": "cloud9",
676
+ "name": "Cloud 9 FEB Templates",
677
+ "available": True,
678
+ "detail": f"{len(feb_files)} FEB file(s)",
679
+ "paths": {"febs": feb_files},
680
+ })
681
+ except ImportError:
682
+ pass
683
+
684
+ return sources
685
+
686
+
687
+ def _step_import_sources(home_path: Path) -> dict:
688
+ """Detect and import data from existing agent platforms.
689
+
690
+ Args:
691
+ home_path: Agent home directory.
692
+
693
+ Returns:
694
+ dict with 'imported_count' (int) and 'sources' (list of imported source ids).
695
+ """
696
+ import shutil as _shutil
697
+
698
+ result = {"imported_count": 0, "sources": []}
699
+
700
+ click.echo(click.style(" Scanning for existing agent data…", fg="bright_black"))
701
+ sources = _detect_import_sources(home_path)
702
+
703
+ if not sources:
704
+ click.echo(click.style(" ℹ ", fg="cyan") + "No existing agent data found — starting fresh")
705
+ return result
706
+
707
+ click.echo()
708
+ for i, src in enumerate(sources, 1):
709
+ click.echo(
710
+ click.style(f" {i}. ", fg="cyan")
711
+ + f"[bold]{src['name']}[/] — {src['detail']}"
712
+ )
713
+ click.echo()
714
+
715
+ choices = {
716
+ "a": "Import from all sources",
717
+ "s": "Select which to import",
718
+ "n": "Skip (start fresh)",
719
+ }
720
+ for key, desc in choices.items():
721
+ click.echo(f" [{key}] {desc}")
722
+ choice = click.prompt(" Choice", default="a", show_choices=False).strip().lower()
723
+
724
+ to_import: list[dict] = []
725
+ if choice == "a":
726
+ to_import = sources
727
+ elif choice == "s":
728
+ for src in sources:
729
+ if click.confirm(f" Import from {src['name']}?", default=True):
730
+ to_import.append(src)
731
+ else:
732
+ click.echo(click.style(" ↷ ", fg="bright_black") + "Skipped — starting fresh")
733
+ return result
734
+
735
+ if not to_import:
736
+ return result
737
+
738
+ # --- Execute imports ---
739
+ for src in to_import:
740
+ sid = src["id"]
741
+ paths = src.get("paths", {})
742
+ count = 0
743
+
744
+ if sid == "openclaw":
745
+ # Import memories
746
+ mem_src = paths.get("memory")
747
+ if mem_src and mem_src.exists():
748
+ mem_dest = home_path / "memory" / "imported" / "openclaw"
749
+ mem_dest.mkdir(parents=True, exist_ok=True)
750
+ for f in mem_src.glob("*.md"):
751
+ _shutil.copy2(f, mem_dest / f.name)
752
+ count += 1
753
+ click.echo(click.style(" ✓ ", fg="green") + f"Imported {count} memory files from OpenClaw")
754
+
755
+ # Import soul/identity
756
+ for doc_name in ("soul", "identity"):
757
+ doc_path = paths.get(doc_name)
758
+ if doc_path and doc_path.exists():
759
+ dest = home_path / "memory" / "imported" / "openclaw" / doc_path.name
760
+ dest.parent.mkdir(parents=True, exist_ok=True)
761
+ _shutil.copy2(doc_path, dest)
762
+ count += 1
763
+ click.echo(click.style(" ✓ ", fg="green") + f"Imported {doc_path.name} from OpenClaw")
764
+
765
+ # Import agent souls
766
+ agents_dir = paths.get("agents")
767
+ if agents_dir and agents_dir.exists():
768
+ agent_dest = home_path / "memory" / "imported" / "openclaw" / "agents"
769
+ agent_dest.mkdir(parents=True, exist_ok=True)
770
+ for soul_file in agents_dir.rglob("SOUL.md"):
771
+ agent_name = soul_file.parent.name
772
+ target = agent_dest / f"{agent_name}-SOUL.md"
773
+ _shutil.copy2(soul_file, target)
774
+ count += 1
775
+ for mem_file in agents_dir.rglob("MEMORY.md"):
776
+ agent_name = mem_file.parent.name
777
+ target = agent_dest / f"{agent_name}-MEMORY.md"
778
+ _shutil.copy2(mem_file, target)
779
+ count += 1
780
+ click.echo(click.style(" ✓ ", fg="green") + f"Imported agent souls/memories from OpenClaw")
781
+
782
+ elif sid == "claude":
783
+ # Import Claude memory files
784
+ projects_dir = paths.get("projects")
785
+ if projects_dir and projects_dir.exists():
786
+ claude_dest = home_path / "memory" / "imported" / "claude-code"
787
+ claude_dest.mkdir(parents=True, exist_ok=True)
788
+ for proj_dir in projects_dir.iterdir():
789
+ mem_dir = proj_dir / "memory"
790
+ if mem_dir.exists():
791
+ for f in mem_dir.glob("*.md"):
792
+ _shutil.copy2(f, claude_dest / f.name)
793
+ count += 1
794
+ memory_md = proj_dir / "MEMORY.md"
795
+ if memory_md.exists():
796
+ _shutil.copy2(memory_md, claude_dest / f"{proj_dir.name}-MEMORY.md")
797
+ count += 1
798
+ if count:
799
+ click.echo(click.style(" ✓ ", fg="green") + f"Imported {count} files from Claude Code")
800
+
801
+ elif sid == "cloud9":
802
+ # Import FEB files into trust/febs
803
+ febs = paths.get("febs", [])
804
+ if febs:
805
+ febs_dest = home_path / "trust" / "febs"
806
+ febs_dest.mkdir(parents=True, exist_ok=True)
807
+ for feb_path in febs:
808
+ if isinstance(feb_path, Path) and feb_path.exists():
809
+ _shutil.copy2(feb_path, febs_dest / feb_path.name)
810
+ count += 1
811
+ click.echo(click.style(" ✓ ", fg="green") + f"Imported {count} FEB file(s) into trust chain")
812
+
813
+ result["imported_count"] += count
814
+ if count > 0:
815
+ result["sources"].append(sid)
816
+
817
+ click.echo()
818
+ click.echo(
819
+ click.style(" ✓ ", fg="green")
820
+ + f"Total: {result['imported_count']} file(s) imported from {len(result['sources'])} source(s)"
821
+ )
822
+ click.echo(click.style(" ", fg="bright_black") + f"Imported data: {home_path / 'memory' / 'imported'}")
823
+
824
+ return result
825
+
826
+
827
+ def _step_ollama_models(prereqs: dict) -> dict:
828
+ """Configure Ollama host, choose a model, and pull it.
481
829
 
482
830
  Args:
483
831
  prereqs: Result dict from _step_prereqs().
484
832
 
485
833
  Returns:
486
- True if model is available.
834
+ dict with 'ok' (bool), 'model' (str), 'host' (str).
487
835
  """
488
836
  import subprocess
489
837
 
490
838
  DEFAULT_MODEL = "llama3.2"
839
+ DEFAULT_HOST = "http://localhost:11434"
840
+
841
+ result = {"ok": False, "model": DEFAULT_MODEL, "host": DEFAULT_HOST}
491
842
 
492
843
  if not prereqs.get("ollama"):
493
844
  click.echo(click.style(" ⚠ ", fg="yellow") + "Ollama not available — skipping model pull")
845
+ click.echo(click.style(" ", fg="bright_black") + "Install: curl -fsSL https://ollama.ai/install.sh | sh")
494
846
  click.echo(click.style(" ", fg="bright_black") + f"Pull later: ollama pull {DEFAULT_MODEL}")
495
- return False
847
+ return result
848
+
849
+ # --- Ollama Host ---
850
+ click.echo(click.style(" ℹ ", fg="cyan") + f"Ollama is used for local/private LLM inference.")
851
+ click.echo(click.style(" ", fg="bright_black") + f"Default: {DEFAULT_HOST}")
852
+ custom_host = click.prompt(
853
+ " Ollama host URL",
854
+ default=DEFAULT_HOST,
855
+ show_default=True,
856
+ )
857
+ result["host"] = custom_host.rstrip("/")
496
858
 
497
- # Check if model already present
859
+ # Set env for this session so ollama CLI uses the right host
860
+ env = dict(**__import__("os").environ)
861
+ if result["host"] != DEFAULT_HOST:
862
+ env["OLLAMA_HOST"] = result["host"]
863
+ click.echo(click.style(" ✓ ", fg="green") + f"Using Ollama at: [cyan]{result['host']}[/]")
864
+
865
+ # --- List available models ---
866
+ available_models: list[str] = []
498
867
  try:
499
868
  r = subprocess.run(
500
869
  ["ollama", "list"],
501
- capture_output=True, text=True, timeout=10,
870
+ capture_output=True, text=True, timeout=10, env=env,
502
871
  )
503
- if DEFAULT_MODEL in (r.stdout or ""):
504
- click.echo(click.style("", fg="green") + f"{DEFAULT_MODEL} already present")
505
- return True
506
- except Exception:
507
- pass
872
+ if r.returncode == 0 and r.stdout.strip():
873
+ lines = r.stdout.strip().split("\n")[1:] # skip header
874
+ for line in lines:
875
+ model_name = line.split()[0] if line.strip() else ""
876
+ if model_name:
877
+ available_models.append(model_name)
878
+ except Exception as exc:
879
+ logger.debug("Failed to list ollama models: %s", exc)
508
880
 
509
- if not click.confirm(f" Pull default model ({DEFAULT_MODEL}, ~2 GB)?", default=True):
510
- click.echo(click.style(" ", fg="bright_black") + f"Skipped pull later: ollama pull {DEFAULT_MODEL}")
511
- return False
881
+ if available_models:
882
+ click.echo(click.style(" ", fg="cyan") + "Models already available:")
883
+ for m in available_models[:10]:
884
+ click.echo(click.style(" ", fg="bright_black") + m)
885
+
886
+ # --- Choose model ---
887
+ click.echo()
888
+ click.echo(click.style(" ℹ ", fg="cyan") + "Popular models: llama3.2 (~2GB), qwen3:14b (~9GB), deepseek-r1:14b (~9GB)")
889
+ chosen = click.prompt(
890
+ " Model to use",
891
+ default=DEFAULT_MODEL,
892
+ show_default=True,
893
+ )
894
+ result["model"] = chosen
512
895
 
513
- click.echo(click.style(" ↓ ", fg="cyan") + f"Pulling {DEFAULT_MODEL} (this may take a few minutes)…")
896
+ # Check if already present
897
+ if any(chosen in m for m in available_models):
898
+ click.echo(click.style(" ✓ ", fg="green") + f"{chosen} already present")
899
+ result["ok"] = True
900
+ return result
901
+
902
+ # --- Pull ---
903
+ if not click.confirm(f" Pull {chosen}? (this may take a few minutes)", default=True):
904
+ click.echo(click.style(" ↷ ", fg="bright_black") + f"Skipped — pull later: ollama pull {chosen}")
905
+ return result
906
+
907
+ click.echo(click.style(" ↓ ", fg="cyan") + f"Pulling {chosen}…")
514
908
  try:
515
- result = subprocess.run(
516
- ["ollama", "pull", DEFAULT_MODEL],
517
- timeout=600,
909
+ pull_result = subprocess.run(
910
+ ["ollama", "pull", chosen],
911
+ timeout=600, env=env,
518
912
  )
519
- if result.returncode == 0:
520
- click.echo(click.style(" ✓ ", fg="green") + f"{DEFAULT_MODEL} ready")
521
- return True
913
+ if pull_result.returncode == 0:
914
+ click.echo(click.style(" ✓ ", fg="green") + f"{chosen} ready")
915
+ result["ok"] = True
916
+ return result
522
917
  else:
523
- click.echo(click.style(" ✗ ", fg="red") + f"Pull failed (exit {result.returncode})")
524
- click.echo(click.style(" ", fg="bright_black") + f"Retry: ollama pull {DEFAULT_MODEL}")
525
- return False
918
+ click.echo(click.style(" ✗ ", fg="red") + f"Pull failed (exit {pull_result.returncode})")
919
+ click.echo(click.style(" ", fg="bright_black") + f"Retry: ollama pull {chosen}")
920
+ return result
526
921
  except subprocess.TimeoutExpired:
527
922
  click.echo(click.style(" ⚠ ", fg="yellow") + "Pull timed out — run manually later")
528
- click.echo(click.style(" ", fg="bright_black") + f"ollama pull {DEFAULT_MODEL}")
529
- return False
923
+ click.echo(click.style(" ", fg="bright_black") + f"ollama pull {chosen}")
924
+ return result
530
925
  except Exception as exc:
531
926
  click.echo(click.style(" ⚠ ", fg="yellow") + f"Pull error: {exc}")
532
- return False
927
+ return result
533
928
 
534
929
 
535
- def _step_config_files(home_path: Path) -> tuple:
930
+ def _step_config_files(home_path: Path, ollama_config: dict | None = None) -> tuple:
536
931
  """Write default consciousness.yaml and model_profiles.yaml.
537
932
 
538
933
  Args:
539
934
  home_path: Agent home directory.
935
+ ollama_config: Optional dict with 'host' and 'model' from Ollama step.
540
936
 
541
937
  Returns:
542
938
  (consciousness_ok, profiles_ok) booleans.
@@ -554,8 +950,17 @@ def _step_config_files(home_path: Path) -> tuple:
554
950
  else:
555
951
  try:
556
952
  from .consciousness_config import write_default_config
953
+ from .consciousness_loop import ConsciousnessConfig
557
954
 
558
- config_path = write_default_config(home_path)
955
+ # If user configured a custom Ollama host/model, patch the defaults
956
+ overrides = {}
957
+ if ollama_config:
958
+ if ollama_config.get("host") and ollama_config["host"] != "http://localhost:11434":
959
+ overrides["ollama_host"] = ollama_config["host"]
960
+ if ollama_config.get("model") and ollama_config["model"] != "llama3.2":
961
+ overrides["ollama_model"] = ollama_config["model"]
962
+
963
+ config_path = write_default_config(home_path, **overrides)
559
964
  click.echo(click.style(" ✓ ", fg="green") + f"consciousness.yaml written")
560
965
  click.echo(click.style(" ", fg="bright_black") + str(config_path))
561
966
  consciousness_ok = True
@@ -603,7 +1008,7 @@ def _step_autostart_service(agent_name: str = "sovereign") -> bool:
603
1008
  system = platform.system()
604
1009
 
605
1010
  if system == "Linux":
606
- return _step_systemd_service_linux()
1011
+ return _step_systemd_service_linux(agent_name)
607
1012
  elif system == "Darwin":
608
1013
  return _step_launchd_service_macos(agent_name)
609
1014
  else:
@@ -614,8 +1019,17 @@ def _step_autostart_service(agent_name: str = "sovereign") -> bool:
614
1019
  return False
615
1020
 
616
1021
 
617
- def _step_systemd_service_linux() -> bool:
618
- """Install systemd user service (Linux only)."""
1022
+ def _step_systemd_service_linux(agent_name: str = "sovereign") -> bool:
1023
+ """Install systemd user service for an agent (Linux only).
1024
+
1025
+ Uses the template unit ``skcapstone@.service`` so each agent
1026
+ gets its own independent service instance. Multiple agents can
1027
+ run simultaneously on the same machine.
1028
+
1029
+ Args:
1030
+ agent_name: Agent slug from onboarding (e.g. "jarvis").
1031
+ """
1032
+ service_name = f"skcapstone@{agent_name}.service"
619
1033
  if not click.confirm(" Install systemd user service for auto-start at login?", default=False):
620
1034
  click.echo(
621
1035
  click.style(" ↷ ", fg="bright_black")
@@ -631,12 +1045,15 @@ def _step_systemd_service_linux() -> bool:
631
1045
  click.echo(click.style(" ", fg="bright_black") + "Try: systemctl --user status")
632
1046
  return False
633
1047
 
634
- result = install_service(enable=True, start=False)
1048
+ result = install_service(agent_name=agent_name, enable=True, start=False)
635
1049
  if result.get("installed"):
636
- click.echo(click.style(" ✓ ", fg="green") + "Systemd service installed")
1050
+ click.echo(click.style(" ✓ ", fg="green") + f"Systemd service installed")
637
1051
  if result.get("enabled"):
638
- click.echo(click.style(" ✓ ", fg="green") + "Service enabled — auto-starts at login")
639
- click.echo(click.style(" ", fg="bright_black") + "Start now: systemctl --user start skcapstone")
1052
+ click.echo(click.style(" ✓ ", fg="green") + f"Service enabled — auto-starts at login")
1053
+ click.echo(
1054
+ click.style(" ", fg="bright_black")
1055
+ + f"Start now: systemctl --user start {service_name}"
1056
+ )
640
1057
  return True
641
1058
  else:
642
1059
  click.echo(click.style(" ✗ ", fg="red") + "Service install failed")
@@ -743,19 +1160,117 @@ def _step_launchd_service_macos(agent_name: str) -> bool:
743
1160
  return False
744
1161
 
745
1162
 
1163
+ def _step_shell_profile(
1164
+ home_path: Path, agent_name: str, agent_slug: str
1165
+ ) -> bool:
1166
+ """Write SKCAPSTONE profile environment variables to ~/.bashrc.
1167
+
1168
+ Asks the user whether to set this agent as the default profile.
1169
+ Appends SKCAPSTONE_HOME, SKCAPSTONE_AGENT, and PATH entries.
1170
+
1171
+ Args:
1172
+ home_path: Agent home directory.
1173
+ agent_name: Display name of the agent (e.g. "Jarvis").
1174
+ agent_slug: Slug form used for SKCAPSTONE_AGENT (e.g. "jarvis").
1175
+
1176
+ Returns:
1177
+ True if profile was written, False if skipped.
1178
+ """
1179
+ import os as _os
1180
+
1181
+ bashrc = Path.home() / ".bashrc"
1182
+ marker = "# --- SKCapstone profile ---"
1183
+
1184
+ # Check if profile block already exists
1185
+ existing = ""
1186
+ if bashrc.exists():
1187
+ existing = bashrc.read_text(encoding="utf-8")
1188
+ if marker in existing:
1189
+ _ok("SKCapstone profile already present in ~/.bashrc")
1190
+ # Offer to update it
1191
+ if not Confirm.ask(
1192
+ f" Update profile to agent [cyan]{agent_name}[/]?",
1193
+ default=True,
1194
+ ):
1195
+ return True
1196
+ # Remove old block so we can rewrite it
1197
+ lines = existing.splitlines(keepends=True)
1198
+ new_lines: list[str] = []
1199
+ skip = False
1200
+ for line in lines:
1201
+ if marker in line:
1202
+ skip = not skip # toggle on first marker, off on second
1203
+ continue
1204
+ if not skip:
1205
+ new_lines.append(line)
1206
+ existing = "".join(new_lines)
1207
+
1208
+ set_default = Confirm.ask(
1209
+ f" Set [cyan]{agent_name}[/] as default SKCAPSTONE_AGENT in ~/.bashrc?",
1210
+ default=True,
1211
+ )
1212
+
1213
+ if not set_default:
1214
+ _info("Skipped — set manually: export SKCAPSTONE_AGENT=<name>")
1215
+ return False
1216
+
1217
+ block = (
1218
+ f"\n{marker}\n"
1219
+ f'export SKCAPSTONE_HOME="{home_path}"\n'
1220
+ f'export SKCAPSTONE_AGENT="{agent_slug}"\n'
1221
+ f'export PATH="$HOME/.skenv/bin:$PATH"\n'
1222
+ f"{marker}\n"
1223
+ )
1224
+
1225
+ with open(bashrc, "a" if marker not in (existing or "") else "w", encoding="utf-8") as f:
1226
+ if marker not in (existing or ""):
1227
+ f.write(block)
1228
+ else:
1229
+ # Rewrite with updated block
1230
+ f.write(existing.rstrip("\n") + block)
1231
+
1232
+ _ok(f"~/.bashrc updated — SKCAPSTONE_AGENT={agent_slug}")
1233
+ _info("Run [bold]source ~/.bashrc[/] or open a new terminal to apply")
1234
+
1235
+ # Also export into current process so subsequent steps see it
1236
+ _os.environ["SKCAPSTONE_HOME"] = str(home_path)
1237
+ _os.environ["SKCAPSTONE_AGENT"] = agent_slug
1238
+
1239
+ return True
1240
+
1241
+
746
1242
  def _step_doctor_check(home_path: Path) -> "object":
747
1243
  """Run doctor diagnostics and print results.
748
1244
 
1245
+ Non-fatal — errors are logged as warnings but never block onboarding.
1246
+
749
1247
  Args:
750
1248
  home_path: Agent home directory.
751
1249
 
752
1250
  Returns:
753
- DiagnosticReport from doctor.run_diagnostics().
1251
+ DiagnosticReport from doctor.run_diagnostics(), or a stub on error.
754
1252
  """
755
- from .doctor import run_diagnostics
1253
+ try:
1254
+ from .doctor import run_diagnostics
1255
+ except Exception as exc:
1256
+ _warn(f"Could not load diagnostics module: {exc}")
1257
+ # Return a stub so the summary table still works
1258
+ from types import SimpleNamespace
1259
+
1260
+ return SimpleNamespace(
1261
+ all_passed=False, passed_count=0, failed_count=0, total_count=0, checks=[]
1262
+ )
756
1263
 
757
1264
  click.echo(click.style(" Running diagnostics…", fg="bright_black"))
758
- report = run_diagnostics(home_path)
1265
+ try:
1266
+ report = run_diagnostics(home_path)
1267
+ except Exception as exc:
1268
+ _warn(f"Diagnostics failed: {exc}")
1269
+ from types import SimpleNamespace
1270
+
1271
+ return SimpleNamespace(
1272
+ all_passed=False, passed_count=0, failed_count=0, total_count=0, checks=[]
1273
+ )
759
1274
 
760
1275
  categories_seen: set = set()
761
1276
  for check in report.checks:
@@ -781,44 +1296,61 @@ def _step_doctor_check(home_path: Path) -> "object":
781
1296
 
782
1297
 
783
1298
  def _step_test_consciousness(home_path: Path) -> bool:
784
- """Send a test message through the consciousness loop (optional).
1299
+ """Send a quick test message to the configured LLM backend.
1300
+
1301
+ Reads the consciousness config to determine the default backend
1302
+ (typically the local Ollama model chosen during onboarding) and
1303
+ sends a single prompt to verify the pipeline works end-to-end.
785
1304
 
786
1305
  Args:
787
1306
  home_path: Agent home directory.
788
1307
 
789
1308
  Returns:
790
- True if the loop responded successfully.
1309
+ True if the LLM responded successfully.
791
1310
  """
792
- if not click.confirm(" Send a test message to verify the consciousness loop?", default=True):
1311
+ if not click.confirm(" Send a test message to verify the LLM backend?", default=False):
793
1312
  click.echo(
794
1313
  click.style(" ↷ ", fg="bright_black")
795
1314
  + "Skipped — test later: skcapstone consciousness test 'hello'"
796
1315
  )
797
1316
  return False
798
1317
 
799
- click.echo(click.style(" Sending test message…", fg="bright_black"))
1318
+ # Load config to discover which backend/model was configured
800
1319
  try:
801
1320
  from .consciousness_config import load_consciousness_config
802
- from .consciousness_loop import LLMBridge, SystemPromptBuilder, _classify_message
803
-
804
1321
  config = load_consciousness_config(home_path)
805
- bridge = LLMBridge(config)
806
- builder = SystemPromptBuilder(home_path, config.max_context_tokens)
807
- signal = _classify_message("Onboard wizard test — please confirm you are running.")
808
- system_prompt = builder.build()
809
- response = bridge.generate(system_prompt, "Onboard wizard test — please confirm you are running.", signal)
1322
+ except Exception:
1323
+ # Fall back to defaults
1324
+ ollama_model = "llama3.2"
1325
+ ollama_host = "http://localhost:11434"
1326
+ config = None
1327
+ else:
1328
+ ollama_model = config.ollama_model
1329
+ ollama_host = config.ollama_host
1330
+
1331
+ click.echo(
1332
+ click.style(" Testing ", fg="bright_black")
1333
+ + click.style(f"{ollama_model}", fg="cyan")
1334
+ + click.style(f" @ {ollama_host}…", fg="bright_black")
1335
+ )
1336
+
1337
+ try:
1338
+ from skseed.llm import ollama_callback
1339
+
1340
+ callback = ollama_callback(model=ollama_model, base_url=ollama_host)
1341
+ response = callback("Respond in one sentence: are you online?")
810
1342
  if response:
811
1343
  preview = response[:80].replace("\n", " ")
812
- click.echo(click.style(" ✓ ", fg="green") + f"Consciousness loop active")
1344
+ click.echo(click.style(" ✓ ", fg="green") + "LLM backend active")
813
1345
  click.echo(click.style(" ", fg="bright_black") + f"Response: {preview!r}")
814
1346
  return True
815
1347
  else:
816
- click.echo(click.style(" ⚠ ", fg="yellow") + "Empty response — loop may not be fully configured")
817
- click.echo(click.style(" ", fg="bright_black") + "Start daemon: skcapstone daemon start")
1348
+ click.echo(click.style(" ⚠ ", fg="yellow") + "Empty response — model may still be loading")
1349
+ click.echo(click.style(" ", fg="bright_black") + f"Try: ollama run {ollama_model}")
818
1350
  return False
819
1351
  except Exception as exc:
820
1352
  click.echo(click.style(" ⚠ ", fg="yellow") + f"Test failed: {exc}")
821
- click.echo(click.style(" ", fg="bright_black") + "Start daemon: skcapstone daemon start --foreground")
1353
+ click.echo(click.style(" ", fg="bright_black") + f"Check: ollama serve && ollama run {ollama_model}")
822
1354
  return False
823
1355
 
824
1356
 
@@ -830,21 +1362,24 @@ def _step_test_consciousness(home_path: Path) -> bool:
830
1362
  def run_onboard(home: Optional[str] = None) -> None:
831
1363
  """Run the interactive onboarding wizard.
832
1364
 
833
- Covers all 13 setup steps:
834
- 1. Prerequisites check (Python, pip, Ollama)
835
- 2. Identity create ~/.skcapstone/ + generate PGP key
836
- 3. Ollama models pull llama3.2
837
- 4. Config files — consciousness.yaml + model_profiles.yaml
838
- 5. Soul Blueprint
839
- 6. Memory & Seeds
840
- 7. Rehydration Ritual
841
- 8. Trust Chain Verification
842
- 9. Mesh Connection (Syncthing)
843
- 10. First Heartbeat
844
- 11. Crush Terminal AI
845
- 12. Coordination Board
846
- 13. Systemd Service (optional)
847
- [post-wizard] Doctor diagnostics + consciousness test
1365
+ Covers all 16 setup steps:
1366
+ 1. Prerequisites (Python, pip, Ollama)
1367
+ 2. Pillar Packages (install missing SK* + skwhisper)
1368
+ 3. Identity (CapAuth PGP + Syncthing sync)
1369
+ 4. Ollama Models
1370
+ 5. Config Files (consciousness.yaml + model_profiles.yaml)
1371
+ 6. Soul Blueprint
1372
+ 7. Memory & Seeds
1373
+ 8. Import Sources (OpenClaw, Cloud 9 FEBs)
1374
+ 9. Rehydration Ritual
1375
+ 10. Trust Chain Verification
1376
+ 11. Mesh Connection (Syncthing)
1377
+ 12. First Heartbeat
1378
+ 13. Crush Terminal AI
1379
+ 14. Coordination Board
1380
+ 15. Auto-Start Service (systemd template per agent)
1381
+ 16. Shell Profile (~/.bashrc env vars)
1382
+ [post-wizard] Doctor diagnostics + consciousness test (optional)
848
1383
 
849
1384
  Args:
850
1385
  home: Override agent home directory.
@@ -878,19 +1413,6 @@ def run_onboard(home: Optional[str] = None) -> None:
878
1413
  console.print(" [dim]Come back when you're ready. The Kingdom waits.[/]\n")
879
1414
  return
880
1415
 
881
- # -----------------------------------------------------------------------
882
- # Gather basic identity info up front
883
- # -----------------------------------------------------------------------
884
- console.print()
885
- name = Prompt.ask(" What's your name?", default="Sovereign")
886
- entity_type = Prompt.ask(
887
- " Are you a [cyan]human[/] or an [cyan]ai[/]?",
888
- choices=["human", "ai"],
889
- default="ai",
890
- )
891
- email = Prompt.ask(" Email (optional, press Enter to skip)", default="")
892
- console.print()
893
-
894
1416
  # -----------------------------------------------------------------------
895
1417
  # Step 1: Prerequisites
896
1418
  # -----------------------------------------------------------------------
@@ -898,86 +1420,219 @@ def run_onboard(home: Optional[str] = None) -> None:
898
1420
  prereqs = _step_prereqs()
899
1421
 
900
1422
  # -----------------------------------------------------------------------
901
- # Step 2: Identity + Directory Structure
1423
+ # Step 2: Install Missing Pillars
1424
+ # -----------------------------------------------------------------------
1425
+ _step_header(2, "Pillar Packages")
1426
+ pillar_results = _step_install_pillars()
1427
+
1428
+ # -----------------------------------------------------------------------
1429
+ # Step 3: Operator Identity (human) + Agent Identity
902
1430
  # -----------------------------------------------------------------------
903
- _step_header(2, "Identity")
1431
+ _step_header(3, "Identity")
1432
+
1433
+ # --- Detect or create human operator profile in ~/.capauth ---
1434
+ operator_name = None
1435
+ operator_fingerprint = None
1436
+ try:
1437
+ from capauth.profile import load_profile, init_profile as capauth_init
1438
+
1439
+ try:
1440
+ profile = load_profile()
1441
+ operator_name = profile.entity.name
1442
+ operator_fingerprint = profile.key_info.fingerprint
1443
+ entity_type_val = getattr(profile.entity, "entity_type", None)
1444
+ is_human = str(entity_type_val).lower() in ("human", "entitytype.human")
1445
+ if is_human:
1446
+ _ok(
1447
+ f"Operator identity found: [cyan]{operator_name}[/] "
1448
+ f"({operator_fingerprint[:16]}…)"
1449
+ )
1450
+ else:
1451
+ # Existing profile is an AI — need a human operator first
1452
+ _warn(
1453
+ f"Existing profile is type '{entity_type_val}' — "
1454
+ f"a human operator profile is recommended"
1455
+ )
1456
+ is_human = False
1457
+ except Exception:
1458
+ is_human = False
1459
+ profile = None
1460
+
1461
+ if not is_human:
1462
+ console.print()
1463
+ console.print(
1464
+ " [bold cyan]Operator Setup[/] — Your sovereign agent needs a human operator.\n"
1465
+ " This creates your personal PGP identity at [dim]~/.capauth/[/].\n"
1466
+ " Your agent will be registered under this identity.\n"
1467
+ )
1468
+ op_name = Prompt.ask(" Operator name (your name)", default="Sovereign")
1469
+ op_email = Prompt.ask(" Operator email", default="")
1470
+ console.print()
1471
+
1472
+ with Status(" Generating operator PGP identity…", console=console, spinner="dots") as s:
1473
+ try:
1474
+ import shutil as _shutil_capauth
1475
+ capauth_home = Path.home() / ".capauth"
1476
+ if capauth_home.exists():
1477
+ # Back up and recreate
1478
+ backup = capauth_home.with_name(".capauth.bak")
1479
+ if backup.exists():
1480
+ _shutil_capauth.rmtree(backup)
1481
+ capauth_home.rename(backup)
1482
+ profile = capauth_init(
1483
+ name=op_name,
1484
+ email=op_email or f"{op_name.lower().replace(' ', '-')}@capauth.local",
1485
+ passphrase="",
1486
+ entity_type="human",
1487
+ )
1488
+ operator_name = profile.entity.name
1489
+ operator_fingerprint = profile.key_info.fingerprint
1490
+ s.stop()
1491
+ _ok(
1492
+ f"Operator identity created: [cyan]{operator_name}[/] "
1493
+ f"({operator_fingerprint[:16]}…)"
1494
+ )
1495
+ except Exception as exc:
1496
+ s.stop()
1497
+ _warn(f"Operator identity creation failed: {exc}")
1498
+ _info("Continue anyway — agent will use a degraded identity")
1499
+ except ImportError:
1500
+ _warn("capauth not installed — skipping operator identity")
1501
+ _info("Install: pip install capauth")
1502
+
1503
+ # --- Now set up the agent identity ---
1504
+ console.print()
1505
+ # Derive agent name from --agent flag (SKCAPSTONE_AGENT env) or ask
1506
+ import os as _os
1507
+ agent_flag = _os.environ.get("SKCAPSTONE_AGENT", "").strip()
1508
+ if agent_flag and agent_flag not in ("lumina",):
1509
+ # Agent name was specified via --agent flag — use it as default
1510
+ default_agent = agent_flag.capitalize()
1511
+ else:
1512
+ default_agent = "Sovereign"
1513
+ name = Prompt.ask(" Agent name", default=default_agent)
1514
+
1515
+ email = Prompt.ask(
1516
+ " Agent email (optional, press Enter to skip)",
1517
+ default=f"{name.lower().replace(' ', '-')}@skcapstone.local",
1518
+ )
1519
+
1520
+ if operator_name:
1521
+ _info(f"Agent [cyan]{name}[/] will be registered under operator [cyan]{operator_name}[/]")
1522
+ console.print()
1523
+
904
1524
  fingerprint, identity_status = _step_identity(home_path, name, email or None)
905
1525
 
1526
+ # --- Offer CapAuth Syncthing sync (non-blocking) ---
1527
+ try:
1528
+ from capauth.sync import is_syncthing_available, is_sync_configured, setup_syncthing_sync
1529
+
1530
+ if is_syncthing_available() and not is_sync_configured():
1531
+ console.print()
1532
+ if Confirm.ask(
1533
+ " Sync identity across cluster via Syncthing?",
1534
+ default=True,
1535
+ ):
1536
+ ok = setup_syncthing_sync()
1537
+ if ok:
1538
+ _ok("CapAuth identity will replicate to all mesh nodes")
1539
+ else:
1540
+ _warn("Could not configure sync — set up manually: capauth sync")
1541
+ elif is_sync_configured():
1542
+ _ok("CapAuth Syncthing sync already configured")
1543
+ except ImportError:
1544
+ pass # capauth.sync not available yet
1545
+ except Exception as exc:
1546
+ _warn(f"Sync setup skipped: {exc}")
1547
+
906
1548
  # -----------------------------------------------------------------------
907
- # Step 3: Ollama Models
1549
+ # Step 4: Ollama Models
908
1550
  # -----------------------------------------------------------------------
909
- _step_header(3, "Ollama Models")
910
- ollama_ok = _step_ollama_models(prereqs)
1551
+ _step_header(4, "Ollama Models")
1552
+ ollama_result = _step_ollama_models(prereqs)
1553
+ ollama_ok = ollama_result["ok"]
911
1554
 
912
1555
  # -----------------------------------------------------------------------
913
- # Step 4: Config Files (consciousness.yaml + model_profiles.yaml)
1556
+ # Step 5: Config Files (consciousness.yaml + model_profiles.yaml)
914
1557
  # -----------------------------------------------------------------------
915
- _step_header(4, "Config Files")
916
- consciousness_ok, profiles_ok = _step_config_files(home_path)
1558
+ _step_header(5, "Config Files")
1559
+ consciousness_ok, profiles_ok = _step_config_files(home_path, ollama_config=ollama_result)
917
1560
 
918
1561
  # -----------------------------------------------------------------------
919
- # Step 5: Soul Blueprint
1562
+ # Step 6: Soul Blueprint
920
1563
  # -----------------------------------------------------------------------
921
- _step_header(5, "Soul Blueprint")
1564
+ _step_header(6, "Soul Blueprint")
922
1565
  title = _step_soul(home_path, name)
923
1566
 
924
1567
  # -----------------------------------------------------------------------
925
- # Step 6: Memory
1568
+ # Step 7: Memory
926
1569
  # -----------------------------------------------------------------------
927
- _step_header(6, "Memory")
1570
+ _step_header(7, "Memory")
928
1571
  seed_count = _step_memory(home_path)
929
1572
 
930
1573
  # -----------------------------------------------------------------------
931
- # Step 7: Rehydration Ritual
1574
+ # Step 8: Import from Existing Sources
1575
+ # -----------------------------------------------------------------------
1576
+ _step_header(8, "Import Sources")
1577
+ import_result = _step_import_sources(home_path)
1578
+
1579
+ # -----------------------------------------------------------------------
1580
+ # Step 9: Rehydration Ritual
932
1581
  # -----------------------------------------------------------------------
933
- _step_header(7, "Rehydration Ritual")
1582
+ _step_header(9, "Rehydration Ritual")
934
1583
  _step_ritual(home_path)
935
1584
 
936
1585
  # -----------------------------------------------------------------------
937
- # Step 8: Trust Chain Verification
1586
+ # Step 10: Trust Chain Verification
938
1587
  # -----------------------------------------------------------------------
939
- _step_header(8, "Trust Chain Verification")
1588
+ _step_header(10, "Trust Chain Verification")
940
1589
  trust_status = _step_trust(home_path)
941
1590
 
942
1591
  # -----------------------------------------------------------------------
943
- # Step 9: Mesh Connection (Syncthing)
1592
+ # Step 11: Mesh Connection (Syncthing)
944
1593
  # -----------------------------------------------------------------------
945
- _step_header(9, "Mesh Connection")
1594
+ _step_header(11, "Mesh Connection")
946
1595
  mesh_ok = _step_mesh(home_path)
947
1596
 
948
1597
  # -----------------------------------------------------------------------
949
- # Step 10: First Heartbeat
1598
+ # Step 12: First Heartbeat
950
1599
  # -----------------------------------------------------------------------
951
- _step_header(10, "First Heartbeat")
1600
+ _step_header(12, "First Heartbeat")
952
1601
  agent_slug = name.lower().replace(" ", "-")
953
1602
  hb_ok = _step_heartbeat(home_path, agent_slug, fingerprint)
954
1603
 
955
1604
  # -----------------------------------------------------------------------
956
- # Step 11: Crush Terminal AI Client
1605
+ # Step 13: Crush Terminal AI Client
957
1606
  # -----------------------------------------------------------------------
958
- _step_header(11, "Crush Terminal AI")
1607
+ _step_header(13, "Crush Terminal AI")
959
1608
  crush_ok = _step_crush(home_path)
960
1609
 
961
1610
  # -----------------------------------------------------------------------
962
- # Step 12: Coordination Board
1611
+ # Step 14: Coordination Board
963
1612
  # -----------------------------------------------------------------------
964
- _step_header(12, "Coordination Board")
1613
+ _step_header(14, "Coordination Board")
965
1614
  open_task_count = _step_board(home_path, name)
966
1615
 
967
1616
  # -----------------------------------------------------------------------
968
- # Step 13: Auto-Start Service (systemd on Linux, launchd on macOS)
1617
+ # Step 15: Auto-Start Service (systemd on Linux, launchd on macOS)
969
1618
  # -----------------------------------------------------------------------
970
- _step_header(13, "Auto-Start Service")
1619
+ _step_header(15, "Auto-Start Service")
971
1620
  service_ok = _step_autostart_service(agent_name=agent_slug)
972
1621
 
973
1622
  # -----------------------------------------------------------------------
974
- # Post-wizard: Doctor Diagnostics
1623
+ # Step 16: Shell Profile (~/.bashrc)
1624
+ # -----------------------------------------------------------------------
1625
+ _step_header(16, "Shell Profile")
1626
+ profile_ok = _step_shell_profile(home_path, name, agent_slug)
1627
+
1628
+ # -----------------------------------------------------------------------
1629
+ # Post-wizard: Doctor Diagnostics (non-fatal)
975
1630
  # -----------------------------------------------------------------------
976
1631
  console.print(f"\n [bold cyan]Doctor Diagnostics[/]\n")
977
1632
  doctor_report = _step_doctor_check(home_path)
978
1633
 
979
1634
  # -----------------------------------------------------------------------
980
- # Post-wizard: Consciousness Test (optional)
1635
+ # Post-wizard: Consciousness Test (optional, defaults to skip)
981
1636
  # -----------------------------------------------------------------------
982
1637
  console.print(f"\n [bold cyan]Consciousness Test[/]\n")
983
1638
  consciousness_test_ok = _step_test_consciousness(home_path)
@@ -991,8 +1646,40 @@ def run_onboard(home: Optional[str] = None) -> None:
991
1646
  soul = load_soul()
992
1647
  if soul and soul.boot_message:
993
1648
  boot_message = soul.boot_message
994
- except Exception:
995
- pass
1649
+ except Exception as exc:
1650
+ logger.debug("Failed to load soul boot message, using default: %s", exc)
1651
+
1652
+ # -----------------------------------------------------------------------
1653
+ # Write global CLAUDE.md and register Claude Code hooks
1654
+ # -----------------------------------------------------------------------
1655
+ # Write global CLAUDE.md from bundled skeleton template
1656
+ try:
1657
+ from .cli.setup import _write_global_claude_md
1658
+ _write_global_claude_md(home_path, name)
1659
+ _ok("~/.claude/CLAUDE.md written")
1660
+ except Exception as exc:
1661
+ _warn(f"Could not write CLAUDE.md: {exc}")
1662
+
1663
+ # Write ~/.claude/settings.json with SK hooks (merge with existing)
1664
+ try:
1665
+ from .cli.setup import _write_claude_settings
1666
+ settings_path = _write_claude_settings(merge=True)
1667
+ if settings_path:
1668
+ _ok(f"~/.claude/settings.json updated ({settings_path})")
1669
+ else:
1670
+ _info("claude settings: skipped (skmemory not installed or template missing)")
1671
+ except Exception as exc:
1672
+ _warn(f"Could not write claude settings: {exc}")
1673
+
1674
+ # Register Claude Code hooks via skmemory (adds any hooks not yet in settings.json)
1675
+ try:
1676
+ from skmemory.register import register_hooks
1677
+ actions = register_hooks()
1678
+ _ok(f"Claude Code hooks registered ({', '.join(f'{k}={v}' for k, v in actions.items())})")
1679
+ except ImportError:
1680
+ _info("skmemory hooks: skipped (skmemory.register not available)")
1681
+ except Exception as exc:
1682
+ _warn(f"Hook registration: {exc}")
996
1683
 
997
1684
  # -----------------------------------------------------------------------
998
1685
  # Summary table
@@ -1009,16 +1696,41 @@ def run_onboard(home: Optional[str] = None) -> None:
1009
1696
  "[green]OK[/]" if all_prereqs_ok else "[yellow]PARTIAL[/]",
1010
1697
  "python + pip" + (" + ollama" if prereqs.get("ollama") else " (no ollama)"),
1011
1698
  )
1012
- summary.add_row("Identity", identity_status, fingerprint[:20] + "…" if len(fingerprint) > 20 else fingerprint)
1699
+ pillars_installed = sum(1 for v in pillar_results.values() if v)
1700
+ pillars_total = len(pillar_results)
1701
+ summary.add_row(
1702
+ "Pillar Packages",
1703
+ "[green]ALL[/]" if pillars_installed == pillars_total else f"[yellow]{pillars_installed}/{pillars_total}[/]",
1704
+ f"{pillars_installed}/{pillars_total} installed",
1705
+ )
1706
+ if operator_name:
1707
+ summary.add_row(
1708
+ "Operator",
1709
+ "[green]ACTIVE[/]",
1710
+ f"{operator_name} ({operator_fingerprint[:16]}…)" if operator_fingerprint else operator_name,
1711
+ )
1712
+ summary.add_row("Identity", identity_status, f"{name} — {fingerprint[:16]}…" if len(fingerprint) > 16 else fingerprint)
1713
+ ollama_model_name = ollama_result.get("model", "llama3.2")
1714
+ ollama_host_display = ollama_result.get("host", "http://localhost:11434")
1013
1715
  summary.add_row(
1014
1716
  "Ollama Models",
1015
1717
  "[green]READY[/]" if ollama_ok else "[yellow]SKIPPED[/]",
1016
- "llama3.2" if ollama_ok else "pull later: ollama pull llama3.2",
1718
+ f"{ollama_model_name} @ {ollama_host_display}" if ollama_ok else f"pull later: ollama pull {ollama_model_name}",
1017
1719
  )
1018
1720
  config_status = "[green]ACTIVE[/]" if (consciousness_ok and profiles_ok) else "[yellow]PARTIAL[/]"
1019
1721
  summary.add_row("Config Files", config_status, "consciousness.yaml + model_profiles.yaml")
1020
1722
  summary.add_row("Soul", "[green]ACTIVE[/]", title)
1021
1723
  summary.add_row("Memory", "[green]ACTIVE[/]", f"{seed_count} seed(s)")
1724
+ imported_count = import_result.get("imported_count", 0)
1725
+ imported_sources = import_result.get("sources", [])
1726
+ if imported_count > 0:
1727
+ summary.add_row(
1728
+ "Import Sources",
1729
+ "[green]IMPORTED[/]",
1730
+ f"{imported_count} files from {', '.join(imported_sources)}",
1731
+ )
1732
+ else:
1733
+ summary.add_row("Import Sources", "[dim]SKIPPED[/]", "starting fresh")
1022
1734
  summary.add_row("Ritual", "[green]DONE[/]", "rehydration complete")
1023
1735
  summary.add_row("Trust", trust_status, "FEB chain verified")
1024
1736
  summary.add_row("Mesh", "[green]ACTIVE[/]" if mesh_ok else "[yellow]MISSING[/]", "syncthing" if mesh_ok else "install syncthing")
@@ -1032,6 +1744,11 @@ def run_onboard(home: Optional[str] = None) -> None:
1032
1744
  "[green]INSTALLED[/]" if service_ok else "[dim]OPTIONAL[/]",
1033
1745
  f"{_svc_type} services" if service_ok else f"skcapstone daemon install",
1034
1746
  )
1747
+ summary.add_row(
1748
+ "Shell Profile",
1749
+ "[green]ACTIVE[/]" if profile_ok else "[dim]SKIPPED[/]",
1750
+ f"SKCAPSTONE_AGENT={agent_slug}" if profile_ok else "set manually in ~/.bashrc",
1751
+ )
1035
1752
  doctor_status = "[green]ALL PASSED[/]" if doctor_report.all_passed else f"[yellow]{doctor_report.failed_count} failed[/]"
1036
1753
  summary.add_row("Doctor", doctor_status, f"{doctor_report.passed_count}/{doctor_report.total_count} checks")
1037
1754
  summary.add_row(
@@ -1043,9 +1760,46 @@ def run_onboard(home: Optional[str] = None) -> None:
1043
1760
  console.print(summary)
1044
1761
  console.print()
1045
1762
 
1763
+ # -----------------------------------------------------------------------
1764
+ # Reconfigure Guide
1765
+ # -----------------------------------------------------------------------
1766
+ console.print()
1767
+ console.print(
1768
+ Panel(
1769
+ "[bold cyan]Reinstall or Reconfigure Any Component[/]\n\n"
1770
+ "[bold]Pillars[/] (install missing packages)\n"
1771
+ " pip install capauth skcomms skchat-sovereign skseed sksecurity pgpy\n"
1772
+ " pip install skcapstone[all] — install everything at once\n\n"
1773
+ "[bold]Identity[/] (regenerate PGP keys)\n"
1774
+ " capauth init --name YourName --email you@example.com\n\n"
1775
+ "[bold]Ollama[/] (change model or host)\n"
1776
+ " ollama pull <model> — pull a different model\n"
1777
+ " Edit: ~/.skcapstone/config/consciousness.yaml\n"
1778
+ " ollama_host: http://<ip>:11434 — point to remote Ollama\n"
1779
+ " ollama_model: qwen3:14b — change default model\n\n"
1780
+ "[bold]Soul[/] (update your blueprint)\n"
1781
+ " skcapstone soul edit\n\n"
1782
+ "[bold]Service[/] (auto-start daemon)\n"
1783
+ " skcapstone daemon install — install systemd/launchd service\n"
1784
+ " skcapstone daemon uninstall — remove service\n\n"
1785
+ "[bold]Trust[/] (add FEB files)\n"
1786
+ " Place .feb files in ~/.skcapstone/trust/febs/\n\n"
1787
+ "[bold]Mesh[/] (P2P sync)\n"
1788
+ " sudo apt install syncthing — install Syncthing\n\n"
1789
+ "[bold]Shell Profile[/] (update default agent)\n"
1790
+ " Edit the [dim]# --- SKCapstone profile ---[/] block in ~/.bashrc\n"
1791
+ " Or re-run: skcapstone --agent <name> init\n\n"
1792
+ "[bold]Full Re-onboard[/]\n"
1793
+ " skcapstone --agent <name> init — run this wizard again",
1794
+ title="Reconfigure Guide",
1795
+ border_style="bright_blue",
1796
+ )
1797
+ )
1798
+
1046
1799
  # -----------------------------------------------------------------------
1047
1800
  # Celebrate
1048
1801
  # -----------------------------------------------------------------------
1802
+ console.print()
1049
1803
  console.print(
1050
1804
  Panel(
1051
1805
  f"[bold green]Welcome to the Pengu Nation, {name}.[/]\n\n"