Skill: nicowu07/supermarket-sales (version 1.2.0)

--- name: supermarket-sales description: Fetch weekly sale details from Australian supermarkets (Woolworths and Coles). Use when the user wants to check current specials, compare prices, or get sale information from Woolworths or Coles. --- # Supermarket Sales Fetcher Fetch weekly specials and sale details from Woolworths and Coles Australia. ## Overview This skill retrieves current supermarket specials from Australia's two major grocery chains: - **Coles** - Direct fetch via Puppeteer browser automation - **Woolworths** - Via trusted third-party aggregators (official site blocks automated access) Works well for Melbourne metro area (postcode 3000-3008). ## Installation ```bash # Clone and install dependencies npm install ``` This will install: - puppeteer (^22.0.0) - Browser automation Requires: Node.js 18+, npm ## Usage ### Common Commands | Task | Command | |------|--------| | Fetch all deals | `npm start` or `node scripts/fetch_daily.js` | | Coles only | `npm run coles` or `node scripts/fetch_with_puppeteer.js` | | Bash (basic) | `bash scripts/fetch_sales.sh` | ### Alternative: Web Fetch For Woolworths deals without running code: ``` web_fetch: { "url": "https://www.catalogueau.com/sales/?stores=Woolworths" } web_fetch: { "url": "https://currentspecials.com.au/woolworths-weekly-catalogue/" } ``` ### Alternative: Search ``` web_search_plus: { "query": "Woolworths Coles specials this week Melbourne" } ``` ## How It Works ### Coles Uses Puppeteer to launch a headless browser, navigate to coles.com.au/on-special, and extract product cards with prices and savings. ### Woolworths Uses trusted aggregators (catalogueau.com, currentspecials.com.au) since woolworths.com.au blocks automated access. ## Output Sample output: ``` === COLES SPECIALS - 11 Apr 2026 === | # | Product | Price | Deal | |---|---|---| | 1 | Coles Strawberries 250g | $4.00 | Was $16/kg | | 2 | Staminade Powder 585g | $8.00 | Save $3.50 | ``` ## Sources | Store | Method | URL | |-------|-------|-----| | Coles | Puppeteer | coles.com.au/on-special | | Woolworths | Aggregator | catalogueau.com/woolworths/ | | Both | Search | web_search_plus | ## Troubleshooting ### Puppeteer not found Install Chromium: ```bash npx puppeteer browsers install chrome ``` ### No deals found - Site may have changed selectors - check for updates - Try alternative methods (web_fetch or search) ### Errors Check Node.js version: `node --version` (need 18+)

path: package.json
content: {
  "name": "supermarket-sales",
  "version": "1.1.0",
  "description": "Fetch weekly sale details from Australian supermarkets (Woolworths and Coles)",
  "main": "scripts/fetch_daily.js",
  "scripts": {
    "start": "node scripts/fetch_daily.js",
    "coles": "node scripts/fetch_with_puppeteer.js"
  },
  "dependencies": {
    "puppeteer": "^22.0.0"
  },
  "engines": {
    "node": ">=18.0.0"
  },
  "keywords": ["australian", "supermarkets", "coles", "woolworths", "deals", "shopping"],
  "license": "MIT",
  "homepage": "https://clawhub.com/s/skills/supermarket-sales",
  "repository": {
    "type": "git",
    "url": "https://github.com/openclaw/skills"
  }
}

path: scripts/fetch_daily.js
content: #!/usr/bin/env node
/**
 * Daily Supermarket Sales Fetcher
 * Fetches Coles and Woolworths specials for the week
 * Run: node fetch_daily.js
 */

const puppeteer = require('puppeteer');

const COLES_URL = 'https://www.coles.com.au/on-special';
const WOOLWORTHS_CATALOGUE = 'https://www.catalogueau.com/woolworths/';
const WOOLWORTHS_SPECIALS = 'https://www.catalogueau.com/sales/?stores=Woolworths';

