memento-mcp 0.2.5 → 0.3.1

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`, configures Claude Code hooks, and sets up the MCP server all in one command. Restart Claude Code to load the new config.
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.
14
14
 
15
15
  ---
16
16
 
@@ -43,29 +43,55 @@ Save the `api_key` from the response — you'll need it next.
43
43
 
44
44
  ### Step 2: Configure your MCP client
45
45
 
46
- **Claude Code (project-level):** Create `.mcp.json` in your project root.
46
+ **Claude Code** `.mcp.json` in your project root (or `~/.claude.json` globally):
47
47
 
48
- **Claude Code (global):** Add to `~/.claude.json` under `"mcpServers"`.
48
+ ```json
49
+ {
50
+ "mcpServers": {
51
+ "memento": {
52
+ "command": "npx",
53
+ "args": ["-y", "memento-mcp"]
54
+ }
55
+ }
56
+ }
57
+ ```
58
+
59
+ **OpenAI Codex** — `.codex/config.toml`:
60
+
61
+ ```toml
62
+ [mcp_servers.memento]
63
+ command = "npx"
64
+ args = ["-y", "memento-mcp"]
65
+ ```
49
66
 
50
- **Claude Desktop:** Add to your `claude_desktop_config.json`.
67
+ **Gemini CLI** `.gemini/settings.json`:
51
68
 
52
69
  ```json
53
70
  {
54
71
  "mcpServers": {
55
72
  "memento": {
56
- "command": "node",
57
- "args": ["/home/you/memento-protocol/src/index.js"],
58
- "env": {
59
- "MEMENTO_API_KEY": "mp_live_your_key_here",
60
- "MEMENTO_API_URL": "https://memento-api.myrakrusemark.workers.dev",
61
- "MEMENTO_WORKSPACE": "my-project"
62
- }
73
+ "command": "npx",
74
+ "args": ["-y", "memento-mcp"]
75
+ }
76
+ }
77
+ }
78
+ ```
79
+
80
+ **OpenCode** — `opencode.json`:
81
+
82
+ ```json
83
+ {
84
+ "mcp": {
85
+ "memento": {
86
+ "type": "local",
87
+ "command": ["npx", "-y", "memento-mcp"],
88
+ "enabled": true
63
89
  }
64
90
  }
65
91
  }
