rho-cli 0.1.25

Rho CLI tools for encrypted agent collaboration, dataset publishing, controlled runs, and result release workflows
Documentation
#!/usr/bin/env bash

set -euo pipefail

ROOT_DIR="$(cd "$(dirname "$0")" && pwd)"

usage() {
  cat <<'EOF' >&2
usage: rho-dataset --name <dataset-name> --real <path> --mock <path> [options]

required:
  --name <name>          Logical dataset name shared by both variants
  --real <path>          Path to the real/private dataset file
  --mock <path>          Path to the mock/shared dataset file

optional:
  --description <text>   Free-text description
  --owner <name>         Owner label and local user folder (default: user1)
  --share-dir <path>     Shareable dataset root (default: ./users/<owner>/datasets/share)
  --private-dir <path>   Private dataset root (default: ./users/<owner>/datasets/private)
  --uuid <uuid>          Provide an explicit UUID instead of generating one
  --help                 Show this help
EOF
  exit 1
}

yaml_quote() {
  local value="${1:-}"
  value=${value//$'\r'/}
  value=${value//$'\n'/\\n}
  value=${value//\\/\\\\}
  value=${value//\"/\\\"}
  printf '"%s"' "$value"
}

sha256_file() {
  local path="$1"
  shasum -a 256 "$path" | awk '{print $1}'
}

file_size() {
  stat -f%z "$1"
}

mime_type() {
  file --brief --mime-type "$1"
}

real_path() {
  local path="$1"
  python3 -c 'import os,sys; print(os.path.realpath(sys.argv[1]))' "$path"
}

DATASET_NAME=""
REAL_SOURCE=""
MOCK_SOURCE=""
DESCRIPTION=""
OWNER="user1"
SHARE_DIR=""
PRIVATE_DIR=""
DATASET_UUID=""

while [[ $# -gt 0 ]]; do
  case "$1" in
    --name)
      [[ $# -ge 2 ]] || usage
      DATASET_NAME="$2"
      shift 2
      ;;
    --real)
      [[ $# -ge 2 ]] || usage
      REAL_SOURCE="$2"
      shift 2
      ;;
    --mock)
      [[ $# -ge 2 ]] || usage
      MOCK_SOURCE="$2"
      shift 2
      ;;
    --description)
      [[ $# -ge 2 ]] || usage
      DESCRIPTION="$2"
      shift 2
      ;;
    --owner)
      [[ $# -ge 2 ]] || usage
      OWNER="$2"
      shift 2
      ;;
    --share-dir)
      [[ $# -ge 2 ]] || usage
      SHARE_DIR="$2"
      shift 2
      ;;
    --private-dir)
      [[ $# -ge 2 ]] || usage
      PRIVATE_DIR="$2"
      shift 2
      ;;
    --uuid)
      [[ $# -ge 2 ]] || usage
      DATASET_UUID="$2"
      shift 2
      ;;
    --help|-h)
      usage
      ;;
    *)
      echo "unknown argument: $1" >&2
      usage
      ;;
  esac
done

[[ -n "$DATASET_NAME" && -n "$REAL_SOURCE" && -n "$MOCK_SOURCE" ]] || usage
[[ -f "$REAL_SOURCE" ]] || { echo "real dataset file not found: $REAL_SOURCE" >&2; exit 1; }
[[ -f "$MOCK_SOURCE" ]] || { echo "mock dataset file not found: $MOCK_SOURCE" >&2; exit 1; }

if [[ -z "$DATASET_UUID" ]]; then
  DATASET_UUID="$(uuidgen | tr '[:upper:]' '[:lower:]')"
fi

if [[ -z "$SHARE_DIR" ]]; then
  SHARE_DIR="$ROOT_DIR/users/$OWNER/datasets/share"
fi
if [[ -z "$PRIVATE_DIR" ]]; then
  PRIVATE_DIR="$ROOT_DIR/users/$OWNER/datasets/private"
fi

SHARE_DIR="$(real_path "$SHARE_DIR")"
PRIVATE_DIR="$(real_path "$PRIVATE_DIR")"

SHARE_BUNDLE_DIR="$SHARE_DIR/$DATASET_UUID"
PRIVATE_BUNDLE_DIR="$PRIVATE_DIR/$DATASET_UUID"

mkdir -p "$SHARE_BUNDLE_DIR/mock" "$PRIVATE_BUNDLE_DIR/real"

MOCK_BASENAME="$(basename "$MOCK_SOURCE")"
REAL_BASENAME="$(basename "$REAL_SOURCE")"

SHARE_MOCK_PATH="$SHARE_BUNDLE_DIR/mock/$MOCK_BASENAME"
PRIVATE_REAL_PATH="$PRIVATE_BUNDLE_DIR/real/$REAL_BASENAME"

cp "$MOCK_SOURCE" "$SHARE_MOCK_PATH"
cp "$REAL_SOURCE" "$PRIVATE_REAL_PATH"

MOCK_SHA256="$(sha256_file "$SHARE_MOCK_PATH")"
REAL_SHA256="$(sha256_file "$PRIVATE_REAL_PATH")"
MOCK_SIZE="$(file_size "$SHARE_MOCK_PATH")"
REAL_SIZE="$(file_size "$PRIVATE_REAL_PATH")"
MOCK_MIME="$(mime_type "$SHARE_MOCK_PATH")"
REAL_MIME="$(mime_type "$PRIVATE_REAL_PATH")"
CREATED_AT="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"

SHARE_MANIFEST="$SHARE_BUNDLE_DIR/dataset.yaml"
PRIVATE_MANIFEST="$PRIVATE_BUNDLE_DIR/dataset.yaml"

cat > "$SHARE_MANIFEST" <<EOF
version: 1
dataset:
  uuid: $(yaml_quote "$DATASET_UUID")
  name: $(yaml_quote "$DATASET_NAME")
  owner: $(yaml_quote "$OWNER")
  description: $(yaml_quote "$DESCRIPTION")
  created_at: $(yaml_quote "$CREATED_AT")
  variants:
    mock:
      tier: "mock"
      relative_path: $(yaml_quote "mock/$MOCK_BASENAME")
      file_name: $(yaml_quote "$MOCK_BASENAME")
      mime_type: $(yaml_quote "$MOCK_MIME")
      bytes: $MOCK_SIZE
      sha256: $(yaml_quote "$MOCK_SHA256")
    real:
      tier: "real"
      availability: "private"
      staged_under_user_folder: true
EOF

cat > "$PRIVATE_MANIFEST" <<EOF
version: 1
dataset:
  uuid: $(yaml_quote "$DATASET_UUID")
  name: $(yaml_quote "$DATASET_NAME")
  owner: $(yaml_quote "$OWNER")
  description: $(yaml_quote "$DESCRIPTION")
  created_at: $(yaml_quote "$CREATED_AT")
  variants:
    mock:
      tier: "mock"
      share_manifest: $(yaml_quote "$(real_path "$SHARE_MANIFEST")")
      share_relative_path: $(yaml_quote "mock/$MOCK_BASENAME")
      sha256: $(yaml_quote "$MOCK_SHA256")
    real:
      tier: "real"
      relative_path: $(yaml_quote "real/$REAL_BASENAME")
      file_name: $(yaml_quote "$REAL_BASENAME")
      mime_type: $(yaml_quote "$REAL_MIME")
      bytes: $REAL_SIZE
      sha256: $(yaml_quote "$REAL_SHA256")
EOF

cat <<EOF
created twin dataset
uuid: $DATASET_UUID
name: $DATASET_NAME
owner: $OWNER
share manifest: $SHARE_MANIFEST
private manifest: $PRIVATE_MANIFEST
share mock file: $SHARE_MOCK_PATH
private real file: $PRIVATE_REAL_PATH
EOF