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 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 sets up hooks (Claude Code only) — all in one command.
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. Three production-ready scripts are included in `scripts/`:
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 | Event | What it does |
160
- |--------|-------|-------------|
161
- | `memento-userprompt-recall.sh` | UserPromptSubmit | Recalls memories relevant to the user's message |
162
- | `memento-stop-recall.sh` | Stop | Recalls memories from the assistant's own output (autonomous work) |
163
- | `memento-precompact-distill.sh` | PreCompact | Extracts memories from the conversation before context compression |
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memento-mcp",
3
- "version": "0.3.4",
3
+ "version": "0.3.6",
4
4
  "mcpName": "io.github.myrakrusemark/memento-protocol",
5
5
  "description": "The Memento Protocol — persistent memory for AI agents",
6
6
  "type": "module",
package/scripts/README.md CHANGED
@@ -1,18 +1,56 @@
1
1
  # Memento Protocol — Hook Scripts
2
2
 
3
- Automation hooks for [Claude Code](https://docs.anthropic.com/en/docs/claude-code) that connect the Memento API to session lifecycle events. These scripts make memory automatic — recall on every message, distillation before context loss.
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
- ## Naming convention
5
+ ## Supported agents
6
6
 
7
- Hook scripts follow the pattern `[system]-[hook]-[verb].sh`:
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
- | Script | Event | What it does |
10
- |--------|-------|-------------|
11
- | `memento-userprompt-recall.sh` | UserPromptSubmit | Recall memories relevant to the user's message |
12
- | `memento-stop-recall.sh` | Stop | Recall memories relevant to the assistant's own output |
13
- | `memento-precompact-distill.sh` | PreCompact | Extract memories from the conversation before context compression |
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
- ## Set up hooks
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 in Claude Code
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": "/path/to/memento-protocol/scripts/memento-userprompt-recall.sh",
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": "/path/to/memento-protocol/scripts/memento-stop-recall.sh",
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": "/path/to/memento-protocol/scripts/memento-precompact-distill.sh",
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
- Replace `/path/to/memento-protocol` with the actual absolute path to your clone.
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-userprompt-recall.sh` — UserPromptSubmit
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 Claude Code compresses the conversation. Parses the full JSONL transcript and extracts novel facts, decisions, and observations as stored memories. Supports two extraction backends:
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 can output data in several formats. These scripts use two:
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 only works for UserPromptSubmit, PreToolUse, and PostToolUse events. For Stop hooks, the `decision: "block"` pattern is the only mechanism that injects content into model context.
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
- - **UserPromptSubmit:** `{ "prompt": "user's message" }`
169
- - **Stop:** `{ "last_assistant_message": "...", "stop_hook_active": false }`
170
- - **PreCompact:** `{ "transcript_path": "~/.claude/projects/.../conversation.jsonl" }`
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
- function mergeJsonFile(filePath, data) {
200
- const existing = readJsonFile(filePath) || {};
201
- const merged = deepMerge(existing, data);
202
- writeJsonFile(filePath, merged);
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 — only if Claude Code selected
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 (hasClaude) {
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("\nClaude Code hooks (automate recall + distillation):");
485
+ console.log("\nAgent hooks (automate recall + distillation):");
470
486
  enableUserPrompt = await askYesNo(
471
487
  rl,
472
- " UserPromptSubmit — recall on every message?",
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 + write Claude Code settings — gated on hasClaude
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 (hasClaude && anyHookEnabled) {
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
- // 8. Write .claude/settings.local.json (hooks)
550
- const hooks = {};
551
- if (enableUserPrompt) {
552
- hooks.UserPromptSubmit = [
553
- {
554
- hooks: [
555
- {
556
- type: "command",
557
- command: path.join(localScriptsDir, "memento-userprompt-recall.sh"),
558
- timeout: 5000,
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
- if (enableStop) {
565
- hooks.Stop = [
566
- {
567
- hooks: [
568
- {
569
- type: "command",
570
- command: path.join(localScriptsDir, "memento-stop-recall.sh"),
571
- timeout: 5000,
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
- if (enablePreCompact) {
578
- hooks.PreCompact = [
579
- {
580
- hooks: [
581
- {
582
- type: "command",
583
- command: path.join(localScriptsDir, "memento-precompact-distill.sh"),
584
- timeout: 30000,
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
- if (enableSessionStart) {
591
- hooks.SessionStart = [
592
- {
593
- hooks: [
594
- {
595
- type: "command",
596
- command: path.join(
597
- localScriptsDir,
598
- "memento-sessionstart-identity.sh",
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 displayCmd = `${cmd} ${args[0]}${args.length > 1 ? " ..." : ""}`;
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 Claude Code workspaces
750
- // Detect by .claude/ dir (older configs may not have agents field)
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 hasClaude = (config.agents || []).includes("claude-code")
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 claudeSettings = readJsonFile(settingsPath) || {};
758
- const sessionStartCmd = path.join(localScriptsDir, "memento-sessionstart-identity.sh");
759
- const existingHooks = claudeSettings.hooks?.["SessionStart"] || [];
760
- const hasSessionStart = existingHooks.some((entry) =>
761
- entry.hooks?.some((h) => h.command === sessionStartCmd)
762
- );
763
- if (!hasSessionStart) {
764
- claudeSettings.hooks = claudeSettings.hooks || {};
765
- claudeSettings.hooks["SessionStart"] = [
766
- ...existingHooks,
767
- { hooks: [{ type: "command", command: sessionStartCmd, timeout: 10000 }] },
768
- ];
769
- writeJsonFile(settingsPath, claudeSettings);
770
- hooksUpdated = true;
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 (hooksUpdated) {
822
+ if (registeredHooks.length > 0) {
780
823
  console.log("\n Registered hooks:");
781
- console.log(" SessionStart → memento-sessionstart-identity.sh (identity + version check)");
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");