@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.
@@ -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.0",
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
- "fatherstatedb": "^0.2.1",
32
- "@anthropic-ai/sdk": "^0.39.0"
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
+ }