Communitygithub.com

xiaowu89/skill-compress

Habilidade de compressão de imagens no lado do servidor com saída via CDN — suporta processamento em lote e modo de arquivo único.

O que é skill-compress?

Esta habilidade permite a compressão inteligente de imagens utilizando um serviço remoto MCP (nx-mcp-server). Ela é útil para desenvolvedores que desejam otimizar imagens sem instalar bibliotecas locais como sharp, transferindo todo o processamento para o servidor. A habilidade oferece dois modos: compressão em lote de todo um diretório ou compressão de um único arquivo. O resultado é entregue por meio de URLs de CDN, facilitando a distribuição. A execução é puramente em memória (via stdin com heredoc do Node.js), sem criar arquivos temporários no disco. Recomendada para ambientes de desenvolvimento com Claude Code (ou qualquer agente que suporte MCP), a habilidade lida automaticamente com a configuração do MCP, autenticação via API Key e limitadores de tamanho (arquivos acima de 5 MB são ignorados). O código embutido realiza requisições concorrentes (pool de até 5), calcula a taxa de compressão e exibe um resumo detalhado de economia de espaço.

Funciona com✓Claude Code~Codex CLI✓Cursor
npx skills add xiaowu89/skill-compress

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

Compressão de Imagens

Compressão inteligente de imagens através do serviço remoto nx-mcp-server.

Configuração

Crie o arquivo .mcp.json na raiz do projeto:

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

Ordem de busca: raiz do projeto → diretório home do usuário. Extraia url → MCP_URL, env.NX_API_KEY → API_KEY.

A habilidade conecta-se diretamente ao endpoint MCP, não sendo necessário reiniciar o Claude Code.

Não tem uma Chave de API? Entre em contato pelo WeChat: zhjian_2026.

Fluxo de Compressão

Passo 1: Verificar configuração

cat .mcp.json 2>/dev/null || cat ~/.mcp.json 2>/dev/null
  • Encontrado → registre url e NX_API_KEY, prossiga para o passo 2
  • Não encontrado → pergunte ao usuário se ele já possui uma Chave de API:
    • Tem chave: crie o arquivo ~/.mcp.json (válido para instalação global e por projeto)
    • Não tem chave: informe que é necessário contatar o WeChat zhjian_2026 para obtê-la

⚠️ Não prossiga com os passos seguintes se a configuração estiver ausente.

Passo 2: Executar compressão (uma única chamada Bash, puramente em memória, zero arquivos)

Substitua PIC_DIR, MCP_URL, API_KEY, QUALITY conforme necessário. O heredoc alimenta o Node.js diretamente via stdin:

NODE_PATH=$(npm root -g) node << 'COMPRESSEOF'
const fs=require('fs'),path=require('path');
let PIC_DIR='<caminho absoluto do diretório de imagens>';
const SINGLE_FILE='<caminho da imagem única, vazio para comprimir todo o diretório>';
const MCP_URL='<url lida do .mcp.json>';
const API_KEY='<NX_API_KEY lida do .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 de ${imgs.length} imagem(ns), ${(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 o limite de 5MB'});skip++;console.log(`  [${i+1}/${imgs.length}] ${f} ${(osz/1024).toFixed(0)}KB ⚠️ Excede 5MB, pulando`);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(`Comprimíveis: ${records.length-skip}, Pulados: ${skip}`);
console.time('compressão');
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 concorrência: mantém sempre LIMIT requisições em voo, completa uma e adiciona outra
	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++;
	}
	
	// Janela deslizante: mantém sempre LIMIT requisições em paralelo
	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('compressão');
console.log('\n'+'='.repeat(100));
console.log(`${'Arquivo'.padEnd(40)} ${'Original'.padStart(8)} ${'Comprimido'.padStart(8)} ${'Taxa'.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} imagem(ns) | ✅ ${pass} sucesso | ❌ ${fail} falha | Economia total: ${(totalSaved/1024).toFixed(0)}KB`);

})();
COMPRESSEOF

Parâmetros de compressão: quality padrão 90 (1-100). Não requer sharp local, o processamento é feito diretamente no servidor.

Passo 3: Baixar para o computador (se o usuário desejar)

Não execute a compressão novamente! As URLs de CDN geradas pela compressão já foram exibidas no console. Use curl para baixar:

curl -o <diretório de saída>/<nome do arquivo> "<URL do CDN>"
  • ✅ Sucesso: A URL do CDN compactada pode ser usada diretamente
  • ⚠️ Acima de 5MB: O arquivo excede o limite do servidor, necessita tratamento manual
  • ❌ Falha: Tente novamente uma vez

Referência Rápida de Campos de Retorno

CampoTipoDescrição
originalSizenumberTamanho original (bytes)
compressedSizenumberTamanho após compressão (bytes)
ratiostringTaxa de compressão, ex.: "71.5%"
compressedUrlstringURL do CDN

Referência Rápida de Erros Comuns

Sintoma do ErroCausaAção Correta
MISSING_API_KEYParâmetro apiKey não enviadoÉ obrigatório enviar
API_AUTH_FAILEDChave de API inválidaVerifique a chave no .mcp.json
-32602 Invalid request parametersEtapa notifications/initialized não enviadaÉ necessário o fluxo completo: init → notified → call
406 Not AcceptableCabeçalho Accept ausenteDeclare tanto application/json quanto text/event-stream
Arquivo acima de 5MB puladoLimitação do servidorInforme o usuário para tratar manualmente

Proibições

  • ❌ Não comprimir localmente (compressão inteligente do servidor; sharp local é desnecessário)
  • ❌ Não enviar o parâmetro output (ele não existe)
  • ❌ Não omitir o parâmetro apiKey
  • ❌ Não omitir a etapa notifications/initialized
  • ❌ Não escrever nenhum arquivo temporário (heredoc via stdin, execução puramente em memória)
  • ❌ Não dividir o fluxo em múltiplas chamadas Bash (resolva tudo em uma única execução node << 'COMPRESSEOF')
  • ❌ Não usar barras invertidas nos caminhos; use sempre barras normais (d:/path)

Habilidades Relacionadas