- onboard-client.py: client replica scaffolding CLI - gws/: Google Workspace sync (Gmail, Calendar, Drive) - lifestream/: life event stream collector - muscriptor-mcp/: audio → MIDI MCP server - music-mcp/: music library MCP server - data_sharing/: consent-gated data sharing (Python + TS) - sync-mirrors.py: GitHub → Forgejo mirror engine - brain-to-gbrain.py, vault-sync.py, test-all.sh - shared/: TS data-sharing library + index - dirac: provider registry update - .gitignore: exclude Rust build artifacts Co-authored-by: Álvaro de Campos <campos@portugalfuturista.org>
123 lines
5.5 KiB
Python
123 lines
5.5 KiB
Python
"""
|
|
OnboardingConfig — all parameters needed to scaffold a new client.
|
|
|
|
Collected interactively (prompts) or from CLI flags / a JSON config file.
|
|
Every field has a sensible default so a client can be onboarded with just
|
|
a name and a git URL.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field, asdict
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
import json
|
|
import re
|
|
|
|
|
|
def _slugify(name: str) -> str:
|
|
"""Turn 'Acme Corp' into 'acme-corp'."""
|
|
slug = re.sub(r"[^a-z0-9]+", "-", name.lower().strip()).strip("-")
|
|
return slug or "client"
|
|
|
|
|
|
def _infer_git_provider(url: str) -> str:
|
|
"""Guess forge type from a git URL."""
|
|
u = url.lower()
|
|
if "github.com" in u:
|
|
return "github"
|
|
if "gitlab.com" in u:
|
|
return "gitlab"
|
|
if "codeberg.org" in u:
|
|
return "codeberg"
|
|
# Self-hosted Forgejo / Gitea — generic
|
|
return "forgejo"
|
|
|
|
|
|
@dataclass
|
|
class OnboardingConfig:
|
|
# ── Identity ────────────────────────────────────────────────
|
|
client_name: str = "" # "Acme Corp"
|
|
client_slug: str = "" # "acme-corp" (auto from name)
|
|
replica_name: str = "" # "Réplica Omnisciente — Acme" (auto)
|
|
description: str = "" # one-line description
|
|
|
|
# ── Git ──────────────────────────────────────────────────────
|
|
git_url: str = "" # git remote URL for the new repo
|
|
git_provider: str = "" # github | gitlab | forgejo | codeberg
|
|
forge_remote_name: str = "origin" # remote name to configure
|
|
|
|
# ── Infrastructure ──────────────────────────────────────────
|
|
sync_endpoint: str = "" # MCP sync endpoint (blank = local-only)
|
|
proxmox_host: str = "" # Proxmox host for brain sync (blank = none)
|
|
ct_id: int = 0 # CT ID for brain sync (0 = local-only)
|
|
|
|
# ── Team / Heteronyms ───────────────────────────────────────
|
|
lead_engineer_name: str = "" # principal engineer (first heteronym)
|
|
lead_engineer_email: str = "" # email
|
|
team_members: list[dict] = field(default_factory=list)
|
|
# Each: {"name": ..., "email": ..., "role": ...}
|
|
|
|
# ── Realms (initial) ────────────────────────────────────────
|
|
realms: list[dict] = field(default_factory=list)
|
|
# Each: {"slug": ..., "name": ..., "repo": ..., "stack": ..., "description": ...}
|
|
|
|
# ── Models ──────────────────────────────────────────────────
|
|
default_local_model: str = "qwen2.5-coder:14b"
|
|
default_cloud_model: str = "gemini-2.5-pro"
|
|
ollama_url: str = "http://127.0.0.1:11434"
|
|
|
|
# ── Power toggles (which capabilities to copy) ──────────────
|
|
include_brain: bool = True
|
|
include_skills: bool = True
|
|
include_providers: bool = True
|
|
include_connectors: bool = True
|
|
include_mcp_config: bool = True
|
|
include_identity: bool = True
|
|
include_scripts: bool = True
|
|
include_ci: bool = True
|
|
include_provisioner: bool = False # advanced: savearth-workspace specific
|
|
|
|
# ── Meta ────────────────────────────────────────────────────
|
|
output_dir: str = "" # where to create the project (default: ./<slug>)
|
|
dry_run: bool = False
|
|
source_replica: str = "" # path to canonical replica-omnisciente (auto-detected)
|
|
|
|
def __post_init__(self):
|
|
if not self.client_slug:
|
|
self.client_slug = _slugify(self.client_name)
|
|
if not self.replica_name:
|
|
self.replica_name = f"Réplica Omnisciente — {self.client_name}".strip("— ")
|
|
if not self.git_provider and self.git_url:
|
|
self.git_provider = _infer_git_provider(self.git_url)
|
|
if not self.source_replica:
|
|
# Auto-detect: this file lives in <replica>/scripts/onboarding/
|
|
self.source_replica = str(
|
|
Path(__file__).resolve().parents[2]
|
|
)
|
|
if not self.output_dir:
|
|
self.output_dir = str(Path.cwd() / self.client_slug)
|
|
|
|
def to_json(self) -> str:
|
|
return json.dumps(asdict(self), indent=2)
|
|
|
|
@classmethod
|
|
def from_json(cls, json_str: str) -> "OnboardingConfig":
|
|
data = json.loads(json_str)
|
|
# Remove computed fields that __post_init__ will re-derive
|
|
for key in ("client_slug", "replica_name", "git_provider", "source_replica"):
|
|
data.pop(key, None)
|
|
return cls(**data)
|
|
|
|
@classmethod
|
|
def from_json_file(cls, path: str) -> "OnboardingConfig":
|
|
return cls.from_json(Path(path).read_text())
|
|
|
|
def validate(self) -> list[str]:
|
|
"""Return a list of validation errors (empty = valid)."""
|
|
errors = []
|
|
if not self.client_name:
|
|
errors.append("client_name is required")
|
|
if not self.client_slug:
|
|
errors.append("client_slug could not be derived from client_name")
|
|
return errors
|