Skill: shaike1/openclaw-skill-red-alert (version 1.1.0)

--- name: oref-native description: > Israeli Home Front Command alerts - fully OpenClaw native. No Home Assistant. No wacli. No Docker monitor. OpenClaw handles everything: WhatsApp + TTS. --- # ORef Native - OpenClaw Only 🚨 ## Architecture ``` Pikud Ha-Oref API ↓ oref_native.py (cron every 5s) ↓ openclaw message → 📱 WhatsApp group openclaw tts → 🔊 Voice announcement ``` ## vs. Old System | | Old | Native | |--|-----|--------| | WhatsApp | wacli binary | openclaw message | | TTS | Home Assistant | openclaw tts | | Lights | Home Assistant | ❌ (not needed) | | Dependencies | Docker + HA + wacli | **OpenClaw only** | ## Setup ```bash # Install cron (every 5 seconds via loop) nohup python3 /root/.openclaw/workspace/skills/oref-native/oref_native.py \ >> /var/log/oref_native.log 2>&1 & # Or add to crontab (restart on boot) @reboot python3 /root/.openclaw/workspace/skills/oref-native/oref_native.py >> /var/log/oref_native.log 2>&1 ``` ## Environment Variables | Variable | Default | Description | |----------|---------|-------------| | OREF_API_URL | http://localhost:49000/current | oref-alerts proxy | | MONITORED_AREAS | הרצליה,הרצליה - גליל ים ומרכז | Comma-separated areas | | WHATSAPP_GROUP_JID | - | WhatsApp group JID | | WHATSAPP_OWNER | - | Personal WhatsApp number | | OPENCLAW_BIN | openclaw | Path to openclaw binary | ## Alert Routing | Type | WhatsApp | TTS | |------|----------|-----| | 🚀 Rockets (cat=1) | ✅ | ✅ | | ✈️ Aircraft (cat=2) | ✅ | ✅ | | 🔴 Infiltration (cat=10) | ✅ | ✅ | | ✅ All clear (cat=13) | ✅ | ✅ | | ⚠️ Pre-alert (cat=14) | ❌ | ✅ |

path: README.md
content: # 🚨 ORef Alerts - OpenClaw Native

Real-time Israeli Home Front Command (פיקוד העורף) alert system.  
**No Home Assistant. No extra WhatsApp binary. OpenClaw handles everything.**

---

## Architecture

```
Pikud Ha-Oref API
       ↓
oref_native.py (polls every 5s)
       ↓
📱 openclaw message → WhatsApp group
📞 3CX outbound call → your extension
🔊 Home Assistant TTS → speaker (optional)
```

---

## Quick Install

```bash
git clone https://github.com/YOUR_USER/oref-native
cd oref-native
bash install.sh
```

The installer will:
- Pull `dmatik/oref-alerts` Docker proxy
- Install Python dependencies
- Ask for your config interactively
- Start the monitor + add to crontab

---

## Configuration (`.env`)

```env
# API
OREF_API_URL=http://localhost:49000/current
OREF_POLL_INTERVAL=5
OREF_COOLDOWN=60

# Areas to monitor (comma separated Hebrew names)
MONITORED_AREAS=הרצליה,הרצליה - גליל ים ומרכז

# WhatsApp (via OpenClaw)
WHATSAPP_GROUP_JID=[REDACTED_SECRET]

# 3CX Phone Call
CX3_API=http://localhost:3000/api/outbound-call
CX3_EXTENSION=12610
CX3_ENABLED=true        # false = disable calls

# Home Assistant TTS (optional)
HASS_SERVER=https://homeassistant.local
HASS_TOKEN=your_token_here
HA_TTS_SPEAKER=media_player.your_speaker
```

---

## Alert Routing

| Type | WhatsApp | 📞 3CX Call | 🔊 Speaker |
|------|----------|-------------|------------|
| 🚀 Rockets (cat=1) | ✅ | ✅ | ✅ |
| ✈️ Aircraft (cat=2) | ✅ | ✅ | ✅ |
| 🔴 Infiltration (cat=10) | ✅ | ✅ | ✅ |
| ✅ All clear (cat=13) | ✅ | ❌ | ✅ |
| ⚠️ Pre-alert (cat=14) | ❌ | ❌ | ✅ |

---

## vs. Old Stack

| | Before | Now |
|--|--------|-----|
| WhatsApp | wacli binary | openclaw message |
| Voice call | ❌ | 3CX outbound call |
| TTS | Home Assistant | HA (optional) |
| Services | 5 | 2 (proxy + monitor) |
| Dependencies | Docker + HA + wacli | **OpenClaw + Docker proxy** |

---

## Commands

```bash
# Live logs
tail -f /var/log/oref_native.log

# Stop monitor
pkill -f oref_native.py

# Restart
bash install.sh

# Test API
curl -s http://localhost:49000/current | python3 -m json.tool

# Test 3CX call manually
curl -X POST http://localhost:3000/api/outbound-call \
  -H "Content-Type: application/json" \
  -d '{"to":"12610","message":"בדיקת מערכת התרעות","language":"he"}'
```

---

## Requirements

- [OpenClaw](https://github.com/openclaw/openclaw) 
- Docker
- Python 3.8+
- `openclaw-3cx` (for phone calls, optional)
- Home Assistant (for speaker TTS, optional)

---

## ⚠️ Common Issue: Duplicate Monitor

If you get alerts from wrong areas, check for a rogue process:
```bash
ps aux | grep monitor.py | grep -v grep
# Kill any non-Docker monitor:
kill <PID>
```
Only `oref_native.py` should run. Not `monitor.py` directly.


path: install.sh
content: #!/bin/bash
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# 🚨 ORef Alerts - OpenClaw Native - Installer
# Israeli Home Front Command real-time alerts
# via WhatsApp + 3CX call - no Home Assistant needed
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

set -e

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ENV_FILE="$SCRIPT_DIR/.env"
LOG_FILE="/var/log/oref_native.log"

echo "🚨 ORef Alerts - OpenClaw Native Installer"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"

# ── 1. בדוק דרישות ───────────────────────────────
echo "📦 Checking dependencies..."
command -v python3 >/dev/null || { echo "❌ python3 required"; exit 1; }
command -v openclaw >/dev/null || { echo "❌ openclaw required"; exit 1; }
pip3 install requests gtts 2>/dev/null | tail -1
echo "✅ Dependencies OK"

# ── 2. הגדרת oref-alerts Docker proxy ────────────
echo ""
echo "🐳 Starting oref-alerts API proxy..."
if ! docker ps | grep -q oref-alerts; then
    docker run -d \
        --name oref-alerts \
        --restart unless-stopped \
        -p 49000:9001 \
        -e TZ="Asia/Jerusalem" \
        dmatik/oref-alerts:latest
    echo "✅ oref-alerts started on port 49000"
else
    echo "✅ oref-alerts already running"
fi

# ── 3. קובץ הגדרות ──────────────────────────────
if [ ! -f "$ENV_FILE" ]; then
    echo ""
    echo "⚙️  Configuration Setup"
    echo "━━━━━━━━━━━━━━━━━━━━━━"

    read -p "📍 Monitored areas (comma separated, e.g. הרצליה,תל אביב): " AREAS
    read -p "📱 WhatsApp group JID (e.g. [REDACTED_SECRET]): " WA_GROUP
    read -p "📞 3CX extension to call on alert (e.g. 12610, leave empty to disable): " CX3_EXT
    read -p "🔑 Home Assistant URL (leave empty to disable): " HA_URL
    read -p "🔑 Home Assistant Token (leave empty to disable): " HA_TOKEN

    CX3_ENABLED="false"
    [ -n "$CX3_EXT" ] && CX3_ENABLED="true"

    cat > "$ENV_FILE" << EOF
OREF_API_URL=http://localhost:49000/current
OREF_POLL_INTERVAL=5
OREF_COOLDOWN=60
MONITORED_AREAS=$AREAS
WHATSAPP_GROUP_JID=$WA_GROUP
CX3_API=http://localhost:3000/api/outbound-call
CX3_EXTENSION=$CX3_EXT
CX3_ENABLED=$CX3_ENABLED
HASS_SERVER=$HA_URL
HASS_TOKEN=$HA_TOKEN
HA_TTS_SPEAKER=media_player.home_assistant_voice_09a069_media_player
EOF
    echo "✅ Config saved to $ENV_FILE"
else
    echo "✅ Using existing config: $ENV_FILE"
fi

# ── 4. הפעלה ─────────────────────────────────────
echo ""
echo "🚀 Starting ORef monitor..."
pkill -f oref_native.py 2>/dev/null || true
sleep 1

source "$ENV_FILE"
export $(cat "$ENV_FILE" | grep -v "^#" | xargs)

nohup python3 "$SCRIPT_DIR/oref_native.py" >> "$LOG_FILE" 2>&1 &
PID=$!
sleep 2

if ps -p $PID > /dev/null 2>&1; then
    echo "✅ Monitor running (PID: $PID)"
else
    echo "❌ Monitor failed to start. Check: tail -20 $LOG_FILE"
    exit 1
fi

# ── 5. crontab ────────────────────────────────────
ENV_EXPORTS=$(cat "$ENV_FILE" | grep -v "^#" | tr '\n' ' ')
CRON_CMD="@reboot $ENV_EXPORTS python3 $SCRIPT_DIR/oref_native.py >> $LOG_FILE 2>&1 &"
(crontab -l 2>/dev/null | grep -v oref_native; echo "$CRON_CMD") | crontab -
echo "✅ Added to crontab (auto-start on reboot)"

echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "🎉 Installation complete!"
echo ""
echo "📋 Commands:"
echo "  tail -f $LOG_FILE     # live logs"
echo "  pkill -f oref_native  # stop monitor"
echo "  bash $SCRIPT_DIR/install.sh  # restart"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"


path: oref_native.py
content: #!/usr/bin/env python3
"""
🚨 ORef Alerts - OpenClaw Native
מערכת התרעות פיקוד העורף - ניהול מלא דרך OpenClaw בלבד

ללא תלות ב-Home Assistant, ללא wacli נפרד.
הכל דרך OpenClaw CLI:
  - openclaw message → WhatsApp
  - openclaw tts     → הכרזה קולית
"""

import requests
import subprocess
import time
import logging
import os
import json
from datetime import datetime
import zoneinfo

# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# הגדרות
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
OREF_API        = os.getenv("OREF_API_URL", "http://localhost:49000/current")
POLL_INTERVAL   = int(os.getenv("OREF_POLL_INTERVAL", "5"))
COOLDOWN_SEC    = int(os.getenv("OREF_COOLDOWN", "60"))
OPENCLAW_BIN    = os.getenv("OPENCLAW_BIN", "openclaw")

# Home Assistant - לרמקול בלבד
HA_URL          = os.getenv("HASS_SERVER", "https://ha.right-api.com")
HA_TOKEN        = os.getenv("HASS_TOKEN", "")
HA_TTS_SPEAKER  = os.getenv("HA_TTS_SPEAKER", "media_player.home_assistant_voice_09a069_media_player")

# 3CX - חיוג טלפוני
CX3_API         = os.getenv("CX3_API", "http://localhost:3000/api/outbound-call")
CX3_EXTENSION   = os.getenv("CX3_EXTENSION", "12610")
CX3_ENABLED     = os.getenv("CX3_ENABLED", "true").lower() == "true"

# פילטר אזורים
MONITORED_AREAS = [a.strip() for a in os.getenv("MONITORED_AREAS", "הרצליה,הרצליה - גליל ים ומרכז").split(",") if a.strip()]

# יעדי WhatsApp
WHATSAPP_GROUP  = os.getenv("WHATSAPP_GROUP_JID", "[REDACTED_SECRET]")
WHATSAPP_OWNER  = os.getenv("WHATSAPP_OWNER", "+972525173322")

# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# סוגי התרעות
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
ALERT_TYPES = {
    "1":  {"emoji": "🚀", "level": "CRITICAL", "whatsapp": True,
           "action": "כנסו למרחב המוגן מיד! יש לכם 90 שניות!",
           "tts": "אזעקה! ירי רקטות וטילים! כנסו למרחב המוגן מיד!"},
    "2":  {"emoji": "✈️", "level": "CRITICAL", "whatsapp": True,
           "action": "כנסו למרחב המוגן מיד!",
           "tts": "אזעקה! חדירת כלי טיס עוין! כנסו למרחב המוגן מיד!"},
    "10": {"emoji": "🔴", "level": "CRITICAL", "whatsapp": True,
           "action": "חדירת מחבלים באזור!",
           "tts": "התרעה! חדירת מחבלים באזור!"},
    "13": {"emoji": "✅", "level": "ALL_CLEAR", "whatsapp": True,
           "action": "ניתן לצאת מהמרחב המוגן.",
           "tts": "האירוע הסתיים. ניתן לצאת מהמרחב המוגן."},
    "14": {"emoji": "⚠️",  "level": "WARNING",  "whatsapp": False,
           "action": "התכוננו! צפויות התרעות בקרוב!",
           "tts": "שימו לב! בדקות הקרובות צפויות התרעות באזורכם. התכוננו!"},
}
DEFAULT_TYPE = {"emoji": "🚨", "level": "UNKNOWN", "whatsapp": True,
                "action": "עיקבו אחר הוראות פיקוד העורף.",
                "tts": "התרעת פיקוד העורף. עיקבו אחר ההוראות."}

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(message)s",
    handlers=[
        logging.FileHandler("/var/log/oref_native.log"),
        logging.StreamHandler()
    ]
)
log = logging.getLogger("oref-native")

