Skip to content

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.

  1. Make the user commands directory if it doesn’t exist:

    Terminal window
    mkdir -p ~/.config/pounce/commands
  2. Create ~/.config/pounce/commands/say-hello.sh:

    #!/bin/bash
    # pounce: name = Say Hello
    # pounce: description = A friendly notification
    # pounce: icon = hand.wave
    osascript -e 'display notification "🐾" with title "Pounce"'
  3. 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 # 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.

KeyMeaningDefault
nameTitle shown in the palettethe filename (without .sh)
descriptionSubtitle(empty)
iconAn SF Symbol name (e.g. wifi, sparkles)sparkles
submenutrue / 1 — the command re-invokes Pounce for a second stepfalse

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:

Terminal window
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…

Beyond commands, pounce is a dmenu-style picker: pipe it lines, it returns the chosen one on stdout.

Terminal window
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
  • actionslabel | 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).

Pounce merges commands from several directories. Later sources shadow earlier ones by filename, so your own always win:

  1. $POUNCE_BUILTIN_DIR — the built-ins that ship with the package.
  2. $POUNCE_EXTRA_COMMAND_DIRS (colon-separated) — layers added by Nix consumers like the rice.
  3. $POUNCE_COMMAND_PATH (colon-separated) — ad-hoc extra directories.
  4. ~/.config/pounce/commandsyours, highest precedence.
  • Scripts don’t need the executable bit — Pounce runs them with bash either 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.