UNPKG

lookatni-file-markers

Version:

Advanced file organization system with unique markers for code sharing and project management. Extract files from marked content, generate markers, and validate project integrity.

707 lines (706 loc) 32.1 kB
#!/usr/bin/env node "use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.LookAtniGenerator = void 0; const child_process_1 = require("child_process"); const crypto = __importStar(require("crypto")); const fs = __importStar(require("fs")); const os = __importStar(require("os")); const path = __importStar(require("path")); const FS_CHAR = String.fromCharCode(28); const MARKER_START = `//${FS_CHAR}/`; const MARKER_END = `/${FS_CHAR}//`; const colors = { RED: '\x1b[0;31m', GREEN: '\x1b[0;32m', YELLOW: '\x1b[1;33m', BLUE: '\x1b[0;34m', PURPLE: '\x1b[0;35m', CYAN: '\x1b[0;36m', NC: '\x1b[0m' }; class LookAtniGenerator { constructor(options) { this.options = options; this.stats = { totalFiles: 0, processedFiles: 0, skippedFiles: 0, totalSize: 0, outputSize: 0, largestFile: { path: '', size: 0 } }; } printHelp() { console.log(`${colors.CYAN}🔧 LookAtni Marker Generator v4.0${colors.NC}`); console.log("================================================"); console.log("Gera arquivo com marcadores únicos a partir de estrutura existente"); console.log(""); console.log("Uso: tsx generateMarkers.ts [diretorio] [arquivo_saida] [opcoes]"); console.log(""); console.log("Parâmetros:"); console.log(" diretorio : Diretório para escanear (padrão: .)"); console.log(" arquivo_saida : Arquivo de saída (padrão: lookatni-code.txt)"); console.log(""); console.log("Opções:"); console.log(" -e, --exclude PATTERN : Excluir arquivos/diretórios (pode usar múltiplas vezes)"); console.log(" -i, --include PATTERN : Incluir apenas arquivos que correspondem ao padrão"); console.log(" -m, --max-size SIZE : Tamanho máximo do arquivo em KB (padrão: 1000)"); console.log(" -v, --verbose : Saída detalhada"); console.log(" -h, --help : Esta ajuda"); console.log(""); console.log(`${colors.PURPLE}🌐 Opções Remotas (NOVO!):${colors.NC}`); console.log(" -r, --remote USER@HOST:/PATH : Enviar arquivo para servidor remoto via SCP"); console.log(" -k, --ssh-key PATH : Chave SSH personalizada para autenticação"); console.log(" -c, --compress : Usar compressão durante o upload"); console.log(" --retry N : Número de tentativas de upload (padrão: 3)"); console.log(""); console.log("Exemplos Básicos:"); console.log(" tsx generateMarkers.ts ./src projeto.txt"); console.log(" tsx generateMarkers.ts . codigo.txt --exclude node_modules --exclude .git"); console.log(" tsx generateMarkers.ts ./meu-projeto saida.txt --include '*.js' --include '*.ts'"); console.log(""); console.log(`${colors.CYAN}🚀 Exemplos com Upload Remoto:${colors.NC}`); console.log(" # Backup automático"); console.log(" tsx generateMarkers.ts ./src backup.txt --remote backup@servidor:/snapshots/"); console.log(""); console.log(" # Colaboração com compressão"); console.log(" tsx generateMarkers.ts . projeto.txt --remote dev@team:/sharing/ --compress"); console.log(""); console.log(" # Deploy com chave SSH específica"); console.log(" tsx generateMarkers.ts ./dist deploy.txt --remote prod@servidor:/deploy/ --ssh-key ~/.ssh/deploy_key"); console.log(""); console.log(" # Sync com retry customizado"); console.log(" tsx generateMarkers.ts . sync.txt --remote laptop@casa:/sync/ --retry 5"); console.log(""); console.log("💡 O arquivo gerado pode ser usado com:"); console.log(" tsx extractFiles.ts codigo.txt ./novo-projeto"); console.log(""); console.log(`${colors.YELLOW}🔐 Configuração SSH:${colors.NC}`); console.log(" • Configure chaves SSH sem senha: ssh-keygen -t rsa -b 4096"); console.log(" • Copie a chave pública: ssh-copy-id user@servidor"); console.log(" • Teste a conexão: ssh user@servidor"); } shouldExcludeFile(filePath) { const fileName = path.basename(filePath); const relativePath = filePath; const defaultExclusions = [ 'node_modules', '.git', '.vscode', '.idea', 'dist', 'build', 'out', 'coverage', '.nyc_output', 'package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', '.DS_Store', 'Thumbs.db', '*.log', '*.tmp', '*.temp' ]; for (const pattern of defaultExclusions) { if (pattern.includes('*')) { const regex = new RegExp(pattern.replace(/\*/g, '.*')); if (regex.test(fileName)) { return true; } } else if (relativePath.includes(pattern) || fileName === pattern) { return true; } } for (const pattern of this.options.excludePatterns) { if (pattern.includes('*')) { const regex = new RegExp(pattern.replace(/\*/g, '.*')); if (regex.test(fileName) || regex.test(relativePath)) { return true; } } else if (relativePath.includes(pattern) || fileName === pattern) { return true; } } return false; } shouldIncludeFile(filePath) { if (this.options.includePatterns.length === 0) { return true; } const fileName = path.basename(filePath); const relativePath = filePath; for (const pattern of this.options.includePatterns) { if (pattern.includes('*')) { const regex = new RegExp(pattern.replace(/\*/g, '.*')); if (regex.test(fileName) || regex.test(relativePath)) { return true; } } else if (relativePath.includes(pattern) || fileName === pattern) { return true; } } return false; } isTextFile(filePath) { const textExtensions = [ '.txt', '.md', '.json', '.js', '.ts', '.tsx', '.jsx', '.html', '.htm', '.css', '.scss', '.sass', '.less', '.xml', '.yaml', '.yml', '.toml', '.ini', '.cfg', '.py', '.java', '.c', '.cpp', '.h', '.hpp', '.cs', '.php', '.rb', '.go', '.rs', '.swift', '.kt', '.scala', '.clj', '.hs', '.elm', '.f#', '.vb', '.sql', '.sh', '.bash', '.zsh', '.fish', '.ps1', '.bat', '.cmd', '.dockerfile', '.gitignore', '.gitattributes', '.editorconfig', '.prettierrc', '.eslintrc', '.babelrc', '.stylelintrc' ]; const binaryExtensions = [ '.exe', '.dll', '.so', '.dylib', '.bin', '.o', '.obj', '.a', '.lib', '.zip', '.tar', '.gz', '.bz2', '.7z', '.rar', '.iso', '.dmg', '.img', '.deb', '.rpm', '.msi', '.pkg', '.app', '.apk', '.ipa', '.jpg', '.jpeg', '.png', '.gif', '.bmp', '.ico', '.svg', '.webp', '.tiff', '.psd', '.ai', '.mp3', '.mp4', '.avi', '.mkv', '.wav', '.flac', '.mov', '.wmv', '.m4a', '.ogg', '.webm', '.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx', '.ttf', '.otf', '.woff', '.woff2', '.eot', '.pyc', '.pyo', '.class' ]; const ext = path.extname(filePath).toLowerCase(); if (binaryExtensions.includes(ext)) { if (this.options.verbose) { console.log(`${colors.YELLOW}🔍 Detected binary file (extension): ${filePath}${colors.NC}`); } return false; } if (textExtensions.includes(ext)) { return true; } if (!ext && fs.existsSync(filePath)) { try { const buffer = fs.readFileSync(filePath, { flag: 'r' }); if (buffer.length > 0) { const checkSize = Math.min(512, buffer.length); for (let i = 0; i < checkSize; i++) { if (buffer[i] === 0) { if (this.options.verbose) { console.log(`${colors.YELLOW}🔍 Detected binary file (null bytes): ${filePath}${colors.NC}`); } return false; } } } } catch (error) { if (this.options.verbose) { console.log(`${colors.YELLOW}🔍 Cannot read file (assuming binary): ${filePath}${colors.NC}`); } return false; } } return true; } getAllFiles(dir, basePath = '') { const files = []; try { const items = fs.readdirSync(dir, { withFileTypes: true }); for (const item of items) { const fullPath = path.join(dir, item.name); const relativePath = path.join(basePath, item.name); if (this.shouldExcludeFile(relativePath)) { if (this.options.verbose) { console.log(`${colors.YELLOW}⏭️ Excluindo: ${relativePath}${colors.NC}`); } continue; } if (item.isDirectory()) { files.push(...this.getAllFiles(fullPath, relativePath)); } else if (item.isFile()) { if (this.shouldIncludeFile(relativePath) && this.isTextFile(fullPath)) { const stat = fs.statSync(fullPath); const sizeKB = stat.size / 1024; if (sizeKB <= this.options.maxSizeKB) { files.push(relativePath); this.stats.totalSize += stat.size; if (stat.size > this.stats.largestFile.size) { this.stats.largestFile = { path: relativePath, size: stat.size }; } } else { if (this.options.verbose) { console.log(`${colors.YELLOW}📏 Arquivo muito grande, pulando: ${relativePath} (${Math.round(sizeKB)}KB > ${this.options.maxSizeKB}KB)${colors.NC}`); } this.stats.skippedFiles++; } } else if (this.options.verbose) { console.log(`${colors.YELLOW}🚫 Não incluído: ${relativePath}${colors.NC}`); } } } } catch (error) { console.error(`${colors.RED}❌ Erro ao ler diretório ${dir}: ${error}${colors.NC}`); } return files; } async generateMarkers(sourceDir, outputFile) { const startTime = Date.now(); console.log(`${colors.CYAN}🔧 LookAtni Marker Generator v4.0${colors.NC}`); console.log("================================================"); if (!fs.existsSync(sourceDir)) { throw new Error(`${colors.RED}❌ Erro: Diretório '${sourceDir}' não encontrado!${colors.NC}`); } if (!fs.statSync(sourceDir).isDirectory()) { throw new Error(`${colors.RED}❌ Erro: '${sourceDir}' não é um diretório!${colors.NC}`); } console.log(`${colors.BLUE}📁 Diretório fonte: ${sourceDir}${colors.NC}`); console.log(`${colors.BLUE}📄 Arquivo de saída: ${outputFile}${colors.NC}`); console.log(`${colors.BLUE}📏 Tamanho máximo por arquivo: ${this.options.maxSizeKB}KB${colors.NC}`); if (this.options.excludePatterns.length > 0) { console.log(`${colors.BLUE}🚫 Padrões de exclusão: ${this.options.excludePatterns.join(', ')}${colors.NC}`); } if (this.options.includePatterns.length > 0) { console.log(`${colors.BLUE}✅ Padrões de inclusão: ${this.options.includePatterns.join(', ')}${colors.NC}`); } if (this.options.remote) { console.log(""); console.log(`${colors.PURPLE}🌐 Configuração Remota:${colors.NC}`); console.log(`${colors.BLUE}📡 Destino: ${this.options.remote}${colors.NC}`); if (this.options.sshKey) { console.log(`${colors.BLUE}🔑 Chave SSH: ${this.options.sshKey}${colors.NC}`); } if (this.options.compress) { console.log(`${colors.BLUE}🗜️ Compressão: Habilitada${colors.NC}`); } console.log(`${colors.BLUE}🔄 Tentativas: ${this.options.retryAttempts}${colors.NC}`); } console.log(""); console.log(`${colors.GREEN}🔍 Escaneando arquivos...${colors.NC}`); const files = this.getAllFiles(sourceDir); this.stats.totalFiles = files.length; if (files.length === 0) { throw new Error(`${colors.RED}❌ Nenhum arquivo encontrado para processar!${colors.NC}\n${colors.YELLOW}💡 Verifique os filtros de inclusão/exclusão${colors.NC}`); } console.log(`${colors.GREEN}📋 Encontrados ${files.length} arquivo(s) para processar${colors.NC}`); console.log(""); if (this.options.verbose) { console.log(`${colors.PURPLE}📁 Arquivos a serem processados:${colors.NC}`); files.forEach((file, index) => { console.log(` ${(index + 1).toString().padStart(3)}. ${file}`); }); console.log(""); } console.log(`${colors.GREEN}🚀 Gerando marcadores...${colors.NC}`); let output = ''; output += await this.generateRichHeader(sourceDir, files.length); output += `\n`; for (let i = 0; i < files.length; i++) { const file = files[i]; const filePath = path.join(sourceDir, file); console.log(`[${(i + 1).toString().padStart(3)}/${files.length}] 📄 ${file}`); try { const content = fs.readFileSync(filePath, 'utf-8'); output += `${MARKER_START} ${file} ${MARKER_END}\n`; output += content; if (!content.endsWith('\n')) { output += '\n'; } if (i < files.length - 1) { output += '\n'; } this.stats.processedFiles++; if (this.options.verbose) { const lines = content.split('\n').length; const bytes = Buffer.byteLength(content, 'utf-8'); console.log(` ${colors.GREEN}✅ Adicionado (${lines} linhas, ${bytes} bytes)${colors.NC}`); } } catch (error) { console.log(` ${colors.RED}❌ Erro ao ler arquivo: ${error}${colors.NC}`); this.stats.skippedFiles++; } } try { fs.writeFileSync(outputFile, output, 'utf-8'); this.stats.outputSize = Buffer.byteLength(output, 'utf-8'); console.log(""); console.log(`${colors.GREEN}💾 Arquivo salvo: ${outputFile}${colors.NC}`); if (this.options.remote) { const remoteInfo = this.parseRemoteDestination(this.options.remote); if (remoteInfo) { console.log(""); console.log(`${colors.CYAN}🚀 Iniciando upload remoto...${colors.NC}`); const connectionOk = await this.testSSHConnection(this.options.remote, this.options.sshKey); if (connectionOk) { const uploadSuccess = await this.uploadWithRetry(outputFile, this.options.remote, this.options.sshKey); if (uploadSuccess) { console.log(`${colors.GREEN}🎉 Upload remoto concluído com sucesso!${colors.NC}`); console.log(`${colors.BLUE}📍 Localização remota: ${this.options.remote}${colors.NC}`); } else { console.log(`${colors.RED}❌ Falha no upload remoto após ${this.options.retryAttempts} tentativas${colors.NC}`); console.log(`${colors.YELLOW}💡 Arquivo local salvo em: ${outputFile}${colors.NC}`); } } else { console.log(`${colors.RED}❌ Não foi possível conectar ao servidor remoto${colors.NC}`); console.log(`${colors.YELLOW}💡 Verifique a conectividade SSH e as credenciais${colors.NC}`); console.log(`${colors.YELLOW}💡 Arquivo local salvo em: ${outputFile}${colors.NC}`); } } } } catch (error) { throw new Error(`${colors.RED}❌ Erro ao salvar arquivo: ${error}${colors.NC}`); } const duration = Date.now() - startTime; this.printSummary(outputFile, duration); } async generateRichHeader(sourceDir, totalFiles) { const now = new Date(); const hostname = os.hostname(); const platform = os.platform(); const arch = os.arch(); const release = os.release(); const userInfo = os.userInfo(); const shell = process.env.SHELL || 'unknown'; let osInfo = `${platform} ${arch}`; if (platform === 'linux') { try { const releaseInfo = fs.readFileSync('/etc/os-release', 'utf-8'); const prettyName = releaseInfo.match(/PRETTY_NAME="([^"]+)"/); if (prettyName) { osInfo = `${platform} ${arch} (${prettyName[1]})`; } } catch (error) { osInfo = `${platform} ${arch}`; } } const hashInput = `${sourceDir}-${totalFiles}-${now.toISOString()}-${hostname}`; const hash = crypto.createHash('sha256').update(hashInput).digest('hex').substring(0, 20); let version = 'unknown'; try { const packagePath = path.join(__dirname, '../../package.json'); if (fs.existsSync(packagePath)) { const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf-8')); version = `v${packageJson.version}`; } } catch (error) { } const rawSizeMB = (this.stats.totalSize / (1024 * 1024)).toFixed(1); const header = `# LookAtni Code Snapshot # ----------------------- # Data de geração: ${now.toISOString()} # Fonte: ${path.resolve(sourceDir)} # Hostname: ${hostname} # Sistema: ${osInfo} # Kernel: ${release} # Usuário: ${userInfo.username} # UID: ${userInfo.uid} # Shell: ${shell} # Total de arquivos: ${totalFiles} # Tamanho bruto: ${rawSizeMB} MB # Gerado por: lookatni@${version} (via CLI Script) # Comando usado: tsx generateMarkers.ts ${process.argv.slice(2).join(' ')} # Hash do snapshot: ${hash}... `; return header; } printSummary(outputFile, duration) { console.log(""); console.log(`${colors.CYAN}🎉 Geração concluída em ${duration}ms!${colors.NC}`); console.log("================================"); console.log(`${colors.PURPLE}📊 Resumo:${colors.NC}`); console.log(` • ${colors.GREEN}✅ Processados: ${this.stats.processedFiles}${colors.NC}`); console.log(` • ${colors.YELLOW}⏭️ Pulados: ${this.stats.skippedFiles}${colors.NC}`); console.log(` • ${colors.BLUE}📁 Total encontrados: ${this.stats.totalFiles}${colors.NC}`); console.log(` • ${colors.CYAN}💾 Tamanho de entrada: ${Math.round(this.stats.totalSize / 1024)}KB${colors.NC}`); console.log(` • ${colors.CYAN}💾 Tamanho de saída: ${Math.round(this.stats.outputSize / 1024)}KB${colors.NC}`); console.log(` • ${colors.PURPLE}📏 Maior arquivo: ${this.stats.largestFile.path} (${Math.round(this.stats.largestFile.size / 1024)}KB)${colors.NC}`); console.log(` • ${colors.YELLOW}⏱️ Tempo: ${duration}ms${colors.NC}`); console.log(""); console.log(`${colors.GREEN}🔍 Arquivo gerado: ${outputFile}${colors.NC}`); console.log(""); console.log(`${colors.CYAN}🚀 Para extrair os arquivos:${colors.NC}`); console.log(` ${colors.YELLOW}tsx extractFiles.ts ${outputFile} ./extracted${colors.NC}`); console.log(""); console.log(`${colors.CYAN}🔧 Para visualizar marcadores:${colors.NC}`); console.log(` ${colors.YELLOW}grep '^${MARKER_START.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}' ${outputFile}${colors.NC}`); console.log(""); console.log(`${colors.PURPLE}💡 Comandos úteis:${colors.NC}`); console.log(` ${colors.YELLOW}# Contar marcadores:${colors.NC}`); console.log(` grep '^${MARKER_START.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}' ${outputFile} | wc -l`); console.log(` ${colors.YELLOW}# Listar arquivos:${colors.NC}`); console.log(` grep '^${MARKER_START.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}' ${outputFile} | sed 's/^${MARKER_START.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')} \\(.*\\) ${MARKER_END.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$/\\1/'`); console.log(` ${colors.YELLOW}# Validar formato:${colors.NC}`); console.log(` tsx extractFiles.ts ${outputFile} --format`); } async testSSHConnection(remote, sshKey) { return new Promise((resolve) => { const [userHost] = remote.split(':'); const sshArgs = ['ssh', '-o', 'ConnectTimeout=10', '-o', 'BatchMode=yes']; if (sshKey) { sshArgs.push('-i', sshKey); } sshArgs.push(userHost, 'echo "SSH_TEST_OK"'); if (this.options.verbose) { console.log(`${colors.BLUE}🔑 Testando conexão SSH: ${userHost}${colors.NC}`); } const ssh = (0, child_process_1.spawn)(sshArgs[0], sshArgs.slice(1), { stdio: 'pipe' }); let output = ''; ssh.stdout.on('data', (data) => { output += data.toString(); }); ssh.on('close', (code) => { const success = code === 0 && output.includes('SSH_TEST_OK'); if (this.options.verbose) { console.log(`${success ? colors.GREEN + '✅' : colors.RED + '❌'} Conexão SSH: ${success ? 'Sucesso' : 'Falhou'}${colors.NC}`); } resolve(success); }); ssh.on('error', () => resolve(false)); }); } async uploadFileWithSCP(localFile, remote, sshKey) { return new Promise((resolve) => { const scpArgs = ['scp', '-o', 'ConnectTimeout=30']; if (sshKey) { scpArgs.push('-i', sshKey); } if (this.options.compress) { scpArgs.push('-C'); } scpArgs.push(localFile, remote); if (this.options.verbose) { console.log(`${colors.CYAN}📤 Enviando: ${localFile}${remote}${colors.NC}`); console.log(`${colors.YELLOW}🔧 Comando: ${scpArgs.join(' ')}${colors.NC}`); } const scp = (0, child_process_1.spawn)(scpArgs[0], scpArgs.slice(1), { stdio: 'pipe' }); let errorOutput = ''; scp.stderr.on('data', (data) => { errorOutput += data.toString(); }); scp.on('close', (code) => { const success = code === 0; if (!success && this.options.verbose) { console.log(`${colors.RED}❌ Erro no SCP: ${errorOutput}${colors.NC}`); } resolve(success); }); scp.on('error', (err) => { if (this.options.verbose) { console.log(`${colors.RED}❌ Erro ao executar SCP: ${err.message}${colors.NC}`); } resolve(false); }); }); } async uploadWithRetry(localFile, remote, sshKey) { for (let attempt = 1; attempt <= this.options.retryAttempts; attempt++) { if (this.options.verbose && attempt > 1) { console.log(`${colors.YELLOW}🔄 Tentativa ${attempt}/${this.options.retryAttempts}${colors.NC}`); } const success = await this.uploadFileWithSCP(localFile, remote, sshKey); if (success) { if (attempt > 1) { console.log(`${colors.GREEN}✅ Upload bem-sucedido na tentativa ${attempt}${colors.NC}`); } return true; } if (attempt < this.options.retryAttempts) { const delay = Math.min(1000 * Math.pow(2, attempt - 1), 5000); if (this.options.verbose) { console.log(`${colors.YELLOW}⏳ Aguardando ${delay}ms antes da próxima tentativa...${colors.NC}`); } await new Promise(resolve => setTimeout(resolve, delay)); } } return false; } parseRemoteDestination(remote) { const match = remote.match(/^([^:]+):(.+)$/); if (!match) { console.log(`${colors.RED}❌ Formato de destino remoto inválido: ${remote}${colors.NC}`); console.log(`${colors.YELLOW}💡 Use o formato: user@host:/path/to/destination${colors.NC}`); return null; } return { userHost: match[1], path: match[2] }; } } exports.LookAtniGenerator = LookAtniGenerator; async function main() { const args = process.argv.slice(2); const options = { excludePatterns: [], includePatterns: [], maxSizeKB: 1000, verbose: false, compress: false, retryAttempts: 3 }; let sourceDir = '.'; let outputFile = 'lookatni-code.txt'; for (let i = 0; i < args.length; i++) { const arg = args[i]; switch (arg) { case '-e': case '--exclude': if (i + 1 < args.length) { options.excludePatterns.push(args[++i]); } else { console.error(`❌ Padrão de exclusão necessário após ${arg}`); process.exit(1); } break; case '-i': case '--include': if (i + 1 < args.length) { options.includePatterns.push(args[++i]); } else { console.error(`❌ Padrão de inclusão necessário após ${arg}`); process.exit(1); } break; case '-m': case '--max-size': if (i + 1 < args.length) { const size = parseInt(args[++i]); if (isNaN(size) || size <= 0) { console.error(`❌ Tamanho máximo inválido: ${args[i]}`); process.exit(1); } options.maxSizeKB = size; } else { console.error(`❌ Tamanho máximo necessário após ${arg}`); process.exit(1); } break; case '-r': case '--remote': if (i + 1 < args.length) { options.remote = args[++i]; } else { console.error(`❌ Destino remoto necessário após ${arg}`); process.exit(1); } break; case '-k': case '--ssh-key': if (i + 1 < args.length) { options.sshKey = args[++i]; } else { console.error(`❌ Caminho da chave SSH necessário após ${arg}`); process.exit(1); } break; case '-c': case '--compress': options.compress = true; break; case '--retry': if (i + 1 < args.length) { const retry = parseInt(args[++i]); if (isNaN(retry) || retry <= 0) { console.error(`❌ Número de tentativas inválido: ${args[i]}`); process.exit(1); } options.retryAttempts = retry; } else { console.error(`❌ Número de tentativas necessário após ${arg}`); process.exit(1); } break; case '-v': case '--verbose': options.verbose = true; break; case '-h': case '--help': new LookAtniGenerator(options).printHelp(); process.exit(0); default: if (arg.startsWith('-')) { console.error(`❌ Opção desconhecida: ${arg}`); console.error("Use --help para ajuda"); process.exit(1); } else { if (sourceDir === '.') { sourceDir = arg; } else if (outputFile === 'lookatni-code.txt') { outputFile = arg; } else { console.error("❌ Muitos argumentos!"); process.exit(1); } } break; } } try { const generator = new LookAtniGenerator(options); await generator.generateMarkers(sourceDir, outputFile); process.exit(0); } catch (error) { console.error(error instanceof Error ? error.message : String(error)); process.exit(1); } } if (require.main === module) { main().catch(console.error); }