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 +21 -0
- package/dist/index.js +12 -2
- package/dist/index.js.map +1 -1
- package/dist/record.d.ts +3 -0
- package/dist/record.d.ts.map +1 -0
- package/dist/record.js +94 -0
- package/dist/record.js.map +1 -0
- package/package.json +4 -3
- package/scripts/postinstall.js +123 -6
- package/scripts/universal-memory-stop-hook.mjs +261 -0
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":"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
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/record.d.ts
ADDED
|
@@ -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":";AAEA,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAY5D,SAAS,WAAW,CAAC,IAAc,EAAE,IAAY;IAC/C,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,IAAI,GAAG,KAAK,CAAC,CAAC;QAAE,OAAO,SAAS,CAAC;IACjC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;IAC5B,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,SAAS,CAAC;IACvD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,OAAO,CAAC,IAAc,EAAE,IAAY;IAC3C,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AAC7B,CAAC;AAED,KAAK,UAAU,SAAS;IACtB,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;QACxC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC3E,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AAChD,CAAC;AAED,SAAS,KAAK;IACZ,OAAO;QACL,QAAQ;QACR,6HAA6H;QAC7H,kCAAkC;QAClC,EAAE;QACF,oCAAoC;QACpC,8JAA8J;QAC9J,EAAE;QACF,mCAAmC;QACnC,iFAAiF;KAClF,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED,SAAS,cAAc,CAAC,IAAc;IACpC,IAAI,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,EAAE,CAAC;QAC5B,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,YAAY,GAAG,WAAW,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IACjD,MAAM,WAAW,GAAG,WAAW,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC9C,MAAM,OAAO,GAAG,WAAW,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;IAC/C,MAAM,MAAM,GAAG,WAAW,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;IAC7C,MAAM,UAAU,GAAG,WAAW,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;IACrD,MAAM,iBAAiB,GAAG,WAAW,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACrD,MAAM,YAAY,GAAG,WAAW,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;IAEzD,OAAO;QACL,YAAY;QACZ,WAAW;QACX,OAAO;QACP,MAAM;QACN,UAAU;QACV,iBAAiB;QACjB,YAAY;KACb,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAEnC,IAAI,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC;QACnD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,KAAK,EAAE,IAAI,CAAC,CAAC;QACrC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,IAAI,KAAK,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IACjC,IAAI,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,EAAE,CAAC;QAC5B,MAAM,GAAG,GAAG,MAAM,SAAS,EAAE,CAAC;QAC9B,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC;YAChB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,6CAA6C,CAAC,CAAC;YACpE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,KAAK,EAAE,IAAI,CAAC,CAAC;YACrC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAgB,CAAC;IACzC,CAAC;IAED,MAAM,WAAW,GAAG,KAAK,CAAC,YAAY,CAAC;IACvC,MAAM,UAAU,GAAG,KAAK,CAAC,WAAW,CAAC;IAErC,IAAI,CAAC,WAAW,IAAI,CAAC,UAAU,EAAE,CAAC;QAChC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,4EAA4E,CAAC,CAAC;QACnG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,KAAK,EAAE,IAAI,CAAC,CAAC;QACrC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,WAAW,GACf,KAAK,CAAC,YAAY,IAAI,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,SAAS,CAAC;IAE7D,MAAM,aAAa,GAAG,mBAAmB,CACvC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,SAAS,CAC1C,CAAC;IAEF,MAAM,YAAY,GAAG,MAAM,aAAa,CAAC,kBAAkB,CACzD,WAAW,EACX,UAAU,EACV;QACE,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,SAAS,EAAE,KAAK,CAAC,UAAU;QAC3B,gBAAgB,EAAE,KAAK,CAAC,iBAAiB;KACnC,CACT,CAAC;IAEF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,YAAY,CAAC,EAAE,IAAI,CAAC,CAAC;AAC/C,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACrB,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACvE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,gBAAgB,OAAO,IAAI,CAAC,CAAC;IAClD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "universal-memory-mcp",
|
|
3
|
-
"version": "0.
|
|
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.
|
|
28
|
+
"universal-memory-core": "^0.1.4"
|
|
28
29
|
},
|
|
29
30
|
"devDependencies": {
|
|
30
31
|
"@types/node": "^20.11.0",
|
package/scripts/postinstall.js
CHANGED
|
@@ -12,8 +12,10 @@ import fs from 'fs';
|
|
|
12
12
|
import path from 'path';
|
|
13
13
|
import os from 'os';
|
|
14
14
|
|
|
15
|
-
const
|
|
16
|
-
const
|
|
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
|
|
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
|
|
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();
|