Skill: huberteff/hfnews (version 1.0.0)

--- name: hfnews description: Fetch and filter news from multiple sources with stopwords/blacklist support. Customized for Hubert's interests (IT, Cybersecurity) with political/sports noise filtered out. --- # News Fetcher ## Usage ```bash # All categories news # Specific category news Allgemeines news IT news Cybersecurity ``` ## Output Format Simple list: ``` Allgemeines: - Titel URL - Titel URL - Titel URL - Titel URL - Titel URL IT: - Titel URL - Titel URL - Titel URL - Titel URL - Titel URL Cybersecurity: - Titel URL - Titel URL - Titel URL - Titel URL - Titel URL ``` ## Blacklist Words to filter out: - Sport, Trump/USA, SPD, Iran, Bürgergeld, Mietreform, Mieterschutz - Regenpause, Ukraine, Putin, Epstein - Bilder des Tages, Karrierefrage - Stellenmarkt, Jobs ## Categories ### Allgemeines - Tagesschau: https://www.tagesschau.de/ - FAZ: https://www.faz.net/aktuell/ - WiWo: https://www.wiwo.de/ - Süddeutsche: https://www.sueddeutsche.de/ - Spiegel: https://www.spiegel.de/ - Mittelbayerische: https://www.mittelbayerische.de/lokales/stadt-regensburg ### IT - Heise: https://www.heise.de/ - Golem: https://www.golem.de/ - Slashdot: https://slashdot.org/ ### Cybersecurity - The Hacker News: https://thehackernews.com/ - BleepingComputer: https://www.bleepingcomputer.com/ - Logbuch Netzpolitik: https://logbuch-netzpolitik.de/

path: fetcher.js
content: #!/usr/bin/env node
/**
 * News fetcher - grouped by topic, output as simple list
 */

const puppeteer = require('puppeteer');

// Blacklist
const BLACKLIST = new Set([
    "sport", "sportlich", "sports",
    "trump", "usa", "vereinigte staaten",
    "spd", "sozialdemokrat",
    "iran",
    "bürgergeld", "buergergeld",
    "mietreform", "miet-reform", "mieterschutz",
    "regenpause",
    "ukraine", "ukrainisch",
    "putin", "russland",
    "epstein", "jeffrey epstein",
    "bilder des tages", "bild des tages",
    "karrierefrage", "karriere frage",
    "streik", "streiks", "streikende",
    "ivanti",
    "fortinet",
    "solarwinds"
]);

// Navigation patterns to exclude
const NAV_PATTERNS = [
    /^\/(thema|rubrik|kategorie|section|archive|archiv|alle|index|startseite)/i,
    /\/abo|\/subscribe|\/login|\/newsletter/i,
    /wetter|horoskop|unterhaltung/i,
    /stellenmarkt|jobs?-|karriere/i
];

// News sources
const SOURCES = {
    Allgemeines: [
        { name: "Tagesschau", url: "https://www.tagesschau.de/" },
        { name: "FAZ", url: "https://www.faz.net/aktuell/" },
        { name: "WiWo", url: "https://www.wiwo.de/" },
        { name: "Süddeutsche", url: "https://www.sueddeutsche.de/" },
        { name: "Spiegel", url: "https://www.spiegel.de/" },
        { name: "Mittelbayerische", url: "https://www.mittelbayerische.de/lokales/stadt-regensburg" },
    ],
    IT: [
        { name: "Heise", url: "https://www.heise.de/" },
        { name: "Golem", url: "https://www.golem.de/" },
        { name: "Slashdot", url: "https://slashdot.org/" },
    ],
    Cybersecurity: [
        { name: "The Hacker News", url: "https://thehackernews.com/" },
        { name: "BleepingComputer", url: "https://www.bleepingcomputer.com/" },
        { name: "Logbuch Netzpolitik", url: "https://logbuch-netzpolitik.de/" },
    ]
};

function isFiltered(text) {
    if (!text) return true;
    const lower = text.toLowerCase();
    for (const word of BLACKLIST) {
        if (lower.includes(word)) return true;
    }
    return false;
}

function isNavigation(url, title) {
    for (const pattern of NAV_PATTERNS) {
        if (pattern.test(url)) return true;
    }
    const genericTitles = ['startseite', 'alle nachrichten', 'neueste nachrichten' ];
    if (genericTitles.includes(title.toLowerCase())) return true;
    return false;
}

