Skill: gedigi/peekaboox (version 1.0.0)

--- name: linux-desktop description: > Control and automate the Linux desktop GUI on X11. Use this skill to take screenshots, find and click UI elements, type text, send keyboard shortcuts, scroll, manage windows (focus, minimize, maximize, close, move, resize), and use your own vision to locate elements on screen. Requires X11 (not Wayland). Use for desktop automation, GUI testing, remote desktop control, and any task requiring interaction with graphical applications. metadata: clawdbot: emoji: "\U0001F5A5" os: ["linux"] requires: bins: ["xdotool", "wmctrl", "scrot"] env: ["DISPLAY"] install: - id: apt kind: shell command: bash install.sh label: Install dependencies (apt/dnf/pacman) --- # Linux Desktop GUI Automation Automate any X11 Linux desktop: capture screens, find and click elements, type, use hotkeys, manage windows. Preferred screenshot interpretation path: capture with `capture.sh` and interpret the image directly in your OpenClaw chat (existing image-capable model connection). ## Prerequisites - X11 session running (XFCE, GNOME on X11, KDE on X11, i3, openbox, etc.) - `DISPLAY` environment variable set (usually `:0`) - Run `bash install.sh` once to install dependencies - No extra key needed for screenshot interpretation when using OpenClaw's image-capable chat path ## Quick Reference | Task | Command | |------|---------| | Take screenshot | `bash capture.sh` | | Screenshot of window | `bash capture.sh --window "Firefox"` | | List windows | `bash inspect.sh` | | Active window info | `bash inspect.sh --active` | | Find window by name | `bash inspect.sh --window "Firefox"` | | Click at coordinates | `bash click.sh --x 500 --y 300` | | Right-click | `bash click.sh --x 500 --y 300 --button right` | | Double-click | `bash click.sh --x 500 --y 300 --double` | | Click relative to window | `bash click.sh --window "Firefox" --x 200 --y 150` | | Type text | `bash type.sh "hello world"` | | Type into window | `bash type.sh --window "Terminal" "ls -la"` | | Send hotkey | `bash hotkey.sh "ctrl+c"` | | Send Enter | `bash hotkey.sh "Return"` | | Scroll down | `bash scroll.sh --direction down --amount 3` | | Scroll up at position | `bash scroll.sh --x 500 --y 300 --direction up --amount 3` | | Focus window | `bash window.sh --action focus --window "Firefox"` | | Minimize window | `bash window.sh --action minimize --window "Firefox"` | | Maximize window | `bash window.sh --action maximize --window "Firefox"` | | Close window | `bash window.sh --action close --window "Firefox"` | | Move window | `bash window.sh --action move --window "Firefox" --x 100 --y 50` | | Resize window | `bash window.sh --action resize --window "Firefox" --width 1280 --height 800` | ## Typical Automation Workflow For most GUI automation tasks, follow this pattern: 1. **Capture** a screenshot with `capture.sh` — note the file path printed 2. **Look** at the screenshot yourself to understand what's on screen 3. **Find** the target element by examining the screenshot and estimating its pixel coordinates 4. **Act** using the coordinates: `click.sh --x X --y Y` 5. **Verify** by capturing another screenshot and checking the result ### Example: Click the Save button in a dialog ```bash # Step 1: Capture the screen SCREENSHOT=$(bash capture.sh | tail -1) # Step 2: Look at the screenshot (read the image file with your vision) # Examine the image and identify the Save button's position # Step 3: Click at the coordinates you identified bash click.sh --x 450 --y 320 ``` ### Example: Type into a specific application ```bash # Focus the terminal window and type a command bash type.sh --window "Terminal" "ls -la" bash hotkey.sh "Return" ``` ### Example: Window management ```bash # Maximize Firefox, then focus a terminal bash window.sh --action maximize --window "Firefox" bash window.sh --action focus --window "Terminal" ``` ## JSON Output All tools support a `--json` flag for machine-readable output: ```json {"success": true, "output": "...", "error": null} ``` On failure: ```json {"success": false, "output": null, "error": "Error description"} ``` ## Environment Setup If `DISPLAY` is not set (e.g., running over SSH), set it before calling any tool: ```bash export DISPLAY=:0 ``` For headless servers with a virtual display: ```bash Xvfb :0 -screen 0 1920x1080x24 & export DISPLAY=:0 ``` ## Hotkey Reference Key names follow X11 conventions: | Key | Name | |-----|------| | Enter | `Return` | | Tab | `Tab` | | Escape | `Escape` | | Backspace | `BackSpace` | | Delete | `Delete` | | Home | `Home` | | End | `End` | | Page Up | `Page_Up` | | Page Down | `Page_Down` | | F1-F12 | `F1` through `F12` | | Super/Win | `super` | | Ctrl | `ctrl` | | Alt | `alt` | | Shift | `shift` | Combine with `+`: `ctrl+c`, `ctrl+shift+t`, `alt+F4`, `super+d` ## Limitations - X11 only — does not work on Wayland sessions - Cannot interact with Wayland-native apps in a Wayland session - Some apps with custom rendering (games, Electron apps with security flags) may resist automation - Screenshot quality depends on compositor; disable compositing if captures look wrong

path: LEARN_AND_TEST.md
content: # Learn and Test the linux-desktop Skill

