memento-mcp 0.3.2 → 0.3.3

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
@@ -150,7 +150,7 @@ not just at session boundaries.
150
150
 
151
151
  Installing Memento gives your agent memory. *The Protocol* is the system you build around it — orientation after context loss, automatic recall, writing discipline, distillation before context resets, identity that persists across sessions.
152
152
 
153
- Full guide: **[The Protocol](https://hifathom.com/projects/memento/protocol)** on hifathom.com.
153
+ Full guide: **[The Protocol](https://hifathom.com/memento/docs/protocol)** on hifathom.com.
154
154
 
155
155
  ## Hooks
156
156
 
@@ -168,19 +168,19 @@ See **[scripts/README.md](scripts/README.md)** for setup, configuration, and how
168
168
 
169
169
  ## Dashboard
170
170
 
171
- Browse and manage memories visually at [hifathom.com/dashboard](https://hifathom.com/dashboard). Paste your API key and workspace name to connect.
171
+ Browse and manage memories visually at [hifathom.com/memento/dashboard](https://hifathom.com/memento/dashboard). Paste your API key and workspace name to connect.
172
172
 
173
173
  ---
174
174
 
175
175
  ## Documentation
176
176
 
177
- Full reference docs at [hifathom.com/projects/memento](https://hifathom.com/projects/memento):
177
+ Full reference docs at [hifathom.com/memento](https://hifathom.com/memento):
178
178
 
179
- - **[Quick Start](https://hifathom.com/projects/memento/quick-start)** — 5-minute setup guide
180
- - **[The Protocol](https://hifathom.com/projects/memento/protocol)** — orientation, recall hooks, writing discipline, distillation, identity
181
- - **[Core Concepts](https://hifathom.com/projects/memento/concepts)** — memories, working memory, skip lists, identity crystals
182
- - **[MCP Tools](https://hifathom.com/projects/memento/mcp-tools)** — full tool reference with parameters and examples
183
- - **[API Reference](https://hifathom.com/projects/memento/api)** — REST endpoints, request/response schemas, authentication
179
+ - **[Quick Start](https://hifathom.com/memento/docs/quick-start)** — 5-minute setup guide
180
+ - **[The Protocol](https://hifathom.com/memento/docs/protocol)** — orientation, recall hooks, writing discipline, distillation, identity
181
+ - **[Core Concepts](https://hifathom.com/memento/docs/concepts)** — memories, working memory, skip lists, identity crystals
182
+ - **[MCP Tools](https://hifathom.com/memento/docs/mcp-tools)** — full tool reference with parameters and examples
183
+ - **[API Reference](https://hifathom.com/memento/docs/api)** — REST endpoints, request/response schemas, authentication
184
184
  - **[Self-Hosting](docs/self-hosting.md)** — deploy your own instance with Cloudflare Workers + Turso
185
185
 
186
186
  ---
@@ -82,5 +82,5 @@ Workers AI and Vectorize are included in the Cloudflare Workers free tier.
82
82
 
83
83
  - You manage your own Turso databases and Cloudflare account
84
84
  - You handle upgrades by pulling from the repo and redeploying
85
- - No usage dashboard at hifathom.com (you'd use Cloudflare's dashboard)
85
+ - No usage dashboard at hifathom.com/memento/dashboard (you'd use Cloudflare's dashboard)
86
86
  - Signup endpoint creates keys in your control plane database
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memento-mcp",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "mcpName": "io.github.myrakrusemark/memento-protocol",
5
5
  "description": "The Memento Protocol — persistent memory for AI agents",
6
6
  "type": "module",
@@ -99,9 +99,33 @@ PID3=$!
99
99
 
100
100
  wait $PID1 $PID2 $PID3 2>/dev/null
101
101
 
102
- # Build output from the three responses
102
+ # Version check (non-blocking, best-effort)
103
+ # Read installed version from .memento/version (written by init/update)
104
+ VERSION_FILE=""
105
+ _vd="$(pwd)"
106
+ while true; do
107
+ if [ -f "$_vd/.memento/version" ]; then
108
+ VERSION_FILE="$_vd/.memento/version"
109
+ break
110
+ fi
111
+ _vp="$(dirname "$_vd")"
112
+ [ "$_vp" = "$_vd" ] && break
113
+ _vd="$_vp"
114
+ done
115
+
116
+ export LOCAL_VERSION=""
117
+ export LATEST_VERSION=""
118
+ if [ -n "$VERSION_FILE" ]; then
119
+ LOCAL_VERSION=$(cat "$VERSION_FILE" 2>/dev/null | tr -d '[:space:]')
120
+ if [ -n "$LOCAL_VERSION" ]; then
121
+ LATEST_VERSION=$(curl -s --max-time 2 "https://registry.npmjs.org/memento-mcp/latest" 2>/dev/null \
122
+ | python3 -c "import json,sys; print(json.load(sys.stdin).get('version',''))" 2>/dev/null || echo "")
123
+ fi
124
+ fi
125
+
126
+ # Build output from the three responses + version check
103
127
  python3 -c "
104
- import json, sys
128
+ import json, sys, os
105
129
 
106
130
  sections = []
107
131
 
@@ -155,6 +179,12 @@ if not sections:
155
179
  context = '\n\n'.join(sections)
156
180
  context += '\n\nREMINDER: If Memento MCP tools are not loaded, run: ToolSearch query=\"+memento\" max_results=20'
157
181
 
182
+ # 4. Version check — append update notice if newer version available
183
+ local_ver = os.environ.get('LOCAL_VERSION', '').strip()
184
+ latest_ver = os.environ.get('LATEST_VERSION', '').strip()
185
+ if local_ver and latest_ver and local_ver != latest_ver:
186
+ context += f'\n\nMemento update available: v{local_ver} → v{latest_ver}. Run: npx memento-mcp update'
187
+
158
188
  print(json.dumps({
159
189
  'hookSpecificOutput': {
160
190
  'hookEventName': 'SessionStart',
@@ -163,7 +193,11 @@ print(json.dumps({
163
193
  }))
164
194
  " "$IDENTITY_TMP" "$ACTIVE_TMP" "$SKIP_TMP" 2>/dev/null
165
195
 
166
- # Toast: done
167
- "$TOAST" memento " Identity loaded" &>/dev/null
196
+ # Toast: done (with update notice if applicable)
197
+ if [ -n "$LATEST_VERSION" ] && [ -n "$LOCAL_VERSION" ] && [ "$LATEST_VERSION" != "$LOCAL_VERSION" ]; then
198
+ "$TOAST" memento "⬆ Memento v${LATEST_VERSION} available" &>/dev/null
199
+ else
200
+ "$TOAST" memento "✓ Identity loaded" &>/dev/null
201
+ fi
168
202
 
169
203
  exit 0
@@ -78,13 +78,13 @@ QUERY="${ASSISTANT_MSG:0:500}"
78
78
  # Toast: start retrieving
79
79
  "$TOAST" memento "⏳ Autonomous recall..." &>/dev/null
80
80
 
81
- # Call Memento /v1/context
82
- RESULT=$(curl -s --max-time 3 \
81
+ # Call Memento /v1/context (with auto_extract for passive memory formation)
82
+ RESULT=$(curl -s --max-time 8 \
83
83
  -X POST \
84
84
  -H "Authorization: Bearer $MEMENTO_KEY" \
85
85
  -H "X-Memento-Workspace: $MEMENTO_WS" \
86
86
  -H "Content-Type: application/json" \
87
- -d "{\"message\": $(echo "$QUERY" | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read()))'), \"include\": [\"memories\", \"skip_list\"]}" \
87
+ -d "{\"message\": $(echo "$QUERY" | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read()))'), \"include\": [\"memories\", \"skip_list\"], \"auto_extract\": true, \"extract_role\": \"assistant\", \"extract_content\": $(echo "$ASSISTANT_MSG" | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read()))')}" \
88
88
  "$MEMENTO_API/v1/context" 2>/dev/null \
89
89
  | python3 -c "
90
90
  import json, sys
@@ -111,35 +111,60 @@ try:
111
111
  for s in skip_matches:
112
112
  lines.append(f' ⚠ SKIP: {s[\"item\"]} — {s[\"reason\"]} (expires: {s[\"expires\"]})')
113
113
 
114
+ # Auto-extracted memories
115
+ extracted = data.get('extracted', [])
116
+ if extracted:
117
+ lines.append('')
118
+ lines.append('MEMORIES STORED:')
119
+ for e in extracted:
120
+ etags = [t for t in e.get('tags', []) if t != 'source:auto-extract']
121
+ etag_str = f' [{\", \".join(etags)}]' if etags else ''
122
+ lines.append(f' {e[\"id\"]} ({e.get(\"type\", \"observation\")}){etag_str} — {e[\"content\"]}')
123
+
124
+ # Output: recall_count \t extracted_count \t detail
125
+ extracted_count = len(extracted)
114
126
  detail = '\n'.join(lines)
115
- print(f'{count}\t{detail}')
127
+ print(f'{count}\t{extracted_count}\t{detail}')
116
128
  except Exception:
117
- print('0\t')
129
+ print('0\t0\t')
118
130
  " 2>/dev/null)
119
131
 
120
- # Parse
132
+ # Parse count, extracted count, and detail
121
133
  SAAS_COUNT=$(echo "$RESULT" | head -1 | cut -f1)
122
- SAAS_DETAIL=$(echo "$RESULT" | head -1 | cut -f2-)
134
+ EXTRACTED_COUNT=$(echo "$RESULT" | head -1 | cut -f2)
135
+ SAAS_DETAIL=$(echo "$RESULT" | head -1 | cut -f3-)
123
136
  REMAINING=$(echo "$RESULT" | tail -n +2)
124
137
  if [ -n "$REMAINING" ]; then
125
138
  SAAS_DETAIL="$SAAS_DETAIL"$'\n'"$REMAINING"
126
139
  fi
127
140
 
128
141
  if [ -z "$SAAS_COUNT" ] || [ "$SAAS_COUNT" = "0" ]; then
129
- "$TOAST" memento " No memories matched" &>/dev/null
130
- exit 0
142
+ if [ -n "$EXTRACTED_COUNT" ] && [ "$EXTRACTED_COUNT" != "0" ]; then
143
+ "$TOAST" memento "✓ ${EXTRACTED_COUNT} memories stored" &>/dev/null
144
+ else
145
+ "$TOAST" memento "✓ No memories matched" &>/dev/null
146
+ exit 0
147
+ fi
148
+ else
149
+ if [ -n "$EXTRACTED_COUNT" ] && [ "$EXTRACTED_COUNT" != "0" ]; then
150
+ "$TOAST" memento "✓ ${SAAS_COUNT} recalled, ${EXTRACTED_COUNT} stored" &>/dev/null
151
+ else
152
+ "$TOAST" memento "✓ ${SAAS_COUNT} memories recalled" &>/dev/null
153
+ fi
131
154
  fi
132
155
 
133
- # Toast: result
134
- "$TOAST" memento "✓ ${SAAS_COUNT} memories recalled" &>/dev/null
156
+ # Build summary line
157
+ SUMMARY="Autonomous Recall: ${SAAS_COUNT} memories"
158
+ if [ -n "$EXTRACTED_COUNT" ] && [ "$EXTRACTED_COUNT" != "0" ]; then
159
+ SUMMARY="${SUMMARY}, ${EXTRACTED_COUNT} memories stored"
160
+ fi
135
161
 
136
162
  # Block the Stop so Claude continues — the reason becomes Claude's next instruction.
137
- REASON="Autonomous Recall: ${SAAS_COUNT} memories surfaced from your last response.
163
+ REASON="${SUMMARY} surfaced from your last response.
138
164
  ${SAAS_DETAIL}
139
165
 
140
166
  You have absorbed these memories into context. If any recalled memory is stale, wrong, or overlaps with others — update, delete, or consolidate it now. Otherwise continue naturally."
141
167
 
142
- SUMMARY="Autonomous Recall: ${SAAS_COUNT} memories"
143
168
  python3 -c "
144
169
  import json, sys
145
170
  print(json.dumps({
@@ -70,13 +70,13 @@ QUERY="${USER_MESSAGE:0:500}"
70
70
  # Toast: start retrieving
71
71
  "$TOAST" memento "⏳ Retrieving memories..." &>/dev/null
72
72
 
73
- # Call Memento SaaS /v1/context
74
- SAAS_OUTPUT=$(curl -s --max-time 3 \
73
+ # Call Memento SaaS /v1/context (with auto_extract for passive memory formation)
74
+ SAAS_OUTPUT=$(curl -s --max-time 8 \
75
75
  -X POST \
76
76
  -H "Authorization: Bearer $MEMENTO_KEY" \
77
77
  -H "X-Memento-Workspace: $MEMENTO_WS" \
78
78
  -H "Content-Type: application/json" \
79
- -d "{\"message\": $(echo "$QUERY" | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read()))'), \"include\": [\"memories\", \"skip_list\"]}" \
79
+ -d "{\"message\": $(echo "$QUERY" | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read()))'), \"include\": [\"memories\", \"skip_list\"], \"auto_extract\": true, \"extract_role\": \"user\", \"extract_content\": $(echo "$USER_MESSAGE" | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read()))')}" \
80
80
  "$MEMENTO_API/v1/context" 2>/dev/null \
81
81
  | python3 -c "
82
82
  import json, sys
@@ -105,16 +105,28 @@ try:
105
105
  for s in skip_matches:
106
106
  lines.append(f' ⚠ SKIP: {s[\"item\"]} — {s[\"reason\"]} (expires: {s[\"expires\"]})')
107
107
 
108
- # Output count and detail as tab-separated on first line, rest follows
108
+ # Auto-extracted memories
109
+ extracted = data.get('extracted', [])
110
+ if extracted:
111
+ lines.append('')
112
+ lines.append('MEMORIES STORED:')
113
+ for e in extracted:
114
+ etags = [t for t in e.get('tags', []) if t != 'source:auto-extract']
115
+ etag_str = f' [{\", \".join(etags)}]' if etags else ''
116
+ lines.append(f' {e[\"id\"]} ({e.get(\"type\", \"observation\")}){etag_str} — {e[\"content\"]}')
117
+
118
+ # Output: recall_count \t extracted_count \t detail
119
+ extracted_count = len(extracted)
109
120
  detail = '\n'.join(lines)
110
- print(f'{count}\t{detail}')
121
+ print(f'{count}\t{extracted_count}\t{detail}')
111
122
  except Exception:
112
- print('0\t')
123
+ print('0\t0\t')
113
124
  " 2>/dev/null)
114
125
 
115
- # Parse count and detail
126
+ # Parse count, extracted count, and detail
116
127
  SAAS_COUNT=$(echo "$SAAS_OUTPUT" | head -1 | cut -f1)
117
- SAAS_DETAIL=$(echo "$SAAS_OUTPUT" | head -1 | cut -f2-)
128
+ EXTRACTED_COUNT=$(echo "$SAAS_OUTPUT" | head -1 | cut -f2)
129
+ SAAS_DETAIL=$(echo "$SAAS_OUTPUT" | head -1 | cut -f3-)
118
130
  # Append any remaining lines (skip warnings etc.)
119
131
  REMAINING=$(echo "$SAAS_OUTPUT" | tail -n +2)
120
132
  if [ -n "$REMAINING" ]; then
@@ -122,18 +134,30 @@ if [ -n "$REMAINING" ]; then
122
134
  fi
123
135
 
124
136
  if [ -z "$SAAS_COUNT" ] || [ "$SAAS_COUNT" = "0" ]; then
125
- "$TOAST" memento " No memories matched" &>/dev/null
126
- exit 0
137
+ if [ -n "$EXTRACTED_COUNT" ] && [ "$EXTRACTED_COUNT" != "0" ]; then
138
+ "$TOAST" memento "✓ ${EXTRACTED_COUNT} memories stored" &>/dev/null
139
+ else
140
+ "$TOAST" memento "✓ No memories matched" &>/dev/null
141
+ exit 0
142
+ fi
143
+ else
144
+ if [ -n "$EXTRACTED_COUNT" ] && [ "$EXTRACTED_COUNT" != "0" ]; then
145
+ "$TOAST" memento "✓ ${SAAS_COUNT} recalled, ${EXTRACTED_COUNT} stored" &>/dev/null
146
+ else
147
+ "$TOAST" memento "✓ ${SAAS_COUNT} memories recalled" &>/dev/null
148
+ fi
127
149
  fi
128
150
 
129
- # Toast: result
130
- "$TOAST" memento "✓ ${SAAS_COUNT} memories recalled" &>/dev/null
151
+ # Build summary line
152
+ SUMMARY="Memento Recall: ${SAAS_COUNT} memories"
153
+ if [ -n "$EXTRACTED_COUNT" ] && [ "$EXTRACTED_COUNT" != "0" ]; then
154
+ SUMMARY="${SUMMARY}, ${EXTRACTED_COUNT} memories stored"
155
+ fi
131
156
 
132
- DETAIL_TEXT="Memento Recall: ${SAAS_COUNT} memories"
157
+ DETAIL_TEXT="$SUMMARY"
133
158
  DETAIL_TEXT="$DETAIL_TEXT"$'\n'"$SAAS_DETAIL"
134
159
  DETAIL_TEXT="$DETAIL_TEXT"$'\n'$'\n'"REMINDER: If Memento MCP tools are not loaded, run: ToolSearch query=\"+memento\" max_results=20"
135
160
 
136
- SUMMARY="Memento Recall: ${SAAS_COUNT} memories"
137
161
  python3 -c "
138
162
  import json, sys
139
163
  summary = sys.argv[1]
package/src/cli.js CHANGED
@@ -9,6 +9,7 @@ import readline from "node:readline";
9
9
  import fs from "node:fs";
10
10
  import path from "node:path";
11
11
  import https from "node:https";
12
+ import { execFileSync } from "node:child_process";
12
13
  import { fileURLToPath } from "node:url";
13
14
  import { DEFAULTS } from "./config.js";
14
15
 
@@ -92,6 +93,91 @@ function httpsPost(url, body) {
92
93
  });
93
94
  }
94
95
 
96
+ // ---------------------------------------------------------------------------
97
+ // Instructions blob — used by headless integration and fallback print
98
+ // ---------------------------------------------------------------------------
99
+
100
+ const INSTRUCTIONS_BLOB = `## Memento Protocol
101
+
102
+ Working memory is managed by Memento. MCP tools available:
103
+ \`memento_store\`, \`memento_recall\`, \`memento_item_list\`,
104
+ \`memento_skip_add\`, \`memento_skip_check\`.
105
+
106
+ **Memory discipline — notes are instructions, not logs.**
107
+ Write: "Skip X until condition Y" — not "checked X, it was quiet."
108
+ Every memory must answer: could a future agent with zero context
109
+ read this and know exactly what to do?
110
+
111
+ Use \`memento_store\` when you learn something worth keeping.
112
+ Use \`memento_skip_add\` for things to explicitly not re-investigate.
113
+ Use \`memento_recall\` to search memories by keyword or tag.
114
+ Hooks run automatically — recall before responses, distillation
115
+ before compaction. Trust the hooks. Focus on writing good memories.`;
116
+
117
+ // ---------------------------------------------------------------------------
118
+ // Headless agent integration
119
+ // ---------------------------------------------------------------------------
120
+
121
+ const HEADLESS_CMDS = {
122
+ "claude-code": (prompt) => ["claude", "-p", prompt],
123
+ "codex": (prompt) => ["codex", "exec", prompt],
124
+ "gemini": (prompt) => ["gemini", prompt],
125
+ "opencode": (prompt) => ["opencode", "run", prompt],
126
+ };
127
+
128
+ function buildIntegrationPrompt(blob) {
129
+ return [
130
+ "The following instructions were generated by memento-mcp init for this project.",
131
+ "Add them to the file where you store persistent behavioral instructions",
132
+ "(e.g. CLAUDE.md for Claude Code). If the file exists, read it first and",
133
+ "integrate the new section without removing existing content. If a section",
134
+ "with the same heading already exists, replace it. If no instructions file",
135
+ "exists yet, create one.",
136
+ "",
137
+ "--- INSTRUCTIONS ---",
138
+ blob,
139
+ "--- END ---",
140
+ ].join("\n");
141
+ }
142
+
143
+ function runAgentHeadless(agentKey, prompt) {
144
+ const cmdBuilder = HEADLESS_CMDS[agentKey];
145
+ if (!cmdBuilder) return null;
146
+ const [cmd, ...args] = cmdBuilder(prompt);
147
+ try {
148
+ const result = execFileSync(cmd, args, {
149
+ cwd: process.cwd(),
150
+ encoding: "utf8",
151
+ stdio: ["pipe", "pipe", "inherit"],
152
+ timeout: 60000,
153
+ });
154
+ return result;
155
+ } catch {
156
+ return null;
157
+ }
158
+ }
159
+
160
+ // ---------------------------------------------------------------------------
161
+ // CLI flag parsing
162
+ // ---------------------------------------------------------------------------
163
+
164
+ function parseFlags(argv) {
165
+ const flags = { nonInteractive: false, apiKey: null };
166
+ for (let i = 0; i < argv.length; i++) {
167
+ if (argv[i] === "-y" || argv[i] === "--yes") {
168
+ flags.nonInteractive = true;
169
+ } else if (argv[i] === "--api-key" && argv[i + 1]) {
170
+ flags.apiKey = argv[i + 1];
171
+ i++;
172
+ }
173
+ }
174
+ // Also check environment variable
175
+ if (!flags.apiKey && process.env.MEMENTO_API_KEY) {
176
+ flags.apiKey = process.env.MEMENTO_API_KEY;
177
+ }
178
+ return flags;
179
+ }
180
+
95
181
  // ---------------------------------------------------------------------------
96
182
  // File writers
97
183
  // ---------------------------------------------------------------------------
@@ -232,14 +318,14 @@ export { AGENTS, writeMcpJson, writeCodexToml, writeGeminiJson, writeOpencodeJso
232
318
  // CLI
233
319
  // ---------------------------------------------------------------------------
234
320
 
235
- async function runInit() {
321
+ async function runInit(flags = {}) {
322
+ const { nonInteractive = false, apiKey: flagApiKey = null } = flags;
236
323
  const cwd = process.cwd();
237
324
  const projectName = path.basename(cwd);
238
325
 
239
- const rl = readline.createInterface({
240
- input: process.stdin,
241
- output: process.stdout,
242
- });
326
+ const rl = nonInteractive
327
+ ? null
328
+ : readline.createInterface({ input: process.stdin, output: process.stdout });
243
329
 
244
330
  console.log(`
245
331
 
@@ -251,12 +337,29 @@ async function runInit() {
251
337
  `);
252
338
 
253
339
  // 1. Workspace name
254
- const workspace = await ask(rl, "Workspace name", projectName);
340
+ const workspace = nonInteractive
341
+ ? projectName
342
+ : await ask(rl, "Workspace name", projectName);
255
343
 
256
344
  // 2. API key
257
- let apiKey = await ask(rl, "API key (leave blank to sign up)");
345
+ let apiKey = flagApiKey || "";
346
+ if (!nonInteractive) {
347
+ apiKey = await ask(rl, "API key (leave blank to sign up)");
348
+ }
258
349
  if (!apiKey) {
259
- const email = await ask(rl, "Email for account recovery (optional)");
350
+ if (nonInteractive) {
351
+ // 5-second countdown warning, then auto-signup
352
+ process.stderr.write(
353
+ "\n No API key provided — a new one will be generated.\n" +
354
+ " Press Ctrl+C to cancel.\n "
355
+ );
356
+ for (let i = 5; i > 0; i--) {
357
+ process.stderr.write(`${i}... `);
358
+ await new Promise((r) => setTimeout(r, 1000));
359
+ }
360
+ process.stderr.write("\n\n");
361
+ }
362
+ const email = nonInteractive ? "" : await ask(rl, "Email for account recovery (optional)");
260
363
  console.log("\nSigning up...");
261
364
  try {
262
365
  const body = { workspace };
@@ -267,73 +370,84 @@ async function runInit() {
267
370
  console.log(` API key: ${apiKey}`);
268
371
  } else if (resp.error) {
269
372
  console.error(` Signup failed: ${resp.error}`);
270
- rl.close();
373
+ rl?.close();
271
374
  process.exit(1);
272
375
  } else {
273
376
  console.error(" Unexpected response:", JSON.stringify(resp));
274
- rl.close();
377
+ rl?.close();
275
378
  process.exit(1);
276
379
  }
277
380
  } catch (err) {
278
381
  console.error(` Signup failed: ${err.message}`);
279
- rl.close();
382
+ rl?.close();
280
383
  process.exit(1);
281
384
  }
282
385
  }
283
386
 
284
387
  // 3. Features
285
- console.log("\nOptional features:");
286
- const enableImages = await askYesNo(
287
- rl,
288
- " Enable image attachments? (attach images to memories via memento_store)",
289
- false,
290
- );
291
- const enableIdentity = await askYesNo(
292
- rl,
293
- " Enable identity crystal? (persist a first-person identity snapshot across sessions)",
294
- false,
295
- );
388
+ let enableImages = false;
389
+ let enableIdentity = false;
390
+ if (!nonInteractive) {
391
+ console.log("\nOptional features:");
392
+ enableImages = await askYesNo(
393
+ rl,
394
+ " Enable image attachments? (attach images to memories via memento_store)",
395
+ false,
396
+ );
397
+ enableIdentity = await askYesNo(
398
+ rl,
399
+ " Enable identity crystal? (persist a first-person identity snapshot across sessions)",
400
+ false,
401
+ );
402
+ }
296
403
 
297
404
  // 4. Agent detection + selection
298
405
  const agentKeys = Object.keys(AGENTS);
299
406
  const detected = agentKeys.filter((key) => AGENTS[key].detect(cwd));
300
407
 
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
- }
408
+ let selectedAgents;
409
+ if (nonInteractive) {
410
+ // Auto-detect: use first detected agent, or default to claude-code
411
+ selectedAgents = detected.length > 0 ? [detected[0]] : ["claude-code"];
412
+ console.log(` Agent: ${AGENTS[selectedAgents[0]].name} (auto-detected)`);
413
+ } else {
414
+ console.log("\nDetected agents:");
415
+ const markers = {
416
+ "claude-code": ".claude/",
417
+ codex: ".codex/",
418
+ gemini: ".gemini/",
419
+ opencode: "opencode.json",
420
+ };
421
+ for (const key of agentKeys) {
422
+ const agent = AGENTS[key];
423
+ const isDetected = detected.includes(key);
424
+ const mark = isDetected ? "✓" : " ";
425
+ const hint = isDetected ? ` (${markers[key]} found)` : "";
426
+ console.log(` ${mark} ${agent.name}${hint}`);
427
+ }
315
428
 
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
- });
429
+ console.log("\n Configure for which agents?");
430
+ agentKeys.forEach((key, i) => {
431
+ const mark = detected.includes(key) ? " ✓" : "";
432
+ console.log(` ${i + 1}. ${AGENTS[key].name}${mark}`);
433
+ });
321
434
 
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");
435
+ const defaultSelection =
436
+ detected.length > 0
437
+ ? detected.map((key) => agentKeys.indexOf(key) + 1).join(",")
438
+ : "1";
439
+ const selectionStr = await ask(rl, "\n Enter numbers, comma-separated", defaultSelection);
440
+
441
+ const selectedIndices = selectionStr
442
+ .split(",")
443
+ .map((s) => parseInt(s.trim(), 10))
444
+ .filter((n) => n >= 1 && n <= agentKeys.length);
445
+ selectedAgents = [...new Set(selectedIndices.map((i) => agentKeys[i - 1]))];
446
+
447
+ if (selectedAgents.length === 0) {
448
+ console.log(" No agents selected. Defaulting to Claude Code.");
449
+ selectedAgents.push("claude-code");
450
+ }
337
451
  }
338
452
 
339
453
  const hasClaude = selectedAgents.includes("claude-code");
@@ -345,28 +459,36 @@ async function runInit() {
345
459
  let enableSessionStart = false;
346
460
 
347
461
  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(
462
+ if (nonInteractive) {
463
+ // All hooks on by default in non-interactive mode
464
+ enableUserPrompt = true;
465
+ enableStop = true;
466
+ enablePreCompact = true;
467
+ enableSessionStart = false; // identity not enabled in -y mode
468
+ } else {
469
+ console.log("\nClaude Code hooks (automate recall + distillation):");
470
+ enableUserPrompt = await askYesNo(
362
471
  rl,
363
- " SessionStartinject identity + active items at startup?",
472
+ " UserPromptSubmitrecall on every message?",
364
473
  true,
365
474
  );
475
+ enableStop = await askYesNo(rl, " Stop — autonomous recall after responses?", true);
476
+ enablePreCompact = await askYesNo(
477
+ rl,
478
+ " PreCompact — distill memories before context compression?",
479
+ true,
480
+ );
481
+ if (enableIdentity) {
482
+ enableSessionStart = await askYesNo(
483
+ rl,
484
+ " SessionStart — inject identity + active items at startup?",
485
+ true,
486
+ );
487
+ }
366
488
  }
367
489
  }
368
490
 
369
- rl.close();
491
+ rl?.close();
370
492
 
371
493
  // Build config
372
494
  const config = {
@@ -418,6 +540,12 @@ async function runInit() {
418
540
  }
419
541
  created.push(".memento/scripts/");
420
542
 
543
+ // 7b. Write .memento/version for update checks
544
+ const pkgJsonPath = path.resolve(__dirname, "..", "package.json");
545
+ const pkgVersion = JSON.parse(fs.readFileSync(pkgJsonPath, "utf8")).version;
546
+ const versionPath = path.join(cwd, ".memento", "version");
547
+ fs.writeFileSync(versionPath, pkgVersion + "\n");
548
+
421
549
  // 8. Write .claude/settings.local.json (hooks)
422
550
  const hooks = {};
423
551
  if (enableUserPrompt) {
@@ -522,7 +650,140 @@ async function runInit() {
522
650
  }
523
651
  console.log(" Your agent will wake up remembering.\n");
524
652
 
525
- // 12. CLAUDE.md / AGENTS.md boilerplate
653
+ // 12. Auto-integrate agent instructions
654
+ const primaryAgent = selectedAgents[0];
655
+ const prompt = buildIntegrationPrompt(INSTRUCTIONS_BLOB);
656
+ const cmdParts = HEADLESS_CMDS[primaryAgent]?.(prompt);
657
+
658
+ if (nonInteractive) {
659
+ // Auto-run headless integration, no prompt
660
+ if (cmdParts) {
661
+ console.log(` Integrating instructions via ${AGENTS[primaryAgent].name}...`);
662
+ const result = runAgentHeadless(primaryAgent, prompt);
663
+ if (result !== null) {
664
+ console.log(result);
665
+ } else {
666
+ // Fallback: print the blob
667
+ console.log(" Agent integration failed — paste these instructions manually:\n");
668
+ printInstructionsFallback(selectedAgents);
669
+ }
670
+ } else {
671
+ printInstructionsFallback(selectedAgents);
672
+ }
673
+ } else {
674
+ // Interactive: ask with explicit command shown
675
+ if (cmdParts) {
676
+ const [cmd, ...args] = cmdParts;
677
+ const displayCmd = `${cmd} ${args[0]}${args.length > 1 ? " ..." : ""}`;
678
+ const rl2 = readline.createInterface({ input: process.stdin, output: process.stdout });
679
+ console.log("─".repeat(60));
680
+ const integrate = await askYesNo(
681
+ rl2,
682
+ `\n Auto-integrate instructions into your project?\n This will run: ${displayCmd}\n\n Proceed?`,
683
+ true,
684
+ );
685
+ rl2.close();
686
+
687
+ if (integrate) {
688
+ console.log(`\n Running ${AGENTS[primaryAgent].name}...`);
689
+ const result = runAgentHeadless(primaryAgent, prompt);
690
+ if (result !== null) {
691
+ console.log(result);
692
+ } else {
693
+ console.log(" Agent integration failed — paste these instructions manually:\n");
694
+ printInstructionsFallback(selectedAgents);
695
+ }
696
+ } else {
697
+ printInstructionsFallback(selectedAgents);
698
+ }
699
+ } else {
700
+ printInstructionsFallback(selectedAgents);
701
+ }
702
+ }
703
+ }
704
+
705
+ // ---------------------------------------------------------------------------
706
+ // Update command — copy fresh hook scripts to an existing installation
707
+ // ---------------------------------------------------------------------------
708
+
709
+ async function runUpdate() {
710
+ const cwd = process.cwd();
711
+ const configPath = path.join(cwd, ".memento.json");
712
+
713
+ if (!fs.existsSync(configPath)) {
714
+ console.error(
715
+ " Error: .memento.json not found in current directory.\n" +
716
+ " Run `npx memento-mcp init` first to set up Memento.\n"
717
+ );
718
+ process.exit(1);
719
+ }
720
+
721
+ const pkgJsonPath = path.resolve(__dirname, "..", "package.json");
722
+ const pkgVersion = JSON.parse(fs.readFileSync(pkgJsonPath, "utf8")).version;
723
+
724
+ const pkgScriptsDir = path.resolve(__dirname, "..", "scripts");
725
+ const localScriptsDir = path.join(cwd, ".memento", "scripts");
726
+
727
+ if (!fs.existsSync(localScriptsDir)) {
728
+ fs.mkdirSync(localScriptsDir, { recursive: true });
729
+ }
730
+
731
+ // Copy all .sh files from package scripts/ to local .memento/scripts/
732
+ const scriptFiles = fs
733
+ .readdirSync(pkgScriptsDir)
734
+ .filter((f) => f.endsWith(".sh"));
735
+
736
+ const updated = [];
737
+ for (const name of scriptFiles) {
738
+ const src = path.join(pkgScriptsDir, name);
739
+ const dest = path.join(localScriptsDir, name);
740
+ fs.copyFileSync(src, dest);
741
+ fs.chmodSync(dest, 0o755);
742
+ updated.push(name);
743
+ }
744
+
745
+ // Write .memento/version
746
+ const versionPath = path.join(cwd, ".memento", "version");
747
+ fs.writeFileSync(versionPath, pkgVersion + "\n");
748
+
749
+ // Ensure SessionStart hook is registered for Claude Code workspaces
750
+ const config = readJsonFile(configPath) || {};
751
+ const agents = config.agents || [];
752
+ const hasClaude = agents.includes("claude-code");
753
+ let hooksUpdated = false;
754
+ if (hasClaude) {
755
+ const settingsPath = path.join(cwd, ".claude", "settings.local.json");
756
+ const claudeSettings = readJsonFile(settingsPath) || {};
757
+ const sessionStartCmd = path.join(localScriptsDir, "memento-sessionstart-identity.sh");
758
+ const existingHooks = claudeSettings.hooks?.["SessionStart"] || [];
759
+ const hasSessionStart = existingHooks.some((entry) =>
760
+ entry.hooks?.some((h) => h.command === sessionStartCmd)
761
+ );
762
+ if (!hasSessionStart) {
763
+ claudeSettings.hooks = claudeSettings.hooks || {};
764
+ claudeSettings.hooks["SessionStart"] = [
765
+ ...existingHooks,
766
+ { hooks: [{ type: "command", command: sessionStartCmd, timeout: 10000 }] },
767
+ ];
768
+ writeJsonFile(settingsPath, claudeSettings);
769
+ hooksUpdated = true;
770
+ }
771
+ }
772
+
773
+ console.log(`\n ✓ Memento hooks updated to v${pkgVersion}\n`);
774
+ console.log(" Updated scripts:");
775
+ for (const name of updated) {
776
+ console.log(` ${name}`);
777
+ }
778
+ if (hooksUpdated) {
779
+ console.log("\n Registered hooks:");
780
+ console.log(" SessionStart → memento-sessionstart-identity.sh (identity + version check)");
781
+ }
782
+ console.log(`\n Version written to .memento/version`);
783
+ console.log(" Restart your agent session to pick up changes.\n");
784
+ }
785
+
786
+ function printInstructionsFallback(selectedAgents) {
526
787
  const hasNonClaude = selectedAgents.some((k) => k !== "claude-code");
527
788
  const docTarget = hasNonClaude
528
789
  ? "your CLAUDE.md, AGENTS.md, or equivalent"
@@ -536,22 +797,7 @@ async function runInit() {
536
797
 
537
798
  ── paste below this line ──────────────────────────────
538
799
 
539
- ## Memento Protocol
540
-
541
- Working memory is managed by Memento. MCP tools available:
542
- \`memento_store\`, \`memento_recall\`, \`memento_item_list\`,
543
- \`memento_skip_add\`, \`memento_skip_check\`.
544
-
545
- **Memory discipline — notes are instructions, not logs.**
546
- Write: "Skip X until condition Y" — not "checked X, it was quiet."
547
- Every memory must answer: could a future agent with zero context
548
- read this and know exactly what to do?
549
-
550
- Use \`memento_store\` when you learn something worth keeping.
551
- Use \`memento_skip_add\` for things to explicitly not re-investigate.
552
- Use \`memento_recall\` to search memories by keyword or tag.
553
- Hooks run automatically — recall before responses, distillation
554
- before compaction. Trust the hooks. Focus on writing good memories.
800
+ ${INSTRUCTIONS_BLOB}
555
801
 
556
802
  ── paste above this line ──────────────────────────────
557
803
  `);
@@ -569,7 +815,13 @@ if (isMain) {
569
815
  const args = process.argv.slice(2);
570
816
 
571
817
  if (args[0] === "init") {
572
- runInit().catch((err) => {
818
+ const flags = parseFlags(args.slice(1));
819
+ runInit(flags).catch((err) => {
820
+ console.error(err);
821
+ process.exit(1);
822
+ });
823
+ } else if (args[0] === "update") {
824
+ runUpdate().catch((err) => {
573
825
  console.error(err);
574
826
  process.exit(1);
575
827
  });
@@ -584,11 +836,15 @@ if (isMain) {
584
836
  Memento Protocol CLI
585
837
 
586
838
  Usage:
587
- npx memento-mcp init Set up Memento in the current project
588
- npx memento-mcp Start the MCP server (used by .mcp.json)
839
+ npx memento-mcp init Set up Memento in the current project
840
+ npx memento-mcp init -y Non-interactive setup (uses defaults)
841
+ npx memento-mcp init --api-key KEY Provide API key (skips signup)
842
+ npx memento-mcp update Update hook scripts to latest version
843
+ npx memento-mcp Start the MCP server (used by .mcp.json)
844
+
845
+ The -y flag enables fully non-interactive setup for CI/scripting.
846
+ Combine with --api-key to skip auto-signup.
589
847
 
590
- This creates .memento.json, configures your agent's MCP client,
591
- and sets up hooks (Claude Code) — all in one command.
592
848
  Supports Claude Code, Codex, Gemini CLI, and OpenCode.
593
849
  `);
594
850
  process.exit(1);