memento-mcp 0.3.4 → 0.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -7
- package/package.json +1 -1
- package/scripts/README.md +157 -26
- package/scripts/memento-codex-notify.sh +79 -0
- package/src/cli.js +135 -90
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ AI agents have anterograde amnesia — every session starts blank. The Memento P
|
|
|
10
10
|
npx memento-mcp init
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
This creates `.memento.json`, detects your agent (Claude Code, Codex, Gemini CLI, OpenCode), writes the correct MCP config, and
|
|
13
|
+
This creates `.memento.json`, detects your agent (Claude Code, Codex, Gemini CLI, OpenCode), writes the correct MCP config, and registers hooks — all in one command.
|
|
14
14
|
|
|
15
15
|
---
|
|
16
16
|
|
|
@@ -154,13 +154,29 @@ Full guide: **[The Protocol](https://hifathom.com/memento/docs/protocol)** on hi
|
|
|
154
154
|
|
|
155
155
|
## Hooks
|
|
156
156
|
|
|
157
|
-
Hooks automate memory at session boundaries — recall on every message, distillation before context loss.
|
|
157
|
+
Hooks automate memory at session boundaries — recall on every message, distillation before context loss, identity injection at session start. Five production-ready scripts are included in `scripts/`:
|
|
158
158
|
|
|
159
|
-
| Script |
|
|
160
|
-
|
|
161
|
-
| `memento-
|
|
162
|
-
| `memento-
|
|
163
|
-
| `memento-
|
|
159
|
+
| Script | What it does |
|
|
160
|
+
|--------|-------------|
|
|
161
|
+
| `memento-sessionstart-identity.sh` | Injects identity crystal + version check at session start |
|
|
162
|
+
| `memento-userprompt-recall.sh` | Recalls memories relevant to the user's message |
|
|
163
|
+
| `memento-stop-recall.sh` | Recalls memories from the assistant's own output (autonomous work) |
|
|
164
|
+
| `memento-precompact-distill.sh` | Extracts memories from the conversation before context compression |
|
|
165
|
+
| `memento-codex-notify.sh` | Stores post-turn summaries from Codex CLI as memory observations |
|
|
166
|
+
|
|
167
|
+
### Agent hook support
|
|
168
|
+
|
|
169
|
+
`npx memento-mcp init` auto-detects your agent and registers the appropriate hooks. Not all agents support the same hook events — here's what gets wired up:
|
|
170
|
+
|
|
171
|
+
| Hook | Claude Code | Gemini CLI | Codex CLI |
|
|
172
|
+
|------|:-----------:|:----------:|:---------:|
|
|
173
|
+
| Session start / identity | `SessionStart` | `SessionStart` | — |
|
|
174
|
+
| Recall on user message | `UserPromptSubmit` | `BeforeAgent` | — |
|
|
175
|
+
| Recall on assistant output | `Stop` | `SessionEnd` | — |
|
|
176
|
+
| Pre-compaction distillation | `PreCompact` | `PreCompress` | — |
|
|
177
|
+
| Post-turn memory storage | — | — | `notify` |
|
|
178
|
+
|
|
179
|
+
**OpenCode** uses a TypeScript plugin system — MCP tools work, but hooks require a different integration pattern.
|
|
164
180
|
|
|
165
181
|
See **[scripts/README.md](scripts/README.md)** for setup, configuration, and how to write your own hooks.
|
|
166
182
|
|
package/package.json
CHANGED
package/scripts/README.md
CHANGED
|
@@ -1,18 +1,56 @@
|
|
|
1
1
|
# Memento Protocol — Hook Scripts
|
|
2
2
|
|
|
3
|
-
Automation hooks
|
|
3
|
+
Automation hooks that connect the Memento API to agent lifecycle events. These scripts make memory automatic — recall on every message, distillation before context loss, identity injection at session start.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Supported agents
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Memento hooks work with three CLI agents. Each has a different hook system, but the same scripts power all of them.
|
|
8
8
|
|
|
9
|
-
|
|
|
10
|
-
|
|
11
|
-
|
|
|
12
|
-
|
|
|
13
|
-
|
|
|
9
|
+
| Hook | Claude Code | Gemini CLI | Codex CLI |
|
|
10
|
+
|------|:-----------:|:----------:|:---------:|
|
|
11
|
+
| Session start / identity | `SessionStart` | `SessionStart` | — |
|
|
12
|
+
| Recall on user message | `UserPromptSubmit` | `BeforeAgent` | — |
|
|
13
|
+
| Recall on assistant output | `Stop` | `SessionEnd` | — |
|
|
14
|
+
| Pre-compaction distillation | `PreCompact` | `PreCompress` | — |
|
|
15
|
+
| Post-turn memory storage | — | — | `notify` |
|
|
14
16
|
|
|
15
|
-
|
|
17
|
+
**OpenCode** uses a TypeScript plugin system — MCP tools work, but hooks require a different integration pattern.
|
|
18
|
+
|
|
19
|
+
**Claude Code** and **Gemini CLI** have near-identical hook architectures — JSON on stdin, JSON on stdout. The same shell scripts work for both; only the event names differ.
|
|
20
|
+
|
|
21
|
+
**Codex CLI** has a single `notify` mechanism — fire-and-forget, JSON as `argv[1]`, no context injection. Useful for post-turn memory storage only.
|
|
22
|
+
|
|
23
|
+
## Scripts
|
|
24
|
+
|
|
25
|
+
| Script | What it does |
|
|
26
|
+
|--------|-------------|
|
|
27
|
+
| `memento-sessionstart-identity.sh` | Injects identity crystal + version check at session start |
|
|
28
|
+
| `memento-userprompt-recall.sh` | Recalls memories relevant to the user's message |
|
|
29
|
+
| `memento-stop-recall.sh` | Recalls memories from the assistant's own output |
|
|
30
|
+
| `memento-precompact-distill.sh` | Extracts memories from the conversation before context compression |
|
|
31
|
+
| `memento-codex-notify.sh` | Stores post-turn summaries from Codex CLI as memory observations |
|
|
32
|
+
|
|
33
|
+
## Automatic setup
|
|
34
|
+
|
|
35
|
+
The recommended way to set up hooks:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npx memento-mcp init
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
This detects your agent, registers hooks in the correct config file, and copies scripts to `.memento/scripts/`. No manual configuration needed.
|
|
42
|
+
|
|
43
|
+
To update hooks in an existing project:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npx memento-mcp update
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Manual setup
|
|
52
|
+
|
|
53
|
+
If you prefer to configure hooks yourself:
|
|
16
54
|
|
|
17
55
|
### 1. Create a `.env` file
|
|
18
56
|
|
|
@@ -36,18 +74,29 @@ The `.env` file is gitignored. All scripts source it automatically.
|
|
|
36
74
|
chmod +x scripts/*.sh
|
|
37
75
|
```
|
|
38
76
|
|
|
39
|
-
### 3. Register hooks
|
|
77
|
+
### 3. Register hooks
|
|
78
|
+
|
|
79
|
+
#### Claude Code
|
|
40
80
|
|
|
41
|
-
Add to `.claude/settings.json` (project-level) or `~/.claude/settings.json` (global):
|
|
81
|
+
Add to `.claude/settings.local.json` (project-level) or `~/.claude/settings.json` (global):
|
|
42
82
|
|
|
43
83
|
```json
|
|
44
84
|
{
|
|
45
85
|
"hooks": {
|
|
86
|
+
"SessionStart": [
|
|
87
|
+
{
|
|
88
|
+
"hooks": [{
|
|
89
|
+
"type": "command",
|
|
90
|
+
"command": "bash .memento/scripts/memento-sessionstart-identity.sh",
|
|
91
|
+
"timeout": 10000
|
|
92
|
+
}]
|
|
93
|
+
}
|
|
94
|
+
],
|
|
46
95
|
"UserPromptSubmit": [
|
|
47
96
|
{
|
|
48
97
|
"hooks": [{
|
|
49
98
|
"type": "command",
|
|
50
|
-
"command": "
|
|
99
|
+
"command": "bash .memento/scripts/memento-userprompt-recall.sh",
|
|
51
100
|
"timeout": 5000
|
|
52
101
|
}]
|
|
53
102
|
}
|
|
@@ -56,7 +105,7 @@ Add to `.claude/settings.json` (project-level) or `~/.claude/settings.json` (glo
|
|
|
56
105
|
{
|
|
57
106
|
"hooks": [{
|
|
58
107
|
"type": "command",
|
|
59
|
-
"command": "
|
|
108
|
+
"command": "bash .memento/scripts/memento-stop-recall.sh",
|
|
60
109
|
"timeout": 5000
|
|
61
110
|
}]
|
|
62
111
|
}
|
|
@@ -65,7 +114,54 @@ Add to `.claude/settings.json` (project-level) or `~/.claude/settings.json` (glo
|
|
|
65
114
|
{
|
|
66
115
|
"hooks": [{
|
|
67
116
|
"type": "command",
|
|
68
|
-
"command": "
|
|
117
|
+
"command": "bash .memento/scripts/memento-precompact-distill.sh",
|
|
118
|
+
"timeout": 30000
|
|
119
|
+
}]
|
|
120
|
+
}
|
|
121
|
+
]
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
#### Gemini CLI
|
|
127
|
+
|
|
128
|
+
Add to `.gemini/settings.json`:
|
|
129
|
+
|
|
130
|
+
```json
|
|
131
|
+
{
|
|
132
|
+
"hooks": {
|
|
133
|
+
"SessionStart": [
|
|
134
|
+
{
|
|
135
|
+
"hooks": [{
|
|
136
|
+
"type": "command",
|
|
137
|
+
"command": "bash .memento/scripts/memento-sessionstart-identity.sh",
|
|
138
|
+
"timeout": 10000
|
|
139
|
+
}]
|
|
140
|
+
}
|
|
141
|
+
],
|
|
142
|
+
"BeforeAgent": [
|
|
143
|
+
{
|
|
144
|
+
"hooks": [{
|
|
145
|
+
"type": "command",
|
|
146
|
+
"command": "bash .memento/scripts/memento-userprompt-recall.sh",
|
|
147
|
+
"timeout": 5000
|
|
148
|
+
}]
|
|
149
|
+
}
|
|
150
|
+
],
|
|
151
|
+
"SessionEnd": [
|
|
152
|
+
{
|
|
153
|
+
"hooks": [{
|
|
154
|
+
"type": "command",
|
|
155
|
+
"command": "bash .memento/scripts/memento-stop-recall.sh",
|
|
156
|
+
"timeout": 5000
|
|
157
|
+
}]
|
|
158
|
+
}
|
|
159
|
+
],
|
|
160
|
+
"PreCompress": [
|
|
161
|
+
{
|
|
162
|
+
"hooks": [{
|
|
163
|
+
"type": "command",
|
|
164
|
+
"command": "bash .memento/scripts/memento-precompact-distill.sh",
|
|
69
165
|
"timeout": 30000
|
|
70
166
|
}]
|
|
71
167
|
}
|
|
@@ -74,13 +170,29 @@ Add to `.claude/settings.json` (project-level) or `~/.claude/settings.json` (glo
|
|
|
74
170
|
}
|
|
75
171
|
```
|
|
76
172
|
|
|
77
|
-
|
|
173
|
+
#### Codex CLI
|
|
174
|
+
|
|
175
|
+
Add to `.codex/config.toml`:
|
|
176
|
+
|
|
177
|
+
```toml
|
|
178
|
+
notify = ["bash", ".memento/scripts/memento-codex-notify.sh"]
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Codex `notify` fires on `agent-turn-complete` — the script receives JSON as `argv[1]` (not stdin) and stores a memory observation from the assistant's response. Fire-and-forget only — no context injection.
|
|
78
182
|
|
|
79
183
|
---
|
|
80
184
|
|
|
81
185
|
## Script details
|
|
82
186
|
|
|
83
|
-
### `memento-
|
|
187
|
+
### `memento-sessionstart-identity.sh` — SessionStart
|
|
188
|
+
|
|
189
|
+
Fires when a session begins. Injects the identity crystal into model context and checks whether a newer version of memento-mcp is available.
|
|
190
|
+
|
|
191
|
+
- **Timeout:** 10 seconds
|
|
192
|
+
- **Model sees:** Identity crystal text + update notice (if newer version available)
|
|
193
|
+
- **Version check:** Compares `.memento/version` against npm registry (2s timeout, silent on failure)
|
|
194
|
+
|
|
195
|
+
### `memento-userprompt-recall.sh` — UserPromptSubmit / BeforeAgent
|
|
84
196
|
|
|
85
197
|
Fires before every agent response. Sends the user's message to `/v1/context`, which returns relevant memories and skip list warnings.
|
|
86
198
|
|
|
@@ -91,7 +203,7 @@ Fires before every agent response. Sends the user's message to `/v1/context`, wh
|
|
|
91
203
|
|
|
92
204
|
**Output format:** JSON with `systemMessage` (user display) + `hookSpecificOutput.additionalContext` (model context).
|
|
93
205
|
|
|
94
|
-
### `memento-stop-recall.sh` — Stop
|
|
206
|
+
### `memento-stop-recall.sh` — Stop / SessionEnd
|
|
95
207
|
|
|
96
208
|
Fires after every assistant response. Uses the assistant's own output as the recall query — so memories surface during autonomous work, not just on user messages.
|
|
97
209
|
|
|
@@ -105,9 +217,9 @@ Fires after every assistant response. Uses the assistant's own output as the rec
|
|
|
105
217
|
|
|
106
218
|
**Why this matters:** Without the Stop hook, memories only surface when a human sends a message. For autonomous agents that work independently — running ping routines, doing research, monitoring news — their own memories never get recalled. The Stop hook closes that gap.
|
|
107
219
|
|
|
108
|
-
### `memento-precompact-distill.sh` — PreCompact
|
|
220
|
+
### `memento-precompact-distill.sh` — PreCompact / PreCompress
|
|
109
221
|
|
|
110
|
-
Fires before
|
|
222
|
+
Fires before the agent compresses the conversation. Parses the full JSONL transcript and extracts novel facts, decisions, and observations as stored memories. Supports two extraction backends:
|
|
111
223
|
|
|
112
224
|
- **`"llama"` (default)** — sends transcript to `/v1/distill`, which runs Llama 3.1 8B via Cloudflare Workers AI. Free.
|
|
113
225
|
- **`"claude-code"`** — runs `claude -p` locally for better extraction quality, then pushes to `/v1/memories/ingest`. Uses API credits.
|
|
@@ -135,19 +247,33 @@ Configure the model in `.memento.json`:
|
|
|
135
247
|
|
|
136
248
|
**Why this matters:** Context compaction destroys information. Without distillation, anything discussed but not explicitly saved is lost. This hook captures what's novel — deduplicating against existing memories — so nothing important vanishes.
|
|
137
249
|
|
|
250
|
+
### `memento-codex-notify.sh` — Codex notify
|
|
251
|
+
|
|
252
|
+
Receives JSON as `argv[1]` on `agent-turn-complete` events. Extracts the assistant's response and stores it as a memory observation — best-effort, fire-and-forget.
|
|
253
|
+
|
|
254
|
+
- **Event filter:** Only handles `agent-turn-complete` (other event types exit silently)
|
|
255
|
+
- **Minimum threshold:** Messages under 50 characters are skipped
|
|
256
|
+
- **Truncation:** Stores first 500 characters of the assistant's response
|
|
257
|
+
- **Tags:** `codex`, `turn-summary`, `auto-capture`
|
|
258
|
+
- **Config:** Reads `.memento.json` for API key, URL, and workspace
|
|
259
|
+
|
|
260
|
+
**Why this matters:** Codex CLI can't inject context back into the model, so recall hooks don't apply. But post-turn storage still captures what the agent learned — available for recall in future sessions via any agent.
|
|
261
|
+
|
|
138
262
|
---
|
|
139
263
|
|
|
140
264
|
## Hook output formats
|
|
141
265
|
|
|
142
|
-
Claude Code hooks
|
|
266
|
+
Claude Code and Gemini CLI hooks output data in the same JSON formats:
|
|
143
267
|
|
|
144
268
|
| Format | Where it appears | Used by |
|
|
145
269
|
|--------|-----------------|---------|
|
|
146
270
|
| `systemMessage` | User's terminal | All scripts |
|
|
147
|
-
| `hookSpecificOutput.additionalContext` | Model context (system-reminder) | UserPromptSubmit recall |
|
|
148
|
-
| `decision: "block"` with `reason` | Model context (next instruction) | Stop recall |
|
|
271
|
+
| `hookSpecificOutput.additionalContext` | Model context (system-reminder) | UserPromptSubmit / BeforeAgent recall |
|
|
272
|
+
| `decision: "block"` with `reason` | Model context (next instruction) | Stop / SessionEnd recall |
|
|
149
273
|
|
|
150
|
-
The `additionalContext` approach
|
|
274
|
+
The `additionalContext` approach works for UserPromptSubmit/BeforeAgent, PreToolUse, and PostToolUse events. For Stop/SessionEnd hooks, the `decision: "block"` pattern is the only mechanism that injects content into model context.
|
|
275
|
+
|
|
276
|
+
Codex `notify` has no output mechanism — it's fire-and-forget.
|
|
151
277
|
|
|
152
278
|
---
|
|
153
279
|
|
|
@@ -165,8 +291,13 @@ The `additionalContext` approach only works for UserPromptSubmit, PreToolUse, an
|
|
|
165
291
|
|
|
166
292
|
Follow the naming convention: `[system]-[hook]-[verb].sh`. Your script receives JSON on stdin with event-specific fields:
|
|
167
293
|
|
|
168
|
-
|
|
169
|
-
- **
|
|
170
|
-
- **
|
|
294
|
+
**Claude Code / Gemini CLI** (JSON on stdin):
|
|
295
|
+
- **SessionStart:** `{ "session_id": "..." }`
|
|
296
|
+
- **UserPromptSubmit / BeforeAgent:** `{ "prompt": "user's message" }`
|
|
297
|
+
- **Stop / SessionEnd:** `{ "last_assistant_message": "...", "stop_hook_active": false }`
|
|
298
|
+
- **PreCompact / PreCompress:** `{ "transcript_path": "~/.claude/projects/.../conversation.jsonl" }`
|
|
299
|
+
|
|
300
|
+
**Codex CLI** (JSON as `argv[1]`):
|
|
301
|
+
- **agent-turn-complete:** `{ "type": "agent-turn-complete", "last-assistant-message": "...", "input-messages": [...] }`
|
|
171
302
|
|
|
172
303
|
Source `.env` for credentials, call the Memento API, and output JSON to stdout. Exit 0 for no-op (nothing to report).
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Codex CLI notify hook — post-turn memory storage to Memento.
|
|
3
|
+
# Receives JSON payload as argv[1] on agent-turn-complete events.
|
|
4
|
+
#
|
|
5
|
+
# Extracts the assistant's response and stores it as a Memento observation.
|
|
6
|
+
# Best-effort — failures are silent. This is fire-and-forget.
|
|
7
|
+
|
|
8
|
+
set -o pipefail
|
|
9
|
+
|
|
10
|
+
PAYLOAD="$1"
|
|
11
|
+
[ -z "$PAYLOAD" ] && exit 0
|
|
12
|
+
|
|
13
|
+
# Only handle agent-turn-complete events
|
|
14
|
+
EVENT_TYPE=$(echo "$PAYLOAD" | python3 -c "import json,sys; print(json.load(sys.stdin).get('type',''))" 2>/dev/null)
|
|
15
|
+
[ "$EVENT_TYPE" != "agent-turn-complete" ] && exit 0
|
|
16
|
+
|
|
17
|
+
# Find .memento.json by walking up
|
|
18
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
19
|
+
CONFIG_JSON=""
|
|
20
|
+
_d="$(pwd)"
|
|
21
|
+
while true; do
|
|
22
|
+
if [ -f "$_d/.memento.json" ]; then
|
|
23
|
+
CONFIG_JSON=$(cat "$_d/.memento.json" 2>/dev/null)
|
|
24
|
+
break
|
|
25
|
+
fi
|
|
26
|
+
_p="$(dirname "$_d")"
|
|
27
|
+
[ "$_p" = "$_d" ] && break
|
|
28
|
+
_d="$_p"
|
|
29
|
+
done
|
|
30
|
+
|
|
31
|
+
[ -z "$CONFIG_JSON" ] && exit 0
|
|
32
|
+
|
|
33
|
+
# Extract config
|
|
34
|
+
MEMENTO_API_KEY=$(echo "$CONFIG_JSON" | python3 -c "import json,sys; print(json.load(sys.stdin).get('apiKey',''))" 2>/dev/null)
|
|
35
|
+
MEMENTO_API_URL=$(echo "$CONFIG_JSON" | python3 -c "import json,sys; print(json.load(sys.stdin).get('apiUrl',''))" 2>/dev/null)
|
|
36
|
+
MEMENTO_WORKSPACE=$(echo "$CONFIG_JSON" | python3 -c "import json,sys; print(json.load(sys.stdin).get('workspace',''))" 2>/dev/null)
|
|
37
|
+
|
|
38
|
+
MEMENTO_API="${MEMENTO_API_URL:-https://memento-api.myrakrusemark.workers.dev}"
|
|
39
|
+
[ -z "$MEMENTO_API_KEY" ] && exit 0
|
|
40
|
+
|
|
41
|
+
# Extract turn content and store to Memento API
|
|
42
|
+
python3 -c "
|
|
43
|
+
import json, sys, urllib.request
|
|
44
|
+
|
|
45
|
+
payload = json.loads(sys.argv[1])
|
|
46
|
+
api_url = sys.argv[2]
|
|
47
|
+
api_key = sys.argv[3]
|
|
48
|
+
workspace = sys.argv[4]
|
|
49
|
+
|
|
50
|
+
assistant_msg = payload.get('last-assistant-message', '')
|
|
51
|
+
|
|
52
|
+
# Only store if there's meaningful content
|
|
53
|
+
if not assistant_msg or len(assistant_msg) < 50:
|
|
54
|
+
sys.exit(0)
|
|
55
|
+
|
|
56
|
+
# Truncate for storage
|
|
57
|
+
summary = assistant_msg[:500]
|
|
58
|
+
if len(assistant_msg) > 500:
|
|
59
|
+
summary += '...'
|
|
60
|
+
|
|
61
|
+
data = json.dumps({
|
|
62
|
+
'content': summary,
|
|
63
|
+
'type': 'observation',
|
|
64
|
+
'tags': ['codex', 'turn-summary', 'auto-capture']
|
|
65
|
+
}).encode()
|
|
66
|
+
|
|
67
|
+
req = urllib.request.Request(
|
|
68
|
+
f'{api_url}/v1/memories',
|
|
69
|
+
data=data,
|
|
70
|
+
headers={
|
|
71
|
+
'Authorization': f'Bearer {api_key}',
|
|
72
|
+
'Content-Type': 'application/json',
|
|
73
|
+
'X-Memento-Workspace': workspace
|
|
74
|
+
}
|
|
75
|
+
)
|
|
76
|
+
urllib.request.urlopen(req, timeout=3)
|
|
77
|
+
" "$PAYLOAD" "$MEMENTO_API" "$MEMENTO_API_KEY" "$MEMENTO_WORKSPACE" 2>/dev/null || true
|
|
78
|
+
|
|
79
|
+
exit 0
|
package/src/cli.js
CHANGED
|
@@ -119,7 +119,7 @@ before compaction. Trust the hooks. Focus on writing good memories.`;
|
|
|
119
119
|
// ---------------------------------------------------------------------------
|
|
120
120
|
|
|
121
121
|
const HEADLESS_CMDS = {
|
|
122
|
-
"claude-code": (prompt) => ["claude", "-p", prompt],
|
|
122
|
+
"claude-code": (prompt) => ["claude", "-p", "--dangerously-skip-permissions", prompt],
|
|
123
123
|
"codex": (prompt) => ["codex", "exec", prompt],
|
|
124
124
|
"gemini": (prompt) => ["gemini", prompt],
|
|
125
125
|
"opencode": (prompt) => ["opencode", "run", prompt],
|
|
@@ -196,10 +196,23 @@ function writeJsonFile(filePath, data) {
|
|
|
196
196
|
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
199
|
+
/**
|
|
200
|
+
* Idempotently register a hook in a settings object.
|
|
201
|
+
* Works for both Claude Code and Gemini CLI (same JSON structure).
|
|
202
|
+
* Returns true if a new hook was added, false if already present.
|
|
203
|
+
*/
|
|
204
|
+
function ensureHook(settings, eventName, command, timeout) {
|
|
205
|
+
const existing = settings.hooks?.[eventName] || [];
|
|
206
|
+
const alreadyRegistered = existing.some((entry) =>
|
|
207
|
+
entry.hooks?.some((h) => h.command === command)
|
|
208
|
+
);
|
|
209
|
+
if (alreadyRegistered) return false;
|
|
210
|
+
if (!settings.hooks) settings.hooks = {};
|
|
211
|
+
settings.hooks[eventName] = [
|
|
212
|
+
...existing,
|
|
213
|
+
{ hooks: [{ type: "command", command, timeout }] },
|
|
214
|
+
];
|
|
215
|
+
return true;
|
|
203
216
|
}
|
|
204
217
|
|
|
205
218
|
function appendToGitignore(cwd, line) {
|
|
@@ -452,13 +465,16 @@ async function runInit(flags = {}) {
|
|
|
452
465
|
|
|
453
466
|
const hasClaude = selectedAgents.includes("claude-code");
|
|
454
467
|
|
|
455
|
-
// 5. Hooks —
|
|
468
|
+
// 5. Hooks — if any hook-supporting agent is selected (Claude Code, Gemini CLI)
|
|
469
|
+
const hasGemini = selectedAgents.includes("gemini");
|
|
470
|
+
const hasHookAgent = hasClaude || hasGemini;
|
|
471
|
+
|
|
456
472
|
let enableUserPrompt = false;
|
|
457
473
|
let enableStop = false;
|
|
458
474
|
let enablePreCompact = false;
|
|
459
475
|
let enableSessionStart = false;
|
|
460
476
|
|
|
461
|
-
if (
|
|
477
|
+
if (hasHookAgent) {
|
|
462
478
|
if (nonInteractive) {
|
|
463
479
|
// All hooks on by default in non-interactive mode
|
|
464
480
|
enableUserPrompt = true;
|
|
@@ -466,10 +482,10 @@ async function runInit(flags = {}) {
|
|
|
466
482
|
enablePreCompact = true;
|
|
467
483
|
enableSessionStart = false; // identity not enabled in -y mode
|
|
468
484
|
} else {
|
|
469
|
-
console.log("\
|
|
485
|
+
console.log("\nAgent hooks (automate recall + distillation):");
|
|
470
486
|
enableUserPrompt = await askYesNo(
|
|
471
487
|
rl,
|
|
472
|
-
"
|
|
488
|
+
" Prompt recall — recall on every message?",
|
|
473
489
|
true,
|
|
474
490
|
);
|
|
475
491
|
enableStop = await askYesNo(rl, " Stop — autonomous recall after responses?", true);
|
|
@@ -514,11 +530,12 @@ async function runInit(flags = {}) {
|
|
|
514
530
|
writeJsonFile(configPath, config);
|
|
515
531
|
created.push(".memento.json");
|
|
516
532
|
|
|
517
|
-
// 7. Copy hook scripts
|
|
533
|
+
// 7. Copy hook scripts — gated on any hook-supporting agent
|
|
534
|
+
const hasCodex = selectedAgents.includes("codex");
|
|
518
535
|
const anyHookEnabled =
|
|
519
536
|
enableUserPrompt || enableStop || enablePreCompact || enableSessionStart;
|
|
520
537
|
|
|
521
|
-
if (
|
|
538
|
+
if (hasHookAgent && anyHookEnabled) {
|
|
522
539
|
const pkgScriptsDir = path.resolve(__dirname, "..", "scripts");
|
|
523
540
|
const localScriptsDir = path.join(cwd, ".memento", "scripts");
|
|
524
541
|
if (!fs.existsSync(localScriptsDir))
|
|
@@ -532,8 +549,12 @@ async function runInit(flags = {}) {
|
|
|
532
549
|
enableSessionStart && "memento-sessionstart-identity.sh",
|
|
533
550
|
].filter(Boolean);
|
|
534
551
|
|
|
552
|
+
// Also copy Codex notify script if Codex is selected
|
|
553
|
+
if (hasCodex) scriptFiles.push("memento-codex-notify.sh");
|
|
554
|
+
|
|
535
555
|
for (const name of scriptFiles) {
|
|
536
556
|
const src = path.join(pkgScriptsDir, name);
|
|
557
|
+
if (!fs.existsSync(src)) continue; // skip if script doesn't exist yet
|
|
537
558
|
const dest = path.join(localScriptsDir, name);
|
|
538
559
|
fs.copyFileSync(src, dest);
|
|
539
560
|
fs.chmodSync(dest, 0o755);
|
|
@@ -546,67 +567,65 @@ async function runInit(flags = {}) {
|
|
|
546
567
|
const versionPath = path.join(cwd, ".memento", "version");
|
|
547
568
|
fs.writeFileSync(versionPath, pkgVersion + "\n");
|
|
548
569
|
|
|
549
|
-
//
|
|
550
|
-
const
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
570
|
+
// Hook script commands (absolute paths)
|
|
571
|
+
const recallCmd = path.join(localScriptsDir, "memento-userprompt-recall.sh");
|
|
572
|
+
const stopCmd = path.join(localScriptsDir, "memento-stop-recall.sh");
|
|
573
|
+
const precompactCmd = path.join(localScriptsDir, "memento-precompact-distill.sh");
|
|
574
|
+
const sessionStartCmd = path.join(localScriptsDir, "memento-sessionstart-identity.sh");
|
|
575
|
+
|
|
576
|
+
// 8a. Claude Code hooks — .claude/settings.local.json
|
|
577
|
+
if (hasClaude) {
|
|
578
|
+
const settingsPath = path.join(cwd, ".claude", "settings.local.json");
|
|
579
|
+
const settings = readJsonFile(settingsPath) || {};
|
|
580
|
+
let changed = false;
|
|
581
|
+
if (enableUserPrompt) changed = ensureHook(settings, "UserPromptSubmit", recallCmd, 5000) || changed;
|
|
582
|
+
if (enableStop) changed = ensureHook(settings, "Stop", stopCmd, 5000) || changed;
|
|
583
|
+
if (enablePreCompact) changed = ensureHook(settings, "PreCompact", precompactCmd, 30000) || changed;
|
|
584
|
+
if (enableSessionStart) changed = ensureHook(settings, "SessionStart", sessionStartCmd, 10000) || changed;
|
|
585
|
+
if (changed) {
|
|
586
|
+
writeJsonFile(settingsPath, settings);
|
|
587
|
+
created.push(".claude/settings.local.json");
|
|
588
|
+
}
|
|
563
589
|
}
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
590
|
+
|
|
591
|
+
// 8b. Gemini CLI hooks — .gemini/settings.json
|
|
592
|
+
if (hasGemini) {
|
|
593
|
+
const settingsPath = path.join(cwd, ".gemini", "settings.json");
|
|
594
|
+
const settings = readJsonFile(settingsPath) || {};
|
|
595
|
+
let changed = false;
|
|
596
|
+
if (enableUserPrompt) changed = ensureHook(settings, "BeforeAgent", recallCmd, 5000) || changed;
|
|
597
|
+
if (enableStop) changed = ensureHook(settings, "SessionEnd", stopCmd, 5000) || changed;
|
|
598
|
+
if (enablePreCompact) changed = ensureHook(settings, "PreCompress", precompactCmd, 30000) || changed;
|
|
599
|
+
if (enableSessionStart) changed = ensureHook(settings, "SessionStart", sessionStartCmd, 10000) || changed;
|
|
600
|
+
if (changed) {
|
|
601
|
+
writeJsonFile(settingsPath, settings);
|
|
602
|
+
created.push(".gemini/settings.json (hooks)");
|
|
603
|
+
}
|
|
576
604
|
}
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// 8c. Codex CLI notify — .codex/config.toml (fire-and-forget, post-turn memory storage)
|
|
608
|
+
if (hasCodex) {
|
|
609
|
+
const pkgScriptsDir = path.resolve(__dirname, "..", "scripts");
|
|
610
|
+
const localScriptsDir = path.join(cwd, ".memento", "scripts");
|
|
611
|
+
// Ensure the notify script is copied (may not have been copied above if no hook agent)
|
|
612
|
+
const notifySrc = path.join(pkgScriptsDir, "memento-codex-notify.sh");
|
|
613
|
+
const notifyDest = path.join(localScriptsDir, "memento-codex-notify.sh");
|
|
614
|
+
if (fs.existsSync(notifySrc) && !fs.existsSync(notifyDest)) {
|
|
615
|
+
fs.mkdirSync(localScriptsDir, { recursive: true });
|
|
616
|
+
fs.copyFileSync(notifySrc, notifyDest);
|
|
617
|
+
fs.chmodSync(notifyDest, 0o755);
|
|
589
618
|
}
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
),
|
|
600
|
-
timeout: 10000,
|
|
601
|
-
},
|
|
602
|
-
],
|
|
603
|
-
},
|
|
604
|
-
];
|
|
619
|
+
const notifyScript = notifyDest;
|
|
620
|
+
const codexTomlPath = path.join(cwd, ".codex", "config.toml");
|
|
621
|
+
let tomlContent = "";
|
|
622
|
+
try { tomlContent = fs.readFileSync(codexTomlPath, "utf-8"); } catch { /* doesn't exist yet */ }
|
|
623
|
+
if (!tomlContent.includes("notify")) {
|
|
624
|
+
const notifyLine = `\nnotify = ["bash", "${notifyScript}"]\n`;
|
|
625
|
+
const separator = tomlContent && !tomlContent.endsWith("\n") ? "\n" : "";
|
|
626
|
+
fs.writeFileSync(codexTomlPath, tomlContent + separator + notifyLine);
|
|
627
|
+
created.push(".codex/config.toml (notify)");
|
|
605
628
|
}
|
|
606
|
-
|
|
607
|
-
const settingsPath = path.join(cwd, ".claude", "settings.local.json");
|
|
608
|
-
mergeJsonFile(settingsPath, { hooks });
|
|
609
|
-
created.push(".claude/settings.local.json");
|
|
610
629
|
}
|
|
611
630
|
|
|
612
631
|
// 9. Per-agent config files
|
|
@@ -674,12 +693,13 @@ async function runInit(flags = {}) {
|
|
|
674
693
|
// Interactive: ask with explicit command shown
|
|
675
694
|
if (cmdParts) {
|
|
676
695
|
const [cmd, ...args] = cmdParts;
|
|
677
|
-
const
|
|
696
|
+
const flagArgs = args.slice(0, -1).join(" ");
|
|
697
|
+
const displayCmd = `${cmd} ${flagArgs} <prompt>`;
|
|
678
698
|
const rl2 = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
679
699
|
console.log("─".repeat(60));
|
|
680
700
|
const integrate = await askYesNo(
|
|
681
701
|
rl2,
|
|
682
|
-
`\n Auto-integrate instructions into your project?\n This will run: ${displayCmd}\n\n Proceed?`,
|
|
702
|
+
`\n Auto-integrate instructions into your project?\n This will run: ${displayCmd}\n\n ⚠ This uses --dangerously-skip-permissions so the agent can\n write to CLAUDE.md without prompting. If you prefer, decline\n and we'll print the instructions for you to add manually.\n\n Proceed?`,
|
|
683
703
|
true,
|
|
684
704
|
);
|
|
685
705
|
rl2.close();
|
|
@@ -746,28 +766,51 @@ async function runUpdate() {
|
|
|
746
766
|
const versionPath = path.join(cwd, ".memento", "version");
|
|
747
767
|
fs.writeFileSync(versionPath, pkgVersion + "\n");
|
|
748
768
|
|
|
749
|
-
// Ensure SessionStart hook is registered for
|
|
750
|
-
// Detect by
|
|
769
|
+
// Ensure SessionStart hook is registered for agents that support hooks
|
|
770
|
+
// Detect by config agents field or directory presence (older configs may lack agents)
|
|
751
771
|
const config = readJsonFile(configPath) || {};
|
|
752
|
-
const
|
|
772
|
+
const agents = config.agents || [];
|
|
773
|
+
const sessionStartCmd = path.join(localScriptsDir, "memento-sessionstart-identity.sh");
|
|
774
|
+
const registeredHooks = [];
|
|
775
|
+
|
|
776
|
+
// Claude Code
|
|
777
|
+
const hasClaude = agents.includes("claude-code")
|
|
753
778
|
|| fs.existsSync(path.join(cwd, ".claude"));
|
|
754
|
-
let hooksUpdated = false;
|
|
755
779
|
if (hasClaude) {
|
|
756
780
|
const settingsPath = path.join(cwd, ".claude", "settings.local.json");
|
|
757
|
-
const
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
781
|
+
const settings = readJsonFile(settingsPath) || {};
|
|
782
|
+
if (ensureHook(settings, "SessionStart", sessionStartCmd, 10000)) {
|
|
783
|
+
writeJsonFile(settingsPath, settings);
|
|
784
|
+
registeredHooks.push("Claude Code → .claude/settings.local.json");
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
// Gemini CLI
|
|
789
|
+
const hasGemini = agents.includes("gemini")
|
|
790
|
+
|| fs.existsSync(path.join(cwd, ".gemini"));
|
|
791
|
+
if (hasGemini) {
|
|
792
|
+
const settingsPath = path.join(cwd, ".gemini", "settings.json");
|
|
793
|
+
const settings = readJsonFile(settingsPath) || {};
|
|
794
|
+
if (ensureHook(settings, "SessionStart", sessionStartCmd, 10000)) {
|
|
795
|
+
writeJsonFile(settingsPath, settings);
|
|
796
|
+
registeredHooks.push("Gemini CLI → .gemini/settings.json");
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
// Codex CLI — ensure notify is configured
|
|
801
|
+
const hasCodex = agents.includes("codex")
|
|
802
|
+
|| fs.existsSync(path.join(cwd, ".codex"));
|
|
803
|
+
if (hasCodex) {
|
|
804
|
+
const notifyScript = path.join(localScriptsDir, "memento-codex-notify.sh");
|
|
805
|
+
const codexTomlPath = path.join(cwd, ".codex", "config.toml");
|
|
806
|
+
let tomlContent = "";
|
|
807
|
+
try { tomlContent = fs.readFileSync(codexTomlPath, "utf-8"); } catch { /* doesn't exist yet */ }
|
|
808
|
+
if (!tomlContent.includes("notify") && fs.existsSync(notifyScript)) {
|
|
809
|
+
const notifyLine = `\nnotify = ["bash", "${notifyScript}"]\n`;
|
|
810
|
+
const separator = tomlContent && !tomlContent.endsWith("\n") ? "\n" : "";
|
|
811
|
+
fs.mkdirSync(path.dirname(codexTomlPath), { recursive: true });
|
|
812
|
+
fs.writeFileSync(codexTomlPath, tomlContent + separator + notifyLine);
|
|
813
|
+
registeredHooks.push("Codex CLI → .codex/config.toml (notify)");
|
|
771
814
|
}
|
|
772
815
|
}
|
|
773
816
|
|
|
@@ -776,9 +819,11 @@ async function runUpdate() {
|
|
|
776
819
|
for (const name of updated) {
|
|
777
820
|
console.log(` ${name}`);
|
|
778
821
|
}
|
|
779
|
-
if (
|
|
822
|
+
if (registeredHooks.length > 0) {
|
|
780
823
|
console.log("\n Registered hooks:");
|
|
781
|
-
|
|
824
|
+
for (const hook of registeredHooks) {
|
|
825
|
+
console.log(` ${hook}`);
|
|
826
|
+
}
|
|
782
827
|
}
|
|
783
828
|
console.log(`\n Version written to .memento/version`);
|
|
784
829
|
console.log(" Restart your agent session to pick up changes.\n");
|