Skill: mozi1924/spotlight-search (version 0.1.2)

--- name: spotlight-search description: Search files, apps, and metadata on macOS using Spotlight CLI tools (`mdfind`, `mdls`, `mdutil`) with a fallback script for cases where indexing is incomplete. Use when the task is to find documents, PDFs, apps, file paths, or metadata on a Mac, especially for prompts like "spotlight search", "find this file on mac", "search my computer", "find PDFs", "locate an app", or "check file metadata". metadata: { "openclaw": { "emoji": "🔦", "os": ["darwin"], "requires": { "bins": ["mdfind", "mdls", "mdutil", "find"] }, }, } --- # Spotlight Search ## Overview Use `mdfind` first for indexed search, `mdls` for metadata inspection, and `mdutil` only when diagnosing indexing issues. If Spotlight misses a known file, use the bundled fallback script instead of hand-rolling a `find` command every time. ## Quick Start Preferred flow: 1. Use `mdfind` for normal search. 2. Use `mdls` when the task is about metadata, not discovery. 3. Use `./scripts/spotlight_search.sh` when Spotlight indexing is incomplete or results are unexpectedly empty. 4. Treat the fallback script as a filename/path matcher, not a general shell helper. 5. Use `mdutil` only to inspect indexing, and only suggest re-indexing when the user explicitly wants that system-level change. ```bash # Find files containing "invoice" in name or content mdfind "invoice" # Find PDFs modified today mdfind 'kind:pdf AND kMDItemContentModificationDate >= $time.today' # Find applications whose name contains "chrome" mdfind 'kMDItemContentType == com.apple.application-bundle && kMDItemFSName == "*chrome*"cd' # Search only within ~/Documents mdfind -onlyin ~/Documents "kind:pdf" # View metadata for a file mdls /path/to/file ``` For a hybrid search that tries Spotlight first and falls back to `find`, use the bundled script: ```bash ./scripts/spotlight_search.sh "query" ./scripts/spotlight_search.sh -t pdf "report" ./scripts/spotlight_search.sh -d ~/Documents "meeting" ``` ## Core Capabilities ### 1. File Search Spotlight indexes file contents and metadata, making it ideal for locating documents when you remember words inside them. **Common patterns:** - **Simple keyword**: `mdfind "quarterly report"` - **By file type**: `mdfind "kind:pdf invoice"` - **By extension**: `mdfind 'kMDItemFSName == "*.md"cd'` - **By date**: `mdfind 'kMDItemContentModificationDate >= $time.yesterday'` - **By size**: `mdfind 'kMDItemFSSize > 1000000'` (files >1 MB) **When to use:** You recall words from the file's content, need to filter by type/date, or want to search across all indexed volumes quickly. ### 2. Application Search Spotlight knows about installed applications (`.app` bundles) and can find them by name or bundle identifier. **Examples:** ```bash # All applications mdfind 'kMDItemContentType == com.apple.application-bundle' # Applications containing "visual" in name mdfind 'kMDItemContentType == com.apple.application-bundle && kMDItemFSName == "*visual*"cd' ``` **When to use:** You need to locate an installed app, verify its presence, or get its bundle path. ### 3. Metadata Inspection Use `mdls` to view the rich metadata Spotlight stores for any file. ```bash # All metadata mdls /path/to/file # Specific attribute mdls -name kMDItemKind /path/to/file ``` **Common attributes:** `kMDItemKind`, `kMDItemContentType`, `kMDItemAuthors`, `kMDItemContentCreationDate`, `kMDItemPixelHeight`, `kMDItemDurationSeconds`. **When to use:** You need to check file properties (creation date, dimensions, author) without opening the file. ### 4. Hybrid Search (Fallback) Spotlight indexing may be incomplete (excluded directories, recent files not yet indexed). The bundled script `scripts/spotlight_search.sh` provides a hybrid approach: 1. Attempts Spotlight search via `mdfind` 2. If no results, falls back to `find` with name‑based matching 3. Uses shell argument arrays instead of `eval`, so the query is passed as data rather than executable shell text 4. Supports filtering by kind (`pdf`, `image`, `application`, etc.) **Usage:** ```bash ./scripts/spotlight_search.sh "query" ./scripts/spotlight_search.sh -t pdf "report" ./scripts/spotlight_search.sh -d ~/Documents -l 10 "draft" ``` **When to use:** You want maximum recall, or Spotlight is returning empty results for known files. ## Reference Materials For detailed query syntax, operator reference, and advanced examples, see: - **[mdfind Query Reference](references/mdfind-queries.md)** – Comprehensive guide to `mdfind` syntax, attribute filters, date comparisons, and practical examples. Load this reference when you need to construct complex queries or understand Spotlight's capabilities. ## Workflow Guidance ### Choosing the Right Tool | Scenario | Recommended Tool | Example | |----------|-----------------|---------| | "Find files containing 'budget'" | `mdfind "budget"` | Content‑aware search | | "Find all PDFs modified this week" | `mdfind 'kind:pdf AND kMDItemContentModificationDate >= $time.this_week'` | Metadata‑filtered search | | "Find the Chrome app" | `mdfind 'kMDItemContentType == com.apple.application-bundle && kMDItemFSName == "*chrome*"cd'` | Application search | | "Search only in Downloads folder" | `mdfind -onlyin ~/Downloads "kind:image"` | Scoped search | | "Check when a file was created" | `mdls -name kMDItemContentCreationDate /path/to/file` | Metadata inspection | | "Spotlight returns nothing but I know the file exists" | `./scripts/spotlight_search.sh "filename"` | Hybrid fallback | ### Troubleshooting **No results for known files?** 1. Check indexing status: `mdutil -s /` 2. Ensure the directory is indexed (Spotlight excludes some system and Library folders) 3. Re-index only with explicit user approval because it is a privileged, system-wide action: `sudo mdutil -E /` **Query syntax errors?** - Enclose queries containing spaces or special characters in single quotes. - Use `cd` after `==` for case‑ and diacritic‑insensitive matching: `kMDItemFSName == "*.pdf"cd` - Combine filters with `AND`/`OR` and parentheses. **Slow performance?** - Limit results with `-limit N` - Restrict search scope with `-onlyin` ## Notes - Spotlight indexing is not real‑time; newly created files may take minutes to appear. - Some directories (e.g., `~/Library`, system folders) may be excluded from indexing. - `mdfind` searches all indexed volumes by default; use `-onlyin` to constrain. - The hybrid script requires `find` (always present) and optionally `fd` (faster). ## See Also - `man mdfind` - `man mdls` - `man mdutil` - Apple's Uniform Type Identifier reference: https://developer.apple.com/documentation/uniformtypeidentifiers

