Commonly used CLI tools exposed through Podman shims for POSIX-oriented shells.
Shimmy wraps popular CLI tools in lightweight Podman containers, providing:
- No local installations required — tools run in containers
- Consistent environments across different machines and projects
- Customizable — override container images via environment variables
- Transparent usage — add to PATH and use tools as if they were installed locally
For tools that do not ship a usable upstream container image, Shimmy can build and cache a local image from a checked-in Containerfile context. The image tag is derived from the build-context hash and resolved platform, so Podman reuses the cached image until the Containerfile, supporting files, or host platform changes.
Shimmy also resolves the container platform at runtime without changing the command a user runs. Linux hosts run containers as linux/amd64, while macOS hosts run containers as linux/arm64. Explicit <PREFIX>_IMAGE overrides still select the image reference, and Shimmy applies the platform selection underneath.
Contributor guidance lives in CONTRIBUTING.md.
That document is the contributor source of truth, including naming conventions for files, functions, and variables. It is also referenced from AGENTS.md and the shared project prompt so future AI contributors pick it up automatically.
| Tool | Purpose | Image Source | Usage |
|---|---|---|---|
| aws | AWS CLI | public.ecr.aws/aws-cli/aws-cli:2.31.21 |
aws s3 ls, aws sts get-caller-identity |
| go | Go toolchain CLI | docker.io/library/golang:latest |
go version, go test ./... |
| jq | JSON processor | docker.io/stedolan/jq:latest |
jq .foo file.json |
| netcat | TCP/UDP debugging client | local build from images/netcat/Containerfile |
netcat --help, netcat example.com 443 |
| rg | Ripgrep search | docker.io/vszl/ripgrep:latest |
rg "pattern" . |
| task | Taskfile task runner | local build from images/task/Containerfile |
task --version, task --list |
| terraform | Infrastructure as Code | docker.io/hashicorp/terraform:latest |
terraform plan, terraform apply |
| textual | Textual developer CLI | local build from images/textual/Containerfile |
textual --help, textual run app.py |
- POSIX shell —
/bin/shor another POSIX-compatible shell for the current proof-of-concept rewrite - Podman CLI — Explicit required dependency. Podman Desktop is not required.
For macOS run
podman machine initif needed, then runpodman machine startfrom a normal user shell after installation. Install and configure for rootless operation separately before using Shimmy. Official install guide: https://podman.io/docs/installation If Podman is installed from the macOS pkg installer, the binary may live at/opt/podman/bin/podman.shimmy activateaccounts for that path for interactive shell activation, and Shimmy's shared Podman preflight also checks it directly for runtime shims plus Podman-backed lifecycle commands such asshimmy update --pull,shimmy update --build, andshimmy test. When Podman is installed but unreachable, Shimmy now fails with shared guidance that points topodman info, user-shellpodman machine start,podman system connection list, andCONTAINER_HOSTverification.
Shimmy expects a working rootless Podman engine setup. On some minimal Linux environments, including Chromebook's Crostini, rootless requirements for subordinate id ranges do not exist. In this scenario Podman will warn "no subuid ranges found" and fall back to a single UID/GID mapping.
Check your configuration (should output a range of id values, eg: 10000:65536):
grep "^$(whoami):" /etc/subuid /etc/subgid
When only a single id is present run this command to correct.
- sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 $(whoami)
- podman system migrate
Shimmy includes a packaged Codex plugin under plugins/shimmy. The plugin provides Shimmy-specific skills plus the jq and ripgrep tool skills, so Codex can apply the repository's conventions when creating shims, troubleshooting Podman-backed commands, managing Shimmy installs, and working on the jq or rg wrappers.
The primary plugin intentionally does not bundle every tool skill. Optional tool-specific plugins can supplement it later, for example AWS, Terraform, or Go plugins, but the Codex plugin manifest used here does not declare plugin dependencies. Install or enable supplemental plugins independently when a workstation or repo needs those capabilities.
This repository also includes .agents/plugins/marketplace.json, which registers the local Shimmy plugin from ./plugins/shimmy.
Open a new Codex or VS Code Codex session from the Shimmy checkout after the plugin files are present. Codex discovers plugins at session startup, so an already-running session may not see newly added plugin metadata until it is restarted.
When prompted, install or enable the shimmy plugin from the local marketplace. Once enabled, requests that involve Shimmy, jq, or ripgrep shim work can use the packaged skills automatically.
For one workstation-wide setup, copy or sync the packaged plugin into your home plugin directory and register it in your home marketplace:
~/plugins/shimmy/
~/.agents/plugins/marketplace.json
The home marketplace entry should point at ./plugins/shimmy:
{
"name": "shimmy-local",
"interface": {
"displayName": "Shimmy Local"
},
"plugins": [
{
"name": "shimmy",
"source": {
"source": "local",
"path": "./plugins/shimmy"
},
"policy": {
"installation": "AVAILABLE",
"authentication": "ON_INSTALL"
},
"category": "Developer Tools"
}
]
}After that, start a new Codex session in any repository on that workstation and install or enable the Shimmy plugin from the local marketplace.
Clone or copy this repository, then use the packaged plugin directory from plugins/shimmy. For a repo-local setup, open Codex from the Shimmy checkout so it can read .agents/plugins/marketplace.json. For a workstation-wide setup, copy plugins/shimmy to ~/plugins/shimmy on the target machine and add the home marketplace entry shown above.
Linux and macOS can use the same plugin layout. Shimmy itself still requires a POSIX shell and a working rootless Podman installation. On macOS, start the Podman machine from a normal user shell before using Podman-backed Shimmy tools:
podman machine start
podman infoOn Windows, use WSL or another POSIX-compatible environment for Shimmy workflows. On Chromebooks, use Crostini and a bash shell.
Put the plugin and marketplace files in the Linux home directory used by Codex in that environment, for example /home/<user>/plugins/shimmy and /home/<user>/.agents/plugins/marketplace.json. Configure Podman inside that same environment before running Shimmy-backed tools.
Use the repo-root shimmy wrapper as the primary control surface:
./shimmy install
./shimmy status
./shimmy update --pull --build
./shimmy test
./shimmy uninstallThe wrapper delegates to script-based interfaces in scripts/.
After ./shimmy install, activate the installed Shimmy paths in the current shell immediately with:
eval "$(./shimmy activate)"./shimmy install writes one activation file under the install root and updates your shell startup file by default so future shells can source it. It cannot change your current shell session, so use eval "$(./shimmy activate)" to make the install available immediately.
The installed activation file is:
~/.config/shimmy/activate.shStartup files contain only a small managed block that sources that activation file. This keeps the PATH logic in one place even when multiple startup files need to load Shimmy.
Supported startup shells:
| Shell | Startup files updated by default |
|---|---|
bash |
~/.bashrc and the first existing login file from ~/.bash_profile, ~/.bash_login, or ~/.profile; creates ~/.bash_profile if none exist |
zsh |
~/.zshrc |
sh |
~/.profile |
ksh |
~/.profile |
mksh |
~/.profile |
You can override or skip that behavior:
./shimmy install --shell zsh
./shimmy install --startup-file "$HOME/.config/shimmy/profile"
./shimmy install --no-startupYou can repeat --startup-file when more than one startup file should source Shimmy. Shimmy writes one managed startup block per startup file, so rerunning install refreshes those blocks idempotently instead of appending duplicates.
If you prefer not to modify your startup files, use --no-startup and add activation logic manually.
./shimmy install --no-startup
eval "$(./shimmy activate)"If your startup file ever needs to be rewritten or repaired later, use update:
./shimmy update --repair-startup
./shimmy update --repair-startup --shell zsh
./shimmy update --repair-startup --startup-file "$HOME/.config/shimmy/profile"Without --repair-startup, update refreshes installed assets only and leaves startup files alone.
The active install layout is always derived from one install root, which defaults to ~/.config/shimmy and can be overridden with --install-dir.
Common install arguments still pass through to the installer:
./shimmy install --install-dir "$HOME/.local/share/shimmy"
./shimmy install --shim aws --shim terraformUse the underlying scripts directly when you want the lower-level interfaces explicitly:
sh ./scripts/install-shimmy.sh
sh ./scripts/status-shimmy.sh
sh ./scripts/update-shimmy.sh --pull --build
sh ./scripts/test-shimmy.sh
sh ./scripts/install-shimmy.sh --uninstallThis is the same functionality the wrapper exposes, without the repo-root dispatcher.
Shimmy stores install state in one POSIX-readable manifest under the install root:
~/.config/shimmy/install-manifest.txtThe manifest is the source of truth for activation, status, update, startup-file repair, and uninstall. It uses one key=value entry per line and repeated keys for lists.
Core fields include:
install_dir— active install rootactivate_file— generated activation scriptstartup_shell— shell used for managed startup-file selectionstartup_file— managed startup file; repeated when more than one file is updatedshim— installed shim name; repeated for each installed tool
Shimmy also reserves shimmy_* fields for lifecycle metadata such as the installed source URL/ref, update policy, last update check, previous ref, and validation status. Install and update preserve unknown shimmy_* fields so agent-driven lifecycle metadata is not lost during normal refreshes.
For machine-readable inspection, use:
./shimmy status --format manifestThe current implementation can reinstall from the checked-out source, refresh remote images with --pull, rebuild local images with --build, repair startup files, and preserve lifecycle metadata. Full latest-version resolution, semver enforcement such as >=0.10.0, and rollback to release versions require a release/tag/version convention for Shimmy itself.
Once shims are in your PATH, use tools naturally:
# Terraform
terraform version
terraform -chdir=examples/dev plan
# AWS CLI
aws s3 ls
aws sts get-caller-identity
# Go CLI
go version
go test ./...
# jq
echo '{"name": "shimmy"}' | jq .name
# ripgrep
rg "pattern" .
# Task
task --version
task --list
# Textual CLI
textual --help
textual run app.pyEach shim respects environment variables for customization.
AWS_IMAGE— Container image (default:public.ecr.aws/aws-cli/aws-cli:2.31.21)AWS_IMAGE_PULL— Set toalwaysto force pulling the latest image
Example:
AWS_IMAGE=public.ecr.aws/aws-cli/aws-cli:2.31.21 aws --versionMounts:
$PWD→/work(read-write)~/.aws→/root/.aws(read-only, if exists)
Environment variables forwarded:
AWS_*
Runtime platform:
- Linux →
linux/amd64 - macOS →
linux/arm64
GO_IMAGE— Container image (default:docker.io/library/golang:latest)GO_IMAGE_PULL— Set toalwaysto force pulling the latest image
Example:
GO_IMAGE=docker.io/library/golang:latest go versionMounts:
$PWD→/work(read-write)
Container I/O:
- Keeps stdin open without allocating a container TTY, which avoids Podman terminal resize signal warnings for short-lived commands such as
go help test.
Runtime platform:
- Linux →
linux/amd64 - macOS →
linux/arm64
JQ_IMAGE— Container image (default:docker.io/stedolan/jq:latest)JQ_IMAGE_PULL— Set toalwaysto force pulling the latest image
Example:
JQ_IMAGE=ghcr.io/jqlang/jq:latest jq --versionMounts:
$PWD→/work(read-write)
Runtime platform:
- Linux →
linux/amd64 - macOS →
linux/arm64
NETCAT_IMAGE— Override the runtime image entirelyNETCAT_IMAGE_BUILD— Set toalwaysto rebuild the local Netcat image even if it is already cachedNETCAT_IMAGE_PULL— Set toalwaysto force pullingNETCAT_IMAGEwhen using an explicit remote overrideNETCAT_BASE_IMAGE— Override theContainerfilebase image (default build arg:registry.access.redhat.com/ubi9/ubi-minimal:latest)
Example:
NETCAT_BASE_IMAGE=registry.access.redhat.com/ubi9/ubi-minimal:latest netcat --helpThe default Netcat image is built locally from images/netcat/Containerfile, which starts from UBI 9 minimal and installs the nmap-ncat package. This keeps the base image small while still using a practical Red Hat-supported package manager for the install. Shimmy tags the resulting image under localhost/shimmy-netcat:<context-hash>-<platform> so Podman keeps a reusable local cache and automatically rebuilds when the build context or runtime platform changes.
Mounts:
$PWD→/work(read-write)
Runtime platform:
- Linux →
linux/amd64 - macOS →
linux/arm64
RG_IMAGE— Container image (default:docker.io/vszl/ripgrep:latest)RG_IMAGE_PULL— Set toalwaysto force pulling the latest image
Example:
RG_IMAGE=docker.io/vszl/ripgrep:latest rg --versionMounts:
$PWD→/work(read-write)
Runtime platform:
- Linux →
linux/amd64 - macOS →
linux/arm64
TASK_IMAGE— Override the runtime image entirelyTASK_IMAGE_BUILD— Set toalwaysto rebuild the local Task image even if it is already cachedTASK_IMAGE_PULL— Set toalwaysto force pullingTASK_IMAGEwhen using an explicit remote overrideTASK_BASE_IMAGE— Override theContainerfilebase image (default build arg:alpine:3.22)TASK_VERSION— Override the Task release version installed into the local image (default build arg:v3.45.5)
Example:
TASK_VERSION=v3.45.5 task --versionThe default Task image is built locally from images/task/Containerfile, which starts from Alpine and installs the official Task release binary from GitHub Releases. Shimmy tags the resulting image under localhost/shimmy-task:<context-hash>-<platform> so Podman keeps a reusable local cache and automatically rebuilds when the build context or runtime platform changes.
Mounts:
$PWD→$PWD(read-write)$PWD→/work(read-write)$HOME→$HOME(read-write, if it exists)/tmp→/tmp(read-write, if it exists)
When CONTAINER_HOST points at a unix-domain Podman socket, the task shim also forwards that socket into the container so Task-driven automation can launch other shims.
Environment variables forwarded:
CONTAINER_HOSTwhen explicitly setSHIMMY_HOST_PATHHOMEwhen the home directory mount is enabled
Runtime platform:
- Linux →
linux/amd64 - macOS →
linux/arm64
TF_IMAGE— Container image (default:docker.io/hashicorp/terraform:latest)TF_IMAGE_PULL— Set toalwaysto force pulling the latest image
Example:
TF_IMAGE=docker.io/hashicorp/terraform:latest
TF_IMAGE_PULL=always
terraform version
terraform planMounts:
$PWD→/work(read-write)~/.aws→/root/.aws(read-only, if exists)~/.terraform.d/plugin-cache→/root/.terraform.d/plugin-cache(if exists)
Environment variables forwarded:
AWS_*TF_VAR_*
Runtime platform:
- Linux →
linux/amd64 - macOS →
linux/arm64
TEXTUAL_IMAGE— Override the runtime image entirelyTEXTUAL_IMAGE_BUILD— Set toalwaysto rebuild the local Textual image even if it is already cachedTEXTUAL_IMAGE_PULL— Set toalwaysto force pullingTEXTUAL_IMAGEwhen using an explicit remote overrideTEXTUAL_BASE_IMAGE— Override theContainerfilebase image (default build arg:python:3.13-slim-bookworm)
Example:
TEXTUAL_BASE_IMAGE=python:3.13-slim-bookworm textual --helpThe default Textual image is built locally from images/textual/Containerfile, which starts from python:3.13-slim-bookworm and installs textual plus textual-dev. This matches the official Textual docs, where the textual command comes from the developer tools package. Shimmy tags the resulting image under localhost/shimmy-textual:<context-hash>-<platform> so Podman keeps a reusable local cache and automatically rebuilds when the build context or runtime platform changes.
Mounts:
$PWD→/work(read-write)
Runtime platform:
- Linux →
linux/amd64 - macOS →
linux/arm64
The interactive shims (aws, task, terraform, and textual) request -it only when both stdin and stdout are attached to a terminal, so version and help commands still work cleanly in scripts and smoke tests.
Run the test suite to validate that shim containers run via Podman:
./shimmy test
# or
sh ./scripts/test-shimmy.shTests verify:
/bin/shparser compatibility for the repo wrapper, shared shim helpers, repo lifecycle scripts, and all supported in-scope shims- install, activate, status, machine-readable manifest output, update, startup-file repair, and uninstall behavior for the single-root manifest layout
- live Podman execution for the supported shim set:
aws,jq,netcat,rg,task,terraform, andtextual
shimmy/
├── shimmy # Repo-root wrapper command
├── shims/ # OCI wrapper scripts
│ ├── aws
│ ├── jq
│ ├── netcat
│ ├── rg
│ ├── task
│ ├── textual
│ └── terraform
├── images/ # Custom shim image build contexts
│ ├── netcat
│ ├── task
│ └── textual
├── lib/
│ ├── repo/ # Repo-only sourced helpers for wrapper/scripts
│ └── shims/ # Installed shared helper scripts for shims
├── scripts/
│ ├── install-shimmy.sh # Installation script
│ ├── status-shimmy.sh # Status script
│ ├── test-shimmy.sh # Test suite
│ └── update-shimmy.sh # Update script
├── plugins/
│ └── shimmy/ # Packaged Codex plugin for Shimmy skills
├── .agents/
│ ├── plugins/ # Local Codex plugin marketplace metadata
│ └── skills/ # Repo-local agent skills used while developing Shimmy
├── .pre-commit-config.yaml # Git https://github.com/pre-commit/pre-commit-hooks
├── .github/
│ └── workflows/
│ └── test.yml # CI/CD workflow
└── README.md # This file
This code was and human-reviewed/curated in concert with Codex GPT-5.5.
See LICENSE file for details.