# @nanstey/pi-command-shortcuts

Bind keyboard shortcuts to any extension slash command in [pi](https://pi.dev).

pi's built-in `keybindings.json` only remaps built-in actions — it can't bind a
key to an extension command like `/voice`. This extension does, driven by a
small JSON config.

## Install

```bash
pi install npm:@nanstey/pi-command-shortcuts
```

## Usage

Create `~/.pi/agent/command-shortcuts.json` mapping keys to commands:

```json
{
  "ctrl+r": "voice",
  "ctrl+g": { "command": "websearch", "args": "" }
}
```

Reload with `/reload` (or restart pi). Press the key to run the command.

Each value is either:

- **a string** — the command name (no leading slash). The owning extension is
  auto-discovered by scanning your installed packages.
- **an object** — `{ "command": "...", "from": "...", "args": "..." }`
  - `command` — command name to run (required).
  - `from` — package name (`@scope/pkg`) or module path that registers the
    command. Skips the scan; faster and unambiguous. Optional.
  - `args` — default argument string passed to the command. Optional.

Keys use pi's key syntax, e.g. `ctrl+r`, `ctrl+shift+v`, `alt+enter`.

## How it works

Extension command handlers are private to pi, and shortcut handlers have no
"run command by name" API. So this extension re-runs the owning extension's
factory with a `Proxy` of the real `pi` that:

- captures `registerCommand()` handlers,
- no-ops the other `register*` / `on()` calls so nothing is double-registered,
- forwards every other method (`sendMessage`, `sendUserMessage`, …) to the real
  `pi`, so a captured handler behaves exactly like the real command.

The resolved handler is cached per binding and invoked when you press the key.
Modules are loaded through pi's own loader, so TypeScript extensions work
without a build step.

## Notes

- **Context limitation.** Shortcut handlers receive an `ExtensionContext`, not
  the richer `ExtensionCommandContext`. Commands that rely on command-only
  methods (`newSession`, `waitForIdle`, `fork`, …) may not work from a key. This
  is a pi limitation tracked in [pi#4422](https://github.com/earendil-works/pi/issues/4422).
  Commands that only use `ctx.ui` and messaging (like `/voice`) work fine.
- **Conflicts.** If a key already drives a built-in action (e.g. `ctrl+r` is
  the session-picker rename), pi logs a harmless conflict diagnostic and this
  binding wins in the editor. Pick a free combo (`ctrl+shift+…`) to avoid it.
- **Discovery.** Prefer `from` for speed and to avoid importing unrelated
  extensions during the auto-scan.

## License

MIT
