Skip to content

Instantly share code, notes, and snippets.

@androidStern
Created April 2, 2025 03:13
Show Gist options
  • Select an option

  • Save androidStern/1f7e9ffa62a4b1db357fe2b6fb56b8a9 to your computer and use it in GitHub Desktop.

Select an option

Save androidStern/1f7e9ffa62a4b1db357fe2b6fb56b8a9 to your computer and use it in GitHub Desktop.
CTX - A tool to manage named lists of files in your repo as 'contexts' for quick pasting into LLM chats. Use it to create a list of auth related files, for example and easily copy their contents with CTX later. Inspired by YacineMTB's context script.
#!/usr/bin/env bash
#
# ctx - Manage file/directory context lists for LLM usage, then pipe combined content to clipboard.
#
# Dependencies:
# - fzf, fd, bat, pbcopy (on macOS) or an equivalent on other systems.
# - highlight (optional) for nicer previews, else we fallback to bat with --style=plain.
#
# Usage:
# ctx -n <context_name> Create or overwrite a context by selecting multiple files/folders in fzf.
# ctx -p <context_name> Read .llm_context/<context_name>, dump contents, copy to clipboard.
# ctx -p Fuzzy-select from .llm_context/* with a preview, then copy result to clipboard.
# ctx -e Edit a context. Opens in default editor.
# ctx -d Fuzzy-select a context to delete (with confirmation).
# ------------------------- Configuration -------------------------
# Attempt to locate the project's root via Git; if not in a Git repo, fallback to current dir.
if command -v git &>/dev/null && git rev-parse --show-toplevel &>/dev/null; then
PROJECT_ROOT="$(git rev-parse --show-toplevel)"
else
PROJECT_ROOT="$(pwd)"
fi
CONTEXT_DIR="$PROJECT_ROOT/.llm_context"
# ------------------------- Helpers -----------------------
die() {
echo "Error: $*" >&2
exit 1
}
check_dependencies() {
for cmd in fzf fd bat pbcopy; do
command -v "$cmd" &>/dev/null || die "Missing dependency: $cmd"
done
}
add_to_gitignore() {
local ignore_file="$PROJECT_ROOT/.gitignore"
local target_entry=".llm_context"
# If there's no .gitignore, do nothing.
if [[ ! -f "$ignore_file" ]]; then
# echo "No .gitignore found in $PROJECT_ROOT; skipping ignore update."
return 0
fi
# Check if .llm_context is already ignored
if grep -qxF "$target_entry" "$ignore_file"; then
# echo ".llm_context is already in .gitignore; nothing to do."
return 0
fi
# Otherwise, append it
echo "$target_entry" >>"$ignore_file"
echo "Added '$target_entry' to .gitignore."
}
init_context_dir() {
[ -d "$CONTEXT_DIR" ] || mkdir -p "$CONTEXT_DIR"
add_to_gitignore
}
show_banner() {
local gist_url="https://gist.githubusercontent.com/androidStern/d9a66190784921b5d86552ae63441286/raw/67b543455ab09a5cf250cdc5b9802f9e707e9816/ctx_logo.txt"
# Try to fetch the banner; if curl fails or is empty, we do nothing.
local banner
banner=$(curl --fail -s "$gist_url" 2>/dev/null) || return 0
[[ -z "$banner" ]] && return 0
# "Auto-scroll" line-by-line
while IFS= read -r line; do
echo "$line"
sleep 0.03 # adjust speed as desired
done <<<"$banner"
}
show_help() {
show_banner
cat <<EOF
NAME
ctx - Manage lists of files/directores as named "contexts" for LLM usage. Easily copy all file contents in a context to your clipboard.
SYNOPSIS
ctx [-n | new] <context_name>
ctx [-p | copy] [<context_name>]
ctx [-e | edit] [<context_name>]
ctx [-d | del] [<context_name>]
ctx [-h | --help | help]
DESCRIPTION
ctx lets you quickly group files (or folders) into named "contexts" using a fuzzy finder. Later, it can instantly dump
all the grouped files' contents directly to your clipboard — ideal for pasting into LLM prompts.
Easily edit or delete contexts anytime.
COMMANDS
-n, new <context_name>
Create or overwrite a context by picking multiple files/folders in fzf.
If <context_name> already exists, it will be overwritten.
-p, copy [<context_name>]
Copy the specified context to your clipboard. If <context_name> is omitted,
fuzzy-select an existing context.
-e, edit [<context_name>]
Open the specified context file in your \$EDITOR. If omitted, fuzzy-select first.
-d, del [<context_name>]
Delete the specified context. If omitted, fuzzy-select from existing contexts.
Confirmation is required before deletion.
-h, --help, help
Show this help message.
EXAMPLES
ctx -n myContext # or ctx new myContext
ctx -p myContext # or ctx copy myContext
ctx -e # or ctx edit (then pick a context)
ctx -d oldStuff # or ctx del oldStuff
EOF
}
# ------------------------- "mfat" multi-file-formated-cat ------------------
mfat() {
echo "- The following content is a combination of docs, code, and PRDs related to my application."
echo "- Each section is delineated by 'File: /path/to/file'."
echo
while IFS="" read -r selection; do
# Skip blank lines
[[ -z "$selection" ]] && continue
echo "-------------------------------------------------------------------"
echo "File: $selection"
if [[ -d "$selection" ]]; then
fd --type file . "$selection" |
xargs -I{} bat --decorations=always --style=header "{}"
elif [[ -f "$selection" ]]; then
bat --decorations=always --style=header "$selection"
else
echo "Skipping: '$selection' (not a file or directory)"
fi
echo
done
}
# ------------------------- Core Functions -------------------------
create_context() {
local ctx_name="$1"
local ctx_file="$CONTEXT_DIR/$ctx_name"
echo "Select files/directories with fzf (press TAB to mark multiple, Enter to confirm):"
echo "-----------------------------------------------------------------------"
fd --type file --type directory --exclude "node_modules" . |
sort |
fzf --multi \
--preview 'if [ -d {} ]; then tree -C -L 1 {} | head -50; else bat --color=always --style=header {} | head -50; fi' \
--bind "tab:toggle,shift-tab:toggle+up,ctrl-space:toggle-preview" \
--header 'Tab: select | Ctrl-Space: toggle preview' \
>"$ctx_file"
echo "Created/updated context list -> $ctx_file"
}
copy_context() {
local ctx_name="$1"
local ctx_file="$CONTEXT_DIR/$ctx_name"
if [ ! -f "$ctx_file" ]; then
die "Context file not found: $ctx_file"
fi
mfat <"$ctx_file" | pbcopy
echo "Context '$ctx_name' content copied to clipboard."
}
fzf_pick_context_and_copy() {
cd "$CONTEXT_DIR" || die "Failed to cd into $CONTEXT_DIR"
# List context files ignoring hidden
local contexts
contexts=$(find . -maxdepth 1 -type f -not -path "*/\.*" | sort)
if [ -z "$contexts" ]; then
echo "No contexts found in '$CONTEXT_DIR'."
cd - &>/dev/null
return 1
fi
local selected
selected=$(echo "$contexts" | sed 's|^\./||' | fzf \
--height 40% \
--layout=reverse \
--border \
--prompt="Select context: " \
--preview="(highlight -O ansi -s monokai --force --syntax-by-name=md $CONTEXT_DIR/{} 2>/dev/null || \
bat --color=always --style=plain --language=markdown $CONTEXT_DIR/{})" \
--preview-window="right:50%")
if [ -n "$selected" ]; then
cd - &>/dev/null
copy_context "$selected"
else
cd - &>/dev/null
echo "No context selected."
fi
}
delete_context() {
cd "$CONTEXT_DIR" || die "Failed to cd into $CONTEXT_DIR"
local contexts
contexts=$(find . -maxdepth 1 -type f -not -path "*/\.*" | sort)
if [ -z "$contexts" ]; then
echo "No contexts found in '$CONTEXT_DIR'."
cd - &>/dev/null
return 1
fi
local selected
selected=$(echo "$contexts" | sed 's|^\./||' | fzf \
--height 40% \
--layout=reverse \
--border \
--prompt="Select context to delete: " \
--preview="(highlight -O ansi -s monokai --force --syntax-by-name=md $CONTEXT_DIR/{} 2>/dev/null || \
bat --color=always --style=plain --language=markdown $CONTEXT_DIR/{})" \
--preview-window="right:50%")
cd - &>/dev/null
if [ -n "$selected" ]; then
echo "Deleting '$selected'. Press 'y' to confirm, or CTRL-C to abort."
rm -i "$CONTEXT_DIR/$selected"
else
echo "No context selected."
fi
}
edit_context() {
local ctx_name="$1"
# Use $EDITOR if defined, otherwise fallback to nano
local editor="${EDITOR:-nano}"
# If user specified a context name, open that file directly
if [[ -n "$ctx_name" ]]; then
local ctx_file="$CONTEXT_DIR/$ctx_name"
if [[ ! -f "$ctx_file" ]]; then
echo "Context '$ctx_name' does not exist: $ctx_file"
return 1
fi
"$editor" "$ctx_file"
return 0
fi
# Otherwise, fuzzy-pick a context
cd "$CONTEXT_DIR" || {
echo "Failed to enter $CONTEXT_DIR"
return 1
}
local contexts
contexts=$(find . -maxdepth 1 -type f -not -path "*/\.*" | sort)
if [[ -z "$contexts" ]]; then
echo "No contexts found in '$CONTEXT_DIR'."
cd - &>/dev/null
return 1
fi
local selected
selected=$(echo "$contexts" | sed 's|^\./||' | fzf \
--height 40% \
--layout=reverse \
--border \
--prompt="Select context to edit: " \
--preview="(highlight -O ansi -s monokai --force --syntax-by-name=md $CONTEXT_DIR/{} 2>/dev/null || \
bat --color=always --style=plain --language=markdown $CONTEXT_DIR/{})" \
--preview-window="right:50%")
cd - &>/dev/null
if [[ -n "$selected" ]]; then
"$editor" "$CONTEXT_DIR/$selected"
else
echo "No context selected."
fi
}
# ------------------------- Main Entry Point -----------------------
main() {
check_dependencies
init_context_dir
# If no args, show help
if [ $# -lt 1 ]; then
show_help
exit 0
fi
local cmd="$1"
shift # Next argument may be <context_name>
case "$cmd" in
# --------------- Short Flags ---------------
-n)
# ctx -n <context_name>
if [[ -z "$1" ]]; then
die "Please specify a context name after '-n'."
fi
create_context "$1"
;;
-p)
# ctx -p [context_name?]
if [[ -z "$1" ]]; then
fzf_pick_context_and_copy
else
copy_context "$1"
fi
;;
-e)
# ctx -e [context_name?]
if [[ -z "$1" ]]; then
edit_context
else
edit_context "$1"
fi
;;
-d)
# ctx -d [context_name?]
if [[ -z "$1" ]]; then
delete_context
else
# Immediately delete if file exists
local ctx_file="$CONTEXT_DIR/$1"
if [[ ! -f "$ctx_file" ]]; then
echo "Context '$1' does not exist."
exit 1
fi
echo "Deleting '$1'. Press 'y' to confirm, or CTRL-C to abort."
rm -i "$ctx_file"
fi
;;
-h | --help)
show_help
;;
# --------------- Verbose Subcommands ---------------
help)
show_help
;;
new)
# ctx new <context_name>
if [[ -z "$1" ]]; then
die "Please specify a context name: ctx new <name>"
fi
create_context "$1"
;;
copy)
# ctx copy [context_name?]
if [[ -z "$1" ]]; then
fzf_pick_context_and_copy
else
copy_context "$1"
fi
;;
edit)
# ctx edit [context_name?]
if [[ -z "$1" ]]; then
edit_context
else
edit_context "$1"
fi
;;
del)
# ctx del [context_name?]
if [[ -z "$1" ]]; then
delete_context
else
local ctx_file="$CONTEXT_DIR/$1"
if [[ ! -f "$ctx_file" ]]; then
echo "Context '$1' does not exist."
exit 1
fi
echo "Deleting '$1'. Press 'y' to confirm, or CTRL-C to abort."
rm -i "$ctx_file"
fi
;;
*)
echo "Unrecognized command or flag: $cmd"
show_help
exit 1
;;
esac
}
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment