Back to skills
SkillHub ClubWrite Technical DocsFull StackTech Writer

competitor-watch

Know what your competitors ship before their customers do. Automated monitoring of competitor websites, product pages, pricing, content, and social presence. Detects changes, extracts new features, tracks pricing updates, and alerts you with digestible summaries. Your agent watches the competition 24/7 so you can focus on building. Configure competitor tiers (fierce rivals get deep monitoring, adjacents get high-level), set check frequency, define alert thresholds, and receive smart diffs highlighting what actually matters. Use when setting up competitive intelligence, tracking product launches, monitoring pricing changes, or staying ahead of market moves.

Packaged view

This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.

Stars
3,100
Hot score
99
Updated
March 20, 2026
Overall rating
C0.0
Composite score
0.0
Best-practice grade
B71.9

Install command

npx @skill-hub/cli install openclaw-skills-competitor-watch

Repository

openclaw/skills

Skill path: skills/audsmith28/competitor-watch

Know what your competitors ship before their customers do. Automated monitoring of competitor websites, product pages, pricing, content, and social presence. Detects changes, extracts new features, tracks pricing updates, and alerts you with digestible summaries. Your agent watches the competition 24/7 so you can focus on building. Configure competitor tiers (fierce rivals get deep monitoring, adjacents get high-level), set check frequency, define alert thresholds, and receive smart diffs highlighting what actually matters. Use when setting up competitive intelligence, tracking product launches, monitoring pricing changes, or staying ahead of market moves.

Open repository

Best for

Primary workflow: Write Technical Docs.

Technical facets: Full Stack, Tech Writer.

Target audience: everyone.

License: Unknown.

Original source

Catalog source: SkillHub Club.

Repository owner: openclaw.

This is still a mirrored public skill entry. Review the repository before installing into production workflows.

What it helps with

  • Install competitor-watch into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
  • Review https://github.com/openclaw/skills before adding competitor-watch to shared team environments
  • Use competitor-watch for development workflows

Works across

Claude CodeCodex CLIGemini CLIOpenCode

Favorites: 0.

Sub-skills: 0.

Aggregator: No.

Original source / Raw SKILL.md

---
name: competitor-watch
description: Know what your competitors ship before their customers do. Automated monitoring of competitor websites, product pages, pricing, content, and social presence. Detects changes, extracts new features, tracks pricing updates, and alerts you with digestible summaries. Your agent watches the competition 24/7 so you can focus on building. Configure competitor tiers (fierce rivals get deep monitoring, adjacents get high-level), set check frequency, define alert thresholds, and receive smart diffs highlighting what actually matters. Use when setting up competitive intelligence, tracking product launches, monitoring pricing changes, or staying ahead of market moves.
metadata:
  clawdbot:
    emoji: "πŸ”"
    requires:
      skills: []
---

# Competitor Watch β€” Your 24/7 Competitive Intelligence Agent

**They ship a feature. You know in minutes, not weeks.**

Competitor Watch monitors your competitive landscape automaticallyβ€”websites, product pages, pricing, blog posts, social accountsβ€”and alerts you when something changes. No more finding out your rival launched that feature you've been building after their customers already know about it.

**What makes it different:** Tiered monitoring (fierce rivals get deep tracking, adjacents get high-level), intelligent diffing (highlights what matters, filters noise), and digestible summaries that tell you *so what* instead of dumping raw HTML changes.

## The Problem

You're a founder or product leader. You have 3-5 direct competitors and maybe a dozen companies in adjacent spaces. They're all shipping, pricing, pivoting, and posting. You check manually when you remember (usually when a customer mentions it). By the time you notice a major move, it's too late to react strategically.

Manual competitive intelligence doesn't scale. Bookmarking competitor URLs and checking them weekly doesn't work. You need an agent that watches constantly and only interrupts when something actually matters.

## What It Does

- **Website Monitoring**: Track homepage, product pages, pricing pages, docs
- **Change Detection**: Smart diffing that filters out timestamps, session IDs, ads
- **Content Extraction**: Pull new blog posts, feature announcements, case studies
- **Pricing Tracking**: Detect price changes, plan additions, new tiers
- **Social Listening**: Monitor Twitter/LinkedIn for major announcements
- **Tiered Tracking**: Deep monitoring for direct rivals, high-level for adjacents
- **Smart Alerts**: Summaries that tell you *what changed* and *why it matters*

## Setup

1. Run `scripts/setup.sh` to initialize config and data directories
2. Edit `~/.config/competitor-watch/config.json` with your competitive landscape
3. Add competitors: `scripts/add-competitor.sh "CompanyName" https://example.com --tier=fierce`
4. Test monitoring: `scripts/check.sh --dry-run`
5. Set up cron or heartbeat: Run `check.sh` every 30-60 minutes

## Config

Config lives at `~/.config/competitor-watch/config.json`. See `config.example.json` for full schema.

