claude-memory-layer 1.0.6 ā 1.0.8
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/.claude/settings.local.json +4 -1
- package/.claude-plugin/plugin.json +3 -3
- package/.history/package_20260201142928.json +46 -0
- package/.history/package_20260201192048.json +47 -0
- package/README.md +26 -26
- package/dist/.claude-plugin/plugin.json +3 -3
- package/dist/cli/index.js +1109 -25
- package/dist/cli/index.js.map +4 -4
- package/dist/core/index.js +192 -5
- package/dist/core/index.js.map +4 -4
- package/dist/hooks/session-end.js +262 -18
- package/dist/hooks/session-end.js.map +4 -4
- package/dist/hooks/session-start.js +262 -18
- package/dist/hooks/session-start.js.map +4 -4
- package/dist/hooks/stop.js +262 -18
- package/dist/hooks/stop.js.map +4 -4
- package/dist/hooks/user-prompt-submit.js +262 -18
- package/dist/hooks/user-prompt-submit.js.map +4 -4
- package/dist/server/api/index.js +4728 -0
- package/dist/server/api/index.js.map +7 -0
- package/dist/server/index.js +4790 -0
- package/dist/server/index.js.map +7 -0
- package/dist/services/memory-service.js +269 -18
- package/dist/services/memory-service.js.map +4 -4
- package/dist/ui/index.html +1225 -0
- package/package.json +4 -2
- package/scripts/build.ts +33 -3
- package/src/cli/index.ts +311 -6
- package/src/core/db-wrapper.ts +8 -1
- package/src/core/event-store.ts +52 -3
- package/src/core/graduation-worker.ts +171 -0
- package/src/core/graduation.ts +15 -2
- package/src/core/index.ts +1 -0
- package/src/core/retriever.ts +18 -0
- package/src/core/types.ts +1 -1
- package/src/mcp/index.ts +2 -2
- package/src/mcp/tools.ts +1 -1
- package/src/server/api/citations.ts +7 -3
- package/src/server/api/events.ts +7 -3
- package/src/server/api/search.ts +7 -3
- package/src/server/api/sessions.ts +7 -3
- package/src/server/api/stats.ts +175 -5
- package/src/server/index.ts +18 -9
- package/src/services/memory-service.ts +107 -19
- package/src/ui/index.html +1225 -0
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-memory-layer",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
4
4
|
"description": "Claude Code plugin that learns from conversations to provide personalized assistance",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
7
|
-
"
|
|
7
|
+
"claude-memory-layer": "dist/cli/index.js"
|
|
8
8
|
},
|
|
9
9
|
"type": "module",
|
|
10
10
|
"scripts": {
|
|
@@ -29,10 +29,12 @@
|
|
|
29
29
|
"node": ">=18.0.0"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
+
"@hono/node-server": "^1.13.0",
|
|
32
33
|
"@lancedb/lancedb": "^0.5.0",
|
|
33
34
|
"@xenova/transformers": "^2.17.0",
|
|
34
35
|
"commander": "^12.0.0",
|
|
35
36
|
"duckdb": "^0.10.0",
|
|
37
|
+
"hono": "^4.0.0",
|
|
36
38
|
"zod": "^3.22.0"
|
|
37
39
|
},
|
|
38
40
|
"devDependencies": {
|
package/scripts/build.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Build script for
|
|
2
|
+
* Build script for claude-memory-layer plugin
|
|
3
3
|
* Uses esbuild for fast bundling
|
|
4
4
|
*/
|
|
5
5
|
|
|
@@ -23,11 +23,16 @@ const commonOptions: esbuild.BuildOptions = {
|
|
|
23
23
|
format: 'esm',
|
|
24
24
|
sourcemap: true,
|
|
25
25
|
external: [
|
|
26
|
+
'@hono/node-server',
|
|
27
|
+
'@hono/node-server/serve-static',
|
|
26
28
|
'@lancedb/lancedb',
|
|
27
29
|
'@xenova/transformers',
|
|
28
30
|
'duckdb',
|
|
29
31
|
'commander',
|
|
30
|
-
'zod'
|
|
32
|
+
'zod',
|
|
33
|
+
'hono',
|
|
34
|
+
'hono/cors',
|
|
35
|
+
'hono/logger'
|
|
31
36
|
],
|
|
32
37
|
banner: {
|
|
33
38
|
js: `import { createRequire } from 'module';
|
|
@@ -40,7 +45,7 @@ const __dirname = dirname(__filename);`
|
|
|
40
45
|
};
|
|
41
46
|
|
|
42
47
|
async function build() {
|
|
43
|
-
console.log('šØ Building
|
|
48
|
+
console.log('šØ Building claude-memory-layer plugin...\n');
|
|
44
49
|
|
|
45
50
|
// Build CLI
|
|
46
51
|
console.log('š¦ Building CLI...');
|
|
@@ -83,16 +88,41 @@ async function build() {
|
|
|
83
88
|
outfile: 'dist/services/memory-service.js'
|
|
84
89
|
});
|
|
85
90
|
|
|
91
|
+
// Build server
|
|
92
|
+
console.log('š¦ Building server...');
|
|
93
|
+
await esbuild.build({
|
|
94
|
+
...commonOptions,
|
|
95
|
+
entryPoints: ['src/server/index.ts'],
|
|
96
|
+
outfile: 'dist/server/index.js',
|
|
97
|
+
external: [...(commonOptions.external || []), 'hono']
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Build server API
|
|
101
|
+
await esbuild.build({
|
|
102
|
+
...commonOptions,
|
|
103
|
+
entryPoints: ['src/server/api/index.ts'],
|
|
104
|
+
outfile: 'dist/server/api/index.js',
|
|
105
|
+
external: [...(commonOptions.external || []), 'hono']
|
|
106
|
+
});
|
|
107
|
+
|
|
86
108
|
// Copy plugin manifest
|
|
87
109
|
console.log('š Copying plugin files...');
|
|
88
110
|
fs.cpSync('.claude-plugin', path.join(outdir, '.claude-plugin'), { recursive: true });
|
|
89
111
|
|
|
112
|
+
// Copy UI files
|
|
113
|
+
console.log('š Copying UI files...');
|
|
114
|
+
if (fs.existsSync('src/ui')) {
|
|
115
|
+
fs.cpSync('src/ui', path.join(outdir, 'ui'), { recursive: true });
|
|
116
|
+
}
|
|
117
|
+
|
|
90
118
|
console.log('\nā
Build complete!');
|
|
91
119
|
console.log(`\nOutput: ${outdir}/`);
|
|
92
120
|
console.log(' - cli/index.js');
|
|
93
121
|
console.log(' - hooks/*.js');
|
|
94
122
|
console.log(' - core/index.js');
|
|
95
123
|
console.log(' - services/memory-service.js');
|
|
124
|
+
console.log(' - server/index.js');
|
|
125
|
+
console.log(' - ui/index.html');
|
|
96
126
|
console.log(' - .claude-plugin/');
|
|
97
127
|
}
|
|
98
128
|
|
package/src/cli/index.ts
CHANGED
|
@@ -5,19 +5,247 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { Command } from 'commander';
|
|
8
|
+
import { exec } from 'child_process';
|
|
9
|
+
import * as fs from 'fs';
|
|
10
|
+
import * as path from 'path';
|
|
11
|
+
import * as os from 'os';
|
|
8
12
|
import {
|
|
9
13
|
getDefaultMemoryService,
|
|
10
14
|
getMemoryServiceForProject
|
|
11
15
|
} from '../services/memory-service.js';
|
|
12
16
|
import { createSessionHistoryImporter } from '../services/session-history-importer.js';
|
|
17
|
+
import { startServer, stopServer, isServerRunning } from '../server/index.js';
|
|
18
|
+
|
|
19
|
+
// ============================================================
|
|
20
|
+
// Hook Installation Utilities
|
|
21
|
+
// ============================================================
|
|
22
|
+
|
|
23
|
+
const CLAUDE_SETTINGS_PATH = path.join(os.homedir(), '.claude', 'settings.json');
|
|
24
|
+
|
|
25
|
+
interface ClaudeSettings {
|
|
26
|
+
hooks?: {
|
|
27
|
+
UserPromptSubmit?: Array<{ matcher: string; hooks: Array<{ type: string; command: string }> }>;
|
|
28
|
+
PostToolUse?: Array<{ matcher: string; hooks: Array<{ type: string; command: string }> }>;
|
|
29
|
+
SessionStart?: Array<{ matcher: string; hooks: Array<{ type: string; command: string }> }>;
|
|
30
|
+
Stop?: Array<{ matcher: string; hooks: Array<{ type: string; command: string }> }>;
|
|
31
|
+
};
|
|
32
|
+
[key: string]: unknown;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function getPluginPath(): string {
|
|
36
|
+
// Try to find the dist directory
|
|
37
|
+
const possiblePaths = [
|
|
38
|
+
path.join(__dirname, '..'), // When running from dist/cli
|
|
39
|
+
path.join(__dirname, '../..', 'dist'), // When running from src
|
|
40
|
+
path.join(process.cwd(), 'dist'), // Current working directory
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
for (const p of possiblePaths) {
|
|
44
|
+
const hooksPath = path.join(p, 'hooks', 'user-prompt-submit.js');
|
|
45
|
+
if (fs.existsSync(hooksPath)) {
|
|
46
|
+
return p;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Fallback to npm global installation path
|
|
51
|
+
return path.join(os.homedir(), '.npm-global', 'lib', 'node_modules', 'claude-memory-layer', 'dist');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function loadClaudeSettings(): ClaudeSettings {
|
|
55
|
+
try {
|
|
56
|
+
if (fs.existsSync(CLAUDE_SETTINGS_PATH)) {
|
|
57
|
+
const content = fs.readFileSync(CLAUDE_SETTINGS_PATH, 'utf-8');
|
|
58
|
+
return JSON.parse(content);
|
|
59
|
+
}
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.error('Warning: Could not read existing settings:', error);
|
|
62
|
+
}
|
|
63
|
+
return {};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function saveClaudeSettings(settings: ClaudeSettings): void {
|
|
67
|
+
const dir = path.dirname(CLAUDE_SETTINGS_PATH);
|
|
68
|
+
if (!fs.existsSync(dir)) {
|
|
69
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Atomic write
|
|
73
|
+
const tempPath = CLAUDE_SETTINGS_PATH + '.tmp';
|
|
74
|
+
fs.writeFileSync(tempPath, JSON.stringify(settings, null, 2));
|
|
75
|
+
fs.renameSync(tempPath, CLAUDE_SETTINGS_PATH);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function getHooksConfig(pluginPath: string): ClaudeSettings['hooks'] {
|
|
79
|
+
return {
|
|
80
|
+
UserPromptSubmit: [
|
|
81
|
+
{
|
|
82
|
+
matcher: '',
|
|
83
|
+
hooks: [
|
|
84
|
+
{
|
|
85
|
+
type: 'command',
|
|
86
|
+
command: `node ${path.join(pluginPath, 'hooks', 'user-prompt-submit.js')}`
|
|
87
|
+
}
|
|
88
|
+
]
|
|
89
|
+
}
|
|
90
|
+
],
|
|
91
|
+
PostToolUse: [
|
|
92
|
+
{
|
|
93
|
+
matcher: '',
|
|
94
|
+
hooks: [
|
|
95
|
+
{
|
|
96
|
+
type: 'command',
|
|
97
|
+
command: `node ${path.join(pluginPath, 'hooks', 'post-tool-use.js')}`
|
|
98
|
+
}
|
|
99
|
+
]
|
|
100
|
+
}
|
|
101
|
+
]
|
|
102
|
+
};
|
|
103
|
+
}
|
|
13
104
|
|
|
14
105
|
const program = new Command();
|
|
15
106
|
|
|
16
107
|
program
|
|
17
|
-
.name('
|
|
108
|
+
.name('claude-memory-layer')
|
|
18
109
|
.description('Claude Code Memory Plugin CLI')
|
|
19
110
|
.version('1.0.0');
|
|
20
111
|
|
|
112
|
+
// ============================================================
|
|
113
|
+
// Install / Uninstall Commands
|
|
114
|
+
// ============================================================
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Install command - register hooks with Claude Code
|
|
118
|
+
*/
|
|
119
|
+
program
|
|
120
|
+
.command('install')
|
|
121
|
+
.description('Install hooks into Claude Code settings')
|
|
122
|
+
.option('--path <path>', 'Custom plugin path (defaults to auto-detect)')
|
|
123
|
+
.action(async (options) => {
|
|
124
|
+
try {
|
|
125
|
+
const pluginPath = options.path || getPluginPath();
|
|
126
|
+
|
|
127
|
+
// Verify hooks exist
|
|
128
|
+
const userPromptHook = path.join(pluginPath, 'hooks', 'user-prompt-submit.js');
|
|
129
|
+
if (!fs.existsSync(userPromptHook)) {
|
|
130
|
+
console.error(`\nā Hook files not found at: ${pluginPath}`);
|
|
131
|
+
console.error(' Make sure you have built the plugin with "npm run build"');
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Load existing settings
|
|
136
|
+
const settings = loadClaudeSettings();
|
|
137
|
+
|
|
138
|
+
// Add hooks (merge with existing)
|
|
139
|
+
const newHooks = getHooksConfig(pluginPath);
|
|
140
|
+
settings.hooks = {
|
|
141
|
+
...settings.hooks,
|
|
142
|
+
...newHooks
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
// Save settings
|
|
146
|
+
saveClaudeSettings(settings);
|
|
147
|
+
|
|
148
|
+
console.log('\nā
Claude Memory Layer installed!\n');
|
|
149
|
+
console.log('Hooks registered:');
|
|
150
|
+
console.log(' - UserPromptSubmit: Memory retrieval on user input');
|
|
151
|
+
console.log(' - PostToolUse: Store tool observations\n');
|
|
152
|
+
console.log('Plugin path:', pluginPath);
|
|
153
|
+
console.log('\nā ļø Restart Claude Code for changes to take effect.\n');
|
|
154
|
+
console.log('Commands:');
|
|
155
|
+
console.log(' claude-memory-layer dashboard - Open web dashboard');
|
|
156
|
+
console.log(' claude-memory-layer search - Search memories');
|
|
157
|
+
console.log(' claude-memory-layer stats - View statistics');
|
|
158
|
+
console.log(' claude-memory-layer uninstall - Remove hooks\n');
|
|
159
|
+
} catch (error) {
|
|
160
|
+
console.error('Install failed:', error);
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Uninstall command - remove hooks from Claude Code
|
|
167
|
+
*/
|
|
168
|
+
program
|
|
169
|
+
.command('uninstall')
|
|
170
|
+
.description('Remove hooks from Claude Code settings')
|
|
171
|
+
.action(async () => {
|
|
172
|
+
try {
|
|
173
|
+
// Load existing settings
|
|
174
|
+
const settings = loadClaudeSettings();
|
|
175
|
+
|
|
176
|
+
if (!settings.hooks) {
|
|
177
|
+
console.log('\nš No hooks installed.\n');
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Remove our hooks
|
|
182
|
+
delete settings.hooks.UserPromptSubmit;
|
|
183
|
+
delete settings.hooks.PostToolUse;
|
|
184
|
+
|
|
185
|
+
// Clean up empty hooks object
|
|
186
|
+
if (Object.keys(settings.hooks).length === 0) {
|
|
187
|
+
delete settings.hooks;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Save settings
|
|
191
|
+
saveClaudeSettings(settings);
|
|
192
|
+
|
|
193
|
+
console.log('\nā
Claude Memory Layer uninstalled!\n');
|
|
194
|
+
console.log('Hooks removed from Claude Code settings.');
|
|
195
|
+
console.log('Your memory data is preserved and can be accessed with:');
|
|
196
|
+
console.log(' claude-memory-layer dashboard\n');
|
|
197
|
+
console.log('ā ļø Restart Claude Code for changes to take effect.\n');
|
|
198
|
+
} catch (error) {
|
|
199
|
+
console.error('Uninstall failed:', error);
|
|
200
|
+
process.exit(1);
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Status command - check installation status
|
|
206
|
+
*/
|
|
207
|
+
program
|
|
208
|
+
.command('status')
|
|
209
|
+
.description('Check plugin installation status')
|
|
210
|
+
.action(async () => {
|
|
211
|
+
try {
|
|
212
|
+
const settings = loadClaudeSettings();
|
|
213
|
+
const pluginPath = getPluginPath();
|
|
214
|
+
|
|
215
|
+
console.log('\nš§ Claude Memory Layer Status\n');
|
|
216
|
+
|
|
217
|
+
// Check hooks
|
|
218
|
+
const hasUserPromptHook = settings.hooks?.UserPromptSubmit?.some(h =>
|
|
219
|
+
h.hooks?.some(hook => hook.command?.includes('user-prompt-submit'))
|
|
220
|
+
);
|
|
221
|
+
const hasPostToolHook = settings.hooks?.PostToolUse?.some(h =>
|
|
222
|
+
h.hooks?.some(hook => hook.command?.includes('post-tool-use'))
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
console.log('Hooks:');
|
|
226
|
+
console.log(` UserPromptSubmit: ${hasUserPromptHook ? 'ā
Installed' : 'ā Not installed'}`);
|
|
227
|
+
console.log(` PostToolUse: ${hasPostToolHook ? 'ā
Installed' : 'ā Not installed'}`);
|
|
228
|
+
|
|
229
|
+
// Check plugin files
|
|
230
|
+
const hooksExist = fs.existsSync(path.join(pluginPath, 'hooks', 'user-prompt-submit.js'));
|
|
231
|
+
console.log(`\nPlugin files: ${hooksExist ? 'ā
Found' : 'ā Not found'}`);
|
|
232
|
+
console.log(` Path: ${pluginPath}`);
|
|
233
|
+
|
|
234
|
+
// Check dashboard
|
|
235
|
+
const dashboardRunning = await isServerRunning(37777);
|
|
236
|
+
console.log(`\nDashboard: ${dashboardRunning ? 'ā
Running at http://localhost:37777' : 'ā¹ļø Not running'}`);
|
|
237
|
+
|
|
238
|
+
if (!hasUserPromptHook || !hasPostToolHook) {
|
|
239
|
+
console.log('\nš” Run "claude-memory-layer install" to set up hooks.\n');
|
|
240
|
+
} else {
|
|
241
|
+
console.log('\nā
Plugin is fully installed and configured.\n');
|
|
242
|
+
}
|
|
243
|
+
} catch (error) {
|
|
244
|
+
console.error('Status check failed:', error);
|
|
245
|
+
process.exit(1);
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
|
|
21
249
|
/**
|
|
22
250
|
* Search command
|
|
23
251
|
*/
|
|
@@ -349,7 +577,7 @@ program
|
|
|
349
577
|
console.log(`... and ${sessions.length - 20} more sessions`);
|
|
350
578
|
}
|
|
351
579
|
|
|
352
|
-
console.log('\nUse "
|
|
580
|
+
console.log('\nUse "claude-memory-layer import --session <path>" to import a specific session');
|
|
353
581
|
} catch (error) {
|
|
354
582
|
console.error('List failed:', error);
|
|
355
583
|
process.exit(1);
|
|
@@ -389,7 +617,7 @@ endlessCmd
|
|
|
389
617
|
console.log(' - Working Set: Recent context kept active');
|
|
390
618
|
console.log(' - Consolidation: Automatic memory integration');
|
|
391
619
|
console.log(' - Continuity: Seamless context transitions\n');
|
|
392
|
-
console.log('Use "
|
|
620
|
+
console.log('Use "claude-memory-layer endless status" to view current state');
|
|
393
621
|
|
|
394
622
|
await service.shutdown();
|
|
395
623
|
} catch (error) {
|
|
@@ -462,7 +690,7 @@ endlessCmd
|
|
|
462
690
|
}
|
|
463
691
|
} else {
|
|
464
692
|
console.log('Endless Mode is disabled.');
|
|
465
|
-
console.log('Use "
|
|
693
|
+
console.log('Use "claude-memory-layer endless enable" to activate.');
|
|
466
694
|
}
|
|
467
695
|
|
|
468
696
|
await service.shutdown();
|
|
@@ -488,7 +716,7 @@ endlessCmd
|
|
|
488
716
|
|
|
489
717
|
if (!service.isEndlessModeActive()) {
|
|
490
718
|
console.log('\nā ļø Endless Mode is not active');
|
|
491
|
-
console.log('Use "
|
|
719
|
+
console.log('Use "claude-memory-layer endless enable" first');
|
|
492
720
|
process.exit(1);
|
|
493
721
|
}
|
|
494
722
|
|
|
@@ -527,7 +755,7 @@ endlessCmd
|
|
|
527
755
|
|
|
528
756
|
if (!service.isEndlessModeActive()) {
|
|
529
757
|
console.log('\nā ļø Endless Mode is not active');
|
|
530
|
-
console.log('Use "
|
|
758
|
+
console.log('Use "claude-memory-layer endless enable" first');
|
|
531
759
|
process.exit(1);
|
|
532
760
|
}
|
|
533
761
|
|
|
@@ -628,4 +856,81 @@ endlessCmd
|
|
|
628
856
|
}
|
|
629
857
|
});
|
|
630
858
|
|
|
859
|
+
/**
|
|
860
|
+
* Dashboard command - start web dashboard
|
|
861
|
+
*/
|
|
862
|
+
program
|
|
863
|
+
.command('dashboard')
|
|
864
|
+
.description('Open memory dashboard in browser')
|
|
865
|
+
.option('-p, --port <port>', 'Server port', '37777')
|
|
866
|
+
.option('--no-open', 'Do not auto-open browser')
|
|
867
|
+
.action(async (options) => {
|
|
868
|
+
const port = parseInt(options.port, 10);
|
|
869
|
+
|
|
870
|
+
try {
|
|
871
|
+
// Check if server is already running
|
|
872
|
+
const running = await isServerRunning(port);
|
|
873
|
+
if (running) {
|
|
874
|
+
console.log(`\nš§ Dashboard already running at http://localhost:${port}\n`);
|
|
875
|
+
if (options.open) {
|
|
876
|
+
openBrowser(`http://localhost:${port}`);
|
|
877
|
+
}
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
// Start the server
|
|
882
|
+
console.log('\nš§ Starting Code Memory Dashboard...\n');
|
|
883
|
+
startServer(port);
|
|
884
|
+
|
|
885
|
+
// Open browser
|
|
886
|
+
if (options.open) {
|
|
887
|
+
setTimeout(() => {
|
|
888
|
+
openBrowser(`http://localhost:${port}`);
|
|
889
|
+
}, 500);
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
console.log(`\nš Dashboard: http://localhost:${port}`);
|
|
893
|
+
console.log('Press Ctrl+C to stop the server\n');
|
|
894
|
+
|
|
895
|
+
// Handle graceful shutdown
|
|
896
|
+
const shutdown = () => {
|
|
897
|
+
console.log('\n\nš Shutting down dashboard...');
|
|
898
|
+
stopServer();
|
|
899
|
+
process.exit(0);
|
|
900
|
+
};
|
|
901
|
+
|
|
902
|
+
process.on('SIGINT', shutdown);
|
|
903
|
+
process.on('SIGTERM', shutdown);
|
|
904
|
+
|
|
905
|
+
// Keep process alive
|
|
906
|
+
await new Promise(() => {});
|
|
907
|
+
} catch (error) {
|
|
908
|
+
console.error('Dashboard failed:', error);
|
|
909
|
+
process.exit(1);
|
|
910
|
+
}
|
|
911
|
+
});
|
|
912
|
+
|
|
913
|
+
/**
|
|
914
|
+
* Open URL in default browser
|
|
915
|
+
*/
|
|
916
|
+
function openBrowser(url: string): void {
|
|
917
|
+
const platform = process.platform;
|
|
918
|
+
let command: string;
|
|
919
|
+
|
|
920
|
+
if (platform === 'darwin') {
|
|
921
|
+
command = `open "${url}"`;
|
|
922
|
+
} else if (platform === 'win32') {
|
|
923
|
+
command = `start "" "${url}"`;
|
|
924
|
+
} else {
|
|
925
|
+
command = `xdg-open "${url}"`;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
exec(command, (error) => {
|
|
929
|
+
if (error) {
|
|
930
|
+
console.log(`\nā ļø Could not open browser automatically.`);
|
|
931
|
+
console.log(` Please open ${url} manually.\n`);
|
|
932
|
+
}
|
|
933
|
+
});
|
|
934
|
+
}
|
|
935
|
+
|
|
631
936
|
program.parse();
|
package/src/core/db-wrapper.ts
CHANGED
|
@@ -37,10 +37,17 @@ export function toDate(value: unknown): Date {
|
|
|
37
37
|
return new Date(String(value));
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
export interface DatabaseOptions {
|
|
41
|
+
readOnly?: boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
40
44
|
/**
|
|
41
45
|
* Creates a new DuckDB database with Promise-based API
|
|
42
46
|
*/
|
|
43
|
-
export function createDatabase(path: string): Database {
|
|
47
|
+
export function createDatabase(path: string, options?: DatabaseOptions): Database {
|
|
48
|
+
if (options?.readOnly) {
|
|
49
|
+
return new duckdb.Database(path, { access_mode: 'READ_ONLY' });
|
|
50
|
+
}
|
|
44
51
|
return new duckdb.Database(path);
|
|
45
52
|
}
|
|
46
53
|
|
package/src/core/event-store.ts
CHANGED
|
@@ -12,14 +12,20 @@ import {
|
|
|
12
12
|
OutboxItem
|
|
13
13
|
} from './types.js';
|
|
14
14
|
import { makeCanonicalKey, makeDedupeKey } from './canonical-key.js';
|
|
15
|
-
import { createDatabase, dbRun, dbAll, dbClose, toDate, type Database } from './db-wrapper.js';
|
|
15
|
+
import { createDatabase, dbRun, dbAll, dbClose, toDate, type Database, type DatabaseOptions } from './db-wrapper.js';
|
|
16
|
+
|
|
17
|
+
export interface EventStoreOptions extends DatabaseOptions {
|
|
18
|
+
// Additional options can be added here
|
|
19
|
+
}
|
|
16
20
|
|
|
17
21
|
export class EventStore {
|
|
18
22
|
private db: Database;
|
|
19
23
|
private initialized = false;
|
|
24
|
+
private readonly readOnly: boolean;
|
|
20
25
|
|
|
21
|
-
constructor(private dbPath: string) {
|
|
22
|
-
this.
|
|
26
|
+
constructor(private dbPath: string, options?: EventStoreOptions) {
|
|
27
|
+
this.readOnly = options?.readOnly ?? false;
|
|
28
|
+
this.db = createDatabase(dbPath, { readOnly: this.readOnly });
|
|
23
29
|
}
|
|
24
30
|
|
|
25
31
|
/**
|
|
@@ -28,6 +34,12 @@ export class EventStore {
|
|
|
28
34
|
async initialize(): Promise<void> {
|
|
29
35
|
if (this.initialized) return;
|
|
30
36
|
|
|
37
|
+
// In read-only mode, skip schema creation (tables already exist)
|
|
38
|
+
if (this.readOnly) {
|
|
39
|
+
this.initialized = true;
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
31
43
|
// L0 EventStore: Single Source of Truth (immutable, append-only)
|
|
32
44
|
await dbRun(this.db, `
|
|
33
45
|
CREATE TABLE IF NOT EXISTS events (
|
|
@@ -611,6 +623,43 @@ export class EventStore {
|
|
|
611
623
|
return rows;
|
|
612
624
|
}
|
|
613
625
|
|
|
626
|
+
/**
|
|
627
|
+
* Get events by memory level
|
|
628
|
+
*/
|
|
629
|
+
async getEventsByLevel(level: string, options?: { limit?: number; offset?: number }): Promise<MemoryEvent[]> {
|
|
630
|
+
await this.initialize();
|
|
631
|
+
|
|
632
|
+
const limit = options?.limit || 50;
|
|
633
|
+
const offset = options?.offset || 0;
|
|
634
|
+
|
|
635
|
+
const rows = await dbAll<Record<string, unknown>>(
|
|
636
|
+
this.db,
|
|
637
|
+
`SELECT e.* FROM events e
|
|
638
|
+
INNER JOIN memory_levels ml ON e.id = ml.event_id
|
|
639
|
+
WHERE ml.level = ?
|
|
640
|
+
ORDER BY e.timestamp DESC
|
|
641
|
+
LIMIT ? OFFSET ?`,
|
|
642
|
+
[level, limit, offset]
|
|
643
|
+
);
|
|
644
|
+
|
|
645
|
+
return rows.map(row => this.rowToEvent(row));
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/**
|
|
649
|
+
* Get memory level for a specific event
|
|
650
|
+
*/
|
|
651
|
+
async getEventLevel(eventId: string): Promise<string | null> {
|
|
652
|
+
await this.initialize();
|
|
653
|
+
|
|
654
|
+
const rows = await dbAll<{ level: string }>(
|
|
655
|
+
this.db,
|
|
656
|
+
`SELECT level FROM memory_levels WHERE event_id = ?`,
|
|
657
|
+
[eventId]
|
|
658
|
+
);
|
|
659
|
+
|
|
660
|
+
return rows.length > 0 ? rows[0].level : null;
|
|
661
|
+
}
|
|
662
|
+
|
|
614
663
|
// ============================================================
|
|
615
664
|
// Endless Mode Helper Methods
|
|
616
665
|
// ============================================================
|