Skill: udiedrichsen/device-assistant (version 1.0.0)

--- name: device-assistant version: 1.0.0 description: "Personal device and appliance manager with error code lookup and troubleshooting. Tracks all your devices (appliances, electronics, software) with model numbers, manuals, and warranty info. When something breaks, tell it the error code and get instant solutions. Use when: device shows error, need manual, warranty check, adding new device, maintenance reminder. Triggers: /device, /geräte, 'mein Geschirrspüler', 'Fehler E24', 'Fehlermeldung', device problems, appliance issues." author: clawdbot license: MIT metadata: clawdbot: emoji: "🔧" triggers: ["/device", "/geräte"] requires: bins: ["jq", "curl"] tags: ["devices", "appliances", "troubleshooting", "maintenance", "home", "warranty"] --- # Device Assistant 🔧 Personal device manager with error code lookup, troubleshooting, and maintenance tracking. ## Features - **Device Registry**: Track all devices with model, serial, purchase info - **Error Lookup**: Instant error code explanations - **Troubleshooting**: Step-by-step solutions - **Manual Links**: Quick access to documentation - **Warranty Tracking**: Know when warranties expire - **Maintenance Reminders**: Filter changes, updates, etc. ## Commands | Command | Action | |---------|--------| | `/device` | List all devices or show status | | `/device add` | Add a new device (interactive) | | `/device list [category]` | List devices by category | | `/device info <name>` | Show device details | | `/device error <name> <code>` | Lookup error code | | `/device help <name> <problem>` | Troubleshoot a problem | | `/device manual <name>` | Get manual/documentation | | `/device warranty` | Show warranty status | | `/device maintenance` | Show maintenance schedule | | `/device remove <name>` | Remove a device | ## Natural Language The skill understands natural queries: - *"Mein Geschirrspüler zeigt E24"* - *"Waschmaschine macht komische Geräusche"* - *"Wo ist die Anleitung für den Thermomix?"* - *"Wann läuft die Garantie vom TV aus?"* ## Device Categories | Category | Examples | |----------|----------| | `kitchen` | Geschirrspüler, Kühlschrank, Backofen, Thermomix | | `laundry` | Waschmaschine, Trockner | | `electronics` | TV, Router, NAS, Computer | | `climate` | Heizung, Klimaanlage, Luftreiniger | | `smart-home` | Hue, Homematic, Sensoren | | `software` | Apps, Betriebssysteme, Lizenzen | | `other` | Alles andere | ## Handler Commands ```bash handler.sh status $WORKSPACE # Overview handler.sh list [category] $WORKSPACE # List devices handler.sh add <json> $WORKSPACE # Add device handler.sh info <device-id> $WORKSPACE # Device details handler.sh error <device-id> <code> $WORKSPACE # Error lookup handler.sh troubleshoot <device-id> <problem> $WS # Get help handler.sh manual <device-id> $WORKSPACE # Manual link handler.sh warranty $WORKSPACE # Warranty overview handler.sh maintenance $WORKSPACE # Maintenance due handler.sh update <device-id> <json> $WORKSPACE # Update device handler.sh remove <device-id> $WORKSPACE # Remove device handler.sh search <query> $WORKSPACE # Search devices handler.sh log <device-id> <note> $WORKSPACE # Add maintenance log ``` ## Data Structure ### Device Entry ```json { "id": "dishwasher-1", "name": "Geschirrspüler", "nickname": "Spüli", "category": "kitchen", "manufacturer": "Siemens", "model": "SN658X06TE", "serialNumber": "ABC123456", "purchaseDate": "2022-03-15", "purchasePrice": 899, "warranty": { "expires": "2025-03-15", "type": "manufacturer", "notes": "3 Jahre Siemens" }, "manualUrl": "https://...", "supportUrl": "https://...", "location": "Küche", "notes": "Salz alle 2 Monate nachfüllen", "maintenance": [ {"task": "Filter reinigen", "interval": "monthly", "lastDone": "2026-01-01"} ], "errorHistory": [], "addedAt": "2024-01-01" } ``` ## Error Code Lookup The skill searches for error codes using: 1. **Built-in database** for common manufacturers 2. **Web search** for specific model + error code 3. **Manual extraction** if manual URL is stored ### Common Error Code Patterns | Manufacturer | Pattern | Example | |--------------|---------|---------| | Siemens/Bosch | E + number | E24, E15 | | Miele | F + number | F11, F53 | | AEG | E + number | E10, E20 | | Samsung | Letters + numbers | UE, OE, LE | | LG | Letters | OE, IE, PE | ## Example Session ``` User: Ich habe einen neuen Geschirrspüler Bot: 🔧 Neues Gerät hinzufügen: Welche Infos hast du? • Hersteller? • Modell? • Seriennummer? (optional) • Kaufdatum? User: Siemens SN658X06TE, gekauft am 15.3.2022 Bot: ✅ Gespeichert! 📦 Geschirrspüler (Siemens SN658X06TE) 📅 Gekauft: 15.03.2022 🛡️ Garantie: bis 15.03.2025 (ABGELAUFEN) 📄 Anleitung: [Link gefunden] Spitzname vergeben? (z.B. "Spüli") ``` ``` User: Mein Geschirrspüler zeigt E24 Bot: 🔧 Siemens SN658X06TE — Fehler E24 ⚠️ Bedeutung: Abpumpproblem Das Wasser wird nicht richtig abgepumpt. 🔨 Lösung: 1. Gerät ausschalten 2. Sieb am Boden prüfen und reinigen 3. Abflussschlauch auf Knicke prüfen 4. Siphon-Anschluss kontrollieren 5. Gerät neu starten 📄 Details: Anleitung S. 45 [Problem gelöst] [Weitere Hilfe] ``` ## Data Files Stored in `$WORKSPACE/memory/device-assistant/`: | File | Purpose | |------|---------| | `devices.json` | All registered devices | | `error-history.json` | Past errors and solutions | | `maintenance-log.json` | Maintenance history | | `error-codes/` | Cached error code info | ## Warranty Alerts The skill can remind you before warranties expire: ``` ⚠️ Garantie-Warnung: Diese Geräte laufen bald ab: • TV Samsung (noch 30 Tage) • Waschmaschine (noch 45 Tage) Tipp: Jetzt prüfen ob alles funktioniert! ``` ## Requirements - `jq` (JSON processing) - `curl` (web lookups) - Internet for error code search

