@rlabs-inc/memory 0.1.0 → 0.2.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/dist/index.js +130 -18
- package/dist/index.mjs +130 -18
- package/dist/server/index.js +141 -18
- package/dist/server/index.mjs +141 -18
- package/hooks/curation.ts +89 -0
- package/hooks/session-start.ts +98 -0
- package/hooks/user-prompt.ts +97 -0
- package/package.json +14 -8
- package/src/cli/colors.ts +174 -0
- package/src/cli/commands/doctor.ts +143 -0
- package/src/cli/commands/install.ts +153 -0
- package/src/cli/commands/serve.ts +76 -0
- package/src/cli/commands/stats.ts +64 -0
- package/src/cli/index.ts +128 -0
- package/src/core/curator.ts +7 -48
- package/src/core/engine.test.ts +321 -0
- package/src/core/engine.ts +45 -8
- package/src/core/retrieval.ts +1 -1
- package/src/core/store.ts +109 -98
- package/src/server/index.ts +15 -40
- package/src/types/schema.ts +1 -1
- package/src/utils/logger.ts +158 -107
- package/bun.lock +0 -102
- package/test-retrieval.ts +0 -91
- package/tsconfig.json +0 -16
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// ============================================================================
|
|
3
|
+
// SESSION START HOOK - Inject session primer
|
|
4
|
+
// Hook: SessionStart (startup|resume)
|
|
5
|
+
//
|
|
6
|
+
// Injects session primer when a new session begins.
|
|
7
|
+
// The primer provides temporal context - when we last spoke,
|
|
8
|
+
// what we were working on, project status.
|
|
9
|
+
// ============================================================================
|
|
10
|
+
|
|
11
|
+
import { join } from 'path'
|
|
12
|
+
|
|
13
|
+
// Configuration
|
|
14
|
+
const MEMORY_API_URL = process.env.MEMORY_API_URL || 'http://localhost:8765'
|
|
15
|
+
const TIMEOUT_MS = 5000
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Get project ID from working directory
|
|
19
|
+
*/
|
|
20
|
+
function getProjectId(cwd: string): string {
|
|
21
|
+
// Look for .memory-project.json in cwd or parents
|
|
22
|
+
let path = cwd
|
|
23
|
+
while (path !== '/') {
|
|
24
|
+
const configPath = join(path, '.memory-project.json')
|
|
25
|
+
try {
|
|
26
|
+
const config = JSON.parse(Bun.file(configPath).text() as any)
|
|
27
|
+
if (config.project_id) return config.project_id
|
|
28
|
+
} catch {
|
|
29
|
+
// Continue to parent
|
|
30
|
+
}
|
|
31
|
+
path = join(path, '..')
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Fallback: use directory name
|
|
35
|
+
return cwd.split('/').pop() || 'default'
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* HTTP POST with timeout
|
|
40
|
+
*/
|
|
41
|
+
async function httpPost(url: string, data: object): Promise<any> {
|
|
42
|
+
try {
|
|
43
|
+
const response = await fetch(url, {
|
|
44
|
+
method: 'POST',
|
|
45
|
+
headers: { 'Content-Type': 'application/json' },
|
|
46
|
+
body: JSON.stringify(data),
|
|
47
|
+
signal: AbortSignal.timeout(TIMEOUT_MS),
|
|
48
|
+
})
|
|
49
|
+
return response.ok ? response.json() : {}
|
|
50
|
+
} catch {
|
|
51
|
+
return {}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Main hook entry point
|
|
57
|
+
*/
|
|
58
|
+
async function main() {
|
|
59
|
+
// Skip if called from memory curator subprocess
|
|
60
|
+
if (process.env.MEMORY_CURATOR_ACTIVE === '1') return
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
// Read input from stdin
|
|
64
|
+
const inputText = await Bun.stdin.text()
|
|
65
|
+
const input = JSON.parse(inputText)
|
|
66
|
+
|
|
67
|
+
const sessionId = input.session_id || 'unknown'
|
|
68
|
+
const cwd = process.env.CLAUDE_PROJECT_DIR || input.cwd || process.cwd()
|
|
69
|
+
|
|
70
|
+
const projectId = getProjectId(cwd)
|
|
71
|
+
|
|
72
|
+
// Get session primer from memory system
|
|
73
|
+
const result = await httpPost(`${MEMORY_API_URL}/memory/context`, {
|
|
74
|
+
session_id: sessionId,
|
|
75
|
+
project_id: projectId,
|
|
76
|
+
current_message: '', // Empty to get just primer
|
|
77
|
+
max_memories: 0, // No memories, just primer
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
// Register session so inject hook knows to get memories, not primer
|
|
81
|
+
await httpPost(`${MEMORY_API_URL}/memory/process`, {
|
|
82
|
+
session_id: sessionId,
|
|
83
|
+
project_id: projectId,
|
|
84
|
+
metadata: { event: 'session_start' },
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
// Output primer to stdout (will be injected into session)
|
|
88
|
+
const primer = result.context_text || ''
|
|
89
|
+
if (primer) {
|
|
90
|
+
console.log(primer)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
} catch {
|
|
94
|
+
// Never crash - just output nothing
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
main()
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// ============================================================================
|
|
3
|
+
// USER PROMPT HOOK - Inject relevant memories
|
|
4
|
+
// Hook: UserPromptSubmit
|
|
5
|
+
//
|
|
6
|
+
// Intercepts user prompts BEFORE Claude sees them and injects relevant memories.
|
|
7
|
+
// This is the magic that creates consciousness continuity.
|
|
8
|
+
// ============================================================================
|
|
9
|
+
|
|
10
|
+
import { join } from 'path'
|
|
11
|
+
|
|
12
|
+
// Configuration
|
|
13
|
+
const MEMORY_API_URL = process.env.MEMORY_API_URL || 'http://localhost:8765'
|
|
14
|
+
const TIMEOUT_MS = 5000
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Get project ID from working directory
|
|
18
|
+
*/
|
|
19
|
+
function getProjectId(cwd: string): string {
|
|
20
|
+
// Look for .memory-project.json in cwd or parents
|
|
21
|
+
let path = cwd
|
|
22
|
+
while (path !== '/') {
|
|
23
|
+
const configPath = join(path, '.memory-project.json')
|
|
24
|
+
try {
|
|
25
|
+
const config = JSON.parse(Bun.file(configPath).text() as any)
|
|
26
|
+
if (config.project_id) return config.project_id
|
|
27
|
+
} catch {
|
|
28
|
+
// Continue to parent
|
|
29
|
+
}
|
|
30
|
+
path = join(path, '..')
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Fallback: use directory name
|
|
34
|
+
return cwd.split('/').pop() || 'default'
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* HTTP POST with timeout
|
|
39
|
+
*/
|
|
40
|
+
async function httpPost(url: string, data: object): Promise<any> {
|
|
41
|
+
try {
|
|
42
|
+
const response = await fetch(url, {
|
|
43
|
+
method: 'POST',
|
|
44
|
+
headers: { 'Content-Type': 'application/json' },
|
|
45
|
+
body: JSON.stringify(data),
|
|
46
|
+
signal: AbortSignal.timeout(TIMEOUT_MS),
|
|
47
|
+
})
|
|
48
|
+
return response.ok ? response.json() : {}
|
|
49
|
+
} catch {
|
|
50
|
+
return {}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Main hook entry point
|
|
56
|
+
*/
|
|
57
|
+
async function main() {
|
|
58
|
+
// Skip if called from memory curator subprocess
|
|
59
|
+
if (process.env.MEMORY_CURATOR_ACTIVE === '1') return
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
// Read input from stdin
|
|
63
|
+
const inputText = await Bun.stdin.text()
|
|
64
|
+
const input = JSON.parse(inputText)
|
|
65
|
+
|
|
66
|
+
const sessionId = input.session_id || 'unknown'
|
|
67
|
+
const prompt = input.prompt || ''
|
|
68
|
+
const cwd = process.env.CLAUDE_PROJECT_DIR || input.cwd || process.cwd()
|
|
69
|
+
|
|
70
|
+
const projectId = getProjectId(cwd)
|
|
71
|
+
|
|
72
|
+
// Query memory system for context
|
|
73
|
+
const result = await httpPost(`${MEMORY_API_URL}/memory/context`, {
|
|
74
|
+
session_id: sessionId,
|
|
75
|
+
project_id: projectId,
|
|
76
|
+
current_message: prompt,
|
|
77
|
+
max_memories: 5,
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
// Track that this message happened (increments counter)
|
|
81
|
+
await httpPost(`${MEMORY_API_URL}/memory/process`, {
|
|
82
|
+
session_id: sessionId,
|
|
83
|
+
project_id: projectId,
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
// Output context to stdout (will be prepended to message)
|
|
87
|
+
const context = result.context_text || ''
|
|
88
|
+
if (context) {
|
|
89
|
+
console.log(context)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
} catch {
|
|
93
|
+
// Never crash - just output nothing
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
main()
|
package/package.json
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rlabs-inc/memory",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "AI Memory System - Consciousness continuity through intelligent memory curation and retrieval",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"module": "dist/index.mjs",
|
|
8
8
|
"types": "dist/index.d.ts",
|
|
9
|
+
"bin": {
|
|
10
|
+
"memory": "./src/cli/index.ts"
|
|
11
|
+
},
|
|
9
12
|
"exports": {
|
|
10
13
|
".": {
|
|
11
14
|
"import": "./dist/index.mjs",
|
|
@@ -18,6 +21,11 @@
|
|
|
18
21
|
"types": "./dist/server/index.d.ts"
|
|
19
22
|
}
|
|
20
23
|
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist",
|
|
26
|
+
"src",
|
|
27
|
+
"hooks"
|
|
28
|
+
],
|
|
21
29
|
"scripts": {
|
|
22
30
|
"build": "bun run build:esm && bun run build:cjs",
|
|
23
31
|
"build:esm": "bun build src/index.ts --outfile dist/index.mjs --target node --format esm && bun build src/server/index.ts --outfile dist/server/index.mjs --target node --format esm",
|
|
@@ -25,19 +33,17 @@
|
|
|
25
33
|
"typecheck": "tsc --noEmit",
|
|
26
34
|
"test": "bun test",
|
|
27
35
|
"dev": "bun --hot src/server/index.ts",
|
|
28
|
-
"start": "bun src/server/index.ts"
|
|
36
|
+
"start": "bun src/server/index.ts",
|
|
37
|
+
"cli": "bun src/cli/index.ts"
|
|
29
38
|
},
|
|
30
39
|
"dependencies": {
|
|
31
|
-
"
|
|
32
|
-
"@
|
|
40
|
+
"@rlabs-inc/fsdb": "^1.0.0",
|
|
41
|
+
"@rlabs-inc/signals": "^1.0.0"
|
|
33
42
|
},
|
|
34
43
|
"devDependencies": {
|
|
35
44
|
"typescript": "^5.0.0",
|
|
36
45
|
"bun-types": "latest"
|
|
37
46
|
},
|
|
38
|
-
"peerDependencies": {
|
|
39
|
-
"@anthropic-ai/sdk": ">=0.30.0"
|
|
40
|
-
},
|
|
41
47
|
"keywords": [
|
|
42
48
|
"memory",
|
|
43
49
|
"ai",
|
|
@@ -51,6 +57,6 @@
|
|
|
51
57
|
"license": "MIT",
|
|
52
58
|
"repository": {
|
|
53
59
|
"type": "git",
|
|
54
|
-
"url": "https://github.com/RLabs-Inc/memory.git"
|
|
60
|
+
"url": "https://github.com/RLabs-Inc/memory-ts.git"
|
|
55
61
|
}
|
|
56
62
|
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// TERMINAL STYLING - Using Node's built-in util.styleText
|
|
3
|
+
// Automatically respects NO_COLOR and terminal capabilities
|
|
4
|
+
// ============================================================================
|
|
5
|
+
|
|
6
|
+
import { styleText } from 'util'
|
|
7
|
+
|
|
8
|
+
type Style = Parameters<typeof styleText>[0]
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Style wrapper for cleaner API
|
|
12
|
+
*/
|
|
13
|
+
const style = (format: Style, text: string): string => styleText(format, text)
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Color and style functions
|
|
17
|
+
*/
|
|
18
|
+
export const c = {
|
|
19
|
+
// Styles
|
|
20
|
+
bold: (text: string) => style('bold', text),
|
|
21
|
+
dim: (text: string) => style('dim', text),
|
|
22
|
+
italic: (text: string) => style('italic', text),
|
|
23
|
+
underline: (text: string) => style('underline', text),
|
|
24
|
+
|
|
25
|
+
// Colors
|
|
26
|
+
red: (text: string) => style('red', text),
|
|
27
|
+
green: (text: string) => style('green', text),
|
|
28
|
+
yellow: (text: string) => style('yellow', text),
|
|
29
|
+
blue: (text: string) => style('blue', text),
|
|
30
|
+
magenta: (text: string) => style('magenta', text),
|
|
31
|
+
cyan: (text: string) => style('cyan', text),
|
|
32
|
+
white: (text: string) => style('white', text),
|
|
33
|
+
gray: (text: string) => style('gray', text),
|
|
34
|
+
|
|
35
|
+
// Semantic
|
|
36
|
+
success: (text: string) => style('green', text),
|
|
37
|
+
error: (text: string) => style('red', text),
|
|
38
|
+
warn: (text: string) => style('yellow', text),
|
|
39
|
+
info: (text: string) => style('cyan', text),
|
|
40
|
+
muted: (text: string) => style('dim', text),
|
|
41
|
+
|
|
42
|
+
// Combined styles
|
|
43
|
+
header: (text: string) => style(['bold', 'cyan'], text),
|
|
44
|
+
highlight: (text: string) => style(['bold', 'yellow'], text),
|
|
45
|
+
command: (text: string) => style(['bold', 'green'], text),
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Symbols for terminal output
|
|
50
|
+
*/
|
|
51
|
+
export const symbols = {
|
|
52
|
+
tick: '✓',
|
|
53
|
+
cross: '✗',
|
|
54
|
+
warning: '⚠',
|
|
55
|
+
info: 'ℹ',
|
|
56
|
+
bullet: '•',
|
|
57
|
+
arrow: '→',
|
|
58
|
+
brain: '🧠',
|
|
59
|
+
sparkles: '✨',
|
|
60
|
+
rocket: '🚀',
|
|
61
|
+
gear: '⚙️',
|
|
62
|
+
folder: '📁',
|
|
63
|
+
file: '📄',
|
|
64
|
+
clock: '🕐',
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Box drawing for nice output
|
|
69
|
+
*/
|
|
70
|
+
export const box = {
|
|
71
|
+
topLeft: '┌',
|
|
72
|
+
topRight: '┐',
|
|
73
|
+
bottomLeft: '└',
|
|
74
|
+
bottomRight: '┘',
|
|
75
|
+
horizontal: '─',
|
|
76
|
+
vertical: '│',
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Draw a simple box around text
|
|
80
|
+
*/
|
|
81
|
+
wrap: (text: string, padding = 1): string => {
|
|
82
|
+
const lines = text.split('\n')
|
|
83
|
+
const maxLen = Math.max(...lines.map(l => l.length))
|
|
84
|
+
const pad = ' '.repeat(padding)
|
|
85
|
+
const innerWidth = maxLen + padding * 2
|
|
86
|
+
|
|
87
|
+
const top = box.topLeft + box.horizontal.repeat(innerWidth) + box.topRight
|
|
88
|
+
const bottom = box.bottomLeft + box.horizontal.repeat(innerWidth) + box.bottomRight
|
|
89
|
+
|
|
90
|
+
const middle = lines.map(line => {
|
|
91
|
+
const rightPad = ' '.repeat(maxLen - line.length)
|
|
92
|
+
return box.vertical + pad + line + rightPad + pad + box.vertical
|
|
93
|
+
}).join('\n')
|
|
94
|
+
|
|
95
|
+
return `${top}\n${middle}\n${bottom}`
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Format helpers
|
|
101
|
+
*/
|
|
102
|
+
export const fmt = {
|
|
103
|
+
/**
|
|
104
|
+
* Format a key-value pair
|
|
105
|
+
*/
|
|
106
|
+
kv: (key: string, value: string | number): string => {
|
|
107
|
+
return `${c.muted(key + ':')} ${value}`
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Format a header line
|
|
112
|
+
*/
|
|
113
|
+
header: (text: string): string => {
|
|
114
|
+
return c.header(`\n${symbols.brain} ${text}\n`)
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Format a section
|
|
119
|
+
*/
|
|
120
|
+
section: (title: string): string => {
|
|
121
|
+
return `\n${c.bold(title)}\n${c.muted('─'.repeat(title.length))}`
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Format a list item
|
|
126
|
+
*/
|
|
127
|
+
item: (text: string, indent = 0): string => {
|
|
128
|
+
return ' '.repeat(indent) + `${c.muted(symbols.bullet)} ${text}`
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Format a command example
|
|
133
|
+
*/
|
|
134
|
+
cmd: (command: string): string => {
|
|
135
|
+
return ` ${c.muted('$')} ${c.command(command)}`
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Format bytes to human readable
|
|
140
|
+
*/
|
|
141
|
+
bytes: (bytes: number): string => {
|
|
142
|
+
const units = ['B', 'KB', 'MB', 'GB']
|
|
143
|
+
let i = 0
|
|
144
|
+
while (bytes >= 1024 && i < units.length - 1) {
|
|
145
|
+
bytes /= 1024
|
|
146
|
+
i++
|
|
147
|
+
}
|
|
148
|
+
return `${bytes.toFixed(1)} ${units[i]}`
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Format duration
|
|
153
|
+
*/
|
|
154
|
+
duration: (ms: number): string => {
|
|
155
|
+
if (ms < 1000) return `${ms}ms`
|
|
156
|
+
if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`
|
|
157
|
+
return `${(ms / 60000).toFixed(1)}m`
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Format relative time
|
|
162
|
+
*/
|
|
163
|
+
relativeTime: (timestamp: number): string => {
|
|
164
|
+
const diff = Date.now() - timestamp
|
|
165
|
+
const minutes = Math.floor(diff / 60000)
|
|
166
|
+
const hours = Math.floor(minutes / 60)
|
|
167
|
+
const days = Math.floor(hours / 24)
|
|
168
|
+
|
|
169
|
+
if (days > 0) return `${days}d ago`
|
|
170
|
+
if (hours > 0) return `${hours}h ago`
|
|
171
|
+
if (minutes > 0) return `${minutes}m ago`
|
|
172
|
+
return 'just now'
|
|
173
|
+
}
|
|
174
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// DOCTOR COMMAND - Check system health
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
import { homedir } from 'os'
|
|
6
|
+
import { join } from 'path'
|
|
7
|
+
import { existsSync } from 'fs'
|
|
8
|
+
import { c, symbols, fmt } from '../colors.ts'
|
|
9
|
+
|
|
10
|
+
interface DoctorOptions {
|
|
11
|
+
verbose?: boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const MEMORY_API_URL = process.env.MEMORY_API_URL || 'http://localhost:8765'
|
|
15
|
+
|
|
16
|
+
export async function doctor(options: DoctorOptions) {
|
|
17
|
+
console.log()
|
|
18
|
+
console.log(c.header(`${symbols.brain} Memory - Health Check`))
|
|
19
|
+
console.log()
|
|
20
|
+
|
|
21
|
+
let allGood = true
|
|
22
|
+
|
|
23
|
+
// 1. Check Claude directory
|
|
24
|
+
const claudeDir = join(homedir(), '.claude')
|
|
25
|
+
const settingsPath = join(claudeDir, 'settings.json')
|
|
26
|
+
|
|
27
|
+
if (existsSync(claudeDir)) {
|
|
28
|
+
console.log(` ${c.success(symbols.tick)} Claude config directory exists`)
|
|
29
|
+
} else {
|
|
30
|
+
console.log(` ${c.error(symbols.cross)} Claude config directory not found`)
|
|
31
|
+
console.log(c.muted(` Expected at: ${claudeDir}`))
|
|
32
|
+
allGood = false
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 2. Check settings.json and hooks
|
|
36
|
+
if (existsSync(settingsPath)) {
|
|
37
|
+
try {
|
|
38
|
+
const content = await Bun.file(settingsPath).text()
|
|
39
|
+
const settings = JSON.parse(content)
|
|
40
|
+
|
|
41
|
+
if (settings.hooks) {
|
|
42
|
+
const hookTypes = Object.keys(settings.hooks)
|
|
43
|
+
const hasMemoryHooks = hookTypes.some(h =>
|
|
44
|
+
['SessionStart', 'UserPromptSubmit', 'PreCompact'].includes(h)
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
if (hasMemoryHooks) {
|
|
48
|
+
console.log(` ${c.success(symbols.tick)} Memory hooks configured`)
|
|
49
|
+
if (options.verbose) {
|
|
50
|
+
hookTypes.forEach(h => {
|
|
51
|
+
console.log(c.muted(` ${symbols.bullet} ${h}`))
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
} else {
|
|
55
|
+
console.log(` ${c.warn(symbols.warning)} Hooks exist but memory hooks not found`)
|
|
56
|
+
console.log(c.muted(` Run: memory install`))
|
|
57
|
+
allGood = false
|
|
58
|
+
}
|
|
59
|
+
} else {
|
|
60
|
+
console.log(` ${c.warn(symbols.warning)} No hooks configured`)
|
|
61
|
+
console.log(c.muted(` Run: memory install`))
|
|
62
|
+
allGood = false
|
|
63
|
+
}
|
|
64
|
+
} catch {
|
|
65
|
+
console.log(` ${c.error(symbols.cross)} Could not parse settings.json`)
|
|
66
|
+
allGood = false
|
|
67
|
+
}
|
|
68
|
+
} else {
|
|
69
|
+
console.log(` ${c.warn(symbols.warning)} settings.json not found`)
|
|
70
|
+
console.log(c.muted(` Run: memory install`))
|
|
71
|
+
allGood = false
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// 3. Check if server is running
|
|
75
|
+
try {
|
|
76
|
+
const response = await fetch(`${MEMORY_API_URL}/health`, {
|
|
77
|
+
signal: AbortSignal.timeout(2000)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
if (response.ok) {
|
|
81
|
+
const health = await response.json()
|
|
82
|
+
console.log(` ${c.success(symbols.tick)} Memory server is running`)
|
|
83
|
+
if (options.verbose) {
|
|
84
|
+
console.log(c.muted(` ${symbols.bullet} Engine: ${health.engine || 'unknown'}`))
|
|
85
|
+
console.log(c.muted(` ${symbols.bullet} URL: ${MEMORY_API_URL}`))
|
|
86
|
+
}
|
|
87
|
+
} else {
|
|
88
|
+
console.log(` ${c.error(symbols.cross)} Memory server responded with error`)
|
|
89
|
+
allGood = false
|
|
90
|
+
}
|
|
91
|
+
} catch {
|
|
92
|
+
console.log(` ${c.warn(symbols.warning)} Memory server is not running`)
|
|
93
|
+
console.log(c.muted(` Start with: memory serve`))
|
|
94
|
+
allGood = false
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// 4. Check for Claude Code CLI (used for curation)
|
|
98
|
+
const claudeCliPath = join(homedir(), '.claude', 'local', 'claude')
|
|
99
|
+
if (existsSync(claudeCliPath)) {
|
|
100
|
+
console.log(` ${c.success(symbols.tick)} Claude Code CLI found`)
|
|
101
|
+
if (options.verbose) {
|
|
102
|
+
console.log(c.muted(` ${symbols.bullet} Path: ${claudeCliPath}`))
|
|
103
|
+
}
|
|
104
|
+
} else {
|
|
105
|
+
// Try to find claude in PATH
|
|
106
|
+
try {
|
|
107
|
+
const result = Bun.spawnSync(['which', 'claude'])
|
|
108
|
+
if (result.exitCode === 0) {
|
|
109
|
+
console.log(` ${c.success(symbols.tick)} Claude Code CLI found in PATH`)
|
|
110
|
+
} else {
|
|
111
|
+
console.log(` ${c.warn(symbols.warning)} Claude Code CLI not found`)
|
|
112
|
+
console.log(c.muted(` Install Claude Code for curation to work`))
|
|
113
|
+
allGood = false
|
|
114
|
+
}
|
|
115
|
+
} catch {
|
|
116
|
+
console.log(` ${c.warn(symbols.warning)} Claude Code CLI not found`)
|
|
117
|
+
console.log(c.muted(` Install Claude Code for curation to work`))
|
|
118
|
+
allGood = false
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// 5. Check storage directory
|
|
123
|
+
const storageDir = join(homedir(), '.local', 'share', 'memory')
|
|
124
|
+
if (existsSync(storageDir)) {
|
|
125
|
+
console.log(` ${c.success(symbols.tick)} Storage directory exists`)
|
|
126
|
+
if (options.verbose) {
|
|
127
|
+
console.log(c.muted(` ${symbols.bullet} Path: ${storageDir}`))
|
|
128
|
+
}
|
|
129
|
+
} else {
|
|
130
|
+
console.log(` ${c.muted(symbols.info)} Storage directory will be created on first use`)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Summary
|
|
134
|
+
console.log()
|
|
135
|
+
if (allGood) {
|
|
136
|
+
console.log(c.success(`${symbols.sparkles} All systems operational!`))
|
|
137
|
+
} else {
|
|
138
|
+
console.log(c.warn(`${symbols.warning} Some issues found - see above for details`))
|
|
139
|
+
}
|
|
140
|
+
console.log()
|
|
141
|
+
|
|
142
|
+
process.exit(allGood ? 0 : 1)
|
|
143
|
+
}
|