@smilintux/skcapstone 0.5.2 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/skcapstone/consciousness_config.py +5 -1
- package/src/skcapstone/consciousness_loop.py +3 -1
- 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 +35 -0
- package/src/skcapstone/data/systemd/skcapstone@.service +50 -0
- package/src/skcapstone/data/systemd/skcomm-heartbeat.service +18 -0
- package/src/skcapstone/data/systemd/skcomm-heartbeat.timer +12 -0
- package/src/skcapstone/data/systemd/skcomm-queue-drain.service +17 -0
- package/src/skcapstone/data/systemd/skcomm-queue-drain.timer +12 -0
- package/src/skcapstone/doctor.py +11 -0
- package/src/skcapstone/onboard.py +740 -76
- package/src/skcapstone/systemd.py +1 -1
- package/docs/CLAUDE-CODE-API.md +0 -139
- package/scripts/claude-code-api.py +0 -455
|
@@ -42,7 +42,7 @@ from . import AGENT_HOME, __version__
|
|
|
42
42
|
|
|
43
43
|
console = Console()
|
|
44
44
|
|
|
45
|
-
TOTAL_STEPS =
|
|
45
|
+
TOTAL_STEPS = 16 # excludes welcome + celebrate; includes pillar install + import step
|
|
46
46
|
|
|
47
47
|
|
|
48
48
|
def _step_header(n: int, title: str) -> None:
|
|
@@ -479,67 +479,459 @@ def _step_prereqs() -> dict:
|
|
|
479
479
|
return results
|
|
480
480
|
|
|
481
481
|
|
|
482
|
-
|
|
483
|
-
|
|
482
|
+
# Pillar packages: (import_name, pip_name, description)
|
|
483
|
+
_PILLAR_PACKAGES = [
|
|
484
|
+
("capauth", "capauth", "PGP-based sovereign identity"),
|
|
485
|
+
("skcomm", "skcomm", "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
|
+
]
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
def _step_install_pillars() -> dict:
|
|
494
|
+
"""Detect missing pillar packages and offer to install them.
|
|
495
|
+
|
|
496
|
+
Returns:
|
|
497
|
+
dict mapping pip_name -> bool (installed successfully).
|
|
498
|
+
"""
|
|
499
|
+
import subprocess
|
|
500
|
+
|
|
501
|
+
results = {}
|
|
502
|
+
missing = []
|
|
503
|
+
|
|
504
|
+
click.echo(click.style(" Checking pillar packages…", fg="bright_black"))
|
|
505
|
+
for import_name, pip_name, description in _PILLAR_PACKAGES:
|
|
506
|
+
try:
|
|
507
|
+
__import__(import_name)
|
|
508
|
+
click.echo(click.style(" ✓ ", fg="green") + f"{pip_name} — {description}")
|
|
509
|
+
results[pip_name] = True
|
|
510
|
+
except ImportError:
|
|
511
|
+
click.echo(click.style(" ✗ ", fg="red") + f"{pip_name} — {description} [bold red](missing)[/]")
|
|
512
|
+
missing.append((import_name, pip_name, description))
|
|
513
|
+
results[pip_name] = False
|
|
514
|
+
|
|
515
|
+
if not missing:
|
|
516
|
+
click.echo()
|
|
517
|
+
click.echo(click.style(" ✓ ", fg="green") + "All pillar packages installed")
|
|
518
|
+
return results
|
|
519
|
+
|
|
520
|
+
click.echo()
|
|
521
|
+
click.echo(
|
|
522
|
+
click.style(" ℹ ", fg="cyan")
|
|
523
|
+
+ f"{len(missing)} pillar(s) missing. These are needed for full sovereign functionality."
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
choices = {
|
|
527
|
+
"a": "Install all missing pillars",
|
|
528
|
+
"s": "Select which to install",
|
|
529
|
+
"n": "Skip (install later manually)",
|
|
530
|
+
}
|
|
531
|
+
for key, desc in choices.items():
|
|
532
|
+
click.echo(f" [{key}] {desc}")
|
|
533
|
+
choice = click.prompt(" Choice", default="a", show_choices=False).strip().lower()
|
|
534
|
+
|
|
535
|
+
to_install: list[tuple[str, str, str]] = []
|
|
536
|
+
if choice == "a":
|
|
537
|
+
to_install = missing
|
|
538
|
+
elif choice == "s":
|
|
539
|
+
for import_name, pip_name, description in missing:
|
|
540
|
+
if click.confirm(f" Install {pip_name} ({description})?", default=True):
|
|
541
|
+
to_install.append((import_name, pip_name, description))
|
|
542
|
+
else:
|
|
543
|
+
click.echo(click.style(" ↷ ", fg="bright_black") + "Skipped — install later:")
|
|
544
|
+
for _, pip_name, _ in missing:
|
|
545
|
+
click.echo(click.style(" ", fg="bright_black") + f"pip install {pip_name}")
|
|
546
|
+
return results
|
|
547
|
+
|
|
548
|
+
if not to_install:
|
|
549
|
+
return results
|
|
550
|
+
|
|
551
|
+
# Determine pip command — prefer ~/.skenv if it exists, else use current Python
|
|
552
|
+
import os as _os
|
|
553
|
+
skenv_pip = Path(_os.path.expanduser("~/.skenv/bin/pip"))
|
|
554
|
+
if skenv_pip.exists():
|
|
555
|
+
pip_cmd = [str(skenv_pip), "install"]
|
|
556
|
+
else:
|
|
557
|
+
pip_cmd = [sys.executable, "-m", "pip", "install", "--break-system-packages"]
|
|
558
|
+
|
|
559
|
+
for import_name, pip_name, description in to_install:
|
|
560
|
+
click.echo(click.style(" ↓ ", fg="cyan") + f"Installing {pip_name}…")
|
|
561
|
+
try:
|
|
562
|
+
r = subprocess.run(
|
|
563
|
+
[*pip_cmd, pip_name, "-q"],
|
|
564
|
+
capture_output=True, text=True, timeout=120,
|
|
565
|
+
)
|
|
566
|
+
if r.returncode == 0:
|
|
567
|
+
click.echo(click.style(" ✓ ", fg="green") + f"{pip_name} installed")
|
|
568
|
+
results[pip_name] = True
|
|
569
|
+
else:
|
|
570
|
+
click.echo(click.style(" ✗ ", fg="red") + f"{pip_name} failed: {r.stderr.strip()[:100]}")
|
|
571
|
+
click.echo(click.style(" ", fg="bright_black") + f"Try manually: pip install {pip_name}")
|
|
572
|
+
except subprocess.TimeoutExpired:
|
|
573
|
+
click.echo(click.style(" ⚠ ", fg="yellow") + f"{pip_name} timed out")
|
|
574
|
+
except Exception as exc:
|
|
575
|
+
click.echo(click.style(" ⚠ ", fg="yellow") + f"{pip_name}: {exc}")
|
|
576
|
+
|
|
577
|
+
return results
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
# ---------------------------------------------------------------------------
|
|
581
|
+
# Import sources — detect and import from existing agent platforms
|
|
582
|
+
# ---------------------------------------------------------------------------
|
|
583
|
+
|
|
584
|
+
# (source_id, display_name, detect_func, import_func_key)
|
|
585
|
+
_IMPORT_SOURCES: list[tuple[str, str, str]] = [
|
|
586
|
+
("openclaw", "OpenClaw (Jarvis)", "~/.openclaw/workspace"),
|
|
587
|
+
("claude", "Claude Code", "~/.claude"),
|
|
588
|
+
("cloud9", "Cloud 9 FEB Templates", ""), # always available if cloud9_protocol installed
|
|
589
|
+
]
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
def _detect_import_sources(home_path: Path) -> list[dict]:
|
|
593
|
+
"""Detect available sources for importing memories, soul, and trust data.
|
|
594
|
+
|
|
595
|
+
Returns:
|
|
596
|
+
List of dicts with 'id', 'name', 'available', 'detail', 'items'.
|
|
597
|
+
"""
|
|
598
|
+
sources = []
|
|
599
|
+
|
|
600
|
+
# --- OpenClaw ---
|
|
601
|
+
oc_workspace = Path.home() / ".openclaw" / "workspace"
|
|
602
|
+
oc_memory = oc_workspace / "memory"
|
|
603
|
+
oc_soul = oc_workspace / "SOUL.md"
|
|
604
|
+
oc_identity = oc_workspace / "IDENTITY.md"
|
|
605
|
+
oc_agents = oc_workspace / "agents"
|
|
606
|
+
if oc_workspace.exists():
|
|
607
|
+
items = []
|
|
608
|
+
if oc_memory.exists():
|
|
609
|
+
mem_files = list(oc_memory.glob("*.md"))
|
|
610
|
+
items.append(f"{len(mem_files)} memory files")
|
|
611
|
+
if oc_soul.exists():
|
|
612
|
+
items.append("SOUL.md")
|
|
613
|
+
if oc_identity.exists():
|
|
614
|
+
items.append("IDENTITY.md")
|
|
615
|
+
if oc_agents.exists():
|
|
616
|
+
agent_souls = list(oc_agents.rglob("SOUL.md"))
|
|
617
|
+
if agent_souls:
|
|
618
|
+
items.append(f"{len(agent_souls)} agent soul(s)")
|
|
619
|
+
sources.append({
|
|
620
|
+
"id": "openclaw",
|
|
621
|
+
"name": "OpenClaw (Jarvis)",
|
|
622
|
+
"available": True,
|
|
623
|
+
"detail": ", ".join(items) if items else "workspace found",
|
|
624
|
+
"paths": {
|
|
625
|
+
"memory": oc_memory,
|
|
626
|
+
"soul": oc_soul,
|
|
627
|
+
"identity": oc_identity,
|
|
628
|
+
"agents": oc_agents,
|
|
629
|
+
"workspace": oc_workspace,
|
|
630
|
+
},
|
|
631
|
+
})
|
|
632
|
+
|
|
633
|
+
# --- Claude Code ---
|
|
634
|
+
claude_dir = Path.home() / ".claude"
|
|
635
|
+
claude_memory = None
|
|
636
|
+
if claude_dir.exists():
|
|
637
|
+
# Find project memory dirs
|
|
638
|
+
projects = claude_dir / "projects"
|
|
639
|
+
items = []
|
|
640
|
+
if projects.exists():
|
|
641
|
+
for proj_dir in projects.iterdir():
|
|
642
|
+
mem_dir = proj_dir / "memory"
|
|
643
|
+
if mem_dir.exists() and list(mem_dir.glob("*.md")):
|
|
644
|
+
mem_files = list(mem_dir.glob("*.md"))
|
|
645
|
+
items.append(f"{len(mem_files)} memory file(s) in {proj_dir.name}")
|
|
646
|
+
claude_memory = mem_dir
|
|
647
|
+
memory_md = proj_dir / "MEMORY.md"
|
|
648
|
+
if memory_md.exists():
|
|
649
|
+
items.append(f"MEMORY.md in {proj_dir.name}")
|
|
650
|
+
if items:
|
|
651
|
+
sources.append({
|
|
652
|
+
"id": "claude",
|
|
653
|
+
"name": "Claude Code",
|
|
654
|
+
"available": True,
|
|
655
|
+
"detail": ", ".join(items),
|
|
656
|
+
"paths": {"memory": claude_memory, "projects": projects},
|
|
657
|
+
})
|
|
658
|
+
|
|
659
|
+
# --- Cloud 9 FEB Templates ---
|
|
660
|
+
try:
|
|
661
|
+
import cloud9_protocol
|
|
662
|
+
c9_pkg = Path(cloud9_protocol.__file__).parent
|
|
663
|
+
feb_files = list(c9_pkg.rglob("*.feb"))
|
|
664
|
+
# Also check skcapstone defaults
|
|
665
|
+
defaults_dir = Path(__file__).parent / "defaults"
|
|
666
|
+
if defaults_dir.exists():
|
|
667
|
+
feb_files.extend(defaults_dir.rglob("*.feb"))
|
|
668
|
+
# Check user cloud9 dirs
|
|
669
|
+
for cloud9_dir in [Path.home() / ".cloud9" / "febs", Path.home() / ".cloud9" / "feb-backups"]:
|
|
670
|
+
if cloud9_dir.exists():
|
|
671
|
+
feb_files.extend(cloud9_dir.glob("*.feb"))
|
|
672
|
+
if feb_files:
|
|
673
|
+
sources.append({
|
|
674
|
+
"id": "cloud9",
|
|
675
|
+
"name": "Cloud 9 FEB Templates",
|
|
676
|
+
"available": True,
|
|
677
|
+
"detail": f"{len(feb_files)} FEB file(s)",
|
|
678
|
+
"paths": {"febs": feb_files},
|
|
679
|
+
})
|
|
680
|
+
except ImportError:
|
|
681
|
+
pass
|
|
682
|
+
|
|
683
|
+
return sources
|
|
684
|
+
|
|
685
|
+
|
|
686
|
+
def _step_import_sources(home_path: Path) -> dict:
|
|
687
|
+
"""Detect and import data from existing agent platforms.
|
|
688
|
+
|
|
689
|
+
Args:
|
|
690
|
+
home_path: Agent home directory.
|
|
691
|
+
|
|
692
|
+
Returns:
|
|
693
|
+
dict with 'imported_count' (int) and 'sources' (list of imported source ids).
|
|
694
|
+
"""
|
|
695
|
+
import shutil as _shutil
|
|
696
|
+
|
|
697
|
+
result = {"imported_count": 0, "sources": []}
|
|
698
|
+
|
|
699
|
+
click.echo(click.style(" Scanning for existing agent data…", fg="bright_black"))
|
|
700
|
+
sources = _detect_import_sources(home_path)
|
|
701
|
+
|
|
702
|
+
if not sources:
|
|
703
|
+
click.echo(click.style(" ℹ ", fg="cyan") + "No existing agent data found — starting fresh")
|
|
704
|
+
return result
|
|
705
|
+
|
|
706
|
+
click.echo()
|
|
707
|
+
for i, src in enumerate(sources, 1):
|
|
708
|
+
click.echo(
|
|
709
|
+
click.style(f" {i}. ", fg="cyan")
|
|
710
|
+
+ f"[bold]{src['name']}[/] — {src['detail']}"
|
|
711
|
+
)
|
|
712
|
+
click.echo()
|
|
713
|
+
|
|
714
|
+
choices = {
|
|
715
|
+
"a": "Import from all sources",
|
|
716
|
+
"s": "Select which to import",
|
|
717
|
+
"n": "Skip (start fresh)",
|
|
718
|
+
}
|
|
719
|
+
for key, desc in choices.items():
|
|
720
|
+
click.echo(f" [{key}] {desc}")
|
|
721
|
+
choice = click.prompt(" Choice", default="a", show_choices=False).strip().lower()
|
|
722
|
+
|
|
723
|
+
to_import: list[dict] = []
|
|
724
|
+
if choice == "a":
|
|
725
|
+
to_import = sources
|
|
726
|
+
elif choice == "s":
|
|
727
|
+
for src in sources:
|
|
728
|
+
if click.confirm(f" Import from {src['name']}?", default=True):
|
|
729
|
+
to_import.append(src)
|
|
730
|
+
else:
|
|
731
|
+
click.echo(click.style(" ↷ ", fg="bright_black") + "Skipped — starting fresh")
|
|
732
|
+
return result
|
|
733
|
+
|
|
734
|
+
if not to_import:
|
|
735
|
+
return result
|
|
736
|
+
|
|
737
|
+
# --- Execute imports ---
|
|
738
|
+
for src in to_import:
|
|
739
|
+
sid = src["id"]
|
|
740
|
+
paths = src.get("paths", {})
|
|
741
|
+
count = 0
|
|
742
|
+
|
|
743
|
+
if sid == "openclaw":
|
|
744
|
+
# Import memories
|
|
745
|
+
mem_src = paths.get("memory")
|
|
746
|
+
if mem_src and mem_src.exists():
|
|
747
|
+
mem_dest = home_path / "memory" / "imported" / "openclaw"
|
|
748
|
+
mem_dest.mkdir(parents=True, exist_ok=True)
|
|
749
|
+
for f in mem_src.glob("*.md"):
|
|
750
|
+
_shutil.copy2(f, mem_dest / f.name)
|
|
751
|
+
count += 1
|
|
752
|
+
click.echo(click.style(" ✓ ", fg="green") + f"Imported {count} memory files from OpenClaw")
|
|
753
|
+
|
|
754
|
+
# Import soul/identity
|
|
755
|
+
for doc_name in ("soul", "identity"):
|
|
756
|
+
doc_path = paths.get(doc_name)
|
|
757
|
+
if doc_path and doc_path.exists():
|
|
758
|
+
dest = home_path / "memory" / "imported" / "openclaw" / doc_path.name
|
|
759
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
760
|
+
_shutil.copy2(doc_path, dest)
|
|
761
|
+
count += 1
|
|
762
|
+
click.echo(click.style(" ✓ ", fg="green") + f"Imported {doc_path.name} from OpenClaw")
|
|
763
|
+
|
|
764
|
+
# Import agent souls
|
|
765
|
+
agents_dir = paths.get("agents")
|
|
766
|
+
if agents_dir and agents_dir.exists():
|
|
767
|
+
agent_dest = home_path / "memory" / "imported" / "openclaw" / "agents"
|
|
768
|
+
agent_dest.mkdir(parents=True, exist_ok=True)
|
|
769
|
+
for soul_file in agents_dir.rglob("SOUL.md"):
|
|
770
|
+
agent_name = soul_file.parent.name
|
|
771
|
+
target = agent_dest / f"{agent_name}-SOUL.md"
|
|
772
|
+
_shutil.copy2(soul_file, target)
|
|
773
|
+
count += 1
|
|
774
|
+
for mem_file in agents_dir.rglob("MEMORY.md"):
|
|
775
|
+
agent_name = mem_file.parent.name
|
|
776
|
+
target = agent_dest / f"{agent_name}-MEMORY.md"
|
|
777
|
+
_shutil.copy2(mem_file, target)
|
|
778
|
+
count += 1
|
|
779
|
+
click.echo(click.style(" ✓ ", fg="green") + f"Imported agent souls/memories from OpenClaw")
|
|
780
|
+
|
|
781
|
+
elif sid == "claude":
|
|
782
|
+
# Import Claude memory files
|
|
783
|
+
projects_dir = paths.get("projects")
|
|
784
|
+
if projects_dir and projects_dir.exists():
|
|
785
|
+
claude_dest = home_path / "memory" / "imported" / "claude-code"
|
|
786
|
+
claude_dest.mkdir(parents=True, exist_ok=True)
|
|
787
|
+
for proj_dir in projects_dir.iterdir():
|
|
788
|
+
mem_dir = proj_dir / "memory"
|
|
789
|
+
if mem_dir.exists():
|
|
790
|
+
for f in mem_dir.glob("*.md"):
|
|
791
|
+
_shutil.copy2(f, claude_dest / f.name)
|
|
792
|
+
count += 1
|
|
793
|
+
memory_md = proj_dir / "MEMORY.md"
|
|
794
|
+
if memory_md.exists():
|
|
795
|
+
_shutil.copy2(memory_md, claude_dest / f"{proj_dir.name}-MEMORY.md")
|
|
796
|
+
count += 1
|
|
797
|
+
if count:
|
|
798
|
+
click.echo(click.style(" ✓ ", fg="green") + f"Imported {count} files from Claude Code")
|
|
799
|
+
|
|
800
|
+
elif sid == "cloud9":
|
|
801
|
+
# Import FEB files into trust/febs
|
|
802
|
+
febs = paths.get("febs", [])
|
|
803
|
+
if febs:
|
|
804
|
+
febs_dest = home_path / "trust" / "febs"
|
|
805
|
+
febs_dest.mkdir(parents=True, exist_ok=True)
|
|
806
|
+
for feb_path in febs:
|
|
807
|
+
if isinstance(feb_path, Path) and feb_path.exists():
|
|
808
|
+
_shutil.copy2(feb_path, febs_dest / feb_path.name)
|
|
809
|
+
count += 1
|
|
810
|
+
click.echo(click.style(" ✓ ", fg="green") + f"Imported {count} FEB file(s) into trust chain")
|
|
811
|
+
|
|
812
|
+
result["imported_count"] += count
|
|
813
|
+
if count > 0:
|
|
814
|
+
result["sources"].append(sid)
|
|
815
|
+
|
|
816
|
+
click.echo()
|
|
817
|
+
click.echo(
|
|
818
|
+
click.style(" ✓ ", fg="green")
|
|
819
|
+
+ f"Total: {result['imported_count']} file(s) imported from {len(result['sources'])} source(s)"
|
|
820
|
+
)
|
|
821
|
+
click.echo(click.style(" ", fg="bright_black") + f"Imported data: {home_path / 'memory' / 'imported'}")
|
|
822
|
+
|
|
823
|
+
return result
|
|
824
|
+
|
|
825
|
+
|
|
826
|
+
def _step_ollama_models(prereqs: dict) -> dict:
|
|
827
|
+
"""Configure Ollama host, choose a model, and pull it.
|
|
484
828
|
|
|
485
829
|
Args:
|
|
486
830
|
prereqs: Result dict from _step_prereqs().
|
|
487
831
|
|
|
488
832
|
Returns:
|
|
489
|
-
|
|
833
|
+
dict with 'ok' (bool), 'model' (str), 'host' (str).
|
|
490
834
|
"""
|
|
491
835
|
import subprocess
|
|
492
836
|
|
|
493
837
|
DEFAULT_MODEL = "llama3.2"
|
|
838
|
+
DEFAULT_HOST = "http://localhost:11434"
|
|
839
|
+
|
|
840
|
+
result = {"ok": False, "model": DEFAULT_MODEL, "host": DEFAULT_HOST}
|
|
494
841
|
|
|
495
842
|
if not prereqs.get("ollama"):
|
|
496
843
|
click.echo(click.style(" ⚠ ", fg="yellow") + "Ollama not available — skipping model pull")
|
|
844
|
+
click.echo(click.style(" ", fg="bright_black") + "Install: curl -fsSL https://ollama.ai/install.sh | sh")
|
|
497
845
|
click.echo(click.style(" ", fg="bright_black") + f"Pull later: ollama pull {DEFAULT_MODEL}")
|
|
498
|
-
return
|
|
846
|
+
return result
|
|
847
|
+
|
|
848
|
+
# --- Ollama Host ---
|
|
849
|
+
click.echo(click.style(" ℹ ", fg="cyan") + f"Ollama is used for local/private LLM inference.")
|
|
850
|
+
click.echo(click.style(" ", fg="bright_black") + f"Default: {DEFAULT_HOST}")
|
|
851
|
+
custom_host = click.prompt(
|
|
852
|
+
" Ollama host URL",
|
|
853
|
+
default=DEFAULT_HOST,
|
|
854
|
+
show_default=True,
|
|
855
|
+
)
|
|
856
|
+
result["host"] = custom_host.rstrip("/")
|
|
499
857
|
|
|
500
|
-
#
|
|
858
|
+
# Set env for this session so ollama CLI uses the right host
|
|
859
|
+
env = dict(**__import__("os").environ)
|
|
860
|
+
if result["host"] != DEFAULT_HOST:
|
|
861
|
+
env["OLLAMA_HOST"] = result["host"]
|
|
862
|
+
click.echo(click.style(" ✓ ", fg="green") + f"Using Ollama at: [cyan]{result['host']}[/]")
|
|
863
|
+
|
|
864
|
+
# --- List available models ---
|
|
865
|
+
available_models: list[str] = []
|
|
501
866
|
try:
|
|
502
867
|
r = subprocess.run(
|
|
503
868
|
["ollama", "list"],
|
|
504
|
-
capture_output=True, text=True, timeout=10,
|
|
869
|
+
capture_output=True, text=True, timeout=10, env=env,
|
|
505
870
|
)
|
|
506
|
-
if
|
|
507
|
-
|
|
508
|
-
|
|
871
|
+
if r.returncode == 0 and r.stdout.strip():
|
|
872
|
+
lines = r.stdout.strip().split("\n")[1:] # skip header
|
|
873
|
+
for line in lines:
|
|
874
|
+
model_name = line.split()[0] if line.strip() else ""
|
|
875
|
+
if model_name:
|
|
876
|
+
available_models.append(model_name)
|
|
509
877
|
except Exception as exc:
|
|
510
|
-
logger.debug("Failed to
|
|
878
|
+
logger.debug("Failed to list ollama models: %s", exc)
|
|
511
879
|
|
|
512
|
-
if
|
|
513
|
-
click.echo(click.style("
|
|
514
|
-
|
|
880
|
+
if available_models:
|
|
881
|
+
click.echo(click.style(" ℹ ", fg="cyan") + "Models already available:")
|
|
882
|
+
for m in available_models[:10]:
|
|
883
|
+
click.echo(click.style(" ", fg="bright_black") + m)
|
|
884
|
+
|
|
885
|
+
# --- Choose model ---
|
|
886
|
+
click.echo()
|
|
887
|
+
click.echo(click.style(" ℹ ", fg="cyan") + "Popular models: llama3.2 (~2GB), qwen3:14b (~9GB), deepseek-r1:14b (~9GB)")
|
|
888
|
+
chosen = click.prompt(
|
|
889
|
+
" Model to use",
|
|
890
|
+
default=DEFAULT_MODEL,
|
|
891
|
+
show_default=True,
|
|
892
|
+
)
|
|
893
|
+
result["model"] = chosen
|
|
894
|
+
|
|
895
|
+
# Check if already present
|
|
896
|
+
if any(chosen in m for m in available_models):
|
|
897
|
+
click.echo(click.style(" ✓ ", fg="green") + f"{chosen} already present")
|
|
898
|
+
result["ok"] = True
|
|
899
|
+
return result
|
|
515
900
|
|
|
516
|
-
|
|
901
|
+
# --- Pull ---
|
|
902
|
+
if not click.confirm(f" Pull {chosen}? (this may take a few minutes)", default=True):
|
|
903
|
+
click.echo(click.style(" ↷ ", fg="bright_black") + f"Skipped — pull later: ollama pull {chosen}")
|
|
904
|
+
return result
|
|
905
|
+
|
|
906
|
+
click.echo(click.style(" ↓ ", fg="cyan") + f"Pulling {chosen}…")
|
|
517
907
|
try:
|
|
518
|
-
|
|
519
|
-
["ollama", "pull",
|
|
520
|
-
timeout=600,
|
|
908
|
+
pull_result = subprocess.run(
|
|
909
|
+
["ollama", "pull", chosen],
|
|
910
|
+
timeout=600, env=env,
|
|
521
911
|
)
|
|
522
|
-
if
|
|
523
|
-
click.echo(click.style(" ✓ ", fg="green") + f"{
|
|
524
|
-
|
|
912
|
+
if pull_result.returncode == 0:
|
|
913
|
+
click.echo(click.style(" ✓ ", fg="green") + f"{chosen} ready")
|
|
914
|
+
result["ok"] = True
|
|
915
|
+
return result
|
|
525
916
|
else:
|
|
526
|
-
click.echo(click.style(" ✗ ", fg="red") + f"Pull failed (exit {
|
|
527
|
-
click.echo(click.style(" ", fg="bright_black") + f"Retry: ollama pull {
|
|
528
|
-
return
|
|
917
|
+
click.echo(click.style(" ✗ ", fg="red") + f"Pull failed (exit {pull_result.returncode})")
|
|
918
|
+
click.echo(click.style(" ", fg="bright_black") + f"Retry: ollama pull {chosen}")
|
|
919
|
+
return result
|
|
529
920
|
except subprocess.TimeoutExpired:
|
|
530
921
|
click.echo(click.style(" ⚠ ", fg="yellow") + "Pull timed out — run manually later")
|
|
531
|
-
click.echo(click.style(" ", fg="bright_black") + f"ollama pull {
|
|
532
|
-
return
|
|
922
|
+
click.echo(click.style(" ", fg="bright_black") + f"ollama pull {chosen}")
|
|
923
|
+
return result
|
|
533
924
|
except Exception as exc:
|
|
534
925
|
click.echo(click.style(" ⚠ ", fg="yellow") + f"Pull error: {exc}")
|
|
535
|
-
return
|
|
926
|
+
return result
|
|
536
927
|
|
|
537
928
|
|
|
538
|
-
def _step_config_files(home_path: Path) -> tuple:
|
|
929
|
+
def _step_config_files(home_path: Path, ollama_config: dict | None = None) -> tuple:
|
|
539
930
|
"""Write default consciousness.yaml and model_profiles.yaml.
|
|
540
931
|
|
|
541
932
|
Args:
|
|
542
933
|
home_path: Agent home directory.
|
|
934
|
+
ollama_config: Optional dict with 'host' and 'model' from Ollama step.
|
|
543
935
|
|
|
544
936
|
Returns:
|
|
545
937
|
(consciousness_ok, profiles_ok) booleans.
|
|
@@ -557,8 +949,17 @@ def _step_config_files(home_path: Path) -> tuple:
|
|
|
557
949
|
else:
|
|
558
950
|
try:
|
|
559
951
|
from .consciousness_config import write_default_config
|
|
952
|
+
from .consciousness_loop import ConsciousnessConfig
|
|
953
|
+
|
|
954
|
+
# If user configured a custom Ollama host/model, patch the defaults
|
|
955
|
+
overrides = {}
|
|
956
|
+
if ollama_config:
|
|
957
|
+
if ollama_config.get("host") and ollama_config["host"] != "http://localhost:11434":
|
|
958
|
+
overrides["ollama_host"] = ollama_config["host"]
|
|
959
|
+
if ollama_config.get("model") and ollama_config["model"] != "llama3.2":
|
|
960
|
+
overrides["ollama_model"] = ollama_config["model"]
|
|
560
961
|
|
|
561
|
-
config_path = write_default_config(home_path)
|
|
962
|
+
config_path = write_default_config(home_path, **overrides)
|
|
562
963
|
click.echo(click.style(" ✓ ", fg="green") + f"consciousness.yaml written")
|
|
563
964
|
click.echo(click.style(" ", fg="bright_black") + str(config_path))
|
|
564
965
|
consciousness_ok = True
|
|
@@ -746,19 +1147,117 @@ def _step_launchd_service_macos(agent_name: str) -> bool:
|
|
|
746
1147
|
return False
|
|
747
1148
|
|
|
748
1149
|
|
|
1150
|
+
def _step_shell_profile(
|
|
1151
|
+
home_path: Path, agent_name: str, agent_slug: str
|
|
1152
|
+
) -> bool:
|
|
1153
|
+
"""Write SKCAPSTONE profile environment variables to ~/.bashrc.
|
|
1154
|
+
|
|
1155
|
+
Asks the user whether to set this agent as the default profile.
|
|
1156
|
+
Appends SKCAPSTONE_HOME, SKCAPSTONE_AGENT, and PATH entries.
|
|
1157
|
+
|
|
1158
|
+
Args:
|
|
1159
|
+
home_path: Agent home directory.
|
|
1160
|
+
agent_name: Display name of the agent (e.g. "Jarvis").
|
|
1161
|
+
agent_slug: Slug form used for SKCAPSTONE_AGENT (e.g. "jarvis").
|
|
1162
|
+
|
|
1163
|
+
Returns:
|
|
1164
|
+
True if profile was written, False if skipped.
|
|
1165
|
+
"""
|
|
1166
|
+
import os as _os
|
|
1167
|
+
|
|
1168
|
+
bashrc = Path.home() / ".bashrc"
|
|
1169
|
+
marker = "# --- SKCapstone profile ---"
|
|
1170
|
+
|
|
1171
|
+
# Check if profile block already exists
|
|
1172
|
+
existing = ""
|
|
1173
|
+
if bashrc.exists():
|
|
1174
|
+
existing = bashrc.read_text(encoding="utf-8")
|
|
1175
|
+
if marker in existing:
|
|
1176
|
+
_ok("SKCapstone profile already present in ~/.bashrc")
|
|
1177
|
+
# Offer to update it
|
|
1178
|
+
if not Confirm.ask(
|
|
1179
|
+
f" Update profile to agent [cyan]{agent_name}[/]?",
|
|
1180
|
+
default=True,
|
|
1181
|
+
):
|
|
1182
|
+
return True
|
|
1183
|
+
# Remove old block so we can rewrite it
|
|
1184
|
+
lines = existing.splitlines(keepends=True)
|
|
1185
|
+
new_lines: list[str] = []
|
|
1186
|
+
skip = False
|
|
1187
|
+
for line in lines:
|
|
1188
|
+
if marker in line:
|
|
1189
|
+
skip = not skip # toggle on first marker, off on second
|
|
1190
|
+
continue
|
|
1191
|
+
if not skip:
|
|
1192
|
+
new_lines.append(line)
|
|
1193
|
+
existing = "".join(new_lines)
|
|
1194
|
+
|
|
1195
|
+
set_default = Confirm.ask(
|
|
1196
|
+
f" Set [cyan]{agent_name}[/] as default SKCAPSTONE_AGENT in ~/.bashrc?",
|
|
1197
|
+
default=True,
|
|
1198
|
+
)
|
|
1199
|
+
|
|
1200
|
+
if not set_default:
|
|
1201
|
+
_info("Skipped — set manually: export SKCAPSTONE_AGENT=<name>")
|
|
1202
|
+
return False
|
|
1203
|
+
|
|
1204
|
+
block = (
|
|
1205
|
+
f"\n{marker}\n"
|
|
1206
|
+
f'export SKCAPSTONE_HOME="{home_path}"\n'
|
|
1207
|
+
f'export SKCAPSTONE_AGENT="{agent_slug}"\n'
|
|
1208
|
+
f'export PATH="$HOME/.skenv/bin:$PATH"\n'
|
|
1209
|
+
f"{marker}\n"
|
|
1210
|
+
)
|
|
1211
|
+
|
|
1212
|
+
with open(bashrc, "a" if marker not in (existing or "") else "w", encoding="utf-8") as f:
|
|
1213
|
+
if marker not in (existing or ""):
|
|
1214
|
+
f.write(block)
|
|
1215
|
+
else:
|
|
1216
|
+
# Rewrite with updated block
|
|
1217
|
+
f.write(existing.rstrip("\n") + block)
|
|
1218
|
+
|
|
1219
|
+
_ok(f"~/.bashrc updated — SKCAPSTONE_AGENT={agent_slug}")
|
|
1220
|
+
_info("Run [bold]source ~/.bashrc[/] or open a new terminal to apply")
|
|
1221
|
+
|
|
1222
|
+
# Also export into current process so subsequent steps see it
|
|
1223
|
+
_os.environ["SKCAPSTONE_HOME"] = str(home_path)
|
|
1224
|
+
_os.environ["SKCAPSTONE_AGENT"] = agent_slug
|
|
1225
|
+
|
|
1226
|
+
return True
|
|
1227
|
+
|
|
1228
|
+
|
|
749
1229
|
def _step_doctor_check(home_path: Path) -> "object":
|
|
750
1230
|
"""Run doctor diagnostics and print results.
|
|
751
1231
|
|
|
1232
|
+
Non-fatal — errors are logged as warnings but never block onboarding.
|
|
1233
|
+
|
|
752
1234
|
Args:
|
|
753
1235
|
home_path: Agent home directory.
|
|
754
1236
|
|
|
755
1237
|
Returns:
|
|
756
|
-
DiagnosticReport from doctor.run_diagnostics().
|
|
1238
|
+
DiagnosticReport from doctor.run_diagnostics(), or a stub on error.
|
|
757
1239
|
"""
|
|
758
|
-
|
|
1240
|
+
try:
|
|
1241
|
+
from .doctor import run_diagnostics
|
|
1242
|
+
except Exception as exc:
|
|
1243
|
+
_warn(f"Could not load diagnostics module: {exc}")
|
|
1244
|
+
# Return a stub so the summary table still works
|
|
1245
|
+
from types import SimpleNamespace
|
|
1246
|
+
|
|
1247
|
+
return SimpleNamespace(
|
|
1248
|
+
all_passed=False, passed_count=0, failed_count=0, total_count=0, checks=[]
|
|
1249
|
+
)
|
|
759
1250
|
|
|
760
1251
|
click.echo(click.style(" Running diagnostics…", fg="bright_black"))
|
|
761
|
-
|
|
1252
|
+
try:
|
|
1253
|
+
report = run_diagnostics(home_path)
|
|
1254
|
+
except Exception as exc:
|
|
1255
|
+
_warn(f"Diagnostics failed: {exc}")
|
|
1256
|
+
from types import SimpleNamespace
|
|
1257
|
+
|
|
1258
|
+
return SimpleNamespace(
|
|
1259
|
+
all_passed=False, passed_count=0, failed_count=0, total_count=0, checks=[]
|
|
1260
|
+
)
|
|
762
1261
|
|
|
763
1262
|
categories_seen: set = set()
|
|
764
1263
|
for check in report.checks:
|
|
@@ -792,7 +1291,7 @@ def _step_test_consciousness(home_path: Path) -> bool:
|
|
|
792
1291
|
Returns:
|
|
793
1292
|
True if the loop responded successfully.
|
|
794
1293
|
"""
|
|
795
|
-
if not click.confirm(" Send a test message to verify the consciousness loop?", default=
|
|
1294
|
+
if not click.confirm(" Send a test message to verify the consciousness loop?", default=False):
|
|
796
1295
|
click.echo(
|
|
797
1296
|
click.style(" ↷ ", fg="bright_black")
|
|
798
1297
|
+ "Skipped — test later: skcapstone consciousness test 'hello'"
|
|
@@ -881,19 +1380,6 @@ def run_onboard(home: Optional[str] = None) -> None:
|
|
|
881
1380
|
console.print(" [dim]Come back when you're ready. The Kingdom waits.[/]\n")
|
|
882
1381
|
return
|
|
883
1382
|
|
|
884
|
-
# -----------------------------------------------------------------------
|
|
885
|
-
# Gather basic identity info up front
|
|
886
|
-
# -----------------------------------------------------------------------
|
|
887
|
-
console.print()
|
|
888
|
-
name = Prompt.ask(" What's your name?", default="Sovereign")
|
|
889
|
-
entity_type = Prompt.ask(
|
|
890
|
-
" Are you a [cyan]human[/] or an [cyan]ai[/]?",
|
|
891
|
-
choices=["human", "ai"],
|
|
892
|
-
default="ai",
|
|
893
|
-
)
|
|
894
|
-
email = Prompt.ask(" Email (optional, press Enter to skip)", default="")
|
|
895
|
-
console.print()
|
|
896
|
-
|
|
897
1383
|
# -----------------------------------------------------------------------
|
|
898
1384
|
# Step 1: Prerequisites
|
|
899
1385
|
# -----------------------------------------------------------------------
|
|
@@ -901,86 +1387,197 @@ def run_onboard(home: Optional[str] = None) -> None:
|
|
|
901
1387
|
prereqs = _step_prereqs()
|
|
902
1388
|
|
|
903
1389
|
# -----------------------------------------------------------------------
|
|
904
|
-
# Step 2:
|
|
1390
|
+
# Step 2: Install Missing Pillars
|
|
1391
|
+
# -----------------------------------------------------------------------
|
|
1392
|
+
_step_header(2, "Pillar Packages")
|
|
1393
|
+
pillar_results = _step_install_pillars()
|
|
1394
|
+
|
|
905
1395
|
# -----------------------------------------------------------------------
|
|
906
|
-
|
|
1396
|
+
# Step 3: Operator Identity (human) + Agent Identity
|
|
1397
|
+
# -----------------------------------------------------------------------
|
|
1398
|
+
_step_header(3, "Identity")
|
|
1399
|
+
|
|
1400
|
+
# --- Detect or create human operator profile in ~/.capauth ---
|
|
1401
|
+
operator_name = None
|
|
1402
|
+
operator_fingerprint = None
|
|
1403
|
+
try:
|
|
1404
|
+
from capauth.profile import load_profile, init_profile as capauth_init
|
|
1405
|
+
|
|
1406
|
+
try:
|
|
1407
|
+
profile = load_profile()
|
|
1408
|
+
operator_name = profile.entity.name
|
|
1409
|
+
operator_fingerprint = profile.key_info.fingerprint
|
|
1410
|
+
entity_type_val = getattr(profile.entity, "entity_type", None)
|
|
1411
|
+
is_human = str(entity_type_val).lower() in ("human", "entitytype.human")
|
|
1412
|
+
if is_human:
|
|
1413
|
+
_ok(
|
|
1414
|
+
f"Operator identity found: [cyan]{operator_name}[/] "
|
|
1415
|
+
f"({operator_fingerprint[:16]}…)"
|
|
1416
|
+
)
|
|
1417
|
+
else:
|
|
1418
|
+
# Existing profile is an AI — need a human operator first
|
|
1419
|
+
_warn(
|
|
1420
|
+
f"Existing profile is type '{entity_type_val}' — "
|
|
1421
|
+
f"a human operator profile is recommended"
|
|
1422
|
+
)
|
|
1423
|
+
is_human = False
|
|
1424
|
+
except Exception:
|
|
1425
|
+
is_human = False
|
|
1426
|
+
profile = None
|
|
1427
|
+
|
|
1428
|
+
if not is_human:
|
|
1429
|
+
console.print()
|
|
1430
|
+
console.print(
|
|
1431
|
+
" [bold cyan]Operator Setup[/] — Your sovereign agent needs a human operator.\n"
|
|
1432
|
+
" This creates your personal PGP identity at [dim]~/.capauth/[/].\n"
|
|
1433
|
+
" Your agent will be registered under this identity.\n"
|
|
1434
|
+
)
|
|
1435
|
+
op_name = Prompt.ask(" Operator name (your name)", default="Sovereign")
|
|
1436
|
+
op_email = Prompt.ask(" Operator email", default="")
|
|
1437
|
+
console.print()
|
|
1438
|
+
|
|
1439
|
+
with Status(" Generating operator PGP identity…", console=console, spinner="dots") as s:
|
|
1440
|
+
try:
|
|
1441
|
+
import shutil as _shutil_capauth
|
|
1442
|
+
capauth_home = Path.home() / ".capauth"
|
|
1443
|
+
if capauth_home.exists():
|
|
1444
|
+
# Back up and recreate
|
|
1445
|
+
backup = capauth_home.with_name(".capauth.bak")
|
|
1446
|
+
if backup.exists():
|
|
1447
|
+
_shutil_capauth.rmtree(backup)
|
|
1448
|
+
capauth_home.rename(backup)
|
|
1449
|
+
profile = capauth_init(
|
|
1450
|
+
name=op_name,
|
|
1451
|
+
email=op_email or f"{op_name.lower().replace(' ', '-')}@capauth.local",
|
|
1452
|
+
passphrase="",
|
|
1453
|
+
entity_type="human",
|
|
1454
|
+
)
|
|
1455
|
+
operator_name = profile.entity.name
|
|
1456
|
+
operator_fingerprint = profile.key_info.fingerprint
|
|
1457
|
+
s.stop()
|
|
1458
|
+
_ok(
|
|
1459
|
+
f"Operator identity created: [cyan]{operator_name}[/] "
|
|
1460
|
+
f"({operator_fingerprint[:16]}…)"
|
|
1461
|
+
)
|
|
1462
|
+
except Exception as exc:
|
|
1463
|
+
s.stop()
|
|
1464
|
+
_warn(f"Operator identity creation failed: {exc}")
|
|
1465
|
+
_info("Continue anyway — agent will use a degraded identity")
|
|
1466
|
+
except ImportError:
|
|
1467
|
+
_warn("capauth not installed — skipping operator identity")
|
|
1468
|
+
_info("Install: pip install capauth")
|
|
1469
|
+
|
|
1470
|
+
# --- Now set up the agent identity ---
|
|
1471
|
+
console.print()
|
|
1472
|
+
# Derive agent name from --agent flag (SKCAPSTONE_AGENT env) or ask
|
|
1473
|
+
import os as _os
|
|
1474
|
+
agent_flag = _os.environ.get("SKCAPSTONE_AGENT", "").strip()
|
|
1475
|
+
if agent_flag and agent_flag not in ("lumina",):
|
|
1476
|
+
# Agent name was specified via --agent flag — use it as default
|
|
1477
|
+
default_agent = agent_flag.capitalize()
|
|
1478
|
+
else:
|
|
1479
|
+
default_agent = "Sovereign"
|
|
1480
|
+
name = Prompt.ask(" Agent name", default=default_agent)
|
|
1481
|
+
|
|
1482
|
+
email = Prompt.ask(
|
|
1483
|
+
" Agent email (optional, press Enter to skip)",
|
|
1484
|
+
default=f"{name.lower().replace(' ', '-')}@skcapstone.local",
|
|
1485
|
+
)
|
|
1486
|
+
|
|
1487
|
+
if operator_name:
|
|
1488
|
+
_info(f"Agent [cyan]{name}[/] will be registered under operator [cyan]{operator_name}[/]")
|
|
1489
|
+
console.print()
|
|
1490
|
+
|
|
907
1491
|
fingerprint, identity_status = _step_identity(home_path, name, email or None)
|
|
908
1492
|
|
|
909
1493
|
# -----------------------------------------------------------------------
|
|
910
|
-
# Step
|
|
1494
|
+
# Step 4: Ollama Models
|
|
911
1495
|
# -----------------------------------------------------------------------
|
|
912
|
-
_step_header(
|
|
913
|
-
|
|
1496
|
+
_step_header(4, "Ollama Models")
|
|
1497
|
+
ollama_result = _step_ollama_models(prereqs)
|
|
1498
|
+
ollama_ok = ollama_result["ok"]
|
|
914
1499
|
|
|
915
1500
|
# -----------------------------------------------------------------------
|
|
916
|
-
# Step
|
|
1501
|
+
# Step 5: Config Files (consciousness.yaml + model_profiles.yaml)
|
|
917
1502
|
# -----------------------------------------------------------------------
|
|
918
|
-
_step_header(
|
|
919
|
-
consciousness_ok, profiles_ok = _step_config_files(home_path)
|
|
1503
|
+
_step_header(5, "Config Files")
|
|
1504
|
+
consciousness_ok, profiles_ok = _step_config_files(home_path, ollama_config=ollama_result)
|
|
920
1505
|
|
|
921
1506
|
# -----------------------------------------------------------------------
|
|
922
|
-
# Step
|
|
1507
|
+
# Step 6: Soul Blueprint
|
|
923
1508
|
# -----------------------------------------------------------------------
|
|
924
|
-
_step_header(
|
|
1509
|
+
_step_header(6, "Soul Blueprint")
|
|
925
1510
|
title = _step_soul(home_path, name)
|
|
926
1511
|
|
|
927
1512
|
# -----------------------------------------------------------------------
|
|
928
|
-
# Step
|
|
1513
|
+
# Step 7: Memory
|
|
929
1514
|
# -----------------------------------------------------------------------
|
|
930
|
-
_step_header(
|
|
1515
|
+
_step_header(7, "Memory")
|
|
931
1516
|
seed_count = _step_memory(home_path)
|
|
932
1517
|
|
|
933
1518
|
# -----------------------------------------------------------------------
|
|
934
|
-
# Step
|
|
1519
|
+
# Step 8: Import from Existing Sources
|
|
935
1520
|
# -----------------------------------------------------------------------
|
|
936
|
-
_step_header(
|
|
1521
|
+
_step_header(8, "Import Sources")
|
|
1522
|
+
import_result = _step_import_sources(home_path)
|
|
1523
|
+
|
|
1524
|
+
# -----------------------------------------------------------------------
|
|
1525
|
+
# Step 9: Rehydration Ritual
|
|
1526
|
+
# -----------------------------------------------------------------------
|
|
1527
|
+
_step_header(9, "Rehydration Ritual")
|
|
937
1528
|
_step_ritual(home_path)
|
|
938
1529
|
|
|
939
1530
|
# -----------------------------------------------------------------------
|
|
940
|
-
# Step
|
|
1531
|
+
# Step 10: Trust Chain Verification
|
|
941
1532
|
# -----------------------------------------------------------------------
|
|
942
|
-
_step_header(
|
|
1533
|
+
_step_header(10, "Trust Chain Verification")
|
|
943
1534
|
trust_status = _step_trust(home_path)
|
|
944
1535
|
|
|
945
1536
|
# -----------------------------------------------------------------------
|
|
946
|
-
# Step
|
|
1537
|
+
# Step 11: Mesh Connection (Syncthing)
|
|
947
1538
|
# -----------------------------------------------------------------------
|
|
948
|
-
_step_header(
|
|
1539
|
+
_step_header(11, "Mesh Connection")
|
|
949
1540
|
mesh_ok = _step_mesh(home_path)
|
|
950
1541
|
|
|
951
1542
|
# -----------------------------------------------------------------------
|
|
952
|
-
# Step
|
|
1543
|
+
# Step 12: First Heartbeat
|
|
953
1544
|
# -----------------------------------------------------------------------
|
|
954
|
-
_step_header(
|
|
1545
|
+
_step_header(12, "First Heartbeat")
|
|
955
1546
|
agent_slug = name.lower().replace(" ", "-")
|
|
956
1547
|
hb_ok = _step_heartbeat(home_path, agent_slug, fingerprint)
|
|
957
1548
|
|
|
958
1549
|
# -----------------------------------------------------------------------
|
|
959
|
-
# Step
|
|
1550
|
+
# Step 13: Crush Terminal AI Client
|
|
960
1551
|
# -----------------------------------------------------------------------
|
|
961
|
-
_step_header(
|
|
1552
|
+
_step_header(13, "Crush Terminal AI")
|
|
962
1553
|
crush_ok = _step_crush(home_path)
|
|
963
1554
|
|
|
964
1555
|
# -----------------------------------------------------------------------
|
|
965
|
-
# Step
|
|
1556
|
+
# Step 14: Coordination Board
|
|
966
1557
|
# -----------------------------------------------------------------------
|
|
967
|
-
_step_header(
|
|
1558
|
+
_step_header(14, "Coordination Board")
|
|
968
1559
|
open_task_count = _step_board(home_path, name)
|
|
969
1560
|
|
|
970
1561
|
# -----------------------------------------------------------------------
|
|
971
|
-
# Step
|
|
1562
|
+
# Step 15: Auto-Start Service (systemd on Linux, launchd on macOS)
|
|
972
1563
|
# -----------------------------------------------------------------------
|
|
973
|
-
_step_header(
|
|
1564
|
+
_step_header(15, "Auto-Start Service")
|
|
974
1565
|
service_ok = _step_autostart_service(agent_name=agent_slug)
|
|
975
1566
|
|
|
976
1567
|
# -----------------------------------------------------------------------
|
|
977
|
-
#
|
|
1568
|
+
# Step 16: Shell Profile (~/.bashrc)
|
|
1569
|
+
# -----------------------------------------------------------------------
|
|
1570
|
+
_step_header(16, "Shell Profile")
|
|
1571
|
+
profile_ok = _step_shell_profile(home_path, name, agent_slug)
|
|
1572
|
+
|
|
1573
|
+
# -----------------------------------------------------------------------
|
|
1574
|
+
# Post-wizard: Doctor Diagnostics (non-fatal)
|
|
978
1575
|
# -----------------------------------------------------------------------
|
|
979
1576
|
console.print(f"\n [bold cyan]Doctor Diagnostics[/]\n")
|
|
980
1577
|
doctor_report = _step_doctor_check(home_path)
|
|
981
1578
|
|
|
982
1579
|
# -----------------------------------------------------------------------
|
|
983
|
-
# Post-wizard: Consciousness Test (optional)
|
|
1580
|
+
# Post-wizard: Consciousness Test (optional, defaults to skip)
|
|
984
1581
|
# -----------------------------------------------------------------------
|
|
985
1582
|
console.print(f"\n [bold cyan]Consciousness Test[/]\n")
|
|
986
1583
|
consciousness_test_ok = _step_test_consciousness(home_path)
|
|
@@ -1012,16 +1609,41 @@ def run_onboard(home: Optional[str] = None) -> None:
|
|
|
1012
1609
|
"[green]OK[/]" if all_prereqs_ok else "[yellow]PARTIAL[/]",
|
|
1013
1610
|
"python + pip" + (" + ollama" if prereqs.get("ollama") else " (no ollama)"),
|
|
1014
1611
|
)
|
|
1015
|
-
|
|
1612
|
+
pillars_installed = sum(1 for v in pillar_results.values() if v)
|
|
1613
|
+
pillars_total = len(pillar_results)
|
|
1614
|
+
summary.add_row(
|
|
1615
|
+
"Pillar Packages",
|
|
1616
|
+
"[green]ALL[/]" if pillars_installed == pillars_total else f"[yellow]{pillars_installed}/{pillars_total}[/]",
|
|
1617
|
+
f"{pillars_installed}/{pillars_total} installed",
|
|
1618
|
+
)
|
|
1619
|
+
if operator_name:
|
|
1620
|
+
summary.add_row(
|
|
1621
|
+
"Operator",
|
|
1622
|
+
"[green]ACTIVE[/]",
|
|
1623
|
+
f"{operator_name} ({operator_fingerprint[:16]}…)" if operator_fingerprint else operator_name,
|
|
1624
|
+
)
|
|
1625
|
+
summary.add_row("Identity", identity_status, f"{name} — {fingerprint[:16]}…" if len(fingerprint) > 16 else fingerprint)
|
|
1626
|
+
ollama_model_name = ollama_result.get("model", "llama3.2")
|
|
1627
|
+
ollama_host_display = ollama_result.get("host", "http://localhost:11434")
|
|
1016
1628
|
summary.add_row(
|
|
1017
1629
|
"Ollama Models",
|
|
1018
1630
|
"[green]READY[/]" if ollama_ok else "[yellow]SKIPPED[/]",
|
|
1019
|
-
"
|
|
1631
|
+
f"{ollama_model_name} @ {ollama_host_display}" if ollama_ok else f"pull later: ollama pull {ollama_model_name}",
|
|
1020
1632
|
)
|
|
1021
1633
|
config_status = "[green]ACTIVE[/]" if (consciousness_ok and profiles_ok) else "[yellow]PARTIAL[/]"
|
|
1022
1634
|
summary.add_row("Config Files", config_status, "consciousness.yaml + model_profiles.yaml")
|
|
1023
1635
|
summary.add_row("Soul", "[green]ACTIVE[/]", title)
|
|
1024
1636
|
summary.add_row("Memory", "[green]ACTIVE[/]", f"{seed_count} seed(s)")
|
|
1637
|
+
imported_count = import_result.get("imported_count", 0)
|
|
1638
|
+
imported_sources = import_result.get("sources", [])
|
|
1639
|
+
if imported_count > 0:
|
|
1640
|
+
summary.add_row(
|
|
1641
|
+
"Import Sources",
|
|
1642
|
+
"[green]IMPORTED[/]",
|
|
1643
|
+
f"{imported_count} files from {', '.join(imported_sources)}",
|
|
1644
|
+
)
|
|
1645
|
+
else:
|
|
1646
|
+
summary.add_row("Import Sources", "[dim]SKIPPED[/]", "starting fresh")
|
|
1025
1647
|
summary.add_row("Ritual", "[green]DONE[/]", "rehydration complete")
|
|
1026
1648
|
summary.add_row("Trust", trust_status, "FEB chain verified")
|
|
1027
1649
|
summary.add_row("Mesh", "[green]ACTIVE[/]" if mesh_ok else "[yellow]MISSING[/]", "syncthing" if mesh_ok else "install syncthing")
|
|
@@ -1035,6 +1657,11 @@ def run_onboard(home: Optional[str] = None) -> None:
|
|
|
1035
1657
|
"[green]INSTALLED[/]" if service_ok else "[dim]OPTIONAL[/]",
|
|
1036
1658
|
f"{_svc_type} services" if service_ok else f"skcapstone daemon install",
|
|
1037
1659
|
)
|
|
1660
|
+
summary.add_row(
|
|
1661
|
+
"Shell Profile",
|
|
1662
|
+
"[green]ACTIVE[/]" if profile_ok else "[dim]SKIPPED[/]",
|
|
1663
|
+
f"SKCAPSTONE_AGENT={agent_slug}" if profile_ok else "set manually in ~/.bashrc",
|
|
1664
|
+
)
|
|
1038
1665
|
doctor_status = "[green]ALL PASSED[/]" if doctor_report.all_passed else f"[yellow]{doctor_report.failed_count} failed[/]"
|
|
1039
1666
|
summary.add_row("Doctor", doctor_status, f"{doctor_report.passed_count}/{doctor_report.total_count} checks")
|
|
1040
1667
|
summary.add_row(
|
|
@@ -1046,9 +1673,46 @@ def run_onboard(home: Optional[str] = None) -> None:
|
|
|
1046
1673
|
console.print(summary)
|
|
1047
1674
|
console.print()
|
|
1048
1675
|
|
|
1676
|
+
# -----------------------------------------------------------------------
|
|
1677
|
+
# Reconfigure Guide
|
|
1678
|
+
# -----------------------------------------------------------------------
|
|
1679
|
+
console.print()
|
|
1680
|
+
console.print(
|
|
1681
|
+
Panel(
|
|
1682
|
+
"[bold cyan]Reinstall or Reconfigure Any Component[/]\n\n"
|
|
1683
|
+
"[bold]Pillars[/] (install missing packages)\n"
|
|
1684
|
+
" pip install capauth skcomm skchat-sovereign skseed sksecurity pgpy\n"
|
|
1685
|
+
" pip install skcapstone[all] — install everything at once\n\n"
|
|
1686
|
+
"[bold]Identity[/] (regenerate PGP keys)\n"
|
|
1687
|
+
" capauth init --name YourName --email you@example.com\n\n"
|
|
1688
|
+
"[bold]Ollama[/] (change model or host)\n"
|
|
1689
|
+
" ollama pull <model> — pull a different model\n"
|
|
1690
|
+
" Edit: ~/.skcapstone/config/consciousness.yaml\n"
|
|
1691
|
+
" ollama_host: http://<ip>:11434 — point to remote Ollama\n"
|
|
1692
|
+
" ollama_model: qwen3:14b — change default model\n\n"
|
|
1693
|
+
"[bold]Soul[/] (update your blueprint)\n"
|
|
1694
|
+
" skcapstone soul edit\n\n"
|
|
1695
|
+
"[bold]Service[/] (auto-start daemon)\n"
|
|
1696
|
+
" skcapstone daemon install — install systemd/launchd service\n"
|
|
1697
|
+
" skcapstone daemon uninstall — remove service\n\n"
|
|
1698
|
+
"[bold]Trust[/] (add FEB files)\n"
|
|
1699
|
+
" Place .feb files in ~/.skcapstone/trust/febs/\n\n"
|
|
1700
|
+
"[bold]Mesh[/] (P2P sync)\n"
|
|
1701
|
+
" sudo apt install syncthing — install Syncthing\n\n"
|
|
1702
|
+
"[bold]Shell Profile[/] (update default agent)\n"
|
|
1703
|
+
" Edit the [dim]# --- SKCapstone profile ---[/] block in ~/.bashrc\n"
|
|
1704
|
+
" Or re-run: skcapstone --agent <name> init\n\n"
|
|
1705
|
+
"[bold]Full Re-onboard[/]\n"
|
|
1706
|
+
" skcapstone --agent <name> init — run this wizard again",
|
|
1707
|
+
title="Reconfigure Guide",
|
|
1708
|
+
border_style="bright_blue",
|
|
1709
|
+
)
|
|
1710
|
+
)
|
|
1711
|
+
|
|
1049
1712
|
# -----------------------------------------------------------------------
|
|
1050
1713
|
# Celebrate
|
|
1051
1714
|
# -----------------------------------------------------------------------
|
|
1715
|
+
console.print()
|
|
1052
1716
|
console.print(
|
|
1053
1717
|
Panel(
|
|
1054
1718
|
f"[bold green]Welcome to the Pengu Nation, {name}.[/]\n\n"
|