Communitygithub.com

xiaowu89/skill-compress

Serverseitige Bildkomprimierung mit CDN-Ausgabe – unterstützt Batch-Verarbeitung und Einzeldatei-Modus

Was ist skill-compress?

Diese Skill ermöglicht eine intelligente, serverseitige Bildkomprimierung über den nx-mcp-server. Sie eignet sich besonders für Entwickler, die Bilder optimieren möchten, ohne lokale Tools wie sharp zu installieren. Die Skill unterstützt sowohl die Batch-Verarbeitung eines gesamten Verzeichnisses als auch die Komprimierung einzelner Dateien. Die Kommunikation erfolgt ausschließlich über eine MCP-Schnittstelle, ohne dass Dateien lokal geschrieben werden – alles läuft im Arbeitsspeicher. Nach der Komprimierung werden CDN-URLs ausgegeben, die direkt in Webanwendungen oder Content-Management-Systeme eingebunden werden können. Die Skill ist für Claude Code optimiert und erfordert eine gültige API-Key aus der ModelScope-Plattform. Der gesamte Vorgang – von der Prüfung der Konfiguration über die Komprimierung bis zur Ausgabe der CDN-Links – wird innerhalb eines einzigen Node.js-Skripts durchgeführt, das über eine Bash-Here-Doc ausgeführt wird. Fehler wie fehlende API-Keys oder zu große Dateien werden detailliert gemeldet.

Funktioniert mit✓Claude Code~Codex CLI✓Cursor
npx skills add xiaowu89/skill-compress

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

Bildkomprimierung

Intelligente Bildkomprimierung über den nx-mcp-server Remote-Komprimierungsdienst.

Konfiguration

Erstelle im Projektstammverzeichnis eine .mcp.json:

{
  "mcpServers": {
    "nx-mcp-compress": {
      "type": "url",
      "url": "https://mcp.api-inference.modelscope.net/4378d43d3e7d4c/mcp",
      "env": {
        "NX_API_KEY": "Dein API Key"
      }
    }
  }
}

Suchreihenfolge: Projektstammverzeichnis → Benutzerverzeichnis. Extrahiere url → MCP_URL, env.NX_API_KEY → API_KEY.

Die Skill verbindet sich direkt mit dem MCP-Endpunkt. Kein Neustart von Claude Code erforderlich.

Kein API Key? Kontaktiere WeChat zhjian_2026 um einen zu erhalten.

Komprimierungsablauf

Schritt 1: Konfiguration prüfen

cat .mcp.json 2>/dev/null || cat ~/.mcp.json 2>/dev/null
  • Gefunden → Notiere url und NX_API_KEY, fahre mit Schritt 2 fort
  • Nicht gefunden → Frage den Benutzer, ob er bereits einen API Key hat:
    • Key vorhanden: Erstelle ~/.mcp.json für den Benutzer (funktioniert global und pro Projekt)
    • Kein Key vorhanden: Informiere den Benutzer, dass er WeChat zhjian_2026 kontaktieren soll

⚠️ Wenn die Konfiguration fehlt, fahre nicht mit den nächsten Schritten fort.

Schritt 2: Komprimierung ausführen (ein Bash-Aufruf, komplett im Arbeitsspeicher, null Dateien)

Ersetze PIC_DIR, MCP_URL, API_KEY und QUALITY und führe den folgenden Here-Doc direkt über stdin an node aus:

NODE_PATH=$(npm root -g) node << 'COMPRESSEOF'
const fs=require('fs'),path=require('path');
let PIC_DIR='<Absoluter Pfad zum Zielbildverzeichnis>';
const SINGLE_FILE='<Pfad zu einem einzelnen Bild, leer lassen für gesamtes Verzeichnis>';
const MCP_URL='<url aus .mcp.json>';
const API_KEY='<NX_API_KEY aus .mcp.json>';
const QUALITY=90;
(async()=>{
const exts=['.png','.jpg','.jpeg','.webp','.bmp','.tga'];
let imgs;
if(SINGLE_FILE){imgs=[path.basename(SINGLE_FILE)];PIC_DIR=path.dirname(SINGLE_FILE)}
else{imgs=fs.readdirSync(PIC_DIR).filter(f=>exts.includes(path.extname(f).toLowerCase())).sort()}
const origTotal=imgs.reduce((s,f)=>s+fs.statSync(path.join(PIC_DIR,f)).size,0);
console.log(`Insgesamt ${imgs.length} Bilder, gesamt ${(origTotal/1024).toFixed(0)}KB`);
const maxSize=5*1024*1024;
const records=[];let skip=0;
for(let i=0;i<imgs.length;i++){const f=imgs[i],fp=path.join(PIC_DIR,f),osz=fs.statSync(fp).size;
if(osz>maxSize){records.push({name:f,origKb:osz,compKb:0,compUrl:null,ratio:null,error:'Größer als 5MB Limit'});skip++;console.log(`  [${i+1}/${imgs.length}] ${f} ${(osz/1024).toFixed(0)}KB ⚠️ Größer als 5MB, übersprungen`);continue}
const buf=fs.readFileSync(fp);const ext=path.extname(f).toLowerCase().slice(1);
const mime=ext==='png'?'image/png':ext==='webp'?'image/webp':'image/jpeg';
records.push({name:f,origKb:osz,compKb:0,compUrl:null,ratio:null,dataUrl:'data:'+mime+';base64,'+buf.toString('base64')});}
console.log(`Komprimierbar: ${records.length-skip} Bilder, übersprungen: ${skip} Bilder`);
console.time('Komprimierung');
const H={'Content-Type':'application/json','Accept':'application/json, text/event-stream'};
const r1=await fetch(MCP_URL,{method:'POST',headers:H,body:JSON.stringify({jsonrpc:'2.0',id:'1',method:'initialize',params:{protocolVersion:'2025-06-18',capabilities:{},clientInfo:{name:'cc',version:'1'}}})});
H['Mcp-Session-Id']=r1.headers.get('Mcp-Session-Id');console.log('MCP: init');
await fetch(MCP_URL,{method:'POST',headers:H,body:JSON.stringify({jsonrpc:'2.0',method:'notifications/initialized'})});console.log('MCP: notified');
// Parallelpool: immer LIMIT Anfragen gleichzeitig, eine beendet, nächste startet
	const LIMIT=5;
	let totalSaved=0,pass=0,fail=0,done=0;
	const pending=records.filter(r=>!r.error);
	
	async function compressOne(r,idx){
	  try{
	    const r3=await fetch(MCP_URL,{method:'POST',headers:H,body:JSON.stringify({jsonrpc:'2.0',id:'3',method:'tools/call',params:{name:'nx_compress',arguments:{files:[r.dataUrl],quality:QUALITY,apiKey:API_KEY}}})});
	    const raw=await r3.json();
	    if(!raw.result){r.error='MCP: '+(raw.error?.message||'unknown');fail++}
	    else{
	      const inner=JSON.parse(raw.result.content[0].text);
	      if(inner.error){r.error=inner.code+': '+inner.error;fail++}
	      else{const it=inner.items[0];
	        if(it.error){r.error=it.error;fail++}
	        else{r.compKb=it.compressedSize/1024;r.ratio=it.ratio;r.compUrl=it.compressedUrl;totalSaved+=it.originalSize-it.compressedSize;pass++;console.log(`  [${idx+1}/${imgs.length}] ${r.name} ${(r.origKb/1024).toFixed(0)}KB→${(r.compKb).toFixed(0)}KB (${r.ratio})`)}
	      }
	    }
	  }catch(e){r.error=e.message;fail++}
	  done++;
	}
	
	// Sliding Window: immer LIMIT gleichzeitige Anfragen
	let i=0;
	const running=new Set();
	while(i<pending.length||running.size>0){
	  while(running.size<LIMIT&&i<pending.length){
	    const p=compressOne(pending[i],i).then(()=>running.delete(p));
	    running.add(p);i++;
	  }
	  if(running.size>0) await Promise.race(running);
	}console.timeEnd('Komprimierung');
console.log('\n'+'='.repeat(100));
console.log(`${'Datei'.padEnd(40)} ${'Original'.padStart(8)} ${'Komprimiert'.padStart(8)} ${'Verhältnis'.padStart(8)} ${'Ergebnis'.padStart(6)}`);
console.log('-'.repeat(100));
for(const r of records){const oszS=(r.origKb/1024).toFixed(1)+'KB';
if(r.compUrl){const cszS=(r.compKb).toFixed(1)+'KB';console.log(`${r.name.padEnd(40)} ${oszS.padStart(8)} ${cszS.padStart(8)} ${(r.ratio||'?').padStart(8)} ${'✅'.padStart(6)} ${r.compUrl}`)}
else{console.log(`${r.name.padEnd(40)} ${oszS.padStart(8)} ${'—'.padStart(8)} ${'—'.padStart(8)} ${'❌'.padStart(6)}`)}}
console.log(`\n📊 ${records.length} Bilder | ✅ ${pass} erfolgreich | ❌ ${fail} fehlgeschlagen | Gesparte ${(totalSaved/1024).toFixed(0)}KB`);

})();
COMPRESSEOF