You have a new skill called `linux-desktop` that lets you control the Linux GUI. Work through each step below to learn how it works. Run all commands from the skill directory.

## Setup

```bash
export DISPLAY=:0
bash install.sh
```

## Test 1: See What's on Screen

Take a screenshot, then look at the image yourself to understand what's on screen:

```bash
SHOT=$(bash capture.sh | tail -1)
# Now read the image file at $SHOT using your vision to see the desktop
```

## Test 2: List Open Windows

```bash
bash inspect.sh
```

Pick a window from the list and get its details:

```bash
bash inspect.sh --window "<name from list>"
```

## Test 3: Click Something

Take a screenshot, look at it to find an element, then click it:

```bash
SHOT=$(bash capture.sh | tail -1)
# Look at the screenshot to find the clock in the panel
# Estimate its coordinates, then click:
bash click.sh --x <X> --y <Y>
```

## Test 4: Type and Use Hotkeys

Open a terminal and type into it:

```bash
bash window.sh --action focus --window "Terminal"
bash type.sh "echo hello from openclaw"
bash hotkey.sh "Return"
```

## Test 5: Window Management

```bash
bash window.sh --action maximize --window "Terminal"
bash capture.sh
bash window.sh --action minimize --window "Terminal"
```

## Test 6: Scroll

```bash
bash scroll.sh --direction down --amount 5
bash scroll.sh --direction up --amount 5
```

## Full Automated Run

Run the built-in test suite to verify everything works:

```bash
bash test.sh
```

If all tests pass, the skill is ready. You can now use it to automate any GUI task by following the capture-look-act-verify loop described in SKILL.md.


path: README.md
content: # linux-desktop — OpenClaw Linux GUI Automation Skill

Full GUI automation for Linux X11 desktops. This skill gives OpenClaw the ability to capture screenshots, inspect windows, click, type, send hotkeys, scroll, and manage windows.

## Requirements

- Linux with X11 session (XFCE, GNOME on X11, KDE on X11, i3, openbox, etc.)
- `DISPLAY` environment variable set (usually `:0`)

## Installation

```bash
bash install.sh
```

This installs system dependencies (`xdotool`, `wmctrl`, `scrot`, `x11-utils`, `imagemagick`, Python).

Supported package managers: apt (Debian/Ubuntu), dnf (Fedora/RHEL), pacman (Arch).

## Tools

| Script | Purpose |
|--------|---------|
| `capture.sh` | Take screenshots (full screen or specific window) |
| `inspect.sh` | List windows, get window details (JSON output) |
| `click.sh` | Mouse click at coordinates (left/right/middle, single/double) |
| `type.sh` | Type text into the focused window |
| `hotkey.sh` | Send keyboard shortcuts (ctrl+c, alt+F4, etc.) |
| `scroll.sh` | Scroll up/down at current or specified position |
| `window.sh` | Window management (focus, minimize, maximize, close, move, resize) |

## Quick Start

```bash
# Take a screenshot
bash capture.sh

# List all open windows
bash inspect.sh

# Click at coordinates
bash click.sh --x 500 --y 300

# Type text
bash type.sh "Hello world"

# Send Ctrl+C
bash hotkey.sh "ctrl+c"
```

Preferred workflow: capture screenshot, interpret it directly in your OpenClaw chat, then act with coordinates.

## Testing

```bash
bash test.sh
```

## OpenClaw Integration

Because this repo is dedicated to this single skill, use the repo root as the skill folder:

```bash
cp -r <this-repo> ~/.openclaw/workspace/skills/linux-desktop/
```

Restart the OpenClaw gateway; the agent will read `SKILL.md`.

## Limitations

- **X11 only** — does not work on Wayland sessions
- Some applications with custom rendering may resist automation


path: capture.sh
content: #!/bin/bash
# capture.sh — Take a screenshot of the full screen or a specific window
# Outputs the saved file path on the last line of stdout
set -e

# --- Defaults ---
OUTPUT=""
WINDOW_NAME=""
JSON_OUTPUT=false

# --- Parse arguments ---
while [[ $# -gt 0 ]]; do
    case "$1" in
        --window)
            WINDOW_NAME="$2"
            shift 2
            ;;
        --output)
            OUTPUT="$2"
            shift 2
            ;;
        --json)
            JSON_OUTPUT=true
            shift
            ;;
        -h|--help)
            echo "Usage: capture.sh [--window NAME] [--output PATH] [--json]"
            echo ""
            echo "Options:"
            echo "  --window NAME   Capture a specific window by name"
            echo "  --output PATH   Save screenshot to this path (default: /tmp/linux-desktop-capture-TIMESTAMP.png)"
            echo "  --json          Output result as JSON"
            exit 0
            ;;
        *)
            echo "ERROR: Unknown argument: $1" >&2
            exit 1
            ;;
    esac
done

# --- Check DISPLAY ---
if [ -z "$DISPLAY" ]; then
    if $JSON_OUTPUT; then
        echo '{"success": false, "output": null, "error": "DISPLAY not set. Run: export DISPLAY=:0"}'
    else
        echo "ERROR: DISPLAY not set. Run: export DISPLAY=:0" >&2
    fi
    exit 1
fi

# --- Default output path ---
if [ -z "$OUTPUT" ]; then
    OUTPUT="/tmp/linux-desktop-capture-$(date +%s).png"
fi

