Writing pounce commands
Pounce has no plugin SDK. A command is one shell script with an optional metadata header. Drop it in a folder and it’s in the palette on the next open — no restart, no manifest, no account.
Your first command
Section titled “Your first command”-
Make the user commands directory if it doesn’t exist:
Terminal window mkdir -p ~/.config/pounce/commands -
Create
~/.config/pounce/commands/say-hello.sh:#!/bin/bash# pounce: name = Say Hello# pounce: description = A friendly notification# pounce: icon = hand.waveosascript -e 'display notification "🐾" with title "Pounce"' -
Open Pounce (⌘Space), type
say, and hit Return. Your command is right there — the notification fires.
That’s the whole model. No build step; the registry is re-scanned every time you summon the palette.
The metadata header
Section titled “The metadata header”The # pounce: key = value lines at the top describe how the command appears.
All are optional; parsing stops at the first non-header line (within the first
30 lines), and whitespace around the = is tolerant.
| Key | Meaning | Default |
|---|---|---|
name | Title shown in the palette | the filename (without .sh) |
description | Subtitle | (empty) |
icon | An SF Symbol name (e.g. wifi, sparkles) | sparkles |
submenu | true / 1 — the command re-invokes Pounce for a second step | false |
Two-step commands (submenus)
Section titled “Two-step commands (submenus)”Set submenu = true and pipe your options through pounce again to build a
two-step flow — the list swaps in place with no flicker:
#!/bin/bash# pounce: name = Brew Services# pounce: description = Start or stop a service# pounce: icon = cup.and.saucer# pounce: submenu = true
service=$(list_services | pounce -p "Service:")[ -n "$service" ] && toggle_service "$service"When the second step is a search rather than a list — the user types free
text and your script answers by fetching — pass --chain. On an empty match
Enter hands the raw text back, and --chain tells Pounce that text feeds another
pounce, so the window holds its loading skeleton instead of fading out between
steps:
query=$(printf '' | pounce --chain -p "App Store — type a search, then Enter")[ -n "$query" ] && mas search "$query" | pounce -p "Install:"Anything you can pipe into pounce becomes a picker. Which brings us to…
Pounce as a generic picker
Section titled “Pounce as a generic picker”Beyond commands, pounce is a dmenu-style picker: pipe it lines, it returns the
chosen one on stdout.
echo -e "one\ntwo\nthree" | pounce -p "Pick:"A piped list keeps the order you gave it — only the launcher’s own apps get sorted, so a ranking your script already computed (an App Store relevance list, say) survives into the picker instead of being alphabetized away.
Each input line can carry extra columns, tab-separated, for richer rows:
title <TAB> subtitle <TAB> icon <TAB> actions <TAB> group- actions —
label | key:label | key:label…(the first is Return, the rest are modifier combos). - group — an optional section header to bucket rows under (as Force Quit does with Applications / Background).
Where commands are discovered
Section titled “Where commands are discovered”Pounce merges commands from several directories. Later sources shadow earlier ones by filename, so your own always win:
$POUNCE_BUILTIN_DIR— the built-ins that ship with the package.$POUNCE_EXTRA_COMMAND_DIRS(colon-separated) — layers added by Nix consumers like the rice.$POUNCE_COMMAND_PATH(colon-separated) — ad-hoc extra directories.~/.config/pounce/commands— yours, highest precedence.
- Scripts don’t need the executable bit — Pounce runs them with
basheither way — but they must end in.sh: that suffix is how Pounce finds them, and a file without it is never listed as a command. - Keep long-running work fast to start; the palette dismisses as soon as the script is spawned.
- For grouped or dynamic output, remember the tab-separated column format above.
- Read the built-in commands for real, copy-pasteable examples of submenus, grouping, and icons.
See the Pounce config & CLI reference for every flag and config key.