Komprimierungsparameter: quality Standardwert 90 (1-100). Kein lokales sharp erforderlich, direkte serverseitige Komprimierung.

Schritt 3: Herunterladen auf den lokalen Rechner (falls gewünscht)

Führe die Komprimierung nicht erneut aus! Die CDN-URLs aus den Komprimierungsergebnissen wurden bereits in der Konsole ausgegeben. Lade sie direkt mit curl herunter:

curl -o <Ausgabeverzeichnis>/<Dateiname> "<CDN-URL>"
  • ✅ Erfolg: Die komprimierte CDN-URL kann direkt verwendet werden
  • ⚠️ Größer als 5MB: Die Datei überschreitet das Serverlimit und muss manuell behandelt werden
  • ❌ Fehlgeschlagen: Einmal wiederholen

Rückgabefelder im Überblick

FeldTypBeschreibung
originalSizenumberOriginalgröße (Bytes)
compressedSizenumberGröße nach Komprimierung (Bytes)
ratiostringKomprimierungsverhältnis, z. B. "71.5%"
compressedUrlstringCDN-URL

Häufige Fehler im Überblick

FehlererscheinungUrsacheRichtiges Vorgehen
MISSING_API_KEYapiKey nicht übergebenMuss angegeben werden
API_AUTH_FAILEDAPI Key ungültigÜberprüfe den Key in .mcp.json
-32602 Invalid request parametersnotifications/initialized nicht gesendetDrei Schritte erforderlich: init → notified → call
406 Not AcceptableAccept-Header fehltDeklariere sowohl application/json als auch text/event-stream
Datei größer als 5MB und übersprungenServerlimitWeisen den Benutzer auf manuelle Bearbeitung hin

Verbotene Handlungen

  • ❌ Keine lokale Komprimierung (serverseitige intelligente Komprimierung, lokales sharp ist überflüssig)
  • ❌ Keinen output-Parameter übergeben (existiert nicht)
  • ❌ Keinen apiKey-Parameter auslassen
  • ❌ Keinen Schritt notifications/initialized auslassen
  • ❌ Keine temporären Dateien schreiben (Here-Doc direkt über stdin, reiner Arbeitsspeicher)
  • ❌ Den Ablauf nicht in mehrere Bash-Aufrufe aufteilen (alles mit einem node << 'COMPRESSEOF' erledigen)
  • ❌ Keine Backslash-Pfade verwenden, immer Vorwärtsschrägstriche (z. B. d:/pfad)

Verwandte Skills