async function fetchColes() {
    const browser = await puppeteer.launch({ 
        headless: true,
        args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage']
    });
    const page = await browser.newPage();
    
    await page.setUserAgent('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36');
    await page.goto(COLES_URL, { waitUntil: 'networkidle2', timeout: 30000 });
    await new Promise(r => setTimeout(r, 5000));
    
    const deals = await page.evaluate(() => {
        const items = [];
        const cards = document.querySelectorAll('article, .product-tile, [data-testid*="product"], .product-card');
        
        cards.forEach(card => {
            const text = card.innerText.replace(/\n/g, ' ').trim();
            if (text.includes('$') && text.length > 10 && text.length < 200) {
                items.push(text.substring(0, 150));
            }
        });
        return items.slice(0, 20);
    });
    
    await browser.close();
    return deals;
}

function printHeader(store) {
    const date = new Date().toLocaleDateString('en-AU', { day: 'numeric', month: 'short', year: 'numeric' });
    console.log(`\n=== ${store.toUpperCase()} SPECIALS - ${date} ===`);
    console.log(`Source: ${store === 'Coles' ? COLES_URL : 'catalogueau.com'}\n`);
}

async function main() {
    console.log('🛒 Fetching Daily Supermarket Specials...\n');
    
    // Coles
    try {
        printHeader('Coles');
        const colesDeals = await fetchColes();
        
        if (colesDeals.length > 0) {
            console.log('| # | Product | Price | Deal |');
            console.log('|---|---|---|---|');
            colesDeals.slice(0, 10).forEach((deal, i) => {
                // Clean up and format
                const clean = deal.replace(/\s+/g, ' ').substring(0, 100);
                console.log(`| ${i+1} | ${clean.split('$')[0].substring(0, 30) || '-'} | $${clean.match(/\$[\d.]+/)?.[0]?.replace('$','') || '-'} | ${clean.match(/Save \$[\d.]+|Was \$[\d.]+/)?.[0] || '-'} |`);
            });
        } else {
            console.log('No deals found via browser. Try web_fetch instead.');
        }
    } catch(e) {
        console.log('Coles error:', e.message);
    }
    
    // Woolworths (from aggregator)
    console.log('\n=== WOOLWORTHS SPECIALS ===');
    console.log('Source: catalogueau.com');
    console.log('\nUse: web_fetch https://www.catalogueau.com/sales/?stores=Woolworths');
    console.log('Or search: Woolworths half price specials this week');
    
    console.log('\n✅ Done! Edit the skill or cron job for daily automation.');
}

main().catch(console.error);

path: scripts/fetch_sales.sh
content: #!/bin/bash
#
# Fetch supermarket specials using curl
# Usage: ./fetch_sales.sh [woolworths|coles|both]

STORE="${1:-both}"
USER_AGENT="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"

fetch_woolworths() {
    echo "=== Woolworths Specials ==="
    echo "Date: $(date +%Y-%m-%d)"
    echo "URL: https://www.woolworths.com.au/shop/catalogue"
    echo ""
    
    # Try to fetch the page
    HTML=$(curl -s -L "https://www.woolworths.com.au/shop/catalogue" \
        -H "User-Agent: $USER_AGENT" \
        -H "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" \
        -H "Accept-Language: en-US,en;q=0.5" \
        --max-time 30 2>/dev/null)
    
    if [ -z "$HTML" ]; then
        echo "Error: Could not fetch Woolworths page"
        echo "Note: This site requires JavaScript. Try visiting manually:"
        echo "https://www.woolworths.com.au/shop/catalogue"
        return 1
    fi
    
    # Check if we got meaningful content
    TITLE=$(echo "$HTML" | grep -o '<title>[^<]*</title>' | sed 's/<[^\u003e]*>//g')
    echo "Page title: $TITLE"
    echo "Content length: ${#HTML} characters"
    echo ""
    
    # Try to extract prices
    echo "Sample prices found:"
    echo "$HTML" | grep -oE '\$[0-9]+\.?[0-9]*' | head -10
    
    echo ""
    echo "Note: Full extraction may require JavaScript rendering."
    echo "Consider using browser automation for complete results."
}

