Pal
A fast, extensible command palette for Linux. Launch apps, switch windows, control audio, manage clipboard, and more - all from a unified interface.
pal run fzf apps # launch applications
pal run rofi pals # pick a palette to run
pal run fzf combine # combined view of multiple palettes
Features
- Builtin palettes - Apps, bookmarks, SSH hosts, processes, and more
- Builtin frontends - fzf, rofi, and stdin work out of the box
- Plugin system - Extend with bash, python, or any language
- Layered config - Defaults + user config + project config + env vars
- Icon support - XDG icons for rofi, UTF/Nerd Font icons for terminal frontends
- Combine palettes - Merge multiple palettes into one view
- Input palettes - Text input mode with live results (calculator, eval, etc.)
- Prompts - Ask for user input on pick, usable from plugins and standalone scripts
- Caching - Pre-computed display for fast startup on heavy palettes
Installation
Requires Rust 1.70+ (rustup)
Quick Start
# Initialize config at ~/.config/pal/config.toml
# Run with default palette and frontend
# Run specific palette with specific frontend
# List items without frontend (useful for debugging)
# Prompt user for input
# Run an action on a value
|
# List installed remote plugins
# Update all remote plugins
# Show loaded configuration
Builtin Palettes
| Palette | Description |
|---|---|
apps |
List and launch desktop applications |
bookmarks |
Browser bookmarks (Firefox/Chrome) |
ssh |
SSH hosts from ~/.ssh/config |
pals |
List and run other palettes |
psg |
List and kill processes |
combine |
Combine multiple palettes into one |
Builtin Frontends
| Frontend | Description |
|---|---|
fzf |
Terminal fuzzy finder |
rofi |
Desktop launcher with icons |
stdin |
Simple numbered list selection |
Configuration
Config is loaded in order (later overrides earlier):
- Built-in defaults
pal.default.toml(in current directory)~/.config/pal/config.toml(user config)pal.toml(in current directory)-c <path>(CLI argument)PAL_*environment variables
Example Config
[]
= "combine"
= "fzf"
[]
[]
= "builtin/palettes/combine"
= "view-grid"
= ["pals", "quickcmds"]
[]
= "utilities-terminal"
= true
= true
= "~/.config/pal/commands.json"
= "cmd"
= "cmd"
[]
= "~/.config/pal/plugins/audio"
= "audio-card"
Data Files (auto_list)
For simple palettes, use a JSON lines file or a JSON array:
The id field is optional and defaults to name if missing.
Icons
Items and palettes support three icon types, used by different frontends:
| Field | Used by | Example |
|---|---|---|
icon_xdg |
rofi (freedesktop icon names) | utilities-terminal |
icon_utf |
fzf (UTF-8/Nerd Font glyphs) | |
icon |
Fallback for either | terminal |
Rofi prefers icon_xdg, fzf prefers icon_utf, both fall back to icon. Character icons (non-ASCII) are rendered inline, while XDG icon names are shown as images in rofi.
Set a palette-level icon in config, and it applies to all items that don't have their own:
[]
= "utilities-terminal"
= ""
Input Palettes
Input palettes accept text input instead of filtering a static list. The query is passed to the plugin's list command via stdin, and the plugin returns items based on it.
[]
= "github:zcag/pal/plugins/palettes/calc"
= true
= "Calculate"
| Field | Description |
|---|---|
input |
Enable text input mode |
input_prompt |
Custom prompt message (defaults to palette name) |
fzf reloads results live as you type using --bind change:reload. rofi uses script mode - type a query, press Enter to see results, then select. Other frontends use a two-step prompt then select flow.
The plugin's list command receives the query on stdin:
Prompts
Prompts let you collect user input before or during pick. There are two mechanisms:
Item-level prompts
Add a prompts array to any item. When the item is picked, each prompt is shown to the user via the active frontend. Collected values are substituted into {{key}} placeholders in all item fields and injected as PAL_<KEY> env vars.
This works in data files, plugin output, and through the combine palette.
Prompt types
| Type | Description | Extra fields |
|---|---|---|
text |
Free text input (default) | |
choice |
Select from a list | options: array of strings |
pal prompt command
Prompt the user directly from any script - plugin pick scripts, custom scripts, or anywhere. Uses the same prompt spec format.
# Text prompt
host=
# Choice prompt
algo=
# Multiple prompts - returns JSON object
result=
# → {"host": "myserver", "port": "8080"}
# From stdin
|
When called inside a pal flow (e.g., from a plugin pick script), it uses the current frontend (_PAL_FRONTEND). When called standalone, it uses the config default.
Example plugin pick script using pal prompt:
Caching
For palettes with expensive list operations (like combine with many sub-palettes), enable caching to pre-compute the frontend display:
[]
= "builtin/palettes/combine"
= ["apps", "bookmarks", "cmds"]
= true
On first run, items are listed, formatted, and cached at ~/.cache/pal/. Subsequent runs read directly from cache and regenerate in the background for next time. Currently supported for the rofi frontend.
Plugin Development
Plugins are directories with a plugin.toml and an executable.
plugin.toml
= "my-palette"
= "Description of my palette"
= "0.1"
= ["run.sh"]
run.sh
#!/usr/bin/env bash
Plugin Config Access
Plugins receive their config via environment variable:
# In your plugin
cfg=
Remote Plugins
Load plugins directly from GitHub repositories:
[]
= "github:zcag/pal/plugins/palettes/ip"
# With specific branch or tag
[]
= "github:zcag/pal/plugins/palettes/ip@v1.0"
# Data files also support github: URLs
[]
= "github:zcag/pal/plugins/palettes/colors"
= "github:zcag/pal/plugins/palettes/colors/data.json"
Plugins are cloned on first use to ~/.local/share/pal/plugins/ using git sparse checkout. Requires git to be installed.
Example Plugins
The plugins/ directory contains ready-to-use plugins. Use them directly via GitHub:
[]
= "github:zcag/pal/plugins/palettes/audio"
| Plugin | Description |
|---|---|
audio |
Switch audio output devices (PipeWire/PulseAudio) |
clipboard |
Clipboard history (cliphist/clipman) |
wifi |
Connect to WiFi networks (nmcli) |
windows |
Focus windows (Hyprland/Sway/X11) |
systemd |
Manage systemd services |
ble |
Connect Bluetooth devices |
hue |
Control Philips Hue scenes |
repos |
Browse GitHub repositories (gh cli) |
chars |
Unicode character picker |
icons |
Freedesktop icon picker |
nerd |
Nerd Font icon picker |
emoji |
Emoji picker |
colors |
Color picker (hex/rgb/hsl) |
calc |
Calculator (qalc/bc) |
ip |
Network info (public/local IP, gateway, DNS) |
docker |
Docker container management |
op |
1Password items |
media |
Media player control (playerctl) |
power |
Power menu (shutdown, reboot, etc.) |
Actions
Actions define what happens when an item is picked with auto_pick:
[]
= true
= true
= "commands.json"
= "cmd" # run as shell command
= "cmd" # field containing the command
Built-in Actions
| Action | Description |
|---|---|
cmd |
Execute the value as a shell command |
copy |
Copy value to clipboard (wl-copy/xclip/pbcopy) with notification |
open |
Open value with xdg-open/open |
Actions are resolved locally first (plugins/actions/ in config dir), then fetched from GitHub as a fallback.
Item Environment Variables
When an item is picked, all its JSON keys are injected as PAL_<KEY> environment variables into the action process:
# Available in your action script:
This works for both auto_pick actions and plugin-based palettes.
Custom Actions
Create custom actions as plugins in plugins/actions/:
# plugins/actions/notify/run.sh
Environment Variables
| Variable | Description |
|---|---|
_PAL_CONFIG |
Path to current config file |
_PAL_CONFIG_DIR |
Directory of current config file |
_PAL_PALETTE |
Current palette name |
_PAL_FRONTEND |
Current frontend name |
_PAL_PLUGIN_CONFIG |
JSON config for current plugin |
PAL_<KEY> |
Item key-value pairs injected on pick (e.g. PAL_NAME, PAL_HEX) |
Tips
Combine for a unified launcher
[]
= "builtin/palettes/combine"
= ["apps", "bookmarks", "quickcmds"]
Different frontends for different contexts
# Terminal
# Desktop (bind to hotkey)
Project-specific palettes
Create pal.toml in your project:
[]
= true
= "scripts.json"
= "cmd"
= "cmd"
Roadmap
- capability system between palettes (or items of palettes) and fe's
- hotlink support.
pal://run?fe=rofi?palette=commands?item="confetti" -
pal integrate xdgfor registering hotlink -
pal doctorfor config validation - REST API frontend
Disclaimer
This is a rewrite of my personal bash spaghetti that I implemented over the years, covering many palettes and frontends for various stuff. Inspired by Raycast - an awesome macOS Spotlight alternative that's also quite customizable. Many of the custom palettes here are ported from my custom Raycast plugins after I left macOS.
This is also an experiment for myself on Rust and AI-assisted coding. I have minimal Rust knowledge, and this is my first time properly using an AI agent for development. Claude Code was heavily used in this project - it straight up implemented a ton of the palettes based on my descriptions and reference bash scripts from the original pal.
License
MIT