- 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>
283 lines
10 KiB
Python
283 lines
10 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Réplica Omnisciente — Client Onboarding Tool
|
|
|
|
Scaffolds a new client's complete replica-omnisciente: brain (.aurelio/),
|
|
realms/, scripts/, CI/CD, identity, providers, connectors — the full power set.
|
|
|
|
USAGE (interactive):
|
|
python3 scripts/onboard-client.py
|
|
|
|
USAGE (CLI flags):
|
|
python3 scripts/onboard-client.py \\
|
|
--client-name "Acme Corp" \\
|
|
--git-url https://github.com/acme/replica \\
|
|
--lead-engineer "Jane Doe" \\
|
|
--realm iot-backend --realm-name "IoT Backend" --realm-repo https://github.com/acme/iot
|
|
|
|
USAGE (JSON config):
|
|
python3 scripts/onboard-client.py --config onboarding.json
|
|
|
|
USAGE (dry run — preview without writing):
|
|
python3 scripts/onboard-client.py --client-name "Acme Corp" --dry-run
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import os
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
# Bootstrap: ensure we can import the onboarding package
|
|
SCRIPTS_DIR = Path(__file__).resolve().parent
|
|
sys.path.insert(0, str(SCRIPTS_DIR))
|
|
|
|
from onboarding import OnboardingConfig, scaffold_client
|
|
from onboarding.config import _slugify
|
|
|
|
|
|
def _prompt(label: str, default: str = "") -> str:
|
|
"""Prompt with default, return user input or default."""
|
|
suffix = f" [{default}]" if default else ""
|
|
val = input(f"{label}{suffix}: ").strip()
|
|
return val or default
|
|
|
|
|
|
def _prompt_bool(label: str, default: bool = True) -> bool:
|
|
d = "Y/n" if default else "y/N"
|
|
val = input(f"{label} [{d}]: ").strip().lower()
|
|
if not val:
|
|
return default
|
|
return val in ("y", "yes", "true", "1")
|
|
|
|
|
|
def _prompt_list(label: str, fields: list[str]) -> list[dict]:
|
|
"""Prompt for a list of items (e.g. team members, realms)."""
|
|
items = []
|
|
print(f"\n{label} (press Enter with empty name to finish):")
|
|
while True:
|
|
first_field = fields[0]
|
|
val = input(f" {first_field.capitalize()}: ").strip()
|
|
if not val:
|
|
break
|
|
item = {first_field: val}
|
|
for f in fields[1:]:
|
|
item[f] = input(f" {f.capitalize()}: ").strip()
|
|
items.append(item)
|
|
print()
|
|
return items
|
|
|
|
|
|
def interactive_wizard() -> OnboardingConfig:
|
|
"""Run the interactive onboarding wizard."""
|
|
print()
|
|
print("=" * 60)
|
|
print(" RÉPLICA OMNISCIENTE — CLIENT ONBOARDING")
|
|
print("=" * 60)
|
|
print()
|
|
|
|
# ── Identity ──
|
|
print("── Identity ──")
|
|
client_name = _prompt("Client name")
|
|
slug = _slugify(client_name)
|
|
client_slug = _prompt("Client slug", slug)
|
|
description = _prompt("Description (one line)")
|
|
|
|
# ── Git ──
|
|
print("\n── Git Repository ──")
|
|
git_url = _prompt("Git URL (leave empty for local-only)")
|
|
forge_remote = _prompt("Remote name", "origin")
|
|
|
|
# ── Infrastructure ──
|
|
print("\n── Infrastructure ──")
|
|
sync_endpoint = _prompt("Sync endpoint URL (leave empty for local-only)")
|
|
proxmox_host = _prompt("Proxmox host IP (leave empty if none)")
|
|
ct_id = 0
|
|
if proxmox_host:
|
|
ct_id = int(_prompt("Proxmox CT ID", "0") or "0")
|
|
|
|
# ── Team ──
|
|
print("\n── Lead Engineer ──")
|
|
lead_name = _prompt("Lead engineer name")
|
|
lead_email = _prompt("Lead engineer email")
|
|
|
|
print("\n── Team Members ──")
|
|
team = _prompt_list("Add team members", ["name", "email", "role"])
|
|
|
|
# ── Realms ──
|
|
print("\n── Initial Realms ──")
|
|
realms_raw = _prompt_list("Add realms", ["slug", "name", "repo", "stack", "description"])
|
|
realms = []
|
|
for r in realms_raw:
|
|
realms.append({
|
|
"slug": r.get("slug", ""),
|
|
"name": r.get("name", ""),
|
|
"repo": r.get("repo", ""),
|
|
"stack": r.get("stack", ""),
|
|
"description": r.get("description", ""),
|
|
})
|
|
|
|
# ── Models ──
|
|
print("\n── Models ──")
|
|
local_model = _prompt("Default local model", "qwen2.5-coder:14b")
|
|
cloud_model = _prompt("Default cloud model", "gemini-2.5-pro")
|
|
ollama_url = _prompt("Ollama URL", "http://127.0.0.1:11434")
|
|
|
|
# ── Powers ──
|
|
print("\n── Capabilities to include ──")
|
|
print("(These are the 'powers' of Réplica Omnisciente to copy)")
|
|
include_skills = _prompt_bool("Copy skills tree (CLI guides)?", True)
|
|
include_providers = _prompt_bool("Include provider registry?", True)
|
|
include_connectors = _prompt_bool("Include connector registry?", True)
|
|
include_scripts = _prompt_bool("Copy scripts (sync, importers)?", True)
|
|
include_ci = _prompt_bool("Generate CI/CD workflows?", True)
|
|
|
|
return OnboardingConfig(
|
|
client_name=client_name,
|
|
client_slug=client_slug,
|
|
description=description,
|
|
git_url=git_url,
|
|
forge_remote_name=forge_remote,
|
|
sync_endpoint=sync_endpoint,
|
|
proxmox_host=proxmox_host,
|
|
ct_id=ct_id,
|
|
lead_engineer_name=lead_name,
|
|
lead_engineer_email=lead_email,
|
|
team_members=team,
|
|
realms=realms,
|
|
default_local_model=local_model,
|
|
default_cloud_model=cloud_model,
|
|
ollama_url=ollama_url,
|
|
include_skills=include_skills,
|
|
include_providers=include_providers,
|
|
include_connectors=include_connectors,
|
|
include_scripts=include_scripts,
|
|
include_ci=include_ci,
|
|
)
|
|
|
|
|
|
def build_from_args(args: argparse.Namespace) -> OnboardingConfig:
|
|
"""Build config from CLI flags (non-interactive mode)."""
|
|
realms = []
|
|
if args.realm:
|
|
for i, slug in enumerate(args.realm):
|
|
realms.append({
|
|
"slug": slug,
|
|
"name": args.realm_name[i] if i < len(args.realm_name) else slug,
|
|
"repo": args.realm_repo[i] if i < len(args.realm_repo) else "",
|
|
"stack": args.realm_stack[i] if i < len(args.realm_stack) else "",
|
|
"description": args.realm_desc[i] if i < len(args.realm_desc) else "",
|
|
})
|
|
|
|
return OnboardingConfig(
|
|
client_name=args.client_name,
|
|
client_slug=args.client_slug or "",
|
|
description=args.description or "",
|
|
git_url=args.git_url or "",
|
|
forge_remote_name=args.remote_name or "origin",
|
|
sync_endpoint=args.sync_endpoint or "",
|
|
proxmox_host=args.proxmox_host or "",
|
|
ct_id=args.ct_id or 0,
|
|
lead_engineer_name=args.lead_engineer or "",
|
|
lead_engineer_email=args.lead_email or "",
|
|
realms=realms,
|
|
default_local_model=args.local_model or "qwen2.5-coder:14b",
|
|
default_cloud_model=args.cloud_model or "gemini-2.5-pro",
|
|
ollama_url=args.ollama_url or "http://127.0.0.1:11434",
|
|
include_skills=not args.no_skills,
|
|
include_providers=not args.no_providers,
|
|
include_connectors=not args.no_connectors,
|
|
include_scripts=not args.no_scripts,
|
|
include_ci=not args.no_ci,
|
|
output_dir=args.output or "",
|
|
dry_run=args.dry_run,
|
|
)
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description="Scaffold a new client's Réplica Omnisciente",
|
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
epilog=__doc__,
|
|
)
|
|
|
|
# ── Source ──
|
|
parser.add_argument("--config", help="JSON config file (skips all other flags)")
|
|
parser.add_argument("--interactive", "-i", action="store_true",
|
|
help="Force interactive wizard even with flags")
|
|
|
|
# ── Identity ──
|
|
parser.add_argument("--client-name", help="Client name")
|
|
parser.add_argument("--client-slug", help="Client slug (auto from name)")
|
|
parser.add_argument("--description", help="One-line description")
|
|
|
|
# ── Git ──
|
|
parser.add_argument("--git-url", help="Git remote URL")
|
|
parser.add_argument("--remote-name", default="origin", help="Git remote name")
|
|
parser.add_argument("--output", "-o", help="Output directory (default: ./<slug>)")
|
|
|
|
# ── Infrastructure ──
|
|
parser.add_argument("--sync-endpoint", help="MCP sync endpoint URL")
|
|
parser.add_argument("--proxmox-host", help="Proxmox host for brain sync")
|
|
parser.add_argument("--ct-id", type=int, help="Proxmox CT ID")
|
|
|
|
# ── Team ──
|
|
parser.add_argument("--lead-engineer", help="Lead engineer name")
|
|
parser.add_argument("--lead-email", help="Lead engineer email")
|
|
|
|
# ── Realms ──
|
|
parser.add_argument("--realm", action="append", default=[], help="Realm slug (repeatable)")
|
|
parser.add_argument("--realm-name", action="append", default=[])
|
|
parser.add_argument("--realm-repo", action="append", default=[])
|
|
parser.add_argument("--realm-stack", action="append", default=[])
|
|
parser.add_argument("--realm-desc", action="append", default=[])
|
|
|
|
# ── Models ──
|
|
parser.add_argument("--local-model", help="Default local model")
|
|
parser.add_argument("--cloud-model", help="Default cloud model")
|
|
parser.add_argument("--ollama-url", help="Ollama URL")
|
|
|
|
# ── Power toggles ──
|
|
parser.add_argument("--no-skills", action="store_true", help="Skip skills tree")
|
|
parser.add_argument("--no-providers", action="store_true", help="Skip provider registry")
|
|
parser.add_argument("--no-connectors", action="store_true", help="Skip connector registry")
|
|
parser.add_argument("--no-scripts", action="store_true", help="Skip scripts")
|
|
parser.add_argument("--no-ci", action="store_true", help="Skip CI/CD")
|
|
|
|
# ── Meta ──
|
|
parser.add_argument("--dry-run", action="store_true", help="Preview without writing")
|
|
parser.add_argument("--print-json", action="store_true",
|
|
help="Print resolved config as JSON and exit")
|
|
|
|
args = parser.parse_args()
|
|
|
|
# ── Resolve config ──
|
|
if args.config:
|
|
cfg = OnboardingConfig.from_json_file(args.config)
|
|
if args.dry_run:
|
|
cfg.dry_run = True
|
|
if args.output:
|
|
cfg.output_dir = args.output
|
|
elif args.interactive or not args.client_name:
|
|
cfg = interactive_wizard()
|
|
if args.dry_run:
|
|
cfg.dry_run = True
|
|
else:
|
|
cfg = build_from_args(args)
|
|
|
|
if args.print_json:
|
|
print(cfg.to_json())
|
|
return
|
|
|
|
# ── Execute ──
|
|
try:
|
|
scaffold_client(cfg)
|
|
except Exception as e:
|
|
print(f"\nERROR: {e}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|