memento-mcp 0.2.4 → 0.2.5
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/package.json +1 -1
- package/scripts/memento-sessionstart-identity.sh +162 -0
- package/src/cli.js +25 -1
- package/src/config.js +1 -0
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
|
@@ -206,6 +206,14 @@ async function runInit() {
|
|
|
206
206
|
" PreCompact — distill memories before context compression?",
|
|
207
207
|
true
|
|
208
208
|
);
|
|
209
|
+
let enableSessionStart = false;
|
|
210
|
+
if (enableIdentity) {
|
|
211
|
+
enableSessionStart = await askYesNo(
|
|
212
|
+
rl,
|
|
213
|
+
" SessionStart — inject identity + active items at startup?",
|
|
214
|
+
true
|
|
215
|
+
);
|
|
216
|
+
}
|
|
209
217
|
|
|
210
218
|
rl.close();
|
|
211
219
|
|
|
@@ -221,6 +229,7 @@ async function runInit() {
|
|
|
221
229
|
"userprompt-recall": { enabled: enableUserPrompt },
|
|
222
230
|
"stop-recall": { enabled: enableStop },
|
|
223
231
|
"precompact-distill": { enabled: enablePreCompact },
|
|
232
|
+
"sessionstart-identity": { enabled: enableSessionStart },
|
|
224
233
|
},
|
|
225
234
|
};
|
|
226
235
|
|
|
@@ -233,7 +242,7 @@ async function runInit() {
|
|
|
233
242
|
|
|
234
243
|
// 6. Copy hook scripts into .memento/scripts/ for stable paths
|
|
235
244
|
// (pointing into the npx cache would break on cache clear or update)
|
|
236
|
-
const anyHookEnabled = enableUserPrompt || enableStop || enablePreCompact;
|
|
245
|
+
const anyHookEnabled = enableUserPrompt || enableStop || enablePreCompact || enableSessionStart;
|
|
237
246
|
if (anyHookEnabled) {
|
|
238
247
|
const pkgScriptsDir = path.resolve(__dirname, "..", "scripts");
|
|
239
248
|
const localScriptsDir = path.join(cwd, ".memento", "scripts");
|
|
@@ -244,6 +253,7 @@ async function runInit() {
|
|
|
244
253
|
enableUserPrompt && "memento-userprompt-recall.sh",
|
|
245
254
|
enableStop && "memento-stop-recall.sh",
|
|
246
255
|
enablePreCompact && "memento-precompact-distill.sh",
|
|
256
|
+
enableSessionStart && "memento-sessionstart-identity.sh",
|
|
247
257
|
].filter(Boolean);
|
|
248
258
|
|
|
249
259
|
for (const name of scriptFiles) {
|
|
@@ -296,6 +306,20 @@ async function runInit() {
|
|
|
296
306
|
];
|
|
297
307
|
}
|
|
298
308
|
|
|
309
|
+
if (enableSessionStart) {
|
|
310
|
+
hooks.SessionStart = [
|
|
311
|
+
{
|
|
312
|
+
hooks: [
|
|
313
|
+
{
|
|
314
|
+
type: "command",
|
|
315
|
+
command: path.join(localScriptsDir, "memento-sessionstart-identity.sh"),
|
|
316
|
+
timeout: 10000,
|
|
317
|
+
},
|
|
318
|
+
],
|
|
319
|
+
},
|
|
320
|
+
];
|
|
321
|
+
}
|
|
322
|
+
|
|
299
323
|
const settingsPath = path.join(cwd, ".claude", "settings.local.json");
|
|
300
324
|
mergeJsonFile(settingsPath, { hooks });
|
|
301
325
|
created.push(".claude/settings.local.json");
|
package/src/config.js
CHANGED
|
@@ -19,6 +19,7 @@ export const DEFAULTS = {
|
|
|
19
19
|
"userprompt-recall": { enabled: true, limit: 5, maxLength: 200 },
|
|
20
20
|
"stop-recall": { enabled: true, limit: 5, maxLength: 200 },
|
|
21
21
|
"precompact-distill": { enabled: true },
|
|
22
|
+
"sessionstart-identity": { enabled: true },
|
|
22
23
|
},
|
|
23
24
|
};
|
|
24
25
|
|