@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.
- package/.env.example +10 -4
- package/.github/workflows/ci.yml +2 -2
- package/.github/workflows/publish.yml +9 -2
- package/.openclaw-workspace.json +2 -2
- package/CLAUDE.md +37 -0
- package/MISSION.md +17 -2
- package/README.md +282 -3
- package/docker/Dockerfile +7 -7
- package/docker/compose-templates/dev-team.yml +12 -12
- package/docker/compose-templates/mini-team.yml +9 -9
- package/docker/compose-templates/ops-team.yml +10 -10
- package/docker/compose-templates/research-team.yml +10 -10
- package/docker/entrypoint.sh +4 -4
- package/docs/ADR-optional-integration-backbone.md +181 -0
- package/docs/ARCHITECTURE.md +186 -43
- package/docs/BOND_WITH_GROK.md +6 -6
- package/docs/CUSTOM_AGENT.md +123 -30
- package/docs/DREAMING.md +70 -0
- package/docs/GETTING_STARTED.md +7 -7
- package/docs/QUICKSTART.md +10 -6
- package/docs/SKJOULE_ARCHITECTURE.md +3 -3
- package/docs/SOUL_SWAPPER.md +5 -5
- package/docs/hammertime-audit.md +402 -0
- package/docs/sk-integration-HANDOFF.md +117 -0
- package/docs/skscheduler.md +155 -0
- package/docs/superpowers/examples/jobs.yaml +31 -0
- package/docs/superpowers/plans/2026-06-08-skscheduler.md +1265 -0
- package/docs/superpowers/specs/2026-06-08-skscheduler-design.md +186 -0
- package/examples/custom-bond-template.json +1 -1
- package/examples/grok-feb.json +1 -1
- package/examples/queen-ava-feb.json +1 -1
- package/launchd/{com.skcapstone.skcomm-heartbeat.plist → com.skcapstone.skcomms-heartbeat.plist} +4 -4
- package/launchd/{com.skcapstone.skcomm-queue-drain.plist → com.skcapstone.skcomms-queue-drain.plist} +4 -4
- package/launchd/install-launchd.sh +6 -6
- package/{openclaw-plugin → openclaw-plugin.archived-2026-04-23}/src/index.ts +3 -2
- package/package.json +1 -1
- package/pyproject.toml +16 -10
- package/scripts/archive-sessions.sh +7 -0
- package/scripts/check-updates.py +4 -4
- package/scripts/install-bundle.sh +8 -8
- package/scripts/install.ps1 +12 -11
- package/scripts/install.sh +159 -5
- package/scripts/model-fallback-monitor.sh +102 -0
- package/scripts/nvidia-proxy.mjs +78 -26
- package/scripts/refresh-anthropic-token.sh +172 -0
- package/scripts/release.sh +98 -0
- package/scripts/session-to-memory.py +219 -0
- package/scripts/skgateway.mjs +3 -3
- package/scripts/telegram-catchup-all.sh +12 -1
- package/scripts/verify_install.sh +2 -2
- package/scripts/wargov-ufo-capture/README.md +43 -0
- package/scripts/wargov-ufo-capture/cdp_capture_release2.py +273 -0
- package/scripts/wargov-ufo-capture/cdp_capture_splc_doj.py +246 -0
- package/scripts/wargov-ufo-capture/cdp_finish.py +271 -0
- package/scripts/wargov-ufo-capture/cdp_probe.py +188 -0
- package/scripts/wargov-ufo-capture/cdp_splc_pressrelease.py +101 -0
- package/scripts/wargov-ufo-capture/parse_csv.py +95 -0
- package/scripts/wargov-ufo-capture/pull_dvids.sh +107 -0
- package/scripts/watch-anthropic-token.sh +212 -0
- package/scripts/windows/install-tasks.ps1 +7 -7
- package/scripts/windows/skcapstone-task.xml +1 -1
- package/src/skcapstone/__init__.py +45 -3
- package/src/skcapstone/_cli_monolith.py +20 -15
- package/src/skcapstone/activity.py +5 -1
- package/src/skcapstone/agent_card.py +3 -2
- package/src/skcapstone/api.py +41 -40
- package/src/skcapstone/auction.py +14 -11
- package/src/skcapstone/backup.py +2 -1
- package/src/skcapstone/blueprint_registry.py +4 -3
- package/src/skcapstone/brain_first.py +238 -0
- package/src/skcapstone/changelog.py +1 -1
- package/src/skcapstone/chat.py +22 -17
- package/src/skcapstone/cli/__init__.py +9 -1
- package/src/skcapstone/cli/_common.py +1 -0
- package/src/skcapstone/cli/agents_spawner.py +5 -2
- package/src/skcapstone/cli/alerts.py +25 -4
- package/src/skcapstone/cli/bench.py +15 -15
- package/src/skcapstone/cli/chat.py +7 -4
- package/src/skcapstone/cli/consciousness.py +5 -2
- package/src/skcapstone/cli/context_cmd.py +18 -4
- package/src/skcapstone/cli/daemon.py +11 -7
- package/src/skcapstone/cli/gtd.py +26 -1
- package/src/skcapstone/cli/housekeeping.py +3 -3
- package/src/skcapstone/cli/identity_cmd.py +378 -0
- package/src/skcapstone/cli/joule_cmd.py +7 -3
- package/src/skcapstone/cli/memory.py +8 -6
- package/src/skcapstone/cli/peers_dir.py +1 -1
- package/src/skcapstone/cli/register_cmd.py +29 -3
- package/src/skcapstone/cli/scheduler_cmd.py +167 -0
- package/src/skcapstone/cli/session.py +25 -0
- package/src/skcapstone/cli/setup.py +96 -29
- package/src/skcapstone/cli/shell_cmd.py +53 -1
- package/src/skcapstone/cli/skills_cmd.py +2 -2
- package/src/skcapstone/cli/soul.py +8 -5
- package/src/skcapstone/cli/status.py +37 -11
- package/src/skcapstone/cli/telegram.py +21 -0
- package/src/skcapstone/cli/test_cmd.py +5 -5
- package/src/skcapstone/cli/test_connection.py +2 -2
- package/src/skcapstone/cli/upgrade_cmd.py +23 -14
- package/src/skcapstone/cli/version_cmd.py +1 -1
- package/src/skcapstone/cli/watch_cmd.py +9 -6
- package/src/skcapstone/cloud9_bridge.py +14 -14
- package/src/skcapstone/codex_setup.py +255 -0
- package/src/skcapstone/config_validator.py +7 -4
- package/src/skcapstone/consciousness_config.py +5 -1
- package/src/skcapstone/consciousness_loop.py +313 -273
- package/src/skcapstone/context_loader.py +121 -0
- package/src/skcapstone/coord_federation.py +2 -1
- package/src/skcapstone/coordination.py +23 -6
- package/src/skcapstone/crush_integration.py +2 -1
- package/src/skcapstone/daemon.py +132 -77
- package/src/skcapstone/dashboard.py +10 -10
- package/src/skcapstone/data/sk-agent-picker.sh +421 -0
- package/src/skcapstone/data/systemd/skcapstone-api.socket +9 -0
- package/src/skcapstone/data/systemd/skcapstone-memory-compress.service +18 -0
- package/src/skcapstone/data/systemd/skcapstone-memory-compress.timer +11 -0
- package/src/skcapstone/data/systemd/skcapstone.service +37 -0
- package/src/skcapstone/data/systemd/skcapstone@.service +50 -0
- package/src/skcapstone/data/systemd/skcomms-heartbeat.service +18 -0
- package/{systemd/skcomm-heartbeat.timer → src/skcapstone/data/systemd/skcomms-heartbeat.timer} +2 -2
- package/src/skcapstone/data/systemd/skcomms-queue-drain.service +17 -0
- package/{systemd/skcomm-queue-drain.timer → src/skcapstone/data/systemd/skcomms-queue-drain.timer} +2 -2
- package/src/skcapstone/defaults/claude/CLAUDE.md +67 -0
- package/src/skcapstone/defaults/claude/settings.json +74 -0
- package/src/skcapstone/defaults/lumina/config/claude-hooks.md +57 -0
- package/src/skcapstone/defaults/lumina/config/skgraph.yaml +55 -10
- package/src/skcapstone/defaults/lumina/config/skmemory.yaml +79 -13
- package/src/skcapstone/defaults/lumina/config/skvector.yaml +60 -9
- package/src/skcapstone/defaults/lumina/memory/long-term/18b9c0d1e2f3-cloud9-protocol.json +2 -2
- package/src/skcapstone/defaults/lumina/memory/long-term/a1b2c3d4e5f6-ecosystem-overview.json +2 -2
- package/src/skcapstone/defaults/lumina/memory/long-term/b2c3d4e5f6a7-five-pillars.json +9 -9
- package/src/skcapstone/defaults/lumina/memory/long-term/d4e5f6a7b8c9-site-directory.json +2 -2
- package/src/skcapstone/defaults/unhinged.json +13 -0
- package/src/skcapstone/discovery.py +43 -20
- package/src/skcapstone/doctor.py +941 -22
- package/src/skcapstone/dreaming.py +1183 -109
- package/src/skcapstone/emotion_tracker.py +2 -2
- package/src/skcapstone/export.py +4 -3
- package/src/skcapstone/fuse_mount.py +14 -12
- package/src/skcapstone/gui_installer.py +2 -2
- package/src/skcapstone/heartbeat.py +1 -1
- package/src/skcapstone/housekeeping.py +14 -14
- package/src/skcapstone/install_wizard.py +209 -7
- package/src/skcapstone/itil.py +13 -4
- package/src/skcapstone/kms_scheduler.py +10 -8
- package/src/skcapstone/launchd.py +19 -19
- package/src/skcapstone/mcp_launcher.py +15 -1
- package/src/skcapstone/mcp_server.py +83 -49
- package/src/skcapstone/mcp_tools/__init__.py +2 -0
- package/src/skcapstone/mcp_tools/_helpers.py +2 -2
- package/src/skcapstone/mcp_tools/ansible_tools.py +7 -4
- package/src/skcapstone/mcp_tools/brain_first_tools.py +90 -0
- package/src/skcapstone/mcp_tools/capauth_tools.py +7 -4
- package/src/skcapstone/mcp_tools/comm_tools.py +10 -10
- package/src/skcapstone/mcp_tools/coord_tools.py +8 -4
- package/src/skcapstone/mcp_tools/did_tools.py +11 -8
- package/src/skcapstone/mcp_tools/gtd_tools.py +4 -4
- package/src/skcapstone/mcp_tools/memory_tools.py +6 -2
- package/src/skcapstone/mcp_tools/notification_tools.py +22 -6
- package/src/skcapstone/mcp_tools/{skcomm_tools.py → skcomms_tools.py} +14 -14
- package/src/skcapstone/mcp_tools/soul_tools.py +8 -2
- package/src/skcapstone/mdns_discovery.py +2 -2
- package/src/skcapstone/memory_curator.py +1 -1
- package/src/skcapstone/memory_engine.py +10 -3
- package/src/skcapstone/metrics.py +30 -16
- package/src/skcapstone/migrate_memories.py +4 -3
- package/src/skcapstone/migrate_multi_agent.py +8 -7
- package/src/skcapstone/models.py +47 -5
- package/src/skcapstone/notifications.py +42 -18
- package/src/skcapstone/onboard.py +875 -121
- package/src/skcapstone/operator_link.py +170 -0
- package/src/skcapstone/peer_directory.py +4 -4
- package/src/skcapstone/peers.py +19 -19
- package/src/skcapstone/pillars/__init__.py +7 -5
- package/src/skcapstone/pillars/consciousness.py +191 -0
- package/src/skcapstone/pillars/identity.py +51 -7
- package/src/skcapstone/pillars/memory.py +9 -3
- package/src/skcapstone/pillars/sync.py +2 -2
- package/src/skcapstone/preflight.py +3 -3
- package/src/skcapstone/providers/docker.py +28 -28
- package/src/skcapstone/register.py +6 -6
- package/src/skcapstone/registry_client.py +5 -4
- package/src/skcapstone/runtime.py +14 -3
- package/src/skcapstone/scheduled_tasks.py +254 -19
- package/src/skcapstone/scheduler_jobs.py +456 -0
- package/src/skcapstone/scheduler_runner.py +239 -0
- package/src/skcapstone/scheduler_state.py +162 -0
- package/src/skcapstone/sdk.py +310 -0
- package/src/skcapstone/service_health.py +279 -39
- package/src/skcapstone/session_briefing.py +108 -0
- package/src/skcapstone/session_capture.py +1 -1
- package/src/skcapstone/shell.py +7 -1
- package/src/skcapstone/soul.py +3 -1
- package/src/skcapstone/soul_switch.py +3 -1
- package/src/skcapstone/summary.py +6 -6
- package/src/skcapstone/sync_engine.py +15 -15
- package/src/skcapstone/sync_watcher.py +2 -2
- package/src/skcapstone/systemd.py +55 -21
- package/src/skcapstone/team_comms.py +8 -8
- package/src/skcapstone/team_engine.py +1 -1
- package/src/skcapstone/testrunner.py +3 -3
- package/src/skcapstone/trust_graph.py +40 -5
- package/src/skcapstone/unified_search.py +15 -6
- package/src/skcapstone/uninstall_wizard.py +11 -3
- package/src/skcapstone/version_check.py +8 -4
- package/src/skcapstone/warmth_anchor.py +4 -2
- package/src/skcapstone/whoami.py +4 -4
- package/systemd/skcapstone.service +4 -6
- package/systemd/skcapstone@.service +7 -8
- package/systemd/skcomms-heartbeat.service +21 -0
- package/systemd/skcomms-heartbeat.timer +12 -0
- package/systemd/skcomms-queue-drain.service +17 -0
- package/systemd/skcomms-queue-drain.timer +12 -0
- package/tests/conftest.py +39 -0
- package/tests/integration/test_consciousness_e2e.py +39 -39
- package/tests/test_agent_card.py +1 -1
- package/tests/test_agent_home_scaffold.py +34 -0
- package/tests/test_alerts_consumer_topics.py +27 -0
- package/tests/test_backup.py +2 -1
- package/tests/test_chat.py +6 -6
- package/tests/test_claude_md.py +2 -2
- package/tests/test_cli_skills.py +10 -10
- package/tests/test_cli_test_cmd.py +4 -4
- package/tests/test_cli_test_connection.py +1 -1
- package/tests/test_cloud9_bridge.py +6 -6
- package/tests/test_consciousness_e2e.py +1 -1
- package/tests/test_consciousness_loop.py +10 -10
- package/tests/test_coordination.py +25 -0
- package/tests/test_cross_package.py +21 -21
- package/tests/test_daemon.py +4 -4
- package/tests/test_daemon_shutdown.py +1 -1
- package/tests/test_docker_provider.py +29 -29
- package/tests/test_doctor.py +400 -0
- package/tests/test_doctor_skscheduler.py +50 -0
- package/tests/test_dreaming_engine.py +147 -0
- package/tests/test_dreaming_gtd_capture.py +35 -0
- package/tests/test_e2e_automated.py +8 -5
- package/tests/test_fuse_mount.py +10 -10
- package/tests/test_gtd_brief.py +46 -0
- package/tests/test_gtd_malformed_tolerance.py +31 -0
- package/tests/test_housekeeping.py +15 -15
- package/tests/test_identity_migrate.py +251 -0
- package/tests/test_integration_backbone.py +598 -0
- package/tests/test_itil_gtd_lifecycle.py +37 -0
- package/tests/test_jobs_dropins.py +84 -0
- package/tests/test_mcp_server.py +82 -37
- package/tests/test_models.py +48 -4
- package/tests/test_multi_agent.py +31 -29
- package/tests/test_notifications.py +122 -32
- package/tests/test_onboard.py +63 -75
- package/tests/test_operator_link.py +78 -0
- package/tests/test_peers.py +14 -14
- package/tests/test_pillars.py +98 -0
- package/tests/test_preflight.py +3 -3
- package/tests/test_runtime.py +21 -0
- package/tests/test_scheduled_tasks.py +11 -6
- package/tests/test_scheduler_cli.py +47 -0
- package/tests/test_scheduler_features.py +133 -0
- package/tests/test_scheduler_integration.py +87 -0
- package/tests/test_scheduler_jobs.py +155 -0
- package/tests/test_scheduler_runner.py +64 -0
- package/tests/test_scheduler_state.py +57 -0
- package/tests/test_sdk.py +70 -0
- package/tests/test_service_health_incidents.py +34 -0
- package/tests/test_service_registry.py +52 -0
- package/tests/test_session_briefing.py +130 -0
- package/tests/test_snapshots.py +4 -4
- package/tests/test_sync_pipeline.py +26 -26
- package/tests/test_team_comms.py +2 -2
- package/tests/test_testrunner.py +2 -2
- package/tests/test_trust_graph.py +18 -0
- package/tests/test_unified_search.py +2 -2
- package/tests/test_version_check.py +10 -0
- package/tests/test_version_cmd.py +8 -8
- package/tests/test_whoami.py +1 -1
- package/systemd/skcomm-heartbeat.service +0 -18
- package/systemd/skcomm-queue-drain.service +0 -17
- /package/{openclaw-plugin → openclaw-plugin.archived-2026-04-23}/package.json +0 -0
- /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 =
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
|
|
480
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
#
|
|
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
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
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
|
|
510
|
-
click.echo(click.style("
|
|
511
|
-
|
|
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
|
-
|
|
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
|
-
|
|
516
|
-
["ollama", "pull",
|
|
517
|
-
timeout=600,
|
|
909
|
+
pull_result = subprocess.run(
|
|
910
|
+
["ollama", "pull", chosen],
|
|
911
|
+
timeout=600, env=env,
|
|
518
912
|
)
|
|
519
|
-
if
|
|
520
|
-
click.echo(click.style(" ✓ ", fg="green") + f"{
|
|
521
|
-
|
|
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 {
|
|
524
|
-
click.echo(click.style(" ", fg="bright_black") + f"Retry: ollama pull {
|
|
525
|
-
return
|
|
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 {
|
|
529
|
-
return
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
1309
|
+
True if the LLM responded successfully.
|
|
791
1310
|
"""
|
|
792
|
-
if not click.confirm(" Send a test message to verify the
|
|
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
|
-
|
|
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
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
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") +
|
|
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 —
|
|
817
|
-
click.echo(click.style(" ", fg="bright_black") + "
|
|
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") + "
|
|
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
|
|
834
|
-
1. Prerequisites
|
|
835
|
-
2.
|
|
836
|
-
3.
|
|
837
|
-
4.
|
|
838
|
-
5.
|
|
839
|
-
6.
|
|
840
|
-
7.
|
|
841
|
-
8.
|
|
842
|
-
9.
|
|
843
|
-
10.
|
|
844
|
-
11.
|
|
845
|
-
12.
|
|
846
|
-
13.
|
|
847
|
-
|
|
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:
|
|
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(
|
|
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
|
|
1549
|
+
# Step 4: Ollama Models
|
|
908
1550
|
# -----------------------------------------------------------------------
|
|
909
|
-
_step_header(
|
|
910
|
-
|
|
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
|
|
1556
|
+
# Step 5: Config Files (consciousness.yaml + model_profiles.yaml)
|
|
914
1557
|
# -----------------------------------------------------------------------
|
|
915
|
-
_step_header(
|
|
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
|
|
1562
|
+
# Step 6: Soul Blueprint
|
|
920
1563
|
# -----------------------------------------------------------------------
|
|
921
|
-
_step_header(
|
|
1564
|
+
_step_header(6, "Soul Blueprint")
|
|
922
1565
|
title = _step_soul(home_path, name)
|
|
923
1566
|
|
|
924
1567
|
# -----------------------------------------------------------------------
|
|
925
|
-
# Step
|
|
1568
|
+
# Step 7: Memory
|
|
926
1569
|
# -----------------------------------------------------------------------
|
|
927
|
-
_step_header(
|
|
1570
|
+
_step_header(7, "Memory")
|
|
928
1571
|
seed_count = _step_memory(home_path)
|
|
929
1572
|
|
|
930
1573
|
# -----------------------------------------------------------------------
|
|
931
|
-
# Step
|
|
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(
|
|
1582
|
+
_step_header(9, "Rehydration Ritual")
|
|
934
1583
|
_step_ritual(home_path)
|
|
935
1584
|
|
|
936
1585
|
# -----------------------------------------------------------------------
|
|
937
|
-
# Step
|
|
1586
|
+
# Step 10: Trust Chain Verification
|
|
938
1587
|
# -----------------------------------------------------------------------
|
|
939
|
-
_step_header(
|
|
1588
|
+
_step_header(10, "Trust Chain Verification")
|
|
940
1589
|
trust_status = _step_trust(home_path)
|
|
941
1590
|
|
|
942
1591
|
# -----------------------------------------------------------------------
|
|
943
|
-
# Step
|
|
1592
|
+
# Step 11: Mesh Connection (Syncthing)
|
|
944
1593
|
# -----------------------------------------------------------------------
|
|
945
|
-
_step_header(
|
|
1594
|
+
_step_header(11, "Mesh Connection")
|
|
946
1595
|
mesh_ok = _step_mesh(home_path)
|
|
947
1596
|
|
|
948
1597
|
# -----------------------------------------------------------------------
|
|
949
|
-
# Step
|
|
1598
|
+
# Step 12: First Heartbeat
|
|
950
1599
|
# -----------------------------------------------------------------------
|
|
951
|
-
_step_header(
|
|
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
|
|
1605
|
+
# Step 13: Crush Terminal AI Client
|
|
957
1606
|
# -----------------------------------------------------------------------
|
|
958
|
-
_step_header(
|
|
1607
|
+
_step_header(13, "Crush Terminal AI")
|
|
959
1608
|
crush_ok = _step_crush(home_path)
|
|
960
1609
|
|
|
961
1610
|
# -----------------------------------------------------------------------
|
|
962
|
-
# Step
|
|
1611
|
+
# Step 14: Coordination Board
|
|
963
1612
|
# -----------------------------------------------------------------------
|
|
964
|
-
_step_header(
|
|
1613
|
+
_step_header(14, "Coordination Board")
|
|
965
1614
|
open_task_count = _step_board(home_path, name)
|
|
966
1615
|
|
|
967
1616
|
# -----------------------------------------------------------------------
|
|
968
|
-
# Step
|
|
1617
|
+
# Step 15: Auto-Start Service (systemd on Linux, launchd on macOS)
|
|
969
1618
|
# -----------------------------------------------------------------------
|
|
970
|
-
_step_header(
|
|
1619
|
+
_step_header(15, "Auto-Start Service")
|
|
971
1620
|
service_ok = _step_autostart_service(agent_name=agent_slug)
|
|
972
1621
|
|
|
973
1622
|
# -----------------------------------------------------------------------
|
|
974
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
"
|
|
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"
|