Last active
March 18, 2025 14:57
-
-
Save bkataru/a1c353aa053ae3b61a541f38e229b05e to your computer and use it in GitHub Desktop.
bash & powershell scripts to compile the contents of the files in the current directory as context for an LLM
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <# | |
| .SYNOPSIS | |
| Generate a context file by compiling the contents of files in a directory. | |
| .DESCRIPTION | |
| This script scans a specified directory, compiles file contents into a single output file, and excludes specified directories, files, and optionally hidden paths (with dot-prefixed names). It respects a maximum file size limit and provides detailed logging. | |
| .PARAMETER OutputFile | |
| Specifies the output file name. Default is "context.txt". | |
| .PARAMETER SourceDir | |
| Specifies the source directory to scan. Default is the current directory. | |
| .PARAMETER ExcludeDir | |
| Specifies directories to exclude. Can be used multiple times. | |
| .PARAMETER ExcludeFile | |
| Specifies files to exclude. Can be used multiple times. | |
| .PARAMETER NoHidden | |
| Excludes paths with dot-prefixed components (e.g., ".git"). | |
| .PARAMETER MaxSize | |
| Maximum file size in bytes. Default is 1MB (1048576 bytes). | |
| .PARAMETER Verbose | |
| Enables verbose output for detailed logging. | |
| .PARAMETER Help | |
| Displays this help message. | |
| .EXAMPLE | |
| .\GenerateContext.ps1 -SourceDir ./project -ExcludeDir build -ExcludeFile README.md | |
| #> | |
| param( | |
| [Alias('o')][string]$OutputFile = "context.txt", | |
| [Alias('s')][string]$SourceDir = ".", | |
| [Alias('e')][string[]]$ExcludeDir, | |
| [Alias('f')][string[]]$ExcludeFile, | |
| [Alias('n')][switch]$NoHidden, | |
| [Alias('m')][ValidateRange(1, [int]::MaxValue)][int]$MaxSize = 1048576, | |
| [Alias('v')][switch]$Verbose, | |
| [Alias('h')][switch]$Help | |
| ) | |
| # Show help if no parameters are provided | |
| if ($PSBoundParameters.Count -eq 0) { | |
| Get-Help $MyInvocation.MyCommand.Path -Detailed | |
| exit 0 | |
| } | |
| # Show help if -Help is specified | |
| if ($Help) { | |
| Get-Help $MyInvocation.MyCommand.Path -Detailed | |
| exit 0 | |
| } | |
| # Logging function | |
| function Write-Log { | |
| param( | |
| [Parameter(Mandatory)][string]$Level, | |
| [Parameter(Mandatory)][string]$Message | |
| ) | |
| $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" | |
| $logMessage = "[$timestamp] [$Level] $Message" | |
| if ($Verbose -or $Level -eq "ERROR") { | |
| [Console]::Error.WriteLine($logMessage) | |
| } | |
| } | |
| # Configuration | |
| $SCRIPT_NAME = Split-Path -Leaf $MyInvocation.MyCommand.Path | |
| $DEFAULT_EXCLUDE_DIRS = @(".git", "node_modules", "venv", ".venv") | |
| $DEFAULT_EXCLUDE_FILES = @($SCRIPT_NAME, $OutputFile) | |
| $EXCLUDE_DIRS = @($DEFAULT_EXCLUDE_DIRS + $ExcludeDir | Sort-Object -Unique) | |
| $EXCLUDE_FILES = @($DEFAULT_EXCLUDE_FILES + $ExcludeFile | Sort-Object -Unique) | |
| $INCLUDE_HIDDEN = -not $NoHidden.IsPresent | |
| $MAX_FILE_SIZE = $MaxSize | |
| # Validate source directory | |
| $resolvedSourceDir = Resolve-Path -Path $SourceDir -ErrorAction Stop | |
| if (-not (Test-Path -Path $resolvedSourceDir -PathType Container)) { | |
| Write-Log "ERROR" "Source directory '$SourceDir' does not exist" | |
| exit 1 | |
| } | |
| # Function to check if any path component starts with a dot | |
| function Test-HiddenPath { | |
| param([string]$Path) | |
| $relativePath = $Path.Substring($resolvedSourceDir.Path.Length).TrimStart('\', '/') | |
| $components = @($relativePath -split '[\\\/]' | Where-Object { $_ }) | |
| foreach ($part in $components) { | |
| if ($part -like '.*') { | |
| return $true | |
| } | |
| } | |
| return $false | |
| } | |
| # Initialize variables | |
| $FILE_COUNT = 0 | |
| $SKIPPED_COUNT = 0 | |
| $TOTAL_SIZE = 0 | |
| $TEMP_FILE = New-TemporaryFile | |
| try { | |
| Write-Log "INFO" "Starting context generation from: $SourceDir" | |
| Write-Log "INFO" "Output file: $OutputFile" | |
| # Collect and process files | |
| Get-ChildItem -Path $resolvedSourceDir -Recurse -File -Force -ErrorAction Stop | | |
| Where-Object { | |
| $dirPath = $_.Directory.FullName | |
| $relDirPath = if ($dirPath -eq $resolvedSourceDir.Path) { '' } else { $dirPath.Substring($resolvedSourceDir.Path.Length).TrimStart('\', '/') } | |
| $dirs = @($relDirPath -split '[\\\/]' | Where-Object { $_ }) | |
| $excludeDirMatch = $dirs | Where-Object { $_ -in $EXCLUDE_DIRS } | |
| $hiddenMatch = if (-not $INCLUDE_HIDDEN) { Test-HiddenPath $_.FullName } else { $false } | |
| -not ($excludeDirMatch -or $hiddenMatch) -and | |
| $_.Name -notin $EXCLUDE_FILES -and | |
| $_.Length -le $MAX_FILE_SIZE | |
| } | ForEach-Object { | |
| $file = $_ | |
| $relPath = $file.FullName.Substring($resolvedSourceDir.Path.Length).TrimStart('\', '/') | |
| $fileSize = $file.Length | |
| try { | |
| $content = Get-Content -Path $file.FullName -Raw -ErrorAction Stop | |
| @" | |
| ==== FILE: $relPath ==== | |
| $content | |
| "@ | Add-Content -Path $TEMP_FILE -ErrorAction Stop | |
| $FILE_COUNT++ | |
| $TOTAL_SIZE += $fileSize | |
| Write-Log "INFO" "Added: $relPath ($fileSize bytes)" | |
| } catch { | |
| Write-Log "ERROR" "Failed to read file: $relPath - $($_.Exception.Message)" | |
| $SKIPPED_COUNT++ | |
| } | |
| } | |
| # Finalize output | |
| Move-Item -Path $TEMP_FILE -Destination $OutputFile -Force -ErrorAction Stop | |
| Write-Log "INFO" "Context generation complete" | |
| Write-Log "INFO" "Files processed: $FILE_COUNT" | |
| Write-Log "INFO" "Files skipped: $SKIPPED_COUNT" | |
| Write-Log "INFO" "Total size: $TOTAL_SIZE bytes" | |
| } catch { | |
| Write-Log "ERROR" "Script failed: $($_.Exception.Message)" | |
| exit 1 | |
| } finally { | |
| if (Test-Path -Path $TEMP_FILE) { | |
| Remove-Item -Path $TEMP_FILE -Force | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # Default configuration | |
| OUTPUT_FILE="context.txt" | |
| SCRIPT_NAME=$(basename "$0") | |
| DEFAULT_EXCLUDE_DIRS=(".git" "node_modules" "venv" ".venv") | |
| DEFAULT_EXCLUDE_FILES=("$SCRIPT_NAME" "$OUTPUT_FILE") | |
| EXCLUDE_DIRS=() | |
| EXCLUDE_FILES=() | |
| SOURCE_DIR="." | |
| VERBOSE=false | |
| INCLUDE_HIDDEN=true | |
| MAX_FILE_SIZE=1048576 # 1MB in bytes | |
| # Function for logging | |
| log() { | |
| local level="$1" | |
| local message="$2" | |
| local timestamp=$(date "+%Y-%m-%d %H:%M:%S") | |
| if [[ "$VERBOSE" == true || "$level" == "ERROR" ]]; then | |
| echo "[$timestamp] [$level] $message" >&2 | |
| fi | |
| } | |
| # Function to display help | |
| show_help() { | |
| cat << EOF | |
| Usage: $SCRIPT_NAME [OPTIONS] | |
| Generate a context file by compiling the contents of files in a directory. | |
| Options: | |
| -o, --output FILE Specify output file (default: $OUTPUT_FILE) | |
| -s, --source DIR Source directory to scan (default: current directory) | |
| -e, --exclude-dir DIR Directory to exclude (can be used multiple times) | |
| -f, --exclude-file FILE File to exclude (can be used multiple times) | |
| -n, --no-hidden Exclude hidden files and directories | |
| -m, --max-size SIZE Maximum file size in bytes (default: 1MB) | |
| -v, --verbose Enable verbose output | |
| -h, --help Display this help message | |
| Example: | |
| $SCRIPT_NAME --source ./project --exclude-dir build --exclude-file README.md | |
| EOF | |
| } | |
| # Parse command line arguments | |
| while [[ $# -gt 0 ]]; do | |
| case "$1" in | |
| -o|--output) | |
| OUTPUT_FILE="$2" | |
| shift 2 | |
| ;; | |
| -s|--source) | |
| SOURCE_DIR="$2" | |
| shift 2 | |
| ;; | |
| -e|--exclude-dir) | |
| EXCLUDE_DIRS+=("$2") | |
| shift 2 | |
| ;; | |
| -f|--exclude-file) | |
| EXCLUDE_FILES+=("$2") | |
| shift 2 | |
| ;; | |
| -n|--no-hidden) | |
| INCLUDE_HIDDEN=false | |
| shift | |
| ;; | |
| -m|--max-size) | |
| MAX_FILE_SIZE="$2" | |
| shift 2 | |
| ;; | |
| -v|--verbose) | |
| VERBOSE=true | |
| shift | |
| ;; | |
| -h|--help) | |
| show_help | |
| exit 0 | |
| ;; | |
| *) | |
| log "ERROR" "Unknown option: $1" | |
| show_help | |
| exit 1 | |
| ;; | |
| esac | |
| done | |
| # Combine default and user-specified exclusions | |
| EXCLUDE_DIRS+=("${DEFAULT_EXCLUDE_DIRS[@]}") | |
| EXCLUDE_FILES+=("${DEFAULT_EXCLUDE_FILES[@]}") | |
| # Check if source directory exists | |
| if [[ ! -d "$SOURCE_DIR" ]]; then | |
| log "ERROR" "Source directory '$SOURCE_DIR' does not exist" | |
| exit 1 | |
| fi | |
| # Build find command exclusion arguments | |
| FIND_ARGS=() | |
| # Add directory exclusions | |
| for dir in "${EXCLUDE_DIRS[@]}"; do | |
| FIND_ARGS+=("-path" "$SOURCE_DIR/$dir" "-prune" "-o") | |
| done | |
| # Add file type condition | |
| FIND_ARGS+=("-type" "f") | |
| # Add file exclusions | |
| for file in "${EXCLUDE_FILES[@]}"; do | |
| FIND_ARGS+=("-not" "-name" "$file") | |
| done | |
| # Handle hidden files exclusion | |
| if [[ "$INCLUDE_HIDDEN" == false ]]; then | |
| FIND_ARGS+=("-not" "-path" "*/\.*") | |
| fi | |
| # Prepare output file | |
| log "INFO" "Starting context generation from directory: $SOURCE_DIR" | |
| log "INFO" "Output will be written to: $OUTPUT_FILE" | |
| # Initialize the context string | |
| CONTEXT="" | |
| FILE_COUNT=0 | |
| SKIPPED_COUNT=0 | |
| TOTAL_SIZE=0 | |
| # Create a temporary file for the context | |
| TEMP_FILE=$(mktemp) | |
| trap 'rm -f "$TEMP_FILE"' EXIT | |
| while IFS= read -r -d '' file; do | |
| # Get file size | |
| FILE_SIZE=$(stat -c%s "$file" 2>/dev/null || stat -f%z "$file" 2>/dev/null) | |
| # Skip files exceeding max size | |
| if (( FILE_SIZE > MAX_FILE_SIZE )); then | |
| log "WARN" "Skipping file (too large): $file ($FILE_SIZE bytes)" | |
| ((SKIPPED_COUNT++)) | |
| continue | |
| fi | |
| # Get relative path | |
| REL_PATH="${file#$SOURCE_DIR/}" | |
| if [[ "$REL_PATH" == "$file" ]]; then | |
| REL_PATH="${file#./}" | |
| fi | |
| # Try to read file content | |
| if FILE_CONTENT=$(cat "$file" 2>/dev/null); then | |
| # Append file metadata and content to context | |
| { | |
| echo "==== FILE: $REL_PATH ====" | |
| echo "$FILE_CONTENT" | |
| echo "" | |
| } >> "$TEMP_FILE" | |
| ((FILE_COUNT++)) | |
| ((TOTAL_SIZE+=FILE_SIZE)) | |
| log "INFO" "Added file: $REL_PATH ($FILE_SIZE bytes)" | |
| else | |
| log "ERROR" "Failed to read file: $file" | |
| ((SKIPPED_COUNT++)) | |
| fi | |
| done < <(find "$SOURCE_DIR" "${FIND_ARGS[@]}" -print0) | |
| # Move temporary file to output file | |
| if mv "$TEMP_FILE" "$OUTPUT_FILE" 2>/dev/null; then | |
| log "INFO" "Context generation complete" | |
| log "INFO" "Files processed: $FILE_COUNT" | |
| log "INFO" "Files skipped: $SKIPPED_COUNT" | |
| log "INFO" "Total size: $TOTAL_SIZE bytes" | |
| else | |
| log "ERROR" "Failed to write output file: $OUTPUT_FILE" | |
| exit 1 | |
| fi | |
| exit 0 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment