Skill: patfitzner/morning-briefing-composer (version 1.0.1)

--- name: morning-briefing description: "Aggregates weather, game updates, and concert data into a daily markdown briefing. Triggered by 'morning briefing', 'daily briefing', 'run my briefing', 'what's new today'." metadata: openclaw: emoji: "🌅" os: [linux, darwin] requires: bins: [curl, jq] --- # morning-briefing A pure aggregator skill that reads data produced by other skills, filters and formats it, and writes a composed markdown briefing to `data/briefing.md`. ## Usage 1. **Initialize config** (first run only): ```bash bash scripts/init_config.sh ``` 2. **Run the briefing**: ```bash bash scripts/morning_briefing.sh ``` The script outputs the path to the generated markdown file. 3. **Read the file and present its contents verbatim to the user.** Do not summarize, reformat, paraphrase, add commentary, or editorialize. Output the entire markdown file exactly as written — the markdown IS the briefing. Do not wrap it in a code block. Do not omit sections. Do not change heading levels or link formatting. ## Data Sources Weather is built-in (direct curl to wttr.in). Other sections are driven by external skills that write JSON data files: - **steam-games-updates** — game news from Steam - **upcoming-metal-concerts** — concert listings from concerts-metal.com Each source has a jq template in `assets/templates/<source-id>.jq` that formats the raw JSON into markdown. ## Customization Edit `~/.openclaw/config/morning-briefing.json` to: - **Toggle weather** — set `weather.enabled` to `false` - **Change location** — set `weather.location` - **Enable/disable a source** — set `sources.<id>.enabled` - **Filter game updates** — add `"games": ["CS2"]` to steam-games-updates preferences - **Filter concerts by city** — edit `cities` array in upcoming-metal-concerts preferences - **Change concert window** — edit `days_ahead` in upcoming-metal-concerts preferences ## Adding a New Source 1. Add an entry to `sources` in the config with `data_path` and `preferences` 2. Create `assets/templates/<source-id>.jq` that accepts the source's JSON schema 3. The template receives `$preferences` (argjson) and `$today`/`$cutoff` (arg) variables

path: scripts/init_config.sh
content: #!/usr/bin/env bash
set -euo pipefail

CONFIG_DIR="${HOME}/.openclaw/config"
CONFIG_PATH="${CONFIG_DIR}/morning-briefing.json"

if [[ -f "$CONFIG_PATH" ]]; then
    echo "$CONFIG_PATH"
    exit 0
fi

mkdir -p "$CONFIG_DIR"

cat > "$CONFIG_PATH" << 'EOF'
{
  "timezone": "Europe/Madrid",
  "weather": {
    "enabled": true,
    "location": "Valencia"
  },
  "sources": {
    "steam-games-updates": {
      "enabled": true,
      "data_path": "../steam-games-updates/data/updates.json",
      "preferences": {}
    },
    "upcoming-metal-concerts": {
      "enabled": true,
      "data_path": "../upcoming-metal-concerts/data/concerts.json",
      "preferences": {
        "cities": ["Valencia", "Málaga", "Albacete"],
        "days_ahead": 14
      }
    }
  }
}
EOF

echo "$CONFIG_PATH"


path: scripts/morning_briefing.sh
content: #!/usr/bin/env bash
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
SKILL_DIR="$(dirname "$SCRIPT_DIR")"
CONFIG_PATH="${HOME}/.openclaw/config/morning-briefing.json"
OUTPUT_PATH="${SKILL_DIR}/data/briefing.md"

if [[ ! -f "$CONFIG_PATH" ]]; then
    echo "Config not found at $CONFIG_PATH" >&2
    echo "Run init_config.sh first." >&2
    exit 1
fi

mkdir -p "$(dirname "$OUTPUT_PATH")"

config=$(cat "$CONFIG_PATH")
tz=$(echo "$config" | jq -r '.timezone // "UTC"')
today=$(TZ="$tz" date +%Y-%m-%d)

# --- Build briefing into output file ---
{

# --- Header ---
echo "# Morning Briefing"
echo "$(TZ="$tz" date '+%A, %B %d, %Y')"
echo ""

# --- Weather ---
weather_enabled=$(echo "$config" | jq -r '.weather.enabled // false')
if [[ "$weather_enabled" == "true" ]]; then
    location=$(echo "$config" | jq -r '.weather.location // "Valencia"')
    echo "## Weather"
    echo ""
    weather_data=$(curl -s --max-time 10 "wttr.in/${location}?format=%l:+%c+%t+(feels+like+%f),+%w+wind,+%h+humidity" 2>/dev/null) || weather_data="Weather unavailable"
    echo "$weather_data"
    echo ""
fi

# --- Sources ---
echo "$config" | jq -r '.sources | keys[]' | while read -r source_id; do
    enabled=$(echo "$config" | jq -r ".sources[\"$source_id\"].enabled // true")
    [[ "$enabled" == "false" ]] && continue

    data_path=$(echo "$config" | jq -r ".sources[\"$source_id\"].data_path")
    data_path="${data_path/#\~/$HOME}"
    # Resolve relative paths from skill directory
    [[ "$data_path" != /* ]] && data_path="${SKILL_DIR}/${data_path}"

    [[ ! -f "$data_path" ]] && continue

    template="${SKILL_DIR}/assets/templates/${source_id}.jq"
    [[ ! -f "$template" ]] && continue

    preferences=$(echo "$config" | jq -c ".sources[\"$source_id\"].preferences // {}")

    # Compute cutoff date if days_ahead is set in preferences
    days_ahead=$(echo "$preferences" | jq -r '.days_ahead // 0')
    cutoff=""
    if [[ "$days_ahead" -gt 0 ]] 2>/dev/null; then
        if date -d "1 day" +%Y-%m-%d &>/dev/null; then
            cutoff=$(TZ="$tz" date -d "+${days_ahead} days" +%Y-%m-%d)
        else
            cutoff=$(TZ="$tz" date -v "+${days_ahead}d" +%Y-%m-%d)
        fi
    fi

    section=$(jq -r \
        --argjson preferences "$preferences" \
        --arg today "$today" \
        --arg cutoff "$cutoff" \
        -f "$template" "$data_path" 2>/dev/null) || continue

    if [[ -n "$section" && "$section" != "null" ]]; then
        echo "$section"
        echo ""
    fi
done

# --- Footer ---
echo "---"
echo "*Generated at $(TZ="$tz" date '+%H:%M %Z')*"

} > "$OUTPUT_PATH"

echo "IMPORTANT: Read $OUTPUT_PATH and present its contents to the user EXACTLY as written. Do not summarize, reformat, paraphrase, or editorialize. Output the markdown verbatim."


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