path: scripts/handler.sh
content: #!/usr/bin/env bash
# Device Assistant Handler
# Personal device manager with error lookup and troubleshooting
# Usage: handler.sh <command> [args...] <workspace>

set -euo pipefail

command -v jq >/dev/null 2>&1 || { echo '{"status":"error","message":"jq required"}'; exit 1; }
command -v curl >/dev/null 2>&1 || { echo '{"status":"error","message":"curl required"}'; exit 1; }

CMD="${1:-status}"
shift || true

# Get workspace (always last argument)
WORKSPACE="${!#:-$(pwd)}"
DATA_DIR="$WORKSPACE/memory/device-assistant"
CACHE_DIR="$DATA_DIR/cache"

mkdir -p "$DATA_DIR" "$CACHE_DIR"

# Files
DEVICES_FILE="$DATA_DIR/devices.json"
HISTORY_FILE="$DATA_DIR/error-history.json"
MAINTENANCE_FILE="$DATA_DIR/maintenance-log.json"

# Initialize files
[[ -f "$DEVICES_FILE" ]] || echo '{"devices":[]}' > "$DEVICES_FILE"
[[ -f "$HISTORY_FILE" ]] || echo '{"entries":[]}' > "$HISTORY_FILE"
[[ -f "$MAINTENANCE_FILE" ]] || echo '{"entries":[]}' > "$MAINTENANCE_FILE"

# ============================================
# Helper Functions
# ============================================

generate_id() {
    local name="$1"
    local base
    base=$(echo "$name" | tr '[:upper:]' '[:lower:]' | tr ' äöü' '-aou' | tr -cd 'a-z0-9-')
    local count
    count=$(jq --arg base "$base" '[.devices[] | select(.id | startswith($base))] | length' "$DEVICES_FILE")
    if [[ "$count" -gt 0 ]]; then
        echo "${base}-$((count + 1))"
    else
        echo "$base"
    fi
}

