Declare your shell tools in one config file. shdeps installs and updates them everywhere — brew, apt, dnf, pacman, zypper, apk, GitHub repos, or GitHub release binaries. One manifest, any machine.
- Declarative config — one line per dependency in
*.conffiles - Multiple install methods — system packages (brew/apt/dnf/pacman/zypper/apk), automatic GitHub release/repo selection, explicit GitHub repos, explicit GitHub release binaries, Rust crates (
cargo install), Go modules (go install), Python CLI tools (uv tool install), Node.js CLI tools (npm install -g), or fully custom hooks - Cross-platform — Linux, macOS, WSL with
os:andhost:filtering per dep - Package manager abstraction — batched installs with individual retry fallback
- Smart binary matching — multi-pass asset selection by OS, arch, and libc
- TTL-based caching — avoids redundant network calls
- Post-install hooks — run arbitrary setup when a dependency changes
- Config composition — split deps across multiple
*.conffiles in a config directory - Usable from CLI, Bash, Lua, or Rust — Rust
shdepsCLI, stable Bash functions viasource shdeps.sh, a Lua runtime asset API, and a documented Rust crate API
shdeps occupies a deliberately narrow niche: "the set of CLI tools I want on every box I touch, installed via whatever native means is best on that box, and sourceable from my shell init." It is not a polyglot runtime version manager and does not try to be one. If you're choosing between shdeps and one of the established tools below, this section should help.
| Tool | Primary purpose | Per-project versions | Cross-OS package abstraction | Sourceable from shell init |
|---|---|---|---|---|
| shdeps | One CLI install list across all machines | No | Yes (brew/apt/dnf/pacman/zypper/apk + GitHub) | Yes (source shdeps.sh exposes shdeps_* Bash API) |
| mise | Polyglot language-runtime + tool version manager | Yes (per directory) | Mostly via plugins | Yes (shell activation) |
| asdf | Polyglot language-runtime version manager (asdf protocol; mise is the modern asdf-compatible alternative) | Yes | Via plugins | Yes (shell activation) |
| aqua | Declarative GitHub Releases-focused CLI installer | Yes (per directory) | No (downloads release archives) | Yes (shell activation) |
| chezmoi | Dotfile manager (with external-binary support) | No | No (basic external download) | N/A (manages dotfiles, not shell API) |
| Homebrew | Native package manager (macOS-first) | No | macOS-only (Linuxbrew variant) | No (only PATH) |
| Nix / Home Manager | Hermetic, reproducible package manager | Implicit (per generation) | Yes, hermetic | Yes (after substantial setup) |
- You want one config file that says "install
jq,fzf,ripgrep,neovimeverywhere" and shdeps figures out whether that meansbrew,apt, a GitHub release archive, or a Cargo crate on each host. - You write Bash dotfiles or hooks and want
source shdeps.shto give youshdeps_update,shdeps_dep_source,shdeps_platform,shdeps_pkg_mgr, etc. — so your.bashrccan both install and use tools without forking shells. - You're comfortable with "latest stable" semantics. shdeps does not pin tool versions, and that is a deliberate design choice — pinning would conflict with
pkg-installed tools whose version is the distro's call. - You want custom escape hatches: any tool that doesn't fit
pkg/github*/cargo/go/uv/npmgets acustommethod with a 4-function Bash hook (exists/version/install/uninstall) and shdeps treats it as a first-class entry.
- You need per-project tool versions (Node 18 in repo A, Node 20 in repo B). Use mise or asdf — that's their job.
- You want hermetic, byte-reproducible installs across machines. Use Nix.
- You want a GitHub Releases-first registry with curated install recipes shared across users. Use aqua.
- Your dotfile workflow is dominated by templating, encryption, or multi-host file rewriting, and tool install is incidental. Use chezmoi (and call
shdeps updatefrom a chezmoi script if you still want the shdeps install model).
shdeps does not own $PATH or shadow other tools. It is designed to run alongside mise, Homebrew, or asdf. Each entry in your config explicitly names its install method, so shdeps only touches what you told it to manage; everything else stays under whichever tool owns it. The pkg method defers to the system package manager and skips entries that are already present, so it never re-installs what your distro (or Homebrew, etc.) already provides.
curl -fsSL https://raw.githubusercontent.com/cgraf78/shdeps/main/install.sh | bashThis installs the latest release archive to ~/.local/share/shdeps and
symlinks the Rust CLI into ~/.local/bin/shdeps. Re-running the installer is
idempotent. Fresh bootstrap expects release assets; if they are unavailable,
the installer fails instead of silently cloning and building a managed source
checkout. Developer/source-checkout installs are still supported when you run
install.sh from a git checkout or set SHDEPS_REPO explicitly.
Then create a config and run:
mkdir -p ~/.config/shdeps
cat > ~/.config/shdeps/deps.conf << 'EOF'
jq pkg
fzf pkg
EOF
shdeps updateThe CLI loads all *.conf files from ~/.config/shdeps/ (sorted alphabetically). Split deps across multiple files for organization (e.g., 00-core.conf, 50-tools.conf, 99-local.conf). The Bash API (source shdeps.sh) defaults to ./shdeps/.
shdeps self-updateRelease installs update from the latest matching archive, while source
checkouts use git pull --ff-only and skip updates when the working tree has
uncommitted changes (active development). shdeps update also performs a
quiet, best-effort self-check for supported shdeps installs before dependency
updates. That automatic check is TTL-gated by SHDEPS_SELF_UPDATE_TTL; use
--force or SHDEPS_FORCE=1 with shdeps update to bypass the self-update
TTL.
curl -fsSL https://raw.githubusercontent.com/cgraf78/shdeps/main/install.sh | bash -s -- --uninstallOr manually: rm -rf ~/.local/share/shdeps ~/.local/bin/shdeps.
# name method [cmd] [aliases] [filter]
| Field | Required | Description |
|---|---|---|
name |
yes | Dependency name (used for hooks, logging, tracking). For github, github:repo, and github:release: GitHub owner/repo. For go: full module path. |
method |
yes | Install method: pkg, github, github:repo, github:release, cargo, go, uv, npm, or custom |
cmd |
no | Command to check for existence (defaults to name). Supports mgr:name qualifiers for platform-specific command names (e.g., apt:batcat). |
aliases |
no | For pkg: per-manager package name overrides (apt:fd-find,dnf:fd-find). Use NONE to skip a specific manager (e.g., brew:NONE). |
filter |
no | Platform and hostname filter. Use os: and host: prefixes (e.g., os:linux, host:nas, os:linux,host:nas, os:!wsl). |
Use - for fields you want to skip. See examples/deps.conf for a full example.
| Variable | Default | Description |
|---|---|---|
SHDEPS_CONF_DIR |
~/.config/shdeps/ (CLI) or ./shdeps/ (Bash API) |
Config directory (all *.conf files loaded) |
SHDEPS_HOOKS_DIR |
<conf_dir>/hooks.d |
Post-install hooks directory |
SHDEPS_STATE_DIR |
${XDG_STATE_HOME:-$HOME/.local/state}/shdeps |
Cache/state directory |
SHDEPS_FORCE |
0 |
Bypass TTL cache (check for updates now) |
SHDEPS_REINSTALL |
0 |
Force reinstall all deps |
SHDEPS_QUIET |
0 |
Suppress non-result output and interactive prompts |
SHDEPS_REMOTE_TTL |
3600 |
Cache TTL in seconds |
SHDEPS_SELF_UPDATE_TTL |
3600 |
Self-update attempt TTL in seconds for shdeps update |
SHDEPS_GIT_DEV_DIR |
~/git |
Dev clone directory used only by github:repo deps (prefers <dir>/<repo> over a managed clone) |
SHDEPS_INSTALL_DIR |
~/.local/share |
Base directory for shdeps-owned install roots (github:repo, archive-style github:release, cargo, go, uv, npm). Raw release binaries install into SHDEPS_BIN_DIR instead. |
SHDEPS_BIN_DIR |
~/.local/bin |
Directory for binary symlinks and raw github:release binaries |
SHDEPS_LOG_LEVEL |
1 |
0=quiet, 1=normal, 2=verbose |
SHDEPS_JOBS |
auto (nproc) |
Max concurrent read-only probes. Explicit values win; 1 = sequential. |
Installs via the detected package manager (brew, apt, dnf, pacman, zypper, or apk). Packages are batched into a single install command for speed.
jq pkg
bat pkg apt:batcat
fd pkg apt:fdfind apt:fd-find,dnf:fd-find
dust pkg - - os:macos
htop pkg - - host:nas
Use aliases to map names across package managers. Use NONE to skip a dep on a specific manager (e.g., brew:NONE). Use filter with os: and host: prefixes to limit deps to specific platforms or machines.
Resolves to an existing concrete GitHub method before install, status, prune,
and method-transition logic runs. shdeps prefers github:release when the
latest stable release has a compatible asset for the current host and requested
command. If no compatible release asset is available, it falls back to
github:repo.
cgraf78/ds github
The manifest still records only the resolved concrete method
(github:release or github:repo), and shdeps list shows the same resolved
method. This keeps installed-state cleanup compatible with explicit methods
and avoids turning bare github into a third artifact ownership model.
Local development clones in $SHDEPS_GIT_DEV_DIR/<repo> are considered only
when github resolves to github:repo. A compatible release asset wins over a
local clone; use explicit github:repo when live-checkout behavior is required.
Clones a GitHub repo into $SHDEPS_INSTALL_DIR/<owner>/<repo> (default ~/.local/share/<owner>/<repo>). Prefers local dev clones in $SHDEPS_GIT_DEV_DIR/<repo> (default ~/git/<repo>, symlinked for live development). Falls back to a shallow clone for fresh installs. Every executable directly under the repo's bin/ directory is symlinked into $SHDEPS_BIN_DIR; existing regular files in $SHDEPS_BIN_DIR are preserved so shdeps never overwrites a user-owned command.
Local clone discovery is intentionally limited to github:repo. Other methods,
including github:release, ignore $SHDEPS_GIT_DEV_DIR so changing a dep's
method cleanly changes ownership and install behavior instead of accidentally
continuing to use a live checkout.
cgraf78/ds github:repo
The owner/repo is the name field. Override the repo URL with SHDEPS_<NAME>_REPO env vars.
Downloads the latest release binary from GitHub, matching the current OS and architecture. Handles tarballs, zips, compressed singles (.gz, .bz2, .zst), and raw binaries. Archive-style releases install into a shdeps-owned root under $SHDEPS_INSTALL_DIR/<owner>/<repo> so bundled assets can be linked. Raw and compressed single-binary releases install directly to $SHDEPS_BIN_DIR/<cmd>, preserving the historical Bash behavior for this method.
neovim/neovim github:release nvim
mvdan/sh github:release
The owner/repo is the name field.
Installs crates from crates.io via cargo install --root "$SHDEPS_INSTALL_DIR/<name>" <name>. Binaries land in a per-dep install directory and are symlinked into $SHDEPS_BIN_DIR.
ripgrep cargo rg
tokei cargo
The name field is the crate name. Override cmd when the crate installs a binary with a different name (e.g., the ripgrep crate installs rg). --reinstall passes --force to cargo.
Requires cargo on $PATH. If absent, shdeps warns once at startup and skips all cargo deps.
Installs Go binaries via GOBIN=... go install <module>@latest. Supports any module host (github.com, golang.org, k8s.io, private registries).
github.com/junegunn/fzf go
golang.org/x/tools/cmd/goimports go
github.com/charmbracelet/gum go gum
The name field is the full Go module path (including any cmd/... subpath). cmd defaults to the basename of the module path.
Requires go on $PATH. If absent, shdeps warns once at startup and skips all go deps.
Installs Python CLI tools from PyPI via uv tool install. Each dep gets its own isolated venv under $SHDEPS_INSTALL_DIR/<name>/tools/ with the binary at $SHDEPS_INSTALL_DIR/<name>/bin/<cmd>, symlinked into $SHDEPS_BIN_DIR.
ruff uv
black uv
mypy uv
poetry uv
The name field is the PyPI package name. Override cmd when the package's executable name differs from the package name. --reinstall passes --force to uv tool install.
Requires uv on $PATH (install via pipx install uv, brew install uv, or Astral's installer). If absent, shdeps warns once at startup and skips all uv deps.
Installs Node CLI packages from the npm registry via npm install -g --prefix "$SHDEPS_INSTALL_DIR/<name>" <name>. Binaries land under the per-dep install directory and are symlinked into $SHDEPS_BIN_DIR.
prettier npm
typescript npm tsc
The name field is the npm package name. Override cmd when the package's executable name differs from the package name.
Requires npm on $PATH. If absent, shdeps warns once at startup and skips all npm deps.
No built-in install logic. Entirely managed by a post-install hook file.
nerd-fonts custom
Place hook files in <hooks_dir>/<name>.sh. For methods whose name contains path segments (github:*, go), hooks nest accordingly — e.g., hooks.d/owner/repo.sh or hooks.d/github.com/owner/repo.sh. For custom deps, hooks define the full install lifecycle. For other methods, hooks provide optional post-install setup.
Custom dep hooks:
exists(name)— return 0 if installed (or not applicable), 1 if missing. Required forcustomdeps.version(name)— print version string to stdout. Optional.install(name)— perform the install unconditionally. shdeps only calls this whenexists()returns 1 or--reinstallis used.uninstall(name)— reverse whatinstall()orpost()created. Optional. Called byshdeps prunewhen removing an orphaned dep (any method). For custom deps, this is the only cleanup. For other methods, runs before the built-in cleanup.post(name)— optional post-install setup.
Non-custom dep hooks (pkg, github, github:repo, github:release, cargo, go, uv, npm):
post(name)— runs after shdeps installs/updates the dep (symlinking, config, etc.).
All public API functions are available to hook authors. See examples/hooks.d/example-hook.sh.
shdeps automatically discovers man pages and shell completions bundled inside
github:repo installs and archive-style github:release installs, then
symlinks them into standard XDG user-local directories. Tools like neovim, gum,
ripgrep, fd, bat, and hyperfine ship these files but they're not discoverable
without this linking. Raw single-binary releases and cargo, go, uv, and
npm deps do not provide bundled extras through shdeps — generate completions
from the tool itself in a post() hook (see
examples/hooks.d/example-hook.sh).
What gets linked:
| Type | Target directory | Auto-discovered by shell? |
|---|---|---|
| Man pages | ~/.local/share/man/man<N>/ |
No |
| Bash completions | ~/.local/share/bash-completion/completions/ |
Yes |
| Zsh completions | ~/.local/share/zsh/site-functions/ |
No |
| Fish completions | ~/.local/share/fish/vendor_completions.d/ |
Yes |
Required shell config (bash and fish need nothing):
# Man pages — add to your shell env
export MANPATH="$HOME/.local/share/man:$MANPATH"
# Zsh completions — add before compinit
fpath=("$HOME/.local/share/zsh/site-functions" $fpath)Symlinks are tracked per-dep in $SHDEPS_STATE_DIR/<name>.links. Running shdeps prune removes symlinks along with the dep. Updates clean stale symlinks before re-linking.
Usage: shdeps [options] <command> [args]
Commands:
update Install/update all dependencies
self-update Update shdeps itself (git pull, skips dirty trees)
list List all configured dependencies with status
check <name> Check if a specific dependency is installed
dep-root <name> Print a configured dependency root directory
dep-path <name> <rel> Print a path below a configured dependency root
dep-file <name> <rel> Print a readable regular file below a dependency root
dep-links <name> Print public command links owned by a dependency
prune Remove orphaned deps no longer in config
version Print shdeps version
help Show this help message
Options:
-c, --config <path> Config directory or file (default: ~/.config/shdeps/)
-f, --force Bypass TTL cache (check for updates now)
-R, --reinstall Force reinstall all dependencies (implies --force)
-q, --quiet Suppress non-result output and interactive prompts
-v, --verbose Verbose output (log level 2)
Prune options:
-y Skip confirmation prompt
--dry-run Show what would be removed without removing
Examples:
shdeps update
shdeps -c ~/.config/myapp/ update
shdeps --force update
shdeps list
shdeps check jq
shdeps dep-links cgraf78/sley
shdeps prune --dry-run
shdeps prune -y
Exit codes:
0 Success
1 Error
2 Usage error
When you remove a dep from your config, shdeps update will notify you that it's orphaned. Run shdeps prune to clean up the artifacts:
# Remove a dep from config, then update
shdeps update
# ==> 1 orphaned dep(s) (removed from config but still installed):
# neovim/neovim (github:release)
# Run: shdeps prune
shdeps prune # interactive confirmation
shdeps prune -y # skip confirmation
shdeps prune --dry-run # preview without removingFor pkg deps, prune warns that manual removal is needed (system packages may be shared). For custom deps, prune calls the optional uninstall() hook function.
export SHDEPS_CONF_DIR="$HOME/.config/myapp"
source /path/to/shdeps.sh
shdeps_updateFor projects that embed shdeps (e.g., dotfiles managers), install.sh --bootstrap provides a single sourceable entry point that handles discovery, sourcing, and CLI symlink setup:
# Set your project's config before bootstrapping
export SHDEPS_CONF_DIR="$HOME/.config/myapp"
export SHDEPS_HOOKS_DIR="$HOME/.config/myapp/hooks.d"
# Source shdeps — finds it automatically, installs if missing
. ~/git/shdeps/install.sh --bootstrap ||
. ~/.local/share/shdeps/install.sh --bootstrap ||
{ echo "shdeps not found"; return 1; }
# shdeps_update is now available
shdeps_updateThe --bootstrap flag:
- Finds shdeps.sh via
$SHDEPS_LIB→$SHDEPS_GIT_DEV_DIR/shdeps/→$SHDEPS_DIR/→ fresh install - Sources it into the caller (all
shdeps_*functions become available) - Symlinks the CLI into
$SHDEPS_BIN(default~/.local/bin/shdeps) - Keeps bootstrap fast by avoiding release freshness checks unless
SHDEPS_FORCE=1 - Is idempotent — safe to call multiple times
- Does not leak
set -einto the caller's shell
Call shdeps_update after bootstrap to install dependencies and run the
TTL-gated self-check.
The sourceable Bash API defines the public shdeps_ functions and delegates
behavior to the Rust binary through a small hidden bridge. This is the complete
shell contract available to callers and hook authors, and it is intended to
remain stable because Bash hook code depends on it. A Rust crate API also
exists; this Bash section documents the shell-facing contract specifically.
| Function | Description |
|---|---|
shdeps_update |
Install/update all dependencies |
shdeps_self_update [dir] |
Update shdeps itself (git pull, skips dirty trees) |
shdeps_prune [-y] [--dry-run] |
Remove orphaned deps no longer in config |
shdeps_load |
Parse config and return dep count |
shdeps_version |
Print version string |
shdeps_filter_match <spec> |
Check if current platform/host matches a filter spec (e.g., os:linux,host:nas, os:!wsl) |
shdeps_platform_match <spec> |
Check if current platform matches a spec (e.g., linux,macos, !wsl) |
shdeps_host_match <spec> |
Check if current hostname matches a spec (e.g., nas,taylor, !workstation) |
shdeps_platform |
Print normalized platform name (linux, macos, wsl) |
shdeps_force |
Return 0 if force mode is active (TTL bypass) |
shdeps_reinstall |
Return 0 if reinstall mode is active |
shdeps_pkg_mgr |
Print detected package manager (brew, apt, dnf, pacman, zypper, apk, or empty) |
shdeps_pkg_install <package> |
Install one package immediately via the detected package manager |
shdeps_pkg_install_for_mgr <mgr:package>... |
Install the package spec matching the detected package manager |
shdeps_install_dir |
Print base install directory ($SHDEPS_INSTALL_DIR, default ~/.local/share) |
shdeps_git_dev_dir |
Print git dev clone directory ($SHDEPS_GIT_DEV_DIR, default ~/git) |
shdeps_bin_dir |
Print binary symlink directory ($SHDEPS_BIN_DIR, default ~/.local/bin) |
shdeps_dep_root <name> |
Print a configured dependency root when shdeps owns one |
shdeps_dep_path <name> <rel> |
Print a path below a dependency root; rejects absolute and parent-traversal paths |
shdeps_dep_file <name> <rel> |
Print a dependency asset path only when it is a readable regular file |
shdeps_dep_links <name> |
Print shdeps-owned command links as command<TAB>public-path<TAB>target-path rows |
shdeps_dep_source <name> <rel> |
Source an existing dependency asset into the current Bash process |
shdeps_link_extras <name> <dir> |
Discover and symlink man pages and completions from an install dir |
shdeps_unlink_extras <name> |
Remove all extras symlinks tracked for a dep |
shdeps_require_sudo |
Acquire sudo; returns 0 if root or sudo obtained |
shdeps_log |
Normal log line |
shdeps_warn |
Warning (always shown unless quiet) |
shdeps_log_ok |
Success highlight |
shdeps_log_dim |
Dimmed / low-importance line |
shdeps_log_header |
Section header |
shdeps_dep_root follows the same ownership rules as installation. For
github:repo, it prefers $SHDEPS_GIT_DEV_DIR/<repo> when a local development
clone exists, then falls back to $SHDEPS_INSTALL_DIR/<owner>/<repo>. For
methods with per-dependency install directories (cargo, go, uv, npm,
and archive-style github:release installs), it returns
$SHDEPS_INSTALL_DIR/<name> when present. Raw single-binary github:release
installs and package-manager deps do not have shdeps-owned roots.
shdeps_dep_links is for health checks and integrations that need to verify
shdeps-owned public commands without duplicating shdeps' link rules. It is a
cheap local lookup: no hooks, package-manager probes, GitHub calls, or version
commands run. github:repo dependencies report every executable directly under
the resolved dependency bin/ directory; single-binary methods report the
configured command and manifest target.
shdeps ships a Lua module at lua/shdeps.lua inside the shdeps root. In a
source checkout that is typically ~/git/shdeps/lua/shdeps.lua; in a release
install it is $SHDEPS_DIR/lua/shdeps.lua (for the default installer,
~/.local/share/shdeps/lua/shdeps.lua). Release staging treats this file and
its lua/shdeps/ implementation modules as required public artifacts.
The Lua API is for runtime asset resolution from Lua hosts such as Neovim and
WezTerm. It deliberately delegates to the shdeps CLI instead of parsing
configuration itself, so install-root ownership, local development checkout
preference, filters, and path validation remain single-sourced in the Rust
implementation.
local shdeps = dofile(os.getenv("HOME") .. "/.local/share/shdeps/lua/shdeps.lua")
local api = shdeps.new({ home = os.getenv("HOME") })
local setup = api.dep_file("cgraf78/termnav", "lib/termnav/nvim/setup.lua")
local env = api.env()Lua hosts that start before shell init has normalized PATH can load the bootstrap helper from any known shdeps root and let it locate the active API module:
local bootstrap = dofile(os.getenv("HOME") .. "/.local/share/shdeps/lua/shdeps/bootstrap.lua")
local shdeps = bootstrap.load({ home = os.getenv("HOME") })
local api = shdeps.new({ home = os.getenv("HOME") })shdeps.new(options) accepts:
| Option | Description |
|---|---|
home |
Home directory to use for child commands; also defaults SHDEPS_CONF_DIR to <home>/.config/shdeps |
conf_dir |
Explicit shdeps config directory for child commands |
bin |
Explicit shdeps binary path; useful for tests and embedded launchers |
bin_dir |
Directory containing the shdeps binary |
root |
Explicit shdeps root containing shdeps, shdeps.sh, and lua/shdeps.lua |
env |
Table of extra environment overrides copied into child commands |
The returned object exposes:
| Function | Description |
|---|---|
api.dep_root(name) |
Return a configured dependency root, or nil when shdeps cannot resolve one |
api.dep_path(name, rel) |
Return a validated path below a dependency root, or nil |
api.dep_file(name, rel) |
Return a readable regular file below a dependency root, or nil |
api.env() |
Return a fresh table of environment overrides for shdeps-aware child tools |
The module also exposes default-object helpers with the same names:
shdeps.dep_root, shdeps.dep_path, shdeps.dep_file, and shdeps.env.
api.env() includes the selected HOME, SHDEPS_CONF_DIR, and a PATH
prefixed with the selected shdeps binary directory when the binary is known by
absolute path. That keeps dependency-owned child tools that shell out to
shdeps aligned with the same install that Lua used for resolution.
lua/shdeps/bootstrap.lua exposes:
| Function | Description |
|---|---|
bootstrap.load(options) |
Locate and return the shdeps Lua API module |
bootstrap.new(options) |
Locate the Lua API module and return shdeps.new(options) |
bootstrap.paths(options) |
Return candidate lua/shdeps.lua paths in the order they will be checked |
bootstrap.load(options) checks options.lua, $SHDEPS_LUA,
options.root, $SHDEPS_LIB's sibling root, $SHDEPS_GIT_DEV_DIR/shdeps,
<home>/git/shdeps, $SHDEPS_DIR, <home>/.local/share/shdeps, and finally
the bootstrap file's own root.
shdeps builds a normal Rust library crate (shdeps) alongside the CLI. The
binary, tests, and future Rust consumers call into documented modules such as
shdeps::config, shdeps::runtime, shdeps::status, shdeps::update, and
shdeps::prune.
The Rust API is intentionally documented in code because it owns the real
implementation boundaries. Its stability promise is narrower than the CLI and
Bash API while the Rust port is still landing: downstream Rust callers can use
the crate, but module boundaries may still tighten before the first public crate
release. shdeps does not currently promise a stable C ABI or libshdeps.a
static archive.
cargo test --locked
tests/shell/install-sh-test
tests/shell/installer-flow-test
tests/shell/release-scripts-test
SHDEPS_RUST_CLI=target/debug/shdeps tests/shell/shdeps-wrapper-testThe standalone CLI is a Rust binary. The sourceable Bash API and hook prelude
require Bash 4.3+; install.sh itself is kept compatible with the stock macOS
Bash 3.2 installer path.
After main is clean, current, and green in CI, cut a release with:
scripts/release.sh --pushThe script creates the UTC committer-timestamp/hash release tag and pushes it. GitHub Actions then builds and smokes the release artifacts. Archive names use:
shdeps-YYYYMMDD-HHMMSS-<8hex>-<asset-platform>.tar.gz
shdeps-YYYYMMDD-HHMMSS-<8hex>-<asset-platform>.tar.gz.sha256