async function fetchSource(url, sourceName) {
    let browser = null;
    try {
        browser = await puppeteer.launch({
            headless: true,
            executablePath: '/usr/bin/chromium',
            args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', '--disable-gpu']
        });
        
        const page = await browser.newPage();
        await page.setViewport({ width: 1280, height: 800 });
        await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36');
        await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000 });
        
        const headlines = await page.evaluate(() => {
            const links = [];
            document.querySelectorAll('a').forEach(a => {
                const href = a.getAttribute('href');
                const text = a.innerText.trim();
                if (href && text && text.length > 25 && text.length < 180) {
                    links.push({ href, text });
                }
            });
            return links;
        });
        
        const articles = [];
        for (const h of headlines) {
            if (isNavigation(h.href, h.text)) continue;
            if (isFiltered(h.text) || isFiltered(h.href)) continue;
            
            let fullUrl = h.href;
            try {
                if (h.href.startsWith('/')) {
                    fullUrl = new URL(h.href, url).href;
                } else if (h.href.startsWith('http')) {
                    fullUrl = h.href;
                } else {
                    continue;
                }
            } catch (e) {
                continue;
            }
            
            articles.push({
                title: h.text,
                url: fullUrl,
                source: sourceName
            });
        }
        
        return articles.slice(0, 10);
        
    } catch (error) {
        console.error(`  [Error ${sourceName}: ${error.message}]`);
        return [];
    } finally {
        if (browser) await browser.close();
    }
}

function extractKeywords(title) {
    const lower = title.toLowerCase();
    const keywords = new Set();
    
    // Key topics
    const topics = {
        'afd': ['afd', 'generation deutschland', 'alternative für deutschland'],
        'bsw': ['bsw', 'bündnis sahra wagenknecht', 'sahra wagenknecht'],
        'ampel': ['ampel', 'ampelkoalition'],
        'migration': ['migration', 'asyl', 'flüchtlinge', 'grenz'],
        'wirtschaft': ['wirtschaft', 'wirtschaftswachstum', 'bruttoinlandsprodukt'],
        'energie': ['energie', 'strom', 'gas', 'energiekrise', 'erneuerbar'],
        'klima': ['klima', 'klimawandel', 'co2', 'temperaturanstieg'],
        'israel': ['israel', 'gaza', 'palästina', 'nahost', 'herzog'],
        'china': ['china', 'chinesisch', 'peking'],
        'europa': ['europa', 'europäische union', 'eu', 'brüssel'],
        'bahn': ['bahn', 'zug', 'deutsche bahn'],
        'gesundheit': ['gesundheit', 'krankenhaus', 'ärzte', 'pflege'],
        'rente': ['rente', 'altersrente'],
        'bildung': ['bildung', 'schule', 'universität', 'studium'],
        'digital': ['digital', 'ki', 'kuenstliche intelligenz', 'ai'],
        'cybersecurity': ['cybersecurity', 'cyber', 'hack', 'sicherheitslücke', 'cve'],
        'bitcoin': ['bitcoin', 'crypto', 'kryptowährung'],
        'immobilien': ['immobilien', 'immobilienmarkt', 'miete', 'wohnung'],
        'elektromobilitaet': ['elektromobil', 'e-auto', 'elektroauto', 'ev'],
        'sozialstaat': ['sozialstaat', 'sozialleistungen', 'bürgergeld']
    };
    
    for (const [topic, topicKeywords] of Object.entries(topics)) {
        for (const kw of topicKeywords) {
            if (lower.includes(kw)) {
                keywords.add(topic);
                break;
            }
        }
    }
    
    // Add significant words
    const stopWords = new Set(['und', 'die', 'der', 'das', 'für', 'auf', 'von', 'aus', 'mit', 'bei', 'nach', 'wie', 'was', 'auch', 'nur', 'noch', 'sich', 'nicht', 'über', 'zum', 'zur', 'den', 'dem', 'ein', 'eine', 'einer', 'einem', 'einen', 'ist', 'war', 'wird', 'wurden', 'wurde', 'hat', 'haben', 'hatte', 'hatten', 'kann', 'können', 'konnte', 'konnten', 'muss', 'müssen', 'musste', 'mussten', 'soll', 'sollen', 'sollte', 'sollten', 'will', 'wollen', 'wollte', 'wollten', 'darf', 'dürfen', 'durfte', 'durften', 'mag', 'mögen', 'mochte', 'mochten', 'trotz', 'wegen', 'ins', 'ans', 'beim', 'unter', 'gegen', 'zwischen', 'heute', 'gestern', 'morgen', 'jetzt', 'schon', 'immer', 'wieder', 'erst', 'mehr', 'viel', 'wenig', 'alle', 'alles', 'jeder', 'jede', 'jedes', 'niemand', 'jemand', 'etwas', 'nichts', 'kein', 'keine', 'keiner', 'keinem', 'keinen', 'neue', 'neuen', 'neuer', 'neues', 'erste', 'ersten', 'zweite', 'dritte', 'welt', 'land', 'frage', 'antwort', 'jahre', 'jahren', 'tag', 'tagen', 'monat', 'monate', 'politik', 'politikern', 'parteien', 'politik', 'politik', 'video', 'livestream', 'ard', 'faz', 'wiwo', 'süddeutsche', 'sz', 'pro', 'edition', 'epaper', 'digital', 'a+,', ' Sonntagszeitung', 'faz.net']);
    
    const words = lower.split(/\W+/).filter(w => w.length > 4 && !stopWords.has(w));
    words.slice(0, 5).forEach(w => keywords.add(w.slice(0, 10)));
    
    return Array.from(keywords);
}

