From ccfg-shell
Scaffold a new shell script or shell project with production-ready defaults
How this command is triggered — by the user, by Claude, or both
Slash command
/ccfg-shell:scaffold <name> [--type=script|project] [--shell=bash|sh]This command is limited to the following tools:
The summary Claude sees in its command listing — used to decide when to auto-load this command
# scaffold Create a new shell script or shell project with production-ready structure, safety defaults, and consistent conventions. Scripts are immediately executable and pass shellcheck and shfmt validation. ## Usage ## Overview The scaffold command creates shell scripts or projects with: - **Safety preamble**: `set -euo pipefail` (bash) or `set -eu` (POSIX sh) - **main() pattern**: Structured code organization with functions - **Trap cleanup**: Automatic resource cleanup on exit - **Argument parsing**: Template for handling flags and arguments - **Executable permissions**: `chmod +...
Create a new shell script or shell project with production-ready structure, safety defaults, and consistent conventions. Scripts are immediately executable and pass shellcheck and shfmt validation.
ccfg shell scaffold deploy.sh # Single bash script (default)
ccfg shell scaffold install.sh --shell=sh # Single POSIX sh script
ccfg shell scaffold mytools --type=project # Full project layout
ccfg shell scaffold mytools --type=project --shell=sh # POSIX sh project
The scaffold command creates shell scripts or projects with:
set -euo pipefail (bash) or set -eu (POSIX sh)chmod +x applied automaticallyAll generated code follows shell best practices and is immediately usable.
A standalone executable shell script with full safety features.
Generated file (bash):
#!/usr/bin/env bash
#
# deploy.sh - [Brief description]
#
# Usage: deploy.sh [OPTIONS] <args>
#
set -euo pipefail
# --- Script directory ---
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# --- Logging ---
log() { printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >&2; }
warn() { printf '[%s] WARN: %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >&2; }
err() { printf '[%s] ERROR: %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >&2; }
die() { err "$@"; exit 1; }
# --- Cleanup ---
cleanup() {
# Remove temporary files, kill background processes, etc.
:
}
trap cleanup EXIT
# --- Usage ---
usage() {
cat <<'EOF'
Usage: deploy.sh [OPTIONS] <args>
[Description of what this script does]
Options:
-h, --help Show this help message
-v, --verbose Enable verbose output
Arguments:
args [Description of required arguments]
Examples:
deploy.sh -v target
deploy.sh --help
EOF
}
# --- Argument parsing ---
parse_args() {
VERBOSE=false
while (( $# > 0 )); do
case "$1" in
-h|--help) usage; exit 0 ;;
-v|--verbose) VERBOSE=true; shift ;;
--) shift; break ;;
-*) die "Unknown option: $1" ;;
*) break ;;
esac
done
# Validate required arguments
if (( $# < 1 )); then
die "Missing required argument. Run with --help for usage."
fi
}
# --- Main ---
main() {
parse_args "$@"
log "Starting..."
# Script logic here
log "Done"
}
main "$@"
Generated file (POSIX sh):
#!/bin/sh
#
# install.sh - [Brief description]
#
# Usage: install.sh [OPTIONS] <args>
#
set -eu
# --- Logging ---
log() { printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >&2; }
warn() { printf '[%s] WARN: %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >&2; }
err() { printf '[%s] ERROR: %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >&2; }
die() { err "$@"; exit 1; }
# --- Cleanup ---
cleanup() {
:
}
trap cleanup EXIT
# --- Usage ---
usage() {
cat <<'EOF'
Usage: install.sh [OPTIONS] <args>
[Description of what this script does]
Options:
-h Show this help message
-v Enable verbose output
Arguments:
args [Description of required arguments]
EOF
}
# --- Argument parsing ---
VERBOSE=false
while getopts ":hv" opt; do
case $opt in
h) usage; exit 0 ;;
v) VERBOSE=true ;;
*) die "Unknown option: -$OPTARG" ;;
esac
done
shift $((OPTIND - 1))
# --- Main ---
main() {
if [ $# -lt 1 ]; then
die "Missing required argument. Run with -h for usage."
fi
log "Starting..."
# Script logic here
log "Done"
}
main "$@"
Key differences between bash and POSIX sh scripts:
| Feature | Bash | POSIX sh |
|---|---|---|
| Preamble | set -euo pipefail | set -eu |
| Test syntax | [[ ]] | [ ] |
| Arrays | Supported | Not available |
| Local variables | local var= | Use function-scoped naming |
| Pattern matching | [[ $x == *.txt ]] | case $x in *.txt) |
| Arg parsing | while+case with long opts | getopts with short opts |
| Script dir | ${BASH_SOURCE[0]} | $0 (less reliable) |
A multi-file shell project with organized directory structure.
Generated structure:
mytools/
bin/
mytool # Main entry point (executable, no extension)
lib/
logging.sh # Logging functions
utils.sh # Utility functions
test/
mytool.bats # bats test file
Makefile # Build and validation targets
.shellcheckrc # shellcheck configuration
.editorconfig # Editor and shfmt configuration
README.md # Project documentation
Before creating any files, validate the input:
For single script:
.sh or .bash, or have no extension (for bin/ scripts)..)For project:
Validation:
validate_script_name() {
local name="$1"
if [[ "$name" == *".."* ]]; then
die "Invalid name: contains path traversal"
fi
if [[ -e "$name" ]]; then
die "File already exists: $name"
fi
}
validate_project_name() {
local name="$1"
if [[ ! "$name" =~ ^[a-z][a-z0-9_-]*$ ]]; then
die "Invalid project name: must be lowercase, start with letter, contain only letters/numbers/hyphens/underscores"
fi
if [[ -d "$name" ]]; then
die "Directory already exists: $name"
fi
}
The --shell flag controls which shell dialect to generate:
--shell=bash (default): Bash 4+ with full features--shell=sh: POSIX sh compatible (no bashisms)POSIX sh restrictions (enforced in generated code):
[[ ]] (use [ ] with proper quoting)local keyword (widely supported but not strictly POSIX)pipefail option${BASH_SOURCE[0]} (use $0)[[ ]] (use case statements)<<< here-strings<() or >(){1..10} brace expansionFor --type=script (the default):
Write the script file:
Generate the appropriate template based on --shell (see templates above in Script Types section).
Set executable permissions:
chmod +x "$script_name"
Display success message:
Created: deploy.sh (bash script)
Next steps:
1. Edit deploy.sh to add your logic
2. Run: shellcheck deploy.sh
3. Run: ./deploy.sh --help
For --type=project:
Create directory structure:
mkdir -p "$name"/{bin,lib,test}
Generate bin/mytool (main entry point):
The main entry point in bin/ has no file extension. It sources libraries from lib/ and
implements the main logic.
#!/usr/bin/env bash
#
# mytool - [Brief description]
#
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LIB_DIR="$SCRIPT_DIR/../lib"
# shellcheck source=../lib/logging.sh
source "$LIB_DIR/logging.sh"
# shellcheck source=../lib/utils.sh
source "$LIB_DIR/utils.sh"
usage() {
cat <<'EOF'
Usage: mytool [OPTIONS] <command> [args...]
Commands:
run Execute the main operation
check Verify prerequisites
help Show this help message
Options:
-h, --help Show this help message
-v, --verbose Enable verbose output
--version Show version
EOF
}
VERSION="0.1.0"
main() {
local verbose=false
while (( $# > 0 )); do
case "$1" in
-h|--help) usage; exit 0 ;;
-v|--verbose) verbose=true; shift ;;
--version) echo "mytool $VERSION"; exit 0 ;;
--) shift; break ;;
-*) die "Unknown option: $1" ;;
*) break ;;
esac
done
local command="${1:-help}"
shift || true
case "$command" in
run) cmd_run "$@" ;;
check) cmd_check "$@" ;;
help) usage ;;
*) die "Unknown command: $command. Run 'mytool help' for usage." ;;
esac
}
cmd_run() {
log "Running main operation..."
# Implementation here
log "Done"
}
cmd_check() {
log "Checking prerequisites..."
require_cmd "curl"
require_cmd "jq"
log "All prerequisites met"
}
main "$@"
Generate lib/logging.sh:
#!/usr/bin/env bash
# logging.sh - Logging utility functions
#
# Source this file to get structured logging functions.
# All log output goes to stderr so stdout remains clean for data.
[[ -n "${_LOGGING_SH_LOADED:-}" ]] && return 0
_LOGGING_SH_LOADED=1
# Detect color support
if [[ -t 2 ]]; then
_LOG_RED='\033[0;31m'
_LOG_GREEN='\033[0;32m'
_LOG_YELLOW='\033[0;33m'
_LOG_BLUE='\033[0;34m'
_LOG_RESET='\033[0m'
else
_LOG_RED='' _LOG_GREEN='' _LOG_YELLOW='' _LOG_BLUE='' _LOG_RESET=''
fi
log() {
printf "${_LOG_BLUE}[%s]${_LOG_RESET} %s\n" "$(date '+%H:%M:%S')" "$*" >&2
}
warn() {
printf "${_LOG_YELLOW}[%s] WARN:${_LOG_RESET} %s\n" "$(date '+%H:%M:%S')" "$*" >&2
}
err() {
printf "${_LOG_RED}[%s] ERROR:${_LOG_RESET} %s\n" "$(date '+%H:%M:%S')" "$*" >&2
}
die() {
err "$@"
exit 1
}
success() {
printf "${_LOG_GREEN}[%s] OK:${_LOG_RESET} %s\n" "$(date '+%H:%M:%S')" "$*" >&2
}
Generate lib/utils.sh:
#!/usr/bin/env bash
# utils.sh - Common utility functions
#
# Source this file to get common helpers.
[[ -n "${_UTILS_SH_LOADED:-}" ]] && return 0
_UTILS_SH_LOADED=1
# Check if a command exists
require_cmd() {
local cmd="$1"
if ! command -v "$cmd" &>/dev/null; then
die "Required command not found: $cmd"
fi
}
# Confirm action with user
confirm() {
local prompt="${1:-Continue?}"
local default="${2:-n}"
if [[ "$default" == "y" ]]; then
printf '%s [Y/n] ' "$prompt" >&2
else
printf '%s [y/N] ' "$prompt" >&2
fi
local reply
read -r reply
reply="${reply:-$default}"
[[ "$reply" =~ ^[Yy]$ ]]
}
# Create a temporary directory with automatic cleanup
make_temp_dir() {
local prefix="${1:-tmp}"
local tmpdir
tmpdir=$(mktemp -d "${TMPDIR:-/tmp}/${prefix}.XXXXXX")
# Note: caller should set up trap for cleanup
echo "$tmpdir"
}
# Retry a command with backoff
retry() {
local max_attempts="${1:?max_attempts required}"
local delay="${2:?delay required}"
shift 2
local attempt=1
until "$@"; do
if (( attempt >= max_attempts )); then
err "Command failed after $max_attempts attempts: $*"
return 1
fi
warn "Attempt $attempt/$max_attempts failed, retrying in ${delay}s..."
sleep "$delay"
(( attempt++ ))
(( delay *= 2 ))
done
}
Generate test/mytool.bats:
#!/usr/bin/env bats
# Test suite for mytool
setup() {
SCRIPT_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
PATH="$SCRIPT_DIR/bin:$PATH"
}
@test "mytool --help exits 0" {
run mytool --help
[ "$status" -eq 0 ]
}
@test "mytool --help shows usage" {
run mytool --help
[ "$status" -eq 0 ]
[[ "${output}" == *"Usage:"* ]]
}
@test "mytool --version shows version" {
run mytool --version
[ "$status" -eq 0 ]
[[ "${output}" == *"0.1.0"* ]]
}
@test "mytool unknown command fails" {
run mytool nonexistent
[ "$status" -eq 1 ]
[[ "${output}" == *"Unknown command"* ]]
}
@test "mytool unknown option fails" {
run mytool --nonexistent
[ "$status" -eq 1 ]
[[ "${output}" == *"Unknown option"* ]]
}
Generate Makefile:
SHELL := /bin/bash
.DEFAULT_GOAL := help
SCRIPTS := $(shell find bin lib -type f -name '*.sh' -o -type f -executable 2>/dev/null)
.PHONY: help
help: ## Show this help
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-15s\033[0m %s\n", $$1, $$2}'
.PHONY: lint
lint: ## Run shellcheck
@shellcheck -x $(SCRIPTS)
.PHONY: format
format: ## Format with shfmt
@shfmt -w $(SCRIPTS)
.PHONY: format-check
format-check: ## Check formatting
@shfmt -d $(SCRIPTS)
.PHONY: test
test: ## Run bats tests
@bats test/
.PHONY: validate
validate: lint format-check test ## Run all checks
Generate .shellcheckrc:
# ShellCheck configuration
# https://www.shellcheck.net/wiki/
# Follow source directives
external-sources=true
# Default shell (can be overridden by shebang)
shell=bash
Generate .editorconfig:
# EditorConfig for shell scripts
# https://editorconfig.org/
root = true
[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
trim_trailing_whitespace = true
[*.{sh,bash}]
indent_style = space
indent_size = 4
[Makefile]
indent_style = tab
[*.bats]
indent_style = space
indent_size = 4
Set executable permissions:
chmod +x "$name/bin/mytool"
When --shell=sh is used with --type=project, the following changes apply:
#!/bin/sh instead of #!/usr/bin/env bash[[ ]], use [ ] with proper quoting${BASH_SOURCE[0]}, use $0# POSIX sh guard against double-source
if [ "${_LOGGING_SH_LOADED:-}" = "1" ]; then
return 0 2>/dev/null || true
fi
_LOGGING_SH_LOADED=1
.shellcheckrc sets shell=shgetopts instead of while+case with long optionsSingle script success:
Created: deploy.sh (bash script with main() pattern)
chmod +x deploy.sh (already done)
Next steps:
1. Edit deploy.sh to add your script logic
2. Validate: shellcheck deploy.sh && shfmt -d deploy.sh
3. Run: ./deploy.sh --help
Project success:
Created project: mytools/
mytools/
bin/mytool Main entry point
lib/logging.sh Logging functions
lib/utils.sh Utility functions
test/mytool.bats Test suite
Makefile Build targets
.shellcheckrc shellcheck config
.editorconfig Editor config
Next steps:
1. cd mytools
2. Edit bin/mytool to add commands
3. Run tests: make test
4. Validate: make validate
5. Try it: bin/mytool --help
Every generated script must include the appropriate safety preamble:
set -euo pipefail (always, no exceptions)set -eu (always, no exceptions)Never generate scripts without these safety settings.
Every generated script must use the main() pattern:
main "$@" at the bottom of the fileEvery generated script must include a cleanup function and EXIT trap, even if the cleanup function is initially empty. This establishes the pattern for developers to fill in.
Always set executable permissions on generated scripts. The user should never have to manually
chmod +x a scaffolded script.
Never overwrite existing files without explicit confirmation. If the target file or directory already exists, report the conflict and exit.
When --shell=sh is selected, the generated code must not contain ANY bashisms:
[[ ]] conditionalslocal keyword (though widely supported, avoid for strict POSIX)pipefail${BASH_SOURCE[0]}<<< here-strings<() or >() process substitution{1..10} brace expansion$(( )) with ++ or -- operators== in [ ] (use = instead)All generated code must pass shellcheck -x and shfmt -d immediately after creation. This is a
hard requirement. Never generate code with known shellcheck warnings.
Developer needs a one-off script for a deployment task.
ccfg shell scaffold deploy-staging.sh
Creates a single bash script with argument parsing, logging, and cleanup. Developer fills in the logic.
Project needs a portable installer that works on any Unix system.
ccfg shell scaffold install.sh --shell=sh
Creates a POSIX sh script that avoids all bashisms. Safe to run on Alpine, BusyBox, dash, and other minimal shells.
Team is building a multi-command shell tool.
ccfg shell scaffold myctl --type=project
Creates a full project with bin/, lib/, test/, Makefile, and configuration files. The main
entry point supports subcommands.
Developer wants to add a script to an existing project.
ccfg shell scaffold scripts/backup.sh
Creates the script in the specified path (creating parent directories if needed) without generating a full project structure.
Choose a different name or remove the existing file first. The scaffold command will never silently overwrite existing files.
Project names must start with a lowercase letter and contain only lowercase letters, numbers,
hyphens, and underscores. Examples: my-tool, deploy_utils, backup2.
This should never happen. Generated code must pass shellcheck. If it does, this is a bug in the scaffold command template.
The scaffold command runs chmod +x automatically. If permissions are wrong, check filesystem
restrictions (e.g., FAT32/exFAT filesystems do not support Unix permissions).
The scaffold command creates production-ready shell scripts and projects with safety defaults,
structured organization, and immediate shellcheck compliance. By enforcing the main() pattern, trap
cleanup, and strict mode, scaffolded scripts establish good habits from the first line of code.
Choose --shell=sh when portability across minimal Unix environments is required, and
--shell=bash (the default) when Bash 4+ features are needed.
npx claudepluginhub jsamuelsen11/claude-config --plugin ccfg-shell/bash-templateGenerates production-ready bash script templates for CLI tools, daemons, libraries, installers, or CI/CD pipelines with customizable options.
/shellCreates secure Bash shell scripts for a given purpose with strict error handling, input validation, security best practices, and cleanup. Optional robustness like 'production'.
/scaffoldScaffolds bash scripts for git workflows, testing, linting, coverage, and CI from claude-commands repo into project root and scripts/ directory, then verifies setup.
/scaffoldScaffolds production-ready projects from templates with Harness CI/CD integration, environment configuration (Docker, Kubernetes, Helm), git init, and post-generation setup.
/createCreates a new scaffold template in .scaffold/<name> with scaffold.yaml config, Go template files for project or template scaffolds, and validates via lint and dry-run generation.
/shellProvides shell configuration guide for bash/zsh covering startup files, environment variables, and options based on query like bash, zsh, startup.