# State
last_alert_id  = None
alert_sent_at  = None
all_clear_sent = False


# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# OpenClaw - שליחת WhatsApp
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
def openclaw_whatsapp(target: str, message: str):
    """שלח הודעת WhatsApp דרך OpenClaw CLI"""
    try:
        cmd = [OPENCLAW_BIN, "message", "send",
               "--channel", "whatsapp",
               "--target", target,
               "--message", message]
        r = subprocess.run(cmd, capture_output=True, text=True, timeout=15)
        if r.returncode == 0:
            log.info(f"✅ WhatsApp → {target}")
        else:
            log.error(f"❌ WhatsApp failed: {r.stderr[:100]}")
    except Exception as e:
        log.error(f"❌ WhatsApp error: {e}")


# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# OpenClaw - TTS הכרזה קולית
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
def cx3_call(text: str):
    """חייג לשלוחה 3CX עם הודעת התרעה"""
    try:
        r = requests.post(CX3_API,
            json={"to": CX3_EXTENSION, "message": text, "language": "he"},
            timeout=10)
        if r.status_code == 200:
            log.info(f"✅ 3CX call → {CX3_EXTENSION}")
        else:
            log.warning(f"⚠️ 3CX {r.status_code}: {r.text[:80]}")
    except Exception as e:
        log.warning(f"⚠️ 3CX error: {e}")