66
92
  ```
67
93
 
68
- > **Tip:** Replace the path with the actual absolute path to `src/index.js` in your clone. Run `echo "$(pwd)/src/index.js"` from inside the repo to get it.
94
+ Set credentials via `.memento.json` (created by `npx memento-mcp init`) or environment variables `MEMENTO_API_KEY`, `MEMENTO_API_URL`, `MEMENTO_WORKSPACE`.
69
95
 
70
96
  ### Step 3: Restart your client
71
97
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memento-mcp",
3
- "version": "0.2.5",
3
+ "version": "0.3.1",
4
4
  "mcpName": "io.github.myrakrusemark/memento-protocol",
5
5
  "description": "The Memento Protocol — persistent memory for AI agents",
6
6
  "type": "module",
@@ -0,0 +1,140 @@
1
+ #!/bin/bash
2
+ # hook-toast.sh — tmux popup notifications for agent hooks.
3
+ #
4
+ # Usage:
5
+ # hook-toast.sh <system> <message> # one-shot toast (auto-closes after 2s)
6
+ # hook-toast.sh <system> --status <id> # start a multi-stage toast (poll mode)
7
+ # hook-toast.sh --update <id> <message> # update a running toast
8
+ # hook-toast.sh --close <id> # close a running toast (after delay)
9
+ #
10
+ # Built-in systems: fathom, memento
11
+ # fathom → 📝 purple (colour141)
12
+ # memento → 🧠 teal (colour37)
13
+ #
14
+ # Any other system name works too — gets a default icon and grey border.
15
+ #
16
+ # Multi-stage example (PreCompact):
17
+ # hook-toast.sh memento --status precompact
18
+ # hook-toast.sh --update precompact "⏳ Getting context..."
19
+ # hook-toast.sh --update precompact "⏳ Extracting memories..."
20
+ # hook-toast.sh --update precompact "✓ Stored 7 memories"
21
+ # hook-toast.sh --close precompact
22
+
23
+ TOAST_DIR="/tmp/hook-toast"
24
+ mkdir -p "$TOAST_DIR"
25
+
26
+ # Bail silently if not in tmux
27
+ if ! tmux info &>/dev/null; then
28
+ exit 0
29
+ fi
30
+
31
+ get_style() {
32
+ case "$1" in
33
+ memento) echo "colour37" ;;
34
+ fathom) echo "colour141" ;;
35
+ *) echo "colour245" ;;
36
+ esac
37
+ }
38
+
39
+ get_icon() {
40
+ case "$1" in
41
+ memento) echo "🧠" ;;
42
+ fathom) echo "📝" ;;
43
+ *) echo "📌" ;;
44
+ esac
45
+ }
46
+
47
+ get_title() {
48
+ case "$1" in
49
+ memento) echo "Memento" ;;
50
+ fathom) echo "Fathom" ;;
51
+ *) echo "$1" ;;
52
+ esac
53
+ }
54
+
55
+ # One-shot toast
56
+ toast_oneshot() {
57
+ local system="$1"
58
+ local message="$2"
59
+ local colour icon title
60
+ colour=$(get_style "$system")
61
+ icon=$(get_icon "$system")
62
+ title=$(get_title "$system")
63
+
64
+ (tmux display-popup -x R -y 0 -w 42 -h 3 \
65
+ -s "fg=$colour" -T " $icon $title " -E \
66
+ "echo ' $message'; sleep 2" &>/dev/null &)
67
+ }
68
+
69
+ # Start a multi-stage toast (polling status file)
70
+ toast_start() {
71
+ local system="$1"
72
+ local id="$2"
73
+ local status_file="$TOAST_DIR/$id"
74
+ local colour icon title
75
+ colour=$(get_style "$system")
76
+ icon=$(get_icon "$system")
77
+ title=$(get_title "$system")
78
+
79
+ echo "⏳ Starting..." > "$status_file"
80
+
81
+ (tmux display-popup -x R -y 0 -w 42 -h 3 \
82
+ -s "fg=$colour" -T " $icon $title " -E "
83
+ LAST=''
84
+ while [ -f '$status_file' ]; do
85
+ MSG=\$(cat '$status_file' 2>/dev/null)
86
+ if [ \"\$MSG\" != \"\$LAST\" ]; then
87
+ clear
88
+ echo \" \$MSG\"
89
+ LAST=\"\$MSG\"
90
+ fi
91
+ case \"\$MSG\" in ✓*|✗*) sleep 2; break ;; esac
92
+ sleep 0.2
93
+ done
94
+ " &>/dev/null &)
95
+ }
96
+
97
+ # Update a running toast
98
+ toast_update() {
99
+ local id="$1"
100
+ local message="$2"
101
+ echo "$message" > "$TOAST_DIR/$id"
102
+ }
103
+
104
+ # Close a running toast (remove status file — popup exits on next poll)
105
+ toast_close() {
106
+ local id="$1"
107
+ # Give the popup time to display the final message
108
+ sleep 2
109
+ rm -f "$TOAST_DIR/$id"
110
+ }
111
+
112
+ # --- Argument parsing ---
113
+ case "${1:-}" in
114
+ --update)
115
+ toast_update "$2" "$3"
116
+ ;;
117
+ --close)
118
+ toast_close "$2"
119
+ ;;
120
+ --*)
121
+ echo "Unknown option: $1" >&2
122
+ exit 1
123
+ ;;
124
+ "")
125
+ echo "Usage: hook-toast.sh <system> <message>" >&2
126
+ echo " hook-toast.sh <system> --status <id>" >&2
127
+ echo " hook-toast.sh --update <id> <message>" >&2
128
+ echo " hook-toast.sh --close <id>" >&2
129
+ exit 1
130
+ ;;
131
+ *)
132
+ SYSTEM="$1"
133
+ shift
134
+ if [ "${1:-}" = "--status" ]; then
135
+ toast_start "$SYSTEM" "$2"
136
+ else
137
+ toast_oneshot "$SYSTEM" "$*"
138
+ fi
139
+ ;;
140
+ esac
@@ -8,17 +8,27 @@
8
8
  set -o pipefail
9
9
 
10
10
  SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
11
+ TOAST="$SCRIPT_DIR/hook-toast.sh"
12
+
13
+ # Toast: start multi-stage
14
+ "$TOAST" memento --status distill &>/dev/null
15
+ "$TOAST" --update distill "⏳ Getting context..." &>/dev/null
16
+
11
17
  INPUT=$(cat)
12
18
 
13
19
  TRANSCRIPT_PATH=$(echo "$INPUT" | jq -r '.transcript_path' | sed "s|~|$HOME|")
14
20
 
15
21
  # Only distill if transcript exists and has content
16
22
  if [ ! -f "$TRANSCRIPT_PATH" ]; then
23
+ "$TOAST" --update distill "✗ No transcript found" &>/dev/null
24
+ ("$TOAST" --close distill &>/dev/null &)
17
25
  exit 0
18
26
  fi
19
27
 
20
28
  LINE_COUNT=$(wc -l < "$TRANSCRIPT_PATH")
21
29
  if [ "$LINE_COUNT" -lt 2 ]; then
30
+ "$TOAST" --update distill "✓ Skipped (tiny conversation)" &>/dev/null
31
+ ("$TOAST" --close distill &>/dev/null &)
22
32
  exit 0
23
33
  fi
24
34
 
@@ -79,9 +89,14 @@ else
79
89
  fi
80
90
 
81
91
  if [ ${#TRANSCRIPT_TEXT} -lt 200 ]; then
92
+ "$TOAST" --update distill "✓ Skipped (too short)" &>/dev/null
93
+ ("$TOAST" --close distill &>/dev/null &)
82
94
  exit 0 # Too short to distill anything useful
83
95
  fi
84
96
 
97
+ # Toast: extracting
98
+ "$TOAST" --update distill "⏳ Extracting memories..." &>/dev/null
99
+
85
100
  DISTILL_MODEL="${DISTILL_MODEL:-llama}"
86
101
 
87
102
  # claude-code path: run extraction locally via claude -p, push to /v1/memories/ingest
@@ -166,9 +181,15 @@ print(json.dumps({'memories': parsed, 'source': 'distill:claude-code'}))
166
181
  -d "$INGEST_PAYLOAD" \
167
182
  "$MEMENTO_API/v1/memories/ingest" > /dev/null 2>&1
168
183
 
184
+ "$TOAST" --update distill "✓ Stored ${MEMORY_COUNT} memories" &>/dev/null
185
+ ("$TOAST" --close distill &>/dev/null &)
186
+
169
187
  python3 -c "import json,sys; print(json.dumps({'systemMessage': sys.argv[1]}))" \
170
188
  "Memento Distill (claude-code): ${MEMORY_COUNT} memories — ${TYPE_BREAKDOWN}"
171
189
  else
190
+ "$TOAST" --update distill "✓ No memories extracted" &>/dev/null
191
+ ("$TOAST" --close distill &>/dev/null &)
192
+
172
193
  python3 -c "import json,sys; print(json.dumps({'systemMessage': sys.argv[1]}))" \
173
194
  "Memento Distill (claude-code): no memories extracted"
174
195
  fi
@@ -206,9 +227,15 @@ MEMORY_COUNT=$(echo "$DISTILL_SUMMARY" | cut -d'|' -f1)
206
227
  TYPE_BREAKDOWN=$(echo "$DISTILL_SUMMARY" | cut -d'|' -f2)
207
228
 
208
229
  if [ "${MEMORY_COUNT:-0}" -gt 0 ] 2>/dev/null; then
230
+ "$TOAST" --update distill "✓ Stored ${MEMORY_COUNT} memories" &>/dev/null
231
+ ("$TOAST" --close distill &>/dev/null &)
232
+
209
233
  python3 -c "import json,sys; print(json.dumps({'systemMessage': sys.argv[1]}))" \
210
234
  "Memento Distill: ${MEMORY_COUNT} memories — ${TYPE_BREAKDOWN}"
211
235
  else
236
+ "$TOAST" --update distill "✓ No memories extracted" &>/dev/null
237
+ ("$TOAST" --close distill &>/dev/null &)
238
+
212
239
  python3 -c "import json,sys; print(json.dumps({'systemMessage': sys.argv[1]}))" \
213
240
  "Memento Distill: no memories extracted"
214
241
  fi
@@ -10,6 +10,7 @@
10
10
  set -o pipefail
11
11
 
12
12
  SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
13
+ TOAST="$SCRIPT_DIR/hook-toast.sh"
13
14
 
14
15
  # --- Config from .memento.json (if present) ---
15
16
  CONFIG_JSON=$(python3 -c "
@@ -80,6 +81,9 @@ ACTIVE_TMP=$(mktemp)
80
81
  SKIP_TMP=$(mktemp)
81
82
  trap 'rm -f "$IDENTITY_TMP" "$ACTIVE_TMP" "$SKIP_TMP"' EXIT
82
83
 
84
+ # Toast: loading identity
85
+ "$TOAST" memento "⏳ Loading identity..." &>/dev/null
86
+
83
87
  # Fetch all three endpoints in parallel
84
88
  curl -s --max-time 3 -H "$AUTH_HEADER" -H "$WS_HEADER" \
85
89
  "$MEMENTO_API/v1/identity" > "$IDENTITY_TMP" 2>/dev/null &
@@ -159,4 +163,7 @@ print(json.dumps({
159
163
  }))
160
164
  " "$IDENTITY_TMP" "$ACTIVE_TMP" "$SKIP_TMP" 2>/dev/null
161
165
 
166
+ # Toast: done
167
+ "$TOAST" memento "✓ Identity loaded" &>/dev/null
168
+
162
169
  exit 0
@@ -6,6 +6,7 @@
6
6
  set -o pipefail
7
7
 
8
8
  SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
9
+ TOAST="$SCRIPT_DIR/hook-toast.sh"
9
10
 
10
11
  # --- Config from .memento.json (if present) ---
11
12
  CONFIG_JSON=$(python3 -c "
@@ -74,6 +75,9 @@ fi
74
75
  # Truncate to first 500 chars for the query
75
76
  QUERY="${ASSISTANT_MSG:0:500}"
76
77
 
78
+ # Toast: start retrieving
79
+ "$TOAST" memento "⏳ Autonomous recall..." &>/dev/null
80
+
77
81
  # Call Memento /v1/context
78
82
  RESULT=$(curl -s --max-time 3 \
79
83
  -X POST \
@@ -122,9 +126,13 @@ if [ -n "$REMAINING" ]; then
122
126
  fi
123
127
 
124
128
  if [ -z "$SAAS_COUNT" ] || [ "$SAAS_COUNT" = "0" ]; then
129
+ "$TOAST" memento "✓ No memories matched" &>/dev/null
125
130
  exit 0
126
131
  fi
127
132
 
133
+ # Toast: result
134
+ "$TOAST" memento "✓ ${SAAS_COUNT} memories recalled" &>/dev/null
135
+
128
136
  # Block the Stop so Claude continues — the reason becomes Claude's next instruction.
129
137
  REASON="Autonomous Recall: ${SAAS_COUNT} memories surfaced from your last response.
130
138
  ${SAAS_DETAIL}
@@ -7,6 +7,7 @@
7
7
  set -o pipefail
8
8
 
9
9
  SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
10
+ TOAST="$SCRIPT_DIR/hook-toast.sh"
10
11
 
11
12
  # --- Config from .memento.json (if present) ---
12
13
  CONFIG_JSON=$(python3 -c "
@@ -66,6 +67,9 @@ fi
66
67
 
67
68
  QUERY="${USER_MESSAGE:0:500}"
68
69
 
70
+ # Toast: start retrieving
71
+ "$TOAST" memento "⏳ Retrieving memories..." &>/dev/null
72
+
69
73
  # Call Memento SaaS /v1/context
70
74
  SAAS_OUTPUT=$(curl -s --max-time 3 \
71
75
  -X POST \
@@ -118,9 +122,13 @@ if [ -n "$REMAINING" ]; then
118
122
  fi
119
123
 
120
124
  if [ -z "$SAAS_COUNT" ] || [ "$SAAS_COUNT" = "0" ]; then
125
+ "$TOAST" memento "✓ No memories matched" &>/dev/null
121
126
  exit 0
122
127
  fi
123
128
 
129
+ # Toast: result
130
+ "$TOAST" memento "✓ ${SAAS_COUNT} memories recalled" &>/dev/null
131
+
124
132
  DETAIL_TEXT="Memento Recall: ${SAAS_COUNT} memories"
125
133
  DETAIL_TEXT="$DETAIL_TEXT"$'\n'"$SAAS_DETAIL"
126
134
  DETAIL_TEXT="$DETAIL_TEXT"$'\n'$'\n'"REMINDER: If Memento MCP tools are not loaded, run: ToolSearch query=\"+memento\" max_results=20"
package/src/cli.js CHANGED
@@ -96,6 +96,14 @@ function httpsPost(url, body) {
96
96
  // File writers
97
97
  // ---------------------------------------------------------------------------
98
98
 
99
+ function readJsonFile(filePath) {
100
+ try {
101
+ return JSON.parse(fs.readFileSync(filePath, "utf8"));
102
+ } catch {
103
+ return null;
104
+ }
105
+ }
106
+
99
107
  function writeJsonFile(filePath, data) {
100
108
  const dir = path.dirname(filePath);
101
109
  if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
@@ -103,14 +111,7 @@ function writeJsonFile(filePath, data) {
103
111
  }
104
112
 
105
113
  function mergeJsonFile(filePath, data) {
106
- let existing = {};
107
- if (fs.existsSync(filePath)) {
108
- try {
109
- existing = JSON.parse(fs.readFileSync(filePath, "utf8"));
110
- } catch {
111
- // Corrupt file — overwrite
112
- }
113
- }
114
+ const existing = readJsonFile(filePath) || {};
114
115
  const merged = deepMerge(existing, data);
115
116
  writeJsonFile(filePath, merged);
116
117
  }
@@ -127,6 +128,106 @@ function appendToGitignore(cwd, line) {
127
128
  return true;
128
129
  }
129
130
 
131
+ // ---------------------------------------------------------------------------
132
+ // Agent registry — per-agent MCP config writers
133
+ // ---------------------------------------------------------------------------
134
+
135
+ const MCP_SERVER_ENTRY = {
136
+ command: "npx",
137
+ args: ["-y", "memento-mcp"],
138
+ };
139
+
140
+ function writeMcpJson(cwd) {
141
+ const filePath = path.join(cwd, ".mcp.json");
142
+ const existing = readJsonFile(filePath) || {};
143
+ deepMerge(existing, { mcpServers: { memento: MCP_SERVER_ENTRY } });
144
+ writeJsonFile(filePath, existing);
145
+ return ".mcp.json";
146
+ }
147
+
148
+ function writeCodexToml(cwd) {
149
+ const dir = path.join(cwd, ".codex");
150
+ fs.mkdirSync(dir, { recursive: true });
151
+ const filePath = path.join(dir, "config.toml");
152
+
153
+ let content = "";
154
+ try {
155
+ content = fs.readFileSync(filePath, "utf8");
156
+ } catch {
157
+ /* file doesn't exist */
158
+ }
159
+
160
+ // Skip if memento section already exists
161
+ if (/\[mcp_servers\.memento\]/.test(content)) {
162
+ return ".codex/config.toml (already configured)";
163
+ }
164
+
165
+ const section = `\n[mcp_servers.memento]\ncommand = "npx"\nargs = ["-y", "memento-mcp"]\n`;
166
+ const separator = content && !content.endsWith("\n") ? "\n" : "";
167
+ fs.writeFileSync(filePath, content + separator + section);
168
+ return ".codex/config.toml";
169
+ }
170
+
171
+ function writeGeminiJson(cwd) {
172
+ const dir = path.join(cwd, ".gemini");
173
+ fs.mkdirSync(dir, { recursive: true });
174
+ const filePath = path.join(dir, "settings.json");
175
+ const existing = readJsonFile(filePath) || {};
176
+ deepMerge(existing, { mcpServers: { memento: MCP_SERVER_ENTRY } });
177
+ writeJsonFile(filePath, existing);
178
+ return ".gemini/settings.json";
179
+ }
180
+
181
+ function writeOpencodeJson(cwd) {
182
+ const filePath = path.join(cwd, "opencode.json");
183
+ const existing = readJsonFile(filePath) || {};
184
+ deepMerge(existing, {
185
+ mcp: {
186
+ memento: {
187
+ type: "local",
188
+ command: ["npx", "-y", "memento-mcp"],
189
+ enabled: true,
190
+ },
191
+ },
192
+ });
193
+ writeJsonFile(filePath, existing);
194
+ return "opencode.json";
195
+ }
196
+
197
+ const AGENTS = {
198
+ "claude-code": {
199
+ name: "Claude Code",
200
+ detect: (cwd) => fs.existsSync(path.join(cwd, ".claude")),
201
+ configWriter: writeMcpJson,
202
+ hasHooks: true,
203
+ nextSteps: "Restart Claude Code to activate.",
204
+ },
205
+ codex: {
206
+ name: "OpenAI Codex",
207
+ detect: (cwd) => fs.existsSync(path.join(cwd, ".codex")),
208
+ configWriter: writeCodexToml,
209
+ hasHooks: false,
210
+ nextSteps: "Run `codex` in this directory — memento tools load automatically.",
211
+ },
212
+ gemini: {
213
+ name: "Gemini CLI",
214
+ detect: (cwd) => fs.existsSync(path.join(cwd, ".gemini")),
215
+ configWriter: writeGeminiJson,
216
+ hasHooks: false,
217
+ nextSteps: "Run `gemini` in this directory — memento tools load automatically.",
218
+ },
219
+ opencode: {
220
+ name: "OpenCode",
221
+ detect: (cwd) => fs.existsSync(path.join(cwd, "opencode.json")),
222
+ configWriter: writeOpencodeJson,
223
+ hasHooks: false,
224
+ nextSteps: "Run `opencode` in this directory — memento tools load automatically.",
225
+ },
226
+ };
227
+
228
+ // Exported for testing
229
+ export { AGENTS, writeMcpJson, writeCodexToml, writeGeminiJson, writeOpencodeJson };
230
+
130
231
  // ---------------------------------------------------------------------------
131
232
  // CLI
132
233
  // ---------------------------------------------------------------------------
@@ -193,26 +294,76 @@ async function runInit() {
193
294
  false,
194
295
  );
195
296
 
196
- // 4. Hooks
197
- console.log("\nClaude Code hooks (automate recall + distillation):");
198
- const enableUserPrompt = await askYesNo(
199
- rl,
200
- " UserPromptSubmit — recall on every message?",
201
- true
202
- );
203
- const enableStop = await askYesNo(rl, " Stop — autonomous recall after responses?", true);
204
- const enablePreCompact = await askYesNo(
205
- rl,
206
- " PreCompact — distill memories before context compression?",
207
- true
208
- );
297
+ // 4. Agent detection + selection
298
+ const agentKeys = Object.keys(AGENTS);
299
+ const detected = agentKeys.filter((key) => AGENTS[key].detect(cwd));
300
+
301
+ console.log("\nDetected agents:");
302
+ const markers = {
303
+ "claude-code": ".claude/",
304
+ codex: ".codex/",
305
+ gemini: ".gemini/",
306
+ opencode: "opencode.json",
307
+ };
308
+ for (const key of agentKeys) {
309
+ const agent = AGENTS[key];
310
+ const isDetected = detected.includes(key);
311
+ const mark = isDetected ? "✓" : " ";
312
+ const hint = isDetected ? ` (${markers[key]} found)` : "";
313
+ console.log(` ${mark} ${agent.name}${hint}`);
314
+ }
315
+
316
+ console.log("\n Configure for which agents?");
317
+ agentKeys.forEach((key, i) => {
318
+ const mark = detected.includes(key) ? " ✓" : "";
319
+ console.log(` ${i + 1}. ${AGENTS[key].name}${mark}`);
320
+ });
321
+
322
+ const defaultSelection =
323
+ detected.length > 0
324
+ ? detected.map((key) => agentKeys.indexOf(key) + 1).join(",")
325
+ : "1";
326
+ const selectionStr = await ask(rl, "\n Enter numbers, comma-separated", defaultSelection);
327
+
328
+ const selectedIndices = selectionStr
329
+ .split(",")
330
+ .map((s) => parseInt(s.trim(), 10))
331
+ .filter((n) => n >= 1 && n <= agentKeys.length);
332
+ const selectedAgents = [...new Set(selectedIndices.map((i) => agentKeys[i - 1]))];
333
+
334
+ if (selectedAgents.length === 0) {
335
+ console.log(" No agents selected. Defaulting to Claude Code.");
336
+ selectedAgents.push("claude-code");
337
+ }
338
+
339
+ const hasClaude = selectedAgents.includes("claude-code");
340
+
341
+ // 5. Hooks — only if Claude Code selected
342
+ let enableUserPrompt = false;
343
+ let enableStop = false;
344
+ let enablePreCompact = false;
209
345
  let enableSessionStart = false;
210
- if (enableIdentity) {
211
- enableSessionStart = await askYesNo(
346
+
347
+ if (hasClaude) {
348
+ console.log("\nClaude Code hooks (automate recall + distillation):");
349
+ enableUserPrompt = await askYesNo(
350
+ rl,
351
+ " UserPromptSubmit — recall on every message?",
352
+ true,
353
+ );
354
+ enableStop = await askYesNo(rl, " Stop — autonomous recall after responses?", true);
355
+ enablePreCompact = await askYesNo(
212
356
  rl,
213
- " SessionStartinject identity + active items at startup?",
214
- true
357
+ " PreCompactdistill memories before context compression?",
358
+ true,
215
359
  );
360
+ if (enableIdentity) {
361
+ enableSessionStart = await askYesNo(
362
+ rl,
363
+ " SessionStart — inject identity + active items at startup?",
364
+ true,
365
+ );
366
+ }
216
367
  }
217
368
 
218
369
  rl.close();
@@ -221,6 +372,7 @@ async function runInit() {
221
372
  const config = {
222
373
  apiKey,
223
374
  workspace,
375
+ agents: selectedAgents,
224
376
  features: {
225
377
  images: enableImages,
226
378
  identity: enableIdentity,
@@ -235,21 +387,23 @@ async function runInit() {
235
387
 
236
388
  const created = [];
237
389
 
238
- // 5. Write .memento.json
390
+ // 6. Write .memento.json
239
391
  const configPath = path.join(cwd, ".memento.json");
240
392
  writeJsonFile(configPath, config);
241
393
  created.push(".memento.json");
242
394
 
243
- // 6. Copy hook scripts into .memento/scripts/ for stable paths
244
- // (pointing into the npx cache would break on cache clear or update)
245
- const anyHookEnabled = enableUserPrompt || enableStop || enablePreCompact || enableSessionStart;
246
- if (anyHookEnabled) {
395
+ // 7. Copy hook scripts + write Claude Code settings — gated on hasClaude
396
+ const anyHookEnabled =
397
+ enableUserPrompt || enableStop || enablePreCompact || enableSessionStart;
398
+
399
+ if (hasClaude && anyHookEnabled) {
247
400
  const pkgScriptsDir = path.resolve(__dirname, "..", "scripts");
248
401
  const localScriptsDir = path.join(cwd, ".memento", "scripts");
249
402
  if (!fs.existsSync(localScriptsDir))
250
403
  fs.mkdirSync(localScriptsDir, { recursive: true });
251
404
 
252
405
  const scriptFiles = [
406
+ "hook-toast.sh",
253
407
  enableUserPrompt && "memento-userprompt-recall.sh",
254
408
  enableStop && "memento-stop-recall.sh",
255
409
  enablePreCompact && "memento-precompact-distill.sh",
@@ -264,7 +418,7 @@ async function runInit() {
264
418
  }
265
419
  created.push(".memento/scripts/");
266
420
 
267
- // 7. Write .claude/settings.local.json (hooks)
421
+ // 8. Write .claude/settings.local.json (hooks)
268
422
  const hooks = {};
269
423
  if (enableUserPrompt) {
270
424
  hooks.UserPromptSubmit = [
@@ -305,14 +459,16 @@ async function runInit() {
305
459
  },
306
460
  ];
307
461
  }
308
-
309
462
  if (enableSessionStart) {
310
463
  hooks.SessionStart = [
311
464
  {
312
465
  hooks: [
313
466
  {
314
467
  type: "command",
315
- command: path.join(localScriptsDir, "memento-sessionstart-identity.sh"),
468
+ command: path.join(
469
+ localScriptsDir,
470
+ "memento-sessionstart-identity.sh",
471
+ ),
316
472
  timeout: 10000,
317
473
  },
318
474
  ],
@@ -325,30 +481,29 @@ async function runInit() {
325
481
  created.push(".claude/settings.local.json");
326
482
  }
327
483
 
328
- // 8. Write .mcp.json
329
- const mcpPath = path.join(cwd, ".mcp.json");
330
- mergeJsonFile(mcpPath, {
331
- mcpServers: {
332
- memento: {
333
- command: "npx",
334
- args: ["-y", "memento-mcp"],
335
- },
336
- },
337
- });
338
- created.push(".mcp.json");
484
+ // 9. Per-agent config files
485
+ for (const agentKey of selectedAgents) {
486
+ const agent = AGENTS[agentKey];
487
+ const result = agent.configWriter(cwd);
488
+ created.push(result);
489
+ }
339
490
 
340
- // 9. Add .memento.json and .memento/scripts/ to .gitignore
491
+ // 10. Add .memento.json and .memento/scripts/ to .gitignore
341
492
  let gitignoreUpdated = false;
342
493
  if (appendToGitignore(cwd, ".memento.json")) gitignoreUpdated = true;
343
494
  if (appendToGitignore(cwd, ".memento/scripts/")) gitignoreUpdated = true;
344
495
  if (gitignoreUpdated) created.push(".gitignore (updated)");
345
496
 
346
- // 10. Summary
497
+ // 11. Summary
347
498
  const labels = {
348
499
  ".memento.json": "workspace config + credentials",
349
500
  ".memento/scripts/": "hook scripts (recall + distillation)",
350
501
  ".claude/settings.local.json": "hooks registered with Claude Code",
351
- ".mcp.json": "MCP server registered",
502
+ ".mcp.json": "MCP server registered (Claude Code)",
503
+ ".codex/config.toml": "MCP server registered (Codex)",
504
+ ".codex/config.toml (already configured)": "MCP server (Codex, skipped)",
505
+ ".gemini/settings.json": "MCP server registered (Gemini CLI)",
506
+ "opencode.json": "MCP server registered (OpenCode)",
352
507
  ".gitignore (updated)": "credentials excluded from git",
353
508
  };
354
509
  const colWidth = Math.max(...created.map((f) => f.length)) + 2;
@@ -358,14 +513,25 @@ async function runInit() {
358
513
  const label = labels[f] || "";
359
514
  console.log(` ${f.padEnd(colWidth)}${label}`);
360
515
  }
361
- console.log("\n Restart Claude Code to activate.");
516
+
517
+ // Per-agent next steps
518
+ console.log("\n Next steps:");
519
+ for (const agentKey of selectedAgents) {
520
+ const agent = AGENTS[agentKey];
521
+ console.log(` · ${agent.name}: ${agent.nextSteps}`);
522
+ }
362
523
  console.log(" Your agent will wake up remembering.\n");
363
524
 
364
- // 11. CLAUDE.md boilerplate
525
+ // 12. CLAUDE.md / AGENTS.md boilerplate
526
+ const hasNonClaude = selectedAgents.some((k) => k !== "claude-code");
527
+ const docTarget = hasNonClaude
528
+ ? "your CLAUDE.md, AGENTS.md, or equivalent"
529
+ : "your CLAUDE.md";
530
+
365
531
  console.log("─".repeat(60));
366
532
  console.log(`
