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
329 lines • 18.1 kB
JavaScript
/**
* RVFA Appliance Builder -- Constructs self-contained .rvf appliance files.
*
* Creates a single binary containing kernel, runtime, Ruflo CLI, models/keys,
* AgentDB data, and the verification suite. See ADR-058.
*/
import { createHash, scryptSync, randomBytes, createCipheriv, createDecipheriv, } from 'node:crypto';
import { readFileSync, existsSync, writeFileSync, mkdirSync } from 'node:fs';
import { resolve, dirname } from 'node:path';
import { execSync } from 'node:child_process';
import { RvfaWriter, } from './rvfa-format.js';
// ── Encryption Constants ─────────────────────────────────────
const SCRYPT_KEY_LEN = 32;
const SCRYPT_SALT_LEN = 32;
const SCRYPT_OPTS = { N: 16384, r: 8, p: 1 };
const AES_IV_LEN = 16;
const AES_TAG_LEN = 16;
const AES_ALG = 'aes-256-gcm';
// ── Catalog ──────────────────────────────────────────────────
const RUFLO_COMMANDS = 'init agent swarm memory mcp task session config status start workflow hooks hive-mind daemon neural security performance providers plugins deployment embeddings claims migrate process doctor completions'.split(' ');
const AGENT_TYPES = 'coder reviewer tester planner researcher security-architect security-auditor memory-specialist performance-engineer hierarchical-coordinator mesh-coordinator adaptive-coordinator collective-intelligence-coordinator swarm-memory-manager byzantine-coordinator raft-manager gossip-coordinator consensus-builder crdt-synchronizer quorum-manager security-manager perf-analyzer performance-benchmarker task-orchestrator memory-coordinator smart-agent github-modes pr-manager code-review-swarm issue-tracker release-manager workflow-automation project-board-sync repo-architect multi-repo-swarm sparc-coord sparc-coder specification pseudocode architecture refinement backend-dev mobile-dev ml-developer cicd-engineer api-docs system-architect code-analyzer base-template-generator tdd-london-swarm production-validator'.split(' ');
const HOOK_TYPES = 'pre-edit post-edit pre-command post-command pre-task post-task session-start session-end session-restore notify route explain pretrain build-agents transfer teammate-idle task-completed'.split(' ');
const WORKER_TYPES = 'ultralearn optimize consolidate predict audit map preload deepdive document refactor benchmark testgaps'.split(' ');
const OFFLINE_MODELS = [
{ name: 'phi-3-mini-q4', format: 'gguf', sizeHint: '2.3GB', params: '3.8B' },
{ name: 'qwen2.5-coder-3b-q4', format: 'gguf', sizeHint: '1.7GB', params: '3B' },
];
// ── API Key Encryption / Decryption ──────────────────────────
/** Encrypt API keys from a .env file. Output: salt(32)+iv(16)+tag(16)+ciphertext */
export function encryptApiKeys(envPath, passphrase) {
const keys = parseEnvFile(readFileSync(envPath, 'utf-8'));
const plaintext = Buffer.from(JSON.stringify(keys), 'utf-8');
const salt = randomBytes(SCRYPT_SALT_LEN);
const key = scryptSync(passphrase, salt, SCRYPT_KEY_LEN, SCRYPT_OPTS);
const iv = randomBytes(AES_IV_LEN);
const cipher = createCipheriv(AES_ALG, key, iv);
const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]);
return Buffer.concat([salt, iv, cipher.getAuthTag(), encrypted]);
}
/** Decrypt API keys previously encrypted with encryptApiKeys. */
export function decryptApiKeys(buf, passphrase) {
const minLen = SCRYPT_SALT_LEN + AES_IV_LEN + AES_TAG_LEN + 1;
if (buf.length < minLen) {
throw new Error(`Encrypted buffer too short: need >= ${minLen}B, got ${buf.length}B`);
}
let off = 0;
const salt = buf.subarray(off, off += SCRYPT_SALT_LEN);
const iv = buf.subarray(off, off += AES_IV_LEN);
const tag = buf.subarray(off, off += AES_TAG_LEN);
const ciphertext = buf.subarray(off);
const key = scryptSync(passphrase, salt, SCRYPT_KEY_LEN, SCRYPT_OPTS);
const decipher = createDecipheriv(AES_ALG, key, iv);
decipher.setAuthTag(tag);
return JSON.parse(Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString('utf-8'));
}
export class RvfaBuilder {
opts;
constructor(options) {
this.opts = {
arch: options.arch || 'x86_64',
profile: options.profile,
output: resolve(options.output),
rufloVersion: options.rufloVersion || detectRufloVersion(),
models: options.models ?? defaultModelsForProfile(options.profile),
apiKeys: options.apiKeys ?? '',
verbose: options.verbose ?? false,
};
}
async build() {
const t0 = performance.now();
this.log(`Building RVFA appliance (profile=${this.opts.profile}, arch=${this.opts.arch})`);
const outDir = dirname(this.opts.output);
if (!existsSync(outDir))
mkdirSync(outDir, { recursive: true });
const stages = [
{ id: 'kernel', raw: this.buildKernelSection(), label: 'Kernel (Alpine rootfs)' },
{ id: 'runtime', raw: this.buildRuntimeSection(), label: 'Runtime (Node.js + Claude Code)' },
{ id: 'ruflo', raw: this.buildRufloSection(), label: 'Ruflo CLI' },
{ id: 'models', raw: this.buildModelsSection(), label: `Models (${this.opts.profile})` },
{ id: 'data', raw: this.buildDataSection(), label: 'Data (AgentDB)' },
{ id: 'verify', raw: this.buildVerifySection(), label: 'Verify (test suite)' },
];
const writer = new RvfaWriter(this.buildHeaderPartial());
const summary = [];
for (const s of stages) {
const st = performance.now();
this.log(` Stage: ${s.label}...`);
writer.addSection(s.id, s.raw, { type: s.id });
this.log(` ${fmtBytes(s.raw.length)} raw (${elapsed(st)})`);
summary.push({ id: s.id, size: 0, originalSize: s.raw.length });
}
const binary = writer.build();
// Patch compressed sizes from the built header
try {
const hLen = binary.readUInt32LE(8);
const hdr = JSON.parse(binary.subarray(12, 12 + hLen).toString('utf-8'));
for (const sec of hdr.sections) {
const e = summary.find((x) => x.id === sec.id);
if (e)
e.size = sec.size;
}
}
catch { /* non-fatal */ }
writeFileSync(this.opts.output, binary);
const duration = performance.now() - t0;
this.log('');
this.log('RVFA appliance built successfully.');
this.log(` Output: ${this.opts.output} Size: ${fmtBytes(binary.length)} Duration: ${elapsed(t0)}`);
for (const s of summary) {
const r = s.originalSize > 0 && s.size > 0
? ` (${((1 - s.size / s.originalSize) * 100).toFixed(1)}% reduction)` : '';
this.log(` ${s.id}: ${fmtBytes(s.originalSize)} -> ${fmtBytes(s.size)}${r}`);
}
return { outputPath: this.opts.output, size: binary.length, sections: summary, duration, profile: this.opts.profile };
}
// ── Section Builders ─────────────────────────────────────
buildKernelSection() {
return jsonBuf({
type: 'kernel', distribution: 'alpine', version: '3.23', arch: this.opts.arch,
packages: ['busybox', 'dumb-init', 'musl'],
init: '/sbin/init -> ruflo-init (PID 1)',
features: ['minimal rootfs', 'read-only root filesystem', 'tmpfs for /tmp and /run', 'seccomp profile applied'],
sizeTarget: '~5MB compressed',
note: 'Manifest-only: actual rootfs fetched during full build pipeline',
});
}
buildRuntimeSection() {
let nodeVersion = 'v22.0.0';
try {
nodeVersion = execSync('node --version', { encoding: 'utf-8' }).trim();
}
catch { /* keep default */ }
return jsonBuf({
type: 'runtime',
node: { version: nodeVersion, target: 'v22', variant: `linux-${this.opts.arch}-musl`, stripped: true, excludes: ['npm', 'corepack', 'debug-symbols'] },
claudeCode: { name: 'claude-code-cli', entrypoint: '/usr/local/bin/claude' },
paths: { node: '/usr/local/bin/node', claude: '/usr/local/bin/claude' },
});
}
buildRufloSection() {
let packageMeta = null;
try {
const raw = execSync('npm pack ruflo@latest --dry-run --json 2>/dev/null', { encoding: 'utf-8', timeout: 15_000 });
const parsed = JSON.parse(raw);
if (Array.isArray(parsed) && parsed.length > 0)
packageMeta = parsed[0];
}
catch { /* manifest-only fallback */ }
return jsonBuf({
type: 'ruflo', version: this.opts.rufloVersion,
package: packageMeta ?? { name: 'ruflo', version: this.opts.rufloVersion },
commands: RUFLO_COMMANDS, commandCount: RUFLO_COMMANDS.length,
agents: AGENT_TYPES, agentCount: AGENT_TYPES.length,
hooks: { count: HOOK_TYPES.length, types: HOOK_TYPES },
workers: { count: WORKER_TYPES.length, types: WORKER_TYPES },
mcpTools: 215,
});
}
buildModelsSection() {
const p = this.opts.profile;
const resolveModels = (names) => names.map((n) => OFFLINE_MODELS.find((m) => m.name === n) ?? { name: n, format: 'gguf', sizeHint: 'unknown', params: 'unknown' });
if (p === 'cloud') {
const content = { type: 'models', profile: 'cloud', provider: 'api-vault', models: [] };
if (this.opts.apiKeys && existsSync(this.opts.apiKeys)) {
const enc = encryptApiKeys(this.opts.apiKeys, generateBuildPassphrase());
content.vault = { format: AES_ALG, kdf: 'scrypt', kdfParams: SCRYPT_OPTS, encrypted: enc.toString('base64') };
this.log(' API keys encrypted into vault');
}
else {
content.vault = null;
content.note = 'No API keys provided; set --api-keys to include vault';
}
return jsonBuf(content);
}
if (p === 'hybrid') {
const content = {
type: 'models', profile: 'hybrid', provider: 'hybrid', engine: 'ruvllm+api-vault',
localModels: resolveModels(this.opts.models),
routing: { tier1: { handler: 'agent-booster', latency: '<1ms' }, tier2: { handler: 'local-model', latency: '~200ms' }, tier3: { handler: 'cloud-api', latency: '2-5s' }, complexityThreshold: 0.3 },
};
if (this.opts.apiKeys && existsSync(this.opts.apiKeys)) {
const enc = encryptApiKeys(this.opts.apiKeys, generateBuildPassphrase());
content.vault = { format: AES_ALG, kdf: 'scrypt', encrypted: enc.toString('base64') };
this.log(' API keys encrypted into vault');
}
return jsonBuf(content);
}
// offline
const names = this.opts.models.length > 0 ? this.opts.models : ['phi-3-mini-q4', 'qwen2.5-coder-3b-q4'];
return jsonBuf({
type: 'models', profile: 'offline', provider: 'ruvllm', engine: 'ruvllm',
models: resolveModels(names),
routing: { tier1: { handler: 'agent-booster-wasm', latency: '<1ms' }, tier2: { handler: 'phi-3-mini-q4', latency: '~200ms' }, tier3: { handler: 'qwen2.5-coder-3b-q4', latency: '~2s' }, fallbackToCloud: false },
kvCache: { backend: 'rvf', persistence: true },
note: 'Manifest-only: GGUF weights fetched during full build pipeline',
});
}
buildDataSection() {
// Empty RVF database header (matches rvf-backend.ts RVF\0 magic)
const rvfMagic = Buffer.from([0x52, 0x56, 0x46, 0x00]);
const hdrJson = Buffer.from(JSON.stringify({
magic: 'RVF\0', version: 1, dimensions: 1536, metric: 'cosine',
quantization: 'fp32', entryCount: 0, createdAt: Date.now(), updatedAt: Date.now(),
}), 'utf-8');
const hdrLen = Buffer.alloc(4);
hdrLen.writeUInt32LE(hdrJson.length, 0);
const rvfDb = Buffer.concat([rvfMagic, hdrLen, hdrJson]);
const manifest = jsonBuf({
type: 'data',
components: {
agentDb: { format: 'rvf', magicBytes: 'RVF\\0', databaseSize: rvfDb.length },
hnswIndex: { type: 'hnsw-index', dimensions: 1536, metric: 'cosine', m: 16, efConstruction: 200, maxElements: 100_000, vectorCount: 0 },
sonaPatterns: { type: 'sona-patterns', version: 1, architecture: 'self-optimizing-neural', adaptationTime: '<0.05ms', patterns: [], expertCount: 8, moeConfig: { topK: 2, capacityFactor: 1.25, loadBalancingLoss: 0.01 } },
pluginRegistry: { source: 'ipfs', snapshotted: true, pluginCount: 20 },
},
});
return Buffer.concat([rvfDb, Buffer.from('\n---DATA-MANIFEST---\n'), manifest]);
}
buildVerifySection() {
const scriptPath = resolve(dirname(new URL(import.meta.url).pathname), '../../../../scripts/verify-appliance.sh');
let script;
if (existsSync(scriptPath)) {
script = readFileSync(scriptPath);
this.log(` Bundled verify-appliance.sh (${fmtBytes(script.length)})`);
}
else {
script = Buffer.from([
'#!/bin/sh', 'set -e', 'RUFLO_CMD="${RUFLO_CMD:-ruflo}"',
'echo "Running basic verification..."',
'$RUFLO_CMD --version && echo " OK: CLI" || echo " FAIL: CLI"',
'$RUFLO_CMD doctor && echo " OK: Doctor" || echo " FAIL: Doctor"',
'echo "Verification complete."',
].join('\n'), 'utf-8');
this.log(' Using stub verify script (verify-appliance.sh not found)');
}
const manifest = jsonBuf({
type: 'verify-manifest', categories: 35, criticalChecks: 95,
script: 'verify-appliance.sh',
scriptSha256: createHash('sha256').update(script).digest('hex'),
quickMode: { categories: [1, 2, 3, 4, 5, 25] },
});
return Buffer.concat([script, Buffer.from('\n---VERIFY-MANIFEST---\n'), manifest]);
}
// ── Header ───────────────────────────────────────────────
buildHeaderPartial() {
const providerMap = { cloud: 'api-vault', hybrid: 'hybrid', offline: 'ruvllm' };
const caps = ['cli-26-commands', 'agents-60-plus', 'hooks-17', 'workers-12', 'mcp-215-tools', 'agentdb-rvf', 'hnsw-search', 'sona-patterns', 'security-scanning', 'performance-profiling', 'hive-mind-consensus', 'plugin-registry'];
if (this.opts.profile !== 'cloud')
caps.push('local-inference-ruvllm');
if (this.opts.profile !== 'offline')
caps.push('cloud-api-vault');
const boot = {
entrypoint: '/opt/ruflo/bin/cli.js',
args: ['--profile', this.opts.profile],
env: { NODE_ENV: 'production', CLAUDE_FLOW_MEMORY_BACKEND: 'hybrid', CLAUDE_FLOW_LOG_LEVEL: 'info' },
isolation: this.opts.profile === 'cloud' ? 'container' : 'native',
};
const models = {
provider: providerMap[this.opts.profile],
engine: this.opts.profile === 'cloud' ? undefined : 'ruvllm-0.1.0',
models: this.opts.models.length > 0 ? this.opts.models : undefined,
vaultEncryption: this.opts.profile !== 'offline' ? 'aes-256-gcm' : undefined,
};
return {
magic: 'RVFA', version: 1, name: 'ruflo-appliance', appVersion: this.opts.rufloVersion,
arch: this.opts.arch, platform: 'linux', profile: this.opts.profile,
created: new Date().toISOString(), boot, models, capabilities: caps,
};
}
log(msg) {
if (this.opts.verbose)
console.log(`[RvfaBuilder] ${msg}`);
}
}
// ── Helpers ──────────────────────────────────────────────────
function jsonBuf(obj) {
return Buffer.from(JSON.stringify(obj, null, 2), 'utf-8');
}
function parseEnvFile(content) {
const result = {};
for (const line of content.split('\n')) {
const t = line.trim();
if (!t || t.startsWith('#'))
continue;
const eq = t.indexOf('=');
if (eq === -1)
continue;
const k = t.substring(0, eq).trim();
let v = t.substring(eq + 1).trim();
if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'")))
v = v.slice(1, -1);
if (k)
result[k] = v;
}
return result;
}
function detectRufloVersion() {
try {
const p = resolve(dirname(new URL(import.meta.url).pathname), '../../package.json');
if (existsSync(p))
return JSON.parse(readFileSync(p, 'utf-8')).version ?? '3.5.0';
}
catch { /* ignore */ }
return '3.5.0';
}
function defaultModelsForProfile(profile) {
if (profile === 'offline')
return ['phi-3-mini-q4', 'qwen2.5-coder-3b-q4'];
if (profile === 'hybrid')
return ['phi-3-mini-q4'];
return [];
}
function generateBuildPassphrase() {
return randomBytes(32).toString('hex');
}
function fmtBytes(b) {
if (b < 1024)
return `${b}B`;
if (b < 1048576)
return `${(b / 1024).toFixed(1)}KB`;
if (b < 1073741824)
return `${(b / 1048576).toFixed(1)}MB`;
return `${(b / 1073741824).toFixed(2)}GB`;
}
function elapsed(t) {
const ms = performance.now() - t;
return ms < 1000 ? `${ms.toFixed(0)}ms` : `${(ms / 1000).toFixed(2)}s`;
}
//# sourceMappingURL=rvfa-builder.js.map