permamind
Version:
An MCP server that provides an immortal memory layer for AI agents and clients
526 lines (450 loc) • 18.6 kB
JavaScript
import { readFileSync, writeFileSync, existsSync } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import { execSync } from 'child_process';
import { homedir, platform } from 'os';
import { createInterface } from 'readline';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Read package.json for version info
const packagePath = join(__dirname, '..', 'package.json');
const packageJson = JSON.parse(readFileSync(packagePath, 'utf8'));
function showBanner() {
const banner = `
██████╗ ███████╗██████╗ ███╗ ███╗ █████╗ ███╗ ███╗██╗███╗ ██╗██████╗
██╔══██╗██╔════╝██╔══██╗████╗ ████║██╔══██╗████╗ ████║██║████╗ ██║██╔══██╗
██████╔╝█████╗ ██████╔╝██╔████╔██║███████║██╔████╔██║██║██╔██╗ ██║██║ ██║
██╔═══╝ ██╔══╝ ██╔══██╗██║╚██╔╝██║██╔══██║██║╚██╔╝██║██║██║╚██╗██║██║ ██║
██║ ███████╗██║ ██║██║ ╚═╝ ██║██║ ██║██║ ╚═╝ ██║██║██║ ╚████║██████╔╝
╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝╚═════╝
🧠 Immortal Memory Layer for AI Agents • Built on Arweave • Powered by AO
`;
return banner;
}
function showCompactBanner() {
return `🧠 PERMAMIND - Immortal AI Memory • Built on Arweave • Powered by AO`;
}
const HELP_TEXT = `${showBanner()}
Permamind MCP Server v${packageJson.version}
${packageJson.description}
Usage:
permamind [options]
Options:
--help, -h Show this help message
--version, -v Show version information
--setup, -s Run configuration wizard
--config <path> Use custom configuration file
--test, -t Test server startup and configuration
--generate-seed Generate a new seed phrase
--export-seed Export current server seed phrase
--import-seed Import an existing seed phrase
--info Show server information
Environment Variables:
SEED_PHRASE Seed phrase for Arweave wallet
HUB_REGISTRY_ID Custom hub registry ID (optional)
Examples:
permamind # Start the MCP server
permamind --setup # Run configuration wizard
permamind --test # Test server configuration
permamind --generate-seed # Generate new seed phrase
permamind --export-seed # Export current seed phrase
permamind --import-seed # Import existing seed phrase
For more information, visit: ${packageJson.homepage}
`;
function showVersion() {
process.stdout.write(`permamind v${packageJson.version}\n`);
}
function showHelp() {
process.stdout.write(HELP_TEXT);
}
async function generateSeedPhrase() {
console.log(showBanner());
console.log('🔐 Seed Phrase Generator');
console.log('');
const rl = createInterface({
input: process.stdin,
output: process.stdout
});
try {
// Import the mnemonic generation functionality
let generateMnemonic;
try {
({ generateMnemonic } = await import('../dist/mnemonic.js'));
} catch (importErr) {
// Try to build if dist is missing
const distPath = join(__dirname, '..', 'dist');
if (!existsSync(distPath)) {
process.stderr.write('📦 Building permamind...\n');
execSync('npm run build', {
cwd: join(__dirname, '..'),
stdio: 'inherit'
});
({ generateMnemonic } = await import('../dist/mnemonic.js'));
} else {
throw importErr;
}
}
const seedPhrase = await generateMnemonic();
process.stderr.write('🎲 Generated new seed phrase:\n');
process.stderr.write(seedPhrase + '\n');
process.stderr.write('\n⚠️ IMPORTANT: Store this seed phrase securely!\n');
process.stderr.write('Without it, you will lose access to your Arweave wallet and stored memories.\n');
const save = await new Promise(resolve => {
rl.question('\nWould you like to save this seed phrase? (y/n): ', resolve);
});
if (save.toLowerCase() === 'y') {
const choice = await new Promise(resolve => {
rl.question('\nHow would you like to save it?\n1. Environment variable (recommended)\n2. Save to config file\n3. Both\nEnter choice (1-3): ', resolve);
});
if (choice === '1' || choice === '3') {
process.stderr.write('\n📝 Add to your shell profile (.bashrc, .zshrc, etc.):');
process.stderr.write(`export SEED_PHRASE="${seedPhrase}"`);
if (platform() === 'win32') {
process.stderr.write('\n📝 For Windows:');
process.stderr.write(`set SEED_PHRASE=${seedPhrase}`);
process.stderr.write('Or PowerShell:');
process.stderr.write(`$env:SEED_PHRASE="${seedPhrase}"`);
}
}
if (choice === '2' || choice === '3') {
const configPath = getSeedConfigPath();
const configDir = dirname(configPath);
// Ensure directory exists
const fs = await import('fs');
if (!fs.existsSync(configDir)) {
fs.mkdirSync(configDir, { recursive: true });
}
const config = {
seedPhrase: seedPhrase,
createdAt: new Date().toISOString(),
source: 'generated'
};
writeFileSync(configPath, JSON.stringify(config, null, 2));
process.stderr.write(`\n💾 Seed phrase saved to: ${configPath}`);
process.stderr.write('⚠️ Keep this file secure and backed up!');
}
} else {
process.stderr.write('\n💡 Remember to save this seed phrase manually!');
process.stderr.write('You can set it later with: permamind --import-seed');
}
} catch (err) {
if (err.code === 'MODULE_NOT_FOUND') {
console.error('Error importing mnemonic module:', err.message);
console.error('This typically means the project is not built properly.');
console.error('Please run: npm run build');
} else {
console.error('Error generating seed phrase:', err.message);
console.error('If the problem persists, please file an issue: https://github.com/ALLiDoizCode/Permamind/issues');
}
process.exit(1);
} finally {
rl.close();
}
}
function getSeedConfigPath() {
const home = homedir();
const os = platform();
switch (os) {
case 'darwin':
return join(home, '.config', 'permamind', 'seed.json');
case 'win32':
return join(home, 'AppData', 'Roaming', 'permamind', 'seed.json');
default:
return join(home, '.config', 'permamind', 'seed.json');
}
}
async function exportSeedPhrase() {
try {
// Check if there's an environment variable first
if (process.env.SEED_PHRASE) {
process.stderr.write('🔑 Current seed phrase (from environment):');
process.stderr.write(process.env.SEED_PHRASE);
process.stderr.write('\n💡 This seed phrase is set via environment variable');
return;
}
// Check if there's a saved seed phrase
const configPath = getSeedConfigPath();
if (existsSync(configPath)) {
try {
const config = JSON.parse(readFileSync(configPath, 'utf8'));
if (config.seedPhrase) {
process.stderr.write('🔑 Current seed phrase (from saved config):');
process.stderr.write(config.seedPhrase);
process.stderr.write('\n💡 This seed phrase is saved in:', configPath);
return;
}
} catch (err) {
console.warn('⚠️ Could not read saved seed phrase config');
}
}
// Try to get seed phrase from a temporary server startup
process.stderr.write('🔍 No persistent seed phrase found.');
process.stderr.write('💡 The server generates temporary seed phrases when none is provided.');
process.stderr.write('');
process.stderr.write('To get a persistent seed phrase:');
process.stderr.write('1. Generate one: permamind --generate-seed');
process.stderr.write('2. Import existing: permamind --import-seed');
process.stderr.write('3. Set environment: export SEED_PHRASE="your phrase here"');
} catch (err) {
console.error('❌ Error exporting seed phrase:', err.message);
process.exit(1);
}
}
async function importSeedPhrase() {
const rl = createInterface({
input: process.stdin,
output: process.stdout
});
try {
process.stderr.write('🔐 Import Existing Seed Phrase');
process.stderr.write('===============================\n');
const seedPhrase = await new Promise(resolve => {
rl.question('Enter your 12-word seed phrase: ', resolve);
});
if (!seedPhrase || seedPhrase.trim().split(' ').length !== 12) {
console.error('❌ Invalid seed phrase. Must be exactly 12 words.');
process.exit(1);
}
// Validate the seed phrase by trying to import it
try {
let validateMnemonic;
try {
({ validateMnemonic } = await import('../dist/mnemonic.js'));
} catch (importErr) {
// Try to build if dist is missing
const distPath = join(__dirname, '..', 'dist');
if (!existsSync(distPath)) {
process.stderr.write('📦 Building permamind for validation...\n');
execSync('npm run build', {
cwd: join(__dirname, '..'),
stdio: 'inherit'
});
({ validateMnemonic } = await import('../dist/mnemonic.js'));
} else {
throw importErr;
}
}
if (!(await validateMnemonic(seedPhrase.trim()))) {
console.error('❌ Invalid seed phrase. Please check and try again.');
process.exit(1);
}
} catch (err) {
console.warn('⚠️ Could not validate seed phrase:', err.message);
}
const choice = await new Promise(resolve => {
rl.question('\nHow would you like to save this seed phrase?\n1. Environment variable (recommended)\n2. Save to config file\n3. Both\nEnter choice (1-3): ', resolve);
});
const trimmedSeed = seedPhrase.trim();
if (choice === '1' || choice === '3') {
process.stderr.write('\n📝 Add to your shell profile (.bashrc, .zshrc, etc.):');
process.stderr.write(`export SEED_PHRASE="${trimmedSeed}"`);
if (platform() === 'win32') {
process.stderr.write('\n📝 For Windows:');
process.stderr.write(`set SEED_PHRASE=${trimmedSeed}`);
process.stderr.write('Or PowerShell:');
process.stderr.write(`$env:SEED_PHRASE="${trimmedSeed}"`);
}
}
if (choice === '2' || choice === '3') {
const configPath = getSeedConfigPath();
const configDir = dirname(configPath);
// Ensure directory exists
const fs = await import('fs');
if (!fs.existsSync(configDir)) {
fs.mkdirSync(configDir, { recursive: true });
}
const config = {
seedPhrase: trimmedSeed,
createdAt: new Date().toISOString(),
source: 'imported'
};
writeFileSync(configPath, JSON.stringify(config, null, 2));
process.stderr.write(`\n💾 Seed phrase saved to: ${configPath}`);
process.stderr.write('⚠️ Keep this file secure and backed up!');
}
process.stderr.write('\n✅ Seed phrase imported successfully!');
process.stderr.write('🔄 Restart your MCP clients to use the new seed phrase.');
} catch (err) {
console.error('❌ Error importing seed phrase:', err.message);
process.exit(1);
} finally {
rl.close();
}
}
async function testServer() {
process.stderr.write(showCompactBanner() + '\n');
process.stderr.write('🔧 Testing Permamind server configuration...');
// Check if dist directory exists
const distPath = join(__dirname, '..', 'dist');
try {
const fs = await import('fs');
if (!fs.existsSync(distPath)) {
console.error('Error: Distribution files not found. Run: npm run build');
process.exit(1);
}
} catch (err) {
console.error('Error checking distribution files:', err.message);
process.exit(1);
}
// Check environment variables
if (!process.env.SEED_PHRASE) {
console.warn('Warning: SEED_PHRASE environment variable not set');
process.stderr.write('Generate one with: permamind --generate-seed');
}
process.stderr.write('✓ Configuration looks good');
process.stderr.write('Start the server with: permamind');
}
async function startServer(configPath) {
try {
const serverPath = join(__dirname, '..', 'dist', 'server.js');
// Check if server file exists
const fs = await import('fs');
if (!fs.existsSync(serverPath)) {
console.error('❌ Error: Server file not found at:', serverPath);
console.error('📦 Please build the project first: npm run build');
console.error('🔧 If you installed globally, the build should be included');
console.error('🐛 If this persists, file an issue: https://github.com/ALLiDoizCode/Permamind/issues');
process.exit(1);
}
// Validate Node.js version
const nodeVersion = process.version;
const majorVersion = parseInt(nodeVersion.slice(1).split('.')[0]);
if (majorVersion < 20) {
console.error(`❌ Error: Node.js ${nodeVersion} is not supported`);
console.error('🟢 Please upgrade to Node.js 20 or higher');
console.error('📥 Download from: https://nodejs.org/');
process.exit(1);
}
// Set config path if provided
if (configPath) {
if (!fs.existsSync(configPath)) {
console.error('❌ Error: Config file not found:', configPath);
process.exit(1);
}
process.env.CONFIG_PATH = configPath;
process.stderr.write('📝 Using config file:', configPath);
}
// Check for seed phrase from various sources
let seedPhrase = process.env.SEED_PHRASE;
if (!seedPhrase) {
// Check for saved seed phrase
const configPath = getSeedConfigPath();
if (existsSync(configPath)) {
try {
const config = JSON.parse(readFileSync(configPath, 'utf8'));
if (config.seedPhrase) {
seedPhrase = config.seedPhrase;
process.env.SEED_PHRASE = seedPhrase;
// Silent loading for MCP protocol compatibility
}
} catch (err) {
// Silent error handling for MCP protocol compatibility
}
}
}
// Note: Removing console output during MCP server startup to prevent JSON protocol interference
// Seed phrase validation occurs silently - use --test flag for diagnostics
// Start the server by importing and running it
// Note: No console output here as it interferes with MCP JSON protocol
await import(serverPath);
} catch (err) {
if (err.code === 'MODULE_NOT_FOUND') {
console.error('❌ Error: Missing dependencies or build files');
console.error('📦 Try: npm run build');
console.error('🔧 If globally installed, try: npm update -g permamind');
} else if (err.code === 'EACCES') {
console.error('❌ Error: Permission denied');
console.error('🔐 Check file permissions and try again');
} else {
console.error('❌ Error starting server:', err.message);
console.error('🔍 Stack trace:', err.stack);
}
console.error('\n🩺 Troubleshooting:');
console.error('1. Check Node.js version: node --version (need 20+)');
console.error('2. Rebuild project: npm run build');
console.error('3. Test configuration: permamind --test');
console.error('4. File an issue: https://github.com/ALLiDoizCode/Permamind/issues');
process.exit(1);
}
}
function runSetup() {
try {
execSync(`node "${join(__dirname, 'permamind-setup.js')}"`, { stdio: 'inherit' });
} catch (err) {
console.error('Error running setup wizard:', err.message);
process.exit(1);
}
}
// Parse command line arguments
const args = process.argv.slice(2);
if (args.length === 0) {
// No arguments, start the server
startServer().catch(err => {
console.error('Server failed to start:', err.message);
process.exit(1);
});
} else {
const command = args[0];
switch (command) {
case '--help':
case '-h':
showHelp();
break;
case '--version':
case '-v':
showVersion();
break;
case '--setup':
case '-s':
runSetup();
break;
case '--config':
if (args[1]) {
startServer(args[1]).catch(err => {
console.error('Server failed to start:', err.message);
process.exit(1);
});
} else {
console.error('Error: --config requires a file path');
process.exit(1);
}
break;
case '--test':
case '-t':
testServer().catch(err => {
console.error('Test failed:', err.message);
process.exit(1);
});
break;
case '--generate-seed':
generateSeedPhrase().catch(err => {
console.error('Seed generation failed:', err.message);
process.exit(1);
});
break;
case '--export-seed':
exportSeedPhrase().catch(err => {
console.error('Seed export failed:', err.message);
process.exit(1);
});
break;
case '--import-seed':
importSeedPhrase().catch(err => {
console.error('Seed import failed:', err.message);
process.exit(1);
});
break;
case '--info':
console.log(showBanner());
process.stdout.write(`Permamind MCP Server v${packageJson.version}\n`);
process.stdout.write(`Description: ${packageJson.description}\n`);
process.stdout.write(`Repository: ${packageJson.repository.url}\n`);
process.stdout.write(`Author: ${packageJson.author}\n`);
break;
default:
console.error(`Unknown command: ${command}`);
process.stderr.write('Use --help for available options');
process.exit(1);
}
}