Skip to main content
Glama
cliReport.tsβ€’9.09 kB
import path from 'node:path'; import pc from 'picocolors'; import type { RepomixConfigMerged } from '../config/configSchema.js'; import type { SkippedFileInfo } from '../core/file/fileCollect.js'; import type { PackResult } from '../core/packager.js'; import type { SuspiciousFileResult } from '../core/security/securityCheck.js'; import { logger } from '../shared/logger.js'; import { reportTokenCountTree } from './reporters/tokenCountTreeReporter.js'; /** * Convert an absolute path to a relative path if it's under cwd, otherwise return as-is. */ export const getDisplayPath = (absolutePath: string, cwd: string): string => { return absolutePath.startsWith(cwd) ? path.relative(cwd, absolutePath) : absolutePath; }; export interface ReportOptions { skillDir?: string; } /** * Reports the results of packing operation including top files, security check, summary, and completion. */ export const reportResults = ( cwd: string, packResult: PackResult, config: RepomixConfigMerged, options: ReportOptions = {}, ): void => { logger.log(''); if (config.output.topFilesLength > 0) { reportTopFiles( packResult.fileCharCounts, packResult.fileTokenCounts, config.output.topFilesLength, packResult.totalTokens, ); logger.log(''); } if (config.output.tokenCountTree) { reportTokenCountTree(packResult.processedFiles, packResult.fileTokenCounts, config); logger.log(''); } reportSecurityCheck( cwd, packResult.suspiciousFilesResults, packResult.suspiciousGitDiffResults, packResult.suspiciousGitLogResults, config, ); logger.log(''); reportSkippedFiles(cwd, packResult.skippedFiles); logger.log(''); reportSummary(cwd, packResult, config, options); logger.log(''); reportCompletion(); }; export const reportSummary = ( cwd: string, packResult: PackResult, config: RepomixConfigMerged, options: ReportOptions = {}, ) => { let securityCheckMessage = ''; if (config.security.enableSecurityCheck) { if (packResult.suspiciousFilesResults.length > 0) { securityCheckMessage = pc.yellow( `${packResult.suspiciousFilesResults.length.toLocaleString()} suspicious file(s) detected and excluded`, ); } else { securityCheckMessage = pc.white('βœ” No suspicious files detected'); } } else { securityCheckMessage = pc.dim('Security check disabled'); } logger.log(pc.white('πŸ“Š Pack Summary:')); logger.log(pc.dim('────────────────')); logger.log(`${pc.white(' Total Files:')} ${pc.white(packResult.totalFiles.toLocaleString())} files`); logger.log(`${pc.white(' Total Tokens:')} ${pc.white(packResult.totalTokens.toLocaleString())} tokens`); logger.log(`${pc.white(' Total Chars:')} ${pc.white(packResult.totalCharacters.toLocaleString())} chars`); // Show skill output path or regular output path if (config.skillGenerate !== undefined && options.skillDir) { const displayPath = getDisplayPath(options.skillDir, cwd); logger.log(`${pc.white(' Output:')} ${pc.white(displayPath)} ${pc.dim('(skill directory)')}`); } else { const outputPath = path.resolve(cwd, config.output.filePath); const displayPath = getDisplayPath(outputPath, cwd); logger.log(`${pc.white(' Output:')} ${pc.white(displayPath)}`); } logger.log(`${pc.white(' Security:')} ${pc.white(securityCheckMessage)}`); if (config.output.git?.includeDiffs) { let gitDiffsMessage = ''; if (packResult.gitDiffTokenCount) { gitDiffsMessage = pc.white( `βœ” Git diffs included ${pc.dim(`(${packResult.gitDiffTokenCount.toLocaleString()} tokens)`)}`, ); } else { gitDiffsMessage = pc.dim('βœ– No git diffs included'); } logger.log(`${pc.white(' Git Diffs:')} ${gitDiffsMessage}`); } if (config.output.git?.includeLogs) { let gitLogsMessage = ''; if (packResult.gitLogTokenCount) { gitLogsMessage = pc.white( `βœ” Git logs included ${pc.dim(`(${packResult.gitLogTokenCount.toLocaleString()} tokens)`)}`, ); } else { gitLogsMessage = pc.dim('βœ– No git logs included'); } logger.log(`${pc.white(' Git Logs:')} ${gitLogsMessage}`); } }; export const reportSecurityCheck = ( rootDir: string, suspiciousFilesResults: SuspiciousFileResult[], suspiciousGitDiffResults: SuspiciousFileResult[], suspiciousGitLogResults: SuspiciousFileResult[], config: RepomixConfigMerged, ) => { if (!config.security.enableSecurityCheck) { return; } logger.log(pc.white('πŸ”Ž Security Check:')); logger.log(pc.dim('──────────────────')); // Report results for files if (suspiciousFilesResults.length === 0) { logger.log(`${pc.green('βœ”')} ${pc.white('No suspicious files detected.')}`); } else { logger.log(pc.yellow(`${suspiciousFilesResults.length} suspicious file(s) detected and excluded from the output:`)); suspiciousFilesResults.forEach((suspiciousFilesResult, index) => { const relativeFilePath = path.relative(rootDir, suspiciousFilesResult.filePath); logger.log(`${pc.white(`${index + 1}.`)} ${pc.white(relativeFilePath)}`); const issueCount = suspiciousFilesResult.messages.length; const issueText = issueCount === 1 ? 'security issue' : 'security issues'; logger.log(pc.dim(` - ${issueCount} ${issueText} detected`)); }); logger.log(pc.yellow('\nThese files have been excluded from the output for security reasons.')); logger.log(pc.yellow('Please review these files for potential sensitive information.')); } // Report git-related security issues reportSuspiciousGitContent('Git diffs', suspiciousGitDiffResults); reportSuspiciousGitContent('Git logs', suspiciousGitLogResults); }; const reportSuspiciousGitContent = (title: string, results: SuspiciousFileResult[]) => { if (results.length === 0) { return; } logger.log(''); logger.log(pc.yellow(`${results.length} security issue(s) found in ${title}:`)); results.forEach((suspiciousResult, index) => { logger.log(`${pc.white(`${index + 1}.`)} ${pc.white(suspiciousResult.filePath)}`); const issueCount = suspiciousResult.messages.length; const issueText = issueCount === 1 ? 'security issue' : 'security issues'; logger.log(pc.dim(` - ${issueCount} ${issueText} detected`)); }); logger.log(pc.yellow(`\nNote: ${title} with security issues are still included in the output.`)); logger.log(pc.yellow(`Please review the ${title.toLowerCase()} before sharing the output.`)); }; export const reportTopFiles = ( fileCharCounts: Record<string, number>, fileTokenCounts: Record<string, number>, topFilesLength: number, totalTokens: number, ) => { const topFilesLengthStrLen = topFilesLength.toString().length; logger.log(pc.white(`πŸ“ˆ Top ${topFilesLength} Files by Token Count:`)); logger.log(pc.dim(`─────────────────────────────${'─'.repeat(topFilesLengthStrLen)}`)); // Filter files that have token counts (top candidates by char count) const filesWithTokenCounts = Object.entries(fileTokenCounts) .filter(([, tokenCount]) => tokenCount > 0) .sort((a, b) => b[1] - a[1]) .slice(0, topFilesLength); // Use the actual total tokens from the entire output filesWithTokenCounts.forEach(([filePath, tokenCount], index) => { const charCount = fileCharCounts[filePath]; const percentageOfTotal = totalTokens > 0 ? Number(((tokenCount / totalTokens) * 100).toFixed(1)) : 0; const indexString = `${index + 1}.`.padEnd(3, ' '); logger.log( `${pc.white(`${indexString}`)} ${pc.white(filePath)} ${pc.dim(`(${tokenCount.toLocaleString()} tokens, ${charCount.toLocaleString()} chars, ${percentageOfTotal}%)`)}`, ); }); }; export const reportSkippedFiles = (_rootDir: string, skippedFiles: SkippedFileInfo[]) => { const binaryContentFiles = skippedFiles.filter((file) => file.reason === 'binary-content'); if (binaryContentFiles.length === 0) { return; } logger.log(pc.white('πŸ“„ Binary Files Detected:')); logger.log(pc.dim('─────────────────────────')); if (binaryContentFiles.length === 1) { logger.log(pc.yellow('1 file detected as binary by content inspection:')); } else { logger.log(pc.yellow(`${binaryContentFiles.length} files detected as binary by content inspection:`)); } binaryContentFiles.forEach((file, index) => { logger.log(`${pc.white(`${index + 1}.`)} ${pc.white(file.path)}`); }); logger.log(pc.yellow('\nThese files have been excluded from the output.')); logger.log(pc.yellow('Please review these files if you expected them to contain text content.')); }; export const reportCompletion = () => { logger.log(pc.green('πŸŽ‰ All Done!')); logger.log(pc.white('Your repository has been successfully packed.')); logger.log(''); logger.log(`πŸ’‘ Repomix is now available in your browser! Try it at ${pc.underline('https://repomix.com')}`); };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/yamadashy/repomix'

If you have feedback or need assistance with the MCP directory API, please join our Discord server