Communitygithub.com

xiaowu89/skill-compress

Habilidad de compresión de imágenes del lado del servidor con salida CDN: admite procesamiento por lotes y modo de archivo único.

¿Qué es skill-compress?

Esta habilidad permite comprimir imágenes de forma inteligente mediante el servicio remoto nx-mcp-server, con salida directa a CDN. Es útil para desarrolladores que necesitan optimizar el peso de imágenes en proyectos web o aplicaciones sin depender de herramientas locales como sharp. Funciona en cualquier entorno que soporte el protocolo MCP (Model Context Protocol), como Claude Code, y no requiere instalación local de librerías de compresión. El flujo de trabajo es simple: el agente verifica la configuración en `.mcp.json`, envía las imágenes en base64 al endpoint MCP, y recibe las URLs comprimidas en la CDN. Soporta tanto un archivo individual como un directorio completo, con un límite de 5 MB por imagen. El proceso se ejecuta en una sola llamada Bash mediante un heredoc de Node.js, completamente en memoria, sin archivos temporales. También incluye un resumen detallado de la compresión y enlaces de descarga directa.

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

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Compresión de imágenes

Comprime imágenes de forma inteligente mediante el servicio remoto nx-mcp-server.

Configuración

Crea un archivo .mcp.json en la raíz del proyecto:

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

Orden de búsqueda: raíz del proyecto → directorio home del usuario. Extrae url → MCP_URL, env.NX_API_KEY → API_KEY.

La skill se conecta directamente al endpoint MCP, sin necesidad de reiniciar Claude Code.

¿No tienes API Key? Contacta a WeChat zhjian_2026 para obtener una.

Flujo de compresión

Paso 1: Verificar configuración

cat .mcp.json 2>/dev/null || cat ~/.mcp.json 2>/dev/null
  • Encontrado → Registra url y NX_API_KEY, continúa al paso 2
  • No encontrado → Pregunta al usuario si ya tiene API Key:
    • Sí tiene Key: Ayuda al usuario a crear ~/.mcp.json (funciona tanto para instalación global como de proyecto)
    • No tiene Key: Informa que contacte a WeChat zhjian_2026

⚠️ Si falta la configuración, no continúes con los pasos siguientes.

Paso 2: Ejecutar compresión (una sola llamada Bash, en memoria, cero archivos)

Reemplaza PIC_DIR, MCP_URL, API_KEY, QUALITY y luego pasa el heredoc directamente a node mediante stdin:

NODE_PATH=$(npm root -g) node << 'COMPRESSEOF'
const fs=require('fs'),path=require('path');
let PIC_DIR='<ruta absoluta del directorio de imágenes>';
const SINGLE_FILE='<ruta de una imagen individual, vacío para comprimir todo el directorio>';
const MCP_URL='<url leída de .mcp.json>';
const API_KEY='<NX_API_KEY leído de .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} imágenes, ${(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:'Excede el límite de 5MB'});skip++;console.log(`  [${i+1}/${imgs.length}] ${f} ${(osz/1024).toFixed(0)}KB ⚠️ Excede 5MB, se omite`);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(`Compresibles: ${records.length-skip} imágenes, omitidas: ${skip} imágenes`);
console.time('Compresión');
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');
// Pool de concurrencia: mantiene siempre LIMIT solicitudes activas, completa una y agrega otra
	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++;
	}
	
	// Ventana deslizante: mantiene siempre LIMIT solicitudes 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('Compresión');
console.log('\n'+'='.repeat(100));
console.log(`${'Archivo'.padEnd(40)} ${'Original'.padStart(8)} ${'Comprimido'.padStart(8)} ${'Tasa'.padStart(8)} ${'Resultado'.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} imágenes | ✅ ${pass} exitosas | ❌ ${fail} fallidas | Ahorro total ${(totalSaved/1024).toFixed(0)}KB`);

})();
COMPRESSEOF

Parámetros de compresión: quality por defecto 90 (1-100), no necesita sharp local, se envía directamente al servidor para comprimir.

Paso 3: Descargar localmente (si el usuario lo necesita)

No vuelvas a ejecutar la compresión. Las URLs CDN de los resultados ya se mostraron en la consola. Usa curl para descargar:

curl -o <directorio_salida>/<nombre_archivo> "<URL_CDN>"
  • ✅ Éxito: La URL CDN comprimida se puede usar directamente
  • ⚠️ Supera 5MB: El archivo excede el límite del servidor, debe procesarse manualmente
  • ❌ Fallo: Reintentar una vez

Referencia rápida de campos de respuesta

CampoTipoDescripción
originalSizenumberTamaño original (bytes)
compressedSizenumberTamaño comprimido (bytes)
ratiostringTasa de compresión, ej. "71.5%"
compressedUrlstringDirección CDN

Referencia rápida de errores comunes

SíntomaCausaSolución
MISSING_API_KEYNo se pasó apiKeyDebe pasarse obligatoriamente
API_AUTH_FAILEDAPI Key inválidaVerifica la Key en .mcp.json
-32602 Invalid request parametersNo se envió notifications/initializedDeben seguirse tres pasos: init → notified → call
406 Not AcceptableFalta el encabezado AcceptDeclara tanto application/json como text/event-stream
Archivo >5MB omitidoLímite del servidorIndica al usuario que lo procese manualmente

Prohibiciones

  • ❌ No comprimir localmente (la compresión inteligente del servidor es suficiente, no es necesario sharp local)
  • ❌ No pasar el parámetro output (no existe)
  • ❌ No omitir el parámetro apiKey
  • ❌ No omitir el paso notifications/initialized
  • ❌ No escribir archivos temporales (heredoc va directo a stdin, ejecución en memoria pura)
  • ❌ No dividir el flujo en múltiples llamadas Bash (una sola ejecución node << 'COMPRESSEOF' lo resuelve)
  • ❌ No usar rutas con barras invertidas, usa siempre barras normales (d:/path)

Skills relacionados