@rlabs-inc/memory 0.3.2 → 0.3.5
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 +2520 -1404
- package/dist/index.mjs +2513 -1397
- package/dist/server/index.mjs +35910 -2628
- package/hooks/gemini/curation.ts +73 -0
- package/hooks/gemini/session-start.ts +72 -0
- package/hooks/gemini/user-prompt.ts +77 -0
- package/package.json +3 -2
- package/src/cli/commands/install.ts +267 -29
- package/src/cli/index.ts +8 -9
- package/src/cli/commands/install-gemini.ts +0 -146
- /package/hooks/{curation.ts → claude/curation.ts} +0 -0
- /package/hooks/{session-start.ts → claude/session-start.ts} +0 -0
- /package/hooks/{user-prompt.ts → claude/user-prompt.ts} +0 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// ============================================================================
|
|
3
|
+
// GEMINI CURATION HOOK
|
|
4
|
+
// Hook: SessionEnd / PreCompress
|
|
5
|
+
//
|
|
6
|
+
// Triggers memory curation.
|
|
7
|
+
// ============================================================================
|
|
8
|
+
|
|
9
|
+
import { styleText } from 'util'
|
|
10
|
+
|
|
11
|
+
const MEMORY_API_URL = process.env.MEMORY_API_URL || 'http://localhost:8765'
|
|
12
|
+
|
|
13
|
+
const info = (text: string) => styleText('cyan', text)
|
|
14
|
+
const success = (text: string) => styleText('green', text)
|
|
15
|
+
const warn = (text: string) => styleText('yellow', text)
|
|
16
|
+
|
|
17
|
+
function getProjectId(cwd: string): string {
|
|
18
|
+
return cwd.split('/').pop() || 'default'
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function main() {
|
|
22
|
+
if (process.env.MEMORY_CURATOR_ACTIVE === '1') return
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
const inputText = await Bun.stdin.text()
|
|
26
|
+
const input = inputText ? JSON.parse(inputText) : {}
|
|
27
|
+
|
|
28
|
+
const sessionId = input.session_id || process.env.GEMINI_SESSION_ID || 'unknown'
|
|
29
|
+
const cwd = input.cwd || process.env.GEMINI_PROJECT_DIR || process.cwd()
|
|
30
|
+
const projectId = getProjectId(cwd)
|
|
31
|
+
|
|
32
|
+
// Gemini: PreCompress has 'trigger', SessionEnd has 'reason'
|
|
33
|
+
const eventName = input.hook_event_name || 'unknown'
|
|
34
|
+
let trigger = 'session_end'
|
|
35
|
+
|
|
36
|
+
if (eventName === 'PreCompress') {
|
|
37
|
+
trigger = 'pre_compact'
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
console.error(info(`🧠 Curating memories (${eventName})...`))
|
|
41
|
+
|
|
42
|
+
const response = await fetch(`${MEMORY_API_URL}/memory/checkpoint`, {
|
|
43
|
+
method: 'POST',
|
|
44
|
+
headers: { 'Content-Type': 'application/json' },
|
|
45
|
+
body: JSON.stringify({
|
|
46
|
+
session_id: sessionId,
|
|
47
|
+
project_id: projectId,
|
|
48
|
+
claude_session_id: sessionId,
|
|
49
|
+
trigger,
|
|
50
|
+
cwd,
|
|
51
|
+
cli_type: 'gemini-cli'
|
|
52
|
+
}),
|
|
53
|
+
signal: AbortSignal.timeout(5000),
|
|
54
|
+
}).catch(() => null)
|
|
55
|
+
|
|
56
|
+
if (response?.ok) {
|
|
57
|
+
console.error(success('✨ Memory curation started'))
|
|
58
|
+
// For PreCompress, we can send a system message
|
|
59
|
+
if (eventName === 'PreCompress') {
|
|
60
|
+
console.log(JSON.stringify({
|
|
61
|
+
systemMessage: "🧠 Memories curated before compression"
|
|
62
|
+
}))
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
console.error(warn('⚠️ Memory server not available'))
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
} catch (error: any) {
|
|
69
|
+
console.error(warn(`⚠️ Hook error: ${error.message}`))
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
main()
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// ============================================================================
|
|
3
|
+
// GEMINI SESSION START HOOK
|
|
4
|
+
// Hook: SessionStart
|
|
5
|
+
//
|
|
6
|
+
// Injects session primer when a new Gemini session begins.
|
|
7
|
+
// ============================================================================
|
|
8
|
+
|
|
9
|
+
const MEMORY_API_URL = process.env.MEMORY_API_URL || 'http://localhost:8765'
|
|
10
|
+
const TIMEOUT_MS = 5000
|
|
11
|
+
|
|
12
|
+
function getProjectId(cwd: string): string {
|
|
13
|
+
return cwd.split('/').pop() || 'default'
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function httpPost(url: string, data: object): Promise<any> {
|
|
17
|
+
try {
|
|
18
|
+
const response = await fetch(url, {
|
|
19
|
+
method: 'POST',
|
|
20
|
+
headers: { 'Content-Type': 'application/json' },
|
|
21
|
+
body: JSON.stringify(data),
|
|
22
|
+
signal: AbortSignal.timeout(TIMEOUT_MS),
|
|
23
|
+
})
|
|
24
|
+
return response.ok ? response.json() : {}
|
|
25
|
+
} catch {
|
|
26
|
+
return {}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function main() {
|
|
31
|
+
if (process.env.MEMORY_CURATOR_ACTIVE === '1') return
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const inputText = await Bun.stdin.text()
|
|
35
|
+
const input = inputText ? JSON.parse(inputText) : {}
|
|
36
|
+
|
|
37
|
+
// Gemini provides session_id in the common input fields
|
|
38
|
+
const sessionId = input.session_id || process.env.GEMINI_SESSION_ID || 'unknown'
|
|
39
|
+
const cwd = input.cwd || process.env.GEMINI_PROJECT_DIR || process.cwd()
|
|
40
|
+
const projectId = getProjectId(cwd)
|
|
41
|
+
|
|
42
|
+
const result = await httpPost(`${MEMORY_API_URL}/memory/context`, {
|
|
43
|
+
session_id: sessionId,
|
|
44
|
+
project_id: projectId,
|
|
45
|
+
current_message: '',
|
|
46
|
+
max_memories: 0,
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
await httpPost(`${MEMORY_API_URL}/memory/process`, {
|
|
50
|
+
session_id: sessionId,
|
|
51
|
+
project_id: projectId,
|
|
52
|
+
metadata: { event: 'session_start', platform: 'gemini' },
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
const primer = result.context_text || ''
|
|
56
|
+
|
|
57
|
+
if (primer) {
|
|
58
|
+
// Gemini expects a structured JSON response for context injection
|
|
59
|
+
console.log(JSON.stringify({
|
|
60
|
+
hookSpecificOutput: {
|
|
61
|
+
hookEventName: "SessionStart",
|
|
62
|
+
additionalContext: primer
|
|
63
|
+
}
|
|
64
|
+
}))
|
|
65
|
+
}
|
|
66
|
+
} catch (e) {
|
|
67
|
+
// Fail silently, but ensure we don't output invalid JSON if we crashed mid-stream
|
|
68
|
+
process.exit(0)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
main()
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// ============================================================================
|
|
3
|
+
// GEMINI BEFORE AGENT HOOK
|
|
4
|
+
// Hook: BeforeAgent (equivalent to Claude's UserPromptSubmit)
|
|
5
|
+
//
|
|
6
|
+
// Intercepts user prompts to inject relevant memories.
|
|
7
|
+
// ============================================================================
|
|
8
|
+
|
|
9
|
+
const MEMORY_API_URL = process.env.MEMORY_API_URL || 'http://localhost:8765'
|
|
10
|
+
const TIMEOUT_MS = 5000
|
|
11
|
+
|
|
12
|
+
function getProjectId(cwd: string): string {
|
|
13
|
+
return cwd.split('/').pop() || 'default'
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function httpPost(url: string, data: object): Promise<any> {
|
|
17
|
+
try {
|
|
18
|
+
const response = await fetch(url, {
|
|
19
|
+
method: 'POST',
|
|
20
|
+
headers: { 'Content-Type': 'application/json' },
|
|
21
|
+
body: JSON.stringify(data),
|
|
22
|
+
signal: AbortSignal.timeout(TIMEOUT_MS),
|
|
23
|
+
})
|
|
24
|
+
return response.ok ? response.json() : {}
|
|
25
|
+
} catch {
|
|
26
|
+
return {}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function main() {
|
|
31
|
+
if (process.env.MEMORY_CURATOR_ACTIVE === '1') return
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const inputText = await Bun.stdin.text()
|
|
35
|
+
const input = inputText ? JSON.parse(inputText) : {}
|
|
36
|
+
|
|
37
|
+
const sessionId = input.session_id || process.env.GEMINI_SESSION_ID || 'unknown'
|
|
38
|
+
const prompt = input.prompt || '' // Gemini passes 'prompt' in BeforeAgent
|
|
39
|
+
const cwd = input.cwd || process.env.GEMINI_PROJECT_DIR || process.cwd()
|
|
40
|
+
const projectId = getProjectId(cwd)
|
|
41
|
+
|
|
42
|
+
const result = await httpPost(`${MEMORY_API_URL}/memory/context`, {
|
|
43
|
+
session_id: sessionId,
|
|
44
|
+
project_id: projectId,
|
|
45
|
+
current_message: prompt,
|
|
46
|
+
max_memories: 5,
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
await httpPost(`${MEMORY_API_URL}/memory/process`, {
|
|
50
|
+
session_id: sessionId,
|
|
51
|
+
project_id: projectId,
|
|
52
|
+
metadata: { platform: 'gemini' }
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
const context = result.context_text || ''
|
|
56
|
+
|
|
57
|
+
if (context) {
|
|
58
|
+
// Gemini requires structured JSON output
|
|
59
|
+
console.log(JSON.stringify({
|
|
60
|
+
decision: "allow",
|
|
61
|
+
hookSpecificOutput: {
|
|
62
|
+
hookEventName: "BeforeAgent",
|
|
63
|
+
additionalContext: context
|
|
64
|
+
}
|
|
65
|
+
}))
|
|
66
|
+
} else {
|
|
67
|
+
// Must always output valid JSON or nothing?
|
|
68
|
+
// Safest to output "allow" if no context
|
|
69
|
+
console.log(JSON.stringify({ decision: "allow" }))
|
|
70
|
+
}
|
|
71
|
+
} catch {
|
|
72
|
+
// Fail safe
|
|
73
|
+
console.log(JSON.stringify({ decision: "allow" }))
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
main()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rlabs-inc/memory",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.5",
|
|
4
4
|
"description": "AI Memory System - Consciousness continuity through intelligent memory curation and retrieval",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
"files": [
|
|
25
25
|
"dist",
|
|
26
26
|
"src",
|
|
27
|
-
"hooks"
|
|
27
|
+
"hooks",
|
|
28
|
+
"gemini-hooks"
|
|
28
29
|
],
|
|
29
30
|
"scripts": {
|
|
30
31
|
"build": "bun run build:esm && bun run build:cjs",
|
|
@@ -1,20 +1,41 @@
|
|
|
1
1
|
// ============================================================================
|
|
2
|
-
// INSTALL COMMAND - Set up Claude Code
|
|
2
|
+
// INSTALL COMMAND - Set up hooks for Claude Code or Gemini CLI
|
|
3
3
|
// ============================================================================
|
|
4
4
|
|
|
5
5
|
import { homedir } from 'os'
|
|
6
|
-
import { join
|
|
6
|
+
import { join } from 'path'
|
|
7
7
|
import { existsSync, mkdirSync } from 'fs'
|
|
8
8
|
import { c, symbols, fmt } from '../colors.ts'
|
|
9
9
|
|
|
10
10
|
interface InstallOptions {
|
|
11
11
|
verbose?: boolean
|
|
12
12
|
force?: boolean
|
|
13
|
+
claude?: boolean
|
|
14
|
+
gemini?: boolean
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
export async function install(options: InstallOptions) {
|
|
18
|
+
// Determine which platform to install for
|
|
19
|
+
const installClaude = options.claude || (!options.claude && !options.gemini)
|
|
20
|
+
const installGemini = options.gemini
|
|
21
|
+
|
|
22
|
+
if (!installClaude && !installGemini) {
|
|
23
|
+
console.log(c.error(`Please specify --claude or --gemini`))
|
|
24
|
+
process.exit(1)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (installClaude) {
|
|
28
|
+
await installClaudeHooks(options)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (installGemini) {
|
|
32
|
+
await installGeminiHooks(options)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function installClaudeHooks(options: InstallOptions) {
|
|
16
37
|
console.log()
|
|
17
|
-
console.log(c.header(`${symbols.brain} Memory - Install Hooks`))
|
|
38
|
+
console.log(c.header(`${symbols.brain} Memory - Install Claude Code Hooks`))
|
|
18
39
|
console.log()
|
|
19
40
|
|
|
20
41
|
const claudeDir = join(homedir(), '.claude')
|
|
@@ -23,7 +44,7 @@ export async function install(options: InstallOptions) {
|
|
|
23
44
|
// Find the hooks directory (relative to this CLI)
|
|
24
45
|
const cliPath = import.meta.dir
|
|
25
46
|
const packageRoot = join(cliPath, '..', '..', '..')
|
|
26
|
-
const hooksDir = join(packageRoot, 'hooks')
|
|
47
|
+
const hooksDir = join(packageRoot, 'hooks', 'claude')
|
|
27
48
|
|
|
28
49
|
console.log(` ${fmt.kv('Claude config', claudeDir)}`)
|
|
29
50
|
console.log(` ${fmt.kv('Hooks source', hooksDir)}`)
|
|
@@ -31,7 +52,9 @@ export async function install(options: InstallOptions) {
|
|
|
31
52
|
|
|
32
53
|
// Check if hooks directory exists
|
|
33
54
|
if (!existsSync(hooksDir)) {
|
|
34
|
-
console.log(
|
|
55
|
+
console.log(
|
|
56
|
+
c.error(` ${symbols.cross} Hooks directory not found at ${hooksDir}`)
|
|
57
|
+
)
|
|
35
58
|
console.log(c.muted(` Make sure the memory package is properly installed`))
|
|
36
59
|
process.exit(1)
|
|
37
60
|
}
|
|
@@ -50,7 +73,11 @@ export async function install(options: InstallOptions) {
|
|
|
50
73
|
settings = JSON.parse(content)
|
|
51
74
|
console.log(` ${c.success(symbols.tick)} Found existing settings.json`)
|
|
52
75
|
} catch {
|
|
53
|
-
console.log(
|
|
76
|
+
console.log(
|
|
77
|
+
` ${c.warn(
|
|
78
|
+
symbols.warning
|
|
79
|
+
)} Could not parse settings.json, creating backup`
|
|
80
|
+
)
|
|
54
81
|
const backupPath = `${settingsPath}.backup.${Date.now()}`
|
|
55
82
|
await Bun.write(backupPath, await Bun.file(settingsPath).text())
|
|
56
83
|
}
|
|
@@ -69,10 +96,10 @@ export async function install(options: InstallOptions) {
|
|
|
69
96
|
{
|
|
70
97
|
type: 'command',
|
|
71
98
|
command: `bun "${sessionStartHook}"`,
|
|
72
|
-
timeout: 10
|
|
73
|
-
}
|
|
74
|
-
]
|
|
75
|
-
}
|
|
99
|
+
timeout: 10,
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
},
|
|
76
103
|
],
|
|
77
104
|
UserPromptSubmit: [
|
|
78
105
|
{
|
|
@@ -80,10 +107,10 @@ export async function install(options: InstallOptions) {
|
|
|
80
107
|
{
|
|
81
108
|
type: 'command',
|
|
82
109
|
command: `bun "${userPromptHook}"`,
|
|
83
|
-
timeout: 10
|
|
84
|
-
}
|
|
85
|
-
]
|
|
86
|
-
}
|
|
110
|
+
timeout: 10,
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
},
|
|
87
114
|
],
|
|
88
115
|
PreCompact: [
|
|
89
116
|
{
|
|
@@ -92,10 +119,10 @@ export async function install(options: InstallOptions) {
|
|
|
92
119
|
{
|
|
93
120
|
type: 'command',
|
|
94
121
|
command: `bun "${curationHook}"`,
|
|
95
|
-
timeout: 120
|
|
96
|
-
}
|
|
97
|
-
]
|
|
98
|
-
}
|
|
122
|
+
timeout: 120,
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
},
|
|
99
126
|
],
|
|
100
127
|
SessionEnd: [
|
|
101
128
|
{
|
|
@@ -103,11 +130,11 @@ export async function install(options: InstallOptions) {
|
|
|
103
130
|
{
|
|
104
131
|
type: 'command',
|
|
105
132
|
command: `bun "${curationHook}"`,
|
|
106
|
-
timeout: 120
|
|
107
|
-
}
|
|
108
|
-
]
|
|
109
|
-
}
|
|
110
|
-
]
|
|
133
|
+
timeout: 120,
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
},
|
|
137
|
+
],
|
|
111
138
|
}
|
|
112
139
|
|
|
113
140
|
// Check for existing hooks
|
|
@@ -115,13 +142,27 @@ export async function install(options: InstallOptions) {
|
|
|
115
142
|
const existingHooks = Object.keys(settings.hooks)
|
|
116
143
|
if (existingHooks.length > 0) {
|
|
117
144
|
console.log()
|
|
118
|
-
console.log(
|
|
119
|
-
|
|
145
|
+
console.log(
|
|
146
|
+
c.warn(
|
|
147
|
+
` ${symbols.warning} Existing hooks found: ${existingHooks.join(
|
|
148
|
+
', '
|
|
149
|
+
)}`
|
|
150
|
+
)
|
|
151
|
+
)
|
|
152
|
+
console.log(
|
|
153
|
+
c.muted(
|
|
154
|
+
` Use --force to overwrite, or manually merge in settings.json`
|
|
155
|
+
)
|
|
156
|
+
)
|
|
120
157
|
console.log()
|
|
121
158
|
|
|
122
159
|
// Show what would be added
|
|
123
160
|
console.log(c.bold(' Hooks to add:'))
|
|
124
|
-
console.log(
|
|
161
|
+
console.log(
|
|
162
|
+
c.muted(
|
|
163
|
+
' ' + JSON.stringify(hooksConfig, null, 2).split('\n').join('\n ')
|
|
164
|
+
)
|
|
165
|
+
)
|
|
125
166
|
console.log()
|
|
126
167
|
process.exit(1)
|
|
127
168
|
}
|
|
@@ -130,7 +171,7 @@ export async function install(options: InstallOptions) {
|
|
|
130
171
|
// Merge hooks
|
|
131
172
|
settings.hooks = {
|
|
132
173
|
...settings.hooks,
|
|
133
|
-
...hooksConfig
|
|
174
|
+
...hooksConfig,
|
|
134
175
|
}
|
|
135
176
|
|
|
136
177
|
// Write settings
|
|
@@ -138,12 +179,14 @@ export async function install(options: InstallOptions) {
|
|
|
138
179
|
await Bun.write(settingsPath, JSON.stringify(settings, null, 2))
|
|
139
180
|
console.log(` ${c.success(symbols.tick)} Updated ${settingsPath}`)
|
|
140
181
|
} catch (error: any) {
|
|
141
|
-
console.log(
|
|
182
|
+
console.log(
|
|
183
|
+
c.error(` ${symbols.cross} Failed to write settings: ${error.message}`)
|
|
184
|
+
)
|
|
142
185
|
process.exit(1)
|
|
143
186
|
}
|
|
144
187
|
|
|
145
188
|
console.log()
|
|
146
|
-
console.log(c.success(`${symbols.sparkles}
|
|
189
|
+
console.log(c.success(`${symbols.sparkles} Claude Code hooks installed!`))
|
|
147
190
|
console.log()
|
|
148
191
|
console.log(c.bold('Next steps:'))
|
|
149
192
|
console.log(` 1. Start the memory server: ${c.command('memory serve')}`)
|
|
@@ -151,3 +194,198 @@ export async function install(options: InstallOptions) {
|
|
|
151
194
|
console.log(` 3. Memories will be automatically injected`)
|
|
152
195
|
console.log()
|
|
153
196
|
}
|
|
197
|
+
|
|
198
|
+
async function installGeminiHooks(options: InstallOptions) {
|
|
199
|
+
console.log()
|
|
200
|
+
console.log(c.header(`${symbols.brain} Memory - Install Gemini CLI Hooks`))
|
|
201
|
+
console.log()
|
|
202
|
+
|
|
203
|
+
const geminiDir = join(homedir(), '.gemini')
|
|
204
|
+
const targetHooksDir = join(geminiDir, 'hooks')
|
|
205
|
+
const settingsPath = join(geminiDir, 'settings.json')
|
|
206
|
+
|
|
207
|
+
// Find the hooks directory (relative to this CLI)
|
|
208
|
+
const cliPath = import.meta.dir
|
|
209
|
+
const packageRoot = join(cliPath, '..', '..', '..')
|
|
210
|
+
const hooksDir = join(packageRoot, 'hooks', 'gemini')
|
|
211
|
+
|
|
212
|
+
console.log(` ${fmt.kv('Gemini config', geminiDir)}`)
|
|
213
|
+
console.log(` ${fmt.kv('Hooks source', hooksDir)}`)
|
|
214
|
+
console.log(` ${fmt.kv('Hooks target', targetHooksDir)}`)
|
|
215
|
+
console.log()
|
|
216
|
+
|
|
217
|
+
// Check if source hooks directory exists
|
|
218
|
+
if (!existsSync(hooksDir)) {
|
|
219
|
+
console.log(
|
|
220
|
+
c.error(` ${symbols.cross} Hooks directory not found at ${hooksDir}`)
|
|
221
|
+
)
|
|
222
|
+
process.exit(1)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Ensure .gemini directory exists
|
|
226
|
+
if (!existsSync(geminiDir)) {
|
|
227
|
+
try {
|
|
228
|
+
mkdirSync(geminiDir, { recursive: true })
|
|
229
|
+
console.log(` ${c.success(symbols.tick)} Created ${geminiDir}`)
|
|
230
|
+
} catch {
|
|
231
|
+
console.log(
|
|
232
|
+
` ${c.warn(
|
|
233
|
+
symbols.warning
|
|
234
|
+
)} Could not create ${geminiDir} (sandbox restriction?)`
|
|
235
|
+
)
|
|
236
|
+
console.log(
|
|
237
|
+
` ${c.muted(
|
|
238
|
+
'Skipping config write, printing manual instructions instead.'
|
|
239
|
+
)}`
|
|
240
|
+
)
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Ensure target hooks directory exists
|
|
245
|
+
if (!existsSync(targetHooksDir)) {
|
|
246
|
+
try {
|
|
247
|
+
mkdirSync(targetHooksDir, { recursive: true })
|
|
248
|
+
console.log(` ${c.success(symbols.tick)} Created ${targetHooksDir}`)
|
|
249
|
+
} catch {
|
|
250
|
+
console.log(
|
|
251
|
+
` ${c.warn(symbols.warning)} Could not create ${targetHooksDir}`
|
|
252
|
+
)
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Copy hooks to target directory
|
|
257
|
+
const filesToCopy = ['session-start.ts', 'user-prompt.ts', 'curation.ts']
|
|
258
|
+
for (const file of filesToCopy) {
|
|
259
|
+
const source = join(hooksDir, file)
|
|
260
|
+
const target = join(targetHooksDir, file)
|
|
261
|
+
try {
|
|
262
|
+
const content = await Bun.file(source).text()
|
|
263
|
+
await Bun.write(target, content)
|
|
264
|
+
console.log(` ${c.success(symbols.tick)} Installed hook: ${file}`)
|
|
265
|
+
} catch (e: any) {
|
|
266
|
+
console.log(
|
|
267
|
+
` ${c.error(symbols.cross)} Failed to copy ${file}: ${e.message}`
|
|
268
|
+
)
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Read existing settings or create new
|
|
273
|
+
let settings: any = {}
|
|
274
|
+
if (existsSync(settingsPath)) {
|
|
275
|
+
try {
|
|
276
|
+
const content = await Bun.file(settingsPath).text()
|
|
277
|
+
settings = JSON.parse(content)
|
|
278
|
+
console.log(` ${c.success(symbols.tick)} Found existing settings.json`)
|
|
279
|
+
} catch {
|
|
280
|
+
console.log(` ${c.warn(symbols.warning)} Could not parse settings.json`)
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Build hooks configuration pointing to TARGET directory
|
|
285
|
+
const sessionStartHook = join(targetHooksDir, 'session-start.ts')
|
|
286
|
+
const userPromptHook = join(targetHooksDir, 'user-prompt.ts')
|
|
287
|
+
const curationHook = join(targetHooksDir, 'curation.ts')
|
|
288
|
+
|
|
289
|
+
// Based on Gemini CLI documentation
|
|
290
|
+
const hooksConfig = {
|
|
291
|
+
SessionStart: [
|
|
292
|
+
{
|
|
293
|
+
matcher: 'startup|resume',
|
|
294
|
+
hooks: [
|
|
295
|
+
{
|
|
296
|
+
name: 'load-session-primer',
|
|
297
|
+
type: 'command',
|
|
298
|
+
command: `bun "${sessionStartHook}"`,
|
|
299
|
+
description: 'Load session primer at the beginning of a session',
|
|
300
|
+
},
|
|
301
|
+
],
|
|
302
|
+
},
|
|
303
|
+
],
|
|
304
|
+
BeforeAgent: [
|
|
305
|
+
{
|
|
306
|
+
matcher: '*',
|
|
307
|
+
hooks: [
|
|
308
|
+
{
|
|
309
|
+
name: 'inject-memories',
|
|
310
|
+
type: 'command',
|
|
311
|
+
command: `bun "${userPromptHook}"`,
|
|
312
|
+
description: 'Inject relevant memories into user prompt',
|
|
313
|
+
},
|
|
314
|
+
],
|
|
315
|
+
},
|
|
316
|
+
],
|
|
317
|
+
PreCompress: [
|
|
318
|
+
{
|
|
319
|
+
matcher: 'auto|manual',
|
|
320
|
+
hooks: [
|
|
321
|
+
{
|
|
322
|
+
name: 'curate-memories',
|
|
323
|
+
type: 'command',
|
|
324
|
+
command: `bun "${curationHook}"`,
|
|
325
|
+
description: 'Curate memories before context compression',
|
|
326
|
+
},
|
|
327
|
+
],
|
|
328
|
+
},
|
|
329
|
+
],
|
|
330
|
+
SessionEnd: [
|
|
331
|
+
{
|
|
332
|
+
matcher: 'exit|logout',
|
|
333
|
+
hooks: [
|
|
334
|
+
{
|
|
335
|
+
name: 'curate-memories',
|
|
336
|
+
type: 'command',
|
|
337
|
+
command: `bun "${curationHook}"`,
|
|
338
|
+
description: 'Curate memories before session end',
|
|
339
|
+
},
|
|
340
|
+
],
|
|
341
|
+
},
|
|
342
|
+
],
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Merge hooks
|
|
346
|
+
if (!settings.hooks) {
|
|
347
|
+
settings.hooks = {}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
settings.hooks = {
|
|
351
|
+
...settings.hooks,
|
|
352
|
+
...hooksConfig,
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Enable the hooks
|
|
356
|
+
const enabledHooks = new Set(settings.hooks.enabled || [])
|
|
357
|
+
enabledHooks.add('SessionStart')
|
|
358
|
+
enabledHooks.add('BeforeAgent')
|
|
359
|
+
enabledHooks.add('PreCompress')
|
|
360
|
+
enabledHooks.add('SessionEnd')
|
|
361
|
+
settings.hooks.enabled = Array.from(enabledHooks)
|
|
362
|
+
|
|
363
|
+
// Write settings
|
|
364
|
+
try {
|
|
365
|
+
if (existsSync(geminiDir)) {
|
|
366
|
+
await Bun.write(settingsPath, JSON.stringify(settings, null, 2))
|
|
367
|
+
console.log(` ${c.success(symbols.tick)} Updated ${settingsPath}`)
|
|
368
|
+
} else {
|
|
369
|
+
throw new Error('Gemini directory does not exist')
|
|
370
|
+
}
|
|
371
|
+
} catch (error: any) {
|
|
372
|
+
console.log(
|
|
373
|
+
c.error(` ${symbols.cross} Failed to write settings: ${error.message}`)
|
|
374
|
+
)
|
|
375
|
+
console.log()
|
|
376
|
+
console.log(c.bold('Manual Installation Instructions:'))
|
|
377
|
+
console.log('Add the following to your ~/.gemini/settings.json:')
|
|
378
|
+
console.log()
|
|
379
|
+
console.log(JSON.stringify({ hooks: hooksConfig }, null, 2))
|
|
380
|
+
console.log()
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
console.log()
|
|
384
|
+
console.log(c.success(`${symbols.sparkles} Gemini CLI hooks configured!`))
|
|
385
|
+
console.log()
|
|
386
|
+
console.log(c.bold('Next steps:'))
|
|
387
|
+
console.log(` 1. Start the memory server: ${c.command('memory serve')}`)
|
|
388
|
+
console.log(` 2. Open Gemini CLI in any project`)
|
|
389
|
+
console.log(` 3. Memories will be automatically injected`)
|
|
390
|
+
console.log()
|
|
391
|
+
}
|
package/src/cli/index.ts
CHANGED
|
@@ -22,8 +22,7 @@ ${fmt.cmd('memory <command> [options]')}
|
|
|
22
22
|
${c.bold('Commands:')}
|
|
23
23
|
${c.command('serve')} Start the memory server ${c.muted('(default)')}
|
|
24
24
|
${c.command('stats')} Show memory statistics
|
|
25
|
-
${c.command('install')} Set up
|
|
26
|
-
${c.command('install-gemini')} Set up Gemini CLI hooks
|
|
25
|
+
${c.command('install')} Set up hooks ${c.muted('(--claude or --gemini)')}
|
|
27
26
|
${c.command('doctor')} Check system health
|
|
28
27
|
${c.command('help')} Show this help message
|
|
29
28
|
|
|
@@ -31,13 +30,16 @@ ${c.bold('Options:')}
|
|
|
31
30
|
${c.cyan('-p, --port')} <port> Server port ${c.muted('(default: 8765)')}
|
|
32
31
|
${c.cyan('-v, --verbose')} Verbose output
|
|
33
32
|
${c.cyan('-q, --quiet')} Minimal output
|
|
33
|
+
${c.cyan('--claude')} Install hooks for Claude Code
|
|
34
|
+
${c.cyan('--gemini')} Install hooks for Gemini CLI
|
|
34
35
|
${c.cyan('--version')} Show version
|
|
35
36
|
|
|
36
37
|
${c.bold('Examples:')}
|
|
37
38
|
${fmt.cmd('memory')} ${c.muted('# Start server on default port')}
|
|
38
39
|
${fmt.cmd('memory serve --port 9000')} ${c.muted('# Start on custom port')}
|
|
39
40
|
${fmt.cmd('memory stats')} ${c.muted('# Show memory statistics')}
|
|
40
|
-
${fmt.cmd('memory install')} ${c.muted('#
|
|
41
|
+
${fmt.cmd('memory install')} ${c.muted('# Install Claude Code hooks (default)')}
|
|
42
|
+
${fmt.cmd('memory install --gemini')} ${c.muted('# Install Gemini CLI hooks')}
|
|
41
43
|
|
|
42
44
|
${c.muted('Documentation: https://github.com/RLabs-Inc/memory')}
|
|
43
45
|
`)
|
|
@@ -62,6 +64,9 @@ async function main() {
|
|
|
62
64
|
quiet: { type: 'boolean', short: 'q', default: false },
|
|
63
65
|
help: { type: 'boolean', short: 'h', default: false },
|
|
64
66
|
version: { type: 'boolean', default: false },
|
|
67
|
+
force: { type: 'boolean', default: false },
|
|
68
|
+
claude: { type: 'boolean', default: false },
|
|
69
|
+
gemini: { type: 'boolean', default: false },
|
|
65
70
|
},
|
|
66
71
|
allowPositionals: true,
|
|
67
72
|
strict: false, // Allow unknown options for subcommands
|
|
@@ -104,12 +109,6 @@ async function main() {
|
|
|
104
109
|
break
|
|
105
110
|
}
|
|
106
111
|
|
|
107
|
-
case 'install-gemini': {
|
|
108
|
-
const { installGemini } = await import('./commands/install-gemini.ts')
|
|
109
|
-
await installGemini(values)
|
|
110
|
-
break
|
|
111
|
-
}
|
|
112
|
-
|
|
113
112
|
case 'doctor':
|
|
114
113
|
case 'check': {
|
|
115
114
|
const { doctor } = await import('./commands/doctor.ts')
|