A Zsh module that lets you define meta commands - high-level actions like open, list, see, or copy that automatically resolve to the right shell command based on file type, MIME type, source patterns, and custom tests.
Instead of remembering dozens of commands for different file formats and protocols, you define rules once and use simple verbs everywhere.
Aliases are static. You can alias o to open, but what if you want open to behave differently for PDFs, archives, SSH hosts, and SQLite databases? zhint solves this with a rule engine:
# "open" a PDF -> opens in Preview (macOS) or converts to text
# "open" an SSH URI -> starts an SSH session
# "open" a .tar.gz -> opens in Finder
# "list" a .tar.gz -> lists archive contents
# "list" an SSH path -> runs ls on remote host
# "see" a text file -> cats it
# "see" a PDF -> extracts text with pdftotextAll through the same verbs: o, l, c, cp, etc.
- Define rules that match by action, source pattern, file type, MIME type, and custom tests
- URI-aware: parses
ssh://,http://, local paths, and more into components - Placeholder substitution:
%F(filepath),%H(host),%P(port), conditional%(P.-p %P.%), etc. - Command fallbacks: list multiple commands, the first available one is used
- Tab completion for actions, remote files (SSH), and archive contents
- Comes with built-in hint collections for archives, remote access, media, and more
git clone https://github.com/yemelgen/zhint.git
cd zhint
# Copy functions to your zsh functions directory
mkdir -p ~/.zsh/functions
cp functions/* ~/.zsh/functions/
# Add to your .zshrc
echo 'fpath=(~/.zsh/functions $fpath)' >> ~/.zshrc
echo 'autoload -Uz zhint zrun' >> ~/.zshrc
# Load the built-in hint collections (optional)
echo 'for f in /path/to/zhint/hints/*.zsh; do source "$f"; done' >> ~/.zshrcThen restart your shell or run source ~/.zshrc.
zhint -l # Should list loaded rules
zrun -h # Should show usageAdd the following to your .zshrc:
ZHINT_DIR="/path/to/zhint" # adjust to your install location
fpath=($ZHINT_DIR/functions $fpath)
autoload -Uz compinit && compinit
autoload -Uz zhint && zhint
autoload -Uz zrun && zrun
# Load built-in hint collections
for file in $ZHINT_DIR/hints/*.zsh; do
source "$file"
done
# Short aliases for meta commands
if typeset -f zrun > /dev/null; then
alias l='zrun l' # list
alias ll='zrun ll' # long list
alias c='zrun c' # see (cat)
alias o='zrun o' # open
alias e='zrun e' # edit
alias x='zrun x' # examine
alias s='zrun s' # search
alias cp='zrun cp' # copy
alias mv='zrun mv' # move
alias rm='zrun rm' # remove
alias cv='zrun cv' # convert
fiThen restart your shell or run source ~/.zshrc. Now you can just type:
l archive.tar.gz # list archive contents
l archive.tar.gz#subdir/ # list inside archive subdirectory (with tab completion!)
c archive.tar.gz#path/file.txt # see a file inside an archive
o myfile.pdf # open with default app
c ssh://server/etc/hosts # cat a remote file
cp report.json https://api.example.com/upload # POST to HTTP endpointA rule has 9 colon-separated fields:
actions:sources:src_types:src_mimes:destinations:dst_types:dst_mimes:tests:commands
| Field | Description | Example |
|---|---|---|
actions |
Comma-separated meta command names | open,o |
sources |
Glob patterns for source inputs | *.pdf, ssh:* |
src_types |
File type: f d b c s (string) |
f |
src_mimes |
MIME type patterns | text/*, application/pdf |
destinations |
Glob patterns for destination | *.zip, ssh:* |
dst_types |
Destination file type | d |
dst_mimes |
Destination MIME type | . |
tests |
Commands that must return 0 | which jq |
commands |
Comma-separated commands (first available is used) | code %F,vim %F,less %F |
Use . for "match anything" and * for "match any non-empty value".
# Open text files with code, fallback to open, then less
zhint 'open,o:*:f:text/*:.:.:.:.:code %F,open %F,less %F'
# List tar archive contents
zhint 'list,l:*:f:application/gzip:.:.:.:.:tar -tf %F'
# SSH into a remote host
zhint 'open,o:ssh\:*:.:.:.:.:.:.:ssh %(P.-p %P.%) %(U.%U@.%)%H'
# Copy files to remote via SCP
zhint 'copy,cp:*:f,d:.:ssh\:*:.:.:.:scp %(P0.-P %P0.%) %F %(U0.%U0@.%)%H0:%F0'
# POST JSON to an HTTP endpoint
zhint 'copy,cp:*:f:application/json:http*:.:.:.:curl -X POST -s %T -H "Content-Type: application/json" -d @%F | jq .'Placeholders are substituted into the commands field at runtime:
| Placeholder | Description |
|---|---|
%C |
The action name |
%X |
All sources (quoted) |
%T |
Destination |
%F |
All parsed file paths |
%A |
All source inputs |
%S |
All parsed schemes |
%U |
All parsed usernames |
%W |
All parsed passwords |
%H |
All parsed hostnames |
%P |
All parsed ports |
%Q |
All parsed queries |
%R |
All parsed fragments |
Access individual arguments with a digit suffix: %F0 (destination path), %F1 (first source), %F2 (second source), etc.
%(placeholder.ifTrue.ifFalse%)
If the placeholder has a value, use ifTrue; otherwise use ifFalse. For example:
%(P.-p %P.%) # Produces "-p 22" if port is set, empty otherwise
%(U.%U@.%) # Produces "user@" if username is set, empty otherwise
zhint -a 'rule...' # Add a rule (or just: zhint 'rule...')
zhint -d 'rule...' # Delete a rule
zhint -l # List all ruleszrun open -v myfile.pdf # Verbose: shows rule matching and placeholder substitution
zrun open -t myfile.pdf # Test mode: prints the resolved command without executingMIT License. See LICENSE.txt.