path: references/mdfind-queries.md
content: # mdfind Query Reference

## Overview

`mdfind` is macOS's command-line interface to Spotlight. It searches indexed metadata (file contents, attributes, and system metadata) using the same index that powers the GUI Spotlight search.

## Basic Usage

```bash
mdfind <query>
```

Searches all indexed volumes for files matching the query.

## Query Syntax

### Simple Text Search

```bash
mdfind "invoice"
mdfind "quarterly report"
```

Searches file **contents** and **metadata** for the given words.

### Attribute Filters

Use `attribute:value` syntax to restrict search to specific metadata.

#### Common Attributes

- `kind:` – File type category
  - `kind:pdf`, `kind:image`, `kind:video`, `kind:application`
  - `kind:folder`, `kind:alias`, `kind:contact`
- `kMDItemFSName` – File system name (supports glob patterns with `==` operator)
- `kMDItemDisplayName` – Display name (includes extension)
- `kMDItemContentType` – Uniform Type Identifier (UTI)
  - `com.apple.application-bundle` (applications)
  - `public.plain-text` (text files)
  - `public.html` (HTML files)
  - `public.jpeg` (JPEG images)
- `kMDItemAuthors` – Author names
- `kMDItemAlbum` – Music album
- `kMDItemPixelHeight`, `kMDItemPixelWidth` – Image dimensions
- `kMDItemDurationSeconds` – Audio/video duration
- `kMDItemEmailAddresses` – Email addresses
- `kMDItemPhoneNumbers` – Phone numbers

#### Comparison Operators

- `==` – Equal (case‑insensitive, diacritic‑insensitive by default)
- `=` – Equal (same as `==`)
- `!=` – Not equal
- `<`, `<=`, `>`, `>=` – Numeric or date comparisons

Use `cd` after an operator for case‑ and diacritic‑insensitive matching (default).  
Use `c` for case‑sensitive, `d` for diacritic‑sensitive.

Examples:

```bash
mdfind 'kMDItemFSName == "*.pdf"cd'
mdfind 'kMDItemPixelHeight >= 1000'
```

#### Boolean Operators

- `AND`, `&&` – Logical AND
- `OR`, `||` – Logical OR
- `NOT`, `!` – Logical NOT (prefix operator)

Parentheses can group sub‑expressions.

Example:

