memento-mcp 0.2.4 → 0.3.0

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.4",
3
+ "version": "0.3.0",
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,162 @@
1
+ #!/bin/bash
2
+ # SessionStart hook (Memento) — inject identity crystal + active items at startup.
3
+ # JSON output: hookSpecificOutput.additionalContext with identity, active work, and skip list.
4
+ #
5
+ # Three API calls (parallel where possible):
6
+ # 1. GET /v1/identity — identity crystal
7
+ # 2. GET /v1/working-memory/items?category=active_work&status=active — current tasks
8
+ # 3. GET /v1/working-memory/items?category=skip_list&status=active — skip list
9
+
10
+ set -o pipefail
11
+
12
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
13
+
14
+ # --- Config from .memento.json (if present) ---
15
+ CONFIG_JSON=$(python3 -c "
16
+ import json, os
17
+ d = os.getcwd()
18
+ while True:
19
+ p = os.path.join(d, '.memento.json')
20
+ if os.path.isfile(p):
21
+ with open(p) as f:
22
+ print(f.read())
23
+ break
24
+ parent = os.path.dirname(d)
25
+ if parent == d:
26
+ break
27
+ d = parent
28
+ " 2>/dev/null)
29
+
30
+ if [ -n "$CONFIG_JSON" ]; then
31
+ HOOK_NAME="sessionstart-identity"
32
+ HOOK_ENABLED=$(echo "$CONFIG_JSON" | python3 -c "
33
+ import json, sys
34
+ cfg = json.load(sys.stdin)
35
+ hook = cfg.get('hooks', {}).get('$HOOK_NAME', {})
36
+ print('true' if hook.get('enabled', True) else 'false')
37
+ " 2>/dev/null)
38
+
39
+ if [ "$HOOK_ENABLED" = "false" ]; then
40
+ exit 0
41
+ fi
42
+
43
+ # Dual gate: also check features.identity
44
+ IDENTITY_ENABLED=$(echo "$CONFIG_JSON" | python3 -c "
45
+ import json, sys
46
+ cfg = json.load(sys.stdin)
47
+ print('true' if cfg.get('features', {}).get('identity', False) else 'false')
48
+ " 2>/dev/null)
49
+
50
+ if [ "$IDENTITY_ENABLED" = "false" ]; then
51
+ exit 0
52
+ fi
53
+
54
+ MEMENTO_API_KEY="${MEMENTO_API_KEY:-$(echo "$CONFIG_JSON" | python3 -c "import json,sys; print(json.load(sys.stdin).get('apiKey',''))" 2>/dev/null)}"
55
+ MEMENTO_API_URL="${MEMENTO_API_URL:-$(echo "$CONFIG_JSON" | python3 -c "import json,sys; print(json.load(sys.stdin).get('apiUrl',''))" 2>/dev/null)}"
56
+ MEMENTO_WORKSPACE="${MEMENTO_WORKSPACE:-$(echo "$CONFIG_JSON" | python3 -c "import json,sys; print(json.load(sys.stdin).get('workspace',''))" 2>/dev/null)}"
57
+ fi
58
+ # --- End config block ---
59
+
60
+ # Consume stdin (SessionStart sends JSON we don't need)
61
+ cat > /dev/null
62
+
63
+ # Source credentials from .env (gitignored) — fallback if no .memento.json
64
+ if [ -f "$SCRIPT_DIR/../.env" ]; then
65
+ set -a
66
+ source "$SCRIPT_DIR/../.env"
67
+ set +a
68
+ fi
69
+
70
+ MEMENTO_API="${MEMENTO_API_URL:-https://memento-api.myrakrusemark.workers.dev}"
71
+ MEMENTO_KEY="${MEMENTO_API_KEY:?MEMENTO_API_KEY not set — check memento-protocol/.env or .memento.json}"
72
+ MEMENTO_WS="${MEMENTO_WORKSPACE:-default}"
73
+
74
+ AUTH_HEADER="Authorization: Bearer $MEMENTO_KEY"
75
+ WS_HEADER="X-Memento-Workspace: $MEMENTO_WS"
76
+
77
+ # Temp files for parallel curl results
78
+ IDENTITY_TMP=$(mktemp)
79
+ ACTIVE_TMP=$(mktemp)
80
+ SKIP_TMP=$(mktemp)
81
+ trap 'rm -f "$IDENTITY_TMP" "$ACTIVE_TMP" "$SKIP_TMP"' EXIT
82
+
83
+ # Fetch all three endpoints in parallel
84
+ curl -s --max-time 3 -H "$AUTH_HEADER" -H "$WS_HEADER" \
85
+ "$MEMENTO_API/v1/identity" > "$IDENTITY_TMP" 2>/dev/null &
86
+ PID1=$!
87
+
88
+ curl -s --max-time 3 -H "$AUTH_HEADER" -H "$WS_HEADER" \
89
+ "$MEMENTO_API/v1/working-memory/items?category=active_work&status=active" > "$ACTIVE_TMP" 2>/dev/null &
90
+ PID2=$!
91
+
92
+ curl -s --max-time 3 -H "$AUTH_HEADER" -H "$WS_HEADER" \
93
+ "$MEMENTO_API/v1/working-memory/items?category=skip_list&status=active" > "$SKIP_TMP" 2>/dev/null &
94
+ PID3=$!
95
+
96
+ wait $PID1 $PID2 $PID3 2>/dev/null
97
+
98
+ # Build output from the three responses
99
+ python3 -c "
100
+ import json, sys
101
+
102
+ sections = []
103
+
104
+ # 1. Identity crystal — MCP envelope format: { content: [{ text: '...' }] }
105
+ try:
106
+ with open(sys.argv[1]) as f:
107
+ identity_data = json.load(f)
108
+ crystal = identity_data.get('content', [{}])[0].get('text', '')
109
+ # Skip if empty or placeholder
110
+ if crystal and 'no identity crystal' not in crystal.lower() and 'placeholder' not in crystal.lower():
111
+ sections.append('# Identity Crystal\n\n' + crystal)
112
+ except Exception:
113
+ pass
114
+
115
+ # Format items using the same pattern as memento_item_list (index.js:831-838)
116
+ def format_items(items):
117
+ lines = []
118
+ for item in items:
119
+ tags = item.get('tags', [])
120
+ tag_str = f' [{', '.join(tags)}]' if tags else ''
121
+ status = item.get('status', 'active')
122
+ status_str = f' ({status})' if status != 'active' else ''
123
+ next_action = item.get('next_action', '')
124
+ next_str = f'\n Next: {next_action}' if next_action else ''
125
+ lines.append(f'**{item[\"id\"]}** {item[\"category\"]}: {item[\"title\"]}{status_str}{tag_str}{next_str}')
126
+ return '\n\n'.join(lines)
127
+
128
+ # 2. Active work items — JSON format: { items: [...] }
129
+ try:
130
+ with open(sys.argv[2]) as f:
131
+ active_data = json.load(f)
132
+ active_items = active_data.get('items', [])
133
+ if active_items:
134
+ sections.append('## Active Work\n\n' + format_items(active_items))
135
+ except Exception:
136
+ pass
137
+
138
+ # 3. Skip list items — same JSON format
139
+ try:
140
+ with open(sys.argv[3]) as f:
141
+ skip_data = json.load(f)
142
+ skip_items = skip_data.get('items', [])
143
+ if skip_items:
144
+ sections.append('## Skip List\n\n' + format_items(skip_items))
145
+ except Exception:
146
+ pass
147
+
148
+ if not sections:
149
+ sys.exit(0)
150
+
151
+ context = '\n\n'.join(sections)
152
+ context += '\n\nREMINDER: If Memento MCP tools are not loaded, run: ToolSearch query=\"+memento\" max_results=20'
153
+
154
+ print(json.dumps({
155
+ 'hookSpecificOutput': {
156
+ 'hookEventName': 'SessionStart',
157
+ 'additionalContext': context
158
+ }
159
+ }))
160
+ " "$IDENTITY_TMP" "$ACTIVE_TMP" "$SKIP_TMP" 2>/dev/null
161
+
162
+ exit 0
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,19 +294,77 @@ 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;
345
+ let enableSessionStart = false;
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(
356
+ rl,
357
+ " PreCompact — distill memories before context compression?",
358
+ true,
359
+ );
360
+ if (enableIdentity) {
361
+ enableSessionStart = await askYesNo(
362
+ rl,
363
+ " SessionStart — inject identity + active items at startup?",
364
+ true,
365
+ );
366
+ }
367
+ }
209
368
 
210
369
  rl.close();
211
370
 
@@ -213,6 +372,7 @@ async function runInit() {
213
372
  const config = {
214
373
  apiKey,
215
374
  workspace,
375
+ agents: selectedAgents,
216
376
  features: {
217
377
  images: enableImages,
218
378
  identity: enableIdentity,
@@ -221,20 +381,22 @@ async function runInit() {
221
381
  "userprompt-recall": { enabled: enableUserPrompt },
222
382
  "stop-recall": { enabled: enableStop },
223
383
  "precompact-distill": { enabled: enablePreCompact },
384
+ "sessionstart-identity": { enabled: enableSessionStart },
224
385
  },
225
386
  };
226
387
 
227
388
  const created = [];
228
389
 
229
- // 5. Write .memento.json
390
+ // 6. Write .memento.json
230
391
  const configPath = path.join(cwd, ".memento.json");
231
392
  writeJsonFile(configPath, config);
232
393
  created.push(".memento.json");
233
394
 
234
- // 6. Copy hook scripts into .memento/scripts/ for stable paths
235
- // (pointing into the npx cache would break on cache clear or update)
236
- const anyHookEnabled = enableUserPrompt || enableStop || enablePreCompact;
237
- 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) {
238
400
  const pkgScriptsDir = path.resolve(__dirname, "..", "scripts");
239
401
  const localScriptsDir = path.join(cwd, ".memento", "scripts");
240
402
  if (!fs.existsSync(localScriptsDir))
@@ -244,6 +406,7 @@ async function runInit() {
244
406
  enableUserPrompt && "memento-userprompt-recall.sh",
245
407
  enableStop && "memento-stop-recall.sh",
246
408
  enablePreCompact && "memento-precompact-distill.sh",
409
+ enableSessionStart && "memento-sessionstart-identity.sh",
247
410
  ].filter(Boolean);
248
411
 
249
412
  for (const name of scriptFiles) {
@@ -254,7 +417,7 @@ async function runInit() {
254
417
  }
255
418
  created.push(".memento/scripts/");
256
419
 
257
- // 7. Write .claude/settings.local.json (hooks)
420
+ // 8. Write .claude/settings.local.json (hooks)
258
421
  const hooks = {};
259
422
  if (enableUserPrompt) {
260
423
  hooks.UserPromptSubmit = [
@@ -295,36 +458,51 @@ async function runInit() {
295
458
  },
296
459
  ];
297
460
  }
461
+ if (enableSessionStart) {
462
+ hooks.SessionStart = [
463
+ {
464
+ hooks: [
465
+ {
466
+ type: "command",
467
+ command: path.join(
468
+ localScriptsDir,
469
+ "memento-sessionstart-identity.sh",
470
+ ),
471
+ timeout: 10000,
472
+ },
473
+ ],
474
+ },
475
+ ];
476
+ }
298
477
 
299
478
  const settingsPath = path.join(cwd, ".claude", "settings.local.json");
300
479
  mergeJsonFile(settingsPath, { hooks });
301
480
  created.push(".claude/settings.local.json");
302
481
  }
303
482
 
304
- // 8. Write .mcp.json
305
- const mcpPath = path.join(cwd, ".mcp.json");
306
- mergeJsonFile(mcpPath, {
307
- mcpServers: {
308
- memento: {
309
- command: "npx",
310
- args: ["-y", "memento-mcp"],
311
- },
312
- },
313
- });
314
- created.push(".mcp.json");
483
+ // 9. Per-agent config files
484
+ for (const agentKey of selectedAgents) {
485
+ const agent = AGENTS[agentKey];
486
+ const result = agent.configWriter(cwd);
487
+ created.push(result);
488
+ }
315
489
 
316
- // 9. Add .memento.json and .memento/scripts/ to .gitignore
490
+ // 10. Add .memento.json and .memento/scripts/ to .gitignore
317
491
  let gitignoreUpdated = false;
318
492
  if (appendToGitignore(cwd, ".memento.json")) gitignoreUpdated = true;
319
493
  if (appendToGitignore(cwd, ".memento/scripts/")) gitignoreUpdated = true;
320
494
  if (gitignoreUpdated) created.push(".gitignore (updated)");
321
495
 
322
- // 10. Summary
496
+ // 11. Summary
323
497
  const labels = {
324
498
  ".memento.json": "workspace config + credentials",
325
499
  ".memento/scripts/": "hook scripts (recall + distillation)",
326
500
  ".claude/settings.local.json": "hooks registered with Claude Code",
327
- ".mcp.json": "MCP server registered",
501
+ ".mcp.json": "MCP server registered (Claude Code)",
502
+ ".codex/config.toml": "MCP server registered (Codex)",
503
+ ".codex/config.toml (already configured)": "MCP server (Codex, skipped)",
504
+ ".gemini/settings.json": "MCP server registered (Gemini CLI)",
505
+ "opencode.json": "MCP server registered (OpenCode)",
328
506
  ".gitignore (updated)": "credentials excluded from git",
329
507
  };
330
508
  const colWidth = Math.max(...created.map((f) => f.length)) + 2;
@@ -334,14 +512,25 @@ async function runInit() {
334
512
  const label = labels[f] || "";
335
513
  console.log(` ${f.padEnd(colWidth)}${label}`);
336
514
  }
337
- console.log("\n Restart Claude Code to activate.");
515
+
516
+ // Per-agent next steps
517
+ console.log("\n Next steps:");
518
+ for (const agentKey of selectedAgents) {
519
+ const agent = AGENTS[agentKey];
520
+ console.log(` · ${agent.name}: ${agent.nextSteps}`);
521
+ }
338
522
  console.log(" Your agent will wake up remembering.\n");
339
523
 
340
- // 11. CLAUDE.md boilerplate
524
+ // 12. CLAUDE.md / AGENTS.md boilerplate
525
+ const hasNonClaude = selectedAgents.some((k) => k !== "claude-code");
526
+ const docTarget = hasNonClaude
527
+ ? "your CLAUDE.md, AGENTS.md, or equivalent"
528
+ : "your CLAUDE.md";
529
+
341
530
  console.log("─".repeat(60));
342
531
  console.log(`
343
- One more step: paste the following into your CLAUDE.md,
344
- or hand it to Claude and ask it to add it. This teaches
532
+ One more step: paste the following into ${docTarget},
533
+ or hand it to your agent and ask it to add it. This teaches
345
534
  your agent the memory discipline Memento expects.
346
535
 
347
536
  ── paste below this line ──────────────────────────────
@@ -368,32 +557,39 @@ before compaction. Trust the hooks. Focus on writing good memories.
368
557
  }
369
558
 
370
559
  // ---------------------------------------------------------------------------
371
- // Entrypoint
560
+ // Entrypoint — only run when this module is the entry point (not imported)
372
561
  // ---------------------------------------------------------------------------
373
562
 
374
- const args = process.argv.slice(2);
563
+ const isMain =
564
+ process.argv[1] &&
565
+ (process.argv[1].endsWith("/cli.js") || process.argv[1].endsWith("memento-mcp"));
375
566
 
376
- if (args[0] === "init") {
377
- runInit().catch((err) => {
378
- console.error(err);
379
- process.exit(1);
380
- });
381
- } else if (args.length === 0) {
382
- // No args — start the MCP server (this is what .mcp.json invokes)
383
- // Must call main() explicitly because the isMainModule guard in index.js
384
- // checks process.argv[1] which still points to cli.js, not index.js.
385
- const { main } = await import("./index.js");
386
- await main();
387
- } else {
388
- console.log(`
567
+ if (isMain) {
568
+ const args = process.argv.slice(2);
569
+
570
+ if (args[0] === "init") {
571
+ runInit().catch((err) => {
572
+ console.error(err);
573
+ process.exit(1);
574
+ });
575
+ } else if (args.length === 0) {
576
+ // No args start the MCP server (this is what .mcp.json invokes)
577
+ // Must call main() explicitly because the isMainModule guard in index.js
578
+ // checks process.argv[1] which still points to cli.js, not index.js.
579
+ const { main } = await import("./index.js");
580
+ await main();
581
+ } else {
582
+ console.log(`
389
583
  Memento Protocol CLI
390
584
 
391
585
  Usage:
392
586
  npx memento-mcp init Set up Memento in the current project
393
587
  npx memento-mcp Start the MCP server (used by .mcp.json)
394
588
 
395
- This creates .memento.json, configures Claude Code hooks,
396
- and sets up the MCP server — all in one command.
589
+ This creates .memento.json, configures your agent's MCP client,
590
+ and sets up hooks (Claude Code) — all in one command.
591
+ Supports Claude Code, Codex, Gemini CLI, and OpenCode.
397
592
  `);
398
- process.exit(1);
593
+ process.exit(1);
594
+ }
399
595
  }
package/src/config.js CHANGED
@@ -14,11 +14,13 @@ 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 },
20
21
  "stop-recall": { enabled: true, limit: 5, maxLength: 200 },
21
22
  "precompact-distill": { enabled: true },
23
+ "sessionstart-identity": { enabled: true },
22
24
  },
23
25
  };
24
26
 
@@ -68,5 +70,7 @@ export function resolveConfig(startDir = process.cwd()) {
68
70
  hooks[key] = { ...defaults, ...(fileConfig.hooks?.[key] || {}) };
69
71
  }
70
72
 
71
- return { apiKey, apiUrl, workspace, features, hooks };
73
+ const agents = fileConfig.agents || DEFAULTS.agents;
74
+
75
+ return { apiKey, apiUrl, workspace, agents, features, hooks };
72
76
  }