-
Notifications
You must be signed in to change notification settings - Fork 0
Configuration
The config file is config.toml in the config directory. Top-level keys:
shell = "zsh" # "zsh" (default) or "bash"
match = ["*.zsh"] # file patterns for plugins without `use`
apply = ["source"] # templates for plugins without `apply`
[env] # rendered before every plugin
ZSH_THEME = "robbyrussell"
plugins = "(git npm)" # values are assignment right-hand sides
[templates] # custom apply templates, see Templates
announce = "..."
[plugins.NAME] # one table per plugin[env] keys must be valid shell variable names. Values are pasted after =, so arrays are written as strings that already look like shell syntax.
Top-level match and apply set defaults for every plugin. A plugin's own use or apply replaces the default, the two never merge. Patterns in match follow the built-in rule, the first pattern that selects any file wins. Patterns in a plugin's use all contribute.
Each plugin sets exactly one of these. Two sources, or none, is an error that names the plugin.
| Key | Accepts | Installs to |
|---|---|---|
github |
owner/repository |
.../shelf/repos/github.com/owner/repository |
gist |
user/id |
.../shelf/repos/gist.github.com/... |
gitlab |
owner/repository |
.../shelf/repos/gitlab.com/... |
bitbucket |
team/repository |
.../shelf/repos/bitbucket.org/... |
codeberg |
owner/repository |
.../shelf/repos/codeberg.org/... |
git |
Git URL or local repo path | .../shelf/repos/<host>/<owner>/<repo> |
remote |
Absolute URL of one file | .../shelf/downloads/<host>/<path> |
local |
File or directory on disk | nothing, used in place |
inline |
Shell source in the TOML string | nothing, stored in the lock |
The forge keys clone over https by default. proto = "ssh" or "git" switches the transport, and shelf add --proto ssh writes the same field.
local paths expand ~, ~/..., and environment variables before shelf touches them, so local = "~/dotfiles/plugins" and local = "$HOME/dotfiles/plugins" both work.
| Field | Type | Effect |
|---|---|---|
use |
list of globs | Files to load, matched recursively against the plugin directory. Without it, the shell defaults below apply. |
ignore |
list of globs | Excluded from whatever use or the defaults selected. |
file |
path | Load this one file and nothing else. Fails the lock if the file is missing. |
dir |
path | Treat this subdirectory as the plugin root. |
apply |
list of template names | Defaults to ["source"]. |
branch, tag, rev
|
string | Git ref to check out. Precedence is rev, then branch, then tag. The ref is checked out detached. |
proto |
https, git, ssh
|
Forge transport only. On a plain git or remote source it is rejected. |
depth |
int | Clone depth. Default 1. 0 clones full history. |
cloneopts |
list of strings | Extra arguments passed to git clone. |
build |
list of strings | Each entry runs through sh -c in the plugin directory when the lock is built, before file selection, so pipes and redirection work. Needs a directory source, so inline and remote reject it. |
hooks |
table |
pre and post shell code wrapped around the plugin's files. |
profiles |
list of strings | Load only under those profiles. No profiles key means always. |
optional |
bool | Only valid with local. Missing path is skipped instead of failing. |
frozen |
bool | Never fetched by update, lock --update, or source --update. |
Default file globs when use is absent:
zsh: <name>.plugin.zsh <name>.zsh <name>.sh <name>.zsh-theme
*.plugin.zsh *.zsh *.sh *.zsh-theme
bash: <name>.plugin.bash <name>.plugin.sh <name>.bash <name>.sh
*.plugin.bash *.plugin.sh *.bash *.sh
use and ignore take doublestar globs relative to the plugin directory. ** crosses directories. A typo in a pattern fails the lock even when no file would have matched, so a broken glob does not silently load nothing.
depth and cloneopts only affect fresh clones. Apply them to an installed plugin with shelf lock --reinstall. Both are recorded in the lock, so later reinstalls reproduce the same clone.
build runs on fresh install, update, reinstall, and a stale relock. Its output goes to stderr and --quiet hides it. A non-zero exit fails the lock. Because it runs before file selection, use can pick up files that build just generated.
Selection follows the first rule that applies.
-
fileis set. Exactly that file loads, and the lock fails when it is missing. - The
localsource points at a single file. That file loads and its parent directory becomes the plugin directory. -
useis set. Every pattern contributes, matches from all patterns merge. - Top-level
matchis set. The first pattern that selects any file wins, the rest are skipped. - None of the above. The shell defaults listed above apply with the same first-match rule.
Patterns are doublestar globs matched against paths relative to the plugin directory, and {{ name }} in a pattern expands to the plugin name. Shelf validates every pattern before matching, so a typo fails the lock even when the tree holds no candidates. The .git directory is never walked. ignore subtracts from whatever selected the files, results sort by base name, and duplicates from overlapping patterns collapse to one path.
[plugins.kubeconfig]
github = "example/kube-plugin"
profiles = ["work"]shelf --profile work source # or SHELF_PROFILE=workPlugins without profiles load under every profile. A profile that matches nothing produces a warning on stderr and still loads the unprofiled plugins, which turns most typos into something you notice immediately.
Each profile gets its own lock file, plugins.work.lock, so switching profiles relocks once and then hits the fast path.
Every field shows up at least once below. A real config uses a handful of these.
# ~/.config/shelf/config.toml
shell = "zsh"
# Defaults for plugins that set neither field. First matching pattern wins.
match = ["{{ name }}.plugin.zsh", "*.plugin.zsh", "*.zsh-theme", "*.zsh"]
apply = ["source"]
[env]
EDITOR = "nvim"
plugins = "(git npm)" # looks like TOML, is shell syntax after the quotes
[templates] # custom templates, covered under Templates
announce = "echo \"loading {{ name }}\"\n{% for file in files %}source \"{{ file }}\"\n{% endfor %}"
[plugins.powerlevel10k]
github = "romkatv/powerlevel10k" # forge shorthand, owner/repo
frozen = true # never fetched by update
[plugins.tokyo-night]
github = "folke/tokyo-night.nvim"
tag = "v1.0.0" # ref pin, see precedence above
apply = ["defer"] # per-plugin templates
profiles = ["dotfiles"] # loads only with --profile dotfiles
[plugins.private-tools]
github = "myorg/tools"
proto = "ssh" # forge transport, https by default
depth = 0 # full history instead of the shallow default
cloneopts = ["--single-branch"] # extra arguments for git clone
rev = "0123456789abcdef0123456789abcdef01234567" # exact commit, wins over branch and tag
[plugins.oh-my-zsh-sudo]
github = "ohmyzsh/ohmyzsh"
dir = "plugins/sudo" # subdirectory is the plugin root
use = ["*.plugin.zsh"] # explicit file selection
ignore = ["**/test*"] # excluded from whatever matched
hooks = { pre = "echo before", post = "echo after" } # around the files when loading
[plugins.fzf]
github = "junegunn/fzf"
build = ["make install"] # sh -c in the source root, before file selection
file = "shell/key-bindings.zsh" # one exact file
[plugins.dotfiles-themes]
local = "~/dotfiles/themes" # ~ and $VAR expand
optional = true # skipped when the path is missing
[plugins.gist-snippet]
gist = "579d02802b1cc17baed07753d09f5009"
[plugins.from-gitlab]
gitlab = "owner/repo" # bitbucket and codeberg work the same way
[plugins.raw-file]
remote = "https://example.com/plugin.zsh"
[plugins.tiny]
inline = 'tiny() { echo "defined in the config"; }'Validation runs before anything installs. Exactly one source per plugin, optional only with local, proto only on forge sources, cloneopts and depth only on git sources, no source-specific field on inline, and build only on directory sources. The troubleshooting section lists every message this produces.