# --- Capture ---
if [ -n "$WINDOW_NAME" ]; then
    # Find the window ID by name
    WIN_ID=$(xdotool search --name "$WINDOW_NAME" | head -1)
    if [ -z "$WIN_ID" ]; then
        if $JSON_OUTPUT; then
            echo "{\"success\": false, \"output\": null, \"error\": \"Window not found: $WINDOW_NAME\"}"
        else
            echo "ERROR: Window not found: $WINDOW_NAME" >&2
        fi
        exit 1
    fi

    # Try import (ImageMagick) first for window-specific capture, fall back to scrot
    if command -v import &>/dev/null; then
        import -window "$WIN_ID" "$OUTPUT"
    else
        # Focus the window then use scrot -u (focused window)
        xdotool windowactivate --sync "$WIN_ID"
        sleep 0.3
        scrot -u "$OUTPUT"
    fi
else
    # Full screen capture
    scrot "$OUTPUT"
fi

# --- Output ---
if $JSON_OUTPUT; then
    echo "{\"success\": true, \"output\": \"$OUTPUT\", \"error\": null}"
else
    echo "Screenshot saved: $OUTPUT"
    echo "$OUTPUT"
fi


path: click.sh
content: #!/bin/bash
# click.sh — Move mouse and click at coordinates, optionally relative to a window
set -e

# --- Defaults ---
X=""
Y=""
BUTTON="left"
DOUBLE=false
WINDOW_NAME=""
JSON_OUTPUT=false

# --- Parse arguments ---
while [[ $# -gt 0 ]]; do
    case "$1" in
        --x)
            X="$2"
            shift 2
            ;;
        --y)
            Y="$2"
            shift 2
            ;;
        --button)
            BUTTON="$2"
            shift 2
            ;;
        --double)
            DOUBLE=true
            shift
            ;;
        --window)
            WINDOW_NAME="$2"
            shift 2
            ;;
        --json)
            JSON_OUTPUT=true
            shift
            ;;
        -h|--help)
            echo "Usage: click.sh --x X --y Y [--button left|right|middle] [--double] [--window NAME] [--json]"
            echo ""
            echo "Options:"
            echo "  --x X           X coordinate"
            echo "  --y Y           Y coordinate"
            echo "  --button TYPE   Button: left (default), right, middle"
            echo "  --double        Double-click"
            echo "  --window NAME   Click relative to window position"
            echo "  --json          Output result as JSON"
            exit 0
            ;;
        *)
            echo "ERROR: Unknown argument: $1" >&2
            exit 1
            ;;
    esac
done

# --- Check DISPLAY ---
if [ -z "$DISPLAY" ]; then
    if $JSON_OUTPUT; then
        echo '{"success": false, "output": null, "error": "DISPLAY not set. Run: export DISPLAY=:0"}'
    else
        echo "ERROR: DISPLAY not set. Run: export DISPLAY=:0" >&2
    fi
    exit 1
fi

# --- Validate coordinates ---
if [ -z "$X" ] || [ -z "$Y" ]; then
    MSG="Both --x and --y are required"
    if $JSON_OUTPUT; then
        echo "{\"success\": false, \"output\": null, \"error\": \"$MSG\"}"
    else
        echo "ERROR: $MSG" >&2
    fi
    exit 1
fi

# --- Map button name to xdotool button number ---
case "$BUTTON" in
    left)   BTN_NUM=1 ;;
    middle) BTN_NUM=2 ;;
    right)  BTN_NUM=3 ;;
    *)
        MSG="Unknown button: $BUTTON (use left, right, or middle)"
        if $JSON_OUTPUT; then
            echo "{\"success\": false, \"output\": null, \"error\": \"$MSG\"}"
        else
            echo "ERROR: $MSG" >&2
        fi
        exit 1
        ;;
esac

# --- Window-relative click ---
if [ -n "$WINDOW_NAME" ]; then
    WIN_ID=$(xdotool search --name "$WINDOW_NAME" | head -1)
    if [ -z "$WIN_ID" ]; then
        MSG="Window not found: $WINDOW_NAME"
        if $JSON_OUTPUT; then
            echo "{\"success\": false, \"output\": null, \"error\": \"$MSG\"}"
        else
            echo "ERROR: $MSG" >&2
        fi
        exit 1
    fi

    # Get window position
    GEOM=$(xdotool getwindowgeometry --shell "$WIN_ID")
    WIN_X=$(echo "$GEOM" | grep "^X=" | cut -d= -f2)
    WIN_Y=$(echo "$GEOM" | grep "^Y=" | cut -d= -f2)

    # Calculate absolute coordinates
    X=$((WIN_X + X))
    Y=$((WIN_Y + Y))
fi

# --- Move and click ---
xdotool mousemove "$X" "$Y"

if $DOUBLE; then
    xdotool click --repeat 2 --delay 50 "$BTN_NUM"
else
    xdotool click "$BTN_NUM"
fi

# Short pause to let UI respond
sleep 0.1

# --- Output ---
if $JSON_OUTPUT; then
    echo "{\"success\": true, \"output\": \"Clicked $BUTTON at ($X, $Y)\", \"error\": null}"
else
    echo "Clicked $BUTTON at ($X, $Y)"
fi


path: hotkey.sh
content: #!/bin/bash
# hotkey.sh — Send keyboard shortcuts/hotkeys
set -e

# --- Defaults ---
KEYS=""
JSON_OUTPUT=false

