#!/usr/bin/env node
import { Command } from 'commander';
import chalk from 'chalk';
import prompts from 'prompts';
import { spawn } from 'child_process';
import fs from 'fs/promises';
import path from 'path';
import { fileURLToPath } from 'url';
import os from 'os';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const program = new Command();
// Version from package.json
const packageJson = JSON.parse(
await fs.readFile(path.join(__dirname, '../package.json'), 'utf-8')
);
program
.name('persistent-context')
.description('AI memory management and context persistence system with MCP server')
.version(packageJson.version);
// Initialize command
program
.command('init')
.description('Initialize persistent context store configuration')
.option('--claude', 'Setup for Claude Desktop integration')
.option('--docker', 'Generate Docker configuration')
.option('--podman', 'Generate Podman configuration')
.action(async (options) => {
console.log(chalk.blue('š Initializing Persistent Context MCP...'));
const config = await prompts([
{
type: 'text',
name: 'neo4jUri',
message: 'Neo4j URI',
initial: 'bolt://localhost:7687'
},
{
type: 'text',
name: 'neo4jUser',
message: 'Neo4j Username',
initial: 'neo4j'
},
{
type: 'password',
name: 'neo4jPassword',
message: 'Neo4j Password',
initial: 'password'
},
{
type: 'number',
name: 'port',
message: 'MCP Server Port',
initial: 3000
},
{
type: 'text',
name: 'dataDir',
message: 'Data Directory',
initial: path.join(os.homedir(), '.persistent-context')
},
{
type: 'confirm',
name: 'useOpenAI',
message: 'Enable OpenAI integration?',
initial: false
}
]);
if (config.useOpenAI) {
const openaiConfig = await prompts([
{
type: 'password',
name: 'openaiApiKey',
message: 'OpenAI API Key'
}
]);
config.openaiApiKey = openaiConfig.openaiApiKey;
}
// Create data directory
await fs.mkdir(config.dataDir, { recursive: true });
// Generate .env file
const envContent = `# Persistent Context MCP Configuration
# Generated by: persistent-context init
# Neo4j Configuration
NEO4J_URI=${config.neo4jUri}
NEO4J_USER=${config.neo4jUser}
NEO4J_PASSWORD=${config.neo4jPassword}
# Server Configuration
PORT=${config.port}
NODE_ENV=production
LOG_LEVEL=info
# Data Storage
DATA_DIR=${config.dataDir}
BACKUP_DIR=${path.join(config.dataDir, 'backups')}
# AI Integration (Optional)
${config.openaiApiKey ? `OPENAI_API_KEY=${config.openaiApiKey}` : '# OPENAI_API_KEY=your-api-key'}
# Security - Recommended to store in secure vault
# For production, use:
# - AWS Secrets Manager
# - Azure Key Vault
# - HashiCorp Vault
# - Environment-specific secret management
SECRETS_PATH=${path.join(config.dataDir, 'secrets')}
# Feature Flags
ENABLE_UI=true
ENABLE_MCP=true
ENABLE_AUTO_BACKUP=true
BACKUP_INTERVAL_HOURS=24
`;
const envPath = path.join(process.cwd(), '.env');
await fs.writeFile(envPath, envContent);
console.log(chalk.green(`ā
Created .env file at ${envPath}`));
// Setup for Claude Desktop if requested
if (options.claude) {
await setupClaude(config);
}
// Generate Docker/Podman configs if requested
if (options.docker || options.podman) {
await generateContainerConfig(config, options.podman ? 'podman' : 'docker');
}
console.log(chalk.green('\nā
Initialization complete!'));
console.log(chalk.blue('\nNext steps:'));
console.log('1. Start Neo4j: ' + chalk.yellow('persistent-context neo4j start'));
console.log('2. Start MCP Server: ' + chalk.yellow('persistent-context start'));
console.log('3. Open UI: ' + chalk.yellow('persistent-context ui'));
});
// Start command
program
.command('start')
.description('Start the MCP server')
.option('-d, --daemon', 'Run as daemon')
.option('--ui', 'Also start the UI server')
.action(async (options) => {
console.log(chalk.blue('š Starting Persistent Context MCP Server...'));
const serverPath = path.join(__dirname, 'mcp-server.js');
if (options.daemon) {
const child = spawn('node', [serverPath], {
detached: true,
stdio: 'ignore'
});
child.unref();
console.log(chalk.green('ā
MCP Server started as daemon'));
} else {
const child = spawn('node', [serverPath], {
stdio: 'inherit'
});
if (options.ui) {
const uiChild = spawn('npm', ['run', 'start:ui'], {
stdio: 'inherit',
cwd: __dirname
});
}
}
});
// UI command
program
.command('ui')
.description('Start the web UI')
.option('-p, --port <port>', 'UI port', '5173')
.action(async (options) => {
console.log(chalk.blue(`šØ Starting UI on port ${options.port}...`));
spawn('vite', ['--port', options.port], {
stdio: 'inherit',
cwd: path.join(__dirname, '..')
});
});
// Neo4j commands
program
.command('neo4j <action>')
.description('Manage Neo4j database (start|stop|status)')
.action(async (action) => {
switch(action) {
case 'start':
console.log(chalk.blue('šļø Starting Neo4j...'));
spawn('docker', [
'run', '-d',
'--name', 'neo4j-pcmcp',
'-p', '7474:7474',
'-p', '7687:7687',
'-e', 'NEO4J_AUTH=neo4j/password',
'neo4j:5-community'
], { stdio: 'inherit' });
break;
case 'stop':
console.log(chalk.yellow('š Stopping Neo4j...'));
spawn('docker', ['stop', 'neo4j-pcmcp'], { stdio: 'inherit' });
spawn('docker', ['rm', 'neo4j-pcmcp'], { stdio: 'inherit' });
break;
case 'status':
spawn('docker', ['ps', '-a', '--filter', 'name=neo4j-pcmcp'], { stdio: 'inherit' });
break;
default:
console.log(chalk.red(`Unknown action: ${action}`));
console.log('Available actions: start, stop, status');
}
});
// Claude setup command
program
.command('claude-setup')
.description('Configure Claude Desktop to use this MCP server')
.action(async () => {
const config = await fs.readFile('.env', 'utf-8').catch(() => '');
const port = config.match(/PORT=(\d+)/)?.[1] || '3000';
await setupClaude({ port });
});
// Docker/Podman commands
program
.command('container <runtime>')
.description('Generate container configuration (docker|podman)')
.action(async (runtime) => {
if (runtime !== 'docker' && runtime !== 'podman') {
console.log(chalk.red('Runtime must be either "docker" or "podman"'));
return;
}
const config = await fs.readFile('.env', 'utf-8').catch(() => '');
await generateContainerConfig({}, runtime);
});
// Helper functions
async function setupClaude(config: any) {
console.log(chalk.blue('\nš± Setting up Claude Desktop integration...'));
const claudeConfigDir = path.join(os.homedir(), 'Library', 'Application Support', 'Claude');
const configPath = path.join(claudeConfigDir, 'claude_desktop_config.json');
try {
await fs.mkdir(claudeConfigDir, { recursive: true });
let existingConfig = {};
try {
const content = await fs.readFile(configPath, 'utf-8');
existingConfig = JSON.parse(content);
} catch {
// Config doesn't exist yet
}
const mcpConfig = {
...existingConfig,
mcpServers: {
...(existingConfig as any).mcpServers,
'persistent-context': {
command: 'persistent-context',
args: ['start'],
env: {
PORT: config.port?.toString() || '3000'
}
}
}
};
await fs.writeFile(configPath, JSON.stringify(mcpConfig, null, 2));
console.log(chalk.green(`ā
Claude Desktop configured at ${configPath}`));
console.log(chalk.yellow('ā ļø Please restart Claude Desktop to load the MCP server'));
} catch (error) {
console.log(chalk.red('ā Failed to configure Claude Desktop:'), error);
console.log(chalk.yellow('\nManual configuration:'));
console.log(`Add to ${configPath}:`);
console.log(chalk.gray(JSON.stringify({
mcpServers: {
'persistent-context': {
command: 'persistent-context',
args: ['start'],
env: {
PORT: config.port?.toString() || '3000'
}
}
}
}, null, 2)));
}
}
async function generateContainerConfig(config: any, runtime: 'docker' | 'podman') {
console.log(chalk.blue(`\nš³ Generating ${runtime} configuration...`));
const composeContent = `version: '3.8'
services:
neo4j:
image: neo4j:5-community
container_name: pcmcp-neo4j
ports:
- "7474:7474"
- "7687:7687"
environment:
- NEO4J_AUTH=\${NEO4J_USER}/\${NEO4J_PASSWORD}
- NEO4J_PLUGINS=["apoc"]
volumes:
- neo4j-data:/data
- neo4j-logs:/logs
networks:
- pcmcp-network
mcp-server:
image: persistent-context-mcp:latest
build:
context: .
dockerfile: Dockerfile
container_name: pcmcp-server
ports:
- "\${PORT}:3000"
- "5173:5173" # UI port
environment:
- NEO4J_URI=bolt://neo4j:7687
- NEO4J_USER=\${NEO4J_USER}
- NEO4J_PASSWORD=\${NEO4J_PASSWORD}
- NODE_ENV=production
volumes:
- ./data:/app/data
- ./backups:/app/backups
depends_on:
- neo4j
networks:
- pcmcp-network
restart: unless-stopped
networks:
pcmcp-network:
driver: bridge
volumes:
neo4j-data:
neo4j-logs:
`;
const filename = runtime === 'podman' ? 'podman-compose.yml' : 'docker-compose.yml';
await fs.writeFile(filename, composeContent);
console.log(chalk.green(`ā
Created ${filename}`));
console.log(chalk.blue('\nTo start services:'));
console.log(chalk.yellow(`${runtime}-compose up -d`));
}
// Parse arguments
program.parse(process.argv);