claude-memory-agent 2.0.1 → 2.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.
- package/README.md +206 -206
- package/agent_card.py +186 -0
- package/bin/cli.js +327 -185
- 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 +35 -270
- package/config.py +103 -4
- package/dashboard.html +4902 -2689
- package/hooks/extract_memories.py +439 -0
- package/hooks/grounding-hook.py +422 -348
- package/hooks/pre_compact_hook.py +76 -0
- package/hooks/session_end.py +293 -192
- package/hooks/session_end_hook.py +149 -0
- package/hooks/session_start.py +227 -227
- package/hooks/stop_hook.py +372 -0
- package/install.py +972 -902
- package/main.py +5240 -2859
- package/mcp_server.py +451 -0
- package/package.json +58 -47
- package/requirements.txt +12 -8
- package/services/__init__.py +50 -50
- 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/curator.py +1606 -0
- package/services/database.py +4118 -2485
- 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/__init__.py +21 -1
- package/skills/confidence_tracker.py +441 -0
- package/skills/context.py +675 -0
- package/skills/curator.py +348 -0
- package/skills/search.py +444 -213
- package/skills/session_review.py +605 -0
- package/skills/store.py +484 -179
- package/terminal_dashboard.py +474 -0
- package/update_system.py +829 -817
- package/hooks/__pycache__/auto-detect-response.cpython-312.pyc +0 -0
- package/hooks/__pycache__/auto_capture.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__/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__/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__/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
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { confirm } = require('@inquirer/prompts');
|
|
4
|
+
const chalk = require('chalk');
|
|
5
|
+
const { stepHeader, printBox, TOTAL_STEPS } = require('../ui.js');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Step 7: Review & Install
|
|
9
|
+
* Displays a summary of all collected configuration and asks for confirmation.
|
|
10
|
+
*
|
|
11
|
+
* @param {{ scope: string, projectPath: string|null, provider: string, model: string,
|
|
12
|
+
* port: number, host: string, autoStart: boolean, logLevel?: string,
|
|
13
|
+
* dbPath?: string, authEnabled?: boolean, hotTierDays?: number,
|
|
14
|
+
* warmTierDays?: number, hooks?: string[] }} config
|
|
15
|
+
* @returns {boolean} true if user confirms, false to abort
|
|
16
|
+
*/
|
|
17
|
+
async function promptConfirm(config) {
|
|
18
|
+
stepHeader(7, TOTAL_STEPS, 'Review & Install');
|
|
19
|
+
|
|
20
|
+
// Format scope display
|
|
21
|
+
const scopeLabels = { global: 'Global', project: 'Project-specific', both: 'Both' };
|
|
22
|
+
const scopeDisplay = scopeLabels[config.scope] || config.scope;
|
|
23
|
+
|
|
24
|
+
// Format binding display
|
|
25
|
+
const bindingDisplay = config.host === '127.0.0.1'
|
|
26
|
+
? 'localhost only'
|
|
27
|
+
: 'all interfaces (0.0.0.0)';
|
|
28
|
+
|
|
29
|
+
// Format auto-start display
|
|
30
|
+
const autoStartDisplay = config.autoStart ? 'Yes' : 'No';
|
|
31
|
+
|
|
32
|
+
// Format log level (use default if not customized)
|
|
33
|
+
const logLevel = config.logLevel || 'INFO';
|
|
34
|
+
|
|
35
|
+
// Format auth display
|
|
36
|
+
const authDisplay = config.authEnabled ? 'Enabled' : 'Disabled';
|
|
37
|
+
|
|
38
|
+
// Format hooks display
|
|
39
|
+
const defaultHooks = ['session_start', 'grounding', 'session_end'];
|
|
40
|
+
const hooks = config.hooks || defaultHooks;
|
|
41
|
+
const hooksDisplay = hooks.join(', ');
|
|
42
|
+
|
|
43
|
+
const label = (text) => chalk.dim(text);
|
|
44
|
+
const value = (text) => chalk.white.bold(text);
|
|
45
|
+
|
|
46
|
+
const lines = [
|
|
47
|
+
`${label('Scope:')} ${value(scopeDisplay)}`,
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
if (config.projectPath) {
|
|
51
|
+
lines.push(`${label('Project:')} ${value(config.projectPath)}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
lines.push(
|
|
55
|
+
`${label('Provider:')} ${value(config.provider)}`,
|
|
56
|
+
`${label('Model:')} ${value(config.model)}`,
|
|
57
|
+
`${label('Port:')} ${value(String(config.port))}`,
|
|
58
|
+
`${label('Binding:')} ${value(bindingDisplay)}`,
|
|
59
|
+
`${label('Auto-start:')} ${value(autoStartDisplay)}`,
|
|
60
|
+
`${label('Log Level:')} ${value(logLevel)}`,
|
|
61
|
+
`${label('Auth:')} ${value(authDisplay)}`,
|
|
62
|
+
`${label('Hooks:')} ${value(hooksDisplay)}`,
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
if (config.dbPath) {
|
|
66
|
+
lines.push(`${label('DB Path:')} ${value(config.dbPath)}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (config.hotTierDays) {
|
|
70
|
+
lines.push(`${label('Hot Tier:')} ${value(config.hotTierDays + ' days')}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (config.warmTierDays) {
|
|
74
|
+
lines.push(`${label('Warm Tier:')} ${value(config.warmTierDays + ' days')}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
printBox('Installation Summary', lines);
|
|
78
|
+
|
|
79
|
+
const proceed = await confirm({
|
|
80
|
+
message: 'Proceed with installation?',
|
|
81
|
+
default: true,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
return proceed;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
module.exports = { promptConfirm };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { select, confirm } = require('@inquirer/prompts');
|
|
4
|
+
const chalk = require('chalk');
|
|
5
|
+
const { stepHeader, TOTAL_STEPS } = require('../ui.js');
|
|
6
|
+
const { getModelsByProvider, formatModelChoice } = require('../models.js');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Step 4: Embedding Model
|
|
10
|
+
* Prompts the user to choose an embedding model based on their selected provider.
|
|
11
|
+
*
|
|
12
|
+
* @param {'ollama'|'sentence-transformers'} provider
|
|
13
|
+
* @param {{ ollama: { running: boolean, models: string[] } }} env
|
|
14
|
+
* @returns {{ model: string, pullModel: boolean }}
|
|
15
|
+
*/
|
|
16
|
+
async function promptModel(provider, env) {
|
|
17
|
+
stepHeader(4, TOTAL_STEPS, 'Embedding Model');
|
|
18
|
+
|
|
19
|
+
console.log(chalk.dim(' Models are ordered from lightest to most powerful:\n'));
|
|
20
|
+
|
|
21
|
+
const installedModels = (env.ollama && env.ollama.models) || [];
|
|
22
|
+
const models = getModelsByProvider(provider);
|
|
23
|
+
|
|
24
|
+
const choices = models.map((model) => ({
|
|
25
|
+
name: formatModelChoice(model, installedModels),
|
|
26
|
+
value: model.name,
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
// Find and mark the recommended model as default
|
|
30
|
+
const recommendedIndex = models.findIndex((m) => m.recommended);
|
|
31
|
+
if (recommendedIndex !== -1) {
|
|
32
|
+
choices[recommendedIndex].default = true;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const defaultModel = recommendedIndex !== -1 ? models[recommendedIndex].name : undefined;
|
|
36
|
+
|
|
37
|
+
const model = await select({
|
|
38
|
+
message: 'Select an embedding model:',
|
|
39
|
+
choices,
|
|
40
|
+
default: defaultModel,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
let pullModel = false;
|
|
44
|
+
|
|
45
|
+
if (provider === 'ollama' && !installedModels.includes(model)) {
|
|
46
|
+
console.log(chalk.yellow('\n This model is not installed in Ollama.'));
|
|
47
|
+
|
|
48
|
+
pullModel = await confirm({
|
|
49
|
+
message: `Download it now? (ollama pull ${model})`,
|
|
50
|
+
default: true,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return { model, pullModel };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = { promptModel };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { select, confirm } = require('@inquirer/prompts');
|
|
4
|
+
const chalk = require('chalk');
|
|
5
|
+
const { stepHeader, printBox, TOTAL_STEPS } = require('../ui.js');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Step 3: Embedding Provider
|
|
9
|
+
* Prompts the user to choose between Ollama and sentence-transformers.
|
|
10
|
+
*
|
|
11
|
+
* @param {{ ollama: { running: boolean, models: string[] } }} env
|
|
12
|
+
* @returns {{ provider: 'ollama'|'sentence-transformers' }}
|
|
13
|
+
*/
|
|
14
|
+
async function promptProvider(env) {
|
|
15
|
+
stepHeader(3, TOTAL_STEPS, 'Embedding Provider');
|
|
16
|
+
|
|
17
|
+
const ollamaRunning = env.ollama && env.ollama.running;
|
|
18
|
+
const ollamaStatus = ollamaRunning
|
|
19
|
+
? chalk.green(' (running)')
|
|
20
|
+
: chalk.yellow(' (not detected)');
|
|
21
|
+
|
|
22
|
+
let provider = null;
|
|
23
|
+
|
|
24
|
+
while (provider === null) {
|
|
25
|
+
const choice = await select({
|
|
26
|
+
message: 'Which embedding provider would you like to use?',
|
|
27
|
+
choices: [
|
|
28
|
+
{
|
|
29
|
+
name: `Standalone (sentence-transformers) - runs locally, no external service needed`,
|
|
30
|
+
value: 'sentence-transformers',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: `Ollama - uses Ollama for embeddings${ollamaStatus}`,
|
|
34
|
+
value: 'ollama',
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
if (choice === 'ollama' && !ollamaRunning) {
|
|
40
|
+
printBox('Warning', [
|
|
41
|
+
chalk.yellow('Ollama is not running. To install:'),
|
|
42
|
+
'',
|
|
43
|
+
' 1. Download from https://ollama.ai/download',
|
|
44
|
+
' 2. Run: ollama serve',
|
|
45
|
+
' 3. Then re-run this installer',
|
|
46
|
+
]);
|
|
47
|
+
|
|
48
|
+
const continueAnyway = await confirm({
|
|
49
|
+
message: 'Continue with Ollama anyway?',
|
|
50
|
+
default: false,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
if (!continueAnyway) {
|
|
54
|
+
// Re-prompt by continuing the loop
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
provider = choice;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return { provider };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
module.exports = { promptProvider };
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { select, input } = require('@inquirer/prompts');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const chalk = require('chalk');
|
|
7
|
+
const { stepHeader, TOTAL_STEPS } = require('../ui.js');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Step 2: Installation Scope
|
|
11
|
+
* Prompts the user to choose where the memory agent configuration is installed.
|
|
12
|
+
*
|
|
13
|
+
* @returns {{ scope: 'global'|'project'|'both', projectPath: string|null }}
|
|
14
|
+
*/
|
|
15
|
+
async function promptScope() {
|
|
16
|
+
stepHeader(2, TOTAL_STEPS, 'Installation Scope');
|
|
17
|
+
|
|
18
|
+
const scope = await select({
|
|
19
|
+
message: 'Where should the memory agent be configured?',
|
|
20
|
+
choices: [
|
|
21
|
+
{
|
|
22
|
+
name: 'Global (recommended)',
|
|
23
|
+
value: 'global',
|
|
24
|
+
description: 'Configures ~/.claude/settings.json',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: 'Project-specific',
|
|
28
|
+
value: 'project',
|
|
29
|
+
description: 'Configures .claude/settings.local.json in your project',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: 'Both',
|
|
33
|
+
value: 'both',
|
|
34
|
+
description: 'Global config + project-specific override',
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
let projectPath = null;
|
|
40
|
+
|
|
41
|
+
if (scope === 'project' || scope === 'both') {
|
|
42
|
+
projectPath = await input({
|
|
43
|
+
message: 'Project directory path:',
|
|
44
|
+
default: process.cwd(),
|
|
45
|
+
validate: (value) => {
|
|
46
|
+
const resolved = path.resolve(value);
|
|
47
|
+
if (!fs.existsSync(resolved)) {
|
|
48
|
+
return `Directory does not exist: ${resolved}`;
|
|
49
|
+
}
|
|
50
|
+
return true;
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
projectPath = path.resolve(projectPath);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return { scope, projectPath };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
module.exports = { promptScope };
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { input, select, confirm } = require('@inquirer/prompts');
|
|
4
|
+
const chalk = require('chalk');
|
|
5
|
+
const net = require('net');
|
|
6
|
+
const { stepHeader, TOTAL_STEPS } = require('../ui.js');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Check if a port is available by attempting to listen on it briefly.
|
|
10
|
+
*
|
|
11
|
+
* @param {number} port
|
|
12
|
+
* @returns {Promise<boolean>} true if the port is available
|
|
13
|
+
*/
|
|
14
|
+
function isPortAvailable(port) {
|
|
15
|
+
return new Promise((resolve) => {
|
|
16
|
+
const server = net.createServer();
|
|
17
|
+
server.once('error', () => resolve(false));
|
|
18
|
+
server.once('listening', () => {
|
|
19
|
+
server.close(() => resolve(true));
|
|
20
|
+
});
|
|
21
|
+
server.listen(port, '127.0.0.1');
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Step 5: Server Settings
|
|
27
|
+
* Prompts the user for port, host binding, and auto-start preference.
|
|
28
|
+
*
|
|
29
|
+
* @returns {{ port: number, host: string, autoStart: boolean }}
|
|
30
|
+
*/
|
|
31
|
+
async function promptServer() {
|
|
32
|
+
stepHeader(5, TOTAL_STEPS, 'Server Settings');
|
|
33
|
+
|
|
34
|
+
const portStr = await input({
|
|
35
|
+
message: 'Server port:',
|
|
36
|
+
default: '8102',
|
|
37
|
+
validate: async (value) => {
|
|
38
|
+
const num = Number(value);
|
|
39
|
+
if (!Number.isInteger(num) || num < 1 || num > 65535) {
|
|
40
|
+
return 'Port must be a number between 1 and 65535';
|
|
41
|
+
}
|
|
42
|
+
const available = await isPortAvailable(num);
|
|
43
|
+
if (!available) {
|
|
44
|
+
return `Port ${num} is already in use`;
|
|
45
|
+
}
|
|
46
|
+
return true;
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const port = Number(portStr);
|
|
51
|
+
|
|
52
|
+
const host = await select({
|
|
53
|
+
message: 'Dashboard binding:',
|
|
54
|
+
choices: [
|
|
55
|
+
{
|
|
56
|
+
name: 'Localhost only (127.0.0.1) - Secure, recommended',
|
|
57
|
+
value: '127.0.0.1',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: 'All interfaces (0.0.0.0) - Access from other devices',
|
|
61
|
+
value: '0.0.0.0',
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const autoStart = await confirm({
|
|
67
|
+
message: 'Start the agent after installation?',
|
|
68
|
+
default: true,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
return { port, host, autoStart };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
module.exports = { promptServer };
|
package/bin/lib/ui.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const boxen = require('boxen');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Total number of steps in the onboarding wizard.
|
|
8
|
+
*/
|
|
9
|
+
const TOTAL_STEPS = 7;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Print a step header for the wizard.
|
|
13
|
+
* Renders as:
|
|
14
|
+
*
|
|
15
|
+
* Step 3/7 -- Embedding Model
|
|
16
|
+
* ----------------------------------------
|
|
17
|
+
*
|
|
18
|
+
* @param {number} step - Current step number
|
|
19
|
+
* @param {number} total - Total number of steps
|
|
20
|
+
* @param {string} title - Step title
|
|
21
|
+
*/
|
|
22
|
+
function stepHeader(step, total, title) {
|
|
23
|
+
console.log('');
|
|
24
|
+
console.log(' ' + chalk.bold.cyan('Step ' + step + '/' + total) + chalk.bold(' \u2014 ' + title));
|
|
25
|
+
console.log(' ' + chalk.dim('\u2500'.repeat(40)));
|
|
26
|
+
console.log('');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Return a star rating string (filled + empty, out of 5), colored yellow.
|
|
31
|
+
* @param {number} count - Number of filled stars
|
|
32
|
+
* @returns {string}
|
|
33
|
+
*/
|
|
34
|
+
function stars(count) {
|
|
35
|
+
const filled = '\u2605'.repeat(Math.min(count, 5));
|
|
36
|
+
const empty = '\u2606'.repeat(Math.max(5 - count, 0));
|
|
37
|
+
return chalk.yellow(filled + empty);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Print a bordered box with a title and content lines.
|
|
42
|
+
* Uses the boxen package for drawing.
|
|
43
|
+
*
|
|
44
|
+
* @param {string} title - Box title
|
|
45
|
+
* @param {string[]} lines - Array of content lines to display inside the box
|
|
46
|
+
*/
|
|
47
|
+
function printBox(title, lines) {
|
|
48
|
+
const content = lines.join('\n');
|
|
49
|
+
const box = boxen(content, {
|
|
50
|
+
title: title,
|
|
51
|
+
titleAlignment: 'left',
|
|
52
|
+
padding: 1,
|
|
53
|
+
margin: { top: 0, bottom: 0, left: 2, right: 0 },
|
|
54
|
+
borderColor: 'cyan',
|
|
55
|
+
borderStyle: 'round',
|
|
56
|
+
});
|
|
57
|
+
console.log(box);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Print a thin horizontal divider across the terminal width.
|
|
62
|
+
* Falls back to 60 columns if terminal width is not available.
|
|
63
|
+
*/
|
|
64
|
+
function divider() {
|
|
65
|
+
const width = process.stdout.columns || 60;
|
|
66
|
+
console.log(chalk.dim('\u2500'.repeat(width)));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
module.exports = {
|
|
70
|
+
TOTAL_STEPS,
|
|
71
|
+
stepHeader,
|
|
72
|
+
stars,
|
|
73
|
+
printBox,
|
|
74
|
+
divider,
|
|
75
|
+
};
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { spawn } = require('child_process');
|
|
6
|
+
|
|
7
|
+
const AGENT_DIR = path.resolve(path.dirname(__dirname));
|
|
8
|
+
|
|
9
|
+
async function runWizard() {
|
|
10
|
+
// Import lib modules
|
|
11
|
+
const { printBanner } = require('./lib/banner');
|
|
12
|
+
const { detectEnvironment, printEnvironment } = require('./lib/environment');
|
|
13
|
+
const { stepHeader, TOTAL_STEPS } = require('./lib/ui');
|
|
14
|
+
const { promptScope } = require('./lib/steps/scope');
|
|
15
|
+
const { promptProvider } = require('./lib/steps/provider');
|
|
16
|
+
const { promptModel } = require('./lib/steps/model');
|
|
17
|
+
const { promptServer } = require('./lib/steps/server');
|
|
18
|
+
const { promptAdvanced } = require('./lib/steps/advanced');
|
|
19
|
+
const { promptConfirm } = require('./lib/steps/confirm');
|
|
20
|
+
const { runInstaller } = require('./lib/installer');
|
|
21
|
+
|
|
22
|
+
// Screen 1: Banner + Environment Detection
|
|
23
|
+
printBanner();
|
|
24
|
+
stepHeader(1, TOTAL_STEPS, 'Environment Detection');
|
|
25
|
+
const env = await detectEnvironment();
|
|
26
|
+
printEnvironment(env);
|
|
27
|
+
|
|
28
|
+
console.log(''); // spacing
|
|
29
|
+
|
|
30
|
+
// Screen 2: Installation Scope
|
|
31
|
+
const { scope, projectPath } = await promptScope();
|
|
32
|
+
|
|
33
|
+
// Screen 3: Embedding Provider
|
|
34
|
+
const { provider } = await promptProvider(env);
|
|
35
|
+
|
|
36
|
+
// Screen 4: Model Selection
|
|
37
|
+
const { model, pullModel } = await promptModel(provider, env);
|
|
38
|
+
|
|
39
|
+
// Pull Ollama model if needed
|
|
40
|
+
if (pullModel && provider === 'ollama') {
|
|
41
|
+
const ora = require('ora');
|
|
42
|
+
const spinner = ora(`Downloading ${model}...`).start();
|
|
43
|
+
try {
|
|
44
|
+
const { execSync } = require('child_process');
|
|
45
|
+
execSync(`ollama pull ${model}`, { stdio: ['pipe', 'pipe', 'pipe'], timeout: 300000 });
|
|
46
|
+
spinner.succeed(`Downloaded ${model}`);
|
|
47
|
+
} catch (e) {
|
|
48
|
+
spinner.fail(`Failed to download ${model}. You can run "ollama pull ${model}" manually later.`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Screen 5: Server Settings
|
|
53
|
+
const { port, host, autoStart } = await promptServer();
|
|
54
|
+
|
|
55
|
+
// Screen 6: Advanced Settings
|
|
56
|
+
const advancedResult = await promptAdvanced();
|
|
57
|
+
|
|
58
|
+
// Build final config
|
|
59
|
+
const config = {
|
|
60
|
+
scope,
|
|
61
|
+
projectPath,
|
|
62
|
+
provider,
|
|
63
|
+
model,
|
|
64
|
+
port,
|
|
65
|
+
host,
|
|
66
|
+
autoStart,
|
|
67
|
+
logLevel: advancedResult ? advancedResult.logLevel : 'INFO',
|
|
68
|
+
dbPath: advancedResult ? advancedResult.dbPath : null,
|
|
69
|
+
authEnabled: advancedResult ? advancedResult.authEnabled : false,
|
|
70
|
+
hotTierDays: advancedResult ? advancedResult.hotTierDays : 14,
|
|
71
|
+
warmTierDays: advancedResult ? advancedResult.warmTierDays : 90,
|
|
72
|
+
hooks: advancedResult ? advancedResult.hooks : ['session_start', 'grounding', 'session_end'],
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// Screen 7: Review & Confirm
|
|
76
|
+
const confirmed = await promptConfirm(config);
|
|
77
|
+
if (!confirmed) {
|
|
78
|
+
console.log('\n Installation cancelled.\n');
|
|
79
|
+
process.exit(0);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Run Installation
|
|
83
|
+
console.log('');
|
|
84
|
+
const result = await runInstaller(config, AGENT_DIR);
|
|
85
|
+
|
|
86
|
+
if (result.success) {
|
|
87
|
+
const chalk = require('chalk');
|
|
88
|
+
const boxen = require('boxen');
|
|
89
|
+
|
|
90
|
+
const completionMsg = [
|
|
91
|
+
'',
|
|
92
|
+
chalk.green.bold(' Installation Complete!'),
|
|
93
|
+
'',
|
|
94
|
+
` Dashboard: ${chalk.cyan(`http://localhost:${port}/dashboard`)}`,
|
|
95
|
+
` Health: ${chalk.cyan(`http://localhost:${port}/health`)}`,
|
|
96
|
+
'',
|
|
97
|
+
chalk.dim(' Quick commands:'),
|
|
98
|
+
` ${chalk.bold('claude-memory-agent status')} - Check agent status`,
|
|
99
|
+
` ${chalk.bold('claude-memory-agent dashboard')} - Open dashboard`,
|
|
100
|
+
` ${chalk.bold('claude-memory-agent doctor')} - Diagnose issues`,
|
|
101
|
+
` ${chalk.bold('claude-memory-agent logs')} - View logs`,
|
|
102
|
+
'',
|
|
103
|
+
].join('\n');
|
|
104
|
+
|
|
105
|
+
console.log(boxen(completionMsg, {
|
|
106
|
+
padding: 1,
|
|
107
|
+
margin: 1,
|
|
108
|
+
borderStyle: 'round',
|
|
109
|
+
borderColor: 'green',
|
|
110
|
+
}));
|
|
111
|
+
} else {
|
|
112
|
+
console.log('\n Installation completed with errors:');
|
|
113
|
+
result.errors.forEach(e => console.log(` - ${e}`));
|
|
114
|
+
console.log('');
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Non-interactive fallback for CI/piped
|
|
119
|
+
function runDefaults() {
|
|
120
|
+
const { runInstaller } = require('./lib/installer');
|
|
121
|
+
const config = {
|
|
122
|
+
scope: 'global',
|
|
123
|
+
projectPath: null,
|
|
124
|
+
provider: 'sentence-transformers',
|
|
125
|
+
model: 'BAAI/bge-base-en-v1.5',
|
|
126
|
+
port: 8102,
|
|
127
|
+
host: '127.0.0.1',
|
|
128
|
+
autoStart: false,
|
|
129
|
+
logLevel: 'INFO',
|
|
130
|
+
dbPath: null,
|
|
131
|
+
authEnabled: false,
|
|
132
|
+
hotTierDays: 14,
|
|
133
|
+
warmTierDays: 90,
|
|
134
|
+
hooks: ['session_start', 'grounding', 'session_end'],
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
return runInstaller(config, AGENT_DIR);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Main entry
|
|
141
|
+
async function main() {
|
|
142
|
+
try {
|
|
143
|
+
if (!process.stdin.isTTY) {
|
|
144
|
+
// Non-interactive: use defaults silently
|
|
145
|
+
await runDefaults();
|
|
146
|
+
} else {
|
|
147
|
+
await runWizard();
|
|
148
|
+
}
|
|
149
|
+
} catch (err) {
|
|
150
|
+
if (err.name === 'ExitPromptError') {
|
|
151
|
+
// User pressed Ctrl+C
|
|
152
|
+
console.log('\n Setup cancelled.\n');
|
|
153
|
+
process.exit(0);
|
|
154
|
+
}
|
|
155
|
+
console.error('Setup error:', err.message);
|
|
156
|
+
process.exit(1);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (require.main === module) {
|
|
161
|
+
main();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
module.exports = { main, runWizard, runDefaults };
|