replica-omnisciente/scripts/onboarding/config.py
Raphael Cautus (Maestro) 2f26f2d836 feat(scripts): onboarding, GWS, lifestream, muscriptor, music, data-sharing
- 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>
2026-07-31 14:58:02 +01:00

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