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
b9ce67f64c
commit
6de6adec17
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://opensource.org/licenses/MIT)
|
||||
[](https://soulcraft.com/console)
|
||||
[](https://soulcraft.com)
|
||||
[](https://soulcraft.com/brain-cloud)
|
||||
[](https://nodejs.org/)
|
||||
[](https://www.typescriptlang.org/)
|
||||
|
||||
|
|
@ -107,6 +108,7 @@ Try Brainy instantly in your browser. No signup. No credit card.
|
|||
|
||||
## ⚡ Quick Start (60 Seconds)
|
||||
|
||||
### Open Source (Local Storage)
|
||||
```bash
|
||||
npm install @soulcraft/brainy
|
||||
```
|
||||
|
|
@ -125,6 +127,31 @@ await brain.addVerb("Elon Musk", "founded", "Tesla")
|
|||
|
||||
// Search naturally
|
||||
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
|
||||
|
||||
// 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.'))
|
||||
} else {
|
||||
console.log(chalk.yellow('No Brain Cloud found. Setting up:'))
|
||||
console.log('\n1. Visit: ' + chalk.cyan('https://soulcraft.com'))
|
||||
console.log('2. Sign up for Brain Cloud')
|
||||
console.log('3. Run ' + chalk.green('brainy connect') + ' again')
|
||||
console.log('\n1. Visit: ' + chalk.cyan('https://soulcraft.com/brain-cloud'))
|
||||
console.log('2. Get your Early Access license key')
|
||||
console.log('3. Run ' + chalk.green('brainy cloud setup') + ' for auto-configuration')
|
||||
}
|
||||
} catch (error) {
|
||||
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
|
||||
.command('cloud [action]')
|
||||
.description('Manage Brain Cloud connection')
|
||||
.command('cloud-legacy [action]')
|
||||
.description('Legacy Brain Cloud connection (deprecated - use "brainy cloud")')
|
||||
.option('--connect <id>', 'Connect to existing Brain Cloud instance')
|
||||
.option('--export <id>', 'Export all data from Brain Cloud instance')
|
||||
.option('--status <id>', 'Check status of Brain Cloud instance')
|
||||
.option('--dashboard <id>', 'Open dashboard for Brain Cloud instance')
|
||||
.option('--migrate', 'Migrate between local and cloud')
|
||||
.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
|
||||
console.log(chalk.cyan('\n⚛️ BRAIN CLOUD - AI Memory That Never Forgets'))
|
||||
console.log(chalk.gray('━'.repeat(50)))
|
||||
|
|
@ -383,9 +497,47 @@ augment
|
|||
}
|
||||
} catch {}
|
||||
|
||||
console.log(chalk.dim('📦 Available:'))
|
||||
console.log(chalk.dim(' • notion-sync (Premium)'))
|
||||
console.log(chalk.dim(' • 40+ more at app.soulcraft.com'))
|
||||
// Fetch from catalog API
|
||||
try {
|
||||
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
|
||||
|
|
@ -719,9 +871,10 @@ if (!process.argv.slice(2).length) {
|
|||
console.log(' brainy search "query" # Search across all dimensions')
|
||||
console.log(' brainy chat # AI chat with full context')
|
||||
console.log('')
|
||||
console.log(chalk.bold('AI Memory:'))
|
||||
console.log(chalk.green(' brainy connect # Connect to Brain Cloud'))
|
||||
console.log(' brainy cloud --status <id> # Check cloud status')
|
||||
console.log(chalk.bold('Brain Cloud (Premium):'))
|
||||
console.log(chalk.green(' brainy cloud setup # Auto-setup with provisioning'))
|
||||
console.log(' brainy cloud connect <id> # Connect to existing instance')
|
||||
console.log(' brainy cloud dashboard # Open Brain Cloud dashboard')
|
||||
console.log('')
|
||||
console.log(chalk.bold('AI Coordination:'))
|
||||
console.log(' brainy install brain-jar # Install coordination')
|
||||
|
|
@ -857,4 +1010,310 @@ When working with multiple AI assistants, we automatically coordinate:
|
|||
} catch (error) {
|
||||
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