367
- One more step: paste the following into your CLAUDE.md,
368
- or hand it to Claude and ask it to add it. This teaches
533
+ One more step: paste the following into ${docTarget},
534
+ or hand it to your agent and ask it to add it. This teaches
369
535
  your agent the memory discipline Memento expects.
370
536
 
371
537
  ── paste below this line ──────────────────────────────
@@ -392,32 +558,39 @@ before compaction. Trust the hooks. Focus on writing good memories.
392
558
  }
393
559
 
394
560
  // ---------------------------------------------------------------------------
395
- // Entrypoint
561
+ // Entrypoint — only run when this module is the entry point (not imported)
396
562
  // ---------------------------------------------------------------------------
397
563
 
398
- const args = process.argv.slice(2);
564
+ const isMain =
565
+ process.argv[1] &&
566
+ (process.argv[1].endsWith("/cli.js") || process.argv[1].endsWith("memento-mcp"));
399
567
 
400
- if (args[0] === "init") {
401
- runInit().catch((err) => {
402
- console.error(err);
403
- process.exit(1);
404
- });
405
- } else if (args.length === 0) {
406
- // No args — start the MCP server (this is what .mcp.json invokes)
407
- // Must call main() explicitly because the isMainModule guard in index.js
408
- // checks process.argv[1] which still points to cli.js, not index.js.
409
- const { main } = await import("./index.js");
410
- await main();
411
- } else {
412
- console.log(`
568
+ if (isMain) {
569
+ const args = process.argv.slice(2);
570
+
571
+ if (args[0] === "init") {
572
+ runInit().catch((err) => {
573
+ console.error(err);
574
+ process.exit(1);
575
+ });
576
+ } else if (args.length === 0) {
577
+ // No args start the MCP server (this is what .mcp.json invokes)
578
+ // Must call main() explicitly because the isMainModule guard in index.js
579
+ // checks process.argv[1] which still points to cli.js, not index.js.
580
+ const { main } = await import("./index.js");
581
+ await main();
582
+ } else {
583
+ console.log(`
413
584
  Memento Protocol CLI
414
585
 
415
586
  Usage:
416
587
  npx memento-mcp init Set up Memento in the current project
417
588
  npx memento-mcp Start the MCP server (used by .mcp.json)
418
589
 
419
- This creates .memento.json, configures Claude Code hooks,
420
- and sets up the MCP server — all in one command.
590
+ This creates .memento.json, configures your agent's MCP client,
591
+ and sets up hooks (Claude Code) — all in one command.
592
+ Supports Claude Code, Codex, Gemini CLI, and OpenCode.
421
593
  `);
422
- process.exit(1);
594
+ process.exit(1);
595
+ }
423
596
  }
package/src/config.js CHANGED
@@ -14,6 +14,7 @@ import path from "node:path";
14
14
  export const DEFAULTS = {
15
15
  apiUrl: "https://memento-api.myrakrusemark.workers.dev",
16
16
  workspace: "default",
17
+ agents: [],
17
18
  features: { images: false, identity: false },
18
19
  hooks: {
19
20
  "userprompt-recall": { enabled: true, limit: 5, maxLength: 200 },
@@ -69,5 +70,7 @@ export function resolveConfig(startDir = process.cwd()) {
69
70
  hooks[key] = { ...defaults, ...(fileConfig.hooks?.[key] || {}) };
70
71
  }
71
72
 
72
- return { apiKey, apiUrl, workspace, features, hooks };
73
+ const agents = fileConfig.agents || DEFAULTS.agents;
74
+
75
+ return { apiKey, apiUrl, workspace, agents, features, hooks };
73
76
  }