Use this skill when the user wants to create, write, or install shell scripts...
This skill helps you create and install shell scripts to the user's PATH.
Activate this skill when the user:
Default to Fish shell for all new scripts unless:
Install all scripts to: ~/bin/scripts
IMPORTANT: ~/bin/scripts is a git repository. All script creation and editing must use proper git workflow.
Follow these steps in order:
cd ~/bin/scriptsgit checkout -b add-<script-name>add-backup-tool, add-git-helper)~/bin/scripts/<script-name>#!/usr/bin/env fish#!/usr/bin/env bashchmod +x ~/bin/scripts/<script-name>/dev/null (or another pipe), which silently hides whole classes of bugs. If the script will be invoked interactively by a human, it MUST be tested that way before declaring it working.<script-name>?"git add <script-name>git checkout maingit merge add-<script-name>git branch -d add-<script-name># For Fish: add to ~/.config/fish/config.fish
fish_add_path $HOME/bin/scripts
# For bash: add to ~/.bashrc or ~/.bash_profile
# For zsh: add to ~/.zshrc
export PATH="$HOME/bin/scripts:$PATH"
which <script-name>CRITICAL: All scripts must implement structured logging using the logger command.
logger command with structured key=value formatscriptname[PID] (use $$ for PID in bash, $fish_pid in fish)$fish_pid to get the process ID, NOT $$ or %selfMap script events to appropriate log levels:
user.notice: Process status, file operations, success events
user.info -- see retention below.user.warning: Unsupported formats, non-critical issues, recoverable errors
user.error: Missing dependencies, failures, exit conditions
user.debug: File creation, skipped files, detailed operations
--debug flag (below) so the operator
has a way to see it without a second terminal.CRITICAL: Priority determines whether a message can be read back later. Verified on macOS 15 (Darwin 24.6):
| Priority | Unified level | Retrievable afterward? |
|---|---|---|
notice, warning, err, error, crit |
default (Df) |
Yes -- plain log show, weeks later |
info |
info (I) |
No -- memory-only, evicted in minutes; needs --info even when fresh |
debug |
debug (Db) |
No -- absent from log show at any flag; visible only in log stream --level debug |
This is why status logging uses user.notice. A script that logs its normal
operation at user.info is writing diagnostics nobody can ever read back --
it fails silently, since logger reports success either way.
Two traps when checking whether logging works:
log show hides info-level messages unless you pass --info. An empty
result is not proof that nothing was logged.log is a zsh builtin. Under zsh a bare log show ... fails with
zsh:log:1: too many arguments and prints nothing -- which looks exactly
like "no matching entries". Always invoke /usr/bin/log explicitly in
scripts, tooling, and agent tool calls. (Interactive fish resolves log
to /usr/bin/log correctly, so this only bites non-fish contexts.)Known limitation (macOS only): the -t tag is discarded by unified
logging. Messages surface as process logger, and no field preserves
scriptname[PID] -- so the tag cannot be used as a filter. This is a platform
difference, not a bug: on Linux the tag is a first-class field (journald's
SYSLOG_IDENTIFIER, so journalctl -t web2md works), which is where the
scriptname[PID] convention comes from. macOS's model identifies messages by
subsystem, category and process, and there is no CLI route to set a subsystem
-- logger's whole flag set is [-is] [-f file] [-p pri] [-t tag].
The macOS man page is misleading here: it still describes logger as an
interface to syslog(3) and promises -t will "mark every line in the log",
describing a system macOS has not used since 10.12.
So on macOS the script name has to ride in the message body. Gate it on the OS so the prefix does not become redundant noise on Linux (see the templates below), then filter with either:
/usr/bin/log show --last 1h --predicate 'eventMessage BEGINSWITH "script=web2md"'
journalctl -t web2md # Linux, using the tag directly
Fish:
logger -t (basename (status filename))"[$fish_pid]" -p user.notice "action=start status=processing"
logger -t (basename (status filename))"[$fish_pid]" -p user.error "action=fail error=\"missing dependency\""
Bash:
logger -t "$(basename "$0")[$$]" -p user.notice "action=start status=processing"
logger -t "$(basename "$0")[$$]" -p user.error "action=fail error=\"missing dependency\""
Reading them back:
/usr/bin/log show --last 1h --predicate 'process == "logger"' --style syslog
/usr/bin/log stream --level debug --predicate 'process == "logger"' # includes debug
--debug Flag (REQUIRED)Every script takes --debug (short form -d unless already claimed), which
routes debug output to stderr immediately instead of relying on a live stream:
function log_debug
test "$DEBUG" = 1; and echo "[debug] $argv" >&2
logger -t "$SCRIPT_NAME[$fish_pid]" -p user.debug "$LOG_TAG$argv"
end
Set the global right after argument parsing:
argparse 'h/help' 'v/version' 'test' 'd/debug' 'fish-completions' -- $argv
if set -q _flag_debug
set -g DEBUG 1
end
The logger call stays unconditional: a script that runs unattended (launchd,
a watcher, a cron job) cannot be handed --debug, and the live stream is the
only way to observe it. Document the flag in usage and in the generated fish
completions like any other option. If the script forwards unrecognized
arguments to another tool, consume --debug during parsing so it is not
passed through.
CRITICAL: Scripts that process data (read input, transform, write output) MUST follow these I/O standards.
Default to stdin when no file argument is provided:
set input_file "-"
if test (count $argv) -gt 0
set input_file $argv[1]
end
# Validate file exists (if not stdin)
if test "$input_file" != "-" -a ! -f "$input_file"
echo "Error: Input file not found: $input_file" >&2
log_error "action=read_input status=not_found file=\"$input_file\""
exit 2
end
Default to stdout, but provide -o/--output flag for file output:
set output_file "-"
set append_mode 0
argparse 'o/output=' 'a/append' -- $argv
if set -q _flag_output
set output_file $_flag_output
end
if set -q _flag_append
set append_mode 1
end
# Validate: append requires output file
if test $append_mode -eq 1 -a "$output_file" = "-"
echo "Error: --append requires --output to specify a file" >&2
exit 2
end
stdout = data, stderr = messages
# WRONG - mixes data and messages
echo "Processing 100 items..."
echo "$result_data"
# CORRECT - separates streams
echo "Processing 100 items..." >&2 # Progress to stderr
echo "$result_data" # Data to stdout
Why: Pipes capture stdout only. Messages on stderr appear to user but don't pollute the data stream.
For structured data, prefer TSV (tab-separated values):
# TSV format (no header, easy to pipe)
echo -e "$url\t$title\t$format\t$notes"
Benefits:
cut, awk, sortFor scripts that iterate over multiple items or perform long-running operations:
# Auto-detect interactive vs batch mode
set progress_mode 0
if isatty stderr
set progress_mode 1 # Interactive - show progress by default
end
# Parse flags (in argparse)
argparse 'progress' 'no-progress' -- $argv
# Explicit flags override auto-detection
if set -q _flag_progress
set progress_mode 1
end
if set -q _flag_no_progress
set progress_mode 0
end
# In processing loop:
set current 0
set total (count $items)
for item in $items
set current (math $current + 1)
if test $progress_mode -eq 1
# Non-scrolling in-place update (\r returns to start of line)
printf "\r[%d/%d] %s" $current $total "$item" >&2
end
# ... do work ...
end
# Final newline to complete the progress line
if test $progress_mode -eq 1
printf "\n" >&2
end
# Summary stats to stderr (always show, even in no-progress mode)
echo "Results: $valid valid, $invalid invalid" >&2
Key Points:
printf "\r..." for non-scrolling in-place updates# Example: Enable piping between related tools
cat urls.txt | extract-urls | validate-urls | download-videos
# Each script in the chain:
# - Reads from stdin OR file
# - Writes data to stdout
# - Writes progress/errors to stderr
# - Uses consistent format (one-per-line or TSV)
Add these to your usage() function:
echo "Examples:"
echo " $SCRIPT_NAME input.txt -o output.txt"
echo " cat input.txt | $SCRIPT_NAME"
echo " $SCRIPT_NAME < input.txt > output.txt"
echo " $SCRIPT_NAME input.txt | other-tool"
--help / -h argument with usage information--version / -v argument showing script version--test argument that runs unit and regression tests of the code--fish-completions argument that installs fish shell tab completions to ~/.config/fish/completions/<script-name>.fishScripts that process input data and produce output MUST also include:
-o / --output FILE - Write output to FILE instead of stdout (default: stdout)-a / --append - Append to output file instead of overwriting (requires -o)Scripts that process multiple items or perform long-running operations MUST include:
--progress - Force in-place progress updates (even in batch mode)--no-progress - Suppress progress updates (even in interactive mode)isatty stderr (interactive = progress on, batch = progress off)# Installation: Copy to ~/bin/scripts# Installation: Copy to ~/.config/fish/functions# Installation: Source from ~/.config/fish/config.fish or ~/.bashrcCRITICAL: Fish shell has specific behaviors that differ from bash/zsh. See the comprehensive guide at ~/fish-shell-rules.md for detailed rules, examples, and bug history from this project.
Variable Scoping: Use set -g (not set -l) for variables accessed across functions
# WRONG: set -l urls "..." # Will be empty in called functions
# CORRECT: set -g urls "..." # Visible everywhere
Directory Changes: Fish (cd dir && cmd) does NOT create subshell - always save/restore $PWD
# WRONG: (cd "$temp" && process) # Changes parent directory!
# CORRECT: set orig $PWD; cd "$temp"; process; cd "$orig"
Multi-line Output: Use | string collect to preserve newlines in command substitution
# WRONG: set output (command) # Collapses newlines to spaces
# CORRECT: set output (command | string collect)
stdin Detection: Always check for empty args before reading stdin
# WRONG: if not isatty stdin # False positive with redirects
# CORRECT: if test (count $argv) -eq 0; and not isatty stdin
Array Iteration: Use direct iteration (never echo/split)
# WRONG: for x in (echo "$array" | string split \n)
# CORRECT: for x in $array
String Operations: Use Fish's string built-in instead of grep/sed/awk
# WRONG: echo "$text" | grep "pattern"
# CORRECT: string match "*pattern*" $text
See ~/fish-shell-rules.md for complete rules and the commit history showing why these rules exist.
#!/usr/bin/env fish for portability--help and --version flags#!/usr/bin/env bash for portability--help and --version flagsset -e or appropriate error checkscd commands: When scripts change directories, subsequent operations with relative paths may breakset origin_dir (pwd) (fish) or origin_dir=$(pwd) (bash)The single biggest source of "works in CI / hangs in real life" bugs.
When an AI agent runs a script via the Bash tool, the script's stdin is usually /dev/null (or another pipe), and stdout/stderr are pipes β not a terminal. A script that the user will run interactively at the terminal sees something completely different: stdin/stdout/stderr are all attached to a TTY. Several common failure modes only surface on the real TTY:
(cat | string collect) inside a fish function called from a pipeline reads the controlling TTY, not the function's piped stdin β hangs forever waiting for Ctrl-D.read -P "..." answer blocks indefinitely if the script ends up wired to a non-interactive stdin.isatty stdin returns different things, which can flip code paths (auto-detect of progress, paging, prompts).$EDITOR $tmpfile) needs a real TTY to function; agent test runs may either hang on a vim that can't draw, or quietly skip the step.tput cols or isatty stdout looks right in agent runs and broken when piped (or vice versa).How to test correctly. Match the invocation context to the script's intended use:
| Intended use | Correct test invocation |
|---|---|
| Human runs in terminal | User runs it themselves in their real shell, OR allocate a PTY (see below) |
Piped into / out of (a | script | b) |
Test with the actual pipe in place, both ends |
| Cron / launchd / CI | Test with stdin from /dev/null, no TTY |
| Mixed (interactive + pipe-friendly) | Test BOTH paths β they exercise different code |
Allocating a PTY from an agent context. If you (the AI) need to verify TTY-bound behavior yourself without the user, use script(1) (BSD/macOS variant) or expect/unbuffer to allocate a pseudo-terminal:
# macOS/BSD: -q quiet, -t 0 no timing file, runs cmd under a PTY
script -q /dev/null my-script --some-flag </dev/null
# Or, if `expect` is installed:
unbuffer my-script --some-flag
What to actually verify in a TTY-mode test:
timeout 30 script -q /dev/null ./my-script) so a hang fails the test instead of stalling forever.lsof -p <pid> on a hung process to inspect fd 0 is the definitive diagnostic.od -c or cat -e, not by eye β fish list-join collapses \n to space and it's easy to miss in normal output.$EDITOR, test with EDITOR=true (succeeds, file unchanged) and a fake editor that mutates the file, to ensure both paths work.The mindset: before declaring a script done, ask "what is stdin/stdout/stderr in the real invocation, and have I actually tested that configuration?" If the only test was cmd > /dev/null 2>&1 < /dev/null from inside an agent, the answer is no.
#!/usr/bin/env fish
# Script: example-script
# Version: 1.0.0
# Description: What this script does
# Installation: Copy to ~/bin/scripts
set VERSION "1.0.0"
set SCRIPT_NAME (basename (status filename))
# macOS unified logging discards logger's -t tag, so the script name has to
# ride in the message body instead. Linux keeps the tag (journald
# SYSLOG_IDENTIFIER), where this prefix would be redundant.
if test (uname) = Darwin
set -g LOG_TAG "script=$SCRIPT_NAME "
else
set -g LOG_TAG ""
end
function log_info
logger -t "$SCRIPT_NAME[$fish_pid]" -p user.notice "$LOG_TAG$argv"
end
function log_error
logger -t "$SCRIPT_NAME[$fish_pid]" -p user.error "$LOG_TAG$argv"
end
function log_debug
test "$DEBUG" = 1; and echo "[debug] $argv" >&2
logger -t "$SCRIPT_NAME[$fish_pid]" -p user.debug "$LOG_TAG$argv"
end
function show_version
echo "$SCRIPT_NAME version $VERSION"
exit 0
end
function run_tests
log_info "action=test status=starting"
# Add unit and regression tests here
echo "Running unit tests..."
# Example test placeholder
# Replace with actual test logic
echo "All tests passed!"
log_info "action=test status=complete"
exit 0
end
function install_fish_completions
set -l completions_file ~/.config/fish/completions/$SCRIPT_NAME.fish
# Check if file already exists
if test -f "$completions_file"
echo "Error: Completions file already exists: $completions_file" >&2
echo "Remove it first if you want to regenerate completions." >&2
exit 1
end
# Ensure directory exists
mkdir -p ~/.config/fish/completions
# Generate and write completions
echo "# Fish completions for $SCRIPT_NAME
# Generated by $SCRIPT_NAME --fish-completions
# Complete flags
complete -c $SCRIPT_NAME -s h -l help -d 'Show help message'
complete -c $SCRIPT_NAME -s d -l debug -d 'Print debug output to stderr'
complete -c $SCRIPT_NAME -s v -l version -d 'Show version information'
complete -c $SCRIPT_NAME -l test -d 'Run unit and regression tests'
complete -c $SCRIPT_NAME -l fish-completions -d 'Install fish shell completions'
# Add script-specific completions here
" > "$completions_file"
and begin
echo "Fish completions installed to: $completions_file"
echo ""
echo "Completions will be available in new fish shell sessions."
echo "To use them immediately in this session, run:"
echo " source $completions_file"
end
or begin
echo "Error: Failed to write completions file" >&2
exit 1
end
exit 0
end
function usage
echo "Usage: $SCRIPT_NAME [options]"
echo ""
echo "Description: What this script does"
echo ""
echo "Options:"
echo " -d, --debug Print debug output to stderr"
echo " -h, --help Show this help message"
echo " -v, --version Show version information"
echo " --test Run unit and regression tests"
echo " --fish-completions Install fish shell completions"
exit 0
end
function main
log_info "action=start status=processing"
# Your script logic here
echo "Hello from example Fish script!"
log_info "action=complete status=success"
end
# Parse arguments
argparse 'h/help' 'v/version' 'test' 'd/debug' 'fish-completions' -- $argv
or begin
usage
end
if set -q _flag_debug
set -g DEBUG 1
end
if set -q _flag_help
usage
end
if set -q _flag_version
show_version
end
if set -q _flag_test
run_tests
end
if set -q _flag_fish_completions
install_fish_completions
end
log_debug "action=init args=\"$argv\""
main
#!/usr/bin/env bash
set -e
# Script: example-script
# Version: 1.0.0
# Description: What this script does
# Installation: Copy to ~/bin/scripts
VERSION="1.0.0"
SCRIPT_NAME="$(basename "$0")"
# macOS unified logging discards logger's -t tag, so the script name has to
# ride in the message body instead. Linux keeps the tag (journald
# SYSLOG_IDENTIFIER), where this prefix would be redundant. $OSTYPE avoids a
# fork; use [ "$(uname)" = Darwin ] in a plain sh script.
if [[ "$OSTYPE" == darwin* ]]; then
LOG_TAG="script=${SCRIPT_NAME} "
else
LOG_TAG=""
fi
log_info() {
logger -t "${SCRIPT_NAME}[$$]" -p user.notice "${LOG_TAG}$*"
}
log_error() {
logger -t "${SCRIPT_NAME}[$$]" -p user.error "${LOG_TAG}$*"
}
log_debug() {
[ "$DEBUG" = 1 ] && echo "[debug] $*" >&2
logger -t "${SCRIPT_NAME}[$$]" -p user.debug "${LOG_TAG}$*"
}
show_version() {
echo "$SCRIPT_NAME version $VERSION"
exit 0
}
run_tests() {
log_info "action=test status=starting"
# Add unit and regression tests here
echo "Running unit tests..."
# Example test placeholder
# Replace with actual test logic
echo "All tests passed!"
log_info "action=test status=complete"
exit 0
}
install_fish_completions() {
local completions_file=~/.config/fish/completions/$SCRIPT_NAME.fish
# Check if file already exists
if [[ -f "$completions_file" ]]; then
echo "Error: Completions file already exists: $completions_file" >&2
echo "Remove it first if you want to regenerate completions." >&2
exit 1
fi
# Ensure directory exists
mkdir -p ~/.config/fish/completions
# Generate and write completions
cat > "$completions_file" << 'EOF'
# Fish completions for $SCRIPT_NAME
# Generated by $SCRIPT_NAME --fish-completions
# Complete flags
complete -c $SCRIPT_NAME -s h -l help -d 'Show help message'
complete -c $SCRIPT_NAME -s d -l debug -d 'Print debug output to stderr'
complete -c $SCRIPT_NAME -s v -l version -d 'Show version information'
complete -c $SCRIPT_NAME -l test -d 'Run unit and regression tests'
complete -c $SCRIPT_NAME -l fish-completions -d 'Install fish shell completions'
# Add script-specific completions here
EOF
if [[ $? -eq 0 ]]; then
echo "Fish completions installed to: $completions_file"
echo ""
echo "Completions will be available in new fish shell sessions."
echo "To use them immediately in this session, run:"
echo " source $completions_file"
else
echo "Error: Failed to write completions file" >&2
exit 1
fi
exit 0
}
usage() {
echo "Usage: $SCRIPT_NAME [options]"
echo ""
echo "Description: What this script does"
echo ""
echo "Options:"
echo " -d, --debug Print debug output to stderr"
echo " -h, --help Show this help message"
echo " -v, --version Show version information"
echo " --test Run unit and regression tests"
echo " --fish-completions Install fish shell completions"
exit 0
}
main() {
log_info "action=start status=processing"
# Your script logic here
echo "Hello from example bash script!"
log_info "action=complete status=success"
}
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
usage
;;
-v|--version)
show_version
;;
--test)
run_tests
;;
--fish-completions)
install_fish_completions
;;
*)
echo "Unknown option: $1"
usage
;;
esac
shift
done
log_debug "action=init args=\"$*\""
main
For scripts that process input and produce output (especially multi-item processing), use this enhanced template:
#!/usr/bin/env fish
# Script: process-items
# Version: 1.0.0
# Description: Process items from input and output results
# Installation: Copy to ~/bin/scripts
set VERSION "1.0.0"
set SCRIPT_NAME (basename (status filename))
# macOS unified logging discards logger's -t tag, so the script name has to
# ride in the message body instead. Linux keeps the tag (journald
# SYSLOG_IDENTIFIER), where this prefix would be redundant.
if test (uname) = Darwin
set -g LOG_TAG "script=$SCRIPT_NAME "
else
set -g LOG_TAG ""
end
# Logging functions
function log_info
logger -t "$SCRIPT_NAME[$fish_pid]" -p user.notice "$LOG_TAG$argv"
end
function log_error
logger -t "$SCRIPT_NAME[$fish_pid]" -p user.error "$LOG_TAG$argv"
end
function show_version
echo "$SCRIPT_NAME version $VERSION"
exit 0
end
function usage
echo "Usage: $SCRIPT_NAME [options] [input-file]"
echo ""
echo "Process items from input and output results."
echo ""
echo "Arguments:"
echo " input-file File containing items (default: stdin)"
echo ""
echo "Options:"
echo " -d, --debug Print debug output to stderr"
echo " -h, --help Show this help message"
echo " -v, --version Show version information"
echo " -o, --output FILE Output file (default: stdout)"
echo " -a, --append Append to existing file instead of overwriting"
echo " --progress Force progress updates (even in batch mode)"
echo " --no-progress Suppress progress updates (even in interactive mode)"
echo " --test Run unit and regression tests"
echo " --fish-completions Install fish shell completions"
echo ""
echo "Examples:"
echo " $SCRIPT_NAME input.txt -o output.txt"
echo " cat input.txt | $SCRIPT_NAME"
echo " $SCRIPT_NAME input.txt | other-tool"
exit 0
end
function main
# I/O configuration
set input_file "-"
set output_file "-"
set append_mode 0
# Progress configuration (auto-detect TTY)
set progress_mode 0
if isatty stderr
set progress_mode 1
end
# Parse arguments
argparse 'h/help' 'v/version' 'o/output=' 'a/append' 'progress' 'no-progress' 'test' 'd/debug' 'fish-completions' -- $argv
or begin
usage
exit 2
end
# Handle flags
if set -q _flag_debug
set -g DEBUG 1
end
if set -q _flag_help; usage; end
if set -q _flag_version; show_version; end
if set -q _flag_output; set output_file $_flag_output; end
if set -q _flag_append; set append_mode 1; end
if set -q _flag_progress; set progress_mode 1; end
if set -q _flag_no_progress; set progress_mode 0; end
# Get input file from remaining arguments
if test (count $argv) -gt 0
set input_file $argv[1]
if test "$input_file" != "-" -a ! -f "$input_file"
echo "Error: Input file not found: $input_file" >&2
log_error "action=read_input status=not_found file=\"$input_file\""
exit 2
end
end
# Validate append requires output file
if test $append_mode -eq 1 -a "$output_file" = "-"
echo "Error: --append requires --output to specify a file" >&2
exit 2
end
log_info "action=start input=\"$input_file\" output=\"$output_file\""
# Read input items
if test "$input_file" = "-"
set items (cat)
else
set items (cat $input_file)
end
# Process items with progress
set current 0
set total (count $items)
set results
for item in $items
set current (math $current + 1)
# Show progress (non-scrolling, in-place update)
if test $progress_mode -eq 1
printf "\r[%d/%d] Processing: %s" $current $total "$item" >&2
end
# Process item (replace with actual logic)
set result (process_item $item)
set -a results $result
end
# Complete progress line
if test $progress_mode -eq 1
printf "\n" >&2
end
# Summary to stderr (always show)
echo "Processed $total items" >&2
# Write output (data to stdout or file)
if test "$output_file" = "-"
printf "%s\n" $results
else
if test $append_mode -eq 1
printf "%s\n" $results >> "$output_file"
else
printf "%s\n" $results > "$output_file"
end
end
log_info "action=complete status=success count=$total"
end
function process_item
# Replace with actual processing logic
echo "processed: $argv[1]"
end
# Run main
main $argv
NOTE: For Fish-specific errors (variable scoping, directory changes, stdin detection, etc.), see ~/fish-shell-rules.md which documents recurring bugs from this project's history.
WRONG:
echo "Processing file..." # Goes to stdout
echo "$result" # Also goes to stdout
CORRECT:
echo "Processing file..." >&2 # Messages to stderr
echo "$result" # Data to stdout
WRONG: Only accepting file arguments
cat $argv[1] # Fails if no file specified
CORRECT: Default to stdin/stdout
set input_file "-"
if test (count $argv) -gt 0
set input_file $argv[1]
end
cdWRONG:
cd /some/directory
cat config.txt # Where is this file now?
CORRECT:
# Store original directory or use absolute paths
set origin_dir (pwd)
cd /some/directory
# ... work ...
cd $origin_dir
cat config.txt
WRONG:
curl $url > data.json
process_file data.json # What if curl failed?
CORRECT:
curl $url > data.json
if test $status -ne 0
log_error "action=download status=failed url=\"$url\""
exit 1
end
WRONG:
if not command -v tool
exit 1 # User has no idea what happened
end
CORRECT:
if not command -v tool
echo "Error: 'tool' is not installed" >&2
echo "Install with: brew install tool" >&2
log_error "action=dependency_check status=missing tool=tool"
exit 3
end
WRONG: Allowing --append without --output
# Should validate that append mode requires a file
CORRECT:
if test $append_mode -eq 1 -a "$output_file" = "-"
echo "Error: --append requires --output to specify a file" >&2
exit 2
end
WRONG: Asking for user input in a data-processing script
read -P "Continue? (y/n): " answer
CORRECT: Use flags for all options, avoid prompts in pipe-friendly scripts
chmod +x was applied to the script~/bin/scripts is in PATHgit merge --abortgit stashgit branch -D add-<script-name> or use a different nameadd-<script-name>git checkout main && git branch -D add-<script-name>IMPORTANT: Use detailed, structured commit messages with the following format:
Add <script-name>: <brief one-line description>
Features:
- Feature 1 with details
- Feature 2 with details
- Feature 3 with details
Technical details:
- Implementation detail 1
- Implementation detail 2
- Configuration or dependency notes
Co-Authored-By: Claude <noreply@anthropic.com>
Guidelines:
Example:
Add backup-tool: Automated backup script with compression and rotation
Features:
- Automatic backup of specified directories
- Gzip compression with configurable level
- Rotation policy (keeps last N backups)
- Email notifications on completion or failure
- Dry-run mode for testing
Technical details:
- Implemented in Fish shell with structured logging
- Uses rsync for efficient file copying
- Logger integration for syslog monitoring
- Configuration via environment variables
- Requires: rsync, gzip, mail utilities
Co-Authored-By: Claude <noreply@anthropic.com>