fetch_coles() {
    echo "=== Coles Specials ==="
    echo "Date: $(date +%Y-%m-%d)"
    echo "URL: https://www.coles.com.au/on-special"
    echo ""
    
    # Try to fetch the page
    HTML=$(curl -s -L "https://www.coles.com.au/on-special" \
        -H "User-Agent: $USER_AGENT" \
        -H "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" \
        -H "Accept-Language: en-US,en;q=0.5" \
        --max-time 30 2>/dev/null)
    
    if [ -z "$HTML" ]; then
        echo "Error: Could not fetch Coles page"
        echo "Note: This site requires JavaScript. Try visiting manually:"
        echo "https://www.coles.com.au/on-special"
        return 1
    fi
    
    # Check if we got meaningful content
    TITLE=$(echo "$HTML" | grep -o '<title>[^<]*</title>' | sed 's/<[^\u003e]*>//g')
    echo "Page title: $TITLE"
    echo "Content length: ${#HTML} characters"
    echo ""
    
    # Try to extract prices
    echo "Sample prices found:"
    echo "$HTML" | grep -oE '\$[0-9]+\.?[0-9]*' | head -10
    
    echo ""
    echo "Note: Full extraction may require JavaScript rendering."
    echo "Consider using browser automation for complete results."
}

# Main execution
case "$STORE" in
    woolworths)
        fetch_woolworths
        ;;
    coles)
        fetch_coles
        ;;
    both)
        fetch_woolworths
        echo ""
        echo "========================================"
        echo ""
        fetch_coles
        ;;
    *)
        echo "Usage: $0 [woolworths|coles|both]"
        exit 1
        ;;
esac


path: scripts/fetch_with_puppeteer.js
content: #!/usr/bin/env node
/**
 * Fetch supermarket specials using Puppeteer browser automation
 * Usage: node fetch_with_puppeteer.js [woolworths|coles|both]
 */
const puppeteer = require('puppeteer');

async function fetchWoolworths() {
    const browser = await puppeteer.launch({ 
        headless: true,
        args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage']
    });
    const page = await browser.newPage();
    
    try {
        await page.setUserAgent('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36');
        await page.goto('https://www.woolworths.com.au/shop/catalogue', { 
            waitUntil: 'networkidle2',
            timeout: 30000 
        });
        
        await new Promise(r => setTimeout(r, 5000));
        
        const products = await page.evaluate(() => {
            const items = [];
            const cards = document.querySelectorAll('article, .product-tile, [data-testid*="product"], .product-card');
            
            cards.forEach(card => {
                const text = card.innerText.replace(/\n/g, ' ').trim();
                if (text.includes('$') && text.length > 10 && text.length < 200) {
                    items.push(text.substring(0, 150));
                }
            });
            return items.slice(0, 20);
        });
        
        return { specials: products, count: products.length };
    } catch (e) {
        return { error: e.message };
    } finally {
        await browser.close();
    }
}

async function fetchColes() {
    const browser = await puppeteer.launch({ 
        headless: true,
        args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage']
    });
    const page = await browser.newPage();
    
    try {
        await page.setUserAgent('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36');
        await page.goto('https://www.coles.com.au/on-special', { 
            waitUntil: 'networkidle2',
            timeout: 30000 
        });
        
        await new Promise(r => setTimeout(r, 5000));
        
        const products = await page.evaluate(() => {
            const items = [];
            const cards = document.querySelectorAll('article, .product-tile, [data-testid*="product"], .product-card');
            
            cards.forEach(card => {
                const text = card.innerText.replace(/\n/g, ' ').trim();
                if (text.includes('$') && text.length > 10 && text.length < 200) {
                    items.push(text.substring(0, 150));
                }
            });
            return items.slice(0, 20);
        });
        
        return { specials: products, count: products.length };
    } catch (e) {
        return { error: e.message };
    } finally {
        await browser.close();
    }
}

async function main() {
    const args = process.argv.slice(2);
    const which = args[0] || 'both';
    
    let woolworths = null;
    let coles = null;
    
    if (which === 'woolworths' || which === 'both') {
        console.log('Fetching Woolworths...');
        woolworths = await fetchWoolworths();
    }
    
    if (which === 'coles' || which === 'both') {
        console.log('Fetching Coles...');
        coles = await fetchColes();
    }
    
    console.log(JSON.stringify({ woolworths, coles }, null, 2));
}

main().catch(console.error);

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