Communitygithub.com

xiaowu89/skill-compress

Compétence de compression d'images côté serveur avec sortie CDN — prise en charge du traitement par lots et du mode fichier unique

Qu'est-ce que skill-compress ?

Cette compétence permet de compresser intelligemment des images via le service de compression distant nx-mcp-server. Elle est conçue pour les développeurs utilisant Claude Code, Cursor ou d'autres agents IA compatibles MCP. L'utilisateur configure simplement un fichier `.mcp.json` avec une clé API, et la compression s'effectue entièrement en mémoire, sans écrire de fichiers temporaires. La compétence prend en charge le traitement par lots d'un répertoire entier ou d'un fichier unique, avec une fenêtre de concurrence de 5 requêtes pour optimiser le temps d'exécution. Les images compressées sont hébergées sur un CDN, et la compétence affiche un récapitulatif détaillé incluant le taux de compression, les fichiers ignorés (>5 Mo) et les échecs éventuels. Elle intègre une gestion robuste des erreurs et des recommandations pour chaque code d'erreur courant.

Compatible avec✓Claude Code~Codex CLI✓Cursor
npx skills add xiaowu89/skill-compress

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Compression d'images

Compression intelligente d'images via le service distant nx-mcp-server.

Configuration

Créez un fichier .mcp.json à la racine du projet :

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

Ordre de recherche : racine du projet → répertoire personnel. Extrait url → MCP_URL, env.NX_API_KEY → API_KEY.

La compétence se connecte directement au point de terminaison MCP, sans nécessiter de redémarrage de Claude Code.

Pas de clé API ? Contactez WeChat zhjian_2026 pour en obtenir une.

Processus de compression

Étape 1 : Vérifier la configuration

cat .mcp.json 2>/dev/null || cat ~/.mcp.json 2>/dev/null
  • Trouvé → enregistrer url et NX_API_KEY, passer à l'étape 2
  • Introuvable → demander à l'utilisateur s'il possède déjà une clé API :
    • A une clé : aider à créer ~/.mcp.json (valable pour une installation globale ou par projet)
    • Pas de clé : informer de contacter WeChat zhjian_2026 pour en obtenir une

⚠️ Ne pas poursuivre les étapes suivantes si la configuration est manquante.

Étape 2 : Exécuter la compression (un seul appel Bash, 100% mémoire, zéro fichier)

Remplacez PIC_DIR, MCP_URL, API_KEY, QUALITY, puis le heredoc alimente node directement via stdin :

NODE_PATH=$(npm root -g) node << 'COMPRESSEOF'
const fs=require('fs'),path=require('path');
let PIC_DIR='<chemin absolu du répertoire d\'images>';
const SINGLE_FILE='<chemin de l\'image unique, vide pour compresser tout le répertoire>';
const MCP_URL='<url lue depuis .mcp.json>';
const API_KEY='<NX_API_KEY lue depuis .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(`Total ${imgs.length} images, ${(origTotal/1024).toFixed(0)} Ko`);
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:'Dépasse la limite de 5 Mo'});skip++;console.log(`  [${i+1}/${imgs.length}] ${f} ${(osz/1024).toFixed(0)} Ko ⚠️ Dépasse 5 Mo, ignoré`);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(`Compressibles : ${records.length-skip}, Ignorés : ${skip}`);
console.time('Compression');
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 : notifié');
// Pool de concurrence : toujours LIMIT requêtes en vol, une terminée on en lance une autre
	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)} Ko → ${(r.compKb).toFixed(0)} Ko (${r.ratio})`)}
	      }
	    }
	  }catch(e){r.error=e.message;fail++}
	  done++;
	}
	
	// Fenêtre glissante : toujours LIMIT opérations concurrentes
	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('Compression');
console.log('\n'+'='.repeat(100));
console.log(`${'Fichier'.padEnd(40)} ${'Original'.padStart(8)} ${'Compressé'.padStart(8)} ${'Taux'.padStart(8)} ${'Résultat'.padStart(6)}`);
console.log('-'.repeat(100));
for(const r of records){const oszS=(r.origKb/1024).toFixed(1)+' Ko';
if(r.compUrl){const cszS=(r.compKb).toFixed(1)+' Ko';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} images | ✅ ${pass} réussies | ❌ ${fail} échecs | Économisé ${(totalSaved/1024).toFixed(0)} Ko`);

})();
COMPRESSEOF

Paramètre de compression : quality par défaut 90 (1-100). Pas besoin de sharp en local, l'envoi est direct au serveur pour compression.

Étape 3 : Télécharger en local (si l'utilisateur le souhaite)

Ne relancez pas la compression ! Les URLs CDN des résultats sont déjà affichées dans la console. Utilisez curl pour télécharger :

curl -o <répertoire de sortie>/<nom du fichier> "<URL CDN>"
  • ✅ Réussi : L'URL CDN compressée peut être utilisée directement
  • ⚠️ Plus de 5 Mo : Le fichier dépasse la limite serveur, doit être traité manuellement
  • ❌ Échec : Réessayez une fois

Référence rapide des champs

ChampTypeDescription
originalSizenumberTaille originale (octets)
compressedSizenumberTaille après compression (octets)
ratiostringTaux de compression, ex. "71,5%"
compressedUrlstringAdresse CDN

Référence rapide des erreurs courantes

Erreur observéeCauseAction correcte
MISSING_API_KEYapiKey non fourniObligatoire
API_AUTH_FAILEDClé API invalideVérifier la clé dans .mcp.json
-32602 Invalid request parametersnotifications/initialized non envoyéSuivre les trois étapes : init → notified → call
406 Not AcceptableEn-tête Accept manquantDéclarer à la fois application/json et text/event-stream
Fichier > 5 Mo ignoréLimite serveurInformer l'utilisateur de traiter manuellement

Interdictions

  • ❌ Ne pas compresser localement (compression intelligente côté serveur, sharp local est superflu)
  • ❌ Ne pas envoyer le paramètre output (il n'existe pas)
  • ❌ Ne pas omettre le paramètre apiKey
  • ❌ Ne pas sauter l'étape notifications/initialized
  • ❌ Ne pas écrire de fichiers temporaires (heredoc direct sur stdin, exécution en mémoire)
  • ❌ Ne pas diviser le flux en plusieurs appels Bash (un seul node << 'COMPRESSEOF' suffit)
  • ❌ Ne pas utiliser de chemins avec antislash ; utilisez toujours des slashes (d:/path)

Skills associés