agency-skills-suite/agency-archivist/scripts/extract_archive.js
AgentePotente 0b4c56d744 Script agency-archivist: compatibilità universale e {project}/
- extract_archive.js: --client → --project, rimossa dipendenza da .openclaw
- scan_resources.js: --client → --project, basePath configurabile
- generate_catalog.js: --client → --project, basePath configurabile
- Environment variable: AGENCY_PROJECTS_BASE per specificare base directory
- Default: current working directory (compatibile con qualsiasi sistema)
- Percorsi aggiornati: clients/{client}/ → {project}/
- Documentazione script aggiornata (usage, options, examples)

Vantaggi:
 Compatibile con OpenClaw e altri sistemi
 Non richiede struttura .openclaw/workspace
 Configurabile via ENV o --base-path
 Funziona in qualsiasi directory di progetto
2026-03-11 00:48:40 +01:00

371 lines
12 KiB
JavaScript
Executable file
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env node
/**
* extract_archive.js — Estrae archivi (zip, tar, rar) e organizza risorse
*
* Usage:
* node extract_archive.js <path_or_url> --project <project_path>
* node extract_archive.js brand_assets.zip --project demo_co_srl
* node extract_archive.js https://example.com/assets.zip --project campagna_x
*
* Options:
* --base-path Base directory (default: current dir o ENV)
* --keep-archive Mantieni file originale
* --verbose Log dettagliato
* --dry-run Simula senza estrazione
*
* Environment:
* AGENCY_PROJECTS_BASE Base directory per progetti (opzionale)
*/
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
// Mapping parole chiave → cartelle
const CATEGORY_KEYWORDS = {
'images/logo': ['logo', 'marchio', 'brand', 'logotipo'],
'images/prodotto': ['prodotto', 'product', 'item', 'articolo'],
'images/team': ['team', 'staff', 'ufficio', 'office', 'persone', 'people'],
'images/stock': ['sfondo', 'background', 'texture', 'stock'],
'videos/promo': ['promo', 'reel', 'trailer', 'advertisement'],
'videos/tutorial': ['tutorial', 'howto', 'demo', 'dimostrazione', 'guida'],
'documents/brand': ['brand', 'guideline', 'manual', 'linee guida'],
'documents/product': ['scheda', 'datasheet', 'spec', 'specifiche'],
};
function getFileType(filename) {
const ext = path.extname(filename).toLowerCase().slice(1);
const imageExts = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'bmp', 'tiff'];
const videoExts = ['mp4', 'mov', 'avi', 'mkv', 'webm', 'wmv'];
const docExts = ['pdf', 'doc', 'docx', 'txt', 'md', 'ppt', 'pptx', 'xls', 'xlsx'];
if (imageExts.includes(ext)) return 'images';
if (videoExts.includes(ext)) return 'videos';
if (docExts.includes(ext)) return 'documents';
return 'other';
}
function categorizeFile(filename, fileType) {
const filenameLower = filename.toLowerCase();
for (const [category, keywords] of Object.entries(CATEGORY_KEYWORDS)) {
const baseType = category.split('/')[0];
if (baseType === fileType) {
for (const keyword of keywords) {
if (filenameLower.includes(keyword)) {
return category;
}
}
}
}
return fileType !== 'other' ? `${fileType}/` : 'misc/';
}
function formatSize(bytes) {
const units = ['B', 'KB', 'MB', 'GB'];
let size = bytes;
for (const unit of units) {
if (size < 1024) return `${size.toFixed(1)} ${unit}`;
size /= 1024;
}
return `${size.toFixed(1)} TB`;
}
function downloadFile(url, destPath, verbose = false) {
try {
if (verbose) console.log(`📥 Download: ${url}`);
// Usa curl o wget (più affidabili)
execSync(`curl -L -o "${destPath}" "${url}"`, { stdio: verbose ? 'inherit' : 'pipe' });
if (verbose) console.log(`✅ Download completato: ${destPath}`);
return true;
} catch (error) {
console.error(`❌ Errore download: ${error.message}`);
return false;
}
}
function extractArchive(archivePath, extractTo, verbose = false) {
const filename = path.basename(archivePath);
const ext = path.extname(filename).toLowerCase();
try {
// Crea cartella temporanea
if (!fs.existsSync(extractTo)) {
fs.mkdirSync(extractTo, { recursive: true });
}
if (ext === '.zip') {
execSync(`unzip -o "${archivePath}" -d "${extractTo}"`, {
stdio: verbose ? 'inherit' : 'pipe'
});
const output = execSync(`unzip -l "${archivePath}" | tail -n +4 | head -n -2`, { encoding: 'utf8' });
return output.split('\n').filter(line => line.trim()).map(line => {
const parts = line.trim().split(/\s+/);
return parts[parts.length - 1];
});
} else if (ext === '.gz' && filename.includes('.tar')) {
execSync(`tar -xzf "${archivePath}" -C "${extractTo}"`, {
stdio: verbose ? 'inherit' : 'pipe'
});
const output = execSync(`tar -tzf "${archivePath}"`, { encoding: 'utf8' });
return output.split('\n').filter(line => line.trim());
} else if (ext === '.rar') {
try {
execSync(`unrar x -o+ "${archivePath}" "${extractTo}"`, {
stdio: verbose ? 'inherit' : 'pipe'
});
const output = execSync(`unrar l "${archivePath}" | tail -n +5 | head -n -2`, { encoding: 'utf8' });
return output.split('\n').filter(line => line.trim()).map(line => {
const parts = line.trim().split(/\s+/);
return parts[parts.length - 1];
});
} catch (error) {
console.error('❌ Supporto RAR non disponibile. Installa: sudo apt-get install unrar');
return [];
}
} else {
console.error(`❌ Formato ${ext} non supportato. Usa zip, tar.gz, o rar.`);
return [];
}
} catch (error) {
console.error(`❌ Errore estrazione: ${error.message}`);
return [];
}
}
function organizeFiles(tempDir, assetsDir, verbose = false) {
const organized = [];
// Crea struttura cartelle
const folders = [
'images/logo', 'images/prodotto', 'images/team', 'images/stock',
'videos/promo', 'videos/tutorial',
'documents/brand', 'documents/product'
];
for (const folder of folders) {
fs.mkdirSync(path.join(assetsDir, folder), { recursive: true });
}
// Walk ricorsivo
function walkDir(dir) {
const files = [];
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
files.push(...walkDir(fullPath));
} else if (!entry.name.startsWith('.') && entry.name !== 'Thumbs.db') {
files.push(fullPath);
}
}
return files;
}
const allFiles = walkDir(tempDir);
for (const srcPath of allFiles) {
const filename = path.basename(srcPath);
const fileType = getFileType(filename);
const category = categorizeFile(filename, fileType);
const destFolder = path.join(assetsDir, category);
let destPath = path.join(destFolder, filename);
// Gestisci duplicati
let counter = 1;
const base = path.basename(filename, path.extname(filename));
const ext = path.extname(filename);
while (fs.existsSync(destPath)) {
destPath = path.join(destFolder, `${base}_${counter}${ext}`);
counter++;
}
// Copia file
fs.copyFileSync(srcPath, destPath);
const stats = fs.statSync(destPath);
organized.push({
original: filename,
destination: path.relative(assetsDir, destPath),
type: fileType,
category: category,
size: stats.size
});
if (verbose) {
console.log(` 📁 ${filename}${category}/`);
}
}
return organized;
}
function logOperation(project, archiveName, organizedFiles, opsLogPath) {
const timestamp = new Date().toISOString().slice(0, 16).replace('T', ' ');
const images = organizedFiles.filter(f => f.type === 'images');
const videos = organizedFiles.filter(f => f.type === 'videos');
const docs = organizedFiles.filter(f => f.type === 'documents');
const logEntry = `
## ${timestamp} — Archivist Upload
- **Archivio:** \`${archiveName}\`
- **File estratti:** ${organizedFiles.length}
- **Status:** ✅ Completato
### Dettagli
| Tipo | Count | Dimensione Totale |
|------|-------|-------------------|
| Immagini | ${images.length} | ${formatSize(images.reduce((sum, f) => sum + f.size, 0))} |
| Video | ${videos.length} | ${formatSize(videos.reduce((sum, f) => sum + f.size, 0))} |
| Documenti | ${docs.length} | ${formatSize(docs.reduce((sum, f) => sum + f.size, 0))} |
`;
fs.appendFileSync(opsLogPath, logEntry);
}
function main() {
const args = process.argv.slice(2);
// Parse arguments
let pathOrUrl = null;
let project = null;
let basePath = process.env.AGENCY_PROJECTS_BASE || process.cwd();
let keepArchive = false;
let verbose = false;
let dryRun = false;
for (let i = 0; i < args.length; i++) {
if (args[i] === '--project' && args[i + 1]) {
project = args[++i];
} else if (args[i] === '--base-path' && args[i + 1]) {
basePath = args[++i];
} else if (args[i] === '--keep-archive') {
keepArchive = true;
} else if (args[i] === '--verbose') {
verbose = true;
} else if (args[i] === '--dry-run') {
dryRun = true;
} else if (!args[i].startsWith('--')) {
pathOrUrl = args[i];
}
}
if (!pathOrUrl || !project) {
console.error('Usage: node extract_archive.js <path_or_url> --project <project_name>');
console.error('Options: --base-path <dir>, --keep-archive, --verbose, --dry-run');
console.error('Environment: AGENCY_PROJECTS_BASE (opzionale)');
process.exit(1);
}
// Path
const projectDir = path.join(basePath, project);
const assetsDir = path.join(projectDir, 'assets');
const archiveDir = path.join(assetsDir, 'archive');
const opsLog = path.join(projectDir, 'ops', 'run_log.md');
// Verifica cartella progetto
if (!fs.existsSync(projectDir)) {
console.error(`❌ Cartella progetto non trovata: ${projectDir}`);
console.error(' Crea prima il progetto con agency-orchestrator');
process.exit(1);
}
// Crea cartelle
fs.mkdirSync(archiveDir, { recursive: true });
fs.mkdirSync(path.join(projectDir, 'ops'), { recursive: true });
// URL o path locale?
const isUrl = pathOrUrl.startsWith('http://') || pathOrUrl.startsWith('https://') || pathOrUrl.startsWith('ftp://');
let archivePath;
let archiveName;
if (isUrl) {
archiveName = path.basename(pathOrUrl.split('?')[0]);
archivePath = path.join(archiveDir, archiveName);
if (dryRun) {
console.log(`🔍 [DRY-RUN] Download: ${pathOrUrl}${archivePath}`);
process.exit(0);
}
if (!downloadFile(pathOrUrl, archivePath, verbose)) {
process.exit(1);
}
} else {
archivePath = pathOrUrl;
archiveName = path.basename(archivePath);
if (!fs.existsSync(archivePath)) {
console.error(`❌ File non trovato: ${archivePath}`);
process.exit(1);
}
if (dryRun) {
console.log(`🔍 [DRY-RUN] Estrai: ${archivePath}${assetsDir}`);
process.exit(0);
}
// Copia in archive/
fs.copyFileSync(archivePath, path.join(archiveDir, archiveName));
}
if (verbose) {
console.log(`\n📦 Archivio: ${archiveName}`);
console.log(`📁 Destinazione: ${assetsDir}`);
console.log();
}
// Estrai in temporanea
const tempDir = path.join(archiveDir, '.temp_extract');
fs.mkdirSync(tempDir, { recursive: true });
console.log('🔄 Estrazione in corso...');
const extracted = extractArchive(path.join(archiveDir, archiveName), tempDir, verbose);
if (extracted.length === 0) {
console.error('❌ Nessun file estratto');
fs.rmSync(tempDir, { recursive: true, force: true });
process.exit(1);
}
// Organizza file
console.log('\n🗂 Organizzazione file...');
const organized = organizeFiles(tempDir, assetsDir, verbose);
// Pulisci temporanea
fs.rmSync(tempDir, { recursive: true, force: true });
// Log operazione
logOperation(project, archiveName, organized, opsLog);
// Elimina archivio originale (se non --keep-archive)
if (!keepArchive) {
fs.unlinkSync(path.join(archiveDir, archiveName));
if (verbose) console.log('\n🗑 Archivio originale eliminato');
}
// Riepilogo
console.log('\n✅ Completato!');
console.log(` 📦 File estratti: ${organized.length}`);
console.log(` 📁 Cartella: ${assetsDir}`);
console.log(` 📝 Log: ${opsLog}`);
console.log(`\n👉 Prossimo step: node scripts/scan_resources.js --project ${project}`);
}
main();