function groupArticles(articles) {
    const groups = [];
    const used = new Set();
    
    for (let i = 0; i < articles.length; i++) {
        if (used.has(i)) continue;
        
        const a1 = articles[i];
        const kw1 = extractKeywords(a1.title);
        
        const group = {
            title: a1.title.length > 70 ? a1.title.slice(0, 70) + '...' : a1.title,
            keywords: kw1,
            links: [{ source: a1.source, url: a1.url }]
        };
        
        for (let j = i + 1; j < articles.length; j++) {
            if (used.has(j)) continue;
            
            const a2 = articles[j];
            // Skip same source
            if (a2.source === a1.source) continue;
            
            const kw2 = extractKeywords(a2.title);
            
            // Calculate similarity
            const set1 = new Set(kw1);
            const set2 = new Set(kw2);
            const intersection = [...set1].filter(x => set2.has(x));
            const union = new Set([...set1, ...set2]);
            const similarity = union.size > 0 ? intersection.length / union.size : 0;
            
            if (similarity > 0.35) {
                used.add(j);
                group.links.push({ source: a2.source, url: a2.url });
                // Use shorter/better title
                if (a2.title.length < group.title.length && a2.title.length > 25) {
                    group.title = a2.title.length > 70 ? a2.title.slice(0, 70) + '...' : a2.title;
                }
            }
        }
        
        used.add(i);
        groups.push(group);
    }
    
    // Sort by number of links (multi-source first)
    groups.sort((a, b) => b.links.length - a.links.length);
    
    return groups;
}

async function fetchCategory(category, sources) {
    console.log(`\n${category}:`);
    
    let allArticles = [];
    for (const source of sources) {
        console.log(`  ${source.name}...`);
        const articles = await fetchSource(source.url, source.name);
        allArticles = allArticles.concat(articles);
    }
    
    if (allArticles.length === 0) {
        console.log("  (keine Artikel gefunden)");
        return;
    }
    
    const groups = groupArticles(allArticles);
    
    for (const group of groups.slice(0, 12)) {
        const links = group.links.map(l => `${l.url}`).join(' ');
        console.log(`- ${group.title} ${links}`);
    }
}

async function main() {
    const category = process.argv[2]?.toLowerCase();
    
    if (category && !SOURCES[category]) {
        console.log(`Usage: node fetcher.js [general|it|cybersecurity]`);
        console.log(`Categories: ${Object.keys(SOURCES).join(', ')}`);
        process.exit(1);
    }
    
    const categories = category ? [category] : Object.keys(SOURCES);
    
    for (const cat of categories) {
        await fetchCategory(cat, SOURCES[cat]);
    }
}

main().catch(console.error);


