Shims
A shim is a symlink in ~/.local/bin pointing at the kapsl binary, named after a tool. When you run it, kapsl sees the name in argv[0] and dispatches to that tool — same image, same scan, same sandbox, none of the prefix.
Install and list
kapsl --install jq
INSTALLED jq → ~/.local/bin/jq
kapsl --list
Installed shims:
jq
--install <tool> removes whatever was at that path first (a pipx shim, a Homebrew symlink) and links the binary in. --list shows what it installed — and, because it resolves symlinks, anything else on that path pointing at the binary. With none: No shims installed. Run 'kapsl --install <tool>' to add one.
The result is native-feeling, and the exit codes come through:
jq --version
jq-1.8.1
echo '{"name":"kapsl"}' | jq '.name'
"kapsl"
jq -e '.missing' a.json
# exit code 1 — jq's own, passed through
There is no jq on this host; jq-1.8.1 is the version inside the image. That is the whole trick: dispatch on argv[0]. The binary checks its own invocation name against the catalogue, and a name that resolves is run as a containerized tool with every remaining argument belonging to that tool.
KAPSL_ARGS: the only flag path
Because every argument after a shimmed name belongs to the tool, there is no place left on the command line for a kapsl flag. KAPSL_ARGS is that place:
KAPSL_ARGS="--skip-scan" jq -n 42
WARNING --skip-scan bypasses vulnerability scanning for this run
42
The rules, as implemented:
-
Flags only, shell-split. The value is split the way a shell would, so
KAPSL_ARGS="-e @pip:requirements-dev.txt"keeps the quoted argument whole. Any non-flag token is rejected loudly rather than silently becoming the tool name:KAPSL_ARGS="--skip-scan jq" jq -n 42 ERROR KAPSL_ARGS may only contain kapsl flags, found 'jq' (tool names and tool arguments belong on the command line) -
A value flag is a subset. From the environment you can carry the flags that make sense there —
-e/--env,-E,-p/--port,--cap,--platform,--runtime,--node,-n/--namespace,--pvc. Verbs like--searchand--completionsdo not. A value flag at the end with no value would swallow the tool name, so that fails with a named error too. -
CLI wins. In CLI mode (
kapsl --cap net jq …)KAPSL_ARGSis honored as well, and a flag set explicitly on the command line drops the environment copy — one recipe works everywhere, and the explicit spelling is the one that counts.
Nothing about a shim relaxes the sandbox; it only changes how you spell the invocation. And be deliberate about it, because the environment is under the control of whoever set it — a compromised parent shell can widen a run this way. It is the escape hatch for a one-off where the flag has no other route, documented as such.
Completions
kapsl --completions <shell> prints the completion script for kapsl's own flags (bash, zsh, fish, …), generated standalone — no config file, no container engine needed, so it works even with a broken kapsl.toml:
kapsl --completions zsh > ~/.zfunc/_boksWhen a shim is the right level
The three-level framing from the language pages: level 1 is kapsl tool … on the line; level 2 is the project overlay doing the configuration; level 3 is the shim, for daily driving. A shim is for named, PATH-stable tools — python, node, jq, cargo. It cannot install a built binary (kapsl --install ./hello fails: shims are for named tools), and if the name already belongs to something on your host — rustup's cargo, a Homebrew jq — the install replaces it and the other manager will not know that happened. Prefer level 2, or shim a project-scoped alias name instead of the contested one.