# --- Parse arguments ---
while [[ $# -gt 0 ]]; do
    case "$1" in
        --json)
            JSON_OUTPUT=true
            shift
            ;;
        -h|--help)
            echo "Usage: hotkey.sh [--json] KEYS"
            echo ""
            echo "Options:"
            echo "  --json   Output result as JSON"
            echo "  KEYS     Key combination (e.g., ctrl+c, alt+F4, super+d, Return, Tab)"
            echo ""
            echo "Key names follow X11 conventions:"
            echo "  Return, Tab, Escape, BackSpace, Delete, Home, End,"
            echo "  Page_Up, Page_Down, F1-F12, super, ctrl, alt, shift"
            exit 0
            ;;
        -*)
            echo "ERROR: Unknown option: $1" >&2
            exit 1
            ;;
        *)
            KEYS="$1"
            shift
            ;;
    esac
done

# --- Check DISPLAY ---
if [ -z "$DISPLAY" ]; then
    if $JSON_OUTPUT; then
        echo '{"success": false, "output": null, "error": "DISPLAY not set. Run: export DISPLAY=:0"}'
    else
        echo "ERROR: DISPLAY not set. Run: export DISPLAY=:0" >&2
    fi
    exit 1
fi

# --- Validate keys ---
if [ -z "$KEYS" ]; then
    MSG="No key combination provided"
    if $JSON_OUTPUT; then
        echo "{\"success\": false, \"output\": null, \"error\": \"$MSG\"}"
    else
        echo "ERROR: $MSG" >&2
    fi
    exit 1
fi

# --- Send key combination ---
xdotool key --clearmodifiers "$KEYS"

# --- Output ---
if $JSON_OUTPUT; then
    echo "{\"success\": true, \"output\": \"Sent hotkey: $KEYS\", \"error\": null}"
else
    echo "Sent hotkey: $KEYS"
fi


path: inspect.sh
content: #!/bin/bash
# inspect.sh — List open windows or get details about a specific window
# Outputs JSON for easy agent consumption
set -e

# --- Defaults ---
WINDOW_NAME=""
ACTIVE=false

# --- Parse arguments ---
while [[ $# -gt 0 ]]; do
    case "$1" in
        --window)
            WINDOW_NAME="$2"
            shift 2
            ;;
        --active)
            ACTIVE=true
            shift
            ;;
        --json)
            # Always JSON, accepted for consistency
            shift
            ;;
        -h|--help)
            echo "Usage: inspect.sh [--window NAME] [--active] [--json]"
            echo ""
            echo "Options:"
            echo "  --window NAME   Get details for a window matching NAME"
            echo "  --active        Get details for the currently focused window"
            echo "  --json          Output as JSON (default)"
            exit 0
            ;;
        *)
            echo "ERROR: Unknown argument: $1" >&2
            exit 1
            ;;
    esac
done

# --- Check DISPLAY ---
if [ -z "$DISPLAY" ]; then
    echo '{"success": false, "windows": [], "error": "DISPLAY not set. Run: export DISPLAY=:0"}'
    exit 1
fi

# --- Active window mode ---
if $ACTIVE; then
    WIN_ID_DEC=$(xdotool getactivewindow 2>/dev/null || true)
    if [ -z "$WIN_ID_DEC" ]; then
        echo '{"success": false, "windows": [], "error": "No active window found"}'
        exit 1
    fi
    WIN_ID=$(printf "0x%08x" "$WIN_ID_DEC")
    WIN_NAME=$(xdotool getactivewindow getwindowname 2>/dev/null || echo "unknown")
    WIN_CLASS=$(xprop -id "$WIN_ID_DEC" WM_CLASS 2>/dev/null | sed 's/.*= //' | tr -d '"' | cut -d',' -f2 | xargs 2>/dev/null || echo "unknown")
    GEOM=$(xdotool getactivewindow getwindowgeometry --shell 2>/dev/null || true)
    X=$(echo "$GEOM" | grep "^X=" | cut -d= -f2 || echo "0")
    Y=$(echo "$GEOM" | grep "^Y=" | cut -d= -f2 || echo "0")
    WIDTH=$(echo "$GEOM" | grep "^WIDTH=" | cut -d= -f2 || echo "0")
    HEIGHT=$(echo "$GEOM" | grep "^HEIGHT=" | cut -d= -f2 || echo "0")

    WIN_NAME_ESC=$(echo "$WIN_NAME" | sed 's/\\/\\\\/g; s/"/\\"/g')
    WIN_CLASS_ESC=$(echo "$WIN_CLASS" | sed 's/\\/\\\\/g; s/"/\\"/g')

    cat <<ENDJSON
{
  "success": true,
  "windows": [{
    "id": "$WIN_ID",
    "name": "$WIN_NAME_ESC",
    "class": "$WIN_CLASS_ESC",
    "x": ${X:-0},
    "y": ${Y:-0},
    "width": ${WIDTH:-0},
    "height": ${HEIGHT:-0}
  }],
  "error": null
}
ENDJSON
    exit 0
fi

# --- List windows using wmctrl ---
if ! command -v wmctrl &>/dev/null; then
    echo '{"success": false, "windows": [], "error": "wmctrl not installed. Run: bash install.sh"}'
    exit 1
fi

# Build window list into an array, then output as JSON
ENTRIES=()

