Version: 2.0
Date: January 2026
Status: Design Phase
- Executive Summary
- Core Architecture
- File Format Specification
- DSL Language Design
- Compilation System
- Hook System
- State Management
- Conflict Detection
- Registry & Distribution
- Tooling & Commands
- Use Cases & Examples
- Implementation Phases
- Inspiration & Prior Art
- Future Considerations
Potato is a bash extension framework that executes custom scripts ("toppings") at shell lifecycle points. This document outlines the evolution from simple bash hooks to a structured, declarative system while maintaining backward compatibility and simplicity.
- Simplicity First: Like Git hooks - bash scripts in directories
- Progressive Enhancement: Optional structure for those who need it
- Dual Format:
.topping(declarative DSL) compiles to.sh(bash scripts) - TypeScript-like Model:
.topping→.shsimilar to.ts→.js - No Magic: Always expose the compiled bash layer
- Filename convention:
name.{bashrc,precommand,postcommand}.sh - Optional DSL:
name.toppingcompiles toname.{type}.sh - Bash as runtime: Everything runs as bash underneath
- Git Hooks simplicity: Start simple, add structure as needed
- Systemd-inspired metadata: Optional structured configuration
User types command
↓
┌───────────────────────┐
│ precommand.sh hooks │ ← Can abort command
└───────────────────────┘
↓
┌───────────────────────┐
│ Command executes │
└───────────────────────┘
↓
┌───────────────────────┐
│ postcommand.sh hooks │ ← React to results
└───────────────────────┘
~/.potato/
├── config # Global configuration
├── toppings-available/
│ ├── git-prompt.bashrc.sh
│ ├── npm-auto.postcommand.sh
│ └── aws-safety.topping # Source DSL
├── toppings-enabled/ # Symlinks (systemd-style)
│ └── git-prompt.bashrc.sh -> ../toppings-available/git-prompt.bashrc.sh
├── compiled/ # Compiled from .topping
│ └── aws-safety.precommand.sh
└── cache/ # State, hashes, etc.
└── file-hashes/
# Config
~/.config/potato/config
~/.config/potato/toppings/
# Data/State
~/.local/share/potato/enabled/
~/.local/share/potato/cache/
# Cache
~/.cache/potato/file-hashes/Format: Standard bash scripts with optional metadata comments
#!/usr/bin/env bash
# POTATO_NAME: npm-auto-install
# POTATO_VERSION: 1.0.0
# POTATO_DESCRIPTION: Auto-install npm packages when package.json changes
# POTATO_AUTHOR: npmjs
# POTATO_CONFLICTS: yarn-auto-install, pnpm-auto-install
# POTATO_REQUIRES: npm
# POTATO_TRIGGERS: file_changed(package.json)
# POTATO_PRIORITY: 50
# POTATO_ASYNC: false
# Actual bash code
[[ -f package.json ]] || return 0
if potato_on_file_changed package.json; then
npm install
fiNaming Convention:
name.bashrc.sh- Runs once at shell startupname.precommand.sh- Runs before each commandname.postcommand.sh- Runs after each command
Format: INI-like declarative syntax (systemd-inspired)
[Topping]
Name=npm-auto-install
Version=1.0.0
Description=Auto-install npm packages when package.json changes
Author=npmjs
Conflicts=yarn-auto-install pnpm-auto-install
Requires=npm
Priority=50
[Trigger]
Type=postcommand
On=file_changed package.json
On=file_changed package-lock.json
Condition=file_exists package.json
[Action]
Run=npm install
RunIf=npm outdated --parseable | grep -q .
Silent=false
Background=falseCompiles to: npm-auto-install.postcommand.sh
Metadata about the topping.
[Topping]
Name=string # Unique identifier
Version=semver # Semantic version
Description=string # Human-readable description
Author=string # Author name or email
Homepage=url # Documentation URL
License=string # License identifier
Conflicts=list # Space-separated conflicting toppings
Requires=list # Required commands/tools
Before=list # Must run before these toppings
After=list # Must run after these toppings
Priority=integer # 0-100, higher runs firstWhen the topping should execute.
[Trigger]
Type=bashrc|precommand|postcommand
# File-based triggers
On=file_exists <path> # File exists in $PWD
On=file_missing <path> # File doesn't exist
On=file_changed <path> # File modified since last check
On=file_older <path> <days> # File older than N days
On=file_newer <path1> <path2> # File1 is newer than file2
# Directory triggers
On=dir_entered <pattern> # Entered directory matching pattern
On=dir_exited <pattern> # Exited directory matching pattern
On=dir_contains <pattern> # Current dir contains files matching pattern
# Command triggers (precommand/postcommand only)
On=command <regex> # Command matches regex
On=command_failed <regex> # Command failed (postcommand only)
On=command_succeeded <regex> # Command succeeded (postcommand only)
# Environment triggers
On=env_set <VAR> # Environment variable is set
On=env_equals <VAR> <value> # Env var equals value
On=env_matches <VAR> <regex> # Env var matches regex
# Time-based triggers
On=time_after HH:MM # After time of day
On=time_before HH:MM # Before time of day
On=weekday Mon,Tue,Wed # Specific days of week
# Composite triggers
All # All On= conditions must match (AND)
Any # Any On= condition matches (OR)
Not=<trigger> # Negate a trigger
# Additional conditions
Condition=<bash expression> # Raw bash condition (escape hatch)What to do when triggered.
[Action]
# Execution
Run=<command> # Execute command
RunSilent=<command> # Execute, suppress output
RunBackground=<command> # Execute in background
RunIf=<condition> # Only run if condition true
# User interaction
Warn=<message> # Print warning to stderr
Confirm=<message> # Ask for confirmation
ConfirmMatch=<pattern> # Require specific input
Abort # Cancel command (precommand only)
AbortIf=<condition> # Conditional abort
# Logging
Log=<message> # Log to potato log
LogLevel=info|warn|error # Log level
# State management
Set=<VAR> <value> # Set environment variable
Unset=<VAR> # Unset environment variable
Touch=<path> # Create marker file
Remove=<path> # Remove marker file
# Control flow
Skip # Skip this topping execution
SkipIf=<condition> # Conditional skip[Topping]
Name=readme-viewer
Description=Show README when entering directory
[Trigger]
Type=postcommand
On=dir_entered *
On=file_exists README.md
[Action]
Run=cat README.md[Topping]
Name=aws-production-safety
Description=Prevent accidental production deletions
[Trigger]
Type=precommand
On=command ^aws.*delete
On=env_equals AWS_PROFILE production
All
[Action]
Warn=⚠️ You are about to DELETE in PRODUCTION!
Confirm=Type 'DELETE' to continue
ConfirmMatch=DELETE
AbortIf=!confirmed[Topping]
Name=docker-dev-helper
Description=Auto-manage Docker development environment
[Trigger]
Type=postcommand
On=dir_entered */project-*
On=file_exists docker-compose.yml
On=command docker-compose up
All
[Action]
Log=Starting development environment
Run=docker-compose up -d
RunIf=! docker-compose ps | grep -q Up
Warn=Development environment is now running
Set=DEV_RUNNING true┌─────────────────┐
│ name.topping │
└────────┬────────┘
│
↓
┌─────────────────┐
│ Parser │ Parse INI format
└────────┬────────┘
│
↓
┌─────────────────┐
│ Validator │ Check syntax, dependencies
└────────┬────────┘
│
↓
┌─────────────────┐
│ Code Generator │ Generate bash code
└────────┬────────┘
│
↓
┌─────────────────┐
│ name.{type}.sh │ Compiled bash script
└─────────────────┘
potato__ compile name.toppingSteps:
- Parse: Read .topping file, parse INI sections
- Validate: Check syntax, verify dependencies exist
- Generate: Create bash script with equivalent logic
- Add metadata: Include original .topping as comments
- Make executable: chmod +x
- Link: Create symlink in enabled/ if needed
#!/usr/bin/env bash
# AUTO-GENERATED from npm-auto-install.topping
# DO NOT EDIT - Changes will be overwritten
# Regenerate with: potato__ compile npm-auto-install.topping
#
# POTATO_NAME: npm-auto-install
# POTATO_VERSION: 1.0.0
# POTATO_SOURCE: npm-auto-install.topping
# POTATO_COMPILED_AT: 2026-01-01T12:00:00Z
# Generated condition checks
[[ -f "package.json" ]] || return 0
# Check file changed
_HASH_FILE="/tmp/potato-hash-$(echo "$PWD/package.json" | md5sum | cut -d' ' -f1)"
_CURRENT_HASH=$(md5sum "package.json" 2>/dev/null | cut -d' ' -f1)
_PREV_HASH=$(cat "$_HASH_FILE" 2>/dev/null)
if [[ "$_CURRENT_HASH" != "$_PREV_HASH" ]]; then
echo "$_CURRENT_HASH" > "$_HASH_FILE"
# Generated action
npm install
fi# Auto-recompile on changes
potato__ watch name.topping
# Recompile all
potato__ compile --all
# Check if recompilation needed
potato__ check- bashrc - Shell startup
- precommand - Before each command (via DEBUG trap)
- postcommand - After each command (via DEBUG trap + PROMPT_COMMAND)
# Directory change hook (special case of postcommand)
on_directory_change.sh
# Triggered only when $PWD changes
# Exit hook
on_exit.sh
# Runs when shell exits (via trap EXIT)
# Error hook
on_error.sh
# Runs when command fails (check $? in postcommand)# Command not found
on_command_not_found.sh
# Override command_not_found_handle function
# Background job completion
on_job_done.sh
# Trigger when background job finishes# Specific command hooks
on_git_commit.sh
on_npm_install.sh
# Would require parsing $BASH_COMMAND - fragile
# Periodic hooks
on_every_5min.sh
# Would require background timer processPhase 1: Keep current three hooks (bashrc, precommand, postcommand)
Phase 2: Add directory_change as special postcommand optimization
# Built into potato framework
if [[ "$PWD" != "${POTATO_PREV_PWD:-}" ]]; then
export POTATO_PREV_PWD="$PWD"
# Run *.directory_change.sh hooks
fiPhase 3: Add exit and error hooks (via bash traps)
Ad-hoc exported variables:
export POTATO_PREV_PWD="$PWD"
export POTATO_LAST_COMMAND="$BASH_COMMAND"# Set state (persists across commands)
potato_state_set KEY VALUE
# Get state
potato_state_get KEY
# Delete state
potato_state_del KEY
# Check if state exists
potato_state_has KEY
# Implementation uses namespaced env vars
# POTATO_STATE_KEY=VALUE# Check if file changed
potato_on_file_changed FILE
# Implementation:
# - Stores MD5 hash in ~/.cache/potato/file-hashes/
# - Compares current vs previous
# - Returns 0 if changed, 1 if same# Store data across shell sessions
potato_persist_set KEY VALUE
# Writes to ~/.local/share/potato/state/KEY
potato_persist_get KEY
# Reads from ~/.local/share/potato/state/KEY# POTATO_CONFLICTS: yarn-auto-install, pnpm-auto-installPotato parses this and warns if multiple conflicting toppings enabled.
potato__ conflictsAnalyzes all enabled toppings:
- Same trigger conditions
- Same files being watched
- Same commands being intercepted
Multiple toppings with same priority on same trigger → Warning
# In .topping file
[Topping]
Conflicts=other-topping
Priority=60 # Higher priority winsOr manual:
potato__ disable conflicting-topping# Show what will run
potato__ inspect
# Show conflicts
potato__ conflicts
# Show trigger analysis
potato__ triggers
# Dry run
POTATO_DRY_RUN=1 command| Feature | Homebrew | npm | apt | GitHub Actions | Systemd |
|---|---|---|---|---|---|
| Central registry | ✓ | ✓ | ✓ | ✓ | ✗ |
| Versioning | ✓ | ✓ | ✓ | ✓ | ✗ |
| Dependencies | ✓ | ✓ | ✓ | ✗ | ✓ |
| Conflicts | ✓ | ✗ | ✓ | ✗ | ✓ |
| Search | ✓ | ✓ | ✓ | ✓ | ✗ |
| Community | taps | registry | PPAs | marketplace | ✗ |
Primary: GitHub as registry (like Homebrew taps)
# Add registry
potato__ tap add user/repo
# Install from registry
potato__ install aws-safety
# Fetches from default registry
# Install from specific tap
potato__ install user/repo/custom-topping
# Install from URL
potato__ install https://github.com/user/repo/toppings/name.topping
# Version pinning
potato__ install aws-safety@1.2.0
potato__ install aws-safety@latestgithub.com/potato-toppings/official/
├── README.md
├── index.json # Topping index
└── toppings/
├── aws-safety/
│ ├── 1.0.0.topping
│ ├── 1.1.0.topping
│ ├── latest.topping -> 1.1.0.topping
│ └── README.md
└── npm-auto-install/
└── ...
{
"toppings": [
{
"name": "aws-safety",
"description": "Prevent accidental AWS production deletions",
"author": "potato-team",
"homepage": "https://github.com/potato-toppings/official",
"latest_version": "1.1.0",
"versions": ["1.0.0", "1.1.0"],
"requires": ["aws"],
"conflicts": [],
"downloads": 1234,
"stars": 56
}
]
}potato__ install aws-safety- Check enabled taps
- Search for
aws-safetyin tap indexes - Download
aws-safety/latest.topping - Compile to
aws-safety.{type}.sh - Place in
toppings-available/ - Ask user to enable:
potato__ enable aws-safety
# Official tap (default)
potato__ tap add potato/official
# Community taps
potato__ tap add aws-users/aws-toppings
potato__ tap add docker-fans/docker-helpers
# List taps
potato__ tap list
# Update tap indexes
potato__ tap update# Topping management
potato__ enable <topping> # Enable a topping
potato__ disable <topping> # Disable a topping
potato__ list # List enabled toppings
potato__ list --all # List all available toppings
# Compilation
potato__ compile <topping> # Compile .topping to .sh
potato__ compile --all # Recompile all .topping files
potato__ watch <topping> # Auto-recompile on changes
potato__ check # Check if recompilation needed
# Registry
potato__ tap add <url> # Add topping registry
potato__ tap list # List registries
potato__ tap update # Update registry indexes
potato__ install <topping> # Install from registry
potato__ uninstall <topping> # Remove topping
potato__ search <query> # Search registries
potato__ info <topping> # Show topping information
potato__ upgrade # Update all installed toppings
# Inspection
potato__ inspect # Show what hooks are active
potato__ conflicts # Detect conflicting toppings
potato__ triggers # Show trigger analysis
potato__ status <topping> # Show topping status
potato__ logs <topping> # Show topping logs
# Debugging
potato__ test <topping> # Test a topping
potato__ debug <topping> # Run with debug output
potato__ trace # Trace hook execution
# Utilities
potato__ reload # Reload all toppings
potato__ validate <topping> # Validate syntax
potato__ convert <old> # Convert old format to new$ potato__ inspect
Active Toppings (3 enabled):
============================
[bashrc] - Runs at shell startup
1. git-prompt (priority: 50)
Description: Show git branch in prompt
Source: git-prompt.bashrc.sh
Status: ✓ Loaded
[precommand] - Runs before each command
1. aws-safety (priority: 10)
Description: Prevent AWS production deletions
Source: aws-safety.topping → aws-safety.precommand.sh
Compiled: 2026-01-01 12:00
Triggers: command(^aws.*delete) AND env(AWS_PROFILE=production)
Actions: warn, confirm, abort
Status: ✓ Active
[postcommand] - Runs after each command
1. npm-auto-install (priority: 50)
Description: Auto-install npm packages
Source: npm-auto-install.topping → npm-auto-install.postcommand.sh
Compiled: 2026-01-01 11:00
Triggers: file_changed(package.json)
Actions: run(npm install)
Status: ✓ Active
⚠️ Conflicts with: yarn-auto-install (disabled)
$ potato__ conflicts
Potential Conflicts:
===================
package.json monitoring:
- npm-auto-install (enabled)
- yarn-auto-install (disabled)
→ OK: Only one enabled
No active conflicts detected.$ POTATO_DRY_RUN=1 cd /project
[DRY RUN] Hooks that would execute:
====================================
[postcommand]
1. npm-auto-install
Trigger matched: file_changed(package.json)
Would run: npm install
2. nvm-auto-switch
Trigger matched: file_exists(.nvmrc)
Would run: nvm use
$ POTATO_DRY_RUN=1 git push
[DRY RUN] Hooks that would execute:
====================================
[precommand]
1. git-push-checklist
Trigger matched: command(^git push)
Would show: confirmation dialog
User action required: yes/noScenario: Automatically install dependencies when package files change
[Topping]
Name=npm-auto-install
Description=Auto-install npm packages when package.json changes
Requires=npm
Conflicts=yarn-auto-install pnpm-auto-install
[Trigger]
Type=postcommand
On=file_changed package.json
On=file_changed package-lock.json
[Action]
Run=npm install
Log=Installed npm packagesScenario: Prevent accidental production deletions
[Topping]
Name=aws-production-safety
Description=Require confirmation for AWS deletions in production
Requires=aws
[Trigger]
Type=precommand
On=command ^aws.*delete
On=env_equals AWS_PROFILE production
All
[Action]
Warn=⚠️ You are about to DELETE in PRODUCTION!
Confirm=Type 'DELETE' to continue
ConfirmMatch=DELETE
AbortScenario: Auto-switch Node version when entering project
[Topping]
Name=nvm-auto-switch
Description=Auto-switch Node version based on .nvmrc
Requires=nvm
[Trigger]
Type=postcommand
On=dir_entered *
On=file_exists .nvmrc
[Action]
Run=nvm use
Silent=trueScenario: Remind about PR checklist before pushing
[Topping]
Name=git-pr-checklist
Description=Show PR checklist before git push
Priority=10
[Trigger]
Type=precommand
On=command ^git push
[Action]
Warn=📋 Pre-push checklist:
Warn= - Tests passing?
Warn= - Docs updated?
Warn= - No debug code?
Confirm=Ready to push?
AbortIf=!confirmedScenario: Auto-cleanup stopped containers
[Topping]
Name=docker-auto-cleanup
Description=Cleanup stopped containers after docker stop
Requires=docker
[Trigger]
Type=postcommand
On=command ^docker (stop|kill)
[Action]
RunBackground=docker container prune -f
Log=Cleaned up stopped containersScenario: Automatically track time spent in projects
[Topping]
Name=project-time-tracker
Description=Track time spent in project directories
[Trigger]
Type=postcommand
On=dir_entered */projects/*
[Action]
Set=PROJECT_START_TIME $(date +%s)
Log=Started working on project: $PWD
[Trigger]
Type=postcommand
On=dir_exited */projects/*
[Action]
Run=echo "Time spent: $(($(date +%s) - $PROJECT_START_TIME))s" >> ~/.work-log
Unset=PROJECT_START_TIMEScenario: Log all database connections for audit
[Topping]
Name=db-audit-logger
Description=Log database connections for compliance
[Trigger]
Type=postcommand
On=command ^(psql|mysql|mongo)
[Action]
Log=DB Connection: $BASH_COMMAND
Run=echo "[$(date)] $USER: $BASH_COMMAND" >> /var/log/db-audit.logScenario: Auto-update tmux preview pane with README
#!/usr/bin/env bash
# tmux-preview.postcommand.sh
# POTATO_NAME: tmux-preview
# POTATO_DESCRIPTION: Auto-update tmux preview pane with file content
# POTATO_REQUIRES: tmux
[[ -z "$TMUX" ]] && return 0
# Detect directory change
if [[ "$PWD" != "${POTATO_PREV_PWD:-}" ]]; then
export POTATO_PREV_PWD="$PWD"
# Ensure preview pane exists
if [[ $(tmux list-panes | wc -l) -eq 1 ]]; then
tmux split-window -h -d "cat"
fi
PANE=$(tmux list-panes -F "#{pane_id}" | tail -1)
# Show README or directory listing
if [[ -f README.md ]]; then
tmux respawn-pane -t "$PANE" -k "cat README.md; cat"
else
tmux respawn-pane -t "$PANE" -k "ls -lah; cat"
fi
fiGoal: Ship working system with bash scripts
Features:
- ✅ Current
.bashrc,.precommand,.postcommandsystem - ✅ Basic enable/disable via symlinks
- ✅
potato__ listcommand - ✅ Helper functions (e.g.,
potato_on_file_changed) - ✅ Documentation with examples
Deliverables:
- Working potato framework
- 5-10 example toppings
- README.md, AGENTS.md, DEVELOPERS.md
- Installation script
Goal: Add metadata without breaking existing toppings
Features:
- Parse comment-based metadata (
# POTATO_*) potato__ inspectcommandpotato__ conflictsdetection- Priority system
- Dependency checking
Deliverables:
- Metadata parser
- Inspection tools
- Conflict detection
- Updated documentation
Goal: Add .topping format that compiles to .sh
Features:
- INI parser for
.toppingfiles - Bash code generator
potato__ compilecommand- Auto-recompilation on changes
- Source tracking (which .topping generated which .sh)
Deliverables:
- Compiler implementation
- 10+ .topping examples
- Compilation documentation
- Migration guide
Goal: Enable sharing and discovery
Features:
- Registry system (GitHub-based)
potato__ tapcommandspotato__ install/uninstallpotato__ search- Version management
- Official topping repository
Deliverables:
- Registry infrastructure
- Official toppings repo
- Installation system
- Search functionality
Goal: Polish and advanced capabilities
Features:
- Additional hooks (directory_change, on_exit)
- State management API
- Async/background execution
- Testing framework
- Web-based topping browser
- IDE integration
Deliverables:
- Enhanced hook system
- State API
- Testing tools
- Community tools
What we borrowed:
- Simplicity: bash scripts in a directory
- Naming convention by lifecycle event
- Per-project vs global
- Skip mechanism
Link: https://git-scm.com/docs/githooks
What we borrowed:
- INI-like configuration format
Before=/After=orderingConflicts=/Requires=dependencies- Enable/disable via symlinks
- Rich metadata
Link: https://systemd.io
What we borrowed:
- Tap system for registries
- Formula structure
depends_on/conflicts_with- Community model
- Version management
Link: https://brew.sh
What we borrowed:
- Event-driven triggers (
on:) - Marketplace for sharing
- YAML declarative syntax
- Conditional execution
Link: https://github.com/features/actions
What we borrowed:
- Hook concept for bash
- preexec/precmd pattern
- Using DEBUG trap
Link: https://github.com/rcaloras/bash-preexec
What we borrowed:
- Directory-specific configuration
.envrcpattern- Auto-loading on directory change
Link: https://direnv.net
What we borrowed:
- DSL that generates runtime config