Skip to content

Instantly share code, notes, and snippets.

@basperheim
Last active August 5, 2025 21:05
Show Gist options
  • Select an option

  • Save basperheim/894ce1b8fd2c147c8416ba45e8f83b3b to your computer and use it in GitHub Desktop.

Select an option

Save basperheim/894ce1b8fd2c147c8416ba45e8f83b3b to your computer and use it in GitHub Desktop.
Fast, Flexible Directory Tree & File Preview Tool for Sharing Context with LLMs

detailed_tree.sh

A robust, cross-platform shell function for quickly sharing a directory’s structure and previewing file contents.
Ideal for collaborating with others, sharing context with ChatGPT or other LLMs, or just auditing a codebase without manually sifting through files.


Features

  • Smart Exclusions
    By default, skips common binary, dependency, build, and hidden files and directories (e.g. node_modules, .git, *.pyc), keeping output clean and focused.
  • Custom Excludes
    Easily add your own exclusions (files or directories) via --exclude '*.md,*.env,sensitive_dir'—pattern syntax matches shell globs.
  • File Previews with Clipping
    For each text or code file, prints up to the first 35 and last 5 lines (customizable). If you want the whole file, just add --show-all.
  • AI/LLM-Optimized Output
    Makes it trivial to copy-paste directory trees and relevant code into ChatGPT or other AI assistants, without manual curation or exposing secrets/build artifacts.
  • Debug Mode
    Use --debug to print exactly what would run—great for transparency or troubleshooting.
  • Cross-Platform
    Works out-of-the-box on both macOS (zsh or bash) and Linux (bash, zsh, or most POSIX shells).

Why Use This?

AI assistants are powerful, but don’t have access to your filesystem.
This tool lets you rapidly provide them with just enough context—not too much noise, not too little detail—for code review, bug diagnosis, or architecture advice.

Developers, security reviewers, educators, and anyone working with code in AI or human collaboration scenarios will find it invaluable.


Requirements

  • Shell:
    Bash or Zsh (default shells on most Linux distros and macOS since Catalina).
  • Utilities:
    • tree
      • Install on macOS: brew install tree
      • Install on Ubuntu/Debian: sudo apt install tree
    • file (for file type detection; pre-installed on most systems)

Installation

Copy the function from detailed_tree.sh into your .bashrc, .zshrc, or any sourced shell file.
Or simply source the script directly:

source /path/to/detailed_tree.sh

Then restart your terminal, or source ~/.zshrc.


Usage

# Print tree with defaults (skips binary/dependency dirs)
detailed_tree

# Print tree for a specific directory
detailed_tree /path/to/dir

# Print tree, and preview ALL lines of each file
detailed_tree --show-all

# Add your own exclusions (comma-separated globs)
detailed_tree --exclude '*.md,*.json,private_dir'

# See exactly what will run (no file or tree output)
detailed_tree --debug --exclude '*.log'

# Combine options, order doesn't matter
detailed_tree /some/dir --exclude '*.txt' --show-all

Example Output

📂 Directory Tree:
.
├── app.js
├── package.json
└── src
    └── index.js

📄 File Contents:

────────────────────────────────────────────
./app.js:
const express = require('express');
...

────────────────────────────────────────────
./src/index.js:
import something from './lib';
...
... (clipped, 50 lines total — showing first 35 and last 5)

💡 Use --show-all to view the full file: detailed_tree [directory] [--show-all]

How It Works

  • Uses tree for pretty directory display and full-file listing (with exclusions).
  • Previews only text/code files (based on MIME type), not binaries.
  • Excludes all common clutter by default; lets you add more as needed.
  • Handles exclusions the way tree expects (multiple -I arguments, not comma-separated).
  • Handles shell compatibility—works in both zsh (default macOS shell) and bash (default on most Linux systems).

Customization

Change these in the function if you want:

  • clip_head: (default: 35) Number of lines from the start of each file to show.
  • clip_tail: (default: 5) Number of lines from the end of each file to show.
  • clip_total: (default: 40) If a file is ≤ this many lines, show the whole file.

License

MIT


Credits

Written and published by Benji Asperheim—adapted for shell, cross-platform, and AI/LLM workflows.

detailed_tree() {
set -euo pipefail
local root="."
local show_all="false"
local user_exclude=""
local debug="false"
local clip_head=35
local clip_tail=5
local clip_total=40
# Built-in exclusions (one per -I flag, space separated!)
local builtins='-I yarn.lock -I node_modules -I vendor -I dist -I build -I bin -I target -I .git -I __pycache__ -I *.pyc -I *.o -I *.so -I *.class -I *.wasm -I *.exe -I *.out -I *.a -I *.bin'
# Parse args
while [[ $# -gt 0 ]]; do
case "$1" in
--show-all) show_all="true"; shift ;;
--exclude)
if [[ -z "$2" ]]; then
echo "❌ --exclude requires a comma-separated list argument."
return 1
fi
user_exclude="$2"; shift 2
;;
--debug) debug="true"; shift ;;
-*)
echo "❌ Unknown flag: $1"; return 1
;;
*) root="$1"; shift ;;
esac
done
# Build user excludes as -I pattern ...
local user_excludes=""
if [[ -n "$user_exclude" ]]; then
# Replace all commas (and optional following space) with a space, then split by whitespace
for excl in $(echo "$user_exclude" | sed 's/,\s*/ /g'); do
user_excludes="$user_excludes -I \"$excl\""
done
fi
# Build the full exclude string
local tree_excludes="$builtins$user_excludes"
# Debug output
if [[ "$debug" == "true" ]]; then
echo "===== [detailed_tree DEBUG] ====="
echo "root: $root"
echo "show_all: $show_all"
echo "user_exclude: $user_exclude"
echo "builtins: $builtins"
echo "user_excludes: $user_excludes"
echo "tree_excludes: $tree_excludes"
echo "clip_head: $clip_head"
echo "clip_tail: $clip_tail"
echo "clip_total: $clip_total"
echo
echo "Commands that would run:"
echo "tree --dirsfirst $tree_excludes \"$root\""
echo "tree -if --noreport $tree_excludes \"$root\""
echo "================================="
return 0
fi
# In zsh: prevent un-matched globs from erroring out
if [ -n "$ZSH_VERSION" ]; then
setopt no_nomatch
fi
echo "📂 Directory Tree:"
eval tree --dirsfirst $tree_excludes "\"$root\""
echo ""
echo "📄 File Contents:"
eval tree -if --noreport $tree_excludes "\"$root\"" | while IFS= read -r file; do
# Only act on non-empty, regular files
if [ -n "$file" ] && [ -f "$file" ]; then
# Always reset this variable
mimetype=""
mimetype=$(file --mime-type -b "$file")
if [[ "$mimetype" != text/* && "$mimetype" != application/json ]]; then
continue
fi
echo "\n────────────────────────────────────────────"
echo -e "\x1b[33m$file:\x1b[37m"
if [ "$show_all" = "true" ]; then
cat "$file"
else
total_lines=$(wc -l < "$file" | xargs)
if [ "$total_lines" -le "$clip_total" ]; then
cat "$file"
else
head -n "$clip_head" "$file"
echo -e "\n... (clipped, $total_lines lines total — showing first $clip_head and last $clip_tail)\n"
tail -n "$clip_tail" "$file"
echo ""
echo -e "💡 Use --show-all to view the full file: detailed_tree [directory] [--show-all]"
fi
fi
echo ""
fi
done
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment