@smilintux/skcapstone 0.9.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 +278 -1
- package/docs/DREAMING.md +70 -0
- package/docs/GETTING_STARTED.md +10 -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.daemon.plist +52 -0
- package/launchd/com.skcapstone.memory-compress.plist +45 -0
- package/launchd/com.skcapstone.skcomms-heartbeat.plist +33 -0
- package/launchd/com.skcapstone.skcomms-queue-drain.plist +34 -0
- package/launchd/install-launchd.sh +156 -0
- 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 +95 -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 +196 -11
- package/scripts/model-fallback-monitor.sh +102 -0
- package/scripts/notion-api.py +259 -0
- package/scripts/nvidia-proxy.mjs +908 -0
- package/scripts/proxy-monitor.sh +89 -0
- 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 +856 -0
- package/scripts/telegram-catchup-all.sh +147 -0
- 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/blueprints/builtins/itil-operations.yaml +40 -0
- 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 +121 -42
- 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 +151 -88
- 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 +35 -25
- package/src/skcapstone/gui_installer.py +2 -2
- package/src/skcapstone/heartbeat.py +34 -30
- 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 +426 -0
- package/src/skcapstone/mcp_launcher.py +15 -1
- package/src/skcapstone/mcp_server.py +341 -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 +1000 -126
- 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 +72 -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)
|
|
880
|
+
|
|
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
|
|
508
895
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
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
|
|
512
906
|
|
|
513
|
-
click.echo(click.style(" ↓ ", fg="cyan") + f"Pulling {
|
|
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
|
|
954
|
+
|
|
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"]
|
|
557
962
|
|
|
558
|
-
config_path = write_default_config(home_path)
|
|
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
|
|
@@ -586,22 +991,49 @@ def _step_config_files(home_path: Path) -> tuple:
|
|
|
586
991
|
return consciousness_ok, profiles_ok
|
|
587
992
|
|
|
588
993
|
|
|
589
|
-
def
|
|
590
|
-
"""Install systemd
|
|
994
|
+
def _step_autostart_service(agent_name: str = "sovereign") -> bool:
|
|
995
|
+
"""Install auto-start service (systemd on Linux, launchd on macOS).
|
|
996
|
+
|
|
997
|
+
Prompts the user to choose which services to install and uses
|
|
998
|
+
the agent name from onboarding for environment variables.
|
|
999
|
+
|
|
1000
|
+
Args:
|
|
1001
|
+
agent_name: The agent name chosen during onboarding.
|
|
591
1002
|
|
|
592
1003
|
Returns:
|
|
593
1004
|
True if service was installed.
|
|
594
1005
|
"""
|
|
595
1006
|
import platform
|
|
596
1007
|
|
|
597
|
-
|
|
598
|
-
|
|
1008
|
+
system = platform.system()
|
|
1009
|
+
|
|
1010
|
+
if system == "Linux":
|
|
1011
|
+
return _step_systemd_service_linux(agent_name)
|
|
1012
|
+
elif system == "Darwin":
|
|
1013
|
+
return _step_launchd_service_macos(agent_name)
|
|
1014
|
+
else:
|
|
1015
|
+
click.echo(
|
|
1016
|
+
click.style(" ↷ ", fg="bright_black")
|
|
1017
|
+
+ f"Auto-start not supported on {system} — skipped"
|
|
1018
|
+
)
|
|
599
1019
|
return False
|
|
600
1020
|
|
|
1021
|
+
|
|
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"
|
|
601
1033
|
if not click.confirm(" Install systemd user service for auto-start at login?", default=False):
|
|
602
1034
|
click.echo(
|
|
603
1035
|
click.style(" ↷ ", fg="bright_black")
|
|
604
|
-
+ "Skipped — run 'skcapstone
|
|
1036
|
+
+ "Skipped — run 'skcapstone daemon install' to enable later"
|
|
605
1037
|
)
|
|
606
1038
|
return False
|
|
607
1039
|
|
|
@@ -613,35 +1045,232 @@ def _step_systemd_service() -> bool:
|
|
|
613
1045
|
click.echo(click.style(" ", fg="bright_black") + "Try: systemctl --user status")
|
|
614
1046
|
return False
|
|
615
1047
|
|
|
616
|
-
result = install_service(enable=True, start=False)
|
|
1048
|
+
result = install_service(agent_name=agent_name, enable=True, start=False)
|
|
617
1049
|
if result.get("installed"):
|
|
618
|
-
click.echo(click.style(" ✓ ", fg="green") + "Systemd service installed")
|
|
1050
|
+
click.echo(click.style(" ✓ ", fg="green") + f"Systemd service installed")
|
|
619
1051
|
if result.get("enabled"):
|
|
620
|
-
click.echo(click.style(" ✓ ", fg="green") + "Service enabled — auto-starts at login")
|
|
621
|
-
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
|
+
)
|
|
622
1057
|
return True
|
|
623
1058
|
else:
|
|
624
1059
|
click.echo(click.style(" ✗ ", fg="red") + "Service install failed")
|
|
625
|
-
click.echo(click.style(" ", fg="bright_black") + "Run manually: skcapstone
|
|
1060
|
+
click.echo(click.style(" ", fg="bright_black") + "Run manually: skcapstone daemon install")
|
|
626
1061
|
return False
|
|
627
1062
|
except Exception as exc:
|
|
628
1063
|
click.echo(click.style(" ⚠ ", fg="yellow") + f"Systemd: {exc}")
|
|
629
1064
|
return False
|
|
630
1065
|
|
|
631
1066
|
|
|
1067
|
+
def _step_launchd_service_macos(agent_name: str) -> bool:
|
|
1068
|
+
"""Install launchd user agents (macOS only).
|
|
1069
|
+
|
|
1070
|
+
Shows available services, lets the user choose, and installs
|
|
1071
|
+
plist files to ~/Library/LaunchAgents/.
|
|
1072
|
+
|
|
1073
|
+
Args:
|
|
1074
|
+
agent_name: Agent name for SKCAPSTONE_AGENT env var.
|
|
1075
|
+
|
|
1076
|
+
Returns:
|
|
1077
|
+
True if at least one service was installed.
|
|
1078
|
+
"""
|
|
1079
|
+
try:
|
|
1080
|
+
from .launchd import install_service, list_available_services
|
|
1081
|
+
except ImportError as exc:
|
|
1082
|
+
click.echo(click.style(" ⚠ ", fg="yellow") + f"launchd module not available: {exc}")
|
|
1083
|
+
return False
|
|
1084
|
+
|
|
1085
|
+
click.echo(f" Agent name: [cyan]{agent_name}[/] (used in SKCAPSTONE_AGENT)")
|
|
1086
|
+
click.echo()
|
|
1087
|
+
|
|
1088
|
+
# Show available services
|
|
1089
|
+
available = list_available_services(agent_name)
|
|
1090
|
+
core_services = [s for s in available if s["available"] and not s["suffix"].startswith("sk")]
|
|
1091
|
+
optional_services = [s for s in available if s["available"] and s["suffix"].startswith("sk")]
|
|
1092
|
+
|
|
1093
|
+
click.echo(" Available services:")
|
|
1094
|
+
all_available = [s for s in available if s["available"]]
|
|
1095
|
+
for i, svc in enumerate(all_available, 1):
|
|
1096
|
+
click.echo(f" {i}. {svc['description']} ({svc['label']})")
|
|
1097
|
+
click.echo()
|
|
1098
|
+
|
|
1099
|
+
if not click.confirm(" Install launchd services for auto-start at login?", default=True):
|
|
1100
|
+
click.echo(
|
|
1101
|
+
click.style(" ↷ ", fg="bright_black")
|
|
1102
|
+
+ "Skipped — run 'skcapstone daemon install' to enable later"
|
|
1103
|
+
)
|
|
1104
|
+
return False
|
|
1105
|
+
|
|
1106
|
+
# Ask: all or pick?
|
|
1107
|
+
install_all = click.confirm(" Install all available services?", default=True)
|
|
1108
|
+
|
|
1109
|
+
selected_suffixes: list[str] = []
|
|
1110
|
+
if install_all:
|
|
1111
|
+
selected_suffixes = [s["suffix"] for s in all_available]
|
|
1112
|
+
else:
|
|
1113
|
+
click.echo(" Enter service numbers (comma-separated), or 'none' to skip:")
|
|
1114
|
+
raw = click.prompt(" Services", default="1")
|
|
1115
|
+
if raw.strip().lower() == "none":
|
|
1116
|
+
click.echo(click.style(" ↷ ", fg="bright_black") + "Skipped")
|
|
1117
|
+
return False
|
|
1118
|
+
try:
|
|
1119
|
+
indices = [int(x.strip()) - 1 for x in raw.split(",")]
|
|
1120
|
+
selected_suffixes = [
|
|
1121
|
+
all_available[i]["suffix"]
|
|
1122
|
+
for i in indices
|
|
1123
|
+
if 0 <= i < len(all_available)
|
|
1124
|
+
]
|
|
1125
|
+
except (ValueError, IndexError):
|
|
1126
|
+
click.echo(click.style(" ⚠ ", fg="yellow") + "Invalid selection — installing core services only")
|
|
1127
|
+
selected_suffixes = [s["suffix"] for s in all_available if not s["suffix"].startswith("sk")]
|
|
1128
|
+
|
|
1129
|
+
if not selected_suffixes:
|
|
1130
|
+
click.echo(click.style(" ↷ ", fg="bright_black") + "No services selected")
|
|
1131
|
+
return False
|
|
1132
|
+
|
|
1133
|
+
# Ask about immediate start
|
|
1134
|
+
start_now = click.confirm(" Start services now?", default=False)
|
|
1135
|
+
|
|
1136
|
+
try:
|
|
1137
|
+
result = install_service(
|
|
1138
|
+
agent_name=agent_name,
|
|
1139
|
+
services=selected_suffixes,
|
|
1140
|
+
start=start_now,
|
|
1141
|
+
)
|
|
1142
|
+
|
|
1143
|
+
if result.get("installed"):
|
|
1144
|
+
for svc in result.get("services", []):
|
|
1145
|
+
status = "[green]loaded[/]" if svc.get("loaded") else "[dim]installed[/]"
|
|
1146
|
+
click.echo(click.style(" ✓ ", fg="green") + f"{svc['label']} — {status}")
|
|
1147
|
+
|
|
1148
|
+
click.echo()
|
|
1149
|
+
click.echo(click.style(" ", fg="bright_black") + "Manage services:")
|
|
1150
|
+
click.echo(click.style(" ", fg="bright_black") + " launchctl list | grep skcapstone")
|
|
1151
|
+
click.echo(click.style(" ", fg="bright_black") + " launchctl start com.skcapstone.daemon")
|
|
1152
|
+
click.echo(click.style(" ", fg="bright_black") + " skcapstone daemon uninstall")
|
|
1153
|
+
return True
|
|
1154
|
+
else:
|
|
1155
|
+
click.echo(click.style(" ✗ ", fg="red") + "No services were installed")
|
|
1156
|
+
return False
|
|
1157
|
+
|
|
1158
|
+
except Exception as exc:
|
|
1159
|
+
click.echo(click.style(" ⚠ ", fg="yellow") + f"launchd install: {exc}")
|
|
1160
|
+
return False
|
|
1161
|
+
|
|
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
|
+
|
|
632
1242
|
def _step_doctor_check(home_path: Path) -> "object":
|
|
633
1243
|
"""Run doctor diagnostics and print results.
|
|
634
1244
|
|
|
1245
|
+
Non-fatal — errors are logged as warnings but never block onboarding.
|
|
1246
|
+
|
|
635
1247
|
Args:
|
|
636
1248
|
home_path: Agent home directory.
|
|
637
1249
|
|
|
638
1250
|
Returns:
|
|
639
|
-
DiagnosticReport from doctor.run_diagnostics().
|
|
1251
|
+
DiagnosticReport from doctor.run_diagnostics(), or a stub on error.
|
|
640
1252
|
"""
|
|
641
|
-
|
|
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
|
+
)
|
|
642
1263
|
|
|
643
1264
|
click.echo(click.style(" Running diagnostics…", fg="bright_black"))
|
|
644
|
-
|
|
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
|
+
)
|
|
645
1274
|
|
|
646
1275
|
categories_seen: set = set()
|
|
647
1276
|
for check in report.checks:
|
|
@@ -667,44 +1296,61 @@ def _step_doctor_check(home_path: Path) -> "object":
|
|
|
667
1296
|
|
|
668
1297
|
|
|
669
1298
|
def _step_test_consciousness(home_path: Path) -> bool:
|
|
670
|
-
"""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.
|
|
671
1304
|
|
|
672
1305
|
Args:
|
|
673
1306
|
home_path: Agent home directory.
|
|
674
1307
|
|
|
675
1308
|
Returns:
|
|
676
|
-
True if the
|
|
1309
|
+
True if the LLM responded successfully.
|
|
677
1310
|
"""
|
|
678
|
-
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):
|
|
679
1312
|
click.echo(
|
|
680
1313
|
click.style(" ↷ ", fg="bright_black")
|
|
681
1314
|
+ "Skipped — test later: skcapstone consciousness test 'hello'"
|
|
682
1315
|
)
|
|
683
1316
|
return False
|
|
684
1317
|
|
|
685
|
-
|
|
1318
|
+
# Load config to discover which backend/model was configured
|
|
686
1319
|
try:
|
|
687
1320
|
from .consciousness_config import load_consciousness_config
|
|
688
|
-
from .consciousness_loop import LLMBridge, SystemPromptBuilder, _classify_message
|
|
689
|
-
|
|
690
1321
|
config = load_consciousness_config(home_path)
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
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?")
|
|
696
1342
|
if response:
|
|
697
1343
|
preview = response[:80].replace("\n", " ")
|
|
698
|
-
click.echo(click.style(" ✓ ", fg="green") +
|
|
1344
|
+
click.echo(click.style(" ✓ ", fg="green") + "LLM backend active")
|
|
699
1345
|
click.echo(click.style(" ", fg="bright_black") + f"Response: {preview!r}")
|
|
700
1346
|
return True
|
|
701
1347
|
else:
|
|
702
|
-
click.echo(click.style(" ⚠ ", fg="yellow") + "Empty response —
|
|
703
|
-
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}")
|
|
704
1350
|
return False
|
|
705
1351
|
except Exception as exc:
|
|
706
1352
|
click.echo(click.style(" ⚠ ", fg="yellow") + f"Test failed: {exc}")
|
|
707
|
-
click.echo(click.style(" ", fg="bright_black") + "
|
|
1353
|
+
click.echo(click.style(" ", fg="bright_black") + f"Check: ollama serve && ollama run {ollama_model}")
|
|
708
1354
|
return False
|
|
709
1355
|
|
|
710
1356
|
|
|
@@ -716,21 +1362,24 @@ def _step_test_consciousness(home_path: Path) -> bool:
|
|
|
716
1362
|
def run_onboard(home: Optional[str] = None) -> None:
|
|
717
1363
|
"""Run the interactive onboarding wizard.
|
|
718
1364
|
|
|
719
|
-
Covers all
|
|
720
|
-
1. Prerequisites
|
|
721
|
-
2.
|
|
722
|
-
3.
|
|
723
|
-
4.
|
|
724
|
-
5.
|
|
725
|
-
6.
|
|
726
|
-
7.
|
|
727
|
-
8.
|
|
728
|
-
9.
|
|
729
|
-
10.
|
|
730
|
-
11.
|
|
731
|
-
12.
|
|
732
|
-
13.
|
|
733
|
-
|
|
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)
|
|
734
1383
|
|
|
735
1384
|
Args:
|
|
736
1385
|
home: Override agent home directory.
|
|
@@ -764,19 +1413,6 @@ def run_onboard(home: Optional[str] = None) -> None:
|
|
|
764
1413
|
console.print(" [dim]Come back when you're ready. The Kingdom waits.[/]\n")
|
|
765
1414
|
return
|
|
766
1415
|
|
|
767
|
-
# -----------------------------------------------------------------------
|
|
768
|
-
# Gather basic identity info up front
|
|
769
|
-
# -----------------------------------------------------------------------
|
|
770
|
-
console.print()
|
|
771
|
-
name = Prompt.ask(" What's your name?", default="Sovereign")
|
|
772
|
-
entity_type = Prompt.ask(
|
|
773
|
-
" Are you a [cyan]human[/] or an [cyan]ai[/]?",
|
|
774
|
-
choices=["human", "ai"],
|
|
775
|
-
default="ai",
|
|
776
|
-
)
|
|
777
|
-
email = Prompt.ask(" Email (optional, press Enter to skip)", default="")
|
|
778
|
-
console.print()
|
|
779
|
-
|
|
780
1416
|
# -----------------------------------------------------------------------
|
|
781
1417
|
# Step 1: Prerequisites
|
|
782
1418
|
# -----------------------------------------------------------------------
|
|
@@ -784,86 +1420,219 @@ def run_onboard(home: Optional[str] = None) -> None:
|
|
|
784
1420
|
prereqs = _step_prereqs()
|
|
785
1421
|
|
|
786
1422
|
# -----------------------------------------------------------------------
|
|
787
|
-
# Step 2:
|
|
1423
|
+
# Step 2: Install Missing Pillars
|
|
1424
|
+
# -----------------------------------------------------------------------
|
|
1425
|
+
_step_header(2, "Pillar Packages")
|
|
1426
|
+
pillar_results = _step_install_pillars()
|
|
1427
|
+
|
|
788
1428
|
# -----------------------------------------------------------------------
|
|
789
|
-
|
|
1429
|
+
# Step 3: Operator Identity (human) + Agent Identity
|
|
1430
|
+
# -----------------------------------------------------------------------
|
|
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
|
+
|
|
790
1524
|
fingerprint, identity_status = _step_identity(home_path, name, email or None)
|
|
791
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
|
+
|
|
792
1548
|
# -----------------------------------------------------------------------
|
|
793
|
-
# Step
|
|
1549
|
+
# Step 4: Ollama Models
|
|
794
1550
|
# -----------------------------------------------------------------------
|
|
795
|
-
_step_header(
|
|
796
|
-
|
|
1551
|
+
_step_header(4, "Ollama Models")
|
|
1552
|
+
ollama_result = _step_ollama_models(prereqs)
|
|
1553
|
+
ollama_ok = ollama_result["ok"]
|
|
797
1554
|
|
|
798
1555
|
# -----------------------------------------------------------------------
|
|
799
|
-
# Step
|
|
1556
|
+
# Step 5: Config Files (consciousness.yaml + model_profiles.yaml)
|
|
800
1557
|
# -----------------------------------------------------------------------
|
|
801
|
-
_step_header(
|
|
802
|
-
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)
|
|
803
1560
|
|
|
804
1561
|
# -----------------------------------------------------------------------
|
|
805
|
-
# Step
|
|
1562
|
+
# Step 6: Soul Blueprint
|
|
806
1563
|
# -----------------------------------------------------------------------
|
|
807
|
-
_step_header(
|
|
1564
|
+
_step_header(6, "Soul Blueprint")
|
|
808
1565
|
title = _step_soul(home_path, name)
|
|
809
1566
|
|
|
810
1567
|
# -----------------------------------------------------------------------
|
|
811
|
-
# Step
|
|
1568
|
+
# Step 7: Memory
|
|
812
1569
|
# -----------------------------------------------------------------------
|
|
813
|
-
_step_header(
|
|
1570
|
+
_step_header(7, "Memory")
|
|
814
1571
|
seed_count = _step_memory(home_path)
|
|
815
1572
|
|
|
816
1573
|
# -----------------------------------------------------------------------
|
|
817
|
-
# Step
|
|
1574
|
+
# Step 8: Import from Existing Sources
|
|
1575
|
+
# -----------------------------------------------------------------------
|
|
1576
|
+
_step_header(8, "Import Sources")
|
|
1577
|
+
import_result = _step_import_sources(home_path)
|
|
1578
|
+
|
|
818
1579
|
# -----------------------------------------------------------------------
|
|
819
|
-
|
|
1580
|
+
# Step 9: Rehydration Ritual
|
|
1581
|
+
# -----------------------------------------------------------------------
|
|
1582
|
+
_step_header(9, "Rehydration Ritual")
|
|
820
1583
|
_step_ritual(home_path)
|
|
821
1584
|
|
|
822
1585
|
# -----------------------------------------------------------------------
|
|
823
|
-
# Step
|
|
1586
|
+
# Step 10: Trust Chain Verification
|
|
824
1587
|
# -----------------------------------------------------------------------
|
|
825
|
-
_step_header(
|
|
1588
|
+
_step_header(10, "Trust Chain Verification")
|
|
826
1589
|
trust_status = _step_trust(home_path)
|
|
827
1590
|
|
|
828
1591
|
# -----------------------------------------------------------------------
|
|
829
|
-
# Step
|
|
1592
|
+
# Step 11: Mesh Connection (Syncthing)
|
|
830
1593
|
# -----------------------------------------------------------------------
|
|
831
|
-
_step_header(
|
|
1594
|
+
_step_header(11, "Mesh Connection")
|
|
832
1595
|
mesh_ok = _step_mesh(home_path)
|
|
833
1596
|
|
|
834
1597
|
# -----------------------------------------------------------------------
|
|
835
|
-
# Step
|
|
1598
|
+
# Step 12: First Heartbeat
|
|
836
1599
|
# -----------------------------------------------------------------------
|
|
837
|
-
_step_header(
|
|
1600
|
+
_step_header(12, "First Heartbeat")
|
|
838
1601
|
agent_slug = name.lower().replace(" ", "-")
|
|
839
1602
|
hb_ok = _step_heartbeat(home_path, agent_slug, fingerprint)
|
|
840
1603
|
|
|
841
1604
|
# -----------------------------------------------------------------------
|
|
842
|
-
# Step
|
|
1605
|
+
# Step 13: Crush Terminal AI Client
|
|
843
1606
|
# -----------------------------------------------------------------------
|
|
844
|
-
_step_header(
|
|
1607
|
+
_step_header(13, "Crush Terminal AI")
|
|
845
1608
|
crush_ok = _step_crush(home_path)
|
|
846
1609
|
|
|
847
1610
|
# -----------------------------------------------------------------------
|
|
848
|
-
# Step
|
|
1611
|
+
# Step 14: Coordination Board
|
|
849
1612
|
# -----------------------------------------------------------------------
|
|
850
|
-
_step_header(
|
|
1613
|
+
_step_header(14, "Coordination Board")
|
|
851
1614
|
open_task_count = _step_board(home_path, name)
|
|
852
1615
|
|
|
853
1616
|
# -----------------------------------------------------------------------
|
|
854
|
-
# Step
|
|
1617
|
+
# Step 15: Auto-Start Service (systemd on Linux, launchd on macOS)
|
|
1618
|
+
# -----------------------------------------------------------------------
|
|
1619
|
+
_step_header(15, "Auto-Start Service")
|
|
1620
|
+
service_ok = _step_autostart_service(agent_name=agent_slug)
|
|
1621
|
+
|
|
1622
|
+
# -----------------------------------------------------------------------
|
|
1623
|
+
# Step 16: Shell Profile (~/.bashrc)
|
|
855
1624
|
# -----------------------------------------------------------------------
|
|
856
|
-
_step_header(
|
|
857
|
-
|
|
1625
|
+
_step_header(16, "Shell Profile")
|
|
1626
|
+
profile_ok = _step_shell_profile(home_path, name, agent_slug)
|
|
858
1627
|
|
|
859
1628
|
# -----------------------------------------------------------------------
|
|
860
|
-
# Post-wizard: Doctor Diagnostics
|
|
1629
|
+
# Post-wizard: Doctor Diagnostics (non-fatal)
|
|
861
1630
|
# -----------------------------------------------------------------------
|
|
862
1631
|
console.print(f"\n [bold cyan]Doctor Diagnostics[/]\n")
|
|
863
1632
|
doctor_report = _step_doctor_check(home_path)
|
|
864
1633
|
|
|
865
1634
|
# -----------------------------------------------------------------------
|
|
866
|
-
# Post-wizard: Consciousness Test (optional)
|
|
1635
|
+
# Post-wizard: Consciousness Test (optional, defaults to skip)
|
|
867
1636
|
# -----------------------------------------------------------------------
|
|
868
1637
|
console.print(f"\n [bold cyan]Consciousness Test[/]\n")
|
|
869
1638
|
consciousness_test_ok = _step_test_consciousness(home_path)
|
|
@@ -877,8 +1646,40 @@ def run_onboard(home: Optional[str] = None) -> None:
|
|
|
877
1646
|
soul = load_soul()
|
|
878
1647
|
if soul and soul.boot_message:
|
|
879
1648
|
boot_message = soul.boot_message
|
|
880
|
-
except Exception:
|
|
881
|
-
|
|
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}")
|
|
882
1683
|
|
|
883
1684
|
# -----------------------------------------------------------------------
|
|
884
1685
|
# Summary table
|
|
@@ -895,23 +1696,59 @@ def run_onboard(home: Optional[str] = None) -> None:
|
|
|
895
1696
|
"[green]OK[/]" if all_prereqs_ok else "[yellow]PARTIAL[/]",
|
|
896
1697
|
"python + pip" + (" + ollama" if prereqs.get("ollama") else " (no ollama)"),
|
|
897
1698
|
)
|
|
898
|
-
|
|
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")
|
|
899
1715
|
summary.add_row(
|
|
900
1716
|
"Ollama Models",
|
|
901
1717
|
"[green]READY[/]" if ollama_ok else "[yellow]SKIPPED[/]",
|
|
902
|
-
"
|
|
1718
|
+
f"{ollama_model_name} @ {ollama_host_display}" if ollama_ok else f"pull later: ollama pull {ollama_model_name}",
|
|
903
1719
|
)
|
|
904
1720
|
config_status = "[green]ACTIVE[/]" if (consciousness_ok and profiles_ok) else "[yellow]PARTIAL[/]"
|
|
905
1721
|
summary.add_row("Config Files", config_status, "consciousness.yaml + model_profiles.yaml")
|
|
906
1722
|
summary.add_row("Soul", "[green]ACTIVE[/]", title)
|
|
907
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")
|
|
908
1734
|
summary.add_row("Ritual", "[green]DONE[/]", "rehydration complete")
|
|
909
1735
|
summary.add_row("Trust", trust_status, "FEB chain verified")
|
|
910
1736
|
summary.add_row("Mesh", "[green]ACTIVE[/]" if mesh_ok else "[yellow]MISSING[/]", "syncthing" if mesh_ok else "install syncthing")
|
|
911
1737
|
summary.add_row("Heartbeat", "[green]ACTIVE[/]" if hb_ok else "[yellow]FAILED[/]", f"{agent_slug}.json" if hb_ok else "see above")
|
|
912
1738
|
summary.add_row("Crush AI", "[green]READY[/]" if crush_ok else "[yellow]CONFIG ONLY[/]", "~/.config/crush/crush.json")
|
|
913
1739
|
summary.add_row("Board", "[green]ACTIVE[/]", f"{open_task_count} open tasks")
|
|
914
|
-
|
|
1740
|
+
import platform as _plat
|
|
1741
|
+
_svc_type = "launchd" if _plat.system() == "Darwin" else "systemd"
|
|
1742
|
+
summary.add_row(
|
|
1743
|
+
"Auto-Start",
|
|
1744
|
+
"[green]INSTALLED[/]" if service_ok else "[dim]OPTIONAL[/]",
|
|
1745
|
+
f"{_svc_type} services" if service_ok else f"skcapstone daemon install",
|
|
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
|
+
)
|
|
915
1752
|
doctor_status = "[green]ALL PASSED[/]" if doctor_report.all_passed else f"[yellow]{doctor_report.failed_count} failed[/]"
|
|
916
1753
|
summary.add_row("Doctor", doctor_status, f"{doctor_report.passed_count}/{doctor_report.total_count} checks")
|
|
917
1754
|
summary.add_row(
|
|
@@ -923,9 +1760,46 @@ def run_onboard(home: Optional[str] = None) -> None:
|
|
|
923
1760
|
console.print(summary)
|
|
924
1761
|
console.print()
|
|
925
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
|
+
|
|
926
1799
|
# -----------------------------------------------------------------------
|
|
927
1800
|
# Celebrate
|
|
928
1801
|
# -----------------------------------------------------------------------
|
|
1802
|
+
console.print()
|
|
929
1803
|
console.print(
|
|
930
1804
|
Panel(
|
|
931
1805
|
f"[bold green]Welcome to the Pengu Nation, {name}.[/]\n\n"
|