/SKILL.md
Writes portable POSIX sh scripts with getoptions argument parsing, single-file builds, and production patterns (traps, temp files, logging, pipelines). Triggers: shell script, sh script, bash script, CLI tool, command-line tool, getoptions, argument parsing, Makefile recipe, cron job, init script, git hook, CI/CD shell step, deployment script, automation task. Do NOT activate for Python, Node.js, or other non-shell scripting tasks.
npx skillsauth add thesmart/shellcraft shellcraftInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Shell scripts and libraries SHALL:
mktemp is OK)Shell scripts and libraries SHALL NOT:
set -o pipefail (not POSIX-standard)Shell scripts SHALL:
#!/bin/sh shebang--help before all other args0 on success, 2 on bad arguments, 1 on all other failuresgetoptions to declare and validate arguments>&2) — include what failed and how to fix itShell scripts SHALL NOT:
Shell scripts SHOULD:
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" when sourcing relative filesShell libraries SHALL:
return, not exit — exit terminates the parent shell| Term | Canonical name | Definition |
| ------------------------------------------------- | ----------------------- | ----------------------------------------------------------------------------------------------------------------- |
| script, executable, utility | shell script | A directly-executable shell file: no extension, chmod ug+x. |
| library, lib, sourced file | shell lib | A .sh file meant to be imported or inlined by other scripts, not executed directly. |
| flags, options, arguments, parameters, CLI inputs | arguments | The command-line args declared in parser_definition() and parsed by getoptions. |
| vendor libs | vendor lib | A third-party shell file (.sh) under vendor/, provided as is for use by the skill. |
| vendor context | vendor context file | A Markdown (.md) file co-located with a vendor lib that documents its API. Read this instead of the lib source. |
snake_caseUPPER_SNAKE_CASEkebab-case (lowercase, hyphens)readonly for constants set once at startup| Code | Meaning |
| ----- | -------------------------------- |
| 0 | Success |
| 1 | General error |
| 2 | Usage / invalid arguments |
| 126 | Command found but not executable |
| 127 | Command not found |
| 130 | Interrupted (SIGINT / Ctrl-C) |
Best practices:
|| die "message" pattern for error propagationprintf '%s\n' "$msg" over echo "$msg" — echo behavior varies across shellsprintf '%s' (no newline) when building partial outputtrap handlers for cleanup and safetymktemp for in-progress work (prevents partial artifacts)chmod ug+x.sh file extension, no execute bitHere is a pseudo-grammar for style of CLI arguments:
args = [ command , " " , { subcommand , " " } ]
, { option , " " }
, { positional , " " }
, [ separator , " " , { positional } ] ;
option = short_group | long_option ;
short_group = "-" , SHORT_CHAR , { SHORT_CHAR }
, [ " " , value ] ;
long_option = "--" , LONG_NAME , [ "=" , value | " " , value ] ;
separator = "--"
command = TOKEN ;
subcommand = TOKEN ;
value = TOKEN ;
positional = TOKEN ;
(* The "--" is optional and rare. When present, it
terminates option parsing — everything after it
is collected as positional arguments in `$@`. *)
ALWAYS, EXPLICITLY validate file/directory args early with die, before real work. Skip
emptiness, symlinks, and extensions — focus on safety not business logic.
File input:
[ -e "$FILE" ] || die "file not found: $FILE"
[ -f "$FILE" ] || die "not a regular file: $FILE"
[ -r "$FILE" ] || die "file not readable: $FILE"
Directory input:
[ -e "$DIR" ] || die "directory not found: $DIR"
[ -d "$DIR" ] || die "not a directory: $DIR"
[ -r "$DIR" ] || die "directory not readable: $DIR"
[ -x "$DIR" ] || die "directory not traversable: $DIR"
File output:
_outdir="$(dirname "$FILE")"
[ -d "$_outdir" ] || die "parent directory not found: $_outdir"
[ -w "$_outdir" ] || die "parent directory not writable: $_outdir"
if [ -e "$FILE" ]; then
[ -f "$FILE" ] || die "not a regular file: $FILE"
[ -w "$FILE" ] || die "file not writable: $FILE"
fi
Directory output:
[ -e "$DIR" ] || die "directory not found: $DIR"
[ -d "$DIR" ] || die "not a directory: $DIR"
[ -w "$DIR" ] || die "directory not writable: $DIR"
External utilities from POSIX.1-2017 (IEEE Std 1003.1-2017), Shell & Utilities volume,
Chapter 4. These are assumed to be present on the system and may be used freely without a
command -v guard.
awk: pattern scanning and text processingbasename: return non-directory portion of pathnamebc: arbitrary precision calculator languagecat: concatenate and print fileschgrp: change file group ownershipchmod: change file modeschown: change file ownershipcksum: write file checksums and sizescmp: compare two files byte-by-bytecomm: select or reject lines common to two sorted filescommand: identify a command typecp: copy filescsplit: split files based on contextcut: extract fields/columns from linesdate: print or set the system date and timedd: convert and copy a filedf: report free disk spacediff: compare files line by linedirname: return directory portion of pathnamedu: estimate file space usageenv: set environment and execute a commandexpand: convert tabs to spacesexpr: evaluate expressions (prefer $(( )) for arithmetic)false: return false value (exit 1)file: determine file typefind: find filesfold: fold long lines for finite width outputgetconf: get system configuration valuesgrep: search file contents for patternshead: output first part of filesiconv: codeset conversionid: print user and group IDsjoin: relational join of two sorted fileskill: terminate or signal a processlink: create a hard link to a fileln: link fileslocale: get locale-specific informationlogger: log messages to the system loglogname: return the user's login namels: list directory contentsmkdir: make directoriesmkfifo: make FIFO special files (named pipes)mktemp: create a temporary file or directory (not POSIX — see notes below)mv: move filesnice: invoke a utility with an altered nice valuenl: line numbering filternohup: invoke a utility immune to hangupsod: dump files in various formatspaste: merge corresponding lines of filespatch: apply changes to filespathchk: check pathnamesprintf: format and print dataps: report process statusrenice: set nice values of running processesrm: remove directory entriesrmdir: remove directoriessed: stream editorsh: shell, the standard command language interpretersleep: suspend execution for an intervalsort: sort lines of textsplit: split files into piecesstrings: find printable strings in filesstty: set the options for a terminaltail: output last part of filestee: duplicate stdin to stdout and a filetouch: change file access and modification timestput: terminal capability queries (colors, cursor)tr: translate or delete characterstrue: return true value (exit 0)tsort: topological sorttty: return user's terminal nametype: check how a name would be interpreteduname: print system informationunexpand: convert spaces to tabsuniq: report or filter out repeated linesunlink: call the unlink functionwc: count lines, words, and bytesxargs: build and execute commands from stdinALWAYS verify non-standard utilities (not on the list above) before using:
command -v jq >/dev/null || die "jq is required but not found"
Utilities with common non-portable pitfalls:
date+%s (epoch seconds) is not POSIX. GNU uses -d, BSD uses -j -f for date parsing. Stick to
format strings like +%Y-%m-%d or +%H:%M:%S.
echoBehavior of echo varies across shells and platforms — backslash interpretation and -n flag
handling are not standardized. Prefer printf '%s\n' for reliable output:
# bad: may interpret escapes or not, depending on platform
echo "value is\t$val"
# good: consistent everywhere
printf 'value is\t%s\n' "$val"
findPOSIX-safe primaries: -name, -type, -exec {} \;, -newer, -perm, -print.
Not POSIX: -maxdepth, -mindepth, -print0, -regex, -delete. Use -prune to limit
depth:
# equivalent to -maxdepth 1
find . ! -name . -prune -type f
grepBRE (basic regular expressions) by default. Use -E for extended regex. No -P (Perl regex)
in POSIX. Use -q for silent match testing:
echo "$line" | grep -qE '^[0-9]+$' || die "not a number"
lsNever parse ls output — filenames with spaces, newlines, or special characters will break it.
Use find or shell glob patterns instead:
# bad: parsing ls
for f in $(ls *.txt); do ...
# good: glob
for f in *.txt; do
[ -f "$f" ] || continue
...
done
mktempNot in any POSIX spec but ubiquitous across GNU coreutils, BSD, and BusyBox. mktemp and
mktemp -d work on all major implementations. Avoid -t with a template — the syntax differs
between GNU and BSD. Assumed available because there is no portable alternative for safe temp file
creation.
readOnly -r is POSIX (disable backslash escaping). Always use read -r.
Not portable: -n, -t, -p, -a, -s, -d.
sedBRE by default. Use -E for extended regex (POSIX 2024, widely supported).
-i (in-place) is not POSIX. Use a temp file:
sed 's/old/new/g' "$file" > "$file.tmp" && mv "$file.tmp" "$file"
sleepFractional seconds (sleep 0.5) are not POSIX — only integer seconds are guaranteed.
sortPOSIX-safe flags: -n, -r, -k, -t, -u, -f, -b.
Not POSIX: -V (version sort), -z (null delimiter), -h (human numeric).
test / [ ]Use = not == for string comparison. -a (and) and -o (or) are obsolescent — chain separate
tests instead:
# bad
[ -f "$f" -a -r "$f" ]
# good
[ -f "$f" ] && [ -r "$f" ]
trUse POSIX character classes: [:upper:], [:lower:], [:digit:], [:alpha:], [:space:].
echo "$val" | tr '[:lower:]' '[:upper:]'
xargs-0 (null delimiter) is not POSIX. Use newline-delimited input or find -exec instead.
Read these additional references only when the script requires them:
--verbose, --quiet, timestamped
logs, or structured stderr outputmktemp, writes
intermediate files, or must write output atomicallyxargs/FIFOscase patterns, regex, here-docs)SCRIPT_DIRAll conventions and utilities target POSIX.1-2017 (IEEE Std 1003.1-2017), Shell & Utilities volume.
Follow these steps to author the target script.
Define the goal clearly
Identify the target script's arguments and outputs.
Consider edge cases and failure modes.
Outline the control flow.
Decide if the target script needs parameter parsing:
getoptions-3.3.2.shusage() manuallyDecide the dependency bundling strategy (when using vendor libs):
getoptions-3.3.2.sh + getoptions-3.3.2.md to shell_modules/
alongside the script. Add to VCS; shared by all scripts in that directory.vendor/inline. Use when the script
must be self-contained (distributed binary, curl | sh, CI without a repo).Infer the right strategy automatically: portability/distribution → inlined; project repo → bundled; minimal options → none. If genuinely unclear, ask the end-user.
Write an example of how an end-user would use the target script to accomplish the goal.
Evaluate the plan and ensure the it aligns with end-user's stated intent.
Follow the plan and write the script using the appropriate template. Make the script executable
(chmod ug+x) if needed.
Target script has flags, named params, or subcommands — use getoptions-3.3.2.sh (vendor context
file: vendor/getoptions-3.3.2.md):
#!/bin/sh
# --- helpers ---
die() { printf '%s\n' "error: $*" >&2; exit 1; }
# --- imports (see: reference/imports.md) ---
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
. "${SCRIPT_DIR}/shell_modules/getoptions-3.3.2.sh" # @inline getoptions
# --- getoptions ---
parser_definition() {
# Replace {{SCRIPT_NAME}} with the script's file name
setup REST help:usage -- "Usage: {{SCRIPT_NAME}} [options] [arguments]"
msg -- 'Options:'
disp :usage -h --help
}
# If script requires arguments, uncomment next line:
# [ $# -eq 0 ] && set -- --help
eval "$(getoptions parser_definition - "$0") die 'failed to parse arguments'"
# --- script ---
# WRITE SCRIPT HERE
Script takes only positional arguments — no import needed, define help manually:
#!/bin/sh
# --- helpers ---
die() { printf '%s\n' "error: $*" >&2; exit 1; }
usage() {
printf 'Usage: {{SCRIPT_NAME}} <arg1> [arg2]\n'
printf '\n'
printf 'Description of what the script does.\n'
}
case ${1-} in --help|-h) usage; exit 0 ;; esac
# --- script ---
# WRITE SCRIPT HERE
sh -n script-name--help output is clear and complete.mktemp.Fix any issues encountered and try again.
Skip this step for shell libs. Execute the strategy chosen in Step 1:
bundled — copy vendor libs and their context files to shell_modules/:
cp vendor/getoptions-3.3.2.sh shell_modules/
cp vendor/getoptions-3.3.2.md shell_modules/
The shell_modules/ directory is a sibling of the target script. The .md context file is
essential — it lets future agents understand the dependency without access to the shellcraft skill.
Commit shell_modules/ to VCS alongside the script.
inlined — embed all @inline-tagged imports into a distributable single-file build:
vendor/inline --target path/to/script --inline path/to/lib.sh --key <key> --overwrite
none — no build step needed.
Read $PATH and $HOME. Select two or three of the most suitable locations for potentially
installing the script. Prefer at least one path in $HOME. Ask the end-user if they would like to
install the script, provide options with rationale for why that might be a good location.
tools
Use when work should span one or more detached tasks but still behave like one job with a single owner context. TaskFlow is the durable flow substrate under authoring layers like Lobster, ACPX, plugins, or plain code. Keep conditional logic in the caller; use TaskFlow for flow identity, child-task linkage, waiting state, revision-checked mutations, and user-facing emergence.
tools
# Lobster Lobster executes multi-step workflows with approval checkpoints. Use it when: - User wants a repeatable automation (triage, monitor, sync) - Actions need human approval before executing (send, post, delete) - Multiple tool calls should run as one deterministic operation ## When to use Lobster | User intent | Use Lobster? | | ------------------------------------------------------ | --------------------------
tools
# Lobster Lobster executes multi-step workflows with approval checkpoints. Use it when: - User wants a repeatable automation (triage, monitor, sync) - Actions need human approval before executing (send, post, delete) - Multiple tool calls should run as one deterministic operation ## When to use Lobster | User intent | Use Lobster? | | ------------------------------------------------------ | --------------------------
tools
A CLI tool for making authenticated requests to the X (Twitter) API. Use this skill when you need to post tweets, reply, quote, search, read posts, manage followers, send DMs, upload media, or interact with any X API v2 endpoint.