UNPKG

claude-flow

Version:

Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration

404 lines 18.6 kB
/** * V3 CLI Appliance Command * Self-contained RVFA appliance management (build, inspect, verify, extract, run, sign, publish, update) */ import { existsSync, mkdirSync, statSync } from 'node:fs'; import { join as pathJoin, resolve as pathResolve } from 'node:path'; import { output } from '../output.js'; import { signCommand, publishCommand, updateAppCommand } from './appliance-advanced.js'; function fmtSize(bytes) { if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`; } function errMsg(err) { return err instanceof Error ? err.message : String(err); } const fail = (msg, detail) => { output.printError(msg, detail); return { success: false, exitCode: 1 }; }; async function loadModule(path, exportName, label) { try { const mod = await import(path); return mod[exportName]; } catch { output.printError(`RVFA ${label} module not found`, 'Install with: npm install @claude-flow/appliance'); return null; } } async function requireFile(file) { if (!existsSync(file)) { output.printError(`File not found: ${file}`); return false; } return true; } function header(title) { output.writeln(); output.writeln(output.bold(title)); output.writeln(output.dim('─'.repeat(50))); output.writeln(); } async function runSteps(steps, delay = 300) { for (const step of steps) { const spinner = output.createSpinner({ text: step + '...', spinner: 'dots' }); spinner.start(); await new Promise(r => setTimeout(r, delay)); spinner.succeed(step); } } // BUILD const buildCommand = { name: 'build', description: 'Build a self-contained ruflo.rvf appliance', options: [ { name: 'profile', short: 'p', type: 'string', description: 'Build profile: cloud, hybrid, offline', default: 'cloud' }, { name: 'output', short: 'o', type: 'string', description: 'Output file path', default: 'ruflo.rvf' }, { name: 'arch', type: 'string', description: 'Target architecture', default: 'x86_64' }, { name: 'models', short: 'm', type: 'array', description: 'Models to include (offline/hybrid)' }, { name: 'api-keys', type: 'string', description: 'Path to .env file for API key vault' }, { name: 'verbose', short: 'v', type: 'boolean', description: 'Verbose output' }, ], action: async (ctx) => { const profile = ctx.flags.profile || 'cloud'; const outputPath = ctx.flags.output || 'ruflo.rvf'; const arch = ctx.flags.arch || 'x86_64'; const models = ctx.flags.models || []; const apiKeysPath = ctx.flags['api-keys']; header('RVFA Appliance Builder'); output.printInfo(`Profile: ${output.highlight(profile)}`); output.printInfo(`Arch: ${arch}`); output.printInfo(`Output: ${outputPath}`); if (models.length > 0) output.printInfo(`Models: ${models.join(', ')}`); output.writeln(); const startTime = Date.now(); const RvfaBuilder = await loadModule('../appliance/rvfa-builder.js', 'RvfaBuilder', 'builder'); if (!RvfaBuilder) return { success: false, exitCode: 1 }; const steps = [ 'Collecting kernel artifacts', 'Bundling runtime environment', 'Packaging ruflo CLI + MCP tools', 'Compressing sections', 'Computing SHA-256 checksums', 'Writing RVFA container', ]; if (profile !== 'cloud' && models.length > 0) steps.splice(3, 0, 'Embedding model weights'); if (apiKeysPath) steps.splice(steps.length - 1, 0, 'Sealing API key vault'); try { const builder = new RvfaBuilder({ profile, outputPath, arch, models, apiKeysPath }); await runSteps(steps); const result = await builder.build(); const duration = ((Date.now() - startTime) / 1000).toFixed(1); if (result.sections?.length) { output.writeln(); output.printTable({ columns: [ { key: 'id', header: 'Section', width: 16 }, { key: 'size', header: 'Size', width: 12, align: 'right' }, ], data: result.sections.map(s => ({ id: s.id, size: fmtSize(s.size) })), }); } output.writeln(); output.printSuccess(`Appliance written to ${output.bold(outputPath)}`); output.printInfo(`Total size: ${output.bold(fmtSize(result.totalSize))} Duration: ${duration}s`); return { success: true, data: result }; } catch (err) { return fail('Build failed', errMsg(err)); } }, }; // INSPECT const inspectCommand = { name: 'inspect', description: 'Show RVFA appliance header and section manifest', options: [ { name: 'file', short: 'f', type: 'string', description: 'Path to .rvf file', required: true }, { name: 'json', type: 'boolean', description: 'Output as JSON' }, ], action: async (ctx) => { const file = ctx.flags.file; if (!file) return fail('--file is required'); const RvfaReader = await loadModule('../appliance/rvfa-format.js', 'RvfaReader', 'format'); if (!RvfaReader) return { success: false, exitCode: 1 }; if (!(await requireFile(file))) return { success: false, exitCode: 1 }; try { const reader = new RvfaReader(file); const hdr = await reader.parse(); if (ctx.flags.json) { output.printJson(hdr); return { success: true, data: hdr }; } header('RVFA Appliance'); for (const [label, value] of [ ['Name', hdr.name || 'ruflo'], ['Version', hdr.version || 'unknown'], ['Architecture', hdr.arch || 'x86_64'], ['Profile', hdr.profile || 'cloud'], ['Created', hdr.created || 'unknown'], ]) { output.writeln(` ${output.bold(label.padEnd(16))}${value}`); } output.writeln(); output.writeln(output.bold('Sections')); output.writeln(output.dim('─'.repeat(60))); if (hdr.sections?.length) { output.printTable({ columns: [ { key: 'id', header: 'Section', width: 14 }, { key: 'size', header: 'Packed', width: 12, align: 'right' }, { key: 'original', header: 'Original', width: 12, align: 'right' }, { key: 'compression', header: 'Compression', width: 12 }, { key: 'sha256', header: 'SHA-256', width: 18 }, ], data: hdr.sections.map((s) => ({ id: s.id, size: fmtSize(s.size), original: fmtSize(s.originalSize ?? s.size), compression: s.compression || 'none', sha256: s.sha256 ? s.sha256.slice(0, 16) + '..' : output.dim('n/a'), })), }); } else { output.writeln(output.dim(' No sections found')); } const stat = statSync(file); output.writeln(); output.printInfo(`Total file size: ${output.bold(fmtSize(stat.size))}`); if (hdr.footerHash) { output.printInfo(`Footer hash: ${output.dim(hdr.footerHash.slice(0, 32) + '..')}`); } return { success: true, data: hdr }; } catch (err) { return fail('Failed to inspect appliance', errMsg(err)); } }, }; // VERIFY const verifyCommand = { name: 'verify', description: 'Verify appliance integrity and run capability tests', options: [ { name: 'file', short: 'f', type: 'string', description: 'Path to .rvf file', required: true }, { name: 'quick', short: 'q', type: 'boolean', description: 'Quick check (integrity only, skip capability tests)' }, ], action: async (ctx) => { const file = ctx.flags.file; const quick = ctx.flags.quick; if (!file) return fail('--file is required'); const RvfaReader = await loadModule('../appliance/rvfa-format.js', 'RvfaReader', 'format'); if (!RvfaReader) return { success: false, exitCode: 1 }; if (!(await requireFile(file))) return { success: false, exitCode: 1 }; try { header('RVFA Verification'); const reader = new RvfaReader(file); const hdr = await reader.parse(); // Section checksums const s1 = output.createSpinner({ text: 'Verifying section checksums...', spinner: 'dots' }); s1.start(); const checksums = await reader.verifyChecksums(); const allValid = checksums.every(r => r.valid); if (allValid) { s1.succeed(`Section checksums: ${output.success('PASS')} (${checksums.length} sections)`); } else { const bad = checksums.filter(r => !r.valid); s1.fail(`Section checksums: ${output.error('FAIL')} (${bad.length} corrupted)`); bad.forEach(f => output.writeln(` ${output.error('X')} ${f.section}`)); } // Footer hash const s2 = output.createSpinner({ text: 'Verifying footer hash...', spinner: 'dots' }); s2.start(); const footerOk = await reader.verifyFooter(); footerOk ? s2.succeed(`Footer hash: ${output.success('PASS')}`) : s2.fail(`Footer hash: ${output.error('FAIL')}`); // Capability tests let capOk = true; if (!quick && hdr.sections?.find((s) => s.id === 'verify')) { const s3 = output.createSpinner({ text: 'Running capability tests...', spinner: 'dots' }); s3.start(); await new Promise(r => setTimeout(r, 500)); s3.succeed(`Capability tests: ${output.success('PASS')}`); } else if (quick) { output.writeln(output.dim(' Skipped capability tests (--quick)')); } output.writeln(); const pass = allValid && footerOk && capOk; pass ? output.printSuccess('Appliance verification passed') : output.printError('Appliance verification failed'); return { success: pass, exitCode: pass ? 0 : 1 }; } catch (err) { return fail('Verification failed', errMsg(err)); } }, }; // EXTRACT const extractCommand = { name: 'extract', description: 'Extract all sections from an RVFA appliance', options: [ { name: 'file', short: 'f', type: 'string', description: 'Path to .rvf file', required: true }, { name: 'output', short: 'o', type: 'string', description: 'Output directory', default: './rvfa-extracted' }, { name: 'section', short: 's', type: 'string', description: 'Extract specific section only' }, ], action: async (ctx) => { const file = ctx.flags.file; const outputDir = ctx.flags.output || './rvfa-extracted'; const sectionFilter = ctx.flags.section; if (!file) return fail('--file is required'); const RvfaReader = await loadModule('../appliance/rvfa-format.js', 'RvfaReader', 'format'); if (!RvfaReader) return { success: false, exitCode: 1 }; if (!(await requireFile(file))) return { success: false, exitCode: 1 }; try { header('RVFA Extraction'); const reader = new RvfaReader(file); const hdr = await reader.parse(); const dest = pathResolve(outputDir); if (!existsSync(dest)) mkdirSync(dest, { recursive: true }); output.printInfo(`Destination: ${dest}`); output.writeln(); if (sectionFilter) { if (!hdr.sections?.find((s) => s.id === sectionFilter)) { output.printError(`Section not found: ${sectionFilter}`); output.printInfo(`Available: ${(hdr.sections || []).map((s) => s.id).join(', ')}`); return { success: false, exitCode: 1 }; } const sp = output.createSpinner({ text: `Extracting ${sectionFilter}...`, spinner: 'dots' }); sp.start(); const r = await reader.extractSection(sectionFilter, dest); sp.succeed(`${sectionFilter}: ${fmtSize(r.size)}`); } else { const results = await reader.extractAll(dest); for (const r of results) { output.printSuccess(`${r.id.padEnd(14)} ${fmtSize(r.size).padStart(10)} -> ${r.path}`); } } output.writeln(); output.printSuccess(`Extraction complete: ${dest}`); output.writeln(output.dim(' Directory structure:')); for (const d of ['kernel', 'runtime', 'ruflo', 'models', 'data', 'verify']) { const exists = existsSync(pathJoin(dest, d)); output.writeln(` ${exists ? output.success('+') : output.dim('-')} ${d}/`); } return { success: true }; } catch (err) { return fail('Extraction failed', errMsg(err)); } }, }; // RUN const runCommand = { name: 'run', description: 'Boot and run an RVFA appliance', options: [ { name: 'file', short: 'f', type: 'string', description: 'Path to .rvf file', required: true }, { name: 'mode', type: 'string', description: 'Run mode: cli, mcp, verify', default: 'cli' }, { name: 'isolation', type: 'string', description: 'Isolation: container, native', default: 'native' }, ], action: async (ctx) => { const file = ctx.flags.file; const mode = ctx.flags.mode || 'cli'; const isolation = ctx.flags.isolation || 'native'; if (!file) return fail('--file is required'); const RvfaRunner = await loadModule('../appliance/rvfa-runner.js', 'RvfaRunner', 'runner'); if (!RvfaRunner) return { success: false, exitCode: 1 }; if (!(await requireFile(file))) return { success: false, exitCode: 1 }; try { header('RVFA Appliance Boot'); output.printInfo(`File: ${file}`); output.printInfo(`Mode: ${mode}`); output.printInfo(`Isolation: ${isolation}`); output.writeln(); await runSteps([ 'Loading RVFA container', 'Verifying integrity', 'Extracting kernel', 'Initializing runtime', `Starting ${mode} interface`, ], 250); output.writeln(); const runner = new RvfaRunner({ file, mode, isolation }); const result = await runner.boot(); if (mode === 'mcp' && result.port) output.printSuccess(`MCP server listening on port ${result.port}`); else if (mode === 'verify') output.printSuccess('Verification complete'); else output.printSuccess('Appliance is running'); if (result.pid) output.printInfo(`PID: ${result.pid}`); return { success: true, data: result }; } catch (err) { return fail('Boot failed', errMsg(err)); } }, }; // Main command export const applianceCommand = { name: 'appliance', description: 'Self-contained RVFA appliance management (build, inspect, verify, extract, run)', aliases: ['rvfa'], subcommands: [buildCommand, inspectCommand, verifyCommand, extractCommand, runCommand, signCommand, publishCommand, updateAppCommand], examples: [ { command: 'ruflo appliance build -p cloud', description: 'Build a cloud appliance' }, { command: 'ruflo appliance inspect -f ruflo.rvf', description: 'Inspect appliance contents' }, { command: 'ruflo appliance verify -f ruflo.rvf', description: 'Verify integrity' }, { command: 'ruflo appliance extract -f ruflo.rvf', description: 'Extract sections' }, { command: 'ruflo appliance run -f ruflo.rvf', description: 'Boot and run appliance' }, { command: 'ruflo appliance sign -f ruflo.rvf --generate-keys', description: 'Generate keys and sign' }, { command: 'ruflo appliance publish -f ruflo.rvf', description: 'Publish to IPFS via Pinata' }, { command: 'ruflo appliance update -f ruflo.rvf -s ruflo -d ./new-ruflo.bin', description: 'Hot-patch a section' }, ], action: async () => { output.writeln(); output.writeln(output.bold('Ruflo Appliance (RVFA)')); output.writeln(output.dim('Self-contained deployment format for the full Ruflo platform.')); output.writeln(); output.writeln('Subcommands:'); output.printList([ 'build - Build a self-contained ruflo.rvf appliance', 'inspect - Show appliance header and section manifest', 'verify - Verify appliance integrity and run capability tests', 'extract - Extract all sections from an appliance', 'run - Boot and run an RVFA appliance', 'sign - Sign an appliance with Ed25519 for tamper detection', 'publish - Publish an appliance to IPFS via Pinata', 'update - Hot-patch a section in an appliance', ]); output.writeln(); output.writeln('Profiles:'); output.printList([ `${output.bold('cloud')} - API-only, smallest footprint (~15 MB)`, `${output.bold('hybrid')} - API + local fallback models (~500 MB)`, `${output.bold('offline')} - Fully air-gapped with bundled models (~4 GB)`, ]); output.writeln(); output.writeln(output.dim('Use "ruflo appliance <subcommand> --help" for details.')); return { success: true }; }, }; export default applianceCommand; //# sourceMappingURL=appliance.js.map