Skill: moodykong/onedrive-integration (version 0.1.0)

--- name: onedrive-integration description: Copy large/long files to OneDrive for sharing when the user is on Telegram or WhatsApp and wants to view a full document or long file. Use to place files into OneDrive under an OpenClaw folder and provide the new filename/location. --- # onedrive-integration ## Goal When the user needs a *full* document / long file in Telegram/WhatsApp (where pasting is awkward), copy the file(s) into OneDrive so the user can open/share from there. ## Prerequisites ```bash command -v python3 python3 --version ``` ## Configuration (portable) Machine-specific config lives alongside the skill: - Example (do not edit): `~/.openclaw/skills/onedrive-integration/config.env.example` - Real (machine-specific): `~/.openclaw/skills/onedrive-integration/config.env` Keys: - `ONEDRIVE_ROOT` (required) — e.g. `/mnt/c/Users/<windows_user>/OneDrive` - `ONEDRIVE_SUBDIR` (optional) — default `openclaw` If config is missing/unset, do **not** guess—ask Boss to confirm the correct OneDrive folder, then write `config.env`. ## Initialization / installation / onboarding ### Preferred (chat-first) Because the primary interface is chat (Telegram), the preferred onboarding flow is: 1. Ask Boss to confirm the correct OneDrive root folder. 2. Write the real config file: `config.env`. 3. Run a smoke test by copying a small temp file. ### Optional (terminal) If you are running in a real terminal, you can use the interactive onboarding script: ```bash ~/.openclaw/skills/onedrive-integration/scripts/onboard.sh ``` ## How it works Copy requested file(s) into: - `${ONEDRIVE_ROOT}/${ONEDRIVE_SUBDIR}/` (defaults to `openclaw/`) During copy, rename files to include their source path to avoid collisions. ### Rename rules - Convert absolute path into a safe filename: - strip leading `/` - replace path separators (`/` and `\\`) with `-` - lowercase - replace any non `[a-z0-9._-]` with `-` - collapse multiple `-` Examples: - `/home/miles/folder1/file1.md` → `wsl-home-miles-folder1-file1.md` - `/mnt/c/Users/<user>/folder1/file1.md` → `c-users-<user>-folder1-file1.md` ### Implementation Canonical executable lives inside the skill folder: - `~/.openclaw/skills/onedrive-integration/scripts/copy_to_onedrive.py` Run: ```bash ~/.openclaw/skills/onedrive-integration/scripts/copy_to_onedrive.py <paths...> ``` (Reads `config.env` automatically.) Optional overrides: - `.../copy_to_onedrive.py --onedrive-root "..." --subdir "..." <paths...>` - `.../copy_to_onedrive.py --config /path/to/config.env <paths...>` The script: - creates `${ONEDRIVE_ROOT}/${ONEDRIVE_SUBDIR}/` if missing - copies files (preserve timestamps) - prints the destination paths (no secrets) ## Notes - If the request is a *browser-only* document (no local file), download it to a temp path first, then copy. - If `ONEDRIVE_ROOT` is unset, do **not** guess—ask for confirmation. ## Executables / bin placement - Keep the canonical script inside the skill folder. - Optional: symlink into a common PATH dir (only if you want a short command): ```bash mkdir -p ~/.local/bin ln -sf ~/.openclaw/skills/onedrive-integration/scripts/copy_to_onedrive.py ~/.local/bin/copy-to-onedrive ```

path: config.env
content: # onedrive-integration config (machine-specific)
# Example file: /home/miles/.openclaw/skills/onedrive-integration/config.env.example

ONEDRIVE_ROOT="/mnt/c/Users/kongz/OneDrive"
ONEDRIVE_SUBDIR="openclaw"


path: scripts/copy_to_onedrive.py
content: #!/usr/bin/env python3
import argparse
import re
import shutil
from pathlib import Path


def detect_prefix(src: Path) -> str:
    p = str(src)
    if p.startswith('/mnt/c/Users/'):
        return 'c-'
    # Treat everything else as WSL/Linux path
    return 'wsl-'


def safe_name(src: Path) -> str:
    p = str(src)
    p = p.lstrip('/')
    p = p.replace('\\', '/')
    p = p.replace('/', '-')
    p = p.lower()
    # keep a-z0-9._-
    p = re.sub(r'[^a-z0-9._-]+', '-', p)
    p = re.sub(r'-{2,}', '-', p).strip('-')

    prefix = detect_prefix(src)
    if not p.startswith(prefix):
        p = prefix + p
    return p


def copy_one(src: Path, dest_dir: Path) -> Path:
    if not src.exists():
        raise FileNotFoundError(str(src))
    if src.is_dir():
        raise IsADirectoryError(str(src))

    dest_dir.mkdir(parents=True, exist_ok=True)
    out = dest_dir / safe_name(src)

    # Avoid overwrite by suffixing if needed
    if out.exists():
        stem = out.stem
        suf = out.suffix
        i = 2
        while True:
            cand = out.with_name(f"{stem}--{i}{suf}")
            if not cand.exists():
                out = cand
                break
            i += 1

    shutil.copy2(src, out)
    return out


def read_env_file(path: Path) -> dict:
    """Very small dotenv-style parser for KEY=VALUE lines."""
    out: dict = {}
    if not path.exists():
        return out

    for raw in path.read_text(encoding='utf-8').splitlines():
        line = raw.strip()
        if not line or line.startswith('#'):
            continue
        if '=' not in line:
            continue
        k, v = line.split('=', 1)
        k = k.strip()
        v = v.strip()
        # strip optional quotes
        if (v.startswith('"') and v.endswith('"')) or (v.startswith("'") and v.endswith("'")):
            v = v[1:-1]
        out[k] = v
    return out


def default_config_path() -> Path:
    # ~/.openclaw/skills/onedrive-integration/config.env
    return Path(__file__).resolve().parent.parent / 'config.env'


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument('--config', default=None, help='Path to config.env (defaults to skill-local config.env)')
    ap.add_argument('--onedrive-root', default=None)
    ap.add_argument('--subdir', default=None, help='Destination folder within OneDrive (default: openclaw)')
    ap.add_argument('paths', nargs='+')
    args = ap.parse_args()

    cfg_path = Path(args.config).expanduser() if args.config else default_config_path()
    cfg = read_env_file(cfg_path)

    onedrive_root = args.onedrive_root or cfg.get('ONEDRIVE_ROOT')
    if not onedrive_root:
        raise SystemExit(
            'ONEDRIVE_ROOT is not set. Provide --onedrive-root or create config.env next to the skill.'
        )

    subdir = args.subdir or cfg.get('ONEDRIVE_SUBDIR') or 'openclaw'

    onedrive_root_p = Path(onedrive_root).expanduser()
    dest_dir = onedrive_root_p / subdir

    outs = []
    for p in args.paths:
        src = Path(p)
        outs.append(copy_one(src, dest_dir))

    for o in outs:
        print(str(o))


if __name__ == '__main__':
    main()


path: scripts/onboard.sh
content: #!/usr/bin/env bash
set -euo pipefail

SCRIPT_PATH="$(readlink -f "${BASH_SOURCE[0]}")"
SCRIPT_DIR="$(cd -- "$(dirname -- "$SCRIPT_PATH")" && pwd)"
SKILL_DIR="$(cd -- "${SCRIPT_DIR}/.." && pwd)"
CFG_EXAMPLE="${SKILL_DIR}/config.env.example"
CFG_REAL="${SKILL_DIR}/config.env"

say() { printf '%s\n' "$*"; }

have() { command -v "$1" >/dev/null 2>&1; }

fail() { echo "onedrive-integration onboarding: $*" >&2; exit 1; }

prereqs() {
  have python3 || fail "python3 not found"
}

discover_candidates() {
  # Print one candidate per line
  shopt -s nullglob
  local paths=(/mnt/c/Users/*/OneDrive*)
  for p in "${paths[@]}"; do
    [[ -d "$p" ]] && echo "$p"
  done
}

write_real_config() {
  local root="$1"
  local subdir="$2"
  [[ -f "$CFG_EXAMPLE" ]] || fail "missing example config: $CFG_EXAMPLE"

  cat > "$CFG_REAL" <<EOF
# onedrive-integration config (machine-specific)
# Example file: ${CFG_EXAMPLE}

ONEDRIVE_ROOT="${root}"
ONEDRIVE_SUBDIR="${subdir}"
EOF
  say "Wrote: $CFG_REAL"
}

main() {
  prereqs

  say "Using example config: $CFG_EXAMPLE"
  say "Writing real config:   $CFG_REAL"
  say

  say "Discovered OneDrive candidates:" 
  mapfile -t cands < <(discover_candidates)
  if [[ ${#cands[@]} -eq 0 ]]; then
    fail "no OneDrive folders found under /mnt/c/Users/*/OneDrive*"
  fi

  local i=1
  for c in "${cands[@]}"; do
    say "  [$i] $c"
    i=$((i+1))
  done

  say
  read -r -p "Select a number (or type an absolute path): " choice

  local root=""
  if [[ "$choice" =~ ^[0-9]+$ ]] && (( choice >= 1 && choice <= ${#cands[@]} )); then
    root="${cands[$((choice-1))]}"
  else
    root="$choice"
  fi

  [[ -d "$root" ]] || fail "not a directory: $root"

  read -r -p "Destination subdir within OneDrive [openclaw]: " subdir
  subdir="${subdir:-openclaw}"

  say
  say "About to set:" 
  say "  ONEDRIVE_ROOT=\"$root\""
  say "  ONEDRIVE_SUBDIR=\"$subdir\""
  read -r -p "Confirm? [y/N]: " ok
  if [[ ! "$ok" =~ ^[Yy]$ ]]; then
    fail "aborted"
  fi

  write_real_config "$root" "$subdir"

  # Smoke test
  local tmp
  tmp="$(mktemp)"
  echo "onedrive-integration smoke test" > "$tmp"
  say "Running smoke test copy..."
  python3 "${SKILL_DIR}/scripts/copy_to_onedrive.py" "$tmp" | head -n 1
  say "Done."
}

main "$@"


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: suspicious | SkillSpector: suspicious (HIGH)