```bash
mdfind '(kind:pdf OR kind:image) AND (kMDItemFSName == "*invoice*")'
```

### Date Filters

Use `kMDItemContentCreationDate`, `kMDItemContentModificationDate`, `kMDItemLastUsedDate`.

Dates can be specified in ISO 8601 format (`YYYY-MM-DD`) or as relative times:

- `$time.today`
- `$time.yesterday`
- `$time.this_week`
- `$time.this_month`
- `$time.this_year`

Examples:

```bash
mdfind 'kind:pdf AND kMDItemContentModificationDate >= $time.today'
mdfind 'kMDItemContentCreationDate >= 2024-01-01'
```

### Searching Specific Directories

Use `-onlyin <directory>` to restrict search to a folder.

```bash
mdfind -onlyin ~/Documents "kind:pdf"
mdfind -onlyin /Applications "kMDItemContentType == com.apple.application-bundle"
```

### Limiting Results

`-limit <N>` returns at most N matches.

```bash
mdfind -limit 10 "kind:image"
```

## Practical Examples

### Find Applications

```bash
# All applications
mdfind 'kMDItemContentType == com.apple.application-bundle'

# Applications whose name contains "chrome"
mdfind 'kMDItemContentType == com.apple.application-bundle && kMDItemFSName == "*chrome*"cd'
```

### Find PDFs Modified Today

```bash
mdfind 'kind:pdf AND kMDItemContentModificationDate >= $time.today'
```

### Find Images Larger Than 2 MB

```bash
mdfind 'kind:image AND kMDItemFSSize > 2000000'
```

### Find Files with "invoice" in Name or Content

```bash
mdfind 'invoice'
```

### Find Contacts

```bash
mdfind 'kind:contact'
```

### Find Recent Documents (Opened in Last 7 Days)

```bash
mdfind 'kMDItemLastUsedDate >= $time.today(-7)'
```

### Find Files with Specific Extension

```bash
mdfind 'kMDItemFSName == "*.md"cd'
```

## Checking Metadata for a File

Use `mdls` to see all Spotlight metadata for a given file:

```bash
mdls /path/to/file
```

Use `grep` to filter:

```bash
mdls -name kMDItemKind /path/to/file
```

## Notes

- Spotlight indexing may exclude certain directories (e.g., `~/Library`, system folders). Use `mdutil -s /` to check indexing status.
- Newly created files may not appear until the index updates (usually within minutes).
- To rebuild the index for a volume: `sudo mdutil -E /`.
- `mdfind` only searches indexed volumes. Use `-a` to search all volumes (including non‑indexed) but performance will be slower.

## Further Reading

- `man mdfind`
- `man mdls`
- Apple's Uniform Type Identifier reference: https://developer.apple.com/documentation/uniformtypeidentifiers


path: scripts/spotlight_search.sh
content: #!/bin/bash
#
# Hybrid file search: tries Spotlight (mdfind) first, falls back to find.
# Safe version: avoids eval and passes user input as arguments.
#
# Usage: spotlight_search.sh [options] <query>
#
# Options:
#   -t, --type <kind>      Filter by kind (pdf, image, application, video, audio, text)
#   -d, --dir <directory>  Search only within directory (default: current directory)
#   -l, --limit <N>        Limit results to N (default: 20)
#   -f, --fallback-only    Skip Spotlight, use only fallback search
#   -v, --verbose          Show detailed output
#   -h, --help             Show this help

set -euo pipefail

TYPE=""
DIR="."
LIMIT=20
FALLBACK_ONLY=false
VERBOSE=false
QUERY=""

usage() {
  cat <<EOF
Usage: $0 [options] <query>

Hybrid file search: tries Spotlight (mdfind) first, falls back to find.

Options:
  -t, --type <kind>      Filter by kind (pdf, image, application, video, audio, text)
  -d, --dir <directory>  Search only within directory (default: current directory)
  -l, --limit <N>        Limit results to N (default: 20)
  -f, --fallback-only    Skip Spotlight, use only fallback search
  -v, --verbose          Show detailed output
  -h, --help             Show this help

Examples:
  $0 "invoice"
  $0 -t pdf "report"
  $0 -d ~/Documents "meeting"
EOF
}

log() {
  if $VERBOSE; then
    echo "[INFO] $*" >&2
  fi
}