get_device() {
    local identifier="$1"
    # Search by id, name, or nickname (case-insensitive)
    jq -c --arg id "$identifier" '[.devices[] | select(.id == $id or (.name | ascii_downcase) == ($id | ascii_downcase) or (.nickname // "" | ascii_downcase) == ($id | ascii_downcase))][0] // empty' "$DEVICES_FILE"
}

get_device_id() {
    local identifier="$1"
    local device
    device=$(get_device "$identifier")
    echo "$device" | jq -r '.id // empty'
}

# Search web for error code
search_error_code() {
    local manufacturer="$1"
    local model="$2"
    local error_code="$3"
    
    # Build search query
    local query="${manufacturer} ${model} Fehler ${error_code} Lösung"
    local encoded_query
    encoded_query=$(echo -n "$query" | jq -sRr @uri)
    
    # Check cache first
    local cache_key
    cache_key=$(echo -n "${manufacturer}-${model}-${error_code}" | tr ' ' '-' | tr '[:upper:]' '[:lower:]')
    local cache_file="$CACHE_DIR/error-${cache_key}.json"
    
    if [[ -f "$cache_file" ]]; then
        local cache_age=$(($(date +%s) - $(stat -c %Y "$cache_file" 2>/dev/null || echo 0)))
        if [[ $cache_age -lt 604800 ]]; then  # 1 week cache
            cat "$cache_file"
            return 0
        fi
    fi
    
    # Return structured response with search suggestion
    local result
    result=$(jq -n \
        --arg manufacturer "$manufacturer" \
        --arg model "$model" \
        --arg code "$error_code" \
        --arg query "$query" \
        '{
            manufacturer: $manufacturer,
            model: $model,
            errorCode: $code,
            searchQuery: $query,
            searchUrl: ("https://www.google.com/search?q=" + ($query | @uri))
        }')
    
    echo "$result" > "$cache_file"
    echo "$result"
}

