@rlabs-inc/memory 0.3.3 → 0.3.6

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.
@@ -1,20 +1,41 @@
1
1
  // ============================================================================
2
- // INSTALL COMMAND - Set up Claude Code hooks
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, dirname } from 'path'
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(c.error(` ${symbols.cross} Hooks directory not found at ${hooksDir}`))
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(` ${c.warn(symbols.warning)} Could not parse settings.json, creating backup`)
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(c.warn(` ${symbols.warning} Existing hooks found: ${existingHooks.join(', ')}`))
119
- console.log(c.muted(` Use --force to overwrite, or manually merge in settings.json`))
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(c.muted(' ' + JSON.stringify(hooksConfig, null, 2).split('\n').join('\n ')))
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(c.error(` ${symbols.cross} Failed to write settings: ${error.message}`))
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} Hooks installed successfully!`))
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
+ }