path: package-lock.json
content: {
  "name": "news-fetcher",
  "version": "1.0.0",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "": {
      "name": "news-fetcher",
      "version": "1.0.0",
      "dependencies": {
        "puppeteer": "^23.0.0"
      }
    },
    "node_modules/@babel/code-frame": {
      "version": "7.29.0",
      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "@babel/helper-validator-identifier": "^7.28.5",
        "js-tokens": "^4.0.0",
        "picocolors": "^1.1.1"
      },
      "engines": {
        "node": ">=6.9.0"
      }
    },
    "node_modules/@babel/helper-validator-identifier": {
      "version": "7.28.5",
      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=6.9.0"
      }
    },
    "node_modules/@puppeteer/browsers": {
      "version": "2.6.1",
      "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.6.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "Apache-2.0",
      "dependencies": {
        "debug": "^4.4.0",
        "extract-zip": "^2.0.1",
        "progress": "^2.0.3",
        "proxy-agent": "^6.5.0",
        "semver": "^7.6.3",
        "tar-fs": "^3.0.6",
        "unbzip2-stream": "^1.4.3",
        "yargs": "^17.7.2"
      },
      "bin": {
        "browsers": "lib/cjs/main-cli.js"
      },
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@tootallnate/quickjs-emscripten": {
      "version": "0.23.0",
      "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/@types/node": {
      "version": "25.2.2",
      "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "optional": true,
      "dependencies": {
        "undici-types": "~7.16.0"
      }
    },
    "node_modules/@types/yauzl": {
      "version": "2.10.3",
      "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "optional": true,
      "dependencies": {
        "@types/node": "*"
      }
    },
    "node_modules/agent-base": {
      "version": "7.1.4",
      "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 14"
      }
    },
    "node_modules/ansi-regex": {
      "version": "5.0.1",
      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/ansi-styles": {
      "version": "4.3.0",
      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "color-convert": "^2.0.1"
      },
      "engines": {
        "node": ">=8"
      },
      "funding": {
        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
      }
    },
    "node_modules/argparse": {
      "version": "2.0.1",
      "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "Python-2.0"
    },
    "node_modules/ast-types": {
      "version": "0.13.4",
      "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "tslib": "^2.0.1"
      },
      "engines": {
        "node": ">=4"
      }
    },
    "node_modules/b4a": {
      "version": "1.7.3",
      "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "Apache-2.0",
      "peerDependencies": {
        "react-native-b4a": "*"
      },
      "peerDependenciesMeta": {
        "react-native-b4a": {
          "optional": true
        }
      }
    },
    "node_modules/bare-events": {
      "version": "2.8.2",
      "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "Apache-2.0",
      "peerDependencies": {
        "bare-abort-controller": "*"
      },
      "peerDependenciesMeta": {
        "bare-abort-controller": {
          "optional": true
        }
      }
    },
    "node_modules/bare-fs": {
      "version": "4.5.3",
      "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.3.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "Apache-2.0",
      "optional": true,
      "dependencies": {
        "bare-events": "^2.5.4",
        "bare-path": "^3.0.0",
        "bare-stream": "^2.6.4",
        "bare-url": "^2.2.2",
        "fast-fifo": "^1.3.2"
      },
      "engines": {
        "bare": ">=1.16.0"
      },
      "peerDependencies": {
        "bare-buffer": "*"
      },
      "peerDependenciesMeta": {
        "bare-buffer": {
          "optional": true
        }
      }
    },
    "node_modules/bare-os": {
      "version": "3.6.2",
      "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "Apache-2.0",
      "optional": true,
      "engines": {
        "bare": ">=1.14.0"
      }
    },
    "node_modules/bare-path": {
      "version": "3.0.0",
      "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "Apache-2.0",
      "optional": true,
      "dependencies": {
        "bare-os": "^3.0.1"
      }
    },
    "node_modules/bare-stream": {
      "version": "2.7.0",
      "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.7.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "Apache-2.0",
      "optional": true,
      "dependencies": {
        "streamx": "^2.21.0"
      },
      "peerDependencies": {
        "bare-buffer": "*",
        "bare-events": "*"
      },
      "peerDependenciesMeta": {
        "bare-buffer": {
          "optional": true
        },
        "bare-events": {
          "optional": true
        }
      }
    },
    "node_modules/bare-url": {
      "version": "2.3.2",
      "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.3.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "Apache-2.0",
      "optional": true,
      "dependencies": {
        "bare-path": "^3.0.0"
      }
    },
    "node_modules/base64-js": {
      "version": "1.5.1",
      "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
      "integrity": [REDACTED_SECRET],
      "funding": [
        {
          "type": "github",
          "url": "https://github.com/sponsors/feross"
        },
        {
          "type": "patreon",
          "url": "https://www.patreon.com/feross"
        },
        {
          "type": "consulting",
          "url": "https://feross.org/support"
        }
      ],
      "license": "MIT"
    },
    "node_modules/basic-ftp": {
      "version": "5.1.0",
      "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=10.0.0"
      }
    },
    "node_modules/buffer": {
      "version": "5.7.1",
      "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
      "integrity": [REDACTED_SECRET],
      "funding": [
        {
          "type": "github",
          "url": "https://github.com/sponsors/feross"
        },
        {
          "type": "patreon",
          "url": "https://www.patreon.com/feross"
        },
        {
          "type": "consulting",
          "url": "https://feross.org/support"
        }
      ],
      "license": "MIT",
      "dependencies": {
        "base64-js": "^1.3.1",
        "ieee754": "^1.1.13"
      }
    },
    "node_modules/buffer-crc32": {
      "version": "0.2.13",
      "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": "*"
      }
    },
    "node_modules/callsites": {
      "version": "3.1.0",
      "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=6"
      }
    },
    "node_modules/chromium-bidi": {
      "version": "0.11.0",
      "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.11.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "Apache-2.0",
      "dependencies": {
        "mitt": "3.0.1",
        "zod": "3.23.8"
      },
      "peerDependencies": {
        "devtools-protocol": "*"
      }
    },
    "node_modules/cliui": {
      "version": "8.0.1",
      "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "ISC",
      "dependencies": {
        "string-width": "^4.2.0",
        "strip-ansi": "^6.0.1",
        "wrap-ansi": "^7.0.0"
      },
      "engines": {
        "node": ">=12"
      }
    },
    "node_modules/color-convert": {
      "version": "2.0.1",
      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "color-name": "~1.1.4"
      },
      "engines": {
        "node": ">=7.0.0"
      }
    },
    "node_modules/color-name": {
      "version": "1.1.4",
      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/cosmiconfig": {
      "version": "9.0.0",
      "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "env-paths": "^2.2.1",
        "import-fresh": "^3.3.0",
        "js-yaml": "^4.1.0",
        "parse-json": "^5.2.0"
      },
      "engines": {
        "node": ">=14"
      },
      "funding": {
        "url": "https://github.com/sponsors/d-fischer"
      },
      "peerDependencies": {
        "typescript": ">=4.9.5"
      },
      "peerDependenciesMeta": {
        "typescript": {
          "optional": true
        }
      }
    },
    "node_modules/data-uri-to-buffer": {
      "version": "6.0.2",
      "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 14"
      }
    },
    "node_modules/debug": {
      "version": "4.4.3",
      "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "ms": "^2.1.3"
      },
      "engines": {
        "node": ">=6.0"
      },
      "peerDependenciesMeta": {
        "supports-color": {
          "optional": true
        }
      }
    },
    "node_modules/degenerator": {
      "version": "5.0.1",
      "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "ast-types": "^0.13.4",
        "escodegen": "^2.1.0",
        "esprima": "^4.0.1"
      },
      "engines": {
        "node": ">= 14"
      }
    },
    "node_modules/devtools-protocol": {
      "version": "0.0.1367902",
      "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1367902.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "BSD-3-Clause"
    },
    "node_modules/emoji-regex": {
      "version": "8.0.0",
      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/end-of-stream": {
      "version": "1.4.5",
      "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "once": "^1.4.0"
      }
    },
    "node_modules/env-paths": {
      "version": "2.2.1",
      "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=6"
      }
    },
    "node_modules/error-ex": {
      "version": "1.3.4",
      "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "is-arrayish": "^0.2.1"
      }
    },
    "node_modules/escalade": {
      "version": "3.2.0",
      "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=6"
      }
    },
    "node_modules/escodegen": {
      "version": "2.1.0",
      "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "BSD-2-Clause",
      "dependencies": {
        "esprima": "^4.0.1",
        "estraverse": "^5.2.0",
        "esutils": "^2.0.2"
      },
      "bin": {
        "escodegen": "bin/escodegen.js",
        "esgenerate": "bin/esgenerate.js"
      },
      "engines": {
        "node": ">=6.0"
      },
      "optionalDependencies": {
        "source-map": "~0.6.1"
      }
    },
    "node_modules/esprima": {
      "version": "4.0.1",
      "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "BSD-2-Clause",
      "bin": {
        "esparse": "bin/esparse.js",
        "esvalidate": "bin/esvalidate.js"
      },
      "engines": {
        "node": ">=4"
      }
    },
    "node_modules/estraverse": {
      "version": "5.3.0",
      "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "BSD-2-Clause",
      "engines": {
        "node": ">=4.0"
      }
    },
    "node_modules/esutils": {
      "version": "2.0.3",
      "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "BSD-2-Clause",
      "engines": {
        "node": ">=0.10.0"
      }
    },
    "node_modules/events-universal": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "Apache-2.0",
      "dependencies": {
        "bare-events": "^2.7.0"
      }
    },
    "node_modules/extract-zip": {
      "version": "2.0.1",
      "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "BSD-2-Clause",
      "dependencies": {
        "debug": "^4.1.1",
        "get-stream": "^5.1.0",
        "yauzl": "^2.10.0"
      },
      "bin": {
        "extract-zip": "cli.js"
      },
      "engines": {
        "node": ">= 10.17.0"
      },
      "optionalDependencies": {
        "@types/yauzl": "^2.9.1"
      }
    },
    "node_modules/fast-fifo": {
      "version": "1.3.2",
      "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/fd-slicer": {
      "version": "1.1.0",
      "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "pend": "~1.2.0"
      }
    },
    "node_modules/get-caller-file": {
      "version": "2.0.5",
      "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "ISC",
      "engines": {
        "node": "6.* || 8.* || >= 10.*"
      }
    },
    "node_modules/get-stream": {
      "version": "5.2.0",
      "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "pump": "^3.0.0"
      },
      "engines": {
        "node": ">=8"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/get-uri": {
      "version": "6.0.5",
      "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "basic-ftp": "^5.0.2",
        "data-uri-to-buffer": "^6.0.2",
        "debug": "^4.3.4"
      },
      "engines": {
        "node": ">= 14"
      }
    },
    "node_modules/http-proxy-agent": {
      "version": "7.0.2",
      "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "agent-base": "^7.1.0",
        "debug": "^4.3.4"
      },
      "engines": {
        "node": ">= 14"
      }
    },
    "node_modules/https-proxy-agent": {
      "version": "7.0.6",
      "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "agent-base": "^7.1.2",
        "debug": "4"
      },
      "engines": {
        "node": ">= 14"
      }
    },
    "node_modules/ieee754": {
      "version": "1.2.1",
      "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
      "integrity": [REDACTED_SECRET],
      "funding": [
        {
          "type": "github",
          "url": "https://github.com/sponsors/feross"
        },
        {
          "type": "patreon",
          "url": "https://www.patreon.com/feross"
        },
        {
          "type": "consulting",
          "url": "https://feross.org/support"
        }
      ],
      "license": "BSD-3-Clause"
    },
    "node_modules/import-fresh": {
      "version": "3.3.1",
      "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "parent-module": "^1.0.0",
        "resolve-from": "^4.0.0"
      },
      "engines": {
        "node": ">=6"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/ip-address": {
      "version": "10.1.0",
      "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 12"
      }
    },
    "node_modules/is-arrayish": {
      "version": "0.2.1",
      "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/is-fullwidth-code-point": {
      "version": "3.0.0",
      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/js-tokens": {
      "version": "4.0.0",
      "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/js-yaml": {
      "version": "4.1.1",
      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "argparse": "^2.0.1"
      },
      "bin": {
        "js-yaml": "bin/js-yaml.js"
      }
    },
    "node_modules/json-parse-even-better-errors": {
      "version": "2.3.1",
      "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/lines-and-columns": {
      "version": "1.2.4",
      "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/lru-cache": {
      "version": "7.18.3",
      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "ISC",
      "engines": {
        "node": ">=12"
      }
    },
    "node_modules/mitt": {
      "version": "3.0.1",
      "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/ms": {
      "version": "2.1.3",
      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/netmask": {
      "version": "2.0.2",
      "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.4.0"
      }
    },
    "node_modules/once": {
      "version": "1.4.0",
      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "ISC",
      "dependencies": {
        "wrappy": "1"
      }
    },
    "node_modules/pac-proxy-agent": {
      "version": "7.2.0",
      "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "@tootallnate/quickjs-emscripten": "^0.23.0",
        "agent-base": "^7.1.2",
        "debug": "^4.3.4",
        "get-uri": "^6.0.1",
        "http-proxy-agent": "^7.0.0",
        "https-proxy-agent": "^7.0.6",
        "pac-resolver": "^7.0.1",
        "socks-proxy-agent": "^8.0.5"
      },
      "engines": {
        "node": ">= 14"
      }
    },
    "node_modules/pac-resolver": {
      "version": "7.0.1",
      "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "degenerator": "^5.0.0",
        "netmask": "^2.0.2"
      },
      "engines": {
        "node": ">= 14"
      }
    },
    "node_modules/parent-module": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "callsites": "^3.0.0"
      },
      "engines": {
        "node": ">=6"
      }
    },
    "node_modules/parse-json": {
      "version": "5.2.0",
      "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "@babel/code-frame": "^7.0.0",
        "error-ex": "^1.3.1",
        "json-parse-even-better-errors": "^2.3.0",
        "lines-and-columns": "^1.1.6"
      },
      "engines": {
        "node": ">=8"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/pend": {
      "version": "1.2.0",
      "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/picocolors": {
      "version": "1.1.1",
      "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "ISC"
    },
    "node_modules/progress": {
      "version": "2.0.3",
      "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=0.4.0"
      }
    },
    "node_modules/proxy-agent": {
      "version": "6.5.0",
      "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "agent-base": "^7.1.2",
        "debug": "^4.3.4",
        "http-proxy-agent": "^7.0.1",
        "https-proxy-agent": "^7.0.6",
        "lru-cache": "^7.14.1",
        "pac-proxy-agent": "^7.1.0",
        "proxy-from-env": "^1.1.0",
        "socks-proxy-agent": "^8.0.5"
      },
      "engines": {
        "node": ">= 14"
      }
    },
    "node_modules/proxy-from-env": {
      "version": "1.1.0",
      "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/pump": {
      "version": "3.0.3",
      "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "end-of-stream": "^1.1.0",
        "once": "^1.3.1"
      }
    },
    "node_modules/puppeteer": {
      "version": "23.11.1",
      "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-23.11.1.tgz",
      "integrity": [REDACTED_SECRET],
      "deprecated": "< 24.15.0 is no longer supported",
      "hasInstallScript": true,
      "license": "Apache-2.0",
      "dependencies": {
        "@puppeteer/browsers": "2.6.1",
        "chromium-bidi": "0.11.0",
        "cosmiconfig": "^9.0.0",
        "devtools-protocol": "0.0.1367902",
        "puppeteer-core": "23.11.1",
        "typed-query-selector": "^2.12.0"
      },
      "bin": {
        "puppeteer": "lib/cjs/puppeteer/node/cli.js"
      },
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/puppeteer-core": {
      "version": "23.11.1",
      "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-23.11.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "Apache-2.0",
      "dependencies": {
        "@puppeteer/browsers": "2.6.1",
        "chromium-bidi": "0.11.0",
        "debug": "^4.4.0",
        "devtools-protocol": "0.0.1367902",
        "typed-query-selector": "^2.12.0",
        "ws": "^8.18.0"
      },
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/require-directory": {
      "version": "2.1.1",
      "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=0.10.0"
      }
    },
    "node_modules/resolve-from": {
      "version": "4.0.0",
      "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=4"
      }
    },
    "node_modules/semver": {
      "version": "7.7.4",
      "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "ISC",
      "bin": {
        "semver": "bin/semver.js"
      },
      "engines": {
        "node": ">=10"
      }
    },
    "node_modules/smart-buffer": {
      "version": "4.2.0",
      "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 6.0.0",
        "npm": ">= 3.0.0"
      }
    },
    "node_modules/socks": {
      "version": "2.8.7",
      "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "ip-address": "^10.0.1",
        "smart-buffer": "^4.2.0"
      },
      "engines": {
        "node": ">= 10.0.0",
        "npm": ">= 3.0.0"
      }
    },
    "node_modules/socks-proxy-agent": {
      "version": "8.0.5",
      "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "agent-base": "^7.1.2",
        "debug": "^4.3.4",
        "socks": "^2.8.3"
      },
      "engines": {
        "node": ">= 14"
      }
    },
    "node_modules/source-map": {
      "version": "0.6.1",
      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "BSD-3-Clause",
      "optional": true,
      "engines": {
        "node": ">=0.10.0"
      }
    },
    "node_modules/streamx": {
      "version": "2.23.0",
      "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "events-universal": "^1.0.0",
        "fast-fifo": "^1.3.2",
        "text-decoder": "^1.1.0"
      }
    },
    "node_modules/string-width": {
      "version": "4.2.3",
      "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "emoji-regex": "^8.0.0",
        "is-fullwidth-code-point": "^3.0.0",
        "strip-ansi": "^6.0.1"
      },
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/strip-ansi": {
      "version": "6.0.1",
      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "ansi-regex": "^5.0.1"
      },
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/tar-fs": {
      "version": "3.1.1",
      "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "pump": "^3.0.0",
        "tar-stream": "^3.1.5"
      },
      "optionalDependencies": {
        "bare-fs": "^4.0.1",
        "bare-path": "^3.0.0"
      }
    },
    "node_modules/tar-stream": {
      "version": "3.1.7",
      "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "b4a": "^1.6.4",
        "fast-fifo": "^1.2.0",
        "streamx": "^2.15.0"
      }
    },
    "node_modules/text-decoder": {
      "version": "1.2.3",
      "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "Apache-2.0",
      "dependencies": {
        "b4a": "^1.6.4"
      }
    },
    "node_modules/through": {
      "version": "2.3.8",
      "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/tslib": {
      "version": "2.8.1",
      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "0BSD"
    },
    "node_modules/typed-query-selector": {
      "version": "2.12.0",
      "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/unbzip2-stream": {
      "version": "1.4.3",
      "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "buffer": "^5.2.1",
        "through": "^2.3.8"
      }
    },
    "node_modules/undici-types": {
      "version": "7.16.0",
      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "optional": true
    },
    "node_modules/wrap-ansi": {
      "version": "7.0.0",
      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "ansi-styles": "^4.0.0",
        "string-width": "^4.1.0",
        "strip-ansi": "^6.0.0"
      },
      "engines": {
        "node": ">=10"
      },
      "funding": {
        "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
      }
    },
    "node_modules/wrappy": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "ISC"
    },
    "node_modules/ws": {
      "version": "8.19.0",
      "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=10.0.0"
      },
      "peerDependencies": {
        "bufferutil": "^4.0.1",
        "utf-8-validate": ">=5.0.2"
      },
      "peerDependenciesMeta": {
        "bufferutil": {
          "optional": true
        },
        "utf-8-validate": {
          "optional": true
        }
      }
    },
    "node_modules/y18n": {
      "version": "5.0.8",
      "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "ISC",
      "engines": {
        "node": ">=10"
      }
    },
    "node_modules/yargs": {
      "version": "17.7.2",
      "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "cliui": "^8.0.1",
        "escalade": "^3.1.1",
        "get-caller-file": "^2.0.5",
        "require-directory": "^2.1.1",
        "string-width": "^4.2.3",
        "y18n": "^5.0.5",
        "yargs-parser": "^21.1.1"
      },
      "engines": {
        "node": ">=12"
      }
    },
    "node_modules/yargs-parser": {
      "version": "21.1.1",
      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "ISC",
      "engines": {
        "node": ">=12"
      }
    },
    "node_modules/yauzl": {
      "version": "2.10.0",
      "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "buffer-crc32": "~0.2.3",
        "fd-slicer": "~1.1.0"
      }
    },
    "node_modules/zod": {
      "version": "3.23.8",
      "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "funding": {
        "url": "https://github.com/sponsors/colinhacks"
      }
    }
  }
}


