@objctp/opencode-shell-routines 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +114 -0
  3. package/agents/shell-architect.md +88 -0
  4. package/agents/shell-expert.md +60 -0
  5. package/commands/shell-audit.md +47 -0
  6. package/commands/shell-batch-exec.md +48 -0
  7. package/commands/shell-new.md +57 -0
  8. package/commands/shell-routines-setup.md +66 -0
  9. package/commands/shell-test-run.md +46 -0
  10. package/opencode.json +19 -0
  11. package/package.json +34 -0
  12. package/plugins/shell-hooks.ts +150 -0
  13. package/scripts/lib-batch.sh +297 -0
  14. package/scripts/lib-common.sh +332 -0
  15. package/skills/shell-batch-operations/SKILL.md +97 -0
  16. package/skills/shell-batch-operations/assets/batch-template.sh +124 -0
  17. package/skills/shell-batch-operations/examples/data-pipeline.sh +157 -0
  18. package/skills/shell-batch-operations/examples/file-batch.sh +140 -0
  19. package/skills/shell-batch-operations/references/decision-tree.md +53 -0
  20. package/skills/shell-best-practices/SKILL.md +313 -0
  21. package/skills/shell-best-practices/assets/library.sh +142 -0
  22. package/skills/shell-best-practices/assets/minimal.sh +54 -0
  23. package/skills/shell-best-practices/assets/posix.sh +180 -0
  24. package/skills/shell-best-practices/assets/standard.sh +203 -0
  25. package/skills/shell-best-practices/references/patterns.md +386 -0
  26. package/skills/shell-best-practices/references/security.md +195 -0
  27. package/skills/shell-debugging/SKILL.md +115 -0
  28. package/skills/shell-debugging/examples/debug-session.md +165 -0
  29. package/skills/shell-debugging/references/debugging-guide.md +336 -0
  30. package/skills/shell-profiling/SKILL.md +154 -0
  31. package/skills/shell-profiling/examples/profile-session.md +225 -0
  32. package/skills/shell-profiling/references/optimisation-patterns.md +373 -0
  33. package/skills/shell-profiling/references/profiling-tools.md +318 -0
  34. package/skills/shell-profiling/scripts/bench.sh +82 -0
  35. package/skills/shell-profiling/scripts/trace-aggregate.sh +34 -0
  36. package/skills/shell-review/SKILL.md +61 -0
  37. package/skills/shell-review/examples/sample-review.md +42 -0
  38. package/skills/shell-review/references/guidelines.md +48 -0
  39. package/skills/shell-review/references/review-template.md +56 -0
  40. package/skills/shell-security/SKILL.md +128 -0
  41. package/skills/shell-security/examples/dangerous-command-review.md +231 -0
  42. package/skills/shell-security/examples/secure-script-example.sh +317 -0
  43. package/skills/shell-security/references/dangerous-commands.md +561 -0
  44. package/skills/shell-security/references/security-patterns.md +30 -0
  45. package/skills/shell-security/references/sensitive-files.md +525 -0
  46. package/skills/shell-security/scripts/security-audit.sh +208 -0
  47. package/skills/shell-test/SKILL.md +237 -0
  48. package/skills/shell-test/examples/test-example.md +74 -0
  49. package/skills/shell-test/references/advanced-patterns.md +52 -0
  50. package/skills/shell-test/references/assertions.md +184 -0
  51. package/skills/shell-test/references/test-template.md +60 -0
  52. package/skills/shell-test/scripts/public-coverage.sh +93 -0
@@ -0,0 +1,140 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Example: Batch file processing
4
+ # Description: Find all .txt files, count lines in each, return summary statistics
5
+ # Usage: ./file-batch.sh [directory]
6
+ #
7
+ # shellcheck disable=SC1091 # dynamic source paths resolved at runtime
8
+ # shellcheck disable=SC2034
9
+
10
+ set -euo pipefail
11
+
12
+ SCRIPT_NAME="${0##*/}"
13
+
14
+ # Source batch utilities
15
+ if [[ -n "${CLAUDE_PLUGIN_ROOT:-}" ]]; then
16
+ source "${CLAUDE_PLUGIN_ROOT}/scripts/lib-batch.sh"
17
+ elif [[ -f "$(dirname "$0")/../../scripts/lib-batch.sh" ]]; then
18
+ source "$(dirname "$0")/../../scripts/lib-batch.sh"
19
+ else
20
+ echo "Error: Cannot find lib-batch.sh" >&2
21
+ exit 2
22
+ fi
23
+
24
+ # Configuration
25
+ SEARCH_DIR="${1:-.}"
26
+ MAX_SIZE="${MAX_SIZE:-10485760}" # 10MB default max file size
27
+
28
+ # Process a single text file
29
+ function process_file() {
30
+ local file="$1"
31
+ local lines
32
+ local size
33
+ local filename
34
+
35
+ filename="${file##*/}"
36
+ lines=$(wc -l <"$file" 2>/dev/null)
37
+ lines="${lines// /}"
38
+ size=$(wc -c <"$file" 2>/dev/null)
39
+ size="${size// /}"
40
+
41
+ printf '%s|%s|%s\n' "$filename" "$lines" "$size"
42
+ }
43
+
44
+ # Main processing
45
+ function main() {
46
+ declare -A RESULTS
47
+ declare -a METADATA
48
+ declare -a ERRORS
49
+
50
+ # Metadata
51
+ batch_add_metadata METADATA "script" "$SCRIPT_NAME"
52
+ batch_add_metadata METADATA "search_dir" "$SEARCH_DIR"
53
+ batch_add_metadata METADATA "started" "$(date -Iseconds)"
54
+
55
+ batch_progress "Searching for .txt files in: ${SEARCH_DIR}"
56
+
57
+ # Variables for statistics
58
+ local file_count=0
59
+ local total_lines=0
60
+ local total_size=0
61
+ local largest_file=""
62
+ local largest_lines=0
63
+ local smallest_file=""
64
+ local smallest_lines=""
65
+ local first_file=true
66
+
67
+ # Process files
68
+ local temp_file
69
+ temp_file=$(mktemp)
70
+ trap 'rm -f "$temp_file"' EXIT
71
+
72
+ while IFS= read -r -d '' file; do
73
+ batch_progress "Found: ${file}"
74
+
75
+ # Check file size
76
+ local file_size
77
+ file_size=$(wc -c <"$file" 2>/dev/null)
78
+ file_size="${file_size// /}"
79
+
80
+ if ((file_size > MAX_SIZE)); then
81
+ batch_add_error ERRORS "File too large, skipping: ${file} (${file_size} bytes)"
82
+ continue
83
+ fi
84
+
85
+ # Process file
86
+ if output=$(process_file "$file"); then
87
+ echo "$output" >>"$temp_file"
88
+ ((file_count++))
89
+ else
90
+ batch_add_error ERRORS "Failed to process: ${file}"
91
+ fi
92
+ done < <(find "$SEARCH_DIR" -name "*.txt" -print0 2>/dev/null)
93
+
94
+ batch_progress "Processing statistics from ${file_count} files"
95
+
96
+ # Calculate statistics
97
+ while IFS='|' read -r filename lines size; do
98
+ total_lines=$((total_lines + lines))
99
+ total_size=$((total_size + size))
100
+
101
+ # Track largest/smallest
102
+ if ((lines > largest_lines)); then
103
+ largest_lines=$lines
104
+ largest_file="$filename"
105
+ fi
106
+
107
+ if $first_file || ((lines < smallest_lines)); then
108
+ first_file=false
109
+ smallest_lines=$lines
110
+ smallest_file="$filename"
111
+ fi
112
+ done <"$temp_file"
113
+
114
+ # Calculate averages
115
+ local avg_lines=0
116
+ local avg_size=0
117
+ if ((file_count > 0)); then
118
+ avg_lines=$((total_lines / file_count))
119
+ avg_size=$((total_size / file_count))
120
+ fi
121
+
122
+ # Store results
123
+ batch_add_result RESULTS "file_count" "$file_count"
124
+ batch_add_result RESULTS "total_lines" "$total_lines"
125
+ batch_add_result RESULTS "total_size" "$total_size"
126
+ batch_add_result RESULTS "avg_lines" "$avg_lines"
127
+ batch_add_result RESULTS "avg_size" "$avg_size"
128
+ batch_add_result RESULTS "largest_file" "$largest_file"
129
+ batch_add_result RESULTS "largest_lines" "$largest_lines"
130
+ batch_add_result RESULTS "smallest_file" "$smallest_file"
131
+ batch_add_result RESULTS "smallest_lines" "$smallest_lines"
132
+
133
+ # Complete metadata
134
+ batch_add_metadata METADATA "completed" "$(date -Iseconds)"
135
+
136
+ # Output JSON
137
+ batch_output RESULTS METADATA ERRORS
138
+ }
139
+
140
+ main "$@"
@@ -0,0 +1,53 @@
1
+ # Batch vs Individual Operations Decision Tree
2
+
3
+ Visual guide for deciding when to write batch scripts vs using individual tool calls.
4
+
5
+ ## Decision Flowchart
6
+
7
+ 1. **1-2 operations** → Individual calls
8
+ 2. **10+ operations** → Batch script
9
+ 3. **3-10 operations** → Evaluate:
10
+ - Same operation on different inputs → Batch script
11
+ - Different operations + interactive → Individual calls
12
+ - Different operations + non-interactive → Batch script
13
+
14
+ ## Decision Matrix
15
+
16
+ | Scenario | Operations | Data Type | Interactive | Recommendation |
17
+ | --------------------------------- | ---------- | --------- | ----------- | ---------------- |
18
+ | Count files in directory | 1 | N/A | No | Individual call |
19
+ | Find and count lines in each .txt | 2-N | Files | No | **Batch script** |
20
+ | Debug failing deployment | 3+ | Commands | Yes | Individual calls |
21
+ | Rename 100 files by pattern | N | Files | No | **Batch script** |
22
+ | Check service status | 1 | N/A | Maybe | Individual call |
23
+ | Extract, transform, load data | 3+ | Pipeline | No | **Batch script** |
24
+ | Generate 10 reports | N | Files | No | **Batch script** |
25
+
26
+ ## Key Decision Factors
27
+
28
+ ### 1. Number of Operations
29
+
30
+ - **1-2 operations**: Individual calls (overhead of script not worth it)
31
+ - **3-10 operations**: Depends on other factors
32
+ - **10+ operations**: Almost always batch
33
+
34
+ ### 2. Operation Similarity
35
+
36
+ - **Same operation, different inputs**: Batch (file processing, bulk operations)
37
+ - **Different operations**: Depends on complexity and interactivity
38
+
39
+ ### 3. Interactivity Needs
40
+
41
+ - **Need to see results between steps**: Individual calls
42
+ - **Can proceed without human input**: Batch
43
+ - **Debugging/diagnosing**: Individual calls
44
+
45
+ ### 4. Token Constraints
46
+
47
+ - **Constrained context**: Batch (saves up to 98.7% tokens)
48
+ - **Plenty of context**: Either approach works
49
+
50
+ ### 5. Error Handling
51
+
52
+ - **Operations may fail unpredictably**: Individual calls for diagnosis
53
+ - **Predictable operations with known error modes**: Batch with error collection
@@ -0,0 +1,313 @@
1
+ ---
2
+ name: shell-best-practices
3
+ description: Write secure, portable bash scripts with proper structure, error handling, and quoting. Use when scaffolding a new script ("write a bash script", "scaffold", "new shell file") or modifying, auditing, or hardening an existing one ("fix this script", "refactor bash", "add error handling to"). Covers bash functions, helpers, deployment scripts, and file operations.
4
+ allowed-tools: Read, Write, Edit, Bash
5
+ argument-hint: [script-name-or-path]
6
+ ---
7
+
8
+ # Shell Best Practices Skill
9
+
10
+ Guides Claude to write secure, portable, well-structured bash scripts — and to scaffold new scripts from the right template when starting from scratch.
11
+
12
+ ## When This Skill Applies
13
+
14
+ - Writing or modifying any shell script
15
+ - Creating a new bash script ("create a script", "new bash file", "scaffold", "script template")
16
+ - Improving or auditing existing shell code
17
+ - Writing bash functions, helpers, or libraries
18
+
19
+ ## Two Modes
20
+
21
+ **Mode A — Modify/improve**: Apply the core standards below to the existing script at `$ARGUMENTS`.
22
+
23
+ **Mode B — New script**: Select the appropriate template (see Templates section), create the file at `$ARGUMENTS` (see naming rules in Scaffolding Process), fill in the placeholders, then apply core standards.
24
+
25
+ ---
26
+
27
+ ## Core Standards
28
+
29
+ Every script must include:
30
+
31
+ ```bash
32
+ #!/usr/bin/env bash
33
+ set -euo pipefail
34
+ ```
35
+
36
+ - **Shebang**: `#!/usr/bin/env bash` — not `/bin/bash`
37
+ - **Strict mode**: `set -euo pipefail` — catches unbound variables, failed commands, and pipe errors
38
+ - **Strict mode caveat**: `set -e` does not exit on failures inside `if`/`while` conditions, `&&`/`||` chains, subshells, or negated commands. Use explicit exit-code checks or `trap ERR` for reliable error handling:
39
+ ```bash
40
+ trap 'echo "Error at line $LINENO" >&2; exit 1' ERR
41
+ ```
42
+
43
+ ### Quoting
44
+
45
+ - Always quote variable expansions: `"$var"`, `"${array[@]}"`
46
+ - Quote command substitutions: `"$(cmd)"`
47
+ - Never leave variables unquoted in command positions
48
+
49
+ ### Naming Conventions
50
+
51
+ - Local variables: `lower_case_with_underscores`
52
+ - Constants and globals/exports: `UPPERCASE_WITH_UNDERSCORES`
53
+ - Public functions: `<namespace>::function_name` — use `shroutines::` for plugin-internal scripts, or the project name (e.g. `myapp::`) for project-specific scripts
54
+ - Private functions: `_function_name` — leading underscore signals internal use; not part of any public API
55
+ - Use `local -r` for constants inside functions — scopes the variable and protects it. Use `readonly` for script-level constants (maximum portability). Use `declare -r` at the top level only when you need type flags (`-i`, `-a`, `-lx`). The meaningful distinction is `local -r` (scoped) vs `readonly`/`declare -r` (global):
56
+
57
+ ```bash
58
+ # Script-level — readonly for portability
59
+ SCRIPT_NAME=$(basename "$0")
60
+ readonly SCRIPT_NAME
61
+ readonly VERSION="0.1.0"
62
+
63
+ # Script-level with type flag — declare -r
64
+ declare -ar EXIT_CODES=(["ok"]=0 ["error"]=1 ["usage"]=2)
65
+
66
+ # Function-scoped — local -r
67
+ _parse_args() {
68
+ local -r max_retries=3
69
+ }
70
+
71
+ # Public function — shroutines:: namespace
72
+ shroutines::process_file() {
73
+ local input="$1"
74
+ # ...
75
+ }
76
+ ```
77
+
78
+ ### Functions
79
+
80
+ > Replace the `shroutines::` prefix with the target project's namespace when scaffolding scripts for external projects.
81
+
82
+ ```bash
83
+ shroutines::process_file() {
84
+ local input_path="$1"
85
+
86
+ if [[ ! -r "$input_path" ]]; then
87
+ echo "Error: cannot read file: $input_path" >&2
88
+ return 1
89
+ fi
90
+
91
+ # logic here
92
+
93
+ return 0
94
+ }
95
+ ```
96
+
97
+ - Use `local` for all function-scoped variables
98
+ - **Separate declaration and assignment** when the value comes from a command substitution — `local` does not propagate the exit code:
99
+
100
+ ```bash
101
+ # BAD - $? is always 0 (exit code of 'local', not my_func)
102
+ local my_var="$(my_func)"
103
+ (( $? == 0 )) || return
104
+
105
+ # GOOD - separate lines preserve the exit code
106
+ local my_var
107
+ my_var="$(my_func)"
108
+ (( $? == 0 )) || return
109
+ ```
110
+
111
+ - End functions with explicit `return 0` — makes success exit point visible and distinguishes "fell off the end" from "deliberately succeeded"
112
+ - Use `[[ ]]` for bash tests, `[ ]` for POSIX sh
113
+ - Errors go to stderr: `>&2`
114
+ - Return meaningful exit codes: 0 = success, 1 = error, 2 = misuse
115
+
116
+ ### File Structure
117
+
118
+ Every script must follow this top-to-bottom order for any sections that are present:
119
+
120
+ 1. **Shebang and strict mode** — `#!/usr/bin/env bash` + `set -euo pipefail`
121
+ 2. **Constants** — `readonly` / `declare -r` values that never change
122
+ 3. **Globals** — `UPPERCASE` variables with script-wide scope
123
+ 4. **Private functions** — `_function_name` helpers, internal utilities
124
+ 5. **Public functions** — `shroutines::function_name` entry points and API
125
+ 6. **Guard and execution** — `BASH_SOURCE[0]` guard + `main "$@"`
126
+
127
+ ```bash
128
+ #!/usr/bin/env bash
129
+ set -euo pipefail
130
+
131
+ readonly VERSION="0.1.0"
132
+
133
+ VERBOSE=0
134
+ OUTPUT_FILE=""
135
+
136
+ _log_info() { printf '[INFO] %s\n' "$*" >&2; return 0; }
137
+
138
+ shroutines::process() {
139
+ local input="$1"
140
+ return 0
141
+ }
142
+
143
+ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
144
+ main "$@"
145
+ fi
146
+ ```
147
+
148
+ ### Line Length
149
+
150
+ - Soft limit: **120 characters** per line
151
+ - Break long strings, pipelines, or argument lists across lines
152
+ - Prefer `printf` over `echo` for multi-line output
153
+
154
+ - **Do not use `&& ... || ...` as if/else** — if the middle command fails, the `||` branch runs even though the `&&` condition succeeded:
155
+
156
+ ```bash
157
+ # BAD - rollback runs if deploy succeeds but healthcheck fails
158
+ deploy && healthcheck || rollback
159
+
160
+ # GOOD - explicit if/else
161
+ if deploy && healthcheck; then
162
+ echo "Deploy succeeded"
163
+ else
164
+ rollback
165
+ fi
166
+ ```
167
+
168
+ ### Comment Conventions
169
+
170
+ Only comment when the code itself cannot convey the information: a hidden constraint, a subtle invariant, a bug workaround, or behaviour that would surprise a careful reader. Write one explaining _why_ not _what_. If removing the comment wouldn't confuse a future reader, don't write it.
171
+
172
+ **File header** — every script starts with one:
173
+
174
+ ```bash
175
+ #!/usr/bin/env bash
176
+ #
177
+ # [BRIEF DESCRIPTION OF WHAT THIS SCRIPT DOES]
178
+ # Usage: [SCRIPT_NAME] [ARGUMENTS]
179
+ #
180
+ ```
181
+
182
+ **Section dividers** — only when an individual section exceeds ~50 lines or complexity makes structure worth signposting. The standard file structure ordering is self-evident; dividers between short sections add noise. `#` tail fills to the nearest of 40, 80, or 120 columns:
183
+
184
+ ```bash
185
+ ###
186
+ ### :::: [description] :::: ###########
187
+ ###
188
+ ```
189
+
190
+ **Public function docs** — only when the function's name and arguments don't fully convey its contract: non-obvious return codes, argument constraints, side effects, or failure conditions. Public (`shroutines::`) functions only; never on private (`_`) helpers.
191
+
192
+ ```bash
193
+ # [description]
194
+ # Arguments:
195
+ # $1 - [name]: [description]
196
+ # $2 - [name]: [description]
197
+ # Returns:
198
+ # 0 - [success description]
199
+ # 1 - [failure description]
200
+ ```
201
+
202
+ **Inline comments** — trailing `#` on the same line:
203
+
204
+ ```bash
205
+ # GOOD — explains why
206
+ local -r threshold=$((mem_total / 10)) # 10% of total memory
207
+
208
+ # BAD — restates what
209
+ local -r threshold=$((mem_total / 10)) # calculate threshold
210
+ ```
211
+
212
+ **Annotation comments** — bare `#` line above a block:
213
+
214
+ ```bash
215
+ # Track descriptors so the trap can close them even if the list changes later
216
+ exec 3>/var/log/daemon.log
217
+ exec 4>&1
218
+ _OPEN_FDS+=(3 4)
219
+ ```
220
+
221
+ ### Security Prohibitions
222
+
223
+ - **Never use `eval`** — command injection risk
224
+ - **Never pipe to `sh` or `bash`** — injection risk
225
+
226
+ For input validation, secrets handling, temp-file hygiene, and file permissions, see `references/security.md`.
227
+
228
+ ---
229
+
230
+ ## Templates (Mode B — New Scripts)
231
+
232
+ Choose based on complexity and purpose:
233
+
234
+ | Template | Use When |
235
+ | -------------------- | ---------------------------------------------------------------------------- |
236
+ | `assets/standard.sh` | Most scripts — argument parsing, error handling, direct execution |
237
+ | `assets/minimal.sh` | Simple one-task utilities, no complex flag parsing |
238
+ | `assets/library.sh` | Sourced by other scripts; provides reusable functions, no direct execution |
239
+ | `assets/posix.sh` | POSIX sh — containers, embedded, Alpine, CI base images, maximum portability |
240
+
241
+ ### Template Selection Guide
242
+
243
+ - Does it need `--flag` style options or multiple arguments? → **standard**
244
+ - Is it a short utility doing one thing? → **minimal**
245
+ - Will other scripts `source` it? → **library**
246
+ - Must run in containers, embedded, Alpine, or under dash? → **posix**
247
+
248
+ ### Scaffolding Process
249
+
250
+ 1. Determine script name from `$ARGUMENTS` or ask the user
251
+ 2. Select template type based on purpose
252
+ 3. Create file at `$ARGUMENTS` — for directly executed scripts, omit the `.sh` extension (e.g. `deploy` not `deploy.sh`); for libraries meant to be sourced, keep the `.sh` extension (e.g. `lib-common.sh`)
253
+ 4. Copy template content and fill in placeholders:
254
+ - Script description
255
+ - Usage examples
256
+ - Function implementations
257
+ 5. Apply all core standards above
258
+ 6. If POSIX portability is required: use the POSIX template instead, apply `#!/bin/sh` shebang, and follow the POSIX sh Feature Restrictions below. Run `checkbashisms` to verify the final result
259
+
260
+ **Done when:** the automated ShellCheck and shfmt hooks report clean on the scaffolded file — and `checkbashisms` for `#!/bin/sh` scripts. Resolve every finding before considering the script complete.
261
+
262
+ ---
263
+
264
+ ## POSIX vs Bash
265
+
266
+ This plugin targets **Bash 4.4+** as its primary compatibility tier. POSIX sh is a secondary tier for maximum portability.
267
+
268
+ **Rule: If the shebang says `#!/bin/sh`, the script must contain zero bashisms.** Use `#!/usr/bin/env bash` if you need bash features. The hook pipeline detects the shebang and configures ShellCheck, shfmt, and checkbashisms accordingly.
269
+
270
+ ### Shebang Discipline
271
+
272
+ | Shebang | Meaning | Tooling |
273
+ | --------------------- | -------------------------------- | -------------------------------------------------------- |
274
+ | `#!/usr/bin/env bash` | Bash-specific. Bashisms allowed. | shellcheck `-s bash`, shfmt `-ln bash`, `bash -n` |
275
+ | `#!/bin/sh` | POSIX sh only. No bash features. | shellcheck `-s sh`, shfmt `-ln posix`, `checkbashisms` |
276
+ | `#!/usr/bin/dash` | Explicit dash. Same as POSIX sh. | shellcheck `-s dash`, shfmt `-ln posix`, `checkbashisms` |
277
+
278
+ ### When to Choose POSIX sh
279
+
280
+ Choose `#!/bin/sh` when:
281
+
282
+ - The script runs in containers with minimal base images (Alpine, distroless)
283
+ - It must execute on embedded systems or CI runners with no bash
284
+ - It is a lightweight utility (init script, hook, wrapper) that doesn't need arrays or complex string manipulation
285
+
286
+ Choose `#!/usr/bin/env bash` when:
287
+
288
+ - You need arrays, associative arrays, or pattern matching with `[[ ]]`
289
+ - The script does complex string manipulation, argument parsing, or data processing
290
+ - It sources a library (the library template uses namerefs and associative arrays)
291
+
292
+ For POSIX sh scripts, ensure no bashisms are present. Common traps: `[[ ]]`, arrays, `${var,,}`, `<<<`, `source`, `function` keyword, `echo -e`. Use `checkbashisms` to verify.
293
+
294
+ When the user specifies POSIX portability, use `assets/posix.sh` instead of the bash templates.
295
+
296
+ ---
297
+
298
+ ## Reference Files
299
+
300
+ - `references/patterns.md` — Argument parsing, temp files, arrays, string manipulation, parallel processing, progress output, exit code handling
301
+ - `references/security.md` — Preventive security patterns for writing: injection prevention, input validation, temp files, signal handling
302
+ - `${CLAUDE_PLUGIN_ROOT}/scripts/lib-common.sh` — General-purpose runtime library (logging, validation, temp files, string/array utilities), all functions `shroutines::`-namespaced. Source directly when the script can depend on the plugin being installed
303
+
304
+ Always consult these reference files before producing output — both for reviewing existing scripts and scaffolding new ones.
305
+
306
+ ---
307
+
308
+ ## Integration
309
+
310
+ - **`shell-security`** — Destructive commands, credential exposure, system file risks
311
+ - **`shell-review`** — Structured quality review of a completed script
312
+ - **`shell-architect`** agent — Multi-file project design, performance decisions, library vs executable structure
313
+ - **Hook automation** — ShellCheck and shfmt run automatically after file creation/modification
@@ -0,0 +1,142 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Description: [LIBRARY NAME] - Reusable bash functions
4
+ # Usage: source /path/to/[FILE].sh
5
+ #
6
+ # This file provides reusable functions for [PURPOSE]
7
+ #
8
+ # For general-purpose utilities (logging, validation, temp files), consider
9
+ # sourcing the plugin runtime library instead:
10
+ # source "${CLAUDE_PLUGIN_ROOT}/scripts/lib-common.sh"
11
+ #
12
+ # Functions:
13
+ # shroutines::function_name - Description of what the function does
14
+ #
15
+
16
+ # Guard against direct execution
17
+ [[ "${BASH_SOURCE[0]}" == "${0}" ]] && {
18
+ echo "Error: This file should be sourced, not executed" >&2
19
+ exit 2
20
+ }
21
+
22
+ set -euo pipefail
23
+
24
+ ###
25
+ ### :::: Constants :::: ###############
26
+ ###
27
+
28
+ # shellcheck disable=SC2034 # template placeholder, used after scaffolding
29
+ readonly _LIB_VERSION="0.1.0"
30
+
31
+ ###
32
+ ### :::: Globals :::: #################
33
+ ###
34
+
35
+ # shellcheck disable=SC2034
36
+ _LIB_TEMP_FILES=()
37
+
38
+ ###
39
+ ### :::: Private functions :::: #######
40
+ ###
41
+
42
+ # Cleanup tracking
43
+ function _lib_cleanup() {
44
+ local f
45
+ for f in "${_LIB_TEMP_FILES[@]+"${_LIB_TEMP_FILES[@]}"}"; do
46
+ rm -f "$f"
47
+ done
48
+ return 0
49
+ }
50
+
51
+ trap _lib_cleanup EXIT
52
+
53
+ ###
54
+ ### :::: Public functions :::: ########
55
+ ###
56
+
57
+ # Validates user input against a pattern
58
+ # Arguments:
59
+ # $1 - input: The string to validate
60
+ # $2 - pattern: Regex pattern to match (optional, defaults to alphanumeric)
61
+ # Returns:
62
+ # 0 - valid input
63
+ # 1 - invalid input
64
+
65
+ function shroutines::validate_input() {
66
+ local input="$1"
67
+ local pattern="${2:-^[a-zA-Z0-9_-]+$}"
68
+
69
+ [[ "$input" =~ $pattern ]]
70
+ return 0
71
+ }
72
+
73
+ # Log a message with timestamp and level (no subprocess)
74
+ # Arguments:
75
+ # $1 - level: Log level (INFO, WARN, ERROR, DEBUG)
76
+ # $2 - message: The message to log
77
+ # Returns: None
78
+ function shroutines::log_message() {
79
+ printf '[%(%Y-%m-%d %H:%M:%S)T] [%s] %s\n' -1 "$1" "${*:2}" >&2
80
+ return 0
81
+ }
82
+
83
+ # Check if a command is available
84
+ # Arguments:
85
+ # $1 - command: Name of the command to check
86
+ # Returns:
87
+ # 0 - command exists
88
+ # 1 - command not found
89
+ function shroutines::require_command() {
90
+ local cmd="$1"
91
+
92
+ if ! command -v "$cmd" >/dev/null 2>&1; then
93
+ shroutines::log_message "ERROR" "Required command not found: $cmd"
94
+ return 1
95
+ fi
96
+
97
+ return 0
98
+ }
99
+
100
+ # Ensure a directory exists, create if missing
101
+ # Arguments:
102
+ # $1 - path: Directory path to ensure
103
+ # $2 - mode: Optional permissions (default: 0755)
104
+ # Returns:
105
+ # 0 - directory exists or was created
106
+ # 1 - failed to create directory
107
+ function shroutines::ensure_dir() {
108
+ local path="$1"
109
+ local mode="${2:-0755}"
110
+
111
+ if [[ ! -d "$path" ]]; then
112
+ mkdir -p "$path" || return 1
113
+ chmod "$mode" "$path"
114
+ fi
115
+
116
+ return 0
117
+ }
118
+
119
+ # Create a temporary file with automatic cleanup
120
+ # Arguments:
121
+ # $1 - var_name: Name of variable to store temp file path
122
+ # Returns:
123
+ # 0 - temp file created successfully
124
+ # 1 - failed to create temp file
125
+ function shroutines::temp_file() {
126
+ local -n var_ref="$1"
127
+ local tmp
128
+
129
+ tmp=$(mktemp) || return 1
130
+ # shellcheck disable=SC2034 # nameref: assignment is the intended use
131
+ var_ref="$tmp"
132
+
133
+ _LIB_TEMP_FILES+=("$tmp")
134
+ return 0
135
+ }
136
+
137
+ # Export functions for use in subshells
138
+ export -f shroutines::validate_input
139
+ export -f shroutines::log_message
140
+ export -f shroutines::require_command
141
+ export -f shroutines::ensure_dir
142
+ export -f shroutines::temp_file
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Description: [BRIEF DESCRIPTION]
4
+ # Usage: [SCRIPT_NAME] [ARGUMENTS]
5
+ # shellcheck disable=SC2034
6
+
7
+ set -euo pipefail
8
+
9
+ ###
10
+ ### :::: Constants :::: ###############
11
+ ###
12
+
13
+ readonly VERSION="0.1.0"
14
+
15
+ ###
16
+ ### :::: Globals :::: ###############
17
+ ###
18
+
19
+ INPUT=""
20
+
21
+ ###
22
+ ### :::: Private functions :::: ########
23
+ ###
24
+
25
+ # Main logic
26
+ function _main() {
27
+ local input="$1"
28
+
29
+ if [[ -z "$input" ]]; then
30
+ echo "Error: input required" >&2
31
+ exit 2
32
+ fi
33
+
34
+ # Process input
35
+ echo "Processing: $input"
36
+ return 0
37
+ }
38
+
39
+ ###
40
+ ### :::: Public functions :::: ########
41
+ ###
42
+
43
+ function shroutines::main() {
44
+ _main "$@"
45
+ return 0
46
+ }
47
+
48
+ ###
49
+ ### :::: Guard and execution :::: #####
50
+ ###
51
+
52
+ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
53
+ shroutines::main "$@"
54
+ fi