while IFS= read -r line; do
    [ -z "$line" ] && continue

    WIN_ID=$(echo "$line" | awk '{print $1}')
    DESKTOP=$(echo "$line" | awk '{print $2}')
    X=$(echo "$line" | awk '{print $3}')
    Y=$(echo "$line" | awk '{print $4}')
    WIDTH=$(echo "$line" | awk '{print $5}')
    HEIGHT=$(echo "$line" | awk '{print $6}')
    # Window name is everything after the 7th field
    WIN_NAME=$(echo "$line" | awk '{for(i=8;i<=NF;i++) printf "%s ", $i; print ""}' | sed 's/ *$//')

    # Get window class via xprop
    WIN_CLASS=$(xprop -id "$WIN_ID" WM_CLASS 2>/dev/null | sed 's/.*= //' | tr -d '"' | cut -d',' -f2 | xargs 2>/dev/null || echo "unknown")

    # Filter by window name if specified
    if [ -n "$WINDOW_NAME" ]; then
        if ! echo "$WIN_NAME" | grep -qi "$WINDOW_NAME"; then
            continue
        fi
    fi

    WIN_NAME_ESC=$(echo "$WIN_NAME" | sed 's/\\/\\\\/g; s/"/\\"/g')
    WIN_CLASS_ESC=$(echo "$WIN_CLASS" | sed 's/\\/\\\\/g; s/"/\\"/g')

    ENTRY=$(cat <<ENDENTRY
    {
      "id": "$WIN_ID",
      "name": "$WIN_NAME_ESC",
      "class": "$WIN_CLASS_ESC",
      "x": $X,
      "y": $Y,
      "width": $WIDTH,
      "height": $HEIGHT,
      "desktop": $DESKTOP
    }
ENDENTRY
)
    ENTRIES+=("$ENTRY")
done < <(wmctrl -lG 2>/dev/null)

# Output JSON
echo "{"
echo "  \"success\": true,"
echo "  \"windows\": ["

for i in "${!ENTRIES[@]}"; do
    if [ "$i" -gt 0 ]; then
        echo "    ,"
    fi
    echo "${ENTRIES[$i]}"
done

echo "  ],"
echo "  \"error\": null"
echo "}"


path: install.sh
content: #!/bin/bash
# install.sh — Install dependencies for the linux-desktop OpenClaw skill
# Supports apt (Debian/Ubuntu), dnf (Fedora/RHEL), and pacman (Arch)
set -e

echo "Installing linux-desktop skill dependencies..."

# Warn if running under Wayland
if [ "$XDG_SESSION_TYPE" = "wayland" ]; then
    echo "WARNING: Wayland session detected. This skill requires X11." >&2
    echo "Log out and select an X11/Xorg session at login to use this skill." >&2
fi

# Detect package manager and install system packages
if command -v apt-get &>/dev/null; then
    sudo apt-get update -q
    sudo apt-get install -y xdotool wmctrl scrot x11-utils imagemagick python3 python3-venv python3-pip
elif command -v dnf &>/dev/null; then
    sudo dnf install -y xdotool wmctrl scrot xorg-x11-utils ImageMagick python3 python3-pip
elif command -v pacman &>/dev/null; then
    sudo pacman -S --noconfirm xdotool wmctrl scrot xorg-xwininfo imagemagick python python-pip
else
    echo "ERROR: Unsupported package manager. Install manually: xdotool wmctrl scrot" >&2
    exit 1
fi

echo ""
echo "Installation complete."
echo "Test with: xdotool getactivewindow"
echo "Make sure DISPLAY is set (e.g., export DISPLAY=:0)"


path: scroll.sh
content: #!/bin/bash
# scroll.sh — Scroll at the current mouse position or specified coordinates
set -e

# --- Defaults ---
DIRECTION=""
AMOUNT=3
X=""
Y=""
JSON_OUTPUT=false

# --- Parse arguments ---
while [[ $# -gt 0 ]]; do
    case "$1" in
        --direction)
            DIRECTION="$2"
            shift 2
            ;;
        --amount)
            AMOUNT="$2"
            shift 2
            ;;
        --x)
            X="$2"
            shift 2
            ;;
        --y)
            Y="$2"
            shift 2
            ;;
        --json)
            JSON_OUTPUT=true
            shift
            ;;
        -h|--help)
            echo "Usage: scroll.sh --direction up|down [--amount N] [--x X --y Y] [--json]"
            echo ""
            echo "Options:"
            echo "  --direction DIR   Scroll direction: up or down (required)"
            echo "  --amount N        Number of scroll ticks (default: 3)"
            echo "  --x X             Move mouse to X before scrolling"
            echo "  --y Y             Move mouse to Y before scrolling"
            echo "  --json            Output result as JSON"
            exit 0
            ;;
        *)
            echo "ERROR: Unknown argument: $1" >&2
            exit 1
            ;;
    esac
done

# --- Check DISPLAY ---
if [ -z "$DISPLAY" ]; then
    if $JSON_OUTPUT; then
        echo '{"success": false, "output": null, "error": "DISPLAY not set. Run: export DISPLAY=:0"}'
    else
        echo "ERROR: DISPLAY not set. Run: export DISPLAY=:0" >&2
    fi
    exit 1
fi

# --- Validate direction ---
if [ -z "$DIRECTION" ]; then
    MSG="--direction is required (up or down)"
    if $JSON_OUTPUT; then
        echo "{\"success\": false, \"output\": null, \"error\": \"$MSG\"}"
    else
        echo "ERROR: $MSG" >&2
    fi
    exit 1
fi

# Map direction to X11 button: 4 = scroll up, 5 = scroll down
case "$DIRECTION" in
    up)   SCROLL_BTN=4 ;;
    down) SCROLL_BTN=5 ;;
    *)
        MSG="Unknown direction: $DIRECTION (use up or down)"
        if $JSON_OUTPUT; then
            echo "{\"success\": false, \"output\": null, \"error\": \"$MSG\"}"
        else
            echo "ERROR: $MSG" >&2
        fi
        exit 1
        ;;
esac

# --- Move mouse if coordinates given ---
if [ -n "$X" ] && [ -n "$Y" ]; then
    xdotool mousemove "$X" "$Y"
    sleep 0.05
fi

# --- Scroll ---
xdotool click --repeat "$AMOUNT" --delay 50 "$SCROLL_BTN"

# --- Output ---
if $JSON_OUTPUT; then
    echo "{\"success\": true, \"output\": \"Scrolled $DIRECTION $AMOUNT ticks\", \"error\": null}"
else
    echo "Scrolled $DIRECTION $AMOUNT ticks"
fi


path: test.sh
content: #!/bin/bash
# test.sh — Basic end-to-end test for the linux-desktop skill
# Verifies that tools are installed and core functions work
set -e

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PASS=0
FAIL=0
SKIP=0

pass() {
    echo "  PASS: $1"
    PASS=$((PASS + 1))
}

fail() {
    echo "  FAIL: $1"
    FAIL=$((FAIL + 1))
}

skip() {
    echo "  SKIP: $1"
    SKIP=$((SKIP + 1))
}

echo "=== linux-desktop skill tests ==="
echo ""

# --- Test 1: Check required commands ---
echo "[1/5] Checking required commands..."
for cmd in xdotool wmctrl scrot xwininfo; do
    if command -v "$cmd" &>/dev/null; then
        pass "$cmd is installed"
    else
        fail "$cmd is NOT installed (run: bash install.sh)"
    fi
done

# --- Test 2: Check DISPLAY ---
echo ""
echo "[2/5] Checking DISPLAY variable..."
if [ -n "$DISPLAY" ]; then
    pass "DISPLAY is set to '$DISPLAY'"
else
    fail "DISPLAY is not set"
    echo ""
    echo "Cannot run remaining tests without DISPLAY. Set it with: export DISPLAY=:0"
    echo ""
    echo "Results: $PASS passed, $FAIL failed, $SKIP skipped"
    exit 1
fi

# --- Test 3: Take a screenshot ---
echo ""
echo "[3/5] Testing screenshot capture..."
SCREENSHOT=$("$SCRIPT_DIR/capture.sh" --json 2>/dev/null)
if echo "$SCREENSHOT" | python3 -c "import sys,json; d=json.load(sys.stdin); assert d['success']" 2>/dev/null; then
    SHOT_PATH=$(echo "$SCREENSHOT" | python3 -c "import sys,json; print(json.load(sys.stdin)['output'])")
    if [ -f "$SHOT_PATH" ]; then
        pass "Screenshot captured: $SHOT_PATH"
        # Clean up
        rm -f "$SHOT_PATH"
    else
        fail "Screenshot file not found at reported path"
    fi
else
    fail "capture.sh returned failure"
fi

# --- Test 4: List windows ---
echo ""
echo "[4/5] Testing window listing..."
WINDOWS=$("$SCRIPT_DIR/inspect.sh" 2>/dev/null)
if echo "$WINDOWS" | python3 -c "import sys,json; d=json.load(sys.stdin); assert d['success']" 2>/dev/null; then
    WIN_COUNT=$(echo "$WINDOWS" | python3 -c "import sys,json; print(len(json.load(sys.stdin)['windows']))")
    pass "Found $WIN_COUNT open windows"
else
    fail "inspect.sh returned failure"
fi

# --- Test 5: Script syntax checks ---
echo ""
echo "[5/5] Checking script syntax..."
for script in capture.sh inspect.sh click.sh type.sh hotkey.sh scroll.sh window.sh; do
    if bash -n "$SCRIPT_DIR/$script" 2>/dev/null; then
        pass "$script syntax OK"
    else
        fail "$script has syntax errors"
    fi
done

# --- Summary ---
echo ""
echo "=== Results ==="
echo "  $PASS passed, $FAIL failed, $SKIP skipped"

if [ "$FAIL" -gt 0 ]; then
    echo ""
    echo "Some tests failed. Run 'bash install.sh' to install missing dependencies."
    exit 1
else
    echo ""
    echo "All tests passed!"
    exit 0
fi


path: type.sh
content: #!/bin/bash
# type.sh — Type text into the currently focused window
set -e

# --- Defaults ---
TEXT=""
DELAY=""
WINDOW_NAME=""
JSON_OUTPUT=false

# --- Parse arguments ---
while [[ $# -gt 0 ]]; do
    case "$1" in
        --delay)
            DELAY="$2"
            shift 2
            ;;
        --window)
            WINDOW_NAME="$2"
            shift 2
            ;;
        --json)
            JSON_OUTPUT=true
            shift
            ;;
        -h|--help)
            echo "Usage: type.sh [--delay MS] [--window NAME] [--json] TEXT"
            echo ""
            echo "Options:"
            echo "  --delay MS      Delay in milliseconds between keystrokes"
            echo "  --window NAME   Focus window before typing"
            echo "  --json          Output result as JSON"
            echo "  TEXT            The text to type (last positional argument)"
            exit 0
            ;;
        -*)
            echo "ERROR: Unknown option: $1" >&2
            exit 1
            ;;
        *)
            TEXT="$1"
            shift
            ;;
    esac
done

# --- Check DISPLAY ---
if [ -z "$DISPLAY" ]; then
    if $JSON_OUTPUT; then
        echo '{"success": false, "output": null, "error": "DISPLAY not set. Run: export DISPLAY=:0"}'
    else
        echo "ERROR: DISPLAY not set. Run: export DISPLAY=:0" >&2
    fi
    exit 1
fi

# --- Validate text ---
if [ -z "$TEXT" ]; then
    MSG="No text provided"
    if $JSON_OUTPUT; then
        echo "{\"success\": false, \"output\": null, \"error\": \"$MSG\"}"
    else
        echo "ERROR: $MSG" >&2
    fi
    exit 1
fi

# --- Focus window if specified ---
if [ -n "$WINDOW_NAME" ]; then
    WIN_ID=$(xdotool search --name "$WINDOW_NAME" | head -1)
    if [ -z "$WIN_ID" ]; then
        MSG="Window not found: $WINDOW_NAME"
        if $JSON_OUTPUT; then
            echo "{\"success\": false, \"output\": null, \"error\": \"$MSG\"}"
        else
            echo "ERROR: $MSG" >&2
        fi
        exit 1
    fi
    xdotool windowfocus --sync "$WIN_ID"
    sleep 0.2
fi

# --- Type text ---
CMD="xdotool type --clearmodifiers"
if [ -n "$DELAY" ]; then
    CMD="$CMD --delay $DELAY"
fi

$CMD -- "$TEXT"

# --- Output ---
CHAR_COUNT=${#TEXT}
if $JSON_OUTPUT; then
    echo "{\"success\": true, \"output\": \"Typed $CHAR_COUNT characters\", \"error\": null}"
else
    echo "Typed $CHAR_COUNT characters"
fi


path: window.sh
content: #!/bin/bash
# window.sh — Window management: focus, minimize, maximize, close, move, resize, list
set -e

# --- Defaults ---
ACTION=""
WINDOW_NAME=""
X=""
Y=""
WIDTH=""
HEIGHT=""
JSON_OUTPUT=false

# --- Parse arguments ---
while [[ $# -gt 0 ]]; do
    case "$1" in
        --action)
            ACTION="$2"
            shift 2
            ;;
        --window)
            WINDOW_NAME="$2"
            shift 2
            ;;
        --x)
            X="$2"
            shift 2
            ;;
        --y)
            Y="$2"
            shift 2
            ;;
        --width)
            WIDTH="$2"
            shift 2
            ;;
        --height)
            HEIGHT="$2"
            shift 2
            ;;
        --json)
            JSON_OUTPUT=true
            shift
            ;;
        -h|--help)
            echo "Usage: window.sh --action ACTION --window NAME [options] [--json]"
            echo ""
            echo "Actions:"
            echo "  focus      Focus/raise window"
            echo "  minimize   Minimize window"
            echo "  maximize   Maximize window"
            echo "  close      Close window"
            echo "  move       Move window (requires --x and --y)"
            echo "  resize     Resize window (requires --width and --height)"
            echo "  list       List all windows (alias for inspect.sh)"
            echo ""
            echo "Options:"
            echo "  --window NAME    Window name to match"
            echo "  --x X            X position (for move)"
            echo "  --y Y            Y position (for move)"
            echo "  --width W        Width (for resize)"
            echo "  --height H       Height (for resize)"
            echo "  --json           Output result as JSON"
            exit 0
            ;;
        *)
            echo "ERROR: Unknown argument: $1" >&2
            exit 1
            ;;
    esac
done

# --- Check DISPLAY ---
if [ -z "$DISPLAY" ]; then
    if $JSON_OUTPUT; then
        echo '{"success": false, "output": null, "error": "DISPLAY not set. Run: export DISPLAY=:0"}'
    else
        echo "ERROR: DISPLAY not set. Run: export DISPLAY=:0" >&2
    fi
    exit 1
fi

# --- Validate action ---
if [ -z "$ACTION" ]; then
    MSG="--action is required"
    if $JSON_OUTPUT; then
        echo "{\"success\": false, \"output\": null, \"error\": \"$MSG\"}"
    else
        echo "ERROR: $MSG" >&2
    fi
    exit 1
fi

# --- Handle list action (delegate to inspect.sh) ---
if [ "$ACTION" = "list" ]; then
    SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
    if $JSON_OUTPUT; then
        exec bash "$SCRIPT_DIR/inspect.sh" --json
    else
        exec bash "$SCRIPT_DIR/inspect.sh"
    fi
fi

# --- Validate window name for all other actions ---
if [ -z "$WINDOW_NAME" ]; then
    MSG="--window is required for action: $ACTION"
    if $JSON_OUTPUT; then
        echo "{\"success\": false, \"output\": null, \"error\": \"$MSG\"}"
    else
        echo "ERROR: $MSG" >&2
    fi
    exit 1
