c3749daf48
Tests / test-linux (3.13) (push) Failing after 0s
Tests / test-linux (3.11) (push) Failing after 1s
Tests / lint (push) Failing after 0s
Tests / test-linux (3.9) (push) Failing after 1s
Docker / build (push) Failing after 1s
Docker / build-gpu (push) Failing after 2s
Tests / test-windows (push) Has been cancelled
Tests / test-macos (push) Has been cancelled
158 lines
9.4 KiB
Markdown
158 lines
9.4 KiB
Markdown
# MemPalace Cursor IDE Hooks
|
|
|
|
Auto-save and session-recall hooks for the [Cursor](https://cursor.com) IDE,
|
|
matching the behaviour of the existing Claude Code + Codex hooks at the repo
|
|
root and adding two Cursor-only capabilities (`sessionStart` recall and a
|
|
preCompact transcript snapshot).
|
|
|
|
For the rendered documentation see
|
|
[`website/guide/cursor-hooks.md`](../../website/guide/cursor-hooks.md) or
|
|
the published version at
|
|
[mempalaceofficial.com/guide/cursor-hooks](https://mempalaceofficial.com/guide/cursor-hooks.html).
|
|
|
|
## What's here
|
|
|
|
| File | Role |
|
|
|-------------------------------------|---------------------------------------------------------------------|
|
|
| `lib/common.sh` | Shared bash helpers (parse, log, counter, wing inference, kill switch). Sourced by all three hooks. |
|
|
| `mempal_save_hook_cursor.sh` | Cursor `stop` hook. Counts stop invocations per conversation, emits a `followup_message` every `SAVE_INTERVAL` (default 15) telling the agent to file the session into MemPalace. |
|
|
| `mempal_precompact_hook_cursor.sh` | Cursor `preCompact` hook. Runs `mempalace mine` synchronously on the transcript before compaction, then drops a `.pending` marker so the next stop forces a save nudge. |
|
|
| `mempal_wake_hook_cursor.sh` | Cursor `sessionStart` hook. Returns `additional_context` telling the agent to recall scoped to the wing inferred from the workspace root. Cursor-only — Claude Code has no equivalent. |
|
|
| `install.sh` | Optional installer. Copies the scripts to `~/.mempalace/hooks/cursor/` and merges entries into `~/.cursor/hooks.json` (or `.cursor/hooks.json` for project scope). Supports `--dry-run` and `--uninstall`. |
|
|
| `STDIN_SHAPE.md` | Reference. Per-event stdin / stdout schema with citations to the official Cursor docs. |
|
|
|
|
## Quick install
|
|
|
|
Preview first (writes nothing, prints the would-be JSON to stdout):
|
|
|
|
```bash
|
|
hooks/cursor/install.sh --scope user --dry-run
|
|
```
|
|
|
|
Apply — writes `~/.cursor/hooks.json` and copies the scripts to `~/.mempalace/hooks/cursor/`:
|
|
|
|
```bash
|
|
hooks/cursor/install.sh --scope user
|
|
```
|
|
|
|
Pass `--scope project --target <repo>` to write `<repo>/.cursor/hooks.json` instead.
|
|
The installer never auto-runs — it is a documented opt-in step. We do not
|
|
modify your Cursor config on `pip install mempalace` because editor config
|
|
is sacred and should never be touched without explicit consent.
|
|
|
|
## Manual install (no installer)
|
|
|
|
The minimum wiring is `stop` only. Add to `~/.cursor/hooks.json`:
|
|
|
|
```json
|
|
{
|
|
"version": 1,
|
|
"hooks": {
|
|
"stop": [
|
|
{
|
|
"command": "/absolute/path/to/hooks/cursor/mempal_save_hook_cursor.sh",
|
|
"loop_limit": 1
|
|
}
|
|
]
|
|
}
|
|
}
|
|
```
|
|
|
|
For the full triple (recommended), also wire `sessionStart` and `preCompact`
|
|
— see [`examples/cursor/hooks.json`](../../examples/cursor/hooks.json).
|
|
|
|
After editing the file, Cursor watches `hooks.json` and reloads
|
|
automatically. If hooks still do not fire, restart Cursor and check the
|
|
Hooks panel in Settings.
|
|
|
|
## Configuration
|
|
|
|
All knobs are env vars; defaults match the Claude Code hooks where
|
|
possible so a single hook-state directory works for both editors.
|
|
|
|
| Variable | Default | Purpose |
|
|
|--------------------------------|------------------------------------|---------|
|
|
| `MEMPAL_SAVE_INTERVAL` | `15` | Number of `stop` events between save followups. |
|
|
| `MEMPAL_CURSOR_SILENT` | (unset) | Set to `1`/`true`/`yes` to suppress the `followup_message`. The hook still runs its best-effort background mine and keeps its counters — it just stays silent. `MEMPAL_VERBOSE=false`/`0`/`no` does the same. See note below on why the followup is on by default. |
|
|
| `MEMPAL_DIR` | (unset) | Optional project directory to also mine on each save. Additive — never replaces the transcript mine. |
|
|
| `MEMPAL_PYTHON` | auto-detected | Path to a Python 3 interpreter. Fallback order: `$MEMPAL_PYTHON` → `command -v python3` → bare `python3`. Useful when Cursor is launched from a GUI on macOS and the inherited PATH lacks your installed `python3`. |
|
|
| `MEMPAL_STATE_DIR` | `$HOME/.mempalace/hook_state` | Where the hook keeps its per-conversation counter files, pending-save markers, and `cursor_hook.log`. |
|
|
| `MEMPAL_STATE_TTL_DAYS` | `30` | Age (days) after which stale `cursor_*.count` / `cursor_*.pending` state files are garbage-collected. A daily-throttled sweep runs from the hooks; only Cursor state is touched (shared logs and other editors' state are left alone). |
|
|
| `MEMPAL_DISABLE_HOOK` | (unset) | Set to `1`/`true`/`yes` to disable all three hooks. Emergency kill switch. |
|
|
| `MEMPALACE_HOOKS_AUTO_SAVE` | (unset) | Set to `false`/`0`/`no` to disable. Same semantics as the Claude Code hooks. Also honoured via `~/.mempalace/config.json` → `{"hooks": {"auto_save": false}}`. |
|
|
|
|
## Debugging
|
|
|
|
Everything appends to:
|
|
|
|
```bash
|
|
cat ~/.mempalace/hook_state/cursor_hook.log
|
|
```
|
|
|
|
Example log lines (ISO 8601 + event + conversation id):
|
|
|
|
```
|
|
[2026-05-27T02:16:01Z] [event=sessionStart] [conv=abc123] workspace=/Users/me/proj wing=proj
|
|
[2026-05-27T02:21:33Z] [event=stop] [conv=abc123] counter 0 -> 1 (interval=15)
|
|
[2026-05-27T02:42:09Z] [event=stop] [conv=abc123] counter 14 -> 15 (interval=15)
|
|
[2026-05-27T02:42:09Z] [event=stop] [conv=abc123] TRIGGERING SAVE at counter=15
|
|
[2026-05-27T02:42:11Z] [event=stop] [conv=abc123] loop_count>0; letting agent stop
|
|
```
|
|
|
|
When a hook can't parse its stdin (corrupt payload, future Cursor schema
|
|
change), the raw input (capped at 4096 bytes, mode 0600) lands at:
|
|
|
|
```
|
|
~/.mempalace/hook_state/cursor_last_input.log
|
|
~/.mempalace/hook_state/cursor_last_python_err.log
|
|
```
|
|
|
|
These are overwritten on each failure, never appended, so a repeating
|
|
misconfiguration cannot grow disk usage.
|
|
|
|
## What differs from the Claude Code hooks
|
|
|
|
| Aspect | Claude Code hooks (`hooks/mempal_*.sh`) | Cursor hooks (`hooks/cursor/*.sh`) |
|
|
|-------------------------|------------------------------------------------|-----------------------------------------------------|
|
|
| Counter key | `session_id` | `conversation_id` (Cursor's stable per-conv id) |
|
|
| Loop guard | `stop_hook_active` flag in stdin | `loop_count` field in stdin |
|
|
| Counting method | Parses JSONL transcript for user messages | Counts `stop` invocations (transcript schema undoc) |
|
|
| Capture path | Background `mine --mode convos` (normalize.py has a Claude parser) | Background mine is best-effort (no Cursor parser); the `followup_message` carries verbatim capture |
|
|
| Save default | Silent — diary nudge opt-IN behind `MEMPAL_VERBOSE=true` | Followup ON by default; opt-OUT via `MEMPAL_CURSOR_SILENT=1` / `MEMPAL_VERBOSE=false` |
|
|
| PreCompact behaviour | `decision: block` forces save before compaction | Pre-mine + pending-save marker (Cursor preCompact is observational-only) |
|
|
| sessionStart | n/a (Claude Code has no equivalent) | `additional_context` injects recall guidance |
|
|
| State dir | `$HOME/.mempalace/hook_state` (hardcoded) | Same default, plus `MEMPAL_STATE_DIR` env override |
|
|
| Kill switch | `MEMPALACE_HOOKS_AUTO_SAVE=false` | Same, plus `MEMPAL_DISABLE_HOOK=1` alias |
|
|
| Log file | `hook.log` | `cursor_hook.log` (kept separate to avoid cross-tool log churn) |
|
|
|
|
See [`STDIN_SHAPE.md`](STDIN_SHAPE.md) for the per-event schema and
|
|
[`website/guide/cursor-hooks.md`](../../website/guide/cursor-hooks.md) for
|
|
the full walkthrough with diagrams.
|
|
|
|
## Why the followup is on by default (Cursor-specific)
|
|
|
|
Unlike the Claude Code hook — which is silent by default because its
|
|
background `mempalace mine --mode convos` captures the verbatim transcript
|
|
on its own — Cursor's transcript format is **undocumented** and
|
|
`mempalace/normalize.py` has **no Cursor parser**. The background mine on
|
|
the Cursor `stop`/`preCompact` hooks is therefore **best-effort only**: it
|
|
does not yet yield clean verbatim conversation drawers.
|
|
|
|
That makes the `followup_message` the **load-bearing verbatim-capture
|
|
path** for Cursor — it drives the agent to file its own in-context
|
|
verbatim quotes via `mempalace_add_drawer` / `mempalace_diary_write`.
|
|
Silencing it by default would leave a default Cursor install capturing
|
|
nothing, which is why it is on by default here. Set `MEMPAL_CURSOR_SILENT=1`
|
|
(or `MEMPAL_VERBOSE=false`) if you prefer the Claude-style silent
|
|
behaviour and accept the reduced capture. Once `normalize.py` learns to
|
|
read Cursor transcripts, this default will flip to silent to match Claude.
|
|
|
|
## Cost
|
|
|
|
Zero extra LLM tokens spent by the hooks themselves. The hooks are local
|
|
bash scripts that run on your machine. The followup message the save hook
|
|
emits is a normal user turn — it counts the same as any other user message
|
|
and does not invoke any extra LLM call beyond the one the user would
|
|
otherwise make. Suppress it with `MEMPAL_CURSOR_SILENT=1` if you want zero
|
|
followups in the chat window.
|