while [[ $# -gt 0 ]]; do
  case "$1" in
    -t|--type)
      [[ $# -ge 2 ]] || { echo "Error: Missing value for $1" >&2; exit 1; }
      TYPE="$2"
      shift 2
      ;;
    -d|--dir)
      [[ $# -ge 2 ]] || { echo "Error: Missing value for $1" >&2; exit 1; }
      DIR="$2"
      shift 2
      ;;
    -l|--limit)
      [[ $# -ge 2 ]] || { echo "Error: Missing value for $1" >&2; exit 1; }
      LIMIT="$2"
      shift 2
      ;;
    -f|--fallback-only)
      FALLBACK_ONLY=true
      shift
      ;;
    -v|--verbose)
      VERBOSE=true
      shift
      ;;
    -h|--help)
      usage
      exit 0
      ;;
    --)
      shift
      if [[ $# -gt 0 ]]; then
        QUERY="$1"
        shift
      fi
      break
      ;;
    -*)
      echo "Error: Unknown option $1" >&2
      exit 1
      ;;
    *)
      if [[ -z "$QUERY" ]]; then
        QUERY="$1"
      else
        echo "Error: Multiple query arguments not supported" >&2
        exit 1
      fi
      shift
      ;;
  esac
done

if [[ -z "$QUERY" ]]; then
  echo "Error: Query argument required" >&2
  exit 1
fi

if [[ ! "$LIMIT" =~ ^[0-9]+$ ]] || [[ "$LIMIT" -lt 1 ]]; then
  echo "Error: Limit must be a positive integer" >&2
  exit 1
fi

if [[ ! -d "$DIR" ]]; then
  echo "Error: Directory '$DIR' does not exist" >&2
  exit 1
fi

DIR="$(cd "$DIR" && pwd)"

fallback_search() {
  local q="$1"
  local dir="$2"
  local limit="$3"
  local kind="$4"

  log "Starting fallback search with find..."

  local -a cmd=(find "$dir")

  case "$kind" in
    application)
      cmd+=( -type d )
      ;;
    "")
      cmd+=( -type f )
      ;;
    *)
      cmd+=( -type f )
      ;;
  esac

  case "$kind" in
    pdf)
      cmd+=( -iname "*.pdf" )
      ;;
    image)
      cmd+=( \( -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.png" -o -iname "*.gif" -o -iname "*.bmp" -o -iname "*.tiff" -o -iname "*.webp" \) )
      ;;
    application)
      cmd+=( -iname "*.app" )
      ;;
    video)
      cmd+=( \( -iname "*.mp4" -o -iname "*.mov" -o -iname "*.avi" -o -iname "*.mkv" -o -iname "*.webm" \) )
      ;;
    audio)
      cmd+=( \( -iname "*.mp3" -o -iname "*.m4a" -o -iname "*.wav" -o -iname "*.flac" -o -iname "*.aac" \) )
      ;;
    text)
      cmd+=( \( -iname "*.txt" -o -iname "*.md" -o -iname "*.json" -o -iname "*.yml" -o -iname "*.yaml" -o -iname "*.xml" -o -iname "*.csv" \) )
      ;;
    "")
      ;;
    *)
      echo "Error: Unsupported type '$kind'" >&2
      exit 1
      ;;
  esac

  cmd+=( -iname "*${q}*" )

  log "Running fallback search safely with argument array"
  "${cmd[@]}" 2>/dev/null | head -n "$limit"
}

spotlight_search() {
  local q="$1"
  local dir="$2"
  local limit="$3"
  local kind="$4"

  log "Starting Spotlight search..."

  local -a cmd=(mdfind)
  if [[ "$dir" != "$HOME" ]]; then
    cmd+=( -onlyin "$dir" )
  fi

  local full_query="$q"
  if [[ -n "$kind" ]]; then
    if [[ -n "$full_query" ]]; then
      full_query="kind:$kind ${full_query}"
    else
      full_query="kind:$kind"
    fi
  fi

  log "Running Spotlight search safely with argument array"
  "${cmd[@]}" "$full_query" 2>/dev/null | head -n "$limit"
}

RESULTS=""
if ! $FALLBACK_ONLY; then
  log "Attempting Spotlight search..."
  RESULTS="$(spotlight_search "$QUERY" "$DIR" "$LIMIT" "$TYPE")"
  if [[ -n "$RESULTS" ]]; then
    printf '%s\n' "$RESULTS"
    log "Spotlight search successful."
    exit 0
  fi
  log "Spotlight returned no results."
fi

log "Using fallback search..."
RESULTS="$(fallback_search "$QUERY" "$DIR" "$LIMIT" "$TYPE")"
if [[ -n "$RESULTS" ]]; then
  printf '%s\n' "$RESULTS"
  log "Fallback search successful."
else
  log "No results found."
  exit 1
fi


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