def ha_tts(text: str):
    """הכרז קולית דרך רמקול Home Assistant"""
    try:
        r = requests.post(
            f"{HA_URL}/api/services/tts/google_translate_say",
            headers={"Authorization": f"Bearer {HA_TOKEN}", "Content-Type": "application/json"},
            json={"entity_id": HA_TTS_SPEAKER, "message": text, "language": "iw"},
            timeout=8
        )
        if r.status_code in (200, 201):
            log.info("✅ Speaker announced")
        else:
            log.warning(f"⚠️ Speaker {r.status_code}: {r.text[:80]}")
    except Exception as e:
        log.warning(f"⚠️ Speaker error: {e}")


# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# בניית הודעה
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
def build_message(data: dict, atype: dict) -> str:
    current = data.get("current", {})
    title   = current.get("title", "התרעה")
    areas   = current.get("data", []) or []
    desc    = current.get("desc", "")

    if MONITORED_AREAS and areas:
        matched = [a for a in areas if any(m in a for m in MONITORED_AREAS)]
        areas_to_show = matched if matched else areas[:5]
    else:
        areas_to_show = areas[:5]

    now = datetime.now(zoneinfo.ZoneInfo("Asia/Jerusalem")).strftime("%H:%M:%S")
    return (
        f"{atype['emoji']} *{title}* {atype['emoji']}\n\n"
        f"⏰ {now}\n"
        f"⚡ {atype['action']}"
        + (f"\n📍 {', '.join(areas_to_show)}" if areas_to_show else "")
        + (f"\n📋 {desc}" if desc else "")
    )


# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# שליחת התרעה
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
def dispatch(data: dict):
    current = data.get("current", {})
    cat     = str(current.get("cat", "1"))
    atype   = ALERT_TYPES.get(cat, DEFAULT_TYPE)
    level   = atype["level"]

    log.info(f"🚨 [{level}] Dispatching alert")

    wa_msg   = build_message(data, atype)
    tts_text = atype["tts"]

    # 1️⃣ WhatsApp - קבוצת עדכונים בלבד
    if atype["whatsapp"]:
        openclaw_whatsapp(WHATSAPP_GROUP, wa_msg)

    # 3️⃣ רמקול HA - תמיד
    ha_tts(tts_text)


# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# לולאת ניטור
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
def check_alert():
    global last_alert_id, alert_sent_at, all_clear_sent
    try:
        data = requests.get(OREF_API, timeout=5).json()
    except Exception as e:
        log.warning(f"⚠️ API: {e}")
        return

    if not data.get("alert"):
        if last_alert_id:
            log.info("✅ No active alert")
            last_alert_id  = None
            all_clear_sent = False
        return

    current  = data.get("current", {})
    cat      = str(current.get("cat", ""))
    alert_id = current.get("id", "")

    # פילטר אזורים (גם לסיום אירוע)
    if MONITORED_AREAS:
        areas   = current.get("data", []) or []
        matched = [a for a in areas if any(m in a for m in MONITORED_AREAS)]
        if not matched:
            log.debug(f"⏭️ Skipped (not in area): {areas}")
            return

    # סיום אירוע - שלח פעם אחת
    if cat == "13":
        if not all_clear_sent:
            all_clear_sent = True
            dispatch(data)
        return

    # מנע ספאם
    if alert_id == last_alert_id:
        return
    now = time.time()
    if alert_sent_at and (now - alert_sent_at) < COOLDOWN_SEC:
        return

    last_alert_id  = alert_id
    alert_sent_at  = now
    all_clear_sent = False
    dispatch(data)


def main():
    log.info("🚀 ORef Native Monitor started (OpenClaw-only mode)")
    log.info(f"📡 Polling: {OREF_API} every {POLL_INTERVAL}s")
    log.info(f"📍 Monitored: {MONITORED_AREAS}")
    log.info(f"📱 Group: {WHATSAPP_GROUP}")
    log.info("━" * 50)
    while True:
        try:
            check_alert()
        except Exception as e:
            log.error(f"❌ {e}")
        time.sleep(POLL_INTERVAL)


if __name__ == "__main__":
    main()


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