@ruvector/ruvllm 4.0.1 → 4.2.0

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.
Files changed (171) hide show
  1. package/README.md +446 -160
  2. package/bin/cli.js +1066 -0
  3. package/bin/gpu-delegate.js +67 -0
  4. package/bin/ruvllm-adapters.js +5 -0
  5. package/bin/ruvllm-gpu-loop.js +5 -0
  6. package/bin/serve.js +265 -0
  7. package/dist/cjs/benchmarks/embedding-benchmark.d.ts +102 -0
  8. package/dist/cjs/benchmarks/embedding-benchmark.d.ts.map +1 -0
  9. package/dist/cjs/benchmarks/embedding-benchmark.js +436 -0
  10. package/dist/cjs/benchmarks/index.d.ts +34 -0
  11. package/dist/cjs/benchmarks/index.d.ts.map +1 -0
  12. package/dist/cjs/benchmarks/index.js +132 -0
  13. package/dist/cjs/benchmarks/model-comparison.d.ts +71 -0
  14. package/dist/cjs/benchmarks/model-comparison.d.ts.map +1 -0
  15. package/dist/cjs/benchmarks/model-comparison.js +476 -0
  16. package/dist/cjs/benchmarks/routing-benchmark.d.ts +70 -0
  17. package/dist/cjs/benchmarks/routing-benchmark.d.ts.map +1 -0
  18. package/dist/cjs/benchmarks/routing-benchmark.js +289 -0
  19. package/dist/cjs/contrastive.d.ts +229 -0
  20. package/dist/cjs/contrastive.d.ts.map +1 -0
  21. package/dist/cjs/contrastive.js +589 -0
  22. package/dist/cjs/engine.d.ts +160 -0
  23. package/dist/cjs/engine.d.ts.map +1 -0
  24. package/dist/cjs/engine.js +552 -0
  25. package/dist/cjs/export.d.ts +182 -0
  26. package/dist/cjs/export.d.ts.map +1 -0
  27. package/dist/cjs/export.js +422 -0
  28. package/dist/cjs/federated.d.ts +233 -0
  29. package/dist/cjs/federated.d.ts.map +1 -0
  30. package/dist/cjs/federated.js +525 -0
  31. package/dist/cjs/index.d.ts +68 -0
  32. package/dist/cjs/index.d.ts.map +1 -0
  33. package/dist/cjs/index.js +104 -0
  34. package/dist/cjs/intelligence.d.ts +136 -0
  35. package/dist/cjs/intelligence.d.ts.map +1 -0
  36. package/dist/cjs/intelligence.js +226 -0
  37. package/dist/cjs/lora.d.ts +251 -0
  38. package/dist/cjs/lora.d.ts.map +1 -0
  39. package/dist/cjs/lora.js +509 -0
  40. package/dist/cjs/models.d.ts +129 -0
  41. package/dist/cjs/models.d.ts.map +1 -0
  42. package/dist/cjs/models.js +323 -0
  43. package/dist/cjs/native.d.ts +140 -0
  44. package/dist/cjs/native.d.ts.map +1 -0
  45. package/dist/cjs/native.js +98 -0
  46. package/dist/cjs/session.d.ts +80 -0
  47. package/dist/cjs/session.d.ts.map +1 -0
  48. package/dist/cjs/session.js +203 -0
  49. package/dist/cjs/simd.d.ts +90 -0
  50. package/dist/cjs/simd.d.ts.map +1 -0
  51. package/dist/cjs/simd.js +209 -0
  52. package/dist/cjs/sona.d.ts +223 -0
  53. package/dist/cjs/sona.d.ts.map +1 -0
  54. package/dist/cjs/sona.js +562 -0
  55. package/dist/cjs/streaming.d.ts +55 -0
  56. package/dist/cjs/streaming.d.ts.map +1 -0
  57. package/dist/cjs/streaming.js +131 -0
  58. package/dist/cjs/training.d.ts +312 -0
  59. package/dist/cjs/training.d.ts.map +1 -0
  60. package/dist/cjs/training.js +610 -0
  61. package/dist/cjs/types.d.ts +696 -0
  62. package/dist/cjs/types.d.ts.map +1 -0
  63. package/dist/cjs/types.js +6 -0
  64. package/dist/esm/benchmarks/embedding-benchmark.d.ts +102 -0
  65. package/dist/esm/benchmarks/embedding-benchmark.d.ts.map +1 -0
  66. package/dist/esm/benchmarks/embedding-benchmark.js +427 -0
  67. package/dist/esm/benchmarks/index.d.ts +34 -0
  68. package/dist/esm/benchmarks/index.d.ts.map +1 -0
  69. package/dist/esm/benchmarks/index.js +110 -0
  70. package/dist/esm/benchmarks/model-comparison.d.ts +71 -0
  71. package/dist/esm/benchmarks/model-comparison.d.ts.map +1 -0
  72. package/dist/esm/benchmarks/model-comparison.js +434 -0
  73. package/dist/esm/benchmarks/routing-benchmark.d.ts +70 -0
  74. package/dist/esm/benchmarks/routing-benchmark.d.ts.map +1 -0
  75. package/dist/esm/benchmarks/routing-benchmark.js +283 -0
  76. package/dist/esm/contrastive.d.ts +229 -0
  77. package/dist/esm/contrastive.d.ts.map +1 -0
  78. package/dist/esm/contrastive.js +578 -0
  79. package/dist/esm/engine.d.ts +160 -0
  80. package/dist/esm/engine.d.ts.map +1 -0
  81. package/dist/esm/engine.js +548 -0
  82. package/dist/esm/export.d.ts +182 -0
  83. package/dist/esm/export.d.ts.map +1 -0
  84. package/dist/esm/export.js +414 -0
  85. package/dist/esm/federated.d.ts +233 -0
  86. package/dist/esm/federated.d.ts.map +1 -0
  87. package/dist/esm/federated.js +520 -0
  88. package/dist/esm/index.d.ts +68 -0
  89. package/dist/esm/index.d.ts.map +1 -0
  90. package/dist/esm/index.js +84 -0
  91. package/dist/esm/intelligence.d.ts +136 -0
  92. package/dist/esm/intelligence.d.ts.map +1 -0
  93. package/dist/esm/intelligence.js +188 -0
  94. package/dist/esm/lora.d.ts +251 -0
  95. package/dist/esm/lora.d.ts.map +1 -0
  96. package/dist/esm/lora.js +504 -0
  97. package/dist/esm/models.d.ts +129 -0
  98. package/dist/esm/models.d.ts.map +1 -0
  99. package/dist/esm/models.js +282 -0
  100. package/dist/esm/native.d.ts +140 -0
  101. package/dist/esm/native.d.ts.map +1 -0
  102. package/dist/esm/native.js +100 -0
  103. package/dist/esm/session.d.ts +80 -0
  104. package/dist/esm/session.d.ts.map +1 -0
  105. package/dist/esm/session.js +199 -0
  106. package/dist/esm/simd.d.ts +90 -0
  107. package/dist/esm/simd.d.ts.map +1 -0
  108. package/dist/esm/simd.js +205 -0
  109. package/dist/esm/sona.d.ts +223 -0
  110. package/dist/esm/sona.d.ts.map +1 -0
  111. package/dist/esm/sona.js +556 -0
  112. package/dist/esm/streaming.d.ts +55 -0
  113. package/dist/esm/streaming.d.ts.map +1 -0
  114. package/dist/esm/streaming.js +126 -0
  115. package/dist/esm/training.d.ts +312 -0
  116. package/dist/esm/training.d.ts.map +1 -0
  117. package/dist/esm/training.js +603 -0
  118. package/dist/esm/types.d.ts +696 -0
  119. package/dist/esm/types.d.ts.map +1 -0
  120. package/dist/esm/types.js +5 -0
  121. package/gpu/_load.cjs +38 -0
  122. package/gpu/_load.mjs +33 -0
  123. package/gpu/adapters.cjs +3 -0
  124. package/gpu/adapters.mjs +5 -0
  125. package/gpu/engine.cjs +3 -0
  126. package/gpu/engine.mjs +5 -0
  127. package/gpu/fixture.cjs +3 -0
  128. package/gpu/fixture.mjs +5 -0
  129. package/gpu/gpu-host.cjs +3 -0
  130. package/gpu/gpu-host.mjs +5 -0
  131. package/gpu/gpu-loop.cjs +3 -0
  132. package/gpu/gpu-loop.mjs +5 -0
  133. package/gpu/gpu-spool-adapter.cjs +3 -0
  134. package/gpu/gpu-spool-adapter.mjs +5 -0
  135. package/gpu/vast-host-adapter.cjs +3 -0
  136. package/gpu/vast-host-adapter.mjs +5 -0
  137. package/package.json +159 -49
  138. package/scripts/ensemble-model-compare.js +287 -0
  139. package/scripts/fix-esm-extensions.mjs +220 -0
  140. package/scripts/huggingface/README.md +537 -0
  141. package/scripts/huggingface/publish.sh +112 -0
  142. package/scripts/hybrid-model-compare.js +373 -0
  143. package/scripts/improved-model-compare.js +288 -0
  144. package/scripts/optimized-model-compare.js +364 -0
  145. package/scripts/real-model-compare.js +280 -0
  146. package/scripts/smoke-esm.mjs +82 -0
  147. package/scripts/training/agentic-flow-capabilities.json +329 -0
  148. package/scripts/training/claude-code-synth.js +505 -0
  149. package/scripts/training/claude-flow-capabilities.json +1322 -0
  150. package/scripts/training/claude-hard-negatives.js +222 -0
  151. package/scripts/training/contrastive-finetune.js +517 -0
  152. package/scripts/training/ecosystem-triplets.jsonl +1467 -0
  153. package/scripts/training/generate-ecosystem-triplets.js +1324 -0
  154. package/scripts/training/routing-dataset.js +634 -0
  155. package/scripts/training/ruvector-capabilities.json +798 -0
  156. package/scripts/training/validate-ecosystem.js +381 -0
  157. package/scripts/training/validation-results.json +23 -0
  158. package/GPU-LOOP.md +0 -229
  159. package/LICENSE +0 -201
  160. package/NOTICE +0 -9
  161. package/examples/gpu-loop.example.json +0 -26
  162. package/src/adapters-cli.mjs +0 -23
  163. package/src/adapters.mjs +0 -91
  164. package/src/cli.mjs +0 -30
  165. package/src/engine.mjs +0 -238
  166. package/src/fixture.mjs +0 -38
  167. package/src/gpu-host.mjs +0 -47
  168. package/src/gpu-loop-cli.mjs +0 -67
  169. package/src/gpu-loop.mjs +0 -184
  170. package/src/gpu-spool-adapter.mjs +0 -80
  171. package/src/vast-host-adapter.mjs +0 -274
