@aitytech/agentkits-memory 1.0.0 ā 2.0.0
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/README.md +267 -149
- package/assets/agentkits-memory-add-memory.png +0 -0
- package/assets/agentkits-memory-memory-detail.png +0 -0
- package/assets/agentkits-memory-memory-list.png +0 -0
- package/assets/logo.svg +24 -0
- package/dist/better-sqlite3-backend.d.ts +192 -0
- package/dist/better-sqlite3-backend.d.ts.map +1 -0
- package/dist/better-sqlite3-backend.js +801 -0
- package/dist/better-sqlite3-backend.js.map +1 -0
- package/dist/cli/save.js +0 -0
- package/dist/cli/setup.d.ts +6 -2
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +289 -42
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/viewer.js +25 -56
- package/dist/cli/viewer.js.map +1 -1
- package/dist/cli/web-viewer.d.ts +14 -0
- package/dist/cli/web-viewer.d.ts.map +1 -0
- package/dist/cli/web-viewer.js +1769 -0
- package/dist/cli/web-viewer.js.map +1 -0
- package/dist/embeddings/embedding-cache.d.ts +131 -0
- package/dist/embeddings/embedding-cache.d.ts.map +1 -0
- package/dist/embeddings/embedding-cache.js +217 -0
- package/dist/embeddings/embedding-cache.js.map +1 -0
- package/dist/embeddings/index.d.ts +11 -0
- package/dist/embeddings/index.d.ts.map +1 -0
- package/dist/embeddings/index.js +11 -0
- package/dist/embeddings/index.js.map +1 -0
- package/dist/embeddings/local-embeddings.d.ts +140 -0
- package/dist/embeddings/local-embeddings.d.ts.map +1 -0
- package/dist/embeddings/local-embeddings.js +293 -0
- package/dist/embeddings/local-embeddings.js.map +1 -0
- package/dist/hooks/context.d.ts +6 -1
- package/dist/hooks/context.d.ts.map +1 -1
- package/dist/hooks/context.js +12 -2
- package/dist/hooks/context.js.map +1 -1
- package/dist/hooks/observation.d.ts +6 -1
- package/dist/hooks/observation.d.ts.map +1 -1
- package/dist/hooks/observation.js +12 -2
- package/dist/hooks/observation.js.map +1 -1
- package/dist/hooks/service.d.ts +1 -6
- package/dist/hooks/service.d.ts.map +1 -1
- package/dist/hooks/service.js +33 -85
- package/dist/hooks/service.js.map +1 -1
- package/dist/hooks/session-init.d.ts +6 -1
- package/dist/hooks/session-init.d.ts.map +1 -1
- package/dist/hooks/session-init.js +12 -2
- package/dist/hooks/session-init.js.map +1 -1
- package/dist/hooks/summarize.d.ts +6 -1
- package/dist/hooks/summarize.d.ts.map +1 -1
- package/dist/hooks/summarize.js +12 -2
- package/dist/hooks/summarize.js.map +1 -1
- package/dist/index.d.ts +10 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +172 -94
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.js +17 -3
- package/dist/mcp/server.js.map +1 -1
- package/dist/migration.js +3 -3
- package/dist/migration.js.map +1 -1
- package/dist/search/hybrid-search.d.ts +262 -0
- package/dist/search/hybrid-search.d.ts.map +1 -0
- package/dist/search/hybrid-search.js +688 -0
- package/dist/search/hybrid-search.js.map +1 -0
- package/dist/search/index.d.ts +13 -0
- package/dist/search/index.d.ts.map +1 -0
- package/dist/search/index.js +13 -0
- package/dist/search/index.js.map +1 -0
- package/dist/search/token-economics.d.ts +161 -0
- package/dist/search/token-economics.d.ts.map +1 -0
- package/dist/search/token-economics.js +239 -0
- package/dist/search/token-economics.js.map +1 -0
- package/dist/types.d.ts +0 -68
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +23 -8
- package/src/__tests__/better-sqlite3-backend.test.ts +1466 -0
- package/src/__tests__/cache-manager.test.ts +499 -0
- package/src/__tests__/embedding-integration.test.ts +481 -0
- package/src/__tests__/hnsw-index.test.ts +727 -0
- package/src/__tests__/index.test.ts +432 -0
- package/src/better-sqlite3-backend.ts +1000 -0
- package/src/cli/setup.ts +358 -47
- package/src/cli/viewer.ts +28 -63
- package/src/cli/web-viewer.ts +1956 -0
- package/src/embeddings/__tests__/embedding-cache.test.ts +269 -0
- package/src/embeddings/__tests__/local-embeddings.test.ts +495 -0
- package/src/embeddings/embedding-cache.ts +318 -0
- package/src/embeddings/index.ts +20 -0
- package/src/embeddings/local-embeddings.ts +419 -0
- package/src/hooks/__tests__/handlers.test.ts +58 -17
- package/src/hooks/__tests__/integration.test.ts +77 -26
- package/src/hooks/context.ts +13 -2
- package/src/hooks/observation.ts +13 -2
- package/src/hooks/service.ts +39 -100
- package/src/hooks/session-init.ts +13 -2
- package/src/hooks/summarize.ts +13 -2
- package/src/index.ts +210 -116
- package/src/mcp/server.ts +20 -3
- package/src/search/__tests__/hybrid-search.test.ts +669 -0
- package/src/search/__tests__/token-economics.test.ts +276 -0
- package/src/search/hybrid-search.ts +968 -0
- package/src/search/index.ts +29 -0
- package/src/search/token-economics.ts +367 -0
- package/src/types.ts +0 -96
- package/src/__tests__/sqljs-backend.test.ts +0 -410
- package/src/migration.ts +0 -574
- package/src/sql.js.d.ts +0 -70
- package/src/sqljs-backend.ts +0 -789
package/src/cli/setup.ts
CHANGED
|
@@ -2,14 +2,18 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* AgentKits Memory Setup CLI
|
|
4
4
|
*
|
|
5
|
-
* Sets up memory hooks
|
|
5
|
+
* Sets up memory hooks, MCP server, and downloads embedding model.
|
|
6
|
+
* Supports multiple AI tools: Claude Code, Cursor, Windsurf, etc.
|
|
6
7
|
*
|
|
7
8
|
* Usage:
|
|
8
9
|
* npx agentkits-memory-setup [options]
|
|
9
10
|
*
|
|
10
11
|
* Options:
|
|
11
12
|
* --project-dir=X Project directory (default: cwd)
|
|
12
|
-
* --force Overwrite existing
|
|
13
|
+
* --force Overwrite existing configuration
|
|
14
|
+
* --skip-model Skip embedding model download
|
|
15
|
+
* --skip-mcp Skip MCP server configuration
|
|
16
|
+
* --show-hooks Show full hooks JSON for manual configuration
|
|
13
17
|
* --json Output result as JSON
|
|
14
18
|
*
|
|
15
19
|
* @module @agentkits/memory/cli/setup
|
|
@@ -17,6 +21,7 @@
|
|
|
17
21
|
|
|
18
22
|
import * as fs from 'node:fs';
|
|
19
23
|
import * as path from 'node:path';
|
|
24
|
+
import { LocalEmbeddingsService } from '../embeddings/local-embeddings.js';
|
|
20
25
|
|
|
21
26
|
const args = process.argv.slice(2);
|
|
22
27
|
|
|
@@ -27,6 +32,8 @@ interface HookEntry {
|
|
|
27
32
|
|
|
28
33
|
interface HooksConfig {
|
|
29
34
|
SessionStart?: HookEntry[];
|
|
35
|
+
UserPromptSubmit?: HookEntry[];
|
|
36
|
+
PostToolUse?: HookEntry[];
|
|
30
37
|
Stop?: HookEntry[];
|
|
31
38
|
PreCompact?: HookEntry[];
|
|
32
39
|
[key: string]: HookEntry[] | undefined;
|
|
@@ -34,10 +41,36 @@ interface HooksConfig {
|
|
|
34
41
|
|
|
35
42
|
interface ClaudeSettings {
|
|
36
43
|
hooks?: HooksConfig;
|
|
44
|
+
mcpServers?: Record<string, McpServerConfig>;
|
|
37
45
|
[key: string]: unknown;
|
|
38
46
|
}
|
|
39
47
|
|
|
48
|
+
interface McpServerConfig {
|
|
49
|
+
command: string;
|
|
50
|
+
args?: string[];
|
|
51
|
+
env?: Record<string, string>;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface McpConfig {
|
|
55
|
+
mcpServers: Record<string, McpServerConfig>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const MEMORY_MCP_SERVER: McpServerConfig = {
|
|
59
|
+
command: 'npx',
|
|
60
|
+
args: ['-y', 'agentkits-memory-server'],
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Memory hooks for Claude Code lifecycle events
|
|
65
|
+
*
|
|
66
|
+
* Hook Events:
|
|
67
|
+
* - SessionStart: Load memory context at session start
|
|
68
|
+
* - UserPromptSubmit: Capture user intent when prompt submitted
|
|
69
|
+
* - PostToolUse: Capture observations after tool execution (Edit, Write, Bash, Task)
|
|
70
|
+
* - Stop: Generate summary when session ends
|
|
71
|
+
*/
|
|
40
72
|
const MEMORY_HOOKS: HooksConfig = {
|
|
73
|
+
// Load memory context at session start
|
|
41
74
|
SessionStart: [
|
|
42
75
|
{
|
|
43
76
|
matcher: '',
|
|
@@ -45,11 +78,42 @@ const MEMORY_HOOKS: HooksConfig = {
|
|
|
45
78
|
{
|
|
46
79
|
type: 'command',
|
|
47
80
|
command: 'npx --yes agentkits-memory-hook context',
|
|
81
|
+
timeout: 15,
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
|
|
87
|
+
// Capture user intent when prompt submitted
|
|
88
|
+
UserPromptSubmit: [
|
|
89
|
+
{
|
|
90
|
+
matcher: '',
|
|
91
|
+
hooks: [
|
|
92
|
+
{
|
|
93
|
+
type: 'command',
|
|
94
|
+
command: 'npx --yes agentkits-memory-hook session-init',
|
|
48
95
|
timeout: 10,
|
|
49
96
|
},
|
|
50
97
|
],
|
|
51
98
|
},
|
|
52
99
|
],
|
|
100
|
+
|
|
101
|
+
// Capture observations after tool execution
|
|
102
|
+
// Only track meaningful tools: Edit, Write, Bash, Task
|
|
103
|
+
PostToolUse: [
|
|
104
|
+
{
|
|
105
|
+
matcher: 'Edit|Write|Bash|Task',
|
|
106
|
+
hooks: [
|
|
107
|
+
{
|
|
108
|
+
type: 'command',
|
|
109
|
+
command: 'npx --yes agentkits-memory-hook observation',
|
|
110
|
+
timeout: 15,
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
|
|
116
|
+
// Generate summary when session ends
|
|
53
117
|
Stop: [
|
|
54
118
|
{
|
|
55
119
|
matcher: '',
|
|
@@ -57,7 +121,7 @@ const MEMORY_HOOKS: HooksConfig = {
|
|
|
57
121
|
{
|
|
58
122
|
type: 'command',
|
|
59
123
|
command: 'npx --yes agentkits-memory-hook summarize',
|
|
60
|
-
timeout:
|
|
124
|
+
timeout: 15,
|
|
61
125
|
},
|
|
62
126
|
],
|
|
63
127
|
},
|
|
@@ -81,37 +145,222 @@ function parseArgs(): Record<string, string | boolean> {
|
|
|
81
145
|
return parsed;
|
|
82
146
|
}
|
|
83
147
|
|
|
148
|
+
interface MergeResult {
|
|
149
|
+
merged: HooksConfig;
|
|
150
|
+
added: string[];
|
|
151
|
+
skipped: string[];
|
|
152
|
+
manualRequired: string[];
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Merge memory hooks with existing hooks configuration
|
|
157
|
+
* Always preserves existing hooks, never overwrites
|
|
158
|
+
*/
|
|
84
159
|
function mergeHooks(
|
|
85
160
|
existing: HooksConfig | undefined,
|
|
86
161
|
newHooks: HooksConfig,
|
|
87
162
|
force: boolean
|
|
88
|
-
):
|
|
89
|
-
|
|
90
|
-
|
|
163
|
+
): MergeResult {
|
|
164
|
+
const result: MergeResult = {
|
|
165
|
+
merged: { ...existing },
|
|
166
|
+
added: [],
|
|
167
|
+
skipped: [],
|
|
168
|
+
manualRequired: [],
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
// If no existing hooks, add all memory hooks
|
|
172
|
+
if (!existing) {
|
|
173
|
+
result.merged = { ...newHooks };
|
|
174
|
+
result.added = Object.keys(newHooks);
|
|
175
|
+
return result;
|
|
91
176
|
}
|
|
92
177
|
|
|
93
|
-
const
|
|
178
|
+
for (const [event, memoryHooks] of Object.entries(newHooks)) {
|
|
179
|
+
if (!memoryHooks) continue;
|
|
94
180
|
|
|
95
|
-
|
|
96
|
-
if (!hooks) continue;
|
|
181
|
+
const existingHooks = result.merged[event];
|
|
97
182
|
|
|
98
|
-
|
|
183
|
+
// Case 1: No existing hooks for this event ā add ours
|
|
99
184
|
if (!existingHooks) {
|
|
100
|
-
merged[event] =
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
185
|
+
result.merged[event] = memoryHooks;
|
|
186
|
+
result.added.push(event);
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Case 2: Check if our memory hook already exists
|
|
191
|
+
const hasMemoryHook = existingHooks.some((h: HookEntry) =>
|
|
192
|
+
h.hooks.some((hook) => hook.command.includes('agentkits-memory'))
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
if (hasMemoryHook) {
|
|
196
|
+
if (force) {
|
|
197
|
+
// Remove old memory hooks, add new ones
|
|
198
|
+
const filtered = existingHooks.filter(
|
|
199
|
+
(h: HookEntry) => !h.hooks.some((hook) => hook.command.includes('agentkits-memory'))
|
|
200
|
+
);
|
|
201
|
+
result.merged[event] = [...filtered, ...memoryHooks];
|
|
202
|
+
result.added.push(`${event} (updated)`);
|
|
203
|
+
} else {
|
|
204
|
+
result.skipped.push(`${event} (already configured)`);
|
|
205
|
+
}
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Case 3: PostToolUse with different matcher - needs attention
|
|
210
|
+
if (event === 'PostToolUse') {
|
|
211
|
+
const ourMatcher = memoryHooks[0]?.matcher || '';
|
|
212
|
+
const existingMatchers = existingHooks.map((h: HookEntry) => h.matcher || '');
|
|
213
|
+
|
|
214
|
+
// Check for potential conflicts (different matchers that might overlap)
|
|
215
|
+
const hasConflict = existingMatchers.some(
|
|
216
|
+
(m) => m && m !== ourMatcher && (m === '*' || ourMatcher.split('|').some((t) => m.includes(t)))
|
|
105
217
|
);
|
|
106
218
|
|
|
107
|
-
if (
|
|
108
|
-
//
|
|
109
|
-
merged[event] = [...existingHooks, ...
|
|
219
|
+
if (hasConflict) {
|
|
220
|
+
// Add anyway but warn user
|
|
221
|
+
result.merged[event] = [...existingHooks, ...memoryHooks];
|
|
222
|
+
result.added.push(event);
|
|
223
|
+
result.manualRequired.push(
|
|
224
|
+
`PostToolUse: Added with matcher "${ourMatcher}", existing has different matchers - verify no conflicts`
|
|
225
|
+
);
|
|
226
|
+
continue;
|
|
110
227
|
}
|
|
111
228
|
}
|
|
229
|
+
|
|
230
|
+
// Case 4: Append our hooks to existing
|
|
231
|
+
result.merged[event] = [...existingHooks, ...memoryHooks];
|
|
232
|
+
result.added.push(event);
|
|
112
233
|
}
|
|
113
234
|
|
|
114
|
-
return
|
|
235
|
+
return result;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Configure MCP server for different AI tools
|
|
240
|
+
* Creates/updates config files for: Claude Code, Cursor, Windsurf, etc.
|
|
241
|
+
*/
|
|
242
|
+
function configureMcp(
|
|
243
|
+
projectDir: string,
|
|
244
|
+
claudeSettings: ClaudeSettings,
|
|
245
|
+
force: boolean,
|
|
246
|
+
asJson: boolean
|
|
247
|
+
): { configured: string[]; skipped: string[] } {
|
|
248
|
+
const configured: string[] = [];
|
|
249
|
+
const skipped: string[] = [];
|
|
250
|
+
|
|
251
|
+
// 1. Add to Claude Code settings.json (mcpServers key)
|
|
252
|
+
// Always merge with existing servers, never overwrite
|
|
253
|
+
if (!claudeSettings.mcpServers) {
|
|
254
|
+
claudeSettings.mcpServers = {};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (!claudeSettings.mcpServers.memory || force) {
|
|
258
|
+
claudeSettings.mcpServers.memory = MEMORY_MCP_SERVER;
|
|
259
|
+
configured.push('Claude Code (.claude/settings.json)');
|
|
260
|
+
} else {
|
|
261
|
+
skipped.push('Claude Code (already configured)');
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// 2. Create/update root .mcp.json for other tools (Cursor, Windsurf, Claude Code, etc.)
|
|
265
|
+
// Always merge with existing servers, never overwrite
|
|
266
|
+
const mcpJsonPath = path.join(projectDir, '.mcp.json');
|
|
267
|
+
try {
|
|
268
|
+
let existing: McpConfig = { mcpServers: {} };
|
|
269
|
+
|
|
270
|
+
// Load existing config if present
|
|
271
|
+
if (fs.existsSync(mcpJsonPath)) {
|
|
272
|
+
try {
|
|
273
|
+
existing = JSON.parse(fs.readFileSync(mcpJsonPath, 'utf-8')) as McpConfig;
|
|
274
|
+
existing.mcpServers = existing.mcpServers || {};
|
|
275
|
+
} catch {
|
|
276
|
+
// If parse fails, start fresh but warn
|
|
277
|
+
if (!asJson) {
|
|
278
|
+
console.warn(' ā .mcp.json parse error, creating new config');
|
|
279
|
+
}
|
|
280
|
+
existing = { mcpServers: {} };
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Add or update memory server
|
|
285
|
+
if (!existing.mcpServers.memory || force) {
|
|
286
|
+
existing.mcpServers.memory = MEMORY_MCP_SERVER;
|
|
287
|
+
fs.writeFileSync(mcpJsonPath, JSON.stringify(existing, null, 2));
|
|
288
|
+
configured.push('Universal (.mcp.json)');
|
|
289
|
+
} else {
|
|
290
|
+
skipped.push('.mcp.json (already configured)');
|
|
291
|
+
}
|
|
292
|
+
} catch (error) {
|
|
293
|
+
skipped.push(`.mcp.json (error: ${error instanceof Error ? error.message : 'unknown'})`);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (!asJson && configured.length > 0) {
|
|
297
|
+
console.log('\nš MCP Server configured for:');
|
|
298
|
+
for (const tool of configured) {
|
|
299
|
+
console.log(` ā ${tool}`);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return { configured, skipped };
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async function downloadModel(cacheDir: string, asJson: boolean): Promise<boolean> {
|
|
307
|
+
if (!asJson) {
|
|
308
|
+
console.log('\nš„ Downloading embedding model...');
|
|
309
|
+
console.log(' Model: multilingual-e5-small (~470MB)');
|
|
310
|
+
console.log(' This enables semantic search in 100+ languages.\n');
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
try {
|
|
314
|
+
const embeddingsService = new LocalEmbeddingsService({
|
|
315
|
+
showProgress: !asJson,
|
|
316
|
+
cacheDir: path.join(cacheDir, 'embeddings-cache'),
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
await embeddingsService.initialize();
|
|
320
|
+
|
|
321
|
+
// Verify model works with a test embedding
|
|
322
|
+
const testResult = await embeddingsService.embed('Test embedding');
|
|
323
|
+
|
|
324
|
+
if (testResult.embedding.length !== 384) {
|
|
325
|
+
throw new Error(`Unexpected embedding dimension: ${testResult.embedding.length}`);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (!asJson) {
|
|
329
|
+
console.log(' ā Model downloaded and verified\n');
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return true;
|
|
333
|
+
} catch (error) {
|
|
334
|
+
if (!asJson) {
|
|
335
|
+
console.error(' ā Model download failed:', error instanceof Error ? error.message : error);
|
|
336
|
+
console.log(' Model will be downloaded on first use.\n');
|
|
337
|
+
}
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Print full hooks configuration for manual setup
|
|
344
|
+
*/
|
|
345
|
+
function printHooksConfig(): void {
|
|
346
|
+
console.log('\n' + 'ā'.repeat(60));
|
|
347
|
+
console.log('š MEMORY HOOKS CONFIGURATION\n');
|
|
348
|
+
console.log('Copy and paste this JSON into your settings file.\n');
|
|
349
|
+
|
|
350
|
+
console.log('For Claude Code: .claude/settings.json');
|
|
351
|
+
console.log('ā'.repeat(40));
|
|
352
|
+
console.log(JSON.stringify({ hooks: MEMORY_HOOKS }, null, 2));
|
|
353
|
+
|
|
354
|
+
console.log('\nFor Cursor/Windsurf: .cursor/mcp.json or settings');
|
|
355
|
+
console.log('ā'.repeat(40));
|
|
356
|
+
console.log('Add hooks section with the same configuration above.\n');
|
|
357
|
+
|
|
358
|
+
console.log('Hook Events:');
|
|
359
|
+
console.log(' ⢠SessionStart - Load memory context when session begins');
|
|
360
|
+
console.log(' ⢠UserPromptSubmit - Capture user intent on each prompt');
|
|
361
|
+
console.log(' ⢠PostToolUse - Record actions (Edit, Write, Bash, Task)');
|
|
362
|
+
console.log(' ⢠Stop - Generate summary when session ends');
|
|
363
|
+
console.log('ā'.repeat(60) + '\n');
|
|
115
364
|
}
|
|
116
365
|
|
|
117
366
|
async function main() {
|
|
@@ -119,12 +368,25 @@ async function main() {
|
|
|
119
368
|
const projectDir = (options['project-dir'] as string) || process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
120
369
|
const force = !!options.force;
|
|
121
370
|
const asJson = !!options.json;
|
|
371
|
+
const skipModel = !!options['skip-model'];
|
|
372
|
+
const skipMcp = !!options['skip-mcp'];
|
|
373
|
+
const showHooks = !!options['show-hooks'];
|
|
374
|
+
|
|
375
|
+
// Just show hooks config and exit
|
|
376
|
+
if (showHooks) {
|
|
377
|
+
printHooksConfig();
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
122
380
|
|
|
123
381
|
const claudeDir = path.join(projectDir, '.claude');
|
|
124
382
|
const settingsPath = path.join(claudeDir, 'settings.json');
|
|
125
383
|
const memoryDir = path.join(claudeDir, 'memory');
|
|
126
384
|
|
|
127
385
|
try {
|
|
386
|
+
if (!asJson) {
|
|
387
|
+
console.log('\nš§ AgentKits Memory Setup\n');
|
|
388
|
+
}
|
|
389
|
+
|
|
128
390
|
// Create directories
|
|
129
391
|
if (!fs.existsSync(claudeDir)) {
|
|
130
392
|
fs.mkdirSync(claudeDir, { recursive: true });
|
|
@@ -141,49 +403,98 @@ async function main() {
|
|
|
141
403
|
}
|
|
142
404
|
|
|
143
405
|
// Merge hooks
|
|
144
|
-
|
|
406
|
+
const hooksResult = mergeHooks(settings.hooks, MEMORY_HOOKS, force);
|
|
407
|
+
settings.hooks = hooksResult.merged;
|
|
408
|
+
|
|
409
|
+
// Configure MCP server
|
|
410
|
+
let mcpResult = { configured: [] as string[], skipped: [] as string[] };
|
|
411
|
+
if (!skipMcp) {
|
|
412
|
+
mcpResult = configureMcp(projectDir, settings, force, asJson);
|
|
413
|
+
}
|
|
145
414
|
|
|
146
415
|
// Write settings
|
|
147
416
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
148
417
|
|
|
149
|
-
//
|
|
150
|
-
|
|
151
|
-
if (!
|
|
152
|
-
|
|
153
|
-
activeContextPath,
|
|
154
|
-
`# Active Context
|
|
155
|
-
|
|
156
|
-
**Task**: None
|
|
157
|
-
**Status**: Ready
|
|
158
|
-
**Updated**: ${new Date().toISOString()}
|
|
159
|
-
|
|
160
|
-
## Current Focus
|
|
161
|
-
|
|
162
|
-
No active task.
|
|
163
|
-
|
|
164
|
-
## Notes
|
|
165
|
-
|
|
166
|
-
Memory system initialized.
|
|
167
|
-
`
|
|
168
|
-
);
|
|
418
|
+
// Download embedding model
|
|
419
|
+
let modelDownloaded = false;
|
|
420
|
+
if (!skipModel) {
|
|
421
|
+
modelDownloaded = await downloadModel(memoryDir, asJson);
|
|
169
422
|
}
|
|
170
423
|
|
|
171
424
|
const result = {
|
|
172
425
|
success: true,
|
|
173
426
|
settingsPath,
|
|
174
427
|
memoryDir,
|
|
175
|
-
hooksAdded:
|
|
176
|
-
|
|
428
|
+
hooksAdded: hooksResult.added,
|
|
429
|
+
hooksSkipped: hooksResult.skipped,
|
|
430
|
+
hooksManualRequired: hooksResult.manualRequired,
|
|
431
|
+
mcpConfigured: mcpResult.configured,
|
|
432
|
+
modelDownloaded,
|
|
433
|
+
message: 'Memory setup complete',
|
|
177
434
|
};
|
|
178
435
|
|
|
179
436
|
if (asJson) {
|
|
180
437
|
console.log(JSON.stringify(result, null, 2));
|
|
181
438
|
} else {
|
|
182
|
-
console.log('
|
|
183
|
-
console.log(
|
|
184
|
-
console.log(
|
|
185
|
-
|
|
186
|
-
|
|
439
|
+
console.log('ā
Setup Complete\n');
|
|
440
|
+
console.log(`š Settings: ${settingsPath}`);
|
|
441
|
+
console.log(`š Memory: ${memoryDir}`);
|
|
442
|
+
|
|
443
|
+
// Show hooks status
|
|
444
|
+
if (hooksResult.added.length > 0) {
|
|
445
|
+
console.log(`\nš Hooks added: ${hooksResult.added.join(', ')}`);
|
|
446
|
+
}
|
|
447
|
+
if (hooksResult.skipped.length > 0) {
|
|
448
|
+
console.log(` Skipped: ${hooksResult.skipped.join(', ')}`);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Show manual action required
|
|
452
|
+
if (hooksResult.manualRequired.length > 0) {
|
|
453
|
+
console.log('\nā ļø Manual review recommended:');
|
|
454
|
+
for (const msg of hooksResult.manualRequired) {
|
|
455
|
+
console.log(` ⢠${msg}`);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Model status
|
|
460
|
+
if (modelDownloaded) {
|
|
461
|
+
console.log('\nš¦ Model: Downloaded and ready');
|
|
462
|
+
} else if (skipModel) {
|
|
463
|
+
console.log('\nš¦ Model: Skipped (will download on first use)');
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
console.log('\nš Restart your AI tool to activate.');
|
|
467
|
+
console.log('š” Open web viewer: npx agentkits-memory-web');
|
|
468
|
+
console.log('š Show hooks config: npx agentkits-memory-setup --show-hooks\n');
|
|
469
|
+
|
|
470
|
+
// Show manual hook instructions if some hooks couldn't be added
|
|
471
|
+
const allHookEvents = Object.keys(MEMORY_HOOKS);
|
|
472
|
+
const addedEvents = hooksResult.added.map((h) => h.replace(/ \(.*\)$/, ''));
|
|
473
|
+
const missingEvents = allHookEvents.filter(
|
|
474
|
+
(e) => !addedEvents.includes(e) && !hooksResult.skipped.some((s) => s.startsWith(e))
|
|
475
|
+
);
|
|
476
|
+
|
|
477
|
+
if (missingEvents.length > 0) {
|
|
478
|
+
console.log('ā'.repeat(60));
|
|
479
|
+
console.log('š MANUAL SETUP REQUIRED\n');
|
|
480
|
+
console.log(`Some hooks could not be auto-configured.`);
|
|
481
|
+
console.log(`Missing: ${missingEvents.join(', ')}\n`);
|
|
482
|
+
console.log(`To add manually:`);
|
|
483
|
+
console.log(`1. Open: ${settingsPath}`);
|
|
484
|
+
console.log(`2. Add/merge the following into the "hooks" section:\n`);
|
|
485
|
+
|
|
486
|
+
// Generate copy-paste JSON for missing hooks only
|
|
487
|
+
const missingHooksJson: Record<string, HookEntry[]> = {};
|
|
488
|
+
for (const event of missingEvents) {
|
|
489
|
+
const hookConfig = MEMORY_HOOKS[event];
|
|
490
|
+
if (hookConfig) {
|
|
491
|
+
missingHooksJson[event] = hookConfig;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
console.log(JSON.stringify(missingHooksJson, null, 2));
|
|
496
|
+
console.log('\nā'.repeat(60));
|
|
497
|
+
}
|
|
187
498
|
}
|
|
188
499
|
} catch (error) {
|
|
189
500
|
const result = {
|
package/src/cli/viewer.ts
CHANGED
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
|
|
21
21
|
import * as fs from 'node:fs';
|
|
22
22
|
import * as path from 'node:path';
|
|
23
|
-
import
|
|
24
|
-
import
|
|
23
|
+
import Database from 'better-sqlite3';
|
|
24
|
+
import type { Database as BetterDatabase } from 'better-sqlite3';
|
|
25
25
|
|
|
26
26
|
const args = process.argv.slice(2);
|
|
27
27
|
const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
@@ -37,54 +37,45 @@ function parseArgs(): Record<string, string | boolean> {
|
|
|
37
37
|
return parsed;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
const require = createRequire(import.meta.url);
|
|
42
|
-
const sqlJsPath = require.resolve('sql.js');
|
|
43
|
-
|
|
44
|
-
const SQL = await initSqlJs({
|
|
45
|
-
locateFile: (file: string) => path.join(path.dirname(sqlJsPath), file),
|
|
46
|
-
});
|
|
47
|
-
|
|
40
|
+
function loadDatabase(): BetterDatabase | null {
|
|
48
41
|
const dbPath = path.join(projectDir, '.claude/memory/memory.db');
|
|
49
42
|
|
|
50
43
|
if (fs.existsSync(dbPath)) {
|
|
51
|
-
|
|
52
|
-
return new SQL.Database(new Uint8Array(buffer));
|
|
44
|
+
return new Database(dbPath);
|
|
53
45
|
} else {
|
|
54
46
|
console.log(`\nš No database found at: ${dbPath}\n`);
|
|
55
47
|
console.log('Run Claude Code with memory MCP server to create entries.');
|
|
56
|
-
|
|
48
|
+
return null;
|
|
57
49
|
}
|
|
58
50
|
}
|
|
59
51
|
|
|
60
|
-
|
|
52
|
+
function main() {
|
|
61
53
|
const options = parseArgs();
|
|
62
54
|
const limit = parseInt(options.limit as string, 10) || 20;
|
|
63
55
|
const namespace = options.namespace as string | undefined;
|
|
64
56
|
const asJson = !!options.json;
|
|
65
57
|
|
|
66
58
|
try {
|
|
67
|
-
const db =
|
|
59
|
+
const db = loadDatabase();
|
|
60
|
+
if (!db) {
|
|
61
|
+
process.exit(0);
|
|
62
|
+
}
|
|
68
63
|
|
|
69
64
|
if (options.stats) {
|
|
70
65
|
// Get stats
|
|
71
|
-
const
|
|
72
|
-
const total =
|
|
66
|
+
const totalRow = db.prepare('SELECT COUNT(*) as count FROM memory_entries').get() as { count: number };
|
|
67
|
+
const total = totalRow?.count || 0;
|
|
73
68
|
|
|
74
|
-
const
|
|
69
|
+
const nsRows = db.prepare('SELECT namespace, COUNT(*) as count FROM memory_entries GROUP BY namespace').all() as { namespace: string; count: number }[];
|
|
75
70
|
const byNamespace: Record<string, number> = {};
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
byNamespace[row[0] as string] = row[1] as number;
|
|
79
|
-
}
|
|
71
|
+
for (const row of nsRows) {
|
|
72
|
+
byNamespace[row.namespace] = row.count;
|
|
80
73
|
}
|
|
81
74
|
|
|
82
|
-
const
|
|
75
|
+
const typeRows = db.prepare('SELECT type, COUNT(*) as count FROM memory_entries GROUP BY type').all() as { type: string; count: number }[];
|
|
83
76
|
const byType: Record<string, number> = {};
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
byType[row[0] as string] = row[1] as number;
|
|
87
|
-
}
|
|
77
|
+
for (const row of typeRows) {
|
|
78
|
+
byType[row.type] = row.count;
|
|
88
79
|
}
|
|
89
80
|
|
|
90
81
|
if (asJson) {
|
|
@@ -107,32 +98,23 @@ async function main() {
|
|
|
107
98
|
}
|
|
108
99
|
|
|
109
100
|
if (options.export) {
|
|
110
|
-
const
|
|
111
|
-
if (
|
|
101
|
+
const rows = db.prepare('SELECT * FROM memory_entries').all() as Record<string, unknown>[];
|
|
102
|
+
if (rows.length === 0) {
|
|
112
103
|
console.log('No entries to export.');
|
|
113
104
|
db.close();
|
|
114
105
|
return;
|
|
115
106
|
}
|
|
116
107
|
|
|
117
|
-
const columns = result[0].columns;
|
|
118
|
-
const entries = result[0].values.map(row => {
|
|
119
|
-
const entry: Record<string, unknown> = {};
|
|
120
|
-
columns.forEach((col, i) => {
|
|
121
|
-
entry[col] = row[i];
|
|
122
|
-
});
|
|
123
|
-
return entry;
|
|
124
|
-
});
|
|
125
|
-
|
|
126
108
|
const filename = `memory-export-${Date.now()}.json`;
|
|
127
|
-
fs.writeFileSync(filename, JSON.stringify({ entries, exportedAt: new Date().toISOString() }, null, 2));
|
|
128
|
-
console.log(`ā Exported ${
|
|
109
|
+
fs.writeFileSync(filename, JSON.stringify({ entries: rows, exportedAt: new Date().toISOString() }, null, 2));
|
|
110
|
+
console.log(`ā Exported ${rows.length} entries to ${filename}`);
|
|
129
111
|
db.close();
|
|
130
112
|
return;
|
|
131
113
|
}
|
|
132
114
|
|
|
133
115
|
// Default: list entries
|
|
134
116
|
let query = 'SELECT id, key, content, type, namespace, tags, created_at FROM memory_entries';
|
|
135
|
-
const params: string[] = [];
|
|
117
|
+
const params: (string | number)[] = [];
|
|
136
118
|
|
|
137
119
|
if (namespace) {
|
|
138
120
|
query += ' WHERE namespace = ?';
|
|
@@ -140,12 +122,9 @@ async function main() {
|
|
|
140
122
|
}
|
|
141
123
|
|
|
142
124
|
query += ' ORDER BY created_at DESC LIMIT ?';
|
|
143
|
-
params.push(
|
|
125
|
+
params.push(limit);
|
|
144
126
|
|
|
145
|
-
const
|
|
146
|
-
stmt.bind(params);
|
|
147
|
-
|
|
148
|
-
const entries: Array<{
|
|
127
|
+
const entries = db.prepare(query).all(...params) as {
|
|
149
128
|
id: string;
|
|
150
129
|
key: string;
|
|
151
130
|
content: string;
|
|
@@ -153,21 +132,7 @@ async function main() {
|
|
|
153
132
|
namespace: string;
|
|
154
133
|
tags: string;
|
|
155
134
|
created_at: number;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
while (stmt.step()) {
|
|
159
|
-
const row = stmt.getAsObject();
|
|
160
|
-
entries.push({
|
|
161
|
-
id: row.id as string,
|
|
162
|
-
key: row.key as string,
|
|
163
|
-
content: row.content as string,
|
|
164
|
-
type: row.type as string,
|
|
165
|
-
namespace: row.namespace as string,
|
|
166
|
-
tags: row.tags as string,
|
|
167
|
-
created_at: row.created_at as number,
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
stmt.free();
|
|
135
|
+
}[];
|
|
171
136
|
|
|
172
137
|
if (entries.length === 0) {
|
|
173
138
|
console.log('\nš No memories found in database.\n');
|
|
@@ -201,8 +166,8 @@ async function main() {
|
|
|
201
166
|
}
|
|
202
167
|
|
|
203
168
|
// Get total count
|
|
204
|
-
const
|
|
205
|
-
const totalCount =
|
|
169
|
+
const countRow = db.prepare('SELECT COUNT(*) as count FROM memory_entries').get() as { count: number };
|
|
170
|
+
const totalCount = countRow?.count || entries.length;
|
|
206
171
|
|
|
207
172
|
console.log(`\nShowing ${entries.length} of ${totalCount} total entries`);
|
|
208
173
|
console.log('Use --limit=N to see more, --namespace=X to filter\n');
|