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.
- package/bin/cli.js +11 -1
- package/bin/lib/banner.js +39 -0
- package/bin/lib/environment.js +166 -0
- package/bin/lib/installer.js +291 -0
- package/bin/lib/models.js +95 -0
- package/bin/lib/steps/advanced.js +101 -0
- package/bin/lib/steps/confirm.js +87 -0
- package/bin/lib/steps/model.js +57 -0
- package/bin/lib/steps/provider.js +65 -0
- package/bin/lib/steps/scope.js +59 -0
- package/bin/lib/steps/server.js +74 -0
- package/bin/lib/ui.js +75 -0
- package/bin/onboarding.js +164 -0
- package/bin/postinstall.js +22 -257
- package/config.py +103 -4
- package/dashboard.html +697 -27
- package/hooks/extract_memories.py +439 -0
- package/hooks/pre_compact_hook.py +76 -0
- package/hooks/session_end_hook.py +149 -0
- package/hooks/stop_hook.py +372 -0
- package/install.py +91 -37
- package/main.py +1636 -892
- package/mcp_server.py +451 -0
- package/package.json +14 -3
- package/requirements.txt +12 -8
- package/services/adaptive_ranker.py +272 -0
- package/services/agent_catalog.json +153 -0
- package/services/agent_registry.py +245 -730
- package/services/claude_md_sync.py +320 -4
- package/services/consolidation.py +417 -0
- package/services/database.py +586 -105
- package/services/embedding_pipeline.py +262 -0
- package/services/embeddings.py +493 -85
- package/services/memory_decay.py +408 -0
- package/services/native_memory_paths.py +86 -0
- package/services/native_memory_sync.py +496 -0
- package/services/response_manager.py +183 -0
- package/services/terminal_ui.py +199 -0
- package/services/tier_manager.py +235 -0
- package/services/websocket.py +26 -6
- package/skills/search.py +136 -61
- package/skills/session_review.py +210 -23
- package/skills/store.py +125 -18
- package/terminal_dashboard.py +474 -0
- package/hooks/__pycache__/auto-detect-response.cpython-312.pyc +0 -0
- package/hooks/__pycache__/auto_capture.cpython-312.pyc +0 -0
- package/hooks/__pycache__/grounding-hook.cpython-312.pyc +0 -0
- package/hooks/__pycache__/session_end.cpython-312.pyc +0 -0
- package/hooks/__pycache__/session_start.cpython-312.pyc +0 -0
- package/services/__pycache__/__init__.cpython-312.pyc +0 -0
- package/services/__pycache__/agent_registry.cpython-312.pyc +0 -0
- package/services/__pycache__/auth.cpython-312.pyc +0 -0
- package/services/__pycache__/auto_inject.cpython-312.pyc +0 -0
- package/services/__pycache__/claude_md_sync.cpython-312.pyc +0 -0
- package/services/__pycache__/cleanup.cpython-312.pyc +0 -0
- package/services/__pycache__/compaction_flush.cpython-312.pyc +0 -0
- package/services/__pycache__/confidence.cpython-312.pyc +0 -0
- package/services/__pycache__/curator.cpython-312.pyc +0 -0
- package/services/__pycache__/daily_log.cpython-312.pyc +0 -0
- package/services/__pycache__/database.cpython-312.pyc +0 -0
- package/services/__pycache__/embeddings.cpython-312.pyc +0 -0
- package/services/__pycache__/insights.cpython-312.pyc +0 -0
- package/services/__pycache__/llm_analyzer.cpython-312.pyc +0 -0
- package/services/__pycache__/memory_md_sync.cpython-312.pyc +0 -0
- package/services/__pycache__/retry_queue.cpython-312.pyc +0 -0
- package/services/__pycache__/timeline.cpython-312.pyc +0 -0
- package/services/__pycache__/vector_index.cpython-312.pyc +0 -0
- package/services/__pycache__/websocket.cpython-312.pyc +0 -0
- package/skills/__pycache__/__init__.cpython-312.pyc +0 -0
- package/skills/__pycache__/admin.cpython-312.pyc +0 -0
- package/skills/__pycache__/checkpoint.cpython-312.pyc +0 -0
- package/skills/__pycache__/claude_md.cpython-312.pyc +0 -0
- package/skills/__pycache__/cleanup.cpython-312.pyc +0 -0
- package/skills/__pycache__/confidence_tracker.cpython-312.pyc +0 -0
- package/skills/__pycache__/context.cpython-312.pyc +0 -0
- package/skills/__pycache__/curator.cpython-312.pyc +0 -0
- package/skills/__pycache__/grounding.cpython-312.pyc +0 -0
- package/skills/__pycache__/insights.cpython-312.pyc +0 -0
- package/skills/__pycache__/natural_language.cpython-312.pyc +0 -0
- package/skills/__pycache__/retrieve.cpython-312.pyc +0 -0
- package/skills/__pycache__/search.cpython-312.pyc +0 -0
- package/skills/__pycache__/session_review.cpython-312.pyc +0 -0
- package/skills/__pycache__/state.cpython-312.pyc +0 -0
- package/skills/__pycache__/store.cpython-312.pyc +0 -0
- package/skills/__pycache__/summarize.cpython-312.pyc +0 -0
- package/skills/__pycache__/timeline.cpython-312.pyc +0 -0
- package/skills/__pycache__/verification.cpython-312.pyc +0 -0
- package/test_automation.py +0 -221
- package/test_complete.py +0 -338
- package/test_full.py +0 -322
- 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
|
-
|
|
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
|
+
};
|