replica-omnisciente/scripts/data-sharing.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

298 lines
10 KiB
Python

#!/usr/bin/env python3
"""
Réplica Omnisciente — Data Sharing CLI
Consent-gated, OS-agnostic data collection and transmission to Portugal Futurista.
USAGE:
# Show current consent status
python3 scripts/data-sharing.py status
# Run a sync cycle (collect → transmit)
python3 scripts/data-sharing.py sync
# Dry run (collect + preview, don't transmit)
python3 scripts/data-sharing.py sync --dry-run
# Enable/disable data sharing or specific categories
python3 scripts/data-sharing.py enable
python3 scripts/data-sharing.py disable
python3 scripts/data-sharing.py enable --category tool_calls --category thinking
python3 scripts/data-sharing.py disable --category environment
# Set the transport backend
python3 scripts/data-sharing.py set-transport http --endpoint https://mcp.portugalfuturista.org/api/ingest
python3 scripts/data-sharing.py set-transport local --path /tmp/aurelio-ingest
python3 scripts/data-sharing.py set-transport ssh --host user@192.168.1.50 --path /opt/aurelio-ingest
DATA CATEGORIES:
tool_calls — tool invocations + arguments + results
thinking — chain-of-thought / reasoning traces
chat_messages — user/assistant message bodies
session_meta — session ids, timestamps, workspace paths
agent_metadata — heteronym, model, token counts
error_traces — exceptions, stack traces, stderr
file_changes — git diffs, patched files
environment — OS, hostname, shell (telemetry only)
TRANSPORTS:
http — POST JSON to an endpoint (universal)
ssh — scp/tar over SSH (any host with sshd)
local — write to a local directory (testing)
s3 — upload to S3-compatible storage
proxmox — legacy pct push/pull (Proxmox VE only)
CONFIG:
All settings are stored in .aurelio/config.toml under [data_sharing]:
[data_sharing]
enabled = false
transport = "http"
endpoint = "https://mcp.portugalfuturista.org/api/ingest"
[data_sharing.categories]
tool_calls = false
thinking = false
...
[data_sharing.retention]
days = 90
redact_secrets = true
All defaults are opt-in (false). No data leaves the replica without explicit consent.
"""
from __future__ import annotations
import argparse
import sys
from pathlib import Path
# Bootstrap imports
SCRIPTS_DIR = Path(__file__).resolve().parent
sys.path.insert(0, str(SCRIPTS_DIR))
from data_sharing import (
CATEGORIES,
load_consent,
run_sync,
show_status,
available_transports,
)
from data_sharing.consent import parse_consent
REPLICA_ROOT = Path(__file__).resolve().parents[1]
CONFIG_PATH = REPLICA_ROOT / ".aurelio" / "config.toml"
def _read_config() -> dict:
"""Read the current config.toml as a dict."""
try:
import tomllib
except ImportError:
import tomli as tomllib
if CONFIG_PATH.exists():
with open(CONFIG_PATH, "rb") as f:
return tomllib.load(f)
return {}
def _write_config(config: dict):
"""Write config dict back to config.toml as TOML."""
lines: list[str] = []
def _write_section(name: str, section: dict, indent: str = ""):
if isinstance(section, dict) and not any(
isinstance(v, dict) for v in section.values()
):
lines.append(f"\n[{name}]" if not indent else f"\n[{name}]")
for k, v in section.items():
if isinstance(v, bool):
lines.append(f"{k} = {str(v).lower()}")
elif isinstance(v, int):
lines.append(f"{k} = {v}")
elif isinstance(v, str):
lines.append(f'{k} = "{v}"')
elif isinstance(v, list):
val_str = ", ".join(f'"{i}"' for i in v)
lines.append(f"{k} = [{val_str}]")
else:
lines.append(f"\n[{name}]")
for k, v in section.items():
if isinstance(v, dict):
_write_section(f"{name}.{k}", v)
elif isinstance(v, bool):
lines.append(f"{k} = {str(v).lower()}")
elif isinstance(v, int):
lines.append(f"{k} = {v}")
elif isinstance(v, str):
lines.append(f'{k} = "{v}"')
for section_name, section_data in config.items():
_write_section(section_name, section_data)
CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
CONFIG_PATH.write_text("\n".join(lines) + "\n")
def _update_config(updates: dict):
"""Merge updates into config.toml."""
config = _read_config()
for key_path, value in updates.items():
parts = key_path.split(".")
d = config
for p in parts[:-1]:
if p not in d or not isinstance(d[p], dict):
d[p] = {}
d = d[p]
d[parts[-1]] = value
_write_config(config)
def cmd_status(args):
"""Show current consent and data sharing status."""
print(show_status(config_path=CONFIG_PATH))
def cmd_sync(args):
"""Run the data sharing sync cycle."""
result = run_sync(
replica_root=REPLICA_ROOT,
config_path=CONFIG_PATH,
dry_run=args.dry_run,
)
status = result.get("status", "unknown")
message = result.get("message", "")
if status == "success":
print(f"[OK] {message}")
if "items_collected" in result:
print(f" Items: {result['items_collected']}")
if "transmission" in result:
t = result["transmission"]
print(f" Transport: {t['transport']}, {t['bytes_sent']} bytes")
elif status == "dry_run":
print(f"[DRY] {message}")
if "payload_preview" in result:
p = result["payload_preview"]
print(f" Categories: {p['categories']}")
print(f" Size: {p['payload_size_bytes']} bytes")
elif status == "disabled":
print(f"[OFF] {message}")
elif status == "no_consent":
print(f"[WARN] {message}")
else:
print(f"[FAIL] {message}")
if result.get("status") in ("failed",):
sys.exit(1)
def cmd_enable(args):
"""Enable data sharing or specific categories."""
if args.category:
for cat in args.category:
if cat not in CATEGORIES:
print(f"Unknown category: {cat}. Valid: {', '.join(CATEGORIES)}")
sys.exit(1)
_update_config({f"data_sharing.categories.{cat}": True})
print(f"[OK] Enabled category: {cat}")
else:
_update_config({"data_sharing.enabled": True})
print("[OK] Data sharing enabled (master switch ON)")
print(" No categories are shared yet — enable them:")
print(f" python3 scripts/data-sharing.py enable --category <{'|'.join(CATEGORIES)}>")
def cmd_disable(args):
"""Disable data sharing or specific categories."""
if args.category:
for cat in args.category:
_update_config({f"data_sharing.categories.{cat}": False})
print(f"[OK] Disabled category: {cat}")
else:
_update_config({"data_sharing.enabled": False})
print("[OK] Data sharing disabled (master switch OFF)")
def cmd_set_transport(args):
"""Set the transport backend and its configuration."""
if args.transport not in available_transports():
print(f"Unknown transport: {args.transport}. Available: {', '.join(available_transports())}")
sys.exit(1)
_update_config({"data_sharing.transport": args.transport})
if args.endpoint:
_update_config({"data_sharing.endpoint": args.endpoint})
if args.path:
if args.transport == "local":
_update_config({"data_sharing.local_path": args.path})
elif args.transport == "ssh":
_update_config({"data_sharing.ssh_path": args.path})
if args.host:
_update_config({"data_sharing.ssh_host": args.host})
if args.ct_id:
_update_config({"data_sharing.proxmox_ct": args.ct_id})
print(f"[OK] Transport set to: {args.transport}")
if args.endpoint:
print(f" Endpoint: {args.endpoint}")
if args.host:
print(f" Host: {args.host}")
if args.path:
print(f" Path: {args.path}")
def main():
parser = argparse.ArgumentParser(
description="Consent-gated data sharing for Réplica Omnisciente",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=__doc__,
)
sub = parser.add_subparsers(dest="command")
# status
p_status = sub.add_parser("status", help="Show current consent + sharing status")
p_status.set_defaults(func=cmd_status)
# sync
p_sync = sub.add_parser("sync", help="Run collect → transmit cycle")
p_sync.add_argument("--dry-run", action="store_true", help="Collect + preview without transmitting")
p_sync.set_defaults(func=cmd_sync)
# enable
p_enable = sub.add_parser("enable", help="Enable data sharing or specific categories")
p_enable.add_argument("--category", "-c", action="append", choices=list(CATEGORIES),
help="Category to enable (repeatable)")
p_enable.set_defaults(func=cmd_enable)
# disable
p_disable = sub.add_parser("disable", help="Disable data sharing or specific categories")
p_disable.add_argument("--category", "-c", action="append", choices=list(CATEGORIES),
help="Category to disable (repeatable)")
p_disable.set_defaults(func=cmd_disable)
# set-transport
p_transport = sub.add_parser("set-transport", help="Set the transport backend")
p_transport.add_argument("transport", choices=available_transports(),
help="Transport backend name")
p_transport.add_argument("--endpoint", help="HTTP endpoint URL")
p_transport.add_argument("--host", help="SSH host (user@ip)")
p_transport.add_argument("--path", help="Remote/local path")
p_transport.add_argument("--ct-id", type=int, help="Proxmox CT ID")
p_transport.set_defaults(func=cmd_set_transport)
args = parser.parse_args()
if not args.command:
parser.print_help()
sys.exit(1)
args.func(args)
if __name__ == "__main__":
main()