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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memento-mcp",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
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
@@ -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