@rlabs-inc/memory 0.1.0 → 0.2.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/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 +74 -0
- package/hooks/session-start.ts +82 -0
- package/hooks/user-prompt.ts +81 -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,81 @@
|
|
|
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
|
+
// Configuration
|
|
11
|
+
const MEMORY_API_URL = process.env.MEMORY_API_URL || 'http://localhost:8765'
|
|
12
|
+
const TIMEOUT_MS = 5000
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get project ID from working directory
|
|
16
|
+
*/
|
|
17
|
+
function getProjectId(cwd: string): string {
|
|
18
|
+
return cwd.split('/').pop() || 'default'
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* HTTP POST with timeout
|
|
23
|
+
*/
|
|
24
|
+
async function httpPost(url: string, data: object): Promise<any> {
|
|
25
|
+
try {
|
|
26
|
+
const response = await fetch(url, {
|
|
27
|
+
method: 'POST',
|
|
28
|
+
headers: { 'Content-Type': 'application/json' },
|
|
29
|
+
body: JSON.stringify(data),
|
|
30
|
+
signal: AbortSignal.timeout(TIMEOUT_MS),
|
|
31
|
+
})
|
|
32
|
+
return response.ok ? response.json() : {}
|
|
33
|
+
} catch {
|
|
34
|
+
return {}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Main hook entry point
|
|
40
|
+
*/
|
|
41
|
+
async function main() {
|
|
42
|
+
// Skip if called from memory curator subprocess
|
|
43
|
+
if (process.env.MEMORY_CURATOR_ACTIVE === '1') return
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
// Read input from stdin
|
|
47
|
+
const inputText = await Bun.stdin.text()
|
|
48
|
+
const input = JSON.parse(inputText)
|
|
49
|
+
|
|
50
|
+
const sessionId = input.session_id || 'unknown'
|
|
51
|
+
const prompt = input.prompt || ''
|
|
52
|
+
const cwd = process.env.CLAUDE_PROJECT_DIR || input.cwd || process.cwd()
|
|
53
|
+
|
|
54
|
+
const projectId = getProjectId(cwd)
|
|
55
|
+
|
|
56
|
+
// Query memory system for context
|
|
57
|
+
const result = await httpPost(`${MEMORY_API_URL}/memory/context`, {
|
|
58
|
+
session_id: sessionId,
|
|
59
|
+
project_id: projectId,
|
|
60
|
+
current_message: prompt,
|
|
61
|
+
max_memories: 5,
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
// Track that this message happened (increments counter)
|
|
65
|
+
await httpPost(`${MEMORY_API_URL}/memory/process`, {
|
|
66
|
+
session_id: sessionId,
|
|
67
|
+
project_id: projectId,
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
// Output context to stdout (will be prepended to message)
|
|
71
|
+
const context = result.context_text || ''
|
|
72
|
+
if (context) {
|
|
73
|
+
console.log(context)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
} catch {
|
|
77
|
+
// Never crash - just output nothing
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
main()
|
package/package.json
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rlabs-inc/memory",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
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
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// INSTALL COMMAND - Set up Claude Code hooks
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
import { homedir } from 'os'
|
|
6
|
+
import { join, dirname } from 'path'
|
|
7
|
+
import { existsSync, mkdirSync } from 'fs'
|
|
8
|
+
import { c, symbols, fmt } from '../colors.ts'
|
|
9
|
+
|
|
10
|
+
interface InstallOptions {
|
|
11
|
+
verbose?: boolean
|
|
12
|
+
force?: boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function install(options: InstallOptions) {
|
|
16
|
+
console.log()
|
|
17
|
+
console.log(c.header(`${symbols.brain} Memory - Install Hooks`))
|
|
18
|
+
console.log()
|
|
19
|
+
|
|
20
|
+
const claudeDir = join(homedir(), '.claude')
|
|
21
|
+
const settingsPath = join(claudeDir, 'settings.json')
|
|
22
|
+
|
|
23
|
+
// Find the hooks directory (relative to this CLI)
|
|
24
|
+
const cliPath = import.meta.dir
|
|
25
|
+
const packageRoot = join(cliPath, '..', '..', '..')
|
|
26
|
+
const hooksDir = join(packageRoot, 'hooks')
|
|
27
|
+
|
|
28
|
+
console.log(` ${fmt.kv('Claude config', claudeDir)}`)
|
|
29
|
+
console.log(` ${fmt.kv('Hooks source', hooksDir)}`)
|
|
30
|
+
console.log()
|
|
31
|
+
|
|
32
|
+
// Check if hooks directory exists
|
|
33
|
+
if (!existsSync(hooksDir)) {
|
|
34
|
+
console.log(c.error(` ${symbols.cross} Hooks directory not found at ${hooksDir}`))
|
|
35
|
+
console.log(c.muted(` Make sure the memory package is properly installed`))
|
|
36
|
+
process.exit(1)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Ensure .claude directory exists
|
|
40
|
+
if (!existsSync(claudeDir)) {
|
|
41
|
+
mkdirSync(claudeDir, { recursive: true })
|
|
42
|
+
console.log(` ${c.success(symbols.tick)} Created ${claudeDir}`)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Read existing settings or create new
|
|
46
|
+
let settings: any = {}
|
|
47
|
+
if (existsSync(settingsPath)) {
|
|
48
|
+
try {
|
|
49
|
+
const content = await Bun.file(settingsPath).text()
|
|
50
|
+
settings = JSON.parse(content)
|
|
51
|
+
console.log(` ${c.success(symbols.tick)} Found existing settings.json`)
|
|
52
|
+
} catch {
|
|
53
|
+
console.log(` ${c.warn(symbols.warning)} Could not parse settings.json, creating backup`)
|
|
54
|
+
const backupPath = `${settingsPath}.backup.${Date.now()}`
|
|
55
|
+
await Bun.write(backupPath, await Bun.file(settingsPath).text())
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Build hooks configuration
|
|
60
|
+
const sessionStartHook = join(hooksDir, 'session-start.ts')
|
|
61
|
+
const userPromptHook = join(hooksDir, 'user-prompt.ts')
|
|
62
|
+
const curationHook = join(hooksDir, 'curation.ts')
|
|
63
|
+
|
|
64
|
+
const hooksConfig = {
|
|
65
|
+
SessionStart: [
|
|
66
|
+
{
|
|
67
|
+
matcher: 'startup|resume',
|
|
68
|
+
hooks: [
|
|
69
|
+
{
|
|
70
|
+
type: 'command',
|
|
71
|
+
command: `bun "${sessionStartHook}"`,
|
|
72
|
+
timeout: 10
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
],
|
|
77
|
+
UserPromptSubmit: [
|
|
78
|
+
{
|
|
79
|
+
hooks: [
|
|
80
|
+
{
|
|
81
|
+
type: 'command',
|
|
82
|
+
command: `bun "${userPromptHook}"`,
|
|
83
|
+
timeout: 10
|
|
84
|
+
}
|
|
85
|
+
]
|
|
86
|
+
}
|
|
87
|
+
],
|
|
88
|
+
PreCompact: [
|
|
89
|
+
{
|
|
90
|
+
matcher: 'auto|manual',
|
|
91
|
+
hooks: [
|
|
92
|
+
{
|
|
93
|
+
type: 'command',
|
|
94
|
+
command: `bun "${curationHook}"`,
|
|
95
|
+
timeout: 120
|
|
96
|
+
}
|
|
97
|
+
]
|
|
98
|
+
}
|
|
99
|
+
],
|
|
100
|
+
SessionEnd: [
|
|
101
|
+
{
|
|
102
|
+
hooks: [
|
|
103
|
+
{
|
|
104
|
+
type: 'command',
|
|
105
|
+
command: `bun "${curationHook}"`,
|
|
106
|
+
timeout: 120
|
|
107
|
+
}
|
|
108
|
+
]
|
|
109
|
+
}
|
|
110
|
+
]
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Check for existing hooks
|
|
114
|
+
if (settings.hooks && !options.force) {
|
|
115
|
+
const existingHooks = Object.keys(settings.hooks)
|
|
116
|
+
if (existingHooks.length > 0) {
|
|
117
|
+
console.log()
|
|
118
|
+
console.log(c.warn(` ${symbols.warning} Existing hooks found: ${existingHooks.join(', ')}`))
|
|
119
|
+
console.log(c.muted(` Use --force to overwrite, or manually merge in settings.json`))
|
|
120
|
+
console.log()
|
|
121
|
+
|
|
122
|
+
// Show what would be added
|
|
123
|
+
console.log(c.bold(' Hooks to add:'))
|
|
124
|
+
console.log(c.muted(' ' + JSON.stringify(hooksConfig, null, 2).split('\n').join('\n ')))
|
|
125
|
+
console.log()
|
|
126
|
+
process.exit(1)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Merge hooks
|
|
131
|
+
settings.hooks = {
|
|
132
|
+
...settings.hooks,
|
|
133
|
+
...hooksConfig
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Write settings
|
|
137
|
+
try {
|
|
138
|
+
await Bun.write(settingsPath, JSON.stringify(settings, null, 2))
|
|
139
|
+
console.log(` ${c.success(symbols.tick)} Updated ${settingsPath}`)
|
|
140
|
+
} catch (error: any) {
|
|
141
|
+
console.log(c.error(` ${symbols.cross} Failed to write settings: ${error.message}`))
|
|
142
|
+
process.exit(1)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
console.log()
|
|
146
|
+
console.log(c.success(`${symbols.sparkles} Hooks installed successfully!`))
|
|
147
|
+
console.log()
|
|
148
|
+
console.log(c.bold('Next steps:'))
|
|
149
|
+
console.log(` 1. Start the memory server: ${c.command('memory serve')}`)
|
|
150
|
+
console.log(` 2. Open Claude Code in any project`)
|
|
151
|
+
console.log(` 3. Memories will be automatically injected`)
|
|
152
|
+
console.log()
|
|
153
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// SERVE COMMAND - Start the memory server
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
import { c, symbols, fmt, box } from '../colors.ts'
|
|
6
|
+
import { createServer } from '../../server/index.ts'
|
|
7
|
+
|
|
8
|
+
interface ServeOptions {
|
|
9
|
+
port?: string
|
|
10
|
+
verbose?: boolean
|
|
11
|
+
quiet?: boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function serve(options: ServeOptions) {
|
|
15
|
+
const port = parseInt(options.port || process.env.MEMORY_PORT || '8765')
|
|
16
|
+
const host = process.env.MEMORY_HOST || 'localhost'
|
|
17
|
+
const storageMode = (process.env.MEMORY_STORAGE_MODE || 'central') as 'central' | 'local'
|
|
18
|
+
const apiKey = process.env.ANTHROPIC_API_KEY
|
|
19
|
+
|
|
20
|
+
if (!options.quiet) {
|
|
21
|
+
console.log()
|
|
22
|
+
console.log(c.header(`${symbols.brain} Memory Server`))
|
|
23
|
+
console.log()
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const { server } = createServer({
|
|
28
|
+
port,
|
|
29
|
+
host,
|
|
30
|
+
storageMode,
|
|
31
|
+
curator: { apiKey },
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
if (!options.quiet) {
|
|
35
|
+
const url = `http://${host}:${port}`
|
|
36
|
+
|
|
37
|
+
console.log(` ${c.success(symbols.tick)} Server running at ${c.cyan(url)}`)
|
|
38
|
+
console.log()
|
|
39
|
+
console.log(` ${fmt.kv('Storage', storageMode)}`)
|
|
40
|
+
console.log(` ${fmt.kv('Curation', apiKey ? c.success('enabled') : c.warn('disabled'))}`)
|
|
41
|
+
console.log()
|
|
42
|
+
console.log(c.muted(` Press Ctrl+C to stop`))
|
|
43
|
+
console.log()
|
|
44
|
+
|
|
45
|
+
if (options.verbose) {
|
|
46
|
+
console.log(c.muted('─'.repeat(50)))
|
|
47
|
+
console.log()
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Keep process alive
|
|
52
|
+
process.on('SIGINT', () => {
|
|
53
|
+
if (!options.quiet) {
|
|
54
|
+
console.log()
|
|
55
|
+
console.log(` ${symbols.info} Shutting down...`)
|
|
56
|
+
}
|
|
57
|
+
server.stop()
|
|
58
|
+
process.exit(0)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
process.on('SIGTERM', () => {
|
|
62
|
+
server.stop()
|
|
63
|
+
process.exit(0)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
} catch (error: any) {
|
|
67
|
+
if (error.code === 'EADDRINUSE') {
|
|
68
|
+
console.error(c.error(`${symbols.cross} Port ${port} is already in use`))
|
|
69
|
+
console.log(c.muted(` Try a different port with --port <number>`))
|
|
70
|
+
console.log(c.muted(` Or check if another memory server is running`))
|
|
71
|
+
} else {
|
|
72
|
+
console.error(c.error(`${symbols.cross} Failed to start server: ${error.message}`))
|
|
73
|
+
}
|
|
74
|
+
process.exit(1)
|
|
75
|
+
}
|
|
76
|
+
}
|