@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.
@@ -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
+ }
@@ -0,0 +1,64 @@
1
+ // ============================================================================
2
+ // STATS COMMAND - Show memory statistics
3
+ // ============================================================================
4
+
5
+ import { c, symbols, fmt } from '../colors.ts'
6
+
7
+ interface StatsOptions {
8
+ project?: string
9
+ verbose?: boolean
10
+ }
11
+
12
+ const MEMORY_API_URL = process.env.MEMORY_API_URL || 'http://localhost:8765'
13
+
14
+ export async function stats(options: StatsOptions) {
15
+ console.log()
16
+ console.log(c.header(`${symbols.brain} Memory Statistics`))
17
+ console.log()
18
+
19
+ try {
20
+ // Check if server is running
21
+ const healthResponse = await fetch(`${MEMORY_API_URL}/health`).catch(() => null)
22
+
23
+ if (!healthResponse?.ok) {
24
+ console.log(` ${c.error(symbols.cross)} Memory server is not running`)
25
+ console.log()
26
+ console.log(c.muted(` Start it with: memory serve`))
27
+ console.log()
28
+ process.exit(1)
29
+ }
30
+
31
+ const health = await healthResponse.json()
32
+ console.log(` ${c.success(symbols.tick)} Server: ${c.cyan(MEMORY_API_URL)}`)
33
+ console.log(` ${fmt.kv('Engine', health.engine || 'unknown')}`)
34
+ console.log()
35
+
36
+ // Get project stats if project specified
37
+ if (options.project) {
38
+ const statsUrl = `${MEMORY_API_URL}/memory/stats?project_id=${encodeURIComponent(options.project)}`
39
+ const statsResponse = await fetch(statsUrl)
40
+
41
+ if (statsResponse.ok) {
42
+ const projectStats = await statsResponse.json()
43
+ console.log(fmt.section(`Project: ${options.project}`))
44
+ console.log()
45
+ console.log(` ${fmt.kv('Total Memories', projectStats.totalMemories || 0)}`)
46
+ console.log(` ${fmt.kv('Total Sessions', projectStats.totalSessions || 0)}`)
47
+ console.log(` ${fmt.kv('Stale Memories', projectStats.staleMemories || 0)}`)
48
+ if (projectStats.latestSession) {
49
+ console.log(` ${fmt.kv('Latest Session', projectStats.latestSession.slice(0, 8) + '...')}`)
50
+ }
51
+ } else {
52
+ console.log(c.warn(` ${symbols.warning} Could not fetch stats for project: ${options.project}`))
53
+ }
54
+ } else {
55
+ console.log(c.muted(` Use --project <name> to see project-specific stats`))
56
+ }
57
+
58
+ console.log()
59
+
60
+ } catch (error: any) {
61
+ console.error(c.error(`${symbols.cross} Error: ${error.message}`))
62
+ process.exit(1)
63
+ }
64
+ }
@@ -0,0 +1,128 @@
1
+ #!/usr/bin/env bun
2
+ // ============================================================================
3
+ // MEMORY CLI - Beautiful command-line interface
4
+ // ============================================================================
5
+
6
+ import { parseArgs } from 'util'
7
+ import { c, symbols, fmt } from './colors.ts'
8
+
9
+ const VERSION = '0.1.0'
10
+
11
+ /**
12
+ * Show help message
13
+ */
14
+ function showHelp() {
15
+ console.log(`
16
+ ${c.header(`${symbols.brain} Memory`)} ${c.muted(`v${VERSION}`)}
17
+ ${c.muted('Consciousness continuity for Claude')}
18
+
19
+ ${c.bold('Usage:')}
20
+ ${fmt.cmd('memory <command> [options]')}
21
+
22
+ ${c.bold('Commands:')}
23
+ ${c.command('serve')} Start the memory server ${c.muted('(default)')}
24
+ ${c.command('stats')} Show memory statistics
25
+ ${c.command('install')} Set up Claude Code hooks
26
+ ${c.command('doctor')} Check system health
27
+ ${c.command('help')} Show this help message
28
+
29
+ ${c.bold('Options:')}
30
+ ${c.cyan('-p, --port')} <port> Server port ${c.muted('(default: 8765)')}
31
+ ${c.cyan('-v, --verbose')} Verbose output
32
+ ${c.cyan('-q, --quiet')} Minimal output
33
+ ${c.cyan('--version')} Show version
34
+
35
+ ${c.bold('Examples:')}
36
+ ${fmt.cmd('memory')} ${c.muted('# Start server on default port')}
37
+ ${fmt.cmd('memory serve --port 9000')} ${c.muted('# Start on custom port')}
38
+ ${fmt.cmd('memory stats')} ${c.muted('# Show memory statistics')}
39
+ ${fmt.cmd('memory install')} ${c.muted('# Set up hooks for Claude Code')}
40
+
41
+ ${c.muted('Documentation: https://github.com/RLabs-Inc/memory')}
42
+ `)
43
+ }
44
+
45
+ /**
46
+ * Show version
47
+ */
48
+ function showVersion() {
49
+ console.log(`${symbols.brain} memory v${VERSION}`)
50
+ }
51
+
52
+ /**
53
+ * Main entry point
54
+ */
55
+ async function main() {
56
+ const { values, positionals } = parseArgs({
57
+ args: Bun.argv.slice(2),
58
+ options: {
59
+ port: { type: 'string', short: 'p', default: '8765' },
60
+ verbose: { type: 'boolean', short: 'v', default: false },
61
+ quiet: { type: 'boolean', short: 'q', default: false },
62
+ help: { type: 'boolean', short: 'h', default: false },
63
+ version: { type: 'boolean', default: false },
64
+ },
65
+ allowPositionals: true,
66
+ strict: false, // Allow unknown options for subcommands
67
+ })
68
+
69
+ // Handle global flags
70
+ if (values.version) {
71
+ showVersion()
72
+ process.exit(0)
73
+ }
74
+
75
+ const command = positionals[0] || 'serve'
76
+
77
+ if (values.help && command === 'serve') {
78
+ showHelp()
79
+ process.exit(0)
80
+ }
81
+
82
+ // Route to commands
83
+ switch (command) {
84
+ case 'serve':
85
+ case 'start':
86
+ case 'run': {
87
+ const { serve } = await import('./commands/serve.ts')
88
+ await serve(values)
89
+ break
90
+ }
91
+
92
+ case 'stats':
93
+ case 'status': {
94
+ const { stats } = await import('./commands/stats.ts')
95
+ await stats(values)
96
+ break
97
+ }
98
+
99
+ case 'install':
100
+ case 'setup': {
101
+ const { install } = await import('./commands/install.ts')
102
+ await install(values)
103
+ break
104
+ }
105
+
106
+ case 'doctor':
107
+ case 'check': {
108
+ const { doctor } = await import('./commands/doctor.ts')
109
+ await doctor(values)
110
+ break
111
+ }
112
+
113
+ case 'help':
114
+ showHelp()
115
+ break
116
+
117
+ default:
118
+ console.error(c.error(`Unknown command: ${command}`))
119
+ console.log(c.muted(`Run 'memory help' for usage information`))
120
+ process.exit(1)
121
+ }
122
+ }
123
+
124
+ // Run
125
+ main().catch(err => {
126
+ console.error(c.error(`Error: ${err.message}`))
127
+ process.exit(1)
128
+ })
@@ -70,8 +70,6 @@ export class Curator {
70
70
  cliType: config.cliType ?? 'claude-code',
71
71
  }
72
72
 
73
- // Log which CLI we're using
74
- console.log(`🧠 Curator initialized with CLI: ${cliCommand}`)
75
73
  }
76
74
 
77
75
  /**
@@ -154,8 +152,9 @@ Return ONLY this JSON structure:
154
152
  "interaction_tone": "The tone/style of interaction (e.g., professional and focused, warm collaborative friendship, mentor-student dynamic, casual technical discussion, or null if neutral)",
155
153
  "project_snapshot": {
156
154
  "current_phase": "Current state (if applicable)",
157
- "recent_achievements": "What was accomplished (if applicable)",
158
- "active_challenges": "What remains (if applicable)"
155
+ "recent_achievements": ["What was accomplished (if applicable)"],
156
+ "active_challenges": ["What remains (if applicable)"],
157
+ "next_steps": ["Planned next actions (if applicable)"]
159
158
  },
160
159
  "memories": [
161
160
  {
@@ -207,8 +206,7 @@ Return ONLY this JSON structure:
207
206
  } : undefined,
208
207
  memories: this._parseMemories(data.memories ?? []),
209
208
  }
210
- } catch (error) {
211
- console.error('Failed to parse curation response:', error)
209
+ } catch {
212
210
  return {
213
211
  session_summary: '',
214
212
  memories: [],
@@ -328,14 +326,6 @@ Return ONLY this JSON structure:
328
326
  )
329
327
  }
330
328
 
331
- // Log the command we're about to execute
332
- console.log(`\n📋 Executing CLI command:`)
333
- console.log(` Command: ${this._config.cliCommand}`)
334
- console.log(` Args: --resume ${sessionId} -p [user_message] --append-system-prompt [curation_instructions] --output-format json --max-turns 1`)
335
- console.log(` CWD: ${cwd || 'not set'}`)
336
- console.log(` User message: "${userMessage.slice(0, 50)}..."`)
337
- console.log(` System prompt length: ${systemPrompt.length} chars`)
338
-
339
329
  // Execute CLI
340
330
  const proc = Bun.spawn([this._config.cliCommand, ...args], {
341
331
  cwd,
@@ -353,24 +343,7 @@ Return ONLY this JSON structure:
353
343
  ])
354
344
  const exitCode = await proc.exited
355
345
 
356
- // Log the results
357
- console.log(`\n📤 CLI Response:`)
358
- console.log(` Exit code: ${exitCode}`)
359
- console.log(` Stdout length: ${stdout.length} chars`)
360
- console.log(` Stderr length: ${stderr.length} chars`)
361
-
362
- if (stderr) {
363
- console.log(`\n⚠️ Stderr output:`)
364
- console.log(stderr.slice(0, 1000)) // First 1000 chars
365
- }
366
-
367
- if (stdout) {
368
- console.log(`\n📥 Stdout output (first 500 chars):`)
369
- console.log(stdout.slice(0, 500))
370
- }
371
-
372
346
  if (exitCode !== 0) {
373
- console.error(`\n❌ CLI exited with code ${exitCode}`)
374
347
  return { session_summary: '', memories: [] }
375
348
  }
376
349
 
@@ -381,9 +354,6 @@ Return ONLY this JSON structure:
381
354
 
382
355
  // Check for error response FIRST (like Python does)
383
356
  if (cliOutput.type === 'error' || cliOutput.is_error === true) {
384
- console.log(`\n❌ CLI returned error:`)
385
- console.log(` Type: ${cliOutput.type}`)
386
- console.log(` Message: ${cliOutput.message || cliOutput.error || 'Unknown error'}`)
387
357
  return { session_summary: '', memories: [] }
388
358
  }
389
359
 
@@ -391,10 +361,7 @@ Return ONLY this JSON structure:
391
361
  let aiResponse = ''
392
362
  if (typeof cliOutput.result === 'string') {
393
363
  aiResponse = cliOutput.result
394
- console.log(`\n📦 Extracted result from CLI wrapper (${aiResponse.length} chars)`)
395
364
  } else {
396
- console.log(`\n⚠️ No result field in CLI output`)
397
- console.log(` Keys: ${Object.keys(cliOutput).join(', ')}`)
398
365
  return { session_summary: '', memories: [] }
399
366
  }
400
367
 
@@ -402,23 +369,15 @@ Return ONLY this JSON structure:
402
369
  const codeBlockMatch = aiResponse.match(/```(?:json)?\s*([\s\S]*?)```/)
403
370
  if (codeBlockMatch) {
404
371
  aiResponse = codeBlockMatch[1]!.trim()
405
- console.log(`📝 Extracted JSON from markdown code block`)
406
372
  }
407
373
 
408
374
  // Now find the JSON object (same regex as Python)
409
375
  const jsonMatch = aiResponse.match(/\{[\s\S]*\}/)?.[0]
410
376
  if (jsonMatch) {
411
- console.log(`✅ Found JSON object (${jsonMatch.length} chars)`)
412
- const result = this.parseCurationResponse(jsonMatch)
413
- console.log(` Parsed ${result.memories.length} memories`)
414
- return result
415
- } else {
416
- console.log(`\n⚠️ No JSON object found in AI response`)
417
- console.log(` Response preview: ${aiResponse.slice(0, 200)}...`)
377
+ return this.parseCurationResponse(jsonMatch)
418
378
  }
419
- } catch (error) {
420
- console.error('\n❌ Failed to parse CLI output:', error)
421
- console.log(` Raw stdout (first 500 chars): ${stdout.slice(0, 500)}`)
379
+ } catch {
380
+ // Parse error - return empty result
422
381
  }
423
382
 
424
383
  return { session_summary: '', memories: [] }