Key sections:
- **competitors** β€” List of companies to track (name, URLs, tier, tags)
- **tiers** β€” Define monitoring depth (fierce, important, watching, adjacent)
- **monitoring** β€” What to track (pages, content, pricing, social)
- **diffing** β€” Change detection settings (ignore patterns, similarity threshold)
- **alerts** β€” When and how to notify (min change size, cooldown, channel)
- **scheduling** β€” Check frequency per tier

### Tiered Monitoring

**Fierce** (direct competitors in every deal):
- Check every 30 minutes
- Monitor: pricing, features, docs, blog, social
- Alert on: any meaningful change
- Keep: 90 days of snapshots

**Important** (frequent competitive overlap):
- Check every 2 hours
- Monitor: pricing, features, blog
- Alert on: medium+ changes
- Keep: 30 days of snapshots

**Watching** (potential future threat):
- Check daily
- Monitor: homepage, blog
- Alert on: major changes only
- Keep: 14 days of snapshots

**Adjacent** (different market, relevant trends):
- Check weekly
- Monitor: blog, major announcements
- Alert on: significant pivots or launches
- Keep: 7 days of snapshots

## Scripts

| Script | Purpose |
|--------|---------|
| `scripts/setup.sh` | Initialize config and data directories |
| `scripts/add-competitor.sh` | Add a competitor (interactive or flags) |
| `scripts/check.sh` | Run monitoring sweep (all or specific tier) |
| `scripts/diff.sh` | Compare snapshots, generate change report |
| `scripts/report.sh` | Format digest of recent changes |

All scripts support `--dry-run` for testing without storing snapshots.

## Monitoring Cycle

Run `scripts/check.sh` on schedule (cron or heartbeat). The check:
1. Loads competitor list filtered by tier schedule
2. Fetches each configured URL (web_fetch or browser if needed)
3. Stores snapshot with timestamp
4. Compares to previous snapshot (calls `diff.sh`)
5. Scores change significance (text diff size, pricing changes, new sections)
6. Generates alert if threshold met
7. Updates last-check timestamp and change log

## Diffing Logic

`diff.sh` does intelligent comparison:

**Filters out noise:**
- Timestamps, session IDs, cache busters (`?v=123`)
- Dynamic ad content, tracking pixels
- Social share counts, "Last updated" dates
- Common CMS artifacts

**Highlights signal:**
- New product sections or features
- Pricing/plan changes (keyword matching)
- Added/removed navigation items
- New blog posts or case studies
- Significant text additions (>200 words)

**Change scoring:**
- Minor: <5% content change, cosmetic updates β†’ No alert
- Medium: 5-15% change, new blog post β†’ Alert if Important+ tier
- Major: >15% change, pricing update, new product section β†’ Always alert
- Critical: Explicit keywords ("launching", "announcing", "now available") β†’ Urgent alert

## Alerts

When a meaningful change is detected, `report.sh` generates a summary:

```
🚨 COMPETITOR CHANGE: Acme Corp (fierce rival)

Page: https://acme.com/pricing
Detected: 2026-02-11 13:45 EST
Change: MAJOR (pricing update + new tier)

What changed:
β€’ New "Enterprise" tier added at $999/mo
β€’ "Pro" tier price increased from $49 to $79 (+61%)
β€’ Added "Custom AI workflows" feature to all plans

Raw diff: ~/.config/competitor-watch/data/snapshots/acme-corp/pricing/diff-2026-02-11-1345.txt

β€”
View full snapshot: check.sh --snapshot acme-corp pricing
```

Alerts respect cooldown (don't spam on every tiny update) and tier settings.

## Adding Competitors

Interactive mode:
```bash
scripts/add-competitor.sh
# Prompts for: name, homepage, tier, pages to track, tags
```

Flag mode:
```bash
scripts/add-competitor.sh "Acme Corp" https://acme.com \
  --tier fierce \
  --pages pricing,features,blog \
  --tags "direct-competitor,ai-tools" \
  --twitter @acmecorp
```

## Managing Competitors

```bash
# List all
scripts/check.sh --list

# Check specific competitor
scripts/check.sh --competitor "Acme Corp"

# Check tier only
scripts/check.sh --tier fierce

# View change history
scripts/report.sh --competitor "Acme Corp" --days 30

# Update tier
# (Edit ~/.config/competitor-watch/config.json, or re-run add-competitor)
```

## Data Files

```
~/.config/competitor-watch/
β”œβ”€β”€ config.json              # Competitor list and settings
β”œβ”€β”€ data/
β”‚   β”œβ”€β”€ snapshots/
β”‚   β”‚   β”œβ”€β”€ acme-corp/
β”‚   β”‚   β”‚   β”œβ”€β”€ pricing/
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ 2026-02-11-1000.txt
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ 2026-02-11-1030.txt
β”‚   β”‚   β”‚   β”‚   └── diff-2026-02-11-1030.txt
β”‚   β”‚   β”‚   └── features/...
β”‚   β”‚   └── competitor-b/...
β”‚   β”œβ”€β”€ change-log.json      # All detected changes
β”‚   β”œβ”€β”€ last-checks.json     # When each URL was last checked
β”‚   └── alert-history.json   # Sent alerts (for cooldown)
└── reports/
    └── daily-digest-2026-02-11.md
```

## Scheduling

### Cron (recommended for production)
```bash
# Check fierce rivals every 30 min
*/30 * * * * /path/to/skills/competitor-watch/scripts/check.sh --tier fierce

# Check important every 2 hours
0 */2 * * * /path/to/skills/competitor-watch/scripts/check.sh --tier important

# Daily digest report at 9 AM
0 9 * * * /path/to/skills/competitor-watch/scripts/report.sh --daily
```

### Heartbeat (for integrated monitoring)
Add to your `HEARTBEAT.md`:
```markdown
## Competitor Watch
- Run `skills/competitor-watch/scripts/check.sh --tier fierce` (if >30 min since last)
- Check alert-history.json for unsent alerts
```

## Integration with Clawdbot

Alerts can be sent via:
- **Telegram/Discord/Slack**: Direct message with summary
- **File**: Write report to workspace, mention in next interaction
- **Heartbeat**: Surface in proactive check ("Acme Corp updated pricing")

## Best Practices

**Tier carefully:**
- Only 2-3 competitors should be "fierce" (high monitoring cost)
- Use "important" for companies in 30%+ of deals
- "Watching" for emerging threats or fast-growing startups
- "Adjacent" for market signals, not tactical intel

**Focus on delta:**
- You don't need to read their entire website daily
- The diff is the productβ€”new features, pricing changes, messaging shifts
- Archive old snapshots after tier retention window

**Combine with human intel:**
- Competitor Watch automates the tedious part (checking URLs)
- You still need: sales call notes, customer chatter, market analysis
- Use this skill to ensure you never miss the *public* signals

**Avoid over-alerting:**
- Set thresholds appropriate to tier (minor changes on fierce rivals OK, but only major for adjacents)
- Use cooldown periods (don't alert twice in 6 hours for same page)
- Weekly digest > real-time spam for lower tiers

## Use Cases

**Product team:**
- Track feature launches β†’ Validate roadmap prioritization
- Monitor docs/changelogs β†’ Understand their capabilities
- Watch integrations page β†’ Know their ecosystem moves

**Sales/GTM:**
- Pricing changes β†’ Update battlecards and objection handling
- New case studies β†’ Understand their positioning and wins
- Messaging shifts β†’ Adjust competitive positioning

**Marketing:**
- Content velocity β†’ Benchmark publishing cadence
- Campaign themes β†’ Spot market narrative shifts
- Social engagement β†’ Understand what resonates

**Founders:**
- High-level awareness without daily manual checking
- React strategically to major moves (launches, pivots, funding)
- Focus on building, not obsessing over competitors

## Privacy & Ethics

- Only monitors **public** web content
- Respects robots.txt and rate limits
- No scraping of authenticated/paywalled content
- No impersonation or deceptive data collection
- Use for competitive intelligence, not corporate espionage

## Future Enhancements

- **Social listening**: Twitter/LinkedIn post monitoring (beyond just checking profile)
- **GitHub tracking**: Public repo commits, release notes, contributor activity
- **Product Hunt launches**: Auto-add when competitor ships on PH
- **App store monitoring**: iOS/Android app updates, rating changes
- **Job postings**: Track hiring (eng roles = product expansion signals)
- **LLM-powered summaries**: GPT-4 analysis of change significance
- **Slack/Discord webhooks**: Push alerts to team channels
- **Browser automation**: Handle JS-heavy sites that web_fetch can't parse

---

**Know what they're shipping. Before their customers do.**


---

## Referenced Files

> The following files are referenced in this skill and included for context.

### scripts/add-competitor.sh

```bash
#!/bin/bash
# competitor-watch/scripts/add-competitor.sh β€” Add a new competitor to the config

set -euo pipefail

CONFIG_DIR="${CW_CONFIG_DIR:-$HOME/.config/competitor-watch}"
CONFIG_FILE="$CONFIG_DIR/config.json"

# --- Help ---
show_help() {
cat << EOF
Usage: $0 [OPTIONS] "Competitor Name" "https://homepage.url"

Add a new competitor to the monitoring configuration.
Can be run interactively or with command-line flags.

Options:
  --tier <name>      Set competitor tier (e.g., fierce, important).
  --pages <p1,p2>    Comma-separated list of pages to track (e.g., pricing,blog).
  --tags <t1,t2>     Comma-separated list of tags.
  --twitter <handle> Twitter handle (without @).
  --linkedin <path>  LinkedIn company path.
  --notes "..."      Notes about the competitor.
  -h, --help         Show this help message.

Example:
  $0 "Acme Corp" https://acme.com --tier fierce --pages pricing,features
EOF
}

# --- Check Dependencies ---
if ! command -v jq &> /dev/null; then
    echo "Error: jq is not installed. Please install it to manage config. (e.g., 'brew install jq')"
    exit 1
fi

if [ ! -f "$CONFIG_FILE" ]; then
    echo "Error: Configuration file not found at $CONFIG_FILE"
    echo "Please run 'scripts/setup.sh' first."
    exit 1
fi

# --- Parse Arguments ---
NAME=""
HOMEPAGE=""
TIER=""
PAGES_STR=""
TAGS_STR=""
TWITTER=""
LINKEDIN=""
NOTES=""

# Use a while loop for robust argument parsing
if [ "$#" -eq 0 ]; then
    # Interactive mode if no arguments
    echo "Entering interactive mode..."
    read -p "Competitor Name: " NAME
    read -p "Homepage URL: " HOMEPAGE
    read -p "Tier (e.g., fierce, important, watching): " TIER
    read -p "Pages to track (comma-separated, e.g., pricing,blog): " PAGES_STR
    read -p "Tags (comma-separated): " TAGS_STR
    echo ""
else
    # Flag mode
    while [[ $# -gt 0 ]]; do
        case "$1" in
            -h|--help)
                show_help
                exit 0
                ;;
            --tier)
                TIER="$2"
                shift 2
                ;;
            --pages)
                PAGES_STR="$2"
                shift 2
                ;;
            --tags)
                TAGS_STR="$2"
                shift 2
                ;;
            --twitter)
                TWITTER="$2"
                shift 2
                ;;
            --linkedin)
                LINKEDIN="$2"
                shift 2
                ;;
            --notes)
                NOTES="$2"
                shift 2
                ;;
            -*)
                echo "Unknown option: $1"
                show_help
                exit 1
                ;;
            *)
                if [ -z "$NAME" ]; then
                    NAME="$1"
                elif [ -z "$HOMEPAGE" ]; then
                    HOMEPAGE="$1"
                else
                    echo "Unknown positional argument: $1"
                    show_help
                    exit 1
                fi
                shift
                ;;
        esac
    done
fi

# --- Validation ---
if [ -z "$NAME" ] || [ -z "$HOMEPAGE" ]; then
    echo "Error: Competitor Name and Homepage URL are required."
    show_help
    exit 1
fi

ID=$(echo "$NAME" | tr '[:upper:]' '[:lower:]' | tr -s '[:punct:][:space:]' '-' | sed 's/^-//;s/-$//')
echo "Generated Competitor ID: $ID"

# Check if ID already exists
if jq -e --arg id "$ID" '.competitors[] | select(.id == $id)' "$CONFIG_FILE" > /dev/null; then
    echo "Error: Competitor with ID '$ID' already exists. Choose a different name."
    exit 1
fi

# --- Build JSON Objects ---
PAGES_JSON="{}"
if [ -n "$PAGES_STR" ]; then
    PAGES_JSON=$(echo "$PAGES_STR" | tr ',' '\n' | awk -F ':' '{
        if (NF==1) { key=$1; value="/\"" key "\"" } else { key=$1; value=$2 }
        print "    \"" key "\": \"" value
    }' | sed 's|: "/|: "|' | paste -sd, - | sed 's/^/{ /;s/$/ }/')
    # A bit complex to handle simple names (pricing -> /pricing) and full URLs
    # For this script, we'll keep it simple and assume page names map to subpaths
    PAGES_JSON=$(echo "$PAGES_STR" | tr ',' '\n' | while read -r page; do
        echo "\"$page\": \"$HOMEPAGE/$page\""
    done | paste -sd, - | sed 's/^/{ /;s/$/ }/')
fi

SOCIAL_JSON="{}"
if [ -n "$TWITTER" ] || [ -n "$LINKEDIN" ]; then
    TWITTER_JSON=""
    [ -n "$TWITTER" ] && TWITTER_JSON="\"twitter\": \"$TWITTER\""
    LINKEDIN_JSON=""
    [ -n "$LINKEDIN" ] && LINKEDIN_JSON="\"linkedin\": \"$LINKEDIN\""

    SOCIAL_JSON=$(echo "$TWITTER_JSON $LINKEDIN_JSON" | sed 's/  */, /g;s/^, //;s/, $//;s/^/{ /;s/$/ }/')
fi

TAGS_JSON="[]"
if [ -n "$TAGS_STR" ]; then
    TAGS_JSON=$(echo "$TAGS_STR" | tr ',' '\n' | jq -R . | jq -s . | jq -c .)
fi

# --- Construct Final Competitor Object ---
NEW_COMPETITOR=$(jq -n \
    --arg id "$ID" \
    --arg name "$NAME" \
    --arg homepage "$HOMEPAGE" \
    --arg tier "$TIER" \
    --argjson pages "$PAGES_JSON" \
    --argjson social "$SOCIAL_JSON" \
    --argjson tags "$TAGS_JSON" \
    --arg notes "$NOTES" \
    '{
        id: $id,
        name: $name,
        homepage: $homepage,
        tier: $tier,
        tags: $tags,
        pages: $pages,
        social: $social,
        notes: $notes
    }')

# --- Update Config File ---
TMP_CONFIG=$(mktemp)
jq --argjson new_comp "$NEW_COMPETITOR" '.competitors += [$new_comp]' "$CONFIG_FILE" > "$TMP_CONFIG" && mv "$TMP_CONFIG" "$CONFIG_FILE"

echo ""
echo "βœ… Successfully added '$NAME' to the configuration."
echo "Review the changes in: $CONFIG_FILE"

```

### scripts/check.sh

```bash
#!/bin/bash
# competitor-watch/scripts/check.sh β€” Run monitoring sweep

set -euo pipefail

# --- Config ---
CONFIG_DIR="${CW_CONFIG_DIR:-$HOME/.config/competitor-watch}"
CONFIG_FILE="$CONFIG_DIR/config.json"
DATA_DIR="$CONFIG_DIR/data"
SNAPSHOT_DIR="$DATA_DIR/snapshots"
LAST_CHECKS_FILE="$DATA_DIR/last-checks.json"
CHANGE_LOG_FILE="$DATA_DIR/change-log.json"
SKILL_DIR="$(cd "$(dirname "$0")/.." && pwd)"

# --- Color Codes ---
C_INFO='\033[0;36m'
C_SUCCESS='\033[0;32m'
C_WARN='\033[0;33m'
C_ERROR='\033[0;31m'
C_RESET='\033[0m'

# --- Log Functions ---
log_info() { echo -e "${C_INFO}INFO: $1${C_RESET}"; }
log_success() { echo -e "${C_SUCCESS}SUCCESS: $1${C_RESET}"; }
log_warn() { echo -e "${C_WARN}WARN: $1${C_RESET}"; }
log_error() { echo -e "${C_ERROR}ERROR: $1${C_RESET}"; exit 1; }

# --- Argument Parsing ---
DRY_RUN=false
FORCE_CHECK=false
TARGET_TIER=""
TARGET_COMPETITOR=""

while [[ $# -gt 0 ]]; do
    case "$1" in
        --dry-run)
            DRY_RUN=true
            shift
            ;;
        --force)
            FORCE_CHECK=true
            shift
            ;;
        --tier)
            TARGET_TIER="$2"
            shift 2
            ;;
        --competitor)
            TARGET_COMPETITOR="$2"
            shift 2
            ;;
        *)
            log_error "Unknown option: $1"
            ;;
    esac
done

# --- Validation ---
if ! command -v jq &> /dev/null; then log_error "jq is not installed."; fi
if [ ! -f "$CONFIG_FILE" ]; then log_error "Config file not found. Run setup.sh first."; fi

# --- Main Logic ---
log_info "Starting competitor check sweep..."
[ "$DRY_RUN" = true ] && log_warn "Running in DRY-RUN mode. No files will be written."

# Load competitors and tiers from config
COMPETITORS=$(jq -c '.competitors[]' "$CONFIG_FILE")
TIERS=$(jq -c '.tiers' "$CONFIG_FILE")

# Loop through each competitor
echo "$COMPETITORS" | while IFS= read -r competitor_json; do
    comp_id=$(echo "$competitor_json" | jq -r '.id')
    comp_name=$(echo "$competitor_json" | jq -r '.name')
    comp_tier=$(echo "$competitor_json" | jq -r '.tier')
    comp_pages=$(echo "$competitor_json" | jq -c '.pages')

    # --- Filtering ---
    if [ -n "$TARGET_TIER" ] && [ "$comp_tier" != "$TARGET_TIER" ]; then continue; fi
    if [ -n "$TARGET_COMPETITOR" ] && [[ "$comp_id" != "$TARGET_COMPETITOR" && "$comp_name" != "$TARGET_COMPETITOR" ]]; then continue; fi

    log_info "Processing: $comp_name (Tier: $comp_tier)"
    tier_config=$(echo "$TIERS" | jq -c ".\"$comp_tier\"")
    check_interval=$(echo "$tier_config" | jq -r '.check_interval_minutes')

    # Loop through each page for the competitor
    echo "$comp_pages" | jq -c 'to_entries[]' | while IFS= read -r page_entry; do
        page_name=$(echo "$page_entry" | jq -r '.key')
        page_url=$(echo "$page_entry" | jq -r '.value')
        check_key="${comp_id}.${page_name}"

        # --- Check Schedule ---
        last_check_timestamp=$(jq -r ".\"$check_key\" // 0" "$LAST_CHECKS_FILE")
        current_timestamp=$(date +%s)
        minutes_since_last_check=$(( (current_timestamp - last_check_timestamp) / 60 ))

        if [ "$FORCE_CHECK" = false ] && [ "$minutes_since_last_check" -lt "$check_interval" ]; then
            echo "  - Skipping '$page_name' (checked $minutes_since_last_check mins ago, interval is $check_interval)"
            continue
        fi

        echo "  - Checking '$page_name' ($page_url)"

        # --- Fetch Content (Simulated with clawd) ---
        # In a real scenario, this would be `clawd web_fetch ...`
        # For this script, we'll use a placeholder.
        fetch_command="clawd web_fetch --url \"$page_url\" --extractMode text"
        echo "    - Running: $fetch_command"

        if [ "$DRY_RUN" = true ]; then
            new_content="DRY RUN CONTENT for $page_url at $(date)"
        else
            # SIMULATED: In a real environment, you'd capture the output of the clawd tool call
            # For now, let's just create some sample content.
            # new_content=$(clawd web_fetch --url "$page_url" --extractMode text)
            new_content="Real content for $page_url at $(date)\n$(head -c 100 /dev/urandom | base64)"
        fi
        
        # --- Manage Snapshots ---
        comp_snapshot_dir="$SNAPSHOT_DIR/$comp_id/$page_name"
        mkdir -p "$comp_snapshot_dir"

        timestamp=$(date +"%Y-%m-%d-%H%M%S")
        new_snapshot_file="$comp_snapshot_dir/$timestamp.txt"
        
        echo "    - Saving snapshot to $new_snapshot_file"
        if [ "$DRY_RUN" = false ]; then
            echo "$new_content" > "$new_snapshot_file"
        fi

        # Find previous snapshot
        previous_snapshot_file=$(ls -1 "$comp_snapshot_dir" | grep -v "$timestamp" | sort -r | head -n 1)

        if [ -z "$previous_snapshot_file" ]; then
            log_warn "    - No previous snapshot found. This is the first check."
        else
            log_info "    - Comparing with previous snapshot: $previous_snapshot_file"
            # --- Call Diff Script ---
            diff_output=$("$SKILL_DIR/scripts/diff.sh" "$comp_snapshot_dir/$previous_snapshot_file" "$new_snapshot_file")
            diff_exit_code=$?
            
            if [ "$diff_exit_code" -eq 1 ]; then
                log_success "    - SIGNIFICANT CHANGE DETECTED!"
                change_id=$(uuidgen)
                
                # Log the change
                if [ "$DRY_RUN" = false ]; then
                    change_data=$(echo "$diff_output" | jq --arg id "$change_id" --arg comp "$comp_id" --arg page "$page_name" '. + {id: $id, competitor: $comp, page: $page, timestamp: now | todate}')
                    jq --argjson data "$change_data" '.[$data.id] = $data' "$CHANGE_LOG_FILE" > tmp.json && mv tmp.json "$CHANGE_LOG_FILE"

                    # --- Call Report Script ---
                    log_info "    - Generating alert..."
                    "$SKILL_DIR/scripts/report.sh" --alert "$change_id"
                else
                    echo "    - DRY RUN: Would log change and generate alert."
                    echo "    - Diff output: $diff_output"
                fi
            else
                log_info "    - No significant changes found."
            fi
        fi

        # Update last check time
        if [ "$DRY_RUN" = false ]; then
            jq --arg key "$check_key" --arg time "$current_timestamp" '.[$key] = ($time | tonumber)' "$LAST_CHECKS_FILE" > tmp.json && mv tmp.json "$LAST_CHECKS_FILE"
        fi
        echo ""

    done
done

log_info "Sweep complete."

```

### scripts/report.sh

```bash
#!/bin/bash
# competitor-watch/scripts/report.sh β€” Format and send change reports

set -euo pipefail

# --- Config ---
CONFIG_DIR="${CW_CONFIG_DIR:-$HOME/.config/competitor-watch}"
CONFIG_FILE="$CONFIG_DIR/config.json"
CHANGE_LOG_FILE="$CONFIG_DIR/data/change-log.json"

# --- Argument Parsing ---
MODE=""
ARG=""

case "$1" in
    --alert)
        MODE="alert"
        ARG="$2"
        ;;
    --daily-digest)
        MODE="digest"
        ;;
    --list)
        MODE="list"
        ;;
    *)
        echo "Usage: $0 [MODE]"
        echo "Modes:"
        echo "  --alert <change_id>   Send a real-time alert for a specific change."
        echo "  --daily-digest        Generate and send a digest of recent changes."
        echo "  --list                List recent changes from the log."
        exit 1
        ;;
esac

# --- Validation ---
if ! command -v jq &> /dev/null; then echo "jq not installed"; exit 1; fi
if [ ! -f "$CHANGE_LOG_FILE" ]; then echo "Change log not found."; exit 1; fi

# --- Functions ---

format_alert() {
    local change_id="$1"
    local change_json=$(jq -r ".\"$change_id\"" "$CHANGE_LOG_FILE")
    
    if [ "$change_json" == "null" ]; then
        echo "Error: Change with ID $change_id not found."
        return 1
    fi

    local comp_id=$(echo "$change_json" | jq -r '.competitor')
    local page_name=$(echo "$change_json" | jq -r '.page')
    local comp_name=$(jq -r --arg id "$comp_id" '.competitors[] | select(.id == $id) | .name' "$CONFIG_FILE")
    local comp_tier=$(jq -r --arg id "$comp_id" '.competitors[] | select(.id == $id) | .tier' "$CONFIG_FILE")
    local added=$(echo "$change_json" | jq -r '.summary.added')
    local removed=$(echo "$change_json" | jq -r '.summary.removed')
    local score=$(echo "$change_json" | jq -r '.score')
    
    local report=""
    report+="🚨 COMPETITOR CHANGE: $comp_name ($comp_tier)\n"
    report+="========================================\n"
    report+="Page: $page_name\n"
    report+="Detected: $(echo "$change_json" | jq -r '.timestamp')\n"
    report+="Significance Score: $score\n\n"

    if [ -n "$added" ]; then
        report+="What's New:\n$added\n\n"
    fi
    if [ -n "$removed" ]; then
        report+="What's Removed:\n$removed\n\n"
    fi
    
    report+="---\n"
    report+="Raw diff available in change log."

    echo -e "$report"
}

send_alert() {
    local formatted_report="$1"
    local alert_channel=$(jq -r '.alerts.channel' "$CONFIG_FILE")

    # This is a simulation of sending a message via the clawd tool
    echo "--- Sending Alert via $alert_channel ---"
    # In a real environment:
    # clawd message send --channel "$alert_channel" --message "$formatted_report"
    echo "$formatted_report"
    echo "------------------------------------"
}

# --- Main Logic ---

case "$MODE" in
    "alert")
        if [ -z "$ARG" ]; then
            echo "Error: --alert requires a change_id."
            exit 1
        fi
        formatted_report=$(format_alert "$ARG")
        if [ $? -eq 0 ]; then
            send_alert "$formatted_report"
        fi
        ;;
    "digest")
        echo "Daily digest feature not yet implemented."
        # Future logic:
        # 1. Get all changes from the last 24 hours from CHANGE_LOG_FILE
        # 2. Group them by competitor
        # 3. Format a summary report
        # 4. Send the report
        ;;
    "list")
        echo "--- Recent Changes ---"
        jq -r 'to_entries[] | .value | "\(.timestamp) | \(.competitor) (\(.page)) | Score: \(.score)"' "$CHANGE_LOG_FILE" | sort -r | head -n 20
        ;;
esac

exit 0

```

### scripts/setup.sh

```bash
#!/bin/bash
# competitor-watch/scripts/setup.sh β€” Initialize config and data directories

set -euo pipefail

CONFIG_DIR="${CW_CONFIG_DIR:-$HOME/.config/competitor-watch}"
SKILL_DIR="$(cd "$(dirname "$0")/.." && pwd)"

echo "πŸ” Competitor Watch Setup"
echo "━━━━━━━━━━━━━━━━━━━━━━━━"

# Create main config directory
mkdir -p "$CONFIG_DIR"
echo "βœ“ Created config directory: $CONFIG_DIR"

# Copy example config if none exists
if [ ! -f "$CONFIG_DIR/config.json" ]; then
  cp "$SKILL_DIR/config.example.json" "$CONFIG_DIR/config.json"
  echo "βœ“ Copied example configuration to config.json"
  echo "  β†ͺ Please edit this file with your competitors and settings."
else
  echo "β€’ config.json already exists (skipped copy)"
fi

# Create data directories
mkdir -p "$CONFIG_DIR/data/snapshots"
mkdir -p "$CONFIG_DIR/reports"
echo "βœ“ Created data and reports directories"

# Initialize data files
for file in change-log.json last-checks.json alert-history.json; do
  if [ ! -f "$CONFIG_DIR/data/$file" ]; then
    echo '{}' > "$CONFIG_DIR/data/$file"
    echo "βœ“ Created empty data file: $file"
  else
    echo "β€’ Data file $file already exists (skipped creation)"
  fi
done

echo ""
echo "πŸŽ‰ Setup complete!"
echo ""
echo "Next steps:"
echo "  1. Edit $CONFIG_DIR/config.json to define your competitors and tiers."
echo "  2. Use the 'add-competitor.sh' script to easily add new companies."
echo "     β†’ scripts/add-competitor.sh \"Acme Corp\" https://acme.com --tier fierce"
echo "  3. Run your first check (in dry-run mode to test):"
echo "     β†’ scripts/check.sh --dry-run"
echo ""
echo "🎯 Your agent is now ready to watch the competition."

```

### scripts/diff.sh

```bash
#!/bin/bash
# competitor-watch/scripts/diff.sh β€” Compare two snapshots and score changes

set -euo pipefail

# --- Config ---
CONFIG_DIR="${CW_CONFIG_DIR:-$HOME/.config/competitor-watch}"
CONFIG_FILE="$CONFIG_DIR/config.json"

# --- Arguments ---
if [ "$#" -ne 2 ]; then
    echo "Usage: $0 <old_snapshot_file> <new_snapshot_file>"
    exit 1
fi
OLD_FILE="$1"
NEW_FILE="$2"

# --- Validation ---
if ! command -v jq &> /dev/null; then echo "jq not installed"; exit 1; fi
if [ ! -f "$OLD_FILE" ] || [ ! -f "$NEW_FILE" ]; then echo "Snapshot file not found"; exit 1; fi

# --- Pre-processing: Filter noise ---
IGNORE_PATTERNS=$(jq -r '.diffing.ignore_patterns | join("|")' "$CONFIG_FILE")
MIN_CHANGE_CHARS=$(jq -r '.diffing.min_change_chars' "$CONFIG_FILE")
SIMILARITY_THRESHOLD=$(jq -r '.diffing.similarity_threshold' "$CONFIG_FILE")

# Use temp files for cleaned content
OLD_CLEAN=$(mktemp)
NEW_CLEAN=$(mktemp)
trap 'rm -f "$OLD_CLEAN" "$NEW_CLEAN"' EXIT

# This is a simple grep-based filter. More complex filtering could use sed.
grep -Eiv "$IGNORE_PATTERNS" "$OLD_FILE" > "$OLD_CLEAN"
grep -Eiv "$IGNORE_PATTERNS" "$NEW_FILE" > "$NEW_CLEAN"

# --- Diff Calculation ---
# Use diff with a unified format for better parsing
RAW_DIFF=$(diff -u "$OLD_CLEAN" "$NEW_CLEAN" || true)

if [ -z "$RAW_DIFF" ]; then
    # No changes at all, exit successfully
    exit 0
fi

LINES_ADDED=$(echo "$RAW_DIFF" | grep -c '^+' || true)
LINES_REMOVED=$(echo "$RAW_DIFF" | grep -c '^-' || true)
# Subtract the file headers from the count
LINES_ADDED=$((LINES_ADDED - 1))
LINES_REMOVED=$((LINES_REMOVED - 1))

TOTAL_CHANGES=$((LINES_ADDED + LINES_REMOVED))
TOTAL_LINES=$(wc -l < "$NEW_CLEAN")

# Avoid division by zero
if [ "$TOTAL_LINES" -eq 0 ]; then
    CHANGE_PERCENT=0
else
    CHANGE_PERCENT=$(awk "BEGIN {printf \"%.4f\", $TOTAL_CHANGES / $TOTAL_LINES}")
fi

# --- Scoring ---
# This is a simplistic scoring model. It could be expanded significantly.
# We'll use the percentage change as a base score.
SCORE=$CHANGE_PERCENT

# Boost score based on keywords
KEYWORDS_CONFIG=$(jq -c '.diffing.keyword_weights' "$CONFIG_FILE")
echo "$KEYWORDS_CONFIG" | jq -c 'to_entries[]' | while IFS= read -r entry; do
    group_name=$(echo "$entry" | jq -r '.key')
    keywords=$(echo "$entry" | jq -r '.value.keywords | join("|")')
    weight=$(echo "$entry" | jq -r '.value.weight')

    # Count keyword matches in added lines
    matches=$(echo "$RAW_DIFF" | grep -E '^\+' | grep -Eic "($keywords)" || true)
    
    if [ "$matches" -gt 0 ]; then
        boost=$(awk "BEGIN {printf \"%.4f\", $matches * $weight * 0.1}")
        SCORE=$(awk "BEGIN {printf \"%.4f\", $SCORE + $boost}")
    fi
done

# --- Decision ---
# Compare score to 1 minus the similarity threshold
SIGNIFICANCE_THRESHOLD=$(awk "BEGIN {printf \"%.4f\", 1 - $SIMILARITY_THRESHOLD}")

# Create summary
SUMMARY_ADDED=$(echo "$RAW_DIFF" | grep '^\+' | sed 's/^+//' | head -n 5 | sed 's/^/β€’ /')
SUMMARY_REMOVED=$(echo "$RAW_DIFF" | grep '^-' | sed 's/^-//' | head -n 5 | sed 's/^/β€’ /')

# --- Output JSON ---
# This structured output is consumed by check.sh
jq -n \
    --argjson score "$SCORE" \
    --argjson changes "$TOTAL_CHANGES" \
    --argjson added "$LINES_ADDED" \
    --argjson removed "$LINES_REMOVED" \
    --argjson percent "$CHANGE_PERCENT" \
    --arg diff "$RAW_DIFF" \
    --arg summary_added "$SUMMARY_ADDED" \
    --arg summary_removed "$SUMMARY_REMOVED" \
    '{
        score: $score,
        total_changes: $changes,
        lines_added: $added,
        lines_removed: $removed,
        change_percentage: $percent,
        summary: {
            added: $summary_added,
            removed: $summary_removed
        },
        raw_diff: $diff
    }'

# --- Exit Code ---
# Exit with 1 if change is significant, 0 otherwise
if (( $(awk "BEGIN {print ($SCORE > $SIGNIFICANCE_THRESHOLD)}") )); then
    exit 1
else
    exit 0
fi

```



---

## Skill Companion Files

> Additional files collected from the skill directory layout.

### _meta.json

```json
{
  "owner": "audsmith28",
  "slug": "competitor-watch",
  "displayName": "Competitor Watch",
  "latest": {
    "version": "1.1.0",
    "publishedAt": 1770842531466,
    "commit": "https://github.com/openclaw/skills/commit/9aea1bf5295756bf410f4e97d668dd9c926d1d4b"
  },
  "history": []
}

```

competitor-watch | SkillHub