# Config reference (/docs/pounce/config)



## Config file [#config-file]

Pounce reads `~/.config/pounce/config.json`, re-read on every open. No restart
is needed, except for the things the daemon sets up **at startup**: `windows`,
`autoQuit`, `appHotkeys`, `pages`, `mouseChords`, and the `items` hotkeys. Those are captured
when armed and need a restart to change:

```sh
launchctl kickstart -k "gui/$(id -u)/com.hausfold.pounce"
```

Inside haus, a rebuild does that bounce for you when one of those keys moves.

```sh
pounce settings         # the same settings, as a window
pounce config init      # writes ~/.config/pounce/config.json
pounce config print     # …or just look at it, touching nothing
```

[`pounce settings`](/docs/pounce/cli#subcommands) (also the **Pounce Settings**
row, or `mode:settings` for a hotkey of your own) shows the value pounce will
actually use rather than what the file says before clamping, and it edits this
file rather than shadowing it: a click rewrites exactly **one line**, so your
comments, your ordering and any key pounce has never heard of survive. Setting
something back to its default comments its line out again, which is this file's
own grammar for "unset".

`init` writes **every** setting at its default, one commented-out line each with
a sentence above it, so you make the file minimal by deleting the lines you never
touched. It never overwrites an existing config: it writes `config.json.new`
beside it, and `--force` replaces.

<Callout type="warn" title="Inside haus, `config init` refuses">
  Your `config.json` is generated from `haus.launcher.*`, and the next rebuild
  would put the generated one straight back. The Settings window says the same
  thing a different way: it opens read-only, every control showing what your host
  file chose. Change it in your host file instead; see [the Launcher
  room](/docs/haus/rooms/launcher).
</Callout>

Comments and trailing commas are fine (JSON5); unknown keys are ignored, so an
older Pounce never chokes on a config written by a newer one.

```jsonc
{
  "theme": "nebelung",       // "nebelung" (default), "mocha", or a themes/ file
  "themeLight": "nebelung-latte",  // used in macOS Light Mode
  "themeDark": "nebelung",         // used in macOS Dark Mode
  "windowMode": "default",   // "default" (720px) or "compact" (600px, tighter)
  "scale": 1.0,              // 0.8-2.0: how big the whole UI is drawn
  "fontFamily": null,        // family name; null (default) is macOS's own
  "stage": {                 // what an empty query shows, see below
    "enabled": true,
    "tiles": 7               // how many tiles the strip holds (1-12)
  },
  "ranking": {               // how the launcher learns, see below
    "learnFromQueries": true,
    "stickyTiles": true,
    "useContext": true,
    "predictNext": true
  },
  "hotkey": {
    "enabled": true,
    "key": "space",          // "space", "return", "tab", "escape", "a"–"z", "0"–"9"
    "modifiers": ["cmd"]     // any of "cmd", "shift", "opt", "ctrl"
  },
  "clipboard": {
    "enabled": true,
    "maxEntries": 200,
    "blacklistBundleIds": ["com.apple.Passwords"],
    "autoPaste": false       // synthesize ⌘V into the prior app (needs Accessibility)
  },
  "quickAnswers": { "currency": true },   // ECB rates, so "100 usd in eur" answers inline
  "updates": { "check": true },           // nudge (never install) when a release is out
  "urlScheme": {             // pounce://run?item=… — an item behind a link, see below
    "enabled": true,
    "confirm": true          // ask on screen before a link RUNS something
  },
  "shortcuts": { "enabled": true },       // your Shortcuts library, as rows
  "fileSearch": {
    "enabled": true,
    "homeOnly": true,        // scope to ~ instead of the whole Spotlight index
    "maxResults": 60
  },
  "systemSettings": {         // System Settings, opened by the setting rather than the pane
    "enabled": true,
    "subItemMinQuery": 3,     // characters before the settings INSIDE a pane join the results; 0 = from the first keystroke
    "maxPerPane": 8           // how many settings one pane may contribute to a list; 0 = no cap
  },
  "apps": {
    "demoteBundleIds": [],   // sink these below everything else; REPLACES a built-in list of Apple utilities (Feedback Assistant, Audio MIDI Setup, …)
    "hideBundleIds": []      // drop these from the list entirely
  },
  "windows": {
    "enabled": false,        // the MRU window switcher (needs Accessibility)
    "key": "tab",
    "modifiers": ["cmd"]
  },
  "autoQuit": {
    "enabled": false,
    "delay": 2,
    "exclude": ["com.apple.finder"]  // REPLACES the default, doesn't extend it
  },
  "appHotkeys": {            // chords that only exist over one app, see below
    "enabled": false,        // needs Accessibility
    "scopes": []          // worked example below
  },
  "pages": {                 // an MRU walk over a family of workspaces, see below
    "enabled": false,        // needs Accessibility
    "key": "tab",
    "modifiers": ["ctrl"],
    "prefix": "T",           // walks workspaces named prefix, or prefix/…
    "bundleId": null,        // scope the walk to one app; null = anywhere
    "mruFile": null          // recency file your window manager's hook maintains
  },
  "mouseChords": {           // a modifier + a click, on the window under the pointer
    "enabled": false,        // needs Accessibility and AeroSpace
    "chords": []             // empty means no click is consumed anywhere
  },
  "fnKey": "tap",            // how pounce takes the Fn/Globe key, see below
  "items": {                 // per-item enable / alias / hotkey / where it is listed
    "cmd:emoji": { "alias": "emo", "hotkey": "opt+space e" }
  }
}
```

