Skip to content

Writing Shell Commands

Shell commands are bash scripts placed in the project's usercmd/ directory. They become available as skimindex <name> subcommands without rebuilding the container image.


Quick start

Step 1. Create usercmd/ in your project root (done automatically by skimindex init):

mkdir -p usercmd

Step 2. Write a script there, e.g. usercmd/hello.sh:

#!/usr/bin/env bash
# ============================================================
# hello.sh
# Say hello — one-line description shown in skimindex --help.
# ============================================================
set -euo pipefail

source "${SKIMINDEX_SCRIPTS_DIR}/__skimindex.sh"   # logging + config + stamping

loginfo "Hello, skimindex!"

Step 3. Invoke it immediately — no rebuild needed:

skimindex hello

The first non-empty, non-separator comment line after the filename becomes the description shown in skimindex --help under the user subcommands section.

How it works

usercmd/ is declared in config/skimindex.toml:

[local_directories]
usercmd = "usercmd"   # bind-mounted to /usercmd inside the container

When a subcommand is not built-in, skimindex.sh checks for /usercmd/<subcmd>.sh and executes it with bash. Before calling the script it exports SKIMINDEX_SCRIPTS_DIR=/app/scripts so that __skimindex.sh can always be sourced portably.


Adding options

Use a standard while / case loop. Expose --help by re-printing the header block with sed:

#!/usr/bin/env bash
# ============================================================
# greet.sh
# Print a greeting for a given name.
#
# Usage:
#   skimindex greet --name NAME [--shout]
# ============================================================

set -euo pipefail

source "${SKIMINDEX_SCRIPTS_DIR}/__skimindex.sh"

NAME=""
SHOUT=false

while [[ $# -gt 0 ]]; do
    case "$1" in
        -h|--help)
            sed -En '2,/^# =+$/{ s/^# ?//; /^=+$/d; p; }' "$0"
            exit 0
            ;;
        --name)   NAME="$2";  shift 2 ;;
        --shout)  SHOUT=true; shift   ;;
        *) logerror "Unknown option: $1"; exit 1 ;;
    esac
done

[[ -z "$NAME" ]] && { logerror "--name is required"; exit 1; }

msg="Hello, ${NAME}!"
[[ "$SHOUT" == true ]] && msg="${msg^^}"
loginfo "$msg"

Using configuration variables

After source __skimindex.sh, all [data.X], [role.X], [source.X] and root-level values from config/skimindex.toml are available as environment variables.

#!/usr/bin/env bash
# ============================================================
# show_config.sh
# Display resolved paths for a data section.
#
# Usage:
#   skimindex show_config --section NAME
# ============================================================

set -euo pipefail

source "${SKIMINDEX_SCRIPTS_DIR}/__skimindex.sh"

SECTION=""

while [[ $# -gt 0 ]]; do
    case "$1" in
        --section) SECTION="$2"; shift 2 ;;
        *) logerror "Unknown option: $1"; exit 1 ;;
    esac
done

[[ -z "$SECTION" ]] && { logerror "--section is required"; exit 1; }

SECTION_UP="${SECTION^^}"

source_var="SKIMINDEX__DATA__${SECTION_UP}__SOURCE"
role_var="SKIMINDEX__DATA__${SECTION_UP}__ROLE"
dir_var="SKIMINDEX__DATA__${SECTION_UP}__DIRECTORY"

loginfo "Section  : $SECTION"
loginfo "Source   : ${!source_var:-<not set>}"
loginfo "Role     : ${!role_var:-<not set>}"
loginfo "Directory: ${!dir_var:-<not set>}"

The full list of sections and their variables is produced at runtime by python3 -m skimindex.config (the same call made by __skimindex_config.sh).


See Bash Shell API Reference for logging, stamp, and configuration variable references.