path: package.json
content: {
  "name": "news-fetcher",
  "version": "1.0.0",
  "description": "News fetcher with headless browser",
  "main": "fetcher.js",
  "dependencies": {
    "puppeteer": "^23.0.0"
  }
}


path: scripts/news.py
content: #!/usr/bin/env python3
"""News fetcher using RSS feeds - fast and reliable."""

import sys
import json
import re
from urllib.request import urlopen
from urllib.error import URLError
from html import unescape
import xml.etree.ElementTree as ET

# RSS Feed sources
SOURCES = {
    "general": [
        {"name": "Tagesschau", "url": "https://www.tagesschau.de/infocomments/extern/feed10344~_all-_all.json"},
        {"name": "FAZ", "url": "https://www.faz.net/rss/aktuell/"},
        {"name": "Spiegel", "url": "https://www.spiegel.de/schlagzeilen/tops/atom.xml"},
        {"name": "Süddeutsche", "url": "https://www.sueddeutsche.de/list/panorama feed"},
    ],
    "it": [
        {"name": "Heise", "url": "https://www.heise.de/rss/heise-topnews.rss"},
        {"name": "Golem", "url": "https://www.golem.de/rss.php"},
        {"name": "Slashdot", "url": "https://slashdot.org/slashdot.rss"},
    ],
    "cybersecurity": [
        {"name": "The Hacker News", "url": "https://feeds.feedburner.com/TheHackersNews"},
        {"name": "BleepingComputer", "url": "https://www.bleepingcomputer.com/feed/"},
        {"name": "Logbuch Netzpolitik", "url": "https://logbuch-netzpolitik.de/layer/feed mp3"},
    ]
}