package/bin/cli.js ADDED
@@ -0,0 +1,1066 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * RuvLLM CLI - Self-learning LLM orchestration
4
+ *
5
+ * Usage:
6
+ * ruvllm query "What is machine learning?" --model ./model.gguf
7
+ * ruvllm generate "Write a haiku about AI" --model ./model.gguf
8
+ * ruvllm memory add "Important context"
9
+ * ruvllm memory search "context"
10
+ * ruvllm models list
11
+ * ruvllm models download claude-code
12
+ * ruvllm stats
13
+ * ruvllm benchmark
14
+ */
15
+
16
+ // `ruvllm gpu ...` delegates to @ruvector/ruvllm-gpu-loop before dist/ (and the
17
+ // native binding) is loaded, so it works even where the runtime cannot load.
18
+ if (process.argv[2] === 'gpu') {
19
+ require('./gpu-delegate.js').runGpuCommand(process.argv.slice(3));
20
+ return;
21
+ }
22
+
23
+ const { RuvLLM, SimdOps, version, hasSimdSupport, ModelDownloader, listModels, getModelInfo, RUVLTRA_MODELS, getDefaultModelsDir, runRoutingBenchmark, formatRoutingResults, baselineKeywordRouter, runEmbeddingBenchmark, formatEmbeddingResults, runFullBenchmark, formatFullResults, ROUTING_TEST_CASES, runFullComparison, formatComparisonResults, ContrastiveTrainer, AGENT_TRAINING_DATA, generateTrainingDataset, generateContrastivePairs, getDatasetStats, tripletLoss, cosineSimilarity } = require('../dist/cjs/index.js');
24
+
25
+ const args = process.argv.slice(2);
26
+ const command = args[0];
27
+
28
+ // Parse CLI arguments
29
+ function parseArgs(args) {
30
+ const result = { flags: {}, positional: [] };
31
+ for (let i = 0; i < args.length; i++) {
32
+ const arg = args[i];
33
+ if (arg.startsWith('--')) {
34
+ const key = arg.slice(2);
35
+ const nextArg = args[i + 1];
36
+ if (nextArg && !nextArg.startsWith('--')) {
37
+ result.flags[key] = nextArg;
38
+ i++;
39
+ } else {
40
+ result.flags[key] = true;
41
+ }
42
+ } else if (!result.command) {
43
+ result.command = arg;
44
+ } else {
45
+ result.positional.push(arg);
46
+ }
47
+ }
48
+ return result;
49
+ }
50
+
51
+ // Format output
52
+ function formatJson(obj) {
53
+ return JSON.stringify(obj, null, 2);
54
+ }
55
+
56
+ function formatTable(data) {
57
+ const maxKeyLen = Math.max(...Object.keys(data).map(k => k.length));
58
+ return Object.entries(data)
59
+ .map(([k, v]) => ` ${k.padEnd(maxKeyLen)} : ${v}`)
60
+ .join('\n');
61
+ }
62
+
63
+ // Commands
64
+ function generationConfig(flags) {
65
+ const config = {};
66
+ if (flags.temperature) config.temperature = parseFloat(flags.temperature);
67
+ if (flags['max-tokens']) config.maxTokens = parseInt(flags['max-tokens']);
68
+ if (flags['top-p']) config.topP = parseFloat(flags['top-p']);
69
+ if (flags['top-k']) config.topK = parseInt(flags['top-k']);
70
+ return config;
71
+ }
72
+
73
+ // query/generate need a model: without --model they fail with
74
+ // RUVLLM_NO_LANGUAGE_MODEL (and the CLI exits 1).
75
+ function loadModelFromFlags(llm, flags) {
76
+ if (!flags.model || flags.model === true) return;
77
+ const options = {};
78
+ if (flags['max-context']) options.maxContext = parseInt(flags['max-context']);
79
+ const info = llm.loadModel(flags.model, options);
80
+ if (!flags.json) console.error(`Loaded ${info.name} (${info.architecture}, ${info.quantization ?? 'quantization unknown'})`);
81
+ }
82
+
83
+ async function runQuery(llm, text, flags) {
84
+ loadModelFromFlags(llm, flags);
85
+ const response = llm.query(text, generationConfig(flags));
86
+
87
+ if (flags.json) {
88
+ console.log(formatJson(response));
89
+ } else {
90
+ console.log('\n' + response.text);
91
+ console.log(`\n--- Route: ${response.model} | Confidence: ${(response.confidence * 100).toFixed(1)}% | Tokens: ${response.promptTokens}+${response.completionTokens} (${response.finishReason}) | Latency: ${response.latencyMs.toFixed(2)}ms ---`);
92
+ }
93
+ }
94
+
95
+ async function runGenerate(llm, prompt, flags) {
96
+ loadModelFromFlags(llm, flags);
97
+ const config = generationConfig(flags);
98
+
99
+ if (flags.json) {
100
+ console.log(formatJson(llm.generateDetailed(prompt, config)));
101
+ } else {
102
+ console.log(llm.generate(prompt, config));
103
+ }
104
+ }
105
+
106
+ async function runMemoryAdd(llm, content, flags) {
107
+ const metadata = flags.metadata ? JSON.parse(flags.metadata) : undefined;
108
+ const id = llm.addMemory(content, metadata);
109
+ console.log(`Added memory with ID: ${id}`);
110
+ }
111
+
112
+ async function runMemorySearch(llm, query, flags) {
113
+ const k = flags.k ? parseInt(flags.k) : 10;
114
+ const results = llm.searchMemory(query, k);
115
+
116
+ if (flags.json) {
117
+ console.log(formatJson(results));
118
+ } else {
119
+ if (results.length === 0) {
120
+ console.log('No results found.');
121
+ return;
122
+ }
123
+ results.forEach((r, i) => {
124
+ console.log(`\n[${i + 1}] Score: ${r.score.toFixed(4)} | ID: ${r.id}`);
125
+ console.log(` ${r.content.slice(0, 100)}${r.content.length > 100 ? '...' : ''}`);
126
+ });
127
+ }
128
+ }
129
+
130
+ async function runStats(llm, flags) {
131
+ let stats;
132
+ try {
133
+ stats = llm.stats();
134
+ } catch (e) {
135
+ stats = null;
136
+ }
137
+
138
+ if (!stats) {
139
+ if (flags.json) {
140
+ console.log(formatJson({ error: 'No inference data available' }));
141
+ } else {
142
+ console.log('\nNo inference data available. Run some queries first.');
143
+ }
144
+ return;
145
+ }
146
+
147
+ if (flags.json) {
148
+ console.log(formatJson(stats));
149
+ } else {
150
+ console.log('\nRuvLLM Statistics:');
151
+ console.log(formatTable({
152
+ 'Total Queries': stats.totalQueries ?? 0,
153
+ 'Memory Nodes': stats.memoryNodes ?? 0,
154
+ 'Patterns Learned': stats.patternsLearned ?? 0,
155
+ 'Avg Latency': `${(stats.avgLatencyMs ?? 0).toFixed(2)}ms`,
156
+ 'Cache Hit Rate': `${((stats.cacheHitRate ?? 0) * 100).toFixed(1)}%`,
157
+ 'Router Accuracy': `${((stats.routerAccuracy ?? 0) * 100).toFixed(1)}%`,
158
+ }));
159
+ }
160
+ }
161
+
162
+ async function runRoute(llm, text, flags) {
163
+ const decision = llm.route(text);
164
+
165
+ if (flags.json) {
166
+ console.log(formatJson(decision));
167
+ } else {
168
+ console.log('\nRouting Decision:');
169
+ console.log(formatTable({
170
+ 'Model': decision.model,
171
+ 'Context Size': decision.contextSize,
172
+ 'Temperature': decision.temperature.toFixed(2),
173
+ 'Top-P': decision.topP.toFixed(2),
174
+ 'Confidence': `${(decision.confidence * 100).toFixed(1)}%`,
175
+ }));
176
+ }
177
+ }
178
+
179
+ async function runEmbed(llm, text, flags) {
180
+ const embedding = llm.embed(text);
181
+
182
+ if (flags.json) {
183
+ console.log(formatJson({ embedding, dimensions: embedding.length }));
184
+ } else {
185
+ console.log(`Embedding (${embedding.length} dimensions):`);
186
+ console.log(` First 10: [${embedding.slice(0, 10).map(x => x.toFixed(4)).join(', ')}...]`);
187
+ console.log(` Norm: ${Math.sqrt(embedding.reduce((s, x) => s + x * x, 0)).toFixed(4)}`);
188
+ }
189
+ }
190
+
191
+ async function runSimilarity(llm, text1, text2, flags) {
192
+ const score = llm.similarity(text1, text2);
193
+
194
+ if (flags.json) {
195
+ console.log(formatJson({ text1, text2, similarity: score }));
196
+ } else {
197
+ console.log(`Similarity: ${(score * 100).toFixed(2)}%`);
198
+ }
199
+ }
200
+
201
+ async function runBenchmark(flags) {
202
+ const simd = new SimdOps();
203
+ const dims = flags.dims ? parseInt(flags.dims) : 768;
204
+ const iterations = flags.iterations ? parseInt(flags.iterations) : 1000;
205
+
206
+ // Generate test vectors
207
+ const a = Array.from({ length: dims }, () => Math.random());
208
+ const b = Array.from({ length: dims }, () => Math.random());
209
+
210
+ console.log(`\nBenchmark: ${dims} dimensions, ${iterations} iterations`);
211
+ console.log(`SIMD: ${simd.isNative() ? 'Native' : 'JavaScript fallback'}`);
212
+ console.log(`Capabilities: ${simd.capabilities().join(', ')}`);
213
+ console.log('');
214
+
215
+ // Dot product benchmark
216
+ let start = Date.now();
217
+ for (let i = 0; i < iterations; i++) {
218
+ simd.dotProduct(a, b);
219
+ }
220
+ let elapsed = Date.now() - start;
221
+ console.log(`Dot Product: ${elapsed}ms (${(iterations / elapsed * 1000).toFixed(0)} ops/sec)`);
222
+
223
+ // Cosine similarity benchmark
224
+ start = Date.now();
225
+ for (let i = 0; i < iterations; i++) {
226
+ simd.cosineSimilarity(a, b);
227
+ }
228
+ elapsed = Date.now() - start;
229
+ console.log(`Cosine Similarity: ${elapsed}ms (${(iterations / elapsed * 1000).toFixed(0)} ops/sec)`);
230
+
231
+ // L2 distance benchmark
232
+ start = Date.now();
233
+ for (let i = 0; i < iterations; i++) {
234
+ simd.l2Distance(a, b);
235
+ }
236
+ elapsed = Date.now() - start;
237
+ console.log(`L2 Distance: ${elapsed}ms (${(iterations / elapsed * 1000).toFixed(0)} ops/sec)`);
238
+
239
+ // Softmax benchmark
240
+ start = Date.now();
241
+ for (let i = 0; i < iterations; i++) {
242
+ simd.softmax(a);
243
+ }
244
+ elapsed = Date.now() - start;
245
+ console.log(`Softmax: ${elapsed}ms (${(iterations / elapsed * 1000).toFixed(0)} ops/sec)`);
246
+ }
247
+
248
+ async function runInfo(flags) {
249
+ const llm = new RuvLLM();
250
+
251
+ const info = {
252
+ version: version(),
253
+ native: llm.isNativeLoaded(),
254
+ simd: hasSimdSupport(),
255
+ capabilities: llm.simdCapabilities(),
256
+ platform: process.platform,
257
+ arch: process.arch,
258
+ nodeVersion: process.version,
259
+ };
260
+
261
+ if (flags.json) {
262
+ console.log(formatJson(info));
263
+ } else {
264
+ console.log('\nRuvLLM Info:');
265
+ console.log(formatTable({
266
+ 'Version': info.version,
267
+ 'Native Module': info.native ? 'Loaded' : 'Fallback (JS)',
268
+ 'SIMD Support': info.simd ? 'Yes' : 'No',
269
+ 'Capabilities': info.capabilities.join(', '),
270
+ 'Platform': `${info.platform}-${info.arch}`,
271
+ 'Node.js': info.nodeVersion,
272
+ }));
273
+ }
274
+ }
275
+
276
+ // Model management commands
277
+ async function runModelsList(flags) {
278
+ const downloader = new ModelDownloader();
279
+ const status = downloader.getStatus();
280
+
281
+ if (flags.json) {
282
+ console.log(formatJson(status));
283
+ } else {
284
+ console.log('\n╔══════════════════════════════════════════════════════════════════════════╗');
285
+ console.log('║ RuvLTRA Models ║');
286
+ console.log('║ https://huggingface.co/ruv/ruvltra ║');
287
+ console.log('╠══════════════════════════════════════════════════════════════════════════╣');
288
+ console.log('║ Model │ Size │ Params │ Status │ Use Case ║');
289
+ console.log('╠══════════════════════════════════════════════════════════════════════════╣');
290
+
291
+ for (const { model, downloaded } of status) {
292
+ const statusIcon = downloaded ? '✓ Ready ' : '○ Not DL ';
293
+ const name = model.id.padEnd(12);
294
+ const size = model.size.padEnd(7);
295
+ const params = model.parameters.padEnd(6);
296
+ const useCase = model.useCase.slice(0, 24).padEnd(24);
297
+ console.log(`║ ${name} │ ${size} │ ${params} │ ${statusIcon} │ ${useCase} ║`);
298
+ }
299
+
300
+ console.log('╚══════════════════════════════════════════════════════════════════════════╝');
301
+ console.log(`\nModels directory: ${getDefaultModelsDir()}`);
302
+ console.log('\nDownload with: ruvllm models download <model-id>');
303
+ console.log(' Examples: ruvllm models download claude-code');
304
+ console.log(' ruvllm models download --all');
305
+ }
306
+ }
307
+
308
+ async function runModelsDownload(modelId, flags) {
309
+ const downloader = new ModelDownloader();
310
+
311
+ if (flags.all) {
312
+ console.log('\nDownloading all RuvLTRA models...\n');
313
+ const models = listModels();
314
+
315
+ for (const model of models) {
316
+ console.log(`\n[${model.id}] ${model.name} (${model.size})`);
317
+
318
+ if (downloader.isDownloaded(model.id) && !flags.force) {
319
+ console.log(' Already downloaded, skipping (use --force to re-download)');
320
+ continue;
321
+ }
322
+
323
+ try {
324
+ const lastPercent = { value: -1 };
325
+ const path = await downloader.download(model.id, {
326
+ force: flags.force,
327
+ onProgress: (p) => {
328
+ const percent = Math.floor(p.percent / 5) * 5; // Round to 5%
329
+ if (percent !== lastPercent.value) {
330
+ const bar = '█'.repeat(percent / 5) + '░'.repeat(20 - percent / 5);
331
+ const speed = (p.speedBps / 1024 / 1024).toFixed(1);
332
+ const eta = p.etaSeconds < 60
333
+ ? `${Math.ceil(p.etaSeconds)}s`
334
+ : `${Math.ceil(p.etaSeconds / 60)}m`;
335
+ process.stdout.write(`\r [${bar}] ${p.percent}% | ${speed} MB/s | ETA: ${eta} `);
336
+ lastPercent.value = percent;
337
+ }
338
+ },
339
+ });
340
+ console.log(`\n ✓ Downloaded to: ${path}`);
341
+ } catch (error) {
342
+ console.error(`\n ✗ Failed: ${error.message}`);
343
+ }
344
+ }
345
+
346
+ console.log('\n\nDownload complete!');
347
+ return;
348
+ }
349
+
350
+ if (!modelId) {
351
+ console.error('Error: model ID required. Use --all to download all models.');
352
+ console.error('\nAvailable models:');
353
+ listModels().forEach(m => console.error(` - ${m.id}: ${m.name} (${m.size})`));
354
+ process.exit(1);
355
+ }
356
+
357
+ const model = getModelInfo(modelId);
358
+ if (!model) {
359
+ console.error(`Error: Unknown model "${modelId}"`);
360
+ console.error('\nAvailable models:');
361
+ listModels().forEach(m => console.error(` - ${m.id}: ${m.name} (${m.size})`));
362
+ process.exit(1);
363
+ }
364
+
365
+ console.log(`\nDownloading ${model.name} (${model.size})...`);
366
+ console.log(`From: ${model.url}\n`);
367
+
368
+ if (downloader.isDownloaded(modelId) && !flags.force) {
369
+ const path = downloader.getModelPath(modelId);
370
+ console.log(`Model already downloaded at: ${path}`);
371
+ console.log('Use --force to re-download.');
372
+ return;
373
+ }
374
+
375
+ const lastPercent = { value: -1 };
376
+ try {
377
+ const path = await downloader.download(modelId, {
378
+ force: flags.force,
379
+ onProgress: (p) => {
380
+ const percent = Math.floor(p.percent / 2) * 2; // Round to 2%
381
+ if (percent !== lastPercent.value) {
382
+ const bar = '█'.repeat(Math.floor(percent / 5)) + '░'.repeat(20 - Math.floor(percent / 5));
383
+ const downloaded = (p.downloaded / 1024 / 1024).toFixed(1);
384
+ const total = (p.total / 1024 / 1024).toFixed(1);
385
+ const speed = (p.speedBps / 1024 / 1024).toFixed(1);
386
+ const eta = p.etaSeconds < 60
387
+ ? `${Math.ceil(p.etaSeconds)}s`
388
+ : `${Math.ceil(p.etaSeconds / 60)}m`;
389
+ process.stdout.write(`\r[${bar}] ${p.percent}% | ${downloaded}/${total} MB | ${speed} MB/s | ETA: ${eta} `);
390
+ lastPercent.value = percent;
391
+ }
392
+ },
393
+ });
394
+ console.log(`\n\n✓ Downloaded to: ${path}`);
395
+ console.log(`\nModel ready to use!`);
396
+ console.log(` Context length: ${model.contextLength} tokens`);
397
+ console.log(` Quantization: ${model.quantization}`);
398
+ } catch (error) {
399
+ console.error(`\n\n✗ Download failed: ${error.message}`);
400
+ process.exit(1);
401
+ }
402
+ }
403
+
404
+ async function runModelsStatus(flags) {
405
+ const downloader = new ModelDownloader();
406
+ const status = downloader.getStatus();
407
+
408
+ if (flags.json) {
409
+ console.log(formatJson(status.map(s => ({
410
+ id: s.model.id,
411
+ name: s.model.name,
412
+ downloaded: s.downloaded,
413
+ path: s.path,
414
+ size: s.model.size,
415
+ }))));
416
+ } else {
417
+ console.log('\nModel Status:');
418
+ console.log(`Directory: ${getDefaultModelsDir()}\n`);
419
+
420
+ for (const { model, downloaded, path } of status) {
421
+ const icon = downloaded ? '✓' : '○';
422
+ const status = downloaded ? 'Ready' : 'Not downloaded';
423
+ console.log(` ${icon} ${model.name.padEnd(25)} ${status.padEnd(15)} ${model.size}`);
424
+ if (downloaded) {
425
+ console.log(` Path: ${path}`);
426
+ }
427
+ }
428
+ }
429
+ }
430
+
431
+ async function runModelsDelete(modelId, flags) {
432
+ const downloader = new ModelDownloader();
433
+
434
+ if (flags.all) {
435
+ const count = downloader.deleteAll();
436
+ console.log(`Deleted ${count} model(s).`);
437
+ return;
438
+ }
439
+
440
+ if (!modelId) {
441
+ console.error('Error: model ID required. Use --all to delete all models.');
442
+ process.exit(1);
443
+ }
444
+
445
+ if (downloader.delete(modelId)) {
446
+ console.log(`Deleted model: ${modelId}`);
447
+ } else {
448
+ console.log(`Model not found or not downloaded: ${modelId}`);
449
+ }
450
+ }
451
+
452
+ // Benchmark commands for Claude Code use cases
453
+ async function runBenchmarkRouting(flags) {
454
+ console.log('\nRunning Routing Benchmark...');
455
+ console.log(`Testing ${ROUTING_TEST_CASES.length} task routing scenarios\n`);
456
+
457
+ const llm = new RuvLLM({ embeddingDim: 768, learningEnabled: false });
458
+
459
+ // Router function using the model
460
+ const modelRouter = (task) => {
461
+ try {
462
+ const decision = llm.route(task);
463
+ return { agent: decision.model, confidence: decision.confidence };
464
+ } catch (e) {
465
+ // Fallback to keyword router if model not available
466
+ return baselineKeywordRouter(task);
467
+ }
468
+ };
469
+
470
+ // Run with baseline (keyword) router
471
+ console.log('Baseline (keyword matching):');
472
+ const baselineResults = runRoutingBenchmark(baselineKeywordRouter);
473
+
474
+ if (flags.json) {
475
+ console.log(JSON.stringify(baselineResults, null, 2));
476
+ } else {
477
+ console.log(formatRoutingResults(baselineResults));
478
+ }
479
+
480
+ // Try model router if native is available
481
+ if (llm.isNativeLoaded() && !flags['baseline-only']) {
482
+ console.log('\nModel Router (RuvLTRA):');
483
+ const modelResults = runRoutingBenchmark(modelRouter);
484
+ if (flags.json) {
485
+ console.log(JSON.stringify(modelResults, null, 2));
486
+ } else {
487
+ console.log(formatRoutingResults(modelResults));
488
+ }
489
+
490
+ // Comparison
491
+ const improvement = modelResults.accuracy - baselineResults.accuracy;
492
+ console.log(`\nComparison: Model ${improvement >= 0 ? '+' : ''}${(improvement * 100).toFixed(1)}% vs baseline`);
493
+ }
494
+ }
495
+
496
+ async function runBenchmarkEmbedding(flags) {
497
+ console.log('\nRunning Embedding Benchmark...');
498
+ console.log('Testing similarity detection, clustering, and search quality\n');
499
+
500
+ const llm = new RuvLLM({ embeddingDim: 768, learningEnabled: false });
501
+ const simd = new SimdOps();
502
+
503
+ // Embedder function
504
+ const embedder = (text) => {
505
+ try {
506
+ return llm.embed(text);
507
+ } catch (e) {
508
+ // Fallback: simple hash-based embedding for testing
509
+ const hash = text.split('').reduce((h, c) => ((h << 5) - h + c.charCodeAt(0)) | 0, 0);
510
+ return Array.from({ length: 768 }, (_, i) => Math.sin(hash + i) * 0.5);
511
+ }
512
+ };
513
+
514
+ // Similarity function
515
+ const similarity = (a, b) => {
516
+ try {
517
+ return simd.cosineSimilarity(a, b);
518
+ } catch (e) {
519
+ // Fallback cosine similarity
520
+ let dot = 0, normA = 0, normB = 0;
521
+ for (let i = 0; i < a.length; i++) {
522
+ dot += a[i] * b[i];
523
+ normA += a[i] * a[i];
524
+ normB += b[i] * b[i];
525
+ }
526
+ return dot / (Math.sqrt(normA) * Math.sqrt(normB));
527
+ }
528
+ };
529
+
530
+ const results = runEmbeddingBenchmark(embedder, similarity);
531
+
532
+ if (flags.json) {
533
+ console.log(JSON.stringify(results, null, 2));
534
+ } else {
535
+ console.log(formatEmbeddingResults(results));
536
+ }
537
+ }
538
+
539
+ async function runBenchmarkCompare(flags) {
540
+ try {
541
+ const results = await runFullComparison();
542
+
543
+ if (flags.json) {
544
+ console.log(JSON.stringify(results, null, 2));
545
+ } else {
546
+ console.log(formatComparisonResults(results));
547
+ }
548
+ } catch (error) {
549
+ console.error('Comparison failed:', error.message);
550
+ process.exit(1);
551
+ }
552
+ }
553
+
554
+ // Training commands
555
+ async function runTrainContrastive(flags) {
556
+ console.log('\n╔═══════════════════════════════════════════════════════════════════════════╗');
557
+ console.log('║ RuvLTRA Contrastive Fine-tuning Pipeline ║');
558
+ console.log('╚═══════════════════════════════════════════════════════════════════════════╝\n');
559
+
560
+ const stats = getDatasetStats();
561
+ const outputPath = flags.output || './training-output';
562
+
563
+ console.log(`Training examples: ${stats.totalExamples}`);
564
+ console.log(`Contrastive pairs: ${stats.contrastivePairs}`);
565
+ console.log(`Agent types: ${stats.agentTypes}`);
566
+ console.log(`Output: ${outputPath}\n`);
567
+
568
+ // Create trainer
569
+ const trainer = new ContrastiveTrainer({
570
+ epochs: parseInt(flags.epochs) || 10,
571
+ batchSize: parseInt(flags['batch-size']) || 16,
572
+ learningRate: parseFloat(flags.lr) || 0.0001,
573
+ margin: parseFloat(flags.margin) || 0.5,
574
+ outputPath,
575
+ });
576
+
577
+ // Get embeddings for agents
578
+ const llm = new RuvLLM({ embeddingDim: 768, learningEnabled: false });
579
+ const agentEmbeddings = {};
580
+
581
+ console.log('Computing agent embeddings...');
582
+ for (const [agent, data] of Object.entries(AGENT_TRAINING_DATA)) {
583
+ process.stdout.write(` ${agent}... `);
584
+ try {
585
+ const emb = llm.embed(data.description);
586
+ agentEmbeddings[agent] = emb;
587
+ trainer.addAgentEmbedding(agent, emb);
588
+ console.log('done');
589
+ } catch (e) {
590
+ // Fallback embedding
591
+ const hash = data.description.split('').reduce((h, c) => ((h << 5) - h + c.charCodeAt(0)) | 0, 0);
592
+ const emb = Array.from({ length: 768 }, (_, i) => Math.sin(hash + i) * 0.5);
593
+ agentEmbeddings[agent] = emb;
594
+ trainer.addAgentEmbedding(agent, emb);
595
+ console.log('done (fallback)');
596
+ }
597
+ }
598
+
599
+ // Generate triplets
600
+ console.log('\nGenerating training triplets...');
601
+ const examples = generateTrainingDataset();
602
+ const agents = Object.keys(AGENT_TRAINING_DATA);
603
+
604
+ let tripletCount = 0;
605
+ for (const example of examples.slice(0, 200)) { // Limit for speed
606
+ let taskEmb;
607
+ try {
608
+ taskEmb = llm.embed(example.task);
609
+ } catch (e) {
610
+ const hash = example.task.split('').reduce((h, c) => ((h << 5) - h + c.charCodeAt(0)) | 0, 0);
611
+ taskEmb = Array.from({ length: 768 }, (_, i) => Math.sin(hash + i) * 0.5);
612
+ }
613
+
614
+ const positiveAgent = example.agent;
615
+ const positiveEmb = agentEmbeddings[positiveAgent];
616
+
617
+ // Hard negatives
618
+ const hardNegatives = example.confusing_with
619
+ ? [example.confusing_with]
620
+ : agents.filter(a => a !== positiveAgent).slice(0, 2);
621
+
622
+ for (const negAgent of hardNegatives) {
623
+ const negEmb = agentEmbeddings[negAgent];
624
+ if (negEmb) {
625
+ trainer.addTriplet(
626
+ example.task, taskEmb,
627
+ positiveAgent, positiveEmb,
628
+ negAgent, negEmb,
629
+ !!example.confusing_with
630
+ );
631
+ tripletCount++;
632
+ }
633
+ }
634
+ }
635
+
636
+ console.log(`Created ${tripletCount} triplets\n`);
637
+
638
+ // Train
639
+ console.log('Training...');
640
+ const result = trainer.train();
641
+
642
+ // Export data
643
+ console.log('\nExporting training data...');
644
+ const exportPath = trainer.exportTrainingData();
645
+ const loraConfig = trainer.generateLoRAConfig();
646
+ const scriptPath = trainer.generateTrainingScript();
647
+
648
+ // Summary
649
+ console.log('\n═══════════════════════════════════════════════════════════════════════════');
650
+ console.log(' TRAINING SUMMARY');
651
+ console.log('═══════════════════════════════════════════════════════════════════════════\n');
652
+
653
+ console.log('Training data exported:');
654
+ console.log(` - ${exportPath}/triplets.jsonl (${tripletCount} triplets)`);
655
+ console.log(` - ${exportPath}/triplets.csv (spreadsheet format)`);
656
+ console.log(` - ${exportPath}/embeddings.json (precomputed embeddings)`);
657
+ console.log(` - ${exportPath}/lora_config.json (LoRA configuration)`);
658
+ console.log(` - ${exportPath}/train.sh (training script)\n`);
659
+
660
+ console.log('Training loss (simulated):');
661
+ console.log(` Initial: ${result.initialLoss.toFixed(4)}`);
662
+ console.log(` Final: ${result.finalLoss.toFixed(4)}`);
663
+ console.log(` Improvement: ${result.improvement.toFixed(1)}%\n`);
664
+
665
+ console.log('To fine-tune on GPU:');
666
+ console.log(` cd ${exportPath}`);
667
+ console.log(' chmod +x train.sh && ./train.sh\n');
668
+ }
669
+
670
+ async function runTrainDataset(flags) {
671
+ console.log('\n╔═══════════════════════════════════════════════════════════════════════════╗');
672
+ console.log('║ RuvLTRA Training Dataset Generator ║');
673
+ console.log('╚═══════════════════════════════════════════════════════════════════════════╝\n');
674
+
675
+ const stats = getDatasetStats();
676
+ const outputPath = flags.output || './data/training';
677
+
678
+ console.log(`Generating training data for ${stats.agentTypes} agent types...\n`);
679
+
680
+ // Generate dataset
681
+ const examples = generateTrainingDataset();
682
+ const pairs = generateContrastivePairs();
683
+
684
+ // Export
685
+ const { existsSync, mkdirSync, writeFileSync } = require('fs');
686
+ const { join } = require('path');
687
+
688
+ if (!existsSync(outputPath)) {
689
+ mkdirSync(outputPath, { recursive: true });
690
+ }
691
+
692
+ // JSONL format
693
+ writeFileSync(
694
+ join(outputPath, 'routing-examples.jsonl'),
695
+ examples.map(e => JSON.stringify(e)).join('\n')
696
+ );
697
+
698
+ // Contrastive pairs
699
+ writeFileSync(
700
+ join(outputPath, 'contrastive-pairs.jsonl'),
701
+ pairs.map(p => JSON.stringify(p)).join('\n')
702
+ );
703
+
704
+ // CSV format
705
+ const csv = [
706
+ 'task,agent,complexity,confusing_with',
707
+ ...examples.map(e => `"${e.task.replace(/"/g, '""')}",${e.agent},${e.complexity || ''},${e.confusing_with || ''}`)
708
+ ].join('\n');
709
+ writeFileSync(join(outputPath, 'routing-examples.csv'), csv);
710
+
711
+ console.log('Export complete:');
712
+ console.log(` - ${join(outputPath, 'routing-examples.jsonl')} (${examples.length} examples)`);
713
+ console.log(` - ${join(outputPath, 'contrastive-pairs.jsonl')} (${pairs.length} pairs)`);
714
+ console.log(` - ${join(outputPath, 'routing-examples.csv')} (spreadsheet format)\n`);
715
+
716
+ console.log('Dataset statistics:');
717
+ console.log(` Total examples: ${stats.totalExamples}`);
718
+ console.log(` Contrastive pairs: ${stats.contrastivePairs}`);
719
+ console.log(` Agent types: ${stats.agentTypes}`);
720
+ console.log(` Agents: ${stats.agents.join(', ')}\n`);
721
+ }
722
+
723
+ async function runTrainStats(flags) {
724
+ const stats = getDatasetStats();
725
+
726
+ if (flags.json) {
727
+ console.log(JSON.stringify(stats, null, 2));
728
+ } else {
729
+ console.log('\nTraining Dataset Statistics:');
730
+ console.log(` Total examples: ${stats.totalExamples}`);
731
+ console.log(` Contrastive pairs: ${stats.contrastivePairs}`);
732
+ console.log(` Agent types: ${stats.agentTypes}`);
733
+ console.log('\nAgents:');
734
+ for (const agent of stats.agents) {
735
+ const data = AGENT_TRAINING_DATA[agent];
736
+ console.log(` - ${agent}: ${data.examples.length} examples, ${data.keywords.length} keywords`);
737
+ }
738
+ }
739
+ }
740
+
741
+ async function runBenchmarkFull(flags) {
742
+ console.log('\n╔═══════════════════════════════════════════════════════════════════════════╗');
743
+ console.log('║ RUVLTRA FULL BENCHMARK SUITE ║');
744
+ console.log('║ Evaluating for Claude Code Use Cases ║');
745
+ console.log('╚═══════════════════════════════════════════════════════════════════════════╝\n');
746
+
747
+ const llm = new RuvLLM({ embeddingDim: 768, learningEnabled: false });
748
+ const simd = new SimdOps();
749
+
750
+ const modelName = llm.isNativeLoaded() ? 'RuvLTRA (native)' : 'RuvLTRA (JS fallback)';
751
+
752
+ // Router
753
+ const router = (task) => {
754
+ try {
755
+ const decision = llm.route(task);
756
+ return { agent: decision.model, confidence: decision.confidence };
757
+ } catch (e) {
758
+ return baselineKeywordRouter(task);
759
+ }
760
+ };
761
+
762
+ // Embedder
763
+ const embedder = (text) => {
764
+ try {
765
+ return llm.embed(text);
766
+ } catch (e) {
767
+ const hash = text.split('').reduce((h, c) => ((h << 5) - h + c.charCodeAt(0)) | 0, 0);
768
+ return Array.from({ length: 768 }, (_, i) => Math.sin(hash + i) * 0.5);
769
+ }
770
+ };
771
+
772
+ // Similarity
773
+ const similarity = (a, b) => {
774
+ try {
775
+ return simd.cosineSimilarity(a, b);
776
+ } catch (e) {
777
+ let dot = 0, normA = 0, normB = 0;
778
+ for (let i = 0; i < a.length; i++) {
779
+ dot += a[i] * b[i];
780
+ normA += a[i] * a[i];
781
+ normB += b[i] * b[i];
782
+ }
783
+ return dot / (Math.sqrt(normA) * Math.sqrt(normB));
784
+ }
785
+ };
786
+
787
+ const results = runFullBenchmark(router, embedder, similarity, modelName);
788
+
789
+ if (flags.json) {
790
+ console.log(JSON.stringify(results, null, 2));
791
+ } else {
792
+ console.log(formatFullResults(results));
793
+ }
794
+ }
795
+
796
+ function printHelp() {
797
+ console.log(`
798
+ RuvLLM - Self-learning LLM Orchestration
799
+
800
+ Usage: ruvllm <command> [options]
801
+
802
+ Commands:
803
+ query <text> --model <gguf> Route, then answer with the model (chat template)
804
+ generate <prompt> --model <gguf>
805
+ Continue a raw prompt with the model
806
+ route <text> Get routing decision for query
807
+ memory add <content> Add content to memory
808
+ memory search <query> Search memory for similar content
809
+ embed <text> Get embedding for text
810
+ similarity <t1> <t2> Compute similarity between texts
811
+ stats Show engine statistics
812
+ benchmark Run SIMD performance benchmark
813
+ info Show system information
814
+ serve <gguf> HTTP server: /health, /query, /v1/chat/completions
815
+ (--host 127.0.0.1 --port 3000; needs a loadable model)
816
+ help Show this help message
817
+
818
+ GPU job loop (via @ruvector/ruvllm-gpu-loop, Node 22+):
819
+ gpu run --config <abs> --plan <abs>
820
+ Bounded weight-edit experiment (4.0.x "ruvllm run")
821
+ gpu fixture --dir <abs> --binary <abs>
822
+ CPU fixture experiment (4.0.x "ruvllm fixture")
823
+ gpu loop status|tick|run --config <abs>
824
+ Scheduled GPU job loop (same as ruvllm-gpu-loop)
825
+ gpu adapters export|training-plan|select
826
+ Adapter archives (same as ruvllm-adapters)
827
+
828
+ Model Management:
829
+ models list List available RuvLTRA models
830
+ models download <id> Download a model from HuggingFace
831
+ models download --all Download all available models
832
+ models status Check which models are downloaded
833
+ models delete <id> Delete a downloaded model
834
+
835
+ Claude Code Benchmarks:
836
+ benchmark routing Test agent routing accuracy (100 tasks)
837
+ benchmark embedding Test embedding quality (similarity, search, clustering)
838
+ benchmark full Run complete benchmark suite
839
+ benchmark compare Compare Qwen base vs RuvLTRA Claude Code
840
+ benchmark simd Run SIMD performance benchmark
841
+
842
+ Training & Fine-tuning:
843
+ train contrastive Run contrastive fine-tuning pipeline
844
+ train dataset Generate training dataset (JSONL, CSV)
845
+ train stats Show training dataset statistics
846
+
847
+ Options:
848
+ --json Output as JSON
849
+ --model <path> GGUF file to load for query/generate (required for them;
850
+ needs a native binary with model support, see README)
851
+ --max-context <int> Context window for --model (default: 4096)
852
+ --temperature <float> Sampling temperature (0 = greedy)
853
+ --max-tokens <int> Maximum tokens to generate
854
+ --top-p <float> Nucleus sampling (0.0-1.0)
855
+ --top-k <int> Top-k sampling
856
+ --k <int> Number of results for search
857
+ --metadata <json> Metadata for memory add
858
+ --dims <int> Dimensions for benchmark (default: 768)
859
+ --iterations <int> Iterations for benchmark (default: 1000)
860
+ --force Force re-download even if model exists
861
+ --all Apply to all models (download/delete)
862
+ --output <path> Output directory for training data
863
+ --epochs <int> Number of training epochs (default: 10)
864
+ --batch-size <int> Training batch size (default: 16)
865
+ --lr <float> Learning rate (default: 0.0001)
866
+ --margin <float> Triplet loss margin (default: 0.5)
867
+
868
+ Available Models (from https://huggingface.co/ruv/ruvltra):
869
+ claude-code RuvLTRA Claude Code (398MB) - Claude Code workflows
870
+ small RuvLTRA Small (398MB) - Edge devices, IoT
871
+ medium RuvLTRA Medium (669MB) - General purpose
872
+
873
+ Examples:
874
+ ruvllm query "What is machine learning?" --model ./qwen2.5-0.5b-instruct-q4_k_m.gguf
875
+ ruvllm generate "Once upon a time" --model ./model.gguf --max-tokens 64 --json
876
+ ruvllm memory add "Important context" --metadata '{"type":"note"}'
877
+ ruvllm memory search "context" --k 5
878
+ ruvllm similarity "hello world" "hi there"
879
+ ruvllm benchmark --dims 1024 --iterations 5000
880
+ ruvllm serve ./qwen2.5-0.5b-instruct-q4_k_m.gguf --port 3000
881
+ ruvllm gpu loop status --config /abs/private/gpu-loop.json
882
+
883
+ # Model management
884
+ ruvllm models list
885
+ ruvllm models download claude-code
886
+ ruvllm models download --all
887
+ ruvllm models status
888
+
889
+ # Claude Code benchmarks
890
+ ruvllm benchmark routing # Test task routing accuracy
891
+ ruvllm benchmark embedding # Test embedding quality
892
+ ruvllm benchmark full # Run complete benchmark suite
893
+
894
+ # Training & fine-tuning
895
+ ruvllm train contrastive # Run contrastive fine-tuning
896
+ ruvllm train dataset # Generate training dataset
897
+ ruvllm train stats # Show dataset statistics
898
+ ruvllm train contrastive --epochs 20 --output ./my-training
899
+
900
+ Learn more: https://github.com/ruvnet/ruvector
901
+ `);
902
+ }
903
+
904
+ // Main
905
+ async function main() {
906
+ const parsed = parseArgs(args);
907
+ const { command, positional, flags } = parsed;
908
+
909
+ if (!command || command === 'help' || flags.help) {
910
+ printHelp();
911
+ return;
912
+ }
913
+
914
+ // Create engine for commands that need it
915
+ const llm = new RuvLLM({
916
+ embeddingDim: flags.dim ? parseInt(flags.dim) : 768,
917
+ learningEnabled: flags['no-learning'] ? false : true,
918
+ });
919
+
920
+ try {
921
+ switch (command) {
922
+ case 'query':
923
+ if (!positional[0]) {
924
+ console.error('Error: query text required');
925
+ process.exit(1);
926
+ }
927
+ await runQuery(llm, positional[0], flags);
928
+ break;
929
+
930
+ case 'generate':
931
+ if (!positional[0]) {
932
+ console.error('Error: prompt required');
933
+ process.exit(1);
934
+ }
935
+ await runGenerate(llm, positional[0], flags);
936
+ break;
937
+
938
+ case 'route':
939
+ if (!positional[0]) {
940
+ console.error('Error: text required');
941
+ process.exit(1);
942
+ }
943
+ await runRoute(llm, positional[0], flags);
944
+ break;
945
+
946
+ case 'memory':
947
+ const subcommand = positional[0];
948
+ if (subcommand === 'add') {
949
+ if (!positional[1]) {
950
+ console.error('Error: content required');
951
+ process.exit(1);
952
+ }
953
+ await runMemoryAdd(llm, positional[1], flags);
954
+ } else if (subcommand === 'search') {
955
+ if (!positional[1]) {
956
+ console.error('Error: query required');
957
+ process.exit(1);
958
+ }
959
+ await runMemorySearch(llm, positional[1], flags);
960
+ } else {
961
+ console.error('Error: unknown memory subcommand. Use "add" or "search"');
962
+ process.exit(1);
963
+ }
964
+ break;
965
+
966
+ case 'embed':
967
+ if (!positional[0]) {
968
+ console.error('Error: text required');
969
+ process.exit(1);
970
+ }
971
+ await runEmbed(llm, positional[0], flags);
972
+ break;
973
+
974
+ case 'similarity':
975
+ if (!positional[0] || !positional[1]) {
976
+ console.error('Error: two texts required');
977
+ process.exit(1);
978
+ }
979
+ await runSimilarity(llm, positional[0], positional[1], flags);
980
+ break;
981
+
982
+ case 'stats':
983
+ await runStats(llm, flags);
984
+ break;
985
+
986
+ case 'benchmark':
987
+ const benchSubcmd = positional[0];
988
+ if (benchSubcmd === 'routing') {
989
+ await runBenchmarkRouting(flags);
990
+ } else if (benchSubcmd === 'embedding' || benchSubcmd === 'embeddings') {
991
+ await runBenchmarkEmbedding(flags);
992
+ } else if (benchSubcmd === 'full' || benchSubcmd === 'all') {
993
+ await runBenchmarkFull(flags);
994
+ } else if (benchSubcmd === 'compare') {
995
+ await runBenchmarkCompare(flags);
996
+ } else if (benchSubcmd === 'simd' || !benchSubcmd) {
997
+ // Default to SIMD benchmark for backwards compatibility
998
+ await runBenchmark(flags);
999
+ } else {
1000
+ console.error(`Unknown benchmark type: ${benchSubcmd}`);
1001
+ console.error('Available: routing, embedding, full, simd, compare');
1002
+ process.exit(1);
1003
+ }
1004
+ break;
1005
+
1006
+ case 'info':
1007
+ await runInfo(flags);
1008
+ break;
1009
+
1010
+ case 'serve':
1011
+ // Keeps running until SIGINT/SIGTERM; exits 1 if no model can be loaded.
1012
+ require('./serve.js').runServe({ RuvLLM, version: require('../package.json').version }, positional, flags);
1013
+ return;
1014
+
1015
+ case 'models':
1016
+ const modelsSubcmd = positional[0];
1017
+ if (!modelsSubcmd || modelsSubcmd === 'list') {
1018
+ await runModelsList(flags);
1019
+ } else if (modelsSubcmd === 'download') {
1020
+ await runModelsDownload(positional[1], flags);
1021
+ } else if (modelsSubcmd === 'status') {
1022
+ await runModelsStatus(flags);
1023
+ } else if (modelsSubcmd === 'delete' || modelsSubcmd === 'remove') {
1024
+ await runModelsDelete(positional[1], flags);
1025
+ } else {
1026
+ // Treat subcommand as model ID for download
1027
+ await runModelsDownload(modelsSubcmd, flags);
1028
+ }
1029
+ break;
1030
+
1031
+ case 'train':
1032
+ const trainSubcmd = positional[0];
1033
+ if (!trainSubcmd || trainSubcmd === 'contrastive') {
1034
+ await runTrainContrastive(flags);
1035
+ } else if (trainSubcmd === 'dataset') {
1036
+ await runTrainDataset(flags);
1037
+ } else if (trainSubcmd === 'stats') {
1038
+ await runTrainStats(flags);
1039
+ } else {
1040
+ console.error(`Unknown train subcommand: ${trainSubcmd}`);
1041
+ console.error('Available: contrastive, dataset, stats');
1042
+ process.exit(1);
1043
+ }
1044
+ break;
1045
+
1046
+ default:
1047
+ console.error(`Unknown command: ${command}`);
1048
+ console.error('Run "ruvllm help" for usage information.');
1049
+ process.exit(1);
1050
+ }
1051
+ } catch (error) {
1052
+ console.error('Error:', error.message);
1053
+ if (error.code === 'RUVLLM_NO_LANGUAGE_MODEL') {
1054
+ console.error('Hint: pass --model <path to a GGUF file>');
1055
+ }
1056
+ if (flags.verbose) {
1057
+ console.error(error.stack);
1058
+ }
1059
+ process.exit(1);
1060
+ }
1061
+ }
1062
+
1063
+ main().catch(err => {
1064
+ console.error('Fatal error:', err);
1065
+ process.exit(1);
1066
+ });