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 +38 -12
- package/package.json +1 -1
- package/scripts/memento-sessionstart-identity.sh +162 -0
- package/src/cli.js +259 -63
- package/src/config.js +5 -1
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`,
|
|
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
|
|
46
|
+
**Claude Code** — `.mcp.json` in your project root (or `~/.claude.json` globally):
|
|
47
47
|
|
|
48
|
-
|
|
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
|
-
**
|
|
67
|
+
**Gemini CLI** — `.gemini/settings.json`:
|
|
51
68
|
|
|
52
69
|
```json
|
|
53
70
|
{
|
|
54
71
|
"mcpServers": {
|
|
55
72
|
"memento": {
|
|
56
|
-
"command": "
|
|
57
|
-
"args": ["
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
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
|
@@ -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
|
-
|
|
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.
|
|
197
|
-
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
305
|
-
const
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
|
344
|
-
or hand it to
|
|
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
|
|
563
|
+
const isMain =
|
|
564
|
+
process.argv[1] &&
|
|
565
|
+
(process.argv[1].endsWith("/cli.js") || process.argv[1].endsWith("memento-mcp"));
|
|
375
566
|
|
|
376
|
-
if (
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
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
|
|
396
|
-
and sets up
|
|
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
|
-
|
|
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
|
-
|
|
73
|
+
const agents = fileConfig.agents || DEFAULTS.agents;
|
|
74
|
+
|
|
75
|
+
return { apiKey, apiUrl, workspace, agents, features, hooks };
|
|
72
76
|
}
|