fi

# --- Perform action ---
case "$ACTION" in
    focus)
        # Try wmctrl first (fuzzy match), fall back to xdotool
        if command -v wmctrl &>/dev/null; then
            wmctrl -a "$WINDOW_NAME"
        else
            WIN_ID=$(xdotool search --name "$WINDOW_NAME" | head -1)
            if [ -z "$WIN_ID" ]; then
                MSG="Window not found: $WINDOW_NAME"
                if $JSON_OUTPUT; then
                    echo "{\"success\": false, \"output\": null, \"error\": \"$MSG\"}"
                else
                    echo "ERROR: $MSG" >&2
                fi
                exit 1
            fi
            xdotool windowactivate "$WIN_ID"
        fi
        RESULT="Focused window: $WINDOW_NAME"
        ;;

    minimize)
        WIN_ID=$(xdotool search --name "$WINDOW_NAME" | head -1)
        if [ -z "$WIN_ID" ]; then
            MSG="Window not found: $WINDOW_NAME"
            if $JSON_OUTPUT; then
                echo "{\"success\": false, \"output\": null, \"error\": \"$MSG\"}"
            else
                echo "ERROR: $MSG" >&2
            fi
            exit 1
        fi
        xdotool windowminimize "$WIN_ID"
        RESULT="Minimized window: $WINDOW_NAME"
        ;;

    maximize)
        if ! command -v wmctrl &>/dev/null; then
            MSG="wmctrl required for maximize. Run: bash install.sh"
            if $JSON_OUTPUT; then
                echo "{\"success\": false, \"output\": null, \"error\": \"$MSG\"}"
            else
                echo "ERROR: $MSG" >&2
            fi
            exit 1
        fi
        wmctrl -r "$WINDOW_NAME" -b add,maximized_vert,maximized_horz
        RESULT="Maximized window: $WINDOW_NAME"
        ;;

    close)
        if command -v wmctrl &>/dev/null; then
            wmctrl -c "$WINDOW_NAME"
        else
            WIN_ID=$(xdotool search --name "$WINDOW_NAME" | head -1)
            if [ -z "$WIN_ID" ]; then
                MSG="Window not found: $WINDOW_NAME"
                if $JSON_OUTPUT; then
                    echo "{\"success\": false, \"output\": null, \"error\": \"$MSG\"}"
                else
                    echo "ERROR: $MSG" >&2
                fi
                exit 1
            fi
            xdotool windowclose "$WIN_ID"
        fi
        RESULT="Closed window: $WINDOW_NAME"
        ;;

    move)
        if [ -z "$X" ] || [ -z "$Y" ]; then
            MSG="--x and --y are required for move"
            if $JSON_OUTPUT; then
                echo "{\"success\": false, \"output\": null, \"error\": \"$MSG\"}"
            else
                echo "ERROR: $MSG" >&2
            fi
            exit 1
        fi
        if command -v wmctrl &>/dev/null; then
            wmctrl -r "$WINDOW_NAME" -e "0,$X,$Y,-1,-1"
        else
            WIN_ID=$(xdotool search --name "$WINDOW_NAME" | head -1)
            if [ -z "$WIN_ID" ]; then
                MSG="Window not found: $WINDOW_NAME"
                if $JSON_OUTPUT; then
                    echo "{\"success\": false, \"output\": null, \"error\": \"$MSG\"}"
                else
                    echo "ERROR: $MSG" >&2
                fi
                exit 1
            fi
            xdotool windowmove "$WIN_ID" "$X" "$Y"
        fi
        RESULT="Moved window '$WINDOW_NAME' to ($X, $Y)"
        ;;

    resize)
        if [ -z "$WIDTH" ] || [ -z "$HEIGHT" ]; then
            MSG="--width and --height are required for resize"
            if $JSON_OUTPUT; then
                echo "{\"success\": false, \"output\": null, \"error\": \"$MSG\"}"
            else
                echo "ERROR: $MSG" >&2
            fi
            exit 1
        fi
        if command -v wmctrl &>/dev/null; then
            wmctrl -r "$WINDOW_NAME" -e "0,-1,-1,$WIDTH,$HEIGHT"
        else
            WIN_ID=$(xdotool search --name "$WINDOW_NAME" | head -1)
            if [ -z "$WIN_ID" ]; then
                MSG="Window not found: $WINDOW_NAME"
                if $JSON_OUTPUT; then
                    echo "{\"success\": false, \"output\": null, \"error\": \"$MSG\"}"
                else
                    echo "ERROR: $MSG" >&2
                fi
                exit 1
            fi
            xdotool windowsize "$WIN_ID" "$WIDTH" "$HEIGHT"
        fi
        RESULT="Resized window '$WINDOW_NAME' to ${WIDTH}x${HEIGHT}"
        ;;

    *)
        MSG="Unknown action: $ACTION (use focus, minimize, maximize, close, move, resize, list)"
        if $JSON_OUTPUT; then
            echo "{\"success\": false, \"output\": null, \"error\": \"$MSG\"}"
        else
            echo "ERROR: $MSG" >&2
        fi
        exit 1
        ;;
esac

# --- Output ---
if $JSON_OUTPUT; then
    echo "{\"success\": true, \"output\": \"$RESULT\", \"error\": null}"
else
    echo "$RESULT"
fi


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