replica-omnisciente/scripts/onboarding/scaffolder.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

424 lines
17 KiB
Python

"""
Scaffolder — creates the full directory tree and files for a new client.
This is the engine that turns an OnboardingConfig into a living replica-omnisciente
skeleton on disk.
"""
from __future__ import annotations
import os
import shutil
import stat
from pathlib import Path
from .config import OnboardingConfig
from . import templates
class ScaffoldError(Exception):
pass
def _write(path: Path, content: str, cfg: OnboardingConfig, executable: bool = False) -> bool:
"""Write a file unless dry_run. Returns True if written."""
if cfg.dry_run:
print(f" [DRY] {path}")
return False
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(content)
if executable:
path.chmod(path.stat().st_mode | stat.S_IEXEC | stat.S_IXGRP | stat.S_IXOTH)
print(f" [OK] {path}")
return True
def _copy_dir(src: Path, dst: Path, cfg: OnboardingConfig, label: str = "") -> bool:
"""Copy a directory tree unless dry_run."""
if cfg.dry_run:
print(f" [DRY] {dst}/ ({label})")
return False
if not src.exists():
print(f" [SKIP] {dst}/ (source '{src}' does not exist)")
return False
shutil.copytree(src, dst, dirs_exist_ok=True)
print(f" [OK] {dst}/ ({label})")
return True
def _copy_file(src: Path, dst: Path, cfg: OnboardingConfig, executable: bool = False) -> bool:
if cfg.dry_run:
print(f" [DRY] {dst}")
return False
if not src.exists():
print(f" [SKIP] {dst} (source '{src}' does not exist)")
return False
shutil.copy2(src, dst)
if executable:
dst.chmod(dst.stat().st_mode | stat.S_IEXEC | stat.S_IXGRP | stat.S_IXOTH)
print(f" [OK] {dst}")
return True
def scaffold_client(cfg: OnboardingConfig) -> Path:
"""
Scaffold a complete client replica-omnisciente.
Args:
cfg: Fully populated OnboardingConfig.
Returns:
Path to the created project root.
Raises:
ScaffoldError: If validation fails or output dir already exists (non-dry-run).
"""
errors = cfg.validate()
if errors:
raise ScaffoldError("Validation failed:\n" + "\n".join(f" - {e}" for e in errors))
root = Path(cfg.output_dir)
src = Path(cfg.source_replica)
mode = "[DRY RUN] " if cfg.dry_run else ""
print(f"\n{'='*60}")
print(f"{mode}Scaffolding: {cfg.replica_name}")
print(f"{'='*60}")
print(f" Client: {cfg.client_name} ({cfg.client_slug})")
print(f" Output: {root}")
print(f" Source: {src}")
print(f" Git: {cfg.git_url or '(not set)'}")
print(f" Endpoint: {cfg.sync_endpoint or '(local-only)'}")
print(f" Realms: {len(cfg.realms)}")
print(f" Team: {1 + len(cfg.team_members)} heteronym(s)")
print()
if not cfg.dry_run:
if root.exists() and any(root.iterdir()):
raise ScaffoldError(f"Output directory already exists and is not empty: {root}")
root.mkdir(parents=True, exist_ok=True)
# ── Root files ──────────────────────────────────────────────
print("─ Root files ─")
_write(root / "AGENTS.md", templates.agents_md(cfg), cfg)
_write(root / "README.md", templates.readme_md(cfg), cfg)
_write(root / ".gitignore", templates.gitignore(), cfg)
_write(root / ".env.example", templates.env_example(cfg), cfg)
# ── .aurelio/ ───────────────────────────────────────────────
aurelio = root / ".aurelio"
print("\n─ .aurelio/ ─")
_write(aurelio / "config.toml", templates.aurelio_config_toml(cfg), cfg)
_write(aurelio / "mcp_config.json", templates.mcp_config_json(cfg), cfg)
# Brain (empty structure)
_write(aurelio / "brain" / ".gitkeep", "", cfg)
_write(aurelio / "memory" / "index.md",
f"# {cfg.replica_name} — Global Memory\n", cfg)
_write(aurelio / "plans" / ".gitkeep", "", cfg)
_write(aurelio / "sessions" / ".gitkeep", "", cfg)
# Sync script
if cfg.sync_endpoint or cfg.proxmox_host:
_write(aurelio / "sync.py", templates.sync_py(cfg), cfg, executable=True)
else:
_write(aurelio / "sync.py", templates.sync_py(cfg), cfg, executable=True)
# ── .aurelio/identity/ ──────────────────────────────────────
if cfg.include_identity:
print("\n─ .aurelio/identity/ ─")
ident = aurelio / "identity"
_write(ident / "heteronyms.json", templates.heteronyms_json(cfg), cfg)
_write(ident / "README.md", templates.identity_readme(cfg), cfg)
# Heteronym .md stubs
heteronimos = aurelio / "heteronimos"
if cfg.lead_engineer_name:
slug = cfg.lead_engineer_name.lower().replace(" ", "-")
_write(heteronimos / f"{slug}.md",
f"---\nslug: {slug}\nname: {cfg.lead_engineer_name}\nroleDefinition: Lead engineer for {cfg.client_name}.\n---\n## {cfg.lead_engineer_name}\n\n{cfg.lead_engineer_name} is the principal engineer for {cfg.client_name}.\n",
cfg)
for member in cfg.team_members:
slug = member["name"].lower().replace(" ", "-")
_write(heteronimos / f"{slug}.md",
f"---\nslug: {slug}\nname: {member['name']}\nroleDefinition: {member.get('role', 'Engineer')} for {cfg.client_name}.\n---\n## {member['name']}\n\n{member['name']} is a {member.get('role', 'engineer')} for {cfg.client_name}.\n",
cfg)
# ── .aurelio/providers/ ─────────────────────────────────────
if cfg.include_providers:
print("\n─ .aurelio/providers/ ─")
prov = aurelio / "providers"
_write(prov / "registry.yaml", templates.providers_registry_yaml(cfg), cfg)
_write(prov / "dist" / ".gitkeep", "", cfg)
# ── .aurelio/connectors/ ────────────────────────────────────
if cfg.include_connectors:
print("\n─ .aurelio/connectors/ ─")
conn = aurelio / "connectors"
_write(conn / "registry.yaml", templates.connectors_registry_yaml(cfg), cfg)
_write(conn / "dist" / ".gitkeep", "", cfg)
# ── .aurelio/skills/ ────────────────────────────────────────
if cfg.include_skills:
print("\n─ .aurelio/skills/ ─")
skills_dst = aurelio / "skills"
skills_src = src / ".aurelio" / "skills"
if skills_src.exists():
# Copy the full skills tree (these are portable CLI guides)
_copy_dir(skills_src, skills_dst, cfg, label=f"skills tree")
else:
_write(skills_dst / ".gitkeep", "", cfg)
# ── .aurelio/knowledge/ + chronicle/ ────────────────────────
_write(aurelio / "knowledge" / ".gitkeep", "", cfg)
chronicle = aurelio / "chronicle"
_write(chronicle / "README.md",
f"# Chronicle: {cfg.replica_name}\n\nTimeline of events across all realms.\n", cfg)
_write(chronicle / "timeline.yaml", "version: 1\nevents: []\n", cfg)
_write(chronicle / "realms" / ".gitkeep", "", cfg)
# ── .aurelio/swarm/ ─────────────────────────────────────────
_write(aurelio / "swarm" / "README.md",
f"# Swarm\n\nAgent fleet for {cfg.replica_name}.\n", cfg)
# ── realms/ ─────────────────────────────────────────────────
if cfg.realms:
print("\n─ realms/ ─")
for realm in cfg.realms:
rdir = root / "realms" / realm["slug"]
_write(rdir / "AGENTS.md", templates.realm_agents_md(cfg, realm), cfg)
ra = rdir / ".aurelio"
_write(ra / "config.toml", templates.realm_config_toml(cfg, realm), cfg)
_write(ra / "memory" / "index.md", templates.realm_memory_index(cfg, realm), cfg)
_write(ra / "plans" / ".gitkeep", "", cfg)
# ── scripts/ ────────────────────────────────────────────────
if cfg.include_scripts:
print("\n─ scripts/ ─")
scripts_dst = root / "scripts"
scripts_src = src / "scripts"
# Copy the agent-importer engine (portable, no project-specific deps)
_write(scripts_dst / "onboard-client.py", _onboard_cli_stub(cfg), cfg, executable=True)
# Copy sync-agents-to-brain.py if it exists
sync_agents = scripts_src / "sync-agents-to-brain.py"
if sync_agents.exists():
_copy_file(sync_agents, scripts_dst / "sync-agents-to-brain.py", cfg, executable=True)
# Copy agent_importers package
importers_src = scripts_src / "agent_importers"
if importers_src.exists():
_copy_dir(importers_src, scripts_dst / "agent_importers", cfg, label="agent importers")
# Copy onboarding package itself (self-replicating)
onboarding_src = scripts_src / "onboarding"
if onboarding_src.exists():
_copy_dir(onboarding_src, scripts_dst / "onboarding", cfg, label="onboarding package")
# Copy data_sharing package (consent-gated data transmission)
ds_src = scripts_src / "data_sharing"
if ds_src.exists():
_copy_dir(ds_src, scripts_dst / "data_sharing", cfg, label="data sharing layer")
ds_cli = scripts_src / "data-sharing.py"
if ds_cli.exists():
_copy_file(ds_cli, scripts_dst / "data-sharing.py", cfg, executable=True)
# Copy provider/connector mirror generators if they exist
for gen in ["generate-provider-mirrors.py", "generate-connector-mirrors.py"]:
gen_src = scripts_src / gen
if gen_src.exists():
_copy_file(gen_src, scripts_dst / gen, cfg, executable=True)
# ── CI/CD ───────────────────────────────────────────────────
if cfg.include_ci:
print("\n─ CI/CD ─")
if cfg.git_provider in ("forgejo", "codeberg"):
_write(root / ".forgejo" / "workflows" / "aurelio-sync.yml",
templates.forgejo_workflow(cfg), cfg)
if cfg.git_provider in ("github", "gitlab"):
_write(root / ".github" / "workflows" / "build.yml",
templates.github_workflow(cfg), cfg)
# Always include both so the client can switch forges later
if cfg.git_provider not in ("forgejo", "codeberg"):
_write(root / ".forgejo" / "workflows" / "aurelio-sync.yml",
templates.forgejo_workflow(cfg), cfg)
if cfg.git_provider not in ("github", "gitlab"):
_write(root / ".github" / "workflows" / "build.yml",
templates.github_workflow(cfg), cfg)
# ── Git init ────────────────────────────────────────────────
if not cfg.dry_run:
print("\n─ Git init ─")
_git_init(root, cfg)
print(f"\n{'='*60}")
mode = "[DRY RUN] " if cfg.dry_run else ""
print(f"{mode}Done: {cfg.replica_name}")
print(f"{'='*60}")
if not cfg.dry_run:
print(f"\nNext steps:")
print(f" cd {root}")
print(f" cp .env.example .env # fill in API keys")
if cfg.git_url:
print(f" git remote add {cfg.forge_remote_name} {cfg.git_url}")
print(f" git push -u {cfg.forge_remote_name} main")
print(f" # Read AGENTS.md for the full guide")
print()
return root
def _git_init(root: Path, cfg: OnboardingConfig):
"""Initialize git and make initial commit."""
import subprocess
try:
# Initialize repo
subprocess.run(["git", "init"], cwd=root, check=True, capture_output=True)
# Set per-repo identity if none is configured globally
has_identity = subprocess.run(
["git", "config", "user.email"],
cwd=root, capture_output=True, text=True
).returncode == 0
if not has_identity:
subprocess.run(["git", "config", "user.email",
cfg.lead_engineer_email or f"noreply@{cfg.client_slug}.com"],
cwd=root, check=True, capture_output=True)
subprocess.run(["git", "config", "user.name",
cfg.lead_engineer_name or cfg.client_name],
cwd=root, check=True, capture_output=True)
subprocess.run(["git", "add", "-A"], cwd=root, check=True, capture_output=True)
subprocess.run(
["git", "commit", "-m",
f"feat: initial scaffold of {cfg.replica_name}\n\n"
f"Scaffolded from Réplica Omnisciente template.\n"
f"Client: {cfg.client_name}"],
cwd=root, check=True, capture_output=True
)
if cfg.git_url:
subprocess.run(
["git", "remote", "add", cfg.forge_remote_name, cfg.git_url],
cwd=root, check=True, capture_output=True
)
print(f" [OK] git initialized + initial commit")
except FileNotFoundError:
print(f" [WARN] git not found — skipping git init")
except subprocess.CalledProcessError as e:
print(f" [WARN] git init failed: {e.stderr.decode() if e.stderr else e}")
def _onboard_cli_stub(cfg: OnboardingConfig) -> str:
"""Generate a self-contained CLI script for the scaffolded client."""
return f'''#!/usr/bin/env python3
"""
{cfg.replica_name} — Onboarding & Realm Management CLI
Usage:
python3 scripts/onboard-client.py --add-realm <slug> --name "Name" --repo <url>
python3 scripts/onboard-client.py --list-realms
python3 scripts/onboard-client.py --info
"""
import argparse
import json
import sys
from pathlib import Path
# Ensure we can import the onboarding package
SCRIPTS_DIR = Path(__file__).parent
sys.path.insert(0, str(SCRIPTS_DIR))
from onboarding import OnboardingConfig, scaffold_client
from onboarding.templates import realm_agents_md, realm_config_toml, realm_memory_index
ROOT = Path(__file__).resolve().parents[1]
def add_realm(args):
"""Add a new realm to this replica."""
realm = {{
"slug": args.slug,
"name": args.name or args.slug,
"repo": args.repo or "",
"stack": args.stack or "",
"description": args.description or "",
}}
rdir = ROOT / "realms" / realm["slug"]
if rdir.exists() and not args.force:
print(f"Realm '{{realm['slug']}}' already exists. Use --force to overwrite.")
sys.exit(1)
# Load current config for template rendering
cfg = OnboardingConfig(client_name="{cfg.client_name}")
cfg.source_replica = str(ROOT)
rdir.mkdir(parents=True, exist_ok=True)
(rdir / "AGENTS.md").write_text(realm_agents_md(cfg, realm))
ra = rdir / ".aurelio"
ra.mkdir(parents=True, exist_ok=True)
(ra / "config.toml").write_text(realm_config_toml(cfg, realm))
(ra / "memory").mkdir(exist_ok=True)
(ra / "memory" / "index.md").write_text(realm_memory_index(cfg, realm))
(ra / "plans").mkdir(exist_ok=True)
print(f"Realm '{{realm['slug']}}' created at {{rdir}}")
print(f" AGENTS.md, .aurelio/config.toml, .aurelio/memory/index.md")
def list_realms(args):
"""List all realms."""
realms_dir = ROOT / "realms"
if not realms_dir.exists():
print("No realms directory.")
return
print("Realms:")
for d in sorted(realms_dir.iterdir()):
if d.is_dir():
agents_file = d / "AGENTS.md"
name = d.name
if agents_file.exists():
first_line = agents_file.read_text().splitlines()[0] if agents_file.read_text() else ""
name = first_line.replace("#", "").strip() or d.name
print(f" - {{d.name}}: {{name}}")
def info(args):
"""Show replica info."""
cfg = OnboardingConfig(client_name="{cfg.client_name}")
print(f"Replica: {{cfg.replica_name}}")
print(f"Client: {{cfg.client_name}} ({{cfg.client_slug}})")
print(f"Root: {{ROOT}}")
def main():
parser = argparse.ArgumentParser(description="{cfg.replica_name} — Onboarding CLI")
sub = parser.add_subparsers(dest="command")
p_add = sub.add_parser("add-realm", help="Add a new realm")
p_add.add_argument("slug", help="Realm slug (e.g. 'my-project')")
p_add.add_argument("--name", help="Human-readable name")
p_add.add_argument("--repo", help="Git repository URL")
p_add.add_argument("--stack", help="Technology stack summary")
p_add.add_argument("--description", help="One-line description")
p_add.add_argument("--force", action="store_true", help="Overwrite if exists")
p_add.set_defaults(func=add_realm)
p_list = sub.add_parser("list-realms", help="List all realms")
p_list.set_defaults(func=list_realms)
p_info = sub.add_parser("info", help="Show replica info")
p_info.set_defaults(func=info)
args = parser.parse_args()
if not args.command:
parser.print_help()
sys.exit(1)
args.func(args)
if __name__ == "__main__":
main()
'''