moflo 4.10.2 → 4.10.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,231 +0,0 @@
1
- /**
2
- * ruvLLM Bridge -- Local Language Model Inference
3
- *
4
- * Provides 3-tier routing for on-device GGUF model inference:
5
- * Tier 1: Agent Booster (WASM, <1ms) -- simple transforms
6
- * Tier 2: Local model via GGUF engine (~200ms) -- routing, classification
7
- * Tier 3: Cloud API (2-5s) -- complex reasoning
8
- *
9
- * The bridge degrades gracefully when no local models are available.
10
- *
11
- * @module moflo/appliance/ruvllm-bridge
12
- */
13
- import { readdir, stat } from 'node:fs/promises';
14
- import { join, extname, basename } from 'node:path';
15
- const DEFAULT_CONFIG = {
16
- modelsDir: './models', defaultModel: '', maxTokens: 512,
17
- temperature: 0.7, contextSize: 4096, kvCachePath: '', verbose: false,
18
- };
19
- // ── Quantization / parameter heuristics ─────────────────────
20
- const QUANT_PATTERNS = [
21
- [/q4_k_m/i, 'q4_k_m'], [/q4_k_s/i, 'q4_k_s'], [/q4_0/i, 'q4_0'],
22
- [/q5_k_m/i, 'q5_k_m'], [/q5_0/i, 'q5_0'], [/q8_0/i, 'q8_0'],
23
- [/f16/i, 'f16'], [/f32/i, 'f32'],
24
- ];
25
- function inferQuantization(filename) {
26
- for (const [re, label] of QUANT_PATTERNS)
27
- if (re.test(filename))
28
- return label;
29
- return 'unknown';
30
- }
31
- function inferParameters(filename) {
32
- const m = filename.match(/(\d+)[._-]?b/i);
33
- return m ? m[0].toUpperCase().replace(/[._-]/g, '') : 'unknown';
34
- }
35
- // ── Complexity heuristic ────────────────────────────────────
36
- const HIGH = new Set([
37
- 'architect', 'design', 'refactor', 'security', 'audit', 'complex',
38
- 'analyze', 'distributed', 'concurrent', 'algorithm', 'investigate',
39
- 'optimize', 'debug', 'system', 'integration',
40
- ]);
41
- const LOW = new Set([
42
- 'rename', 'typo', 'format', 'comment', 'version', 'bump',
43
- 'move', 'copy', 'delete', 'simple', 'config',
44
- ]);
45
- function estimateComplexity(desc) {
46
- const words = desc.toLowerCase().split(/\s+/);
47
- let score = 0.3;
48
- for (const w of words) {
49
- if (HIGH.has(w))
50
- score += 0.15;
51
- if (LOW.has(w))
52
- score -= 0.1;
53
- }
54
- return Math.max(0, Math.min(1, score + Math.min(0.2, words.length / 200)));
55
- }
56
- // ── Bridge ──────────────────────────────────────────────────
57
- export class RuvllmBridge {
58
- config;
59
- models = new Map();
60
- activeModel = null;
61
- kvCacheEntries = 0;
62
- ggufEngine = null;
63
- constructor(config) {
64
- if (!config.modelsDir)
65
- throw new Error('RuvllmConfig.modelsDir is required');
66
- this.config = { ...DEFAULT_CONFIG, ...config };
67
- }
68
- /** Initialize GGUF engine and scan modelsDir. */
69
- async initialize() {
70
- // Initialize GGUF engine for local model inference
71
- try {
72
- const { GgufEngine } = await import('./gguf-engine.js');
73
- this.ggufEngine = new GgufEngine({
74
- contextSize: this.config.contextSize,
75
- maxTokens: this.config.maxTokens,
76
- temperature: this.config.temperature,
77
- kvCachePath: this.config.kvCachePath,
78
- verbose: this.config.verbose,
79
- });
80
- await this.ggufEngine.initialize();
81
- }
82
- catch {
83
- // GGUF engine is optional
84
- }
85
- await this.scanModelsDir();
86
- if (this.config.verbose) {
87
- const pkgs = [
88
- this.ggufEngine && 'gguf-engine',
89
- ].filter(Boolean);
90
- if (pkgs.length)
91
- console.log(`[ruvLLM] Loaded: ${pkgs.join(', ')}`);
92
- console.log(`[ruvLLM] ${this.models.size} model(s) in ${this.config.modelsDir}`);
93
- }
94
- }
95
- /** Return all discovered GGUF models. */
96
- async listModels() {
97
- return Array.from(this.models.values());
98
- }
99
- /** Load a model into memory (delegates to GGUF engine). */
100
- async loadModel(name) {
101
- const info = this.models.get(name);
102
- if (!info)
103
- throw new Error(`Model "${name}" not found. Available: ${[...this.models.keys()].join(', ')}`);
104
- if (this.ggufEngine) {
105
- const meta = await this.ggufEngine.loadModel(info.path);
106
- if (meta.architecture)
107
- info.parameters = meta.architecture;
108
- if (meta.quantization)
109
- info.quantization = meta.quantization;
110
- }
111
- info.loaded = true;
112
- this.activeModel = name;
113
- }
114
- /**
115
- * Generate text from a prompt. Routes through tiers:
116
- * 1. Agent Booster (trivial transforms, no LLM).
117
- * 2. Local GGUF model.
118
- * 3. Cloud fallback (empty response -- caller handles upstream).
119
- */
120
- async generate(request) {
121
- const start = performance.now();
122
- const modelName = request.model ?? this.config.defaultModel ?? this.activeModel ?? '';
123
- // Tier 1: Agent Booster
124
- const booster = this.tryAgentBooster(request.prompt);
125
- if (booster !== null) {
126
- return { text: booster, model: 'agent-booster', tokensUsed: 0, latencyMs: performance.now() - start, tier: 1, cached: false };
127
- }
128
- // Tier 2: Local model via GGUF engine
129
- const info = this.models.get(modelName);
130
- if (info?.loaded && this.ggufEngine) {
131
- try {
132
- const r = await this.ggufEngine.generate({
133
- prompt: request.prompt,
134
- maxTokens: request.maxTokens ?? this.config.maxTokens,
135
- temperature: request.temperature ?? this.config.temperature,
136
- stopSequences: request.stopSequences,
137
- });
138
- return { text: r.text, model: modelName, tokensUsed: r.tokensUsed, latencyMs: performance.now() - start, tier: 2, cached: false };
139
- }
140
- catch (err) {
141
- if (this.config.verbose)
142
- console.warn('[ruvLLM] Local generation failed, tier 3 fallback:', err);
143
- }
144
- }
145
- // Tier 3: Cloud fallback
146
- return { text: '', model: 'cloud-fallback', tokensUsed: 0, latencyMs: performance.now() - start, tier: 3, cached: false };
147
- }
148
- /** Route a task description to the optimal tier using complexity heuristics. */
149
- async routeTask(description) {
150
- const complexity = estimateComplexity(description);
151
- const words = description.split(/\s+/).length;
152
- if (words < 15 && complexity < 0.25)
153
- return { tier: 1, model: 'agent-booster', confidence: 0.9 };
154
- if (complexity < 0.55 && this.activeModel)
155
- return { tier: 2, model: this.activeModel, confidence: 0.7 };
156
- return { tier: 3, model: 'cloud', confidence: 0.6 };
157
- }
158
- /** Return current bridge status. */
159
- async getStatus() {
160
- return {
161
- available: this.models.size > 0,
162
- modelsLoaded: [...this.models.values()].filter((m) => m.loaded).map((m) => m.name),
163
- kvCacheSize: this.kvCacheEntries,
164
- };
165
- }
166
- /** Unload models and clean up. */
167
- async shutdown() {
168
- if (this.ggufEngine) {
169
- await this.ggufEngine.shutdown();
170
- this.ggufEngine = null;
171
- }
172
- for (const info of this.models.values())
173
- info.loaded = false;
174
- this.activeModel = null;
175
- this.kvCacheEntries = 0;
176
- }
177
- // ── Private ───────────────────────────────────────────────
178
- async scanModelsDir() {
179
- try {
180
- const entries = await readdir(this.config.modelsDir);
181
- for (const entry of entries) {
182
- if (extname(entry).toLowerCase() !== '.gguf')
183
- continue;
184
- const fullPath = join(this.config.modelsDir, entry);
185
- const s = await stat(fullPath);
186
- if (!s.isFile())
187
- continue;
188
- const name = basename(entry, '.gguf');
189
- this.models.set(name, {
190
- name, path: fullPath, format: 'gguf',
191
- quantization: inferQuantization(entry), size: s.size,
192
- parameters: inferParameters(entry), loaded: false,
193
- });
194
- }
195
- }
196
- catch {
197
- // modelsDir may not exist -- tier 1 and tier 3 still work
198
- }
199
- }
200
- /** Tier-1 Agent Booster: handle trivial transforms without any LLM. */
201
- tryAgentBooster(prompt) {
202
- const t = prompt.trim();
203
- if (t.length > 200)
204
- return null;
205
- if (/^(convert|change)\s+(var|let)\s+to\s+const$/i.test(t)) {
206
- return 'Use the Edit tool to replace `var`/`let` declarations with `const`.';
207
- }
208
- if (/^remove\s+console\.(log|warn|error|debug|info)$/i.test(t)) {
209
- const m = t.toLowerCase().match(/console\.(\w+)/)?.[1] ?? 'log';
210
- return `Use the Edit tool to remove all \`console.${m}\` calls.`;
211
- }
212
- return null;
213
- }
214
- }
215
- // ── Singleton accessor ──────────────────────────────────────
216
- let instance = null;
217
- /** Get or create the singleton RuvllmBridge. Config required on first call. */
218
- export function getRuvllmBridge(config) {
219
- if (!instance && config)
220
- instance = new RuvllmBridge(config);
221
- if (!instance)
222
- throw new Error('ruvLLM bridge not initialized. Call with config first.');
223
- return instance;
224
- }
225
- /** Reset the singleton (useful for tests). */
226
- export function resetRuvllmBridge() { instance = null; }
227
- /** Check whether ruvLLM bridge is available (GGUF engine is the primary backend). */
228
- export async function isRuvllmAvailable() {
229
- return true; // Pure TS implementation — always available
230
- }
231
- //# sourceMappingURL=ruvllm-bridge.js.map
@@ -1,325 +0,0 @@
1
- /**
2
- * RVFA Appliance Builder -- Constructs self-contained .rvf appliance files.
3
- *
4
- * Creates a single binary containing kernel, runtime, Ruflo CLI, models/keys,
5
- * AgentDB data, and the verification suite. See ADR-058.
6
- */
7
- import { createHash, scryptSync, randomBytes, createCipheriv, createDecipheriv, } from 'node:crypto';
8
- import { readFileSync, existsSync, writeFileSync, mkdirSync } from 'node:fs';
9
- import { dirname, join, resolve } from 'node:path';
10
- import { execSync } from 'node:child_process';
11
- import { RvfaWriter, } from './rvfa-format.js';
12
- import { locateMofloRootPath } from '../services/moflo-require.js';
13
- import { VERSION } from '../version.js';
14
- // ── Encryption Constants ─────────────────────────────────────
15
- const SCRYPT_KEY_LEN = 32;
16
- const SCRYPT_SALT_LEN = 32;
17
- const SCRYPT_OPTS = { N: 16384, r: 8, p: 1 };
18
- const AES_IV_LEN = 16;
19
- const AES_TAG_LEN = 16;
20
- const AES_ALG = 'aes-256-gcm';
21
- // ── Catalog ──────────────────────────────────────────────────
22
- 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(' ');
23
- const AGENT_TYPES = 'coder reviewer tester planner researcher security-auditor backend-dev frontend-dev database-dev cicd-engineer api-docs system-architect code-analyzer analyst base-template-generator test-long-runner'.split(' ');
24
- 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(' ');
25
- const WORKER_TYPES = 'ultralearn optimize consolidate predict audit map preload deepdive document refactor benchmark testgaps'.split(' ');
26
- const OFFLINE_MODELS = [
27
- { name: 'phi-3-mini-q4', format: 'gguf', sizeHint: '2.3GB', params: '3.8B' },
28
- { name: 'qwen2.5-coder-3b-q4', format: 'gguf', sizeHint: '1.7GB', params: '3B' },
29
- ];
30
- // ── API Key Encryption / Decryption ──────────────────────────
31
- /** Encrypt API keys from a .env file. Output: salt(32)+iv(16)+tag(16)+ciphertext */
32
- export function encryptApiKeys(envPath, passphrase) {
33
- const keys = parseEnvFile(readFileSync(envPath, 'utf-8'));
34
- const plaintext = Buffer.from(JSON.stringify(keys), 'utf-8');
35
- const salt = randomBytes(SCRYPT_SALT_LEN);
36
- const key = scryptSync(passphrase, salt, SCRYPT_KEY_LEN, SCRYPT_OPTS);
37
- const iv = randomBytes(AES_IV_LEN);
38
- const cipher = createCipheriv(AES_ALG, key, iv);
39
- const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]);
40
- return Buffer.concat([salt, iv, cipher.getAuthTag(), encrypted]);
41
- }
42
- /** Decrypt API keys previously encrypted with encryptApiKeys. */
43
- export function decryptApiKeys(buf, passphrase) {
44
- const minLen = SCRYPT_SALT_LEN + AES_IV_LEN + AES_TAG_LEN + 1;
45
- if (buf.length < minLen) {
46
- throw new Error(`Encrypted buffer too short: need >= ${minLen}B, got ${buf.length}B`);
47
- }
48
- let off = 0;
49
- const salt = buf.subarray(off, off += SCRYPT_SALT_LEN);
50
- const iv = buf.subarray(off, off += AES_IV_LEN);
51
- const tag = buf.subarray(off, off += AES_TAG_LEN);
52
- const ciphertext = buf.subarray(off);
53
- const key = scryptSync(passphrase, salt, SCRYPT_KEY_LEN, SCRYPT_OPTS);
54
- const decipher = createDecipheriv(AES_ALG, key, iv);
55
- decipher.setAuthTag(tag);
56
- return JSON.parse(Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString('utf-8'));
57
- }
58
- export class RvfaBuilder {
59
- opts;
60
- constructor(options) {
61
- this.opts = {
62
- arch: options.arch || 'x86_64',
63
- profile: options.profile,
64
- output: resolve(options.output),
65
- rufloVersion: options.rufloVersion || VERSION,
66
- models: options.models ?? defaultModelsForProfile(options.profile),
67
- apiKeys: options.apiKeys ?? '',
68
- verbose: options.verbose ?? false,
69
- };
70
- }
71
- async build() {
72
- const t0 = performance.now();
73
- this.log(`Building RVFA appliance (profile=${this.opts.profile}, arch=${this.opts.arch})`);
74
- const outDir = dirname(this.opts.output);
75
- if (!existsSync(outDir))
76
- mkdirSync(outDir, { recursive: true });
77
- const stages = [
78
- { id: 'kernel', raw: this.buildKernelSection(), label: 'Kernel (Alpine rootfs)' },
79
- { id: 'runtime', raw: this.buildRuntimeSection(), label: 'Runtime (Node.js + Claude Code)' },
80
- { id: 'ruflo', raw: this.buildRufloSection(), label: 'Ruflo CLI' },
81
- { id: 'models', raw: this.buildModelsSection(), label: `Models (${this.opts.profile})` },
82
- { id: 'data', raw: this.buildDataSection(), label: 'Data (AgentDB)' },
83
- { id: 'verify', raw: this.buildVerifySection(), label: 'Verify (test suite)' },
84
- ];
85
- const writer = new RvfaWriter(this.buildHeaderPartial());
86
- const summary = [];
87
- for (const s of stages) {
88
- const st = performance.now();
89
- this.log(` Stage: ${s.label}...`);
90
- writer.addSection(s.id, s.raw, { type: s.id });
91
- this.log(` ${fmtBytes(s.raw.length)} raw (${elapsed(st)})`);
92
- summary.push({ id: s.id, size: 0, originalSize: s.raw.length });
93
- }
94
- const binary = writer.build();
95
- // Patch compressed sizes from the built header
96
- try {
97
- const hLen = binary.readUInt32LE(8);
98
- const hdr = JSON.parse(binary.subarray(12, 12 + hLen).toString('utf-8'));
99
- for (const sec of hdr.sections) {
100
- const e = summary.find((x) => x.id === sec.id);
101
- if (e)
102
- e.size = sec.size;
103
- }
104
- }
105
- catch { /* non-fatal */ }
106
- writeFileSync(this.opts.output, binary);
107
- const duration = performance.now() - t0;
108
- this.log('');
109
- this.log('RVFA appliance built successfully.');
110
- this.log(` Output: ${this.opts.output} Size: ${fmtBytes(binary.length)} Duration: ${elapsed(t0)}`);
111
- for (const s of summary) {
112
- const r = s.originalSize > 0 && s.size > 0
113
- ? ` (${((1 - s.size / s.originalSize) * 100).toFixed(1)}% reduction)` : '';
114
- this.log(` ${s.id}: ${fmtBytes(s.originalSize)} -> ${fmtBytes(s.size)}${r}`);
115
- }
116
- return { outputPath: this.opts.output, size: binary.length, sections: summary, duration, profile: this.opts.profile };
117
- }
118
- // ── Section Builders ─────────────────────────────────────
119
- buildKernelSection() {
120
- return jsonBuf({
121
- type: 'kernel', distribution: 'alpine', version: '3.23', arch: this.opts.arch,
122
- packages: ['busybox', 'dumb-init', 'musl'],
123
- init: '/sbin/init -> ruflo-init (PID 1)',
124
- features: ['minimal rootfs', 'read-only root filesystem', 'tmpfs for /tmp and /run', 'seccomp profile applied'],
125
- sizeTarget: '~5MB compressed',
126
- note: 'Manifest-only: actual rootfs fetched during full build pipeline',
127
- });
128
- }
129
- buildRuntimeSection() {
130
- let nodeVersion = 'v22.0.0';
131
- try {
132
- nodeVersion = execSync('node --version', { encoding: 'utf-8', windowsHide: true }).trim();
133
- }
134
- catch { /* keep default */ }
135
- return jsonBuf({
136
- type: 'runtime',
137
- node: { version: nodeVersion, target: 'v22', variant: `linux-${this.opts.arch}-musl`, stripped: true, excludes: ['npm', 'corepack', 'debug-symbols'] },
138
- claudeCode: { name: 'claude-code-cli', entrypoint: '/usr/local/bin/claude' },
139
- paths: { node: '/usr/local/bin/node', claude: '/usr/local/bin/claude' },
140
- });
141
- }
142
- buildRufloSection() {
143
- let packageMeta = null;
144
- try {
145
- const nd = process.platform === 'win32' ? 'NUL' : '/dev/null';
146
- const raw = execSync(`npm pack ruflo@latest --dry-run --json 2>${nd}`, { encoding: 'utf-8', timeout: 15_000, windowsHide: true });
147
- const parsed = JSON.parse(raw);
148
- if (Array.isArray(parsed) && parsed.length > 0)
149
- packageMeta = parsed[0];
150
- }
151
- catch { /* manifest-only fallback */ }
152
- return jsonBuf({
153
- type: 'ruflo', version: this.opts.rufloVersion,
154
- package: packageMeta ?? { name: 'ruflo', version: this.opts.rufloVersion },
155
- commands: RUFLO_COMMANDS, commandCount: RUFLO_COMMANDS.length,
156
- agents: AGENT_TYPES, agentCount: AGENT_TYPES.length,
157
- hooks: { count: HOOK_TYPES.length, types: HOOK_TYPES },
158
- workers: { count: WORKER_TYPES.length, types: WORKER_TYPES },
159
- mcpTools: 215,
160
- });
161
- }
162
- buildModelsSection() {
163
- const p = this.opts.profile;
164
- const resolveModels = (names) => names.map((n) => OFFLINE_MODELS.find((m) => m.name === n) ?? { name: n, format: 'gguf', sizeHint: 'unknown', params: 'unknown' });
165
- if (p === 'cloud') {
166
- const content = { type: 'models', profile: 'cloud', provider: 'api-vault', models: [] };
167
- if (this.opts.apiKeys && existsSync(this.opts.apiKeys)) {
168
- const enc = encryptApiKeys(this.opts.apiKeys, generateBuildPassphrase());
169
- content.vault = { format: AES_ALG, kdf: 'scrypt', kdfParams: SCRYPT_OPTS, encrypted: enc.toString('base64') };
170
- this.log(' API keys encrypted into vault');
171
- }
172
- else {
173
- content.vault = null;
174
- content.note = 'No API keys provided; set --api-keys to include vault';
175
- }
176
- return jsonBuf(content);
177
- }
178
- if (p === 'hybrid') {
179
- const content = {
180
- type: 'models', profile: 'hybrid', provider: 'hybrid', engine: 'ruvllm+api-vault',
181
- localModels: resolveModels(this.opts.models),
182
- routing: { tier1: { handler: 'agent-booster', latency: '<1ms' }, tier2: { handler: 'local-model', latency: '~200ms' }, tier3: { handler: 'cloud-api', latency: '2-5s' }, complexityThreshold: 0.3 },
183
- };
184
- if (this.opts.apiKeys && existsSync(this.opts.apiKeys)) {
185
- const enc = encryptApiKeys(this.opts.apiKeys, generateBuildPassphrase());
186
- content.vault = { format: AES_ALG, kdf: 'scrypt', encrypted: enc.toString('base64') };
187
- this.log(' API keys encrypted into vault');
188
- }
189
- return jsonBuf(content);
190
- }
191
- // offline
192
- const names = this.opts.models.length > 0 ? this.opts.models : ['phi-3-mini-q4', 'qwen2.5-coder-3b-q4'];
193
- return jsonBuf({
194
- type: 'models', profile: 'offline', provider: 'ruvllm', engine: 'ruvllm',
195
- models: resolveModels(names),
196
- 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 },
197
- kvCache: { backend: 'rvf', persistence: true },
198
- note: 'Manifest-only: GGUF weights fetched during full build pipeline',
199
- });
200
- }
201
- buildDataSection() {
202
- // Empty RVF database header (matches rvf-backend.ts RVF\0 magic)
203
- const rvfMagic = Buffer.from([0x52, 0x56, 0x46, 0x00]);
204
- const hdrJson = Buffer.from(JSON.stringify({
205
- magic: 'RVF\0', version: 1, dimensions: 1536, metric: 'cosine',
206
- quantization: 'fp32', entryCount: 0, createdAt: Date.now(), updatedAt: Date.now(),
207
- }), 'utf-8');
208
- const hdrLen = Buffer.alloc(4);
209
- hdrLen.writeUInt32LE(hdrJson.length, 0);
210
- const rvfDb = Buffer.concat([rvfMagic, hdrLen, hdrJson]);
211
- const manifest = jsonBuf({
212
- type: 'data',
213
- components: {
214
- agentDb: { format: 'rvf', magicBytes: 'RVF\\0', databaseSize: rvfDb.length },
215
- hnswIndex: { type: 'hnsw-index', dimensions: 1536, metric: 'cosine', m: 16, efConstruction: 200, maxElements: 100_000, vectorCount: 0 },
216
- 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 } },
217
- pluginRegistry: { source: 'ipfs', snapshotted: true, pluginCount: 20 },
218
- },
219
- });
220
- return Buffer.concat([rvfDb, Buffer.from('\n---DATA-MANIFEST---\n'), manifest]);
221
- }
222
- buildVerifySection() {
223
- // Walk up to moflo root for the bundled verify script — see issue #575
224
- // (prior `new URL().pathname` was Windows-broken and the depth was wrong).
225
- const scriptPath = locateMofloRootPath(join('scripts', 'verify-appliance.sh'));
226
- let script;
227
- if (scriptPath && existsSync(scriptPath)) {
228
- script = readFileSync(scriptPath);
229
- this.log(` Bundled verify-appliance.sh (${fmtBytes(script.length)})`);
230
- }
231
- else {
232
- script = Buffer.from([
233
- '#!/bin/sh', 'set -e', 'RUFLO_CMD="${RUFLO_CMD:-ruflo}"',
234
- 'echo "Running basic verification..."',
235
- '$RUFLO_CMD --version && echo " OK: CLI" || echo " FAIL: CLI"',
236
- '$RUFLO_CMD doctor && echo " OK: Doctor" || echo " FAIL: Doctor"',
237
- 'echo "Verification complete."',
238
- ].join('\n'), 'utf-8');
239
- this.log(' Using stub verify script (verify-appliance.sh not found)');
240
- }
241
- const manifest = jsonBuf({
242
- type: 'verify-manifest', categories: 35, criticalChecks: 95,
243
- script: 'verify-appliance.sh',
244
- scriptSha256: createHash('sha256').update(script).digest('hex'),
245
- quickMode: { categories: [1, 2, 3, 4, 5, 25] },
246
- });
247
- return Buffer.concat([script, Buffer.from('\n---VERIFY-MANIFEST---\n'), manifest]);
248
- }
249
- // ── Header ───────────────────────────────────────────────
250
- buildHeaderPartial() {
251
- const providerMap = { cloud: 'api-vault', hybrid: 'hybrid', offline: 'ruvllm' };
252
- 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'];
253
- if (this.opts.profile !== 'cloud')
254
- caps.push('local-inference-ruvllm');
255
- if (this.opts.profile !== 'offline')
256
- caps.push('cloud-api-vault');
257
- const boot = {
258
- entrypoint: '/opt/ruflo/bin/cli.js',
259
- args: ['--profile', this.opts.profile],
260
- env: { NODE_ENV: 'production', CLAUDE_FLOW_MEMORY_BACKEND: 'hybrid', CLAUDE_FLOW_LOG_LEVEL: 'info' },
261
- isolation: this.opts.profile === 'cloud' ? 'container' : 'native',
262
- };
263
- const models = {
264
- provider: providerMap[this.opts.profile],
265
- engine: this.opts.profile === 'cloud' ? undefined : 'ruvllm-0.1.0',
266
- models: this.opts.models.length > 0 ? this.opts.models : undefined,
267
- vaultEncryption: this.opts.profile !== 'offline' ? 'aes-256-gcm' : undefined,
268
- };
269
- return {
270
- magic: 'RVFA', version: 1, name: 'ruflo-appliance', appVersion: this.opts.rufloVersion,
271
- arch: this.opts.arch, platform: 'linux', profile: this.opts.profile,
272
- created: new Date().toISOString(), boot, models, capabilities: caps,
273
- };
274
- }
275
- log(msg) {
276
- if (this.opts.verbose)
277
- console.log(`[RvfaBuilder] ${msg}`);
278
- }
279
- }
280
- // ── Helpers ──────────────────────────────────────────────────
281
- function jsonBuf(obj) {
282
- return Buffer.from(JSON.stringify(obj, null, 2), 'utf-8');
283
- }
284
- function parseEnvFile(content) {
285
- const result = {};
286
- for (const line of content.split(/\r?\n/)) {
287
- const t = line.trim();
288
- if (!t || t.startsWith('#'))
289
- continue;
290
- const eq = t.indexOf('=');
291
- if (eq === -1)
292
- continue;
293
- const k = t.substring(0, eq).trim();
294
- let v = t.substring(eq + 1).trim();
295
- if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'")))
296
- v = v.slice(1, -1);
297
- if (k)
298
- result[k] = v;
299
- }
300
- return result;
301
- }
302
- function defaultModelsForProfile(profile) {
303
- if (profile === 'offline')
304
- return ['phi-3-mini-q4', 'qwen2.5-coder-3b-q4'];
305
- if (profile === 'hybrid')
306
- return ['phi-3-mini-q4'];
307
- return [];
308
- }
309
- function generateBuildPassphrase() {
310
- return randomBytes(32).toString('hex');
311
- }
312
- function fmtBytes(b) {
313
- if (b < 1024)
314
- return `${b}B`;
315
- if (b < 1048576)
316
- return `${(b / 1024).toFixed(1)}KB`;
317
- if (b < 1073741824)
318
- return `${(b / 1048576).toFixed(1)}MB`;
319
- return `${(b / 1073741824).toFixed(2)}GB`;
320
- }
321
- function elapsed(t) {
322
- const ms = performance.now() - t;
323
- return ms < 1000 ? `${ms.toFixed(0)}ms` : `${(ms / 1000).toFixed(2)}s`;
324
- }
325
- //# sourceMappingURL=rvfa-builder.js.map