# Get known error codes for common manufacturers
get_known_error() {
    local manufacturer="${1,,}"
    local code="${2^^}"
    
    case "$manufacturer" in
        siemens|bosch|neff|constructa)
            case "$code" in
                E15) echo '{"meaning":"Wasserschutz aktiv","description":"Wasser in der Bodenwanne erkannt.","solution":["Gerät ausschalten","Wasser aus Bodenwanne entfernen","Auf Undichtigkeit prüfen","Gerät trocknen lassen"]}' ;;
                E24) echo '{"meaning":"Abpumpproblem","description":"Wasser wird nicht richtig abgepumpt.","solution":["Sieb am Boden reinigen","Abflussschlauch prüfen","Siphon kontrollieren","Pumpe auf Fremdkörper prüfen"]}' ;;
                E25) echo '{"meaning":"Ablaufpumpe blockiert","description":"Pumpe kann nicht arbeiten.","solution":["Pumpendeckel öffnen","Fremdkörper entfernen","Flügelrad prüfen"]}' ;;
                E22) echo '{"meaning":"Filter verstopft","description":"Siebe sind verschmutzt.","solution":["Alle Siebe entnehmen","Unter Wasser reinigen","Siebe wieder einsetzen"]}' ;;
                E01|E1) echo '{"meaning":"Heizungsfehler","description":"Wasser wird nicht erhitzt.","solution":["Service kontaktieren","Heizelement defekt"]}' ;;
                E09) echo '{"meaning":"Heizfehler","description":"Problem mit der Heizung.","solution":["Service kontaktieren"]}' ;;
                *) echo '{}' ;;
            esac
            ;;
        miele)
            case "$code" in
                F11) echo '{"meaning":"Abpumpfehler","description":"Wasser läuft nicht ab.","solution":["Ablauf prüfen","Filter reinigen","Pumpe prüfen"]}' ;;
                F12|F13) echo '{"meaning":"Zulaufproblem","description":"Wasser läuft nicht zu.","solution":["Wasserhahn prüfen","Zulaufschlauch prüfen","Sieb im Zulauf reinigen"]}' ;;
                F53) echo '{"meaning":"Motorfehler","description":"Motor funktioniert nicht korrekt.","solution":["Service kontaktieren"]}' ;;
                *) echo '{}' ;;
            esac
            ;;
        samsung)
            case "$code" in
                UE|UB|E4) echo '{"meaning":"Unwucht","description":"Wäsche ungleichmäßig verteilt.","solution":["Wäsche gleichmäßiger verteilen","Nicht zu viel/wenig beladen","Gerät ausrichten"]}' ;;
                OE|OF|E3) echo '{"meaning":"Überlauf","description":"Zu viel Wasser im Gerät.","solution":["Wasserzulauf schließen","Ablauf prüfen","Service kontaktieren"]}' ;;
                LE|LE1|E9) echo '{"meaning":"Wasserleck","description":"Leck erkannt.","solution":["Gerät sofort ausschalten","Auf Undichtigkeit prüfen","Service kontaktieren"]}' ;;
                HE|HE1|HE2) echo '{"meaning":"Heizungsfehler","description":"Wasser wird nicht erhitzt.","solution":["Service kontaktieren"]}' ;;
                *) echo '{}' ;;
            esac
            ;;
        lg)
            case "$code" in
                OE) echo '{"meaning":"Abflussfehler","description":"Wasser läuft nicht ab.","solution":["Ablaufschlauch prüfen","Filter reinigen","Pumpe prüfen"]}' ;;
                IE) echo '{"meaning":"Zulaufproblem","description":"Kein Wasserzulauf.","solution":["Wasserhahn öffnen","Zulaufschlauch prüfen"]}' ;;
                UE) echo '{"meaning":"Unwucht","description":"Ungleichmäßige Beladung.","solution":["Wäsche verteilen","Gerät ausrichten"]}' ;;
                PE) echo '{"meaning":"Drucksensorfehler","description":"Wasserstandssensor defekt.","solution":["Service kontaktieren"]}' ;;
                *) echo '{}' ;;
            esac
            ;;
        aeg|electrolux)
            case "$code" in
                E10) echo '{"meaning":"Zulaufproblem","description":"Wasser läuft nicht zu.","solution":["Wasserhahn prüfen","Zulaufschlauch prüfen","Sieb reinigen"]}' ;;
                E20) echo '{"meaning":"Abpumpfehler","description":"Wasser läuft nicht ab.","solution":["Filter reinigen","Ablauf prüfen","Pumpe prüfen"]}' ;;
                E40) echo '{"meaning":"Türproblem","description":"Tür nicht richtig geschlossen.","solution":["Tür schließen","Türschloss prüfen"]}' ;;
                *) echo '{}' ;;
            esac
            ;;
        *)
            echo '{}'
            ;;
    esac
}

# ============================================
# Commands
# ============================================

case "$CMD" in
    # ----------------------------------------
    # STATUS
    # ----------------------------------------
    status)
        device_count=$(jq '.devices | length' "$DEVICES_FILE")
        
        # Check for expiring warranties (next 90 days)
        today=$(date +%s)
        ninety_days=$((today + 7776000))
        expiring=$(jq --argjson today "$today" --argjson future "$ninety_days" '[.devices[] | select(.warranty.expires != null) | select((.warranty.expires | strptime("%Y-%m-%d") | mktime) > $today and (.warranty.expires | strptime("%Y-%m-%d") | mktime) < $future)] | length' "$DEVICES_FILE" 2>/dev/null || echo "0")
        
        # Check for due maintenance
        maintenance_due=$(jq '[.devices[] | .maintenance[]? | select(.lastDone != null)] | length' "$DEVICES_FILE" 2>/dev/null || echo "0")
        
        # Categories summary
        categories=$(jq '[.devices[].category] | group_by(.) | map({category: .[0], count: length})' "$DEVICES_FILE")
        
        jq -n \
            --argjson count "$device_count" \
            --argjson expiring "$expiring" \
            --argjson maintenance "$maintenance_due" \
            --argjson categories "$categories" \
            '{
                status: "ok",
                deviceCount: $count,
                warningWarrantyExpiring: $expiring,
                maintenanceTracked: $maintenance,
                byCategory: $categories,
                commands: {
                    "/device list": "Alle Geräte anzeigen",
                    "/device add": "Neues Gerät hinzufügen",
                    "/device info <name>": "Geräte-Details",
                    "/device error <name> <code>": "Fehlercode nachschlagen",
                    "/device help <name> <problem>": "Problemlösung",
                    "/device warranty": "Garantie-Übersicht",
                    "/device maintenance": "Wartungsplan"
                }
            }'
        ;;
        
    # ----------------------------------------
    # LIST
    # ----------------------------------------
    list)
        category="${1:-}"
        
        if [[ -n "$category" && "$category" != "$WORKSPACE" ]]; then
            devices=$(jq --arg cat "$category" '[.devices[] | select(.category == $cat)]' "$DEVICES_FILE")
        else
            devices=$(jq '.devices' "$DEVICES_FILE")
        fi
        
        echo "$devices" | jq '{
            status: "ok",
            count: length,
            devices: [.[] | {
                id: .id,
                name: .name,
                nickname: .nickname,
                manufacturer: .manufacturer,
                model: .model,
                category: .category,
                location: .location
            }]
        }'
        ;;
        
    # ----------------------------------------
    # ADD
    # ----------------------------------------
    add)
        json_data="${1:-}"
        
        if [[ -z "$json_data" || "$json_data" == "$WORKSPACE" ]]; then
            # Interactive mode - return prompt
            echo '{"status":"ok","action":"prompt","message":"Gerätedaten benötigt","required":["name","manufacturer","model"],"optional":["serialNumber","purchaseDate","purchasePrice","warranty","manualUrl","location","category","nickname"]}'
            exit 0
        fi
        
        # Parse and validate JSON
        if ! echo "$json_data" | jq -e . >/dev/null 2>&1; then
            echo '{"status":"error","message":"Invalid JSON"}'
            exit 1
        fi
        
        name=$(echo "$json_data" | jq -r '.name // empty')
        [[ -z "$name" ]] && { echo '{"status":"error","message":"Name required"}'; exit 1; }
        
        # Generate ID
        device_id=$(generate_id "$name")
        
        # Build device entry
        device=$(echo "$json_data" | jq --arg id "$device_id" --arg added "$(date -Iseconds)" '
            {
                id: $id,
                name: .name,
                nickname: (.nickname // null),
                category: (.category // "other"),
                manufacturer: (.manufacturer // "Unknown"),
                model: (.model // "Unknown"),
                serialNumber: (.serialNumber // null),
                purchaseDate: (.purchaseDate // null),
                purchasePrice: (.purchasePrice // null),
                warranty: (.warranty // null),
                manualUrl: (.manualUrl // null),
                supportUrl: (.supportUrl // null),
                location: (.location // null),
                notes: (.notes // null),
                maintenance: (.maintenance // []),
                errorHistory: [],
                addedAt: $added
            }
        ')
        
        # Add to devices file
        jq --argjson device "$device" '.devices += [$device]' "$DEVICES_FILE" > "$DEVICES_FILE.tmp"
        mv "$DEVICES_FILE.tmp" "$DEVICES_FILE"
        
        echo "$device" | jq '{status: "ok", message: "Gerät hinzugefügt", device: .}'
        ;;
        
    # ----------------------------------------
    # INFO
    # ----------------------------------------
    info)
        identifier="${1:-}"
        [[ -z "$identifier" || "$identifier" == "$WORKSPACE" ]] && { echo '{"status":"error","message":"Device name/id required"}'; exit 1; }
        
        device=$(get_device "$identifier")
        [[ -z "$device" || "$device" == "null" ]] && { echo '{"status":"error","message":"Device not found: '"$identifier"'"}'; exit 1; }
        
        # Calculate warranty status
        warranty_status="unknown"
        warranty_expires=$(echo "$device" | jq -r '.warranty.expires // empty')
        if [[ -n "$warranty_expires" && "$warranty_expires" != "null" ]]; then
            expires_ts=$(date -d "$warranty_expires" +%s 2>/dev/null || echo 0)
            today_ts=$(date +%s)
            if [[ $expires_ts -gt 0 ]]; then
                if [[ $expires_ts -gt $today_ts ]]; then
                    days_left=$(( (expires_ts - today_ts) / 86400 ))
                    warranty_status="aktiv (noch $days_left Tage)"
                else
                    warranty_status="ABGELAUFEN"
                fi
            fi
        fi
        
        echo "$device" | jq --arg ws "$warranty_status" '. + {status: "ok", warrantyStatus: $ws}'
        ;;
        
    # ----------------------------------------
    # ERROR - Lookup error code
    # ----------------------------------------
    error)
        identifier="${1:-}"
        error_code="${2:-}"
        
        [[ -z "$identifier" || "$identifier" == "$WORKSPACE" ]] && { echo '{"status":"error","message":"Device name required"}'; exit 1; }
        [[ -z "$error_code" || "$error_code" == "$WORKSPACE" ]] && { echo '{"status":"error","message":"Error code required"}'; exit 1; }
        
        device=$(get_device "$identifier")
        [[ -z "$device" ]] && { echo '{"status":"error","message":"Device not found: '"$identifier"'"}'; exit 1; }
        
        manufacturer=$(echo "$device" | jq -r '.manufacturer')
        model=$(echo "$device" | jq -r '.model')
        device_name=$(echo "$device" | jq -r '.name')
        device_id=$(echo "$device" | jq -r '.id')
        
        # Try built-in error codes first
        known_error=$(get_known_error "$manufacturer" "$error_code")
        
        if [[ "$known_error" != "{}" ]]; then
            # Found in built-in database
            result=$(echo "$known_error" | jq --arg mfr "$manufacturer" --arg mdl "$model" --arg code "$error_code" --arg name "$device_name" '
                {
                    status: "ok",
                    source: "builtin",
                    device: $name,
                    manufacturer: $mfr,
                    model: $mdl,
                    errorCode: $code,
                    meaning: .meaning,
                    description: .description,
                    solution: .solution
                }
            ')
        else
            # Search online
            search_result=$(search_error_code "$manufacturer" "$model" "$error_code")
            result=$(echo "$search_result" | jq --arg name "$device_name" '
                {
                    status: "ok",
                    source: "search",
                    device: $name,
                    manufacturer: .manufacturer,
                    model: .model,
                    errorCode: .errorCode,
                    meaning: "Unbekannt",
                    description: "Fehlercode nicht in Datenbank",
                    searchUrl: .searchUrl,
                    suggestion: "Bitte online suchen oder Handbuch prüfen"
                }
            ')
        fi
        
        # Log error to history
        jq --arg device "$device_id" --arg code "$error_code" --arg ts "$(date -Iseconds)" \
            '.entries = [{timestamp: $ts, deviceId: $device, errorCode: $code}] + .entries[:99]' \
            "$HISTORY_FILE" > "$HISTORY_FILE.tmp"
        mv "$HISTORY_FILE.tmp" "$HISTORY_FILE"
        
        echo "$result"
        ;;
        
    # ----------------------------------------
    # TROUBLESHOOT - General problem help
    # ----------------------------------------
    troubleshoot)
        identifier="${1:-}"
        problem="${2:-}"
        
        [[ -z "$identifier" ]] && { echo '{"status":"error","message":"Device name required"}'; exit 1; }
        
        device=$(get_device "$identifier")
        [[ -z "$device" ]] && { echo '{"status":"error","message":"Device not found"}'; exit 1; }
        
        manufacturer=$(echo "$device" | jq -r '.manufacturer')
        model=$(echo "$device" | jq -r '.model')
        device_name=$(echo "$device" | jq -r '.name')
        manual_url=$(echo "$device" | jq -r '.manualUrl // empty')
        support_url=$(echo "$device" | jq -r '.supportUrl // empty')
        
        # Build search query for the problem
        query="${manufacturer} ${model} ${problem} Lösung"
        search_url="https://www.google.com/search?q=$(echo -n "$query" | jq -sRr @uri)"
        
        jq -n \
            --arg device "$device_name" \
            --arg mfr "$manufacturer" \
            --arg mdl "$model" \
            --arg problem "$problem" \
            --arg searchUrl "$search_url" \
            --arg manualUrl "$manual_url" \
            --arg supportUrl "$support_url" \
            '{
                status: "ok",
                device: $device,
                manufacturer: $mfr,
                model: $mdl,
                problem: $problem,
                resources: {
                    searchUrl: $searchUrl,
                    manualUrl: (if $manualUrl != "" then $manualUrl else null end),
                    supportUrl: (if $supportUrl != "" then $supportUrl else null end)
                },
                generalTips: [
                    "Gerät aus- und wieder einschalten",
                    "Bedienungsanleitung prüfen",
                    "Hersteller-Support kontaktieren"
                ]
            }'
        ;;
        
    # ----------------------------------------
    # MANUAL
    # ----------------------------------------
    manual)
        identifier="${1:-}"
        [[ -z "$identifier" ]] && { echo '{"status":"error","message":"Device name required"}'; exit 1; }
        
        device=$(get_device "$identifier")
        [[ -z "$device" ]] && { echo '{"status":"error","message":"Device not found"}'; exit 1; }
        
        manufacturer=$(echo "$device" | jq -r '.manufacturer')
        model=$(echo "$device" | jq -r '.model')
        manual_url=$(echo "$device" | jq -r '.manualUrl // empty')
        
        # If no manual URL stored, provide search link
        if [[ -z "$manual_url" ]]; then
            query="${manufacturer} ${model} Bedienungsanleitung PDF"
            search_url="https://www.google.com/search?q=$(echo -n "$query" | jq -sRr @uri)"
            manual_url="$search_url"
            stored=false
        else
            stored=true
        fi
        
        jq -n \
            --arg device "$(echo "$device" | jq -r '.name')" \
            --arg mfr "$manufacturer" \
            --arg mdl "$model" \
            --arg url "$manual_url" \
            --argjson stored "$stored" \
            '{
                status: "ok",
                device: $device,
                manufacturer: $mfr,
                model: $mdl,
                manualUrl: $url,
                isStored: $stored
            }'
        ;;
        
    # ----------------------------------------
    # WARRANTY
    # ----------------------------------------
    warranty)
        today=$(date +%s)
        
        warranties=$(jq --argjson today "$today" '
            [.devices[] | select(.warranty.expires != null) | 
                {
                    name: .name,
                    manufacturer: .manufacturer,
                    model: .model,
                    expires: .warranty.expires,
                    expiresTs: (.warranty.expires | strptime("%Y-%m-%d") | mktime),
                    daysLeft: (((.warranty.expires | strptime("%Y-%m-%d") | mktime) - $today) / 86400 | floor)
                }
            ] | sort_by(.expiresTs)
        ' "$DEVICES_FILE" 2>/dev/null || echo "[]")
        
        active=$(echo "$warranties" | jq '[.[] | select(.daysLeft > 0)]')
        expired=$(echo "$warranties" | jq '[.[] | select(.daysLeft <= 0)]')
        expiring_soon=$(echo "$warranties" | jq '[.[] | select(.daysLeft > 0 and .daysLeft <= 90)]')
        
        jq -n \
            --argjson active "$active" \
            --argjson expired "$expired" \
            --argjson soon "$expiring_soon" \
            '{
                status: "ok",
                active: ($active | length),
                expired: ($expired | length),
                expiringSoon: $soon,
                allWarranties: ($active + $expired)
            }'
        ;;
        
    # ----------------------------------------
    # MAINTENANCE
    # ----------------------------------------
    maintenance)
        # Get all devices with maintenance tasks
        devices_with_maintenance=$(jq '[.devices[] | select(.maintenance | length > 0) | {
            id: .id,
            name: .name,
            tasks: .maintenance
        }]' "$DEVICES_FILE")
        
        jq -n --argjson devices "$devices_with_maintenance" '{
            status: "ok",
            devicesWithMaintenance: ($devices | length),
            devices: $devices
        }'
        ;;
        
    # ----------------------------------------
    # UPDATE
    # ----------------------------------------
    update)
        identifier="${1:-}"
        json_data="${2:-}"
        
        [[ -z "$identifier" ]] && { echo '{"status":"error","message":"Device name required"}'; exit 1; }
        [[ -z "$json_data" || "$json_data" == "$WORKSPACE" ]] && { echo '{"status":"error","message":"Update data required"}'; exit 1; }
        
        device_id=$(get_device_id "$identifier")
        [[ -z "$device_id" ]] && { echo '{"status":"error","message":"Device not found"}'; exit 1; }
        
        # Merge updates
        jq --arg id "$device_id" --argjson updates "$json_data" '
            .devices = [.devices[] | if .id == $id then . * $updates else . end]
        ' "$DEVICES_FILE" > "$DEVICES_FILE.tmp"
        mv "$DEVICES_FILE.tmp" "$DEVICES_FILE"
        
        echo '{"status":"ok","message":"Device updated","deviceId":"'"$device_id"'"}'
        ;;
        
    # ----------------------------------------
    # REMOVE
    # ----------------------------------------
    remove)
        identifier="${1:-}"
        [[ -z "$identifier" ]] && { echo '{"status":"error","message":"Device name required"}'; exit 1; }
        
        device_id=$(get_device_id "$identifier")
        [[ -z "$device_id" ]] && { echo '{"status":"error","message":"Device not found"}'; exit 1; }
        
        jq --arg id "$device_id" '.devices = [.devices[] | select(.id != $id)]' "$DEVICES_FILE" > "$DEVICES_FILE.tmp"
        mv "$DEVICES_FILE.tmp" "$DEVICES_FILE"
        
        echo '{"status":"ok","message":"Device removed","deviceId":"'"$device_id"'"}'
        ;;
        
    # ----------------------------------------
    # SEARCH
    # ----------------------------------------
    search)
        query="${1:-}"
        [[ -z "$query" ]] && { echo '{"status":"error","message":"Search query required"}'; exit 1; }
        
        results=$(jq --arg q "$query" '
            [.devices[] | select(
                (.name | ascii_downcase | contains($q | ascii_downcase)) or
                (.manufacturer | ascii_downcase | contains($q | ascii_downcase)) or
                (.model | ascii_downcase | contains($q | ascii_downcase)) or
                ((.nickname // "") | ascii_downcase | contains($q | ascii_downcase)) or
                ((.location // "") | ascii_downcase | contains($q | ascii_downcase))
            )]
        ' "$DEVICES_FILE")
        
        echo "$results" | jq '{status: "ok", query: "'"$query"'", results: ., count: length}'
        ;;
        
    # ----------------------------------------
    # LOG - Add maintenance log
    # ----------------------------------------
    log)
        identifier="${1:-}"
        note="${2:-}"
        
        [[ -z "$identifier" ]] && { echo '{"status":"error","message":"Device name required"}'; exit 1; }
        [[ -z "$note" || "$note" == "$WORKSPACE" ]] && { echo '{"status":"error","message":"Note required"}'; exit 1; }
        
        device_id=$(get_device_id "$identifier")
        [[ -z "$device_id" ]] && { echo '{"status":"error","message":"Device not found"}'; exit 1; }
        
        # Add to maintenance log
        jq --arg device "$device_id" --arg note "$note" --arg ts "$(date -Iseconds)" \
            '.entries = [{timestamp: $ts, deviceId: $device, note: $note}] + .entries' \
            "$MAINTENANCE_FILE" > "$MAINTENANCE_FILE.tmp"
        mv "$MAINTENANCE_FILE.tmp" "$MAINTENANCE_FILE"
        
        echo '{"status":"ok","message":"Maintenance logged","deviceId":"'"$device_id"'"}'
        ;;
        
    # ----------------------------------------
    # HELP
    # ----------------------------------------
    help)
        echo '{"status":"ok","commands":["status","list","add","info","error","troubleshoot","manual","warranty","maintenance","update","remove","search","log"]}'
        ;;
        
    *)
        echo '{"status":"error","message":"Unknown command: '"$CMD"'"}'
        exit 1
        ;;
esac


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