# Script Template Unified script template covering both Bash and Python runtimes. ## Usage Context | Phase | Usage | |-------|-------| | Optional | Use when declaring `## Scripts` in Phase/Action | | Execution | Invoke via `ExecuteScript('script-id', params)` | | Output Location | `.claude/skills/{skill-name}/scripts/{script-id}.{ext}` | --- ## Invocation Interface Specification All scripts share the same calling convention: ``` Caller | ExecuteScript('script-id', { key: value }) | Script Entry ├─ Parameter parsing (--key value) ├─ Input validation (required parameter checks, file exists) ├─ Core processing (data read -> transform -> write) └─ Output result (last line: single-line JSON -> stdout) ├─ Success: {"status":"success", "output_file":"...", ...} └─ Failure: stderr output error message, exit 1 ``` ### Return Format ```typescript interface ScriptResult { success: boolean; // exit code === 0 stdout: string; // Standard output stderr: string; // Standard error outputs: object; // JSON output parsed from stdout last line } ``` ### Parameter Convention | Parameter | Required | Description | |-----------|----------|-------------| | `--input-path` | Yes | Input file path | | `--output-dir` | Yes | Output directory (specified by caller) | | Others | Optional | Script-specific parameters | --- ## Bash Implementation ```bash #!/bin/bash # {{script_description}} set -euo pipefail # ============================================================ # Parameter Parsing # ============================================================ INPUT_PATH="" OUTPUT_DIR="" while [[ "$#" -gt 0 ]]; do case $1 in --input-path) INPUT_PATH="$2"; shift ;; --output-dir) OUTPUT_DIR="$2"; shift ;; --help) echo "Usage: $0 --input-path --output-dir " exit 0 ;; *) echo "Error: Unknown parameter $1" >&2 exit 1 ;; esac shift done # ============================================================ # Parameter Validation # ============================================================ [[ -z "$INPUT_PATH" ]] && { echo "Error: --input-path is required parameter" >&2; exit 1; } [[ -z "$OUTPUT_DIR" ]] && { echo "Error: --output-dir is required parameter" >&2; exit 1; } [[ ! -f "$INPUT_PATH" ]] && { echo "Error: Input file does not exist: $INPUT_PATH" >&2; exit 1; } command -v jq &> /dev/null || { echo "Error: jq is required" >&2; exit 1; } mkdir -p "$OUTPUT_DIR" # ============================================================ # Core Logic # ============================================================ OUTPUT_FILE="$OUTPUT_DIR/result.txt" ITEMS_COUNT=0 # TODO: Implement processing logic while IFS= read -r line; do echo "$line" >> "$OUTPUT_FILE" ((ITEMS_COUNT++)) done < "$INPUT_PATH" # ============================================================ # Output JSON Result (use jq to build, avoid escaping issues) # ============================================================ jq -n \ --arg output_file "$OUTPUT_FILE" \ --argjson items_processed "$ITEMS_COUNT" \ '{output_file: $output_file, items_processed: $items_processed, status: "success"}' ``` ### Bash Common Patterns ```bash # File iteration for file in "$INPUT_DIR"/*.json; do [[ -f "$file" ]] || continue # Processing logic... done # Temp file (auto cleanup) TEMP_FILE=$(mktemp) trap "rm -f $TEMP_FILE" EXIT # Tool dependency check require_command() { command -v "$1" &> /dev/null || { echo "Error: $1 required" >&2; exit 1; } } require_command jq # jq processing VALUE=$(jq -r '.field' "$INPUT_PATH") # Read field jq '.field = "new"' input.json > output.json # Modify field jq -s 'add' file1.json file2.json > merged.json # Merge files ``` --- ## Python Implementation ```python #!/usr/bin/env python3 """ {{script_description}} """ import argparse import json import sys from pathlib import Path def main(): parser = argparse.ArgumentParser(description='{{script_description}}') parser.add_argument('--input-path', type=str, required=True, help='Input file path') parser.add_argument('--output-dir', type=str, required=True, help='Output directory') args = parser.parse_args() # Validate input input_path = Path(args.input_path) if not input_path.exists(): print(f"Error: Input file does not exist: {input_path}", file=sys.stderr) sys.exit(1) output_dir = Path(args.output_dir) output_dir.mkdir(parents=True, exist_ok=True) # Execute processing try: result = process(input_path, output_dir) except Exception as e: print(f"Error: {e}", file=sys.stderr) sys.exit(1) # Output JSON result print(json.dumps(result)) def process(input_path: Path, output_dir: Path) -> dict: """Core processing logic""" # TODO: Implement processing logic output_file = output_dir / 'result.json' with open(input_path, 'r', encoding='utf-8') as f: data = json.load(f) processed_count = len(data) if isinstance(data, list) else 1 with open(output_file, 'w', encoding='utf-8') as f: json.dump(data, f, indent=2, ensure_ascii=False) return { 'output_file': str(output_file), 'items_processed': processed_count, 'status': 'success' } if __name__ == '__main__': main() ``` ### Python Common Patterns ```python # File iteration def process_files(input_dir: Path, pattern: str = '*.json') -> list: return [ {'file': str(f), 'data': json.load(f.open())} for f in input_dir.glob(pattern) ] # Data transformation def transform(data: dict) -> dict: return { 'id': data.get('id'), 'name': data.get('name', '').strip(), 'timestamp': datetime.now().isoformat() } # External command invocation import subprocess def run_command(cmd: list) -> str: result = subprocess.run(cmd, capture_output=True, text=True) if result.returncode != 0: raise RuntimeError(result.stderr) return result.stdout ``` --- ## Runtime Selection Guide ``` Task Characteristics | ├─ File processing / system commands / pipeline operations │ └─ Choose Bash (.sh) │ ├─ JSON data processing / complex transformation / data analysis │ └─ Choose Python (.py) │ └─ Simple read/write / format conversion └─ Either (Bash is lighter) ``` --- ## Generation Function ```javascript function generateScript(scriptConfig) { const runtime = scriptConfig.runtime || 'bash'; // 'bash' | 'python' const ext = runtime === 'python' ? '.py' : '.sh'; if (runtime === 'python') { return generatePythonScript(scriptConfig); } return generateBashScript(scriptConfig); } function generateBashScript(scriptConfig) { const { description, inputs = [], outputs = [] } = scriptConfig; const paramDefs = inputs.map(i => `${i.name.toUpperCase().replace(/-/g, '_')}="${i.default || ''}"` ).join('\n'); const paramParse = inputs.map(i => ` --${i.name}) ${i.name.toUpperCase().replace(/-/g, '_')}="$2"; shift ;;` ).join('\n'); const paramValidation = inputs.filter(i => i.required).map(i => { const VAR = i.name.toUpperCase().replace(/-/g, '_'); return `[[ -z "$${VAR}" ]] && { echo "Error: --${i.name} is required parameter" >&2; exit 1; }`; }).join('\n'); return `#!/bin/bash # ${description} set -euo pipefail ${paramDefs} while [[ "$#" -gt 0 ]]; do case $1 in ${paramParse} *) echo "Unknown parameter: $1" >&2; exit 1 ;; esac shift done ${paramValidation} # TODO: Implement processing logic # Output result (jq build) jq -n ${outputs.map(o => `--arg ${o.name} "$${o.name.toUpperCase().replace(/-/g, '_')}"` ).join(' \\\n ')} \ '{${outputs.map(o => `${o.name}: $${o.name}`).join(', ')}}' `; } function generatePythonScript(scriptConfig) { const { description, inputs = [], outputs = [] } = scriptConfig; const argDefs = inputs.map(i => ` parser.add_argument('--${i.name}', type=${i.type || 'str'}, ${ i.required ? 'required=True' : `default='${i.default || ''}'` }, help='${i.description || i.name}')` ).join('\n'); const resultFields = outputs.map(o => ` '${o.name}': None # ${o.description || o.name}` ).join(',\n'); return `#!/usr/bin/env python3 """ ${description} """ import argparse import json import sys from pathlib import Path def main(): parser = argparse.ArgumentParser(description='${description}') ${argDefs} args = parser.parse_args() # TODO: Implement processing logic result = { ${resultFields} } print(json.dumps(result)) if __name__ == '__main__': main() `; } ``` --- ## Directory Convention ``` scripts/ ├── process-data.py # id: process-data, runtime: python ├── validate.sh # id: validate, runtime: bash └── transform.js # id: transform, runtime: node ``` - **Name is ID**: Filename (without extension) = script ID - **Extension is runtime**: `.py` -> python, `.sh` -> bash, `.js` -> node