`quickAnswers.currency` and `updates.check` are the only two settings that
touch the network; set both `false` for a fully offline Pounce.
`autoQuit.enabled` and `windows.enabled` turn on [the two opt-in
behaviours](/docs/pounce/beyond-launching); both blocks are startup-only, so
**any** edit inside them needs the daemon restart above, not just flipping
`enabled`.

`systemSettings` is the one block worth tuning rather than flipping. Every pane
is a row and so is every setting inside one, with `⏎` opening the pane already
scrolled to what you picked. There are about 700 individual settings and
Accessibility alone ships 342 of them, which is why two of the three keys
exist: `subItemMinQuery` keeps short queries about your
apps, and `maxPerPane` stops one pane from being the whole answer. Panes
themselves are always searchable, and start ranked below everything at an empty
query until you use them. What those rows are, and where their names come from,
is [Commands](/docs/pounce/commands#built-in-commands).

`windowMode` and `scale` compose: a compact launcher at `1.4` is still the
compact layout, just bigger. Values outside 0.8–2.0 clamp rather than reject. On
haus, `scale` is written for you from `haus.ui.scale`.

`fontFamily` is a family name as Font Book spells it, and it reaches every string
in the UI. Left out (the default) you get macOS's own, and naming the system font
gets you the system font rather than a frozen copy of it. Pounce installs
nothing, so a family this Mac doesn't have falls back quietly. Keycaps and code
previews stay monospaced whatever you put here, and SF Symbols keep Apple's
metrics. On haus it is written for you from
[`haus.fonts.sans.name`](/docs/haus/reference/options/#haus-fonts-sans-name),
which moves pounce, perch and trill together.

`stage` is [what you see before you type](/docs/pounce/using#before-you-type):
`"stage": { "enabled": false }` gives you the flat list Pounce has always shown,
and `tiles` clamps to 1–12 rather than rejecting. A bare `"stage": false` is not
that: the block is only read as an object, so it is ignored in silence and the
stage stays on. `"windowMode": "compact"` has no stage at all, so the key does
nothing there.

## How it learns (`ranking`) [#how-it-learns-ranking]

Four switches, all on. Turn them all off and you have fuzzy match plus frecency,
which is what pounce did before they existed. [How ranking
works](/docs/pounce/using#how-ranking-works) tells the same story from the
reader's side, in full.

| Key                | On means                                                                                                                                                                  |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `learnFromQueries` | The row you keep taking for a query, and for each prefix of it, wins the next time you type it. Two picks, never one                                                      |
| `stickyTiles`      | Tiles hold their positions across summons, so `⌘3` is the same thing next week. Off, the strip is the ranked list's first few rows again and moves whenever the list does |
| `useContext`       | A promotion towards what you reach for from *here*: the app in front, the workspace, the part of the day, weekday or weekend                                              |
| `predictNext`      | The **NEXT** info card, offering what usually follows what you just did. `⇥` takes it, `⏎` never does                                                                     |

<Callout title="A fresh pounce has no tiles">
  `stickyTiles` gives slots to habits, and a pounce nobody has used yet has none,
  so the strip starts empty and fills as you work. The alternative is opening on
  seven alphabetical accidents you then have to unlearn.
</Callout>

## Themes [#themes]

`themeLight` / `themeDark` are resolved per open, so flipping macOS appearance
shows on the next summon; either falls back to `theme`, and `theme` alone pins
one palette for both modes.

Any `theme` value that isn't built-in resolves to
`~/.config/pounce/themes/<name>.json`: a flat catppuccin-style `name → "#hex"`
map ([nebelung's](https://github.com/hausfold/nebelung) `palette/*.hex.json`
files verbatim), which is how a desktop's
[`theme.flavor` / `theme.contrast`](/docs/haus/rooms/appearance) reach Pounce
without a rebuild. Pounce reads `base`,
`surface0`–`surface2`, `text`, `subtext0`, `overlay0`, `mauve` and `blue`; a
file missing any of them falls back to the built-in default. A file also
*shadows* a built-in of the same name, so `themes/nebelung.json` wins over the
compiled-in one. Window chrome follows the **palette's** own lightness rather
than the system's, so a light palette in Dark Mode still draws a light panel.

```sh
mkdir -p ~/.config/pounce/themes
curl -fsSLo ~/.config/pounce/themes/nebelung-latte.json \
  https://raw.githubusercontent.com/hausfold/nebelung/main/palette/nebelung-latte.hex.json
# config.json:  "theme": "nebelung-latte"
```

## Per-item settings (`items`) [#per-item-settings-items]

One map: hide a row, give it a search shorthand, give it a global key, or list it
only where it is useful. Each entry is keyed by an **item key**:

| Item key                    | Addresses                                                                                                                                                 |
| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `cmd:<id>`                  | a command script, by filename without `.sh`                                                                                                               |
| `app:/Applications/Foo.app` | an application, by path                                                                                                                                   |
| `shortcut:<uuid>`           | a Shortcut from your Shortcuts library, by its uuid                                                                                                       |
| `setting:<pane>[?<anchor>]` | a System Settings pane, or one setting inside it: `setting:com.apple.Displays-Settings.extension?nightShiftSection`. `pounce doctor` prints the exact key |
| `mode:<name>`               | a built-in window: `launcher`, `clipboard`, `emoji`, `screenshots`, `camera`, `filesearch`                                                                |

```jsonc
{
  "items": {
    "cmd:emoji":                     { "alias": "emo", "hotkey": "opt+e" },
    "cmd:brew-services":             { "enabled": false },
    "cmd:lane-here":                 { "workspaces": ["T"] },
    "cmd:peek":                      { "bundleIds": ["com.mitchellh.ghostty"] },
    "app:/Applications/Ghostty.app": { "alias": "term", "hotkey": "opt+t" },
    "mode:clipboard":                { "hotkey": "cmd+shift+v" }
  }
}
```

`enabled: false` drops the row without disarming a hotkey bound to it; keeping
an item off the list but on a key is a legitimate setup. `alias` is a search
shorthand that wins over whatever app fuzzy-matches the same letters. `hotkey`
runs the item directly, skipping the palette: the last segment is the key, the
rest modifiers, and the object form `{"key": …, "modifiers": …}` works too.

Two more decorate the row rather than change what it does:

* **`hint`** draws a keycap chip on the row's right edge and binds nothing. It
  is for keys something *else* carries: a window manager's launch mode, a
  page-scoped binding, any external binder. An item whose own `hotkey` is
  registered draws its chord automatically, so this is only for the bindings
  pounce cannot see; where both exist, `hint` wins.
* **`state`** is a read-only command whose first stdout line becomes a live
  badge beside the row: `"cmd:focus": { "state": "pounce focus status" }` shows
  **On** or **Off**. It never runs on the keystroke that opens the palette:
  results are cached 30s, a stale one refreshes in the background under a 1.5s
  cap, and the row appears without a badge until one lands. It runs with the
  daemon's grants, repeatedly, so it must be `status`-shaped and never `toggle`.

### Rows that only exist where they work [#rows-that-only-exist-where-they-work]

`enabled` is a decision you make once. `workspaces` and `bundleIds` are asked
again on every summon, so a row can be listed only where it means something: a
command that reads the focused terminal's directory has nothing to read over a
browser.

`workspaces` takes workspace names (a bare string works for one). `"T"` matches
the page `T` and every `T/…` child, the shape [`pages`](#walking-a-family-of-workspaces-pages)
already uses; `"T/*"` is the children alone, `"T/main"` is that one page.
Matching ignores case, which is one of two places this is deliberately not the
same predicate as `pages.prefix`: the walk matches case-sensitively and has no
`/*` form. Which page you are on comes from `pages.mruFile`, so set
that even if you never turn the ⌃Tab walk on. **With no readable `mruFile` this
filters nothing**, deliberately: a Mac that cannot answer the question would
otherwise hide the row forever.

`bundleIds` takes bundle ids, and lists the row only while one of them is
frontmost, also ignoring case; read one off any running app with
`osascript -e 'id of app "Notes"'`. The same string is what
`apps.hideBundleIds`, `apps.demoteBundleIds` and `autoQuit.exclude` want. Set
both keys and the row wants both.

Both ask **where you are**, neither with a subprocess. Either way it scopes the
**row**, never a `hotkey` on the same item, the same way `enabled: false` leaves
a key armed. Scoping a chord instead is
[`appHotkeys`](#chords-that-only-exist-over-one-app-apphotkeys), which has to
consume the keystroke to do it. Neither asks *is there anything here to act on*:
that is the command script's own
[`whenFile`](/docs/pounce/writing-commands#a-row-that-hides-when-there-is-nothing-to-act-on).

A row that never appears looks exactly like a row you never installed, so
`pounce doctor` names every scoped item, what it asks for, the page you are on
and which rows that leaves out. It fails outright when something asks about
`workspaces` while `mruFile` is unset or points somewhere it cannot read, since
both leave the scoping doing nothing.

<Callout title="haus does this to three rows">
  The [Launcher room](/docs/haus/rooms/launcher) lists **New Agent Lane**, **New
  Shell Window** and its `⇧` twin only on the terminal pages, because all three
  inherit the focused window's directory. **Spawn Agent** asks which repo instead
  of inheriting one, so it stays everywhere.
</Callout>

The laptop **Fn/Globe key** is a one-step special case
(`"mode:emoji": { "hotkey": "fn" }`), firing only on a lone tap so Fn
combinations keep working; `"globe"` / `"function"` are accepted aliases. How
pounce takes that key at all is [`fnKey`](#the-fnglobe-key-fnkey). haus binds
emoji to it by default, and `haus.launcher.items."mode:emoji".hotkey = null`
leaves Globe native.

### Leader sequences [#leader-sequences]

Add a space for a two-step key: `"opt+space e"` is ⌥Space then E, the Emacs/VS
Code notation. Sequences sharing a leader share it (⌥Space registers once), and
can run longer than two steps.

On a tiling setup a leader can't collide with the ⌥/⌘ chords a window manager
already owns, and it needs **no Accessibility grant**: pressing it grabs the
next-step keys as ordinary global hotkeys for \~2s and releases them the instant
one fires. Escape cancels; hesitate \~0.45s and a which-key overlay lists the next
keys. `pounce doctor` reports every binding it armed.

### Driving Pounce from another binder [#driving-pounce-from-another-binder]

AeroSpace modes, skhd, Shortcuts:

```sh
pounce run cmd:emoji
pounce run mode:clipboard
```

Same target grammar as `items`, through the same path a native binding takes; a
malformed target exits non-zero with a reason.

### Running an item from a link (`urlScheme`) [#running-an-item-from-a-link-urlscheme]

A `pounce://` link runs the same item keys, from anywhere that can open a URL
and nothing else — a row in an Obsidian base, a line in a note, a cell in a
spreadsheet, a Shortcut, a web page:

```
pounce://run?item=cmd:todos
pounce://run?item=cmd:new-lane&arg=/Users/me/notes/a%20task.md
pounce://run?item=mode:clipboard
```

`arg` is repeatable and positional: the script reads them as `$1`, `$2` and so
on, and nothing crosses a shell, so a value with spaces or quotes in it arrives
as one argument and needs none of its own. Only a `cmd:` item takes arguments —
an app, a Shortcut and a settings pane are told what to do by something that
isn't `argv` — and a link that passes one to anything else is refused rather
than quietly stripped. Four is the limit, because four is exactly what the
confirm sheet can draw a row each for, and a link whose payload is summarised
rather than shown is one you would be agreeing to unseen. Percent-encode a
value that contains `&`, `#`, `%` or a space.

**A link that would run something is confirmed on screen first.** The sheet
names the command, the app that opened the link, and every argument it carries,
because the arguments are the part somebody else wrote. A link that only
*opens* something — clipboard history, the emoji picker, a System Settings pane
— is not confirmed: that is what every link on a Mac already does.
`mode:camera` is the exception on the other side, and asks like anything else
that acts: it starts a capture session, indicator light and all, which is a
device rather than a view.

A link that arrives while pounce already has something on screen is refused
rather than taking it. Every other way in is you — a hotkey pressed while a
picker is up is you changing your mind — and a link is not: it can arrive from
a background app at any moment, and it should not be able to end a picker some
script is waiting on.

|           |                                                                                                                                                                                                                                                                            |
| --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `enabled` | Whether links work at all. The scheme itself is claimed by Pounce.app and can't be unclaimed, so this is the switch that closes the door; every link is then refused with a banner                                                                                         |
| `confirm` | Ask before a link runs a `cmd:`, an `app:` or a `shortcut:`. Set `false` to trust a link exactly as much as your keyboard — a command's own [`confirm =` header](/docs/pounce/writing-commands#what-a-command-says-it-will-do) still gets its sheet, and nothing else does |

Nothing is handed back to whatever opened the link: a URL has no exit code, and
the page or note that carried it has already moved on. So a link pounce can't
honour — a typo'd item, an argument on something that takes none, a command
that isn't installed — says so in a notification instead of failing silently.

## The Fn/Globe key (`fnKey`) [#the-fnglobe-key-fnkey]

Binding an item to `fn` is a one-step special case, and `fnKey` decides how
pounce gets the key:

|           |                                                                                                                                                                                                                                                                                                                                                     |
| --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `"tap"`   | The default. An event tap, so it needs Accessibility, and it **shares** the key: macOS's own Globe handler sits below the event stream a tap can see, so the Emoji & Symbols picker can still open alongside pounce's. Turning off System Settings ▸ Keyboard ▸ "Press 🌐 key to" helps, but it wants a logout to take and the two are still racing |
| `"remap"` | Takes Fn away at the HID layer: it becomes F19, which pounce binds like any ordinary key. No Accessibility for this binding, and nothing left to race. The cost is that Fn stops being Fn everywhere: no Fn+arrows, no Fn+Delete, no Fn+F1–F12                                                                                                      |

`remap` undoes itself and falls back to the tap if F19 is already taken or the
keyboard doesn't expose Fn. Standalone it is applied while the daemon runs and
removed when it exits; [inside haus](/docs/haus/rooms/launcher#the-fnglobe-key)
it is declared instead, so it outlives the daemon. `pounce doctor` reports
which of the two is carrying the key.

Three things to know before you turn `remap` on:

* **F19 is a real key on the full-size Magic Keyboard**, the last of the F row,
  so on that keyboard it fires this binding too. Every key below F13 is one
  people actually bind, which makes F19 the least-bad choice rather than a free
  one.
* **A keyboard that re-enumerates loses the mapping.** Unplugging and replugging
  an external keyboard, and some sleep/wake cycles, drop it, and the binding
  then quietly stops working. `pounce doctor` says so; restarting the daemon
  reinstalls it.
* **The HID mapping is one shared list.** Pounce merges into whatever is already
  there (a Karabiner rule, a Caps-to-F18 leader) and puts back what it found on
  exit, and it refuses to write at all if it couldn't read the list first. What
  it cannot do is preserve a mapping something *else* added mid-session, so
  re-apply that one afterwards. To look or to clear by hand:

```sh
hidutil property --get UserKeyMapping
hidutil property --set '{"UserKeyMapping":[]}'   # clears ALL mappings, not just ours
```

## Chords that only exist over one app (`appHotkeys`) [#chords-that-only-exist-over-one-app-apphotkeys]

A global hotkey is all-or-nothing: macOS swallows the chord everywhere or
nowhere. `appHotkeys` is the other shape. Each scope names a `bundleId` and the
chords to consume **while that app is frontmost**; everywhere else the keystroke
passes through untouched, so ⌘N still opens a new window in your browser.

```jsonc
"appHotkeys": {
  "enabled": true,
  "scopes": [
    { "bundleId": "com.mitchellh.ghostty",
      "keys": [ { "key": "n", "modifiers": ["cmd"], "target": "cmd:shell-here" } ] }
  ]
}
```

`target` is the same item-key grammar as `items` and `pounce run`, so a scoped
chord and a palette row are one address. It reads keys as they pass, so it needs
Accessibility, and it is armed at startup: an edit wants the [daemon
restart](#config-file).

## Walking a family of workspaces (`pages`) [#walking-a-family-of-workspaces-pages]

⌘Tab's muscle memory, aimed at workspaces instead of windows: hold a modifier,
tap a key, step through the **non-empty** AeroSpace workspaces under a prefix,
most recent first. Keep holding and a HUD lists each page with the windows on it,
so you pick by recognising what is there rather than by remembering its name.
**Nothing is focused until you land** on the release; ↵ lands early, ⎋ leaves you
where you were.

```jsonc
"pages": { "enabled": true, "prefix": "T", "modifiers": ["ctrl"], "key": "tab" }
```

`prefix` matches a workspace named `T` or anything under `T/`. Recency comes from
`mruFile`, a newest-first list your window manager's workspace-change hook pushes
onto; because the walk visits nothing on the way, passing through three pages
doesn't rank them above where you started. Its head line is also how the palette
answers [which page you are on](#rows-that-only-exist-where-they-work).
`bundleId` scopes the chord like a scope above, which is how a browser keeps ⌃Tab
for its own tabs.

<Callout title="haus wires both for you">
  The [Development room](/docs/haus/rooms/development) sets these up as the lane
  keyboard: ⌘N/⌘⇧N open a shell window over Ghostty, ⌘⏎ starts an agent lane,
  ⌘T opens a neutral terminal off any page, ⌃Tab/⌃⇧Tab walk the
  `T/<repo>` lane pages.
</Callout>

## A modifier and a click (`mouseChords`) [#a-modifier-and-a-click-mousechords]

A mouse chord acts on the window **under the pointer** rather than the one you
are in, so "zoom that one over there" is one gesture instead of
focus-then-zoom.

```jsonc
"mouseChords": {
  "enabled": true,
  "chords": [
    { "button": "right", "modifiers": ["alt"], "action": "fullscreen" }
  ]
}
```

That makes **⌥ + right-click** zoom whichever window you clicked to fill its own
workspace, focusing it on the way; `fullscreen` is a toggle, so the same chord
puts it back. It needs [AeroSpace](https://github.com/nikitabobko/AeroSpace)
(`aerospace fullscreen --window-id`) and Accessibility, and it is read once at
daemon start.

One deliberate no-op: a window **alone** on its workspace does not zoom. It
already fills the workspace, and arming AeroSpace's fullscreen mode there would
make the next window open behind it. Zooming back **out** always works, even for
a window whose siblings have closed since. The guard needs AeroSpace to answer a
window listing; when it cannot, the chord fires ungated rather than leaving you
with a chord that does nothing.

The click is consumed over *every* app, so the chord you pick is a chord you give
up everywhere:

| chord               | what it costs                                                                                                              |
| ------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| **⌥ + right-click** | the quietest: macOS's ⌥ *variant* of a context menu, and ⌥ + right-drag where an app uses it                               |
| ⌥ + left-click      | multi-cursor in GUI editors, ⌥-click to download a link, and every ⌥-drag: the down is swallowed, so the drag never starts |
| ctrl + click        | context menus machine-wide. This is macOS's own secondary click; don't                                                     |

<Callout type="warn" title="A chord with no modifier is refused">
  It would swallow every click on the machine, including the ones you'd need to
  fix the config. Pounce logs the refusal and carries on. Clicking the desktop
  passes through untouched, and anything drawn above ordinary windows (the menu
  bar, the Dock, a status bar) is transparent to the chord rather than "clicked".
</Callout>

## File paths [#file-paths]

| Path                                  | What                               |
| ------------------------------------- | ---------------------------------- |
| `~/.config/pounce/config.json`        | Configuration                      |
| `~/.config/pounce/commands/`          | Your commands (highest precedence) |
| `~/.config/pounce/themes/`            | Extra palettes, by name            |
| `~/.config/pounce/cheatsheet.json`    | Optional cheatsheet content        |
| `~/.local/share/pounce/frecency.json` | Usage history for ranking          |
| `~/.local/share/pounce/pounce.sock`   | Daemon control socket              |
| `~/.local/state/pounce/drafts/`       | Saved drafts, per `--draft` key    |

## Environment variables [#environment-variables]

Set by packagers (haus), rarely by hand: `POUNCE_BUILTIN_DIR`,
`POUNCE_EXTRA_COMMAND_DIRS` (colon-separated Nix layers),
`POUNCE_COMMAND_PATH` (colon-separated ad-hoc dirs).