# Words to filter (case-insensitive)
BLACKLIST = {
    "sport", "trump", "usa", "iran", "ukraine", "putin", "epstein",
    "bürgergeld", "mietreform", "jobs", "stellenmarkt", "karriere",
    "bilder des tages", "streik", "ivanti", "fortinet", "solarwinds", "spd"
}

def clean_text(text):
    """Clean text."""
    if not text:
        return ""
    text = unescape(text)
    text = re.sub(r'\s+', ' ', text).strip()
    return text

def is_filtered(title):
    """Check if headline contains blacklisted words."""
    if not title:
        return True
    title_lower = title.lower()
    for word in BLACKLIST:
        if word in title_lower:
            return True
    return False

def extract_rss_headlines(url, source_name):
    """Extract headlines from RSS feed."""
    try:
        with urlopen(url, timeout=15) as response:
            content = response.read()
        
        # Try XML parsing
        try:
            root = ET.fromstring(content)
            headlines = []
            
            # Handle different RSS formats
            for item in root.findall('.//item') or root.findall('.//entry'):
                title_elem = item.find('title') or item.find('title')
                link_elem = item.find('link') or item.find('link')
                
                if title_elem is not None and title_elem.text:
                    title = clean_text(title_elem.text)
                    link = ""
                    if link_elem is not None:
                        link = clean_text(link_elem.text) or clean_text(link_elem.get('href', '')) or clean_text(link_elem.get('url', ''))
                    
                    if title and link and not is_filtered(title):
                        headlines.append({"title": title, "url": link, "source": source_name})
            
            return headlines[:15]
            
        except ET.ParseError:
            # Fallback: try JSON (Tagesschau uses JSON)
            import json
            data = json.loads(content)
            headlines = []
            for item in data[:15]:
                title = clean_text(item.get('title', ''))
                link = item.get('link', '')
                if title and link and not is_filtered(title):
                    headlines.append({"title": title, "url": link, "source": source_name})
            return headlines
        
    except (URLError, Exception) as e:
        print(f"  [Error: {source_name} - {e}]", file=sys.stderr)
        return []

def display_category(category, sources):
    """Display news for a category."""
    print(f"\n{'='*50}")
    print(f"  📰 {category.upper()} NEWS")
    print(f"{'='*50}\n")
    
    all_headlines = []
    
    for source in sources:
        headlines = extract_rss_headlines(source["url"], source["name"])
        all_headlines.extend(headlines)
    
    if not all_headlines:
        print("  [No headlines found]")
        return
    
    # Remove duplicates based on title
    seen = set()
    unique_headlines = []
    for h in all_headlines:
        key = h["title"].lower()[:50]
        if key not in seen:
            seen.add(key)
            unique_headlines.append(h)
    
    for i, h in enumerate(unique_headlines[:10], 1):
        print(f"  {i}. {h['title']}")
        print(f"  {h['url']}\n")

def main():
    category = None
    if len(sys.argv) > 1:
        category = sys.argv[1].lower()
    
    if category and category not in SOURCES:
        print(f"Unknown category: {category}")
        print(f"Available: {', '.join(SOURCES.keys())} (or no category for all)")
        sys.exit(1)
    
    categories_to_fetch = [category] if category else list(SOURCES.keys())
    
    for cat in categories_to_fetch:
        display_category(cat, SOURCES[cat])

if __name__ == "__main__":
    main()


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