Potato is a modular bash extension system that lets you add custom functionality to your shell through "toppings" - small, focused scripts that hook into bash at different execution points.
# Add potato to your PATH and source it in ~/.bashrc
export PATH="$PATH:/path/to/potato-dir"
source potato__ startupToppings are bash scripts that execute at specific lifecycle events in your shell. Think of them as plugins or hooks that extend bash functionality without cluttering your main configuration.
Potato supports three types of toppings based on filename suffix:
| Type | Suffix | When It Runs | Use Cases |
|---|---|---|---|
| Startup | .bashrc |
Once when shell starts | Custom prompts, environment setup, aliases |
| Pre-command | .precommand |
Before each command executes | Validation, warnings, command modification |
| Post-command | .postcommand |
After each command completes | Logging, notifications, cleanup, state updates |
┌─────────────────────────────────────────────────┐
│ Shell Startup │
│ └─> All *.bashrc toppings load │
└─────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────┐
│ User types command: cd /tmp │
└─────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────┐
│ All *.precommand toppings execute │
│ (can inspect/modify/cancel command) │
└─────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────┐
│ Command executes: cd /tmp │
└─────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────┐
│ All *.postcommand toppings execute │
│ (can react to command results) │
└─────────────────────────────────────────────────┘
potato__ listOutput example:
Enabled toppings:
git-prompt.bashrc
command-logger.postcommand
rm-warning.precommand
potato__ enable /path/to/topping-name.bashrc
potato__ enable ./rm-warning.precommand
potato__ enable ../command-log.postcommandThe filename must follow the naming convention: NAME.TYPE where TYPE is one of:
bashrc- for startup hooksprecommand- for pre-execution hookspostcommand- for post-execution hooks
potato__ disable topping-name.bashrcAfter enabling/disabling toppings:
source potato__ startupOr restart your shell.
File: git-prompt.bashrc
#!/usr/bin/env bash
# git-prompt.bashrc - Shows current git branch in PS1
__git_branch() {
git branch 2>/dev/null | grep '^*' | sed 's/* //'
}
# Add git branch to prompt
PS1='\u@\h:\w$(__git_branch | sed "s/./ (&)/")\$ 'File: rm-warning.precommand
#!/usr/bin/env bash
# rm-warning.precommand - Warns before dangerous rm commands
# Check if command starts with 'rm'
if [[ "$BASH_COMMAND" =~ ^rm.*\*.*$ ]]; then
echo "⚠️ WARNING: You're about to rm with wildcards!"
echo "Command: $BASH_COMMAND"
read -p "Continue? (y/N): " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
# Cancel the command
return 1
fi
fiFile: command-logger.postcommand
#!/usr/bin/env bash
# command-logger.postcommand - Logs commands and execution time
LOG_FILE="$HOME/.command_history.log"
# Log the command with timestamp
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $PWD: $BASH_COMMAND" >> "$LOG_FILE"
# Optional: Track command success/failure
if [[ $? -eq 0 ]]; then
echo " ✓ Success" >> "$LOG_FILE"
else
echo " ✗ Failed (exit code: $?)" >> "$LOG_FILE"
fiFile: tmux-preview.postcommand
#!/usr/bin/env bash
# tmux-preview.postcommand - Auto-updates tmux preview pane
[[ -z "$TMUX" ]] && return 0
# Detect directory changes
if [[ "$PWD" != "${POTATO_PREV_PWD:-}" ]]; then
export POTATO_PREV_PWD="$PWD"
# Create preview pane if needed
if [[ $(tmux list-panes | wc -l) -eq 1 ]]; then
tmux split-window -h -d "cat"
fi
PREVIEW_PANE=$(tmux list-panes -F "#{pane_id}" | tail -1)
# Show README or directory listing
if [[ -f "README.md" ]]; then
tmux respawn-pane -t "$PREVIEW_PANE" -k "cat README.md; cat"
else
tmux respawn-pane -t "$PREVIEW_PANE" -k "ls -lah; cat"
fi
fi$PWD- Current directory$HOME- User home directory$USER- Username- All standard bash variables
$BASH_COMMAND- The command about to execute (precommand) or just executed (postcommand)$?- Exit code of last command (postcommand only)
You can create persistent variables by exporting them:
# In a .bashrc topping
export POTATO_MY_VARIABLE="value"
# Access in .precommand or .postcommand
echo $POTATO_MY_VARIABLEEach topping should do one thing well. Don't create a massive topping that does everything.
❌ Bad: everything.postcommand (500 lines, does logging, git updates, notifications, etc.)
✅ Good: Separate toppings for each feature
Always check if commands/tools exist before using them:
if command -v git &>/dev/null; then
# Use git
fi- Return 0 for success
- Return 1 to cancel/fail (especially in precommand)
- Don't exit (it will close your shell!)
# In precommand
if [[ some_dangerous_condition ]]; then
echo "Cancelling command"
return 1 # ✓ Correct
# exit 1 # ✗ Wrong - closes shell!
fiPostcommand toppings run after every command. Keep them fast!
❌ Bad: Network calls, slow computations
✅ Good: Quick checks, simple logging
When developing toppings, use a log file:
LOG="/tmp/my-topping.log"
echo "Debug: something happened" >> "$LOG"Use clear, descriptive names:
git-prompt.bashrc✓warn-rm.precommand✓stuff.postcommand✗
Let's create a development environment setup:
1. dev-env.bashrc - Set up environment
#!/usr/bin/env bash
export DEV_ROOT="$HOME/projects"
export PATH="$PATH:$DEV_ROOT/bin"
alias proj='cd $DEV_ROOT'2. git-check.precommand - Warn about committing to main
#!/usr/bin/env bash
if [[ "$BASH_COMMAND" =~ ^git\ commit ]]; then
branch=$(git branch 2>/dev/null | grep '^*' | cut -d' ' -f2)
if [[ "$branch" == "main" || "$branch" == "master" ]]; then
echo "⚠️ You're committing to $branch!"
read -p "Continue? (y/N): " -n 1 -r
echo
[[ ! $REPLY =~ ^[Yy]$ ]] && return 1
fi
fi3. project-tracker.postcommand - Track time in projects
#!/usr/bin/env bash
TRACK_FILE="$HOME/.project_time.log"
if [[ "$PWD" != "${PREV_PROJECT_DIR:-}" ]]; then
export PREV_PROJECT_DIR="$PWD"
if [[ "$PWD" =~ ^$HOME/projects/ ]]; then
project=$(basename "$PWD")
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Entered: $project" >> "$TRACK_FILE"
fi
fiEnable them all:
potato__ enable dev-env.bashrc
potato__ enable git-check.precommand
potato__ enable project-tracker.postcommand
source potato__ startup-
Check if it's enabled:
potato__ list
-
Check the filename: Must end with
.bashrc,.precommand, or.postcommand -
Reload potato:
source potato__ startup -
Add debug logging:
echo "Topping executed at $(date)" >> /tmp/debug.log
Postcommands run after every command. Profile them:
time source /path/to/topping.postcommandConsider:
- Moving heavy work to background:
some_slow_task & - Caching results
- Only running on specific commands
Export them in a .bashrc topping:
# In startup.bashrc
export MY_VAR="value"
# Now accessible in all toppings
echo $MY_VARPotato requires:
- bash (4.0+)
- undies framework (must be in
$PATH)
Optional but recommended:
bat- Better file viewingtree- Directory visualizationjq- JSON processing
# Clone the repository
git clone https://github.com/pendashteh/potato.git
cd potato
# Add to your ~/.bashrc
echo 'export PATH="$PATH:'$(pwd)'"' >> ~/.bashrc
echo 'source potato__ startup' >> ~/.bashrc
# Reload
source ~/.bashrcTo contribute a topping:
- Write your topping following the naming convention
- Test it thoroughly
- Add it to the
toppings/directory - Document what it does and any dependencies
- Submit a pull request
Toppings are loaded alphabetically within each type:
- All
.bashrctoppings (alphabetically) .precommandtoppings execute before each command (alphabetically).postcommandtoppings execute after each command (alphabetically)
To control order, use numeric prefixes:
01-first.bashrc02-second.bashrc99-last.bashrc
Enable toppings conditionally:
# In your .bashrc
if [[ "$HOSTNAME" == "work-laptop" ]]; then
potato__ enable work-settings.bashrc
fiToppings can communicate via exported variables:
# In early.postcommand
export POTATO_COMMAND_COUNT=$((POTATO_COMMAND_COUNT + 1))
# In later.postcommand
if [[ $POTATO_COMMAND_COUNT -gt 100 ]]; then
echo "You've run 100 commands!"
fiGPL-3.0 License
Created by pendashteh
Note: This is a complete rewrite of the original potato. The old version is available in the old branch.