claude-memory-agent 2.1.0 → 2.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.
Files changed (91) hide show
  1. package/bin/cli.js +11 -1
  2. package/bin/lib/banner.js +39 -0
  3. package/bin/lib/environment.js +166 -0
  4. package/bin/lib/installer.js +291 -0
  5. package/bin/lib/models.js +95 -0
  6. package/bin/lib/steps/advanced.js +101 -0
  7. package/bin/lib/steps/confirm.js +87 -0
  8. package/bin/lib/steps/model.js +57 -0
  9. package/bin/lib/steps/provider.js +65 -0
  10. package/bin/lib/steps/scope.js +59 -0
  11. package/bin/lib/steps/server.js +74 -0
  12. package/bin/lib/ui.js +75 -0
  13. package/bin/onboarding.js +164 -0
  14. package/bin/postinstall.js +22 -257
  15. package/config.py +103 -4
  16. package/dashboard.html +697 -27
  17. package/hooks/extract_memories.py +439 -0
  18. package/hooks/pre_compact_hook.py +76 -0
  19. package/hooks/session_end_hook.py +149 -0
  20. package/hooks/stop_hook.py +372 -0
  21. package/install.py +91 -37
  22. package/main.py +1636 -892
  23. package/mcp_server.py +451 -0
  24. package/package.json +14 -3
  25. package/requirements.txt +12 -8
  26. package/services/adaptive_ranker.py +272 -0
  27. package/services/agent_catalog.json +153 -0
  28. package/services/agent_registry.py +245 -730
  29. package/services/claude_md_sync.py +320 -4
  30. package/services/consolidation.py +417 -0
  31. package/services/database.py +586 -105
  32. package/services/embedding_pipeline.py +262 -0
  33. package/services/embeddings.py +493 -85
  34. package/services/memory_decay.py +408 -0
  35. package/services/native_memory_paths.py +86 -0
  36. package/services/native_memory_sync.py +496 -0
  37. package/services/response_manager.py +183 -0
  38. package/services/terminal_ui.py +199 -0
  39. package/services/tier_manager.py +235 -0
  40. package/services/websocket.py +26 -6
  41. package/skills/search.py +136 -61
  42. package/skills/session_review.py +210 -23
  43. package/skills/store.py +125 -18
  44. package/terminal_dashboard.py +474 -0
  45. package/hooks/__pycache__/auto-detect-response.cpython-312.pyc +0 -0
  46. package/hooks/__pycache__/auto_capture.cpython-312.pyc +0 -0
  47. package/hooks/__pycache__/grounding-hook.cpython-312.pyc +0 -0
  48. package/hooks/__pycache__/session_end.cpython-312.pyc +0 -0
  49. package/hooks/__pycache__/session_start.cpython-312.pyc +0 -0
  50. package/services/__pycache__/__init__.cpython-312.pyc +0 -0
  51. package/services/__pycache__/agent_registry.cpython-312.pyc +0 -0
  52. package/services/__pycache__/auth.cpython-312.pyc +0 -0
  53. package/services/__pycache__/auto_inject.cpython-312.pyc +0 -0
  54. package/services/__pycache__/claude_md_sync.cpython-312.pyc +0 -0
  55. package/services/__pycache__/cleanup.cpython-312.pyc +0 -0
  56. package/services/__pycache__/compaction_flush.cpython-312.pyc +0 -0
  57. package/services/__pycache__/confidence.cpython-312.pyc +0 -0
  58. package/services/__pycache__/curator.cpython-312.pyc +0 -0
  59. package/services/__pycache__/daily_log.cpython-312.pyc +0 -0
  60. package/services/__pycache__/database.cpython-312.pyc +0 -0
  61. package/services/__pycache__/embeddings.cpython-312.pyc +0 -0
  62. package/services/__pycache__/insights.cpython-312.pyc +0 -0
  63. package/services/__pycache__/llm_analyzer.cpython-312.pyc +0 -0
  64. package/services/__pycache__/memory_md_sync.cpython-312.pyc +0 -0
  65. package/services/__pycache__/retry_queue.cpython-312.pyc +0 -0
  66. package/services/__pycache__/timeline.cpython-312.pyc +0 -0
  67. package/services/__pycache__/vector_index.cpython-312.pyc +0 -0
  68. package/services/__pycache__/websocket.cpython-312.pyc +0 -0
  69. package/skills/__pycache__/__init__.cpython-312.pyc +0 -0
  70. package/skills/__pycache__/admin.cpython-312.pyc +0 -0
  71. package/skills/__pycache__/checkpoint.cpython-312.pyc +0 -0
  72. package/skills/__pycache__/claude_md.cpython-312.pyc +0 -0
  73. package/skills/__pycache__/cleanup.cpython-312.pyc +0 -0
  74. package/skills/__pycache__/confidence_tracker.cpython-312.pyc +0 -0
  75. package/skills/__pycache__/context.cpython-312.pyc +0 -0
  76. package/skills/__pycache__/curator.cpython-312.pyc +0 -0
  77. package/skills/__pycache__/grounding.cpython-312.pyc +0 -0
  78. package/skills/__pycache__/insights.cpython-312.pyc +0 -0
  79. package/skills/__pycache__/natural_language.cpython-312.pyc +0 -0
  80. package/skills/__pycache__/retrieve.cpython-312.pyc +0 -0
  81. package/skills/__pycache__/search.cpython-312.pyc +0 -0
  82. package/skills/__pycache__/session_review.cpython-312.pyc +0 -0
  83. package/skills/__pycache__/state.cpython-312.pyc +0 -0
  84. package/skills/__pycache__/store.cpython-312.pyc +0 -0
  85. package/skills/__pycache__/summarize.cpython-312.pyc +0 -0
  86. package/skills/__pycache__/timeline.cpython-312.pyc +0 -0
  87. package/skills/__pycache__/verification.cpython-312.pyc +0 -0
  88. package/test_automation.py +0 -221
  89. package/test_complete.py +0 -338
  90. package/test_full.py +0 -322
  91. package/verify_db.py +0 -134
