Skip to content

Instantly share code, notes, and snippets.

@bkataru
Last active March 18, 2025 14:57
Show Gist options
  • Select an option

  • Save bkataru/a1c353aa053ae3b61a541f38e229b05e to your computer and use it in GitHub Desktop.

Select an option

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
<#
.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
}
}
#!/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