This document provides guidelines for creating and maintaining tool installation scripts in the claudio-plugin/tools/ directory.
The tools/ directory contains installation scripts for CLI tools used by skills. Each tool has its own subdirectory with a standardized install.sh script.
Design Philosophy:
- Keep scripts simple and focused
- Reuse common functionality via
common.sh - Support Linux only (x86_64 and ARM64)
- Minimal options (only
--check) - Let the tools do one thing well: install if missing
claudio-plugin/tools/
├── common.sh # Shared library with common functions
├── TOOLS.md # This guide
├── <tool-name>/
│ └── install.sh # Installation script for the tool
└── ...
IMPORTANT: Always check common.sh first before writing new functions. Reuse existing functions to maintain consistency and reduce duplication.
log()- Simple logging to stdout
detect_arch()- Detect architecture (returns:x86_64oraarch64)verify_linux()- Verify running on Linux (exits with error if not)
command_exists()- Check if command exists in PATH
version_gte()- Semantic version comparison (returns 0 if v1 >= v2)
is_in_path()- Check if directory is in PATHwarn_if_not_in_path()- Warn user if install directory not in PATH
Always source the common library at the top of your script:
#!/usr/bin/env bash
set -euo pipefail
# ============================================================================
# LOAD COMMON LIBRARY
# ============================================================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=../common.sh
source "$SCRIPT_DIR/../common.sh"Supported Platforms:
- ✓ Linux x86_64 (amd64)
- ✓ Linux ARM64 (aarch64)
NOT Supported:
- ✗ macOS (Darwin)
- ✗ Windows
- ✗ Other architectures (ARM32, i386, etc.)
Rationale: Focus on Linux containers and CI/CD environments where these scripts are most commonly used.
Use this template when creating a new tool installer:
#!/usr/bin/env bash
#
# <Tool Name> Installation Script (Linux Only)
#
# This script installs or updates <tool> on Linux systems.
# Supports: x86_64 and ARM64 (aarch64) architectures only.
#
# Usage:
# ./install.sh # Check and install <tool>
# ./install.sh --check # Only check, don't install
set -euo pipefail
# ============================================================================
# LOAD COMMON LIBRARY
# ============================================================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=../common.sh
source "$SCRIPT_DIR/../common.sh"
# ============================================================================
# DEPENDENCY VERSION
# ============================================================================
# This version is tracked by Renovate for automatic updates
# renovate: datasource=github-releases depName=<org>/<repo>
TOOL_VERSION="x.y.z"
# ============================================================================
# CONFIGURATION
# ============================================================================
# Determine install directory - prefer /usr/local/bin, fallback to ~/.local/bin
if [ -z "${INSTALL_DIR:-}" ]; then
if [ -w "/usr/local/bin" ]; then
INSTALL_DIR="/usr/local/bin"
else
INSTALL_DIR="$HOME/.local/bin"
fi
fi
TMP_DIR="${TMP_DIR:-/tmp/<tool>-install}"
# ============================================================================
# HELPER FUNCTIONS
# ============================================================================
# Get installed <tool> version
get_tool_version() {
if command_exists <tool>; then
# Extract version from tool output
<tool> --version 2>&1 | grep -oP '<pattern>' || echo "unknown"
else
echo "not_installed"
fi
}
# ============================================================================
# TOOL INSTALLATION
# ============================================================================
check_tool() {
local current_version
current_version=$(get_tool_version)
if [ "$current_version" = "not_installed" ]; then
log "<Tool> is not installed"
return 1
fi
log "<Tool> version: $current_version"
if version_gte "$current_version" "$TOOL_VERSION"; then
log "<Tool> is up to date (>= $TOOL_VERSION)"
return 0
else
log "<Tool> version $current_version is older than required $TOOL_VERSION"
return 1
fi
}
install_tool() {
local arch
arch=$(detect_arch)
log "Installing <Tool> v${TOOL_VERSION} for Linux $arch..."
# Verify we're on Linux
verify_linux || return 1
# Create temporary directory
mkdir -p "$TMP_DIR"
# Download based on architecture
local download_url
if [ "$arch" = "x86_64" ]; then
download_url="<url-for-x86_64>"
else
download_url="<url-for-aarch64>"
fi
log "Downloading from: $download_url"
# Download and install (adapt based on tool's distribution format)
# For binary:
curl -fsSL "$download_url" -o "${TMP_DIR}/<tool>"
chmod +x "${TMP_DIR}/<tool>"
# For archive (zip/tar.gz):
# curl -fsSL "$download_url" -o "${TMP_DIR}/<tool>.zip"
# unzip -q "${TMP_DIR}/<tool>.zip" -d "${TMP_DIR}"
# chmod +x "${TMP_DIR}/<tool>/bin/<tool>"
# Install to INSTALL_DIR
log "Installing to: $INSTALL_DIR"
if [ "$INSTALL_DIR" = "/usr/local/bin" ]; then
sudo mv "${TMP_DIR}/<tool>" "$INSTALL_DIR/<tool>"
else
mv "${TMP_DIR}/<tool>" "$INSTALL_DIR/<tool>"
fi
# Cleanup
rm -rf "$TMP_DIR"
# Verify installation
if check_tool; then
log "✓ <Tool> installed successfully"
return 0
else
log "✗ <Tool> installation verification failed" >&2
return 1
fi
}
# ============================================================================
# MAIN SCRIPT
# ============================================================================
main() {
local check_only=false
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
-c|--check)
check_only=true
shift
;;
*)
log "ERROR: Unknown option: $1" >&2
log "Usage: $(basename "$0") [--check]" >&2
exit 1
;;
esac
done
# Ensure INSTALL_DIR and TMP_DIR exist
mkdir -p "$INSTALL_DIR"
mkdir -p "$TMP_DIR"
# Check if INSTALL_DIR is in PATH
warn_if_not_in_path "$INSTALL_DIR"
# Execute based on options
if [ "$check_only" = true ]; then
check_tool
exit $?
fi
# Install if needed
if ! check_tool; then
echo ""
log "Installing <Tool>..."
install_tool
fi
}
# Run main function
main "$@"✓ Use common.sh functions - Always check if a function exists in common.sh before implementing it yourself
✓ Support Linux x86_64 and ARM64 only - Use detect_arch() and verify_linux() from common.sh
✓ Version tracking with Renovate - Use the # renovate: comment format for automatic updates
✓ Smart install directory - Prefer /usr/local/bin (writable), fallback to ~/.local/bin
✓ Verify installation - Always call check_tool() after installation to verify success
✓ Cleanup temporary files - Always remove $TMP_DIR after installation
✓ Use set -euo pipefail - Fail fast on errors
✓ Minimal options - Only support --check flag
✓ Single responsibility - Script should do one thing: install the tool if not present
✓ Clear logging - Use log() for all output, use ✓ and ✗ for success/failure
✓ Make scripts executable - chmod +x install.sh
✗ Don't add --help or -h - Keep scripts simple, usage is in header comment
✗ Don't add --version - Not needed, version info is in the script itself
✗ Don't add --force - Scripts should be idempotent, re-running is safe
✗ Don't support macOS or Windows - Linux only
✗ Don't support other architectures - x86_64 and ARM64 only
✗ Don't duplicate common.sh functions - Reuse existing functions
✗ Don't use relative paths in source - Always calculate $SCRIPT_DIR first
✗ Don't hardcode paths - Use $INSTALL_DIR and $TMP_DIR variables
✗ Don't forget cleanup - Always rm -rf "$TMP_DIR" after installation
✗ Don't create complex argument parsing - Only --check is needed
Use the following comment format to enable automatic dependency updates:
# renovate: datasource=github-releases depName=<org>/<repo>
TOOL_VERSION="x.y.z"Common datasources:
github-releases- GitHub releasesnpm- npm packagespypi- Python packagesdocker- Docker images
Example:
# renovate: datasource=github-releases depName=aws/aws-cli
AWS_CLI_VERSION="2.15.17"
# renovate: datasource=github-releases depName=jqlang/jq
JQ_VERSION="1.7.1"For tools distributed as a single binary (like jq):
install_tool() {
local arch
arch=$(detect_arch)
verify_linux || return 1
mkdir -p "$TMP_DIR"
# Determine download URL based on architecture
local download_url
if [ "$arch" = "x86_64" ]; then
download_url="https://example.com/tool-linux-amd64"
else
download_url="https://example.com/tool-linux-arm64"
fi
log "Downloading from: $download_url"
curl -fsSL "$download_url" -o "${TMP_DIR}/tool"
chmod +x "${TMP_DIR}/tool"
log "Installing to: $INSTALL_DIR"
if [ "$INSTALL_DIR" = "/usr/local/bin" ]; then
sudo mv "${TMP_DIR}/tool" "$INSTALL_DIR/tool"
else
mv "${TMP_DIR}/tool" "$INSTALL_DIR/tool"
fi
rm -rf "$TMP_DIR"
}For tools distributed as zip/tar.gz (like AWS CLI):
install_tool() {
local arch
arch=$(detect_arch)
verify_linux || return 1
# Check for required tools
if ! command_exists unzip; then
log "ERROR: unzip is required but not installed" >&2
return 1
fi
mkdir -p "$TMP_DIR"
cd "$TMP_DIR"
# Download archive
local download_url
if [ "$arch" = "x86_64" ]; then
download_url="https://example.com/tool-linux-x86_64.zip"
else
download_url="https://example.com/tool-linux-aarch64.zip"
fi
log "Downloading from: $download_url"
curl -fsSL "$download_url" -o "tool.zip"
log "Extracting..."
unzip -q tool.zip
log "Installing to: $INSTALL_DIR"
if [ "$INSTALL_DIR" = "/usr/local/bin" ]; then
sudo ./tool/install --update
else
./tool/install --install-dir "$HOME/.local/tool" --bin-dir "$INSTALL_DIR" --update
fi
cd - >/dev/null
rm -rf "$TMP_DIR"
}For tools best installed via package manager:
install_tool() {
verify_linux || return 1
# Detect package manager
if command_exists apt-get; then
log "Installing via apt-get..."
sudo apt-get update
sudo apt-get install -y tool
elif command_exists yum; then
log "Installing via yum..."
sudo yum install -y tool
elif command_exists dnf; then
log "Installing via dnf..."
sudo dnf install -y tool
else
log "ERROR: No supported package manager found" >&2
return 1
fi
}Before committing, test your script:
# 1. Check syntax
bash -n tools/<tool>/install.sh
# 2. Test --check when tool is not installed
tools/<tool>/install.sh --check
# 3. Test installation
tools/<tool>/install.sh
# 4. Test --check when tool is installed
tools/<tool>/install.sh --check
# 5. Test idempotency (should not reinstall)
tools/<tool>/install.sh
# 6. Verify the tool works
<tool> --versionLet's walk through adding a hypothetical tool called kubectl:
mkdir -p claudio-plugin/tools/kubectltouch claudio-plugin/tools/kubectl/install.sh
chmod +x claudio-plugin/tools/kubectl/install.shCopy the template from this guide and customize:
- Replace
<Tool Name>withkubectl - Replace
<tool>placeholders withkubectl - Add Renovate comment with proper datasource
- Implement
get_tool_version()to extract version - Implement download URLs for x86_64 and aarch64
- Choose appropriate installation pattern
# Check syntax
bash -n tools/kubectl/install.sh
# Test installation
tools/kubectl/install.sh
# Verify
kubectl versionUpdate skill documentation to reference the new tool:
**Installation:**
```bash
# kubectl (required)
../../../tools/kubectl/install.sh # Check and install kubectl
../../../tools/kubectl/install.sh --check # Check only, don't installWrong:
source ../common.sh # Relative path fails in different contextsCorrect:
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/../common.sh"Wrong:
download_url="https://example.com/tool-amd64" # HardcodedCorrect:
arch=$(detect_arch) # Use common.sh function
if [ "$arch" = "x86_64" ]; then
download_url="https://example.com/tool-amd64"
else
download_url="https://example.com/tool-arm64"
fiWrong:
install_tool() {
# Assumes Linux, will fail on macOS
curl -fsSL "https://example.com/tool-linux" -o tool
}Correct:
install_tool() {
verify_linux || return 1 # Use common.sh function
curl -fsSL "https://example.com/tool-linux" -o tool
}Wrong:
install_tool() {
mkdir -p "$TMP_DIR"
# ... installation ...
# Forgot to cleanup!
}Correct:
install_tool() {
mkdir -p "$TMP_DIR"
# ... installation ...
rm -rf "$TMP_DIR" # Always cleanup
}Wrong:
# Script has no execute permission
-rw-r--r-- install.shCorrect:
chmod +x tools/<tool>/install.sh
# Now it has execute permission
-rwxr-xr-x install.shUse this checklist when creating a new tool installer:
- Created directory:
tools/<tool>/ - Created script:
tools/<tool>/install.sh - Made script executable:
chmod +x - Sourced
common.shcorrectly - Added Renovate version tracking comment
- Used
detect_arch()from common.sh - Used
verify_linux()from common.sh - Used
command_exists()from common.sh - Used
version_gte()from common.sh - Used
warn_if_not_in_path()from common.sh - Implemented
get_tool_version()function - Implemented
check_tool()function - Implemented
install_tool()function - Only supports
--checkflag (no--help,--version,--force) - Only supports Linux x86_64 and ARM64
- Cleans up temporary files
- Verifies installation after completing
- Tested with
bash -nfor syntax - Tested installation flow
- Tested idempotency (running twice)
- Updated skill documentation (SKILL.md)
When adding a new function to common.sh:
- Document the function with comments
- Add it to the appropriate section (Logging, Platform Detection, etc.)
- Update this guide's "Available Functions" section
- Test with existing scripts to ensure no breakage
When updating a tool installer:
- Check if the change should go in
common.shinstead - Maintain backward compatibility
- Update version tracking comments if needed
- Test all affected scripts
If a tool is no longer needed:
- Remove the tool directory:
rm -rf tools/<tool>/ - Update skill documentation to remove references
- Update any dependency checker scripts
When in doubt:
- Check existing scripts (
aws-cli/install.sh,jq/install.sh) - Check
common.shfor available functions - Follow the template in this guide
- Keep it simple - don't add unnecessary features
This guide follows the conventions established in February 2025 for the Claudio Skills Plugin project.