diff --git a/blender-mv.sh b/blender-mv.sh
new file mode 100755
index 0000000..650a5ec
--- /dev/null
+++ b/blender-mv.sh
@@ -0,0 +1,177 @@
+#!/usr/bin/env bash
+# blender-mv: A smart file/directory mover that automatically updates Blender paths
+
+set -e
+
+print_usage() {
+ echo "Usage: blender-mv [OPTIONS] SOURCE DEST"
+ echo ""
+ echo "Options:"
+ echo " --search-project
Scan this specific project directory for .blend files"
+ echo " to update (useful if you move an external asset folder,"
+ echo " and old .blend files referencing it are elsewhere)."
+ echo ""
+ echo "Example: blender-mv ./old_folder ./new_folder"
+}
+
+SEARCH_PROJECT=""
+
+# Parse arguments
+while [[ "$#" -gt 0 ]]; do
+ case $1 in
+ --search-project) SEARCH_PROJECT="$2"; shift 2 ;;
+ -h|--help) print_usage; exit 0 ;;
+ *)
+ if [[ -z "$SOURCE" ]]; then
+ SOURCE="$1"
+ elif [[ -z "$DEST" ]]; then
+ DEST="$1"
+ else
+ echo "Unknown parameter passed: $1"
+ print_usage
+ exit 1
+ fi
+ shift
+ ;;
+ esac
+done
+
+if [[ -z "$SOURCE" || -z "$DEST" ]]; then
+ echo "Error: Both SOURCE and DEST must be provided."
+ print_usage
+ exit 1
+fi
+
+if [[ ! -e "$SOURCE" ]]; then
+ echo "Error: Source path '$SOURCE' does not exist."
+ exit 1
+fi
+
+# Function to get absolute normalized path (handles missing destination gracefully)
+get_abs_path() {
+ python3 -c 'import os,sys; print(os.path.normpath(os.path.abspath(sys.argv[1])))' "$1" 2>/dev/null
+}
+
+# Resolve source before moving
+ABS_SOURCE=$(get_abs_path "$SOURCE")
+
+# Determine resulting target path
+if [[ -d "$DEST" ]]; then
+ # If DEST is an existing dir, standard mv puts SOURCE inside it
+ BASENAME_SOURCE=$(basename "$SOURCE")
+ ABS_DEST=$(get_abs_path "$DEST/$BASENAME_SOURCE")
+else
+ # Placed exactly at DEST (rename or target dir doesn't exist yet)
+ ABS_DEST=$(get_abs_path "$DEST")
+fi
+
+echo "Moving: $SOURCE -> $DEST"
+mv "$SOURCE" "$DEST"
+
+echo "Replacing paths in Blender's recent-files lists..."
+# Find all recent-files.txt across Blender versions and replace the string
+find "$HOME/Library/Application Support/Blender" -type f -name "recent-files.txt" 2>/dev/null | while read -r RECENT_FILE; do
+ # Using sed with alternative delimiter to avoid path interference
+ sed -i '' "s|${ABS_SOURCE}|${ABS_DEST}|g" "$RECENT_FILE"
+done
+
+# Prepare the embedded Python payload to update blend paths
+TMP_PY=$(mktemp /tmp/blender_update_paths.XXXXXX.py)
+cat << 'EOF' > "$TMP_PY"
+import bpy
+import sys
+
+def replace_in_prop(prop, old_path, new_path):
+ if prop and isinstance(prop, str) and old_path in prop:
+ return prop.replace(old_path, new_path)
+ return prop
+
+def run():
+ # Args are passed after `--`
+ if '--' not in sys.argv:
+ return
+ idx = sys.argv.index('--')
+ if len(sys.argv) <= idx + 2:
+ return
+
+ old_path = sys.argv[idx + 1]
+ new_path = sys.argv[idx + 2]
+
+ changed = False
+
+ # 1. Libraries (Linked objects/collections)
+ for lib in bpy.data.libraries:
+ new_fp = replace_in_prop(lib.filepath, old_path, new_path)
+ if new_fp != lib.filepath:
+ lib.filepath = new_fp
+ changed = True
+
+ # 2. Images (Textures, Env Maps)
+ for img in bpy.data.images:
+ new_fp = replace_in_prop(img.filepath, old_path, new_path)
+ if new_fp != img.filepath:
+ img.filepath = new_fp
+ changed = True
+
+ # 3. Cache files (Alembic/VDB)
+ for cache in bpy.data.cache_files:
+ new_fp = replace_in_prop(cache.filepath, old_path, new_path)
+ if new_fp != cache.filepath:
+ cache.filepath = new_fp
+ changed = True
+
+ # 4. Fonts
+ for font in bpy.data.fonts:
+ new_fp = replace_in_prop(font.filepath, old_path, new_path)
+ if new_fp != font.filepath:
+ font.filepath = new_fp
+ changed = True
+
+ # 5. Video strips
+ for scene in bpy.data.scenes:
+ if scene.sequence_editor:
+ for strip in scene.sequence_editor.sequences_all:
+ if strip.type in ('MOVIE', 'IMAGE'):
+ new_fp = replace_in_prop(strip.filepath, old_path, new_path)
+ if new_fp != strip.filepath:
+ strip.filepath = new_fp
+ changed = True
+
+ if changed:
+ print(f"[*] Updated paths in {bpy.data.filepath}")
+ bpy.ops.wm.save_mainfile()
+
+try:
+ run()
+except Exception as e:
+ print(f"[!] Error updating blend file paths: {e}")
+EOF
+
+# Determine what to search for blend files
+if [[ -n "$SEARCH_PROJECT" ]]; then
+ SEARCH_ROOT=$(get_abs_path "$SEARCH_PROJECT")
+ echo "Scanning project root '$SEARCH_ROOT' for .blend files..."
+elif [[ -d "$ABS_DEST" ]]; then
+ SEARCH_ROOT="$ABS_DEST"
+ echo "Scanning moved directory for .blend files..."
+elif [[ "$ABS_DEST" == *.blend ]]; then
+ SEARCH_ROOT="$ABS_DEST"
+ echo "Updating moved .blend file..."
+else
+ echo "No project root defined and moved object is not a directory or .blend file."
+ echo "Finished."
+ rm -f "$TMP_PY"
+ exit 0
+fi
+
+# Execute headless blender on all found blend files
+if [[ -d "$SEARCH_ROOT" ]]; then
+ find "$SEARCH_ROOT" -type f -name "*.blend" -print0 | while IFS= read -r -d '' BLEND_FILE; do
+ blender -b "$BLEND_FILE" -P "$TMP_PY" -- "$ABS_SOURCE" "$ABS_DEST" >/dev/null 2>&1 || echo "Warning: Failed to process $BLEND_FILE"
+ done
+elif [[ -f "$SEARCH_ROOT" && "$SEARCH_ROOT" == *.blend ]]; then
+ blender -b "$SEARCH_ROOT" -P "$TMP_PY" -- "$ABS_SOURCE" "$ABS_DEST" >/dev/null 2>&1 || echo "Warning: Failed to process $SEARCH_ROOT"
+fi
+
+rm -f "$TMP_PY"
+echo "Success: Move complete and paths updated."