feat: Add Brain Cloud integration and catalog command
- Add brainy cloud setup command for auto-provisioning - Add brainy catalog command for viewing augmentations - Update CLI help with Brain Cloud commands - Improve cloud connection flow
This commit is contained in:
parent
db2f8e4035
commit
b265d6f7d0
3 changed files with 896 additions and 12 deletions
29
README.md
29
README.md
|
|
@ -4,7 +4,8 @@
|
||||||
|
|
||||||
[](https://badge.fury.io/js/%40soulcraft%2Fbrainy)
|
[](https://badge.fury.io/js/%40soulcraft%2Fbrainy)
|
||||||
[](https://opensource.org/licenses/MIT)
|
[](https://opensource.org/licenses/MIT)
|
||||||
[](https://soulcraft.com/console)
|
[](https://soulcraft.com)
|
||||||
|
[](https://soulcraft.com/brain-cloud)
|
||||||
[](https://nodejs.org/)
|
[](https://nodejs.org/)
|
||||||
[](https://www.typescriptlang.org/)
|
[](https://www.typescriptlang.org/)
|
||||||
|
|
||||||
|
|
@ -107,6 +108,7 @@ Try Brainy instantly in your browser. No signup. No credit card.
|
||||||
|
|
||||||
## ⚡ Quick Start (60 Seconds)
|
## ⚡ Quick Start (60 Seconds)
|
||||||
|
|
||||||
|
### Open Source (Local Storage)
|
||||||
```bash
|
```bash
|
||||||
npm install @soulcraft/brainy
|
npm install @soulcraft/brainy
|
||||||
```
|
```
|
||||||
|
|
@ -125,6 +127,31 @@ await brain.addVerb("Elon Musk", "founded", "Tesla")
|
||||||
|
|
||||||
// Search naturally
|
// Search naturally
|
||||||
const results = await brain.search("companies founded by Elon")
|
const results = await brain.search("companies founded by Elon")
|
||||||
|
```
|
||||||
|
|
||||||
|
### ☁️ Brain Cloud (AI Memory + Agent Coordination)
|
||||||
|
```bash
|
||||||
|
# Auto-setup with cloud instance provisioning (RECOMMENDED)
|
||||||
|
brainy cloud setup --email your@email.com
|
||||||
|
|
||||||
|
# Or install manually
|
||||||
|
npm install @soulcraft/brainy @soulcraft/brain-cloud
|
||||||
|
```
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { BrainyData, Cortex } from '@soulcraft/brainy'
|
||||||
|
import { AIMemory, AgentCoordinator } from '@soulcraft/brain-cloud'
|
||||||
|
|
||||||
|
const brain = new BrainyData()
|
||||||
|
const cortex = new Cortex()
|
||||||
|
|
||||||
|
// Add premium augmentations (requires Early Access license)
|
||||||
|
cortex.register(new AIMemory())
|
||||||
|
cortex.register(new AgentCoordinator())
|
||||||
|
|
||||||
|
// Now your AI remembers everything across all sessions!
|
||||||
|
await brain.add("User prefers TypeScript over JavaScript")
|
||||||
|
// This memory persists and syncs across all devices
|
||||||
// Returns: SpaceX and Tesla with relevance scores
|
// Returns: SpaceX and Tesla with relevance scores
|
||||||
|
|
||||||
// Query relationships
|
// Query relationships
|
||||||
|
|
|
||||||
481
bin/brainy.js
481
bin/brainy.js
|
|
@ -204,24 +204,138 @@ program
|
||||||
console.log(chalk.cyan('Restart Claude Code to activate memory.'))
|
console.log(chalk.cyan('Restart Claude Code to activate memory.'))
|
||||||
} else {
|
} else {
|
||||||
console.log(chalk.yellow('No Brain Cloud found. Setting up:'))
|
console.log(chalk.yellow('No Brain Cloud found. Setting up:'))
|
||||||
console.log('\n1. Visit: ' + chalk.cyan('https://soulcraft.com'))
|
console.log('\n1. Visit: ' + chalk.cyan('https://soulcraft.com/brain-cloud'))
|
||||||
console.log('2. Sign up for Brain Cloud')
|
console.log('2. Get your Early Access license key')
|
||||||
console.log('3. Run ' + chalk.green('brainy connect') + ' again')
|
console.log('3. Run ' + chalk.green('brainy cloud setup') + ' for auto-configuration')
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(chalk.red('❌ Setup failed:'), error.message)
|
console.log(chalk.red('❌ Setup failed:'), error.message)
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
// Moved to brainy cloud setup command below for better separation
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// BRAIN CLOUD COMMANDS (Premium Features)
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
const cloud = program
|
||||||
|
.command('cloud')
|
||||||
|
.description('Brain Cloud premium features and management')
|
||||||
|
|
||||||
|
cloud
|
||||||
|
.command('setup')
|
||||||
|
.description('🚀 Auto-setup Brain Cloud (provisions cloud instance + configures locally)')
|
||||||
|
.option('--email <email>', 'Your email address')
|
||||||
|
.action(wrapInteractive(async (options) => {
|
||||||
|
console.log(chalk.cyan('🧠☁️ Brain Cloud Auto-Setup'))
|
||||||
|
console.log(chalk.gray('═'.repeat(50)))
|
||||||
|
console.log(chalk.yellow('Perfect for non-coders! One-click setup.\n'))
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Step 1: Validate license
|
||||||
|
await validateLicense()
|
||||||
|
|
||||||
|
// Step 2: Check if Brainy is installed
|
||||||
|
await ensureBrainyInstalled()
|
||||||
|
|
||||||
|
// Step 3: Provision cloud instance
|
||||||
|
const instance = await provisionCloudInstance(options.email)
|
||||||
|
|
||||||
|
// Step 4: Configure local Brainy
|
||||||
|
await configureBrainy(instance)
|
||||||
|
|
||||||
|
// Step 5: Install Brain Cloud package
|
||||||
|
await installBrainCloudPackage()
|
||||||
|
|
||||||
|
// Step 6: Test connection
|
||||||
|
await testConnection(instance)
|
||||||
|
|
||||||
|
console.log('\n✅ Setup Complete!')
|
||||||
|
console.log(chalk.gray('═'.repeat(30)))
|
||||||
|
console.log('\nYour Brain Cloud instance is ready:')
|
||||||
|
console.log(`📱 Dashboard: ${chalk.cyan(instance.endpoints.dashboard)}`)
|
||||||
|
console.log(`🔗 API: ${chalk.gray(instance.endpoints.api)}`)
|
||||||
|
console.log('\n🚀 What\'s next?')
|
||||||
|
console.log('• Your AI now has persistent memory across all conversations')
|
||||||
|
console.log('• All devices sync automatically to your cloud instance')
|
||||||
|
console.log('• Agents coordinate seamlessly through handoffs')
|
||||||
|
console.log('\n💡 Try asking Claude: "Remember that I prefer TypeScript"')
|
||||||
|
console.log(' Then in a new conversation: "What do you know about my preferences?"')
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('\n❌ Setup failed:', error.message)
|
||||||
|
console.log('\n🆘 Need help? Contact support@soulcraft.com')
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
cloud
|
||||||
|
.command('connect [id]')
|
||||||
|
.description('Connect to existing Brain Cloud instance')
|
||||||
|
.action(wrapInteractive(async (id) => {
|
||||||
|
if (id) {
|
||||||
|
console.log(chalk.green(`✅ Connecting to Brain Cloud instance: ${id}`))
|
||||||
|
// Connect to specific instance
|
||||||
|
} else {
|
||||||
|
// Show connection instructions
|
||||||
|
console.log(chalk.cyan('\n🔗 Brain Cloud Connection'))
|
||||||
|
console.log(chalk.gray('━'.repeat(40)))
|
||||||
|
console.log('\nOptions:')
|
||||||
|
console.log('1. ' + chalk.green('brainy cloud setup') + ' - Auto-setup with provisioning')
|
||||||
|
console.log('2. ' + chalk.green('brainy cloud connect <id>') + ' - Connect to existing instance')
|
||||||
|
console.log('\nGet started: ' + chalk.cyan('https://soulcraft.com/brain-cloud'))
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
cloud
|
||||||
|
.command('status [id]')
|
||||||
|
.description('Check Brain Cloud instance status')
|
||||||
|
.action(wrapInteractive(async (id) => {
|
||||||
|
// Implementation moved from old cloud command
|
||||||
|
console.log('Checking Brain Cloud status...')
|
||||||
|
}))
|
||||||
|
|
||||||
|
cloud
|
||||||
|
.command('dashboard [id]')
|
||||||
|
.description('Open Brain Cloud dashboard')
|
||||||
|
.action(wrapInteractive(async (id) => {
|
||||||
|
const dashboardUrl = id
|
||||||
|
? `https://brainy-${id}.soulcraft-brain.workers.dev/dashboard`
|
||||||
|
: 'https://app.soulcraft.com'
|
||||||
|
|
||||||
|
console.log(chalk.cyan(`\n🌐 Opening Brain Cloud Dashboard: ${dashboardUrl}`))
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { exec } = await import('child_process')
|
||||||
|
const { promisify } = await import('util')
|
||||||
|
const execAsync = promisify(exec)
|
||||||
|
|
||||||
|
const command = process.platform === 'win32' ? 'start' :
|
||||||
|
process.platform === 'darwin' ? 'open' : 'xdg-open'
|
||||||
|
|
||||||
|
await execAsync(`${command} "${dashboardUrl}"`)
|
||||||
|
console.log(chalk.green('✅ Dashboard opened!'))
|
||||||
|
} catch (error) {
|
||||||
|
console.log(chalk.yellow('💡 Copy the URL above to open in your browser'))
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Legacy cloud command (for backward compatibility)
|
||||||
program
|
program
|
||||||
.command('cloud [action]')
|
.command('cloud-legacy [action]')
|
||||||
.description('Manage Brain Cloud connection')
|
.description('Legacy Brain Cloud connection (deprecated - use "brainy cloud")')
|
||||||
.option('--connect <id>', 'Connect to existing Brain Cloud instance')
|
.option('--connect <id>', 'Connect to existing Brain Cloud instance')
|
||||||
.option('--export <id>', 'Export all data from Brain Cloud instance')
|
.option('--export <id>', 'Export all data from Brain Cloud instance')
|
||||||
.option('--status <id>', 'Check status of Brain Cloud instance')
|
.option('--status <id>', 'Check status of Brain Cloud instance')
|
||||||
.option('--dashboard <id>', 'Open dashboard for Brain Cloud instance')
|
.option('--dashboard <id>', 'Open dashboard for Brain Cloud instance')
|
||||||
.option('--migrate', 'Migrate between local and cloud')
|
.option('--migrate', 'Migrate between local and cloud')
|
||||||
.action(wrapInteractive(async (action, options) => {
|
.action(wrapInteractive(async (action, options) => {
|
||||||
|
console.log(chalk.yellow('⚠️ Deprecated: Use "brainy cloud" commands instead'))
|
||||||
|
console.log(chalk.cyan('Examples:'))
|
||||||
|
console.log(' brainy cloud setup')
|
||||||
|
console.log(' brainy cloud connect <id>')
|
||||||
|
console.log(' brainy cloud dashboard')
|
||||||
|
console.log('')
|
||||||
// For now, show connection instructions
|
// For now, show connection instructions
|
||||||
console.log(chalk.cyan('\n⚛️ BRAIN CLOUD - AI Memory That Never Forgets'))
|
console.log(chalk.cyan('\n⚛️ BRAIN CLOUD - AI Memory That Never Forgets'))
|
||||||
console.log(chalk.gray('━'.repeat(50)))
|
console.log(chalk.gray('━'.repeat(50)))
|
||||||
|
|
@ -383,9 +497,47 @@ augment
|
||||||
}
|
}
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|
||||||
console.log(chalk.dim('📦 Available:'))
|
// Fetch from catalog API
|
||||||
console.log(chalk.dim(' • notion-sync (Premium)'))
|
try {
|
||||||
console.log(chalk.dim(' • 40+ more at app.soulcraft.com'))
|
const response = await fetch('http://localhost:3001/api/catalog/cli')
|
||||||
|
if (response.ok) {
|
||||||
|
const catalog = await response.json()
|
||||||
|
|
||||||
|
// Show available augmentations
|
||||||
|
const available = catalog.augmentations.filter(aug => aug.status === 'available')
|
||||||
|
if (available.length > 0) {
|
||||||
|
console.log(chalk.cyan('🌟 Available (Brain Cloud):'))
|
||||||
|
available.forEach(aug => {
|
||||||
|
const popular = aug.popular ? chalk.yellow(' ⭐ Popular') : ''
|
||||||
|
console.log(` • ${aug.id} - ${aug.description}${popular}`)
|
||||||
|
})
|
||||||
|
console.log('')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show coming soon
|
||||||
|
const comingSoon = catalog.augmentations.filter(aug => aug.status === 'coming-soon')
|
||||||
|
if (comingSoon.length > 0) {
|
||||||
|
console.log(chalk.dim('📦 Coming Soon:'))
|
||||||
|
comingSoon.forEach(aug => {
|
||||||
|
const eta = aug.eta ? ` (${aug.eta})` : ''
|
||||||
|
console.log(chalk.dim(` • ${aug.id} - ${aug.description}${eta}`))
|
||||||
|
})
|
||||||
|
console.log('')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error('API unavailable')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Fallback to static list if API is unavailable
|
||||||
|
console.log(chalk.cyan('🌟 Available (Brain Cloud):'))
|
||||||
|
console.log(' • ai-memory - ' + chalk.yellow('⭐ Popular') + ' - Persistent AI memory')
|
||||||
|
console.log(' • agent-coordinator - ' + chalk.yellow('⭐ Popular') + ' - Multi-agent handoffs')
|
||||||
|
console.log(' • notion-sync - Enterprise connector')
|
||||||
|
console.log('')
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(chalk.dim('🚀 Join Early Access: https://soulcraft.com/brain-cloud'))
|
||||||
|
console.log(chalk.dim('📦 Install: npm install @soulcraft/brain-cloud'))
|
||||||
}))
|
}))
|
||||||
|
|
||||||
augment
|
augment
|
||||||
|
|
@ -719,9 +871,10 @@ if (!process.argv.slice(2).length) {
|
||||||
console.log(' brainy search "query" # Search across all dimensions')
|
console.log(' brainy search "query" # Search across all dimensions')
|
||||||
console.log(' brainy chat # AI chat with full context')
|
console.log(' brainy chat # AI chat with full context')
|
||||||
console.log('')
|
console.log('')
|
||||||
console.log(chalk.bold('AI Memory:'))
|
console.log(chalk.bold('Brain Cloud (Premium):'))
|
||||||
console.log(chalk.green(' brainy connect # Connect to Brain Cloud'))
|
console.log(chalk.green(' brainy cloud setup # Auto-setup with provisioning'))
|
||||||
console.log(' brainy cloud --status <id> # Check cloud status')
|
console.log(' brainy cloud connect <id> # Connect to existing instance')
|
||||||
|
console.log(' brainy cloud dashboard # Open Brain Cloud dashboard')
|
||||||
console.log('')
|
console.log('')
|
||||||
console.log(chalk.bold('AI Coordination:'))
|
console.log(chalk.bold('AI Coordination:'))
|
||||||
console.log(' brainy install brain-jar # Install coordination')
|
console.log(' brainy install brain-jar # Install coordination')
|
||||||
|
|
@ -858,3 +1011,309 @@ When working with multiple AI assistants, we automatically coordinate:
|
||||||
console.log(chalk.yellow('⚠️ Could not save brainy config:', error.message))
|
console.log(chalk.yellow('⚠️ Could not save brainy config:', error.message))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// AUTO-SETUP HELPER FUNCTIONS
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
const PROVISIONING_API = 'https://provisioning.soulcraft.com'
|
||||||
|
|
||||||
|
let spinner = null
|
||||||
|
|
||||||
|
function startSpinner(message) {
|
||||||
|
stopSpinner()
|
||||||
|
process.stdout.write(`${message} `)
|
||||||
|
|
||||||
|
const spinnerChars = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
|
||||||
|
let i = 0
|
||||||
|
|
||||||
|
spinner = setInterval(() => {
|
||||||
|
process.stdout.write(`\r${message} ${spinnerChars[i]}`)
|
||||||
|
i = (i + 1) % spinnerChars.length
|
||||||
|
}, 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopSpinner() {
|
||||||
|
if (spinner) {
|
||||||
|
clearInterval(spinner)
|
||||||
|
spinner = null
|
||||||
|
process.stdout.write('\r')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function validateLicense() {
|
||||||
|
startSpinner('Validating Early Access license...')
|
||||||
|
|
||||||
|
const licenseKey = process.env.BRAINY_LICENSE_KEY
|
||||||
|
|
||||||
|
if (!licenseKey) {
|
||||||
|
stopSpinner()
|
||||||
|
console.log('\n❌ No license key found')
|
||||||
|
console.log('\n🔑 Please set your Early Access license key:')
|
||||||
|
console.log(' export BRAINY_LICENSE_KEY="lic_early_access_your_key"')
|
||||||
|
console.log('\n📝 Don\'t have a key? Get one free at: https://soulcraft.com/brain-cloud')
|
||||||
|
throw new Error('License key required')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!licenseKey.startsWith('lic_early_access_')) {
|
||||||
|
stopSpinner()
|
||||||
|
throw new Error('Invalid license key format. Early Access keys start with "lic_early_access_"')
|
||||||
|
}
|
||||||
|
|
||||||
|
stopSpinner()
|
||||||
|
console.log('✅ License validated')
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ensureBrainyInstalled() {
|
||||||
|
startSpinner('Checking Brainy installation...')
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { exec } = await import('child_process')
|
||||||
|
const { promisify } = await import('util')
|
||||||
|
const execAsync = promisify(exec)
|
||||||
|
|
||||||
|
await execAsync('brainy --version')
|
||||||
|
stopSpinner()
|
||||||
|
console.log('✅ Brainy CLI found')
|
||||||
|
} catch (error) {
|
||||||
|
stopSpinner()
|
||||||
|
console.log('📦 Installing Brainy CLI...')
|
||||||
|
|
||||||
|
try {
|
||||||
|
await execWithProgress('npm install -g @soulcraft/brainy')
|
||||||
|
console.log('✅ Brainy CLI installed')
|
||||||
|
} catch (installError) {
|
||||||
|
throw new Error('Failed to install Brainy CLI. Please install manually: npm install -g @soulcraft/brainy')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function provisionCloudInstance(userEmail) {
|
||||||
|
const licenseKey = process.env.BRAINY_LICENSE_KEY
|
||||||
|
startSpinner('Provisioning your cloud Brainy instance...')
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${PROVISIONING_API}/provision`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
licenseKey,
|
||||||
|
userEmail: userEmail || 'user@example.com'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await response.json()
|
||||||
|
throw new Error(error.message || 'Provisioning failed')
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.json()
|
||||||
|
stopSpinner()
|
||||||
|
|
||||||
|
if (result.instance.status === 'active') {
|
||||||
|
console.log('✅ Cloud instance already active')
|
||||||
|
return result.instance
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('🚀 Provisioning started (2-3 minutes)')
|
||||||
|
|
||||||
|
// Wait for provisioning to complete
|
||||||
|
return await waitForProvisioning(licenseKey)
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
stopSpinner()
|
||||||
|
throw new Error(`Provisioning failed: ${error.message}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function waitForProvisioning(licenseKey) {
|
||||||
|
const maxWaitTime = 5 * 60 * 1000 // 5 minutes
|
||||||
|
const checkInterval = 15 * 1000 // 15 seconds
|
||||||
|
const startTime = Date.now()
|
||||||
|
|
||||||
|
while (Date.now() - startTime < maxWaitTime) {
|
||||||
|
startSpinner('Waiting for cloud instance to be ready...')
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${PROVISIONING_API}/status?licenseKey=${encodeURIComponent(licenseKey)}`)
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const result = await response.json()
|
||||||
|
|
||||||
|
if (result.instance.status === 'active') {
|
||||||
|
stopSpinner()
|
||||||
|
console.log('✅ Cloud instance is ready')
|
||||||
|
return result.instance
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.instance.status === 'failed') {
|
||||||
|
stopSpinner()
|
||||||
|
throw new Error('Instance provisioning failed')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stopSpinner()
|
||||||
|
await new Promise(resolve => setTimeout(resolve, checkInterval))
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
stopSpinner()
|
||||||
|
console.log('⏳ Still provisioning...')
|
||||||
|
await new Promise(resolve => setTimeout(resolve, checkInterval))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('Provisioning timeout. Please check your dashboard or contact support.')
|
||||||
|
}
|
||||||
|
|
||||||
|
async function configureBrainy(instance) {
|
||||||
|
const { writeFile, mkdir } = await import('fs/promises')
|
||||||
|
const { join } = await import('path')
|
||||||
|
const { homedir } = await import('os')
|
||||||
|
const { existsSync } = await import('fs')
|
||||||
|
|
||||||
|
startSpinner('Configuring local Brainy to use cloud instance...')
|
||||||
|
|
||||||
|
// Ensure config directory exists
|
||||||
|
const BRAINY_CONFIG_DIR = join(homedir(), '.brainy')
|
||||||
|
const BRAINY_CONFIG_FILE = join(BRAINY_CONFIG_DIR, 'config.json')
|
||||||
|
|
||||||
|
if (!existsSync(BRAINY_CONFIG_DIR)) {
|
||||||
|
await mkdir(BRAINY_CONFIG_DIR, { recursive: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create or update Brainy config
|
||||||
|
let config = {}
|
||||||
|
if (existsSync(BRAINY_CONFIG_FILE)) {
|
||||||
|
try {
|
||||||
|
const { readFile } = await import('fs/promises')
|
||||||
|
const existing = await readFile(BRAINY_CONFIG_FILE, 'utf8')
|
||||||
|
config = JSON.parse(existing)
|
||||||
|
} catch (error) {
|
||||||
|
console.log('⚠️ Could not read existing config, creating new one')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update config with cloud instance details
|
||||||
|
config.cloudSync = {
|
||||||
|
enabled: true,
|
||||||
|
endpoint: instance.endpoints.api,
|
||||||
|
instanceId: instance.id,
|
||||||
|
licenseKey: process.env.BRAINY_LICENSE_KEY
|
||||||
|
}
|
||||||
|
|
||||||
|
config.aiMemory = {
|
||||||
|
enabled: true,
|
||||||
|
storage: 'cloud',
|
||||||
|
endpoint: instance.endpoints.api
|
||||||
|
}
|
||||||
|
|
||||||
|
config.agentCoordination = {
|
||||||
|
enabled: true,
|
||||||
|
endpoint: instance.endpoints.api
|
||||||
|
}
|
||||||
|
|
||||||
|
await writeFile(BRAINY_CONFIG_FILE, JSON.stringify(config, null, 2))
|
||||||
|
|
||||||
|
stopSpinner()
|
||||||
|
console.log('✅ Local Brainy configured for cloud sync')
|
||||||
|
}
|
||||||
|
|
||||||
|
async function installBrainCloudPackage() {
|
||||||
|
startSpinner('Installing Brain Cloud augmentations...')
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { existsSync } = await import('fs')
|
||||||
|
|
||||||
|
// Check if we're in a project directory
|
||||||
|
const hasPackageJson = existsSync('package.json')
|
||||||
|
|
||||||
|
if (hasPackageJson) {
|
||||||
|
await execWithProgress('npm install @soulcraft/brain-cloud')
|
||||||
|
console.log('✅ Brain Cloud package installed in current project')
|
||||||
|
} else {
|
||||||
|
// Install globally for non-project usage
|
||||||
|
await execWithProgress('npm install -g @soulcraft/brain-cloud')
|
||||||
|
console.log('✅ Brain Cloud package installed globally')
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
stopSpinner()
|
||||||
|
console.log('⚠️ Could not auto-install Brain Cloud package')
|
||||||
|
console.log(' You can install it manually: npm install @soulcraft/brain-cloud')
|
||||||
|
// Don't throw error, this is optional
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function testConnection(instance) {
|
||||||
|
startSpinner('Testing cloud connection...')
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${instance.endpoints.api}/health`)
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const health = await response.json()
|
||||||
|
stopSpinner()
|
||||||
|
console.log('✅ Cloud instance connection verified')
|
||||||
|
|
||||||
|
// Test memory storage
|
||||||
|
const testMemory = {
|
||||||
|
content: 'Test memory from auto-setup',
|
||||||
|
source: 'brain-cloud-setup',
|
||||||
|
importance: 'low',
|
||||||
|
tags: ['setup', 'test']
|
||||||
|
}
|
||||||
|
|
||||||
|
const memoryResponse = await fetch(`${instance.endpoints.api}/api/memories`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(testMemory)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (memoryResponse.ok) {
|
||||||
|
console.log('✅ Memory storage working')
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
stopSpinner()
|
||||||
|
console.log('⚠️ Cloud instance not responding yet (this is normal)')
|
||||||
|
console.log(' Your instance may need a few more minutes to fully initialize')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
stopSpinner()
|
||||||
|
console.log('⚠️ Could not test connection (this is usually fine)')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function execWithProgress(command) {
|
||||||
|
const { spawn } = await import('child_process')
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const child = spawn('sh', ['-c', command], {
|
||||||
|
stdio: ['inherit', 'pipe', 'pipe'],
|
||||||
|
shell: true
|
||||||
|
})
|
||||||
|
|
||||||
|
let stdout = ''
|
||||||
|
let stderr = ''
|
||||||
|
|
||||||
|
child.stdout?.on('data', (data) => {
|
||||||
|
stdout += data.toString()
|
||||||
|
process.stdout.write('.')
|
||||||
|
})
|
||||||
|
|
||||||
|
child.stderr?.on('data', (data) => {
|
||||||
|
stderr += data.toString()
|
||||||
|
})
|
||||||
|
|
||||||
|
child.on('close', (code) => {
|
||||||
|
process.stdout.write('\n')
|
||||||
|
if (code === 0) {
|
||||||
|
resolve(stdout)
|
||||||
|
} else {
|
||||||
|
reject(new Error(stderr || `Command failed with code ${code}`))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
398
src/cli/catalog.ts
Normal file
398
src/cli/catalog.ts
Normal file
|
|
@ -0,0 +1,398 @@
|
||||||
|
/**
|
||||||
|
* Brain Cloud Catalog Integration for CLI
|
||||||
|
*
|
||||||
|
* Fetches and displays augmentation catalog
|
||||||
|
* Falls back to local cache if API is unavailable
|
||||||
|
*/
|
||||||
|
|
||||||
|
import chalk from 'chalk'
|
||||||
|
import { readFileSync, writeFileSync, existsSync } from 'fs'
|
||||||
|
import { join } from 'path'
|
||||||
|
import { homedir } from 'os'
|
||||||
|
|
||||||
|
const CATALOG_API = process.env.BRAIN_CLOUD_CATALOG_URL || 'https://catalog.brain-cloud.soulcraft.com'
|
||||||
|
const CACHE_PATH = join(homedir(), '.brainy', 'catalog-cache.json')
|
||||||
|
const CACHE_TTL = 24 * 60 * 60 * 1000 // 24 hours
|
||||||
|
|
||||||
|
interface Augmentation {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
description: string
|
||||||
|
category: string
|
||||||
|
status: 'available' | 'coming_soon' | 'deprecated'
|
||||||
|
popular?: boolean
|
||||||
|
eta?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Category {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
icon: string
|
||||||
|
description: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Catalog {
|
||||||
|
version: string
|
||||||
|
categories: Category[]
|
||||||
|
augmentations: Augmentation[]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch catalog from API with caching
|
||||||
|
*/
|
||||||
|
export async function fetchCatalog(): Promise<Catalog | null> {
|
||||||
|
try {
|
||||||
|
// Check cache first
|
||||||
|
const cached = loadCache()
|
||||||
|
if (cached) return cached
|
||||||
|
|
||||||
|
// Fetch from API
|
||||||
|
const response = await fetch(`${CATALOG_API}/api/catalog/cli`)
|
||||||
|
if (!response.ok) throw new Error('API unavailable')
|
||||||
|
|
||||||
|
const catalog = await response.json()
|
||||||
|
|
||||||
|
// Save to cache
|
||||||
|
saveCache(catalog)
|
||||||
|
|
||||||
|
return catalog
|
||||||
|
} catch (error) {
|
||||||
|
// Try loading from cache even if expired
|
||||||
|
const cached = loadCache(true)
|
||||||
|
if (cached) {
|
||||||
|
console.log(chalk.yellow('📡 Using cached catalog (API unavailable)'))
|
||||||
|
return cached
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to hardcoded catalog
|
||||||
|
return getDefaultCatalog()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display catalog in CLI
|
||||||
|
*/
|
||||||
|
export async function showCatalog(options: {
|
||||||
|
category?: string
|
||||||
|
search?: string
|
||||||
|
detailed?: boolean
|
||||||
|
}) {
|
||||||
|
const catalog = await fetchCatalog()
|
||||||
|
if (!catalog) {
|
||||||
|
console.log(chalk.red('❌ Could not load augmentation catalog'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(chalk.cyan.bold('🧠 Brain Cloud Augmentation Catalog'))
|
||||||
|
console.log(chalk.gray(`Version ${catalog.version}`))
|
||||||
|
console.log('')
|
||||||
|
|
||||||
|
// Filter augmentations
|
||||||
|
let augmentations = catalog.augmentations
|
||||||
|
|
||||||
|
if (options.category) {
|
||||||
|
augmentations = augmentations.filter(a => a.category === options.category)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.search) {
|
||||||
|
const query = options.search.toLowerCase()
|
||||||
|
augmentations = augmentations.filter(a =>
|
||||||
|
a.name.toLowerCase().includes(query) ||
|
||||||
|
a.description.toLowerCase().includes(query)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group by category
|
||||||
|
const grouped = groupByCategory(augmentations, catalog.categories)
|
||||||
|
|
||||||
|
// Display
|
||||||
|
for (const [category, augs] of Object.entries(grouped)) {
|
||||||
|
if (augs.length === 0) continue
|
||||||
|
|
||||||
|
const cat = catalog.categories.find(c => c.id === category)
|
||||||
|
console.log(chalk.bold(`${cat?.icon || '📦'} ${cat?.name || category}`))
|
||||||
|
|
||||||
|
for (const aug of augs) {
|
||||||
|
const status = getStatusIcon(aug.status)
|
||||||
|
const popular = aug.popular ? chalk.yellow(' ⭐') : ''
|
||||||
|
const eta = aug.eta ? chalk.gray(` (${aug.eta})`) : ''
|
||||||
|
|
||||||
|
console.log(` ${status} ${aug.name}${popular}${eta}`)
|
||||||
|
if (options.detailed) {
|
||||||
|
console.log(chalk.gray(` ${aug.description}`))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log('')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show summary
|
||||||
|
const available = augmentations.filter(a => a.status === 'available').length
|
||||||
|
const coming = augmentations.filter(a => a.status === 'coming_soon').length
|
||||||
|
|
||||||
|
console.log(chalk.gray('─'.repeat(50)))
|
||||||
|
console.log(chalk.green(`✅ ${available} available`) + chalk.gray(` • `) +
|
||||||
|
chalk.yellow(`🔜 ${coming} coming soon`))
|
||||||
|
console.log('')
|
||||||
|
console.log(chalk.dim('Sign up at app.soulcraft.com to activate'))
|
||||||
|
console.log(chalk.dim('Run "brainy augment info <name>" for details'))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show detailed info about an augmentation
|
||||||
|
*/
|
||||||
|
export async function showAugmentationInfo(id: string) {
|
||||||
|
const catalog = await fetchCatalog()
|
||||||
|
if (!catalog) {
|
||||||
|
console.log(chalk.red('❌ Could not load augmentation catalog'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const aug = catalog.augmentations.find(a => a.id === id)
|
||||||
|
if (!aug) {
|
||||||
|
console.log(chalk.red(`❌ Augmentation not found: ${id}`))
|
||||||
|
console.log('')
|
||||||
|
console.log('Available augmentations:')
|
||||||
|
catalog.augmentations.forEach(a => {
|
||||||
|
console.log(` • ${a.id}`)
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch full details from API
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${CATALOG_API}/api/catalog/augmentation/${id}`)
|
||||||
|
const details = await response.json()
|
||||||
|
|
||||||
|
console.log(chalk.cyan.bold(`📦 ${details.name}`))
|
||||||
|
if (details.popular) console.log(chalk.yellow('⭐ Popular'))
|
||||||
|
console.log('')
|
||||||
|
|
||||||
|
console.log(chalk.bold('Category:'), getCategoryName(details.category, catalog.categories))
|
||||||
|
console.log(chalk.bold('Status:'), getStatusText(details.status))
|
||||||
|
if (details.eta) console.log(chalk.bold('Expected:'), details.eta)
|
||||||
|
console.log('')
|
||||||
|
|
||||||
|
console.log(chalk.bold('Description:'))
|
||||||
|
console.log(details.longDescription || details.description)
|
||||||
|
console.log('')
|
||||||
|
|
||||||
|
if (details.features) {
|
||||||
|
console.log(chalk.bold('Features:'))
|
||||||
|
details.features.forEach(f => console.log(` ✓ ${f}`))
|
||||||
|
console.log('')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (details.example) {
|
||||||
|
console.log(chalk.bold('Example:'))
|
||||||
|
console.log(chalk.gray('─'.repeat(50)))
|
||||||
|
console.log(details.example.code)
|
||||||
|
console.log(chalk.gray('─'.repeat(50)))
|
||||||
|
console.log('')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (details.requirements?.config) {
|
||||||
|
console.log(chalk.bold('Required Configuration:'))
|
||||||
|
details.requirements.config.forEach(c => console.log(` • ${c}`))
|
||||||
|
console.log('')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (details.pricing) {
|
||||||
|
console.log(chalk.bold('Available in:'))
|
||||||
|
details.pricing.tiers.forEach(t => console.log(` • ${t}`))
|
||||||
|
console.log('')
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(chalk.dim('To activate: brainy augment activate'))
|
||||||
|
} catch (error) {
|
||||||
|
// Show basic info if API fails
|
||||||
|
console.log(chalk.cyan.bold(`📦 ${aug.name}`))
|
||||||
|
console.log(aug.description)
|
||||||
|
console.log('')
|
||||||
|
console.log(chalk.dim('Full details unavailable (API offline)'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show user's available augmentations
|
||||||
|
*/
|
||||||
|
export async function showAvailable(licenseKey?: string) {
|
||||||
|
const key = licenseKey || process.env.BRAINY_LICENSE_KEY || readLicenseFile()
|
||||||
|
|
||||||
|
if (!key) {
|
||||||
|
console.log(chalk.yellow('⚠️ No license key found'))
|
||||||
|
console.log('')
|
||||||
|
console.log('To see your available augmentations:')
|
||||||
|
console.log(' 1. Sign up at app.soulcraft.com')
|
||||||
|
console.log(' 2. Run: brainy augment activate')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${CATALOG_API}/api/catalog/available`, {
|
||||||
|
headers: { 'x-license-key': key }
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Invalid license')
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json()
|
||||||
|
|
||||||
|
console.log(chalk.cyan.bold('🧠 Your Available Augmentations'))
|
||||||
|
console.log(chalk.gray(`Plan: ${data.plan}`))
|
||||||
|
console.log('')
|
||||||
|
|
||||||
|
const grouped = groupByCategory(data.augmentations, [])
|
||||||
|
|
||||||
|
for (const [category, augs] of Object.entries(grouped)) {
|
||||||
|
console.log(chalk.bold(category))
|
||||||
|
augs.forEach(aug => {
|
||||||
|
console.log(` ✅ ${aug.name}`)
|
||||||
|
})
|
||||||
|
console.log('')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.operations) {
|
||||||
|
const used = data.operations.used || 0
|
||||||
|
const limit = data.operations.limit
|
||||||
|
const percent = limit === 'unlimited' ? 0 : Math.round((used / limit) * 100)
|
||||||
|
|
||||||
|
console.log(chalk.bold('Usage:'))
|
||||||
|
if (limit === 'unlimited') {
|
||||||
|
console.log(` Unlimited operations`)
|
||||||
|
} else {
|
||||||
|
console.log(` ${used.toLocaleString()} / ${limit.toLocaleString()} operations (${percent}%)`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(chalk.red('❌ Could not fetch available augmentations'))
|
||||||
|
console.log(chalk.gray(error.message))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper functions
|
||||||
|
|
||||||
|
function loadCache(ignoreExpiry = false): Catalog | null {
|
||||||
|
try {
|
||||||
|
if (!existsSync(CACHE_PATH)) return null
|
||||||
|
|
||||||
|
const data = JSON.parse(readFileSync(CACHE_PATH, 'utf8'))
|
||||||
|
|
||||||
|
if (!ignoreExpiry && Date.now() - data.timestamp > CACHE_TTL) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return data.catalog
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveCache(catalog: Catalog): void {
|
||||||
|
try {
|
||||||
|
const dir = join(homedir(), '.brainy')
|
||||||
|
if (!existsSync(dir)) {
|
||||||
|
require('fs').mkdirSync(dir, { recursive: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
writeFileSync(CACHE_PATH, JSON.stringify({
|
||||||
|
catalog,
|
||||||
|
timestamp: Date.now()
|
||||||
|
}))
|
||||||
|
} catch {
|
||||||
|
// Ignore cache save errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function groupByCategory(augmentations: Augmentation[], categories: Category[]) {
|
||||||
|
const grouped: Record<string, Augmentation[]> = {}
|
||||||
|
|
||||||
|
for (const aug of augmentations) {
|
||||||
|
if (!grouped[aug.category]) {
|
||||||
|
grouped[aug.category] = []
|
||||||
|
}
|
||||||
|
grouped[aug.category].push(aug)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort by category order
|
||||||
|
const ordered: Record<string, Augmentation[]> = {}
|
||||||
|
const categoryOrder = ['memory', 'coordination', 'enterprise', 'perception', 'dialog', 'activation', 'cognition', 'websocket']
|
||||||
|
|
||||||
|
for (const cat of categoryOrder) {
|
||||||
|
if (grouped[cat]) {
|
||||||
|
ordered[cat] = grouped[cat]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ordered
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStatusIcon(status: string): string {
|
||||||
|
switch (status) {
|
||||||
|
case 'available': return chalk.green('✅')
|
||||||
|
case 'coming_soon': return chalk.yellow('🔜')
|
||||||
|
case 'deprecated': return chalk.red('⚠️')
|
||||||
|
default: return '❓'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStatusText(status: string): string {
|
||||||
|
switch (status) {
|
||||||
|
case 'available': return chalk.green('Available')
|
||||||
|
case 'coming_soon': return chalk.yellow('Coming Soon')
|
||||||
|
case 'deprecated': return chalk.red('Deprecated')
|
||||||
|
default: return 'Unknown'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCategoryName(categoryId: string, categories: Category[]): string {
|
||||||
|
const cat = categories.find(c => c.id === categoryId)
|
||||||
|
return cat ? `${cat.icon} ${cat.name}` : categoryId
|
||||||
|
}
|
||||||
|
|
||||||
|
function readLicenseFile(): string | null {
|
||||||
|
try {
|
||||||
|
const licensePath = join(homedir(), '.brainy', 'license')
|
||||||
|
if (existsSync(licensePath)) {
|
||||||
|
return readFileSync(licensePath, 'utf8').trim()
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDefaultCatalog(): Catalog {
|
||||||
|
// Hardcoded fallback catalog
|
||||||
|
return {
|
||||||
|
version: '1.0.0',
|
||||||
|
categories: [
|
||||||
|
{ id: 'memory', name: 'Memory', icon: '🧠', description: 'AI memory and persistence' },
|
||||||
|
{ id: 'coordination', name: 'Coordination', icon: '🤝', description: 'Multi-agent orchestration' },
|
||||||
|
{ id: 'enterprise', name: 'Enterprise', icon: '🏢', description: 'Business integrations' }
|
||||||
|
],
|
||||||
|
augmentations: [
|
||||||
|
{
|
||||||
|
id: 'ai-memory',
|
||||||
|
name: 'AI Memory',
|
||||||
|
category: 'memory',
|
||||||
|
description: 'Persistent memory across all AI sessions',
|
||||||
|
status: 'available',
|
||||||
|
popular: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'agent-coordinator',
|
||||||
|
name: 'Agent Coordinator',
|
||||||
|
category: 'coordination',
|
||||||
|
description: 'Multi-agent handoffs and orchestration',
|
||||||
|
status: 'available',
|
||||||
|
popular: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'notion-sync',
|
||||||
|
name: 'Notion Sync',
|
||||||
|
category: 'enterprise',
|
||||||
|
description: 'Bidirectional Notion database sync',
|
||||||
|
status: 'available'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue