universal-memory-mcp 0.2.2 → 0.3.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 slicenferqin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/index.js CHANGED
@@ -55,6 +55,10 @@ Use specific keywords for better results (e.g., "TypeScript preferences", "datab
55
55
  type: 'string',
56
56
  description: 'Filter by project name',
57
57
  },
58
+ client: {
59
+ type: 'string',
60
+ description: 'Filter by client name (e.g., claude-code, opencode, trae)',
61
+ },
58
62
  limit: {
59
63
  type: 'number',
60
64
  description: 'Max results to return, default 10',
@@ -92,6 +96,10 @@ This builds memory over time, allowing recall of past discussions in future sess
92
96
  type: 'string',
93
97
  description: 'Project name if in a project context',
94
98
  },
99
+ client: {
100
+ type: 'string',
101
+ description: 'Client name (e.g., claude-code, opencode, trae)',
102
+ },
95
103
  session_id: {
96
104
  type: 'string',
97
105
  description: 'Session ID for grouping related conversations',
@@ -142,12 +150,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
142
150
  try {
143
151
  switch (name) {
144
152
  case 'memory_search': {
145
- const { query, time_range, project, limit } = args;
153
+ const { query, time_range, project, client, limit } = args;
146
154
  const results = await memoryManager.search(query, {
147
155
  timeRange: time_range
148
156
  ? [new Date(time_range[0]), new Date(time_range[1])]
149
157
  : undefined,
150
158
  project,
159
+ client,
151
160
  limit,
152
161
  });
153
162
  if (results.length === 0) {
@@ -177,9 +186,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
177
186
  };
178
187
  }
179
188
  case 'memory_record': {
180
- const { user_message, ai_response, project, session_id } = args;
189
+ const { user_message, ai_response, project, client, session_id } = args;
181
190
  const conversation = await memoryManager.recordConversation(user_message, ai_response, {
182
191
  project,
192
+ client,
183
193
  sessionId: session_id,
184
194
  });
185
195
  return {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA;;;;;;;GAOG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EACL,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,oCAAoC,CAAC;AAE5C,OAAO,EAAE,mBAAmB,EAAqB,MAAM,uBAAuB,CAAC;AAE/E,wBAAwB;AACxB,MAAM,aAAa,GAAG,mBAAmB,EAAE,CAAC;AAE5C,oBAAoB;AACpB,MAAM,MAAM,GAAG,IAAI,MAAM,CACvB;IACE,IAAI,EAAE,sBAAsB;IAC5B,OAAO,EAAE,OAAO;CACjB,EACD;IACE,YAAY,EAAE;QACZ,KAAK,EAAE,EAAE;KACV;CACF,CACF,CAAC;AAEF,eAAe;AACf,MAAM,CAAC,iBAAiB,CAAC,sBAAsB,EAAE,KAAK,IAAI,EAAE;IAC1D,OAAO;QACL,KAAK,EAAE;YACL;gBACE,IAAI,EAAE,eAAe;gBACrB,WAAW,EAAE;;;;;;;;;;gGAU2E;gBACxF,WAAW,EAAE;oBACX,IAAI,EAAE,QAAQ;oBACd,UAAU,EAAE;wBACV,KAAK,EAAE;4BACL,IAAI,EAAE,QAAQ;4BACd,WAAW,EAAE,6BAA6B;yBAC3C;wBACD,UAAU,EAAE;4BACV,IAAI,EAAE,OAAO;4BACb,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;4BACzB,WAAW,EAAE,uDAAuD;yBACrE;wBACD,OAAO,EAAE;4BACP,IAAI,EAAE,QAAQ;4BACd,WAAW,EAAE,wBAAwB;yBACtC;wBACD,KAAK,EAAE;4BACL,IAAI,EAAE,QAAQ;4BACd,WAAW,EAAE,mCAAmC;yBACjD;qBACF;oBACD,QAAQ,EAAE,CAAC,OAAO,CAAC;iBACpB;aACF;YACD;gBACE,IAAI,EAAE,eAAe;gBACrB,WAAW,EAAE;;;;;;;;;;;sFAWiE;gBAC9E,WAAW,EAAE;oBACX,IAAI,EAAE,QAAQ;oBACd,UAAU,EAAE;wBACV,YAAY,EAAE;4BACZ,IAAI,EAAE,QAAQ;4BACd,WAAW,EAAE,2BAA2B;yBACzC;wBACD,WAAW,EAAE;4BACX,IAAI,EAAE,QAAQ;4BACd,WAAW,EAAE,uCAAuC;yBACrD;wBACD,OAAO,EAAE;4BACP,IAAI,EAAE,QAAQ;4BACd,WAAW,EAAE,sCAAsC;yBACpD;wBACD,UAAU,EAAE;4BACV,IAAI,EAAE,QAAQ;4BACd,WAAW,EAAE,+CAA+C;yBAC7D;qBACF;oBACD,QAAQ,EAAE,CAAC,cAAc,EAAE,aAAa,CAAC;iBAC1C;aACF;YACD;gBACE,IAAI,EAAE,yBAAyB;gBAC/B,WAAW,EAAE;;;;;;;;;;;;;;qDAcgC;gBAC7C,WAAW,EAAE;oBACX,IAAI,EAAE,QAAQ;oBACd,UAAU,EAAE;wBACV,QAAQ,EAAE;4BACR,IAAI,EAAE,QAAQ;4BACd,IAAI,EAAE,CAAC,WAAW,EAAE,aAAa,EAAE,UAAU,EAAE,OAAO,CAAC;4BACvD,WAAW,EAAE,iBAAiB;yBAC/B;wBACD,OAAO,EAAE;4BACP,IAAI,EAAE,QAAQ;4BACd,WAAW,EAAE,qBAAqB;yBACnC;qBACF;oBACD,QAAQ,EAAE,CAAC,UAAU,EAAE,SAAS,CAAC;iBAClC;aACF;SACF;KACF,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,oBAAoB;AACpB,MAAM,CAAC,iBAAiB,CAAC,qBAAqB,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;IAChE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAEjD,IAAI,CAAC;QACH,QAAQ,IAAI,EAAE,CAAC;YACb,KAAK,eAAe,CAAC,CAAC,CAAC;gBACrB,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,IAK7C,CAAC;gBAEF,MAAM,OAAO,GAAG,MAAM,aAAa,CAAC,MAAM,CAAC,KAAK,EAAE;oBAChD,SAAS,EAAE,UAAU;wBACnB,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;wBACpD,CAAC,CAAC,SAAS;oBACb,OAAO;oBACP,KAAK;iBACN,CAAC,CAAC;gBAEH,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;oBACzB,OAAO;wBACL,OAAO,EAAE;4BACP;gCACE,IAAI,EAAE,MAAM;gCACZ,IAAI,EAAE,+BAA+B,KAAK,IAAI;6BAC/C;yBACF;qBACF,CAAC;gBACJ,CAAC;gBAED,MAAM,SAAS,GAAG,OAAO;qBACtB,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;oBACZ,MAAM,IAAI,GAAG,CAAC,CAAC,SAAS,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;oBACrD,MAAM,WAAW,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;oBACvD,OAAO,cAAc,CAAC,GAAG,CAAC,GAAG,WAAW,KAAK,IAAI,gBAAgB,CAAC,CAAC,CAAC,KAAK,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC;gBAClH,CAAC,CAAC;qBACD,IAAI,CAAC,aAAa,CAAC,CAAC;gBAEvB,OAAO;oBACL,OAAO,EAAE;wBACP;4BACE,IAAI,EAAE,MAAM;4BACZ,IAAI,EAAE,SAAS,OAAO,CAAC,MAAM,0BAA0B,SAAS,EAAE;yBACnE;qBACF;iBACF,CAAC;YACJ,CAAC;YAED,KAAK,eAAe,CAAC,CAAC,CAAC;gBACrB,MAAM,EAAE,YAAY,EAAE,WAAW,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,IAK1D,CAAC;gBAEF,MAAM,YAAY,GAAG,MAAM,aAAa,CAAC,kBAAkB,CACzD,YAAY,EACZ,WAAW,EACX;oBACE,OAAO;oBACP,SAAS,EAAE,UAAU;iBACtB,CACF,CAAC;gBAEF,OAAO;oBACL,OAAO,EAAE;wBACP;4BACE,IAAI,EAAE,MAAM;4BACZ,IAAI,EAAE,8BAA8B,YAAY,CAAC,EAAE,GAAG;yBACvD;qBACF;iBACF,CAAC;YACJ,CAAC;YAED,KAAK,yBAAyB,CAAC,CAAC,CAAC;gBAC/B,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,IAG7B,CAAC;gBAEF,MAAM,aAAa,CAAC,oBAAoB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;gBAE5D,OAAO;oBACL,OAAO,EAAE;wBACP;4BACE,IAAI,EAAE,MAAM;4BACZ,IAAI,EAAE,+BAA+B,QAAQ,SAAS,OAAO,EAAE;yBAChE;qBACF;iBACF,CAAC;YACJ,CAAC;YAED;gBACE,MAAM,IAAI,KAAK,CAAC,iBAAiB,IAAI,EAAE,CAAC,CAAC;QAC7C,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACvE,OAAO;YACL,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAM;oBACZ,IAAI,EAAE,UAAU,OAAO,EAAE;iBAC1B;aACF;YACD,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,eAAe;AACf,KAAK,UAAU,IAAI;IACjB,2BAA2B;IAC3B,MAAM,aAAa,CAAC,UAAU,EAAE,CAAC;IAEjC,OAAO,CAAC,KAAK,CAAC,yCAAyC,CAAC,CAAC;IACzD,OAAO,CAAC,KAAK,CAAC,iBAAiB,aAAa,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC;IAEjE,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAEhC,OAAO,CAAC,KAAK,CAAC,qCAAqC,CAAC,CAAC;AACvD,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACrB,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC;IACrC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=record.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"record.d.ts","sourceRoot":"","sources":["../src/record.ts"],"names":[],"mappings":""}
package/dist/record.js ADDED
@@ -0,0 +1,94 @@
1
+ #!/usr/bin/env node
2
+ import { createMemoryManager } from 'universal-memory-core';
3
+ function getArgValue(args, name) {
4
+ const idx = args.indexOf(name);
5
+ if (idx === -1)
6
+ return undefined;
7
+ const value = args[idx + 1];
8
+ if (!value || value.startsWith('--'))
9
+ return undefined;
10
+ return value;
11
+ }
12
+ function hasFlag(args, name) {
13
+ return args.includes(name);
14
+ }
15
+ async function readStdin() {
16
+ const chunks = [];
17
+ for await (const chunk of process.stdin) {
18
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
19
+ }
20
+ return Buffer.concat(chunks).toString('utf8');
21
+ }
22
+ function usage() {
23
+ return [
24
+ 'Usage:',
25
+ ' universal-memory-record --user <text> --ai <text> [--project <name>] [--client <name>] [--session-id <id>] [--cwd <path>]',
26
+ ' universal-memory-record --json',
27
+ '',
28
+ 'JSON mode reads stdin with fields:',
29
+ ' { "user_message": "...", "ai_response": "...", "project": "...", "client": "...", "session_id": "...", "working_directory": "...", "storage_path": "..." }',
30
+ '',
31
+ 'Storage path resolution priority:',
32
+ ' --storage-path / JSON.storage_path > env.MEMORY_PATH > default (~/.ai_memory)',
33
+ ].join('\n');
34
+ }
35
+ function normalizeInput(argv) {
36
+ if (hasFlag(argv, '--json')) {
37
+ return {};
38
+ }
39
+ const user_message = getArgValue(argv, '--user');
40
+ const ai_response = getArgValue(argv, '--ai');
41
+ const project = getArgValue(argv, '--project');
42
+ const client = getArgValue(argv, '--client');
43
+ const session_id = getArgValue(argv, '--session-id');
44
+ const working_directory = getArgValue(argv, '--cwd');
45
+ const storage_path = getArgValue(argv, '--storage-path');
46
+ return {
47
+ user_message,
48
+ ai_response,
49
+ project,
50
+ client,
51
+ session_id,
52
+ working_directory,
53
+ storage_path,
54
+ };
55
+ }
56
+ async function main() {
57
+ const argv = process.argv.slice(2);
58
+ if (hasFlag(argv, '--help') || hasFlag(argv, '-h')) {
59
+ process.stdout.write(`${usage()}\n`);
60
+ process.exit(0);
61
+ }
62
+ let input = normalizeInput(argv);
63
+ if (hasFlag(argv, '--json')) {
64
+ const raw = await readStdin();
65
+ if (!raw.trim()) {
66
+ process.stderr.write('Error: --json provided but stdin is empty\n');
67
+ process.stderr.write(`${usage()}\n`);
68
+ process.exit(1);
69
+ }
70
+ input = JSON.parse(raw);
71
+ }
72
+ const userMessage = input.user_message;
73
+ const aiResponse = input.ai_response;
74
+ if (!userMessage || !aiResponse) {
75
+ process.stderr.write('Error: missing required fields user_message/ai_response (or --user/--ai)\n');
76
+ process.stderr.write(`${usage()}\n`);
77
+ process.exit(1);
78
+ }
79
+ const storagePath = input.storage_path || process.env.MEMORY_PATH || undefined;
80
+ const memoryManager = createMemoryManager(storagePath ? { storagePath } : undefined);
81
+ const conversation = await memoryManager.recordConversation(userMessage, aiResponse, {
82
+ project: input.project,
83
+ client: input.client,
84
+ sessionId: input.session_id,
85
+ workingDirectory: input.working_directory,
86
+ });
87
+ process.stdout.write(`${conversation.id}\n`);
88
+ }
89
+ main().catch((error) => {
90
+ const message = error instanceof Error ? error.message : String(error);
91
+ process.stderr.write(`Fatal error: ${message}\n`);
92
+ process.exit(1);
93
+ });
94
+ //# sourceMappingURL=record.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"record.js","sourceRoot":"","sources":["../src/record.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "universal-memory-mcp",
3
- "version": "0.2.2",
3
+ "version": "0.3.1",
4
4
  "description": "MCP Server for persistent AI memory across sessions",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
8
8
  "bin": {
9
- "universal-memory-mcp": "./dist/index.js"
9
+ "universal-memory-mcp": "./dist/index.js",
10
+ "universal-memory-record": "./dist/record.js"
10
11
  },
11
12
  "exports": {
12
13
  ".": {
@@ -24,7 +25,7 @@
24
25
  },
25
26
  "dependencies": {
26
27
  "@modelcontextprotocol/sdk": "^1.0.0",
27
- "universal-memory-core": "^0.1.3"
28
+ "universal-memory-core": "^0.1.4"
28
29
  },
29
30
  "devDependencies": {
30
31
  "@types/node": "^20.11.0",
@@ -12,8 +12,10 @@ import fs from 'fs';
12
12
  import path from 'path';
13
13
  import os from 'os';
14
14
 
15
- const CLAUDE_SETTINGS_PATH = path.join(os.homedir(), '.claude', 'settings.json');
16
- const CLAUDE_SKILLS_PATH = path.join(os.homedir(), '.claude', 'skills');
15
+ const CLAUDE_DIR = path.join(os.homedir(), '.claude');
16
+ const CLAUDE_SETTINGS_PATH = path.join(CLAUDE_DIR, 'settings.json');
17
+ const CLAUDE_SKILLS_PATH = path.join(CLAUDE_DIR, 'skills');
18
+ const CLAUDE_HOOKS_PATH = path.join(CLAUDE_DIR, 'hooks');
17
19
 
18
20
  // MCP server configuration
19
21
  const MCP_CONFIG = {
@@ -155,6 +157,21 @@ memory_update_long_term({
155
157
  - Use project name when in project context
156
158
  `;
157
159
 
160
+ /**
161
+ * Check if Claude Code is installed
162
+ */
163
+ function checkClaudeCodeInstalled() {
164
+ if (!fs.existsSync(CLAUDE_DIR)) {
165
+ console.log('\n⚠️ Claude Code not detected!\n');
166
+ console.log('Please install Claude Code first:');
167
+ console.log(' https://code.claude.com/\n');
168
+ console.log('After installing Claude Code, run:');
169
+ console.log(' npm install -g universal-memory-mcp\n');
170
+ return false;
171
+ }
172
+ return true;
173
+ }
174
+
158
175
  /**
159
176
  * Read JSON file safely
160
177
  */
@@ -253,6 +270,92 @@ function installSkill() {
253
270
  return true;
254
271
  }
255
272
 
273
+ /**
274
+ * Install Stop hook script
275
+ */
276
+ function installStopHook() {
277
+ console.log('\n🪝 Installing Stop hook...');
278
+
279
+ const hookScriptPath = path.join(CLAUDE_HOOKS_PATH, 'universal-memory-stop-hook.mjs');
280
+
281
+ // Create hooks directory if not exists
282
+ if (!fs.existsSync(CLAUDE_HOOKS_PATH)) {
283
+ fs.mkdirSync(CLAUDE_HOOKS_PATH, { recursive: true });
284
+ console.log(' Created hooks directory');
285
+ }
286
+
287
+ // Get source script path (in the same directory as this postinstall script)
288
+ const sourceScript = new URL('universal-memory-stop-hook.mjs', import.meta.url).pathname;
289
+
290
+ // Check if hook already exists
291
+ if (fs.existsSync(hookScriptPath)) {
292
+ const existingContent = fs.readFileSync(hookScriptPath, 'utf-8');
293
+ const newContent = fs.readFileSync(sourceScript, 'utf-8');
294
+
295
+ if (existingContent === newContent) {
296
+ console.log(' Stop hook already installed (same version)');
297
+ return false;
298
+ }
299
+
300
+ // Backup existing hook
301
+ const backupPath = `${hookScriptPath}.backup.${Date.now()}`;
302
+ fs.copyFileSync(hookScriptPath, backupPath);
303
+ console.log(` Backed up existing hook to: ${backupPath}`);
304
+ }
305
+
306
+ fs.copyFileSync(sourceScript, hookScriptPath);
307
+ fs.chmodSync(hookScriptPath, 0o755); // Make executable
308
+ console.log(' Stop hook installed successfully');
309
+ return true;
310
+ }
311
+
312
+ /**
313
+ * Configure Stop hook in Claude settings
314
+ */
315
+ function configureStopHook() {
316
+ console.log('\n⚙️ Configuring Stop hook...');
317
+
318
+ let settings = readJsonFile(CLAUDE_SETTINGS_PATH) || {};
319
+
320
+ // Initialize hooks if not exists
321
+ if (!settings.hooks) {
322
+ settings.hooks = {};
323
+ }
324
+
325
+ // Initialize Stop hook array if not exists
326
+ if (!settings.hooks.Stop) {
327
+ settings.hooks.Stop = [];
328
+ }
329
+
330
+ // Check if our hook is already configured
331
+ const hookScriptPath = path.join(os.homedir(), '.claude', 'hooks', 'universal-memory-stop-hook.mjs');
332
+ const hookCommand = `node ${hookScriptPath}`;
333
+ const alreadyConfigured = settings.hooks.Stop.some(entry =>
334
+ entry.hooks?.some(hook =>
335
+ hook.type === 'command' && hook.command.includes('universal-memory-stop-hook')
336
+ )
337
+ );
338
+
339
+ if (alreadyConfigured) {
340
+ console.log(' Stop hook already configured');
341
+ return false;
342
+ }
343
+
344
+ // Add Stop hook configuration
345
+ settings.hooks.Stop.push({
346
+ hooks: [
347
+ {
348
+ type: 'command',
349
+ command: hookCommand
350
+ }
351
+ ]
352
+ });
353
+
354
+ writeJsonFile(CLAUDE_SETTINGS_PATH, settings);
355
+ console.log(' Stop hook configured successfully');
356
+ return true;
357
+ }
358
+
256
359
  /**
257
360
  * Main installation
258
361
  */
@@ -261,26 +364,39 @@ function main() {
261
364
  console.log('║ Universal Memory MCP - Setup ║');
262
365
  console.log('╚════════════════════════════════════════════════════════════╝');
263
366
 
367
+ // Check Claude Code installation
368
+ if (!checkClaudeCodeInstalled()) {
369
+ process.exit(0);
370
+ }
371
+
264
372
  let needsRestart = false;
265
373
 
266
374
  try {
267
- // Configure MCP server
375
+ // 1. Configure MCP server
268
376
  const mcpConfigured = configureMcpServer();
269
377
  if (mcpConfigured) needsRestart = true;
270
378
 
271
- // Install skill
379
+ // 2. Install skill
272
380
  const skillInstalled = installSkill();
273
381
  if (skillInstalled) needsRestart = true;
274
382
 
383
+ // 3. Install Stop hook script
384
+ const hookInstalled = installStopHook();
385
+ if (hookInstalled) needsRestart = true;
386
+
387
+ // 4. Configure Stop hook
388
+ const hookConfigured = configureStopHook();
389
+ if (hookConfigured) needsRestart = true;
390
+
275
391
  // Summary
276
392
  console.log('\n' + '═'.repeat(60));
277
393
 
278
394
  if (needsRestart) {
279
395
  console.log('\n✅ Setup complete!\n');
280
- console.log('⚠️ IMPORTANT: Please restart Claude Code to enable the MCP server.\n');
396
+ console.log('⚠️ IMPORTANT: Please restart Claude Code to enable all features.\n');
281
397
  console.log('After restart, Claude will automatically:');
282
398
  console.log(' • Search past conversations when you reference them');
283
- console.log(' • Record important conversations for future recall');
399
+ console.log(' • Record EVERY conversation automatically (via Stop hook)');
284
400
  console.log(' • Remember your preferences and decisions\n');
285
401
  } else {
286
402
  console.log('\n✅ Already configured! No changes needed.\n');
@@ -289,6 +405,7 @@ function main() {
289
405
  console.log('📁 Configuration locations:');
290
406
  console.log(` MCP config: ${CLAUDE_SETTINGS_PATH}`);
291
407
  console.log(` Skill: ${path.join(CLAUDE_SKILLS_PATH, 'memory-assistant', 'SKILL.md')}`);
408
+ console.log(` Stop hook: ${path.join(CLAUDE_HOOKS_PATH, 'universal-memory-stop-hook.mjs')}`);
292
409
  console.log(` Memory storage: ${path.join(os.homedir(), '.ai_memory')}\n`);
293
410
 
294
411
  } catch (error) {
@@ -0,0 +1,261 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import { spawnSync } from 'node:child_process';
6
+
7
+ // Enable debug logging via environment variable
8
+ const DEBUG = process.env.UNIVERSAL_MEMORY_DEBUG === '1';
9
+
10
+ function debugLog(message) {
11
+ if (DEBUG) {
12
+ fs.appendFileSync('/tmp/universal-memory-stop-hook.log', `[${new Date().toISOString()}] ${message}\n`);
13
+ }
14
+ }
15
+
16
+ function readStdinSync() {
17
+ return fs.readFileSync(0, 'utf8');
18
+ }
19
+
20
+ function isNonEmptyString(value) {
21
+ return typeof value === 'string' && value.trim().length > 0;
22
+ }
23
+
24
+ function contentToText(content) {
25
+ if (typeof content === 'string') return content;
26
+ if (Array.isArray(content)) {
27
+ return content
28
+ .map((item) => {
29
+ if (typeof item === 'string') return item;
30
+ if (item && typeof item === 'object' && typeof item.text === 'string') return item.text;
31
+ if (item && typeof item === 'object' && typeof item.content === 'string') return item.content;
32
+ return '';
33
+ })
34
+ .filter(Boolean)
35
+ .join('\n');
36
+ }
37
+ if (content && typeof content === 'object') {
38
+ if (typeof content.text === 'string') return content.text;
39
+ if (typeof content.content === 'string') return content.content;
40
+ }
41
+ return '';
42
+ }
43
+
44
+ function extractMessages(transcriptJson) {
45
+ if (!transcriptJson || typeof transcriptJson !== 'object') return [];
46
+
47
+ if (Array.isArray(transcriptJson.messages)) return transcriptJson.messages;
48
+ if (Array.isArray(transcriptJson.turns)) return transcriptJson.turns;
49
+ if (Array.isArray(transcriptJson.events)) return transcriptJson.events;
50
+ if (Array.isArray(transcriptJson.transcript)) return transcriptJson.transcript;
51
+
52
+ if (transcriptJson.conversation && Array.isArray(transcriptJson.conversation.messages)) {
53
+ return transcriptJson.conversation.messages;
54
+ }
55
+
56
+ return [];
57
+ }
58
+
59
+ function extractLastExchange(messages) {
60
+ const normalized = messages
61
+ .map((m) => {
62
+ if (!m || typeof m !== 'object') return null;
63
+ const role = m.role || m.author || m.type;
64
+ const content = m.content ?? m.text ?? m.message ?? m.data?.content;
65
+ return {
66
+ role: typeof role === 'string' ? role : '',
67
+ text: contentToText(content),
68
+ };
69
+ })
70
+ .filter(Boolean)
71
+ .filter((m) => isNonEmptyString(m.role) && isNonEmptyString(m.text));
72
+
73
+ const roleNorm = (r) => String(r).toLowerCase();
74
+ const lastUserIdx = (() => {
75
+ for (let i = normalized.length - 1; i >= 0; i--) {
76
+ if (roleNorm(normalized[i].role) === 'user') return i;
77
+ }
78
+ return -1;
79
+ })();
80
+
81
+ const lastAssistantIdxAfterUser = (() => {
82
+ if (lastUserIdx === -1) return -1;
83
+ for (let i = lastUserIdx + 1; i < normalized.length; i++) {
84
+ if (roleNorm(normalized[i].role) === 'assistant') return i;
85
+ }
86
+ return -1;
87
+ })();
88
+
89
+ const lastAssistantIdx = (() => {
90
+ for (let i = normalized.length - 1; i >= 0; i--) {
91
+ if (roleNorm(normalized[i].role) === 'assistant') return i;
92
+ }
93
+ return -1;
94
+ })();
95
+
96
+ const userText = lastUserIdx !== -1 ? normalized[lastUserIdx].text : '';
97
+ const aiText =
98
+ lastAssistantIdxAfterUser !== -1
99
+ ? normalized[lastAssistantIdxAfterUser].text
100
+ : lastAssistantIdx !== -1
101
+ ? normalized[lastAssistantIdx].text
102
+ : '';
103
+
104
+ return { userText, aiText };
105
+ }
106
+
107
+ function truncate(text, maxChars) {
108
+ if (!isNonEmptyString(text)) return '';
109
+ if (text.length <= maxChars) return text;
110
+ return text.slice(0, maxChars) + `\n\n[truncated to ${maxChars} chars]`;
111
+ }
112
+
113
+ function findUp(startDir, relativeTarget) {
114
+ let dir = startDir;
115
+ for (;;) {
116
+ const candidate = path.join(dir, relativeTarget);
117
+ if (fs.existsSync(candidate)) return candidate;
118
+ const parent = path.dirname(dir);
119
+ if (parent === dir) return null;
120
+ dir = parent;
121
+ }
122
+ }
123
+
124
+ function runRecordCommand(payload, cwd) {
125
+ const input = JSON.stringify(payload);
126
+
127
+ const direct = spawnSync('universal-memory-record', ['--json'], {
128
+ input,
129
+ encoding: 'utf8',
130
+ cwd,
131
+ stdio: ['pipe', 'pipe', 'pipe'],
132
+ });
133
+ if (direct.status === 0) return { ok: true, id: direct.stdout.trim() };
134
+
135
+ const npx = spawnSync(
136
+ 'npx',
137
+ ['-y', '--package', 'universal-memory-mcp', 'universal-memory-record', '--json'],
138
+ {
139
+ input,
140
+ encoding: 'utf8',
141
+ cwd,
142
+ stdio: ['pipe', 'pipe', 'pipe'],
143
+ }
144
+ );
145
+ if (npx.status === 0) return { ok: true, id: npx.stdout.trim() };
146
+
147
+ const distPath = findUp(cwd, path.join('packages', 'mcp-server', 'dist', 'record.js'));
148
+ if (!distPath) {
149
+ return {
150
+ ok: false,
151
+ error: 'universal-memory-record not found (PATH/npx) and dist/record.js not found',
152
+ };
153
+ }
154
+
155
+ const fallback = spawnSync('node', [distPath, '--json'], {
156
+ input,
157
+ encoding: 'utf8',
158
+ cwd,
159
+ stdio: ['pipe', 'pipe', 'pipe'],
160
+ });
161
+ if (fallback.status === 0) return { ok: true, id: fallback.stdout.trim() };
162
+ return { ok: false, error: fallback.stderr || fallback.stdout || 'record command failed' };
163
+ }
164
+
165
+ function detectProjectName(cwd) {
166
+ try {
167
+ const gitDir = findUp(cwd, '.git');
168
+ if (gitDir) return path.basename(path.dirname(gitDir));
169
+ } catch {
170
+ return undefined;
171
+ }
172
+ return undefined;
173
+ }
174
+
175
+ function main() {
176
+ debugLog('Stop hook triggered');
177
+
178
+ const raw = readStdinSync();
179
+ if (!raw.trim()) {
180
+ debugLog('No stdin input, exiting');
181
+ process.exit(0);
182
+ }
183
+
184
+ debugLog(`Received input: ${raw.substring(0, 200)}...`);
185
+
186
+ let hookInput;
187
+ try {
188
+ hookInput = JSON.parse(raw);
189
+ } catch (err) {
190
+ debugLog(`Failed to parse input JSON: ${err.message}`);
191
+ process.exit(0);
192
+ }
193
+
194
+ const cwd = hookInput.cwd || process.cwd();
195
+ const transcriptPath = hookInput.transcript_path || hookInput.transcriptPath;
196
+ const sessionId = hookInput.session_id || hookInput.sessionId;
197
+
198
+ debugLog(`transcript_path: ${transcriptPath}`);
199
+
200
+ if (!isNonEmptyString(transcriptPath)) {
201
+ debugLog('No transcript_path, exiting');
202
+ process.exit(0);
203
+ }
204
+
205
+ let messages;
206
+ try {
207
+ const transcriptContent = fs.readFileSync(transcriptPath, 'utf8');
208
+
209
+ // Parse JSONL format (one JSON object per line)
210
+ const lines = transcriptContent.trim().split('\n').filter(line => line.trim());
211
+ const entries = lines.map(line => JSON.parse(line));
212
+
213
+ debugLog(`Successfully read ${entries.length} transcript entries`);
214
+
215
+ // Extract messages from JSONL entries
216
+ messages = entries
217
+ .filter(entry => entry.type === 'user' || entry.type === 'assistant')
218
+ .map(entry => ({
219
+ role: entry.type === 'user' ? 'user' : 'assistant',
220
+ content: entry.message?.content || entry.content
221
+ }));
222
+
223
+ debugLog(`Extracted ${messages.length} messages from JSONL`);
224
+ } catch (err) {
225
+ debugLog(`Failed to read transcript: ${err.message}`);
226
+ process.exit(0);
227
+ }
228
+
229
+ const { userText, aiText } = extractLastExchange(messages);
230
+ debugLog(`userText length: ${userText.length}, aiText length: ${aiText.length}`);
231
+
232
+ if (!isNonEmptyString(userText) || !isNonEmptyString(aiText)) {
233
+ debugLog('Empty user or AI text, exiting');
234
+ process.exit(0);
235
+ }
236
+
237
+ // Determine client - default to 'claude-code' if not provided
238
+ const client = isNonEmptyString(hookInput.client) ? hookInput.client : 'claude-code';
239
+
240
+ const payload = {
241
+ user_message: truncate(userText, 8000),
242
+ ai_response: truncate(aiText, 20000),
243
+ project: detectProjectName(cwd),
244
+ client: client,
245
+ session_id: isNonEmptyString(sessionId) ? sessionId : undefined,
246
+ working_directory: cwd,
247
+ };
248
+
249
+ debugLog(`Payload: client=${client}, project=${payload.project}, session_id=${payload.session_id}`);
250
+
251
+ const res = runRecordCommand(payload, cwd);
252
+ if (!res.ok) {
253
+ const errorMsg = `universal-memory stop hook warning: ${res.error}\n`;
254
+ process.stderr.write(errorMsg);
255
+ debugLog(`Error: ${res.error}`);
256
+ } else {
257
+ debugLog(`Successfully saved memory: ${res.id}`);
258
+ }
259
+ }
260
+
261
+ main();