package/bin/cli.js CHANGED
@@ -247,7 +247,17 @@ function main() {
247
247
  switch (command) {
248
248
  case 'install':
249
249
  case 'setup':
250
- runPython('install.py', args.slice(1));
250
+ // Run the onboarding wizard
251
+ try {
252
+ const { main: runOnboarding } = require('./onboarding');
253
+ runOnboarding().catch(err => {
254
+ console.error('Setup error:', err.message);
255
+ process.exit(1);
256
+ });
257
+ } catch (e) {
258
+ // Fallback to Python installer if onboarding deps missing
259
+ runPython('install.py', args.slice(1));
260
+ }
251
261
  break;
252
262
 
253
263
  case 'start':
@@ -0,0 +1,39 @@
1
+ 'use strict';
2
+
3
+ const chalk = require('chalk');
4
+ const gradient = require('gradient-string');
5
+
6
+ const BANNER = `
7
+ ██████╗██╗ █████╗ ██╗ ██╗██████╗ ███████╗
8
+ ██╔════╝██║ ██╔══██╗██║ ██║██╔══██╗██╔════╝
9
+ ██║ ██║ ███████║██║ ██║██║ ██║█████╗
10
+ ██║ ██║ ██╔══██║██║ ██║██║ ██║██╔══╝
11
+ ╚██████╗███████╗██║ ██║╚██████╔╝██████╔╝███████╗
12
+ ╚═════╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝
13
+ ███╗ ███╗███████╗███╗ ███╗ ██████╗ ██████╗ ██╗ ██╗
14
+ ████╗ ████║██╔════╝████╗ ████║██╔═══██╗██╔══██╗╚██╗ ██╔╝
15
+ ██╔████╔██║█████╗ ██╔████╔██║██║ ██║██████╔╝ ╚████╔╝
16
+ ██║╚██╔╝██║██╔══╝ ██║╚██╔╝██║██║ ██║██╔══██╗ ╚██╔╝
17
+ ██║ ╚═╝ ██║███████╗██║ ╚═╝ ██║╚██████╔╝██║ ██║ ██║
18
+ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝`;
19
+
20
+ const VERSION = 'v2.1.0';
21
+
22
+ /**
23
+ * Print the Claude Memory Agent ASCII art banner with gradient coloring.
24
+ * Uses a cool blue-to-purple gradient (mind -> cristal).
25
+ */
26
+ function printBanner() {
27
+ const coolGradient = gradient(['#0575E6', '#7B68EE', '#A855F7', '#6C63FF']);
28
+ console.log(coolGradient(BANNER));
29
+
30
+ // Center the version string beneath the banner
31
+ const bannerWidth = 56; // approximate width of the widest banner line
32
+ const padding = Math.max(0, Math.floor((bannerWidth - VERSION.length) / 2));
33
+ const centeredVersion = ' '.repeat(padding) + VERSION;
34
+ console.log('');
35
+ console.log(chalk.dim(centeredVersion));
36
+ console.log('');
37
+ }
38
+
39
+ module.exports = { printBanner };
@@ -0,0 +1,166 @@
1
+ 'use strict';
2
+
3
+ const os = require('os');
4
+ const http = require('http');
5
+ const { execSync } = require('child_process');
6
+ const chalk = require('chalk');
7
+
8
+ /**
9
+ * Try running a command and return its stdout, or null on failure.
10
+ * @param {string} cmd - Command to execute
11
+ * @returns {string|null}
12
+ */
13
+ function tryExec(cmd) {
14
+ try {
15
+ return execSync(cmd, {
16
+ encoding: 'utf8',
17
+ stdio: ['pipe', 'pipe', 'pipe'],
18
+ timeout: 5000,
19
+ }).trim();
20
+ } catch (_) {
21
+ return null;
22
+ }
23
+ }
24
+
25
+ /**
26
+ * Detect the Python interpreter available on this system.
27
+ * Tries python3, python, py in order. Returns { found, version, cmd }.
28
+ * @returns {{ found: boolean, version: string|null, cmd: string|null }}
29
+ */
30
+ function detectPython() {
31
+ const commands = ['python3', 'python', 'py'];
32
+ for (const cmd of commands) {
33
+ const output = tryExec(`${cmd} --version`);
34
+ if (output && output.includes('Python 3')) {
35
+ const match = output.match(/Python\s+([\d.]+)/);
36
+ return { found: true, version: match ? match[1] : output, cmd };
37
+ }
38
+ }
39
+ return { found: false, version: null, cmd: null };
40
+ }
41
+
42
+ /**
43
+ * Detect whether Claude Code CLI is installed.
44
+ * @returns {{ found: boolean, version: string|null }}
45
+ */
46
+ function detectClaude() {
47
+ const commands = process.platform === 'win32'
48
+ ? ['claude', 'claude.cmd', 'claude-code', 'claude-code.cmd']
49
+ : ['claude', 'claude-code'];
50
+
51
+ for (const cmd of commands) {
52
+ const output = tryExec(`${cmd} --version`);
53
+ if (output) {
54
+ const firstLine = output.split('\n')[0].trim();
55
+ return { found: true, version: firstLine };
56
+ }
57
+ }
58
+ return { found: false, version: null };
59
+ }
60
+
61
+ /**
62
+ * Check whether Ollama is running and list available models.
63
+ * Makes an HTTP GET to localhost:11434/api/tags with a 2-second timeout.
64
+ * @returns {Promise<{ running: boolean, models: string[] }>}
65
+ */
66
+ function detectOllama() {
67
+ return new Promise((resolve) => {
68
+ const req = http.get('http://localhost:11434/api/tags', { timeout: 2000 }, (res) => {
69
+ let data = '';
70
+ res.on('data', (chunk) => { data += chunk; });
71
+ res.on('end', () => {
72
+ try {
73
+ const json = JSON.parse(data);
74
+ const models = (json.models || []).map((m) => {
75
+ const name = m.name || '';
76
+ return name.includes(':') ? name.split(':')[0] : name;
77
+ });
78
+ resolve({ running: true, models });
79
+ } catch (_) {
80
+ resolve({ running: false, models: [] });
81
+ }
82
+ });
83
+ });
84
+
85
+ req.on('error', () => resolve({ running: false, models: [] }));
86
+ req.on('timeout', () => {
87
+ req.destroy();
88
+ resolve({ running: false, models: [] });
89
+ });
90
+ });
91
+ }
92
+
93
+ /**
94
+ * Detect the full environment: OS, Node, Python, Claude Code, and Ollama.
95
+ * @returns {Promise<Object>} Environment information object
96
+ */
97
+ async function detectEnvironment() {
98
+ const python = detectPython();
99
+ const claude = detectClaude();
100
+ const ollama = await detectOllama();
101
+
102
+ return {
103
+ os: {
104
+ platform: os.platform(),
105
+ arch: os.arch(),
106
+ release: os.release(),
107
+ },
108
+ node: {
109
+ version: process.version,
110
+ path: process.execPath,
111
+ },
112
+ python,
113
+ claude,
114
+ ollama,
115
+ };
116
+ }
117
+
118
+ /**
119
+ * Print the environment detection results in a formatted, colored layout.
120
+ * @param {Object} env - Environment object from detectEnvironment()
121
+ */
122
+ function printEnvironment(env) {
123
+ const ok = chalk.green('\u2713');
124
+ const warn = chalk.yellow('!');
125
+
126
+ console.log('');
127
+ console.log(chalk.bold(' Environment Detection'));
128
+ console.log(chalk.dim(' ' + '\u2500'.repeat(40)));
129
+
130
+ // OS
131
+ console.log(` ${ok} ${chalk.bold('OS')} ${env.os.platform} ${env.os.arch} (${env.os.release})`);
132
+
133
+ // Node
134
+ console.log(` ${ok} ${chalk.bold('Node.js')} ${env.node.version}`);
135
+
136
+ // Python
137
+ if (env.python.found) {
138
+ console.log(` ${ok} ${chalk.bold('Python')} ${env.python.version} ${chalk.dim('(' + env.python.cmd + ')')}`);
139
+ } else {
140
+ console.log(` ${warn} ${chalk.bold('Python')} ${chalk.yellow('not found')} ${chalk.dim('(install Python 3.9+)')}`);
141
+ }
142
+
143
+ // Claude Code
144
+ if (env.claude.found) {
145
+ console.log(` ${ok} ${chalk.bold('Claude')} ${env.claude.version}`);
146
+ } else {
147
+ console.log(` ${warn} ${chalk.bold('Claude')} ${chalk.yellow('not found')} ${chalk.dim('(npm i -g @anthropic-ai/claude-code)')}`);
148
+ }
149
+
150
+ // Ollama
151
+ if (env.ollama.running) {
152
+ console.log(` ${ok} ${chalk.bold('Ollama')} running`);
153
+ if (env.ollama.models.length > 0) {
154
+ const modelList = env.ollama.models.join(', ');
155
+ console.log(` ${chalk.dim('models: ' + modelList)}`);
156
+ } else {
157
+ console.log(` ${chalk.dim('no models pulled')}`);
158
+ }
159
+ } else {
160
+ console.log(` ${warn} ${chalk.bold('Ollama')} ${chalk.yellow('not running')} ${chalk.dim('(optional - for ollama provider)')}`);
161
+ }
162
+
163
+ console.log('');
164
+ }
165
+
166
+ module.exports = { detectEnvironment, printEnvironment };
@@ -0,0 +1,291 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { execSync, spawn } = require('child_process');
6
+ const ora = require('ora');
7
+
8
+ /**
9
+ * Detect which Python command is available (python3, python, or py).
10
+ * @returns {string|null} The command string, or null if none found
11
+ */
12
+ function detectPythonCmd() {
13
+ const commands = ['python3', 'python', 'py'];
14
+ for (const cmd of commands) {
15
+ try {
16
+ const output = execSync(`${cmd} --version`, {
17
+ encoding: 'utf8',
18
+ stdio: ['pipe', 'pipe', 'pipe'],
19
+ timeout: 5000,
20
+ });
21
+ if (output && output.includes('Python 3')) {
22
+ return cmd;
23
+ }
24
+ } catch (_) {
25
+ // try next
26
+ }
27
+ }
28
+ return null;
29
+ }
30
+
31
+ /**
32
+ * Run a command synchronously with shell on Windows.
33
+ * @param {string} cmd - Command to execute
34
+ * @param {string} cwd - Working directory
35
+ * @returns {{ success: boolean, output: string }}
36
+ */
37
+ function runSync(cmd, cwd) {
38
+ try {
39
+ const output = execSync(cmd, {
40
+ encoding: 'utf8',
41
+ cwd: cwd,
42
+ stdio: ['pipe', 'pipe', 'pipe'],
43
+ timeout: 300000, // 5 minutes for pip installs
44
+ shell: true,
45
+ });
46
+ return { success: true, output: output || '' };
47
+ } catch (err) {
48
+ const stderr = err.stderr ? err.stderr.toString() : err.message;
49
+ return { success: false, output: stderr };
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Build the .env file content string from the config object.
55
+ * @param {Object} config - Wizard configuration selections
56
+ * @param {string} agentDir - Path to the agent directory
57
+ * @returns {string} .env file contents
58
+ */
59
+ function buildEnvContent(config, agentDir) {
60
+ const timestamp = new Date().toISOString();
61
+ const dbPath = config.dbPath || path.join(agentDir, 'memories.db').replace(/\\/g, '/');
62
+ const memoryUrl = 'http://' + (config.host === '0.0.0.0' ? 'localhost' : config.host) + ':' + config.port;
63
+
64
+ const lines = [
65
+ '# Claude Memory Agent Configuration',
66
+ '# Generated by onboarding wizard',
67
+ '# Date: ' + timestamp,
68
+ '',
69
+ '# Server Configuration',
70
+ 'HOST=' + config.host,
71
+ 'PORT=' + config.port,
72
+ 'MEMORY_AGENT_URL=' + memoryUrl,
73
+ '',
74
+ '# Embedding Configuration',
75
+ 'EMBEDDING_PROVIDER=' + config.provider,
76
+ 'EMBEDDING_MODEL=' + config.model,
77
+ '',
78
+ '# Ollama Configuration (only needed if EMBEDDING_PROVIDER=ollama)',
79
+ 'OLLAMA_HOST=http://localhost:11434',
80
+ '',
81
+ '# Database Configuration',
82
+ 'DATABASE_PATH=' + dbPath,
83
+ 'USE_VECTOR_INDEX=true',
84
+ 'DB_POOL_SIZE=5',
85
+ 'DB_TIMEOUT=30.0',
86
+ '',
87
+ '# Logging',
88
+ 'LOG_LEVEL=' + config.logLevel,
89
+ '',
90
+ '# Authentication',
91
+ 'AUTH_ENABLED=' + (config.authEnabled ? 'true' : 'false'),
92
+ '',
93
+ '# Memory Tiers',
94
+ 'TIER_HOT_MAX_AGE_DAYS=' + config.hotTierDays,
95
+ 'TIER_WARM_MAX_AGE_DAYS=' + config.warmTierDays,
96
+ ];
97
+
98
+ return lines.join('\n') + '\n';
99
+ }
100
+
101
+ /**
102
+ * Build the argument array for install.py based on wizard config.
103
+ * @param {Object} config - Wizard configuration selections
104
+ * @returns {string[]} Arguments for install.py
105
+ */
106
+ function buildInstallArgs(config) {
107
+ const args = ['--auto', '--skip-env', '--skip-deps', '--skip-claude-check'];
108
+
109
+ if (config.scope === 'project') {
110
+ args.push('--scope', 'project', '--project-path', config.projectPath);
111
+ } else if (config.scope === 'global') {
112
+ args.push('--scope', 'global');
113
+ } else if (config.scope === 'both') {
114
+ args.push('--scope', 'both', '--project-path', config.projectPath);
115
+ }
116
+
117
+ if (!config.autoStart) {
118
+ args.push('--no-start');
119
+ }
120
+
121
+ return args;
122
+ }
123
+
124
+ /**
125
+ * Run the full installer sequence, bridging from the Node wizard to install.py.
126
+ *
127
+ * Steps:
128
+ * 1. Write .env file from config
129
+ * 2. Check Python dependencies
130
+ * 3. Install Python packages
131
+ * 4. Creating configuration (already done)
132
+ * 5. Configure Claude Code MCP via install.py
133
+ * 6. Install hooks (handled by install.py)
134
+ * 7. Start Memory Agent (handled by install.py if autoStart)
135
+ *
136
+ * @param {Object} config - All wizard selections
137
+ * @param {string} agentDir - Absolute path to the memory-agent directory
138
+ * @returns {Promise<{ success: boolean, errors: string[] }>}
139
+ */
140
+ async function runInstaller(config, agentDir) {
141
+ const errors = [];
142
+
143
+ // Detect Python
144
+ const pythonCmd = detectPythonCmd();
145
+ if (!pythonCmd) {
146
+ return { success: false, errors: ['Python 3 is required but was not found on this system.'] };
147
+ }
148
+
149
+ // --- Step 1: Write .env file ---
150
+ const spinnerEnv = ora('Writing configuration file (.env)').start();
151
+ try {
152
+ const envContent = buildEnvContent(config, agentDir);
153
+ const envPath = path.join(agentDir, '.env');
154
+ fs.writeFileSync(envPath, envContent, 'utf8');
155
+ spinnerEnv.succeed('Configuration file written (.env)');
156
+ } catch (err) {
157
+ spinnerEnv.fail('Failed to write .env file');
158
+ errors.push('Failed to write .env: ' + err.message);
159
+ return { success: false, errors };
160
+ }
161
+
162
+ // --- Step 2: Check Python dependencies ---
163
+ const spinnerCheck = ora('Checking Python dependencies').start();
164
+ const checkResult = runSync(pythonCmd + ' -m pip check', agentDir);
165
+ if (checkResult.success) {
166
+ spinnerCheck.succeed('Python dependencies verified');
167
+ } else {
168
+ spinnerCheck.warn('Dependency check had warnings (will attempt install)');
169
+ }
170
+
171
+ // --- Step 3: Install Python packages ---
172
+ const spinnerPip = ora('Installing Python packages').start();
173
+ const requirementsPath = path.join(agentDir, 'requirements.txt');
174
+ if (fs.existsSync(requirementsPath)) {
175
+ const pipResult = runSync(
176
+ pythonCmd + ' -m pip install -r requirements.txt -q --disable-pip-version-check',
177
+ agentDir
178
+ );
179
+ if (pipResult.success) {
180
+ spinnerPip.succeed('Python packages installed');
181
+ } else {
182
+ spinnerPip.fail('Failed to install Python packages');
183
+ errors.push('pip install failed: ' + pipResult.output);
184
+ }
185
+ } else {
186
+ spinnerPip.warn('requirements.txt not found, skipping');
187
+ }
188
+
189
+ // --- Step 4: Configuration already created ---
190
+ const spinnerConfig = ora('Creating configuration').start();
191
+ spinnerConfig.succeed('Configuration created (.env written in step 1)');
192
+
193
+ // --- Step 5: Configure Claude Code MCP ---
194
+ const spinnerMcp = ora('Configuring Claude Code MCP').start();
195
+ const installPyPath = path.join(agentDir, 'install.py');
196
+ if (fs.existsSync(installPyPath)) {
197
+ const installArgs = buildInstallArgs(config);
198
+ const mcpResult = runSync(
199
+ pythonCmd + ' ' + JSON.stringify(installPyPath) + ' ' + installArgs.join(' '),
200
+ agentDir
201
+ );
202
+ if (mcpResult.success) {
203
+ spinnerMcp.succeed('Claude Code MCP configured');
204
+ } else {
205
+ spinnerMcp.fail('MCP configuration had issues');
206
+ errors.push('install.py: ' + mcpResult.output);
207
+ }
208
+ } else {
209
+ spinnerMcp.warn('install.py not found, skipping MCP configuration');
210
+ }
211
+
212
+ // --- Step 6: Hooks ---
213
+ const spinnerHooks = ora('Installing hooks').start();
214
+ if (Array.isArray(config.hooks) && config.hooks.length > 0) {
215
+ spinnerHooks.succeed('Hooks configured (' + config.hooks.length + ' selected)');
216
+ } else {
217
+ spinnerHooks.succeed('No hooks selected (handled by install.py)');
218
+ }
219
+
220
+ // --- Step 7: Start agent ---
221
+ if (config.autoStart) {
222
+ const spinnerStart = ora('Starting Memory Agent').start();
223
+ const startScript = path.join(agentDir, 'main.py');
224
+ if (fs.existsSync(startScript)) {
225
+ try {
226
+ const child = spawn(pythonCmd, [startScript], {
227
+ cwd: agentDir,
228
+ detached: true,
229
+ stdio: 'ignore',
230
+ shell: process.platform === 'win32',
231
+ });
232
+ child.unref();
233
+ // Give it a moment to start
234
+ await new Promise((resolve) => setTimeout(resolve, 2000));
235
+ spinnerStart.succeed('Memory Agent started (background)');
236
+ } catch (err) {
237
+ spinnerStart.fail('Failed to start Memory Agent');
238
+ errors.push('Start failed: ' + err.message);
239
+ }
240
+ } else {
241
+ spinnerStart.warn('main.py not found, skipping auto-start');
242
+ }
243
+ }
244
+
245
+ return { success: errors.length === 0, errors };
246
+ }
247
+
248
+ /**
249
+ * Basic setup fallback when onboarding wizard dependencies are not available.
250
+ * Writes a default .env file and attempts Python dependency installation.
251
+ * @param {string} agentDir - Path to the agent directory
252
+ */
253
+ async function basicSetup(agentDir) {
254
+ const config = {
255
+ scope: 'global',
256
+ projectPath: null,
257
+ provider: 'sentence-transformers',
258
+ model: 'BAAI/bge-base-en-v1.5',
259
+ port: 8102,
260
+ host: '127.0.0.1',
261
+ autoStart: false,
262
+ logLevel: 'INFO',
263
+ dbPath: null,
264
+ authEnabled: false,
265
+ hotTierDays: 14,
266
+ warmTierDays: 90,
267
+ hooks: ['session_start', 'grounding', 'session_end'],
268
+ };
269
+
270
+ // Write .env
271
+ const envContent = buildEnvContent(config, agentDir);
272
+ const envPath = path.join(agentDir, '.env');
273
+ if (!fs.existsSync(envPath)) {
274
+ fs.writeFileSync(envPath, envContent, 'utf8');
275
+ console.log('Created default .env configuration');
276
+ }
277
+
278
+ // Install Python deps
279
+ const pythonCmd = detectPythonCmd();
280
+ if (pythonCmd) {
281
+ const reqPath = path.join(agentDir, 'requirements.txt');
282
+ if (fs.existsSync(reqPath)) {
283
+ runSync(pythonCmd + ' -m pip install -r requirements.txt -q --disable-pip-version-check', agentDir);
284
+ console.log('Python dependencies installed');
285
+ }
286
+ }
287
+
288
+ console.log('Basic setup complete. Run "claude-memory-agent install" for full configuration.');
289
+ }
290
+
291
+ module.exports = { runInstaller, basicSetup };
@@ -0,0 +1,95 @@
1
+ 'use strict';
2
+
3
+ const chalk = require('chalk');
4
+
5
+ /**
6
+ * Embedding models available through the Ollama provider.
7
+ */
8
+ const OLLAMA_MODELS = [
9
+ { name: 'all-minilm', tier: 'Light', dim: 384, size: '~46MB', speed: 5, desc: 'Quick searches, low resources' },
10
+ { name: 'nomic-embed-text', tier: 'Standard', dim: 768, size: '~274MB', speed: 4, desc: 'General purpose (recommended)', recommended: true },
11
+ { name: 'mxbai-embed-large', tier: 'Pro', dim: 1024, size: '~670MB', speed: 3, desc: 'High-precision recall' },
12
+ { name: 'snowflake-arctic-embed', tier: 'Pro', dim: 1024, size: '~670MB', speed: 3, desc: 'Multilingual projects' },
13
+ { name: 'bge-m3', tier: 'Pro', dim: 1024, size: '~1.2GB', speed: 2, desc: 'Dense retrieval, research' },
14
+ ];
15
+
16
+ /**
17
+ * Embedding models for the sentence-transformers (standalone) provider.
18
+ */
19
+ const STANDALONE_MODELS = [
20
+ { name: 'all-MiniLM-L6-v2', tier: 'Light', dim: 384, size: '~80MB', speed: 5, desc: 'Fastest, minimal resources' },
21
+ { name: 'BAAI/bge-base-en-v1.5', tier: 'Standard', dim: 768, size: '~440MB', speed: 4, desc: 'Good balance (recommended)', recommended: true },
22
+ { name: 'Alibaba-NLP/gte-large-en-v1.5', tier: 'Pro', dim: 1024, size: '~1.5GB', speed: 3, desc: 'Best quality English' },
23
+ ];
24
+
25
+ /**
26
+ * Return the model list for a given provider.
27
+ * @param {'ollama'|'sentence-transformers'} provider
28
+ * @returns {Array<Object>}
29
+ */
30
+ function getModelsByProvider(provider) {
31
+ if (provider === 'ollama') {
32
+ return OLLAMA_MODELS;
33
+ }
34
+ return STANDALONE_MODELS;
35
+ }
36
+
37
+ /**
38
+ * Build a star rating string: filled stars followed by empty stars, colored yellow.
39
+ * @param {number} count - Number of filled stars (out of 5)
40
+ * @returns {string}
41
+ */
42
+ function starsString(count) {
43
+ const filled = '\u2605'.repeat(count);
44
+ const empty = '\u2606'.repeat(5 - count);
45
+ return chalk.yellow(filled + empty);
46
+ }
47
+
48
+ /**
49
+ * Format a model object into a display string suitable for inquirer choices.
50
+ *
51
+ * Example output:
52
+ * [Light] all-minilm 384d ~46MB ***** Quick searches [installed]
53
+ *
54
+ * @param {Object} model - Model object with name, tier, dim, size, speed, desc
55
+ * @param {string[]} installedModels - Array of model names currently installed
56
+ * @returns {string}
57
+ */
58
+ function formatModelChoice(model, installedModels) {
59
+ const tierLabel = chalk.cyan('[' + model.tier + ']');
60
+ const tierPad = 10 - model.tier.length - 2; // account for brackets
61
+ const tierSpacing = ' '.repeat(Math.max(tierPad, 1));
62
+
63
+ const namePad = 26 - model.name.length;
64
+ const nameSpacing = ' '.repeat(Math.max(namePad, 1));
65
+
66
+ const dimStr = chalk.dim(model.dim + 'd');
67
+ const dimPad = 6 - String(model.dim).length;
68
+ const dimSpacing = ' '.repeat(Math.max(dimPad, 1));
69
+
70
+ const sizeStr = chalk.dim(model.size);
71
+ const sizePad = 8 - model.size.length;
72
+ const sizeSpacing = ' '.repeat(Math.max(sizePad, 1));
73
+
74
+ const rating = starsString(model.speed);
75
+
76
+ const isInstalled = Array.isArray(installedModels) && installedModels.includes(model.name);
77
+ const installedBadge = isInstalled ? chalk.green(' [installed]') : '';
78
+
79
+ return (
80
+ tierLabel + tierSpacing +
81
+ model.name + nameSpacing +
82
+ dimStr + dimSpacing +
83
+ sizeStr + sizeSpacing +
84
+ rating + ' ' +
85
+ model.desc +
86
+ installedBadge
87
+ );
88
+ }
89
+
90
+ module.exports = {
91
+ OLLAMA_MODELS,
92
+ STANDALONE_MODELS,
93
+ getModelsByProvider,
94
+ formatModelChoice,
95
+ };